diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 88b53c8a23..9768f3c2c2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -256,6 +256,8 @@ jobs: # - field-error-states # - fields-relationship - fields + - fields/collections/Blocks + - fields/collections/Array # - fields/lexical # - live-preview # - localization diff --git a/test/fields/collections/Array/e2e.spec.ts b/test/fields/collections/Array/e2e.spec.ts new file mode 100644 index 0000000000..ef7c9b7ade --- /dev/null +++ b/test/fields/collections/Array/e2e.spec.ts @@ -0,0 +1,288 @@ +import type { Page } from '@playwright/test' + +import { expect, test } from '@playwright/test' +import path from 'path' +import { wait } from 'payload/utilities' +import { fileURLToPath } from 'url' + +import type { PayloadTestSDK } from '../../../helpers/sdk/index.js' +import type { Config } from '../../payload-types.js' + +import { + ensureAutoLoginAndCompilationIsDone, + initPageConsoleErrorCatch, + saveDocAndAssert, +} from '../../../helpers.js' +import { AdminUrlUtil } from '../../../helpers/adminUrlUtil.js' +import { initPayloadE2ENoConfig } from '../../../helpers/initPayloadE2ENoConfig.js' +import { reInitializeDB } from '../../../helpers/reInit.js' +import { RESTClient } from '../../../helpers/rest.js' + +const filename = fileURLToPath(import.meta.url) +const currentFolder = path.dirname(filename) +const dirname = path.resolve(currentFolder, '../../') + +const { beforeAll, beforeEach, describe } = test + +let payload: PayloadTestSDK +let client: RESTClient +let page: Page +let serverURL: string +// If we want to make this run in parallel: test.describe.configure({ mode: 'parallel' }) + +describe('Array', () => { + beforeAll(async ({ browser }) => { + process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit + ;({ payload, serverURL } = await initPayloadE2ENoConfig({ + dirname, + })) + + const context = await browser.newContext() + page = await context.newPage() + initPageConsoleErrorCatch(page) + }) + beforeEach(async () => { + await reInitializeDB({ + serverURL, + snapshotKey: 'fieldsArrayTest', + uploadsDir: path.resolve(dirname, '../Upload/uploads'), + }) + + if (client) { + await client.logout() + } + client = new RESTClient(null, { defaultSlug: 'users', serverURL }) + await client.login() + + await ensureAutoLoginAndCompilationIsDone({ page, serverURL }) + }) + + let url: AdminUrlUtil + beforeAll(() => { + url = new AdminUrlUtil(serverURL, 'array-fields') + }) + + test('should be readOnly', async () => { + await page.goto(url.create) + const field = page.locator('#field-readOnly__0__text') + await expect(field).toBeDisabled() + }) + + test('should have defaultValue', async () => { + await page.goto(url.create) + const field = page.locator('#field-readOnly__0__text') + await expect(field).toHaveValue('defaultValue') + }) + + test('should render RowLabel using a component', async () => { + const label = 'custom row label as component' + await page.goto(url.create) + await page.locator('#field-rowLabelAsComponent >> .array-field__add-row').click() + + await page.locator('#field-rowLabelAsComponent__0__title').fill(label) + await wait(100) + const customRowLabel = page.locator( + '#rowLabelAsComponent-row-0 >> .array-field__row-header > :text("custom row label")', + ) + await expect(customRowLabel).toHaveCSS('text-transform', 'uppercase') + }) + + test('should bypass min rows validation when no rows present and field is not required', async () => { + await page.goto(url.create) + await saveDocAndAssert(page) + await expect(page.locator('.Toastify')).toContainText('successfully') + }) + + test('should fail min rows validation when rows are present', async () => { + await page.goto(url.create) + await page.locator('#field-arrayWithMinRows >> .array-field__add-row').click() + + await page.click('#action-save', { delay: 100 }) + await expect(page.locator('.Toastify')).toContainText( + 'The following field is invalid: arrayWithMinRows', + ) + }) + + describe('row manipulation', () => { + test('should add, remove and duplicate rows', async () => { + const assertText0 = 'array row 1' + const assertGroupText0 = 'text in group in row 1' + const assertText1 = 'array row 2' + const assertText3 = 'array row 3' + const assertGroupText3 = 'text in group in row 3' + await page.goto(url.create) + await page.mouse.wheel(0, 1750) + await page.locator('#field-potentiallyEmptyArray').scrollIntoViewIfNeeded() + await wait(300) + + // Add 3 rows + await page.locator('#field-potentiallyEmptyArray > .array-field__add-row').click() + await wait(300) + await page.locator('#field-potentiallyEmptyArray > .array-field__add-row').click() + await wait(300) + await page.locator('#field-potentiallyEmptyArray > .array-field__add-row').click() + await wait(300) + + // Fill out row 1 + await page.locator('#field-potentiallyEmptyArray__0__text').fill(assertText0) + await page + .locator('#field-potentiallyEmptyArray__0__groupInRow__textInGroupInRow') + .fill(assertGroupText0) + // Fill out row 2 + await page.locator('#field-potentiallyEmptyArray__1__text').fill(assertText1) + // Fill out row 3 + await page.locator('#field-potentiallyEmptyArray__2__text').fill(assertText3) + await page + .locator('#field-potentiallyEmptyArray__2__groupInRow__textInGroupInRow') + .fill(assertGroupText3) + + // Remove row 1 + await page.locator('#potentiallyEmptyArray-row-0 .array-actions__button').click() + await page + .locator('#potentiallyEmptyArray-row-0 .popup__scroll-container .array-actions__remove') + .click() + // Remove row 2 + await page.locator('#potentiallyEmptyArray-row-0 .array-actions__button').click() + await page + .locator('#potentiallyEmptyArray-row-0 .popup__scroll-container .array-actions__remove') + .click() + + // Save document + await saveDocAndAssert(page) + + // Scroll to array row (fields are not rendered in DOM until on screen) + await page.locator('#field-potentiallyEmptyArray__0__groupInRow').scrollIntoViewIfNeeded() + + // Expect the remaining row to be the third row + const input = page.locator('#field-potentiallyEmptyArray__0__groupInRow__textInGroupInRow') + await expect(input).toHaveValue(assertGroupText3) + + // Duplicate row + await page.locator('#potentiallyEmptyArray-row-0 .array-actions__button').click() + await page + .locator('#potentiallyEmptyArray-row-0 .popup__scroll-container .array-actions__duplicate') + .click() + + // Update duplicated row group field text + await page + .locator('#field-potentiallyEmptyArray__1__groupInRow__textInGroupInRow') + .fill(`${assertGroupText3} duplicate`) + + // Save document + await saveDocAndAssert(page) + + // Expect the second row to be a duplicate of the remaining row + await expect( + page.locator('#field-potentiallyEmptyArray__1__groupInRow__textInGroupInRow'), + ).toHaveValue(`${assertGroupText3} duplicate`) + + // Remove row 1 + await page.locator('#potentiallyEmptyArray-row-0 .array-actions__button').click() + await page + .locator('#potentiallyEmptyArray-row-0 .popup__scroll-container .array-actions__remove') + .click() + + // Save document + await saveDocAndAssert(page) + + // Expect the remaining row to be the copy of the duplicate row + await expect( + page.locator('#field-potentiallyEmptyArray__0__groupInRow__textInGroupInRow'), + ).toHaveValue(`${assertGroupText3} duplicate`) + }) + }) + + // TODO: re-enable this test + test.skip('should bulk update', async () => { + await payload.create({ + collection: 'array-fields', + data: { + title: 'for test 1', + items: [ + { + text: 'test 1', + }, + { + text: 'test 2', + }, + ], + }, + }) + + await payload.create({ + collection: 'array-fields', + data: { + title: 'for test 2', + items: [ + { + text: 'test 3', + }, + ], + }, + }) + + await payload.create({ + collection: 'array-fields', + data: { + title: 'for test 3', + items: [ + { + text: 'test 4', + }, + { + text: 'test 5', + }, + { + text: 'test 6', + }, + ], + }, + }) + + const bulkText = 'Bulk update text' + await page.goto(url.list) + await page.waitForSelector('.table > table > tbody > tr td.cell-title') + const rows = page.locator('.table > table > tbody > tr', { + has: page.locator('td.cell-title a', { + hasText: 'for test', + }), + }) + const count = await rows.count() + + for (let i = 0; i < count; i++) { + await rows + .nth(i) + .locator('td.cell-_select .checkbox-input__input > input[type="checkbox"]') + .click() + } + await page.locator('.edit-many__toggle').click() + await page.locator('.field-select .rs__control').click() + + const arrayOption = page.locator('.rs__option', { + hasText: 'Items', + }) + + await expect(arrayOption).toBeVisible() + + await arrayOption.click() + await wait(200) + + const addRowButton = page.locator('#field-items > .array-field__add-row') + + await expect(addRowButton).toBeVisible() + + await addRowButton.click() + await wait(200) + + const targetInput = page.locator('#field-items__0__text') + + await expect(targetInput).toBeVisible() + + await targetInput.fill(bulkText) + + await page.locator('#edit-array-fields .form-submit .edit-many__save').click() + await expect(page.locator('.Toastify__toast--success')).toContainText( + 'Updated 3 Array Fields successfully.', + ) + }) +}) diff --git a/test/fields/collections/Blocks/e2e.spec.ts b/test/fields/collections/Blocks/e2e.spec.ts new file mode 100644 index 0000000000..35854bc930 --- /dev/null +++ b/test/fields/collections/Blocks/e2e.spec.ts @@ -0,0 +1,245 @@ +import type { Page } from '@playwright/test' + +import { expect, test } from '@playwright/test' +import path from 'path' +import { fileURLToPath } from 'url' + +import { + ensureAutoLoginAndCompilationIsDone, + initPageConsoleErrorCatch, + saveDocAndAssert, +} from '../../../helpers.js' +import { AdminUrlUtil } from '../../../helpers/adminUrlUtil.js' +import { initPayloadE2ENoConfig } from '../../../helpers/initPayloadE2ENoConfig.js' +import { reInitializeDB } from '../../../helpers/reInit.js' +import { RESTClient } from '../../../helpers/rest.js' + +const filename = fileURLToPath(import.meta.url) +const currentFolder = path.dirname(filename) +const dirname = path.resolve(currentFolder, '../../') + +const { beforeAll, beforeEach, describe } = test + +let client: RESTClient +let page: Page +let serverURL: string +// If we want to make this run in parallel: test.describe.configure({ mode: 'parallel' }) + +describe('Block fields', () => { + beforeAll(async ({ browser }) => { + process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit + ;({ serverURL } = await initPayloadE2ENoConfig({ + dirname, + })) + + const context = await browser.newContext() + page = await context.newPage() + initPageConsoleErrorCatch(page) + }) + beforeEach(async () => { + await reInitializeDB({ + serverURL, + snapshotKey: 'blockFieldsTest', + uploadsDir: path.resolve(dirname, '../Upload/uploads'), + }) + + if (client) { + await client.logout() + } + client = new RESTClient(null, { defaultSlug: 'users', serverURL }) + await client.login() + + await ensureAutoLoginAndCompilationIsDone({ page, serverURL }) + }) + + let url: AdminUrlUtil + beforeAll(() => { + url = new AdminUrlUtil(serverURL, 'block-fields') + }) + + test('should open blocks drawer and select first block', async () => { + await page.goto(url.create) + const addButton = page.locator('#field-blocks > .blocks-field__drawer-toggler') + await expect(addButton).toContainText('Add Block') + await addButton.click() + + const blocksDrawer = page.locator('[id^=drawer_1_blocks-drawer-]') + await expect(blocksDrawer).toBeVisible() + + // select the first block in the drawer + const firstBlockSelector = blocksDrawer + .locator('.blocks-drawer__blocks .blocks-drawer__block') + .first() + await expect(firstBlockSelector).toContainText('Content') + await firstBlockSelector.click() + + // ensure the block was appended to the rows + const addedRow = page.locator('#field-blocks .blocks-field__row').last() + await expect(addedRow).toBeVisible() + await expect(addedRow.locator('.blocks-field__block-pill-content')).toContainText('Content') + }) + + test('should open blocks drawer from block row and add below', async () => { + await page.goto(url.create) + const firstRow = page.locator('#field-blocks #blocks-row-0') + const rowActions = firstRow.locator('.collapsible__actions') + await expect(rowActions).toBeVisible() + + await rowActions.locator('.array-actions__button').click() + const addBelowButton = rowActions.locator('.array-actions__action.array-actions__add') + await expect(addBelowButton).toBeVisible() + await addBelowButton.click() + + const blocksDrawer = page.locator('[id^=drawer_1_blocks-drawer-]') + await expect(blocksDrawer).toBeVisible() + + // select the first block in the drawer + const firstBlockSelector = blocksDrawer + .locator('.blocks-drawer__blocks .blocks-drawer__block') + .first() + await expect(firstBlockSelector).toContainText('Content') + await firstBlockSelector.click() + + // ensure the block was inserted beneath the first in the rows + const addedRow = page.locator('#field-blocks #blocks-row-1') + await expect(addedRow).toBeVisible() + await expect(addedRow.locator('.blocks-field__block-pill-content')).toContainText('Content') // went from `Number` to `Content` + }) + + test('should use i18n block labels', async () => { + await page.goto(url.create) + await expect(page.locator('#field-i18nBlocks .blocks-field__header')).toContainText('Block en') + + const addButton = page.locator('#field-i18nBlocks > .blocks-field__drawer-toggler') + await expect(addButton).toContainText('Add Block en') + await addButton.click() + + const blocksDrawer = page.locator('[id^=drawer_1_blocks-drawer-]') + await expect(blocksDrawer).toBeVisible() + + // select the first block in the drawer + const firstBlockSelector = blocksDrawer + .locator('.blocks-drawer__blocks .blocks-drawer__block') + .first() + await expect(firstBlockSelector).toContainText('Text en') + await firstBlockSelector.click() + + // ensure the block was appended to the rows + const firstRow = page.locator('#field-i18nBlocks .blocks-field__row').first() + await expect(firstRow).toBeVisible() + await expect(firstRow.locator('.blocks-field__block-pill-text')).toContainText('Text en') + }) + + test('should add different blocks with similar field configs', async () => { + await page.goto(url.create) + + async function addBlock(name: 'Block A' | 'Block B') { + await page + .locator('#field-blocksWithSimilarConfigs') + .getByRole('button', { name: 'Add Blocks With Similar Config' }) + .click() + await page.getByRole('button', { name }).click() + } + + await addBlock('Block A') + + await page + .locator('#blocksWithSimilarConfigs-row-0') + .getByRole('button', { name: 'Add Item' }) + .click() + await page + .locator('input[name="blocksWithSimilarConfigs.0.items.0.title"]') + .fill('items>0>title') + + await expect( + page.locator('input[name="blocksWithSimilarConfigs.0.items.0.title"]'), + ).toHaveValue('items>0>title') + + await addBlock('Block B') + + await page + .locator('#blocksWithSimilarConfigs-row-1') + .getByRole('button', { name: 'Add Item' }) + .click() + await page + .locator('input[name="blocksWithSimilarConfigs.1.items.0.title2"]') + .fill('items>1>title') + + await expect( + page.locator('input[name="blocksWithSimilarConfigs.1.items.0.title2"]'), + ).toHaveValue('items>1>title') + }) + + test('should bypass min rows validation when no rows present and field is not required', async () => { + await page.goto(url.create) + await saveDocAndAssert(page) + await expect(page.locator('.Toastify')).toContainText('successfully') + }) + + test('should fail min rows validation when rows are present', async () => { + await page.goto(url.create) + + await page + .locator('#field-blocksWithMinRows') + .getByRole('button', { name: 'Add Blocks With Min Row' }) + .click() + + const blocksDrawer = page.locator('[id^=drawer_1_blocks-drawer-]') + await expect(blocksDrawer).toBeVisible() + + const firstBlockSelector = blocksDrawer + .locator('.blocks-drawer__blocks .blocks-drawer__block') + .first() + + await firstBlockSelector.click() + + const firstRow = page.locator('input[name="blocksWithMinRows.0.blockTitle"]') + await expect(firstRow).toBeVisible() + await firstRow.fill('first row') + await expect(firstRow).toHaveValue('first row') + + await page.click('#action-save', { delay: 100 }) + await expect(page.locator('.Toastify')).toContainText( + 'The following field is invalid: blocksWithMinRows', + ) + }) + + describe('row manipulation', () => { + describe('react hooks', () => { + test('should add 2 new block rows', async () => { + await page.goto(url.create) + + await page + .locator('.custom-blocks-field-management') + .getByRole('button', { name: 'Add Block 1' }) + .click() + + const customBlocks = page.locator( + '#field-customBlocks input[name="customBlocks.0.block1Title"]', + ) + + await page.mouse.wheel(0, 1750) + + await customBlocks.scrollIntoViewIfNeeded() + + await expect(customBlocks).toHaveValue('Block 1: Prefilled Title') + + await page + .locator('.custom-blocks-field-management') + .getByRole('button', { name: 'Add Block 2' }) + .click() + await expect( + page.locator('#field-customBlocks input[name="customBlocks.1.block2Title"]'), + ).toHaveValue('Block 2: Prefilled Title') + + await page + .locator('.custom-blocks-field-management') + .getByRole('button', { name: 'Replace Block 2' }) + .click() + await expect( + page.locator('#field-customBlocks input[name="customBlocks.1.block1Title"]'), + ).toHaveValue('REPLACED BLOCK') + }) + }) + }) +}) diff --git a/test/fields/e2e.spec.ts b/test/fields/e2e.spec.ts index 0a9414aa7e..255d4911ef 100644 --- a/test/fields/e2e.spec.ts +++ b/test/fields/e2e.spec.ts @@ -534,434 +534,6 @@ describe('fields', () => { }) }) - describe('blocks', () => { - let url: AdminUrlUtil - beforeAll(() => { - url = new AdminUrlUtil(serverURL, 'block-fields') - }) - - test('should open blocks drawer and select first block', async () => { - await page.goto(url.create) - const addButton = page.locator('#field-blocks > .blocks-field__drawer-toggler') - await expect(addButton).toContainText('Add Block') - await addButton.click() - - const blocksDrawer = page.locator('[id^=drawer_1_blocks-drawer-]') - await expect(blocksDrawer).toBeVisible() - - // select the first block in the drawer - const firstBlockSelector = blocksDrawer - .locator('.blocks-drawer__blocks .blocks-drawer__block') - .first() - await expect(firstBlockSelector).toContainText('Content') - await firstBlockSelector.click() - - // ensure the block was appended to the rows - const addedRow = page.locator('#field-blocks .blocks-field__row').last() - await expect(addedRow).toBeVisible() - await expect(addedRow.locator('.blocks-field__block-pill-content')).toContainText('Content') - }) - - test('should open blocks drawer from block row and add below', async () => { - await page.goto(url.create) - const firstRow = page.locator('#field-blocks #blocks-row-0') - const rowActions = firstRow.locator('.collapsible__actions') - await expect(rowActions).toBeVisible() - - await rowActions.locator('.array-actions__button').click() - const addBelowButton = rowActions.locator('.array-actions__action.array-actions__add') - await expect(addBelowButton).toBeVisible() - await addBelowButton.click() - - const blocksDrawer = page.locator('[id^=drawer_1_blocks-drawer-]') - await expect(blocksDrawer).toBeVisible() - - // select the first block in the drawer - const firstBlockSelector = blocksDrawer - .locator('.blocks-drawer__blocks .blocks-drawer__block') - .first() - await expect(firstBlockSelector).toContainText('Content') - await firstBlockSelector.click() - - // ensure the block was inserted beneath the first in the rows - const addedRow = page.locator('#field-blocks #blocks-row-1') - await expect(addedRow).toBeVisible() - await expect(addedRow.locator('.blocks-field__block-pill-content')).toContainText('Content') // went from `Number` to `Content` - }) - - test('should use i18n block labels', async () => { - await page.goto(url.create) - await expect(page.locator('#field-i18nBlocks .blocks-field__header')).toContainText( - 'Block en', - ) - - const addButton = page.locator('#field-i18nBlocks > .blocks-field__drawer-toggler') - await expect(addButton).toContainText('Add Block en') - await addButton.click() - - const blocksDrawer = page.locator('[id^=drawer_1_blocks-drawer-]') - await expect(blocksDrawer).toBeVisible() - - // select the first block in the drawer - const firstBlockSelector = blocksDrawer - .locator('.blocks-drawer__blocks .blocks-drawer__block') - .first() - await expect(firstBlockSelector).toContainText('Text en') - await firstBlockSelector.click() - - // ensure the block was appended to the rows - const firstRow = page.locator('#field-i18nBlocks .blocks-field__row').first() - await expect(firstRow).toBeVisible() - await expect(firstRow.locator('.blocks-field__block-pill-text')).toContainText('Text en') - }) - - test('should add different blocks with similar field configs', async () => { - await page.goto(url.create) - - async function addBlock(name: 'Block A' | 'Block B') { - await page - .locator('#field-blocksWithSimilarConfigs') - .getByRole('button', { name: 'Add Blocks With Similar Config' }) - .click() - await page.getByRole('button', { name }).click() - } - - await addBlock('Block A') - - await page - .locator('#blocksWithSimilarConfigs-row-0') - .getByRole('button', { name: 'Add Item' }) - .click() - await page - .locator('input[name="blocksWithSimilarConfigs.0.items.0.title"]') - .fill('items>0>title') - - await expect( - page.locator('input[name="blocksWithSimilarConfigs.0.items.0.title"]'), - ).toHaveValue('items>0>title') - - await addBlock('Block B') - - await page - .locator('#blocksWithSimilarConfigs-row-1') - .getByRole('button', { name: 'Add Item' }) - .click() - await page - .locator('input[name="blocksWithSimilarConfigs.1.items.0.title2"]') - .fill('items>1>title') - - await expect( - page.locator('input[name="blocksWithSimilarConfigs.1.items.0.title2"]'), - ).toHaveValue('items>1>title') - }) - - test('should bypass min rows validation when no rows present and field is not required', async () => { - await page.goto(url.create) - await saveDocAndAssert(page) - await expect(page.locator('.Toastify')).toContainText('successfully') - }) - - test('should fail min rows validation when rows are present', async () => { - await page.goto(url.create) - - await page - .locator('#field-blocksWithMinRows') - .getByRole('button', { name: 'Add Blocks With Min Row' }) - .click() - - const blocksDrawer = page.locator('[id^=drawer_1_blocks-drawer-]') - await expect(blocksDrawer).toBeVisible() - - const firstBlockSelector = blocksDrawer - .locator('.blocks-drawer__blocks .blocks-drawer__block') - .first() - - await firstBlockSelector.click() - - const firstRow = page.locator('input[name="blocksWithMinRows.0.blockTitle"]') - await expect(firstRow).toBeVisible() - await firstRow.fill('first row') - await expect(firstRow).toHaveValue('first row') - - await page.click('#action-save', { delay: 100 }) - await expect(page.locator('.Toastify')).toContainText( - 'The following field is invalid: blocksWithMinRows', - ) - }) - - describe('row manipulation', () => { - describe('react hooks', () => { - test('should add 2 new block rows', async () => { - await page.goto(url.create) - - await page - .locator('.custom-blocks-field-management') - .getByRole('button', { name: 'Add Block 1' }) - .click() - - const customBlocks = page.locator( - '#field-customBlocks input[name="customBlocks.0.block1Title"]', - ) - - await page.mouse.wheel(0, 1750) - - await customBlocks.scrollIntoViewIfNeeded() - - await expect(customBlocks).toHaveValue('Block 1: Prefilled Title') - - await page - .locator('.custom-blocks-field-management') - .getByRole('button', { name: 'Add Block 2' }) - .click() - await expect( - page.locator('#field-customBlocks input[name="customBlocks.1.block2Title"]'), - ).toHaveValue('Block 2: Prefilled Title') - - await page - .locator('.custom-blocks-field-management') - .getByRole('button', { name: 'Replace Block 2' }) - .click() - await expect( - page.locator('#field-customBlocks input[name="customBlocks.1.block1Title"]'), - ).toHaveValue('REPLACED BLOCK') - }) - }) - }) - }) - - describe('array', () => { - let url: AdminUrlUtil - beforeAll(() => { - url = new AdminUrlUtil(serverURL, 'array-fields') - }) - - test('should be readOnly', async () => { - await page.goto(url.create) - const field = page.locator('#field-readOnly__0__text') - await expect(field).toBeDisabled() - }) - - test('should have defaultValue', async () => { - await page.goto(url.create) - const field = page.locator('#field-readOnly__0__text') - await expect(field).toHaveValue('defaultValue') - }) - - test('should render RowLabel using a component', async () => { - const label = 'custom row label as component' - await page.goto(url.create) - await page.locator('#field-rowLabelAsComponent >> .array-field__add-row').click() - - await page.locator('#field-rowLabelAsComponent__0__title').fill(label) - await wait(100) - const customRowLabel = page.locator( - '#rowLabelAsComponent-row-0 >> .array-field__row-header > :text("custom row label")', - ) - await expect(customRowLabel).toHaveCSS('text-transform', 'uppercase') - }) - - test('should bypass min rows validation when no rows present and field is not required', async () => { - await page.goto(url.create) - await saveDocAndAssert(page) - await expect(page.locator('.Toastify')).toContainText('successfully') - }) - - test('should fail min rows validation when rows are present', async () => { - await page.goto(url.create) - await page.locator('#field-arrayWithMinRows >> .array-field__add-row').click() - - await page.click('#action-save', { delay: 100 }) - await expect(page.locator('.Toastify')).toContainText( - 'The following field is invalid: arrayWithMinRows', - ) - }) - - describe('row manipulation', () => { - test('should add, remove and duplicate rows', async () => { - const assertText0 = 'array row 1' - const assertGroupText0 = 'text in group in row 1' - const assertText1 = 'array row 2' - const assertText3 = 'array row 3' - const assertGroupText3 = 'text in group in row 3' - await page.goto(url.create) - await page.mouse.wheel(0, 1750) - await page.locator('#field-potentiallyEmptyArray').scrollIntoViewIfNeeded() - await wait(300) - - // Add 3 rows - await page.locator('#field-potentiallyEmptyArray > .array-field__add-row').click() - await wait(300) - await page.locator('#field-potentiallyEmptyArray > .array-field__add-row').click() - await wait(300) - await page.locator('#field-potentiallyEmptyArray > .array-field__add-row').click() - await wait(300) - - // Fill out row 1 - await page.locator('#field-potentiallyEmptyArray__0__text').fill(assertText0) - await page - .locator('#field-potentiallyEmptyArray__0__groupInRow__textInGroupInRow') - .fill(assertGroupText0) - // Fill out row 2 - await page.locator('#field-potentiallyEmptyArray__1__text').fill(assertText1) - // Fill out row 3 - await page.locator('#field-potentiallyEmptyArray__2__text').fill(assertText3) - await page - .locator('#field-potentiallyEmptyArray__2__groupInRow__textInGroupInRow') - .fill(assertGroupText3) - - // Remove row 1 - await page.locator('#potentiallyEmptyArray-row-0 .array-actions__button').click() - await page - .locator('#potentiallyEmptyArray-row-0 .popup__scroll-container .array-actions__remove') - .click() - // Remove row 2 - await page.locator('#potentiallyEmptyArray-row-0 .array-actions__button').click() - await page - .locator('#potentiallyEmptyArray-row-0 .popup__scroll-container .array-actions__remove') - .click() - - // Save document - await saveDocAndAssert(page) - - // Scroll to array row (fields are not rendered in DOM until on screen) - await page.locator('#field-potentiallyEmptyArray__0__groupInRow').scrollIntoViewIfNeeded() - - // Expect the remaining row to be the third row - const input = page.locator('#field-potentiallyEmptyArray__0__groupInRow__textInGroupInRow') - await expect(input).toHaveValue(assertGroupText3) - - // Duplicate row - await page.locator('#potentiallyEmptyArray-row-0 .array-actions__button').click() - await page - .locator( - '#potentiallyEmptyArray-row-0 .popup__scroll-container .array-actions__duplicate', - ) - .click() - - // Update duplicated row group field text - await page - .locator('#field-potentiallyEmptyArray__1__groupInRow__textInGroupInRow') - .fill(`${assertGroupText3} duplicate`) - - // Save document - await saveDocAndAssert(page) - - // Expect the second row to be a duplicate of the remaining row - await expect( - page.locator('#field-potentiallyEmptyArray__1__groupInRow__textInGroupInRow'), - ).toHaveValue(`${assertGroupText3} duplicate`) - - // Remove row 1 - await page.locator('#potentiallyEmptyArray-row-0 .array-actions__button').click() - await page - .locator('#potentiallyEmptyArray-row-0 .popup__scroll-container .array-actions__remove') - .click() - - // Save document - await saveDocAndAssert(page) - - // Expect the remaining row to be the copy of the duplicate row - await expect( - page.locator('#field-potentiallyEmptyArray__0__groupInRow__textInGroupInRow'), - ).toHaveValue(`${assertGroupText3} duplicate`) - }) - }) - - // TODO: re-enable this test - test.skip('should bulk update', async () => { - await payload.create({ - collection: 'array-fields', - data: { - title: 'for test 1', - items: [ - { - text: 'test 1', - }, - { - text: 'test 2', - }, - ], - }, - }) - - await payload.create({ - collection: 'array-fields', - data: { - title: 'for test 2', - items: [ - { - text: 'test 3', - }, - ], - }, - }) - - await payload.create({ - collection: 'array-fields', - data: { - title: 'for test 3', - items: [ - { - text: 'test 4', - }, - { - text: 'test 5', - }, - { - text: 'test 6', - }, - ], - }, - }) - - const bulkText = 'Bulk update text' - await page.goto(url.list) - await page.waitForSelector('.table > table > tbody > tr td.cell-title') - const rows = page.locator('.table > table > tbody > tr', { - has: page.locator('td.cell-title a', { - hasText: 'for test', - }), - }) - const count = await rows.count() - - for (let i = 0; i < count; i++) { - await rows - .nth(i) - .locator('td.cell-_select .checkbox-input__input > input[type="checkbox"]') - .click() - } - await page.locator('.edit-many__toggle').click() - await page.locator('.field-select .rs__control').click() - - const arrayOption = page.locator('.rs__option', { - hasText: 'Items', - }) - - await expect(arrayOption).toBeVisible() - - await arrayOption.click() - await wait(200) - - const addRowButton = page.locator('#field-items > .array-field__add-row') - - await expect(addRowButton).toBeVisible() - - await addRowButton.click() - await wait(200) - - const targetInput = page.locator('#field-items__0__text') - - await expect(targetInput).toBeVisible() - - await targetInput.fill(bulkText) - - await page.locator('#edit-array-fields .form-submit .edit-many__save').click() - await expect(page.locator('.Toastify__toast--success')).toContainText( - 'Updated 3 Array Fields successfully.', - ) - }) - }) - describe('tabs', () => { let url: AdminUrlUtil beforeAll(() => {