## Description Currently, you cannot create, delete, or duplicate documents within the document drawer directly. To create a document within a relationship field, for example, you must first navigate to the parent field and open the "create new" drawer. Similarly (but worse), to duplicate or delete a document, you must _navigate to the parent document to perform these actions_ which is incredibly disruptive to the content editing workflow. This becomes especially apparent within the relationship field where you can edit documents inline, but cannot duplicate or delete them. This PR supports all document-level actions within the document drawer so that these actions can be performed on-the-fly without navigating away. Inline duplication flow on a polymorphic "hasOne" relationship: https://github.com/user-attachments/assets/bb80404a-079d-44a1-b9bc-14eb2ab49a46 Inline deletion flow on a polymorphic "hasOne" relationship: https://github.com/user-attachments/assets/10f3587f-f70a-4cca-83ee-5dbcad32f063 - [x] I have read and understand the [CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md) document in this repository. ## Type of change - [x] New feature (non-breaking change which adds functionality) ## Checklist: - [x] I have added tests that prove my fix is effective or that my feature works - [x] Existing test suite passes locally with my changes
262 lines
9.8 KiB
TypeScript
262 lines
9.8 KiB
TypeScript
import type { Page } from '@playwright/test'
|
|
|
|
import { expect, test } from '@playwright/test'
|
|
import { openDocControls } from 'helpers/e2e/openDocControls.js'
|
|
import path from 'path'
|
|
import { wait } from 'payload/shared'
|
|
import { fileURLToPath } from 'url'
|
|
|
|
import type { PayloadTestSDK } from '../helpers/sdk/index.js'
|
|
import type { Config, LocalizedPost } from './payload-types.js'
|
|
|
|
import {
|
|
changeLocale,
|
|
ensureCompilationIsDone,
|
|
initPageConsoleErrorCatch,
|
|
saveDocAndAssert,
|
|
} from '../helpers.js'
|
|
import { AdminUrlUtil } from '../helpers/adminUrlUtil.js'
|
|
import { initPayloadE2ENoConfig } from '../helpers/initPayloadE2ENoConfig.js'
|
|
import { POLL_TOPASS_TIMEOUT, TEST_TIMEOUT_LONG } from '../playwright.config.js'
|
|
import {
|
|
englishTitle,
|
|
localizedPostsSlug,
|
|
spanishLocale,
|
|
withRequiredLocalizedFields,
|
|
} from './shared.js'
|
|
const filename = fileURLToPath(import.meta.url)
|
|
const dirname = path.dirname(filename)
|
|
|
|
/**
|
|
* TODO: Localization
|
|
*
|
|
* Fieldtypes to test: (collections for each field type)
|
|
* - localized and non-localized: array, block, group, relationship, text
|
|
*
|
|
* Repeat above for Globals
|
|
*/
|
|
|
|
const { beforeAll, describe } = test
|
|
let url: AdminUrlUtil
|
|
let urlWithRequiredLocalizedFields: AdminUrlUtil
|
|
|
|
const defaultLocale = 'en'
|
|
const title = 'english title'
|
|
const spanishTitle = 'spanish title'
|
|
const arabicTitle = 'arabic title'
|
|
const description = 'description'
|
|
|
|
let page: Page
|
|
let payload: PayloadTestSDK<Config>
|
|
let serverURL: string
|
|
|
|
describe('Localization', () => {
|
|
beforeAll(async ({ browser }, testInfo) => {
|
|
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
|
;({ payload, serverURL } = await initPayloadE2ENoConfig({ dirname }))
|
|
|
|
url = new AdminUrlUtil(serverURL, localizedPostsSlug)
|
|
urlWithRequiredLocalizedFields = new AdminUrlUtil(serverURL, withRequiredLocalizedFields)
|
|
|
|
const context = await browser.newContext()
|
|
page = await context.newPage()
|
|
|
|
initPageConsoleErrorCatch(page)
|
|
|
|
await ensureCompilationIsDone({ page, serverURL })
|
|
})
|
|
|
|
describe('localized text', () => {
|
|
test('create english post, switch to spanish', async () => {
|
|
await page.goto(url.create)
|
|
|
|
await fillValues({ description, title })
|
|
await saveDocAndAssert(page)
|
|
|
|
// Change back to English
|
|
await changeLocale(page, 'es')
|
|
|
|
// Localized field should not be populated
|
|
await expect
|
|
.poll(() => page.locator('#field-title').inputValue(), {
|
|
timeout: 45000,
|
|
})
|
|
.not.toBe(title)
|
|
|
|
await expect(page.locator('#field-description')).toHaveValue(description)
|
|
|
|
await fillValues({ description, title: spanishTitle })
|
|
await saveDocAndAssert(page)
|
|
await changeLocale(page, defaultLocale)
|
|
|
|
// Expect english title
|
|
await expect(page.locator('#field-title')).toHaveValue(title)
|
|
await expect(page.locator('#field-description')).toHaveValue(description)
|
|
})
|
|
|
|
test('create spanish post, add english', async () => {
|
|
await page.goto(url.create)
|
|
|
|
const newLocale = 'es'
|
|
|
|
// Change to Spanish
|
|
await changeLocale(page, newLocale)
|
|
|
|
await fillValues({ description, title: spanishTitle })
|
|
await saveDocAndAssert(page)
|
|
|
|
// Change back to English
|
|
await changeLocale(page, defaultLocale)
|
|
|
|
// Localized field should not be populated
|
|
await expect(page.locator('#field-title')).toBeEmpty()
|
|
await expect(page.locator('#field-description')).toHaveValue(description)
|
|
|
|
// Add English
|
|
|
|
await fillValues({ description, title })
|
|
await saveDocAndAssert(page)
|
|
|
|
await expect(page.locator('#field-title')).toHaveValue(title)
|
|
await expect(page.locator('#field-description')).toHaveValue(description)
|
|
})
|
|
|
|
test('create arabic post, add english', async () => {
|
|
await page.goto(url.create)
|
|
const newLocale = 'ar'
|
|
await changeLocale(page, newLocale)
|
|
await fillValues({ description, title: arabicTitle })
|
|
await saveDocAndAssert(page)
|
|
await changeLocale(page, defaultLocale)
|
|
await expect(page.locator('#field-title')).toBeEmpty()
|
|
await expect(page.locator('#field-description')).toHaveValue(description)
|
|
await fillValues({ description, title })
|
|
await saveDocAndAssert(page)
|
|
await expect(page.locator('#field-title')).toHaveValue(title)
|
|
await expect(page.locator('#field-description')).toHaveValue(description)
|
|
})
|
|
})
|
|
|
|
describe('localized duplicate', () => {
|
|
test('should duplicate data for all locales', async () => {
|
|
const localizedPost = await payload.create({
|
|
collection: localizedPostsSlug,
|
|
data: {
|
|
localizedCheckbox: true,
|
|
title: englishTitle,
|
|
},
|
|
locale: defaultLocale,
|
|
})
|
|
|
|
const id = localizedPost.id.toString()
|
|
|
|
await payload.update({
|
|
id,
|
|
collection: localizedPostsSlug,
|
|
data: {
|
|
localizedCheckbox: false,
|
|
title: spanishTitle,
|
|
},
|
|
locale: spanishLocale,
|
|
})
|
|
|
|
await page.goto(url.edit(id))
|
|
await page.waitForURL(`**${url.edit(id)}`)
|
|
await openDocControls(page)
|
|
await page.locator('#action-duplicate').click()
|
|
await expect(page.locator('.payload-toast-container')).toContainText('successfully')
|
|
await expect.poll(() => page.url(), { timeout: POLL_TOPASS_TIMEOUT }).not.toContain(id)
|
|
await expect(page.locator('#field-title')).toHaveValue(englishTitle)
|
|
await changeLocale(page, spanishLocale)
|
|
await expect(page.locator('#field-title')).toBeEnabled()
|
|
await expect(page.locator('#field-title')).toHaveValue(spanishTitle)
|
|
await expect(page.locator('#field-localizedCheckbox')).toBeEnabled()
|
|
await page.reload() // TODO: remove this line, the checkbox _is not_ checked, but Playwright is unable to detect it without a reload for some reason
|
|
await expect(page.locator('#field-localizedCheckbox')).not.toBeChecked()
|
|
})
|
|
|
|
test('should duplicate localized checkbox correctly', async () => {
|
|
await page.goto(url.create)
|
|
await page.waitForURL(url.create)
|
|
await changeLocale(page, defaultLocale)
|
|
await fillValues({ description, title: englishTitle })
|
|
await expect(page.locator('#field-localizedCheckbox')).toBeEnabled()
|
|
await page.locator('#field-localizedCheckbox').click()
|
|
await page.locator('#action-save').click()
|
|
await expect.poll(() => page.url(), { timeout: POLL_TOPASS_TIMEOUT }).not.toContain('create')
|
|
const collectionUrl = page.url()
|
|
await changeLocale(page, spanishLocale)
|
|
await expect(page.locator('#field-localizedCheckbox')).toBeEnabled()
|
|
await page.reload() // TODO: remove this line, the checkbox _is not_ checked, but Playwright is unable to detect it without a reload for some reason
|
|
await expect(page.locator('#field-localizedCheckbox')).not.toBeChecked()
|
|
await changeLocale(page, defaultLocale)
|
|
await openDocControls(page)
|
|
await page.locator('#action-duplicate').click()
|
|
await expect
|
|
.poll(() => page.url(), { timeout: POLL_TOPASS_TIMEOUT })
|
|
.not.toContain(collectionUrl)
|
|
await changeLocale(page, spanishLocale)
|
|
await expect(page.locator('#field-localizedCheckbox')).toBeEnabled()
|
|
await page.reload() // TODO: remove this line, the checkbox _is not_ checked, but Playwright is unable to detect it without a reload for some reason
|
|
await expect(page.locator('#field-localizedCheckbox')).not.toBeChecked()
|
|
})
|
|
|
|
test('should duplicate even if missing some localized data', async () => {
|
|
await page.goto(urlWithRequiredLocalizedFields.create)
|
|
await changeLocale(page, defaultLocale)
|
|
await page.locator('#field-title').fill(englishTitle)
|
|
await page.locator('#field-layout .blocks-field__drawer-toggler').click()
|
|
await page.locator('button[title="Text"]').click()
|
|
await page.fill('#field-layout__0__text', 'test')
|
|
await expect(page.locator('#field-layout__0__text')).toHaveValue('test')
|
|
await saveDocAndAssert(page)
|
|
const originalID = await page.locator('.id-label').innerText()
|
|
await openDocControls(page)
|
|
await page.locator('#action-duplicate').click()
|
|
await expect(page.locator('.id-label')).not.toContainText(originalID)
|
|
await expect(page.locator('#field-title')).toHaveValue(englishTitle)
|
|
await expect(page.locator('.payload-toast-container')).toContainText(
|
|
'successfully duplicated',
|
|
)
|
|
await expect(page.locator('.id-label')).not.toContainText(originalID)
|
|
})
|
|
})
|
|
|
|
describe('locale preference', () => {
|
|
test('ensure preference is used when query param is not', async () => {
|
|
await page.goto(url.create)
|
|
await changeLocale(page, spanishLocale)
|
|
await expect(page.locator('#field-title')).toBeEmpty()
|
|
await fillValues({ title: spanishTitle })
|
|
await saveDocAndAssert(page)
|
|
await page.goto(url.admin)
|
|
await page.goto(url.list)
|
|
await expect(page.locator('.row-1 .cell-title')).toContainText(spanishTitle)
|
|
})
|
|
})
|
|
|
|
describe('localized relationships', () => {
|
|
test('ensure relationship field fetches are localised as well', async () => {
|
|
await page.goto(url.list)
|
|
await changeLocale(page, spanishLocale)
|
|
|
|
const localisedPost = page.locator('.cell-title a').first()
|
|
const localisedPostUrl = await localisedPost.getAttribute('href')
|
|
await page.goto(serverURL + localisedPostUrl)
|
|
await page.waitForURL(serverURL + localisedPostUrl)
|
|
|
|
const selectField = page.locator('#field-children .rs__control')
|
|
await selectField.click()
|
|
|
|
await expect(page.locator('#field-children .rs__menu')).toContainText('spanish-relation2')
|
|
})
|
|
})
|
|
})
|
|
|
|
async function fillValues(data: Partial<LocalizedPost>) {
|
|
const { description: descVal, title: titleVal } = data
|
|
|
|
if (titleVal) await page.locator('#field-title').fill(titleVal)
|
|
if (descVal) await page.locator('#field-description').fill(descVal)
|
|
}
|