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. *