diff --git a/packages/payload/src/utilities/flattenTopLevelFields.spec.ts b/packages/payload/src/utilities/flattenTopLevelFields.spec.ts index d6e5711c8..f7d75e425 100644 --- a/packages/payload/src/utilities/flattenTopLevelFields.spec.ts +++ b/packages/payload/src/utilities/flattenTopLevelFields.spec.ts @@ -49,10 +49,10 @@ describe('flattenFields', () => { i18n, }) - expect(result).toHaveLength(1) - expect(result[0].name).toBe('slug') - expect(result[0].accessor).toBe('meta-slug') - expect(result[0].labelWithPrefix).toBe('Meta Info > Slug') + expect(result).toHaveLength(2) + expect(result[1].name).toBe('slug') + expect(result[1].accessor).toBe('meta-slug') + expect(result[1].labelWithPrefix).toBe('Meta Info > Slug') }) it('should NOT flatten fields inside group without moveSubFieldsToTop', () => { @@ -109,10 +109,10 @@ describe('flattenFields', () => { i18n, }) - expect(hoisted).toHaveLength(1) - expect(hoisted[0].name).toBe('deep') - expect(hoisted[0].accessor).toBe('outer-inner-deep') - expect(hoisted[0].labelWithPrefix).toBe('Outer > Inner > Deep Field') + expect(hoisted).toHaveLength(3) + expect(hoisted[2].name).toBe('deep') + expect(hoisted[2].accessor).toBe('outer-inner-deep') + expect(hoisted[2].labelWithPrefix).toBe('Outer > Inner > Deep Field') const nonHoisted = flattenFields(fields) @@ -142,14 +142,15 @@ describe('flattenFields', () => { i18n, }) - // Should keep the group as a single top-level field - expect(withExtract).toHaveLength(1) - expect(withExtract[0].type).toBe('text') - expect(withExtract[0].accessor).toBeUndefined() - expect(withExtract[0].labelWithPrefix).toBeUndefined() + // Should include top level group and its nested field as a top-level field + expect(withExtract).toHaveLength(2) + expect(withExtract[1].type).toBe('text') + expect(withExtract[1].accessor).toBeUndefined() + expect(withExtract[1].labelWithPrefix).toBeUndefined() const withoutExtract = flattenFields(fields) + // Should return the group as a top-level item, not the inner field expect(withoutExtract).toHaveLength(1) expect(withoutExtract[0].type).toBe('group') expect(withoutExtract[0].accessor).toBeUndefined() @@ -195,10 +196,10 @@ describe('flattenFields', () => { i18n, }) - expect(hoistedResult).toHaveLength(1) - expect(hoistedResult[0].name).toBe('nestedField') - expect(hoistedResult[0].accessor).toBe('namedGroup-nestedField') - expect(hoistedResult[0].labelWithPrefix).toBe('Named Group > Nested Field') + expect(hoistedResult).toHaveLength(5) + expect(hoistedResult[4].name).toBe('nestedField') + expect(hoistedResult[4].accessor).toBe('namedGroup-nestedField') + expect(hoistedResult[4].labelWithPrefix).toBe('Named Group > Nested Field') const nonHoistedResult = flattenFields(fields) @@ -432,9 +433,9 @@ describe('flattenFields', () => { i18n, }) - expect(result).toHaveLength(1) - expect(result[0].accessor).toBe('groupInRow-nestedInRowGroup') - expect(result[0].labelWithPrefix).toBe('Group In Row > Nested In Row Group') + expect(result).toHaveLength(2) + expect(result[1].accessor).toBe('groupInRow-nestedInRowGroup') + expect(result[1].labelWithPrefix).toBe('Group In Row > Nested In Row Group') }) it('should hoist named group fields inside collapsibles', () => { @@ -464,15 +465,114 @@ describe('flattenFields', () => { i18n, }) - expect(result).toHaveLength(1) - expect(result[0].accessor).toBe('groupInCollapsible-nestedInCollapsibleGroup') - expect(result[0].labelWithPrefix).toBe('Group In Collapsible > Nested In Collapsible Group') + expect(result).toHaveLength(2) + expect(result[1].accessor).toBe('groupInCollapsible-nestedInCollapsibleGroup') + expect(result[1].labelWithPrefix).toBe('Group In Collapsible > Nested In Collapsible Group') }) }) describe('tab integration', () => { - it('should hoist named group fields inside tabs when moveSubFieldsToTop is true', () => { - const fields: ClientField[] = [ + const namedTabFields: ClientField[] = [ + { + type: 'tabs', + tabs: [ + { + label: 'Tab One', + name: 'tabOne', + fields: [ + { + type: 'array', + name: 'array', + fields: [ + { + type: 'text', + name: 'text', + }, + ], + }, + { + type: 'row', + fields: [ + { + name: 'arrayInRow', + type: 'array', + fields: [ + { + name: 'textInArrayInRow', + type: 'text', + }, + ], + }, + ], + }, + { + type: 'text', + name: 'textInTab', + label: 'Text In Tab', + }, + { + type: 'group', + name: 'groupInTab', + label: 'Group In Tab', + fields: [ + { + type: 'text', + name: 'nestedTextInTabGroup', + label: 'Nested Text In Tab Group', + }, + ], + }, + ], + }, + ], + }, + ] + + const unnamedTabFields: ClientField[] = [ + { + type: 'tabs', + tabs: [ + { + label: 'Tab One', + fields: [ + { + type: 'array', + name: 'array', + fields: [ + { + type: 'text', + name: 'text', + }, + ], + }, + { + type: 'row', + fields: [ + { + name: 'arrayInRow', + type: 'array', + fields: [ + { + name: 'textInArrayInRow', + type: 'text', + }, + ], + }, + ], + }, + { + type: 'text', + name: 'textInTab', + label: 'Text In Tab', + }, + ], + }, + ], + }, + ] + + it('should hoist named group fields inside unamed tabs when moveSubFieldsToTop is true', () => { + const unnamedTabWithNamedGroup: ClientField[] = [ { type: 'tabs', tabs: [ @@ -497,14 +597,107 @@ describe('flattenFields', () => { }, ] - const result = flattenFields(fields, { + const result = flattenFields(unnamedTabWithNamedGroup, { moveSubFieldsToTop: true, i18n, }) + expect(result).toHaveLength(2) + expect(result[1].accessor).toBe('groupInTab-nestedInTabGroup') + expect(result[1].labelWithPrefix).toBe('Group In Tab > Nested In Tab Group') + }) + + it('should hoist fields inside unnamed groups inside unnamed tabs when moveSubFieldsToTop is true', () => { + const unnamedTabWithUnnamedGroup: ClientField[] = [ + { + type: 'tabs', + tabs: [ + { + label: 'Tab One', + fields: [ + { + type: 'group', + label: 'Unnamed Group In Tab', + fields: [ + { + type: 'text', + name: 'nestedInUnnamedGroup', + label: 'Nested In Unnamed Group', + }, + ], + }, + ], + }, + ], + }, + ] + + const defaultResult = flattenFields(unnamedTabWithUnnamedGroup) + + expect(defaultResult).toHaveLength(1) + expect(defaultResult[0].type).toBe('group') + expect(defaultResult[0].label).toBe('Unnamed Group In Tab') + expect('accessor' in defaultResult[0]).toBe(false) + expect('labelWithPrefix' in defaultResult[0]).toBe(false) + + const hoistedResult = flattenFields(unnamedTabWithUnnamedGroup, { + moveSubFieldsToTop: true, + i18n, + }) + + expect(hoistedResult).toHaveLength(2) + const hoistedField = hoistedResult[1] + expect(hoistedField.name).toBe('nestedInUnnamedGroup') + expect(hoistedField.accessor).toBeUndefined() + expect(hoistedField.labelWithPrefix).toBeUndefined() + }) + + it('should properly hoist fields inside named tabs when moveSubFieldsToTop is true', () => { + const result = flattenFields(namedTabFields, { + moveSubFieldsToTop: true, + i18n, + }) + + expect(result).toHaveLength(5) + expect(result[0].accessor).toBe('tabOne-array') + expect(result[0].labelWithPrefix).toBe('Tab One > array') + expect(result[1].accessor).toBe('tabOne-arrayInRow') + expect(result[1].labelWithPrefix).toBe('Tab One > arrayInRow') + expect(result[2].accessor).toBe('tabOne-textInTab') + expect(result[2].labelWithPrefix).toBe('Tab One > Text In Tab') + expect(result[4].accessor).toBe('tabOne-groupInTab-nestedTextInTabGroup') + expect(result[4].labelWithPrefix).toBe('Tab One > Group In Tab > Nested Text In Tab Group') + }) + + it('should NOT hoist fields inside named tabs when moveSubFieldsToTop is false', () => { + const result = flattenFields(namedTabFields) + + // We expect one top-level field: the tabs container itself is *not* hoisted expect(result).toHaveLength(1) - expect(result[0].accessor).toBe('groupInTab-nestedInTabGroup') - expect(result[0].labelWithPrefix).toBe('Group In Tab > Nested In Tab Group') + + const tabField = result[0] + expect(tabField.type).toBe('tab') + + // Confirm nested fields are NOT hoisted: no accessors or labelWithPrefix at the top level + expect('accessor' in tabField).toBe(false) + expect('labelWithPrefix' in tabField).toBe(false) + }) + + it('should hoist fields inside unnamed tabs regardless of moveSubFieldsToTop', () => { + const resultDefault = flattenFields(unnamedTabFields) + const resultHoisted = flattenFields(unnamedTabFields, { + moveSubFieldsToTop: true, + i18n, + }) + + expect(resultDefault).toHaveLength(3) + expect(resultHoisted).toHaveLength(3) + expect(resultDefault).toEqual(resultHoisted) + + for (const field of resultDefault) { + expect(field.accessor).toBeUndefined() + expect(field.labelWithPrefix).toBeUndefined() + } }) }) }) diff --git a/packages/payload/src/utilities/flattenTopLevelFields.ts b/packages/payload/src/utilities/flattenTopLevelFields.ts index f461600aa..98bbe55e0 100644 --- a/packages/payload/src/utilities/flattenTopLevelFields.ts +++ b/packages/payload/src/utilities/flattenTopLevelFields.ts @@ -50,7 +50,7 @@ type FlattenFieldsOptions = { */ labelPrefix?: string /** - * If true, nested fields inside `group` fields will be lifted to the top level + * If true, nested fields inside `group` & `tabs` fields will be lifted to the top level * and given contextual `accessor` and `labelWithPrefix` values. * Default: false. */ @@ -85,48 +85,93 @@ function flattenFields( } = normalizedOptions return fields.reduce[]>((acc, field) => { - if (fieldHasSubFields(field)) { - if (field.type === 'group') { - if (moveSubFieldsToTop && 'fields' in field) { - const isNamedGroup = 'name' in field && typeof field.name === 'string' && !!field.name + if (field.type === 'group' && 'fields' in field) { + if (moveSubFieldsToTop) { + const isNamedGroup = 'name' in field && typeof field.name === 'string' && !!field.name + const groupName = 'name' in field ? field.name : undefined - const translatedLabel = - 'label' in field && field.label && i18n - ? getTranslation(field.label as string, i18n) - : undefined + const translatedLabel = + 'label' in field && field.label && i18n + ? getTranslation(field.label as string, i18n) + : undefined - const labelWithPrefix = - isNamedGroup && labelPrefix && translatedLabel - ? `${labelPrefix} > ${translatedLabel}` - : (labelPrefix ?? translatedLabel) + const labelWithPrefix = labelPrefix + ? `${labelPrefix} > ${translatedLabel ?? groupName}` + : (translatedLabel ?? groupName) - const nameWithPrefix = - isNamedGroup && field.name - ? pathPrefix - ? `${pathPrefix}-${field.name as string}` - : (field.name as string) - : pathPrefix + const nameWithPrefix = + 'name' in field && field.name + ? pathPrefix + ? `${pathPrefix}-${field.name}` + : field.name + : pathPrefix - acc.push( - ...flattenFields(field.fields as TField[], { - i18n, - keepPresentationalFields, - labelPrefix: isNamedGroup ? labelWithPrefix : labelPrefix, - moveSubFieldsToTop, - pathPrefix: isNamedGroup ? nameWithPrefix : pathPrefix, - }), - ) - } else { - // Just keep the group as-is - acc.push(field as FlattenedField) - } - } else if (['collapsible', 'row'].includes(field.type)) { - // Recurse into row and collapsible - acc.push(...flattenFields(field.fields as TField[], options)) + acc.push( + // Need to include the top-level group field when hoisting its subfields, + // so that `buildColumnState` can detect and render a column if the group + // has a custom admin Cell component defined in its configuration. + // See: packages/ui/src/providers/TableColumns/buildColumnState/index.tsx + field as FlattenedField, + ...flattenFields(field.fields as TField[], { + i18n, + keepPresentationalFields, + labelPrefix: isNamedGroup ? labelWithPrefix : labelPrefix, + moveSubFieldsToTop, + pathPrefix: isNamedGroup ? nameWithPrefix : pathPrefix, + }), + ) } else { - // Do not hoist fields from arrays & blocks + // Hoisting diabled - keep as top level field acc.push(field as FlattenedField) } + } else if (field.type === 'tabs' && 'tabs' in field) { + return [ + ...acc, + ...field.tabs.reduce[]>((tabFields, tab: TabType) => { + if (tabHasName(tab)) { + if (moveSubFieldsToTop) { + const translatedLabel = + 'label' in tab && tab.label && i18n ? getTranslation(tab.label, i18n) : undefined + + const labelWithPrefixForTab = labelPrefix + ? `${labelPrefix} > ${translatedLabel ?? tab.name}` + : (translatedLabel ?? tab.name) + + const pathPrefixForTab = tab.name + ? pathPrefix + ? `${pathPrefix}-${tab.name}` + : tab.name + : pathPrefix + + return [ + ...tabFields, + ...flattenFields(tab.fields as TField[], { + i18n, + keepPresentationalFields, + labelPrefix: labelWithPrefixForTab, + moveSubFieldsToTop, + pathPrefix: pathPrefixForTab, + }), + ] + } else { + // Named tab, hoisting disabled: keep as top-level field + return [ + ...tabFields, + { + ...tab, + type: 'tab', + } as unknown as FlattenedField, + ] + } + } else { + // Unnamed tab: always hoist its fields + return [...tabFields, ...flattenFields(tab.fields as TField[], options)] + } + }, []), + ] + } else if (fieldHasSubFields(field) && ['collapsible', 'row'].includes(field.type)) { + // Recurse into row and collapsible + acc.push(...flattenFields(field.fields as TField[], options)) } else if ( fieldAffectsData(field) || (keepPresentationalFields && fieldIsPresentationalOnly(field)) @@ -148,30 +193,11 @@ function flattenFields( ...(moveSubFieldsToTop && isHoistingFromGroup && { accessor: pathPrefix && name ? `${pathPrefix}-${name}` : (name ?? ''), - labelWithPrefix: - labelPrefix && translatedLabel - ? `${labelPrefix} > ${translatedLabel}` - : (labelPrefix ?? translatedLabel), + labelWithPrefix: labelPrefix + ? `${labelPrefix} > ${translatedLabel ?? name}` + : (translatedLabel ?? name), }), }) - } else if (field.type === 'tabs' && 'tabs' in field) { - return [ - ...acc, - ...field.tabs.reduce[]>((tabFields, tab: TabType) => { - if (tabHasName(tab)) { - return [ - ...tabFields, - { - ...tab, - type: 'tab', - ...(moveSubFieldsToTop && { labelPrefix }), - } as unknown as FlattenedField, - ] - } else { - return [...tabFields, ...flattenFields(tab.fields as TField[], options)] - } - }, []), - ] } return acc diff --git a/packages/ui/src/providers/TableColumns/buildColumnState/index.tsx b/packages/ui/src/providers/TableColumns/buildColumnState/index.tsx index ad7f1cf5a..45201d792 100644 --- a/packages/ui/src/providers/TableColumns/buildColumnState/index.tsx +++ b/packages/ui/src/providers/TableColumns/buildColumnState/index.tsx @@ -139,6 +139,17 @@ export const buildColumnState = (args: BuildColumnStateArgs): Column[] => { return fAccessor === accessor }) + const hasCustomCell = + serverField?.admin && + 'components' in serverField.admin && + serverField.admin.components && + 'Cell' in serverField.admin.components && + serverField.admin.components.Cell + + if (serverField && serverField.type === 'group' && !hasCustomCell) { + return acc // skip any group without a custom cell + } + const columnPreference = columnPreferences?.find( (preference) => clientField && 'name' in clientField && preference.accessor === accessor, ) diff --git a/test/admin/collections/Posts.ts b/test/admin/collections/Posts.ts index 7e1eeec72..5bd77cf22 100644 --- a/test/admin/collections/Posts.ts +++ b/test/admin/collections/Posts.ts @@ -133,7 +133,7 @@ export const Posts: CollectionConfig = { type: 'text', }, { - name: 'group', + name: 'namedGroup', type: 'group', fields: [ { @@ -142,6 +142,54 @@ export const Posts: CollectionConfig = { }, ], }, + { + type: 'group', + label: 'Unnamed group', + fields: [ + { + name: 'textFieldInUnnamedGroup', + type: 'text', + }, + ], + }, + { + name: 'groupWithCustomCell', + type: 'group', + admin: { + components: { + Cell: '/components/CustomGroupCell/index.js#CustomGroupCell', + }, + }, + fields: [ + { + name: 'nestedTextFieldInGroupWithCustomCell', + type: 'text', + }, + ], + }, + { + type: 'tabs', + tabs: [ + { + name: 'namedTab', + fields: [ + { + name: 'nestedTextFieldInNamedTab', + type: 'text', + }, + ], + }, + { + label: 'unnamedTab', + fields: [ + { + name: 'nestedTextFieldInUnnamedTab', + type: 'text', + }, + ], + }, + ], + }, { name: 'relationship', type: 'relationship', diff --git a/test/admin/components/CustomGroupCell/index.tsx b/test/admin/components/CustomGroupCell/index.tsx new file mode 100644 index 000000000..88dfdf728 --- /dev/null +++ b/test/admin/components/CustomGroupCell/index.tsx @@ -0,0 +1,9 @@ +'use client' + +import type { DefaultCellComponentProps } from 'payload' + +import React from 'react' + +export const CustomGroupCell: React.FC = (props) => { + return
{`Custom group cell: ${props?.rowData?.title || 'No data'}`}
+} diff --git a/test/admin/e2e/list-view/e2e.spec.ts b/test/admin/e2e/list-view/e2e.spec.ts index c08058eae..048735799 100644 --- a/test/admin/e2e/list-view/e2e.spec.ts +++ b/test/admin/e2e/list-view/e2e.spec.ts @@ -955,24 +955,64 @@ describe('List View', () => { expect(page.url()).not.toMatch(/columns=/) }) - test('should render field in group as column', async () => { - await createPost({ group: { someTextField: 'nested group text field' } }) + test('should render nested field in named group as separate column', async () => { + await createPost({ namedGroup: { someTextField: 'nested group text field' } }) await page.goto(postsUrl.list) await openColumnControls(page) await page .locator('.column-selector .column-selector__column', { - hasText: exactText('Group > Some Text Field'), + hasText: exactText('Named Group > Some Text Field'), }) .click() - await expect(page.locator('.row-1 .cell-group-someTextField')).toHaveText( + await expect(page.locator('.row-1 .cell-namedGroup-someTextField')).toHaveText( 'nested group text field', ) }) - test('should render top-level and group field with same name in separate columns', async () => { + test('should render nested field in unnamed group as separate column', async () => { + await createPost({ textFieldInUnnamedGroup: 'nested text in unnamed group' }) + await page.goto(postsUrl.list) + await openColumnControls(page) + await page + .locator('.column-selector .column-selector__column', { + hasText: exactText('Text Field In Unnamed Group'), + }) + .click() + await expect(page.locator('.row-1 .cell-textFieldInUnnamedGroup')).toHaveText( + 'nested text in unnamed group', + ) + }) + + test('should not render group field as top level column when custom cell is not defined', async () => { + await createPost({ namedGroup: { someTextField: 'nested group text field' } }) + await page.goto(postsUrl.list) + await openColumnControls(page) + await expect( + page.locator('.column-selector .column-selector__column', { + hasText: exactText('Named Group'), + }), + ).toBeHidden() + }) + + test('should render group field as top level column when custom cell is defined', async () => { + await createPost({ + groupWithCustomCell: { + nestedTextFieldInGroupWithCustomCell: 'nested group text field in group with custom cell', + }, + }) + await page.goto(postsUrl.list) + await openColumnControls(page) + await expect( + page.locator('.column-selector .column-selector__column', { + hasText: exactText('Group With Custom Cell'), + }), + ).toBeVisible() + }) + + test('should render top-level field and group field with same name in separate columns', async () => { await createPost({ someTextField: 'top-level text field', - group: { someTextField: 'nested group text field' }, + namedGroup: { someTextField: 'nested group text field' }, }) await page.goto(postsUrl.list) @@ -988,7 +1028,7 @@ describe('List View', () => { // Enable group column await page .locator('.column-selector .column-selector__column', { - hasText: exactText('Group > Some Text Field'), + hasText: exactText('Named Group > Some Text Field'), }) .click() @@ -996,11 +1036,39 @@ describe('List View', () => { await expect(page.locator('.row-1 .cell-someTextField')).toHaveText('top-level text field') // Expect nested group cell - await expect(page.locator('.row-1 .cell-group-someTextField')).toHaveText( + await expect(page.locator('.row-1 .cell-namedGroup-someTextField')).toHaveText( 'nested group text field', ) }) + test('should render nested field in named tab as separate column', async () => { + await createPost({ namedTab: { nestedTextFieldInNamedTab: 'nested text in named tab' } }) + await page.goto(postsUrl.list) + await openColumnControls(page) + await page + .locator('.column-selector .column-selector__column', { + hasText: exactText('Named Tab > Nested Text Field In Named Tab'), + }) + .click() + await expect(page.locator('.row-1 .cell-namedTab-nestedTextFieldInNamedTab')).toHaveText( + 'nested text in named tab', + ) + }) + + test('should render nested field in unnamed tab as separate column', async () => { + await createPost({ nestedTextFieldInUnnamedTab: 'nested text in unnamed tab' }) + await page.goto(postsUrl.list) + await openColumnControls(page) + await page + .locator('.column-selector .column-selector__column', { + hasText: exactText('Nested Text Field In Unnamed Tab'), + }) + .click() + await expect(page.locator('.row-1 .cell-nestedTextFieldInUnnamedTab')).toHaveText( + 'nested text in unnamed tab', + ) + }) + test('should drag to reorder columns and save to preferences', async () => { await reorderColumns(page, { fromColumn: 'Number', toColumn: 'ID' }) @@ -1308,7 +1376,11 @@ describe('List View', () => { beforeEach(async () => { // delete all posts created by the seed await deleteAllPosts() - await createPost({ number: 1, group: { someTextField: 'nested group text field' } }) + await createPost({ + number: 1, + namedGroup: { someTextField: 'nested group text field' }, + namedTab: { nestedTextFieldInNamedTab: 'nested text in named tab' }, + }) await createPost({ number: 2 }) }) @@ -1335,33 +1407,69 @@ describe('List View', () => { await openColumnControls(page) await page .locator('.column-selector .column-selector__column', { - hasText: exactText('Group > Some Text Field'), + hasText: exactText('Named Group > Some Text Field'), }) .click() - const upChevron = page.locator('#heading-group-someTextField .sort-column__asc') - const downChevron = page.locator('#heading-group-someTextField .sort-column__desc') + const upChevron = page.locator('#heading-namedGroup-someTextField .sort-column__asc') + const downChevron = page.locator('#heading-namedGroup-someTextField .sort-column__desc') await upChevron.click() - await page.waitForURL(/sort=group.someTextField/) + await page.waitForURL(/sort=namedGroup.someTextField/) - await expect(page.locator('.row-1 .cell-group-someTextField')).toHaveText( + await expect(page.locator('.row-1 .cell-namedGroup-someTextField')).toHaveText( '', ) - await expect(page.locator('.row-2 .cell-group-someTextField')).toHaveText( + await expect(page.locator('.row-2 .cell-namedGroup-someTextField')).toHaveText( 'nested group text field', ) await downChevron.click() - await page.waitForURL(/sort=-group.someTextField/) + await page.waitForURL(/sort=-namedGroup.someTextField/) - await expect(page.locator('.row-1 .cell-group-someTextField')).toHaveText( + await expect(page.locator('.row-1 .cell-namedGroup-someTextField')).toHaveText( 'nested group text field', ) - await expect(page.locator('.row-2 .cell-group-someTextField')).toHaveText( + await expect(page.locator('.row-2 .cell-namedGroup-someTextField')).toHaveText( '', ) }) + test('should allow sorting by nested field within tab in separate column', async () => { + await page.goto(postsUrl.list) + await openColumnControls(page) + await page + .locator('.column-selector .column-selector__column', { + hasText: exactText('Named Tab > Nested Text Field In Named Tab'), + }) + .click() + const upChevron = page.locator( + '#heading-namedTab-nestedTextFieldInNamedTab .sort-column__asc', + ) + const downChevron = page.locator( + '#heading-namedTab-nestedTextFieldInNamedTab .sort-column__desc', + ) + + await upChevron.click() + await page.waitForURL(/sort=namedTab.nestedTextFieldInNamedTab/) + + await expect(page.locator('.row-1 .cell-namedTab-nestedTextFieldInNamedTab')).toHaveText( + '', + ) + await expect(page.locator('.row-2 .cell-namedTab-nestedTextFieldInNamedTab')).toHaveText( + 'nested text in named tab', + ) + + await downChevron.click() + await page.waitForURL(/sort=-namedTab.nestedTextFieldInNamedTab/) + + await expect(page.locator('.row-1 .cell-namedTab-nestedTextFieldInNamedTab')).toHaveText( + 'nested text in named tab', + ) + await expect(page.locator('.row-2 .cell-namedTab-nestedTextFieldInNamedTab')).toHaveText( + '', + ) + }) + test('should sort with existing filters', async () => { await page.goto(postsUrl.list) diff --git a/test/admin/payload-types.ts b/test/admin/payload-types.ts index 8d10d74d8..b218cb319 100644 --- a/test/admin/payload-types.ts +++ b/test/admin/payload-types.ts @@ -238,9 +238,17 @@ export interface Post { }[] | null; someTextField?: string | null; - group?: { + namedGroup?: { someTextField?: string | null; }; + textFieldInUnnamedGroup?: string | null; + groupWithCustomCell?: { + nestedTextFieldInGroupWithCustomCell?: string | null; + }; + namedTab?: { + nestedTextFieldInNamedTab?: string | null; + }; + nestedTextFieldInUnnamedTab?: string | null; relationship?: (string | null) | Post; users?: (string | null) | User; customCell?: string | null; @@ -700,11 +708,23 @@ export interface PostsSelect { number?: T; richText?: T; someTextField?: T; - group?: + namedGroup?: | T | { someTextField?: T; }; + textFieldInUnnamedGroup?: T; + groupWithCustomCell?: + | T + | { + nestedTextFieldInGroupWithCustomCell?: T; + }; + namedTab?: + | T + | { + nestedTextFieldInNamedTab?: T; + }; + nestedTextFieldInUnnamedTab?: T; relationship?: T; users?: T; customCell?: T;