test: move i18n list tests to i18n suite (#13143)

This commit is contained in:
Jarrod Flesch
2025-07-12 07:03:26 -04:00
committed by GitHub
parent 53ad02a8e8
commit a57faebfd7
4 changed files with 199 additions and 58 deletions

View File

@@ -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', () => { describe('placeholder', () => {
test('should display placeholder in filter options', async () => { test('should display placeholder in filter options', async () => {
await page.goto( await page.goto(

View File

@@ -45,7 +45,25 @@ export default buildConfigWithDefaults({
collections: [ collections: [
{ {
slug: 'collection1', slug: 'collection1',
labels: {
singular: {
en: 'EN Collection 1',
es: 'ES Collection 1',
},
plural: {
en: 'EN Collection 1s',
es: 'ES Collection 1s',
},
},
fields: [ fields: [
{
name: 'i18nFieldLabel',
type: 'text',
label: {
en: 'en-label',
es: 'es-label',
},
},
{ {
name: 'fieldDefaultI18nValid', name: 'fieldDefaultI18nValid',
type: 'text', 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: { i18n: {
translations: customTranslationsObject, translations: customTranslationsObject,
}, },

View File

@@ -9,10 +9,17 @@ const { beforeAll, beforeEach, describe } = test
import path from 'path' import path from 'path'
import { fileURLToPath } from 'url' import { fileURLToPath } from 'url'
import type { PayloadTestSDK } from '../helpers/sdk/index.js'
import { ensureCompilationIsDone, initPageConsoleErrorCatch } from '../helpers.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 { initPayloadE2ENoConfig } from '../helpers/initPayloadE2ENoConfig.js'
import { reInitializeDB } from '../helpers/reInitializeDB.js' import { reInitializeDB } from '../helpers/reInitializeDB.js'
import { TEST_TIMEOUT_LONG } from '../playwright.config.js' import { TEST_TIMEOUT_LONG } from '../playwright.config.js'
let payload: PayloadTestSDK<Config>
const filename = fileURLToPath(import.meta.url) const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename) const dirname = path.dirname(filename)
@@ -20,6 +27,7 @@ describe('i18n', () => {
let page: Page let page: Page
let serverURL: string let serverURL: string
let collection1URL: AdminUrlUtil
beforeAll(async ({ browser }, testInfo) => { beforeAll(async ({ browser }, testInfo) => {
const prebuild = false // Boolean(process.env.CI) const prebuild = false // Boolean(process.env.CI)
@@ -27,11 +35,13 @@ describe('i18n', () => {
testInfo.setTimeout(TEST_TIMEOUT_LONG) 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 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>({ ;({ payload, serverURL } = await initPayloadE2ENoConfig<Config>({
dirname, dirname,
prebuild, prebuild,
})) }))
collection1URL = new AdminUrlUtil(serverURL, 'collection1')
const context = await browser.newContext() const context = await browser.newContext()
page = await context.newPage() page = await context.newPage()
initPageConsoleErrorCatch(page) initPageConsoleErrorCatch(page)
@@ -47,7 +57,35 @@ describe('i18n', () => {
await ensureCompilationIsDone({ page, serverURL }) 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 () => { test('ensure i18n labels and useTranslation hooks display correct translation', async () => {
// set language to English
await setUserLanguage('en')
await page.goto(serverURL + '/admin') await page.goto(serverURL + '/admin')
await expect( await expect(
@@ -84,10 +122,9 @@ describe('i18n', () => {
}) })
test('ensure translations update correctly when switching language', async () => { 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 expect(page.locator('div.payload-settings h3')).toHaveText('Payload Settings')
await page.goto(serverURL + '/admin/collections/collection1/create') await page.goto(serverURL + '/admin/collections/collection1/create')
@@ -95,9 +132,8 @@ describe('i18n', () => {
'Add {{label}}', 'Add {{label}}',
) )
await page.goto(serverURL + '/admin/account') // set language to Spanish
await page.locator('div.rs__control').click() await setUserLanguage('es')
await page.locator('div.rs__option').filter({ hasText: 'Español' }).click()
await expect(page.locator('div.payload-settings h3')).toHaveText('Configuración de Payload') await expect(page.locator('div.payload-settings h3')).toHaveText('Configuración de Payload')
await page.goto(serverURL + '/admin/collections/collection1/create') await page.goto(serverURL + '/admin/collections/collection1/create')
@@ -105,4 +141,90 @@ describe('i18n', () => {
'Añadir {{label}}', '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')
})
})
}) })

View File

@@ -84,8 +84,12 @@ export interface Config {
db: { db: {
defaultIDType: string; defaultIDType: string;
}; };
globals: {}; globals: {
globalsSelect: {}; global: Global;
};
globalsSelect: {
global: GlobalSelect<false> | GlobalSelect<true>;
};
locale: null; locale: null;
user: User & { user: User & {
collection: 'users'; collection: 'users';
@@ -119,6 +123,7 @@ export interface UserAuthOperations {
*/ */
export interface Collection1 { export interface Collection1 {
id: string; id: string;
i18nFieldLabel?: string | null;
fieldDefaultI18nValid?: string | null; fieldDefaultI18nValid?: string | null;
fieldDefaultI18nInvalid?: string | null; fieldDefaultI18nInvalid?: string | null;
fieldCustomI18nValidDefault?: string | null; fieldCustomI18nValidDefault?: string | null;
@@ -142,6 +147,13 @@ export interface User {
hash?: string | null; hash?: string | null;
loginAttempts?: number | null; loginAttempts?: number | null;
lockUntil?: string | null; lockUntil?: string | null;
sessions?:
| {
id: string;
createdAt?: string | null;
expiresAt: string;
}[]
| null;
password?: string | null; password?: string | null;
} }
/** /**
@@ -206,6 +218,7 @@ export interface PayloadMigration {
* via the `definition` "collection1_select". * via the `definition` "collection1_select".
*/ */
export interface Collection1Select<T extends boolean = true> { export interface Collection1Select<T extends boolean = true> {
i18nFieldLabel?: T;
fieldDefaultI18nValid?: T; fieldDefaultI18nValid?: T;
fieldDefaultI18nInvalid?: T; fieldDefaultI18nInvalid?: T;
fieldCustomI18nValidDefault?: T; fieldCustomI18nValidDefault?: T;
@@ -228,6 +241,13 @@ export interface UsersSelect<T extends boolean = true> {
hash?: T; hash?: T;
loginAttempts?: T; loginAttempts?: T;
lockUntil?: T; lockUntil?: T;
sessions?:
| T
| {
id?: T;
createdAt?: T;
expiresAt?: T;
};
} }
/** /**
* This interface was referenced by `Config`'s JSON-Schema * This interface was referenced by `Config`'s JSON-Schema
@@ -261,6 +281,26 @@ export interface PayloadMigrationsSelect<T extends boolean = true> {
updatedAt?: T; updatedAt?: T;
createdAt?: 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<T extends boolean = true> {
text?: T;
updatedAt?: T;
createdAt?: T;
globalType?: T;
}
/** /**
* This interface was referenced by `Config`'s JSON-Schema * This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "auth". * via the `definition` "auth".