Remove keyboard navigation from core (#4593)

This commit is contained in:
alschmiedt
2021-01-19 11:50:51 -08:00
committed by GitHub
parent a9ea08aba9
commit a3adc42e8a
21 changed files with 50 additions and 2800 deletions

View File

@@ -24,7 +24,6 @@ goog.require('Blockly.Events.BlockMove');
goog.require('Blockly.Extensions');
goog.require('Blockly.fieldRegistry');
goog.require('Blockly.Input');
goog.require('Blockly.navigation');
goog.require('Blockly.Tooltip');
goog.require('Blockly.utils');
goog.require('Blockly.utils.deprecation');

View File

@@ -23,7 +23,6 @@ goog.require('Blockly.Events');
goog.require('Blockly.Events.BlockMove');
goog.require('Blockly.Events.Selected');
goog.require('Blockly.Msg');
goog.require('Blockly.navigation');
goog.require('Blockly.RenderedConnection');
goog.require('Blockly.TabNavigateCursor');
goog.require('Blockly.Tooltip');
@@ -692,11 +691,12 @@ Blockly.BlockSvg.prototype.tab = function(start, forward) {
var tabCursor = new Blockly.TabNavigateCursor();
tabCursor.setCurNode(Blockly.ASTNode.createFieldNode(start));
var currentNode = tabCursor.getCurNode();
var actionName = forward ?
Blockly.navigation.actionNames.NEXT : Blockly.navigation.actionNames.PREVIOUS;
tabCursor.onBlocklyAction(
/** @type {!Blockly.ShortcutRegistry.KeyboardShortcut} */ ({name: actionName}));
if (forward) {
tabCursor.next();
} else {
tabCursor.prev();
}
var nextNode = tabCursor.getCurNode();
if (nextNode && nextNode !== currentNode) {
@@ -907,10 +907,6 @@ Blockly.BlockSvg.prototype.dispose = function(healStack, animate) {
Blockly.ContextMenu.hide();
}
if (this.workspace.keyboardAccessibilityMode) {
Blockly.navigation.moveCursorOnBlockDelete(this);
}
if (animate && this.rendered) {
this.unplug(healStack);
Blockly.blockAnimations.disposeUiEffect(this);
@@ -1660,7 +1656,8 @@ Blockly.BlockSvg.prototype.updateMarkers_ = function() {
this.workspace.getCursor().draw();
}
if (this.workspace.keyboardAccessibilityMode && this.pathObject.markerSvg) {
this.workspace.getMarker(Blockly.navigation.MARKER_NAME).draw();
// TODO(#4592): Update all markers on the block.
this.workspace.getMarker(Blockly.MarkerManager.LOCAL_MARKER).draw();
}
};

View File

@@ -29,7 +29,7 @@ goog.require('Blockly.utils.userAgent');
goog.requireType('Blockly.blockRendering.ConstantProvider');
goog.requireType('Blockly.IASTNodeLocationSvg');
goog.requireType('Blockly.IASTNodeLocationWithBlock');
goog.requireType('Blockly.IBlocklyActionable');
goog.requireType('Blockly.IKeyboardAccessible');
goog.requireType('Blockly.IRegistrable');
goog.requireType('Blockly.ShortcutRegistry');
@@ -46,7 +46,7 @@ goog.requireType('Blockly.ShortcutRegistry');
* @constructor
* @implements {Blockly.IASTNodeLocationSvg}
* @implements {Blockly.IASTNodeLocationWithBlock}
* @implements {Blockly.IBlocklyActionable}
* @implements {Blockly.IKeyboardAccessible}
* @implements {Blockly.IRegistrable}
*/
Blockly.Field = function(value, opt_validator, opt_config) {
@@ -1024,13 +1024,12 @@ Blockly.Field.prototype.isTabNavigable = function() {
};
/**
* Handles the given action.
* This is only triggered when keyboard accessibility mode is enabled.
* @param {!Blockly.ShortcutRegistry.KeyboardShortcut} _action The action to be handled.
* @return {boolean} True if the field handled the action, false otherwise.
* @package
* Handles the given keyboard shortcut.
* @param {!Blockly.ShortcutRegistry.KeyboardShortcut} _shortcut The shortcut to be handled.
* @return {boolean} True if the shortcut has been handled, false otherwise.
* @public
*/
Blockly.Field.prototype.onBlocklyAction = function(_action) {
Blockly.Field.prototype.onShortcut = function(_shortcut) {
return false;
};
@@ -1077,6 +1076,7 @@ Blockly.Field.prototype.updateMarkers_ = function() {
workspace.getCursor().draw();
}
if (workspace.keyboardAccessibilityMode && this.markerSvg_) {
workspace.getMarker(Blockly.navigation.MARKER_NAME).draw();
// TODO(#4592): Update all markers on the field.
workspace.getMarker(Blockly.MarkerManager.LOCAL_MARKER).draw();
}
};

View File

@@ -18,7 +18,6 @@ goog.require('Blockly.Events');
goog.require('Blockly.Events.BlockChange');
goog.require('Blockly.Field');
goog.require('Blockly.fieldRegistry');
goog.require('Blockly.navigation');
goog.require('Blockly.utils.aria');
goog.require('Blockly.utils.colour');
goog.require('Blockly.utils.dom');
@@ -382,35 +381,6 @@ Blockly.FieldColour.prototype.onKeyDown_ = function(e) {
}
};
/**
* Handles the given action.
* This is only triggered when keyboard accessibility mode is enabled.
* @param {!Blockly.ShortcutRegistry.KeyboardShortcut} action The action to be handled.
* @return {boolean} True if the field handled the action, false otherwise.
* @package
*/
Blockly.FieldColour.prototype.onBlocklyAction = function(action) {
if (this.picker_) {
switch (action.name) {
case Blockly.navigation.actionNames.PREVIOUS:
this.moveHighlightBy_(0, -1);
return true;
case Blockly.navigation.actionNames.NEXT:
this.moveHighlightBy_(0, 1);
return true;
case Blockly.navigation.actionNames.OUT:
this.moveHighlightBy_(-1, 0);
return true;
case Blockly.navigation.actionNames.IN:
this.moveHighlightBy_(1, 0);
return true;
default:
return false;
}
}
return Blockly.FieldColour.superClass_.onBlocklyAction.call(this, action);
};
/**
* Move the currently highlighted position by dx and dy.
* @param {number} dx Change of x

View File

@@ -20,7 +20,6 @@ goog.require('Blockly.Field');
goog.require('Blockly.fieldRegistry');
goog.require('Blockly.Menu');
goog.require('Blockly.MenuItem');
goog.require('Blockly.navigation');
goog.require('Blockly.utils');
goog.require('Blockly.utils.aria');
goog.require('Blockly.utils.Coordinate');
@@ -738,28 +737,4 @@ Blockly.FieldDropdown.validateOptions_ = function(options) {
}
};
/**
* Handles the given action.
* This is only triggered when keyboard accessibility mode is enabled.
* @param {!Blockly.ShortcutRegistry.KeyboardShortcut} action The action to be handled.
* @return {boolean} True if the field handled the action, false otherwise.
* @package
*/
Blockly.FieldDropdown.prototype.onBlocklyAction = function(action) {
if (this.menu_) {
switch (action.name) {
case Blockly.navigation.actionNames.PREVIOUS:
this.menu_.highlightPrevious();
return true;
case Blockly.navigation.actionNames.NEXT:
this.menu_.highlightNext();
return true;
default:
return false;
}
}
return Blockly.FieldDropdown.superClass_.onBlocklyAction.call(this, action);
};
Blockly.fieldRegistry.register('field_dropdown', Blockly.FieldDropdown);

View File

@@ -17,7 +17,6 @@ goog.require('Blockly.blockRendering');
goog.require('Blockly.Events');
goog.require('Blockly.Events.BlockCreate');
goog.require('Blockly.Events.VarCreate');
goog.require('Blockly.FlyoutCursor');
goog.require('Blockly.Gesture');
goog.require('Blockly.Marker');
goog.require('Blockly.Scrollbar');
@@ -31,7 +30,6 @@ goog.require('Blockly.utils.toolbox');
goog.require('Blockly.WorkspaceSvg');
goog.require('Blockly.Xml');
goog.requireType('Blockly.IBlocklyActionable');
goog.requireType('Blockly.IDeleteArea');
goog.requireType('Blockly.IFlyout');
goog.requireType('Blockly.ShortcutRegistry');
@@ -44,7 +42,6 @@ goog.requireType('Blockly.utils.Metrics');
* workspace.
* @constructor
* @abstract
* @implements {Blockly.IBlocklyActionable}
* @implements {Blockly.IDeleteArea}
* @implements {Blockly.IFlyout}
*/
@@ -253,7 +250,6 @@ Blockly.Flyout.prototype.createDom = function(tagName) {
this.svgBackground_, 'flyoutBackgroundColour', 'fill');
this.workspace_.getThemeManager().subscribe(
this.svgBackground_, 'flyoutOpacity', 'fill-opacity');
this.workspace_.getMarkerManager().setCursor(new Blockly.FlyoutCursor());
return this.svgGroup_;
};
@@ -995,18 +991,6 @@ Blockly.Flyout.prototype.placeNewBlock_ = function(oldBlock) {
return block;
};
/**
* Handles the given action.
* This is only triggered when keyboard accessibility mode is enabled.
* @param {!Blockly.ShortcutRegistry.KeyboardShortcut} action The action to be handled.
* @return {boolean} True if the flyout handled the action, false otherwise.
* @package
*/
Blockly.Flyout.prototype.onBlocklyAction = function(action) {
var cursor = this.workspace_.getCursor();
return cursor.onBlocklyAction(action);
};
/**
* Return the deletion rectangle for this flyout in viewport coordinates.
* @return {Blockly.utils.Rect} Rectangle in which to delete.

View File

@@ -21,7 +21,6 @@ goog.require('Blockly.constants');
goog.require('Blockly.Events');
goog.require('Blockly.Events.Click');
goog.require('Blockly.FlyoutDragger');
goog.require('Blockly.navigation');
goog.require('Blockly.Tooltip');
goog.require('Blockly.Touch');
goog.require('Blockly.utils');
@@ -488,13 +487,7 @@ Blockly.Gesture.prototype.doStart = function(e) {
Blockly.Tooltip.block();
if (this.targetBlock_) {
if (!this.targetBlock_.isInFlyout && e.shiftKey &&
this.targetBlock_.workspace.keyboardAccessibilityMode) {
this.creatorWorkspace_.getCursor().setCurNode(
Blockly.ASTNode.createTopNode(this.targetBlock_));
} else {
this.targetBlock_.select();
}
this.targetBlock_.select();
}
if (Blockly.utils.isRightButton(e)) {
@@ -582,7 +575,7 @@ Blockly.Gesture.prototype.handleUp = function(e) {
} else if (this.isBlockClick_()) {
this.doBlockClick_();
} else if (this.isWorkspaceClick_()) {
this.doWorkspaceClick_(e);
this.doWorkspaceClick_();
}
e.preventDefault();
@@ -654,9 +647,6 @@ Blockly.Gesture.prototype.handleWsStart = function(e, ws) {
this.setStartWorkspace_(ws);
this.mostRecentEvent_ = e;
this.doStart(e);
if (this.startWorkspace_.keyboardAccessibilityMode) {
Blockly.navigation.setState(Blockly.navigation.STATE_WS);
}
};
/**
@@ -767,17 +757,11 @@ Blockly.Gesture.prototype.doBlockClick_ = function() {
/**
* Execute a workspace click. When in accessibility mode shift clicking will
* move the cursor.
* @param {!Event} e A mouse up or touch end event.
* @private
*/
Blockly.Gesture.prototype.doWorkspaceClick_ = function(e) {
Blockly.Gesture.prototype.doWorkspaceClick_ = function() {
var ws = this.creatorWorkspace_;
if (e.shiftKey && ws.keyboardAccessibilityMode) {
var screenCoord = new Blockly.utils.Coordinate(e.clientX, e.clientY);
var wsCoord = Blockly.utils.screenToWsCoordinates(ws, screenCoord);
var wsNode = Blockly.ASTNode.createWorkspaceNode(ws, wsCoord);
ws.getCursor().setCurNode(wsNode);
} else if (Blockly.selected) {
if (Blockly.selected) {
Blockly.selected.unselect();
}
this.fireWorkspaceClick_(this.startWorkspace_ || ws);

View File

@@ -14,7 +14,7 @@
goog.provide('Blockly.IASTNodeLocation');
goog.provide('Blockly.IASTNodeLocationSvg');
goog.provide('Blockly.IASTNodeLocationWithBlock');
goog.provide('Blockly.IBlocklyActionable');
goog.provide('Blockly.IKeyboardAccessible');
goog.requireType('Blockly.ShortcutRegistry');
/**
@@ -59,15 +59,14 @@ Blockly.IASTNodeLocationWithBlock.prototype.getSourceBlock;
/**
* An interface for an object that handles Blockly actions when keyboard
* navigation is enabled.
* An interface for an object that handles keyboard shortcuts.
* @interface
*/
Blockly.IBlocklyActionable = function() {};
Blockly.IKeyboardAccessible = function() {};
/**
* Handles the given action.
* @param {!Blockly.ShortcutRegistry.KeyboardShortcut} action The action to be handled.
* @return {boolean} True if the action has been handled, false otherwise.
* Handles the given keyboard shortcut.
* @param {!Blockly.ShortcutRegistry.KeyboardShortcut} shortcut The shortcut to be handled.
* @return {boolean} True if the shortcut has been handled, false otherwise.
*/
Blockly.IBlocklyActionable.prototype.onBlocklyAction;
Blockly.IKeyboardAccessible.prototype.onShortcut;

View File

@@ -15,10 +15,8 @@ goog.provide('Blockly.Cursor');
goog.require('Blockly.ASTNode');
goog.require('Blockly.Marker');
goog.require('Blockly.navigation');
goog.require('Blockly.utils.object');
goog.requireType('Blockly.IBlocklyActionable');
goog.requireType('Blockly.ShortcutRegistry');
@@ -27,7 +25,6 @@ goog.requireType('Blockly.ShortcutRegistry');
* A cursor controls how a user navigates the Blockly AST.
* @constructor
* @extends {Blockly.Marker}
* @implements {Blockly.IBlocklyActionable}
*/
Blockly.Cursor = function() {
Blockly.Cursor.superClass_.constructor.call(this);
@@ -43,7 +40,7 @@ Blockly.utils.object.inherits(Blockly.Cursor, Blockly.Marker);
* Find the next connection, field, or block.
* @return {Blockly.ASTNode} The next element, or null if the current node is
* not set or there is no next value.
* @protected
* @public
*/
Blockly.Cursor.prototype.next = function() {
var curNode = this.getCurNode();
@@ -68,7 +65,7 @@ Blockly.Cursor.prototype.next = function() {
* Find the in connection or field.
* @return {Blockly.ASTNode} The in element, or null if the current node is
* not set or there is no in value.
* @protected
* @public
*/
Blockly.Cursor.prototype.in = function() {
var curNode = this.getCurNode();
@@ -93,7 +90,7 @@ Blockly.Cursor.prototype.in = function() {
* Find the previous connection, field, or block.
* @return {Blockly.ASTNode} The previous element, or null if the current node
* is not set or there is no previous value.
* @protected
* @public
*/
Blockly.Cursor.prototype.prev = function() {
var curNode = this.getCurNode();
@@ -118,7 +115,7 @@ Blockly.Cursor.prototype.prev = function() {
* Find the out connection, field, or block.
* @return {Blockly.ASTNode} The out element, or null if the current node is
* not set or there is no out value.
* @protected
* @public
*/
Blockly.Cursor.prototype.out = function() {
var curNode = this.getCurNode();
@@ -136,35 +133,3 @@ Blockly.Cursor.prototype.out = function() {
}
return newNode;
};
/**
* Handles the given action.
* This is only triggered when keyboard navigation is enabled.
* @param {!Blockly.ShortcutRegistry.KeyboardShortcut} action The action to be handled.
* @return {boolean} True if the action has been handled, false otherwise.
*/
Blockly.Cursor.prototype.onBlocklyAction = function(action) {
// If we are on a field give it the option to handle the action
if (this.getCurNode() &&
this.getCurNode().getType() === Blockly.ASTNode.types.FIELD &&
(/** @type {!Blockly.Field} */ (this.getCurNode().getLocation()))
.onBlocklyAction(action)) {
return true;
}
switch (action.name) {
case Blockly.navigation.actionNames.PREVIOUS:
this.prev();
return true;
case Blockly.navigation.actionNames.OUT:
this.out();
return true;
case Blockly.navigation.actionNames.NEXT:
this.next();
return true;
case Blockly.navigation.actionNames.IN:
this.in();
return true;
default:
return false;
}
};

View File

@@ -1,108 +0,0 @@
/**
* @license
* Copyright 2019 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @fileoverview The class representing a cursor used to navigate the flyout.
* Used primarily for keyboard navigation.
* @author aschmiedt@google.com (Abby Schmiedt)
*/
'use strict';
goog.provide('Blockly.FlyoutCursor');
goog.require('Blockly.Cursor');
goog.require('Blockly.navigation');
goog.require('Blockly.utils.object');
goog.requireType('Blockly.ShortcutRegistry');
/**
* Class for a flyout cursor.
* This controls how a user navigates blocks in the flyout.
* @constructor
* @extends {Blockly.Cursor}
*/
Blockly.FlyoutCursor = function() {
Blockly.FlyoutCursor.superClass_.constructor.call(this);
};
Blockly.utils.object.inherits(Blockly.FlyoutCursor, Blockly.Cursor);
/**
* Handles the given action.
* This is only triggered when keyboard navigation is enabled.
* @param {!Blockly.ShortcutRegistry.KeyboardShortcut} action The action to be handled.
* @return {boolean} True if the action has been handled, false otherwise.
* @override
*/
Blockly.FlyoutCursor.prototype.onBlocklyAction = function(action) {
switch (action.name) {
case Blockly.navigation.actionNames.PREVIOUS:
this.prev();
return true;
case Blockly.navigation.actionNames.NEXT:
this.next();
return true;
default:
return false;
}
};
/**
* Find the next connection, field, or block.
* @return {Blockly.ASTNode} The next element, or null if the current node is
* not set or there is no next value.
* @override
*/
Blockly.FlyoutCursor.prototype.next = function() {
var curNode = this.getCurNode();
if (!curNode) {
return null;
}
var newNode = curNode.next();
if (newNode) {
this.setCurNode(newNode);
}
return newNode;
};
/**
* This is a no-op since a flyout cursor can not go in.
* @return {null} Always null.
* @override
*/
Blockly.FlyoutCursor.prototype.in = function() {
return null;
};
/**
* Find the previous connection, field, or block.
* @return {Blockly.ASTNode} The previous element, or null if the current node
* is not set or there is no previous value.
* @override
*/
Blockly.FlyoutCursor.prototype.prev = function() {
var curNode = this.getCurNode();
if (!curNode) {
return null;
}
var newNode = curNode.prev();
if (newNode) {
this.setCurNode(newNode);
}
return newNode;
};
/**
* This is a no-op since a flyout cursor can not go out.
* @return {null} Always null.
* @override
*/
Blockly.FlyoutCursor.prototype.out = function() {
return null;
};

View File

@@ -14,7 +14,6 @@
goog.provide('Blockly.Marker');
goog.require('Blockly.ASTNode');
goog.require('Blockly.navigation');
/**

File diff suppressed because it is too large Load Diff

View File

@@ -52,6 +52,13 @@ Blockly.MarkerManager = function(workspace){
this.workspace_ = workspace;
};
/**
* The name of the local marker.
* @type {string}
* @const
*/
Blockly.MarkerManager.LOCAL_MARKER = 'local_marker_1';
/**
* Register the marker by adding it to the map of markers.
* @param {string} id A unique identifier for the marker.

View File

@@ -18,7 +18,6 @@ goog.require('Blockly.Events');
goog.require('Blockly.Events.BlockChange');
goog.require('Blockly.Events.BubbleOpen');
goog.require('Blockly.Icon');
goog.require('Blockly.navigation');
goog.require('Blockly.utils');
goog.require('Blockly.utils.dom');
goog.require('Blockly.utils.global');
@@ -414,11 +413,6 @@ Blockly.Mutator.prototype.workspaceChanged_ = function(e) {
// Mutation may have added some elements that need initializing.
block.initSvg();
if ((/** @type {!Blockly.WorkspaceSvg} */ (Blockly.getMainWorkspace()))
.keyboardAccessibilityMode) {
Blockly.navigation.moveCursorOnBlockMutation(block);
}
if (block.rendered) {
block.render();
}

View File

@@ -13,7 +13,6 @@
goog.provide('Blockly.ShortcutRegistry');
goog.require('Blockly.navigation');
goog.require('Blockly.ShortcutItems');
goog.require('Blockly.utils.object');
@@ -43,7 +42,6 @@ Blockly.ShortcutRegistry = function() {
this.keyMap_ = Object.create(null);
Blockly.ShortcutItems.registerDefaultShortcuts();
Blockly.navigation.registerNavigationShortcuts();
};
/**

View File

@@ -17,7 +17,6 @@ goog.require('Blockly.constants');
goog.require('Blockly.Css');
goog.require('Blockly.Events');
goog.require('Blockly.Events.ToolboxItemSelect');
goog.require('Blockly.navigation');
goog.require('Blockly.registry');
goog.require('Blockly.Touch');
goog.require('Blockly.utils');
@@ -26,7 +25,7 @@ goog.require('Blockly.utils.dom');
goog.require('Blockly.utils.Rect');
goog.require('Blockly.utils.toolbox');
goog.requireType('Blockly.IBlocklyActionable');
goog.requireType('Blockly.IKeyboardAccessible');
goog.requireType('Blockly.ICollapsibleToolboxItem');
goog.requireType('Blockly.IDeleteArea');
goog.requireType('Blockly.IFlyout');
@@ -44,7 +43,7 @@ goog.requireType('Blockly.WorkspaceSvg');
* @param {!Blockly.WorkspaceSvg} workspace The workspace in which to create new
* blocks.
* @constructor
* @implements {Blockly.IBlocklyActionable}
* @implements {Blockly.IKeyboardAccessible}
* @implements {Blockly.IDeleteArea}
* @implements {Blockly.IStyleable}
* @implements {Blockly.IToolbox}
@@ -155,6 +154,16 @@ Blockly.Toolbox = function(workspace) {
this.boundEvents_ = [];
};
/**
* Handles the given keyboard shortcut.
* @param {!Blockly.ShortcutRegistry.KeyboardShortcut} _shortcut The shortcut to be handled.
* @return {boolean} True if the shortcut has been handled, false otherwise.
* @public
*/
Blockly.Toolbox.prototype.onShortcut = function(_shortcut) {
return false;
};
/**
* Initializes the toolbox
* @public
@@ -811,32 +820,6 @@ Blockly.Toolbox.prototype.fireSelectEvent_ = function(oldItem, newItem) {
Blockly.Events.fire(event);
};
/**
* Handles the given Blockly action on a toolbox.
* This is only triggered when keyboard accessibility mode is enabled.
* @param {!Blockly.ShortcutRegistry.KeyboardShortcut} action The action to be handled.
* @return {boolean} True if the field handled the action, false otherwise.
* @package
*/
Blockly.Toolbox.prototype.onBlocklyAction = function(action) {
var selected = this.selectedItem_;
if (!selected) {
return false;
}
switch (action.name) {
case Blockly.navigation.actionNames.PREVIOUS:
return this.selectPrevious_();
case Blockly.navigation.actionNames.OUT:
return this.selectParent_();
case Blockly.navigation.actionNames.NEXT:
return this.selectNext_();
case Blockly.navigation.actionNames.IN:
return this.selectChild_();
default:
return false;
}
};
/**
* Closes the current item if it is expanded, or selects the parent.
* @return {boolean} True if a parent category was selected, false otherwise.

View File

@@ -25,7 +25,6 @@ goog.require('Blockly.Gesture');
goog.require('Blockly.Grid');
goog.require('Blockly.MarkerManager');
goog.require('Blockly.Msg');
goog.require('Blockly.navigation');
goog.require('Blockly.Options');
goog.require('Blockly.registry');
goog.require('Blockly.ThemeManager');
@@ -790,8 +789,6 @@ Blockly.WorkspaceSvg.prototype.createDom = function(opt_backgroundClass) {
this.recordDeleteAreas();
this.markerManager_.setCursor(new Blockly.Cursor());
this.markerManager_.registerMarker(Blockly.navigation.MARKER_NAME,
new Blockly.Marker());
this.renderer_.createDom(this.svgGroup_, this.getTheme());
return this.svgGroup_;
@@ -1358,17 +1355,6 @@ Blockly.WorkspaceSvg.prototype.pasteBlock_ = function(xmlBlock) {
try {
var block = Blockly.Xml.domToBlock(xmlBlock, this);
// Handle paste for keyboard navigation
var markedNode = this.getMarker(Blockly.navigation.MARKER_NAME).getCurNode();
if (this.keyboardAccessibilityMode && markedNode &&
markedNode.isConnection()) {
var markedLocation =
/** @type {!Blockly.RenderedConnection} */ (markedNode.getLocation());
Blockly.navigation.insertBlock(/** @type {!Blockly.BlockSvg} */ (block),
markedLocation);
return;
}
// Move the duplicate to original position.
var blockX = parseInt(xmlBlock.getAttribute('x'), 10);
var blockY = parseInt(xmlBlock.getAttribute('y'), 10);

View File

@@ -88,16 +88,4 @@ suite('Gesture', function() {
var block = getTopFlyoutBlock(flyout);
testGestureIsFieldClick(block, true, this.eventsFireStub);
});
test('Shift click in accessibility mode - moves the cursor', function() {
this.workspace.keyboardAccessibilityMode = true;
var eventTarget = this.workspace.svgGroup_;
simulateClick(eventTarget, {shiftKey: true});
var cursor = this.workspace.getCursor();
var cursorNode = cursor.getCurNode();
chai.assert.exists(cursorNode);
chai.assert.equal(cursorNode.getType(), Blockly.ASTNode.types.WORKSPACE);
});
});

View File

@@ -86,8 +86,6 @@
<script src="logic_ternary_test.js"></script>
<script src="metrics_test.js"></script>
<script src="names_test.js"></script>
<script src="navigation_modify_test.js"></script>
<script src="navigation_test.js"></script>
<script src="procedures_test_helpers.js"></script>
<script src="procedures_test.js"></script>
<script src="registry_test.js"></script>
@@ -113,22 +111,6 @@
<button text="insert" callbackkey="insertConnectionRows"></button>
<label text="tooltips"></label>
</xml>
<xml xmlns="https://developers.google.com/blockly/xml" id="toolbox-minimal" style="display: none">
<block type="basic_block"></block>
</xml>
<xml xmlns="https://developers.google.com/blockly/xml" id="toolbox-incorrect" style="display: none">
<block type="basic_block"></block>
<category name="First">
<block type="basic_block">
<field name="TEXT">FirstCategory-FirstBlock</field>
</block>
<block type="basic_block">
<field name="TEXT">FirstCategory-SecondBlock</field>
</block>
</category>
</xml>
<xml xmlns="https://developers.google.com/blockly/xml" id="toolbox-categories" style="display: none">
<category name="First" css-container="something">
<block type="basic_block">
@@ -144,7 +126,6 @@
</block>
</category>
</xml>
<xml xmlns="https://developers.google.com/blockly/xml" id="toolbox-test" style="display: none">
<category name="First" expanded="true" categorystyle="logic_category">
<sep gap="-1"></sep>
@@ -162,10 +143,6 @@
</category>
<category name="lastItem"></category>
</xml>
<xml xmlns="https://developers.google.com/blockly/xml" id="toolbox-connections" style="display: none">
<block type="stack_block"></block>
<block type="row_block"></block>
</xml>
<xml xmlns="https://developers.google.com/blockly/xml" id="gesture-test-toolbox" style="display: none">
<block type="test_field_block"></block>

View File

@@ -1,420 +0,0 @@
/**
* @license
* Copyright 2019 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
suite('Insert/Modify', function() {
function assertModifyFails() {
var modifyResult;
var warnings = captureWarnings(function() {
modifyResult = Blockly.navigation.modify_();
});
chai.assert.isFalse(modifyResult);
chai.assert.equal(warnings.length, 1,
'Expecting 1 warnings for why modify failed.');
}
setup(function() {
sharedTestSetup.call(this);
// NOTE: block positions chosen such that they aren't unintentionally
// bumped out of bounds during tests.
var xmlText = '<xml xmlns="https://developers.google.com/blockly/xml">' +
'<block type="stack_block" id="stack_block_1" x="22" y="38"></block>' +
'<block type="stack_block" id="stack_block_2" x="22" y="113"></block>' +
'<block type="row_block" id="row_block_1" x="23" y="213"></block>' +
'<block type="row_block" id="row_block_2" x="22" y="288"></block>' +
'<block type="statement_block" id="statement_block_1" x="22" y="288"></block>' +
'<block type="statement_block" id="statement_block_2" x="22" y="288"></block>' +
'</xml>';
defineStackBlock(this.sharedCleanup);
defineRowBlock(this.sharedCleanup);
defineStatementBlock(this.sharedCleanup);
var toolbox = document.getElementById('toolbox-connections');
this.workspace = Blockly.inject('blocklyDiv', {toolbox: toolbox});
Blockly.Xml.domToWorkspace(Blockly.Xml.textToDom(xmlText), 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');
Blockly.navigation.enableKeyboardAccessibility();
});
teardown(function() {
sharedTestTeardown.call(this);
});
suite('Marked Connection', function() {
// TODO: Marked connection or cursor connection is already connected.
suite('Marker on next', function() {
setup(function() {
this.workspace.getMarker(Blockly.navigation.MARKER_NAME).setCurNode(
Blockly.ASTNode.createConnectionNode(
this.stack_block_1.nextConnection));
});
test('Cursor on workspace', function() {
this.workspace.getCursor().setCurNode(
Blockly.ASTNode.createWorkspaceNode(this.workspace,
new Blockly.utils.Coordinate(0, 0)));
assertModifyFails();
});
test('Cursor on compatible connection', function() {
this.workspace.getCursor().setCurNode(
Blockly.ASTNode.createConnectionNode(
this.stack_block_2.previousConnection));
chai.assert.isTrue(Blockly.navigation.modify_());
chai.assert.equal(this.stack_block_1.getNextBlock().id, 'stack_block_2');
});
test('Cursor on incompatible connection', function() {
this.workspace.getCursor().setCurNode(
Blockly.ASTNode.createConnectionNode(
this.stack_block_2.nextConnection));
// Connect method will try to find a way to connect blocks with
// incompatible types.
chai.assert.isTrue(Blockly.navigation.modify_());
chai.assert.equal(this.stack_block_1.getNextBlock(), this.stack_block_2);
});
test('Cursor on really incompatible connection', function() {
this.workspace.getCursor().setCurNode(
Blockly.ASTNode.createConnectionNode(
this.row_block_1.outputConnection));
assertModifyFails();
chai.assert.isNull(this.stack_block_1.getNextBlock());
});
test('Cursor on block', function() {
this.workspace.getCursor().setCurNode(
Blockly.ASTNode.createBlockNode(
this.stack_block_2));
chai.assert.isTrue(Blockly.navigation.modify_());
chai.assert.equal(this.stack_block_1.getNextBlock().id, 'stack_block_2');
});
});
suite('Marker on previous', function() {
setup(function() {
this.workspace.getMarker(Blockly.navigation.MARKER_NAME).setCurNode(
Blockly.ASTNode.createConnectionNode(
this.stack_block_1.previousConnection));
});
test('Cursor on compatible connection', function() {
this.workspace.getCursor().setCurNode(
Blockly.ASTNode.createConnectionNode(
this.stack_block_2.nextConnection));
chai.assert.isTrue(Blockly.navigation.modify_());
chai.assert.equal(this.stack_block_1.getPreviousBlock().id, 'stack_block_2');
});
test('Cursor on incompatible connection', function() {
this.workspace.getCursor().setCurNode(
Blockly.ASTNode.createConnectionNode(
this.stack_block_2.previousConnection));
assertModifyFails();
chai.assert.isNull(this.stack_block_1.getPreviousBlock());
});
test('Cursor on really incompatible connection', function() {
this.workspace.getCursor().setCurNode(
Blockly.ASTNode.createConnectionNode(
this.row_block_1.outputConnection));
assertModifyFails();
chai.assert.isNull(this.stack_block_1.getNextBlock());
});
test('Cursor on block', function() {
this.workspace.getCursor().setCurNode(
Blockly.ASTNode.createBlockNode(
this.stack_block_2));
chai.assert.isTrue(Blockly.navigation.modify_());
chai.assert.equal(this.stack_block_1.getPreviousBlock().id, 'stack_block_2');
});
test('Cursor on incompatible block', function() {
this.workspace.getCursor().setCurNode(
Blockly.ASTNode.createBlockNode(
this.row_block_1));
assertModifyFails();
chai.assert.isNull(this.stack_block_1.getPreviousBlock());
});
});
suite('Marker on value input', function() {
setup(function() {
this.workspace.getMarker(Blockly.navigation.MARKER_NAME).setCurNode(
Blockly.ASTNode.createConnectionNode(
this.row_block_1.inputList[0].connection));
});
test('Cursor on compatible connection', function() {
this.workspace.getCursor().setCurNode(
Blockly.ASTNode.createConnectionNode(
this.row_block_2.outputConnection));
chai.assert.isTrue(Blockly.navigation.modify_());
chai.assert.equal(this.row_block_2.getParent().id, 'row_block_1');
});
test('Cursor on incompatible connection', function() {
this.workspace.getCursor().setCurNode(
Blockly.ASTNode.createConnectionNode(
this.row_block_2.inputList[0].connection));
// Connect method will try to find a way to connect blocks with
// incompatible types.
chai.assert.isTrue(Blockly.navigation.modify_());
chai.assert.equal(this.row_block_1.inputList[0].connection.targetBlock(),
this.row_block_2);
});
test('Cursor on really incompatible connection', function() {
this.workspace.getCursor().setCurNode(
Blockly.ASTNode.createConnectionNode(
this.stack_block_1.previousConnection));
assertModifyFails();
});
test('Cursor on block', function() {
this.workspace.getCursor().setCurNode(
Blockly.ASTNode.createBlockNode(
this.row_block_2));
chai.assert.isTrue(Blockly.navigation.modify_());
chai.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.workspace.getMarker(Blockly.navigation.MARKER_NAME).setCurNode(
Blockly.ASTNode.createInputNode(
this.statement_block_1.inputList[0]));
});
test('Cursor on block inside statement', function() {
this.workspace.getCursor().setCurNode(
Blockly.ASTNode.createConnectionNode(
this.stack_block_2.previousConnection));
chai.assert.isTrue(Blockly.navigation.modify_());
chai.assert.equal(this.stack_block_2.previousConnection.targetBlock(),
this.statement_block_1);
});
test('Cursor on stack', function() {
this.workspace.getCursor().setCurNode(
Blockly.ASTNode.createStackNode(
this.statement_block_2));
chai.assert.isTrue(Blockly.navigation.modify_());
chai.assert.equal(this.statement_block_2.getParent().id, 'statement_block_1');
});
test('Cursor on incompatible type', function() {
this.workspace.getCursor().setCurNode(
Blockly.ASTNode.createConnectionNode(
this.row_block_1.outputConnection));
assertModifyFails();
chai.assert.isNull(this.row_block_1.getParent());
});
});
suite('Marker on output', function() {
setup(function() {
this.workspace.getMarker(Blockly.navigation.MARKER_NAME).setCurNode(
Blockly.ASTNode.createConnectionNode(
this.row_block_1.outputConnection));
});
test('Cursor on compatible connection', function() {
this.workspace.getCursor().setCurNode(
Blockly.ASTNode.createConnectionNode(
this.row_block_2.inputList[0].connection));
chai.assert.isTrue(Blockly.navigation.modify_());
chai.assert.equal(this.row_block_1.getParent().id, 'row_block_2');
});
test('Cursor on incompatible connection', function() {
this.workspace.getCursor().setCurNode(
Blockly.ASTNode.createConnectionNode(
this.row_block_2.outputConnection));
assertModifyFails();
});
test('Cursor on really incompatible connection', function() {
this.workspace.getCursor().setCurNode(
Blockly.ASTNode.createConnectionNode(
this.stack_block_1.previousConnection));
assertModifyFails();
});
test('Cursor on block', function() {
this.workspace.getCursor().setCurNode(
Blockly.ASTNode.createBlockNode(
this.row_block_2));
chai.assert.isTrue(Blockly.navigation.modify_());
chai.assert.equal(this.row_block_1.getParent().id, 'row_block_2');
});
});
});
suite('Marked Workspace', function() {
setup(function() {
this.workspace.getMarker(Blockly.navigation.MARKER_NAME).drawer_ = null;
this.workspace.getMarker(Blockly.navigation.MARKER_NAME).setCurNode(
Blockly.ASTNode.createWorkspaceNode(
this.workspace, new Blockly.utils.Coordinate(100, 200)));
});
test('Cursor on row block', function() {
this.workspace.getCursor().setCurNode(
Blockly.ASTNode.createBlockNode(
this.row_block_1));
chai.assert.isTrue(Blockly.navigation.modify_());
var pos = this.row_block_1.getRelativeToSurfaceXY();
chai.assert.equal(pos.x, 100);
chai.assert.equal(pos.y, 200);
});
test('Cursor on output connection', function() {
this.workspace.getCursor().setCurNode(
Blockly.ASTNode.createConnectionNode(
this.row_block_1.outputConnection));
chai.assert.isTrue(Blockly.navigation.modify_());
var pos = this.row_block_1.getRelativeToSurfaceXY();
chai.assert.equal(pos.x, 100);
chai.assert.equal(pos.y, 200);
});
test('Cursor on previous connection', function() {
this.workspace.getCursor().setCurNode(
Blockly.ASTNode.createConnectionNode(
this.stack_block_1.previousConnection));
chai.assert.isTrue(Blockly.navigation.modify_());
var pos = this.stack_block_1.getRelativeToSurfaceXY();
chai.assert.equal(pos.x, 100);
chai.assert.equal(pos.y, 200);
});
test('Cursor on input connection', function() {
this.workspace.getCursor().setCurNode(
Blockly.ASTNode.createConnectionNode(
this.row_block_1.inputList[0].connection));
// Move the source block to the marked location on the workspace.
chai.assert.isTrue(Blockly.navigation.modify_());
});
test('Cursor on next connection', function() {
this.workspace.getCursor().setCurNode(
Blockly.ASTNode.createConnectionNode(
this.stack_block_1.nextConnection));
// Move the source block to the marked location on the workspace.
chai.assert.isTrue(Blockly.navigation.modify_());
});
test('Cursor on child block (row)', function() {
this.row_block_1.inputList[0].connection.connect(
this.row_block_2.outputConnection);
this.workspace.getCursor().setCurNode(
Blockly.ASTNode.createBlockNode(
this.row_block_2));
chai.assert.isTrue(Blockly.navigation.modify_());
chai.assert.isNull(this.row_block_2.getParent());
var pos = this.row_block_2.getRelativeToSurfaceXY();
chai.assert.equal(pos.x, 100);
chai.assert.equal(pos.y, 200);
});
test('Cursor on child block (stack)', function() {
this.stack_block_1.nextConnection.connect(
this.stack_block_2.previousConnection);
this.workspace.getCursor().setCurNode(
Blockly.ASTNode.createBlockNode(
this.stack_block_2));
chai.assert.isTrue(Blockly.navigation.modify_());
chai.assert.isNull(this.stack_block_2.getParent());
var pos = this.stack_block_2.getRelativeToSurfaceXY();
chai.assert.equal(pos.x, 100);
chai.assert.equal(pos.y, 200);
});
test('Cursor on workspace', function() {
this.workspace.getCursor().setCurNode(
Blockly.ASTNode.createWorkspaceNode(
this.workspace, new Blockly.utils.Coordinate(100, 100)));
assertModifyFails();
});
});
suite('Marked Block', function() {
// TODO: Decide whether it ever makes sense to mark a block, and what to do
// if so. For now all of these attempted modifications will fail.
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.workspace.getMarker(Blockly.navigation.MARKER_NAME).setCurNode(
Blockly.ASTNode.createBlockNode(
this.stack_block_1));
});
test('Cursor on workspace', function() {
this.workspace.getCursor().setCurNode(
Blockly.ASTNode.createWorkspaceNode(
this.workspace, new Blockly.utils.Coordinate(100, 100)));
assertModifyFails();
});
});
suite('Marked stack block', function() {
setup(function() {
this.workspace.getMarker(Blockly.navigation.MARKER_NAME).setCurNode(
Blockly.ASTNode.createBlockNode(
this.stack_block_1));
});
test('Cursor on row block', function() {
this.workspace.getCursor().setCurNode(
Blockly.ASTNode.createBlockNode(
this.row_block_1));
assertModifyFails();
});
test('Cursor on stack block', function() {
this.workspace.getCursor().setCurNode(
Blockly.ASTNode.createBlockNode(
this.stack_block_1));
assertModifyFails();
});
test('Cursor on next connection', function() {
this.workspace.getCursor().setCurNode(
Blockly.ASTNode.createConnectionNode(
this.stack_block_2.nextConnection));
assertModifyFails();
});
test('Cursor on previous connection', function() {
this.workspace.getCursor().setCurNode(
Blockly.ASTNode.createConnectionNode(
this.stack_block_2.previousConnection));
assertModifyFails();
});
});
suite('Marked row block', function() {
setup(function() {
this.workspace.getMarker(Blockly.navigation.MARKER_NAME).setCurNode(
Blockly.ASTNode.createBlockNode(
this.row_block_1));
});
test('Cursor on stack block', function() {
this.workspace.getCursor().setCurNode(
Blockly.ASTNode.createBlockNode(
this.stack_block_1));
assertModifyFails();
});
test('Cursor on row block', function() {
this.workspace.getCursor().setCurNode(
Blockly.ASTNode.createBlockNode(
this.row_block_1));
assertModifyFails();
});
test('Cursor on value input connection', function() {
this.workspace.getCursor().setCurNode(
Blockly.ASTNode.createConnectionNode(
this.row_block_2.inputList[0].connection));
assertModifyFails();
});
test('Cursor on output connection', function() {
this.workspace.getCursor().setCurNode(
Blockly.ASTNode.createConnectionNode(
this.row_block_2.outputConnection));
assertModifyFails();
});
});
});
});

View File

@@ -1,803 +0,0 @@
/**
* @license
* Copyright 2019 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @fileoverview Navigation tests.
* @author aschmiedt@google.com (Abby Schmiedt)
*/
'use strict';
suite('Navigation', function() {
function createNavigationWorkspace(enableKeyboardNav, readOnly) {
var toolbox = document.getElementById('toolbox-categories');
var workspace =
Blockly.inject('blocklyDiv', {toolbox: toolbox, readOnly: readOnly});
if (enableKeyboardNav) {
Blockly.navigation.enableKeyboardAccessibility();
Blockly.navigation.currentState_ = Blockly.navigation.STATE_WS;
}
return workspace;
}
setup(function() {
sharedTestSetup.call(this);
});
teardown(function() {
sharedTestTeardown.call(this);
});
// 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": "TEXT",
"text": "default"
}
]
}]);
this.workspace = createNavigationWorkspace(true);
Blockly.navigation.focusToolbox_(this.workspace);
});
teardown(function() {
workspaceTeardown.call(this, this.workspace);
});
var 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) {
var testCaseName = testCase[0];
var mockEvent = testCase[1];
var stubName = testCase[2];
test(testCaseName, function() {
var toolbox = this.workspace.getToolbox();
var selectStub = sinon.stub(toolbox, stubName);
toolbox.selectedItem_ = toolbox.contents_[0];
Blockly.onKeyDown(mockEvent);
sinon.assert.called(selectStub);
});
});
test('Go to flyout', function() {
var mockEvent = createKeyDownEvent(Blockly.utils.KeyCodes.D, 'NotAField');
var keyDownSpy =
sinon.spy(Blockly.ShortcutRegistry.registry, 'onKeyDown');
Blockly.onKeyDown(mockEvent);
chai.assert.isTrue(keyDownSpy.returned(true));
chai.assert.equal(
Blockly.navigation.currentState_, Blockly.navigation.STATE_FLYOUT);
var flyoutCursor = Blockly.navigation.getFlyoutCursor_();
chai.assert.equal(flyoutCursor.getCurNode().getLocation().getFieldValue("TEXT"),
"FirstCategory-FirstBlock");
});
test('Focuses workspace from toolbox (e)', function() {
Blockly.navigation.currentState_ = Blockly.navigation.STATE_TOOLBOX;
var mockEvent = createKeyDownEvent(Blockly.utils.KeyCodes.E, 'NotAField');
var keyDownSpy =
sinon.spy(Blockly.ShortcutRegistry.registry, 'onKeyDown');
Blockly.onKeyDown(mockEvent);
chai.assert.isTrue(keyDownSpy.returned(true));
chai.assert.equal(
Blockly.navigation.currentState_, Blockly.navigation.STATE_WS);
});
test('Focuses workspace from toolbox (escape)', function() {
Blockly.navigation.currentState_ = Blockly.navigation.STATE_TOOLBOX;
var mockEvent =
createKeyDownEvent(Blockly.utils.KeyCodes.ESC, 'NotAField');
var keyDownSpy =
sinon.spy(Blockly.ShortcutRegistry.registry, 'onKeyDown');
Blockly.onKeyDown(mockEvent);
chai.assert.isTrue(keyDownSpy.returned(true));
chai.assert.equal(
Blockly.navigation.currentState_, Blockly.navigation.STATE_WS);
});
// More tests:
// - nested categories
});
// 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": "TEXT",
"text": "default"
}
]
}]);
this.workspace = createNavigationWorkspace(true);
Blockly.navigation.focusToolbox_(this.workspace);
Blockly.navigation.focusFlyout_(this.workspace);
});
teardown(function() {
workspaceTeardown.call(this, this.workspace);
});
// Should be a no-op
test('Previous at beginning', function() {
var mockEvent = createKeyDownEvent(Blockly.utils.KeyCodes.W, 'NotAField');
var keyDownSpy =
sinon.spy(Blockly.ShortcutRegistry.registry, 'onKeyDown');
Blockly.onKeyDown(mockEvent);
chai.assert.isTrue(keyDownSpy.returned(true));
chai.assert.equal(
Blockly.navigation.currentState_, Blockly.navigation.STATE_FLYOUT);
chai.assert.equal(Blockly.navigation.getFlyoutCursor_().getCurNode().getLocation().getFieldValue("TEXT"),
"FirstCategory-FirstBlock");
});
test('Previous', function() {
var flyoutBlocks = this.workspace.getFlyout().getWorkspace().getTopBlocks();
Blockly.navigation.getFlyoutCursor_().setCurNode(
Blockly.ASTNode.createStackNode(flyoutBlocks[1]));
var flyoutBlock = Blockly.navigation.getFlyoutCursor_().getCurNode().getLocation();
chai.assert.equal(flyoutBlock.getFieldValue("TEXT"),
"FirstCategory-SecondBlock");
var mockEvent = createKeyDownEvent(Blockly.utils.KeyCodes.W, 'NotAField');
var keyDownSpy =
sinon.spy(Blockly.ShortcutRegistry.registry, 'onKeyDown');
Blockly.onKeyDown(mockEvent);
chai.assert.isTrue(keyDownSpy.returned(true));
chai.assert.equal(
Blockly.navigation.currentState_, Blockly.navigation.STATE_FLYOUT);
flyoutBlock = Blockly.navigation.getFlyoutCursor_().getCurNode().getLocation();
chai.assert.equal(flyoutBlock.getFieldValue("TEXT"),
"FirstCategory-FirstBlock");
});
test('Next', function() {
var mockEvent = createKeyDownEvent(Blockly.utils.KeyCodes.S, 'NotAField');
var keyDownSpy =
sinon.spy(Blockly.ShortcutRegistry.registry, 'onKeyDown');
Blockly.onKeyDown(mockEvent);
chai.assert.isTrue(keyDownSpy.returned(true));
chai.assert.equal(
Blockly.navigation.currentState_, Blockly.navigation.STATE_FLYOUT);
var flyoutBlock = Blockly.navigation.getFlyoutCursor_().getCurNode().getLocation();
chai.assert.equal(flyoutBlock.getFieldValue("TEXT"),
"FirstCategory-SecondBlock");
});
test('Out', function() {
var mockEvent = createKeyDownEvent(Blockly.utils.KeyCodes.A, 'NotAField');
var keyDownSpy =
sinon.spy(Blockly.ShortcutRegistry.registry, 'onKeyDown');
Blockly.onKeyDown(mockEvent);
chai.assert.isTrue(keyDownSpy.returned(true));
chai.assert.equal(
Blockly.navigation.currentState_, Blockly.navigation.STATE_TOOLBOX);
});
test('Mark', function() {
var mockEvent =
createKeyDownEvent(Blockly.utils.KeyCodes.ENTER, 'NotAField');
var keyDownSpy =
sinon.spy(Blockly.ShortcutRegistry.registry, 'onKeyDown');
Blockly.onKeyDown(mockEvent);
chai.assert.isTrue(keyDownSpy.returned(true));
chai.assert.equal(
Blockly.navigation.currentState_, Blockly.navigation.STATE_WS);
chai.assert.equal(this.workspace.getTopBlocks().length, 1);
});
test('Exit', function() {
var mockEvent =
createKeyDownEvent(Blockly.utils.KeyCodes.ESC, 'NotAField');
var keyDownSpy =
sinon.spy(Blockly.ShortcutRegistry.registry, 'onKeyDown');
Blockly.onKeyDown(mockEvent);
chai.assert.isTrue(keyDownSpy.returned(true));
chai.assert.equal(
Blockly.navigation.currentState_, Blockly.navigation.STATE_WS);
});
});
// 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": "TEXT",
"text": "default"
}
],
"previousStatement": null,
"nextStatement": null
}]);
this.workspace = createNavigationWorkspace(true);
this.basicBlock = this.workspace.newBlock('basic_block');
});
teardown(function() {
workspaceTeardown.call(this, this.workspace);
});
test('Previous', function() {
var prevSpy = sinon.spy(this.workspace.getCursor(), 'prev');
var keyDownSpy =
sinon.spy(Blockly.ShortcutRegistry.registry, 'onKeyDown');
var wEvent = createKeyDownEvent(Blockly.utils.KeyCodes.W, '');
Blockly.onKeyDown(wEvent);
chai.assert.isTrue(keyDownSpy.returned(true));
sinon.assert.calledOnce(prevSpy);
chai.assert.equal(
Blockly.navigation.currentState_, Blockly.navigation.STATE_WS);
});
test('Next', function() {
var nextSpy = sinon.spy(this.workspace.getCursor(), 'next');
var keyDownSpy =
sinon.spy(Blockly.ShortcutRegistry.registry, 'onKeyDown');
var sEvent = createKeyDownEvent(Blockly.utils.KeyCodes.S, '');
Blockly.onKeyDown(sEvent);
chai.assert.isTrue(keyDownSpy.returned(true));
sinon.assert.calledOnce(nextSpy);
chai.assert.equal(
Blockly.navigation.currentState_, Blockly.navigation.STATE_WS);
});
test('Out', function() {
var outSpy = sinon.spy(this.workspace.getCursor(), 'out');
var keyDownSpy =
sinon.spy(Blockly.ShortcutRegistry.registry, 'onKeyDown');
var aEvent = createKeyDownEvent(Blockly.utils.KeyCodes.A, '');
Blockly.onKeyDown(aEvent);
chai.assert.isTrue(keyDownSpy.returned(true));
sinon.assert.calledOnce(outSpy);
chai.assert.equal(
Blockly.navigation.currentState_, Blockly.navigation.STATE_WS);
});
test('In', function() {
var inSpy = sinon.spy(this.workspace.getCursor(), 'in');
var keyDownSpy =
sinon.spy(Blockly.ShortcutRegistry.registry, 'onKeyDown');
var dEvent = createKeyDownEvent(Blockly.utils.KeyCodes.D, '');
Blockly.onKeyDown(dEvent);
chai.assert.isTrue(keyDownSpy.returned(true));
sinon.assert.calledOnce(inSpy);
chai.assert.equal(
Blockly.navigation.currentState_, Blockly.navigation.STATE_WS);
});
test('Insert', function() {
// 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.
var modifyStub = sinon.stub(Blockly.navigation, 'modify_').returns(true);
var keyDownSpy =
sinon.spy(Blockly.ShortcutRegistry.registry, 'onKeyDown');
var iEvent = createKeyDownEvent(Blockly.utils.KeyCodes.I, '');
Blockly.onKeyDown(iEvent);
chai.assert.isTrue(keyDownSpy.returned(true));
sinon.assert.calledOnce(modifyStub);
chai.assert.equal(
Blockly.navigation.currentState_, Blockly.navigation.STATE_WS);
});
test('Mark', function() {
this.workspace.getCursor().setCurNode(
Blockly.ASTNode.createConnectionNode(this.basicBlock.previousConnection));
var keyDownSpy =
sinon.spy(Blockly.ShortcutRegistry.registry, 'onKeyDown');
var enterEvent = createKeyDownEvent(Blockly.utils.KeyCodes.ENTER, '');
Blockly.onKeyDown(enterEvent);
var markedNode = this.workspace.getMarker(Blockly.navigation.MARKER_NAME).getCurNode();
chai.assert.isTrue(keyDownSpy.returned(true));
chai.assert.equal(markedNode.getLocation(), this.basicBlock.previousConnection);
chai.assert.equal(
Blockly.navigation.currentState_, Blockly.navigation.STATE_WS);
});
test('Toolbox', function() {
var keyDownSpy =
sinon.spy(Blockly.ShortcutRegistry.registry, 'onKeyDown');
var tEvent = createKeyDownEvent(Blockly.utils.KeyCodes.T, '');
Blockly.onKeyDown(tEvent);
var firstCategory = this.workspace.getToolbox().contents_[0];
chai.assert.isTrue(keyDownSpy.returned(true));
chai.assert.equal(
this.workspace.getToolbox().getSelectedItem(), firstCategory);
chai.assert.equal(
Blockly.navigation.currentState_, Blockly.navigation.STATE_TOOLBOX);
});
});
suite('Test key press', function() {
setup(function() {
Blockly.defineBlocksWithJsonArray([{
"type": "basic_block",
"message0": "%1",
"args0": [
{
"type": "field_dropdown",
"name": "OP",
"options": [
["%{BKY_MATH_ADDITION_SYMBOL}", "ADD"],
["%{BKY_MATH_SUBTRACTION_SYMBOL}", "MINUS"],
["%{BKY_MATH_MULTIPLICATION_SYMBOL}", "MULTIPLY"],
["%{BKY_MATH_DIVISION_SYMBOL}", "DIVIDE"],
["%{BKY_MATH_POWER_SYMBOL}", "POWER"]
]
}
]
}]);
this.workspace = createNavigationWorkspace(true);
this.workspace.getCursor().drawer_ = null;
this.basicBlock = this.workspace.newBlock('basic_block');
});
teardown(function() {
workspaceTeardown.call(this, this.workspace);
});
test('Action does not exist', function() {
var block = this.workspace.getTopBlocks()[0];
var field = block.inputList[0].fieldRow[0];
var fieldSpy = sinon.spy(field, 'onBlocklyAction');
var mockEvent = createKeyDownEvent(Blockly.utils.KeyCodes.N, '');
var keyDownSpy =
sinon.spy(Blockly.ShortcutRegistry.registry, 'onKeyDown');
this.workspace.getCursor().setCurNode(Blockly.ASTNode.createFieldNode(field));
Blockly.onKeyDown(mockEvent);
chai.assert.isFalse(keyDownSpy.returned(true));
sinon.assert.notCalled(fieldSpy);
});
test('Action exists - field handles action', function() {
var block = this.workspace.getTopBlocks()[0];
var field = block.inputList[0].fieldRow[0];
var mockEvent = createKeyDownEvent(Blockly.utils.KeyCodes.A, '');
var fieldSpy = sinon.stub(field, 'onBlocklyAction').returns(true);
var keyDownSpy =
sinon.spy(Blockly.ShortcutRegistry.registry, 'onKeyDown');
this.workspace.getCursor().setCurNode(Blockly.ASTNode.createFieldNode(field));
Blockly.onKeyDown(mockEvent);
chai.assert.isTrue(keyDownSpy.returned(true));
sinon.assert.calledOnce(fieldSpy);
});
test('Action exists - field does not handle action', function() {
var block = this.workspace.getTopBlocks()[0];
var field = block.inputList[0].fieldRow[0];
var mockEvent = createKeyDownEvent(Blockly.utils.KeyCodes.A, '');
var fieldSpy = sinon.spy(field, 'onBlocklyAction');
var keyDownSpy =
sinon.spy(Blockly.ShortcutRegistry.registry, 'onKeyDown');
this.workspace.getCursor().setCurNode(Blockly.ASTNode.createFieldNode(field));
Blockly.onKeyDown(mockEvent);
chai.assert.isTrue(keyDownSpy.returned(true));
sinon.assert.calledOnce(fieldSpy);
});
test('Toggle Action Off', function() {
var mockEvent = createKeyDownEvent(
Blockly.utils.KeyCodes.K, '',
[Blockly.utils.KeyCodes.SHIFT, Blockly.utils.KeyCodes.CTRL]);
var keyDownSpy =
sinon.spy(Blockly.ShortcutRegistry.registry, 'onKeyDown');
this.workspace.keyboardAccessibilityMode = true;
Blockly.onKeyDown(mockEvent);
chai.assert.isTrue(keyDownSpy.returned(true));
chai.assert.isFalse(this.workspace.keyboardAccessibilityMode);
});
test('Toggle Action On', function() {
var mockEvent = createKeyDownEvent(
Blockly.utils.KeyCodes.K, '',
[Blockly.utils.KeyCodes.SHIFT, Blockly.utils.KeyCodes.CTRL]);
var keyDownSpy =
sinon.spy(Blockly.ShortcutRegistry.registry, 'onKeyDown');
this.workspace.keyboardAccessibilityMode = false;
Blockly.onKeyDown(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_dropdown",
"name": "NAME",
"options": [
[
"a",
"optionA"
]
]
},
{
"type": "input_value",
"name": "NAME"
}
],
"previousStatement": null,
"nextStatement": null,
"colour": 230,
"tooltip": "",
"helpUrl": ""
}]);
this.workspace = createNavigationWorkspace(true, true);
Blockly.mainWorkspace = this.workspace;
this.workspace.getCursor().drawer_ = null;
this.fieldBlock1 = this.workspace.newBlock('field_block');
});
teardown(function() {
workspaceTeardown.call(this, this.workspace);
});
test('Perform valid action for read only', function() {
var astNode = Blockly.ASTNode.createBlockNode(this.fieldBlock1);
var mockEvent = createKeyDownEvent(Blockly.utils.KeyCodes.S, '');
this.workspace.getCursor().setCurNode(astNode);
var keyDownSpy =
sinon.spy(Blockly.ShortcutRegistry.registry, 'onKeyDown');
Blockly.onKeyDown(mockEvent);
chai.assert.isTrue(keyDownSpy.returned(true));
});
test('Perform invalid action for read only', function() {
var astNode = Blockly.ASTNode.createBlockNode(this.fieldBlock1);
var mockEvent = createKeyDownEvent(Blockly.utils.KeyCodes.I, '');
this.workspace.getCursor().setCurNode(astNode);
var keyDownSpy =
sinon.spy(Blockly.ShortcutRegistry.registry, 'onKeyDown');
Blockly.onKeyDown(mockEvent);
chai.assert.isTrue(keyDownSpy.returned(false));
});
test('Try to perform action on a field', function() {
var field = this.fieldBlock1.inputList[0].fieldRow[0];
var astNode = Blockly.ASTNode.createFieldNode(field);
var mockEvent = createKeyDownEvent(Blockly.utils.KeyCodes.ENTER, '');
this.workspace.getCursor().setCurNode(astNode);
var keyDownSpy =
sinon.spy(Blockly.ShortcutRegistry.registry, 'onKeyDown');
Blockly.onKeyDown(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": "TEXT",
"text": "default"
}
],
"previousStatement": null,
"nextStatement": null,
}]);
this.workspace = createNavigationWorkspace(true);
var basicBlock = this.workspace.newBlock('basic_block');
var basicBlock2 = this.workspace.newBlock('basic_block');
this.basicBlock = basicBlock;
this.basicBlock2 = basicBlock2;
});
teardown(function() {
workspaceTeardown.call(this, this.workspace);
});
test('Insert from flyout with a valid connection marked', function() {
var previousConnection = this.basicBlock.previousConnection;
var prevNode = Blockly.ASTNode.createConnectionNode(previousConnection);
this.workspace.getMarker(Blockly.navigation.MARKER_NAME).setCurNode(prevNode);
Blockly.navigation.focusToolbox_(this.workspace);
Blockly.navigation.focusFlyout_(this.workspace);
Blockly.navigation.insertFromFlyout(this.workspace);
var insertedBlock = this.basicBlock.previousConnection.targetBlock();
chai.assert.isTrue(insertedBlock !== null);
chai.assert.equal(Blockly.navigation.currentState_,
Blockly.navigation.STATE_WS);
});
test('Insert Block from flyout without marking a connection', function() {
Blockly.navigation.focusToolbox_(this.workspace);
Blockly.navigation.focusFlyout_(this.workspace);
Blockly.navigation.insertFromFlyout(this.workspace);
var 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(Blockly.navigation.currentState_,
Blockly.navigation.STATE_WS);
});
test('Connect two blocks that are on the workspace', function() {
var targetNode = Blockly.ASTNode.createConnectionNode(this.basicBlock.previousConnection);
this.workspace.getMarker(Blockly.navigation.MARKER_NAME).setCurNode(targetNode);
var sourceNode = Blockly.ASTNode.createConnectionNode(this.basicBlock2.nextConnection);
this.workspace.getCursor().setCurNode(sourceNode);
Blockly.navigation.modify_();
var 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(true);
var basicBlock = this.workspace.newBlock('basic_block');
var basicBlock2 = this.workspace.newBlock('basic_block');
var basicBlock3 = this.workspace.newBlock('basic_block');
var basicBlock4 = this.workspace.newBlock('basic_block');
var inlineBlock1 = this.workspace.newBlock('inline_block');
var inlineBlock2 = this.workspace.newBlock('inline_block');
this.basicBlock = basicBlock;
this.basicBlock2 = basicBlock2;
this.basicBlock3 = basicBlock3;
this.basicBlock4 = basicBlock4;
this.inlineBlock1 = inlineBlock1;
this.inlineBlock2 = inlineBlock2;
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() {
workspaceTeardown.call(this, this.workspace);
});
test('Connect cursor on previous into stack', function() {
var markedLocation = this.basicBlock2.previousConnection;
var cursorLocation = this.basicBlock3.previousConnection;
Blockly.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() {
var markedLocation = this.basicBlock3.previousConnection;
var cursorLocation = this.basicBlock2.previousConnection;
Blockly.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() {
var markedLocation = this.basicBlock2.previousConnection;
var cursorLocation = this.basicBlock4.nextConnection;
Blockly.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() {
var markedLocation = this.basicBlock3.previousConnection;
var cursorLocation = this.basicBlock2.nextConnection;
Blockly.navigation.connect_(cursorLocation, markedLocation);
chai.assert.equal(this.basicBlock3.previousConnection.targetBlock(), this.basicBlock2);
});
test('Try to connect input that is descendant of output', function() {
var markedLocation = this.inlineBlock2.inputList[0].connection;
var cursorLocation = this.inlineBlock1.outputConnection;
Blockly.navigation.connect_(cursorLocation, markedLocation);
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(true);
this.basicBlockA = this.workspace.newBlock('basic_block');
this.basicBlockB = this.workspace.newBlock('basic_block');
});
teardown(function() {
workspaceTeardown.call(this, this.workspace);
});
test('Delete block - has parent ', function() {
this.basicBlockA.nextConnection.connect(this.basicBlockB.previousConnection);
var astNode = Blockly.ASTNode.createBlockNode(this.basicBlockB);
// Set the cursor to be on the child block
this.workspace.getCursor().setCurNode(astNode);
// Remove the child block
this.basicBlockB.dispose();
chai.assert.equal(this.workspace.getCursor().getCurNode().getType(),
Blockly.ASTNode.types.NEXT);
});
test('Delete block - no parent ', function() {
var astNode = Blockly.ASTNode.createBlockNode(this.basicBlockB);
this.workspace.getCursor().setCurNode(astNode);
this.basicBlockB.dispose();
chai.assert.equal(this.workspace.getCursor().getCurNode().getType(),
Blockly.ASTNode.types.WORKSPACE);
});
test('Delete parent block', function() {
this.basicBlockA.nextConnection.connect(this.basicBlockB.previousConnection);
var astNode = Blockly.ASTNode.createBlockNode(this.basicBlockB);
// Set the cursor to be on the child block
this.workspace.getCursor().setCurNode(astNode);
// Remove the parent block
this.basicBlockA.dispose();
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);
var astNode = Blockly.ASTNode.createStackNode(this.basicBlockA);
// Set the cursor to be on the stack
this.workspace.getCursor().setCurNode(astNode);
// Remove the top block in the stack
this.basicBlockA.dispose();
chai.assert.equal(this.workspace.getCursor().getCurNode().getType(),
Blockly.ASTNode.types.WORKSPACE);
});
});
});