Skip to content

Commit fb36a8a

Browse files
PIG208gnprice
authored andcommitted
api: Add typing events.
Signed-off-by: Zixuan James Li <[email protected]>
1 parent ca4c3d0 commit fb36a8a

File tree

4 files changed

+148
-2
lines changed

4 files changed

+148
-2
lines changed

lib/api/model/events.dart

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ sealed class Event {
6262
case 'remove': return UpdateMessageFlagsRemoveEvent.fromJson(json);
6363
default: return UnexpectedEvent.fromJson(json);
6464
}
65+
case 'typing': return TypingEvent.fromJson(json);
6566
case 'reaction': return ReactionEvent.fromJson(json);
6667
case 'heartbeat': return HeartbeatEvent.fromJson(json);
6768
// TODO add many more event types
@@ -736,8 +737,9 @@ class DeleteMessageEvent extends Event {
736737
Map<String, dynamic> toJson() => _$DeleteMessageEventToJson(this);
737738
}
738739

739-
/// As in [DeleteMessageEvent.messageType]
740-
/// or [UpdateMessageFlagsMessageDetail.type].
740+
/// As in [DeleteMessageEvent.messageType],
741+
/// [UpdateMessageFlagsMessageDetail.type],
742+
/// or [TypingEvent.messageType].
741743
@JsonEnum(alwaysCreate: true)
742744
enum MessageType {
743745
stream,
@@ -871,6 +873,69 @@ class UpdateMessageFlagsMessageDetail {
871873
Map<String, dynamic> toJson() => _$UpdateMessageFlagsMessageDetailToJson(this);
872874
}
873875

876+
/// A Zulip event of type `typing`:
877+
/// https://zulip.com/api/get-events#typing-start
878+
/// https://zulip.com/api/get-events#typing-stop
879+
@JsonSerializable(fieldRename: FieldRename.snake)
880+
class TypingEvent extends Event {
881+
@override
882+
@JsonKey(includeToJson: true)
883+
String get type => 'typing';
884+
885+
final TypingOp op;
886+
@MessageTypeConverter()
887+
final MessageType messageType;
888+
@JsonKey(readValue: _readSenderId)
889+
final int senderId;
890+
@JsonKey(name: 'recipients', fromJson: _recipientIdsFromJson)
891+
final List<int>? recipientIds;
892+
final int? streamId;
893+
final String? topic;
894+
895+
TypingEvent({
896+
required super.id,
897+
required this.op,
898+
required this.messageType,
899+
required this.senderId,
900+
required this.recipientIds,
901+
required this.streamId,
902+
required this.topic,
903+
});
904+
905+
static Object? _readSenderId(Map<Object?, Object?> json, String key) {
906+
return (json['sender'] as Map<String, dynamic>)['user_id'];
907+
}
908+
909+
static List<int>? _recipientIdsFromJson(Object? json) {
910+
if (json == null) return null;
911+
return (json as List<Object?>).map(
912+
(item) => (item as Map<String, Object?>)['user_id'] as int).toList();
913+
}
914+
915+
factory TypingEvent.fromJson(Map<String, dynamic> json) {
916+
final result = _$TypingEventFromJson(json);
917+
// Crunchy-shell validation
918+
switch (result.messageType) {
919+
case MessageType.stream:
920+
result.streamId as int;
921+
result.topic as String;
922+
case MessageType.direct:
923+
result.recipientIds as List<int>;
924+
}
925+
return result;
926+
}
927+
928+
@override
929+
Map<String, dynamic> toJson() => _$TypingEventToJson(this);
930+
}
931+
932+
/// As in [TypingEvent.op].
933+
@JsonEnum(fieldRename: FieldRename.snake)
934+
enum TypingOp {
935+
start,
936+
stop
937+
}
938+
874939
/// A Zulip event of type `reaction`, with op `add` or `remove`.
875940
///
876941
/// See:

lib/api/model/events.g.dart

Lines changed: 28 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/api/model/events_checks.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,14 @@ extension UpdateMessageFlagsMessageDetailCheck on Subject<UpdateMessageFlagsMess
7272
Subject<MessageType?> get type => has((e) => e.type, 'type');
7373
}
7474

75+
extension TypingEventChecks on Subject<TypingEvent> {
76+
Subject<MessageType> get messageType => has((e) => e.messageType, 'messageType');
77+
Subject<int> get senderId => has((e) => e.senderId, 'senderId');
78+
Subject<List<int>?> get recipientIds => has((e) => e.recipientIds, 'recipientIds');
79+
Subject<int?> get streamId => has((e) => e.streamId, 'streamId');
80+
Subject<String?> get topic => has((e) => e.topic, 'topic');
81+
}
82+
7583
extension HeartbeatEventChecks on Subject<HeartbeatEvent> {
7684
// No properties not covered by Event.
7785
}

test/api/model/events_test.dart

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,4 +197,49 @@ void main() {
197197
.values.single.type.equals(MessageType.direct);
198198
});
199199
});
200+
201+
group('typing status event', () {
202+
final baseJson = {
203+
'id': 1,
204+
'type': 'typing',
205+
'op': 'start',
206+
'sender': {'user_id': 123, 'email': '[email protected]'},
207+
};
208+
209+
final directMessageJson = {
210+
...baseJson,
211+
'message_type': 'direct',
212+
'recipients': [1, 2, 3].map((e) => {'user_id': e, 'email': '$e@example.com'}).toList(),
213+
};
214+
215+
test('direct message typing events', () {
216+
check(TypingEvent.fromJson(directMessageJson))
217+
..recipientIds.isNotNull().deepEquals([1, 2, 3])
218+
..senderId.equals(123);
219+
});
220+
221+
test('private type missing recipient', () {
222+
check(() => TypingEvent.fromJson({
223+
...baseJson, 'message_type': 'private'})).throws<void>();
224+
});
225+
226+
test('private -> direct', () {
227+
check(TypingEvent.fromJson({
228+
...directMessageJson,
229+
'message_type': 'private',
230+
})).messageType.equals(MessageType.direct);
231+
});
232+
233+
test('stream type missing streamId/topic', () {
234+
check(() => TypingEvent.fromJson({
235+
...baseJson, 'message_type': 'stream', 'stream_id': 123, 'topic': 'foo'}))
236+
.returnsNormally();
237+
check(() => TypingEvent.fromJson({
238+
...baseJson, 'message_type': 'stream'})).throws<void>();
239+
check(() => TypingEvent.fromJson({
240+
...baseJson, 'message_type': 'stream', 'topic': 'foo'})).throws<void>();
241+
check(() => TypingEvent.fromJson({
242+
...baseJson, 'message_type': 'stream', 'stream_id': 123})).throws<void>();
243+
});
244+
});
200245
}

0 commit comments

Comments
 (0)