diff --git a/src/lib/vm-listener-hoc.jsx b/src/lib/vm-listener-hoc.jsx index 79a21210e77efe62886ac3dfa32d8b063d29d28f..b6207a5b7dd1a693df06bb3b2015880799da1f86 100644 --- a/src/lib/vm-listener-hoc.jsx +++ b/src/lib/vm-listener-hoc.jsx @@ -87,9 +87,9 @@ const vmListenerHOC = function (WrappedComponent) { // Don't capture keys intended for Blockly inputs. if (e.target !== document && e.target !== document.body) return; + const key = (!e.key || e.key === 'Dead') ? e.keyCode : e.key; this.props.vm.postIOData('keyboard', { - keyCode: e.keyCode, - key: e.key, + key: key, isDown: true }); @@ -102,9 +102,9 @@ const vmListenerHOC = function (WrappedComponent) { handleKeyUp (e) { // Always capture up events, // even those that have switched to other targets. + const key = (!e.key || e.key === 'Dead') ? e.keyCode : e.key; this.props.vm.postIOData('keyboard', { - keyCode: e.keyCode, - key: e.key, + key: key, isDown: false }); diff --git a/test/unit/util/vm-listener-hoc.test.jsx b/test/unit/util/vm-listener-hoc.test.jsx index 78993f0ce4a61f6a56dd7f55e0d78b7df32babee..011f66135f9e647218b24c74f4b85733ce9eddcf 100644 --- a/test/unit/util/vm-listener-hoc.test.jsx +++ b/test/unit/util/vm-listener-hoc.test.jsx @@ -133,4 +133,49 @@ describe('VMListenerHOC', () => { const actions = store.getActions(); expect(actions.length).toEqual(0); }); + + test('keypresses go to the vm', () => { + const Component = () => (<div />); + const WrappedComponent = vmListenerHOC(Component); + + // Mock document.addEventListener so we can trigger keypresses manually + // Cannot use the enzyme simulate method because that only works on synthetic events + const eventTriggers = {}; + document.addEventListener = jest.fn((event, cb) => { + eventTriggers[event] = cb; + }); + + vm.postIOData = jest.fn(); + + store = mockStore({ + scratchGui: { + mode: {isFullScreen: true}, + modals: {soundRecorder: true}, + vm: vm + } + }); + mount( + <WrappedComponent + attachKeyboardEvents + store={store} + vm={vm} + /> + ); + + // keyboard events that do not target the document or body are ignored + eventTriggers.keydown({key: 'A', target: null}); + expect(vm.postIOData).not.toHaveBeenLastCalledWith('keyboard', {key: 'A', isDown: true}); + + // keydown/up with target as the document are sent to the vm via postIOData + eventTriggers.keydown({key: 'A', target: document}); + expect(vm.postIOData).toHaveBeenLastCalledWith('keyboard', {key: 'A', isDown: true}); + + eventTriggers.keyup({key: 'A', target: document}); + expect(vm.postIOData).toHaveBeenLastCalledWith('keyboard', {key: 'A', isDown: false}); + + // When key is 'Dead' e.g. bluetooth keyboards on iOS, it sends keyCode instead + // because the VM can process both named keys or keyCodes as the `key` property + eventTriggers.keyup({key: 'Dead', keyCode: 10, target: document}); + expect(vm.postIOData).toHaveBeenLastCalledWith('keyboard', {key: 10, isDown: false}); + }); });