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:
parent
4d04952213
commit
af1a5cc2f1
7 changed files with 83 additions and 32 deletions
26
client/i18n-test.js
Normal file
26
client/i18n-test.js
Normal 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;
|
|
@ -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;
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
|
@ -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
|
||||||
|
};
|
||||||
|
|
|
@ -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 />', () => {
|
||||||
|
|
|
@ -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));
|
|
||||||
|
|
|
@ -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
40
client/test-utils.js
Normal 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 };
|
Loading…
Reference in a new issue