diff --git a/core/connection.js b/core/connection.js index 9cd65f3c59c..e5a632a326f 100644 --- a/core/connection.js +++ b/core/connection.js @@ -134,7 +134,7 @@ Blockly.Connection.prototype.connect_ = function(childConnection) { // 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( + var connection = Blockly.Connection.lastConnectionInRow( childBlock, orphanBlock); if (connection) { orphanBlock.outputConnection.connect(connection); @@ -372,31 +372,30 @@ Blockly.Connection.connectReciprocally_ = function(first, second) { }; /** - * Returns the single connection on the block that will accept the orphaned - * block, if one can be found. If the block has multiple compatible connections - * (even if they are filled) this returns null. If the block has no compatible - * connections, this returns null. + * Does the given block have one and only one connection point that will accept + * an orphaned block? * @param {!Blockly.Block} block The superior block. * @param {!Blockly.Block} orphanBlock The inferior block. * @return {Blockly.Connection} The suitable connection point on 'block', * or null. * @private */ -Blockly.Connection.getSingleConnection_ = function(block, orphanBlock) { - var foundConnection = null; +Blockly.Connection.singleConnection_ = function(block, orphanBlock) { + var connection = null; var output = orphanBlock.outputConnection; - var typeChecker = output.getConnectionChecker(); - - for (var i = 0, input; (input = block.inputList[i]); i++) { - var connection = input.connection; - if (connection && typeChecker.canConnect(output, connection, false)) { - if (foundConnection) { + for (var i = 0; i < block.inputList.length; i++) { + var thisConnection = block.inputList[i].connection; + var typeChecker = output.getConnectionChecker(); + if (thisConnection && + thisConnection.type == Blockly.connectionTypes.INPUT_VALUE && + typeChecker.canConnect(output, thisConnection, false)) { + if (connection) { return null; // More than one connection. } - foundConnection = connection; + connection = thisConnection; } } - return foundConnection; + return connection; }; /** @@ -411,19 +410,18 @@ Blockly.Connection.getSingleConnection_ = function(block, orphanBlock) { * of blocks, or null. * @package */ -Blockly.Connection.getConnectionForOrphanedOutput = - function(startBlock, orphanBlock) { - var newBlock = startBlock; - var connection; - while ((connection = Blockly.Connection.getSingleConnection_( - /** @type {!Blockly.Block} */ (newBlock), orphanBlock))) { - newBlock = connection.targetBlock(); - if (!newBlock || newBlock.isShadow()) { - return connection; - } - } - return null; - }; +Blockly.Connection.lastConnectionInRow = function(startBlock, orphanBlock) { + var newBlock = startBlock; + var connection; + while ((connection = Blockly.Connection.singleConnection_( + /** @type {!Blockly.Block} */ (newBlock), orphanBlock))) { + newBlock = connection.targetBlock(); + if (!newBlock || newBlock.isShadow()) { + return connection; + } + } + return null; +}; /** * Disconnect this connection. diff --git a/core/renderers/common/renderer.js b/core/renderers/common/renderer.js index 4d7c6ca170f..af575403f0b 100644 --- a/core/renderers/common/renderer.js +++ b/core/renderers/common/renderer.js @@ -248,13 +248,19 @@ Blockly.blockRendering.Renderer.prototype.orphanCanConnectAtEnd = function(topBlock, orphanBlock, localType) { var orphanConnection = null; var lastConnection = null; - if (localType == Blockly.connectionTypes.OUTPUT_VALUE) { + if (localType == + Blockly.connectionTypes + .OUTPUT_VALUE) { // We are replacing an output. orphanConnection = orphanBlock.outputConnection; - lastConnection = - Blockly.Connection.getConnectionForOrphanedOutput( + // TODO: I don't think this function necessarily has the correct logic, + // but for now it is being kept for behavioral backwards-compat. + lastConnection = Blockly.Connection + .lastConnectionInRow( /** @type {!Blockly.Block} **/ (topBlock), orphanBlock); - } else { + } else { // We are replacing a previous. orphanConnection = orphanBlock.previousConnection; + // TODO: This lives on the block while lastConnectionInRow lives on + // on the connection. Something is fishy. lastConnection = topBlock.lastConnectionInStack(); } diff --git a/tests/mocha/connection_test.js b/tests/mocha/connection_test.js index 3d96c18c5c6..73882b90b80 100644 --- a/tests/mocha/connection_test.js +++ b/tests/mocha/connection_test.js @@ -156,6 +156,9 @@ suite('Connection', function() { teardown(function() { workspaceTeardown.call(this, this.workspace); + delete Blockly.Blocks['stack_block']; + delete Blockly.Blocks['row_block']; + delete Blockly.Blocks['statement_block']; }); suite('Add - No Block Connected', function() { @@ -781,327 +784,4 @@ 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'); - }); - }); - }); });