From 991c944ac3f646c9ef18e7cd9802524d20446ee7 Mon Sep 17 00:00:00 2001 From: Jessie Keck <jessie.keck@gmail.com> Date: Thu, 21 Feb 2019 15:04:49 -0800 Subject: [PATCH] Add a LanguageSettings component and hook it up to the WorkspaceMenu. --- .../mirador/language_switching.test.js | 24 +++++ .../src/components/LanguageSettings.test.js | 92 +++++++++++++++++++ locales/de/translation.json | 1 + locales/en/translation.json | 1 + src/components/LanguageSettings.js | 53 +++++++++++ src/components/WorkspaceMenu.js | 9 ++ src/config/settings.js | 4 + src/containers/LanguageSettings.js | 25 +++++ 8 files changed, 209 insertions(+) create mode 100644 __tests__/integration/mirador/language_switching.test.js create mode 100644 __tests__/src/components/LanguageSettings.test.js create mode 100644 src/components/LanguageSettings.js create mode 100644 src/containers/LanguageSettings.js diff --git a/__tests__/integration/mirador/language_switching.test.js b/__tests__/integration/mirador/language_switching.test.js new file mode 100644 index 000000000..134ec4a3b --- /dev/null +++ b/__tests__/integration/mirador/language_switching.test.js @@ -0,0 +1,24 @@ +describe('Language Switching', () => { + describe('Application Language', () => { + it('allows the user to switch the application language', async () => { + await page.goto('http://127.0.0.1:4488/__tests__/integration/mirador/'); + + await expect(page).toClick('#menuBtn'); + await expect(page).toMatchElement('ul[role="menu"]'); + await expect(page).toMatchElement('li p', { text: 'Language' }); + + await expect(page).not.toMatchElement('li', { text: 'Deutsch' }); + await expect(page).not.toMatchElement('li', { text: 'English' }); + await expect(page).toClick('li', { text: 'Language' }); + await expect(page).toMatchElement('li', { text: 'Deutsch' }); + await expect(page).toMatchElement('li', { text: 'English' }); + + await expect(page).toMatchElement('[aria-label="Toggle window sidebar"]'); + await expect(page).not.toMatchElement('[aria-label="Seitenleiste umschalten"]'); + await expect(page).toClick('li', { text: 'Deutsch' }); + await page.waitFor(1000); + await expect(page).not.toMatchElement('[aria-label="Toggle window sidebar"]'); + await expect(page).toMatchElement('[aria-label="Seitenleiste umschalten"]'); + }); + }); +}); diff --git a/__tests__/src/components/LanguageSettings.test.js b/__tests__/src/components/LanguageSettings.test.js new file mode 100644 index 000000000..e38cbb8cd --- /dev/null +++ b/__tests__/src/components/LanguageSettings.test.js @@ -0,0 +1,92 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import LanguageSettings from '../../../src/components/LanguageSettings'; + +/** + * Helper function to create a shallow wrapper around LanguageSettings + */ +function createWrapper(props) { + return shallow( + <LanguageSettings + active={lang => lang === 'de'} + handleClick={() => {}} + languages={{}} + {...props} + />, + ); +} + +describe('LanguageSettings', () => { + let wrapper; + const languages = { + de: 'Deutsch', + en: 'English', + }; + + + it('renders a list with a list item for each language passed in props', () => { + wrapper = createWrapper({ languages }); + + expect(wrapper.find('WithStyles(MenuItem)').length).toBe(2); + }); + + it('non-active list items are buttons (and active are not)', () => { + wrapper = createWrapper({ languages }); + + expect( + wrapper + .find('WithStyles(MenuItem)') + .first() // The German / active button + .prop('button'), + ).toBe(false); + + expect( + wrapper + .find('WithStyles(MenuItem)') + .last() // The English / non-active button + .prop('button'), + ).toBe(true); + }); + + it('renders the check icon when the active prop returns true', () => { + wrapper = createWrapper({ languages }); + + expect( + wrapper + .find('WithStyles(MenuItem)') + .first() + .find('WithStyles(ListItemIcon) pure(CheckSharpIcon)') + .length, + ).toBe(1); + }); + + it('renders the language value in an Typography element wrapped in a ListItemText', () => { + wrapper = createWrapper({ languages }); + + const firstListText = wrapper + .find('WithStyles(MenuItem)') + .first() + .find('WithStyles(ListItemText) WithStyles(Typography)') + .children() + .text(); + + expect(firstListText).toEqual('Deutsch'); + }); + + it('triggers the handleClick prop when clicking a list item', () => { + const mockHandleClick = jest.fn(); + wrapper = createWrapper({ languages, handleClick: mockHandleClick }); + + wrapper.find('WithStyles(MenuItem)').last().simulate('click'); + + expect(mockHandleClick).toHaveBeenCalledTimes(1); + expect(mockHandleClick).toHaveBeenCalledWith('en'); + }); + + it('passes the language prop to the active prop function to determine if the given language is active', () => { + const mockActiveFn = jest.fn(); + wrapper = createWrapper({ active: mockActiveFn, languages: { en: 'English' } }); + + expect(mockActiveFn).toHaveBeenCalledWith('en'); + }); +}); diff --git a/locales/de/translation.json b/locales/de/translation.json index 448fab989..48e789a85 100644 --- a/locales/de/translation.json +++ b/locales/de/translation.json @@ -21,6 +21,7 @@ "downloadExportWorkspace": "Download/Export Arbeitsfläche", "fetchManifest": "Hinzufügen", "fullScreen": "Vollbild", + "language": "Sprache", "light": "Hell", "listAllOpenWindows": "Liste der geöffneten Fenster", "manifestError": "Die Ressource konnte nicht hinzugefügt werden:", diff --git a/locales/en/translation.json b/locales/en/translation.json index 8dd85ecce..5320a8eaa 100644 --- a/locales/en/translation.json +++ b/locales/en/translation.json @@ -22,6 +22,7 @@ "fetchManifest": "Add", "fullScreen": "Full Screen", "hideZoomControls": "Hide zoom controls", + "language": "Language", "light": "Light", "listAllOpenWindows": "List all open windows", "manifestError": "The resource cannot be added:", diff --git a/src/components/LanguageSettings.js b/src/components/LanguageSettings.js new file mode 100644 index 000000000..bdfbe1348 --- /dev/null +++ b/src/components/LanguageSettings.js @@ -0,0 +1,53 @@ +import React, { Component } from 'react'; +import ListItemIcon from '@material-ui/core/ListItemIcon'; +import ListItemText from '@material-ui/core/ListItemText'; +import List from '@material-ui/core/List'; +import MenuItem from '@material-ui/core/MenuItem'; +import Typography from '@material-ui/core/Typography'; +import CheckIcon from '@material-ui/icons/CheckSharp'; +import PropTypes from 'prop-types'; + +/** + * LanguageSettings ~ the workspace sub menu to change the language + * of the application +*/ +export default class LanguageSettings extends Component { + /** + * Returns the rendered component + */ + render() { + const { + handleClick, languages, active, + } = this.props; + + return ( + <List> + { + Object.keys(languages).map(language => ( + <MenuItem + button={!(active(language))} + key={language} + onClick={() => { handleClick(language); }} + > + { + active(language) + && <ListItemIcon><CheckIcon /></ListItemIcon> + } + <ListItemText inset> + <Typography variant="inherit"> + {languages[language]} + </Typography> + </ListItemText> + </MenuItem> + )) + } + </List> + ); + } +} + +LanguageSettings.propTypes = { + active: PropTypes.func.isRequired, + handleClick: PropTypes.func.isRequired, + languages: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types +}; diff --git a/src/components/WorkspaceMenu.js b/src/components/WorkspaceMenu.js index de769d15a..facbe4fbd 100644 --- a/src/components/WorkspaceMenu.js +++ b/src/components/WorkspaceMenu.js @@ -1,6 +1,7 @@ import React, { Component } from 'react'; import Menu from '@material-ui/core/Menu'; import Divider from '@material-ui/core/Divider'; +import LanguageIcon from '@material-ui/icons/Language'; import ListItemIcon from '@material-ui/core/ListItemIcon'; import LoupeIcon from '@material-ui/icons/Loupe'; import MenuItem from '@material-ui/core/MenuItem'; @@ -9,6 +10,8 @@ import SaveAltIcon from '@material-ui/icons/SaveAlt'; import SettingsIcon from '@material-ui/icons/Settings'; import ViewHeadlineIcon from '@material-ui/icons/ViewHeadline'; import PropTypes from 'prop-types'; +import LanguageSettings from '../containers/LanguageSettings'; +import NestedMenu from './NestedMenu'; import WindowList from '../containers/WindowList'; import WorkspaceSettings from '../containers/WorkspaceSettings'; import WorkspaceExport from '../containers/WorkspaceExport'; @@ -92,6 +95,7 @@ class WorkspaceMenu extends Component { </ListItemIcon> <Typography varient="inherit">{t('listAllOpenWindows')}</Typography> </MenuItem> + <Divider /> <MenuItem aria-haspopup="true" onClick={(e) => { this.handleZoomToggleClick(e); handleClose(e); }} @@ -104,6 +108,11 @@ class WorkspaceMenu extends Component { { showZoomControls ? t('hideZoomControls') : t('showZoomControls') } </Typography> </MenuItem> + + <NestedMenu icon={<LanguageIcon />} label={t('language')}> + <LanguageSettings afterSelect={handleClose} /> + </NestedMenu> + <Divider /> <MenuItem aria-haspopup="true" diff --git a/src/config/settings.js b/src/config/settings.js index a2d481e5d..f0bf81a99 100644 --- a/src/config/settings.js +++ b/src/config/settings.js @@ -11,6 +11,10 @@ export default { } }, language: 'en', + availableLanguages: { // All the languages available in the language switcher + de: 'Deutsch', + en: 'English', + }, translations: { }, window: { diff --git a/src/containers/LanguageSettings.js b/src/containers/LanguageSettings.js new file mode 100644 index 000000000..b3d543858 --- /dev/null +++ b/src/containers/LanguageSettings.js @@ -0,0 +1,25 @@ +import { connect } from 'react-redux'; +import * as actions from '../state/actions'; +import LanguageSettings from '../components/LanguageSettings'; + +/** + * Map state to props for connect + */ +const mapStateToProps = state => ({ + languages: state.config.availableLanguages, + currentLanguage: state.config.language, + active: language => language === state.config.language, +}); + +/** + * Map action dispatches to props for connect + */ +const mapDispatchToProps = (dispatch, { afterSelect }) => ({ + handleClick: (language) => { + dispatch(actions.updateConfig({ language })); + + afterSelect && afterSelect(); + }, +}); + +export default connect(mapStateToProps, mapDispatchToProps)(LanguageSettings); -- GitLab