Skip to content

Commit e974ed3

Browse files
PIG208gnprice
authored andcommitted
api: Add route setTypingStatus.
The three legacy cases will need to be dropped separately at different server versions. The tests will fail accordingly as we do that. Signed-off-by: Zixuan James Li <[email protected]>
1 parent 7fe8048 commit e974ed3

File tree

4 files changed

+135
-2
lines changed

4 files changed

+135
-2
lines changed

lib/api/model/events.dart

+3-1
Original file line numberDiff line numberDiff line change
@@ -1039,7 +1039,9 @@ class TypingEvent extends Event {
10391039
@JsonEnum(fieldRename: FieldRename.snake)
10401040
enum TypingOp {
10411041
start,
1042-
stop
1042+
stop;
1043+
1044+
String toJson() => _$TypingOpEnumMap[this]!;
10431045
}
10441046

10451047
/// A Zulip event of type `reaction`, with op `add` or `remove`.

lib/api/model/events.g.dart

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/api/route/typing.dart

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import '../core.dart';
2+
import '../model/events.dart';
3+
import 'messages.dart';
4+
5+
6+
/// https://zulip.com/api/set-typing-status
7+
Future<void> setTypingStatus(ApiConnection connection, {
8+
required TypingOp op,
9+
required MessageDestination destination,
10+
}) {
11+
switch (destination) {
12+
case StreamDestination():
13+
final supportsTypeChannel = connection.zulipFeatureLevel! >= 248; // TODO(server-9)
14+
final supportsStreamId = connection.zulipFeatureLevel! >= 215; // TODO(server-8)
15+
return connection.post('setTypingStatus', (_) {}, 'typing', {
16+
'op': RawParameter(op.toJson()),
17+
'type': RawParameter(supportsTypeChannel ? 'channel' : 'stream'),
18+
if (supportsStreamId) 'stream_id': destination.streamId
19+
else 'to': [destination.streamId],
20+
'topic': RawParameter(destination.topic),
21+
});
22+
case DmDestination():
23+
final supportsDirect = connection.zulipFeatureLevel! >= 174; // TODO(server-7)
24+
return connection.post('setTypingStatus', (_) {}, 'typing', {
25+
'op': RawParameter(op.toJson()),
26+
'type': RawParameter(supportsDirect ? 'direct' : 'private'),
27+
'to': destination.userIds,
28+
});
29+
}
30+
}

test/api/route/typing_test.dart

+101
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import 'dart:convert';
2+
3+
import 'package:http/http.dart' as http;
4+
import 'package:checks/checks.dart';
5+
import 'package:flutter_test/flutter_test.dart';
6+
import 'package:zulip/api/model/events.dart';
7+
import 'package:zulip/api/route/messages.dart';
8+
import 'package:zulip/api/route/typing.dart';
9+
10+
import '../../stdlib_checks.dart';
11+
import '../fake_api.dart';
12+
13+
void main() {
14+
const streamId = 123;
15+
const topic = 'topic';
16+
const userIds = [101, 102, 103];
17+
18+
Future<void> checkSetTypingStatus(FakeApiConnection connection,
19+
TypingOp op, {
20+
required MessageDestination destination,
21+
required Map<String, String> expectedBodyFields,
22+
}) async {
23+
connection.prepare(json: {});
24+
await setTypingStatus(connection, op: op, destination: destination);
25+
check(connection.lastRequest).isA<http.Request>()
26+
..method.equals('POST')
27+
..url.path.equals('/api/v1/typing')
28+
..bodyFields.deepEquals(expectedBodyFields);
29+
}
30+
31+
Future<void> checkSetTypingStatusForTopic(TypingOp op, String expectedOp) {
32+
return FakeApiConnection.with_((connection) {
33+
return checkSetTypingStatus(connection, op,
34+
destination: const StreamDestination(streamId, topic),
35+
expectedBodyFields: {
36+
'op': expectedOp,
37+
'type': 'channel',
38+
'stream_id': streamId.toString(),
39+
'topic': topic,
40+
});
41+
});
42+
}
43+
44+
test('send typing status start for topic', () {
45+
return checkSetTypingStatusForTopic(TypingOp.start, 'start');
46+
});
47+
48+
test('send typing status stop for topic', () {
49+
return checkSetTypingStatusForTopic(TypingOp.stop, 'stop');
50+
});
51+
52+
test('send typing status start for dm', () {
53+
return FakeApiConnection.with_((connection) {
54+
return checkSetTypingStatus(connection, TypingOp.start,
55+
destination: const DmDestination(userIds: userIds),
56+
expectedBodyFields: {
57+
'op': 'start',
58+
'type': 'direct',
59+
'to': jsonEncode(userIds),
60+
});
61+
});
62+
});
63+
64+
test('legacy: use "stream" instead of "channel"', () {
65+
return FakeApiConnection.with_(zulipFeatureLevel: 247, (connection) {
66+
return checkSetTypingStatus(connection, TypingOp.start,
67+
destination: const StreamDestination(streamId, topic),
68+
expectedBodyFields: {
69+
'op': 'start',
70+
'type': 'stream',
71+
'stream_id': streamId.toString(),
72+
'topic': topic,
73+
});
74+
});
75+
});
76+
77+
test('legacy: use to=[streamId] instead of stream_id=streamId', () {
78+
return FakeApiConnection.with_(zulipFeatureLevel: 214, (connection) {
79+
return checkSetTypingStatus(connection, TypingOp.start,
80+
destination: const StreamDestination(streamId, topic),
81+
expectedBodyFields: {
82+
'op': 'start',
83+
'type': 'stream',
84+
'to': jsonEncode([streamId]),
85+
'topic': topic,
86+
});
87+
});
88+
});
89+
90+
test('legacy: use "private" instead of "direct"', () {
91+
return FakeApiConnection.with_(zulipFeatureLevel: 173, (connection) {
92+
return checkSetTypingStatus(connection, TypingOp.start,
93+
destination: const DmDestination(userIds: userIds),
94+
expectedBodyFields: {
95+
'op': 'start',
96+
'type': 'private',
97+
'to': jsonEncode(userIds),
98+
});
99+
});
100+
});
101+
}

0 commit comments

Comments
 (0)