diff --git a/tests/mocha/connection_test.js b/tests/mocha/connection_test.js
index 3d96c18c5..2f7fe0907 100644
--- a/tests/mocha/connection_test.js
+++ b/tests/mocha/connection_test.js
@@ -1104,4 +1104,633 @@ suite('Connection', function() {
});
});
});
+
+ suite('Connect', function() {
+ setup(function() {
+ this.workspace = new Blockly.Workspace();
+ Blockly.defineBlocksWithJsonArray([
+ {
+ "type": "stack_block",
+ "message0": "%1",
+ "args0": [
+ {
+ "type": "field_input",
+ "name": "FIELD",
+ "text": "default"
+ }
+ ],
+ "previousStatement": 'check1',
+ "nextStatement": 'check1'
+ },
+ {
+ "type": "stack_block_1to2",
+ "message0": "",
+ "previousStatement": 'check1',
+ "nextStatement": 'check2'
+ },
+ {
+ "type": "stack_block_2to1",
+ "message0": "",
+ "previousStatement": 'check2',
+ "nextStatement": 'check1'
+ },
+ {
+ "type": "stack_block_noend",
+ "message0": "",
+ "previousStatement": 'check1',
+ },
+ {
+ "type": "row_block",
+ "message0": "%1 %2",
+ "args0": [
+ {
+ "type": "field_input",
+ "name": "FIELD",
+ "text": "default"
+ },
+ {
+ "type": "input_value",
+ "name": "INPUT",
+ "check": 'check1'
+ }
+ ],
+ "output": 'check1'
+ },
+ {
+ "type": "row_block_1to2",
+ "message0": "%1",
+ "args0": [
+ {
+ "type": "input_value",
+ "name": "INPUT",
+ "check": 'check1'
+ }
+ ],
+ "output": 'check2'
+ },
+ {
+ "type": "row_block_2to1",
+ "message0": "%1",
+ "args0": [
+ {
+ "type": "input_value",
+ "name": "INPUT",
+ "check": 'check2'
+ }
+ ],
+ "output": 'check1'
+ },
+ {
+ "type": "row_block_noend",
+ "message0": "",
+ "output": 'check1'
+ },
+ {
+ "type": "statement_block",
+ "message0": "%1 %2",
+ "args0": [
+ {
+ "type": "field_input",
+ "name": "FIELD",
+ "text": "default"
+ },
+ {
+ "type": "input_statement",
+ "name": "STATEMENT",
+ "check": 'check1'
+ }
+ ],
+ "previousStatement": 'check1',
+ "nextStatement": 'check1'
+ },
+ {
+ "type": "statement_block_1to2",
+ "message0": "%1",
+ "args0": [
+ {
+ "type": "input_statement",
+ "name": "STATEMENT",
+ "check": 'check1'
+ }
+ ],
+ "previousStatement": 'check1',
+ "nextStatement": 'check2'
+ },
+ {
+ "type": "statement_block_2to1",
+ "message0": "%1",
+ "args0": [
+ {
+ "type": "input_statement",
+ "name": "STATEMENT",
+ "check": 'check2'
+ }
+ ],
+ "previousStatement": 'check2',
+ "nextStatement": 'check1'
+ },
+ {
+ "type": "statement_block_noend",
+ "message0": "%1",
+ "args0": [
+ {
+ "type": "input_statement",
+ "name": "STATEMENT",
+ "check": 'check1'
+ }
+ ],
+ "previousStatement": 'check1',
+ },
+ ]);
+
+ // Used to make sure we don't get stray shadow blocks or anything.
+ this.assertBlockCount = function(count) {
+ chai.assert.equal(this.workspace.getAllBlocks().length, count);
+ };
+ });
+
+ teardown(function() {
+ sharedTestTeardown.call(this);
+ });
+
+ suite('Disconnect from old parent', function() {
+ test('Value', function() {
+ var oldParent = this.workspace.newBlock('row_block');
+ var newParent = this.workspace.newBlock('row_block');
+ var child = this.workspace.newBlock('row_block');
+
+ oldParent.getInput('INPUT').connection.connect(child.outputConnection);
+ newParent.getInput('INPUT').connection.connect(child.outputConnection);
+
+ chai.assert.isFalse(
+ oldParent.getInput('INPUT').connection.isConnected());
+ this.assertBlockCount(3);
+ });
+
+ test('Statement', function() {
+ var oldParent = this.workspace.newBlock('statement_block');
+ var newParent = this.workspace.newBlock('statement_block');
+ var child = this.workspace.newBlock('stack_block');
+
+ oldParent.getInput('STATEMENT').connection
+ .connect(child.previousConnection);
+ newParent.getInput('STATEMENT').connection
+ .connect(child.previousConnection);
+
+ chai.assert.isFalse(
+ oldParent.getInput('STATEMENT').connection.isConnected());
+ this.assertBlockCount(3);
+ });
+
+ test('Next', function() {
+ var oldParent = this.workspace.newBlock('stack_block');
+ var newParent = this.workspace.newBlock('stack_block');
+ var child = this.workspace.newBlock('stack_block');
+
+ oldParent.nextConnection.connect(child.previousConnection);
+ newParent.nextConnection.connect(child.previousConnection);
+
+ chai.assert.isFalse(oldParent.nextConnection.isConnected());
+ this.assertBlockCount(3);
+ });
+ });
+
+ suite('Shadow dissolves', function() {
+ test('Value', function() {
+ var newParent = this.workspace.newBlock('row_block');
+ var child = this.workspace.newBlock('row_block');
+ var xml = Blockly.Xml.textToDom(
+ ''
+ );
+ newParent.getInput('INPUT').connection.setShadowDom(xml);
+ chai.assert.isTrue(newParent.getInputTargetBlock('INPUT').isShadow());
+
+ newParent.getInput('INPUT').connection.connect(child.outputConnection);
+
+ chai.assert.isFalse(newParent.getInputTargetBlock('INPUT').isShadow());
+ this.assertBlockCount(2);
+ });
+
+ test('Statement', function() {
+ var newParent = this.workspace.newBlock('statement_block');
+ var child = this.workspace.newBlock('stack_block');
+ var xml = Blockly.Xml.textToDom(
+ ''
+ );
+ newParent.getInput('STATEMENT').connection.setShadowDom(xml);
+ chai.assert.isTrue(
+ newParent.getInputTargetBlock('STATEMENT').isShadow());
+
+ newParent.getInput('STATEMENT').connection
+ .connect(child.previousConnection);
+
+ chai.assert.isFalse(
+ newParent.getInputTargetBlock('STATEMENT').isShadow());
+ this.assertBlockCount(2);
+ });
+
+ test('Next', function() {
+ var newParent = this.workspace.newBlock('stack_block');
+ var child = this.workspace.newBlock('stack_block');
+ var xml = Blockly.Xml.textToDom(
+ ''
+ );
+ newParent.nextConnection.setShadowDom(xml);
+ chai.assert.isTrue(newParent.getNextBlock().isShadow());
+
+ newParent.nextConnection.connect(child.previousConnection);
+
+ chai.assert.isFalse(newParent.getNextBlock().isShadow());
+ this.assertBlockCount(2);
+ });
+ });
+
+ suite('Saving shadow values', function() {
+ test('Value', function() {
+ var newParent = this.workspace.newBlock('row_block');
+ var child = this.workspace.newBlock('row_block');
+ var xml = Blockly.Xml.textToDom(
+ ''
+ );
+ newParent.getInput('INPUT').connection.setShadowDom(xml);
+ newParent.getInputTargetBlock('INPUT').setFieldValue('new', 'FIELD');
+
+ newParent.getInput('INPUT').connection.connect(child.outputConnection);
+ newParent.getInput('INPUT').connection.disconnect();
+
+ const target = newParent.getInputTargetBlock('INPUT');
+ chai.assert.isTrue(target.isShadow());
+ chai.assert.equal(target.getFieldValue('FIELD'), 'new');
+ this.assertBlockCount(3);
+ });
+
+ test('Statement', function() {
+ var newParent = this.workspace.newBlock('statement_block');
+ var child = this.workspace.newBlock('stack_block');
+ var xml = Blockly.Xml.textToDom(
+ ''
+ );
+ newParent.getInput('STATEMENT').connection.setShadowDom(xml);
+ newParent.getInputTargetBlock('STATEMENT')
+ .setFieldValue('new', 'FIELD');
+
+ newParent.getInput('STATEMENT').connection
+ .connect(child.previousConnection);
+ newParent.getInput('STATEMENT').connection.disconnect();
+
+ const target = newParent.getInputTargetBlock('STATEMENT');
+ chai.assert.isTrue(target.isShadow());
+ chai.assert.equal(target.getFieldValue('FIELD'), 'new');
+ this.assertBlockCount(3);
+ });
+
+ test('Next', function() {
+ var newParent = this.workspace.newBlock('stack_block');
+ var child = this.workspace.newBlock('stack_block');
+ var xml = Blockly.Xml.textToDom(
+ ''
+ );
+ newParent.nextConnection.setShadowDom(xml);
+ newParent.getNextBlock().setFieldValue('new', 'FIELD');
+
+ newParent.nextConnection.connect(child.previousConnection);
+ newParent.nextConnection.disconnect();
+
+ const target = newParent.getNextBlock();
+ chai.assert.isTrue(target.isShadow());
+ chai.assert.equal(target.getFieldValue('FIELD'), 'new');
+ this.assertBlockCount(3);
+ });
+ });
+
+ 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');
+
+ 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);
+ });
+
+ 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');
+
+ 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(spy.calledOnce);
+ this.assertBlockCount(3);
+ });
+ });
+
+ suite('Statement', function() {
+ suite('No shadows', function() {
+ test('Simple', function() {
+ var parent = this.workspace.newBlock('statement_block');
+ var oldChild = this.workspace.newBlock('stack_block');
+ var newChild = this.workspace.newBlock('stack_block');
+ parent.getInput('STATEMENT').connection
+ .connect(oldChild.previousConnection);
+
+ parent.getInput('STATEMENT').connection
+ .connect(newChild.previousConnection);
+
+ chai.assert.isTrue(
+ parent.getInput('STATEMENT').connection.isConnected());
+ chai.assert.equal(
+ parent.getInputTargetBlock('STATEMENT'), newChild);
+ chai.assert.isTrue(newChild.nextConnection.isConnected());
+ chai.assert.equal(newChild.getNextBlock(), oldChild);
+ this.assertBlockCount(3);
+ });
+
+ test('Bad check in between', function() {
+ var parent = this.workspace.newBlock('statement_block');
+ var oldChild = this.workspace.newBlock('stack_block');
+ var newChild1 = this.workspace.newBlock('stack_block_1to2');
+ var newChild2 = this.workspace.newBlock('stack_block_2to1');
+ parent.getInput('STATEMENT').connection
+ .connect(oldChild.previousConnection);
+ newChild1.nextConnection.connect(newChild2.previousConnection);
+
+ parent.getInput('STATEMENT').connection
+ .connect(newChild1.previousConnection);
+
+ chai.assert.isTrue(
+ parent.getInput('STATEMENT').connection.isConnected());
+ chai.assert.equal(
+ parent.getInputTargetBlock('STATEMENT'), newChild1);
+ chai.assert.isTrue(newChild2.nextConnection.isConnected());
+ chai.assert.equal(newChild2.getNextBlock(), oldChild);
+ this.assertBlockCount(4);
+ });
+
+ test('Bad check at end', function() {
+ var parent = this.workspace.newBlock('statement_block');
+ var oldChild = this.workspace.newBlock('stack_block');
+ var newChild = this.workspace.newBlock('stack_block_1to2');
+ parent.getInput('STATEMENT').connection
+ .connect(oldChild.previousConnection);
+ var spy = sinon.spy(oldChild.previousConnection, 'onFailedConnect');
+
+ parent.getInput('STATEMENT').connection
+ .connect(newChild.previousConnection);
+
+ chai.assert.isTrue(
+ parent.getInput('STATEMENT').connection.isConnected());
+ chai.assert.equal(
+ parent.getInputTargetBlock('STATEMENT'), newChild);
+ chai.assert.isFalse(newChild.nextConnection.isConnected());
+ chai.assert.isTrue(spy.calledOnce);
+ this.assertBlockCount(3);
+ });
+
+ test('No end connection', function() {
+ var parent = this.workspace.newBlock('statement_block');
+ var oldChild = this.workspace.newBlock('stack_block');
+ var newChild = this.workspace.newBlock('stack_block_noend');
+ parent.getInput('STATEMENT').connection
+ .connect(oldChild.previousConnection);
+ var spy = sinon.spy(oldChild.previousConnection, 'onFailedConnect');
+
+ parent.getInput('STATEMENT').connection
+ .connect(newChild.previousConnection);
+
+ chai.assert.isTrue(
+ parent.getInput('STATEMENT').connection.isConnected());
+ chai.assert.equal(
+ parent.getInputTargetBlock('STATEMENT'), newChild);
+ chai.assert.isTrue(spy.calledOnce);
+ this.assertBlockCount(3);
+ });
+ });
+
+ suite('Shadows', function() {
+ test('Simple', function() {
+ var parent = this.workspace.newBlock('statement_block');
+ var oldChild = this.workspace.newBlock('stack_block');
+ var newChild = this.workspace.newBlock('stack_block');
+ parent.getInput('STATEMENT').connection
+ .connect(oldChild.previousConnection);
+ var xml = Blockly.Xml.textToDom(
+ ''
+ );
+ newChild.nextConnection.setShadowDom(xml);
+
+ parent.getInput('STATEMENT').connection
+ .connect(newChild.previousConnection);
+
+ chai.assert.isTrue(
+ parent.getInput('STATEMENT').connection.isConnected());
+ chai.assert.equal(
+ parent.getInputTargetBlock('STATEMENT'), newChild);
+ chai.assert.isTrue(newChild.nextConnection.isConnected());
+ chai.assert.equal(newChild.getNextBlock(), oldChild);
+ this.assertBlockCount(3);
+ });
+
+ test('Bad check in between', function() {
+ var parent = this.workspace.newBlock('statement_block');
+ var oldChild = this.workspace.newBlock('stack_block');
+ var newChild1 = this.workspace.newBlock('stack_block_1to2');
+ var newChild2 = this.workspace.newBlock('stack_block_2to1');
+ parent.getInput('STATEMENT').connection
+ .connect(oldChild.previousConnection);
+ newChild1.nextConnection.connect(newChild2.previousConnection);
+ var xml = Blockly.Xml.textToDom(
+ ''
+ );
+ newChild2.nextConnection.setShadowDom(xml);
+
+ parent.getInput('STATEMENT').connection
+ .connect(newChild1.previousConnection);
+
+ chai.assert.isTrue(
+ parent.getInput('STATEMENT').connection.isConnected());
+ chai.assert.equal(
+ parent.getInputTargetBlock('STATEMENT'), newChild1);
+ chai.assert.isTrue(newChild2.nextConnection.isConnected());
+ chai.assert.equal(newChild2.getNextBlock(), oldChild);
+ this.assertBlockCount(4);
+ });
+
+ test('Bad check at end', function() {
+ var parent = this.workspace.newBlock('statement_block');
+ var oldChild = this.workspace.newBlock('stack_block');
+ var newChild = this.workspace.newBlock('stack_block_1to2');
+ parent.getInput('STATEMENT').connection
+ .connect(oldChild.previousConnection);
+ var xml = Blockly.Xml.textToDom(
+ ''
+ );
+ newChild.nextConnection.setShadowDom(xml);
+ var spy = sinon.spy(oldChild.previousConnection, 'onFailedConnect');
+
+ parent.getInput('STATEMENT').connection
+ .connect(newChild.previousConnection);
+
+ chai.assert.isTrue(
+ parent.getInput('STATEMENT').connection.isConnected());
+ chai.assert.equal(
+ parent.getInputTargetBlock('STATEMENT'), newChild);
+ chai.assert.isTrue(newChild.nextConnection.isConnected());
+ chai.assert.isTrue(newChild.getNextBlock().isShadow());
+ chai.assert.isTrue(spy.calledOnce);
+ this.assertBlockCount(4);
+ });
+ });
+ });
+
+ suite('Next', function() {
+ suite('No shadows', function() {
+ test('Simple', function() {
+ var parent = this.workspace.newBlock('stack_block');
+ var oldChild = this.workspace.newBlock('stack_block');
+ var newChild = this.workspace.newBlock('stack_block');
+ parent.nextConnection.connect(oldChild.previousConnection);
+
+ parent.nextConnection.connect(newChild.previousConnection);
+
+ chai.assert.isTrue(parent.nextConnection.isConnected());
+ chai.assert.equal(parent.getNextBlock(), newChild);
+ chai.assert.isTrue(newChild.nextConnection.isConnected());
+ chai.assert.equal(newChild.getNextBlock(), oldChild);
+ this.assertBlockCount(3);
+ });
+
+ test('Bad check in between', function() {
+ var parent = this.workspace.newBlock('stack_block');
+ var oldChild = this.workspace.newBlock('stack_block');
+ var newChild1 = this.workspace.newBlock('stack_block_1to2');
+ var newChild2 = this.workspace.newBlock('stack_block_2to1');
+ parent.nextConnection.connect(oldChild.previousConnection);
+ newChild1.nextConnection.connect(newChild2.previousConnection);
+
+ parent.nextConnection.connect(newChild1.previousConnection);
+
+ chai.assert.isTrue(parent.nextConnection.isConnected());
+ chai.assert.equal(parent.getNextBlock(), newChild1);
+ chai.assert.isTrue(newChild2.nextConnection.isConnected());
+ chai.assert.equal(newChild2.getNextBlock(), oldChild);
+ this.assertBlockCount(4);
+ });
+
+ test('Bad check at end', function() {
+ var parent = this.workspace.newBlock('stack_block');
+ var oldChild = this.workspace.newBlock('stack_block');
+ var newChild = this.workspace.newBlock('stack_block_1to2');
+ parent.nextConnection.connect(oldChild.previousConnection);
+ var spy = sinon.spy(oldChild.previousConnection, 'onFailedConnect');
+
+ parent.nextConnection.connect(newChild.previousConnection);
+
+ chai.assert.isTrue(parent.nextConnection.isConnected());
+ chai.assert.equal(parent.getNextBlock(), newChild);
+ chai.assert.isFalse(newChild.nextConnection.isConnected());
+ chai.assert.isTrue(spy.calledOnce);
+ this.assertBlockCount(3);
+ });
+
+ test('No end connection', function() {
+ var parent = this.workspace.newBlock('stack_block');
+ var oldChild = this.workspace.newBlock('stack_block');
+ var newChild = this.workspace.newBlock('stack_block_noend');
+ parent.nextConnection.connect(oldChild.previousConnection);
+ var spy = sinon.spy(oldChild.previousConnection, 'onFailedConnect');
+
+ parent.nextConnection.connect(newChild.previousConnection);
+
+ chai.assert.isTrue(parent.nextConnection.isConnected());
+ chai.assert.equal(parent.getNextBlock(), newChild);
+ chai.assert.isTrue(spy.calledOnce);
+ this.assertBlockCount(3);
+ });
+ });
+
+ suite('Shadows', function() {
+ test('Simple', function() {
+ var parent = this.workspace.newBlock('stack_block');
+ var oldChild = this.workspace.newBlock('stack_block');
+ var newChild = this.workspace.newBlock('stack_block');
+ parent.nextConnection.connect(oldChild.previousConnection);
+ var xml = Blockly.Xml.textToDom(
+ ''
+ );
+ newChild.nextConnection.setShadowDom(xml);
+
+ parent.nextConnection.connect(newChild.previousConnection);
+
+ chai.assert.isTrue(parent.nextConnection.isConnected());
+ chai.assert.equal(parent.getNextBlock(), newChild);
+ chai.assert.isTrue(newChild.nextConnection.isConnected());
+ chai.assert.equal(newChild.getNextBlock(), oldChild);
+ this.assertBlockCount(3);
+ });
+
+ test('Bad check in between', function() {
+ var parent = this.workspace.newBlock('stack_block');
+ var oldChild = this.workspace.newBlock('stack_block');
+ var newChild1 = this.workspace.newBlock('stack_block_1to2');
+ var newChild2 = this.workspace.newBlock('stack_block_2to1');
+ parent.nextConnection.connect(oldChild.previousConnection);
+ newChild1.nextConnection.connect(newChild2.previousConnection);
+ var xml = Blockly.Xml.textToDom(
+ ''
+ );
+ newChild2.nextConnection.setShadowDom(xml);
+
+ parent.nextConnection.connect(newChild1.previousConnection);
+
+ chai.assert.isTrue(parent.nextConnection.isConnected());
+ chai.assert.equal(parent.getNextBlock(), newChild1);
+ chai.assert.isTrue(newChild2.nextConnection.isConnected());
+ chai.assert.equal(newChild2.getNextBlock(), oldChild);
+ this.assertBlockCount(4);
+ });
+
+ test('Bad check at end', function() {
+ var parent = this.workspace.newBlock('stack_block');
+ var oldChild = this.workspace.newBlock('stack_block');
+ var newChild = this.workspace.newBlock('stack_block_1to2');
+ parent.nextConnection.connect(oldChild.previousConnection);
+ var xml = Blockly.Xml.textToDom(
+ ''
+ );
+ newChild.nextConnection.setShadowDom(xml);
+ var spy = sinon.spy(oldChild.previousConnection, 'onFailedConnect');
+
+ parent.nextConnection.connect(newChild.previousConnection);
+
+ chai.assert.isTrue(parent.nextConnection.isConnected());
+ chai.assert.equal(parent.getNextBlock(), newChild);
+ chai.assert.isTrue(newChild.nextConnection.isConnected());
+ chai.assert.isTrue(newChild.getNextBlock().isShadow());
+ chai.assert.isTrue(spy.calledOnce);
+ this.assertBlockCount(4);
+ });
+ });
+ });
+ });
+ });
});