diff --git a/src/containers/controls.jsx b/src/containers/controls.jsx index 2869a047a6ba5722762d86f32875419cd14cfb0b..1d4a666d2ac63d7159953b80a89622d6c8056cbc 100644 --- a/src/containers/controls.jsx +++ b/src/containers/controls.jsx @@ -20,6 +20,9 @@ class Controls extends React.Component { if (e.shiftKey) { this.props.vm.setTurboMode(!this.props.turbo); } else { + if (!this.props.vm.started) { + this.props.vm.start(); + } this.props.vm.greenFlag(); analytics.event({ category: 'general', diff --git a/src/lib/vm-manager-hoc.jsx b/src/lib/vm-manager-hoc.jsx index 404b7e3c84c03102e352035cadbc52c739848628..159e5c5c0d634a9483190878feca7bc0876a6b70 100644 --- a/src/lib/vm-manager-hoc.jsx +++ b/src/lib/vm-manager-hoc.jsx @@ -27,12 +27,16 @@ const vmManagerHOC = function (WrappedComponent) { ]); } componentDidMount () { - if (this.props.vm.initialized) return; - this.audioEngine = new AudioEngine(); - this.props.vm.attachAudioEngine(this.audioEngine); - this.props.vm.setCompatibilityMode(true); - this.props.vm.start(); - this.props.vm.initialized = true; + if (!this.props.vm.initialized) { + this.audioEngine = new AudioEngine(); + this.props.vm.attachAudioEngine(this.audioEngine); + this.props.vm.setCompatibilityMode(true); + this.props.vm.initialized = true; + } + if (!this.props.isPlayerOnly && !this.props.vm.started) { + this.props.vm.start(); + this.props.vm.started = true; + } } componentDidUpdate (prevProps) { // if project is in loading state, AND fonts are loaded, @@ -41,6 +45,11 @@ const vmManagerHOC = function (WrappedComponent) { (!prevProps.isLoadingWithId || !prevProps.fontsLoaded)) { this.loadProject(); } + // Start the VM if entering editor mode with an unstarted vm + if (!this.props.isPlayerOnly && !this.props.vm.started) { + this.props.vm.start(); + this.props.vm.started = true; + } } loadProject () { return this.props.vm.loadProject(this.props.projectData) @@ -79,6 +88,7 @@ const vmManagerHOC = function (WrappedComponent) { cloudHost: PropTypes.string, fontsLoaded: PropTypes.bool, isLoadingWithId: PropTypes.bool, + isPlayerOnly: PropTypes.bool, loadingState: PropTypes.oneOf(LoadingStates), onError: PropTypes.func, onLoadedProject: PropTypes.func, @@ -94,7 +104,8 @@ const vmManagerHOC = function (WrappedComponent) { isLoadingWithId: getIsLoadingWithId(loadingState), projectData: state.scratchGui.projectState.projectData, projectId: state.scratchGui.projectState.projectId, - loadingState: loadingState + loadingState: loadingState, + isPlayerOnly: state.scratchGui.mode.isPlayerOnly }; }; diff --git a/test/unit/util/vm-manager-hoc.test.jsx b/test/unit/util/vm-manager-hoc.test.jsx index 8c065d83d9006b4a4a4553277c45bc07e44b5ffc..4cee7a49c06944d76958efcab2f7a352431c1b79 100644 --- a/test/unit/util/vm-manager-hoc.test.jsx +++ b/test/unit/util/vm-manager-hoc.test.jsx @@ -16,7 +16,8 @@ describe('VMManagerHOC', () => { beforeEach(() => { store = mockStore({ scratchGui: { - projectState: {} + projectState: {}, + mode: {} // Mocked by using override props in tests when needed } }); vm = new VM(); @@ -24,34 +25,79 @@ describe('VMManagerHOC', () => { vm.setCompatibilityMode = jest.fn(); vm.start = jest.fn(); }); - test('when it mounts, the vm is initialized', () => { + test('when it mounts in player mode, the vm is initialized but not started', () => { const Component = () => (<div />); const WrappedComponent = vmManagerHOC(Component); mount( <WrappedComponent + isPlayerOnly + store={store} + vm={vm} + /> + ); + expect(vm.attachAudioEngine.mock.calls.length).toBe(1); + expect(vm.setCompatibilityMode.mock.calls.length).toBe(1); + expect(vm.initialized).toBe(true); + + // But vm should not be started automatically + expect(vm.start.mock.calls.length).toBe(0); + expect(vm.started).not.toBe(true); + }); + test('when it mounts in editor mode, the vm is initialized and started', () => { + const Component = () => (<div />); + const WrappedComponent = vmManagerHOC(Component); + mount( + <WrappedComponent + isPlayerOnly={false} store={store} vm={vm} /> ); expect(vm.attachAudioEngine.mock.calls.length).toBe(1); expect(vm.setCompatibilityMode.mock.calls.length).toBe(1); - expect(vm.start.mock.calls.length).toBe(1); expect(vm.initialized).toBe(true); + + expect(vm.start.mock.calls.length).toBe(1); + expect(vm.started).toBe(true); }); - test('if it mounts with an initialized vm, it does not reinitialize the vm', () => { + test('if it mounts with an initialized vm, it does not reinitialize the vm but will start it', () => { const Component = () => <div />; const WrappedComponent = vmManagerHOC(Component); vm.initialized = true; + vm.started = false; mount( <WrappedComponent + isPlayerOnly={false} store={store} vm={vm} /> ); expect(vm.attachAudioEngine.mock.calls.length).toBe(0); expect(vm.setCompatibilityMode.mock.calls.length).toBe(0); - expect(vm.start.mock.calls.length).toBe(0); expect(vm.initialized).toBe(true); + + expect(vm.start.mock.calls.length).toBe(1); + expect(vm.started).toBe(true); + }); + + test('if it mounts without starting the VM, it can be started by switching to editor mode', () => { + const Component = () => <div />; + const WrappedComponent = vmManagerHOC(Component); + vm.initialized = true; + vm.started = false; + const mounted = mount( + <WrappedComponent + isPlayerOnly + store={store} + vm={vm} + /> + ); + expect(vm.start.mock.calls.length).toBe(0); + mounted.setProps({ + isPlayerOnly: false + }); + expect(vm.start.mock.calls.length).toBe(1); + expect(vm.started).toBe(true); }); test('if the isLoadingWithId prop becomes true, it loads project data into the vm', () => { vm.loadProject = jest.fn(() => Promise.resolve());