Skip to content

Commit 9a77275

Browse files
committed
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 fe60d28 commit 9a77275

File tree

4 files changed

+122
-2
lines changed

4 files changed

+122
-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+
'type': RawParameter((supportsTypeChannel) ? 'channel' : 'stream'),
17+
'op': RawParameter(op.toJson()),
18+
'topic': RawParameter(destination.topic),
19+
...(supportsStreamId) ? {'stream_id': destination.streamId}
20+
: {'to': [destination.streamId]}
21+
});
22+
case DmDestination():
23+
final supportsDirect = connection.zulipFeatureLevel! >= 174; // TODO(server-7)
24+
return connection.post('setTypingStatus', (_) {}, 'typing', {
25+
'type': RawParameter((supportsDirect) ? 'direct' : 'private'),
26+
'op': RawParameter(op.toJson()),
27+
'to': destination.userIds,
28+
});
29+
}
30+
}

test/api/route/typing_test.dart

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
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, TypingOp op, {
19+
required MessageDestination destination,
20+
required Map<String, String> expectedBodyFields,
21+
}) async {
22+
connection.prepare(json: {});
23+
await setTypingStatus(connection, op: op, destination: destination);
24+
check(connection.lastRequest).isA<http.Request>()
25+
..method.equals('POST')
26+
..url.path.equals('/api/v1/typing')
27+
..bodyFields.deepEquals(expectedBodyFields);
28+
}
29+
30+
Future<void> checkSetTypingStatusForTopic(TypingOp op, String expectedOp) =>
31+
FakeApiConnection.with_((connection) =>
32+
checkSetTypingStatus(connection, op,
33+
destination: const StreamDestination(streamId, topic),
34+
expectedBodyFields: {
35+
'type': 'channel',
36+
'op': expectedOp,
37+
'stream_id': streamId.toString(),
38+
'topic': topic,
39+
}));
40+
41+
test('send typing status start for topic', () =>
42+
checkSetTypingStatusForTopic(TypingOp.start, 'start'));
43+
44+
test('send typing status stop for topic', () =>
45+
checkSetTypingStatusForTopic(TypingOp.stop, 'stop'));
46+
47+
test('send typing status start for dm', () =>
48+
FakeApiConnection.with_((connection) =>
49+
checkSetTypingStatus(connection, TypingOp.start,
50+
destination: const DmDestination(userIds: userIds),
51+
expectedBodyFields: {
52+
'type': 'direct',
53+
'op': 'start',
54+
'to': jsonEncode(userIds),
55+
})));
56+
57+
test('legacy: use "stream" instead of "channel"', () =>
58+
FakeApiConnection.with_(zulipFeatureLevel: 247, (connection) =>
59+
checkSetTypingStatus(connection, TypingOp.start,
60+
destination: const StreamDestination(streamId, topic),
61+
expectedBodyFields: {
62+
'type': 'stream',
63+
'op': 'start',
64+
'stream_id': streamId.toString(),
65+
'topic': topic,
66+
})));
67+
68+
test('legacy: use to=[streamId] instead of stream_id=streamId', () =>
69+
FakeApiConnection.with_(zulipFeatureLevel: 214, (connection) =>
70+
checkSetTypingStatus(connection, TypingOp.start,
71+
destination: const StreamDestination(streamId, topic),
72+
expectedBodyFields: {
73+
'type': 'stream',
74+
'op': 'start',
75+
'to': jsonEncode([streamId]),
76+
'topic': topic,
77+
})));
78+
79+
test('legacy: use "private" instead of "direct"', () =>
80+
FakeApiConnection.with_(zulipFeatureLevel: 173, (connection) =>
81+
checkSetTypingStatus(connection, TypingOp.start,
82+
destination: const DmDestination(userIds: userIds),
83+
expectedBodyFields: {
84+
'type': 'private',
85+
'op': 'start',
86+
'to': jsonEncode(userIds),
87+
})));
88+
}

0 commit comments

Comments
 (0)