diff --git a/test/index.html b/test/index.html
index a528362f..bf0f5f13 100644
--- a/test/index.html
+++ b/test/index.html
@@ -164,6 +164,9 @@
+
diff --git a/test/loadTestBlocks.js b/test/loadTestBlocks.js
index ce0bd347..3c6ddf14 100644
--- a/test/loadTestBlocks.js
+++ b/test/loadTestBlocks.js
@@ -220,7 +220,7 @@ const simpleCircle = {
},
{
'type': 'p5_draw',
- 'id': 'draw_root',
+ 'id': 'p5_draw_1',
'x': 0,
'y': 332,
'deletable': false,
@@ -414,6 +414,162 @@ const moreBlocks = {
},
};
+const navigationTestBlocks = {
+ 'blocks': {
+ 'languageVersion': 0,
+ 'blocks': [
+ {
+ 'type': 'p5_setup',
+ 'id': 'p5_setup_1',
+ 'x': 0,
+ 'y': 75,
+ 'deletable': false,
+ 'inputs': {
+ 'STATEMENTS': {
+ 'block': {
+ 'type': 'p5_canvas',
+ 'id': 'p5_canvas_1',
+ 'deletable': false,
+ 'movable': false,
+ 'fields': {
+ 'WIDTH': 400,
+ 'HEIGHT': 400,
+ },
+ },
+ },
+ },
+ },
+ {
+ 'type': 'p5_draw',
+ 'id': 'p5_draw_1',
+ 'x': 0,
+ 'y': 332,
+ 'deletable': false,
+ 'inputs': {
+ 'STATEMENTS': {
+ 'block': {
+ 'type': 'controls_if',
+ 'id': 'controls_if_1',
+ 'next': {
+ 'block': {
+ 'type': 'controls_if',
+ 'id': 'controls_if_2',
+ 'inputs': {
+ 'IF0': {
+ 'block': {
+ 'type': 'logic_boolean',
+ 'id': 'logic_boolean_1',
+ 'fields': {
+ 'BOOL': 'TRUE',
+ },
+ },
+ },
+ 'DO0': {
+ 'block': {
+ 'type': 'text_print',
+ 'id': 'text_print_1',
+ 'inputs': {
+ 'TEXT': {
+ 'shadow': {
+ 'type': 'text',
+ 'id': 'text_1',
+ 'fields': {
+ 'TEXT': 'abc',
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ 'next': {
+ 'block': {
+ 'type': 'controls_repeat',
+ 'id': 'controls_repeat_1',
+ 'fields': {
+ 'TIMES': 10,
+ },
+ 'inputs': {
+ 'DO': {
+ 'block': {
+ 'type': 'draw_emoji',
+ 'id': 'draw_emoji_1',
+ 'fields': {
+ 'emoji': '❤️',
+ },
+ 'next': {
+ 'block': {
+ 'type': 'simple_circle',
+ 'id': 'simple_circle_1',
+ 'inputs': {
+ 'COLOR': {
+ 'shadow': {
+ 'type': 'colour_picker',
+ 'id': 'colour_picker_1',
+ 'fields': {
+ 'COLOUR': '#ff0000',
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ 'next': {
+ 'block': {
+ 'type': 'controls_repeat_ext',
+ 'id': 'controls_repeat_ext_1',
+ 'inputs': {
+ 'TIMES': {
+ 'shadow': {
+ 'type': 'math_number',
+ 'id': 'math_number_1',
+ 'fields': {
+ 'NUM': 10,
+ },
+ },
+ 'block': {
+ 'type': 'math_modulo',
+ 'id': 'math_modulo_1',
+ 'inputs': {
+ 'DIVIDEND': {
+ 'shadow': {
+ 'type': 'math_number',
+ 'id': 'math_number_2',
+ 'fields': {
+ 'NUM': 64,
+ },
+ },
+ },
+ 'DIVISOR': {
+ 'shadow': {
+ 'type': 'math_number',
+ 'id': 'math_number_3',
+ 'fields': {
+ 'NUM': 10,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ ],
+ },
+};
+
/**
* Loads saved state from local storage into the given workspace.
* @param {Blockly.Workspace} workspace Blockly workspace to load into.
@@ -425,6 +581,7 @@ export const load = function (workspace, scenarioString) {
'sun': sunnyDay,
'simpleCircle': simpleCircle,
'moreBlocks': moreBlocks,
+ 'navigationTestBlocks': navigationTestBlocks,
};
const data = JSON.stringify(scenarioMap[scenarioString]);
diff --git a/test/webdriverio/test/basic_test.ts b/test/webdriverio/test/basic_test.ts
index 6b142b0b..6cf8c77d 100644
--- a/test/webdriverio/test/basic_test.ts
+++ b/test/webdriverio/test/basic_test.ts
@@ -9,6 +9,7 @@ import * as Blockly from 'blockly';
import {
focusWorkspace,
setCurrentCursorNodeById,
+ setCurrentCursorNodeByIdAndFieldName,
getCurrentCursorNodeFieldName,
getCurrentCursorNodeId,
getCurrentCursorNodeType,
@@ -25,7 +26,7 @@ suite('Keyboard navigation', function () {
// Setup Selenium for all of the tests
suiteSetup(async function () {
- this.browser = await testSetup(testFileLocations.BASE);
+ this.browser = await testSetup(testFileLocations.NAVIGATION_TEST_BLOCKS);
});
test('Default workspace', async function () {
@@ -33,13 +34,13 @@ suite('Keyboard navigation', function () {
return Blockly.getMainWorkspace().getAllBlocks(false).length;
});
- chai.assert.equal(blockCount, 7);
+ chai.assert.equal(blockCount, 16);
});
test('Selected block', async function () {
await tabNavigateToWorkspace(this.browser);
-
- for (let i = 0; i < 8; i++) {
+
+ for (let i = 0; i < 14; i++) {
await this.browser.keys(Key.ArrowDown);
await this.browser.pause(PAUSE_TIME);
}
@@ -47,20 +48,24 @@ suite('Keyboard navigation', function () {
const selectedId = await this.browser.execute(() => {
return Blockly.common.getSelected()?.id;
});
- chai.assert.equal(selectedId, 'draw_circle_1');
+ chai.assert.equal(selectedId, 'simple_circle_1');
+ chai.assert.equal(
+ await getCurrentCursorNodeType(this.browser),
+ Blockly.ASTNode.types.BLOCK,
+ );
});
test('Down from statement block selects next connection', async function () {
await focusWorkspace(this.browser);
await this.browser.pause(PAUSE_TIME);
- await setCurrentCursorNodeById(this.browser, 'create_canvas_1');
+ await setCurrentCursorNodeById(this.browser, 'p5_canvas_1');
await this.browser.pause(PAUSE_TIME);
await this.browser.keys(Key.ArrowDown);
await this.browser.pause(PAUSE_TIME);
chai.assert.equal(
await getCurrentCursorNodeId(this.browser),
- 'create_canvas_1',
+ 'p5_canvas_1',
);
chai.assert.equal(
await getCurrentCursorNodeType(this.browser),
@@ -71,14 +76,14 @@ suite('Keyboard navigation', function () {
test("Up from statement block selects previous block's connection", async function () {
await focusWorkspace(this.browser);
await this.browser.pause(PAUSE_TIME);
- await setCurrentCursorNodeById(this.browser, 'set_background_color_1');
+ await setCurrentCursorNodeById(this.browser, 'simple_circle_1');
await this.browser.pause(PAUSE_TIME);
await this.browser.keys(Key.ArrowUp);
await this.browser.pause(PAUSE_TIME);
chai.assert.equal(
await getCurrentCursorNodeId(this.browser),
- 'create_canvas_1',
+ 'draw_emoji_1',
);
chai.assert.equal(
await getCurrentCursorNodeType(this.browser),
@@ -104,7 +109,7 @@ suite('Keyboard navigation', function () {
test('Up from child block selects input connection', async function () {
await focusWorkspace(this.browser);
await this.browser.pause(PAUSE_TIME);
- await setCurrentCursorNodeById(this.browser, 'create_canvas_1');
+ await setCurrentCursorNodeById(this.browser, 'p5_canvas_1');
await this.browser.pause(PAUSE_TIME);
await this.browser.keys(Key.ArrowUp);
await this.browser.pause(PAUSE_TIME);
@@ -119,14 +124,14 @@ suite('Keyboard navigation', function () {
test('Right from block selects first field', async function () {
await focusWorkspace(this.browser);
await this.browser.pause(PAUSE_TIME);
- await setCurrentCursorNodeById(this.browser, 'create_canvas_1');
+ await setCurrentCursorNodeById(this.browser, 'p5_canvas_1');
await this.browser.pause(PAUSE_TIME);
await this.browser.keys(Key.ArrowRight);
await this.browser.pause(PAUSE_TIME);
chai.assert.equal(
await getCurrentCursorNodeId(this.browser),
- 'create_canvas_1',
+ 'p5_canvas_1',
);
chai.assert.equal(
await getCurrentCursorNodeType(this.browser),
@@ -137,4 +142,340 @@ suite('Keyboard navigation', function () {
'WIDTH',
);
});
+
+ test('Right from block selects first inline input', async function () {
+ await focusWorkspace(this.browser);
+ await this.browser.pause(PAUSE_TIME);
+ await setCurrentCursorNodeById(this.browser, 'simple_circle_1');
+ await this.browser.pause(PAUSE_TIME);
+ await this.browser.keys(Key.ArrowRight);
+ await this.browser.pause(PAUSE_TIME);
+
+ chai.assert.equal(
+ await getCurrentCursorNodeId(this.browser),
+ 'colour_picker_1',
+ );
+ chai.assert.equal(
+ await getCurrentCursorNodeType(this.browser),
+ Blockly.ASTNode.types.BLOCK,
+ );
+ });
+
+ test('Up from first field selects block', async function () {
+ await focusWorkspace(this.browser);
+ await this.browser.pause(PAUSE_TIME);
+ await setCurrentCursorNodeByIdAndFieldName(
+ this.browser,
+ 'p5_canvas_1',
+ 'WIDTH',
+ );
+ await this.browser.pause(PAUSE_TIME);
+ await this.browser.keys(Key.ArrowUp);
+ await this.browser.pause(PAUSE_TIME);
+
+ chai.assert.equal(
+ await getCurrentCursorNodeId(this.browser),
+ 'p5_canvas_1',
+ );
+ chai.assert.equal(
+ await getCurrentCursorNodeType(this.browser),
+ Blockly.ASTNode.types.BLOCK,
+ );
+ });
+
+ test('Left from first field selects block', async function () {
+ await focusWorkspace(this.browser);
+ await this.browser.pause(PAUSE_TIME);
+ await setCurrentCursorNodeByIdAndFieldName(
+ this.browser,
+ 'p5_canvas_1',
+ 'WIDTH',
+ );
+ await this.browser.pause(PAUSE_TIME);
+ await this.browser.keys(Key.ArrowLeft);
+ await this.browser.pause(PAUSE_TIME);
+
+ chai.assert.equal(
+ await getCurrentCursorNodeId(this.browser),
+ 'p5_canvas_1',
+ );
+ chai.assert.equal(
+ await getCurrentCursorNodeType(this.browser),
+ Blockly.ASTNode.types.BLOCK,
+ );
+ });
+
+ test('Right from first field selects second field', async function () {
+ await focusWorkspace(this.browser);
+ await this.browser.pause(PAUSE_TIME);
+ await setCurrentCursorNodeByIdAndFieldName(
+ this.browser,
+ 'p5_canvas_1',
+ 'WIDTH',
+ );
+ await this.browser.pause(PAUSE_TIME);
+ await this.browser.keys(Key.ArrowRight);
+ await this.browser.pause(PAUSE_TIME);
+
+ chai.assert.equal(
+ await getCurrentCursorNodeId(this.browser),
+ 'p5_canvas_1',
+ );
+ chai.assert.equal(
+ await getCurrentCursorNodeType(this.browser),
+ Blockly.ASTNode.types.FIELD,
+ );
+ chai.assert.equal(
+ await getCurrentCursorNodeFieldName(this.browser),
+ 'HEIGHT',
+ );
+ });
+
+ test('Left from second field selects first field', async function () {
+ await focusWorkspace(this.browser);
+ await this.browser.pause(PAUSE_TIME);
+ await setCurrentCursorNodeByIdAndFieldName(
+ this.browser,
+ 'p5_canvas_1',
+ 'HEIGHT',
+ );
+ await this.browser.pause(PAUSE_TIME);
+ await this.browser.keys(Key.ArrowLeft);
+ await this.browser.pause(PAUSE_TIME);
+
+ chai.assert.equal(
+ await getCurrentCursorNodeId(this.browser),
+ 'p5_canvas_1',
+ );
+ chai.assert.equal(
+ await getCurrentCursorNodeType(this.browser),
+ Blockly.ASTNode.types.FIELD,
+ );
+ chai.assert.equal(
+ await getCurrentCursorNodeFieldName(this.browser),
+ 'WIDTH',
+ );
+ });
+
+ test("Right from second field selects block's next connection", async function () {
+ await focusWorkspace(this.browser);
+ await this.browser.pause(PAUSE_TIME);
+ await setCurrentCursorNodeByIdAndFieldName(
+ this.browser,
+ 'p5_canvas_1',
+ 'HEIGHT',
+ );
+ await this.browser.pause(PAUSE_TIME);
+ await this.browser.keys(Key.ArrowRight);
+ await this.browser.pause(PAUSE_TIME);
+
+ chai.assert.equal(
+ await getCurrentCursorNodeId(this.browser),
+ 'p5_canvas_1',
+ );
+ chai.assert.equal(
+ await getCurrentCursorNodeType(this.browser),
+ Blockly.ASTNode.types.NEXT,
+ );
+ });
+
+ test("Down from field selects block's next connection", async function () {
+ await focusWorkspace(this.browser);
+ await this.browser.pause(PAUSE_TIME);
+ await setCurrentCursorNodeByIdAndFieldName(
+ this.browser,
+ 'p5_canvas_1',
+ 'WIDTH',
+ );
+ await this.browser.pause(PAUSE_TIME);
+ await this.browser.keys(Key.ArrowDown);
+ await this.browser.pause(PAUSE_TIME);
+
+ chai.assert.equal(
+ await getCurrentCursorNodeId(this.browser),
+ 'p5_canvas_1',
+ );
+ chai.assert.equal(
+ await getCurrentCursorNodeType(this.browser),
+ Blockly.ASTNode.types.NEXT,
+ );
+ });
+
+ test("Down from field selects block's child connection", async function () {
+ await focusWorkspace(this.browser);
+ await this.browser.pause(PAUSE_TIME);
+ await setCurrentCursorNodeByIdAndFieldName(
+ this.browser,
+ 'controls_repeat_1',
+ 'TIMES',
+ );
+ await this.browser.pause(PAUSE_TIME);
+ await this.browser.keys(Key.ArrowDown);
+ await this.browser.pause(PAUSE_TIME);
+
+ chai.assert.equal(
+ await getCurrentCursorNodeId(this.browser),
+ 'controls_repeat_1',
+ );
+ chai.assert.equal(
+ await getCurrentCursorNodeType(this.browser),
+ Blockly.ASTNode.types.INPUT,
+ );
+ });
+
+ test('Up from inline input selects statement block', async function () {
+ await focusWorkspace(this.browser);
+ await this.browser.pause(PAUSE_TIME);
+ await setCurrentCursorNodeById(this.browser, 'math_number_2');
+ await this.browser.pause(PAUSE_TIME);
+ await this.browser.keys(Key.ArrowUp);
+ await this.browser.pause(PAUSE_TIME);
+
+ chai.assert.equal(
+ await getCurrentCursorNodeId(this.browser),
+ 'controls_repeat_ext_1',
+ );
+ chai.assert.equal(
+ await getCurrentCursorNodeType(this.browser),
+ Blockly.ASTNode.types.BLOCK,
+ );
+ });
+
+ test('Left from first inline input selects block', async function () {
+ await focusWorkspace(this.browser);
+ await this.browser.pause(PAUSE_TIME);
+ await setCurrentCursorNodeById(this.browser, 'math_number_2');
+ await this.browser.pause(PAUSE_TIME);
+ await this.browser.keys(Key.ArrowLeft);
+ await this.browser.pause(PAUSE_TIME);
+
+ chai.assert.equal(
+ await getCurrentCursorNodeId(this.browser),
+ 'math_modulo_1',
+ );
+ chai.assert.equal(
+ await getCurrentCursorNodeType(this.browser),
+ Blockly.ASTNode.types.BLOCK,
+ );
+ });
+
+ test('Right from first inline input selects second inline input', async function () {
+ await focusWorkspace(this.browser);
+ await this.browser.pause(PAUSE_TIME);
+ await setCurrentCursorNodeById(this.browser, 'math_number_2');
+ await this.browser.pause(PAUSE_TIME);
+ await this.browser.keys(Key.ArrowRight);
+ await this.browser.pause(PAUSE_TIME);
+
+ chai.assert.equal(
+ await getCurrentCursorNodeId(this.browser),
+ 'math_number_3',
+ );
+ chai.assert.equal(
+ await getCurrentCursorNodeType(this.browser),
+ Blockly.ASTNode.types.BLOCK,
+ );
+ });
+
+ test('Left from second inline input selects first inline input', async function () {
+ await focusWorkspace(this.browser);
+ await this.browser.pause(PAUSE_TIME);
+ await setCurrentCursorNodeById(this.browser, 'math_number_3');
+ await this.browser.pause(PAUSE_TIME);
+ await this.browser.keys(Key.ArrowLeft);
+ await this.browser.pause(PAUSE_TIME);
+
+ chai.assert.equal(
+ await getCurrentCursorNodeId(this.browser),
+ 'math_number_2',
+ );
+ chai.assert.equal(
+ await getCurrentCursorNodeType(this.browser),
+ Blockly.ASTNode.types.BLOCK,
+ );
+ });
+
+ test("Right from last inline input selects block's next connection", async function () {
+ await focusWorkspace(this.browser);
+ await this.browser.pause(PAUSE_TIME);
+ await setCurrentCursorNodeById(this.browser, 'colour_picker_1');
+ await this.browser.pause(PAUSE_TIME);
+ await this.browser.keys(Key.ArrowRight);
+ await this.browser.pause(PAUSE_TIME);
+
+ chai.assert.equal(
+ await getCurrentCursorNodeId(this.browser),
+ 'simple_circle_1',
+ );
+ chai.assert.equal(
+ await getCurrentCursorNodeType(this.browser),
+ Blockly.ASTNode.types.NEXT,
+ );
+ });
+
+ test("Down from inline input selects block's next connection", async function () {
+ await focusWorkspace(this.browser);
+ await this.browser.pause(PAUSE_TIME);
+ await setCurrentCursorNodeById(this.browser, 'colour_picker_1');
+ await this.browser.pause(PAUSE_TIME);
+ await this.browser.keys(Key.ArrowDown);
+ await this.browser.pause(PAUSE_TIME);
+
+ chai.assert.equal(
+ await getCurrentCursorNodeId(this.browser),
+ 'simple_circle_1',
+ );
+ chai.assert.equal(
+ await getCurrentCursorNodeType(this.browser),
+ Blockly.ASTNode.types.NEXT,
+ );
+ });
+
+ test("Down from inline input selects block's child connection", async function () {
+ await focusWorkspace(this.browser);
+ await this.browser.pause(PAUSE_TIME);
+ await setCurrentCursorNodeById(this.browser, 'math_number_2');
+ await this.browser.pause(PAUSE_TIME);
+ await this.browser.keys(Key.ArrowDown);
+ await this.browser.pause(PAUSE_TIME);
+
+ chai.assert.equal(
+ await getCurrentCursorNodeId(this.browser),
+ 'controls_repeat_ext_1',
+ );
+ chai.assert.equal(
+ await getCurrentCursorNodeType(this.browser),
+ Blockly.ASTNode.types.INPUT,
+ );
+ });
+
+ /*
+ // This test fails because the curly quote icons get selected.
+ test('Right from text block selects input and skips curly quote icons', async function () {
+ await focusWorkspace(this.browser);
+ await this.browser.pause(PAUSE_TIME);
+ await setCurrentCursorNodeById(this.browser, 'text_print_1');
+ await this.browser.pause(PAUSE_TIME);
+ await this.browser.keys(Key.ArrowRight);
+ await this.browser.pause(PAUSE_TIME);
+
+ chai.assert.equal(await getCurrentCursorNodeId(this.browser), 'text_1');
+ chai.assert.equal(
+ await getCurrentCursorNodeType(this.browser),
+ Blockly.ASTNode.types.BLOCK,
+ );
+
+ await this.browser.keys(Key.ArrowRight);
+ await this.browser.pause(PAUSE_TIME);
+
+ chai.assert.equal(
+ await getCurrentCursorNodeId(this.browser),
+ 'text_print_1',
+ );
+ chai.assert.equal(
+ await getCurrentCursorNodeType(this.browser),
+ Blockly.ASTNode.types.NEXT,
+ );
+ });
+ */
});
diff --git a/test/webdriverio/test/test_setup.ts b/test/webdriverio/test/test_setup.ts
index 7e108295..cfd0239f 100644
--- a/test/webdriverio/test/test_setup.ts
+++ b/test/webdriverio/test/test_setup.ts
@@ -134,6 +134,10 @@ const createTestUrl = (options?: URLSearchParams) => {
export const testFileLocations = {
BASE: createTestUrl(),
// eslint-disable-next-line @typescript-eslint/naming-convention
+ NAVIGATION_TEST_BLOCKS: createTestUrl(
+ new URLSearchParams({scenario: 'navigationTestBlocks'}),
+ ),
+ // eslint-disable-next-line @typescript-eslint/naming-convention
BASE_RTL: createTestUrl(new URLSearchParams({rtl: 'true'})),
GERAS: createTestUrl(new URLSearchParams({renderer: 'geras'})),
// eslint-disable-next-line @typescript-eslint/naming-convention
@@ -179,15 +183,43 @@ export async function setCurrentCursorNodeById(
) {
return await browser.execute((blockId) => {
const workspaceSvg = Blockly.getMainWorkspace() as Blockly.WorkspaceSvg;
- const rootBlock = workspaceSvg.getBlockById(blockId);
- if (rootBlock) {
+ const block = workspaceSvg.getBlockById(blockId);
+ if (block) {
workspaceSvg
.getCursor()
- ?.setCurNode(Blockly.ASTNode.createBlockNode(rootBlock));
+ ?.setCurNode(Blockly.ASTNode.createBlockNode(block));
}
}, blockId);
}
+/**
+ * Select a block with the given id as the current cursor node.
+ *
+ * @param browser The active WebdriverIO Browser object.
+ * @param blockId The id of the block to select.
+ * @param fieldName The name of the field on the block to select.
+ */
+export async function setCurrentCursorNodeByIdAndFieldName(
+ browser: WebdriverIO.Browser,
+ blockId: string,
+ fieldName: string,
+) {
+ return await browser.execute(
+ (blockId, fieldName) => {
+ const workspaceSvg = Blockly.getMainWorkspace() as Blockly.WorkspaceSvg;
+ const block = workspaceSvg.getBlockById(blockId);
+ const field = block?.getField(fieldName);
+ if (field) {
+ workspaceSvg
+ .getCursor()
+ ?.setCurNode(Blockly.ASTNode.createFieldNode(field));
+ }
+ },
+ blockId,
+ fieldName,
+ );
+}
+
/**
* Get the ID of the block at the current cursor node.
*