@longzai-intelligence/pagination 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +22 -0
- package/.turbo/turbo-lint.log +1 -0
- package/.turbo/turbo-test$colon$coverage.log +36 -0
- package/.turbo/turbo-test.log +14 -0
- package/.turbo/turbo-typecheck.log +4 -0
- package/CHANGELOG.md +11 -0
- package/README.md +218 -0
- package/coverage/base.css +224 -0
- package/coverage/block-navigation.js +87 -0
- package/coverage/coverage-final.json +12 -0
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +161 -0
- package/coverage/prettify.css +1 -0
- package/coverage/prettify.js +2 -0
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +210 -0
- package/coverage/src/adapters/index.html +131 -0
- package/coverage/src/adapters/index.ts.html +118 -0
- package/coverage/src/adapters/typeorm.adapter.ts.html +940 -0
- package/coverage/src/index.html +116 -0
- package/coverage/src/index.ts.html +253 -0
- package/coverage/src/types/index.html +176 -0
- package/coverage/src/types/index.ts.html +133 -0
- package/coverage/src/types/pagination.types.ts.html +292 -0
- package/coverage/src/types/result.types.ts.html +415 -0
- package/coverage/src/types/sort.types.ts.html +265 -0
- package/coverage/src/types/typeorm.d.ts.html +196 -0
- package/coverage/src/utils/index.html +146 -0
- package/coverage/src/utils/index.ts.html +178 -0
- package/coverage/src/utils/pagination.util.ts.html +703 -0
- package/coverage/src/utils/validation.util.ts.html +535 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +117 -0
- package/dist/index.d.mts +117 -0
- package/dist/index.mjs +1 -0
- package/eslint.config.ts +3 -0
- package/package.json +62 -0
- package/src/__tests__/index.test.ts +66 -0
- package/src/__tests__/pagination.util.test.ts +253 -0
- package/src/__tests__/typeorm.adapter.test.ts +214 -0
- package/src/__tests__/validation.util.test.ts +122 -0
- package/src/adapters/index.ts +11 -0
- package/src/adapters/typeorm.adapter.ts +285 -0
- package/src/index.ts +56 -0
- package/src/types/index.ts +16 -0
- package/src/types/pagination.types.ts +69 -0
- package/src/types/result.types.ts +110 -0
- package/src/types/sort.types.ts +60 -0
- package/src/types/typeorm.d.ts +37 -0
- package/src/utils/index.ts +31 -0
- package/src/utils/pagination.util.ts +206 -0
- package/src/utils/validation.util.ts +150 -0
- package/tsconfig/.cache/app.tsbuildinfo +1 -0
- package/tsconfig/.cache/build.tsbuildinfo +1 -0
- package/tsconfig/.cache/node.tsbuildinfo +1 -0
- package/tsconfig/.cache/test.tsbuildinfo +1 -0
- package/tsconfig/app.json +12 -0
- package/tsconfig/node.json +11 -0
- package/tsconfig/test.json +14 -0
- package/tsconfig.json +9 -0
- package/tsdown.config.ts +6 -0
- package/vitest.config.ts +3 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { FindManyOptions, FindOptionsOrder, ObjectLiteral, Repository, SelectQueryBuilder } from "typeorm";
|
|
2
|
+
|
|
3
|
+
//#region src/types/pagination.types.d.ts
|
|
4
|
+
type PaginationParams = {
|
|
5
|
+
page?: number;
|
|
6
|
+
pageSize?: number;
|
|
7
|
+
};
|
|
8
|
+
type OffsetPaginationParams = {
|
|
9
|
+
limit?: number;
|
|
10
|
+
offset?: number;
|
|
11
|
+
};
|
|
12
|
+
type CursorPaginationParams = {
|
|
13
|
+
cursor?: string;
|
|
14
|
+
limit?: number;
|
|
15
|
+
};
|
|
16
|
+
declare const PAGINATION_DEFAULTS: {
|
|
17
|
+
readonly page: 1;
|
|
18
|
+
readonly pageSize: 20;
|
|
19
|
+
readonly maxPageSize: 100;
|
|
20
|
+
readonly minPageSize: 1;
|
|
21
|
+
};
|
|
22
|
+
type PaginationDefaults = typeof PAGINATION_DEFAULTS;
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region src/types/result.types.d.ts
|
|
25
|
+
type PaginatedResult<T> = {
|
|
26
|
+
items: T[];
|
|
27
|
+
total: number;
|
|
28
|
+
page: number;
|
|
29
|
+
pageSize: number;
|
|
30
|
+
totalPages: number;
|
|
31
|
+
hasNextPage: boolean;
|
|
32
|
+
hasPreviousPage: boolean;
|
|
33
|
+
};
|
|
34
|
+
type OffsetPaginatedResult<T> = {
|
|
35
|
+
items: T[];
|
|
36
|
+
total: number;
|
|
37
|
+
limit: number;
|
|
38
|
+
offset: number;
|
|
39
|
+
hasMore: boolean;
|
|
40
|
+
};
|
|
41
|
+
type CursorPaginatedResult<T> = {
|
|
42
|
+
items: T[];
|
|
43
|
+
nextCursor: string | null;
|
|
44
|
+
previousCursor: string | null;
|
|
45
|
+
hasMore: boolean;
|
|
46
|
+
};
|
|
47
|
+
//#endregion
|
|
48
|
+
//#region src/types/sort.types.d.ts
|
|
49
|
+
type SortOrder = 'asc' | 'desc';
|
|
50
|
+
type SortParams<T extends string = string> = {
|
|
51
|
+
sortBy?: T;
|
|
52
|
+
sortOrder?: SortOrder;
|
|
53
|
+
};
|
|
54
|
+
type SortItem<T extends string = string> = {
|
|
55
|
+
field: T;
|
|
56
|
+
order: SortOrder;
|
|
57
|
+
};
|
|
58
|
+
type MultiSortParams<T extends string = string> = {
|
|
59
|
+
sort?: SortItem<T>[];
|
|
60
|
+
};
|
|
61
|
+
//#endregion
|
|
62
|
+
//#region src/utils/pagination.util.d.ts
|
|
63
|
+
type OffsetPaginationResult = {
|
|
64
|
+
page: number;
|
|
65
|
+
pageSize: number;
|
|
66
|
+
};
|
|
67
|
+
type PagePaginationResult = {
|
|
68
|
+
offset: number;
|
|
69
|
+
limit: number;
|
|
70
|
+
};
|
|
71
|
+
declare function normalizePagination(params?: PaginationParams): Required<PaginationParams>;
|
|
72
|
+
declare function normalizeOffsetPagination(params?: OffsetPaginationParams): Required<OffsetPaginationParams>;
|
|
73
|
+
declare function calculateOffset(page: number, pageSize: number): number;
|
|
74
|
+
declare function calculateTotalPages(total: number, pageSize: number): number;
|
|
75
|
+
declare function hasNextPage(page: number, totalPages: number): boolean;
|
|
76
|
+
declare function hasPreviousPage(page: number): boolean;
|
|
77
|
+
declare function offsetToPage(offset: number, limit: number): OffsetPaginationResult;
|
|
78
|
+
declare function pageToOffset(page: number, pageSize: number): PagePaginationResult;
|
|
79
|
+
declare function createPaginatedResult<T>(items: T[], total: number, page: number, pageSize: number): PaginatedResult<T>;
|
|
80
|
+
//#endregion
|
|
81
|
+
//#region src/utils/validation.util.d.ts
|
|
82
|
+
type PaginationValidationResult = {
|
|
83
|
+
page: number;
|
|
84
|
+
pageSize: number;
|
|
85
|
+
};
|
|
86
|
+
type OffsetPaginationValidationResult = {
|
|
87
|
+
limit: number;
|
|
88
|
+
offset: number;
|
|
89
|
+
};
|
|
90
|
+
declare function isValidPage(page: unknown): page is number;
|
|
91
|
+
declare function isValidPageSize(pageSize: unknown): pageSize is number;
|
|
92
|
+
declare function isValidOffset(offset: unknown): offset is number;
|
|
93
|
+
declare function isValidLimit(limit: unknown): limit is number;
|
|
94
|
+
declare function isValidPagination(params: unknown): params is PaginationValidationResult;
|
|
95
|
+
declare function isValidOffsetPagination(params: unknown): params is OffsetPaginationValidationResult;
|
|
96
|
+
//#endregion
|
|
97
|
+
//#region src/adapters/typeorm.adapter.d.ts
|
|
98
|
+
type EntityMapper<TEntity, TDto> = (entity: TEntity) => TDto | Promise<TDto>;
|
|
99
|
+
type TypeOrmPaginateOptionsWithMapper<TEntity extends ObjectLiteral, TDto> = {
|
|
100
|
+
mapper: EntityMapper<TEntity, TDto>;
|
|
101
|
+
order?: FindOptionsOrder<TEntity>;
|
|
102
|
+
where?: FindManyOptions<TEntity>['where'];
|
|
103
|
+
relations?: FindManyOptions<TEntity>['relations'];
|
|
104
|
+
select?: FindManyOptions<TEntity>['select'];
|
|
105
|
+
};
|
|
106
|
+
type TypeOrmPaginateOptionsNoMapper<TEntity extends ObjectLiteral> = {
|
|
107
|
+
order?: FindOptionsOrder<TEntity>;
|
|
108
|
+
where?: FindManyOptions<TEntity>['where'];
|
|
109
|
+
relations?: FindManyOptions<TEntity>['relations'];
|
|
110
|
+
select?: FindManyOptions<TEntity>['select'];
|
|
111
|
+
};
|
|
112
|
+
type TypeOrmPaginateOptions<TEntity extends ObjectLiteral, TDto = TEntity> = TypeOrmPaginateOptionsWithMapper<TEntity, TDto> | TypeOrmPaginateOptionsNoMapper<TEntity>;
|
|
113
|
+
declare function paginateWithRepository<TEntity extends ObjectLiteral, TDto = TEntity>(repository: Repository<TEntity>, params: PaginationParams, options?: TypeOrmPaginateOptions<TEntity, TDto>): Promise<PaginatedResult<TEntity | TDto>>;
|
|
114
|
+
declare function paginateWithQueryBuilder<TEntity extends ObjectLiteral, TDto = TEntity>(queryBuilder: SelectQueryBuilder<TEntity>, params: PaginationParams, mapper?: EntityMapper<TEntity, TDto>): Promise<PaginatedResult<TEntity | TDto>>;
|
|
115
|
+
declare function createPaginationQueryBuilder<TEntity extends ObjectLiteral>(repository: Repository<TEntity>, alias?: string): SelectQueryBuilder<TEntity>;
|
|
116
|
+
//#endregion
|
|
117
|
+
export { type CursorPaginatedResult, type CursorPaginationParams, type EntityMapper, type MultiSortParams, type OffsetPaginatedResult, type OffsetPaginationParams, type OffsetPaginationResult, type OffsetPaginationValidationResult, PAGINATION_DEFAULTS, type PagePaginationResult, type PaginatedResult, type PaginationDefaults, type PaginationParams, type PaginationValidationResult, type SortItem, type SortOrder, type SortParams, type TypeOrmPaginateOptions, calculateOffset, calculateTotalPages, createPaginatedResult, createPaginationQueryBuilder, hasNextPage, hasPreviousPage, isValidLimit, isValidOffset, isValidOffsetPagination, isValidPage, isValidPageSize, isValidPagination, normalizeOffsetPagination, normalizePagination, offsetToPage, pageToOffset, paginateWithQueryBuilder, paginateWithRepository };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e={page:1,pageSize:20,maxPageSize:100,minPageSize:1};function t(t){return{page:Math.max(1,Math.floor(t?.page??e.page)),pageSize:Math.min(e.maxPageSize,Math.max(e.minPageSize,Math.floor(t?.pageSize??e.pageSize)))}}function n(t){return{limit:Math.min(e.maxPageSize,Math.max(e.minPageSize,Math.floor(t?.limit??e.pageSize))),offset:Math.max(0,Math.floor(t?.offset??0))}}function r(e,t){return(e-1)*t}function i(e,t){return t<=0?0:Math.ceil(e/t)}function a(e,t){return e<t}function o(e){return e>1}function s(e,t){let n=Math.max(1,t);return{page:Math.floor(e/n)+1,pageSize:n}}function c(e,t){let n=Math.max(1,e),r=Math.max(1,t);return{offset:(n-1)*r,limit:r}}function l(e,t,n,r){let s=i(t,r);return{items:e,total:t,page:n,pageSize:r,totalPages:s,hasNextPage:a(n,s),hasPreviousPage:o(n)}}function u(e){return typeof e==`object`&&!!e}function d(e){return typeof e==`number`&&Number.isInteger(e)&&e>=1}function f(t){return typeof t==`number`&&Number.isInteger(t)&&t>=e.minPageSize&&t<=e.maxPageSize}function p(e){return typeof e==`number`&&Number.isInteger(e)&&e>=0}function m(t){return typeof t==`number`&&Number.isInteger(t)&&t>=e.minPageSize&&t<=e.maxPageSize}function h(e){if(!u(e))return!1;let t=e.page,n=e.pageSize;return(t===void 0||d(t))&&(n===void 0||f(n))}function g(e){if(!u(e))return!1;let t=e.limit,n=e.offset;return(t===void 0||m(t))&&(n===void 0||p(n))}function _(e){return e!==void 0&&`mapper`in e}function v(e,t,n,r){let s=i(t,r);return{items:e,total:t,page:n,pageSize:r,totalPages:s,hasNextPage:a(n,s),hasPreviousPage:o(n)}}async function y(e,n,i){let{page:a,pageSize:o}=t(n),s={skip:r(a,o),take:o,where:i?.where,relations:i?.relations,select:i?.select,order:i?.order??{createdAt:`DESC`}},[c,l]=await e.findAndCount(s);return _(i)?v(await Promise.all(c.map(i.mapper)),l,a,o):v(c,l,a,o)}async function b(e,n,i){let{page:a,pageSize:o}=t(n),s=r(a,o);e.skip(s).take(o);let[c,l]=await e.getManyAndCount();return v(i?await Promise.all(c.map(i)):c,l,a,o)}function x(e,t){return e.createQueryBuilder(t??e.metadata.name.toLowerCase())}export{e as PAGINATION_DEFAULTS,r as calculateOffset,i as calculateTotalPages,l as createPaginatedResult,x as createPaginationQueryBuilder,a as hasNextPage,o as hasPreviousPage,m as isValidLimit,p as isValidOffset,g as isValidOffsetPagination,d as isValidPage,f as isValidPageSize,h as isValidPagination,n as normalizeOffsetPagination,t as normalizePagination,s as offsetToPage,c as pageToOffset,b as paginateWithQueryBuilder,y as paginateWithRepository};
|
package/eslint.config.ts
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@longzai-intelligence/pagination",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.cjs",
|
|
6
|
+
"types": "./dist/index.d.mts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": {
|
|
10
|
+
"import": "./dist/index.d.mts",
|
|
11
|
+
"require": "./dist/index.d.cts"
|
|
12
|
+
},
|
|
13
|
+
"import": "./dist/index.mjs",
|
|
14
|
+
"require": "./dist/index.cjs"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsdown",
|
|
19
|
+
"build:prod": "NODE_ENV=production tsdown",
|
|
20
|
+
"prepublishOnly": "bun run build:prod",
|
|
21
|
+
"dev": "tsdown --watch",
|
|
22
|
+
"clean": "rimraf dist tsconfig/.cache",
|
|
23
|
+
"test": "vitest run",
|
|
24
|
+
"test:watch": "vitest",
|
|
25
|
+
"test:coverage": "vitest run --coverage",
|
|
26
|
+
"lint": "eslint .",
|
|
27
|
+
"lint:fix": "eslint . --fix",
|
|
28
|
+
"typecheck": "bun run typecheck:app && bun run typecheck:node && bun run typecheck:test",
|
|
29
|
+
"typecheck:app": "tsc --noEmit -p tsconfig/app.json",
|
|
30
|
+
"typecheck:node": "tsc --noEmit -p tsconfig/node.json",
|
|
31
|
+
"typecheck:test": "tsc --noEmit -p tsconfig/test.json",
|
|
32
|
+
"upgrade-deps": "ncu -u"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"typeorm": "^0.3.0"
|
|
36
|
+
},
|
|
37
|
+
"peerDependenciesMeta": {
|
|
38
|
+
"typeorm": {
|
|
39
|
+
"optional": true
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/bun": "1.3.11",
|
|
44
|
+
"@types/node": "^25.6.0",
|
|
45
|
+
"eslint": "^10.2.0",
|
|
46
|
+
"rimraf": "^6.1.3",
|
|
47
|
+
"typescript": "^6.0.2",
|
|
48
|
+
"vitest": "^4.1.4",
|
|
49
|
+
"@longzai-intelligence/eslint-preset-library": "0.0.5",
|
|
50
|
+
"@longzai-intelligence/vitest-config": "0.0.5",
|
|
51
|
+
"@longzai-intelligence/typescript-config": "0.0.3",
|
|
52
|
+
"tsdown": "^0.21.7",
|
|
53
|
+
"@longzai-intelligence/tsdown-config": "0.0.1"
|
|
54
|
+
},
|
|
55
|
+
"keywords": [
|
|
56
|
+
"pagination",
|
|
57
|
+
"typeorm",
|
|
58
|
+
"typescript"
|
|
59
|
+
],
|
|
60
|
+
"license": "UNLICENSED",
|
|
61
|
+
"module": "./dist/index.mjs"
|
|
62
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file index.test.ts
|
|
3
|
+
* 主入口导出测试
|
|
4
|
+
* @author Longzai Intelligence
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, it, expect } from 'vitest';
|
|
8
|
+
|
|
9
|
+
describe('pagination 包导出', () => {
|
|
10
|
+
it('应正确导出所有类型', async () => {
|
|
11
|
+
const module = await import('@/index');
|
|
12
|
+
|
|
13
|
+
expect(module.PAGINATION_DEFAULTS).toBeDefined();
|
|
14
|
+
expect(module.PAGINATION_DEFAULTS.page).toBe(1);
|
|
15
|
+
expect(module.PAGINATION_DEFAULTS.pageSize).toBe(20);
|
|
16
|
+
expect(module.PAGINATION_DEFAULTS.maxPageSize).toBe(100);
|
|
17
|
+
expect(module.PAGINATION_DEFAULTS.minPageSize).toBe(1);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('应正确导出所有工具函数', async () => {
|
|
21
|
+
const module = await import('@/index');
|
|
22
|
+
|
|
23
|
+
expect(module.normalizePagination).toBeDefined();
|
|
24
|
+
expect(module.normalizeOffsetPagination).toBeDefined();
|
|
25
|
+
expect(module.calculateOffset).toBeDefined();
|
|
26
|
+
expect(module.calculateTotalPages).toBeDefined();
|
|
27
|
+
expect(module.hasNextPage).toBeDefined();
|
|
28
|
+
expect(module.hasPreviousPage).toBeDefined();
|
|
29
|
+
expect(module.offsetToPage).toBeDefined();
|
|
30
|
+
expect(module.pageToOffset).toBeDefined();
|
|
31
|
+
expect(module.createPaginatedResult).toBeDefined();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('应正确导出所有验证函数', async () => {
|
|
35
|
+
const module = await import('@/index');
|
|
36
|
+
|
|
37
|
+
expect(module.isValidPage).toBeDefined();
|
|
38
|
+
expect(module.isValidPageSize).toBeDefined();
|
|
39
|
+
expect(module.isValidOffset).toBeDefined();
|
|
40
|
+
expect(module.isValidLimit).toBeDefined();
|
|
41
|
+
expect(module.isValidPagination).toBeDefined();
|
|
42
|
+
expect(module.isValidOffsetPagination).toBeDefined();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('应正确导出所有适配器', async () => {
|
|
46
|
+
const module = await import('@/index');
|
|
47
|
+
|
|
48
|
+
expect(module.paginateWithRepository).toBeDefined();
|
|
49
|
+
expect(module.paginateWithQueryBuilder).toBeDefined();
|
|
50
|
+
expect(module.createPaginationQueryBuilder).toBeDefined();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('normalizePagination 应正常工作', async () => {
|
|
54
|
+
const { normalizePagination } = await import('@/index');
|
|
55
|
+
|
|
56
|
+
const result = normalizePagination({ page: 2, pageSize: 50 });
|
|
57
|
+
expect(result).toEqual({ page: 2, pageSize: 50 });
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('calculateOffset 应正常工作', async () => {
|
|
61
|
+
const { calculateOffset } = await import('@/index');
|
|
62
|
+
|
|
63
|
+
expect(calculateOffset(1, 20)).toBe(0);
|
|
64
|
+
expect(calculateOffset(2, 20)).toBe(20);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file pagination.util.test.ts
|
|
3
|
+
* 分页计算工具函数测试
|
|
4
|
+
* @author Longzai Intelligence
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, it, expect } from 'vitest';
|
|
8
|
+
import {
|
|
9
|
+
normalizePagination,
|
|
10
|
+
normalizeOffsetPagination,
|
|
11
|
+
calculateOffset,
|
|
12
|
+
calculateTotalPages,
|
|
13
|
+
hasNextPage,
|
|
14
|
+
hasPreviousPage,
|
|
15
|
+
offsetToPage,
|
|
16
|
+
pageToOffset,
|
|
17
|
+
createPaginatedResult,
|
|
18
|
+
} from '@/utils/pagination.util';
|
|
19
|
+
import { PAGINATION_DEFAULTS } from '@/types';
|
|
20
|
+
|
|
21
|
+
describe('normalizePagination', () => {
|
|
22
|
+
it('应返回默认值当参数为空时', () => {
|
|
23
|
+
const result = normalizePagination();
|
|
24
|
+
expect(result).toEqual({
|
|
25
|
+
page: PAGINATION_DEFAULTS.page,
|
|
26
|
+
pageSize: PAGINATION_DEFAULTS.pageSize,
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('应返回默认值当参数为 undefined 时', () => {
|
|
31
|
+
const result = normalizePagination(undefined);
|
|
32
|
+
expect(result).toEqual({
|
|
33
|
+
page: PAGINATION_DEFAULTS.page,
|
|
34
|
+
pageSize: PAGINATION_DEFAULTS.pageSize,
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('应正确规范化有效的分页参数', () => {
|
|
39
|
+
const result = normalizePagination({ page: 2, pageSize: 50 });
|
|
40
|
+
expect(result).toEqual({ page: 2, pageSize: 50 });
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('应限制 pageSize 不超过最大值', () => {
|
|
44
|
+
const result = normalizePagination({ pageSize: 200 });
|
|
45
|
+
expect(result.pageSize).toBe(PAGINATION_DEFAULTS.maxPageSize);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('应限制 pageSize 不小于最小值', () => {
|
|
49
|
+
const result = normalizePagination({ pageSize: 0 });
|
|
50
|
+
expect(result.pageSize).toBe(PAGINATION_DEFAULTS.minPageSize);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('应限制 page 不小于 1', () => {
|
|
54
|
+
const result = normalizePagination({ page: -1 });
|
|
55
|
+
expect(result.page).toBe(1);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('应正确处理小数值', () => {
|
|
59
|
+
const result = normalizePagination({ page: 1.5, pageSize: 10.9 });
|
|
60
|
+
expect(result).toEqual({ page: 1, pageSize: 10 });
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('应正确处理部分参数', () => {
|
|
64
|
+
const result = normalizePagination({ page: 5 });
|
|
65
|
+
expect(result).toEqual({ page: 5, pageSize: PAGINATION_DEFAULTS.pageSize });
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe('normalizeOffsetPagination', () => {
|
|
70
|
+
it('应返回默认值当参数为空时', () => {
|
|
71
|
+
const result = normalizeOffsetPagination();
|
|
72
|
+
expect(result).toEqual({
|
|
73
|
+
limit: PAGINATION_DEFAULTS.pageSize,
|
|
74
|
+
offset: 0,
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('应正确规范化有效的 offset 分页参数', () => {
|
|
79
|
+
const result = normalizeOffsetPagination({ limit: 50, offset: 100 });
|
|
80
|
+
expect(result).toEqual({ limit: 50, offset: 100 });
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('应限制 limit 不超过最大值', () => {
|
|
84
|
+
const result = normalizeOffsetPagination({ limit: 200 });
|
|
85
|
+
expect(result.limit).toBe(PAGINATION_DEFAULTS.maxPageSize);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('应限制 limit 不小于最小值', () => {
|
|
89
|
+
const result = normalizeOffsetPagination({ limit: 0 });
|
|
90
|
+
expect(result.limit).toBe(PAGINATION_DEFAULTS.minPageSize);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('应限制 offset 不小于 0', () => {
|
|
94
|
+
const result = normalizeOffsetPagination({ offset: -10 });
|
|
95
|
+
expect(result.offset).toBe(0);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('应正确处理小数值', () => {
|
|
99
|
+
const result = normalizeOffsetPagination({ limit: 10.9, offset: 50.5 });
|
|
100
|
+
expect(result).toEqual({ limit: 10, offset: 50 });
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe('calculateOffset', () => {
|
|
105
|
+
it('应正确计算第一页的偏移量', () => {
|
|
106
|
+
expect(calculateOffset(1, 20)).toBe(0);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('应正确计算第二页的偏移量', () => {
|
|
110
|
+
expect(calculateOffset(2, 20)).toBe(20);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('应正确计算任意页的偏移量', () => {
|
|
114
|
+
expect(calculateOffset(5, 10)).toBe(40);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('应正确处理不同的页面大小', () => {
|
|
118
|
+
expect(calculateOffset(3, 50)).toBe(100);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
describe('calculateTotalPages', () => {
|
|
123
|
+
it('应正确计算总页数', () => {
|
|
124
|
+
expect(calculateTotalPages(100, 20)).toBe(5);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('应正确处理有余数的情况', () => {
|
|
128
|
+
expect(calculateTotalPages(95, 20)).toBe(5);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('应正确处理总数为 0 的情况', () => {
|
|
132
|
+
expect(calculateTotalPages(0, 20)).toBe(0);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('应正确处理总数小于页面大小的情况', () => {
|
|
136
|
+
expect(calculateTotalPages(15, 20)).toBe(1);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('应正确处理 pageSize 为 0 的情况', () => {
|
|
140
|
+
expect(calculateTotalPages(100, 0)).toBe(0);
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
describe('hasNextPage', () => {
|
|
145
|
+
it('应返回 true 当有下一页时', () => {
|
|
146
|
+
expect(hasNextPage(1, 5)).toBe(true);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('应返回 false 当在最后一页时', () => {
|
|
150
|
+
expect(hasNextPage(5, 5)).toBe(false);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('应返回 false 当总页数为 0 时', () => {
|
|
154
|
+
expect(hasNextPage(1, 0)).toBe(false);
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
describe('hasPreviousPage', () => {
|
|
159
|
+
it('应返回 false 当在第一页时', () => {
|
|
160
|
+
expect(hasPreviousPage(1)).toBe(false);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('应返回 true 当不在第一页时', () => {
|
|
164
|
+
expect(hasPreviousPage(2)).toBe(true);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('应返回 true 当在大页码时', () => {
|
|
168
|
+
expect(hasPreviousPage(100)).toBe(true);
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
describe('offsetToPage', () => {
|
|
173
|
+
it('应正确转换 offset 0 到第一页', () => {
|
|
174
|
+
expect(offsetToPage(0, 20)).toEqual({ page: 1, pageSize: 20 });
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('应正确转换 offset 到对应页', () => {
|
|
178
|
+
expect(offsetToPage(40, 20)).toEqual({ page: 3, pageSize: 20 });
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('应正确处理非整除情况', () => {
|
|
182
|
+
expect(offsetToPage(25, 20)).toEqual({ page: 2, pageSize: 20 });
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('应正确处理 limit 为 0 的情况', () => {
|
|
186
|
+
expect(offsetToPage(0, 0)).toEqual({ page: 1, pageSize: 1 });
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
describe('pageToOffset', () => {
|
|
191
|
+
it('应正确转换第一页到 offset 0', () => {
|
|
192
|
+
expect(pageToOffset(1, 20)).toEqual({ offset: 0, limit: 20 });
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('应正确转换页码到 offset', () => {
|
|
196
|
+
expect(pageToOffset(3, 20)).toEqual({ offset: 40, limit: 20 });
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('应正确处理 page 为 0 的情况', () => {
|
|
200
|
+
expect(pageToOffset(0, 20)).toEqual({ offset: 0, limit: 20 });
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('应正确处理 pageSize 为 0 的情况', () => {
|
|
204
|
+
expect(pageToOffset(1, 0)).toEqual({ offset: 0, limit: 1 });
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
describe('createPaginatedResult', () => {
|
|
209
|
+
it('应正确创建分页结果', () => {
|
|
210
|
+
const items = [1, 2, 3];
|
|
211
|
+
const result = createPaginatedResult(items, 100, 2, 20);
|
|
212
|
+
|
|
213
|
+
expect(result).toEqual({
|
|
214
|
+
items,
|
|
215
|
+
total: 100,
|
|
216
|
+
page: 2,
|
|
217
|
+
pageSize: 20,
|
|
218
|
+
totalPages: 5,
|
|
219
|
+
hasNextPage: true,
|
|
220
|
+
hasPreviousPage: true,
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('应正确处理第一页', () => {
|
|
225
|
+
const items = [1, 2, 3];
|
|
226
|
+
const result = createPaginatedResult(items, 100, 1, 20);
|
|
227
|
+
|
|
228
|
+
expect(result.hasPreviousPage).toBe(false);
|
|
229
|
+
expect(result.hasNextPage).toBe(true);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it('应正确处理最后一页', () => {
|
|
233
|
+
const items = [1, 2, 3];
|
|
234
|
+
const result = createPaginatedResult(items, 100, 5, 20);
|
|
235
|
+
|
|
236
|
+
expect(result.hasPreviousPage).toBe(true);
|
|
237
|
+
expect(result.hasNextPage).toBe(false);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('应正确处理空结果', () => {
|
|
241
|
+
const result = createPaginatedResult([], 0, 1, 20);
|
|
242
|
+
|
|
243
|
+
expect(result).toEqual({
|
|
244
|
+
items: [],
|
|
245
|
+
total: 0,
|
|
246
|
+
page: 1,
|
|
247
|
+
pageSize: 20,
|
|
248
|
+
totalPages: 0,
|
|
249
|
+
hasNextPage: false,
|
|
250
|
+
hasPreviousPage: false,
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
});
|