Files
payloadcms/test/relationships/int.spec.ts
Dan Ribbens b9dec2f714 Chore/next poc merge main (#5204)
* 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>
2024-02-28 13:44:17 -05:00

783 lines
21 KiB
TypeScript

import { randomBytes } from 'crypto'
import type { Payload } from '../../packages/payload/src'
import type { PayloadRequest } from '../../packages/payload/src/types'
import type {
ChainedRelation,
CustomIdNumberRelation,
CustomIdRelation,
Director,
Post,
Relation,
} from './payload-types'
import { getPayload } from '../../packages/payload/src'
import { devUser } from '../credentials'
import { NextRESTClient } from '../helpers/NextRESTClient'
import { startMemoryDB } from '../startMemoryDB'
import configPromise from './config'
import {
chainedRelSlug,
customIdNumberSlug,
customIdSlug,
defaultAccessRelSlug,
polymorphicRelationshipsSlug,
relationSlug,
slug,
treeSlug,
usersSlug,
} from './config'
let apiUrl
let jwt
let restClient: NextRESTClient
let payload: Payload
const headers = {
'Content-Type': 'application/json',
}
const { email, password } = devUser
type EasierChained = { id: string; relation: EasierChained }
describe('Relationships', () => {
beforeAll(async () => {
const config = await startMemoryDB(configPromise)
payload = await getPayload({ config })
restClient = new NextRESTClient(payload.config)
await restClient.login({ slug: usersSlug })
})
afterAll(async () => {
if (typeof payload.db.destroy === 'function') {
await payload.db.destroy(payload)
}
})
beforeEach(async () => {
await clearDocs()
})
describe('Querying', () => {
describe('Relationships', () => {
let post: Post
let relation: Relation
let filteredRelation: Relation
let defaultAccessRelation: Relation
let chained: ChainedRelation
let chained2: ChainedRelation
let chained3: ChainedRelation
let customIdRelation: CustomIdRelation
let customIdNumberRelation: CustomIdNumberRelation
let generatedCustomId: string
let generatedCustomIdNumber: number
const nameToQuery = 'name'
beforeEach(async () => {
relation = await payload.create<Relation>({
collection: relationSlug,
data: {
name: nameToQuery,
},
})
filteredRelation = await payload.create<Relation>({
collection: relationSlug,
data: {
name: nameToQuery,
disableRelation: false,
},
})
defaultAccessRelation = await payload.create<Relation>({
collection: defaultAccessRelSlug,
data: {
name: 'default access',
},
})
chained3 = await payload.create<ChainedRelation>({
collection: chainedRelSlug,
data: {
name: 'chain3',
},
})
chained2 = await payload.create<ChainedRelation>({
collection: chainedRelSlug,
data: {
name: 'chain2',
relation: chained3.id,
},
})
chained = await payload.create<ChainedRelation>({
collection: chainedRelSlug,
data: {
name: 'chain1',
relation: chained2.id,
},
})
chained3 = await payload.update<ChainedRelation>({
id: chained3.id,
collection: chainedRelSlug,
data: {
name: 'chain3',
relation: chained.id,
},
})
generatedCustomId = `custom-${randomBytes(32).toString('hex').slice(0, 12)}`
customIdRelation = await payload.create<CustomIdRelation>({
collection: customIdSlug,
data: {
id: generatedCustomId,
name: 'custom-id',
},
})
generatedCustomIdNumber = Math.floor(Math.random() * 1_000_000) + 1
customIdNumberRelation = await payload.create<CustomIdNumberRelation>({
collection: customIdNumberSlug,
data: {
id: generatedCustomIdNumber,
name: 'custom-id-number',
},
})
post = await createPost({
chainedRelation: chained.id,
customIdNumberRelation: customIdNumberRelation.id,
customIdRelation: customIdRelation.id,
defaultAccessRelation: defaultAccessRelation.id,
filteredRelation: filteredRelation.id,
maxDepthRelation: relation.id,
relationField: relation.id,
})
await createPost() // Extra post to allow asserting totalDoc count
})
it('should prevent an unauthorized population of strict access', async () => {
const doc = await restClient
.GET(`/${slug}/${post.id}`, { auth: false })
.then((res) => res.json())
expect(doc.defaultAccessRelation).toEqual(defaultAccessRelation.id)
})
it('should populate strict access when authorized', async () => {
const doc = await restClient.GET(`/${slug}/${post.id}`).then((res) => res.json())
expect(doc.defaultAccessRelation).toEqual(defaultAccessRelation)
})
it('should use filterOptions to limit relationship options', async () => {
const doc = await restClient.GET(`/${slug}/${post.id}`).then((res) => res.json())
expect(doc.filteredRelation).toMatchObject({ id: filteredRelation.id })
await restClient.PATCH(`/${relationSlug}/${filteredRelation.id}`, {
body: JSON.stringify({
disableRelation: true,
}),
})
const updatedDoc = await restClient.GET(`/${slug}/${post.id}`).then((res) => res.json())
// No change to existing relation
expect(updatedDoc.filteredRelation).toMatchObject({ id: filteredRelation.id })
// Attempt to update post with a now filtered relation
const response = await restClient.PATCH(`/${slug}/${post.id}`, {
body: JSON.stringify({
filteredRelation: filteredRelation.id,
}),
})
const result = await response.json()
expect(result.errors?.[0]).toMatchObject({
name: 'ValidationError',
data: expect.anything(),
message: expect.any(String),
})
expect(response.status).toEqual(400)
})
it('should count totalDocs correctly when using or in where query and relation contains hasMany relationship fields', async () => {
const user = (
await payload.find({
collection: 'users',
})
).docs[0]
const user2 = await payload.create({
collection: 'users',
data: {
email: '1@test.com',
password: 'fwefe',
},
})
const user3 = await payload.create({
collection: 'users',
data: {
email: '2@test.com',
password: 'fwsefe',
},
})
const user4 = await payload.create({
collection: 'users',
data: {
email: '3@test.com',
password: 'fwddsefe',
},
})
await Promise.all([
payload.create({
collection: 'movieReviews',
data: {
likes: [user3.id, user2.id, user.id, user4.id],
movieReviewer: user.id,
visibility: 'public',
},
}),
payload.create({
collection: 'movieReviews',
data: {
movieReviewer: user2.id,
visibility: 'public',
},
}),
])
const query = await payload.find({
collection: 'movieReviews',
depth: 1,
where: {
or: [
{
visibility: {
equals: 'public',
},
},
{
movieReviewer: {
equals: user.id,
},
},
],
},
})
expect(query.totalDocs).toEqual(2)
})
// https://github.com/payloadcms/payload/issues/4240
it('should allow querying by relationship id field', async () => {
/**
* This test shows something which breaks on postgres but not on mongodb.
*/
const someDirector = await payload.create({
collection: 'directors',
data: {
name: 'Quentin Tarantino',
},
})
await payload.create({
collection: 'movies',
data: {
name: 'Pulp Fiction',
},
})
await payload.create({
collection: 'movies',
data: {
name: 'Pulp Fiction',
},
})
await payload.create({
collection: 'movies',
data: {
name: 'Harry Potter',
},
})
await payload.create({
collection: 'movies',
data: {
name: 'Lord of the Rings is boring',
director: someDirector.id,
},
})
// This causes the following error:
// "Your "id" field references a column "directors"."id", but the table "directors" is not part of the query! Did you forget to join it?"
// This only happens on postgres, not on mongodb
const query = await payload.find({
collection: 'movies',
depth: 5,
limit: 1,
where: {
or: [
{
name: {
equals: 'Pulp Fiction',
},
},
{
'director.id': {
equals: someDirector.id,
},
},
],
},
})
expect(query.totalDocs).toEqual(3)
expect(query.docs).toHaveLength(1) // Due to limit: 1
})
describe('Custom ID', () => {
it('should query a custom id relation', async () => {
const { customIdRelation } = await restClient
.GET(`/${slug}/${post.id}`)
.then((res) => res.json())
expect(customIdRelation).toMatchObject({ id: generatedCustomId })
})
it('should query a custom id number relation', async () => {
const { customIdNumberRelation } = await restClient
.GET(`/${slug}/${post.id}`)
.then((res) => res.json())
expect(customIdNumberRelation).toMatchObject({ id: generatedCustomIdNumber })
})
it('should validate the format of text id relationships', async () => {
await expect(async () =>
createPost({
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error Sending bad data to test error handling
customIdRelation: 1234,
}),
).rejects.toThrow('The following field is invalid: customIdRelation')
})
it('should validate the format of number id relationships', async () => {
await expect(async () =>
createPost({
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error Sending bad data to test error handling
customIdNumberRelation: 'bad-input',
}),
).rejects.toThrow('The following field is invalid: customIdNumberRelation')
})
it('should allow update removing a relationship', async () => {
const response = await restClient.PATCH(`/${slug}/${post.id}`, {
body: JSON.stringify({
customIdRelation: null,
relationField: null,
}),
})
const doc = await response.json()
expect(response.status).toEqual(200)
expect(doc.relationField).toBeFalsy()
})
})
describe('depth', () => {
it('should populate to depth', async () => {
const doc = await restClient
.GET(`/${slug}/${post.id}`, {
query: {
depth: 2,
},
})
.then((res) => res.json())
const depth0 = doc?.chainedRelation as EasierChained
expect(depth0.id).toEqual(chained.id)
expect(depth0.relation.id).toEqual(chained2.id)
expect(depth0.relation.relation as unknown as string).toEqual(chained3.id)
expect(depth0.relation.relation).toEqual(chained3.id)
})
it('should only populate ID if depth 0', async () => {
const doc = await restClient
.GET(`/${slug}/${post.id}`, {
query: {
depth: 0,
},
})
.then((res) => res.json())
expect(doc?.chainedRelation).toEqual(chained.id)
})
it('should respect maxDepth at field level', async () => {
const doc = await restClient
.GET(`/${slug}/${post.id}`, {
query: {
depth: 1,
},
})
.then((res) => res.json())
expect(doc?.maxDepthRelation).toEqual(relation.id)
expect(doc?.maxDepthRelation).not.toHaveProperty('name')
// should not affect other fields
expect(doc?.relationField).toMatchObject({ id: relation.id, name: relation.name })
})
})
})
describe('Nested Querying', () => {
let thirdLevelID: string
let secondLevelID: string
let firstLevelID: string
beforeAll(async () => {
const thirdLevelDoc = await payload.create({
collection: 'chained',
data: {
name: 'third',
},
})
thirdLevelID = thirdLevelDoc.id
const secondLevelDoc = await payload.create({
collection: 'chained',
data: {
name: 'second',
relation: thirdLevelID,
},
})
secondLevelID = secondLevelDoc.id
const firstLevelDoc = await payload.create({
collection: 'chained',
data: {
name: 'first',
relation: secondLevelID,
},
})
firstLevelID = firstLevelDoc.id
})
it('should allow querying one level deep', async () => {
const query1 = await payload.find({
collection: 'chained',
where: {
'relation.name': {
equals: 'second',
},
},
})
expect(query1.docs).toHaveLength(1)
expect(query1.docs[0].id).toStrictEqual(firstLevelID)
const query2 = await payload.find({
collection: 'chained',
where: {
'relation.name': {
equals: 'third',
},
},
})
expect(query2.docs).toHaveLength(1)
expect(query2.docs[0].id).toStrictEqual(secondLevelID)
})
it('should allow querying two levels deep', async () => {
const query = await payload.find({
collection: 'chained',
where: {
'relation.relation.name': {
equals: 'third',
},
},
})
expect(query.docs).toHaveLength(1)
expect(query.docs[0].id).toStrictEqual(firstLevelID)
})
})
describe('Nested Querying Separate Collections', () => {
let director: Director
beforeAll(async () => {
// 1. create a director
director = await payload.create({
collection: 'directors',
data: {
name: 'Quentin Tarantino',
},
})
// 2. create a movie
const movie = await payload.create({
collection: 'movies',
data: {
name: 'Pulp Fiction',
director: director.id,
},
})
// 3. create a screening
await payload.create({
collection: 'screenings',
data: {
name: 'Pulp Fiction Screening',
movie: movie.id,
},
})
})
it('should allow querying two levels deep', async () => {
const query = await payload.find({
collection: 'screenings',
where: {
'movie.director.name': {
equals: director.name,
},
},
})
expect(query.docs).toHaveLength(1)
})
})
describe('Multiple Docs', () => {
const movieList = [
'Pulp Fiction',
'Reservoir Dogs',
'Once Upon a Time in Hollywood',
'Shrek',
'Shrek 2',
'Shrek 3',
'Scream',
'The Matrix',
'The Matrix Reloaded',
'The Matrix Revolutions',
'The Matrix Resurrections',
'The Haunting',
'The Haunting of Hill House',
'The Haunting of Bly Manor',
'Insidious',
]
beforeAll(async () => {
await Promise.all(
movieList.map(async (movie) => {
return await payload.create({
collection: 'movies',
data: {
name: movie,
},
})
}),
)
})
it('should return more than 10 docs in relationship', async () => {
const allMovies = await payload.find({
collection: 'movies',
limit: 20,
})
const movieIDs = allMovies.docs.map((doc) => doc.id)
await payload.create({
collection: 'directors',
data: {
name: 'Quentin Tarantino',
movies: movieIDs,
},
})
const director = await payload.find({
collection: 'directors',
where: {
name: {
equals: 'Quentin Tarantino',
},
},
})
expect(director.docs[0].movies.length).toBeGreaterThan(10)
})
it('should allow clearing hasMany relationships', async () => {
const fiveMovies = await payload.find({
collection: 'movies',
depth: 0,
limit: 5,
})
const movieIDs = fiveMovies.docs.map((doc) => doc.id)
const stanley = await payload.create({
collection: 'directors',
data: {
name: 'Stanley Kubrick',
movies: movieIDs,
},
})
expect(stanley.movies).toHaveLength(5)
const stanleyNeverMadeMovies = await payload.update({
id: stanley.id,
collection: 'directors',
data: {
movies: null,
},
})
expect(stanleyNeverMadeMovies.movies).toHaveLength(0)
})
})
describe('Hierarchy', () => {
it('finds 1 root item with equals', async () => {
const {
docs: [item],
totalDocs: count,
} = await payload.find({
collection: treeSlug,
where: {
parent: { equals: null },
},
})
expect(count).toBe(1)
expect(item.text).toBe('root')
})
it('finds 1 root item with exists', async () => {
const {
docs: [item],
totalDocs: count,
} = await payload.find({
collection: treeSlug,
where: {
parent: { exists: false },
},
})
expect(count).toBe(1)
expect(item.text).toBe('root')
})
it('finds 1 sub item with equals', async () => {
const {
docs: [item],
totalDocs: count,
} = await payload.find({
collection: treeSlug,
where: {
parent: { not_equals: null },
},
})
expect(count).toBe(1)
expect(item.text).toBe('sub')
})
it('finds 1 sub item with exists', async () => {
const {
docs: [item],
totalDocs: count,
} = await payload.find({
collection: treeSlug,
where: {
parent: { exists: true },
},
})
expect(count).toBe(1)
expect(item.text).toBe('sub')
})
})
})
describe('Polymorphic Relationships', () => {
it('should allow REST querying on polymorphic relationships', async () => {
const movie = await payload.create({
collection: 'movies',
data: {
name: 'Pulp Fiction 2',
},
})
await payload.create({
collection: polymorphicRelationshipsSlug,
data: {
polymorphic: {
relationTo: 'movies',
value: movie.id,
},
},
})
const queryOne = await restClient
.GET(`/${polymorphicRelationshipsSlug}`, {
query: {
where: {
and: [
{
'polymorphic.value': {
equals: movie.id,
},
},
{
'polymorphic.relationTo': {
equals: 'movies',
},
},
],
},
},
})
.then((res) => res.json())
const queryTwo = await restClient
.GET(`/${polymorphicRelationshipsSlug}`, {
query: {
where: {
and: [
{
'polymorphic.relationTo': {
equals: 'movies',
},
},
{
'polymorphic.value': {
equals: movie.id,
},
},
],
},
},
headers: {
Authorization: `JWT ${token}`,
},
})
.then((res) => res.json())
expect(queryOne.result.docs).toHaveLength(1)
expect(queryTwo.result.docs).toHaveLength(1)
})
})
})
async function createPost(overrides?: Partial<Post>) {
return payload.create({ collection: slug, data: { title: 'title', ...overrides } })
}
async function clearDocs(): Promise<void> {
await payload.delete({
collection: slug,
where: { id: { exists: true } },
})
}