Skip to content

Commit eff9a33

Browse files
committed
api: Add route setTypingStatus.
Signed-off-by: Zixuan James Li <[email protected]>
1 parent c47dac5 commit eff9a33

File tree

4 files changed

+120
-1
lines changed

4 files changed

+120
-1
lines changed

lib/api/model/events.dart

+3-1
Original file line numberDiff line numberDiff line change
@@ -936,7 +936,9 @@ class TypingEvent extends Event {
936936
@JsonEnum(fieldRename: FieldRename.snake)
937937
enum TypingOp {
938938
start,
939-
stop
939+
stop;
940+
941+
String toJson() => _$TypingOpEnumMap[this]!;
940942
}
941943

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

lib/api/route/messages.dart

+10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 'package:json_annotation/json_annotation.dart';
22

3+
import '../../model/narrow.dart';
34
import '../core.dart';
45
import '../exception.dart';
56
import '../model/model.dart';
@@ -221,6 +222,15 @@ Future<SendMessageResult> sendMessage(
221222
/// This is either a [StreamDestination] or a [DmDestination].
222223
sealed class MessageDestination {
223224
const MessageDestination();
225+
226+
factory MessageDestination.fromSendableNarrow(SendableNarrow narrow) {
227+
switch (narrow) {
228+
case TopicNarrow():
229+
return StreamDestination(narrow.streamId, narrow.topic);
230+
case DmNarrow():
231+
return DmDestination(userIds: narrow.allRecipientIds);
232+
}
233+
}
224234
}
225235

226236
/// A conversation in a stream, for specifying to [sendMessage].

lib/api/route/typing.dart

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
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+
final supportsTypeChannel = connection.zulipFeatureLevel! >= 248; // TODO(server-9)
12+
switch (destination) {
13+
case StreamDestination():
14+
return connection.post('setTypingStatus', (_) {}, 'typing', {
15+
'type': RawParameter((supportsTypeChannel) ? 'channel' : 'stream'),
16+
'op': RawParameter(op.toJson()),
17+
'stream_id': destination.streamId,
18+
'topic': RawParameter(destination.topic),
19+
});
20+
case DmDestination():
21+
return connection.post('setTypingStatus', (_) {}, 'typing', {
22+
'op': RawParameter(op.toJson()),
23+
'to': destination.userIds,
24+
});
25+
}
26+
}

test/api/route/typing_test.dart

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

0 commit comments

Comments
 (0)