Merge remote-tracking branch 'origin/main' into chore/bump-next-15.4.1
This commit is contained in:
@@ -1,7 +1,13 @@
|
||||
import type { MongooseAdapter } from '@payloadcms/db-mongodb'
|
||||
import type { PostgresAdapter } from '@payloadcms/db-postgres/types'
|
||||
import type { NextRESTClient } from 'helpers/NextRESTClient.js'
|
||||
import type { Payload, PayloadRequest, TypeWithID, ValidationError } from 'payload'
|
||||
import type {
|
||||
DataFromCollectionSlug,
|
||||
Payload,
|
||||
PayloadRequest,
|
||||
TypeWithID,
|
||||
ValidationError,
|
||||
} from 'payload'
|
||||
|
||||
import {
|
||||
migrateRelationshipsV2_V3,
|
||||
@@ -379,6 +385,118 @@ describe('database', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('should find distinct field values of the collection', async () => {
|
||||
await payload.delete({ collection: 'posts', where: {} })
|
||||
const titles = [
|
||||
'title-1',
|
||||
'title-2',
|
||||
'title-3',
|
||||
'title-4',
|
||||
'title-5',
|
||||
'title-6',
|
||||
'title-7',
|
||||
'title-8',
|
||||
'title-9',
|
||||
].map((title) => ({ title }))
|
||||
|
||||
for (const { title } of titles) {
|
||||
// eslint-disable-next-line jest/no-conditional-in-test
|
||||
const docsCount = Math.random() > 0.5 ? 3 : Math.random() > 0.5 ? 2 : 1
|
||||
for (let i = 0; i < docsCount; i++) {
|
||||
await payload.create({ collection: 'posts', data: { title } })
|
||||
}
|
||||
}
|
||||
|
||||
const res = await payload.findDistinct({
|
||||
collection: 'posts',
|
||||
field: 'title',
|
||||
})
|
||||
|
||||
expect(res.values).toStrictEqual(titles)
|
||||
|
||||
// const resREST = await restClient
|
||||
// .GET('/posts/distinct', {
|
||||
// headers: {
|
||||
// Authorization: `Bearer ${token}`,
|
||||
// },
|
||||
// query: { sortOrder: 'asc', field: 'title' },
|
||||
// })
|
||||
// .then((res) => res.json())
|
||||
|
||||
// expect(resREST.values).toEqual(titles)
|
||||
|
||||
const resLimit = await payload.findDistinct({
|
||||
collection: 'posts',
|
||||
field: 'title',
|
||||
limit: 3,
|
||||
})
|
||||
|
||||
expect(resLimit.values).toStrictEqual(
|
||||
['title-1', 'title-2', 'title-3'].map((title) => ({ title })),
|
||||
)
|
||||
// count is still 9
|
||||
expect(resLimit.totalDocs).toBe(9)
|
||||
|
||||
const resDesc = await payload.findDistinct({
|
||||
collection: 'posts',
|
||||
sort: '-title',
|
||||
field: 'title',
|
||||
})
|
||||
|
||||
expect(resDesc.values).toStrictEqual(titles.toReversed())
|
||||
|
||||
const resAscDefault = await payload.findDistinct({
|
||||
collection: 'posts',
|
||||
field: 'title',
|
||||
})
|
||||
|
||||
expect(resAscDefault.values).toStrictEqual(titles)
|
||||
})
|
||||
|
||||
it('should populate distinct relationships when depth>0', async () => {
|
||||
await payload.delete({ collection: 'posts', where: {} })
|
||||
|
||||
const categories = ['category-1', 'category-2', 'category-3', 'category-4'].map((title) => ({
|
||||
title,
|
||||
}))
|
||||
|
||||
const categoriesIDS: { category: string }[] = []
|
||||
|
||||
for (const { title } of categories) {
|
||||
const doc = await payload.create({ collection: 'categories', data: { title } })
|
||||
categoriesIDS.push({ category: doc.id })
|
||||
}
|
||||
|
||||
for (const { category } of categoriesIDS) {
|
||||
// eslint-disable-next-line jest/no-conditional-in-test
|
||||
const docsCount = Math.random() > 0.5 ? 3 : Math.random() > 0.5 ? 2 : 1
|
||||
for (let i = 0; i < docsCount; i++) {
|
||||
await payload.create({ collection: 'posts', data: { title: randomUUID(), category } })
|
||||
}
|
||||
}
|
||||
|
||||
const resultDepth0 = await payload.findDistinct({
|
||||
collection: 'posts',
|
||||
sort: 'category.title',
|
||||
field: 'category',
|
||||
})
|
||||
expect(resultDepth0.values).toStrictEqual(categoriesIDS)
|
||||
const resultDepth1 = await payload.findDistinct({
|
||||
depth: 1,
|
||||
collection: 'posts',
|
||||
field: 'category',
|
||||
sort: 'category.title',
|
||||
})
|
||||
|
||||
for (let i = 0; i < resultDepth1.values.length; i++) {
|
||||
const fromRes = resultDepth1.values[i] as any
|
||||
const id = categoriesIDS[i].category as any
|
||||
const title = categories[i]?.title
|
||||
expect(fromRes.category.title).toBe(title)
|
||||
expect(fromRes.category.id).toBe(id)
|
||||
}
|
||||
})
|
||||
|
||||
describe('Compound Indexes', () => {
|
||||
beforeEach(async () => {
|
||||
await payload.delete({ collection: 'compound-indexes', where: {} })
|
||||
@@ -2807,7 +2925,7 @@ describe('database', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('should update simple', async () => {
|
||||
it('should use optimized updateOne', async () => {
|
||||
const post = await payload.create({
|
||||
collection: 'posts',
|
||||
data: {
|
||||
@@ -2818,7 +2936,7 @@ describe('database', () => {
|
||||
arrayWithIDs: [{ text: 'some text' }],
|
||||
},
|
||||
})
|
||||
const res = await payload.db.updateOne({
|
||||
const res = (await payload.db.updateOne({
|
||||
where: { id: { equals: post.id } },
|
||||
data: {
|
||||
title: 'hello updated',
|
||||
@@ -2826,14 +2944,89 @@ describe('database', () => {
|
||||
tab: { text: 'in tab updated' },
|
||||
},
|
||||
collection: 'posts',
|
||||
})
|
||||
})) as unknown as DataFromCollectionSlug<'posts'>
|
||||
|
||||
expect(res.title).toBe('hello updated')
|
||||
expect(res.text).toBe('other text (should not be nuked)')
|
||||
expect(res.group.text).toBe('in group updated')
|
||||
expect(res.tab.text).toBe('in tab updated')
|
||||
expect(res.group?.text).toBe('in group updated')
|
||||
expect(res.tab?.text).toBe('in tab updated')
|
||||
expect(res.arrayWithIDs).toHaveLength(1)
|
||||
expect(res.arrayWithIDs[0].text).toBe('some text')
|
||||
expect(res.arrayWithIDs?.[0]?.text).toBe('some text')
|
||||
})
|
||||
|
||||
it('should use optimized updateMany', async () => {
|
||||
const post1 = await payload.create({
|
||||
collection: 'posts',
|
||||
data: {
|
||||
text: 'other text (should not be nuked)',
|
||||
title: 'hello',
|
||||
group: { text: 'in group' },
|
||||
tab: { text: 'in tab' },
|
||||
arrayWithIDs: [{ text: 'some text' }],
|
||||
},
|
||||
})
|
||||
const post2 = await payload.create({
|
||||
collection: 'posts',
|
||||
data: {
|
||||
text: 'other text 2 (should not be nuked)',
|
||||
title: 'hello',
|
||||
group: { text: 'in group' },
|
||||
tab: { text: 'in tab' },
|
||||
arrayWithIDs: [{ text: 'some text' }],
|
||||
},
|
||||
})
|
||||
|
||||
const res = (await payload.db.updateMany({
|
||||
where: { id: { in: [post1.id, post2.id] } },
|
||||
data: {
|
||||
title: 'hello updated',
|
||||
group: { text: 'in group updated' },
|
||||
tab: { text: 'in tab updated' },
|
||||
},
|
||||
collection: 'posts',
|
||||
})) as unknown as Array<DataFromCollectionSlug<'posts'>>
|
||||
|
||||
expect(res).toHaveLength(2)
|
||||
const resPost1 = res?.find((r) => r.id === post1.id)
|
||||
const resPost2 = res?.find((r) => r.id === post2.id)
|
||||
expect(resPost1?.text).toBe('other text (should not be nuked)')
|
||||
expect(resPost2?.text).toBe('other text 2 (should not be nuked)')
|
||||
|
||||
for (const post of res) {
|
||||
expect(post.title).toBe('hello updated')
|
||||
expect(post.group?.text).toBe('in group updated')
|
||||
expect(post.tab?.text).toBe('in tab updated')
|
||||
expect(post.arrayWithIDs).toHaveLength(1)
|
||||
expect(post.arrayWithIDs?.[0]?.text).toBe('some text')
|
||||
}
|
||||
})
|
||||
|
||||
it('should allow incremental number update', async () => {
|
||||
const post = await payload.create({ collection: 'posts', data: { number: 1, title: 'post' } })
|
||||
|
||||
const res = await payload.db.updateOne({
|
||||
data: {
|
||||
number: {
|
||||
$inc: 10,
|
||||
},
|
||||
},
|
||||
collection: 'posts',
|
||||
where: { id: { equals: post.id } },
|
||||
})
|
||||
|
||||
expect(res.number).toBe(11)
|
||||
|
||||
const res2 = await payload.db.updateOne({
|
||||
data: {
|
||||
number: {
|
||||
$inc: -3,
|
||||
},
|
||||
},
|
||||
collection: 'posts',
|
||||
where: { id: { equals: post.id } },
|
||||
})
|
||||
|
||||
expect(res2.number).toBe(8)
|
||||
})
|
||||
|
||||
it('should support x3 nesting blocks', async () => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"id": "bf183b76-944c-4e83-bd58-4aa993885106",
|
||||
"id": "80e7a0d2-ffb3-4f22-8597-0442b3ab8102",
|
||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
@@ -1,9 +1,9 @@
|
||||
import * as migration_20250707_123508 from './20250707_123508.js'
|
||||
import * as migration_20250714_201659 from './20250714_201659.js';
|
||||
|
||||
export const migrations = [
|
||||
{
|
||||
up: migration_20250707_123508.up,
|
||||
down: migration_20250707_123508.down,
|
||||
name: '20250707_123508',
|
||||
up: migration_20250714_201659.up,
|
||||
down: migration_20250714_201659.down,
|
||||
name: '20250714_201659'
|
||||
},
|
||||
]
|
||||
];
|
||||
|
||||
@@ -21,6 +21,25 @@ export const allDatabaseAdapters = {
|
||||
strength: 1,
|
||||
},
|
||||
})`,
|
||||
firestore: `
|
||||
import { mongooseAdapter, compatabilityOptions } from '@payloadcms/db-mongodb'
|
||||
|
||||
export const databaseAdapter = mongooseAdapter({
|
||||
...compatabilityOptions.firestore,
|
||||
url:
|
||||
process.env.DATABASE_URI ||
|
||||
process.env.MONGODB_MEMORY_SERVER_URI ||
|
||||
'mongodb://127.0.0.1/payloadtests',
|
||||
collation: {
|
||||
strength: 1,
|
||||
},
|
||||
// The following options prevent some tests from failing.
|
||||
// More work needed to get tests succeeding without these options.
|
||||
ensureIndexes: true,
|
||||
transactionOptions: {},
|
||||
disableIndexHints: false,
|
||||
useAlternativeDropDatabase: false,
|
||||
})`,
|
||||
postgres: `
|
||||
import { postgresAdapter } from '@payloadcms/db-postgres'
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ const dirname = path.dirname(filename)
|
||||
const writeDBAdapter = process.env.WRITE_DB_ADAPTER !== 'false'
|
||||
process.env.PAYLOAD_DROP_DATABASE = process.env.PAYLOAD_DROP_DATABASE || 'true'
|
||||
|
||||
if (process.env.PAYLOAD_DATABASE === 'mongodb') {
|
||||
if (process.env.PAYLOAD_DATABASE === 'mongodb' || process.env.PAYLOAD_DATABASE === 'firestore') {
|
||||
throw new Error('Not supported')
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ import { devUser } from '../credentials.js'
|
||||
type ValidPath = `/${string}`
|
||||
type RequestOptions = {
|
||||
auth?: boolean
|
||||
query?: {
|
||||
query?: { [key: string]: unknown } & {
|
||||
depth?: number
|
||||
fallbackLocale?: string
|
||||
joins?: JoinQuery
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import type { Payload } from 'payload'
|
||||
|
||||
export function isMongoose(_payload?: Payload) {
|
||||
return _payload?.db?.name === 'mongoose' || ['mongodb'].includes(process.env.PAYLOAD_DATABASE)
|
||||
return (
|
||||
_payload?.db?.name === 'mongoose' ||
|
||||
['firestore', 'mongodb'].includes(process.env.PAYLOAD_DATABASE)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -14,13 +14,17 @@ declare global {
|
||||
*/
|
||||
// eslint-disable-next-line no-restricted-exports
|
||||
export default async () => {
|
||||
if (process.env.DATABASE_URI) {
|
||||
return
|
||||
}
|
||||
process.env.NODE_ENV = 'test'
|
||||
process.env.PAYLOAD_DROP_DATABASE = 'true'
|
||||
process.env.NODE_OPTIONS = '--no-deprecation'
|
||||
process.env.DISABLE_PAYLOAD_HMR = 'true'
|
||||
|
||||
if (
|
||||
(!process.env.PAYLOAD_DATABASE || process.env.PAYLOAD_DATABASE === 'mongodb') &&
|
||||
(!process.env.PAYLOAD_DATABASE ||
|
||||
['firestore', 'mongodb'].includes(process.env.PAYLOAD_DATABASE)) &&
|
||||
!global._mongoMemoryServer
|
||||
) {
|
||||
console.log('Starting memory db...')
|
||||
|
||||
@@ -38,7 +38,7 @@ const dirname = path.dirname(filename)
|
||||
|
||||
type EasierChained = { id: string; relation: EasierChained }
|
||||
|
||||
const mongoIt = process.env.PAYLOAD_DATABASE === 'mongodb' ? it : it.skip
|
||||
const mongoIt = ['firestore', 'mongodb'].includes(process.env.PAYLOAD_DATABASE || '') ? it : it.skip
|
||||
|
||||
describe('Relationships', () => {
|
||||
beforeAll(async () => {
|
||||
@@ -791,6 +791,47 @@ describe('Relationships', () => {
|
||||
expect(localized_res_2.docs).toStrictEqual([movie_1, movie_2])
|
||||
})
|
||||
|
||||
it('should sort by multiple properties of a relationship', async () => {
|
||||
await payload.delete({ collection: 'directors', where: {} })
|
||||
await payload.delete({ collection: 'movies', where: {} })
|
||||
|
||||
const createDirector = {
|
||||
collection: 'directors',
|
||||
data: {
|
||||
name: 'Dan',
|
||||
},
|
||||
} as const
|
||||
|
||||
const director_1 = await payload.create(createDirector)
|
||||
const director_2 = await payload.create(createDirector)
|
||||
|
||||
const movie_1 = await payload.create({
|
||||
collection: 'movies',
|
||||
depth: 0,
|
||||
data: { director: director_1.id, name: 'Some Movie 1' },
|
||||
})
|
||||
|
||||
const movie_2 = await payload.create({
|
||||
collection: 'movies',
|
||||
depth: 0,
|
||||
data: { director: director_2.id, name: 'Some Movie 2' },
|
||||
})
|
||||
|
||||
const res_1 = await payload.find({
|
||||
collection: 'movies',
|
||||
sort: ['director.name', 'director.createdAt'],
|
||||
depth: 0,
|
||||
})
|
||||
const res_2 = await payload.find({
|
||||
collection: 'movies',
|
||||
sort: ['director.name', '-director.createdAt'],
|
||||
depth: 0,
|
||||
})
|
||||
|
||||
expect(res_1.docs).toStrictEqual([movie_1, movie_2])
|
||||
expect(res_2.docs).toStrictEqual([movie_2, movie_1])
|
||||
})
|
||||
|
||||
it('should sort by a property of a hasMany relationship', async () => {
|
||||
const movie1 = await payload.create({
|
||||
collection: 'movies',
|
||||
|
||||
Reference in New Issue
Block a user