Refactor connect logic (#4880)

* Refactor connect logic.

* Fixup from rebase

* Fix build

* PR Comments
This commit is contained in:
Beka Westberg
2021-06-18 09:53:43 -07:00
committed by GitHub
parent 292911062e
commit 13bb9f5bf6
9 changed files with 441 additions and 500 deletions

View File

@@ -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_;
};

View File

@@ -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));
};
/**

View File

@@ -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_;
};
/**

View File

@@ -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;

View File

@@ -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);
}
};
/**

View File

@@ -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));
};
/**

64
package-lock.json generated
View File

@@ -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": {

View File

@@ -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",

View File

@@ -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('<xml><shadow type="output"/></xml>')
.firstChild);
oldChild.getInput('INPUT2').connection.setShadowDom(
Blockly.Xml.textToDom('<xml><shadow type="output"/></xml>')
.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('<xml><shadow type="output"/></xml>')
.firstChild);
childY.getInput('INPUT').connection.setShadowDom(
Blockly.Xml.textToDom('<xml><shadow type="output"/></xml>')
.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('<xml><shadow type="output"/></xml>')
.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('<xml><shadow type="output"/></xml>')
.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('<xml><shadow type="row_block"/></xml>')
.firstChild);
newChild.getInput('INPUT2').connection.setShadowDom(
Blockly.Xml.textToDom('<xml><shadow type="row_block"/></xml>')
.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('<xml><shadow type="row_block"/></xml>')
.firstChild);
childY.getInput('INPUT').connection.setShadowDom(
Blockly.Xml.textToDom('<xml><shadow type="row_block"/></xml>')
.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('<xml><shadow type="row_block"/></xml>')
.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('<xml><shadow type="row_block"/></xml>')
.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);
});
});
});