diff --git a/src/components/sound-editor/sound-editor.jsx b/src/components/sound-editor/sound-editor.jsx index 2168ee651bf5b1c94dbd8de4bc0b6697bf3cda5f..7ac6811abfb4c6219608f84ab541bbc533123278 100644 --- a/src/components/sound-editor/sound-editor.jsx +++ b/src/components/sound-editor/sound-editor.jsx @@ -1,6 +1,8 @@ import PropTypes from 'prop-types'; import React from 'react'; import classNames from 'classnames'; +import {defineMessages, FormattedMessage, injectIntl, intlShape} from 'react-intl'; + import Waveform from '../waveform/waveform.jsx'; import Label from '../forms/label.jsx'; import Input from '../forms/input.jsx'; @@ -15,6 +17,34 @@ import trimIcon from './icon--trim.svg'; const BufferedInput = BufferedInputHOC(Input); +const messages = defineMessages({ + sound: { + id: 'soundEditor.sound', + description: 'Lable for the name of the sound', + defaultMessage: 'Sound' + }, + play: { + id: 'soundEditor.play', + description: 'Title of the button to start playing the sound', + defaultMessage: 'Play' + }, + stop: { + id: 'soundEditor.stop', + description: 'Title of the button to stop the sound', + defaultMessage: 'Stop' + }, + trim: { + id: 'soundEditor.trim', + description: 'Title of the button to start trimminging the sound', + defaultMessage: 'Trim' + }, + save: { + id: 'soundEditor.save', + description: 'Title of the button to save trimmed sound', + defaultMessage: 'Save' + } +}); + const SoundEditor = props => ( <div className={styles.editorContainer}> <div className={styles.row}> @@ -22,7 +52,7 @@ const SoundEditor = props => ( {props.playhead ? ( <button className={classNames(styles.button, styles.stopButtonn)} - title={'Stop'} + title={props.intl.formatMessage(messages.stop)} onClick={props.onStop} > <img src={stopIcon} /> @@ -30,7 +60,7 @@ const SoundEditor = props => ( ) : ( <button className={classNames(styles.button, styles.playButton)} - title={'Play'} + title={props.intl.formatMessage(messages.play)} onClick={props.onPlay} > <img src={playIcon} /> @@ -38,7 +68,7 @@ const SoundEditor = props => ( )} </div> <div className={styles.inputGroup}> - <Label text="Sound"> + <Label text={props.intl.formatMessage(messages.sound)}> <BufferedInput tabIndex="1" type="text" @@ -52,11 +82,19 @@ const SoundEditor = props => ( className={classNames(styles.button, styles.trimButton, { [styles.trimButtonActive]: props.trimStart !== null })} - title={props.trimStart === null ? 'Trim' : 'Save'} + title={props.trimStart === null ? ( + props.intl.formatMessage(messages.trim) + ) : ( + props.intl.formatMessage(messages.save) + )} onClick={props.onActivateTrim} > <img src={trimIcon} /> - {props.trimStart === null ? 'Trim' : 'Save'} + {props.trimStart === null ? ( + <FormattedMessage {...messages.trim} /> + ) : ( + <FormattedMessage {...messages.save} /> + )} </button> </div> </div> @@ -81,6 +119,7 @@ const SoundEditor = props => ( SoundEditor.propTypes = { chunkLevels: PropTypes.arrayOf(PropTypes.number).isRequired, + intl: intlShape, name: PropTypes.string.isRequired, onActivateTrim: PropTypes.func, onChangeName: PropTypes.func.isRequired, @@ -93,4 +132,4 @@ SoundEditor.propTypes = { trimStart: PropTypes.number }; -export default SoundEditor; +export default injectIntl(SoundEditor); diff --git a/test/helpers/intl-helpers.js b/test/helpers/intl-helpers.js new file mode 100644 index 0000000000000000000000000000000000000000..d658aeae0981adc70d8de4f57d3ec196a06f2df1 --- /dev/null +++ b/test/helpers/intl-helpers.js @@ -0,0 +1,45 @@ +/* + * Helpers for using enzyme and react-test-renderer with react-intl + * Directly from https://github.com/yahoo/react-intl/wiki/Testing-with-React-Intl + */ +import React from 'react'; +import renderer from 'react-test-renderer'; +import {IntlProvider, intlShape} from 'react-intl'; +import {mount, shallow} from 'enzyme'; + +const intlProvider = new IntlProvider({locale: 'en'}, {}); +const {intl} = intlProvider.getChildContext(); + +const nodeWithIntlProp = node => { + return React.cloneElement(node, {intl}); +}; + +const shallowWithIntl = (node, {context} = {}) => { + return shallow( + nodeWithIntlProp(node), + { + context: Object.assign({}, context, {intl}) + } + ); +}; + +const mountWithIntl = (node, {context, childContextTypes} = {}) => { + return mount( + nodeWithIntlProp(node), + { + context: Object.assign({}, context, {intl}), + childContextTypes: Object.assign({}, {intl: intlShape}, childContextTypes) + } + ); +}; + +// react-test-renderer component for use with snapshot testing +const componentWithIntl = (children, props = {locale: 'en'}) => { + return renderer.create(<IntlProvider {...props}>{children}</IntlProvider>); +}; + +export { + componentWithIntl, + shallowWithIntl, + mountWithIntl +}; diff --git a/test/unit/components/__snapshots__/sound-editor.test.jsx.snap b/test/unit/components/__snapshots__/sound-editor.test.jsx.snap index b32ad42e3c4444d3ad99f57bc5365002594af51b..c757bc5d5679537c6125e4e7ed3b5a97be4d88ae 100644 --- a/test/unit/components/__snapshots__/sound-editor.test.jsx.snap +++ b/test/unit/components/__snapshots__/sound-editor.test.jsx.snap @@ -54,7 +54,9 @@ exports[`Sound Editor Component matches snapshot 1`] = ` <img src="test-file-stub" /> - Save + <span> + Save + </span> </button> </div> </div> diff --git a/test/unit/components/sound-editor.test.jsx b/test/unit/components/sound-editor.test.jsx index f22609cdb56cf971fd23a9c094450106c7c83b24..9c98edda9ba2632ad95e1760bc4fdafd12ed1289 100644 --- a/test/unit/components/sound-editor.test.jsx +++ b/test/unit/components/sound-editor.test.jsx @@ -1,8 +1,7 @@ /* eslint-env jest */ import React from 'react'; // eslint-disable-line no-unused-vars -import {mount} from 'enzyme'; +import {mountWithIntl, componentWithIntl} from '../../helpers/intl-helpers'; import SoundEditor from '../../../src/components/sound-editor/sound-editor'; // eslint-disable-line no-unused-vars -import renderer from 'react-test-renderer'; describe('Sound Editor Component', () => { let props; @@ -23,38 +22,36 @@ describe('Sound Editor Component', () => { }); test('matches snapshot', () => { - const component = renderer.create( - <SoundEditor {...props} /> - ); + const component = componentWithIntl(<SoundEditor {...props} />); expect(component.toJSON()).toMatchSnapshot(); }); test('trim button appears when trims are null', () => { - const wrapper = mount(<SoundEditor {...props} trimStart={null} trimEnd={null} />); + const wrapper = mountWithIntl(<SoundEditor {...props} trimStart={null} trimEnd={null} />); wrapper.find('button[title="Trim"]').simulate('click'); expect(props.onActivateTrim).toHaveBeenCalled(); }); test('save button appears when trims are not null', () => { - const wrapper = mount(<SoundEditor {...props} trimStart={0.25} trimEnd={0.75} />); + const wrapper = mountWithIntl(<SoundEditor {...props} trimStart={0.25} trimEnd={0.75} />); wrapper.find('button[title="Save"]').simulate('click'); expect(props.onActivateTrim).toHaveBeenCalled(); }); test('play button appears when playhead is null', () => { - const wrapper = mount(<SoundEditor {...props} playhead={null} />); + const wrapper = mountWithIntl(<SoundEditor {...props} playhead={null} />); wrapper.find('button[title="Play"]').simulate('click'); expect(props.onPlay).toHaveBeenCalled(); }); test('stop button appears when playhead is not null', () => { - const wrapper = mount(<SoundEditor {...props} playhead={0.5} />); + const wrapper = mountWithIntl(<SoundEditor {...props} playhead={0.5} />); wrapper.find('button[title="Stop"]').simulate('click'); expect(props.onStop).toHaveBeenCalled(); }); test('submitting name calls the callback', () => { - const wrapper = mount(<SoundEditor {...props} />); + const wrapper = mountWithIntl(<SoundEditor {...props} />); wrapper.find('input') .simulate('change', {target: {value: 'hello'}}) .simulate('blur'); diff --git a/test/unit/containers/sound-editor.test.jsx b/test/unit/containers/sound-editor.test.jsx index a0838689fae8c18b30233b912847a3d8e1d04254..39f1e19b07b2f3a9a24aee00136fdb7a2eefa0c3 100644 --- a/test/unit/containers/sound-editor.test.jsx +++ b/test/unit/containers/sound-editor.test.jsx @@ -1,6 +1,6 @@ /* eslint-env jest */ import React from 'react'; // eslint-disable-line no-unused-vars -import {mount} from 'enzyme'; +import {mountWithIntl} from '../../helpers/intl-helpers'; import configureStore from 'redux-mock-store'; import mockAudioBufferPlayer from '../../__mocks__/audio-buffer-player.js'; @@ -38,7 +38,7 @@ describe('Sound Editor Container', () => { }); test('should pass the correct data to the component from the store', () => { - const wrapper = mount(<SoundEditor store={store} soundIndex={soundIndex} />); + const wrapper = mountWithIntl(<SoundEditor store={store} soundIndex={soundIndex} />); const componentProps = wrapper.find(SoundEditorComponent).props(); // Data retreived and processed by the `connect` with the store expect(componentProps.name).toEqual('first name'); @@ -52,7 +52,7 @@ describe('Sound Editor Container', () => { }); test('it plays when clicked and stops when clicked again', () => { - const wrapper = mount(<SoundEditor store={store} soundIndex={soundIndex} />); + const wrapper = mountWithIntl(<SoundEditor store={store} soundIndex={soundIndex} />); const component = wrapper.find(SoundEditorComponent); // Ensure rendering doesn't start playing any sounds expect(mockAudioBufferPlayer.instance.play.mock.calls).toEqual([]); @@ -71,7 +71,7 @@ describe('Sound Editor Container', () => { }); test('it sets the component props for trimming and submits to the vm', () => { - const wrapper = mount(<SoundEditor store={store} soundIndex={soundIndex} />); + const wrapper = mountWithIntl(<SoundEditor store={store} soundIndex={soundIndex} />); const component = wrapper.find(SoundEditorComponent); component.props().onActivateTrim(); @@ -85,7 +85,7 @@ describe('Sound Editor Container', () => { }); test('it submits name changes to the vm', () => { - const wrapper = mount(<SoundEditor store={store} soundIndex={soundIndex} />); + const wrapper = mountWithIntl(<SoundEditor store={store} soundIndex={soundIndex} />); const component = wrapper.find(SoundEditorComponent); component.props().onChangeName('hello'); expect(vm.renameSound).toHaveBeenCalledWith(soundIndex, 'hello');