Files
payloadcms/test/form-state/int.spec.ts
T. R. Bernstein 4a5f01a78f
Some checks failed
ci / changes (push) Has been cancelled
ci / lint (push) Has been cancelled
ci / build (push) Has been cancelled
ci / tests-unit (push) Has been cancelled
ci / tests-types (push) Has been cancelled
ci / int-cosmosdb (push) Has been cancelled
ci / int-documentdb (push) Has been cancelled
ci / int-firestore (push) Has been cancelled
ci / int-mongodb (push) Has been cancelled
ci / int-postgres (push) Has been cancelled
ci / int-postgres-custom-schema (push) Has been cancelled
ci / int-postgres-uuid (push) Has been cancelled
ci / int-sqlite (push) Has been cancelled
ci / int-sqlite-uuid (push) Has been cancelled
ci / int-supabase (push) Has been cancelled
ci / e2e-_community (push) Has been cancelled
ci / e2e-access-control (push) Has been cancelled
ci / e2e-admin-bar (push) Has been cancelled
ci / e2e-admin-root (push) Has been cancelled
ci / e2e-admin__e2e__document-view (push) Has been cancelled
ci / e2e-admin__e2e__general (push) Has been cancelled
ci / e2e-admin__e2e__list-view (push) Has been cancelled
ci / e2e-auth (push) Has been cancelled
ci / e2e-auth-basic (push) Has been cancelled
ci / e2e-bulk-edit (push) Has been cancelled
ci / e2e-field-error-states (push) Has been cancelled
ci / e2e-fields-relationship (push) Has been cancelled
ci / e2e-fields__collections__Array (push) Has been cancelled
ci / e2e-fields__collections__Blocks#config.blockreferences.ts (push) Has been cancelled
ci / e2e-fields__collections__Blocks (push) Has been cancelled
ci / e2e-fields__collections__Checkbox (push) Has been cancelled
ci / e2e-fields__collections__Collapsible (push) Has been cancelled
ci / e2e-fields__collections__ConditionalLogic (push) Has been cancelled
ci / e2e-fields__collections__CustomID (push) Has been cancelled
ci / e2e-fields__collections__Date (push) Has been cancelled
ci / e2e-fields__collections__Email (push) Has been cancelled
ci / e2e-fields__collections__Indexed (push) Has been cancelled
ci / e2e-fields__collections__JSON (push) Has been cancelled
ci / e2e-fields__collections__Number (push) Has been cancelled
ci / e2e-fields__collections__Point (push) Has been cancelled
ci / e2e-fields__collections__Radio (push) Has been cancelled
ci / e2e-fields__collections__Relationship (push) Has been cancelled
ci / e2e-fields__collections__Row (push) Has been cancelled
ci / e2e-fields__collections__Select (push) Has been cancelled
ci / e2e-fields__collections__Tabs (push) Has been cancelled
ci / e2e-fields__collections__Tabs2 (push) Has been cancelled
ci / e2e-fields__collections__Text (push) Has been cancelled
ci / e2e-fields__collections__UI (push) Has been cancelled
ci / e2e-fields__collections__Upload (push) Has been cancelled
ci / e2e-folders (push) Has been cancelled
ci / e2e-form-state (push) Has been cancelled
ci / e2e-group-by (push) Has been cancelled
ci / e2e-hooks (push) Has been cancelled
ci / e2e-i18n (push) Has been cancelled
ci / e2e-joins (push) Has been cancelled
ci / e2e-lexical__collections__LexicalHeadingFeature (push) Has been cancelled
ci / e2e-lexical__collections__LexicalJSXConverter (push) Has been cancelled
ci / e2e-lexical__collections__LexicalLinkFeature (push) Has been cancelled
ci / e2e-lexical__collections__Lexical__e2e__blocks#config.blockreferences.ts (push) Has been cancelled
ci / e2e-lexical__collections__Lexical__e2e__blocks (push) Has been cancelled
ci / e2e-lexical__collections__Lexical__e2e__main (push) Has been cancelled
ci / e2e-lexical__collections__OnDemandForm (push) Has been cancelled
ci / e2e-lexical__collections__RichText (push) Has been cancelled
ci / e2e-lexical__collections___LexicalFullyFeatured (push) Has been cancelled
ci / e2e-lexical__collections___LexicalFullyFeatured__db (push) Has been cancelled
ci / e2e-live-preview (push) Has been cancelled
ci / e2e-localization (push) Has been cancelled
ci / e2e-locked-documents (push) Has been cancelled
ci / e2e-plugin-cloud-storage (push) Has been cancelled
ci / e2e-plugin-form-builder (push) Has been cancelled
ci / e2e-plugin-import-export (push) Has been cancelled
ci / e2e-plugin-multi-tenant (push) Has been cancelled
ci / e2e-plugin-nested-docs (push) Has been cancelled
ci / e2e-plugin-seo (push) Has been cancelled
ci / e2e-query-presets (push) Has been cancelled
ci / e2e-sort (push) Has been cancelled
ci / e2e-trash (push) Has been cancelled
ci / e2e-uploads (push) Has been cancelled
ci / e2e-versions (push) Has been cancelled
ci / e2e-turbo-_community (push) Has been cancelled
ci / e2e-turbo-access-control (push) Has been cancelled
ci / e2e-turbo-admin-bar (push) Has been cancelled
ci / e2e-turbo-admin-root (push) Has been cancelled
ci / e2e-turbo-admin__e2e__document-view (push) Has been cancelled
ci / e2e-turbo-admin__e2e__general (push) Has been cancelled
ci / e2e-turbo-admin__e2e__list-view (push) Has been cancelled
ci / e2e-turbo-auth (push) Has been cancelled
ci / e2e-turbo-auth-basic (push) Has been cancelled
ci / e2e-turbo-bulk-edit (push) Has been cancelled
ci / e2e-turbo-field-error-states (push) Has been cancelled
ci / e2e-turbo-fields-relationship (push) Has been cancelled
ci / e2e-turbo-fields__collections__Array (push) Has been cancelled
ci / e2e-turbo-fields__collections__Blocks#config.blockreferences.ts (push) Has been cancelled
ci / e2e-turbo-fields__collections__Blocks (push) Has been cancelled
ci / e2e-turbo-fields__collections__Checkbox (push) Has been cancelled
ci / e2e-turbo-fields__collections__Collapsible (push) Has been cancelled
ci / e2e-turbo-fields__collections__ConditionalLogic (push) Has been cancelled
ci / e2e-turbo-fields__collections__CustomID (push) Has been cancelled
ci / e2e-turbo-fields__collections__Date (push) Has been cancelled
ci / e2e-turbo-fields__collections__Email (push) Has been cancelled
ci / e2e-turbo-fields__collections__Indexed (push) Has been cancelled
ci / e2e-turbo-fields__collections__JSON (push) Has been cancelled
ci / e2e-turbo-fields__collections__Number (push) Has been cancelled
ci / e2e-turbo-fields__collections__Point (push) Has been cancelled
ci / e2e-turbo-fields__collections__Radio (push) Has been cancelled
ci / e2e-turbo-fields__collections__Relationship (push) Has been cancelled
ci / e2e-turbo-fields__collections__Row (push) Has been cancelled
ci / e2e-turbo-fields__collections__Select (push) Has been cancelled
ci / e2e-turbo-fields__collections__Tabs (push) Has been cancelled
ci / e2e-turbo-fields__collections__Tabs2 (push) Has been cancelled
ci / e2e-turbo-fields__collections__Text (push) Has been cancelled
ci / e2e-turbo-fields__collections__UI (push) Has been cancelled
ci / e2e-turbo-fields__collections__Upload (push) Has been cancelled
ci / e2e-turbo-folders (push) Has been cancelled
ci / e2e-turbo-form-state (push) Has been cancelled
ci / e2e-turbo-group-by (push) Has been cancelled
ci / e2e-turbo-hooks (push) Has been cancelled
ci / e2e-turbo-i18n (push) Has been cancelled
ci / e2e-turbo-joins (push) Has been cancelled
ci / e2e-turbo-lexical__collections__LexicalHeadingFeature (push) Has been cancelled
ci / e2e-turbo-lexical__collections__LexicalJSXConverter (push) Has been cancelled
ci / e2e-turbo-lexical__collections__LexicalLinkFeature (push) Has been cancelled
ci / e2e-turbo-lexical__collections__Lexical__e2e__blocks#config.blockreferences.ts (push) Has been cancelled
ci / e2e-turbo-lexical__collections__Lexical__e2e__blocks (push) Has been cancelled
ci / e2e-turbo-lexical__collections__Lexical__e2e__main (push) Has been cancelled
ci / e2e-turbo-lexical__collections__OnDemandForm (push) Has been cancelled
ci / e2e-turbo-lexical__collections__RichText (push) Has been cancelled
ci / e2e-turbo-lexical__collections___LexicalFullyFeatured (push) Has been cancelled
ci / e2e-turbo-lexical__collections___LexicalFullyFeatured__db (push) Has been cancelled
ci / e2e-turbo-live-preview (push) Has been cancelled
ci / e2e-turbo-localization (push) Has been cancelled
ci / e2e-turbo-locked-documents (push) Has been cancelled
ci / e2e-turbo-plugin-cloud-storage (push) Has been cancelled
ci / e2e-turbo-plugin-form-builder (push) Has been cancelled
ci / e2e-turbo-plugin-import-export (push) Has been cancelled
ci / e2e-turbo-plugin-multi-tenant (push) Has been cancelled
ci / e2e-turbo-plugin-nested-docs (push) Has been cancelled
ci / e2e-turbo-plugin-seo (push) Has been cancelled
ci / e2e-turbo-query-presets (push) Has been cancelled
ci / e2e-turbo-sort (push) Has been cancelled
ci / e2e-turbo-trash (push) Has been cancelled
ci / e2e-turbo-uploads (push) Has been cancelled
ci / e2e-turbo-versions (push) Has been cancelled
ci / build-template-blank-mongodb (push) Has been cancelled
ci / build-template-website-mongodb (push) Has been cancelled
ci / build-template-with-payload-cloud-mongodb (push) Has been cancelled
ci / build-template-with-vercel-mongodb-mongodb (push) Has been cancelled
ci / build-template-plugin- (push) Has been cancelled
ci / build-template-with-postgres-postgres (push) Has been cancelled
ci / build-template-with-vercel-postgres-postgres (push) Has been cancelled
ci / tests-type-generation (push) Has been cancelled
ci / All Green (push) Has been cancelled
ci / Publish Canary (push) Has been cancelled
ci / analyze (push) Has been cancelled
publish-prerelease / publish-prerelease-${{ github.ref_name }}-${{ github.sha }} (push) Has been cancelled
lock-issues / lock_issues (push) Has been cancelled
stale / stale (push) Has been cancelled
audit-dependencies / audit (push) Has been cancelled
activity-notifications / run (push) Has been cancelled
chore: Update code to new repo
2025-10-08 23:27:45 +02:00

