fix(ui): refreshes column state during hmr and respects admin.disableListColumn despite preferences (#9846)
Partial fix for #9774. When `admin.disableListColumn` is set retroactively, it continues to appear in column state, but shouldn't. This was because the table column context was not refreshing after HMR runs, and would instead hold onto these stale columns until the page itself refreshes. Similarly, this was also a problem when the user had saved any of these columns to their list preferences, where those prefs would take precedence despite these properties being set on the underlying fields. The fix is to filter these columns from all requests that send them, and ensure local component state properly refreshes itself.
This commit is contained in:
@@ -27,7 +27,7 @@ const description = 'Description'
|
||||
let payload: PayloadTestSDK<Config>
|
||||
|
||||
import { goToFirstCell, navigateToDoc } from 'helpers/e2e/navigateToDoc.js'
|
||||
import { toggleColumn } from 'helpers/e2e/toggleColumn.js'
|
||||
import { openListColumns, toggleColumn } from 'helpers/e2e/toggleColumn.js'
|
||||
import path from 'path'
|
||||
import { wait } from 'payload/shared'
|
||||
import { fileURLToPath } from 'url'
|
||||
@@ -206,21 +206,16 @@ describe('admin2', () => {
|
||||
test('should toggle columns', async () => {
|
||||
const columnCountLocator = 'table > thead > tr > th'
|
||||
await createPost()
|
||||
await page.locator('.list-controls__toggle-columns').click()
|
||||
await openListColumns(page, {})
|
||||
const numberOfColumns = await page.locator(columnCountLocator).count()
|
||||
await expect(page.locator('.column-selector')).toBeVisible()
|
||||
await expect(page.locator('table > thead > tr > th:nth-child(2)')).toHaveText('ID')
|
||||
|
||||
const idButton = page.locator(`.column-selector .column-selector__column`, {
|
||||
hasText: exactText('ID'),
|
||||
})
|
||||
|
||||
await idButton.click()
|
||||
await toggleColumn(page, { columnLabel: 'ID', targetState: 'off' })
|
||||
await page.locator('#heading-id').waitFor({ state: 'detached' })
|
||||
await page.locator('.cell-id').first().waitFor({ state: 'detached' })
|
||||
await expect(page.locator(columnCountLocator)).toHaveCount(numberOfColumns - 1)
|
||||
await expect(page.locator('table > thead > tr > th:nth-child(2)')).toHaveText('Number')
|
||||
await idButton.click()
|
||||
await toggleColumn(page, { columnLabel: 'ID', targetState: 'on' })
|
||||
await expect(page.locator('.cell-id').first()).toBeVisible()
|
||||
await expect(page.locator(columnCountLocator)).toHaveCount(numberOfColumns)
|
||||
await expect(page.locator('table > thead > tr > th:nth-child(2)')).toHaveText('ID')
|
||||
@@ -744,8 +739,7 @@ describe('admin2', () => {
|
||||
|
||||
test('should sort with existing filters', async () => {
|
||||
await page.goto(postsUrl.list)
|
||||
const column = await toggleColumn(page, { columnLabel: 'ID' })
|
||||
await expect(column).not.toHaveClass('column-selector__column--active')
|
||||
await toggleColumn(page, { columnLabel: 'ID', targetState: 'off' })
|
||||
await page.locator('#heading-id').waitFor({ state: 'detached' })
|
||||
await page.locator('#heading-title button.sort-column__asc').click()
|
||||
await page.waitForURL(/sort=title/)
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import type { Page } from '@playwright/test'
|
||||
import type { GeneratedTypes } from 'helpers/sdk/types.js'
|
||||
|
||||
import { expect, test } from '@playwright/test'
|
||||
import { openListColumns, toggleColumn } from 'helpers/e2e/toggleColumn.js'
|
||||
import { upsertPrefs } from 'helpers/e2e/upsertPrefs.js'
|
||||
import path from 'path'
|
||||
import { wait } from 'payload/shared'
|
||||
import { fileURLToPath } from 'url'
|
||||
@@ -203,6 +205,32 @@ describe('Text', () => {
|
||||
).toBeVisible()
|
||||
})
|
||||
|
||||
test('should respect admin.disableListColumn despite preferences', async () => {
|
||||
await upsertPrefs<Config, GeneratedTypes<any>>({
|
||||
payload,
|
||||
user: client.user,
|
||||
value: {
|
||||
columns: [
|
||||
{
|
||||
accessor: 'disableListColumnText',
|
||||
active: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
await page.goto(url.list)
|
||||
await openListColumns(page, {})
|
||||
await expect(
|
||||
page.locator(`.column-selector .column-selector__column`, {
|
||||
hasText: exactText('Disable List Column Text'),
|
||||
}),
|
||||
).toBeHidden()
|
||||
|
||||
await expect(page.locator('#heading-disableListColumnText')).toBeHidden()
|
||||
await expect(page.locator('table .row-1 .cell-disableListColumnText')).toBeHidden()
|
||||
})
|
||||
|
||||
test('should hide field in filter when admin.disableListFilter is true', async () => {
|
||||
await page.goto(url.list)
|
||||
await page.locator('.list-controls__toggle-where').click()
|
||||
|
||||
@@ -156,14 +156,12 @@ const TextFields: CollectionConfig = {
|
||||
type: 'text',
|
||||
admin: {
|
||||
disableListColumn: true,
|
||||
disableListFilter: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'disableListFilterText',
|
||||
type: 'text',
|
||||
admin: {
|
||||
disableListColumn: false,
|
||||
disableListFilter: true,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { Page } from '@playwright/test'
|
||||
|
||||
import { expect } from '@playwright/test'
|
||||
import { wait } from 'payload/shared'
|
||||
|
||||
import { exactText } from '../../helpers.js'
|
||||
|
||||
|
||||
59
test/helpers/e2e/upsertPrefs.ts
Normal file
59
test/helpers/e2e/upsertPrefs.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import type { PayloadTestSDK } from 'helpers/sdk/index.js'
|
||||
import type { GeneratedTypes } from 'helpers/sdk/types.js'
|
||||
import type { TypedUser } from 'payload'
|
||||
|
||||
export const upsertPrefs = async <
|
||||
TConfig extends GeneratedTypes<any>,
|
||||
TGeneratedTypes extends GeneratedTypes<any>,
|
||||
>({
|
||||
payload,
|
||||
user,
|
||||
value,
|
||||
}: {
|
||||
payload: PayloadTestSDK<TConfig>
|
||||
user: TypedUser
|
||||
value: Record<string, any>
|
||||
}): Promise<TGeneratedTypes['collections']['payload-preferences']> => {
|
||||
let prefs = await payload
|
||||
.find({
|
||||
collection: 'payload-preferences',
|
||||
depth: 0,
|
||||
limit: 1,
|
||||
where: {
|
||||
and: [
|
||||
{ key: { equals: 'text-fields-list' } },
|
||||
{ 'user.value': { equals: user.id } },
|
||||
{ 'user.relationTo': { equals: user.collection } },
|
||||
],
|
||||
},
|
||||
})
|
||||
?.then((res) => res.docs?.[0])
|
||||
|
||||
if (!prefs) {
|
||||
prefs = await payload.create({
|
||||
collection: 'payload-preferences',
|
||||
depth: 0,
|
||||
data: {
|
||||
key: 'text-fields-list',
|
||||
user: {
|
||||
relationTo: user.collection,
|
||||
value: user.id,
|
||||
},
|
||||
value,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
prefs = await payload.update({
|
||||
collection: 'payload-preferences',
|
||||
id: prefs.id,
|
||||
data: {
|
||||
value: {
|
||||
...(prefs?.value ?? {}),
|
||||
...value,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return prefs
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import type { Where } from 'payload'
|
||||
import type { Config } from 'payload'
|
||||
import type { PaginatedDocs } from 'payload'
|
||||
import type { Config, PaginatedDocs, TypedUser, Where } from 'payload'
|
||||
|
||||
import * as qs from 'qs-esm'
|
||||
|
||||
@@ -120,6 +118,8 @@ export class RESTClient {
|
||||
|
||||
serverURL: string
|
||||
|
||||
public user: TypedUser
|
||||
|
||||
constructor(config: Config, args: Args) {
|
||||
this.config = config
|
||||
this.serverURL = args.serverURL
|
||||
@@ -254,7 +254,9 @@ export class RESTClient {
|
||||
const response = await fetch(`${this.serverURL}/api/${slug}${whereQuery}`, options)
|
||||
const { status } = response
|
||||
const result = await response.json()
|
||||
if (result.errors) throw new Error(result.errors[0].message)
|
||||
if (result.errors) {
|
||||
throw new Error(result.errors[0].message)
|
||||
}
|
||||
return { result, status }
|
||||
}
|
||||
|
||||
@@ -310,7 +312,9 @@ export class RESTClient {
|
||||
method: 'POST',
|
||||
})
|
||||
|
||||
let { token } = await response.json()
|
||||
const { user } = await response.json()
|
||||
|
||||
let token = user.token
|
||||
|
||||
// If the token is not in the response body, then we can extract it from the cookies
|
||||
if (!token) {
|
||||
@@ -319,6 +323,7 @@ export class RESTClient {
|
||||
token = tokenMatchResult?.groups?.token
|
||||
}
|
||||
|
||||
this.user = user
|
||||
this.token = token
|
||||
|
||||
return token
|
||||
|
||||
@@ -7,6 +7,7 @@ export const handler: PayloadHandler = async (req) => {
|
||||
await addDataAndFileToRequest(req)
|
||||
|
||||
const { data, payload, user } = req
|
||||
|
||||
const operation = data?.operation ? String(data.operation) : undefined
|
||||
|
||||
if (data?.operation && typeof payload[operation] === 'function') {
|
||||
|
||||
Reference in New Issue
Block a user