chore: adds schema path to useFieldPath provider, more passing tests

This commit is contained in:
Jarrod Flesch
2024-02-20 15:56:11 -05:00
parent 726596d568
commit a5e2fa80e8
58 changed files with 483 additions and 316 deletions

View File

@@ -42,7 +42,7 @@ const nextConfig = {
alias: {
...config.resolve.alias,
'@payloadcms/ui/scss': path.resolve(__dirname, './packages/ui/src/scss/styles.scss'),
'payload-config': process.env.PAYLOAD_CONFIG_PATH,
// 'payload-config': process.env.PAYLOAD_CONFIG_PATH,
},
fallback: {
...config.resolve.fallback,

View File

@@ -73,7 +73,6 @@
"get-port": "5.1.1",
"get-stream": "6.0.1",
"glob": "8.1.0",
"graphql-request": "6.1.0",
"husky": "^8.0.3",
"isomorphic-fetch": "3.0.0",
"jest": "29.7.0",
@@ -123,7 +122,8 @@
]
},
"dependencies": {
"@sentry/react": "^7.77.0"
"@sentry/react": "^7.77.0",
"passport-strategy": "1.0.0"
},
"pnpm": {
"overrides": {

View File

@@ -5,8 +5,27 @@ import type { PopulationsByCollection, UpdatedDocument } from './types'
import { traverseFields } from './traverseFields'
const defaultRequestHandler = ({ apiPath, endpoint, serverURL }) => {
const url = `${serverURL}${apiPath}/${endpoint}`
return fetch(url, {
credentials: 'include',
headers: {
'Content-Type': 'application/json',
},
})
}
export const mergeData = async <T>(args: {
apiRoute?: string
collectionPopulationRequestHandler?: ({
apiPath,
endpoint,
serverURL,
}: {
apiPath: string
endpoint: string
serverURL: string
}) => Promise<Response>
depth?: number
externallyUpdatedRelationship?: UpdatedDocument
fieldSchema: ReturnType<typeof fieldSchemaToJSON>
@@ -44,19 +63,16 @@ export const mergeData = async <T>(args: {
await Promise.all(
Object.entries(populationsByCollection).map(async ([collection, populations]) => {
const ids = new Set(populations.map(({ id }) => id))
const url = `${serverURL}${
apiRoute || '/api'
}/${collection}?depth=${depth}&where[id][in]=${Array.from(ids).join(',')}`
let res: PaginatedDocs
const ids = new Set(populations.map(({ id }) => id))
const requestHandler = args.collectionPopulationRequestHandler || defaultRequestHandler
try {
res = await fetch(url, {
credentials: 'include',
headers: {
'Content-Type': 'application/json',
},
res = await requestHandler({
apiPath: apiRoute || '/api',
endpoint: `${collection}?depth=${depth}&where[id][in]=${Array.from(ids).join(',')}`,
serverURL,
}).then((res) => res.json())
if (res?.docs?.length > 0) {

View File

@@ -164,6 +164,7 @@ export const updateOperation = async <TSlug extends keyof GeneratedTypes['collec
const promises = docs.map(async (doc) => {
const { id } = doc
let data = {
...newFileData,
...bulkUpdateData,
}

View File

@@ -88,7 +88,7 @@ export const updateByIDOperation = async <TSlug extends keyof GeneratedTypes['co
}
let { data } = args
const { password } = data
const password = data?.password
const shouldSaveDraft = Boolean(draftArg && collectionConfig.versions.drafts)
const shouldSavePassword = Boolean(password && collectionConfig.auth && !shouldSaveDraft)

View File

@@ -1,5 +1,4 @@
import fs from 'fs'
import path from 'path'
import type { SanitizedCollectionConfig } from '../collections/config/types'
import type { SanitizedConfig } from '../config/types'
@@ -20,7 +19,6 @@ type Args = {
export const deleteAssociatedFiles: (args: Args) => Promise<void> = async ({
collectionConfig,
config,
doc,
files = [],
overrideDelete,
@@ -28,9 +26,9 @@ export const deleteAssociatedFiles: (args: Args) => Promise<void> = async ({
}) => {
if (!collectionConfig.upload) return
if (overrideDelete || files.length > 0) {
const staticPath = path.resolve(config.paths.configDir, collectionConfig.upload.staticDir)
const { staticDir: staticPath } = collectionConfig.upload
const fileToDelete = `${staticPath}/${doc.filename}`
const fileToDelete = `${staticPath}/${doc.filename as string}`
try {
if (await fileExists(fileToDelete)) {

View File

@@ -3,7 +3,6 @@ import type { OutputInfo, Sharp, SharpOptions } from 'sharp'
import { fromBuffer } from 'file-type'
import fs from 'fs'
import mkdirp from 'mkdirp'
import path from 'path'
import sanitize from 'sanitize-filename'
import sharp from 'sharp'
@@ -38,7 +37,6 @@ type Result<T> = Promise<{
export const generateFileData = async <T>({
collection: { config: collectionConfig },
config,
data,
overwriteExistingFiles,
req,
@@ -59,10 +57,7 @@ export const generateFileData = async <T>({
const { disableLocalStorage, formatOptions, imageSizes, resizeOptions, staticDir, trimOptions } =
collectionConfig.upload
let staticPath = staticDir
if (staticDir.indexOf('/') !== 0) {
staticPath = path.resolve(config.paths.configDir, staticDir)
}
const staticPath = staticDir
if (!file && uploadEdits && data) {
const { filename, url } = data as FileData

View File

@@ -1,4 +1,4 @@
@import 'payload/scss';
@import '../../../ui/src/scss/styles.scss';
.rich-text__button {
position: relative;

View File

@@ -3,7 +3,7 @@
import type { User } from 'payload/auth'
import type { SanitizedCollectionConfig } from 'payload/types'
import { useAuth, useConfig } from 'payload/components/utilities'
import { useAuth, useConfig } from '@payloadcms/ui/providers'
import * as React from 'react'
type options = {

View File

@@ -1,4 +1,4 @@
@import 'payload/scss';
@import '../../../../../ui/src/scss/styles.scss';
.rich-text-blockquote {
&[data-slate-node='element'] {

View File

@@ -1,4 +1,4 @@
@import 'payload/scss';
@import '../../../../../../ui/src/scss/styles.scss';
.rich-text-link {
position: relative;

View File

@@ -1,4 +1,4 @@
@import 'payload/scss';
@import '../../../../../../ui/src/scss/styles.scss';
.rich-text-link-edit-modal {
&__template {

View File

@@ -9,7 +9,7 @@ import {
useEditDepth,
useTranslation,
} from '@payloadcms/ui'
import { useHotkey } from 'payload/components/hooks'
import { useHotkey } from '@payloadcms/ui/hooks'
import React, { useRef } from 'react'
import type { Props } from './types'

View File

@@ -1,4 +1,4 @@
@import 'payload/scss';
@import '../../../../../ui/src/scss/styles.scss';
.rich-text-ol {
&[data-slate-node='element'] {

View File

@@ -1,4 +1,4 @@
@import 'payload/scss';
@import '../../../../../../ui/src/scss/styles.scss';
.relationship-rich-text-button {
display: flex;

View File

@@ -1,4 +1,4 @@
@import 'payload/scss';
@import '../../../../../../ui/src/scss/styles.scss';
.rich-text-relationship {
@extend %body;

View File

@@ -1,4 +1,4 @@
@import 'payload/scss';
@import '../../../../../ui/src/scss/styles.scss';
.rich-text-ul {
&[data-slate-node='element'] {

View File

@@ -1,4 +1,4 @@
@import 'payload/scss';
@import '../../../../../../ui/src/scss/styles.scss';
.upload-rich-text-button {
display: flex;

View File

@@ -1,4 +1,4 @@
@import 'payload/scss';
@import '../../../../../../ui/src/scss/styles.scss';
.rich-text-upload {
@extend %body;

View File

@@ -1,4 +1,4 @@
@import 'payload/scss';
@import '../../../../../ui/src/scss/styles.scss';
.icon--indent-left {
height: $baseline;

View File

@@ -1,4 +1,4 @@
@import 'payload/scss';
@import '../../../../../ui/src/scss/styles.scss';
.icon--indent-right {
height: $baseline;

View File

@@ -1,4 +1,4 @@
@import 'payload/scss';
@import '../../../../../ui/src/scss/styles.scss';
.icon--link {
width: $baseline;

View File

@@ -1,4 +1,4 @@
@import 'payload/scss';
@import '../../../../../ui/src/scss/styles.scss';
.icon--relationship {
height: $baseline;

View File

@@ -1,4 +1,4 @@
@import 'payload/scss';
@import '../../../../../ui/src/scss/styles.scss';
.icon--upload {
height: $baseline;

View File

@@ -1,4 +1,4 @@
@import 'payload/scss';
@import '../../../ui/src/scss/styles.scss';
.rich-text {
margin-bottom: base(2);

View File

@@ -1,3 +1,4 @@
export { useIntersect } from '../hooks/useIntersect'
export { default as usePayloadAPI } from '../hooks/usePayloadAPI'
export { default as useThumbnail } from '../hooks/useThumbnail'
export { default as useHotkey } from '../hooks/useHotkey'

View File

@@ -18,7 +18,7 @@ const Error: React.FC<ErrorProps> = (props) => {
path: pathFromProps,
} = props
const pathFromContext = useFieldPath()
const { path: pathFromContext } = useFieldPath()
const path = pathFromProps || pathFromContext
const hasSubmitted = useFormSubmitted()

View File

@@ -1,15 +1,32 @@
'use client'
import React from 'react'
const FieldPathContext = React.createContext<string>('')
type FieldPathContextType = {
path: string
schemaPath: string
}
const FieldPathContext = React.createContext<FieldPathContextType>({
path: '',
schemaPath: '',
})
export const FieldPathProvider: React.FC<{
path: string
schemaPath: string
children: React.ReactNode
}> = (props) => {
const { children, path } = props
const { children, path, schemaPath } = props
return <FieldPathContext.Provider value={path}>{children}</FieldPathContext.Provider>
return (
<FieldPathContext.Provider
value={{
path,
schemaPath,
}}
>
{children}
</FieldPathContext.Provider>
)
}
export const useFieldPath = () => {

View File

@@ -7,7 +7,12 @@ export const RenderField: React.FC<{
Field: React.ReactNode
}> = (props) => {
const { name, Field } = props
const pathFromContext = useFieldPath()
const { path: pathFromContext, schemaPath: schemaPathFromContext } = useFieldPath()
const path = `${pathFromContext ? `${pathFromContext}.` : ''}${name || ''}`
return <FieldPathProvider path={path}>{Field}</FieldPathProvider>
const schemaPath = `${schemaPathFromContext ? `${schemaPathFromContext}.` : ''}${name || ''}`
return (
<FieldPathProvider path={path} schemaPath={schemaPath}>
{Field}
</FieldPathProvider>
)
}

View File

@@ -126,7 +126,7 @@ export const ArrayRow: React.FC<ArrayRowProps> = ({
onToggle={(collapsed) => setCollapse(row.id, collapsed)}
>
<HiddenInput name={`${path}.id`} value={row.id} />
<FieldPathProvider path={path}>
<FieldPathProvider path={path} schemaPath={parentPath}>
<RenderFields
className={`${baseClass}__fields`}
fieldMap={fieldMap}

View File

@@ -132,7 +132,7 @@ export const BlockRow: React.FC<BlockFieldProps> = ({
onToggle={(collapsed) => setCollapse(row.id, collapsed)}
>
<HiddenInput name={`${path}.id`} value={row.id} />
<FieldPathProvider path={path}>
<FieldPathProvider path={path} schemaPath={`${parentPath}.${block.slug}`}>
<RenderFields
className={`${baseClass}__fields`}
fieldMap={block.subfields}

View File

@@ -36,7 +36,7 @@ const CollapsibleField: React.FC<Props> = (props) => {
const Label = LabelFromProps || <LabelComp label={label} required={required} />
const pathFromContext = useFieldPath()
const { path: pathFromContext } = useFieldPath()
const path = pathFromProps || pathFromContext
const { i18n } = useTranslation()

View File

@@ -30,12 +30,12 @@ const Group: React.FC<Props> = (props) => {
Label: LabelFromProps,
label,
required,
name,
} = props
const Label = LabelFromProps || <LabelComp label={label} required={required} />
const path = useFieldPath()
const { path, schemaPath } = useFieldPath()
const { i18n } = useTranslation()
const isWithinCollapsible = useCollapsible()
const isWithinGroup = useGroup()
@@ -70,7 +70,7 @@ const Group: React.FC<Props> = (props) => {
width,
}}
>
<FieldPathProvider path={path}>
<FieldPathProvider path={path} schemaPath={schemaPath}>
<GroupProvider>
<div className={`${baseClass}__wrap`}>
<div className={`${baseClass}__header`}>

View File

@@ -6,4 +6,5 @@ export type Props = FormFieldBase & {
indexPath: string
permissions: FieldPermissions
hideGutter?: boolean
name?: string
}

View File

@@ -34,7 +34,7 @@ const TabsField: React.FC<Props> = (props) => {
name,
} = props
const pathFromContext = useFieldPath()
const { path: pathFromContext, schemaPath } = useFieldPath()
const path = pathFromContext || pathFromProps || name
const { getPreference, setPreference } = usePreferences()
const { preferencesKey } = useDocumentInfo()
@@ -132,7 +132,10 @@ const TabsField: React.FC<Props> = (props) => {
.join(' ')}
>
{Description}
<FieldPathProvider path={'name' in activeTabConfig ? activeTabConfig.name : ''}>
<FieldPathProvider
path={'name' in activeTabConfig ? activeTabConfig.name : ''}
schemaPath={schemaPath}
>
<RenderFields
fieldMap={activeTabConfig.subfields}
forceRender={forceRender}

View File

@@ -55,7 +55,7 @@ const Text: React.FC<Props> = (props) => {
[validate, minLength, maxLength, required],
)
const { setValue, value, path, showError } = useField({
const { setValue, value, path, showError, schemaPath } = useField({
validate: memoizedValidate,
path: pathFromProps || name,
})

View File

@@ -20,7 +20,7 @@ import { useFieldPath } from '../FieldPathProvider'
const useField = <T,>(options: Options): FieldType<T> => {
const { disableFormData = false, hasRows, validate } = options
const pathFromContext = useFieldPath()
const { path: pathFromContext, schemaPath } = useFieldPath()
const path = options.path || pathFromContext
@@ -87,6 +87,7 @@ const useField = <T,>(options: Options): FieldType<T> => {
valid: field?.valid,
value,
path,
schemaPath,
}),
[
field?.errorMessage,
@@ -99,6 +100,7 @@ const useField = <T,>(options: Options): FieldType<T> => {
value,
initialValue,
path,
schemaPath,
],
)

View File

@@ -22,4 +22,5 @@ export type FieldType<T> = {
valid?: boolean
value: T
path: string
schemaPath: string
}

View File

@@ -9,7 +9,7 @@ export const withCondition = <P extends Record<string, unknown>>(
): React.FC<P> => {
const CheckForCondition: React.FC<P> = (props) => {
const { name } = props
const pathFromContext = useFieldPath()
const { path: pathFromContext } = useFieldPath()
const path = pathFromContext || name
return (

View File

@@ -1,3 +1,4 @@
'use client'
/* eslint-disable no-shadow */
import { useModal } from '@faceless-ui/modal'
import { useCallback, useEffect } from 'react'

35
pnpm-lock.yaml generated
View File

@@ -16,6 +16,9 @@ importers:
'@sentry/react':
specifier: ^7.77.0
version: 7.101.1(react@18.2.0)
passport-strategy:
specifier: 1.0.0
version: 1.0.0
react-router-dom:
specifier: 5.3.4
version: 5.3.4(react@18.2.0)
@@ -137,9 +140,6 @@ importers:
glob:
specifier: 8.1.0
version: 8.1.0
graphql-request:
specifier: 6.1.0
version: 6.1.0(graphql@16.8.1)
husky:
specifier: ^8.0.3
version: 8.0.3
@@ -2983,14 +2983,6 @@ packages:
- supports-color
dev: true
/@graphql-typed-document-node/core@3.2.0(graphql@16.8.1):
resolution: {integrity: sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==}
peerDependencies:
graphql: ^16.8.1
dependencies:
graphql: 16.8.1
dev: true
/@hapi/hoek@9.3.0:
resolution: {integrity: sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==}
dev: false
@@ -7431,14 +7423,6 @@ packages:
cross-spawn: 7.0.3
dev: true
/cross-fetch@3.1.8:
resolution: {integrity: sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==}
dependencies:
node-fetch: 2.6.12
transitivePeerDependencies:
- encoding
dev: true
/cross-spawn@5.1.0:
resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==}
dependencies:
@@ -9713,18 +9697,6 @@ packages:
lodash.get: 4.4.2
dev: false
/graphql-request@6.1.0(graphql@16.8.1):
resolution: {integrity: sha512-p+XPfS4q7aIpKVcgmnZKhMNqhltk20hfXtkaIkTfjjmiKMJ5xrt5c743cL03y/K7y1rg3WrIC49xGiEQ4mxdNw==}
peerDependencies:
graphql: ^16.8.1
dependencies:
'@graphql-typed-document-node/core': 3.2.0(graphql@16.8.1)
cross-fetch: 3.1.8
graphql: 16.8.1
transitivePeerDependencies:
- encoding
dev: true
/graphql-scalars@1.22.2(graphql@16.8.1):
resolution: {integrity: sha512-my9FB4GtghqXqi/lWSVAOPiTzTnnEzdOXCsAC2bb5V7EFNQjVjwy3cSSbUvgYOtDuDibd+ZsCDhz+4eykYOlhQ==}
engines: {node: '>=10'}
@@ -13002,7 +12974,6 @@ packages:
/passport-strategy@1.0.0:
resolution: {integrity: sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==}
engines: {node: '>= 0.4.0'}
dev: true
/path-exists@3.0.0:
resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==}

View File

@@ -19,6 +19,27 @@ export const PostsCollection: CollectionConfig = {
relationTo: mediaSlug,
type: 'upload',
},
{
name: 'blocksField',
type: 'blocks',
blocks: [
{
slug: 'block1',
fields: [
{
type: 'group',
name: 'group1',
fields: [
{
type: 'text',
name: 'group1Text',
},
],
},
],
},
],
},
],
slug: postsSlug,
}

View File

@@ -2,11 +2,11 @@ import type { Request } from 'express'
import { Strategy } from 'passport-strategy'
import type { Payload } from '../../../packages/payload/src/payload'
import type { Payload } from '../../../packages/payload/src'
import { buildConfigWithDefaults } from '../../buildConfigWithDefaults'
import { usersSlug } from './shared'
export const slug = 'users'
export const strategyName = 'test-local'
export class CustomStrategy extends Strategy {
@@ -23,7 +23,7 @@ export class CustomStrategy extends Strategy {
}
this.ctx
.find({
collection: slug,
collection: usersSlug,
where: {
code: {
equals: req.headers.code,
@@ -36,8 +36,8 @@ export class CustomStrategy extends Strategy {
.then((users) => {
if (users.docs && users.docs.length) {
const user = users.docs[0]
user.collection = slug
user._strategy = `${slug}-${strategyName}`
user.collection = usersSlug
user._strategy = `${usersSlug}-${strategyName}`
this.success(user)
} else {
this.error(null)
@@ -52,7 +52,7 @@ export default buildConfigWithDefaults({
},
collections: [
{
slug,
slug: usersSlug,
auth: {
disableLocalStrategy: true,
strategies: [

View File

@@ -1,10 +1,15 @@
import payload from '../../../packages/payload/src'
import { initPayloadTest } from '../../helpers/configHelpers'
import { slug } from './config'
import type { Payload } from '../../../packages/payload/src'
import { getPayload } from '../../../packages/payload/src'
import { NextRESTClient } from '../../helpers/NextRESTClient'
import { startMemoryDB } from '../../startMemoryDB'
import configPromise from './config'
import { usersSlug } from './shared'
require('isomorphic-fetch')
let apiUrl
let payload: Payload
let restClient: NextRESTClient
const [code, secret, name] = ['test', 'strategy', 'Tester']
@@ -14,8 +19,9 @@ const headers = {
describe('AuthStrategies', () => {
beforeAll(async () => {
const { serverURL } = await initPayloadTest({ __dirname, init: { local: false } })
apiUrl = `${serverURL}/api`
const config = await startMemoryDB(configPromise)
payload = await getPayload({ config })
restClient = new NextRESTClient(payload.config)
})
afterAll(async () => {
@@ -26,21 +32,19 @@ describe('AuthStrategies', () => {
describe('create user', () => {
beforeAll(async () => {
await fetch(`${apiUrl}/${slug}`, {
await restClient.POST(`/${usersSlug}`, {
body: JSON.stringify({
code,
secret,
name,
}),
headers,
method: 'post',
})
})
it('should return a logged in user from /me', async () => {
const response = await fetch(`${apiUrl}/${slug}/me`, {
const response = await restClient.GET(`/${usersSlug}/me`, {
headers: {
...headers,
code,
secret,
},

View File

@@ -0,0 +1 @@
export const usersSlug = 'users'

View File

@@ -1,21 +1,25 @@
import payload from '../../../packages/payload/src'
import type { Payload } from '../../../packages/payload/src'
import { getPayload } from '../../../packages/payload/src'
import { devUser } from '../../credentials'
import { initPayloadTest } from '../../helpers/configHelpers'
import { RESTClient } from '../../helpers/rest'
import { NextRESTClient } from '../../helpers/NextRESTClient'
import { startMemoryDB } from '../../startMemoryDB'
import { collectionSlug } from './config'
import configPromise from './config'
require('isomorphic-fetch')
let client: RESTClient
let restClient: NextRESTClient
let payload: Payload
describe('Remove token from auth responses', () => {
beforeAll(async () => {
const config = await initPayloadTest({ __dirname, init: { local: false } })
const { serverURL } = config
client = new RESTClient(config, { serverURL, defaultSlug: collectionSlug })
const config = await startMemoryDB(configPromise)
payload = await getPayload({ config })
restClient = new NextRESTClient(payload.config)
await client.endpoint(`/api/${collectionSlug}/first-register`, 'post', devUser)
await client.login()
await restClient.POST(`/${collectionSlug}/first-register`, {
body: JSON.stringify(devUser),
})
await restClient.login({ slug: collectionSlug, credentials: devUser })
})
afterAll(async () => {
@@ -25,27 +29,28 @@ describe('Remove token from auth responses', () => {
})
it('should not include token in response from /login', async () => {
const { status, data } = await client.endpoint(`/api/${collectionSlug}/login`, 'post', devUser)
expect(status).toBe(200)
expect(data.token).not.toBeDefined()
expect(data.user.email).toBeDefined()
const result = await restClient.login({
slug: collectionSlug,
credentials: devUser,
})
expect(result.token).not.toBeDefined()
expect(result.user.email).toBeDefined()
})
it('should not include token in response from /me', async () => {
const { status, data } = await client.endpointWithAuth(`/api/${collectionSlug}/me`)
expect(status).toBe(200)
expect(data.token).not.toBeDefined()
expect(data.user.email).toBeDefined()
const response = await restClient.GET(`/${collectionSlug}/me`)
const result = await response.json()
expect(response.status).toBe(200)
expect(result.token).not.toBeDefined()
expect(result.user.email).toBeDefined()
})
it('should not include token in response from /refresh-token', async () => {
const { status, data } = await client.endpointWithAuth(
`/api/${collectionSlug}/refresh-token`,
'post',
)
expect(status).toBe(200)
expect(data.refreshedToken).not.toBeDefined()
expect(data.user.email).toBeDefined()
const response = await restClient.POST(`/${collectionSlug}/refresh-token`)
const result = await response.json()
expect(response.status).toBe(200)
expect(result.refreshedToken).not.toBeDefined()
expect(result.user.email).toBeDefined()
})
it('should not include token in response from /reset-password', async () => {
@@ -55,17 +60,13 @@ describe('Remove token from auth responses', () => {
disableEmail: true,
})
const { status, data } = await client.endpoint(
`/api/${collectionSlug}/reset-password`,
'post',
{
token,
password: devUser.password,
},
)
const response = await restClient.POST(`/${collectionSlug}/reset-password`, {
body: JSON.stringify({ token, password: devUser.password }),
})
const result = await response.json()
expect(status).toBe(200)
expect(data.token).not.toBeDefined()
expect(data.user.email).toBeDefined()
expect(response.status).toBe(200)
expect(result.token).not.toBeDefined()
expect(result.user.email).toBeDefined()
})
})

View File

@@ -1,18 +1,16 @@
import type { IndexDirection, IndexOptions } from 'mongoose'
import { GraphQLClient } from 'graphql-request'
import type { MongooseAdapter } from '../../packages/db-mongodb/src/index'
import type { SanitizedConfig } from '../../packages/payload/src/config/types'
import type { Payload } from '../../packages/payload/src'
import type { PaginatedDocs } from '../../packages/payload/src/database/types'
import type { RichTextField } from './payload-types'
import type { GroupField } from './payload-types'
import payload from '../../packages/payload/src'
import { getPayload } from '../../packages/payload/src'
import { devUser } from '../credentials'
import { initPayloadTest } from '../helpers/configHelpers'
import { NextRESTClient } from '../helpers/NextRESTClient'
import { isMongoose } from '../helpers/isMongoose'
import { RESTClient } from '../helpers/rest'
import configPromise from '../uploads/config'
import { startMemoryDB } from '../startMemoryDB'
import { arrayDefaultValue } from './collections/Array'
import { blocksDoc } from './collections/Blocks/shared'
import { dateDoc } from './collections/Date/shared'
@@ -28,8 +26,8 @@ import {
} 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 { GroupField } from './payload-types'
import {
arrayFieldsSlug,
blockFieldsSlug,
@@ -39,22 +37,19 @@ import {
textFieldsSlug,
} from './slugs'
let client: RESTClient
let graphQLClient: GraphQLClient
let serverURL: string
let config: SanitizedConfig
let token: string
let restClient: NextRESTClient
let user: any
let payload: Payload
describe('Fields', () => {
beforeAll(async () => {
;({ serverURL } = await initPayloadTest({ __dirname, init: { local: false } }))
config = await configPromise
client = new RESTClient(config, { defaultSlug: 'point-fields', serverURL })
const graphQLURL = `${serverURL}${config.routes.api}${config.routes.graphQL}`
graphQLClient = new GraphQLClient(graphQLURL)
token = await client.login()
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',
@@ -67,8 +62,10 @@ describe('Fields', () => {
beforeEach(async () => {
await clearAndSeedEverything(payload)
client = new RESTClient(config, { defaultSlug: 'point-fields', serverURL })
await client.login()
await restClient.login({
slug: 'users',
credentials: devUser,
})
})
describe('text', () => {
@@ -1061,14 +1058,12 @@ describe('Fields', () => {
}
}
}`
const response = await graphQLClient.request(
query,
{},
{
Authorization: `JWT ${token}`,
},
)
const { docs }: PaginatedDocs<RichTextField> = response.RichTextFields
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')
})

View File

@@ -1,8 +1,6 @@
import type { SerializedEditorState, SerializedParagraphNode } from 'lexical'
import { GraphQLClient } from 'graphql-request'
import type { SanitizedConfig } from '../../packages/payload/src/config/types'
import type { Payload } from '../../packages/payload/src'
import type { PaginatedDocs } from '../../packages/payload/src/database/types'
import type {
SerializedBlockNode,
@@ -12,10 +10,10 @@ import type {
} from '../../packages/richtext-lexical/src'
import type { LexicalField, LexicalMigrateField, RichTextField } from './payload-types'
import payload from '../../packages/payload/src'
import { initPayloadTest } from '../helpers/configHelpers'
import { RESTClient } from '../helpers/rest'
import configPromise from '../uploads/config'
import { getPayload } from '../../packages/payload/src'
import { devUser } from '../credentials'
import { NextRESTClient } from '../helpers/NextRESTClient'
import { startMemoryDB } from '../startMemoryDB'
import { arrayDoc } from './collections/Array/shared'
import { lexicalDocData } from './collections/Lexical/data'
import { lexicalMigrateDocData } from './collections/LexicalMigrate/data'
@@ -23,6 +21,7 @@ import { richTextDocData } from './collections/RichText/data'
import { generateLexicalRichText } from './collections/RichText/generateLexicalRichText'
import { textDoc } from './collections/Text/shared'
import { uploadsDoc } from './collections/Upload/shared'
import configPromise from './config'
import { clearAndSeedEverything } from './seed'
import {
arrayFieldsSlug,
@@ -33,10 +32,8 @@ import {
uploadsSlug,
} from './slugs'
let client: RESTClient
let graphQLClient: GraphQLClient
let serverURL: string
let config: SanitizedConfig
let payload: Payload
let restClient: NextRESTClient
let token: string
let createdArrayDocID: number | string = null
@@ -46,19 +43,17 @@ let createdRichTextDocID: number | string = null
describe('Lexical', () => {
beforeAll(async () => {
;({ serverURL } = await initPayloadTest({ __dirname, init: { local: false } }))
config = await configPromise
client = new RESTClient(config, { defaultSlug: richTextFieldsSlug, serverURL })
const graphQLURL = `${serverURL}${config.routes.api}${config.routes.graphQL}`
graphQLClient = new GraphQLClient(graphQLURL)
token = await client.login()
const config = await startMemoryDB(configPromise)
payload = await getPayload({ config })
restClient = new NextRESTClient(payload.config)
})
beforeEach(async () => {
await clearAndSeedEverything(payload)
client = new RESTClient(config, { defaultSlug: richTextFieldsSlug, serverURL })
await client.login()
await restClient.login({
slug: 'users',
credentials: devUser,
})
createdArrayDocID = (
await payload.find({

View File

@@ -155,7 +155,10 @@ export class NextRESTClient {
return this._PATCH(request, { params: { slug } })
}
async POST(path: ValidPath, options: RequestInit & { file?: boolean } = {}): Promise<Response> {
async POST(
path: ValidPath,
options: RequestInit & RequestOptions & { file?: boolean } = {},
): Promise<Response> {
const { url, slug, params } = this.generateRequestParts(path)
const queryParams = generateQueryString({}, params)
@@ -176,15 +179,23 @@ export class NextRESTClient {
password: string
}
slug: string
}): Promise<string> {
this.token = await this.POST(`/${slug}/login`, {
}): Promise<{ [key: string]: any }> {
const response = await this.POST(`/${slug}/login`, {
body: JSON.stringify(
credentials ? { ...credentials } : { email: devUser.email, password: devUser.password },
),
})
.then((res) => res.json())
.then((data) => data.token)
const result = await response.json()
return this.token
this.token = result.token
if (!result.token) {
// If the token is not in the response body, then we can extract it from the cookies
const setCookie = response.headers.get('Set-Cookie')
const tokenMatchResult = setCookie?.match(/payload-token=(?<token>.+?);/)
this.token = tokenMatchResult?.groups?.token
}
return result
}
}

View File

@@ -13,9 +13,9 @@ export const Media: CollectionConfig = {
type: 'text',
required: true,
},
{
name: 'caption',
type: 'richText',
},
// {
// name: 'caption',
// type: 'richText',
// },
],
}

View File

@@ -13,8 +13,6 @@ import { seed } from './seed'
import { mobileBreakpoint } from './shared'
import { formatLivePreviewURL } from './utilities/formatLivePreviewURL'
const mockModulePath = path.resolve(__dirname, './mocks/mockFSModule.js')
export default buildConfigWithDefaults({
admin: {
livePreview: {
@@ -25,16 +23,6 @@ export default buildConfigWithDefaults({
collections: ['pages', 'posts'],
globals: ['header', 'footer'],
},
webpack: (config) => ({
...config,
resolve: {
...config.resolve,
alias: {
...config?.resolve?.alias,
fs: mockModulePath,
},
},
}),
},
cors: ['http://localhost:3001'],
csrf: ['http://localhost:3001'],

View File

@@ -9,6 +9,7 @@ import { traverseRichText } from '../../packages/live-preview/src/traverseRichTe
import { getPayload } from '../../packages/payload/src'
import getFileByPath from '../../packages/payload/src/uploads/getFileByPath'
import { fieldSchemaToJSON } from '../../packages/payload/src/utilities/fieldSchemaToJSON'
import { NextRESTClient } from '../helpers/NextRESTClient'
import { startMemoryDB } from '../startMemoryDB'
import { Pages } from './collections/Pages'
import { postsSlug } from './collections/Posts'
@@ -18,6 +19,11 @@ import { tenantsSlug } from './shared'
const schemaJSON = fieldSchemaToJSON(Pages.fields)
let payload: Payload
let restClient: NextRESTClient
function collectionPopulationRequestHandler({ endpoint }: { endpoint: string }) {
return restClient.GET(`/${endpoint}`)
}
describe('Collections - Live Preview', () => {
let serverURL
@@ -29,6 +35,7 @@ describe('Collections - Live Preview', () => {
beforeAll(async () => {
const config = await startMemoryDB(configPromise)
payload = await getPayload({ config })
restClient = new NextRESTClient(payload.config)
tenant = await payload.create({
collection: tenantsSlug,
@@ -140,6 +147,7 @@ describe('Collections - Live Preview', () => {
initialData,
serverURL,
returnNumberOfRequests: true,
collectionPopulationRequestHandler,
})
expect(mergedData.title).toEqual('Test Page (Changed)')
@@ -165,6 +173,7 @@ describe('Collections - Live Preview', () => {
initialData,
serverURL,
returnNumberOfRequests: true,
collectionPopulationRequestHandler,
})
expect(mergedData.hero.media).toMatchObject(media)
@@ -183,6 +192,7 @@ describe('Collections - Live Preview', () => {
},
initialData: mergedData,
serverURL,
collectionPopulationRequestHandler,
})
expect(mergedDataWithoutUpload.hero.media).toBeFalsy()
@@ -210,6 +220,7 @@ describe('Collections - Live Preview', () => {
initialData,
serverURL,
returnNumberOfRequests: true,
collectionPopulationRequestHandler,
})
expect(merge1.richTextSlate).toHaveLength(1)
@@ -236,6 +247,7 @@ describe('Collections - Live Preview', () => {
initialData,
serverURL,
returnNumberOfRequests: true,
collectionPopulationRequestHandler,
})
expect(merge2.richTextSlate).toHaveLength(1)
@@ -295,6 +307,7 @@ describe('Collections - Live Preview', () => {
initialData,
serverURL,
returnNumberOfRequests: true,
collectionPopulationRequestHandler,
})
expect(merge1.richTextLexical.root.children).toHaveLength(2)
@@ -340,6 +353,7 @@ describe('Collections - Live Preview', () => {
initialData,
serverURL,
returnNumberOfRequests: true,
collectionPopulationRequestHandler,
})
expect(merge2.richTextLexical.root.children).toHaveLength(1)
@@ -362,6 +376,7 @@ describe('Collections - Live Preview', () => {
initialData,
serverURL,
returnNumberOfRequests: true,
collectionPopulationRequestHandler,
})
expect(merge1._numberOfRequests).toEqual(1)
@@ -383,6 +398,7 @@ describe('Collections - Live Preview', () => {
initialData,
serverURL,
returnNumberOfRequests: true,
collectionPopulationRequestHandler,
})
expect(merge1._numberOfRequests).toEqual(1)
@@ -404,6 +420,7 @@ describe('Collections - Live Preview', () => {
initialData,
serverURL,
returnNumberOfRequests: true,
collectionPopulationRequestHandler,
})
expect(merge1._numberOfRequests).toEqual(1)
@@ -428,6 +445,7 @@ describe('Collections - Live Preview', () => {
initialData,
serverURL,
returnNumberOfRequests: true,
collectionPopulationRequestHandler,
})
expect(merge1._numberOfRequests).toEqual(1)
@@ -457,6 +475,7 @@ describe('Collections - Live Preview', () => {
initialData,
serverURL,
returnNumberOfRequests: true,
collectionPopulationRequestHandler,
})
expect(merge2._numberOfRequests).toEqual(0)
@@ -482,6 +501,7 @@ describe('Collections - Live Preview', () => {
},
initialData,
serverURL,
collectionPopulationRequestHandler,
})
expect(merge1.tab.relationshipInTab).toMatchObject(testPost)
@@ -518,6 +538,7 @@ describe('Collections - Live Preview', () => {
initialData,
serverURL,
returnNumberOfRequests: true,
collectionPopulationRequestHandler,
})
expect(merge1._numberOfRequests).toEqual(1)
@@ -574,6 +595,7 @@ describe('Collections - Live Preview', () => {
initialData: merge1,
serverURL,
returnNumberOfRequests: true,
collectionPopulationRequestHandler,
})
expect(merge2._numberOfRequests).toEqual(1)
@@ -649,6 +671,7 @@ describe('Collections - Live Preview', () => {
initialData,
serverURL,
returnNumberOfRequests: true,
collectionPopulationRequestHandler,
})
expect(merge1._numberOfRequests).toEqual(2)
@@ -711,6 +734,7 @@ describe('Collections - Live Preview', () => {
initialData: merge1,
serverURL,
returnNumberOfRequests: true,
collectionPopulationRequestHandler,
})
expect(merge2._numberOfRequests).toEqual(1)
@@ -776,6 +800,7 @@ describe('Collections - Live Preview', () => {
initialData,
serverURL,
returnNumberOfRequests: true,
collectionPopulationRequestHandler,
})
expect(merge1._numberOfRequests).toEqual(2)
@@ -842,6 +867,7 @@ describe('Collections - Live Preview', () => {
initialData: merge1,
serverURL,
returnNumberOfRequests: true,
collectionPopulationRequestHandler,
})
expect(merge2._numberOfRequests).toEqual(1)
@@ -895,6 +921,7 @@ describe('Collections - Live Preview', () => {
initialData,
serverURL,
returnNumberOfRequests: true,
collectionPopulationRequestHandler,
})
expect(merge1._numberOfRequests).toEqual(0)
@@ -954,6 +981,7 @@ describe('Collections - Live Preview', () => {
initialData,
serverURL,
returnNumberOfRequests: true,
collectionPopulationRequestHandler,
})
// Check that the relationship on the first has been removed
@@ -982,6 +1010,7 @@ describe('Collections - Live Preview', () => {
initialData,
serverURL,
returnNumberOfRequests: true,
collectionPopulationRequestHandler,
})
expect(merge1._numberOfRequests).toEqual(1)
@@ -1027,6 +1056,7 @@ describe('Collections - Live Preview', () => {
externallyUpdatedRelationship,
serverURL,
returnNumberOfRequests: true,
collectionPopulationRequestHandler,
})
expect(merge2._numberOfRequests).toEqual(1)
@@ -1184,6 +1214,7 @@ describe('Collections - Live Preview', () => {
initialData,
serverURL,
returnNumberOfRequests: true,
collectionPopulationRequestHandler,
})
// Check that the blocks have been reordered
@@ -1216,6 +1247,7 @@ describe('Collections - Live Preview', () => {
initialData,
serverURL,
returnNumberOfRequests: true,
collectionPopulationRequestHandler,
})
// Check that the block has been removed
@@ -1235,6 +1267,7 @@ describe('Collections - Live Preview', () => {
initialData,
serverURL,
returnNumberOfRequests: true,
collectionPopulationRequestHandler,
})
// Check that the block has been removed

View File

@@ -7,13 +7,17 @@ import removeFiles from '../helpers/removeFiles'
import { Uploads1 } from './collections/Upload1'
import Uploads2 from './collections/Upload2'
import AdminThumbnailCol from './collections/admin-thumbnail'
import { audioSlug, enlargeSlug, mediaSlug, reduceSlug, relationSlug } from './shared'
import {
audioSlug,
enlargeSlug,
mediaSlug,
reduceSlug,
relationSlug,
unstoredMediaSlug,
} from './shared'
export default buildConfigWithDefaults({
serverURL: undefined,
paths: {
configDir: __dirname,
},
collections: [
{
slug: relationSlug,
@@ -44,7 +48,7 @@ export default buildConfigWithDefaults({
slug: 'gif-resize',
upload: {
staticURL: '/media-gif',
staticDir: './media-gif',
staticDir: path.resolve(__dirname, './media-gif'),
mimeTypes: ['image/gif'],
resizeOptions: {
position: 'center',
@@ -75,7 +79,7 @@ export default buildConfigWithDefaults({
slug: 'no-image-sizes',
upload: {
staticURL: '/no-image-sizes',
staticDir: './no-image-sizes',
staticDir: path.resolve(__dirname, './no-image-sizes'),
mimeTypes: ['image/png', 'image/jpg', 'image/jpeg'],
resizeOptions: {
position: 'center',
@@ -89,7 +93,7 @@ export default buildConfigWithDefaults({
slug: 'object-fit',
upload: {
staticURL: '/object-fit',
staticDir: './object-fit',
staticDir: path.resolve(__dirname, './object-fit'),
mimeTypes: ['image/png', 'image/jpg', 'image/jpeg'],
imageSizes: [
{
@@ -125,7 +129,7 @@ export default buildConfigWithDefaults({
upload: {
focalPoint: false,
staticURL: '/crop-only',
staticDir: './crop-only',
staticDir: path.resolve(__dirname, './crop-only'),
mimeTypes: ['image/png', 'image/jpg', 'image/jpeg'],
imageSizes: [
{
@@ -152,7 +156,7 @@ export default buildConfigWithDefaults({
upload: {
crop: false,
staticURL: '/focal-only',
staticDir: './focal-only',
staticDir: path.resolve(__dirname, './focal-only'),
mimeTypes: ['image/png', 'image/jpg', 'image/jpeg'],
imageSizes: [
{
@@ -178,7 +182,7 @@ export default buildConfigWithDefaults({
slug: mediaSlug,
upload: {
staticURL: '/media',
staticDir: './media',
staticDir: path.resolve(__dirname, './media'),
// crop: false,
// focalPoint: false,
mimeTypes: [
@@ -284,7 +288,7 @@ export default buildConfigWithDefaults({
slug: enlargeSlug,
upload: {
staticURL: '/enlarge',
staticDir: './media/enlarge',
staticDir: path.resolve(__dirname, './media/enlarge'),
mimeTypes: [
'image/png',
'image/jpg',
@@ -332,7 +336,7 @@ export default buildConfigWithDefaults({
slug: reduceSlug,
upload: {
staticURL: '/reduce',
staticDir: './media/reduce',
staticDir: path.resolve(__dirname, './media/reduce'),
mimeTypes: [
'image/png',
'image/jpg',
@@ -374,7 +378,7 @@ export default buildConfigWithDefaults({
slug: 'media-trim',
upload: {
staticURL: '/media-trim',
staticDir: './media-trim',
staticDir: path.resolve(__dirname, './media-trim'),
mimeTypes: ['image/png', 'image/jpg', 'image/jpeg'],
trimOptions: 0,
imageSizes: [
@@ -404,7 +408,7 @@ export default buildConfigWithDefaults({
fields: [],
},
{
slug: 'unstored-media',
slug: unstoredMediaSlug,
upload: {
staticURL: '/media',
disableLocalStorage: true,
@@ -416,7 +420,7 @@ export default buildConfigWithDefaults({
upload: {
// Either use another web server like `npx serve -l 4000` (http://localhost:4000) or use the static server from the previous collection to serve the media folder (http://localhost:3000/media)
staticURL: 'http://localhost:3000/media',
staticDir: './media',
staticDir: path.resolve(__dirname, './media'),
},
fields: [],
},
@@ -427,7 +431,7 @@ export default buildConfigWithDefaults({
slug: 'optional-file',
upload: {
staticURL: '/optional',
staticDir: './optional',
staticDir: path.resolve(__dirname, './optional'),
filesRequiredOnCreate: false,
},
fields: [],
@@ -436,7 +440,7 @@ export default buildConfigWithDefaults({
slug: 'required-file',
upload: {
staticURL: '/required',
staticDir: './required',
staticDir: path.resolve(__dirname, './required'),
filesRequiredOnCreate: true,
},
fields: [],

View File

@@ -12,7 +12,14 @@ import getFileByPath from '../../packages/payload/src/uploads/getFileByPath'
import { NextRESTClient } from '../helpers/NextRESTClient'
import { startMemoryDB } from '../startMemoryDB'
import configPromise from './config'
import { enlargeSlug, mediaSlug, reduceSlug, relationSlug, usersSlug } from './shared'
import {
enlargeSlug,
mediaSlug,
reduceSlug,
relationSlug,
unstoredMediaSlug,
usersSlug,
} from './shared'
const getMimeType = (
filePath: string,
@@ -214,7 +221,7 @@ describe('Collections - Uploads', () => {
formData.append('file', fileBlob)
// unstored media
const response = await restClient.POST(`/${mediaSlug}`, {
const response = await restClient.POST(`/${unstoredMediaSlug}`, {
body: formData,
file: true,
})
@@ -368,8 +375,8 @@ describe('Collections - Uploads', () => {
const expectedPath = path.join(__dirname, './media')
// Check that previously existing files were removed
expect(await fileExists(path.join(expectedPath, mediaDoc.filename))).toBe(true)
expect(await fileExists(path.join(expectedPath, mediaDoc.sizes.icon.filename))).toBe(true)
expect(await fileExists(path.join(expectedPath, mediaDoc.filename))).toBe(false)
expect(await fileExists(path.join(expectedPath, mediaDoc.sizes.icon.filename))).toBe(false)
})
it('update - update many', async () => {
@@ -404,8 +411,8 @@ describe('Collections - Uploads', () => {
const expectedPath = path.join(__dirname, './media')
// Check that previously existing files were removed
expect(await fileExists(path.join(expectedPath, mediaDoc.filename))).toBe(true)
expect(await fileExists(path.join(expectedPath, mediaDoc.sizes.icon.filename))).toBe(true)
expect(await fileExists(path.join(expectedPath, mediaDoc.filename))).toBe(false)
expect(await fileExists(path.join(expectedPath, mediaDoc.sizes.icon.filename))).toBe(false)
})
it('should remove existing media on re-upload', async () => {
@@ -462,7 +469,7 @@ describe('Collections - Uploads', () => {
// Replace the temp file with a new one
const newFilePath = path.resolve(__dirname, './temp-renamed.png')
const newFile = await getFileByPath(newFilePath)
newFile.name = 'temp-renamed.png'
newFile.name = 'temp-renamed-second.png'
const updatedMediaDoc = (await payload.update({
collection: mediaSlug,
@@ -474,6 +481,7 @@ describe('Collections - Uploads', () => {
})) as unknown as { docs: Media[] }
// Check that the replacement file was created and the old one was removed
expect(updatedMediaDoc.docs[0].filename).toEqual(newFile.name)
expect(await fileExists(path.join(expectedPath, updatedMediaDoc.docs[0].filename))).toBe(true)
expect(await fileExists(path.join(expectedPath, mediaDoc.filename))).toBe(false)
})
@@ -588,13 +596,13 @@ describe('Collections - Uploads', () => {
const formData = new FormData()
formData.append('file', await bufferToFileBlob(path.join(__dirname, './image.png')))
const response = await restClient.POST(`/${mediaSlug}`, {
body: formData,
file: true,
})
expect(response.status).toBe(200)
const { doc } = await restClient
.POST(`/${mediaSlug}`, {
body: formData,
file: true,
})
.then((res) => res.json())
const { doc } = await response.json()
const response2 = await restClient.DELETE(`/${mediaSlug}/${doc.id}`)
expect(response2.status).toBe(200)
@@ -605,12 +613,12 @@ describe('Collections - Uploads', () => {
const formData = new FormData()
formData.append('file', await bufferToFileBlob(path.join(__dirname, './image.png')))
const response = await restClient.POST(`/${mediaSlug}`, {
body: formData,
file: true,
})
expect(response.status).toBe(200)
const { doc } = await response.json()
const { doc } = await restClient
.POST(`/${mediaSlug}`, {
body: formData,
file: true,
})
.then((res) => res.json())
const { errors } = await restClient
.DELETE(`/${mediaSlug}`, {

View File

@@ -11,3 +11,5 @@ export const enlargeSlug = 'enlarge'
export const reduceSlug = 'reduce'
export const adminThumbnailSlug = 'admin-thumbnail'
export const unstoredMediaSlug = 'unstored-media'

View File

@@ -1,13 +1,10 @@
import type { CollectionConfig } from '../../../packages/payload/src/collections/config/types'
import { extractTranslations } from 'payload/dist/translations-new/extractTranslations'
import CollectionVersionButton from '../elements/CollectionVersionButton'
import CollectionVersionsButton from '../elements/CollectionVersionsButton'
import { CustomPublishButton } from '../elements/CustomSaveButton'
import { draftCollectionSlug } from '../slugs'
const labels = extractTranslations(['version:draft', 'version:published', 'version:status'])
const DraftPosts: CollectionConfig = {
access: {
read: ({ req: { user } }) => {

View File

@@ -1,20 +1,21 @@
import { GraphQLClient, request } from 'graphql-request'
import type { Payload } from '../../packages/payload/src'
import payload from '../../packages/payload/src'
import { getPayload } from '../../packages/payload/src'
import { devUser } from '../credentials'
import { initPayloadTest } from '../helpers/configHelpers'
import { NextRESTClient } from '../helpers/NextRESTClient'
import { startMemoryDB } from '../startMemoryDB'
import AutosavePosts from './collections/Autosave'
import configPromise from './config'
import AutosaveGlobal from './globals/Autosave'
import { clearAndSeedEverything } from './seed'
import { autosaveCollectionSlug, draftCollectionSlug } from './slugs'
let payload: Payload
let restClient: NextRESTClient
let collectionLocalPostID: string
let collectionLocalVersionID
let graphQLURL
let graphQLClient
let token
let collectionGraphQLPostID
@@ -34,10 +35,9 @@ const formatGraphQLID = (id: number | string) =>
describe('Versions', () => {
beforeAll(async () => {
const config = await configPromise
const { serverURL } = await initPayloadTest({ __dirname, init: { local: false } })
graphQLURL = `${serverURL}${config.routes.api}${config.routes.graphQL}`
const config = await startMemoryDB(configPromise)
payload = await getPayload({ config })
restClient = new NextRESTClient(payload.config)
const login = `
mutation {
@@ -48,10 +48,11 @@ describe('Versions', () => {
token
}
}`
const { data } = await restClient
.GRAPHQL_POST({ body: JSON.stringify({ query: login }) })
.then((res) => res.json())
const response = await request(graphQLURL, login)
token = response.loginUser.token
graphQLClient = new GraphQLClient(graphQLURL, { headers: { Authorization: `JWT ${token}` } })
token = data.loginUser.token
})
beforeEach(async () => {
@@ -67,10 +68,7 @@ describe('Versions', () => {
})
collectionLocalPostID = autosavePost.id
const updatedPost: {
_status?: string
title: string
} = await payload.update({
await payload.update({
id: collectionLocalPostID,
collection,
data: {
@@ -788,10 +786,16 @@ describe('Versions', () => {
}
}`
const response = await graphQLClient.request(query)
const { data } = await restClient
.GRAPHQL_POST({
body: JSON.stringify({ query }),
headers: {
Authorization: `JWT ${token}`,
},
})
.then((res) => res.json())
const data = response.createAutosavePost
collectionGraphQLPostID = data.id
collectionGraphQLPostID = data.createAutosavePost.id
})
describe('Create', () => {
it('should allow a new doc to be created with draft status', async () => {
@@ -808,11 +812,16 @@ describe('Versions', () => {
}
}`
const response = await graphQLClient.request(query)
const { data } = await restClient
.GRAPHQL_POST({
body: JSON.stringify({ query }),
headers: {
Authorization: `JWT ${token}`,
},
})
.then((res) => res.json())
const data = response.createAutosavePost
expect(data._status).toStrictEqual('draft')
expect(data.createAutosavePost._status).toStrictEqual('draft')
})
})
@@ -831,7 +840,12 @@ describe('Versions', () => {
createdAt
}
}`
await graphQLClient.request(update)
await restClient.GRAPHQL_POST({
body: JSON.stringify({ query: update }),
headers: {
Authorization: `JWT ${token}`,
},
})
// language=graphQL
const query = `query {
@@ -844,9 +858,16 @@ describe('Versions', () => {
}
}`
const response = await graphQLClient.request(query)
const { data } = await restClient
.GRAPHQL_POST({
body: JSON.stringify({ query }),
headers: {
Authorization: `JWT ${token}`,
},
})
.then((res) => res.json())
collectionGraphQLVersionID = response.versionsAutosavePosts.docs[0].id
collectionGraphQLVersionID = data.versionsAutosavePosts.docs[0].id
})
it('should allow read of versions by version id', async () => {
@@ -862,13 +883,18 @@ describe('Versions', () => {
}
}`
const response = await graphQLClient.request(query)
const { data } = await restClient
.GRAPHQL_POST({
body: JSON.stringify({ query }),
headers: {
Authorization: `JWT ${token}`,
},
})
.then((res) => res.json())
const data = response.versionAutosavePost
expect(data.id).toBeDefined()
expect(data.parent.id).toStrictEqual(collectionGraphQLPostID)
expect(data.version.title).toStrictEqual(updatedTitle2)
expect(data.versionAutosavePost.id).toBeDefined()
expect(data.versionAutosavePost.parent.id).toStrictEqual(collectionGraphQLPostID)
expect(data.versionAutosavePost.version.title).toStrictEqual(updatedTitle2)
})
it('should allow read of versions by querying version content', async () => {
@@ -887,10 +913,16 @@ describe('Versions', () => {
}
}`
const response = await graphQLClient.request(query)
const { data } = await restClient
.GRAPHQL_POST({
body: JSON.stringify({ query }),
headers: {
Authorization: `JWT ${token}`,
},
})
.then((res) => res.json())
const data = response.versionsAutosavePosts
const doc = data.docs[0]
const doc = data.versionsAutosavePosts.docs[0]
expect(doc.id).toBeDefined()
expect(doc.parent.id).toStrictEqual(collectionGraphQLPostID)
@@ -911,7 +943,12 @@ describe('Versions', () => {
createdAt
}
}`
await graphQLClient.request(update)
await restClient.GRAPHQL_POST({
body: JSON.stringify({ query: update }),
headers: {
Authorization: `JWT ${token}`,
},
})
// language=graphQL
const query = `query {
@@ -924,9 +961,16 @@ describe('Versions', () => {
}
}`
const response = await graphQLClient.request(query)
const { data } = await restClient
.GRAPHQL_POST({
body: JSON.stringify({ query }),
headers: {
Authorization: `JWT ${token}`,
},
})
.then((res) => res.json())
collectionGraphQLVersionID = response.versionsAutosavePosts.docs[0].id
collectionGraphQLVersionID = data.versionsAutosavePosts.docs[0].id
})
it('should allow a version to be restored', async () => {
// Update it
@@ -939,7 +983,12 @@ describe('Versions', () => {
createdAt
}
}`
await graphQLClient.request(update)
await restClient.GRAPHQL_POST({
body: JSON.stringify({ query: update }),
headers: {
Authorization: `JWT ${token}`,
},
})
// restore a versionsPost
const restore = `mutation {
@@ -948,7 +997,12 @@ describe('Versions', () => {
}
}`
await graphQLClient.request(restore)
await restClient.GRAPHQL_POST({
body: JSON.stringify({ query: restore }),
headers: {
Authorization: `JWT ${token}`,
},
})
const query = `query {
AutosavePost(id: ${formatGraphQLID(collectionGraphQLPostID)}) {
@@ -956,9 +1010,16 @@ describe('Versions', () => {
}
}`
const response = await graphQLClient.request(query)
const data = response.AutosavePost
expect(data.title).toStrictEqual(collectionGraphQLOriginalTitle)
const { data } = await restClient
.GRAPHQL_POST({
body: JSON.stringify({ query }),
headers: {
Authorization: `JWT ${token}`,
},
})
.then((res) => res.json())
expect(data.AutosavePost.title).toStrictEqual(collectionGraphQLOriginalTitle)
})
})
})
@@ -973,7 +1034,7 @@ describe('Versions', () => {
slug: globalSlug,
})
const updatedGlobal = await payload.updateGlobal({
await payload.updateGlobal({
data: {
title: title2,
},
@@ -1179,7 +1240,12 @@ describe('Versions', () => {
title
}
}`
await graphQLClient.request(update)
await restClient.GRAPHQL_POST({
body: JSON.stringify({ query: update }),
headers: {
Authorization: `JWT ${token}`,
},
})
// language=graphQL
const query = `query {
@@ -1193,9 +1259,16 @@ describe('Versions', () => {
}
}`
const response = await graphQLClient.request(query)
const { data } = await restClient
.GRAPHQL_POST({
body: JSON.stringify({ query }),
headers: {
Authorization: `JWT ${token}`,
},
})
.then((res) => res.json())
globalGraphQLVersionID = response.versionsAutosaveGlobal.docs[0].id
globalGraphQLVersionID = data.versionsAutosaveGlobal.docs[0].id
})
describe('Read', () => {
it('should allow read of versions by version id', async () => {
@@ -1209,12 +1282,17 @@ describe('Versions', () => {
}
}`
const response = await graphQLClient.request(query)
const { data } = await restClient
.GRAPHQL_POST({
body: JSON.stringify({ query }),
headers: {
Authorization: `JWT ${token}`,
},
})
.then((res) => res.json())
const data = response.versionAutosaveGlobal
expect(data.id).toBeDefined()
expect(data.version.title).toStrictEqual(globalGraphQLOriginalTitle)
expect(data.versionAutosaveGlobal.id).toBeDefined()
expect(data.versionAutosaveGlobal.version.title).toStrictEqual(globalGraphQLOriginalTitle)
})
it('should allow read of versions by querying version content', async () => {
@@ -1230,10 +1308,16 @@ describe('Versions', () => {
}
}`
const response = await graphQLClient.request(query)
const { data } = await restClient
.GRAPHQL_POST({
body: JSON.stringify({ query }),
headers: {
Authorization: `JWT ${token}`,
},
})
.then((res) => res.json())
const data = response.versionsAutosaveGlobal
const doc = data.docs[0]
const doc = data.versionsAutosaveGlobal.docs[0]
expect(doc.id).toBeDefined()
expect(doc.version.title).toStrictEqual(globalGraphQLOriginalTitle)
@@ -1249,7 +1333,12 @@ describe('Versions', () => {
}
}`
await graphQLClient.request(restore)
await restClient.GRAPHQL_POST({
body: JSON.stringify({ query: restore }),
headers: {
Authorization: `JWT ${token}`,
},
})
const query = `query {
AutosaveGlobal {
@@ -1257,9 +1346,15 @@ describe('Versions', () => {
}
}`
const response = await graphQLClient.request(query)
const data = response.AutosaveGlobal
expect(data.title).toStrictEqual(globalGraphQLOriginalTitle)
const { data } = await restClient
.GRAPHQL_POST({
body: JSON.stringify({ query }),
headers: {
Authorization: `JWT ${token}`,
},
})
.then((res) => res.json())
expect(data.AutosaveGlobal.title).toStrictEqual(globalGraphQLOriginalTitle)
})
})
})

View File

@@ -41,7 +41,7 @@
"@payloadcms/translations/api": ["./packages/translations/src/all"],
"@payloadcms/next/*": ["./packages/next/src/*"],
"@payloadcms/graphql": ["./packages/graphql/src"],
"payload-config": ["./test/uploads/config.ts"]
"payload-config": ["./test/_community/config.ts"]
}
},
"exclude": ["dist", "build", "temp", "node_modules"],