fix(ui): block rows unexpectedly collapse and array rows not collapsed on init (#12987)

This commit is contained in:
Jacob Fletcher
2025-06-30 21:12:26 -04:00
committed by GitHub
parent c07187d804
commit 3f30a2e300
12 changed files with 283 additions and 136 deletions

View File

@@ -2,7 +2,9 @@ import type { BrowserContext, Locator, Page } from '@playwright/test'
import type { PayloadTestSDK } from 'helpers/sdk/index.js'
import { expect, test } from '@playwright/test'
import { toggleBlockOrArrayRow } from 'helpers/e2e/toggleCollapsible.js'
import * as path from 'path'
import { wait } from 'payload/shared'
import { fileURLToPath } from 'url'
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' })
await wait(500)
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__noRead')).toBeHidden()
await expect(field.locator('#field-array__0__noUpdate')).toBeDisabled()

View File

@@ -169,6 +169,13 @@ export interface User {
hash?: string | null;
loginAttempts?: number | null;
lockUntil?: string | null;
sessions?:
| {
id: string;
createdAt?: string | null;
expiresAt: string;
}[]
| null;
password?: string | null;
}
/**
@@ -287,6 +294,13 @@ export interface UsersSelect<T extends boolean = true> {
hash?: T;
loginAttempts?: T;
lockUntil?: T;
sessions?:
| T
| {
id?: T;
createdAt?: T;
expiresAt?: T;
};
}
/**
* This interface was referenced by `Config`'s JSON-Schema

View File

@@ -3,6 +3,7 @@ import type { Page } from '@playwright/test'
import { expect, test } from '@playwright/test'
import { assertToastErrors } from 'helpers/assertToastErrors.js'
import { toggleBlockOrArrayRow } from 'helpers/e2e/toggleCollapsible.js'
import path from 'path'
import { wait } from 'payload/shared'
import { fileURLToPath } from 'url'
@@ -384,12 +385,43 @@ describe('Array', () => {
await expect(page.locator('#custom-text-field')).toBeVisible()
})
test('should not re-close initCollapsed true array rows on input in create new view', async () => {
await loadCreatePage()
test('should initialize array rows with collapsed state', async () => {
await page.goto(url.create)
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')
await expect(collapsedArrayRow).toBeHidden()
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()
})
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', () => {

View File

@@ -5,6 +5,7 @@ import { addBlock } from 'helpers/e2e/addBlock.js'
import { openBlocksDrawer } from 'helpers/e2e/openBlocksDrawer.js'
import { reorderBlocks } from 'helpers/e2e/reorderBlocks.js'
import { scrollEntirePage } from 'helpers/e2e/scrollEntirePage.js'
import { toggleBlockOrArrayRow } from 'helpers/e2e/toggleCollapsible.js'
import path from 'path'
import { wait } from 'payload/shared'
import { fileURLToPath } from 'url'
@@ -13,7 +14,7 @@ import {
ensureCompilationIsDone,
initPageConsoleErrorCatch,
saveDocAndAssert,
throttleTest,
// throttleTest,
} from '../../../helpers.js'
import { AdminUrlUtil } from '../../../helpers/adminUrlUtil.js'
import { assertToastErrors } from '../../../helpers/assertToastErrors.js'
@@ -84,7 +85,7 @@ describe('Block fields', () => {
await addBlock({
page,
fieldName: 'blocks',
blockLabel: 'Content',
blockToSelect: 'Content',
})
// ensure the block was appended to the rows
@@ -186,6 +187,53 @@ describe('Block fields', () => {
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 () => {
await page.goto(url.create)
await expect(page.locator('#field-i18nBlocks .blocks-field__header')).toContainText('Block en')
@@ -193,7 +241,7 @@ describe('Block fields', () => {
await addBlock({
page,
fieldName: 'i18nBlocks',
blockLabel: 'Text en',
blockToSelect: 'Text en',
})
// ensure the block was appended to the rows
@@ -210,7 +258,7 @@ describe('Block fields', () => {
await addBlock({
page,
fieldName: 'blocks',
blockLabel: 'Content',
blockToSelect: 'Content',
})
await expect(
@@ -226,7 +274,7 @@ describe('Block fields', () => {
await addBlock({
page,
fieldName: 'blocksWithSimilarConfigs',
blockLabel: 'Block A',
blockToSelect: 'Block A',
})
await page
@@ -245,7 +293,7 @@ describe('Block fields', () => {
await addBlock({
page,
fieldName: 'blocksWithSimilarConfigs',
blockLabel: 'Block B',
blockToSelect: 'Block B',
})
await page
@@ -274,7 +322,7 @@ describe('Block fields', () => {
await addBlock({
page,
fieldName: 'blocksWithMinRows',
blockLabel: 'Block With Min Row',
blockToSelect: 'Block With Min Row',
})
const firstRow = page.locator('input[name="blocksWithMinRows.0.blockTitle"]')

View File

@@ -226,6 +226,13 @@ export interface User {
hash?: string | null;
loginAttempts?: number | null;
lockUntil?: string | null;
sessions?:
| {
id: string;
createdAt?: string | null;
expiresAt: string;
}[]
| null;
password?: string | null;
}
/**
@@ -1838,6 +1845,13 @@ export interface UsersSelect<T extends boolean = true> {
hash?: T;
loginAttempts?: T;
lockUntil?: T;
sessions?:
| T
| {
id?: T;
createdAt?: T;
expiresAt?: T;
};
}
/**
* This interface was referenced by `Config`'s JSON-Schema

View File

@@ -88,7 +88,7 @@ test.describe('Form State', () => {
async () => {
await addBlock({
page,
blockLabel: 'Text',
blockToSelect: 'Text',
fieldName: 'blocks',
})
},

View File

@@ -8,21 +8,24 @@ import { openBlocksDrawer } from './openBlocksDrawer.js'
export const addBlock = async ({
page,
fieldName = 'blocks',
blockLabel = 'Block',
blockToSelect = 'Block',
}: {
blockLabel: string
/**
* The name of the block to select from the blocks drawer.
*/
blockToSelect: string
fieldName: string
page: Page
}) => {
const blocksDrawer = await openBlocksDrawer({ page, fieldName })
const blockCard = blocksDrawer.locator('.blocks-drawer__block .thumbnail-card__label', {
hasText: blockLabel,
hasText: blockToSelect,
})
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
}

View 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 })
}