Use translations for all tests (#1568)

* Toolbar ARIA Labels
* Ensure all tests use real translations

All tests should import the 'test-utils' file which re-exports all of
the @testing-library functions. It wraps render() in a react-i18next
instance.

It's important that the component being tested is the one returned from
withTranslation().

Co-authored-by: ov <omar.verduga@gmail.com>
This commit is contained in:
Andrew Nicolaou 2020-08-26 13:19:34 +01:00 committed by GitHub
parent 4d04952213
commit af1a5cc2f1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 83 additions and 32 deletions

26
client/i18n-test.js Normal file
View file

@ -0,0 +1,26 @@
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import translations from '../translations/locales/en-US/translations.json';
i18n
.use(initReactI18next)
.init({
lng: 'en-US',
fallbackLng: 'en-US',
// have a common namespace used around the full app
ns: ['translations'],
defaultNS: 'translations',
debug: false,
interpolation: {
escapeValue: false, // not needed for react!!
},
resources: { 'en-US': { translations } },
});
export default i18n;

View file

@ -3,21 +3,3 @@ import '@babel/polyfill';
// See: https://github.com/testing-library/jest-dom // See: https://github.com/testing-library/jest-dom
// eslint-disable-next-line import/no-extraneous-dependencies // eslint-disable-next-line import/no-extraneous-dependencies
import '@testing-library/jest-dom'; import '@testing-library/jest-dom';
import lodash from 'lodash';
// For testing, we use en-US and provide a mock implementation
// of t() that finds the correct translation
import translations from '../translations/locales/en-US/translations.json';
// This function name needs to be prefixed with "mock" so that Jest doesn't
// complain that it's out-of-scope in the mock below
const mockTranslate = key => lodash.get(translations, key);
jest.mock('react-i18next', () => ({
// this mock makes sure any components using the translate HoC receive the t function as a prop
withTranslation: () => (Component) => {
Component.defaultProps = { ...Component.defaultProps, t: mockTranslate };
return Component;
},
}));

View file

@ -67,7 +67,7 @@ FileName.propTypes = {
name: PropTypes.string.isRequired name: PropTypes.string.isRequired
}; };
export class FileNode extends React.Component { class FileNode extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
@ -419,4 +419,7 @@ const TranslatedFileNode = withTranslation()(FileNode);
const ConnectedFileNode = connect(mapStateToProps, mapDispatchToProps)(TranslatedFileNode); const ConnectedFileNode = connect(mapStateToProps, mapDispatchToProps)(TranslatedFileNode);
export default ConnectedFileNode; export {
TranslatedFileNode as FileNode,
ConnectedFileNode as default
};

View file

@ -1,5 +1,6 @@
import React from 'react'; import React from 'react';
import { fireEvent, render, screen, waitFor, within } from '@testing-library/react';
import { fireEvent, render, screen, waitFor, within } from '../../../test-utils';
import { FileNode } from './FileNode'; import { FileNode } from './FileNode';
describe('<FileNode />', () => { describe('<FileNode />', () => {

View file

@ -86,7 +86,7 @@ class Toolbar extends React.Component {
this.props.setTextOutput(true); this.props.setTextOutput(true);
this.props.setGridOutput(true); this.props.setGridOutput(true);
}} }}
aria-label="Play sketch" aria-label={this.props.t('Toolbar.PlaySketchARIA')}
disabled={this.props.infiniteLoop} disabled={this.props.infiniteLoop}
> >
<PlayIcon focusable="false" aria-hidden="true" /> <PlayIcon focusable="false" aria-hidden="true" />
@ -94,7 +94,7 @@ class Toolbar extends React.Component {
<button <button
className={playButtonClass} className={playButtonClass}
onClick={this.props.startSketch} onClick={this.props.startSketch}
aria-label="Play only visual sketch" aria-label={this.props.t('Toolbar.PlayOnlyVisualSketchARIA')}
disabled={this.props.infiniteLoop} disabled={this.props.infiniteLoop}
> >
<PlayIcon focusable="false" aria-hidden="true" /> <PlayIcon focusable="false" aria-hidden="true" />
@ -102,7 +102,7 @@ class Toolbar extends React.Component {
<button <button
className={stopButtonClass} className={stopButtonClass}
onClick={this.props.stopSketch} onClick={this.props.stopSketch}
aria-label="Stop sketch" aria-label={this.props.t('Toolbar.StopSketchARIA')}
> >
<StopIcon focusable="false" aria-hidden="true" /> <StopIcon focusable="false" aria-hidden="true" />
</button> </button>
@ -129,7 +129,7 @@ class Toolbar extends React.Component {
} }
}} }}
disabled={!canEditProjectName} disabled={!canEditProjectName}
aria-label="Edit sketch name" aria-label={this.props.t('Toolbar.EditSketchARIA')}
> >
<span>{this.props.project.name}</span> <span>{this.props.project.name}</span>
{ {
@ -145,7 +145,7 @@ class Toolbar extends React.Component {
type="text" type="text"
maxLength="128" maxLength="128"
className="toolbar__project-name-input" className="toolbar__project-name-input"
aria-label="New sketch name" aria-label={this.props.t('Toolbar.NewSketchNameARIA')}
value={this.state.projectNameInputValue} value={this.state.projectNameInputValue}
onChange={this.handleProjectNameChange} onChange={this.handleProjectNameChange}
ref={(element) => { this.projectNameInput = element; }} ref={(element) => { this.projectNameInput = element; }}
@ -165,7 +165,7 @@ class Toolbar extends React.Component {
<button <button
className={preferencesButtonClass} className={preferencesButtonClass}
onClick={this.props.openPreferences} onClick={this.props.openPreferences}
aria-label="Open Preferences" aria-label={this.props.t('Toolbar.OpenPreferencesARIA')}
> >
<PreferencesIcon focusable="false" aria-hidden="true" /> <PreferencesIcon focusable="false" aria-hidden="true" />
</button> </button>
@ -200,6 +200,7 @@ Toolbar.propTypes = {
saveProject: PropTypes.func.isRequired, saveProject: PropTypes.func.isRequired,
currentUser: PropTypes.string, currentUser: PropTypes.string,
t: PropTypes.func.isRequired t: PropTypes.func.isRequired
}; };
Toolbar.defaultProps = { Toolbar.defaultProps = {
@ -225,6 +226,5 @@ const mapDispatchToProps = {
...projectActions, ...projectActions,
}; };
export const ToolbarComponent = Toolbar; export const ToolbarComponent = withTranslation()(Toolbar);
// export default connect(mapStateToProps, mapDispatchToProps)(Toolbar); export default connect(mapStateToProps, mapDispatchToProps)(ToolbarComponent);
export default withTranslation()(connect(mapStateToProps, mapDispatchToProps)(Toolbar));

View file

@ -1,8 +1,7 @@
import React from 'react'; import React from 'react';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import lodash from 'lodash'; import lodash from 'lodash';
import { fireEvent, render, screen, waitFor } from '../../../test-utils';
import { ToolbarComponent } from './Toolbar'; import { ToolbarComponent } from './Toolbar';
const renderComponent = (extraProps = {}) => { const renderComponent = (extraProps = {}) => {

40
client/test-utils.js Normal file
View file

@ -0,0 +1,40 @@
/**
* This file re-exports @testing-library but ensures that
* any calls to render have translations available.
*
* This means tested components will be able to call
* `t()` and have the translations of the default
* language
*
* See: https://react.i18next.com/misc/testing#testing-without-stubbing
*/
// eslint-disable-next-line import/no-extraneous-dependencies
import { render } from '@testing-library/react';
import React from 'react';
import PropTypes from 'prop-types';
import { I18nextProvider } from 'react-i18next';
import i18n from './i18n-test';
// re-export everything
// eslint-disable-next-line import/no-extraneous-dependencies
export * from '@testing-library/react';
const Providers = ({ children }) => (
// eslint-disable-next-line react/jsx-filename-extension
<I18nextProvider i18n={i18n}>
{children}
</I18nextProvider>
);
Providers.propTypes = {
children: PropTypes.element.isRequired,
};
const customRender = (ui, options) =>
render(ui, { wrapper: Providers, ...options });
// override render method
export { customRender as render };