From d5f3d157264fceb173befcd3ea985e9a05569f9c Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Fri, 11 Jul 2025 10:54:19 -0700 Subject: [PATCH 01/57] feat: Add support for keyboard navigation to/from block comments. (#9227) * refactor: Update `TextInputBubble` to use `CommentEditor` for text editing. * feat: Designate `Bubble` as implementing `IFocusableNode`. * feat: Dismiss focused bubbles on Escape. * feat: Add support for keyboard navigation to block comments. * fix: Scroll comment editors rather than zooming the workspace. * chore: Add param to docstring. --- core/bubbles/bubble.ts | 33 ++++- core/bubbles/textinput_bubble.ts | 120 +++++------------- core/comments/comment_editor.ts | 6 + core/comments/rendered_workspace_comment.ts | 9 -- core/icons/comment_icon.ts | 6 +- .../block_comment_navigation_policy.ts | 76 +++++++++++ .../comment_editor_navigation_policy.ts | 54 ++++++++ core/keyboard_nav/icon_navigation_policy.ts | 7 + core/navigator.ts | 4 + core/workspace_svg.ts | 6 + 10 files changed, 221 insertions(+), 100 deletions(-) create mode 100644 core/keyboard_nav/block_comment_navigation_policy.ts create mode 100644 core/keyboard_nav/comment_editor_navigation_policy.ts diff --git a/core/bubbles/bubble.ts b/core/bubbles/bubble.ts index 20e730abb18..c42e602544e 100644 --- a/core/bubbles/bubble.ts +++ b/core/bubbles/bubble.ts @@ -9,7 +9,9 @@ import * as common from '../common.js'; import {BubbleDragStrategy} from '../dragging/bubble_drag_strategy.js'; import {getFocusManager} from '../focus_manager.js'; import {IBubble} from '../interfaces/i_bubble.js'; +import type {IFocusableNode} from '../interfaces/i_focusable_node.js'; import type {IFocusableTree} from '../interfaces/i_focusable_tree.js'; +import type {IHasBubble} from '../interfaces/i_has_bubble.js'; import {ISelectable} from '../interfaces/i_selectable.js'; import {ContainerRegion} from '../metrics_manager.js'; import {Scrollbar} from '../scrollbar.js'; @@ -27,7 +29,7 @@ import {WorkspaceSvg} from '../workspace_svg.js'; * bubble, where it has a "tail" that points to the block, and a "head" that * displays arbitrary svg elements. */ -export abstract class Bubble implements IBubble, ISelectable { +export abstract class Bubble implements IBubble, ISelectable, IFocusableNode { /** The width of the border around the bubble. */ static readonly BORDER_WIDTH = 6; @@ -100,12 +102,14 @@ export abstract class Bubble implements IBubble, ISelectable { * element that's represented by this bubble (as a focusable node). This * element will have its ID overwritten. If not provided, the focusable * element of this node will default to the bubble's SVG root. + * @param owner The object responsible for hosting/spawning this bubble. */ constructor( public readonly workspace: WorkspaceSvg, protected anchor: Coordinate, protected ownerRect?: Rect, overriddenFocusableElement?: SVGElement | HTMLElement, + protected owner?: IHasBubble & IFocusableNode, ) { this.id = idGenerator.getNextUniqueId(); this.svgRoot = dom.createSvgElement( @@ -145,6 +149,13 @@ export abstract class Bubble implements IBubble, ISelectable { this, this.onMouseDown, ); + + browserEvents.conditionalBind( + this.focusableElement, + 'keydown', + this, + this.onKeyDown, + ); } /** Dispose of this bubble. */ @@ -229,6 +240,19 @@ export abstract class Bubble implements IBubble, ISelectable { getFocusManager().focusNode(this); } + /** + * Handles key events when this bubble is focused. By default, closes the + * bubble on Escape. + * + * @param e The keyboard event to handle. + */ + protected onKeyDown(e: KeyboardEvent) { + if (e.key === 'Escape' && this.owner) { + this.owner.setBubbleVisible(false); + getFocusManager().focusNode(this.owner); + } + } + /** Positions the bubble relative to its anchor. Does not render its tail. */ protected positionRelativeToAnchor() { let left = this.anchor.x; @@ -694,4 +718,11 @@ export abstract class Bubble implements IBubble, ISelectable { canBeFocused(): boolean { return true; } + + /** + * Returns the object that owns/hosts this bubble, if any. + */ + getOwner(): (IHasBubble & IFocusableNode) | undefined { + return this.owner; + } } diff --git a/core/bubbles/textinput_bubble.ts b/core/bubbles/textinput_bubble.ts index 7479c06cfc5..0bad5fabce6 100644 --- a/core/bubbles/textinput_bubble.ts +++ b/core/bubbles/textinput_bubble.ts @@ -4,7 +4,11 @@ * SPDX-License-Identifier: Apache-2.0 */ +import {CommentEditor} from '../comments/comment_editor.js'; import * as Css from '../css.js'; +import {getFocusManager} from '../focus_manager.js'; +import type {IFocusableNode} from '../interfaces/i_focusable_node.js'; +import type {IHasBubble} from '../interfaces/i_has_bubble.js'; import * as touch from '../touch.js'; import {browserEvents} from '../utils.js'; import {Coordinate} from '../utils/coordinate.js'; @@ -21,12 +25,6 @@ import {Bubble} from './bubble.js'; * Used by the comment icon. */ export class TextInputBubble extends Bubble { - /** The root of the elements specific to the text element. */ - private inputRoot: SVGForeignObjectElement; - - /** The text input area element. */ - private textArea: HTMLTextAreaElement; - /** The group containing the lines indicating the bubble is resizable. */ private resizeGroup: SVGGElement; @@ -42,18 +40,12 @@ export class TextInputBubble extends Bubble { */ private resizePointerMoveListener: browserEvents.Data | null = null; - /** Functions listening for changes to the text of this bubble. */ - private textChangeListeners: (() => void)[] = []; - /** Functions listening for changes to the size of this bubble. */ private sizeChangeListeners: (() => void)[] = []; /** Functions listening for changes to the location of this bubble. */ private locationChangeListeners: (() => void)[] = []; - /** The text of this bubble. */ - private text = ''; - /** The default size of this bubble, including borders. */ private readonly DEFAULT_SIZE = new Size( 160 + Bubble.DOUBLE_BORDER, @@ -68,46 +60,47 @@ export class TextInputBubble extends Bubble { private editable = true; + /** View responsible for supporting text editing. */ + private editor: CommentEditor; + /** * @param workspace The workspace this bubble belongs to. * @param anchor The anchor location of the thing this bubble is attached to. * The tail of the bubble will point to this location. * @param ownerRect An optional rect we don't want the bubble to overlap with * when automatically positioning. + * @param owner The object that owns/hosts this bubble. */ constructor( public readonly workspace: WorkspaceSvg, protected anchor: Coordinate, protected ownerRect?: Rect, + protected owner?: IHasBubble & IFocusableNode, ) { - super(workspace, anchor, ownerRect, TextInputBubble.createTextArea()); + super(workspace, anchor, ownerRect, undefined, owner); dom.addClass(this.svgRoot, 'blocklyTextInputBubble'); - this.textArea = this.getFocusableElement() as HTMLTextAreaElement; - this.inputRoot = this.createEditor(this.contentContainer, this.textArea); + this.editor = new CommentEditor(workspace, this.id, () => { + getFocusManager().focusNode(this); + }); + this.contentContainer.appendChild(this.editor.getDom()); this.resizeGroup = this.createResizeHandle(this.svgRoot, workspace); this.setSize(this.DEFAULT_SIZE, true); } /** @returns the text of this bubble. */ getText(): string { - return this.text; + return this.editor.getText(); } /** Sets the text of this bubble. Calls change listeners. */ setText(text: string) { - this.text = text; - this.textArea.value = text; - this.onTextChange(); + this.editor.setText(text); } /** Sets whether or not the text in the bubble is editable. */ setEditable(editable: boolean) { this.editable = editable; - if (this.editable) { - this.textArea.removeAttribute('readonly'); - } else { - this.textArea.setAttribute('readonly', ''); - } + this.editor.setEditable(editable); } /** Returns whether or not the text in the bubble is editable. */ @@ -117,7 +110,7 @@ export class TextInputBubble extends Bubble { /** Adds a change listener to be notified when this bubble's text changes. */ addTextChangeListener(listener: () => void) { - this.textChangeListeners.push(listener); + this.editor.addTextChangeListener(listener); } /** Adds a change listener to be notified when this bubble's size changes. */ @@ -130,58 +123,6 @@ export class TextInputBubble extends Bubble { this.locationChangeListeners.push(listener); } - /** Creates and returns the editable text area for this bubble's editor. */ - private static createTextArea(): HTMLTextAreaElement { - const textArea = document.createElementNS( - dom.HTML_NS, - 'textarea', - ) as HTMLTextAreaElement; - textArea.className = 'blocklyTextarea blocklyText'; - return textArea; - } - - /** Creates and returns the UI container element for this bubble's editor. */ - private createEditor( - container: SVGGElement, - textArea: HTMLTextAreaElement, - ): SVGForeignObjectElement { - const inputRoot = dom.createSvgElement( - Svg.FOREIGNOBJECT, - { - 'x': Bubble.BORDER_WIDTH, - 'y': Bubble.BORDER_WIDTH, - }, - container, - ); - - const body = document.createElementNS(dom.HTML_NS, 'body'); - body.setAttribute('xmlns', dom.HTML_NS); - body.className = 'blocklyMinimalBody'; - - textArea.setAttribute('dir', this.workspace.RTL ? 'RTL' : 'LTR'); - body.appendChild(textArea); - inputRoot.appendChild(body); - - this.bindTextAreaEvents(textArea); - - return inputRoot; - } - - /** Binds events to the text area element. */ - private bindTextAreaEvents(textArea: HTMLTextAreaElement) { - // Don't zoom with mousewheel; let it scroll instead. - browserEvents.conditionalBind(textArea, 'wheel', this, (e: Event) => { - e.stopPropagation(); - }); - // Don't let the pointerdown event get to the workspace. - browserEvents.conditionalBind(textArea, 'pointerdown', this, (e: Event) => { - e.stopPropagation(); - touch.clearTouchIdentifier(); - }); - - browserEvents.conditionalBind(textArea, 'change', this, this.onTextChange); - } - /** Creates the resize handler elements and binds events to them. */ private createResizeHandle( container: SVGGElement, @@ -220,8 +161,12 @@ export class TextInputBubble extends Bubble { const widthMinusBorder = size.width - Bubble.DOUBLE_BORDER; const heightMinusBorder = size.height - Bubble.DOUBLE_BORDER; - this.inputRoot.setAttribute('width', `${widthMinusBorder}`); - this.inputRoot.setAttribute('height', `${heightMinusBorder}`); + this.editor.updateSize( + new Size(widthMinusBorder, heightMinusBorder), + new Size(0, 0), + ); + this.editor.getDom().setAttribute('x', `${Bubble.DOUBLE_BORDER / 2}`); + this.editor.getDom().setAttribute('y', `${Bubble.DOUBLE_BORDER / 2}`); this.resizeGroup.setAttribute('y', `${heightMinusBorder}`); if (this.workspace.RTL) { @@ -312,14 +257,6 @@ export class TextInputBubble extends Bubble { this.onSizeChange(); } - /** Handles a text change event for the text area. Calls event listeners. */ - private onTextChange() { - this.text = this.textArea.value; - for (const listener of this.textChangeListeners) { - listener(); - } - } - /** Handles a size change event for the text area. Calls event listeners. */ private onSizeChange() { for (const listener of this.sizeChangeListeners) { @@ -333,6 +270,15 @@ export class TextInputBubble extends Bubble { listener(); } } + + /** + * Returns the text editor component of this bubble. + * + * @internal + */ + getEditor() { + return this.editor; + } } Css.register(` diff --git a/core/comments/comment_editor.ts b/core/comments/comment_editor.ts index 69dadd884f5..ac1559c4b3d 100644 --- a/core/comments/comment_editor.ts +++ b/core/comments/comment_editor.ts @@ -53,6 +53,7 @@ export class CommentEditor implements IFocusableNode { 'textarea', ) as HTMLTextAreaElement; this.textArea.setAttribute('tabindex', '-1'); + this.textArea.setAttribute('dir', this.workspace.RTL ? 'RTL' : 'LTR'); dom.addClass(this.textArea, 'blocklyCommentText'); dom.addClass(this.textArea, 'blocklyTextarea'); dom.addClass(this.textArea, 'blocklyText'); @@ -86,6 +87,11 @@ export class CommentEditor implements IFocusableNode { }, ); + // Don't zoom with mousewheel; let it scroll instead. + browserEvents.conditionalBind(this.textArea, 'wheel', this, (e: Event) => { + e.stopPropagation(); + }); + // Register listener for keydown events that would finish editing. browserEvents.conditionalBind( this.textArea, diff --git a/core/comments/rendered_workspace_comment.ts b/core/comments/rendered_workspace_comment.ts index 3457e611a7e..c4c1f3d4ec3 100644 --- a/core/comments/rendered_workspace_comment.ts +++ b/core/comments/rendered_workspace_comment.ts @@ -74,15 +74,6 @@ export class RenderedWorkspaceComment this, this.startGesture, ); - // Don't zoom with mousewheel; let it scroll instead. - browserEvents.conditionalBind( - this.view.getSvgRoot(), - 'wheel', - this, - (e: Event) => { - e.stopPropagation(); - }, - ); } /** diff --git a/core/icons/comment_icon.ts b/core/icons/comment_icon.ts index 959eb2500f5..8f5a82c0d15 100644 --- a/core/icons/comment_icon.ts +++ b/core/icons/comment_icon.ts @@ -11,7 +11,6 @@ import type {BlockSvg} from '../block_svg.js'; import {TextInputBubble} from '../bubbles/textinput_bubble.js'; import {EventType} from '../events/type.js'; import * as eventUtils from '../events/utils.js'; -import type {IBubble} from '../interfaces/i_bubble.js'; import type {IHasBubble} from '../interfaces/i_has_bubble.js'; import type {ISerializable} from '../interfaces/i_serializable.js'; import * as renderManagement from '../render_management.js'; @@ -62,7 +61,7 @@ export class CommentIcon extends Icon implements IHasBubble, ISerializable { /** * The visibility of the bubble for this comment. * - * This is used to track what the visibile state /should/ be, not necessarily + * This is used to track what the visible state /should/ be, not necessarily * what it currently /is/. E.g. sometimes this will be true, but the block * hasn't been rendered yet, so the bubble will not currently be visible. */ @@ -340,7 +339,7 @@ export class CommentIcon extends Icon implements IHasBubble, ISerializable { } /** See IHasBubble.getBubble. */ - getBubble(): IBubble | null { + getBubble(): TextInputBubble | null { return this.textInputBubble; } @@ -365,6 +364,7 @@ export class CommentIcon extends Icon implements IHasBubble, ISerializable { this.sourceBlock.workspace as WorkspaceSvg, this.getAnchorLocation(), this.getBubbleOwnerRect(), + this, ); this.textInputBubble.setText(this.getText()); this.textInputBubble.setSize(this.bubbleSize, true); diff --git a/core/keyboard_nav/block_comment_navigation_policy.ts b/core/keyboard_nav/block_comment_navigation_policy.ts new file mode 100644 index 00000000000..f2f1ab7e107 --- /dev/null +++ b/core/keyboard_nav/block_comment_navigation_policy.ts @@ -0,0 +1,76 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import {TextInputBubble} from '../bubbles/textinput_bubble.js'; +import type {IFocusableNode} from '../interfaces/i_focusable_node.js'; +import type {INavigationPolicy} from '../interfaces/i_navigation_policy.js'; + +/** + * Set of rules controlling keyboard navigation from an TextInputBubble. + */ +export class BlockCommentNavigationPolicy + implements INavigationPolicy +{ + /** + * Returns the first child of the given block comment. + * + * @param current The block comment to return the first child of. + * @returns The text editor of the given block comment bubble. + */ + getFirstChild(current: TextInputBubble): IFocusableNode | null { + return current.getEditor(); + } + + /** + * Returns the parent of the given block comment. + * + * @param current The block comment to return the parent of. + * @returns The parent block of the given block comment. + */ + getParent(current: TextInputBubble): IFocusableNode | null { + return current.getOwner() ?? null; + } + + /** + * Returns the next peer node of the given block comment. + * + * @param _current The block comment to find the following element of. + * @returns Null. + */ + getNextSibling(_current: TextInputBubble): IFocusableNode | null { + return null; + } + + /** + * Returns the previous peer node of the given block comment. + * + * @param _current The block comment to find the preceding element of. + * @returns Null. + */ + getPreviousSibling(_current: TextInputBubble): IFocusableNode | null { + return null; + } + + /** + * Returns whether or not the given block comment can be navigated to. + * + * @param current The instance to check for navigability. + * @returns True if the given block comment can be focused. + */ + isNavigable(current: TextInputBubble): boolean { + return current.canBeFocused(); + } + + /** + * Returns whether the given object can be navigated from by this policy. + * + * @param current The object to check if this policy applies to. + * @returns True if the object is an TextInputBubble. + */ + isApplicable(current: any): current is TextInputBubble { + return current instanceof TextInputBubble; + } +} diff --git a/core/keyboard_nav/comment_editor_navigation_policy.ts b/core/keyboard_nav/comment_editor_navigation_policy.ts new file mode 100644 index 00000000000..456df8e97c8 --- /dev/null +++ b/core/keyboard_nav/comment_editor_navigation_policy.ts @@ -0,0 +1,54 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import {CommentEditor} from '../comments/comment_editor.js'; +import type {IFocusableNode} from '../interfaces/i_focusable_node.js'; +import type {INavigationPolicy} from '../interfaces/i_navigation_policy.js'; + +/** + * Set of rules controlling keyboard navigation from a comment editor. + * This is a no-op placeholder (other than isNavigable/isApplicable) since + * comment editors handle their own navigation when editing ends. + */ +export class CommentEditorNavigationPolicy + implements INavigationPolicy +{ + getFirstChild(_current: CommentEditor): IFocusableNode | null { + return null; + } + + getParent(_current: CommentEditor): IFocusableNode | null { + return null; + } + + getNextSibling(_current: CommentEditor): IFocusableNode | null { + return null; + } + + getPreviousSibling(_current: CommentEditor): IFocusableNode | null { + return null; + } + + /** + * Returns whether or not the given comment editor can be navigated to. + * + * @param current The instance to check for navigability. + * @returns False. + */ + isNavigable(current: CommentEditor): boolean { + return current.canBeFocused(); + } + + /** + * Returns whether the given object can be navigated from by this policy. + * + * @param current The object to check if this policy applies to. + * @returns True if the object is a CommentEditor. + */ + isApplicable(current: any): current is CommentEditor { + return current instanceof CommentEditor; + } +} diff --git a/core/keyboard_nav/icon_navigation_policy.ts b/core/keyboard_nav/icon_navigation_policy.ts index 70631ce81af..112239d0655 100644 --- a/core/keyboard_nav/icon_navigation_policy.ts +++ b/core/keyboard_nav/icon_navigation_policy.ts @@ -6,6 +6,7 @@ import {BlockSvg} from '../block_svg.js'; import {getFocusManager} from '../focus_manager.js'; +import {CommentIcon} from '../icons/comment_icon.js'; import {Icon} from '../icons/icon.js'; import {MutatorIcon} from '../icons/mutator_icon.js'; import type {IFocusableNode} from '../interfaces/i_focusable_node.js'; @@ -29,6 +30,12 @@ export class IconNavigationPolicy implements INavigationPolicy { getFocusManager().getFocusedNode() === current ) { return current.getBubble()?.getWorkspace() ?? null; + } else if ( + current instanceof CommentIcon && + current.bubbleIsVisible() && + getFocusManager().getFocusedNode() === current + ) { + return current.getBubble()?.getEditor() ?? null; } return null; diff --git a/core/navigator.ts b/core/navigator.ts index 2f095f6f962..9c7c22f5987 100644 --- a/core/navigator.ts +++ b/core/navigator.ts @@ -6,8 +6,10 @@ import type {IFocusableNode} from './interfaces/i_focusable_node.js'; import type {INavigationPolicy} from './interfaces/i_navigation_policy.js'; +import {BlockCommentNavigationPolicy} from './keyboard_nav/block_comment_navigation_policy.js'; import {BlockNavigationPolicy} from './keyboard_nav/block_navigation_policy.js'; import {CommentBarButtonNavigationPolicy} from './keyboard_nav/comment_bar_button_navigation_policy.js'; +import {CommentEditorNavigationPolicy} from './keyboard_nav/comment_editor_navigation_policy.js'; import {ConnectionNavigationPolicy} from './keyboard_nav/connection_navigation_policy.js'; import {FieldNavigationPolicy} from './keyboard_nav/field_navigation_policy.js'; import {IconNavigationPolicy} from './keyboard_nav/icon_navigation_policy.js'; @@ -33,6 +35,8 @@ export class Navigator { new IconNavigationPolicy(), new WorkspaceCommentNavigationPolicy(), new CommentBarButtonNavigationPolicy(), + new BlockCommentNavigationPolicy(), + new CommentEditorNavigationPolicy(), ]; /** diff --git a/core/workspace_svg.ts b/core/workspace_svg.ts index d713f11cf43..4180c1099d1 100644 --- a/core/workspace_svg.ts +++ b/core/workspace_svg.ts @@ -22,6 +22,7 @@ import type {Block} from './block.js'; import type {BlockSvg} from './block_svg.js'; import type {BlocklyOptions} from './blockly_options.js'; import * as browserEvents from './browser_events.js'; +import {TextInputBubble} from './bubbles/textinput_bubble.js'; import {COMMENT_COLLAPSE_BAR_BUTTON_FOCUS_IDENTIFIER} from './comments/collapse_comment_bar_button.js'; import {COMMENT_EDITOR_FOCUS_IDENTIFIER} from './comments/comment_editor.js'; import {COMMENT_DELETE_BAR_BUTTON_FOCUS_IDENTIFIER} from './comments/delete_comment_bar_button.js'; @@ -2868,6 +2869,11 @@ export class WorkspaceSvg bubble.getFocusableElement().id === id ) { return bubble; + } else if ( + bubble instanceof TextInputBubble && + bubble.getEditor().getFocusableElement().id === id + ) { + return bubble.getEditor(); } } } From 2c6c2e1a9d3addd72c5c5cc697f7bd246d6cee1e Mon Sep 17 00:00:00 2001 From: Erik Pasternak Date: Fri, 11 Jul 2025 13:50:22 -0700 Subject: [PATCH 02/57] fix: Fix toolbox categories tests (Almost) This fixes the the toolbox categories tests except for dragging out the four empty statement blocks, which is fixed by https://github.com/google/blockly-samples/pull/2580 Once the latest version of samples is published the toolbox suite can be re-enabled. --- tests/browser/test/toolbox_drag_test.mjs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/browser/test/toolbox_drag_test.mjs b/tests/browser/test/toolbox_drag_test.mjs index 32c20140692..d2a7ecce1bd 100644 --- a/tests/browser/test/toolbox_drag_test.mjs +++ b/tests/browser/test/toolbox_drag_test.mjs @@ -11,7 +11,7 @@ import * as chai from 'chai'; import {Key} from 'webdriverio'; import { - dragBlockTypeFromFlyout, + getBlockTypeFromCategory, getCategory, PAUSE_TIME, screenDirection, @@ -148,7 +148,12 @@ async function openCategories(browser, categoryList, directionMultiplier) { continue; } const blockType = await getNthBlockType(browser, categoryName, i); - dragBlockTypeFromFlyout(browser, categoryName, blockType, 50, 20); + const blockElem = await getBlockTypeFromCategory( + browser, + categoryName, + blockType, + ); + await blockElem.dragAndDrop({x: 50 * directionMultiplier, y: 20}); await browser.pause(PAUSE_TIME); // Should be one top level block on the workspace. const topBlockCount = await browser.execute(() => { From 802d3f887dd9e7559f9116b4ba6e42dd805dd168 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Jul 2025 08:35:50 -0700 Subject: [PATCH 03/57] chore(deps): bump google-closure-compiler (#9233) Bumps [google-closure-compiler](https://github.com/google/closure-compiler-npm) from 20250625.0.0 to 20250709.0.0. - [Release notes](https://github.com/google/closure-compiler-npm/releases) - [Commits](https://github.com/google/closure-compiler-npm/compare/v20250625.0.0...v20250709.0.0) --- updated-dependencies: - dependency-name: google-closure-compiler dependency-version: 20250709.0.0 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 56 ++++++++++++++++++++++++++--------------------- package.json | 2 +- 2 files changed, 32 insertions(+), 26 deletions(-) diff --git a/package-lock.json b/package-lock.json index 21178897ded..6a60c9a36a4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,7 +30,7 @@ "eslint-plugin-prettier": "^5.2.1", "glob": "^11.0.1", "globals": "^16.0.0", - "google-closure-compiler": "^20250625.0.0", + "google-closure-compiler": "^20250709.0.0", "gulp": "^5.0.0", "gulp-concat": "^2.6.1", "gulp-gzip": "^1.4.2", @@ -4949,13 +4949,14 @@ } }, "node_modules/google-closure-compiler": { - "version": "20250625.0.0", - "resolved": "https://registry.npmjs.org/google-closure-compiler/-/google-closure-compiler-20250625.0.0.tgz", - "integrity": "sha512-FQ6yKCRYwo4493Rq6lZrxpmWuJGZuuSruCdtArptkoThadzw4TM0YvQJvwRYnQDUpjj6/x7G14l2n/+8G39AIA==", + "version": "20250709.0.0", + "resolved": "https://registry.npmjs.org/google-closure-compiler/-/google-closure-compiler-20250709.0.0.tgz", + "integrity": "sha512-FUdjG7vri7Pi/iswJj1bFcE3cYOcGLnez2nKaEK8qSailRFQlnp8j9vuT60EOU8FLzckEPI0Sf882Q7vJPilFg==", "dev": true, + "license": "Apache-2.0", "dependencies": { "chalk": "5.x", - "google-closure-compiler-java": "^20250625.0.0", + "google-closure-compiler-java": "^20250709.0.0", "minimist": "1.x", "vinyl": "3.x", "vinyl-sourcemaps-apply": "^0.2.0" @@ -4967,67 +4968,72 @@ "node": ">=18" }, "optionalDependencies": { - "google-closure-compiler-linux": "^20250625.0.0", - "google-closure-compiler-linux-arm64": "^20250625.0.0", - "google-closure-compiler-macos": "^20250625.0.0", - "google-closure-compiler-windows": "^20250625.0.0" + "google-closure-compiler-linux": "^20250709.0.0", + "google-closure-compiler-linux-arm64": "^20250709.0.0", + "google-closure-compiler-macos": "^20250709.0.0", + "google-closure-compiler-windows": "^20250709.0.0" } }, "node_modules/google-closure-compiler-java": { - "version": "20250625.0.0", - "resolved": "https://registry.npmjs.org/google-closure-compiler-java/-/google-closure-compiler-java-20250625.0.0.tgz", - "integrity": "sha512-T916Kvb7JYaIiH9spiJXVKeualLV7PO/KXOJzMhLrW4M6etfvr3s2cTqlhUk+BrxvgxqWBWFbMDRUZbVGPnBaw==", - "dev": true + "version": "20250709.0.0", + "resolved": "https://registry.npmjs.org/google-closure-compiler-java/-/google-closure-compiler-java-20250709.0.0.tgz", + "integrity": "sha512-gyriPJ8nYxYVa5wqeMJZsOdFoDDcHSmGHG9VNYjQrcdIOWyxW9Ggcb2gtrI/MEa54CLoRbzUJ12ELO1mzePMlQ==", + "dev": true, + "license": "Apache-2.0" }, "node_modules/google-closure-compiler-linux": { - "version": "20250625.0.0", - "resolved": "https://registry.npmjs.org/google-closure-compiler-linux/-/google-closure-compiler-linux-20250625.0.0.tgz", - "integrity": "sha512-2cOYLfG7RF49FnGG+yBGlEndE0es8D7+YIGgF8KnGIkxrfiZhOTyQftFx4z48TZ1Be/1JtM2eNXbD2fuR9nJdA==", + "version": "20250709.0.0", + "resolved": "https://registry.npmjs.org/google-closure-compiler-linux/-/google-closure-compiler-linux-20250709.0.0.tgz", + "integrity": "sha512-kpl9W+696vnGzpa/ewfwpsRR3t42g3CDQ5hFjQAitxtZpnejU7ik94+O8D+56049zS2O85LdWRDCbckvzEXw+w==", "cpu": [ "x32", "x64" ], "dev": true, + "license": "Apache-2.0", "optional": true, "os": [ "linux" ] }, "node_modules/google-closure-compiler-linux-arm64": { - "version": "20250625.0.0", - "resolved": "https://registry.npmjs.org/google-closure-compiler-linux-arm64/-/google-closure-compiler-linux-arm64-20250625.0.0.tgz", - "integrity": "sha512-2vKY8UpL03CFe+k1qFma/HnUZnTM3V3K5ukxmk/Xwt3D7CTwn/039zA3AjxsGW5vLp4guVyLtqbS711KeGpLNA==", + "version": "20250709.0.0", + "resolved": "https://registry.npmjs.org/google-closure-compiler-linux-arm64/-/google-closure-compiler-linux-arm64-20250709.0.0.tgz", + "integrity": "sha512-3mLAD9JpAM0StUb2VTOw4L/rIxksTO7lOfuI0+OyexQfLIRLM8M9jeUgrOAPbmgDsyYZ8Q3pHX2qcnURexZsrw==", "cpu": [ "arm64" ], "dev": true, + "license": "Apache-2.0", "optional": true, "os": [ "linux" ] }, "node_modules/google-closure-compiler-macos": { - "version": "20250625.0.0", - "resolved": "https://registry.npmjs.org/google-closure-compiler-macos/-/google-closure-compiler-macos-20250625.0.0.tgz", - "integrity": "sha512-/S3d5/oKKw2pEu42Bn+fnoKR0cAjlhOQP1IM0D1aDqNS+jMUXo4bV7RSVB+NSVL65XxIVQOqbnkD5Cfoe8lbrw==", + "version": "20250709.0.0", + "resolved": "https://registry.npmjs.org/google-closure-compiler-macos/-/google-closure-compiler-macos-20250709.0.0.tgz", + "integrity": "sha512-2/MXSVgM+HmnzwbyWdfY2ZVjKgK8LFtCKhsQQhsSV/f2jnrHcuG9+RkzLrzQsO1zPpHaLcXAkizf4AUpCfuzBA==", "cpu": [ "arm64" ], "dev": true, + "license": "Apache-2.0", "optional": true, "os": [ "darwin" ] }, "node_modules/google-closure-compiler-windows": { - "version": "20250625.0.0", - "resolved": "https://registry.npmjs.org/google-closure-compiler-windows/-/google-closure-compiler-windows-20250625.0.0.tgz", - "integrity": "sha512-YBNRFTSuWXDJad1pJ1SPjPFpgImrQr7XeW1D9YrPCv1T5cfM8vy01jFkZIDuUha38kHsPvk7kG3rkYYrJpD8+Q==", + "version": "20250709.0.0", + "resolved": "https://registry.npmjs.org/google-closure-compiler-windows/-/google-closure-compiler-windows-20250709.0.0.tgz", + "integrity": "sha512-ZnmgRzx0qIVQu0zw7ZTJQz3tMFVhwzeODZfXRnYDLeNkJA7IBaWsNHTALA7pUcgPM+YDDr4ihQOexMc0u4s7LQ==", "cpu": [ "x32", "x64" ], "dev": true, + "license": "Apache-2.0", "optional": true, "os": [ "win32" diff --git a/package.json b/package.json index d464ae5f171..4df4f9586d1 100644 --- a/package.json +++ b/package.json @@ -117,7 +117,7 @@ "eslint-plugin-prettier": "^5.2.1", "glob": "^11.0.1", "globals": "^16.0.0", - "google-closure-compiler": "^20250625.0.0", + "google-closure-compiler": "^20250709.0.0", "gulp": "^5.0.0", "gulp-concat": "^2.6.1", "gulp-gzip": "^1.4.2", From 52634e4dec56de31c790d3e718cebffef147f27e Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Mon, 14 Jul 2025 08:55:13 -0700 Subject: [PATCH 04/57] fix: Focus the first element in flyouts. (#9228) * fix: Focus the first element in flyouts. * refactor: Adjust retrieval of flyout elements. --- core/workspace_svg.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/core/workspace_svg.ts b/core/workspace_svg.ts index 4180c1099d1..b666dc97a5d 100644 --- a/core/workspace_svg.ts +++ b/core/workspace_svg.ts @@ -2727,6 +2727,19 @@ export class WorkspaceSvg previousNode: IFocusableNode | null, ): IFocusableNode | null { if (!previousNode) { + const flyout = this.targetWorkspace?.getFlyout(); + if (this.isFlyout && flyout) { + // Return the first focusable item of the flyout. + return ( + flyout + .getContents() + .find((flyoutItem) => { + const element = flyoutItem.getElement(); + return isFocusableNode(element) && element.canBeFocused(); + }) + ?.getElement() ?? null + ); + } return this.getTopBlocks(true)[0] ?? null; } else return null; } From 9f66f0c59608d82547e06d5f6ec1b5228116b111 Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Mon, 14 Jul 2025 09:53:17 -0700 Subject: [PATCH 05/57] fix: Fire a `VarTypeChange` event when changing a variable's type. (#9236) --- core/variable_map.ts | 10 ++++++++++ tests/mocha/variable_map_test.js | 21 +++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/core/variable_map.ts b/core/variable_map.ts index 403893332e5..3dd4bf5475d 100644 --- a/core/variable_map.ts +++ b/core/variable_map.ts @@ -109,6 +109,9 @@ export class VariableMap variable: IVariableModel, newType: string, ): IVariableModel { + const oldType = variable.getType(); + if (oldType === newType) return variable; + this.variableMap.get(variable.getType())?.delete(variable.getId()); variable.setType(newType); const newTypeVariables = @@ -118,6 +121,13 @@ export class VariableMap if (!this.variableMap.has(newType)) { this.variableMap.set(newType, newTypeVariables); } + eventUtils.fire( + new (eventUtils.get(EventType.VAR_TYPE_CHANGE))( + variable, + oldType, + newType, + ), + ); return variable; } diff --git a/tests/mocha/variable_map_test.js b/tests/mocha/variable_map_test.js index c02887ceaca..2d6cee0b94a 100644 --- a/tests/mocha/variable_map_test.js +++ b/tests/mocha/variable_map_test.js @@ -505,5 +505,26 @@ suite('Variable Map', function () { }); }); }); + + suite('variable type change events', function () { + test('are fired when a variable has its type changed', function () { + const variable = this.variableMap.createVariable( + 'name1', + 'type1', + 'id1', + ); + this.variableMap.changeVariableType(variable, 'type2'); + assertEventFired( + this.eventSpy, + Blockly.Events.VarTypeChange, + { + oldType: 'type1', + newType: 'type2', + varId: 'id1', + }, + this.workspace.id, + ); + }); + }); }); }); From 55bd1aff795e59ef56748735b3f45cb882be2433 Mon Sep 17 00:00:00 2001 From: Erik Pasternak Date: Mon, 14 Jul 2025 10:09:51 -0700 Subject: [PATCH 06/57] Enable categories test --- tests/browser/test/toolbox_drag_test.mjs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/browser/test/toolbox_drag_test.mjs b/tests/browser/test/toolbox_drag_test.mjs index d2a7ecce1bd..66b74d9e2c5 100644 --- a/tests/browser/test/toolbox_drag_test.mjs +++ b/tests/browser/test/toolbox_drag_test.mjs @@ -179,9 +179,9 @@ async function openCategories(browser, categoryList, directionMultiplier) { chai.assert.equal(failureCount, 0); } -// TODO (#9217) These take too long to run and are very flakey. Need to find a -// better way to test whatever this is trying to test. -suite.skip('Open toolbox categories', function () { +// TODO (#9217) These take too long to run and are very flakey. Need to pull +// these out into their own test runner. +suite('Open toolbox categories', function () { this.timeout(0); test('opening every toolbox category in the category toolbox in LTR', async function () { From ec01df4adaa407301b4eac5d4cd9c0526c677a5c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Jul 2025 18:11:03 +0100 Subject: [PATCH 07/57] chore(deps): bump @microsoft/api-documenter from 7.26.26 to 7.26.29 (#9234) Bumps [@microsoft/api-documenter](https://github.com/microsoft/rushstack/tree/HEAD/apps/api-documenter) from 7.26.26 to 7.26.29. - [Changelog](https://github.com/microsoft/rushstack/blob/main/apps/api-documenter/CHANGELOG.md) - [Commits](https://github.com/microsoft/rushstack/commits/@microsoft/api-documenter_v7.26.29/apps/api-documenter) --- updated-dependencies: - dependency-name: "@microsoft/api-documenter" dependency-version: 7.26.29 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6a60c9a36a4..1bdb22c7597 100644 --- a/package-lock.json +++ b/package-lock.json @@ -912,9 +912,9 @@ } }, "node_modules/@microsoft/api-documenter": { - "version": "7.26.26", - "resolved": "https://registry.npmjs.org/@microsoft/api-documenter/-/api-documenter-7.26.26.tgz", - "integrity": "sha512-085FwdwQcXGvwtMJFajwhu5eZOQ3PXsyLIoq3WXAQr/7M6Vn59GMGjuB/+lIXqmWKkxzeFAX5f9sKqr9X7zI3g==", + "version": "7.26.29", + "resolved": "https://registry.npmjs.org/@microsoft/api-documenter/-/api-documenter-7.26.29.tgz", + "integrity": "sha512-5gqnUCut1BeNmOZIE8hUJbzq3DxFcAyXL12oF6aFVtTDF8WiVs/J1HtlLYbxeIff6qbI1LfLnr16t+WOm9UVJw==", "dev": true, "license": "MIT", "dependencies": { From 7479c2b5c74b984dbf3d8ed638a7d4d03560d1c2 Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Mon, 14 Jul 2025 10:22:09 -0700 Subject: [PATCH 08/57] fix: Fix order of arguments in `IVariableMap.createVariable()`. (#9231) --- core/interfaces/i_variable_map.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/interfaces/i_variable_map.ts b/core/interfaces/i_variable_map.ts index 6c21aa8e0cb..22b4eda9012 100644 --- a/core/interfaces/i_variable_map.ts +++ b/core/interfaces/i_variable_map.ts @@ -43,7 +43,7 @@ export interface IVariableMap> { * Creates a new variable with the given name. If ID is not specified, the * variable map should create one. Returns the new variable. */ - createVariable(name: string, id?: string, type?: string | null): T; + createVariable(name: string, type?: string, id?: string | null): T; /* Adds a variable to this variable map. */ addVariable(variable: T): void; From c1c7ddb2938183b9b0a62fe4f618b3976c9ff414 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Jul 2025 11:17:27 -0700 Subject: [PATCH 09/57] chore(deps): bump chai from 5.2.0 to 5.2.1 (#9235) Bumps [chai](https://github.com/chaijs/chai) from 5.2.0 to 5.2.1. - [Release notes](https://github.com/chaijs/chai/releases) - [Changelog](https://github.com/chaijs/chai/blob/main/History.md) - [Commits](https://github.com/chaijs/chai/compare/v5.2.0...v5.2.1) --- updated-dependencies: - dependency-name: chai dependency-version: 5.2.1 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1bdb22c7597..0db79293f64 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2766,9 +2766,9 @@ } }, "node_modules/chai": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz", - "integrity": "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.1.tgz", + "integrity": "sha512-5nFxhUrX0PqtyogoYOA8IPswy5sZFTOsBFl/9bNsmDLgsxYTzSZQJDPppDnZPTQbzSEm0hqGjWPzRemQCYbD6A==", "dev": true, "license": "MIT", "dependencies": { @@ -2779,7 +2779,7 @@ "pathval": "^2.0.0" }, "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/chalk": { From 908712e19d2d028df9352f55be9046a47eeae303 Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Tue, 15 Jul 2025 11:06:27 -0700 Subject: [PATCH 10/57] fix: Refer to correct replacement method in deprecation warning. (#9237) --- core/workspace.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/workspace.ts b/core/workspace.ts index 5f205193912..88745f4205c 100644 --- a/core/workspace.ts +++ b/core/workspace.ts @@ -469,7 +469,7 @@ export class Workspace { 'Blockly.Workspace.getVariableUsesById', 'v12', 'v13', - 'Blockly.Workspace.getVariableMap().getVariableUsesById', + 'Blockly.Variables.getVariableUsesById', ); return getVariableUsesById(this, id); } From 3c7add57eed7e2564a69f650d8381db3d1f2eadd Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Fri, 18 Jul 2025 14:27:49 -0700 Subject: [PATCH 11/57] fix: Make non-autoclosing flyouts stay open. (#9245) * chore: Add tests for toolbox/flyout/focus autoclose behavior. * fix: Don't force-close non-autoclosing flyouts. --- core/toolbox/toolbox.ts | 10 ++------ core/workspace_svg.ts | 4 +-- tests/mocha/toolbox_test.js | 51 +++++++++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 11 deletions(-) diff --git a/core/toolbox/toolbox.ts b/core/toolbox/toolbox.ts index 4979fdfa40f..f34034d3399 100644 --- a/core/toolbox/toolbox.ts +++ b/core/toolbox/toolbox.ts @@ -22,10 +22,7 @@ import '../events/events_toolbox_item_select.js'; import {EventType} from '../events/type.js'; import * as eventUtils from '../events/utils.js'; import {getFocusManager} from '../focus_manager.js'; -import { - isAutoHideable, - type IAutoHideable, -} from '../interfaces/i_autohideable.js'; +import {type IAutoHideable} from '../interfaces/i_autohideable.js'; import type {ICollapsibleToolboxItem} from '../interfaces/i_collapsible_toolbox_item.js'; import {isDeletable} from '../interfaces/i_deletable.js'; import type {IDraggable} from '../interfaces/i_draggable.js'; @@ -1150,10 +1147,7 @@ export class Toolbox // If navigating to anything other than the toolbox's flyout then clear the // selection so that the toolbox's flyout can automatically close. if (!nextTree || nextTree !== this.flyout?.getWorkspace()) { - this.clearSelection(); - if (this.flyout && isAutoHideable(this.flyout)) { - this.flyout.autoHide(false); - } + this.autoHide(false); } } } diff --git a/core/workspace_svg.ts b/core/workspace_svg.ts index b666dc97a5d..6c6b5930110 100644 --- a/core/workspace_svg.ts +++ b/core/workspace_svg.ts @@ -2908,11 +2908,9 @@ export class WorkspaceSvg // Only hide the flyout if the flyout's workspace is losing focus and that // focus isn't returning to the flyout itself, the toolbox, or ephemeral. if (getFocusManager().ephemeralFocusTaken()) return; - const flyout = this.targetWorkspace.getFlyout(); const toolbox = this.targetWorkspace.getToolbox(); if (toolbox && nextTree === toolbox) return; - if (toolbox) toolbox.clearSelection(); - if (flyout && isAutoHideable(flyout)) flyout.autoHide(false); + if (isAutoHideable(toolbox)) toolbox.autoHide(false); } } diff --git a/tests/mocha/toolbox_test.js b/tests/mocha/toolbox_test.js index f32319c6779..4e92cd28fd3 100644 --- a/tests/mocha/toolbox_test.js +++ b/tests/mocha/toolbox_test.js @@ -183,6 +183,57 @@ suite('Toolbox', function () { }); }); + suite('focus management', function () { + setup(function () { + this.toolbox = getInjectedToolbox(); + }); + teardown(function () { + this.toolbox.dispose(); + }); + + test('Losing focus hides autoclosing flyout', function () { + // Focus the toolbox and select a category to open the flyout. + const target = this.toolbox.HtmlDiv.querySelector( + '.blocklyToolboxCategory', + ); + Blockly.getFocusManager().focusNode(this.toolbox); + target.dispatchEvent( + new PointerEvent('pointerdown', { + target, + bubbles: true, + }), + ); + assert.isTrue(this.toolbox.getFlyout().isVisible()); + + // Focus the workspace to trigger the toolbox to close the flyout. + Blockly.getFocusManager().focusNode(this.toolbox.getWorkspace()); + assert.isFalse(this.toolbox.getFlyout().isVisible()); + }); + + test('Losing focus does not hide non-autoclosing flyout', function () { + // Make the toolbox's flyout non-autoclosing. + this.toolbox.getFlyout().setAutoClose(false); + + // Focus the toolbox and select a category to open the flyout. + const target = this.toolbox.HtmlDiv.querySelector( + '.blocklyToolboxCategory', + ); + Blockly.getFocusManager().focusNode(this.toolbox); + target.dispatchEvent( + new PointerEvent('pointerdown', { + target, + bubbles: true, + }), + ); + assert.isTrue(this.toolbox.getFlyout().isVisible()); + + // Focus the workspace; this should *not* trigger the toolbox to close the + // flyout, which should remain visible. + Blockly.getFocusManager().focusNode(this.toolbox.getWorkspace()); + assert.isTrue(this.toolbox.getFlyout().isVisible()); + }); + }); + suite('onClick_', function () { setup(function () { this.toolbox = getInjectedToolbox(); From f37e7fede20ed81237f65626cdb1dd20c8f6d5e3 Mon Sep 17 00:00:00 2001 From: Maribeth Moffatt Date: Thu, 24 Jul 2025 12:28:18 -0400 Subject: [PATCH 12/57] chore: fix docs generation script (#9251) * chore: pin api-documenter to a version that uses markdown tables * chore: fix docs generation errors * chore: update patch for api-documenter --- package-lock.json | 346 +++++++++++++++--- package.json | 3 +- ...=> @microsoft+api-documenter+7.22.4.patch} | 25 +- scripts/gulpfiles/docs_tasks.mjs | 4 +- 4 files changed, 306 insertions(+), 72 deletions(-) rename patches/{@microsoft+api-documenter+7.26.26.patch => @microsoft+api-documenter+7.22.4.patch} (85%) diff --git a/package-lock.json b/package-lock.json index 0db79293f64..28f32b01922 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,8 +18,9 @@ "@blockly/theme-modern": "^6.0.3", "@hyperjump/browser": "^1.1.4", "@hyperjump/json-schema": "^1.5.0", - "@microsoft/api-documenter": "^7.22.4", + "@microsoft/api-documenter": "7.22.4", "@microsoft/api-extractor": "^7.29.5", + "ajv": "^8.17.1", "async-done": "^2.0.0", "chai": "^5.1.1", "concurrently": "^9.0.1", @@ -485,6 +486,23 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/@eslint/eslintrc/node_modules/globals": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", @@ -498,6 +516,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, "node_modules/@eslint/js": { "version": "9.30.0", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.30.0.tgz", @@ -912,17 +937,17 @@ } }, "node_modules/@microsoft/api-documenter": { - "version": "7.26.29", - "resolved": "https://registry.npmjs.org/@microsoft/api-documenter/-/api-documenter-7.26.29.tgz", - "integrity": "sha512-5gqnUCut1BeNmOZIE8hUJbzq3DxFcAyXL12oF6aFVtTDF8WiVs/J1HtlLYbxeIff6qbI1LfLnr16t+WOm9UVJw==", + "version": "7.22.4", + "resolved": "https://registry.npmjs.org/@microsoft/api-documenter/-/api-documenter-7.22.4.tgz", + "integrity": "sha512-d4htEhBd8UkFKff/+/nAi/z7rrspm1DanFmsRHLUp4gKMo/8hYDH/IQBWB4r9X/8X72jCv3I++VVWAfichL1rw==", "dev": true, "license": "MIT", "dependencies": { - "@microsoft/api-extractor-model": "7.30.6", - "@microsoft/tsdoc": "~0.15.1", - "@rushstack/node-core-library": "5.13.1", - "@rushstack/terminal": "0.15.3", - "@rushstack/ts-command-line": "5.0.1", + "@microsoft/api-extractor-model": "7.26.8", + "@microsoft/tsdoc": "0.14.2", + "@rushstack/node-core-library": "3.58.0", + "@rushstack/ts-command-line": "4.13.2", + "colors": "~1.2.1", "js-yaml": "~3.13.1", "resolve": "~1.22.1" }, @@ -930,6 +955,106 @@ "api-documenter": "bin/api-documenter" } }, + "node_modules/@microsoft/api-documenter/node_modules/@microsoft/api-extractor-model": { + "version": "7.26.8", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.26.8.tgz", + "integrity": "sha512-ESj3bBJkiMg/8tS0PW4+2rUgTVwOEfy41idTnFgdbVX+O50bN6S99MV6FIPlCZWCnRDcBfwxRXLdAkOQQ0JqGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@microsoft/tsdoc": "0.14.2", + "@microsoft/tsdoc-config": "~0.16.1", + "@rushstack/node-core-library": "3.58.0" + } + }, + "node_modules/@microsoft/api-documenter/node_modules/@microsoft/tsdoc": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.14.2.tgz", + "integrity": "sha512-9b8mPpKrfeGRuhFH5iO1iwCLeIIsV6+H1sRfxbkoGXIyQE2BTsPd9zqSqQJ+pv5sJ/hT5M1zvOFL02MnEezFug==", + "dev": true, + "license": "MIT" + }, + "node_modules/@microsoft/api-documenter/node_modules/@microsoft/tsdoc-config": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc-config/-/tsdoc-config-0.16.2.tgz", + "integrity": "sha512-OGiIzzoBLgWWR0UdRJX98oYO+XKGf7tiK4Zk6tQ/E4IJqGCe7dvkTvgDZV5cFJUzLGDOjeAXrnZoA6QkVySuxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@microsoft/tsdoc": "0.14.2", + "ajv": "~6.12.6", + "jju": "~1.4.0", + "resolve": "~1.19.0" + } + }, + "node_modules/@microsoft/api-documenter/node_modules/@microsoft/tsdoc-config/node_modules/resolve": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz", + "integrity": "sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.1.0", + "path-parse": "^1.0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@microsoft/api-documenter/node_modules/@rushstack/node-core-library": { + "version": "3.58.0", + "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-3.58.0.tgz", + "integrity": "sha512-DHAZ3LTOEq2/EGURznpTJDnB3SNE2CKMDXuviQ6afhru6RykE3QoqXkeyjbpLb5ib5cpIRCPE/wykNe0xmQj3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "colors": "~1.2.1", + "fs-extra": "~7.0.1", + "import-lazy": "~4.0.0", + "jju": "~1.4.0", + "resolve": "~1.22.1", + "semver": "~7.3.0", + "z-schema": "~5.0.2" + }, + "peerDependencies": { + "@types/node": "*" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@microsoft/api-documenter/node_modules/@rushstack/ts-command-line": { + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.13.2.tgz", + "integrity": "sha512-bCU8qoL9HyWiciltfzg7GqdfODUeda/JpI0602kbN5YH22rzTxyqYvv7aRLENCM7XCQ1VRs7nMkEqgJUOU8Sag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/argparse": "1.0.38", + "argparse": "~1.0.9", + "colors": "~1.2.1", + "string-argv": "~0.3.1" + } + }, + "node_modules/@microsoft/api-documenter/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/@microsoft/api-documenter/node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -952,6 +1077,29 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/@microsoft/api-documenter/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@microsoft/api-documenter/node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@microsoft/api-extractor": { "version": "7.52.8", "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.52.8.tgz", @@ -1056,12 +1204,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@microsoft/tsdoc-config/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1236,13 +1378,6 @@ "node": ">=14.14" } }, - "node_modules/@rushstack/node-core-library/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, "node_modules/@rushstack/rig-package": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/@rushstack/rig-package/-/rig-package-0.5.3.tgz", @@ -1955,16 +2090,16 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" }, "funding": { "type": "github", @@ -2002,28 +2137,6 @@ } } }, - "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.14.0.tgz", - "integrity": "sha512-oYs1UUtO97ZO2lJ4bwnWeQW8/zvOIQLGKcvPTsWmvc2SYgBb+upuNS5NxoLaMU4h8Ju3Nbj6Cq8mD2LQoqVKFA==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.3", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.4.1" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, "node_modules/ansi-gray": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", @@ -2979,6 +3092,16 @@ "color-support": "bin.js" } }, + "node_modules/colors": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.2.5.tgz", + "integrity": "sha512-erNRLao/Y3Fv54qUa0LBB+//Uf3YwMUmdJinN20yMXm9zdKKqH9wt7R9IIVZ+K7ShzfpLV/Zg8+VyrBJYB4lpg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, "node_modules/commander": { "version": "9.5.0", "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", @@ -4068,6 +4191,23 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/eslint/node_modules/eslint-visitor-keys": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", @@ -4092,6 +4232,13 @@ "node": ">=10.13.0" } }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, "node_modules/espree": { "version": "10.4.0", "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", @@ -4340,6 +4487,23 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "node_modules/fast-uri": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", + "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/fast-xml-parser": { "version": "4.5.3", "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.3.tgz", @@ -4598,6 +4762,41 @@ "node": ">=12.20.0" } }, + "node_modules/fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/fs-extra/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/fs-extra/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/fs-mkdirp-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-2.0.1.tgz", @@ -6198,9 +6397,9 @@ "dev": true }, "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true, "license": "MIT" }, @@ -6486,6 +6685,14 @@ "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", "dev": true }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -9207,6 +9414,16 @@ "node": ">= 10.13.0" } }, + "node_modules/validator": { + "version": "13.15.15", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.15.tgz", + "integrity": "sha512-BgWVbCI72aIQy937xbawcs+hrVaN/CZ2UwutgaJ36hGqRrLNM+f5LUT/YPRbo8IV/ASeFzXszezV+y2+rq3l8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/value-or-function": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-4.0.0.tgz", @@ -9758,6 +9975,27 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/z-schema": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/z-schema/-/z-schema-5.0.5.tgz", + "integrity": "sha512-D7eujBWkLa3p2sIpJA0d1pr7es+a7m0vFAnZLlCEKq/Ij2k0MLi9Br2UPxoxdYystm5K1yeBGzub0FlYUEWj2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash.get": "^4.4.2", + "lodash.isequal": "^4.5.0", + "validator": "^13.7.0" + }, + "bin": { + "z-schema": "bin/z-schema" + }, + "engines": { + "node": ">=8.0.0" + }, + "optionalDependencies": { + "commander": "^9.4.1" + } + }, "node_modules/zip-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", diff --git a/package.json b/package.json index 4df4f9586d1..dfff0da7fa5 100644 --- a/package.json +++ b/package.json @@ -105,8 +105,9 @@ "@blockly/theme-modern": "^6.0.3", "@hyperjump/browser": "^1.1.4", "@hyperjump/json-schema": "^1.5.0", - "@microsoft/api-documenter": "^7.22.4", + "@microsoft/api-documenter": "7.22.4", "@microsoft/api-extractor": "^7.29.5", + "ajv": "^8.17.1", "async-done": "^2.0.0", "chai": "^5.1.1", "concurrently": "^9.0.1", diff --git a/patches/@microsoft+api-documenter+7.26.26.patch b/patches/@microsoft+api-documenter+7.22.4.patch similarity index 85% rename from patches/@microsoft+api-documenter+7.26.26.patch rename to patches/@microsoft+api-documenter+7.22.4.patch index de8e47c0419..3cc97035da6 100644 --- a/patches/@microsoft+api-documenter+7.26.26.patch +++ b/patches/@microsoft+api-documenter+7.22.4.patch @@ -1,8 +1,8 @@ diff --git a/node_modules/@microsoft/api-documenter/lib/documenters/MarkdownDocumenter.js b/node_modules/@microsoft/api-documenter/lib/documenters/MarkdownDocumenter.js -index 0f4e2ba..3af2014 100644 +index 5284d10..4f8b439 100644 --- a/node_modules/@microsoft/api-documenter/lib/documenters/MarkdownDocumenter.js +++ b/node_modules/@microsoft/api-documenter/lib/documenters/MarkdownDocumenter.js -@@ -893,12 +893,15 @@ class MarkdownDocumenter { +@@ -877,12 +877,14 @@ class MarkdownDocumenter { } _writeBreadcrumb(output, apiItem) { const configuration = this._tsdocConfiguration; @@ -19,28 +19,23 @@ index 0f4e2ba..3af2014 100644 + // linkText: 'Home', + // urlDestination: this._getLinkFilenameForApiItem(this._apiModel) + // })); -+ + let first = true; for (const hierarchyItem of apiItem.getHierarchy()) { switch (hierarchyItem.kind) { case api_extractor_model_1.ApiItemKind.Model: -@@ -908,18 +911,24 @@ class MarkdownDocumenter { +@@ -892,18 +894,23 @@ class MarkdownDocumenter { // this may change in the future. break; default: - output.appendNodesInParagraph([ -- new tsdoc_1.DocPlainText({ -- configuration, -- text: ' > ' -- }), + if (!first) { + // Only print the breadcrumb separator if it's not the first item we're printing. + output.appendNodeInParagraph( -+ new tsdoc_1.DocPlainText({ -+ configuration, -+ text: ' > ' -+ }) -+ ); + new tsdoc_1.DocPlainText({ + configuration, + text: ' > ' +- }), ++ })); + } + first = false; + output.appendNodeInParagraph( @@ -55,7 +50,7 @@ index 0f4e2ba..3af2014 100644 } } } -@@ -992,11 +1001,8 @@ class MarkdownDocumenter { +@@ -968,11 +975,8 @@ class MarkdownDocumenter { // For overloaded methods, add a suffix such as "MyClass.myMethod_2". let qualifiedName = Utilities_1.Utilities.getSafeFilenameForName(hierarchyItem.displayName); if (api_extractor_model_1.ApiParameterListMixin.isBaseClassOf(hierarchyItem)) { @@ -69,7 +64,7 @@ index 0f4e2ba..3af2014 100644 } switch (hierarchyItem.kind) { case api_extractor_model_1.ApiItemKind.Model: -@@ -1007,7 +1013,8 @@ class MarkdownDocumenter { +@@ -983,7 +987,8 @@ class MarkdownDocumenter { baseName = Utilities_1.Utilities.getSafeFilenameForName(node_core_library_1.PackageName.getUnscopedName(hierarchyItem.displayName)); break; default: diff --git a/scripts/gulpfiles/docs_tasks.mjs b/scripts/gulpfiles/docs_tasks.mjs index 63fdbe66536..51abd480f95 100644 --- a/scripts/gulpfiles/docs_tasks.mjs +++ b/scripts/gulpfiles/docs_tasks.mjs @@ -2,8 +2,8 @@ import {execSync} from 'child_process'; import {Extractor} from 'markdown-tables-to-json'; import * as fs from 'fs'; import * as gulp from 'gulp'; -import * as header from 'gulp-header'; -import * as replace from 'gulp-replace'; +import header from 'gulp-header'; +import replace from 'gulp-replace'; const DOCS_DIR = 'docs'; From 0de5b17c8a531f8582b83c436af186c8490c9f00 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 24 Jul 2025 16:29:34 +0000 Subject: [PATCH 13/57] chore(deps): bump @blockly/theme-modern from 6.0.10 to 7.0.1 Bumps [@blockly/theme-modern](https://github.com/google/blockly-samples/tree/HEAD/plugins/theme-modern) from 6.0.10 to 7.0.1. - [Release notes](https://github.com/google/blockly-samples/releases) - [Changelog](https://github.com/google/blockly-samples/blob/master/plugins/theme-modern/CHANGELOG.md) - [Commits](https://github.com/google/blockly-samples/commits/@blockly/theme-modern@7.0.1/plugins/theme-modern) --- updated-dependencies: - dependency-name: "@blockly/theme-modern" dependency-version: 7.0.1 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- package-lock.json | 11 ++++++----- package.json | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 28f32b01922..95e9a39b22e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ "devDependencies": { "@blockly/block-test": "^7.0.1", "@blockly/dev-tools": "^9.0.0", - "@blockly/theme-modern": "^6.0.3", + "@blockly/theme-modern": "^7.0.1", "@hyperjump/browser": "^1.1.4", "@hyperjump/json-schema": "^1.5.0", "@microsoft/api-documenter": "7.22.4", @@ -236,15 +236,16 @@ } }, "node_modules/@blockly/theme-modern": { - "version": "6.0.10", - "resolved": "https://registry.npmjs.org/@blockly/theme-modern/-/theme-modern-6.0.10.tgz", - "integrity": "sha512-xOVf5Vq5ACgbVsaNAKWb5cE0msUfBxj1G1asp0aBmWo1QCr3Yze4rUtFDaNIoeCd8EsRpuWZgBYg74zPL9eAow==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@blockly/theme-modern/-/theme-modern-7.0.1.tgz", + "integrity": "sha512-aMI3OBp8KCbLU1O14FLUlocK7IeMOyiSenlTJ4lwGcBmZntM2OIcx6o89oAIeq6HkmaH7vMlK+/AgqdB3k0y3A==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=8.17.0" }, "peerDependencies": { - "blockly": "^11.0.0" + "blockly": "^12.0.0" } }, "node_modules/@blockly/theme-tritanopia": { diff --git a/package.json b/package.json index dfff0da7fa5..6ae18bd4a55 100644 --- a/package.json +++ b/package.json @@ -102,7 +102,7 @@ "devDependencies": { "@blockly/block-test": "^7.0.1", "@blockly/dev-tools": "^9.0.0", - "@blockly/theme-modern": "^6.0.3", + "@blockly/theme-modern": "^7.0.1", "@hyperjump/browser": "^1.1.4", "@hyperjump/json-schema": "^1.5.0", "@microsoft/api-documenter": "7.22.4", From c9a8221a2d2cd01cb42aa162b7ce2b8418ec1a9c Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Mon, 28 Jul 2025 13:33:52 -0700 Subject: [PATCH 14/57] fix: Fix displaying HTML elements in `FieldDropdown`. (#9258) --- core/field_dropdown.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/core/field_dropdown.ts b/core/field_dropdown.ts index 9c0d7f29269..8b01ccddab1 100644 --- a/core/field_dropdown.ts +++ b/core/field_dropdown.ts @@ -699,25 +699,30 @@ export class FieldDropdown extends Field { prefix?: string; suffix?: string; } { - let hasImages = false; + let hasNonTextContent = false; const trimmedOptions = options.map((option): MenuOption => { - if (option === FieldDropdown.SEPARATOR) return option; + if (option === FieldDropdown.SEPARATOR) { + hasNonTextContent = true; + return option; + } const [label, value] = option; if (typeof label === 'string') { return [parsing.replaceMessageReferences(label), value]; } - hasImages = true; + hasNonTextContent = true; // Copy the image properties so they're not influenced by the original. // NOTE: No need to deep copy since image properties are only 1 level deep. const imageLabel = isImageProperties(label) ? {...label, alt: parsing.replaceMessageReferences(label.alt)} - : {...label}; + : label; return [imageLabel, value]; }); - if (hasImages || options.length < 2) return {options: trimmedOptions}; + if (hasNonTextContent || options.length < 2) { + return {options: trimmedOptions}; + } const stringOptions = trimmedOptions as [string, string][]; const stringLabels = stringOptions.map(([label]) => label); @@ -793,7 +798,7 @@ export class FieldDropdown extends Field { } else if (typeof option[1] !== 'string') { foundError = true; console.error( - `Invalid option[${i}]: Each FieldDropdown option id must be a string. + `Invalid option[${i}]: Each FieldDropdown option id must be a string. Found ${option[1]} in: ${option}`, ); } else if ( @@ -806,7 +811,7 @@ export class FieldDropdown extends Field { ) { foundError = true; console.error( - `Invalid option[${i}]: Each FieldDropdown option must have a string + `Invalid option[${i}]: Each FieldDropdown option must have a string label, image description, or HTML element. Found ${option[0]} in: ${option}`, ); } From c661dd1c946d12a41b2fc14b0d1deb5e3e3115f3 Mon Sep 17 00:00:00 2001 From: Maribeth Moffatt Date: Mon, 28 Jul 2025 17:35:55 -0400 Subject: [PATCH 15/57] fix: dont save ids when copying blocks and comments (#9255) --- core/block_svg.ts | 1 + core/comments/rendered_workspace_comment.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/core/block_svg.ts b/core/block_svg.ts index 49b4a1ee6f6..1b85d38ce6b 100644 --- a/core/block_svg.ts +++ b/core/block_svg.ts @@ -962,6 +962,7 @@ export class BlockSvg blockState: blocks.save(this, { addCoordinates: true, addNextBlocks: false, + saveIds: false, }) as blocks.State, typeCounts: common.getBlockTypeCounts(this, true), }; diff --git a/core/comments/rendered_workspace_comment.ts b/core/comments/rendered_workspace_comment.ts index c4c1f3d4ec3..49c75e60883 100644 --- a/core/comments/rendered_workspace_comment.ts +++ b/core/comments/rendered_workspace_comment.ts @@ -280,6 +280,7 @@ export class RenderedWorkspaceComment paster: WorkspaceCommentPaster.TYPE, commentState: commentSerialization.save(this, { addCoordinates: true, + saveIds: false, }), }; } From 8a578f5ce3b8e7e8f650369f1e9e19bcbb66e1ae Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Wed, 30 Jul 2025 08:58:30 -0700 Subject: [PATCH 16/57] fix: Allow overriding `VariableModel` via `options.plugins`. (#9257) --- core/registry.ts | 6 +++--- core/variable_map.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/core/registry.ts b/core/registry.ts index 2b00b775dea..4980a559478 100644 --- a/core/registry.ts +++ b/core/registry.ts @@ -119,9 +119,9 @@ export class Type<_T> { /** @internal */ static PASTER = new Type>>('paster'); - static VARIABLE_MODEL = new Type>( - 'variableModel', - ); + static VARIABLE_MODEL = new Type< + IVariableModelStatic & IVariableModel + >('variableModel'); static VARIABLE_MAP = new Type>>( 'variableMap', diff --git a/core/variable_map.ts b/core/variable_map.ts index 3dd4bf5475d..ba36dcea600 100644 --- a/core/variable_map.ts +++ b/core/variable_map.ts @@ -255,9 +255,9 @@ export class VariableMap } const id = opt_id || idGenerator.genUid(); const type = opt_type || ''; - const VariableModel = registry.getObject( + const VariableModel = registry.getClassFromOptions( registry.Type.VARIABLE_MODEL, - registry.DEFAULT, + this.workspace.options, true, ); if (!VariableModel) { From c037e7d47ddd2c2a8ac51e6e648372adc0e14722 Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Wed, 30 Jul 2025 09:48:00 -0700 Subject: [PATCH 17/57] fix: Scroll `CommentBarButton`s into view on selection. (#9259) --- core/keyboard_nav/line_cursor.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/keyboard_nav/line_cursor.ts b/core/keyboard_nav/line_cursor.ts index a301c3b37e0..c621e3a89ef 100644 --- a/core/keyboard_nav/line_cursor.ts +++ b/core/keyboard_nav/line_cursor.ts @@ -14,6 +14,7 @@ */ import {BlockSvg} from '../block_svg.js'; +import {CommentBarButton} from '../comments/comment_bar_button.js'; import {RenderedWorkspaceComment} from '../comments/rendered_workspace_comment.js'; import {Field} from '../field.js'; import {getFocusManager} from '../focus_manager.js'; @@ -403,6 +404,9 @@ export class LineCursor extends Marker { ); } else if (newNode instanceof RenderedWorkspaceComment) { newNode.workspace.scrollBoundsIntoView(newNode.getBoundingRectangle()); + } else if (newNode instanceof CommentBarButton) { + const comment = newNode.getParentComment(); + comment.workspace.scrollBoundsIntoView(comment.getBoundingRectangle()); } } From d9421892fb1b8815fc3092ea3c18e3444c3973df Mon Sep 17 00:00:00 2001 From: michaela-mm <63740955+michaela-mm@users.noreply.github.com> Date: Fri, 1 Aug 2025 20:30:23 +0200 Subject: [PATCH 18/57] fix: Check for existing event group in cleanUp() (#9265) --- core/workspace_svg.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/core/workspace_svg.ts b/core/workspace_svg.ts index 6c6b5930110..af395b077e5 100644 --- a/core/workspace_svg.ts +++ b/core/workspace_svg.ts @@ -1674,7 +1674,10 @@ export class WorkspaceSvg /** Clean up the workspace by ordering all the blocks in a column such that none overlap. */ cleanUp() { this.setResizesEnabled(false); - eventUtils.setGroup(true); + const existingGroup = eventUtils.getGroup(); + if (!existingGroup) { + eventUtils.setGroup(true); + } const topBlocks = this.getTopBlocks(true); const movableBlocks = topBlocks.filter((block) => block.isMovable()); @@ -1722,7 +1725,7 @@ export class WorkspaceSvg block.getHeightWidth().height + minBlockHeight; } - eventUtils.setGroup(false); + eventUtils.setGroup(existingGroup); this.setResizesEnabled(true); } From 71256d69a0794e5a21bc8f1cf8c691fc3a898b9e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Aug 2025 12:22:09 +0000 Subject: [PATCH 19/57] chore(deps): bump eslint-plugin-jsdoc from 51.3.1 to 52.0.2 Bumps [eslint-plugin-jsdoc](https://github.com/gajus/eslint-plugin-jsdoc) from 51.3.1 to 52.0.2. - [Release notes](https://github.com/gajus/eslint-plugin-jsdoc/releases) - [Changelog](https://github.com/gajus/eslint-plugin-jsdoc/blob/main/.releaserc) - [Commits](https://github.com/gajus/eslint-plugin-jsdoc/compare/v51.3.1...v52.0.2) --- updated-dependencies: - dependency-name: eslint-plugin-jsdoc dependency-version: 52.0.2 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- package-lock.json | 9 +++++---- package.json | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 95e9a39b22e..8d4d84a1bfb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,7 @@ "eslint": "^9.15.0", "eslint-config-google": "^0.14.0", "eslint-config-prettier": "^10.1.1", - "eslint-plugin-jsdoc": "^51.3.1", + "eslint-plugin-jsdoc": "^52.0.2", "eslint-plugin-prettier": "^5.2.1", "glob": "^11.0.1", "globals": "^16.0.0", @@ -4088,10 +4088,11 @@ } }, "node_modules/eslint-plugin-jsdoc": { - "version": "51.3.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-51.3.1.tgz", - "integrity": "sha512-9v/e6XyrLf1HIs/uPCgm3GcUpH4BeuGVZJk7oauKKyS7su7d5Q6zx4Fq6TiYh+w7+b4Svy7ZWVCcNZJNx3y52w==", + "version": "52.0.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-52.0.2.tgz", + "integrity": "sha512-fYrnc7OpRifxxKjH78Y9/D/EouQDYD3G++bpR1Y+A+fy+CMzKZAdGIiHTIxCd2U10hb2y1NxN5TJt9aupq1vmw==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "@es-joy/jsdoccomment": "~0.52.0", "are-docs-informative": "^0.0.2", diff --git a/package.json b/package.json index 6ae18bd4a55..3535ae06da9 100644 --- a/package.json +++ b/package.json @@ -114,7 +114,7 @@ "eslint": "^9.15.0", "eslint-config-google": "^0.14.0", "eslint-config-prettier": "^10.1.1", - "eslint-plugin-jsdoc": "^51.3.1", + "eslint-plugin-jsdoc": "^52.0.2", "eslint-plugin-prettier": "^5.2.1", "glob": "^11.0.1", "globals": "^16.0.0", From 5e48e0db8c81262f45cf122a233afecbe41fe066 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Aug 2025 13:58:13 +0000 Subject: [PATCH 20/57] chore(deps): bump concurrently from 9.1.2 to 9.2.0 Bumps [concurrently](https://github.com/open-cli-tools/concurrently) from 9.1.2 to 9.2.0. - [Release notes](https://github.com/open-cli-tools/concurrently/releases) - [Commits](https://github.com/open-cli-tools/concurrently/compare/v9.1.2...v9.2.0) --- updated-dependencies: - dependency-name: concurrently dependency-version: 9.2.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- package-lock.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 95e9a39b22e..cbe2ca26e14 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3238,10 +3238,11 @@ } }, "node_modules/concurrently": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.1.2.tgz", - "integrity": "sha512-H9MWcoPsYddwbOGM6difjVwVZHl63nwMEwDJG/L7VGtuaJhb12h2caPG2tVPWs7emuYix252iGfqOyrz1GczTQ==", + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.0.tgz", + "integrity": "sha512-IsB/fiXTupmagMW4MNp2lx2cdSN2FfZq78vF90LBB+zZHArbIQZjQtzXCiXnvTxCZSvXanTqFLWBjw2UkLx1SQ==", "dev": true, + "license": "MIT", "dependencies": { "chalk": "^4.1.2", "lodash": "^4.17.21", From d5e347db44f0ef38d45f80be88505f73014b024e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Aug 2025 10:02:34 -0700 Subject: [PATCH 21/57] chore(deps): bump actions/first-interaction from 1 to 2 (#9274) Bumps [actions/first-interaction](https://github.com/actions/first-interaction) from 1 to 2. - [Release notes](https://github.com/actions/first-interaction/releases) - [Commits](https://github.com/actions/first-interaction/compare/v1...v2) --- updated-dependencies: - dependency-name: actions/first-interaction dependency-version: '2' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/welcome_new_contributors.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/welcome_new_contributors.yml b/.github/workflows/welcome_new_contributors.yml index 37ca9ef89df..663f0320577 100644 --- a/.github/workflows/welcome_new_contributors.yml +++ b/.github/workflows/welcome_new_contributors.yml @@ -9,7 +9,7 @@ jobs: permissions: pull-requests: write steps: - - uses: actions/first-interaction@v1 + - uses: actions/first-interaction@v2 with: repo-token: ${{ secrets.GITHUB_TOKEN }} pr-message: > From 683a4357ff158121c119d2e6d49b24e639263b7a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Aug 2025 11:13:43 -0700 Subject: [PATCH 22/57] chore(deps): bump gulp-rename from 2.0.0 to 2.1.0 (#9277) Bumps [gulp-rename](https://github.com/hparra/gulp-rename) from 2.0.0 to 2.1.0. - [Release notes](https://github.com/hparra/gulp-rename/releases) - [Changelog](https://github.com/hparra/gulp-rename/blob/master/CHANGELOG.md) - [Commits](https://github.com/hparra/gulp-rename/compare/v2.0.0...v2.1.0) --- updated-dependencies: - dependency-name: gulp-rename dependency-version: 2.1.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index ce30361933f..b1ff8dcbd8b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5536,10 +5536,11 @@ "dev": true }, "node_modules/gulp-rename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/gulp-rename/-/gulp-rename-2.0.0.tgz", - "integrity": "sha512-97Vba4KBzbYmR5VBs9mWmK+HwIf5mj+/zioxfZhOKeXtx5ZjBk57KFlePf5nxq9QsTtFl0ejnHE3zTC9MHXqyQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/gulp-rename/-/gulp-rename-2.1.0.tgz", + "integrity": "sha512-dGuzuH8jQGqCMqC544IEPhs5+O2l+IkdoSZsgd4kY97M1CxQeI3qrmweQBIrxLBbjbe/8uEWK8HHcNBc3OCy4g==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } From f24940e7816891ee78add455d37edbdf7f17ab23 Mon Sep 17 00:00:00 2001 From: Maribeth Moffatt Date: Mon, 4 Aug 2025 16:14:44 -0400 Subject: [PATCH 23/57] fix: dont add comments to full block fields (#9263) * fix: dont add comments to full block fields * chore: remove some nonnull assertions --- core/block.ts | 2 +- core/contextmenu_items.ts | 23 +++++++++++++++++------ 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/core/block.ts b/core/block.ts index 9f7c11d4faf..8ba61b1cac0 100644 --- a/core/block.ts +++ b/core/block.ts @@ -1118,7 +1118,7 @@ export class Block { * * @yields A generator that can be used to iterate the fields on the block. */ - *getFields(): Generator { + *getFields(): Generator { for (const input of this.inputList) { for (const field of input.fieldRow) { yield field; diff --git a/core/contextmenu_items.ts b/core/contextmenu_items.ts index 774bfdde29b..8bb71775fd7 100644 --- a/core/contextmenu_items.ts +++ b/core/contextmenu_items.ts @@ -25,6 +25,12 @@ import {StatementInput} from './renderers/zelos/zelos.js'; import {Coordinate} from './utils/coordinate.js'; import type {WorkspaceSvg} from './workspace_svg.js'; +function isFullBlockField(block?: BlockSvg) { + if (!block || !block.isSimpleReporter()) return false; + const firstField = block.getFields().next().value; + return firstField?.isFullBlockField(); +} + /** * Option to undo previous action. */ @@ -362,10 +368,15 @@ export function registerComment() { preconditionFn(scope: Scope) { const block = scope.block; if ( - !block!.isInFlyout && - block!.workspace.options.comments && - !block!.isCollapsed() && - block!.isEditable() + block && + !block.isInFlyout && + block.workspace.options.comments && + !block.isCollapsed() && + block.isEditable() && + // Either block already has a comment so let us remove it, + // or the block isn't just one full-block field block, which + // shouldn't be allowed to have comments as there's no way to read them. + (block.hasIcon(CommentIcon.TYPE) || !isFullBlockField(block)) ) { return 'enabled'; } @@ -373,8 +384,8 @@ export function registerComment() { }, callback(scope: Scope) { const block = scope.block; - if (block!.hasIcon(CommentIcon.TYPE)) { - block!.setCommentText(null); + if (block && block.hasIcon(CommentIcon.TYPE)) { + block.setCommentText(null); } else { block!.setCommentText(''); } From 88151fcadd00a5812efa67e9221def35da353f93 Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Mon, 4 Aug 2025 13:18:31 -0700 Subject: [PATCH 24/57] fix: Display focused icons without transparency. (#9268) --- core/css.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/css.ts b/core/css.ts index 4f4a4daaf90..30ee47fc58a 100644 --- a/core/css.ts +++ b/core/css.ts @@ -241,7 +241,7 @@ let content = ` cursor: default; } -.blocklyIconGroup:not(:hover), +.blocklyIconGroup:not(:hover):not(:focus), .blocklyIconGroupReadonly { opacity: .6; } From 44e78b1456f969c2050099800be82a97e87433d7 Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Tue, 5 Aug 2025 11:17:10 -0700 Subject: [PATCH 25/57] feat: Add an option to copy subsequent blocks when getting copy data from a block. (#9279) --- core/block_svg.ts | 7 +++++-- tests/mocha/clipboard_test.js | 25 +++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/core/block_svg.ts b/core/block_svg.ts index 1b85d38ce6b..c6065282a5c 100644 --- a/core/block_svg.ts +++ b/core/block_svg.ts @@ -951,9 +951,12 @@ export class BlockSvg /** * Encode a block for copying. * + * @param addNextBlocks If true, copy subsequent blocks attached to this one + * as well. + * * @returns Copy metadata, or null if the block is an insertion marker. */ - toCopyData(): BlockCopyData | null { + toCopyData(addNextBlocks = false): BlockCopyData | null { if (this.isInsertionMarker_) { return null; } @@ -961,7 +964,7 @@ export class BlockSvg paster: BlockPaster.TYPE, blockState: blocks.save(this, { addCoordinates: true, - addNextBlocks: false, + addNextBlocks, saveIds: false, }) as blocks.State, typeCounts: common.getBlockTypeCounts(this, true), diff --git a/tests/mocha/clipboard_test.js b/tests/mocha/clipboard_test.js index 85cdd229777..d58f78b9b50 100644 --- a/tests/mocha/clipboard_test.js +++ b/tests/mocha/clipboard_test.js @@ -61,6 +61,31 @@ suite('Clipboard', function () { ); }); + test('pasting blocks includes next blocks if requested', function () { + const block = Blockly.serialization.blocks.append( + { + 'type': 'controls_if', + 'id': 'blockId', + 'next': { + 'block': { + 'type': 'controls_if', + 'id': 'blockId2', + }, + }, + }, + this.workspace, + ); + assert.equal(this.workspace.getBlocksByType('controls_if').length, 2); + // Both blocks should be copied + const data = block.toCopyData(true); + this.clock.runAll(); + + Blockly.clipboard.paste(data, this.workspace); + this.clock.runAll(); + // After pasting, we should have gone from 2 to 4 blocks. + assert.equal(this.workspace.getBlocksByType('controls_if').length, 4); + }); + test('copied from a mutator pastes them into the mutator', async function () { const block = Blockly.serialization.blocks.append( { From af57a3eaa387a4ebe374332eed912a1124639e3c Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Wed, 6 Aug 2025 12:45:17 -0700 Subject: [PATCH 26/57] refactor: Make `InsertionMarkerPreviewer`'s block serialization amenable to subclassing. (#9282) --- core/insertion_marker_previewer.ts | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/core/insertion_marker_previewer.ts b/core/insertion_marker_previewer.ts index 2343b9adc76..8b5b82468c5 100644 --- a/core/insertion_marker_previewer.ts +++ b/core/insertion_marker_previewer.ts @@ -150,8 +150,17 @@ export class InsertionMarkerPreviewer implements IConnectionPreviewer { return markerConn; } - private createInsertionMarker(origBlock: BlockSvg) { - const blockJson = blocks.save(origBlock, { + /** + * Transforms the given block into a JSON representation used to construct an + * insertion marker. + * + * @param block The block to serialize and use as an insertion marker. + * @returns A JSON-formatted string corresponding to a serialized + * representation of the given block suitable for use as an insertion + * marker. + */ + protected serializeBlockToInsertionMarker(block: BlockSvg) { + const blockJson = blocks.save(block, { addCoordinates: false, addInputBlocks: false, addNextBlocks: false, @@ -160,10 +169,15 @@ export class InsertionMarkerPreviewer implements IConnectionPreviewer { if (!blockJson) { throw new Error( - `Failed to serialize source block. ${origBlock.toDevString()}`, + `Failed to serialize source block. ${block.toDevString()}`, ); } + return blockJson; + } + + private createInsertionMarker(origBlock: BlockSvg) { + const blockJson = this.serializeBlockToInsertionMarker(origBlock); const result = blocks.append(blockJson, this.workspace) as BlockSvg; // Turn shadow blocks that are created programmatically during From 62f3b8914a1d3dfbb1f4a64c41adba56957663d5 Mon Sep 17 00:00:00 2001 From: Maribeth Moffatt Date: Wed, 6 Aug 2025 17:01:59 -0400 Subject: [PATCH 27/57] chore: add tests for clipboard (#9254) * chore: add tests for clipboard * chore: clean up --- tests/browser/test/clipboard_test.mjs | 611 ++++++++++++++++++++++++++ tests/browser/test/test_setup.mjs | 28 ++ 2 files changed, 639 insertions(+) create mode 100644 tests/browser/test/clipboard_test.mjs diff --git a/tests/browser/test/clipboard_test.mjs b/tests/browser/test/clipboard_test.mjs new file mode 100644 index 00000000000..37dd359d3b9 --- /dev/null +++ b/tests/browser/test/clipboard_test.mjs @@ -0,0 +1,611 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as chai from 'chai'; +import {Key} from 'webdriverio'; +import { + PAUSE_TIME, + clickWorkspace, + focusOnBlock, + getAllBlocks, + getBlockTypeFromWorkspace, + getCategory, + getSelectedBlockId, + getSelectedBlockType, + openMutatorForBlock, + testFileLocations, + testSetup, +} from './test_setup.mjs'; + +const testBlockJson = { + 'blocks': { + 'languageVersion': 0, + 'blocks': [ + { + 'type': 'controls_repeat_ext', + 'id': 'controls_repeat_1', + 'x': 88, + 'y': 88, + 'inputs': { + 'TIMES': { + 'shadow': { + 'type': 'math_number', + 'id': 'math_number_shadow_1', + 'fields': { + 'NUM': 10, + }, + }, + }, + 'DO': { + 'block': { + 'type': 'controls_if', + 'id': 'controls_if_1', + '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_shadow_1', + 'fields': { + 'TEXT': 'abc', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + ], + }, +}; + +async function loadStartBlocks(browser) { + await browser.execute((stringifiedJson) => { + // Hangs forever if the json isn't stringified ¯\_(ツ)_/¯ + const testBlockJson = JSON.parse(stringifiedJson); + const workspace = Blockly.common.getMainWorkspace(); + Blockly.serialization.workspaces.load(testBlockJson, workspace); + }, JSON.stringify(testBlockJson)); + await browser.pause(PAUSE_TIME); +} + +suite('Clipboard test', async function () { + // Setting timeout to unlimited as these tests take longer time to run + this.timeout(0); + + // Clear the workspace and load start blocks + setup(async function () { + this.browser = await testSetup(testFileLocations.PLAYGROUND); + await this.browser.pause(PAUSE_TIME); + }); + + test('Paste block to/from main workspace', async function () { + await loadStartBlocks(this.browser); + // Select and copy the "true" block + await focusOnBlock(this.browser, 'logic_boolean_1'); + await this.browser.pause(PAUSE_TIME); + + await this.browser.keys([Key.Ctrl, 'c']); + await this.browser.pause(PAUSE_TIME); + + // Check how many blocks there are before pasting + const allBlocksBeforePaste = await getAllBlocks(this.browser); + + // Paste the block while still in the main workspace + await this.browser.keys([Key.Ctrl, 'v']); + await this.browser.pause(PAUSE_TIME); + + // Check result + const allBlocksAfterPaste = await getAllBlocks(this.browser); + chai.assert.equal( + allBlocksAfterPaste.length, + allBlocksBeforePaste.length + 1, + 'Expected there to be one additional block after paste', + ); + const focusedBlockId = await getSelectedBlockId(this.browser); + chai.assert.notEqual( + focusedBlockId, + 'logic_boolean_1', + 'Newly pasted block should be selected', + ); + const focusedBlockType = await getSelectedBlockType(this.browser); + chai.assert.equal( + focusedBlockType, + 'logic_boolean', + 'Newly pasted block should be selected', + ); + }); + + test('Copying a block also copies and pastes its children', async function () { + await loadStartBlocks(this.browser); + // Select and copy the "if/else" block which has children + await focusOnBlock(this.browser, 'controls_if_1'); + await this.browser.pause(PAUSE_TIME); + + await this.browser.keys([Key.Ctrl, 'c']); + await this.browser.pause(PAUSE_TIME); + + // Check how many blocks there are before pasting + const allBlocksBeforePaste = await getAllBlocks(this.browser); + + // Paste the block while still in the main workspace + await this.browser.keys([Key.Ctrl, 'v']); + await this.browser.pause(PAUSE_TIME); + + // Check result + const allBlocksAfterPaste = await getAllBlocks(this.browser); + chai.assert.equal( + allBlocksAfterPaste.length, + allBlocksBeforePaste.length + 4, + 'Expected there to be four additional blocks after paste', + ); + }); + + test('Paste shadow block to/from main workspace', async function () { + await loadStartBlocks(this.browser); + // Select and copy the shadow number block + await focusOnBlock(this.browser, 'math_number_shadow_1'); + await this.browser.pause(PAUSE_TIME); + + await this.browser.keys([Key.Ctrl, 'c']); + await this.browser.pause(PAUSE_TIME); + + // Check how many blocks there are before pasting + const allBlocksBeforePaste = await getAllBlocks(this.browser); + + // Paste the block while still in the main workspace + await this.browser.keys([Key.Ctrl, 'v']); + await this.browser.pause(PAUSE_TIME); + + // Check result + const allBlocksAfterPaste = await getAllBlocks(this.browser); + chai.assert.equal( + allBlocksAfterPaste.length, + allBlocksBeforePaste.length + 1, + 'Expected there to be one additional block after paste', + ); + const focusedBlockId = await getSelectedBlockId(this.browser); + chai.assert.notEqual( + focusedBlockId, + 'math_number_shadow_1', + 'Newly pasted block should be selected', + ); + const focusedBlockType = await getSelectedBlockType(this.browser); + chai.assert.equal( + focusedBlockType, + 'math_number', + 'Newly pasted block should be selected', + ); + const focusedBlockIsShadow = await this.browser.execute(() => { + return Blockly.common.getSelected().isShadow(); + }); + chai.assert.isFalse( + focusedBlockIsShadow, + 'Expected the pasted version of the block to not be a shadow block', + ); + }); + + test('Copy block from flyout, paste to main workspace', async function () { + // Open flyout + await getCategory(this.browser, 'Logic').then((category) => + category.click(), + ); + + // Focus on first block in flyout + await this.browser.execute(() => { + const ws = Blockly.getMainWorkspace().getFlyout().getWorkspace(); + const block = ws.getBlocksByType('controls_if')[0]; + Blockly.getFocusManager().focusNode(block); + }); + await this.browser.pause(PAUSE_TIME); + + // Copy + await this.browser.keys([Key.Ctrl, 'c']); + await this.browser.pause(PAUSE_TIME); + + // Select the main workspace + await clickWorkspace(this.browser); + await this.browser.pause(PAUSE_TIME); + + // Paste + await this.browser.keys([Key.Ctrl, 'v']); + await this.browser.pause(PAUSE_TIME); + + // Check that the block is now on the workspace and selected + const allBlocks = await getAllBlocks(this.browser); + chai.assert.equal( + allBlocks.length, + 1, + 'Expected there to be one block on main workspace after paste from flyout', + ); + + const focusedBlockType = await getSelectedBlockType(this.browser); + chai.assert.equal( + focusedBlockType, + 'controls_if', + 'Newly pasted block should be selected', + ); + }); + + test('Copy block from flyout, paste while flyout focused', async function () { + // Open flyout + await getCategory(this.browser, 'Logic').then((category) => + category.click(), + ); + + // Focus on first block in flyout + await this.browser.execute(() => { + const ws = Blockly.getMainWorkspace().getFlyout().getWorkspace(); + const block = ws.getBlocksByType('controls_if')[0]; + Blockly.getFocusManager().focusNode(block); + }); + await this.browser.pause(PAUSE_TIME); + + // Copy + await this.browser.keys([Key.Ctrl, 'c']); + await this.browser.pause(PAUSE_TIME); + + // Paste + await this.browser.keys([Key.Ctrl, 'v']); + await this.browser.pause(PAUSE_TIME); + + // Check that the flyout is closed + const flyoutIsVisible = await this.browser + .$('.blocklyToolboxFlyout') + .then((elem) => elem.isDisplayed()); + chai.assert.isFalse(flyoutIsVisible, 'Expected flyout to not be open'); + + // Check that the block is now on the main workspace and selected + const allBlocks = await getAllBlocks(this.browser); + chai.assert.equal( + allBlocks.length, + 1, + 'Expected there to be one block on main workspace after paste from flyout', + ); + + const focusedBlockType = await getSelectedBlockType(this.browser); + chai.assert.equal( + focusedBlockType, + 'controls_if', + 'Newly pasted block should be selected', + ); + }); + + test('Copy block from mutator flyout, paste to mutator workspace', async function () { + // Load the start blocks + await loadStartBlocks(this.browser); + + // Open the controls_if mutator + const block = await getBlockTypeFromWorkspace( + this.browser, + 'controls_if', + 0, + ); + await openMutatorForBlock(this.browser, block); + + // Select the first block in the mutator flyout + await this.browser.execute( + (blockId, mutatorBlockType) => { + const flyoutBlock = Blockly.getMainWorkspace() + .getBlockById(blockId) + .mutator.getWorkspace() + .getFlyout() + .getWorkspace() + .getBlocksByType(mutatorBlockType)[0]; + + Blockly.getFocusManager().focusNode(flyoutBlock); + }, + 'controls_if_1', + 'controls_if_elseif', + ); + await this.browser.pause(PAUSE_TIME); + + // Copy + await this.browser.keys([Key.Ctrl, 'c']); + await this.browser.pause(PAUSE_TIME); + + // Paste + await this.browser.keys([Key.Ctrl, 'v']); + await this.browser.pause(PAUSE_TIME); + + // Check that the block is now in the mutator workspace and selected + const numberOfIfElseBlocks = await this.browser.execute( + (blockId, mutatorBlockType) => { + return Blockly.getMainWorkspace() + .getBlockById(blockId) + .mutator.getWorkspace() + .getBlocksByType(mutatorBlockType).length; + }, + 'controls_if_1', + 'controls_if_elseif', + ); + + chai.assert.equal( + numberOfIfElseBlocks, + 1, + 'Expected there to be one if_else block in mutator workspace', + ); + + const focusedBlockType = await getSelectedBlockType(this.browser); + chai.assert.equal( + focusedBlockType, + 'controls_if_elseif', + 'Newly pasted block should be selected', + ); + }); + + test('Copy block from mutator flyout, paste to main workspace while mutator open', async function () { + // Load the start blocks + await loadStartBlocks(this.browser); + + // Open the controls_if mutator + const block = await getBlockTypeFromWorkspace( + this.browser, + 'controls_if', + 0, + ); + await openMutatorForBlock(this.browser, block); + + // Select the first block in the mutator flyout + await this.browser.execute( + (blockId, mutatorBlockType) => { + const flyoutBlock = Blockly.getMainWorkspace() + .getBlockById(blockId) + .mutator.getWorkspace() + .getFlyout() + .getWorkspace() + .getBlocksByType(mutatorBlockType)[0]; + + Blockly.getFocusManager().focusNode(flyoutBlock); + }, + 'controls_if_1', + 'controls_if_elseif', + ); + await this.browser.pause(PAUSE_TIME); + + // Copy + await this.browser.keys([Key.Ctrl, 'c']); + await this.browser.pause(PAUSE_TIME); + + // Click the main workspace + await clickWorkspace(this.browser); + + // Paste + await this.browser.keys([Key.Ctrl, 'v']); + await this.browser.pause(PAUSE_TIME); + + // Check that the block is now in the mutator workspace and selected + const numberOfIfElseBlocks = await this.browser.execute( + (blockId, mutatorBlockType) => { + return Blockly.getMainWorkspace() + .getBlockById(blockId) + .mutator.getWorkspace() + .getBlocksByType(mutatorBlockType).length; + }, + 'controls_if_1', + 'controls_if_elseif', + ); + + chai.assert.equal( + numberOfIfElseBlocks, + 1, + 'Expected there to be one if_else block in mutator workspace', + ); + + const focusedBlockType = await getSelectedBlockType(this.browser); + chai.assert.equal( + focusedBlockType, + 'controls_if_elseif', + 'Newly pasted block should be selected', + ); + + // Check that there are no new blocks on the main workspace + const numberOfIfElseBlocksOnMainWorkspace = await this.browser.execute( + (mutatorBlockType) => { + return Blockly.getMainWorkspace().getBlocksByType(mutatorBlockType) + .length; + }, + 'controls_if_elseif', + ); + chai.assert.equal( + numberOfIfElseBlocksOnMainWorkspace, + 0, + 'Mutator blocks should not appear on main workspace', + ); + }); + + test('Copy block from mutator flyout, paste to main workspace while mutator closed', async function () { + // Load the start blocks + await loadStartBlocks(this.browser); + + // Open the controls_if mutator + const block = await getBlockTypeFromWorkspace( + this.browser, + 'controls_if', + 0, + ); + await openMutatorForBlock(this.browser, block); + + // Select the first block in the mutator flyout + await this.browser.execute( + (blockId, mutatorBlockType) => { + const flyoutBlock = Blockly.getMainWorkspace() + .getBlockById(blockId) + .mutator.getWorkspace() + .getFlyout() + .getWorkspace() + .getBlocksByType(mutatorBlockType)[0]; + + Blockly.getFocusManager().focusNode(flyoutBlock); + }, + 'controls_if_1', + 'controls_if_elseif', + ); + await this.browser.pause(PAUSE_TIME); + + // Copy + await this.browser.keys([Key.Ctrl, 'c']); + await this.browser.pause(PAUSE_TIME); + + // Close the mutator flyout (calling this method on open mutator closes it) + await openMutatorForBlock(this.browser, block); + + // Click the main workspace + await clickWorkspace(this.browser); + + // Paste + await this.browser.keys([Key.Ctrl, 'v']); + await this.browser.pause(PAUSE_TIME); + + // Check that there are no new blocks on the main workspace + const numberOfIfElseBlocksOnMainWorkspace = await this.browser.execute( + (mutatorBlockType) => { + return Blockly.getMainWorkspace().getBlocksByType(mutatorBlockType) + .length; + }, + 'controls_if_elseif', + ); + chai.assert.equal( + numberOfIfElseBlocksOnMainWorkspace, + 0, + 'Mutator blocks should not appear on main workspace', + ); + }); + + test('Copy workspace comment, paste to main workspace', async function () { + // Add a workspace comment to the workspace + await this.browser.execute(() => { + const workspace = Blockly.getMainWorkspace(); + const json = { + 'workspaceComments': [ + { + 'height': 100, + 'width': 120, + 'id': 'workspace_comment_1', + 'x': 13, + 'y': -12, + 'text': 'This is a comment', + }, + ], + }; + Blockly.serialization.workspaces.load(json, workspace); + }); + await this.browser.pause(PAUSE_TIME); + + // Select the workspace comment + await this.browser.execute(() => { + const comment = Blockly.getMainWorkspace().getCommentById( + 'workspace_comment_1', + ); + Blockly.getFocusManager().focusNode(comment); + }); + await this.browser.pause(PAUSE_TIME); + + // Copy + await this.browser.keys([Key.Ctrl, 'c']); + await this.browser.pause(PAUSE_TIME); + + // Click the main workspace + await clickWorkspace(this.browser); + + // Paste + await this.browser.keys([Key.Ctrl, 'v']); + await this.browser.pause(PAUSE_TIME); + + // Check that there are 2 comments on the workspace + const numberOfComments = await this.browser.execute(() => { + return Blockly.getMainWorkspace().getTopComments().length; + }); + chai.assert.equal( + numberOfComments, + 2, + 'Expected 2 workspace comments after pasting', + ); + }); + + test('Cut block from main workspace, paste to main workspace', async function () { + await loadStartBlocks(this.browser); + // Select and cut the "true" block + await focusOnBlock(this.browser, 'logic_boolean_1'); + await this.browser.pause(PAUSE_TIME); + + await this.browser.keys([Key.Ctrl, 'x']); + await this.browser.pause(PAUSE_TIME); + + // Check that the "true" block was deleted + const trueBlock = await this.browser.execute(() => { + return Blockly.getMainWorkspace().getBlockById('logic_boolean_1') ?? null; + }); + chai.assert.isNull(trueBlock); + + // Check how many blocks there are before pasting + const allBlocksBeforePaste = await getAllBlocks(this.browser); + + // Paste the block while still in the main workspace + await this.browser.keys([Key.Ctrl, 'v']); + await this.browser.pause(PAUSE_TIME); + + // Check result + const allBlocksAfterPaste = await getAllBlocks(this.browser); + chai.assert.equal( + allBlocksAfterPaste.length, + allBlocksBeforePaste.length + 1, + 'Expected there to be one additional block after paste', + ); + }); + + test('Cannot cut block from flyout', async function () { + // Open flyout + await getCategory(this.browser, 'Logic').then((category) => + category.click(), + ); + + // Focus on first block in flyout + await this.browser.execute(() => { + const ws = Blockly.getMainWorkspace().getFlyout().getWorkspace(); + const block = ws.getBlocksByType('controls_if')[0]; + Blockly.getFocusManager().focusNode(block); + }); + await this.browser.pause(PAUSE_TIME); + + // Cut + await this.browser.keys([Key.Ctrl, 'x']); + await this.browser.pause(PAUSE_TIME); + + // Select the main workspace + await clickWorkspace(this.browser); + await this.browser.pause(PAUSE_TIME); + + // Paste + await this.browser.keys([Key.Ctrl, 'v']); + await this.browser.pause(PAUSE_TIME); + + // Check that no block was pasted + const allBlocks = await getAllBlocks(this.browser); + chai.assert.equal( + allBlocks.length, + 0, + 'Expected no blocks in the workspace because nothing to paste', + ); + }); +}); diff --git a/tests/browser/test/test_setup.mjs b/tests/browser/test/test_setup.mjs index 6cf4986fce5..0a8998c3efe 100644 --- a/tests/browser/test/test_setup.mjs +++ b/tests/browser/test/test_setup.mjs @@ -127,6 +127,23 @@ export const screenDirection = { LTR: 1, }; +/** + * Focuses and selects a block with the provided ID. + * + * This throws an error if no block exists for the specified ID. + * + * @param browser The active WebdriverIO Browser object. + * @param blockId The ID of the block to select. + */ +export async function focusOnBlock(browser, blockId) { + return await browser.execute((blockId) => { + const workspaceSvg = Blockly.getMainWorkspace(); + const block = workspaceSvg.getBlockById(blockId); + if (!block) throw new Error(`No block found with ID: ${blockId}.`); + Blockly.getFocusManager().focusNode(block); + }, blockId); +} + /** * @param browser The active WebdriverIO Browser object. * @return A Promise that resolves to the ID of the currently selected block. @@ -138,6 +155,17 @@ export async function getSelectedBlockId(browser) { }); } +/** + * @param browser The active WebdriverIO Browser object. + * @return A Promise that resolves to the ID of the currently selected block. + */ +export async function getSelectedBlockType(browser) { + return await browser.execute(() => { + // Note: selected is an ICopyable and I am assuming that it is a BlockSvg. + return Blockly.common.getSelected()?.type; + }); +} + /** * @param browser The active WebdriverIO Browser object. * @return A Promise that resolves to the selected block's root SVG element, From f9d0ec9d24d6ee78a0e4a52327dbd0e1688b7f1b Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Wed, 6 Aug 2025 14:04:12 -0700 Subject: [PATCH 28/57] refactor: Associate comment bar buttons with the comment view. (#9278) --- core/comments/collapse_comment_bar_button.ts | 9 +++++---- core/comments/comment_bar_button.ts | 16 +++++----------- core/comments/comment_view.ts | 11 ++++++++--- core/comments/delete_comment_bar_button.ts | 6 ++++-- .../comment_bar_button_navigation_policy.ts | 8 +++++--- core/keyboard_nav/line_cursor.ts | 13 +++++++++++-- 6 files changed, 38 insertions(+), 25 deletions(-) diff --git a/core/comments/collapse_comment_bar_button.ts b/core/comments/collapse_comment_bar_button.ts index b0738d70705..304e2af8125 100644 --- a/core/comments/collapse_comment_bar_button.ts +++ b/core/comments/collapse_comment_bar_button.ts @@ -10,6 +10,7 @@ import * as dom from '../utils/dom.js'; import {Svg} from '../utils/svg.js'; import type {WorkspaceSvg} from '../workspace_svg.js'; import {CommentBarButton} from './comment_bar_button.js'; +import type {CommentView} from './comment_view.js'; /** * Magic string appended to the comment ID to create a unique ID for this button. @@ -42,8 +43,9 @@ export class CollapseCommentBarButton extends CommentBarButton { protected readonly id: string, protected readonly workspace: WorkspaceSvg, protected readonly container: SVGGElement, + protected readonly commentView: CommentView, ) { - super(id, workspace, container); + super(id, workspace, container, commentView); this.icon = dom.createSvgElement( Svg.IMAGE, @@ -86,14 +88,13 @@ export class CollapseCommentBarButton extends CommentBarButton { override performAction(e?: Event) { touch.clearTouchIdentifier(); - const comment = this.getParentComment(); - comment.view.bringToFront(); + this.getCommentView().bringToFront(); if (e && e instanceof PointerEvent && browserEvents.isRightButton(e)) { e.stopPropagation(); return; } - comment.setCollapsed(!comment.isCollapsed()); + this.getCommentView().setCollapsed(!this.getCommentView().isCollapsed()); this.workspace.hideChaff(); e?.stopPropagation(); diff --git a/core/comments/comment_bar_button.ts b/core/comments/comment_bar_button.ts index d78a7fd86a1..24a084ad26e 100644 --- a/core/comments/comment_bar_button.ts +++ b/core/comments/comment_bar_button.ts @@ -7,7 +7,7 @@ import type {IFocusableNode} from '../interfaces/i_focusable_node.js'; import {Rect} from '../utils/rect.js'; import type {WorkspaceSvg} from '../workspace_svg.js'; -import type {RenderedWorkspaceComment} from './rendered_workspace_comment.js'; +import type {CommentView} from './comment_view.js'; /** * Button displayed on a comment's top bar. @@ -29,6 +29,7 @@ export abstract class CommentBarButton implements IFocusableNode { protected readonly id: string, protected readonly workspace: WorkspaceSvg, protected readonly container: SVGGElement, + protected readonly commentView: CommentView, ) {} /** @@ -39,17 +40,10 @@ export abstract class CommentBarButton implements IFocusableNode { } /** - * Returns the parent comment of this comment bar button. + * Returns the parent comment view of this comment bar button. */ - getParentComment(): RenderedWorkspaceComment { - const comment = this.workspace.getCommentById(this.id); - if (!comment) { - throw new Error( - `Comment bar button ${this.id} has no corresponding comment`, - ); - } - - return comment; + getCommentView(): CommentView { + return this.commentView; } /** Adjusts the position of this button within its parent container. */ diff --git a/core/comments/comment_view.ts b/core/comments/comment_view.ts index 936d746508f..ca0c261c390 100644 --- a/core/comments/comment_view.ts +++ b/core/comments/comment_view.ts @@ -102,7 +102,7 @@ export class CommentView implements IRenderedElement { constructor( readonly workspace: WorkspaceSvg, - private commentId: string, + readonly commentId: string, ) { this.svgRoot = dom.createSvgElement(Svg.G, { 'class': 'blocklyComment blocklyEditable blocklyDraggable', @@ -176,12 +176,18 @@ export class CommentView implements IRenderedElement { this.commentId, this.workspace, topBarGroup, + this, ); const foldoutButton = new CollapseCommentBarButton( this.commentId, this.workspace, topBarGroup, + this, ); + this.addDisposeListener(() => { + deleteButton.dispose(); + foldoutButton.dispose(); + }); const textPreview = dom.createSvgElement( Svg.TEXT, { @@ -612,13 +618,12 @@ export class CommentView implements IRenderedElement { /** Disposes of this comment view. */ dispose() { this.disposing = true; - this.foldoutButton.dispose(); - this.deleteButton.dispose(); dom.removeNode(this.svgRoot); // Loop through listeners backwards in case they remove themselves. for (let i = this.disposeListeners.length - 1; i >= 0; i--) { this.disposeListeners[i](); } + this.disposeListeners.length = 0; this.disposed = true; } diff --git a/core/comments/delete_comment_bar_button.ts b/core/comments/delete_comment_bar_button.ts index 0b7dcd0ea27..c61db9b9cd2 100644 --- a/core/comments/delete_comment_bar_button.ts +++ b/core/comments/delete_comment_bar_button.ts @@ -11,6 +11,7 @@ import * as dom from '../utils/dom.js'; import {Svg} from '../utils/svg.js'; import type {WorkspaceSvg} from '../workspace_svg.js'; import {CommentBarButton} from './comment_bar_button.js'; +import type {CommentView} from './comment_view.js'; /** * Magic string appended to the comment ID to create a unique ID for this button. @@ -42,8 +43,9 @@ export class DeleteCommentBarButton extends CommentBarButton { protected readonly id: string, protected readonly workspace: WorkspaceSvg, protected readonly container: SVGGElement, + protected readonly commentView: CommentView, ) { - super(id, workspace, container); + super(id, workspace, container, commentView); this.icon = dom.createSvgElement( Svg.IMAGE, @@ -97,7 +99,7 @@ export class DeleteCommentBarButton extends CommentBarButton { return; } - this.getParentComment().dispose(); + this.getCommentView().dispose(); e?.stopPropagation(); getFocusManager().focusNode(this.workspace); } diff --git a/core/keyboard_nav/comment_bar_button_navigation_policy.ts b/core/keyboard_nav/comment_bar_button_navigation_policy.ts index f676f465582..6654d2d8fef 100644 --- a/core/keyboard_nav/comment_bar_button_navigation_policy.ts +++ b/core/keyboard_nav/comment_bar_button_navigation_policy.ts @@ -31,7 +31,9 @@ export class CommentBarButtonNavigationPolicy * @returns The parent comment of the given CommentBarButton. */ getParent(current: CommentBarButton): IFocusableNode | null { - return current.getParentComment(); + return current + .getCommentView() + .workspace.getCommentById(current.getCommentView().commentId); } /** @@ -41,7 +43,7 @@ export class CommentBarButtonNavigationPolicy * @returns The next CommentBarButton, if any. */ getNextSibling(current: CommentBarButton): IFocusableNode | null { - const children = current.getParentComment().view.getCommentBarButtons(); + const children = current.getCommentView().getCommentBarButtons(); const currentIndex = children.indexOf(current); if (currentIndex >= 0 && currentIndex + 1 < children.length) { return children[currentIndex + 1]; @@ -56,7 +58,7 @@ export class CommentBarButtonNavigationPolicy * @returns The CommentBarButton's previous CommentBarButton, if any. */ getPreviousSibling(current: CommentBarButton): IFocusableNode | null { - const children = current.getParentComment().view.getCommentBarButtons(); + const children = current.getCommentView().getCommentBarButtons(); const currentIndex = children.indexOf(current); if (currentIndex > 0) { return children[currentIndex - 1]; diff --git a/core/keyboard_nav/line_cursor.ts b/core/keyboard_nav/line_cursor.ts index c621e3a89ef..13e5a729d0f 100644 --- a/core/keyboard_nav/line_cursor.ts +++ b/core/keyboard_nav/line_cursor.ts @@ -20,6 +20,7 @@ import {Field} from '../field.js'; import {getFocusManager} from '../focus_manager.js'; import type {IFocusableNode} from '../interfaces/i_focusable_node.js'; import * as registry from '../registry.js'; +import {Rect} from '../utils/rect.js'; import {WorkspaceSvg} from '../workspace_svg.js'; import {Marker} from './marker.js'; @@ -405,8 +406,16 @@ export class LineCursor extends Marker { } else if (newNode instanceof RenderedWorkspaceComment) { newNode.workspace.scrollBoundsIntoView(newNode.getBoundingRectangle()); } else if (newNode instanceof CommentBarButton) { - const comment = newNode.getParentComment(); - comment.workspace.scrollBoundsIntoView(comment.getBoundingRectangle()); + const commentView = newNode.getCommentView(); + const xy = commentView.getRelativeToSurfaceXY(); + const size = commentView.getSize(); + const bounds = new Rect( + xy.y, + xy.y + size.height, + xy.x, + xy.x + size.width, + ); + commentView.workspace.scrollBoundsIntoView(bounds); } } From 7d1d745416621cc352c1048e6fb06c9f611cbcc4 Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Wed, 6 Aug 2025 14:08:01 -0700 Subject: [PATCH 29/57] fix: Drag immovable and shadow blocks along with their parent. (#9281) --- core/block.ts | 42 ++++++++++++++++++++++++--------------- tests/mocha/block_test.js | 29 +++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 16 deletions(-) diff --git a/core/block.ts b/core/block.ts index 8ba61b1cac0..d15700a7e19 100644 --- a/core/block.ts +++ b/core/block.ts @@ -501,22 +501,32 @@ export class Block { // Detach this block from the parent's tree. this.previousConnection.disconnect(); } - const nextBlock = this.getNextBlock(); - if (opt_healStack && nextBlock && !nextBlock.isShadow()) { - // Disconnect the next statement. - const nextTarget = this.nextConnection?.targetConnection ?? null; - nextTarget?.disconnect(); - if ( - previousTarget && - this.workspace.connectionChecker.canConnect( - previousTarget, - nextTarget, - false, - ) - ) { - // Attach the next statement to the previous statement. - previousTarget.connect(nextTarget!); - } + + if (!opt_healStack) return; + + // Immovable or shadow next blocks need to move along with the block; keep + // going until we encounter a normal block or run off the end of the stack. + let nextBlock = this.getNextBlock(); + while (nextBlock && (nextBlock.isShadow() || !nextBlock.isMovable())) { + nextBlock = nextBlock.getNextBlock(); + } + if (!nextBlock) return; + + // Disconnect the next statement. + const nextTarget = + nextBlock.previousConnection?.targetBlock()?.nextConnection + ?.targetConnection ?? null; + nextTarget?.disconnect(); + if ( + previousTarget && + this.workspace.connectionChecker.canConnect( + previousTarget, + nextTarget, + false, + ) + ) { + // Attach the next statement to the previous statement. + previousTarget.connect(nextTarget!); } } diff --git a/tests/mocha/block_test.js b/tests/mocha/block_test.js index 62c61ce004c..e3bd470902d 100644 --- a/tests/mocha/block_test.js +++ b/tests/mocha/block_test.js @@ -201,6 +201,35 @@ suite('Blocks', function () { assertUnpluggedHealFailed(blocks); }); + test('Disconnect top of stack with immovable sibling', function () { + this.blocks.B.setMovable(false); + this.blocks.A.unplug(true); + assert.equal(this.blocks.A.nextConnection.targetBlock(), this.blocks.B); + assert.isNull(this.blocks.B.nextConnection.targetBlock()); + assert.isNull(this.blocks.C.previousConnection.targetBlock()); + }); + test('Heal with immovable sibling mid-stack', function () { + const blockD = this.workspace.newBlock('stack_block', 'd'); + this.blocks.C.nextConnection.connect(blockD.previousConnection); + this.blocks.C.setMovable(false); + this.blocks.B.unplug(true); + assert.equal(this.blocks.A.nextConnection.targetBlock(), blockD); + assert.equal(this.blocks.B.nextConnection.targetBlock(), this.blocks.C); + assert.isNull(this.blocks.C.nextConnection.targetBlock()); + }); + test('Heal with immovable sibling and shadow sibling mid-stack', function () { + const blockD = this.workspace.newBlock('stack_block', 'd'); + const blockE = this.workspace.newBlock('stack_block', 'e'); + this.blocks.C.nextConnection.connect(blockD.previousConnection); + blockD.nextConnection.connect(blockE.previousConnection); + this.blocks.C.setMovable(false); + blockD.setShadow(true); + this.blocks.B.unplug(true); + assert.equal(this.blocks.A.nextConnection.targetBlock(), blockE); + assert.equal(this.blocks.B.nextConnection.targetBlock(), this.blocks.C); + assert.equal(this.blocks.C.nextConnection.targetBlock(), blockD); + assert.isNull(blockD.nextConnection.targetBlock()); + }); test('Child is shadow', function () { const blocks = this.blocks; blocks.C.setShadow(true); From 2e252a4bd80d376ab6b93524931e00d61e91683b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Aug 2025 09:38:45 -0700 Subject: [PATCH 30/57] chore(deps): bump google-github-actions/deploy-appengine (#9273) Bumps [google-github-actions/deploy-appengine](https://github.com/google-github-actions/deploy-appengine) from 2.1.5 to 2.1.7. - [Release notes](https://github.com/google-github-actions/deploy-appengine/releases) - [Changelog](https://github.com/google-github-actions/deploy-appengine/blob/main/CHANGELOG.md) - [Commits](https://github.com/google-github-actions/deploy-appengine/compare/v2.1.5...v2.1.7) --- updated-dependencies: - dependency-name: google-github-actions/deploy-appengine dependency-version: 2.1.7 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/appengine_deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/appengine_deploy.yml b/.github/workflows/appengine_deploy.yml index 1dd6d2ffa92..408c1b08476 100644 --- a/.github/workflows/appengine_deploy.yml +++ b/.github/workflows/appengine_deploy.yml @@ -42,7 +42,7 @@ jobs: path: _deploy/ - name: Deploy to App Engine - uses: google-github-actions/deploy-appengine@v2.1.5 + uses: google-github-actions/deploy-appengine@v2.1.7 # For parameters see: # https://github.com/google-github-actions/deploy-appengine#inputs with: From 79d314049558cd776062afcd389aef6c651f095f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Aug 2025 17:39:48 +0100 Subject: [PATCH 31/57] chore(deps): bump actions/download-artifact from 4 to 5 (#9287) Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4 to 5. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/appengine_deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/appengine_deploy.yml b/.github/workflows/appengine_deploy.yml index 408c1b08476..50afec2407c 100644 --- a/.github/workflows/appengine_deploy.yml +++ b/.github/workflows/appengine_deploy.yml @@ -36,7 +36,7 @@ jobs: needs: prepare steps: - name: Download prepared files - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: name: appengine_files path: _deploy/ From b211c02e3cac05d37126f6024afc060509b5328b Mon Sep 17 00:00:00 2001 From: RoboErikG Date: Mon, 11 Aug 2025 10:10:10 -0700 Subject: [PATCH 32/57] Change browser test timeout to 2 hours --- .github/workflows/browser_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/browser_test.yml b/.github/workflows/browser_test.yml index 3675af7b042..e670af9260f 100644 --- a/.github/workflows/browser_test.yml +++ b/.github/workflows/browser_test.yml @@ -11,7 +11,7 @@ permissions: jobs: build: - timeout-minutes: 10 + timeout-minutes: 120 runs-on: ${{ matrix.os }} strategy: From fb63360b9f7dc646293ea6419545ab5a8942a933 Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Tue, 12 Aug 2025 08:55:54 -0700 Subject: [PATCH 33/57] refactor: Remove duplicated method from contextmenu_items.ts. (#9289) --- core/contextmenu_items.ts | 39 +++------------------------------------ 1 file changed, 3 insertions(+), 36 deletions(-) diff --git a/core/contextmenu_items.ts b/core/contextmenu_items.ts index 8bb71775fd7..001a3c58e25 100644 --- a/core/contextmenu_items.ts +++ b/core/contextmenu_items.ts @@ -23,6 +23,7 @@ import {CommentIcon} from './icons/comment_icon.js'; import {Msg} from './msg.js'; import {StatementInput} from './renderers/zelos/zelos.js'; import {Coordinate} from './utils/coordinate.js'; +import * as svgMath from './utils/svg_math.js'; import type {WorkspaceSvg} from './workspace_svg.js'; function isFullBlockField(block?: BlockSvg) { @@ -637,9 +638,9 @@ export function registerCommentCreate() { const comment = new RenderedWorkspaceComment(workspace); comment.setPlaceholderText(Msg['WORKSPACE_COMMENT_DEFAULT_TEXT']); comment.moveTo( - pixelsToWorkspaceCoords( - new Coordinate(location.x, location.y), + svgMath.screenToWsCoordinates( workspace, + new Coordinate(location.x, location.y), ), ); getFocusManager().focusNode(comment); @@ -652,40 +653,6 @@ export function registerCommentCreate() { ContextMenuRegistry.registry.register(createOption); } -/** - * Converts pixel coordinates (relative to the window) to workspace coordinates. - */ -function pixelsToWorkspaceCoords( - pixelCoord: Coordinate, - workspace: WorkspaceSvg, -): Coordinate { - const injectionDiv = workspace.getInjectionDiv(); - // Bounding rect coordinates are in client coordinates, meaning that they - // are in pixels relative to the upper left corner of the visible browser - // window. These coordinates change when you scroll the browser window. - const boundingRect = injectionDiv.getBoundingClientRect(); - - // The client coordinates offset by the injection div's upper left corner. - const clientOffsetPixels = new Coordinate( - pixelCoord.x - boundingRect.left, - pixelCoord.y - boundingRect.top, - ); - - // The offset in pixels between the main workspace's origin and the upper - // left corner of the injection div. - const mainOffsetPixels = workspace.getOriginOffsetInPixels(); - - // The position of the new comment in pixels relative to the origin of the - // main workspace. - const finalOffset = Coordinate.difference( - clientOffsetPixels, - mainOffsetPixels, - ); - // The position of the new comment in main workspace coordinates. - finalOffset.scale(1 / workspace.scale); - return finalOffset; -} - /** Registers all block-scoped context menu items. */ function registerBlockOptions_() { registerDuplicate(); From e74910c8a0b189fe01ac79d653008be0158e6374 Mon Sep 17 00:00:00 2001 From: RoboErikG Date: Tue, 12 Aug 2025 10:32:32 -0700 Subject: [PATCH 34/57] Update block-test version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3535ae06da9..d674b8637c3 100644 --- a/package.json +++ b/package.json @@ -100,7 +100,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "@blockly/block-test": "^7.0.1", + "@blockly/block-test": "^7.0.2", "@blockly/dev-tools": "^9.0.0", "@blockly/theme-modern": "^7.0.1", "@hyperjump/browser": "^1.1.4", From 4f4a450142923a9ba1f36c98836b549f556706f6 Mon Sep 17 00:00:00 2001 From: RoboErikG Date: Wed, 13 Aug 2025 09:39:24 -0700 Subject: [PATCH 35/57] Update dev-tools version in package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d674b8637c3..e7a496b31f8 100644 --- a/package.json +++ b/package.json @@ -101,7 +101,7 @@ "license": "Apache-2.0", "devDependencies": { "@blockly/block-test": "^7.0.2", - "@blockly/dev-tools": "^9.0.0", + "@blockly/dev-tools": "^9.0.2", "@blockly/theme-modern": "^7.0.1", "@hyperjump/browser": "^1.1.4", "@hyperjump/json-schema": "^1.5.0", From 7b784b58c0192cb45aa4e62ef8df072e713238f8 Mon Sep 17 00:00:00 2001 From: RoboErikG Date: Wed, 13 Aug 2025 11:06:10 -0700 Subject: [PATCH 36/57] Add a weekly schedule --- .github/workflows/browser_test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/browser_test.yml b/.github/workflows/browser_test.yml index e670af9260f..51ac0dffada 100644 --- a/.github/workflows/browser_test.yml +++ b/.github/workflows/browser_test.yml @@ -5,6 +5,8 @@ name: Run browser manually on: workflow_dispatch: + schedule: + - cron: '0 6 * * 1' # Runs every Monday at 06:00 UTC permissions: contents: read From 34ea176b88ce1d3f242a6930f9a58859e1d4480c Mon Sep 17 00:00:00 2001 From: Erik Pasternak Date: Wed, 13 Aug 2025 18:14:01 +0000 Subject: [PATCH 37/57] Update package-lock.json --- package-lock.json | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index b1ff8dcbd8b..e03f6d24d46 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,8 +13,8 @@ "jsdom": "26.1.0" }, "devDependencies": { - "@blockly/block-test": "^7.0.1", - "@blockly/dev-tools": "^9.0.0", + "@blockly/block-test": "^7.0.2", + "@blockly/dev-tools": "^9.0.2", "@blockly/theme-modern": "^7.0.1", "@hyperjump/browser": "^1.1.4", "@hyperjump/json-schema": "^1.5.0", @@ -90,10 +90,11 @@ "license": "ISC" }, "node_modules/@blockly/block-test": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@blockly/block-test/-/block-test-7.0.1.tgz", - "integrity": "sha512-w91ZZbpJDKGQJVO7gKqQaM17ffcsW1ktrnSTz/OpDw5R4H+1q05NgWO5gYzGPzLfFdvPcrkc0v00KhD4UG7BRA==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@blockly/block-test/-/block-test-7.0.2.tgz", + "integrity": "sha512-fwbJnMiH4EoX/CR0ZTGzSKaGfpRBn4nudquoWfvG4ekkhTjaNTldDdHvUSeyexzvwZZcT6M4I1Jtq3IoomTKEg==", "dev": true, + "license": "Apache 2.0", "engines": { "node": ">=8.17.0" }, @@ -102,13 +103,13 @@ } }, "node_modules/@blockly/dev-tools": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@blockly/dev-tools/-/dev-tools-9.0.1.tgz", - "integrity": "sha512-OnY24Up00owts0VtOaokUmOQdzH+K1PNcr3LC3huwa9PO0TlKiXTq4V5OuIqBS++enyj93gXQ8PhvFGudkogTQ==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@blockly/dev-tools/-/dev-tools-9.0.2.tgz", + "integrity": "sha512-Ic/+BkqEvLRZxzNQVW/FKXx1cB042xXXPTSmNlTv2qr4oY+hN2fwBtHj3PirBWAzWgMOF8VDTj/EXL36jH1/lg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@blockly/block-test": "^7.0.1", + "@blockly/block-test": "^7.0.2", "@blockly/theme-dark": "^8.0.1", "@blockly/theme-deuteranopia": "^7.0.1", "@blockly/theme-highcontrast": "^7.0.1", From 414f1056e8f576e86d7702ef4738f27bf9129311 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Aug 2025 14:26:32 +0000 Subject: [PATCH 38/57] chore(deps): bump actions/first-interaction from 2 to 3 Bumps [actions/first-interaction](https://github.com/actions/first-interaction) from 2 to 3. - [Release notes](https://github.com/actions/first-interaction/releases) - [Commits](https://github.com/actions/first-interaction/compare/v2...v3) --- updated-dependencies: - dependency-name: actions/first-interaction dependency-version: '3' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/welcome_new_contributors.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/welcome_new_contributors.yml b/.github/workflows/welcome_new_contributors.yml index 663f0320577..4c4860c25b0 100644 --- a/.github/workflows/welcome_new_contributors.yml +++ b/.github/workflows/welcome_new_contributors.yml @@ -9,7 +9,7 @@ jobs: permissions: pull-requests: write steps: - - uses: actions/first-interaction@v2 + - uses: actions/first-interaction@v3 with: repo-token: ${{ secrets.GITHUB_TOKEN }} pr-message: > From 86da7dcbfab72a84182f633295ce0e904b1bb9bd Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Thu, 21 Aug 2025 14:21:02 -0700 Subject: [PATCH 39/57] release: Update version number to 12.3.0-beta.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index e03f6d24d46..c7f681f2e38 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "blockly", - "version": "12.2.0", + "version": "12.3.0-beta.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "blockly", - "version": "12.2.0", + "version": "12.3.0-beta.0", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index e7a496b31f8..c122a32fe1f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "blockly", - "version": "12.2.0", + "version": "12.3.0-beta.0", "description": "Blockly is a library for building visual programming editors.", "keywords": [ "blockly" From 84933b9119b0d874d80a9e7f0fa0b34d6d10fa71 Mon Sep 17 00:00:00 2001 From: Maribeth Moffatt Date: Tue, 19 Aug 2025 11:22:17 -0400 Subject: [PATCH 40/57] chore: lint error on only in mocha tests (#9300) --- eslint.config.mjs | 5 +++++ package-lock.json | 26 ++++++++++++++++++++++++++ package.json | 1 + 3 files changed, 32 insertions(+) diff --git a/eslint.config.mjs b/eslint.config.mjs index f018e525d87..0560586cbcc 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,6 +1,7 @@ import eslint from '@eslint/js'; import googleStyle from 'eslint-config-google'; import jsdoc from 'eslint-plugin-jsdoc'; +import mochaPlugin from 'eslint-plugin-mocha'; import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'; import globals from 'globals'; import tseslint from 'typescript-eslint'; @@ -200,6 +201,9 @@ export default [ }, { files: ['tests/**'], + plugins: { + mocha: mochaPlugin, + }, languageOptions: { globals: { 'Blockly': true, @@ -219,6 +223,7 @@ export default [ 'jsdoc/check-tag-names': ['warn', {'definedTags': ['record']}], 'jsdoc/tag-lines': ['off'], 'jsdoc/no-defaults': ['off'], + 'mocha/no-exclusive-tests': 'error', }, }, { diff --git a/package-lock.json b/package-lock.json index c7f681f2e38..d30a69df659 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,6 +28,7 @@ "eslint-config-google": "^0.14.0", "eslint-config-prettier": "^10.1.1", "eslint-plugin-jsdoc": "^52.0.2", + "eslint-plugin-mocha": "^11.1.0", "eslint-plugin-prettier": "^5.2.1", "glob": "^11.0.1", "globals": "^16.0.0", @@ -4137,6 +4138,31 @@ "spdx-license-ids": "^3.0.0" } }, + "node_modules/eslint-plugin-mocha": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-11.1.0.tgz", + "integrity": "sha512-rKntVWRsQFPbf8OkSgVNRVRrcVAPaGTyEgWCEyXaPDJkTl0v5/lwu1vTk5sWiUJU8l2sxwvGUZzSNrEKdVMeQw==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.1", + "globals": "^15.14.0" + }, + "peerDependencies": { + "eslint": ">=9.0.0" + } + }, + "node_modules/eslint-plugin-mocha/node_modules/globals": { + "version": "15.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", + "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/eslint-plugin-prettier": { "version": "5.5.1", "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.1.tgz", diff --git a/package.json b/package.json index c122a32fe1f..6cfd025c714 100644 --- a/package.json +++ b/package.json @@ -115,6 +115,7 @@ "eslint-config-google": "^0.14.0", "eslint-config-prettier": "^10.1.1", "eslint-plugin-jsdoc": "^52.0.2", + "eslint-plugin-mocha": "^11.1.0", "eslint-plugin-prettier": "^5.2.1", "glob": "^11.0.1", "globals": "^16.0.0", From 29d5b43cd3ba1d210472c064c16e9a1c9dd7cddb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Aug 2025 08:28:11 -0700 Subject: [PATCH 41/57] chore(deps): bump actions/checkout from 4 to 5 (#9320) Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/appengine_deploy.yml | 2 +- .github/workflows/browser_test.yml | 2 +- .github/workflows/build.yml | 6 +++--- .github/workflows/keyboard_plugin_test.yml | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/appengine_deploy.yml b/.github/workflows/appengine_deploy.yml index 50afec2407c..efc6fe9417d 100644 --- a/.github/workflows/appengine_deploy.yml +++ b/.github/workflows/appengine_deploy.yml @@ -15,7 +15,7 @@ jobs: steps: # Checks-out the repository under $GITHUB_WORKSPACE. # When running manually this checks out the master branch. - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Prepare demo files # Install all dependencies, then copy all the files needed for demos. diff --git a/.github/workflows/browser_test.yml b/.github/workflows/browser_test.yml index 51ac0dffada..c2ce9913635 100644 --- a/.github/workflows/browser_test.yml +++ b/.github/workflows/browser_test.yml @@ -26,7 +26,7 @@ jobs: # https://nodejs.org/en/about/releases/ steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: persist-credentials: false diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c4ab688f8fd..d7a4e786ce6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,7 +23,7 @@ jobs: # https://nodejs.org/en/about/releases/ steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: persist-credentials: false @@ -54,7 +54,7 @@ jobs: timeout-minutes: 5 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Use Node.js 20.x uses: actions/setup-node@v4 @@ -71,7 +71,7 @@ jobs: timeout-minutes: 5 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Use Node.js 20.x uses: actions/setup-node@v4 diff --git a/.github/workflows/keyboard_plugin_test.yml b/.github/workflows/keyboard_plugin_test.yml index 753d31dda1e..3d7d3d5d444 100644 --- a/.github/workflows/keyboard_plugin_test.yml +++ b/.github/workflows/keyboard_plugin_test.yml @@ -25,12 +25,12 @@ jobs: steps: - name: Checkout core Blockly - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: path: core-blockly - name: Checkout keyboard navigation plugin - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: repository: 'google/blockly-keyboard-experimentation' ref: 'main' From 10b1d1e848239ee6eff6fb6a1dd3e2cf2ead590f Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Tue, 19 Aug 2025 08:32:31 -0700 Subject: [PATCH 42/57] fix: Fix positioning of pasted blocks and comments in RTL. (#9302) * fix: Fix positioning of pasted blocks in RTL. * fix: Clean up after temporarily making the workspace RTL. * fix: Remove .only. * fix: Fix positioning of pasted comments in RTL. * fix: Fix positioning of text preview on collapsed comments in RTL. --- core/clipboard/block_paster.ts | 3 ++ core/comments/comment_view.ts | 5 +++- core/xml.ts | 2 +- tests/mocha/clipboard_test.js | 51 ++++++++++++++++++++++++++++++++++ 4 files changed, 59 insertions(+), 2 deletions(-) diff --git a/core/clipboard/block_paster.ts b/core/clipboard/block_paster.ts index 750cedca124..e782cc0b004 100644 --- a/core/clipboard/block_paster.ts +++ b/core/clipboard/block_paster.ts @@ -83,6 +83,9 @@ export function moveBlockToNotConflict( block: BlockSvg, originalPosition: Coordinate, ) { + if (block.workspace.RTL) { + originalPosition.x = block.workspace.getWidth() - originalPosition.x; + } const workspace = block.workspace; const snapRadius = config.snapRadius; const bumpOffset = Coordinate.difference( diff --git a/core/comments/comment_view.ts b/core/comments/comment_view.ts index ca0c261c390..b1cd628f8dd 100644 --- a/core/comments/comment_view.ts +++ b/core/comments/comment_view.ts @@ -368,7 +368,10 @@ export class CommentView implements IRenderedElement { const textPreviewWidth = size.width - foldoutSize.getWidth() - deleteSize.getWidth(); - this.textPreview.setAttribute('x', `${foldoutSize.getWidth()}`); + this.textPreview.setAttribute( + 'x', + `${(this.workspace.RTL ? -1 : 1) * foldoutSize.getWidth()}`, + ); this.textPreview.setAttribute( 'y', `${textPreviewMargin + textPreviewSize.height / 2}`, diff --git a/core/xml.ts b/core/xml.ts index cc26d8c8a2c..362a59ab287 100644 --- a/core/xml.ts +++ b/core/xml.ts @@ -68,7 +68,7 @@ export function saveWorkspaceComment( if (!skipId) elem.setAttribute('id', comment.id); const workspace = comment.workspace; - const loc = comment.getRelativeToSurfaceXY(); + const loc = comment.getRelativeToSurfaceXY().clone(); loc.x = workspace.RTL ? workspace.getWidth() - loc.x : loc.x; elem.setAttribute('x', `${loc.x}`); elem.setAttribute('y', `${loc.y}`); diff --git a/tests/mocha/clipboard_test.js b/tests/mocha/clipboard_test.js index d58f78b9b50..5a513b44a9e 100644 --- a/tests/mocha/clipboard_test.js +++ b/tests/mocha/clipboard_test.js @@ -157,6 +157,34 @@ suite('Clipboard', function () { ); }); + test('pasted blocks are bumped to not overlap in RTL', function () { + this.workspace.dispose(); + this.workspace = Blockly.inject('blocklyDiv', {rtl: true}); + const block = Blockly.serialization.blocks.append( + { + 'type': 'controls_if', + 'x': 38, + 'y': 13, + }, + this.workspace, + ); + const data = block.toCopyData(); + + const newBlock = Blockly.clipboard.paste(data, this.workspace); + const oldBlockXY = block.getRelativeToSurfaceXY(); + assert.deepEqual( + newBlock.getRelativeToSurfaceXY(), + new Blockly.utils.Coordinate( + oldBlockXY.x - Blockly.config.snapRadius, + oldBlockXY.y + Blockly.config.snapRadius * 2, + ), + ); + + // Restore an LTR workspace. + this.workspace.dispose(); + this.workspace = Blockly.inject('blocklyDiv'); + }); + test('pasted blocks are bumped to be outside the connection snap radius', function () { Blockly.serialization.workspaces.load( { @@ -208,5 +236,28 @@ suite('Clipboard', function () { new Blockly.utils.Coordinate(40, 40), ); }); + + test('pasted comments are bumped to not overlap in RTL', function () { + this.workspace.dispose(); + this.workspace = Blockly.inject('blocklyDiv', {rtl: true}); + Blockly.Xml.domToWorkspace( + Blockly.utils.xml.textToDom( + '', + ), + this.workspace, + ); + const comment = this.workspace.getTopComments(false)[0]; + const data = comment.toCopyData(); + + const newComment = Blockly.clipboard.paste(data, this.workspace); + const oldCommentXY = comment.getRelativeToSurfaceXY(); + assert.deepEqual( + newComment.getRelativeToSurfaceXY(), + new Blockly.utils.Coordinate(oldCommentXY.x - 30, oldCommentXY.y + 30), + ); + // Restore an LTR workspace. + this.workspace.dispose(); + this.workspace = Blockly.inject('blocklyDiv'); + }); }); }); From 9e1db9e332b6b2c609c22fff27cede7bb8b3acc7 Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Tue, 19 Aug 2025 14:56:59 -0700 Subject: [PATCH 43/57] chore: Fix documentation generation warnings. (#9325) * chore: Replace @yields with @returns. * fix: Update the ESLint config to not require @yields. * chore: Move docs onto getters. --- core/block.ts | 2 +- core/field.ts | 3 --- core/field_input.ts | 6 ++---- eslint.config.mjs | 3 ++- 4 files changed, 5 insertions(+), 9 deletions(-) diff --git a/core/block.ts b/core/block.ts index d15700a7e19..af44facda5d 100644 --- a/core/block.ts +++ b/core/block.ts @@ -1126,7 +1126,7 @@ export class Block { /** * Returns a generator that provides every field on the block. * - * @yields A generator that can be used to iterate the fields on the block. + * @returns A generator that can be used to iterate the fields on the block. */ *getFields(): Generator { for (const input of this.inputList) { diff --git a/core/field.ts b/core/field.ts index fdcb2d693b9..3d12880a93a 100644 --- a/core/field.ts +++ b/core/field.ts @@ -119,9 +119,6 @@ export abstract class Field return this.size; } - /** - * Sets the size of this field. - */ protected set size_(newValue: Size) { this.size = newValue; } diff --git a/core/field_input.ts b/core/field_input.ts index b685309183a..97ad0e9594d 100644 --- a/core/field_input.ts +++ b/core/field_input.ts @@ -102,11 +102,9 @@ export abstract class FieldInput extends Field< */ override SERIALIZABLE = true; - /** - * Sets the size of this field. Although this appears to be a no-op, it must - * exist since the getter is overridden below. - */ protected override set size_(newValue: Size) { + // Although this appears to be a no-op, it must exist since the getter is + // overridden below. super.size_ = newValue; } diff --git a/eslint.config.mjs b/eslint.config.mjs index 0560586cbcc..744e02b45bf 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -89,7 +89,8 @@ function buildTSOverride({files, tsconfig}) { '@typescript-eslint/no-explicit-any': ['off'], // We use this pattern extensively for block (e.g. controls_if) interfaces. '@typescript-eslint/no-empty-object-type': ['off'], - + // TSDoc doesn't support @yields, so don't require that we use it. + 'jsdoc/require-yields': ['off'], // params and returns docs are optional. 'jsdoc/require-param-description': ['off'], 'jsdoc/require-returns': ['off'], From 4a0b710f7c2f7e5f9596e1476f0dc9ffe168344b Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Wed, 20 Aug 2025 11:26:45 -0700 Subject: [PATCH 44/57] fix: Show the delete cursor when dragging a block by an editable field. (#9326) --- core/css.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/css.ts b/core/css.ts index 30ee47fc58a..503b6362ba2 100644 --- a/core/css.ts +++ b/core/css.ts @@ -181,7 +181,8 @@ let content = ` cursor: -webkit-grabbing; } -.blocklyDragging.blocklyDraggingDelete { +.blocklyDragging.blocklyDraggingDelete, +.blocklyDragging.blocklyDraggingDelete .blocklyField { cursor: url("<<>>/handdelete.cur"), auto; } From cf93f0721b244e206c3b5287a65dae4cc0fd1eda Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Thu, 21 Aug 2025 11:05:43 -0700 Subject: [PATCH 45/57] fix: Correct the alignment of narrow text in input fields. (#9327) * fix: Correct the alignment of narrow text in input fields. * chore: Clarify purpose of first argument to positionTextElement_(). --- core/field.ts | 3 +-- core/field_dropdown.ts | 7 +++---- core/field_input.ts | 26 ++++++++++++++++++++++++-- 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/core/field.ts b/core/field.ts index 3d12880a93a..d993e197d23 100644 --- a/core/field.ts +++ b/core/field.ts @@ -849,8 +849,7 @@ export abstract class Field totalHeight = Math.max(totalHeight, constants!.FIELD_BORDER_RECT_HEIGHT); } - this.size_.height = totalHeight; - this.size_.width = totalWidth; + this.size_ = new Size(totalWidth, totalHeight); this.positionTextElement_(xOffset, contentWidth); this.positionBorderRect_(); diff --git a/core/field_dropdown.ts b/core/field_dropdown.ts index 8b01ccddab1..3be5c94c3e3 100644 --- a/core/field_dropdown.ts +++ b/core/field_dropdown.ts @@ -29,6 +29,7 @@ import * as aria from './utils/aria.js'; import {Coordinate} from './utils/coordinate.js'; import * as dom from './utils/dom.js'; import * as parsing from './utils/parsing.js'; +import {Size} from './utils/size.js'; import * as utilsString from './utils/string.js'; import {Svg} from './utils/svg.js'; @@ -553,8 +554,7 @@ export class FieldDropdown extends Field { } else { arrowWidth = dom.getTextWidth(this.arrow as SVGTSpanElement); } - this.size_.width = imageWidth + arrowWidth + xPadding * 2; - this.size_.height = height; + this.size_ = new Size(imageWidth + arrowWidth + xPadding * 2, height); let arrowX = 0; if (block.RTL) { @@ -595,8 +595,7 @@ export class FieldDropdown extends Field { height / 2 - this.getConstants()!.FIELD_DROPDOWN_SVG_ARROW_SIZE / 2, ); } - this.size_.width = textWidth + arrowWidth + xPadding * 2; - this.size_.height = height; + this.size_ = new Size(textWidth + arrowWidth + xPadding * 2, height); this.positionTextElement_(xPadding, textWidth); } diff --git a/core/field_input.ts b/core/field_input.ts index 97ad0e9594d..55383a4c1d2 100644 --- a/core/field_input.ts +++ b/core/field_input.ts @@ -45,6 +45,11 @@ import type {WorkspaceSvg} from './workspace_svg.js'; */ type InputTypes = string | number; +/** + * The minimum width of an input field. + */ +const MINIMUM_WIDTH = 14; + /** * Abstract class for an editable input field. * @@ -113,8 +118,8 @@ export abstract class FieldInput extends Field< */ protected override get size_() { const s = super.size_; - if (s.width < 14) { - s.width = 14; + if (s.width < MINIMUM_WIDTH) { + s.width = MINIMUM_WIDTH; } return s; @@ -730,6 +735,23 @@ export abstract class FieldInput extends Field< return true; } + /** + * Position a field's text element after a size change. This handles both LTR + * and RTL positioning. + * + * @param xMargin x offset to use when positioning the text element. + * @param contentWidth The content width. + */ + protected override positionTextElement_( + xMargin: number, + contentWidth: number, + ) { + const effectiveWidth = xMargin * 2 + contentWidth; + const delta = + effectiveWidth < MINIMUM_WIDTH ? (MINIMUM_WIDTH - effectiveWidth) / 2 : 0; + super.positionTextElement_(xMargin + delta, contentWidth); + } + /** * Use the `getText_` developer hook to override the field's text * representation. When we're currently editing, return the current HTML value From 4891659d6e6ff8c82ec09dfc36ecf0d5b5cfe7be Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Thu, 21 Aug 2025 11:15:07 -0700 Subject: [PATCH 46/57] fix: Fix bug that caused inadvertent scrolling when the `WidgetDiv` was shown. (#9291) * fix: Fix bug that caused inadvertent scrolling when the `WidgetDiv` was shown. * chore: Add test to verify that displaying the context menu does not scroll the page. * chore: Clarify comments. * fix: Remove errant `.only`. * chore: Add test to verify that actively focusing a node does not scroll the page. * fix: Remove inadvertent `.only`. --- core/focus_manager.ts | 8 +++- core/interfaces/i_focusable_node.ts | 3 ++ tests/browser/test/basic_playground_test.mjs | 39 ++++++++++++++++++++ 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/core/focus_manager.ts b/core/focus_manager.ts index 02e0591070f..47e4324540d 100644 --- a/core/focus_manager.ts +++ b/core/focus_manager.ts @@ -309,6 +309,8 @@ export class FocusManager { * Note that this may update the specified node's element's tabindex to ensure * that it can be properly read out by screenreaders while focused. * + * The focused node will not be automatically scrolled into view. + * * @param focusableNode The node that should receive active focus. */ focusNode(focusableNode: IFocusableNode): void { @@ -423,6 +425,8 @@ export class FocusManager { * the returned lambda is called. Additionally, only 1 ephemeral focus context * can be active at any given time (attempting to activate more than one * simultaneously will result in an error being thrown). + * + * This method does not scroll the ephemerally focused element into view. */ takeEphemeralFocus( focusableElement: HTMLElement | SVGElement, @@ -439,7 +443,7 @@ export class FocusManager { if (this.focusedNode) { this.passivelyFocusNode(this.focusedNode, null); } - focusableElement.focus(); + focusableElement.focus({preventScroll: true}); let hasFinishedEphemeralFocus = false; return () => { @@ -574,7 +578,7 @@ export class FocusManager { } this.setNodeToVisualActiveFocus(node); - elem.focus(); + elem.focus({preventScroll: true}); } /** diff --git a/core/interfaces/i_focusable_node.ts b/core/interfaces/i_focusable_node.ts index 24833328d7f..57ec1a126e1 100644 --- a/core/interfaces/i_focusable_node.ts +++ b/core/interfaces/i_focusable_node.ts @@ -59,6 +59,9 @@ export interface IFocusableNode { * they should avoid the following: * - Creating or removing DOM elements (including via the renderer or drawer). * - Affecting focus via DOM focus() calls or the FocusManager. + * + * Implementations may consider scrolling themselves into view here; that is + * not handled by the focus manager. */ onNodeFocus(): void; diff --git a/tests/browser/test/basic_playground_test.mjs b/tests/browser/test/basic_playground_test.mjs index 4c54523bd7f..c7c8a5a370c 100644 --- a/tests/browser/test/basic_playground_test.mjs +++ b/tests/browser/test/basic_playground_test.mjs @@ -101,6 +101,45 @@ suite('Right Clicking on Blocks', function () { await contextMenuSelect(this.browser, this.block, 'Remove Comment'); chai.assert.isNull(await getCommentText(this.browser, this.block.id)); }); + + test('does not scroll the page when node is ephemerally focused', async function () { + const initialScroll = await this.browser.execute(() => { + return window.scrollY; + }); + // This left-right-left sequence was necessary to reproduce unintended + // scrolling; regardless of the number of clicks/context menu activations, + // the page should not scroll. + this.block.click({button: 2}); + this.block.click({button: 0}); + this.block.click({button: 2}); + await this.browser.pause(250); + const finalScroll = await this.browser.execute(() => { + return window.scrollY; + }); + + chai.assert.equal(initialScroll, finalScroll); + }); + + test('does not scroll the page when node is actively focused', async function () { + await this.browser.setWindowSize(500, 300); + await this.browser.setViewport({width: 500, height: 300}); + const initialScroll = await this.browser.execute((blockId) => { + window.scrollTo(0, document.body.scrollHeight); + return window.scrollY; + }, this.block.id); + await this.browser.execute(() => { + Blockly.getFocusManager().focusNode( + Blockly.getMainWorkspace().getToolbox(), + ); + }); + const finalScroll = await this.browser.execute(() => { + return window.scrollY; + }); + + chai.assert.equal(initialScroll, finalScroll); + await this.browser.setWindowSize(800, 600); + await this.browser.setViewport({width: 800, height: 600}); + }); }); suite('Disabling', function () { From c32f6db02287e0140350f7693fc15888ffae656e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Aug 2025 12:06:10 -0700 Subject: [PATCH 47/57] chore(deps): bump eslint-plugin-prettier from 5.5.1 to 5.5.4 (#9319) Bumps [eslint-plugin-prettier](https://github.com/prettier/eslint-plugin-prettier) from 5.5.1 to 5.5.4. - [Release notes](https://github.com/prettier/eslint-plugin-prettier/releases) - [Changelog](https://github.com/prettier/eslint-plugin-prettier/blob/main/CHANGELOG.md) - [Commits](https://github.com/prettier/eslint-plugin-prettier/compare/v5.5.1...v5.5.4) --- updated-dependencies: - dependency-name: eslint-plugin-prettier dependency-version: 5.5.4 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index d30a69df659..285fbba583b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4164,10 +4164,11 @@ } }, "node_modules/eslint-plugin-prettier": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.1.tgz", - "integrity": "sha512-dobTkHT6XaEVOo8IO90Q4DOSxnm3Y151QxPJlM/vKC0bVy+d6cVWQZLlFiuZPP0wS6vZwSKeJgKkcS+KfMBlRw==", + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.4.tgz", + "integrity": "sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg==", "dev": true, + "license": "MIT", "dependencies": { "prettier-linter-helpers": "^1.0.0", "synckit": "^0.11.7" From be5f5a2a0a3d8271c15eaf6ece4c73023a184d88 Mon Sep 17 00:00:00 2001 From: Ennis Nian Date: Fri, 22 Aug 2025 04:59:09 +0800 Subject: [PATCH 48/57] fix: pointercancel event is not handled (#9250) --- core/touch.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/core/touch.ts b/core/touch.ts index 8fb2cd2298c..9af3b1f9494 100644 --- a/core/touch.ts +++ b/core/touch.ts @@ -46,6 +46,7 @@ export const TOUCH_MAP: {[key: string]: string[]} = { 'mouseup': ['pointerup', 'pointercancel'], 'touchend': ['pointerup'], 'touchcancel': ['pointercancel'], + 'pointerup': ['pointerup', 'pointercancel'], }; /** PID of queued long-press task. */ From e358f4e7eeeffe93b995d6d5df0d83523a5a388d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 22 Aug 2025 20:01:03 +0100 Subject: [PATCH 49/57] chore(deps): bump eslint-config-prettier from 10.1.5 to 10.1.8 (#9321) Bumps [eslint-config-prettier](https://github.com/prettier/eslint-config-prettier) from 10.1.5 to 10.1.8. - [Release notes](https://github.com/prettier/eslint-config-prettier/releases) - [Changelog](https://github.com/prettier/eslint-config-prettier/blob/main/CHANGELOG.md) - [Commits](https://github.com/prettier/eslint-config-prettier/compare/v10.1.5...v10.1.8) --- updated-dependencies: - dependency-name: eslint-config-prettier dependency-version: 10.1.8 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 285fbba583b..0f8f34104fc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4076,10 +4076,11 @@ } }, "node_modules/eslint-config-prettier": { - "version": "10.1.5", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.5.tgz", - "integrity": "sha512-zc1UmCpNltmVY34vuLRV61r1K27sWuX39E+uyUnY8xS2Bex88VV9cugG+UZbRSRGtGyFboj+D8JODyme1plMpw==", + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, + "license": "MIT", "bin": { "eslint-config-prettier": "bin/cli.js" }, From cb698928251738e2c912b197479813c535c2f57f Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Fri, 22 Aug 2025 14:55:07 -0700 Subject: [PATCH 50/57] fix: Allow reregistering fields. (#9290) --- core/field_registry.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/field_registry.ts b/core/field_registry.ts index 06bb9acd045..e02ece75c96 100644 --- a/core/field_registry.ts +++ b/core/field_registry.ts @@ -56,11 +56,11 @@ export interface RegistrableField { * @param type The field type name as used in the JSON definition. * @param fieldClass The field class containing a fromJson function that can * construct an instance of the field. - * @throws {Error} if the type name is empty, the field is already registered, - * or the fieldClass is not an object containing a fromJson function. + * @throws {Error} if the type name is empty or the fieldClass is not an object + * containing a fromJson function. */ export function register(type: string, fieldClass: RegistrableField) { - registry.register(registry.Type.FIELD, type, fieldClass); + registry.register(registry.Type.FIELD, type, fieldClass, true); } /** From aeb3e5e1435e0fc5769cea62170de35c2b8e84a6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Aug 2025 09:08:01 -0700 Subject: [PATCH 51/57] chore(deps): bump chai from 5.2.1 to 6.0.1 (#9330) * chore(deps): bump chai from 5.2.1 to 6.0.1 Bumps [chai](https://github.com/chaijs/chai) from 5.2.1 to 6.0.1. - [Release notes](https://github.com/chaijs/chai/releases) - [Changelog](https://github.com/chaijs/chai/blob/main/History.md) - [Commits](https://github.com/chaijs/chai/compare/v5.2.1...v6.0.1) --- updated-dependencies: - dependency-name: chai dependency-version: 6.0.1 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * fix: Fix Chai import path. --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Aaron Dodson --- package-lock.json | 60 ++----------------- package.json | 2 +- tests/mocha/block_json_test.js | 2 +- tests/mocha/block_test.js | 2 +- tests/mocha/blocks/lists_test.js | 2 +- tests/mocha/blocks/logic_ternary_test.js | 2 +- tests/mocha/blocks/loops_test.js | 2 +- tests/mocha/blocks/procedures_test.js | 2 +- tests/mocha/blocks/variables_test.js | 2 +- tests/mocha/clipboard_test.js | 2 +- tests/mocha/comment_deserialization_test.js | 2 +- tests/mocha/comment_test.js | 2 +- tests/mocha/comment_view_test.js | 2 +- tests/mocha/connection_checker_test.js | 2 +- tests/mocha/connection_db_test.js | 2 +- tests/mocha/connection_test.js | 2 +- tests/mocha/contextmenu_items_test.js | 2 +- tests/mocha/contextmenu_test.js | 2 +- tests/mocha/cursor_test.js | 2 +- tests/mocha/dialog_test.js | 2 +- tests/mocha/dropdowndiv_test.js | 2 +- tests/mocha/event_block_change_test.js | 2 +- tests/mocha/event_block_create_test.js | 2 +- tests/mocha/event_block_delete_test.js | 2 +- tests/mocha/event_block_drag_test.js | 2 +- ...nt_block_field_intermediate_change_test.js | 2 +- tests/mocha/event_block_move_test.js | 2 +- tests/mocha/event_bubble_open_test.js | 2 +- tests/mocha/event_click_test.js | 2 +- tests/mocha/event_comment_change_test.js | 2 +- tests/mocha/event_comment_collapse_test.js | 2 +- tests/mocha/event_comment_create_test.js | 2 +- tests/mocha/event_comment_delete_test.js | 2 +- tests/mocha/event_comment_drag_test.js | 2 +- tests/mocha/event_comment_move_test.js | 2 +- tests/mocha/event_comment_resize_test.js | 2 +- tests/mocha/event_selected_test.js | 2 +- tests/mocha/event_test.js | 2 +- tests/mocha/event_theme_change_test.js | 2 +- tests/mocha/event_toolbox_item_select_test.js | 2 +- tests/mocha/event_trashcan_open_test.js | 2 +- tests/mocha/event_var_create_test.js | 2 +- tests/mocha/event_var_delete_test.js | 2 +- tests/mocha/event_var_rename_test.js | 2 +- tests/mocha/event_var_type_change_test.js | 2 +- tests/mocha/event_viewport_test.js | 2 +- tests/mocha/extensions_test.js | 2 +- tests/mocha/field_checkbox_test.js | 2 +- tests/mocha/field_colour_test.js | 2 +- tests/mocha/field_dropdown_test.js | 2 +- tests/mocha/field_image_test.js | 2 +- tests/mocha/field_label_serializable_test.js | 2 +- tests/mocha/field_label_test.js | 2 +- tests/mocha/field_number_test.js | 2 +- tests/mocha/field_registry_test.js | 2 +- tests/mocha/field_test.js | 2 +- tests/mocha/field_textinput_test.js | 2 +- tests/mocha/field_variable_test.js | 2 +- tests/mocha/flyout_test.js | 2 +- tests/mocha/focus_manager_test.js | 2 +- tests/mocha/focusable_tree_traverser_test.js | 2 +- tests/mocha/generator_test.js | 2 +- tests/mocha/gesture_test.js | 2 +- tests/mocha/icon_test.js | 2 +- tests/mocha/input_test.js | 2 +- tests/mocha/insertion_marker_test.js | 2 +- tests/mocha/jso_deserialization_test.js | 2 +- tests/mocha/jso_serialization_test.js | 2 +- tests/mocha/json_test.js | 2 +- .../keyboard_navigation_controller_test.js | 2 +- tests/mocha/layering_test.js | 2 +- tests/mocha/metrics_test.js | 2 +- tests/mocha/mutator_test.js | 2 +- tests/mocha/names_test.js | 2 +- tests/mocha/navigation_test.js | 2 +- tests/mocha/old_workspace_comment_test.js | 2 +- tests/mocha/procedure_map_test.js | 2 +- tests/mocha/rect_test.js | 2 +- tests/mocha/registry_test.js | 2 +- tests/mocha/render_management_test.js | 2 +- tests/mocha/serializer_test.js | 2 +- tests/mocha/shortcut_items_test.js | 2 +- tests/mocha/shortcut_registry_test.js | 2 +- tests/mocha/test_helpers/code_generation.js | 2 +- tests/mocha/test_helpers/events.js | 2 +- tests/mocha/test_helpers/fields.js | 2 +- tests/mocha/test_helpers/procedures.js | 2 +- tests/mocha/test_helpers/serialization.js | 2 +- tests/mocha/test_helpers/variables.js | 2 +- tests/mocha/test_helpers/warnings.js | 2 +- tests/mocha/test_helpers/workspace.js | 2 +- tests/mocha/theme_test.js | 2 +- tests/mocha/toast_test.js | 2 +- tests/mocha/toolbox_test.js | 2 +- tests/mocha/tooltip_test.js | 2 +- tests/mocha/touch_test.js | 2 +- tests/mocha/trashcan_test.js | 2 +- tests/mocha/utils_test.js | 2 +- tests/mocha/variable_map_test.js | 2 +- tests/mocha/variable_model_test.js | 2 +- tests/mocha/widget_div_test.js | 2 +- tests/mocha/workspace_comment_test.js | 2 +- tests/mocha/workspace_svg_test.js | 2 +- tests/mocha/xml_test.js | 2 +- tests/mocha/zoom_controls_test.js | 2 +- 105 files changed, 108 insertions(+), 160 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0f8f34104fc..f7a8f9426b9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,7 @@ "@microsoft/api-extractor": "^7.29.5", "ajv": "^8.17.1", "async-done": "^2.0.0", - "chai": "^5.1.1", + "chai": "^6.0.1", "concurrently": "^9.0.1", "eslint": "^9.15.0", "eslint-config-google": "^0.14.0", @@ -2489,15 +2489,6 @@ "node": ">=0.10.0" } }, - "node_modules/assertion-error": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", - "dev": true, - "engines": { - "node": ">=12" - } - }, "node_modules/assign-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", @@ -2882,18 +2873,11 @@ } }, "node_modules/chai": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.1.tgz", - "integrity": "sha512-5nFxhUrX0PqtyogoYOA8IPswy5sZFTOsBFl/9bNsmDLgsxYTzSZQJDPppDnZPTQbzSEm0hqGjWPzRemQCYbD6A==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.0.1.tgz", + "integrity": "sha512-/JOoU2//6p5vCXh00FpNgtlw0LjvhGttaWc+y7wpW9yjBm3ys0dI8tSKZxIOgNruz5J0RleccatSIC3uxEZP0g==", "dev": true, "license": "MIT", - "dependencies": { - "assertion-error": "^2.0.1", - "check-error": "^2.1.1", - "deep-eql": "^5.0.1", - "loupe": "^3.1.0", - "pathval": "^2.0.0" - }, "engines": { "node": ">=18" } @@ -2914,15 +2898,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/check-error": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", - "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", - "dev": true, - "engines": { - "node": ">= 16" - } - }, "node_modules/cheerio": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0.tgz", @@ -3586,15 +3561,6 @@ "node": ">=0.10" } }, - "node_modules/deep-eql": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", - "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -6794,15 +6760,6 @@ "dev": true, "license": "MIT" }, - "node_modules/loupe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.1.tgz", - "integrity": "sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==", - "dev": true, - "dependencies": { - "get-func-name": "^2.0.1" - } - }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -7731,15 +7688,6 @@ "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", "dev": true }, - "node_modules/pathval": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", - "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", - "dev": true, - "engines": { - "node": ">= 14.16" - } - }, "node_modules/pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", diff --git a/package.json b/package.json index 6cfd025c714..c52a79003e9 100644 --- a/package.json +++ b/package.json @@ -109,7 +109,7 @@ "@microsoft/api-extractor": "^7.29.5", "ajv": "^8.17.1", "async-done": "^2.0.0", - "chai": "^5.1.1", + "chai": "^6.0.1", "concurrently": "^9.0.1", "eslint": "^9.15.0", "eslint-config-google": "^0.14.0", diff --git a/tests/mocha/block_json_test.js b/tests/mocha/block_json_test.js index 4baccef6b7b..31abd6e3484 100644 --- a/tests/mocha/block_json_test.js +++ b/tests/mocha/block_json_test.js @@ -5,7 +5,7 @@ */ import {Align} from '../../build/src/core/inputs/align.js'; -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { sharedTestSetup, sharedTestTeardown, diff --git a/tests/mocha/block_test.js b/tests/mocha/block_test.js index e3bd470902d..1f8f9b1ee41 100644 --- a/tests/mocha/block_test.js +++ b/tests/mocha/block_test.js @@ -11,7 +11,7 @@ import {IconType} from '../../build/src/core/icons/icon_types.js'; import {EndRowInput} from '../../build/src/core/inputs/end_row_input.js'; import {isCommentIcon} from '../../build/src/core/interfaces/i_comment_icon.js'; import {Size} from '../../build/src/core/utils/size.js'; -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import {createRenderedBlock} from './test_helpers/block_definitions.js'; import { createChangeListenerSpy, diff --git a/tests/mocha/blocks/lists_test.js b/tests/mocha/blocks/lists_test.js index 490109d22ca..e749fae90a7 100644 --- a/tests/mocha/blocks/lists_test.js +++ b/tests/mocha/blocks/lists_test.js @@ -5,7 +5,7 @@ */ import {ConnectionType} from '../../../build/src/core/connection_type.js'; -import {assert} from '../../../node_modules/chai/chai.js'; +import {assert} from '../../../node_modules/chai/index.js'; import {defineStatementBlock} from '../test_helpers/block_definitions.js'; import {runSerializationTestSuite} from '../test_helpers/serialization.js'; import { diff --git a/tests/mocha/blocks/logic_ternary_test.js b/tests/mocha/blocks/logic_ternary_test.js index 71920935981..3d343a7caec 100644 --- a/tests/mocha/blocks/logic_ternary_test.js +++ b/tests/mocha/blocks/logic_ternary_test.js @@ -5,7 +5,7 @@ */ import * as eventUtils from '../../../build/src/core/events/utils.js'; -import {assert} from '../../../node_modules/chai/chai.js'; +import {assert} from '../../../node_modules/chai/index.js'; import {runSerializationTestSuite} from '../test_helpers/serialization.js'; import { sharedTestSetup, diff --git a/tests/mocha/blocks/loops_test.js b/tests/mocha/blocks/loops_test.js index f8d74916c29..eb040c884a7 100644 --- a/tests/mocha/blocks/loops_test.js +++ b/tests/mocha/blocks/loops_test.js @@ -5,7 +5,7 @@ */ import * as Blockly from '../../../build/src/core/blockly.js'; -import {assert} from '../../../node_modules/chai/chai.js'; +import {assert} from '../../../node_modules/chai/index.js'; import { sharedTestSetup, sharedTestTeardown, diff --git a/tests/mocha/blocks/procedures_test.js b/tests/mocha/blocks/procedures_test.js index 4b20662cf93..5ac651fe4cd 100644 --- a/tests/mocha/blocks/procedures_test.js +++ b/tests/mocha/blocks/procedures_test.js @@ -5,7 +5,7 @@ */ import * as Blockly from '../../../build/src/core/blockly.js'; -import {assert} from '../../../node_modules/chai/chai.js'; +import {assert} from '../../../node_modules/chai/index.js'; import {defineRowBlock} from '../test_helpers/block_definitions.js'; import { assertCallBlockStructure, diff --git a/tests/mocha/blocks/variables_test.js b/tests/mocha/blocks/variables_test.js index d12691dd476..a317fe11b52 100644 --- a/tests/mocha/blocks/variables_test.js +++ b/tests/mocha/blocks/variables_test.js @@ -5,7 +5,7 @@ */ import {nameUsedWithConflictingParam} from '../../../build/src/core/variables.js'; -import {assert} from '../../../node_modules/chai/chai.js'; +import {assert} from '../../../node_modules/chai/index.js'; import { MockParameterModelWithVar, MockProcedureModel, diff --git a/tests/mocha/clipboard_test.js b/tests/mocha/clipboard_test.js index 5a513b44a9e..ff49c0e303c 100644 --- a/tests/mocha/clipboard_test.js +++ b/tests/mocha/clipboard_test.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { assertEventFired, createChangeListenerSpy, diff --git a/tests/mocha/comment_deserialization_test.js b/tests/mocha/comment_deserialization_test.js index 54ee0b2ff30..f834eb0f301 100644 --- a/tests/mocha/comment_deserialization_test.js +++ b/tests/mocha/comment_deserialization_test.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { sharedTestSetup, sharedTestTeardown, diff --git a/tests/mocha/comment_test.js b/tests/mocha/comment_test.js index 0ff1c239e30..1f52df8fd52 100644 --- a/tests/mocha/comment_test.js +++ b/tests/mocha/comment_test.js @@ -5,7 +5,7 @@ */ import {EventType} from '../../build/src/core/events/type.js'; -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import {assertEventFired} from './test_helpers/events.js'; import { sharedTestSetup, diff --git a/tests/mocha/comment_view_test.js b/tests/mocha/comment_view_test.js index 57a24742457..a60a7a973ff 100644 --- a/tests/mocha/comment_view_test.js +++ b/tests/mocha/comment_view_test.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { sharedTestSetup, sharedTestTeardown, diff --git a/tests/mocha/connection_checker_test.js b/tests/mocha/connection_checker_test.js index fee2966d766..bdbcb70a6ec 100644 --- a/tests/mocha/connection_checker_test.js +++ b/tests/mocha/connection_checker_test.js @@ -5,7 +5,7 @@ */ import {ConnectionType} from '../../build/src/core/connection_type.js'; -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { sharedTestSetup, sharedTestTeardown, diff --git a/tests/mocha/connection_db_test.js b/tests/mocha/connection_db_test.js index 04f685124ca..459c59e3ab4 100644 --- a/tests/mocha/connection_db_test.js +++ b/tests/mocha/connection_db_test.js @@ -6,7 +6,7 @@ import {ConnectionType} from '../../build/src/core/connection_type.js'; import * as idGenerator from '../../build/src/core/utils/idgenerator.js'; -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { sharedTestSetup, sharedTestTeardown, diff --git a/tests/mocha/connection_test.js b/tests/mocha/connection_test.js index cefea1784e7..b36f358eac3 100644 --- a/tests/mocha/connection_test.js +++ b/tests/mocha/connection_test.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { defineRowBlock, defineStackBlock, diff --git a/tests/mocha/contextmenu_items_test.js b/tests/mocha/contextmenu_items_test.js index d9044ec7e28..08ab5d5267b 100644 --- a/tests/mocha/contextmenu_items_test.js +++ b/tests/mocha/contextmenu_items_test.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { sharedTestSetup, sharedTestTeardown, diff --git a/tests/mocha/contextmenu_test.js b/tests/mocha/contextmenu_test.js index 65896112bb1..df5bf79dc35 100644 --- a/tests/mocha/contextmenu_test.js +++ b/tests/mocha/contextmenu_test.js @@ -6,7 +6,7 @@ import {callbackFactory} from '../../build/src/core/contextmenu.js'; import * as xmlUtils from '../../build/src/core/utils/xml.js'; -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { sharedTestSetup, sharedTestTeardown, diff --git a/tests/mocha/cursor_test.js b/tests/mocha/cursor_test.js index 6f841ae09c6..02426ae26b8 100644 --- a/tests/mocha/cursor_test.js +++ b/tests/mocha/cursor_test.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import {createRenderedBlock} from './test_helpers/block_definitions.js'; import { sharedTestSetup, diff --git a/tests/mocha/dialog_test.js b/tests/mocha/dialog_test.js index f250ff0f8aa..7d4147d83f8 100644 --- a/tests/mocha/dialog_test.js +++ b/tests/mocha/dialog_test.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { sharedTestSetup, sharedTestTeardown, diff --git a/tests/mocha/dropdowndiv_test.js b/tests/mocha/dropdowndiv_test.js index fc792fbaf24..495237f18bc 100644 --- a/tests/mocha/dropdowndiv_test.js +++ b/tests/mocha/dropdowndiv_test.js @@ -6,7 +6,7 @@ import {Rect} from '../../build/src/core/utils/rect.js'; import * as style from '../../build/src/core/utils/style.js'; -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { sharedTestSetup, sharedTestTeardown, diff --git a/tests/mocha/event_block_change_test.js b/tests/mocha/event_block_change_test.js index 7de0a23b607..9e1f9c3103e 100644 --- a/tests/mocha/event_block_change_test.js +++ b/tests/mocha/event_block_change_test.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import {defineMutatorBlocks} from './test_helpers/block_definitions.js'; import { sharedTestSetup, diff --git a/tests/mocha/event_block_create_test.js b/tests/mocha/event_block_create_test.js index f59f9435efd..1672b56bb98 100644 --- a/tests/mocha/event_block_create_test.js +++ b/tests/mocha/event_block_create_test.js @@ -5,7 +5,7 @@ */ import {EventType} from '../../build/src/core/events/type.js'; -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import {defineRowBlock} from './test_helpers/block_definitions.js'; import {assertEventFired} from './test_helpers/events.js'; import { diff --git a/tests/mocha/event_block_delete_test.js b/tests/mocha/event_block_delete_test.js index d74b6aa062b..e2fb5b8ce88 100644 --- a/tests/mocha/event_block_delete_test.js +++ b/tests/mocha/event_block_delete_test.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import {defineRowBlock} from './test_helpers/block_definitions.js'; import { sharedTestSetup, diff --git a/tests/mocha/event_block_drag_test.js b/tests/mocha/event_block_drag_test.js index 9b0f2031ad0..cc71e3bf084 100644 --- a/tests/mocha/event_block_drag_test.js +++ b/tests/mocha/event_block_drag_test.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import {defineRowBlock} from './test_helpers/block_definitions.js'; import { sharedTestSetup, diff --git a/tests/mocha/event_block_field_intermediate_change_test.js b/tests/mocha/event_block_field_intermediate_change_test.js index 0ff4e1bbf3c..d917dadcdd5 100644 --- a/tests/mocha/event_block_field_intermediate_change_test.js +++ b/tests/mocha/event_block_field_intermediate_change_test.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { sharedTestSetup, sharedTestTeardown, diff --git a/tests/mocha/event_block_move_test.js b/tests/mocha/event_block_move_test.js index b93457e14c1..6d1890eebeb 100644 --- a/tests/mocha/event_block_move_test.js +++ b/tests/mocha/event_block_move_test.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import {defineRowBlock} from './test_helpers/block_definitions.js'; import { sharedTestSetup, diff --git a/tests/mocha/event_bubble_open_test.js b/tests/mocha/event_bubble_open_test.js index 099a625f6e2..a445a6a7819 100644 --- a/tests/mocha/event_bubble_open_test.js +++ b/tests/mocha/event_bubble_open_test.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import {defineMutatorBlocks} from './test_helpers/block_definitions.js'; import { sharedTestSetup, diff --git a/tests/mocha/event_click_test.js b/tests/mocha/event_click_test.js index 6e18769485b..5c4afbcadf9 100644 --- a/tests/mocha/event_click_test.js +++ b/tests/mocha/event_click_test.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import {defineRowBlock} from './test_helpers/block_definitions.js'; import { sharedTestSetup, diff --git a/tests/mocha/event_comment_change_test.js b/tests/mocha/event_comment_change_test.js index ed5f4d9f6ae..edb539ef555 100644 --- a/tests/mocha/event_comment_change_test.js +++ b/tests/mocha/event_comment_change_test.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { sharedTestSetup, sharedTestTeardown, diff --git a/tests/mocha/event_comment_collapse_test.js b/tests/mocha/event_comment_collapse_test.js index e2d27530708..5c3f61054a1 100644 --- a/tests/mocha/event_comment_collapse_test.js +++ b/tests/mocha/event_comment_collapse_test.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { sharedTestSetup, sharedTestTeardown, diff --git a/tests/mocha/event_comment_create_test.js b/tests/mocha/event_comment_create_test.js index df919541d95..71ef8ed1b75 100644 --- a/tests/mocha/event_comment_create_test.js +++ b/tests/mocha/event_comment_create_test.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { sharedTestSetup, sharedTestTeardown, diff --git a/tests/mocha/event_comment_delete_test.js b/tests/mocha/event_comment_delete_test.js index 2e2bb45c491..dd9f0dd2286 100644 --- a/tests/mocha/event_comment_delete_test.js +++ b/tests/mocha/event_comment_delete_test.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { sharedTestSetup, sharedTestTeardown, diff --git a/tests/mocha/event_comment_drag_test.js b/tests/mocha/event_comment_drag_test.js index d214e0adba1..f6685cc5bdf 100644 --- a/tests/mocha/event_comment_drag_test.js +++ b/tests/mocha/event_comment_drag_test.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { sharedTestSetup, sharedTestTeardown, diff --git a/tests/mocha/event_comment_move_test.js b/tests/mocha/event_comment_move_test.js index aae3fdfe632..b3acea990a6 100644 --- a/tests/mocha/event_comment_move_test.js +++ b/tests/mocha/event_comment_move_test.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { sharedTestSetup, sharedTestTeardown, diff --git a/tests/mocha/event_comment_resize_test.js b/tests/mocha/event_comment_resize_test.js index b74e1abb2bf..bed3e733a65 100644 --- a/tests/mocha/event_comment_resize_test.js +++ b/tests/mocha/event_comment_resize_test.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { sharedTestSetup, sharedTestTeardown, diff --git a/tests/mocha/event_selected_test.js b/tests/mocha/event_selected_test.js index 1ce8306db48..8731099ec96 100644 --- a/tests/mocha/event_selected_test.js +++ b/tests/mocha/event_selected_test.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import {defineRowBlock} from './test_helpers/block_definitions.js'; import { sharedTestSetup, diff --git a/tests/mocha/event_test.js b/tests/mocha/event_test.js index 7423f22f74b..a5019c8a978 100644 --- a/tests/mocha/event_test.js +++ b/tests/mocha/event_test.js @@ -6,7 +6,7 @@ import * as Blockly from '../../build/src/core/blockly.js'; import * as eventUtils from '../../build/src/core/events/utils.js'; -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { assertEventEquals, assertNthCallEventArgEquals, diff --git a/tests/mocha/event_theme_change_test.js b/tests/mocha/event_theme_change_test.js index f20f745b6a0..396347c9e13 100644 --- a/tests/mocha/event_theme_change_test.js +++ b/tests/mocha/event_theme_change_test.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { sharedTestSetup, sharedTestTeardown, diff --git a/tests/mocha/event_toolbox_item_select_test.js b/tests/mocha/event_toolbox_item_select_test.js index bf6a9a46212..02484c35bc1 100644 --- a/tests/mocha/event_toolbox_item_select_test.js +++ b/tests/mocha/event_toolbox_item_select_test.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { sharedTestSetup, sharedTestTeardown, diff --git a/tests/mocha/event_trashcan_open_test.js b/tests/mocha/event_trashcan_open_test.js index 2c809f2dfad..47da09a075a 100644 --- a/tests/mocha/event_trashcan_open_test.js +++ b/tests/mocha/event_trashcan_open_test.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { sharedTestSetup, sharedTestTeardown, diff --git a/tests/mocha/event_var_create_test.js b/tests/mocha/event_var_create_test.js index e374c496541..79af41281de 100644 --- a/tests/mocha/event_var_create_test.js +++ b/tests/mocha/event_var_create_test.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { sharedTestSetup, sharedTestTeardown, diff --git a/tests/mocha/event_var_delete_test.js b/tests/mocha/event_var_delete_test.js index b06943d9a19..93d9ef0ba2d 100644 --- a/tests/mocha/event_var_delete_test.js +++ b/tests/mocha/event_var_delete_test.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { sharedTestSetup, sharedTestTeardown, diff --git a/tests/mocha/event_var_rename_test.js b/tests/mocha/event_var_rename_test.js index 7fbd185ab7b..b6d77cb35bd 100644 --- a/tests/mocha/event_var_rename_test.js +++ b/tests/mocha/event_var_rename_test.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { sharedTestSetup, sharedTestTeardown, diff --git a/tests/mocha/event_var_type_change_test.js b/tests/mocha/event_var_type_change_test.js index d19b0421a33..066c145a3ef 100644 --- a/tests/mocha/event_var_type_change_test.js +++ b/tests/mocha/event_var_type_change_test.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { sharedTestSetup, sharedTestTeardown, diff --git a/tests/mocha/event_viewport_test.js b/tests/mocha/event_viewport_test.js index edacc0da6cb..cd11079fa32 100644 --- a/tests/mocha/event_viewport_test.js +++ b/tests/mocha/event_viewport_test.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { sharedTestSetup, sharedTestTeardown, diff --git a/tests/mocha/extensions_test.js b/tests/mocha/extensions_test.js index 66772cbea4b..8c41861d5d8 100644 --- a/tests/mocha/extensions_test.js +++ b/tests/mocha/extensions_test.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { sharedTestSetup, sharedTestTeardown, diff --git a/tests/mocha/field_checkbox_test.js b/tests/mocha/field_checkbox_test.js index 08190fed823..74357338a5a 100644 --- a/tests/mocha/field_checkbox_test.js +++ b/tests/mocha/field_checkbox_test.js @@ -5,7 +5,7 @@ */ import * as Blockly from '../../build/src/core/blockly.js'; -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import {defineRowBlock} from './test_helpers/block_definitions.js'; import { assertFieldValue, diff --git a/tests/mocha/field_colour_test.js b/tests/mocha/field_colour_test.js index 262f978f29d..975d5a01d4a 100644 --- a/tests/mocha/field_colour_test.js +++ b/tests/mocha/field_colour_test.js @@ -5,7 +5,7 @@ */ import * as Blockly from '../../build/src/core/blockly.js'; -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { createTestBlock, defineRowBlock, diff --git a/tests/mocha/field_dropdown_test.js b/tests/mocha/field_dropdown_test.js index e9bc159146d..a1731e81281 100644 --- a/tests/mocha/field_dropdown_test.js +++ b/tests/mocha/field_dropdown_test.js @@ -5,7 +5,7 @@ */ import * as Blockly from '../../build/src/core/blockly.js'; -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { createTestBlock, defineRowBlock, diff --git a/tests/mocha/field_image_test.js b/tests/mocha/field_image_test.js index a02b3f6b64a..f0358703bdf 100644 --- a/tests/mocha/field_image_test.js +++ b/tests/mocha/field_image_test.js @@ -5,7 +5,7 @@ */ import * as Blockly from '../../build/src/core/blockly.js'; -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { assertFieldValue, runConstructorSuiteTests, diff --git a/tests/mocha/field_label_serializable_test.js b/tests/mocha/field_label_serializable_test.js index a831713412c..443cc6d1753 100644 --- a/tests/mocha/field_label_serializable_test.js +++ b/tests/mocha/field_label_serializable_test.js @@ -5,7 +5,7 @@ */ import * as Blockly from '../../build/src/core/blockly.js'; -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { createTestBlock, defineRowBlock, diff --git a/tests/mocha/field_label_test.js b/tests/mocha/field_label_test.js index cf5b4904493..bae600aff19 100644 --- a/tests/mocha/field_label_test.js +++ b/tests/mocha/field_label_test.js @@ -5,7 +5,7 @@ */ import * as Blockly from '../../build/src/core/blockly.js'; -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import {createTestBlock} from './test_helpers/block_definitions.js'; import { assertFieldValue, diff --git a/tests/mocha/field_number_test.js b/tests/mocha/field_number_test.js index 768766bf013..3c12fed820d 100644 --- a/tests/mocha/field_number_test.js +++ b/tests/mocha/field_number_test.js @@ -5,7 +5,7 @@ */ import * as Blockly from '../../build/src/core/blockly.js'; -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import {defineRowBlock} from './test_helpers/block_definitions.js'; import {runTestCases} from './test_helpers/common.js'; import { diff --git a/tests/mocha/field_registry_test.js b/tests/mocha/field_registry_test.js index 26b33c16c3d..1f19477dee1 100644 --- a/tests/mocha/field_registry_test.js +++ b/tests/mocha/field_registry_test.js @@ -5,7 +5,7 @@ */ import * as Blockly from '../../build/src/core/blockly.js'; -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { sharedTestSetup, sharedTestTeardown, diff --git a/tests/mocha/field_test.js b/tests/mocha/field_test.js index 38f9662d6d6..422b0473418 100644 --- a/tests/mocha/field_test.js +++ b/tests/mocha/field_test.js @@ -5,7 +5,7 @@ */ import * as Blockly from '../../build/src/core/blockly.js'; -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { addBlockTypeToCleanup, addMessageToCleanup, diff --git a/tests/mocha/field_textinput_test.js b/tests/mocha/field_textinput_test.js index 7dc105f72f0..7cafd00d948 100644 --- a/tests/mocha/field_textinput_test.js +++ b/tests/mocha/field_textinput_test.js @@ -5,7 +5,7 @@ */ import * as Blockly from '../../build/src/core/blockly.js'; -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { createTestBlock, defineRowBlock, diff --git a/tests/mocha/field_variable_test.js b/tests/mocha/field_variable_test.js index 2dc8d35a55c..58a20977521 100644 --- a/tests/mocha/field_variable_test.js +++ b/tests/mocha/field_variable_test.js @@ -5,7 +5,7 @@ */ import * as Blockly from '../../build/src/core/blockly.js'; -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { createTestBlock, defineRowBlock, diff --git a/tests/mocha/flyout_test.js b/tests/mocha/flyout_test.js index f6d3019df55..998279a0874 100644 --- a/tests/mocha/flyout_test.js +++ b/tests/mocha/flyout_test.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { sharedTestSetup, sharedTestTeardown, diff --git a/tests/mocha/focus_manager_test.js b/tests/mocha/focus_manager_test.js index 26dcb8dbe68..490fa4301b8 100644 --- a/tests/mocha/focus_manager_test.js +++ b/tests/mocha/focus_manager_test.js @@ -8,7 +8,7 @@ import { FocusManager, getFocusManager, } from '../../build/src/core/focus_manager.js'; -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { sharedTestSetup, sharedTestTeardown, diff --git a/tests/mocha/focusable_tree_traverser_test.js b/tests/mocha/focusable_tree_traverser_test.js index 0f88e1106f9..a384dd4be45 100644 --- a/tests/mocha/focusable_tree_traverser_test.js +++ b/tests/mocha/focusable_tree_traverser_test.js @@ -6,7 +6,7 @@ import {FocusManager} from '../../build/src/core/focus_manager.js'; import {FocusableTreeTraverser} from '../../build/src/core/utils/focusable_tree_traverser.js'; -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { sharedTestSetup, sharedTestTeardown, diff --git a/tests/mocha/generator_test.js b/tests/mocha/generator_test.js index 527448eacc9..3c377e7c1ab 100644 --- a/tests/mocha/generator_test.js +++ b/tests/mocha/generator_test.js @@ -10,7 +10,7 @@ import {JavascriptGenerator} from '../../build/src/generators/javascript/javascr import {LuaGenerator} from '../../build/src/generators/lua/lua_generator.js'; import {PhpGenerator} from '../../build/src/generators/php/php_generator.js'; import {PythonGenerator} from '../../build/src/generators/python/python_generator.js'; -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { sharedTestSetup, sharedTestTeardown, diff --git a/tests/mocha/gesture_test.js b/tests/mocha/gesture_test.js index 3f53b8894b9..af4c599fea3 100644 --- a/tests/mocha/gesture_test.js +++ b/tests/mocha/gesture_test.js @@ -5,7 +5,7 @@ */ import {EventType} from '../../build/src/core/events/type.js'; -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import {defineBasicBlockWithField} from './test_helpers/block_definitions.js'; import {assertEventFired, assertEventNotFired} from './test_helpers/events.js'; import { diff --git a/tests/mocha/icon_test.js b/tests/mocha/icon_test.js index 5855fcfc576..ba1b7116065 100644 --- a/tests/mocha/icon_test.js +++ b/tests/mocha/icon_test.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import {defineEmptyBlock} from './test_helpers/block_definitions.js'; import {MockIcon, MockSerializableIcon} from './test_helpers/icon_mocks.js'; import { diff --git a/tests/mocha/input_test.js b/tests/mocha/input_test.js index 0c2b0973eaf..dfa30858e0e 100644 --- a/tests/mocha/input_test.js +++ b/tests/mocha/input_test.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { sharedTestSetup, sharedTestTeardown, diff --git a/tests/mocha/insertion_marker_test.js b/tests/mocha/insertion_marker_test.js index 9ccf8a00b0f..f8215a847eb 100644 --- a/tests/mocha/insertion_marker_test.js +++ b/tests/mocha/insertion_marker_test.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { sharedTestSetup, sharedTestTeardown, diff --git a/tests/mocha/jso_deserialization_test.js b/tests/mocha/jso_deserialization_test.js index dfd3e62b7f2..f6b47d7de6a 100644 --- a/tests/mocha/jso_deserialization_test.js +++ b/tests/mocha/jso_deserialization_test.js @@ -5,7 +5,7 @@ */ import {EventType} from '../../build/src/core/events/type.js'; -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import {assertEventFired} from './test_helpers/events.js'; import { MockParameterModel, diff --git a/tests/mocha/jso_serialization_test.js b/tests/mocha/jso_serialization_test.js index 7cf415e676a..4a7d5e9e1d3 100644 --- a/tests/mocha/jso_serialization_test.js +++ b/tests/mocha/jso_serialization_test.js @@ -5,7 +5,7 @@ */ import * as Blockly from '../../build/src/core/blockly.js'; -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { defineRowBlock, defineStackBlock, diff --git a/tests/mocha/json_test.js b/tests/mocha/json_test.js index 471d2fb9711..2e4b68df9ec 100644 --- a/tests/mocha/json_test.js +++ b/tests/mocha/json_test.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { addMessageToCleanup, sharedTestSetup, diff --git a/tests/mocha/keyboard_navigation_controller_test.js b/tests/mocha/keyboard_navigation_controller_test.js index c7abd863ec1..dd81e9e4b45 100644 --- a/tests/mocha/keyboard_navigation_controller_test.js +++ b/tests/mocha/keyboard_navigation_controller_test.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { sharedTestSetup, sharedTestTeardown, diff --git a/tests/mocha/layering_test.js b/tests/mocha/layering_test.js index 1ef0ee6973d..b84f23252b3 100644 --- a/tests/mocha/layering_test.js +++ b/tests/mocha/layering_test.js @@ -3,7 +3,7 @@ * Copyright 2023 Google LLC * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { sharedTestSetup, sharedTestTeardown, diff --git a/tests/mocha/metrics_test.js b/tests/mocha/metrics_test.js index 860e802550d..de9a5641ad7 100644 --- a/tests/mocha/metrics_test.js +++ b/tests/mocha/metrics_test.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { sharedTestSetup, sharedTestTeardown, diff --git a/tests/mocha/mutator_test.js b/tests/mocha/mutator_test.js index fb6d8caf09b..72b17d0a4bb 100644 --- a/tests/mocha/mutator_test.js +++ b/tests/mocha/mutator_test.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { createRenderedBlock, defineMutatorBlocks, diff --git a/tests/mocha/names_test.js b/tests/mocha/names_test.js index 732e28cd57a..e449a59fd6c 100644 --- a/tests/mocha/names_test.js +++ b/tests/mocha/names_test.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { sharedTestSetup, sharedTestTeardown, diff --git a/tests/mocha/navigation_test.js b/tests/mocha/navigation_test.js index 5bed2aaab8c..38dc88894b1 100644 --- a/tests/mocha/navigation_test.js +++ b/tests/mocha/navigation_test.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { sharedTestSetup, sharedTestTeardown, diff --git a/tests/mocha/old_workspace_comment_test.js b/tests/mocha/old_workspace_comment_test.js index 08a2523f50e..5038d67bad8 100644 --- a/tests/mocha/old_workspace_comment_test.js +++ b/tests/mocha/old_workspace_comment_test.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { sharedTestSetup, sharedTestTeardown, diff --git a/tests/mocha/procedure_map_test.js b/tests/mocha/procedure_map_test.js index eebd5a9f326..5e29c6ca050 100644 --- a/tests/mocha/procedure_map_test.js +++ b/tests/mocha/procedure_map_test.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import {MockProcedureModel} from './test_helpers/procedures.js'; import { sharedTestSetup, diff --git a/tests/mocha/rect_test.js b/tests/mocha/rect_test.js index 37712dff3a0..652837eaef6 100644 --- a/tests/mocha/rect_test.js +++ b/tests/mocha/rect_test.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { sharedTestSetup, sharedTestTeardown, diff --git a/tests/mocha/registry_test.js b/tests/mocha/registry_test.js index 6bcb8b5b077..2d5e22543ca 100644 --- a/tests/mocha/registry_test.js +++ b/tests/mocha/registry_test.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { sharedTestSetup, sharedTestTeardown, diff --git a/tests/mocha/render_management_test.js b/tests/mocha/render_management_test.js index 4de0635394b..94fa48805cf 100644 --- a/tests/mocha/render_management_test.js +++ b/tests/mocha/render_management_test.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { sharedTestSetup, sharedTestTeardown, diff --git a/tests/mocha/serializer_test.js b/tests/mocha/serializer_test.js index a3a3761e9c6..efd1f308b01 100644 --- a/tests/mocha/serializer_test.js +++ b/tests/mocha/serializer_test.js @@ -5,7 +5,7 @@ */ import * as Blockly from '../../build/src/core/blockly.js'; -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { TestCase, TestSuite, diff --git a/tests/mocha/shortcut_items_test.js b/tests/mocha/shortcut_items_test.js index d96ddbfeadc..dfbae3f0901 100644 --- a/tests/mocha/shortcut_items_test.js +++ b/tests/mocha/shortcut_items_test.js @@ -5,7 +5,7 @@ */ import * as Blockly from '../../build/src/core/blockly.js'; -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import {defineStackBlock} from './test_helpers/block_definitions.js'; import { sharedTestSetup, diff --git a/tests/mocha/shortcut_registry_test.js b/tests/mocha/shortcut_registry_test.js index 5641e17c7d1..a06f01b9c00 100644 --- a/tests/mocha/shortcut_registry_test.js +++ b/tests/mocha/shortcut_registry_test.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import {createTestBlock} from './test_helpers/block_definitions.js'; import { sharedTestSetup, diff --git a/tests/mocha/test_helpers/code_generation.js b/tests/mocha/test_helpers/code_generation.js index 95bd902cd45..e61a45653b2 100644 --- a/tests/mocha/test_helpers/code_generation.js +++ b/tests/mocha/test_helpers/code_generation.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../../node_modules/chai/chai.js'; +import {assert} from '../../../node_modules/chai/index.js'; import {runTestSuites} from './common.js'; /** diff --git a/tests/mocha/test_helpers/events.js b/tests/mocha/test_helpers/events.js index c074bdd77a4..3e0b1e95d8b 100644 --- a/tests/mocha/test_helpers/events.js +++ b/tests/mocha/test_helpers/events.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../../node_modules/chai/chai.js'; +import {assert} from '../../../node_modules/chai/index.js'; /** * Creates spy for workspace fireChangeListener diff --git a/tests/mocha/test_helpers/fields.js b/tests/mocha/test_helpers/fields.js index e082abb4ccc..ab304a808a7 100644 --- a/tests/mocha/test_helpers/fields.js +++ b/tests/mocha/test_helpers/fields.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../../node_modules/chai/chai.js'; +import {assert} from '../../../node_modules/chai/index.js'; import {runTestCases, TestCase} from './common.js'; /** diff --git a/tests/mocha/test_helpers/procedures.js b/tests/mocha/test_helpers/procedures.js index ecf8c13adcc..16ef973350f 100644 --- a/tests/mocha/test_helpers/procedures.js +++ b/tests/mocha/test_helpers/procedures.js @@ -6,7 +6,7 @@ import {ConnectionType} from '../../../build/src/core/connection_type.js'; import {VariableModel} from '../../../build/src/core/variable_model.js'; -import {assert} from '../../../node_modules/chai/chai.js'; +import {assert} from '../../../node_modules/chai/index.js'; /** * Asserts that the procedure definition or call block has the expected var diff --git a/tests/mocha/test_helpers/serialization.js b/tests/mocha/test_helpers/serialization.js index c99f508d4dc..c476eae3d6f 100644 --- a/tests/mocha/test_helpers/serialization.js +++ b/tests/mocha/test_helpers/serialization.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../../node_modules/chai/chai.js'; +import {assert} from '../../../node_modules/chai/index.js'; import {runTestCases} from './common.js'; /** diff --git a/tests/mocha/test_helpers/variables.js b/tests/mocha/test_helpers/variables.js index 83f175f6327..dd19b4904c9 100644 --- a/tests/mocha/test_helpers/variables.js +++ b/tests/mocha/test_helpers/variables.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../../node_modules/chai/chai.js'; +import {assert} from '../../../node_modules/chai/index.js'; /** * Check if a variable with the given values exists. diff --git a/tests/mocha/test_helpers/warnings.js b/tests/mocha/test_helpers/warnings.js index 0e07f846c5a..d718a25c166 100644 --- a/tests/mocha/test_helpers/warnings.js +++ b/tests/mocha/test_helpers/warnings.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../../node_modules/chai/chai.js'; +import {assert} from '../../../node_modules/chai/index.js'; /** * Captures the strings sent to console.warn() when calling a function. diff --git a/tests/mocha/test_helpers/workspace.js b/tests/mocha/test_helpers/workspace.js index 917ce6f629e..b5f3cadabd3 100644 --- a/tests/mocha/test_helpers/workspace.js +++ b/tests/mocha/test_helpers/workspace.js @@ -5,7 +5,7 @@ */ import * as eventUtils from '../../../build/src/core/events/utils.js'; -import {assert} from '../../../node_modules/chai/chai.js'; +import {assert} from '../../../node_modules/chai/index.js'; import {workspaceTeardown} from './setup_teardown.js'; import {assertVariableValues} from './variables.js'; import {assertWarnings} from './warnings.js'; diff --git a/tests/mocha/theme_test.js b/tests/mocha/theme_test.js index 1f425dca6a3..f54641a348f 100644 --- a/tests/mocha/theme_test.js +++ b/tests/mocha/theme_test.js @@ -5,7 +5,7 @@ */ import {EventType} from '../../build/src/core/events/type.js'; -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import {assertEventFired} from './test_helpers/events.js'; import { sharedTestSetup, diff --git a/tests/mocha/toast_test.js b/tests/mocha/toast_test.js index 45e02ad5de8..afb7f7f6cb9 100644 --- a/tests/mocha/toast_test.js +++ b/tests/mocha/toast_test.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { sharedTestSetup, sharedTestTeardown, diff --git a/tests/mocha/toolbox_test.js b/tests/mocha/toolbox_test.js index 4e92cd28fd3..480fdfdc6fc 100644 --- a/tests/mocha/toolbox_test.js +++ b/tests/mocha/toolbox_test.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import {defineStackBlock} from './test_helpers/block_definitions.js'; import { sharedTestSetup, diff --git a/tests/mocha/tooltip_test.js b/tests/mocha/tooltip_test.js index 1edc8ad6e25..0695b9ebe03 100644 --- a/tests/mocha/tooltip_test.js +++ b/tests/mocha/tooltip_test.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { sharedTestSetup, sharedTestTeardown, diff --git a/tests/mocha/touch_test.js b/tests/mocha/touch_test.js index 775665643b7..30a9fe72724 100644 --- a/tests/mocha/touch_test.js +++ b/tests/mocha/touch_test.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { sharedTestSetup, sharedTestTeardown, diff --git a/tests/mocha/trashcan_test.js b/tests/mocha/trashcan_test.js index 5486326f1e0..d96e00f3a21 100644 --- a/tests/mocha/trashcan_test.js +++ b/tests/mocha/trashcan_test.js @@ -6,7 +6,7 @@ import {EventType} from '../../build/src/core/events/type.js'; import * as eventUtils from '../../build/src/core/events/utils.js'; -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { defineBasicBlockWithField, defineMutatorBlocks, diff --git a/tests/mocha/utils_test.js b/tests/mocha/utils_test.js index b6228448db7..accf164b79b 100644 --- a/tests/mocha/utils_test.js +++ b/tests/mocha/utils_test.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { sharedTestSetup, sharedTestTeardown, diff --git a/tests/mocha/variable_map_test.js b/tests/mocha/variable_map_test.js index 2d6cee0b94a..76dffbe9dc7 100644 --- a/tests/mocha/variable_map_test.js +++ b/tests/mocha/variable_map_test.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { assertEventFired, assertEventNotFired, diff --git a/tests/mocha/variable_model_test.js b/tests/mocha/variable_model_test.js index cd2a89db420..eee7ea9bf41 100644 --- a/tests/mocha/variable_model_test.js +++ b/tests/mocha/variable_model_test.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { sharedTestSetup, sharedTestTeardown, diff --git a/tests/mocha/widget_div_test.js b/tests/mocha/widget_div_test.js index 61c94247110..4ad31f96c72 100644 --- a/tests/mocha/widget_div_test.js +++ b/tests/mocha/widget_div_test.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { sharedTestSetup, sharedTestTeardown, diff --git a/tests/mocha/workspace_comment_test.js b/tests/mocha/workspace_comment_test.js index 3ce276e8579..bb87ad82ac6 100644 --- a/tests/mocha/workspace_comment_test.js +++ b/tests/mocha/workspace_comment_test.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { assertEventFired, createChangeListenerSpy, diff --git a/tests/mocha/workspace_svg_test.js b/tests/mocha/workspace_svg_test.js index 207cad45dc7..6193dda2db7 100644 --- a/tests/mocha/workspace_svg_test.js +++ b/tests/mocha/workspace_svg_test.js @@ -5,7 +5,7 @@ */ import {EventType} from '../../build/src/core/events/type.js'; -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import {defineStackBlock} from './test_helpers/block_definitions.js'; import { assertEventFired, diff --git a/tests/mocha/xml_test.js b/tests/mocha/xml_test.js index 218324197bf..cd1bce128bb 100644 --- a/tests/mocha/xml_test.js +++ b/tests/mocha/xml_test.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import { addBlockTypeToCleanup, createGenUidStubWithReturns, diff --git a/tests/mocha/zoom_controls_test.js b/tests/mocha/zoom_controls_test.js index dedc36b75b4..d9bb0f91e9b 100644 --- a/tests/mocha/zoom_controls_test.js +++ b/tests/mocha/zoom_controls_test.js @@ -5,7 +5,7 @@ */ import {EventType} from '../../build/src/core/events/type.js'; -import {assert} from '../../node_modules/chai/chai.js'; +import {assert} from '../../node_modules/chai/index.js'; import {assertEventFired, assertEventNotFired} from './test_helpers/events.js'; import { sharedTestSetup, From 90580a8655f754b0c158be917b04925a1e83690d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 27 Aug 2025 19:12:06 +0100 Subject: [PATCH 52/57] chore(deps): bump eslint from 9.30.0 to 9.34.0 (#9329) Bumps [eslint](https://github.com/eslint/eslint) from 9.30.0 to 9.34.0. - [Release notes](https://github.com/eslint/eslint/releases) - [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md) - [Commits](https://github.com/eslint/eslint/compare/v9.30.0...v9.34.0) --- updated-dependencies: - dependency-name: eslint dependency-version: 9.34.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 60 +++++++++++++++++++++-------------------------- 1 file changed, 27 insertions(+), 33 deletions(-) diff --git a/package-lock.json b/package-lock.json index f7a8f9426b9..847c0d79512 100644 --- a/package-lock.json +++ b/package-lock.json @@ -445,19 +445,21 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.0.tgz", - "integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz", + "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==", "dev": true, + "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/core": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.14.0.tgz", - "integrity": "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==", + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", + "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", "dev": true, + "license": "Apache-2.0", "dependencies": { "@types/json-schema": "^7.0.15" }, @@ -527,10 +529,11 @@ "license": "MIT" }, "node_modules/@eslint/js": { - "version": "9.30.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.30.0.tgz", - "integrity": "sha512-Wzw3wQwPvc9sHM+NjakWTcPx11mbZyiYHuwWa/QfZ7cIRX7WK54PSk7bdyXDaoaopUcMatv1zaQvOAAO8hCdww==", + "version": "9.34.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.34.0.tgz", + "integrity": "sha512-EoyvqQnBNsV1CWaEJ559rxXL4c8V92gxirbawSmVUOWXlsRxxQXl6LmCpdUblgxgSkDIqKnhzba2SjRTI/A5Rw==", "dev": true, + "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -548,30 +551,19 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.3.tgz", - "integrity": "sha512-1+WqvgNMhmlAambTvT3KPtCl/Ibr68VldY2XY40SL1CE0ZXiakFR/cbTspaF5HsnpDMvcYYoJHfl4980NBjGag==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", + "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.15.1", + "@eslint/core": "^0.15.2", "levn": "^0.4.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { - "version": "0.15.1", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz", - "integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, "node_modules/@gulp-sourcemaps/identity-map": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/identity-map/-/identity-map-2.0.1.tgz", @@ -1528,7 +1520,8 @@ "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/node": { "version": "20.16.3", @@ -3970,19 +3963,20 @@ } }, "node_modules/eslint": { - "version": "9.30.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.30.0.tgz", - "integrity": "sha512-iN/SiPxmQu6EVkf+m1qpBxzUhE12YqFLOSySuOyVLJLEF9nzTf+h/1AJYc1JWzCnktggeNrjvQGLngDzXirU6g==", + "version": "9.34.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.34.0.tgz", + "integrity": "sha512-RNCHRX5EwdrESy3Jc9o8ie8Bog+PeYvvSR8sDGoZxNFTvZ4dlxUB3WzQ3bQMztFrSRODGrLLj8g6OFuGY/aiQg==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.0", - "@eslint/config-helpers": "^0.3.0", - "@eslint/core": "^0.14.0", + "@eslint/config-helpers": "^0.3.1", + "@eslint/core": "^0.15.2", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.30.0", - "@eslint/plugin-kit": "^0.3.1", + "@eslint/js": "9.34.0", + "@eslint/plugin-kit": "^0.3.5", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", From f10454cb360321f3b9d64c2769e7bff4cc976756 Mon Sep 17 00:00:00 2001 From: Christopher Allen Date: Wed, 27 Aug 2025 19:18:20 +0100 Subject: [PATCH 53/57] chore: Add node.js v24 to CI build matrix (#9219) Node.js v24 has now been released, so add it to our build matrix. Node v18 is no longer in LTS but we want to continue to test on it until it is removed from package.json engines. --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d7a4e786ce6..cdec5308279 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,7 +18,7 @@ jobs: # TODO (#2114): re-enable osx build. # os: [ubuntu-latest, macos-latest] os: [ubuntu-latest] - node-version: [18.x, 20.x, 22.x] + node-version: [18.x, 20.x, 22.x, 24.x] # See supported Node.js release schedule at # https://nodejs.org/en/about/releases/ From b0569c4a576e3918d8f8c646947331ba7bdb99ad Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Wed, 27 Aug 2025 12:28:06 -0700 Subject: [PATCH 54/57] fix: Prevent mocha tests failures when window does not have focus. (#9332) * chore: Add puppeteer-core as a dev dependency. * fix: Make mocha tests run in a fake-focused window. * fix: Make `test:mocha:interactive` use the same gulp codepath as `test`. --- gulpfile.mjs | 7 ++- package-lock.json | 89 +++++++++++++++++++++++++++----- package.json | 3 +- scripts/gulpfiles/test_tasks.mjs | 12 ++++- tests/mocha/webdriver.js | 18 ++++++- 5 files changed, 111 insertions(+), 18 deletions(-) diff --git a/gulpfile.mjs b/gulpfile.mjs index fd3de3bde8c..ad61bcb516d 100644 --- a/gulpfile.mjs +++ b/gulpfile.mjs @@ -45,7 +45,11 @@ import { publishBeta, recompile, } from './scripts/gulpfiles/release_tasks.mjs'; -import {generators, test} from './scripts/gulpfiles/test_tasks.mjs'; +import { + generators, + interactiveMocha, + test, +} from './scripts/gulpfiles/test_tasks.mjs'; const clean = parallel(cleanBuildDir, cleanReleaseDir); @@ -80,6 +84,7 @@ export { clean, test, generators as testGenerators, + interactiveMocha, buildAdvancedCompilationTest, createRC as gitCreateRC, docs, diff --git a/package-lock.json b/package-lock.json index 847c0d79512..08c105c6980 100644 --- a/package-lock.json +++ b/package-lock.json @@ -51,6 +51,7 @@ "patch-package": "^8.0.0", "prettier": "^3.3.3", "prettier-plugin-organize-imports": "^4.0.0", + "puppeteer-core": "^24.17.0", "readline-sync": "^1.4.10", "rimraf": "^5.0.0", "typescript": "^5.3.3", @@ -1282,18 +1283,18 @@ } }, "node_modules/@puppeteer/browsers": { - "version": "2.10.4", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.10.4.tgz", - "integrity": "sha512-9DxbZx+XGMNdjBynIs4BRSz+M3iRDeB7qRcAr6UORFLphCIM2x3DXgOucvADiifcqCE4XePFUKcnaAMyGbrDlQ==", + "version": "2.10.7", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.10.7.tgz", + "integrity": "sha512-wHWLkQWBjHtajZeqCB74nsa/X70KheyOhySYBRmVQDJiNj0zjZR/naPCvdWjMhcG1LmjaMV/9WtTo5mpe8qWLw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "debug": "^4.4.0", + "debug": "^4.4.1", "extract-zip": "^2.0.1", "progress": "^2.0.3", "proxy-agent": "^6.5.0", - "semver": "^7.7.1", - "tar-fs": "^3.0.8", + "semver": "^7.7.2", + "tar-fs": "^3.1.0", "yargs": "^17.7.2" }, "bin": { @@ -2960,6 +2961,20 @@ "fsevents": "~2.3.2" } }, + "node_modules/chromium-bidi": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-8.0.0.tgz", + "integrity": "sha512-d1VmE0FD7lxZQHzcDUCKZSNRtRwISXDsdg4HjdTR5+Ll5nQ/vzU12JeNmupD6VWffrPSlrnGhEWlLESKH3VO+g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "mitt": "^3.0.1", + "zod": "^3.24.1" + }, + "peerDependencies": { + "devtools-protocol": "*" + } + }, "node_modules/ci-info": { "version": "3.8.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", @@ -3612,6 +3627,13 @@ "node": ">=0.10.0" } }, + "node_modules/devtools-protocol": { + "version": "0.0.1475386", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1475386.tgz", + "integrity": "sha512-RQ809ykTfJ+dgj9bftdeL2vRVxASAuGU+I9LEx9Ij5TXU5HrgAQVmzi72VA+mkzscE12uzlRv5/tWWv9R9J1SA==", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/diff": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", @@ -6895,6 +6917,13 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "dev": true, + "license": "MIT" + }, "node_modules/mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", @@ -7925,6 +7954,24 @@ "node": ">=6" } }, + "node_modules/puppeteer-core": { + "version": "24.17.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.17.0.tgz", + "integrity": "sha512-RYOBKFiF+3RdwIZTEacqNpD567gaFcBAOKTT7742FdB1icXudrPI7BlZbYTYWK2wgGQUXt9Zi1Yn+D5PmCs4CA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@puppeteer/browsers": "2.10.7", + "chromium-bidi": "8.0.0", + "debug": "^4.4.1", + "devtools-protocol": "0.0.1475386", + "typed-query-selector": "^2.12.0", + "ws": "^8.18.3" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/qs": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", @@ -8993,9 +9040,9 @@ } }, "node_modules/tar-fs": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.9.tgz", - "integrity": "sha512-XF4w9Xp+ZQgifKakjZYmFdkLoSWd34VGKcsTCwlNWM7QG3ZbaxnTsaBwnjFZqHRf/rROxaR8rXnbtwdvaDI+lA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.0.tgz", + "integrity": "sha512-5Mty5y/sOF1YWj1J6GiBodjlDc05CUR8PKXrsnFAiSG0xA+GHeWLovaZPYUDXkH/1iKRf2+M5+OrRgzC7O9b7w==", "dev": true, "license": "MIT", "dependencies": { @@ -9212,6 +9259,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/typed-query-selector": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz", + "integrity": "sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==", + "dev": true, + "license": "MIT" + }, "node_modules/typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", @@ -9773,9 +9827,10 @@ "dev": true }, "node_modules/ws": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", - "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", "engines": { "node": ">=10.0.0" }, @@ -10029,6 +10084,16 @@ "dependencies": { "safe-buffer": "~5.2.0" } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/package.json b/package.json index c52a79003e9..9ba8ebe6142 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "test": "gulp test", "test:browser": "cd tests/browser && npx mocha", "test:generators": "gulp testGenerators", - "test:mocha:interactive": "npm run build && concurrently -n tsc,server \"tsc --watch --preserveWatchOutput --outDir \"build/src\" --declarationDir \"build/declarations\"\" \"http-server ./ -o /tests/mocha/index.html -c-1\"", + "test:mocha:interactive": "npm run build && concurrently -n tsc,server \"tsc --watch --preserveWatchOutput --outDir \"build/src\" --declarationDir \"build/declarations\"\" \"gulp interactiveMocha\"", "test:compile:advanced": "gulp buildAdvancedCompilationTest --debug", "updateGithubPages": "npm ci && gulp gitUpdateGithubPages" }, @@ -138,6 +138,7 @@ "patch-package": "^8.0.0", "prettier": "^3.3.3", "prettier-plugin-organize-imports": "^4.0.0", + "puppeteer-core": "^24.17.0", "readline-sync": "^1.4.10", "rimraf": "^5.0.0", "typescript": "^5.3.3", diff --git a/scripts/gulpfiles/test_tasks.mjs b/scripts/gulpfiles/test_tasks.mjs index d4b73cdb3c1..37f9884440d 100644 --- a/scripts/gulpfiles/test_tasks.mjs +++ b/scripts/gulpfiles/test_tasks.mjs @@ -257,9 +257,9 @@ async function metadata() { * Run Mocha tests inside a browser. * @return {Promise} Asynchronous result. */ -async function mocha() { +async function mocha(exitOnCompletion = true) { return runTestTask('mocha', async () => { - const result = await runMochaTestsInBrowser().catch(e => { + const result = await runMochaTestsInBrowser(exitOnCompletion).catch(e => { throw e; }); if (result) { @@ -269,6 +269,14 @@ async function mocha() { }); } +/** + * Run Mocha tests inside a browser and keep the browser open upon completion. + * @return {Promise} Asynchronous result. + */ +export async function interactiveMocha() { + return mocha(false); +} + /** * Helper method for comparison file. * @param {string} file1 First target file. diff --git a/tests/mocha/webdriver.js b/tests/mocha/webdriver.js index 207917c5e6b..06e7a3e6585 100644 --- a/tests/mocha/webdriver.js +++ b/tests/mocha/webdriver.js @@ -15,9 +15,12 @@ const {posixPath} = require('../../scripts/helpers'); * Runs the Mocha tests in this directory in Chrome. It uses webdriverio to * launch Chrome and load index.html. Outputs a summary of the test results * to the console. + * + * @param {boolean} exitOnCompletetion True if the browser should automatically + * quit after tests have finished running. * @return {number} 0 on success, 1 on failure. */ -async function runMochaTestsInBrowser() { +async function runMochaTestsInBrowser(exitOnCompletion = true) { const options = { capabilities: { browserName: 'chrome', @@ -45,6 +48,17 @@ async function runMochaTestsInBrowser() { console.log('Loading URL: ' + url); await browser.url(url); + // Toggle the devtools setting to emulate focus, so that the window will + // always act as if it has focus regardless of the state of the window manager + // or operating system. This improves the reliability of FocusManager-related + // tests. + const puppeteer = await browser.getPuppeteer(); + await browser.call(async () => { + const page = (await puppeteer.pages())[0]; + const session = await page.createCDPSession(); + await session.send('Emulation.setFocusEmulationEnabled', { enabled: true }); + }); + await browser.waitUntil(async() => { const elem = await browser.$('#failureCount'); const text = await elem.getAttribute('tests_failed'); @@ -74,7 +88,7 @@ async function runMochaTestsInBrowser() { if (parseInt(numOfFailure) !== 0) { return 1; } - await browser.deleteSession(); + if (exitOnCompletion) await browser.deleteSession(); return 0; } From e51efe4855f96554fd552b175e8ae10d15f912bf Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Thu, 28 Aug 2025 10:06:52 -0700 Subject: [PATCH 55/57] fix: Fix bug that could cause errant line when rendering. (#9333) --- core/renderers/common/drawer.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/core/renderers/common/drawer.ts b/core/renderers/common/drawer.ts index 7046406adc7..c474bc8c339 100644 --- a/core/renderers/common/drawer.ts +++ b/core/renderers/common/drawer.ts @@ -122,9 +122,12 @@ export class Drawer { } else if (Types.isSpacer(elem)) { this.outlinePath_ += svgPaths.lineOnAxis('h', elem.width); } + // No branch for a square corner, because it's a no-op. } - // No branch for a square corner, because it's a no-op. - this.outlinePath_ += svgPaths.lineOnAxis('v', topRow.height); + this.outlinePath_ += svgPaths.lineOnAxis( + 'v', + topRow.height - topRow.ascenderHeight, + ); } /** From 5afc0d6692f1a2403dc9a6024920111f23e25487 Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Thu, 28 Aug 2025 11:28:40 -0700 Subject: [PATCH 56/57] refactor: Make focusable elements responsible for scrolling themselves into bounds. (#9288) * refactor: Make focusable elements responsible for scrolling themselves into bounds. * chore: Add tests for scrolling focused elements into view. * fix: Removed inadvertent `.only`. * fix: Scroll parent block of connections into bounds on focus. --- core/block_svg.ts | 3 + core/bubbles/bubble.ts | 4 + core/comments/comment_bar_button.ts | 8 +- core/comments/comment_editor.ts | 13 +- core/comments/rendered_workspace_comment.ts | 1 + core/field.ts | 7 +- core/flyout_button.ts | 6 +- core/icons/icon.ts | 12 +- core/keyboard_nav/line_cursor.ts | 30 +-- core/rendered_connection.ts | 7 +- tests/browser/test/basic_playground_test.mjs | 221 +++++++++++++++++++ 11 files changed, 276 insertions(+), 36 deletions(-) diff --git a/core/block_svg.ts b/core/block_svg.ts index c6065282a5c..b3fdeb2d6b6 100644 --- a/core/block_svg.ts +++ b/core/block_svg.ts @@ -1844,6 +1844,9 @@ export class BlockSvg /** See IFocusableNode.onNodeFocus. */ onNodeFocus(): void { this.select(); + this.workspace.scrollBoundsIntoView( + this.getBoundingRectangleWithoutChildren(), + ); } /** See IFocusableNode.onNodeBlur. */ diff --git a/core/bubbles/bubble.ts b/core/bubbles/bubble.ts index c42e602544e..742d300adf1 100644 --- a/core/bubbles/bubble.ts +++ b/core/bubbles/bubble.ts @@ -707,6 +707,10 @@ export abstract class Bubble implements IBubble, ISelectable, IFocusableNode { onNodeFocus(): void { this.select(); this.bringToFront(); + const xy = this.getRelativeToSurfaceXY(); + const size = this.getSize(); + const bounds = new Rect(xy.y, xy.y + size.height, xy.x, xy.x + size.width); + this.workspace.scrollBoundsIntoView(bounds); } /** See IFocusableNode.onNodeBlur. */ diff --git a/core/comments/comment_bar_button.ts b/core/comments/comment_bar_button.ts index 24a084ad26e..be130b0e335 100644 --- a/core/comments/comment_bar_button.ts +++ b/core/comments/comment_bar_button.ts @@ -87,7 +87,13 @@ export abstract class CommentBarButton implements IFocusableNode { } /** Called when this button's focusable DOM element gains focus. */ - onNodeFocus() {} + onNodeFocus() { + const commentView = this.getCommentView(); + const xy = commentView.getRelativeToSurfaceXY(); + const size = commentView.getSize(); + const bounds = new Rect(xy.y, xy.y + size.height, xy.x, xy.x + size.width); + commentView.workspace.scrollBoundsIntoView(bounds); + } /** Called when this button's focusable DOM element loses focus. */ onNodeBlur() {} diff --git a/core/comments/comment_editor.ts b/core/comments/comment_editor.ts index ac1559c4b3d..b4c741ba1ad 100644 --- a/core/comments/comment_editor.ts +++ b/core/comments/comment_editor.ts @@ -10,8 +10,10 @@ import {IFocusableNode} from '../interfaces/i_focusable_node.js'; import {IFocusableTree} from '../interfaces/i_focusable_tree.js'; import * as touch from '../touch.js'; import * as dom from '../utils/dom.js'; +import {Rect} from '../utils/rect.js'; import {Size} from '../utils/size.js'; import {Svg} from '../utils/svg.js'; +import * as svgMath from '../utils/svg_math.js'; import {WorkspaceSvg} from '../workspace_svg.js'; /** @@ -188,7 +190,16 @@ export class CommentEditor implements IFocusableNode { getFocusableTree(): IFocusableTree { return this.workspace; } - onNodeFocus(): void {} + onNodeFocus(): void { + const bbox = Rect.from(this.foreignObject.getBoundingClientRect()); + this.workspace.scrollBoundsIntoView( + Rect.createFromPoint( + svgMath.screenToWsCoordinates(this.workspace, bbox.getOrigin()), + bbox.getWidth(), + bbox.getHeight(), + ), + ); + } onNodeBlur(): void {} canBeFocused(): boolean { if (this.id) return true; diff --git a/core/comments/rendered_workspace_comment.ts b/core/comments/rendered_workspace_comment.ts index 49c75e60883..2903bff4bce 100644 --- a/core/comments/rendered_workspace_comment.ts +++ b/core/comments/rendered_workspace_comment.ts @@ -347,6 +347,7 @@ export class RenderedWorkspaceComment this.select(); // Ensure that the comment is always at the top when focused. this.workspace.getLayerManager()?.append(this, layers.BLOCK); + this.workspace.scrollBoundsIntoView(this.getBoundingRectangle()); } /** See IFocusableNode.onNodeBlur. */ diff --git a/core/field.ts b/core/field.ts index d993e197d23..e025efab709 100644 --- a/core/field.ts +++ b/core/field.ts @@ -1380,7 +1380,12 @@ export abstract class Field } /** See IFocusableNode.onNodeFocus. */ - onNodeFocus(): void {} + onNodeFocus(): void { + const block = this.getSourceBlock() as BlockSvg; + block.workspace.scrollBoundsIntoView( + block.getBoundingRectangleWithoutChildren(), + ); + } /** See IFocusableNode.onNodeBlur. */ onNodeBlur(): void {} diff --git a/core/flyout_button.ts b/core/flyout_button.ts index c9afb8b0159..5a066a23b11 100644 --- a/core/flyout_button.ts +++ b/core/flyout_button.ts @@ -398,7 +398,11 @@ export class FlyoutButton } /** See IFocusableNode.onNodeFocus. */ - onNodeFocus(): void {} + onNodeFocus(): void { + const xy = this.getPosition(); + const bounds = new Rect(xy.y, xy.y + this.height, xy.x, xy.x + this.width); + this.workspace.scrollBoundsIntoView(bounds); + } /** See IFocusableNode.onNodeBlur. */ onNodeBlur(): void {} diff --git a/core/icons/icon.ts b/core/icons/icon.ts index 8f8ff70fc32..f5f76603875 100644 --- a/core/icons/icon.ts +++ b/core/icons/icon.ts @@ -14,6 +14,7 @@ import * as tooltip from '../tooltip.js'; import {Coordinate} from '../utils/coordinate.js'; import * as dom from '../utils/dom.js'; import * as idGenerator from '../utils/idgenerator.js'; +import {Rect} from '../utils/rect.js'; import {Size} from '../utils/size.js'; import {Svg} from '../utils/svg.js'; import type {WorkspaceSvg} from '../workspace_svg.js'; @@ -168,7 +169,16 @@ export abstract class Icon implements IIcon { } /** See IFocusableNode.onNodeFocus. */ - onNodeFocus(): void {} + onNodeFocus(): void { + const blockBounds = (this.sourceBlock as BlockSvg).getBoundingRectangle(); + const bounds = new Rect( + blockBounds.top + this.offsetInBlock.y, + blockBounds.top + this.offsetInBlock.y + this.getSize().height, + blockBounds.left + this.offsetInBlock.x, + blockBounds.left + this.offsetInBlock.x + this.getSize().width, + ); + (this.sourceBlock as BlockSvg).workspace.scrollBoundsIntoView(bounds); + } /** See IFocusableNode.onNodeBlur. */ onNodeBlur(): void {} diff --git a/core/keyboard_nav/line_cursor.ts b/core/keyboard_nav/line_cursor.ts index 13e5a729d0f..30770e47d2d 100644 --- a/core/keyboard_nav/line_cursor.ts +++ b/core/keyboard_nav/line_cursor.ts @@ -14,14 +14,11 @@ */ import {BlockSvg} from '../block_svg.js'; -import {CommentBarButton} from '../comments/comment_bar_button.js'; import {RenderedWorkspaceComment} from '../comments/rendered_workspace_comment.js'; -import {Field} from '../field.js'; import {getFocusManager} from '../focus_manager.js'; import type {IFocusableNode} from '../interfaces/i_focusable_node.js'; import * as registry from '../registry.js'; -import {Rect} from '../utils/rect.js'; -import {WorkspaceSvg} from '../workspace_svg.js'; +import type {WorkspaceSvg} from '../workspace_svg.js'; import {Marker} from './marker.js'; /** @@ -392,31 +389,6 @@ export class LineCursor extends Marker { */ setCurNode(newNode: IFocusableNode) { getFocusManager().focusNode(newNode); - - // Try to scroll cursor into view. - if (newNode instanceof BlockSvg) { - newNode.workspace.scrollBoundsIntoView( - newNode.getBoundingRectangleWithoutChildren(), - ); - } else if (newNode instanceof Field) { - const block = newNode.getSourceBlock() as BlockSvg; - block.workspace.scrollBoundsIntoView( - block.getBoundingRectangleWithoutChildren(), - ); - } else if (newNode instanceof RenderedWorkspaceComment) { - newNode.workspace.scrollBoundsIntoView(newNode.getBoundingRectangle()); - } else if (newNode instanceof CommentBarButton) { - const commentView = newNode.getCommentView(); - const xy = commentView.getRelativeToSurfaceXY(); - const size = commentView.getSize(); - const bounds = new Rect( - xy.y, - xy.y + size.height, - xy.x, - xy.x + size.width, - ); - commentView.workspace.scrollBoundsIntoView(bounds); - } } /** diff --git a/core/rendered_connection.ts b/core/rendered_connection.ts index 84905eeccc2..4a53048bc84 100644 --- a/core/rendered_connection.ts +++ b/core/rendered_connection.ts @@ -644,6 +644,9 @@ export class RenderedConnection /** See IFocusableNode.onNodeFocus. */ onNodeFocus(): void { this.highlight(); + this.getSourceBlock().workspace.scrollBoundsIntoView( + this.getSourceBlock().getBoundingRectangleWithoutChildren(), + ); } /** See IFocusableNode.onNodeBlur. */ @@ -656,12 +659,12 @@ export class RenderedConnection return true; } - private findHighlightSvg(): SVGElement | null { + private findHighlightSvg(): SVGPathElement | null { // This cast is valid as TypeScript's definition is wrong. See: // https://github.com/microsoft/TypeScript/issues/60996. return document.getElementById(this.id) as | unknown - | null as SVGElement | null; + | null as SVGPathElement | null; } } diff --git a/tests/browser/test/basic_playground_test.mjs b/tests/browser/test/basic_playground_test.mjs index c7c8a5a370c..72b3894a6a3 100644 --- a/tests/browser/test/basic_playground_test.mjs +++ b/tests/browser/test/basic_playground_test.mjs @@ -238,3 +238,224 @@ suite('Disabling', function () { }, ); }); + +suite('Focused nodes are scrolled into bounds', function () { + // Setting timeout to unlimited as the webdriver takes a longer time to run + // than most mocha tests. + this.timeout(0); + + // Setup Selenium for all of the tests + suiteSetup(async function () { + this.browser = await testSetup(testFileLocations.PLAYGROUND); + await this.browser.execute(() => { + window.focusScrollTest = async (testcase) => { + const workspace = Blockly.getMainWorkspace(); + const metrics = workspace.getMetricsManager(); + const initialViewport = metrics.getViewMetrics(true); + const elementBounds = await testcase(workspace); + await Blockly.renderManagement.finishQueuedRenders(); + const scrolledViewport = metrics.getViewMetrics(true); + const workspaceBounds = new Blockly.utils.Rect( + scrolledViewport.top, + scrolledViewport.top + scrolledViewport.height, + scrolledViewport.left, + scrolledViewport.left + scrolledViewport.width, + ); + return { + changed: + JSON.stringify(initialViewport) !== + JSON.stringify(scrolledViewport), + intersects: elementBounds.intersects(workspaceBounds), + contains: workspaceBounds.contains( + elementBounds.getOrigin().x, + elementBounds.getOrigin().y, + ), + elementBounds, + workspaceBounds, + }; + }; + }); + }); + + setup(async function () { + await this.browser.execute(() => { + Blockly.serialization.blocks.append( + { + 'type': 'text', + 'x': -500, + 'y': -500, + }, + Blockly.getMainWorkspace(), + ); + Blockly.serialization.blocks.append( + { + 'type': 'controls_if', + 'x': 500, + 'y': 500, + }, + Blockly.getMainWorkspace(), + ); + Blockly.getMainWorkspace().zoomCenter(1); + }); + }); + + test('Focused blocks scroll into bounds', async function () { + const result = await this.browser.execute(async () => { + return await window.focusScrollTest(async (workspace) => { + const block = workspace.getTopBlocks()[0]; + Blockly.getFocusManager().focusNode(block); + return block.getBoundingRectangleWithoutChildren(); + }); + }); + chai.assert.isTrue(result.intersects); + chai.assert.isTrue(result.contains); + chai.assert.isTrue(result.changed); + }); + + test('Focused bubbles scroll into bounds', async function () { + const result = await this.browser.execute(async () => { + return await window.focusScrollTest(async (workspace) => { + const block = workspace.getTopBlocks()[0]; + block.setCommentText('hello world'); + const icon = block.getIcon(Blockly.icons.IconType.COMMENT); + icon.setBubbleVisible(true); + await Blockly.renderManagement.finishQueuedRenders(); + icon.setBubbleLocation(new Blockly.utils.Coordinate(-510, -510)); + Blockly.getFocusManager().focusNode(icon.getBubble()); + const xy = icon.getBubble().getRelativeToSurfaceXY(); + const size = icon.getBubble().getSize(); + return new Blockly.utils.Rect( + xy.y, + xy.y + size.height, + xy.x, + xy.x + size.width, + ); + }); + }); + + chai.assert.isTrue(result.intersects); + chai.assert.isTrue(result.contains); + chai.assert.isTrue(result.changed); + }); + + test('Comment bar buttons scroll into bounds', async function () { + const result = await this.browser.execute(async () => { + return await window.focusScrollTest(async (workspace) => { + const comment = new Blockly.comments.RenderedWorkspaceComment( + workspace, + ); + comment.moveTo(new Blockly.utils.Coordinate(-300, 500)); + const commentBarButton = comment.view.getCommentBarButtons()[0]; + Blockly.getFocusManager().focusNode(commentBarButton); + const xy = comment.view.getRelativeToSurfaceXY(); + const size = comment.view.getSize(); + // Comment bar buttons scroll their parent comment view into view. + return new Blockly.utils.Rect( + xy.y, + xy.y + size.height, + xy.x, + xy.x + size.width, + ); + }); + }); + + chai.assert.isTrue(result.intersects); + chai.assert.isTrue(result.contains); + chai.assert.isTrue(result.changed); + }); + + test('Comment editors scroll into bounds', async function () { + const result = await this.browser.execute(async () => { + return await window.focusScrollTest(async (workspace) => { + const comment = new Blockly.comments.RenderedWorkspaceComment( + workspace, + ); + comment.moveTo(new Blockly.utils.Coordinate(-300, 500)); + const commentEditor = comment.view.getEditorFocusableNode(); + Blockly.getFocusManager().focusNode(commentEditor); + // Comment editor bounds can't be calculated externally since they + // depend on private properties, but the comment view is a reasonable + // proxy. + const xy = comment.view.getRelativeToSurfaceXY(); + const size = comment.view.getSize(); + return new Blockly.utils.Rect( + xy.y, + xy.y + size.height, + xy.x, + xy.x + size.width, + ); + }); + }); + + chai.assert.isTrue(result.intersects); + chai.assert.isTrue(result.contains); + chai.assert.isTrue(result.changed); + }); + + test('Workspace comments scroll into bounds', async function () { + const result = await this.browser.execute(async () => { + return await window.focusScrollTest(async (workspace) => { + const comment = new Blockly.comments.RenderedWorkspaceComment( + workspace, + ); + comment.moveTo(new Blockly.utils.Coordinate(-500, 500)); + Blockly.getFocusManager().focusNode(comment); + return comment.getBoundingRectangle(); + }); + }); + + chai.assert.isTrue(result.intersects); + chai.assert.isTrue(result.contains); + chai.assert.isTrue(result.changed); + }); + + test('Icons scroll into bounds', async function () { + const result = await this.browser.execute(async () => { + return await window.focusScrollTest(async (workspace) => { + const block = workspace.getTopBlocks()[0]; + block.setWarningText('this is bad'); + const icon = block.getIcon(Blockly.icons.IconType.WARNING); + Blockly.getFocusManager().focusNode(icon); + // Icon bounds can't be calculated externally since they depend on + // protected properties, but the parent block is a reasonable proxy. + return block.getBoundingRectangleWithoutChildren(); + }); + }); + + chai.assert.isTrue(result.intersects); + chai.assert.isTrue(result.contains); + chai.assert.isTrue(result.changed); + }); + + test('Fields scroll into bounds', async function () { + const result = await this.browser.execute(async () => { + return await window.focusScrollTest(async (workspace) => { + const block = workspace.getTopBlocks()[0]; + const field = block.getField('TEXT'); + Blockly.getFocusManager().focusNode(field); + // Fields scroll their source block into view. + return block.getBoundingRectangleWithoutChildren(); + }); + }); + + chai.assert.isTrue(result.intersects); + chai.assert.isTrue(result.contains); + chai.assert.isTrue(result.changed); + }); + + test('Connections scroll into bounds', async function () { + const result = await this.browser.execute(async () => { + return await window.focusScrollTest(async (workspace) => { + const block = workspace.getBlocksByType('controls_if')[0]; + Blockly.getFocusManager().focusNode(block.nextConnection); + // Connection bounds can't be calculated externally since they depend on + // protected properties, but the parent block is a reasonable proxy. + return block.getBoundingRectangleWithoutChildren(); + }); + }); + + chai.assert.isTrue(result.intersects); + chai.assert.isTrue(result.contains); + chai.assert.isTrue(result.changed); + }); +}); From 5f21e9b15430c43dad8e139c7c09a0ff2537329d Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Thu, 28 Aug 2025 15:46:39 -0700 Subject: [PATCH 57/57] release: Update version number to 12.3.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 08c105c6980..afd057ce613 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "blockly", - "version": "12.3.0-beta.0", + "version": "12.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "blockly", - "version": "12.3.0-beta.0", + "version": "12.3.0", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index 9ba8ebe6142..7b639a872ea 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "blockly", - "version": "12.3.0-beta.0", + "version": "12.3.0", "description": "Blockly is a library for building visual programming editors.", "keywords": [ "blockly"