/** * @license * Copyright 2020 Google LLC * SPDX-License-Identifier: Apache-2.0 */ goog.module('Blockly.test.keydown'); const {createKeyDownEvent} = goog.require('Blockly.test.helpers.userInput'); const {defineStackBlock} = goog.require('Blockly.test.helpers.blockDefinitions'); const {sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers.setupTeardown'); suite('Key Down', function() { setup(function() { sharedTestSetup.call(this); this.workspace = Blockly.inject('blocklyDiv', {}); }); teardown(function() { sharedTestTeardown.call(this); }); /** * Creates a block and sets it as Blockly.selected. * @param {Blockly.Workspace} workspace The workspace to create a new block on. */ function setSelectedBlock(workspace) { defineStackBlock(); Blockly.common.setSelected(workspace.newBlock('stack_block')); } /** * Creates a test for not running keyDown events when the workspace is in read only mode. * @param {Object} keyEvent Mocked key down event. Use createKeyDownEvent. * @param {string=} opt_name An optional name for the test case. */ function runReadOnlyTest(keyEvent, opt_name) { const name = opt_name ? opt_name : 'Not called when readOnly is true'; test(name, function() { this.workspace.options.readOnly = true; document.dispatchEvent(keyEvent); sinon.assert.notCalled(this.hideChaffSpy); }); } suite('Escape', function() { setup(function() { this.event = createKeyDownEvent(Blockly.utils.KeyCodes.ESC); this.hideChaffSpy = sinon.spy( Blockly.WorkspaceSvg.prototype, 'hideChaff'); }); test('Simple', function() { document.dispatchEvent(this.event); sinon.assert.calledOnce(this.hideChaffSpy); }); runReadOnlyTest(createKeyDownEvent(Blockly.utils.KeyCodes.ESC)); test('Not called when focus is on an HTML input', function() { const event = createKeyDownEvent(Blockly.utils.KeyCodes.ESC); const input = document.createElement('textarea'); input.dispatchEvent(event); sinon.assert.notCalled(this.hideChaffSpy); }); test('Not called on hidden workspaces', function() { this.workspace.isVisible_ = false; document.dispatchEvent(this.event); sinon.assert.notCalled(this.hideChaffSpy); }); }); suite('Delete Block', function() { setup(function() { this.hideChaffSpy = sinon.spy( Blockly.WorkspaceSvg.prototype, 'hideChaff'); setSelectedBlock(this.workspace); this.deleteSpy = sinon.spy(Blockly.common.getSelected(), 'dispose'); }); const testCases = [ ['Delete', createKeyDownEvent(Blockly.utils.KeyCodes.DELETE)], ['Backspace', createKeyDownEvent(Blockly.utils.KeyCodes.BACKSPACE)], ]; // Delete a block. suite('Simple', function() { testCases.forEach(function(testCase) { const testCaseName = testCase[0]; const keyEvent = testCase[1]; test(testCaseName, function() { document.dispatchEvent(keyEvent); sinon.assert.calledOnce(this.hideChaffSpy); sinon.assert.calledOnce(this.deleteSpy); }); }); }); // Do not delete a block if workspace is in readOnly mode. suite('Not called when readOnly is true', function() { testCases.forEach(function(testCase) { const testCaseName = testCase[0]; const keyEvent = testCase[1]; runReadOnlyTest(keyEvent, testCaseName); }); }); }); suite('Copy', function() { setup(function() { setSelectedBlock(this.workspace); this.copySpy = sinon.spy(Blockly.clipboard, 'copy'); this.hideChaffSpy = sinon.spy( Blockly.WorkspaceSvg.prototype, 'hideChaff'); }); const testCases = [ ['Control C', createKeyDownEvent(Blockly.utils.KeyCodes.C, [Blockly.utils.KeyCodes.CTRL])], ['Meta C', createKeyDownEvent(Blockly.utils.KeyCodes.C, [Blockly.utils.KeyCodes.META])], ['Alt C', createKeyDownEvent(Blockly.utils.KeyCodes.C, [Blockly.utils.KeyCodes.ALT])], ]; // Copy a block. suite('Simple', function() { testCases.forEach(function(testCase) { const testCaseName = testCase[0]; const keyEvent = testCase[1]; test(testCaseName, function() { document.dispatchEvent(keyEvent); sinon.assert.calledOnce(this.copySpy); sinon.assert.calledOnce(this.hideChaffSpy); }); }); }); // Do not copy a block if a workspace is in readonly mode. suite('Not called when readOnly is true', function() { testCases.forEach(function(testCase) { const testCaseName = testCase[0]; const keyEvent = testCase[1]; runReadOnlyTest(keyEvent, testCaseName); }); }); // Do not copy a block if a gesture is in progress. suite('Gesture in progress', function() { testCases.forEach(function(testCase) { const testCaseName = testCase[0]; const keyEvent = testCase[1]; test(testCaseName, function() { sinon.stub(Blockly.Gesture, 'inProgress').returns(true); document.dispatchEvent(keyEvent); sinon.assert.notCalled(this.copySpy); sinon.assert.notCalled(this.hideChaffSpy); }); }); }); // Do not copy a block if is is not deletable. suite('Block is not deletable', function() { testCases.forEach(function(testCase) { const testCaseName = testCase[0]; const keyEvent = testCase[1]; test(testCaseName, function() { sinon.stub(Blockly.common.getSelected(), 'isDeletable').returns(false); document.dispatchEvent(keyEvent); sinon.assert.notCalled(this.copySpy); sinon.assert.notCalled(this.hideChaffSpy); }); }); }); // Do not copy a block if it is not movable. suite('Block is not movable', function() { testCases.forEach(function(testCase) { const testCaseName = testCase[0]; const keyEvent = testCase[1]; test(testCaseName, function() { sinon.stub(Blockly.common.getSelected(), 'isMovable').returns(false); document.dispatchEvent(keyEvent); sinon.assert.notCalled(this.copySpy); sinon.assert.notCalled(this.hideChaffSpy); }); }); }); }); suite('Undo', function() { setup(function() { this.undoSpy = sinon.spy(this.workspace, 'undo'); this.hideChaffSpy = sinon.spy( Blockly.WorkspaceSvg.prototype, 'hideChaff'); }); const testCases = [ ['Control Z', createKeyDownEvent(Blockly.utils.KeyCodes.Z, [Blockly.utils.KeyCodes.CTRL])], ['Meta Z', createKeyDownEvent(Blockly.utils.KeyCodes.Z, [Blockly.utils.KeyCodes.META])], ['Alt Z', createKeyDownEvent(Blockly.utils.KeyCodes.Z, [Blockly.utils.KeyCodes.ALT])], ]; // Undo. suite('Simple', function() { testCases.forEach(function(testCase) { const testCaseName = testCase[0]; const keyEvent = testCase[1]; test(testCaseName, function() { document.dispatchEvent(keyEvent); sinon.assert.calledOnce(this.undoSpy); sinon.assert.calledWith(this.undoSpy, false); sinon.assert.calledOnce(this.hideChaffSpy); }); }); }); // Do not undo if a gesture is in progress. suite('Gesture in progress', function() { testCases.forEach(function(testCase) { const testCaseName = testCase[0]; const keyEvent = testCase[1]; test(testCaseName, function() { sinon.stub(Blockly.Gesture, 'inProgress').returns(true); document.dispatchEvent(keyEvent); sinon.assert.notCalled(this.undoSpy); sinon.assert.notCalled(this.hideChaffSpy); }); }); }); // Do not undo if the workspace is in readOnly mode. suite('Not called when readOnly is true', function() { testCases.forEach(function(testCase) { const testCaseName = testCase[0]; const keyEvent = testCase[1]; runReadOnlyTest(keyEvent, testCaseName); }); }); }); suite('Redo', function() { setup(function() { this.redoSpy = sinon.spy(this.workspace, 'undo'); this.hideChaffSpy = sinon.spy( Blockly.WorkspaceSvg.prototype, 'hideChaff'); }); const testCases = [ ['Control Shift Z', createKeyDownEvent(Blockly.utils.KeyCodes.Z, [Blockly.utils.KeyCodes.CTRL, Blockly.utils.KeyCodes.SHIFT])], ['Meta Shift Z', createKeyDownEvent(Blockly.utils.KeyCodes.Z, [Blockly.utils.KeyCodes.META, Blockly.utils.KeyCodes.SHIFT])], ['Alt Shift Z', createKeyDownEvent(Blockly.utils.KeyCodes.Z, [Blockly.utils.KeyCodes.ALT, Blockly.utils.KeyCodes.SHIFT])], ]; // Undo. suite('Simple', function() { testCases.forEach(function(testCase) { const testCaseName = testCase[0]; const keyEvent = testCase[1]; test(testCaseName, function() { document.dispatchEvent(keyEvent); sinon.assert.calledOnce(this.redoSpy); sinon.assert.calledWith(this.redoSpy, true); sinon.assert.calledOnce(this.hideChaffSpy); }); }); }); // Do not undo if a gesture is in progress. suite('Gesture in progress', function() { testCases.forEach(function(testCase) { const testCaseName = testCase[0]; const keyEvent = testCase[1]; test(testCaseName, function() { sinon.stub(Blockly.Gesture, 'inProgress').returns(true); document.dispatchEvent(keyEvent); sinon.assert.notCalled(this.redoSpy); sinon.assert.notCalled(this.hideChaffSpy); }); }); }); // Do not undo if the workspace is in readOnly mode. suite('Not called when readOnly is true', function() { testCases.forEach(function(testCase) { const testCaseName = testCase[0]; const keyEvent = testCase[1]; runReadOnlyTest(keyEvent, testCaseName); }); }); }); suite('UndoWindows', function() { setup(function() { this.ctrlYEvent = createKeyDownEvent(Blockly.utils.KeyCodes.Y, [Blockly.utils.KeyCodes.CTRL]); this.undoSpy = sinon.spy(this.workspace, 'undo'); this.hideChaffSpy = sinon.spy( Blockly.WorkspaceSvg.prototype, 'hideChaff'); }); test('Simple', function() { document.dispatchEvent(this.ctrlYEvent); sinon.assert.calledOnce(this.undoSpy); sinon.assert.calledWith(this.undoSpy, true); sinon.assert.calledOnce(this.hideChaffSpy); }); test('Not called when a gesture is in progress', function() { sinon.stub(Blockly.Gesture, 'inProgress').returns(true); document.dispatchEvent(this.ctrlYEvent); sinon.assert.notCalled(this.undoSpy); sinon.assert.notCalled(this.hideChaffSpy); }); runReadOnlyTest(createKeyDownEvent(Blockly.utils.KeyCodes.Y, [Blockly.utils.KeyCodes.CTRL])); }); });