fix(ui): populate nested fields for enableListViewSelectAPI (#13827)
Fixes an issue with the new experimental `enableListViewSelectAPI`
config option.
Group fields were not populating properly in the list view
### Before (incorrect)
```ts
{
group.field: true
}
```
### After (correct)
```ts
{
group: {
field: true
}
}
```
This commit is contained in:
@@ -1,9 +1,14 @@
|
|||||||
import type { ColumnPreference, SelectType } from 'payload'
|
import type { ColumnPreference, SelectType } from 'payload'
|
||||||
|
|
||||||
export const transformColumnsToSelect = (columns: ColumnPreference[]): SelectType =>
|
import { unflatten } from 'payload/shared'
|
||||||
columns.reduce((acc, column) => {
|
|
||||||
|
export const transformColumnsToSelect = (columns: ColumnPreference[]): SelectType => {
|
||||||
|
const columnsSelect = columns.reduce((acc, column) => {
|
||||||
if (column.active) {
|
if (column.active) {
|
||||||
acc[column.accessor] = true
|
acc[column.accessor] = true
|
||||||
}
|
}
|
||||||
return acc
|
return acc
|
||||||
}, {} as SelectType)
|
}, {} as SelectType)
|
||||||
|
|
||||||
|
return unflatten(columnsSelect)
|
||||||
|
}
|
||||||
|
|||||||
@@ -21,5 +21,15 @@ export const ListViewSelectAPI: CollectionConfig = {
|
|||||||
name: 'description',
|
name: 'description',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'group',
|
||||||
|
type: 'group',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'groupNameField',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ let payload: PayloadTestSDK<Config>
|
|||||||
|
|
||||||
import { listViewSelectAPISlug } from 'admin/collections/ListViewSelectAPI/index.js'
|
import { listViewSelectAPISlug } from 'admin/collections/ListViewSelectAPI/index.js'
|
||||||
import { devUser } from 'credentials.js'
|
import { devUser } from 'credentials.js'
|
||||||
|
import { getRowByCellValueAndAssert } from 'helpers/e2e/getRowByCellValueAndAssert.js'
|
||||||
import {
|
import {
|
||||||
openListColumns,
|
openListColumns,
|
||||||
reorderColumns,
|
reorderColumns,
|
||||||
@@ -993,58 +994,101 @@ describe('List View', () => {
|
|||||||
await toggleColumn(page, { columnLabel: 'ID', columnName: 'id', targetState: 'off' })
|
await toggleColumn(page, { columnLabel: 'ID', columnName: 'id', targetState: 'off' })
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should use select API in the list view when `enableListViewSelectAPI` is true', async () => {
|
describe('enableListViewSelectAPI', () => {
|
||||||
const doc = await payload.create({
|
test('`id` should always be selected even when toggled off', async () => {
|
||||||
collection: listViewSelectAPISlug,
|
const doc = await payload.create({
|
||||||
data: {
|
collection: listViewSelectAPISlug,
|
||||||
title: 'This is a test title',
|
data: {
|
||||||
description: 'This is a test description',
|
title: 'This is a test title',
|
||||||
},
|
description: 'This is a test description',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const selectAPIUrl = new AdminUrlUtil(serverURL, listViewSelectAPISlug)
|
||||||
|
|
||||||
|
await page.goto(selectAPIUrl.list)
|
||||||
|
|
||||||
|
const printedResults = page.locator('#table-state')
|
||||||
|
|
||||||
|
await expect
|
||||||
|
.poll(
|
||||||
|
async () => {
|
||||||
|
const resultText = await printedResults.innerText()
|
||||||
|
const parsedResult = JSON.parse(resultText)
|
||||||
|
return Boolean(parsedResult[0].id && parsedResult[0].description)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timeout: 3000,
|
||||||
|
intervals: [100, 250, 500, 1000],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.toBeTruthy()
|
||||||
|
|
||||||
|
await toggleColumn(page, { columnLabel: 'ID', columnName: 'id', targetState: 'off' })
|
||||||
|
|
||||||
|
await toggleColumn(page, {
|
||||||
|
columnLabel: 'Description',
|
||||||
|
columnName: 'description',
|
||||||
|
targetState: 'off',
|
||||||
|
})
|
||||||
|
|
||||||
|
// Poll until the "description" field is removed from the response BUT `id` is still present
|
||||||
|
// The `id` field will remain selected despite it being inactive
|
||||||
|
await expect
|
||||||
|
.poll(
|
||||||
|
async () => {
|
||||||
|
const resultText = await printedResults.innerText()
|
||||||
|
const parsedResult = JSON.parse(resultText)
|
||||||
|
return Boolean(parsedResult[0].description === undefined && parsedResult[0].id)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timeout: 3000,
|
||||||
|
intervals: [100, 250, 500, 1000],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.toBeTruthy()
|
||||||
})
|
})
|
||||||
|
|
||||||
const selectAPIUrl = new AdminUrlUtil(serverURL, listViewSelectAPISlug)
|
test('group fields should populate with the select API', async () => {
|
||||||
|
const doc = await payload.create({
|
||||||
await page.goto(selectAPIUrl.list)
|
collection: listViewSelectAPISlug,
|
||||||
|
data: {
|
||||||
const printedResults = page.locator('#table-state')
|
title: 'This is a test title',
|
||||||
|
description: 'This is a test description',
|
||||||
await expect
|
group: {
|
||||||
.poll(
|
groupNameField: 'Select Nested Field',
|
||||||
async () => {
|
},
|
||||||
const resultText = await printedResults.innerText()
|
|
||||||
const parsedResult = JSON.parse(resultText)
|
|
||||||
return Boolean(parsedResult[0].id && parsedResult[0].description)
|
|
||||||
},
|
},
|
||||||
{
|
})
|
||||||
timeout: 3000,
|
|
||||||
intervals: [100, 250, 500, 1000],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.toBeTruthy()
|
|
||||||
|
|
||||||
await toggleColumn(page, { columnLabel: 'ID', columnName: 'id', targetState: 'off' })
|
const selectAPIUrl = new AdminUrlUtil(serverURL, listViewSelectAPISlug)
|
||||||
|
|
||||||
await toggleColumn(page, {
|
await page.goto(selectAPIUrl.list)
|
||||||
columnLabel: 'Description',
|
|
||||||
columnName: 'description',
|
await toggleColumn(page, {
|
||||||
targetState: 'off',
|
columnLabel: 'Group > Group Name Field',
|
||||||
|
columnName: 'group.groupNameField',
|
||||||
|
targetState: 'off',
|
||||||
|
})
|
||||||
|
|
||||||
|
await toggleColumn(page, {
|
||||||
|
columnLabel: 'Group > Group Name Field',
|
||||||
|
columnName: 'group.groupNameField',
|
||||||
|
targetState: 'on',
|
||||||
|
})
|
||||||
|
|
||||||
|
await getRowByCellValueAndAssert({
|
||||||
|
cellClass: `.cell-group__groupNameField`,
|
||||||
|
page,
|
||||||
|
textToMatch: 'Select Nested Field',
|
||||||
|
})
|
||||||
|
|
||||||
|
// cleanup after run
|
||||||
|
await payload.delete({
|
||||||
|
id: doc.id,
|
||||||
|
collection: listViewSelectAPISlug,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// Poll until the "description" field is removed from the response BUT `id` is still present
|
|
||||||
// The `id` field will remain selected despite it being inactive
|
|
||||||
await expect
|
|
||||||
.poll(
|
|
||||||
async () => {
|
|
||||||
const resultText = await printedResults.innerText()
|
|
||||||
const parsedResult = JSON.parse(resultText)
|
|
||||||
return Boolean(parsedResult[0].description === undefined && parsedResult[0].id)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
timeout: 3000,
|
|
||||||
intervals: [100, 250, 500, 1000],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.toBeTruthy()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should toggle columns and save to preferences', async () => {
|
test('should toggle columns and save to preferences', async () => {
|
||||||
|
|||||||
@@ -594,6 +594,9 @@ export interface ListViewSelectApi {
|
|||||||
id: string;
|
id: string;
|
||||||
title?: string | null;
|
title?: string | null;
|
||||||
description?: string | null;
|
description?: string | null;
|
||||||
|
group?: {
|
||||||
|
groupNameField?: string | null;
|
||||||
|
};
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
}
|
}
|
||||||
@@ -1153,6 +1156,11 @@ export interface CustomListDrawerSelect<T extends boolean = true> {
|
|||||||
export interface ListViewSelectApiSelect<T extends boolean = true> {
|
export interface ListViewSelectApiSelect<T extends boolean = true> {
|
||||||
title?: T;
|
title?: T;
|
||||||
description?: T;
|
description?: T;
|
||||||
|
group?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
groupNameField?: T;
|
||||||
|
};
|
||||||
updatedAt?: T;
|
updatedAt?: T;
|
||||||
createdAt?: T;
|
createdAt?: T;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ export const testEslintConfig = [
|
|||||||
'createFolderFromDoc',
|
'createFolderFromDoc',
|
||||||
'assertURLParams',
|
'assertURLParams',
|
||||||
'uploadImage',
|
'uploadImage',
|
||||||
|
'getRowByCellValueAndAssert',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
23
test/helpers/e2e/getRowByCellValueAndAssert.ts
Normal file
23
test/helpers/e2e/getRowByCellValueAndAssert.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import type { Locator, Page } from '@playwright/test'
|
||||||
|
|
||||||
|
import { expect } from '@playwright/test'
|
||||||
|
|
||||||
|
export async function getRowByCellValueAndAssert({
|
||||||
|
page,
|
||||||
|
textToMatch,
|
||||||
|
cellClass,
|
||||||
|
}: {
|
||||||
|
cellClass: `.cell-${string}`
|
||||||
|
page: Page
|
||||||
|
textToMatch: string
|
||||||
|
}): Promise<Locator> {
|
||||||
|
const row = page
|
||||||
|
.locator(`.collection-list .table tr`)
|
||||||
|
.filter({
|
||||||
|
has: page.locator(`${cellClass}`, { hasText: textToMatch }),
|
||||||
|
})
|
||||||
|
.first()
|
||||||
|
|
||||||
|
await expect(row).toBeVisible()
|
||||||
|
return row
|
||||||
|
}
|
||||||
@@ -2,6 +2,8 @@ import type { Page } from '@playwright/test'
|
|||||||
|
|
||||||
import type { AdminUrlUtil } from '../../helpers/adminUrlUtil.js'
|
import type { AdminUrlUtil } from '../../helpers/adminUrlUtil.js'
|
||||||
|
|
||||||
|
import { getRowByCellValueAndAssert } from './getRowByCellValueAndAssert.js'
|
||||||
|
|
||||||
export async function goToListDoc({
|
export async function goToListDoc({
|
||||||
page,
|
page,
|
||||||
cellClass,
|
cellClass,
|
||||||
@@ -14,12 +16,7 @@ export async function goToListDoc({
|
|||||||
urlUtil: AdminUrlUtil
|
urlUtil: AdminUrlUtil
|
||||||
}) {
|
}) {
|
||||||
await page.goto(urlUtil.list)
|
await page.goto(urlUtil.list)
|
||||||
const row = page
|
const row = await getRowByCellValueAndAssert({ page, textToMatch, cellClass })
|
||||||
.locator(`.collection-list .table tr`)
|
|
||||||
.filter({
|
|
||||||
has: page.locator(`${cellClass}`, { hasText: textToMatch }),
|
|
||||||
})
|
|
||||||
.first()
|
|
||||||
const cellLink = row.locator(`td a`).first()
|
const cellLink = row.locator(`td a`).first()
|
||||||
const linkURL = await cellLink.getAttribute('href')
|
const linkURL = await cellLink.getAttribute('href')
|
||||||
await page.goto(`${urlUtil.serverURL}${linkURL}`)
|
await page.goto(`${urlUtil.serverURL}${linkURL}`)
|
||||||
|
|||||||
Reference in New Issue
Block a user