From a57faebfd768a17d636a91a4f21f7795857b9409 Mon Sep 17 00:00:00 2001 From: Jarrod Flesch <30633324+JarrodMFlesch@users.noreply.github.com> Date: Sat, 12 Jul 2025 07:03:26 -0400 Subject: [PATCH] test: move i18n list tests to i18n suite (#13143) --- test/admin/e2e/list-view/e2e.spec.ts | 49 ---------- test/i18n/config.ts | 28 ++++++ test/i18n/e2e.spec.ts | 136 +++++++++++++++++++++++++-- test/i18n/payload-types.ts | 44 ++++++++- 4 files changed, 199 insertions(+), 58 deletions(-) diff --git a/test/admin/e2e/list-view/e2e.spec.ts b/test/admin/e2e/list-view/e2e.spec.ts index 4e0dd5b0a..bff63dd6f 100644 --- a/test/admin/e2e/list-view/e2e.spec.ts +++ b/test/admin/e2e/list-view/e2e.spec.ts @@ -1590,55 +1590,6 @@ describe('List View', () => { }) }) - describe('i18n', () => { - test('should display translated collections and globals config options', async () => { - await page.goto(postsUrl.list) - await expect(page.locator('#nav-posts')).toContainText('Posts') - await expect(page.locator('#nav-global-global')).toContainText('Global') - }) - - test('should display translated field titles', async () => { - await createPost() - - await page.locator('.list-controls__toggle-columns').click() - - await expect( - page.locator('.pill-selector__pill', { - hasText: exactText('Title'), - }), - ).toHaveText('Title') - - await openListFilters(page, {}) - - await page.locator('.where-builder__add-first-filter').click() - await page.locator('.condition__field .rs__control').click() - const options = page.locator('.rs__option') - - await expect(options.locator('text=Tab 1 > Title')).toHaveText('Tab 1 > Title') - - await expect(page.locator('#heading-title .sort-column__label')).toHaveText('Title') - await expect(page.locator('.search-filter input')).toHaveAttribute('placeholder', /(Title)/) - }) - - test('should use fallback language on field titles', async () => { - // change language German - await page.goto(postsUrl.account) - await page.locator('.payload-settings__language .react-select').click() - const languageSelect = page.locator('.rs__option') - // text field does not have a 'de' label - await languageSelect.locator('text=Deutsch').click() - - await page.goto(postsUrl.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: exactText('Title'), - }), - ).toHaveText('Title') - }) - }) - describe('placeholder', () => { test('should display placeholder in filter options', async () => { await page.goto( diff --git a/test/i18n/config.ts b/test/i18n/config.ts index 5fe44fbfa..730316f9d 100644 --- a/test/i18n/config.ts +++ b/test/i18n/config.ts @@ -45,7 +45,25 @@ export default buildConfigWithDefaults({ collections: [ { slug: 'collection1', + labels: { + singular: { + en: 'EN Collection 1', + es: 'ES Collection 1', + }, + plural: { + en: 'EN Collection 1s', + es: 'ES Collection 1s', + }, + }, fields: [ + { + name: 'i18nFieldLabel', + type: 'text', + label: { + en: 'en-label', + es: 'es-label', + }, + }, { name: 'fieldDefaultI18nValid', type: 'text', @@ -79,6 +97,16 @@ export default buildConfigWithDefaults({ ], }, ], + globals: [ + { + slug: 'global', + label: { + en: 'EN Global', + es: 'ES Global', + }, + fields: [{ name: 'text', type: 'text' }], + }, + ], i18n: { translations: customTranslationsObject, }, diff --git a/test/i18n/e2e.spec.ts b/test/i18n/e2e.spec.ts index c555c008b..f93bfb245 100644 --- a/test/i18n/e2e.spec.ts +++ b/test/i18n/e2e.spec.ts @@ -9,10 +9,17 @@ 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/openListFilters.js' import { initPayloadE2ENoConfig } from '../helpers/initPayloadE2ENoConfig.js' import { reInitializeDB } from '../helpers/reInitializeDB.js' import { TEST_TIMEOUT_LONG } from '../playwright.config.js' + +let payload: PayloadTestSDK + const filename = fileURLToPath(import.meta.url) const dirname = path.dirname(filename) @@ -20,6 +27,7 @@ describe('i18n', () => { let page: Page let serverURL: string + let collection1URL: AdminUrlUtil beforeAll(async ({ browser }, testInfo) => { const prebuild = false // Boolean(process.env.CI) @@ -27,11 +35,13 @@ describe('i18n', () => { 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({ + ;({ payload, serverURL } = await initPayloadE2ENoConfig({ dirname, prebuild, })) + collection1URL = new AdminUrlUtil(serverURL, 'collection1') + const context = await browser.newContext() page = await context.newPage() initPageConsoleErrorCatch(page) @@ -47,7 +57,35 @@ describe('i18n', () => { 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( @@ -84,10 +122,9 @@ describe('i18n', () => { }) test('ensure translations update correctly when switching language', async () => { - await page.goto(serverURL + '/admin/account') + // set language to English + await setUserLanguage('en') - await page.locator('div.rs__control').click() - await page.locator('div.rs__option').filter({ hasText: 'English' }).click() await expect(page.locator('div.payload-settings h3')).toHaveText('Payload Settings') await page.goto(serverURL + '/admin/collections/collection1/create') @@ -95,9 +132,8 @@ describe('i18n', () => { 'Add {{label}}', ) - await page.goto(serverURL + '/admin/account') - await page.locator('div.rs__control').click() - await page.locator('div.rs__option').filter({ hasText: 'Español' }).click() + // 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') @@ -105,4 +141,90 @@ describe('i18n', () => { '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') + }) + }) }) diff --git a/test/i18n/payload-types.ts b/test/i18n/payload-types.ts index f1a91954e..4210b4ce0 100644 --- a/test/i18n/payload-types.ts +++ b/test/i18n/payload-types.ts @@ -84,8 +84,12 @@ export interface Config { db: { defaultIDType: string; }; - globals: {}; - globalsSelect: {}; + globals: { + global: Global; + }; + globalsSelect: { + global: GlobalSelect | GlobalSelect; + }; locale: null; user: User & { collection: 'users'; @@ -119,6 +123,7 @@ export interface UserAuthOperations { */ export interface Collection1 { id: string; + i18nFieldLabel?: string | null; fieldDefaultI18nValid?: string | null; fieldDefaultI18nInvalid?: string | null; fieldCustomI18nValidDefault?: string | null; @@ -142,6 +147,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; } /** @@ -206,6 +218,7 @@ export interface PayloadMigration { * via the `definition` "collection1_select". */ export interface Collection1Select { + i18nFieldLabel?: T; fieldDefaultI18nValid?: T; fieldDefaultI18nInvalid?: T; fieldCustomI18nValidDefault?: T; @@ -228,6 +241,13 @@ export interface UsersSelect { hash?: T; loginAttempts?: T; lockUntil?: T; + sessions?: + | T + | { + id?: T; + createdAt?: T; + expiresAt?: T; + }; } /** * This interface was referenced by `Config`'s JSON-Schema @@ -261,6 +281,26 @@ export interface PayloadMigrationsSelect { updatedAt?: T; createdAt?: T; } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "global". + */ +export interface Global { + id: string; + text?: string | null; + updatedAt?: string | null; + createdAt?: string | null; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "global_select". + */ +export interface GlobalSelect { + text?: T; + updatedAt?: T; + createdAt?: T; + globalType?: T; +} /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "auth".