}
- * @protected
- */
- this.workspaceStates = {};
-
- /**
- * An optional method that allows a developer to customize how to handle
- * logs, warnings, and errors. The first argument is one of 'log', 'warn',
- * or 'error'. The second argument is the message.
- * @type {?function(Constants.LOGGING_MSG_TYPE, string)}
- * @public
- */
- this.loggingCallback = null;
-
- /**
- * The distance to move the cursor when the cursor is on the workspace.
- * @type {number}
- * @public
- */
- this.WS_MOVE_DISTANCE = 40;
-
- /**
- * The name of the marker to use for keyboard navigation.
- * @type {string}
- * @public
- */
- this.MARKER_NAME = 'local_marker_1';
-
- /**
- * The default coordinate to use when focusing on the workspace and no
- * blocks are present. In pixel coordinates, but will be converted to
- * workspace coordinates when used to position the cursor.
- * @type {!Blockly.utils.Coordinate}
- * @public
- */
- this.DEFAULT_WS_COORDINATE = new Blockly.utils.Coordinate(100, 100);
-
- /**
- * The default coordinate to use when moving the cursor to the workspace
- * after a block has been deleted. In pixel coordinates, but will be
- * converted to workspace coordinates when used to position the cursor.
- * @type {!Blockly.utils.Coordinate}
- * @public
- */
- this.WS_COORDINATE_ON_DELETE = new Blockly.utils.Coordinate(100, 100);
-
- /**
- * Wrapper for method that deals with workspace changes.
- * Used for removing change listener.
- * @type {Function}
- * @protected
- */
- this.wsChangeWrapper = this.workspaceChangeListener.bind(this);
-
- /**
- * Wrapper for method that deals with flyout changes.
- * Used for removing change listener.
- * @type {Function}
- * @protected
- */
- this.flyoutChangeWrapper = this.flyoutChangeListener.bind(this);
-
- /**
- * The list of registered workspaces.
- * Used when removing change listeners in dispose.
- * @type {!Array}
- * @protected
- */
- this.workspaces = [];
- }
-
- /**
- * Adds all necessary change listeners and markers to a workspace for keyboard
- * navigation to work. This must be called for keyboard navigation to work
- * on a workspace.
- * @param {!Blockly.WorkspaceSvg} workspace The workspace to add keyboard
- * navigation to.
- * @public
- */
- addWorkspace(workspace) {
- this.workspaces.push(workspace);
- const flyout = workspace.getFlyout();
- workspace
- .getMarkerManager()
- .registerMarker(this.MARKER_NAME, new Blockly.Marker());
- workspace.addChangeListener(this.wsChangeWrapper);
-
- if (flyout) {
- this.addFlyout(flyout);
- }
- }
-
- /**
- * Removes all keyboard navigation change listeners and markers.
- * @param {!Blockly.WorkspaceSvg} workspace The workspace to remove keyboard
- * navigation from.
- * @public
- */
- removeWorkspace(workspace) {
- const workspaceIdx = this.workspaces.indexOf(workspace);
- const flyout = workspace.getFlyout();
-
- if (workspace.getCursor()) {
- this.disableKeyboardAccessibility(workspace);
- }
-
- if (workspaceIdx > -1) {
- this.workspaces.splice(workspaceIdx, 1);
- }
- if (workspace.getMarkerManager()) {
- workspace.getMarkerManager().unregisterMarker(this.MARKER_NAME);
- }
- workspace.removeChangeListener(this.wsChangeWrapper);
-
- if (flyout) {
- this.removeFlyout(flyout);
- }
- }
-
- /**
- * Sets the state for the given workspace.
- * @param {!Blockly.WorkspaceSvg} workspace The workspace to set the state on.
- * @param {!Constants.STATE} state The navigation state.
- * @protected
- */
- setState(workspace, state) {
- this.workspaceStates[workspace.id] = state;
- }
-
- /**
- * Gets the navigation state of the current workspace.
- * @param {!Blockly.WorkspaceSvg} workspace The workspace to get the state of.
- * @returns {!Constants.STATE} The state of the given workspace.
- * @package
- */
- getState(workspace) {
- return this.workspaceStates[workspace.id];
- }
-
- /**
- * Gets the marker created for keyboard navigation.
- * @param {!Blockly.WorkspaceSvg} workspace The workspace to get the marker
- * from.
- * @returns {?Blockly.Marker} The marker created for keyboard navigation.
- * @protected
- */
- getMarker(workspace) {
- return workspace.getMarker(this.MARKER_NAME);
- }
-
- /**
- * Adds all event listeners and cursors to the flyout that are needed for
- * keyboard navigation to work.
- * @param {!Blockly.IFlyout} flyout The flyout to add a cursor and change
- * listeners to.
- * @protected
- */
- addFlyout(flyout) {
- const flyoutWorkspace = flyout.getWorkspace();
- flyoutWorkspace.addChangeListener(this.flyoutChangeWrapper);
- const FlyoutCursorClass = Blockly.registry.getClass(
- cursorRegistrationType,
- cursorRegistrationName,
- );
- flyoutWorkspace.getMarkerManager().setCursor(new FlyoutCursorClass());
- }
-
- /**
- * Removes all change listeners from the flyout that are needed for
- * keyboard navigation to work.
- * @param {!Blockly.IFlyout} flyout The flyout to add a cursor and event
- * listeners to.
- * @protected
- */
- removeFlyout(flyout) {
- const flyoutWorkspace = flyout.getWorkspace();
- flyoutWorkspace.removeChangeListener(this.flyoutChangeWrapper);
- }
-
- /**
- * Updates the state of keyboard navigation and the position of the cursor
- * based on workspace events.
- * @param {!Blockly.Events.Abstract} e The Blockly event to process.
- * @protected
- */
- workspaceChangeListener(e) {
- const workspace = Blockly.Workspace.getById(e.workspaceId);
- if (!workspace || !workspace.keyboardAccessibilityMode) {
- return;
- }
- switch (e.type) {
- case Blockly.Events.DELETE:
- this.handleBlockDeleteByDrag(workspace, e);
- break;
- case Blockly.Events.BLOCK_CHANGE:
- if (e.element === 'mutation') {
- this.handleBlockMutation(
- workspace,
- /** @type {Blockly.Events.BlockChange} */ (e),
- );
- }
- break;
- case Blockly.Events.CLICK:
- this.handleWorkspaceClick(
- workspace,
- /** @type {Blockly.Events.Click} */ (e),
- );
- break;
- case Blockly.Events.TOOLBOX_ITEM_SELECT:
- this.handleToolboxCategoryClick(
- workspace,
- /** @type {Blockly.Events.ToolboxItemSelect} */ (e),
- );
- break;
- case Blockly.Events.BLOCK_CREATE:
- this.handleBlockCreate(workspace, e);
- }
- }
-
- /**
- * Updates the state of keyboard navigation and the position of the cursor
- * based on events emitted from the flyout's workspace.
- * @param {!Blockly.Events.Abstract} e The Blockly event to process.
- * @protected
- */
- flyoutChangeListener(e) {
- const flyoutWorkspace = Blockly.Workspace.getById(e.workspaceId);
- const mainWorkspace = flyoutWorkspace.targetWorkspace;
- const flyout = mainWorkspace.getFlyout();
-
- // This is called for simple toolboxes and for toolboxes that have a flyout
- // that does not close. Autoclosing flyouts close before we need to focus
- // the cursor on the block that was clicked.
- if (
- mainWorkspace &&
- mainWorkspace.keyboardAccessibilityMode &&
- !flyout.autoClose
- ) {
- if (e.type === Blockly.Events.CLICK && e.targetType === 'block') {
- const block = flyoutWorkspace.getBlockById(e.blockId);
- this.handleBlockClickInFlyout(mainWorkspace, block);
- } else if (e.type === Blockly.Events.SELECTED) {
- const block = flyoutWorkspace.getBlockById(e.newElementId);
- this.handleBlockClickInFlyout(mainWorkspace, block);
- }
- }
- }
-
- /**
- * Moves the cursor to the workspace if a block has been dragged from a simple
- * toolbox. For a category toolbox this is handled in
- * handleToolboxCategoryClick_.
- * @param {!Blockly.WorkspaceSvg} workspace The workspace the cursor belongs
- * to.
- * @param {!Blockly.Events.Abstract} e The Blockly event to process.
- * @protected
- */
- handleBlockCreate(workspace, e) {
- if (this.getState(workspace) === Constants.STATE.FLYOUT) {
- this.resetFlyout(workspace, !!workspace.getToolbox());
- this.setState(workspace, Constants.STATE.WORKSPACE);
- }
- }
-
- /**
- * Moves the cursor to the block level when the block the cursor is on
- * mutates.
- * @param {!Blockly.WorkspaceSvg} workspace The workspace the cursor belongs
- * to.
- * @param {!Blockly.Events.BlockChange} e The Blockly event to process.
- * @protected
- */
- handleBlockMutation(workspace, e) {
- const mutatedBlockId = e.blockId;
- const cursor = workspace.getCursor();
- if (cursor) {
- const curNode = cursor.getCurNode();
- const block = curNode ? curNode.getSourceBlock() : null;
- if (block && block.id === mutatedBlockId) {
- cursor.setCurNode(Blockly.ASTNode.createBlockNode(block));
- }
- }
- }
-
- /**
- * Moves the cursor to the workspace when a user clicks on the workspace.
- * @param {!Blockly.WorkspaceSvg} workspace The workspace the cursor belongs
- * to.
- * @param {!Blockly.Events.Click} e The Blockly event to process.
- * @protected
- */
- handleWorkspaceClick(workspace, e) {
- const workspaceState = this.getState(workspace);
- if (workspaceState !== Constants.STATE.WORKSPACE) {
- this.resetFlyout(workspace, !!workspace.getToolbox());
- this.setState(workspace, Constants.STATE.WORKSPACE);
- }
- }
-
- /**
- * Moves the cursor to the toolbox when a user clicks on a toolbox category.
- * Moves the cursor to the workspace if theh user closes the toolbox category.
- * @param {!Blockly.WorkspaceSvg} workspace The workspace the toolbox is on.
- * @param {!Blockly.Events.ToolboxItemSelect} e The event emitted from the
- * workspace.
- * @protected
- */
- handleToolboxCategoryClick(workspace, e) {
- const workspaceState = this.getState(workspace);
- if (e.newItem && workspaceState !== Constants.STATE.TOOLBOX) {
- // If the toolbox category was just clicked, focus on the toolbox.
- this.focusToolbox(workspace);
- } else if (!e.newItem) {
- // If the toolbox was closed, focus on the workspace.
- this.resetFlyout(workspace, !!workspace.getToolbox());
- this.setState(workspace, Constants.STATE.WORKSPACE);
- }
- }
-
- /**
- * Moves the cursor to the workspace when its parent block is deleted by
- * being dragged to the flyout or to the trashcan.
- * @param {!Blockly.WorkspaceSvg} workspace The workspace the block was on.
- * @param {!Blockly.Events.Delete} e The event emitted when a block is
- * deleted.
- * @protected
- */
- handleBlockDeleteByDrag(workspace, e) {
- const deletedBlockId = e.blockId;
- const ids = e.ids;
- const cursor = workspace.getCursor();
-
- // Make sure the cursor is on a block.
- if (
- !cursor ||
- !cursor.getCurNode() ||
- !cursor.getCurNode().getSourceBlock()
- ) {
- return;
- }
-
- const curNode = cursor.getCurNode();
- const sourceBlock = curNode.getSourceBlock();
- if (sourceBlock.id === deletedBlockId || ids.indexOf(sourceBlock.id) > -1) {
- cursor.setCurNode(
- Blockly.ASTNode.createWorkspaceNode(
- workspace,
- this.WS_COORDINATE_ON_DELETE,
- ),
- );
- }
- }
-
- /**
- * Handles when a user clicks on a block in the flyout by moving the cursor
- * to that stack of blocks and setting the state of navigation to the flyout.
- * @param {!Blockly.WorkspaceSvg} mainWorkspace The workspace the user clicked
- * on.
- * @param {!Blockly.BlockSvg} block The block the user clicked on.
- * @protected
- */
- handleBlockClickInFlyout(mainWorkspace, block) {
- if (!block) {
- return;
- }
- if (block.isShadow()) {
- block = /** @type {Blockly.BlockSvg}*/ (block.getParent());
- }
- this.getFlyoutCursor(mainWorkspace).setCurNode(
- Blockly.ASTNode.createStackNode(block),
- );
- this.setState(mainWorkspace, Constants.STATE.FLYOUT);
- }
-
- /**
- * Moves the cursor to the appropriate location before a block is deleted.
- * This is used when the user deletes a block using the delete or backspace
- * key.
- * @param {!Blockly.WorkspaceSvg} workspace The workspace the block is being
- * deleted on.
- * @param {!Blockly.BlockSvg} deletedBlock The block that is being deleted.
- * @package
- */
- moveCursorOnBlockDelete(workspace, deletedBlock) {
- if (!workspace || !workspace.getCursor()) {
- return;
- }
- const cursor = workspace.getCursor();
- const curNode = cursor.getCurNode();
- const block = curNode ? curNode.getSourceBlock() : null;
-
- if (block === deletedBlock) {
- // If the block has a parent move the cursor to their connection point.
- if (block.getParent()) {
- const topConnection =
- block.previousConnection || block.outputConnection;
- if (topConnection) {
- cursor.setCurNode(
- Blockly.ASTNode.createConnectionNode(
- topConnection.targetConnection,
- ),
- );
- }
- } else {
- // If the block is by itself move the cursor to the workspace.
- cursor.setCurNode(
- Blockly.ASTNode.createWorkspaceNode(
- block.workspace,
- block.getRelativeToSurfaceXY(),
- ),
- );
- }
- // If the cursor is on a block whose parent is being deleted, move the
- // cursor to the workspace.
- } else if (block && deletedBlock.getChildren(false).indexOf(block) > -1) {
- cursor.setCurNode(
- Blockly.ASTNode.createWorkspaceNode(
- block.workspace,
- block.getRelativeToSurfaceXY(),
- ),
- );
- }
- }
-
- /**
- * Sets the navigation state to toolbox and selects the first category in the
- * toolbox. No-op if a toolbox does not exist on the given workspace.
- * @param {!Blockly.WorkspaceSvg} workspace The workspace to get the toolbox
- * on.
- * @package
- */
- focusToolbox(workspace) {
- const toolbox = workspace.getToolbox();
- if (!toolbox) {
- return;
- }
-
- this.setState(workspace, Constants.STATE.TOOLBOX);
- this.resetFlyout(workspace, false /* shouldHide */);
-
- if (!this.getMarker(workspace).getCurNode()) {
- this.markAtCursor(workspace);
- }
-
- if (!toolbox.getSelectedItem()) {
- // Find the first item that is selectable.
- const toolboxItems = toolbox.getToolboxItems();
- for (let i = 0, toolboxItem; (toolboxItem = toolboxItems[i]); i++) {
- if (toolboxItem.isSelectable()) {
- toolbox.selectItemByPosition(i);
- break;
- }
- }
- }
- }
-
- /**
- * Sets the navigation state to flyout and moves the cursor to the first
- * block or button in the flyout.
- * @param {!Blockly.WorkspaceSvg} workspace The workspace the flyout is on.
- * @package
- */
- focusFlyout(workspace) {
- const flyout = workspace.getFlyout();
-
- this.setState(workspace, Constants.STATE.FLYOUT);
-
- if (!this.getMarker(workspace).getCurNode()) {
- this.markAtCursor(workspace);
- }
-
- if (flyout && flyout.getWorkspace()) {
- const flyoutContents = flyout.getContents();
- const firstFlyoutItem = flyoutContents[0];
- if (!firstFlyoutItem) return;
- if (firstFlyoutItem.element instanceof Blockly.FlyoutButton) {
- const astNode = Blockly.ASTNode.createButtonNode(
- firstFlyoutItem.element,
- );
- this.getFlyoutCursor(workspace).setCurNode(astNode);
- } else if (firstFlyoutItem.element instanceof Blockly.BlockSvg) {
- const astNode = Blockly.ASTNode.createStackNode(
- firstFlyoutItem.element,
- );
- this.getFlyoutCursor(workspace).setCurNode(astNode);
- }
- }
- }
-
- /**
- * Sets the navigation state to workspace and moves the cursor to either the
- * top block on a workspace or to the workspace.
- * @param {!Blockly.WorkspaceSvg} workspace The workspace to focus on.
- * @package
- */
- focusWorkspace(workspace) {
- workspace.hideChaff();
- const reset = !!workspace.getToolbox();
-
- this.resetFlyout(workspace, reset);
- this.setState(workspace, Constants.STATE.WORKSPACE);
- this.setCursorOnWorkspaceFocus(workspace);
- }
-
- /**
- * Moves the cursor to the top connection point on on the first top block.
- * If the workspace is empty, moves the cursor to the default location on
- * the workspace.
- * @param {!Blockly.WorkspaceSvg} workspace The main Blockly workspace.
- * @protected
- */
- setCursorOnWorkspaceFocus(workspace) {
- const topBlocks = workspace.getTopBlocks(true);
- const cursor = workspace.getCursor();
- const wsCoordinates = new Blockly.utils.Coordinate(
- this.DEFAULT_WS_COORDINATE.x / workspace.scale,
- this.DEFAULT_WS_COORDINATE.y / workspace.scale,
- );
- if (topBlocks.length > 0) {
- cursor.setCurNode(Blockly.ASTNode.createTopNode(topBlocks[0]));
- } else {
- const wsNode = Blockly.ASTNode.createWorkspaceNode(
- workspace,
- wsCoordinates,
- );
- cursor.setCurNode(wsNode);
- }
- }
-
- /**
- * Gets the cursor on the flyout's workspace.
- * @param {!Blockly.WorkspaceSvg} workspace The main workspace the flyout is
- * on.
- * @returns {?Blockly.FlyoutCursor} The flyout's cursor or null if no flyout
- * exists.
- * @protected
- */
- getFlyoutCursor(workspace) {
- const flyout = workspace.getFlyout();
- const cursor = flyout ? flyout.getWorkspace().getCursor() : null;
-
- return /** @type {?Blockly.FlyoutCursor} */ (cursor);
- }
-
- /**
- * Inserts a block from the flyout.
- * Tries to find a connection on the block to connect to the marked
- * location. If no connection has been marked, or there is not a compatible
- * connection then the block is placed on the workspace.
- * @param {!Blockly.WorkspaceSvg} workspace The main workspace. The workspace
- * the block will be placed on.
- * @package
- */
- insertFromFlyout(workspace) {
- const newBlock = this.createNewBlock(workspace);
- if (!newBlock) {
- return;
- }
- const markerNode = this.getMarker(workspace).getCurNode();
- if (
- !this.tryToConnectMarkerAndCursor(
- workspace,
- markerNode,
- Blockly.ASTNode.createBlockNode(newBlock),
- )
- ) {
- this.warn(
- 'Something went wrong while inserting a block from the flyout.',
- );
- }
-
- this.focusWorkspace(workspace);
- workspace.getCursor().setCurNode(Blockly.ASTNode.createTopNode(newBlock));
- this.removeMark(workspace);
- }
-
- /**
- * Creates a new block based on the current block the flyout cursor is on.
- * @param {!Blockly.WorkspaceSvg} workspace The main workspace. The workspace
- * the block will be placed on.
- * @returns {?Blockly.BlockSvg} The newly created block.
- * @protected
- */
- createNewBlock(workspace) {
- const flyout = workspace.getFlyout();
- if (!flyout || !flyout.isVisible()) {
- this.warn(
- 'Trying to insert from the flyout when the flyout does not ' +
- ' exist or is not visible',
- );
- return null;
- }
-
- const curBlock = /** @type {!Blockly.BlockSvg} */ (
- this.getFlyoutCursor(workspace).getCurNode().getLocation()
- );
- if (!curBlock.isEnabled()) {
- this.warn("Can't insert a disabled block.");
- return null;
- }
-
- const newBlock = flyout.createBlock(curBlock);
- // Render to get the sizing right.
- newBlock.render();
- // Connections are not tracked when the block is first created. Normally
- // there's enough time for them to become tracked in the user's mouse
- // movements, but not here.
- newBlock.setConnectionTracking(true);
- return newBlock;
- }
-
- /**
- * Hides the flyout cursor and optionally hides the flyout.
- * @param {!Blockly.WorkspaceSvg} workspace The workspace.
- * @param {boolean} shouldHide True if the flyout should be hidden.
- * @protected
- */
- resetFlyout(workspace, shouldHide) {
- if (this.getFlyoutCursor(workspace)) {
- this.getFlyoutCursor(workspace).hide();
- if (shouldHide) {
- workspace.getFlyout().hide();
- }
- }
- }
-
- /**
- * Connects the location of the marker and the location of the cursor.
- * No-op if the marker or cursor node are null.
- * @param {!Blockly.WorkspaceSvg} workspace The main workspace.
- * @returns {boolean} True if the cursor and marker locations were connected,
- * false otherwise.
- * @package
- */
- connectMarkerAndCursor(workspace) {
- const markerNode = this.getMarker(workspace).getCurNode();
- const cursorNode = workspace.getCursor().getCurNode();
-
- if (markerNode && cursorNode) {
- return this.tryToConnectMarkerAndCursor(
- workspace,
- markerNode,
- cursorNode,
- );
- }
- return false;
- }
-
- /**
- * Tries to connect the given marker and cursor node.
- * @param {!Blockly.WorkspaceSvg} workspace The main workspace.
- * @param {!Blockly.ASTNode} markerNode The node to try to connect to.
- * @param {!Blockly.ASTNode} cursorNode The node to connect to the markerNode.
- * @returns {boolean} True if the key was handled; false if something went
- * wrong.
- * @protected
- */
- tryToConnectMarkerAndCursor(workspace, markerNode, cursorNode) {
- if (!this.logConnectionWarning(markerNode, cursorNode)) {
- return false;
- }
-
- const markerType = markerNode.getType();
- const cursorType = cursorNode.getType();
-
- const cursorLoc = cursorNode.getLocation();
- const markerLoc = markerNode.getLocation();
- if (markerNode.isConnection() && cursorNode.isConnection()) {
- const cursorConnection = /** @type {!Blockly.RenderedConnection} */ (
- cursorLoc
- );
- const markerConnection = /** @type {!Blockly.RenderedConnection} */ (
- markerLoc
- );
- return this.connect(cursorConnection, markerConnection);
- } else if (
- markerNode.isConnection() &&
- (cursorType == Blockly.ASTNode.types.BLOCK ||
- cursorType == Blockly.ASTNode.types.STACK)
- ) {
- const cursorBlock = /** @type {!Blockly.BlockSvg} */ (cursorLoc);
- const markerConnection = /** @type {!Blockly.RenderedConnection} */ (
- markerLoc
- );
- return this.insertBlock(cursorBlock, markerConnection);
- } else if (markerType == Blockly.ASTNode.types.WORKSPACE) {
- const block = cursorNode ? cursorNode.getSourceBlock() : null;
- return this.moveBlockToWorkspace(
- /** @type {Blockly.BlockSvg} */ (block),
- markerNode,
- );
- }
- this.warn('Unexpected state in tryToConnectMarkerAndCursor.');
- return false;
- }
-
- /**
- * Warns the user if the given cursor or marker node can not be connected.
- * @param {!Blockly.ASTNode} markerNode The node to try to connect to.
- * @param {!Blockly.ASTNode} cursorNode The node to connect to the markerNode.
- * @returns {boolean} True if the marker and cursor are valid types, false
- * otherwise.
- * @protected
- */
- logConnectionWarning(markerNode, cursorNode) {
- if (!markerNode) {
- this.warn('Cannot insert with no marked node.');
- return false;
- }
-
- if (!cursorNode) {
- this.warn('Cannot insert with no cursor node.');
- return false;
- }
- const markerType = markerNode.getType();
- const cursorType = cursorNode.getType();
-
- // Check the marker for invalid types.
- if (markerType == Blockly.ASTNode.types.FIELD) {
- this.warn('Should not have been able to mark a field.');
- return false;
- } else if (markerType == Blockly.ASTNode.types.BLOCK) {
- this.warn('Should not have been able to mark a block.');
- return false;
- } else if (markerType == Blockly.ASTNode.types.STACK) {
- this.warn('Should not have been able to mark a stack.');
- return false;
- }
-
- // Check the cursor for invalid types.
- if (cursorType == Blockly.ASTNode.types.FIELD) {
- this.warn('Cannot attach a field to anything else.');
- return false;
- } else if (cursorType == Blockly.ASTNode.types.WORKSPACE) {
- this.warn('Cannot attach a workspace to anything else.');
- return false;
- }
- return true;
- }
-
- /**
- * Disconnects the block from its parent and moves it to the position of the
- * workspace node.
- * @param {?Blockly.BlockSvg} block The block to be moved to the workspace.
- * @param {!Blockly.ASTNode} wsNode The workspace node holding the position
- * the block will be moved to.
- * @returns {boolean} True if the block can be moved to the workspace,
- * false otherwise.
- * @protected
- */
- moveBlockToWorkspace(block, wsNode) {
- if (!block) {
- return false;
- }
- if (block.isShadow()) {
- this.warn('Cannot move a shadow block to the workspace.');
- return false;
- }
- if (block.getParent()) {
- block.unplug(false);
- }
- block.moveTo(wsNode.getWsCoordinate());
- return true;
- }
-
- /**
- * Disconnects the child block from its parent block. No-op if the two given
- * connections are unrelated.
- * @param {!Blockly.RenderedConnection} movingConnection The connection that
- * is being moved.
- * @param {!Blockly.RenderedConnection} destConnection The connection to be
- * moved to.
- * @protected
- */
- disconnectChild(movingConnection, destConnection) {
- const movingBlock = movingConnection.getSourceBlock();
- const destBlock = destConnection.getSourceBlock();
- let inferiorConnection;
-
- if (movingBlock.getRootBlock() === destBlock.getRootBlock()) {
- if (movingBlock.getDescendants(false).indexOf(destBlock) > -1) {
- inferiorConnection = this.getInferiorConnection(destConnection);
- if (inferiorConnection) {
- inferiorConnection.disconnect();
- }
- } else {
- inferiorConnection = this.getInferiorConnection(movingConnection);
- if (inferiorConnection) {
- inferiorConnection.disconnect();
- }
- }
- }
- }
-
- /**
- * Tries to connect the given connections.
- *
- * If the given connections are not compatible try finding compatible
- * connections on the source blocks of the given connections.
- * @param {?Blockly.RenderedConnection} movingConnection The connection that
- * is being moved.
- * @param {?Blockly.RenderedConnection} destConnection The connection to be
- * moved to.
- * @returns {boolean} True if the two connections or their target connections
- * were connected, false otherwise.
- * @protected
- */
- connect(movingConnection, destConnection) {
- if (!movingConnection || !destConnection) {
- return false;
- }
-
- const movingInferior = this.getInferiorConnection(movingConnection);
- const destSuperior = this.getSuperiorConnection(destConnection);
-
- const movingSuperior = this.getSuperiorConnection(movingConnection);
- const destInferior = this.getInferiorConnection(destConnection);
-
- if (
- movingInferior &&
- destSuperior &&
- this.moveAndConnect(movingInferior, destSuperior)
- ) {
- return true;
- // Try swapping the inferior and superior connections on the blocks.
- } else if (
- movingSuperior &&
- destInferior &&
- this.moveAndConnect(movingSuperior, destInferior)
- ) {
- return true;
- } else if (this.moveAndConnect(movingConnection, destConnection)) {
- return true;
- } else {
- const checker = movingConnection.getConnectionChecker();
- const reason = checker.canConnectWithReason(
- movingConnection,
- destConnection,
- false,
- );
- this.warn(
- 'Connection failed with error: ' +
- checker.getErrorMessage(reason, movingConnection, destConnection),
- );
- return false;
- }
- }
-
- /**
- * Finds the inferior connection on the source block if the given connection
- * is superior.
- * @param {?Blockly.RenderedConnection} connection The connection trying to be
- * connected.
- * @returns {?Blockly.RenderedConnection} The inferior connection or null if
- * none exists.
- * @protected
- */
- getInferiorConnection(connection) {
- const block = /** @type{!Blockly.BlockSvg} */ (connection.getSourceBlock());
- if (!connection.isSuperior()) {
- return connection;
- } else if (block.previousConnection) {
- return block.previousConnection;
- } else if (block.outputConnection) {
- return block.outputConnection;
- } else {
- return null;
- }
- }
-
- /**
- * Finds a superior connection on the source block if the given connection is
- * inferior.
- * @param {?Blockly.RenderedConnection} connection The connection trying to be
- * connected.
- * @returns {?Blockly.RenderedConnection} The superior connection or null if
- * none exists.
- * @protected
- */
- getSuperiorConnection(connection) {
- if (connection.isSuperior()) {
- return connection;
- } else if (connection.targetConnection) {
- return connection.targetConnection;
- }
- return null;
- }
-
- /**
- * Moves the moving connection to the target connection and connects them.
- * @param {?Blockly.RenderedConnection} movingConnection The connection that
- * is being moved.
- * @param {?Blockly.RenderedConnection} destConnection The connection to be
- * moved to.
- * @returns {boolean} True if the connections were connected, false otherwise.
- * @protected
- */
- moveAndConnect(movingConnection, destConnection) {
- if (!movingConnection || !destConnection) {
- return false;
- }
- const movingBlock = movingConnection.getSourceBlock();
- const checker = movingConnection.getConnectionChecker();
-
- if (
- checker.canConnect(movingConnection, destConnection, false) &&
- !destConnection.getSourceBlock().isShadow()
- ) {
- this.disconnectChild(movingConnection, destConnection);
-
- // Position the root block near the connection so it does not move the
- // other block when they are connected.
- if (!destConnection.isSuperior()) {
- const rootBlock = movingBlock.getRootBlock();
-
- const originalOffsetToTarget = {
- x: destConnection.x - movingConnection.x,
- y: destConnection.y - movingConnection.y,
- };
- const originalOffsetInBlock = movingConnection
- .getOffsetInBlock()
- .clone();
- rootBlock.positionNearConnection(
- movingConnection,
- originalOffsetToTarget,
- originalOffsetInBlock,
- );
- }
- destConnection.connect(movingConnection);
- return true;
- }
- return false;
- }
-
- /**
- * Tries to connect the given block to the destination connection, making an
- * intelligent guess about which connection to use on the moving block.
- * @param {!Blockly.BlockSvg} block The block to move.
- * @param {!Blockly.RenderedConnection} destConnection The connection to
- * connect to.
- * @returns {boolean} Whether the connection was successful.
- * @protected
- */
- insertBlock(block, destConnection) {
- switch (destConnection.type) {
- case Blockly.PREVIOUS_STATEMENT:
- if (this.connect(block.nextConnection, destConnection)) {
- return true;
- }
- break;
- case Blockly.NEXT_STATEMENT:
- if (this.connect(block.previousConnection, destConnection)) {
- return true;
- }
- break;
- case Blockly.INPUT_VALUE:
- if (this.connect(block.outputConnection, destConnection)) {
- return true;
- }
- break;
- case Blockly.OUTPUT_VALUE:
- for (let i = 0; i < block.inputList.length; i++) {
- const inputConnection = /** @type {Blockly.RenderedConnection} */ (
- block.inputList[i].connection
- );
- if (
- inputConnection &&
- inputConnection.type === Blockly.INPUT_VALUE &&
- this.connect(inputConnection, destConnection)
- ) {
- return true;
- }
- }
- // If there are no input values pass the output and destination
- // connections to connect_ to find a way to connect the two.
- if (
- block.outputConnection &&
- this.connect(block.outputConnection, destConnection)
- ) {
- return true;
- }
- break;
- }
- this.warn('This block can not be inserted at the marked location.');
- return false;
- }
-
- /**
- * Disconnects the connection that the cursor is pointing to, and bump blocks.
- * This is a no-op if the connection cannot be broken or if the cursor is not
- * pointing to a connection.
- * @param {!Blockly.WorkspaceSvg} workspace The workspace.
- * @package
- */
- disconnectBlocks(workspace) {
- const curNode = workspace.getCursor().getCurNode();
- if (!curNode.isConnection()) {
- this.log(
- 'Cannot disconnect blocks when the cursor is not on a connection',
- );
- return;
- }
- const curConnection = /** @type {!Blockly.RenderedConnection} */ (
- curNode.getLocation()
- );
- if (!curConnection.isConnected()) {
- this.log('Cannot disconnect unconnected connection');
- return;
- }
- const superiorConnection = curConnection.isSuperior()
- ? curConnection
- : curConnection.targetConnection;
-
- const inferiorConnection = curConnection.isSuperior()
- ? curConnection.targetConnection
- : curConnection;
-
- if (inferiorConnection.getSourceBlock().isShadow()) {
- this.log('Cannot disconnect a shadow block');
- return;
- }
- superiorConnection.disconnect();
- inferiorConnection.bumpAwayFrom(superiorConnection);
-
- const rootBlock = superiorConnection.getSourceBlock().getRootBlock();
- rootBlock.bringToFront();
-
- const connectionNode =
- Blockly.ASTNode.createConnectionNode(superiorConnection);
- workspace.getCursor().setCurNode(connectionNode);
- }
-
- /**
- * Moves the marker to the cursor's current location.
- * @param {!Blockly.WorkspaceSvg} workspace The workspace.
- * @protected
- */
- markAtCursor(workspace) {
- this.getMarker(workspace).setCurNode(workspace.getCursor().getCurNode());
- }
-
- /**
- * Removes the marker from its current location and hide it.
- * @param {!Blockly.WorkspaceSvg} workspace The workspace.
- * @protected
- */
- removeMark(workspace) {
- const marker = this.getMarker(workspace);
- marker.setCurNode(null);
- marker.hide();
- }
-
- /**
- * Enables accessibility mode.
- * @param {!Blockly.WorkspaceSvg} workspace The workspace to enable keyboard
- * accessibility mode on.
- * @package
- */
- enableKeyboardAccessibility(workspace) {
- if (
- this.workspaces.indexOf(workspace) > -1 &&
- !workspace.keyboardAccessibilityMode
- ) {
- workspace.keyboardAccessibilityMode = true;
- this.focusWorkspace(workspace);
- }
- }
-
- /**
- * Disables accessibility mode.
- * @param {!Blockly.WorkspaceSvg} workspace The workspace to disable keyboard
- * accessibility mode on.
- * @package
- */
- disableKeyboardAccessibility(workspace) {
- if (
- this.workspaces.indexOf(workspace) > -1 &&
- workspace.keyboardAccessibilityMode
- ) {
- workspace.keyboardAccessibilityMode = false;
- workspace.getCursor().hide();
- this.getMarker(workspace).hide();
- if (this.getFlyoutCursor(workspace)) {
- this.getFlyoutCursor(workspace).hide();
- }
- }
- }
-
- /**
- * Navigation log handler. If loggingCallback is defined, use it.
- * Otherwise just log to the console.log.
- * @param {string} msg The message to log.
- * @protected
- */
- log(msg) {
- if (this.loggingCallback) {
- this.loggingCallback(Constants.LOGGING_MSG_TYPE.LOG, msg);
- } else {
- console.log(msg);
- }
- }
-
- /**
- * Navigation warning handler. If loggingCallback is defined, use it.
- * Otherwise call console.warn.
- * @param {string} msg The warning message.
- * @protected
- */
- warn(msg) {
- if (this.loggingCallback) {
- this.loggingCallback(Constants.LOGGING_MSG_TYPE.WARN, msg);
- } else {
- console.warn(msg);
- }
- }
-
- /**
- * Navigation error handler. If loggingCallback is defined, use it.
- * Otherwise call console.error.
- * @param {string} msg The error message.
- * @protected
- */
- error(msg) {
- if (this.loggingCallback) {
- this.loggingCallback(Constants.LOGGING_MSG_TYPE.ERROR, msg);
- } else {
- console.error(msg);
- }
- }
-
- /**
- * Moves the workspace cursor in the given direction.
- * @param {!Blockly.WorkspaceSvg} workspace The workspace the cursor is on.
- * @param {number} xDirection -1 to move cursor left. 1 to move cursor right.
- * @param {number} yDirection -1 to move cursor up. 1 to move cursor down.
- * @returns {boolean} True if the current node is a workspace, false
- * otherwise.
- * @package
- */
- moveWSCursor(workspace, xDirection, yDirection) {
- const cursor = workspace.getCursor();
- const curNode = workspace.getCursor().getCurNode();
-
- if (curNode.getType() !== Blockly.ASTNode.types.WORKSPACE) {
- return false;
- }
-
- const wsCoord = curNode.getWsCoordinate();
- const newX = xDirection * this.WS_MOVE_DISTANCE + wsCoord.x;
- const newY = yDirection * this.WS_MOVE_DISTANCE + wsCoord.y;
-
- cursor.setCurNode(
- Blockly.ASTNode.createWorkspaceNode(
- workspace,
- new Blockly.utils.Coordinate(newX, newY),
- ),
- );
- return true;
- }
-
- /**
- * Handles hitting the enter key on the workspace.
- * @param {!Blockly.WorkspaceSvg} workspace The workspace.
- * @package
- */
- handleEnterForWS(workspace) {
- const cursor = workspace.getCursor();
- const curNode = cursor.getCurNode();
- const nodeType = curNode.getType();
- if (nodeType == Blockly.ASTNode.types.FIELD) {
- /** @type {!Blockly.Field} */ (curNode.getLocation()).showEditor();
- } else if (
- curNode.isConnection() ||
- nodeType == Blockly.ASTNode.types.WORKSPACE
- ) {
- this.markAtCursor(workspace);
- } else if (nodeType == Blockly.ASTNode.types.BLOCK) {
- this.warn('Cannot mark a block.');
- } else if (nodeType == Blockly.ASTNode.types.STACK) {
- this.warn('Cannot mark a stack.');
- }
- }
-
- /**
- * Pastes the copied block to the marked location.
- * @param {Blockly.BlockCopyData} copyData The data
- * to paste into the workspace.
- * @param {Blockly.WorkspaceSvg} workspace The workspace to paste the data
- * into.
- * @returns {boolean} True if the paste was sucessful, false otherwise.
- * @package
- */
- paste(copyData, workspace) {
- let isHandled = false;
- Blockly.Events.setGroup(true);
- const block = /** @type {Blockly.BlockSvg} */ (
- Blockly.clipboard.paste(copyData, workspace)
- );
- if (block) {
- isHandled = this.insertPastedBlock(workspace, block);
- }
- Blockly.Events.setGroup(false);
- return isHandled;
- }
-
- /**
- * Inserts the pasted block at the marked location if a compatible connection
- * exists. If no connection has been marked, or there is not a compatible
- * connection then the block is placed on the workspace.
- * @param {!Blockly.WorkspaceSvg} workspace The workspace to paste the block
- * on.
- * @param {!Blockly.BlockSvg} block The block to paste.
- * @returns {boolean} True if the block was pasted to the workspace, false
- * otherwise.
- * @protected
- */
- insertPastedBlock(workspace, block) {
- let isHandled = false;
- const markedNode = workspace.getMarker(this.MARKER_NAME).getCurNode();
- if (markedNode) {
- isHandled = this.tryToConnectMarkerAndCursor(
- workspace,
- markedNode,
- Blockly.ASTNode.createBlockNode(block),
- );
- }
- return isHandled;
- }
-
- /**
- * Triggers a flyout button's callback.
- * @param {!Blockly.WorkspaceSvg} workspace The main workspace. The workspace
- * containing a flyout with a button.
- * @package
- */
- triggerButtonCallback(workspace) {
- const button = /** @type {!Blockly.FlyoutButton} */ (
- this.getFlyoutCursor(workspace).getCurNode().getLocation()
- );
- const buttonCallback = workspace.flyoutButtonCallbacks.get(
- button.callbackKey,
- );
- if (typeof buttonCallback === 'function') {
- buttonCallback(button);
- } else {
- throw new Error('No callback function found for flyout button.');
- }
- }
-
- /**
- * Removes the change listeners on all registered workspaces.
- * @package
- */
- dispose() {
- for (const workspace of this.workspaces) {
- this.removeWorkspace(workspace);
- }
- }
-}
diff --git a/plugins/keyboard-navigation/src/navigation_controller.js b/plugins/keyboard-navigation/src/navigation_controller.js
deleted file mode 100644
index d3eaa4c1c..000000000
--- a/plugins/keyboard-navigation/src/navigation_controller.js
+++ /dev/null
@@ -1,1020 +0,0 @@
-/**
- * @license
- * Copyright 2021 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-
-/**
- * @fileoverview Registers all of the keyboard shortcuts that are necessary for
- * navigating blockly using the keyboard.
- * @author aschmiedt@google.com (Abby Schmiedt)
- */
-
-import './gesture_monkey_patch';
-
-import * as Blockly from 'blockly/core';
-
-import * as Constants from './constants';
-import {Navigation} from './navigation';
-
-/**
- * Class for registering shortcuts for keyboard navigation.
- */
-export class NavigationController {
- /** Data copied by the copy or cut keyboard shortcuts. */
- copyData = null;
-
- /** The workspace a copy or cut keyboard shortcut happened in. */
- copyWorkspace = null;
-
- /**
- * Constructor used for registering shortcuts.
- * This will register any default shortcuts for keyboard navigation.
- * This is intended to be a singleton.
- * @param {!Navigation=} optNavigation The class that handles keyboard
- * navigation shortcuts. (Ex: inserting a block, focusing the flyout).
- */
- constructor(optNavigation) {
- /**
- * Handles any keyboard navigation shortcuts.
- * @type {!Navigation}
- * @public
- */
- this.navigation = optNavigation || new Navigation();
- }
-
- /**
- * Registers the default keyboard shortcuts for keyboard navigation.
- * @public
- */
- init() {
- this.addShortcutHandlers();
- this.registerDefaults();
- }
-
- /**
- * Adds methods to core Blockly components that allows them to handle keyboard
- * shortcuts when in keyboard navigation mode.
- * @protected
- */
- addShortcutHandlers() {
- if (Blockly.FieldDropdown) {
- Blockly.FieldDropdown.prototype.onShortcut = this.fieldDropdownHandler;
- }
-
- if (Blockly.Toolbox) {
- Blockly.Toolbox.prototype.onShortcut = this.toolboxHandler;
- }
- }
-
- /**
- * Removes methods on core Blockly components that allows them to handle
- * keyboard shortcuts.
- * @protected
- */
- removeShortcutHandlers() {
- if (Blockly.FieldDropdown) {
- Blockly.FieldDropdown.prototype.onShortcut = null;
- }
-
- if (Blockly.Toolbox) {
- Blockly.Toolbox.prototype.onShortcut = null;
- }
- }
-
- /**
- * Handles the given keyboard shortcut.
- * This is only triggered when keyboard accessibility mode is enabled.
- * @param {!Blockly.ShortcutRegistry.KeyboardShortcut} shortcut The shortcut
- * to be handled.
- * @returns {boolean} True if the field handled the shortcut,
- * false otherwise.
- * @this {Blockly.FieldDropdown}
- * @protected
- */
- fieldDropdownHandler(shortcut) {
- if (this.menu_) {
- switch (shortcut.name) {
- case Constants.SHORTCUT_NAMES.PREVIOUS:
- this.menu_.highlightPrevious();
- return true;
- case Constants.SHORTCUT_NAMES.NEXT:
- this.menu_.highlightNext();
- return true;
- default:
- return false;
- }
- }
- // If we haven't already handled the shortcut, let the default Field
- // handler try.
- return Blockly.Field.prototype.onShortcut.call(this, shortcut);
- }
-
- /**
- * Handles the given keyboard shortcut.
- * This is only triggered when keyboard accessibility mode is enabled.
- * @param {!Blockly.ShortcutRegistry.KeyboardShortcut} shortcut The shortcut
- * to be handled.
- * @returns {boolean} True if the toolbox handled the shortcut,
- * false otherwise.
- * @this {Blockly.Toolbox}
- * @protected
- */
- toolboxHandler(shortcut) {
- if (!this.selectedItem_) {
- return false;
- }
- switch (shortcut.name) {
- case Constants.SHORTCUT_NAMES.PREVIOUS:
- return this.selectPrevious();
- case Constants.SHORTCUT_NAMES.OUT:
- return this.selectParent();
- case Constants.SHORTCUT_NAMES.NEXT:
- return this.selectNext();
- case Constants.SHORTCUT_NAMES.IN:
- return this.selectChild();
- default:
- return false;
- }
- }
-
- /**
- * Adds all necessary event listeners and markers to a workspace for keyboard
- * navigation to work. This must be called for keyboard navigation to work
- * on a workspace.
- * @param {!Blockly.WorkspaceSvg} workspace The workspace to add keyboard
- * navigation to.
- * @public
- */
- addWorkspace(workspace) {
- this.navigation.addWorkspace(workspace);
- }
-
- /**
- * Removes all necessary event listeners and markers to a workspace for
- * keyboard navigation to work.
- * @param {!Blockly.WorkspaceSvg} workspace The workspace to remove keyboard
- * navigation from.
- * @public
- */
- removeWorkspace(workspace) {
- this.navigation.removeWorkspace(workspace);
- }
-
- /**
- * Turns on keyboard navigation.
- * @param {!Blockly.WorkspaceSvg} workspace The workspace to turn on keyboard
- * navigation for.
- * @public
- */
- enable(workspace) {
- this.navigation.enableKeyboardAccessibility(workspace);
- }
-
- /**
- * Turns off keyboard navigation.
- * @param {!Blockly.WorkspaceSvg} workspace The workspace to turn off keyboard
- * navigation on.
- * @public
- */
- disable(workspace) {
- this.navigation.disableKeyboardAccessibility(workspace);
- }
-
- /**
- * Gives the cursor to the field to handle if the cursor is on a field.
- * @param {!Blockly.WorkspaceSvg} workspace The workspace to check.
- * @param {!Blockly.ShortcutRegistry.KeyboardShortcut} shortcut The shortcut
- * to give to the field.
- * @returns {boolean} True if the shortcut was handled by the field, false
- * otherwise.
- * @protected
- */
- fieldShortcutHandler(workspace, shortcut) {
- const cursor = workspace.getCursor();
- if (!cursor || !cursor.getCurNode()) {
- return false;
- }
- const curNode = cursor.getCurNode();
- if (curNode.getType() === Blockly.ASTNode.types.FIELD) {
- return /** @type {!Blockly.Field} */ (curNode.getLocation()).onShortcut(
- shortcut,
- );
- }
- return false;
- }
-
- /**
- * Keyboard shortcut to go to the previous location when in keyboard
- * navigation mode.
- * @protected
- */
- registerPrevious() {
- /** @type {!Blockly.ShortcutRegistry.KeyboardShortcut} */
- const previousShortcut = {
- name: Constants.SHORTCUT_NAMES.PREVIOUS,
- preconditionFn: (workspace) => {
- return workspace.keyboardAccessibilityMode;
- },
- callback: (workspace, e, shortcut) => {
- const flyout = workspace.getFlyout();
- const toolbox = workspace.getToolbox();
- let isHandled = false;
- switch (this.navigation.getState(workspace)) {
- case Constants.STATE.WORKSPACE:
- isHandled = this.fieldShortcutHandler(workspace, shortcut);
- if (!isHandled) {
- workspace.getCursor().prev();
- isHandled = true;
- }
- return isHandled;
- case Constants.STATE.FLYOUT:
- isHandled = this.fieldShortcutHandler(workspace, shortcut);
- if (!isHandled) {
- flyout.getWorkspace().getCursor().prev();
- isHandled = true;
- }
- return isHandled;
- case Constants.STATE.TOOLBOX:
- return toolbox && typeof toolbox.onShortcut == 'function'
- ? toolbox.onShortcut(shortcut)
- : false;
- default:
- return false;
- }
- },
- };
-
- Blockly.ShortcutRegistry.registry.register(previousShortcut);
- Blockly.ShortcutRegistry.registry.addKeyMapping(
- Blockly.utils.KeyCodes.W,
- previousShortcut.name,
- );
- }
-
- /**
- * Keyboard shortcut to turn keyboard navigation on or off.
- * @protected
- */
- registerToggleKeyboardNav() {
- /** @type {!Blockly.ShortcutRegistry.KeyboardShortcut} */
- const toggleKeyboardNavShortcut = {
- name: Constants.SHORTCUT_NAMES.TOGGLE_KEYBOARD_NAV,
- callback: (workspace) => {
- if (workspace.keyboardAccessibilityMode) {
- this.navigation.disableKeyboardAccessibility(workspace);
- } else {
- this.navigation.enableKeyboardAccessibility(workspace);
- }
- return true;
- },
- };
-
- Blockly.ShortcutRegistry.registry.register(toggleKeyboardNavShortcut);
- const ctrlShiftK = Blockly.ShortcutRegistry.registry.createSerializedKey(
- Blockly.utils.KeyCodes.K,
- [Blockly.utils.KeyCodes.CTRL, Blockly.utils.KeyCodes.SHIFT],
- );
- Blockly.ShortcutRegistry.registry.addKeyMapping(
- ctrlShiftK,
- toggleKeyboardNavShortcut.name,
- );
- }
-
- /**
- * Keyboard shortcut to go to the out location when in keyboard navigation
- * mode.
- * @protected
- */
- registerOut() {
- /** @type {!Blockly.ShortcutRegistry.KeyboardShortcut} */
- const outShortcut = {
- name: Constants.SHORTCUT_NAMES.OUT,
- preconditionFn: (workspace) => {
- return workspace.keyboardAccessibilityMode;
- },
- callback: (workspace, e, shortcut) => {
- const toolbox = workspace.getToolbox();
- let isHandled = false;
- switch (this.navigation.getState(workspace)) {
- case Constants.STATE.WORKSPACE:
- isHandled = this.fieldShortcutHandler(workspace, shortcut);
- if (!isHandled) {
- workspace.getCursor().out();
- isHandled = true;
- }
- return isHandled;
- case Constants.STATE.FLYOUT:
- this.navigation.focusToolbox(workspace);
- return true;
- case Constants.STATE.TOOLBOX:
- return toolbox && typeof toolbox.onShortcut == 'function'
- ? toolbox.onShortcut(shortcut)
- : false;
- default:
- return false;
- }
- },
- };
-
- Blockly.ShortcutRegistry.registry.register(outShortcut);
- Blockly.ShortcutRegistry.registry.addKeyMapping(
- Blockly.utils.KeyCodes.A,
- outShortcut.name,
- );
- }
-
- /**
- * Keyboard shortcut to go to the next location when in keyboard navigation
- * mode.
- * @protected
- */
- registerNext() {
- /** @type {!Blockly.ShortcutRegistry.KeyboardShortcut} */
- const nextShortcut = {
- name: Constants.SHORTCUT_NAMES.NEXT,
- preconditionFn: (workspace) => {
- return workspace.keyboardAccessibilityMode;
- },
- callback: (workspace, e, shortcut) => {
- const toolbox = workspace.getToolbox();
- const flyout = workspace.getFlyout();
- let isHandled = false;
- switch (this.navigation.getState(workspace)) {
- case Constants.STATE.WORKSPACE:
- isHandled = this.fieldShortcutHandler(workspace, shortcut);
- if (!isHandled) {
- workspace.getCursor().next();
- isHandled = true;
- }
- return isHandled;
- case Constants.STATE.FLYOUT:
- isHandled = this.fieldShortcutHandler(workspace, shortcut);
- if (!isHandled) {
- flyout.getWorkspace().getCursor().next();
- isHandled = true;
- }
- return isHandled;
- case Constants.STATE.TOOLBOX:
- return toolbox && typeof toolbox.onShortcut == 'function'
- ? toolbox.onShortcut(shortcut)
- : false;
- default:
- return false;
- }
- },
- };
-
- Blockly.ShortcutRegistry.registry.register(nextShortcut);
- Blockly.ShortcutRegistry.registry.addKeyMapping(
- Blockly.utils.KeyCodes.S,
- nextShortcut.name,
- );
- }
-
- /**
- * Keyboard shortcut to go to the in location when in keyboard navigation
- * mode.
- * @protected
- */
- registerIn() {
- /** @type {!Blockly.ShortcutRegistry.KeyboardShortcut} */
- const inShortcut = {
- name: Constants.SHORTCUT_NAMES.IN,
- preconditionFn: (workspace) => {
- return workspace.keyboardAccessibilityMode;
- },
- callback: (workspace, e, shortcut) => {
- const toolbox = workspace.getToolbox();
- let isHandled = false;
- switch (this.navigation.getState(workspace)) {
- case Constants.STATE.WORKSPACE:
- isHandled = this.fieldShortcutHandler(workspace, shortcut);
- if (!isHandled) {
- workspace.getCursor().in();
- isHandled = true;
- }
- return isHandled;
- case Constants.STATE.TOOLBOX:
- isHandled =
- toolbox && typeof toolbox.onShortcut == 'function'
- ? toolbox.onShortcut(shortcut)
- : false;
- if (!isHandled) {
- this.navigation.focusFlyout(workspace);
- }
- return true;
- default:
- return false;
- }
- },
- };
-
- Blockly.ShortcutRegistry.registry.register(inShortcut);
- Blockly.ShortcutRegistry.registry.addKeyMapping(
- Blockly.utils.KeyCodes.D,
- inShortcut.name,
- );
- }
-
- /**
- * Keyboard shortcut to connect a block to a marked location when in keyboard
- * navigation mode.
- * @protected
- */
- registerInsert() {
- /** @type {!Blockly.ShortcutRegistry.KeyboardShortcut} */
- const insertShortcut = {
- name: Constants.SHORTCUT_NAMES.INSERT,
- preconditionFn: (workspace) => {
- return (
- workspace.keyboardAccessibilityMode && !workspace.options.readOnly
- );
- },
- callback: (workspace) => {
- switch (this.navigation.getState(workspace)) {
- case Constants.STATE.WORKSPACE:
- return this.navigation.connectMarkerAndCursor(workspace);
- default:
- return false;
- }
- },
- };
-
- Blockly.ShortcutRegistry.registry.register(insertShortcut);
- Blockly.ShortcutRegistry.registry.addKeyMapping(
- Blockly.utils.KeyCodes.I,
- insertShortcut.name,
- );
- }
-
- /**
- * Keyboard shortcut to mark a location when in keyboard navigation mode.
- * @protected
- */
- registerMark() {
- /** @type {!Blockly.ShortcutRegistry.KeyboardShortcut} */
- const markShortcut = {
- name: Constants.SHORTCUT_NAMES.MARK,
- preconditionFn: (workspace) => {
- return (
- workspace.keyboardAccessibilityMode && !workspace.options.readOnly
- );
- },
- callback: (workspace) => {
- let flyoutCursor;
- let curNode;
- let nodeType;
-
- switch (this.navigation.getState(workspace)) {
- case Constants.STATE.WORKSPACE:
- this.navigation.handleEnterForWS(workspace);
- return true;
- case Constants.STATE.FLYOUT:
- flyoutCursor = this.navigation.getFlyoutCursor(workspace);
- if (!flyoutCursor) {
- return false;
- }
- curNode = flyoutCursor.getCurNode();
- nodeType = curNode.getType();
-
- switch (nodeType) {
- case Blockly.ASTNode.types.STACK:
- this.navigation.insertFromFlyout(workspace);
- break;
- case Blockly.ASTNode.types.BUTTON:
- this.navigation.triggerButtonCallback(workspace);
- break;
- }
-
- return true;
- default:
- return false;
- }
- },
- };
-
- Blockly.ShortcutRegistry.registry.register(markShortcut);
- Blockly.ShortcutRegistry.registry.addKeyMapping(
- Blockly.utils.KeyCodes.ENTER,
- markShortcut.name,
- );
- }
-
- /**
- * Keyboard shortcut to disconnect two blocks when in keyboard navigation
- * mode.
- * @protected
- */
- registerDisconnect() {
- /** @type {!Blockly.ShortcutRegistry.KeyboardShortcut} */
- const disconnectShortcut = {
- name: Constants.SHORTCUT_NAMES.DISCONNECT,
- preconditionFn: (workspace) => {
- return (
- workspace.keyboardAccessibilityMode && !workspace.options.readOnly
- );
- },
- callback: (workspace) => {
- switch (this.navigation.getState(workspace)) {
- case Constants.STATE.WORKSPACE:
- this.navigation.disconnectBlocks(workspace);
- return true;
- default:
- return false;
- }
- },
- };
-
- Blockly.ShortcutRegistry.registry.register(disconnectShortcut);
- Blockly.ShortcutRegistry.registry.addKeyMapping(
- Blockly.utils.KeyCodes.X,
- disconnectShortcut.name,
- );
- }
-
- /**
- * Keyboard shortcut to focus on the toolbox when in keyboard navigation
- * mode.
- * @protected
- */
- registerToolboxFocus() {
- /** @type {!Blockly.ShortcutRegistry.KeyboardShortcut} */
- const focusToolboxShortcut = {
- name: Constants.SHORTCUT_NAMES.TOOLBOX,
- preconditionFn: (workspace) => {
- return (
- workspace.keyboardAccessibilityMode && !workspace.options.readOnly
- );
- },
- callback: (workspace) => {
- switch (this.navigation.getState(workspace)) {
- case Constants.STATE.WORKSPACE:
- if (!workspace.getToolbox()) {
- this.navigation.focusFlyout(workspace);
- } else {
- this.navigation.focusToolbox(workspace);
- }
- return true;
- default:
- return false;
- }
- },
- };
-
- Blockly.ShortcutRegistry.registry.register(focusToolboxShortcut);
- Blockly.ShortcutRegistry.registry.addKeyMapping(
- Blockly.utils.KeyCodes.T,
- focusToolboxShortcut.name,
- );
- }
-
- /**
- * Keyboard shortcut to exit the current location and focus on the workspace
- * when in keyboard navigation mode.
- * @protected
- */
- registerExit() {
- /** @type {!Blockly.ShortcutRegistry.KeyboardShortcut} */
- const exitShortcut = {
- name: Constants.SHORTCUT_NAMES.EXIT,
- preconditionFn: (workspace) => {
- return workspace.keyboardAccessibilityMode;
- },
- callback: (workspace) => {
- switch (this.navigation.getState(workspace)) {
- case Constants.STATE.FLYOUT:
- this.navigation.focusWorkspace(workspace);
- return true;
- case Constants.STATE.TOOLBOX:
- this.navigation.focusWorkspace(workspace);
- return true;
- default:
- return false;
- }
- },
- };
-
- Blockly.ShortcutRegistry.registry.register(exitShortcut, true);
- Blockly.ShortcutRegistry.registry.addKeyMapping(
- Blockly.utils.KeyCodes.ESC,
- exitShortcut.name,
- true,
- );
- Blockly.ShortcutRegistry.registry.addKeyMapping(
- Blockly.utils.KeyCodes.E,
- exitShortcut.name,
- true,
- );
- }
-
- /**
- * Keyboard shortcut to move the cursor on the workspace to the left when in
- * keyboard navigation mode.
- * @protected
- */
- registerWorkspaceMoveLeft() {
- /** @type {!Blockly.ShortcutRegistry.KeyboardShortcut} */
- const wsMoveLeftShortcut = {
- name: Constants.SHORTCUT_NAMES.MOVE_WS_CURSOR_LEFT,
- preconditionFn: (workspace) => {
- return (
- workspace.keyboardAccessibilityMode && !workspace.options.readOnly
- );
- },
- callback: (workspace) => {
- return this.navigation.moveWSCursor(workspace, -1, 0);
- },
- };
-
- Blockly.ShortcutRegistry.registry.register(wsMoveLeftShortcut);
- const shiftA = Blockly.ShortcutRegistry.registry.createSerializedKey(
- Blockly.utils.KeyCodes.A,
- [Blockly.utils.KeyCodes.SHIFT],
- );
- Blockly.ShortcutRegistry.registry.addKeyMapping(
- shiftA,
- wsMoveLeftShortcut.name,
- );
- }
-
- /**
- * Keyboard shortcut to move the cursor on the workspace to the right when in
- * keyboard navigation mode.
- * @protected
- */
- registerWorkspaceMoveRight() {
- /** @type {!Blockly.ShortcutRegistry.KeyboardShortcut} */
- const wsMoveRightShortcut = {
- name: Constants.SHORTCUT_NAMES.MOVE_WS_CURSOR_RIGHT,
- preconditionFn: (workspace) => {
- return (
- workspace.keyboardAccessibilityMode && !workspace.options.readOnly
- );
- },
- callback: (workspace) => {
- return this.navigation.moveWSCursor(workspace, 1, 0);
- },
- };
-
- Blockly.ShortcutRegistry.registry.register(wsMoveRightShortcut);
- const shiftD = Blockly.ShortcutRegistry.registry.createSerializedKey(
- Blockly.utils.KeyCodes.D,
- [Blockly.utils.KeyCodes.SHIFT],
- );
- Blockly.ShortcutRegistry.registry.addKeyMapping(
- shiftD,
- wsMoveRightShortcut.name,
- );
- }
-
- /**
- * Keyboard shortcut to move the cursor on the workspace up when in keyboard
- * navigation mode.
- * @protected
- */
- registerWorkspaceMoveUp() {
- /** @type {!Blockly.ShortcutRegistry.KeyboardShortcut} */
- const wsMoveUpShortcut = {
- name: Constants.SHORTCUT_NAMES.MOVE_WS_CURSOR_UP,
- preconditionFn: (workspace) => {
- return (
- workspace.keyboardAccessibilityMode && !workspace.options.readOnly
- );
- },
- callback: (workspace) => {
- return this.navigation.moveWSCursor(workspace, 0, -1);
- },
- };
-
- Blockly.ShortcutRegistry.registry.register(wsMoveUpShortcut);
- const shiftW = Blockly.ShortcutRegistry.registry.createSerializedKey(
- Blockly.utils.KeyCodes.W,
- [Blockly.utils.KeyCodes.SHIFT],
- );
- Blockly.ShortcutRegistry.registry.addKeyMapping(
- shiftW,
- wsMoveUpShortcut.name,
- );
- }
-
- /**
- * Keyboard shortcut to move the cursor on the workspace down when in
- * keyboard navigation mode.
- * @protected
- */
- registerWorkspaceMoveDown() {
- /** @type {!Blockly.ShortcutRegistry.KeyboardShortcut} */
- const wsMoveDownShortcut = {
- name: Constants.SHORTCUT_NAMES.MOVE_WS_CURSOR_DOWN,
- preconditionFn: (workspace) => {
- return (
- workspace.keyboardAccessibilityMode && !workspace.options.readOnly
- );
- },
- callback: (workspace) => {
- return this.navigation.moveWSCursor(workspace, 0, 1);
- },
- };
-
- Blockly.ShortcutRegistry.registry.register(wsMoveDownShortcut);
- const shiftW = Blockly.ShortcutRegistry.registry.createSerializedKey(
- Blockly.utils.KeyCodes.S,
- [Blockly.utils.KeyCodes.SHIFT],
- );
- Blockly.ShortcutRegistry.registry.addKeyMapping(
- shiftW,
- wsMoveDownShortcut.name,
- );
- }
-
- /**
- * Keyboard shortcut to copy the block the cursor is currently on.
- * @protected
- */
- registerCopy() {
- /** @type {!Blockly.ShortcutRegistry.KeyboardShortcut} */
- const copyShortcut = {
- name: Constants.SHORTCUT_NAMES.COPY,
- preconditionFn: (workspace) => {
- if (
- workspace.keyboardAccessibilityMode &&
- !workspace.options.readOnly
- ) {
- const curNode = workspace.getCursor().getCurNode();
- if (curNode && curNode.getSourceBlock()) {
- const sourceBlock = curNode.getSourceBlock();
- return (
- !Blockly.Gesture.inProgress() &&
- sourceBlock &&
- sourceBlock.isDeletable() &&
- sourceBlock.isMovable()
- );
- }
- }
- return false;
- },
- callback: (workspace) => {
- const sourceBlock = /** @type {Blockly.BlockSvg} */ (
- workspace.getCursor().getCurNode().getSourceBlock()
- );
- workspace.hideChaff();
- this.copyData = sourceBlock.toCopyData();
- this.copyWorkspace = sourceBlock.workspace;
- return !!this.copyData;
- },
- };
-
- Blockly.ShortcutRegistry.registry.register(copyShortcut);
-
- const ctrlC = Blockly.ShortcutRegistry.registry.createSerializedKey(
- Blockly.utils.KeyCodes.C,
- [Blockly.utils.KeyCodes.CTRL],
- );
- Blockly.ShortcutRegistry.registry.addKeyMapping(
- ctrlC,
- copyShortcut.name,
- true,
- );
-
- const altC = Blockly.ShortcutRegistry.registry.createSerializedKey(
- Blockly.utils.KeyCodes.C,
- [Blockly.utils.KeyCodes.ALT],
- );
- Blockly.ShortcutRegistry.registry.addKeyMapping(
- altC,
- copyShortcut.name,
- true,
- );
-
- const metaC = Blockly.ShortcutRegistry.registry.createSerializedKey(
- Blockly.utils.KeyCodes.C,
- [Blockly.utils.KeyCodes.META],
- );
- Blockly.ShortcutRegistry.registry.addKeyMapping(
- metaC,
- copyShortcut.name,
- true,
- );
- }
-
- /**
- * Register shortcut to paste the copied block to the marked location.
- * @protected
- */
- registerPaste() {
- /** @type {!Blockly.ShortcutRegistry.KeyboardShortcut} */
- const pasteShortcut = {
- name: Constants.SHORTCUT_NAMES.PASTE,
- preconditionFn: (workspace) => {
- return (
- workspace.keyboardAccessibilityMode &&
- !workspace.options.readOnly &&
- !Blockly.Gesture.inProgress()
- );
- },
- callback: () => {
- if (!this.copyData || !this.copyWorkspace) return false;
- return this.navigation.paste(this.copyData, this.copyWorkspace);
- },
- };
-
- Blockly.ShortcutRegistry.registry.register(pasteShortcut);
-
- const ctrlV = Blockly.ShortcutRegistry.registry.createSerializedKey(
- Blockly.utils.KeyCodes.V,
- [Blockly.utils.KeyCodes.CTRL],
- );
- Blockly.ShortcutRegistry.registry.addKeyMapping(
- ctrlV,
- pasteShortcut.name,
- true,
- );
-
- const altV = Blockly.ShortcutRegistry.registry.createSerializedKey(
- Blockly.utils.KeyCodes.V,
- [Blockly.utils.KeyCodes.ALT],
- );
- Blockly.ShortcutRegistry.registry.addKeyMapping(
- altV,
- pasteShortcut.name,
- true,
- );
-
- const metaV = Blockly.ShortcutRegistry.registry.createSerializedKey(
- Blockly.utils.KeyCodes.V,
- [Blockly.utils.KeyCodes.META],
- );
- Blockly.ShortcutRegistry.registry.addKeyMapping(
- metaV,
- pasteShortcut.name,
- true,
- );
- }
-
- /**
- * Keyboard shortcut to copy and delete the block the cursor is on using
- * ctrl+x, cmd+x, or alt+x.
- * @protected
- */
- registerCut() {
- /** @type {!Blockly.ShortcutRegistry.KeyboardShortcut} */
- const cutShortcut = {
- name: Constants.SHORTCUT_NAMES.CUT,
- preconditionFn: (workspace) => {
- if (
- workspace.keyboardAccessibilityMode &&
- !workspace.options.readOnly
- ) {
- const curNode = workspace.getCursor().getCurNode();
- if (curNode && curNode.getSourceBlock()) {
- const sourceBlock = curNode.getSourceBlock();
- return (
- !Blockly.Gesture.inProgress() &&
- sourceBlock &&
- sourceBlock.isDeletable() &&
- sourceBlock.isMovable() &&
- !sourceBlock.workspace.isFlyout
- );
- }
- }
- return false;
- },
- callback: (workspace) => {
- const sourceBlock = /** @type {Blockly.BlockSvg} */ (
- workspace.getCursor().getCurNode().getSourceBlock()
- );
- this.copyData = sourceBlock.toCopyData();
- this.copyWorkspace = sourceBlock.workspace;
- this.navigation.moveCursorOnBlockDelete(workspace, sourceBlock);
- sourceBlock.checkAndDelete();
- return true;
- },
- };
-
- Blockly.ShortcutRegistry.registry.register(cutShortcut);
-
- const ctrlX = Blockly.ShortcutRegistry.registry.createSerializedKey(
- Blockly.utils.KeyCodes.X,
- [Blockly.utils.KeyCodes.CTRL],
- );
- Blockly.ShortcutRegistry.registry.addKeyMapping(
- ctrlX,
- cutShortcut.name,
- true,
- );
-
- const altX = Blockly.ShortcutRegistry.registry.createSerializedKey(
- Blockly.utils.KeyCodes.X,
- [Blockly.utils.KeyCodes.ALT],
- );
- Blockly.ShortcutRegistry.registry.addKeyMapping(
- altX,
- cutShortcut.name,
- true,
- );
-
- const metaX = Blockly.ShortcutRegistry.registry.createSerializedKey(
- Blockly.utils.KeyCodes.X,
- [Blockly.utils.KeyCodes.META],
- );
- Blockly.ShortcutRegistry.registry.addKeyMapping(
- metaX,
- cutShortcut.name,
- true,
- );
- }
-
- /**
- * Registers shortcut to delete the block the cursor is on using delete or
- * backspace.
- * @protected
- */
- registerDelete() {
- /** @type {!Blockly.ShortcutRegistry.KeyboardShortcut} */
- const deleteShortcut = {
- name: Constants.SHORTCUT_NAMES.DELETE,
- preconditionFn: function (workspace) {
- if (
- workspace.keyboardAccessibilityMode &&
- !workspace.options.readOnly
- ) {
- const curNode = workspace.getCursor().getCurNode();
- if (curNode && curNode.getSourceBlock()) {
- const sourceBlock = curNode.getSourceBlock();
- return sourceBlock && sourceBlock.isDeletable();
- }
- }
- return false;
- },
- callback: (workspace, e) => {
- const sourceBlock = workspace.getCursor().getCurNode().getSourceBlock();
- // Delete or backspace.
- // Stop the browser from going back to the previous page.
- // Do this first to prevent an error in the delete code from resulting
- // in data loss.
- e.preventDefault();
- // Don't delete while dragging. Jeez.
- if (Blockly.Gesture.inProgress()) {
- return false;
- }
- this.navigation.moveCursorOnBlockDelete(workspace, sourceBlock);
- sourceBlock.checkAndDelete();
- return true;
- },
- };
- Blockly.ShortcutRegistry.registry.register(deleteShortcut);
- Blockly.ShortcutRegistry.registry.addKeyMapping(
- Blockly.utils.KeyCodes.DELETE,
- deleteShortcut.name,
- true,
- );
- Blockly.ShortcutRegistry.registry.addKeyMapping(
- Blockly.utils.KeyCodes.BACKSPACE,
- deleteShortcut.name,
- true,
- );
- }
-
- /**
- * Registers all default keyboard shortcut items for keyboard navigation. This
- * should be called once per instance of KeyboardShortcutRegistry.
- * @protected
- */
- registerDefaults() {
- this.registerPrevious();
- this.registerNext();
- this.registerIn();
- this.registerOut();
-
- this.registerDisconnect();
- this.registerExit();
- this.registerInsert();
- this.registerMark();
- this.registerToolboxFocus();
- this.registerToggleKeyboardNav();
-
- this.registerWorkspaceMoveDown();
- this.registerWorkspaceMoveLeft();
- this.registerWorkspaceMoveUp();
- this.registerWorkspaceMoveRight();
-
- this.registerCopy();
- this.registerPaste();
- this.registerCut();
- this.registerDelete();
- }
-
- /**
- * Removes all the keyboard navigation shortcuts.
- * @public
- */
- dispose() {
- const shortcutNames = Object.values(Constants.SHORTCUT_NAMES);
- for (const name of shortcutNames) {
- Blockly.ShortcutRegistry.registry.unregister(name);
- }
- this.removeShortcutHandlers();
- this.navigation.dispose();
- }
-}
diff --git a/plugins/keyboard-navigation/test/index.html b/plugins/keyboard-navigation/test/index.html
deleted file mode 100644
index e0db0d325..000000000
--- a/plugins/keyboard-navigation/test/index.html
+++ /dev/null
@@ -1,53 +0,0 @@
-
-
-
-
- Blockly Plugin Test
-
-
-
-
-
- Keyboard Navigation is our first step towards an accessible Blockly.
- For more information on how the default keyboard navigation works please
- see the
- documentation .
-
-
-
- Cursors
- The cursor controls how the user navigates the blocks, inputs, fields and
- connections on a workspace. This demo shows three different cursors:
- Default Cursor: This cursor uses previous, next, in, and out to
- navigate through the different parts of a block. See the
- developer documentation
- for more information.
- Basic Cursor: Uses pre order traversal to allow users to navigate
- through everything using only the previous and next command.
- Line Cursor: We tried to make this cursor mimic a text editor.
- Navigating up and down will take the cursor to the next and previous
- "line" of code. Navigating in and out will move the cursor through all the
- fields and inputs in that "line" of code.
-
- Enable Accessibility Mode:
-
-
- Default Cursor
- Basic Cursor
- Line Cursor
-
-
-
-
-
diff --git a/plugins/keyboard-navigation/test/index.js b/plugins/keyboard-navigation/test/index.js
deleted file mode 100644
index 919ab3103..000000000
--- a/plugins/keyboard-navigation/test/index.js
+++ /dev/null
@@ -1,83 +0,0 @@
-/**
- * @license
- * Copyright 2020 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-
-/**
- * @fileoverview Plugin test.
- */
-
-import {createPlayground} from '@blockly/dev-tools';
-import * as Blockly from 'blockly';
-import {toolbox} from './toolbox';
-
-import {LineCursor, NavigationController} from '../src';
-
-let controller;
-
-/**
- * Create a workspace.
- * @param {HTMLElement} blocklyDiv The blockly container div.
- * @param {!Blockly.BlocklyOptions} options The Blockly options.
- * @returns {!Blockly.WorkspaceSvg} The created workspace.
- */
-function createWorkspace(blocklyDiv, options) {
- const workspace = Blockly.inject(blocklyDiv, options);
- controller.addWorkspace(workspace);
- return workspace;
-}
-
-document.addEventListener('DOMContentLoaded', function () {
- controller = new NavigationController();
- controller.init();
- const defaultOptions = {
- toolbox: toolbox,
- };
- createPlayground(
- document.getElementById('root'),
- createWorkspace,
- defaultOptions,
- );
-});
-
-document
- .getElementById('accessibilityModeCheck')
- .addEventListener('click', (e) => {
- if (e.target.checked) {
- controller.enable(Blockly.getMainWorkspace());
- } else {
- controller.disable(Blockly.getMainWorkspace());
- }
- });
-
-document.getElementById('cursorChanger').addEventListener('change', (e) => {
- const cursorType = e.target.value;
- const accessibilityCheckbox = document.getElementById(
- 'accessibilityModeCheck',
- );
- const markerManager = Blockly.getMainWorkspace().getMarkerManager();
- const oldCurNode = markerManager.getCursor().getCurNode();
-
- document.getElementById('cursorChanger').value = cursorType;
- if (cursorType === 'basic') {
- Blockly.ASTNode.NAVIGATE_ALL_FIELDS = false;
- markerManager.setCursor(new Blockly.BasicCursor());
- } else if (cursorType === 'line') {
- Blockly.ASTNode.NAVIGATE_ALL_FIELDS = true;
- markerManager.setCursor(new LineCursor());
- } else {
- Blockly.ASTNode.NAVIGATE_ALL_FIELDS = false;
- markerManager.setCursor(new Blockly.Cursor());
- }
- if (oldCurNode) {
- markerManager.getCursor().setCurNode(oldCurNode);
- }
-
- if (!accessibilityCheckbox.checked) {
- accessibilityCheckbox.checked = true;
- controller.enable(Blockly.getMainWorkspace());
- }
-
- document.activeElement.blur();
-});
diff --git a/plugins/keyboard-navigation/test/navigation_modify_test.mocha.js b/plugins/keyboard-navigation/test/navigation_modify_test.mocha.js
deleted file mode 100644
index 492b98f9c..000000000
--- a/plugins/keyboard-navigation/test/navigation_modify_test.mocha.js
+++ /dev/null
@@ -1,691 +0,0 @@
-/**
- * @license
- * Copyright 2021 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-
-const chai = require('chai');
-const Blockly = require('blockly');
-const {Navigation} = require('../src/navigation');
-const assert = chai.assert;
-const {testHelpers} = require('@blockly/dev-tools');
-const {captureWarnings} = testHelpers;
-
-suite('Insert/Modify', function () {
- /**
- * Check that modify failed.
- * @param {Navigation} navigation The class under test.
- * @param {Blockly.WorkspaceSvg} workspace The main workspace.
- * @param {!Blockly.ASTNode} markerNode The node to try to connect to.
- * @param {!Blockly.ASTNode} cursorNode The node to connect to the markerNode.
- */
- function assertModifyFails(navigation, workspace, markerNode, cursorNode) {
- let modifyResult;
- const warnings = captureWarnings(function () {
- modifyResult = navigation.tryToConnectMarkerAndCursor(
- workspace,
- markerNode,
- cursorNode,
- );
- });
- assert.isFalse(modifyResult);
- assert.equal(
- warnings.length,
- 1,
- 'Expecting 1 warnings for why modify failed.',
- );
- }
-
- /**
- * Define default blocks.
- */
- function defineTestBlocks() {
- Blockly.defineBlocksWithJsonArray([
- {
- type: 'stack_block',
- message0: '',
- previousStatement: null,
- nextStatement: null,
- },
- {
- type: 'row_block',
- message0: '%1',
- args0: [
- {
- type: 'input_value',
- name: 'INPUT',
- },
- ],
- output: null,
- },
- {
- type: 'statement_block',
- message0: '%1',
- args0: [
- {
- type: 'input_statement',
- name: 'NAME',
- },
- ],
- previousStatement: null,
- nextStatement: null,
- colour: 230,
- tooltip: '',
- helpUrl: '',
- },
- ]);
- }
-
- setup(function () {
- this.jsdomCleanup = require('jsdom-global')(
- '
',
- );
- // We are running these tests in node even thought they require a rendered
- // workspace, which doesn't exactly work. The rendering system expects
- // cancelAnimationFrame to be defined so we need to define it.
- window.cancelAnimationFrame = function () {};
-
- // NOTE: block positions chosen such that they aren't unintentionally
- // bumped out of bounds during tests.
- const xmlText = `
-
-
-
-
-
-
-
-
- `;
-
- defineTestBlocks();
-
- this.workspace = Blockly.inject('blocklyDiv', {
- toolbox: `
-
-
-
- `,
- });
- Blockly.Xml.domToWorkspace(
- Blockly.utils.xml.textToDom(xmlText),
- this.workspace,
- );
- this.navigation = new Navigation();
- this.navigation.addWorkspace(this.workspace);
-
- this.stack_block_1 = this.workspace.getBlockById('stack_block_1');
- this.stack_block_2 = this.workspace.getBlockById('stack_block_2');
- this.row_block_1 = this.workspace.getBlockById('row_block_1');
- this.row_block_2 = this.workspace.getBlockById('row_block_2');
- this.statement_block_1 = this.workspace.getBlockById('statement_block_1');
- this.statement_block_2 = this.workspace.getBlockById('statement_block_2');
- this.navigation.enableKeyboardAccessibility(this.workspace);
- });
-
- teardown(function () {
- delete Blockly.Blocks['stack_block'];
- delete Blockly.Blocks['row_block'];
- delete Blockly.Blocks['statement_block'];
- window.cancelAnimationFrame = undefined;
- this.jsdomCleanup();
- });
-
- suite('Marked Connection', function () {
- suite('Marker on next', function () {
- setup(function () {
- this.markerNode = Blockly.ASTNode.createConnectionNode(
- this.stack_block_1.nextConnection,
- );
- });
- test('Cursor on workspace', function () {
- const cursorNode = Blockly.ASTNode.createWorkspaceNode(
- this.workspace,
- new Blockly.utils.Coordinate(0, 0),
- );
- assertModifyFails(
- this.navigation,
- this.workspace,
- this.markerNode,
- cursorNode,
- );
- });
- test('Cursor on compatible connection', function () {
- const cursorNode = Blockly.ASTNode.createConnectionNode(
- this.stack_block_2.previousConnection,
- );
- assert.isTrue(
- this.navigation.tryToConnectMarkerAndCursor(
- this.workspace,
- this.markerNode,
- cursorNode,
- ),
- );
- assert.equal(this.stack_block_1.getNextBlock().id, 'stack_block_2');
- });
- test('Cursor on incompatible connection', function () {
- // Connect method will try to find a way to connect blocks with
- // incompatible types.
- const cursorNode = Blockly.ASTNode.createConnectionNode(
- this.stack_block_2.nextConnection,
- );
- assert.isTrue(
- this.navigation.tryToConnectMarkerAndCursor(
- this.workspace,
- this.markerNode,
- cursorNode,
- ),
- );
- assert.equal(this.stack_block_1.getNextBlock(), this.stack_block_2);
- });
- test('Cursor on really incompatible connection', function () {
- const cursorNode = Blockly.ASTNode.createConnectionNode(
- this.row_block_1.outputConnection,
- );
- assertModifyFails(
- this.navigation,
- this.workspace,
- this.markerNode,
- cursorNode,
- );
- assert.isNull(this.stack_block_1.getNextBlock());
- });
- test('Cursor on block', function () {
- const cursorNode = Blockly.ASTNode.createBlockNode(this.stack_block_2);
- assert.isTrue(
- this.navigation.tryToConnectMarkerAndCursor(
- this.workspace,
- this.markerNode,
- cursorNode,
- ),
- );
- assert.equal(this.stack_block_1.getNextBlock().id, 'stack_block_2');
- });
- });
-
- suite('Marker on previous', function () {
- setup(function () {
- this.markerNode = Blockly.ASTNode.createConnectionNode(
- this.stack_block_1.previousConnection,
- );
- });
- test('Cursor on compatible connection', function () {
- const cursorNode = Blockly.ASTNode.createConnectionNode(
- this.stack_block_2.nextConnection,
- );
- assert.isTrue(
- this.navigation.tryToConnectMarkerAndCursor(
- this.workspace,
- this.markerNode,
- cursorNode,
- ),
- );
- assert.equal(this.stack_block_1.getPreviousBlock().id, 'stack_block_2');
- });
- test('Cursor on incompatible connection', function () {
- const cursorNode = Blockly.ASTNode.createConnectionNode(
- this.stack_block_2.previousConnection,
- );
- assertModifyFails(
- this.navigation,
- this.workspace,
- this.markerNode,
- cursorNode,
- );
- assert.isNull(this.stack_block_1.getPreviousBlock());
- });
- test('Cursor on really incompatible connection', function () {
- const cursorNode = Blockly.ASTNode.createConnectionNode(
- this.row_block_1.outputConnection,
- );
- assertModifyFails(
- this.navigation,
- this.workspace,
- this.markerNode,
- cursorNode,
- );
- assert.isNull(this.stack_block_1.getNextBlock());
- });
- test('Cursor on block', function () {
- const cursorNode = Blockly.ASTNode.createBlockNode(this.stack_block_2);
- assert.isTrue(
- this.navigation.tryToConnectMarkerAndCursor(
- this.workspace,
- this.markerNode,
- cursorNode,
- ),
- );
- assert.equal(this.stack_block_1.getPreviousBlock().id, 'stack_block_2');
- });
- test('Cursor on incompatible block', function () {
- const cursorNode = Blockly.ASTNode.createBlockNode(this.row_block_1);
- assertModifyFails(
- this.navigation,
- this.workspace,
- this.markerNode,
- cursorNode,
- );
- assert.isNull(this.stack_block_1.getPreviousBlock());
- });
- });
-
- suite('Marker on value input', function () {
- setup(function () {
- this.markerNode = Blockly.ASTNode.createConnectionNode(
- this.row_block_1.inputList[0].connection,
- );
- });
- test('Cursor on compatible connection', function () {
- const cursorNode = Blockly.ASTNode.createConnectionNode(
- this.row_block_2.outputConnection,
- );
- assert.isTrue(
- this.navigation.tryToConnectMarkerAndCursor(
- this.workspace,
- this.markerNode,
- cursorNode,
- ),
- );
- assert.equal(this.row_block_2.getParent().id, 'row_block_1');
- });
- test('Cursor on incompatible connection', function () {
- // Connect method will try to find a way to connect blocks with
- // incompatible types.
- const cursorNode = Blockly.ASTNode.createConnectionNode(
- this.row_block_2.inputList[0].connection,
- );
- assert.isTrue(
- this.navigation.tryToConnectMarkerAndCursor(
- this.workspace,
- this.markerNode,
- cursorNode,
- ),
- );
- assert.equal(
- this.row_block_1.inputList[0].connection.targetBlock(),
- this.row_block_2,
- );
- });
- test('Cursor on really incompatible connection', function () {
- const cursorNode = Blockly.ASTNode.createConnectionNode(
- this.stack_block_1.previousConnection,
- );
- assertModifyFails(
- this.navigation,
- this.workspace,
- this.markerNode,
- cursorNode,
- );
- });
- test('Cursor on block', function () {
- const cursorNode = Blockly.ASTNode.createBlockNode(this.row_block_2);
- assert.isTrue(
- this.navigation.tryToConnectMarkerAndCursor(
- this.workspace,
- this.markerNode,
- cursorNode,
- ),
- );
- assert.equal(this.row_block_2.getParent().id, 'row_block_1');
- });
- });
-
- suite('Marked Statement input', function () {
- setup(function () {
- this.statement_block_1.inputList[0].connection.connect(
- this.stack_block_1.previousConnection,
- );
- this.stack_block_1.nextConnection.connect(
- this.stack_block_2.previousConnection,
- );
- this.markerNode = Blockly.ASTNode.createInputNode(
- this.statement_block_1.inputList[0],
- );
- });
- test('Cursor on block inside statement', function () {
- const cursorNode = Blockly.ASTNode.createConnectionNode(
- this.stack_block_2.previousConnection,
- );
- assert.isTrue(
- this.navigation.tryToConnectMarkerAndCursor(
- this.workspace,
- this.markerNode,
- cursorNode,
- ),
- );
- assert.equal(
- this.stack_block_2.previousConnection.targetBlock(),
- this.statement_block_1,
- );
- });
- test('Cursor on stack', function () {
- const cursorNode = Blockly.ASTNode.createStackNode(
- this.statement_block_2,
- );
- assert.isTrue(
- this.navigation.tryToConnectMarkerAndCursor(
- this.workspace,
- this.markerNode,
- cursorNode,
- ),
- );
- assert.equal(
- this.statement_block_2.getParent().id,
- 'statement_block_1',
- );
- });
- test('Cursor on incompatible type', function () {
- const cursorNode = Blockly.ASTNode.createConnectionNode(
- this.row_block_1.outputConnection,
- );
- assertModifyFails(
- this.navigation,
- this.workspace,
- this.markerNode,
- cursorNode,
- );
- assert.isNull(this.row_block_1.getParent());
- });
- });
-
- suite('Marker on output', function () {
- setup(function () {
- this.markerNode = Blockly.ASTNode.createConnectionNode(
- this.row_block_1.outputConnection,
- );
- });
- test('Cursor on compatible connection', function () {
- const cursorNode = Blockly.ASTNode.createConnectionNode(
- this.row_block_2.inputList[0].connection,
- );
- assert.isTrue(
- this.navigation.tryToConnectMarkerAndCursor(
- this.workspace,
- this.markerNode,
- cursorNode,
- ),
- );
- assert.equal(this.row_block_1.getParent().id, 'row_block_2');
- });
- test('Cursor on incompatible connection', function () {
- const cursorNode = Blockly.ASTNode.createConnectionNode(
- this.row_block_2.outputConnection,
- );
- assertModifyFails(
- this.navigation,
- this.workspace,
- this.markerNode,
- cursorNode,
- );
- });
- test('Cursor on really incompatible connection', function () {
- const cursorNode = Blockly.ASTNode.createConnectionNode(
- this.stack_block_1.previousConnection,
- );
- assertModifyFails(
- this.navigation,
- this.workspace,
- this.markerNode,
- cursorNode,
- );
- });
- test('Cursor on block', function () {
- const cursorNode = Blockly.ASTNode.createBlockNode(this.row_block_2);
- assert.isTrue(
- this.navigation.tryToConnectMarkerAndCursor(
- this.workspace,
- this.markerNode,
- cursorNode,
- ),
- );
- assert.equal(this.row_block_1.getParent().id, 'row_block_2');
- });
- });
- });
-
- suite('Marked Workspace', function () {
- setup(function () {
- this.markerNode = Blockly.ASTNode.createWorkspaceNode(
- this.workspace,
- new Blockly.utils.Coordinate(100, 200),
- );
- });
- test('Cursor on row block', function () {
- const cursorNode = Blockly.ASTNode.createBlockNode(this.row_block_1);
- assert.isTrue(
- this.navigation.tryToConnectMarkerAndCursor(
- this.workspace,
- this.markerNode,
- cursorNode,
- ),
- );
- const pos = this.row_block_1.getRelativeToSurfaceXY();
- assert.equal(pos.x, 100);
- assert.equal(pos.y, 200);
- });
-
- test('Cursor on output connection', function () {
- const cursorNode = Blockly.ASTNode.createConnectionNode(
- this.row_block_1.outputConnection,
- );
- assert.isTrue(
- this.navigation.tryToConnectMarkerAndCursor(
- this.workspace,
- this.markerNode,
- cursorNode,
- ),
- );
- const pos = this.row_block_1.getRelativeToSurfaceXY();
- assert.equal(pos.x, 100);
- assert.equal(pos.y, 200);
- });
-
- test('Cursor on previous connection', function () {
- const cursorNode = Blockly.ASTNode.createConnectionNode(
- this.stack_block_1.previousConnection,
- );
- assert.isTrue(
- this.navigation.tryToConnectMarkerAndCursor(
- this.workspace,
- this.markerNode,
- cursorNode,
- ),
- );
- const pos = this.stack_block_1.getRelativeToSurfaceXY();
- assert.equal(pos.x, 100);
- assert.equal(pos.y, 200);
- });
-
- test('Cursor on input connection', function () {
- // Move the source block to the marked location on the workspace.
- const cursorNode = Blockly.ASTNode.createConnectionNode(
- this.row_block_1.inputList[0].connection,
- );
- assert.isTrue(
- this.navigation.tryToConnectMarkerAndCursor(
- this.workspace,
- this.markerNode,
- cursorNode,
- ),
- );
- });
-
- test('Cursor on next connection', function () {
- // Move the source block to the marked location on the workspace.
- const cursorNode = Blockly.ASTNode.createConnectionNode(
- this.stack_block_1.nextConnection,
- );
- assert.isTrue(
- this.navigation.tryToConnectMarkerAndCursor(
- this.workspace,
- this.markerNode,
- cursorNode,
- ),
- );
- });
-
- test('Cursor on child block (row)', function () {
- this.row_block_1.inputList[0].connection.connect(
- this.row_block_2.outputConnection,
- );
-
- const cursorNode = Blockly.ASTNode.createBlockNode(this.row_block_2);
- assert.isTrue(
- this.navigation.tryToConnectMarkerAndCursor(
- this.workspace,
- this.markerNode,
- cursorNode,
- ),
- );
- assert.isNull(this.row_block_2.getParent());
- const pos = this.row_block_2.getRelativeToSurfaceXY();
- assert.equal(pos.x, 100);
- assert.equal(pos.y, 200);
- });
-
- test('Cursor on child block (stack)', function () {
- this.stack_block_1.nextConnection.connect(
- this.stack_block_2.previousConnection,
- );
-
- const cursorNode = Blockly.ASTNode.createBlockNode(this.stack_block_2);
- assert.isTrue(
- this.navigation.tryToConnectMarkerAndCursor(
- this.workspace,
- this.markerNode,
- cursorNode,
- ),
- );
- assert.isNull(this.stack_block_2.getParent());
- const pos = this.stack_block_2.getRelativeToSurfaceXY();
- assert.equal(pos.x, 100);
- assert.equal(pos.y, 200);
- });
-
- test('Cursor on workspace', function () {
- const cursorNode = Blockly.ASTNode.createWorkspaceNode(
- this.workspace,
- new Blockly.utils.Coordinate(100, 100),
- );
- assertModifyFails(
- this.navigation,
- this.workspace,
- this.markerNode,
- cursorNode,
- );
- });
- });
-
- suite('Marked Block', function () {
- suite('Marked any block', function () {
- // These tests are using a stack block, but do not depend on the type of
- // the block.
- setup(function () {
- this.markerNode = Blockly.ASTNode.createBlockNode(this.stack_block_1);
- });
- test('Cursor on workspace', function () {
- const cursorNode = Blockly.ASTNode.createWorkspaceNode(
- this.workspace,
- new Blockly.utils.Coordinate(100, 100),
- );
- assertModifyFails(
- this.navigation,
- this.workspace,
- this.markerNode,
- cursorNode,
- );
- });
- });
- suite('Marked stack block', function () {
- setup(function () {
- this.markerNode = Blockly.ASTNode.createBlockNode(this.stack_block_1);
- });
- test('Cursor on row block', function () {
- const cursorNode = Blockly.ASTNode.createBlockNode(this.row_block_1);
- assertModifyFails(
- this.navigation,
- this.workspace,
- this.markerNode,
- cursorNode,
- );
- });
- test('Cursor on stack block', function () {
- const cursorNode = Blockly.ASTNode.createBlockNode(this.stack_block_1);
- assertModifyFails(
- this.navigation,
- this.workspace,
- this.markerNode,
- cursorNode,
- );
- });
- test('Cursor on next connection', function () {
- const cursorNode = Blockly.ASTNode.createConnectionNode(
- this.stack_block_2.nextConnection,
- );
- assertModifyFails(
- this.navigation,
- this.workspace,
- this.markerNode,
- cursorNode,
- );
- });
- test('Cursor on previous connection', function () {
- const cursorNode = Blockly.ASTNode.createConnectionNode(
- this.stack_block_2.previousConnection,
- );
- assertModifyFails(
- this.navigation,
- this.workspace,
- this.markerNode,
- cursorNode,
- );
- });
- });
- suite('Marked row block', function () {
- setup(function () {
- this.markerNode = Blockly.ASTNode.createBlockNode(this.row_block_1);
- });
- test('Cursor on stack block', function () {
- const cursorNode = Blockly.ASTNode.createBlockNode(this.stack_block_1);
- assertModifyFails(
- this.navigation,
- this.workspace,
- this.markerNode,
- cursorNode,
- );
- });
- test('Cursor on row block', function () {
- const cursorNode = Blockly.ASTNode.createBlockNode(this.row_block_1);
- assertModifyFails(
- this.navigation,
- this.workspace,
- this.markerNode,
- cursorNode,
- );
- });
- test('Cursor on value input connection', function () {
- const cursorNode = Blockly.ASTNode.createConnectionNode(
- this.row_block_2.inputList[0].connection,
- );
- assertModifyFails(
- this.navigation,
- this.workspace,
- this.markerNode,
- cursorNode,
- );
- });
- test('Cursor on output connection', function () {
- const cursorNode = Blockly.ASTNode.createConnectionNode(
- this.row_block_2.outputConnection,
- );
- assertModifyFails(
- this.navigation,
- this.workspace,
- this.markerNode,
- cursorNode,
- );
- });
- });
- });
-});
diff --git a/plugins/keyboard-navigation/test/navigation_test.mocha.js b/plugins/keyboard-navigation/test/navigation_test.mocha.js
deleted file mode 100644
index 81705b850..000000000
--- a/plugins/keyboard-navigation/test/navigation_test.mocha.js
+++ /dev/null
@@ -1,1368 +0,0 @@
-/**
- * @license
- * Copyright 2020 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-
-/**
- * @fileoverview
- * @author aschmiedt@google.com (Abby Schmiedt)
- */
-'use strict';
-
-const chai = require('chai');
-const sinon = require('sinon');
-
-const Blockly = require('blockly');
-const {NavigationController, Constants} = require('../src/index');
-const {
- createNavigationWorkspace,
- createKeyDownEvent,
-} = require('./test_helper');
-
-suite('Navigation', function () {
- setup(function () {
- this.jsdomCleanup = require('jsdom-global')(
- '
',
- );
- // We are running these tests in node even thought they require a rendered
- // workspace, which doesn't exactly work. The rendering system expects
- // cancelAnimationFrame to be defined so we need to define it.
- window.cancelAnimationFrame = function () {};
- this.controller = new NavigationController();
- this.controller.init();
- this.navigation = this.controller.navigation;
-
- this.getContextStub = sinon
- .stub(window.HTMLCanvasElement.prototype, 'getContext')
- .callsFake(() => {
- return {
- measureText: function () {
- return {width: 0};
- },
- };
- });
- });
-
- teardown(function () {
- this.controller.dispose();
- window.cancelAnimationFrame = undefined;
- this.jsdomCleanup();
- sinon.restore();
- });
-
- // Test that toolbox key handlers call through to the right functions and
- // transition correctly between toolbox, workspace, and flyout.
- suite('Tests toolbox keys', function () {
- setup(function () {
- Blockly.defineBlocksWithJsonArray([
- {
- type: 'basic_block',
- message0: '%1',
- args0: [
- {
- type: 'field_input',
- name: 'TEXTFIELD',
- text: 'test',
- },
- ],
- },
- ]);
- this.workspace = createNavigationWorkspace(this.navigation, true);
- this.navigation.focusToolbox(this.workspace);
- });
-
- teardown(function () {
- this.navigation.removeWorkspace(this.workspace);
- this.workspace.dispose();
- sinon.restore();
- delete Blockly.Blocks['basic_block'];
- });
-
- const testCases = [
- [
- 'Calls toolbox selectNext',
- createKeyDownEvent(Blockly.utils.KeyCodes.S, 'NotAField'),
- 'selectNext',
- ],
- [
- 'Calls toolbox selectPrevious',
- createKeyDownEvent(Blockly.utils.KeyCodes.W, 'NotAField'),
- 'selectPrevious',
- ],
- [
- 'Calls toolbox selectParent',
- createKeyDownEvent(Blockly.utils.KeyCodes.D, 'NotAField'),
- 'selectChild',
- ],
- [
- 'Calls toolbox selectChild',
- createKeyDownEvent(Blockly.utils.KeyCodes.A, 'NotAField'),
- 'selectParent',
- ],
- ];
-
- testCases.forEach(function (testCase) {
- const testCaseName = testCase[0];
- const mockEvent = testCase[1];
- const stubName = testCase[2];
- test(testCaseName, function () {
- const toolbox = this.workspace.getToolbox();
- const selectStub = sinon.stub(toolbox, stubName);
- toolbox.selectedItem_ = toolbox.contents.values().next().value;
- Blockly.ShortcutRegistry.registry.onKeyDown(this.workspace, mockEvent);
- sinon.assert.called(selectStub);
- });
- });
-
- test('Go to flyout', function () {
- const navigation = this.navigation;
- const mockEvent = createKeyDownEvent(
- Blockly.utils.KeyCodes.D,
- 'NotAField',
- );
- const keyDownSpy = sinon.spy(
- Blockly.ShortcutRegistry.registry,
- 'onKeyDown',
- );
-
- Blockly.ShortcutRegistry.registry.onKeyDown(this.workspace, mockEvent);
-
- chai.assert.isTrue(keyDownSpy.returned(true));
- chai.assert.equal(
- navigation.getState(this.workspace),
- Constants.STATE.FLYOUT,
- );
-
- const flyoutCursor = navigation.getFlyoutCursor(this.workspace);
- // See test_helper.js for hardcoded field values.
- chai.assert.equal(
- flyoutCursor.getCurNode().getLocation().getFieldValue('TEXTFIELD'),
- 'first',
- );
- });
-
- test('Focuses workspace from toolbox (e)', function () {
- const navigation = this.navigation;
- navigation.setState(this.workspace, Constants.STATE.TOOLBOX);
- const mockEvent = createKeyDownEvent(
- Blockly.utils.KeyCodes.E,
- 'NotAField',
- );
- const keyDownSpy = sinon.spy(
- Blockly.ShortcutRegistry.registry,
- 'onKeyDown',
- );
-
- Blockly.ShortcutRegistry.registry.onKeyDown(this.workspace, mockEvent);
-
- chai.assert.isTrue(keyDownSpy.returned(true));
- chai.assert.equal(
- navigation.getState(this.workspace),
- Constants.STATE.WORKSPACE,
- );
- });
- test('Focuses workspace from toolbox (escape)', function () {
- const navigation = this.navigation;
- navigation.setState(this.workspace, Constants.STATE.TOOLBOX);
- const mockEvent = createKeyDownEvent(
- Blockly.utils.KeyCodes.ESC,
- 'NotAField',
- );
- const keyDownSpy = sinon.spy(
- Blockly.ShortcutRegistry.registry,
- 'onKeyDown',
- );
-
- Blockly.ShortcutRegistry.registry.onKeyDown(this.workspace, mockEvent);
-
- chai.assert.isTrue(keyDownSpy.returned(true));
- chai.assert.equal(
- navigation.getState(this.workspace),
- Constants.STATE.WORKSPACE,
- );
- });
- });
-
- // Test that flyout key handlers call through to the right functions and
- // transition correctly between toolbox, workspace, and flyout.
- suite('Tests flyout keys', function () {
- setup(function () {
- Blockly.defineBlocksWithJsonArray([
- {
- type: 'basic_block',
- message0: '%1',
- args0: [
- {
- type: 'field_input',
- name: 'TEXTFIELD',
- text: 'test',
- },
- ],
- },
- ]);
- this.workspace = createNavigationWorkspace(this.navigation, true);
- this.navigation.focusToolbox(this.workspace);
- this.navigation.focusFlyout(this.workspace);
- });
-
- teardown(function () {
- this.navigation.removeWorkspace(this.workspace);
- this.workspace.dispose();
- sinon.restore();
- delete Blockly.Blocks['basic_block'];
- });
- // Should be a no-op
- test('Previous at beginning', function () {
- const mockEvent = createKeyDownEvent(
- Blockly.utils.KeyCodes.W,
- 'NotAField',
- );
- const keyDownSpy = sinon.spy(
- Blockly.ShortcutRegistry.registry,
- 'onKeyDown',
- );
-
- Blockly.ShortcutRegistry.registry.onKeyDown(this.workspace, mockEvent);
-
- chai.assert.isTrue(keyDownSpy.returned(true));
- chai.assert.equal(
- this.navigation.getState(this.workspace),
- Constants.STATE.FLYOUT,
- );
- // See test_helper.js for hardcoded field values.
- chai.assert.equal(
- this.navigation
- .getFlyoutCursor(this.workspace)
- .getCurNode()
- .getLocation()
- .getFieldValue('TEXTFIELD'),
- 'first',
- );
- });
- test('Previous', function () {
- const flyoutBlocks = this.workspace
- .getFlyout()
- .getWorkspace()
- .getTopBlocks();
- this.navigation
- .getFlyoutCursor(this.workspace)
- .setCurNode(Blockly.ASTNode.createStackNode(flyoutBlocks[1]));
- let flyoutBlock = this.navigation
- .getFlyoutCursor(this.workspace)
- .getCurNode()
- .getLocation();
- // See test_helper.js for hardcoded field values.
- chai.assert.equal(flyoutBlock.getFieldValue('TEXTFIELD'), 'second');
-
- const mockEvent = createKeyDownEvent(
- Blockly.utils.KeyCodes.W,
- 'NotAField',
- );
- const keyDownSpy = sinon.spy(
- Blockly.ShortcutRegistry.registry,
- 'onKeyDown',
- );
-
- Blockly.ShortcutRegistry.registry.onKeyDown(this.workspace, mockEvent);
-
- chai.assert.isTrue(keyDownSpy.returned(true));
- chai.assert.equal(
- this.navigation.getState(this.workspace),
- Constants.STATE.FLYOUT,
- );
- flyoutBlock = this.navigation
- .getFlyoutCursor(this.workspace)
- .getCurNode()
- .getLocation();
- // See test_helper.js for hardcoded field values.
- chai.assert.equal(flyoutBlock.getFieldValue('TEXTFIELD'), 'first');
- });
-
- test('Next', function () {
- const mockEvent = createKeyDownEvent(
- Blockly.utils.KeyCodes.S,
- 'NotAField',
- );
- const keyDownSpy = sinon.spy(
- Blockly.ShortcutRegistry.registry,
- 'onKeyDown',
- );
-
- Blockly.ShortcutRegistry.registry.onKeyDown(this.workspace, mockEvent);
-
- chai.assert.isTrue(keyDownSpy.returned(true));
- chai.assert.equal(
- this.navigation.getState(this.workspace),
- Constants.STATE.FLYOUT,
- );
- const flyoutBlock = this.navigation
- .getFlyoutCursor(this.workspace)
- .getCurNode()
- .getLocation();
- // See test_helper.js for hardcoded field values.
- chai.assert.equal(flyoutBlock.getFieldValue('TEXTFIELD'), 'second');
- });
-
- test('Out', function () {
- const mockEvent = createKeyDownEvent(
- Blockly.utils.KeyCodes.A,
- 'NotAField',
- );
- const keyDownSpy = sinon.spy(
- Blockly.ShortcutRegistry.registry,
- 'onKeyDown',
- );
-
- Blockly.ShortcutRegistry.registry.onKeyDown(this.workspace, mockEvent);
-
- chai.assert.isTrue(keyDownSpy.returned(true));
- chai.assert.equal(
- this.navigation.getState(this.workspace),
- Constants.STATE.TOOLBOX,
- );
- });
-
- test('Mark', function () {
- const mockEvent = createKeyDownEvent(
- Blockly.utils.KeyCodes.ENTER,
- 'NotAField',
- );
- const keyDownSpy = sinon.spy(
- Blockly.ShortcutRegistry.registry,
- 'onKeyDown',
- );
-
- Blockly.ShortcutRegistry.registry.onKeyDown(this.workspace, mockEvent);
-
- chai.assert.isTrue(keyDownSpy.returned(true));
- chai.assert.equal(
- this.navigation.getState(this.workspace),
- Constants.STATE.WORKSPACE,
- );
- chai.assert.equal(this.workspace.getTopBlocks().length, 1);
- });
-
- test('Mark - Disabled Block', function () {
- this.navigation.loggingCallback = function (type, msg) {
- chai.assert.equal(msg, "Can't insert a disabled block.");
- };
- const flyout = this.workspace.getFlyout();
- const topBlock = flyout.getWorkspace().getTopBlocks()[0];
- topBlock.setEnabled(false);
- const mockEvent = createKeyDownEvent(
- Blockly.utils.KeyCodes.ENTER,
- 'NotAField',
- );
- const keyDownSpy = sinon.spy(
- Blockly.ShortcutRegistry.registry,
- 'onKeyDown',
- );
-
- Blockly.ShortcutRegistry.registry.onKeyDown(this.workspace, mockEvent);
-
- chai.assert.isTrue(keyDownSpy.returned(true));
- chai.assert.equal(
- this.navigation.getState(this.workspace),
- Constants.STATE.FLYOUT,
- );
- chai.assert.equal(this.workspace.getTopBlocks().length, 0);
- this.navigation.loggingCallback = null;
- });
-
- test('Exit', function () {
- const mockEvent = createKeyDownEvent(
- Blockly.utils.KeyCodes.ESC,
- 'NotAField',
- );
- const keyDownSpy = sinon.spy(
- Blockly.ShortcutRegistry.registry,
- 'onKeyDown',
- );
-
- Blockly.ShortcutRegistry.registry.onKeyDown(this.workspace, mockEvent);
-
- chai.assert.isTrue(keyDownSpy.returned(true));
- chai.assert.equal(
- this.navigation.getState(this.workspace),
- Constants.STATE.WORKSPACE,
- );
- });
- });
- // Test that workspace key handlers call through to the right functions and
- // transition correctly between toolbox, workspace, and flyout.
- suite('Tests workspace keys', function () {
- setup(function () {
- Blockly.defineBlocksWithJsonArray([
- {
- type: 'basic_block',
- message0: '%1',
- args0: [
- {
- type: 'field_input',
- name: 'TEXTFIELD',
- text: 'test',
- },
- ],
- previousStatement: null,
- nextStatement: null,
- },
- ]);
- this.workspace = createNavigationWorkspace(this.navigation, true);
- this.basicBlock = this.workspace.newBlock('basic_block');
- });
-
- teardown(function () {
- this.navigation.removeWorkspace(this.workspace);
- this.workspace.dispose();
- sinon.restore();
- delete Blockly.Blocks['basic_block'];
- });
-
- test('Previous', function () {
- const prevSpy = sinon.spy(this.workspace.getCursor(), 'prev');
- const keyDownSpy = sinon.spy(
- Blockly.ShortcutRegistry.registry,
- 'onKeyDown',
- );
- const wEvent = createKeyDownEvent(Blockly.utils.KeyCodes.W, '');
-
- Blockly.ShortcutRegistry.registry.onKeyDown(this.workspace, wEvent);
-
- chai.assert.isTrue(keyDownSpy.returned(true));
- sinon.assert.calledOnce(prevSpy);
- chai.assert.equal(
- this.navigation.getState(this.workspace),
- Constants.STATE.WORKSPACE,
- );
- });
-
- test('Next', function () {
- const nextSpy = sinon.spy(this.workspace.getCursor(), 'next');
- const keyDownSpy = sinon.spy(
- Blockly.ShortcutRegistry.registry,
- 'onKeyDown',
- );
- const sEvent = createKeyDownEvent(Blockly.utils.KeyCodes.S, '');
-
- Blockly.ShortcutRegistry.registry.onKeyDown(this.workspace, sEvent);
-
- chai.assert.isTrue(keyDownSpy.returned(true));
- sinon.assert.calledOnce(nextSpy);
- chai.assert.equal(
- this.navigation.getState(this.workspace),
- Constants.STATE.WORKSPACE,
- );
- });
-
- test('Out', function () {
- const outSpy = sinon.spy(this.workspace.getCursor(), 'out');
- const keyDownSpy = sinon.spy(
- Blockly.ShortcutRegistry.registry,
- 'onKeyDown',
- );
- const aEvent = createKeyDownEvent(Blockly.utils.KeyCodes.A, '');
-
- Blockly.ShortcutRegistry.registry.onKeyDown(this.workspace, aEvent);
-
- chai.assert.isTrue(keyDownSpy.returned(true));
- sinon.assert.calledOnce(outSpy);
- chai.assert.equal(
- this.navigation.getState(this.workspace),
- Constants.STATE.WORKSPACE,
- );
- });
-
- test('In', function () {
- const inSpy = sinon.spy(this.workspace.getCursor(), 'in');
- const keyDownSpy = sinon.spy(
- Blockly.ShortcutRegistry.registry,
- 'onKeyDown',
- );
- const dEvent = createKeyDownEvent(Blockly.utils.KeyCodes.D, '');
-
- Blockly.ShortcutRegistry.registry.onKeyDown(this.workspace, dEvent);
-
- chai.assert.isTrue(keyDownSpy.returned(true));
- sinon.assert.calledOnce(inSpy);
- chai.assert.equal(
- this.navigation.getState(this.workspace),
- Constants.STATE.WORKSPACE,
- );
- });
-
- test('Insert', function () {
- const blockNode = Blockly.ASTNode.createBlockNode(this.basicBlock);
- this.navigation.getMarker(this.workspace).setCurNode(blockNode);
- // Stub modify as we are not testing its behavior, only if it was called.
- // Otherwise, there is a warning because there is no marked node.
- const modifyStub = sinon
- .stub(this.navigation, 'tryToConnectMarkerAndCursor')
- .returns(true);
- const keyDownSpy = sinon.spy(
- Blockly.ShortcutRegistry.registry,
- 'onKeyDown',
- );
- const iEvent = createKeyDownEvent(Blockly.utils.KeyCodes.I, '');
-
- Blockly.ShortcutRegistry.registry.onKeyDown(this.workspace, iEvent);
-
- chai.assert.isTrue(keyDownSpy.returned(true));
- sinon.assert.calledOnce(modifyStub);
- chai.assert.equal(
- this.navigation.getState(this.workspace),
- Constants.STATE.WORKSPACE,
- );
- });
-
- test('Mark', function () {
- this.workspace
- .getCursor()
- .setCurNode(
- Blockly.ASTNode.createConnectionNode(
- this.basicBlock.previousConnection,
- ),
- );
- const keyDownSpy = sinon.spy(
- Blockly.ShortcutRegistry.registry,
- 'onKeyDown',
- );
- const enterEvent = createKeyDownEvent(Blockly.utils.KeyCodes.ENTER, '');
-
- Blockly.ShortcutRegistry.registry.onKeyDown(this.workspace, enterEvent);
-
- const markedNode = this.workspace
- .getMarker(this.navigation.MARKER_NAME)
- .getCurNode();
- chai.assert.isTrue(keyDownSpy.returned(true));
- chai.assert.equal(
- markedNode.getLocation(),
- this.basicBlock.previousConnection,
- );
- chai.assert.equal(
- this.navigation.getState(this.workspace),
- Constants.STATE.WORKSPACE,
- );
- });
-
- test('Toolbox', function () {
- const keyDownSpy = sinon.spy(
- Blockly.ShortcutRegistry.registry,
- 'onKeyDown',
- );
- const tEvent = createKeyDownEvent(Blockly.utils.KeyCodes.T, '');
-
- Blockly.ShortcutRegistry.registry.onKeyDown(this.workspace, tEvent);
-
- const firstCategory = this.workspace
- .getToolbox()
- .contents.values()
- .next().value;
- chai.assert.isTrue(keyDownSpy.returned(true));
- chai.assert.equal(
- this.workspace.getToolbox().getSelectedItem(),
- firstCategory,
- );
- chai.assert.equal(
- this.navigation.getState(this.workspace),
- Constants.STATE.TOOLBOX,
- );
- });
- });
-
- suite('Test key press', function () {
- setup(function () {
- Blockly.defineBlocksWithJsonArray([
- {
- type: 'basic_block',
- message0: '%1',
- args0: [
- {
- type: 'field_input',
- name: 'TEXTFIELD',
- text: 'test',
- },
- ],
- },
- ]);
- this.workspace = createNavigationWorkspace(this.navigation, true);
-
- this.workspace.getCursor().drawer_ = null;
- this.basicBlock = this.workspace.newBlock('basic_block');
- this.basicBlock.initSvg();
- this.basicBlock.render();
- });
- teardown(function () {
- this.navigation.removeWorkspace(this.workspace);
- this.workspace.dispose();
- sinon.restore();
- delete Blockly.Blocks['basic_block'];
- });
-
- test('Action does not exist', function () {
- const block = this.workspace.getTopBlocks()[0];
- const field = block.inputList[0].fieldRow[0];
- const fieldSpy = sinon.spy(field, 'onShortcut');
- const mockEvent = createKeyDownEvent(Blockly.utils.KeyCodes.N, '');
- const keyDownSpy = sinon.spy(
- Blockly.ShortcutRegistry.registry,
- 'onKeyDown',
- );
- this.workspace
- .getCursor()
- .setCurNode(Blockly.ASTNode.createFieldNode(field));
-
- Blockly.ShortcutRegistry.registry.onKeyDown(this.workspace, mockEvent);
-
- chai.assert.isFalse(keyDownSpy.returned(true));
- sinon.assert.notCalled(fieldSpy);
- });
-
- test('Action exists - field handles action', function () {
- const block = this.workspace.getTopBlocks()[0];
- const field = block.inputList[0].fieldRow[0];
- const mockEvent = createKeyDownEvent(Blockly.utils.KeyCodes.A, '');
- const fieldSpy = sinon.stub(field, 'onShortcut').returns(true);
- const keyDownSpy = sinon.spy(
- Blockly.ShortcutRegistry.registry,
- 'onKeyDown',
- );
- this.workspace
- .getCursor()
- .setCurNode(Blockly.ASTNode.createFieldNode(field));
-
- Blockly.ShortcutRegistry.registry.onKeyDown(this.workspace, mockEvent);
-
- chai.assert.isTrue(keyDownSpy.returned(true));
- sinon.assert.calledOnce(fieldSpy);
- });
-
- test('Action exists - field does not handle action', function () {
- const block = this.workspace.getTopBlocks()[0];
- const field = block.inputList[0].fieldRow[0];
- const mockEvent = createKeyDownEvent(Blockly.utils.KeyCodes.A, '');
- const fieldSpy = sinon.spy(field, 'onShortcut');
- const keyDownSpy = sinon.spy(
- Blockly.ShortcutRegistry.registry,
- 'onKeyDown',
- );
- this.workspace
- .getCursor()
- .setCurNode(Blockly.ASTNode.createFieldNode(field));
-
- Blockly.ShortcutRegistry.registry.onKeyDown(this.workspace, mockEvent);
-
- chai.assert.isTrue(keyDownSpy.returned(true));
- sinon.assert.calledOnce(fieldSpy);
- });
-
- test('Toggle Action Off', function () {
- const mockEvent = createKeyDownEvent(Blockly.utils.KeyCodes.K, '', [
- Blockly.utils.KeyCodes.SHIFT,
- Blockly.utils.KeyCodes.CTRL,
- ]);
- const keyDownSpy = sinon.spy(
- Blockly.ShortcutRegistry.registry,
- 'onKeyDown',
- );
- this.workspace.keyboardAccessibilityMode = true;
-
- Blockly.ShortcutRegistry.registry.onKeyDown(this.workspace, mockEvent);
-
- chai.assert.isTrue(keyDownSpy.returned(true));
- chai.assert.isFalse(this.workspace.keyboardAccessibilityMode);
- });
-
- test('Toggle Action On', function () {
- const mockEvent = createKeyDownEvent(Blockly.utils.KeyCodes.K, '', [
- Blockly.utils.KeyCodes.SHIFT,
- Blockly.utils.KeyCodes.CTRL,
- ]);
- const keyDownSpy = sinon.spy(
- Blockly.ShortcutRegistry.registry,
- 'onKeyDown',
- );
- this.workspace.keyboardAccessibilityMode = false;
-
- Blockly.ShortcutRegistry.registry.onKeyDown(this.workspace, mockEvent);
-
- chai.assert.isTrue(keyDownSpy.returned(true));
- chai.assert.isTrue(this.workspace.keyboardAccessibilityMode);
- });
-
- suite('Test key press in read only mode', function () {
- setup(function () {
- Blockly.defineBlocksWithJsonArray([
- {
- type: 'field_block',
- message0: '%1 %2',
- args0: [
- {
- type: 'field_input',
- name: 'TEXTFIELD',
- text: 'test',
- },
- {
- type: 'input_value',
- name: 'NAME',
- },
- ],
- previousStatement: null,
- nextStatement: null,
- colour: 230,
- tooltip: '',
- helpUrl: '',
- },
- ]);
- this.workspace = createNavigationWorkspace(this.navigation, true, true);
- Blockly.common.setMainWorkspace(this.workspace);
- this.workspace.getCursor().drawer_ = null;
-
- this.fieldBlock1 = this.workspace.newBlock('field_block');
- this.fieldBlock1.initSvg();
- this.fieldBlock1.render();
- });
-
- teardown(function () {
- this.navigation.removeWorkspace(this.workspace);
- this.workspace.dispose();
- sinon.restore();
- delete Blockly.Blocks['field_block'];
- });
-
- test('Perform valid action for read only', function () {
- const astNode = Blockly.ASTNode.createBlockNode(this.fieldBlock1);
- const mockEvent = createKeyDownEvent(Blockly.utils.KeyCodes.S, '');
- this.workspace.getCursor().setCurNode(astNode);
- const keyDownSpy = sinon.spy(
- Blockly.ShortcutRegistry.registry,
- 'onKeyDown',
- );
-
- Blockly.ShortcutRegistry.registry.onKeyDown(this.workspace, mockEvent);
-
- chai.assert.isTrue(keyDownSpy.returned(true));
- });
-
- test('Perform invalid action for read only', function () {
- const astNode = Blockly.ASTNode.createBlockNode(this.fieldBlock1);
- const mockEvent = createKeyDownEvent(Blockly.utils.KeyCodes.I, '');
- this.workspace.getCursor().setCurNode(astNode);
- const keyDownSpy = sinon.spy(
- Blockly.ShortcutRegistry.registry,
- 'onKeyDown',
- );
-
- Blockly.ShortcutRegistry.registry.onKeyDown(this.workspace, mockEvent);
-
- chai.assert.isTrue(keyDownSpy.returned(false));
- });
-
- test('Try to perform action on a field', function () {
- const field = this.fieldBlock1.inputList[0].fieldRow[0];
- const astNode = Blockly.ASTNode.createFieldNode(field);
- const mockEvent = createKeyDownEvent(Blockly.utils.KeyCodes.ENTER, '');
- this.workspace.getCursor().setCurNode(astNode);
- const keyDownSpy = sinon.spy(
- Blockly.ShortcutRegistry.registry,
- 'onKeyDown',
- );
-
- Blockly.ShortcutRegistry.registry.onKeyDown(this.workspace, mockEvent);
-
- chai.assert.isTrue(keyDownSpy.returned(false));
- });
- });
- });
- suite('Insert Functions', function () {
- setup(function () {
- Blockly.defineBlocksWithJsonArray([
- {
- type: 'basic_block',
- message0: '%1',
- args0: [
- {
- type: 'field_input',
- name: 'TEXTFIELD',
- text: 'test',
- },
- ],
- previousStatement: null,
- nextStatement: null,
- },
- ]);
-
- this.workspace = createNavigationWorkspace(this.navigation, true);
-
- const basicBlock = this.workspace.newBlock('basic_block');
- const basicBlock2 = this.workspace.newBlock('basic_block');
-
- this.basicBlock = basicBlock;
- this.basicBlock2 = basicBlock2;
- });
-
- teardown(function () {
- this.navigation.removeWorkspace(this.workspace);
- this.workspace.dispose();
- sinon.restore();
- delete Blockly.Blocks['basic_block'];
- });
-
- test('Insert from flyout with a valid connection marked', function () {
- const previousConnection = this.basicBlock.previousConnection;
- const prevNode = Blockly.ASTNode.createConnectionNode(previousConnection);
- this.workspace
- .getMarker(this.navigation.MARKER_NAME)
- .setCurNode(prevNode);
-
- this.navigation.focusToolbox(this.workspace);
- this.navigation.focusFlyout(this.workspace);
- this.navigation.insertFromFlyout(this.workspace);
-
- const insertedBlock = this.basicBlock.previousConnection.targetBlock();
-
- chai.assert.isTrue(insertedBlock !== null);
- chai.assert.equal(
- this.navigation.getState(this.workspace),
- Constants.STATE.WORKSPACE,
- );
- });
-
- test('Insert Block from flyout without marking a connection', function () {
- this.navigation.focusToolbox(this.workspace);
- this.navigation.focusFlyout(this.workspace);
- this.navigation.insertFromFlyout(this.workspace);
-
- const numBlocks = this.workspace.getTopBlocks().length;
-
- // Make sure the block was not connected to anything
- chai.assert.isNull(this.basicBlock.previousConnection.targetConnection);
- chai.assert.isNull(this.basicBlock.nextConnection.targetConnection);
-
- // Make sure that the block was added to the workspace
- chai.assert.equal(numBlocks, 3);
- chai.assert.equal(
- this.navigation.getState(this.workspace),
- Constants.STATE.WORKSPACE,
- );
- });
-
- test('Connect two blocks that are on the workspace', function () {
- const targetNode = Blockly.ASTNode.createConnectionNode(
- this.basicBlock.previousConnection,
- );
- const sourceNode = Blockly.ASTNode.createConnectionNode(
- this.basicBlock2.nextConnection,
- );
-
- this.navigation.tryToConnectMarkerAndCursor(
- this.workspace,
- targetNode,
- sourceNode,
- );
- const insertedBlock = this.basicBlock.previousConnection.targetBlock();
-
- chai.assert.isNotNull(insertedBlock);
- });
- });
- suite('Connect Blocks', function () {
- setup(function () {
- Blockly.defineBlocksWithJsonArray([
- {
- type: 'basic_block',
- message0: '',
- previousStatement: null,
- nextStatement: null,
- },
- {
- type: 'inline_block',
- message0: '%1 %2',
- args0: [
- {
- type: 'input_value',
- name: 'NAME',
- },
- {
- type: 'input_value',
- name: 'NAME',
- },
- ],
- inputsInline: true,
- output: null,
- tooltip: '',
- helpUrl: '',
- },
- ]);
-
- this.workspace = createNavigationWorkspace(this.navigation, true);
-
- const basicBlock = this.workspace.newBlock('basic_block');
- const basicBlock2 = this.workspace.newBlock('basic_block');
- const basicBlock3 = this.workspace.newBlock('basic_block');
- const basicBlock4 = this.workspace.newBlock('basic_block');
-
- const inlineBlock1 = this.workspace.newBlock('inline_block');
- const inlineBlock2 = this.workspace.newBlock('inline_block');
- const inlineBlock3 = this.workspace.newBlock('inline_block');
-
- this.basicBlock = basicBlock;
- this.basicBlock2 = basicBlock2;
- this.basicBlock3 = basicBlock3;
- this.basicBlock4 = basicBlock4;
-
- this.inlineBlock1 = inlineBlock1;
- this.inlineBlock2 = inlineBlock2;
- this.inlineBlock3 = inlineBlock3;
-
- this.basicBlock.nextConnection.connect(
- this.basicBlock2.previousConnection,
- );
-
- this.basicBlock3.nextConnection.connect(
- this.basicBlock4.previousConnection,
- );
-
- this.inlineBlock1.inputList[0].connection.connect(
- this.inlineBlock2.outputConnection,
- );
- });
-
- teardown(function () {
- this.navigation.removeWorkspace(this.workspace);
- this.workspace.dispose();
- sinon.restore();
- delete Blockly.Blocks['basic_block'];
- delete Blockly.Blocks['inline_block'];
- });
-
- test('Connect cursor on previous into stack', function () {
- const markedLocation = this.basicBlock2.previousConnection;
- const cursorLocation = this.basicBlock3.previousConnection;
-
- this.navigation.connect(cursorLocation, markedLocation);
-
- chai.assert.equal(
- this.basicBlock.nextConnection.targetBlock(),
- this.basicBlock3,
- );
- chai.assert.equal(
- this.basicBlock2.previousConnection.targetBlock(),
- this.basicBlock4,
- );
- });
-
- test('Connect marker on previous into stack', function () {
- const markedLocation = this.basicBlock3.previousConnection;
- const cursorLocation = this.basicBlock2.previousConnection;
-
- this.navigation.connect(cursorLocation, markedLocation);
-
- chai.assert.equal(
- this.basicBlock.nextConnection.targetBlock(),
- this.basicBlock3,
- );
- chai.assert.equal(
- this.basicBlock2.previousConnection.targetBlock(),
- this.basicBlock4,
- );
- });
-
- test('Connect cursor on next into stack', function () {
- const markedLocation = this.basicBlock2.previousConnection;
- const cursorLocation = this.basicBlock4.nextConnection;
-
- this.navigation.connect(cursorLocation, markedLocation);
-
- chai.assert.equal(
- this.basicBlock.nextConnection.targetBlock(),
- this.basicBlock4,
- );
- chai.assert.isNull(this.basicBlock3.nextConnection.targetConnection);
- });
-
- test('Connect cursor with parents', function () {
- const markedLocation = this.basicBlock3.previousConnection;
- const cursorLocation = this.basicBlock2.nextConnection;
-
- this.navigation.connect(cursorLocation, markedLocation);
-
- chai.assert.equal(
- this.basicBlock3.previousConnection.targetBlock(),
- this.basicBlock2,
- );
- });
-
- test('Try to connect input that is descendant of output', function () {
- const markedLocation = this.inlineBlock2.inputList[0].connection;
- const cursorLocation = this.inlineBlock1.outputConnection;
-
- this.navigation.connect(cursorLocation, markedLocation);
-
- chai.assert.isNull(this.inlineBlock2.outputConnection.targetBlock());
- chai.assert.equal(
- this.inlineBlock1.outputConnection.targetBlock(),
- this.inlineBlock2,
- );
- });
- test.skip('Do not connect a shadow block', function () {
- // TODO(https://github.com/google/blockly-samples/issues/538): Update
- // tests after this bug is fixed.
- this.inlineBlock2.setShadow(true);
-
- const markedLocation = this.inlineBlock2.outputConnection;
- const cursorLocation = this.inlineBlock3.inputList[0].connection;
- const didConnect = this.navigation.connect(
- cursorLocation,
- markedLocation,
- );
- chai.assert.isFalse(didConnect);
- chai.assert.isNull(this.inlineBlock2.outputConnection.targetBlock());
- chai.assert.equal(
- this.inlineBlock1.outputConnection.targetBlock(),
- this.inlineBlock2,
- );
- });
- });
-
- suite('Test cursor move on block delete', function () {
- setup(function () {
- Blockly.defineBlocksWithJsonArray([
- {
- type: 'basic_block',
- message0: '',
- previousStatement: null,
- nextStatement: null,
- },
- ]);
- this.workspace = createNavigationWorkspace(this.navigation, true);
-
- this.basicBlockA = this.workspace.newBlock('basic_block');
- this.basicBlockB = this.workspace.newBlock('basic_block');
- });
-
- teardown(function () {
- this.navigation.removeWorkspace(this.workspace);
- this.workspace.dispose();
- sinon.restore();
- delete Blockly.Blocks['basic_block'];
- });
-
- test('Delete block - has parent ', function () {
- this.basicBlockA.nextConnection.connect(
- this.basicBlockB.previousConnection,
- );
- const astNode = Blockly.ASTNode.createBlockNode(this.basicBlockB);
- // Set the cursor to be on the child block
- this.workspace.getCursor().setCurNode(astNode);
- // Remove the child block
- const mockEvent = createKeyDownEvent(Blockly.utils.KeyCodes.DELETE, '');
-
- // Actions that happen when a block is deleted were causing problems.
- // Since this is not what we are trying to test and does not effect the
- // feature, disable events.
- Blockly.Events.disable();
- Blockly.ShortcutRegistry.registry.onKeyDown(this.workspace, mockEvent);
- Blockly.Events.enable();
-
- chai.assert.equal(
- this.workspace.getCursor().getCurNode().getType(),
- Blockly.ASTNode.types.NEXT,
- );
- });
-
- test('Delete block - no parent ', function () {
- const astNode = Blockly.ASTNode.createBlockNode(this.basicBlockB);
- this.workspace.getCursor().setCurNode(astNode);
-
- const mockEvent = createKeyDownEvent(Blockly.utils.KeyCodes.DELETE, '');
-
- // Actions that happen when a block is deleted were causing problems.
- // Since this is not what we are trying to test and does not effect the
- // feature, disable events.
- Blockly.Events.disable();
- Blockly.ShortcutRegistry.registry.onKeyDown(this.workspace, mockEvent);
- Blockly.Events.enable();
-
- chai.assert.equal(
- this.workspace.getCursor().getCurNode().getType(),
- Blockly.ASTNode.types.WORKSPACE,
- );
- });
-
- test('Delete parent block', function () {
- this.basicBlockA.nextConnection.connect(
- this.basicBlockB.previousConnection,
- );
- const astNode = Blockly.ASTNode.createBlockNode(this.basicBlockB);
- const mockDeleteBlockEvent = {
- blockId: this.basicBlockA,
- ids: [this.basicBlockA.id, this.basicBlockB.id],
- };
- // Set the cursor to be on the child block
- this.workspace.getCursor().setCurNode(astNode);
- // Remove the parent block
- this.navigation.handleBlockDeleteByDrag(
- this.workspace,
- mockDeleteBlockEvent,
- );
- chai.assert.equal(
- this.workspace.getCursor().getCurNode().getType(),
- Blockly.ASTNode.types.WORKSPACE,
- );
- });
-
- test('Delete top block in stack', function () {
- this.basicBlockA.nextConnection.connect(
- this.basicBlockB.previousConnection,
- );
- const astNode = Blockly.ASTNode.createStackNode(this.basicBlockA);
- const mockDeleteBlockEvent = {
- blockId: this.basicBlockA.id,
- ids: [this.basicBlockA.id, this.basicBlockB.id],
- };
- // Set the cursor to be on the stack
- this.workspace.getCursor().setCurNode(astNode);
- // Remove the top block in the stack
- this.navigation.handleBlockDeleteByDrag(
- this.workspace,
- mockDeleteBlockEvent,
- );
- chai.assert.equal(
- this.workspace.getCursor().getCurNode().getType(),
- Blockly.ASTNode.types.WORKSPACE,
- );
- });
- });
-
- suite('Test workspace listener', function () {
- setup(function () {
- Blockly.defineBlocksWithJsonArray([
- {
- type: 'basic_block',
- message0: '%1',
- args0: [
- {
- type: 'field_input',
- name: 'TEXTFIELD',
- text: 'test',
- },
- ],
- previousStatement: null,
- nextStatement: null,
- },
- ]);
- this.workspace = createNavigationWorkspace(this.navigation, true);
- this.workspaceChangeListener = this.navigation.wsChangeWrapper;
- this.basicBlockA = this.workspace.newBlock('basic_block');
- });
-
- teardown(function () {
- this.navigation.removeWorkspace(this.workspace);
- this.workspace.dispose();
- delete Blockly.Blocks['basic_block'];
- sinon.restore();
- });
-
- test('Handle block mutation', function () {
- const e = {
- type: Blockly.Events.BLOCK_CHANGE,
- element: 'mutation',
- blockId: this.basicBlockA.id,
- workspaceId: this.workspace.id,
- };
- const cursor = this.workspace.getCursor();
- const nextNode = Blockly.ASTNode.createConnectionNode(
- this.basicBlockA.nextConnection,
- );
- cursor.setCurNode(nextNode);
- this.workspaceChangeListener(e);
- chai.assert.equal(
- cursor.getCurNode().getType(),
- Blockly.ASTNode.types.BLOCK,
- );
- });
- test('Handle workspace click', function () {
- const e = {
- type: Blockly.Events.CLICK,
- workspaceId: this.workspace.id,
- };
- this.navigation.focusFlyout(this.workspace);
- chai.assert.equal(
- this.navigation.getState(this.workspace),
- Constants.STATE.FLYOUT,
- );
-
- this.workspaceChangeListener(e);
-
- chai.assert.equal(
- this.navigation.getState(this.workspace),
- Constants.STATE.WORKSPACE,
- );
- });
- test('Focus toolbox if category clicked', function () {
- const e = {
- type: Blockly.Events.TOOLBOX_ITEM_SELECT,
- workspaceId: this.workspace.id,
- newItem: true,
- };
- const toolboxFocusStub = sinon.spy(this.navigation, 'focusToolbox');
-
- this.navigation.focusWorkspace(this.workspace);
- chai.assert.equal(
- this.navigation.getState(this.workspace),
- Constants.STATE.WORKSPACE,
- );
-
- this.workspaceChangeListener(e);
-
- sinon.assert.calledOnce(toolboxFocusStub);
- chai.assert.equal(
- this.navigation.getState(this.workspace),
- Constants.STATE.TOOLBOX,
- );
- });
- test('Focus workspace if toolbox is unselected', function () {
- const e = {
- type: Blockly.Events.TOOLBOX_ITEM_SELECT,
- workspaceId: this.workspace.id,
- newItem: false,
- };
- const resetFlyoutStub = sinon.spy(this.navigation, 'resetFlyout');
- this.navigation.setState(this.workspace, Constants.STATE.TOOLBOX);
-
- this.workspaceChangeListener(e);
-
- sinon.assert.calledOnce(resetFlyoutStub);
- chai.assert.equal(
- this.navigation.getState(this.workspace),
- Constants.STATE.WORKSPACE,
- );
- });
- test('Focus workspace when block created on workspace', function () {
- const e = {
- type: Blockly.Events.BLOCK_CREATE,
- workspaceId: this.workspace.id,
- };
- const resetFlyoutStub = sinon.spy(this.navigation, 'resetFlyout');
- // Only works when someone is in the flyout.
- this.navigation.setState(this.workspace, Constants.STATE.FLYOUT);
-
- this.workspaceChangeListener(e);
-
- sinon.assert.calledOnce(resetFlyoutStub);
- chai.assert.equal(
- this.navigation.getState(this.workspace),
- Constants.STATE.WORKSPACE,
- );
- });
- });
-
- suite('Test simple flyout listener', function () {
- setup(function () {
- Blockly.defineBlocksWithJsonArray([
- {
- type: 'basic_block',
- message0: '%1',
- args0: [
- {
- type: 'field_input',
- name: 'TEXTFIELD',
- text: 'test',
- },
- ],
- },
- ]);
- this.workspace = createNavigationWorkspace(this.navigation, true);
- this.flyoutChangeListener = this.navigation.flyoutChangeWrapper;
- this.basicBlockA = this.workspace.newBlock('basic_block');
-
- this.navigation.focusToolbox(this.workspace);
- this.workspace.getFlyout().autoClose = false;
- });
-
- teardown(function () {
- delete Blockly.Blocks['basic_block'];
- this.navigation.removeWorkspace(this.workspace);
- this.workspace.dispose();
- sinon.restore();
- });
- test('Handle block click in flyout - click event', function () {
- const flyout = this.workspace.getFlyout();
- const flyoutWorkspace = flyout.getWorkspace();
- const firstFlyoutBlock = flyoutWorkspace.getTopBlocks()[0];
- const e = {
- type: Blockly.Events.CLICK,
- workspaceId: flyoutWorkspace.id,
- targetType: 'block',
- blockId: firstFlyoutBlock.id,
- };
- const flyoutCursor = flyoutWorkspace.getCursor();
- this.navigation.focusWorkspace(this.workspace);
-
- this.flyoutChangeListener(e);
-
- chai.assert.equal(
- flyoutCursor.getCurNode().getType(),
- Blockly.ASTNode.types.STACK,
- );
- chai.assert.equal(
- this.navigation.getState(this.workspace),
- Constants.STATE.FLYOUT,
- );
- });
- test('Handle block click in flyout - select event', function () {
- const flyout = this.workspace.getFlyout();
- const flyoutWorkspace = flyout.getWorkspace();
- const firstFlyoutBlock = flyoutWorkspace.getTopBlocks()[0];
- const e = {
- type: Blockly.Events.SELECTED,
- workspaceId: flyoutWorkspace.id,
- newElementId: firstFlyoutBlock.id,
- };
- const flyoutCursor = flyoutWorkspace.getCursor();
- this.navigation.focusWorkspace(this.workspace);
-
- this.flyoutChangeListener(e);
-
- chai.assert.equal(
- flyoutCursor.getCurNode().getType(),
- Blockly.ASTNode.types.STACK,
- );
- chai.assert.equal(
- this.navigation.getState(this.workspace),
- Constants.STATE.FLYOUT,
- );
- });
- });
-
- suite('Test clean up methods', function () {
- setup(function () {
- this.workspace = createNavigationWorkspace(this.navigation, true);
- });
- test('All listeners and markers removed', function () {
- const numListeners = this.workspace.listeners.length;
- const markerName = this.navigation.MARKER_NAME;
- this.navigation.removeWorkspace(this.workspace);
- chai.assert.equal(this.workspace.listeners.length, numListeners - 1);
-
- const marker = this.workspace.getMarkerManager().getMarker(markerName);
- chai.assert.isNull(marker);
- });
- test('Keyboard accessibility mode can not be enabled', function () {
- this.navigation.removeWorkspace(this.workspace);
- this.navigation.enableKeyboardAccessibility(this.workspace);
- chai.assert.isFalse(this.workspace.keyboardAccessibilityMode);
- });
- test('Dispose', function () {
- const numListeners = this.workspace.listeners.length;
- const flyout = this.workspace.getFlyout();
- const numFlyoutListeners = flyout.getWorkspace().listeners.length;
- this.navigation.dispose();
- chai.assert.equal(this.workspace.listeners.length, numListeners - 1);
- chai.assert.equal(
- flyout.getWorkspace().listeners.length,
- numFlyoutListeners - 1,
- );
- });
- });
-});
diff --git a/plugins/keyboard-navigation/test/shortcuts_test.mocha.js b/plugins/keyboard-navigation/test/shortcuts_test.mocha.js
deleted file mode 100644
index 4e1909521..000000000
--- a/plugins/keyboard-navigation/test/shortcuts_test.mocha.js
+++ /dev/null
@@ -1,531 +0,0 @@
-/**
- * @license
- * Copyright 2021 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-
-const sinon = require('sinon');
-const chai = require('chai');
-
-const Blockly = require('blockly');
-
-const {NavigationController} = require('../src/index');
-const {
- createNavigationWorkspace,
- createKeyDownEvent,
-} = require('./test_helper');
-
-suite('Shortcut Tests', function () {
- setup(function () {
- this.jsdomCleanup = require('jsdom-global')(
- '
',
- );
- // We are running these tests in node even thought they require a rendered
- // workspace, which doesn't exactly work. The rendering system expects
- // cancelAnimationFrame to be defined so we need to define it.
- window.cancelAnimationFrame = function () {};
-
- Blockly.utils.dom.getFastTextWidthWithSizeString = function () {
- return 10;
- };
- Blockly.defineBlocksWithJsonArray([
- {
- type: 'basic_block',
- message0: '',
- previousStatement: null,
- nextStatement: null,
- },
- ]);
- this.controller = new NavigationController();
- this.controller.init();
- this.navigation = this.controller.navigation;
- this.workspace = createNavigationWorkspace(this.navigation, true);
- this.controller.addWorkspace(this.workspace);
- this.basicBlock = this.workspace.newBlock('basic_block');
- });
-
- teardown(function () {
- window.cancelAnimationFrame = undefined;
- this.controller.dispose();
- this.workspace.dispose();
- this.jsdomCleanup();
- delete Blockly.Blocks['basic_block'];
- });
-
- suite('Deleting blocks', function () {
- setup(function () {
- const blockNode = Blockly.ASTNode.createBlockNode(this.basicBlock);
- this.workspace.getCursor().setCurNode(blockNode);
- });
-
- teardown(function () {
- sinon.restore();
- });
-
- const testCases = [
- {
- name: 'Delete',
- deleteEvent: createKeyDownEvent(
- Blockly.utils.KeyCodes.DELETE,
- 'NotAField',
- ),
- },
- {
- name: 'Backspace',
- deleteEvent: createKeyDownEvent(
- Blockly.utils.KeyCodes.BACKSPACE,
- 'NotAField',
- ),
- },
- ];
-
- suite('delete keybinds trigger deletion', function () {
- testCases.forEach(function (testCase) {
- test(testCase.name, function () {
- Blockly.ShortcutRegistry.registry.onKeyDown(
- this.workspace,
- testCase.deleteEvent,
- );
- chai.assert.equal(
- this.workspace.getTopBlocks().length,
- 0,
- 'Expected the block to be deleted.',
- );
- });
- });
- });
-
- suite(
- 'delete keybinds do not trigger deletion if workspace is readonly',
- function () {
- testCases.forEach(function (testCase) {
- test(testCase.name, function () {
- this.workspace.options.readOnly = true;
- Blockly.ShortcutRegistry.registry.onKeyDown(
- this.workspace,
- testCase.deleteEvent,
- );
- chai.assert.equal(
- this.workspace.getTopBlocks().length,
- 1,
- 'Expected the block to not be deleted.',
- );
- });
- });
- },
- );
- });
-
- suite('Copy and paste', function () {
- teardown(function () {
- sinon.restore();
- });
- const testCases = [
- {
- name: 'Control',
- copyEvent: createKeyDownEvent(Blockly.utils.KeyCodes.C, 'NotAField', [
- Blockly.utils.KeyCodes.CTRL,
- ]),
- pasteEvent: createKeyDownEvent(Blockly.utils.KeyCodes.V, 'NotAField', [
- Blockly.utils.KeyCodes.CTRL,
- ]),
- },
- {
- name: 'Meta',
- copyEvent: createKeyDownEvent(Blockly.utils.KeyCodes.C, 'NotAField', [
- Blockly.utils.KeyCodes.META,
- ]),
- pasteEvent: createKeyDownEvent(Blockly.utils.KeyCodes.V, 'NotAField', [
- Blockly.utils.KeyCodes.META,
- ]),
- },
- {
- name: 'Alt',
- copyEvent: createKeyDownEvent(Blockly.utils.KeyCodes.C, 'NotAField', [
- Blockly.utils.KeyCodes.ALT,
- ]),
- pasteEvent: createKeyDownEvent(Blockly.utils.KeyCodes.V, 'NotAField', [
- Blockly.utils.KeyCodes.ALT,
- ]),
- },
- ];
-
- suite('copy and paste keybinds duplicate blocks', function () {
- setup(function () {
- const blockNode = Blockly.ASTNode.createBlockNode(this.basicBlock);
- this.workspace.getCursor().setCurNode(blockNode);
- });
-
- testCases.forEach(function (testCase) {
- test(testCase.name, function () {
- Blockly.ShortcutRegistry.registry.onKeyDown(
- this.workspace,
- testCase.copyEvent,
- );
- Blockly.ShortcutRegistry.registry.onKeyDown(
- this.workspace,
- testCase.pasteEvent,
- );
- chai.assert.equal(
- this.workspace.getTopBlocks().length,
- 2,
- 'Expected the block to be duplicated.',
- );
- });
- });
- });
-
- suite(
- 'copy and paste does nothing if the cursor is not on a block',
- function () {
- setup(function () {
- const workspaceNode = Blockly.ASTNode.createWorkspaceNode(
- this.workspace,
- new Blockly.utils.Coordinate(100, 100),
- );
- this.workspace.getCursor().setCurNode(workspaceNode);
- });
- testCases.forEach(function (testCase) {
- test(testCase.name, function () {
- Blockly.ShortcutRegistry.registry.onKeyDown(
- this.workspace,
- testCase.copyEvent,
- );
- Blockly.ShortcutRegistry.registry.onKeyDown(
- this.workspace,
- testCase.pasteEvent,
- );
- chai.assert.equal(
- this.workspace.getTopBlocks().length,
- 1,
- 'Expected the block to not be duplicated.',
- );
- });
- });
- },
- );
-
- suite(
- 'copy and paste do nothing if the cursor is on a shadow block',
- function () {
- setup(function () {
- this.basicBlock.setShadow(true);
- const blockNode = Blockly.ASTNode.createBlockNode(this.basicBlock);
- this.workspace.getCursor().setCurNode(blockNode);
- });
- testCases.forEach(function (testCase) {
- test(testCase.name, function () {
- Blockly.ShortcutRegistry.registry.onKeyDown(
- this.workspace,
- testCase.copyEvent,
- );
- Blockly.ShortcutRegistry.registry.onKeyDown(
- this.workspace,
- testCase.pasteEvent,
- );
- chai.assert.equal(
- this.workspace.getTopBlocks().length,
- 1,
- 'Expected the block to not be duplicated.',
- );
- });
- });
- },
- );
-
- suite(
- 'copy and paste do nothing if the workspace is readonly',
- function () {
- setup(function () {
- this.workspace.options.readonly = true;
- });
- testCases.forEach(function (testCase) {
- test(testCase.name, function () {
- Blockly.ShortcutRegistry.registry.onKeyDown(
- this.workspace,
- testCase.copyEvent,
- );
- Blockly.ShortcutRegistry.registry.onKeyDown(
- this.workspace,
- testCase.pasteEvent,
- );
- chai.assert.equal(
- this.workspace.getTopBlocks().length,
- 1,
- 'Expected the block to not be duplicated.',
- );
- });
- });
- },
- );
-
- suite('copy and paste do nothing if a gesture is in progress', function () {
- setup(function () {
- sinon.stub(Blockly.Gesture, 'inProgress').returns(true);
- });
- testCases.forEach(function (testCase) {
- test(testCase.name, function () {
- Blockly.ShortcutRegistry.registry.onKeyDown(
- this.workspace,
- testCase.copyEvent,
- );
- Blockly.ShortcutRegistry.registry.onKeyDown(
- this.workspace,
- testCase.pasteEvent,
- );
- chai.assert.equal(
- this.workspace.getTopBlocks().length,
- 1,
- 'Expected the block to not be duplicated.',
- );
- });
- });
- });
-
- suite(
- 'copy and paste do nothing if the block is not deletable',
- function () {
- setup(function () {
- this.basicBlock.setDeletable(false);
- });
- testCases.forEach(function (testCase) {
- test(testCase.name, function () {
- Blockly.ShortcutRegistry.registry.onKeyDown(
- this.workspace,
- testCase.copyEvent,
- );
- Blockly.ShortcutRegistry.registry.onKeyDown(
- this.workspace,
- testCase.pasteEvent,
- );
- chai.assert.equal(
- this.workspace.getTopBlocks().length,
- 1,
- 'Expected the block to not be duplicated.',
- );
- });
- });
- },
- );
- });
-
- suite('Cut and paste', function () {
- teardown(function () {
- sinon.restore();
- });
- const testCases = [
- {
- name: 'Control',
- cutEvent: createKeyDownEvent(Blockly.utils.KeyCodes.X, 'NotAField', [
- Blockly.utils.KeyCodes.CTRL,
- ]),
- pasteEvent: createKeyDownEvent(Blockly.utils.KeyCodes.V, 'NotAField', [
- Blockly.utils.KeyCodes.CTRL,
- ]),
- },
- {
- name: 'Meta',
- cutEvent: createKeyDownEvent(Blockly.utils.KeyCodes.X, 'NotAField', [
- Blockly.utils.KeyCodes.META,
- ]),
- pasteEvent: createKeyDownEvent(Blockly.utils.KeyCodes.V, 'NotAField', [
- Blockly.utils.KeyCodes.META,
- ]),
- },
- {
- name: 'Alt',
- cutEvent: createKeyDownEvent(Blockly.utils.KeyCodes.X, 'NotAField', [
- Blockly.utils.KeyCodes.ALT,
- ]),
- pasteEvent: createKeyDownEvent(Blockly.utils.KeyCodes.V, 'NotAField', [
- Blockly.utils.KeyCodes.ALT,
- ]),
- },
- ];
-
- suite('cut and paste keybinds duplicate blocks', function () {
- setup(function () {
- const blockNode = Blockly.ASTNode.createBlockNode(this.basicBlock);
- this.workspace.getCursor().setCurNode(blockNode);
- });
-
- testCases.forEach(function (testCase) {
- test(testCase.name, function () {
- Blockly.ShortcutRegistry.registry.onKeyDown(
- this.workspace,
- testCase.cutEvent,
- );
- chai.assert.equal(
- this.workspace.getTopBlocks().length,
- 0,
- 'Expected the block to be deleted.',
- );
- Blockly.ShortcutRegistry.registry.onKeyDown(
- this.workspace,
- testCase.pasteEvent,
- );
- chai.assert.equal(
- this.workspace.getTopBlocks().length,
- 1,
- 'Expected the block to be duplicated.',
- );
- });
- });
- });
-
- suite(
- 'cut and paste does nothing if the cursor is not on a block',
- function () {
- setup(function () {
- const workspaceNode = Blockly.ASTNode.createWorkspaceNode(
- this.workspace,
- new Blockly.utils.Coordinate(100, 100),
- );
- this.workspace.getCursor().setCurNode(workspaceNode);
- });
- testCases.forEach(function (testCase) {
- test(testCase.name, function () {
- Blockly.ShortcutRegistry.registry.onKeyDown(
- this.workspace,
- testCase.cutEvent,
- );
- chai.assert.equal(
- this.workspace.getTopBlocks().length,
- 1,
- 'Expected the block to not be deleted.',
- );
- Blockly.ShortcutRegistry.registry.onKeyDown(
- this.workspace,
- testCase.pasteEvent,
- );
- chai.assert.equal(
- this.workspace.getTopBlocks().length,
- 1,
- 'Expected the block to not be duplicated.',
- );
- });
- });
- },
- );
-
- suite(
- 'cut and paste do nothing if the cursor is on a shadow block',
- function () {
- setup(function () {
- this.basicBlock.setShadow(true);
- const blockNode = Blockly.ASTNode.createBlockNode(this.basicBlock);
- this.workspace.getCursor().setCurNode(blockNode);
- });
- testCases.forEach(function (testCase) {
- test(testCase.name, function () {
- Blockly.ShortcutRegistry.registry.onKeyDown(
- this.workspace,
- testCase.cutEvent,
- );
- chai.assert.equal(
- this.workspace.getTopBlocks().length,
- 1,
- 'Expected the block to not be deleted.',
- );
- Blockly.ShortcutRegistry.registry.onKeyDown(
- this.workspace,
- testCase.pasteEvent,
- );
- chai.assert.equal(
- this.workspace.getTopBlocks().length,
- 1,
- 'Expected the block to not be duplicated.',
- );
- });
- });
- },
- );
-
- suite('cut and paste do nothing if the workspace is readonly', function () {
- setup(function () {
- this.workspace.options.readonly = true;
- });
- testCases.forEach(function (testCase) {
- test(testCase.name, function () {
- Blockly.ShortcutRegistry.registry.onKeyDown(
- this.workspace,
- testCase.cutEvent,
- );
- chai.assert.equal(
- this.workspace.getTopBlocks().length,
- 1,
- 'Expected the block to not be deleted.',
- );
- Blockly.ShortcutRegistry.registry.onKeyDown(
- this.workspace,
- testCase.pasteEvent,
- );
- chai.assert.equal(
- this.workspace.getTopBlocks().length,
- 1,
- 'Expected the block to not be duplicated.',
- );
- });
- });
- });
-
- suite('cut and paste do nothing if a gesture is in progress', function () {
- setup(function () {
- sinon.stub(Blockly.Gesture, 'inProgress').returns(true);
- });
- testCases.forEach(function (testCase) {
- test(testCase.name, function () {
- Blockly.ShortcutRegistry.registry.onKeyDown(
- this.workspace,
- testCase.cutEvent,
- );
- chai.assert.equal(
- this.workspace.getTopBlocks().length,
- 1,
- 'Expected the block to not be deleted.',
- );
- Blockly.ShortcutRegistry.registry.onKeyDown(
- this.workspace,
- testCase.pasteEvent,
- );
- chai.assert.equal(
- this.workspace.getTopBlocks().length,
- 1,
- 'Expected the block to not be duplicated.',
- );
- });
- });
- });
-
- suite(
- 'cut and paste do nothing if the block is not deletable',
- function () {
- setup(function () {
- this.basicBlock.setDeletable(false);
- });
- testCases.forEach(function (testCase) {
- test(testCase.name, function () {
- Blockly.ShortcutRegistry.registry.onKeyDown(
- this.workspace,
- testCase.cutEvent,
- );
- chai.assert.equal(
- this.workspace.getTopBlocks().length,
- 1,
- 'Expected the block to not be deleted.',
- );
- Blockly.ShortcutRegistry.registry.onKeyDown(
- this.workspace,
- testCase.pasteEvent,
- );
- chai.assert.equal(
- this.workspace.getTopBlocks().length,
- 1,
- 'Expected the block to not be duplicated.',
- );
- });
- });
- },
- );
- });
-});
diff --git a/plugins/keyboard-navigation/test/test_helper.js b/plugins/keyboard-navigation/test/test_helper.js
deleted file mode 100644
index 99abcb5fb..000000000
--- a/plugins/keyboard-navigation/test/test_helper.js
+++ /dev/null
@@ -1,89 +0,0 @@
-/**
- * @license
- * Copyright 2021 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-
-const {Constants} = require('../src/index');
-const {Navigation} = require('../src/index');
-const Blockly = require('blockly/core');
-
-/**
- * Creates a workspace for testing keyboard navigation.
- * @param {Navigation} navigation Object holding navigation classes.
- * @param {boolean} enableKeyboardNav True to enable keyboard navigation, false
- * otherwise.
- * @param {boolean} readOnly True for a read only workspace, false otherwise.
- * @returns {Blockly.WorkspaceSvg} The created workspace.
- */
-export function createNavigationWorkspace(
- navigation,
- enableKeyboardNav,
- readOnly,
-) {
- const workspace = Blockly.inject('blocklyDiv', {
- toolbox: `
-
-
-
- first
-
-
- second
-
-
-
-
- third
-
-
-
- `,
- readOnly: readOnly,
- });
- if (enableKeyboardNav) {
- navigation.addWorkspace(workspace);
- navigation.enableKeyboardAccessibility(workspace);
- navigation.setState(workspace, Constants.STATE.WORKSPACE);
- }
- return workspace;
-}
-
-/**
- * Creates a key down event used for testing.
- * @param {number} keyCode The keycode for the event. Use Blockly.utils.KeyCodes
- * enum.
- * @param {string} type The type of the target. This only matters for the
- * Blockly.utils.isTargetInput method.
- * @param {?Array} modifiers A list of modifiers. Use
- * Blockly.utils.KeyCodes enum.
- * @returns {Object} The mocked keydown
- * event.
- */
-export function createKeyDownEvent(keyCode, type, modifiers) {
- const event = {
- keyCode: keyCode,
- target: {type: type},
- getModifierState: function (name) {
- if (name == 'Shift' && this.shiftKey) {
- return true;
- } else if (name == 'Control' && this.ctrlKey) {
- return true;
- } else if (name == 'Meta' && this.metaKey) {
- return true;
- } else if (name == 'Alt' && this.altKey) {
- return true;
- }
- return false;
- },
- preventDefault: function () {},
- };
- if (modifiers && modifiers.length) {
- event.altKey = modifiers.includes(Blockly.utils.KeyCodes.ALT);
- event.ctrlKey = modifiers.includes(Blockly.utils.KeyCodes.CTRL);
- event.metaKey = modifiers.includes(Blockly.utils.KeyCodes.META);
- event.shiftKey = modifiers.includes(Blockly.utils.KeyCodes.SHIFT);
- }
- return event;
-}
diff --git a/plugins/keyboard-navigation/test/toolbox.js b/plugins/keyboard-navigation/test/toolbox.js
deleted file mode 100644
index 95dd4ff7b..000000000
--- a/plugins/keyboard-navigation/test/toolbox.js
+++ /dev/null
@@ -1,218 +0,0 @@
-/**
- * @license
- * Copyright 2024 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-
-/**
- * @fileoverview A custom toolbox for the plugin test.
- */
-
-export const toolbox = {
- kind: 'categoryToolbox',
- contents: [
- {
- kind: 'category',
- name: 'Logic',
- categorystyle: 'logic_category',
- contents: [
- {
- type: 'controls_if',
- kind: 'block',
- },
- {
- type: 'logic_compare',
- kind: 'block',
- fields: {
- OP: 'EQ',
- },
- },
- {
- type: 'logic_operation',
- kind: 'block',
- fields: {
- OP: 'AND',
- },
- },
- ],
- },
- {
- kind: 'category',
- name: 'Loops',
- categorystyle: 'loop_category',
- contents: [
- {
- type: 'controls_repeat_ext',
- kind: 'block',
- inputs: {
- TIMES: {
- shadow: {
- type: 'math_number',
- fields: {
- NUM: 10,
- },
- },
- },
- },
- },
- {
- type: 'controls_repeat',
- kind: 'block',
- enabled: false,
- fields: {
- TIMES: 10,
- },
- },
- {
- type: 'controls_whileUntil',
- kind: 'block',
- fields: {
- MODE: 'WHILE',
- },
- },
- {
- type: 'controls_for',
- kind: 'block',
- fields: {
- VAR: {
- name: 'i',
- },
- },
- inputs: {
- FROM: {
- shadow: {
- type: 'math_number',
- fields: {
- NUM: 1,
- },
- },
- },
- TO: {
- shadow: {
- type: 'math_number',
- fields: {
- NUM: 10,
- },
- },
- },
- BY: {
- shadow: {
- type: 'math_number',
- fields: {
- NUM: 1,
- },
- },
- },
- },
- },
- {
- type: 'controls_forEach',
- kind: 'block',
- fields: {
- VAR: {
- name: 'j',
- },
- },
- },
- {
- type: 'controls_flow_statements',
- kind: 'block',
- enabled: false,
- fields: {
- FLOW: 'BREAK',
- },
- },
- ],
- },
- {
- kind: 'sep',
- },
- {
- kind: 'category',
- name: 'Variables',
- custom: 'VARIABLE',
- categorystyle: 'variable_category',
- },
- {
- kind: 'category',
- name: 'Buttons and Blocks',
- categorystyle: 'loop_category',
- contents: [
- {
- type: 'controls_repeat',
- kind: 'block',
- fields: {
- TIMES: 10,
- },
- },
- {
- kind: 'BUTTON',
- text: 'Randomize Button Style',
- callbackkey: 'setRandomStyle',
- },
- {
- kind: 'BUTTON',
- text: 'Randomize Button Style',
- callbackkey: 'setRandomStyle',
- },
- {
- type: 'controls_repeat',
- kind: 'block',
- fields: {
- TIMES: 10,
- },
- },
- {
- kind: 'BUTTON',
- text: 'Randomize Button Style',
- callbackkey: 'setRandomStyle',
- },
- ],
- },
- {
- kind: 'sep',
- },
- {
- kind: 'category',
- name: 'Nested Categories',
- contents: [
- {
- kind: 'category',
- name: 'sub-category 1',
- contents: [
- {
- kind: 'BUTTON',
- text: 'Randomize Button Style',
- callbackkey: 'setRandomStyle',
- },
- {
- type: 'logic_boolean',
- kind: 'block',
- fields: {
- BOOL: 'TRUE',
- },
- },
- ],
- },
- {
- kind: 'category',
- name: 'sub-category 2',
- contents: [
- {
- type: 'logic_boolean',
- kind: 'block',
- fields: {
- BOOL: 'FALSE',
- },
- },
- {
- kind: 'BUTTON',
- text: 'Randomize Button Style',
- callbackkey: 'setRandomStyle',
- },
- ],
- },
- ],
- },
- ],
-};