fix: hidden and disabled fields cause incorrect field paths (#9680)

This commit is contained in:
Jacob Fletcher
2024-12-03 21:22:28 -05:00
committed by GitHub
parent fce210b9b1
commit a53c1d5517
26 changed files with 303 additions and 51 deletions

View File

@@ -50,7 +50,7 @@ The following options are available:
| **`style`** | [CSS Properties](https://developer.mozilla.org/en-US/docs/Web/CSS) to inject into the root element of the field. |
| **`className`** | Attach a [CSS class attribute](https://developer.mozilla.org/en-US/docs/Web/CSS/Class_selectors) to the root DOM element of a field. |
| **`readOnly`** | Setting a field to `readOnly` has no effect on the API whatsoever but disables the admin component's editability to prevent editors from modifying the field's value. |
| **`disabled`** | If a field is `disabled`, it is completely omitted from the [Admin Panel](../admin/overview). |
| **`disabled`** | If a field is `disabled`, it is completely omitted from the [Admin Panel](../admin/overview) entirely. |
| **`disableBulkEdit`** | Set `disableBulkEdit` to `true` to prevent fields from appearing in the select options when making edits for multiple documents. Defaults to `true` for UI fields. |
| **`disableListColumn`** | Set `disableListColumn` to `true` to prevent fields from appearing in the list view column selector. |
| **`disableListFilter`** | Set `disableListFilter` to `true` to prevent fields from appearing in the list view filter options. |

View File

@@ -1,7 +1,7 @@
'use client'
import type { DiffMethod } from 'react-diff-viewer-continued'
import { fieldAffectsData } from 'payload/shared'
import { fieldAffectsData, fieldIsID } from 'payload/shared'
import React from 'react'
import type { diffComponents as _diffComponents } from './fields/index.js'
@@ -29,7 +29,7 @@ const RenderFieldsToDiff: React.FC<Props> = ({
return (
<div className={baseClass}>
{fields?.map((field, i) => {
if ('name' in field && field.name === 'id') {
if (fieldIsID(field)) {
return null
}

View File

@@ -234,7 +234,6 @@ export const createClientCollectionConfig = ({
}
break
break
default:
clientCollection[key] = collection[key]
}

View File

@@ -19,6 +19,8 @@ export {
fieldIsArrayType,
fieldIsBlockType,
fieldIsGroupType,
fieldIsHiddenOrDisabled,
fieldIsID,
fieldIsLocalized,
fieldIsPresentationalOnly,
fieldIsSidebar,

View File

@@ -75,28 +75,24 @@ export const createClientField = ({
}): ClientField => {
const clientField: ClientField = {} as ClientField
const isHidden = 'hidden' in incomingField && incomingField?.hidden
const disabledFromAdmin =
incomingField?.admin && 'disabled' in incomingField.admin && incomingField.admin.disabled
if (fieldAffectsData(incomingField) && (isHidden || disabledFromAdmin)) {
return null
}
for (const key in incomingField) {
if (serverOnlyFieldProperties.includes(key as any)) {
continue
}
switch (key) {
case 'admin':
if (!incomingField.admin) {
break
}
clientField.admin = {} as AdminClient
for (const adminKey in incomingField.admin) {
if (serverOnlyFieldAdminProperties.includes(adminKey as any)) {
continue
}
switch (adminKey) {
case 'description':
if ('description' in incomingField.admin) {
@@ -107,16 +103,20 @@ export const createClientField = ({
}
break
default:
clientField.admin[adminKey] = incomingField.admin[adminKey]
}
}
break
case 'blocks':
case 'fields':
case 'tabs':
// Skip - we handle sub-fields in the switch below
break
case 'label':
//@ts-expect-error - would need to type narrow
if (typeof incomingField.label === 'function') {
@@ -126,7 +126,9 @@ export const createClientField = ({
//@ts-expect-error - would need to type narrow
clientField.label = incomingField.label
}
break
default:
clientField[key] = incomingField[key]
}
@@ -243,6 +245,7 @@ export const createClientField = ({
break
}
case 'richText': {
if (!incomingField?.editor) {
throw new MissingEditorProp(incomingField) // while we allow disabling editor functionality, you should not have any richText fields defined if you do not have an editor
@@ -269,6 +272,7 @@ export const createClientField = ({
if (serverOnlyFieldProperties.includes(key as any)) {
continue
}
if (key === 'fields') {
clientTab.fields = createClientFields({
defaultIDType,
@@ -320,10 +324,8 @@ export const createClientFields = ({
importMap,
})
if (clientField) {
clientFields.push(clientField)
}
}
const hasID = flattenTopLevelFields(fields).some((f) => fieldAffectsData(f) && f.name === 'id')

View File

@@ -1709,6 +1709,21 @@ export function fieldIsSidebar<TField extends ClientField | Field | TabAsField |
return 'admin' in field && 'position' in field.admin && field.admin.position === 'sidebar'
}
export function fieldIsID<TField extends ClientField | Field>(
field: TField,
): field is { name: 'id' } & TField {
return 'name' in field && field.name === 'id'
}
export function fieldIsHiddenOrDisabled<
TField extends ClientField | Field | TabAsField | TabAsFieldClient,
>(field: TField): field is { admin: { hidden: true } } & TField {
return (
('hidden' in field && field.hidden) ||
('admin' in field && 'disabled' in field.admin && field.admin.disabled)
)
}
export function fieldAffectsData<
TField extends ClientField | Field | TabAsField | TabAsFieldClient,
>(

View File

@@ -590,6 +590,7 @@ export class BasePayload {
if (!fieldAffectsData(field)) {
return
}
if (field.name === 'id') {
customIDType = field.type
return true

View File

@@ -52,11 +52,11 @@ export const fieldSchemaToJSON = (fields: ClientField[]): FieldSchemaJSON => {
break
case 'collapsible':
case 'collapsible': // eslint-disable no-fallthrough
case 'row':
result = result.concat(fieldSchemaToJSON(field.fields))
break
case 'group':
acc.push({
name: field.name,
@@ -66,8 +66,7 @@ export const fieldSchemaToJSON = (fields: ClientField[]): FieldSchemaJSON => {
break
case 'relationship':
case 'relationship': // eslint-disable no-fallthrough
case 'upload':
acc.push({
name: field.name,
@@ -77,6 +76,7 @@ export const fieldSchemaToJSON = (fields: ClientField[]): FieldSchemaJSON => {
})
break
case 'tabs': {
let tabFields = []

View File

@@ -1,7 +1,7 @@
'use client'
import type { ClientField, FieldWithPath, FormState } from 'payload'
import { fieldAffectsData, fieldHasSubFields } from 'payload/shared'
import { fieldAffectsData, fieldHasSubFields, fieldIsHiddenOrDisabled } from 'payload/shared'
import React, { Fragment, useState } from 'react'
import { RenderCustomComponent } from '../../elements/RenderCustomComponent/index.js'
@@ -69,7 +69,7 @@ const reduceFields = ({
(fieldAffectsData(field) || field.type === 'ui') &&
(field.admin.disableBulkEdit ||
field.unique ||
field.admin.hidden ||
fieldIsHiddenOrDisabled(field) ||
('readOnly' in field && field.readOnly))
) {
return fieldsToUse

View File

@@ -3,7 +3,7 @@ import type { DefaultCellComponentProps, UploadFieldClient } from 'payload'
import { getTranslation } from '@payloadcms/translations'
import LinkImport from 'next/link.js'
import { fieldAffectsData } from 'payload/shared'
import { fieldAffectsData, fieldIsID } from 'payload/shared'
import React from 'react' // TODO: abstract this out to support all routers
import { useConfig } from '../../../providers/Config/index.js'
@@ -77,7 +77,7 @@ export const DefaultCell: React.FC<DefaultCellComponentProps> = (props) => {
}
}
if ('name' in field && field.name === 'id') {
if (fieldIsID(field)) {
return (
<WrapElement {...wrapElementProps}>
<CodeCell

View File

@@ -11,7 +11,12 @@ import type {
} from 'payload'
import { MissingEditorProp } from 'payload'
import { deepCopyObjectSimple, fieldIsPresentationalOnly } from 'payload/shared'
import {
deepCopyObjectSimple,
fieldIsHiddenOrDisabled,
fieldIsID,
fieldIsPresentationalOnly,
} from 'payload/shared'
import React from 'react'
import type { ColumnPreferences } from '../../providers/ListQuery/index.js'
@@ -70,7 +75,7 @@ export const buildColumnState = (args: Args): Column[] => {
// place the `ID` field first, if it exists
// do the same for the `useAsTitle` field with precedence over the `ID` field
// then sort the rest of the fields based on the `defaultColumns` or `columnPreferences`
const idFieldIndex = sortedFieldMap?.findIndex((field) => 'name' in field && field.name === 'id')
const idFieldIndex = sortedFieldMap?.findIndex((field) => fieldIsID(field))
if (idFieldIndex > -1) {
const idField = sortedFieldMap.splice(idFieldIndex, 1)[0]
@@ -117,6 +122,10 @@ export const buildColumnState = (args: Args): Column[] => {
const activeColumnsIndices = []
const sorted: Column[] = sortedFieldMap?.reduce((acc, field, index) => {
if (fieldIsHiddenOrDisabled(field) && !fieldIsID(field)) {
return acc
}
const _field = _sortedFieldMap.find(
(f) => 'name' in field && 'name' in f && f.name === field.name,
)

View File

@@ -3,7 +3,7 @@ import type { ClientTranslationKeys, I18nClient } from '@payloadcms/translations
import type { ClientField } from 'payload'
import { getTranslation } from '@payloadcms/translations'
import { tabHasName } from 'payload/shared'
import { fieldIsHiddenOrDisabled, fieldIsID, tabHasName } from 'payload/shared'
import type { FieldCondition } from './types.js'
@@ -29,7 +29,7 @@ export const reduceClientFields = ({
pathPrefix,
}: ReduceClientFieldsArgs): FieldCondition[] => {
return fields.reduce((reduced, field) => {
if (field.admin?.disableListFilter) {
if (field.admin?.disableListFilter || (fieldIsHiddenOrDisabled(field) && !fieldIsID(field))) {
return reduced
}
@@ -163,7 +163,6 @@ export const reduceClientFields = ({
reduced.push(formattedField)
return reduced
}
return reduced
}, [])
}

View File

@@ -1,6 +1,6 @@
'use client'
import { getFieldPaths } from 'payload/shared'
import { fieldIsHiddenOrDisabled, getFieldPaths } from 'payload/shared'
import React from 'react'
import type { RenderFieldsProps } from './types.js'
@@ -45,7 +45,7 @@ export const RenderFields: React.FC<RenderFieldsProps> = (props) => {
{fields.map((field, i) => {
// For sidebar fields in the main fields array, `field` will be `null`, and visa versa
// This is to keep the order of the fields consistent and maintain the correct index paths for the main fields (i)
if (!field || field?.admin?.disabled) {
if (!field || fieldIsHiddenOrDisabled(field)) {
return null
}

View File

@@ -17,6 +17,8 @@ import {
deepCopyObjectSimple,
fieldAffectsData,
fieldHasSubFields,
fieldIsHiddenOrDisabled,
fieldIsID,
fieldIsSidebar,
getFieldPaths,
tabHasName,
@@ -138,11 +140,9 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom
const requiresRender = renderAllFields || previousFormState?.[path]?.requiresRender
const isHiddenField = 'hidden' in field && field?.hidden
const disabledFromAdmin = field?.admin && 'disabled' in field.admin && field.admin.disabled
let fieldPermissions: SanitizedFieldPermissions = true
if (fieldAffectsData(field) && !(isHiddenField || disabledFromAdmin)) {
if (fieldAffectsData(field) && !fieldIsHiddenOrDisabled(field)) {
fieldPermissions =
parentPermissions === true
? parentPermissions
@@ -233,7 +233,7 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom
if (!omitParents && (!filter || filter(args))) {
state[parentPath + '.id'] = {
fieldSchema: includeSchema
? field.fields.find((field) => 'name' in field && field.name === 'id')
? field.fields.find((field) => fieldIsID(field))
: undefined,
initialValue: row.id,
valid: true,
@@ -343,9 +343,7 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom
if (!omitParents && (!filter || filter(args))) {
state[parentPath + '.id'] = {
fieldSchema: includeSchema
? block.fields.find(
(blockField) => 'name' in blockField && blockField.name === 'id',
)
? block.fields.find((blockField) => fieldIsID(blockField))
: undefined,
initialValue: row.id,
valid: true,
@@ -724,9 +722,7 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom
}
}
const isDisabled = field?.admin && 'disabled' in field.admin && field.admin.disabled
if (requiresRender && !isDisabled && renderFieldFn) {
if (requiresRender && renderFieldFn && !fieldIsHiddenOrDisabled(field)) {
const fieldState = state[path]
const fieldConfig = fieldSchemaMap.get(schemaPath)

View File

@@ -2,10 +2,12 @@ import type { ClientComponentProps, ClientField, FieldPaths, ServerComponentProp
import { getTranslation } from '@payloadcms/translations'
import { createClientField, MissingEditorProp } from 'payload'
import { fieldIsHiddenOrDisabled } from 'payload/shared'
import type { RenderFieldMethod } from './types.js'
import { RenderServerComponent } from '../../elements/RenderServerComponent/index.js'
// eslint-disable-next-line payload/no-imports-from-exports-dir -- need this to reference already existing bundle. Otherwise, bundle size increases., payload/no-imports-from-exports-dir
import { FieldDescription } from '../../exports/client/index.js'
@@ -45,6 +47,10 @@ export const renderField: RenderFieldMethod = ({
importMap: req.payload.importMap,
})
if (fieldIsHiddenOrDisabled(clientField)) {
return
}
const clientProps: ClientComponentProps & Partial<FieldPaths> = {
customComponents: fieldState?.customComponents || {},
field: clientField,

View File

@@ -100,6 +100,7 @@ export const buildTableState = async (
if (!canAccessAdmin) {
throw new Error('Unauthorized')
}
// Match the user collection to the global admin config
} else if (adminUserSlug !== incomingUserSlug) {
throw new Error('Unauthorized')

View File

@@ -1,6 +1,6 @@
import type { Field } from 'payload'
import { fieldAffectsData } from 'payload/shared'
import { fieldAffectsData, fieldIsID } from 'payload/shared'
export const formatFields = (fields: Field[], isEditing?: boolean): Field[] =>
isEditing ? fields.filter((field) => !fieldAffectsData(field) || field.name !== 'id') : fields
isEditing ? fields.filter((field) => !fieldAffectsData(field) || !fieldIsID(field)) : fields

View File

@@ -8,6 +8,7 @@ import type {
} from 'payload'
import { getTranslation, type I18nClient } from '@payloadcms/translations'
import { fieldIsHiddenOrDisabled, fieldIsID } from 'payload/shared'
// eslint-disable-next-line payload/no-imports-from-exports-dir
import type { Column } from '../exports/client/index.js'
@@ -17,6 +18,7 @@ import { RenderServerComponent } from '../elements/RenderServerComponent/index.j
import { buildColumnState } from '../elements/TableColumns/buildColumnState.js'
import { filterFields } from '../elements/TableColumns/filterFields.js'
import { getInitialColumns } from '../elements/TableColumns/getInitialColumns.js'
// eslint-disable-next-line payload/no-imports-from-exports-dir
import { Pill, Table } from '../exports/client/index.js'
@@ -26,6 +28,10 @@ export const renderFilters = (
): Map<string, React.ReactNode> =>
fields.reduce(
(acc, field) => {
if (fieldIsHiddenOrDisabled(field)) {
return acc
}
if ('name' in field && field.admin?.components?.Filter) {
acc.set(
field.name,

View File

@@ -218,6 +218,27 @@ export interface CustomField {
descriptionAsFunction?: string | null;
descriptionAsComponent?: string | null;
customSelectField?: string | null;
relationshipFieldWithBeforeAfterInputs?: (string | null) | Post;
arrayFieldWithBeforeAfterInputs?:
| {
someTextField?: string | null;
id?: string | null;
}[]
| null;
blocksFieldWithBeforeAfterInputs?:
| {
textField?: string | null;
id?: string | null;
blockName?: string | null;
blockType: 'blockFields';
}[]
| null;
text?: string | null;
groupFieldWithBeforeAfterInputs?: {
textOne?: string | null;
textTwo?: string | null;
};
radioFieldWithBeforeAfterInputs?: ('one' | 'two' | 'three') | null;
updatedAt: string;
createdAt: string;
}
@@ -538,6 +559,32 @@ export interface CustomFieldsSelect<T extends boolean = true> {
descriptionAsFunction?: T;
descriptionAsComponent?: T;
customSelectField?: T;
relationshipFieldWithBeforeAfterInputs?: T;
arrayFieldWithBeforeAfterInputs?:
| T
| {
someTextField?: T;
id?: T;
};
blocksFieldWithBeforeAfterInputs?:
| T
| {
blockFields?:
| T
| {
textField?: T;
id?: T;
blockName?: T;
};
};
text?: T;
groupFieldWithBeforeAfterInputs?:
| T
| {
textOne?: T;
textTwo?: T;
};
radioFieldWithBeforeAfterInputs?: T;
updatedAt?: T;
createdAt?: T;
}

View File

@@ -1,6 +1,7 @@
import type { Page } from '@playwright/test'
import { expect, test } from '@playwright/test'
import { openListColumns, toggleColumn } from 'helpers/e2e/toggleColumn.js'
import path from 'path'
import { wait } from 'payload/shared'
import { fileURLToPath } from 'url'
@@ -13,6 +14,7 @@ import {
exactText,
initPageConsoleErrorCatch,
saveDocAndAssert,
selectTableRow,
} from '../../../helpers.js'
import { AdminUrlUtil } from '../../../helpers/adminUrlUtil.js'
import { initPayloadE2ENoConfig } from '../../../helpers/initPayloadE2ENoConfig.js'
@@ -67,6 +69,93 @@ describe('Text', () => {
await ensureCompilationIsDone({ page, serverURL })
})
describe('hidden and disabled fields', () => {
test('should not render top-level hidden fields in the UI', async () => {
await page.goto(url.create)
await expect(page.locator('#field-hiddenTextField')).toBeHidden()
await page.goto(url.list)
await expect(page.locator('.cell-hiddenTextField')).toBeHidden()
await expect(page.locator('#heading-hiddenTextField')).toBeHidden()
const columnContainer = await openListColumns(page, {})
await expect(
columnContainer.locator('.column-selector__column', {
hasText: exactText('Hidden Text Field'),
}),
).toBeHidden()
await selectTableRow(page, 'Seeded text document')
await page.locator('.edit-many__toggle').click()
await page.locator('.field-select .rs__control').click()
const hiddenFieldOption = page.locator('.rs__option', {
hasText: exactText('Hidden Text Field'),
})
await expect(hiddenFieldOption).toBeHidden()
})
test('should not show disabled fields in the UI', async () => {
await page.goto(url.create)
await expect(page.locator('#field-disabledTextField')).toHaveCount(0)
await page.goto(url.list)
await expect(page.locator('.cell-disabledTextField')).toBeHidden()
await expect(page.locator('#heading-disabledTextField')).toBeHidden()
const columnContainer = await openListColumns(page, {})
await expect(
columnContainer.locator('.column-selector__column', {
hasText: exactText('Disabled Text Field'),
}),
).toBeHidden()
await selectTableRow(page, 'Seeded text document')
await page.locator('.edit-many__toggle').click()
await page.locator('.field-select .rs__control').click()
const disabledFieldOption = page.locator('.rs__option', {
hasText: exactText('Disabled Text Field'),
})
await expect(disabledFieldOption).toBeHidden()
})
test('should render hidden input for admin.hidden fields', async () => {
await page.goto(url.create)
await expect(page.locator('#field-adminHiddenTextField')).toHaveAttribute('type', 'hidden')
await page.goto(url.list)
await expect(page.locator('.cell-adminHiddenTextField').first()).toBeVisible()
await expect(page.locator('#heading-adminHiddenTextField')).toBeVisible()
const columnContainer = await openListColumns(page, {})
await expect(
columnContainer.locator('.column-selector__column', {
hasText: exactText('Admin Hidden Text Field'),
}),
).toBeVisible()
await selectTableRow(page, 'Seeded text document')
await page.locator('.edit-many__toggle').click()
await page.locator('.field-select .rs__control').click()
const adminHiddenFieldOption = page.locator('.rs__option', {
hasText: exactText('Admin Hidden Text Field'),
})
await expect(adminHiddenFieldOption).toBeVisible()
})
test('hidden and disabled fields should not break subsequent field paths', async () => {
await page.goto(url.create)
await expect(page.locator('#custom-field-schema-path')).toHaveText('text-fields._index-4')
})
})
test('should display field in list view', async () => {
await page.goto(url.list)
const textCell = page.locator('.row-1 .cell-text')
@@ -129,7 +218,15 @@ describe('Text', () => {
test('should display i18n label in cells when missing field data', async () => {
await page.goto(url.list)
await page.waitForURL(new RegExp(`${url.list}.*\\?.*`))
await toggleColumn(page, {
targetState: 'on',
columnLabel: 'Text en',
})
const textCell = page.locator('.row-1 .cell-i18nText')
await expect(textCell).toHaveText('<No Text en>')
})

View File

@@ -17,6 +17,36 @@ const TextFields: CollectionConfig = {
beforeDuplicate: [({ value }) => `${value} - duplicate`],
},
},
{
name: 'hiddenTextField',
type: 'text',
hidden: true,
},
{
name: 'adminHiddenTextField',
type: 'text',
admin: {
hidden: true,
description: 'This field should be hidden',
},
},
{
name: 'disabledTextField',
type: 'text',
admin: {
disabled: true,
description: 'This field should be disabled',
},
},
{
type: 'row',
admin: {
components: {
Field: './components/CustomField.tsx#CustomField',
},
},
fields: [],
},
{
name: 'localizedText',
type: 'text',

View File

@@ -0,0 +1,9 @@
'use client'
import type { TextFieldServerComponent } from 'payload'
import React from 'react'
export const CustomField: TextFieldServerComponent = ({ schemaPath }) => {
return <div id="custom-field-schema-path">{schemaPath}</div>
}

View File

@@ -50,6 +50,7 @@ describe('fields', () => {
await ensureCompilationIsDone({ page, serverURL })
})
beforeEach(async () => {
await reInitializeDB({
serverURL,

View File

@@ -869,6 +869,9 @@ export interface BlockField {
export interface TextField {
id: string;
text: string;
hiddenTextField?: string | null;
adminHiddenTextField?: string | null;
disabledTextField?: string | null;
localizedText?: string | null;
i18nText?: string | null;
defaultString?: string | null;
@@ -3234,6 +3237,9 @@ export interface TabsFieldsSelect<T extends boolean = true> {
*/
export interface TextFieldsSelect<T extends boolean = true> {
text?: T;
hiddenTextField?: T;
adminHiddenTextField?: T;
disabledTextField?: T;
localizedText?: T;
i18nText?: T;
defaultString?: T;

View File

@@ -1,18 +1,17 @@
import type { Page } from '@playwright/test'
import { expect } from '@playwright/test'
import { wait } from 'payload/shared'
import { exactText } from '../../helpers.js'
export const toggleColumn = async (
export const openListColumns = async (
page: Page,
{
togglerSelector = '.list-controls__toggle-columns',
columnContainerSelector = '.list-controls__columns',
columnLabel,
}: {
columnContainerSelector?: string
columnLabel: string
togglerSelector?: string
},
): Promise<any> => {
@@ -25,6 +24,25 @@ export const toggleColumn = async (
await expect(page.locator(`${columnContainerSelector}.rah-static--height-auto`)).toBeVisible()
return columnContainer
}
export const toggleColumn = async (
page: Page,
{
togglerSelector,
columnContainerSelector,
columnLabel,
targetState: targetStateFromArgs,
}: {
columnContainerSelector?: string
columnLabel: string
targetState?: 'off' | 'on'
togglerSelector?: string
},
): Promise<any> => {
const columnContainer = await openListColumns(page, { togglerSelector, columnContainerSelector })
const column = columnContainer.locator(`.column-selector .column-selector__column`, {
hasText: exactText(columnLabel),
})
@@ -33,16 +51,24 @@ export const toggleColumn = async (
el.classList.contains('column-selector__column--active'),
)
const targetState =
targetStateFromArgs !== undefined ? targetStateFromArgs : isActiveBeforeClick ? 'off' : 'on'
await expect(column).toBeVisible()
if (
(isActiveBeforeClick && targetState === 'off') ||
(!isActiveBeforeClick && targetState === 'on')
) {
await column.click()
}
if (isActiveBeforeClick) {
if (targetState === 'off') {
// no class
await expect(column).not.toHaveClass('column-selector__column--active')
await expect(column).not.toHaveClass(/column-selector__column--active/)
} else {
// has class
await expect(column).toHaveClass('column-selector__column--active')
await expect(column).toHaveClass(/column-selector__column--active/)
}
return column

View File

@@ -37,7 +37,7 @@
],
"paths": {
"@payload-config": [
"./test/live-preview/config.ts"
"./test/_community/config.ts"
],
"@payloadcms/live-preview": [
"./packages/live-preview/src"