fix(ui): issues with prevent leave and autosave when the form is submitted but invalid (#11233)

Fixes https://github.com/payloadcms/payload/issues/11224
Fixes https://github.com/payloadcms/payload/issues/10492

This PR fixes a few weird behaviours when `validate: true` is set on drafts:
- when autosave is on and you submit an invalid form it would get stuck in an infinite loop
- PreventLeave would not trigger for submitted but invalid forms leading to potential data loss

Changes:
- Adds e2e tests for the above scenarios
- Adds a new `isValid` flag on the `Form` context provider to signal globally if the form is in a valid or invalid state
  - Components like Autosave will manage this internally since it manages its own submission flow as well
- Adds PreventLeave to Autosave too for when form is invalid meaning data hasn't been actually saved so we want to prevent the user accidentally losing data by reloading or closing the page


The following tests have been added
![image](https://github.com/user-attachments/assets/db208aa4-6ed6-4287-b200-59575cd3c9d0)
This commit is contained in:
Paul
2025-02-18 19:12:41 +00:00
committed by GitHub
parent ede7bd7b4b
commit 06debf5e14
13 changed files with 441 additions and 18 deletions

View File

@@ -22,6 +22,7 @@ import { useTranslation } from '../../providers/Translation/index.js'
import './index.scss'
import { formatTimeToNow } from '../../utilities/formatDate.js'
import { reduceFieldsToValuesWithValidation } from '../../utilities/reduceFieldsToValuesWithValidation.js'
import { LeaveWithoutSaving } from '../LeaveWithoutSaving/index.js'
const baseClass = 'autosave'
// The minimum time the saving state should be shown
@@ -55,25 +56,36 @@ export const Autosave: React.FC<Props> = ({ id, collection, global: globalDoc })
const isProcessingRef = useRef(false)
const { reportUpdate } = useDocumentEvents()
const { dispatchFields, setSubmitted } = useForm()
const submitted = useFormSubmitted()
const versionsConfig = docConfig?.versions
const { dispatchFields, isValid, setIsValid, setSubmitted } = useForm()
const [fields] = useAllFormFields()
const modified = useFormModified()
const submitted = useFormSubmitted()
const { code: locale } = useLocale()
const { i18n, t } = useTranslation()
const versionsConfig = docConfig?.versions
let interval = versionDefaults.autosaveInterval
if (versionsConfig.drafts && versionsConfig.drafts.autosave) {
interval = versionsConfig.drafts.autosave.interval
}
const validateOnDraft = Boolean(
docConfig?.versions?.drafts && docConfig?.versions?.drafts.validate,
)
const [saving, setSaving] = useState(false)
const debouncedFields = useDebounce(fields, interval)
const fieldRef = useRef(fields)
const modifiedRef = useRef(modified)
const localeRef = useRef(locale)
/**
* Track the validation internally so Autosave can determine when to run queue processing again
* Helps us prevent infinite loops when the queue is processing and the form is invalid
*/
const isValidRef = useRef(isValid)
const debouncedRef = useRef(debouncedFields)
debouncedRef.current = debouncedFields
@@ -97,6 +109,14 @@ export const Autosave: React.FC<Props> = ({ id, collection, global: globalDoc })
return
}
if (!isValidRef.current) {
// Clear queue so we don't end up in an infinite loop
queueRef.current = []
// Reset internal validation state so queue processing can run again
isValidRef.current = true
return
}
isProcessingRef.current = true
const latestAction = queueRef.current[queueRef.current.length - 1]
queueRef.current = []
@@ -149,7 +169,7 @@ export const Autosave: React.FC<Props> = ({ id, collection, global: globalDoc })
const skipSubmission =
submitted && !valid && versionsConfig?.drafts && versionsConfig?.drafts?.validate
if (!skipSubmission) {
if (!skipSubmission && isValidRef.current) {
await fetch(url, {
body: JSON.stringify(data),
credentials: 'include',
@@ -222,8 +242,11 @@ export const Autosave: React.FC<Props> = ({ id, collection, global: globalDoc })
toast.error(err.message || i18n.t('error:unknown'))
})
// Set valid to false internally so the queue doesn't process
isValidRef.current = false
setIsValid(false)
setSubmitted(true)
setSaving(false)
return
}
} else {
@@ -232,11 +255,15 @@ export const Autosave: React.FC<Props> = ({ id, collection, global: globalDoc })
// Manually update the data since this function doesn't fire the `submit` function from useForm
if (document) {
setIsValid(true)
// Reset internal state allowing the queue to process
isValidRef.current = true
updateSavedDocumentData(document)
}
}
})
.then(() => {
.finally(() => {
// If request was faster than minimum animation time, animate the difference
if (endTimestamp - startTimestamp < minimumAnimationTime) {
autosaveTimeout = setTimeout(
@@ -282,6 +309,7 @@ export const Autosave: React.FC<Props> = ({ id, collection, global: globalDoc })
return (
<div className={baseClass}>
{validateOnDraft && !isValid && <LeaveWithoutSaving />}
{saving && t('general:saving')}
{!saving && Boolean(lastUpdateTime) && (
<React.Fragment>

View File

@@ -1,7 +1,7 @@
'use client'
import React, { useCallback, useEffect } from 'react'
import { useFormModified } from '../../forms/Form/index.js'
import { useForm, useFormModified } from '../../forms/Form/index.js'
import { useAuth } from '../../providers/Auth/index.js'
import { useTranslation } from '../../providers/Translation/index.js'
import { Button } from '../Button/index.js'
@@ -59,11 +59,12 @@ const Component: React.FC<{
export const LeaveWithoutSaving: React.FC = () => {
const { closeModal } = useModal()
const modified = useFormModified()
const { isValid } = useForm()
const { user } = useAuth()
const [show, setShow] = React.useState(false)
const [hasAccepted, setHasAccepted] = React.useState(false)
const prevent = Boolean(modified && user)
const prevent = Boolean((modified || !isValid) && user)
const onPrevent = useCallback(() => {
setShow(true)

View File

@@ -96,6 +96,11 @@ export const Form: React.FC<FormProps> = (props) => {
const [disabled, setDisabled] = useState(disabledFromProps || false)
const [isMounted, setIsMounted] = useState(false)
const [modified, setModified] = useState(false)
/**
* Tracks wether the form state passes validation.
* For example the state could be submitted but invalid as field errors have been returned.
*/
const [isValid, setIsValid] = useState(true)
const [initializing, setInitializing] = useState(initializingFromProps)
const [processing, setProcessing] = useState(false)
const [submitted, setSubmitted] = useState(false)
@@ -177,6 +182,8 @@ export const Form: React.FC<FormProps> = (props) => {
dispatchFields({ type: 'REPLACE_STATE', state: validatedFieldState })
}
setIsValid(isValid)
return isValid
}, [collectionSlug, config, dispatchFields, id, operation, t, user, documentForm])
@@ -257,6 +264,8 @@ export const Form: React.FC<FormProps> = (props) => {
([, field]) => field.valid !== false,
)
setIsValid(isValid)
if (!isValid) {
setProcessing(false)
setSubmitted(true)
@@ -268,6 +277,7 @@ export const Form: React.FC<FormProps> = (props) => {
const isValid =
skipValidation || disableValidationOnSubmit ? true : await contextRef.current.validateForm()
setIsValid(isValid)
// If not valid, prevent submission
if (!isValid) {
errorToast(t('error:correctInvalidFields'))
@@ -408,6 +418,8 @@ export const Form: React.FC<FormProps> = (props) => {
[[], []],
)
setIsValid(false)
dispatchFields({
type: 'ADD_SERVER_ERRORS',
errors: fieldErrors,
@@ -615,6 +627,7 @@ export const Form: React.FC<FormProps> = (props) => {
contextRef.current.setModified = setModified
contextRef.current.setProcessing = setProcessing
contextRef.current.setSubmitted = setSubmitted
contextRef.current.setIsValid = setIsValid
contextRef.current.disabled = disabled
contextRef.current.setDisabled = setDisabled
contextRef.current.formRef = formRef
@@ -626,6 +639,7 @@ export const Form: React.FC<FormProps> = (props) => {
contextRef.current.replaceFieldRow = replaceFieldRow
contextRef.current.uuid = uuid
contextRef.current.initializing = initializing
contextRef.current.isValid = isValid
useEffect(() => {
setIsMounted(true)

View File

@@ -39,14 +39,17 @@ export const initContextState: Context = {
getFields: (): FormState => ({}),
getSiblingData,
initializing: undefined,
isValid: true,
removeFieldRow: () => undefined,
replaceFieldRow: () => undefined,
replaceState: () => undefined,
reset,
setDisabled: () => undefined,
setIsValid: () => undefined,
setModified,
setProcessing,
setSubmitted,
submit,
validateForm,
}

View File

@@ -227,6 +227,11 @@ export type Context = {
getFields: GetFields
getSiblingData: GetSiblingData
initializing: boolean
/**
* Tracks wether the form state passes validation.
* For example the state could be submitted but invalid as field errors have been returned.
*/
isValid: boolean
removeFieldRow: ({ path, rowIndex }: { path: string; rowIndex: number }) => void
replaceFieldRow: ({
blockType,
@@ -244,6 +249,7 @@ export type Context = {
replaceState: (state: FormState) => void
reset: Reset
setDisabled: (disabled: boolean) => void
setIsValid: (processing: boolean) => void
setModified: SetModified
setProcessing: SetProcessing
setSubmitted: SetSubmitted

View File

@@ -4,20 +4,32 @@ import { expect } from '@playwright/test'
import { wait } from 'payload/shared'
import { POLL_TOPASS_TIMEOUT } from 'playwright.config.js'
export async function waitForAutoSaveToRunAndComplete(page: Page) {
export async function waitForAutoSaveToRunAndComplete(
page: Page,
expectation: 'error' | 'success' = 'success',
) {
await expect(async () => {
await expect(page.locator('.autosave:has-text("Saving...")')).toBeVisible()
}).toPass({
timeout: POLL_TOPASS_TIMEOUT,
intervals: [50],
})
await wait(500)
await expect(async () => {
await expect(
page.locator('.autosave:has-text("Last saved less than a minute ago")'),
).toBeVisible()
}).toPass({
timeout: POLL_TOPASS_TIMEOUT,
})
if (expectation === 'success') {
await expect(async () => {
await expect(
page.locator('.autosave:has-text("Last saved less than a minute ago")'),
).toBeVisible()
}).toPass({
timeout: POLL_TOPASS_TIMEOUT,
})
} else {
await expect(async () => {
await expect(page.locator('.payload-toast-container .toast-error')).toBeVisible()
}).toPass({
timeout: POLL_TOPASS_TIMEOUT,
})
}
}

View File

@@ -0,0 +1,33 @@
import type { CollectionConfig } from 'payload'
import { autosaveWithValidateCollectionSlug } from '../slugs.js'
const AutosaveWithValidatePosts: CollectionConfig = {
slug: autosaveWithValidateCollectionSlug,
labels: {
singular: 'Autosave with Validate Post',
plural: 'Autosave with Validate Posts',
},
admin: {
useAsTitle: 'title',
defaultColumns: ['title', 'subtitle', 'createdAt', '_status'],
},
versions: {
maxPerDoc: 35,
drafts: {
validate: true,
autosave: {
interval: 250,
},
},
},
fields: [
{
name: 'title',
type: 'text',
required: true,
},
],
}
export default AutosaveWithValidatePosts

View File

@@ -0,0 +1,21 @@
import type { CollectionConfig } from 'payload'
import { draftWithValidateCollectionSlug } from '../slugs.js'
const DraftsWithValidate: CollectionConfig = {
slug: draftWithValidateCollectionSlug,
fields: [
{
name: 'title',
type: 'text',
required: true,
},
],
versions: {
drafts: {
validate: true,
},
},
}
export default DraftsWithValidate

View File

@@ -4,11 +4,13 @@ const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js'
import AutosavePosts from './collections/Autosave.js'
import AutosaveWithValidate from './collections/AutosaveWithValidate.js'
import CustomIDs from './collections/CustomIDs.js'
import { Diff } from './collections/Diff.js'
import DisablePublish from './collections/DisablePublish.js'
import DraftPosts from './collections/Drafts.js'
import DraftWithMax from './collections/DraftsWithMax.js'
import DraftsWithValidate from './collections/DraftsWithValidate.js'
import LocalizedPosts from './collections/Localized.js'
import { Media } from './collections/Media.js'
import Posts from './collections/Posts.js'
@@ -32,8 +34,10 @@ export default buildConfigWithDefaults({
DisablePublish,
Posts,
AutosavePosts,
AutosaveWithValidate,
DraftPosts,
DraftWithMax,
DraftsWithValidate,
LocalizedPosts,
VersionPosts,
CustomIDs,

View File

@@ -22,7 +22,7 @@
* - specify locales to show
*/
import type { BrowserContext, Page } from '@playwright/test'
import type { BrowserContext, Dialog, Page } from '@playwright/test'
import { expect, test } from '@playwright/test'
import path from 'path'
@@ -51,6 +51,7 @@ import { titleToDelete } from './shared.js'
import {
autosaveCollectionSlug,
autoSaveGlobalSlug,
autosaveWithValidateCollectionSlug,
customIDSlug,
diffCollectionSlug,
disablePublishGlobalSlug,
@@ -59,6 +60,7 @@ import {
draftGlobalSlug,
draftWithMaxCollectionSlug,
draftWithMaxGlobalSlug,
draftWithValidateCollectionSlug,
localizedCollectionSlug,
localizedGlobalSlug,
postCollectionSlug,
@@ -79,6 +81,8 @@ describe('Versions', () => {
let url: AdminUrlUtil
let serverURL: string
let autosaveURL: AdminUrlUtil
let autosaveWithValidateURL: AdminUrlUtil
let draftWithValidateURL: AdminUrlUtil
let disablePublishURL: AdminUrlUtil
let customIDURL: AdminUrlUtil
let postURL: AdminUrlUtil
@@ -115,6 +119,7 @@ describe('Versions', () => {
beforeAll(() => {
url = new AdminUrlUtil(serverURL, draftCollectionSlug)
autosaveURL = new AdminUrlUtil(serverURL, autosaveCollectionSlug)
autosaveWithValidateURL = new AdminUrlUtil(serverURL, autosaveWithValidateCollectionSlug)
disablePublishURL = new AdminUrlUtil(serverURL, disablePublishSlug)
customIDURL = new AdminUrlUtil(serverURL, customIDSlug)
postURL = new AdminUrlUtil(serverURL, postCollectionSlug)
@@ -812,6 +817,232 @@ describe('Versions', () => {
})
})
describe('Collections with draft validation', () => {
beforeAll(() => {
autosaveWithValidateURL = new AdminUrlUtil(serverURL, autosaveWithValidateCollectionSlug)
draftWithValidateURL = new AdminUrlUtil(serverURL, draftWithValidateCollectionSlug)
})
test('- can save', async () => {
await page.goto(draftWithValidateURL.create)
const titleField = page.locator('#field-title')
await titleField.fill('Initial')
await saveDocAndAssert(page, '#action-save-draft')
await expect(titleField).toBeEnabled()
await titleField.fill('New title')
await saveDocAndAssert(page, '#action-save-draft')
await page.reload()
// Ensure its saved
await expect(page.locator('#field-title')).toHaveValue('New title')
})
test('- can safely trigger validation errors and then continue editing', async () => {
await page.goto(draftWithValidateURL.create)
const titleField = page.locator('#field-title')
await titleField.fill('Initial')
await saveDocAndAssert(page, '#action-save-draft')
await page.reload()
await expect(titleField).toBeEnabled()
await titleField.fill('')
await saveDocAndAssert(page, '#action-save-draft', 'error')
await titleField.fill('New title')
await saveDocAndAssert(page, '#action-save-draft')
await page.reload()
// Ensure its saved
await expect(page.locator('#field-title')).toHaveValue('New title')
})
test('- shows a prevent leave alert when form is submitted but invalid', async () => {
await page.goto(draftWithValidateURL.create)
// Flag to check against if window alert has been displayed and dismissed since we can only check via events
let alertDisplayed = false
async function dismissAlert(dialog: Dialog) {
alertDisplayed = true
await dialog.dismiss()
}
async function acceptAlert(dialog: Dialog) {
await dialog.accept()
}
const titleField = page.locator('#field-title')
await titleField.fill('Initial')
await saveDocAndAssert(page, '#action-save-draft')
// Remove required data, then let autosave trigger
await expect(titleField).toBeEnabled()
await titleField.fill('')
await saveDocAndAssert(page, '#action-save-draft', 'error')
// Expect the prevent leave and then dismiss it
page.on('dialog', dismissAlert)
await expect(async () => {
await page.reload({ timeout: 500 }) // custom short timeout since we want this to fail
}).not.toPass({
timeout: POLL_TOPASS_TIMEOUT,
})
await expect(() => {
expect(alertDisplayed).toEqual(true)
}).toPass({
timeout: POLL_TOPASS_TIMEOUT,
})
// Remove event listener and reset our flag
page.removeListener('dialog', dismissAlert)
await expect(page.locator('#field-title')).toHaveValue('')
// Now has updated data
await titleField.fill('New title')
await saveDocAndAssert(page, '#action-save-draft')
await expect(page.locator('#field-title')).toHaveValue('New title')
await page.reload()
page.on('dialog', acceptAlert)
// Ensure data is saved
await expect(page.locator('#field-title')).toHaveValue('New title')
// Fill with invalid data again, then reload and accept the warning, should contain previous data
await titleField.fill('')
await page.reload()
await expect(titleField).toBeEnabled()
// Contains previous data
await expect(page.locator('#field-title')).toHaveValue('New title')
// Remove listener
page.removeListener('dialog', acceptAlert)
})
test('- with autosave - can save', async () => {
await page.goto(autosaveWithValidateURL.create)
const titleField = page.locator('#field-title')
await titleField.fill('Initial')
await saveDocAndAssert(page, '#action-save-draft')
await expect(titleField).toBeEnabled()
await titleField.fill('New title')
await waitForAutoSaveToRunAndComplete(page)
await page.reload()
// Ensure its saved
await expect(page.locator('#field-title')).toHaveValue('New title')
})
test('- with autosave - can safely trigger validation errors and then continue editing', async () => {
// This test has to make sure we don't enter an infinite loop when draft.validate is on and we have autosave enabled
await page.goto(autosaveWithValidateURL.create)
const titleField = page.locator('#field-title')
await titleField.fill('Initial')
await saveDocAndAssert(page, '#action-save-draft')
await page.reload()
await expect(titleField).toBeEnabled()
await titleField.fill('')
await waitForAutoSaveToRunAndComplete(page, 'error')
await titleField.fill('New title')
await waitForAutoSaveToRunAndComplete(page)
await page.reload()
// Ensure its saved
await expect(page.locator('#field-title')).toHaveValue('New title')
})
test('- with autosave - shows a prevent leave alert when form is submitted but invalid', async () => {
await page.goto(autosaveWithValidateURL.create)
// Flag to check against if window alert has been displayed and dismissed since we can only check via events
let alertDisplayed = false
async function dismissAlert(dialog: Dialog) {
alertDisplayed = true
await dialog.dismiss()
}
async function acceptAlert(dialog: Dialog) {
await dialog.accept()
}
const titleField = page.locator('#field-title')
await titleField.fill('Initial')
await saveDocAndAssert(page, '#action-save-draft')
// Remove required data, then let autosave trigger
await expect(titleField).toBeEnabled()
await titleField.fill('')
await waitForAutoSaveToRunAndComplete(page, 'error')
// Expect the prevent leave and then dismiss it
page.on('dialog', dismissAlert)
await expect(async () => {
await page.reload({ timeout: 500 }) // custom short timeout since we want this to fail
}).not.toPass({
timeout: POLL_TOPASS_TIMEOUT,
})
await expect(() => {
expect(alertDisplayed).toEqual(true)
}).toPass({
timeout: POLL_TOPASS_TIMEOUT,
})
// Remove event listener and reset our flag
page.removeListener('dialog', dismissAlert)
await expect(page.locator('#field-title')).toHaveValue('')
// Now has updated data
await titleField.fill('New title')
await waitForAutoSaveToRunAndComplete(page)
await expect(page.locator('#field-title')).toHaveValue('New title')
await page.reload()
page.on('dialog', acceptAlert)
// Ensure data is saved
await expect(page.locator('#field-title')).toHaveValue('New title')
// Fill with invalid data again, then reload and accept the warning, should contain previous data
await titleField.fill('')
await page.reload()
await expect(titleField).toBeEnabled()
// Contains previous data
await expect(page.locator('#field-title')).toHaveValue('New title')
// Remove listener
page.removeListener('dialog', acceptAlert)
})
})
describe('Globals - publish individual locale', () => {
beforeAll(() => {
url = new AdminUrlUtil(serverURL, localizedGlobalSlug)

View File

@@ -69,8 +69,10 @@ export interface Config {
'disable-publish': DisablePublish;
posts: Post;
'autosave-posts': AutosavePost;
'autosave-with-validate-posts': AutosaveWithValidatePost;
'draft-posts': DraftPost;
'draft-with-max-posts': DraftWithMaxPost;
'draft-with-validate-posts': DraftWithValidatePost;
'localized-posts': LocalizedPost;
'version-posts': VersionPost;
'custom-ids': CustomId;
@@ -87,8 +89,10 @@ export interface Config {
'disable-publish': DisablePublishSelect<false> | DisablePublishSelect<true>;
posts: PostsSelect<false> | PostsSelect<true>;
'autosave-posts': AutosavePostsSelect<false> | AutosavePostsSelect<true>;
'autosave-with-validate-posts': AutosaveWithValidatePostsSelect<false> | AutosaveWithValidatePostsSelect<true>;
'draft-posts': DraftPostsSelect<false> | DraftPostsSelect<true>;
'draft-with-max-posts': DraftWithMaxPostsSelect<false> | DraftWithMaxPostsSelect<true>;
'draft-with-validate-posts': DraftWithValidatePostsSelect<false> | DraftWithValidatePostsSelect<true>;
'localized-posts': LocalizedPostsSelect<false> | LocalizedPostsSelect<true>;
'version-posts': VersionPostsSelect<false> | VersionPostsSelect<true>;
'custom-ids': CustomIdsSelect<false> | CustomIdsSelect<true>;
@@ -221,6 +225,17 @@ export interface DraftPost {
createdAt: string;
_status?: ('draft' | 'published') | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "autosave-with-validate-posts".
*/
export interface AutosaveWithValidatePost {
id: string;
title: string;
updatedAt: string;
createdAt: string;
_status?: ('draft' | 'published') | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "draft-with-max-posts".
@@ -245,6 +260,17 @@ export interface DraftWithMaxPost {
createdAt: string;
_status?: ('draft' | 'published') | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "draft-with-validate-posts".
*/
export interface DraftWithValidatePost {
id: string;
title: string;
updatedAt: string;
createdAt: string;
_status?: ('draft' | 'published') | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "localized-posts".
@@ -497,6 +523,10 @@ export interface PayloadLockedDocument {
relationTo: 'autosave-posts';
value: string | AutosavePost;
} | null)
| ({
relationTo: 'autosave-with-validate-posts';
value: string | AutosaveWithValidatePost;
} | null)
| ({
relationTo: 'draft-posts';
value: string | DraftPost;
@@ -505,6 +535,10 @@ export interface PayloadLockedDocument {
relationTo: 'draft-with-max-posts';
value: string | DraftWithMaxPost;
} | null)
| ({
relationTo: 'draft-with-validate-posts';
value: string | DraftWithValidatePost;
} | null)
| ({
relationTo: 'localized-posts';
value: string | LocalizedPost;
@@ -607,6 +641,16 @@ export interface AutosavePostsSelect<T extends boolean = true> {
createdAt?: T;
_status?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "autosave-with-validate-posts_select".
*/
export interface AutosaveWithValidatePostsSelect<T extends boolean = true> {
title?: T;
updatedAt?: T;
createdAt?: T;
_status?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "draft-posts_select".
@@ -660,6 +704,16 @@ export interface DraftWithMaxPostsSelect<T extends boolean = true> {
createdAt?: T;
_status?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "draft-with-validate-posts_select".
*/
export interface DraftWithValidatePostsSelect<T extends boolean = true> {
title?: T;
updatedAt?: T;
createdAt?: T;
_status?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "localized-posts_select".

View File

@@ -7,7 +7,12 @@ import type { DraftPost } from './payload-types.js'
import { devUser } from '../credentials.js'
import { executePromises } from '../helpers/executePromises.js'
import { titleToDelete } from './shared.js'
import { diffCollectionSlug, draftCollectionSlug, mediaCollectionSlug } from './slugs.js'
import {
autosaveWithValidateCollectionSlug,
diffCollectionSlug,
draftCollectionSlug,
mediaCollectionSlug,
} from './slugs.js'
import { textToLexicalJSON } from './textToLexicalJSON.js'
const filename = fileURLToPath(import.meta.url)
@@ -120,6 +125,13 @@ export async function seed(_payload: Payload, parallel: boolean = false) {
draft: true,
})
await _payload.create({
collection: autosaveWithValidateCollectionSlug,
data: {
title: 'Initial seeded title',
},
})
const diffDoc = await _payload.create({
collection: diffCollectionSlug,
locale: 'en',

View File

@@ -1,8 +1,12 @@
export const autosaveCollectionSlug = 'autosave-posts'
export const autosaveWithValidateCollectionSlug = 'autosave-with-validate-posts'
export const customIDSlug = 'custom-ids'
export const draftCollectionSlug = 'draft-posts'
export const draftWithValidateCollectionSlug = 'draft-with-validate-posts'
export const draftWithMaxCollectionSlug = 'draft-with-max-posts'
export const postCollectionSlug = 'posts'