diff --git a/core/block.js b/core/block.js
index 0f4bf1cb0..f4eba58da 100644
--- a/core/block.js
+++ b/core/block.js
@@ -575,20 +575,21 @@ Blockly.Block.prototype.getConnections_ = function(_all) {
/**
* Walks down a stack of blocks and finds the last next connection on the stack.
+ * @param {boolean} ignoreShadows If true,the last connection on a non-shadow
+ * block will be returned. If false, this will follow shadows to find the
+ * last connection.
* @return {?Blockly.Connection} The last next connection on the stack, or null.
* @package
*/
-Blockly.Block.prototype.lastConnectionInStack = function() {
+Blockly.Block.prototype.lastConnectionInStack = function(ignoreShadows) {
var nextConnection = this.nextConnection;
while (nextConnection) {
var nextBlock = nextConnection.targetBlock();
- if (!nextBlock) {
- // Found a next connection with nothing on the other side.
+ if (!nextBlock || (ignoreShadows && nextBlock.isShadow())) {
return nextConnection;
}
nextConnection = nextBlock.nextConnection;
}
- // Ran out of next connections.
return null;
};
@@ -607,7 +608,6 @@ Blockly.Block.prototype.bumpNeighbours = function() {
* @return {?Blockly.Block} The block (if any) that holds the current block.
*/
Blockly.Block.prototype.getParent = function() {
- // Look at the DOM to see if we are nested in another block.
return this.parentBlock_;
};
diff --git a/core/block_svg.js b/core/block_svg.js
index b5822507b..0917dadf3 100644
--- a/core/block_svg.js
+++ b/core/block_svg.js
@@ -1475,14 +1475,18 @@ Blockly.BlockSvg.prototype.getConnections_ = function(all) {
/**
* Walks down a stack of blocks and finds the last next connection on the stack.
+ * @param {boolean} ignoreShadows If true,the last connection on a non-shadow
+ * block will be returned. If false, this will follow shadows to find the
+ * last connection.
* @return {?Blockly.RenderedConnection} The last next connection on the stack,
* or null.
* @package
* @override
*/
-Blockly.BlockSvg.prototype.lastConnectionInStack = function() {
+Blockly.BlockSvg.prototype.lastConnectionInStack = function(ignoreShadows) {
return /** @type {Blockly.RenderedConnection} */ (
- Blockly.BlockSvg.superClass_.lastConnectionInStack.call(this));
+ Blockly.BlockSvg.superClass_
+ .lastConnectionInStack.call(this, ignoreShadows));
};
/**
diff --git a/core/connection.js b/core/connection.js
index c9415dd05..27526c711 100644
--- a/core/connection.js
+++ b/core/connection.js
@@ -104,106 +104,58 @@ Blockly.Connection.prototype.y = 0;
* @protected
*/
Blockly.Connection.prototype.connect_ = function(childConnection) {
+ var INPUT = Blockly.connectionTypes.INPUT_VALUE;
var parentConnection = this;
var parentBlock = parentConnection.getSourceBlock();
var childBlock = childConnection.getSourceBlock();
- // Disconnect any existing parent on the child connection.
+
+ // Make sure the childConnection is available.
if (childConnection.isConnected()) {
childConnection.disconnect();
}
+
+ // Make sure the parentConnection is available.
+ var orphan;
if (parentConnection.isConnected()) {
- // Other connection is already connected to something.
- // Disconnect it and reattach it or bump it as needed.
- var orphanBlock = parentConnection.targetBlock();
- var shadowDom = parentConnection.getShadowDom();
- // Temporarily set the shadow DOM to null so it does not respawn.
- parentConnection.shadowDom_ = null;
- // Displaced shadow blocks dissolve rather than reattaching or bumping.
- if (orphanBlock.isShadow()) {
- // Save the shadow block so that field values are preserved.
- // This cast assumes that a block can not be both a shadow block and an insertion marker.
- shadowDom = /** @type {!Element} */ (Blockly.Xml.blockToDom(orphanBlock));
- orphanBlock.dispose(false);
- orphanBlock = null;
- } else if (parentConnection.type == Blockly.connectionTypes.INPUT_VALUE) {
- // Value connections.
- // If female block is already connected, disconnect and bump the male.
- if (!orphanBlock.outputConnection) {
- throw Error('Orphan block does not have an output connection.');
- }
- // Attempt to reattach the orphan at the end of the newly inserted
- // block. Since this block may be a row, walk down to the end
- // or to the first (and only) shadow block.
- var connection = Blockly.Connection.getConnectionForOrphanedOutput(
- childBlock, orphanBlock);
- if (connection) {
- orphanBlock.outputConnection.connect(connection);
- orphanBlock = null;
- }
- } else if (
- parentConnection.type == Blockly.connectionTypes.NEXT_STATEMENT) {
- // Statement connections.
- // Statement blocks may be inserted into the middle of a stack.
- // Split the stack.
- if (!orphanBlock.previousConnection) {
- throw Error('Orphan block does not have a previous connection.');
- }
- // Attempt to reattach the orphan at the bottom of the newly inserted
- // block. Since this block may be a stack, walk down to the end.
- var newBlock = childBlock;
- while (newBlock.nextConnection) {
- var nextBlock = newBlock.getNextBlock();
- if (nextBlock && !nextBlock.isShadow()) {
- newBlock = nextBlock;
- } else {
- var checker = orphanBlock.workspace.connectionChecker;
- if (checker.canConnect(
- orphanBlock.previousConnection, newBlock.nextConnection, false)) {
- newBlock.nextConnection.connect(orphanBlock.previousConnection);
- orphanBlock = null;
- }
- break;
- }
- }
- }
- if (orphanBlock) {
- // Unable to reattach orphan.
+ var shadowDom = parentConnection.getShadowDom(true);
+ parentConnection.shadowDom_ = null; // Set to null so it doesn't respawn.
+ var target = parentConnection.targetBlock();
+ if (target.isShadow()) {
+ target.dispose(false);
+ } else {
parentConnection.disconnect();
- if (Blockly.Events.recordUndo) {
- // Bump it off to the side after a moment.
- var group = Blockly.Events.getGroup();
- setTimeout(function() {
- // Verify orphan hasn't been deleted or reconnected.
- if (orphanBlock.workspace && !orphanBlock.getParent()) {
- Blockly.Events.setGroup(group);
- if (orphanBlock.outputConnection) {
- orphanBlock.outputConnection.onFailedConnect(parentConnection);
- } else if (orphanBlock.previousConnection) {
- orphanBlock.previousConnection.onFailedConnect(parentConnection);
- }
- Blockly.Events.setGroup(false);
- }
- }, Blockly.BUMP_DELAY);
- }
+ orphan = target;
}
- // Restore the shadow DOM.
parentConnection.shadowDom_ = shadowDom;
}
+ // Connect the new connection to the parent.
var event;
if (Blockly.Events.isEnabled()) {
event = new (Blockly.Events.get(Blockly.Events.BLOCK_MOVE))(childBlock);
}
- // Establish the connections.
Blockly.Connection.connectReciprocally_(parentConnection, childConnection);
- // Demote the inferior block so that one is a child of the superior one.
childBlock.setParent(parentBlock);
if (event) {
event.recordNew();
Blockly.Events.fire(event);
}
+
+ // Deal with the orphan if it exists.
+ if (orphan) {
+ var orphanConnection = parentConnection.type === INPUT ?
+ orphan.outputConnection : orphan.previousConnection;
+ var connection = Blockly.Connection.getConnectionForOrphanedConnection(
+ childBlock, /** @type {!Blockly.Connection} */ (orphanConnection));
+ if (connection) {
+ orphanConnection.connect(connection);
+ } else {
+ orphanConnection.onFailedConnect(parentConnection);
+ }
+ }
};
+
/**
* Dispose of this connection and deal with connected blocks.
* @package
@@ -318,7 +270,8 @@ Blockly.Connection.prototype.isConnectionAllowed = function(candidate) {
};
/**
- * Behavior after a connection attempt fails.
+ * Called when an attempted connection fails. NOP by default (i.e. for headless
+ * workspaces).
* @param {!Blockly.Connection} _otherConnection Connection that this connection
* failed to connect to.
* @package
@@ -409,9 +362,9 @@ Blockly.Connection.getSingleConnection_ = function(block, orphanBlock) {
* @param {!Blockly.Block} orphanBlock The block that is looking for a home.
* @return {?Blockly.Connection} The suitable connection point on the chain
* of blocks, or null.
- * @package
+ * @private
*/
-Blockly.Connection.getConnectionForOrphanedOutput =
+Blockly.Connection.getConnectionForOrphanedOutput_ =
function(startBlock, orphanBlock) {
var newBlock = startBlock;
var connection;
@@ -425,6 +378,32 @@ Blockly.Connection.getConnectionForOrphanedOutput =
return null;
};
+/**
+ * Returns the connection (starting at the startBlock) which will accept
+ * the given connection. This includes compatible connection types and
+ * connection checks.
+ * @param {!Blockly.Block} startBlock The block on which to start the search.
+ * @param {!Blockly.Connection} orphanConnection The connection that is looking
+ * for a home.
+ * @return {?Blockly.Connection} The suitable connection point on the chain of
+ * blocks, or null.
+ */
+Blockly.Connection.getConnectionForOrphanedConnection =
+ function(startBlock, orphanConnection) {
+ if (orphanConnection.type === Blockly.connectionTypes.OUTPUT_VALUE) {
+ return Blockly.Connection.getConnectionForOrphanedOutput_(
+ startBlock, orphanConnection.getSourceBlock());
+ }
+ // Otherwise we're dealing with a stack.
+ var connection = startBlock.lastConnectionInStack(true);
+ var checker = orphanConnection.getConnectionChecker();
+ if (connection &&
+ checker.canConnect(orphanConnection, connection, false)) {
+ return connection;
+ }
+ return null;
+ };
+
/**
* Disconnect this connection.
*/
@@ -614,11 +593,18 @@ Blockly.Connection.prototype.setShadowDom = function(shadow) {
};
/**
- * Returns the XML representation of the connection's shadow block.
+ * Returns the xml representation of the connection's shadow block.
+ * @param {boolean=} returnCurrent If true, and the shadow block is currently
+ * attached to this connection, this serializes the state of that block
+ * and returns it (so that field values are correct). Otherwise the saved
+ * shadowDom is just returned.
* @return {?Element} Shadow DOM representation of a block or null.
*/
-Blockly.Connection.prototype.getShadowDom = function() {
- return this.shadowDom_;
+Blockly.Connection.prototype.getShadowDom = function(returnCurrent) {
+ return (returnCurrent && this.targetBlock().isShadow()) ?
+ /** @type {!Element} */ (Blockly.Xml.blockToDom(
+ /** @type {!Blockly.Block} */ (this.targetBlock()))) :
+ this.shadowDom_;
};
/**
diff --git a/core/insertion_marker_manager.js b/core/insertion_marker_manager.js
index 0e038d666..d0fea4502 100644
--- a/core/insertion_marker_manager.js
+++ b/core/insertion_marker_manager.js
@@ -331,7 +331,7 @@ Blockly.InsertionMarkerManager.prototype.createMarkerBlock_ = function(sourceBlo
Blockly.InsertionMarkerManager.prototype.initAvailableConnections_ = function() {
var available = this.topBlock_.getConnections_(false);
// Also check the last connection on this stack
- var lastOnStack = this.topBlock_.lastConnectionInStack();
+ var lastOnStack = this.topBlock_.lastConnectionInStack(true);
if (lastOnStack && lastOnStack != this.topBlock_.nextConnection) {
available.push(lastOnStack);
this.lastOnStack_ = lastOnStack;
diff --git a/core/rendered_connection.js b/core/rendered_connection.js
index a9b9c8d33..f9d3864e3 100644
--- a/core/rendered_connection.js
+++ b/core/rendered_connection.js
@@ -449,14 +449,26 @@ Blockly.RenderedConnection.prototype.isConnectionAllowed = function(candidate,
/**
* Behavior after a connection attempt fails.
+ * Bumps this connection away from the other connection. Called when an
+ * attempted connection fails.
* @param {!Blockly.Connection} otherConnection Connection that this connection
* failed to connect to.
* @package
*/
-Blockly.RenderedConnection.prototype.onFailedConnect = function(
- otherConnection) {
- this.bumpAwayFrom(otherConnection);
-};
+Blockly.RenderedConnection.prototype.onFailedConnect =
+ function(otherConnection) {
+ var block = this.getSourceBlock();
+ if (Blockly.Events.recordUndo) {
+ var group = Blockly.Events.getGroup();
+ setTimeout(function() {
+ if (!block.isDisposed() && !block.getParent()) {
+ Blockly.Events.setGroup(group);
+ this.bumpAwayFrom(otherConnection);
+ Blockly.Events.setGroup(false);
+ }
+ }.bind(this), Blockly.BUMP_DELAY);
+ }
+ };
/**
diff --git a/core/renderers/common/renderer.js b/core/renderers/common/renderer.js
index 4d7c6ca17..77df5e518 100644
--- a/core/renderers/common/renderer.js
+++ b/core/renderers/common/renderer.js
@@ -246,23 +246,11 @@ Blockly.blockRendering.Renderer.prototype.shouldHighlightConnection =
*/
Blockly.blockRendering.Renderer.prototype.orphanCanConnectAtEnd =
function(topBlock, orphanBlock, localType) {
- var orphanConnection = null;
- var lastConnection = null;
- if (localType == Blockly.connectionTypes.OUTPUT_VALUE) {
- orphanConnection = orphanBlock.outputConnection;
- lastConnection =
- Blockly.Connection.getConnectionForOrphanedOutput(
- /** @type {!Blockly.Block} **/ (topBlock), orphanBlock);
- } else {
- orphanConnection = orphanBlock.previousConnection;
- lastConnection = topBlock.lastConnectionInStack();
- }
-
- if (!lastConnection) {
- return false;
- }
- return orphanConnection.getConnectionChecker().canConnect(
- lastConnection, orphanConnection, false);
+ var orphanConnection = localType === Blockly.connectionTypes.OUTPUT_VALUE ?
+ orphanBlock.outputConnection : orphanBlock.previousConnection;
+ return !!Blockly.Connection.getConnectionForOrphanedConnection(
+ /** @type {!Blockly.Block} **/ (topBlock),
+ /** @type {!Blockly.Connection} **/ (orphanConnection));
};
/**
diff --git a/package-lock.json b/package-lock.json
index d2f40ff1f..a76db89d3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -248,15 +248,15 @@
"dev": true
},
"@eslint/eslintrc": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.1.tgz",
- "integrity": "sha512-5v7TDE9plVhvxQeWLXDTvFvJBdH6pEsdnl2g/dAptmuFEPedQ4Erq5rsDsX+mvAM610IhNaO2W5V1dOOnDKxkQ==",
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.2.tgz",
+ "integrity": "sha512-8nmGq/4ycLpIwzvhI4tNDmQztZ8sp+hI7cyG8i1nQDhkAbRzHpXPidRAHlNvCZQpJTKw5ItIpMw9RSToGF00mg==",
"dev": true,
"requires": {
"ajv": "^6.12.4",
"debug": "^4.1.1",
"espree": "^7.3.0",
- "globals": "^12.1.0",
+ "globals": "^13.9.0",
"ignore": "^4.0.6",
"import-fresh": "^3.2.1",
"js-yaml": "^3.13.1",
@@ -265,12 +265,12 @@
},
"dependencies": {
"globals": {
- "version": "12.4.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz",
- "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==",
+ "version": "13.9.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.9.0.tgz",
+ "integrity": "sha512-74/FduwI/JaIrr1H8e71UbDE+5x7pIPs1C2rrwC52SszOo043CsWOZEMW7o2Y58xwm9b+0RBKDxY5n2sUpEFxA==",
"dev": true,
"requires": {
- "type-fest": "^0.8.1"
+ "type-fest": "^0.20.2"
}
}
}
@@ -2650,13 +2650,13 @@
}
},
"eslint": {
- "version": "7.27.0",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.27.0.tgz",
- "integrity": "sha512-JZuR6La2ZF0UD384lcbnd0Cgg6QJjiCwhMD6eU4h/VGPcVGwawNNzKU41tgokGXnfjOOyI6QIffthhJTPzzuRA==",
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.28.0.tgz",
+ "integrity": "sha512-UMfH0VSjP0G4p3EWirscJEQ/cHqnT/iuH6oNZOB94nBjWbMnhGEPxsZm1eyIW0C/9jLI0Fow4W5DXLjEI7mn1g==",
"dev": true,
"requires": {
"@babel/code-frame": "7.12.11",
- "@eslint/eslintrc": "^0.4.1",
+ "@eslint/eslintrc": "^0.4.2",
"ajv": "^6.10.0",
"chalk": "^4.0.0",
"cross-spawn": "^7.0.2",
@@ -2673,7 +2673,7 @@
"fast-deep-equal": "^3.1.3",
"file-entry-cache": "^6.0.1",
"functional-red-black-tree": "^1.0.1",
- "glob-parent": "^5.0.0",
+ "glob-parent": "^5.1.2",
"globals": "^13.6.0",
"ignore": "^4.0.6",
"import-fresh": "^3.0.0",
@@ -2706,18 +2706,18 @@
}
},
"@babel/helper-validator-identifier": {
- "version": "7.14.0",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz",
- "integrity": "sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==",
+ "version": "7.14.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz",
+ "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==",
"dev": true
},
"@babel/highlight": {
- "version": "7.14.0",
- "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.0.tgz",
- "integrity": "sha512-YSCOwxvTYEIMSGaBQb5kDDsCopDdiUGsqpatp3fOlI4+2HQSkTmEVWnVuySdAC5EWCqSWWTv0ib63RjR7dTBdg==",
+ "version": "7.14.5",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz",
+ "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==",
"dev": true,
"requires": {
- "@babel/helper-validator-identifier": "^7.14.0",
+ "@babel/helper-validator-identifier": "^7.14.5",
"chalk": "^2.0.0",
"js-tokens": "^4.0.0"
},
@@ -2899,12 +2899,6 @@
"requires": {
"prelude-ls": "^1.2.1"
}
- },
- "type-fest": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
- "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
- "dev": true
}
}
},
@@ -7219,9 +7213,9 @@
}
},
"regexpp": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz",
- "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==",
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz",
+ "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==",
"dev": true
},
"registry-auth-token": {
@@ -8327,9 +8321,9 @@
},
"dependencies": {
"ajv": {
- "version": "8.5.0",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.5.0.tgz",
- "integrity": "sha512-Y2l399Tt1AguU3BPRP9Fn4eN+Or+StUGWCUpbnFyXSo8NZ9S4uj+AG2pjs5apK+ZMOwYOz1+a+VKvKH7CudXgQ==",
+ "version": "8.6.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.0.tgz",
+ "integrity": "sha512-cnUG4NSBiM4YFBxgZIj/In3/6KX+rQ2l2YPRVcvAMQGWEPKuXoPIhxzwqh31jA3IPbI4qEOp/5ILI4ynioXsGQ==",
"dev": true,
"requires": {
"fast-deep-equal": "^3.1.1",
@@ -8661,9 +8655,9 @@
"dev": true
},
"type-fest": {
- "version": "0.8.1",
- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz",
- "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==",
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
+ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
"dev": true
},
"typedarray": {
diff --git a/package.json b/package.json
index 3df16bb0a..ec9c1b8e1 100644
--- a/package.json
+++ b/package.json
@@ -65,7 +65,7 @@
"chai": "^4.2.0",
"clang-format": "^1.5.0",
"concurrently": "^6.0.0",
- "eslint": "^7.6.0",
+ "eslint": "^7.28.0",
"google-closure-compiler": "^20210505.0.0",
"google-closure-deps": "^20210406.0.0",
"gulp": "^4.0.2",
diff --git a/tests/mocha/connection_test.js b/tests/mocha/connection_test.js
index 118d27412..67f2c75e2 100644
--- a/tests/mocha/connection_test.js
+++ b/tests/mocha/connection_test.js
@@ -782,329 +782,6 @@ suite('Connection', function() {
});
});
- suite('getConnectionForOrphanedOutput', function() {
- setup(function() {
- this.workspace = new Blockly.Workspace();
-
- Blockly.defineBlocksWithJsonArray([
- {
- 'type': 'input',
- 'message0': '%1',
- 'args0': [
- {
- 'type': 'input_value',
- 'name': 'INPUT',
- 'check': 'check'
- }
- ],
- },
- {
- 'type': 'output',
- 'message0': '',
- 'output': 'check',
- },
- ]);
- });
-
- teardown(function() {
- workspaceTeardown.call(this, this.workspace);
- });
-
- suite('No available spots', function() {
- setup(function() {
- Blockly.defineBlocksWithJsonArray([
- {
- 'type': 'output_and_statements',
- 'message0': '%1 %2',
- 'args0': [
- {
- 'type': 'input_statement',
- 'name': 'INPUT',
- 'check': 'check'
- },
- {
- 'type': 'input_statement',
- 'name': 'INPUT2',
- 'check': 'check'
- }
- ],
- 'output': 'check',
- },
- {
- 'type': 'output_and_inputs',
- 'message0': '%1 %2',
- 'args0': [
- {
- 'type': 'input_value',
- 'name': 'INPUT',
- 'check': 'check2'
- },
- {
- 'type': 'input_value',
- 'name': 'INPUT2',
- 'check': 'check2'
- }
- ],
- 'output': 'check',
- },
- {
- 'type': 'check_to_check2',
- 'message0': '%1',
- 'args0': [
- {
- 'type': 'input_value',
- 'name': 'INPUT',
- 'check': 'check2'
- },
- ],
- 'output': 'check',
- },
- {
- 'type': 'check2_to_check',
- 'message0': '%1',
- 'args0': [
- {
- 'type': 'input_value',
- 'name': 'CHECK2TOCHECKINPUT',
- 'check': 'check'
- },
- ],
- 'output': 'check2',
- },
- ]);
- });
-
- test('No connection', function() {
- const parent = this.workspace.newBlock('input');
- const oldChild = this.workspace.newBlock('output');
- const newChild = this.workspace.newBlock('output');
-
- parent.getInput('INPUT').connection.connect(oldChild.outputConnection);
- chai.assert.notExists(
- Blockly.Connection.getConnectionForOrphanedOutput(oldChild, newChild));
- });
-
- test('All statements', function() {
- const parent = this.workspace.newBlock('input');
- const oldChild = this.workspace.newBlock('output_and_statements');
- const newChild = this.workspace.newBlock('output');
-
- parent.getInput('INPUT').connection.connect(oldChild.outputConnection);
- chai.assert.notExists(
- Blockly.Connection.getConnectionForOrphanedOutput(oldChild, newChild));
- });
-
- test('Bad checks', function() {
- const parent = this.workspace.newBlock('input');
- const oldChild = this.workspace.newBlock('output_and_inputs');
- const newChild = this.workspace.newBlock('output');
-
- parent.getInput('INPUT').connection.connect(oldChild.outputConnection);
- chai.assert.notExists(
- Blockly.Connection.getConnectionForOrphanedOutput(oldChild, newChild));
- });
-
- test('Through different types', function() {
- const parent = this.workspace.newBlock('input');
- const oldChild = this.workspace.newBlock('check_to_check2');
- const otherChild = this.workspace.newBlock('check2_to_check');
- const newChild = this.workspace.newBlock('output');
-
- parent.getInput('INPUT').connection
- .connect(oldChild.outputConnection);
- oldChild.getInput('INPUT').connection
- .connect(otherChild.outputConnection);
-
- chai.assert.notExists(
- Blockly.Connection.getConnectionForOrphanedOutput(oldChild, newChild));
- });
- });
-
- suite('Multiple available spots', function() {
- setup(function() {
- Blockly.defineBlocksWithJsonArray([
- {
- 'type': 'multiple_inputs',
- 'message0': '%1 %2',
- 'args0': [
- {
- 'type': 'input_value',
- 'name': 'INPUT',
- 'check': 'check'
- },
- {
- 'type': 'input_value',
- 'name': 'INPUT2',
- 'check': 'check'
- },
- ],
- 'output': 'check',
- },
- {
- 'type': 'single_input',
- 'message0': '%1',
- 'args0': [
- {
- 'type': 'input_value',
- 'name': 'INPUT',
- 'check': 'check'
- },
- ],
- 'output': 'check',
- },
- ]);
- });
-
- suite('No shadows', function() {
- test('Top block', function() {
- const parent = this.workspace.newBlock('input');
- const oldChild = this.workspace.newBlock('multiple_inputs');
- const newChild = this.workspace.newBlock('output');
-
- parent.getInput('INPUT').connection.connect(oldChild.outputConnection);
- chai.assert.notExists(
- Blockly.Connection.getConnectionForOrphanedOutput(oldChild, newChild));
- });
-
- test('Child blocks', function() {
- const parent = this.workspace.newBlock('input');
- const oldChild = this.workspace.newBlock('multiple_inputs');
- const childX = this.workspace.newBlock('single_input');
- const childY = this.workspace.newBlock('single_input');
- const newChild = this.workspace.newBlock('output');
-
- parent.getInput('INPUT').connection.connect(oldChild.outputConnection);
- oldChild.getInput('INPUT').connection.connect(childX.outputConnection);
- oldChild.getInput('INPUT2').connection.connect(childY.outputConnection);
-
- chai.assert.notExists(
- Blockly.Connection.getConnectionForOrphanedOutput(oldChild, newChild));
- });
-
- test('Spots filled', function() {
- const parent = this.workspace.newBlock('input');
- const oldChild = this.workspace.newBlock('multiple_inputs');
- const otherChild = this.workspace.newBlock('output');
- const newChild = this.workspace.newBlock('output');
-
- parent.getInput('INPUT').connection
- .connect(oldChild.outputConnection);
- oldChild.getInput('INPUT').connection
- .connect(otherChild.outputConnection);
-
- chai.assert.notExists(
- Blockly.Connection.getConnectionForOrphanedOutput(oldChild, newChild));
- });
- });
-
- suite('Shadows', function() {
- test('Top block', function() {
- const parent = this.workspace.newBlock('input');
- const oldChild = this.workspace.newBlock('multiple_inputs');
- const newChild = this.workspace.newBlock('output');
-
- parent.getInput('INPUT').connection.connect(oldChild.outputConnection);
- oldChild.getInput('INPUT').connection.setShadowDom(
- Blockly.Xml.textToDom('')
- .firstChild);
- oldChild.getInput('INPUT2').connection.setShadowDom(
- Blockly.Xml.textToDom('')
- .firstChild);
-
- chai.assert.notExists(
- Blockly.Connection.getConnectionForOrphanedOutput(oldChild, newChild));
- });
-
- test('Child blocks', function() {
- const parent = this.workspace.newBlock('input');
- const oldChild = this.workspace.newBlock('multiple_inputs');
- const childX = this.workspace.newBlock('single_input');
- const childY = this.workspace.newBlock('single_input');
- const newChild = this.workspace.newBlock('output');
-
- parent.getInput('INPUT').connection.connect(oldChild.outputConnection);
- oldChild.getInput('INPUT').connection.connect(childX.outputConnection);
- oldChild.getInput('INPUT2').connection.connect(childY.outputConnection);
- childX.getInput('INPUT').connection.setShadowDom(
- Blockly.Xml.textToDom('')
- .firstChild);
- childY.getInput('INPUT').connection.setShadowDom(
- Blockly.Xml.textToDom('')
- .firstChild);
-
- chai.assert.notExists(
- Blockly.Connection.getConnectionForOrphanedOutput(oldChild, newChild));
- });
-
- test('Spots filled', function() {
- const parent = this.workspace.newBlock('input');
- const oldChild = this.workspace.newBlock('multiple_inputs');
- const otherChild = this.workspace.newBlock('output');
- const newChild = this.workspace.newBlock('output');
-
- parent.getInput('INPUT').connection
- .connect(oldChild.outputConnection);
- oldChild.getInput('INPUT').connection
- .connect(otherChild.outputConnection);
- oldChild.getInput('INPUT2').connection.setShadowDom(
- Blockly.Xml.textToDom('')
- .firstChild);
-
- chai.assert.notExists(
- Blockly.Connection.getConnectionForOrphanedOutput(oldChild, newChild));
- });
- });
- });
-
- suite('Single available spot', function() {
- setup(function() {
- Blockly.defineBlocksWithJsonArray([
- {
- 'type': 'single_input',
- 'message0': '%1',
- 'args0': [
- {
- 'type': 'input_value',
- 'name': 'INPUT',
- 'check': 'check'
- },
- ],
- 'output': 'check',
- },
- ]);
- });
-
- test('No shadows', function() {
- const parent = this.workspace.newBlock('input');
- const oldChild = this.workspace.newBlock('single_input');
- const newChild = this.workspace.newBlock('output');
-
- parent.getInput('INPUT').connection.connect(oldChild.outputConnection);
-
- const result = Blockly.Connection
- .getConnectionForOrphanedOutput(oldChild, newChild);
- chai.assert.exists(result);
- chai.assert.equal(result.getParentInput().name, 'INPUT');
- });
-
- test('Shadows', function() {
- const parent = this.workspace.newBlock('input');
- const oldChild = this.workspace.newBlock('single_input');
- const newChild = this.workspace.newBlock('output');
-
- parent.getInput('INPUT').connection.connect(oldChild.outputConnection);
- oldChild.getInput('INPUT').connection.setShadowDom(
- Blockly.Xml.textToDom('')
- .firstChild);
-
- const result = Blockly.Connection
- .getConnectionForOrphanedOutput(oldChild, newChild);
- chai.assert.exists(result);
- chai.assert.equal(result.getParentInput().name, 'INPUT');
- });
- });
- });
-
suite('Connect', function() {
setup(function() {
this.workspace = new Blockly.Workspace();
@@ -1185,6 +862,40 @@ suite('Connection', function() {
"message0": "",
"output": 'check1'
},
+ {
+ "type": "row_block_multiple_inputs",
+ "message0": "%1 %2",
+ "args0": [
+ {
+ "type": "input_value",
+ "name": "INPUT",
+ "check": 'check1'
+ },
+ {
+ "type": "input_value",
+ "name": "INPUT2",
+ "check": 'check1'
+ }
+ ],
+ "output": 'check1'
+ },
+ {
+ 'type': 'output_to_statements',
+ 'message0': '%1 %2',
+ 'args0': [
+ {
+ 'type': 'input_statement',
+ 'name': 'INPUT',
+ 'check': 'check1'
+ },
+ {
+ 'type': 'input_statement',
+ 'name': 'INPUT2',
+ 'check': 'check1'
+ }
+ ],
+ 'output': 'check1',
+ },
{
"type": "statement_block",
"message0": "%1 %2",
@@ -1401,42 +1112,288 @@ suite('Connection', function() {
suite('Reattach or bump orphan', function() {
suite('Value', function() {
- // Only one test for this b/c tested by getConnectionForOrphanedOutput.
- test('Simple', function() {
- var parent = this.workspace.newBlock('row_block');
- var oldChild = this.workspace.newBlock('row_block');
- var newChild = this.workspace.newBlock('row_block');
- parent.getInput('INPUT').connection.connect(oldChild.outputConnection);
- var spy = sinon.spy(
- Blockly.Connection, 'getConnectionForOrphanedOutput');
+ suite('No available spots', function() {
+ test('No connection', function() {
+ var parent = this.workspace.newBlock('row_block');
+ var oldChild = this.workspace.newBlock('row_block');
+ var newChild = this.workspace.newBlock('row_block_noend');
+ parent.getInput('INPUT').connection
+ .connect(oldChild.outputConnection);
- parent.getInput('INPUT').connection.connect(newChild.outputConnection);
+ parent.getInput('INPUT').connection
+ .connect(newChild.outputConnection);
- chai.assert.isTrue(parent.getInput('INPUT').connection.isConnected());
- chai.assert.equal(parent.getInputTargetBlock('INPUT'), newChild);
- chai.assert.isTrue(newChild.getInput('INPUT').connection.isConnected());
- chai.assert.equal(newChild.getInputTargetBlock('INPUT'), oldChild);
- // Make sure it is actually getting called, so all functionality has
- // been tested.
- // Future people: if you ever stop calling this function you need to
- // add more tests for reattaching orphans.
- chai.assert.isTrue(spy.calledOnce);
- this.assertBlockCount(3);
+ chai.assert.isTrue(
+ parent.getInput('INPUT').connection.isConnected());
+ chai.assert.equal(
+ parent.getInputTargetBlock('INPUT'), newChild);
+ chai.assert.isFalse(
+ oldChild.outputConnection.isConnected());
+ });
+
+ test('All statements', function() {
+ var parent = this.workspace.newBlock('row_block');
+ var oldChild = this.workspace.newBlock('row_block');
+ var newChild = this.workspace.newBlock('output_to_statements');
+ parent.getInput('INPUT').connection
+ .connect(oldChild.outputConnection);
+
+ parent.getInput('INPUT').connection
+ .connect(newChild.outputConnection);
+
+ chai.assert.isTrue(
+ parent.getInput('INPUT').connection.isConnected());
+ chai.assert.equal(
+ parent.getInputTargetBlock('INPUT'), newChild);
+ chai.assert.isFalse(
+ oldChild.outputConnection.isConnected());
+ });
+
+ test('Bad checks', function() {
+ var parent = this.workspace.newBlock('row_block');
+ var oldChild = this.workspace.newBlock('row_block');
+ var newChild = this.workspace.newBlock('row_block_2to1');
+ parent.getInput('INPUT').connection
+ .connect(oldChild.outputConnection);
+
+ parent.getInput('INPUT').connection
+ .connect(newChild.outputConnection);
+
+ chai.assert.isTrue(
+ parent.getInput('INPUT').connection.isConnected());
+ chai.assert.equal(
+ parent.getInputTargetBlock('INPUT'), newChild);
+ chai.assert.isFalse(
+ oldChild.outputConnection.isConnected());
+ });
+
+ test('Through different types', function() {
+ const parent = this.workspace.newBlock('row_block');
+ const oldChild = this.workspace.newBlock('row_block');
+ const newChild = this.workspace.newBlock('row_block_2to1');
+ const otherChild = this.workspace.newBlock('row_block_1to2');
+
+ parent.getInput('INPUT').connection
+ .connect(oldChild.outputConnection);
+ newChild.getInput('INPUT').connection
+ .connect(otherChild.outputConnection);
+
+ parent.getInput('INPUT').connection
+ .connect(newChild.outputConnection);
+
+ chai.assert.isTrue(
+ parent.getInput('INPUT').connection.isConnected());
+ chai.assert.equal(
+ parent.getInputTargetBlock('INPUT'), newChild);
+ chai.assert.isFalse(
+ oldChild.outputConnection.isConnected());
+ });
});
- test('Bump', function() {
- var parent = this.workspace.newBlock('row_block');
- var oldChild = this.workspace.newBlock('row_block');
- var newChild = this.workspace.newBlock('row_block_noend');
- parent.getInput('INPUT').connection.connect(oldChild.outputConnection);
- var spy = sinon.spy(oldChild.outputConnection, 'onFailedConnect');
+ suite('Multiple available spots', function() {
+ suite('No shadows', function() {
+ test('Top block', function() {
+ const parent = this.workspace.newBlock('row_block');
+ const oldChild = this.workspace.newBlock('row_block');
+ const newChild = this.workspace.newBlock(
+ 'row_block_multiple_inputs');
- parent.getInput('INPUT').connection.connect(newChild.outputConnection);
+ parent.getInput('INPUT').connection
+ .connect(oldChild.outputConnection);
- chai.assert.isTrue(parent.getInput('INPUT').connection.isConnected());
- chai.assert.equal(parent.getInputTargetBlock('INPUT'), newChild);
- chai.assert.isTrue(spy.calledOnce);
- this.assertBlockCount(3);
+ parent.getInput('INPUT').connection
+ .connect(newChild.outputConnection);
+
+ chai.assert.isTrue(
+ parent.getInput('INPUT').connection.isConnected());
+ chai.assert.equal(
+ parent.getInputTargetBlock('INPUT'), newChild);
+ chai.assert.isFalse(
+ oldChild.outputConnection.isConnected());
+ });
+
+ test('Child blocks', function() {
+ const parent = this.workspace.newBlock('row_block');
+ const oldChild = this.workspace.newBlock('row_block');
+ const newChild = this.workspace.newBlock(
+ 'row_block_multiple_inputs');
+ const childX = this.workspace.newBlock('row_block');
+ const childY = this.workspace.newBlock('row_block');
+
+ parent.getInput('INPUT').connection
+ .connect(oldChild.outputConnection);
+ newChild.getInput('INPUT').connection
+ .connect(childX.outputConnection);
+ newChild.getInput('INPUT2').connection
+ .connect(childY.outputConnection);
+
+ parent.getInput('INPUT').connection
+ .connect(newChild.outputConnection);
+
+ chai.assert.isTrue(
+ parent.getInput('INPUT').connection.isConnected());
+ chai.assert.equal(
+ parent.getInputTargetBlock('INPUT'), newChild);
+ chai.assert.isFalse(
+ oldChild.outputConnection.isConnected());
+ });
+
+ test('Spots filled', function() {
+ const parent = this.workspace.newBlock('row_block');
+ const oldChild = this.workspace.newBlock('row_block');
+ const newChild = this.workspace.newBlock(
+ 'row_block_multiple_inputs');
+ const otherChild = this.workspace.newBlock('row_block_noend');
+
+ parent.getInput('INPUT').connection
+ .connect(oldChild.outputConnection);
+ newChild.getInput('INPUT').connection
+ .connect(otherChild.outputConnection);
+
+ parent.getInput('INPUT').connection
+ .connect(newChild.outputConnection);
+
+ chai.assert.isTrue(
+ parent.getInput('INPUT').connection.isConnected());
+ chai.assert.equal(
+ parent.getInputTargetBlock('INPUT'), newChild);
+ chai.assert.isFalse(
+ oldChild.outputConnection.isConnected());
+ });
+ });
+
+ suite('Shadows', function() {
+ test('Top block', function() {
+ const parent = this.workspace.newBlock('row_block');
+ const oldChild = this.workspace.newBlock('row_block');
+ const newChild = this.workspace.newBlock(
+ 'row_block_multiple_inputs');
+
+ parent.getInput('INPUT').connection
+ .connect(oldChild.outputConnection);
+ newChild.getInput('INPUT').connection.setShadowDom(
+ Blockly.Xml.textToDom('')
+ .firstChild);
+ newChild.getInput('INPUT2').connection.setShadowDom(
+ Blockly.Xml.textToDom('')
+ .firstChild);
+
+ parent.getInput('INPUT').connection
+ .connect(newChild.outputConnection);
+
+ chai.assert.isTrue(
+ parent.getInput('INPUT').connection.isConnected());
+ chai.assert.equal(
+ parent.getInputTargetBlock('INPUT'), newChild);
+ chai.assert.isFalse(
+ oldChild.outputConnection.isConnected());
+ });
+
+ test('Child blocks', function() {
+ const parent = this.workspace.newBlock('row_block');
+ const oldChild = this.workspace.newBlock('row_block');
+ const newChild = this.workspace.newBlock(
+ 'row_block_multiple_inputs');
+ const childX = this.workspace.newBlock('row_block');
+ const childY = this.workspace.newBlock('row_block');
+
+ parent.getInput('INPUT').connection
+ .connect(oldChild.outputConnection);
+ newChild.getInput('INPUT').connection
+ .connect(childX.outputConnection);
+ newChild.getInput('INPUT2').connection
+ .connect(childY.outputConnection);
+ childX.getInput('INPUT').connection.setShadowDom(
+ Blockly.Xml.textToDom('')
+ .firstChild);
+ childY.getInput('INPUT').connection.setShadowDom(
+ Blockly.Xml.textToDom('')
+ .firstChild);
+
+ parent.getInput('INPUT').connection
+ .connect(newChild.outputConnection);
+
+ chai.assert.isTrue(
+ parent.getInput('INPUT').connection.isConnected());
+ chai.assert.equal(
+ parent.getInputTargetBlock('INPUT'), newChild);
+ chai.assert.isFalse(
+ oldChild.outputConnection.isConnected());
+ });
+
+ test('Spots filled', function() {
+ const parent = this.workspace.newBlock('row_block');
+ const oldChild = this.workspace.newBlock('row_block');
+ const newChild = this.workspace.newBlock(
+ 'row_block_multiple_inputs');
+ const otherChild = this.workspace.newBlock('row_block_noend');
+
+ parent.getInput('INPUT').connection
+ .connect(oldChild.outputConnection);
+ newChild.getInput('INPUT').connection
+ .connect(otherChild.outputConnection);
+ newChild.getInput('INPUT2').connection.setShadowDom(
+ Blockly.Xml.textToDom('')
+ .firstChild);
+
+ parent.getInput('INPUT').connection
+ .connect(newChild.outputConnection);
+
+ chai.assert.isTrue(
+ parent.getInput('INPUT').connection.isConnected());
+ chai.assert.equal(
+ parent.getInputTargetBlock('INPUT'), newChild);
+ chai.assert.isFalse(
+ oldChild.outputConnection.isConnected());
+ });
+ });
+ });
+
+ suite('Single available spot', function() {
+ test('No shadows', function() {
+ const parent = this.workspace.newBlock('row_block');
+ const oldChild = this.workspace.newBlock('row_block');
+ const newChild = this.workspace.newBlock('row_block');
+
+ parent.getInput('INPUT').connection
+ .connect(oldChild.outputConnection);
+
+ parent.getInput('INPUT').connection
+ .connect(newChild.outputConnection);
+
+ chai.assert.isTrue(
+ parent.getInput('INPUT').connection.isConnected());
+ chai.assert.equal(
+ parent.getInputTargetBlock('INPUT'), newChild);
+ chai.assert.isTrue(
+ newChild.getInput('INPUT').connection.isConnected());
+ chai.assert.equal(
+ newChild.getInputTargetBlock('INPUT'), oldChild);
+ });
+
+ test('Shadows', function() {
+ const parent = this.workspace.newBlock('row_block');
+ const oldChild = this.workspace.newBlock('row_block');
+ const newChild = this.workspace.newBlock('row_block');
+
+ parent.getInput('INPUT').connection
+ .connect(oldChild.outputConnection);
+ newChild.getInput('INPUT').connection.setShadowDom(
+ Blockly.Xml.textToDom('')
+ .firstChild);
+
+ parent.getInput('INPUT').connection
+ .connect(newChild.outputConnection);
+
+ chai.assert.isTrue(
+ parent.getInput('INPUT').connection.isConnected());
+ chai.assert.equal(
+ parent.getInputTargetBlock('INPUT'), newChild);
+ chai.assert.isTrue(
+ newChild.getInput('INPUT').connection.isConnected());
+ chai.assert.equal(
+ newChild.getInputTargetBlock('INPUT'), oldChild);
+ });
});
});