diff --git a/core/connection.js b/core/connection.js
index eeb0718c3..5e17ef25b 100644
--- a/core/connection.js
+++ b/core/connection.js
@@ -773,8 +773,11 @@ Blockly.Connection.prototype.createShadowBlock_ = function(attemptToConnect) {
var blockShadow = Blockly.serialization.blocks.loadInternal(
shadowState,
parentBlock.workspace,
- attemptToConnect ? this : undefined,
- true);
+ {
+ parentConnection: attemptToConnect ? this : undefined,
+ isShadow: true,
+ recordUndo: false,
+ });
return blockShadow;
}
diff --git a/core/serialization/blocks.js b/core/serialization/blocks.js
index d49c875ed..650de5ce1 100644
--- a/core/serialization/blocks.js
+++ b/core/serialization/blocks.js
@@ -279,24 +279,52 @@ const saveConnection = function(connection) {
* @return {!Block} The block that was just loaded.
*/
const load = function(state, workspace, {recordUndo = false} = {}) {
+ return loadInternal(state, workspace, {recordUndo});
+};
+exports.load = load;
+
+/**
+ * Loads the block represented by the given state into the given workspace.
+ * This is defined internally so that the extra parameters don't clutter our
+ * external API.
+ * But it is exported so that other places within Blockly can call it directly
+ * with the extra paramters.
+ * @param {!State} state The state of a block to deserialize into the workspace.
+ * @param {!Workspace} workspace The workspace to add the block to.
+ * @param {{parentConnection: (!Connection|undefined), isShadow:
+ * (boolean|undefined), recordUndo: (boolean|undefined)}=} param1
+ * parentConnection: If provided, the system will attempt to connect the
+ * block to this connection after it is created. Undefined by default.
+ * isShadow: The block will be set to a shadow block after it is created.
+ * False by default.
+ * recordUndo: If true, events triggered by this function will be undo-able
+ * by the user. False by default.
+ * @return {!Block} The block that was just loaded.
+ */
+const loadInternal = function(
+ state,
+ workspace,
+ {
+ parentConnection = undefined,
+ isShadow = false,
+ recordUndo = false
+ } = {}
+) {
const prevRecordUndo = Events.recordUndo;
Events.recordUndo = recordUndo;
const existingGroup = Events.getGroup();
if (!existingGroup) {
Events.setGroup(true);
}
-
- // We only want to fire an event for the top block.
Events.disable();
- const block = loadInternal(state, workspace);
+ const block = loadPrivate(state, workspace, {parentConnection, isShadow});
Events.enable();
Events.fire(new (Events.get(Events.BLOCK_CREATE))(block));
-
Events.setGroup(existingGroup);
Events.recordUndo = prevRecordUndo;
-
+
// Adding connections to the connection db is expensive. This defers that
// operation to decrease load time.
if (workspace.rendered) {
@@ -309,22 +337,32 @@ const load = function(state, workspace, {recordUndo = false} = {}) {
return block;
};
-exports.load = load;
+/** @package */
+exports.loadInternal = loadInternal;
/**
* Loads the block represented by the given state into the given workspace.
- * This is defined internally so that the extra optional parameter doesn't
- * clutter our external API.
+ * This is defined privately so that it can be called recursively without firing
+ * eroneous events. Events (and other things we only want to occur on the top
+ * block) are handled by loadInternal.
* @param {!State} state The state of a block to deserialize into the workspace.
* @param {!Workspace} workspace The workspace to add the block to.
- * @param {!Connection=} parentConnection The optional parent connection to
- * attach the block to.
- * @param {boolean} isShadow Whether the block we are loading is a shadow block
- * or not.
+ * @param {{parentConnection: (!Connection|undefined), isShadow:
+ * (boolean|undefined), recordUndo: (boolean|undefined)}=} param1
+ * parentConnection: If provided, the system will attempt to connect the
+ * block to this connection after it is created. Undefined by default.
+ * isShadow: The block will be set to a shadow block after it is created.
+ * False by default.
* @return {!Block} The block that was just loaded.
*/
-const loadInternal = function(
- state, workspace, parentConnection = undefined, isShadow = false) {
+const loadPrivate = function(
+ state,
+ workspace,
+ {
+ parentConnection = undefined,
+ isShadow = false,
+ } = {}
+) {
if (!state['type']) {
throw new MissingBlockType(state);
}
@@ -340,10 +378,9 @@ const loadInternal = function(
loadInputBlocks(block, state);
loadNextBlocks(block, state);
initBlock(block, workspace.rendered);
+
return block;
};
-/** @package */
-exports.loadInternal = loadInternal;
/**
* Applies any coordinate information available on the state object to the
@@ -533,10 +570,10 @@ const loadConnection = function(connection, connectionState) {
connection.setShadowState(connectionState['shadow']);
}
if (connectionState['block']) {
- loadInternal(
+ loadPrivate(
connectionState['block'],
connection.getSourceBlock().workspace,
- connection);
+ {parentConnection: connection});
}
};
diff --git a/tests/mocha/block_change_event_test.js b/tests/mocha/block_change_event_test.js
index 242546f2e..76e035b58 100644
--- a/tests/mocha/block_change_event_test.js
+++ b/tests/mocha/block_change_event_test.js
@@ -1,4 +1,3 @@
-
/**
* @license
* Copyright 2021 Google LLC
diff --git a/tests/mocha/block_create_event_test.js b/tests/mocha/block_create_event_test.js
new file mode 100644
index 000000000..3d6c246ee
--- /dev/null
+++ b/tests/mocha/block_create_event_test.js
@@ -0,0 +1,53 @@
+/**
+ * @license
+ * Copyright 2021 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+suite('Block Create Event', function() {
+ setup(function() {
+ sharedTestSetup.call(this);
+ this.workspace = new Blockly.Workspace();
+ });
+
+ teardown(function() {
+ sharedTestTeardown.call(this);
+ });
+
+ test('Create shadow on disconnect', function() {
+ Blockly.Events.disable();
+ const block = Blockly.serialization.blocks.load(
+ {
+ "type": "text_print",
+ "inputs": {
+ "TEXT": {
+ "shadow": {
+ "type": "text",
+ "id": "shadowId",
+ "fields": {
+ "TEXT": "abc"
+ }
+ },
+ "block": {
+ "type": "text",
+ "fields": {
+ "TEXT": ""
+ }
+ }
+ }
+ }
+ },
+ this.workspace);
+ Blockly.Events.enable();
+ block.getInput('TEXT').connection.disconnect();
+ assertEventFired(
+ this.eventsFireStub,
+ Blockly.Events.BlockCreate,
+ {'recordUndo': false},
+ this.workspace.id,
+ 'shadowId');
+ const calls = this.eventsFireStub.getCalls();
+ const event = calls[calls.length - 1].args[0];
+ chai.assert.equal(event.xml.tagName, 'shadow');
+ });
+});
diff --git a/tests/mocha/index.html b/tests/mocha/index.html
index 4f67cf441..d2ede9a2a 100644
--- a/tests/mocha/index.html
+++ b/tests/mocha/index.html
@@ -55,6 +55,7 @@
+