1
+ import 'package:checks/checks.dart' ;
2
+ import 'package:test/scaffolding.dart' ;
3
+ import 'package:zulip/api/model/events.dart' ;
4
+ import 'package:zulip/api/model/model.dart' ;
5
+ import 'package:zulip/api/route/messages.dart' ;
6
+ import 'package:zulip/model/message_list.dart' ;
7
+ import 'package:zulip/model/narrow.dart' ;
8
+ import 'package:zulip/model/store.dart' ;
9
+ import '../api/fake_api.dart' ;
10
+ import '../api/model/model_checks.dart' ;
11
+ import '../model/binding.dart' ;
12
+ import '../model/test_store.dart' ;
13
+ import '../example_data.dart' as eg;
14
+
15
+ const int userId = 1 ;
16
+ const int streamId = 2 ;
17
+
18
+ Future <PerAccountStore > setupStore (ZulipStream stream) async {
19
+ addTearDown (TestZulipBinding .instance.reset);
20
+
21
+ await TestZulipBinding .instance.globalStore.add (eg.selfAccount, eg.initialSnapshot ());
22
+
23
+ final store = await TestZulipBinding .instance.globalStore.perAccount (eg.selfAccount.id);
24
+ store.addUser (eg.user (userId: userId));
25
+ store.addStream (stream);
26
+
27
+ return store;
28
+ }
29
+
30
+ Future <MessageListView > messageListViewWithMessages (List <Message > messages, PerAccountStore store, Narrow narrow) async {
31
+ final messageList = MessageListView .init (store: store, narrow: narrow);
32
+
33
+ final connection = store.connection as FakeApiConnection ;
34
+
35
+ connection.prepare (json: GetMessagesResult (
36
+ anchor: messages.first.id,
37
+ foundNewest: true ,
38
+ foundOldest: true ,
39
+ foundAnchor: true ,
40
+ historyLimited: false ,
41
+ messages: messages,
42
+ ).toJson ());
43
+
44
+ await messageList.fetch ();
45
+
46
+ check (messageList.messages.length).equals (messages.length);
47
+
48
+ return messageList;
49
+ }
50
+
51
+ void main () async {
52
+ TestZulipBinding .ensureInitialized ();
53
+
54
+ const narrow = StreamNarrow (streamId);
55
+ final stream = eg.stream (streamId: streamId);
56
+
57
+ group ('update message tests' , () {
58
+
59
+ test ('find message in message list returns index of message' , () async {
60
+ final store = await setupStore (stream);
61
+
62
+ final m1 = eg.streamMessage (id: 2 , stream: stream);
63
+ final m2 = eg.streamMessage (id: 4 , stream: stream);
64
+ final m3 = eg.streamMessage (id: 6 , stream: stream);
65
+
66
+ final messageList = await messageListViewWithMessages ([m1, m2, m3], store, narrow);
67
+ //The implementation of this uses a binary search, so let's test it
68
+ //a bit more exhaustively.
69
+
70
+ //Before the first element
71
+ var idx = messageList.findMessageWithId (1 );
72
+ check (idx).equals (- 1 );
73
+
74
+ //Is the first element
75
+ idx = messageList.findMessageWithId (2 );
76
+ check (idx).equals (0 );
77
+
78
+ //Between first and second
79
+ idx = messageList.findMessageWithId (3 );
80
+ check (idx).equals (- 1 );
81
+
82
+ //Is the second
83
+ idx = messageList.findMessageWithId (4 );
84
+ check (idx).equals (1 );
85
+
86
+ //Between second and third
87
+ idx = messageList.findMessageWithId (5 );
88
+ check (idx).equals (- 1 );
89
+
90
+ //Is the third
91
+ idx = messageList.findMessageWithId (6 );
92
+ check (idx).equals (2 );
93
+
94
+ //After the third
95
+ idx = messageList.findMessageWithId (7 );
96
+ check (idx).equals (- 1 );
97
+
98
+ //Invalid IDs
99
+ idx = messageList.findMessageWithId (- 8409 );
100
+ check (idx).equals (- 1 );
101
+
102
+ idx = messageList.findMessageWithId (0 );
103
+ check (idx).equals (- 1 );
104
+ });
105
+
106
+ test ('update events are correctly applied to message when it is in the stream' , () async {
107
+ final store = await setupStore (stream);
108
+
109
+ const oldContent = "<p>Hello, world</p>" ;
110
+ const newContent = "<p>Hello, edited</p>" ;
111
+ const newTimestamp = 99999 ;
112
+
113
+ List <String > oldFlags = [];
114
+ List <String > newFlags = ["starred" ];
115
+
116
+ final mockMessage = eg.streamMessage (id: 243 , stream: stream, content: oldContent, flags: oldFlags);
117
+ final messageList = await messageListViewWithMessages ([mockMessage], store, narrow);
118
+
119
+ final updateEvent = UpdateMessageEvent (
120
+ id: 1 ,
121
+ messageId: mockMessage.id,
122
+ messageIds: [mockMessage.id],
123
+ flags: newFlags,
124
+ renderedContent: newContent,
125
+ editTimestamp: newTimestamp,
126
+ isMeMessage: true ,
127
+ userId: userId
128
+ );
129
+
130
+ final message = messageList.messages[0 ];
131
+ check (message)
132
+ ..content.equals (oldContent)
133
+ ..flags.deepEquals (oldFlags)
134
+ ..isMeMessage.equals (false );
135
+
136
+ var listenersNotified = false ;
137
+
138
+ messageList.addListener (() { listenersNotified = true ; });
139
+ messageList.maybeUpdateMessage (updateEvent);
140
+
141
+ final updatedMessage = messageList.messages[0 ];
142
+ check (updatedMessage).identicalTo (message);
143
+ check (listenersNotified).equals (true );
144
+
145
+ check (message)
146
+ ..content.equals (newContent)
147
+ ..lastEditTimestamp.equals (newTimestamp)
148
+ ..flags.equals (newFlags)
149
+ ..isMeMessage.equals (true );
150
+ });
151
+
152
+ test ('update event is ignored when message is not in the message list' , () async {
153
+ final store = await setupStore (stream);
154
+
155
+ const oldContent = "<p>Hello, world</p>" ;
156
+ const newContent = "<p>Hello, edited</p>" ;
157
+ const newTimestamp = 99999 ;
158
+
159
+ final mockMessage = eg.streamMessage (id: 243 , stream: stream, content: oldContent);
160
+ final messageList = await messageListViewWithMessages ([mockMessage], store, narrow);
161
+
162
+ final updateEvent = UpdateMessageEvent (
163
+ id: 1 ,
164
+ messageId: 972 ,
165
+ messageIds: [972 ],
166
+ flags: mockMessage.flags,
167
+ renderedContent: newContent,
168
+ editTimestamp: newTimestamp,
169
+ userId: userId,
170
+ );
171
+
172
+ final message = messageList.messages[0 ];
173
+ check (message).content.equals (oldContent);
174
+
175
+ var listenersNotified = false ;
176
+
177
+ messageList.addListener (() { listenersNotified = true ; });
178
+ messageList.maybeUpdateMessage (updateEvent);
179
+
180
+ check (listenersNotified).equals (false );
181
+ check (message).content.equals (oldContent);
182
+
183
+ });
184
+ test ('rendering-only update does not change timestamp' , () async {
185
+ final store = await setupStore (stream);
186
+
187
+ const oldContent = "<p>Hello, world</p>" ;
188
+ const newContent = "<p>Hello, world</p> <div>Some link preview</div>" ;
189
+ const newTimestamp = 99999 ;
190
+
191
+ final mockMessage = eg.streamMessage (id: 972 , stream: stream, content: oldContent);
192
+ final messageList = await messageListViewWithMessages ([mockMessage], store, narrow);
193
+
194
+ final updateEvent = UpdateMessageEvent (
195
+ id: 1 ,
196
+ messageId: 972 ,
197
+ messageIds: [972 ],
198
+ flags: mockMessage.flags,
199
+ renderedContent: newContent,
200
+ editTimestamp: newTimestamp,
201
+ renderingOnly: true ,
202
+ userId: null ,
203
+ );
204
+
205
+ final message = messageList.messages[0 ];
206
+ messageList.maybeUpdateMessage (updateEvent);
207
+ check (message)
208
+ ..content.equals (newContent)
209
+ ..lastEditTimestamp.isNull ();
210
+ });
211
+
212
+ test ('rendering-only update does not change timestamp (for old server versions)' , () async {
213
+ final store = await setupStore (stream);
214
+
215
+ const oldContent = "<p>Hello, world</p>" ;
216
+ const newContent = "<p>Hello, world</p> <div>Some link preview</div>" ;
217
+ const newTimestamp = 99999 ;
218
+
219
+ final mockMessage = eg.streamMessage (id: 972 , stream: stream, content: oldContent);
220
+ final messageList = await messageListViewWithMessages ([mockMessage], store, narrow);
221
+
222
+ final updateEvent = UpdateMessageEvent (
223
+ id: 1 ,
224
+ messageId: 972 ,
225
+ messageIds: [972 ],
226
+ flags: mockMessage.flags,
227
+ renderedContent: newContent,
228
+ editTimestamp: newTimestamp,
229
+ userId: null ,
230
+ );
231
+
232
+ final message = messageList.messages[0 ];
233
+ messageList.maybeUpdateMessage (updateEvent);
234
+ check (message)
235
+ ..content.equals (newContent)
236
+ ..lastEditTimestamp.isNull ();
237
+ });
238
+
239
+
240
+ });
241
+ }
0 commit comments