import type { Page } from '@playwright/test' import type { PayloadTestSDK } from 'helpers/sdk/index.js' import { expect, test } from '@playwright/test' import { devUser } from 'credentials.js' import { sortColumn, toggleColumn } from 'helpers/e2e/columns/index.js' import { addListFilter } from 'helpers/e2e/filters/index.js' import { goToNextPage } from 'helpers/e2e/goToNextPage.js' import { addGroupBy, clearGroupBy, closeGroupBy, openGroupBy } from 'helpers/e2e/groupBy/index.js' import { deletePreferences } from 'helpers/e2e/preferences.js' import { openNav } from 'helpers/e2e/toggleNav.js' import { reInitializeDB } from 'helpers/reInitializeDB.js' import * as path from 'path' import { fileURLToPath } from 'url' import type { Config, Post } from './payload-types.js' import { ensureCompilationIsDone, exactText, initPageConsoleErrorCatch, selectTableRow, } from '../helpers.js' import { AdminUrlUtil } from '../helpers/adminUrlUtil.js' import { initPayloadE2ENoConfig } from '../helpers/initPayloadE2ENoConfig.js' import { TEST_TIMEOUT_LONG } from '../playwright.config.js' import { postsSlug } from './collections/Posts/index.js' const { beforeEach } = test const filename = fileURLToPath(import.meta.url) const dirname = path.dirname(filename) test.describe('Group By', () => { let page: Page let url: AdminUrlUtil let serverURL: string let payload: PayloadTestSDK let user: any test.beforeAll(async ({ browser }, testInfo) => { testInfo.setTimeout(TEST_TIMEOUT_LONG) ;({ payload, serverURL } = await initPayloadE2ENoConfig({ dirname })) url = new AdminUrlUtil(serverURL, 'posts') const context = await browser.newContext() page = await context.newPage() initPageConsoleErrorCatch(page) await ensureCompilationIsDone({ page, serverURL }) user = await payload.login({ collection: 'users', data: { email: devUser.email, password: devUser.password, }, }) }) beforeEach(async () => { // await throttleTest({ // page, // context, // delay: 'Fast 4G', // }) await reInitializeDB({ serverURL, snapshotKey: 'groupByTests', }) await ensureCompilationIsDone({ page, serverURL }) }) test('should display group-by button only when `admin.groupBy` is enabled', async () => { await page.goto(url.list) await expect(page.locator('#toggle-group-by')).toBeVisible() await page.goto(new AdminUrlUtil(serverURL, 'users').list) await expect(page.locator('#toggle-group-by')).toBeHidden() }) test('should open and close group-by dropdown', async () => { await page.goto(url.list) await openGroupBy(page) await expect(page.locator('#list-controls-group-by.rah-static--height-auto')).toBeVisible() await closeGroupBy(page) await expect(page.locator('#list-controls-group-by.rah-static--height-auto')).toBeHidden() }) test('should display field options in group-by dropdown', async () => { await page.goto(url.list) const { groupByContainer } = await openGroupBy(page) // TODO: expect no initial selection and for the sort control to be disabled const field = groupByContainer.locator('#group-by--field-select') await field.click() await expect( field.locator('.rs__option', { hasText: exactText('Title'), }), ).toBeVisible() }) test('should omit unsupported fields from appearing as options in the group-by dropdown', async () => { await page.goto(url.list) await openGroupBy(page) // certain fields are not allowed to be grouped by, for example rich text and the ID field itself const forbiddenOptions = ['ID', 'Content'] const field = page.locator('#group-by--field-select') await field.click() for (const fieldOption of forbiddenOptions) { const optionEl = page.locator('.rs__option', { hasText: exactText(fieldOption) }) await expect(optionEl).toHaveCount(0) } }) test('should properly group by field', async () => { await page.goto(url.list) await addGroupBy(page, { fieldLabel: 'Category', fieldPath: 'category' }) await expect(page.locator('.table-wrap')).toHaveCount(2) await expect(page.locator('.group-by-header')).toHaveCount(2) await expect( page.locator('.group-by-header__heading', { hasText: exactText('Category 1') }), ).toBeVisible() await expect(page.locator('.table-wrap').first().locator('tbody tr')).toHaveCount(10) const table1CategoryCells = page .locator('.table-wrap') .first() .locator('tbody tr td.cell-category') // TODO: is there a way to iterate over all cells and check they all match? I could not get this to work. await expect(table1CategoryCells.first()).toHaveText(/Category 1/) await expect( page.locator('.group-by-header__heading', { hasText: exactText('Category 2') }), ).toBeVisible() const table2 = page.locator('.table-wrap').nth(1) await expect(table2).toBeVisible() await table2.scrollIntoViewIfNeeded() await expect(page.locator('.table-wrap').nth(1).locator('tbody tr')).toHaveCount(10) const table2CategoryCells = page .locator('.table-wrap') .nth(1) .locator('tbody tr td.cell-category') // TODO: is there a way to iterate over all cells and check they all match? I could not get this to work. await expect(table2CategoryCells.first()).toHaveText(/Category 2/) }) test('should load group-by from user preferences', async () => { await deletePreferences({ payload, key: `${postsSlug}.list`, user, }) await page.goto(url.list) await expect(page).not.toHaveURL(/groupBy=/) await addGroupBy(page, { fieldLabel: 'Category', fieldPath: 'category' }) await expect(page).toHaveURL(/groupBy=category/) await expect(page.locator('.table-wrap')).toHaveCount(2) await page.goto(url.admin) // click on the "Posts" link in the sidebar to invoke a soft navigation await openNav(page) await page.locator(`.nav a[href="/admin/collections/${postsSlug}"]`).click() await expect(page).toHaveURL(/groupBy=category/) await expect(page.locator('.table-wrap')).toHaveCount(2) }) test('should reset group-by using the global "clear" button', async () => { await page.goto(url.list) const { groupByContainer } = await openGroupBy(page) const field = groupByContainer.locator('#group-by--field-select') await expect(field.locator('.react-select--single-value')).toHaveText('Select a value') await expect(groupByContainer.locator('#group-by--reset')).toBeHidden() await addGroupBy(page, { fieldLabel: 'Category', fieldPath: 'category' }) await expect(page.locator('.table-wrap')).toHaveCount(2) await expect(page.locator('.group-by-header')).toHaveCount(2) await clearGroupBy(page) }) test('should reset group-by using the select field\'s "x" button', async () => { await page.goto(url.list) const { field, groupByContainer } = await addGroupBy(page, { fieldLabel: 'Category', fieldPath: 'category', }) await expect(page.locator('.table-wrap')).toHaveCount(2) await expect(page.locator('.group-by-header')).toHaveCount(2) // click the "x" button on the select field itself await field.locator('.clear-indicator').click() await expect(field.locator('.react-select--single-value')).toHaveText('Select a value') await expect(page).not.toHaveURL(/&groupBy=/) await expect(groupByContainer.locator('#field-direction input')).toBeDisabled() await expect(page.locator('.table-wrap')).toHaveCount(1) await expect(page.locator('.group-by-header')).toHaveCount(0) }) test('should group by relationships even when their values are null', async () => { await payload.create({ collection: postsSlug, data: { title: 'My Post', category: null, }, }) await page.goto(url.list) await addGroupBy(page, { fieldLabel: 'Category', fieldPath: 'category' }) await expect(page.locator('.table-wrap')).toHaveCount(3) await expect( page.locator('.group-by-header__heading', { hasText: exactText('No value') }), ).toBeVisible() }) test('should group by date fields even when their values are null', async () => { await payload.create({ collection: postsSlug, data: { title: 'My Post', date: null, }, }) await page.goto(url.list) await addGroupBy(page, { fieldLabel: 'Date', fieldPath: 'date' }) await expect(page.locator('.table-wrap')).toHaveCount(1) await expect( page.locator('.group-by-header__heading', { hasText: exactText('No value') }), ).toBeVisible() }) test('should group by boolean values', async () => { await Promise.all([ await payload.create({ collection: postsSlug, data: { title: 'Null Post', checkbox: null, }, }), await payload.create({ collection: postsSlug, data: { title: 'True Post', checkbox: true, }, }), await payload.create({ collection: postsSlug, data: { title: 'False Post', checkbox: false, }, }), ]) await page.goto(url.list) await addGroupBy(page, { fieldLabel: 'Checkbox', fieldPath: 'checkbox', }) await expect(page.locator('.table-wrap')).toHaveCount(3) await expect(page.locator('.group-by-header')).toHaveCount(3) await expect( page.locator('.group-by-header__heading', { hasText: exactText('No value') }), ).toBeVisible() await expect( page.locator('.group-by-header__heading', { hasText: exactText('True') }), ).toBeVisible() await expect( page.locator('.group-by-header__heading', { hasText: exactText('False') }), ).toBeVisible() }) test('should sort the group-by field globally', async () => { await page.goto(url.list) const { groupByContainer } = await addGroupBy(page, { fieldLabel: 'Category', fieldPath: 'category', }) const firstHeading = page.locator('.group-by-header__heading').first() await expect(firstHeading).toHaveText(/Category 1/) const secondHeading = page.locator('.group-by-header__heading').nth(1) await expect(secondHeading).toHaveText(/Category 2/) await groupByContainer.locator('#group-by--sort').click() await groupByContainer.locator('.rs__option', { hasText: exactText('Descending') })?.click() await expect(page.locator('.group-by-header__heading').first()).toHaveText(/Category 2/) await expect(page.locator('.group-by-header__heading').nth(1)).toHaveText(/Category 1/) }) test('should sort by columns within each table (will affect all tables)', async () => { await page.goto(url.list) await addGroupBy(page, { fieldLabel: 'Category', fieldPath: 'category', }) const table1 = page.locator('.table-wrap').first() await sortColumn(page, { scope: table1, fieldLabel: 'Title', fieldPath: 'title', targetState: 'asc', }) const table1AscOrder = ['Find me', 'Post 1', 'Post 10', 'Post 11'] const table2AscOrder = ['Find me', 'Post 16', 'Post 17', 'Post 18'] const table1Titles = table1.locator('tbody tr td.cell-title') const table2Titles = page.locator('.table-wrap').nth(1).locator('tbody tr td.cell-title') await expect(table1Titles).toHaveCount(10) await expect(table2Titles).toHaveCount(10) // Note: it would be nice to put this in a loop, but this was flaky await expect(table1Titles.nth(0)).toHaveText(table1AscOrder[0] || '') await expect(table1Titles.nth(1)).toHaveText(table1AscOrder[1] || '') await expect(table2Titles.nth(0)).toHaveText(table2AscOrder[0] || '') await expect(table2Titles.nth(1)).toHaveText(table2AscOrder[1] || '') await sortColumn(page, { scope: table1, fieldLabel: 'Title', fieldPath: 'title', targetState: 'desc', }) const table1DescOrder = ['Post 9', 'Post 8', 'Post 7', 'Post 6'] const table2DescOrder = ['Post 30', 'Post 29', 'Post 28', 'Post 27'] // Note: it would be nice to put this in a loop, but this was flaky await expect(table1Titles.nth(0)).toHaveText(table1DescOrder[0] || '') await expect(table1Titles.nth(1)).toHaveText(table1DescOrder[1] || '') await expect(table2Titles.nth(0)).toHaveText(table2DescOrder[0] || '') await expect(table2Titles.nth(1)).toHaveText(table2DescOrder[1] || '') }) test('should apply columns to all tables', async () => { await page.goto(url.list) await addGroupBy(page, { fieldLabel: 'Category', fieldPath: 'category', }) const table1ColumnHeadings = page.locator('.table-wrap').nth(0).locator('thead tr th') await expect(table1ColumnHeadings.nth(1)).toHaveText('Title') await expect(table1ColumnHeadings.nth(2)).toHaveText('Category') const table2ColumnHeadings = page.locator('.table-wrap').nth(1).locator('thead tr th') await expect(table2ColumnHeadings.nth(1)).toHaveText('Title') await expect(table2ColumnHeadings.nth(2)).toHaveText('Category') await toggleColumn(page, { columnLabel: 'Title', targetState: 'off' }) await expect(table1ColumnHeadings.locator('text=Title')).toHaveCount(0) await expect(table1ColumnHeadings.nth(1)).toHaveText('Category') await expect(table2ColumnHeadings.locator('text=Title')).toHaveCount(0) await expect(table2ColumnHeadings.nth(1)).toHaveText('Category') }) test('should apply filters to all tables', async () => { await page.goto(url.list) await addGroupBy(page, { fieldLabel: 'Category', fieldPath: 'category', }) await addListFilter({ page, fieldLabel: 'Title', operatorLabel: 'equals', value: 'Find me', }) const table1 = page.locator('.table-wrap').first() await expect(table1).toBeVisible() const table1Rows = table1.locator('tbody tr') await expect(table1Rows).toHaveCount(1) await expect(table1Rows.first().locator('td.cell-title')).toHaveText('Find me') const table2 = page.locator('.table-wrap').nth(1) await expect(table2).toBeVisible() const table2Rows = table2.locator('tbody tr') await expect(table2Rows).toHaveCount(1) await expect(table2Rows.first().locator('td.cell-title')).toHaveText('Find me') }) test('should apply filters to the distinct results of the group-by field', async () => { // This ensures that no tables are rendered without docs await page.goto(url.list) await addGroupBy(page, { fieldLabel: 'Category', fieldPath: 'category', }) await expect(page.locator('.table-wrap')).toHaveCount(2) await addListFilter({ page, fieldLabel: 'Category', operatorLabel: 'equals', value: 'Category 1', }) await expect(page.locator('.table-wrap')).toHaveCount(1) // Reset the filter by reloading the page without URL params // TODO: There are no current test helpers for this await page.goto(url.list) await addListFilter({ page, fieldLabel: 'Title', operatorLabel: 'equals', value: 'This title does not exist', }) await expect(page.locator('.table-wrap')).toHaveCount(0) await page.locator('.collection-list__no-results').isVisible() }) test('should paginate globally (all tables)', async () => { await page.goto(url.list) await addGroupBy(page, { fieldLabel: 'Title', fieldPath: 'title' }) await expect(page.locator('.sticky-toolbar')).toBeVisible() }) test('should paginate per table', async () => { await page.goto(url.list) await addGroupBy(page, { fieldLabel: 'Category', fieldPath: 'category' }) const table1 = page.locator('.table-wrap').first() const table2 = page.locator('.table-wrap').nth(1) await expect(table1.locator('.page-controls')).toBeVisible() await expect(table2.locator('.page-controls')).toBeVisible() await goToNextPage(page, { scope: table1, // TODO: this actually does affect the URL, but not in the same way as traditional pagination // e.g. it manipulates the `?queryByGroup=` param instead of `?page=2` affectsURL: false, }) }) test('should reset ?queryByGroup= param when other params change', async () => { await page.goto(url.list) await addGroupBy(page, { fieldLabel: 'Category', fieldPath: 'category' }) const table1 = page.locator('.table-wrap').first() const table2 = page.locator('.table-wrap').nth(1) await expect(table1.locator('.page-controls')).toBeVisible() await expect(table2.locator('.page-controls')).toBeVisible() await goToNextPage(page, { scope: table1, affectsURL: false, }) await expect(page).toHaveURL(/queryByGroup=/) await clearGroupBy(page) await expect(page).not.toHaveURL(/queryByGroup=/) }) test('should not render per table pagination controls when group-by is not active', async () => { // delete user prefs to ensure that group-by isn't populated after loading the page await deletePreferences({ payload, key: `${postsSlug}.list`, user }) await page.goto(url.list) await expect(page.locator('.page-controls')).toHaveCount(1) }) test('should render date fields in proper format when displayed as table headers', async () => { await page.goto(url.list) await addGroupBy(page, { fieldLabel: 'Updated At', fieldPath: 'updatedAt' }) // the value of the updated at column in the table should match exactly the value in the table cell const table1 = page.locator('.table-wrap').first() const firstTableHeading = table1.locator('.group-by-header__heading') const firstRowUpdatedAtCell = table1.locator('tbody tr td.cell-updatedAt').first() const headingText = (await firstTableHeading.textContent())?.trim() const cellText = (await firstRowUpdatedAtCell.textContent())?.trim() expect(headingText).toBeTruthy() expect(cellText).toBeTruthy() expect(headingText).toEqual(cellText) }) test.skip('should group by nested fields', async () => { await page.goto(url.list) expect(true).toBe(true) }) test('can select all rows within a single table as expected', async () => { await page.goto(url.list) await addGroupBy(page, { fieldLabel: 'Category', fieldPath: 'category' }) const firstTable = page.locator('.table-wrap').first() const firstTableRows = firstTable.locator('tbody tr') await expect(firstTableRows).toHaveCount(10) await firstTable.locator('input#select-all').check() await expect(page.locator('.list-header .list-selection')).toBeHidden() await expect(firstTable.locator('button#select-all-across-pages')).toBeVisible() await firstTable.locator('button#select-all-across-pages').click() await expect(firstTable.locator('button#select-all-across-pages')).toBeHidden() }) test('can bulk edit within a single table without affecting the others', async () => { await page.goto(url.list) await addGroupBy(page, { fieldLabel: 'Category', fieldPath: 'category' }) const firstTable = page.locator('.table-wrap').first() const secondTable = page.locator('.table-wrap').nth(1) const firstTableRows = firstTable.locator('tbody tr') const secondTableRows = secondTable.locator('tbody tr') await sortColumn(page, { scope: firstTable, fieldLabel: 'Title', fieldPath: 'title', targetState: 'asc', }) // select 'Find me' from both tables, only the first should get edited in the end await selectTableRow(firstTable, 'Find me') await selectTableRow(secondTable, 'Find me') await firstTable.locator('.list-selection .edit-many__toggle').click() const modal = page.locator('[id$="-edit-posts"]').first() await expect(modal).toBeVisible() await modal.locator('.field-select .rs__control').click() await modal.locator('.field-select .rs__option', { hasText: exactText('Title') }).click() const field = modal.locator(`#field-title`) await expect(field).toBeVisible() await field.fill('Find me (updated)') await modal.locator('.form-submit button[type="submit"].edit-many__save').click() await expect( firstTableRows.locator('td.cell-title', { hasText: exactText('Find me (updated)') }), ).toHaveCount(0) await expect( secondTableRows.locator('td.cell-title', { hasText: exactText('Find me') }), ).toHaveCount(1) }) test('can bulk delete within a single table without affecting the others', async () => { await page.goto(url.list) await addGroupBy(page, { fieldLabel: 'Category', fieldPath: 'category' }) const firstTable = page.locator('.table-wrap').first() const secondTable = page.locator('.table-wrap').nth(1) const firstTableRows = firstTable.locator('tbody tr') const secondTableRows = secondTable.locator('tbody tr') await sortColumn(page, { scope: firstTable, fieldLabel: 'Title', fieldPath: 'title', targetState: 'asc', }) // select 'Find me' from both tables, only the first should get deleted in the end await selectTableRow(firstTable, 'Find me') await selectTableRow(secondTable, 'Find me') await firstTable.locator('.list-selection .delete-documents__toggle').click() const modal = page.locator('[id$="-confirm-delete-many-docs"]').first() await expect(modal).toBeVisible() await modal.locator('#confirm-action').click() await expect( firstTableRows.locator('td.cell-title', { hasText: exactText('Find me') }), ).toHaveCount(0) await expect( secondTableRows.locator('td.cell-title', { hasText: exactText('Find me') }), ).toHaveCount(1) }) test('can bulk edit across pages within a single table without affecting the others', async () => { await page.goto(url.list) await addGroupBy(page, { fieldLabel: 'Category', fieldPath: 'category' }) const firstTable = page.locator('.table-wrap').first() const secondTable = page.locator('.table-wrap').nth(1) const firstTableRows = firstTable.locator('tbody tr') const secondTableRows = secondTable.locator('tbody tr') // click the select all checkbox, then the "select all across pages" button await firstTable.locator('input#select-all').check() await firstTable.locator('button#select-all-across-pages').click() // now edit all titles and ensure that only the first table gets updated, not the second await firstTable.locator('.list-selection .edit-many__toggle').click() const modal = page.locator('[id$="-edit-posts"]').first() await expect(modal).toBeVisible() await modal.locator('.field-select .rs__control').click() await modal.locator('.field-select .rs__option', { hasText: exactText('Title') }).click() const field = modal.locator(`#field-title`) await expect(field).toBeVisible() await field.fill('Bulk edit across all pages') await modal.locator('.form-submit button[type="submit"].edit-many__save').click() await expect( firstTableRows.locator('td.cell-title', { hasText: exactText('Bulk edit across all pages') }), ).toHaveCount(10) await expect( secondTableRows.locator('td.cell-title', { hasText: exactText('Bulk edit across all pages'), }), ).toHaveCount(0) }) test('should group by monomorphic has one relationship field', async () => { const relationshipsUrl = new AdminUrlUtil(serverURL, 'relationships') await page.goto(relationshipsUrl.list) await addGroupBy(page, { fieldLabel: 'Mono Has One Relationship', fieldPath: 'MonoHasOneRelationship', }) // Should show populated values first, then "No value" await expect(page.locator('.table-wrap')).toHaveCount(2) await expect(page.locator('.group-by-header')).toHaveCount(2) // Check that Category 1 appears as a group await expect( page.locator('.group-by-header__heading', { hasText: exactText('Category 1') }), ).toBeVisible() // Check that "No value" appears last await expect( page.locator('.group-by-header__heading', { hasText: exactText('No value') }), ).toBeVisible() }) test('should group by monomorphic has many relationship field', async () => { const relationshipsUrl = new AdminUrlUtil(serverURL, 'relationships') await page.goto(relationshipsUrl.list) await addGroupBy(page, { fieldLabel: 'Mono Has Many Relationship', fieldPath: 'MonoHasManyRelationship', }) // Should flatten hasMany arrays - each category gets its own group await expect(page.locator('.table-wrap')).toHaveCount(3) await expect(page.locator('.group-by-header')).toHaveCount(3) // Both categories should appear as separate groups await expect( page.locator('.group-by-header__heading', { hasText: exactText('Category 1') }), ).toBeVisible() await expect( page.locator('.group-by-header__heading', { hasText: exactText('Category 2') }), ).toBeVisible() // "No value" should appear last await expect( page.locator('.group-by-header__heading', { hasText: exactText('No value') }), ).toBeVisible() }) test('should group by polymorphic has one relationship field', async () => { const relationshipsUrl = new AdminUrlUtil(serverURL, 'relationships') await page.goto(relationshipsUrl.list) await addGroupBy(page, { fieldLabel: 'Poly Has One Relationship', fieldPath: 'PolyHasOneRelationship', }) // Should show groups for both collection types plus "No value" await expect(page.locator('.table-wrap')).toHaveCount(3) await expect(page.locator('.group-by-header')).toHaveCount(3) // Check for Category 1 group await expect( page.locator('.group-by-header__heading', { hasText: exactText('Category 1') }), ).toBeVisible() // Check for Post group (should display the post's title as useAsTitle) await expect(page.locator('.group-by-header__heading', { hasText: 'Find me' })).toBeVisible() // "No value" should appear last await expect( page.locator('.group-by-header__heading', { hasText: exactText('No value') }), ).toBeVisible() }) test('should group by polymorphic has many relationship field', async () => { const relationshipsUrl = new AdminUrlUtil(serverURL, 'relationships') await page.goto(relationshipsUrl.list) await addGroupBy(page, { fieldLabel: 'Poly Has Many Relationship', fieldPath: 'PolyHasManyRelationship', }) // Should flatten polymorphic hasMany arrays - each relationship gets its own group // Expecting: Category 1, Category 2, Post, and "No value" = 4 groups await expect(page.locator('.table-wrap')).toHaveCount(4) await expect(page.locator('.group-by-header')).toHaveCount(4) // Check for both category groups await expect( page.locator('.group-by-header__heading', { hasText: exactText('Category 1') }), ).toBeVisible() await expect( page.locator('.group-by-header__heading', { hasText: exactText('Category 2') }), ).toBeVisible() // Check for post group await expect(page.locator('.group-by-header__heading', { hasText: 'Find me' })).toBeVisible() // "No value" should appear last (documents without any relationships) await expect( page.locator('.group-by-header__heading', { hasText: exactText('No value') }), ).toBeVisible() }) test('should hide field from groupBy options when admin.disableGroupBy is true', async () => { await page.goto(url.list) const { groupByContainer } = await openGroupBy(page) const field = groupByContainer.locator('#group-by--field-select') await field.click() await expect( field.locator('.rs__option', { hasText: exactText('Virtual Title From Category'), }), ).toBeHidden() }) test.describe('Trash', () => { test('should show trashed docs in trash view when group-by is active', async () => { await page.goto(url.list) // Enable group-by on Category await addGroupBy(page, { fieldLabel: 'Category', fieldPath: 'category' }) await expect(page.locator('.table-wrap')).toHaveCount(2) // We expect 2 groups initially // Trash the first document in the first group const firstTable = page.locator('.table-wrap').first() await firstTable.locator('.row-1 .cell-_select input').check() await firstTable.locator('.list-selection__button[aria-label="Delete"]').click() const firstGroupID = await firstTable .locator('.group-by-header__heading') .getAttribute('data-group-id') const modalId = `[id^="${firstGroupID}-confirm-delete-many-docs"]` await expect(page.locator(modalId)).toBeVisible() // Confirm trash (skip permanent delete) await page.locator(`${modalId} #confirm-action`).click() await expect(page.locator('.payload-toast-container .toast-success')).toHaveText( '1 Post moved to trash.', ) // Go to the trash view await page.locator('#trash-view-pill').click() await expect(page).toHaveURL(/\/posts\/trash(\?|$)/) // Re-enable group-by on Category in trash view await addGroupBy(page, { fieldLabel: 'Category', fieldPath: 'category' }) await expect(page.locator('.table-wrap')).toHaveCount(1) // Should only have Category 1 (or the trashed doc's category) // Ensure the trashed doc is visible await expect( page.locator('.table-wrap tbody tr td.cell-title', { hasText: 'Find me' }), ).toBeVisible() }) test('should properly clear group-by in trash view', async () => { await createTrashedPostDoc({ title: 'Trashed Post 1' }) await page.goto(url.trash) // Enable group-by on Title await addGroupBy(page, { fieldLabel: 'Title', fieldPath: 'title' }) await expect(page.locator('.table-wrap')).toHaveCount(1) await expect(page.locator('.group-by-header')).toHaveText('Trashed Post 1') await page.locator('#group-by--reset').click() await expect(page.locator('.group-by-header')).toBeHidden() }) test('should properly navigate to trashed doc edit view from group-by in trash view', async () => { await createTrashedPostDoc({ title: 'Trashed Post 1' }) await page.goto(url.trash) // Enable group-by on Title await addGroupBy(page, { fieldLabel: 'Title', fieldPath: 'title' }) await expect(page.locator('.table-wrap')).toHaveCount(1) await expect(page.locator('.group-by-header')).toHaveText('Trashed Post 1') await page.locator('.table-wrap tbody tr td.cell-title a').click() await expect(page).toHaveURL(/\/posts\/trash\/\d+/) }) }) async function createTrashedPostDoc(data: Partial): Promise { return payload.create({ collection: postsSlug, data: { ...data, deletedAt: new Date().toISOString(), // Set the post as trashed }, }) as unknown as Promise } })