### What? Adds a new `disableGroupBy` admin config property for fields to control their visibility in the list view GroupBy dropdown. ### Why Previously, the GroupByBuilder was incorrectly using `disableListFilter` to determine which fields to show in the group-by dropdown. ### How - Added new `disableGroupBy` property to the field admin config types. - Updated `GroupByBuilder` to filter fields based on `disableGroupBy` instead of `disableListFilter` --- - To see the specific tasks where the Asana app for GitHub is being used, see below: - https://app.asana.com/0/0/1211511898438807
898 lines
30 KiB
TypeScript
898 lines
30 KiB
TypeScript
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<Config>
|
|
let user: any
|
|
|
|
test.beforeAll(async ({ browser }, testInfo) => {
|
|
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
|
;({ payload, serverURL } = await initPayloadE2ENoConfig<Config>({ 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<Post>): Promise<Post> {
|
|
return payload.create({
|
|
collection: postsSlug,
|
|
data: {
|
|
...data,
|
|
deletedAt: new Date().toISOString(), // Set the post as trashed
|
|
},
|
|
}) as unknown as Promise<Post>
|
|
}
|
|
})
|