Skip to content

Commit c1be53c

Browse files
committed
fix: optimistic updates for translated messages
1 parent 6f50412 commit c1be53c

2 files changed

Lines changed: 31 additions & 0 deletions

File tree

‎src/messageOperations/MessageOperationStatePolicy.ts‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,13 @@ export class MessageOperationStatePolicy {
104104
// that never left the device stays `failed`.
105105
const isFailed = localMessage.status === 'failed';
106106
const editedAt = nowNs();
107+
// The translations in `i18n` are of the text being replaced, and a UI renders them in place of
108+
// `text`, so they would hide the edit. The server translates the new text in its response.
109+
const textChanged = localMessage.text !== previous?.text;
107110
const applied: LocalMessage = {
108111
...localMessage,
109112
error: isFailed ? localMessage.error : undefined,
113+
...(textChanged ? { i18n: undefined } : {}),
110114
updated_at: editedAt,
111115
// `message_text_updated_at` drives the "edited" indicator, so it appears immediately, but only
112116
// for a message the server has seen: a failed one never had a confirmed text update.

‎test/unit/messageOperations/MessageOperations.test.ts‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -917,6 +917,33 @@ describe('MessageOperations — optimistic lifecycle', () => {
917917
expect(store.get('m1')?.text).toBe('from a websocket event');
918918
});
919919

920+
// UIs render `i18n.<language>_text` in place of `text`, so a translation of the old text left on
921+
// the optimistic copy hides the edit until the server responds, or offline, until the replay.
922+
it('drops the translations of the replaced text', async () => {
923+
const seed = makeLocalMessage({
924+
i18n: { en_text: 'before', es_text: 'antes', language: 'en' },
925+
id: 'm1',
926+
status: 'received',
927+
text: 'before',
928+
});
929+
const { ops, persisted } = harness({ seed });
930+
931+
await ops.update({ localMessage: { ...seed, text: 'after' } });
932+
933+
expect(persisted[0].text).toBe('after');
934+
expect(persisted[0].i18n).toBeUndefined();
935+
});
936+
937+
it('keeps the translations when the edit leaves the text unchanged', async () => {
938+
const i18n = { en_text: 'same', es_text: 'igual', language: 'en' };
939+
const seed = makeLocalMessage({ i18n, id: 'm1', status: 'received', text: 'same' });
940+
const { ops, persisted } = harness({ seed });
941+
942+
await ops.update({ localMessage: { ...seed, attachments: [] } });
943+
944+
expect(persisted[0].i18n).toEqual(i18n);
945+
});
946+
920947
it('stamps message_text_updated_at so the "edited" indicator shows immediately', async () => {
921948
const seed = makeLocalMessage({ id: 'm1', status: 'received' });
922949
const { ops, persisted } = harness({ seed });

0 commit comments

Comments
 (0)