Skip to content

Commit c823d5c

Browse files
committed
api: Add route setTypingStatus.
The two 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 6d8a03a commit c823d5c

File tree

4 files changed

+128
-2
lines changed

4 files changed

+128
-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

+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
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> checkTopicExpectedOp(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 to topic', () =>
42+
checkTopicExpectedOp(TypingOp.start, 'start'));
43+
44+
test('send typing status stop to topic', () =>
45+
checkTopicExpectedOp(TypingOp.stop, 'stop'));
46+
47+
Future<void> checkDmExpectedOp(TypingOp op, String expectedOp) =>
48+
FakeApiConnection.with_((connection) =>
49+
checkSetTypingStatus(connection, op,
50+
destination: const DmDestination(userIds: userIds),
51+
expectedBodyFields: {
52+
'type': 'direct',
53+
'op': expectedOp,
54+
'to': jsonEncode(userIds),
55+
}));
56+
57+
test('send typing status start to dm', () =>
58+
checkDmExpectedOp(TypingOp.start, 'start'));
59+
60+
test('send typing status stop to dm', () =>
61+
checkDmExpectedOp(TypingOp.stop, 'stop'));
62+
63+
test('use stream on legacy server', () =>
64+
FakeApiConnection.with_(zulipFeatureLevel: 247, (connection) =>
65+
checkSetTypingStatus(connection, TypingOp.start,
66+
destination: const StreamDestination(streamId, topic),
67+
expectedBodyFields: {
68+
'type': 'stream',
69+
'op': 'start',
70+
'stream_id': streamId.toString(),
71+
'topic': topic,
72+
})));
73+
74+
test('use to=[streamId] on legacy server', () =>
75+
FakeApiConnection.with_(zulipFeatureLevel: 214, (connection) =>
76+
checkSetTypingStatus(connection, TypingOp.start,
77+
destination: const StreamDestination(streamId, topic),
78+
expectedBodyFields: {
79+
'type': 'stream',
80+
'op': 'start',
81+
'to': jsonEncode([streamId]),
82+
'topic': topic,
83+
})));
84+
85+
test('use "private" on legacy server', () =>
86+
FakeApiConnection.with_(zulipFeatureLevel: 173, (connection) =>
87+
checkSetTypingStatus(connection, TypingOp.start,
88+
destination: const DmDestination(userIds: userIds),
89+
expectedBodyFields: {
90+
'type': 'private',
91+
'op': 'start',
92+
'to': jsonEncode(userIds),
93+
})));
94+
}

0 commit comments

Comments
 (0)