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
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
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
|
||||
};
|
||||
|
||||
export class FileNode extends React.Component {
|
||||
class FileNode extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
|
@ -419,4 +419,7 @@ const TranslatedFileNode = withTranslation()(FileNode);
|
|||
|
||||
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 { fireEvent, render, screen, waitFor, within } from '@testing-library/react';
|
||||
|
||||
import { fireEvent, render, screen, waitFor, within } from '../../../test-utils';
|
||||
import { FileNode } from './FileNode';
|
||||
|
||||
describe('<FileNode />', () => {
|
||||
|
|
|
@ -86,7 +86,7 @@ class Toolbar extends React.Component {
|
|||
this.props.setTextOutput(true);
|
||||
this.props.setGridOutput(true);
|
||||
}}
|
||||
aria-label="Play sketch"
|
||||
aria-label={this.props.t('Toolbar.PlaySketchARIA')}
|
||||
disabled={this.props.infiniteLoop}
|
||||
>
|
||||
<PlayIcon focusable="false" aria-hidden="true" />
|
||||
|
@ -94,7 +94,7 @@ class Toolbar extends React.Component {
|
|||
<button
|
||||
className={playButtonClass}
|
||||
onClick={this.props.startSketch}
|
||||
aria-label="Play only visual sketch"
|
||||
aria-label={this.props.t('Toolbar.PlayOnlyVisualSketchARIA')}
|
||||
disabled={this.props.infiniteLoop}
|
||||
>
|
||||
<PlayIcon focusable="false" aria-hidden="true" />
|
||||
|
@ -102,7 +102,7 @@ class Toolbar extends React.Component {
|
|||
<button
|
||||
className={stopButtonClass}
|
||||
onClick={this.props.stopSketch}
|
||||
aria-label="Stop sketch"
|
||||
aria-label={this.props.t('Toolbar.StopSketchARIA')}
|
||||
>
|
||||
<StopIcon focusable="false" aria-hidden="true" />
|
||||
</button>
|
||||
|
@ -129,7 +129,7 @@ class Toolbar extends React.Component {
|
|||
}
|
||||
}}
|
||||
disabled={!canEditProjectName}
|
||||
aria-label="Edit sketch name"
|
||||
aria-label={this.props.t('Toolbar.EditSketchARIA')}
|
||||
>
|
||||
<span>{this.props.project.name}</span>
|
||||
{
|
||||
|
@ -145,7 +145,7 @@ class Toolbar extends React.Component {
|
|||
type="text"
|
||||
maxLength="128"
|
||||
className="toolbar__project-name-input"
|
||||
aria-label="New sketch name"
|
||||
aria-label={this.props.t('Toolbar.NewSketchNameARIA')}
|
||||
value={this.state.projectNameInputValue}
|
||||
onChange={this.handleProjectNameChange}
|
||||
ref={(element) => { this.projectNameInput = element; }}
|
||||
|
@ -165,7 +165,7 @@ class Toolbar extends React.Component {
|
|||
<button
|
||||
className={preferencesButtonClass}
|
||||
onClick={this.props.openPreferences}
|
||||
aria-label="Open Preferences"
|
||||
aria-label={this.props.t('Toolbar.OpenPreferencesARIA')}
|
||||
>
|
||||
<PreferencesIcon focusable="false" aria-hidden="true" />
|
||||
</button>
|
||||
|
@ -200,6 +200,7 @@ Toolbar.propTypes = {
|
|||
saveProject: PropTypes.func.isRequired,
|
||||
currentUser: PropTypes.string,
|
||||
t: PropTypes.func.isRequired
|
||||
|
||||
};
|
||||
|
||||
Toolbar.defaultProps = {
|
||||
|
@ -225,6 +226,5 @@ const mapDispatchToProps = {
|
|||
...projectActions,
|
||||
};
|
||||
|
||||
export const ToolbarComponent = Toolbar;
|
||||
// export default connect(mapStateToProps, mapDispatchToProps)(Toolbar);
|
||||
export default withTranslation()(connect(mapStateToProps, mapDispatchToProps)(Toolbar));
|
||||
export const ToolbarComponent = withTranslation()(Toolbar);
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ToolbarComponent);
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import React from 'react';
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
import lodash from 'lodash';
|
||||
|
||||
|
||||
import { fireEvent, render, screen, waitFor } from '../../../test-utils';
|
||||
import { ToolbarComponent } from './Toolbar';
|
||||
|
||||
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