import React from 'react'; import {mountWithIntl} from '../../helpers/intl-helpers.jsx'; import configureStore from 'redux-mock-store'; import mockAudioBufferPlayer from '../../__mocks__/audio-buffer-player.js'; import mockAudioEffects from '../../__mocks__/audio-effects.js'; import SoundEditor from '../../../src/containers/sound-editor'; import SoundEditorComponent from '../../../src/components/sound-editor/sound-editor'; jest.mock('react-ga'); jest.mock('../../../src/lib/audio/audio-buffer-player', () => mockAudioBufferPlayer); jest.mock('../../../src/lib/audio/audio-effects', () => mockAudioEffects); describe('Sound Editor Container', () => { const mockStore = configureStore(); let store; let soundIndex; let soundBuffer; const samples = new Float32Array([0, 0, 0]); // eslint-disable-line no-undef let vm; beforeEach(() => { soundIndex = 0; soundBuffer = { sampleRate: 0, getChannelData: jest.fn(() => samples) }; vm = { getSoundBuffer: jest.fn(() => soundBuffer), renameSound: jest.fn(), updateSoundBuffer: jest.fn(), editingTarget: { sprite: { sounds: [{name: 'first name', id: 'first id'}] } } }; store = mockStore({scratchGui: {vm: vm}}); }); test('should pass the correct data to the component from the store', () => { const wrapper = mountWithIntl( <SoundEditor soundIndex={soundIndex} store={store} /> ); const componentProps = wrapper.find(SoundEditorComponent).props(); // Data retreived and processed by the `connect` with the store expect(componentProps.name).toEqual('first name'); expect(componentProps.chunkLevels).toEqual([0]); expect(mockAudioBufferPlayer.instance.samples).toEqual(samples); // Initial data expect(componentProps.playhead).toEqual(null); expect(componentProps.trimStart).toEqual(null); expect(componentProps.trimEnd).toEqual(null); }); test('it plays when clicked and stops when clicked again', () => { const wrapper = mountWithIntl( <SoundEditor soundIndex={soundIndex} store={store} /> ); let component = wrapper.find(SoundEditorComponent); // Ensure rendering doesn't start playing any sounds expect(mockAudioBufferPlayer.instance.play.mock.calls).toEqual([]); expect(mockAudioBufferPlayer.instance.stop.mock.calls).toEqual([]); component.props().onPlay(); expect(mockAudioBufferPlayer.instance.play).toHaveBeenCalled(); // Mock the audio buffer player calling onUpdate mockAudioBufferPlayer.instance.onUpdate(0.5); wrapper.update(); component = wrapper.find(SoundEditorComponent); expect(component.props().playhead).toEqual(0.5); component.props().onStop(); wrapper.update(); component = wrapper.find(SoundEditorComponent); expect(mockAudioBufferPlayer.instance.stop).toHaveBeenCalled(); expect(component.props().playhead).toEqual(null); }); test('it submits name changes to the vm', () => { const wrapper = mountWithIntl( <SoundEditor soundIndex={soundIndex} store={store} /> ); const component = wrapper.find(SoundEditorComponent); component.props().onChangeName('hello'); expect(vm.renameSound).toHaveBeenCalledWith(soundIndex, 'hello'); }); test('it handles an effect by submitting the result and playing', async () => { const wrapper = mountWithIntl( <SoundEditor soundIndex={soundIndex} store={store} /> ); const component = wrapper.find(SoundEditorComponent); component.props().onReverse(); // Could be any of the effects, just testing the end result await mockAudioEffects.instance._finishProcessing(soundBuffer); expect(mockAudioBufferPlayer.instance.play).toHaveBeenCalled(); expect(vm.updateSoundBuffer).toHaveBeenCalled(); }); test('it handles reverse effect correctly', () => { const wrapper = mountWithIntl( <SoundEditor soundIndex={soundIndex} store={store} /> ); const component = wrapper.find(SoundEditorComponent); component.props().onReverse(); expect(mockAudioEffects.instance.name).toEqual(mockAudioEffects.effectTypes.REVERSE); expect(mockAudioEffects.instance.process).toHaveBeenCalled(); }); test('it handles louder effect correctly', () => { const wrapper = mountWithIntl( <SoundEditor soundIndex={soundIndex} store={store} /> ); const component = wrapper.find(SoundEditorComponent); component.props().onLouder(); expect(mockAudioEffects.instance.name).toEqual(mockAudioEffects.effectTypes.LOUDER); expect(mockAudioEffects.instance.process).toHaveBeenCalled(); }); test('it handles softer effect correctly', () => { const wrapper = mountWithIntl( <SoundEditor soundIndex={soundIndex} store={store} /> ); const component = wrapper.find(SoundEditorComponent); component.props().onSofter(); expect(mockAudioEffects.instance.name).toEqual(mockAudioEffects.effectTypes.SOFTER); expect(mockAudioEffects.instance.process).toHaveBeenCalled(); }); test('it handles faster effect correctly', () => { const wrapper = mountWithIntl( <SoundEditor soundIndex={soundIndex} store={store} /> ); const component = wrapper.find(SoundEditorComponent); component.props().onFaster(); expect(mockAudioEffects.instance.name).toEqual(mockAudioEffects.effectTypes.FASTER); expect(mockAudioEffects.instance.process).toHaveBeenCalled(); }); test('it handles slower effect correctly', () => { const wrapper = mountWithIntl( <SoundEditor soundIndex={soundIndex} store={store} /> ); const component = wrapper.find(SoundEditorComponent); component.props().onSlower(); expect(mockAudioEffects.instance.name).toEqual(mockAudioEffects.effectTypes.SLOWER); expect(mockAudioEffects.instance.process).toHaveBeenCalled(); }); test('it handles echo effect correctly', () => { const wrapper = mountWithIntl( <SoundEditor soundIndex={soundIndex} store={store} /> ); const component = wrapper.find(SoundEditorComponent); component.props().onEcho(); expect(mockAudioEffects.instance.name).toEqual(mockAudioEffects.effectTypes.ECHO); expect(mockAudioEffects.instance.process).toHaveBeenCalled(); }); test('it handles robot effect correctly', () => { const wrapper = mountWithIntl( <SoundEditor soundIndex={soundIndex} store={store} /> ); const component = wrapper.find(SoundEditorComponent); component.props().onRobot(); expect(mockAudioEffects.instance.name).toEqual(mockAudioEffects.effectTypes.ROBOT); expect(mockAudioEffects.instance.process).toHaveBeenCalled(); }); test('undo/redo stack state', async () => { const wrapper = mountWithIntl( <SoundEditor soundIndex={soundIndex} store={store} /> ); let component = wrapper.find(SoundEditorComponent); // Undo and redo should be disabled initially expect(component.prop('canUndo')).toEqual(false); expect(component.prop('canRedo')).toEqual(false); // Submitting new samples should make it possible to undo component.props().onFaster(); await mockAudioEffects.instance._finishProcessing(soundBuffer); wrapper.update(); component = wrapper.find(SoundEditorComponent); expect(component.prop('canUndo')).toEqual(true); expect(component.prop('canRedo')).toEqual(false); // Undoing should make it possible to redo and not possible to undo again await component.props().onUndo(); wrapper.update(); component = wrapper.find(SoundEditorComponent); expect(component.prop('canUndo')).toEqual(false); expect(component.prop('canRedo')).toEqual(true); // Redoing should make it possible to undo and not possible to redo again await component.props().onRedo(); wrapper.update(); component = wrapper.find(SoundEditorComponent); expect(component.prop('canUndo')).toEqual(true); expect(component.prop('canRedo')).toEqual(false); // New submission should clear the redo stack await component.props().onUndo(); // Undo to go back to a state where redo is enabled wrapper.update(); component = wrapper.find(SoundEditorComponent); expect(component.prop('canRedo')).toEqual(true); component.props().onFaster(); await mockAudioEffects.instance._finishProcessing(soundBuffer); wrapper.update(); component = wrapper.find(SoundEditorComponent); expect(component.prop('canRedo')).toEqual(false); }); test('undo and redo submit new samples and play the sound', async () => { const wrapper = mountWithIntl( <SoundEditor soundIndex={soundIndex} store={store} /> ); let component = wrapper.find(SoundEditorComponent); // Set up an undoable state component.props().onFaster(); await mockAudioEffects.instance._finishProcessing(soundBuffer); wrapper.update(); component = wrapper.find(SoundEditorComponent); // Undo should update the sound buffer and play the new samples await component.props().onUndo(); expect(mockAudioBufferPlayer.instance.play).toHaveBeenCalled(); expect(vm.updateSoundBuffer).toHaveBeenCalled(); // Clear the mocks call history to assert again for redo. vm.updateSoundBuffer.mockClear(); mockAudioBufferPlayer.instance.play.mockClear(); // Undo should update the sound buffer and play the new samples await component.props().onRedo(); expect(mockAudioBufferPlayer.instance.play).toHaveBeenCalled(); expect(vm.updateSoundBuffer).toHaveBeenCalled(); }); });