Internationalisation: Add basic e2e test to ensure languages load correctly (#96556)

* fix pseudo locale and add simple e2e test

* add comment

* update test and add comments

* fix unit test

* try 5 instead of 6?

* clear input

* only check pseudo in dev mode

* don't check pseudo in CI

* don't test pseudo locale

* isolate language changes

* move waits around

* only wait for preferences on first visit

* add proper loading state

* bit of tidy up

* hook up ids correctly

* use disabled/isLoading instead of skeleton

* set props on Field
This commit is contained in:
Ashley Harrison 2024-11-19 15:07:14 +00:00 committed by GitHub
parent 32de1a00d3
commit b662879c98
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 90 additions and 6 deletions

View File

@ -0,0 +1,65 @@
import { e2e } from '../utils';
import { fromBaseUrl } from '../utils/support/url';
describe('Verify i18n', () => {
const I18N_USER = 'i18n-test';
const I18N_PASSWORD = 'i18n-test';
// create a new user to isolate the language changes from other tests
before(() => {
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'));
cy.request({
method: 'POST',
url: fromBaseUrl('/api/admin/users'),
body: {
email: I18N_USER,
login: I18N_USER,
name: I18N_USER,
password: I18N_PASSWORD,
},
}).then((response) => {
cy.wrap(response.body.uid).as('uid');
});
});
// remove the user created in the before hook
after(() => {
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'));
cy.get('@uid').then((uid) => {
cy.request({
method: 'DELETE',
url: fromBaseUrl(`/api/admin/users/${uid}`),
});
});
});
beforeEach(() => {
e2e.flows.login(I18N_USER, I18N_PASSWORD);
});
// map between languages in the language picker and the corresponding translation of the 'Language' label
const languageMap: Record<string, string> = {
Deutsch: 'Sprache',
English: 'Language',
Español: 'Idioma',
Français: 'Langue',
'Português Brasileiro': 'Idioma',
'中文(简体)': '语言',
};
// basic test which loops through the defined languages in the picker
// and verifies that the corresponding label is translated correctly
it('loads all the languages correctly', () => {
cy.visit('/profile');
const LANGUAGE_SELECTOR = '[id="locale-select"]';
cy.wrap(Object.entries(languageMap)).each(([language, label]: [string, string]) => {
cy.get(LANGUAGE_SELECTOR).should('not.be.disabled');
cy.get(LANGUAGE_SELECTOR).click();
cy.get(LANGUAGE_SELECTOR).clear().type(language).type('{downArrow}{enter}');
e2e.components.UserProfile.preferencesSaveButton().click();
cy.contains('label', label).should('be.visible');
cy.get(LANGUAGE_SELECTOR).should('have.value', language);
});
});
});

View File

@ -31,7 +31,9 @@ export interface Props {
onConfirm?: () => Promise<boolean>; onConfirm?: () => Promise<boolean>;
} }
export type State = UserPreferencesDTO; export type State = UserPreferencesDTO & {
isLoading: boolean;
};
function getLanguageOptions(): ComboboxOption[] { function getLanguageOptions(): ComboboxOption[] {
const languageOptions = LANGUAGES.map((v) => ({ const languageOptions = LANGUAGES.map((v) => ({
@ -69,6 +71,7 @@ export class SharedPreferences extends PureComponent<Props, State> {
this.service = new PreferencesService(props.resourceUri); this.service = new PreferencesService(props.resourceUri);
this.state = { this.state = {
isLoading: false,
theme: '', theme: '',
timezone: '', timezone: '',
weekStart: '', weekStart: '',
@ -87,9 +90,13 @@ export class SharedPreferences extends PureComponent<Props, State> {
} }
async componentDidMount() { async componentDidMount() {
this.setState({
isLoading: true,
});
const prefs = await this.service.load(); const prefs = await this.service.load();
this.setState({ this.setState({
isLoading: false,
homeDashboardUID: prefs.homeDashboardUID, homeDashboardUID: prefs.homeDashboardUID,
theme: prefs.theme, theme: prefs.theme,
timezone: prefs.timezone, timezone: prefs.timezone,
@ -144,7 +151,7 @@ export class SharedPreferences extends PureComponent<Props, State> {
}; };
render() { render() {
const { theme, timezone, weekStart, homeDashboardUID, language } = this.state; const { theme, timezone, weekStart, homeDashboardUID, language, isLoading } = this.state;
const { disabled } = this.props; const { disabled } = this.props;
const styles = getStyles(); const styles = getStyles();
const languages = getLanguageOptions(); const languages = getLanguageOptions();
@ -153,7 +160,11 @@ export class SharedPreferences extends PureComponent<Props, State> {
return ( return (
<form onSubmit={this.onSubmitForm} className={styles.form}> <form onSubmit={this.onSubmitForm} className={styles.form}>
<FieldSet label={<Trans i18nKey="shared-preferences.title">Preferences</Trans>} disabled={disabled}> <FieldSet label={<Trans i18nKey="shared-preferences.title">Preferences</Trans>} disabled={disabled}>
<Field label={t('shared-preferences.fields.theme-label', 'Interface theme')}> <Field
loading={isLoading}
disabled={isLoading}
label={t('shared-preferences.fields.theme-label', 'Interface theme')}
>
<Combobox <Combobox
options={this.themeOptions} options={this.themeOptions}
value={currentThemeOption.value} value={currentThemeOption.value}
@ -163,6 +174,8 @@ export class SharedPreferences extends PureComponent<Props, State> {
</Field> </Field>
<Field <Field
loading={isLoading}
disabled={isLoading}
label={ label={
<Label htmlFor="home-dashboard-select"> <Label htmlFor="home-dashboard-select">
<span className={styles.labelText}> <span className={styles.labelText}>
@ -183,6 +196,8 @@ export class SharedPreferences extends PureComponent<Props, State> {
</Field> </Field>
<Field <Field
loading={isLoading}
disabled={isLoading}
label={t('shared-dashboard.fields.timezone-label', 'Timezone')} label={t('shared-dashboard.fields.timezone-label', 'Timezone')}
data-testid={selectors.components.TimeZonePicker.containerV2} data-testid={selectors.components.TimeZonePicker.containerV2}
> >
@ -195,17 +210,21 @@ export class SharedPreferences extends PureComponent<Props, State> {
</Field> </Field>
<Field <Field
loading={isLoading}
disabled={isLoading}
label={t('shared-preferences.fields.week-start-label', 'Week start')} label={t('shared-preferences.fields.week-start-label', 'Week start')}
data-testid={selectors.components.WeekStartPicker.containerV2} data-testid={selectors.components.WeekStartPicker.containerV2}
> >
<WeekStartPicker <WeekStartPicker
value={weekStart || ''} value={weekStart || ''}
onChange={this.onWeekStartChanged} onChange={this.onWeekStartChanged}
inputId={'shared-preferences-week-start-picker'} inputId="shared-preferences-week-start-picker"
/> />
</Field> </Field>
<Field <Field
loading={isLoading}
disabled={isLoading}
label={ label={
<Label htmlFor="locale-select"> <Label htmlFor="locale-select">
<span className={styles.labelText}> <span className={styles.labelText}>

View File

@ -21,7 +21,7 @@ describe('internationalization constants', () => {
expect(GERMAN_GERMANY).toBe('de-DE'); expect(GERMAN_GERMANY).toBe('de-DE');
expect(BRAZILIAN_PORTUGUESE).toBe('pt-BR'); expect(BRAZILIAN_PORTUGUESE).toBe('pt-BR');
expect(CHINESE_SIMPLIFIED).toBe('zh-Hans'); expect(CHINESE_SIMPLIFIED).toBe('zh-Hans');
expect(PSEUDO_LOCALE).toBe('pseudo-LOCALE'); expect(PSEUDO_LOCALE).toBe('pseudo');
expect(DEFAULT_LANGUAGE).toBe(ENGLISH_US); expect(DEFAULT_LANGUAGE).toBe(ENGLISH_US);
}); });

View File

@ -7,7 +7,7 @@ export const SPANISH_SPAIN = 'es-ES';
export const GERMAN_GERMANY = 'de-DE'; export const GERMAN_GERMANY = 'de-DE';
export const BRAZILIAN_PORTUGUESE = 'pt-BR'; export const BRAZILIAN_PORTUGUESE = 'pt-BR';
export const CHINESE_SIMPLIFIED = 'zh-Hans'; export const CHINESE_SIMPLIFIED = 'zh-Hans';
export const PSEUDO_LOCALE = 'pseudo-LOCALE'; export const PSEUDO_LOCALE = 'pseudo';
export const DEFAULT_LANGUAGE = ENGLISH_US; export const DEFAULT_LANGUAGE = ENGLISH_US;