Skip to content

Commit

Permalink
fix: hide search by midpoint behind a flag
Browse files Browse the repository at this point in the history
  • Loading branch information
MartinCupela committed Oct 21, 2024
1 parent 544753a commit d009882
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 48 deletions.
8 changes: 7 additions & 1 deletion src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -310,12 +310,18 @@ export function formatMessage<StreamChatGenerics extends ExtendableGenerics = De

export const findIndexInSortedArray = <T, L>({
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.
Expand Down Expand Up @@ -353,7 +359,7 @@ export const findIndexInSortedArray = <T, L>({

const comparableMiddle = selectValueToCompare(sortedArray[middle]);

if (comparableNeedle === comparableMiddle) return middle;
if (returnOnMidMatch && comparableNeedle === comparableMiddle) return middle;

if (
(sortDirection === 'ascending' && comparableNeedle < comparableMiddle) ||
Expand Down
40 changes: 29 additions & 11 deletions test/unit/channel_state.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 () {
Expand Down
119 changes: 83 additions & 36 deletions test/unit/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,59 +125,106 @@ 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);
});
});

describe('descending order', () => {
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);
});
});
});

0 comments on commit d009882

Please sign in to comment.