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:
170
scratch-gui/test/unit/containers/menu-bar-hoc.test.jsx
Normal file
170
scratch-gui/test/unit/containers/menu-bar-hoc.test.jsx
Normal file
@@ -0,0 +1,170 @@
|
||||
describe('no-op', () => {
|
||||
test('no-op', () => {});
|
||||
});
|
||||
// tw: these seem to be hopelessly broken to the increasing scope of changes we make to the menu bar, disable for now...
|
||||
/*
|
||||
import React from 'react';
|
||||
import {mount} from 'enzyme';
|
||||
import configureStore from 'redux-mock-store';
|
||||
import MenuBarHOC from '../../../src/containers/menu-bar-hoc.jsx';
|
||||
|
||||
describe('Menu Bar HOC', () => {
|
||||
const mockStore = configureStore();
|
||||
let store;
|
||||
|
||||
beforeEach(() => {
|
||||
store = mockStore({
|
||||
scratchGui: {
|
||||
projectChanged: true
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('Logged in user who IS owner and HAS changed project will NOT be prompted to save', () => {
|
||||
const Component = () => (<div />);
|
||||
const WrappedComponent = MenuBarHOC(Component);
|
||||
const wrapper = mount(
|
||||
<WrappedComponent
|
||||
canCreateNew
|
||||
canSave
|
||||
projectChanged
|
||||
// assume the user will click "cancel" on the confirm dialog
|
||||
confirmWithMessage={() => (false)} // eslint-disable-line react/jsx-no-bind
|
||||
store={store}
|
||||
/>
|
||||
);
|
||||
const child = wrapper.find(Component);
|
||||
expect(child.props().projectChanged).toBeUndefined();
|
||||
expect(child.props().confirmReadyToReplaceProject('message')).toBe(true);
|
||||
});
|
||||
|
||||
test('Logged in user who IS owner and has NOT changed project will NOT be prompted to save', () => {
|
||||
const Component = () => (<div />);
|
||||
const WrappedComponent = MenuBarHOC(Component);
|
||||
const wrapper = mount(
|
||||
<WrappedComponent
|
||||
canCreateNew
|
||||
canSave
|
||||
confirmWithMessage={() => (false)} // eslint-disable-line react/jsx-no-bind
|
||||
projectChanged={false}
|
||||
store={store}
|
||||
/>
|
||||
);
|
||||
const child = wrapper.find(Component);
|
||||
expect(child.props().projectChanged).toBeUndefined();
|
||||
expect(child.props().confirmReadyToReplaceProject('message')).toBe(true);
|
||||
});
|
||||
|
||||
test('Logged in user who is NOT owner and HAS changed project will NOT be prompted to save', () => {
|
||||
const Component = () => (<div />);
|
||||
const WrappedComponent = MenuBarHOC(Component);
|
||||
const wrapper = mount(
|
||||
<WrappedComponent
|
||||
canCreateNew
|
||||
projectChanged
|
||||
canSave={false}
|
||||
confirmWithMessage={() => (false)} // eslint-disable-line react/jsx-no-bind
|
||||
store={store}
|
||||
/>
|
||||
);
|
||||
const child = wrapper.find(Component);
|
||||
expect(child.props().projectChanged).toBeUndefined();
|
||||
expect(child.props().confirmReadyToReplaceProject('message')).toBe(true);
|
||||
});
|
||||
|
||||
test('Logged OUT user who HAS changed project WILL be prompted to save', () => {
|
||||
const Component = () => (<div />);
|
||||
const WrappedComponent = MenuBarHOC(Component);
|
||||
const wrapper = mount(
|
||||
<WrappedComponent
|
||||
projectChanged
|
||||
canCreateNew={false}
|
||||
canSave={false}
|
||||
confirmWithMessage={() => (false)} // eslint-disable-line react/jsx-no-bind
|
||||
store={store}
|
||||
/>
|
||||
);
|
||||
const child = wrapper.find(Component);
|
||||
expect(child.props().projectChanged).toBeUndefined();
|
||||
expect(child.props().confirmReadyToReplaceProject('message')).toBe(false);
|
||||
});
|
||||
|
||||
test('Logged OUT user who has NOT changed project WILL NOT be prompted to save', () => {
|
||||
const Component = () => (<div />);
|
||||
const WrappedComponent = MenuBarHOC(Component);
|
||||
const wrapper = mount(
|
||||
<WrappedComponent
|
||||
canCreateNew={false}
|
||||
canSave={false}
|
||||
confirmWithMessage={() => (false)} // eslint-disable-line react/jsx-no-bind
|
||||
projectChanged={false}
|
||||
store={store}
|
||||
/>
|
||||
);
|
||||
const child = wrapper.find(Component);
|
||||
expect(child.props().projectChanged).toBeUndefined();
|
||||
expect(child.props().confirmReadyToReplaceProject('message')).toBe(true);
|
||||
});
|
||||
|
||||
test('Logged in user who IS owner and HAS changed project SHOULD save before transition to project page', () => {
|
||||
const Component = () => (<div />);
|
||||
const WrappedComponent = MenuBarHOC(Component);
|
||||
const wrapper = mount(
|
||||
<WrappedComponent
|
||||
canSave
|
||||
projectChanged
|
||||
store={store}
|
||||
/>
|
||||
);
|
||||
const child = wrapper.find(Component);
|
||||
expect(child.props().projectChanged).toBeUndefined();
|
||||
expect(child.props().shouldSaveBeforeTransition()).toBe(true);
|
||||
});
|
||||
|
||||
test('Logged in user who IS owner and has NOT changed project should NOT save before transition', () => {
|
||||
const Component = () => (<div />);
|
||||
const WrappedComponent = MenuBarHOC(Component);
|
||||
const wrapper = mount(
|
||||
<WrappedComponent
|
||||
canSave
|
||||
projectChanged={false}
|
||||
store={store}
|
||||
/>
|
||||
);
|
||||
const child = wrapper.find(Component);
|
||||
expect(child.props().projectChanged).toBeUndefined();
|
||||
expect(child.props().shouldSaveBeforeTransition()).toBe(false);
|
||||
});
|
||||
|
||||
test('Logged in user who is NOT owner and HAS changed project should NOT save before transition', () => {
|
||||
const Component = () => (<div />);
|
||||
const WrappedComponent = MenuBarHOC(Component);
|
||||
const wrapper = mount(
|
||||
<WrappedComponent
|
||||
projectChanged
|
||||
canSave={false}
|
||||
store={store}
|
||||
/>
|
||||
);
|
||||
const child = wrapper.find(Component);
|
||||
expect(child.props().projectChanged).toBeUndefined();
|
||||
expect(child.props().shouldSaveBeforeTransition()).toBe(false);
|
||||
});
|
||||
|
||||
test('Logged in user who is NOT owner and has NOT changed project should NOT save before transition', () => {
|
||||
const Component = () => (<div />);
|
||||
const WrappedComponent = MenuBarHOC(Component);
|
||||
const wrapper = mount(
|
||||
<WrappedComponent
|
||||
canSave={false}
|
||||
projectChanged={false}
|
||||
store={store}
|
||||
/>
|
||||
);
|
||||
const child = wrapper.find(Component);
|
||||
expect(child.props().projectChanged).toBeUndefined();
|
||||
expect(child.props().shouldSaveBeforeTransition()).toBe(false);
|
||||
});
|
||||
|
||||
});
|
||||
*/
|
||||
76
scratch-gui/test/unit/containers/save-status.test.jsx
Normal file
76
scratch-gui/test/unit/containers/save-status.test.jsx
Normal file
@@ -0,0 +1,76 @@
|
||||
import React from 'react';
|
||||
import {Provider} from 'react-redux';
|
||||
import configureStore from 'redux-mock-store';
|
||||
import {mountWithIntl} from '../../helpers/intl-helpers.jsx';
|
||||
import SaveStatus from '../../../src/components/menu-bar/save-status.jsx';
|
||||
import InlineMessages from '../../../src/containers/inline-messages.jsx';
|
||||
import {AlertTypes} from '../../../src/lib/alerts/index.jsx';
|
||||
|
||||
// Stub the manualUpdateProject action creator for later testing
|
||||
jest.mock('../../../src/reducers/project-state', () => ({
|
||||
manualUpdateProject: jest.fn(() => ({type: 'stubbed'}))
|
||||
}));
|
||||
|
||||
describe('SaveStatus container', () => {
|
||||
const mockStore = configureStore();
|
||||
|
||||
test('if there are inline messages, they are shown instead of save now', () => {
|
||||
const store = mockStore({
|
||||
scratchGui: {
|
||||
projectChanged: true,
|
||||
alerts: {
|
||||
alertsList: [
|
||||
{alertId: 'saveSuccess', alertType: AlertTypes.INLINE}
|
||||
]
|
||||
}
|
||||
}
|
||||
});
|
||||
const wrapper = mountWithIntl(
|
||||
<Provider store={store}>
|
||||
<SaveStatus />
|
||||
</Provider>
|
||||
);
|
||||
expect(wrapper.find(InlineMessages).exists()).toBe(true);
|
||||
expect(wrapper.contains('Save Now')).not.toBe(true);
|
||||
});
|
||||
|
||||
test('save now is shown if there are project changes and no inline messages', () => {
|
||||
const store = mockStore({
|
||||
scratchGui: {
|
||||
projectChanged: true,
|
||||
alerts: {
|
||||
alertsList: []
|
||||
}
|
||||
}
|
||||
});
|
||||
const wrapper = mountWithIntl(
|
||||
<Provider store={store}>
|
||||
<SaveStatus />
|
||||
</Provider>
|
||||
);
|
||||
expect(wrapper.find(InlineMessages).exists()).not.toBe(true);
|
||||
expect(wrapper.contains('Save Now')).toBe(true);
|
||||
|
||||
// Clicking save now should dispatch the manualUpdateProject action (stubbed above)
|
||||
wrapper.find('[children="Save Now"]').simulate('click');
|
||||
expect(store.getActions()[0].type).toEqual('stubbed');
|
||||
});
|
||||
|
||||
test('neither is shown if there are no project changes or inline messages', () => {
|
||||
const store = mockStore({
|
||||
scratchGui: {
|
||||
projectChanged: false,
|
||||
alerts: {
|
||||
alertsList: []
|
||||
}
|
||||
}
|
||||
});
|
||||
const wrapper = mountWithIntl(
|
||||
<Provider store={store}>
|
||||
<SaveStatus />
|
||||
</Provider>
|
||||
);
|
||||
expect(wrapper.find(InlineMessages).exists()).not.toBe(true);
|
||||
expect(wrapper.contains('Save Now')).not.toBe(true);
|
||||
});
|
||||
});
|
||||
111
scratch-gui/test/unit/containers/slider-prompt.test.jsx
Normal file
111
scratch-gui/test/unit/containers/slider-prompt.test.jsx
Normal file
@@ -0,0 +1,111 @@
|
||||
import React from 'react';
|
||||
import {shallow} from 'enzyme';
|
||||
import SliderPrompt from '../../../src/containers/slider-prompt.jsx';
|
||||
import SliderPromptComponent from '../../../src/components/slider-prompt/slider-prompt.jsx';
|
||||
|
||||
describe('Slider Prompt Container', () => {
|
||||
let onCancel;
|
||||
let onOk;
|
||||
|
||||
beforeEach(() => {
|
||||
onCancel = jest.fn();
|
||||
onOk = jest.fn();
|
||||
});
|
||||
|
||||
test('Min/max are shown with decimal when isDiscrete is false', () => {
|
||||
const wrapper = shallow(
|
||||
<SliderPrompt
|
||||
isDiscrete={false}
|
||||
maxValue={100}
|
||||
minValue={0}
|
||||
onCancel={onCancel}
|
||||
onOk={onOk}
|
||||
/>
|
||||
);
|
||||
const componentProps = wrapper.find(SliderPromptComponent).props();
|
||||
expect(componentProps.minValue).toBe('0.00');
|
||||
expect(componentProps.maxValue).toBe('100.00');
|
||||
});
|
||||
|
||||
test('Min/max are NOT shown with decimal when isDiscrete is true', () => {
|
||||
const wrapper = shallow(
|
||||
<SliderPrompt
|
||||
isDiscrete
|
||||
maxValue={100}
|
||||
minValue={0}
|
||||
onCancel={onCancel}
|
||||
onOk={onOk}
|
||||
/>
|
||||
);
|
||||
const componentProps = wrapper.find(SliderPromptComponent).props();
|
||||
expect(componentProps.minValue).toBe('0');
|
||||
expect(componentProps.maxValue).toBe('100');
|
||||
});
|
||||
|
||||
test('Entering a number with a decimal submits with isDiscrete=false', () => {
|
||||
const wrapper = shallow(
|
||||
<SliderPrompt
|
||||
isDiscrete
|
||||
maxValue={100}
|
||||
minValue={0}
|
||||
onCancel={onCancel}
|
||||
onOk={onOk}
|
||||
/>
|
||||
);
|
||||
const componentProps = wrapper.find(SliderPromptComponent).props();
|
||||
componentProps.onChangeMin({target: {value: '1.0'}});
|
||||
componentProps.onOk();
|
||||
expect(onOk).toHaveBeenCalledWith(1, 100, false);
|
||||
});
|
||||
|
||||
test('Entering integers submits with isDiscrete=true', () => {
|
||||
const wrapper = shallow(
|
||||
<SliderPrompt
|
||||
isDiscrete={false}
|
||||
maxValue={100.1}
|
||||
minValue={12.32}
|
||||
onCancel={onCancel}
|
||||
onOk={onOk}
|
||||
/>
|
||||
);
|
||||
const componentProps = wrapper.find(SliderPromptComponent).props();
|
||||
componentProps.onChangeMin({target: {value: '1'}});
|
||||
componentProps.onChangeMax({target: {value: '2'}});
|
||||
componentProps.onOk();
|
||||
expect(onOk).toHaveBeenCalledWith(1, 2, true);
|
||||
});
|
||||
|
||||
test('Enter button submits the form', () => {
|
||||
const wrapper = shallow(
|
||||
<SliderPrompt
|
||||
isDiscrete={false}
|
||||
maxValue={100.1}
|
||||
minValue={12.32}
|
||||
onCancel={onCancel}
|
||||
onOk={onOk}
|
||||
/>
|
||||
);
|
||||
const componentProps = wrapper.find(SliderPromptComponent).props();
|
||||
componentProps.onChangeMin({target: {value: '1'}});
|
||||
componentProps.onChangeMax({target: {value: '2'}});
|
||||
componentProps.onKeyPress({key: 'Enter'});
|
||||
expect(onOk).toHaveBeenCalledWith(1, 2, true);
|
||||
});
|
||||
|
||||
test('Validates number-ness before submitting', () => {
|
||||
const wrapper = shallow(
|
||||
<SliderPrompt
|
||||
isDiscrete={false}
|
||||
maxValue={100.1}
|
||||
minValue={12.32}
|
||||
onCancel={onCancel}
|
||||
onOk={onOk}
|
||||
/>
|
||||
);
|
||||
const componentProps = wrapper.find(SliderPromptComponent).props();
|
||||
componentProps.onChangeMin({target: {value: 'hello'}});
|
||||
componentProps.onOk();
|
||||
expect(onOk).not.toHaveBeenCalled();
|
||||
expect(onCancel).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
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);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,58 @@
|
||||
import React from 'react';
|
||||
import {mountWithIntl} from '../../helpers/intl-helpers.jsx';
|
||||
import configureStore from 'redux-mock-store';
|
||||
import {Provider} from 'react-redux';
|
||||
|
||||
import SpriteSelectorItem from '../../../src/containers/sprite-selector-item';
|
||||
import DeleteButton from '../../../src/components/delete-button/delete-button';
|
||||
|
||||
describe('SpriteSelectorItem Container', () => {
|
||||
const mockStore = configureStore();
|
||||
let className;
|
||||
let costumeURL;
|
||||
let name;
|
||||
let onClick;
|
||||
let dispatchSetHoveredSprite;
|
||||
let onDeleteButtonClick;
|
||||
let selected;
|
||||
let id;
|
||||
let store;
|
||||
// Wrap this in a function so it gets test specific states and can be reused.
|
||||
const getContainer = function () {
|
||||
return (
|
||||
<Provider store={store}>
|
||||
<SpriteSelectorItem
|
||||
className={className}
|
||||
costumeURL={costumeURL}
|
||||
dispatchSetHoveredSprite={dispatchSetHoveredSprite}
|
||||
id={id}
|
||||
name={name}
|
||||
selected={selected}
|
||||
onClick={onClick}
|
||||
onDeleteButtonClick={onDeleteButtonClick}
|
||||
/>
|
||||
</Provider>
|
||||
);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
store = mockStore({scratchGui: {
|
||||
hoveredTarget: {receivedBlocks: false, sprite: null},
|
||||
assetDrag: {dragging: false}
|
||||
}});
|
||||
className = 'ponies';
|
||||
costumeURL = 'https://scratch.mit.edu/foo/bar/pony';
|
||||
id = 1337;
|
||||
name = 'Pony sprite';
|
||||
onClick = jest.fn();
|
||||
onDeleteButtonClick = jest.fn();
|
||||
dispatchSetHoveredSprite = jest.fn();
|
||||
selected = true;
|
||||
});
|
||||
|
||||
test('should delete the sprite', () => {
|
||||
const wrapper = mountWithIntl(getContainer());
|
||||
wrapper.find(DeleteButton).simulate('click');
|
||||
expect(onDeleteButtonClick).toHaveBeenCalledWith(1337);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user