Files
payloadcms/test/i18n/e2e.spec.ts
Jacob Fletcher 8a7124a15e fix(next): resolve filterOptions by path (#13779)
Follow up to #11375.

When setting `filterOptions` on relationship or upload fields _that are
nested within a named field_, those options won't be applied to the
`Filter` component in the list view.

This is because of how we key the results when resolving `filterOptions`
on the server. Instead of using the field path as expected, we were
using the field name, causing a failed lookup on the front-end. This
also solves an issue where two fields with the same name would override
each other's `filterOptions`, since field names alone are not unique.

Unrelated: this PR also does some general housekeeping to e2e test
helpers.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211332845301583
2025-09-11 13:24:16 -07:00

231 lines
7.4 KiB
TypeScript

import type { Page } from '@playwright/test'
import { expect, test } from '@playwright/test'
import type { Config } from './payload-types.js'
const { beforeAll, beforeEach, describe } = test
import path from 'path'
import { fileURLToPath } from 'url'
import type { PayloadTestSDK } from '../helpers/sdk/index.js'
import { ensureCompilationIsDone, initPageConsoleErrorCatch } from '../helpers.js'
import { AdminUrlUtil } from '../helpers/adminUrlUtil.js'
import { openListFilters } from '../helpers/e2e/filters/index.js'
import { initPayloadE2ENoConfig } from '../helpers/initPayloadE2ENoConfig.js'
import { reInitializeDB } from '../helpers/reInitializeDB.js'
import { TEST_TIMEOUT_LONG } from '../playwright.config.js'
let payload: PayloadTestSDK<Config>
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
describe('i18n', () => {
let page: Page
let serverURL: string
let collection1URL: AdminUrlUtil
beforeAll(async ({ browser }, testInfo) => {
const prebuild = false // Boolean(process.env.CI)
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
;({ payload, serverURL } = await initPayloadE2ENoConfig<Config>({
dirname,
prebuild,
}))
collection1URL = new AdminUrlUtil(serverURL, 'collection1')
const context = await browser.newContext()
page = await context.newPage()
initPageConsoleErrorCatch(page)
await ensureCompilationIsDone({ page, serverURL })
})
beforeEach(async () => {
await reInitializeDB({
serverURL,
snapshotKey: 'i18nTests',
})
await ensureCompilationIsDone({ page, serverURL })
})
async function setUserLanguage(language: 'de' | 'en' | 'es') {
{
const LanguageLabel = {
de: {
fieldLabel: 'Sprache',
valueLabel: 'Deutsch',
},
en: {
fieldLabel: 'Language',
valueLabel: 'English',
},
es: {
fieldLabel: 'Idioma',
valueLabel: 'Español',
},
}[language]
await page.goto(serverURL + '/admin/account')
await page.locator('.payload-settings__language .react-select').click()
await page.locator('.rs__option', { hasText: LanguageLabel.valueLabel }).click()
await expect(
page.locator('.payload-settings__language', { hasText: LanguageLabel.fieldLabel }),
).toBeVisible()
}
}
test('ensure i18n labels and useTranslation hooks display correct translation', async () => {
// set language to English
await setUserLanguage('en')
await page.goto(serverURL + '/admin')
await expect(
page.locator('.componentWithDefaultI18n .componentWithDefaultI18nValidT'),
).toHaveText('Add Link')
await expect(
page.locator('.componentWithDefaultI18n .componentWithDefaultI18nValidI18nT'),
).toHaveText('Add Link')
await expect(
page.locator('.componentWithDefaultI18n .componentWithDefaultI18nInvalidT'),
).toHaveText('fields:addLink2')
await expect(
page.locator('.componentWithDefaultI18n .componentWithDefaultI18nInvalidI18nT'),
).toHaveText('fields:addLink2')
await expect(
page.locator('.componentWithCustomI18n .componentWithCustomI18nDefaultValidT'),
).toHaveText('Add Link')
await expect(
page.locator('.componentWithCustomI18n .componentWithCustomI18nDefaultValidI18nT'),
).toHaveText('Add Link')
await expect(
page.locator('.componentWithCustomI18n .componentWithCustomI18nDefaultInvalidT'),
).toHaveText('fields:addLink2')
await expect(
page.locator('.componentWithCustomI18n .componentWithCustomI18nDefaultInvalidI18nT'),
).toHaveText('fields:addLink2')
await expect(
page.locator('.componentWithCustomI18n .componentWithCustomI18nCustomValidT'),
).toHaveText('My custom translation')
await expect(
page.locator('.componentWithCustomI18n .componentWithCustomI18nCustomValidI18nT'),
).toHaveText('My custom translation')
})
test('ensure translations update correctly when switching language', async () => {
// set language to English
await setUserLanguage('en')
await expect(page.locator('div.payload-settings h3')).toHaveText('Payload Settings')
await page.goto(serverURL + '/admin/collections/collection1/create')
await expect(page.locator('label[for="field-fieldDefaultI18nValid"]')).toHaveText(
'Add {{label}}',
)
// set language to Spanish
await setUserLanguage('es')
await expect(page.locator('div.payload-settings h3')).toHaveText('Configuración de Payload')
await page.goto(serverURL + '/admin/collections/collection1/create')
await expect(page.locator('label[for="field-fieldDefaultI18nValid"]')).toHaveText(
'Añadir {{label}}',
)
})
describe('i18n labels', () => {
test('should show translated document field label', async () => {
// set language to Spanish
await setUserLanguage('es')
await page.goto(collection1URL.create)
await expect(
page.locator('label[for="field-i18nFieldLabel"]', {
hasText: 'es-label',
}),
).toBeVisible()
})
test('should show translated pill field label', async () => {
// set language to Spanish
await setUserLanguage('es')
await page.goto(collection1URL.list)
await page.locator('.list-controls__toggle-columns').click()
// expecting the label to fall back to english as default fallbackLng
await expect(
page.locator('.pill-selector__pill', {
hasText: 'es-label',
}),
).toBeVisible()
})
test('should show fallback pill field label', async () => {
// set language to German
await setUserLanguage('de')
await page.goto(collection1URL.list)
await page.locator('.list-controls__toggle-columns').click()
// expecting the label to fall back to english as default fallbackLng
await expect(
page.locator('.pill-selector__pill', {
hasText: 'en-label',
}),
).toBeVisible()
})
test('should show translated field label in where builder', async () => {
await payload.create({
collection: 'collection1',
data: {
i18nFieldLabel: 'Test',
},
})
// set language to Spanish
await setUserLanguage('es')
await page.goto(collection1URL.list)
await openListFilters(page, {})
await page.locator('.where-builder__add-first-filter').click()
await page.locator('.condition__field .rs__control').click()
await expect(page.locator('.rs__option', { hasText: 'es-label' })).toBeVisible()
// expect heading to be translated
await expect(
page.locator('#heading-i18nFieldLabel .sort-column__label', { hasText: 'es-label' }),
).toBeVisible()
await expect(page.locator('.search-filter input')).toHaveAttribute(
'placeholder',
/(Buscar por ID)/,
)
})
test('should display translated collections and globals config options', async () => {
// set language to Spanish
await setUserLanguage('es')
await page.goto(collection1URL.list)
await expect(
page.locator('#nav-collection1', {
hasText: 'ES Collection 1s',
}),
).toBeVisible()
await expect(page.locator('#nav-global-global')).toContainText('ES Global')
})
})
})