From 3a3e83fe44f12b9b617870da0385dcb3d7307bee Mon Sep 17 00:00:00 2001 From: Ben Henning Date: Wed, 21 Aug 2024 17:41:38 +0000 Subject: [PATCH 01/32] Fix workspace clean up not considering immovables. At a high-level, this change ensures that cleaning up a workspace doesn't move blocks in a way that overlaps with immovable blocks. It also adds missing testing coverage for both Rect (used for bounding box calculations during workspace cleanup) and WorkspaceSvg (for verifying the updated clean up functionality). This also renames the clean up function to be 'tidyUp' since that better suits what's happening (as opposed to other clean-up routines which are actually deinitializing objects). --- core/contextmenu_items.ts | 2 +- core/utils/rect.ts | 59 +- core/workspace_svg.ts | 49 +- .../workspacefactory/wfactory_controller.js | 6 +- tests/mocha/contextmenu_items_test.js | 6 +- tests/mocha/index.html | 1 + tests/mocha/rect_test.js | 968 ++++++++++++++++++ tests/mocha/workspace_svg_test.js | 428 +++++++- 8 files changed, 1493 insertions(+), 26 deletions(-) create mode 100644 tests/mocha/rect_test.js diff --git a/core/contextmenu_items.ts b/core/contextmenu_items.ts index 254906ce7ff..ec98d7e8337 100644 --- a/core/contextmenu_items.ts +++ b/core/contextmenu_items.ts @@ -91,7 +91,7 @@ export function registerCleanup() { return 'hidden'; }, callback(scope: Scope) { - scope.workspace!.cleanUp(); + scope.workspace!.tidyUp(); }, scopeType: ContextMenuRegistry.ScopeType.WORKSPACE, id: 'cleanWorkspace', diff --git a/core/utils/rect.ts b/core/utils/rect.ts index 817462b699d..adf0d2b94d9 100644 --- a/core/utils/rect.ts +++ b/core/utils/rect.ts @@ -13,6 +13,8 @@ */ // Former goog.module ID: Blockly.utils.Rect +import {Coordinate} from './coordinate.js'; + /** * Class for representing rectangular regions. */ @@ -30,10 +32,21 @@ export class Rect { public right: number, ) {} + /** + * Creates a new copy of this rectangle. + * + * @returns A copy of this Rect. + */ + clone(): Rect { + return new Rect(this.top, this.bottom, this.left, this.right); + } + + /** Returns the height of this rectangle. */ getHeight(): number { return this.bottom - this.top; } + /** Returns the width of this rectangle. */ getWidth(): number { return this.right - this.left; } @@ -59,11 +72,45 @@ export class Rect { * @returns Whether this rectangle intersects the provided rectangle. */ intersects(other: Rect): boolean { - return !( - this.left > other.right || - this.right < other.left || - this.top > other.bottom || - this.bottom < other.top - ); + // The following logic can be derived and then simplified from a longer form symmetrical check + // of verifying each rectangle's borders with the other rectangle by checking if either end of + // the border's line segment is contained within the other rectangle. The simplified version + // used here can be logically interpreted as ensuring that each line segment of 'this' rectangle + // is not outside the bounds of the 'other' rectangle (proving there's an intersection). + return (this.left <= other.right) + && (this.right >= other.left) + && (this.bottom >= other.top) + && (this.top <= other.bottom); + } + + /** + * Compares bounding rectangles for equality. + * + * @param a A Rect. + * @param b A Rect. + * @returns True iff the bounding rectangles are equal, or if both are null. + */ + static equals(a?: Rect | null, b?: Rect | null): boolean { + if (a === b) { + return true; + } + if (!a || !b) { + return false; + } + return a.top === b.top && a.bottom === b.bottom && a.left === b.left && a.right === b.right; + } + + /** + * Creates a new Rect using a position and supplied dimensions. + * + * @param position The upper left coordinate of the new rectangle. + * @param width The width of the rectangle, in pixels. + * @param height The height of the rectangle, in pixels. + * @returns A newly created Rect using the provided Coordinate and dimensions. + */ + static createFromPoint(position: Coordinate, width: number, height: number): Rect { + const left = position.x; + const top = position.y; + return new Rect(top, top + height, left, left + width); } } diff --git a/core/workspace_svg.ts b/core/workspace_svg.ts index aad748105f0..a92c3f4ae67 100644 --- a/core/workspace_svg.ts +++ b/core/workspace_svg.ts @@ -1644,23 +1644,48 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { return boundary; } - /** Clean up the workspace by ordering all the blocks in a column. */ - cleanUp() { + /** Tidy up the workspace by ordering all the blocks in a column such that none overlap. */ + tidyUp() { this.setResizesEnabled(false); eventUtils.setGroup(true); + const topBlocks = this.getTopBlocks(true); - let cursorY = 0; - for (let i = 0, block; (block = topBlocks[i]); i++) { - if (!block.isMovable()) { - continue; + const movableBlocks = topBlocks.filter((block) => block.isMovable()); + const immovableBlocks = topBlocks.filter((block) => !block.isMovable()); + + const immovableBlockBounds = immovableBlocks.map((block) => block.getBoundingRectangle()); + + const getNextIntersectingImmovableBlock = function(rect: Rect): Rect|null { + for (const immovableRect of immovableBlockBounds) { + if (rect.intersects(immovableRect)) { + return immovableRect; + } } - const xy = block.getRelativeToSurfaceXY(); - block.moveBy(-xy.x, cursorY - xy.y, ['cleanup']); + return null; + }; + + let cursorY = 0; + const minBlockHeight = this.renderer.getConstants().MIN_BLOCK_HEIGHT; + for (const block of movableBlocks) { + // Make the initial movement of shifting the block to its best possible position. + let boundingRect = block.getBoundingRectangle(); + block.moveBy(-boundingRect.left, cursorY - boundingRect.top, ['cleanup']); block.snapToGrid(); - cursorY = - block.getRelativeToSurfaceXY().y + - block.getHeightWidth().height + - this.renderer.getConstants().MIN_BLOCK_HEIGHT; + + boundingRect = block.getBoundingRectangle(); + let conflictingRect = getNextIntersectingImmovableBlock(boundingRect); + while (conflictingRect != null) { + // If the block intersects with an immovable block, move it down past that immovable block. + cursorY = conflictingRect.top + conflictingRect.getHeight() + minBlockHeight; + block.moveBy(0, cursorY - boundingRect.top, ['cleanup']); + block.snapToGrid(); + boundingRect = block.getBoundingRectangle(); + conflictingRect = getNextIntersectingImmovableBlock(boundingRect); + } + + // Ensure all next blocks start past the most recent (which will also put them past all + // previously intersecting immovable blocks). + cursorY = block.getRelativeToSurfaceXY().y + block.getHeightWidth().height + minBlockHeight; } eventUtils.setGroup(false); this.setResizesEnabled(true); diff --git a/demos/blockfactory/workspacefactory/wfactory_controller.js b/demos/blockfactory/workspacefactory/wfactory_controller.js index 385feede8ec..7e2f95c81f7 100644 --- a/demos/blockfactory/workspacefactory/wfactory_controller.js +++ b/demos/blockfactory/workspacefactory/wfactory_controller.js @@ -278,7 +278,7 @@ WorkspaceFactoryController.prototype.clearAndLoadElement = function(id) { this.view.setCategoryTabSelection(id, true); // Order blocks as shown in flyout. - this.toolboxWorkspace.cleanUp(); + this.toolboxWorkspace.tidyUp(); // Update category editing buttons. this.view.updateState(this.model.getIndexByElementId @@ -774,7 +774,7 @@ WorkspaceFactoryController.prototype.importToolboxFromTree_ = function(tree) { // No categories present. // Load all the blocks into a single category evenly spaced. Blockly.Xml.domToWorkspace(tree, this.toolboxWorkspace); - this.toolboxWorkspace.cleanUp(); + this.toolboxWorkspace.tidyUp(); // Convert actual shadow blocks to user-generated shadow blocks. this.convertShadowBlocks(); @@ -799,7 +799,7 @@ WorkspaceFactoryController.prototype.importToolboxFromTree_ = function(tree) { } // Evenly space the blocks. - this.toolboxWorkspace.cleanUp(); + this.toolboxWorkspace.tidyUp(); // Convert actual shadow blocks to user-generated shadow blocks. this.convertShadowBlocks(); diff --git a/tests/mocha/contextmenu_items_test.js b/tests/mocha/contextmenu_items_test.js index b5d480c37b0..ff6f4fe91d1 100644 --- a/tests/mocha/contextmenu_items_test.js +++ b/tests/mocha/contextmenu_items_test.js @@ -123,7 +123,7 @@ suite('Context Menu Items', function () { suite('Cleanup', function () { setup(function () { this.cleanupOption = this.registry.getItem('cleanWorkspace'); - this.cleanupStub = sinon.stub(this.workspace, 'cleanUp'); + this.tidyUpStub = sinon.stub(this.workspace, 'tidyUp'); }); test('Enabled when multiple blocks', function () { @@ -153,9 +153,9 @@ suite('Context Menu Items', function () { ); }); - test('Calls workspace cleanup', function () { + test('Calls workspace tidyUp', function () { this.cleanupOption.callback(this.scope); - sinon.assert.calledOnce(this.cleanupStub); + sinon.assert.calledOnce(this.tidyUpStub); }); test('Has correct label', function () { diff --git a/tests/mocha/index.html b/tests/mocha/index.html index ff3467907d7..adc63da4a12 100644 --- a/tests/mocha/index.html +++ b/tests/mocha/index.html @@ -110,6 +110,7 @@ import './old_workspace_comment_test.js'; import './procedure_map_test.js'; import './blocks/procedures_test.js'; + import './rect_test.js'; import './registry_test.js'; import './render_management_test.js'; import './serializer_test.js'; diff --git a/tests/mocha/rect_test.js b/tests/mocha/rect_test.js new file mode 100644 index 00000000000..67b116be015 --- /dev/null +++ b/tests/mocha/rect_test.js @@ -0,0 +1,968 @@ +/** + * @license + * Copyright 2024 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import {assert} from '../../node_modules/chai/chai.js'; +import { + sharedTestSetup, + sharedTestTeardown, +} from './test_helpers/setup_teardown.js'; + +suite('Rect', function () { + setup(function () { + sharedTestSetup.call(this); + this.createCoord = function(x, y) { + return new Blockly.utils.Coordinate(x, y); + }; + }); + teardown(function () { + sharedTestTeardown.call(this); + }); + + suite('Rect()', function () { + test('initializes properties correctly', function() { + const rect = new Blockly.utils.Rect(1, 2, 3, 4); + + assert.equal(rect.top, 1, 'top should be initialized'); + assert.equal(rect.bottom, 2, 'bottom should be initialized'); + assert.equal(rect.left, 3, 'left should be initialized'); + assert.equal(rect.right, 4, 'right should be initialized'); + }); + }); + + suite('createFromPoint()', function () { + test('initializes properties correctly', function() { + const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 23, 45); + + assert.equal(rect.top, 2, 'top should be initialized'); + assert.equal(rect.bottom, 47, 'bottom should be initialized'); + assert.equal(rect.left, 1, 'left should be initialized'); + assert.equal(rect.right, 24, 'right should be initialized'); + }); + }); + + suite('clone()', function () { + test('copies properties correctly', function() { + const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 23, 45); + + const clonedRect = rect.clone(); + + assert.equal(clonedRect.top, rect.top, 'top should be cloned'); + assert.equal(clonedRect.bottom, rect.bottom, 'bottom should be cloned'); + assert.equal(clonedRect.left, rect.left, 'left should be cloned'); + assert.equal(clonedRect.right, rect.right, 'right should be cloned'); + }); + }); + + suite('equals()', function () { + test('same object instance should equal itself', function() { + const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 23, 45); + + const areEqual = Blockly.utils.Rect.equals(rect, rect) + + assert.isTrue(areEqual, 'an instance should equal itself'); + }); + + test('null instances should be equal', function() { + const areEqual = Blockly.utils.Rect.equals(null, null) + + assert.isTrue(areEqual, 'null should equal null'); + }); + + test('an object and null should not be equal', function() { + const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 23, 45); + + const areEqual = Blockly.utils.Rect.equals(rect, null) + + assert.isFalse(areEqual, 'non-null should not equal null'); + }); + + test('null and an object should not be equal', function() { + const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 23, 45); + + const areEqual = Blockly.utils.Rect.equals(null, rect) + + assert.isFalse(areEqual, 'null should not equal non-null'); + }); + + test('object should equal its clone', function() { + const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 23, 45); + + const areEqual = Blockly.utils.Rect.equals(rect, rect.clone()) + + assert.isTrue(areEqual, 'an instance and its clone should be equal'); + }); + + test('object should equal its clone', function() { + const rect1 = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 23, 45); + const rect2 = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 23, 45); + + const areEqual = Blockly.utils.Rect.equals(rect1, rect2) + + assert.isTrue(areEqual, 'two objects constructed in the same way should be equal'); + }); + + test('object should not equal object with different x position', function() { + const rect1 = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 23, 45); + const rect2 = Blockly.utils.Rect.createFromPoint(this.createCoord(3, 2), 23, 45); + + const areEqual = Blockly.utils.Rect.equals(rect1, rect2) + + assert.isFalse(areEqual, 'two objects with different x positions should not be equal'); + }); + + test('object should not equal object with different y position', function() { + const rect1 = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 23, 45); + const rect2 = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 4), 23, 45); + + const areEqual = Blockly.utils.Rect.equals(rect1, rect2) + + assert.isFalse(areEqual, 'two objects with different y positions should not be equal'); + }); + + test('object should not equal object with different x and y positions', function() { + const rect1 = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 23, 45); + const rect2 = Blockly.utils.Rect.createFromPoint(this.createCoord(3, 4), 23, 45); + + const areEqual = Blockly.utils.Rect.equals(rect1, rect2) + + assert.isFalse(areEqual, 'two objects with different x/y positions should not be equal'); + }); + + test('object should not equal object with different width', function() { + const rect1 = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 23, 45); + const rect2 = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 46, 45); + + const areEqual = Blockly.utils.Rect.equals(rect1, rect2) + + assert.isFalse(areEqual, 'two objects with different widths should not be equal'); + }); + + test('object should not equal object with different height', function() { + const rect1 = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 23, 45); + const rect2 = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 23, 89); + + const areEqual = Blockly.utils.Rect.equals(rect1, rect2) + + assert.isFalse(areEqual, 'two objects with different heights should not be equal'); + }); + + test('object should not equal object with all different properties', function() { + const rect1 = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 23, 45); + const rect2 = Blockly.utils.Rect.createFromPoint(this.createCoord(3, 4), 46, 89); + + const areEqual = Blockly.utils.Rect.equals(rect1, rect2) + + assert.isFalse(areEqual, 'two objects with all different properties should not be equal'); + }); + }); + + suite('getHeight()', function () { + test('computes zero height for empty rectangle', function() { + const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(0, 0), 0, 0); + + assert.equal(rect.getHeight(), 0, 'height should be 0'); + }); + + test('computes height of 1 for unit square rectangle', function() { + const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(0, 0), 1, 1); + + assert.equal(rect.getHeight(), 1, 'height should be 1'); + }); + + test('computes height of 1 for unit square rectangle not at origin', function() { + const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 1, 1); + + assert.equal(rect.getHeight(), 1, 'height should be 1'); + }); + + test('computes height of 1 for unit square rectangle with negative position', function() { + const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(-1, -2), 1, 1); + + assert.equal(rect.getHeight(), 1, 'height should be 1'); + }); + + test('computes decimal height for non-square rectangle not at origin', function() { + const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1.1, 2.2), 3.3, 4.4); + + assert.approximately(rect.getHeight(), 4.4, 1e-5, 'height should be 4.4'); + }); + }); + + suite('getWidth()', function () { + test('computes zero width for empty rectangle', function() { + const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(0, 0), 0, 0); + + assert.equal(rect.getWidth(), 0, 'width should be 0'); + }); + + test('computes width of 1 for unit square rectangle', function() { + const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(0, 0), 1, 1); + + assert.equal(rect.getWidth(), 1, 'width should be 1'); + }); + + test('computes width of 1 for unit square rectangle not at origin', function() { + const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 1, 1); + + assert.equal(rect.getWidth(), 1, 'width should be 1'); + }); + + test('computes width of 1 for unit square rectangle with negative position', function() { + const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(-1, -2), 1, 1); + + assert.equal(rect.getWidth(), 1, 'width should be 1'); + }); + + test('computes decimal width for non-square rectangle not at origin', function() { + const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1.1, 2.2), 3.3, 4.4); + + assert.approximately(rect.getWidth(), 3.3, 1e-5, 'width should be 3.3'); + }); + }); + + suite('contains()', function () { + suite('point contained within rect', function () { + test('origin for zero-sized square', function() { + const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(0, 0), 0, 0); + + const isContained = rect.contains(0, 0); + + assert.isTrue(isContained, 'Rect contains (0, 0)'); + }); + + test('whole number centroid for square at origin', function() { + const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(0, 0), 2, 2); + + const isContained = rect.contains(1, 1); + + assert.isTrue(isContained, 'Rect contains (1, 1)'); + }); + + test('decimal number centroid for square at origin', function() { + const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(0, 0), 1, 1); + + const isContained = rect.contains(0.5, 0.5); + + assert.isTrue(isContained, 'Rect contains (0.5, 0.5)'); + }); + + test('centroid for non-square not at origin', function() { + const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 3, 4); + + const isContained = rect.contains(2.5, 4); + + assert.isTrue(isContained, 'Rect contains (2.5, 4)'); + }); + + test('negative centroid for non-square not at origin', function() { + const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(-10, -20), 3, 5); + + const isContained = rect.contains(-8.5, -17.5); + + assert.isTrue(isContained, 'Rect contains (-8.5, -17.5)'); + }); + + test('NW corner', function() { + const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 3, 4); + + const isContained = rect.contains(1, 2); + + assert.isTrue(isContained, 'Rect contains (1, 2)'); + }); + + test('NE corner', function() { + const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 3, 4); + + const isContained = rect.contains(4, 2); + + assert.isTrue(isContained, 'Rect contains (4, 2)'); + }); + + test('SW corner', function() { + const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 3, 4); + + const isContained = rect.contains(1, 6); + + assert.isTrue(isContained, 'Rect contains (1, 6)'); + }); + + test('SE corner', function() { + const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 3, 4); + + const isContained = rect.contains(4, 6); + + assert.isTrue(isContained, 'Rect contains (4, 6)'); + }); + + test('left edge midpoint', function() { + const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 3, 4); + + const isContained = rect.contains(1, 4); + + assert.isTrue(isContained, 'Rect contains (1, 4)'); + }); + + test('right edge midpoint', function() { + const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 3, 4); + + const isContained = rect.contains(4, 4); + + assert.isTrue(isContained, 'Rect contains (4, 4)'); + }); + + test('top edge midpoint', function() { + const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 3, 4); + + const isContained = rect.contains(2.5, 2); + + assert.isTrue(isContained, 'Rect contains (2.5, 2)'); + }); + + test('bottom edge midpoint', function() { + const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 3, 4); + + const isContained = rect.contains(2.5, 6); + + assert.isTrue(isContained, 'Rect contains (2.5, 6)'); + }); + }); + suite('point not contained within rect', function () { + test('non-origin for zero-sized square', function() { + const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(0, 0), 0, 0); + + const isContained = rect.contains(0.1, 0.1); + + assert.isFalse(isContained, 'Rect does not contain (0.1, 0.1)'); + }); + + test('point at midpoint x but above unit square', function() { + const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 1), 2, 2); + + const isContained = rect.contains(2, 0); + + assert.isFalse(isContained, 'Rect does not contain (2, 0)'); + }); + + test('point at midpoint x but below unit square', function() { + const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 1), 2, 2); + + const isContained = rect.contains(2, 4); + + assert.isFalse(isContained, 'Rect does not contain (2, 4)'); + }); + + test('point at midpoint y but left of unit square', function() { + const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 1), 2, 2); + + const isContained = rect.contains(0, 2); + + assert.isFalse(isContained, 'Rect does not contain (0, 2)'); + }); + + test('point at midpoint y but right of unit square', function() { + const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 1), 2, 2); + + const isContained = rect.contains(4, 2); + + assert.isFalse(isContained, 'Rect does not contain (4, 2)'); + }); + + test('positive point far outside positive rectangle', function() { + const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 3, 4); + + const isContained = rect.contains(45, 89); + + assert.isFalse(isContained, 'Rect does not contain (45, 89)'); + }); + + test('negative point far outside positive rectangle', function() { + const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 3, 4); + + const isContained = rect.contains(-45, -89); + + assert.isFalse(isContained, 'Rect does not contain (-45, -89)'); + }); + + test('positive point far outside negative rectangle', function() { + const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(-10, -20), 3, 5); + + const isContained = rect.contains(45, 89); + + assert.isFalse(isContained, 'Rect does not contain (45, 89)'); + }); + + test('negative point far outside negative rectangle', function() { + const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(-10, -20), 3, 5); + + const isContained = rect.contains(-45, -89); + + assert.isFalse(isContained, 'Rect does not contain (-45, -89)'); + }); + + test('Point just outside NW corner', function() { + const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 3, 4); + + const isContained = rect.contains(0.9, 1.9); + + assert.isFalse(isContained, 'Rect does not contain (0.9, 1.9)'); + }); + + test('Point just outside NE corner', function() { + const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 3, 4); + + const isContained = rect.contains(4.1, 1.9); + + assert.isFalse(isContained, 'Rect does not contain (4.1, 1.9)'); + }); + + test('Point just outside SW corner', function() { + const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 3, 4); + + const isContained = rect.contains(0.9, 6.1); + + assert.isFalse(isContained, 'Rect does not contain (0.9, 6.1)'); + }); + + test('Point just outside SE corner', function() { + const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 3, 4); + + const isContained = rect.contains(4.1, 6.1); + + assert.isFalse(isContained, 'Rect does not contain (4.1, 6.1)'); + }); + + test('Point just outside left edge midpoint', function() { + const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 3, 4); + + const isContained = rect.contains(0.9, 4); + + assert.isFalse(isContained, 'Rect does not contain (0.9, 4)'); + }); + + test('Point just outside right edge midpoint', function() { + const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 3, 4); + + const isContained = rect.contains(4.1, 4); + + assert.isFalse(isContained, 'Rect does not contain (4.1, 4)'); + }); + + test('Point just outside top edge midpoint', function() { + const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 3, 4); + + const isContained = rect.contains(2.5, 1.9); + + assert.isFalse(isContained, 'Rect does not contain (2.5, 1.9)'); + }); + + test('Point just outside bottom edge midpoint', function() { + const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 3, 4); + + const isContained = rect.contains(2.5, 6.1); + + assert.isFalse(isContained, 'Rect does not contain (2.5, 6.1)'); + }); + }); + }); + + // NOTE TO DEVELOPER: For intersection tests, rects are either large (dimension size '2') or small + // (dimension size '1'). For compactness, the comments denoting the scenario being tested try to + // label larger rects with '2' where they can fit, but smaller rects are generally too small to + // fit any text. + suite('intersects()', function () { + suite('does intersect', function () { + test('rect and itself', function() { + const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 23, 45); + + const doIntersect = rect.intersects(rect); + + assert.isTrue(doIntersect, 'a rect always intersects with itself'); + }); + + test('rect and its clone', function() { + const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 23, 45); + + const doIntersect = rect.intersects(rect.clone()); + + assert.isTrue(doIntersect, 'a rect always intersects with its clone'); + }); + + test('two rects of the same positions and dimensions', function() { + const rect1 = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 23, 45); + const rect2 = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 23, 45); + + const doIntersect = rect1.intersects(rect2); + + assert.isTrue(doIntersect, 'two rects with the same positions and dimensions intersect'); + }); + + test('upper left/lower right', function() { + // ┌───┐ + // │2┌───┐ + // └─│─┘2│ + // └───┘ + const nwRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 1), 2, 2); + const seRect = Blockly.utils.Rect.createFromPoint(this.createCoord(2, 2), 2, 2); + + const doIntersectOneWay = nwRect.intersects(seRect); + const doIntersectOtherWay = seRect.intersects(nwRect); + + assert.isTrue(doIntersectOneWay, 'SE corner of NW rect intersects with SE rect'); + assert.isTrue(doIntersectOtherWay, 'NW corner of SE rect intersects with NW rect'); + }); + + test('upper right/lower left', function() { + // ┌───┐ + // ┌───┐2│ + // │2└─│─┘ + // └───┘ + const neRect = Blockly.utils.Rect.createFromPoint(this.createCoord(2, 1), 2, 2); + const swRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 2, 2); + + const doIntersectOneWay = neRect.intersects(swRect); + const doIntersectOtherWay = swRect.intersects(neRect); + + assert.isTrue(doIntersectOneWay, 'SW corner of NE rect intersects with SW rect'); + assert.isTrue(doIntersectOtherWay, 'NE corner of SW rect intersects with NE rect'); + }); + + test('small rect overlapping left side of big rect', function() { + // ┌────┐ + // ┌───┐2 │ + // └───┘ │ + // └────┘ + const bigRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 1), 2, 2); + const smallRect = Blockly.utils.Rect.createFromPoint(this.createCoord(0.5, 1.5), 1, 1); + + const doIntersectOneWay = bigRect.intersects(smallRect); + const doIntersectOtherWay = smallRect.intersects(bigRect); + + assert.isTrue(doIntersectOneWay, 'big rect intersects top/bottom sides of small rect'); + assert.isTrue(doIntersectOtherWay, 'small rect intersects left side of big rect'); + }); + + test('small rect overlapping right side of big rect', function() { + // ┌────┐ + // │ 2┌───┐ + // │ └───┘ + // └────┘ + const bigRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 1), 2, 2); + const smallRect = Blockly.utils.Rect.createFromPoint(this.createCoord(2.5, 1.5), 1, 1); + + const doIntersectOneWay = bigRect.intersects(smallRect); + const doIntersectOtherWay = smallRect.intersects(bigRect); + + assert.isTrue(doIntersectOneWay, 'big rect intersects top/bottom sides of small rect'); + assert.isTrue(doIntersectOtherWay, 'small rect intersects right side of big rect'); + }); + + test('small rect overlapping top side of big rect', function() { + // ┌─┐ + // ┌│─│┐ + // │└─┘│ + // └───┘ + const bigRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 1), 2, 2); + const smallRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1.5, 0.5), 1, 1); + + const doIntersectOneWay = bigRect.intersects(smallRect); + const doIntersectOtherWay = smallRect.intersects(bigRect); + + assert.isTrue(doIntersectOneWay, 'big rect intersects left/right sides of small rect'); + assert.isTrue(doIntersectOtherWay, 'small rect intersects top side of big rect'); + }); + + test('small rect overlapping bottom side of big rect', function() { + // ┌───┐ + // │┌─┐│ + // └│─│┘ + // └─┘ + const bigRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 1), 2, 2); + const smallRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1.5, 2.5), 1, 1); + + const doIntersectOneWay = bigRect.intersects(smallRect); + const doIntersectOtherWay = smallRect.intersects(bigRect); + + assert.isTrue(doIntersectOneWay, 'big rect intersects left/right sides of small rect'); + assert.isTrue(doIntersectOtherWay, 'small rect intersects bottom side of big rect'); + }); + + test('edge only intersection with all corners outside each rect', function() { + // ┌─┐ + // │ │ + // ┌─────┐ + // └─────┘ + // │ │ + // └─┘ + const tallRect = Blockly.utils.Rect.createFromPoint(this.createCoord(2, 1), 1, 2); + const wideRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 2, 1); + + const doIntersectOneWay = tallRect.intersects(wideRect); + const doIntersectOtherWay = wideRect.intersects(tallRect); + + assert.isTrue(doIntersectOneWay, 'tall rect intersects top/bottom of wide rect'); + assert.isTrue(doIntersectOtherWay, 'wide rect intersects left/right of tall rect'); + }); + + test('small rect within larger rect', function() { + // ┌─────┐ + // │ ┌─┐ │ + // │ └─┘ │ + // └─────┘ + const bigRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 1), 2, 2); + const smallRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1.5, 1.5), 1, 1); + + const doIntersectOneWay = bigRect.intersects(smallRect); + const doIntersectOtherWay = smallRect.intersects(bigRect); + + assert.isTrue(doIntersectOneWay, 'big rect intersects small rect since it is contained'); + assert.isTrue(doIntersectOtherWay, 'small rect intersects big rect since it is contained'); + }); + + test('rects overlapping on left/right sides', function() { + // ┌──┌────┐ + // │ 2│ │2 │ + // └──└────┘ + const leftRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 1), 2, 2); + const rightRect = Blockly.utils.Rect.createFromPoint(this.createCoord(2, 1), 2, 2); + + const doIntersectOneWay = leftRect.intersects(rightRect); + const doIntersectOtherWay = rightRect.intersects(leftRect); + + assert.isTrue(doIntersectOneWay, 'Left rect\'s right overlaps with right rect\'s left'); + assert.isTrue(doIntersectOtherWay, 'Right rect\'s left overlaps with left rect\'s right'); + }); + + test('rects overlapping on top/bottom sides', function() { + // ┌───┐ + // ┌───┐ + // │───│ + // └───┘ + const topRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 1), 2, 2); + const bottomRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 2, 2); + + const doIntersectOneWay = topRect.intersects(bottomRect); + const doIntersectOtherWay = bottomRect.intersects(topRect); + + assert.isTrue(doIntersectOneWay, 'Top rect\'s bottom overlaps with bottom rect\'s top'); + assert.isTrue(doIntersectOtherWay, 'Bottom rect\'s top overlaps with top rect\'s bottom'); + }); + + test('rects adjacent on left/right sides', function() { + // ┌───┬───┐ + // │ 2 │ 2 │ + // └───┴───┘ + const leftRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 1), 2, 2); + const rightRect = Blockly.utils.Rect.createFromPoint(this.createCoord(3, 1), 2, 2); + + const doIntersectOneWay = leftRect.intersects(rightRect); + const doIntersectOtherWay = rightRect.intersects(leftRect); + + assert.isTrue(doIntersectOneWay, 'Left rect\'s right intersects with right rect\'s left'); + assert.isTrue(doIntersectOtherWay, 'Right rect\'s left intersects with left rect\'s right'); + }); + + test('rects adjacent on top/bottom sides', function() { + // ┌───┐ + // │ 2 │ + // ├───┤ + // │ 2 │ + // └───┘ + const topRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 1), 2, 2); + const bottomRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 3), 2, 2); + + const doIntersectOneWay = topRect.intersects(bottomRect); + const doIntersectOtherWay = bottomRect.intersects(topRect); + + assert.isTrue(doIntersectOneWay, 'Top rect\'s bottom intersects with bottom rect\'s top'); + assert.isTrue(doIntersectOtherWay, 'Bottom rect\'s top intersects with top rect\'s bottom'); + }); + + test('small left rect adjacent to big right rect', function() { + // ┌───┐ + // ┌─┐ 2 │ + // └─┘ │ + // └───┘ + const bigRect = Blockly.utils.Rect.createFromPoint(this.createCoord(2, 1), 2, 2); + const smallRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 1.5), 1, 1); + + const doIntersectOneWay = bigRect.intersects(smallRect); + const doIntersectOtherWay = smallRect.intersects(bigRect); + + assert.isTrue(doIntersectOneWay, 'big rect\'s left intersects small rect\'s right'); + assert.isTrue(doIntersectOtherWay, 'small rect\'s right intersects big rect\'s left'); + }); + + test('small right rect adjacent to big left rect', function() { + // ┌───┐ + // │ 2 ┌─┐ + // │ └─┘ + // └───┘ + const bigRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 1), 2, 2); + const smallRect = Blockly.utils.Rect.createFromPoint(this.createCoord(3, 1.5), 1, 1); + + const doIntersectOneWay = bigRect.intersects(smallRect); + const doIntersectOtherWay = smallRect.intersects(bigRect); + + assert.isTrue(doIntersectOneWay, 'big rect\'s right intersects small rect\'s left'); + assert.isTrue(doIntersectOtherWay, 'small rect\'s left intersects big rect\'s right'); + }); + + test('small top rect adjacent to big bottom rect', function() { + // ┌─┐ + // ┌└─┘┐ + // │ 2 │ + // └───┘ + const bigRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 1), 2, 2); + const smallRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1.5, 0), 1, 1); + + const doIntersectOneWay = bigRect.intersects(smallRect); + const doIntersectOtherWay = smallRect.intersects(bigRect); + + assert.isTrue(doIntersectOneWay, 'big rect\'s top intersects small rect\'s bottom'); + assert.isTrue(doIntersectOtherWay, 'small rect\'s bottom intersects big rect\'s top'); + }); + + test('small bottom rect adjacent to big top rect', function() { + // ┌───┐ + // │ 2 │ + // └┌─┐┘ + // └─┘ + const bigRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 1), 2, 2); + const smallRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1.5, 3), 1, 1); + + const doIntersectOneWay = bigRect.intersects(smallRect); + const doIntersectOtherWay = smallRect.intersects(bigRect); + + assert.isTrue(doIntersectOneWay, 'big rect\'s bottom intersects small rect\'s top'); + assert.isTrue(doIntersectOtherWay, 'small rect\'s top intersects big rect\'s bottom'); + }); + + test('SW rect corner-adjacent to NE rect', function() { + // ┌───┐ + // │ 2 │ + // ┌───┐───┘ + // │ 2 │ + // └───┘ + const swRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 3), 2, 2); + const neRect = Blockly.utils.Rect.createFromPoint(this.createCoord(3, 1), 2, 2); + + const doIntersectOneWay = swRect.intersects(neRect); + const doIntersectOtherWay = neRect.intersects(swRect); + + assert.isTrue(doIntersectOneWay, 'SW rect intersects with SW corner of NE rect'); + assert.isTrue(doIntersectOtherWay, 'NE rect intersects with NE corner of SW rect'); + }); + + test('NW rect corner-adjacent to SE rect', function() { + // ┌───┐ + // │ 2 │ + // └───┘───┐ + // │ 2 │ + // └───┘ + const nwRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 1), 2, 2); + const seRect = Blockly.utils.Rect.createFromPoint(this.createCoord(3, 3), 2, 2); + + const doIntersectOneWay = seRect.intersects(nwRect); + const doIntersectOtherWay = nwRect.intersects(seRect); + + assert.isTrue(doIntersectOneWay, 'SE rect intersects with SE corner of NW rect'); + assert.isTrue(doIntersectOtherWay, 'NW rect intersects with NW corner of SE rect'); + }); + }); + suite('does not intersect', function () { + test('Same-size rects nearly side-adjacent', function() { + // ┌───┐ ┌───┐ + // │ 2 │ │ 2 │ + // └───┘ └───┘ + const westRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 1), 2, 2); + const eastRect = Blockly.utils.Rect.createFromPoint(this.createCoord(3.5, 1), 2, 2); + + const doIntersectOneWay = westRect.intersects(eastRect); + const doIntersectOtherWay = eastRect.intersects(westRect); + + assert.isFalse(doIntersectOneWay, 'Western rect does not intersect with eastern rect'); + assert.isFalse(doIntersectOtherWay, 'Eastern rect does not intersect with western rect'); + }); + + test('Same-size rects nearly side-adjacent', function() { + // ┌───┐ + // │ 2 │ + // └───┘ + // ┌───┐ + // │ 2 │ + // └───┘ + const northRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 1), 2, 2); + const southRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 3.5), 2, 2); + + const doIntersectOneWay = northRect.intersects(southRect); + const doIntersectOtherWay = southRect.intersects(northRect); + + assert.isFalse(doIntersectOneWay, 'Northern rect does not intersect with southern rect'); + assert.isFalse(doIntersectOtherWay, 'Southern rect does not intersect with northern rect'); + }); + + test('Small rect left of big rect', function() { + // ┌───┐ + // ┌─┐│ 2 │ + // └─┘│ │ + // └───┘ + const westRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 1), 1, 1); + const eastRect = Blockly.utils.Rect.createFromPoint(this.createCoord(2.5, 1), 2, 2); + + const doIntersectOneWay = westRect.intersects(eastRect); + const doIntersectOtherWay = eastRect.intersects(westRect); + + assert.isFalse(doIntersectOneWay, 'Western rect does not intersect with eastern rect'); + assert.isFalse(doIntersectOtherWay, 'Eastern rect does not intersect with western rect'); + }); + + test('Small rect right of big rect', function() { + // ┌───┐ + // │ 2 │┌─┐ + // │ │└─┘ + // └───┘ + const westRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 1), 2, 2); + const eastRect = Blockly.utils.Rect.createFromPoint(this.createCoord(3.5, 1), 1, 1); + + const doIntersectOneWay = westRect.intersects(eastRect); + const doIntersectOtherWay = eastRect.intersects(westRect); + + assert.isFalse(doIntersectOneWay, 'Western rect does not intersect with eastern rect'); + assert.isFalse(doIntersectOtherWay, 'Eastern rect does not intersect with western rect'); + }); + + test('Small rect above big rect', function() { + // ┌─┐ + // └─┘ + // ┌───┐ + // │ 2 │ + // └───┘ + const northRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 1), 1, 1); + const southRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2.5), 2, 2); + + const doIntersectOneWay = northRect.intersects(southRect); + const doIntersectOtherWay = southRect.intersects(northRect); + + assert.isFalse(doIntersectOneWay, 'Northern rect does not intersect with southern rect'); + assert.isFalse(doIntersectOtherWay, 'Southern rect does not intersect with northern rect'); + }); + + test('Small rect below big rect', function() { + // ┌───┐ + // │ 2 │ + // └───┘ + // ┌─┐ + // └─┘ + const northRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 1), 2, 2); + const southRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 3.5), 1, 1); + + const doIntersectOneWay = northRect.intersects(southRect); + const doIntersectOtherWay = southRect.intersects(northRect); + + assert.isFalse(doIntersectOneWay, 'Northern rect does not intersect with southern rect'); + assert.isFalse(doIntersectOtherWay, 'Southern rect does not intersect with northern rect'); + }); + + test('Same-size rects diagonal (NE and SW) to each other', function() { + // ┌───┐ + // │ 2 │ + // └───┘ + // ┌───┐ + // │ 2 │ + // └───┘ + const neRect = Blockly.utils.Rect.createFromPoint(this.createCoord(3.5, 1), 2, 2); + const swRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 3.5), 2, 2); + + const doIntersectOneWay = neRect.intersects(swRect); + const doIntersectOtherWay = swRect.intersects(neRect); + + assert.isFalse(doIntersectOneWay, 'NE rect does not intersect with SW rect'); + assert.isFalse(doIntersectOtherWay, 'SW rect does not intersect with NE rect'); + }); + + test('Same-size rects diagonal (NW and SE) to each other', function() { + // ┌───┐ + // │ 2 │ + // └───┘ + // ┌───┐ + // │ 2 │ + // └───┘ + const nwRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 1), 2, 2); + const seRect = Blockly.utils.Rect.createFromPoint(this.createCoord(3.5, 3.5), 2, 2); + + const doIntersectOneWay = nwRect.intersects(seRect); + const doIntersectOtherWay = seRect.intersects(nwRect); + + assert.isFalse(doIntersectOneWay, 'NW rect does not intersect with SE rect'); + assert.isFalse(doIntersectOtherWay, 'SE rect does not intersect with NW rect'); + }); + + test('Small rect NE of big rect', function() { + // ┌─┐ + // └─┘ + // ┌───┐ + // │ 2 │ + // └───┘ + const neRect = Blockly.utils.Rect.createFromPoint(this.createCoord(3.5, 1), 1, 1); + const swRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2.5), 2, 2); + + const doIntersectOneWay = neRect.intersects(swRect); + const doIntersectOtherWay = swRect.intersects(neRect); + + assert.isFalse(doIntersectOneWay, 'NE rect does not intersect with SW rect'); + assert.isFalse(doIntersectOtherWay, 'SW rect does not intersect with NE rect'); + }); + + test('Small rect NW of big rect', function() { + // ┌─┐ + // └─┘ + // ┌───┐ + // │ 2 │ + // └───┘ + const nwRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 1), 1, 1); + const seRect = Blockly.utils.Rect.createFromPoint(this.createCoord(2.5, 2.5), 2, 2); + + const doIntersectOneWay = nwRect.intersects(seRect); + const doIntersectOtherWay = seRect.intersects(nwRect); + + assert.isFalse(doIntersectOneWay, 'NW rect does not intersect with SE rect'); + assert.isFalse(doIntersectOtherWay, 'SE rect does not intersect with NW rect'); + }); + + test('Small rect SW of big rect', function() { + // ┌───┐ + // │ 2 │ + // └───┘ + // ┌─┐ + // └─┘ + const neRect = Blockly.utils.Rect.createFromPoint(this.createCoord(2.5, 1), 2, 2); + const swRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 3.5), 1, 1); + + const doIntersectOneWay = neRect.intersects(swRect); + const doIntersectOtherWay = swRect.intersects(neRect); + + assert.isFalse(doIntersectOneWay, 'NE rect does not intersect with SW rect'); + assert.isFalse(doIntersectOtherWay, 'SW rect does not intersect with NE rect'); + }); + + test('Small rect SE of big rect', function() { + // ┌───┐ + // │ 2 │ + // └───┘ + // ┌─┐ + // └─┘ + const nwRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 1), 2, 2); + const seRect = Blockly.utils.Rect.createFromPoint(this.createCoord(3.5, 3.5), 1, 1); + + const doIntersectOneWay = nwRect.intersects(seRect); + const doIntersectOtherWay = seRect.intersects(nwRect); + + assert.isFalse(doIntersectOneWay, 'NW rect does not intersect with SE rect'); + assert.isFalse(doIntersectOtherWay, 'SE rect does not intersect with NW rect'); + }); + }); + }); +}); diff --git a/tests/mocha/workspace_svg_test.js b/tests/mocha/workspace_svg_test.js index 93f8f715806..b76cd35165f 100644 --- a/tests/mocha/workspace_svg_test.js +++ b/tests/mocha/workspace_svg_test.js @@ -16,7 +16,6 @@ import * as eventUtils from '../../build/src/core/events/utils.js'; import { sharedTestSetup, sharedTestTeardown, - workspaceTeardown, } from './test_helpers/setup_teardown.js'; import {testAWorkspace} from './test_helpers/workspace.js'; @@ -408,6 +407,433 @@ suite('WorkspaceSvg', function () { }); }); }); + + suite('tidyUp', function () { + test('empty workspace does not change', function() { + this.workspace.tidyUp(); + + const blocks = this.workspace.getTopBlocks(true); + assert.equal(blocks.length, 0, 'workspace is empty'); + }); + + test('single block at (0, 0) does not change', function() { + const blockJson = { + "type": "math_number", + "x": 0, + "y": 0, + "fields": { + "NUM": 123 + } + }; + Blockly.serialization.blocks.append(blockJson, this.workspace); + + this.workspace.tidyUp(); + + const blocks = this.workspace.getTopBlocks(true); + const origin = new Blockly.utils.Coordinate(0, 0); + assert.equal(blocks.length, 1, 'workspace has one top-level block'); + assert.deepEqual(blocks[0].getRelativeToSurfaceXY(), origin, 'block is at origin'); + }); + + test('single block at (10, 15) is moved to (0, 0)', function() { + const blockJson = { + "type": "math_number", + "x": 10, + "y": 15, + "fields": { + "NUM": 123 + } + }; + Blockly.serialization.blocks.append(blockJson, this.workspace); + + this.workspace.tidyUp(); + + const topBlocks = this.workspace.getTopBlocks(true); + const allBlocks = this.workspace.getAllBlocks(false); + const origin = new Blockly.utils.Coordinate(0, 0); + assert.equal(topBlocks.length, 1, 'workspace has one top-level block'); + assert.equal(allBlocks.length, 1, 'workspace has one block overall'); + assert.deepEqual(topBlocks[0].getRelativeToSurfaceXY(), origin, 'block is at origin'); + }); + + test('single block at (10, 15) with child is moved as unit to (0, 0)', function() { + const blockJson = { + "type": "logic_negate", + "id": "parent", + "x": 10, + "y": 15, + "inputs": { + "BOOL": { + "block": { + "type": "logic_boolean", + "id": "child", + "fields": { + "BOOL": "TRUE" + } + } + } + } + }; + Blockly.serialization.blocks.append(blockJson, this.workspace); + + this.workspace.tidyUp(); + + const topBlocks = this.workspace.getTopBlocks(true); + const allBlocks = this.workspace.getAllBlocks(false); + const origin = new Blockly.utils.Coordinate(0, 0); + assert.equal(topBlocks.length, 1, 'workspace has one top-level block'); + assert.equal(allBlocks.length, 2, 'workspace has two blocks overall'); + assert.deepEqual(topBlocks[0].getRelativeToSurfaceXY(), origin, 'block is at origin'); + assert.notDeepEqual(allBlocks[1].getRelativeToSurfaceXY(), origin, 'child is not at origin'); + }); + + test('two blocks first at (10, 15) second at (0, 0) do not switch places', function() { + const blockJson1 = { + "type": "math_number", + "id": "block1", + "x": 10, + "y": 15, + "fields": { + "NUM": 123 + } + }; + const blockJson2 = {...blockJson1, "id": "block2", "x": 0, "y": 0}; + Blockly.serialization.blocks.append(blockJson1, this.workspace); + Blockly.serialization.blocks.append(blockJson2, this.workspace); + + this.workspace.tidyUp(); + + // block1 and block2 do not switch places since blocks are pre-sorted by their position before + // being tidied up, so the order they were added to the workspace doesn't matter. + const topBlocks = this.workspace.getTopBlocks(true); + const block1 = this.workspace.getBlockById('block1'); + const block2 = this.workspace.getBlockById('block2'); + const origin = new Blockly.utils.Coordinate(0, 0); + const belowBlock2 = new Blockly.utils.Coordinate(0, 50); + assert.equal(topBlocks.length, 2, 'workspace has two top-level blocks'); + assert.deepEqual(block2.getRelativeToSurfaceXY(), origin, 'block2 is at origin'); + assert.deepEqual(block1.getRelativeToSurfaceXY(), belowBlock2, 'block1 is below block2'); + }); + + test('two overlapping blocks are moved to origin and below', function() { + const blockJson1 = { + "type": "math_number", + "id": "block1", + "x": 25, + "y": 15, + "fields": { + "NUM": 123 + } + }; + const blockJson2 = {...blockJson1, "id": "block2", "x": 15.25, "y": 20.25}; + Blockly.serialization.blocks.append(blockJson1, this.workspace); + Blockly.serialization.blocks.append(blockJson2, this.workspace); + + this.workspace.tidyUp(); + + const topBlocks = this.workspace.getTopBlocks(true); + const block1 = this.workspace.getBlockById('block1'); + const block2 = this.workspace.getBlockById('block2'); + const origin = new Blockly.utils.Coordinate(0, 0); + const belowBlock1 = new Blockly.utils.Coordinate(0, 50); + assert.equal(topBlocks.length, 2, 'workspace has two top-level blocks'); + assert.deepEqual(block1.getRelativeToSurfaceXY(), origin, 'block1 is at origin'); + assert.deepEqual(block2.getRelativeToSurfaceXY(), belowBlock1, 'block2 is below block1'); + }); + + test('two overlapping blocks with snapping are moved to grid-aligned positions', function() { + const blockJson1 = { + "type": "math_number", + "id": "block1", + "x": 25, + "y": 15, + "fields": { + "NUM": 123 + } + }; + const blockJson2 = {...blockJson1, "id": "block2", "x": 15.25, "y": 20.25}; + Blockly.serialization.blocks.append(blockJson1, this.workspace); + Blockly.serialization.blocks.append(blockJson2, this.workspace); + this.workspace.getGrid().setSpacing(20); + this.workspace.getGrid().setSnapToGrid(true); + + this.workspace.tidyUp(); + + const topBlocks = this.workspace.getTopBlocks(true); + const block1 = this.workspace.getBlockById('block1'); + const block2 = this.workspace.getBlockById('block2'); + const snappedOffOrigin = new Blockly.utils.Coordinate(10, 10); + const belowBlock1 = new Blockly.utils.Coordinate(10, 70); + assert.equal(topBlocks.length, 2, 'workspace has two top-level blocks'); + assert.deepEqual(block1.getRelativeToSurfaceXY(), snappedOffOrigin, 'block1 is near origin'); + assert.deepEqual(block2.getRelativeToSurfaceXY(), belowBlock1, 'block2 is below block1'); + }); + + test('two overlapping blocks are moved to origin and below including children', function() { + const blockJson1 = { + "type": "logic_negate", + "id": "block1", + "x": 10, + "y": 15, + "inputs": { + "BOOL": { + "block": { + "type": "logic_boolean", + "fields": { + "BOOL": "TRUE" + } + } + } + } + }; + const blockJson2 = {...blockJson1, "id": "block2", "x": 15.25, "y": 20.25}; + Blockly.serialization.blocks.append(blockJson1, this.workspace); + Blockly.serialization.blocks.append(blockJson2, this.workspace); + + this.workspace.tidyUp(); + + const topBlocks = this.workspace.getTopBlocks(true); + const allBlocks = this.workspace.getAllBlocks(false); + const block1 = this.workspace.getBlockById('block1'); + const block2 = this.workspace.getBlockById('block2'); + const origin = new Blockly.utils.Coordinate(0, 0); + const belowBlock1 = new Blockly.utils.Coordinate(0, 50); + const block1Pos = block1.getRelativeToSurfaceXY(); + const block2Pos = block2.getRelativeToSurfaceXY(); + const block1ChildPos = block1.getChildren()[0].getRelativeToSurfaceXY(); + const block2ChildPos = block2.getChildren()[0].getRelativeToSurfaceXY(); + assert.equal(topBlocks.length, 2, 'workspace has two top-level block2'); + assert.equal(allBlocks.length, 4, 'workspace has four blocks overall'); + assert.deepEqual(block1Pos, origin, 'block1 is at origin'); + assert.deepEqual(block2Pos, belowBlock1, 'block2 is below block1'); + assert.isAbove(block1ChildPos.x, block1Pos.x, 'block1\'s child is right of it'); + assert.isBelow(block1ChildPos.y, block2Pos.y, 'block1\'s child is above block 2'); + assert.isAbove(block2ChildPos.x, block2Pos.x, 'block2\'s child is right of it'); + assert.isAbove(block2ChildPos.y, block1Pos.y, 'block2\'s child is below block 1'); + }); + + test('two large overlapping blocks are moved to origin and below', function() { + const blockJson1 = { + "type": "controls_repeat_ext", + "id": "block1", + "x": 10, + "y": 20, + "inputs": { + "TIMES": { + "shadow": { + "type": "math_number", + "fields": { + "NUM": 10 + } + } + }, + "DO": { + "block": { + "type": "controls_if", + "inputs": { + "IF0": { + "block": { + "type": "logic_boolean", + "fields": { + "BOOL": "TRUE" + } + } + }, + "DO0": { + "block": { + "type": "text_print", + "inputs": { + "TEXT": { + "shadow": { + "type": "text", + "fields": { + "TEXT": "abc" + } + } + } + } + } + } + } + } + } + } + }; + const blockJson2 = {...blockJson1, "id": "block2", "x": 20, "y": 30}; + Blockly.serialization.blocks.append(blockJson1, this.workspace); + Blockly.serialization.blocks.append(blockJson2, this.workspace); + + this.workspace.tidyUp(); + + const topBlocks = this.workspace.getTopBlocks(true); + const block1 = this.workspace.getBlockById('block1'); + const block2 = this.workspace.getBlockById('block2'); + const origin = new Blockly.utils.Coordinate(0, 0); + const belowBlock1 = new Blockly.utils.Coordinate(0, 144); + assert.equal(topBlocks.length, 2, 'workspace has two top-level blocks'); + assert.deepEqual(block1.getRelativeToSurfaceXY(), origin, 'block1 is at origin'); + assert.deepEqual(block2.getRelativeToSurfaceXY(), belowBlock1, 'block2 is below block1'); + }); + + test('five overlapping blocks are moved in-order as one column', function() { + const blockJson1 = { + "type": "math_number", + "id": "block1", + "x": 1, + "y": 2, + "fields": { + "NUM": 123 + } + }; + const blockJson2 = {...blockJson1, "id": "block2", "x": 3, "y": 4}; + const blockJson3 = {...blockJson1, "id": "block3", "x": 5, "y": 6}; + const blockJson4 = {...blockJson1, "id": "block4", "x": 7, "y": 8}; + const blockJson5 = {...blockJson1, "id": "block5", "x": 9, "y": 10}; + Blockly.serialization.blocks.append(blockJson1, this.workspace); + Blockly.serialization.blocks.append(blockJson2, this.workspace); + Blockly.serialization.blocks.append(blockJson3, this.workspace); + Blockly.serialization.blocks.append(blockJson4, this.workspace); + Blockly.serialization.blocks.append(blockJson5, this.workspace); + + this.workspace.tidyUp(); + + const topBlocks = this.workspace.getTopBlocks(true); + const block1Pos = this.workspace.getBlockById('block1').getRelativeToSurfaceXY(); + const block2Pos = this.workspace.getBlockById('block2').getRelativeToSurfaceXY(); + const block3Pos = this.workspace.getBlockById('block3').getRelativeToSurfaceXY(); + const block4Pos = this.workspace.getBlockById('block4').getRelativeToSurfaceXY(); + const block5Pos = this.workspace.getBlockById('block5').getRelativeToSurfaceXY(); + const origin = new Blockly.utils.Coordinate(0, 0); + assert.equal(topBlocks.length, 5, 'workspace has five top-level blocks'); + assert.deepEqual(block1Pos, origin, 'block1 is at origin'); + assert.equal(block2Pos.x, 0, 'block2.x is at 0'); + assert.equal(block3Pos.x, 0, 'block3.x is at 0'); + assert.equal(block4Pos.x, 0, 'block4.x is at 0'); + assert.equal(block5Pos.x, 0, 'block5.x is at 0'); + assert.isAbove(block2Pos.y, block1Pos.y, 'block2 is below block1'); + assert.isAbove(block3Pos.y, block2Pos.y, 'block3 is below block2'); + assert.isAbove(block4Pos.y, block3Pos.y, 'block4 is below block3'); + assert.isAbove(block5Pos.y, block4Pos.y, 'block5 is below block4'); + }); + + test('single immovable block at (10, 15) is not moved', function() { + const blockJson = { + "type": "math_number", + "x": 10, + "y": 15, + "movable": false, + "fields": { + "NUM": 123 + } + }; + Blockly.serialization.blocks.append(blockJson, this.workspace); + + this.workspace.tidyUp(); + + const topBlocks = this.workspace.getTopBlocks(true); + const allBlocks = this.workspace.getAllBlocks(false); + const origPos = new Blockly.utils.Coordinate(10, 15); + assert.equal(topBlocks.length, 1, 'workspace has one top-level block'); + assert.equal(allBlocks.length, 1, 'workspace has one block overall'); + assert.deepEqual(topBlocks[0].getRelativeToSurfaceXY(), origPos, 'block is at (10, 15)'); + }); + + test('multiple block types immovable blocks are not moved', function() { + const smallBlockJson = { + "type": "math_number", + "fields": { + "NUM": 123 + } + }; + const largeBlockJson = { + "type": "controls_repeat_ext", + "inputs": { + "TIMES": { + "shadow": { + "type": "math_number", + "fields": { + "NUM": 10 + } + } + }, + "DO": { + "block": { + "type": "controls_if", + "inputs": { + "IF0": { + "block": { + "type": "logic_boolean", + "fields": { + "BOOL": "TRUE" + } + } + }, + "DO0": { + "block": { + "type": "text_print", + "inputs": { + "TEXT": { + "shadow": { + "type": "text", + "fields": { + "TEXT": "abc" + } + } + } + } + } + } + } + } + } + } + }; + // Block 1 overlaps block 2 (immovable) from above. + const blockJson1 = {...smallBlockJson, "id": "block1", "x": 1, "y": 2}; + const blockJson2 = {...smallBlockJson, "id": "block2", "x": 10, "y": 20, "movable": false}; + // Block 3 overlaps block 2 (immovable) from below. + const blockJson3 = {...smallBlockJson, "id": "block3", "x": 2, "y": 30}; + const blockJson4 = {...largeBlockJson, "id": "block4", "x": 3, "y": 40}; + // Block 5 (immovable) will end up overlapping with block 4 since it's large and will be + // moved. + const blockJson5 = {...smallBlockJson, "id": "block5", "x": 20, "y": 200, "movable": false}; + Blockly.serialization.blocks.append(blockJson1, this.workspace); + Blockly.serialization.blocks.append(blockJson2, this.workspace); + Blockly.serialization.blocks.append(blockJson3, this.workspace); + Blockly.serialization.blocks.append(blockJson4, this.workspace); + Blockly.serialization.blocks.append(blockJson5, this.workspace); + + this.workspace.tidyUp(); + + const topBlocks = this.workspace.getTopBlocks(true); + const block1Rect = this.workspace.getBlockById('block1').getBoundingRectangle(); + const block2Rect = this.workspace.getBlockById('block2').getBoundingRectangle(); + const block3Rect = this.workspace.getBlockById('block3').getBoundingRectangle(); + const block4Rect = this.workspace.getBlockById('block4').getBoundingRectangle(); + const block5Rect = this.workspace.getBlockById('block5').getBoundingRectangle(); + assert.equal(topBlocks.length, 5, 'workspace has five top-level blocks'); + // Check that immovable blocks haven't moved. + assert.equal(block2Rect.left, 10, 'block2.x is at 10'); + assert.equal(block2Rect.top, 20, 'block2.y is at 20'); + assert.equal(block5Rect.left, 20, 'block5.x is at 20'); + assert.equal(block5Rect.top, 200, 'block5.y is at 200'); + // Check that movable positions have correctly been left-aligned. + assert.equal(block1Rect.left, 0, 'block1.x is at 0'); + assert.equal(block3Rect.left, 0, 'block3.x is at 0'); + assert.equal(block4Rect.left, 0, 'block4.x is at 0'); + // Block order should be: 2, 1, 3, 5, 4 since 2 and 5 are immovable. + assert.isAbove(block1Rect.top, block2Rect.top, 'block1 is below block2'); + assert.isAbove(block3Rect.top, block1Rect.top, 'block3 is below block1'); + assert.isAbove(block5Rect.top, block3Rect.top, 'block5 is below block3'); + assert.isAbove(block4Rect.top, block5Rect.top, 'block4 is below block5'); + // Ensure no blocks intersect (can check in order due to the position verification above). + assert.isFalse(block2Rect.intersects(block1Rect), "block2/block1 do not intersect"); + assert.isFalse(block1Rect.intersects(block3Rect), "block1/block3 do not intersect"); + assert.isFalse(block3Rect.intersects(block5Rect), "block3/block5 do not intersect"); + assert.isFalse(block5Rect.intersects(block4Rect), "block5/block4 do not intersect"); + }); + }); + suite('Workspace Base class', function () { testAWorkspace(); }); From 0413021b7c4b20017c5d6f9291024a940cce45d9 Mon Sep 17 00:00:00 2001 From: Ben Henning Date: Wed, 21 Aug 2024 21:02:07 +0000 Subject: [PATCH 02/32] Auto-fix formatting issues to address CI failure. --- core/utils/rect.ts | 23 +- core/workspace_svg.ts | 16 +- tests/mocha/rect_test.js | 1314 ++++++++++++++++++++++------- tests/mocha/workspace_svg_test.js | 563 +++++++----- 4 files changed, 1381 insertions(+), 535 deletions(-) diff --git a/core/utils/rect.ts b/core/utils/rect.ts index adf0d2b94d9..c7da2a6860b 100644 --- a/core/utils/rect.ts +++ b/core/utils/rect.ts @@ -77,10 +77,12 @@ export class Rect { // the border's line segment is contained within the other rectangle. The simplified version // used here can be logically interpreted as ensuring that each line segment of 'this' rectangle // is not outside the bounds of the 'other' rectangle (proving there's an intersection). - return (this.left <= other.right) - && (this.right >= other.left) - && (this.bottom >= other.top) - && (this.top <= other.bottom); + return ( + this.left <= other.right && + this.right >= other.left && + this.bottom >= other.top && + this.top <= other.bottom + ); } /** @@ -97,7 +99,12 @@ export class Rect { if (!a || !b) { return false; } - return a.top === b.top && a.bottom === b.bottom && a.left === b.left && a.right === b.right; + return ( + a.top === b.top && + a.bottom === b.bottom && + a.left === b.left && + a.right === b.right + ); } /** @@ -108,7 +115,11 @@ export class Rect { * @param height The height of the rectangle, in pixels. * @returns A newly created Rect using the provided Coordinate and dimensions. */ - static createFromPoint(position: Coordinate, width: number, height: number): Rect { + static createFromPoint( + position: Coordinate, + width: number, + height: number, + ): Rect { const left = position.x; const top = position.y; return new Rect(top, top + height, left, left + width); diff --git a/core/workspace_svg.ts b/core/workspace_svg.ts index cfab07d9921..ff47353feb2 100644 --- a/core/workspace_svg.ts +++ b/core/workspace_svg.ts @@ -1656,9 +1656,13 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { const movableBlocks = topBlocks.filter((block) => block.isMovable()); const immovableBlocks = topBlocks.filter((block) => !block.isMovable()); - const immovableBlockBounds = immovableBlocks.map((block) => block.getBoundingRectangle()); + const immovableBlockBounds = immovableBlocks.map((block) => + block.getBoundingRectangle(), + ); - const getNextIntersectingImmovableBlock = function(rect: Rect): Rect|null { + const getNextIntersectingImmovableBlock = function ( + rect: Rect, + ): Rect | null { for (const immovableRect of immovableBlockBounds) { if (rect.intersects(immovableRect)) { return immovableRect; @@ -1679,7 +1683,8 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { let conflictingRect = getNextIntersectingImmovableBlock(boundingRect); while (conflictingRect != null) { // If the block intersects with an immovable block, move it down past that immovable block. - cursorY = conflictingRect.top + conflictingRect.getHeight() + minBlockHeight; + cursorY = + conflictingRect.top + conflictingRect.getHeight() + minBlockHeight; block.moveBy(0, cursorY - boundingRect.top, ['cleanup']); block.snapToGrid(); boundingRect = block.getBoundingRectangle(); @@ -1688,7 +1693,10 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { // Ensure all next blocks start past the most recent (which will also put them past all // previously intersecting immovable blocks). - cursorY = block.getRelativeToSurfaceXY().y + block.getHeightWidth().height + minBlockHeight; + cursorY = + block.getRelativeToSurfaceXY().y + + block.getHeightWidth().height + + minBlockHeight; } eventUtils.setGroup(false); this.setResizesEnabled(true); diff --git a/tests/mocha/rect_test.js b/tests/mocha/rect_test.js index 67b116be015..796e7ec8749 100644 --- a/tests/mocha/rect_test.js +++ b/tests/mocha/rect_test.js @@ -13,7 +13,7 @@ import { suite('Rect', function () { setup(function () { sharedTestSetup.call(this); - this.createCoord = function(x, y) { + this.createCoord = function (x, y) { return new Blockly.utils.Coordinate(x, y); }; }); @@ -22,7 +22,7 @@ suite('Rect', function () { }); suite('Rect()', function () { - test('initializes properties correctly', function() { + test('initializes properties correctly', function () { const rect = new Blockly.utils.Rect(1, 2, 3, 4); assert.equal(rect.top, 1, 'top should be initialized'); @@ -33,8 +33,12 @@ suite('Rect', function () { }); suite('createFromPoint()', function () { - test('initializes properties correctly', function() { - const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 23, 45); + test('initializes properties correctly', function () { + const rect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 2), + 23, + 45, + ); assert.equal(rect.top, 2, 'top should be initialized'); assert.equal(rect.bottom, 47, 'bottom should be initialized'); @@ -44,8 +48,12 @@ suite('Rect', function () { }); suite('clone()', function () { - test('copies properties correctly', function() { - const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 23, 45); + test('copies properties correctly', function () { + const rect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 2), + 23, + 45, + ); const clonedRect = rect.clone(); @@ -57,167 +65,300 @@ suite('Rect', function () { }); suite('equals()', function () { - test('same object instance should equal itself', function() { - const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 23, 45); + test('same object instance should equal itself', function () { + const rect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 2), + 23, + 45, + ); - const areEqual = Blockly.utils.Rect.equals(rect, rect) + const areEqual = Blockly.utils.Rect.equals(rect, rect); assert.isTrue(areEqual, 'an instance should equal itself'); }); - test('null instances should be equal', function() { - const areEqual = Blockly.utils.Rect.equals(null, null) + test('null instances should be equal', function () { + const areEqual = Blockly.utils.Rect.equals(null, null); assert.isTrue(areEqual, 'null should equal null'); }); - test('an object and null should not be equal', function() { - const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 23, 45); + test('an object and null should not be equal', function () { + const rect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 2), + 23, + 45, + ); - const areEqual = Blockly.utils.Rect.equals(rect, null) + const areEqual = Blockly.utils.Rect.equals(rect, null); assert.isFalse(areEqual, 'non-null should not equal null'); }); - test('null and an object should not be equal', function() { - const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 23, 45); + test('null and an object should not be equal', function () { + const rect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 2), + 23, + 45, + ); - const areEqual = Blockly.utils.Rect.equals(null, rect) + const areEqual = Blockly.utils.Rect.equals(null, rect); assert.isFalse(areEqual, 'null should not equal non-null'); }); - test('object should equal its clone', function() { - const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 23, 45); + test('object should equal its clone', function () { + const rect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 2), + 23, + 45, + ); - const areEqual = Blockly.utils.Rect.equals(rect, rect.clone()) + const areEqual = Blockly.utils.Rect.equals(rect, rect.clone()); assert.isTrue(areEqual, 'an instance and its clone should be equal'); }); - test('object should equal its clone', function() { - const rect1 = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 23, 45); - const rect2 = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 23, 45); - - const areEqual = Blockly.utils.Rect.equals(rect1, rect2) - - assert.isTrue(areEqual, 'two objects constructed in the same way should be equal'); + test('object should equal its clone', function () { + const rect1 = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 2), + 23, + 45, + ); + const rect2 = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 2), + 23, + 45, + ); + + const areEqual = Blockly.utils.Rect.equals(rect1, rect2); + + assert.isTrue( + areEqual, + 'two objects constructed in the same way should be equal', + ); }); - test('object should not equal object with different x position', function() { - const rect1 = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 23, 45); - const rect2 = Blockly.utils.Rect.createFromPoint(this.createCoord(3, 2), 23, 45); - - const areEqual = Blockly.utils.Rect.equals(rect1, rect2) - - assert.isFalse(areEqual, 'two objects with different x positions should not be equal'); + test('object should not equal object with different x position', function () { + const rect1 = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 2), + 23, + 45, + ); + const rect2 = Blockly.utils.Rect.createFromPoint( + this.createCoord(3, 2), + 23, + 45, + ); + + const areEqual = Blockly.utils.Rect.equals(rect1, rect2); + + assert.isFalse( + areEqual, + 'two objects with different x positions should not be equal', + ); }); - test('object should not equal object with different y position', function() { - const rect1 = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 23, 45); - const rect2 = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 4), 23, 45); - - const areEqual = Blockly.utils.Rect.equals(rect1, rect2) - - assert.isFalse(areEqual, 'two objects with different y positions should not be equal'); + test('object should not equal object with different y position', function () { + const rect1 = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 2), + 23, + 45, + ); + const rect2 = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 4), + 23, + 45, + ); + + const areEqual = Blockly.utils.Rect.equals(rect1, rect2); + + assert.isFalse( + areEqual, + 'two objects with different y positions should not be equal', + ); }); - test('object should not equal object with different x and y positions', function() { - const rect1 = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 23, 45); - const rect2 = Blockly.utils.Rect.createFromPoint(this.createCoord(3, 4), 23, 45); - - const areEqual = Blockly.utils.Rect.equals(rect1, rect2) - - assert.isFalse(areEqual, 'two objects with different x/y positions should not be equal'); + test('object should not equal object with different x and y positions', function () { + const rect1 = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 2), + 23, + 45, + ); + const rect2 = Blockly.utils.Rect.createFromPoint( + this.createCoord(3, 4), + 23, + 45, + ); + + const areEqual = Blockly.utils.Rect.equals(rect1, rect2); + + assert.isFalse( + areEqual, + 'two objects with different x/y positions should not be equal', + ); }); - test('object should not equal object with different width', function() { - const rect1 = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 23, 45); - const rect2 = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 46, 45); - - const areEqual = Blockly.utils.Rect.equals(rect1, rect2) - - assert.isFalse(areEqual, 'two objects with different widths should not be equal'); + test('object should not equal object with different width', function () { + const rect1 = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 2), + 23, + 45, + ); + const rect2 = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 2), + 46, + 45, + ); + + const areEqual = Blockly.utils.Rect.equals(rect1, rect2); + + assert.isFalse( + areEqual, + 'two objects with different widths should not be equal', + ); }); - test('object should not equal object with different height', function() { - const rect1 = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 23, 45); - const rect2 = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 23, 89); - - const areEqual = Blockly.utils.Rect.equals(rect1, rect2) - - assert.isFalse(areEqual, 'two objects with different heights should not be equal'); + test('object should not equal object with different height', function () { + const rect1 = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 2), + 23, + 45, + ); + const rect2 = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 2), + 23, + 89, + ); + + const areEqual = Blockly.utils.Rect.equals(rect1, rect2); + + assert.isFalse( + areEqual, + 'two objects with different heights should not be equal', + ); }); - test('object should not equal object with all different properties', function() { - const rect1 = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 23, 45); - const rect2 = Blockly.utils.Rect.createFromPoint(this.createCoord(3, 4), 46, 89); - - const areEqual = Blockly.utils.Rect.equals(rect1, rect2) - - assert.isFalse(areEqual, 'two objects with all different properties should not be equal'); + test('object should not equal object with all different properties', function () { + const rect1 = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 2), + 23, + 45, + ); + const rect2 = Blockly.utils.Rect.createFromPoint( + this.createCoord(3, 4), + 46, + 89, + ); + + const areEqual = Blockly.utils.Rect.equals(rect1, rect2); + + assert.isFalse( + areEqual, + 'two objects with all different properties should not be equal', + ); }); }); suite('getHeight()', function () { - test('computes zero height for empty rectangle', function() { - const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(0, 0), 0, 0); + test('computes zero height for empty rectangle', function () { + const rect = Blockly.utils.Rect.createFromPoint( + this.createCoord(0, 0), + 0, + 0, + ); assert.equal(rect.getHeight(), 0, 'height should be 0'); }); - test('computes height of 1 for unit square rectangle', function() { - const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(0, 0), 1, 1); + test('computes height of 1 for unit square rectangle', function () { + const rect = Blockly.utils.Rect.createFromPoint( + this.createCoord(0, 0), + 1, + 1, + ); assert.equal(rect.getHeight(), 1, 'height should be 1'); }); - test('computes height of 1 for unit square rectangle not at origin', function() { - const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 1, 1); + test('computes height of 1 for unit square rectangle not at origin', function () { + const rect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 2), + 1, + 1, + ); assert.equal(rect.getHeight(), 1, 'height should be 1'); }); - test('computes height of 1 for unit square rectangle with negative position', function() { - const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(-1, -2), 1, 1); + test('computes height of 1 for unit square rectangle with negative position', function () { + const rect = Blockly.utils.Rect.createFromPoint( + this.createCoord(-1, -2), + 1, + 1, + ); assert.equal(rect.getHeight(), 1, 'height should be 1'); }); - test('computes decimal height for non-square rectangle not at origin', function() { - const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1.1, 2.2), 3.3, 4.4); + test('computes decimal height for non-square rectangle not at origin', function () { + const rect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1.1, 2.2), + 3.3, + 4.4, + ); assert.approximately(rect.getHeight(), 4.4, 1e-5, 'height should be 4.4'); }); }); suite('getWidth()', function () { - test('computes zero width for empty rectangle', function() { - const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(0, 0), 0, 0); + test('computes zero width for empty rectangle', function () { + const rect = Blockly.utils.Rect.createFromPoint( + this.createCoord(0, 0), + 0, + 0, + ); assert.equal(rect.getWidth(), 0, 'width should be 0'); }); - test('computes width of 1 for unit square rectangle', function() { - const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(0, 0), 1, 1); + test('computes width of 1 for unit square rectangle', function () { + const rect = Blockly.utils.Rect.createFromPoint( + this.createCoord(0, 0), + 1, + 1, + ); assert.equal(rect.getWidth(), 1, 'width should be 1'); }); - test('computes width of 1 for unit square rectangle not at origin', function() { - const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 1, 1); + test('computes width of 1 for unit square rectangle not at origin', function () { + const rect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 2), + 1, + 1, + ); assert.equal(rect.getWidth(), 1, 'width should be 1'); }); - test('computes width of 1 for unit square rectangle with negative position', function() { - const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(-1, -2), 1, 1); + test('computes width of 1 for unit square rectangle with negative position', function () { + const rect = Blockly.utils.Rect.createFromPoint( + this.createCoord(-1, -2), + 1, + 1, + ); assert.equal(rect.getWidth(), 1, 'width should be 1'); }); - test('computes decimal width for non-square rectangle not at origin', function() { - const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1.1, 2.2), 3.3, 4.4); + test('computes decimal width for non-square rectangle not at origin', function () { + const rect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1.1, 2.2), + 3.3, + 4.4, + ); assert.approximately(rect.getWidth(), 3.3, 1e-5, 'width should be 3.3'); }); @@ -225,104 +366,156 @@ suite('Rect', function () { suite('contains()', function () { suite('point contained within rect', function () { - test('origin for zero-sized square', function() { - const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(0, 0), 0, 0); + test('origin for zero-sized square', function () { + const rect = Blockly.utils.Rect.createFromPoint( + this.createCoord(0, 0), + 0, + 0, + ); const isContained = rect.contains(0, 0); assert.isTrue(isContained, 'Rect contains (0, 0)'); }); - test('whole number centroid for square at origin', function() { - const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(0, 0), 2, 2); + test('whole number centroid for square at origin', function () { + const rect = Blockly.utils.Rect.createFromPoint( + this.createCoord(0, 0), + 2, + 2, + ); const isContained = rect.contains(1, 1); assert.isTrue(isContained, 'Rect contains (1, 1)'); }); - test('decimal number centroid for square at origin', function() { - const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(0, 0), 1, 1); + test('decimal number centroid for square at origin', function () { + const rect = Blockly.utils.Rect.createFromPoint( + this.createCoord(0, 0), + 1, + 1, + ); const isContained = rect.contains(0.5, 0.5); assert.isTrue(isContained, 'Rect contains (0.5, 0.5)'); }); - test('centroid for non-square not at origin', function() { - const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 3, 4); + test('centroid for non-square not at origin', function () { + const rect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 2), + 3, + 4, + ); const isContained = rect.contains(2.5, 4); assert.isTrue(isContained, 'Rect contains (2.5, 4)'); }); - test('negative centroid for non-square not at origin', function() { - const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(-10, -20), 3, 5); + test('negative centroid for non-square not at origin', function () { + const rect = Blockly.utils.Rect.createFromPoint( + this.createCoord(-10, -20), + 3, + 5, + ); const isContained = rect.contains(-8.5, -17.5); assert.isTrue(isContained, 'Rect contains (-8.5, -17.5)'); }); - test('NW corner', function() { - const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 3, 4); + test('NW corner', function () { + const rect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 2), + 3, + 4, + ); const isContained = rect.contains(1, 2); assert.isTrue(isContained, 'Rect contains (1, 2)'); }); - test('NE corner', function() { - const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 3, 4); + test('NE corner', function () { + const rect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 2), + 3, + 4, + ); const isContained = rect.contains(4, 2); assert.isTrue(isContained, 'Rect contains (4, 2)'); }); - test('SW corner', function() { - const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 3, 4); + test('SW corner', function () { + const rect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 2), + 3, + 4, + ); const isContained = rect.contains(1, 6); assert.isTrue(isContained, 'Rect contains (1, 6)'); }); - test('SE corner', function() { - const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 3, 4); + test('SE corner', function () { + const rect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 2), + 3, + 4, + ); const isContained = rect.contains(4, 6); assert.isTrue(isContained, 'Rect contains (4, 6)'); }); - test('left edge midpoint', function() { - const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 3, 4); + test('left edge midpoint', function () { + const rect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 2), + 3, + 4, + ); const isContained = rect.contains(1, 4); assert.isTrue(isContained, 'Rect contains (1, 4)'); }); - test('right edge midpoint', function() { - const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 3, 4); + test('right edge midpoint', function () { + const rect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 2), + 3, + 4, + ); const isContained = rect.contains(4, 4); assert.isTrue(isContained, 'Rect contains (4, 4)'); }); - test('top edge midpoint', function() { - const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 3, 4); + test('top edge midpoint', function () { + const rect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 2), + 3, + 4, + ); const isContained = rect.contains(2.5, 2); assert.isTrue(isContained, 'Rect contains (2.5, 2)'); }); - test('bottom edge midpoint', function() { - const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 3, 4); + test('bottom edge midpoint', function () { + const rect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 2), + 3, + 4, + ); const isContained = rect.contains(2.5, 6); @@ -330,136 +523,204 @@ suite('Rect', function () { }); }); suite('point not contained within rect', function () { - test('non-origin for zero-sized square', function() { - const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(0, 0), 0, 0); + test('non-origin for zero-sized square', function () { + const rect = Blockly.utils.Rect.createFromPoint( + this.createCoord(0, 0), + 0, + 0, + ); const isContained = rect.contains(0.1, 0.1); assert.isFalse(isContained, 'Rect does not contain (0.1, 0.1)'); }); - test('point at midpoint x but above unit square', function() { - const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 1), 2, 2); + test('point at midpoint x but above unit square', function () { + const rect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 1), + 2, + 2, + ); const isContained = rect.contains(2, 0); assert.isFalse(isContained, 'Rect does not contain (2, 0)'); }); - test('point at midpoint x but below unit square', function() { - const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 1), 2, 2); + test('point at midpoint x but below unit square', function () { + const rect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 1), + 2, + 2, + ); const isContained = rect.contains(2, 4); assert.isFalse(isContained, 'Rect does not contain (2, 4)'); }); - test('point at midpoint y but left of unit square', function() { - const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 1), 2, 2); + test('point at midpoint y but left of unit square', function () { + const rect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 1), + 2, + 2, + ); const isContained = rect.contains(0, 2); assert.isFalse(isContained, 'Rect does not contain (0, 2)'); }); - test('point at midpoint y but right of unit square', function() { - const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 1), 2, 2); + test('point at midpoint y but right of unit square', function () { + const rect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 1), + 2, + 2, + ); const isContained = rect.contains(4, 2); assert.isFalse(isContained, 'Rect does not contain (4, 2)'); }); - test('positive point far outside positive rectangle', function() { - const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 3, 4); + test('positive point far outside positive rectangle', function () { + const rect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 2), + 3, + 4, + ); const isContained = rect.contains(45, 89); assert.isFalse(isContained, 'Rect does not contain (45, 89)'); }); - test('negative point far outside positive rectangle', function() { - const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 3, 4); + test('negative point far outside positive rectangle', function () { + const rect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 2), + 3, + 4, + ); const isContained = rect.contains(-45, -89); assert.isFalse(isContained, 'Rect does not contain (-45, -89)'); }); - test('positive point far outside negative rectangle', function() { - const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(-10, -20), 3, 5); + test('positive point far outside negative rectangle', function () { + const rect = Blockly.utils.Rect.createFromPoint( + this.createCoord(-10, -20), + 3, + 5, + ); const isContained = rect.contains(45, 89); assert.isFalse(isContained, 'Rect does not contain (45, 89)'); }); - test('negative point far outside negative rectangle', function() { - const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(-10, -20), 3, 5); + test('negative point far outside negative rectangle', function () { + const rect = Blockly.utils.Rect.createFromPoint( + this.createCoord(-10, -20), + 3, + 5, + ); const isContained = rect.contains(-45, -89); assert.isFalse(isContained, 'Rect does not contain (-45, -89)'); }); - test('Point just outside NW corner', function() { - const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 3, 4); + test('Point just outside NW corner', function () { + const rect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 2), + 3, + 4, + ); const isContained = rect.contains(0.9, 1.9); assert.isFalse(isContained, 'Rect does not contain (0.9, 1.9)'); }); - test('Point just outside NE corner', function() { - const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 3, 4); + test('Point just outside NE corner', function () { + const rect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 2), + 3, + 4, + ); const isContained = rect.contains(4.1, 1.9); assert.isFalse(isContained, 'Rect does not contain (4.1, 1.9)'); }); - test('Point just outside SW corner', function() { - const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 3, 4); + test('Point just outside SW corner', function () { + const rect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 2), + 3, + 4, + ); const isContained = rect.contains(0.9, 6.1); assert.isFalse(isContained, 'Rect does not contain (0.9, 6.1)'); }); - test('Point just outside SE corner', function() { - const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 3, 4); + test('Point just outside SE corner', function () { + const rect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 2), + 3, + 4, + ); const isContained = rect.contains(4.1, 6.1); assert.isFalse(isContained, 'Rect does not contain (4.1, 6.1)'); }); - test('Point just outside left edge midpoint', function() { - const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 3, 4); + test('Point just outside left edge midpoint', function () { + const rect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 2), + 3, + 4, + ); const isContained = rect.contains(0.9, 4); assert.isFalse(isContained, 'Rect does not contain (0.9, 4)'); }); - test('Point just outside right edge midpoint', function() { - const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 3, 4); + test('Point just outside right edge midpoint', function () { + const rect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 2), + 3, + 4, + ); const isContained = rect.contains(4.1, 4); assert.isFalse(isContained, 'Rect does not contain (4.1, 4)'); }); - test('Point just outside top edge midpoint', function() { - const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 3, 4); + test('Point just outside top edge midpoint', function () { + const rect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 2), + 3, + 4, + ); const isContained = rect.contains(2.5, 1.9); assert.isFalse(isContained, 'Rect does not contain (2.5, 1.9)'); }); - test('Point just outside bottom edge midpoint', function() { - const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 3, 4); + test('Point just outside bottom edge midpoint', function () { + const rect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 2), + 3, + 4, + ); const isContained = rect.contains(2.5, 6.1); @@ -474,494 +735,933 @@ suite('Rect', function () { // fit any text. suite('intersects()', function () { suite('does intersect', function () { - test('rect and itself', function() { - const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 23, 45); + test('rect and itself', function () { + const rect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 2), + 23, + 45, + ); const doIntersect = rect.intersects(rect); assert.isTrue(doIntersect, 'a rect always intersects with itself'); }); - test('rect and its clone', function() { - const rect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 23, 45); + test('rect and its clone', function () { + const rect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 2), + 23, + 45, + ); const doIntersect = rect.intersects(rect.clone()); assert.isTrue(doIntersect, 'a rect always intersects with its clone'); }); - test('two rects of the same positions and dimensions', function() { - const rect1 = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 23, 45); - const rect2 = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 23, 45); + test('two rects of the same positions and dimensions', function () { + const rect1 = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 2), + 23, + 45, + ); + const rect2 = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 2), + 23, + 45, + ); const doIntersect = rect1.intersects(rect2); - assert.isTrue(doIntersect, 'two rects with the same positions and dimensions intersect'); + assert.isTrue( + doIntersect, + 'two rects with the same positions and dimensions intersect', + ); }); - test('upper left/lower right', function() { + test('upper left/lower right', function () { // ┌───┐ // │2┌───┐ // └─│─┘2│ // └───┘ - const nwRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 1), 2, 2); - const seRect = Blockly.utils.Rect.createFromPoint(this.createCoord(2, 2), 2, 2); + const nwRect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 1), + 2, + 2, + ); + const seRect = Blockly.utils.Rect.createFromPoint( + this.createCoord(2, 2), + 2, + 2, + ); const doIntersectOneWay = nwRect.intersects(seRect); const doIntersectOtherWay = seRect.intersects(nwRect); - assert.isTrue(doIntersectOneWay, 'SE corner of NW rect intersects with SE rect'); - assert.isTrue(doIntersectOtherWay, 'NW corner of SE rect intersects with NW rect'); + assert.isTrue( + doIntersectOneWay, + 'SE corner of NW rect intersects with SE rect', + ); + assert.isTrue( + doIntersectOtherWay, + 'NW corner of SE rect intersects with NW rect', + ); }); - test('upper right/lower left', function() { + test('upper right/lower left', function () { // ┌───┐ // ┌───┐2│ // │2└─│─┘ // └───┘ - const neRect = Blockly.utils.Rect.createFromPoint(this.createCoord(2, 1), 2, 2); - const swRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 2, 2); + const neRect = Blockly.utils.Rect.createFromPoint( + this.createCoord(2, 1), + 2, + 2, + ); + const swRect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 2), + 2, + 2, + ); const doIntersectOneWay = neRect.intersects(swRect); const doIntersectOtherWay = swRect.intersects(neRect); - assert.isTrue(doIntersectOneWay, 'SW corner of NE rect intersects with SW rect'); - assert.isTrue(doIntersectOtherWay, 'NE corner of SW rect intersects with NE rect'); + assert.isTrue( + doIntersectOneWay, + 'SW corner of NE rect intersects with SW rect', + ); + assert.isTrue( + doIntersectOtherWay, + 'NE corner of SW rect intersects with NE rect', + ); }); - test('small rect overlapping left side of big rect', function() { + test('small rect overlapping left side of big rect', function () { // ┌────┐ // ┌───┐2 │ // └───┘ │ // └────┘ - const bigRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 1), 2, 2); - const smallRect = Blockly.utils.Rect.createFromPoint(this.createCoord(0.5, 1.5), 1, 1); + const bigRect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 1), + 2, + 2, + ); + const smallRect = Blockly.utils.Rect.createFromPoint( + this.createCoord(0.5, 1.5), + 1, + 1, + ); const doIntersectOneWay = bigRect.intersects(smallRect); const doIntersectOtherWay = smallRect.intersects(bigRect); - assert.isTrue(doIntersectOneWay, 'big rect intersects top/bottom sides of small rect'); - assert.isTrue(doIntersectOtherWay, 'small rect intersects left side of big rect'); + assert.isTrue( + doIntersectOneWay, + 'big rect intersects top/bottom sides of small rect', + ); + assert.isTrue( + doIntersectOtherWay, + 'small rect intersects left side of big rect', + ); }); - test('small rect overlapping right side of big rect', function() { + test('small rect overlapping right side of big rect', function () { // ┌────┐ // │ 2┌───┐ // │ └───┘ // └────┘ - const bigRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 1), 2, 2); - const smallRect = Blockly.utils.Rect.createFromPoint(this.createCoord(2.5, 1.5), 1, 1); + const bigRect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 1), + 2, + 2, + ); + const smallRect = Blockly.utils.Rect.createFromPoint( + this.createCoord(2.5, 1.5), + 1, + 1, + ); const doIntersectOneWay = bigRect.intersects(smallRect); const doIntersectOtherWay = smallRect.intersects(bigRect); - assert.isTrue(doIntersectOneWay, 'big rect intersects top/bottom sides of small rect'); - assert.isTrue(doIntersectOtherWay, 'small rect intersects right side of big rect'); + assert.isTrue( + doIntersectOneWay, + 'big rect intersects top/bottom sides of small rect', + ); + assert.isTrue( + doIntersectOtherWay, + 'small rect intersects right side of big rect', + ); }); - test('small rect overlapping top side of big rect', function() { + test('small rect overlapping top side of big rect', function () { // ┌─┐ // ┌│─│┐ // │└─┘│ // └───┘ - const bigRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 1), 2, 2); - const smallRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1.5, 0.5), 1, 1); + const bigRect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 1), + 2, + 2, + ); + const smallRect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1.5, 0.5), + 1, + 1, + ); const doIntersectOneWay = bigRect.intersects(smallRect); const doIntersectOtherWay = smallRect.intersects(bigRect); - assert.isTrue(doIntersectOneWay, 'big rect intersects left/right sides of small rect'); - assert.isTrue(doIntersectOtherWay, 'small rect intersects top side of big rect'); + assert.isTrue( + doIntersectOneWay, + 'big rect intersects left/right sides of small rect', + ); + assert.isTrue( + doIntersectOtherWay, + 'small rect intersects top side of big rect', + ); }); - test('small rect overlapping bottom side of big rect', function() { + test('small rect overlapping bottom side of big rect', function () { // ┌───┐ // │┌─┐│ // └│─│┘ // └─┘ - const bigRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 1), 2, 2); - const smallRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1.5, 2.5), 1, 1); + const bigRect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 1), + 2, + 2, + ); + const smallRect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1.5, 2.5), + 1, + 1, + ); const doIntersectOneWay = bigRect.intersects(smallRect); const doIntersectOtherWay = smallRect.intersects(bigRect); - assert.isTrue(doIntersectOneWay, 'big rect intersects left/right sides of small rect'); - assert.isTrue(doIntersectOtherWay, 'small rect intersects bottom side of big rect'); + assert.isTrue( + doIntersectOneWay, + 'big rect intersects left/right sides of small rect', + ); + assert.isTrue( + doIntersectOtherWay, + 'small rect intersects bottom side of big rect', + ); }); - test('edge only intersection with all corners outside each rect', function() { + test('edge only intersection with all corners outside each rect', function () { // ┌─┐ // │ │ // ┌─────┐ // └─────┘ // │ │ // └─┘ - const tallRect = Blockly.utils.Rect.createFromPoint(this.createCoord(2, 1), 1, 2); - const wideRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 2, 1); + const tallRect = Blockly.utils.Rect.createFromPoint( + this.createCoord(2, 1), + 1, + 2, + ); + const wideRect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 2), + 2, + 1, + ); const doIntersectOneWay = tallRect.intersects(wideRect); const doIntersectOtherWay = wideRect.intersects(tallRect); - assert.isTrue(doIntersectOneWay, 'tall rect intersects top/bottom of wide rect'); - assert.isTrue(doIntersectOtherWay, 'wide rect intersects left/right of tall rect'); + assert.isTrue( + doIntersectOneWay, + 'tall rect intersects top/bottom of wide rect', + ); + assert.isTrue( + doIntersectOtherWay, + 'wide rect intersects left/right of tall rect', + ); }); - test('small rect within larger rect', function() { + test('small rect within larger rect', function () { // ┌─────┐ // │ ┌─┐ │ // │ └─┘ │ // └─────┘ - const bigRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 1), 2, 2); - const smallRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1.5, 1.5), 1, 1); + const bigRect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 1), + 2, + 2, + ); + const smallRect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1.5, 1.5), + 1, + 1, + ); const doIntersectOneWay = bigRect.intersects(smallRect); const doIntersectOtherWay = smallRect.intersects(bigRect); - assert.isTrue(doIntersectOneWay, 'big rect intersects small rect since it is contained'); - assert.isTrue(doIntersectOtherWay, 'small rect intersects big rect since it is contained'); + assert.isTrue( + doIntersectOneWay, + 'big rect intersects small rect since it is contained', + ); + assert.isTrue( + doIntersectOtherWay, + 'small rect intersects big rect since it is contained', + ); }); - test('rects overlapping on left/right sides', function() { + test('rects overlapping on left/right sides', function () { // ┌──┌────┐ // │ 2│ │2 │ // └──└────┘ - const leftRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 1), 2, 2); - const rightRect = Blockly.utils.Rect.createFromPoint(this.createCoord(2, 1), 2, 2); + const leftRect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 1), + 2, + 2, + ); + const rightRect = Blockly.utils.Rect.createFromPoint( + this.createCoord(2, 1), + 2, + 2, + ); const doIntersectOneWay = leftRect.intersects(rightRect); const doIntersectOtherWay = rightRect.intersects(leftRect); - assert.isTrue(doIntersectOneWay, 'Left rect\'s right overlaps with right rect\'s left'); - assert.isTrue(doIntersectOtherWay, 'Right rect\'s left overlaps with left rect\'s right'); + assert.isTrue( + doIntersectOneWay, + "Left rect's right overlaps with right rect's left", + ); + assert.isTrue( + doIntersectOtherWay, + "Right rect's left overlaps with left rect's right", + ); }); - test('rects overlapping on top/bottom sides', function() { + test('rects overlapping on top/bottom sides', function () { // ┌───┐ // ┌───┐ // │───│ // └───┘ - const topRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 1), 2, 2); - const bottomRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2), 2, 2); + const topRect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 1), + 2, + 2, + ); + const bottomRect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 2), + 2, + 2, + ); const doIntersectOneWay = topRect.intersects(bottomRect); const doIntersectOtherWay = bottomRect.intersects(topRect); - assert.isTrue(doIntersectOneWay, 'Top rect\'s bottom overlaps with bottom rect\'s top'); - assert.isTrue(doIntersectOtherWay, 'Bottom rect\'s top overlaps with top rect\'s bottom'); + assert.isTrue( + doIntersectOneWay, + "Top rect's bottom overlaps with bottom rect's top", + ); + assert.isTrue( + doIntersectOtherWay, + "Bottom rect's top overlaps with top rect's bottom", + ); }); - test('rects adjacent on left/right sides', function() { + test('rects adjacent on left/right sides', function () { // ┌───┬───┐ // │ 2 │ 2 │ // └───┴───┘ - const leftRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 1), 2, 2); - const rightRect = Blockly.utils.Rect.createFromPoint(this.createCoord(3, 1), 2, 2); + const leftRect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 1), + 2, + 2, + ); + const rightRect = Blockly.utils.Rect.createFromPoint( + this.createCoord(3, 1), + 2, + 2, + ); const doIntersectOneWay = leftRect.intersects(rightRect); const doIntersectOtherWay = rightRect.intersects(leftRect); - assert.isTrue(doIntersectOneWay, 'Left rect\'s right intersects with right rect\'s left'); - assert.isTrue(doIntersectOtherWay, 'Right rect\'s left intersects with left rect\'s right'); + assert.isTrue( + doIntersectOneWay, + "Left rect's right intersects with right rect's left", + ); + assert.isTrue( + doIntersectOtherWay, + "Right rect's left intersects with left rect's right", + ); }); - test('rects adjacent on top/bottom sides', function() { + test('rects adjacent on top/bottom sides', function () { // ┌───┐ // │ 2 │ // ├───┤ // │ 2 │ // └───┘ - const topRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 1), 2, 2); - const bottomRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 3), 2, 2); + const topRect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 1), + 2, + 2, + ); + const bottomRect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 3), + 2, + 2, + ); const doIntersectOneWay = topRect.intersects(bottomRect); const doIntersectOtherWay = bottomRect.intersects(topRect); - assert.isTrue(doIntersectOneWay, 'Top rect\'s bottom intersects with bottom rect\'s top'); - assert.isTrue(doIntersectOtherWay, 'Bottom rect\'s top intersects with top rect\'s bottom'); + assert.isTrue( + doIntersectOneWay, + "Top rect's bottom intersects with bottom rect's top", + ); + assert.isTrue( + doIntersectOtherWay, + "Bottom rect's top intersects with top rect's bottom", + ); }); - test('small left rect adjacent to big right rect', function() { + test('small left rect adjacent to big right rect', function () { // ┌───┐ // ┌─┐ 2 │ // └─┘ │ // └───┘ - const bigRect = Blockly.utils.Rect.createFromPoint(this.createCoord(2, 1), 2, 2); - const smallRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 1.5), 1, 1); + const bigRect = Blockly.utils.Rect.createFromPoint( + this.createCoord(2, 1), + 2, + 2, + ); + const smallRect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 1.5), + 1, + 1, + ); const doIntersectOneWay = bigRect.intersects(smallRect); const doIntersectOtherWay = smallRect.intersects(bigRect); - assert.isTrue(doIntersectOneWay, 'big rect\'s left intersects small rect\'s right'); - assert.isTrue(doIntersectOtherWay, 'small rect\'s right intersects big rect\'s left'); + assert.isTrue( + doIntersectOneWay, + "big rect's left intersects small rect's right", + ); + assert.isTrue( + doIntersectOtherWay, + "small rect's right intersects big rect's left", + ); }); - test('small right rect adjacent to big left rect', function() { + test('small right rect adjacent to big left rect', function () { // ┌───┐ // │ 2 ┌─┐ // │ └─┘ // └───┘ - const bigRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 1), 2, 2); - const smallRect = Blockly.utils.Rect.createFromPoint(this.createCoord(3, 1.5), 1, 1); + const bigRect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 1), + 2, + 2, + ); + const smallRect = Blockly.utils.Rect.createFromPoint( + this.createCoord(3, 1.5), + 1, + 1, + ); const doIntersectOneWay = bigRect.intersects(smallRect); const doIntersectOtherWay = smallRect.intersects(bigRect); - assert.isTrue(doIntersectOneWay, 'big rect\'s right intersects small rect\'s left'); - assert.isTrue(doIntersectOtherWay, 'small rect\'s left intersects big rect\'s right'); + assert.isTrue( + doIntersectOneWay, + "big rect's right intersects small rect's left", + ); + assert.isTrue( + doIntersectOtherWay, + "small rect's left intersects big rect's right", + ); }); - test('small top rect adjacent to big bottom rect', function() { + test('small top rect adjacent to big bottom rect', function () { // ┌─┐ // ┌└─┘┐ // │ 2 │ // └───┘ - const bigRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 1), 2, 2); - const smallRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1.5, 0), 1, 1); + const bigRect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 1), + 2, + 2, + ); + const smallRect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1.5, 0), + 1, + 1, + ); const doIntersectOneWay = bigRect.intersects(smallRect); const doIntersectOtherWay = smallRect.intersects(bigRect); - assert.isTrue(doIntersectOneWay, 'big rect\'s top intersects small rect\'s bottom'); - assert.isTrue(doIntersectOtherWay, 'small rect\'s bottom intersects big rect\'s top'); + assert.isTrue( + doIntersectOneWay, + "big rect's top intersects small rect's bottom", + ); + assert.isTrue( + doIntersectOtherWay, + "small rect's bottom intersects big rect's top", + ); }); - test('small bottom rect adjacent to big top rect', function() { + test('small bottom rect adjacent to big top rect', function () { // ┌───┐ // │ 2 │ // └┌─┐┘ // └─┘ - const bigRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 1), 2, 2); - const smallRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1.5, 3), 1, 1); + const bigRect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 1), + 2, + 2, + ); + const smallRect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1.5, 3), + 1, + 1, + ); const doIntersectOneWay = bigRect.intersects(smallRect); const doIntersectOtherWay = smallRect.intersects(bigRect); - assert.isTrue(doIntersectOneWay, 'big rect\'s bottom intersects small rect\'s top'); - assert.isTrue(doIntersectOtherWay, 'small rect\'s top intersects big rect\'s bottom'); + assert.isTrue( + doIntersectOneWay, + "big rect's bottom intersects small rect's top", + ); + assert.isTrue( + doIntersectOtherWay, + "small rect's top intersects big rect's bottom", + ); }); - test('SW rect corner-adjacent to NE rect', function() { + test('SW rect corner-adjacent to NE rect', function () { // ┌───┐ // │ 2 │ // ┌───┐───┘ // │ 2 │ // └───┘ - const swRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 3), 2, 2); - const neRect = Blockly.utils.Rect.createFromPoint(this.createCoord(3, 1), 2, 2); + const swRect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 3), + 2, + 2, + ); + const neRect = Blockly.utils.Rect.createFromPoint( + this.createCoord(3, 1), + 2, + 2, + ); const doIntersectOneWay = swRect.intersects(neRect); const doIntersectOtherWay = neRect.intersects(swRect); - assert.isTrue(doIntersectOneWay, 'SW rect intersects with SW corner of NE rect'); - assert.isTrue(doIntersectOtherWay, 'NE rect intersects with NE corner of SW rect'); + assert.isTrue( + doIntersectOneWay, + 'SW rect intersects with SW corner of NE rect', + ); + assert.isTrue( + doIntersectOtherWay, + 'NE rect intersects with NE corner of SW rect', + ); }); - test('NW rect corner-adjacent to SE rect', function() { + test('NW rect corner-adjacent to SE rect', function () { // ┌───┐ // │ 2 │ // └───┘───┐ // │ 2 │ // └───┘ - const nwRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 1), 2, 2); - const seRect = Blockly.utils.Rect.createFromPoint(this.createCoord(3, 3), 2, 2); + const nwRect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 1), + 2, + 2, + ); + const seRect = Blockly.utils.Rect.createFromPoint( + this.createCoord(3, 3), + 2, + 2, + ); const doIntersectOneWay = seRect.intersects(nwRect); const doIntersectOtherWay = nwRect.intersects(seRect); - assert.isTrue(doIntersectOneWay, 'SE rect intersects with SE corner of NW rect'); - assert.isTrue(doIntersectOtherWay, 'NW rect intersects with NW corner of SE rect'); + assert.isTrue( + doIntersectOneWay, + 'SE rect intersects with SE corner of NW rect', + ); + assert.isTrue( + doIntersectOtherWay, + 'NW rect intersects with NW corner of SE rect', + ); }); }); suite('does not intersect', function () { - test('Same-size rects nearly side-adjacent', function() { + test('Same-size rects nearly side-adjacent', function () { // ┌───┐ ┌───┐ // │ 2 │ │ 2 │ // └───┘ └───┘ - const westRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 1), 2, 2); - const eastRect = Blockly.utils.Rect.createFromPoint(this.createCoord(3.5, 1), 2, 2); + const westRect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 1), + 2, + 2, + ); + const eastRect = Blockly.utils.Rect.createFromPoint( + this.createCoord(3.5, 1), + 2, + 2, + ); const doIntersectOneWay = westRect.intersects(eastRect); const doIntersectOtherWay = eastRect.intersects(westRect); - assert.isFalse(doIntersectOneWay, 'Western rect does not intersect with eastern rect'); - assert.isFalse(doIntersectOtherWay, 'Eastern rect does not intersect with western rect'); + assert.isFalse( + doIntersectOneWay, + 'Western rect does not intersect with eastern rect', + ); + assert.isFalse( + doIntersectOtherWay, + 'Eastern rect does not intersect with western rect', + ); }); - test('Same-size rects nearly side-adjacent', function() { + test('Same-size rects nearly side-adjacent', function () { // ┌───┐ // │ 2 │ // └───┘ // ┌───┐ // │ 2 │ // └───┘ - const northRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 1), 2, 2); - const southRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 3.5), 2, 2); + const northRect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 1), + 2, + 2, + ); + const southRect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 3.5), + 2, + 2, + ); const doIntersectOneWay = northRect.intersects(southRect); const doIntersectOtherWay = southRect.intersects(northRect); - assert.isFalse(doIntersectOneWay, 'Northern rect does not intersect with southern rect'); - assert.isFalse(doIntersectOtherWay, 'Southern rect does not intersect with northern rect'); + assert.isFalse( + doIntersectOneWay, + 'Northern rect does not intersect with southern rect', + ); + assert.isFalse( + doIntersectOtherWay, + 'Southern rect does not intersect with northern rect', + ); }); - test('Small rect left of big rect', function() { + test('Small rect left of big rect', function () { // ┌───┐ // ┌─┐│ 2 │ // └─┘│ │ // └───┘ - const westRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 1), 1, 1); - const eastRect = Blockly.utils.Rect.createFromPoint(this.createCoord(2.5, 1), 2, 2); + const westRect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 1), + 1, + 1, + ); + const eastRect = Blockly.utils.Rect.createFromPoint( + this.createCoord(2.5, 1), + 2, + 2, + ); const doIntersectOneWay = westRect.intersects(eastRect); const doIntersectOtherWay = eastRect.intersects(westRect); - assert.isFalse(doIntersectOneWay, 'Western rect does not intersect with eastern rect'); - assert.isFalse(doIntersectOtherWay, 'Eastern rect does not intersect with western rect'); + assert.isFalse( + doIntersectOneWay, + 'Western rect does not intersect with eastern rect', + ); + assert.isFalse( + doIntersectOtherWay, + 'Eastern rect does not intersect with western rect', + ); }); - test('Small rect right of big rect', function() { + test('Small rect right of big rect', function () { // ┌───┐ // │ 2 │┌─┐ // │ │└─┘ // └───┘ - const westRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 1), 2, 2); - const eastRect = Blockly.utils.Rect.createFromPoint(this.createCoord(3.5, 1), 1, 1); + const westRect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 1), + 2, + 2, + ); + const eastRect = Blockly.utils.Rect.createFromPoint( + this.createCoord(3.5, 1), + 1, + 1, + ); const doIntersectOneWay = westRect.intersects(eastRect); const doIntersectOtherWay = eastRect.intersects(westRect); - assert.isFalse(doIntersectOneWay, 'Western rect does not intersect with eastern rect'); - assert.isFalse(doIntersectOtherWay, 'Eastern rect does not intersect with western rect'); + assert.isFalse( + doIntersectOneWay, + 'Western rect does not intersect with eastern rect', + ); + assert.isFalse( + doIntersectOtherWay, + 'Eastern rect does not intersect with western rect', + ); }); - test('Small rect above big rect', function() { + test('Small rect above big rect', function () { // ┌─┐ // └─┘ // ┌───┐ // │ 2 │ // └───┘ - const northRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 1), 1, 1); - const southRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2.5), 2, 2); + const northRect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 1), + 1, + 1, + ); + const southRect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 2.5), + 2, + 2, + ); const doIntersectOneWay = northRect.intersects(southRect); const doIntersectOtherWay = southRect.intersects(northRect); - assert.isFalse(doIntersectOneWay, 'Northern rect does not intersect with southern rect'); - assert.isFalse(doIntersectOtherWay, 'Southern rect does not intersect with northern rect'); + assert.isFalse( + doIntersectOneWay, + 'Northern rect does not intersect with southern rect', + ); + assert.isFalse( + doIntersectOtherWay, + 'Southern rect does not intersect with northern rect', + ); }); - test('Small rect below big rect', function() { + test('Small rect below big rect', function () { // ┌───┐ // │ 2 │ // └───┘ // ┌─┐ // └─┘ - const northRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 1), 2, 2); - const southRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 3.5), 1, 1); + const northRect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 1), + 2, + 2, + ); + const southRect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 3.5), + 1, + 1, + ); const doIntersectOneWay = northRect.intersects(southRect); const doIntersectOtherWay = southRect.intersects(northRect); - assert.isFalse(doIntersectOneWay, 'Northern rect does not intersect with southern rect'); - assert.isFalse(doIntersectOtherWay, 'Southern rect does not intersect with northern rect'); + assert.isFalse( + doIntersectOneWay, + 'Northern rect does not intersect with southern rect', + ); + assert.isFalse( + doIntersectOtherWay, + 'Southern rect does not intersect with northern rect', + ); }); - test('Same-size rects diagonal (NE and SW) to each other', function() { + test('Same-size rects diagonal (NE and SW) to each other', function () { // ┌───┐ // │ 2 │ // └───┘ // ┌───┐ // │ 2 │ // └───┘ - const neRect = Blockly.utils.Rect.createFromPoint(this.createCoord(3.5, 1), 2, 2); - const swRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 3.5), 2, 2); + const neRect = Blockly.utils.Rect.createFromPoint( + this.createCoord(3.5, 1), + 2, + 2, + ); + const swRect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 3.5), + 2, + 2, + ); const doIntersectOneWay = neRect.intersects(swRect); const doIntersectOtherWay = swRect.intersects(neRect); - assert.isFalse(doIntersectOneWay, 'NE rect does not intersect with SW rect'); - assert.isFalse(doIntersectOtherWay, 'SW rect does not intersect with NE rect'); + assert.isFalse( + doIntersectOneWay, + 'NE rect does not intersect with SW rect', + ); + assert.isFalse( + doIntersectOtherWay, + 'SW rect does not intersect with NE rect', + ); }); - test('Same-size rects diagonal (NW and SE) to each other', function() { + test('Same-size rects diagonal (NW and SE) to each other', function () { // ┌───┐ // │ 2 │ // └───┘ // ┌───┐ // │ 2 │ // └───┘ - const nwRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 1), 2, 2); - const seRect = Blockly.utils.Rect.createFromPoint(this.createCoord(3.5, 3.5), 2, 2); + const nwRect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 1), + 2, + 2, + ); + const seRect = Blockly.utils.Rect.createFromPoint( + this.createCoord(3.5, 3.5), + 2, + 2, + ); const doIntersectOneWay = nwRect.intersects(seRect); const doIntersectOtherWay = seRect.intersects(nwRect); - assert.isFalse(doIntersectOneWay, 'NW rect does not intersect with SE rect'); - assert.isFalse(doIntersectOtherWay, 'SE rect does not intersect with NW rect'); + assert.isFalse( + doIntersectOneWay, + 'NW rect does not intersect with SE rect', + ); + assert.isFalse( + doIntersectOtherWay, + 'SE rect does not intersect with NW rect', + ); }); - test('Small rect NE of big rect', function() { + test('Small rect NE of big rect', function () { // ┌─┐ // └─┘ // ┌───┐ // │ 2 │ // └───┘ - const neRect = Blockly.utils.Rect.createFromPoint(this.createCoord(3.5, 1), 1, 1); - const swRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 2.5), 2, 2); + const neRect = Blockly.utils.Rect.createFromPoint( + this.createCoord(3.5, 1), + 1, + 1, + ); + const swRect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 2.5), + 2, + 2, + ); const doIntersectOneWay = neRect.intersects(swRect); const doIntersectOtherWay = swRect.intersects(neRect); - assert.isFalse(doIntersectOneWay, 'NE rect does not intersect with SW rect'); - assert.isFalse(doIntersectOtherWay, 'SW rect does not intersect with NE rect'); + assert.isFalse( + doIntersectOneWay, + 'NE rect does not intersect with SW rect', + ); + assert.isFalse( + doIntersectOtherWay, + 'SW rect does not intersect with NE rect', + ); }); - test('Small rect NW of big rect', function() { + test('Small rect NW of big rect', function () { // ┌─┐ // └─┘ // ┌───┐ // │ 2 │ // └───┘ - const nwRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 1), 1, 1); - const seRect = Blockly.utils.Rect.createFromPoint(this.createCoord(2.5, 2.5), 2, 2); + const nwRect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 1), + 1, + 1, + ); + const seRect = Blockly.utils.Rect.createFromPoint( + this.createCoord(2.5, 2.5), + 2, + 2, + ); const doIntersectOneWay = nwRect.intersects(seRect); const doIntersectOtherWay = seRect.intersects(nwRect); - assert.isFalse(doIntersectOneWay, 'NW rect does not intersect with SE rect'); - assert.isFalse(doIntersectOtherWay, 'SE rect does not intersect with NW rect'); + assert.isFalse( + doIntersectOneWay, + 'NW rect does not intersect with SE rect', + ); + assert.isFalse( + doIntersectOtherWay, + 'SE rect does not intersect with NW rect', + ); }); - test('Small rect SW of big rect', function() { + test('Small rect SW of big rect', function () { // ┌───┐ // │ 2 │ // └───┘ // ┌─┐ // └─┘ - const neRect = Blockly.utils.Rect.createFromPoint(this.createCoord(2.5, 1), 2, 2); - const swRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 3.5), 1, 1); + const neRect = Blockly.utils.Rect.createFromPoint( + this.createCoord(2.5, 1), + 2, + 2, + ); + const swRect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 3.5), + 1, + 1, + ); const doIntersectOneWay = neRect.intersects(swRect); const doIntersectOtherWay = swRect.intersects(neRect); - assert.isFalse(doIntersectOneWay, 'NE rect does not intersect with SW rect'); - assert.isFalse(doIntersectOtherWay, 'SW rect does not intersect with NE rect'); + assert.isFalse( + doIntersectOneWay, + 'NE rect does not intersect with SW rect', + ); + assert.isFalse( + doIntersectOtherWay, + 'SW rect does not intersect with NE rect', + ); }); - test('Small rect SE of big rect', function() { + test('Small rect SE of big rect', function () { // ┌───┐ // │ 2 │ // └───┘ // ┌─┐ // └─┘ - const nwRect = Blockly.utils.Rect.createFromPoint(this.createCoord(1, 1), 2, 2); - const seRect = Blockly.utils.Rect.createFromPoint(this.createCoord(3.5, 3.5), 1, 1); + const nwRect = Blockly.utils.Rect.createFromPoint( + this.createCoord(1, 1), + 2, + 2, + ); + const seRect = Blockly.utils.Rect.createFromPoint( + this.createCoord(3.5, 3.5), + 1, + 1, + ); const doIntersectOneWay = nwRect.intersects(seRect); const doIntersectOtherWay = seRect.intersects(nwRect); - assert.isFalse(doIntersectOneWay, 'NW rect does not intersect with SE rect'); - assert.isFalse(doIntersectOtherWay, 'SE rect does not intersect with NW rect'); + assert.isFalse( + doIntersectOneWay, + 'NW rect does not intersect with SE rect', + ); + assert.isFalse( + doIntersectOtherWay, + 'SE rect does not intersect with NW rect', + ); }); }); }); diff --git a/tests/mocha/workspace_svg_test.js b/tests/mocha/workspace_svg_test.js index 9218ad22b96..d90c2110624 100644 --- a/tests/mocha/workspace_svg_test.js +++ b/tests/mocha/workspace_svg_test.js @@ -408,21 +408,21 @@ suite('WorkspaceSvg', function () { }); suite('tidyUp', function () { - test('empty workspace does not change', function() { + test('empty workspace does not change', function () { this.workspace.tidyUp(); const blocks = this.workspace.getTopBlocks(true); assert.equal(blocks.length, 0, 'workspace is empty'); }); - test('single block at (0, 0) does not change', function() { + test('single block at (0, 0) does not change', function () { const blockJson = { - "type": "math_number", - "x": 0, - "y": 0, - "fields": { - "NUM": 123 - } + 'type': 'math_number', + 'x': 0, + 'y': 0, + 'fields': { + 'NUM': 123, + }, }; Blockly.serialization.blocks.append(blockJson, this.workspace); @@ -431,17 +431,21 @@ suite('WorkspaceSvg', function () { const blocks = this.workspace.getTopBlocks(true); const origin = new Blockly.utils.Coordinate(0, 0); assert.equal(blocks.length, 1, 'workspace has one top-level block'); - assert.deepEqual(blocks[0].getRelativeToSurfaceXY(), origin, 'block is at origin'); + assert.deepEqual( + blocks[0].getRelativeToSurfaceXY(), + origin, + 'block is at origin', + ); }); - test('single block at (10, 15) is moved to (0, 0)', function() { + test('single block at (10, 15) is moved to (0, 0)', function () { const blockJson = { - "type": "math_number", - "x": 10, - "y": 15, - "fields": { - "NUM": 123 - } + 'type': 'math_number', + 'x': 10, + 'y': 15, + 'fields': { + 'NUM': 123, + }, }; Blockly.serialization.blocks.append(blockJson, this.workspace); @@ -452,26 +456,30 @@ suite('WorkspaceSvg', function () { const origin = new Blockly.utils.Coordinate(0, 0); assert.equal(topBlocks.length, 1, 'workspace has one top-level block'); assert.equal(allBlocks.length, 1, 'workspace has one block overall'); - assert.deepEqual(topBlocks[0].getRelativeToSurfaceXY(), origin, 'block is at origin'); + assert.deepEqual( + topBlocks[0].getRelativeToSurfaceXY(), + origin, + 'block is at origin', + ); }); - test('single block at (10, 15) with child is moved as unit to (0, 0)', function() { + test('single block at (10, 15) with child is moved as unit to (0, 0)', function () { const blockJson = { - "type": "logic_negate", - "id": "parent", - "x": 10, - "y": 15, - "inputs": { - "BOOL": { - "block": { - "type": "logic_boolean", - "id": "child", - "fields": { - "BOOL": "TRUE" - } - } - } - } + 'type': 'logic_negate', + 'id': 'parent', + 'x': 10, + 'y': 15, + 'inputs': { + 'BOOL': { + 'block': { + 'type': 'logic_boolean', + 'id': 'child', + 'fields': { + 'BOOL': 'TRUE', + }, + }, + }, + }, }; Blockly.serialization.blocks.append(blockJson, this.workspace); @@ -482,21 +490,29 @@ suite('WorkspaceSvg', function () { const origin = new Blockly.utils.Coordinate(0, 0); assert.equal(topBlocks.length, 1, 'workspace has one top-level block'); assert.equal(allBlocks.length, 2, 'workspace has two blocks overall'); - assert.deepEqual(topBlocks[0].getRelativeToSurfaceXY(), origin, 'block is at origin'); - assert.notDeepEqual(allBlocks[1].getRelativeToSurfaceXY(), origin, 'child is not at origin'); + assert.deepEqual( + topBlocks[0].getRelativeToSurfaceXY(), + origin, + 'block is at origin', + ); + assert.notDeepEqual( + allBlocks[1].getRelativeToSurfaceXY(), + origin, + 'child is not at origin', + ); }); - test('two blocks first at (10, 15) second at (0, 0) do not switch places', function() { + test('two blocks first at (10, 15) second at (0, 0) do not switch places', function () { const blockJson1 = { - "type": "math_number", - "id": "block1", - "x": 10, - "y": 15, - "fields": { - "NUM": 123 - } + 'type': 'math_number', + 'id': 'block1', + 'x': 10, + 'y': 15, + 'fields': { + 'NUM': 123, + }, }; - const blockJson2 = {...blockJson1, "id": "block2", "x": 0, "y": 0}; + const blockJson2 = {...blockJson1, 'id': 'block2', 'x': 0, 'y': 0}; Blockly.serialization.blocks.append(blockJson1, this.workspace); Blockly.serialization.blocks.append(blockJson2, this.workspace); @@ -510,21 +526,34 @@ suite('WorkspaceSvg', function () { const origin = new Blockly.utils.Coordinate(0, 0); const belowBlock2 = new Blockly.utils.Coordinate(0, 50); assert.equal(topBlocks.length, 2, 'workspace has two top-level blocks'); - assert.deepEqual(block2.getRelativeToSurfaceXY(), origin, 'block2 is at origin'); - assert.deepEqual(block1.getRelativeToSurfaceXY(), belowBlock2, 'block1 is below block2'); + assert.deepEqual( + block2.getRelativeToSurfaceXY(), + origin, + 'block2 is at origin', + ); + assert.deepEqual( + block1.getRelativeToSurfaceXY(), + belowBlock2, + 'block1 is below block2', + ); }); - test('two overlapping blocks are moved to origin and below', function() { + test('two overlapping blocks are moved to origin and below', function () { const blockJson1 = { - "type": "math_number", - "id": "block1", - "x": 25, - "y": 15, - "fields": { - "NUM": 123 - } + 'type': 'math_number', + 'id': 'block1', + 'x': 25, + 'y': 15, + 'fields': { + 'NUM': 123, + }, + }; + const blockJson2 = { + ...blockJson1, + 'id': 'block2', + 'x': 15.25, + 'y': 20.25, }; - const blockJson2 = {...blockJson1, "id": "block2", "x": 15.25, "y": 20.25}; Blockly.serialization.blocks.append(blockJson1, this.workspace); Blockly.serialization.blocks.append(blockJson2, this.workspace); @@ -536,21 +565,34 @@ suite('WorkspaceSvg', function () { const origin = new Blockly.utils.Coordinate(0, 0); const belowBlock1 = new Blockly.utils.Coordinate(0, 50); assert.equal(topBlocks.length, 2, 'workspace has two top-level blocks'); - assert.deepEqual(block1.getRelativeToSurfaceXY(), origin, 'block1 is at origin'); - assert.deepEqual(block2.getRelativeToSurfaceXY(), belowBlock1, 'block2 is below block1'); + assert.deepEqual( + block1.getRelativeToSurfaceXY(), + origin, + 'block1 is at origin', + ); + assert.deepEqual( + block2.getRelativeToSurfaceXY(), + belowBlock1, + 'block2 is below block1', + ); }); - test('two overlapping blocks with snapping are moved to grid-aligned positions', function() { + test('two overlapping blocks with snapping are moved to grid-aligned positions', function () { const blockJson1 = { - "type": "math_number", - "id": "block1", - "x": 25, - "y": 15, - "fields": { - "NUM": 123 - } + 'type': 'math_number', + 'id': 'block1', + 'x': 25, + 'y': 15, + 'fields': { + 'NUM': 123, + }, + }; + const blockJson2 = { + ...blockJson1, + 'id': 'block2', + 'x': 15.25, + 'y': 20.25, }; - const blockJson2 = {...blockJson1, "id": "block2", "x": 15.25, "y": 20.25}; Blockly.serialization.blocks.append(blockJson1, this.workspace); Blockly.serialization.blocks.append(blockJson2, this.workspace); this.workspace.getGrid().setSpacing(20); @@ -564,28 +606,41 @@ suite('WorkspaceSvg', function () { const snappedOffOrigin = new Blockly.utils.Coordinate(10, 10); const belowBlock1 = new Blockly.utils.Coordinate(10, 70); assert.equal(topBlocks.length, 2, 'workspace has two top-level blocks'); - assert.deepEqual(block1.getRelativeToSurfaceXY(), snappedOffOrigin, 'block1 is near origin'); - assert.deepEqual(block2.getRelativeToSurfaceXY(), belowBlock1, 'block2 is below block1'); + assert.deepEqual( + block1.getRelativeToSurfaceXY(), + snappedOffOrigin, + 'block1 is near origin', + ); + assert.deepEqual( + block2.getRelativeToSurfaceXY(), + belowBlock1, + 'block2 is below block1', + ); }); - test('two overlapping blocks are moved to origin and below including children', function() { + test('two overlapping blocks are moved to origin and below including children', function () { const blockJson1 = { - "type": "logic_negate", - "id": "block1", - "x": 10, - "y": 15, - "inputs": { - "BOOL": { - "block": { - "type": "logic_boolean", - "fields": { - "BOOL": "TRUE" - } - } - } - } + 'type': 'logic_negate', + 'id': 'block1', + 'x': 10, + 'y': 15, + 'inputs': { + 'BOOL': { + 'block': { + 'type': 'logic_boolean', + 'fields': { + 'BOOL': 'TRUE', + }, + }, + }, + }, + }; + const blockJson2 = { + ...blockJson1, + 'id': 'block2', + 'x': 15.25, + 'y': 20.25, }; - const blockJson2 = {...blockJson1, "id": "block2", "x": 15.25, "y": 20.25}; Blockly.serialization.blocks.append(blockJson1, this.workspace); Blockly.serialization.blocks.append(blockJson2, this.workspace); @@ -605,60 +660,76 @@ suite('WorkspaceSvg', function () { assert.equal(allBlocks.length, 4, 'workspace has four blocks overall'); assert.deepEqual(block1Pos, origin, 'block1 is at origin'); assert.deepEqual(block2Pos, belowBlock1, 'block2 is below block1'); - assert.isAbove(block1ChildPos.x, block1Pos.x, 'block1\'s child is right of it'); - assert.isBelow(block1ChildPos.y, block2Pos.y, 'block1\'s child is above block 2'); - assert.isAbove(block2ChildPos.x, block2Pos.x, 'block2\'s child is right of it'); - assert.isAbove(block2ChildPos.y, block1Pos.y, 'block2\'s child is below block 1'); + assert.isAbove( + block1ChildPos.x, + block1Pos.x, + "block1's child is right of it", + ); + assert.isBelow( + block1ChildPos.y, + block2Pos.y, + "block1's child is above block 2", + ); + assert.isAbove( + block2ChildPos.x, + block2Pos.x, + "block2's child is right of it", + ); + assert.isAbove( + block2ChildPos.y, + block1Pos.y, + "block2's child is below block 1", + ); }); - test('two large overlapping blocks are moved to origin and below', function() { + test('two large overlapping blocks are moved to origin and below', function () { const blockJson1 = { - "type": "controls_repeat_ext", - "id": "block1", - "x": 10, - "y": 20, - "inputs": { - "TIMES": { - "shadow": { - "type": "math_number", - "fields": { - "NUM": 10 - } - } + 'type': 'controls_repeat_ext', + 'id': 'block1', + 'x': 10, + 'y': 20, + 'inputs': { + 'TIMES': { + 'shadow': { + 'type': 'math_number', + 'fields': { + 'NUM': 10, + }, + }, }, - "DO": { - "block": { - "type": "controls_if", - "inputs": { - "IF0": { - "block": { - "type": "logic_boolean", - "fields": { - "BOOL": "TRUE" - } - } + 'DO': { + 'block': { + 'type': 'controls_if', + 'inputs': { + 'IF0': { + 'block': { + 'type': 'logic_boolean', + 'fields': { + 'BOOL': 'TRUE', + }, + }, + }, + 'DO0': { + 'block': { + 'type': 'text_print', + 'inputs': { + 'TEXT': { + 'shadow': { + 'type': 'text', + 'fields': { + 'TEXT': 'abc', + }, + }, + }, + }, + }, }, - "DO0": { - "block": { - "type": "text_print", - "inputs": { - "TEXT": { - "shadow": { - "type": "text", - "fields": { - "TEXT": "abc" - } - } - } - } - } - } - } - } - } - } + }, + }, + }, + }, }; - const blockJson2 = {...blockJson1, "id": "block2", "x": 20, "y": 30}; + const blockJson2 = {...blockJson1, 'id': 'block2', 'x': 20, 'y': 30}; Blockly.serialization.blocks.append(blockJson1, this.workspace); Blockly.serialization.blocks.append(blockJson2, this.workspace); @@ -670,24 +741,32 @@ suite('WorkspaceSvg', function () { const origin = new Blockly.utils.Coordinate(0, 0); const belowBlock1 = new Blockly.utils.Coordinate(0, 144); assert.equal(topBlocks.length, 2, 'workspace has two top-level blocks'); - assert.deepEqual(block1.getRelativeToSurfaceXY(), origin, 'block1 is at origin'); - assert.deepEqual(block2.getRelativeToSurfaceXY(), belowBlock1, 'block2 is below block1'); + assert.deepEqual( + block1.getRelativeToSurfaceXY(), + origin, + 'block1 is at origin', + ); + assert.deepEqual( + block2.getRelativeToSurfaceXY(), + belowBlock1, + 'block2 is below block1', + ); }); - test('five overlapping blocks are moved in-order as one column', function() { + test('five overlapping blocks are moved in-order as one column', function () { const blockJson1 = { - "type": "math_number", - "id": "block1", - "x": 1, - "y": 2, - "fields": { - "NUM": 123 - } + 'type': 'math_number', + 'id': 'block1', + 'x': 1, + 'y': 2, + 'fields': { + 'NUM': 123, + }, }; - const blockJson2 = {...blockJson1, "id": "block2", "x": 3, "y": 4}; - const blockJson3 = {...blockJson1, "id": "block3", "x": 5, "y": 6}; - const blockJson4 = {...blockJson1, "id": "block4", "x": 7, "y": 8}; - const blockJson5 = {...blockJson1, "id": "block5", "x": 9, "y": 10}; + const blockJson2 = {...blockJson1, 'id': 'block2', 'x': 3, 'y': 4}; + const blockJson3 = {...blockJson1, 'id': 'block3', 'x': 5, 'y': 6}; + const blockJson4 = {...blockJson1, 'id': 'block4', 'x': 7, 'y': 8}; + const blockJson5 = {...blockJson1, 'id': 'block5', 'x': 9, 'y': 10}; Blockly.serialization.blocks.append(blockJson1, this.workspace); Blockly.serialization.blocks.append(blockJson2, this.workspace); Blockly.serialization.blocks.append(blockJson3, this.workspace); @@ -697,11 +776,21 @@ suite('WorkspaceSvg', function () { this.workspace.tidyUp(); const topBlocks = this.workspace.getTopBlocks(true); - const block1Pos = this.workspace.getBlockById('block1').getRelativeToSurfaceXY(); - const block2Pos = this.workspace.getBlockById('block2').getRelativeToSurfaceXY(); - const block3Pos = this.workspace.getBlockById('block3').getRelativeToSurfaceXY(); - const block4Pos = this.workspace.getBlockById('block4').getRelativeToSurfaceXY(); - const block5Pos = this.workspace.getBlockById('block5').getRelativeToSurfaceXY(); + const block1Pos = this.workspace + .getBlockById('block1') + .getRelativeToSurfaceXY(); + const block2Pos = this.workspace + .getBlockById('block2') + .getRelativeToSurfaceXY(); + const block3Pos = this.workspace + .getBlockById('block3') + .getRelativeToSurfaceXY(); + const block4Pos = this.workspace + .getBlockById('block4') + .getRelativeToSurfaceXY(); + const block5Pos = this.workspace + .getBlockById('block5') + .getRelativeToSurfaceXY(); const origin = new Blockly.utils.Coordinate(0, 0); assert.equal(topBlocks.length, 5, 'workspace has five top-level blocks'); assert.deepEqual(block1Pos, origin, 'block1 is at origin'); @@ -715,15 +804,15 @@ suite('WorkspaceSvg', function () { assert.isAbove(block5Pos.y, block4Pos.y, 'block5 is below block4'); }); - test('single immovable block at (10, 15) is not moved', function() { + test('single immovable block at (10, 15) is not moved', function () { const blockJson = { - "type": "math_number", - "x": 10, - "y": 15, - "movable": false, - "fields": { - "NUM": 123 - } + 'type': 'math_number', + 'x': 10, + 'y': 15, + 'movable': false, + 'fields': { + 'NUM': 123, + }, }; Blockly.serialization.blocks.append(blockJson, this.workspace); @@ -734,68 +823,84 @@ suite('WorkspaceSvg', function () { const origPos = new Blockly.utils.Coordinate(10, 15); assert.equal(topBlocks.length, 1, 'workspace has one top-level block'); assert.equal(allBlocks.length, 1, 'workspace has one block overall'); - assert.deepEqual(topBlocks[0].getRelativeToSurfaceXY(), origPos, 'block is at (10, 15)'); + assert.deepEqual( + topBlocks[0].getRelativeToSurfaceXY(), + origPos, + 'block is at (10, 15)', + ); }); - test('multiple block types immovable blocks are not moved', function() { + test('multiple block types immovable blocks are not moved', function () { const smallBlockJson = { - "type": "math_number", - "fields": { - "NUM": 123 - } + 'type': 'math_number', + 'fields': { + 'NUM': 123, + }, }; const largeBlockJson = { - "type": "controls_repeat_ext", - "inputs": { - "TIMES": { - "shadow": { - "type": "math_number", - "fields": { - "NUM": 10 - } - } + 'type': 'controls_repeat_ext', + 'inputs': { + 'TIMES': { + 'shadow': { + 'type': 'math_number', + 'fields': { + 'NUM': 10, + }, + }, }, - "DO": { - "block": { - "type": "controls_if", - "inputs": { - "IF0": { - "block": { - "type": "logic_boolean", - "fields": { - "BOOL": "TRUE" - } - } + 'DO': { + 'block': { + 'type': 'controls_if', + 'inputs': { + 'IF0': { + 'block': { + 'type': 'logic_boolean', + 'fields': { + 'BOOL': 'TRUE', + }, + }, }, - "DO0": { - "block": { - "type": "text_print", - "inputs": { - "TEXT": { - "shadow": { - "type": "text", - "fields": { - "TEXT": "abc" - } - } - } - } - } - } - } - } - } - } + 'DO0': { + 'block': { + 'type': 'text_print', + 'inputs': { + 'TEXT': { + 'shadow': { + 'type': 'text', + 'fields': { + 'TEXT': 'abc', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, }; // Block 1 overlaps block 2 (immovable) from above. - const blockJson1 = {...smallBlockJson, "id": "block1", "x": 1, "y": 2}; - const blockJson2 = {...smallBlockJson, "id": "block2", "x": 10, "y": 20, "movable": false}; + const blockJson1 = {...smallBlockJson, 'id': 'block1', 'x': 1, 'y': 2}; + const blockJson2 = { + ...smallBlockJson, + 'id': 'block2', + 'x': 10, + 'y': 20, + 'movable': false, + }; // Block 3 overlaps block 2 (immovable) from below. - const blockJson3 = {...smallBlockJson, "id": "block3", "x": 2, "y": 30}; - const blockJson4 = {...largeBlockJson, "id": "block4", "x": 3, "y": 40}; + const blockJson3 = {...smallBlockJson, 'id': 'block3', 'x': 2, 'y': 30}; + const blockJson4 = {...largeBlockJson, 'id': 'block4', 'x': 3, 'y': 40}; // Block 5 (immovable) will end up overlapping with block 4 since it's large and will be // moved. - const blockJson5 = {...smallBlockJson, "id": "block5", "x": 20, "y": 200, "movable": false}; + const blockJson5 = { + ...smallBlockJson, + 'id': 'block5', + 'x': 20, + 'y': 200, + 'movable': false, + }; Blockly.serialization.blocks.append(blockJson1, this.workspace); Blockly.serialization.blocks.append(blockJson2, this.workspace); Blockly.serialization.blocks.append(blockJson3, this.workspace); @@ -805,11 +910,21 @@ suite('WorkspaceSvg', function () { this.workspace.tidyUp(); const topBlocks = this.workspace.getTopBlocks(true); - const block1Rect = this.workspace.getBlockById('block1').getBoundingRectangle(); - const block2Rect = this.workspace.getBlockById('block2').getBoundingRectangle(); - const block3Rect = this.workspace.getBlockById('block3').getBoundingRectangle(); - const block4Rect = this.workspace.getBlockById('block4').getBoundingRectangle(); - const block5Rect = this.workspace.getBlockById('block5').getBoundingRectangle(); + const block1Rect = this.workspace + .getBlockById('block1') + .getBoundingRectangle(); + const block2Rect = this.workspace + .getBlockById('block2') + .getBoundingRectangle(); + const block3Rect = this.workspace + .getBlockById('block3') + .getBoundingRectangle(); + const block4Rect = this.workspace + .getBlockById('block4') + .getBoundingRectangle(); + const block5Rect = this.workspace + .getBlockById('block5') + .getBoundingRectangle(); assert.equal(topBlocks.length, 5, 'workspace has five top-level blocks'); // Check that immovable blocks haven't moved. assert.equal(block2Rect.left, 10, 'block2.x is at 10'); @@ -826,10 +941,22 @@ suite('WorkspaceSvg', function () { assert.isAbove(block5Rect.top, block3Rect.top, 'block5 is below block3'); assert.isAbove(block4Rect.top, block5Rect.top, 'block4 is below block5'); // Ensure no blocks intersect (can check in order due to the position verification above). - assert.isFalse(block2Rect.intersects(block1Rect), "block2/block1 do not intersect"); - assert.isFalse(block1Rect.intersects(block3Rect), "block1/block3 do not intersect"); - assert.isFalse(block3Rect.intersects(block5Rect), "block3/block5 do not intersect"); - assert.isFalse(block5Rect.intersects(block4Rect), "block5/block4 do not intersect"); + assert.isFalse( + block2Rect.intersects(block1Rect), + 'block2/block1 do not intersect', + ); + assert.isFalse( + block1Rect.intersects(block3Rect), + 'block1/block3 do not intersect', + ); + assert.isFalse( + block3Rect.intersects(block5Rect), + 'block3/block5 do not intersect', + ); + assert.isFalse( + block5Rect.intersects(block4Rect), + 'block5/block4 do not intersect', + ); }); }); From 348a5b33b8692d15b16220f06f6c85eb569b3b28 Mon Sep 17 00:00:00 2001 From: Ben Henning Date: Thu, 22 Aug 2024 18:00:22 +0000 Subject: [PATCH 03/32] Addressed self-review comment. --- tests/mocha/rect_test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/mocha/rect_test.js b/tests/mocha/rect_test.js index 796e7ec8749..37712dff3a0 100644 --- a/tests/mocha/rect_test.js +++ b/tests/mocha/rect_test.js @@ -119,7 +119,7 @@ suite('Rect', function () { assert.isTrue(areEqual, 'an instance and its clone should be equal'); }); - test('object should equal its clone', function () { + test('object should equal an exact explicit copy', function () { const rect1 = Blockly.utils.Rect.createFromPoint( this.createCoord(1, 2), 23, From 05795a06ea56ba5aa816c3025a967e36094e69c7 Mon Sep 17 00:00:00 2001 From: Ben Henning Date: Tue, 3 Sep 2024 23:13:50 +0000 Subject: [PATCH 04/32] Rename 'tidyUp' back to 'cleanUp'. --- core/contextmenu_items.ts | 2 +- core/workspace_svg.ts | 4 +-- .../workspacefactory/wfactory_controller.js | 6 ++--- tests/mocha/contextmenu_items_test.js | 6 ++--- tests/mocha/workspace_svg_test.js | 26 +++++++++---------- 5 files changed, 22 insertions(+), 22 deletions(-) diff --git a/core/contextmenu_items.ts b/core/contextmenu_items.ts index 04b94eed745..25ffab59b8b 100644 --- a/core/contextmenu_items.ts +++ b/core/contextmenu_items.ts @@ -91,7 +91,7 @@ export function registerCleanup() { return 'hidden'; }, callback(scope: Scope) { - scope.workspace!.tidyUp(); + scope.workspace!.cleanUp(); }, scopeType: ContextMenuRegistry.ScopeType.WORKSPACE, id: 'cleanWorkspace', diff --git a/core/workspace_svg.ts b/core/workspace_svg.ts index d118c858406..b8ef96292bc 100644 --- a/core/workspace_svg.ts +++ b/core/workspace_svg.ts @@ -1645,8 +1645,8 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { return boundary; } - /** Tidy up the workspace by ordering all the blocks in a column such that none overlap. */ - tidyUp() { + /** Clean up the workspace by ordering all the blocks in a column such that none overlap. */ + cleanUp() { this.setResizesEnabled(false); eventUtils.setGroup(true); diff --git a/demos/blockfactory/workspacefactory/wfactory_controller.js b/demos/blockfactory/workspacefactory/wfactory_controller.js index 7e2f95c81f7..385feede8ec 100644 --- a/demos/blockfactory/workspacefactory/wfactory_controller.js +++ b/demos/blockfactory/workspacefactory/wfactory_controller.js @@ -278,7 +278,7 @@ WorkspaceFactoryController.prototype.clearAndLoadElement = function(id) { this.view.setCategoryTabSelection(id, true); // Order blocks as shown in flyout. - this.toolboxWorkspace.tidyUp(); + this.toolboxWorkspace.cleanUp(); // Update category editing buttons. this.view.updateState(this.model.getIndexByElementId @@ -774,7 +774,7 @@ WorkspaceFactoryController.prototype.importToolboxFromTree_ = function(tree) { // No categories present. // Load all the blocks into a single category evenly spaced. Blockly.Xml.domToWorkspace(tree, this.toolboxWorkspace); - this.toolboxWorkspace.tidyUp(); + this.toolboxWorkspace.cleanUp(); // Convert actual shadow blocks to user-generated shadow blocks. this.convertShadowBlocks(); @@ -799,7 +799,7 @@ WorkspaceFactoryController.prototype.importToolboxFromTree_ = function(tree) { } // Evenly space the blocks. - this.toolboxWorkspace.tidyUp(); + this.toolboxWorkspace.cleanUp(); // Convert actual shadow blocks to user-generated shadow blocks. this.convertShadowBlocks(); diff --git a/tests/mocha/contextmenu_items_test.js b/tests/mocha/contextmenu_items_test.js index ff6f4fe91d1..a9e2bb3de62 100644 --- a/tests/mocha/contextmenu_items_test.js +++ b/tests/mocha/contextmenu_items_test.js @@ -123,7 +123,7 @@ suite('Context Menu Items', function () { suite('Cleanup', function () { setup(function () { this.cleanupOption = this.registry.getItem('cleanWorkspace'); - this.tidyUpStub = sinon.stub(this.workspace, 'tidyUp'); + this.cleanUpStub = sinon.stub(this.workspace, 'cleanUp'); }); test('Enabled when multiple blocks', function () { @@ -153,9 +153,9 @@ suite('Context Menu Items', function () { ); }); - test('Calls workspace tidyUp', function () { + test('Calls workspace cleanUp', function () { this.cleanupOption.callback(this.scope); - sinon.assert.calledOnce(this.tidyUpStub); + sinon.assert.calledOnce(this.cleanUpStub); }); test('Has correct label', function () { diff --git a/tests/mocha/workspace_svg_test.js b/tests/mocha/workspace_svg_test.js index d90c2110624..75c0625fb15 100644 --- a/tests/mocha/workspace_svg_test.js +++ b/tests/mocha/workspace_svg_test.js @@ -407,9 +407,9 @@ suite('WorkspaceSvg', function () { }); }); - suite('tidyUp', function () { + suite('cleanUp', function () { test('empty workspace does not change', function () { - this.workspace.tidyUp(); + this.workspace.cleanUp(); const blocks = this.workspace.getTopBlocks(true); assert.equal(blocks.length, 0, 'workspace is empty'); @@ -426,7 +426,7 @@ suite('WorkspaceSvg', function () { }; Blockly.serialization.blocks.append(blockJson, this.workspace); - this.workspace.tidyUp(); + this.workspace.cleanUp(); const blocks = this.workspace.getTopBlocks(true); const origin = new Blockly.utils.Coordinate(0, 0); @@ -449,7 +449,7 @@ suite('WorkspaceSvg', function () { }; Blockly.serialization.blocks.append(blockJson, this.workspace); - this.workspace.tidyUp(); + this.workspace.cleanUp(); const topBlocks = this.workspace.getTopBlocks(true); const allBlocks = this.workspace.getAllBlocks(false); @@ -483,7 +483,7 @@ suite('WorkspaceSvg', function () { }; Blockly.serialization.blocks.append(blockJson, this.workspace); - this.workspace.tidyUp(); + this.workspace.cleanUp(); const topBlocks = this.workspace.getTopBlocks(true); const allBlocks = this.workspace.getAllBlocks(false); @@ -516,7 +516,7 @@ suite('WorkspaceSvg', function () { Blockly.serialization.blocks.append(blockJson1, this.workspace); Blockly.serialization.blocks.append(blockJson2, this.workspace); - this.workspace.tidyUp(); + this.workspace.cleanUp(); // block1 and block2 do not switch places since blocks are pre-sorted by their position before // being tidied up, so the order they were added to the workspace doesn't matter. @@ -557,7 +557,7 @@ suite('WorkspaceSvg', function () { Blockly.serialization.blocks.append(blockJson1, this.workspace); Blockly.serialization.blocks.append(blockJson2, this.workspace); - this.workspace.tidyUp(); + this.workspace.cleanUp(); const topBlocks = this.workspace.getTopBlocks(true); const block1 = this.workspace.getBlockById('block1'); @@ -598,7 +598,7 @@ suite('WorkspaceSvg', function () { this.workspace.getGrid().setSpacing(20); this.workspace.getGrid().setSnapToGrid(true); - this.workspace.tidyUp(); + this.workspace.cleanUp(); const topBlocks = this.workspace.getTopBlocks(true); const block1 = this.workspace.getBlockById('block1'); @@ -644,7 +644,7 @@ suite('WorkspaceSvg', function () { Blockly.serialization.blocks.append(blockJson1, this.workspace); Blockly.serialization.blocks.append(blockJson2, this.workspace); - this.workspace.tidyUp(); + this.workspace.cleanUp(); const topBlocks = this.workspace.getTopBlocks(true); const allBlocks = this.workspace.getAllBlocks(false); @@ -733,7 +733,7 @@ suite('WorkspaceSvg', function () { Blockly.serialization.blocks.append(blockJson1, this.workspace); Blockly.serialization.blocks.append(blockJson2, this.workspace); - this.workspace.tidyUp(); + this.workspace.cleanUp(); const topBlocks = this.workspace.getTopBlocks(true); const block1 = this.workspace.getBlockById('block1'); @@ -773,7 +773,7 @@ suite('WorkspaceSvg', function () { Blockly.serialization.blocks.append(blockJson4, this.workspace); Blockly.serialization.blocks.append(blockJson5, this.workspace); - this.workspace.tidyUp(); + this.workspace.cleanUp(); const topBlocks = this.workspace.getTopBlocks(true); const block1Pos = this.workspace @@ -816,7 +816,7 @@ suite('WorkspaceSvg', function () { }; Blockly.serialization.blocks.append(blockJson, this.workspace); - this.workspace.tidyUp(); + this.workspace.cleanUp(); const topBlocks = this.workspace.getTopBlocks(true); const allBlocks = this.workspace.getAllBlocks(false); @@ -907,7 +907,7 @@ suite('WorkspaceSvg', function () { Blockly.serialization.blocks.append(blockJson4, this.workspace); Blockly.serialization.blocks.append(blockJson5, this.workspace); - this.workspace.tidyUp(); + this.workspace.cleanUp(); const topBlocks = this.workspace.getTopBlocks(true); const block1Rect = this.workspace From 483f8fb65b7f24e0ccd48ddd423af2b1b12c6c00 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Sep 2024 08:28:01 -0700 Subject: [PATCH 05/32] chore(deps): bump @typescript-eslint/eslint-plugin from 8.1.0 to 8.4.0 (#8567) Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 8.1.0 to 8.4.0. - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.4.0/packages/eslint-plugin) --- updated-dependencies: - dependency-name: "@typescript-eslint/eslint-plugin" 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 | 103 ++++++++++++++++++++-------------------------- 1 file changed, 45 insertions(+), 58 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4d8de7dba2a..9a6fcca2c59 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1242,17 +1242,16 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.1.0.tgz", - "integrity": "sha512-LlNBaHFCEBPHyD4pZXb35mzjGkuGKXU5eeCA1SxvHfiRES0E82dOounfVpL4DCqYvJEKab0bZIA0gCRpdLKkCw==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.4.0.tgz", + "integrity": "sha512-rg8LGdv7ri3oAlenMACk9e+AR4wUV0yrrG+XKsGKOK0EVgeEDqurkXMPILG2836fW4ibokTB5v4b6Z9+GYQDEw==", "dev": true, - "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.1.0", - "@typescript-eslint/type-utils": "8.1.0", - "@typescript-eslint/utils": "8.1.0", - "@typescript-eslint/visitor-keys": "8.1.0", + "@typescript-eslint/scope-manager": "8.4.0", + "@typescript-eslint/type-utils": "8.4.0", + "@typescript-eslint/utils": "8.4.0", + "@typescript-eslint/visitor-keys": "8.4.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -1415,14 +1414,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.1.0.tgz", - "integrity": "sha512-DsuOZQji687sQUjm4N6c9xABJa7fjvfIdjqpSIIVOgaENf2jFXiM9hIBZOL3hb6DHK9Nvd2d7zZnoMLf9e0OtQ==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.4.0.tgz", + "integrity": "sha512-n2jFxLeY0JmKfUqy3P70rs6vdoPjHK8P/w+zJcV3fk0b0BwRXC/zxRTEnAsgYT7MwdQDt/ZEbtdzdVC+hcpF0A==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.1.0", - "@typescript-eslint/visitor-keys": "8.1.0" + "@typescript-eslint/types": "8.4.0", + "@typescript-eslint/visitor-keys": "8.4.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1433,11 +1431,10 @@ } }, "node_modules/@typescript-eslint/scope-manager/node_modules/@typescript-eslint/types": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.1.0.tgz", - "integrity": "sha512-q2/Bxa0gMOu/2/AKALI0tCKbG2zppccnRIRCW6BaaTlRVaPKft4oVYPp7WOPpcnsgbr0qROAVCVKCvIQ0tbWog==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.4.0.tgz", + "integrity": "sha512-T1RB3KQdskh9t3v/qv7niK6P8yvn7ja1mS7QK7XfRVL6wtZ8/mFs/FHf4fKvTA0rKnqnYxl/uHFNbnEt0phgbw==", "dev": true, - "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -1447,14 +1444,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.1.0.tgz", - "integrity": "sha512-oLYvTxljVvsMnldfl6jIKxTaU7ok7km0KDrwOt1RHYu6nxlhN3TIx8k5Q52L6wR33nOwDgM7VwW1fT1qMNfFIA==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.4.0.tgz", + "integrity": "sha512-pu2PAmNrl9KX6TtirVOrbLPLwDmASpZhK/XU7WvoKoCUkdtq9zF7qQ7gna0GBZFN0hci0vHaSusiL2WpsQk37A==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.1.0", - "@typescript-eslint/utils": "8.1.0", + "@typescript-eslint/typescript-estree": "8.4.0", + "@typescript-eslint/utils": "8.4.0", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, @@ -1486,16 +1482,15 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.1.0.tgz", - "integrity": "sha512-NTHhmufocEkMiAord/g++gWKb0Fr34e9AExBRdqgWdVBaKoei2dIyYKD9Q0jBnvfbEA5zaf8plUFMUH6kQ0vGg==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.4.0.tgz", + "integrity": "sha512-kJ2OIP4dQw5gdI4uXsaxUZHRwWAGpREJ9Zq6D5L0BweyOrWsL6Sz0YcAZGWhvKnH7fm1J5YFE1JrQL0c9dd53A==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "8.1.0", - "@typescript-eslint/visitor-keys": "8.1.0", + "@typescript-eslint/types": "8.4.0", + "@typescript-eslint/visitor-keys": "8.4.0", "debug": "^4.3.4", - "globby": "^11.1.0", + "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", @@ -1515,11 +1510,10 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/types": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.1.0.tgz", - "integrity": "sha512-q2/Bxa0gMOu/2/AKALI0tCKbG2zppccnRIRCW6BaaTlRVaPKft4oVYPp7WOPpcnsgbr0qROAVCVKCvIQ0tbWog==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.4.0.tgz", + "integrity": "sha512-T1RB3KQdskh9t3v/qv7niK6P8yvn7ja1mS7QK7XfRVL6wtZ8/mFs/FHf4fKvTA0rKnqnYxl/uHFNbnEt0phgbw==", "dev": true, - "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -1533,7 +1527,6 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, - "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } @@ -1543,7 +1536,6 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, - "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -1559,7 +1551,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, - "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -1568,16 +1559,15 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.1.0.tgz", - "integrity": "sha512-ypRueFNKTIFwqPeJBfeIpxZ895PQhNyH4YID6js0UoBImWYoSjBsahUn9KMiJXh94uOjVBgHD9AmkyPsPnFwJA==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.4.0.tgz", + "integrity": "sha512-swULW8n1IKLjRAgciCkTCafyTHHfwVQFt8DovmaF69sKbOxTSFMmIZaSHjqO9i/RV0wIblaawhzvtva8Nmm7lQ==", "dev": true, - "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.1.0", - "@typescript-eslint/types": "8.1.0", - "@typescript-eslint/typescript-estree": "8.1.0" + "@typescript-eslint/scope-manager": "8.4.0", + "@typescript-eslint/types": "8.4.0", + "@typescript-eslint/typescript-estree": "8.4.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1591,11 +1581,10 @@ } }, "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.1.0.tgz", - "integrity": "sha512-q2/Bxa0gMOu/2/AKALI0tCKbG2zppccnRIRCW6BaaTlRVaPKft4oVYPp7WOPpcnsgbr0qROAVCVKCvIQ0tbWog==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.4.0.tgz", + "integrity": "sha512-T1RB3KQdskh9t3v/qv7niK6P8yvn7ja1mS7QK7XfRVL6wtZ8/mFs/FHf4fKvTA0rKnqnYxl/uHFNbnEt0phgbw==", "dev": true, - "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -1605,13 +1594,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.1.0.tgz", - "integrity": "sha512-ba0lNI19awqZ5ZNKh6wCModMwoZs457StTebQ0q1NP58zSi2F6MOZRXwfKZy+jB78JNJ/WH8GSh2IQNzXX8Nag==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.4.0.tgz", + "integrity": "sha512-zTQD6WLNTre1hj5wp09nBIDiOc2U5r/qmzo7wxPn4ZgAjHql09EofqhF9WF+fZHzL5aCyaIpPcT2hyxl73kr9A==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.1.0", + "@typescript-eslint/types": "8.4.0", "eslint-visitor-keys": "^3.4.3" }, "engines": { @@ -1623,11 +1611,10 @@ } }, "node_modules/@typescript-eslint/visitor-keys/node_modules/@typescript-eslint/types": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.1.0.tgz", - "integrity": "sha512-q2/Bxa0gMOu/2/AKALI0tCKbG2zppccnRIRCW6BaaTlRVaPKft4oVYPp7WOPpcnsgbr0qROAVCVKCvIQ0tbWog==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.4.0.tgz", + "integrity": "sha512-T1RB3KQdskh9t3v/qv7niK6P8yvn7ja1mS7QK7XfRVL6wtZ8/mFs/FHf4fKvTA0rKnqnYxl/uHFNbnEt0phgbw==", "dev": true, - "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, From 8211c1a530fc827354c648c08f640f8fec90eff1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 13 Sep 2024 17:58:59 +0100 Subject: [PATCH 06/32] chore(deps): bump @hyperjump/browser from 1.1.4 to 1.1.6 (#8569) Bumps [@hyperjump/browser](https://github.com/hyperjump-io/browser) from 1.1.4 to 1.1.6. - [Commits](https://github.com/hyperjump-io/browser/compare/v1.1.4...v1.1.6) --- updated-dependencies: - dependency-name: "@hyperjump/browser" 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, 3 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9a6fcca2c59..c00d0fe11a9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -472,11 +472,10 @@ "dev": true }, "node_modules/@hyperjump/browser": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@hyperjump/browser/-/browser-1.1.4.tgz", - "integrity": "sha512-85rfa3B79MssMOxNChvXJhfgvIXqA2FEzwrxKe9iMpCKZVQIxQe54w210VeFM0D33pVOeNskg7TyptSjenY2+w==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@hyperjump/browser/-/browser-1.1.6.tgz", + "integrity": "sha512-i27uPV7SxK1GOn7TLTRxTorxchYa5ur9JHgtl6TxZ1MHuyb9ROAnXxEeu4q4H1836Xb7lL2PGPsaa5Jl3p+R6g==", "dev": true, - "license": "MIT", "dependencies": { "@hyperjump/json-pointer": "^1.1.0", "@hyperjump/uri": "^1.2.0", From 561b4189fb2fe186a91eb27d9cfbdffc41782367 Mon Sep 17 00:00:00 2001 From: John Nesky Date: Fri, 13 Sep 2024 12:53:37 -0700 Subject: [PATCH 07/32] fix: Factor out workspace drag methods into utils. (#8566) --- core/bubbles/textinput_bubble.ts | 6 ++- core/comments/comment_view.ts | 8 ++-- core/utils/drag.ts | 74 ++++++++++++++++++++++++++++++++ core/workspace_svg.ts | 25 ++--------- 4 files changed, 85 insertions(+), 28 deletions(-) create mode 100644 core/utils/drag.ts diff --git a/core/bubbles/textinput_bubble.ts b/core/bubbles/textinput_bubble.ts index 675dbb5398e..befbb2f21bf 100644 --- a/core/bubbles/textinput_bubble.ts +++ b/core/bubbles/textinput_bubble.ts @@ -9,6 +9,7 @@ import * as touch from '../touch.js'; import {browserEvents} from '../utils.js'; import {Coordinate} from '../utils/coordinate.js'; import * as dom from '../utils/dom.js'; +import * as drag from '../utils/drag.js'; import {Rect} from '../utils/rect.js'; import {Size} from '../utils/size.js'; import {Svg} from '../utils/svg.js'; @@ -224,7 +225,8 @@ export class TextInputBubble extends Bubble { return; } - this.workspace.startDrag( + drag.start( + this.workspace, e, new Coordinate( this.workspace.RTL ? -this.getSize().width : this.getSize().width, @@ -264,7 +266,7 @@ export class TextInputBubble extends Bubble { /** Handles pointer move events on the resize target. */ private onResizePointerMove(e: PointerEvent) { - const delta = this.workspace.moveDrag(e); + const delta = drag.move(this.workspace, e); this.setSize( new Size(this.workspace.RTL ? -delta.x : delta.x, delta.y), false, diff --git a/core/comments/comment_view.ts b/core/comments/comment_view.ts index 72f72fa38b1..bd90c757657 100644 --- a/core/comments/comment_view.ts +++ b/core/comments/comment_view.ts @@ -11,6 +11,7 @@ import * as layers from '../layers.js'; import * as touch from '../touch.js'; import {Coordinate} from '../utils/coordinate.js'; import * as dom from '../utils/dom.js'; +import * as drag from '../utils/drag.js'; import {Size} from '../utils/size.js'; import {Svg} from '../utils/svg.js'; import {WorkspaceSvg} from '../workspace_svg.js'; @@ -524,8 +525,8 @@ export class CommentView implements IRenderedElement { this.preResizeSize = this.getSize(); - // TODO(#7926): Move this into a utils file. - this.workspace.startDrag( + drag.start( + this.workspace, e, new Coordinate( this.workspace.RTL ? -this.getSize().width : this.getSize().width, @@ -569,8 +570,7 @@ export class CommentView implements IRenderedElement { /** Resizes the comment in response to a drag on the resize handle. */ private onResizePointerMove(e: PointerEvent) { - // TODO(#7926): Move this into a utils file. - const size = this.workspace.moveDrag(e); + const size = drag.move(this.workspace, e); this.setSizeWithoutFiringEvents( new Size(this.workspace.RTL ? -size.x : size.x, size.y), ); diff --git a/core/utils/drag.ts b/core/utils/drag.ts new file mode 100644 index 00000000000..a6322933bf9 --- /dev/null +++ b/core/utils/drag.ts @@ -0,0 +1,74 @@ +/** + * @license + * Copyright 2024 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as browserEvents from '../browser_events.js'; +import type {WorkspaceSvg} from '../workspace_svg.js'; +import {Coordinate} from './coordinate.js'; + +const workspaceToDragDelta: WeakMap = new WeakMap(); + +/** + * Convert from mouse coordinates to workspace coordinates. + * + * @param workspace The workspace where the pointer event is occurring. + * @param e The pointer event with the source coordinates. + */ +function mouseToWorkspacePoint( + workspace: WorkspaceSvg, + e: PointerEvent, +): SVGPoint { + const point = browserEvents.mouseToSvg( + e, + workspace.getParentSvg(), + workspace.getInverseScreenCTM(), + ); + // Fix scale of mouse event. + point.x /= workspace.scale; + point.y /= workspace.scale; + return point; +} + +/** + * Start tracking a drag of an object on this workspace by recording the offset + * between the pointer's current location and the object's starting location. + * + * Used for resizing block comments and workspace comments. + * + * @param workspace The workspace where the drag is occurring. + * @param e Pointer down event. + * @param xy Starting location of object. + */ +export function start( + workspace: WorkspaceSvg, + e: PointerEvent, + xy: Coordinate, +) { + const point = mouseToWorkspacePoint(workspace, e); + // Record the starting offset between the bubble's location and the mouse. + workspaceToDragDelta.set(workspace, Coordinate.difference(xy, point)); +} + +/** + * Compute the new position of a dragged object in this workspace based on the + * current pointer position and the offset between the pointer's starting + * location and the object's starting location. + * + * The start function should have be called previously, when the drag started. + * + * Used for resizing block comments and workspace comments. + * + * @param workspace The workspace where the drag is occurring. + * @param e Pointer move event. + * @returns New location of object. + */ +export function move(workspace: WorkspaceSvg, e: PointerEvent): Coordinate { + const point = mouseToWorkspacePoint(workspace, e); + const dragDelta = workspaceToDragDelta.get(workspace); + if (!dragDelta) { + throw new Error('Drag not initialized'); + } + return Coordinate.sum(dragDelta, point); +} diff --git a/core/workspace_svg.ts b/core/workspace_svg.ts index b8ef96292bc..fed5e3cb16b 100644 --- a/core/workspace_svg.ts +++ b/core/workspace_svg.ts @@ -63,6 +63,7 @@ import type {Trashcan} from './trashcan.js'; import * as arrayUtils from './utils/array.js'; import {Coordinate} from './utils/coordinate.js'; import * as dom from './utils/dom.js'; +import * as drag from './utils/drag.js'; import type {Metrics} from './utils/metrics.js'; import {Rect} from './utils/rect.js'; import {Size} from './utils/size.js'; @@ -181,9 +182,6 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { /** Vertical scroll value when scrolling started in pixel units. */ startScrollY = 0; - /** Distance from mouse to object being dragged. */ - private dragDeltaXY: Coordinate | null = null; - /** Current scale. */ scale = 1; @@ -1447,16 +1445,7 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { * @param xy Starting location of object. */ startDrag(e: PointerEvent, xy: Coordinate) { - // Record the starting offset between the bubble's location and the mouse. - const point = browserEvents.mouseToSvg( - e, - this.getParentSvg(), - this.getInverseScreenCTM(), - ); - // Fix scale of mouse event. - point.x /= this.scale; - point.y /= this.scale; - this.dragDeltaXY = Coordinate.difference(xy, point); + drag.start(this, e, xy); } /** @@ -1466,15 +1455,7 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { * @returns New location of object. */ moveDrag(e: PointerEvent): Coordinate { - const point = browserEvents.mouseToSvg( - e, - this.getParentSvg(), - this.getInverseScreenCTM(), - ); - // Fix scale of mouse event. - point.x /= this.scale; - point.y /= this.scale; - return Coordinate.sum(this.dragDeltaXY!, point); + return drag.move(this, e); } /** From de6982abd2cd15b9843fee82e46d3dcc2f741c5a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Sep 2024 08:21:11 -0700 Subject: [PATCH 08/32] chore(deps): bump @microsoft/api-documenter from 7.25.10 to 7.25.14 (#8578) Bumps [@microsoft/api-documenter](https://github.com/microsoft/rushstack/tree/HEAD/apps/api-documenter) from 7.25.10 to 7.25.14. - [Changelog](https://github.com/microsoft/rushstack/blob/main/apps/api-documenter/CHANGELOG.md) - [Commits](https://github.com/microsoft/rushstack/commits/@microsoft/api-documenter_v7.25.14/apps/api-documenter) --- updated-dependencies: - dependency-name: "@microsoft/api-documenter" 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 | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/package-lock.json b/package-lock.json index c00d0fe11a9..cc2b59a4bd8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -630,16 +630,16 @@ } }, "node_modules/@microsoft/api-documenter": { - "version": "7.25.10", - "resolved": "https://registry.npmjs.org/@microsoft/api-documenter/-/api-documenter-7.25.10.tgz", - "integrity": "sha512-GYc5AALrP9gxYPpkPc/BXXdekg+Ge8p9yyO1aRVwJDGzCXR7XRUvh6gc2jay/DmBx4KfyMx0LFWJ0HcUXudqgQ==", + "version": "7.25.14", + "resolved": "https://registry.npmjs.org/@microsoft/api-documenter/-/api-documenter-7.25.14.tgz", + "integrity": "sha512-nysAB+j4l5Al3XvCdee6tw0rw4fXpnlIq9En2opcc3DgITeoehiaYYoZZqoqOQSKlSUDWF7Z55GGsvntVrcBkg==", "dev": true, "dependencies": { - "@microsoft/api-extractor-model": "7.29.4", + "@microsoft/api-extractor-model": "7.29.8", "@microsoft/tsdoc": "~0.15.0", - "@rushstack/node-core-library": "5.5.1", - "@rushstack/terminal": "0.13.3", - "@rushstack/ts-command-line": "4.22.4", + "@rushstack/node-core-library": "5.9.0", + "@rushstack/terminal": "0.14.2", + "@rushstack/ts-command-line": "4.22.8", "js-yaml": "~3.13.1", "resolve": "~1.22.1" }, @@ -648,20 +648,20 @@ } }, "node_modules/@microsoft/api-documenter/node_modules/@microsoft/api-extractor-model": { - "version": "7.29.4", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.29.4.tgz", - "integrity": "sha512-LHOMxmT8/tU1IiiiHOdHFF83Qsi+V8d0kLfscG4EvQE9cafiR8blOYr8SfkQKWB1wgEilQgXJX3MIA4vetDLZw==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.29.8.tgz", + "integrity": "sha512-t3Z/xcO6TRbMcnKGVMs4uMzv/gd5j0NhMiJIGjD4cJMeFJ1Hf8wnLSx37vxlRlL0GWlGJhnFgxvnaL6JlS+73g==", "dev": true, "dependencies": { "@microsoft/tsdoc": "~0.15.0", "@microsoft/tsdoc-config": "~0.17.0", - "@rushstack/node-core-library": "5.5.1" + "@rushstack/node-core-library": "5.9.0" } }, "node_modules/@microsoft/api-documenter/node_modules/@rushstack/node-core-library": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-5.5.1.tgz", - "integrity": "sha512-ZutW56qIzH8xIOlfyaLQJFx+8IBqdbVCZdnj+XT1MorQ1JqqxHse8vbCpEM+2MjsrqcbxcgDIbfggB1ZSQ2A3g==", + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-5.9.0.tgz", + "integrity": "sha512-MMsshEWkTbXqxqFxD4gcIUWQOCeBChlGczdZbHfqmNZQFLHB3yWxDFSMHFUdu2/OB9NUk7Awn5qRL+rws4HQNg==", "dev": true, "dependencies": { "ajv": "~8.13.0", @@ -683,12 +683,12 @@ } }, "node_modules/@microsoft/api-documenter/node_modules/@rushstack/terminal": { - "version": "0.13.3", - "resolved": "https://registry.npmjs.org/@rushstack/terminal/-/terminal-0.13.3.tgz", - "integrity": "sha512-fc3zjXOw8E0pXS5t9vTiIPx9gHA0fIdTXsu9mT4WbH+P3mYvnrX0iAQ5a6NvyK1+CqYWBTw/wVNx7SDJkI+WYQ==", + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/@rushstack/terminal/-/terminal-0.14.2.tgz", + "integrity": "sha512-2fC1wqu1VCExKC0/L+0noVcFQEXEnoBOtCIex1TOjBzEDWcw8KzJjjj7aTP6mLxepG0XIyn9OufeFb6SFsa+sg==", "dev": true, "dependencies": { - "@rushstack/node-core-library": "5.5.1", + "@rushstack/node-core-library": "5.9.0", "supports-color": "~8.1.1" }, "peerDependencies": { @@ -701,12 +701,12 @@ } }, "node_modules/@microsoft/api-documenter/node_modules/@rushstack/ts-command-line": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.22.4.tgz", - "integrity": "sha512-QoyhbWfyF9Ixg5DWdPzxO3h2RmJ7i5WH9b7qLzD5h5WFya/ZqicjdPrVwQiGtrFvAbBj8jhcC9DhbzU9xAk78g==", + "version": "4.22.8", + "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.22.8.tgz", + "integrity": "sha512-XbFjOoV7qZHJnSuFUHv0pKaFA4ixyCuki+xMjsMfDwfvQjs5MYG0IK5COal3tRnG7KCDe2l/G+9LrzYE/RJhgg==", "dev": true, "dependencies": { - "@rushstack/terminal": "0.13.3", + "@rushstack/terminal": "0.14.2", "@types/argparse": "1.0.38", "argparse": "~1.0.9", "string-argv": "~0.3.1" From 73416d4db559302d2b090d112e1c74612910445a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Sep 2024 13:30:19 -0700 Subject: [PATCH 09/32] chore(deps): bump @typescript-eslint/parser from 8.2.0 to 8.5.0 (#8577) Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 8.2.0 to 8.5.0. - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.5.0/packages/parser) --- updated-dependencies: - dependency-name: "@typescript-eslint/parser" 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 | 114 ++++++++++------------------------------------ 1 file changed, 25 insertions(+), 89 deletions(-) diff --git a/package-lock.json b/package-lock.json index cc2b59a4bd8..7e34a9307db 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1274,15 +1274,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.2.0.tgz", - "integrity": "sha512-j3Di+o0lHgPrb7FxL3fdEy6LJ/j2NE8u+AP/5cQ9SKb+JLH6V6UHDqJ+e0hXBkHP1wn1YDFjYCS9LBQsZDlDEg==", + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.5.0.tgz", + "integrity": "sha512-gF77eNv0Xz2UJg/NbpWJ0kqAm35UMsvZf1GHj8D9MRFTj/V3tAciIWXfmPLsAAF/vUlpWPvUDyH1jjsr0cMVWw==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "8.2.0", - "@typescript-eslint/types": "8.2.0", - "@typescript-eslint/typescript-estree": "8.2.0", - "@typescript-eslint/visitor-keys": "8.2.0", + "@typescript-eslint/scope-manager": "8.5.0", + "@typescript-eslint/types": "8.5.0", + "@typescript-eslint/typescript-estree": "8.5.0", + "@typescript-eslint/visitor-keys": "8.5.0", "debug": "^4.3.4" }, "engines": { @@ -1302,13 +1302,13 @@ } }, "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.2.0.tgz", - "integrity": "sha512-OFn80B38yD6WwpoHU2Tz/fTz7CgFqInllBoC3WP+/jLbTb4gGPTy9HBSTsbDWkMdN55XlVU0mMDYAtgvlUspGw==", + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.5.0.tgz", + "integrity": "sha512-06JOQ9Qgj33yvBEx6tpC8ecP9o860rsR22hWMEd12WcTRrfaFgHr2RB/CA/B+7BMhHkXT4chg2MyboGdFGawYg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.2.0", - "@typescript-eslint/visitor-keys": "8.2.0" + "@typescript-eslint/types": "8.5.0", + "@typescript-eslint/visitor-keys": "8.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1319,9 +1319,9 @@ } }, "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.2.0.tgz", - "integrity": "sha512-6a9QSK396YqmiBKPkJtxsgZZZVjYQ6wQ/TlI0C65z7vInaETuC6HAHD98AGLC8DyIPqHytvNuS8bBVvNLKyqvQ==", + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.5.0.tgz", + "integrity": "sha512-qjkormnQS5wF9pjSi6q60bKUHH44j2APxfh9TQRXK8wbYVeDYYdYJGIROL87LGZZ2gz3Rbmjc736qyL8deVtdw==", "dev": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1332,15 +1332,15 @@ } }, "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.2.0.tgz", - "integrity": "sha512-kiG4EDUT4dImplOsbh47B1QnNmXSoUqOjWDvCJw/o8LgfD0yr7k2uy54D5Wm0j4t71Ge1NkynGhpWdS0dEIAUA==", + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.5.0.tgz", + "integrity": "sha512-vEG2Sf9P8BPQ+d0pxdfndw3xIXaoSjliG0/Ejk7UggByZPKXmJmw3GW5jV2gHNQNawBUyfahoSiCFVov0Ruf7Q==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.2.0", - "@typescript-eslint/visitor-keys": "8.2.0", + "@typescript-eslint/types": "8.5.0", + "@typescript-eslint/visitor-keys": "8.5.0", "debug": "^4.3.4", - "globby": "^11.1.0", + "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", @@ -1360,12 +1360,12 @@ } }, "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.2.0.tgz", - "integrity": "sha512-sbgsPMW9yLvS7IhCi8IpuK1oBmtbWUNP+hBdwl/I9nzqVsszGnNGti5r9dUtF5RLivHUFFIdRvLiTsPhzSyJ3Q==", + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.5.0.tgz", + "integrity": "sha512-yTPqMnbAZJNy2Xq2XU8AdtOW9tJIr+UQb64aXB9f3B1498Zx9JorVgFJcZpEc9UBuCCrdzKID2RGAMkYcDtZOw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.2.0", + "@typescript-eslint/types": "8.5.0", "eslint-visitor-keys": "^3.4.3" }, "engines": { @@ -2191,16 +2191,6 @@ "node": ">=0.10.0" } }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/assertion-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", @@ -3412,19 +3402,6 @@ "node": ">=0.3.1" } }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -4825,27 +4802,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/glogg": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/glogg/-/glogg-2.2.0.tgz", @@ -7269,16 +7225,6 @@ "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", "dev": true }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/pathval": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", @@ -8039,16 +7985,6 @@ "node": ">=0.3.1" } }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/slashes": { "version": "3.0.12", "resolved": "https://registry.npmjs.org/slashes/-/slashes-3.0.12.tgz", From 51f6dab0b26256510f3967131a8175eb6cd83a25 Mon Sep 17 00:00:00 2001 From: John Nesky Date: Fri, 20 Sep 2024 14:46:06 -0700 Subject: [PATCH 10/32] fix: Simplify list and text WHERE validation (#8575) * fix: Simplify list and text WHERE validation * Addressing PR review comments. --- blocks/lists.ts | 139 +++++++++++++++++++++--------------------------- blocks/text.ts | 54 +++++++++---------- 2 files changed, 86 insertions(+), 107 deletions(-) diff --git a/blocks/lists.ts b/blocks/lists.ts index 28ff17b3dfe..6754b6847db 100644 --- a/blocks/lists.ts +++ b/blocks/lists.ts @@ -412,6 +412,24 @@ const LISTS_GETINDEX = { this.appendDummyInput() .appendField(modeMenu, 'MODE') .appendField('', 'SPACE'); + const menu = fieldRegistry.fromJson({ + type: 'field_dropdown', + options: this.WHERE_OPTIONS, + }) as FieldDropdown; + menu.setValidator( + /** @param value The input value. */ + function (this: FieldDropdown, value: string) { + const oldValue: string | null = this.getValue(); + const oldAt = oldValue === 'FROM_START' || oldValue === 'FROM_END'; + const newAt = value === 'FROM_START' || value === 'FROM_END'; + if (newAt !== oldAt) { + const block = this.getSourceBlock() as GetIndexBlock; + block.updateAt_(newAt); + } + return undefined; + }, + ); + this.appendDummyInput().appendField(menu, 'WHERE'); this.appendDummyInput('AT'); if (Msg['LISTS_GET_INDEX_TAIL']) { this.appendDummyInput('TAIL').appendField(Msg['LISTS_GET_INDEX_TAIL']); @@ -577,31 +595,6 @@ const LISTS_GETINDEX = { } else { this.appendDummyInput('AT'); } - const menu = fieldRegistry.fromJson({ - type: 'field_dropdown', - options: this.WHERE_OPTIONS, - }) as FieldDropdown; - menu.setValidator( - /** - * @param value The input value. - * @returns Null if the field has been replaced; otherwise undefined. - */ - function (this: FieldDropdown, value: string) { - const newAt = value === 'FROM_START' || value === 'FROM_END'; - // The 'isAt' variable is available due to this function being a - // closure. - if (newAt !== isAt) { - const block = this.getSourceBlock() as GetIndexBlock; - block.updateAt_(newAt); - // This menu has been destroyed and replaced. Update the - // replacement. - block.setFieldValue(value, 'WHERE'); - return null; - } - return undefined; - }, - ); - this.getInput('AT')!.appendField(menu, 'WHERE'); if (Msg['LISTS_GET_INDEX_TAIL']) { this.moveInputBefore('TAIL', null); } @@ -644,6 +637,24 @@ const LISTS_SETINDEX = { this.appendDummyInput() .appendField(operationDropdown, 'MODE') .appendField('', 'SPACE'); + const menu = fieldRegistry.fromJson({ + type: 'field_dropdown', + options: this.WHERE_OPTIONS, + }) as FieldDropdown; + menu.setValidator( + /** @param value The input value. */ + function (this: FieldDropdown, value: string) { + const oldValue: string | null = this.getValue(); + const oldAt = oldValue === 'FROM_START' || oldValue === 'FROM_END'; + const newAt = value === 'FROM_START' || value === 'FROM_END'; + if (newAt !== oldAt) { + const block = this.getSourceBlock() as SetIndexBlock; + block.updateAt_(newAt); + } + return undefined; + }, + ); + this.appendDummyInput().appendField(menu, 'WHERE'); this.appendDummyInput('AT'); this.appendValueInput('TO').appendField(Msg['LISTS_SET_INDEX_INPUT_TO']); this.setInputsInline(true); @@ -756,36 +767,10 @@ const LISTS_SETINDEX = { } else { this.appendDummyInput('AT'); } - const menu = fieldRegistry.fromJson({ - type: 'field_dropdown', - options: this.WHERE_OPTIONS, - }) as FieldDropdown; - menu.setValidator( - /** - * @param value The input value. - * @returns Null if the field has been replaced; otherwise undefined. - */ - function (this: FieldDropdown, value: string) { - const newAt = value === 'FROM_START' || value === 'FROM_END'; - // The 'isAt' variable is available due to this function being a - // closure. - if (newAt !== isAt) { - const block = this.getSourceBlock() as SetIndexBlock; - block.updateAt_(newAt); - // This menu has been destroyed and replaced. Update the - // replacement. - block.setFieldValue(value, 'WHERE'); - return null; - } - return undefined; - }, - ); this.moveInputBefore('AT', 'TO'); if (this.getInput('ORDINAL')) { this.moveInputBefore('ORDINAL', 'TO'); } - - this.getInput('AT')!.appendField(menu, 'WHERE'); }, }; blocks['lists_setIndex'] = LISTS_SETINDEX; @@ -818,7 +803,30 @@ const LISTS_GETSUBLIST = { this.appendValueInput('LIST') .setCheck('Array') .appendField(Msg['LISTS_GET_SUBLIST_INPUT_IN_LIST']); + const createMenu = (n: 1 | 2): FieldDropdown => { + const menu = fieldRegistry.fromJson({ + type: 'field_dropdown', + options: + this[('WHERE_OPTIONS_' + n) as 'WHERE_OPTIONS_1' | 'WHERE_OPTIONS_2'], + }) as FieldDropdown; + menu.setValidator( + /** @param value The input value. */ + function (this: FieldDropdown, value: string) { + const oldValue: string | null = this.getValue(); + const oldAt = oldValue === 'FROM_START' || oldValue === 'FROM_END'; + const newAt = value === 'FROM_START' || value === 'FROM_END'; + if (newAt !== oldAt) { + const block = this.getSourceBlock() as GetSublistBlock; + block.updateAt_(n, newAt); + } + return undefined; + }, + ); + return menu; + }; + this.appendDummyInput('WHERE1_INPUT').appendField(createMenu(1), 'WHERE1'); this.appendDummyInput('AT1'); + this.appendDummyInput('WHERE2_INPUT').appendField(createMenu(2), 'WHERE2'); this.appendDummyInput('AT2'); if (Msg['LISTS_GET_SUBLIST_TAIL']) { this.appendDummyInput('TAIL').appendField(Msg['LISTS_GET_SUBLIST_TAIL']); @@ -896,35 +904,10 @@ const LISTS_GETSUBLIST = { } else { this.appendDummyInput('AT' + n); } - const menu = fieldRegistry.fromJson({ - type: 'field_dropdown', - options: - this[('WHERE_OPTIONS_' + n) as 'WHERE_OPTIONS_1' | 'WHERE_OPTIONS_2'], - }) as FieldDropdown; - menu.setValidator( - /** - * @param value The input value. - * @returns Null if the field has been replaced; otherwise undefined. - */ - function (this: FieldDropdown, value: string) { - const newAt = value === 'FROM_START' || value === 'FROM_END'; - // The 'isAt' variable is available due to this function being a - // closure. - if (newAt !== isAt) { - const block = this.getSourceBlock() as GetSublistBlock; - block.updateAt_(n, newAt); - // This menu has been destroyed and replaced. - // Update the replacement. - block.setFieldValue(value, 'WHERE' + n); - return null; - } - }, - ); - this.getInput('AT' + n)!.appendField(menu, 'WHERE' + n); if (n === 1) { - this.moveInputBefore('AT1', 'AT2'); + this.moveInputBefore('AT1', 'WHERE2_INPUT'); if (this.getInput('ORDINAL1')) { - this.moveInputBefore('ORDINAL1', 'AT2'); + this.moveInputBefore('ORDINAL1', 'WHERE2_INPUT'); } } if (Msg['LISTS_GET_SUBLIST_TAIL']) { diff --git a/blocks/text.ts b/blocks/text.ts index 5ab63183641..a7ad5374ac4 100644 --- a/blocks/text.ts +++ b/blocks/text.ts @@ -216,7 +216,30 @@ const GET_SUBSTRING_BLOCK = { this.appendValueInput('STRING') .setCheck('String') .appendField(Msg['TEXT_GET_SUBSTRING_INPUT_IN_TEXT']); + const createMenu = (n: 1 | 2): FieldDropdown => { + const menu = fieldRegistry.fromJson({ + type: 'field_dropdown', + options: + this[('WHERE_OPTIONS_' + n) as 'WHERE_OPTIONS_1' | 'WHERE_OPTIONS_2'], + }) as FieldDropdown; + menu.setValidator( + /** @param value The input value. */ + function (this: FieldDropdown, value: any): null | undefined { + const oldValue: string | null = this.getValue(); + const oldAt = oldValue === 'FROM_START' || oldValue === 'FROM_END'; + const newAt = value === 'FROM_START' || value === 'FROM_END'; + if (newAt !== oldAt) { + const block = this.getSourceBlock() as GetSubstringBlock; + block.updateAt_(n, newAt); + } + return undefined; + }, + ); + return menu; + }; + this.appendDummyInput('WHERE1_INPUT').appendField(createMenu(1), 'WHERE1'); this.appendDummyInput('AT1'); + this.appendDummyInput('WHERE2_INPUT').appendField(createMenu(2), 'WHERE2'); this.appendDummyInput('AT2'); if (Msg['TEXT_GET_SUBSTRING_TAIL']) { this.appendDummyInput('TAIL').appendField(Msg['TEXT_GET_SUBSTRING_TAIL']); @@ -288,37 +311,10 @@ const GET_SUBSTRING_BLOCK = { this.removeInput('TAIL', true); this.appendDummyInput('TAIL').appendField(Msg['TEXT_GET_SUBSTRING_TAIL']); } - const menu = fieldRegistry.fromJson({ - type: 'field_dropdown', - options: - this[('WHERE_OPTIONS_' + n) as 'WHERE_OPTIONS_1' | 'WHERE_OPTIONS_2'], - }) as FieldDropdown; - menu.setValidator( - /** - * @param value The input value. - * @returns Null if the field has been replaced; otherwise undefined. - */ - function (this: FieldDropdown, value: any): null | undefined { - const newAt = value === 'FROM_START' || value === 'FROM_END'; - // The 'isAt' variable is available due to this function being a - // closure. - if (newAt !== isAt) { - const block = this.getSourceBlock() as GetSubstringBlock; - block.updateAt_(n, newAt); - // This menu has been destroyed and replaced. - // Update the replacement. - block.setFieldValue(value, 'WHERE' + n); - return null; - } - return undefined; - }, - ); - - this.getInput('AT' + n)!.appendField(menu, 'WHERE' + n); if (n === 1) { - this.moveInputBefore('AT1', 'AT2'); + this.moveInputBefore('AT1', 'WHERE2_INPUT'); if (this.getInput('ORDINAL1')) { - this.moveInputBefore('ORDINAL1', 'AT2'); + this.moveInputBefore('ORDINAL1', 'WHERE2_INPUT'); } } }, From bc2b142f62443ce3851c000d990d5a96006b4f5d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Sep 2024 08:29:40 -0700 Subject: [PATCH 11/32] chore(deps): bump @blockly/theme-modern from 6.0.3 to 6.0.7 (#8583) Bumps [@blockly/theme-modern](https://github.com/google/blockly-samples/tree/HEAD/plugins/theme-modern) from 6.0.3 to 6.0.7. - [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@6.0.7/plugins/theme-modern) --- updated-dependencies: - dependency-name: "@blockly/theme-modern" 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, 3 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7e34a9307db..bafb98ef327 100644 --- a/package-lock.json +++ b/package-lock.json @@ -223,11 +223,10 @@ } }, "node_modules/@blockly/theme-modern": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@blockly/theme-modern/-/theme-modern-6.0.3.tgz", - "integrity": "sha512-pGtwrxqUHfFmT2s8DRZ/FGuBo3hdoVZt66FDFWicripRv5OteXlmLiw3zjbVnca34LHwJ0lKCvUvARAVbPVnHg==", + "version": "6.0.7", + "resolved": "https://registry.npmjs.org/@blockly/theme-modern/-/theme-modern-6.0.7.tgz", + "integrity": "sha512-RUEmunGe1L6So0sTpBd1yUz3foUAzjTj1x0y3P4iyuGu0HzfLIacqUpdU4wQNteGPbKSBp7qDFRXaH/V2eJ6QA==", "dev": true, - "license": "Apache-2.0", "engines": { "node": ">=8.17.0" }, From 8d44a4d93a62d6580c4f09290f26c192de48a6a4 Mon Sep 17 00:00:00 2001 From: John Nesky Date: Tue, 24 Sep 2024 16:09:41 -0700 Subject: [PATCH 12/32] fix: Group field validator changes with field value changes. (#8589) --- core/field.ts | 101 ++++++++++++++------------ tests/mocha/jso_serialization_test.js | 2 +- 2 files changed, 57 insertions(+), 46 deletions(-) diff --git a/core/field.ts b/core/field.ts index c9d3781a257..c702abadc4a 100644 --- a/core/field.ts +++ b/core/field.ts @@ -1086,57 +1086,68 @@ export abstract class Field return; } - const classValidation = this.doClassValidation_(newValue); - const classValue = this.processValidation_( - newValue, - classValidation, - fireChangeEvent, - ); - if (classValue instanceof Error) { - if (doLogging) console.log('invalid class validation, return'); - return; + // Field validators are allowed to make changes to the workspace, which + // should get grouped with the field value change event. + const existingGroup = eventUtils.getGroup(); + if (!existingGroup) { + eventUtils.setGroup(true); } - const localValidation = this.getValidator()?.call(this, classValue); - const localValue = this.processValidation_( - classValue, - localValidation, - fireChangeEvent, - ); - if (localValue instanceof Error) { - if (doLogging) console.log('invalid local validation, return'); - return; - } + try { + const classValidation = this.doClassValidation_(newValue); + const classValue = this.processValidation_( + newValue, + classValidation, + fireChangeEvent, + ); + if (classValue instanceof Error) { + if (doLogging) console.log('invalid class validation, return'); + return; + } - const source = this.sourceBlock_; - if (source && source.disposed) { - if (doLogging) console.log('source disposed, return'); - return; - } + const localValidation = this.getValidator()?.call(this, classValue); + const localValue = this.processValidation_( + classValue, + localValidation, + fireChangeEvent, + ); + if (localValue instanceof Error) { + if (doLogging) console.log('invalid local validation, return'); + return; + } - const oldValue = this.getValue(); - if (oldValue === localValue) { - if (doLogging) console.log('same, doValueUpdate_, return'); - this.doValueUpdate_(localValue); - return; - } + const source = this.sourceBlock_; + if (source && source.disposed) { + if (doLogging) console.log('source disposed, return'); + return; + } - this.doValueUpdate_(localValue); - if (fireChangeEvent && source && eventUtils.isEnabled()) { - eventUtils.fire( - new (eventUtils.get(EventType.BLOCK_CHANGE))( - source, - 'field', - this.name || null, - oldValue, - localValue, - ), - ); - } - if (this.isDirty_) { - this.forceRerender(); + const oldValue = this.getValue(); + if (oldValue === localValue) { + if (doLogging) console.log('same, doValueUpdate_, return'); + this.doValueUpdate_(localValue); + return; + } + + this.doValueUpdate_(localValue); + if (fireChangeEvent && source && eventUtils.isEnabled()) { + eventUtils.fire( + new (eventUtils.get(EventType.BLOCK_CHANGE))( + source, + 'field', + this.name || null, + oldValue, + localValue, + ), + ); + } + if (this.isDirty_) { + this.forceRerender(); + } + if (doLogging) console.log(this.value_); + } finally { + eventUtils.setGroup(existingGroup); } - if (doLogging) console.log(this.value_); } /** diff --git a/tests/mocha/jso_serialization_test.js b/tests/mocha/jso_serialization_test.js index 7e68edb989b..7cf415e676a 100644 --- a/tests/mocha/jso_serialization_test.js +++ b/tests/mocha/jso_serialization_test.js @@ -533,7 +533,7 @@ suite('JSO Serialization', function () { }, 'block': { 'type': 'text', - 'id': 'id3', + 'id': 'id4', 'fields': { 'TEXT': '', }, From e5a2e6262282afc3f8c191d0bd863d7853bd952b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 25 Sep 2024 17:29:14 +0100 Subject: [PATCH 13/32] chore(deps): bump @typescript-eslint/eslint-plugin from 8.4.0 to 8.6.0 (#8584) Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 8.4.0 to 8.6.0. - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.6.0/packages/eslint-plugin) --- updated-dependencies: - dependency-name: "@typescript-eslint/eslint-plugin" 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 | 88 +++++++++++++++++++++++------------------------ 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/package-lock.json b/package-lock.json index bafb98ef327..ba35c504a15 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1240,16 +1240,16 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.4.0.tgz", - "integrity": "sha512-rg8LGdv7ri3oAlenMACk9e+AR4wUV0yrrG+XKsGKOK0EVgeEDqurkXMPILG2836fW4ibokTB5v4b6Z9+GYQDEw==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.6.0.tgz", + "integrity": "sha512-UOaz/wFowmoh2G6Mr9gw60B1mm0MzUtm6Ic8G2yM1Le6gyj5Loi/N+O5mocugRGY+8OeeKmkMmbxNqUCq3B4Sg==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.4.0", - "@typescript-eslint/type-utils": "8.4.0", - "@typescript-eslint/utils": "8.4.0", - "@typescript-eslint/visitor-keys": "8.4.0", + "@typescript-eslint/scope-manager": "8.6.0", + "@typescript-eslint/type-utils": "8.6.0", + "@typescript-eslint/utils": "8.6.0", + "@typescript-eslint/visitor-keys": "8.6.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -1412,13 +1412,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.4.0.tgz", - "integrity": "sha512-n2jFxLeY0JmKfUqy3P70rs6vdoPjHK8P/w+zJcV3fk0b0BwRXC/zxRTEnAsgYT7MwdQDt/ZEbtdzdVC+hcpF0A==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.6.0.tgz", + "integrity": "sha512-ZuoutoS5y9UOxKvpc/GkvF4cuEmpokda4wRg64JEia27wX+PysIE9q+lzDtlHHgblwUWwo5/Qn+/WyTUvDwBHw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.4.0", - "@typescript-eslint/visitor-keys": "8.4.0" + "@typescript-eslint/types": "8.6.0", + "@typescript-eslint/visitor-keys": "8.6.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1429,9 +1429,9 @@ } }, "node_modules/@typescript-eslint/scope-manager/node_modules/@typescript-eslint/types": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.4.0.tgz", - "integrity": "sha512-T1RB3KQdskh9t3v/qv7niK6P8yvn7ja1mS7QK7XfRVL6wtZ8/mFs/FHf4fKvTA0rKnqnYxl/uHFNbnEt0phgbw==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.6.0.tgz", + "integrity": "sha512-rojqFZGd4MQxw33SrOy09qIDS8WEldM8JWtKQLAjf/X5mGSeEFh5ixQlxssMNyPslVIk9yzWqXCsV2eFhYrYUw==", "dev": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1442,13 +1442,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.4.0.tgz", - "integrity": "sha512-pu2PAmNrl9KX6TtirVOrbLPLwDmASpZhK/XU7WvoKoCUkdtq9zF7qQ7gna0GBZFN0hci0vHaSusiL2WpsQk37A==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.6.0.tgz", + "integrity": "sha512-dtePl4gsuenXVwC7dVNlb4mGDcKjDT/Ropsk4za/ouMBPplCLyznIaR+W65mvCvsyS97dymoBRrioEXI7k0XIg==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "8.4.0", - "@typescript-eslint/utils": "8.4.0", + "@typescript-eslint/typescript-estree": "8.6.0", + "@typescript-eslint/utils": "8.6.0", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, @@ -1480,13 +1480,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.4.0.tgz", - "integrity": "sha512-kJ2OIP4dQw5gdI4uXsaxUZHRwWAGpREJ9Zq6D5L0BweyOrWsL6Sz0YcAZGWhvKnH7fm1J5YFE1JrQL0c9dd53A==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.6.0.tgz", + "integrity": "sha512-MOVAzsKJIPIlLK239l5s06YXjNqpKTVhBVDnqUumQJja5+Y94V3+4VUFRA0G60y2jNnTVwRCkhyGQpavfsbq/g==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.4.0", - "@typescript-eslint/visitor-keys": "8.4.0", + "@typescript-eslint/types": "8.6.0", + "@typescript-eslint/visitor-keys": "8.6.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -1508,9 +1508,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/types": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.4.0.tgz", - "integrity": "sha512-T1RB3KQdskh9t3v/qv7niK6P8yvn7ja1mS7QK7XfRVL6wtZ8/mFs/FHf4fKvTA0rKnqnYxl/uHFNbnEt0phgbw==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.6.0.tgz", + "integrity": "sha512-rojqFZGd4MQxw33SrOy09qIDS8WEldM8JWtKQLAjf/X5mGSeEFh5ixQlxssMNyPslVIk9yzWqXCsV2eFhYrYUw==", "dev": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1557,15 +1557,15 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.4.0.tgz", - "integrity": "sha512-swULW8n1IKLjRAgciCkTCafyTHHfwVQFt8DovmaF69sKbOxTSFMmIZaSHjqO9i/RV0wIblaawhzvtva8Nmm7lQ==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.6.0.tgz", + "integrity": "sha512-eNp9cWnYf36NaOVjkEUznf6fEgVy1TWpE0o52e4wtojjBx7D1UV2WAWGzR+8Y5lVFtpMLPwNbC67T83DWSph4A==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.4.0", - "@typescript-eslint/types": "8.4.0", - "@typescript-eslint/typescript-estree": "8.4.0" + "@typescript-eslint/scope-manager": "8.6.0", + "@typescript-eslint/types": "8.6.0", + "@typescript-eslint/typescript-estree": "8.6.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1579,9 +1579,9 @@ } }, "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.4.0.tgz", - "integrity": "sha512-T1RB3KQdskh9t3v/qv7niK6P8yvn7ja1mS7QK7XfRVL6wtZ8/mFs/FHf4fKvTA0rKnqnYxl/uHFNbnEt0phgbw==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.6.0.tgz", + "integrity": "sha512-rojqFZGd4MQxw33SrOy09qIDS8WEldM8JWtKQLAjf/X5mGSeEFh5ixQlxssMNyPslVIk9yzWqXCsV2eFhYrYUw==", "dev": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1592,12 +1592,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.4.0.tgz", - "integrity": "sha512-zTQD6WLNTre1hj5wp09nBIDiOc2U5r/qmzo7wxPn4ZgAjHql09EofqhF9WF+fZHzL5aCyaIpPcT2hyxl73kr9A==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.6.0.tgz", + "integrity": "sha512-wapVFfZg9H0qOYh4grNVQiMklJGluQrOUiOhYRrQWhx7BY/+I1IYb8BczWNbbUpO+pqy0rDciv3lQH5E1bCLrg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.4.0", + "@typescript-eslint/types": "8.6.0", "eslint-visitor-keys": "^3.4.3" }, "engines": { @@ -1609,9 +1609,9 @@ } }, "node_modules/@typescript-eslint/visitor-keys/node_modules/@typescript-eslint/types": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.4.0.tgz", - "integrity": "sha512-T1RB3KQdskh9t3v/qv7niK6P8yvn7ja1mS7QK7XfRVL6wtZ8/mFs/FHf4fKvTA0rKnqnYxl/uHFNbnEt0phgbw==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.6.0.tgz", + "integrity": "sha512-rojqFZGd4MQxw33SrOy09qIDS8WEldM8JWtKQLAjf/X5mGSeEFh5ixQlxssMNyPslVIk9yzWqXCsV2eFhYrYUw==", "dev": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" From f7a2c4dcd06c1a84a251f7f94b788ddfcced922b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 25 Sep 2024 17:33:25 +0100 Subject: [PATCH 14/32] chore(deps): bump webdriverio from 9.0.7 to 9.0.9 (#8582) Bumps [webdriverio](https://github.com/webdriverio/webdriverio/tree/HEAD/packages/webdriverio) from 9.0.7 to 9.0.9. - [Release notes](https://github.com/webdriverio/webdriverio/releases) - [Changelog](https://github.com/webdriverio/webdriverio/blob/main/CHANGELOG.md) - [Commits](https://github.com/webdriverio/webdriverio/commits/v9.0.9/packages/webdriverio) --- updated-dependencies: - dependency-name: webdriverio 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 | 167 +++++++++++++++++++++++----------------------- 1 file changed, 84 insertions(+), 83 deletions(-) diff --git a/package-lock.json b/package-lock.json index ba35c504a15..c838c1c2ea1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -937,9 +937,9 @@ } }, "node_modules/@promptbook/utils": { - "version": "0.68.0", - "resolved": "https://registry.npmjs.org/@promptbook/utils/-/utils-0.68.0.tgz", - "integrity": "sha512-EaV8YtUrbFLAjwOx9JcJqnfSiF+dm4kLrB2umzVJn/yEFMIOoC0GTWz1mX328HSA5cBvqC7+SBeWubplL9THcg==", + "version": "0.70.0-1", + "resolved": "https://registry.npmjs.org/@promptbook/utils/-/utils-0.70.0-1.tgz", + "integrity": "sha512-qd2lLRRN+sE6UuNMi2tEeUUeb4zmXnxY5EMdfHVXNE+bqBDpUC7/aEfXgA3jnUXEr+xFjQ8PTFQgWvBMaKvw0g==", "dev": true, "funding": [ { @@ -956,9 +956,9 @@ } }, "node_modules/@puppeteer/browsers": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.3.1.tgz", - "integrity": "sha512-uK7o3hHkK+naEobMSJ+2ySYyXtQkBxIH8Gn4MK9ciePjNV+Pf+PgY/W7iPzn2MTjl3stcYB5AlcTmPYw7AXDwA==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.4.0.tgz", + "integrity": "sha512-x8J1csfIygOwf6D6qUAZ0ASk3z63zPb7wkNeHRerCMh82qWKUrOgkuP005AJC8lDL6/evtXETGEJVcwykKT4/g==", "dev": true, "dependencies": { "debug": "^4.3.6", @@ -1628,27 +1628,27 @@ "dev": true }, "node_modules/@wdio/config": { - "version": "9.0.6", - "resolved": "https://registry.npmjs.org/@wdio/config/-/config-9.0.6.tgz", - "integrity": "sha512-WsACM5QjT3ZsoPVqHroYt8pOkZx4/6PTdNKm45VL8NHhQe5w9IFbl1fKxFHQ7ZkPI3F+EFvFvubO8puJ0OcSmQ==", + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@wdio/config/-/config-9.0.8.tgz", + "integrity": "sha512-37L+hd+A1Nyehd/pgfTrLC6w+Ngbu0CIoFh9Vv6v8Cgu5Hih0TLofvlg+J1BNbcTd5eQ2tFKZBDeFMhQaIiTpg==", "dev": true, "dependencies": { - "@wdio/logger": "9.0.4", - "@wdio/types": "9.0.4", - "@wdio/utils": "9.0.6", + "@wdio/logger": "9.0.8", + "@wdio/types": "9.0.8", + "@wdio/utils": "9.0.8", "decamelize": "^6.0.0", "deepmerge-ts": "^7.0.3", "glob": "^10.2.2", "import-meta-resolve": "^4.0.0" }, "engines": { - "node": ">=18" + "node": ">=18.20.0" } }, "node_modules/@wdio/logger": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.0.4.tgz", - "integrity": "sha512-b6gcu0PTVb3fgK4kyAH/k5UUWN5FOUdAfhA4PAY/IZvxZTMFYMqnrZb0WRWWWqL6nu9pcrOVtCOdPBvj0cb+Nw==", + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.0.8.tgz", + "integrity": "sha512-uIyYIDBwLczmsp9JE5hN3ME8Xg+9WNBfSNXD69ICHrY9WPTzFf94UeTuavK7kwSKF3ro2eJbmNZItYOfnoovnw==", "dev": true, "dependencies": { "chalk": "^5.1.2", @@ -1657,7 +1657,7 @@ "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=18" + "node": ">=18.20.0" } }, "node_modules/@wdio/logger/node_modules/chalk": { @@ -1688,44 +1688,44 @@ } }, "node_modules/@wdio/protocols": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-9.0.4.tgz", - "integrity": "sha512-T9v8Jkp94NxLLil5J7uJ/+YHk5/7fhOggzGcN+LvuCHS6jbJFZ/11c4SUEuXw27Yqk01fFXPBbF6TwcTTOuW/Q==", + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-9.0.8.tgz", + "integrity": "sha512-xRH54byFf623/w/KW62xkf/C2mGyigSfMm+UT3tNEAd5ZA9X2VAWQWQBPzdcrsck7Fxk4zlQX8Kb34RSs7Cy4Q==", "dev": true }, "node_modules/@wdio/repl": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-9.0.4.tgz", - "integrity": "sha512-5Bc5ARjWA7t6MZNnVJ9WvO1iDsy6iOsrSDWiP7APWAdaF/SJCP3SFE2c+PdQJpJWhr2Kk0fRGuyDM+GdsmZhwg==", + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-9.0.8.tgz", + "integrity": "sha512-3iubjl4JX5zD21aFxZwQghqC3lgu+mSs8c3NaiYYNCC+IT5cI/8QuKlgh9s59bu+N3gG988jqMJeCYlKuUv/iw==", "dev": true, "dependencies": { "@types/node": "^20.1.0" }, "engines": { - "node": ">=18" + "node": ">=18.20.0" } }, "node_modules/@wdio/types": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.0.4.tgz", - "integrity": "sha512-MN7O4Uk3zPWvkN8d6SNdIjd7qHUlTxS7j0QfRPu6TdlYbHu6BJJ8Rr84y7GcUzCnTAJ1nOIpvUyR8MY3hOaVKg==", + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.0.8.tgz", + "integrity": "sha512-pmz2iRWddTanrv8JC7v3wUGm17KRv2WyyJhQfklMSANn9V1ep6pw1RJG2WJnKq4NojMvH1nVv1sMZxXrYPhpYw==", "dev": true, "dependencies": { "@types/node": "^20.1.0" }, "engines": { - "node": ">=18" + "node": ">=18.20.0" } }, "node_modules/@wdio/utils": { - "version": "9.0.6", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.0.6.tgz", - "integrity": "sha512-cnPXeW/sfqyKFuRRmADRZDNvFwEBMr7j7wwWLO6q5opoW++dwOdJW4WV0wDZbPcXTtGFCSrGCDLLdGcTAWMb3A==", + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.0.8.tgz", + "integrity": "sha512-p3EgOdkhCvMxJFd3WTtSChqYFQu2mz69/5tOsljDaL+4QYwnRR7O8M9wFsL3/9XMVcHdnC4Ija2VRxQ/lb+hHQ==", "dev": true, "dependencies": { "@puppeteer/browsers": "^2.2.0", - "@wdio/logger": "9.0.4", - "@wdio/types": "9.0.4", + "@wdio/logger": "9.0.8", + "@wdio/types": "9.0.8", "decamelize": "^6.0.0", "deepmerge-ts": "^7.0.3", "edgedriver": "^5.6.1", @@ -1738,7 +1738,7 @@ "wait-port": "^1.1.0" }, "engines": { - "node": ">=18" + "node": ">=18.20.0" } }, "node_modules/@yarnpkg/lockfile": { @@ -2285,9 +2285,9 @@ } }, "node_modules/b4a": { - "version": "1.6.4", - "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.4.tgz", - "integrity": "sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw==", + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.6.tgz", + "integrity": "sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==", "dev": true }, "node_modules/bach": { @@ -2318,9 +2318,9 @@ "optional": true }, "node_modules/bare-fs": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-2.3.3.tgz", - "integrity": "sha512-7RYKL+vZVCyAsMLi5SPu7QGauGGT8avnP/HO571ndEuV4MYdGXvLhtW67FuLPeEI8EiIY7zbbRR9x7x7HU0kgw==", + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-2.3.5.tgz", + "integrity": "sha512-SlE9eTxifPDJrT6YgemQ1WGFleevzwY+XAP1Xqgl56HtcrisC2CHCZ2tq6dBpcH2TnNxwUEUGhweo+lrQtYuiw==", "dev": true, "optional": true, "dependencies": { @@ -2330,9 +2330,9 @@ } }, "node_modules/bare-os": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-2.4.2.tgz", - "integrity": "sha512-HZoJwzC+rZ9lqEemTMiO0luOePoGYNBgsLLgegKR/cljiJvcDNhDZQkzC+NC5Oh0aHbdBNSOHpghwMuB5tqhjg==", + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-2.4.4.tgz", + "integrity": "sha512-z3UiI2yi1mK0sXeRdc4O1Kk8aOa/e+FNWZcTiPB/dfTWyLypuE99LibgRaQki914Jq//yAWylcAt+mknKdixRQ==", "dev": true, "optional": true }, @@ -2347,13 +2347,14 @@ } }, "node_modules/bare-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.2.0.tgz", - "integrity": "sha512-+o9MG5bPRRBlkVSpfFlMag3n7wMaIZb4YZasU2+/96f+3HTQ4F9DKQeu3K/Sjz1W0umu6xvVq1ON0ipWdMlr3A==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.3.0.tgz", + "integrity": "sha512-pVRWciewGUeCyKEuRxwv06M079r+fRjAQjBEK2P6OYGrO43O+Z0LrPZZEjlc4mB6C2RpZ9AxJ1s7NLEtOHO6eA==", "dev": true, "optional": true, "dependencies": { - "streamx": "^2.18.0" + "b4a": "^1.6.6", + "streamx": "^2.20.0" } }, "node_modules/base64-js": { @@ -4135,9 +4136,9 @@ "dev": true }, "node_modules/fast-xml-parser": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", - "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.0.tgz", + "integrity": "sha512-/PlTQCI96+fZMAOLMZK4CWG1ItCbfZ/0jx7UIJFChPNrx7tcEgerUgWbeieCM9MfHInUDyK8DWYZ+YrywDJuTg==", "dev": true, "funding": [ { @@ -6234,9 +6235,9 @@ } }, "node_modules/locate-app": { - "version": "2.4.35", - "resolved": "https://registry.npmjs.org/locate-app/-/locate-app-2.4.35.tgz", - "integrity": "sha512-LmAKEZ5UR4yja7YTYyapV1eNG3Yc/W9N28xTX5tzOJ68NyWs4Laf6wmH3l8wST7T4kaaYaDqdW1UQ+5nOFVBpw==", + "version": "2.4.43", + "resolved": "https://registry.npmjs.org/locate-app/-/locate-app-2.4.43.tgz", + "integrity": "sha512-BX6NEdECUGcDQw8aqqg02qLyF9rF8V+dAfyAnBzL2AofIlIvf4Q6EGXnzVWpWot9uBE+x/o8CjXHo7Zlegu91Q==", "dev": true, "funding": [ { @@ -6249,7 +6250,7 @@ } ], "dependencies": { - "@promptbook/utils": "0.68.0", + "@promptbook/utils": "0.70.0-1", "type-fest": "2.13.0", "userhome": "1.0.0" } @@ -6347,9 +6348,9 @@ } }, "node_modules/loglevel": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.1.tgz", - "integrity": "sha512-hP3I3kCrDIMuRwAwHltphhDM1r8i55H33GgqjXbrisuJhF4kRhW1dNuxsRklp4bXl8DSdLaNLuiL4A/LWRfxvg==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.2.tgz", + "integrity": "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==", "dev": true, "engines": { "node": ">= 0.6.0" @@ -7452,9 +7453,9 @@ "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" }, "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", + "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", "dev": true, "dependencies": { "end-of-stream": "^1.1.0", @@ -8166,9 +8167,9 @@ "dev": true }, "node_modules/streamx": { - "version": "2.18.0", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.18.0.tgz", - "integrity": "sha512-LLUC1TWdjVdn1weXGcSxyTR3T4+acB6tVGXT95y0nGbca4t4o/ng1wKAGTljm9VicuCVLvRlqFYXYy5GwgM7sQ==", + "version": "2.20.1", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.20.1.tgz", + "integrity": "sha512-uTa0mU6WUC65iUvzKH4X9hEdvSW7rbPxPtwfWiLMSj3qTdQbAiUboZTxauKfpFuGIGa1C2BYijZ7wgdUXICJhA==", "dev": true, "dependencies": { "fast-fifo": "^1.3.2", @@ -8937,39 +8938,39 @@ } }, "node_modules/webdriver": { - "version": "9.0.7", - "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-9.0.7.tgz", - "integrity": "sha512-0PN4omqCGlgi3RG0LyrQXr0RUmlDCenNtpN+dfzikfYoV+CHiCw2GMnZp2XCuYUnU01MaCKgRQxLuGobyZov+A==", + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-9.0.8.tgz", + "integrity": "sha512-UnV0ANriSTUgypGk0pz8lApeQuHt+72WEDQG5hFwkkSvggtKLyWdT7+PQkNoXvDajTmiLIqUOq8XPI/Pm71rtw==", "dev": true, "dependencies": { "@types/node": "^20.1.0", "@types/ws": "^8.5.3", - "@wdio/config": "9.0.6", - "@wdio/logger": "9.0.4", - "@wdio/protocols": "9.0.4", - "@wdio/types": "9.0.4", - "@wdio/utils": "9.0.6", + "@wdio/config": "9.0.8", + "@wdio/logger": "9.0.8", + "@wdio/protocols": "9.0.8", + "@wdio/types": "9.0.8", + "@wdio/utils": "9.0.8", "deepmerge-ts": "^7.0.3", "ws": "^8.8.0" }, "engines": { - "node": ">=18" + "node": ">=18.20.0" } }, "node_modules/webdriverio": { - "version": "9.0.7", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.0.7.tgz", - "integrity": "sha512-/6CvJkKpUWYbX/59PNJCHXGLPwulQ/bXZwlIUrsF6MWKdf8Eb6yTaXkMJBaBy5x496b50PQcXkbe+qpfsnqXsg==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.0.9.tgz", + "integrity": "sha512-IwvKzhcJ9NjOL55xwj27uTTKkfxsg77dmAfqoKFSP5dQ70JzU+NgxiALEjjWQDybtt1yGIkHk7wjjxjboMU1uw==", "dev": true, "dependencies": { "@types/node": "^20.11.30", "@types/sinonjs__fake-timers": "^8.1.5", - "@wdio/config": "9.0.6", - "@wdio/logger": "9.0.4", - "@wdio/protocols": "9.0.4", - "@wdio/repl": "9.0.4", - "@wdio/types": "9.0.4", - "@wdio/utils": "9.0.6", + "@wdio/config": "9.0.8", + "@wdio/logger": "9.0.8", + "@wdio/protocols": "9.0.8", + "@wdio/repl": "9.0.8", + "@wdio/types": "9.0.8", + "@wdio/utils": "9.0.8", "archiver": "^7.0.1", "aria-query": "^5.3.0", "cheerio": "^1.0.0-rc.12", @@ -8988,10 +8989,10 @@ "rgb2hex": "0.2.5", "serialize-error": "^11.0.3", "urlpattern-polyfill": "^10.0.0", - "webdriver": "9.0.7" + "webdriver": "9.0.8" }, "engines": { - "node": ">=18" + "node": ">=18.20.0" }, "peerDependencies": { "puppeteer-core": "^22.3.0" From 5fd337bb4d12b68766cae8094893ed0131285666 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Sep 2024 10:56:31 -0700 Subject: [PATCH 15/32] chore(deps): bump concurrently from 8.2.2 to 9.0.1 (#8602) Bumps [concurrently](https://github.com/open-cli-tools/concurrently) from 8.2.2 to 9.0.1. - [Release notes](https://github.com/open-cli-tools/concurrently/releases) - [Commits](https://github.com/open-cli-tools/concurrently/compare/v8.2.2...v9.0.1) --- updated-dependencies: - dependency-name: concurrently 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 | 52 +++++------------------------------------------ package.json | 2 +- 2 files changed, 6 insertions(+), 48 deletions(-) diff --git a/package-lock.json b/package-lock.json index c838c1c2ea1..1b9f4b50483 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,7 @@ "@typescript-eslint/parser": "^8.1.0", "async-done": "^2.0.0", "chai": "^5.1.1", - "concurrently": "^8.0.1", + "concurrently": "^9.0.1", "eslint": "^8.4.1", "eslint-config-google": "^0.14.0", "eslint-config-prettier": "^9.0.0", @@ -68,18 +68,6 @@ "node": ">=0.10.0" } }, - "node_modules/@babel/runtime": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.5.tgz", - "integrity": "sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==", - "dev": true, - "dependencies": { - "regenerator-runtime": "^0.13.11" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@blockly/block-test": { "version": "6.0.8", "resolved": "https://registry.npmjs.org/@blockly/block-test/-/block-test-6.0.8.tgz", @@ -2927,17 +2915,15 @@ } }, "node_modules/concurrently": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-8.2.2.tgz", - "integrity": "sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.0.1.tgz", + "integrity": "sha512-wYKvCd/f54sTXJMSfV6Ln/B8UrfLBKOYa+lzc6CHay3Qek+LorVSBdMVfyewFhRbH0Rbabsk4D+3PL/VjQ5gzg==", "dev": true, "dependencies": { "chalk": "^4.1.2", - "date-fns": "^2.30.0", "lodash": "^4.17.21", "rxjs": "^7.8.1", "shell-quote": "^1.8.1", - "spawn-command": "0.0.2", "supports-color": "^8.1.1", "tree-kill": "^1.2.2", "yargs": "^17.7.2" @@ -2947,7 +2933,7 @@ "concurrently": "dist/bin/concurrently.js" }, "engines": { - "node": "^14.13.0 || >=16.0.0" + "node": ">=18" }, "funding": { "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" @@ -3242,22 +3228,6 @@ "node": ">=18" } }, - "node_modules/date-fns": { - "version": "2.30.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", - "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.21.0" - }, - "engines": { - "node": ">=0.11" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/date-fns" - } - }, "node_modules/debug": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", @@ -7602,12 +7572,6 @@ "node": ">= 10.13.0" } }, - "node_modules/regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", - "dev": true - }, "node_modules/remove-trailing-separator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", @@ -8073,12 +8037,6 @@ "node": ">= 10.13.0" } }, - "node_modules/spawn-command": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz", - "integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==", - "dev": true - }, "node_modules/spdx-exceptions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", diff --git a/package.json b/package.json index 2ec843f3d60..4842951823f 100644 --- a/package.json +++ b/package.json @@ -111,7 +111,7 @@ "@typescript-eslint/parser": "^8.1.0", "async-done": "^2.0.0", "chai": "^5.1.1", - "concurrently": "^8.0.1", + "concurrently": "^9.0.1", "eslint": "^8.4.1", "eslint-config-google": "^0.14.0", "eslint-config-prettier": "^9.0.0", From 9b3603a3ea68ebf0b3632bc160170aeef7e59849 Mon Sep 17 00:00:00 2001 From: John Nesky Date: Tue, 1 Oct 2024 17:00:59 -0700 Subject: [PATCH 16/32] fix: Let block factory overwrite user defined blocks. (#8605) --- .../blockfactory/block_library_controller.js | 4 +-- demos/blockfactory/block_library_view.js | 26 +++++++++---------- demos/blockfactory/blocks.js | 4 +++ demos/blockfactory/factory.js | 5 ++-- 4 files changed, 22 insertions(+), 17 deletions(-) diff --git a/demos/blockfactory/block_library_controller.js b/demos/blockfactory/block_library_controller.js index 7bb34e8d623..8eed54db02c 100644 --- a/demos/blockfactory/block_library_controller.js +++ b/demos/blockfactory/block_library_controller.js @@ -109,9 +109,9 @@ BlockLibraryController.prototype.clearBlockLibrary = function() { BlockLibraryController.prototype.saveToBlockLibrary = function() { var blockType = this.getCurrentBlockType(); // If user has not changed the name of the starter block. - if (blockType === 'block_type') { + if (reservedBlockFactoryBlocks.has(blockType) || blockType === 'block_type') { // Do not save block if it has the default type, 'block_type'. - var msg = 'You cannot save a block under the name "block_type". Try ' + + var msg = `You cannot save a block under the name "${blockType}". Try ` + 'changing the name before saving. Then, click on the "Block Library"' + ' button to view your saved blocks.'; alert(msg); diff --git a/demos/blockfactory/block_library_view.js b/demos/blockfactory/block_library_view.js index a479804080b..2c91ce3782e 100644 --- a/demos/blockfactory/block_library_view.js +++ b/demos/blockfactory/block_library_view.js @@ -104,36 +104,36 @@ BlockLibraryView.prototype.updateButtons = // User is editing a block. if (!isInLibrary) { - // Block type has not been saved to library yet. Disable the delete button - // and allow user to save. + // Block type has not been saved to the library yet. + // Disable the delete button. this.saveButton.textContent = 'Save "' + blockType + '"'; - this.saveButton.disabled = false; this.deleteButton.disabled = true; } else { - // Block type has already been saved. Disable the save button unless the - // there are unsaved changes (checked below). + // A version of the block type has already been saved. + // Enable the delete button. this.saveButton.textContent = 'Update "' + blockType + '"'; - this.saveButton.disabled = true; this.deleteButton.disabled = false; } this.deleteButton.textContent = 'Delete "' + blockType + '"'; - // If changes to block have been made and are not saved, make button - // green to encourage user to save the block. + this.saveButton.classList.remove('button_alert', 'button_warn'); if (!savedChanges) { - var buttonFormatClass = 'button_warn'; + var buttonFormatClass; - // If block type is the default, 'block_type', make button red to alert - // user. - if (blockType === 'block_type') { + var isReserved = reservedBlockFactoryBlocks.has(blockType); + if (isReserved || blockType === 'block_type') { + // Make button red to alert user that the block type can't be saved. buttonFormatClass = 'button_alert'; + } else { + // Block type has not been saved to library yet or has unsaved changes. + // Make the button green to encourage the user to save the block. + buttonFormatClass = 'button_warn'; } this.saveButton.classList.add(buttonFormatClass); this.saveButton.disabled = false; } else { // No changes to save. - this.saveButton.classList.remove('button_alert', 'button_warn'); this.saveButton.disabled = true; } diff --git a/demos/blockfactory/blocks.js b/demos/blockfactory/blocks.js index 34313cad14f..9a983460f5f 100644 --- a/demos/blockfactory/blocks.js +++ b/demos/blockfactory/blocks.js @@ -914,3 +914,7 @@ function inputNameCheck(referenceBlock) { 'There are ' + count + ' input blocks\n with this name.' : null; referenceBlock.setWarningText(msg); } + +// Make a set of all of block types that are required for the block factory. +var reservedBlockFactoryBlocks = + new Set(Object.getOwnPropertyNames(Blockly.Blocks)); diff --git a/demos/blockfactory/factory.js b/demos/blockfactory/factory.js index 07ee889de7f..2e6ebc924e1 100644 --- a/demos/blockfactory/factory.js +++ b/demos/blockfactory/factory.js @@ -187,8 +187,9 @@ BlockFactory.updatePreview = function() { // Don't let the user create a block type that already exists, // because it doesn't work. var warnExistingBlock = function(blockType) { - if (blockType in Blockly.Blocks) { - var text = `You can't make a block called ${blockType} in this tool because that name already exists.`; + if (reservedBlockFactoryBlocks.has(blockType)) { + var text = `You can't make a block called ${blockType} in this tool ` + + `because that name is reserved.`; FactoryUtils.getRootBlock(BlockFactory.mainWorkspace).setWarningText(text); console.error(text); return true; From 9cd58e325a90a6ab22f93dbdd14a7a7162366944 Mon Sep 17 00:00:00 2001 From: Christopher Allen Date: Wed, 2 Oct 2024 12:30:19 +0100 Subject: [PATCH 17/32] refactor(shortcuts): Improve shortcut registry documentation & style (#8598) * refactor(shortcuts): Improve shortcut registry documentation & style Make some minor refactorings of shortcut_registry.ts to improve readability and style, and add documentation for the KeyboardShortcut interface type. * docs(shortcuts): Fix JSDoc typos etc. for PR #8598 * chore: Format --- core/shortcut_registry.ts | 131 ++++++++++++++++++++++++++++---------- 1 file changed, 98 insertions(+), 33 deletions(-) diff --git a/core/shortcut_registry.ts b/core/shortcut_registry.ts index 161a2ed1495..32615c86686 100644 --- a/core/shortcut_registry.ts +++ b/core/shortcut_registry.ts @@ -45,31 +45,27 @@ export class ShortcutRegistry { * Registers a keyboard shortcut. * * @param shortcut The shortcut for this key code. - * @param opt_allowOverrides True to prevent a warning when overriding an + * @param allowOverrides True to prevent a warning when overriding an * already registered item. * @throws {Error} if a shortcut with the same name already exists. */ - register(shortcut: KeyboardShortcut, opt_allowOverrides?: boolean) { + register(shortcut: KeyboardShortcut, allowOverrides?: boolean) { const registeredShortcut = this.shortcuts.get(shortcut.name); - if (registeredShortcut && !opt_allowOverrides) { + if (registeredShortcut && !allowOverrides) { throw new Error(`Shortcut named "${shortcut.name}" already exists.`); } this.shortcuts.set(shortcut.name, shortcut); const keyCodes = shortcut.keyCodes; - if (keyCodes && keyCodes.length > 0) { - for (let i = 0; i < keyCodes.length; i++) { - this.addKeyMapping( - keyCodes[i], - shortcut.name, - !!shortcut.allowCollision, - ); + if (keyCodes?.length) { + for (const keyCode of keyCodes) { + this.addKeyMapping(keyCode, shortcut.name, !!shortcut.allowCollision); } } } /** - * Unregisters a keyboard shortcut registered with the given key code. This + * Unregisters a keyboard shortcut registered with the given name. This * will also remove any key mappings that reference this shortcut. * * @param shortcutName The name of the shortcut to unregister. @@ -92,27 +88,34 @@ export class ShortcutRegistry { /** * Adds a mapping between a keycode and a keyboard shortcut. * + * Normally only one shortcut can be mapped to any given keycode, + * but setting allowCollisions to true allows a keyboard to be + * mapped to multiple shortcuts. In that case, when onKeyDown is + * called with the given keystroke, it will process the mapped + * shortcuts in reverse order, from the most- to least-recently + * mapped). + * * @param keyCode The key code for the keyboard shortcut. If registering a key * code with a modifier (ex: ctrl+c) use * ShortcutRegistry.registry.createSerializedKey; * @param shortcutName The name of the shortcut to execute when the given * keycode is pressed. - * @param opt_allowCollision True to prevent an error when adding a shortcut + * @param allowCollision True to prevent an error when adding a shortcut * to a key that is already mapped to a shortcut. * @throws {Error} if the given key code is already mapped to a shortcut. */ addKeyMapping( keyCode: string | number | KeyCodes, shortcutName: string, - opt_allowCollision?: boolean, + allowCollision?: boolean, ) { keyCode = `${keyCode}`; const shortcutNames = this.keyMap.get(keyCode); - if (shortcutNames && !opt_allowCollision) { + if (shortcutNames && !allowCollision) { throw new Error( `Shortcut named "${shortcutName}" collides with shortcuts "${shortcutNames}"`, ); - } else if (shortcutNames && opt_allowCollision) { + } else if (shortcutNames && allowCollision) { shortcutNames.unshift(shortcutName); } else { this.keyMap.set(keyCode, [shortcutName]); @@ -127,19 +130,19 @@ export class ShortcutRegistry { * ShortcutRegistry.registry.createSerializedKey; * @param shortcutName The name of the shortcut to execute when the given * keycode is pressed. - * @param opt_quiet True to not console warn when there is no shortcut to + * @param quiet True to not console warn when there is no shortcut to * remove. * @returns True if a key mapping was removed, false otherwise. */ removeKeyMapping( keyCode: string, shortcutName: string, - opt_quiet?: boolean, + quiet?: boolean, ): boolean { const shortcutNames = this.keyMap.get(keyCode); if (!shortcutNames) { - if (!opt_quiet) { + if (!quiet) { console.warn( `No keyboard shortcut named "${shortcutName}" registered with key code "${keyCode}"`, ); @@ -155,7 +158,7 @@ export class ShortcutRegistry { } return true; } - if (!opt_quiet) { + if (!quiet) { console.warn( `No keyboard shortcut named "${shortcutName}" registered with key code "${keyCode}"`, ); @@ -172,7 +175,7 @@ export class ShortcutRegistry { */ removeAllKeyMappings(shortcutName: string) { for (const keyCode of this.keyMap.keys()) { - this.removeKeyMapping(keyCode, shortcutName, true); + this.removeKeyMapping(keyCode, shortcutName, /* quiet= */ true); } } @@ -219,6 +222,21 @@ export class ShortcutRegistry { /** * Handles key down events. * + * - Any `KeyboardShortcut`(s) mapped to the keycodes that cause + * event `e` to be fired will be processed, in order from least- + * to most-recently registered. + * - If the shortcut's `preconditionFn` exists it will be called. + * If `preconditionFn` returns false the shortcut's `callback` + * function will be skipped. Processing will continue with the + * next shortcut, if any. + * - The shortcut's `callback` function will then be called. If it + * returns true, processing will terminate and `onKeyDown` will + * return true. If it returns false, processing will continue + * with with the next shortcut, if any. + * - If all registered shortcuts for the given keycode have been + * processed without any having returned true, `onKeyDown` will + * return false. + * * @param workspace The main workspace where the event was captured. * @param e The key down event. * @returns True if the event was handled, false otherwise. @@ -226,17 +244,17 @@ export class ShortcutRegistry { onKeyDown(workspace: WorkspaceSvg, e: KeyboardEvent): boolean { const key = this.serializeKeyEvent_(e); const shortcutNames = this.getShortcutNamesByKeyCode(key); - if (!shortcutNames) { - return false; - } - for (let i = 0, shortcutName; (shortcutName = shortcutNames[i]); i++) { + if (!shortcutNames) return false; + for (const shortcutName of shortcutNames) { const shortcut = this.shortcuts.get(shortcutName); - if (!shortcut?.preconditionFn || shortcut?.preconditionFn(workspace)) { - // If the key has been handled, stop processing shortcuts. - if (shortcut?.callback && shortcut?.callback(workspace, e, shortcut)) { - return true; - } + if ( + !shortcut || + (shortcut.preconditionFn && !shortcut.preconditionFn(workspace)) + ) { + continue; } + // If the key has been handled, stop processing shortcuts. + if (shortcut.callback?.(workspace, e, shortcut)) return true; } return false; } @@ -301,7 +319,7 @@ export class ShortcutRegistry { * @throws {Error} if the modifier is not in the valid modifiers list. */ private checkModifiers_(modifiers: KeyCodes[]) { - for (let i = 0, modifier; (modifier = modifiers[i]); i++) { + for (const modifier of modifiers) { if (!(modifier in ShortcutRegistry.modifierKeys)) { throw new Error(modifier + ' is not a valid modifier key.'); } @@ -313,7 +331,7 @@ export class ShortcutRegistry { * * @param keyCode Number code representing the key. * @param modifiers List of modifier key codes to be used with the key. All - * valid modifiers can be found in the ShortcutRegistry.modifierKeys. + * valid modifiers can be found in the `ShortcutRegistry.modifierKeys`. * @returns The serialized key code for the given modifiers and key. */ createSerializedKey(keyCode: number, modifiers: KeyCodes[] | null): string { @@ -344,12 +362,59 @@ export class ShortcutRegistry { } export namespace ShortcutRegistry { + /** Interface defining a keyboard shortcut. */ export interface KeyboardShortcut { - callback?: (p1: WorkspaceSvg, p2: Event, p3: KeyboardShortcut) => boolean; + /** + * The function to be called when the shorctut is invoked. + * + * @param workspace The `WorkspaceSvg` when the shortcut was + * invoked. + * @param e The event that caused the shortcut to be activated. + * @param shortcut The `KeyboardShortcut` that was activated + * (i.e., the one this callback is attached to). + * @returns Returning true ends processing of the invoked keycode. + * Returning false causes processing to continue with the + * next-most-recently registered shortcut for the invoked + * keycode. + */ + callback?: ( + workspace: WorkspaceSvg, + e: Event, + shortcut: KeyboardShortcut, + ) => boolean; + + /** The name of the shortcut. Should be unique. */ name: string; - preconditionFn?: (p1: WorkspaceSvg) => boolean; + + /** + * A function to be called when the shortcut is invoked, before + * calling `callback`, to decide if this shortcut is applicable in + * the current situation. + * + * @param workspace The `WorkspaceSvg` where the shortcut was + * invoked. + * @returns True iff `callback` function should be called. + */ + preconditionFn?: (workspace: WorkspaceSvg) => boolean; + + /** Optional arbitray extra data attached to the shortcut. */ metadata?: object; + + /** + * Optional list of key codes to be bound (via + * ShortcutRegistry.prototype.addKeyMapping) to this shortcut. + */ keyCodes?: (number | string)[]; + + /** + * Value of `allowCollision` to pass to `addKeyMapping` when + * binding this shortcut's `.keyCodes` (if any). + * + * N.B.: this is only used for binding keycodes at the time this + * shortcut is initially registered, not for any subsequent + * `addKeyMapping` calls that happen to reference this shortcut's + * name. + */ allowCollision?: boolean; } From c8c4684d3beeec305fc52f3c4a7a3882455f537c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Oct 2024 12:56:32 +0100 Subject: [PATCH 18/32] chore(deps): bump @hyperjump/json-schema from 1.9.6 to 1.9.8 (#8603) Bumps [@hyperjump/json-schema](https://github.com/hyperjump-io/json-schema) from 1.9.6 to 1.9.8. - [Commits](https://github.com/hyperjump-io/json-schema/compare/v1.9.6...v1.9.8) --- updated-dependencies: - dependency-name: "@hyperjump/json-schema" 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 1b9f4b50483..0368f230c17 100644 --- a/package-lock.json +++ b/package-lock.json @@ -488,9 +488,9 @@ } }, "node_modules/@hyperjump/json-schema": { - "version": "1.9.6", - "resolved": "https://registry.npmjs.org/@hyperjump/json-schema/-/json-schema-1.9.6.tgz", - "integrity": "sha512-tv0JLDESJCGEPR1LNDNHEdr/AV+kjmfwpsayNPnk8Qy55VPZvXpr5SpExlq8HwB9IXJp1ScfIcrcVrpaWN2Yxw==", + "version": "1.9.8", + "resolved": "https://registry.npmjs.org/@hyperjump/json-schema/-/json-schema-1.9.8.tgz", + "integrity": "sha512-qmdMpYn8CpYR7z3fxkL6fgkDvMaAEFKtmYu3XDi6hWW2BT+rLl7T4Y4QpafEIR4wkcmCxcJf9me9FmxKpv3i9g==", "dev": true, "dependencies": { "@hyperjump/json-pointer": "^1.1.0", From 51c5809ed98d6af7d5673c9c4ab40148b2d78dbe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Oct 2024 08:25:39 -0700 Subject: [PATCH 19/32] chore(deps): bump @typescript-eslint/parser from 8.5.0 to 8.8.1 (#8620) Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 8.5.0 to 8.8.1. - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.8.1/packages/parser) --- updated-dependencies: - dependency-name: "@typescript-eslint/parser" 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 | 48 +++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0368f230c17..e7901d91730 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1261,15 +1261,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.5.0.tgz", - "integrity": "sha512-gF77eNv0Xz2UJg/NbpWJ0kqAm35UMsvZf1GHj8D9MRFTj/V3tAciIWXfmPLsAAF/vUlpWPvUDyH1jjsr0cMVWw==", + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.8.1.tgz", + "integrity": "sha512-hQUVn2Lij2NAxVFEdvIGxT9gP1tq2yM83m+by3whWFsWC+1y8pxxxHUFE1UqDu2VsGi2i6RLcv4QvouM84U+ow==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "8.5.0", - "@typescript-eslint/types": "8.5.0", - "@typescript-eslint/typescript-estree": "8.5.0", - "@typescript-eslint/visitor-keys": "8.5.0", + "@typescript-eslint/scope-manager": "8.8.1", + "@typescript-eslint/types": "8.8.1", + "@typescript-eslint/typescript-estree": "8.8.1", + "@typescript-eslint/visitor-keys": "8.8.1", "debug": "^4.3.4" }, "engines": { @@ -1289,13 +1289,13 @@ } }, "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.5.0.tgz", - "integrity": "sha512-06JOQ9Qgj33yvBEx6tpC8ecP9o860rsR22hWMEd12WcTRrfaFgHr2RB/CA/B+7BMhHkXT4chg2MyboGdFGawYg==", + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.8.1.tgz", + "integrity": "sha512-X4JdU+66Mazev/J0gfXlcC/dV6JI37h+93W9BRYXrSn0hrE64IoWgVkO9MSJgEzoWkxONgaQpICWg8vAN74wlA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.5.0", - "@typescript-eslint/visitor-keys": "8.5.0" + "@typescript-eslint/types": "8.8.1", + "@typescript-eslint/visitor-keys": "8.8.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1306,9 +1306,9 @@ } }, "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.5.0.tgz", - "integrity": "sha512-qjkormnQS5wF9pjSi6q60bKUHH44j2APxfh9TQRXK8wbYVeDYYdYJGIROL87LGZZ2gz3Rbmjc736qyL8deVtdw==", + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.8.1.tgz", + "integrity": "sha512-WCcTP4SDXzMd23N27u66zTKMuEevH4uzU8C9jf0RO4E04yVHgQgW+r+TeVTNnO1KIfrL8ebgVVYYMMO3+jC55Q==", "dev": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1319,13 +1319,13 @@ } }, "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.5.0.tgz", - "integrity": "sha512-vEG2Sf9P8BPQ+d0pxdfndw3xIXaoSjliG0/Ejk7UggByZPKXmJmw3GW5jV2gHNQNawBUyfahoSiCFVov0Ruf7Q==", + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.8.1.tgz", + "integrity": "sha512-A5d1R9p+X+1js4JogdNilDuuq+EHZdsH9MjTVxXOdVFfTJXunKJR/v+fNNyO4TnoOn5HqobzfRlc70NC6HTcdg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.5.0", - "@typescript-eslint/visitor-keys": "8.5.0", + "@typescript-eslint/types": "8.8.1", + "@typescript-eslint/visitor-keys": "8.8.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -1347,12 +1347,12 @@ } }, "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.5.0.tgz", - "integrity": "sha512-yTPqMnbAZJNy2Xq2XU8AdtOW9tJIr+UQb64aXB9f3B1498Zx9JorVgFJcZpEc9UBuCCrdzKID2RGAMkYcDtZOw==", + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.8.1.tgz", + "integrity": "sha512-0/TdC3aeRAsW7MDvYRwEc1Uwm0TIBfzjPFgg60UU2Haj5qsCs9cc3zNgY71edqE3LbWfF/WoZQd3lJoDXFQpag==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.5.0", + "@typescript-eslint/types": "8.8.1", "eslint-visitor-keys": "^3.4.3" }, "engines": { From 4a0b26f0ebe998549b031a18f61d99564f340795 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Oct 2024 14:40:59 -0700 Subject: [PATCH 20/32] chore(deps): bump jsdom from 25.0.0 to 25.0.1 (#8604) Bumps [jsdom](https://github.com/jsdom/jsdom) from 25.0.0 to 25.0.1. - [Release notes](https://github.com/jsdom/jsdom/releases) - [Changelog](https://github.com/jsdom/jsdom/blob/main/Changelog.md) - [Commits](https://github.com/jsdom/jsdom/compare/25.0.0...25.0.1) --- updated-dependencies: - dependency-name: jsdom 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> --- package-lock.json | 108 +++++++++++++++++++--------------------------- package.json | 2 +- 2 files changed, 46 insertions(+), 64 deletions(-) diff --git a/package-lock.json b/package-lock.json index e7901d91730..5333addb062 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "jsdom": "25.0.0" + "jsdom": "25.0.1" }, "devDependencies": { "@blockly/block-test": "^6.0.4", @@ -3168,6 +3168,17 @@ "node": ">=0.10.0" } }, + "node_modules/cssstyle": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.1.0.tgz", + "integrity": "sha512-h66W1URKpBS5YMI/V8PyXvTMFT8SupJ1IzoIV8IeBC/ji8WVmrO8dGlTi+2dh6whmdk6BiKJLD/ZBkhWbcg6nA==", + "dependencies": { + "rrweb-cssom": "^0.7.1" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/d": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", @@ -5901,11 +5912,11 @@ } }, "node_modules/jsdom": { - "version": "25.0.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-25.0.0.tgz", - "integrity": "sha512-OhoFVT59T7aEq75TVw9xxEfkXgacpqAhQaYgP9y/fDqWQCMB/b1H66RfmPm/MaeaAIU9nDwMOVTlPN51+ao6CQ==", + "version": "25.0.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-25.0.1.tgz", + "integrity": "sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==", "dependencies": { - "cssstyle": "^4.0.1", + "cssstyle": "^4.1.0", "data-urls": "^5.0.0", "decimal.js": "^10.4.3", "form-data": "^4.0.0", @@ -5918,7 +5929,7 @@ "rrweb-cssom": "^0.7.1", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", - "tough-cookie": "^4.1.4", + "tough-cookie": "^5.0.0", "w3c-xmlserializer": "^5.0.0", "webidl-conversions": "^7.0.0", "whatwg-encoding": "^3.1.1", @@ -5939,22 +5950,6 @@ } } }, - "node_modules/jsdom/node_modules/cssstyle": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.0.1.tgz", - "integrity": "sha512-8ZYiJ3A/3OkDd093CBT/0UKDWry7ak4BdPTFP2+QEP7cmhouyq/Up709ASSj2cK02BbZiMgk7kYjZNS4QP5qrQ==", - "dependencies": { - "rrweb-cssom": "^0.6.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/jsdom/node_modules/cssstyle/node_modules/rrweb-cssom": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz", - "integrity": "sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==" - }, "node_modules/jsdom/node_modules/html-encoding-sniffer": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", @@ -5966,11 +5961,6 @@ "node": ">=18" } }, - "node_modules/jsdom/node_modules/rrweb-cssom": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz", - "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==" - }, "node_modules/jsdom/node_modules/tr46": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", @@ -7417,11 +7407,6 @@ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "dev": true }, - "node_modules/psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" - }, "node_modules/pump": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", @@ -7455,11 +7440,6 @@ "integrity": "sha512-lT5yCqEBgfoMYpf3F2xQRK7zEr1rhIIZuceDK6+xRkJQ4NMbHTwXqk4NkwDwQMNqXgG9r9fyHnzwNVs6zV5KRw==", "dev": true }, - "node_modules/querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" - }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -7652,7 +7632,8 @@ "node_modules/requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", + "dev": true }, "node_modules/resolve": { "version": "1.22.8", @@ -7751,6 +7732,11 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rrweb-cssom": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz", + "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==" + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -8411,6 +8397,22 @@ "next-tick": "1" } }, + "node_modules/tldts": { + "version": "6.1.48", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.48.tgz", + "integrity": "sha512-SPbnh1zaSzi/OsmHb1vrPNnYuwJbdWjwo5TbBYYMlTtH3/1DSb41t8bcSxkwDmmbG2q6VLPVvQc7Yf23T+1EEw==", + "dependencies": { + "tldts-core": "^6.1.48" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.48", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.48.tgz", + "integrity": "sha512-3gD9iKn/n2UuFH1uilBviK9gvTNT6iYwdqrj1Vr5mh8FuelvpRNaYVH4pNYqUgOGU4aAdL9X35eLuuj0gRsx+A==" + }, "node_modules/tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -8448,25 +8450,14 @@ } }, "node_modules/tough-cookie": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", - "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.0.0.tgz", + "integrity": "sha512-FRKsF7cz96xIIeMZ82ehjC3xW2E+O2+v11udrDYewUbszngYhsGa8z6YUMMzO9QJZzzyd0nGGXnML/TReX6W8Q==", "dependencies": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" + "tldts": "^6.1.32" }, "engines": { - "node": ">=6" - } - }, - "node_modules/tough-cookie/node_modules/universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "engines": { - "node": ">= 4.0.0" + "node": ">=16" } }, "node_modules/tree-kill": { @@ -8657,15 +8648,6 @@ "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", "dev": true }, - "node_modules/url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, "node_modules/urlpattern-polyfill": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", diff --git a/package.json b/package.json index 4842951823f..9fcdada411b 100644 --- a/package.json +++ b/package.json @@ -143,7 +143,7 @@ "yargs": "^17.2.1" }, "dependencies": { - "jsdom": "25.0.0" + "jsdom": "25.0.1" }, "engines": { "node": ">=18" From 60da7d87625080ee44093c45a56689456883d6f6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Oct 2024 14:41:13 -0700 Subject: [PATCH 21/32] chore(deps): bump @microsoft/api-extractor from 7.47.0 to 7.47.9 (#8612) Bumps [@microsoft/api-extractor](https://github.com/microsoft/rushstack/tree/HEAD/apps/api-extractor) from 7.47.0 to 7.47.9. - [Changelog](https://github.com/microsoft/rushstack/blob/main/apps/api-extractor/CHANGELOG.md) - [Commits](https://github.com/microsoft/rushstack/commits/@microsoft/api-extractor_v7.47.9/apps/api-extractor) --- updated-dependencies: - dependency-name: "@microsoft/api-extractor" 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 | 154 ++++++++-------------------------------------- 1 file changed, 26 insertions(+), 128 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5333addb062..c454c971a2b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -634,87 +634,6 @@ "api-documenter": "bin/api-documenter" } }, - "node_modules/@microsoft/api-documenter/node_modules/@microsoft/api-extractor-model": { - "version": "7.29.8", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.29.8.tgz", - "integrity": "sha512-t3Z/xcO6TRbMcnKGVMs4uMzv/gd5j0NhMiJIGjD4cJMeFJ1Hf8wnLSx37vxlRlL0GWlGJhnFgxvnaL6JlS+73g==", - "dev": true, - "dependencies": { - "@microsoft/tsdoc": "~0.15.0", - "@microsoft/tsdoc-config": "~0.17.0", - "@rushstack/node-core-library": "5.9.0" - } - }, - "node_modules/@microsoft/api-documenter/node_modules/@rushstack/node-core-library": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-5.9.0.tgz", - "integrity": "sha512-MMsshEWkTbXqxqFxD4gcIUWQOCeBChlGczdZbHfqmNZQFLHB3yWxDFSMHFUdu2/OB9NUk7Awn5qRL+rws4HQNg==", - "dev": true, - "dependencies": { - "ajv": "~8.13.0", - "ajv-draft-04": "~1.0.0", - "ajv-formats": "~3.0.1", - "fs-extra": "~7.0.1", - "import-lazy": "~4.0.0", - "jju": "~1.4.0", - "resolve": "~1.22.1", - "semver": "~7.5.4" - }, - "peerDependencies": { - "@types/node": "*" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@microsoft/api-documenter/node_modules/@rushstack/terminal": { - "version": "0.14.2", - "resolved": "https://registry.npmjs.org/@rushstack/terminal/-/terminal-0.14.2.tgz", - "integrity": "sha512-2fC1wqu1VCExKC0/L+0noVcFQEXEnoBOtCIex1TOjBzEDWcw8KzJjjj7aTP6mLxepG0XIyn9OufeFb6SFsa+sg==", - "dev": true, - "dependencies": { - "@rushstack/node-core-library": "5.9.0", - "supports-color": "~8.1.1" - }, - "peerDependencies": { - "@types/node": "*" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@microsoft/api-documenter/node_modules/@rushstack/ts-command-line": { - "version": "4.22.8", - "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.22.8.tgz", - "integrity": "sha512-XbFjOoV7qZHJnSuFUHv0pKaFA4ixyCuki+xMjsMfDwfvQjs5MYG0IK5COal3tRnG7KCDe2l/G+9LrzYE/RJhgg==", - "dev": true, - "dependencies": { - "@rushstack/terminal": "0.14.2", - "@types/argparse": "1.0.38", - "argparse": "~1.0.9", - "string-argv": "~0.3.1" - } - }, - "node_modules/@microsoft/api-documenter/node_modules/ajv": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.13.0.tgz", - "integrity": "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==", - "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/@microsoft/api-documenter/node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -737,40 +656,19 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/@microsoft/api-documenter/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/@microsoft/api-documenter/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, "node_modules/@microsoft/api-extractor": { - "version": "7.47.0", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.47.0.tgz", - "integrity": "sha512-LT8yvcWNf76EpDC+8/ArTVSYePvuDQ+YbAUrsTcpg3ptiZ93HIcMCozP/JOxDt+rrsFfFHcpfoselKfPyRI0GQ==", + "version": "7.47.9", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.47.9.tgz", + "integrity": "sha512-TTq30M1rikVsO5wZVToQT/dGyJY7UXJmjiRtkHPLb74Prx3Etw8+bX7Bv7iLuby6ysb7fuu1NFWqma+csym8Jw==", "dev": true, "dependencies": { - "@microsoft/api-extractor-model": "7.29.2", + "@microsoft/api-extractor-model": "7.29.8", "@microsoft/tsdoc": "~0.15.0", "@microsoft/tsdoc-config": "~0.17.0", - "@rushstack/node-core-library": "5.4.1", - "@rushstack/rig-package": "0.5.2", - "@rushstack/terminal": "0.13.0", - "@rushstack/ts-command-line": "4.22.0", + "@rushstack/node-core-library": "5.9.0", + "@rushstack/rig-package": "0.5.3", + "@rushstack/terminal": "0.14.2", + "@rushstack/ts-command-line": "4.22.8", "lodash": "~4.17.15", "minimatch": "~3.0.3", "resolve": "~1.22.1", @@ -783,14 +681,14 @@ } }, "node_modules/@microsoft/api-extractor-model": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.29.2.tgz", - "integrity": "sha512-hAYajOjQan3uslhKJRwvvHIdLJ+ZByKqdSsJ/dgHFxPtEbdKpzMDO8zuW4K5gkSMYl5D0LbNwxkhxr51P2zsmw==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.29.8.tgz", + "integrity": "sha512-t3Z/xcO6TRbMcnKGVMs4uMzv/gd5j0NhMiJIGjD4cJMeFJ1Hf8wnLSx37vxlRlL0GWlGJhnFgxvnaL6JlS+73g==", "dev": true, "dependencies": { "@microsoft/tsdoc": "~0.15.0", "@microsoft/tsdoc-config": "~0.17.0", - "@rushstack/node-core-library": "5.4.1" + "@rushstack/node-core-library": "5.9.0" } }, "node_modules/@microsoft/api-extractor/node_modules/minimatch": { @@ -978,9 +876,9 @@ } }, "node_modules/@rushstack/node-core-library": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-5.4.1.tgz", - "integrity": "sha512-WNnwdS8r9NZ/2K3u29tNoSRldscFa7SxU0RT+82B6Dy2I4Hl2MeCSKm4EXLXPKeNzLGvJ1cqbUhTLviSF8E6iA==", + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-5.9.0.tgz", + "integrity": "sha512-MMsshEWkTbXqxqFxD4gcIUWQOCeBChlGczdZbHfqmNZQFLHB3yWxDFSMHFUdu2/OB9NUk7Awn5qRL+rws4HQNg==", "dev": true, "dependencies": { "ajv": "~8.13.0", @@ -1024,9 +922,9 @@ "dev": true }, "node_modules/@rushstack/rig-package": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@rushstack/rig-package/-/rig-package-0.5.2.tgz", - "integrity": "sha512-mUDecIJeH3yYGZs2a48k+pbhM6JYwWlgjs2Ca5f2n1G2/kgdgP9D/07oglEGf6mRyXEnazhEENeYTSNDRCwdqA==", + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@rushstack/rig-package/-/rig-package-0.5.3.tgz", + "integrity": "sha512-olzSSjYrvCNxUFZowevC3uz8gvKr3WTpHQ7BkpjtRpA3wK+T0ybep/SRUMfr195gBzJm5gaXw0ZMgjIyHqJUow==", "dev": true, "dependencies": { "resolve": "~1.22.1", @@ -1034,12 +932,12 @@ } }, "node_modules/@rushstack/terminal": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@rushstack/terminal/-/terminal-0.13.0.tgz", - "integrity": "sha512-Ou44Q2s81BqJu3dpYedAX54am9vn245F0HzqVrfJCMQk5pGgoKKOBOjkbfZC9QKcGNaECh6pwH2s5noJt7X6ew==", + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/@rushstack/terminal/-/terminal-0.14.2.tgz", + "integrity": "sha512-2fC1wqu1VCExKC0/L+0noVcFQEXEnoBOtCIex1TOjBzEDWcw8KzJjjj7aTP6mLxepG0XIyn9OufeFb6SFsa+sg==", "dev": true, "dependencies": { - "@rushstack/node-core-library": "5.4.1", + "@rushstack/node-core-library": "5.9.0", "supports-color": "~8.1.1" }, "peerDependencies": { @@ -1067,12 +965,12 @@ } }, "node_modules/@rushstack/ts-command-line": { - "version": "4.22.0", - "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.22.0.tgz", - "integrity": "sha512-Qj28t6MO3HRgAZ72FDeFsrpdE6wBWxF3VENgvrXh7JF2qIT+CrXiOJIesW80VFZB9QwObSpkB1ilx794fGQg6g==", + "version": "4.22.8", + "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.22.8.tgz", + "integrity": "sha512-XbFjOoV7qZHJnSuFUHv0pKaFA4ixyCuki+xMjsMfDwfvQjs5MYG0IK5COal3tRnG7KCDe2l/G+9LrzYE/RJhgg==", "dev": true, "dependencies": { - "@rushstack/terminal": "0.13.0", + "@rushstack/terminal": "0.14.2", "@types/argparse": "1.0.38", "argparse": "~1.0.9", "string-argv": "~0.3.1" From edc8473f772ed3faa8579c82771dcac28a302c02 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Oct 2024 08:27:32 -0700 Subject: [PATCH 22/32] chore(deps): bump typescript from 5.5.4 to 5.6.3 (#8623) Bumps [typescript](https://github.com/microsoft/TypeScript) from 5.5.4 to 5.6.3. - [Release notes](https://github.com/microsoft/TypeScript/releases) - [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release.yml) - [Commits](https://github.com/microsoft/TypeScript/compare/v5.5.4...v5.6.3) --- updated-dependencies: - dependency-name: typescript 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 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index c454c971a2b..17b354f445b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8431,9 +8431,9 @@ "dev": true }, "node_modules/typescript": { - "version": "5.5.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", - "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", "dev": true, "bin": { "tsc": "bin/tsc", From 437f6a3d93ba3f0affd542391c9ac2c8baa423d8 Mon Sep 17 00:00:00 2001 From: John Nesky Date: Fri, 25 Oct 2024 11:37:55 -0700 Subject: [PATCH 23/32] fix: bump initiator group in an orthogonal direction from neighboring group (#8613) * fix: bump connected connections in a different direction * Bump initiator block group in orthogonal direction. * Revert the wording of a doc comment. * Addressing PR feedback. --- core/block_svg.ts | 4 +- core/connection.ts | 6 +- core/rendered_connection.ts | 110 ++++++++++++++++++++++-------------- 3 files changed, 73 insertions(+), 47 deletions(-) diff --git a/core/block_svg.ts b/core/block_svg.ts index 9797a50e72a..605d25fd4ce 100644 --- a/core/block_svg.ts +++ b/core/block_svg.ts @@ -1472,9 +1472,9 @@ export class BlockSvg if (conn.isConnected() && neighbour.isConnected()) continue; if (conn.isSuperior()) { - neighbour.bumpAwayFrom(conn); + neighbour.bumpAwayFrom(conn, /* initiatedByThis = */ false); } else { - conn.bumpAwayFrom(neighbour); + conn.bumpAwayFrom(neighbour, /* initiatedByThis = */ true); } } } diff --git a/core/connection.ts b/core/connection.ts index 93caf5bcf06..9cc2c28a923 100644 --- a/core/connection.ts +++ b/core/connection.ts @@ -214,11 +214,11 @@ export class Connection implements IASTNodeLocationWithBlock { * Called when an attempted connection fails. NOP by default (i.e. for * headless workspaces). * - * @param _otherConnection Connection that this connection failed to connect - * to. + * @param _superiorConnection Connection that this connection failed to connect + * to. The provided connection should be the superior connection. * @internal */ - onFailedConnect(_otherConnection: Connection) {} + onFailedConnect(_superiorConnection: Connection) {} // NOP /** diff --git a/core/rendered_connection.ts b/core/rendered_connection.ts index 27c532839d2..f73dc0628cc 100644 --- a/core/rendered_connection.ts +++ b/core/rendered_connection.ts @@ -117,59 +117,85 @@ export class RenderedConnection extends Connection { * Move the block(s) belonging to the connection to a point where they don't * visually interfere with the specified connection. * - * @param staticConnection The connection to move away from. + * @param superiorConnection The connection to move away from. The provided + * connection should be the superior connection and should not be + * connected to this connection. + * @param initiatedByThis Whether or not the block group that was manipulated + * recently causing bump checks is associated with the inferior + * connection. Defaults to false. * @internal */ - bumpAwayFrom(staticConnection: RenderedConnection) { + bumpAwayFrom( + superiorConnection: RenderedConnection, + initiatedByThis = false, + ) { if (this.sourceBlock_.workspace.isDragging()) { // Don't move blocks around while the user is doing the same. return; } - // Move the root block. - let rootBlock = this.sourceBlock_.getRootBlock(); - if (rootBlock.isInFlyout) { + let offsetX = + config.snapRadius + Math.floor(Math.random() * BUMP_RANDOMNESS); + let offsetY = + config.snapRadius + Math.floor(Math.random() * BUMP_RANDOMNESS); + /* eslint-disable-next-line @typescript-eslint/no-this-alias */ + const inferiorConnection = this; + const superiorRootBlock = superiorConnection.sourceBlock_.getRootBlock(); + const inferiorRootBlock = inferiorConnection.sourceBlock_.getRootBlock(); + + if (superiorRootBlock.isInFlyout || inferiorRootBlock.isInFlyout) { // Don't move blocks around in a flyout. return; } - let reverse = false; - if (!rootBlock.isMovable()) { - // Can't bump an uneditable block away. + let moveInferior = true; + if (!inferiorRootBlock.isMovable()) { + // Can't bump an immovable block away. // Check to see if the other block is movable. - rootBlock = staticConnection.getSourceBlock().getRootBlock(); - if (!rootBlock.isMovable()) { + if (!superiorRootBlock.isMovable()) { + // Neither block is movable, abort operation. return; + } else { + // Only the superior block group is movable. + moveInferior = false; + // The superior block group moves in the opposite direction. + offsetX = -offsetX; + offsetY = -offsetY; + } + } else if (superiorRootBlock.isMovable()) { + // Both block groups are movable. The one on the inferior side will be + // moved to make space for the superior one. However, it's possible that + // both groups of blocks have an inferior connection that bumps into a + // superior connection on the other group, which could result in both + // groups moving in the same direction and eventually bumping each other + // again. It would be better if one group of blocks could consistently + // move in an orthogonal direction from the other, so that they become + // separated in the end. We can designate one group the "initiator" if + // it's the one that was most recently manipulated, causing inputs to be + // checked for bumpable neighbors. As a useful heuristic, in the case + // where the inferior connection belongs to the initiator group, moving it + // in the orthogonal direction will separate the blocks better. + if (initiatedByThis) { + offsetY = -offsetY; } - // Swap the connections and move the 'static' connection instead. - /* eslint-disable-next-line @typescript-eslint/no-this-alias */ - staticConnection = this; - reverse = true; } + const staticConnection = moveInferior + ? superiorConnection + : inferiorConnection; + const dynamicConnection = moveInferior + ? inferiorConnection + : superiorConnection; + const dynamicRootBlock = moveInferior + ? inferiorRootBlock + : superiorRootBlock; // Raise it to the top for extra visibility. - const selected = common.getSelected() == rootBlock; - if (!selected) rootBlock.addSelect(); - let dx = - staticConnection.x + - config.snapRadius + - Math.floor(Math.random() * BUMP_RANDOMNESS) - - this.x; - let dy = - staticConnection.y + - config.snapRadius + - Math.floor(Math.random() * BUMP_RANDOMNESS) - - this.y; - if (reverse) { - // When reversing a bump due to an uneditable block, bump up. - dy = -dy; - } - if (rootBlock.RTL) { - dx = - staticConnection.x - - config.snapRadius - - Math.floor(Math.random() * BUMP_RANDOMNESS) - - this.x; + const selected = common.getSelected() === dynamicRootBlock; + if (!selected) dynamicRootBlock.addSelect(); + if (dynamicRootBlock.RTL) { + offsetX = -offsetX; } - rootBlock.moveBy(dx, dy, ['bump']); - if (!selected) rootBlock.removeSelect(); + const dx = staticConnection.x + offsetX - dynamicConnection.x; + const dy = staticConnection.y + offsetY - dynamicConnection.y; + dynamicRootBlock.moveBy(dx, dy, ['bump']); + if (!selected) dynamicRootBlock.removeSelect(); } /** @@ -413,11 +439,11 @@ export class RenderedConnection extends Connection { * Bumps this connection away from the other connection. Called when an * attempted connection fails. * - * @param otherConnection Connection that this connection failed to connect - * to. + * @param superiorConnection Connection that this connection failed to connect + * to. The provided connection should be the superior connection. * @internal */ - override onFailedConnect(otherConnection: Connection) { + override onFailedConnect(superiorConnection: Connection) { const block = this.getSourceBlock(); if (eventUtils.getRecordUndo()) { const group = eventUtils.getGroup(); @@ -425,7 +451,7 @@ export class RenderedConnection extends Connection { function (this: RenderedConnection) { if (!block.isDisposed() && !block.getParent()) { eventUtils.setGroup(group); - this.bumpAwayFrom(otherConnection as RenderedConnection); + this.bumpAwayFrom(superiorConnection as RenderedConnection); eventUtils.setGroup(false); } }.bind(this), From 1d2590354f95c450784ba268751f3ad1f7531d9e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Oct 2024 11:06:05 -0700 Subject: [PATCH 24/32] chore(deps): bump @typescript-eslint/parser from 8.8.1 to 8.11.0 (#8629) Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 8.8.1 to 8.11.0. - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.11.0/packages/parser) --- updated-dependencies: - dependency-name: "@typescript-eslint/parser" 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 | 48 +++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/package-lock.json b/package-lock.json index 17b354f445b..3d96af22fdc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1159,15 +1159,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.8.1.tgz", - "integrity": "sha512-hQUVn2Lij2NAxVFEdvIGxT9gP1tq2yM83m+by3whWFsWC+1y8pxxxHUFE1UqDu2VsGi2i6RLcv4QvouM84U+ow==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.11.0.tgz", + "integrity": "sha512-lmt73NeHdy1Q/2ul295Qy3uninSqi6wQI18XwSpm8w0ZbQXUpjCAWP1Vlv/obudoBiIjJVjlztjQ+d/Md98Yxg==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "8.8.1", - "@typescript-eslint/types": "8.8.1", - "@typescript-eslint/typescript-estree": "8.8.1", - "@typescript-eslint/visitor-keys": "8.8.1", + "@typescript-eslint/scope-manager": "8.11.0", + "@typescript-eslint/types": "8.11.0", + "@typescript-eslint/typescript-estree": "8.11.0", + "@typescript-eslint/visitor-keys": "8.11.0", "debug": "^4.3.4" }, "engines": { @@ -1187,13 +1187,13 @@ } }, "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.8.1.tgz", - "integrity": "sha512-X4JdU+66Mazev/J0gfXlcC/dV6JI37h+93W9BRYXrSn0hrE64IoWgVkO9MSJgEzoWkxONgaQpICWg8vAN74wlA==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.11.0.tgz", + "integrity": "sha512-Uholz7tWhXmA4r6epo+vaeV7yjdKy5QFCERMjs1kMVsLRKIrSdM6o21W2He9ftp5PP6aWOVpD5zvrvuHZC0bMQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.8.1", - "@typescript-eslint/visitor-keys": "8.8.1" + "@typescript-eslint/types": "8.11.0", + "@typescript-eslint/visitor-keys": "8.11.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1204,9 +1204,9 @@ } }, "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.8.1.tgz", - "integrity": "sha512-WCcTP4SDXzMd23N27u66zTKMuEevH4uzU8C9jf0RO4E04yVHgQgW+r+TeVTNnO1KIfrL8ebgVVYYMMO3+jC55Q==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.11.0.tgz", + "integrity": "sha512-tn6sNMHf6EBAYMvmPUaKaVeYvhUsrE6x+bXQTxjQRp360h1giATU0WvgeEys1spbvb5R+VpNOZ+XJmjD8wOUHw==", "dev": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1217,13 +1217,13 @@ } }, "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.8.1.tgz", - "integrity": "sha512-A5d1R9p+X+1js4JogdNilDuuq+EHZdsH9MjTVxXOdVFfTJXunKJR/v+fNNyO4TnoOn5HqobzfRlc70NC6HTcdg==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.11.0.tgz", + "integrity": "sha512-yHC3s1z1RCHoCz5t06gf7jH24rr3vns08XXhfEqzYpd6Hll3z/3g23JRi0jM8A47UFKNc3u/y5KIMx8Ynbjohg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.8.1", - "@typescript-eslint/visitor-keys": "8.8.1", + "@typescript-eslint/types": "8.11.0", + "@typescript-eslint/visitor-keys": "8.11.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -1245,12 +1245,12 @@ } }, "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.8.1.tgz", - "integrity": "sha512-0/TdC3aeRAsW7MDvYRwEc1Uwm0TIBfzjPFgg60UU2Haj5qsCs9cc3zNgY71edqE3LbWfF/WoZQd3lJoDXFQpag==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.11.0.tgz", + "integrity": "sha512-EaewX6lxSjRJnc+99+dqzTeoDZUfyrA52d2/HRrkI830kgovWsmIiTfmr0NZorzqic7ga+1bS60lRBUgR3n/Bw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.8.1", + "@typescript-eslint/types": "8.11.0", "eslint-visitor-keys": "^3.4.3" }, "engines": { From 6a674002cc893d40671810f9ad2f84a9d5aa84a4 Mon Sep 17 00:00:00 2001 From: Richard Knoll Date: Mon, 28 Oct 2024 13:05:31 -0700 Subject: [PATCH 25/32] fix: clear touch identifier on comment icon down (#8627) --- core/comments/comment_view.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/comments/comment_view.ts b/core/comments/comment_view.ts index bd90c757657..99c14aaa8f2 100644 --- a/core/comments/comment_view.ts +++ b/core/comments/comment_view.ts @@ -623,6 +623,7 @@ export class CommentView implements IRenderedElement { * event on the foldout icon. */ private onFoldoutDown(e: PointerEvent) { + touch.clearTouchIdentifier(); this.bringToFront(); if (browserEvents.isRightButton(e)) { e.stopPropagation(); @@ -738,6 +739,7 @@ export class CommentView implements IRenderedElement { * delete icon. */ private onDeleteDown(e: PointerEvent) { + touch.clearTouchIdentifier(); if (browserEvents.isRightButton(e)) { e.stopPropagation(); return; From 0d88280faa138aa96b9f58fb13b0dab39fc520a1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Nov 2024 18:50:26 +0000 Subject: [PATCH 26/32] chore(deps): bump @microsoft/api-extractor from 7.47.9 to 7.47.11 (#8622) Bumps [@microsoft/api-extractor](https://github.com/microsoft/rushstack/tree/HEAD/apps/api-extractor) from 7.47.9 to 7.47.11. - [Changelog](https://github.com/microsoft/rushstack/blob/main/apps/api-extractor/CHANGELOG.md) - [Commits](https://github.com/microsoft/rushstack/commits/@microsoft/api-extractor_v7.47.11/apps/api-extractor) --- updated-dependencies: - dependency-name: "@microsoft/api-extractor" 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 | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3d96af22fdc..19a37cfa0b6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -657,9 +657,9 @@ } }, "node_modules/@microsoft/api-extractor": { - "version": "7.47.9", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.47.9.tgz", - "integrity": "sha512-TTq30M1rikVsO5wZVToQT/dGyJY7UXJmjiRtkHPLb74Prx3Etw8+bX7Bv7iLuby6ysb7fuu1NFWqma+csym8Jw==", + "version": "7.47.11", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.47.11.tgz", + "integrity": "sha512-lrudfbPub5wzBhymfFtgZKuBvXxoSIAdrvS2UbHjoMT2TjIEddq6Z13pcve7A03BAouw0x8sW8G4txdgfiSwpQ==", "dev": true, "dependencies": { "@microsoft/api-extractor-model": "7.29.8", @@ -668,7 +668,7 @@ "@rushstack/node-core-library": "5.9.0", "@rushstack/rig-package": "0.5.3", "@rushstack/terminal": "0.14.2", - "@rushstack/ts-command-line": "4.22.8", + "@rushstack/ts-command-line": "4.23.0", "lodash": "~4.17.15", "minimatch": "~3.0.3", "resolve": "~1.22.1", @@ -691,6 +691,27 @@ "@rushstack/node-core-library": "5.9.0" } }, + "node_modules/@microsoft/api-extractor/node_modules/@rushstack/ts-command-line": { + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.23.0.tgz", + "integrity": "sha512-jYREBtsxduPV6ptNq8jOKp9+yx0ld1Tb/Tkdnlj8gTjazl1sF3DwX2VbluyYrNd0meWIL0bNeer7WDf5tKFjaQ==", + "dev": true, + "dependencies": { + "@rushstack/terminal": "0.14.2", + "@types/argparse": "1.0.38", + "argparse": "~1.0.9", + "string-argv": "~0.3.1" + } + }, + "node_modules/@microsoft/api-extractor/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, "node_modules/@microsoft/api-extractor/node_modules/minimatch": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", From d053008b800567e194fba217b93b3df08451e129 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Nov 2024 19:00:57 +0000 Subject: [PATCH 27/32] chore(deps): bump google-github-actions/deploy-appengine (#8630) Bumps [google-github-actions/deploy-appengine](https://github.com/google-github-actions/deploy-appengine) from 2.1.3 to 2.1.4. - [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.3...v2.1.4) --- updated-dependencies: - dependency-name: google-github-actions/deploy-appengine 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 438d2f094a5..938a16fc714 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.3 + uses: google-github-actions/deploy-appengine@v2.1.4 # For parameters see: # https://github.com/google-github-actions/deploy-appengine#inputs with: From aedcfd6da58e7f5dafadb6b4c55ddf4bc9d2a9aa Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Mon, 4 Nov 2024 09:43:17 -0800 Subject: [PATCH 28/32] fix: Use a readonly textarea for non-editable comments. (#8632) * fix: Use a readonly textarea for non-editable comments. * chore: Run formatter. * chore: remove old function definition --- core/bubbles/textinput_bubble.ts | 17 ++++++++++++++++ core/icons/comment_icon.ts | 34 +++++++++++--------------------- tests/mocha/comment_test.js | 6 +++--- 3 files changed, 32 insertions(+), 25 deletions(-) diff --git a/core/bubbles/textinput_bubble.ts b/core/bubbles/textinput_bubble.ts index befbb2f21bf..5b5278b91ff 100644 --- a/core/bubbles/textinput_bubble.ts +++ b/core/bubbles/textinput_bubble.ts @@ -63,6 +63,8 @@ export class TextInputBubble extends Bubble { 20 + Bubble.DOUBLE_BORDER, ); + private editable = true; + /** * @param workspace The workspace this bubble belongs to. * @param anchor The anchor location of the thing this bubble is attached to. @@ -96,6 +98,21 @@ export class TextInputBubble extends Bubble { this.onTextChange(); } + /** 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', ''); + } + } + + /** Returns whether or not the text in the bubble is editable. */ + isEditable(): boolean { + return this.editable; + } + /** Adds a change listener to be notified when this bubble's text changes. */ addTextChangeListener(listener: () => void) { this.textChangeListeners.push(listener); diff --git a/core/icons/comment_icon.ts b/core/icons/comment_icon.ts index 7cf5431d7ac..8cdf779187c 100644 --- a/core/icons/comment_icon.ts +++ b/core/icons/comment_icon.ts @@ -8,7 +8,6 @@ import type {Block} from '../block.js'; import type {BlockSvg} from '../block_svg.js'; -import {TextBubble} from '../bubbles/text_bubble.js'; import {TextInputBubble} from '../bubbles/textinput_bubble.js'; import {EventType} from '../events/type.js'; import * as eventUtils from '../events/utils.js'; @@ -47,12 +46,9 @@ export class CommentIcon extends Icon implements IHasBubble, ISerializable { */ static readonly WEIGHT = 3; - /** The bubble used to show editable text to the user. */ + /** The bubble used to show comment text to the user. */ private textInputBubble: TextInputBubble | null = null; - /** The bubble used to show non-editable text to the user. */ - private textBubble: TextBubble | null = null; - /** The text of this comment. */ private text = ''; @@ -118,7 +114,6 @@ export class CommentIcon extends Icon implements IHasBubble, ISerializable { override dispose() { super.dispose(); this.textInputBubble?.dispose(); - this.textBubble?.dispose(); } override getWeight(): number { @@ -133,7 +128,6 @@ export class CommentIcon extends Icon implements IHasBubble, ISerializable { super.applyColour(); const colour = (this.sourceBlock as BlockSvg).style.colourPrimary; this.textInputBubble?.setColour(colour); - this.textBubble?.setColour(colour); } /** @@ -153,7 +147,6 @@ export class CommentIcon extends Icon implements IHasBubble, ISerializable { super.onLocationChange(blockOrigin); const anchorLocation = this.getAnchorLocation(); this.textInputBubble?.setAnchorLocation(anchorLocation); - this.textBubble?.setAnchorLocation(anchorLocation); } /** Sets the text of this comment. Updates any bubbles if they are visible. */ @@ -170,7 +163,6 @@ export class CommentIcon extends Icon implements IHasBubble, ISerializable { ); this.text = text; this.textInputBubble?.setText(this.text); - this.textBubble?.setText(this.text); } /** Returns the text of this comment. */ @@ -302,33 +294,31 @@ export class CommentIcon extends Icon implements IHasBubble, ISerializable { * to update the state of this icon in response to changes in the bubble. */ private showEditableBubble() { - this.textInputBubble = new TextInputBubble( - this.sourceBlock.workspace as WorkspaceSvg, - this.getAnchorLocation(), - this.getBubbleOwnerRect(), - ); - this.textInputBubble.setText(this.getText()); - this.textInputBubble.setSize(this.bubbleSize, true); - this.textInputBubble.addTextChangeListener(() => this.onTextChange()); - this.textInputBubble.addSizeChangeListener(() => this.onSizeChange()); + this.createBubble(); + this.textInputBubble?.addTextChangeListener(() => this.onTextChange()); + this.textInputBubble?.addSizeChangeListener(() => this.onSizeChange()); } /** Shows the non editable text bubble for this comment. */ private showNonEditableBubble() { - this.textBubble = new TextBubble( - this.getText(), + this.createBubble(); + this.textInputBubble?.setEditable(false); + } + + protected createBubble() { + this.textInputBubble = new TextInputBubble( this.sourceBlock.workspace as WorkspaceSvg, this.getAnchorLocation(), this.getBubbleOwnerRect(), ); + this.textInputBubble.setText(this.getText()); + this.textInputBubble.setSize(this.bubbleSize, true); } /** Hides any open bubbles owned by this comment. */ private hideBubble() { this.textInputBubble?.dispose(); this.textInputBubble = null; - this.textBubble?.dispose(); - this.textBubble = null; } /** diff --git a/tests/mocha/comment_test.js b/tests/mocha/comment_test.js index 8024fa5e33e..79b3d7de662 100644 --- a/tests/mocha/comment_test.js +++ b/tests/mocha/comment_test.js @@ -41,12 +41,12 @@ suite('Comments', function () { }); function assertEditable(comment) { - assert.isNotOk(comment.textBubble); assert.isOk(comment.textInputBubble); + assert.isTrue(comment.textInputBubble.isEditable()); } function assertNotEditable(comment) { - assert.isNotOk(comment.textInputBubble); - assert.isOk(comment.textBubble); + assert.isOk(comment.textInputBubble); + assert.isFalse(comment.textInputBubble.isEditable()); } test('Editable', async function () { await this.comment.setBubbleVisible(true); From 3c91b306d3bb84f7a27c0d16a2590a15d8d4bcc6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 7 Nov 2024 11:54:14 -0800 Subject: [PATCH 29/32] chore(deps): bump eslint-plugin-jsdoc from 48.5.0 to 50.4.3 (#8641) Bumps [eslint-plugin-jsdoc](https://github.com/gajus/eslint-plugin-jsdoc) from 48.5.0 to 50.4.3. - [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/v48.5.0...v50.4.3) --- updated-dependencies: - dependency-name: eslint-plugin-jsdoc 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 | 141 ++++++++++++++++++++++------------------------ package.json | 2 +- 2 files changed, 67 insertions(+), 76 deletions(-) diff --git a/package-lock.json b/package-lock.json index 19a37cfa0b6..a7385f28561 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,7 +28,7 @@ "eslint": "^8.4.1", "eslint-config-google": "^0.14.0", "eslint-config-prettier": "^9.0.0", - "eslint-plugin-jsdoc": "^48.0.2", + "eslint-plugin-jsdoc": "^50.4.3", "glob": "^10.3.4", "google-closure-compiler": "^20240317.0.0", "gulp": "^5.0.0", @@ -235,17 +235,14 @@ } }, "node_modules/@es-joy/jsdoccomment": { - "version": "0.43.1", - "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.43.1.tgz", - "integrity": "sha512-I238eDtOolvCuvtxrnqtlBaw0BwdQuYqK7eA6XIonicMdOOOb75mqdIzkGDUbS04+1Di007rgm9snFRNeVrOog==", + "version": "0.49.0", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.49.0.tgz", + "integrity": "sha512-xjZTSFgECpb9Ohuk5yMX5RhUEbfeQcuOp8IF60e+wyzWEF0M5xeSgqsfLtvPEX8BIyOX9saZqzuGPmZ8oWc+5Q==", "dev": true, "dependencies": { - "@types/eslint": "^8.56.5", - "@types/estree": "^1.0.5", - "@typescript-eslint/types": "^7.2.0", "comment-parser": "1.4.1", - "esquery": "^1.5.0", - "jsdoc-type-pratt-parser": "~4.0.0" + "esquery": "^1.6.0", + "jsdoc-type-pratt-parser": "~4.1.0" }, "engines": { "node": ">=16" @@ -1068,34 +1065,12 @@ "integrity": "sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==", "dev": true }, - "node_modules/@types/eslint": { - "version": "8.56.10", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz", - "integrity": "sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==", - "dev": true, - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", - "dev": true - }, "node_modules/@types/expect": { "version": "1.20.4", "resolved": "https://registry.npmjs.org/@types/expect/-/expect-1.20.4.tgz", "integrity": "sha512-Q5Vn3yjTDyCMV50TB6VRIbQNxSE4OmZR86VSbGaNpfUolm0iePBB4KdEEHmxoY5sT2+2DIvXW0rvMDP2nHZ4Mg==", "dev": true }, - "node_modules/@types/json-schema": { - "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 - }, "node_modules/@types/node": { "version": "20.16.3", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.3.tgz", @@ -1372,20 +1347,6 @@ } } }, - "node_modules/@typescript-eslint/types": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.16.1.tgz", - "integrity": "sha512-AQn9XqCzUXd4bAVEsAXM/Izk11Wx2u4H3BAfQVhSfzfDOm/wAON9nP7J5rpkCxts7E5TELmN845xTUCQrD1xIQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, "node_modules/@typescript-eslint/typescript-estree": { "version": "8.6.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.6.0.tgz", @@ -1678,9 +1639,9 @@ } }, "node_modules/acorn": { - "version": "8.11.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", - "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -3722,21 +3683,22 @@ } }, "node_modules/eslint-plugin-jsdoc": { - "version": "48.5.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-48.5.0.tgz", - "integrity": "sha512-ukXPNpGby3KjCveCizIS8t1EbuJEHYEu/tBg8GCbn/YbHcXwphyvYCdvRZ/oMRfTscGSSzfsWoZ+ZkAP0/6YMQ==", + "version": "50.4.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-50.4.3.tgz", + "integrity": "sha512-uWtwFxGRv6B8sU63HZM5dAGDhgsatb+LONwmILZJhdRALLOkCX2HFZhdL/Kw2ls8SQMAVEfK+LmnEfxInRN8HA==", "dev": true, "dependencies": { - "@es-joy/jsdoccomment": "~0.43.1", + "@es-joy/jsdoccomment": "~0.49.0", "are-docs-informative": "^0.0.2", "comment-parser": "1.4.1", - "debug": "^4.3.4", + "debug": "^4.3.6", "escape-string-regexp": "^4.0.0", - "esquery": "^1.5.0", - "parse-imports": "^2.1.0", - "semver": "^7.6.2", + "espree": "^10.1.0", + "esquery": "^1.6.0", + "parse-imports": "^2.1.1", + "semver": "^7.6.3", "spdx-expression-parse": "^4.0.0", - "synckit": "^0.9.0" + "synckit": "^0.9.1" }, "engines": { "node": ">=18" @@ -3745,10 +3707,39 @@ "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" } }, + "node_modules/eslint-plugin-jsdoc/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-jsdoc/node_modules/espree": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "dev": true, + "dependencies": { + "acorn": "^8.14.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/eslint-plugin-jsdoc/node_modules/semver": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", - "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -3838,9 +3829,9 @@ } }, "node_modules/esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", "dev": true, "dependencies": { "estraverse": "^5.1.0" @@ -5822,9 +5813,9 @@ "dev": true }, "node_modules/jsdoc-type-pratt-parser": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.0.0.tgz", - "integrity": "sha512-YtOli5Cmzy3q4dP26GraSOeAhqecewG04hoO8DY56CH4KJ9Fvv5qKWUCCo3HZob7esJQHCv6/+bnTy72xZZaVQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.1.0.tgz", + "integrity": "sha512-Hicd6JK5Njt2QB6XYFS7ok9e37O8AYk3jTcppG4YVQnYjOemymvTcmc7OWsmq/Qqj5TdRFO5/x/tIPmBeRtGHg==", "dev": true, "engines": { "node": ">=12.0.0" @@ -6858,9 +6849,9 @@ } }, "node_modules/parse-imports": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/parse-imports/-/parse-imports-2.1.0.tgz", - "integrity": "sha512-JQWgmK2o4w8leUkZeZPatWdAny6vXGU/3siIUvMF6J2rDCud9aTt8h/px9oZJ6U3EcfhngBJ635uPFI0q0VAeA==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/parse-imports/-/parse-imports-2.2.1.tgz", + "integrity": "sha512-OL/zLggRp8mFhKL0rNORUTR4yBYujK/uU+xZL+/0Rgm2QE4nLO9v8PzEweSJEbMGKmDRjJE4R3IMJlL2di4JeQ==", "dev": true, "dependencies": { "es-module-lexer": "^1.5.3", @@ -8209,9 +8200,9 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" }, "node_modules/synckit": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.0.tgz", - "integrity": "sha512-7RnqIMq572L8PeEzKeBINYEJDDxpcH8JEgLwUqBd3TkofhFRbkq4QLR0u+36avGAhCRbk2nnmjcW9SE531hPDg==", + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.2.tgz", + "integrity": "sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==", "dev": true, "dependencies": { "@pkgr/core": "^0.1.0", @@ -8225,9 +8216,9 @@ } }, "node_modules/synckit/node_modules/tslib": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", - "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "dev": true }, "node_modules/tar-fs": { diff --git a/package.json b/package.json index 9fcdada411b..10b5e2ab9db 100644 --- a/package.json +++ b/package.json @@ -115,7 +115,7 @@ "eslint": "^8.4.1", "eslint-config-google": "^0.14.0", "eslint-config-prettier": "^9.0.0", - "eslint-plugin-jsdoc": "^48.0.2", + "eslint-plugin-jsdoc": "^50.4.3", "glob": "^10.3.4", "google-closure-compiler": "^20240317.0.0", "gulp": "^5.0.0", From f1cbaab6ef3f7767ff5fecd78509309841e06b42 Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Fri, 8 Nov 2024 14:21:16 -0800 Subject: [PATCH 30/32] refactor: Move functions into FieldDropdown. (#8634) * refactor: Move functions into FieldDropdown. * refactor: Make dropdown field image metrics static. * refactor: Use template literals in FieldDropdown validator. --- core/field_dropdown.ts | 281 ++++++++++++++++++++--------------------- 1 file changed, 134 insertions(+), 147 deletions(-) diff --git a/core/field_dropdown.ts b/core/field_dropdown.ts index 71b17326d95..b1e3b5af26c 100644 --- a/core/field_dropdown.ts +++ b/core/field_dropdown.ts @@ -95,6 +95,15 @@ export class FieldDropdown extends Field { private selectedOption!: MenuOption; override clickTarget_: SVGElement | null = null; + /** + * The y offset from the top of the field to the top of the image, if an image + * is selected. + */ + protected static IMAGE_Y_OFFSET = 5; + + /** The total vertical padding above and below an image. */ + protected static IMAGE_Y_PADDING = FieldDropdown.IMAGE_Y_OFFSET * 2; + /** * @param menuGenerator A non-empty array of options for a dropdown list, or a * function which generates these options. Also accepts Field.SKIP_SETUP @@ -128,8 +137,8 @@ export class FieldDropdown extends Field { if (menuGenerator === Field.SKIP_SETUP) return; if (Array.isArray(menuGenerator)) { - validateOptions(menuGenerator); - const trimmed = trimOptions(menuGenerator); + this.validateOptions(menuGenerator); + const trimmed = this.trimOptions(menuGenerator); this.menuGenerator_ = trimmed.options; this.prefixField = trimmed.prefix || null; this.suffixField = trimmed.suffix || null; @@ -401,7 +410,7 @@ export class FieldDropdown extends Field { if (useCache && this.generatedOptions) return this.generatedOptions; this.generatedOptions = this.menuGenerator_(); - validateOptions(this.generatedOptions); + this.validateOptions(this.generatedOptions); return this.generatedOptions; } @@ -520,7 +529,7 @@ export class FieldDropdown extends Field { const hasBorder = !!this.borderRect_; const height = Math.max( hasBorder ? this.getConstants()!.FIELD_DROPDOWN_BORDER_RECT_HEIGHT : 0, - imageHeight + IMAGE_Y_PADDING, + imageHeight + FieldDropdown.IMAGE_Y_PADDING, ); const xPadding = hasBorder ? this.getConstants()!.FIELD_BORDER_RECT_X_PADDING @@ -661,6 +670,127 @@ export class FieldDropdown extends Field { // override the static fromJson method. return new this(options.options, undefined, options); } + + /** + * Factor out common words in statically defined options. + * Create prefix and/or suffix labels. + */ + protected trimOptions(options: MenuOption[]): { + options: MenuOption[]; + prefix?: string; + suffix?: string; + } { + let hasImages = false; + const trimmedOptions = options.map(([label, value]): MenuOption => { + if (typeof label === 'string') { + return [parsing.replaceMessageReferences(label), value]; + } + + hasImages = 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 = + label.alt !== null + ? {...label, alt: parsing.replaceMessageReferences(label.alt)} + : {...label}; + return [imageLabel, value]; + }); + + if (hasImages || options.length < 2) return {options: trimmedOptions}; + + const stringOptions = trimmedOptions as [string, string][]; + const stringLabels = stringOptions.map(([label]) => label); + + const shortest = utilsString.shortestStringLength(stringLabels); + const prefixLength = utilsString.commonWordPrefix(stringLabels, shortest); + const suffixLength = utilsString.commonWordSuffix(stringLabels, shortest); + + if ( + (!prefixLength && !suffixLength) || + shortest <= prefixLength + suffixLength + ) { + // One or more strings will entirely vanish if we proceed. Abort. + return {options: stringOptions}; + } + + const prefix = prefixLength + ? stringLabels[0].substring(0, prefixLength - 1) + : undefined; + const suffix = suffixLength + ? stringLabels[0].substr(1 - suffixLength) + : undefined; + return { + options: this.applyTrim(stringOptions, prefixLength, suffixLength), + prefix, + suffix, + }; + } + + /** + * Use the calculated prefix and suffix lengths to trim all of the options in + * the given array. + * + * @param options Array of option tuples: + * (human-readable text or image, language-neutral name). + * @param prefixLength The length of the common prefix. + * @param suffixLength The length of the common suffix + * @returns A new array with all of the option text trimmed. + */ + private applyTrim( + options: [string, string][], + prefixLength: number, + suffixLength: number, + ): MenuOption[] { + return options.map(([text, value]) => [ + text.substring(prefixLength, text.length - suffixLength), + value, + ]); + } + + /** + * Validates the data structure to be processed as an options list. + * + * @param options The proposed dropdown options. + * @throws {TypeError} If proposed options are incorrectly structured. + */ + protected validateOptions(options: MenuOption[]) { + if (!Array.isArray(options)) { + throw TypeError('FieldDropdown options must be an array.'); + } + if (!options.length) { + throw TypeError('FieldDropdown options must not be an empty array.'); + } + let foundError = false; + for (let i = 0; i < options.length; i++) { + const tuple = options[i]; + if (!Array.isArray(tuple)) { + foundError = true; + console.error( + `Invalid option[${i}]: Each FieldDropdown option must be an array. + Found: ${tuple}`, + ); + } else if (typeof tuple[1] !== 'string') { + foundError = true; + console.error( + `Invalid option[${i}]: Each FieldDropdown option id must be a string. + Found ${tuple[1]} in: ${tuple}`, + ); + } else if ( + tuple[0] && + typeof tuple[0] !== 'string' && + typeof tuple[0].src !== 'string' + ) { + foundError = true; + console.error( + `Invalid option[${i}]: Each FieldDropdown option must have a string + label or image description. Found ${tuple[0]} in: ${tuple}`, + ); + } + } + if (foundError) { + throw TypeError('Found invalid FieldDropdown options.'); + } + } } /** @@ -721,147 +851,4 @@ export interface FieldDropdownFromJsonConfig extends FieldDropdownConfig { */ export type FieldDropdownValidator = FieldValidator; -/** - * The y offset from the top of the field to the top of the image, if an image - * is selected. - */ -const IMAGE_Y_OFFSET = 5; - -/** The total vertical padding above and below an image. */ -const IMAGE_Y_PADDING: number = IMAGE_Y_OFFSET * 2; - -/** - * Factor out common words in statically defined options. - * Create prefix and/or suffix labels. - */ -function trimOptions(options: MenuOption[]): { - options: MenuOption[]; - prefix?: string; - suffix?: string; -} { - let hasImages = false; - const trimmedOptions = options.map(([label, value]): MenuOption => { - if (typeof label === 'string') { - return [parsing.replaceMessageReferences(label), value]; - } - - hasImages = 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 = - label.alt !== null - ? {...label, alt: parsing.replaceMessageReferences(label.alt)} - : {...label}; - return [imageLabel, value]; - }); - - if (hasImages || options.length < 2) return {options: trimmedOptions}; - - const stringOptions = trimmedOptions as [string, string][]; - const stringLabels = stringOptions.map(([label]) => label); - - const shortest = utilsString.shortestStringLength(stringLabels); - const prefixLength = utilsString.commonWordPrefix(stringLabels, shortest); - const suffixLength = utilsString.commonWordSuffix(stringLabels, shortest); - - if ( - (!prefixLength && !suffixLength) || - shortest <= prefixLength + suffixLength - ) { - // One or more strings will entirely vanish if we proceed. Abort. - return {options: stringOptions}; - } - - const prefix = prefixLength - ? stringLabels[0].substring(0, prefixLength - 1) - : undefined; - const suffix = suffixLength - ? stringLabels[0].substr(1 - suffixLength) - : undefined; - return { - options: applyTrim(stringOptions, prefixLength, suffixLength), - prefix, - suffix, - }; -} - -/** - * Use the calculated prefix and suffix lengths to trim all of the options in - * the given array. - * - * @param options Array of option tuples: - * (human-readable text or image, language-neutral name). - * @param prefixLength The length of the common prefix. - * @param suffixLength The length of the common suffix - * @returns A new array with all of the option text trimmed. - */ -function applyTrim( - options: [string, string][], - prefixLength: number, - suffixLength: number, -): MenuOption[] { - return options.map(([text, value]) => [ - text.substring(prefixLength, text.length - suffixLength), - value, - ]); -} - -/** - * Validates the data structure to be processed as an options list. - * - * @param options The proposed dropdown options. - * @throws {TypeError} If proposed options are incorrectly structured. - */ -function validateOptions(options: MenuOption[]) { - if (!Array.isArray(options)) { - throw TypeError('FieldDropdown options must be an array.'); - } - if (!options.length) { - throw TypeError('FieldDropdown options must not be an empty array.'); - } - let foundError = false; - for (let i = 0; i < options.length; i++) { - const tuple = options[i]; - if (!Array.isArray(tuple)) { - foundError = true; - console.error( - 'Invalid option[' + - i + - ']: Each FieldDropdown option must be an ' + - 'array. Found: ', - tuple, - ); - } else if (typeof tuple[1] !== 'string') { - foundError = true; - console.error( - 'Invalid option[' + - i + - ']: Each FieldDropdown option id must be ' + - 'a string. Found ' + - tuple[1] + - ' in: ', - tuple, - ); - } else if ( - tuple[0] && - typeof tuple[0] !== 'string' && - typeof tuple[0].src !== 'string' - ) { - foundError = true; - console.error( - 'Invalid option[' + - i + - ']: Each FieldDropdown option must have a ' + - 'string label or image description. Found' + - tuple[0] + - ' in: ', - tuple, - ); - } - } - if (foundError) { - throw TypeError('Found invalid FieldDropdown options.'); - } -} - fieldRegistry.register('field_dropdown', FieldDropdown); From 378d5a97d9034c88cd510f9752d72d016d2cd2c1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Nov 2024 07:53:36 -0800 Subject: [PATCH 31/32] chore(deps): bump chai from 5.1.1 to 5.1.2 (#8651) Bumps [chai](https://github.com/chaijs/chai) from 5.1.1 to 5.1.2. - [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.1.1...v5.1.2) --- updated-dependencies: - dependency-name: chai 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 a7385f28561..626befab073 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2414,9 +2414,9 @@ } }, "node_modules/chai": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.1.tgz", - "integrity": "sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.2.tgz", + "integrity": "sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==", "dev": true, "dependencies": { "assertion-error": "^2.0.1", From 9a7de53029afc09dfc9d055a4eae40b316728455 Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Mon, 11 Nov 2024 08:51:16 -0800 Subject: [PATCH 32/32] fix: Fix crash when resizing page while editing a field. (#8646) * fix: Fix crash when resizing page while editing a field. * refactor: Clean up positioning and exceptions. --- core/field_input.ts | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/core/field_input.ts b/core/field_input.ts index 7ef5a01bc7a..722316f4f46 100644 --- a/core/field_input.ts +++ b/core/field_input.ts @@ -28,8 +28,8 @@ import { UnattachedFieldError, } from './field.js'; import {Msg} from './msg.js'; +import * as renderManagement from './render_management.js'; import * as aria from './utils/aria.js'; -import {Coordinate} from './utils/coordinate.js'; import * as dom from './utils/dom.js'; import {Size} from './utils/size.js'; import * as userAgent from './utils/useragent.js'; @@ -630,22 +630,22 @@ export abstract class FieldInput extends Field< /** Resize the editor to fit the text. */ protected resizeEditor_() { - const block = this.getSourceBlock(); - if (!block) { - throw new UnattachedFieldError(); - } - const div = WidgetDiv.getDiv(); - const bBox = this.getScaledBBox(); - div!.style.width = bBox.right - bBox.left + 'px'; - div!.style.height = bBox.bottom - bBox.top + 'px'; + renderManagement.finishQueuedRenders().then(() => { + const block = this.getSourceBlock(); + if (!block) throw new UnattachedFieldError(); + const div = WidgetDiv.getDiv(); + const bBox = this.getScaledBBox(); + div!.style.width = bBox.right - bBox.left + 'px'; + div!.style.height = bBox.bottom - bBox.top + 'px'; - // In RTL mode block fields and LTR input fields the left edge moves, - // whereas the right edge is fixed. Reposition the editor. - const x = block.RTL ? bBox.right - div!.offsetWidth : bBox.left; - const xy = new Coordinate(x, bBox.top); + // In RTL mode block fields and LTR input fields the left edge moves, + // whereas the right edge is fixed. Reposition the editor. + const x = block.RTL ? bBox.right - div!.offsetWidth : bBox.left; + const y = bBox.top; - div!.style.left = xy.x + 'px'; - div!.style.top = xy.y + 'px'; + div!.style.left = `${x}px`; + div!.style.top = `${y}px`; + }); } /** @@ -657,7 +657,7 @@ export abstract class FieldInput extends Field< * div. */ override repositionForWindowResize(): boolean { - const block = this.getSourceBlock(); + const block = this.getSourceBlock()?.getRootBlock(); // This shouldn't be possible. We should never have a WidgetDiv if not using // rendered blocks. if (!(block instanceof BlockSvg)) return false;