* wip moves payload, user and data into partial req * chore: adjust req type * chore(next): installs sass and resolves type errors * feat: working login route/view * fix: me route * chore(next): scaffolds access routes (#4562) * chore(next): scaffolds admin layout and dashboard view (#4566) * chore(next): builds initPage utility (#4589) * feat(3.0): next route handlers (#4590) * chore: removes old files * chore(next): ssr list view (#4594) * chore: removes old files * chore: adjusts graphql file imports to align with new operation exports * chore: allows for custom endpoints * chore: cleanup * chore(next): ssr edit view (#4614) * chore(ui): ssr main nav (#4619) * chore(next): ssr account view (#4620) * chore(next): ssr auth views and document create (#4631) * chore(next): ssr globals view (#4640) * chore(next): scaffolds document layout (#4644) * chore(next): ssr versions view (#4645) * chore(next): ssr field conditions (#4675) * chore(next): ssr field validations (#4700) * chore(next): moves dashboard view into next dir * chore(next): moves account view into next dir * chore(next): moves global edit view into next dir * chore(next): returns isolated configs and locale from initPage * chore(next): ssr api view (#4721) * feat: adds i18n functionality within Rest API, Local and Client contexts (#4749) * chore: separate client translation groups with empty line * chore: add missing translation used in db adapters * chore: simplify next/routes export and import paths * chore: renames PayloadT to Payload * chore(next): custom views (#4748) * chore: fix translation tsconfig * chore: adjust other package ts-configs that rely on translations * chore(next): installs @payloadcms/ui as direct dependency * chore(next): progress to build * chore(next): migrates types (#4792) * fixes acccept-language detection * chore(next): moves remaining components out from payload core (#4794) * chore(deps): removes all unused dependencies from payload core (#4797) * chore(next): achieves buildable state (#4803) * adds Translation component and removes more react-i18next * fixes up remaining translation strings * fixes a few i18n TODO's * chore: remaining translation strings without colons * chore: adds missing ja translations * chore(next): ssr group field (#4830) * chore: removes placeholder t function * chore: removes old file * chore(bundler-webpack): removes webpack bundler * chore(bundler-vite): removes vite bundler * chore(next): ssr tabs field (#4863) * chore(next): ssr row field * chore(next): ssr textarea field * chore(next): wires server action into document edit view (#4873) * chore(next): conditional logic (#4880) * chore(next): ssr radio, point, code, json, ui, and hidden fields (#4891) * chore(next): ssr collapsible field (#4894) * chore: remove findByID from req * chore: adjusts file property on request type * comment clarification * chore: wires up busboy with Requst readstream * chore: ports over express-fileupload into a NextJS compatible format * chore: adjust upload file structure * chore: adds try/catch around routes, corrects a few route responses * chore: renames file/function * chore: improve req type safety in local operations, misc req.files replacements * chore: misc type and fn export changes * chore: ensures root routes take pass unmodified request to root routes * chore: improve types * chore: consolidates locale api req initialization (#4922) * chore(next): overhauls field rendering strategy (#4924) * chore(next): ssr array field (#4937) * chore(next): ssr blocks field (#4942) * chore(next): ssr upload field and document drawer (#4957) * chore(next): wires form submissions (#4982) * chore: api handler adjustments * feat: adds graphql playground handler * adds credentials include setting to playground * remove old playground init, stub graphql handler location * fix: allow for null fallbackLocale * fix: correctly prioritize locales passed as null * chore: move all graphql code into next package * graphql changes * chore: semi working version of graphql http layer * gql fix attempts * rm console log * chore: partial gql changes * chore: adds gql and gql-http back into payload * chore: removes collection from req * chore: separates graphql package out for schema generation * chore: dep cleanup * chore: move graphql handlers * chore: removes unused deps * chore(next): ssr list view (#5032) * chore: refactor response handler order for custom endpoints * chore: add back in condition for collection GET path with 2 slugs * chore: rm optional chain * chore: import sort route file * chore: allows custom endpoints to attempt before erroring * feat: adds memoization to translation functions (#5036) * chore: fix APIError import * chore: return attemptCustomEndpointBeforeError responses * chore(next): properly instantiates table columns * fix(next): attaches params to req and properly assigns prefs key (#5042) * chore: reorganize next route order * chore(next): adds RouteError handler to next routes * chore: builds payload successfully * chore: misc file omissions * fix(ui): maintains proper column order * fix(ui): ensures first cell is a link * fix(next): properly copies url object in createPayloadRequest (#5064) * fix(ui): bumps react-toastify to v10.0.4 to fix hydration warnings * feat: add route for static file GET requests (#5065) * chore(next): allows resolved config promise to be thread through initPage (#5071) * chore(ui): conditionally renders field label from props * feat(next): next install script * chore: pass config to route handlers * feat: initial test suite framework (#4929) * chore(next): renderable account, api, and create first user views (#5084) * fix(next): properly parses search params in find, update, and delete handlers (#5088) * chore(next): ssr versions view (#5085) * chore: adds homepage for scss testing * chore: moves dev folder to top, establishes new test pattern * chore: working turbopack * chore: sets up working dynamic payload-config imports * remove unused code * chore: rm console log * misc * feat: correctly subs out ability to boot REST API within same process * chore: WIP dev suites * chore: removes need for REST_API folder in test dir * removes duplicate bootAdminPanel fn * misc * specify default export * chore: sets up jest to work with next/jest * chore: progress to mongodb and sharp builds * chore: passing community tests * chore: sorta workin * chore: adjust payload-config import * chore: adds rest client for Next handlers * chore: removes test garb * chore: restores payload-config tsconfig path temporarily * chore: establishes pattern for memory db during tests * chore: bumps mongoose to 7 * chore(next): 404s on nested create urls * chore: functional _community e2e * chore: increases e2e expect timeout * fix(next): sanitizes locale toString from client config * chore: type fixes * chore: pulls mongodb from main * chore: uses graphql to log user in * feat: passing auth test suite * chore(ui): threads params through context and conditionally renders document tabs (#5094) * feat(ui): adds params context (#5095) * chore: removes unecessary memory allocation for urlPropertiesObject object * chore: passing graphql test suite * chore: removes references to bson * chore: re-enables mongodb memory server for auth test suite * chore: replace bson with bson-objectid * feat: passing collections-rest int suite * chore: fixes bad imports * chore: more passing int suites * feat: passing globals int tests * feat: passing hooks int test suite * chore: remove last express file * chore: start live-preview int test migration * chore: passing localization int tests * passing relationships int tests * chore: partial passing upload int tests * chore: fixes scss imports * chore(ui): renders document info provider at root (#5106) * chore: adds schema path to useFieldPath provider, more passing tests * chore: begins work to optimize translation imports * chore: add translations to ui ts-config references * chore: add exports folder to package json exports * chore: adds readme how-to-use instructions * chore: attempts refactor of translation imports * chore: adds authentication:account translation key to server keys * chore: finishes translation optimization * chore: ignores warnings from mongodb * chore(ui): renders live document title (#5115) * chore(ui): ssr document tabs (#5116) * chore: handles redirecting from login * chore: handle redirect with no searchParams * chore: handle missing segments * chore(next): migrates server action into standalone api endpoint (#5122) * chore: adjust dashboard colection segments * test: update e2e suites * fix(ui): prevents unnecessary calls to form state * chore: fix finding global config fields from schema path * fix(next): executes root POST endpoints * chore(ui): ignores values returned by form state polling * chore: scaffolds ssr rte * chore: renders client leaves * chore: server-side rendered rich text elements * chore: defines ClientFunction pattern * chore(ui): migrates relationship field * chore: adds translations, cleans up slate * chore: functional slate link * chore: slate upload ssr * chore: relationship slate ssr * chore: remaining slate ssr * chore: fixes circular workspace dep * chore: correct broken int test import paths * chore: remove media files from root * chore: server renders custom edit view * fix(ui): resolves infinite loading in versions view * fix(next): resolves global edit view lookup * chore: payload builds * chore: delete unused files * chore: removes local property from payload * chore: adds mongodb as dev dep in db-mongodb package * chore: hide deprecation warnings for tempfile and jest-environment-jsdom * chore: remove all translations from translations dist * chore: clean ts-config files * chore: simple type fixes * chore(ui): server renders custom list view * chore: fix next config payload-config alias * chore: adds turbo alias paths * chore: adjusts translation generation * chore: improve auth function * chore: eslint config for packages/ui * chore(ui): exports FormState * chore(next): migrates account view to latest patterns * chore: disable barbie mode * chore(ui): lints * chore(next): lints * chore: for alexical * chore: custom handler type signature adjustment * fix: non-boolean condition result causes infinite looping (#4579) * chore(richtext-lexical): upgrade lexical from v0.12.5 to v0.12.6 (#4732) * chore(richtext-lexical): upgrade all lexical packages from 0.12.5 to 0.12.6 * fix(richtext-lexical): fix TypeScript errors * fix indenting * feat(richtext-lexical): Blocks: generate type definitions for blocks fields (#4529) * feat(richtext-lexical)!: Update lexical from 0.12.6 to 0.13.1, port over all useful changes from playground (#5066) * feat(richtext-lexical): Update lexical from 0.12.6 to 0.13.1, port over all useful changes from playground * chore: upgrade lexical version used in monorepo * chore: remove the 3 * chore: upgrade nodemon versions (#5059) * feat: add more options to addFieldStatePromise so that it can be used for field flattening (#4799) * feat(plugin-seo)!: remove support for payload <2.7.0 (#4765) * chore(plugin-seo): remove test script from package.json (#4762) * chore: upgrade @types/nodemailer from v6.4.8 to v6.4.14 (#4733) * chore: revert auth and initPage changes * chore(next): moves edit and list views (#5170) * fix: "The punycode module is deprecated" warning by updating nodemailer * chore: adjust translations tsconfig paths in root * chore: fix merge build --------- Co-authored-by: Jarrod Flesch <jarrodmflesch@gmail.com> Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com> Co-authored-by: Jarrod Flesch <30633324+JarrodMFlesch@users.noreply.github.com> Co-authored-by: Elliot DeNolf <denolfe@gmail.com> Co-authored-by: James <james@trbl.design> Co-authored-by: Alessio Gravili <alessio@gravili.de> Co-authored-by: Alessio Gravili <70709113+AlessioGr@users.noreply.github.com>
1498 lines
40 KiB
TypeScript
1498 lines
40 KiB
TypeScript
import type { IndexDirection, IndexOptions } from 'mongoose'
|
|
|
|
import type { MongooseAdapter } from '../../packages/db-mongodb/src/index'
|
|
import type { Payload } from '../../packages/payload/src'
|
|
import type { PaginatedDocs } from '../../packages/payload/src/database/types'
|
|
import type { GroupField, RichTextField } from './payload-types'
|
|
import type { GroupField } from './payload-types'
|
|
|
|
import { getPayload } from '../../packages/payload/src'
|
|
import { devUser } from '../credentials'
|
|
import { NextRESTClient } from '../helpers/NextRESTClient'
|
|
import { isMongoose } from '../helpers/isMongoose'
|
|
import { startMemoryDB } from '../startMemoryDB'
|
|
import { arrayDefaultValue } from './collections/Array'
|
|
import { blocksDoc } from './collections/Blocks/shared'
|
|
import { dateDoc } from './collections/Date/shared'
|
|
import { groupDefaultChild, groupDefaultValue } from './collections/Group'
|
|
import { groupDoc } from './collections/Group/shared'
|
|
import { defaultNumber } from './collections/Number'
|
|
import { numberDoc } from './collections/Number/shared'
|
|
import { pointDoc } from './collections/Point/shared'
|
|
import {
|
|
localizedTextValue,
|
|
namedTabDefaultValue,
|
|
namedTabText,
|
|
} from './collections/Tabs/constants'
|
|
import { tabsDoc } from './collections/Tabs/shared'
|
|
import { defaultText } from './collections/Text/shared'
|
|
import configPromise from './config'
|
|
import { clearAndSeedEverything } from './seed'
|
|
import {
|
|
arrayFieldsSlug,
|
|
blockFieldsSlug,
|
|
groupFieldsSlug,
|
|
relationshipFieldsSlug,
|
|
tabsFieldsSlug,
|
|
textFieldsSlug,
|
|
} from './slugs'
|
|
|
|
let restClient: NextRESTClient
|
|
let user: any
|
|
let payload: Payload
|
|
|
|
describe('Fields', () => {
|
|
beforeAll(async () => {
|
|
const config = await startMemoryDB(configPromise)
|
|
payload = await getPayload({ config })
|
|
restClient = new NextRESTClient(payload.config)
|
|
await restClient.login({
|
|
slug: 'users',
|
|
credentials: devUser,
|
|
})
|
|
|
|
user = await payload.login({
|
|
collection: 'users',
|
|
data: {
|
|
email: devUser.email,
|
|
password: devUser.password,
|
|
},
|
|
})
|
|
})
|
|
|
|
beforeEach(async () => {
|
|
await clearAndSeedEverything(payload)
|
|
await restClient.login({
|
|
slug: 'users',
|
|
credentials: devUser,
|
|
})
|
|
})
|
|
|
|
describe('text', () => {
|
|
let doc
|
|
const text = 'text field'
|
|
beforeEach(async () => {
|
|
doc = await payload.create({
|
|
collection: 'text-fields',
|
|
data: { text },
|
|
})
|
|
})
|
|
|
|
it('creates with default values', () => {
|
|
expect(doc.text).toEqual(text)
|
|
expect(doc.defaultFunction).toEqual(defaultText)
|
|
expect(doc.defaultAsync).toEqual(defaultText)
|
|
})
|
|
|
|
it('should populate default values in beforeValidate hook', async () => {
|
|
const { dependentOnFieldWithDefaultValue, fieldWithDefaultValue } = await payload.create({
|
|
collection: 'text-fields',
|
|
data: { text },
|
|
})
|
|
|
|
await expect(fieldWithDefaultValue).toEqual(dependentOnFieldWithDefaultValue)
|
|
})
|
|
|
|
it('should localize an array of strings using hasMany', async () => {
|
|
const localizedHasMany = ['hello', 'world']
|
|
const { id } = await payload.create({
|
|
collection: 'text-fields',
|
|
data: {
|
|
localizedHasMany,
|
|
text,
|
|
},
|
|
locale: 'en',
|
|
})
|
|
const localizedDoc = await payload.findByID({
|
|
id,
|
|
collection: 'text-fields',
|
|
locale: 'all',
|
|
})
|
|
|
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
// @ts-expect-error
|
|
expect(localizedDoc.localizedHasMany.en).toEqual(localizedHasMany)
|
|
})
|
|
})
|
|
|
|
describe('relationship', () => {
|
|
let textDoc
|
|
let otherTextDoc
|
|
let selfReferencing
|
|
let parent
|
|
let child
|
|
let grandChild
|
|
let relationshipInArray
|
|
const textDocText = 'text document'
|
|
const otherTextDocText = 'alt text'
|
|
const relationshipText = 'relationship text'
|
|
|
|
beforeEach(async () => {
|
|
textDoc = await payload.create({
|
|
collection: 'text-fields',
|
|
data: {
|
|
text: textDocText,
|
|
},
|
|
})
|
|
otherTextDoc = await payload.create({
|
|
collection: 'text-fields',
|
|
data: {
|
|
text: otherTextDocText,
|
|
},
|
|
})
|
|
const relationship = { relationTo: 'text-fields', value: textDoc.id }
|
|
parent = await payload.create({
|
|
collection: relationshipFieldsSlug,
|
|
data: {
|
|
relationship,
|
|
text: relationshipText,
|
|
},
|
|
})
|
|
|
|
child = await payload.create({
|
|
collection: relationshipFieldsSlug,
|
|
data: {
|
|
relationToSelf: parent.id,
|
|
relationship,
|
|
text: relationshipText,
|
|
},
|
|
})
|
|
|
|
grandChild = await payload.create({
|
|
collection: relationshipFieldsSlug,
|
|
data: {
|
|
relationToSelf: child.id,
|
|
relationship,
|
|
text: relationshipText,
|
|
},
|
|
})
|
|
|
|
selfReferencing = await payload.create({
|
|
collection: relationshipFieldsSlug,
|
|
data: {
|
|
relationship,
|
|
text: relationshipText,
|
|
},
|
|
})
|
|
|
|
relationshipInArray = await payload.create({
|
|
collection: relationshipFieldsSlug,
|
|
data: {
|
|
array: [
|
|
{
|
|
relationship: otherTextDoc.id,
|
|
},
|
|
],
|
|
relationship,
|
|
},
|
|
})
|
|
})
|
|
|
|
it('should query parent self-reference', async () => {
|
|
const childResult = await payload.find({
|
|
collection: relationshipFieldsSlug,
|
|
where: {
|
|
relationToSelf: { equals: parent.id },
|
|
},
|
|
})
|
|
|
|
const grandChildResult = await payload.find({
|
|
collection: relationshipFieldsSlug,
|
|
where: {
|
|
relationToSelf: { equals: child.id },
|
|
},
|
|
})
|
|
|
|
const anyChildren = await payload.find({
|
|
collection: relationshipFieldsSlug,
|
|
})
|
|
const allChildren = await payload.find({
|
|
collection: relationshipFieldsSlug,
|
|
where: {
|
|
'relationToSelf.text': { equals: relationshipText },
|
|
},
|
|
})
|
|
|
|
expect(childResult.docs[0].id).toStrictEqual(child.id)
|
|
expect(grandChildResult.docs[0].id).toStrictEqual(grandChild.id)
|
|
expect(allChildren.docs).toHaveLength(2)
|
|
})
|
|
|
|
it('should query relationship inside array', async () => {
|
|
const result = await payload.find({
|
|
collection: relationshipFieldsSlug,
|
|
where: {
|
|
'array.relationship.text': { equals: otherTextDocText },
|
|
},
|
|
})
|
|
|
|
expect(result.docs).toHaveLength(1)
|
|
expect(result.docs[0]).toMatchObject(relationshipInArray)
|
|
})
|
|
})
|
|
|
|
describe('timestamps', () => {
|
|
const tenMinutesAgo = new Date(Date.now() - 1000 * 60 * 10)
|
|
let doc
|
|
beforeEach(async () => {
|
|
doc = await payload.create({
|
|
collection: 'date-fields',
|
|
data: dateDoc,
|
|
})
|
|
})
|
|
|
|
it('should query updatedAt', async () => {
|
|
const { docs } = await payload.find({
|
|
collection: 'date-fields',
|
|
depth: 0,
|
|
where: {
|
|
updatedAt: {
|
|
greater_than_equal: tenMinutesAgo,
|
|
},
|
|
},
|
|
})
|
|
|
|
expect(docs.map(({ id }) => id)).toContain(doc.id)
|
|
})
|
|
|
|
it('should query createdAt', async () => {
|
|
const result = await payload.find({
|
|
collection: 'date-fields',
|
|
depth: 0,
|
|
where: {
|
|
createdAt: {
|
|
greater_than_equal: tenMinutesAgo,
|
|
},
|
|
},
|
|
})
|
|
|
|
expect(result.docs[0].id).toEqual(doc.id)
|
|
})
|
|
})
|
|
|
|
describe('select', () => {
|
|
let doc
|
|
beforeEach(async () => {
|
|
const { id } = await payload.create({
|
|
collection: 'select-fields',
|
|
data: {
|
|
selectHasManyLocalized: ['one', 'two'],
|
|
},
|
|
locale: 'en',
|
|
})
|
|
doc = await payload.findByID({
|
|
id,
|
|
collection: 'select-fields',
|
|
locale: 'all',
|
|
})
|
|
})
|
|
|
|
it('creates with hasMany localized', () => {
|
|
expect(doc.selectHasManyLocalized.en).toEqual(['one', 'two'])
|
|
})
|
|
|
|
it('retains hasMany updates', async () => {
|
|
const { id } = await payload.create({
|
|
collection: 'select-fields',
|
|
data: {
|
|
selectHasMany: ['one', 'two'],
|
|
},
|
|
})
|
|
|
|
const updatedDoc = await payload.update({
|
|
id,
|
|
collection: 'select-fields',
|
|
data: {
|
|
select: 'one',
|
|
},
|
|
})
|
|
|
|
expect(Array.isArray(updatedDoc.selectHasMany)).toBe(true)
|
|
expect(updatedDoc.selectHasMany).toEqual(['one', 'two'])
|
|
})
|
|
})
|
|
|
|
describe('number', () => {
|
|
let doc
|
|
beforeEach(async () => {
|
|
doc = await payload.create({
|
|
collection: 'number-fields',
|
|
data: numberDoc,
|
|
})
|
|
})
|
|
|
|
it('creates with default values', async () => {
|
|
expect(doc.number).toEqual(numberDoc.number)
|
|
expect(doc.min).toEqual(numberDoc.min)
|
|
expect(doc.max).toEqual(numberDoc.max)
|
|
expect(doc.positiveNumber).toEqual(numberDoc.positiveNumber)
|
|
expect(doc.negativeNumber).toEqual(numberDoc.negativeNumber)
|
|
expect(doc.decimalMin).toEqual(numberDoc.decimalMin)
|
|
expect(doc.decimalMax).toEqual(numberDoc.decimalMax)
|
|
expect(doc.defaultNumber).toEqual(defaultNumber)
|
|
})
|
|
|
|
it('should not create number below minimum', async () => {
|
|
await expect(async () =>
|
|
payload.create({
|
|
collection: 'number-fields',
|
|
data: {
|
|
min: 5,
|
|
},
|
|
}),
|
|
).rejects.toThrow('The following field is invalid: min')
|
|
})
|
|
it('should not create number above max', async () => {
|
|
await expect(async () =>
|
|
payload.create({
|
|
collection: 'number-fields',
|
|
data: {
|
|
max: 15,
|
|
},
|
|
}),
|
|
).rejects.toThrow('The following field is invalid: max')
|
|
})
|
|
|
|
it('should not create number below 0', async () => {
|
|
await expect(async () =>
|
|
payload.create({
|
|
collection: 'number-fields',
|
|
data: {
|
|
positiveNumber: -5,
|
|
},
|
|
}),
|
|
).rejects.toThrow('The following field is invalid: positiveNumber')
|
|
})
|
|
|
|
it('should not create number above 0', async () => {
|
|
await expect(async () =>
|
|
payload.create({
|
|
collection: 'number-fields',
|
|
data: {
|
|
negativeNumber: 5,
|
|
},
|
|
}),
|
|
).rejects.toThrow('The following field is invalid: negativeNumber')
|
|
})
|
|
it('should not create a decimal number below min', async () => {
|
|
await expect(async () =>
|
|
payload.create({
|
|
collection: 'number-fields',
|
|
data: {
|
|
decimalMin: -0.25,
|
|
},
|
|
}),
|
|
).rejects.toThrow('The following field is invalid: decimalMin')
|
|
})
|
|
|
|
it('should not create a decimal number above max', async () => {
|
|
await expect(async () =>
|
|
payload.create({
|
|
collection: 'number-fields',
|
|
data: {
|
|
decimalMax: 1.5,
|
|
},
|
|
}),
|
|
).rejects.toThrow('The following field is invalid: decimalMax')
|
|
})
|
|
it('should localize an array of numbers using hasMany', async () => {
|
|
const localizedHasMany = [5, 10]
|
|
const { id } = await payload.create({
|
|
collection: 'number-fields',
|
|
data: {
|
|
localizedHasMany,
|
|
},
|
|
locale: 'en',
|
|
})
|
|
const localizedDoc = await payload.findByID({
|
|
id,
|
|
collection: 'number-fields',
|
|
locale: 'all',
|
|
})
|
|
|
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
// @ts-expect-error
|
|
expect(localizedDoc.localizedHasMany.en).toEqual(localizedHasMany)
|
|
})
|
|
})
|
|
|
|
if (isMongoose(payload)) {
|
|
describe('indexes', () => {
|
|
let indexes
|
|
const definitions: Record<string, IndexDirection> = {}
|
|
const options: Record<string, IndexOptions> = {}
|
|
|
|
beforeAll(() => {
|
|
indexes = (payload.db as MongooseAdapter).collections[
|
|
'indexed-fields'
|
|
].schema.indexes() as [Record<string, IndexDirection>, IndexOptions]
|
|
|
|
indexes.forEach((index) => {
|
|
const field = Object.keys(index[0])[0]
|
|
definitions[field] = index[0][field]
|
|
// eslint-disable-next-line prefer-destructuring
|
|
options[field] = index[1]
|
|
})
|
|
})
|
|
|
|
it('should have indexes', () => {
|
|
expect(definitions.text).toEqual(1)
|
|
})
|
|
|
|
it('should have unique sparse indexes when field is not required', () => {
|
|
expect(definitions.uniqueText).toEqual(1)
|
|
expect(options.uniqueText).toMatchObject({ sparse: true, unique: true })
|
|
})
|
|
|
|
it('should have unique indexes that are not sparse when field is required', () => {
|
|
expect(definitions.uniqueRequiredText).toEqual(1)
|
|
expect(options.uniqueText).toMatchObject({ unique: true })
|
|
})
|
|
|
|
it('should have 2dsphere indexes on point fields', () => {
|
|
expect(definitions.point).toEqual('2dsphere')
|
|
})
|
|
|
|
it('should have 2dsphere indexes on point fields in groups', () => {
|
|
expect(definitions['group.point']).toEqual('2dsphere')
|
|
})
|
|
|
|
it('should have a sparse index on a unique localized field in a group', () => {
|
|
expect(definitions['group.localizedUnique.en']).toEqual(1)
|
|
expect(options['group.localizedUnique.en']).toMatchObject({ sparse: true, unique: true })
|
|
expect(definitions['group.localizedUnique.es']).toEqual(1)
|
|
expect(options['group.localizedUnique.es']).toMatchObject({ sparse: true, unique: true })
|
|
})
|
|
|
|
it('should have unique indexes in a collapsible', () => {
|
|
expect(definitions['collapsibleLocalizedUnique.en']).toEqual(1)
|
|
expect(options['collapsibleLocalizedUnique.en']).toMatchObject({
|
|
sparse: true,
|
|
unique: true,
|
|
})
|
|
expect(definitions.collapsibleTextUnique).toEqual(1)
|
|
expect(options.collapsibleTextUnique).toMatchObject({ unique: true })
|
|
})
|
|
})
|
|
|
|
describe('version indexes', () => {
|
|
let indexes
|
|
const definitions: Record<string, IndexDirection> = {}
|
|
const options: Record<string, IndexOptions> = {}
|
|
|
|
beforeEach(() => {
|
|
indexes = (payload.db as MongooseAdapter).versions['indexed-fields'].schema.indexes() as [
|
|
Record<string, IndexDirection>,
|
|
IndexOptions,
|
|
]
|
|
indexes.forEach((index) => {
|
|
const field = Object.keys(index[0])[0]
|
|
definitions[field] = index[0][field]
|
|
// eslint-disable-next-line prefer-destructuring
|
|
options[field] = index[1]
|
|
})
|
|
})
|
|
|
|
it('should have versions indexes', () => {
|
|
expect(definitions['version.text']).toEqual(1)
|
|
})
|
|
})
|
|
|
|
describe('point', () => {
|
|
let doc
|
|
const point = [7, -7]
|
|
const localized = [5, -2]
|
|
const group = { point: [1, 9] }
|
|
|
|
beforeEach(async () => {
|
|
const findDoc = await payload.find({
|
|
collection: 'point-fields',
|
|
pagination: false,
|
|
})
|
|
;[doc] = findDoc.docs
|
|
})
|
|
|
|
it('should read', async () => {
|
|
const find = await payload.find({
|
|
collection: 'point-fields',
|
|
pagination: false,
|
|
})
|
|
|
|
;[doc] = find.docs
|
|
|
|
expect(doc.point).toEqual(pointDoc.point)
|
|
expect(doc.localized).toEqual(pointDoc.localized)
|
|
expect(doc.group).toMatchObject(pointDoc.group)
|
|
})
|
|
|
|
it('should create', async () => {
|
|
doc = await payload.create({
|
|
collection: 'point-fields',
|
|
data: {
|
|
group,
|
|
localized,
|
|
point,
|
|
},
|
|
})
|
|
|
|
expect(doc.point).toEqual(point)
|
|
expect(doc.localized).toEqual(localized)
|
|
expect(doc.group).toMatchObject(group)
|
|
})
|
|
|
|
it('should not create duplicate point when unique', async () => {
|
|
// first create the point field
|
|
doc = await payload.create({
|
|
collection: 'point-fields',
|
|
data: {
|
|
group,
|
|
localized,
|
|
point,
|
|
},
|
|
})
|
|
|
|
// Now make sure we can't create a duplicate (since 'localized' is a unique field)
|
|
await expect(() =>
|
|
payload.create({
|
|
collection: 'point-fields',
|
|
data: {
|
|
group,
|
|
localized,
|
|
point,
|
|
},
|
|
}),
|
|
).rejects.toThrow(Error)
|
|
|
|
await expect(async () =>
|
|
payload.create({
|
|
collection: 'number-fields',
|
|
data: {
|
|
min: 5,
|
|
},
|
|
}),
|
|
).rejects.toThrow('The following field is invalid: min')
|
|
|
|
expect(doc.point).toEqual(point)
|
|
expect(doc.localized).toEqual(localized)
|
|
expect(doc.group).toMatchObject(group)
|
|
})
|
|
})
|
|
}
|
|
|
|
describe('unique indexes', () => {
|
|
it('should throw validation error saving on unique fields', async () => {
|
|
const data = {
|
|
text: 'a',
|
|
uniqueText: 'a',
|
|
}
|
|
await payload.create({
|
|
collection: 'indexed-fields',
|
|
data,
|
|
})
|
|
expect(async () => {
|
|
const result = await payload.create({
|
|
collection: 'indexed-fields',
|
|
data,
|
|
})
|
|
return result.error
|
|
}).toBeDefined()
|
|
})
|
|
|
|
it('should not throw validation error saving multiple null values for unique fields', async () => {
|
|
const data = {
|
|
text: 'a',
|
|
uniqueRequiredText: 'a',
|
|
// uniqueText omitted on purpose
|
|
}
|
|
await payload.create({
|
|
collection: 'indexed-fields',
|
|
data,
|
|
})
|
|
data.uniqueRequiredText = 'b'
|
|
const result = await payload.create({
|
|
collection: 'indexed-fields',
|
|
data,
|
|
})
|
|
|
|
expect(result.id).toBeDefined()
|
|
})
|
|
})
|
|
|
|
describe('array', () => {
|
|
let doc
|
|
const collection = arrayFieldsSlug
|
|
|
|
beforeEach(async () => {
|
|
doc = await payload.create({
|
|
collection,
|
|
data: {},
|
|
})
|
|
})
|
|
|
|
it('should create with ids and nested ids', async () => {
|
|
const docWithIDs = (await payload.create({
|
|
collection: groupFieldsSlug,
|
|
data: groupDoc,
|
|
})) as Partial<GroupField>
|
|
expect(docWithIDs.group.subGroup.arrayWithinGroup[0].id).toBeDefined()
|
|
})
|
|
|
|
it('should create with defaultValue', async () => {
|
|
expect(doc.items).toMatchObject(arrayDefaultValue)
|
|
expect(doc.localized).toMatchObject(arrayDefaultValue)
|
|
})
|
|
|
|
it('should create and update localized subfields with versions', async () => {
|
|
const doc = await payload.create({
|
|
collection,
|
|
data: {
|
|
items: [
|
|
{
|
|
localizedText: 'test',
|
|
text: 'required',
|
|
},
|
|
],
|
|
localized: [
|
|
{
|
|
text: 'english',
|
|
},
|
|
],
|
|
},
|
|
})
|
|
|
|
const spanish = await payload.update({
|
|
id: doc.id,
|
|
collection,
|
|
data: {
|
|
items: [
|
|
{
|
|
id: doc.items[0].id,
|
|
localizedText: 'spanish',
|
|
text: 'required',
|
|
},
|
|
],
|
|
},
|
|
locale: 'es',
|
|
})
|
|
|
|
const result = await payload.findByID({
|
|
id: doc.id,
|
|
collection,
|
|
locale: 'all',
|
|
})
|
|
|
|
expect(doc.items[0].localizedText).toStrictEqual('test')
|
|
expect(spanish.items[0].localizedText).toStrictEqual('spanish')
|
|
expect(result.items[0].localizedText.en).toStrictEqual('test')
|
|
expect(result.items[0].localizedText.es).toStrictEqual('spanish')
|
|
})
|
|
|
|
it('should create with nested array', async () => {
|
|
const subArrayText = 'something expected'
|
|
const doc = await payload.create({
|
|
collection,
|
|
data: {
|
|
items: [
|
|
{
|
|
subArray: [
|
|
{
|
|
text: subArrayText,
|
|
},
|
|
],
|
|
text: 'test',
|
|
},
|
|
],
|
|
},
|
|
})
|
|
|
|
const result = await payload.findByID({
|
|
id: doc.id,
|
|
collection,
|
|
})
|
|
|
|
expect(result.items[0]).toMatchObject({
|
|
subArray: [
|
|
{
|
|
text: subArrayText,
|
|
},
|
|
],
|
|
text: 'test',
|
|
})
|
|
expect(result.items[0].subArray[0].text).toStrictEqual(subArrayText)
|
|
})
|
|
|
|
it('should update without overwriting other locales with defaultValue', async () => {
|
|
const localized = [{ text: 'unique' }]
|
|
const enText = 'english'
|
|
const esText = 'spanish'
|
|
const { id } = await payload.create({
|
|
collection,
|
|
data: {
|
|
localized,
|
|
},
|
|
})
|
|
|
|
const enDoc = await payload.update({
|
|
id,
|
|
collection,
|
|
data: {
|
|
localized: [{ text: enText }],
|
|
},
|
|
locale: 'en',
|
|
})
|
|
|
|
const esDoc = await payload.update({
|
|
id,
|
|
collection,
|
|
data: {
|
|
localized: [{ text: esText }],
|
|
},
|
|
locale: 'es',
|
|
})
|
|
|
|
const allLocales = (await payload.findByID({
|
|
id,
|
|
collection,
|
|
locale: 'all',
|
|
})) as unknown as {
|
|
localized: {
|
|
en: unknown
|
|
es: unknown
|
|
}
|
|
}
|
|
|
|
expect(enDoc.localized[0].text).toStrictEqual(enText)
|
|
expect(esDoc.localized[0].text).toStrictEqual(esText)
|
|
expect(allLocales.localized.en[0].text).toStrictEqual(enText)
|
|
expect(allLocales.localized.es[0].text).toStrictEqual(esText)
|
|
})
|
|
})
|
|
|
|
describe('group', () => {
|
|
let document
|
|
|
|
beforeEach(async () => {
|
|
document = await payload.create({
|
|
collection: groupFieldsSlug,
|
|
data: {},
|
|
})
|
|
})
|
|
|
|
it('should create with defaultValue', async () => {
|
|
expect(document.group.defaultParent).toStrictEqual(groupDefaultValue)
|
|
expect(document.group.defaultChild).toStrictEqual(groupDefaultChild)
|
|
})
|
|
|
|
it('should not have duplicate keys', async () => {
|
|
expect(document.arrayOfGroups[0]).toMatchObject({
|
|
id: expect.any(String),
|
|
groupItem: {
|
|
text: 'Hello world',
|
|
},
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('tabs', () => {
|
|
let document
|
|
|
|
beforeEach(async () => {
|
|
document = await payload.create({
|
|
collection: tabsFieldsSlug,
|
|
data: tabsDoc,
|
|
})
|
|
})
|
|
|
|
it('should create with fields inside a named tab', async () => {
|
|
expect(document.tab.text).toStrictEqual(namedTabText)
|
|
})
|
|
|
|
it('should create with defaultValue inside a named tab', async () => {
|
|
expect(document.tab.defaultValue).toStrictEqual(namedTabDefaultValue)
|
|
})
|
|
|
|
it('should create with defaultValue inside a named tab with no other values', async () => {
|
|
expect(document.namedTabWithDefaultValue.defaultValue).toStrictEqual(namedTabDefaultValue)
|
|
})
|
|
|
|
it('should create with localized text inside a named tab', async () => {
|
|
document = await payload.findByID({
|
|
id: document.id,
|
|
collection: tabsFieldsSlug,
|
|
locale: 'all',
|
|
})
|
|
expect(document.localizedTab.en.text).toStrictEqual(localizedTextValue)
|
|
})
|
|
|
|
it('should allow access control on a named tab', async () => {
|
|
document = await payload.findByID({
|
|
id: document.id,
|
|
collection: tabsFieldsSlug,
|
|
overrideAccess: false,
|
|
})
|
|
expect(document.accessControlTab).toBeUndefined()
|
|
})
|
|
|
|
it('should allow hooks on a named tab', async () => {
|
|
const newDocument = await payload.create({
|
|
collection: tabsFieldsSlug,
|
|
data: tabsDoc,
|
|
})
|
|
expect(newDocument.hooksTab.beforeValidate).toBe(true)
|
|
expect(newDocument.hooksTab.beforeChange).toBe(true)
|
|
expect(newDocument.hooksTab.afterChange).toBe(true)
|
|
expect(newDocument.hooksTab.afterRead).toBe(true)
|
|
})
|
|
|
|
it('should return empty object for groups when no data present', async () => {
|
|
const doc = await payload.create({
|
|
collection: groupFieldsSlug,
|
|
data: groupDoc,
|
|
})
|
|
|
|
expect(doc.potentiallyEmptyGroup).toBeDefined()
|
|
})
|
|
})
|
|
|
|
describe('blocks', () => {
|
|
it('should retrieve doc with blocks', async () => {
|
|
const blockFields = await payload.find({
|
|
collection: 'block-fields',
|
|
})
|
|
|
|
expect(blockFields.docs[0].blocks[0].blockType).toEqual(blocksDoc.blocks[0].blockType)
|
|
expect(blockFields.docs[0].blocks[0].text).toEqual(blocksDoc.blocks[0].text)
|
|
|
|
expect(blockFields.docs[0].blocks[2].blockType).toEqual(blocksDoc.blocks[2].blockType)
|
|
expect(blockFields.docs[0].blocks[2].blockName).toEqual(blocksDoc.blocks[2].blockName)
|
|
expect(blockFields.docs[0].blocks[2].subBlocks[0].number).toEqual(
|
|
blocksDoc.blocks[2].subBlocks[0].number,
|
|
)
|
|
expect(blockFields.docs[0].blocks[2].subBlocks[1].text).toEqual(
|
|
blocksDoc.blocks[2].subBlocks[1].text,
|
|
)
|
|
})
|
|
|
|
it('should query based on richtext data within a block', async () => {
|
|
const blockFieldsSuccess = await payload.find({
|
|
collection: 'block-fields',
|
|
where: {
|
|
'blocks.richText.children.text': {
|
|
like: 'fun',
|
|
},
|
|
},
|
|
})
|
|
|
|
expect(blockFieldsSuccess.docs).toHaveLength(1)
|
|
|
|
const blockFieldsFail = await payload.find({
|
|
collection: 'block-fields',
|
|
where: {
|
|
'blocks.richText.children.text': {
|
|
like: 'funny',
|
|
},
|
|
},
|
|
})
|
|
|
|
expect(blockFieldsFail.docs).toHaveLength(0)
|
|
})
|
|
|
|
it('should query based on richtext data within a localized block, specifying locale', async () => {
|
|
const blockFieldsSuccess = await payload.find({
|
|
collection: 'block-fields',
|
|
where: {
|
|
'localizedBlocks.en.richText.children.text': {
|
|
like: 'fun',
|
|
},
|
|
},
|
|
})
|
|
|
|
expect(blockFieldsSuccess.docs).toHaveLength(1)
|
|
|
|
const blockFieldsFail = await payload.find({
|
|
collection: 'block-fields',
|
|
where: {
|
|
'localizedBlocks.en.richText.children.text': {
|
|
like: 'funny',
|
|
},
|
|
},
|
|
})
|
|
|
|
expect(blockFieldsFail.docs).toHaveLength(0)
|
|
})
|
|
|
|
it('should query based on richtext data within a localized block, without specifying locale', async () => {
|
|
const blockFieldsSuccess = await payload.find({
|
|
collection: 'block-fields',
|
|
where: {
|
|
'localizedBlocks.richText.children.text': {
|
|
like: 'fun',
|
|
},
|
|
},
|
|
})
|
|
|
|
expect(blockFieldsSuccess.docs).toHaveLength(1)
|
|
|
|
const blockFieldsFail = await payload.find({
|
|
collection: 'block-fields',
|
|
where: {
|
|
'localizedBlocks.richText.children.text': {
|
|
like: 'funny',
|
|
},
|
|
},
|
|
})
|
|
|
|
expect(blockFieldsFail.docs).toHaveLength(0)
|
|
})
|
|
|
|
it('should create when existing block ids are used', async () => {
|
|
const blockFields = await payload.find({
|
|
collection: 'block-fields',
|
|
limit: 1,
|
|
})
|
|
const [doc] = blockFields.docs
|
|
|
|
const result = await payload.create({
|
|
collection: 'block-fields',
|
|
data: {
|
|
...doc,
|
|
},
|
|
})
|
|
|
|
expect(result.id).toBeDefined()
|
|
})
|
|
|
|
it('should filter based on nested block fields', async () => {
|
|
await payload.create({
|
|
collection: 'block-fields',
|
|
data: {
|
|
blocks: [
|
|
{
|
|
blockType: 'content',
|
|
text: 'green',
|
|
},
|
|
],
|
|
},
|
|
})
|
|
await payload.create({
|
|
collection: 'block-fields',
|
|
data: {
|
|
blocks: [
|
|
{
|
|
blockType: 'content',
|
|
text: 'pink',
|
|
},
|
|
],
|
|
},
|
|
})
|
|
await payload.create({
|
|
collection: 'block-fields',
|
|
data: {
|
|
blocks: [
|
|
{
|
|
blockType: 'content',
|
|
text: 'green',
|
|
},
|
|
],
|
|
},
|
|
})
|
|
|
|
const blockFields = await payload.find({
|
|
collection: 'block-fields',
|
|
overrideAccess: false,
|
|
user,
|
|
where: {
|
|
and: [
|
|
{
|
|
'blocks.text': {
|
|
equals: 'green',
|
|
},
|
|
},
|
|
],
|
|
},
|
|
})
|
|
|
|
const { docs } = blockFields
|
|
expect(docs).toHaveLength(2)
|
|
})
|
|
|
|
it('should query blocks with nested relationship', async () => {
|
|
const textDoc = await payload.create({
|
|
collection: textFieldsSlug,
|
|
data: {
|
|
text: 'test',
|
|
},
|
|
})
|
|
const blockDoc = await payload.create({
|
|
collection: blockFieldsSlug,
|
|
data: {
|
|
relationshipBlocks: [
|
|
{
|
|
blockType: 'relationships',
|
|
relationship: textDoc.id,
|
|
},
|
|
],
|
|
},
|
|
})
|
|
const result = await payload.find({
|
|
collection: blockFieldsSlug,
|
|
where: {
|
|
'relationshipBlocks.relationship': { equals: textDoc.id },
|
|
},
|
|
})
|
|
|
|
expect(result.docs).toHaveLength(1)
|
|
expect(result.docs[0]).toMatchObject(blockDoc)
|
|
})
|
|
|
|
it('should query by blockType', async () => {
|
|
const text = 'blockType query test'
|
|
|
|
const hit = await payload.create({
|
|
collection: blockFieldsSlug,
|
|
data: {
|
|
blocks: [
|
|
{
|
|
blockType: 'content',
|
|
text,
|
|
},
|
|
],
|
|
},
|
|
})
|
|
const miss = await payload.create({
|
|
collection: blockFieldsSlug,
|
|
data: {
|
|
blocks: [
|
|
{
|
|
blockType: 'number',
|
|
number: 5,
|
|
},
|
|
],
|
|
duplicate: [
|
|
{
|
|
blockType: 'content',
|
|
text,
|
|
},
|
|
],
|
|
},
|
|
})
|
|
|
|
const { docs: equalsDocs } = await payload.find({
|
|
collection: blockFieldsSlug,
|
|
where: {
|
|
and: [
|
|
{
|
|
'blocks.blockType': { equals: 'content' },
|
|
},
|
|
{
|
|
'blocks.text': { equals: text },
|
|
},
|
|
],
|
|
},
|
|
})
|
|
|
|
const { docs: inDocs } = await payload.find({
|
|
collection: blockFieldsSlug,
|
|
where: {
|
|
'blocks.blockType': { in: ['content'] },
|
|
},
|
|
})
|
|
|
|
const equalsHitResult = equalsDocs.find(({ id }) => id === hit.id)
|
|
const inHitResult = inDocs.find(({ id }) => id === hit.id)
|
|
const equalsMissResult = equalsDocs.find(({ id }) => id === miss.id)
|
|
const inMissResult = inDocs.find(({ id }) => id === miss.id)
|
|
|
|
expect(equalsHitResult.id).toStrictEqual(hit.id)
|
|
expect(inHitResult.id).toStrictEqual(hit.id)
|
|
expect(equalsMissResult).toBeUndefined()
|
|
expect(inMissResult).toBeUndefined()
|
|
})
|
|
})
|
|
|
|
describe('json', () => {
|
|
it('should save json data', async () => {
|
|
const json = { foo: 'bar' }
|
|
const doc = await payload.create({
|
|
collection: 'json-fields',
|
|
data: {
|
|
json,
|
|
},
|
|
})
|
|
|
|
expect(doc.json).toStrictEqual({ foo: 'bar' })
|
|
})
|
|
|
|
it('should validate json', async () => {
|
|
await expect(async () =>
|
|
payload.create({
|
|
collection: 'json-fields',
|
|
data: {
|
|
json: '{ bad input: true }',
|
|
},
|
|
}),
|
|
).rejects.toThrow('The following field is invalid: json')
|
|
})
|
|
|
|
it('should save empty json objects', async () => {
|
|
const jsonFieldsDoc = await payload.create({
|
|
collection: 'json-fields',
|
|
data: {
|
|
json: {
|
|
state: {},
|
|
},
|
|
},
|
|
})
|
|
|
|
expect(jsonFieldsDoc.json.state).toEqual({})
|
|
|
|
const updatedJsonFieldsDoc = await payload.update({
|
|
id: jsonFieldsDoc.id,
|
|
collection: 'json-fields',
|
|
data: {
|
|
json: {
|
|
state: {},
|
|
},
|
|
},
|
|
})
|
|
|
|
expect(updatedJsonFieldsDoc.json.state).toEqual({})
|
|
})
|
|
|
|
describe('querying', () => {
|
|
let fooBar
|
|
let bazBar
|
|
|
|
beforeEach(async () => {
|
|
fooBar = await payload.create({
|
|
collection: 'json-fields',
|
|
data: {
|
|
json: { foo: 'foobar', number: 5 },
|
|
},
|
|
})
|
|
bazBar = await payload.create({
|
|
collection: 'json-fields',
|
|
data: {
|
|
json: { baz: 'bar', number: 10 },
|
|
},
|
|
})
|
|
})
|
|
|
|
it('should query nested properties - like', async () => {
|
|
const { docs } = await payload.find({
|
|
collection: 'json-fields',
|
|
where: {
|
|
'json.foo': { like: 'bar' },
|
|
},
|
|
})
|
|
|
|
const docIDs = docs.map(({ id }) => id)
|
|
|
|
expect(docIDs).toContain(fooBar.id)
|
|
expect(docIDs).not.toContain(bazBar.id)
|
|
})
|
|
|
|
it('should query nested properties - equals', async () => {
|
|
const { docs } = await payload.find({
|
|
collection: 'json-fields',
|
|
where: {
|
|
'json.foo': { equals: 'foobar' },
|
|
},
|
|
})
|
|
|
|
const notEquals = await payload.find({
|
|
collection: 'json-fields',
|
|
where: {
|
|
'json.foo': { equals: 'bar' },
|
|
},
|
|
})
|
|
|
|
const docIDs = docs.map(({ id }) => id)
|
|
|
|
expect(docIDs).toContain(fooBar.id)
|
|
expect(docIDs).not.toContain(bazBar.id)
|
|
expect(notEquals.docs).toHaveLength(0)
|
|
})
|
|
|
|
it('should query nested numbers - equals', async () => {
|
|
const { docs } = await payload.find({
|
|
collection: 'json-fields',
|
|
where: {
|
|
'json.number': { equals: 5 },
|
|
},
|
|
})
|
|
|
|
const docIDs = docs.map(({ id }) => id)
|
|
|
|
expect(docIDs).toContain(fooBar.id)
|
|
expect(docIDs).not.toContain(bazBar.id)
|
|
})
|
|
|
|
it('should query nested properties - exists', async () => {
|
|
const { docs } = await payload.find({
|
|
collection: 'json-fields',
|
|
where: {
|
|
'json.foo': { exists: true },
|
|
},
|
|
})
|
|
|
|
const docIDs = docs.map(({ id }) => id)
|
|
|
|
expect(docIDs).toContain(fooBar.id)
|
|
expect(docIDs).not.toContain(bazBar.id)
|
|
})
|
|
|
|
it('should query - exists', async () => {
|
|
const nullJSON = await payload.create({
|
|
collection: 'json-fields',
|
|
data: {},
|
|
})
|
|
const hasJSON = await payload.create({
|
|
collection: 'json-fields',
|
|
data: {
|
|
json: [],
|
|
},
|
|
})
|
|
|
|
const docsExistsFalse = await payload.find({
|
|
collection: 'json-fields',
|
|
where: {
|
|
json: { exists: false },
|
|
},
|
|
})
|
|
const docsExistsTrue = await payload.find({
|
|
collection: 'json-fields',
|
|
where: {
|
|
json: { exists: true },
|
|
},
|
|
})
|
|
|
|
const existFalseIDs = docsExistsFalse.docs.map(({ id }) => id)
|
|
const existTrueIDs = docsExistsTrue.docs.map(({ id }) => id)
|
|
|
|
expect(existFalseIDs).toContain(nullJSON.id)
|
|
expect(existTrueIDs).not.toContain(nullJSON.id)
|
|
|
|
expect(existTrueIDs).toContain(hasJSON.id)
|
|
expect(existFalseIDs).not.toContain(hasJSON.id)
|
|
})
|
|
|
|
it('exists should not return null values', async () => {
|
|
const { id } = await payload.create({
|
|
collection: 'select-fields',
|
|
data: {
|
|
select: 'one',
|
|
},
|
|
})
|
|
|
|
const existsResult = await payload.find({
|
|
collection: 'select-fields',
|
|
where: {
|
|
id: { equals: id },
|
|
select: { exists: true },
|
|
},
|
|
})
|
|
|
|
expect(existsResult.docs).toHaveLength(1)
|
|
|
|
const existsFalseResult = await payload.find({
|
|
collection: 'select-fields',
|
|
where: {
|
|
id: { equals: id },
|
|
select: { exists: false },
|
|
},
|
|
})
|
|
|
|
expect(existsFalseResult.docs).toHaveLength(0)
|
|
|
|
await payload.update({
|
|
id,
|
|
collection: 'select-fields',
|
|
data: {
|
|
select: null,
|
|
},
|
|
})
|
|
|
|
const existsTrueResult = await payload.find({
|
|
collection: 'select-fields',
|
|
where: {
|
|
id: { equals: id },
|
|
select: { exists: true },
|
|
},
|
|
})
|
|
|
|
expect(existsTrueResult.docs).toHaveLength(0)
|
|
|
|
const result = await payload.find({
|
|
collection: 'select-fields',
|
|
where: {
|
|
id: { equals: id },
|
|
select: { exists: false },
|
|
},
|
|
})
|
|
|
|
expect(result.docs).toHaveLength(1)
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('richText', () => {
|
|
it('should allow querying on rich text content', async () => {
|
|
const emptyRichTextQuery = await payload.find({
|
|
collection: 'rich-text-fields',
|
|
where: {
|
|
'richText.children.text': {
|
|
like: 'doesnt exist',
|
|
},
|
|
},
|
|
})
|
|
|
|
expect(emptyRichTextQuery.docs).toHaveLength(0)
|
|
|
|
const workingRichTextQuery = await payload.find({
|
|
collection: 'rich-text-fields',
|
|
where: {
|
|
'richText.children.text': {
|
|
like: 'hello',
|
|
},
|
|
},
|
|
})
|
|
|
|
expect(workingRichTextQuery.docs).toHaveLength(1)
|
|
})
|
|
|
|
it('should show center alignment', async () => {
|
|
const query = await payload.find({
|
|
collection: 'rich-text-fields',
|
|
where: {
|
|
'richText.children.text': {
|
|
like: 'hello',
|
|
},
|
|
},
|
|
})
|
|
|
|
expect(query.docs[0].richText[0].textAlign).toEqual('center')
|
|
})
|
|
|
|
it('should populate link relationship', async () => {
|
|
const query = await payload.find({
|
|
collection: 'rich-text-fields',
|
|
where: {
|
|
'richText.children.linkType': {
|
|
equals: 'internal',
|
|
},
|
|
},
|
|
})
|
|
|
|
const nodes = query.docs[0].richText
|
|
expect(nodes).toBeDefined()
|
|
const child = nodes.flatMap((n) => n.children).find((c) => c.doc)
|
|
expect(child).toMatchObject({
|
|
type: 'link',
|
|
linkType: 'internal',
|
|
})
|
|
expect(child.doc.relationTo).toEqual('array-fields')
|
|
|
|
if (payload.db.defaultIDType === 'number') {
|
|
expect(typeof child.doc.value.id).toBe('number')
|
|
} else {
|
|
expect(typeof child.doc.value.id).toBe('string')
|
|
}
|
|
|
|
expect(child.doc.value.items).toHaveLength(6)
|
|
})
|
|
|
|
it('should respect rich text depth parameter', async () => {
|
|
const query = `query {
|
|
RichTextFields {
|
|
docs {
|
|
richText(depth: 2)
|
|
}
|
|
}
|
|
}`
|
|
const { data } = await restClient
|
|
.GRAPHQL_POST({
|
|
body: JSON.stringify({ query }),
|
|
})
|
|
.then((res) => res.json())
|
|
const { docs }: PaginatedDocs<RichTextField> = data.RichTextFields
|
|
const uploadElement = docs[0].richText.find((el) => el.type === 'upload') as any
|
|
expect(uploadElement.value.media.filename).toStrictEqual('payload.png')
|
|
})
|
|
})
|
|
|
|
describe('relationships', () => {
|
|
it('should not crash if querying with empty in operator', async () => {
|
|
const query = await payload.find({
|
|
collection: 'relationship-fields',
|
|
where: {
|
|
'relationship.value': {
|
|
in: [],
|
|
},
|
|
},
|
|
})
|
|
|
|
expect(query.docs).toBeDefined()
|
|
})
|
|
})
|
|
|
|
describe('clearable fields - exists', () => {
|
|
it('exists should not return null values', async () => {
|
|
const { id } = await payload.create({
|
|
collection: 'select-fields',
|
|
data: {
|
|
select: 'one',
|
|
},
|
|
})
|
|
|
|
const existsResult = await payload.find({
|
|
collection: 'select-fields',
|
|
where: {
|
|
id: { equals: id },
|
|
select: { exists: true },
|
|
},
|
|
})
|
|
|
|
expect(existsResult.docs).toHaveLength(1)
|
|
|
|
const existsFalseResult = await payload.find({
|
|
collection: 'select-fields',
|
|
where: {
|
|
id: { equals: id },
|
|
select: { exists: false },
|
|
},
|
|
})
|
|
|
|
expect(existsFalseResult.docs).toHaveLength(0)
|
|
|
|
await payload.update({
|
|
id,
|
|
collection: 'select-fields',
|
|
data: {
|
|
select: null,
|
|
},
|
|
})
|
|
|
|
const existsTrueResult = await payload.find({
|
|
collection: 'select-fields',
|
|
where: {
|
|
id: { equals: id },
|
|
select: { exists: true },
|
|
},
|
|
})
|
|
|
|
expect(existsTrueResult.docs).toHaveLength(0)
|
|
|
|
const result = await payload.find({
|
|
collection: 'select-fields',
|
|
where: {
|
|
id: { equals: id },
|
|
select: { exists: false },
|
|
},
|
|
})
|
|
|
|
expect(result.docs).toHaveLength(1)
|
|
})
|
|
})
|
|
})
|