Initial commit of 001code-html Scratch frontend project.
Includes scratch-gui, scratch-vm, scratch-blocks, scratch-render, scratch-l10n, and deployment config. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
306
scratch-gui/test/unit/containers/sound-editor.test.jsx
Normal file
306
scratch-gui/test/unit/containers/sound-editor.test.jsx
Normal file
@@ -0,0 +1,306 @@
|
||||
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 = {
|
||||
numberOfChannels: 1,
|
||||
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, mode: {isFullScreen: false}}});
|
||||
});
|
||||
|
||||
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();
|
||||
});
|
||||
|
||||
test('isStereo numberOfChannels=1', () => {
|
||||
soundBuffer.numberOfChannels = 1;
|
||||
const wrapper = mountWithIntl(
|
||||
<SoundEditor
|
||||
soundIndex={soundIndex}
|
||||
store={store}
|
||||
/>
|
||||
);
|
||||
const component = wrapper.find(SoundEditorComponent);
|
||||
expect(component.props().isStereo).toEqual(false);
|
||||
});
|
||||
|
||||
test('isStereo numberOfChannels=2', () => {
|
||||
soundBuffer.numberOfChannels = 2;
|
||||
const wrapper = mountWithIntl(
|
||||
<SoundEditor
|
||||
soundIndex={soundIndex}
|
||||
store={store}
|
||||
/>
|
||||
);
|
||||
const component = wrapper.find(SoundEditorComponent);
|
||||
expect(component.props().isStereo).toEqual(true);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user