773 lines
18 KiB
TypeScript

import type { FieldState, FormState, Payload, User } from 'tbsh-cms'
import type React from 'react'
import { buildFormState } from '@tabshiftcms/ui/utilities/buildFormState'
import path from 'path'
import { createLocalReq } from 'tbsh-cms'
import { fileURLToPath } from 'url'
import type { NextRESTClient } from '../helpers/NextRESTClient.js'
import { devUser } from '../credentials.js'
import { initPayloadInt } from '../helpers/initPayloadInt.js'
import { postsSlug } from './collections/Posts/index.js'
// eslint-disable-next-line payload/no-relative-monorepo-imports
import { mergeServerFormState } from '../../packages/ui/src/forms/Form/mergeServerFormState.js'
let payload: Payload
let restClient: NextRESTClient
let user: User
const { email, password } = devUser
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
const DummyReactComponent: React.ReactNode = {
// @ts-expect-error - can ignore, needs to satisfy `typeof value.$$typeof === 'symbol'`
$$typeof: Symbol.for('react.element'),
type: 'div',
props: {},
key: null,
}
describe('Form State', () => {
beforeAll(async () => {
;({ payload, restClient } = await initPayloadInt(dirname, undefined, true))
const data = await restClient
.POST('/users/login', {
body: JSON.stringify({
email,
password,
}),
})
.then((res) => res.json())
user = data.user
})
afterAll(async () => {
await payload.destroy()
})
it('should build entire form state', async () => {
const req = await createLocalReq({ user }, payload)
const postData = await payload.create({
collection: postsSlug,
data: {
title: 'Test Post',
},
})
const { state } = await buildFormState({
mockRSCs: true,
id: postData.id,
collectionSlug: postsSlug,
data: postData,
docPermissions: {
create: true,
delete: true,
fields: true,
read: true,
readVersions: true,
update: true,
},
docPreferences: {
fields: {},
},
documentFormState: undefined,
operation: 'update',
renderAllFields: false,
req,
schemaPath: postsSlug,
})
expect(state).toMatchObject({
title: {
value: postData.title,
initialValue: postData.title,
},
updatedAt: {
value: postData.updatedAt,
initialValue: postData.updatedAt,
},
createdAt: {
value: postData.createdAt,
initialValue: postData.createdAt,
},
renderTracker: {},
validateUsingEvent: {},
blocks: {
initialValue: 0,
rows: [],
value: 0,
},
})
})
it('should use `select` to build partial form state with only specified fields', async () => {
const req = await createLocalReq({ user }, payload)
const postData = await payload.create({
collection: postsSlug,
data: {
title: 'Test Post',
},
})
const { state } = await buildFormState({
mockRSCs: true,
id: postData.id,
collectionSlug: postsSlug,
data: postData,
docPermissions: undefined,
docPreferences: {
fields: {},
},
documentFormState: undefined,
operation: 'update',
renderAllFields: false,
req,
schemaPath: postsSlug,
select: {
title: true,
},
})
expect(state).toStrictEqual({
title: {
value: postData.title,
initialValue: postData.title,
lastRenderedPath: 'title',
addedByServer: true,
},
})
})
it('should not render custom components when `lastRenderedPath` exists', async () => {
const req = await createLocalReq({ user }, payload)
const { state: stateWithRow } = await buildFormState({
mockRSCs: true,
collectionSlug: postsSlug,
formState: {
array: {
rows: [
{
id: '123',
},
],
},
'array.0.id': {
value: '123',
initialValue: '123',
},
},
docPermissions: undefined,
docPreferences: {
fields: {},
},
documentFormState: undefined,
operation: 'update',
renderAllFields: false,
req,
schemaPath: postsSlug,
})
// Ensure that row 1 _DOES_ return with rendered components
expect(stateWithRow?.['array.0.customTextField']?.lastRenderedPath).toStrictEqual(
'array.0.customTextField',
)
expect(stateWithRow?.['array.0.customTextField']?.customComponents?.Field).toBeDefined()
const { state: stateWithTitle } = await buildFormState({
mockRSCs: true,
collectionSlug: postsSlug,
formState: {
array: {
rows: [
{
id: '123',
},
{
id: '456',
},
],
},
'array.0.id': {
value: '123',
initialValue: '123',
},
'array.0.customTextField': {
lastRenderedPath: 'array.0.customTextField',
},
'array.1.id': {
value: '456',
initialValue: '456',
},
},
docPermissions: undefined,
docPreferences: {
fields: {},
},
documentFormState: undefined,
operation: 'update',
renderAllFields: false,
schemaPath: postsSlug,
req,
})
// Ensure that row 1 _DOES NOT_ return with rendered components
expect(stateWithTitle?.['array.0.customTextField']).toHaveProperty('lastRenderedPath')
expect(stateWithTitle?.['array.0.customTextField']).not.toHaveProperty('customComponents')
// Ensure that row 2 _DOES_ return with rendered components
expect(stateWithTitle?.['array.1.customTextField']).toHaveProperty('lastRenderedPath')
expect(stateWithTitle?.['array.1.customTextField']).toHaveProperty('customComponents')
expect(stateWithTitle?.['array.1.customTextField']?.customComponents?.Field).toBeDefined()
})
it('should add `addedByServer` flag to fields that originate on the server', async () => {
const req = await createLocalReq({ user }, payload)
const postData = await payload.create({
collection: postsSlug,
data: {
title: 'Test Post',
blocks: [
{
blockType: 'text',
text: 'Test block',
},
],
},
})
const { state } = await buildFormState({
mockRSCs: true,
id: postData.id,
collectionSlug: postsSlug,
data: postData,
docPermissions: undefined,
docPreferences: {
fields: {},
},
documentFormState: undefined,
operation: 'update',
renderAllFields: false,
req,
schemaPath: postsSlug,
})
expect(state.title?.addedByServer).toBe(true)
expect(state['blocks.0.blockType']?.addedByServer).toBe(true)
// Ensure that `addedByServer` is removed after being received by the client
const newState = mergeServerFormState({
currentState: state,
incomingState: state,
})
expect(newState.title?.addedByServer).toBeUndefined()
})
it('should not omit value and initialValue from fields added by the server', () => {
const currentState: FormState = {
array: {
rows: [
{
id: '1',
},
],
},
}
const serverState: FormState = {
array: {
rows: [
{
id: '1',
},
],
},
'array.0.id': {
value: '1',
initialValue: '1',
},
'array.0.customTextField': {
value: 'Test',
initialValue: 'Test',
addedByServer: true,
},
}
const newState = mergeServerFormState({
currentState,
incomingState: serverState,
})
expect(newState['array.0.customTextField']).toStrictEqual({
passesCondition: true,
valid: true,
value: 'Test',
initialValue: 'Test',
})
})
it('should merge array rows without losing rows added to local state', () => {
const currentState: FormState = {
array: {
errorPaths: [],
rows: [
{
id: '1',
lastRenderedPath: 'array.0.customTextField',
},
{
id: '2',
isLoading: true,
},
],
},
'array.0.id': {
value: '1',
initialValue: '1',
},
'array.1.id': {
value: '2',
initialValue: '2',
},
}
const serverState: FormState = {
array: {
rows: [
{
id: '1',
lastRenderedPath: 'array.0.customTextField',
},
],
},
'array.0.id': {
value: '1',
initialValue: '1',
},
'array.0.customTextField': {
value: 'Test',
initialValue: 'Test',
addedByServer: true,
},
}
const newState = mergeServerFormState({
currentState,
incomingState: serverState,
})
// Row 2 should still exist
expect(newState).toStrictEqual({
array: {
errorPaths: [],
passesCondition: true,
valid: true,
rows: [
{
id: '1',
lastRenderedPath: 'array.0.customTextField',
},
{
id: '2',
isLoading: true,
},
],
},
'array.0.id': {
value: '1',
initialValue: '1',
passesCondition: true,
valid: true,
},
'array.0.customTextField': {
value: 'Test',
initialValue: 'Test',
passesCondition: true,
valid: true,
},
'array.1.id': {
value: '2',
initialValue: '2',
},
})
})
it('should merge array rows without bringing back rows deleted from local state', () => {
const currentState: FormState = {
array: {
rows: [
{
id: '1',
lastRenderedPath: 'array.0.customTextField',
},
],
},
'array.0.id': {
value: '1',
initialValue: '1',
},
}
const serverState: FormState = {
array: {
rows: [
{
id: '1',
lastRenderedPath: 'array.0.customTextField',
},
{
id: '2',
lastRenderedPath: 'array.1.customTextField',
},
],
},
'array.0.id': {
value: '1',
initialValue: '1',
},
'array.0.customTextField': {
value: 'Test',
initialValue: 'Test',
addedByServer: true,
},
'array.1.id': {
value: '2',
initialValue: '2',
},
'array.1.customTextField': {
value: 'Test',
initialValue: 'Test',
},
}
const newState = mergeServerFormState({
currentState,
incomingState: serverState,
})
// Row 2 should not exist
expect(newState).toStrictEqual({
array: {
passesCondition: true,
valid: true,
rows: [
{
id: '1',
lastRenderedPath: 'array.0.customTextField',
},
],
},
'array.0.id': {
value: '1',
initialValue: '1',
passesCondition: true,
valid: true,
},
'array.0.customTextField': {
value: 'Test',
initialValue: 'Test',
passesCondition: true,
valid: true,
},
})
})
it('should merge new fields returned from the server that do not yet exist in local state', () => {
const currentState: FormState = {
array: {
rows: [
{
id: '1',
isLoading: true,
},
],
},
'array.0.id': {
value: '1',
initialValue: '1',
},
}
const serverState: FormState = {
array: {
rows: [
{
id: '1',
lastRenderedPath: 'array.0.customTextField',
isLoading: false,
},
],
},
'array.0.id': {
value: '1',
initialValue: '1',
},
'array.0.customTextField': {
value: 'Test',
initialValue: 'Test',
addedByServer: true,
},
}
const newState = mergeServerFormState({
currentState,
incomingState: serverState,
})
expect(newState).toStrictEqual({
array: {
passesCondition: true,
valid: true,
rows: [
{
id: '1',
lastRenderedPath: 'array.0.customTextField',
isLoading: false,
},
],
},
'array.0.id': {
passesCondition: true,
valid: true,
value: '1',
initialValue: '1',
},
'array.0.customTextField': {
passesCondition: true,
valid: true,
value: 'Test',
initialValue: 'Test',
},
})
})
it('should return the same object reference when only modifying a value', () => {
const currentState = {
title: {
value: 'Test Post',
initialValue: 'Test Post',
valid: true,
passesCondition: true,
},
}
const newState = mergeServerFormState({
currentState,
incomingState: {
title: {
value: 'Test Post (modified)',
initialValue: 'Test Post',
valid: true,
passesCondition: true,
},
},
})
expect(newState === currentState).toBe(true)
})
it('should accept all values from the server regardless of local modifications, e.g. `acceptAllValues` on submit', () => {
const title: FieldState = {
value: 'Test Post (modified on the client)',
initialValue: 'Test Post',
valid: true,
passesCondition: true,
}
const currentState: Record<string, FieldState> = {
title: {
...title,
isModified: true, // This is critical, this is what we're testing
},
computedTitle: {
value: 'Test Post (computed on the client)',
initialValue: 'Test Post',
valid: true,
passesCondition: true,
},
array: {
rows: [
{
id: '1',
customComponents: {
RowLabel: DummyReactComponent,
},
lastRenderedPath: 'array.0.customTextField',
},
],
valid: true,
passesCondition: true,
},
'array.0.id': {
value: '1',
initialValue: '1',
valid: true,
passesCondition: true,
},
'array.0.customTextField': {
value: 'Test Post (modified on the client)',
initialValue: 'Test Post',
valid: true,
passesCondition: true,
},
}
const incomingStateFromServer: Record<string, FieldState> = {
title: {
value: 'Test Post (modified on the server)',
initialValue: 'Test Post',
valid: true,
passesCondition: true,
},
computedTitle: {
value: 'Test Post (computed on the server)',
initialValue: 'Test Post',
valid: true,
passesCondition: true,
},
array: {
rows: [
{
id: '1',
lastRenderedPath: 'array.0.customTextField',
// Omit `customComponents` because the server did not re-render this row
},
],
passesCondition: true,
valid: true,
},
'array.0.id': {
value: '1',
initialValue: '1',
valid: true,
passesCondition: true,
},
'array.0.customTextField': {
value: 'Test Post (modified on the client)',
initialValue: 'Test Post',
valid: true,
passesCondition: true,
},
}
const newState = mergeServerFormState({
acceptValues: true,
currentState,
incomingState: incomingStateFromServer,
})
expect(newState).toStrictEqual({
...incomingStateFromServer,
title: {
...incomingStateFromServer.title,
isModified: true,
},
array: {
...incomingStateFromServer.array,
rows: currentState?.array?.rows,
},
})
})
it('should not accept values from the server if they have been modified locally since the request was made, e.g. `overrideLocalChanges: false` on autosave', () => {
const title: FieldState = {
value: 'Test Post (modified on the client 1)',
initialValue: 'Test Post',
valid: true,
passesCondition: true,
}
const currentState: Record<string, FieldState> = {
title: {
...title,
isModified: true,
},
computedTitle: {
value: 'Test Post',
initialValue: 'Test Post',
valid: true,
passesCondition: true,
},
}
const incomingStateFromServer: Record<string, FieldState> = {
title: {
value: 'Test Post (modified on the server)',
initialValue: 'Test Post',
valid: true,
passesCondition: true,
},
computedTitle: {
value: 'Test Post (modified on the server)',
initialValue: 'Test Post',
valid: true,
passesCondition: true,
},
}
const newState = mergeServerFormState({
acceptValues: { overrideLocalChanges: false },
currentState,
incomingState: incomingStateFromServer,
})
expect(newState).toStrictEqual({
...currentState,
title: {
...currentState.title,
isModified: true,
},
computedTitle: incomingStateFromServer.computedTitle, // This field was not modified locally, so should be updated from the server
})
})
it('should set rows to empty array for empty array fields', async () => {
const req = await createLocalReq({ user }, payload)
// Create a document with an empty array
const postData = await payload.create({
collection: postsSlug,
data: {
title: 'Test Post',
array: [], // Empty array - this should result in rows: [] in form state
},
})
const { state } = await buildFormState({
mockRSCs: true,
id: postData.id,
collectionSlug: postsSlug,
data: postData,
docPermissions: {
create: true,
delete: true,
fields: true,
read: true,
readVersions: true,
update: true,
},
docPreferences: {
fields: {},
},
documentFormState: undefined,
operation: 'update',
renderAllFields: false,
req,
schemaPath: postsSlug,
})
expect(state.array).toBeDefined()
expect(state?.array?.rows).toEqual([]) // should be [] not undefined
})
})