diff --git a/core/keyboard_nav/block_navigation_policy.ts b/core/keyboard_nav/block_navigation_policy.ts index b073253f98e..5adbe8d45b4 100644 --- a/core/keyboard_nav/block_navigation_policy.ts +++ b/core/keyboard_nav/block_navigation_policy.ts @@ -4,10 +4,11 @@ * SPDX-License-Identifier: Apache-2.0 */ -import type {BlockSvg} from '../block_svg.js'; +import {BlockSvg} from '../block_svg.js'; +import type {Field} from '../field.js'; import type {INavigable} from '../interfaces/i_navigable.js'; import type {INavigationPolicy} from '../interfaces/i_navigation_policy.js'; -import type {RenderedConnection} from '../rendered_connection.js'; +import {WorkspaceSvg} from '../workspace_svg.js'; /** * Set of rules controlling keyboard navigation from a block. @@ -24,7 +25,8 @@ export class BlockNavigationPolicy implements INavigationPolicy { for (const field of input.fieldRow) { return field; } - if (input.connection) return input.connection as RenderedConnection; + if (input.connection?.targetBlock()) + return input.connection.targetBlock() as BlockSvg; } return null; @@ -38,12 +40,14 @@ export class BlockNavigationPolicy implements INavigationPolicy { * which it is attached. */ getParent(current: BlockSvg): INavigable | null { - const topBlock = current.getTopStackBlock(); + if (current.previousConnection?.targetBlock()) { + const surroundParent = current.getSurroundParent(); + if (surroundParent) return surroundParent; + } else if (current.outputConnection?.targetBlock()) { + return current.outputConnection.targetBlock(); + } - return ( - (this.getParentConnection(topBlock)?.targetConnection?.getParentInput() - ?.connection as RenderedConnection) ?? topBlock - ); + return current.workspace; } /** @@ -54,21 +58,40 @@ export class BlockNavigationPolicy implements INavigationPolicy { * block, or its next connection. */ getNextSibling(current: BlockSvg): INavigable | null { - const nextConnection = current.nextConnection; - if (!current.outputConnection?.targetConnection && !nextConnection) { - // If this block has no connected output connection and no next - // connection, it must be the last block in the stack, so its next sibling - // is the first block of the next stack on the workspace. - const topBlocks = current.workspace.getTopBlocks(true); - let targetIndex = topBlocks.indexOf(current.getRootBlock()) + 1; - if (targetIndex >= topBlocks.length) { - targetIndex = 0; + if (current.nextConnection?.targetBlock()) { + return current.nextConnection?.targetBlock(); + } + + const parent = this.getParent(current); + let navigatingCrossStacks = false; + let siblings: (BlockSvg | Field)[] = []; + if (parent instanceof BlockSvg) { + for (let i = 0, input; (input = parent.inputList[i]); i++) { + if (input.connection) { + siblings.push(...input.fieldRow); + const child = input.connection.targetBlock(); + if (child) { + siblings.push(child as BlockSvg); + } + } } - const previousBlock = topBlocks[targetIndex]; - return this.getParentConnection(previousBlock) ?? previousBlock; + } else if (parent instanceof WorkspaceSvg) { + siblings = parent.getTopBlocks(true); + navigatingCrossStacks = true; + } else { + return null; } - return nextConnection; + const currentIndex = siblings.indexOf( + navigatingCrossStacks ? current.getRootBlock() : current, + ); + if (currentIndex >= 0 && currentIndex < siblings.length - 1) { + return siblings[currentIndex + 1]; + } else if (currentIndex === siblings.length - 1 && navigatingCrossStacks) { + return siblings[0]; + } + + return null; } /** @@ -79,39 +102,44 @@ export class BlockNavigationPolicy implements INavigationPolicy { * connection/block of the previous block stack if it is a root block. */ getPreviousSibling(current: BlockSvg): INavigable | null { - const parentConnection = this.getParentConnection(current); - if (parentConnection) return parentConnection; - - // If this block has no output/previous connection, it must be a root block, - // so its previous sibling is the last connection of the last block of the - // previous stack on the workspace. - const topBlocks = current.workspace.getTopBlocks(true); - let targetIndex = topBlocks.indexOf(current.getRootBlock()) - 1; - if (targetIndex < 0) { - targetIndex = topBlocks.length - 1; + if (current.previousConnection?.targetBlock()) { + return current.previousConnection?.targetBlock(); } - const lastBlock = topBlocks[targetIndex] - .getDescendants(true) - .reverse() - .pop(); + const parent = this.getParent(current); + let navigatingCrossStacks = false; + let siblings: (BlockSvg | Field)[] = []; + if (parent instanceof BlockSvg) { + for (let i = 0, input; (input = parent.inputList[i]); i++) { + if (input.connection) { + siblings.push(...input.fieldRow); + const child = input.connection.targetBlock(); + if (child) { + siblings.push(child as BlockSvg); + } + } + } + } else if (parent instanceof WorkspaceSvg) { + siblings = parent.getTopBlocks(true); + navigatingCrossStacks = true; + } else { + return null; + } - return lastBlock?.nextConnection ?? lastBlock ?? null; - } + const currentIndex = siblings.indexOf(current); + let result: INavigable | null = null; + if (currentIndex >= 1) { + result = siblings[currentIndex - 1]; + } else if (currentIndex === 0 && navigatingCrossStacks) { + result = siblings[siblings.length - 1]; + } - /** - * Gets the parent connection on a block. - * This is either an output connection, previous connection or undefined. - * If both connections exist return the one that is actually connected - * to another block. - * - * @param block The block to find the parent connection on. - * @returns The connection connecting to the parent of the block. - */ - protected getParentConnection(block: BlockSvg) { - if (!block.outputConnection || block.previousConnection?.isConnected()) { - return block.previousConnection; + // If navigating to a previous stack, our previous sibling is the last + // block in it. + if (navigatingCrossStacks && result instanceof BlockSvg) { + return result.lastConnectionInStack(false)?.getSourceBlock() ?? result; } - return block.outputConnection; + + return result; } } diff --git a/core/keyboard_nav/field_navigation_policy.ts b/core/keyboard_nav/field_navigation_policy.ts index a0e43001e4f..c819f204198 100644 --- a/core/keyboard_nav/field_navigation_policy.ts +++ b/core/keyboard_nav/field_navigation_policy.ts @@ -8,7 +8,6 @@ import type {BlockSvg} from '../block_svg.js'; import type {Field} from '../field.js'; import type {INavigable} from '../interfaces/i_navigable.js'; import type {INavigationPolicy} from '../interfaces/i_navigation_policy.js'; -import type {RenderedConnection} from '../rendered_connection.js'; /** * Set of rules controlling keyboard navigation from a field. @@ -52,8 +51,8 @@ export class FieldNavigationPolicy implements INavigationPolicy> { const fieldRow = newInput.fieldRow; if (fieldIdx < fieldRow.length) return fieldRow[fieldIdx]; fieldIdx = 0; - if (newInput.connection) { - return newInput.connection as RenderedConnection; + if (newInput.connection?.targetBlock()) { + return newInput.connection.targetBlock() as BlockSvg; } } return null; @@ -74,8 +73,8 @@ export class FieldNavigationPolicy implements INavigationPolicy> { let fieldIdx = parentInput.fieldRow.indexOf(current) - 1; for (let i = curIdx; i >= 0; i--) { const input = block.inputList[i]; - if (input.connection && input !== parentInput) { - return input.connection as RenderedConnection; + if (input.connection?.targetBlock() && input !== parentInput) { + return input.connection.targetBlock() as BlockSvg; } const fieldRow = input.fieldRow; if (fieldIdx > -1) return fieldRow[fieldIdx]; diff --git a/core/keyboard_nav/line_cursor.ts b/core/keyboard_nav/line_cursor.ts index 1dcfdd8a295..845fab9519c 100644 --- a/core/keyboard_nav/line_cursor.ts +++ b/core/keyboard_nav/line_cursor.ts @@ -14,8 +14,6 @@ */ import {BlockSvg} from '../block_svg.js'; -import {ConnectionType} from '../connection_type.js'; -import {Field} from '../field.js'; import {FieldCheckbox} from '../field_checkbox.js'; import {FieldDropdown} from '../field_dropdown.js'; import {FieldImage} from '../field_image.js'; @@ -146,7 +144,12 @@ export class LineCursor extends Marker { } const newNode = this.getNextNode( curNode, - this.workspace.isFlyout ? () => true : this.validLineNode.bind(this), + (candidate: INavigable | null) => { + return ( + candidate instanceof BlockSvg && + !candidate.outputConnection?.targetBlock() + ); + }, true, ); @@ -168,11 +171,8 @@ export class LineCursor extends Marker { if (!curNode) { return null; } - const newNode = this.getNextNode( - curNode, - this.workspace.isFlyout ? () => true : this.validInLineNode.bind(this), - true, - ); + + const newNode = this.getNextNode(curNode, () => true, true); if (newNode) { this.setCurNode(newNode); @@ -193,7 +193,12 @@ export class LineCursor extends Marker { } const newNode = this.getPreviousNode( curNode, - this.workspace.isFlyout ? () => true : this.validLineNode.bind(this), + (candidate: INavigable | null) => { + return ( + candidate instanceof BlockSvg && + !candidate.outputConnection?.targetBlock() + ); + }, true, ); @@ -215,11 +220,8 @@ export class LineCursor extends Marker { if (!curNode) { return null; } - const newNode = this.getPreviousNode( - curNode, - this.workspace.isFlyout ? () => true : this.validInLineNode.bind(this), - true, - ); + + const newNode = this.getPreviousNode(curNode, () => true, true); if (newNode) { this.setCurNode(newNode); @@ -229,102 +231,26 @@ export class LineCursor extends Marker { /** * Returns true iff the node to which we would navigate if in() were - * called, which will be a validInLineNode, is also a validLineNode - * - in effect, if the LineCursor is at the end of the 'current + * called is the same as the node to which we would navigate if next() were + * called - in effect, if the LineCursor is at the end of the 'current * line' of the program. */ atEndOfLine(): boolean { const curNode = this.getCurNode(); if (!curNode) return false; - const rightNode = this.getNextNode( + const inNode = this.getNextNode(curNode, () => true, true); + const nextNode = this.getNextNode( curNode, - this.validInLineNode.bind(this), - false, + (candidate: INavigable | null) => { + return ( + candidate instanceof BlockSvg && + !candidate.outputConnection?.targetBlock() + ); + }, + true, ); - return this.validLineNode(rightNode); - } - /** - * Returns true iff the given node represents the "beginning of a - * new line of code" (and thus can be visited by pressing the - * up/down arrow keys). Specifically, if the node is for: - * - * - Any block that is not a value block. - * - A top-level value block (one that is unconnected). - * - An unconnected next statement input. - * - An unconnected 'next' connection - the "blank line at the end". - * This is to facilitate connecting additional blocks to a - * stack/substack. - * - * If options.stackConnections is true (the default) then allow the - * cursor to visit all (useful) stack connection by additionally - * returning true for: - * - * - Any next statement input - * - Any 'next' connection. - * - An unconnected previous statement input. - * - * @param node The AST node to check. - * @returns True if the node should be visited, false otherwise. - */ - protected validLineNode(node: INavigable | null): boolean { - if (!node) return false; - - if (node instanceof BlockSvg) { - return !node.outputConnection?.isConnected(); - } else if (node instanceof RenderedConnection) { - if (node.type === ConnectionType.NEXT_STATEMENT) { - return this.options.stackConnections || !node.isConnected(); - } else if (node.type === ConnectionType.PREVIOUS_STATEMENT) { - return this.options.stackConnections && !node.isConnected(); - } - } - - return false; - } - - /** - * Returns true iff the given node can be visited by the cursor when - * using the left/right arrow keys. Specifically, if the node is - * any node for which validLineNode would return true, plus: - * - * - Any block. - * - Any field that is not a full block field. - * - Any unconnected next or input connection. This is to - * facilitate connecting additional blocks. - * - * @param node The AST node to check whether it is valid. - * @returns True if the node should be visited, false otherwise. - */ - protected validInLineNode(node: INavigable | null): boolean { - if (!node) return false; - if (this.validLineNode(node)) return true; - if (node instanceof BlockSvg || node instanceof Field) { - return true; - } else if ( - node instanceof RenderedConnection && - node.getParentInput() && - (node.type === ConnectionType.INPUT_VALUE || - node.type === ConnectionType.NEXT_STATEMENT) - ) { - return !node.isConnected(); - } - - return false; - } - - /** - * Returns true iff the given node can be visited by the cursor. - * Specifically, if the node is any for which validInLineNode would - * return true, or if it is a workspace node. - * - * @param node The AST node to check whether it is valid. - * @returns True if the node should be visited, false otherwise. - */ - protected validNode(node: INavigable | null): boolean { - return ( - !!node && (this.validInLineNode(node) || node instanceof WorkspaceSvg) - ); + return inNode === nextNode; } /** @@ -347,15 +273,15 @@ export class LineCursor extends Marker { let newNode = this.workspace.getNavigator().getFirstChild(node) || this.workspace.getNavigator().getNextSibling(node); - if (isValid(newNode)) return newNode; - if (newNode) { - visitedNodes.add(node); - return this.getNextNodeImpl(newNode, isValid, visitedNodes); + + let target = node; + while (target && !newNode) { + const parent = this.workspace.getNavigator().getParent(target); + if (!parent) break; + newNode = this.workspace.getNavigator().getNextSibling(parent); + target = parent; } - newNode = this.findSiblingOrParentSibling( - this.workspace.getNavigator().getParent(node), - ); if (isValid(newNode)) return newNode; if (newNode) { visitedNodes.add(node); @@ -402,15 +328,12 @@ export class LineCursor extends Marker { visitedNodes: Set> = new Set>(), ): INavigable | null { if (!node || visitedNodes.has(node)) return null; - let newNode: INavigable | null = this.workspace - .getNavigator() - .getPreviousSibling(node); - if (newNode) { - newNode = this.getRightMostChild(newNode); - } else { - newNode = this.workspace.getNavigator().getParent(node); - } + const newNode = + this.getRightMostChild( + this.workspace.getNavigator().getPreviousSibling(node), + node, + ) || this.workspace.getNavigator().getParent(node); if (isValid(newNode)) return newNode; if (newNode) { @@ -441,24 +364,6 @@ export class LineCursor extends Marker { return this.getPreviousNodeImpl(node, isValid); } - /** - * From the given node find either the next valid sibling or the parent's - * next sibling. - * - * @param node The current position in the AST. - * @returns The next sibling node, the parent's next sibling, or null. - */ - private findSiblingOrParentSibling( - node: INavigable | null, - ): INavigable | null { - if (!node) return null; - const nextNode = this.workspace.getNavigator().getNextSibling(node); - if (nextNode) return nextNode; - return this.findSiblingOrParentSibling( - this.workspace.getNavigator().getParent(node), - ); - } - /** * Get the right most child of a node. * @@ -466,17 +371,22 @@ export class LineCursor extends Marker { * @returns The right most child of the given node, or the node if no child * exists. */ - private getRightMostChild(node: INavigable): INavigable | null { + getRightMostChild( + node: INavigable | null, + stopIfFound: INavigable, + ): INavigable | null { + if (!node) return node; let newNode = this.workspace.getNavigator().getFirstChild(node); - if (!newNode) return node; + if (!newNode || newNode === stopIfFound) return node; for ( let nextNode: INavigable | null = newNode; nextNode; nextNode = this.workspace.getNavigator().getNextSibling(newNode) ) { + if (nextNode === stopIfFound) break; newNode = nextNode; } - return this.getRightMostChild(newNode); + return this.getRightMostChild(newNode, stopIfFound); } /** @@ -537,10 +447,7 @@ export class LineCursor extends Marker { this.potentialNodes = null; if (!nodes) throw new Error('must call preDelete first'); for (const node of nodes) { - if ( - this.validNode(node) && - !this.getSourceBlockFromNode(node)?.disposed - ) { + if (!this.getSourceBlockFromNode(node)?.disposed) { this.setCurNode(node); return; } diff --git a/core/keyboard_nav/workspace_navigation_policy.ts b/core/keyboard_nav/workspace_navigation_policy.ts index 91f3221b558..92b31204eb8 100644 --- a/core/keyboard_nav/workspace_navigation_policy.ts +++ b/core/keyboard_nav/workspace_navigation_policy.ts @@ -22,16 +22,7 @@ export class WorkspaceNavigationPolicy */ getFirstChild(current: WorkspaceSvg): INavigable | null { const blocks = current.getTopBlocks(true); - if (!blocks.length) return null; - const block = blocks[0]; - let topConnection = block.outputConnection; - if ( - !topConnection || - (block.previousConnection && block.previousConnection.isConnected()) - ) { - topConnection = block.previousConnection; - } - return topConnection ?? block; + return blocks.length ? blocks[0] : null; } /** diff --git a/tests/mocha/cursor_test.js b/tests/mocha/cursor_test.js index ded07a5617f..aa4f5618495 100644 --- a/tests/mocha/cursor_test.js +++ b/tests/mocha/cursor_test.js @@ -97,34 +97,34 @@ suite('Cursor', function () { this.cursor.setCurNode(prevNode); this.cursor.next(); const curNode = this.cursor.getCurNode(); - assert.equal(curNode, this.blocks.B.getInput('NAME4').connection); + assert.equal(curNode, this.blocks.C); }); - test('In - From attached input connection', function () { + test('In - From field to attached input connection', function () { const fieldBlock = this.blocks.E; - const inputConnectionNode = this.blocks.A.inputList[0].connection; - this.cursor.setCurNode(inputConnectionNode); + const fieldNode = this.blocks.A.getField('NAME2'); + this.cursor.setCurNode(fieldNode); this.cursor.in(); const curNode = this.cursor.getCurNode(); assert.equal(curNode, fieldBlock); }); - test('Prev - From previous connection does not skip over next connection', function () { + test('Prev - From previous connection does skip over next connection', function () { const prevConnection = this.blocks.B.previousConnection; const prevConnectionNode = prevConnection; this.cursor.setCurNode(prevConnectionNode); this.cursor.prev(); const curNode = this.cursor.getCurNode(); - assert.equal(curNode, this.blocks.A.nextConnection); + assert.equal(curNode, this.blocks.A); }); - test('Prev - From first connection loop to last next connection', function () { - const prevConnection = this.blocks.A.previousConnection; + test('Prev - From first block loop to last block', function () { + const prevConnection = this.blocks.A; const prevConnectionNode = prevConnection; this.cursor.setCurNode(prevConnectionNode); this.cursor.prev(); const curNode = this.cursor.getCurNode(); - assert.equal(curNode, this.blocks.D.nextConnection); + assert.equal(curNode, this.blocks.D); }); test('Out - From field does not skip over block node', function () { @@ -225,11 +225,11 @@ suite('Cursor', function () { }); test('getFirstNode', function () { const node = this.cursor.getFirstNode(); - assert.equal(node, this.blockA.previousConnection); + assert.equal(node, this.blockA); }); test('getLastNode', function () { const node = this.cursor.getLastNode(); - assert.equal(node, this.blockA.nextConnection); + assert.equal(node, this.blockA); }); }); @@ -242,11 +242,11 @@ suite('Cursor', function () { }); test('getFirstNode', function () { const node = this.cursor.getFirstNode(); - assert.equal(node, this.blockA.outputConnection); + assert.equal(node, this.blockA); }); test('getLastNode', function () { const node = this.cursor.getLastNode(); - assert.equal(node, this.blockA.inputList[0].connection); + assert.equal(node, this.blockA); }); }); suite('one c-hat block', function () { @@ -262,7 +262,7 @@ suite('Cursor', function () { }); test('getLastNode', function () { const node = this.cursor.getLastNode(); - assert.equal(node, this.blockA.inputList[0].connection); + assert.equal(node, this.blockA); }); }); @@ -295,12 +295,12 @@ suite('Cursor', function () { test('getFirstNode', function () { const node = this.cursor.getFirstNode(); const blockA = this.workspace.getBlockById('A'); - assert.equal(node, blockA.previousConnection); + assert.equal(node, blockA); }); test('getLastNode', function () { const node = this.cursor.getLastNode(); const blockB = this.workspace.getBlockById('B'); - assert.equal(node, blockB.nextConnection); + assert.equal(node, blockB); }); }); @@ -335,12 +335,12 @@ suite('Cursor', function () { test('getFirstNode', function () { const node = this.cursor.getFirstNode(); const blockA = this.workspace.getBlockById('A'); - assert.equal(node, blockA.outputConnection); + assert.equal(node, blockA); }); test('getLastNode', function () { const node = this.cursor.getLastNode(); const blockB = this.workspace.getBlockById('B'); - assert.equal(node, blockB.inputList[0].connection); + assert.equal(node, blockB); }); }); @@ -385,15 +385,14 @@ suite('Cursor', function () { test('getFirstNode', function () { const node = this.cursor.getFirstNode(); const location = node; - const previousConnection = - this.workspace.getBlockById('A').previousConnection; - assert.equal(location, previousConnection); + const blockA = this.workspace.getBlockById('A'); + assert.equal(location, blockA); }); test('getLastNode', function () { const node = this.cursor.getLastNode(); const location = node; - const nextConnection = this.workspace.getBlockById('D').nextConnection; - assert.equal(location, nextConnection); + const blockD = this.workspace.getBlockById('D'); + assert.equal(location, blockD); }); }); }); @@ -439,8 +438,8 @@ suite('Cursor', function () { this.cursor = this.workspace.getCursor(); this.neverValid = () => false; this.alwaysValid = () => true; - this.isConnection = (node) => { - return node && node instanceof Blockly.RenderedConnection; + this.isBlock = (node) => { + return node && node instanceof Blockly.BlockSvg; }; }); teardown(function () { @@ -528,7 +527,7 @@ suite('Cursor', function () { assert.equal(nextNode, this.blockB.getField('FIELD')); }); test('Always valid - start at end', function () { - const startNode = this.blockC.nextConnection; + const startNode = this.blockC.getField('FIELD'); const nextNode = this.cursor.getNextNode( startNode, this.alwaysValid, @@ -537,29 +536,29 @@ suite('Cursor', function () { assert.isNull(nextNode); }); - test('Valid if connection - start at top', function () { - const startNode = this.blockA.previousConnection; + test('Valid if block - start at top', function () { + const startNode = this.blockA; const nextNode = this.cursor.getNextNode( startNode, - this.isConnection, + this.isBlock, false, ); - assert.equal(nextNode, this.blockA.nextConnection); + assert.equal(nextNode, this.blockB); }); - test('Valid if connection - start in middle', function () { + test('Valid if block - start in middle', function () { const startNode = this.blockB; const nextNode = this.cursor.getNextNode( startNode, - this.isConnection, + this.isBlock, false, ); - assert.equal(nextNode, this.blockB.nextConnection); + assert.equal(nextNode, this.blockC); }); - test('Valid if connection - start at end', function () { - const startNode = this.blockC.nextConnection; + test('Valid if block - start at end', function () { + const startNode = this.blockC.getField('FIELD'); const nextNode = this.cursor.getNextNode( startNode, - this.isConnection, + this.isBlock, false, ); assert.isNull(nextNode); @@ -583,14 +582,10 @@ suite('Cursor', function () { assert.equal(nextNode, this.blockA.previousConnection); }); - test('Valid if connection - start at end - with loopback', function () { - const startNode = this.blockC.nextConnection; - const nextNode = this.cursor.getNextNode( - startNode, - this.isConnection, - true, - ); - assert.equal(nextNode, this.blockA.previousConnection); + test('Valid if block - start at end - with loopback', function () { + const startNode = this.blockC; + const nextNode = this.cursor.getNextNode(startNode, this.isBlock, true); + assert.equal(nextNode, this.blockA); }); }); }); @@ -637,8 +632,8 @@ suite('Cursor', function () { this.cursor = this.workspace.getCursor(); this.neverValid = () => false; this.alwaysValid = () => true; - this.isConnection = (node) => { - return node && node instanceof Blockly.RenderedConnection; + this.isBlock = (node) => { + return node && node instanceof Blockly.BlockSvg; }; }); teardown(function () { @@ -708,7 +703,7 @@ suite('Cursor', function () { }); test('Always valid - start at top', function () { - const startNode = this.blockA.previousConnection; + const startNode = this.blockA; const previousNode = this.cursor.getPreviousNode( startNode, this.alwaysValid, @@ -723,7 +718,7 @@ suite('Cursor', function () { this.alwaysValid, false, ); - assert.equal(previousNode, this.blockB.previousConnection); + assert.equal(previousNode, this.blockA.getField('FIELD')); }); test('Always valid - start at end', function () { const startNode = this.blockC.nextConnection; @@ -735,32 +730,32 @@ suite('Cursor', function () { assert.equal(previousNode, this.blockC.getField('FIELD')); }); - test('Valid if connection - start at top', function () { - const startNode = this.blockA.previousConnection; + test('Valid if block - start at top', function () { + const startNode = this.blockA; const previousNode = this.cursor.getPreviousNode( startNode, - this.isConnection, + this.isBlock, false, ); assert.isNull(previousNode); }); - test('Valid if connection - start in middle', function () { + test('Valid if block - start in middle', function () { const startNode = this.blockB; const previousNode = this.cursor.getPreviousNode( startNode, - this.isConnection, + this.isBlock, false, ); - assert.equal(previousNode, this.blockB.previousConnection); + assert.equal(previousNode, this.blockA); }); - test('Valid if connection - start at end', function () { - const startNode = this.blockC.nextConnection; + test('Valid if block - start at end', function () { + const startNode = this.blockC; const previousNode = this.cursor.getPreviousNode( startNode, - this.isConnection, + this.isBlock, false, ); - assert.equal(previousNode, this.blockC.previousConnection); + assert.equal(previousNode, this.blockB); }); test('Never valid - start at top - with loopback', function () { const startNode = this.blockA.previousConnection; @@ -780,14 +775,14 @@ suite('Cursor', function () { ); assert.equal(previousNode, this.blockC.nextConnection); }); - test('Valid if connection - start at top - with loopback', function () { - const startNode = this.blockA.previousConnection; + test('Valid if block - start at top - with loopback', function () { + const startNode = this.blockA; const previousNode = this.cursor.getPreviousNode( startNode, - this.isConnection, + this.isBlock, true, ); - assert.equal(previousNode, this.blockC.nextConnection); + assert.equal(previousNode, this.blockC); }); }); }); diff --git a/tests/mocha/navigation_test.js b/tests/mocha/navigation_test.js index b049170b079..0462d4daa0d 100644 --- a/tests/mocha/navigation_test.js +++ b/tests/mocha/navigation_test.js @@ -166,7 +166,7 @@ suite('Navigation', function () { }, { 'type': 'fields_and_input2', - 'message0': '%1 %2 %3 hi %4 bye', + 'message0': '%1 %2 %3 %4 bye', 'args0': [ { 'type': 'input_value', @@ -245,6 +245,7 @@ suite('Navigation', function () { const outputNextBlock = this.workspace.newBlock('output_next'); this.blocks.secondBlock = secondBlock; this.blocks.outputNextBlock = outputNextBlock; + this.workspace.cleanUp(); }); suite('Next', function () { setup(function () { @@ -261,12 +262,11 @@ suite('Navigation', function () { const nextNode = this.navigator.getNextSibling(prevConnection); assert.equal(nextNode, this.blocks.statementInput1); }); - test('fromBlockToNext', function () { - const nextConnection = this.blocks.statementInput1.nextConnection; + test('fromBlockToNextBlock', function () { const nextNode = this.navigator.getNextSibling( this.blocks.statementInput1, ); - assert.equal(nextNode, nextConnection); + assert.equal(nextNode, this.blocks.statementInput2); }); test('fromNextToPrevious', function () { const nextConnection = this.blocks.statementInput1.nextConnection; @@ -304,12 +304,12 @@ suite('Navigation', function () { const nextNode = this.navigator.getNextSibling(output); assert.equal(nextNode, this.blocks.fieldWithOutput); }); - test('fromFieldToInput', function () { + test('fromFieldToNestedBlock', function () { const field = this.blocks.statementInput1.inputList[0].fieldRow[1]; const inputConnection = this.blocks.statementInput1.inputList[0].connection; const nextNode = this.navigator.getNextSibling(field); - assert.equal(nextNode, inputConnection); + assert.equal(nextNode, this.blocks.fieldWithOutput); }); test('fromFieldToField', function () { const field = this.blocks.fieldAndInputs.inputList[0].fieldRow[0]; @@ -338,17 +338,17 @@ suite('Navigation', function () { }); test('fromBlockToPrevious', function () { const prevNode = this.navigator.getPreviousSibling( - this.blocks.statementInput1, + this.blocks.statementInput2, ); - const prevConnection = this.blocks.statementInput1.previousConnection; - assert.equal(prevNode, prevConnection); + const previousBlock = this.blocks.statementInput1; + assert.equal(prevNode, previousBlock); }); - test('fromBlockToOutput', function () { + test('fromOutputBlockToPreviousField', function () { const prevNode = this.navigator.getPreviousSibling( this.blocks.fieldWithOutput, ); const outputConnection = this.blocks.fieldWithOutput.outputConnection; - assert.equal(prevNode, outputConnection); + assert.equal(prevNode, [...this.blocks.statementInput1.getFields()][1]); }); test('fromNextToBlock', function () { const nextConnection = this.blocks.statementInput1.nextConnection; @@ -383,11 +383,16 @@ suite('Navigation', function () { assert.isNull(prevNode); }); test('fromFieldToInput', function () { + const outputBlock = this.workspace.newBlock('field_input'); + this.blocks.fieldAndInputs2.inputList[0].connection.connect( + outputBlock.outputConnection, + ); + const field = this.blocks.fieldAndInputs2.inputList[1].fieldRow[0]; const inputConnection = this.blocks.fieldAndInputs2.inputList[0].connection; const prevNode = this.navigator.getPreviousSibling(field); - assert.equal(prevNode, inputConnection); + assert.equal(prevNode, outputBlock); }); test('fromFieldToField', function () { const field = this.blocks.fieldAndInputs.inputList[1].fieldRow[0]; @@ -423,10 +428,10 @@ suite('Navigation', function () { const inNode = this.navigator.getFirstChild(input.connection); assert.equal(inNode, previousConnection); }); - test('fromBlockToInput', function () { - const input = this.blocks.valueInput.inputList[0]; + test('fromBlockToField', function () { + const field = this.blocks.valueInput.getField('NAME'); const inNode = this.navigator.getFirstChild(this.blocks.valueInput); - assert.equal(inNode, input.connection); + assert.equal(inNode, field); }); test('fromBlockToField', function () { const inNode = this.navigator.getFirstChild( @@ -440,12 +445,10 @@ suite('Navigation', function () { assert.isNull(inNode); }); test('fromBlockToInput_DummyInputValue', function () { - const inputConnection = - this.blocks.dummyInputValue.inputList[1].connection; const inNode = this.navigator.getFirstChild( this.blocks.dummyInputValue, ); - assert.equal(inNode, inputConnection); + assert.equal(inNode, null); }); test('fromOuputToNull', function () { const output = this.blocks.fieldWithOutput.outputConnection; @@ -540,25 +543,25 @@ suite('Navigation', function () { const outNode = this.navigator.getParent(next); assert.equal(outNode, this.blocks.secondBlock.inputList[0].connection); }); - test('fromBlockToStack', function () { + test('fromBlockToWorkspace', function () { const outNode = this.navigator.getParent(this.blocks.statementInput2); - assert.equal(outNode, this.blocks.statementInput1); + assert.equal(outNode, this.workspace); }); - test('fromBlockToInput', function () { - const input = this.blocks.statementInput2.inputList[1].connection; + test('fromBlockToEnclosingStatement', function () { + const enclosingStatement = this.blocks.statementInput2; const outNode = this.navigator.getParent(this.blocks.statementInput3); - assert.equal(outNode, input); + assert.equal(outNode, enclosingStatement); }); - test('fromTopBlockToStack', function () { + test('fromTopBlockToWorkspace', function () { const outNode = this.navigator.getParent(this.blocks.statementInput1); - assert.equal(outNode, this.blocks.statementInput1); + assert.equal(outNode, this.workspace); }); - test('fromBlockToStack_OutputConnection', function () { + test('fromOutputBlockToWorkspace', function () { const outNode = this.navigator.getParent(this.blocks.fieldWithOutput2); - assert.equal(outNode, this.blocks.fieldWithOutput2); + assert.equal(outNode, this.workspace); }); - test('fromBlockToInput_OutputConnection', function () { - const inputConnection = this.blocks.secondBlock.inputList[0].connection; + test('fromOutputNextBlockToWorkspace', function () { + const inputConnection = this.blocks.secondBlock; const outNode = this.navigator.getParent(this.blocks.outputNextBlock); assert.equal(outNode, inputConnection); });