chore: improve test suites, upgrade jest and playwright, add debug utilities for lexical (#4011)

* feat(richtext-lexical): 'bottom' position value for plugins

* feat: TestRecorderFeature

* chore: restructuring to seed and clear db before each test

* chore: make sure all tests pass

* chore: make sure indexes are created in seed.ts - this fixes one erroring test

* chore: speed up test runs through db snapshots

* chore: support drizzle when resetting db

* chore: simplify seeding process, by moving boilerplate db reset / snapshot logic into a wrapper function

* chore: add new seeding process to admin test suite

* chore(deps): upgrade jest and playwright

* chore: make sure mongoose-specific tests are not skipped

* chore: fix point test, which was depending on another test (that's bad!)

* chore: fix incorrect import

* chore: remove unnecessary comments

* chore: clearly label lexicalE2E test file as todo

* chore: simplify seed logic

* chore: move versions test suite to new seed system
This commit is contained in:
Alessio Gravili
2023-11-06 16:38:40 +01:00
committed by GitHub
parent 04850694c1
commit 17f7b94555
90 changed files with 2929 additions and 762 deletions

View File

@@ -1,11 +1,10 @@
import type { CollectionConfig } from '../../../packages/payload/src/collections/config/types'
import CustomEditView from '../components/views/CustomEdit'
export const customViews1Slug = 'custom-views-one'
import { customViews1CollectionSlug } from '../slugs'
export const CustomViews1: CollectionConfig = {
slug: customViews1Slug,
slug: customViews1CollectionSlug,
versions: true,
admin: {
components: {

View File

@@ -10,11 +10,11 @@ import {
customNestedTabViewPath,
customTabLabel,
customTabViewPath,
customViews2Slug,
} from '../shared'
import { customViews2CollectionSlug } from '../slugs'
export const CustomViews2: CollectionConfig = {
slug: customViews2Slug,
slug: customViews2CollectionSlug,
versions: true,
admin: {
components: {

View File

@@ -1,7 +1,9 @@
import type { CollectionConfig } from '../../../packages/payload/src/collections/config/types'
import { geoCollectionSlug } from '../slugs'
export const Geo: CollectionConfig = {
slug: 'geo',
slug: geoCollectionSlug,
fields: [
{
name: 'point',

View File

@@ -1,6 +1,6 @@
import type { CollectionConfig } from '../../../packages/payload/src/collections/config/types'
import { group1Collection1Slug } from '../shared'
import { group1Collection1Slug } from '../slugs'
export const CollectionGroup1A: CollectionConfig = {
slug: group1Collection1Slug,

View File

@@ -1,6 +1,6 @@
import type { CollectionConfig } from '../../../packages/payload/src/collections/config/types'
import { group1Collection2Slug } from '../shared'
import { group1Collection2Slug } from '../slugs'
export const CollectionGroup1B: CollectionConfig = {
slug: group1Collection2Slug,

View File

@@ -1,7 +1,9 @@
import type { CollectionConfig } from '../../../packages/payload/src/collections/config/types'
import { group2Collection1Slug } from '../slugs'
export const CollectionGroup2A: CollectionConfig = {
slug: 'group-two-collection-ones',
slug: group2Collection1Slug,
admin: {
group: 'One',
},

View File

@@ -1,7 +1,9 @@
import type { CollectionConfig } from '../../../packages/payload/src/collections/config/types'
import { group2Collection2Slug } from '../slugs'
export const CollectionGroup2B: CollectionConfig = {
slug: 'group-two-collection-twos',
slug: group2Collection2Slug,
admin: {
group: 'One',
},

View File

@@ -1,7 +1,9 @@
import type { CollectionConfig } from '../../../packages/payload/src/collections/config/types'
import { hiddenCollectionSlug } from '../slugs'
export const CollectionHidden: CollectionConfig = {
slug: 'hidden-collection',
slug: hiddenCollectionSlug,
admin: {
hidden: () => true,
},

View File

@@ -1,9 +1,9 @@
import type { CollectionConfig } from '../../../packages/payload/src/collections/config/types'
import { noApiViewCollection } from '../shared'
import { noApiViewCollectionSlug } from '../slugs'
export const CollectionNoApiView: CollectionConfig = {
slug: noApiViewCollection,
slug: noApiViewCollectionSlug,
admin: {
hideAPIURL: true,
},

View File

@@ -3,10 +3,11 @@ import type { CollectionConfig } from '../../../packages/payload/src/collections
import { slateEditor } from '../../../packages/richtext-slate/src'
import DemoUIFieldCell from '../components/DemoUIField/Cell'
import DemoUIFieldField from '../components/DemoUIField/Field'
import { postsSlug, slugPluralLabel, slugSingularLabel } from '../shared'
import { slugPluralLabel, slugSingularLabel } from '../shared'
import { postsCollectionSlug } from '../slugs'
export const Posts: CollectionConfig = {
slug: postsSlug,
slug: postsCollectionSlug,
labels: {
singular: slugSingularLabel,
plural: slugPluralLabel,

View File

@@ -1,7 +1,9 @@
import type { CollectionConfig } from '../../../packages/payload/src/collections/config/types'
import { usersCollectionSlug } from '../slugs'
export const Users: CollectionConfig = {
slug: 'users',
slug: usersCollectionSlug,
auth: true,
admin: {
useAsTitle: 'email',

View File

@@ -27,7 +27,7 @@ import { GlobalGroup1A } from './globals/Group1A'
import { GlobalGroup1B } from './globals/Group1B'
import { GlobalHidden } from './globals/Hidden'
import { GlobalNoApiView } from './globals/NoApiView'
import { seed } from './seed'
import { clearAndSeedEverything } from './seed'
import { customNestedViewPath, customViewPath } from './shared'
export default buildConfigWithDefaults({
@@ -63,6 +63,16 @@ export default buildConfigWithDefaults({
},
},
},
webpack: (config) => ({
...config,
resolve: {
...config.resolve,
alias: {
...config?.resolve?.alias,
fs: path.resolve(__dirname, './mocks/emptyModule.js'),
},
},
}),
},
i18n: {
resources: {
@@ -99,5 +109,7 @@ export default buildConfigWithDefaults({
GlobalGroup1A,
GlobalGroup1B,
],
onInit: seed,
onInit: async (payload) => {
await clearAndSeedEverything(payload)
},
})

View File

@@ -3,7 +3,6 @@ import type { Page } from '@playwright/test'
import { expect, test } from '@playwright/test'
import qs from 'qs'
import type { PayloadRequest } from '../../packages/payload/src/express/types'
import type { Post } from './payload-types'
import payload from '../../packages/payload/src'
@@ -20,6 +19,7 @@ import {
} from '../helpers'
import { AdminUrlUtil } from '../helpers/adminUrlUtil'
import { initPayloadE2E } from '../helpers/configHelpers'
import { clearAndSeedEverything } from './seed'
import {
customEditLabel,
customNestedTabViewPath,
@@ -31,17 +31,19 @@ import {
customTabViewTitle,
customViewPath,
customViewTitle,
customViews2Slug,
slugPluralLabel,
} from './shared'
import {
customViews2CollectionSlug,
globalSlug,
group1Collection1Slug,
group1GlobalSlug,
noApiViewCollection,
noApiViewGlobal,
postsSlug,
slugPluralLabel,
} from './shared'
noApiViewCollectionSlug,
noApiViewGlobalSlug,
postsCollectionSlug,
} from './slugs'
const { afterEach, beforeAll, beforeEach, describe } = test
const { beforeAll, beforeEach, describe } = test
const title = 'Title'
const description = 'Description'
@@ -54,29 +56,21 @@ describe('admin', () => {
beforeAll(async ({ browser }) => {
serverURL = (await initPayloadE2E(__dirname)).serverURL
await clearDocs() // Clear any seeded data from onInit
url = new AdminUrlUtil(serverURL, postsSlug)
customViewsURL = new AdminUrlUtil(serverURL, customViews2Slug)
url = new AdminUrlUtil(serverURL, postsCollectionSlug)
customViewsURL = new AdminUrlUtil(serverURL, customViews2CollectionSlug)
const context = await browser.newContext()
page = await context.newPage()
})
afterEach(async () => {
await clearDocs()
// clear preferences
await payload.db.deleteMany({
collection: 'payload-preferences',
req: {} as PayloadRequest,
where: {},
})
beforeEach(async () => {
await clearAndSeedEverything(payload)
})
describe('Nav', () => {
test('should nav to collection - nav', async () => {
await page.goto(url.admin)
await openNav(page)
await page.locator(`#nav-${postsSlug}`).click()
await page.locator(`#nav-${postsCollectionSlug}`).click()
expect(page.url()).toContain(url.list)
})
@@ -90,7 +84,7 @@ describe('admin', () => {
test('should navigate to collection - card', async () => {
await page.goto(url.admin)
await wait(200)
await page.locator(`#card-${postsSlug}`).click()
await page.locator(`#card-${postsCollectionSlug}`).click()
expect(page.url()).toContain(url.list)
})
@@ -147,7 +141,7 @@ describe('admin', () => {
const { id } = await createPost()
await page.goto(url.edit(id))
const collectionBreadcrumb = page.locator(
`.step-nav a[href="/admin/collections/${postsSlug}"]`,
`.step-nav a[href="/admin/collections/${postsCollectionSlug}"]`,
)
await expect(collectionBreadcrumb).toBeVisible()
await expect(collectionBreadcrumb).toHaveText(slugPluralLabel)
@@ -239,35 +233,35 @@ describe('admin', () => {
})
test('collection - should not show API tab when disabled in config', async () => {
await page.goto(url.collection(noApiViewCollection))
await page.goto(url.collection(noApiViewCollectionSlug))
await page.locator('.collection-list .table a').click()
await expect(page.locator('.doc-tabs__tabs-container')).not.toContainText('API')
})
test('collection - should not enable API route when disabled in config', async () => {
const collectionItems = await payload.find({
collection: noApiViewCollection,
collection: noApiViewCollectionSlug,
limit: 1,
})
expect(collectionItems.docs.length).toBe(1)
await page.goto(`${url.collection(noApiViewCollection)}/${collectionItems.docs[0].id}/api`)
await page.goto(`${url.collection(noApiViewGlobalSlug)}/${collectionItems.docs[0].id}/api`)
await expect(page.locator('.not-found')).toHaveCount(1)
})
test('global - should not show API tab when disabled in config', async () => {
await page.goto(url.global(noApiViewGlobal))
await page.goto(url.global(noApiViewGlobalSlug))
await expect(page.locator('.doc-tabs__tabs-container')).not.toContainText('API')
})
test('global - should not enable API route when disabled in config', async () => {
await page.goto(`${url.global(noApiViewGlobal)}/api`)
await page.goto(`${url.global(noApiViewGlobalSlug)}/api`)
await expect(page.locator('.not-found')).toHaveCount(1)
})
})
describe('ui', () => {
test('collection - should render preview button when `admin.preview` is set', async () => {
const collectionWithPreview = new AdminUrlUtil(serverURL, postsSlug)
const collectionWithPreview = new AdminUrlUtil(serverURL, postsCollectionSlug)
await page.goto(collectionWithPreview.create)
await page.locator('#field-title').fill(title)
await saveDocAndAssert(page)
@@ -406,9 +400,11 @@ describe('admin', () => {
})
test('should bulk delete', async () => {
await createPost()
await createPost()
await createPost()
// First, delete all posts created by the seed
await deleteAllPosts()
await Promise.all([createPost(), createPost(), createPost()])
await page.goto(url.list)
await page.locator('input#select-all').check()
await page.locator('.delete-documents__toggle').click()
@@ -420,9 +416,10 @@ describe('admin', () => {
})
test('should bulk update', async () => {
await createPost()
await createPost()
await createPost()
// First, delete all posts created by the seed
await deleteAllPosts()
await Promise.all([createPost(), createPost(), createPost()])
const bulkTitle = 'Bulk update title'
await page.goto(url.list)
@@ -514,6 +511,9 @@ describe('admin', () => {
})
test('search by id', async () => {
// delete all posts created by the seed
await deleteAllPosts()
const { id } = await createPost()
await page.locator('.search-filter__input').fill(id)
const tableItems = page.locator(tableRowLocator)
@@ -534,6 +534,9 @@ describe('admin', () => {
})
test('toggle columns', async () => {
// delete all posts created by the seed
await deleteAllPosts()
const columnCountLocator = 'table > thead > tr > th'
await createPost()
@@ -592,6 +595,9 @@ describe('admin', () => {
})
test('filter rows', async () => {
// delete all posts created by the seed
await deleteAllPosts()
const { id } = await createPost({ title: 'post1' })
await createPost({ title: 'post2' })
@@ -923,6 +929,9 @@ describe('admin', () => {
describe('multi-select', () => {
beforeEach(async () => {
// delete all posts created by the seed
await deleteAllPosts()
await mapAsync([...Array(3)], async () => {
await createPost()
})
@@ -962,12 +971,6 @@ describe('admin', () => {
})
describe('pagination', () => {
beforeAll(async () => {
await mapAsync([...Array(11)], async () => {
await createPost()
})
})
test('should paginate', async () => {
const pageInfo = page.locator('.collection-list__page-info')
const perPage = page.locator('.per-page')
@@ -999,13 +1002,18 @@ describe('admin', () => {
// TODO: Troubleshoot flaky suite
describe('sorting', () => {
beforeAll(async () => {
await createPost({
number: 1,
})
await createPost({
number: 2,
})
beforeEach(async () => {
// delete all posts created by the seed
await deleteAllPosts()
await Promise.all([
createPost({
number: 1,
}),
createPost({
number: 2,
}),
])
})
test('should sort', async () => {
@@ -1082,18 +1090,26 @@ describe('admin', () => {
async function createPost(overrides?: Partial<Post>): Promise<Post> {
return payload.create({
collection: postsSlug,
collection: postsCollectionSlug,
data: {
description,
title,
...overrides,
},
})
}) as unknown as Promise<Post>
}
async function clearDocs(): Promise<void> {
await payload.delete({
collection: postsSlug,
where: { id: { exists: true } },
async function deleteAllPosts() {
const posts = await payload.find({
collection: postsCollectionSlug,
limit: 100,
})
await Promise.all([
...posts.docs.map((post) => {
return payload.delete({
collection: postsCollectionSlug,
id: post.id,
})
}),
])
}

View File

@@ -1,9 +1,10 @@
import type { GlobalConfig } from '../../../packages/payload/src/globals/config/types'
import CustomEditView from '../components/views/CustomEdit'
import { customGlobalViews1GlobalSlug } from '../slugs'
export const CustomGlobalViews1: GlobalConfig = {
slug: 'custom-global-views-one',
slug: customGlobalViews1GlobalSlug,
versions: true,
admin: {
components: {

View File

@@ -4,9 +4,10 @@ import CustomTabComponent from '../components/CustomTabComponent'
import CustomDefaultEditView from '../components/views/CustomEditDefault'
import CustomView from '../components/views/CustomTab'
import CustomVersionsView from '../components/views/CustomVersions'
import { customGlobalViews2GlobalSlug } from '../slugs'
export const CustomGlobalViews2: GlobalConfig = {
slug: 'custom-global-views-two',
slug: customGlobalViews2GlobalSlug,
versions: true,
admin: {
components: {

View File

@@ -1,6 +1,6 @@
import type { GlobalConfig } from '../../../packages/payload/src/globals/config/types'
import { globalSlug } from '../shared'
import { globalSlug } from '../slugs'
export const Global: GlobalConfig = {
slug: globalSlug,

View File

@@ -1,6 +1,6 @@
import type { GlobalConfig } from '../../../packages/payload/src/globals/config/types'
import { group1GlobalSlug } from '../shared'
import { group1GlobalSlug } from '../slugs'
export const GlobalGroup1A: GlobalConfig = {
slug: group1GlobalSlug,

View File

@@ -1,7 +1,9 @@
import type { GlobalConfig } from '../../../packages/payload/src/globals/config/types'
import { group2GlobalSlug } from '../slugs'
export const GlobalGroup1B: GlobalConfig = {
slug: 'group-globals-two',
slug: group2GlobalSlug,
admin: {
group: 'Group',
},

View File

@@ -1,7 +1,9 @@
import type { GlobalConfig } from '../../../packages/payload/src/globals/config/types'
import { hiddenGlobalSlug } from '../slugs'
export const GlobalHidden: GlobalConfig = {
slug: 'hidden-global',
slug: hiddenGlobalSlug,
admin: {
hidden: () => true,
},

View File

@@ -1,8 +1,9 @@
import type { GlobalConfig } from '../../../packages/payload/src/globals/config/types'
import { noApiViewGlobal } from '../shared'
import { noApiViewGlobalSlug } from '../slugs'
export const GlobalNoApiView: GlobalConfig = {
slug: noApiViewGlobal,
slug: noApiViewGlobalSlug,
admin: {
hideAPIURL: true,
},

View File

@@ -0,0 +1 @@
export default {}

70
test/admin/seed.ts Normal file
View File

@@ -0,0 +1,70 @@
import type { Payload } from '../../packages/payload/src'
import { devUser } from '../credentials'
import { seedDB } from '../helpers/seed'
import {
collectionSlugs,
customViews1CollectionSlug,
customViews2CollectionSlug,
geoCollectionSlug,
noApiViewCollectionSlug,
postsCollectionSlug,
usersCollectionSlug,
} from './slugs'
export async function clearAndSeedEverything(_payload: Payload) {
return await seedDB({
snapshotKey: 'adminTest',
shouldResetDB: true,
collectionSlugs,
_payload,
seedFunction: async (_payload) => {
await Promise.all([
_payload.create({
collection: usersCollectionSlug,
data: {
email: devUser.email,
password: devUser.password,
},
}),
...[...Array(11)].map(() => {
_payload.create({
collection: postsCollectionSlug,
data: {
title: 'Title',
description: 'Description',
},
})
}),
_payload.create({
collection: customViews1CollectionSlug,
data: {
title: 'Custom View',
},
}),
_payload.create({
collection: customViews2CollectionSlug,
data: {
title: 'Custom View',
},
}),
_payload.create({
collection: geoCollectionSlug,
data: {
point: [7, -7],
},
}),
_payload.create({
collection: geoCollectionSlug,
data: {
point: [5, -5],
},
}),
_payload.create({
collection: noApiViewCollectionSlug,
data: {},
}),
])
},
})
}

View File

@@ -1,59 +0,0 @@
import type { Config } from '../../../packages/payload/src/config/types'
import { mapAsync } from '../../../packages/payload/src/utilities/mapAsync'
import { devUser } from '../../credentials'
import { customViews1Slug } from '../collections/CustomViews1'
import { customViews2Slug, noApiViewCollection, postsSlug } from '../shared'
export const seed: Config['onInit'] = async (payload) => {
await payload.create({
collection: 'users',
data: {
email: devUser.email,
password: devUser.password,
},
})
await mapAsync([...Array(11)], async () => {
await payload.create({
collection: postsSlug,
data: {
title: 'Title',
description: 'Description',
},
})
})
await payload.create({
collection: customViews1Slug,
data: {
title: 'Custom View',
},
})
await payload.create({
collection: customViews2Slug,
data: {
title: 'Custom View',
},
})
await payload.create({
collection: 'geo',
data: {
point: [7, -7],
},
})
await payload.create({
collection: 'geo',
data: {
point: [5, -5],
},
})
await payload.create({
collection: noApiViewCollection,
data: {},
})
}

View File

@@ -1,21 +1,7 @@
export const postsSlug = 'posts'
export const group1Collection1Slug = 'group-one-collection-ones'
export const group1Collection2Slug = 'group-one-collection-twos'
export const slugSingularLabel = 'Post'
export const slugPluralLabel = 'Posts'
export const globalSlug = 'global'
export const group1GlobalSlug = 'group-globals-one'
export const noApiViewCollection = 'collection-no-api-view'
export const noApiViewGlobal = 'global-no-api-view'
export const customViewPath = '/custom-view'
export const customViewTitle = 'Custom View'
@@ -24,8 +10,6 @@ export const customNestedViewPath = `${customViewPath}/nested-view`
export const customNestedViewTitle = 'Custom Nested View'
export const customViews2Slug = 'custom-views-two'
export const customEditLabel = 'Custom Edit Label'
export const customTabLabel = 'Custom Tab Label'

41
test/admin/slugs.ts Normal file
View File

@@ -0,0 +1,41 @@
export const usersCollectionSlug = 'users' as const
export const customViews1CollectionSlug = 'custom-views-one' as const
export const customViews2CollectionSlug = 'custom-views-two' as const
export const geoCollectionSlug = 'geo' as const
export const postsCollectionSlug = 'posts' as const
export const group1Collection1Slug = 'group-one-collection-ones' as const
export const group1Collection2Slug = 'group-one-collection-twos' as const
export const group2Collection1Slug = 'group-two-collection-ones' as const
export const group2Collection2Slug = 'group-two-collection-twos' as const
export const hiddenCollectionSlug = 'hidden-collection' as const
export const noApiViewCollectionSlug = 'collection-no-api-view' as const
export const collectionSlugs = [
usersCollectionSlug,
customViews1CollectionSlug,
customViews2CollectionSlug,
geoCollectionSlug,
postsCollectionSlug,
group1Collection1Slug,
group1Collection2Slug,
group2Collection1Slug,
group2Collection2Slug,
hiddenCollectionSlug,
noApiViewCollectionSlug,
]
export const customGlobalViews1GlobalSlug = 'custom-global-views-one' as const
export const customGlobalViews2GlobalSlug = 'custom-global-views-two' as const
export const globalSlug = 'global' as const
export const group1GlobalSlug = 'group-globals-one' as const
export const group2GlobalSlug = 'group-globals-two' as const
export const hiddenGlobalSlug = 'hidden-global' as const
export const noApiViewGlobalSlug = 'global-no-api-view'
export const globalSlugs = [
customGlobalViews1GlobalSlug,
customGlobalViews2GlobalSlug,
globalSlug,
group1GlobalSlug,
group2GlobalSlug,
hiddenGlobalSlug,
noApiViewGlobalSlug,
]

View File

@@ -1,11 +1,10 @@
import type { CollectionConfig } from '../../../../packages/payload/src/collections/config/types'
import { arrayFieldsSlug } from '../../slugs'
import { ArrayRowLabel } from './LabelComponent'
export const arrayDefaultValue = [{ text: 'row one' }, { text: 'row two' }]
export const arrayFieldsSlug = 'array-fields'
const ArrayFields: CollectionConfig = {
slug: arrayFieldsSlug,
admin: {

View File

@@ -1,6 +1,7 @@
import type { CollectionConfig } from '../../../../packages/payload/src/collections/config/types'
import type { BlockField } from '../../../../packages/payload/src/fields/config/types'
import { blockFieldsSlug } from '../../slugs'
import { AddCustomBlocks } from './components/AddCustomBlocks'
export const getBlocksFieldSeedData = (prefix?: string): any => [
@@ -149,7 +150,7 @@ export const getBlocksField = (prefix?: string): BlockField => ({
})
const BlockFields: CollectionConfig = {
slug: 'block-fields',
slug: blockFieldsSlug,
fields: [
getBlocksField(),
{

View File

@@ -1,7 +1,9 @@
import type { CollectionConfig } from '../../../../packages/payload/src/collections/config/types'
import { checkboxFieldsSlug } from '../../slugs'
const CheckboxFields: CollectionConfig = {
slug: 'checkbox-fields',
slug: checkboxFieldsSlug,
fields: [
{
name: 'checkbox',

View File

@@ -1,6 +1,8 @@
import type { CollectionConfig } from '../../../../packages/payload/src/collections/config/types'
import type { CodeField } from '../../payload-types'
import { codeFieldsSlug } from '../../slugs'
const Code: CollectionConfig = {
fields: [
{
@@ -39,7 +41,7 @@ const Code: CollectionConfig = {
type: 'code',
},
],
slug: 'code-fields',
slug: codeFieldsSlug,
}
export const codeDoc: Partial<CodeField> = {

View File

@@ -1,7 +1,7 @@
import type { CollectionConfig } from '../../../../packages/payload/src/collections/config/types'
import { collapsibleFieldsSlug } from '../../slugs'
import { CollapsibleLabelComponent } from './LabelComponent'
import { collapsibleFieldsSlug } from './shared'
const CollapsibleFields: CollectionConfig = {
slug: collapsibleFieldsSlug,

View File

@@ -1 +0,0 @@
export const collapsibleFieldsSlug = 'collapsible-fields'

View File

@@ -1,7 +1,9 @@
import type { CollectionConfig } from '../../../../packages/payload/src/collections/config/types'
import { conditionalLogicSlug } from '../../slugs'
const ConditionalLogic: CollectionConfig = {
slug: 'conditional-logic',
slug: conditionalLogicSlug,
admin: {
useAsTitle: 'text',
},

View File

@@ -1,9 +1,11 @@
import type { CollectionConfig } from '../../../../packages/payload/src/collections/config/types'
import { dateFieldsSlug } from '../../slugs'
export const defaultText = 'default-text'
const DateFields: CollectionConfig = {
slug: 'date-fields',
slug: dateFieldsSlug,
admin: {
useAsTitle: 'default',
},

View File

@@ -1,8 +1,9 @@
import type { CollectionConfig } from '../../../../packages/payload/src/collections/config/types'
import { groupFieldsSlug } from '../../slugs'
export const groupDefaultValue = 'set from parent'
export const groupDefaultChild = 'child takes priority'
export const groupFieldsSlug = 'group-fields'
const GroupFields: CollectionConfig = {
slug: groupFieldsSlug,

View File

@@ -4,6 +4,8 @@ import type {
} from '../../../../packages/payload/src/collections/config/types'
import type { IndexedField } from '../../payload-types'
import { indexedFieldsSlug } from '../../slugs'
const beforeDuplicate: BeforeDuplicate<IndexedField> = ({ data }) => {
return {
...data,
@@ -22,7 +24,7 @@ const beforeDuplicate: BeforeDuplicate<IndexedField> = ({ data }) => {
}
const IndexedFields: CollectionConfig = {
slug: 'indexed-fields',
slug: indexedFieldsSlug,
// used to assert that versions also get indexes
admin: {
hooks: {

View File

@@ -1,5 +1,7 @@
import type { CollectionConfig } from '../../../../packages/payload/src/collections/config/types'
import { jsonFieldsSlug } from '../../slugs'
type JSONField = {
createdAt: string
id: string
@@ -17,7 +19,7 @@ const JSON: CollectionConfig = {
type: 'json',
},
],
slug: 'json-fields',
slug: jsonFieldsSlug,
versions: {
maxPerDoc: 1,
},

View File

@@ -0,0 +1,8 @@
import { generateLexicalRichText } from './generateLexicalRichText'
import { payloadPluginLexicalData } from './generatePayloadPluginLexicalData'
export const lexicalRichTextDoc = {
title: 'Rich Text',
richTextLexicalCustomFields: generateLexicalRichText(),
richTextLexicalWithLexicalPluginData: payloadPluginLexicalData,
}

View File

@@ -5,10 +5,12 @@ import {
HTMLConverterFeature,
LexicalPluginToLexicalFeature,
LinkFeature,
TestRecorderFeature,
TreeviewFeature,
UploadFeature,
lexicalEditor,
} from '../../../../packages/richtext-lexical/src'
import { lexicalFieldsSlug } from '../../slugs'
import {
RelationshipBlock,
RichTextBlock,
@@ -21,7 +23,7 @@ import { generateLexicalRichText } from './generateLexicalRichText'
import { payloadPluginLexicalData } from './generatePayloadPluginLexicalData'
export const LexicalFields: CollectionConfig = {
slug: 'lexical-fields',
slug: lexicalFieldsSlug,
admin: {
useAsTitle: 'title',
listSearchableFields: ['title', 'richTextLexicalCustomFields'],
@@ -35,6 +37,27 @@ export const LexicalFields: CollectionConfig = {
type: 'text',
required: true,
},
{
name: 'richTextLexicalSimple',
type: 'richText',
editor: lexicalEditor({
features: ({ defaultFeatures }) => [
...defaultFeatures,
TestRecorderFeature(),
TreeviewFeature(),
BlocksFeature({
blocks: [
RichTextBlock,
TextBlock,
UploadAndRichTextBlock,
SelectFieldBlock,
RelationshipBlock,
SubBlockBlock,
],
}),
],
}),
},
{
name: 'richTextLexicalCustomFields',
type: 'richText',
@@ -42,6 +65,7 @@ export const LexicalFields: CollectionConfig = {
editor: lexicalEditor({
features: ({ defaultFeatures }) => [
...defaultFeatures,
TestRecorderFeature(),
TreeviewFeature(),
HTMLConverterFeature(),
LinkFeature({
@@ -126,9 +150,3 @@ export const LexicalFields: CollectionConfig = {
},
],
}
export const LexicalRichTextDoc = {
title: 'Rich Text',
richTextLexicalCustomFields: generateLexicalRichText(),
richTextLexicalWithLexicalPluginData: payloadPluginLexicalData,
}

View File

@@ -0,0 +1,958 @@
export const payloadPluginLexicalData = {
words: 49,
preview:
'paragraph text bold italic underline and all subscript superscript code internal link external link…',
comments: [],
characters: 493,
jsonContent: {
root: {
type: 'root',
format: '',
indent: 0,
version: 1,
children: [
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'paragraph text ',
type: 'text',
version: 1,
},
{
detail: 0,
format: 1,
mode: 'normal',
style: '',
text: 'bold',
type: 'text',
version: 1,
},
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: ' ',
type: 'text',
version: 1,
},
{
detail: 0,
format: 2,
mode: 'normal',
style: '',
text: 'italic',
type: 'text',
version: 1,
},
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: ' ',
type: 'text',
version: 1,
},
{
detail: 0,
format: 8,
mode: 'normal',
style: '',
text: 'underline',
type: 'text',
version: 1,
},
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: ' and ',
type: 'text',
version: 1,
},
{
detail: 0,
format: 11,
mode: 'normal',
style: '',
text: 'all',
type: 'text',
version: 1,
},
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: ' ',
type: 'text',
version: 1,
},
{
detail: 0,
format: 32,
mode: 'normal',
style: '',
text: 'subscript',
type: 'text',
version: 1,
},
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: ' ',
type: 'text',
version: 1,
},
{
detail: 0,
format: 64,
mode: 'normal',
style: '',
text: 'superscript',
type: 'text',
version: 1,
},
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: ' ',
type: 'text',
version: 1,
},
{
detail: 0,
format: 16,
mode: 'normal',
style: '',
text: 'code',
type: 'text',
version: 1,
},
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: ' ',
type: 'text',
version: 1,
},
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'internal link',
type: 'text',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'link',
version: 2,
attributes: {
newTab: true,
linkType: 'internal',
doc: {
value: '{{TEXT_DOC_ID}}',
relationTo: 'text-fields',
data: {}, // populated data
},
text: 'internal link',
},
},
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: ' ',
type: 'text',
version: 1,
},
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'external link',
type: 'text',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'link',
version: 2,
attributes: {
newTab: true,
nofollow: false,
url: 'https://fewfwef.de',
linkType: 'custom',
text: 'external link',
},
},
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: ' s. ',
type: 'text',
version: 1,
},
{
detail: 0,
format: 4,
mode: 'normal',
style: '',
text: 'strikethrough',
type: 'text',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'paragraph',
version: 1,
},
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'heading 1',
type: 'text',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'heading',
version: 1,
tag: 'h1',
},
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'heading 2',
type: 'text',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'heading',
version: 1,
tag: 'h2',
},
{
children: [
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'bullet list ',
type: 'text',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'listitem',
version: 1,
value: 1,
},
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'item 2',
type: 'text',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'listitem',
version: 1,
value: 2,
},
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'item 3',
type: 'text',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'listitem',
version: 1,
value: 3,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'list',
version: 1,
listType: 'bullet',
start: 1,
tag: 'ul',
},
{
children: [
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'ordered list',
type: 'text',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'listitem',
version: 1,
value: 1,
},
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'item 2',
type: 'text',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'listitem',
version: 1,
value: 2,
},
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'item 3',
type: 'text',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'listitem',
version: 1,
value: 3,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'list',
version: 1,
listType: 'number',
start: 1,
tag: 'ol',
},
{
children: [
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'check list',
type: 'text',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'listitem',
version: 1,
value: 1,
},
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'item 2',
type: 'text',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'listitem',
version: 1,
value: 2,
},
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'item 3',
type: 'text',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'listitem',
version: 1,
value: 3,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'list',
version: 1,
listType: 'check',
start: 1,
tag: 'ul',
},
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'quoteeee',
type: 'text',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'quote',
version: 1,
},
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'code block line ',
type: 'code-highlight',
version: 1,
},
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: '1',
type: 'code-highlight',
version: 1,
highlightType: 'number',
},
{
type: 'linebreak',
version: 1,
},
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'code block line ',
type: 'code-highlight',
version: 1,
},
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: '2',
type: 'code-highlight',
version: 1,
highlightType: 'number',
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'code',
version: 1,
language: 'javascript',
},
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'Upload:',
type: 'text',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'paragraph',
version: 1,
},
{
children: [
{
type: 'upload',
version: 1,
rawImagePayload: {
value: {
id: '{{UPLOAD_DOC_ID}}',
},
relationTo: 'uploads',
},
caption: {
editorState: {
root: {
children: [
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'upload caption',
type: 'text',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'paragraph',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'root',
version: 1,
},
},
},
showCaption: true,
data: {}, // populated upload data
},
],
direction: null,
format: '',
indent: 0,
type: 'paragraph',
version: 1,
},
{
children: [],
direction: null,
format: '',
indent: 0,
type: 'paragraph',
version: 1,
},
{
children: [
{
children: [
{
children: [
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: '2x2 table top left',
type: 'text',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'paragraph',
version: 1,
},
],
direction: null,
format: '',
indent: 0,
type: 'tablecell',
version: 1,
colSpan: 1,
rowSpan: 1,
backgroundColor: null,
headerState: 3,
},
{
children: [
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: '2x2 table top right',
type: 'text',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'paragraph',
version: 1,
},
],
direction: null,
format: '',
indent: 0,
type: 'tablecell',
version: 1,
colSpan: 1,
rowSpan: 1,
backgroundColor: null,
headerState: 1,
},
],
direction: null,
format: '',
indent: 0,
type: 'tablerow',
version: 1,
},
{
children: [
{
children: [
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: '2x2 table bottom left',
type: 'text',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'paragraph',
version: 1,
},
],
direction: null,
format: '',
indent: 0,
type: 'tablecell',
version: 1,
colSpan: 1,
rowSpan: 1,
backgroundColor: null,
headerState: 2,
},
{
children: [
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: '2x2 table bottom right',
type: 'text',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'paragraph',
version: 1,
},
],
direction: null,
format: '',
indent: 0,
type: 'tablecell',
version: 1,
colSpan: 1,
rowSpan: 1,
backgroundColor: null,
headerState: 0,
},
],
direction: null,
format: '',
indent: 0,
type: 'tablerow',
version: 1,
},
],
direction: null,
format: '',
indent: 0,
type: 'table',
version: 1,
},
{
rows: [
{
cells: [
{
colSpan: 1,
id: 'kafuj',
json: '{"root":{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1}],"direction":null,"format":"","indent":0,"type":"root","version":1}}',
type: 'header',
width: null,
},
{
colSpan: 1,
id: 'iussu',
json: '{"root":{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1}],"direction":null,"format":"","indent":0,"type":"root","version":1}}',
type: 'header',
width: null,
},
],
height: null,
id: 'tnied',
},
{
cells: [
{
colSpan: 1,
id: 'hpnnv',
json: '{"root":{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1}],"direction":null,"format":"","indent":0,"type":"root","version":1}}',
type: 'header',
width: null,
},
{
colSpan: 1,
id: 'ndteg',
json: '{"root":{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1}],"direction":null,"format":"","indent":0,"type":"root","version":1}}',
type: 'normal',
width: null,
},
],
height: null,
id: 'rxyey',
},
{
cells: [
{
colSpan: 1,
id: 'rtueq',
json: '{"root":{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1}],"direction":null,"format":"","indent":0,"type":"root","version":1}}',
type: 'header',
width: null,
},
{
colSpan: 1,
id: 'vrzoi',
json: '{"root":{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1}],"direction":null,"format":"","indent":0,"type":"root","version":1}}',
type: 'normal',
width: null,
},
],
height: null,
id: 'qzglv',
},
],
type: 'tablesheet',
version: 1,
},
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'youtube:',
type: 'text',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'paragraph',
version: 1,
},
{
format: '',
type: 'youtube',
version: 1,
videoID: '3Nwt3qu0_UY',
},
{
children: [
{
equation: '3+3',
inline: true,
type: 'equation',
version: 1,
},
],
direction: null,
format: '',
indent: 0,
type: 'paragraph',
version: 1,
},
{
children: [
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'collapsible title',
type: 'text',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'collapsible-title',
version: 1,
},
{
children: [
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'collabsible conteent',
type: 'text',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'paragraph',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'collapsible-content',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'collapsible-container',
version: 1,
open: true,
},
{
children: [],
direction: null,
format: '',
indent: 0,
type: 'paragraph',
version: 1,
},
{
type: 'horizontalrule',
version: 1,
},
],
direction: 'ltr',
},
},
}

View File

@@ -0,0 +1,73 @@
import type { CollectionConfig } from '../../../../packages/payload/src/collections/config/types'
import {
LexicalPluginToLexicalFeature,
LinkFeature,
TreeviewFeature,
UploadFeature,
lexicalEditor,
} from '../../../../packages/richtext-lexical/src'
import { lexicalMigrateFieldsSlug } from '../../slugs'
import { payloadPluginLexicalData } from './generatePayloadPluginLexicalData'
export const LexicalMigrateFields: CollectionConfig = {
slug: lexicalMigrateFieldsSlug,
admin: {
useAsTitle: 'title',
listSearchableFields: ['title', 'richTextLexicalCustomFields'],
},
access: {
read: () => true,
},
fields: [
{
name: 'title',
type: 'text',
required: true,
},
{
name: 'richTextLexicalWithLexicalPluginData',
type: 'richText',
editor: lexicalEditor({
features: ({ defaultFeatures }) => [
...defaultFeatures,
LexicalPluginToLexicalFeature(),
TreeviewFeature(),
LinkFeature({
fields: [
{
name: 'rel',
label: 'Rel Attribute',
type: 'select',
hasMany: true,
options: ['noopener', 'noreferrer', 'nofollow'],
admin: {
description:
'The rel attribute defines the relationship between a linked resource and the current document. This is a custom link field.',
},
},
],
}),
UploadFeature({
collections: {
uploads: {
fields: [
{
name: 'caption',
type: 'richText',
editor: lexicalEditor(),
},
],
},
},
}),
],
}),
},
],
}
export const LexicalRichTextDoc = {
title: 'Rich Text',
richTextLexicalWithLexicalPluginData: payloadPluginLexicalData,
}

View File

@@ -1,9 +1,11 @@
import type { CollectionConfig } from '../../../../packages/payload/src/collections/config/types'
import { numberFieldsSlug } from '../../slugs'
export const defaultNumber = 5
const NumberFields: CollectionConfig = {
slug: 'number-fields',
slug: numberFieldsSlug,
admin: {
useAsTitle: 'number',
},

View File

@@ -1,6 +1,6 @@
import type { CollectionConfig } from '../../../../packages/payload/src/collections/config/types'
export const pointFieldsSlug = 'point-fields'
import { pointFieldsSlug } from '../../slugs'
const PointFields: CollectionConfig = {
slug: pointFieldsSlug,

View File

@@ -1,7 +1,9 @@
import type { CollectionConfig } from '../../../../packages/payload/src/collections/config/types'
import { radioFieldsSlug } from '../../slugs'
const RadioFields: CollectionConfig = {
slug: 'radio-fields',
slug: radioFieldsSlug,
fields: [
{
name: 'radio',

View File

@@ -1,6 +1,6 @@
import type { CollectionConfig } from '../../../../packages/payload/src/collections/config/types'
export const relationshipFieldsSlug = 'relationship-fields'
import { relationshipFieldsSlug } from '../../slugs'
const RelationshipFields: CollectionConfig = {
fields: [

View File

@@ -0,0 +1,135 @@
import { generateLexicalRichText } from './generateLexicalRichText'
import { generateSlateRichText } from './generateSlateRichText'
export const richTextBlocks = [
{
blockType: 'textBlock',
text: 'Regular text',
},
{
blockType: 'richTextBlock',
text: [
{
children: [
{
text: 'Rich text',
},
],
type: 'h1',
},
],
},
]
export const richTextDoc = {
title: 'Rich Text',
selectHasMany: ['one', 'five'],
richText: generateSlateRichText(),
richTextReadOnly: generateSlateRichText(),
richTextCustomFields: generateSlateRichText(),
richTextLexicalCustomFields: generateLexicalRichText(),
blocks: richTextBlocks,
}
export const richTextBulletsDoc = {
title: 'Bullets and Indentation',
richTextLexicalCustomFields: generateLexicalRichText(),
richText: [
{
type: 'ul',
children: [
{
type: 'li',
children: [
{
children: [
{
text: 'I am semantically connected to my sub-bullets',
},
],
},
{
type: 'ul',
children: [
{
type: 'li',
children: [
{
text: 'I am sub-bullets that are semantically connected to the parent bullet',
},
],
},
],
},
],
},
{
children: [
{
text: 'Normal bullet',
},
],
type: 'li',
},
{
type: 'li',
children: [
{
type: 'ul',
children: [
{
type: 'li',
children: [
{
text: 'I am the old style of sub-bullet',
},
],
},
],
},
],
},
{
type: 'li',
children: [
{
text: 'Another normal bullet',
},
],
},
{
type: 'li',
children: [
{
children: [
{
text: 'This text precedes a nested list',
},
],
},
{
type: 'ul',
children: [
{
type: 'li',
children: [
{
text: 'I am a sub-bullet',
},
],
},
{
type: 'li',
children: [
{
text: 'And I am another sub-bullet',
},
],
},
],
},
],
},
],
},
],
}

View File

@@ -10,12 +10,13 @@ import {
} from '../../../../packages/richtext-lexical/src'
import { lexicalHTML } from '../../../../packages/richtext-lexical/src/field/features/converters/html/field'
import { slateEditor } from '../../../../packages/richtext-slate/src'
import { richTextFieldsSlug } from '../../slugs'
import { RelationshipBlock, SelectFieldBlock, TextBlock, UploadAndRichTextBlock } from './blocks'
import { generateLexicalRichText } from './generateLexicalRichText'
import { generateSlateRichText } from './generateSlateRichText'
const RichTextFields: CollectionConfig = {
slug: 'rich-text-fields',
slug: richTextFieldsSlug,
admin: {
useAsTitle: 'title',
},
@@ -317,137 +318,4 @@ const RichTextFields: CollectionConfig = {
],
}
export const richTextBulletsDoc = {
title: 'Bullets and Indentation',
richTextLexicalCustomFields: generateLexicalRichText(),
richText: [
{
type: 'ul',
children: [
{
type: 'li',
children: [
{
children: [
{
text: 'I am semantically connected to my sub-bullets',
},
],
},
{
type: 'ul',
children: [
{
type: 'li',
children: [
{
text: 'I am sub-bullets that are semantically connected to the parent bullet',
},
],
},
],
},
],
},
{
children: [
{
text: 'Normal bullet',
},
],
type: 'li',
},
{
type: 'li',
children: [
{
type: 'ul',
children: [
{
type: 'li',
children: [
{
text: 'I am the old style of sub-bullet',
},
],
},
],
},
],
},
{
type: 'li',
children: [
{
text: 'Another normal bullet',
},
],
},
{
type: 'li',
children: [
{
children: [
{
text: 'This text precedes a nested list',
},
],
},
{
type: 'ul',
children: [
{
type: 'li',
children: [
{
text: 'I am a sub-bullet',
},
],
},
{
type: 'li',
children: [
{
text: 'And I am another sub-bullet',
},
],
},
],
},
],
},
],
},
],
}
export const richTextBlocks = [
{
blockType: 'textBlock',
text: 'Regular text',
},
{
blockType: 'richTextBlock',
text: [
{
children: [
{
text: 'Rich text',
},
],
type: 'h1',
},
],
},
]
export const richTextDoc = {
title: 'Rich Text',
selectHasMany: ['one', 'five'],
richText: generateSlateRichText(),
richTextReadOnly: generateSlateRichText(),
richTextCustomFields: generateSlateRichText(),
richTextLexicalCustomFields: generateLexicalRichText(),
blocks: richTextBlocks,
}
export default RichTextFields

View File

@@ -1,6 +1,6 @@
import type { CollectionConfig } from '../../../../packages/payload/src/collections/config/types'
export const rowFieldsSlug = 'row-fields'
import { rowFieldsSlug } from '../../slugs'
const RowFields: CollectionConfig = {
slug: rowFieldsSlug,

View File

@@ -1,7 +1,9 @@
import type { CollectionConfig } from '../../../../packages/payload/src/collections/config/types'
import { selectFieldsSlug } from '../../slugs'
const SelectFields: CollectionConfig = {
slug: 'select-fields',
slug: selectFieldsSlug,
fields: [
{
name: 'select',

View File

@@ -1,5 +1,3 @@
export const tabsSlug = 'tabs-fields'
export const namedTabText = 'Some text in a named tab'
export const namedTabDefaultValue = 'default text inside of a named tab'
export const localizedTextValue = 'localized text'

View File

@@ -1,12 +1,13 @@
/* eslint-disable no-param-reassign */
import type { CollectionConfig } from '../../../../packages/payload/src/collections/config/types'
import { tabsFieldsSlug } from '../../slugs'
import { getBlocksField, getBlocksFieldSeedData } from '../Blocks'
import { UIField } from './UIField'
import { localizedTextValue, namedTabDefaultValue, namedTabText, tabsSlug } from './constants'
import { localizedTextValue, namedTabDefaultValue, namedTabText } from './constants'
const TabsFields: CollectionConfig = {
slug: tabsSlug,
slug: tabsFieldsSlug,
access: {
read: () => true,
},

View File

@@ -1,7 +1,8 @@
import type { CollectionConfig } from '../../../../packages/payload/src/collections/config/types'
import { textFieldsSlug } from '../../slugs'
export const defaultText = 'default-text'
export const textFieldsSlug = 'text-fields'
const TextFields: CollectionConfig = {
slug: textFieldsSlug,

View File

@@ -2,8 +2,10 @@ import path from 'path'
import type { CollectionConfig } from '../../../../packages/payload/src/collections/config/types'
import { uploadsSlug } from '../../slugs'
const Uploads: CollectionConfig = {
slug: 'uploads',
slug: uploadsSlug,
upload: {
staticDir: path.resolve(__dirname, './uploads'),
},
@@ -15,7 +17,7 @@ const Uploads: CollectionConfig = {
{
type: 'upload',
name: 'media',
relationTo: 'uploads',
relationTo: uploadsSlug,
filterOptions: {
mimeType: {
equals: 'image/png',

View File

@@ -2,8 +2,10 @@ import path from 'path'
import type { CollectionConfig } from '../../../../packages/payload/src/collections/config/types'
import { uploads2Slug } from '../../slugs'
const Uploads2: CollectionConfig = {
slug: 'uploads2',
slug: uploads2Slug,
upload: {
staticDir: path.resolve(__dirname, './uploads2'),
},
@@ -19,7 +21,7 @@ const Uploads2: CollectionConfig = {
{
type: 'upload',
name: 'media',
relationTo: 'uploads2',
relationTo: uploads2Slug,
},
],
}

View File

@@ -2,8 +2,10 @@ import path from 'path'
import type { CollectionConfig } from '../../../../packages/payload/src/collections/config/types'
import { uploads3Slug } from '../../slugs'
const Uploads3: CollectionConfig = {
slug: 'uploads3',
slug: uploads3Slug,
upload: {
staticDir: path.resolve(__dirname, './uploads3'),
},
@@ -18,7 +20,7 @@ const Uploads3: CollectionConfig = {
{
type: 'upload',
name: 'media',
relationTo: 'uploads3',
relationTo: uploads3Slug,
},
{
type: 'richText',

View File

@@ -1,33 +1,75 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
import fs from 'fs'
import path from 'path'
import getFileByPath from '../../packages/payload/src/uploads/getFileByPath'
import type { CollectionConfig } from '../../packages/payload/src/collections/config/types'
import { buildConfigWithDefaults } from '../buildConfigWithDefaults'
import { devUser } from '../credentials'
import ArrayFields, { arrayDoc } from './collections/Array'
import BlockFields, { blocksDoc } from './collections/Blocks'
import ArrayFields from './collections/Array'
import BlockFields from './collections/Blocks'
import CheckboxFields from './collections/Checkbox'
import CodeFields, { codeDoc } from './collections/Code'
import CollapsibleFields, { collapsibleDoc } from './collections/Collapsible'
import ConditionalLogic, { conditionalLogicDoc } from './collections/ConditionalLogic'
import DateFields, { dateDoc } from './collections/Date'
import GroupFields, { groupDoc } from './collections/Group'
import CodeFields from './collections/Code'
import CollapsibleFields from './collections/Collapsible'
import ConditionalLogic from './collections/ConditionalLogic'
import DateFields from './collections/Date'
import GroupFields from './collections/Group'
import IndexedFields from './collections/Indexed'
import JSONFields, { jsonDoc } from './collections/JSON'
import { LexicalFields, LexicalRichTextDoc } from './collections/Lexical'
import NumberFields, { numberDoc } from './collections/Number'
import PointFields, { pointDoc } from './collections/Point'
import RadioFields, { radiosDoc } from './collections/Radio'
import JSONFields from './collections/JSON'
import { LexicalFields } from './collections/Lexical'
import { LexicalMigrateFields } from './collections/LexicalMigrate'
import NumberFields from './collections/Number'
import PointFields from './collections/Point'
import RadioFields from './collections/Radio'
import RelationshipFields from './collections/Relationship'
import RichTextFields, { richTextBulletsDoc, richTextDoc } from './collections/RichText'
import RichTextFields from './collections/RichText'
import RowFields from './collections/Row'
import SelectFields, { selectsDoc } from './collections/Select'
import TabsFields, { tabsDoc } from './collections/Tabs'
import TextFields, { textDoc, textFieldsSlug } from './collections/Text'
import Uploads, { uploadsDoc } from './collections/Upload'
import SelectFields from './collections/Select'
import TabsFields from './collections/Tabs'
import TextFields from './collections/Text'
import Uploads from './collections/Upload'
import Uploads2 from './collections/Upload2'
import Uploads3 from './collections/Uploads3'
import { clearAndSeedEverything } from './seed'
export const collectionSlugs: CollectionConfig[] = [
LexicalFields,
LexicalMigrateFields,
{
admin: {
useAsTitle: 'email',
},
auth: true,
fields: [
{
name: 'canViewConditionalField',
defaultValue: true,
type: 'checkbox',
},
],
slug: 'users',
},
ArrayFields,
BlockFields,
CheckboxFields,
CodeFields,
CollapsibleFields,
ConditionalLogic,
DateFields,
RadioFields,
GroupFields,
RowFields,
IndexedFields,
JSONFields,
NumberFields,
PointFields,
RelationshipFields,
RichTextFields,
SelectFields,
TabsFields,
TextFields,
Uploads,
Uploads2,
Uploads3,
]
export default buildConfigWithDefaults({
admin: {
@@ -42,139 +84,13 @@ export default buildConfigWithDefaults({
},
}),
},
collections: [
LexicalFields,
{
admin: {
useAsTitle: 'email',
},
auth: true,
fields: [
{
name: 'canViewConditionalField',
defaultValue: true,
type: 'checkbox',
},
],
slug: 'users',
},
ArrayFields,
BlockFields,
CheckboxFields,
CodeFields,
CollapsibleFields,
ConditionalLogic,
DateFields,
RadioFields,
GroupFields,
RowFields,
IndexedFields,
JSONFields,
NumberFields,
PointFields,
RelationshipFields,
RichTextFields,
SelectFields,
TabsFields,
TextFields,
Uploads,
Uploads2,
Uploads3,
],
collections: collectionSlugs,
localization: {
defaultLocale: 'en',
fallback: true,
locales: ['en', 'es'],
},
onInit: async (payload) => {
await payload.create({
collection: 'users',
data: {
email: devUser.email,
password: devUser.password,
},
})
const createdArrayDoc = await payload.create({ collection: 'array-fields', data: arrayDoc })
await payload.create({ collection: 'collapsible-fields', data: collapsibleDoc })
await payload.create({ collection: 'conditional-logic', data: conditionalLogicDoc })
await payload.create({ collection: 'group-fields', data: groupDoc })
await payload.create({ collection: 'select-fields', data: selectsDoc })
await payload.create({ collection: 'radio-fields', data: radiosDoc })
await payload.create({ collection: 'tabs-fields', data: tabsDoc })
await payload.create({ collection: 'point-fields', data: pointDoc })
await payload.create({ collection: 'date-fields', data: dateDoc })
await payload.create({ collection: 'code-fields', data: codeDoc })
await payload.create({ collection: 'json-fields', data: jsonDoc })
const createdTextDoc = await payload.create({ collection: textFieldsSlug, data: textDoc })
const uploadsDir = path.resolve(__dirname, './collections/Upload/uploads')
if (fs.existsSync(uploadsDir))
fs.readdirSync(uploadsDir).forEach((f) => fs.rmSync(`${uploadsDir}/${f}`))
const pngPath = path.resolve(__dirname, './uploads/payload.png')
const pngFile = await getFileByPath(pngPath)
const createdPNGDoc = await payload.create({ collection: 'uploads', data: {}, file: pngFile })
const jpgPath = path.resolve(__dirname, './collections/Upload/payload.jpg')
const jpgFile = await getFileByPath(jpgPath)
const createdJPGDoc = await payload.create({
collection: 'uploads',
data: {
...uploadsDoc,
media: createdPNGDoc.id,
},
file: jpgFile,
})
const formattedID =
payload.db.defaultIDType === 'number' ? createdArrayDoc.id : `"${createdArrayDoc.id}"`
const formattedJPGID =
payload.db.defaultIDType === 'number' ? createdJPGDoc.id : `"${createdJPGDoc.id}"`
const formattedTextID =
payload.db.defaultIDType === 'number' ? createdTextDoc.id : `"${createdTextDoc.id}"`
const richTextDocWithRelId = JSON.parse(
JSON.stringify(richTextDoc)
.replace(/"\{\{ARRAY_DOC_ID\}\}"/g, formattedID)
.replace(/"\{\{UPLOAD_DOC_ID\}\}"/g, formattedJPGID)
.replace(/"\{\{TEXT_DOC_ID\}\}"/g, formattedTextID),
)
const richTextBulletsDocWithRelId = JSON.parse(
JSON.stringify(richTextBulletsDoc)
.replace(/"\{\{ARRAY_DOC_ID\}\}"/g, formattedID)
.replace(/"\{\{UPLOAD_DOC_ID\}\}"/g, formattedJPGID)
.replace(/"\{\{TEXT_DOC_ID\}\}"/g, formattedTextID),
)
const lexicalRichTextDocWithRelId = JSON.parse(
JSON.stringify(LexicalRichTextDoc)
.replace(/"\{\{ARRAY_DOC_ID\}\}"/g, formattedID)
.replace(/"\{\{UPLOAD_DOC_ID\}\}"/g, formattedJPGID)
.replace(/"\{\{TEXT_DOC_ID\}\}"/g, formattedTextID),
)
await payload.create({ collection: 'lexical-fields', data: lexicalRichTextDocWithRelId })
const richTextDocWithRelationship = { ...richTextDocWithRelId }
await payload.create({ collection: 'rich-text-fields', data: richTextBulletsDocWithRelId })
await payload.create({ collection: 'rich-text-fields', data: richTextDocWithRelationship })
await payload.create({ collection: 'number-fields', data: { number: 2 } })
await payload.create({ collection: 'number-fields', data: { number: 3 } })
await payload.create({ collection: 'number-fields', data: numberDoc })
const blocksDocWithRichText = { ...blocksDoc }
// @ts-ignore
blocksDocWithRichText.blocks[0].richText = richTextDocWithRelationship.richText
// @ts-ignore
blocksDocWithRichText.localizedBlocks[0].richText = richTextDocWithRelationship.richText
await payload.create({ collection: 'block-fields', data: blocksDocWithRichText })
await clearAndSeedEverything(payload)
},
})

View File

@@ -10,19 +10,25 @@ import { saveDocAndAssert, saveDocHotkeyAndAssert } from '../helpers'
import { AdminUrlUtil } from '../helpers/adminUrlUtil'
import { initPayloadE2E } from '../helpers/configHelpers'
import { RESTClient } from '../helpers/rest'
import { collapsibleFieldsSlug } from './collections/Collapsible/shared'
import { jsonDoc } from './collections/JSON'
import { numberDoc } from './collections/Number'
import { pointFieldsSlug } from './collections/Point'
import { relationshipFieldsSlug } from './collections/Relationship'
import { tabsSlug } from './collections/Tabs/constants'
import { textDoc, textFieldsSlug } from './collections/Text'
import { textDoc } from './collections/Text'
import { lexicalE2E } from './lexicalE2E'
import { clearAndSeedEverything } from './seed'
import {
collapsibleFieldsSlug,
pointFieldsSlug,
relationshipFieldsSlug,
tabsFieldsSlug,
textFieldsSlug,
} from './slugs'
const { afterEach, beforeAll, describe } = test
const { afterEach, beforeAll, describe, beforeEach } = test
let client: RESTClient
let page: Page
let serverURL
let serverURL: string
// If we want to make this run in parallel: test.describe.configure({ mode: 'parallel' })
describe('fields', () => {
beforeAll(async ({ browser }) => {
@@ -34,7 +40,12 @@ describe('fields', () => {
const context = await browser.newContext()
page = await context.newPage()
})
beforeEach(async () => {
await clearAndSeedEverything(payload)
await client.logout()
client = new RESTClient(null, { serverURL, defaultSlug: 'users' })
await client.login()
})
describe('text', () => {
let url: AdminUrlUtil
beforeAll(() => {
@@ -146,7 +157,7 @@ describe('fields', () => {
describe('indexed', () => {
let url: AdminUrlUtil
beforeAll(() => {
beforeEach(() => {
url = new AdminUrlUtil(serverURL, 'indexed-fields')
})
@@ -162,13 +173,14 @@ describe('fields', () => {
},
},
})
await page.goto(url.create)
await page.locator('#field-text').fill('test')
await page.locator('#field-uniqueText').fill(uniqueText)
// attempt to save
await page.locator('#action-save').click()
await page.click('#action-save', { delay: 100 })
// toast error
await expect(page.locator('.Toastify')).toContainText(
@@ -269,7 +281,7 @@ describe('fields', () => {
let url: AdminUrlUtil
let filledGroupPoint
let emptyGroupPoint
beforeAll(async () => {
beforeEach(async () => {
url = new AdminUrlUtil(serverURL, pointFieldsSlug)
filledGroupPoint = await payload.create({
collection: pointFieldsSlug,
@@ -682,7 +694,7 @@ describe('fields', () => {
describe('tabs', () => {
let url: AdminUrlUtil
beforeAll(() => {
url = new AdminUrlUtil(serverURL, tabsSlug)
url = new AdminUrlUtil(serverURL, tabsFieldsSlug)
})
test('should fill and retain a new value within a tab while switching tabs', async () => {
@@ -750,7 +762,7 @@ describe('fields', () => {
)
})
})
describe('lexical', lexicalE2E(client, page, serverURL))
describe('richText', () => {
async function navigateToRichTextFields() {
const url: AdminUrlUtil = new AdminUrlUtil(serverURL, 'rich-text-fields')
@@ -1473,7 +1485,7 @@ describe('fields', () => {
url = new AdminUrlUtil(serverURL, 'uploads')
})
test('should upload files', async () => {
async function uploadImage() {
await page.goto(url.create)
// create a jpg upload
@@ -1484,10 +1496,15 @@ describe('fields', () => {
await page.locator('#action-save').click()
await wait(200)
await expect(page.locator('.Toastify')).toContainText('successfully')
}
test('should upload files', async () => {
await uploadImage()
})
// test that the image renders
test('should render uploaded image', async () => {
await uploadImage()
await expect(page.locator('.file-field .file-details img')).toHaveAttribute(
'src',
'/uploads/payload-1.jpg',
@@ -1495,6 +1512,7 @@ describe('fields', () => {
})
test('should upload using the document drawer', async () => {
await uploadImage()
// Open the media drawer and create a png upload
await page.locator('.field-type.upload .upload__toggler.doc-drawer__toggler').click()
await page
@@ -1521,10 +1539,20 @@ describe('fields', () => {
})
test('should clear selected upload', async () => {
await uploadImage()
await page.locator('.field-type.upload .upload__toggler.doc-drawer__toggler').click()
await page
.locator('[id^=doc-drawer_uploads_1_] .file-field__upload input[type="file"]')
.setInputFiles(path.resolve(__dirname, './uploads/payload.png'))
await page.locator('[id^=doc-drawer_uploads_1_] #action-save').click()
await wait(200)
await expect(page.locator('.Toastify')).toContainText('successfully')
await page.locator('.field-type.upload .file-details__remove').click()
})
test('should select using the list drawer and restrict mimetype based on filterOptions', async () => {
await uploadImage()
await page.locator('.field-type.upload .upload__toggler.list-drawer__toggler').click()
await wait(200)
const jpgImages = page.locator('[id^=list-drawer_1_] .upload-gallery img[src$=".jpg"]')

View File

@@ -8,28 +8,24 @@ import type { RichTextField } from './payload-types'
import payload from '../../packages/payload/src'
import { initPayloadTest } from '../helpers/configHelpers'
import { isMongoose } from '../helpers/isMongoose'
import { RESTClient } from '../helpers/rest'
import configPromise from '../uploads/config'
import { arrayDefaultValue, arrayFieldsSlug } from './collections/Array'
import { arrayDefaultValue } from './collections/Array'
import { blocksDoc } from './collections/Blocks'
import { dateDoc } from './collections/Date'
import {
groupDefaultChild,
groupDefaultValue,
groupDoc,
groupFieldsSlug,
} from './collections/Group'
import { groupDefaultChild, groupDefaultValue, groupDoc } from './collections/Group'
import { defaultNumber, numberDoc } from './collections/Number'
import { pointDoc } from './collections/Point'
import { relationshipFieldsSlug } from './collections/Relationship'
import { tabsDoc } from './collections/Tabs'
import {
localizedTextValue,
namedTabDefaultValue,
namedTabText,
tabsSlug,
} from './collections/Tabs/constants'
import { defaultText } from './collections/Text'
import { clearAndSeedEverything } from './seed'
import { arrayFieldsSlug, groupFieldsSlug, relationshipFieldsSlug, tabsFieldsSlug } from './slugs'
let client
let graphQLClient: GraphQLClient
@@ -48,10 +44,16 @@ describe('Fields', () => {
token = await client.login()
})
beforeEach(async () => {
await clearAndSeedEverything(payload)
client = new RESTClient(config, { defaultSlug: 'point-fields', serverURL })
await client.login()
})
describe('text', () => {
let doc
const text = 'text field'
beforeAll(async () => {
beforeEach(async () => {
doc = await payload.create({
collection: 'text-fields',
data: { text },
@@ -86,7 +88,7 @@ describe('Fields', () => {
const otherTextDocText = 'alt text'
const relationshipText = 'relationship text'
beforeAll(async () => {
beforeEach(async () => {
textDoc = await payload.create({
collection: 'text-fields',
data: {
@@ -193,7 +195,7 @@ describe('Fields', () => {
describe('timestamps', () => {
const tenMinutesAgo = new Date(Date.now() - 1000 * 60 * 10)
let doc
beforeAll(async () => {
beforeEach(async () => {
doc = await payload.create({
collection: 'date-fields',
data: dateDoc,
@@ -231,7 +233,7 @@ describe('Fields', () => {
describe('select', () => {
let doc
beforeAll(async () => {
beforeEach(async () => {
const { id } = await payload.create({
collection: 'select-fields',
data: {
@@ -273,7 +275,7 @@ describe('Fields', () => {
describe('number', () => {
let doc
beforeAll(async () => {
beforeEach(async () => {
doc = await payload.create({
collection: 'number-fields',
data: numberDoc,
@@ -375,13 +377,13 @@ describe('Fields', () => {
})
})
if (['mongoose'].includes(process.env.PAYLOAD_DATABASE)) {
if (isMongoose(payload) || !['postgres'].includes(process.env.PAYLOAD_DATABASE)) {
describe('indexes', () => {
let indexes
const definitions: Record<string, IndexDirection> = {}
const options: Record<string, IndexOptions> = {}
beforeAll(() => {
beforeEach(() => {
indexes = (payload.db as MongooseAdapter).collections[
'indexed-fields'
].schema.indexes() as [Record<string, IndexDirection>, IndexOptions]
@@ -434,7 +436,7 @@ describe('Fields', () => {
const definitions: Record<string, IndexDirection> = {}
const options: Record<string, IndexOptions> = {}
beforeAll(() => {
beforeEach(() => {
indexes = (payload.db as MongooseAdapter).versions['indexed-fields'].schema.indexes() as [
Record<string, IndexDirection>,
IndexOptions,
@@ -458,7 +460,7 @@ describe('Fields', () => {
const localized = [5, -2]
const group = { point: [1, 9] }
beforeAll(async () => {
beforeEach(async () => {
const findDoc = await payload.find({
collection: 'point-fields',
pagination: false,
@@ -495,6 +497,17 @@ describe('Fields', () => {
})
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',
@@ -546,7 +559,7 @@ describe('Fields', () => {
let doc
const collection = arrayFieldsSlug
beforeAll(async () => {
beforeEach(async () => {
doc = await payload.create({
collection,
data: {},
@@ -611,7 +624,7 @@ describe('Fields', () => {
describe('group', () => {
let document
beforeAll(async () => {
beforeEach(async () => {
document = await payload.create({
collection: groupFieldsSlug,
data: {},
@@ -627,9 +640,9 @@ describe('Fields', () => {
describe('tabs', () => {
let document
beforeAll(async () => {
beforeEach(async () => {
document = await payload.create({
collection: tabsSlug,
collection: tabsFieldsSlug,
data: tabsDoc,
})
})
@@ -649,7 +662,7 @@ describe('Fields', () => {
it('should create with localized text inside a named tab', async () => {
document = await payload.findByID({
id: document.id,
collection: tabsSlug,
collection: tabsFieldsSlug,
locale: 'all',
})
expect(document.localizedTab.en.text).toStrictEqual(localizedTextValue)
@@ -658,7 +671,7 @@ describe('Fields', () => {
it('should allow access control on a named tab', async () => {
document = await payload.findByID({
id: document.id,
collection: tabsSlug,
collection: tabsFieldsSlug,
overrideAccess: false,
})
expect(document.accessControlTab).toBeUndefined()
@@ -666,7 +679,7 @@ describe('Fields', () => {
it('should allow hooks on a named tab', async () => {
const newDocument = await payload.create({
collection: tabsSlug,
collection: tabsFieldsSlug,
data: tabsDoc,
})
expect(newDocument.hooksTab.beforeValidate).toBe(true)

27
test/fields/lexicalE2E.ts Normal file
View File

@@ -0,0 +1,27 @@
import type { Page } from '@playwright/test'
import { expect, test } from '@playwright/test'
import type { RESTClient } from '../helpers/rest'
import { AdminUrlUtil } from '../helpers/adminUrlUtil'
const { describe } = test
export const lexicalE2E = (client: RESTClient, page: Page, serverURL: string) => {
async function navigateToRichTextFields() {
const url: AdminUrlUtil = new AdminUrlUtil(serverURL, 'rich-text-fields')
await page.goto(url.list)
await page.locator('.row-1 .cell-title a').click()
}
return () => {
describe('todo', () => {
test.skip('todo', async () => {
await navigateToRichTextFields()
await page.locator('todo').first().click()
})
})
}
}

147
test/fields/seed.ts Normal file
View File

@@ -0,0 +1,147 @@
import path from 'path'
import { type Payload } from '../../packages/payload/src'
import getFileByPath from '../../packages/payload/src/uploads/getFileByPath'
import { devUser } from '../credentials'
import { seedDB } from '../helpers/seed'
import { arrayDoc } from './collections/Array'
import { blocksDoc } from './collections/Blocks'
import { codeDoc } from './collections/Code'
import { collapsibleDoc } from './collections/Collapsible'
import { conditionalLogicDoc } from './collections/ConditionalLogic'
import { dateDoc } from './collections/Date'
import { groupDoc } from './collections/Group'
import { jsonDoc } from './collections/JSON'
import { lexicalRichTextDoc } from './collections/Lexical/data'
import { numberDoc } from './collections/Number'
import { pointDoc } from './collections/Point'
import { radiosDoc } from './collections/Radio'
import { richTextBulletsDoc, richTextDoc } from './collections/RichText/data'
import { selectsDoc } from './collections/Select'
import { tabsDoc } from './collections/Tabs'
import { textDoc } from './collections/Text'
import { uploadsDoc } from './collections/Upload'
import {
blockFieldsSlug,
codeFieldsSlug,
collapsibleFieldsSlug,
collectionSlugs,
conditionalLogicSlug,
dateFieldsSlug,
groupFieldsSlug,
jsonFieldsSlug,
lexicalFieldsSlug,
lexicalMigrateFieldsSlug,
numberFieldsSlug,
pointFieldsSlug,
radioFieldsSlug,
richTextFieldsSlug,
selectFieldsSlug,
tabsFieldsSlug,
textFieldsSlug,
uploadsSlug,
usersSlug,
} from './slugs'
export async function clearAndSeedEverything(_payload: Payload) {
return await seedDB({
snapshotKey: 'fieldsTest',
shouldResetDB: true,
collectionSlugs,
_payload,
uploadsDir: path.resolve(__dirname, './collections/Upload/uploads'),
seedFunction: async (_payload) => {
const jpgPath = path.resolve(__dirname, './collections/Upload/payload.jpg')
const pngPath = path.resolve(__dirname, './uploads/payload.png')
// Get both files in parallel
const [jpgFile, pngFile] = await Promise.all([getFileByPath(jpgPath), getFileByPath(pngPath)])
const [createdArrayDoc, createdTextDoc, createdPNGDoc] = await Promise.all([
_payload.create({ collection: 'array-fields', data: arrayDoc }),
_payload.create({ collection: textFieldsSlug, data: textDoc }),
_payload.create({ collection: uploadsSlug, data: {}, file: pngFile }),
])
const createdJPGDoc = await _payload.create({
collection: uploadsSlug,
data: {
...uploadsDoc,
media: createdPNGDoc.id,
},
file: jpgFile,
})
const formattedID =
_payload.db.defaultIDType === 'number' ? createdArrayDoc.id : `"${createdArrayDoc.id}"`
const formattedJPGID =
_payload.db.defaultIDType === 'number' ? createdJPGDoc.id : `"${createdJPGDoc.id}"`
const formattedTextID =
_payload.db.defaultIDType === 'number' ? createdTextDoc.id : `"${createdTextDoc.id}"`
const richTextDocWithRelId = JSON.parse(
JSON.stringify(richTextDoc)
.replace(/"\{\{ARRAY_DOC_ID\}\}"/g, `${formattedID}`)
.replace(/"\{\{UPLOAD_DOC_ID\}\}"/g, `${formattedJPGID}`)
.replace(/"\{\{TEXT_DOC_ID\}\}"/g, `${formattedTextID}`),
)
const richTextBulletsDocWithRelId = JSON.parse(
JSON.stringify(richTextBulletsDoc)
.replace(/"\{\{ARRAY_DOC_ID\}\}"/g, `${formattedID}`)
.replace(/"\{\{UPLOAD_DOC_ID\}\}"/g, `${formattedJPGID}`)
.replace(/"\{\{TEXT_DOC_ID\}\}"/g, `${formattedTextID}`),
)
const richTextDocWithRelationship = { ...richTextDocWithRelId }
const blocksDocWithRichText = { ...blocksDoc }
blocksDocWithRichText.blocks[0].richText = richTextDocWithRelationship.richText
blocksDocWithRichText.localizedBlocks[0].richText = richTextDocWithRelationship.richText
const lexicalRichTextDocWithRelId = JSON.parse(
JSON.stringify(lexicalRichTextDoc)
.replace(/"\{\{ARRAY_DOC_ID\}\}"/g, `${formattedID}`)
.replace(/"\{\{UPLOAD_DOC_ID\}\}"/g, `${formattedJPGID}`)
.replace(/"\{\{TEXT_DOC_ID\}\}"/g, `${formattedTextID}`),
)
await Promise.all([
_payload.create({
collection: usersSlug,
data: {
email: devUser.email,
password: devUser.password,
},
}),
_payload.create({ collection: collapsibleFieldsSlug, data: collapsibleDoc }),
_payload.create({ collection: conditionalLogicSlug, data: conditionalLogicDoc }),
_payload.create({ collection: groupFieldsSlug, data: groupDoc }),
_payload.create({ collection: selectFieldsSlug, data: selectsDoc }),
_payload.create({ collection: radioFieldsSlug, data: radiosDoc }),
_payload.create({ collection: tabsFieldsSlug, data: tabsDoc }),
_payload.create({ collection: pointFieldsSlug, data: pointDoc }),
_payload.create({ collection: dateFieldsSlug, data: dateDoc }),
_payload.create({ collection: codeFieldsSlug, data: codeDoc }),
_payload.create({ collection: jsonFieldsSlug, data: jsonDoc }),
_payload.create({ collection: blockFieldsSlug, data: blocksDocWithRichText }),
_payload.create({ collection: lexicalFieldsSlug, data: lexicalRichTextDocWithRelId }),
_payload.create({
collection: lexicalMigrateFieldsSlug,
data: lexicalRichTextDocWithRelId,
}),
_payload.create({ collection: richTextFieldsSlug, data: richTextBulletsDocWithRelId }),
_payload.create({ collection: richTextFieldsSlug, data: richTextDocWithRelationship }),
_payload.create({ collection: numberFieldsSlug, data: { number: 2 } }),
_payload.create({ collection: numberFieldsSlug, data: { number: 3 } }),
_payload.create({ collection: numberFieldsSlug, data: numberDoc }),
])
},
})
}

52
test/fields/slugs.ts Normal file
View File

@@ -0,0 +1,52 @@
export const usersSlug = 'users' as const
export const arrayFieldsSlug = 'array-fields' as const
export const blockFieldsSlug = 'block-fields' as const
export const checkboxFieldsSlug = 'checkbox-fields' as const
export const codeFieldsSlug = 'code-fields' as const
export const collapsibleFieldsSlug = 'collapsible-fields' as const
export const conditionalLogicSlug = 'conditional-logic' as const
export const dateFieldsSlug = 'date-fields' as const
export const groupFieldsSlug = 'group-fields' as const
export const indexedFieldsSlug = 'indexed-fields' as const
export const jsonFieldsSlug = 'json-fields' as const
export const lexicalFieldsSlug = 'lexical-fields' as const
export const lexicalMigrateFieldsSlug = 'lexical-migrate-fields' as const
export const numberFieldsSlug = 'number-fields' as const
export const pointFieldsSlug = 'point-fields' as const
export const radioFieldsSlug = 'radio-fields' as const
export const relationshipFieldsSlug = 'relationship-fields' as const
export const richTextFieldsSlug = 'rich-text-fields' as const
export const rowFieldsSlug = 'row-fields' as const
export const selectFieldsSlug = 'select-fields' as const
export const tabsFieldsSlug = 'tabs-fields' as const
export const textFieldsSlug = 'text-fields' as const
export const uploadsSlug = 'uploads' as const
export const uploads2Slug = 'uploads2' as const
export const uploads3Slug = 'uploads3' as const
export const collectionSlugs = [
usersSlug,
arrayFieldsSlug,
blockFieldsSlug,
checkboxFieldsSlug,
codeFieldsSlug,
collapsibleFieldsSlug,
conditionalLogicSlug,
dateFieldsSlug,
groupFieldsSlug,
indexedFieldsSlug,
jsonFieldsSlug,
lexicalFieldsSlug,
lexicalMigrateFieldsSlug,
numberFieldsSlug,
pointFieldsSlug,
radioFieldsSlug,
relationshipFieldsSlug,
richTextFieldsSlug,
rowFieldsSlug,
selectFieldsSlug,
tabsFieldsSlug,
textFieldsSlug,
uploadsSlug,
uploads2Slug,
uploads3Slug,
]

View File

@@ -40,8 +40,6 @@ export async function initPayloadTest(options: Options): Promise<InitializedPayl
process.env.NODE_ENV = 'test'
process.env.PAYLOAD_CONFIG_PATH = path.resolve(options.__dirname, './config.ts')
const port = await getPort()
if (!initOptions?.local) {
initOptions.express = express()
}
@@ -63,6 +61,7 @@ export async function initPayloadTest(options: Options): Promise<InitializedPayl
await payload.init(initOptions)
const port = await getPort()
if (initOptions.express) {
initOptions.express.listen(port)
}

View File

@@ -0,0 +1,5 @@
import type { Payload } from '../../packages/payload/src'
export function isMongoose(_payload?: Payload) {
return _payload?.db?.name === 'mongoose' || ['mongoose'].includes(process.env.PAYLOAD_DATABASE)
}

36
test/helpers/reset.ts Normal file
View File

@@ -0,0 +1,36 @@
import { sql } from 'drizzle-orm'
import type { PostgresAdapter } from '../../packages/db-postgres/src/types'
import type { Payload } from '../../packages/payload/src'
import { isMongoose } from './isMongoose'
export async function resetDB(_payload: Payload, collectionSlugs: string[]) {
if (isMongoose(_payload)) {
await _payload.db.collections[collectionSlugs[0]].db.dropDatabase()
} else {
const db: PostgresAdapter = _payload.db as unknown as PostgresAdapter
// Alternative to: await db.drizzle.execute(sql`drop schema public cascade; create schema public;`)
// Deleting the schema causes issues when restoring the database from a snapshot later on. That's why we only delete the table data here,
// To avoid having to re-create any table schemas / indexes / whatever
const schema = db.drizzle._.schema
if (!schema) {
return
}
const queries = Object.values(schema).map((table: any) => {
return sql.raw(`DELETE FROM ${table.dbName}`)
})
await db.drizzle.transaction(async (trx) => {
await Promise.all(
queries.map(async (query) => {
if (query) {
await trx.execute(query)
}
}),
)
})
}
}

95
test/helpers/seed.ts Normal file
View File

@@ -0,0 +1,95 @@
import fs from 'fs'
import path from 'path'
import { type Payload } from '../../packages/payload/src'
import { isMongoose } from './isMongoose'
import { resetDB } from './reset'
import { createSnapshot, dbSnapshot, restoreFromSnapshot } from './snapshot'
type SeedFunction = (_payload: Payload) => Promise<void>
export async function seedDB({
shouldResetDB,
_payload,
snapshotKey,
seedFunction,
uploadsDir,
collectionSlugs,
}: {
_payload: Payload
collectionSlugs: string[]
seedFunction: SeedFunction
shouldResetDB: boolean
/**
* Key to uniquely identify the kind of snapshot. Each test suite should pass in a unique key
*/
snapshotKey: string
uploadsDir?: string
}) {
/**
* Reset database and delete uploads directory
*/
if (shouldResetDB) {
let clearUploadsDirPromise: any = Promise.resolve()
if (uploadsDir) {
clearUploadsDirPromise = fs.promises
.access(uploadsDir)
.then(() => fs.promises.readdir(uploadsDir))
.then((files) =>
Promise.all(files.map((file) => fs.promises.rm(path.join(uploadsDir, file)))),
)
.catch((error) => {
if (error.code !== 'ENOENT') {
// If the error is not because the directory doesn't exist
console.error('Error clearing the uploads directory:', error)
throw error
}
// If the directory does not exist, resolve the promise (nothing to clear)
return
})
}
await Promise.all([resetDB(_payload, collectionSlugs), clearUploadsDirPromise])
}
/**
* Mongoose & Postgres: Restore snapshot of old data if available
*
* Note for postgres: For postgres, this needs to happen AFTER the tables were created.
* This does not work if I run payload.db.init or payload.db.connect anywhere. Thus, when resetting the database, we are not dropping the schema, but are instead only deleting the table values
*/
let restored = false
if (dbSnapshot[snapshotKey] && Object.keys(dbSnapshot[snapshotKey]).length) {
await restoreFromSnapshot(_payload, snapshotKey, collectionSlugs)
restored = true
}
/**
* Mongoose: Re-create indexes
* Postgres: No need for any action here, since we only delete the table data and no schemas
*/
// Dropping the db breaks indexes (on mongoose - did not test extensively on postgres yet), so we recreate them here
if (shouldResetDB) {
if (isMongoose(_payload)) {
await Promise.all([
...collectionSlugs.map(async (collectionSlug) => {
await _payload.db.collections[collectionSlug].createIndexes()
}),
])
}
}
/**
* If a snapshot was restored, we don't need to seed the database
*/
if (restored) {
return
}
/**
* Seed the database with data and save it to a snapshot
**/
await seedFunction(_payload)
await createSnapshot(_payload, snapshotKey, collectionSlugs)
}

118
test/helpers/snapshot.ts Normal file
View File

@@ -0,0 +1,118 @@
import type { PgTable } from 'drizzle-orm/pg-core'
import { sql } from 'drizzle-orm'
import type { PostgresAdapter } from '../../packages/db-postgres/src/types'
import type { Payload } from '../../packages/payload/src'
import { isMongoose } from './isMongoose'
export const dbSnapshot = {}
async function createMongooseSnapshot(collectionsObj, snapshotKey: string) {
const snapshot = {}
// Assuming `collectionsObj` is an object where keys are names and values are collection references
for (const collectionName of Object.keys(collectionsObj)) {
const collection = collectionsObj[collectionName]
const documents = await collection.find({}).toArray() // Get all documents
snapshot[collectionName] = documents
}
dbSnapshot[snapshotKey] = snapshot // Save the snapshot in memory
}
async function restoreFromMongooseSnapshot(collectionsObj, snapshotKey: string) {
if (!dbSnapshot[snapshotKey]) {
throw new Error('No snapshot found to restore from.')
}
// Assuming `collectionsObj` is an object where keys are names and values are collection references
for (const [name, documents] of Object.entries(dbSnapshot[snapshotKey])) {
const collection = collectionsObj[name]
// You would typically clear the collection here, but as per your requirement, you do it manually
if ((documents as any[]).length > 0) {
await collection.insertMany(documents)
}
}
}
async function createDrizzleSnapshot(db: PostgresAdapter, snapshotKey: string) {
const snapshot = {}
const schema: Record<string, PgTable> = db.drizzle._.schema
if (!schema) {
return
}
for (const tableName in schema) {
const table = db.drizzle.query[tableName]['fullSchema'][tableName] //db.drizzle._.schema[tableName]
const records = await db.drizzle.select().from(table).execute()
snapshot[tableName] = records
}
dbSnapshot[snapshotKey] = snapshot
}
async function restoreFromDrizzleSnapshot(db: PostgresAdapter, snapshotKey: string) {
if (!dbSnapshot[snapshotKey]) {
throw new Error('No snapshot found to restore from.')
}
const disableFKConstraintChecksQuery = sql.raw('SET session_replication_role = replica;')
const enableFKConstraintChecksQuery = sql.raw('SET session_replication_role = DEFAULT;')
await db.drizzle.transaction(async (trx) => {
// Temporarily disable foreign key constraint checks
await trx.execute(disableFKConstraintChecksQuery)
try {
for (const tableName in dbSnapshot[snapshotKey]) {
const table = db.drizzle.query[tableName]['fullSchema'][tableName]
await trx.delete(table).execute() // This deletes all records from the table. Probably not necessary, as I'm deleting the table before restoring anyways
const records = dbSnapshot[snapshotKey][tableName]
if (records.length > 0) {
await trx.insert(table).values(records).execute()
}
}
} catch (e) {
console.error(e)
} finally {
// Re-enable foreign key constraint checks
await trx.execute(enableFKConstraintChecksQuery)
}
})
}
export async function createSnapshot(
_payload: Payload,
snapshotKey: string,
collectionSlugs: string[],
) {
if (isMongoose(_payload)) {
const mongooseCollections = _payload.db.collections[collectionSlugs[0]].db.collections
await createMongooseSnapshot(mongooseCollections, snapshotKey)
} else {
const db: PostgresAdapter = _payload.db as unknown as PostgresAdapter
await createDrizzleSnapshot(db, snapshotKey)
}
}
/**
* Make sure to delete the db before calling this function
* @param _payload
*/
export async function restoreFromSnapshot(
_payload: Payload,
snapshotKey: string,
collectionSlugs: string[],
) {
if (isMongoose(_payload)) {
const mongooseCollections = _payload.db.collections[collectionSlugs[0]].db.collections
await restoreFromMongooseSnapshot(mongooseCollections, snapshotKey)
} else {
const db: PostgresAdapter = _payload.db as unknown as PostgresAdapter
await restoreFromDrizzleSnapshot(db, snapshotKey)
}
}

View File

@@ -1,9 +1,9 @@
import type { CollectionConfig } from '../../../packages/payload/src/collections/config/types'
import { autosaveSlug } from '../shared'
import { autosaveCollectionSlug } from '../slugs'
const AutosavePosts: CollectionConfig = {
slug: autosaveSlug,
slug: autosaveCollectionSlug,
labels: {
singular: 'Autosave Post',
plural: 'Autosave Posts',

View File

@@ -1,7 +1,7 @@
import type { CollectionConfig } from '../../../packages/payload/src/collections/config/types'
import { CustomPublishButton } from '../elements/CustomSaveButton'
import { draftSlug } from '../shared'
import { draftCollectionSlug } from '../slugs'
const DraftPosts: CollectionConfig = {
access: {
@@ -98,7 +98,7 @@ const DraftPosts: CollectionConfig = {
type: 'blocks',
},
],
slug: draftSlug,
slug: draftCollectionSlug,
versions: {
drafts: true,
maxPerDoc: 35,

View File

@@ -1,19 +1,19 @@
import type { CollectionConfig } from '../../../packages/payload/src/collections/config/types'
import { draftSlug, postSlug, versionSlug } from '../shared'
import { draftCollectionSlug, postCollectionSlug, versionCollectionSlug } from '../slugs'
const Posts: CollectionConfig = {
slug: postSlug,
slug: postCollectionSlug,
fields: [
{
name: 'relationToVersions',
type: 'relationship',
relationTo: versionSlug,
relationTo: versionCollectionSlug,
},
{
name: 'relationToDrafts',
type: 'relationship',
relationTo: draftSlug,
relationTo: draftCollectionSlug,
},
],
}

View File

@@ -1,9 +1,9 @@
import type { CollectionConfig } from '../../../packages/payload/src/collections/config/types'
import { versionSlug } from '../shared'
import { versionCollectionSlug } from '../slugs'
const VersionPosts: CollectionConfig = {
slug: versionSlug,
slug: versionCollectionSlug,
admin: {
useAsTitle: 'title',
defaultColumns: ['title', 'description', 'createdAt'],

View File

@@ -1,3 +1,5 @@
import path from 'path'
import { buildConfigWithDefaults } from '../buildConfigWithDefaults'
import AutosavePosts from './collections/Autosave'
import DraftPosts from './collections/Drafts'
@@ -5,7 +7,7 @@ import Posts from './collections/Posts'
import VersionPosts from './collections/Versions'
import AutosaveGlobal from './globals/Autosave'
import DraftGlobal from './globals/Draft'
import { seed } from './seed'
import { clearAndSeedEverything } from './seed'
export default buildConfigWithDefaults({
collections: [Posts, AutosavePosts, DraftPosts, VersionPosts],
@@ -15,5 +17,19 @@ export default buildConfigWithDefaults({
defaultLocale: 'en',
locales: ['en', 'es'],
},
onInit: seed,
admin: {
webpack: (config) => ({
...config,
resolve: {
...config.resolve,
alias: {
...config?.resolve?.alias,
fs: path.resolve(__dirname, './mocks/emptyModule.js'),
},
},
}),
},
onInit: async (payload) => {
await clearAndSeedEverything(payload)
},
})

View File

@@ -27,20 +27,22 @@ import type { Page } from '@playwright/test'
import { expect, test } from '@playwright/test'
import payload from '../../packages/payload/src'
import wait from '../../packages/payload/src/utilities/wait'
import { globalSlug } from '../admin/shared'
import { globalSlug } from '../admin/slugs'
import { changeLocale, exactText, findTableCell, selectTableRow } from '../helpers'
import { AdminUrlUtil } from '../helpers/adminUrlUtil'
import { initPayloadE2E } from '../helpers/configHelpers'
import { clearAndSeedEverything } from './seed'
import { titleToDelete } from './shared'
import {
autoSaveGlobalSlug,
autosaveSlug,
autosaveCollectionSlug,
draftCollectionSlug,
draftGlobalSlug,
draftSlug,
titleToDelete,
} from './shared'
} from './slugs'
const { beforeAll, describe } = test
const { beforeAll, beforeEach, describe } = test
describe('versions', () => {
let page: Page
@@ -55,10 +57,14 @@ describe('versions', () => {
page = await context.newPage()
})
beforeEach(async () => {
await clearAndSeedEverything(payload)
})
describe('draft collections', () => {
beforeAll(() => {
url = new AdminUrlUtil(serverURL, draftSlug)
autosaveURL = new AdminUrlUtil(serverURL, autosaveSlug)
url = new AdminUrlUtil(serverURL, draftCollectionSlug)
autosaveURL = new AdminUrlUtil(serverURL, autosaveCollectionSlug)
})
// This test has to run before bulk updates that will rename the title

View File

@@ -1,6 +1,6 @@
import type { GlobalConfig } from '../../../packages/payload/src/globals/config/types'
import { autoSaveGlobalSlug } from '../shared'
import { autoSaveGlobalSlug } from '../slugs'
const AutosaveGlobal: GlobalConfig = {
slug: autoSaveGlobalSlug,

View File

@@ -1,6 +1,6 @@
import type { GlobalConfig } from '../../../packages/payload/src/globals/config/types'
import { draftGlobalSlug } from '../shared'
import { draftGlobalSlug } from '../slugs'
const DraftGlobal: GlobalConfig = {
slug: draftGlobalSlug,

View File

@@ -6,7 +6,8 @@ import { initPayloadTest } from '../helpers/configHelpers'
import AutosavePosts from './collections/Autosave'
import configPromise from './config'
import AutosaveGlobal from './globals/Autosave'
import { autosaveSlug, draftSlug } from './shared'
import { clearAndSeedEverything } from './seed'
import { autosaveCollectionSlug, draftCollectionSlug } from './slugs'
let collectionLocalPostID: string
let collectionLocalVersionID
@@ -54,13 +55,7 @@ describe('Versions', () => {
})
beforeEach(async () => {
// First: delete potential existing versions from previous tests
if (collectionLocalPostID) {
await payload.delete({
id: collectionLocalPostID,
collection,
})
}
await clearAndSeedEverything(payload)
// now: initialize
const autosavePost = await payload.create({
@@ -94,7 +89,7 @@ describe('Versions', () => {
describe('Create', () => {
it('should allow creating a draft with missing required field data', async () => {
const draft = await payload.create({
collection: autosaveSlug,
collection: autosaveCollectionSlug,
data: {
description: undefined,
title: 'i have a title',
@@ -213,13 +208,13 @@ describe('Versions', () => {
it('should query drafts with sort', async () => {
const draftsAscending = await payload.find({
collection: draftSlug,
collection: draftCollectionSlug,
draft: true,
sort: 'title',
})
const draftsDescending = await payload.find({
collection: draftSlug,
collection: draftCollectionSlug,
draft: true,
sort: '-title',
})
@@ -233,14 +228,14 @@ describe('Versions', () => {
it('should `findVersions` with sort', async () => {
const draftsAscending = await payload.findVersions({
collection: draftSlug,
collection: draftCollectionSlug,
draft: true,
sort: 'createdAt',
limit: 100,
})
const draftsDescending = await payload.findVersions({
collection: draftSlug,
collection: draftCollectionSlug,
draft: true,
sort: '-createdAt',
limit: 100,
@@ -257,7 +252,7 @@ describe('Versions', () => {
describe('Restore', () => {
it('should return `findVersions` in correct order', async () => {
const somePost = await payload.create({
collection: draftSlug,
collection: draftCollectionSlug,
data: {
description: 'description 1',
title: 'first post',
@@ -266,14 +261,14 @@ describe('Versions', () => {
const updatedPost = await payload.update({
id: somePost.id,
collection: draftSlug,
collection: draftCollectionSlug,
data: {
title: 'This should be the latest version',
},
})
const versions = await payload.findVersions({
collection: draftSlug,
collection: draftCollectionSlug,
where: {
parent: { equals: somePost.id },
},
@@ -286,7 +281,7 @@ describe('Versions', () => {
const updated = 'updated'
const versionedPost = await payload.create({
collection: draftSlug,
collection: draftCollectionSlug,
data: {
description: 'version description',
title: 'version title',
@@ -297,7 +292,7 @@ describe('Versions', () => {
// @ts-ignore
let updatedPost = await payload.update({
id: versionedPost.id,
collection: draftSlug,
collection: draftCollectionSlug,
data: {
blocksField: [
{
@@ -313,7 +308,7 @@ describe('Versions', () => {
// @ts-ignore
updatedPost = await payload.update({
id: versionedPost.id,
collection: draftSlug,
collection: draftCollectionSlug,
data: {
blocksField: [
{
@@ -336,7 +331,7 @@ describe('Versions', () => {
// Make sure it was updated correctly
const draftFromUpdatedPost = await payload.findByID({
id: versionedPost.id,
collection: draftSlug,
collection: draftCollectionSlug,
draft: true,
})
expect(draftFromUpdatedPost.title).toBe(title2)
@@ -344,7 +339,7 @@ describe('Versions', () => {
expect(draftFromUpdatedPost.blocksField[0].localized).toStrictEqual(updated)
const versions = await payload.findVersions({
collection: draftSlug,
collection: draftCollectionSlug,
where: {
parent: {
equals: versionedPost.id,
@@ -356,7 +351,7 @@ describe('Versions', () => {
// restore to previous version
const restoredVersion = await payload.restoreVersion({
id: versionToRestore.id,
collection: draftSlug,
collection: draftCollectionSlug,
})
expect({ ...restoredVersion }).toMatchObject({
@@ -366,7 +361,7 @@ describe('Versions', () => {
const latestDraft = await payload.findByID({
id: versionedPost.id,
collection: draftSlug,
collection: draftCollectionSlug,
draft: true,
})
@@ -435,7 +430,7 @@ describe('Versions', () => {
})
describe('Delete', () => {
let postToDelete
beforeAll(async () => {
beforeEach(async () => {
postToDelete = await payload.create({
collection,
data: {
@@ -633,7 +628,7 @@ describe('Versions', () => {
const updatedTitle2 = 'new title 2'
let firstDraft
beforeAll(async () => {
beforeEach(async () => {
// This will be created in the `draft-posts` collection
firstDraft = await payload.create({
collection: 'draft-posts',
@@ -780,14 +775,6 @@ describe('Versions', () => {
describe('Collections - GraphQL', () => {
beforeEach(async () => {
// First: delete potential existing versions from previous tests
if (collectionGraphQLPostID) {
await payload.delete({
id: collectionGraphQLPostID,
collection,
})
}
const description = 'autosave description'
const query = `mutation {
@@ -1182,7 +1169,7 @@ describe('Versions', () => {
})
describe('Globals - GraphQL', () => {
beforeAll(async () => {
beforeEach(async () => {
// language=graphql
const update = `mutation {
updateAutosaveGlobal(draft: true, data: {

View File

@@ -0,0 +1 @@
export default {}

86
test/versions/seed.ts Normal file
View File

@@ -0,0 +1,86 @@
import { type Payload } from '../../packages/payload/src'
import { devUser } from '../credentials'
import { seedDB } from '../helpers/seed'
import { titleToDelete } from './shared'
import { collectionSlugs, draftCollectionSlug } from './slugs'
export async function clearAndSeedEverything(_payload: Payload) {
return await seedDB({
snapshotKey: 'versionsTest',
shouldResetDB: true,
collectionSlugs,
_payload,
seedFunction: async (_payload) => {
const blocksField = [
{
blockType: 'block',
localized: 'text',
text: 'text',
},
]
await Promise.all([
_payload.create({
collection: 'users',
data: {
email: devUser.email,
password: devUser.password,
},
}),
_payload.create({
collection: draftCollectionSlug,
data: {
blocksField,
description: 'Description',
radio: 'test',
title: 'Draft Title',
},
draft: true,
}),
])
const { id: manyDraftsID } = await _payload.create({
collection: draftCollectionSlug,
data: {
blocksField,
description: 'Description',
radio: 'test',
title: 'Title With Many Versions',
},
draft: true,
})
for (let i = 0; i < 10; i++) {
await _payload.update({
id: manyDraftsID,
collection: draftCollectionSlug,
data: {
title: `Title With Many Versions ${i + 2}`,
},
})
}
await _payload.create({
collection: draftCollectionSlug,
data: {
_status: 'published',
blocksField,
description: 'Description',
radio: 'test',
title: 'Published Title',
},
draft: false,
})
await _payload.create({
collection: draftCollectionSlug,
data: {
blocksField,
description: 'Description',
title: titleToDelete,
},
draft: true,
})
},
})
}

View File

@@ -1,76 +0,0 @@
import type { Config } from '../../../packages/payload/src/config/types'
import { devUser } from '../../credentials'
import { draftSlug, titleToDelete } from '../shared'
export const seed: Config['onInit'] = async (payload) => {
await payload.create({
collection: 'users',
data: {
email: devUser.email,
password: devUser.password,
},
})
const blocksField = [
{
blockType: 'block',
localized: 'text',
text: 'text',
},
]
await payload.create({
collection: draftSlug,
data: {
blocksField,
description: 'Description',
radio: 'test',
title: 'Draft Title',
},
draft: true,
})
const { id: manyDraftsID } = await payload.create({
collection: draftSlug,
data: {
blocksField,
description: 'Description',
radio: 'test',
title: 'Title With Many Versions',
},
draft: true,
})
for (let i = 0; i < 10; i++) {
await payload.update({
id: manyDraftsID,
collection: draftSlug,
data: {
title: `Title With Many Versions ${i + 2}`,
},
})
}
await payload.create({
collection: draftSlug,
data: {
_status: 'published',
blocksField,
description: 'Description',
radio: 'test',
title: 'Published Title',
},
draft: false,
})
await payload.create({
collection: draftSlug,
data: {
blocksField,
description: 'Description',
title: titleToDelete,
},
draft: true,
})
}

View File

@@ -1,9 +1 @@
export const postSlug = 'posts'
export const draftSlug = 'draft-posts'
export const autosaveSlug = 'autosave-posts'
export const versionSlug = 'version-posts'
export const autoSaveGlobalSlug = 'autosave-global'
export const draftGlobalSlug = 'draft-global'
export const titleToDelete = 'Title To Delete'

19
test/versions/slugs.ts Normal file
View File

@@ -0,0 +1,19 @@
export const autosaveCollectionSlug = 'autosave-posts' as const
export const draftCollectionSlug = 'draft-posts' as const
export const postCollectionSlug = 'posts' as const
export const versionCollectionSlug = 'version-posts' as const
export const collectionSlugs = [
autosaveCollectionSlug,
draftCollectionSlug,
postCollectionSlug,
versionCollectionSlug,
]
export const autoSaveGlobalSlug = 'autosave-global' as const
export const draftGlobalSlug = 'draft-global' as const
export const globalSlugs = [autoSaveGlobalSlug, draftGlobalSlug]