diff --git a/src/utils.ts b/src/utils.ts index 3e6a2ebe1..89f28e20b 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -310,12 +310,18 @@ export function formatMessage({ needle, + returnOnMidMatch, sortedArray, selectValueToCompare = (e) => e, sortDirection = 'ascending', }: { needle: T; sortedArray: readonly T[]; + /** + * Returns the index of the midpoint if it matches the target value. + * Should be enabled only if the searched array cannot contain duplicates. + */ + returnOnMidMatch?: boolean; /** * In array of objects (like messages), pick a specific * property to compare needle value to. @@ -353,7 +359,7 @@ export const findIndexInSortedArray = ({ const comparableMiddle = selectValueToCompare(sortedArray[middle]); - if (comparableNeedle === comparableMiddle) return middle; + if (returnOnMidMatch && comparableNeedle === comparableMiddle) return middle; if ( (sortDirection === 'ascending' && comparableNeedle < comparableMiddle) || diff --git a/test/unit/channel_state.js b/test/unit/channel_state.js index 945d9b54e..6b741f105 100644 --- a/test/unit/channel_state.js +++ b/test/unit/channel_state.js @@ -97,21 +97,39 @@ describe('ChannelState addMessagesSorted', function () { it('add a message with same created_at', async function () { const state = new ChannelState(); - - for (let i = 0; i < 10; i++) { - state.addMessagesSorted([generateMsg({ id: `${i}`, date: `2020-01-01T00:00:00.00${i}Z` })]); + const pairCount = 10; + const msgCount = pairCount * 2; + + for (let i = 0; i < msgCount; i += 2) { + state.addMessagesSorted([ + generateMsg({ id: `${i}`, date: `2020-01-01T00:00:00.0${i.toString().padStart(2, '0')}Z` }), + generateMsg({ id: `${i + 1}`, date: `2020-01-01T00:00:00.0${i.toString().padStart(2, '0')}Z` }), + ]); } - for (let i = 10; i < state.messages.length - 1; i++) { - for (let j = i + 1; i < state.messages.length - 1; j++) - expect(state.messages[i].created_at.getTime()).to.be.lessThan(state.messages[j].created_at.getTime()); + for (let i = 0; i < msgCount; i += 2) { + expect(state.messages[i].created_at.getTime()).to.be.eq(state.messages[i + 1].created_at.getTime()); + if (i + 2 < msgCount) { + expect(state.messages[i].created_at.getTime()).to.be.lessThan( + state.messages[i + 2].created_at.getTime(), + ); + } } - expect(state.messages).to.have.length(10); - state.addMessagesSorted([generateMsg({ id: 'id', date: `2020-01-01T00:00:00.007Z` })]); - expect(state.messages).to.have.length(11); - expect(state.messages[7].id).to.be.equal('id'); - expect(state.messages[8].id).to.be.equal('7'); + expect(state.messages).to.have.length(msgCount); + state.addMessagesSorted([generateMsg({ id: '1stAdded', date: `2020-01-01T00:00:00.014Z` })]); + + expect(state.messages).to.have.length(msgCount + 1); + expect(state.messages[14].id).to.be.equal('14'); + expect(state.messages[15].id).to.be.equal('15'); + expect(state.messages[16].id).to.be.equal('1stAdded'); + state.addMessagesSorted([generateMsg({ id: '2ndAdded', date: `2020-01-01T00:00:00.014Z` })]); + + expect(state.messages).to.have.length(msgCount + 2); + expect(state.messages[14].id).to.be.equal('14'); + expect(state.messages[15].id).to.be.equal('15'); + expect(state.messages[16].id).to.be.equal('1stAdded'); + expect(state.messages[17].id).to.be.equal('2ndAdded'); }); it('add lots of messages in order', async function () { diff --git a/test/unit/utils.test.ts b/test/unit/utils.test.ts index df12464e0..7e95c1954 100644 --- a/test/unit/utils.test.ts +++ b/test/unit/utils.test.ts @@ -125,29 +125,53 @@ describe('findIndexInSortedArray', () => { const messages = generateMessages({ sort: 'asc' }).map(formatMessage); it('finds index of the message with closest matching created_at', () => { - const newMessage = formatMessage(generateMsg({ created_at: new Date(timestamp + 22 * 1000) }) as MessageResponse); - - const index = findIndexInSortedArray({ - needle: newMessage, - sortedArray: messages, - sortDirection: 'ascending', - selectValueToCompare: (v) => v.created_at.getTime(), + [ + { + newMessage: formatMessage(generateMsg({ created_at: new Date(timestamp + 22 * 1000) }) as MessageResponse), + returnOnMidMatch: true, + }, + { + newMessage: formatMessage(generateMsg({ created_at: new Date(timestamp + 22 * 1000) }) as MessageResponse), + returnOnMidMatch: false, + }, + ].forEach(({ newMessage, returnOnMidMatch }) => { + const index = findIndexInSortedArray({ + needle: newMessage, + returnOnMidMatch, + sortedArray: messages, + sortDirection: 'ascending', + selectValueToCompare: (v) => v.created_at.getTime(), + }); + + expect(index).to.equal(3); }); - - expect(index).to.equal(3); }); it('finds exact index', () => { - const newMessage = formatMessage(generateMsg({ created_at: new Date(timestamp + 20 * 1000) }) as MessageResponse); - - const index = findIndexInSortedArray({ - needle: newMessage, - sortedArray: messages, - sortDirection: 'ascending', - selectValueToCompare: (v) => v.created_at.getTime(), + [ + { + newMessage: formatMessage(generateMsg({ created_at: new Date(timestamp + 20 * 1000) }) as MessageResponse), + returnOnMidMatch: true, + }, + { + newMessage: formatMessage(generateMsg({ created_at: new Date(timestamp + 20 * 1000) }) as MessageResponse), + returnOnMidMatch: false, + }, + ].forEach(({ newMessage, returnOnMidMatch }) => { + const index = findIndexInSortedArray({ + needle: newMessage, + returnOnMidMatch, + sortedArray: messages, + sortDirection: 'ascending', + selectValueToCompare: (v) => v.created_at.getTime(), + }); + + if (returnOnMidMatch) { + expect(index).to.equal(2); + } else { + expect(index).to.equal(3); + } }); - - expect(index).to.equal(2); }); }); @@ -155,29 +179,52 @@ describe('findIndexInSortedArray', () => { const messages = generateMessages({ sort: 'desc' }).map(formatMessage); it('finds index of the message with closest matching created_at', () => { - const newMessage = formatMessage(generateMsg({ created_at: new Date(timestamp + 22 * 1000) }) as MessageResponse); - - const index = findIndexInSortedArray({ - needle: newMessage, - sortedArray: messages, - sortDirection: 'descending', - selectValueToCompare: (v) => v.created_at.getTime(), + [ + { + newMessage: formatMessage(generateMsg({ created_at: new Date(timestamp + 22 * 1000) }) as MessageResponse), + returnOnMidMatch: true, + }, + { + newMessage: formatMessage(generateMsg({ created_at: new Date(timestamp + 22 * 1000) }) as MessageResponse), + returnOnMidMatch: false, + }, + ].forEach(({ newMessage, returnOnMidMatch }) => { + const index = findIndexInSortedArray({ + needle: newMessage, + returnOnMidMatch, + sortedArray: messages, + sortDirection: 'descending', + selectValueToCompare: (v) => v.created_at.getTime(), + }); + + expect(index).to.equal(7); }); - - expect(index).to.equal(7); }); it('finds exact index', () => { - const newMessage = formatMessage(generateMsg({ created_at: new Date(timestamp + 10 * 1000) }) as MessageResponse); - - const index = findIndexInSortedArray({ - needle: newMessage, - sortedArray: messages, - sortDirection: 'descending', - selectValueToCompare: (v) => v.created_at.getTime(), + [ + { + newMessage: formatMessage(generateMsg({ created_at: new Date(timestamp + 10 * 1000) }) as MessageResponse), + returnOnMidMatch: true, + }, + { + newMessage: formatMessage(generateMsg({ created_at: new Date(timestamp + 10 * 1000) }) as MessageResponse), + returnOnMidMatch: false, + }, + ].forEach(({ newMessage, returnOnMidMatch }) => { + const index = findIndexInSortedArray({ + needle: newMessage, + returnOnMidMatch, + sortedArray: messages, + sortDirection: 'descending', + selectValueToCompare: (v) => v.created_at.getTime(), + }); + if (returnOnMidMatch) { + expect(index).to.equal(8); + } else { + expect(index).to.equal(9); + } }); - - expect(index).to.equal(8); }); }); });