Skip to content

Commit 568e6ff

Browse files
authored
fix: Fix bug that caused blocks to be inserted into an immovable stack. (#750)
* fix: Fix bug that caused blocks to be inserted into an immovable stack. * fix: Fix bug that caused input blocks to bump immovable blocks on insert.
1 parent 0888bc2 commit 568e6ff

File tree

3 files changed

+128
-14
lines changed

3 files changed

+128
-14
lines changed

src/navigation.ts

Lines changed: 47 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -414,24 +414,59 @@ export class Navigation {
414414
const inputType = movingHasOutput
415415
? Blockly.inputs.inputTypes.VALUE
416416
: Blockly.inputs.inputTypes.STATEMENT;
417-
const compatibleInputs = stationaryNode.inputList.filter(
418-
(input) => input.type === inputType,
419-
);
420-
const input = compatibleInputs.length > 0 ? compatibleInputs[0] : null;
421-
let connection = input?.connection;
422-
if (connection) {
417+
const compatibleConnections = stationaryNode.inputList
418+
.filter((input) => input.type === inputType)
419+
.map((input) => input.connection);
420+
for (const connection of compatibleConnections) {
421+
let targetConnection: Blockly.Connection | null | undefined =
422+
connection;
423423
if (inputType === Blockly.inputs.inputTypes.STATEMENT) {
424-
while (connection.targetBlock()?.nextConnection) {
425-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
426-
connection = connection.targetBlock()!.nextConnection!;
424+
while (targetConnection?.targetBlock()?.nextConnection) {
425+
targetConnection = targetConnection?.targetBlock()?.nextConnection;
427426
}
428427
}
429-
return connection as Blockly.RenderedConnection;
428+
429+
if (
430+
targetConnection &&
431+
movingBlock.workspace.connectionChecker.canConnect(
432+
movingHasOutput
433+
? movingBlock.outputConnection
434+
: movingBlock.previousConnection,
435+
targetConnection,
436+
true,
437+
// Since we're connecting programmatically, we don't care how
438+
// close the blocks are when determining if they can be connected.
439+
Infinity,
440+
)
441+
) {
442+
return targetConnection as Blockly.RenderedConnection;
443+
}
430444
}
431445

432-
// 2. Connect statement blocks to next connection.
446+
// 2. Connect statement blocks to next connection. Only return a next
447+
// connection to which the statement block can actually connect; some
448+
// may be ineligible because they are e.g. in the middle of an immovable
449+
// stack.
433450
if (stationaryNode.nextConnection && !movingHasOutput) {
434-
return stationaryNode.nextConnection;
451+
let nextConnection: Blockly.RenderedConnection | null =
452+
stationaryNode.nextConnection;
453+
while (nextConnection) {
454+
if (
455+
movingBlock.workspace.connectionChecker.canConnect(
456+
movingBlock.previousConnection,
457+
nextConnection,
458+
true,
459+
// Since we're connecting programmatically, we don't care how
460+
// close the blocks are when determining if they can be connected.
461+
Infinity,
462+
)
463+
) {
464+
return nextConnection;
465+
}
466+
nextConnection =
467+
nextConnection.getSourceBlock().getNextBlock()?.nextConnection ??
468+
null;
469+
}
435470
}
436471

437472
// 3. Output connection. This will wrap around or displace.

test/loadTestBlocks.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,7 @@ const moreBlocks = {
343343
'DO0': {
344344
'block': {
345345
'type': 'text_print',
346-
'id': 'uSxT~QT8p%D2o)b~)Dki',
346+
'id': 'text_print_2',
347347
'inputs': {
348348
'TEXT': {
349349
'shadow': {
@@ -388,7 +388,7 @@ const moreBlocks = {
388388
'next': {
389389
'block': {
390390
'type': 'text_print',
391-
'id': '-bTQ2YVSuBS/SYn[C^LX',
391+
'id': 'text_print_3',
392392
'inputs': {
393393
'TEXT': {
394394
'shadow': {

test/webdriverio/test/insert_test.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66

77
import * as chai from 'chai';
8+
import * as Blockly from 'blockly';
89
import {Key} from 'webdriverio';
910
import {
1011
getFocusedBlockType,
@@ -16,6 +17,7 @@ import {
1617
testSetup,
1718
sendKeyAndWait,
1819
keyRight,
20+
keyDown,
1921
getCurrentFocusedBlockId,
2022
blockIsPresent,
2123
keyUp,
@@ -103,4 +105,81 @@ suite('Insert test', function () {
103105
await getFocusedBlockType(this.browser),
104106
);
105107
});
108+
109+
test('Does not insert between immovable blocks', async function () {
110+
// Focus the create canvas block; we want to ensure that the newly
111+
// inserted block is not attached to its next connection, because doing
112+
// so would splice it into an immovable stack.
113+
await focusOnBlock(this.browser, 'create_canvas_1');
114+
await this.browser.execute(() => {
115+
Blockly.getMainWorkspace()
116+
.getAllBlocks()
117+
.forEach((b) => b.setMovable(false));
118+
});
119+
await tabNavigateToToolbox(this.browser);
120+
121+
// Insert 'if' block
122+
await keyRight(this.browser);
123+
// Choose.
124+
await sendKeyAndWait(this.browser, Key.Enter);
125+
// Confirm position.
126+
await sendKeyAndWait(this.browser, Key.Enter);
127+
128+
// Assert inserted inside first block p5_setup not at top-level.
129+
chai.assert.equal('controls_if', await getFocusedBlockType(this.browser));
130+
await keyUp(this.browser);
131+
chai.assert.equal(
132+
'p5_background_color',
133+
await getFocusedBlockType(this.browser),
134+
);
135+
});
136+
});
137+
138+
suite('Insert test with more blocks', function () {
139+
// Disable timeouts when non-zero PAUSE_TIME is used to watch tests run.
140+
if (PAUSE_TIME) this.timeout(0);
141+
142+
// Clear the workspace and load start blocks.
143+
setup(async function () {
144+
this.browser = await testSetup(
145+
testFileLocations.MORE_BLOCKS,
146+
this.timeout(),
147+
);
148+
await this.browser.pause(PAUSE_TIME);
149+
});
150+
151+
test('Does not bump immovable input blocks on insert', async function () {
152+
// Focus the print block with a connected input block. Ordinarily, inserting
153+
// an input block would connect it to this block and bump its child, but
154+
// if all blocks are immovable the connected input block should not move
155+
// and the newly inserted block should be added as a top-level block on the
156+
// workspace.
157+
await focusOnBlock(this.browser, 'text_print_2');
158+
await this.browser.execute(() => {
159+
Blockly.getMainWorkspace()
160+
.getAllBlocks()
161+
.forEach((b) => b.setMovable(false));
162+
});
163+
await tabNavigateToToolbox(this.browser);
164+
165+
// Insert number block
166+
await keyDown(this.browser, 2);
167+
await keyRight(this.browser);
168+
// Choose.
169+
await sendKeyAndWait(this.browser, Key.Enter);
170+
// Confirm position.
171+
await sendKeyAndWait(this.browser, Key.Enter);
172+
173+
// Assert inserted at the top-level due to immovable block occupying the
174+
// selected block's input.
175+
chai.assert.equal('math_number', await getFocusedBlockType(this.browser));
176+
const focusedBlockIsParentless = await this.browser.execute(() => {
177+
const focusedNode = Blockly.getFocusManager().getFocusedNode();
178+
return (
179+
focusedNode instanceof Blockly.BlockSvg &&
180+
focusedNode.getParent() === null
181+
);
182+
});
183+
chai.assert.isTrue(focusedBlockIsParentless);
184+
});
106185
});

0 commit comments

Comments
 (0)