mirror of
https://github.com/google/blockly.git
synced 2026-01-06 08:30:13 +01:00
fix: Improve narration and navigation of C-shaped blocks. (#9416)
* fix: Improve narration and navigation of C-shaped blocks. * chore: Satisfy the linter. * chore: Refactor and comment `getBlockNavigationCandidates()`. * refactor: Reduce code duplication in `LineCursor`. * fix: Add missing case when labeling connections.
This commit is contained in:
@@ -39,6 +39,7 @@ import {IconType} from './icons/icon_types.js';
|
|||||||
import {MutatorIcon} from './icons/mutator_icon.js';
|
import {MutatorIcon} from './icons/mutator_icon.js';
|
||||||
import {WarningIcon} from './icons/warning_icon.js';
|
import {WarningIcon} from './icons/warning_icon.js';
|
||||||
import type {Input} from './inputs/input.js';
|
import type {Input} from './inputs/input.js';
|
||||||
|
import {inputTypes} from './inputs/input_types.js';
|
||||||
import type {IBoundedElement} from './interfaces/i_bounded_element.js';
|
import type {IBoundedElement} from './interfaces/i_bounded_element.js';
|
||||||
import {IContextMenu} from './interfaces/i_contextmenu.js';
|
import {IContextMenu} from './interfaces/i_contextmenu.js';
|
||||||
import type {ICopyable} from './interfaces/i_copyable.js';
|
import type {ICopyable} from './interfaces/i_copyable.js';
|
||||||
@@ -267,6 +268,20 @@ export class BlockSvg
|
|||||||
blockTypeText = 'C-shaped block';
|
blockTypeText = 'C-shaped block';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let prefix = '';
|
||||||
|
const parentInput = (
|
||||||
|
this.previousConnection ?? this.outputConnection
|
||||||
|
)?.targetConnection?.getParentInput();
|
||||||
|
if (parentInput && parentInput.type === inputTypes.STATEMENT) {
|
||||||
|
prefix = `Begin ${parentInput.getFieldRowLabel()}, `;
|
||||||
|
} else if (
|
||||||
|
parentInput &&
|
||||||
|
parentInput.type === inputTypes.VALUE &&
|
||||||
|
this.getParent()?.statementInputCount
|
||||||
|
) {
|
||||||
|
prefix = `${parentInput.getFieldRowLabel()} `;
|
||||||
|
}
|
||||||
|
|
||||||
let additionalInfo = blockTypeText;
|
let additionalInfo = blockTypeText;
|
||||||
if (inputSummary && !nestedStatementBlockCount) {
|
if (inputSummary && !nestedStatementBlockCount) {
|
||||||
additionalInfo = `${additionalInfo} with ${inputSummary}`;
|
additionalInfo = `${additionalInfo} with ${inputSummary}`;
|
||||||
@@ -279,7 +294,7 @@ export class BlockSvg
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return blockSummary + ', ' + additionalInfo;
|
return prefix + blockSummary + ', ' + additionalInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
private computeAriaRole() {
|
private computeAriaRole() {
|
||||||
|
|||||||
@@ -303,6 +303,21 @@ export class Input {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a label for this input's row on its parent block.
|
||||||
|
*
|
||||||
|
* Generally this consists of the labels/values of the preceding fields, and
|
||||||
|
* is intended for accessibility descriptions.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
* @returns A description of this input's row on its parent block.
|
||||||
|
*/
|
||||||
|
getFieldRowLabel() {
|
||||||
|
return this.fieldRow.reduce((label, field) => {
|
||||||
|
return `${label} ${field.EDITABLE ? field.getAriaName() : field.getValue()}`;
|
||||||
|
}, '');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a connection based on the type of this input's source block.
|
* Constructs a connection based on the type of this input's source block.
|
||||||
* Properly handles constructing headless connections for headless blocks
|
* Properly handles constructing headless connections for headless blocks
|
||||||
|
|||||||
@@ -124,24 +124,48 @@ function getBlockNavigationCandidates(
|
|||||||
|
|
||||||
for (const input of block.inputList) {
|
for (const input of block.inputList) {
|
||||||
if (!input.isVisible()) continue;
|
if (!input.isVisible()) continue;
|
||||||
|
|
||||||
candidates.push(...input.fieldRow);
|
candidates.push(...input.fieldRow);
|
||||||
if (input.connection?.targetBlock()) {
|
|
||||||
const connectedBlock = input.connection.targetBlock() as BlockSvg;
|
const connection = input.connection as RenderedConnection | null;
|
||||||
if (input.connection.type === ConnectionType.NEXT_STATEMENT && !forward) {
|
if (!connection) continue;
|
||||||
|
|
||||||
|
const connectedBlock = connection.targetBlock();
|
||||||
|
if (connectedBlock) {
|
||||||
|
if (connection.type === ConnectionType.NEXT_STATEMENT && !forward) {
|
||||||
const lastStackBlock = connectedBlock
|
const lastStackBlock = connectedBlock
|
||||||
.lastConnectionInStack(false)
|
.lastConnectionInStack(false)
|
||||||
?.getSourceBlock();
|
?.getSourceBlock();
|
||||||
if (lastStackBlock) {
|
if (lastStackBlock) {
|
||||||
|
// When navigating backward, the last block in a stack in a statement
|
||||||
|
// input is navigable.
|
||||||
candidates.push(lastStackBlock);
|
candidates.push(lastStackBlock);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// When navigating forward, a child block connected to a statement
|
||||||
|
// input is navigable.
|
||||||
candidates.push(connectedBlock);
|
candidates.push(connectedBlock);
|
||||||
}
|
}
|
||||||
} else if (input.connection?.type === ConnectionType.INPUT_VALUE) {
|
} else if (
|
||||||
candidates.push(input.connection as RenderedConnection);
|
connection.type === ConnectionType.INPUT_VALUE ||
|
||||||
|
connection.type === ConnectionType.NEXT_STATEMENT
|
||||||
|
) {
|
||||||
|
// Empty input or statement connections are navigable.
|
||||||
|
candidates.push(connection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
block.nextConnection &&
|
||||||
|
!block.nextConnection.targetBlock() &&
|
||||||
|
(block.lastConnectionInStack(true) !== block.nextConnection ||
|
||||||
|
!!block.getSurroundParent())
|
||||||
|
) {
|
||||||
|
// The empty next connection on the last block in a stack inside of a
|
||||||
|
// statement input is navigable.
|
||||||
|
candidates.push(block.nextConnection);
|
||||||
|
}
|
||||||
|
|
||||||
return candidates;
|
return candidates;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,12 +15,24 @@
|
|||||||
|
|
||||||
import {BlockSvg} from '../block_svg.js';
|
import {BlockSvg} from '../block_svg.js';
|
||||||
import {RenderedWorkspaceComment} from '../comments/rendered_workspace_comment.js';
|
import {RenderedWorkspaceComment} from '../comments/rendered_workspace_comment.js';
|
||||||
|
import {ConnectionType} from '../connection_type.js';
|
||||||
import {getFocusManager} from '../focus_manager.js';
|
import {getFocusManager} from '../focus_manager.js';
|
||||||
import type {IFocusableNode} from '../interfaces/i_focusable_node.js';
|
import type {IFocusableNode} from '../interfaces/i_focusable_node.js';
|
||||||
import * as registry from '../registry.js';
|
import * as registry from '../registry.js';
|
||||||
|
import {RenderedConnection} from '../rendered_connection.js';
|
||||||
import type {WorkspaceSvg} from '../workspace_svg.js';
|
import type {WorkspaceSvg} from '../workspace_svg.js';
|
||||||
import {Marker} from './marker.js';
|
import {Marker} from './marker.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Representation of the direction of travel within a navigation context.
|
||||||
|
*/
|
||||||
|
export enum NavigationDirection {
|
||||||
|
NEXT,
|
||||||
|
PREVIOUS,
|
||||||
|
IN,
|
||||||
|
OUT,
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class for a line cursor.
|
* Class for a line cursor.
|
||||||
*/
|
*/
|
||||||
@@ -51,13 +63,7 @@ export class LineCursor extends Marker {
|
|||||||
}
|
}
|
||||||
const newNode = this.getNextNode(
|
const newNode = this.getNextNode(
|
||||||
curNode,
|
curNode,
|
||||||
(candidate: IFocusableNode | null) => {
|
this.getValidationFunction(NavigationDirection.NEXT),
|
||||||
return (
|
|
||||||
(candidate instanceof BlockSvg &&
|
|
||||||
!candidate.outputConnection?.targetBlock()) ||
|
|
||||||
candidate instanceof RenderedWorkspaceComment
|
|
||||||
);
|
|
||||||
},
|
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -80,7 +86,11 @@ export class LineCursor extends Marker {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newNode = this.getNextNode(curNode, () => true, true);
|
const newNode = this.getNextNode(
|
||||||
|
curNode,
|
||||||
|
this.getValidationFunction(NavigationDirection.IN),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
if (newNode) {
|
if (newNode) {
|
||||||
this.setCurNode(newNode);
|
this.setCurNode(newNode);
|
||||||
@@ -101,13 +111,7 @@ export class LineCursor extends Marker {
|
|||||||
}
|
}
|
||||||
const newNode = this.getPreviousNode(
|
const newNode = this.getPreviousNode(
|
||||||
curNode,
|
curNode,
|
||||||
(candidate: IFocusableNode | null) => {
|
this.getValidationFunction(NavigationDirection.PREVIOUS),
|
||||||
return (
|
|
||||||
(candidate instanceof BlockSvg &&
|
|
||||||
!candidate.outputConnection?.targetBlock()) ||
|
|
||||||
candidate instanceof RenderedWorkspaceComment
|
|
||||||
);
|
|
||||||
},
|
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -130,7 +134,11 @@ export class LineCursor extends Marker {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newNode = this.getPreviousNode(curNode, () => true, true);
|
const newNode = this.getPreviousNode(
|
||||||
|
curNode,
|
||||||
|
this.getValidationFunction(NavigationDirection.OUT),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
if (newNode) {
|
if (newNode) {
|
||||||
this.setCurNode(newNode);
|
this.setCurNode(newNode);
|
||||||
@@ -147,15 +155,14 @@ export class LineCursor extends Marker {
|
|||||||
atEndOfLine(): boolean {
|
atEndOfLine(): boolean {
|
||||||
const curNode = this.getCurNode();
|
const curNode = this.getCurNode();
|
||||||
if (!curNode) return false;
|
if (!curNode) return false;
|
||||||
const inNode = this.getNextNode(curNode, () => true, true);
|
const inNode = this.getNextNode(
|
||||||
|
curNode,
|
||||||
|
this.getValidationFunction(NavigationDirection.IN),
|
||||||
|
true,
|
||||||
|
);
|
||||||
const nextNode = this.getNextNode(
|
const nextNode = this.getNextNode(
|
||||||
curNode,
|
curNode,
|
||||||
(candidate: IFocusableNode | null) => {
|
this.getValidationFunction(NavigationDirection.NEXT),
|
||||||
return (
|
|
||||||
candidate instanceof BlockSvg &&
|
|
||||||
!candidate.outputConnection?.targetBlock()
|
|
||||||
);
|
|
||||||
},
|
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -298,6 +305,92 @@ export class LineCursor extends Marker {
|
|||||||
return this.getRightMostChild(newNode, stopIfFound);
|
return this.getRightMostChild(newNode, stopIfFound);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a function that will be used to determine whether a candidate for
|
||||||
|
* navigation is valid.
|
||||||
|
*
|
||||||
|
* @param direction The direction in which the user is navigating.
|
||||||
|
* @returns A function that takes a proposed navigation candidate and returns
|
||||||
|
* true if navigation should be allowed to proceed to it, or false to find
|
||||||
|
* a different candidate.
|
||||||
|
*/
|
||||||
|
getValidationFunction(
|
||||||
|
direction: NavigationDirection,
|
||||||
|
): (node: IFocusableNode | null) => boolean {
|
||||||
|
switch (direction) {
|
||||||
|
case NavigationDirection.IN:
|
||||||
|
case NavigationDirection.OUT:
|
||||||
|
return () => true;
|
||||||
|
case NavigationDirection.NEXT:
|
||||||
|
case NavigationDirection.PREVIOUS:
|
||||||
|
return (candidate: IFocusableNode | null) => {
|
||||||
|
if (
|
||||||
|
(candidate instanceof BlockSvg &&
|
||||||
|
!candidate.outputConnection?.targetBlock()) ||
|
||||||
|
candidate instanceof RenderedWorkspaceComment ||
|
||||||
|
(candidate instanceof RenderedConnection &&
|
||||||
|
(candidate.type === ConnectionType.NEXT_STATEMENT ||
|
||||||
|
(candidate.type === ConnectionType.INPUT_VALUE &&
|
||||||
|
candidate.getSourceBlock().statementInputCount &&
|
||||||
|
candidate.getSourceBlock().inputList[0] !==
|
||||||
|
candidate.getParentInput())))
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const current = this.getSourceBlockFromNode(this.getCurNode());
|
||||||
|
if (candidate instanceof BlockSvg && current instanceof BlockSvg) {
|
||||||
|
// If the candidate's parent uses inline inputs, disallow the
|
||||||
|
// candidate; it follows that it must be on the same row as its
|
||||||
|
// parent.
|
||||||
|
if (candidate.outputConnection?.targetBlock()?.getInputsInline()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const candidateParents = this.getParents(candidate);
|
||||||
|
// If the candidate block is an (in)direct child of the current
|
||||||
|
// block, disallow it; it cannot be on a different row than the
|
||||||
|
// current block.
|
||||||
|
if (
|
||||||
|
current === this.getCurNode() &&
|
||||||
|
candidateParents.has(current)
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentParents = this.getParents(current);
|
||||||
|
|
||||||
|
const sharedParents = currentParents.intersection(candidateParents);
|
||||||
|
// Allow the candidate if it and the current block have no parents
|
||||||
|
// in common, or if they have a shared parent with external inputs.
|
||||||
|
const result =
|
||||||
|
!sharedParents.size ||
|
||||||
|
sharedParents.values().some((block) => !block.getInputsInline());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a set of all of the parent blocks of the given block.
|
||||||
|
*
|
||||||
|
* @param block The block to retrieve the parents of.
|
||||||
|
* @returns A set of the parents of the given block.
|
||||||
|
*/
|
||||||
|
private getParents(block: BlockSvg): Set<BlockSvg> {
|
||||||
|
const parents = new Set<BlockSvg>();
|
||||||
|
let parent = block.getParent();
|
||||||
|
while (parent) {
|
||||||
|
parents.add(parent);
|
||||||
|
parent = parent.getParent();
|
||||||
|
}
|
||||||
|
|
||||||
|
return parents;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepare for the deletion of a block by making a list of nodes we
|
* Prepare for the deletion of a block by making a list of nodes we
|
||||||
* could move the cursor to afterwards and save it to
|
* could move the cursor to afterwards and save it to
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import {ConnectionType} from './connection_type.js';
|
|||||||
import * as ContextMenu from './contextmenu.js';
|
import * as ContextMenu from './contextmenu.js';
|
||||||
import {ContextMenuRegistry} from './contextmenu_registry.js';
|
import {ContextMenuRegistry} from './contextmenu_registry.js';
|
||||||
import * as eventUtils from './events/utils.js';
|
import * as eventUtils from './events/utils.js';
|
||||||
|
import {inputTypes} from './inputs/input_types.js';
|
||||||
import {IContextMenu} from './interfaces/i_contextmenu.js';
|
import {IContextMenu} from './interfaces/i_contextmenu.js';
|
||||||
import type {IFocusableNode} from './interfaces/i_focusable_node.js';
|
import type {IFocusableNode} from './interfaces/i_focusable_node.js';
|
||||||
import type {IFocusableTree} from './interfaces/i_focusable_tree.js';
|
import type {IFocusableTree} from './interfaces/i_focusable_tree.js';
|
||||||
@@ -334,7 +335,32 @@ export class RenderedConnection
|
|||||||
if (highlightSvg) {
|
if (highlightSvg) {
|
||||||
highlightSvg.style.display = '';
|
highlightSvg.style.display = '';
|
||||||
aria.setRole(highlightSvg, aria.Role.FIGURE);
|
aria.setRole(highlightSvg, aria.Role.FIGURE);
|
||||||
aria.setState(highlightSvg, aria.State.LABEL, 'Open connection');
|
aria.setState(highlightSvg, aria.State.ROLEDESCRIPTION, 'Connection');
|
||||||
|
if (this.type === ConnectionType.NEXT_STATEMENT) {
|
||||||
|
const parentInput =
|
||||||
|
this.getParentInput() ??
|
||||||
|
this.getSourceBlock()
|
||||||
|
.getTopStackBlock()
|
||||||
|
.previousConnection?.targetConnection?.getParentInput();
|
||||||
|
if (parentInput && parentInput.type === inputTypes.STATEMENT) {
|
||||||
|
aria.setState(
|
||||||
|
highlightSvg,
|
||||||
|
aria.State.LABEL,
|
||||||
|
`${this.getParentInput() ? 'Begin' : 'End'} ${parentInput.getFieldRowLabel()}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
this.type === ConnectionType.INPUT_VALUE &&
|
||||||
|
this.getSourceBlock().statementInputCount
|
||||||
|
) {
|
||||||
|
aria.setState(
|
||||||
|
highlightSvg,
|
||||||
|
aria.State.LABEL,
|
||||||
|
`${this.getParentInput()?.getFieldRowLabel()}`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
aria.setState(highlightSvg, aria.State.LABEL, 'Open connection');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -136,22 +136,22 @@ suite('Cursor', function () {
|
|||||||
assert.equal(curNode, fieldBlock);
|
assert.equal(curNode, fieldBlock);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Prev - From previous connection does skip over next connection', function () {
|
test('Prev - From previous connection does not skip over next connection', function () {
|
||||||
const prevConnection = this.blocks.B.previousConnection;
|
const prevConnection = this.blocks.B.previousConnection;
|
||||||
const prevConnectionNode = prevConnection;
|
const prevConnectionNode = prevConnection;
|
||||||
this.cursor.setCurNode(prevConnectionNode);
|
this.cursor.setCurNode(prevConnectionNode);
|
||||||
this.cursor.prev();
|
this.cursor.prev();
|
||||||
const curNode = this.cursor.getCurNode();
|
const curNode = this.cursor.getCurNode();
|
||||||
assert.equal(curNode, this.blocks.A);
|
assert.equal(curNode, this.blocks.A.nextConnection);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Prev - From first block loop to last block', function () {
|
test('Prev - From first block loop to last statement input', function () {
|
||||||
const prevConnection = this.blocks.A;
|
const prevConnection = this.blocks.A;
|
||||||
const prevConnectionNode = prevConnection;
|
const prevConnectionNode = prevConnection;
|
||||||
this.cursor.setCurNode(prevConnectionNode);
|
this.cursor.setCurNode(prevConnectionNode);
|
||||||
this.cursor.prev();
|
this.cursor.prev();
|
||||||
const curNode = this.cursor.getCurNode();
|
const curNode = this.cursor.getCurNode();
|
||||||
assert.equal(curNode, this.blocks.D);
|
assert.equal(curNode, this.blocks.D.getInput('NAME4').connection);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Out - From field does not skip over block node', function () {
|
test('Out - From field does not skip over block node', function () {
|
||||||
@@ -253,12 +253,16 @@ suite('Cursor', function () {
|
|||||||
test('In - from field in nested statement block to next nested statement block', function () {
|
test('In - from field in nested statement block to next nested statement block', function () {
|
||||||
this.cursor.setCurNode(this.secondStatement.getField('NAME'));
|
this.cursor.setCurNode(this.secondStatement.getField('NAME'));
|
||||||
this.cursor.in();
|
this.cursor.in();
|
||||||
|
// Skip over the next connection
|
||||||
|
this.cursor.in();
|
||||||
const curNode = this.cursor.getCurNode();
|
const curNode = this.cursor.getCurNode();
|
||||||
assert.equal(curNode, this.thirdStatement);
|
assert.equal(curNode, this.thirdStatement);
|
||||||
});
|
});
|
||||||
test('In - from field in nested statement block to next stack', function () {
|
test('In - from field in nested statement block to next stack', function () {
|
||||||
this.cursor.setCurNode(this.thirdStatement.getField('NAME'));
|
this.cursor.setCurNode(this.thirdStatement.getField('NAME'));
|
||||||
this.cursor.in();
|
this.cursor.in();
|
||||||
|
// Skip over the next connection
|
||||||
|
this.cursor.in();
|
||||||
const curNode = this.cursor.getCurNode();
|
const curNode = this.cursor.getCurNode();
|
||||||
assert.equal(curNode, this.multiStatement2);
|
assert.equal(curNode, this.multiStatement2);
|
||||||
});
|
});
|
||||||
@@ -266,6 +270,8 @@ suite('Cursor', function () {
|
|||||||
test('Out - from nested statement block to last field of previous nested statement block', function () {
|
test('Out - from nested statement block to last field of previous nested statement block', function () {
|
||||||
this.cursor.setCurNode(this.thirdStatement);
|
this.cursor.setCurNode(this.thirdStatement);
|
||||||
this.cursor.out();
|
this.cursor.out();
|
||||||
|
// Skip over the previous next connection
|
||||||
|
this.cursor.out();
|
||||||
const curNode = this.cursor.getCurNode();
|
const curNode = this.cursor.getCurNode();
|
||||||
assert.equal(curNode, this.secondStatement.getField('NAME'));
|
assert.equal(curNode, this.secondStatement.getField('NAME'));
|
||||||
});
|
});
|
||||||
@@ -273,6 +279,8 @@ suite('Cursor', function () {
|
|||||||
test('Out - from root block to last field of last nested statement block in previous stack', function () {
|
test('Out - from root block to last field of last nested statement block in previous stack', function () {
|
||||||
this.cursor.setCurNode(this.multiStatement2);
|
this.cursor.setCurNode(this.multiStatement2);
|
||||||
this.cursor.out();
|
this.cursor.out();
|
||||||
|
// Skip over the previous next connection
|
||||||
|
this.cursor.out();
|
||||||
const curNode = this.cursor.getCurNode();
|
const curNode = this.cursor.getCurNode();
|
||||||
assert.equal(curNode, this.thirdStatement.getField('NAME'));
|
assert.equal(curNode, this.thirdStatement.getField('NAME'));
|
||||||
});
|
});
|
||||||
@@ -395,7 +403,7 @@ suite('Cursor', function () {
|
|||||||
});
|
});
|
||||||
test('getLastNode', function () {
|
test('getLastNode', function () {
|
||||||
const node = this.cursor.getLastNode();
|
const node = this.cursor.getLastNode();
|
||||||
assert.equal(node, this.blockA);
|
assert.equal(node, this.blockA.inputList[0].connection);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -531,12 +531,13 @@ suite('Navigation', function () {
|
|||||||
);
|
);
|
||||||
assert.equal(nextNode, field);
|
assert.equal(nextNode, field);
|
||||||
});
|
});
|
||||||
test('fromBlockToFieldSkippingInput', function () {
|
test('fromInputToStatementConnection', function () {
|
||||||
const field = this.blocks.buttonBlock.getField('BUTTON3');
|
const connection =
|
||||||
|
this.blocks.buttonBlock.getInput('STATEMENT1').connection;
|
||||||
const nextNode = this.navigator.getNextSibling(
|
const nextNode = this.navigator.getNextSibling(
|
||||||
this.blocks.buttonInput2,
|
this.blocks.buttonInput2,
|
||||||
);
|
);
|
||||||
assert.equal(nextNode, field);
|
assert.equal(nextNode, connection);
|
||||||
});
|
});
|
||||||
test('skipsChildrenOfCollapsedBlocks', function () {
|
test('skipsChildrenOfCollapsedBlocks', function () {
|
||||||
this.blocks.buttonBlock.setCollapsed(true);
|
this.blocks.buttonBlock.setCollapsed(true);
|
||||||
@@ -546,9 +547,9 @@ suite('Navigation', function () {
|
|||||||
test('fromFieldSkipsHiddenInputs', function () {
|
test('fromFieldSkipsHiddenInputs', function () {
|
||||||
this.blocks.buttonBlock.inputList[2].setVisible(false);
|
this.blocks.buttonBlock.inputList[2].setVisible(false);
|
||||||
const fieldStart = this.blocks.buttonBlock.getField('BUTTON2');
|
const fieldStart = this.blocks.buttonBlock.getField('BUTTON2');
|
||||||
const fieldEnd = this.blocks.buttonBlock.getField('BUTTON3');
|
const end = this.blocks.buttonBlock.getInput('STATEMENT1').connection;
|
||||||
const nextNode = this.navigator.getNextSibling(fieldStart);
|
const nextNode = this.navigator.getNextSibling(fieldStart);
|
||||||
assert.equal(nextNode.name, fieldEnd.name);
|
assert.equal(nextNode, end);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -693,9 +694,9 @@ suite('Navigation', function () {
|
|||||||
test('fromFieldSkipsHiddenInputs', function () {
|
test('fromFieldSkipsHiddenInputs', function () {
|
||||||
this.blocks.buttonBlock.inputList[2].setVisible(false);
|
this.blocks.buttonBlock.inputList[2].setVisible(false);
|
||||||
const fieldStart = this.blocks.buttonBlock.getField('BUTTON3');
|
const fieldStart = this.blocks.buttonBlock.getField('BUTTON3');
|
||||||
const fieldEnd = this.blocks.buttonBlock.getField('BUTTON2');
|
const end = this.blocks.buttonBlock.getInput('STATEMENT1').connection;
|
||||||
const nextNode = this.navigator.getPreviousSibling(fieldStart);
|
const nextNode = this.navigator.getPreviousSibling(fieldStart);
|
||||||
assert.equal(nextNode.name, fieldEnd.name);
|
assert.equal(nextNode, end);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"target": "ES2020",
|
"target": "ES2020",
|
||||||
"strict": true,
|
"strict": true,
|
||||||
|
"lib": ["esnext", "dom"],
|
||||||
|
|
||||||
// This does not understand enums only used to define other enums, so we
|
// This does not understand enums only used to define other enums, so we
|
||||||
// cannot leave it enabled.
|
// cannot leave it enabled.
|
||||||
|
|||||||
Reference in New Issue
Block a user