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).
This commit is contained in:
Ben Henning
2024-08-21 17:41:38 +00:00
parent 505f28f1a5
commit 3a3e83fe44
8 changed files with 1493 additions and 26 deletions

View File

@@ -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 () {

View File

@@ -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';

968
tests/mocha/rect_test.js Normal file
View File

@@ -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');
});
});
});
});

View File

@@ -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();
});