mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-13 21:05:19 +00:00
fix(server): pagination input parser (#10245)
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
# Snapshot report for `src/base/graphql/__tests__/pagination.spec.ts`
|
||||
|
||||
The actual snapshot is saved in `pagination.spec.ts.snap`.
|
||||
|
||||
Generated by [AVA](https://avajs.dev).
|
||||
|
||||
## should return encode pageInfo
|
||||
|
||||
> Snapshot 1
|
||||
|
||||
{
|
||||
edges: [
|
||||
{
|
||||
cursor: 'MTE=',
|
||||
node: {
|
||||
id: 11,
|
||||
},
|
||||
},
|
||||
{
|
||||
cursor: 'MTI=',
|
||||
node: {
|
||||
id: 12,
|
||||
},
|
||||
},
|
||||
{
|
||||
cursor: 'MTM=',
|
||||
node: {
|
||||
id: 13,
|
||||
},
|
||||
},
|
||||
{
|
||||
cursor: 'MTQ=',
|
||||
node: {
|
||||
id: 14,
|
||||
},
|
||||
},
|
||||
{
|
||||
cursor: 'MTU=',
|
||||
node: {
|
||||
id: 15,
|
||||
},
|
||||
},
|
||||
{
|
||||
cursor: 'MTY=',
|
||||
node: {
|
||||
id: 16,
|
||||
},
|
||||
},
|
||||
{
|
||||
cursor: 'MTc=',
|
||||
node: {
|
||||
id: 17,
|
||||
},
|
||||
},
|
||||
{
|
||||
cursor: 'MTg=',
|
||||
node: {
|
||||
id: 18,
|
||||
},
|
||||
},
|
||||
{
|
||||
cursor: 'MTk=',
|
||||
node: {
|
||||
id: 19,
|
||||
},
|
||||
},
|
||||
{
|
||||
cursor: 'MjA=',
|
||||
node: {
|
||||
id: 20,
|
||||
},
|
||||
},
|
||||
],
|
||||
pageInfo: {
|
||||
endCursor: 'MjA=',
|
||||
hasNextPage: true,
|
||||
hasPreviousPage: true,
|
||||
startCursor: 'MTE=',
|
||||
},
|
||||
totalCount: 105,
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,106 @@
|
||||
import { Args, Field, ObjectType, Query, Resolver } from '@nestjs/graphql';
|
||||
import test from 'ava';
|
||||
import Sinon from 'sinon';
|
||||
|
||||
import { createTestingApp } from '../../../__tests__/utils';
|
||||
import { Public } from '../../../core/auth';
|
||||
import { paginate, Paginated, PaginationInput } from '../pagination';
|
||||
|
||||
const TOTAL_COUNT = 105;
|
||||
const ITEMS = Array.from({ length: TOTAL_COUNT }, (_, i) => ({ id: i + 1 }));
|
||||
const paginationStub = Sinon.stub().callsFake(input => {
|
||||
const start = input.offset + (input.after ? parseInt(input.after) : 0);
|
||||
return paginate(
|
||||
ITEMS.slice(start, start + input.first),
|
||||
'id',
|
||||
input,
|
||||
TOTAL_COUNT
|
||||
);
|
||||
});
|
||||
|
||||
const query = `query pagination($input: PaginationInput) {
|
||||
pagination(paginationInput: $input) {
|
||||
totalCount
|
||||
edges {
|
||||
cursor
|
||||
node {
|
||||
id
|
||||
}
|
||||
}
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
hasPreviousPage
|
||||
startCursor
|
||||
endCursor
|
||||
}
|
||||
}
|
||||
}`;
|
||||
|
||||
@ObjectType()
|
||||
class TestType {
|
||||
@Field()
|
||||
id!: number;
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
class PaginatedTestType extends Paginated(TestType) {}
|
||||
|
||||
@Public()
|
||||
@Resolver(() => TestType)
|
||||
class TestResolver {
|
||||
@Query(() => PaginatedTestType)
|
||||
async pagination(
|
||||
@Args(
|
||||
'paginationInput',
|
||||
{
|
||||
type: () => PaginationInput,
|
||||
defaultValue: { first: 10, offset: 0 },
|
||||
},
|
||||
PaginationInput.decode
|
||||
)
|
||||
input: PaginationInput
|
||||
) {
|
||||
return paginationStub(input);
|
||||
}
|
||||
}
|
||||
|
||||
const app = await createTestingApp({
|
||||
providers: [TestResolver],
|
||||
});
|
||||
|
||||
test.after.always(async () => {
|
||||
await app.close();
|
||||
});
|
||||
|
||||
test('should decode pagination input', async t => {
|
||||
await app.gql(query, {
|
||||
input: {
|
||||
first: 5,
|
||||
offset: 1,
|
||||
after: Buffer.from('4').toString('base64'),
|
||||
},
|
||||
});
|
||||
|
||||
t.true(
|
||||
paginationStub.calledOnceWithExactly({
|
||||
first: 5,
|
||||
offset: 1,
|
||||
after: '4',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test('should return encode pageInfo', async t => {
|
||||
const result = paginate(
|
||||
ITEMS.slice(10, 20),
|
||||
'id',
|
||||
{
|
||||
first: 10,
|
||||
offset: 0,
|
||||
after: '9',
|
||||
},
|
||||
TOTAL_COUNT
|
||||
);
|
||||
|
||||
t.snapshot(result);
|
||||
});
|
||||
@@ -1,24 +1,26 @@
|
||||
import { Type } from '@nestjs/common';
|
||||
import {
|
||||
Field,
|
||||
FieldMiddleware,
|
||||
InputType,
|
||||
Int,
|
||||
MiddlewareContext,
|
||||
NextFn,
|
||||
ObjectType,
|
||||
} from '@nestjs/graphql';
|
||||
|
||||
const parseCursorMiddleware: FieldMiddleware = async (
|
||||
_ctx: MiddlewareContext,
|
||||
next: NextFn
|
||||
) => {
|
||||
const value = await next();
|
||||
return value === undefined || value === null ? null : decode(value);
|
||||
};
|
||||
import { PipeTransform, Type } from '@nestjs/common';
|
||||
import { Field, InputType, Int, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
@InputType()
|
||||
export class PaginationInput {
|
||||
/**
|
||||
* Because there is no resolver for GraphQL's InputTypes, we can't automatically decode the cursor input from base64 values.
|
||||
* Use this helper as `PipeTransform` to transform input args
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* paginate(@Args('input', PaginationInput.decode) PaginationInput) {}
|
||||
*/
|
||||
static decode: PipeTransform<PaginationInput, PaginationInput> = {
|
||||
transform: value => {
|
||||
return {
|
||||
...value,
|
||||
after: value.after ? decode(value.after) : null,
|
||||
// before: value.before ? decode(value.before) : null,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
@Field(() => Int, {
|
||||
nullable: true,
|
||||
description: 'returns the first n elements from the list.',
|
||||
@@ -37,7 +39,6 @@ export class PaginationInput {
|
||||
nullable: true,
|
||||
description:
|
||||
'returns the elements in the list that come after the specified cursor.',
|
||||
middleware: [parseCursorMiddleware],
|
||||
})
|
||||
after?: string | null;
|
||||
|
||||
@@ -46,7 +47,6 @@ export class PaginationInput {
|
||||
// nullable: true,
|
||||
// description:
|
||||
// 'returns the elements in the list that come before the specified cursor.',
|
||||
// middleware: [parseCursorMiddleware],
|
||||
// })
|
||||
// before?: string | null;
|
||||
}
|
||||
@@ -71,7 +71,7 @@ export function paginate<T>(
|
||||
edges,
|
||||
pageInfo: {
|
||||
hasNextPage: edges.length >= paginationInput.first,
|
||||
hasPreviousPage: paginationInput.offset > 0,
|
||||
hasPreviousPage: !!paginationInput.after || paginationInput.offset > 0,
|
||||
endCursor: edges.length ? edges[edges.length - 1].cursor : null,
|
||||
startCursor: edges.length ? edges[0].cursor : null,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user