diff --git a/src/channel.ts b/src/channel.ts index 04b7191da..ffb9cdadc 100644 --- a/src/channel.ts +++ b/src/channel.ts @@ -1,5 +1,5 @@ import { ChannelState } from './channel_state'; -import { logChatPromiseExecution, normalizeQuerySort } from './utils'; +import { logChatPromiseExecution, messageSetPagination, normalizeQuerySort } from './utils'; import { StreamChat } from './client'; import { APIResponse, @@ -58,6 +58,7 @@ import { AscDesc, } from './types'; import { Role } from './permissions'; +import { DEFAULT_QUERY_CHANNEL_MESSAGE_LIST_PAGE_SIZE } from './constants'; /** * Channel - The Channel class manages it's own state. @@ -1002,7 +1003,7 @@ export class Channel>} Returns a query response */ async query( - options: ChannelQueryOptions, + options?: ChannelQueryOptions, messageSetToAddToIfDoesNotExist: MessageSetType = 'current', ) { // Make sure we wait for the connect promise if there is a pending one @@ -1046,6 +1047,16 @@ export class Channel = Record< string, @@ -56,11 +58,7 @@ export class ChannelState['formatMessage']>>; - }[] = []; + messageSets: MessageSet[] = []; constructor(channel: Channel) { this._channel = channel; this.watcher_count = 0; @@ -108,6 +106,10 @@ export class ChannelState s.isCurrent)?.pagination || DEFAULT_MESSAGE_SET_PAGINATION; + } + /** * addMessageSorted - Add a message to the state * @@ -717,7 +719,7 @@ export class ChannelState !m.parent_id)) { - this.messageSets.push({ messages: [], isCurrent: false, isLatest: false }); + this.messageSets.push({ + messages: [], + isCurrent: false, + isLatest: false, + pagination: DEFAULT_MESSAGE_SET_PAGINATION, + }); targetMessageSetIndex = this.messageSets.length - 1; } break; @@ -846,6 +854,14 @@ export class ChannelState { target.isLatest = target.isLatest || messageSet.isLatest; target.isCurrent = target.isCurrent || messageSet.isCurrent; + target.pagination.hasPrev = + messageSet.messages[0].created_at < target.messages[0].created_at + ? messageSet.pagination.hasPrev + : target.pagination.hasPrev; + target.pagination.hasNext = + target.messages.slice(-1)[0].created_at < messageSet.messages.slice(-1)[0].created_at + ? messageSet.pagination.hasNext + : target.pagination.hasNext; messagesToAdd = [...messagesToAdd, ...messageSet.messages]; }); sources.forEach((s) => this.messageSets.splice(this.messageSets.indexOf(s), 1)); diff --git a/src/client.ts b/src/client.ts index f5c641876..0aa24d247 100644 --- a/src/client.ts +++ b/src/client.ts @@ -21,6 +21,7 @@ import { isFunction, isOnline, isOwnUserBaseProperty, + messageSetPagination, normalizeQuerySort, randomId, retryInterval, @@ -207,6 +208,7 @@ import { import { InsightMetrics, postInsights } from './insights'; import { Thread } from './thread'; import { Moderation } from './moderation'; +import { DEFAULT_QUERY_CHANNELS_MESSAGE_LIST_PAGE_SIZE } from './constants'; function isString(x: unknown): x is string { return typeof x === 'string' || x instanceof String; @@ -1601,7 +1603,7 @@ export class StreamChat[] = [], stateOptions: ChannelStateOptions = {}, + queryChannelsOptions?: ChannelOptions, ) { const { skipInitialization, offlineMode = false } = stateOptions; - - for (const channelState of channelsFromApi) { - this._addChannelConfig(channelState.channel); - } - const channels: Channel[] = []; for (const channelState of channelsFromApi) { + this._addChannelConfig(channelState.channel); const c = this.channel(channelState.channel.type, channelState.channel.id); c.data = channelState.channel; c.offlineMode = offlineMode; c.initialized = !offlineMode; + let updatedMessagesSet; if (skipInitialization === undefined) { - c._initializeState(channelState, 'latest'); + const { messageSet } = c._initializeState(channelState, 'latest'); + updatedMessagesSet = messageSet; } else if (!skipInitialization.includes(channelState.channel.id)) { c.state.clearMessages(); - c._initializeState(channelState, 'latest'); + const { messageSet } = c._initializeState(channelState, 'latest'); + updatedMessagesSet = messageSet; + } + + if (updatedMessagesSet) { + updatedMessagesSet.pagination = { + ...updatedMessagesSet.pagination, + ...messageSetPagination({ + parentSet: updatedMessagesSet, + requestedPageSize: queryChannelsOptions?.message_limit || DEFAULT_QUERY_CHANNELS_MESSAGE_LIST_PAGE_SIZE, + returnedPage: channelState.messages, + logger: this.logger, + }), + }; } channels.push(c); diff --git a/src/constants.ts b/src/constants.ts new file mode 100644 index 000000000..26fddaaf2 --- /dev/null +++ b/src/constants.ts @@ -0,0 +1,4 @@ +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: true, hasPrev: true }; diff --git a/src/types.ts b/src/types.ts index c96d8b31b..556624f41 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1052,7 +1052,7 @@ export type PaginationOptions = { id_lt?: string; id_lte?: string; limit?: number; - offset?: number; + offset?: number; // should be avoided with channel.query() }; export type MessagePaginationOptions = PaginationOptions & { @@ -2900,6 +2900,12 @@ export type ImportTask = { }; export type MessageSetType = 'latest' | 'current' | 'new'; +export type MessageSet = { + isCurrent: boolean; + isLatest: boolean; + messages: FormatMessageResponse[]; + pagination: { hasNext: boolean; hasPrev: boolean }; +}; export type PushProviderUpsertResponse = { push_provider: PushProvider; diff --git a/src/utils.ts b/src/utils.ts index 3730df582..f086a0f49 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -3,12 +3,15 @@ import { AscDesc, ExtendableGenerics, DefaultGenerics, + Logger, OwnUserBase, OwnUserResponse, UserResponse, MessageResponse, FormatMessageResponse, ReactionGroupResponse, + MessageSet, + MessagePaginationOptions, } from './types'; import { AxiosRequestConfig } from 'axios'; @@ -396,3 +399,233 @@ function maybeGetReactionGroupsFallback( return null; } + +type MessagePaginationUpdatedParams = { + parentSet: MessageSet; + requestedPageSize: number; + returnedPage: MessageResponse[]; + logger?: Logger; + messagePaginationOptions?: MessagePaginationOptions; +}; + +export function binarySearchByDateEqualOrNearestGreater( + array: { + created_at?: string; + }[], + targetDate: Date, +): number { + let left = 0; + let right = array.length - 1; + + while (left <= right) { + const mid = Math.floor((left + right) / 2); + const midCreatedAt = array[mid].created_at; + if (!midCreatedAt) { + left += 1; + continue; + } + const midDate = new Date(midCreatedAt); + + if (midDate.getTime() === targetDate.getTime()) { + return mid; + } else if (midDate.getTime() < targetDate.getTime()) { + left = mid + 1; + } else { + right = mid - 1; + } + } + + return left; +} + +const messagePaginationCreatedAtAround = ({ + parentSet, + requestedPageSize, + returnedPage, + messagePaginationOptions, +}: MessagePaginationUpdatedParams) => { + const newPagination = { ...parentSet.pagination }; + if (!messagePaginationOptions?.created_at_around) return newPagination; + let hasPrev; + let hasNext; + let updateHasPrev; + let updateHasNext; + const createdAtAroundDate = new Date(messagePaginationOptions.created_at_around); + const [firstPageMsg, lastPageMsg] = [returnedPage[0], returnedPage.slice(-1)[0]]; + + // expect ASC order (from oldest to newest) + const wholePageHasNewerMessages = + !!firstPageMsg?.created_at && new Date(firstPageMsg.created_at) > createdAtAroundDate; + const wholePageHasOlderMessages = !!lastPageMsg?.created_at && new Date(lastPageMsg.created_at) < createdAtAroundDate; + + const requestedPageSizeNotMet = + requestedPageSize > parentSet.messages.length && requestedPageSize > returnedPage.length; + const noMoreMessages = + (requestedPageSize > parentSet.messages.length || parentSet.messages.length >= returnedPage.length) && + requestedPageSize > returnedPage.length; + + if (wholePageHasNewerMessages) { + hasPrev = false; + updateHasPrev = true; + if (requestedPageSizeNotMet) { + hasNext = false; + updateHasNext = true; + } + } else if (wholePageHasOlderMessages) { + hasNext = false; + updateHasNext = true; + if (requestedPageSizeNotMet) { + hasPrev = false; + updateHasPrev = true; + } + } else if (noMoreMessages) { + hasNext = hasPrev = false; + updateHasPrev = updateHasNext = true; + } else { + const [firstPageMsgIsFirstInSet, lastPageMsgIsLastInSet] = [ + firstPageMsg?.id && firstPageMsg.id === parentSet.messages[0]?.id, + lastPageMsg?.id && lastPageMsg.id === parentSet.messages.slice(-1)[0]?.id, + ]; + updateHasPrev = firstPageMsgIsFirstInSet; + updateHasNext = lastPageMsgIsLastInSet; + const midPointByCount = Math.floor(returnedPage.length / 2); + const midPointByCreationDate = binarySearchByDateEqualOrNearestGreater(returnedPage, createdAtAroundDate); + + if (midPointByCreationDate !== -1) { + hasPrev = midPointByCount <= midPointByCreationDate; + hasNext = midPointByCount >= midPointByCreationDate; + } + } + + if (updateHasPrev && typeof hasPrev !== 'undefined') newPagination.hasPrev = hasPrev; + if (updateHasNext && typeof hasNext !== 'undefined') newPagination.hasNext = hasNext; + + return newPagination; +}; + +const messagePaginationIdAround = ({ + parentSet, + requestedPageSize, + returnedPage, + messagePaginationOptions, +}: MessagePaginationUpdatedParams) => { + const newPagination = { ...parentSet.pagination }; + const { id_around } = messagePaginationOptions || {}; + if (!id_around) return newPagination; + let hasPrev; + let hasNext; + + const [firstPageMsg, lastPageMsg] = [returnedPage[0], returnedPage.slice(-1)[0]]; + const [firstPageMsgIsFirstInSet, lastPageMsgIsLastInSet] = [ + firstPageMsg?.id === parentSet.messages[0]?.id, + lastPageMsg?.id === parentSet.messages.slice(-1)[0]?.id, + ]; + let updateHasPrev = firstPageMsgIsFirstInSet; + let updateHasNext = lastPageMsgIsLastInSet; + + const midPoint = Math.floor(returnedPage.length / 2); + const noMoreMessages = + (requestedPageSize > parentSet.messages.length || parentSet.messages.length >= returnedPage.length) && + requestedPageSize > returnedPage.length; + + if (noMoreMessages) { + hasNext = hasPrev = false; + updateHasPrev = updateHasNext = true; + } else if (!returnedPage[midPoint]) { + return newPagination; + } else if (returnedPage[midPoint].id === id_around) { + hasPrev = hasNext = true; + } else { + let targetMsg; + const halves = [returnedPage.slice(0, midPoint), returnedPage.slice(midPoint)]; + hasPrev = hasNext = true; + for (let i = 0; i < halves.length; i++) { + targetMsg = halves[i].find((message) => message.id === id_around); + if (targetMsg && i === 0) { + hasPrev = false; + } + if (targetMsg && i === 1) { + hasNext = false; + } + } + } + + if (updateHasPrev && typeof hasPrev !== 'undefined') newPagination.hasPrev = hasPrev; + if (updateHasNext && typeof hasNext !== 'undefined') newPagination.hasNext = hasNext; + + return newPagination; +}; + +const messagePaginationLinear = ({ + parentSet, + requestedPageSize, + returnedPage, + messagePaginationOptions, +}: MessagePaginationUpdatedParams) => { + const newPagination = { ...parentSet.pagination }; + + let hasPrev; + let hasNext; + + const [firstPageMsg, lastPageMsg] = [returnedPage[0], returnedPage.slice(-1)[0]]; + const [firstPageMsgIsFirstInSet, lastPageMsgIsLastInSet] = [ + firstPageMsg?.id && firstPageMsg.id === parentSet.messages[0]?.id, + lastPageMsg?.id && lastPageMsg.id === parentSet.messages.slice(-1)[0]?.id, + ]; + + const queriedNextMessages = + messagePaginationOptions && + (messagePaginationOptions.created_at_after_or_equal || + messagePaginationOptions.created_at_after || + messagePaginationOptions.id_gt || + messagePaginationOptions.id_gte); + + const queriedPrevMessages = + typeof messagePaginationOptions === 'undefined' + ? true + : messagePaginationOptions.created_at_before_or_equal || + messagePaginationOptions.created_at_before || + messagePaginationOptions.id_lt || + messagePaginationOptions.id_lte || + messagePaginationOptions.offset; + + const containsUnrecognizedOptionsOnly = + !queriedNextMessages && + !queriedPrevMessages && + !messagePaginationOptions?.id_around && + !messagePaginationOptions?.created_at_around; + + const hasMore = returnedPage.length >= requestedPageSize; + + if (typeof queriedPrevMessages !== 'undefined' || containsUnrecognizedOptionsOnly) { + hasPrev = hasMore; + } + if (typeof queriedNextMessages !== 'undefined') { + hasNext = hasMore; + } + const returnedPageIsEmpty = returnedPage.length === 0; + + if ((firstPageMsgIsFirstInSet || returnedPageIsEmpty) && typeof hasPrev !== 'undefined') + newPagination.hasPrev = hasPrev; + if ((lastPageMsgIsLastInSet || returnedPageIsEmpty) && typeof hasNext !== 'undefined') + newPagination.hasNext = hasNext; + + return newPagination; +}; + +export const messageSetPagination = ( + params: MessagePaginationUpdatedParams, +) => { + if (params.parentSet.messages.length < params.returnedPage.length) { + params.logger?.('error', 'Corrupted message set state: parent set size < returned page size'); + return params.parentSet.pagination; + } + + if (params.messagePaginationOptions?.created_at_around) { + return messagePaginationCreatedAtAround(params); + } else if (params.messagePaginationOptions?.id_around) { + return messagePaginationIdAround(params); + } else { + return messagePaginationLinear(params); + } +}; diff --git a/test/unit/channel.js b/test/unit/channel.js index ab724086d..b9a8a5535 100644 --- a/test/unit/channel.js +++ b/test/unit/channel.js @@ -11,6 +11,7 @@ import sinon from 'sinon'; import { mockChannelQueryResponse } from './test-utils/mockChannelQueryResponse'; import { ChannelState, StreamChat } from '../../src'; +import { DEFAULT_QUERY_CHANNEL_MESSAGE_LIST_PAGE_SIZE } from '../../src/constants'; const expect = chai.expect; @@ -1243,3 +1244,35 @@ describe('Channel _initializeState', () => { expect(Object.keys(channel.state.members)).deep.to.be.equal(['alice']); }); }); + +describe('Channel.query', async () => { + it('should not update pagination for queried message set', async () => { + const client = await getClientWithUser(); + const channel = client.channel('messaging', uuidv4()); + const mockedChannelQueryResponse = { + ...mockChannelQueryResponse, + messages: Array.from({ length: DEFAULT_QUERY_CHANNEL_MESSAGE_LIST_PAGE_SIZE }, generateMsg), + }; + const mock = sinon.mock(client); + mock.expects('post').returns(Promise.resolve(mockedChannelQueryResponse)); + await channel.query(); + expect(channel.state.messageSets.length).to.be.equal(1); + expect(channel.state.messageSets[0].pagination).to.eql({ hasNext: true, hasPrev: true }); + mock.restore(); + }); + + it('should update pagination for queried message set to prevent more pagination', async () => { + const client = await getClientWithUser(); + const channel = client.channel('messaging', uuidv4()); + const mockedChannelQueryResponse = { + ...mockChannelQueryResponse, + messages: Array.from({ length: DEFAULT_QUERY_CHANNEL_MESSAGE_LIST_PAGE_SIZE - 1 }, generateMsg), + }; + const mock = sinon.mock(client); + mock.expects('post').returns(Promise.resolve(mockedChannelQueryResponse)); + await channel.query(); + expect(channel.state.messageSets.length).to.be.equal(1); + expect(channel.state.messageSets[0].pagination).to.eql({ hasNext: true, hasPrev: false }); + mock.restore(); + }); +}); diff --git a/test/unit/channel_state.js b/test/unit/channel_state.js index 5e7dc7b2b..7e66e2a69 100644 --- a/test/unit/channel_state.js +++ b/test/unit/channel_state.js @@ -8,6 +8,7 @@ import { getClientWithUser } from './test-utils/getClient'; import { getOrCreateChannelApi } from './test-utils/getOrCreateChannelApi'; import { ChannelState, StreamChat, Channel } from '../../src'; +import { DEFAULT_MESSAGE_SET_PAGINATION } from '../../src/constants'; const expect = chai.expect; @@ -532,7 +533,9 @@ describe('ChannelState addMessagesSorted', function () { const currentMessages = [generateMsg({ id: '10', date: '2020-01-01T00:00:03.001Z' }), ...overlap1]; state.addMessagesSorted(currentMessages, false, true, true, 'new'); state.messageSets[0].isCurrent = false; + state.messageSets[0].pagination = { hasPrev: true, hasNext: false }; state.messageSets[1].isCurrent = true; + state.messageSets[1].pagination = { hasPrev: false, hasNext: true }; const newMessages = [...overlap1, generateMsg({ id: '12', date: '2020-01-01T00:00:14.001Z' }), ...overlap2]; state.addMessagesSorted(newMessages, false, true, true, 'new'); @@ -544,6 +547,7 @@ describe('ChannelState addMessagesSorted', function () { expect(state.messages[4].id).to.be.equal('14'); expect(state.messages).to.be.equal(state.latestMessages); expect(state.messageSets.length).to.be.equal(1); + expect(state.messageSets[0].pagination).to.be.eql({ hasPrev: false, hasNext: false }); }); }); }); @@ -824,6 +828,19 @@ describe('latestMessages', () => { }); }); +describe('messagePagination', () => { + it('is initiated with defaults', () => { + const state = new ChannelState(); + expect(state.messageSets[0].pagination).to.eql(DEFAULT_MESSAGE_SET_PAGINATION); + }); + it('is retrieved as default if not set', () => { + const state = new ChannelState(); + state.messageSets[0].pagination = undefined; + expect(state.messageSets[0].pagination).to.be.undefined; + expect(state.messagePagination).to.eql(DEFAULT_MESSAGE_SET_PAGINATION); + }); +}); + describe('loadMessageIntoState', () => { it('should do nothing if message is available locally in the current set', async () => { const state = new ChannelState(); diff --git a/test/unit/client.js b/test/unit/client.js index 11694f191..ec9889210 100644 --- a/test/unit/client.js +++ b/test/unit/client.js @@ -8,6 +8,8 @@ import * as utils from '../../src/utils'; import { StreamChat } from '../../src/client'; import { ConnectionState } from '../../src/connection_fallback'; import { StableWSConnection } from '../../src/connection'; +import { mockChannelQueryResponse } from './test-utils/mockChannelQueryResponse'; +import { DEFAULT_QUERY_CHANNEL_MESSAGE_LIST_PAGE_SIZE } from '../../src/constants'; const expect = chai.expect; chai.use(chaiAsPromised); @@ -576,3 +578,37 @@ describe('Client WSFallback', () => { expect(client.wsFallback).to.be.equal(fallback); }); }); + +describe('Channel.queryChannels', async () => { + it('should not update pagination for queried message set', async () => { + const client = await getClientWithUser(); + const mockedChannelsQueryResponse = Array.from({ length: 10 }, () => ({ + ...mockChannelQueryResponse, + messages: Array.from({ length: DEFAULT_QUERY_CHANNEL_MESSAGE_LIST_PAGE_SIZE }, generateMsg), + })); + const mock = sinon.mock(client); + mock.expects('post').returns(Promise.resolve(mockedChannelsQueryResponse)); + await client.queryChannels(); + Object.values(client.activeChannels).forEach((channel) => { + expect(channel.state.messageSets.length).to.be.equal(1); + expect(channel.state.messageSets[0].pagination).to.eql({ hasNext: true, hasPrev: true }); + }); + mock.restore(); + }); + + it('should update pagination for queried message set to prevent more pagination', async () => { + const client = await getClientWithUser(); + const mockedChannelQueryResponse = Array.from({ length: 10 }, () => ({ + ...mockChannelQueryResponse, + messages: Array.from({ length: DEFAULT_QUERY_CHANNEL_MESSAGE_LIST_PAGE_SIZE - 1 }, generateMsg), + })); + const mock = sinon.mock(client); + mock.expects('post').returns(Promise.resolve(mockedChannelQueryResponse)); + await client.queryChannels(); + Object.values(client.activeChannels).forEach((channel) => { + expect(channel.state.messageSets.length).to.be.equal(1); + expect(channel.state.messageSets[0].pagination).to.eql({ hasNext: true, hasPrev: false }); + }); + mock.restore(); + }); +}); diff --git a/test/unit/utils.js b/test/unit/utils.js index 332dbd5ed..1a1da8ec0 100644 --- a/test/unit/utils.js +++ b/test/unit/utils.js @@ -1,5 +1,11 @@ import chai from 'chai'; -import { axiosParamsSerializer, formatMessage, normalizeQuerySort } from '../../src/utils'; +import { + axiosParamsSerializer, + binarySearchByDateEqualOrNearestGreater, + formatMessage, + messageSetPagination, + normalizeQuerySort, +} from '../../src/utils'; import sinon from 'sinon'; const expect = chai.expect; @@ -139,3 +145,2568 @@ describe('reaction groups fallback', () => { }); }); }); + +describe('messageSetPagination', () => { + const consoleErrorSpy = () => { + const _consoleError = console.error; + console.error = () => null; + return () => { + console.error = _consoleError; + }; + }; + const messages = [ + { created_at: '2024-08-05T08:55:00.199808Z', id: '0' }, + { created_at: '2024-08-05T08:55:01.199808Z', id: '1' }, + { created_at: '2024-08-05T08:55:02.199808Z', id: '2' }, + { created_at: '2024-08-05T08:55:03.199808Z', id: '3' }, + { created_at: '2024-08-05T08:55:04.199808Z', id: '4' }, + { created_at: '2024-08-05T08:55:05.199808Z', id: '5' }, + { created_at: '2024-08-05T08:55:06.199808Z', id: '6' }, + { created_at: '2024-08-05T08:55:07.199808Z', id: '7' }, + { created_at: '2024-08-05T08:55:08.199808Z', id: '8' }, + ]; + + describe('linear', () => { + describe('returned page size size is 0', () => { + ['created_at_after_or_equal', 'created_at_after', 'id_gt', 'id_gte'].forEach((option) => { + it(`requested page size === returned page size === parent set size pagination with option ${option}`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: messages.length, + returnedPage: [], + parentSet: { messages: [], pagination: {} }, + }), + ).to.eql({ hasNext: false }); + }); + it(`requested page size === parent set size > returned page size with option ${option}`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: 1, + returnedPage: [], + parentSet: { messages: messages.slice(0, 1), pagination: {} }, + }), + ).to.eql({ hasNext: false }); + }); + it(`returned page size === parent set size pagination < requested page size with option ${option}`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: 1, + returnedPage: [], + parentSet: { messages: [], pagination: {} }, + }), + ).to.eql({ hasNext: false }); + }); + it(`requested page size === returned page size < parent set size pagination with option ${option}`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: messages.length, + returnedPage: [], + parentSet: { messages, pagination: {} }, + }), + ).to.eql({ hasNext: false }); + }); + it(`returned page size < parent set size < requested page size pagination with option ${option}`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: 1, + returnedPage: [], + parentSet: { messages, pagination: {} }, + }), + ).to.eql({ hasNext: false }); + }); + }); + + ['created_at_before_or_equal', 'created_at_before', 'id_lt', 'id_lte', undefined, 'unrecognized'].forEach( + (option) => { + it(`requested page size === returned page size === parent set size pagination with option ${option}`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: messages.length, + returnedPage: [], + parentSet: { messages: [], pagination: {} }, + }), + ).to.eql({ hasPrev: false }); + }); + it(`requested page size === parent set size > returned page size with option ${option}`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: 1, + returnedPage: [], + parentSet: { messages: messages.slice(0, 1), pagination: {} }, + }), + ).to.eql({ hasPrev: false }); + }); + it(`returned page size === parent set size pagination < requested page size with option ${option}`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: 1, + returnedPage: [], + parentSet: { messages: [], pagination: {} }, + }), + ).to.eql({ hasPrev: false }); + }); + it(`requested page size === returned page size < parent set size pagination with option ${option}`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: messages.length, + returnedPage: [], + parentSet: { messages, pagination: {} }, + }), + ).to.eql({ hasPrev: false }); + }); + it(`returned page size < parent set size < requested page size pagination with option ${option}`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: 1, + returnedPage: [], + parentSet: { messages, pagination: {} }, + }), + ).to.eql({ hasPrev: false }); + }); + }, + ); + }); + + ['created_at_after_or_equal', 'created_at_after', 'id_gt', 'id_gte'].forEach((option) => { + it(`requested page size === returned page size === parent set size pagination option ${option}`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: messages.length, + returnedPage: messages, + parentSet: { messages, pagination: {} }, + }), + ).to.eql({ hasNext: true }); + }); + + it(`returned page size === parent set size pagination < requested page size option ${option}`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: messages.length + 1, + returnedPage: messages, + parentSet: { messages, pagination: {} }, + }), + ).to.eql({ hasNext: false }); + }); + + it(`returned page size === parent set size pagination > requested page size option ${option}`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: messages.length - 1, + returnedPage: messages, + parentSet: { messages, pagination: {} }, + }), + ).to.eql({ hasNext: true }); + }); + + describe('first (oldest) page message matches the first parent set message', () => { + it(`requested page size === returned page size < parent set size pagination option ${option}`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: messages.length - 1, + returnedPage: messages.slice(0, -1), + parentSet: { messages, pagination: {} }, + }), + ).to.eql({}); + }); + it(`requested page size === parent set size > returned page size option ${option}`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: messages.length, + returnedPage: messages.slice(0, -1), + parentSet: { messages, pagination: {} }, + }), + ).to.eql({}); + }); + it(`requested page size < returned page size < parent set size pagination option ${option}`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: messages.length - 2, + returnedPage: messages.slice(0, -1), + parentSet: { messages, pagination: {} }, + }), + ).to.eql({}); + }); + it(`requested page size < returned page size < parent set size pagination option ${option}`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: messages.length - 3, + returnedPage: messages.slice(0, -2), + parentSet: { messages: messages.slice(0, -1), pagination: {} }, + }), + ).to.eql({}); + }); + it(`returned page size < parent set size < requested page size pagination option ${option}`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: messages.length, + returnedPage: messages.slice(0, -2), + parentSet: { messages: messages.slice(0, -1), pagination: {} }, + }), + ).to.eql({}); + }); + it(`requested page size === returned page size > parent set size pagination option ${option}`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: messages.length, + returnedPage: messages, + parentSet: { messages: messages.slice(0, -1), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + it(`parent set size < returned page size < requested page size pagination option ${option}`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: messages.length, + returnedPage: messages.slice(0, -1), + parentSet: { messages: messages.slice(0, -2), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + it(`requested page size < parent set size < returned page size pagination option ${option}`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: messages.length - 2, + returnedPage: messages, + parentSet: { messages: messages.slice(0, -1), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + }); + + describe('last page message matches the last parent set message', () => { + it(`requested page size === returned page size < parent set size pagination option ${option}`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: messages.length - 1, + returnedPage: messages.slice(1), + parentSet: { messages, pagination: {} }, + }), + ).to.eql({ hasNext: true }); + }); + it(`requested page size === parent set size > returned page size option ${option}`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: messages.length, + returnedPage: messages.slice(1), + parentSet: { messages, pagination: {} }, + }), + ).to.eql({ hasNext: false }); + }); + it(`requested page size < returned page size < parent set size pagination option ${option}`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: messages.length - 2, + returnedPage: messages.slice(1), + parentSet: { messages, pagination: {} }, + }), + ).to.eql({ hasNext: true }); + }); + it(`returned page size < parent set size < requested page size pagination option ${option}`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: messages.length, + returnedPage: messages.slice(2), + parentSet: { messages: messages.slice(1), pagination: {} }, + }), + ).to.eql({ hasNext: false }); + }); + it(`requested page size === returned page size > parent set size pagination option ${option}`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: messages.length, + returnedPage: messages, + parentSet: { messages: messages.slice(-1), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + it(`parent set size < returned page size < requested page size pagination option ${option}`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: messages.length, + returnedPage: messages.slice(1), + parentSet: { messages: messages.slice(2), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + it(`requested page size < parent set size < returned page size pagination option ${option}`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: messages.length - 2, + returnedPage: messages, + parentSet: { messages: messages.slice(1), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + }); + + describe('first page message & last page message do not match the first and last parent set messages', () => { + it(`requested page size === returned page size === parent set size pagination option ${option}`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: messages.length, + returnedPage: [ + messages[1], + messages[0], + ...messages.slice(2, -2), + messages.slice(-1)[0], + messages.slice(-2, -1)[0], + ], + parentSet: { messages, pagination: {} }, + }), + ).to.eql({}); + }); + it(`requested page size === parent set size > returned page size option ${option}`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: messages.length, + returnedPage: messages.slice(1, -1), + parentSet: { messages, pagination: {} }, + }), + ).to.eql({}); + }); + it(`returned page size === parent set size pagination < requested page size option ${option}`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: messages.length + 1, + returnedPage: [ + messages[1], + messages[0], + ...messages.slice(2, -2), + messages.slice(-1)[0], + messages.slice(-2, -1)[0], + ], + parentSet: { messages, pagination: {} }, + }), + ).to.eql({}); + }); + + it(`returned page size === parent set size pagination > requested page size option ${option}`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: messages.length - 1, + returnedPage: [ + messages[1], + messages[0], + ...messages.slice(2, -2), + messages.slice(-1)[0], + messages.slice(-2, -1)[0], + ], + parentSet: { messages, pagination: {} }, + }), + ).to.eql({}); + }); + + it(`requested page size === returned page size < parent set size pagination option ${option}`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: messages.length - 2, + returnedPage: messages.slice(1, -1), + parentSet: { messages, pagination: {} }, + }), + ).to.eql({}); + }); + it(`requested page size < returned page size < parent set size pagination option ${option}`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: messages.length - 3, + returnedPage: messages.slice(1, -1), + parentSet: { messages, pagination: {} }, + }), + ).to.eql({}); + }); + it(`returned page size < parent set size < requested page size pagination option ${option}`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: messages.length + 1, + returnedPage: messages.slice(1, -1), + parentSet: { messages, pagination: {} }, + }), + ).to.eql({}); + }); + it(`requested page size === returned page size > parent set size pagination option ${option}`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: messages.length, + returnedPage: messages, + parentSet: { messages: messages.slice(1, -1), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + it(`parent set size < returned page size < requested page size pagination option ${option}`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: messages.length, + returnedPage: messages.slice(1), + parentSet: { messages: messages.slice(1, -1), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + it(`requested page size < parent set size < returned page size pagination option ${option}`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: messages.length - 3, + returnedPage: messages, + parentSet: { messages: messages.slice(1, -1), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + }); + }); + + ['created_at_before_or_equal', 'created_at_before', 'id_lt', 'id_lte', undefined, 'unrecognized'].forEach( + (option) => { + it(`requested page size === returned page size === parent set size pagination option ${option}`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: messages.length, + returnedPage: messages, + parentSet: { messages, pagination: {} }, + }), + ).to.eql({ hasPrev: true }); + }); + + it(`returned page size === parent set size pagination < requested page size option ${option}`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: messages.length + 1, + returnedPage: messages, + parentSet: { messages, pagination: {} }, + }), + ).to.eql({ hasPrev: false }); + }); + + it(`returned page size === parent set size pagination > requested page size option ${option}`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: messages.length - 1, + returnedPage: messages, + parentSet: { messages, pagination: {} }, + }), + ).to.eql({ hasPrev: true }); + }); + + describe('first (oldest) page message matches the first parent set message', () => { + it(`requested page size === returned page size < parent set size pagination option ${option}`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: messages.length - 1, + returnedPage: messages.slice(0, -1), + parentSet: { messages, pagination: {} }, + }), + ).to.eql({ hasPrev: true }); + }); + it(`requested page size < returned page size < parent set size pagination option ${option}`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: messages.length - 2, + returnedPage: messages.slice(0, -1), + parentSet: { messages, pagination: {} }, + }), + ).to.eql({ hasPrev: true }); + }); + it(`requested page size === parent set size > returned page size option ${option}`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: messages.length, + returnedPage: messages.slice(0, -1), + parentSet: { messages, pagination: {} }, + }), + ).to.eql({ hasPrev: false }); + }); + it(`requested page size < returned page size < parent set size pagination option ${option}`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: messages.length - 3, + returnedPage: messages.slice(0, -2), + parentSet: { messages: messages.slice(0, -1), pagination: {} }, + }), + ).to.eql({ hasPrev: true }); + }); + it(`returned page size < parent set size < requested page size pagination option ${option}`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: messages.length, + returnedPage: messages.slice(0, -2), + parentSet: { messages: messages.slice(0, -1), pagination: {} }, + }), + ).to.eql({ hasPrev: false }); + }); + it(`requested page size === returned page size > parent set size pagination option ${option}`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: messages.length, + returnedPage: messages, + parentSet: { messages: messages.slice(0, -1), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + it(`parent set size < returned page size < requested page size pagination option ${option}`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: messages.length, + returnedPage: messages.slice(0, -1), + parentSet: { messages: messages.slice(0, -2), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + it(`requested page size < parent set size < returned page size pagination option ${option}`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: messages.length - 2, + returnedPage: messages, + parentSet: { messages: messages.slice(0, -1), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + }); + + describe('last page message matches the last parent set message', () => { + it(`requested page size === returned page size < parent set size pagination option ${option}`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: messages.length - 1, + returnedPage: messages.slice(1), + parentSet: { messages, pagination: {} }, + }), + ).to.eql({}); + }); + it(`requested page size === parent set size > returned page size option ${option}`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: messages.length, + returnedPage: messages.slice(1), + parentSet: { messages, pagination: {} }, + }), + ).to.eql({}); + }); + it(`requested page size < returned page size < parent set size pagination option ${option}`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: messages.length - 2, + returnedPage: messages.slice(1), + parentSet: { messages, pagination: {} }, + }), + ).to.eql({}); + }); + it(`returned page size < parent set size < requested page size pagination option ${option}`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: messages.length, + returnedPage: messages.slice(2), + parentSet: { messages: messages.slice(1), pagination: {} }, + }), + ).to.eql({}); + }); + it(`requested page size === returned page size > parent set size pagination option ${option}`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: messages.length, + returnedPage: messages, + parentSet: { messages: messages.slice(-1), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + it(`parent set size < returned page size < requested page size pagination option ${option}`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: messages.length, + returnedPage: messages.slice(1), + parentSet: { messages: messages.slice(2), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + it(`requested page size < parent set size < returned page size pagination option ${option}`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: messages.length - 2, + returnedPage: messages, + parentSet: { messages: messages.slice(1), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + }); + + describe('first page message & last page message do not match the first and last parent set messages', () => { + it(`requested page size === returned page size === parent set size pagination option ${option}`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: messages.length, + returnedPage: [ + messages[1], + messages[0], + ...messages.slice(2, -2), + messages.slice(-1)[0], + messages.slice(-2, -1)[0], + ], + parentSet: { messages, pagination: {} }, + }), + ).to.eql({}); + }); + it(`requested page size === parent set size > returned page size option ${option}`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: messages.length, + returnedPage: messages.slice(1, -1), + parentSet: { messages, pagination: {} }, + }), + ).to.eql({}); + }); + it(`returned page size === parent set size pagination < requested page size option ${option}`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: messages.length + 1, + returnedPage: [ + messages[1], + messages[0], + ...messages.slice(2, -2), + messages.slice(-1)[0], + messages.slice(-2, -1)[0], + ], + parentSet: { messages, pagination: {} }, + }), + ).to.eql({}); + }); + + it(`returned page size === parent set size pagination > requested page size option ${option}`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: messages.length - 1, + returnedPage: [ + messages[1], + messages[0], + ...messages.slice(2, -2), + messages.slice(-1)[0], + messages.slice(-2, -1)[0], + ], + parentSet: { messages, pagination: {} }, + }), + ).to.eql({}); + }); + + it(`requested page size === returned page size < parent set size pagination option ${option}`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: messages.length - 2, + returnedPage: messages.slice(1, -1), + parentSet: { messages, pagination: {} }, + }), + ).to.eql({}); + }); + it(`requested page size < returned page size < parent set size pagination option ${option}`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: messages.length - 3, + returnedPage: messages.slice(1, -1), + parentSet: { messages, pagination: {} }, + }), + ).to.eql({}); + }); + it(`returned page size < parent set size < requested page size pagination option ${option}`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: messages.length + 1, + returnedPage: messages.slice(1, -1), + parentSet: { messages, pagination: {} }, + }), + ).to.eql({}); + }); + it(`requested page size === returned page size > parent set size pagination option ${option}`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: messages.length, + returnedPage: messages, + parentSet: { messages: messages.slice(1, -1), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + it(`parent set size < returned page size < requested page size pagination option ${option}`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: messages.length, + returnedPage: messages.slice(1), + parentSet: { messages: messages.slice(1, -1), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + it(`requested page size < parent set size < returned page size pagination option ${option}`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: option && { [option]: 'X' }, + requestedPageSize: messages.length - 3, + returnedPage: messages, + parentSet: { messages: messages.slice(1, -1), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + }); + }, + ); + }); + + describe('jumping to a message', () => { + const oddSizeReturnPage = messages; + const evenSizeReturnPage = messages.slice(0, -1); + const createdAtISOString = (index, msgs) => + new Date(new Date(msgs[index].created_at).getTime() - 500).toISOString(); + + [ + { + description: 'odd return page size', + messages: oddSizeReturnPage, + messagePaginationOptions: { + firstHalf: { created_at_around: createdAtISOString(2, oddSizeReturnPage) }, + mid: { created_at_around: createdAtISOString(4, oddSizeReturnPage) }, + secondHalf: { created_at_around: createdAtISOString(6, oddSizeReturnPage) }, + }, + option: 'created_at_around', + }, + { + description: 'even return page size', + messages: evenSizeReturnPage, + messagePaginationOptions: { + firstHalf: { created_at_around: createdAtISOString(2, evenSizeReturnPage) }, + mid: { created_at_around: createdAtISOString(4, evenSizeReturnPage) }, + secondHalf: { created_at_around: createdAtISOString(5, evenSizeReturnPage) }, + }, + option: 'created_at_around', + }, + { + description: 'odd return page size', + messages: oddSizeReturnPage, + messagePaginationOptions: { + firstHalf: { id_around: oddSizeReturnPage[2].id }, + mid: { id_around: oddSizeReturnPage[4].id }, + secondHalf: { id_around: oddSizeReturnPage[6].id }, + }, + option: 'id_around', + }, + { + description: 'even return page size', + messages: evenSizeReturnPage, + messagePaginationOptions: { + firstHalf: { id_around: evenSizeReturnPage[2].id }, + mid: { id_around: evenSizeReturnPage[4].id }, + secondHalf: { id_around: evenSizeReturnPage[5].id }, + }, + option: 'id_around', + }, + ].forEach(({ description, messagePaginationOptions, messages, option }) => { + describe(description, () => { + describe(`with ${option}`, () => { + describe('the target msg is in the first page half', () => { + it(`requested page size === returned page size === parent set size pagination`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.firstHalf, + requestedPageSize: messages.length, + returnedPage: messages, + parentSet: { messages, pagination: {} }, + }), + ).to.eql({ hasPrev: false, hasNext: true }); + }); + + it(`returned page size === parent set size pagination < requested page size`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.firstHalf, + requestedPageSize: messages.length + 1, + returnedPage: messages, + parentSet: { messages, pagination: {} }, + }), + ).to.eql({ hasPrev: false, hasNext: false }); + }); + + it(`returned page size === parent set size pagination > requested page size`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.firstHalf, + requestedPageSize: messages.length - 1, + returnedPage: messages, + parentSet: { messages, pagination: {} }, + }), + ).to.eql({ hasPrev: false, hasNext: true }); + }); + + describe('first (oldest) page message matches the first parent set message', () => { + it(`requested page size === parent set size > returned page size`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.firstHalf, + requestedPageSize: messages.length - 1, + returnedPage: messages.slice(1, -1), + parentSet: { messages: messages.slice(1), pagination: {} }, + }), + ).to.eql({ hasPrev: false, hasNext: false }); + }); + it(`requested page size === returned page size < parent set size pagination`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.firstHalf, + requestedPageSize: messages.length - 2, + returnedPage: messages.slice(1, -1), + parentSet: { messages: messages.slice(1), pagination: {} }, + }), + ).to.eql({ hasPrev: false }); + }); + it(`requested page size < returned page size < parent set size pagination`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.firstHalf, + requestedPageSize: messages.length - 3, + returnedPage: messages.slice(1, -1), + parentSet: { messages: messages.slice(1), pagination: {} }, + }), + ).to.eql({ hasPrev: false }); + }); + it(`returned page size < parent set size < requested page size pagination`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.firstHalf, + requestedPageSize: messages.length, + returnedPage: messages.slice(0, -2), + parentSet: { messages: messages.slice(0, -1), pagination: {} }, + }), + ).to.eql({ hasPrev: false, hasNext: false }); + }); + it(`requested page size === returned page size > parent set size pagination`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.firstHalf, + requestedPageSize: messages.length, + returnedPage: messages, + parentSet: { messages: messages.slice(0, -1), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + it(`parent set size < returned page size < requested page size pagination`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.firstHalf, + requestedPageSize: messages.length, + returnedPage: messages.slice(0, -2), + parentSet: { messages: messages.slice(0, -3), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + it(`requested page size < parent set size < returned page size pagination`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.firstHalf, + requestedPageSize: messages.length - 2, + returnedPage: messages, + parentSet: { messages: messages.slice(0, -1), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + }); + + describe('last page message matches the last parent set message', () => { + it(`requested page size === parent set size > returned page size`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.firstHalf, + requestedPageSize: messages.length - 1, + returnedPage: messages.slice(1, -1), + parentSet: { messages: messages.slice(0, -1), pagination: {} }, + }), + ).to.eql({ hasPrev: false, hasNext: false }); + }); + it(`requested page size === returned page size < parent set size pagination`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.firstHalf, + requestedPageSize: messages.length - 2, + returnedPage: messages.slice(1, -1), + parentSet: { messages: messages.slice(1), pagination: {} }, + }), + ).to.eql({ hasPrev: false }); + }); + it(`requested page size < returned page size < parent set size pagination`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.firstHalf, + requestedPageSize: messages.length - 3, + returnedPage: messages.slice(1, -1), + parentSet: { messages: messages.slice(1), pagination: {} }, + }), + ).to.eql({ hasPrev: false }); + }); + it(`returned page size < parent set size < requested page size pagination`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.firstHalf, + requestedPageSize: messages.length, + returnedPage: messages.slice(1, -1), + parentSet: { messages: messages.slice(0, -1), pagination: {} }, + }), + ).to.eql({ hasPrev: false, hasNext: false }); + }); + it(`requested page size === returned page size > parent set size pagination`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.firstHalf, + requestedPageSize: messages.length, + returnedPage: messages, + parentSet: { messages: messages.slice(1), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + it(`parent set size < returned page size < requested page size pagination`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.firstHalf, + requestedPageSize: messages.length, + returnedPage: messages.slice(2), + parentSet: { messages: messages.slice(3), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + it(`requested page size < parent set size < returned page size pagination`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.firstHalf, + requestedPageSize: messages.length - 2, + returnedPage: messages, + parentSet: { messages: messages.slice(1), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + }); + + describe('first page message & last page message do not match the first and last parent set messages', () => { + it(`requested page size === returned page size === parent set size pagination`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.firstHalf, + requestedPageSize: messages.length, + returnedPage: [ + messages[1], + messages[0], + ...messages.slice(2, -2), + messages.slice(-1)[0], + messages.slice(-2, -1)[0], + ], + parentSet: { messages, pagination: {} }, + }), + ).to.eql({}); + }); + it(`requested page size === parent set size > returned page size`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.firstHalf, + requestedPageSize: messages.length - 1, + returnedPage: messages.slice(1, -1), + parentSet: { messages: messages.slice(0, -1), pagination: {} }, + }), + ).to.eql({ hasPrev: false, hasNext: false }); + }); + it(`returned page size === parent set size pagination < requested page size`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.firstHalf, + requestedPageSize: messages.length, + returnedPage: [messages[0], ...messages.slice(2, -2), messages.slice(-1)[0]], + parentSet: { messages: messages.slice(1, -1), pagination: {} }, + }), + ).to.eql({ hasPrev: false, hasNext: false }); + }); + it(`returned page size === parent set size pagination > requested page size`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.firstHalf, + requestedPageSize: messages.length - 1, + returnedPage: [ + messages[1], + messages[0], + ...messages.slice(2, -2), + messages.slice(-1)[0], + messages.slice(-2, -1)[0], + ], + parentSet: { messages, pagination: {} }, + }), + ).to.eql({}); + }); + it(`requested page size === returned page size < parent set size pagination`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.firstHalf, + requestedPageSize: messages.length, + returnedPage: [ + messages[1], + messages[0], + ...messages.slice(2, -2), + messages.slice(-1)[0], + messages.slice(-2, -1)[0], + ], + parentSet: { messages, pagination: {} }, + }), + ).to.eql({}); + }); + it(`requested page size < returned page size < parent set size pagination`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.firstHalf, + requestedPageSize: messages.length - 3, + returnedPage: [ + messages[0], + ...messages.slice(2, -2), + messages.slice(-2, -1)[0], + ], + parentSet: { messages: messages.slice(1), pagination: {} }, + }), + ).to.eql({}); + }); + it(`returned page size < parent set size < requested page size pagination`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.firstHalf, + requestedPageSize: messages.length + 1, + returnedPage: [messages[1], ...messages.slice(2, -2), messages.slice(-1)[0]], + parentSet: { messages, pagination: {} }, + }), + ).to.eql({ hasPrev: false, hasNext: false }); + }); + it(`requested page size === returned page size > parent set size pagination`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.firstHalf, + requestedPageSize: messages.length, + returnedPage: [ + messages[1], + messages[0], + ...messages.slice(2, -2), + messages.slice(-1)[0], + messages.slice(-2, -1)[0], + ], + parentSet: { messages: messages.slice(1, -1), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + it(`parent set size < returned page size < requested page size pagination`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.firstHalf, + requestedPageSize: messages.length, + returnedPage: [messages[0], ...messages.slice(2, -2), messages.slice(-1)[0]], + parentSet: { messages: messages.slice(1, -2), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + it(`requested page size < parent set size < returned page size pagination`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.firstHalf, + requestedPageSize: messages.length - 2, + returnedPage: [ + messages[1], + messages[0], + ...messages.slice(2, -2), + messages.slice(-1)[0], + messages.slice(-2, -1)[0], + ], + parentSet: { messages: messages.slice(1), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + }); + }); + + describe('the target msg is in the middle of the page', () => { + it(`requested page size === returned page size === parent set size pagination`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.mid, + requestedPageSize: messages.length, + returnedPage: messages, + parentSet: { messages, pagination: {} }, + }), + ).to.eql({ hasPrev: true, hasNext: true }); + }); + + it(`returned page size === parent set size pagination < requested page size`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.mid, + requestedPageSize: messages.length + 1, + returnedPage: messages, + parentSet: { messages, pagination: {} }, + }), + ).to.eql({ hasPrev: false, hasNext: false }); + }); + + it(`returned page size === parent set size pagination > requested page size`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.mid, + requestedPageSize: messages.length - 1, + returnedPage: messages, + parentSet: { messages, pagination: {} }, + }), + ).to.eql({ hasPrev: true, hasNext: true }); + }); + + describe('first (oldest) page message matches the first parent set message', () => { + it(`requested page size === parent set size > returned page size`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.mid, + requestedPageSize: messages.length - 1, + returnedPage: messages.slice(1, -1), + parentSet: { messages: messages.slice(1), pagination: {} }, + }), + ).to.eql({ hasPrev: false, hasNext: false }); + }); + it(`requested page size === returned page size < parent set size pagination`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.mid, + requestedPageSize: messages.length - 2, + returnedPage: messages.slice(1, -1), + parentSet: { messages: messages.slice(1), pagination: {} }, + }), + ).to.eql({ hasPrev: true }); + }); + it(`requested page size < returned page size < parent set size pagination`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.mid, + requestedPageSize: messages.length - 3, + returnedPage: messages.slice(1, -1), + parentSet: { messages: messages.slice(1), pagination: {} }, + }), + ).to.eql({ hasPrev: true }); + }); + it(`returned page size < parent set size < requested page size pagination`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.mid, + requestedPageSize: messages.length, + returnedPage: messages.slice(1, -1), + parentSet: { messages: messages.slice(1), pagination: {} }, + }), + ).to.eql({ hasPrev: false, hasNext: false }); + }); + it(`requested page size === returned page size > parent set size pagination`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.mid, + requestedPageSize: messages.length, + returnedPage: messages, + parentSet: { messages: messages.slice(0, -1), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + it(`parent set size < returned page size < requested page size pagination`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.mid, + requestedPageSize: messages.length, + returnedPage: messages.slice(1, -1), + parentSet: { messages: messages.slice(1, -2), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + it(`requested page size < parent set size < returned page size pagination`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.mid, + requestedPageSize: messages.length - 2, + returnedPage: messages, + parentSet: { messages: messages.slice(0, -1), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + }); + + describe('last page message matches the last parent set message', () => { + it(`requested page size === parent set size > returned page size`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.mid, + requestedPageSize: messages.length - 1, + returnedPage: messages.slice(1, -1), + parentSet: { messages: messages.slice(0, -1), pagination: {} }, + }), + ).to.eql({ hasPrev: false, hasNext: false }); + }); + it(`requested page size === returned page size < parent set size pagination`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.mid, + requestedPageSize: messages.length - 2, + returnedPage: messages.slice(1, -1), + parentSet: { messages: messages.slice(0, -1), pagination: {} }, + }), + ).to.eql({ hasNext: true }); + }); + it(`requested page size < returned page size < parent set size pagination`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.mid, + requestedPageSize: messages.length - 3, + returnedPage: messages.slice(1, -1), + parentSet: { messages: messages.slice(0, -1), pagination: {} }, + }), + ).to.eql({ hasNext: true }); + }); + it(`returned page size < parent set size < requested page size pagination`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.mid, + requestedPageSize: messages.length, + returnedPage: messages.slice(1, -1), + parentSet: { messages: messages.slice(0, -1), pagination: {} }, + }), + ).to.eql({ hasPrev: false, hasNext: false }); + }); + it(`requested page size === returned page size > parent set size pagination`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.mid, + requestedPageSize: messages.length, + returnedPage: messages, + parentSet: { messages: messages.slice(1), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + it(`parent set size < returned page size < requested page size pagination`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.mid, + requestedPageSize: messages.length, + returnedPage: messages.slice(1, -1), + parentSet: { messages: messages.slice(2, -1), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + it(`requested page size < parent set size < returned page size pagination`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.mid, + requestedPageSize: messages.length - 2, + returnedPage: messages, + parentSet: { messages: messages.slice(1), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + }); + + describe('first page message & last page message do not match the first and last parent set messages', () => { + it(`requested page size === returned page size === parent set size pagination`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.mid, + requestedPageSize: messages.length, + returnedPage: [ + messages[1], + messages[0], + ...messages.slice(2, -2), + messages.slice(-1)[0], + messages.slice(-2, -1)[0], + ], + parentSet: { messages, pagination: {} }, + }), + ).to.eql({}); + }); + it(`requested page size === parent set size > returned page size`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.mid, + requestedPageSize: messages.length, + returnedPage: messages.slice(1, -1), + parentSet: { messages, pagination: {} }, + }), + ).to.eql({ hasPrev: false, hasNext: false }); + }); + it(`returned page size === parent set size pagination < requested page size`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.mid, + requestedPageSize: messages.length + 1, + returnedPage: [ + messages[1], + messages[0], + ...messages.slice(2, -2), + messages.slice(-1)[0], + messages.slice(-2, -1)[0], + ], + parentSet: { messages, pagination: {} }, + }), + ).to.eql({ hasPrev: false, hasNext: false }); + }); + it(`returned page size === parent set size pagination > requested page size`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.mid, + requestedPageSize: messages.length - 1, + returnedPage: [ + messages[1], + messages[0], + ...messages.slice(2, -2), + messages.slice(-1)[0], + messages.slice(-2, -1)[0], + ], + parentSet: { messages, pagination: {} }, + }), + ).to.eql({}); + }); + it(`requested page size === returned page size < parent set size pagination`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.mid, + requestedPageSize: messages.length - 2, + returnedPage: messages.slice(1, -1), + parentSet: { messages, pagination: {} }, + }), + ).to.eql({}); + }); + it(`requested page size < returned page size < parent set size pagination`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.mid, + requestedPageSize: messages.length - 3, + returnedPage: messages.slice(1, -1), + parentSet: { messages, pagination: {} }, + }), + ).to.eql({}); + }); + it(`returned page size < parent set size < requested page size pagination`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.mid, + requestedPageSize: messages.length + 1, + returnedPage: messages.slice(1, -1), + parentSet: { messages, pagination: {} }, + }), + ).to.eql({ hasPrev: false, hasNext: false }); + }); + it(`requested page size === returned page size > parent set size pagination`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.mid, + requestedPageSize: messages.length, + returnedPage: messages, + parentSet: { messages: messages.slice(1, -1), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + it(`parent set size < returned page size < requested page size pagination`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.mid, + requestedPageSize: messages.length + 1, + returnedPage: messages, + parentSet: { messages: messages.slice(1, -1), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + it(`requested page size < parent set size < returned page size pagination`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.mid, + requestedPageSize: messages.length - 3, + returnedPage: messages, + parentSet: { messages: messages.slice(1, -1), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + }); + }); + + describe('the target msg is in the second page half', () => { + it(`requested page size === returned page size === parent set size pagination`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.secondHalf, + requestedPageSize: messages.length, + returnedPage: messages, + parentSet: { messages, pagination: {} }, + }), + ).to.eql({ hasPrev: true, hasNext: false }); + }); + + it(`returned page size === parent set size pagination < requested page size`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.secondHalf, + requestedPageSize: messages.length + 1, + returnedPage: messages, + parentSet: { messages, pagination: {} }, + }), + ).to.eql({ hasPrev: false, hasNext: false }); + }); + + it(`returned page size === parent set size pagination > requested page size`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.secondHalf, + requestedPageSize: messages.length - 1, + returnedPage: messages, + parentSet: { messages, pagination: {} }, + }), + ).to.eql({ hasPrev: true, hasNext: false }); + }); + + describe('first (oldest) page message matches the first parent set message', () => { + it(`requested page size === parent set size > returned page size`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.secondHalf, + requestedPageSize: messages.length - 1, + returnedPage: messages.slice(1, -1), + parentSet: { messages: messages.slice(1), pagination: {} }, + }), + ).to.eql({ hasPrev: false, hasNext: false }); + }); + it(`requested page size === returned page size < parent set size pagination`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.secondHalf, + requestedPageSize: messages.length - 2, + returnedPage: messages.slice(1, -1), + parentSet: { messages: messages.slice(1), pagination: {} }, + }), + ).to.eql({ hasPrev: true }); + }); + it(`requested page size < returned page size < parent set size pagination`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.secondHalf, + requestedPageSize: messages.length - 3, + returnedPage: messages.slice(1, -1), + parentSet: { messages: messages.slice(1), pagination: {} }, + }), + ).to.eql({ hasPrev: true }); + }); + it(`returned page size < parent set size < requested page size pagination`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.secondHalf, + requestedPageSize: messages.length, + returnedPage: messages.slice(1, -1), + parentSet: { messages: messages.slice(1), pagination: {} }, + }), + ).to.eql({ hasPrev: false, hasNext: false }); + }); + it(`requested page size === returned page size > parent set size pagination`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.secondHalf, + requestedPageSize: messages.length, + returnedPage: messages, + parentSet: { messages: messages.slice(0, -1), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + it(`parent set size < returned page size < requested page size pagination`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.secondHalf, + requestedPageSize: messages.length, + returnedPage: messages.slice(1, -1), + parentSet: { messages: messages.slice(1, -2), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + it(`requested page size < parent set size < returned page size pagination`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.secondHalf, + requestedPageSize: messages.length - 2, + returnedPage: messages, + parentSet: { messages: messages.slice(0, -1), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + }); + + describe('last page message matches the last parent set message', () => { + it(`requested page size === parent set size > returned page size`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.secondHalf, + requestedPageSize: messages.length - 1, + returnedPage: messages.slice(1, -1), + parentSet: { messages: messages.slice(0, -1), pagination: {} }, + }), + ).to.eql({ hasPrev: false, hasNext: false }); + }); + it(`requested page size === returned page size < parent set size pagination`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.secondHalf, + requestedPageSize: messages.length - 2, + returnedPage: messages.slice(1, -1), + parentSet: { messages: messages.slice(0, -1), pagination: {} }, + }), + ).to.eql({ hasNext: false }); + }); + it(`requested page size < returned page size < parent set size pagination`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.secondHalf, + requestedPageSize: messages.length - 3, + returnedPage: messages.slice(1, -1), + parentSet: { messages: messages.slice(0, -1), pagination: {} }, + }), + ).to.eql({ hasNext: false }); + }); + it(`returned page size < parent set size < requested page size pagination`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.secondHalf, + requestedPageSize: messages.length, + returnedPage: messages.slice(1, -1), + parentSet: { messages: messages.slice(0, -1), pagination: {} }, + }), + ).to.eql({ hasPrev: false, hasNext: false }); + }); + it(`requested page size === returned page size > parent set size pagination`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.secondHalf, + requestedPageSize: messages.length, + returnedPage: messages, + parentSet: { messages: messages.slice(1), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + it(`parent set size < returned page size < requested page size pagination`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.secondHalf, + requestedPageSize: messages.length, + returnedPage: messages.slice(1, -1), + parentSet: { messages: messages.slice(2, -1), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + it(`requested page size < parent set size < returned page size pagination`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.secondHalf, + requestedPageSize: messages.length - 2, + returnedPage: messages, + parentSet: { messages: messages.slice(1), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + }); + + describe('first page message & last page message do not match the first and last parent set messages', () => { + it(`requested page size === returned page size === parent set size pagination`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.secondHalf, + requestedPageSize: messages.length, + returnedPage: [ + messages[1], + messages[0], + ...messages.slice(2, -2), + messages.slice(-1)[0], + messages.slice(-2, -1)[0], + ], + parentSet: { messages, pagination: {} }, + }), + ).to.eql({}); + }); + it(`requested page size === parent set size > returned page size`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.secondHalf, + requestedPageSize: messages.length, + returnedPage: messages.slice(1, -1), + parentSet: { messages, pagination: {} }, + }), + ).to.eql({ hasPrev: false, hasNext: false }); + }); + it(`returned page size === parent set size pagination < requested page size`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.secondHalf, + requestedPageSize: messages.length + 1, + returnedPage: [ + messages[1], + messages[0], + ...messages.slice(2, -2), + messages.slice(-1)[0], + messages.slice(-2, -1)[0], + ], + parentSet: { messages, pagination: {} }, + }), + ).to.eql({ hasPrev: false, hasNext: false }); + }); + it(`returned page size === parent set size pagination > requested page size`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.secondHalf, + requestedPageSize: messages.length - 1, + returnedPage: [ + messages[1], + messages[0], + ...messages.slice(2, -2), + messages.slice(-1)[0], + messages.slice(-2, -1)[0], + ], + parentSet: { messages, pagination: {} }, + }), + ).to.eql({}); + }); + it(`requested page size === returned page size < parent set size pagination`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.secondHalf, + requestedPageSize: messages.length - 2, + returnedPage: messages.slice(1, -1), + parentSet: { messages, pagination: {} }, + }), + ).to.eql({}); + }); + it(`requested page size < returned page size < parent set size pagination`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.secondHalf, + requestedPageSize: messages.length - 3, + returnedPage: messages.slice(1, -1), + parentSet: { messages, pagination: {} }, + }), + ).to.eql({}); + }); + it(`returned page size < parent set size < requested page size pagination`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.secondHalf, + requestedPageSize: messages.length + 1, + returnedPage: messages.slice(1, -1), + parentSet: { messages, pagination: {} }, + }), + ).to.eql({ hasPrev: false, hasNext: false }); + }); + it(`requested page size === returned page size > parent set size pagination`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.secondHalf, + requestedPageSize: messages.length, + returnedPage: messages, + parentSet: { messages: messages.slice(1, -1), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + it(`parent set size < returned page size < requested page size pagination`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.secondHalf, + requestedPageSize: messages.length + 1, + returnedPage: messages, + parentSet: { messages: messages.slice(1, -1), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + it(`requested page size < parent set size < returned page size pagination`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions.secondHalf, + requestedPageSize: messages.length - 3, + returnedPage: messages, + parentSet: { messages: messages.slice(1, -1), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + }); + }); + }); + + describe('with created_at_around', () => { + describe('the target msg created_at < the earliest parent set message creation date', () => { + const created_at_around = '2000-08-05T08:55:00.199808Z'; + + it(`requested page size === returned page size === parent set size pagination`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: { created_at_around }, + requestedPageSize: messages.length, + returnedPage: messages, + parentSet: { messages, pagination: {} }, + }), + ).to.eql({ hasPrev: false }); + }); + + it(`returned page size === parent set size pagination < requested page size`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: { created_at_around }, + requestedPageSize: messages.length + 1, + returnedPage: messages, + parentSet: { messages, pagination: {} }, + }), + ).to.eql({ hasPrev: false, hasNext: false }); + }); + + it(`returned page size === parent set size pagination > requested page size`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: { created_at_around }, + requestedPageSize: messages.length - 1, + returnedPage: messages, + parentSet: { messages, pagination: {} }, + }), + ).to.eql({ hasPrev: false }); + }); + + describe('first (oldest) page message matches the first parent set message', () => { + it(`requested page size === parent set size > returned page size`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: { created_at_around }, + requestedPageSize: messages.length - 1, + returnedPage: messages.slice(1, -1), + parentSet: { messages: messages.slice(1), pagination: {} }, + }), + ).to.eql({ hasPrev: false }); + }); + it(`requested page size === returned page size < parent set size pagination`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: { created_at_around }, + requestedPageSize: messages.length - 2, + returnedPage: messages.slice(1, -1), + parentSet: { messages: messages.slice(1), pagination: {} }, + }), + ).to.eql({ hasPrev: false }); + }); + it(`requested page size < returned page size < parent set size pagination`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: { created_at_around }, + requestedPageSize: messages.length - 3, + returnedPage: messages.slice(1, -1), + parentSet: { messages: messages.slice(1), pagination: {} }, + }), + ).to.eql({ hasPrev: false }); + }); + it(`returned page size < parent set size < requested page size pagination`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: { created_at_around }, + requestedPageSize: messages.length, + returnedPage: messages.slice(1, -1), + parentSet: { messages: messages.slice(1), pagination: {} }, + }), + ).to.eql({ hasPrev: false, hasNext: false }); + }); + it(`requested page size === returned page size > parent set size pagination`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: { created_at_around }, + requestedPageSize: messages.length, + returnedPage: messages, + parentSet: { messages: messages.slice(0, -1), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + it(`parent set size < returned page size < requested page size pagination`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: { created_at_around }, + requestedPageSize: messages.length, + returnedPage: messages.slice(1, -1), + parentSet: { messages: messages.slice(1, -2), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + it(`requested page size < parent set size < returned page size pagination`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: { created_at_around }, + requestedPageSize: messages.length - 2, + returnedPage: messages, + parentSet: { messages: messages.slice(0, -1), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + }); + + describe('last page message matches the last parent set message', () => { + it(`requested page size === parent set size > returned page size`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: { created_at_around }, + requestedPageSize: messages.length - 1, + returnedPage: messages.slice(1, -1), + parentSet: { messages: messages.slice(0, -1), pagination: {} }, + }), + ).to.eql({ hasPrev: false }); + }); + it(`requested page size === returned page size < parent set size pagination`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: { created_at_around }, + requestedPageSize: messages.length - 2, + returnedPage: messages.slice(1, -1), + parentSet: { messages: messages.slice(0, -1), pagination: {} }, + }), + ).to.eql({ hasPrev: false }); + }); + it(`requested page size < returned page size < parent set size pagination`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: { created_at_around }, + requestedPageSize: messages.length - 3, + returnedPage: messages.slice(1, -1), + parentSet: { messages: messages.slice(0, -1), pagination: {} }, + }), + ).to.eql({ hasPrev: false }); + }); + it(`returned page size < parent set size < requested page size pagination`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: { created_at_around }, + requestedPageSize: messages.length, + returnedPage: messages.slice(1, -1), + parentSet: { messages: messages.slice(0, -1), pagination: {} }, + }), + ).to.eql({ hasPrev: false, hasNext: false }); + }); + it(`requested page size === returned page size > parent set size pagination`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: { created_at_around }, + requestedPageSize: messages.length, + returnedPage: messages, + parentSet: { messages: messages.slice(1), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + it(`parent set size < returned page size < requested page size pagination`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: { created_at_around }, + requestedPageSize: messages.length, + returnedPage: messages.slice(1, -1), + parentSet: { messages: messages.slice(2, -1), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + it(`requested page size < parent set size < returned page size pagination`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: { created_at_around }, + requestedPageSize: messages.length - 2, + returnedPage: messages, + parentSet: { messages: messages.slice(1), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + }); + + describe('first page message & last page message do not match the first and last parent set messages', () => { + it(`requested page size === returned page size === parent set size pagination`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: { created_at_around }, + requestedPageSize: messages.length, + returnedPage: [ + messages[1], + messages[0], + ...messages.slice(2, -2), + messages.slice(-1)[0], + messages.slice(-2, -1)[0], + ], + parentSet: { messages, pagination: {} }, + }), + ).to.eql({ hasPrev: false }); + }); + it(`requested page size === parent set size > returned page size`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: { created_at_around }, + requestedPageSize: messages.length, + returnedPage: messages.slice(1, -1), + parentSet: { messages, pagination: {} }, + }), + ).to.eql({ hasPrev: false }); + }); + it(`returned page size === parent set size pagination < requested page size`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: { created_at_around }, + requestedPageSize: messages.length + 1, + returnedPage: [ + messages[1], + messages[0], + ...messages.slice(2, -2), + messages.slice(-1)[0], + messages.slice(-2, -1)[0], + ], + parentSet: { messages, pagination: {} }, + }), + ).to.eql({ hasPrev: false, hasNext: false }); + }); + it(`returned page size === parent set size pagination > requested page size`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: { created_at_around }, + requestedPageSize: messages.length - 1, + returnedPage: [ + messages[1], + messages[0], + ...messages.slice(2, -2), + messages.slice(-1)[0], + messages.slice(-2, -1)[0], + ], + parentSet: { messages, pagination: {} }, + }), + ).to.eql({ hasPrev: false }); + }); + it(`requested page size === returned page size < parent set size pagination`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: { created_at_around }, + requestedPageSize: messages.length - 2, + returnedPage: messages.slice(1, -1), + parentSet: { messages, pagination: {} }, + }), + ).to.eql({ hasPrev: false }); + }); + it(`requested page size < returned page size < parent set size pagination`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: { created_at_around }, + requestedPageSize: messages.length - 3, + returnedPage: messages.slice(1, -1), + parentSet: { messages, pagination: {} }, + }), + ).to.eql({ hasPrev: false }); + }); + it(`returned page size < parent set size < requested page size pagination`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: { created_at_around }, + requestedPageSize: messages.length + 1, + returnedPage: messages.slice(1, -1), + parentSet: { messages, pagination: {} }, + }), + ).to.eql({ hasPrev: false, hasNext: false }); + }); + it(`requested page size === returned page size > parent set size pagination`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: { created_at_around }, + requestedPageSize: messages.length, + returnedPage: messages, + parentSet: { messages: messages.slice(1, -1), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + it(`parent set size < returned page size < requested page size pagination`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: { created_at_around }, + requestedPageSize: messages.length + 1, + returnedPage: messages, + parentSet: { messages: messages.slice(1, -1), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + it(`requested page size < parent set size < returned page size pagination`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: { created_at_around }, + requestedPageSize: messages.length - 3, + returnedPage: messages, + parentSet: { messages: messages.slice(1, -1), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + }); + }); + + describe('the target msg created_at > the latest parent set message creation date', () => { + const created_at_around = '3000-08-05T08:55:00.199808Z'; + + it(`requested page size === returned page size === parent set size pagination`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: { created_at_around }, + requestedPageSize: messages.length, + returnedPage: messages, + parentSet: { messages, pagination: {} }, + }), + ).to.eql({ hasNext: false }); + }); + + it(`returned page size === parent set size pagination < requested page size`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: { created_at_around }, + requestedPageSize: messages.length + 1, + returnedPage: messages, + parentSet: { messages, pagination: {} }, + }), + ).to.eql({ hasPrev: false, hasNext: false }); + }); + + it(`returned page size === parent set size pagination > requested page size`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: { created_at_around }, + requestedPageSize: messages.length - 1, + returnedPage: messages, + parentSet: { messages, pagination: {} }, + }), + ).to.eql({ hasNext: false }); + }); + + describe('first (oldest) page message matches the first parent set message', () => { + it(`requested page size === parent set size > returned page size`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: { created_at_around }, + requestedPageSize: messages.length - 1, + returnedPage: messages.slice(1, -1), + parentSet: { messages: messages.slice(1), pagination: {} }, + }), + ).to.eql({ hasNext: false }); + }); + it(`requested page size === returned page size < parent set size pagination`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: { created_at_around }, + requestedPageSize: messages.length - 2, + returnedPage: messages.slice(1, -1), + parentSet: { messages: messages.slice(1), pagination: {} }, + }), + ).to.eql({ hasNext: false }); + }); + it(`requested page size < returned page size < parent set size pagination`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: { created_at_around }, + requestedPageSize: messages.length - 3, + returnedPage: messages.slice(1, -1), + parentSet: { messages: messages.slice(1), pagination: {} }, + }), + ).to.eql({ hasNext: false }); + }); + it(`returned page size < parent set size < requested page size pagination`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: { created_at_around }, + requestedPageSize: messages.length, + returnedPage: messages.slice(1, -1), + parentSet: { messages: messages.slice(1), pagination: {} }, + }), + ).to.eql({ hasPrev: false, hasNext: false }); + }); + it(`requested page size === returned page size > parent set size pagination`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: { created_at_around }, + requestedPageSize: messages.length, + returnedPage: messages, + parentSet: { messages: messages.slice(0, -1), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + it(`parent set size < returned page size < requested page size pagination`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: { created_at_around }, + requestedPageSize: messages.length, + returnedPage: messages.slice(1, -1), + parentSet: { messages: messages.slice(1, -2), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + it(`requested page size < parent set size < returned page size pagination`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: { created_at_around }, + requestedPageSize: messages.length - 2, + returnedPage: messages, + parentSet: { messages: messages.slice(0, -1), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + }); + + describe('last page message matches the last parent set message', () => { + it(`requested page size === parent set size > returned page size`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: { created_at_around }, + requestedPageSize: messages.length - 1, + returnedPage: messages.slice(1, -1), + parentSet: { messages: messages.slice(0, -1), pagination: {} }, + }), + ).to.eql({ hasNext: false }); + }); + it(`requested page size === returned page size < parent set size pagination`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: { created_at_around }, + requestedPageSize: messages.length - 2, + returnedPage: messages.slice(1, -1), + parentSet: { messages: messages.slice(0, -1), pagination: {} }, + }), + ).to.eql({ hasNext: false }); + }); + it(`requested page size < returned page size < parent set size pagination`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: { created_at_around }, + requestedPageSize: messages.length - 3, + returnedPage: messages.slice(1, -1), + parentSet: { messages: messages.slice(0, -1), pagination: {} }, + }), + ).to.eql({ hasNext: false }); + }); + it(`returned page size < parent set size < requested page size pagination`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: { created_at_around }, + requestedPageSize: messages.length, + returnedPage: messages.slice(1, -1), + parentSet: { messages: messages.slice(0, -1), pagination: {} }, + }), + ).to.eql({ hasPrev: false, hasNext: false }); + }); + it(`requested page size === returned page size > parent set size pagination`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: { created_at_around }, + requestedPageSize: messages.length, + returnedPage: messages, + parentSet: { messages: messages.slice(1), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + it(`parent set size < returned page size < requested page size pagination`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: { created_at_around }, + requestedPageSize: messages.length, + returnedPage: messages.slice(1, -1), + parentSet: { messages: messages.slice(2, -1), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + it(`requested page size < parent set size < returned page size pagination`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: { created_at_around }, + requestedPageSize: messages.length - 2, + returnedPage: messages, + parentSet: { messages: messages.slice(1), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + }); + + describe('first page message & last page message do not match the first and last parent set messages', () => { + it(`requested page size === returned page size === parent set size pagination`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: { created_at_around }, + requestedPageSize: messages.length, + returnedPage: [ + messages[1], + messages[0], + ...messages.slice(2, -2), + messages.slice(-1)[0], + messages.slice(-2, -1)[0], + ], + parentSet: { messages, pagination: {} }, + }), + ).to.eql({ hasNext: false }); + }); + it(`requested page size === parent set size > returned page size`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: { created_at_around }, + requestedPageSize: messages.length, + returnedPage: messages.slice(1, -1), + parentSet: { messages, pagination: {} }, + }), + ).to.eql({ hasNext: false }); + }); + it(`returned page size === parent set size pagination < requested page size`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: { created_at_around }, + requestedPageSize: messages.length + 1, + returnedPage: [ + messages[1], + messages[0], + ...messages.slice(2, -2), + messages.slice(-1)[0], + messages.slice(-2, -1)[0], + ], + parentSet: { messages, pagination: {} }, + }), + ).to.eql({ hasPrev: false, hasNext: false }); + }); + it(`returned page size === parent set size pagination > requested page size`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: { created_at_around }, + requestedPageSize: messages.length - 1, + returnedPage: [ + messages[1], + messages[0], + ...messages.slice(2, -2), + messages.slice(-1)[0], + messages.slice(-2, -1)[0], + ], + parentSet: { messages, pagination: {} }, + }), + ).to.eql({ hasNext: false }); + }); + it(`requested page size === returned page size < parent set size pagination`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: { created_at_around }, + requestedPageSize: messages.length - 2, + returnedPage: messages.slice(1, -1), + parentSet: { messages, pagination: {} }, + }), + ).to.eql({ hasNext: false }); + }); + it(`requested page size < returned page size < parent set size pagination`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: { created_at_around }, + requestedPageSize: messages.length - 3, + returnedPage: messages.slice(1, -1), + parentSet: { messages, pagination: {} }, + }), + ).to.eql({ hasNext: false }); + }); + it(`returned page size < parent set size < requested page size pagination`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: { created_at_around }, + requestedPageSize: messages.length + 1, + returnedPage: messages.slice(1, -1), + parentSet: { messages, pagination: {} }, + }), + ).to.eql({ hasPrev: false, hasNext: false }); + }); + it(`requested page size === returned page size > parent set size pagination`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: { created_at_around }, + requestedPageSize: messages.length, + returnedPage: messages, + parentSet: { messages: messages.slice(1, -1), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + it(`parent set size < returned page size < requested page size pagination`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: { created_at_around }, + requestedPageSize: messages.length + 1, + returnedPage: messages, + parentSet: { messages: messages.slice(1, -1), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + it(`requested page size < parent set size < returned page size pagination`, () => { + const restore = consoleErrorSpy(); + expect( + messageSetPagination({ + messagePaginationOptions: { created_at_around }, + requestedPageSize: messages.length - 3, + returnedPage: messages, + parentSet: { messages: messages.slice(1, -1), pagination: {} }, + }), + ).to.eql({}); + restore(); + }); + }); + }); + }); + }); + }); + + [ + { + description: '0 return page size', + messages: [], + messagePaginationOptions: { created_at_around: createdAtISOString(2, oddSizeReturnPage) }, + option: 'created_at_around', + }, + { + description: '0 return page size', + messages: [], + messagePaginationOptions: { id_around: oddSizeReturnPage[4].id }, + option: 'id_around', + }, + ].forEach(({ description, messagePaginationOptions, messages, option }) => { + describe(description, () => { + describe(`with ${option}`, () => { + it(`requested page size === returned page size === parent set size pagination`, () => { + expect( + messageSetPagination({ + messagePaginationOptions: messagePaginationOptions, + requestedPageSize: messages.length, + returnedPage: messages, + parentSet: { messages, pagination: {} }, + }), + ).to.eql({}); + }); + it(`requested page size === parent set size > returned page size`, () => { + expect( + messageSetPagination({ + messagePaginationOptions, + requestedPageSize: 1, + returnedPage: messages, + parentSet: { messages: evenSizeReturnPage.slice(0, 1), pagination: {} }, + }), + ).to.eql({ hasPrev: false, hasNext: false }); + }); + it(`returned page size === parent set size pagination < requested page size`, () => { + expect( + messageSetPagination({ + messagePaginationOptions, + requestedPageSize: 1, + returnedPage: messages, + parentSet: { messages, pagination: {} }, + }), + ).to.eql({ hasPrev: false, hasNext: false }); + }); + it(`requested page size === returned page size < parent set size pagination`, () => { + expect( + messageSetPagination({ + messagePaginationOptions, + requestedPageSize: messages.length, + returnedPage: messages, + parentSet: { messages: evenSizeReturnPage, pagination: {} }, + }), + ).to.eql({}); + }); + it(`returned page size < parent set size < requested page size pagination`, () => { + expect( + messageSetPagination({ + messagePaginationOptions, + requestedPageSize: 1, + returnedPage: messages, + parentSet: { messages: evenSizeReturnPage, pagination: {} }, + }), + ).to.eql({ hasPrev: false, hasNext: false }); + }); + }); + }); + }); + }); +}); + +describe('', () => { + const messages = [ + { created_at: '2024-08-05T08:55:00.199808Z', id: '0' }, + { created_at: '2024-08-05T08:55:01.199808Z', id: '1' }, + { created_at: '2024-08-05T08:55:02.199808Z', id: '2' }, + { created_at: '2024-08-05T08:55:03.199808Z', id: '3' }, + { created_at: '2024-08-05T08:55:04.199808Z', id: '4' }, + { created_at: '2024-08-05T08:55:05.199808Z', id: '5' }, + { created_at: '2024-08-05T08:55:06.199808Z', id: '6' }, + { created_at: '2024-08-05T08:55:07.199808Z', id: '7' }, + { created_at: '2024-08-05T08:55:08.199808Z', id: '8' }, + ]; + it('finds the nearest newer item', () => { + expect(binarySearchByDateEqualOrNearestGreater(messages, new Date('2024-08-05T08:55:02.299808Z'))).to.eql(3); + }); + it('finds the nearest matching item', () => { + expect(binarySearchByDateEqualOrNearestGreater(messages, new Date('2024-08-05T08:55:07.199808Z'))).to.eql(7); + }); +});