Skip to content

Commit

Permalink
test coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
brietsparks committed Aug 2, 2023
1 parent 26a8c15 commit 74db8ba
Show file tree
Hide file tree
Showing 2 changed files with 281 additions and 28 deletions.
41 changes: 25 additions & 16 deletions src/knex-relay-cursor-pagination.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ type Row = { [key: string]: unknown };
export function createPagination(params: PaginationParams) {
const { first, after, last, before } = params;

if (first === undefined && last === undefined) {
throw new Error('pagination requires either a `first` or `last` param');
}

const paginationSliceParams = getInternalSliceParams({
first,
after,
Expand Down Expand Up @@ -183,7 +187,9 @@ export function createPagination(params: PaginationParams) {
];
}

throw new Error('invalid state for getRows');
throw new Error(
'the queried row count exceeds the expected limit based on the pagination params'
);
};

function getPage<T = Row>(
Expand All @@ -196,6 +202,18 @@ export function createPagination(params: PaginationParams) {

const [items, adjacentItem] = processItems(rows);

if (items.length === 0) {
return {
edges: [],
pageInfo: {
hasNextPage: false,
hasPreviousPage: false,
endCursor: undefined,
startCursor: undefined,
},
};
}

const edges = [];
for (const item of items) {
const cursor = item[cursorAlias];
Expand Down Expand Up @@ -224,8 +242,8 @@ export function createPagination(params: PaginationParams) {
paginationSliceParams.direction === 'forward'
? !!after
: !!adjacentItem,
startCursor: edges.length ? edges[0].cursor : undefined,
endCursor: edges.length ? edges[edges.length - 1].cursor : undefined,
startCursor: edges[0].cursor,
endCursor: edges[edges.length - 1].cursor,
};

return {
Expand Down Expand Up @@ -287,11 +305,7 @@ function getSortDirection(
return 'asc';
}

if (specifiedSortDirection === 'asc') {
return 'desc';
}

throw new Error('unknown state for getSortDirection');
return 'desc';
}

type Comparator = '<' | '>';
Expand All @@ -309,14 +323,9 @@ function getComparator(
}
}

if (specifiedSortDirection === 'asc') {
if (paginationDirection === 'forward') {
return '>';
}
if (paginationDirection === 'backward') {
return '<';
}
if (paginationDirection === 'forward') {
return '>';
}

throw new Error('unknown state for getComparator');
return '<';
}
268 changes: 256 additions & 12 deletions test/knex-relay-cursor-pagination.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,14 @@ describe('createPagination', () => {
await pgContainer.stop();
});

describe('paging variants', () => {
const baseParams: PaginationDatasetParams = {
from: 'posts',
sortColumn: 'creation_timestamp',
sortDirection: 'desc',
cursorColumn: 'id',
};
const baseParams: PaginationDatasetParams = {
from: 'posts',
sortColumn: 'creation_timestamp',
sortDirection: 'desc',
cursorColumn: 'id',
};

describe('paging variants', () => {
const sortedPosts = [...posts].sort(
(a, b) => b.creation_timestamp.getTime() - a.creation_timestamp.getTime()
);
Expand Down Expand Up @@ -371,6 +371,76 @@ describe('createPagination', () => {
expect(pagination.getPage(rows)).toEqual(testCase.expected);
});
});

test('forward paging, ascending', async () => {
const pagination = createPagination({
from: 'posts',
sortColumn: 'creation_timestamp',
sortDirection: 'asc',
cursorColumn: 'id',
first: 3,
});

const rows = await db
.from('posts')
.where(
pagination.where.column,
pagination.where.comparator,
pagination.where.value
)
.orderBy(pagination.orderBy.column, pagination.orderBy.direction)
.limit(pagination.limit)
.select('*');

expect(pagination.getPage(rows)).toEqual({
edges: [
{ node: posts[0], cursor: btoa(posts[0].id) },
{ node: posts[1], cursor: btoa(posts[1].id) },
{ node: posts[2], cursor: btoa(posts[2].id) },
],
pageInfo: {
startCursor: btoa(posts[0].id),
endCursor: btoa(posts[2].id),
hasPreviousPage: false,
hasNextPage: true,
},
});
});

test('backward paging, ascending', async () => {
const pagination = createPagination({
from: 'posts',
sortColumn: 'creation_timestamp',
sortDirection: 'asc',
cursorColumn: 'id',
last: 3,
});

const rows = await db
.from('posts')
.where(
pagination.where.column,
pagination.where.comparator,
pagination.where.value
)
.orderBy(pagination.orderBy.column, pagination.orderBy.direction)
.limit(pagination.limit)
.select('*');

expect(pagination.getPage(rows)).toEqual({
edges: [
{ node: posts[5], cursor: btoa(posts[5].id) },
{ node: posts[6], cursor: btoa(posts[6].id) },
{ node: posts[7], cursor: btoa(posts[7].id) },
],
pageInfo: {
startCursor: btoa(posts[5].id),
endCursor: btoa(posts[7].id),
hasPreviousPage: true,
hasNextPage: false,
},
});
});
});

describe('query from a common-table-expression', () => {
Expand Down Expand Up @@ -545,9 +615,183 @@ describe('createPagination', () => {
});
});

test.todo('throw if no slice params');
test.todo('custom obfuscateCursor');
test.todo('custom deobfuscateCursor');
test.todo('throw onCursorMissing');
test.todo('omit onCursorMissing');
test('empty rows', async () => {
const pagination = createPagination({
from: 'posts',
sortColumn: 'creation_timestamp',
sortDirection: 'desc',
cursorColumn: 'id',
first: 2,
after: btoa('99999999-9999-9999-9999-999999999999'),
});

const rows = await db
.from('posts')
.where(
pagination.where.column,
pagination.where.comparator,
pagination.where.value
)
.orderBy(pagination.orderBy.column, pagination.orderBy.direction)
.limit(pagination.limit)
.select('*');

expect(pagination.getPage(rows)).toEqual({
edges: [],
pageInfo: {
startCursor: undefined,
endCursor: undefined,
hasPreviousPage: false,
hasNextPage: false,
},
});
});

test('throw if no slice params', () => {
const testCase = () =>
createPagination({
from: 'posts',
sortColumn: 'creation_timestamp',
sortDirection: 'desc',
cursorColumn: 'id',
});

expect(testCase).toThrow(
new Error('pagination requires either a `first` or `last` param')
);
});

test('custom obfuscateCursor and deobfuscateCursor', async () => {
const pagination = createPagination({
from: 'posts',
sortColumn: 'creation_timestamp',
sortDirection: 'desc',
cursorColumn: 'id',
first: 3,
obfuscateCursor: (s) => s,
deobfuscateCursor: (s) => s,
});

const rows = await db
.from('posts')
.where(
pagination.where.column,
pagination.where.comparator,
pagination.where.value
)
.orderBy(pagination.orderBy.column, pagination.orderBy.direction)
.limit(pagination.limit)
.select('*');

expect(pagination.getPage(rows)).toEqual({
edges: [
{ node: posts[7], cursor: posts[7].id },
{ node: posts[6], cursor: posts[6].id },
{ node: posts[5], cursor: posts[5].id },
],
pageInfo: {
startCursor: posts[7].id,
endCursor: posts[5].id,
hasPreviousPage: false,
hasNextPage: true,
},
});
});

test('omit item onCursorMissing', async () => {
const pagination = createPagination({
from: 'posts',
sortColumn: 'creation_timestamp',
sortDirection: 'desc',
cursorColumn: 'id',
first: 3,
});

const rows = await db
.from('posts')
.where(
pagination.where.column,
pagination.where.comparator,
pagination.where.value
)
.orderBy(pagination.orderBy.column, pagination.orderBy.direction)
.limit(pagination.limit)
.select('*');

delete rows[0].id;

expect(pagination.getPage(rows)).toEqual({
edges: [
{ node: posts[6], cursor: btoa(posts[6].id) },
{ node: posts[5], cursor: btoa(posts[5].id) },
],
pageInfo: {
startCursor: btoa(posts[6].id),
endCursor: btoa(posts[5].id),
hasPreviousPage: false,
hasNextPage: true,
},
});
});

test('throw onCursorMissing', async () => {
const pagination = createPagination({
from: 'posts',
sortColumn: 'creation_timestamp',
sortDirection: 'desc',
cursorColumn: 'id',
first: 3,
onCursorMissing: 'throw',
});

const rows = await db
.from('posts')
.where(
pagination.where.column,
pagination.where.comparator,
pagination.where.value
)
.orderBy(pagination.orderBy.column, pagination.orderBy.direction)
.limit(pagination.limit)
.select('*');

delete rows[0].id;

const testCase = () => pagination.getPage(rows);

expect(testCase).toThrow(new Error('cursor is missing'));
});

test('throw when too many rows provided', async () => {
const pagination = createPagination({
from: 'posts',
sortColumn: 'creation_timestamp',
sortDirection: 'desc',
cursorColumn: 'id',
first: 3,
});

const rows = await db
.from('posts')
.where(
pagination.where.column,
pagination.where.comparator,
pagination.where.value
)
.orderBy(pagination.orderBy.column, pagination.orderBy.direction)
.limit(pagination.limit)
.select('*');

rows.push({
id: '99999999-9999-9999-9999-999999999999',
});

const testCase = () => pagination.getPage(rows);

expect(testCase).toThrow(
new Error(
'the queried row count exceeds the expected limit based on the pagination params'
)
);
});
});

0 comments on commit 74db8ba

Please sign in to comment.