fix(ui): sort resets columns (#10402)

Fixes #10018. When toggling columns, then sorting them, the table is
reset to the collection's default columns instead of the user's
preferred columns. This is because when sorting columns, a stale
client-side cache of the user's preferences is used to update their sort
preference. This is because when column state is constructed
server-side, it completely bypasses the client-side cache. To fix this,
sort preferences are now also set on the server right alongside column
preferences, which performs an upsert-like operation to ensure that no
existing preferences are lost.
This commit is contained in:
Jacob Fletcher
2025-01-06 16:57:19 -05:00
committed by GitHub
parent df827c0fdd
commit a83a430a3a
11 changed files with 195 additions and 142 deletions

View File

@@ -1001,6 +1001,57 @@ describe('List View', () => {
await expect(page.locator('#heading-id')).toBeHidden()
await expect(page.locator('.cell-id')).toHaveCount(0)
})
test('should sort without resetting column preferences', async () => {
await payload.delete({
collection: 'payload-preferences',
where: {
key: {
equals: `${postsCollectionSlug}.list`,
},
},
})
await page.goto(postsUrl.list)
// sort by title
await page.locator('#heading-title button.sort-column__asc').click()
await page.waitForURL(/sort=title/)
// enable a column that is _not_ part of this collection's default columns
await toggleColumn(page, { columnLabel: 'Status', targetState: 'on' })
await page.locator('#heading-_status').waitFor({ state: 'visible' })
const columnAfterSort = page.locator(
`.list-controls__columns .column-selector .column-selector__column`,
{
hasText: exactText('Status'),
},
)
await expect(columnAfterSort).toHaveClass(/column-selector__column--active/)
await expect(page.locator('#heading-_status')).toBeVisible()
await expect(page.locator('.cell-_status').first()).toBeVisible()
// sort by title again in descending order
await page.locator('#heading-title button.sort-column__desc').click()
await page.waitForURL(/sort=-title/)
// allow time for components to re-render
await wait(100)
// ensure the column is still visible
const columnAfterSecondSort = page.locator(
`.list-controls__columns .column-selector .column-selector__column`,
{
hasText: exactText('Status'),
},
)
await expect(columnAfterSecondSort).toHaveClass(/column-selector__column--active/)
await expect(page.locator('#heading-_status')).toBeVisible()
await expect(page.locator('.cell-_status').first()).toBeVisible()
})
})
describe('i18n', () => {

View File

@@ -135,6 +135,8 @@ export interface Upload {
};
}
/**
* This is a custom collection description.
*
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "posts".
*/
@@ -175,10 +177,14 @@ export interface Post {
defaultValueField?: string | null;
relationship?: (string | null) | Post;
customCell?: string | null;
upload?: (string | null) | Upload;
hiddenField?: string | null;
adminHiddenField?: string | null;
disableListColumnText?: string | null;
disableListFilterText?: string | null;
/**
* This is a very long description that takes many characters to complete and hopefully will wrap instead of push the sidebar open, lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, voluptatum voluptates. Quisquam, voluptatum voluptates.
*/
sidebarField?: string | null;
updatedAt: string;
createdAt: string;
@@ -260,7 +266,13 @@ export interface CustomField {
id: string;
customTextServerField?: string | null;
customTextClientField?: string | null;
/**
* Static field description.
*/
descriptionAsString?: string | null;
/**
* Function description
*/
descriptionAsFunction?: string | null;
descriptionAsComponent?: string | null;
customSelectField?: string | null;
@@ -548,6 +560,7 @@ export interface PostsSelect<T extends boolean = true> {
defaultValueField?: T;
relationship?: T;
customCell?: T;
upload?: T;
hiddenField?: T;
adminHiddenField?: T;
disableListColumnText?: T;