nestjs-paginate/src/paginate.spec.ts

550 lines
18 KiB
TypeScript
Raw Normal View History

2022-02-06 18:55:58 +00:00
import { createConnection, Repository, Column, In, Connection } from 'typeorm'
import {
Paginated,
paginate,
PaginateConfig,
FilterOperator,
isOperator,
getFilterTokens,
2022-01-27 21:12:52 +00:00
OperatorSymbolToFunction,
} from './paginate'
2020-06-26 21:25:03 +00:00
import { PaginateQuery } from './decorator'
import { Entity, PrimaryGeneratedColumn, CreateDateColumn } from 'typeorm'
import { HttpException } from '@nestjs/common'
@Entity()
export class CatEntity {
@PrimaryGeneratedColumn()
id: number
2020-06-28 17:34:00 +00:00
@Column()
name: string
@Column()
color: string
2021-08-19 14:42:18 +00:00
@Column({ nullable: true })
age: number | null
2020-06-26 21:25:03 +00:00
@CreateDateColumn()
createdAt: string
}
describe('paginate', () => {
2022-02-06 18:52:38 +00:00
let connection: Connection
2020-06-26 21:25:03 +00:00
let repo: Repository<CatEntity>
2020-06-28 17:34:00 +00:00
let cats: CatEntity[]
2020-06-26 21:25:03 +00:00
beforeAll(async () => {
2022-02-06 18:52:38 +00:00
connection = await createConnection({
2020-06-26 21:25:03 +00:00
type: 'sqlite',
database: ':memory:',
synchronize: true,
logging: false,
entities: [CatEntity],
})
repo = connection.getRepository(CatEntity)
2020-06-28 17:34:00 +00:00
cats = await repo.save([
2021-08-19 14:42:18 +00:00
repo.create({ name: 'Milo', color: 'brown', age: 6 }),
2022-01-27 21:12:52 +00:00
repo.create({ name: 'Garfield', color: 'ginger', age: 5 }),
2021-08-19 14:42:18 +00:00
repo.create({ name: 'Shadow', color: 'black', age: 4 }),
repo.create({ name: 'George', color: 'white', age: 3 }),
repo.create({ name: 'Leche', color: 'white', age: null }),
2020-06-28 17:34:00 +00:00
])
2020-06-26 21:25:03 +00:00
})
it('should return an instance of Paginated', async () => {
const config: PaginateConfig<CatEntity> = {
sortableColumns: ['id'],
2021-08-19 14:42:18 +00:00
defaultSortBy: [['id', 'ASC']],
2020-06-26 21:25:03 +00:00
defaultLimit: 1,
}
const query: PaginateQuery = {
path: '',
}
2020-06-28 17:34:00 +00:00
const result = await paginate<CatEntity>(query, repo, config)
2020-06-26 21:25:03 +00:00
2020-06-28 17:34:00 +00:00
expect(result).toBeInstanceOf(Paginated)
expect(result.data).toStrictEqual(cats.slice(0, 1))
2020-06-26 21:25:03 +00:00
})
2022-02-06 18:52:38 +00:00
it('should accept a query builder', async () => {
const config: PaginateConfig<CatEntity> = {
sortableColumns: ['id'],
defaultSortBy: [['id', 'ASC']],
defaultLimit: 1,
}
const query: PaginateQuery = {
path: '',
}
const queryBuilder = await repo.createQueryBuilder('cats')
const result = await paginate<CatEntity>(query, queryBuilder, config)
expect(result.data).toStrictEqual(cats.slice(0, 1))
})
it('should accept a query builder with custom condition', async () => {
const config: PaginateConfig<CatEntity> = {
sortableColumns: ['id'],
defaultSortBy: [['id', 'ASC']],
defaultLimit: 1,
}
const query: PaginateQuery = {
path: '',
}
const queryBuilder = await connection
.createQueryBuilder()
.select('cats')
.from(CatEntity, 'cats')
.where('cats.color = :color', { color: 'white' })
const result = await paginate<CatEntity>(query, queryBuilder, config)
expect(result.data).toStrictEqual(cats.slice(3, 4))
})
2020-06-28 17:34:00 +00:00
it('should default to page 1, if negative page is given', async () => {
2020-06-26 21:25:03 +00:00
const config: PaginateConfig<CatEntity> = {
sortableColumns: ['id'],
2020-06-28 17:34:00 +00:00
defaultLimit: 1,
2020-06-26 21:25:03 +00:00
}
const query: PaginateQuery = {
path: '',
2020-06-28 17:34:00 +00:00
page: -1,
2020-06-26 21:25:03 +00:00
}
2020-06-28 17:34:00 +00:00
const result = await paginate<CatEntity>(query, repo, config)
2020-06-26 21:25:03 +00:00
2020-06-28 17:34:00 +00:00
expect(result.meta.currentPage).toBe(1)
expect(result.data).toStrictEqual(cats.slice(0, 1))
2020-06-26 21:25:03 +00:00
})
2021-03-04 12:29:53 +00:00
it('should default to limit maxLimit, if more than maxLimit is given', async () => {
const config: PaginateConfig<CatEntity> = {
sortableColumns: ['id'],
defaultLimit: 5,
maxLimit: 2,
2021-03-04 12:29:53 +00:00
}
const query: PaginateQuery = {
path: '',
page: 1,
limit: 20,
}
const result = await paginate<CatEntity>(query, repo, config)
expect(result.data).toStrictEqual(cats.slice(0, 2))
})
2020-06-26 21:25:03 +00:00
it('should return correct links', async () => {
const config: PaginateConfig<CatEntity> = {
sortableColumns: ['id'],
}
const query: PaginateQuery = {
path: '',
page: 2,
limit: 2,
}
const { links } = await paginate<CatEntity>(query, repo, config)
expect(links.first).toBe('?page=1&limit=2&sortBy=id:ASC')
expect(links.previous).toBe('?page=1&limit=2&sortBy=id:ASC')
expect(links.current).toBe('?page=2&limit=2&sortBy=id:ASC')
expect(links.next).toBe('?page=3&limit=2&sortBy=id:ASC')
expect(links.last).toBe('?page=3&limit=2&sortBy=id:ASC')
})
2020-06-27 18:54:14 +00:00
it('should default to defaultSortBy if query sortBy does not exist', async () => {
2020-06-26 21:25:03 +00:00
const config: PaginateConfig<CatEntity> = {
sortableColumns: ['id', 'createdAt'],
2020-06-28 17:34:00 +00:00
defaultSortBy: [['id', 'DESC']],
2020-06-26 21:25:03 +00:00
}
const query: PaginateQuery = {
path: '',
}
2020-06-28 17:34:00 +00:00
const result = await paginate<CatEntity>(query, repo, config)
2020-06-26 21:25:03 +00:00
2020-06-28 17:34:00 +00:00
expect(result.meta.sortBy).toStrictEqual([['id', 'DESC']])
expect(result.data).toStrictEqual(cats.slice(0).reverse())
2020-06-26 21:25:03 +00:00
})
2020-06-28 17:34:00 +00:00
it('should sort result by multiple columns', async () => {
2020-06-27 18:54:14 +00:00
const config: PaginateConfig<CatEntity> = {
2020-06-28 17:34:00 +00:00
sortableColumns: ['name', 'color'],
2020-06-27 18:54:14 +00:00
}
const query: PaginateQuery = {
path: '',
sortBy: [
2020-06-28 17:34:00 +00:00
['color', 'DESC'],
['name', 'ASC'],
2020-06-27 18:54:14 +00:00
],
}
2020-06-28 17:34:00 +00:00
const result = await paginate<CatEntity>(query, repo, config)
2020-06-27 18:54:14 +00:00
2020-06-28 17:34:00 +00:00
expect(result.meta.sortBy).toStrictEqual([
['color', 'DESC'],
['name', 'ASC'],
2020-06-27 18:54:14 +00:00
])
2020-06-28 17:34:00 +00:00
expect(result.data).toStrictEqual([cats[3], cats[4], cats[1], cats[0], cats[2]])
})
it('should return result based on search term', async () => {
const config: PaginateConfig<CatEntity> = {
sortableColumns: ['id', 'name', 'color'],
searchableColumns: ['name', 'color'],
}
const query: PaginateQuery = {
path: '',
search: 'i',
}
const result = await paginate<CatEntity>(query, repo, config)
expect(result.meta.search).toStrictEqual('i')
expect(result.data).toStrictEqual([cats[0], cats[1], cats[3], cats[4]])
expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&search=i')
2020-06-27 18:54:14 +00:00
})
2021-10-12 11:01:53 +00:00
it('should return result based on search term and searchBy columns', async () => {
const config: PaginateConfig<CatEntity> = {
sortableColumns: ['id', 'name', 'color'],
searchableColumns: ['name', 'color'],
}
const searchTerm = 'white'
const expectedResultData = cats.filter((cat: CatEntity) => cat.color === searchTerm)
const query: PaginateQuery = {
path: '',
search: searchTerm,
searchBy: ['color'],
}
const result = await paginate<CatEntity>(query, repo, config)
expect(result.meta.search).toStrictEqual(searchTerm)
expect(result.meta.searchBy).toStrictEqual(['color'])
expect(result.data).toStrictEqual(expectedResultData)
expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&search=white&searchBy=color')
})
2021-08-19 14:42:18 +00:00
it('should return result based on where config and filter', async () => {
const config: PaginateConfig<CatEntity> = {
sortableColumns: ['id'],
where: {
color: 'white',
},
filterableColumns: {
name: [FilterOperator.NOT],
},
}
const query: PaginateQuery = {
path: '',
filter: {
name: '$not:Leche',
},
}
const result = await paginate<CatEntity>(query, repo, config)
expect(result.meta.filter).toStrictEqual({
name: '$not:Leche',
})
expect(result.data).toStrictEqual([cats[3]])
expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&filter.name=$not:Leche')
})
it('should return result based on where array and filter', async () => {
const config: PaginateConfig<CatEntity> = {
sortableColumns: ['id'],
where: [
{
color: 'white',
},
{
age: 4,
},
],
filterableColumns: {
name: [FilterOperator.NOT],
},
}
const query: PaginateQuery = {
path: '',
filter: {
name: '$not:Leche',
},
}
const result = await paginate<CatEntity>(query, repo, config)
expect(result.meta.filter).toStrictEqual({
name: '$not:Leche',
})
expect(result.data).toStrictEqual([cats[2], cats[3]])
expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&filter.name=$not:Leche')
})
2021-08-19 14:42:18 +00:00
it('should return result based on multiple filter', async () => {
const config: PaginateConfig<CatEntity> = {
sortableColumns: ['id'],
filterableColumns: {
name: [FilterOperator.NOT],
color: [FilterOperator.EQ],
},
}
const query: PaginateQuery = {
path: '',
filter: {
name: '$not:Leche',
color: 'white',
},
}
const result = await paginate<CatEntity>(query, repo, config)
expect(result.meta.filter).toStrictEqual({
name: '$not:Leche',
color: 'white',
})
expect(result.data).toStrictEqual([cats[3]])
expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&filter.name=$not:Leche&filter.color=white')
})
it('should return result based on filter and search term', async () => {
const config: PaginateConfig<CatEntity> = {
sortableColumns: ['id'],
searchableColumns: ['name', 'color'],
filterableColumns: {
id: [FilterOperator.NOT, FilterOperator.IN],
},
}
const query: PaginateQuery = {
path: '',
search: 'white',
filter: {
id: '$not:$in:1,2,5',
},
}
const result = await paginate<CatEntity>(query, repo, config)
expect(result.meta.search).toStrictEqual('white')
expect(result.meta.filter).toStrictEqual({ id: '$not:$in:1,2,5' })
expect(result.data).toStrictEqual([cats[3]])
expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&search=white&filter.id=$not:$in:1,2,5')
})
it('should return result based on filter and where config', async () => {
const config: PaginateConfig<CatEntity> = {
sortableColumns: ['id'],
where: {
color: In(['black', 'white']),
},
filterableColumns: {
id: [FilterOperator.NOT, FilterOperator.IN],
},
}
const query: PaginateQuery = {
path: '',
filter: {
id: '$not:$in:1,2,5',
},
}
const result = await paginate<CatEntity>(query, repo, config)
expect(result.data).toStrictEqual([cats[2], cats[3]])
expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&filter.id=$not:$in:1,2,5')
})
it('should return result based on range filter', async () => {
const config: PaginateConfig<CatEntity> = {
sortableColumns: ['id'],
filterableColumns: {
age: [FilterOperator.GTE],
},
}
const query: PaginateQuery = {
path: '',
filter: {
age: '$gte:4',
},
}
const result = await paginate<CatEntity>(query, repo, config)
2022-01-27 21:12:52 +00:00
expect(result.data).toStrictEqual([cats[0], cats[1], cats[2]])
2021-08-19 14:42:18 +00:00
expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&filter.age=$gte:4')
})
it('should return result based on between range filter', async () => {
const config: PaginateConfig<CatEntity> = {
sortableColumns: ['id'],
filterableColumns: {
age: [FilterOperator.BTW],
},
}
const query: PaginateQuery = {
path: '',
filter: {
2022-01-27 21:12:52 +00:00
age: '$btw:4,5',
},
}
const result = await paginate<CatEntity>(query, repo, config)
2022-01-27 21:12:52 +00:00
expect(result.data).toStrictEqual([cats[1], cats[2]])
expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&filter.age=$btw:4,5')
})
2021-08-19 14:42:18 +00:00
it('should return result based on is null query', async () => {
const config: PaginateConfig<CatEntity> = {
sortableColumns: ['id'],
filterableColumns: {
age: [FilterOperator.NULL],
},
}
const query: PaginateQuery = {
path: '',
filter: {
age: '$null',
},
}
const result = await paginate<CatEntity>(query, repo, config)
2022-01-27 21:12:52 +00:00
expect(result.data).toStrictEqual([cats[4]])
2021-08-19 14:42:18 +00:00
expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&filter.age=$null')
})
it('should return result based on not null query', async () => {
const config: PaginateConfig<CatEntity> = {
sortableColumns: ['id'],
filterableColumns: {
age: [FilterOperator.NOT, FilterOperator.NULL],
},
}
const query: PaginateQuery = {
path: '',
filter: {
age: '$not:$null',
},
}
const result = await paginate<CatEntity>(query, repo, config)
2022-01-27 21:12:52 +00:00
expect(result.data).toStrictEqual([cats[0], cats[1], cats[2], cats[3]])
2021-08-19 14:42:18 +00:00
expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&filter.age=$not:$null')
})
it('should ignore filterable column which is not configured', async () => {
const config: PaginateConfig<CatEntity> = {
sortableColumns: ['id'],
filterableColumns: {
name: [FilterOperator.NOT, FilterOperator.NULL],
},
}
const query: PaginateQuery = {
path: '',
filter: {
age: '$not:$null',
},
}
const result = await paginate<CatEntity>(query, repo, config)
expect(result.data).toStrictEqual(cats)
expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&filter.age=$not:$null')
})
it('should ignore filter operator which is not configured', async () => {
const config: PaginateConfig<CatEntity> = {
sortableColumns: ['id'],
filterableColumns: {
age: [FilterOperator.NOT],
},
}
const query: PaginateQuery = {
path: '',
filter: {
age: '$not:$null',
},
}
const result = await paginate<CatEntity>(query, repo, config)
expect(result.data).toStrictEqual(cats)
expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&filter.age=$not:$null')
})
2020-06-26 21:25:03 +00:00
it('should throw an error when no sortableColumns', async () => {
const config: PaginateConfig<CatEntity> = {
sortableColumns: [],
}
const query: PaginateQuery = {
path: '',
}
try {
await paginate<CatEntity>(query, repo, config)
} catch (err) {
expect(err).toBeInstanceOf(HttpException)
}
})
it.each([
{ operator: '$eq', result: true },
{ operator: '$gte', result: true },
{ operator: '$gt', result: true },
{ operator: '$in', result: true },
{ operator: '$null', result: true },
{ operator: '$lt', result: true },
{ operator: '$lte', result: true },
{ operator: '$btw', result: true },
{ operator: '$not', result: true },
{ operator: '$fake', result: false },
])('should check operator "$operator" valid is $result', ({ operator, result }) => {
expect(isOperator(operator)).toStrictEqual(result)
})
it.each([
{ operator: '$eq', name: 'Equal' },
{ operator: '$gt', name: 'MoreThan' },
{ operator: '$gte', name: 'MoreThanOrEqual' },
{ operator: '$in', name: 'In' },
{ operator: '$null', name: 'IsNull' },
{ operator: '$lt', name: 'LessThan' },
{ operator: '$lte', name: 'LessThanOrEqual' },
{ operator: '$btw', name: 'Between' },
{ operator: '$not', name: 'Not' },
])('should get operator function $name for "$operator"', ({ operator, name }) => {
2022-01-27 21:12:52 +00:00
const func = OperatorSymbolToFunction.get(operator as FilterOperator)
expect(func.name).toStrictEqual(name)
})
it.each([
{ string: '$eq:value', tokens: [null, '$eq', 'value'] },
{ string: '$eq:val:ue', tokens: [null, '$eq', 'val:ue'] },
{ string: '$in:value1,value2,value3', tokens: [null, '$in', 'value1,value2,value3'] },
{ string: '$not:$in:value1:a,value2:b,value3:c', tokens: ['$not', '$in', 'value1:a,value2:b,value3:c'] },
{ string: 'value', tokens: [null, '$eq', 'value'] },
{ string: 'val:ue', tokens: [null, '$eq', 'val:ue'] },
{ string: '$not:value', tokens: [null, '$not', 'value'] },
{ string: '$eq:$not:value', tokens: ['$eq', '$not', 'value'] },
{ string: '$eq:$null', tokens: ['$eq', '$null'] },
{ string: '$null', tokens: [null, '$null'] },
{ string: '', tokens: [null, '$eq', ''] },
{ string: '$eq:$not:$in:value', tokens: [] },
])('should get filter tokens for "$string"', ({ string, tokens }) => {
expect(getFilterTokens(string)).toStrictEqual(tokens)
})
2020-06-26 21:25:03 +00:00
})