diff --git a/src/poll.ts b/src/poll.ts index 79431d694..0f9515f76 100644 --- a/src/poll.ts +++ b/src/poll.ts @@ -158,7 +158,15 @@ export class Poll { const { id: _id, ...pollData } = extractPollData(event.poll); if (pollData.is_closed) this.closeConfirmed = true; // @ts-expect-error type mismatch - this.state.partialNext({ ...pollData, lastActivityAt: event.created_at }); + this.state.partialNext({ + ...pollData, + // Server-owned translations, which `extractPollData` (an update request) leaves out. Copied + // even when absent: the event carries the whole poll, and a change made under channel-only + // translation drops the stale ones. + description_i18n: event.poll.description_i18n, + lastActivityAt: event.created_at, + name_i18n: event.poll.name_i18n, + }); this.upsertOfflineDb(); }; diff --git a/test/unit/poll.test.js b/test/unit/poll.test.js index de9bcfeb3..988a77ac1 100644 --- a/test/unit/poll.test.js +++ b/test/unit/poll.test.js @@ -188,6 +188,66 @@ describe('Poll', () => { expect(poll.data.description).to.equal(description); }); + describe('translations on poll.updated', () => { + const nameI18n = { en_text: 'XY', es_text: 'XY (es)', language: 'en' }; + const descriptionI18n = { en_text: 'About', es_text: 'Acerca de', language: 'en' }; + + it('stores the translations of an edited question', () => { + const poll = new Poll({ client, poll: { ...pollResponse, name_i18n: nameI18n } }); + const editedNameI18n = { en_text: 'XYZ', es_text: 'XYZ (es)', language: 'en' }; + + poll.handlePollUpdated({ + type: 'poll.updated', + poll: { ...pollResponse, name: 'XYZ', name_i18n: editedNameI18n }, + }); + + expect(poll.data.name).to.equal('XYZ'); + expect(poll.data.name_i18n).to.eql(editedNameI18n); + }); + + it('stores a language added after the poll was sent', () => { + const poll = new Poll({ client, poll: { ...pollResponse, name_i18n: nameI18n } }); + const withGerman = { ...nameI18n, de_text: 'XY (de)' }; + + poll.handlePollUpdated({ + type: 'poll.updated', + poll: { ...pollResponse, name_i18n: withGerman }, + }); + + expect(poll.data.name_i18n).to.eql(withGerman); + }); + + it('stores the translations of the description', () => { + const poll = new Poll({ client, poll: pollResponse }); + + poll.handlePollUpdated({ + type: 'poll.updated', + poll: { + ...pollResponse, + description: 'About', + description_i18n: descriptionI18n, + }, + }); + + expect(poll.data.description_i18n).to.eql(descriptionI18n); + }); + + it('clears translations the event no longer carries', () => { + const poll = new Poll({ + client, + poll: { ...pollResponse, description_i18n: descriptionI18n, name_i18n: nameI18n }, + }); + + poll.handlePollUpdated({ + type: 'poll.updated', + poll: { ...pollResponse, name: 'XYZ' }, + }); + + expect(poll.data.name_i18n).to.be.undefined; + expect(poll.data.description_i18n).to.be.undefined; + }); + }); + it("should not update poll state when handlePollUpdated is called with other poll's event", () => { const poll = new Poll({ client, poll: { ...pollResponse, id: 'X' } }); const description = 'Description update'; diff --git a/test/unit/poll_manager.test.ts b/test/unit/poll_manager.test.ts index 6d1da0cd5..3661af186 100644 --- a/test/unit/poll_manager.test.ts +++ b/test/unit/poll_manager.test.ts @@ -589,6 +589,21 @@ describe('PollManager', () => { expect(spy2.calledOnce).to.be.false; }); + it('should keep the translations of poll.updated in the cached poll', () => { + const nameI18n = { de_text: 'Neuer Titel', en_text: 'New title', language: 'en' }; + + client.dispatchEvent({ + type: 'poll.updated', + poll: { + ...pollMessage1.poll, + name: 'New title', + name_i18n: nameI18n, + } as PollResponse, + }); + + expect(pollManager.fromState(pollId1)?.data.name_i18n).to.eql(nameI18n); + }); + const eventHandlerPairs = [ ['poll.closed', 'handlePollClosed'], ['poll.vote_casted', 'handleVoteCasted'],