Skip to content

Commit

Permalink
[i18n] Test EUI i18n tokens coverage (#106377)
Browse files Browse the repository at this point in the history
  • Loading branch information
Bamieh authored Jul 22, 2021
1 parent a9d6454 commit dbfca5c
Show file tree
Hide file tree
Showing 5 changed files with 194 additions and 440 deletions.
8 changes: 4 additions & 4 deletions src/core/public/i18n/__snapshots__/i18n_service.test.tsx.snap

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

100 changes: 100 additions & 0 deletions src/core/public/i18n/i18n_eui_mapping.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

jest.mock('@kbn/i18n');

import { i18n } from '@kbn/i18n';

import i18ntokens from '@elastic/eui/i18ntokens.json';
import { getEuiContextMapping } from './i18n_eui_mapping';

/** Regexp to find {values} usage */
const VALUES_REGEXP = /\{\w+\}/;

describe('@elastic/eui i18n tokens', () => {
const i18nTranslateMock = jest
.fn()
.mockImplementation((id, { defaultMessage }) => defaultMessage);
i18n.translate = i18nTranslateMock;

const euiContextMapping = getEuiContextMapping();

test('all tokens are mapped', () => {
// Extract the tokens from the EUI library: We need to uniq them because they might be duplicated
const euiTokensFromLib = [...new Set(i18ntokens.map(({ token }) => token))];
const euiTokensFromMapping = Object.keys(euiContextMapping);

expect(euiTokensFromMapping.sort()).toStrictEqual(euiTokensFromLib.sort());
});

test('tokens that include {word} should be mapped to functions', () => {
const euiTokensFromLibWithValues = i18ntokens.filter(({ defString }) =>
VALUES_REGEXP.test(defString)
);
const euiTokensFromLib = [...new Set(euiTokensFromLibWithValues.map(({ token }) => token))];
const euiTokensFromMapping = Object.entries(euiContextMapping)
.filter(([, value]) => typeof value === 'function')
.map(([key]) => key);

expect(euiTokensFromMapping.sort()).toStrictEqual(euiTokensFromLib.sort());
});

i18ntokens.forEach(({ token, defString }) => {
describe(`Token "${token}"`, () => {
let i18nTranslateCall: [
string,
{ defaultMessage: string; values?: object; description?: string }
];

beforeAll(() => {
// If it's a function, call it, so we have the mock to register the call.
const entry = euiContextMapping[token as keyof typeof euiContextMapping];
const translationOutput = typeof entry === 'function' ? entry({}) : entry;

// If it's a string, it comes from i18n.translate call
if (typeof translationOutput === 'string') {
// find the call in the mocks
i18nTranslateCall = i18nTranslateMock.mock.calls.find(
([kbnToken]) => kbnToken === `core.${token}`
);
} else {
// Otherwise, it's a fn returning `FormattedMessage` component => read the props
const { id, defaultMessage, values } = translationOutput.props;
i18nTranslateCall = [id, { defaultMessage, values }];
}
});

test('a translation should be registered as `core.{TOKEN}`', () => {
expect(i18nTranslateCall).not.toBeUndefined();
});

test('defaultMessage is in sync with defString', () => {
// Clean up typical errors from the `@elastic/eui` extraction token tool
const normalizedDefString = defString
// Quoted words should use double-quotes
.replace(/\s'/g, ' "')
.replace(/'\s/g, '" ')
// Should not include break-lines
.replace(/\n/g, '')
// Should trim extra spaces
.replace(/\s{2,}/g, ' ')
.trim();

expect(i18nTranslateCall[1].defaultMessage).toBe(normalizedDefString);
});

test('values should match', () => {
const valuesFromEuiLib = defString.match(new RegExp(VALUES_REGEXP, 'g')) || [];
const receivedValuesInMock = Object.keys(i18nTranslateCall[1].values ?? {}).map(
(key) => `{${key}}`
);
expect(receivedValuesInMock.sort()).toStrictEqual(valuesFromEuiLib.sort());
});
});
});
});
123 changes: 60 additions & 63 deletions src/core/public/i18n/i18n_eui_mapping.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ interface EuiValues {
[key: string]: any;
}

export const getEuiContextMapping = () => {
const euiContextMapping: EuiTokensObject = {
export const getEuiContextMapping = (): EuiTokensObject => {
return {
'euiAccordion.isLoading': i18n.translate('core.euiAccordion.isLoading', {
defaultMessage: 'Loading',
}),
Expand All @@ -40,7 +40,7 @@ export const getEuiContextMapping = () => {
page,
pageCount,
}: EuiValues) =>
i18n.translate('core.euiBasicTable.tableDescriptionWithoutPagination', {
i18n.translate('core.euiBasicTable.tableAutoCaptionWithPagination', {
defaultMessage:
'This table contains {itemCount} rows out of {totalItemCount} rows; Page {page} of {pageCount}.',
values: { itemCount, totalItemCount, page, pageCount },
Expand Down Expand Up @@ -219,6 +219,9 @@ export const getEuiContextMapping = () => {
description:
'Screen reader text to describe the composite behavior of the color stops component.',
}),
'euiColumnActions.hideColumn': i18n.translate('core.euiColumnActions.hideColumn', {
defaultMessage: 'Hide column',
}),
'euiColumnActions.sort': ({ schemaLabel }: EuiValues) =>
i18n.translate('core.euiColumnActions.sort', {
defaultMessage: 'Sort {schemaLabel}',
Expand All @@ -230,9 +233,6 @@ export const getEuiContextMapping = () => {
'euiColumnActions.moveRight': i18n.translate('core.euiColumnActions.moveRight', {
defaultMessage: 'Move right',
}),
'euiColumnActions.hideColumn': i18n.translate('core.euiColumnActions.hideColumn', {
defaultMessage: 'Hide column',
}),
'euiColumnSelector.hideAll': i18n.translate('core.euiColumnSelector.hideAll', {
defaultMessage: 'Hide all',
}),
Expand Down Expand Up @@ -369,19 +369,19 @@ export const getEuiContextMapping = () => {
'euiControlBar.screenReaderHeading': i18n.translate('core.euiControlBar.screenReaderHeading', {
defaultMessage: 'Page level controls',
}),
'euiControlBar.customScreenReaderAnnouncement': ({ landmarkHeading }: EuiValues) =>
i18n.translate('core.euiControlBar.customScreenReaderAnnouncement', {
defaultMessage:
'There is a new region landmark called {landmarkHeading} with page level controls at the end of the document.',
values: { landmarkHeading },
}),
'euiControlBar.screenReaderAnnouncement': i18n.translate(
'core.euiControlBar.screenReaderAnnouncement',
{
defaultMessage:
'There is a new region landmark with page level controls at the end of the document.',
}
),
'euiControlBar.customScreenReaderAnnouncement': ({ landmarkHeading }: EuiValues) =>
i18n.translate('core.euiControlBar.customScreenReaderAnnouncement', {
defaultMessage:
'There is a new region landmark called {landmarkHeading} with page level controls at the end of the document.',
values: { landmarkHeading },
}),
'euiDataGrid.screenReaderNotice': i18n.translate('core.euiDataGrid.screenReaderNotice', {
defaultMessage: 'Cell contains interactive content.',
}),
Expand Down Expand Up @@ -466,13 +466,13 @@ export const getEuiContextMapping = () => {
}
),
'euiDataGridSchema.dateSortTextAsc': i18n.translate('core.euiDataGridSchema.dateSortTextAsc', {
defaultMessage: 'New-Old',
defaultMessage: 'Old-New',
description: 'Ascending date label',
}),
'euiDataGridSchema.dateSortTextDesc': i18n.translate(
'core.euiDataGridSchema.dateSortTextDesc',
{
defaultMessage: 'Old-New',
defaultMessage: 'New-Old',
description: 'Descending date label',
}
),
Expand Down Expand Up @@ -519,8 +519,8 @@ export const getEuiContextMapping = () => {
}),
'euiFilterButton.filterBadge': ({ count, hasActiveFilters }: EuiValues) =>
i18n.translate('core.euiFilterButton.filterBadge', {
defaultMessage: '${count} ${filterCountLabel} filters',
values: { count, filterCountLabel: hasActiveFilters ? 'active' : 'available' },
defaultMessage: '{count} {hasActiveFilters} filters',
values: { count, hasActiveFilters: hasActiveFilters ? 'active' : 'available' },
}),
'euiFlyout.closeAriaLabel': i18n.translate('core.euiFlyout.closeAriaLabel', {
defaultMessage: 'Close this dialog',
Expand Down Expand Up @@ -642,19 +642,19 @@ export const getEuiContextMapping = () => {
'euiModal.closeModal': i18n.translate('core.euiModal.closeModal', {
defaultMessage: 'Closes this modal window',
}),
'euiNotificationEventMessages.accordionButtonText': ({
'euiNotificationEventMessages.accordionButtonText': ({ messagesLength }: EuiValues) =>
i18n.translate('core.euiNotificationEventMessages.accordionButtonText', {
defaultMessage: '+ {messagesLength} more',
values: { messagesLength },
}),
'euiNotificationEventMessages.accordionAriaLabelButtonText': ({
messagesLength,
eventName,
}: EuiValues) =>
i18n.translate('core.euiNotificationEventMessages.accordionButtonText', {
i18n.translate('core.euiNotificationEventMessages.accordionAriaLabelButtonText', {
defaultMessage: '+ {messagesLength} messages for {eventName}',
values: { messagesLength, eventName },
}),
'euiNotificationEventMessages.accordionAriaLabelButtonText': ({ messagesLength }: EuiValues) =>
i18n.translate('core.euiNotificationEventMessages.accordionAriaLabelButtonText', {
defaultMessage: '+ {messagesLength} more',
values: { messagesLength },
}),
'euiNotificationEventMeta.contextMenuButton': ({ eventName }: EuiValues) =>
i18n.translate('core.euiNotificationEventMeta.contextMenuButton', {
defaultMessage: 'Menu for {eventName}',
Expand Down Expand Up @@ -682,25 +682,6 @@ export const getEuiContextMapping = () => {
defaultMessage: 'Mark as unread',
}
),
'euiNotificationEventReadIcon.readAria': ({ eventName }: EuiValues) =>
i18n.translate('core.euiNotificationEventReadIcon.readAria', {
defaultMessage: '{eventName} is read',
values: { eventName },
}),
'euiNotificationEventReadIcon.unreadAria': ({ eventName }: EuiValues) =>
i18n.translate('core.euiNotificationEventReadIcon.unreadAria', {
defaultMessage: '{eventName} is unread',
values: { eventName },
}),
'euiNotificationEventReadIcon.read': i18n.translate('core.euiNotificationEventReadIcon.read', {
defaultMessage: 'Read',
}),
'euiNotificationEventReadIcon.unread': i18n.translate(
'core.euiNotificationEventReadIcon.unread',
{
defaultMessage: 'Unread',
}
),
'euiNotificationEventMessages.accordionHideText': i18n.translate(
'core.euiNotificationEventMessages.accordionHideText',
{
Expand All @@ -712,13 +693,11 @@ export const getEuiContextMapping = () => {
defaultMessage: 'Next page, {page}',
values: { page },
}),
'euiPagination.pageOfTotalCompressed': ({ page, total }: EuiValues) => (
<FormattedMessage
id="core.euiPagination.pageOfTotalCompressed"
defaultMessage="{page} of {total}"
values={{ page, total }}
/>
),
'euiPagination.pageOfTotalCompressed': ({ page, total }: EuiValues) =>
i18n.translate('core.euiPagination.pageOfTotalCompressed', {
defaultMessage: '{page} of {total}',
values: { page, total },
}),
'euiPagination.previousPage': ({ page }: EuiValues) =>
i18n.translate('core.euiPagination.previousPage', {
defaultMessage: 'Previous page, {page}',
Expand Down Expand Up @@ -881,7 +860,7 @@ export const getEuiContextMapping = () => {
description: 'Placeholder message while data is asynchronously loaded',
}),
'euiSelectable.noAvailableOptions': i18n.translate('core.euiSelectable.noAvailableOptions', {
defaultMessage: "There aren't any options available",
defaultMessage: 'No options available',
}),
'euiSelectable.noMatchingOptions': ({ searchValue }: EuiValues) => (
<FormattedMessage
Expand Down Expand Up @@ -914,7 +893,7 @@ export const getEuiContextMapping = () => {
'euiSelectableListItem.excludedOptionInstructions': i18n.translate(
'core.euiSelectableListItem.excludedOptionInstructions',
{
defaultMessage: 'To deselect this option, press enter',
defaultMessage: 'To deselect this option, press enter.',
}
),
'euiSelectableTemplateSitewide.loadingResults': i18n.translate(
Expand Down Expand Up @@ -1039,7 +1018,7 @@ export const getEuiContextMapping = () => {
'euiSuperSelect.screenReaderAnnouncement': ({ optionsCount }: EuiValues) =>
i18n.translate('core.euiSuperSelect.screenReaderAnnouncement', {
defaultMessage:
'You are in a form selector of {optionsCount} items and must select a single option. Use the Up and Down keys to navigate or Escape to close.',
'You are in a form selector of {optionsCount} items and must select a single option. Use the up and down keys to navigate or escape to close.',
values: { optionsCount },
}),
'euiSuperSelectControl.selectAnOption': ({ selectedValue }: EuiValues) =>
Expand Down Expand Up @@ -1086,6 +1065,7 @@ export const getEuiContextMapping = () => {
i18n.translate('core.euiTableHeaderCell.titleTextWithDesc', {
defaultMessage: '{innerText}; {description}',
values: { innerText, description },
description: 'Displayed in a cell in the header of the table to describe the field',
}),
'euiTablePagination.rowsPerPage': i18n.translate('core.euiTablePagination.rowsPerPage', {
defaultMessage: 'Rows per page',
Expand All @@ -1111,6 +1091,15 @@ export const getEuiContextMapping = () => {
defaultMessage: 'Notification',
description: 'ARIA label on an element containing a notification',
}),
'euiTourStep.endTour': i18n.translate('core.euiTourStep.endTour', {
defaultMessage: 'End tour',
}),
'euiTourStep.skipTour': i18n.translate('core.euiTourStep.skipTour', {
defaultMessage: 'Skip tour',
}),
'euiTourStep.closeTour': i18n.translate('core.euiTourStep.closeTour', {
defaultMessage: 'Close tour',
}),
'euiTourStepIndicator.isActive': i18n.translate('core.euiTourStepIndicator.isActive', {
defaultMessage: 'active',
description: 'Text for an active tour step',
Expand All @@ -1123,15 +1112,6 @@ export const getEuiContextMapping = () => {
defaultMessage: 'incomplete',
description: 'Text for an incomplete tour step',
}),
'euiTourStep.endTour': i18n.translate('core.euiTourStep.endTour', {
defaultMessage: 'End tour',
}),
'euiTourStep.skipTour': i18n.translate('core.euiTourStep.skipTour', {
defaultMessage: 'Skip tour',
}),
'euiTourStep.closeTour': i18n.translate('core.euiTourStep.closeTour', {
defaultMessage: 'Close tour',
}),
'euiTourStepIndicator.ariaLabel': ({ status, number }: EuiValues) =>
i18n.translate('core.euiTourStepIndicator.ariaLabel', {
defaultMessage: 'Step {number} {status}',
Expand All @@ -1149,7 +1129,24 @@ export const getEuiContextMapping = () => {
defaultMessage: 'You can quickly navigate this list using arrow keys.',
}
),
'euiNotificationEventReadIcon.read': i18n.translate('core.euiNotificationEventReadIcon.read', {
defaultMessage: 'Read',
}),
'euiNotificationEventReadIcon.readAria': ({ eventName }: EuiValues) =>
i18n.translate('core.euiNotificationEventReadIcon.readAria', {
defaultMessage: '{eventName} is read',
values: { eventName },
}),
'euiNotificationEventReadIcon.unread': i18n.translate(
'core.euiNotificationEventReadIcon.unread',
{
defaultMessage: 'Unread',
}
),
'euiNotificationEventReadIcon.unreadAria': ({ eventName }: EuiValues) =>
i18n.translate('core.euiNotificationEventReadIcon.unreadAria', {
defaultMessage: '{eventName} is unread',
values: { eventName },
}),
};

return euiContextMapping;
};
Loading

0 comments on commit dbfca5c

Please sign in to comment.