Skip to content

Commit 6ff98a9

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 f151627 commit 6ff98a9

File tree

4 files changed

+121
-2
lines changed

4 files changed

+121
-2
lines changed

lib/api/model/events.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1002,7 +1002,9 @@ class TypingEvent extends Event {
10021002
@JsonEnum(fieldRename: FieldRename.snake)
10031003
enum TypingOp {
10041004
start,
1005-
stop
1005+
stop;
1006+
1007+
String toJson() => _$TypingOpEnumMap[this]!;
10061008
}
10071009

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

lib/api/model/events.g.dart

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/api/route/typing.dart

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
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+
return connection.post('setTypingStatus', (_) {}, 'typing', {
24+
'op': RawParameter(op.toJson()),
25+
'to': destination.userIds,
26+
});
27+
}
28+
}

test/api/route/typing_test.dart

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

0 commit comments

Comments
 (0)