From 7a67273e7f995e342908032b44c693acf6cedb89 Mon Sep 17 00:00:00 2001 From: Ben Henning Date: Mon, 7 Jul 2025 22:23:04 +0000 Subject: [PATCH 1/3] chore: Add new tests for ephemeral focus. --- test/webdriverio/test/actions_test.ts | 31 ++++++++++++----- test/webdriverio/test/basic_test.ts | 48 ++++++++++++++++++++++----- test/webdriverio/test/test_setup.ts | 13 ++++++++ 3 files changed, 75 insertions(+), 17 deletions(-) diff --git a/test/webdriverio/test/actions_test.ts b/test/webdriverio/test/actions_test.ts index 249e4d65..80dcfcc3 100644 --- a/test/webdriverio/test/actions_test.ts +++ b/test/webdriverio/test/actions_test.ts @@ -15,6 +15,8 @@ import { testFileLocations, testSetup, keyRight, + sendKeyAndWait, + getCurrentFocusedBlockId, } from './test_setup.js'; suite('Menus test', function () { @@ -31,9 +33,7 @@ suite('Menus test', function () { // Navigate to draw_circle_1. await tabNavigateToWorkspace(this.browser); await focusOnBlock(this.browser, 'draw_circle_1'); - await this.browser.pause(PAUSE_TIME); - await this.browser.keys([Key.Ctrl, Key.Return]); - await this.browser.pause(PAUSE_TIME); + await sendKeyAndWait(this.browser, [Key.Ctrl, Key.Return]); chai.assert.isTrue( await contextMenuExists(this.browser, 'Duplicate'), 'The menu should be openable on a block', @@ -48,8 +48,7 @@ suite('Menus test', function () { await moveToToolboxCategory(this.browser, 'Functions'); // Move to flyout. await keyRight(this.browser); - await this.browser.keys([Key.Ctrl, Key.Return]); - await this.browser.pause(PAUSE_TIME); + await sendKeyAndWait(this.browser, [Key.Ctrl, Key.Return]); chai.assert.isTrue( await contextMenuExists(this.browser, 'Help'), @@ -62,12 +61,28 @@ suite('Menus test', function () { await tabNavigateToWorkspace(this.browser); await focusOnBlock(this.browser, 'draw_circle_1'); // Start moving the block - await this.browser.keys('m'); - await this.browser.keys([Key.Ctrl, Key.Return]); - await this.browser.pause(PAUSE_TIME); + await sendKeyAndWait(this.browser, 'm'); + await sendKeyAndWait(this.browser, [Key.Ctrl, Key.Return]); chai.assert.isTrue( await contextMenuExists(this.browser, 'Duplicate', true), 'The menu should not be openable during a move', ); }); + + test('Closing menu restores focus to starting node', async function () { + // Navigate to draw_circle_1. + await tabNavigateToWorkspace(this.browser); + await focusOnBlock(this.browser, 'draw_circle_1'); + // Open the context menu. + await sendKeyAndWait(this.browser, [Key.Ctrl, Key.Return]); + + // Close the context menu. + await sendKeyAndWait(this.browser, Key.Escape); + + // The previously focused now should now again be focused. + chai.assert.strictEqual( + await getCurrentFocusedBlockId(this.browser), + 'draw_circle_1', + ); + }); }); diff --git a/test/webdriverio/test/basic_test.ts b/test/webdriverio/test/basic_test.ts index abfd7dde..c7666e8e 100644 --- a/test/webdriverio/test/basic_test.ts +++ b/test/webdriverio/test/basic_test.ts @@ -21,6 +21,8 @@ import { keyRight, keyUp, keyDown, + sendKeyAndWait, + isEphemeralFocusActive, } from './test_setup.js'; import {Key} from 'webdriverio'; @@ -239,13 +241,11 @@ suite('Keyboard navigation on Blocks', function () { await tabNavigateToWorkspace(this.browser); await this.browser.pause(PAUSE_TIME); await focusOnBlock(this.browser, 'text_print_1'); - await this.browser.keys('m'); - await this.browser.pause(PAUSE_TIME); + await sendKeyAndWait(this.browser, 'm'); chai.assert.isTrue(await isDragging(this.browser)); - await this.browser.keys(Key.Tab); - await this.browser.pause(PAUSE_TIME); + await sendKeyAndWait(this.browser, Key.Tab); chai.assert.isFalse(await isDragging(this.browser)); }); @@ -354,8 +354,7 @@ suite('Keyboard navigation on Fields', function () { // Open a field editor dropdown await focusOnBlockField(this.browser, 'logic_boolean_1', 'BOOL'); await this.browser.pause(PAUSE_TIME); - await this.browser.keys(Key.Enter); - await this.browser.pause(PAUSE_TIME); + await sendKeyAndWait(this.browser, Key.Enter); // Try to navigate to a different block await keyRight(this.browser); @@ -368,13 +367,12 @@ suite('Keyboard navigation on Fields', function () { // Open colour picker await focusOnBlockField(this.browser, 'colour_picker_1', 'COLOUR'); await this.browser.pause(PAUSE_TIME); - await this.browser.keys(Key.Enter); - await this.browser.pause(PAUSE_TIME); + await sendKeyAndWait(this.browser, Key.Enter); // Move right to pick a new colour. await keyRight(this.browser); // Enter to choose. - await this.browser.keys(Key.Enter); + await sendKeyAndWait(this.browser, Key.Enter); // Focus seems to take longer than a single pause to settle. await this.browser.waitUntil( @@ -385,4 +383,36 @@ suite('Keyboard navigation on Fields', function () { {timeout: 1000}, ); }); + + test('Exiting inline field editor should restore focus to field', async function () { + // Select a block with an inline-editable field. + await focusOnBlock(this.browser, 'p5_canvas_1'); + // Select the field. + await keyRight(this.browser); + // Open the field's editor. + await sendKeyAndWait(this.browser, Key.Enter); + + // Exit the editor. + await sendKeyAndWait(this.browser, Key.Escape); + + // The field should be focused without ephemeral focus. + chai.assert.equal(await getFocusedFieldName(this.browser), 'WIDTH'); + chai.assert.isFalse(await isEphemeralFocusActive(this.browser)); + }); + + test('Exiting drop-down field editor should restore focus to field', async function () { + // Select a block with a drop-down editable field. + await focusOnBlock(this.browser, 'logic_boolean_1'); + // Select the field. + await keyRight(this.browser); + // Open the field's editor. + await sendKeyAndWait(this.browser, Key.Enter); + + // Exit the editor. + await sendKeyAndWait(this.browser, Key.Escape); + + // The field should be focused without ephemeral focus. + chai.assert.equal(await getFocusedFieldName(this.browser), 'BOOL'); + chai.assert.isFalse(await isEphemeralFocusActive(this.browser)); + }); }); diff --git a/test/webdriverio/test/test_setup.ts b/test/webdriverio/test/test_setup.ts index 213f0bf5..cb1974e1 100644 --- a/test/webdriverio/test/test_setup.ts +++ b/test/webdriverio/test/test_setup.ts @@ -550,6 +550,19 @@ export async function isDragging( }); } +/** + * Returns whether Blockly's FocusManager currently holds ephemeral focus. + * + * @param browser The active WebdriverIO Browser object. + */ +export async function isEphemeralFocusActive( + browser: WebdriverIO.Browser, +): Promise { + return await browser.execute(() => + Blockly.getFocusManager().ephemeralFocusTaken(), + ); +} + /** * Returns the result of the specificied action precondition. * From de93f589190be3ae3ebd8ee773fd561c1fc1832a Mon Sep 17 00:00:00 2001 From: Ben Henning Date: Mon, 7 Jul 2025 22:50:21 +0000 Subject: [PATCH 2/3] fix: Improve test robustness. --- test/webdriverio/test/basic_test.ts | 4 +++- test/webdriverio/test/test_setup.ts | 5 +++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/test/webdriverio/test/basic_test.ts b/test/webdriverio/test/basic_test.ts index c7666e8e..04aba7a2 100644 --- a/test/webdriverio/test/basic_test.ts +++ b/test/webdriverio/test/basic_test.ts @@ -405,8 +405,10 @@ suite('Keyboard navigation on Fields', function () { await focusOnBlock(this.browser, 'logic_boolean_1'); // Select the field. await keyRight(this.browser); - // Open the field's editor. + // Open the field's editor and select the first option to give a child of + // the editor focus. await sendKeyAndWait(this.browser, Key.Enter); + await keyDown(this.browser); // Exit the editor. await sendKeyAndWait(this.browser, Key.Escape); diff --git a/test/webdriverio/test/test_setup.ts b/test/webdriverio/test/test_setup.ts index cb1974e1..da78aded 100644 --- a/test/webdriverio/test/test_setup.ts +++ b/test/webdriverio/test/test_setup.ts @@ -398,8 +398,9 @@ export async function getFocusedFieldName( browser: WebdriverIO.Browser, ): Promise { return await browser.execute(() => { - const field = Blockly.getFocusManager().getFocusedNode() as Blockly.Field; - return field.name; + const field = + Blockly.getFocusManager().getFocusedNode() as Blockly.Field | null; + return field?.name; }); } From cc3bf49849f0b0b71bab889487676c09d0fd7c56 Mon Sep 17 00:00:00 2001 From: Ben Henning Date: Mon, 7 Jul 2025 22:57:48 +0000 Subject: [PATCH 3/3] chore: Empty commit to re-trigger CI.