diff --git a/package/src/store/__tests__/selectMessagesForChannels.test.ts b/package/src/store/__tests__/selectMessagesForChannels.test.ts index d2fc875d0f..d4b2155237 100644 --- a/package/src/store/__tests__/selectMessagesForChannels.test.ts +++ b/package/src/store/__tests__/selectMessagesForChannels.test.ts @@ -1,29 +1,27 @@ import { generateChannelResponse } from '../../mock-builders/generator/channel'; import { generateMessage } from '../../mock-builders/generator/message'; import { BetterSqlite } from '../../test-utils/BetterSqlite'; +import { getChannels } from '../apis/getChannels'; import { selectMessagesForChannels } from '../apis/queries/selectMessagesForChannels'; import { upsertChannels } from '../apis/upsertChannels'; import { SqliteClient } from '../SqliteClient'; /** - * The client treats the cached page as a `queryChannels` page of 25 messages, so a shorter one reads - * as the start of the channel and older messages never load from the server. + * The client reads the cached page as a channel list page of `message_limit` messages, so a page of + * a different size makes it guess wrongly whether older messages exist, for example that a channel + * with a long history starts at its cached messages. */ describe('selectMessagesForChannels', () => { + let cid: string; + let messages: ReturnType[]; + beforeEach(async () => { await SqliteClient.initializeDatabase(); await BetterSqlite.openDB(); - }); - - afterEach(() => { - BetterSqlite.dropAllTables(); - BetterSqlite.closeDB(); - }); - it("returns a channel's newest 25 messages, oldest first", async () => { const channelResponse = generateChannelResponse({ members: [], messages: [] }); - const cid = channelResponse.channel.cid as string; - const messages = Array.from({ length: 30 }, (_, i) => + cid = channelResponse.channel.cid as string; + messages = Array.from({ length: 30 }, (_, i) => generateMessage({ cid, timestamp: Date.UTC(2020, 0, 1, 0, i) }), ); channelResponse.messages = messages as unknown as typeof channelResponse.messages; @@ -31,9 +29,33 @@ describe('selectMessagesForChannels', () => { channels: [channelResponse] as unknown as Parameters[0]['channels'], isLatestMessagesSet: true, }); + }); + + afterEach(() => { + BetterSqlite.dropAllTables(); + BetterSqlite.closeDB(); + }); + const ids = (rows: { id: string }[]) => rows.map((row) => row.id); + + it("returns a channel's newest 25 messages, oldest first, by default", async () => { const rows = await selectMessagesForChannels([cid]); - expect(rows.map((row) => row.id)).toEqual(messages.slice(-25).map((message) => message.id)); + expect(ids(rows)).toEqual(ids(messages.slice(-25))); + }); + + it("returns a channel's newest `messageLimit` messages", async () => { + expect(ids(await selectMessagesForChannels([cid], 10))).toEqual(ids(messages.slice(-10))); + expect(ids(await selectMessagesForChannels([cid], 50))).toEqual(ids(messages)); + }); + + it('applies the limit that getChannels is given', async () => { + const [channel] = await getChannels({ + channelIds: [cid], + messageLimit: 10, + userId: 'some-user', + }); + + expect(ids(channel.messages)).toEqual(ids(messages.slice(-10))); }); }); diff --git a/package/src/store/apis/getChannelMessages.ts b/package/src/store/apis/getChannelMessages.ts index 6a3a3a80c6..36f527c6d6 100644 --- a/package/src/store/apis/getChannelMessages.ts +++ b/package/src/store/apis/getChannelMessages.ts @@ -12,15 +12,18 @@ import type { TableRow, TableRowJoinedUser } from '../types'; export const getChannelMessages = async ({ channelIds, userId, + messageLimit, }: { channelIds: string[]; userId: string; + messageLimit?: number; }) => { SqliteClient.logger?.('info', 'getChannelMessages', { channelIds, userId, + messageLimit, }); - const messageRows = await selectMessagesForChannels(channelIds); + const messageRows = await selectMessagesForChannels(channelIds, messageLimit); const messageIds = messageRows.map(({ id }) => id); const reactionRows = await selectReactionsForMessages(messageIds, null); diff --git a/package/src/store/apis/getChannels.ts b/package/src/store/apis/getChannels.ts index edbbdf4f16..cb1b2c131c 100644 --- a/package/src/store/apis/getChannels.ts +++ b/package/src/store/apis/getChannels.ts @@ -16,16 +16,19 @@ import { SqliteClient } from '../SqliteClient'; * * @param params.channelIds The channel IDs to fetch. * @param params.userId The ID of the current user. + * @param params.messageLimit How many of each channel's newest messages to return, 25 by default. * @returns The channels with their state. */ export const getChannels = async ({ channelIds, userId, + messageLimit, }: { channelIds: string[]; userId: string; + messageLimit?: number; }): Promise[]> => { - SqliteClient.logger?.('info', 'getChannels', { channelIds, userId }); + SqliteClient.logger?.('info', 'getChannels', { channelIds, userId, messageLimit }); const [channels, cidVsDraft, cidVsMembers, cidVsReads, cidVsMessages, cidVsActiveLocations] = await Promise.all([ @@ -36,6 +39,7 @@ export const getChannels = async ({ getChannelMessages({ channelIds, userId, + messageLimit, }), getChannelActiveLocations({ channelIds }), ]); diff --git a/package/src/store/apis/getChannelsForFilterSort.ts b/package/src/store/apis/getChannelsForFilterSort.ts index 6e4e3f9a78..270a81a125 100644 --- a/package/src/store/apis/getChannelsForFilterSort.ts +++ b/package/src/store/apis/getChannelsForFilterSort.ts @@ -56,6 +56,7 @@ export const getChannelsForFilterSort = async ({ channels: await getChannels({ channelIds: cids, userId, + messageLimit: options?.message_limit, }), predefinedFilter, }; diff --git a/package/src/store/apis/queries/selectMessagesForChannels.ts b/package/src/store/apis/queries/selectMessagesForChannels.ts index 6d556a1a7d..c6257bf039 100644 --- a/package/src/store/apis/queries/selectMessagesForChannels.ts +++ b/package/src/store/apis/queries/selectMessagesForChannels.ts @@ -2,8 +2,12 @@ import { tables } from '../../schema'; import { SqliteClient } from '../../SqliteClient'; import type { TableRowJoinedUser } from '../../types'; +/** The `message_limit` the client assumes for a channel list query that sets none. */ +export const DEFAULT_CHANNEL_LIST_MESSAGE_LIMIT = 25; + export const selectMessagesForChannels = async ( cids: string[], + messageLimit: number = DEFAULT_CHANNEL_LIST_MESSAGE_LIMIT, ): Promise[]> => { const questionMarks = Array(cids.length).fill('?').join(','); const messagesColumnNames = Object.keys(tables.messages.columns) @@ -15,6 +19,7 @@ export const selectMessagesForChannels = async ( SqliteClient.logger?.('info', 'selectMessagesForChannels', { cids, + messageLimit, }); const result = await SqliteClient.executeSql( @@ -38,9 +43,9 @@ export const selectMessagesForChannels = async ( LEFT JOIN users b ON b.id = a.userId - WHERE RowNum <= 25 + WHERE RowNum <= ? ORDER BY a.createdAt ASC`, - cids, + [...cids, messageLimit], ); return result.map((r) => JSON.parse(r.value));