Skip to content

Commit 9849197

Browse files
committed
model: Add Submessage.
Signed-off-by: Zixuan James Li <[email protected]>
1 parent 42d53c1 commit 9849197

File tree

4 files changed

+147
-1
lines changed

4 files changed

+147
-1
lines changed

lib/api/model/model.dart

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import 'dart:convert';
2+
13
import 'package:json_annotation/json_annotation.dart';
24

35
import 'events.dart';
@@ -481,7 +483,7 @@ sealed class Message {
481483
final String senderRealmStr;
482484
@JsonKey(name: 'subject')
483485
final String topic;
484-
// final List<string> submessages; // TODO handle
486+
final List<Submessage> submessages;
485487
final int timestamp;
486488
String get type;
487489

@@ -528,6 +530,7 @@ sealed class Message {
528530
required this.senderId,
529531
required this.senderRealmStr,
530532
required this.topic,
533+
required this.submessages,
531534
required this.timestamp,
532535
required this.flags,
533536
required this.matchContent,
@@ -570,6 +573,46 @@ enum MessageFlag {
570573
String toJson() => _$MessageFlagEnumMap[this]!;
571574
}
572575

576+
/// https://zulip.com/api/get-messages#response
577+
@JsonSerializable(fieldRename: FieldRename.snake)
578+
class Submessage {
579+
const Submessage({
580+
required this.msgType,
581+
required this.content,
582+
required this.messageId,
583+
required this.senderId,
584+
required this.id,
585+
});
586+
587+
@JsonKey(unknownEnumValue: SubmessageType.unknown)
588+
final SubmessageType msgType;
589+
/// JSON-encoded string representing any of the possible [WidgetData].
590+
@JsonKey(readValue: _readContent)
591+
final Object? content;
592+
final int messageId;
593+
final int senderId;
594+
final int id;
595+
596+
static Object? _readContent(Map<Object?, Object?> json, String key) {
597+
try {
598+
return (const JsonDecoder().convert(json[key] as String)) as Object?;
599+
}
600+
catch (e) {
601+
return null;
602+
}
603+
}
604+
605+
factory Submessage.fromJson(Map<String, dynamic> json) =>
606+
_$SubmessageFromJson(json);
607+
608+
Map<String, dynamic> toJson() => _$SubmessageToJson(this);
609+
}
610+
611+
enum SubmessageType {
612+
widget,
613+
unknown,
614+
}
615+
573616
@JsonSerializable(fieldRename: FieldRename.snake)
574617
class StreamMessage extends Message {
575618
@override
@@ -595,6 +638,7 @@ class StreamMessage extends Message {
595638
required super.senderRealmStr,
596639
required super.topic,
597640
required super.timestamp,
641+
required super.submessages,
598642
required super.flags,
599643
required super.matchContent,
600644
required super.matchTopic,
@@ -697,6 +741,7 @@ class DmMessage extends Message {
697741
required super.senderId,
698742
required super.senderRealmStr,
699743
required super.topic,
744+
required super.submessages,
700745
required super.timestamp,
701746
required super.flags,
702747
required super.matchContent,

lib/api/model/model.g.dart

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

test/api/model/model_checks.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,22 @@ extension MessageChecks on Subject<Message> {
4242
Subject<int> get senderId => has((e) => e.senderId, 'senderId');
4343
Subject<String> get senderRealmStr => has((e) => e.senderRealmStr, 'senderRealmStr');
4444
Subject<String> get topic => has((e) => e.topic, 'topic');
45+
Subject<List<Submessage>> get submessages => has((e) => e.submessages, 'submessages');
4546
Subject<int> get timestamp => has((e) => e.timestamp, 'timestamp');
4647
Subject<String> get type => has((e) => e.type, 'type');
4748
Subject<List<MessageFlag>> get flags => has((e) => e.flags, 'flags');
4849
Subject<String?> get matchContent => has((e) => e.matchContent, 'matchContent');
4950
Subject<String?> get matchTopic => has((e) => e.matchTopic, 'matchTopic');
5051
}
5152

53+
extension SubMessageChecks on Subject<Submessage> {
54+
Subject<SubmessageType> get msgType => has((e) => e.msgType, 'msgType');
55+
Subject<Object?> get content => has((e) => e.content, 'content');
56+
Subject<int> get messageId => has((e) => e.messageId, 'messageId');
57+
Subject<int> get senderId => has((e) => e.senderId, 'senderId');
58+
Subject<int> get id => has((e) => e.id, 'id');
59+
}
60+
5261
extension ReactionsChecks on Subject<Reactions> {
5362
Subject<int> get total => has((e) => e.total, 'total');
5463
Subject<List<ReactionWithVotes>> get aggregated => has((e) => e.aggregated, 'aggregated');

test/api/model/model_test.dart

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,67 @@ void main() {
149149
check(m2).flags.deepEquals([MessageFlag.read, MessageFlag.unknown]);
150150
});
151151

152+
test('no crash on unrecognized submessage type', () {
153+
final streamMessage = eg.streamMessage(sender: eg.selfUser);
154+
final dmMessage = eg.dmMessage(from: eg.selfUser, to: [eg.otherUser]);
155+
final submessage = {
156+
'msg_type': 'unknown_widget',
157+
'content': '[]',
158+
'sender_id': eg.selfUser.userId,
159+
'id': 1,
160+
};
161+
162+
check(Message.fromJson(
163+
(deepToJson(streamMessage) as Map<String, dynamic>)
164+
..['submessages'] = [submessage..['message_id'] = streamMessage.id],
165+
)).submessages.single.msgType.equals(SubmessageType.unknown);
166+
167+
check(Message.fromJson(
168+
(deepToJson(dmMessage) as Map<String, dynamic>)
169+
..['submessages'] = [submessage..['message_id'] = dmMessage.id],
170+
)).submessages.single.msgType.equals(SubmessageType.unknown);
171+
});
172+
173+
test('no crash on unknown submessage content encoding', () {
174+
final streamMessage = eg.streamMessage(sender: eg.selfUser);
175+
final submessage = {
176+
'msg_type': 'unknown_widget',
177+
'content': 'not json',
178+
'message_id': streamMessage.id,
179+
'sender_id': eg.selfUser.userId,
180+
'id': 1,
181+
};
182+
183+
check(Message.fromJson(
184+
(deepToJson(streamMessage) as Map<String, dynamic>)
185+
..['submessages'] = [submessage],
186+
)).submessages.single.content.isNull();
187+
});
188+
189+
test('submessage content gets decoded from JSON', () {
190+
final streamMessage = eg.streamMessage(sender: eg.selfUser);
191+
final expected = {
192+
'widget_type': 'poll',
193+
'extra_data': {
194+
'question': 'favorite letter',
195+
'options': ['A', 'B', 'C'],
196+
}
197+
};
198+
final submessage = {
199+
'msg_type': 'unknown_widget',
200+
'content': const JsonEncoder().convert(expected),
201+
'message_id': streamMessage.id,
202+
'sender_id': eg.selfUser.userId,
203+
'id': 1,
204+
};
205+
206+
check(Message.fromJson(
207+
(deepToJson(streamMessage) as Map<String, dynamic>)
208+
..['submessages'] = [submessage]
209+
)).submessages.single.content.isA<Map<String, Object?>>()
210+
.deepEquals(expected);
211+
});
212+
152213
// Code relevant to messageEditState is tested separately in the
153214
// MessageEditState group.
154215
});

0 commit comments

Comments
 (0)