Files
payload/test/fields/collections/ConditionalLogic/e2e.spec.ts
Alessio Gravili a11586811e fix(ui): field.admin.condition data attribute missing document ID when document is being edited (#13676)
Fixes https://github.com/payloadcms/payload/issues/10379

During form state requests, the passed `data` did not have access to the
document ID. This was because the data we use came from the client,
passed as an argument. The client did not pass data that included the
document ID.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211203844178567
2025-09-03 01:14:07 +00:00

291 lines
9.0 KiB
TypeScript

import type { BrowserContext, Page } from '@playwright/test'
import { expect, test } from '@playwright/test'
import { addArrayRow } from 'helpers/e2e/fields/array/index.js'
import path from 'path'
import { fileURLToPath } from 'url'
import type { PayloadTestSDK } from '../../../helpers/sdk/index.js'
import type { Config } from '../../payload-types.js'
import {
ensureCompilationIsDone,
initPageConsoleErrorCatch,
saveDocAndAssert,
// throttleTest,
} from '../../../helpers.js'
import { AdminUrlUtil } from '../../../helpers/adminUrlUtil.js'
import { assertNetworkRequests } from '../../../helpers/e2e/assertNetworkRequests.js'
import { initPayloadE2ENoConfig } from '../../../helpers/initPayloadE2ENoConfig.js'
import { reInitializeDB } from '../../../helpers/reInitializeDB.js'
import { RESTClient } from '../../../helpers/rest.js'
import { TEST_TIMEOUT_LONG } from '../../../playwright.config.js'
import { conditionalLogicSlug } from '../../slugs.js'
const filename = fileURLToPath(import.meta.url)
const currentFolder = path.dirname(filename)
const dirname = path.resolve(currentFolder, '../../')
const { beforeAll, beforeEach, describe } = test
let payload: PayloadTestSDK<Config>
let client: RESTClient
let page: Page
let serverURL: string
let url: AdminUrlUtil
let context: BrowserContext
const toggleConditionAndCheckField = async (toggleLocator: string, fieldLocator: string) => {
const toggle = page.locator(toggleLocator)
if (!(await toggle.isChecked())) {
await expect(page.locator(fieldLocator)).toBeHidden()
await toggle.click()
await expect(page.locator(fieldLocator)).toBeVisible()
} else {
await expect(page.locator(fieldLocator)).toBeVisible()
await toggle.click()
await expect(page.locator(fieldLocator)).toBeHidden()
}
}
describe('Conditional Logic', () => {
beforeAll(async ({ browser }, testInfo) => {
testInfo.setTimeout(TEST_TIMEOUT_LONG)
process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit
;({ payload, serverURL } = await initPayloadE2ENoConfig<Config>({
dirname,
// prebuild,
}))
url = new AdminUrlUtil(serverURL, conditionalLogicSlug)
context = await browser.newContext()
page = await context.newPage()
initPageConsoleErrorCatch(page)
await ensureCompilationIsDone({ page, serverURL })
})
beforeEach(async () => {
// await throttleTest({
// page,
// context,
// delay: 'Fast 4G',
// })
await reInitializeDB({
serverURL,
snapshotKey: 'fieldsTest',
uploadsDir: path.resolve(dirname, './collections/Upload/uploads'),
})
if (client) {
await client.logout()
}
client = new RESTClient({ defaultSlug: 'users', serverURL })
await client.login()
await ensureCompilationIsDone({ page, serverURL })
})
test("should conditionally render field based on another field's data", async () => {
await page.goto(url.create)
await toggleConditionAndCheckField(
'label[for=field-toggleField]',
'label[for=field-fieldWithCondition]',
)
expect(true).toBe(true)
})
test('ensure conditions receive document ID during form state request', async () => {
await page.goto(url.create)
const fieldOnlyVisibleIfNoID = page.locator('#field-fieldWithDocIDCondition')
await expect(fieldOnlyVisibleIfNoID).toBeVisible()
const textField = page.locator('#field-text')
await assertNetworkRequests(
page,
'/admin/collections/conditional-logic',
async () => {
await textField.fill('some text')
},
{
minimumNumberOfRequests: 1,
},
)
await assertNetworkRequests(
page,
'/api/conditional-logic',
async () => {
await saveDocAndAssert(page)
},
{
minimumNumberOfRequests: 1,
},
)
await expect(fieldOnlyVisibleIfNoID).toBeHidden()
// Fill text and wait for form state request to come back
await assertNetworkRequests(
page,
'/admin/collections/conditional-logic',
async () => {
await textField.fill('updated text')
},
{
minimumNumberOfRequests: 1,
},
)
await expect(fieldOnlyVisibleIfNoID).toBeHidden()
})
test('should conditionally render custom field that renders a Payload field', async () => {
await page.goto(url.create)
await toggleConditionAndCheckField(
'label[for=field-toggleField]',
'label[for=field-customFieldWithField]',
)
expect(true).toBe(true)
})
test('should conditionally render custom field that wraps itself with the withCondition HOC (legacy)', async () => {
await page.goto(url.create)
await toggleConditionAndCheckField(
'label[for=field-toggleField]',
'label[for=field-customFieldWithHOC]',
)
expect(true).toBe(true)
})
test('should toggle conditional custom client field', async () => {
await page.goto(url.create)
await toggleConditionAndCheckField('label[for=field-toggleField]', '#custom-client-field')
expect(true).toBe(true)
})
test('should conditionally render custom server field', async () => {
await page.goto(url.create)
await toggleConditionAndCheckField('label[for=field-toggleField]', '#custom-server-field')
expect(true).toBe(true)
})
test('should conditionally render rich text fields', async () => {
await page.goto(url.create)
await toggleConditionAndCheckField(
'label[for=field-toggleField]',
'.field-type.rich-text-lexical',
)
expect(true).toBe(true)
})
test('should show conditional field based on user data', async () => {
await page.goto(url.create)
const userConditional = page.locator('input#field-userConditional')
await expect(userConditional).toBeVisible()
})
test('should show conditional field based on nested field data', async () => {
await page.goto(url.create)
const parentGroupFields = page.locator(
'div#field-parentGroup > .group-field__wrap > .render-fields',
)
await expect(parentGroupFields).toHaveCount(1)
const toggle = page.locator('label[for=field-parentGroup__enableParentGroupFields]')
await toggle.click()
const toggledField = page.locator('input#field-parentGroup__siblingField')
await expect(toggledField).toBeVisible()
})
test('should show conditional field based on siblingData', async () => {
await page.goto(url.create)
const toggle = page.locator('label[for=field-parentGroup__enableParentGroupFields]')
await toggle.click()
const fieldRelyingOnSiblingData = page.locator('input#field-reliesOnParentGroup')
await expect(fieldRelyingOnSiblingData).toBeVisible()
})
test('should not render fields when adding array or blocks rows until form state returns', async () => {
await page.goto(url.create)
await addArrayRow(page, { fieldName: 'arrayWithConditionalField' })
const shimmer = '#field-arrayWithConditionalField .collapsible__content > .shimmer-effect'
await expect(page.locator(shimmer)).toBeVisible()
await expect(page.locator(shimmer)).toBeHidden()
// Do not use `waitForSelector` here, as it will wait for the selector to appear, not disappear
// eslint-disable-next-line playwright/no-wait-for-selector
const wasFieldAttached = await page
.waitForSelector('input#field-arrayWithConditionalField__0__textWithCondition', {
state: 'attached',
timeout: 100, // A small timeout to catch any transient rendering
})
.catch(() => false) // If it doesn't appear, this resolves to `false`
expect(wasFieldAttached).toBeFalsy()
const fieldToToggle = page.locator('input#field-enableConditionalFields')
await fieldToToggle.click()
await expect(
page.locator('input#field-arrayWithConditionalField__0__textWithCondition'),
).toBeVisible()
})
test('should render field based on path argument', async () => {
await page.goto(url.create)
await addArrayRow(page, { fieldName: 'arrayOne' })
await addArrayRow(page, { fieldName: 'arrayOne__0__arrayTwo' })
await addArrayRow(page, { fieldName: 'arrayOne__0__arrayTwo__0__arrayThree' })
const numberField = page.locator('#field-arrayOne__0__arrayTwo__0__arrayThree__0__numberField')
await expect(numberField).toBeHidden()
const selectField = page.locator('#field-arrayOne__0__arrayTwo__0__selectOptions')
await selectField.click({ delay: 100 })
const options = page.locator('.rs__option')
await options.locator('text=Option Two').click()
await expect(numberField).toBeVisible()
})
test('should render field based on operation argument', async () => {
await page.goto(url.create)
const textField = page.locator('#field-text')
const fieldWithOperationCondition = page.locator('#field-fieldWithOperationCondition')
await textField.fill('some text')
await expect(fieldWithOperationCondition).toBeVisible()
await saveDocAndAssert(page)
await expect(fieldWithOperationCondition).toBeHidden()
})
})