Skip to content

Commit 6360b40

Browse files
committed
message: Handle UpdateMessageEvent for moving/editing.
When a channel (a.k.a stream) or a topic gets updated on a message, we get a UpdateMessageEvent from the server. The same happens when the message's content is updated. This updates the edit state to show the "edited" or "moved" marker properly on the messages affected, and leave other move related states (like streamId) untouched until we get to #150. Signed-off-by: Zixuan James Li <[email protected]>
1 parent 95c1619 commit 6360b40

File tree

3 files changed

+140
-4
lines changed

3 files changed

+140
-4
lines changed

lib/model/message.dart

+31-2
Original file line numberDiff line numberDiff line change
@@ -125,10 +125,21 @@ class MessageStoreImpl with MessageStore {
125125
if (message == null) return;
126126

127127
message.flags = event.flags;
128+
// According to the API, the content of the message is edited if origContent
129+
// is not null.
130+
if (event.origContent != null) {
131+
message.editState = MessageEditState.edited;
132+
}
128133
if (event.renderedContent != null) {
129134
assert(message.contentType == 'text/html',
130135
"Message contentType was ${message.contentType}; expected text/html.");
131136
message.content = event.renderedContent!;
137+
if (
138+
!(event.renderingOnly == true
139+
|| event.userId == null) // TODO(server-5) can remove userId check
140+
) {
141+
message.editState = MessageEditState.edited;
142+
}
132143
}
133144
if (event.isMeMessage != null) {
134145
message.isMeMessage = event.isMeMessage!;
@@ -143,7 +154,9 @@ class MessageStoreImpl with MessageStore {
143154
// The interaction between the fields of these events are a bit tricky.
144155
// For reference, see: https://zulip.com/api/get-events#update_message
145156

146-
if (event.origTopic == null) {
157+
bool isTopicUpdated = event.origTopic != null;
158+
159+
if (!isTopicUpdated) {
147160
// There was no move.
148161
assert(() {
149162
if (event.newStreamId != null && event.origStreamId != null
@@ -168,10 +181,26 @@ class MessageStoreImpl with MessageStore {
168181
return;
169182
}
170183

171-
// final newStreamId = event.newStreamId; // null if topic-only move
184+
final newChannelId = event.newStreamId; // null if topic-only move
185+
186+
// Eliminate the case when the topic is updated only to be resolved/unresolved.
187+
// return early as the messages' editState need no update.
188+
if (
189+
newChannelId == null
190+
&& MessageEditState.areSameTopic(event.origTopic!, event.newTopic!)
191+
) return;
192+
172193
// final newTopic = event.newTopic!;
173194
// TODO(#150): Handle message moves. The views' recipient headers
174195
// may need updating, and consequently showSender too.
196+
// Currently only editState gets updated.
197+
for (final messageId in event.messageIds) {
198+
final message = messages[messageId];
199+
if (message == null) continue;
200+
// Do not override the edited marker if the message has also been moved.
201+
if (message.editState == MessageEditState.edited) continue;
202+
message.editState = MessageEditState.moved;
203+
}
175204
}
176205

177206
void handleDeleteMessageEvent(DeleteMessageEvent event) {

test/example_data.dart

+32
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,38 @@ UpdateMessageEvent updateMessageEditEvent(
420420
);
421421
}
422422

423+
UpdateMessageEvent updateMessageMoveEvent(
424+
List<Message> messages, {
425+
int? newStreamId,
426+
String? origTopic,
427+
String? newTopic,
428+
String? origContent,
429+
String? newContent,
430+
}) {
431+
assert(messages.isNotEmpty);
432+
final origMessage = messages[0];
433+
final messageId = origMessage.id;
434+
return UpdateMessageEvent(
435+
id: 0,
436+
userId: selfUser.userId,
437+
renderingOnly: false,
438+
messageId: messageId,
439+
messageIds: messages.map((message) => message.id).toList(),
440+
flags: origMessage.flags,
441+
editTimestamp: 1234567890, // TODO generate timestamp
442+
origStreamId: origMessage is StreamMessage ? origMessage.streamId : null,
443+
newStreamId: newStreamId,
444+
propagateMode: null,
445+
origTopic: origTopic,
446+
newTopic: newTopic,
447+
origContent: origContent,
448+
origRenderedContent: origContent,
449+
content: newContent,
450+
renderedContent: newContent,
451+
isMeMessage: false,
452+
);
453+
}
454+
423455
UpdateMessageFlagsRemoveEvent updateMessageFlagsRemoveEvent(
424456
MessageFlag flag,
425457
Iterable<Message> messages, {

test/model/message_test.dart

+77-2
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,8 @@ void main() {
207207
..content.not((it) => it.equals(updateEvent.renderedContent!))
208208
..lastEditTimestamp.isNull()
209209
..flags.not((it) => it.deepEquals(updateEvent.flags))
210-
..isMeMessage.not((it) => it.equals(updateEvent.isMeMessage!));
210+
..isMeMessage.not((it) => it.equals(updateEvent.isMeMessage!))
211+
..editState.equals(MessageEditState.none);
211212

212213
await store.handleEvent(updateEvent);
213214
checkNotifiedOnce();
@@ -216,7 +217,8 @@ void main() {
216217
..content.equals(updateEvent.renderedContent!)
217218
..lastEditTimestamp.equals(updateEvent.editTimestamp)
218219
..flags.equals(updateEvent.flags)
219-
..isMeMessage.equals(updateEvent.isMeMessage!);
220+
..isMeMessage.equals(updateEvent.isMeMessage!)
221+
..editState.equals(MessageEditState.edited);
220222
});
221223

222224
test('ignore when message unknown', () async {
@@ -269,6 +271,79 @@ void main() {
269271
test('rendering-only update does not change timestamp (for old server versions)', () async {
270272
await checkRenderingOnly(legacy: true);
271273
});
274+
275+
group('Handle message edit state update', () {
276+
final message = eg.streamMessage();
277+
final otherMessage = eg.streamMessage();
278+
279+
Future<void> sendEvent(Message message, UpdateMessageEvent event) async {
280+
await prepare();
281+
await prepareMessages([message, otherMessage]);
282+
283+
await store.handleEvent(event);
284+
checkNotifiedOnce();
285+
}
286+
287+
test('message not moved update', () async {
288+
await sendEvent(message, eg.updateMessageEditEvent(message));
289+
check(store).messages[message.id].editState.equals(MessageEditState.edited);
290+
check(store).messages[otherMessage.id].editState.equals(MessageEditState.none);
291+
});
292+
293+
test('message topic moved update', () async {
294+
await sendEvent(message, eg.updateMessageMoveEvent([message, otherMessage],
295+
origTopic: 'old topic',
296+
newTopic: 'new topic'));
297+
check(store).messages.values.every(((message) => message.editState.equals(MessageEditState.moved)));
298+
});
299+
300+
test('message topic resolved update', () async {
301+
await sendEvent(message, eg.updateMessageMoveEvent([message, otherMessage],
302+
origTopic: 'new topic',
303+
newTopic: '✔ new topic'));
304+
check(store).messages.values.every(((message) => message.editState.equals(MessageEditState.none)));
305+
});
306+
307+
test('message topic unresolved update', () async {
308+
await sendEvent(message, eg.updateMessageMoveEvent([message, otherMessage],
309+
origTopic: '✔ new topic',
310+
newTopic: 'new topic'));
311+
check(store).messages.values.every(((message) => message.editState.equals(MessageEditState.none)));
312+
});
313+
314+
test('message topic both resolved and edited update', () async {
315+
await sendEvent(message, eg.updateMessageMoveEvent([message, otherMessage],
316+
origTopic: 'new topic',
317+
newTopic: '✔ new topic 2'));
318+
check(store).messages.values.every(((message) => message.editState.equals(MessageEditState.moved)));
319+
});
320+
321+
test('message topic both unresolved and edited update', () async {
322+
await sendEvent(message, eg.updateMessageMoveEvent([message, otherMessage],
323+
origTopic: '✔ new topic',
324+
newTopic: 'new topic 2'));
325+
check(store).messages.values.every(((message) => message.editState.equals(MessageEditState.moved)));
326+
});
327+
328+
test('message stream moved update', () async {
329+
await sendEvent(message, eg.updateMessageMoveEvent([message, otherMessage],
330+
origTopic: 'topic',
331+
newTopic: 'topic',
332+
newStreamId: 20));
333+
check(store).messages.values.every(((message) => message.editState.equals(MessageEditState.moved)));
334+
});
335+
336+
test('message is both moved and updated', () async {
337+
await sendEvent(message, eg.updateMessageMoveEvent([message, otherMessage],
338+
origTopic: 'topic',
339+
newTopic: 'topic',
340+
newStreamId: 20,
341+
origContent: 'old content',
342+
newContent: 'new content'));
343+
check(store).messages[message.id].editState.equals(MessageEditState.edited);
344+
check(store).messages[otherMessage.id].editState.equals(MessageEditState.moved);
345+
});
346+
});
272347
});
273348

274349
group('handleUpdateMessageFlagsEvent', () {

0 commit comments

Comments
 (0)