diff --git a/src/channel_state.ts b/src/channel_state.ts index e49f719c4..731a9c7a4 100644 --- a/src/channel_state.ts +++ b/src/channel_state.ts @@ -152,6 +152,15 @@ export class ChannelState { ); } + pruneOldest(maxMessages: number) { + const currentIndex = this.messageSets.findIndex((s) => s.isCurrent); + if (this.messageSets[currentIndex].isLatest) { + const newMessages = this.messageSets[currentIndex].messages; + this.messageSets[currentIndex].messages = newMessages.slice(-maxMessages); + this.messageSets[currentIndex].pagination.hasPrev = true; + } + } + /** * addMessageSorted - Add a message to the state * @@ -821,7 +830,7 @@ export class ChannelState { messages: [], isLatest: true, isCurrent: true, - pagination: DEFAULT_MESSAGE_SET_PAGINATION, + pagination: { ...DEFAULT_MESSAGE_SET_PAGINATION }, }, ]; } @@ -1017,7 +1026,7 @@ export class ChannelState { messages: [], isCurrent: false, isLatest: false, - pagination: DEFAULT_MESSAGE_SET_PAGINATION, + pagination: { ...DEFAULT_MESSAGE_SET_PAGINATION }, }); targetMessageSetIndex = this.messageSets.length - 1; } else { @@ -1026,7 +1035,7 @@ export class ChannelState { messages: [], isCurrent: false, isLatest, - pagination: DEFAULT_MESSAGE_SET_PAGINATION, // fixme: it is problematic decide about pagination without having data + pagination: { ...DEFAULT_MESSAGE_SET_PAGINATION }, // fixme: it is problematic decide about pagination without having data }); if (isLatest) { this.messageSets.slice(1).forEach((set) => { diff --git a/src/constants.ts b/src/constants.ts index 29d3e1151..94c66e146 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,10 +1,13 @@ export const DEFAULT_QUERY_CHANNELS_MESSAGE_LIST_PAGE_SIZE = 25; export const DEFAULT_QUERY_CHANNEL_MESSAGE_LIST_PAGE_SIZE = 100; -export const DEFAULT_MESSAGE_SET_PAGINATION = { hasNext: false, hasPrev: false }; +export const DEFAULT_MESSAGE_SET_PAGINATION = Object.freeze({ + hasNext: false, + hasPrev: false, +}); export const DEFAULT_UPLOAD_SIZE_LIMIT_BYTES = 100 * 1024 * 1024; // 100 MB export const API_MAX_FILES_ALLOWED_PER_MESSAGE = 10; export const MAX_CHANNEL_MEMBER_COUNT_IN_CHANNEL_QUERY = 100; -export const RESERVED_UPDATED_MESSAGE_FIELDS = { +export const RESERVED_UPDATED_MESSAGE_FIELDS = Object.freeze({ // Dates should not be converted back to ISO strings as JS looses precision on them (milliseconds) created_at: true, deleted_at: true, @@ -25,7 +28,7 @@ export const RESERVED_UPDATED_MESSAGE_FIELDS = { html: true, __html: true, user: true, -} as const; -export const LOCAL_MESSAGE_FIELDS = { error: true } as const; +}); +export const LOCAL_MESSAGE_FIELDS = Object.freeze({ error: true }); export const DEFAULT_QUERY_CHANNELS_RETRY_COUNT = 3; export const DEFAULT_QUERY_CHANNELS_MS_BETWEEN_RETRIES = 1000; // 1 second diff --git a/test/unit/channel_state.test.js b/test/unit/channel_state.test.js index 165b8c8ca..ae0a1f961 100644 --- a/test/unit/channel_state.test.js +++ b/test/unit/channel_state.test.js @@ -910,6 +910,87 @@ describe('ChannelState addMessagesSorted', function () { }); }); +describe('ChannelState message pruning', () => { + let channelState; + let initialMessages = []; + + beforeEach(() => { + channelState = new ChannelState(); + initialMessages = Array.from({ length: 10 }, () => + generateMsg({ date: toISOString(100) }), + ); + channelState.addMessagesSorted(initialMessages); + }); + + it('should prune messages from the end when we are in the latest set', () => { + expect(channelState.messageSets.length).to.be.equal(1); + expect(channelState.messageSets[0].isLatest).to.be.equal(true); + expect(channelState.messageSets[0].isCurrent).to.be.equal(true); + expect(channelState.messages.length).to.be.equal(10); + expect(channelState.messagePagination.hasPrev).to.be.equal(false); + + const previousHasNext = channelState.messagePagination.hasNext; + + channelState.pruneOldest(5); + + expect(channelState.messageSets.length).to.be.equal(1); + expect(channelState.messages.length).to.be.equal(5); + expect(channelState.messagePagination.hasPrev).to.be.equal(true); + expect(channelState.messagePagination.hasNext).to.be.equal(previousHasNext); + }); + + it('should do nothing if the current message set is not also the latest', () => { + expect(channelState.messageSets.length).to.be.equal(1); + + channelState.messageSets[0].isLatest = false; + + expect(channelState.messages.length).to.be.equal(10); + expect(channelState.messagePagination.hasPrev).to.be.equal(false); + + channelState.pruneOldest(5); + + expect(channelState.messages.length).to.be.equal(10); + expect(channelState.messagePagination.hasPrev).to.be.equal(false); + }); + + it('should prune the correct messageSet', () => { + channelState.addMessagesSorted( + Array.from({ length: 10 }, () => generateMsg({ date: toISOString(50) })), + false, + true, + true, + 'new', + ); + + expect(channelState.messageSets.length).to.be.equal(2); + + channelState.pruneOldest(5); + + const currentMessageSet = channelState.messageSets.find((ms) => ms.isCurrent); + const otherMessageSet = channelState.messageSets.find((ms) => !ms.isCurrent); + + expect(currentMessageSet.messages.length).to.be.equal(5); + expect(currentMessageSet.pagination.hasPrev).to.be.equal(true); + expect(channelState.messages).to.be.equal(currentMessageSet.messages); + + expect(otherMessageSet.messages.length).to.be.equal(10); + expect(otherMessageSet.pagination.hasPrev).to.be.equal(false); + }); + + it('should correctly apply pruning', () => { + channelState.pruneOldest(5); + + expect(channelState.messages.length).to.be.equal(5); + for (const message of initialMessages.slice(-5)) { + expect(channelState.messages.some((m) => m.id === message.id)).to.be.equal(true); + } + + for (const message of initialMessages.slice(0, 5)) { + expect(channelState.messages.some((m) => m.id === message.id)).to.be.equal(false); + } + }); +}); + describe('ChannelState reactions', () => { const message = generateMsg(); let state;