Skip to content
This repository was archived by the owner on Jun 22, 2021. It is now read-only.

feat: Adds utils for createEndCursorResult and createGetEntitiesResult. #12

Merged
merged 3 commits into from
Mar 28, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/errors/PaginationFilterError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// tslint:disable:no-class
import { BaseError } from 'make-error';

export default class PaginationFilterError extends BaseError {
constructor() {
super();
}
}
53 changes: 30 additions & 23 deletions src/tests/getEntities/paginationTest.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import 'mocha'; // tslint:disable-line:no-import-side-effect
import * as assert from 'power-assert';
import Facade from '../../Facade';
import Cursor from '../../types/Cursor';
import Cursor, { end, start } from '../../types/Cursor';
import Pagination from '../../types/Pagination';
import PaginationDirection, { backward, forward } from '../../types/PaginationDirection';
import Sort from '../../types/Sort';
import { asc } from '../../types/SortOrder';
import createCursorFromEntity from '../../utils/createCursorFromEntity';
import { TestEntity, testEntity } from '../utils/testEntity';

export default (facade: Facade<TestEntity>) => {
const firstId = 'test_id_1';
const secondId = 'test_id_2';
const firstEntity = { ...testEntity, id: firstId };
const secondEntity = { ...testEntity, id: secondId };
const sort: Sort<TestEntity> = { id: asc };

const createTestEntities = async () => {
await facade.createEntity({ id: firstId, entity: firstEntity });
Expand All @@ -19,61 +23,64 @@ export default (facade: Facade<TestEntity>) => {

const paginate = (cursor: Cursor, direction: PaginationDirection) => {
const pagination: Pagination = { cursor, direction, limit: 1 };
return facade.getEntities({ pagination });
return facade.getEntities({ pagination, sort });
};

it('should return all entities when pagination is not defined', async () => {
await createTestEntities();
const result = await facade.getEntities({});
assert.deepEqual(result.entities, [firstEntity, secondEntity]);
assert.equal(result.previousCursor, createCursorFromEntity(firstEntity, sort));
assert.equal(result.nextCursor, end);
});

it('should return first entity when there are two entities limitted to 1', async () => {
it('should return first entity when paginating forward with start cursor', async () => {
await createTestEntities();
const pagination: Pagination = { cursor: undefined, direction: forward, limit: 1 };
const result = await facade.getEntities({ pagination });
assert.deepEqual(result.entities, [firstEntity]);
});

it('should return first entity when paginating forward without cursor', async () => {
await createTestEntities();
const finalResult = await paginate(undefined, forward);
const finalResult = await paginate(start, forward);
assert.deepEqual(finalResult.entities, [firstEntity]);
assert.equal(finalResult.previousCursor, end);
assert.equal(finalResult.nextCursor, createCursorFromEntity(firstEntity, sort));
});

it('should return second entity when paginating forward with first cursor', async () => {
await createTestEntities();
const firstResult = await paginate(undefined, forward);
const firstResult = await paginate(start, forward);
const finalResult = await paginate(firstResult.nextCursor, forward);
assert.deepEqual(finalResult.entities, [secondEntity]);
assert.equal(finalResult.previousCursor, createCursorFromEntity(secondEntity, sort));
assert.equal(finalResult.nextCursor, end);
});

it('should return no entities when paginating forward with second cursor', async () => {
it('should return no entities when paginating forward with end cursor', async () => {
await createTestEntities();
const firstResult = await paginate(undefined, forward);
const secondResult = await paginate(firstResult.nextCursor, forward);
const finalResult = await paginate(secondResult.nextCursor, forward);
const finalResult = await paginate(end, forward);
assert.deepEqual(finalResult.entities, []);
assert.equal(finalResult.previousCursor, start);
assert.equal(finalResult.nextCursor, end);
});

it('should return second entity when paginating backward without cursor', async () => {
it('should return second entity when paginating backward with start cursor', async () => {
await createTestEntities();
const finalResult = await paginate(undefined, backward);
const finalResult = await paginate(start, backward);
assert.deepEqual(finalResult.entities, [secondEntity]);
assert.equal(finalResult.previousCursor, createCursorFromEntity(secondEntity, sort));
assert.equal(finalResult.nextCursor, end);
});

it('should return first entity when paginating backward with first cursor', async () => {
await createTestEntities();
const firstResult = await paginate(undefined, backward);
const firstResult = await paginate(start, backward);
const finalResult = await paginate(firstResult.previousCursor, backward);
assert.deepEqual(finalResult.entities, [firstEntity]);
assert.equal(finalResult.previousCursor, end);
assert.equal(finalResult.nextCursor, createCursorFromEntity(firstEntity, sort));
});

it('should return no entities when paginating backward with second cursor', async () => {
it('should return no entities when paginating backward with end cursor', async () => {
await createTestEntities();
const firstResult = await paginate(undefined, backward);
const secondResult = await paginate(firstResult.previousCursor, backward);
const finalResult = await paginate(secondResult.previousCursor, backward);
const finalResult = await paginate(end, backward);
assert.deepEqual(finalResult.entities, []);
assert.equal(finalResult.previousCursor, end);
assert.equal(finalResult.nextCursor, start);
});
};
5 changes: 4 additions & 1 deletion src/types/Cursor.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
type Cursor = string | undefined;
export const start = undefined;
export const end = '';

type Cursor = string | typeof start | typeof end;

export default Cursor;
3 changes: 2 additions & 1 deletion src/utils/createCursorFromEntity/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import 'mocha'; // tslint:disable-line:no-import-side-effect
import * as assert from 'power-assert';
import { TestEntity, testEntity } from '../../tests/utils/testEntity';
import { end } from '../../types/Cursor';
import { asc } from '../../types/SortOrder';
import createCursorFromEntity from './index';

describe('createCursorFromEntity', () => {
it('should return undefined when the entity is undefined', () => {
const actualResult = createCursorFromEntity<TestEntity>(undefined, { id: asc });
assert.equal(actualResult, undefined);
assert.equal(actualResult, end);
});

it('should return the correct cursor when the entity is defined', () => {
Expand Down
4 changes: 2 additions & 2 deletions src/utils/createCursorFromEntity/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import * as btoa from 'btoa';
import { get, set } from 'lodash';
import Cursor from '../../types/Cursor';
import Cursor, { end } from '../../types/Cursor';
import Entity from '../../types/Entity';
import Sort from '../../types/Sort';

export default <E extends Entity>(entity: E | undefined, sort: Sort<E>): Cursor => {
if (entity === undefined) {
return undefined;
return end;
}
const sortKeys = Object.keys(sort);
const cursorResult = sortKeys.reduce<Partial<E>>((result, sortKey) => {
Expand Down
12 changes: 12 additions & 0 deletions src/utils/createEndCursorResult/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Result } from '../../signatures/GetEntities';
import { end, start } from '../../types/Cursor';
import Entity from '../../types/Entity';
import Pagination from '../../types/Pagination';
import { forward } from '../../types/PaginationDirection';

export default <E extends Entity>(pagination: Pagination): Result<E> => {
if (pagination.direction === forward) {
return { entities: [], previousCursor: start, nextCursor: end };
}
return { entities: [], previousCursor: end, nextCursor: start };
};
37 changes: 37 additions & 0 deletions src/utils/createGetEntitiesResult/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { first, last } from 'lodash';
import { Result } from '../../signatures/GetEntities';
import { end, start } from '../../types/Cursor';
import Entity from '../../types/Entity';
import Pagination from '../../types/Pagination';
import { backward, forward } from '../../types/PaginationDirection';
import Sort from '../../types/Sort';
import createCursorFromEntity from '../../utils/createCursorFromEntity';

export interface Opts<E extends Entity> {
readonly entities: E[];
readonly isEnd: boolean;
readonly pagination: Pagination;
readonly sort: Sort<E>;
}

export default <E extends Entity>({ entities, isEnd, pagination, sort }: Opts<E>): Result<E> => {
const nextCursor = createCursorFromEntity(last(entities), sort);
const previousCursor = createCursorFromEntity(first(entities), sort);

if (isEnd && pagination.direction === forward) {
return { entities, nextCursor: end, previousCursor };
}
if (isEnd && pagination.direction === backward) {
return { entities, nextCursor, previousCursor: end };
}

const isStart = pagination.cursor === start;
if (isStart && pagination.direction === forward) {
return { entities, nextCursor, previousCursor: end };
}
if (isStart && pagination.direction === backward) {
return { entities, nextCursor: end, previousCursor };
}

return { entities, nextCursor, previousCursor };
};
16 changes: 14 additions & 2 deletions src/utils/createPaginationFilter/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import 'mocha'; // tslint:disable-line:no-import-side-effect
import * as assert from 'power-assert';
import PaginationFilterError from '../../errors/PaginationFilterError';
import { TestEntity, testEntity } from '../../tests/utils/testEntity';
import { end, start } from '../../types/Cursor';
import { Filter } from '../../types/Filter';
import Pagination from '../../types/Pagination';
import { backward, forward } from '../../types/PaginationDirection';
Expand All @@ -12,13 +14,23 @@ import createPaginationFilter from './index';
describe('createCursorFromEntity', () => {
const sort: Sort<TestEntity> = { id: asc, numberProp: desc };

it('should return empty filter when the cursor is undefined', () => {
const pagination: Pagination = { cursor: undefined, direction: forward, limit: 1 };
it('should return empty filter when the cursor is start', () => {
const pagination: Pagination = { cursor: start, direction: forward, limit: 1 };
const actualResult = createPaginationFilter<TestEntity>(pagination, sort);
const expectedResult = {};
assert.deepEqual(actualResult, expectedResult);
});

it('should return empty filter when the cursor is end', () => {
const pagination: Pagination = { cursor: end, direction: forward, limit: 1 };
try {
createPaginationFilter<TestEntity>(pagination, sort);
assert.fail();
} catch (err) {
assert.ok(err instanceof PaginationFilterError);
}
});

it('should return the correct filter when the cursor is defined and going forward', () => {
const cursor = createCursorFromEntity<TestEntity>(testEntity, sort);
const pagination: Pagination = { cursor, direction: forward, limit: 1 };
Expand Down
10 changes: 8 additions & 2 deletions src/utils/createPaginationFilter/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import * as atob from 'atob';
import { get, mapValues } from 'lodash';
import PaginationFilterError from '../../errors/PaginationFilterError';
import { end, start } from '../../types/Cursor';
import Entity from '../../types/Entity';
// tslint:disable-next-line:no-unused
import Filter, { ConditionFilter, EntityFilter } from '../../types/Filter';
Expand All @@ -13,10 +15,14 @@ const xor = (conditionA: boolean, conditionB: boolean) => {
};

export default <E extends Entity>(pagination: Pagination, sort: Sort<E>): Filter<E> => {
if (pagination.cursor === undefined) {
if (pagination.cursor === start) {
return {};
}
const cursorObj = JSON.parse(atob(pagination.cursor));
if (pagination.cursor === end) {
throw new PaginationFilterError();
}
const cursor = pagination.cursor;
const cursorObj = JSON.parse(atob(cursor));
const filter = mapValues(cursorObj, (cursorValue, sortKey) => {
const ascendingPagination = !xor(
get(sort, sortKey) === asc,
Expand Down