feat: draft validation (#6746)

Allows draft validation to be enabled at the config level.

You can enable this by:

```ts
// ...collectionConfig
versions: {
  drafts: {
    validate: true // defaults to false
  }
}
```
This commit is contained in:
Jarrod Flesch
2024-06-13 11:08:04 -04:00
committed by GitHub
parent e40570bd0d
commit ff70fd9813
27 changed files with 358 additions and 71 deletions

View File

@@ -5,11 +5,16 @@ import { mediaSlug } from '../Media'
export const postsSlug = 'posts'
export const PostsCollection: CollectionConfig = {
defaultSort: 'title',
fields: [
{
name: 'text',
type: 'text',
},
{
name: 'title',
type: 'text',
},
{
name: 'associatedMedia',
access: {

View File

@@ -32,6 +32,23 @@ export default buildConfigWithDefaults({
collection: postsSlug,
data: {
text: 'example post',
title: 'title1',
},
})
await payload.create({
collection: postsSlug,
data: {
text: 'example post',
title: 'title3',
},
})
await payload.create({
collection: postsSlug,
data: {
text: 'example post',
title: 'title2',
},
})
},

View File

@@ -0,0 +1,12 @@
import type { CollectionConfig } from '../../../../packages/payload/src/collections/config/types'
import { slugs } from '../../shared'
import { ValidateDraftsOn } from '../ValidateDraftsOn'
export const ValidateDraftsOff: CollectionConfig = {
...ValidateDraftsOn,
slug: slugs.validateDraftsOff,
versions: {
drafts: true,
},
}

View File

@@ -0,0 +1,19 @@
import type { CollectionConfig } from '../../../../packages/payload/src/collections/config/types'
import { slugs } from '../../shared'
export const ValidateDraftsOn: CollectionConfig = {
slug: slugs.validateDraftsOn,
fields: [
{
name: 'title',
type: 'text',
required: true,
},
],
versions: {
drafts: {
validate: true,
},
},
}

View File

@@ -0,0 +1,15 @@
import type { CollectionConfig } from '../../../../packages/payload/src/collections/config/types'
import { slugs } from '../../shared'
import { ValidateDraftsOn } from '../ValidateDraftsOn'
export const ValidateDraftsOnAndAutosave: CollectionConfig = {
...ValidateDraftsOn,
slug: slugs.validateDraftsOnAutosave,
versions: {
drafts: {
autosave: true,
validate: true,
},
},
}

View File

@@ -2,9 +2,18 @@ import { buildConfigWithDefaults } from '../buildConfigWithDefaults'
import { devUser } from '../credentials'
import { ErrorFieldsCollection } from './collections/ErrorFields'
import Uploads from './collections/Upload'
import { ValidateDraftsOff } from './collections/ValidateDraftsOff'
import { ValidateDraftsOn } from './collections/ValidateDraftsOn'
import { ValidateDraftsOnAndAutosave } from './collections/ValidateDraftsOnAutosave'
export default buildConfigWithDefaults({
collections: [ErrorFieldsCollection, Uploads],
collections: [
ErrorFieldsCollection,
Uploads,
ValidateDraftsOn,
ValidateDraftsOff,
ValidateDraftsOnAndAutosave,
],
graphQL: {
schemaOutputFile: './test/field-error-states/schema.graphql',
},

View File

@@ -2,17 +2,26 @@ import type { Page } from '@playwright/test'
import { expect, test } from '@playwright/test'
import { initPageConsoleErrorCatch } from '../helpers'
import { initPageConsoleErrorCatch, saveDocAndAssert } from '../helpers'
import { AdminUrlUtil } from '../helpers/adminUrlUtil'
import { initPayloadE2E } from '../helpers/configHelpers'
import { slugs } from './shared'
const { beforeAll, describe } = test
let validateDraftsOff: AdminUrlUtil
let validateDraftsOn: AdminUrlUtil
let validateDraftsOnAutosave: AdminUrlUtil
describe('field error states', () => {
let serverURL: string
let page: Page
beforeAll(async ({ browser }) => {
;({ serverURL } = await initPayloadE2E(__dirname))
validateDraftsOff = new AdminUrlUtil(serverURL, slugs.validateDraftsOff)
validateDraftsOn = new AdminUrlUtil(serverURL, slugs.validateDraftsOn)
validateDraftsOnAutosave = new AdminUrlUtil(serverURL, slugs.validateDraftsOnAutosave)
const context = await browser.newContext()
page = await context.newPage()
initPageConsoleErrorCatch(page)
@@ -49,4 +58,31 @@ describe('field error states', () => {
)
expect(errorPill).toBeNull()
})
describe('draft validations', () => {
// eslint-disable-next-line playwright/expect-expect
test('should not validate drafts by default', async () => {
await page.goto(validateDraftsOff.create)
await page.locator('#field-title').fill('temp')
await page.locator('#field-title').fill('')
await saveDocAndAssert(page, '#action-save-draft')
})
// eslint-disable-next-line playwright/expect-expect
test('should validate drafts when enabled', async () => {
await page.goto(validateDraftsOn.create)
await page.locator('#field-title').fill('temp')
await page.locator('#field-title').fill('')
await saveDocAndAssert(page, '#action-save-draft', 'error')
})
// eslint-disable-next-line playwright/expect-expect
test('should show validation errors when validate and autosave are enabled', async () => {
await page.goto(validateDraftsOnAutosave.create)
await page.locator('#field-title').fill('valid')
await saveDocAndAssert(page)
await page.locator('#field-title').fill('')
await saveDocAndAssert(page, '#action-save', 'error')
})
})
})

View File

@@ -0,0 +1,5 @@
export const slugs = {
validateDraftsOn: 'validate-drafts-on',
validateDraftsOnAutosave: 'validate-drafts-on-autosave',
validateDraftsOff: 'validate-drafts-off',
}

View File

@@ -1,29 +0,0 @@
import type { Page } from '@playwright/test'
import { expect, test } from '@playwright/test'
import { initPageConsoleErrorCatch } from '../helpers'
import { AdminUrlUtil } from '../helpers/adminUrlUtil'
import { initPayloadE2E } from '../helpers/configHelpers'
const { beforeAll, describe } = test
describe('Globals', () => {
let page: Page
let url: AdminUrlUtil
beforeAll(async ({ browser }) => {
const { serverURL } = await initPayloadE2E(__dirname)
url = new AdminUrlUtil(serverURL, 'media')
const context = await browser.newContext()
page = await context.newPage()
initPageConsoleErrorCatch(page)
})
test('can edit media from field', async () => {
await page.goto(url.create)
// const textCell = page.locator('.row-1 .cell-text')
})
})

View File

@@ -51,10 +51,18 @@ export async function saveDocHotkeyAndAssert(page: Page): Promise<void> {
await expect(page.locator('.Toastify')).toContainText('successfully')
}
export async function saveDocAndAssert(page: Page, selector = '#action-save'): Promise<void> {
export async function saveDocAndAssert(
page: Page,
selector = '#action-save',
expectation: 'error' | 'success' = 'success',
): Promise<void> {
await page.click(selector, { delay: 100 })
await expect(page.locator('.Toastify')).toContainText('successfully')
expect(page.url()).not.toContain('create')
if (expectation === 'success') {
await expect(page.locator('.Toastify')).toContainText('successfully')
expect(page.url()).not.toContain('create')
} else {
await expect(page.locator('.Toastify .Toastify__toast--error')).toBeVisible()
}
}
export async function openNav(page: Page): Promise<void> {

View File

@@ -359,18 +359,18 @@ describe('versions', () => {
await page.goto(autosaveURL.create)
await page.locator('#field-title').fill(title)
await page.locator('#field-description').fill(description)
await wait(500) // wait for autosave
await wait(1000) // wait for autosave
await changeLocale(page, spanishLocale)
await page.locator('#field-title').fill(spanishTitle)
await wait(500) // wait for autosave
await wait(1000) // wait for autosave
await changeLocale(page, locale)
await page.locator('#field-description').fill(newDescription)
await wait(500) // wait for autosave
await wait(1000) // wait for autosave
await changeLocale(page, spanishLocale)
await wait(500) // wait for autosave
await wait(1000) // wait for autosave
await page.reload()
await expect(page.locator('#field-title')).toHaveValue(spanishTitle)