test: consolidates list view e2e tests (#10263)

There were a handful of list view e2e tests written into the text and
email field test suite, making them hard to find as they were isolated
from other related tests. A few of these tests were also duplicative
across suites, making CI run them twice unnecessarily.
This commit is contained in:
Jacob Fletcher
2024-12-30 16:09:42 -05:00
committed by GitHub
parent eb69885a89
commit 270ac10fb4
9 changed files with 474 additions and 665 deletions

View File

@@ -182,6 +182,20 @@ export const Posts: CollectionConfig = {
}, },
}, },
}, },
{
name: 'disableListColumnText',
type: 'text',
admin: {
disableListColumn: true,
},
},
{
name: 'disableListFilterText',
type: 'text',
admin: {
disableListFilter: true,
},
},
{ {
name: 'sidebarField', name: 'sidebarField',
type: 'text', type: 'text',

View File

@@ -119,7 +119,37 @@ describe('List View', () => {
}) })
}) })
describe('filtering', () => { describe('list view table', () => {
test('should link second cell', async () => {
const { id } = await createPost()
await page.reload()
const linkCell = page.locator(`${tableRowLocator} td`).nth(1).locator('a')
await expect(linkCell).toHaveAttribute(
'href',
`${adminRoutes.routes.admin}/collections/posts/${id}`,
)
await page.locator('.list-controls__toggle-columns').click()
await expect(page.locator('.list-controls__columns.rah-static--height-auto')).toBeVisible()
await page
.locator('.column-selector .column-selector__column', {
hasText: exactText('ID'),
})
.click()
await page.locator('#heading-id').waitFor({ state: 'detached' })
await page.locator('.cell-id').first().waitFor({ state: 'detached' })
await expect(linkCell).toHaveAttribute(
'href',
`${adminRoutes.routes.admin}/collections/posts/${id}`,
)
})
})
describe('search', () => {
test('should prefill search input from query param', async () => { test('should prefill search input from query param', async () => {
await createPost({ title: 'dennis' }) await createPost({ title: 'dennis' })
await createPost({ title: 'charlie' }) await createPost({ title: 'charlie' })
@@ -191,51 +221,13 @@ describe('List View', () => {
await expect(page.locator('#search-filter-input')).toHaveValue('') await expect(page.locator('#search-filter-input')).toHaveValue('')
}) })
test('should toggle columns', async () => {
const columnCountLocator = 'table > thead > tr > th'
await createPost()
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')
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 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')
}) })
test('should link second cell', async () => { describe('filters', () => {
const { id } = await createPost() test('should respect base list filters', async () => {
await page.reload() await page.goto(baseListFiltersUrl.list)
const linkCell = page.locator(`${tableRowLocator} td`).nth(1).locator('a') await page.waitForURL((url) => url.toString().startsWith(baseListFiltersUrl.list))
await expect(page.locator(tableRowLocator)).toHaveCount(1)
await expect(linkCell).toHaveAttribute(
'href',
`${adminRoutes.routes.admin}/collections/posts/${id}`,
)
await page.locator('.list-controls__toggle-columns').click()
await expect(page.locator('.list-controls__columns.rah-static--height-auto')).toBeVisible()
await page
.locator('.column-selector .column-selector__column', {
hasText: exactText('ID'),
})
.click()
await page.locator('#heading-id').waitFor({ state: 'detached' })
await page.locator('.cell-id').first().waitFor({ state: 'detached' })
await expect(linkCell).toHaveAttribute(
'href',
`${adminRoutes.routes.admin}/collections/posts/${id}`,
)
}) })
test('should filter rows', async () => { test('should filter rows', async () => {
@@ -452,9 +444,228 @@ describe('List View', () => {
await page.waitForURL(new RegExp(`${postsUrl.list}\\?limit=5&page=1`)) await page.waitForURL(new RegExp(`${postsUrl.list}\\?limit=5&page=1`))
await expect(page.locator('.collection-list__page-info')).toHaveText('1-3 of 3') await expect(page.locator('.collection-list__page-info')).toHaveText('1-3 of 3')
}) })
test('should reset filter values for every additional filters', async () => {
await page.goto(postsUrl.list)
await openListFilters(page, {})
await page.locator('.where-builder__add-first-filter').click()
const firstConditionField = page.locator('.condition__field')
const firstOperatorField = page.locator('.condition__operator')
const firstValueField = page.locator('.condition__value >> input')
await firstConditionField.click()
await firstConditionField
.locator('.rs__option', {
hasText: exactText('Tab 1 > Title'),
})
.click()
await expect(firstConditionField.locator('.rs__single-value')).toContainText('Tab 1 > Title')
await firstOperatorField.click()
await firstOperatorField.locator('.rs__option').locator('text=equals').click()
await firstValueField.fill('Test')
await expect(firstValueField).toHaveValue('Test')
await page.locator('.condition__actions-add').click()
const secondLi = page.locator('.where-builder__and-filters li:nth-child(2)')
await expect(secondLi).toBeVisible()
await expect(
secondLi.locator('.condition__field').locator('.rs__single-value'),
).toContainText('Tab 1 > Title')
await expect(secondLi.locator('.condition__operator >> input')).toHaveValue('')
await expect(secondLi.locator('.condition__value >> input')).toHaveValue('')
})
test('should not re-render page upon typing in a value in the filter value field', async () => {
await page.goto(postsUrl.list)
await openListFilters(page, {})
await page.locator('.where-builder__add-first-filter').click()
const firstConditionField = page.locator('.condition__field')
const firstOperatorField = page.locator('.condition__operator')
const firstValueField = page.locator('.condition__value >> input')
await firstConditionField.click()
await firstConditionField
.locator('.rs__option', { hasText: exactText('Tab 1 > Title') })
.click()
await expect(firstConditionField.locator('.rs__single-value')).toContainText('Tab 1 > Title')
await firstOperatorField.click()
await firstOperatorField.locator('.rs__option').locator('text=equals').click()
// Type into the input field instead of filling it
await firstValueField.click()
await firstValueField.type('Test', { delay: 100 }) // Add a delay to simulate typing speed
// Wait for a short period to see if the input loses focus
await page.waitForTimeout(500)
// Check if the input still has the correct value
await expect(firstValueField).toHaveValue('Test')
})
test('should still show second filter if two filters exist and first filter is removed', async () => {
await page.goto(postsUrl.list)
await openListFilters(page, {})
await page.locator('.where-builder__add-first-filter').click()
const firstConditionField = page.locator('.condition__field')
const firstOperatorField = page.locator('.condition__operator')
const firstValueField = page.locator('.condition__value >> input')
await firstConditionField.click()
await firstConditionField
.locator('.rs__option', { hasText: exactText('Tab 1 > Title') })
.click()
await expect(firstConditionField.locator('.rs__single-value')).toContainText('Tab 1 > Title')
await firstOperatorField.click()
await firstOperatorField.locator('.rs__option').locator('text=equals').click()
await firstValueField.fill('Test 1')
await expect(firstValueField).toHaveValue('Test 1')
await wait(500)
await page.locator('.condition__actions-add').click()
const secondLi = page.locator('.where-builder__and-filters li:nth-child(2)')
await expect(secondLi).toBeVisible()
const secondConditionField = secondLi.locator('.condition__field')
const secondOperatorField = secondLi.locator('.condition__operator')
const secondValueField = secondLi.locator('.condition__value >> input')
await secondConditionField.click()
await secondConditionField
.locator('.rs__option', { hasText: exactText('Tab 1 > Title') })
.click()
await expect(secondConditionField.locator('.rs__single-value')).toContainText('Tab 1 > Title')
await secondOperatorField.click()
await secondOperatorField.locator('.rs__option').locator('text=equals').click()
await secondValueField.fill('Test 2')
await expect(secondValueField).toHaveValue('Test 2')
const firstLi = page.locator('.where-builder__and-filters li:nth-child(1)')
const removeButton = firstLi.locator('.condition__actions-remove')
await wait(500)
// remove first filter
await removeButton.click()
const filterListItems = page.locator('.where-builder__and-filters li')
await expect(filterListItems).toHaveCount(1)
await expect(firstValueField).toHaveValue('Test 2')
})
test('should hide field filter when admin.disableListFilter is true', async () => {
await page.goto(postsUrl.list)
await openListFilters(page, {})
await page.locator('.where-builder__add-first-filter').click()
const initialField = page.locator('.condition__field')
await initialField.click()
await expect(
initialField.locator(`.rs__option :has-text("Disable List Filter Text")`),
).toBeHidden()
})
test('should simply disable field filter when admin.disableListFilter is true but still exists in the query', async () => {
await page.goto(
`${postsUrl.list}${qs.stringify(
{
where: {
or: [
{
and: [
{
disableListFilterText: {
equals: 'Disable List Filter Text',
},
},
],
},
],
},
},
{ addQueryPrefix: true },
)}`,
)
await openListFilters(page, {})
const condition = page.locator('.condition__field')
await expect(condition.locator('input.rs__input')).toBeDisabled()
await expect(page.locator('.condition__operator input.rs__input')).toBeDisabled()
await expect(page.locator('.condition__value input.condition-value-text')).toBeDisabled()
await expect(condition.locator('.rs__single-value')).toHaveText('Disable List Filter Text')
await page.locator('button.condition__actions-add').click()
const condition2 = page.locator('.condition__field').nth(1)
await condition2.click()
await expect(
condition2?.locator('.rs__menu-list:has-text("Disable List Filter Text")'),
).toBeHidden()
})
}) })
describe('table columns', () => { describe('table columns', () => {
test('should hide field in column selector when admin.disableListColumn is true', async () => {
await page.goto(postsUrl.list)
await page.locator('.list-controls__toggle-columns').click()
await expect(page.locator('.column-selector')).toBeVisible()
// Check if "Disable List Column Text" is not present in the column options
await expect(
page.locator(`.column-selector .column-selector__column`, {
hasText: exactText('Disable List Column Text'),
}),
).toBeHidden()
})
test('should display field in column selector despite admin.disableListFilter', async () => {
await page.goto(postsUrl.list)
await page.locator('.list-controls__toggle-columns').click()
await expect(page.locator('.column-selector')).toBeVisible()
// Check if "Disable List Filter Text" is present in the column options
await expect(
page.locator(`.column-selector .column-selector__column`, {
hasText: exactText('Disable List Filter Text'),
}),
).toBeVisible()
})
test('should still show field in filter when admin.disableListColumn is true', async () => {
await page.goto(postsUrl.list)
await openListFilters(page, {})
await page.locator('.where-builder__add-first-filter').click()
const initialField = page.locator('.condition__field')
await initialField.click()
await expect(
initialField.locator(`.rs__menu-list:has-text("Disable List Column Text")`),
).toBeVisible()
})
test('should toggle columns', async () => {
const columnCountLocator = 'table > thead > tr > th'
await createPost()
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')
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 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')
})
test('should drag to reorder columns and save to preferences', async () => { test('should drag to reorder columns and save to preferences', async () => {
await createPost() await createPost()
@@ -813,14 +1024,6 @@ describe('List View', () => {
).toHaveText('Title') ).toHaveText('Title')
}) })
}) })
describe('base list filters', () => {
test('should respect base list filters', async () => {
await page.goto(baseListFiltersUrl.list)
await page.waitForURL((url) => url.toString().startsWith(baseListFiltersUrl.list))
await expect(page.locator(tableRowLocator)).toHaveCount(1)
})
})
}) })
async function createPost(overrides?: Partial<Post>): Promise<Post> { async function createPost(overrides?: Partial<Post>): Promise<Post> {

View File

@@ -165,6 +165,8 @@ export interface Post {
defaultValueField?: string | null; defaultValueField?: string | null;
relationship?: (string | null) | Post; relationship?: (string | null) | Post;
customCell?: string | null; customCell?: string | null;
disableListColumnText?: string | null;
disableListFilterText?: string | null;
sidebarField?: string | null; sidebarField?: string | null;
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
@@ -520,6 +522,8 @@ export interface PostsSelect<T extends boolean = true> {
defaultValueField?: T; defaultValueField?: T;
relationship?: T; relationship?: T;
customCell?: T; customCell?: T;
disableListColumnText?: T;
disableListFilterText?: T;
sidebarField?: T; sidebarField?: T;
updatedAt?: T; updatedAt?: T;
createdAt?: T; createdAt?: T;

View File

@@ -9,19 +9,14 @@ import { fileURLToPath } from 'url'
import type { PayloadTestSDK } from '../../../helpers/sdk/index.js' import type { PayloadTestSDK } from '../../../helpers/sdk/index.js'
import type { Config } from '../../payload-types.js' import type { Config } from '../../payload-types.js'
import { import { ensureCompilationIsDone, initPageConsoleErrorCatch } from '../../../helpers.js'
ensureCompilationIsDone,
exactText,
initPageConsoleErrorCatch,
saveDocAndAssert,
} from '../../../helpers.js'
import { AdminUrlUtil } from '../../../helpers/adminUrlUtil.js' import { AdminUrlUtil } from '../../../helpers/adminUrlUtil.js'
import { initPayloadE2ENoConfig } from '../../../helpers/initPayloadE2ENoConfig.js' import { initPayloadE2ENoConfig } from '../../../helpers/initPayloadE2ENoConfig.js'
import { reInitializeDB } from '../../../helpers/reInitializeDB.js' import { reInitializeDB } from '../../../helpers/reInitializeDB.js'
import { RESTClient } from '../../../helpers/rest.js' import { RESTClient } from '../../../helpers/rest.js'
import { TEST_TIMEOUT_LONG } from '../../../playwright.config.js' import { TEST_TIMEOUT_LONG } from '../../../playwright.config.js'
import { emailFieldsSlug } from '../../slugs.js' import { emailFieldsSlug } from '../../slugs.js'
import { anotherEmailDoc, emailDoc } from './shared.js' import { emailDoc } from './shared.js'
const filename = fileURLToPath(import.meta.url) const filename = fileURLToPath(import.meta.url)
const currentFolder = path.dirname(filename) const currentFolder = path.dirname(filename)
@@ -74,60 +69,6 @@ describe('Email', () => {
await expect(emailCell).toHaveText(emailDoc.email) await expect(emailCell).toHaveText(emailDoc.email)
}) })
test('should hide field in column selector when admin.disableListColumn', async () => {
await page.goto(url.list)
await page.locator('.list-controls__toggle-columns').click()
await expect(page.locator('.column-selector')).toBeVisible()
// Check if "Disable List Column Text" is not present in the column options
await expect(
page.locator(`.column-selector .column-selector__column`, {
hasText: exactText('Disable List Column Text'),
}),
).toBeHidden()
})
test('should show field in filter when admin.disableListColumn is true', async () => {
await page.goto(url.list)
await openListFilters(page, {})
await page.locator('.where-builder__add-first-filter').click()
const initialField = page.locator('.condition__field')
await initialField.click()
await expect(
initialField.locator(`.rs__menu-list:has-text("Disable List Column Text")`),
).toBeVisible()
})
test('should display field in list view column selector despite admin.disableListFilter', async () => {
await page.goto(url.list)
await page.locator('.list-controls__toggle-columns').click()
await expect(page.locator('.column-selector')).toBeVisible()
// Check if "Disable List Filter Text" is present in the column options
await expect(
page.locator(`.column-selector .column-selector__column`, {
hasText: exactText('Disable List Filter Text'),
}),
).toBeVisible()
})
test('should hide field in filter when admin.disableListFilter is true', async () => {
await page.goto(url.list)
await openListFilters(page, {})
await page.locator('.where-builder__add-first-filter').click()
const initialField = page.locator('.condition__field')
await initialField.click()
await expect(
initialField.locator(`.rs__option :has-text("Disable List Filter Text")`),
).toBeHidden()
})
test('should have autocomplete', async () => { test('should have autocomplete', async () => {
await page.goto(url.create) await page.goto(url.create)
const autoCompleteEmail = page.locator('#field-emailWithAutocomplete') const autoCompleteEmail = page.locator('#field-emailWithAutocomplete')

View File

@@ -3,12 +3,9 @@ import type { GeneratedTypes } from 'helpers/sdk/types.js'
import { expect, test } from '@playwright/test' import { expect, test } from '@playwright/test'
import { openListColumns } from 'helpers/e2e/openListColumns.js' import { openListColumns } from 'helpers/e2e/openListColumns.js'
import { openListFilters } from 'helpers/e2e/openListFilters.js'
import { toggleColumn } from 'helpers/e2e/toggleColumn.js' import { toggleColumn } from 'helpers/e2e/toggleColumn.js'
import { upsertPrefs } from 'helpers/e2e/upsertPrefs.js' import { upsertPrefs } from 'helpers/e2e/upsertPrefs.js'
import path from 'path' import path from 'path'
import { wait } from 'payload/shared'
import * as qs from 'qs-esm'
import { fileURLToPath } from 'url' import { fileURLToPath } from 'url'
import type { PayloadTestSDK } from '../../../helpers/sdk/index.js' import type { PayloadTestSDK } from '../../../helpers/sdk/index.js'
@@ -167,84 +164,6 @@ describe('Text', () => {
await expect(textCell).toHaveText(textDoc.text) await expect(textCell).toHaveText(textDoc.text)
}) })
test('should hide field in column selector when admin.disableListColumn', async () => {
await page.goto(url.list)
await page.locator('.list-controls__toggle-columns').click()
await expect(page.locator('.column-selector')).toBeVisible()
// Check if "Disable List Column Text" is not present in the column options
await expect(
page.locator(`.column-selector .column-selector__column`, {
hasText: exactText('Disable List Column Text'),
}),
).toBeHidden()
})
test('should show field in filter when admin.disableListColumn is true', async () => {
await page.goto(url.list)
await openListFilters(page, {})
await page.locator('.where-builder__add-first-filter').click()
const initialField = page.locator('.condition__field')
await initialField.click()
await expect(
initialField.locator(`.rs__menu-list:has-text("Disable List Column Text")`),
).toBeVisible()
})
test('should display field in list view column selector despite admin.disableListFilter', async () => {
await page.goto(url.list)
await page.locator('.list-controls__toggle-columns').click()
await expect(page.locator('.column-selector')).toBeVisible()
// Check if "Disable List Filter Text" is present in the column options
await expect(
page.locator(`.column-selector .column-selector__column`, {
hasText: exactText('Disable List Filter Text'),
}),
).toBeVisible()
})
test('should disable field when admin.disableListFilter is true but still exists in the query', async () => {
await page.goto(
`${url.list}${qs.stringify(
{
where: {
or: [
{
and: [
{
disableListFilterText: {
equals: 'Disable List Filter Text',
},
},
],
},
],
},
},
{ addQueryPrefix: true },
)}`,
)
await openListFilters(page, {})
const condition = page.locator('.condition__field')
await expect(condition.locator('input.rs__input')).toBeDisabled()
await expect(page.locator('.condition__operator input.rs__input')).toBeDisabled()
await expect(page.locator('.condition__value input.condition-value-text')).toBeDisabled()
await expect(condition.locator('.rs__single-value')).toHaveText('Disable List Filter Text')
await page.locator('button.condition__actions-add').click()
const condition2 = page.locator('.condition__field').nth(1)
await condition2.click()
await expect(
condition2?.locator('.rs__menu-list:has-text("Disable List Filter Text")'),
).toBeHidden()
})
test('should respect admin.disableListColumn despite preferences', async () => { test('should respect admin.disableListColumn despite preferences', async () => {
await upsertPrefs<Config, GeneratedTypes<any>>({ await upsertPrefs<Config, GeneratedTypes<any>>({
payload, payload,
@@ -271,19 +190,6 @@ describe('Text', () => {
await expect(page.locator('table .row-1 .cell-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 openListFilters(page, {})
await page.locator('.where-builder__add-first-filter').click()
const initialField = page.locator('.condition__field')
await initialField.click()
await expect(
initialField.locator(`.rs__option :has-text("Disable List Filter Text")`),
).toBeHidden()
})
test('should display i18n label in cells when missing field data', async () => { test('should display i18n label in cells when missing field data', async () => {
await page.goto(url.list) await page.goto(url.list)
await page.waitForURL(new RegExp(`${url.list}.*\\?.*`)) await page.waitForURL(new RegExp(`${url.list}.*\\?.*`))
@@ -333,136 +239,4 @@ describe('Text', () => {
await expect(field.locator('.rs__value-container')).toContainText(input) await expect(field.locator('.rs__value-container')).toContainText(input)
await expect(field.locator('.rs__value-container')).toContainText(furtherInput) await expect(field.locator('.rs__value-container')).toContainText(furtherInput)
}) })
test('should reset filter conditions when adding additional filters', async () => {
await page.goto(url.list)
// open the first filter options
await openListFilters(page, {})
await page.locator('.where-builder__add-first-filter').click()
const firstInitialField = page.locator('.condition__field')
const firstOperatorField = page.locator('.condition__operator')
const firstValueField = page.locator('.condition__value >> input')
await firstInitialField.click()
const firstInitialFieldOptions = firstInitialField.locator('.rs__option')
await firstInitialFieldOptions.locator('text=text').first().click()
await expect(firstInitialField.locator('.rs__single-value')).toContainText('Text')
await firstOperatorField.click()
await firstOperatorField.locator('.rs__option').locator('text=equals').click()
await firstValueField.fill('hello')
await wait(500)
await expect(firstValueField).toHaveValue('hello')
// open the second filter options
await page.locator('.condition__actions-add').click()
const secondLi = page.locator('.where-builder__and-filters li:nth-child(2)')
await expect(secondLi).toBeVisible()
const secondInitialField = secondLi.locator('.condition__field')
const secondOperatorField = secondLi.locator('.condition__operator >> input')
const secondValueField = secondLi.locator('.condition__value >> input')
await expect(secondInitialField.locator('.rs__single-value')).toContainText('Text')
await expect(secondOperatorField).toHaveValue('')
await expect(secondValueField).toHaveValue('')
})
test('should not re-render page upon typing in a value in the filter value field', async () => {
await page.goto(url.list)
// open the first filter options
await openListFilters(page, {})
await page.locator('.where-builder__add-first-filter').click()
const firstInitialField = page.locator('.condition__field')
const firstOperatorField = page.locator('.condition__operator')
const firstValueField = page.locator('.condition__value >> input')
await firstInitialField.click()
const firstInitialFieldOptions = firstInitialField.locator('.rs__option')
await firstInitialFieldOptions.locator('text=text').first().click()
await expect(firstInitialField.locator('.rs__single-value')).toContainText('Text')
await firstOperatorField.click()
await firstOperatorField.locator('.rs__option').locator('text=equals').click()
// Type into the input field instead of filling it
await firstValueField.click()
await firstValueField.type('hello', { delay: 100 }) // Add a delay to simulate typing speed
// Wait for a short period to see if the input loses focus
await page.waitForTimeout(500)
// Check if the input still has the correct value
await expect(firstValueField).toHaveValue('hello')
})
test('should still show second filter if two filters exist and first filter is removed', async () => {
await page.goto(url.list)
// open the first filter options
await openListFilters(page, {})
await page.locator('.where-builder__add-first-filter').click()
const firstInitialField = page.locator('.condition__field')
const firstOperatorField = page.locator('.condition__operator')
const firstValueField = page.locator('.condition__value >> input')
await firstInitialField.click()
const firstInitialFieldOptions = firstInitialField.locator('.rs__option')
await firstInitialFieldOptions.locator('text=text').first().click()
await expect(firstInitialField.locator('.rs__single-value')).toContainText('Text')
await firstOperatorField.click()
await firstOperatorField.locator('.rs__option').locator('text=equals').click()
await firstValueField.fill('hello')
await wait(500)
await expect(firstValueField).toHaveValue('hello')
// open the second filter options
await page.locator('.condition__actions-add').click()
const secondLi = page.locator('.where-builder__and-filters li:nth-child(2)')
await expect(secondLi).toBeVisible()
const secondInitialField = secondLi.locator('.condition__field')
const secondOperatorField = secondLi.locator('.condition__operator')
const secondValueField = secondLi.locator('.condition__value >> input')
await secondInitialField.click()
const secondInitialFieldOptions = secondInitialField.locator('.rs__option')
await secondInitialFieldOptions.locator('text=text').first().click()
await expect(secondInitialField.locator('.rs__single-value')).toContainText('Text')
await secondOperatorField.click()
await secondOperatorField.locator('.rs__option').locator('text=equals').click()
await secondValueField.fill('world')
await expect(secondValueField).toHaveValue('world')
await wait(500)
const firstLi = page.locator('.where-builder__and-filters li:nth-child(1)')
const removeButton = firstLi.locator('.condition__actions-remove')
// remove first filter
await removeButton.click()
const filterListItems = page.locator('.where-builder__and-filters li')
await expect(filterListItems).toHaveCount(1)
await expect(firstValueField).toHaveValue('world')
})
}) })

View File

@@ -158,20 +158,6 @@ const TextFields: CollectionConfig = {
return Promise.resolve(req.context.defaultValue) return Promise.resolve(req.context.defaultValue)
}, },
}, },
{
name: 'disableListColumnText',
type: 'text',
admin: {
disableListColumn: true,
},
},
{
name: 'disableListFilterText',
type: 'text',
admin: {
disableListFilter: true,
},
},
{ {
name: 'array', name: 'array',
type: 'array', type: 'array',

View File

@@ -310,6 +310,9 @@ export interface LexicalMigrateField {
export interface LexicalLocalizedField { export interface LexicalLocalizedField {
id: string; id: string;
title: string; title: string;
/**
* Non-localized field with localized block subfields
*/
lexicalBlocksSubLocalized?: { lexicalBlocksSubLocalized?: {
root: { root: {
type: string; type: string;
@@ -325,6 +328,9 @@ export interface LexicalLocalizedField {
}; };
[k: string]: unknown; [k: string]: unknown;
} | null; } | null;
/**
* Localized field with localized block subfields
*/
lexicalBlocksLocalized?: { lexicalBlocksLocalized?: {
root: { root: {
type: string; type: string;
@@ -475,6 +481,9 @@ export interface ArrayField {
id?: string | null; id?: string | null;
}[] }[]
| null; | null;
/**
* Row labels rendered as react components.
*/
rowLabelAsComponent?: rowLabelAsComponent?:
| { | {
title?: string | null; title?: string | null;
@@ -589,6 +598,9 @@ export interface BlockField {
} }
)[] )[]
| null; | null;
/**
* The purpose of this field is to test validateExistingBlockIsIdentical works with similar blocks with group fields
*/
blocksWithSimilarGroup?: blocksWithSimilarGroup?:
| ( | (
| { | {
@@ -777,9 +789,18 @@ export interface TextField {
id: string; id: string;
text: string; text: string;
hiddenTextField?: string | null; hiddenTextField?: string | null;
/**
* This field should be hidden
*/
adminHiddenTextField?: string | null; adminHiddenTextField?: string | null;
/**
* This field should be disabled
*/
disabledTextField?: string | null; disabledTextField?: string | null;
localizedText?: string | null; localizedText?: string | null;
/**
* en description
*/
i18nText?: string | null; i18nText?: string | null;
defaultString?: string | null; defaultString?: string | null;
defaultEmptyString?: string | null; defaultEmptyString?: string | null;
@@ -794,8 +815,6 @@ export interface TextField {
withMinRows?: string[] | null; withMinRows?: string[] | null;
withMaxRows?: string[] | null; withMaxRows?: string[] | null;
defaultValueFromReq?: string | null; defaultValueFromReq?: string | null;
disableListColumnText?: string | null;
disableListFilterText?: string | null;
array?: array?:
| { | {
texts?: string[] | null; texts?: string[] | null;
@@ -900,8 +919,14 @@ export interface ConditionalLogic {
userConditional?: string | null; userConditional?: string | null;
parentGroup?: { parentGroup?: {
enableParentGroupFields?: boolean | null; enableParentGroupFields?: boolean | null;
/**
* Ensures we can rely on nested fields within `data`.
*/
siblingField?: string | null; siblingField?: string | null;
}; };
/**
* Ensures we can rely on nested fields within `siblingsData`.
*/
reliesOnParentGroup?: string | null; reliesOnParentGroup?: string | null;
groupSelection?: ('group1' | 'group2') | null; groupSelection?: ('group1' | 'group2') | null;
group1?: { group1?: {
@@ -981,6 +1006,9 @@ export interface EmailField {
email: string; email: string;
localizedEmail?: string | null; localizedEmail?: string | null;
emailWithAutocomplete?: string | null; emailWithAutocomplete?: string | null;
/**
* en description
*/
i18nEmail?: string | null; i18nEmail?: string | null;
defaultEmail?: string | null; defaultEmail?: string | null;
defaultEmptyString?: string | null; defaultEmptyString?: string | null;
@@ -1010,6 +1038,9 @@ export interface RadioField {
*/ */
export interface GroupField { export interface GroupField {
id: string; id: string;
/**
* This is a group.
*/
group: { group: {
text: string; text: string;
defaultParent?: string | null; defaultParent?: string | null;
@@ -1402,6 +1433,9 @@ export interface RichTextField {
[k: string]: unknown; [k: string]: unknown;
}; };
lexicalCustomFields_html?: string | null; lexicalCustomFields_html?: string | null;
/**
* This rich text field uses the lexical editor.
*/
lexical?: { lexical?: {
root: { root: {
type: string; type: string;
@@ -1417,6 +1451,9 @@ export interface RichTextField {
}; };
[k: string]: unknown; [k: string]: unknown;
} | null; } | null;
/**
* This select field is rendered here to ensure its options dropdown renders above the rich text toolbar.
*/
selectHasMany?: ('one' | 'two' | 'three' | 'four' | 'five' | 'six')[] | null; selectHasMany?: ('one' | 'two' | 'three' | 'four' | 'five' | 'six')[] | null;
richText: { richText: {
[k: string]: unknown; [k: string]: unknown;
@@ -1505,6 +1542,9 @@ export interface TabsFields2 {
*/ */
export interface TabsField { export interface TabsField {
id: string; id: string;
/**
* This should not collapse despite there being many tabs pushing the main fields open.
*/
sidebarField?: string | null; sidebarField?: string | null;
array: { array: {
text: string; text: string;
@@ -2115,257 +2155,42 @@ export interface BlockFieldsSelect<T extends boolean = true> {
blocks?: blocks?:
| T | T
| { | {
content?: content?: T | ContentBlockSelect<T>;
| T number?: T | NumberBlockSelect<T>;
| { subBlocks?: T | SubBlocksBlockSelect<T>;
text?: T; tabs?: T | TabsBlockSelect<T>;
richText?: T;
id?: T;
blockName?: T;
};
number?:
| T
| {
number?: T;
id?: T;
blockName?: T;
};
subBlocks?:
| T
| {
subBlocks?:
| T
| {
text?:
| T
| {
text?: T;
id?: T;
blockName?: T;
};
number?:
| T
| {
number?: T;
id?: T;
blockName?: T;
};
};
id?: T;
blockName?: T;
};
tabs?:
| T
| {
textInCollapsible?: T;
textInRow?: T;
id?: T;
blockName?: T;
};
}; };
duplicate?: duplicate?:
| T | T
| { | {
content?: content?: T | ContentBlockSelect<T>;
| T number?: T | NumberBlockSelect<T>;
| { subBlocks?: T | SubBlocksBlockSelect<T>;
text?: T; tabs?: T | TabsBlockSelect<T>;
richText?: T;
id?: T;
blockName?: T;
};
number?:
| T
| {
number?: T;
id?: T;
blockName?: T;
};
subBlocks?:
| T
| {
subBlocks?:
| T
| {
text?:
| T
| {
text?: T;
id?: T;
blockName?: T;
};
number?:
| T
| {
number?: T;
id?: T;
blockName?: T;
};
};
id?: T;
blockName?: T;
};
tabs?:
| T
| {
textInCollapsible?: T;
textInRow?: T;
id?: T;
blockName?: T;
};
}; };
collapsedByDefaultBlocks?: collapsedByDefaultBlocks?:
| T | T
| { | {
localizedContent?: localizedContent?: T | LocalizedContentBlockSelect<T>;
| T localizedNumber?: T | LocalizedNumberBlockSelect<T>;
| { localizedSubBlocks?: T | LocalizedSubBlocksBlockSelect<T>;
text?: T; localizedTabs?: T | LocalizedTabsBlockSelect<T>;
richText?: T;
id?: T;
blockName?: T;
};
localizedNumber?:
| T
| {
number?: T;
id?: T;
blockName?: T;
};
localizedSubBlocks?:
| T
| {
subBlocks?:
| T
| {
text?:
| T
| {
text?: T;
id?: T;
blockName?: T;
};
number?:
| T
| {
number?: T;
id?: T;
blockName?: T;
};
};
id?: T;
blockName?: T;
};
localizedTabs?:
| T
| {
textInCollapsible?: T;
textInRow?: T;
id?: T;
blockName?: T;
};
}; };
disableSort?: disableSort?:
| T | T
| { | {
localizedContent?: localizedContent?: T | LocalizedContentBlockSelect<T>;
| T localizedNumber?: T | LocalizedNumberBlockSelect<T>;
| { localizedSubBlocks?: T | LocalizedSubBlocksBlockSelect<T>;
text?: T; localizedTabs?: T | LocalizedTabsBlockSelect<T>;
richText?: T;
id?: T;
blockName?: T;
};
localizedNumber?:
| T
| {
number?: T;
id?: T;
blockName?: T;
};
localizedSubBlocks?:
| T
| {
subBlocks?:
| T
| {
text?:
| T
| {
text?: T;
id?: T;
blockName?: T;
};
number?:
| T
| {
number?: T;
id?: T;
blockName?: T;
};
};
id?: T;
blockName?: T;
};
localizedTabs?:
| T
| {
textInCollapsible?: T;
textInRow?: T;
id?: T;
blockName?: T;
};
}; };
localizedBlocks?: localizedBlocks?:
| T | T
| { | {
localizedContent?: localizedContent?: T | LocalizedContentBlockSelect<T>;
| T localizedNumber?: T | LocalizedNumberBlockSelect<T>;
| { localizedSubBlocks?: T | LocalizedSubBlocksBlockSelect<T>;
text?: T; localizedTabs?: T | LocalizedTabsBlockSelect<T>;
richText?: T;
id?: T;
blockName?: T;
};
localizedNumber?:
| T
| {
number?: T;
id?: T;
blockName?: T;
};
localizedSubBlocks?:
| T
| {
subBlocks?:
| T
| {
text?:
| T
| {
text?: T;
id?: T;
blockName?: T;
};
number?:
| T
| {
number?: T;
id?: T;
blockName?: T;
};
};
id?: T;
blockName?: T;
};
localizedTabs?:
| T
| {
textInCollapsible?: T;
textInRow?: T;
id?: T;
blockName?: T;
};
}; };
i18nBlocks?: i18nBlocks?:
| T | T
@@ -2503,6 +2328,116 @@ export interface BlockFieldsSelect<T extends boolean = true> {
updatedAt?: T; updatedAt?: T;
createdAt?: T; createdAt?: T;
} }
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "ContentBlock_select".
*/
export interface ContentBlockSelect<T extends boolean = true> {
text?: T;
richText?: T;
id?: T;
blockName?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "NumberBlock_select".
*/
export interface NumberBlockSelect<T extends boolean = true> {
number?: T;
id?: T;
blockName?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "SubBlocksBlock_select".
*/
export interface SubBlocksBlockSelect<T extends boolean = true> {
subBlocks?:
| T
| {
text?:
| T
| {
text?: T;
id?: T;
blockName?: T;
};
number?:
| T
| {
number?: T;
id?: T;
blockName?: T;
};
};
id?: T;
blockName?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "TabsBlock_select".
*/
export interface TabsBlockSelect<T extends boolean = true> {
textInCollapsible?: T;
textInRow?: T;
id?: T;
blockName?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "localizedContentBlock_select".
*/
export interface LocalizedContentBlockSelect<T extends boolean = true> {
text?: T;
richText?: T;
id?: T;
blockName?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "localizedNumberBlock_select".
*/
export interface LocalizedNumberBlockSelect<T extends boolean = true> {
number?: T;
id?: T;
blockName?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "localizedSubBlocksBlock_select".
*/
export interface LocalizedSubBlocksBlockSelect<T extends boolean = true> {
subBlocks?:
| T
| {
text?:
| T
| {
text?: T;
id?: T;
blockName?: T;
};
number?:
| T
| {
number?: T;
id?: T;
blockName?: T;
};
};
id?: T;
blockName?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "localizedTabsBlock_select".
*/
export interface LocalizedTabsBlockSelect<T extends boolean = true> {
textInCollapsible?: T;
textInRow?: T;
id?: T;
blockName?: T;
}
/** /**
* This interface was referenced by `Config`'s JSON-Schema * This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "checkbox-fields_select". * via the `definition` "checkbox-fields_select".
@@ -3085,53 +3020,10 @@ export interface TabsFieldsSelect<T extends boolean = true> {
blocks?: blocks?:
| T | T
| { | {
content?: content?: T | ContentBlockSelect<T>;
| T number?: T | NumberBlockSelect<T>;
| { subBlocks?: T | SubBlocksBlockSelect<T>;
text?: T; tabs?: T | TabsBlockSelect<T>;
richText?: T;
id?: T;
blockName?: T;
};
number?:
| T
| {
number?: T;
id?: T;
blockName?: T;
};
subBlocks?:
| T
| {
subBlocks?:
| T
| {
text?:
| T
| {
text?: T;
id?: T;
blockName?: T;
};
number?:
| T
| {
number?: T;
id?: T;
blockName?: T;
};
};
id?: T;
blockName?: T;
};
tabs?:
| T
| {
textInCollapsible?: T;
textInRow?: T;
id?: T;
blockName?: T;
};
}; };
group?: group?:
| T | T
@@ -3141,24 +3033,7 @@ export interface TabsFieldsSelect<T extends boolean = true> {
textInRow?: T; textInRow?: T;
numberInRow?: T; numberInRow?: T;
json?: T; json?: T;
tab?: tab?: T | TabWithNameSelect<T>;
| T
| {
array?:
| T
| {
text?: T;
id?: T;
};
text?: T;
defaultValue?: T;
arrayInRow?:
| T
| {
textInArrayInRow?: T;
id?: T;
};
};
namedTabWithDefaultValue?: namedTabWithDefaultValue?:
| T | T
| { | {
@@ -3208,6 +3083,26 @@ export interface TabsFieldsSelect<T extends boolean = true> {
updatedAt?: T; updatedAt?: T;
createdAt?: T; createdAt?: T;
} }
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "TabWithName_select".
*/
export interface TabWithNameSelect<T extends boolean = true> {
array?:
| T
| {
text?: T;
id?: T;
};
text?: T;
defaultValue?: T;
arrayInRow?:
| T
| {
textInArrayInRow?: T;
id?: T;
};
}
/** /**
* This interface was referenced by `Config`'s JSON-Schema * This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "text-fields_select". * via the `definition` "text-fields_select".
@@ -3232,8 +3127,6 @@ export interface TextFieldsSelect<T extends boolean = true> {
withMinRows?: T; withMinRows?: T;
withMaxRows?: T; withMaxRows?: T;
defaultValueFromReq?: T; defaultValueFromReq?: T;
disableListColumnText?: T;
disableListFilterText?: T;
array?: array?:
| T | T
| { | {

View File

@@ -126,9 +126,6 @@ export interface UserAuthOperations {
export interface Post { export interface Post {
id: string; id: string;
title?: string | null; title?: string | null;
/**
* Hides posts for the `filtered` join field in categories
*/
isFiltered?: boolean | null; isFiltered?: boolean | null;
restrictedField?: string | null; restrictedField?: string | null;
upload?: (string | null) | Upload; upload?: (string | null) | Upload;
@@ -231,9 +228,6 @@ export interface Category {
docs?: (string | Post)[] | null; docs?: (string | Post)[] | null;
hasNextPage?: boolean | null; hasNextPage?: boolean | null;
} | null; } | null;
/**
* Static Description
*/
hasManyPosts?: { hasManyPosts?: {
docs?: (string | Post)[] | null; docs?: (string | Post)[] | null;
hasNextPage?: boolean | null; hasNextPage?: boolean | null;

View File

@@ -28,7 +28,7 @@
} }
], ],
"paths": { "paths": {
"@payload-config": ["./test/_community/config.ts"], "@payload-config": ["./test/admin/config.ts"],
"@payloadcms/live-preview": ["./packages/live-preview/src"], "@payloadcms/live-preview": ["./packages/live-preview/src"],
"@payloadcms/live-preview-react": ["./packages/live-preview-react/src/index.ts"], "@payloadcms/live-preview-react": ["./packages/live-preview-react/src/index.ts"],
"@payloadcms/live-preview-vue": ["./packages/live-preview-vue/src/index.ts"], "@payloadcms/live-preview-vue": ["./packages/live-preview-vue/src/index.ts"],