Conversion from svelte params to pagination query to search params
This commit is contained in:
98
src/lib/pagination/pagination.test.ts
Normal file
98
src/lib/pagination/pagination.test.ts
Normal file
@ -0,0 +1,98 @@
|
||||
import { range } from 'ramda'
|
||||
import { describe, expect, test } from 'vitest'
|
||||
import { filterByPropContains, dropAndTake, filterAndCount } from './pagination'
|
||||
|
||||
describe('pagination', () => {
|
||||
test('does not drop any items by default', () => {
|
||||
const items = range(0, 100)
|
||||
expect(dropAndTake({})(items)).toHaveLength(100)
|
||||
})
|
||||
|
||||
test('limits out exact number of items', () => {
|
||||
const items = range(0, 100)
|
||||
expect(dropAndTake({ limit: 10 })(items)).toHaveLength(10)
|
||||
expect(dropAndTake({ limit: 10 })(items)[0]).toBe(0)
|
||||
expect(dropAndTake({ limit: 10 })(items)[9]).toBe(9)
|
||||
})
|
||||
|
||||
test('offset is skipping a number of items from the front', () => {
|
||||
const items = range(0, 100)
|
||||
expect(dropAndTake({ offset: 10 })(items)).toHaveLength(90)
|
||||
expect(dropAndTake({ offset: 10 })(items)[0]).toBe(10)
|
||||
})
|
||||
|
||||
test('is able to combine limit and offset', () => {
|
||||
const items = range(0, 100)
|
||||
expect(dropAndTake({ offset: 10, limit: 10 })(items)).toHaveLength(10)
|
||||
expect(dropAndTake({ offset: 10, limit: 10 })(items)[0]).toBe(10)
|
||||
expect(dropAndTake({ offset: 10, limit: 10 })(items)[9]).toBe(19)
|
||||
})
|
||||
|
||||
test('is able to filter by a field', () => {
|
||||
const items = [
|
||||
{
|
||||
id: 1,
|
||||
prop: ['yes'],
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
prop: ['yes', 'no'],
|
||||
},
|
||||
]
|
||||
|
||||
expect(filterByPropContains({ prop: 'no' })(items)).toHaveLength(1)
|
||||
expect(filterByPropContains({ prop: 'no' })(items)[0].id).toBe(2)
|
||||
|
||||
expect(filterByPropContains({ prop: 'yes' })(items)[0].id).toBe(1)
|
||||
expect(filterByPropContains({ prop: 'yes' })(items)).toHaveLength(2)
|
||||
})
|
||||
|
||||
describe('is able to combine limit and offset while filtering by field', () => {
|
||||
const items = [
|
||||
{
|
||||
id: 1,
|
||||
prop: ['yes'],
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
prop: ['yes', 'no'],
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
prop: ['yes', 'no'],
|
||||
},
|
||||
]
|
||||
|
||||
test('combine all parameters', () => {
|
||||
const result = filterAndCount({
|
||||
offset: 1,
|
||||
limit: 1,
|
||||
filters: { prop: 'no' },
|
||||
})(items)
|
||||
expect(result.totalCount).toBe(2)
|
||||
expect(result.items[0].id).toBe(3)
|
||||
})
|
||||
|
||||
test('with 0 offset', () => {
|
||||
const result = filterAndCount({
|
||||
offset: 0,
|
||||
limit: 1,
|
||||
filters: { prop: 'no' },
|
||||
})(items)
|
||||
expect(result.totalCount).toBe(2)
|
||||
expect(result.items[0].id).toBe(2)
|
||||
})
|
||||
|
||||
test('without filter', () => {
|
||||
const result = filterAndCount({ offset: 1, limit: 1 })(items)
|
||||
expect(result.totalCount).toBe(3)
|
||||
expect(result.items[0].id).toBe(2)
|
||||
})
|
||||
|
||||
test('without any params', () => {
|
||||
const result = filterAndCount({})(items)
|
||||
expect(result.totalCount).toBe(3)
|
||||
expect(result.items.length).toEqual(result.totalCount)
|
||||
})
|
||||
})
|
||||
})
|
44
src/lib/pagination/pagination.ts
Normal file
44
src/lib/pagination/pagination.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import { identity } from 'ramda'
|
||||
import { flow, A } from '@mobily/ts-belt'
|
||||
const { drop, take } = A
|
||||
|
||||
export interface PaginationQuery {
|
||||
offset?: number
|
||||
limit?: number
|
||||
filters?: Record<string, string>
|
||||
}
|
||||
|
||||
export interface PaginationResult<ItemType> {
|
||||
items: ItemType[]
|
||||
totalCount: number
|
||||
}
|
||||
|
||||
export function dropAndTake<Item>({ offset = 0, limit = Infinity }) {
|
||||
return flow(drop<Item>(offset), take<Item>(limit))
|
||||
}
|
||||
|
||||
export function filterByPropContains<Item>(filters: Record<string, string>) {
|
||||
return function (items: Item[]) {
|
||||
return items.filter((item) => {
|
||||
return Object.entries(filters).every(([fieldName, value]) =>
|
||||
item[fieldName].includes(value)
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export function filterAndCount<Item>({
|
||||
filters,
|
||||
...dropTakeParams
|
||||
}: PaginationQuery) {
|
||||
return function (items: Item[]) {
|
||||
const filterFunction = filters
|
||||
? filterByPropContains<Item>(filters)
|
||||
: identity
|
||||
const filteredItems = filterFunction(items)
|
||||
return {
|
||||
items: dropAndTake<Item>(dropTakeParams)(filteredItems),
|
||||
totalCount: filteredItems.length,
|
||||
}
|
||||
}
|
||||
}
|
64
src/lib/pagination/searchParams.test.ts
Normal file
64
src/lib/pagination/searchParams.test.ts
Normal file
@ -0,0 +1,64 @@
|
||||
import { describe, test, expect } from 'vitest'
|
||||
import {
|
||||
getPaginationQueryFromSearchParams,
|
||||
getPaginationSearchParams,
|
||||
parseParams,
|
||||
} from './searchParams'
|
||||
|
||||
describe('convert search params', () => {
|
||||
test('drop take params are not taken as filters', () => {
|
||||
expect(
|
||||
getPaginationQueryFromSearchParams(
|
||||
new URLSearchParams('offset=2&limit=5')
|
||||
)
|
||||
).toEqual({ offset: 2, limit: 5 })
|
||||
})
|
||||
|
||||
test('return empty paginationQuery if ', () => {
|
||||
expect(getPaginationQueryFromSearchParams(new URLSearchParams(''))).toEqual(
|
||||
{}
|
||||
)
|
||||
})
|
||||
|
||||
test('other than drop take params are moved to filters ', () => {
|
||||
expect(
|
||||
getPaginationQueryFromSearchParams(new URLSearchParams('tag=news'))
|
||||
).toEqual({ filters: { tag: 'news' } })
|
||||
})
|
||||
|
||||
test('offset and filter combined', () => {
|
||||
expect(
|
||||
getPaginationQueryFromSearchParams(
|
||||
new URLSearchParams('offset=3&tag=news')
|
||||
)
|
||||
).toEqual({ offset: 3, filters: { tag: 'news' } })
|
||||
})
|
||||
})
|
||||
|
||||
describe('get search params', () => {
|
||||
test('parse params', () => {
|
||||
const params = 'tags/News/page/1'
|
||||
expect(parseParams(params)).toEqual({ tags: 'News', page: '1' })
|
||||
})
|
||||
|
||||
test('should parse values into searchParams for first page', () => {
|
||||
const params = 'tags/News/page/1'
|
||||
expect(getPaginationSearchParams(7, params).toString()).toEqual(
|
||||
'limit=7&offset=0&tags=News'
|
||||
)
|
||||
})
|
||||
|
||||
test('should parse values into searchParams for third page', () => {
|
||||
const params = 'tags/News/page/3'
|
||||
expect(getPaginationSearchParams(7, params).toString()).toEqual(
|
||||
'limit=7&offset=14&tags=News'
|
||||
)
|
||||
})
|
||||
|
||||
test('should return first page without any params specified', () => {
|
||||
const params = ''
|
||||
expect(getPaginationSearchParams(7, params).toString()).toEqual(
|
||||
'limit=7&offset=0'
|
||||
)
|
||||
})
|
||||
})
|
46
src/lib/pagination/searchParams.ts
Normal file
46
src/lib/pagination/searchParams.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import { splitEvery } from 'ramda'
|
||||
import type { PaginationQuery } from './pagination'
|
||||
|
||||
export function getPaginationQueryFromSearchParams(
|
||||
searchParams: URLSearchParams
|
||||
) {
|
||||
return Array.from(searchParams).reduce<PaginationQuery>(
|
||||
(acc, [key, value]) => {
|
||||
const isDropTake = ['offset', 'limit'].includes(key)
|
||||
if (isDropTake) {
|
||||
return {
|
||||
...acc,
|
||||
[key]: Number(value),
|
||||
}
|
||||
}
|
||||
return {
|
||||
...acc,
|
||||
filters: {
|
||||
...acc.filters,
|
||||
[key]: value,
|
||||
},
|
||||
}
|
||||
},
|
||||
{}
|
||||
)
|
||||
}
|
||||
|
||||
export function parseParams(params: string) {
|
||||
const splittedParams = params.split('/')
|
||||
if (splittedParams.length % 2 !== 0) {
|
||||
return []
|
||||
}
|
||||
const splits = splitEvery(2, splittedParams)
|
||||
return Object.fromEntries(splits)
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert svelte `load` params into a `URLSearchParams` so they can be used to fetch endpoints with pagination queries
|
||||
*/
|
||||
export function getPaginationSearchParams(pageSize: number, params: string) {
|
||||
const { page = 1, ...filters } = parseParams(params)
|
||||
|
||||
const offset = pageSize * (page - 1)
|
||||
const limit = pageSize
|
||||
return new URLSearchParams({ limit, offset, ...filters })
|
||||
}
|
Reference in New Issue
Block a user