From 3a3e83fe44f12b9b617870da0385dcb3d7307bee Mon Sep 17 00:00:00 2001 From: Ben Henning Date: Wed, 21 Aug 2024 17:41:38 +0000 Subject: [PATCH 1/4] 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 254906ce7..ec98d7e83 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 817462b69..adf0d2b94 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 aad748105..a92c3f4ae 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 385feede8..7e2f95c81 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 b5d480c37..ff6f4fe91 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 ff3467907..adc63da4a 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 000000000..67b116be0 --- /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 93f8f7158..b76cd3516 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 2/4] Auto-fix formatting issues to address CI failure. --- core/utils/rect.ts | 23 +- core/workspace_svg.ts | 16 +- tests/mocha/rect_test.js | 1286 ++++++++++++++++++++++------- tests/mocha/workspace_svg_test.js | 563 ++++++++----- 4 files changed, 1367 insertions(+), 521 deletions(-) diff --git a/core/utils/rect.ts b/core/utils/rect.ts index adf0d2b94..c7da2a686 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 cfab07d99..ff47353fe 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 67b116be0..796e7ec87 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); + 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) + const areEqual = Blockly.utils.Rect.equals(rect1, rect2); - assert.isTrue(areEqual, 'two objects constructed in the same way should be equal'); + 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); + 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) + const areEqual = Blockly.utils.Rect.equals(rect1, rect2); - assert.isFalse(areEqual, 'two objects with different x positions should not be equal'); + 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); + 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) + const areEqual = Blockly.utils.Rect.equals(rect1, rect2); - assert.isFalse(areEqual, 'two objects with different y positions should not be equal'); + 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); + 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) + const areEqual = Blockly.utils.Rect.equals(rect1, rect2); - assert.isFalse(areEqual, 'two objects with different x/y positions should not be equal'); + 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); + 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) + const areEqual = Blockly.utils.Rect.equals(rect1, rect2); - assert.isFalse(areEqual, 'two objects with different widths should not be equal'); + 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); + 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) + const areEqual = Blockly.utils.Rect.equals(rect1, rect2); - assert.isFalse(areEqual, 'two objects with different heights should not be equal'); + 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); + 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) + const areEqual = Blockly.utils.Rect.equals(rect1, rect2); - assert.isFalse(areEqual, 'two objects with all different properties should not be equal'); + 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 9218ad22b..d90c21106 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 3/4] 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 796e7ec87..37712dff3 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 4/4] 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 04b94eed7..25ffab59b 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 d118c8584..b8ef96292 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 7e2f95c81..385feede8 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 ff6f4fe91..a9e2bb3de 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 d90c21106..75c0625fb 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