Follow up to #11375. When setting `filterOptions` on relationship or upload fields _that are nested within a named field_, those options won't be applied to the `Filter` component in the list view. This is because of how we key the results when resolving `filterOptions` on the server. Instead of using the field path as expected, we were using the field name, causing a failed lookup on the front-end. This also solves an issue where two fields with the same name would override each other's `filterOptions`, since field names alone are not unique. Unrelated: this PR also does some general housekeeping to e2e test helpers. --- - To see the specific tasks where the Asana app for GitHub is being used, see below: - https://app.asana.com/0/0/1211332845301583
412 lines
13 KiB
TypeScript
412 lines
13 KiB
TypeScript
import type { BrowserContext, Page } from '@playwright/test'
|
|
|
|
import { expect, test } from '@playwright/test'
|
|
import { devUser } from 'credentials.js'
|
|
import { openListColumns, toggleColumn } from 'helpers/e2e/columns/index.js'
|
|
import { openNav } from 'helpers/e2e/toggleNav.js'
|
|
import * as path from 'path'
|
|
import { fileURLToPath } from 'url'
|
|
|
|
import type { PayloadTestSDK } from '../helpers/sdk/index.js'
|
|
import type { Config, PayloadQueryPreset } from './payload-types.js'
|
|
|
|
import {
|
|
ensureCompilationIsDone,
|
|
exactText,
|
|
initPageConsoleErrorCatch,
|
|
saveDocAndAssert,
|
|
// throttleTest,
|
|
} from '../helpers.js'
|
|
import { AdminUrlUtil } from '../helpers/adminUrlUtil.js'
|
|
import { initPayloadE2ENoConfig } from '../helpers/initPayloadE2ENoConfig.js'
|
|
import { TEST_TIMEOUT_LONG } from '../playwright.config.js'
|
|
import { assertURLParams } from './helpers/assertURLParams.js'
|
|
import { openQueryPresetDrawer } from './helpers/openQueryPresetDrawer.js'
|
|
import { clearSelectedPreset, selectPreset } from './helpers/togglePreset.js'
|
|
import { seedData } from './seed.js'
|
|
import { pagesSlug } from './slugs.js'
|
|
|
|
const filename = fileURLToPath(import.meta.url)
|
|
const dirname = path.dirname(filename)
|
|
|
|
const { beforeAll, describe, beforeEach } = test
|
|
|
|
let page: Page
|
|
let pagesUrl: AdminUrlUtil
|
|
let payload: PayloadTestSDK<Config>
|
|
let serverURL: string
|
|
let everyoneID: string | undefined
|
|
let context: BrowserContext
|
|
let user: any
|
|
let ownerUser: any
|
|
|
|
let seededData: {
|
|
everyone: PayloadQueryPreset
|
|
onlyMe: PayloadQueryPreset
|
|
specificUsers: PayloadQueryPreset
|
|
}
|
|
|
|
describe('Query Presets', () => {
|
|
beforeAll(async ({ browser }, testInfo) => {
|
|
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
|
;({ payload, serverURL } = await initPayloadE2ENoConfig<Config>({ dirname }))
|
|
|
|
pagesUrl = new AdminUrlUtil(serverURL, pagesSlug)
|
|
|
|
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,
|
|
},
|
|
})
|
|
?.then((res) => res.user) // TODO: this type is wrong
|
|
|
|
ownerUser = await payload
|
|
.find({
|
|
collection: 'users',
|
|
where: {
|
|
name: {
|
|
equals: 'Owner',
|
|
},
|
|
},
|
|
limit: 1,
|
|
depth: 0,
|
|
})
|
|
?.then((res) => res.docs[0])
|
|
})
|
|
|
|
beforeEach(async () => {
|
|
// await throttleTest({
|
|
// page,
|
|
// context,
|
|
// delay: 'Fast 4G',
|
|
// })
|
|
|
|
// clear and reseed everything
|
|
try {
|
|
await payload.delete({
|
|
collection: 'payload-query-presets',
|
|
where: {
|
|
id: {
|
|
exists: true,
|
|
},
|
|
},
|
|
})
|
|
|
|
const [, everyone, onlyMe, specificUsers] = await Promise.all([
|
|
payload.delete({
|
|
collection: 'payload-preferences',
|
|
where: {
|
|
and: [
|
|
{
|
|
key: { equals: 'pages-list' },
|
|
},
|
|
{
|
|
'user.relationTo': {
|
|
equals: 'users',
|
|
},
|
|
},
|
|
{
|
|
'user.value': {
|
|
equals: user.id,
|
|
},
|
|
},
|
|
],
|
|
},
|
|
}),
|
|
payload.create({
|
|
collection: 'payload-query-presets',
|
|
data: seedData.everyone({ ownerUserID: ownerUser?.id || '' }),
|
|
}),
|
|
payload.create({
|
|
collection: 'payload-query-presets',
|
|
data: seedData.onlyMe({ ownerUserID: ownerUser?.id || '' }),
|
|
}),
|
|
payload.create({
|
|
collection: 'payload-query-presets',
|
|
data: seedData.specificUsers({ ownerUserID: ownerUser?.id || '', adminUserID: user.id }),
|
|
}),
|
|
])
|
|
|
|
seededData = {
|
|
everyone,
|
|
onlyMe,
|
|
specificUsers,
|
|
}
|
|
|
|
everyoneID = everyone.id
|
|
} catch (error) {
|
|
console.error('Error in beforeEach:', error)
|
|
}
|
|
})
|
|
|
|
test('should select preset and apply filters', async () => {
|
|
await page.goto(pagesUrl.list)
|
|
|
|
await selectPreset({ page, presetTitle: seededData.everyone.title })
|
|
|
|
await assertURLParams({
|
|
page,
|
|
columns: seededData.everyone.columns,
|
|
preset: everyoneID,
|
|
})
|
|
})
|
|
|
|
test('should clear selected preset and reset filters', async () => {
|
|
await page.goto(pagesUrl.list)
|
|
|
|
await selectPreset({ page, presetTitle: seededData.everyone.title })
|
|
|
|
await clearSelectedPreset({ page })
|
|
|
|
// ensure that the preset was cleared from preferences by navigating without the `?preset=` param
|
|
// e.g. do not do `page.reload()`
|
|
await page.goto(pagesUrl.list)
|
|
|
|
// poll url to ensure that `?preset=` param is not present
|
|
// this is first set to an empty string to clear from the user's preferences
|
|
// it is then removed entirely after it is processed on the server
|
|
const regex = /preset=/
|
|
await page.waitForURL((url) => !regex.test(url.search), { timeout: TEST_TIMEOUT_LONG })
|
|
|
|
await expect(
|
|
page.locator('button#select-preset', {
|
|
hasText: exactText('Select Preset'),
|
|
}),
|
|
).toBeVisible()
|
|
})
|
|
|
|
test('should delete a preset, clear selection, and reset changes', async () => {
|
|
await page.goto(pagesUrl.list)
|
|
await selectPreset({ page, presetTitle: seededData.everyone.title })
|
|
|
|
await page.locator('#delete-preset').click()
|
|
|
|
await page.locator('#confirm-delete-preset #confirm-action').click()
|
|
|
|
// columns can either be omitted or an empty string after being cleared
|
|
const regex = /columns=(?:\[\]|$)/
|
|
|
|
await page.waitForURL((url) => !regex.test(url.search), {
|
|
timeout: TEST_TIMEOUT_LONG,
|
|
})
|
|
|
|
await expect(
|
|
page.locator('button#select-preset', {
|
|
hasText: exactText('Select Preset'),
|
|
}),
|
|
).toBeVisible()
|
|
|
|
await openQueryPresetDrawer({ page })
|
|
const modal = page.locator('[id^=list-drawer_0_]')
|
|
await expect(modal).toBeVisible()
|
|
|
|
await expect(
|
|
modal.locator('tbody tr td button', {
|
|
hasText: exactText(seededData.everyone.title),
|
|
}),
|
|
).toBeHidden()
|
|
})
|
|
|
|
test('should save last used preset to preferences and load on initial render', async () => {
|
|
await page.goto(pagesUrl.list)
|
|
|
|
await selectPreset({ page, presetTitle: seededData.everyone.title })
|
|
|
|
await page.goto(pagesUrl.list)
|
|
|
|
await assertURLParams({
|
|
page,
|
|
columns: seededData.everyone.columns,
|
|
where: seededData.everyone.where,
|
|
preset: everyoneID,
|
|
})
|
|
|
|
// for good measure, also soft navigate away and back
|
|
await page.goto(pagesUrl.admin)
|
|
await openNav(page)
|
|
await page.click(`a[href="/admin/collections/${pagesSlug}"]`)
|
|
|
|
await assertURLParams({
|
|
page,
|
|
columns: seededData.everyone.columns,
|
|
where: seededData.everyone.where,
|
|
preset: everyoneID,
|
|
})
|
|
})
|
|
|
|
test('should only show "edit" and "delete" controls when there is an active preset', async () => {
|
|
await page.goto(pagesUrl.list)
|
|
await expect(page.locator('#edit-preset')).toBeHidden()
|
|
await expect(page.locator('#delete-preset')).toBeHidden()
|
|
await selectPreset({ page, presetTitle: seededData.everyone.title })
|
|
await expect(page.locator('#edit-preset')).toBeVisible()
|
|
await expect(page.locator('#delete-preset')).toBeVisible()
|
|
})
|
|
|
|
test('should only show "reset" and "save" controls when there is an active preset and changes have been made', async () => {
|
|
await page.goto(pagesUrl.list)
|
|
|
|
await expect(page.locator('#reset-preset')).toBeHidden()
|
|
|
|
await expect(page.locator('#save-preset')).toBeHidden()
|
|
|
|
await selectPreset({ page, presetTitle: seededData.onlyMe.title })
|
|
|
|
await toggleColumn(page, { columnLabel: 'ID' })
|
|
|
|
await expect(page.locator('#reset-preset')).toBeVisible()
|
|
|
|
await expect(
|
|
page.locator('#save-preset', {
|
|
hasText: exactText('Save changes'),
|
|
}),
|
|
).toBeVisible()
|
|
})
|
|
|
|
test('should conditionally render "update for everyone" label based on if preset is shared', async () => {
|
|
await page.goto(pagesUrl.list)
|
|
|
|
await selectPreset({ page, presetTitle: seededData.onlyMe.title })
|
|
|
|
await toggleColumn(page, { columnLabel: 'ID' })
|
|
|
|
// When not shared, the label is "Save"
|
|
await expect(page.locator('#save-preset')).toBeVisible()
|
|
|
|
await expect(
|
|
page.locator('#save-preset', {
|
|
hasText: exactText('Save changes'),
|
|
}),
|
|
).toBeVisible()
|
|
|
|
await selectPreset({ page, presetTitle: seededData.everyone.title })
|
|
|
|
await toggleColumn(page, { columnLabel: 'ID' })
|
|
|
|
// When shared, the label is "Update for everyone"
|
|
await expect(
|
|
page.locator('#save-preset', {
|
|
hasText: exactText('Update for everyone'),
|
|
}),
|
|
).toBeVisible()
|
|
})
|
|
|
|
test('should reset active changes', async () => {
|
|
await page.goto(pagesUrl.list)
|
|
await selectPreset({ page, presetTitle: seededData.everyone.title })
|
|
|
|
const { columnContainer } = await toggleColumn(page, { columnLabel: 'ID' })
|
|
|
|
const column = columnContainer.locator(`.pill-selector .pill-selector__pill`, {
|
|
hasText: exactText('ID'),
|
|
})
|
|
|
|
await page.locator('#reset-preset').click()
|
|
|
|
await openListColumns(page, {})
|
|
await expect(column).toHaveClass(/pill-selector__pill--selected/)
|
|
})
|
|
|
|
test.skip('should only enter modified state when changes are made to an active preset', async () => {
|
|
await page.goto(pagesUrl.list)
|
|
await expect(page.locator('.list-controls__modified')).toBeHidden()
|
|
await selectPreset({ page, presetTitle: seededData.everyone.title })
|
|
await expect(page.locator('.list-controls__modified')).toBeHidden()
|
|
await toggleColumn(page, { columnLabel: 'ID' })
|
|
await expect(page.locator('.list-controls__modified')).toBeVisible()
|
|
|
|
await page.locator('#save-preset').click()
|
|
|
|
await expect(page.locator('.list-controls__modified')).toBeHidden()
|
|
await toggleColumn(page, { columnLabel: 'ID' })
|
|
await expect(page.locator('.list-controls__modified')).toBeVisible()
|
|
|
|
await page.locator('#reset-preset').click()
|
|
|
|
await expect(page.locator('.list-controls__modified')).toBeHidden()
|
|
})
|
|
|
|
test('can edit a preset through the document drawer', async () => {
|
|
const presetTitle = 'New Preset'
|
|
|
|
await page.goto(pagesUrl.list)
|
|
|
|
await selectPreset({ page, presetTitle: seededData.everyone.title })
|
|
await page.locator('#edit-preset').click()
|
|
|
|
const drawer = page.locator('[id^=doc-drawer_payload-query-presets_0_]')
|
|
const titleValue = drawer.locator('input[name="title"]')
|
|
await expect(titleValue).toHaveValue(seededData.everyone.title)
|
|
|
|
const newTitle = `${seededData.everyone.title} (Updated)`
|
|
await drawer.locator('input[name="title"]').fill(newTitle)
|
|
|
|
await saveDocAndAssert(page)
|
|
|
|
await drawer.locator('button.doc-drawer__header-close').click()
|
|
await expect(drawer).toBeHidden()
|
|
|
|
await expect(page.locator('button#select-preset')).toHaveText(newTitle)
|
|
})
|
|
|
|
test('should not display query presets when admin.enableQueryPresets is not true', async () => {
|
|
// go to users list view and ensure the query presets select is not visible
|
|
const usersURL = new AdminUrlUtil(serverURL, 'users')
|
|
await page.goto(usersURL.list)
|
|
await expect(page.locator('#select-preset')).toBeHidden()
|
|
})
|
|
|
|
// eslint-disable-next-line playwright/no-skipped-test, playwright/expect-expect
|
|
test.skip('can save a preset', () => {
|
|
// select a preset, make a change to the presets, click "save for everyone" or "save", and ensure the changes persist
|
|
})
|
|
|
|
test('can create new preset', async () => {
|
|
await page.goto(pagesUrl.list)
|
|
|
|
const presetTitle = 'New Preset'
|
|
|
|
await page.locator('#create-new-preset').click()
|
|
const modal = page.locator('[id^=doc-drawer_payload-query-presets_0_]')
|
|
await expect(modal).toBeVisible()
|
|
await modal.locator('input[name="title"]').fill(presetTitle)
|
|
|
|
const currentURL = page.url()
|
|
await saveDocAndAssert(page)
|
|
await expect(modal).toBeHidden()
|
|
|
|
await page.waitForURL(() => page.url() !== currentURL)
|
|
|
|
await expect(
|
|
page.locator('button#select-preset', {
|
|
hasText: exactText(presetTitle),
|
|
}),
|
|
).toBeVisible()
|
|
})
|
|
|
|
test('only shows query presets related to the underlying collection', async () => {
|
|
// no results on `posts` collection
|
|
const postsURL = new AdminUrlUtil(serverURL, 'posts')
|
|
await page.goto(postsURL.list)
|
|
const drawer = await openQueryPresetDrawer({ page })
|
|
await expect(drawer.locator('.table table > tbody > tr')).toHaveCount(0)
|
|
await expect(drawer.locator('.collection-list__no-results')).toBeVisible()
|
|
|
|
// results on `pages` collection
|
|
await page.goto(pagesUrl.list)
|
|
await openQueryPresetDrawer({ page })
|
|
await expect(drawer.locator('.table table > tbody > tr')).toHaveCount(3)
|
|
await drawer.locator('.collection-list__no-results').isHidden()
|
|
})
|
|
})
|