Skip to content

Commit b9b40f4

Browse files
authored
fix: Fix bug in BlockSvg.prototype.setParent (#8934)
* test(BlockSvg): Add tests for setParent(null) when dragging Add tests for scenarios where block(s) unrelated to the block being disconnected has/have been marked as as being dragged. Due to a bug in BlockSvg.prototype.setParent, one of these fails in the case that the dragging block is not a top block. Also add a check to assertNonParentAndOrphan to check that the orphan block's SVG root's parent is the workspace's canvass (i.e., that orphan is a top-level block in the DOM too). * fix(BlockSvg): Fix bug in setParent re: dragging block Fix an incorrect assumption in setParent: the topmost block whose root SVG element has the blocklyDragging class may not actually be a top-level block. * refactor(BlockDragStrategy): Hide connection preview earlier * chore(BlockDragStrategy): prefer ?. to !. Per nit on PR #8934. * fix(BlockSvg): Spelling: "canvass" -> "canvas"
1 parent dee27b9 commit b9b40f4

File tree

3 files changed

+59
-14
lines changed

3 files changed

+59
-14
lines changed

core/block_svg.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -305,14 +305,22 @@ export class BlockSvg
305305
(newParent as BlockSvg).getSvgRoot().appendChild(svgRoot);
306306
} else if (oldParent) {
307307
// If we are losing a parent, we want to move our DOM element to the
308-
// root of the workspace.
309-
const draggingBlock = this.workspace
308+
// root of the workspace. Try to insert it before any top-level
309+
// block being dragged, but note that blocks can have the
310+
// blocklyDragging class even if they're not top blocks (especially
311+
// at start and end of a drag).
312+
const draggingBlockElement = this.workspace
310313
.getCanvas()
311314
.querySelector('.blocklyDragging');
312-
if (draggingBlock) {
313-
this.workspace.getCanvas().insertBefore(svgRoot, draggingBlock);
315+
const draggingParentElement = draggingBlockElement?.parentElement as
316+
| SVGElement
317+
| null
318+
| undefined;
319+
const canvas = this.workspace.getCanvas();
320+
if (draggingParentElement === canvas) {
321+
canvas.insertBefore(svgRoot, draggingBlockElement);
314322
} else {
315-
this.workspace.getCanvas().appendChild(svgRoot);
323+
canvas.appendChild(svgRoot);
316324
}
317325
this.translate(oldXY.x, oldXY.y);
318326
}

core/dragging/block_drag_strategy.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ export class BlockDragStrategy implements IDragStrategy {
238238
const currCandidate = this.connectionCandidate;
239239
const newCandidate = this.getConnectionCandidate(draggingBlock, delta);
240240
if (!newCandidate) {
241-
this.connectionPreviewer!.hidePreview();
241+
this.connectionPreviewer?.hidePreview();
242242
this.connectionCandidate = null;
243243
return;
244244
}
@@ -254,7 +254,7 @@ export class BlockDragStrategy implements IDragStrategy {
254254
local.type === ConnectionType.OUTPUT_VALUE ||
255255
local.type === ConnectionType.PREVIOUS_STATEMENT;
256256
const neighbourIsConnectedToRealBlock =
257-
neighbour.isConnected() && !neighbour.targetBlock()!.isInsertionMarker();
257+
neighbour.isConnected() && !neighbour.targetBlock()?.isInsertionMarker();
258258
if (
259259
localIsOutputOrPrevious &&
260260
neighbourIsConnectedToRealBlock &&
@@ -264,14 +264,14 @@ export class BlockDragStrategy implements IDragStrategy {
264264
local.type,
265265
)
266266
) {
267-
this.connectionPreviewer!.previewReplacement(
267+
this.connectionPreviewer?.previewReplacement(
268268
local,
269269
neighbour,
270270
neighbour.targetBlock()!,
271271
);
272272
return;
273273
}
274-
this.connectionPreviewer!.previewConnection(local, neighbour);
274+
this.connectionPreviewer?.previewConnection(local, neighbour);
275275
}
276276

277277
/**
@@ -385,7 +385,7 @@ export class BlockDragStrategy implements IDragStrategy {
385385
dom.stopTextWidthCache();
386386

387387
blockAnimation.disconnectUiStop();
388-
this.connectionPreviewer!.hidePreview();
388+
this.connectionPreviewer?.hidePreview();
389389

390390
if (!this.block.isDeadOrDying() && this.dragging) {
391391
// These are expensive and don't need to be done if we're deleting, or
@@ -413,7 +413,7 @@ export class BlockDragStrategy implements IDragStrategy {
413413

414414
// Must dispose after connections are applied to not break the dynamic
415415
// connections plugin. See #7859
416-
this.connectionPreviewer!.dispose();
416+
this.connectionPreviewer?.dispose();
417417
this.workspace.setResizesEnabled(true);
418418
eventUtils.setGroup(newGroup);
419419
}
@@ -445,6 +445,9 @@ export class BlockDragStrategy implements IDragStrategy {
445445
return;
446446
}
447447

448+
this.connectionPreviewer?.hidePreview();
449+
this.connectionCandidate = null;
450+
448451
this.startChildConn?.connect(this.block.nextConnection);
449452
if (this.startParentConn) {
450453
switch (this.startParentConn.type) {
@@ -471,9 +474,6 @@ export class BlockDragStrategy implements IDragStrategy {
471474
this.startChildConn = null;
472475
this.startParentConn = null;
473476

474-
this.connectionPreviewer!.hidePreview();
475-
this.connectionCandidate = null;
476-
477477
this.block.setDragging(false);
478478
this.dragging = false;
479479
}

tests/mocha/block_test.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1105,6 +1105,18 @@ suite('Blocks', function () {
11051105
);
11061106
this.textJoinBlock = this.printBlock.getInputTargetBlock('TEXT');
11071107
this.textBlock = this.textJoinBlock.getInputTargetBlock('ADD0');
1108+
this.extraTopBlock = Blockly.Xml.domToBlock(
1109+
Blockly.utils.xml.textToDom(`
1110+
<block type="text_print">
1111+
<value name="TEXT">
1112+
<block type="text">
1113+
<field name="TEXT">drag me</field>
1114+
</block>
1115+
</value>
1116+
</block>`),
1117+
this.workspace,
1118+
);
1119+
this.extraNestedBlock = this.extraTopBlock.getInputTargetBlock('TEXT');
11081120
});
11091121

11101122
function assertBlockIsOnlyChild(parent, child, inputName) {
@@ -1116,6 +1128,10 @@ suite('Blocks', function () {
11161128
assert.equal(nonParent.getChildren().length, 0);
11171129
assert.isNull(nonParent.getInputTargetBlock('TEXT'));
11181130
assert.isNull(orphan.getParent());
1131+
assert.equal(
1132+
orphan.getSvgRoot().parentElement,
1133+
orphan.workspace.getCanvas(),
1134+
);
11191135
}
11201136
function assertOriginalSetup() {
11211137
assertBlockIsOnlyChild(this.printBlock, this.textJoinBlock, 'TEXT');
@@ -1187,6 +1203,27 @@ suite('Blocks', function () {
11871203
);
11881204
assertNonParentAndOrphan(this.textJoinBlock, this.textBlock, 'ADD0');
11891205
});
1206+
test('Setting parent to null with dragging block', function () {
1207+
this.extraTopBlock.setDragging(true);
1208+
this.textBlock.outputConnection.disconnect();
1209+
assert.doesNotThrow(
1210+
this.textBlock.setParent.bind(this.textBlock, null),
1211+
);
1212+
assertNonParentAndOrphan(this.textJoinBlock, this.textBlock, 'ADD0');
1213+
assert.equal(
1214+
this.textBlock.getSvgRoot().nextSibling,
1215+
this.extraTopBlock.getSvgRoot(),
1216+
);
1217+
});
1218+
test('Setting parent to null with non-top dragging block', function () {
1219+
this.extraNestedBlock.setDragging(true);
1220+
this.textBlock.outputConnection.disconnect();
1221+
assert.doesNotThrow(
1222+
this.textBlock.setParent.bind(this.textBlock, null),
1223+
);
1224+
assertNonParentAndOrphan(this.textJoinBlock, this.textBlock, 'ADD0');
1225+
assert.equal(this.textBlock.getSvgRoot().nextSibling, null);
1226+
});
11901227
test('Setting parent to null without disconnecting', function () {
11911228
assert.throws(this.textBlock.setParent.bind(this.textBlock, null));
11921229
assertOriginalSetup.call(this);

0 commit comments

Comments
 (0)