mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
parent
32de1a00d3
commit
b662879c98
65
e2e/various-suite/verify-i18n.spec.ts
Normal file
65
e2e/various-suite/verify-i18n.spec.ts
Normal 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -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}>
|
||||||
|
@ -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);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user