diff --git a/core/events/events_block_create.ts b/core/events/events_block_create.ts index 9abf5bac3ec..dfb21cd81b2 100644 --- a/core/events/events_block_create.ts +++ b/core/events/events_block_create.ts @@ -138,6 +138,7 @@ export class BlockCreate extends BlockBase { 'the constructor, or call fromJson', ); } + if (allShadowBlocks(workspace, this.ids)) return; if (forward) { blocks.append(this.json, workspace); } else { @@ -154,6 +155,26 @@ export class BlockCreate extends BlockBase { } } } +/** + * Returns true if all blocks in the list are shadow blocks. If so, that means + * the top-level block being created is a shadow block. This only happens when a + * block that was covering up a shadow block is removed. We don't need to create + * an additional block in that case because the original block still has its + * shadow block. + * + * @param workspace Workspace to check for blocks + * @param ids A list of block ids that were created in this event + * @returns True if all block ids in the list are shadow blocks + */ +const allShadowBlocks = function ( + workspace: Workspace, + ids: string[], +): boolean { + const shadows = ids + .map((id) => workspace.getBlockById(id)) + .filter((block) => block && block.isShadow()); + return shadows.length === ids.length; +}; export interface BlockCreateJson extends BlockBaseJson { xml: string; diff --git a/tests/mocha/event_block_create_test.js b/tests/mocha/event_block_create_test.js index b17b277e79e..f3aed672f49 100644 --- a/tests/mocha/event_block_create_test.js +++ b/tests/mocha/event_block_create_test.js @@ -56,6 +56,42 @@ suite('Block Create Event', function () { chai.assert.equal(event.xml.tagName, 'shadow'); }); + test('Does not create extra shadow blocks', function () { + const shadowId = 'shadow_block'; + const blockJson = { + 'type': 'math_arithmetic', + 'id': 'parent_with_shadow', + 'fields': {'OP': 'ADD'}, + 'inputs': { + 'A': { + 'shadow': { + 'type': 'math_number', + 'id': shadowId, + 'fields': {'NUM': 1}, + }, + }, + }, + }; + + // If there is a shadow block on the workspace and then we get + // a block create event with the same ID as the shadow block, + // this represents a block that had been covering a shadow block + // being removed. + Blockly.serialization.blocks.append(blockJson, this.workspace); + const shadowBlock = this.workspace.getBlockById(shadowId); + const blocksBefore = this.workspace.getAllBlocks(); + + const event = new Blockly.Events.BlockCreate(shadowBlock); + event.run(true); + + const blocksAfter = this.workspace.getAllBlocks(); + chai.assert.deepEqual( + blocksAfter, + blocksBefore, + 'No new blocks should be created from an event that only creates shadow blocks', + ); + }); + suite('Serialization', function () { test('events round-trip through JSON', function () { const block = this.workspace.newBlock('row_block', 'block_id');