Some checks failed
ci / changes (push) Has been cancelled
ci / lint (push) Has been cancelled
ci / build (push) Has been cancelled
ci / tests-unit (push) Has been cancelled
ci / tests-types (push) Has been cancelled
ci / int-cosmosdb (push) Has been cancelled
ci / int-documentdb (push) Has been cancelled
ci / int-firestore (push) Has been cancelled
ci / int-mongodb (push) Has been cancelled
ci / int-postgres (push) Has been cancelled
ci / int-postgres-custom-schema (push) Has been cancelled
ci / int-postgres-uuid (push) Has been cancelled
ci / int-sqlite (push) Has been cancelled
ci / int-sqlite-uuid (push) Has been cancelled
ci / int-supabase (push) Has been cancelled
ci / e2e-_community (push) Has been cancelled
ci / e2e-access-control (push) Has been cancelled
ci / e2e-admin-bar (push) Has been cancelled
ci / e2e-admin-root (push) Has been cancelled
ci / e2e-admin__e2e__document-view (push) Has been cancelled
ci / e2e-admin__e2e__general (push) Has been cancelled
ci / e2e-admin__e2e__list-view (push) Has been cancelled
ci / e2e-auth (push) Has been cancelled
ci / e2e-auth-basic (push) Has been cancelled
ci / e2e-bulk-edit (push) Has been cancelled
ci / e2e-field-error-states (push) Has been cancelled
ci / e2e-fields-relationship (push) Has been cancelled
ci / e2e-fields__collections__Array (push) Has been cancelled
ci / e2e-fields__collections__Blocks#config.blockreferences.ts (push) Has been cancelled
ci / e2e-fields__collections__Blocks (push) Has been cancelled
ci / e2e-fields__collections__Checkbox (push) Has been cancelled
ci / e2e-fields__collections__Collapsible (push) Has been cancelled
ci / e2e-fields__collections__ConditionalLogic (push) Has been cancelled
ci / e2e-fields__collections__CustomID (push) Has been cancelled
ci / e2e-fields__collections__Date (push) Has been cancelled
ci / e2e-fields__collections__Email (push) Has been cancelled
ci / e2e-fields__collections__Indexed (push) Has been cancelled
ci / e2e-fields__collections__JSON (push) Has been cancelled
ci / e2e-fields__collections__Number (push) Has been cancelled
ci / e2e-fields__collections__Point (push) Has been cancelled
ci / e2e-fields__collections__Radio (push) Has been cancelled
ci / e2e-fields__collections__Relationship (push) Has been cancelled
ci / e2e-fields__collections__Row (push) Has been cancelled
ci / e2e-fields__collections__Select (push) Has been cancelled
ci / e2e-fields__collections__Tabs (push) Has been cancelled
ci / e2e-fields__collections__Tabs2 (push) Has been cancelled
ci / e2e-fields__collections__Text (push) Has been cancelled
ci / e2e-fields__collections__UI (push) Has been cancelled
ci / e2e-fields__collections__Upload (push) Has been cancelled
ci / e2e-folders (push) Has been cancelled
ci / e2e-form-state (push) Has been cancelled
ci / e2e-group-by (push) Has been cancelled
ci / e2e-hooks (push) Has been cancelled
ci / e2e-i18n (push) Has been cancelled
ci / e2e-joins (push) Has been cancelled
ci / e2e-lexical__collections__LexicalHeadingFeature (push) Has been cancelled
ci / e2e-lexical__collections__LexicalJSXConverter (push) Has been cancelled
ci / e2e-lexical__collections__LexicalLinkFeature (push) Has been cancelled
ci / e2e-lexical__collections__Lexical__e2e__blocks#config.blockreferences.ts (push) Has been cancelled
ci / e2e-lexical__collections__Lexical__e2e__blocks (push) Has been cancelled
ci / e2e-lexical__collections__Lexical__e2e__main (push) Has been cancelled
ci / e2e-lexical__collections__OnDemandForm (push) Has been cancelled
ci / e2e-lexical__collections__RichText (push) Has been cancelled
ci / e2e-lexical__collections___LexicalFullyFeatured (push) Has been cancelled
ci / e2e-lexical__collections___LexicalFullyFeatured__db (push) Has been cancelled
ci / e2e-live-preview (push) Has been cancelled
ci / e2e-localization (push) Has been cancelled
ci / e2e-locked-documents (push) Has been cancelled
ci / e2e-plugin-cloud-storage (push) Has been cancelled
ci / e2e-plugin-form-builder (push) Has been cancelled
ci / e2e-plugin-import-export (push) Has been cancelled
ci / e2e-plugin-multi-tenant (push) Has been cancelled
ci / e2e-plugin-nested-docs (push) Has been cancelled
ci / e2e-plugin-seo (push) Has been cancelled
ci / e2e-query-presets (push) Has been cancelled
ci / e2e-sort (push) Has been cancelled
ci / e2e-trash (push) Has been cancelled
ci / e2e-uploads (push) Has been cancelled
ci / e2e-versions (push) Has been cancelled
ci / e2e-turbo-_community (push) Has been cancelled
ci / e2e-turbo-access-control (push) Has been cancelled
ci / e2e-turbo-admin-bar (push) Has been cancelled
ci / e2e-turbo-admin-root (push) Has been cancelled
ci / e2e-turbo-admin__e2e__document-view (push) Has been cancelled
ci / e2e-turbo-admin__e2e__general (push) Has been cancelled
ci / e2e-turbo-admin__e2e__list-view (push) Has been cancelled
ci / e2e-turbo-auth (push) Has been cancelled
ci / e2e-turbo-auth-basic (push) Has been cancelled
ci / e2e-turbo-bulk-edit (push) Has been cancelled
ci / e2e-turbo-field-error-states (push) Has been cancelled
ci / e2e-turbo-fields-relationship (push) Has been cancelled
ci / e2e-turbo-fields__collections__Array (push) Has been cancelled
ci / e2e-turbo-fields__collections__Blocks#config.blockreferences.ts (push) Has been cancelled
ci / e2e-turbo-fields__collections__Blocks (push) Has been cancelled
ci / e2e-turbo-fields__collections__Checkbox (push) Has been cancelled
ci / e2e-turbo-fields__collections__Collapsible (push) Has been cancelled
ci / e2e-turbo-fields__collections__ConditionalLogic (push) Has been cancelled
ci / e2e-turbo-fields__collections__CustomID (push) Has been cancelled
ci / e2e-turbo-fields__collections__Date (push) Has been cancelled
ci / e2e-turbo-fields__collections__Email (push) Has been cancelled
ci / e2e-turbo-fields__collections__Indexed (push) Has been cancelled
ci / e2e-turbo-fields__collections__JSON (push) Has been cancelled
ci / e2e-turbo-fields__collections__Number (push) Has been cancelled
ci / e2e-turbo-fields__collections__Point (push) Has been cancelled
ci / e2e-turbo-fields__collections__Radio (push) Has been cancelled
ci / e2e-turbo-fields__collections__Relationship (push) Has been cancelled
ci / e2e-turbo-fields__collections__Row (push) Has been cancelled
ci / e2e-turbo-fields__collections__Select (push) Has been cancelled
ci / e2e-turbo-fields__collections__Tabs (push) Has been cancelled
ci / e2e-turbo-fields__collections__Tabs2 (push) Has been cancelled
ci / e2e-turbo-fields__collections__Text (push) Has been cancelled
ci / e2e-turbo-fields__collections__UI (push) Has been cancelled
ci / e2e-turbo-fields__collections__Upload (push) Has been cancelled
ci / e2e-turbo-folders (push) Has been cancelled
ci / e2e-turbo-form-state (push) Has been cancelled
ci / e2e-turbo-group-by (push) Has been cancelled
ci / e2e-turbo-hooks (push) Has been cancelled
ci / e2e-turbo-i18n (push) Has been cancelled
ci / e2e-turbo-joins (push) Has been cancelled
ci / e2e-turbo-lexical__collections__LexicalHeadingFeature (push) Has been cancelled
ci / e2e-turbo-lexical__collections__LexicalJSXConverter (push) Has been cancelled
ci / e2e-turbo-lexical__collections__LexicalLinkFeature (push) Has been cancelled
ci / e2e-turbo-lexical__collections__Lexical__e2e__blocks#config.blockreferences.ts (push) Has been cancelled
ci / e2e-turbo-lexical__collections__Lexical__e2e__blocks (push) Has been cancelled
ci / e2e-turbo-lexical__collections__Lexical__e2e__main (push) Has been cancelled
ci / e2e-turbo-lexical__collections__OnDemandForm (push) Has been cancelled
ci / e2e-turbo-lexical__collections__RichText (push) Has been cancelled
ci / e2e-turbo-lexical__collections___LexicalFullyFeatured (push) Has been cancelled
ci / e2e-turbo-lexical__collections___LexicalFullyFeatured__db (push) Has been cancelled
ci / e2e-turbo-live-preview (push) Has been cancelled
ci / e2e-turbo-localization (push) Has been cancelled
ci / e2e-turbo-locked-documents (push) Has been cancelled
ci / e2e-turbo-plugin-cloud-storage (push) Has been cancelled
ci / e2e-turbo-plugin-form-builder (push) Has been cancelled
ci / e2e-turbo-plugin-import-export (push) Has been cancelled
ci / e2e-turbo-plugin-multi-tenant (push) Has been cancelled
ci / e2e-turbo-plugin-nested-docs (push) Has been cancelled
ci / e2e-turbo-plugin-seo (push) Has been cancelled
ci / e2e-turbo-query-presets (push) Has been cancelled
ci / e2e-turbo-sort (push) Has been cancelled
ci / e2e-turbo-trash (push) Has been cancelled
ci / e2e-turbo-uploads (push) Has been cancelled
ci / e2e-turbo-versions (push) Has been cancelled
ci / build-template-blank-mongodb (push) Has been cancelled
ci / build-template-website-mongodb (push) Has been cancelled
ci / build-template-with-payload-cloud-mongodb (push) Has been cancelled
ci / build-template-with-vercel-mongodb-mongodb (push) Has been cancelled
ci / build-template-plugin- (push) Has been cancelled
ci / build-template-with-postgres-postgres (push) Has been cancelled
ci / build-template-with-vercel-postgres-postgres (push) Has been cancelled
ci / tests-type-generation (push) Has been cancelled
ci / All Green (push) Has been cancelled
ci / Publish Canary (push) Has been cancelled
ci / analyze (push) Has been cancelled
publish-prerelease / publish-prerelease-${{ github.ref_name }}-${{ github.sha }} (push) Has been cancelled
lock-issues / lock_issues (push) Has been cancelled
stale / stale (push) Has been cancelled
audit-dependencies / audit (push) Has been cancelled
activity-notifications / run (push) Has been cancelled
925 lines
26 KiB
TypeScript
925 lines
26 KiB
TypeScript
import type { CollectionSlug, Payload } from 'tbsh-cms'
|
|
|
|
import path from 'path'
|
|
import { fileURLToPath } from 'url'
|
|
|
|
import type { NextRESTClient } from '../helpers/NextRESTClient.js'
|
|
import type { Draft, Orderable, OrderableJoin } from './payload-types.js'
|
|
|
|
import { initPayloadInt } from '../helpers/initPayloadInt.js'
|
|
import { draftsSlug } from './collections/Drafts/index.js'
|
|
import { nonUniqueSortSlug } from './collections/NonUniqueSort/index.js'
|
|
import { orderableSlug } from './collections/Orderable/index.js'
|
|
import { orderableJoinSlug } from './collections/OrderableJoin/index.js'
|
|
|
|
let payload: Payload
|
|
let restClient: NextRESTClient
|
|
|
|
const filename = fileURLToPath(import.meta.url)
|
|
const dirname = path.dirname(filename)
|
|
|
|
describe('Sort', () => {
|
|
beforeAll(async () => {
|
|
// @ts-expect-error: initPayloadInt does not have a proper type definition
|
|
;({ payload, restClient } = await initPayloadInt(dirname))
|
|
})
|
|
|
|
afterAll(async () => {
|
|
await payload.destroy()
|
|
})
|
|
|
|
describe('Local API', () => {
|
|
beforeAll(async () => {
|
|
await createData('posts', [
|
|
{ text: 'Post 1', number: 1, number2: 10, group: { number: 100 } },
|
|
{ text: 'Post 2', number: 2, number2: 10, group: { number: 200 } },
|
|
{ text: 'Post 3', number: 3, number2: 5, group: { number: 150 } },
|
|
{ text: 'Post 10', number: 10, number2: 5, group: { number: 200 } },
|
|
{ text: 'Post 11', number: 11, number2: 20, group: { number: 150 } },
|
|
{ text: 'Post 12', number: 12, number2: 20, group: { number: 100 } },
|
|
])
|
|
await createData('default-sort', [
|
|
{ text: 'Post default-5 b', number: 5 },
|
|
{ text: 'Post default-10', number: 10 },
|
|
{ text: 'Post default-5 a', number: 5 },
|
|
{ text: 'Post default-1', number: 1 },
|
|
])
|
|
})
|
|
|
|
afterAll(async () => {
|
|
await payload.delete({ collection: 'posts', where: {} })
|
|
await payload.delete({ collection: 'default-sort', where: {} })
|
|
})
|
|
|
|
describe('Default sort', () => {
|
|
it('should sort posts by default definition in collection', async () => {
|
|
const posts = await payload.find({
|
|
collection: 'default-sort', // 'number,-text'
|
|
})
|
|
|
|
expect(posts.docs.map((post) => post.text)).toEqual([
|
|
'Post default-1',
|
|
'Post default-5 b',
|
|
'Post default-5 a',
|
|
'Post default-10',
|
|
])
|
|
})
|
|
})
|
|
|
|
describe('Single sort field', () => {
|
|
it('should sort posts by text field', async () => {
|
|
const posts = await payload.find({
|
|
collection: 'posts',
|
|
sort: 'text',
|
|
})
|
|
|
|
expect(posts.docs.map((post) => post.text)).toEqual([
|
|
'Post 1',
|
|
'Post 10',
|
|
'Post 11',
|
|
'Post 12',
|
|
'Post 2',
|
|
'Post 3',
|
|
])
|
|
})
|
|
|
|
it('should sort posts by text field desc', async () => {
|
|
const posts = await payload.find({
|
|
collection: 'posts',
|
|
sort: '-text',
|
|
})
|
|
|
|
expect(posts.docs.map((post) => post.text)).toEqual([
|
|
'Post 3',
|
|
'Post 2',
|
|
'Post 12',
|
|
'Post 11',
|
|
'Post 10',
|
|
'Post 1',
|
|
])
|
|
})
|
|
|
|
it('should sort posts by number field', async () => {
|
|
const posts = await payload.find({
|
|
collection: 'posts',
|
|
sort: 'number',
|
|
})
|
|
|
|
expect(posts.docs.map((post) => post.text)).toEqual([
|
|
'Post 1',
|
|
'Post 2',
|
|
'Post 3',
|
|
'Post 10',
|
|
'Post 11',
|
|
'Post 12',
|
|
])
|
|
})
|
|
|
|
it('should sort posts by number field desc', async () => {
|
|
const posts = await payload.find({
|
|
collection: 'posts',
|
|
sort: '-number',
|
|
})
|
|
|
|
expect(posts.docs.map((post) => post.text)).toEqual([
|
|
'Post 12',
|
|
'Post 11',
|
|
'Post 10',
|
|
'Post 3',
|
|
'Post 2',
|
|
'Post 1',
|
|
])
|
|
})
|
|
})
|
|
|
|
describe('Non-unique sorting', () => {
|
|
// There are situations where the sort order is not guaranteed to be consistent, such as when sorting by a non-unique field in MongoDB which does not keep an internal order of items
|
|
// As a result, every time you fetch, including fetching specific pages, the order of items may change and appear as duplicated to some users.
|
|
it('should always be consistent when sorting', async () => {
|
|
const posts = await payload.find({
|
|
collection: nonUniqueSortSlug,
|
|
sort: 'order',
|
|
})
|
|
|
|
const initialMap = posts.docs.map((post) => post.title)
|
|
|
|
const dataFetches = await Promise.all(
|
|
Array.from({ length: 3 }).map(() =>
|
|
payload.find({
|
|
collection: nonUniqueSortSlug,
|
|
sort: 'order',
|
|
}),
|
|
),
|
|
)
|
|
|
|
const [fetch1, fetch2, fetch3] = dataFetches.map((fetch) =>
|
|
fetch.docs.map((post) => post.title),
|
|
)
|
|
|
|
expect(fetch1).toEqual(initialMap)
|
|
|
|
expect(fetch2).toEqual(initialMap)
|
|
|
|
expect(fetch3).toEqual(initialMap)
|
|
})
|
|
|
|
it('should always be consistent when sorting - with limited pages', async () => {
|
|
const posts = await payload.find({
|
|
collection: nonUniqueSortSlug,
|
|
sort: 'order',
|
|
limit: 5,
|
|
page: 2,
|
|
})
|
|
|
|
const initialMap = posts.docs.map((post) => post.title)
|
|
|
|
const dataFetches = await Promise.all(
|
|
Array.from({ length: 3 }).map(() =>
|
|
payload.find({
|
|
collection: nonUniqueSortSlug,
|
|
sort: 'order',
|
|
limit: 5,
|
|
page: 2,
|
|
}),
|
|
),
|
|
)
|
|
|
|
const [fetch1, fetch2, fetch3] = dataFetches.map((fetch) =>
|
|
fetch.docs.map((post) => post.title),
|
|
)
|
|
|
|
expect(fetch1).toEqual(initialMap)
|
|
|
|
expect(fetch2).toEqual(initialMap)
|
|
|
|
expect(fetch3).toEqual(initialMap)
|
|
})
|
|
|
|
it('should sort by createdAt as fallback', async () => {
|
|
// This is the (reverse - newest first) order that the posts are created in so this should remain consistent as the sort should fallback to '-createdAt'
|
|
const postsInOrder = ['Post 9', 'Post 8', 'Post 7', 'Post 6']
|
|
|
|
const dataFetches = await Promise.all(
|
|
Array.from({ length: 3 }).map(() =>
|
|
payload.find({
|
|
collection: nonUniqueSortSlug,
|
|
sort: 'order',
|
|
page: 2,
|
|
limit: 4,
|
|
}),
|
|
),
|
|
)
|
|
|
|
const [fetch1, fetch2, fetch3] = dataFetches.map((fetch) =>
|
|
fetch.docs.map((post) => post.title),
|
|
)
|
|
|
|
console.log({ fetch1, fetch2, fetch3 })
|
|
|
|
expect(fetch1).toEqual(postsInOrder)
|
|
|
|
expect(fetch2).toEqual(postsInOrder)
|
|
|
|
expect(fetch3).toEqual(postsInOrder)
|
|
})
|
|
|
|
it('should always be consistent without sort params in the query', async () => {
|
|
const posts = await payload.find({
|
|
collection: nonUniqueSortSlug,
|
|
})
|
|
|
|
const initialMap = posts.docs.map((post) => post.title)
|
|
|
|
const dataFetches = await Promise.all(
|
|
Array.from({ length: 3 }).map(() =>
|
|
payload.find({
|
|
collection: nonUniqueSortSlug,
|
|
}),
|
|
),
|
|
)
|
|
|
|
const [fetch1, fetch2, fetch3] = dataFetches.map((fetch) =>
|
|
fetch.docs.map((post) => post.title),
|
|
)
|
|
|
|
expect(fetch1).toEqual(initialMap)
|
|
|
|
expect(fetch2).toEqual(initialMap)
|
|
|
|
expect(fetch3).toEqual(initialMap)
|
|
})
|
|
|
|
it('should always be consistent without sort params in the query - with limited pages', async () => {
|
|
const posts = await payload.find({
|
|
collection: nonUniqueSortSlug,
|
|
page: 2,
|
|
limit: 4,
|
|
})
|
|
|
|
const initialMap = posts.docs.map((post) => post.title)
|
|
|
|
const dataFetches = await Promise.all(
|
|
Array.from({ length: 3 }).map(() =>
|
|
payload.find({
|
|
collection: nonUniqueSortSlug,
|
|
page: 2,
|
|
limit: 4,
|
|
}),
|
|
),
|
|
)
|
|
|
|
const [fetch1, fetch2, fetch3] = dataFetches.map((fetch) =>
|
|
fetch.docs.map((post) => post.title),
|
|
)
|
|
|
|
expect(fetch1).toEqual(initialMap)
|
|
|
|
expect(fetch2).toEqual(initialMap)
|
|
|
|
expect(fetch3).toEqual(initialMap)
|
|
})
|
|
})
|
|
|
|
describe('Sort by multiple fields', () => {
|
|
it('should sort posts by multiple fields', async () => {
|
|
const posts = await payload.find({
|
|
collection: 'posts',
|
|
sort: ['number2', 'number'],
|
|
})
|
|
|
|
expect(posts.docs.map((post) => post.text)).toEqual([
|
|
'Post 3', // 5, 3
|
|
'Post 10', // 5, 10
|
|
'Post 1', // 10, 1
|
|
'Post 2', // 10, 2
|
|
'Post 11', // 20, 11
|
|
'Post 12', // 20, 12
|
|
])
|
|
})
|
|
|
|
it('should sort posts by multiple fields asc and desc', async () => {
|
|
const posts = await payload.find({
|
|
collection: 'posts',
|
|
sort: ['number2', '-number'],
|
|
})
|
|
|
|
expect(posts.docs.map((post) => post.text)).toEqual([
|
|
'Post 10', // 5, 10
|
|
'Post 3', // 5, 3
|
|
'Post 2', // 10, 2
|
|
'Post 1', // 10, 1
|
|
'Post 12', // 20, 12
|
|
'Post 11', // 20, 11
|
|
])
|
|
})
|
|
|
|
it('should sort posts by multiple fields with group', async () => {
|
|
const posts = await payload.find({
|
|
collection: 'posts',
|
|
sort: ['-group.number', '-number'],
|
|
})
|
|
|
|
expect(posts.docs.map((post) => post.text)).toEqual([
|
|
'Post 10', // 200, 10
|
|
'Post 2', // 200, 2
|
|
'Post 11', // 150, 11
|
|
'Post 3', // 150, 3
|
|
'Post 12', // 100, 12
|
|
'Post 1', // 100, 1
|
|
])
|
|
})
|
|
})
|
|
|
|
describe('Sort with drafts', () => {
|
|
beforeAll(async () => {
|
|
const testData1 = await payload.create({
|
|
collection: 'drafts',
|
|
data: { text: 'Post 1 draft', number: 10 },
|
|
draft: true,
|
|
})
|
|
await payload.update({
|
|
collection: 'drafts',
|
|
id: testData1.id,
|
|
data: { text: 'Post 1 draft updated', number: 20 },
|
|
draft: true,
|
|
})
|
|
await payload.update({
|
|
collection: 'drafts',
|
|
id: testData1.id,
|
|
data: { text: 'Post 1 draft updated', number: 30 },
|
|
draft: true,
|
|
})
|
|
await payload.update({
|
|
collection: 'drafts',
|
|
id: testData1.id,
|
|
data: { text: 'Post 1 published', number: 15 },
|
|
draft: false,
|
|
})
|
|
const testData2 = await payload.create({
|
|
collection: 'drafts',
|
|
data: { text: 'Post 2 draft', number: 1 },
|
|
draft: true,
|
|
})
|
|
await payload.update({
|
|
collection: 'drafts',
|
|
id: testData2.id,
|
|
data: { text: 'Post 2 published', number: 2 },
|
|
draft: false,
|
|
})
|
|
await payload.update({
|
|
collection: 'drafts',
|
|
id: testData2.id,
|
|
data: { text: 'Post 2 newdraft', number: 100 },
|
|
draft: true,
|
|
})
|
|
await payload.create({
|
|
collection: 'drafts',
|
|
data: { text: 'Post 3 draft', number: 3 },
|
|
draft: true,
|
|
})
|
|
})
|
|
|
|
it('should sort latest without draft', async () => {
|
|
const posts = await payload.find({
|
|
collection: 'drafts',
|
|
sort: 'number',
|
|
draft: false,
|
|
})
|
|
|
|
expect(posts.docs.map((post) => post.text)).toEqual([
|
|
'Post 2 published', // 2
|
|
'Post 3 draft', // 3
|
|
'Post 1 published', // 15
|
|
])
|
|
})
|
|
|
|
it('should sort latest with draft', async () => {
|
|
const posts = await payload.find({
|
|
collection: 'drafts',
|
|
sort: 'number',
|
|
draft: true,
|
|
})
|
|
|
|
expect(posts.docs.map((post) => post.text)).toEqual([
|
|
'Post 3 draft', // 3
|
|
'Post 1 published', // 15
|
|
'Post 2 newdraft', // 100
|
|
])
|
|
})
|
|
|
|
it('should sort versions', async () => {
|
|
const posts = await payload.findVersions({
|
|
collection: 'drafts',
|
|
sort: 'version.number',
|
|
draft: false,
|
|
})
|
|
|
|
expect(posts.docs.map((post) => post.version.text)).toEqual([
|
|
'Post 2 draft', // 1
|
|
'Post 2 published', // 2
|
|
'Post 3 draft', // 3
|
|
'Post 1 draft', // 10
|
|
'Post 1 published', // 15
|
|
'Post 1 draft updated', // 20
|
|
'Post 1 draft updated', // 30
|
|
'Post 2 newdraft', // 100
|
|
])
|
|
})
|
|
})
|
|
|
|
describe('Localized sort', () => {
|
|
beforeAll(async () => {
|
|
const testData1 = await payload.create({
|
|
collection: 'localized',
|
|
data: { text: 'Post 1 english', number: 10 },
|
|
locale: 'en',
|
|
})
|
|
await payload.update({
|
|
collection: 'localized',
|
|
id: testData1.id,
|
|
data: { text: 'Post 1 norsk', number: 20 },
|
|
locale: 'nb',
|
|
})
|
|
const testData2 = await payload.create({
|
|
collection: 'localized',
|
|
data: { text: 'Post 2 english', number: 25 },
|
|
locale: 'en',
|
|
})
|
|
await payload.update({
|
|
collection: 'localized',
|
|
id: testData2.id,
|
|
data: { text: 'Post 2 norsk', number: 5 },
|
|
locale: 'nb',
|
|
})
|
|
})
|
|
|
|
it('should sort localized field', async () => {
|
|
const englishPosts = await payload.find({
|
|
collection: 'localized',
|
|
sort: 'number',
|
|
locale: 'en',
|
|
})
|
|
|
|
expect(englishPosts.docs.map((post) => post.text)).toEqual([
|
|
'Post 1 english', // 10
|
|
'Post 2 english', // 20
|
|
])
|
|
|
|
const norwegianPosts = await payload.find({
|
|
collection: 'localized',
|
|
sort: 'number',
|
|
locale: 'nb',
|
|
})
|
|
|
|
expect(norwegianPosts.docs.map((post) => post.text)).toEqual([
|
|
'Post 2 norsk', // 5
|
|
'Post 1 norsk', // 25
|
|
])
|
|
})
|
|
})
|
|
|
|
describe('Orderable', () => {
|
|
let orderable1: Orderable
|
|
let orderable2: Orderable
|
|
let orderableDraft1: Draft
|
|
let orderableDraft2: Draft
|
|
beforeAll(async () => {
|
|
orderable1 = await payload.create({
|
|
collection: orderableSlug,
|
|
data: {
|
|
title: 'Orderable 1',
|
|
},
|
|
})
|
|
orderable2 = await payload.create({
|
|
collection: orderableSlug,
|
|
data: {
|
|
title: 'Orderable 2',
|
|
},
|
|
})
|
|
orderableDraft1 = await payload.create({
|
|
collection: draftsSlug,
|
|
data: {
|
|
text: 'Orderable 1',
|
|
_status: 'draft',
|
|
},
|
|
})
|
|
orderableDraft2 = await payload.create({
|
|
collection: draftsSlug,
|
|
data: {
|
|
text: 'Orderable 2',
|
|
_status: 'draft',
|
|
},
|
|
})
|
|
})
|
|
|
|
it('should set order by default', async () => {
|
|
const ordered = await payload.find({
|
|
collection: orderableSlug,
|
|
where: {
|
|
title: {
|
|
contains: 'Orderable ',
|
|
},
|
|
},
|
|
})
|
|
|
|
expect(orderable1._order).toBeDefined()
|
|
expect(orderable2._order).toBeDefined()
|
|
expect(parseInt(orderable1._order, 16)).toBeLessThan(parseInt(orderable2._order, 16))
|
|
expect(ordered.docs[0].id).toStrictEqual(orderable1.id)
|
|
expect(ordered.docs[1].id).toStrictEqual(orderable2.id)
|
|
})
|
|
|
|
it('should allow reordering with REST API', async () => {
|
|
const res = await restClient.POST('/reorder', {
|
|
body: JSON.stringify({
|
|
collectionSlug: orderableSlug,
|
|
docsToMove: [orderable1.id],
|
|
newKeyWillBe: 'greater',
|
|
orderableFieldName: '_order',
|
|
target: {
|
|
id: orderable2.id,
|
|
key: orderable2._order,
|
|
},
|
|
}),
|
|
})
|
|
|
|
expect(res.status).toStrictEqual(200)
|
|
|
|
const ordered = await payload.find({
|
|
collection: 'orderable',
|
|
where: {
|
|
title: {
|
|
contains: 'Orderable ',
|
|
},
|
|
},
|
|
})
|
|
|
|
expect(parseInt(ordered.docs[0]._order, 16)).toBeLessThan(
|
|
parseInt(ordered.docs[1]._order, 16),
|
|
)
|
|
})
|
|
|
|
it('should allow reordering with REST API with drafts enabled', async () => {
|
|
const res = await restClient.POST('/reorder', {
|
|
body: JSON.stringify({
|
|
collectionSlug: draftsSlug,
|
|
docsToMove: [orderableDraft1.id],
|
|
newKeyWillBe: 'greater',
|
|
orderableFieldName: '_order',
|
|
target: {
|
|
id: orderableDraft2.id,
|
|
key: orderableDraft2._order,
|
|
},
|
|
}),
|
|
})
|
|
|
|
expect(res.status).toStrictEqual(200)
|
|
|
|
const ordered = await payload.find({
|
|
collection: draftsSlug,
|
|
draft: true,
|
|
where: {
|
|
text: {
|
|
contains: 'Orderable ',
|
|
},
|
|
},
|
|
})
|
|
|
|
expect(ordered.docs).toHaveLength(2)
|
|
|
|
expect(parseInt(ordered.docs[0]._order, 16)).toBeLessThan(
|
|
parseInt(ordered.docs[1]._order, 16),
|
|
)
|
|
})
|
|
|
|
it('should allow to duplicate with reordable', async () => {
|
|
const doc = await payload.create({
|
|
collection: 'orderable',
|
|
data: { title: 'new document' },
|
|
})
|
|
|
|
const docDuplicated = await payload.create({
|
|
duplicateFromID: doc.id,
|
|
collection: 'orderable',
|
|
data: {},
|
|
})
|
|
expect(docDuplicated.title).toBe('new document')
|
|
expect(parseInt(doc._order!, 16)).toBeLessThan(parseInt(docDuplicated._order!, 16))
|
|
|
|
await restClient.POST('/reorder', {
|
|
body: JSON.stringify({
|
|
collectionSlug: orderableSlug,
|
|
docsToMove: [doc.id],
|
|
newKeyWillBe: 'greater',
|
|
orderableFieldName: '_order',
|
|
target: {
|
|
id: docDuplicated.id,
|
|
key: docDuplicated._order,
|
|
},
|
|
}),
|
|
})
|
|
|
|
const docAfterReorder = await payload.findByID({ collection: 'orderable', id: doc.id })
|
|
const docDuplicatedAfterReorder = await payload.findByID({
|
|
collection: 'orderable',
|
|
id: docDuplicated.id,
|
|
})
|
|
expect(parseInt(docAfterReorder._order!, 16)).toBeGreaterThan(
|
|
parseInt(docDuplicatedAfterReorder._order!, 16),
|
|
)
|
|
})
|
|
|
|
it('should not break with existing base 62 digits', async () => {
|
|
const collection = orderableSlug
|
|
// create seed docs with aa, aA, a0 (legacy base64 chars for backward compatibility)
|
|
const aa = await payload.create({
|
|
collection,
|
|
data: {
|
|
title: 'Base62 aa',
|
|
_order: 'aa',
|
|
},
|
|
})
|
|
const aA = await payload.create({
|
|
collection,
|
|
data: {
|
|
title: 'Base62 aA',
|
|
_order: 'aA',
|
|
},
|
|
})
|
|
const a0 = await payload.create({
|
|
collection,
|
|
data: {
|
|
title: 'Base62 a0',
|
|
_order: 'a0',
|
|
},
|
|
})
|
|
|
|
const orderableDoc = await payload.create({
|
|
collection,
|
|
data: {
|
|
title: 'Base62 new',
|
|
},
|
|
})
|
|
|
|
const res = await restClient.POST('/reorder', {
|
|
body: JSON.stringify({
|
|
collectionSlug: orderableSlug,
|
|
docsToMove: [orderableDoc.id],
|
|
newKeyWillBe: 'greater',
|
|
orderableFieldName: '_order',
|
|
target: {
|
|
id: aA.id,
|
|
key: aA._order,
|
|
},
|
|
}),
|
|
})
|
|
|
|
expect(res.status).toStrictEqual(200)
|
|
|
|
const { docs } = await payload.find({
|
|
collection,
|
|
sort: '-_order',
|
|
where: {
|
|
title: {
|
|
contains: 'Base62 ',
|
|
},
|
|
},
|
|
})
|
|
|
|
expect(docs).toHaveLength(4)
|
|
const ids = [aa.id, aA.id, a0.id, orderableDoc.id]
|
|
expect(docs.every(({ id }) => ids.includes(id))).toBe(true)
|
|
|
|
const a0Index = docs.findIndex((d) => d.id === a0.id)
|
|
const aAIndex = docs.findIndex((d) => d.id === aA.id)
|
|
const orderableIndex = docs.findIndex((d) => d.id === orderableDoc.id)
|
|
|
|
// The reordered document should be positioned after the target (aA) since newKeyWillBe: 'greater'
|
|
// and before the target (a0) since (a0) is the smallest fractional index
|
|
expect(orderableIndex).toBeGreaterThan(aAIndex)
|
|
expect(orderableIndex).toBeLessThan(a0Index)
|
|
})
|
|
})
|
|
|
|
describe('Orderable join', () => {
|
|
let related: OrderableJoin
|
|
let orderable1: Orderable
|
|
let orderable2: Orderable
|
|
let orderable3: Orderable
|
|
|
|
beforeAll(async () => {
|
|
related = await payload.create({
|
|
collection: orderableJoinSlug,
|
|
data: {
|
|
title: 'test',
|
|
},
|
|
})
|
|
orderable1 = await payload.create({
|
|
collection: orderableSlug,
|
|
data: {
|
|
title: 'test 1',
|
|
orderableField: related.id,
|
|
},
|
|
})
|
|
|
|
orderable2 = await payload.create({
|
|
collection: orderableSlug,
|
|
data: {
|
|
title: 'test 2',
|
|
orderableField: related.id,
|
|
},
|
|
})
|
|
|
|
orderable3 = await payload.create({
|
|
collection: orderableSlug,
|
|
data: {
|
|
title: 'test 3',
|
|
orderableField: related.id,
|
|
},
|
|
})
|
|
})
|
|
|
|
it('should set order by default', () => {
|
|
expect(orderable1._orderable_orderableJoinField1_order).toBeDefined()
|
|
})
|
|
|
|
it('should allow setting the order with the local API', async () => {
|
|
// create two orderableJoinSlug docs
|
|
orderable2 = await payload.update({
|
|
collection: orderableSlug,
|
|
id: orderable2.id,
|
|
data: {
|
|
title: 'test',
|
|
orderableField: related.id,
|
|
_orderable_orderableJoinField1_order: 'e4',
|
|
},
|
|
})
|
|
const orderable4 = await payload.create({
|
|
collection: orderableSlug,
|
|
data: {
|
|
title: 'test',
|
|
orderableField: related.id,
|
|
_orderable_orderableJoinField1_order: 'e2',
|
|
},
|
|
})
|
|
expect(orderable2._orderable_orderableJoinField1_order).toBe('e4')
|
|
expect(orderable4._orderable_orderableJoinField1_order).toBe('e2')
|
|
})
|
|
|
|
it('should sort join docs in the correct', async () => {
|
|
related = await payload.findByID({
|
|
collection: orderableJoinSlug,
|
|
id: related.id,
|
|
depth: 1,
|
|
})
|
|
const orders = (related.orderableJoinField1 as { docs: Orderable[] }).docs.map((doc) =>
|
|
parseInt(doc._orderable_orderableJoinField1_order, 16),
|
|
) as [number, number, number]
|
|
expect(orders[0]).toBeLessThan(orders[1])
|
|
expect(orders[1]).toBeLessThan(orders[2])
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('REST API', () => {
|
|
beforeAll(async () => {
|
|
await createData('posts', [
|
|
{ text: 'Post 1', number: 1, number2: 10 },
|
|
{ text: 'Post 2', number: 2, number2: 10 },
|
|
{ text: 'Post 3', number: 3, number2: 5 },
|
|
{ text: 'Post 10', number: 10, number2: 5 },
|
|
{ text: 'Post 11', number: 11, number2: 20 },
|
|
{ text: 'Post 12', number: 12, number2: 20 },
|
|
])
|
|
})
|
|
|
|
afterAll(async () => {
|
|
await payload.delete({ collection: 'posts', where: {} })
|
|
})
|
|
|
|
describe('Single sort field', () => {
|
|
it('should sort posts by text field', async () => {
|
|
const res = await restClient
|
|
.GET(`/posts`, {
|
|
query: {
|
|
sort: 'text',
|
|
},
|
|
})
|
|
.then((res) => res.json())
|
|
|
|
expect(res.docs.map((post) => post.text)).toEqual([
|
|
'Post 1',
|
|
'Post 10',
|
|
'Post 11',
|
|
'Post 12',
|
|
'Post 2',
|
|
'Post 3',
|
|
])
|
|
})
|
|
|
|
it('should sort posts by text field desc', async () => {
|
|
const res = await restClient
|
|
.GET(`/posts`, {
|
|
query: {
|
|
sort: '-text',
|
|
},
|
|
})
|
|
.then((res) => res.json())
|
|
|
|
expect(res.docs.map((post) => post.text)).toEqual([
|
|
'Post 3',
|
|
'Post 2',
|
|
'Post 12',
|
|
'Post 11',
|
|
'Post 10',
|
|
'Post 1',
|
|
])
|
|
})
|
|
|
|
it('should sort posts by number field', async () => {
|
|
const res = await restClient
|
|
.GET(`/posts`, {
|
|
query: {
|
|
sort: 'number',
|
|
},
|
|
})
|
|
.then((res) => res.json())
|
|
|
|
expect(res.docs.map((post) => post.text)).toEqual([
|
|
'Post 1',
|
|
'Post 2',
|
|
'Post 3',
|
|
'Post 10',
|
|
'Post 11',
|
|
'Post 12',
|
|
])
|
|
})
|
|
|
|
it('should sort posts by number field desc', async () => {
|
|
const res = await restClient
|
|
.GET(`/posts`, {
|
|
query: {
|
|
sort: '-number',
|
|
},
|
|
})
|
|
.then((res) => res.json())
|
|
|
|
expect(res.docs.map((post) => post.text)).toEqual([
|
|
'Post 12',
|
|
'Post 11',
|
|
'Post 10',
|
|
'Post 3',
|
|
'Post 2',
|
|
'Post 1',
|
|
])
|
|
})
|
|
})
|
|
|
|
describe('Sort by multiple fields', () => {
|
|
it('should sort posts by multiple fields', async () => {
|
|
const res = await restClient
|
|
.GET(`/posts`, {
|
|
query: {
|
|
sort: 'number2,number',
|
|
},
|
|
})
|
|
.then((res) => res.json())
|
|
|
|
expect(res.docs.map((post) => post.text)).toEqual([
|
|
'Post 3', // 5, 3
|
|
'Post 10', // 5, 10
|
|
'Post 1', // 10, 1
|
|
'Post 2', // 10, 2
|
|
'Post 11', // 20, 11
|
|
'Post 12', // 20, 12
|
|
])
|
|
})
|
|
|
|
it('should sort posts by multiple fields asc and desc', async () => {
|
|
const res = await restClient
|
|
.GET(`/posts`, {
|
|
query: {
|
|
sort: 'number2,-number',
|
|
},
|
|
})
|
|
.then((res) => res.json())
|
|
|
|
expect(res.docs.map((post) => post.text)).toEqual([
|
|
'Post 10', // 5, 10
|
|
'Post 3', // 5, 3
|
|
'Post 2', // 10, 2
|
|
'Post 1', // 10, 1
|
|
'Post 12', // 20, 12
|
|
'Post 11', // 20, 11
|
|
])
|
|
})
|
|
})
|
|
})
|
|
})
|
|
|
|
async function createData(collection: CollectionSlug, data: Record<string, any>[]) {
|
|
for (const item of data) {
|
|
await payload.create({ collection, data: item })
|
|
}
|
|
}
|