fix(ui): block rows unexpectedly collapse and array rows not collapsed on init (#12987)
This commit is contained in:
@@ -28,7 +28,6 @@ export function fieldReducer(state: FormState, action: FieldAction): FormState {
|
|||||||
|
|
||||||
const newRow: Row = {
|
const newRow: Row = {
|
||||||
id: (subFieldState?.id?.value as string) || new ObjectId().toHexString(),
|
id: (subFieldState?.id?.value as string) || new ObjectId().toHexString(),
|
||||||
collapsed: false,
|
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import type {
|
import type {
|
||||||
|
ArrayField,
|
||||||
|
BlocksField,
|
||||||
BuildFormStateArgs,
|
BuildFormStateArgs,
|
||||||
ClientFieldSchemaMap,
|
ClientFieldSchemaMap,
|
||||||
|
CollapsedPreferences,
|
||||||
Data,
|
Data,
|
||||||
DocumentPreferences,
|
DocumentPreferences,
|
||||||
Field,
|
Field,
|
||||||
@@ -34,6 +37,7 @@ import {
|
|||||||
import type { RenderFieldMethod } from './types.js'
|
import type { RenderFieldMethod } from './types.js'
|
||||||
|
|
||||||
import { resolveFilterOptions } from '../../utilities/resolveFilterOptions.js'
|
import { resolveFilterOptions } from '../../utilities/resolveFilterOptions.js'
|
||||||
|
import { isRowCollapsed } from './isRowCollapsed.js'
|
||||||
import { iterateFields } from './iterateFields.js'
|
import { iterateFields } from './iterateFields.js'
|
||||||
|
|
||||||
const ObjectId = (ObjectIdImport.default ||
|
const ObjectId = (ObjectIdImport.default ||
|
||||||
@@ -334,10 +338,10 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom
|
|||||||
acc.rows = []
|
acc.rows = []
|
||||||
}
|
}
|
||||||
|
|
||||||
const previousRows = previousFormState?.[path]?.rows || []
|
|
||||||
|
|
||||||
// First, check if `previousFormState` has a matching row
|
// First, check if `previousFormState` has a matching row
|
||||||
const previousRow: Row = previousRows.find((prevRow) => prevRow.id === row.id)
|
const previousRow: Row = (previousFormState?.[path]?.rows || []).find(
|
||||||
|
(prevRow) => prevRow.id === row.id,
|
||||||
|
)
|
||||||
|
|
||||||
const newRow: Row = {
|
const newRow: Row = {
|
||||||
id: row.id,
|
id: row.id,
|
||||||
@@ -350,24 +354,15 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom
|
|||||||
|
|
||||||
acc.rows.push(newRow)
|
acc.rows.push(newRow)
|
||||||
|
|
||||||
const collapsedRowIDsFromPrefs = preferences?.fields?.[path]?.collapsed
|
const isCollapsed = isRowCollapsed({
|
||||||
|
collapsedPrefs: preferences?.fields?.[path]?.collapsed,
|
||||||
|
field,
|
||||||
|
previousRow,
|
||||||
|
row,
|
||||||
|
})
|
||||||
|
|
||||||
const collapsed = (() => {
|
if (isCollapsed) {
|
||||||
if (previousRow) {
|
acc.rows[acc.rows.length - 1].collapsed = true
|
||||||
return previousRow.collapsed ?? false
|
|
||||||
}
|
|
||||||
|
|
||||||
// If previousFormState is undefined, check preferences
|
|
||||||
if (collapsedRowIDsFromPrefs !== undefined) {
|
|
||||||
return collapsedRowIDsFromPrefs.includes(row.id) // Check if collapsed in preferences
|
|
||||||
}
|
|
||||||
|
|
||||||
// If neither exists, fallback to `field.admin.initCollapsed`
|
|
||||||
return field.admin.initCollapsed
|
|
||||||
})()
|
|
||||||
|
|
||||||
if (collapsed) {
|
|
||||||
acc.rows[acc.rows.length - 1].collapsed = collapsed
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return acc
|
return acc
|
||||||
@@ -524,10 +519,10 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
const previousRows = previousFormState?.[path]?.rows || []
|
|
||||||
|
|
||||||
// First, check if `previousFormState` has a matching row
|
// First, check if `previousFormState` has a matching row
|
||||||
const previousRow: Row = previousRows.find((prevRow) => prevRow.id === row.id)
|
const previousRow: Row = (previousFormState?.[path]?.rows || []).find(
|
||||||
|
(prevRow) => prevRow.id === row.id,
|
||||||
|
)
|
||||||
|
|
||||||
const newRow: Row = {
|
const newRow: Row = {
|
||||||
id: row.id,
|
id: row.id,
|
||||||
@@ -541,15 +536,15 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom
|
|||||||
|
|
||||||
acc.rowMetadata.push(newRow)
|
acc.rowMetadata.push(newRow)
|
||||||
|
|
||||||
const collapsedRowIDs = preferences?.fields?.[path]?.collapsed
|
const isCollapsed = isRowCollapsed({
|
||||||
|
collapsedPrefs: preferences?.fields?.[path]?.collapsed,
|
||||||
|
field,
|
||||||
|
previousRow,
|
||||||
|
row,
|
||||||
|
})
|
||||||
|
|
||||||
const collapsed =
|
if (isCollapsed) {
|
||||||
collapsedRowIDs === undefined
|
acc.rowMetadata[acc.rowMetadata.length - 1].collapsed = true
|
||||||
? field.admin.initCollapsed
|
|
||||||
: collapsedRowIDs.includes(row.id)
|
|
||||||
|
|
||||||
if (collapsed) {
|
|
||||||
acc.rowMetadata[acc.rowMetadata.length - 1].collapsed = collapsed
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import type { ArrayField, BlocksField, CollapsedPreferences, Row } from 'payload'
|
||||||
|
|
||||||
|
export function isRowCollapsed({
|
||||||
|
collapsedPrefs,
|
||||||
|
field,
|
||||||
|
previousRow,
|
||||||
|
row,
|
||||||
|
}: {
|
||||||
|
collapsedPrefs: CollapsedPreferences
|
||||||
|
field: ArrayField | BlocksField
|
||||||
|
previousRow: Row | undefined
|
||||||
|
row: Row
|
||||||
|
}): boolean {
|
||||||
|
if (previousRow && 'collapsed' in previousRow) {
|
||||||
|
return previousRow.collapsed ?? false
|
||||||
|
}
|
||||||
|
|
||||||
|
// If previousFormState is `undefined`, check preferences
|
||||||
|
if (collapsedPrefs !== undefined) {
|
||||||
|
return collapsedPrefs.includes(row.id) // Check if collapsed in preferences
|
||||||
|
}
|
||||||
|
|
||||||
|
// If neither exists, fallback to `field.admin.initCollapsed`
|
||||||
|
return field.admin.initCollapsed
|
||||||
|
}
|
||||||
@@ -2,7 +2,9 @@ import type { BrowserContext, Locator, Page } from '@playwright/test'
|
|||||||
import type { PayloadTestSDK } from 'helpers/sdk/index.js'
|
import type { PayloadTestSDK } from 'helpers/sdk/index.js'
|
||||||
|
|
||||||
import { expect, test } from '@playwright/test'
|
import { expect, test } from '@playwright/test'
|
||||||
|
import { toggleBlockOrArrayRow } from 'helpers/e2e/toggleCollapsible.js'
|
||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
|
import { wait } from 'payload/shared'
|
||||||
import { fileURLToPath } from 'url'
|
import { fileURLToPath } from 'url'
|
||||||
|
|
||||||
import type { Config, Post } from './payload-types.js'
|
import type { Config, Post } from './payload-types.js'
|
||||||
@@ -494,8 +496,23 @@ test.describe('Bulk Edit', () => {
|
|||||||
|
|
||||||
const { field } = await selectFieldToEdit(page, { fieldLabel: 'Array', fieldID: 'array' })
|
const { field } = await selectFieldToEdit(page, { fieldLabel: 'Array', fieldID: 'array' })
|
||||||
|
|
||||||
|
await wait(500)
|
||||||
|
|
||||||
await field.locator('button.array-field__add-row').click()
|
await field.locator('button.array-field__add-row').click()
|
||||||
|
|
||||||
|
const row = page.locator(`#array-row-0`)
|
||||||
|
const toggler = row.locator('button.collapsible__toggle')
|
||||||
|
|
||||||
|
await expect(toggler).toHaveClass(/collapsible__toggle--collapsed/)
|
||||||
|
await expect(page.locator(`#field-array__0__optional`)).toBeHidden()
|
||||||
|
|
||||||
|
await toggleBlockOrArrayRow({
|
||||||
|
page,
|
||||||
|
targetState: 'open',
|
||||||
|
rowIndex: 0,
|
||||||
|
fieldName: 'array',
|
||||||
|
})
|
||||||
|
|
||||||
await expect(field.locator('#field-array__0__optional')).toBeVisible()
|
await expect(field.locator('#field-array__0__optional')).toBeVisible()
|
||||||
await expect(field.locator('#field-array__0__noRead')).toBeHidden()
|
await expect(field.locator('#field-array__0__noRead')).toBeHidden()
|
||||||
await expect(field.locator('#field-array__0__noUpdate')).toBeDisabled()
|
await expect(field.locator('#field-array__0__noUpdate')).toBeDisabled()
|
||||||
|
|||||||
@@ -169,6 +169,13 @@ export interface User {
|
|||||||
hash?: string | null;
|
hash?: string | null;
|
||||||
loginAttempts?: number | null;
|
loginAttempts?: number | null;
|
||||||
lockUntil?: string | null;
|
lockUntil?: string | null;
|
||||||
|
sessions?:
|
||||||
|
| {
|
||||||
|
id: string;
|
||||||
|
createdAt?: string | null;
|
||||||
|
expiresAt: string;
|
||||||
|
}[]
|
||||||
|
| null;
|
||||||
password?: string | null;
|
password?: string | null;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@@ -287,6 +294,13 @@ export interface UsersSelect<T extends boolean = true> {
|
|||||||
hash?: T;
|
hash?: T;
|
||||||
loginAttempts?: T;
|
loginAttempts?: T;
|
||||||
lockUntil?: T;
|
lockUntil?: T;
|
||||||
|
sessions?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
id?: T;
|
||||||
|
createdAt?: T;
|
||||||
|
expiresAt?: T;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* This interface was referenced by `Config`'s JSON-Schema
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import type { Page } from '@playwright/test'
|
|||||||
|
|
||||||
import { expect, test } from '@playwright/test'
|
import { expect, test } from '@playwright/test'
|
||||||
import { assertToastErrors } from 'helpers/assertToastErrors.js'
|
import { assertToastErrors } from 'helpers/assertToastErrors.js'
|
||||||
|
import { toggleBlockOrArrayRow } from 'helpers/e2e/toggleCollapsible.js'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { wait } from 'payload/shared'
|
import { wait } from 'payload/shared'
|
||||||
import { fileURLToPath } from 'url'
|
import { fileURLToPath } from 'url'
|
||||||
@@ -384,12 +385,43 @@ describe('Array', () => {
|
|||||||
await expect(page.locator('#custom-text-field')).toBeVisible()
|
await expect(page.locator('#custom-text-field')).toBeVisible()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should not re-close initCollapsed true array rows on input in create new view', async () => {
|
test('should initialize array rows with collapsed state', async () => {
|
||||||
await loadCreatePage()
|
await page.goto(url.create)
|
||||||
|
|
||||||
await page.locator('#field-collapsedArray >> .array-field__add-row').click()
|
await page.locator('#field-collapsedArray >> .array-field__add-row').click()
|
||||||
await page.locator('#field-collapsedArray__0__text').fill('test')
|
|
||||||
const collapsedArrayRow = page.locator('#collapsedArray-row-0 .collapsible--collapsed')
|
const row = page.locator(`#collapsedArray-row-0`)
|
||||||
await expect(collapsedArrayRow).toBeHidden()
|
const toggler = row.locator('button.collapsible__toggle')
|
||||||
|
|
||||||
|
await expect(toggler).toHaveClass(/collapsible__toggle--collapsed/)
|
||||||
|
await expect(page.locator(`#field-collapsedArray__0__text`)).toBeHidden()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should not collapse array rows on input change', async () => {
|
||||||
|
await page.goto(url.create)
|
||||||
|
|
||||||
|
await page.locator('#field-collapsedArray >> .array-field__add-row').click()
|
||||||
|
|
||||||
|
const row = page.locator(`#collapsedArray-row-0`)
|
||||||
|
const toggler = row.locator('button.collapsible__toggle')
|
||||||
|
|
||||||
|
await expect(toggler).toHaveClass(/collapsible__toggle--collapsed/)
|
||||||
|
await expect(page.locator(`#field-collapsedArray__0__text`)).toBeHidden()
|
||||||
|
|
||||||
|
await toggleBlockOrArrayRow({
|
||||||
|
page,
|
||||||
|
rowIndex: 0,
|
||||||
|
fieldName: 'collapsedArray',
|
||||||
|
targetState: 'open',
|
||||||
|
})
|
||||||
|
|
||||||
|
await page.locator('input#field-collapsedArray__0__text').fill('Hello, world!')
|
||||||
|
|
||||||
|
// wait for form state to return, in the future can wire this into watch network requests (if needed)
|
||||||
|
await wait(1000)
|
||||||
|
|
||||||
|
await expect(toggler).toHaveClass(/collapsible__toggle--open/)
|
||||||
|
await expect(page.locator(`#field-collapsedArray__0__text`)).toBeVisible()
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('sortable arrays', () => {
|
describe('sortable arrays', () => {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { addBlock } from 'helpers/e2e/addBlock.js'
|
|||||||
import { openBlocksDrawer } from 'helpers/e2e/openBlocksDrawer.js'
|
import { openBlocksDrawer } from 'helpers/e2e/openBlocksDrawer.js'
|
||||||
import { reorderBlocks } from 'helpers/e2e/reorderBlocks.js'
|
import { reorderBlocks } from 'helpers/e2e/reorderBlocks.js'
|
||||||
import { scrollEntirePage } from 'helpers/e2e/scrollEntirePage.js'
|
import { scrollEntirePage } from 'helpers/e2e/scrollEntirePage.js'
|
||||||
|
import { toggleBlockOrArrayRow } from 'helpers/e2e/toggleCollapsible.js'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { wait } from 'payload/shared'
|
import { wait } from 'payload/shared'
|
||||||
import { fileURLToPath } from 'url'
|
import { fileURLToPath } from 'url'
|
||||||
@@ -13,7 +14,7 @@ import {
|
|||||||
ensureCompilationIsDone,
|
ensureCompilationIsDone,
|
||||||
initPageConsoleErrorCatch,
|
initPageConsoleErrorCatch,
|
||||||
saveDocAndAssert,
|
saveDocAndAssert,
|
||||||
throttleTest,
|
// throttleTest,
|
||||||
} from '../../../helpers.js'
|
} from '../../../helpers.js'
|
||||||
import { AdminUrlUtil } from '../../../helpers/adminUrlUtil.js'
|
import { AdminUrlUtil } from '../../../helpers/adminUrlUtil.js'
|
||||||
import { assertToastErrors } from '../../../helpers/assertToastErrors.js'
|
import { assertToastErrors } from '../../../helpers/assertToastErrors.js'
|
||||||
@@ -84,7 +85,7 @@ describe('Block fields', () => {
|
|||||||
await addBlock({
|
await addBlock({
|
||||||
page,
|
page,
|
||||||
fieldName: 'blocks',
|
fieldName: 'blocks',
|
||||||
blockLabel: 'Content',
|
blockToSelect: 'Content',
|
||||||
})
|
})
|
||||||
|
|
||||||
// ensure the block was appended to the rows
|
// ensure the block was appended to the rows
|
||||||
@@ -186,6 +187,53 @@ describe('Block fields', () => {
|
|||||||
await expect(page.locator('.payload-toast-container')).toContainText('successfully')
|
await expect(page.locator('.payload-toast-container')).toContainText('successfully')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('should initialize block rows with collapsed state', async () => {
|
||||||
|
await page.goto(url.create)
|
||||||
|
|
||||||
|
await addBlock({
|
||||||
|
page,
|
||||||
|
fieldName: 'collapsedByDefaultBlocks',
|
||||||
|
blockToSelect: 'Localized Content',
|
||||||
|
})
|
||||||
|
|
||||||
|
const row = page.locator(`#collapsedByDefaultBlocks-row-4`)
|
||||||
|
const toggler = row.locator('button.collapsible__toggle')
|
||||||
|
|
||||||
|
await expect(toggler).toHaveClass(/collapsible__toggle--collapsed/)
|
||||||
|
await expect(page.locator(`#field-collapsedByDefaultBlocks__4__text`)).toBeHidden()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should not collapse block rows on input change', async () => {
|
||||||
|
await page.goto(url.create)
|
||||||
|
|
||||||
|
await addBlock({
|
||||||
|
page,
|
||||||
|
fieldName: 'collapsedByDefaultBlocks',
|
||||||
|
blockToSelect: 'Localized Content',
|
||||||
|
})
|
||||||
|
|
||||||
|
const row = page.locator(`#collapsedByDefaultBlocks-row-4`)
|
||||||
|
const toggler = row.locator('button.collapsible__toggle')
|
||||||
|
|
||||||
|
await expect(toggler).toHaveClass(/collapsible__toggle--collapsed/)
|
||||||
|
await expect(page.locator(`#field-collapsedByDefaultBlocks__4__text`)).toBeHidden()
|
||||||
|
|
||||||
|
await toggleBlockOrArrayRow({
|
||||||
|
page,
|
||||||
|
fieldName: 'collapsedByDefaultBlocks',
|
||||||
|
targetState: 'open',
|
||||||
|
rowIndex: 4,
|
||||||
|
})
|
||||||
|
|
||||||
|
await page.locator('input#field-collapsedByDefaultBlocks__4__text').fill('Hello, world!')
|
||||||
|
|
||||||
|
// wait for form state to return, in the future can wire this into watch network requests (if needed)
|
||||||
|
await wait(1000)
|
||||||
|
|
||||||
|
await expect(toggler).toHaveClass(/collapsible__toggle--open/)
|
||||||
|
await expect(page.locator(`#field-collapsedByDefaultBlocks__4__text`)).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
test('should use i18n block labels', async () => {
|
test('should use i18n block labels', async () => {
|
||||||
await page.goto(url.create)
|
await page.goto(url.create)
|
||||||
await expect(page.locator('#field-i18nBlocks .blocks-field__header')).toContainText('Block en')
|
await expect(page.locator('#field-i18nBlocks .blocks-field__header')).toContainText('Block en')
|
||||||
@@ -193,7 +241,7 @@ describe('Block fields', () => {
|
|||||||
await addBlock({
|
await addBlock({
|
||||||
page,
|
page,
|
||||||
fieldName: 'i18nBlocks',
|
fieldName: 'i18nBlocks',
|
||||||
blockLabel: 'Text en',
|
blockToSelect: 'Text en',
|
||||||
})
|
})
|
||||||
|
|
||||||
// ensure the block was appended to the rows
|
// ensure the block was appended to the rows
|
||||||
@@ -210,7 +258,7 @@ describe('Block fields', () => {
|
|||||||
await addBlock({
|
await addBlock({
|
||||||
page,
|
page,
|
||||||
fieldName: 'blocks',
|
fieldName: 'blocks',
|
||||||
blockLabel: 'Content',
|
blockToSelect: 'Content',
|
||||||
})
|
})
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
@@ -226,7 +274,7 @@ describe('Block fields', () => {
|
|||||||
await addBlock({
|
await addBlock({
|
||||||
page,
|
page,
|
||||||
fieldName: 'blocksWithSimilarConfigs',
|
fieldName: 'blocksWithSimilarConfigs',
|
||||||
blockLabel: 'Block A',
|
blockToSelect: 'Block A',
|
||||||
})
|
})
|
||||||
|
|
||||||
await page
|
await page
|
||||||
@@ -245,7 +293,7 @@ describe('Block fields', () => {
|
|||||||
await addBlock({
|
await addBlock({
|
||||||
page,
|
page,
|
||||||
fieldName: 'blocksWithSimilarConfigs',
|
fieldName: 'blocksWithSimilarConfigs',
|
||||||
blockLabel: 'Block B',
|
blockToSelect: 'Block B',
|
||||||
})
|
})
|
||||||
|
|
||||||
await page
|
await page
|
||||||
@@ -274,7 +322,7 @@ describe('Block fields', () => {
|
|||||||
await addBlock({
|
await addBlock({
|
||||||
page,
|
page,
|
||||||
fieldName: 'blocksWithMinRows',
|
fieldName: 'blocksWithMinRows',
|
||||||
blockLabel: 'Block With Min Row',
|
blockToSelect: 'Block With Min Row',
|
||||||
})
|
})
|
||||||
|
|
||||||
const firstRow = page.locator('input[name="blocksWithMinRows.0.blockTitle"]')
|
const firstRow = page.locator('input[name="blocksWithMinRows.0.blockTitle"]')
|
||||||
|
|||||||
@@ -226,6 +226,13 @@ export interface User {
|
|||||||
hash?: string | null;
|
hash?: string | null;
|
||||||
loginAttempts?: number | null;
|
loginAttempts?: number | null;
|
||||||
lockUntil?: string | null;
|
lockUntil?: string | null;
|
||||||
|
sessions?:
|
||||||
|
| {
|
||||||
|
id: string;
|
||||||
|
createdAt?: string | null;
|
||||||
|
expiresAt: string;
|
||||||
|
}[]
|
||||||
|
| null;
|
||||||
password?: string | null;
|
password?: string | null;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@@ -1838,6 +1845,13 @@ export interface UsersSelect<T extends boolean = true> {
|
|||||||
hash?: T;
|
hash?: T;
|
||||||
loginAttempts?: T;
|
loginAttempts?: T;
|
||||||
lockUntil?: T;
|
lockUntil?: T;
|
||||||
|
sessions?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
id?: T;
|
||||||
|
createdAt?: T;
|
||||||
|
expiresAt?: T;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* This interface was referenced by `Config`'s JSON-Schema
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ test.describe('Form State', () => {
|
|||||||
async () => {
|
async () => {
|
||||||
await addBlock({
|
await addBlock({
|
||||||
page,
|
page,
|
||||||
blockLabel: 'Text',
|
blockToSelect: 'Text',
|
||||||
fieldName: 'blocks',
|
fieldName: 'blocks',
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -8,21 +8,24 @@ import { openBlocksDrawer } from './openBlocksDrawer.js'
|
|||||||
export const addBlock = async ({
|
export const addBlock = async ({
|
||||||
page,
|
page,
|
||||||
fieldName = 'blocks',
|
fieldName = 'blocks',
|
||||||
blockLabel = 'Block',
|
blockToSelect = 'Block',
|
||||||
}: {
|
}: {
|
||||||
blockLabel: string
|
/**
|
||||||
|
* The name of the block to select from the blocks drawer.
|
||||||
|
*/
|
||||||
|
blockToSelect: string
|
||||||
fieldName: string
|
fieldName: string
|
||||||
page: Page
|
page: Page
|
||||||
}) => {
|
}) => {
|
||||||
const blocksDrawer = await openBlocksDrawer({ page, fieldName })
|
const blocksDrawer = await openBlocksDrawer({ page, fieldName })
|
||||||
|
|
||||||
const blockCard = blocksDrawer.locator('.blocks-drawer__block .thumbnail-card__label', {
|
const blockCard = blocksDrawer.locator('.blocks-drawer__block .thumbnail-card__label', {
|
||||||
hasText: blockLabel,
|
hasText: blockToSelect,
|
||||||
})
|
})
|
||||||
|
|
||||||
await expect(blockCard).toBeVisible()
|
await expect(blockCard).toBeVisible()
|
||||||
|
|
||||||
await blocksDrawer.getByRole('button', { name: exactText(blockLabel) }).click()
|
await blocksDrawer.getByRole('button', { name: exactText(blockToSelect) }).click()
|
||||||
|
|
||||||
// expect to see the block on the page
|
// expect to see the block on the page
|
||||||
}
|
}
|
||||||
|
|||||||
60
test/helpers/e2e/toggleCollapsible.ts
Normal file
60
test/helpers/e2e/toggleCollapsible.ts
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import type { Locator, Page } from '@playwright/test'
|
||||||
|
|
||||||
|
import { expect } from '@playwright/test'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Works for all collapsible field types, including `collapsible`, `array`, and `blocks`.
|
||||||
|
* For arrays and blocks, use the `toggleBlockOrArrayRow` helper instead. It will call this function internally.
|
||||||
|
*/
|
||||||
|
export const toggleCollapsible = async ({
|
||||||
|
toggler,
|
||||||
|
targetState: targetStateFromArgs,
|
||||||
|
}: {
|
||||||
|
targetState?: 'collapsed' | 'open'
|
||||||
|
toggler: Locator
|
||||||
|
}) => {
|
||||||
|
const isCollapsedBeforeClick = await toggler.evaluate((el) =>
|
||||||
|
el.classList.contains('collapsible__toggle--collapsed'),
|
||||||
|
)
|
||||||
|
|
||||||
|
const targetState =
|
||||||
|
targetStateFromArgs !== undefined
|
||||||
|
? targetStateFromArgs
|
||||||
|
: isCollapsedBeforeClick
|
||||||
|
? 'open'
|
||||||
|
: 'collapsed'
|
||||||
|
|
||||||
|
const requiresToggle =
|
||||||
|
(isCollapsedBeforeClick && targetState === 'open') ||
|
||||||
|
(!isCollapsedBeforeClick && targetState === 'collapsed')
|
||||||
|
|
||||||
|
if (requiresToggle) {
|
||||||
|
await toggler.click()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetState === 'collapsed') {
|
||||||
|
await expect(toggler).not.toHaveClass(/collapsible__toggle--open/)
|
||||||
|
await expect(toggler).toHaveClass(/collapsible__toggle--collapsed/)
|
||||||
|
} else {
|
||||||
|
await expect(toggler).toHaveClass(/collapsible__toggle--open/)
|
||||||
|
await expect(toggler).not.toHaveClass(/collapsible__toggle--collapsed/)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const toggleBlockOrArrayRow = async ({
|
||||||
|
page,
|
||||||
|
rowIndex,
|
||||||
|
fieldName,
|
||||||
|
targetState: targetStateFromArgs,
|
||||||
|
}: {
|
||||||
|
fieldName: string
|
||||||
|
page: Page
|
||||||
|
rowIndex: number
|
||||||
|
targetState?: 'collapsed' | 'open'
|
||||||
|
}) => {
|
||||||
|
const row = page.locator(`#${fieldName}-row-${rowIndex}`)
|
||||||
|
|
||||||
|
const toggler = row.locator('button.collapsible__toggle')
|
||||||
|
|
||||||
|
await toggleCollapsible({ toggler, targetState: targetStateFromArgs })
|
||||||
|
}
|
||||||
@@ -21,15 +21,8 @@
|
|||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"emitDeclarationOnly": true,
|
"emitDeclarationOnly": true,
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"lib": [
|
"lib": ["DOM", "DOM.Iterable", "ES2022"],
|
||||||
"DOM",
|
"types": ["node", "jest"],
|
||||||
"DOM.Iterable",
|
|
||||||
"ES2022"
|
|
||||||
],
|
|
||||||
"types": [
|
|
||||||
"node",
|
|
||||||
"jest"
|
|
||||||
],
|
|
||||||
"incremental": true,
|
"incremental": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"plugins": [
|
"plugins": [
|
||||||
@@ -38,72 +31,36 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"paths": {
|
"paths": {
|
||||||
"@payload-config": [
|
"@payload-config": ["./test/_community/config.ts"],
|
||||||
"./test/live-preview/config.ts"
|
"@payloadcms/admin-bar": ["./packages/admin-bar/src"],
|
||||||
],
|
"@payloadcms/live-preview": ["./packages/live-preview/src"],
|
||||||
"@payloadcms/admin-bar": [
|
"@payloadcms/live-preview-react": ["./packages/live-preview-react/src/index.ts"],
|
||||||
"./packages/admin-bar/src"
|
"@payloadcms/live-preview-vue": ["./packages/live-preview-vue/src/index.ts"],
|
||||||
],
|
"@payloadcms/ui": ["./packages/ui/src/exports/client/index.ts"],
|
||||||
"@payloadcms/live-preview": [
|
"@payloadcms/ui/shared": ["./packages/ui/src/exports/shared/index.ts"],
|
||||||
"./packages/live-preview/src"
|
"@payloadcms/ui/rsc": ["./packages/ui/src/exports/rsc/index.ts"],
|
||||||
],
|
"@payloadcms/ui/scss": ["./packages/ui/src/scss.scss"],
|
||||||
"@payloadcms/live-preview-react": [
|
"@payloadcms/ui/scss/app.scss": ["./packages/ui/src/scss/app.scss"],
|
||||||
"./packages/live-preview-react/src/index.ts"
|
"@payloadcms/next/*": ["./packages/next/src/exports/*.ts"],
|
||||||
],
|
|
||||||
"@payloadcms/live-preview-vue": [
|
|
||||||
"./packages/live-preview-vue/src/index.ts"
|
|
||||||
],
|
|
||||||
"@payloadcms/ui": [
|
|
||||||
"./packages/ui/src/exports/client/index.ts"
|
|
||||||
],
|
|
||||||
"@payloadcms/ui/shared": [
|
|
||||||
"./packages/ui/src/exports/shared/index.ts"
|
|
||||||
],
|
|
||||||
"@payloadcms/ui/rsc": [
|
|
||||||
"./packages/ui/src/exports/rsc/index.ts"
|
|
||||||
],
|
|
||||||
"@payloadcms/ui/scss": [
|
|
||||||
"./packages/ui/src/scss.scss"
|
|
||||||
],
|
|
||||||
"@payloadcms/ui/scss/app.scss": [
|
|
||||||
"./packages/ui/src/scss/app.scss"
|
|
||||||
],
|
|
||||||
"@payloadcms/next/*": [
|
|
||||||
"./packages/next/src/exports/*.ts"
|
|
||||||
],
|
|
||||||
"@payloadcms/richtext-lexical/client": [
|
"@payloadcms/richtext-lexical/client": [
|
||||||
"./packages/richtext-lexical/src/exports/client/index.ts"
|
"./packages/richtext-lexical/src/exports/client/index.ts"
|
||||||
],
|
],
|
||||||
"@payloadcms/richtext-lexical/rsc": [
|
"@payloadcms/richtext-lexical/rsc": ["./packages/richtext-lexical/src/exports/server/rsc.ts"],
|
||||||
"./packages/richtext-lexical/src/exports/server/rsc.ts"
|
"@payloadcms/richtext-slate/rsc": ["./packages/richtext-slate/src/exports/server/rsc.ts"],
|
||||||
],
|
|
||||||
"@payloadcms/richtext-slate/rsc": [
|
|
||||||
"./packages/richtext-slate/src/exports/server/rsc.ts"
|
|
||||||
],
|
|
||||||
"@payloadcms/richtext-slate/client": [
|
"@payloadcms/richtext-slate/client": [
|
||||||
"./packages/richtext-slate/src/exports/client/index.ts"
|
"./packages/richtext-slate/src/exports/client/index.ts"
|
||||||
],
|
],
|
||||||
"@payloadcms/plugin-seo/client": [
|
"@payloadcms/plugin-seo/client": ["./packages/plugin-seo/src/exports/client.ts"],
|
||||||
"./packages/plugin-seo/src/exports/client.ts"
|
"@payloadcms/plugin-sentry/client": ["./packages/plugin-sentry/src/exports/client.ts"],
|
||||||
],
|
"@payloadcms/plugin-stripe/client": ["./packages/plugin-stripe/src/exports/client.ts"],
|
||||||
"@payloadcms/plugin-sentry/client": [
|
"@payloadcms/plugin-search/client": ["./packages/plugin-search/src/exports/client.ts"],
|
||||||
"./packages/plugin-sentry/src/exports/client.ts"
|
|
||||||
],
|
|
||||||
"@payloadcms/plugin-stripe/client": [
|
|
||||||
"./packages/plugin-stripe/src/exports/client.ts"
|
|
||||||
],
|
|
||||||
"@payloadcms/plugin-search/client": [
|
|
||||||
"./packages/plugin-search/src/exports/client.ts"
|
|
||||||
],
|
|
||||||
"@payloadcms/plugin-form-builder/client": [
|
"@payloadcms/plugin-form-builder/client": [
|
||||||
"./packages/plugin-form-builder/src/exports/client.ts"
|
"./packages/plugin-form-builder/src/exports/client.ts"
|
||||||
],
|
],
|
||||||
"@payloadcms/plugin-import-export/rsc": [
|
"@payloadcms/plugin-import-export/rsc": [
|
||||||
"./packages/plugin-import-export/src/exports/rsc.ts"
|
"./packages/plugin-import-export/src/exports/rsc.ts"
|
||||||
],
|
],
|
||||||
"@payloadcms/plugin-multi-tenant/rsc": [
|
"@payloadcms/plugin-multi-tenant/rsc": ["./packages/plugin-multi-tenant/src/exports/rsc.ts"],
|
||||||
"./packages/plugin-multi-tenant/src/exports/rsc.ts"
|
|
||||||
],
|
|
||||||
"@payloadcms/plugin-multi-tenant/utilities": [
|
"@payloadcms/plugin-multi-tenant/utilities": [
|
||||||
"./packages/plugin-multi-tenant/src/exports/utilities.ts"
|
"./packages/plugin-multi-tenant/src/exports/utilities.ts"
|
||||||
],
|
],
|
||||||
@@ -113,42 +70,25 @@
|
|||||||
"@payloadcms/plugin-multi-tenant/client": [
|
"@payloadcms/plugin-multi-tenant/client": [
|
||||||
"./packages/plugin-multi-tenant/src/exports/client.ts"
|
"./packages/plugin-multi-tenant/src/exports/client.ts"
|
||||||
],
|
],
|
||||||
"@payloadcms/plugin-multi-tenant": [
|
"@payloadcms/plugin-multi-tenant": ["./packages/plugin-multi-tenant/src/index.ts"],
|
||||||
"./packages/plugin-multi-tenant/src/index.ts"
|
|
||||||
],
|
|
||||||
"@payloadcms/plugin-multi-tenant/translations/languages/all": [
|
"@payloadcms/plugin-multi-tenant/translations/languages/all": [
|
||||||
"./packages/plugin-multi-tenant/src/translations/index.ts"
|
"./packages/plugin-multi-tenant/src/translations/index.ts"
|
||||||
],
|
],
|
||||||
"@payloadcms/plugin-multi-tenant/translations/languages/*": [
|
"@payloadcms/plugin-multi-tenant/translations/languages/*": [
|
||||||
"./packages/plugin-multi-tenant/src/translations/languages/*.ts"
|
"./packages/plugin-multi-tenant/src/translations/languages/*.ts"
|
||||||
],
|
],
|
||||||
"@payloadcms/next": [
|
"@payloadcms/next": ["./packages/next/src/exports/*"],
|
||||||
"./packages/next/src/exports/*"
|
"@payloadcms/storage-azure/client": ["./packages/storage-azure/src/exports/client.ts"],
|
||||||
],
|
"@payloadcms/storage-s3/client": ["./packages/storage-s3/src/exports/client.ts"],
|
||||||
"@payloadcms/storage-azure/client": [
|
|
||||||
"./packages/storage-azure/src/exports/client.ts"
|
|
||||||
],
|
|
||||||
"@payloadcms/storage-s3/client": [
|
|
||||||
"./packages/storage-s3/src/exports/client.ts"
|
|
||||||
],
|
|
||||||
"@payloadcms/storage-vercel-blob/client": [
|
"@payloadcms/storage-vercel-blob/client": [
|
||||||
"./packages/storage-vercel-blob/src/exports/client.ts"
|
"./packages/storage-vercel-blob/src/exports/client.ts"
|
||||||
],
|
],
|
||||||
"@payloadcms/storage-gcs/client": [
|
"@payloadcms/storage-gcs/client": ["./packages/storage-gcs/src/exports/client.ts"],
|
||||||
"./packages/storage-gcs/src/exports/client.ts"
|
|
||||||
],
|
|
||||||
"@payloadcms/storage-uploadthing/client": [
|
"@payloadcms/storage-uploadthing/client": [
|
||||||
"./packages/storage-uploadthing/src/exports/client.ts"
|
"./packages/storage-uploadthing/src/exports/client.ts"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": [
|
"include": ["${configDir}/src"],
|
||||||
"${configDir}/src"
|
"exclude": ["${configDir}/dist", "${configDir}/build", "${configDir}/temp", "**/*.spec.ts"]
|
||||||
],
|
|
||||||
"exclude": [
|
|
||||||
"${configDir}/dist",
|
|
||||||
"${configDir}/build",
|
|
||||||
"${configDir}/temp",
|
|
||||||
"**/*.spec.ts"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user