Lexical tests comprise almost half of the collections in the fields suite, and are starting to become complex to manage. They are sometimes related to other auxiliary collections, so refactoring one test sometimes breaks another, seemingly unrelated one. In addition, the fields suite is very large, taking a long time to compile. This will make it faster. Some ideas for future refactorings: - 3 main collections: defaultFeatures, fully featured, and legacy. Legacy is the current one that has multiple editors and could later be migrated to the first two. - Avoid collections with more than 1 editor. - Create reseed buttons to restore the editor to certain states, to avoid a proliferation of collections and documents. - Reduce the complexity of the three auxiliary collections (text, array, upload), which are rarely or never used and have many fields designed for tests in the fields suite.
432 lines
16 KiB
TypeScript
432 lines
16 KiB
TypeScript
import type { Page } from '@playwright/test'
|
|
|
|
import { expect, test } from '@playwright/test'
|
|
import path from 'path'
|
|
import { wait } from 'payload/shared'
|
|
import { fileURLToPath } from 'url'
|
|
|
|
import {
|
|
ensureCompilationIsDone,
|
|
initPageConsoleErrorCatch,
|
|
saveDocAndAssert,
|
|
} from '../../../helpers.js'
|
|
import { AdminUrlUtil } from '../../../helpers/adminUrlUtil.js'
|
|
import { initPayloadE2ENoConfig } from '../../../helpers/initPayloadE2ENoConfig.js'
|
|
import { reInitializeDB } from '../../../helpers/reInitializeDB.js'
|
|
import { RESTClient } from '../../../helpers/rest.js'
|
|
import { POLL_TOPASS_TIMEOUT, TEST_TIMEOUT_LONG } from '../../../playwright.config.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('Rich Text', () => {
|
|
beforeAll(async ({ browser }, testInfo) => {
|
|
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
|
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<Config>({
|
|
dirname,
|
|
}))
|
|
|
|
const context = await browser.newContext()
|
|
page = await context.newPage()
|
|
initPageConsoleErrorCatch(page)
|
|
|
|
await ensureCompilationIsDone({ page, serverURL })
|
|
})
|
|
beforeEach(async () => {
|
|
await reInitializeDB({
|
|
serverURL,
|
|
snapshotKey: 'fieldsTest',
|
|
uploadsDir: path.resolve(dirname, './collections/Upload/uploads'),
|
|
})
|
|
|
|
if (client) {
|
|
await client.logout()
|
|
}
|
|
client = new RESTClient({ defaultSlug: 'users', serverURL })
|
|
await client.login()
|
|
|
|
await ensureCompilationIsDone({ page, serverURL })
|
|
})
|
|
|
|
async function navigateToRichTextFields() {
|
|
const url: AdminUrlUtil = new AdminUrlUtil(serverURL, 'rich-text-fields')
|
|
await page.goto(url.list)
|
|
|
|
const linkToDoc = page.locator('.row-1 .cell-title a').first()
|
|
await expect(() => expect(linkToDoc).toBeTruthy()).toPass({ timeout: POLL_TOPASS_TIMEOUT })
|
|
const linkDocHref = await linkToDoc.getAttribute('href')
|
|
|
|
await linkToDoc.click()
|
|
|
|
await page.waitForURL(`**${linkDocHref}`)
|
|
}
|
|
|
|
describe('cell', () => {
|
|
test('ensure cells are smaller than 300px in height', async () => {
|
|
const url: AdminUrlUtil = new AdminUrlUtil(serverURL, 'rich-text-fields')
|
|
await page.goto(url.list) // Navigate to rich-text list view
|
|
|
|
const table = page.locator('.list-controls ~ .table')
|
|
const lexicalCell = table.locator('.cell-lexicalCustomFields').first()
|
|
const lexicalHtmlCell = table.locator('.cell-lexicalCustomFields_html').first()
|
|
const entireRow = table.locator('.row-1').first()
|
|
|
|
// Make sure each of the 3 above are no larger than 300px in height:
|
|
await expect
|
|
.poll(async () => (await lexicalCell.boundingBox()).height, {
|
|
timeout: POLL_TOPASS_TIMEOUT,
|
|
})
|
|
.toBeLessThanOrEqual(300)
|
|
await expect
|
|
.poll(async () => (await lexicalHtmlCell.boundingBox()).height, {
|
|
timeout: POLL_TOPASS_TIMEOUT,
|
|
})
|
|
.toBeLessThanOrEqual(300)
|
|
await expect
|
|
.poll(async () => (await entireRow.boundingBox()).height, { timeout: POLL_TOPASS_TIMEOUT })
|
|
.toBeLessThanOrEqual(300)
|
|
})
|
|
})
|
|
|
|
describe('toolbar', () => {
|
|
test('should run url validation', async () => {
|
|
await navigateToRichTextFields()
|
|
|
|
// Open link drawer
|
|
await page.locator('.rich-text__toolbar button:not([disabled]) .link').first().click()
|
|
|
|
// find the drawer
|
|
const editLinkModal = page.locator('[id^=drawer_1_rich-text-link-]')
|
|
await expect(editLinkModal).toBeVisible()
|
|
|
|
// Fill values and click Confirm
|
|
await editLinkModal.locator('#field-text').fill('link text')
|
|
await editLinkModal.locator('label[for="field-linkType-custom-2"]').click()
|
|
await editLinkModal.locator('#field-url').fill('')
|
|
await wait(200)
|
|
await editLinkModal.locator('button[type="submit"]').click()
|
|
await wait(400)
|
|
const errorField = page.locator(
|
|
'[id^=drawer_1_rich-text-link-] .render-fields > :nth-child(3)',
|
|
)
|
|
const hasErrorClass = await errorField.evaluate((el) => el.classList.contains('error'))
|
|
expect(hasErrorClass).toBe(true)
|
|
})
|
|
|
|
// TODO: Flaky test flakes consistently in CI: https://github.com/payloadcms/payload/actions/runs/8913431889/job/24478995959?pr=6155
|
|
test.skip('should create new url custom link', async () => {
|
|
await navigateToRichTextFields()
|
|
|
|
// Open link drawer
|
|
await page.locator('.rich-text__toolbar button:not([disabled]) .link').first().click()
|
|
|
|
// find the drawer
|
|
const editLinkModal = page.locator('[id^=drawer_1_rich-text-link-]')
|
|
await expect(editLinkModal).toBeVisible()
|
|
|
|
await wait(400)
|
|
// Fill values and click Confirm
|
|
await editLinkModal.locator('#field-text').fill('link text')
|
|
await editLinkModal.locator('label[for="field-linkType-custom-2"]').click()
|
|
await editLinkModal.locator('#field-url').fill('https://payloadcms.com')
|
|
await editLinkModal.locator('button[type="submit"]').click()
|
|
await expect(editLinkModal).toBeHidden()
|
|
await wait(400)
|
|
await saveDocAndAssert(page)
|
|
|
|
// Remove link from editor body
|
|
await page.locator('span >> text="link text"').click()
|
|
const popup = page.locator('.popup--active .rich-text-link__popup')
|
|
await expect(popup.locator('.rich-text-link__link-label')).toBeVisible()
|
|
await popup.locator('.rich-text-link__link-close').click()
|
|
await expect(page.locator('span >> text="link text"')).toHaveCount(0)
|
|
})
|
|
|
|
// TODO: Flaky test flakes consistently in CI: https://github.com/payloadcms/payload/actions/runs/8913769794/job/24480056251?pr=6155
|
|
test.skip('should create new internal link', async () => {
|
|
await navigateToRichTextFields()
|
|
|
|
// Open link drawer
|
|
await page.locator('.rich-text__toolbar button:not([disabled]) .link').first().click()
|
|
|
|
// find the drawer
|
|
const editLinkModal = page.locator('[id^=drawer_1_rich-text-link-]')
|
|
await expect(editLinkModal).toBeVisible()
|
|
await wait(400)
|
|
|
|
// Fill values and click Confirm
|
|
await editLinkModal.locator('#field-text').fill('link text')
|
|
await editLinkModal.locator('label[for="field-linkType-internal-2"]').click()
|
|
await editLinkModal.locator('#field-doc .rs__control').click()
|
|
await page.keyboard.type('dev@')
|
|
await editLinkModal
|
|
.locator('#field-doc .rs__menu .rs__option:has-text("dev@payloadcms.com")')
|
|
.click()
|
|
// await wait(200);
|
|
await editLinkModal.locator('button[type="submit"]').click()
|
|
await saveDocAndAssert(page)
|
|
})
|
|
|
|
test('should not create new url link when read only', async () => {
|
|
await navigateToRichTextFields()
|
|
const modalTrigger = page.locator('.rich-text--read-only .rich-text__toolbar button .link')
|
|
await expect(modalTrigger).toBeDisabled()
|
|
})
|
|
|
|
// TODO: this test can't find the selector for the search filter, but functionality works.
|
|
// Need to debug
|
|
test.skip('should search correct useAsTitle field after toggling collection in list drawer', async () => {
|
|
await navigateToRichTextFields()
|
|
|
|
// open link drawer
|
|
const field = page.locator('#field-richText')
|
|
const button = field.locator(
|
|
'button.rich-text-relationship__list-drawer-toggler.list-drawer__toggler',
|
|
)
|
|
await button.click()
|
|
|
|
// check that the search is on the `name` field of the `text-fields` collection
|
|
const drawer = page.locator('[id^=list-drawer_1_]')
|
|
|
|
await expect(drawer.locator('.search-filter__input')).toHaveAttribute(
|
|
'placeholder',
|
|
'Search by Text',
|
|
)
|
|
|
|
// change the selected collection to `array-fields`
|
|
await page.locator('.list-drawer_select-collection-wrap .rs__control').click()
|
|
const menu = page.locator('.list-drawer__select-collection-wrap .rs__menu')
|
|
await menu.locator('.rs__option').getByText('Array Field').click()
|
|
|
|
// check that `id` is now the default search field
|
|
await expect(drawer.locator('.search-filter__input')).toHaveAttribute(
|
|
'placeholder',
|
|
'Search by ID',
|
|
)
|
|
})
|
|
|
|
test('should only list RTE enabled collections in link drawer', async () => {
|
|
await navigateToRichTextFields()
|
|
await wait(1000)
|
|
|
|
await page.locator('.rich-text__toolbar button:not([disabled]) .link').first().click()
|
|
|
|
const editLinkModal = page.locator('[id^=drawer_1_rich-text-link-]')
|
|
await expect(editLinkModal).toBeVisible()
|
|
|
|
await wait(1000)
|
|
|
|
await editLinkModal.locator('label[for="field-linkType-internal-2"]').click()
|
|
await editLinkModal.locator('.relationship__wrap .rs__control').click()
|
|
|
|
const menu = page.locator('.relationship__wrap .rs__menu')
|
|
|
|
// array-fields has enableRichTextLink set to false
|
|
await expect(menu).not.toContainText('Array Fields')
|
|
})
|
|
|
|
test('should only list non-upload collections in relationship drawer', async () => {
|
|
await navigateToRichTextFields()
|
|
await wait(1000)
|
|
|
|
// Open link drawer
|
|
await page
|
|
.locator('.rich-text__toolbar button:not([disabled]) .relationship-rich-text-button')
|
|
.first()
|
|
.click()
|
|
|
|
await wait(1000)
|
|
|
|
// open the list select menu
|
|
await page.locator('.list-drawer__select-collection-wrap .rs__control').click()
|
|
|
|
const menu = page.locator('.list-drawer__select-collection-wrap .rs__menu')
|
|
const regex = /\bUploads\b/
|
|
await expect(menu).not.toContainText(regex)
|
|
})
|
|
|
|
// TODO: Flaky test in CI. Flake: https://github.com/payloadcms/payload/actions/runs/8914532814/job/24482407114
|
|
test.skip('should respect customizing the default fields', async () => {
|
|
const linkText = 'link'
|
|
const value = 'test value'
|
|
await navigateToRichTextFields()
|
|
await wait(1000)
|
|
|
|
const field = page.locator('.rich-text', {
|
|
has: page.locator('#field-richTextCustomFields'),
|
|
})
|
|
// open link drawer
|
|
const button = field.locator('button.rich-text__button.link')
|
|
await button.click()
|
|
await wait(1000)
|
|
|
|
// fill link fields
|
|
const linkDrawer = page.locator('[id^=drawer_1_rich-text-link-]')
|
|
const fields = linkDrawer.locator('.render-fields > .field-type')
|
|
await fields.locator('#field-text').fill(linkText)
|
|
await fields.locator('#field-url').fill('https://payloadcms.com')
|
|
const input = fields.locator('#field-fields__customLinkField')
|
|
await input.fill(value)
|
|
|
|
await wait(1000)
|
|
|
|
// submit link closing drawer
|
|
await linkDrawer.locator('button[type="submit"]').click()
|
|
const linkInEditor = field.locator(`.rich-text-link >> text="${linkText}"`)
|
|
await wait(300)
|
|
|
|
await saveDocAndAssert(page)
|
|
|
|
// open modal again
|
|
await linkInEditor.click()
|
|
|
|
const popup = page.locator('.popup--active .rich-text-link__popup')
|
|
await expect(popup).toBeVisible()
|
|
|
|
await popup.locator('.rich-text-link__link-edit').click()
|
|
|
|
const linkDrawer2 = page.locator('[id^=drawer_1_rich-text-link-]')
|
|
const fields2 = linkDrawer2.locator('.render-fields > .field-type')
|
|
const input2 = fields2.locator('#field-fields__customLinkField')
|
|
|
|
await expect(input2).toHaveValue(value)
|
|
})
|
|
})
|
|
|
|
describe('editor', () => {
|
|
test('should populate url link', async () => {
|
|
await navigateToRichTextFields()
|
|
await wait(500)
|
|
|
|
// Open link popup
|
|
await page.locator('#field-richText span >> text="render links"').click()
|
|
const popup = page.locator('.popup--active .rich-text-link__popup')
|
|
await expect(popup).toBeVisible()
|
|
await expect(popup.locator('a')).toHaveAttribute('href', 'https://payloadcms.com')
|
|
|
|
// Open the drawer
|
|
await popup.locator('.rich-text-link__link-edit').click()
|
|
const editLinkModal = page.locator('[id^=drawer_1_rich-text-link-]')
|
|
await expect(editLinkModal).toBeVisible()
|
|
|
|
// Check the drawer values
|
|
const textField = editLinkModal.locator('#field-text')
|
|
await expect(textField).toHaveValue('render links')
|
|
|
|
await wait(1000)
|
|
// Close the drawer
|
|
await editLinkModal.locator('button[type="submit"]').click()
|
|
await expect(editLinkModal).toBeHidden()
|
|
})
|
|
|
|
test('should populate relationship link', async () => {
|
|
await navigateToRichTextFields()
|
|
|
|
// Open link popup
|
|
await page.locator('#field-richText span >> text="link to relationships"').click()
|
|
const popup = page.locator('.popup--active .rich-text-link__popup')
|
|
await expect(popup).toBeVisible()
|
|
await expect(popup.locator('a')).toHaveAttribute(
|
|
'href',
|
|
/\/admin\/collections\/array-fields\/.*/,
|
|
)
|
|
|
|
// Open the drawer
|
|
await popup.locator('.rich-text-link__link-edit').click()
|
|
const editLinkModal = page.locator('[id^=drawer_1_rich-text-link-]')
|
|
await expect(editLinkModal).toBeVisible()
|
|
|
|
// Check the drawer values
|
|
const textField = editLinkModal.locator('#field-text')
|
|
await expect(textField).toHaveValue('link to relationships')
|
|
})
|
|
|
|
test('should open upload drawer and render custom relationship fields', async () => {
|
|
await navigateToRichTextFields()
|
|
const field = page.locator('#field-richText')
|
|
const button = field.locator('button.rich-text-upload__upload-drawer-toggler')
|
|
|
|
await button.click()
|
|
|
|
const documentDrawer = page.locator('[id^=drawer_1_upload-drawer-]')
|
|
await expect(documentDrawer).toBeVisible()
|
|
const caption = documentDrawer.locator('#field-caption')
|
|
await expect(caption).toBeVisible()
|
|
})
|
|
|
|
test('should open upload document drawer from read-only field', async () => {
|
|
await navigateToRichTextFields()
|
|
const field = page.locator('#field-richTextReadOnly')
|
|
const button = field.locator(
|
|
'button.rich-text-upload__doc-drawer-toggler.doc-drawer__toggler',
|
|
)
|
|
|
|
await button.click()
|
|
|
|
const documentDrawer = page.locator('[id^=doc-drawer_uploads_1_]')
|
|
await expect(documentDrawer).toBeVisible()
|
|
})
|
|
|
|
test('should open relationship document drawer from read-only field', async () => {
|
|
await navigateToRichTextFields()
|
|
const field = page.locator('#field-richTextReadOnly')
|
|
const button = field.locator(
|
|
'button.rich-text-relationship__doc-drawer-toggler.doc-drawer__toggler',
|
|
)
|
|
|
|
await button.click()
|
|
|
|
const documentDrawer = page.locator('[id^=doc-drawer_text-fields_1_]')
|
|
await expect(documentDrawer).toBeVisible()
|
|
})
|
|
|
|
test('should populate new links', async () => {
|
|
await navigateToRichTextFields()
|
|
await wait(1000)
|
|
|
|
// Highlight existing text
|
|
const headingElement = page.locator(
|
|
'#field-richText h1 >> text="Hello, I\'m a rich text field."',
|
|
)
|
|
await headingElement.selectText()
|
|
|
|
await wait(500)
|
|
|
|
// click the toolbar link button
|
|
await page.locator('.rich-text__toolbar button:not([disabled]) .link').first().click()
|
|
|
|
// find the drawer and confirm the values
|
|
const editLinkModal = page.locator('[id^=drawer_1_rich-text-link-]')
|
|
await expect(editLinkModal).toBeVisible()
|
|
const textField = editLinkModal.locator('#field-text')
|
|
await expect(textField).toHaveValue("Hello, I'm a rich text field.")
|
|
})
|
|
test('should not take value from previous block', async () => {
|
|
await navigateToRichTextFields()
|
|
await page.locator('#field-blocks').scrollIntoViewIfNeeded()
|
|
await expect(page.locator('#field-blocks__0__text')).toBeVisible()
|
|
await expect(page.locator('#field-blocks__0__text')).toHaveValue('Regular text')
|
|
await wait(500)
|
|
const editBlock = page.locator('#blocks-row-0 .popup-button')
|
|
await editBlock.click()
|
|
const removeButton = page.locator('#blocks-row-0').getByRole('button', { name: 'Remove' })
|
|
await expect(removeButton).toBeVisible()
|
|
await wait(500)
|
|
await removeButton.click()
|
|
const richTextField = page.locator('#field-blocks__0__text')
|
|
await expect(richTextField).toBeVisible()
|
|
const richTextValue = await richTextField.innerText()
|
|
expect(richTextValue).toContain('Rich text')
|
|
})
|
|
})
|
|
})
|