diff --git a/core/block_svg.js b/core/block_svg.js
index 18f09d5d0..a1fb14f09 100644
--- a/core/block_svg.js
+++ b/core/block_svg.js
@@ -140,14 +140,6 @@ Blockly.BlockSvg = function(workspace, prototypeName, opt_id) {
* @private
*/
this.markerSvg_ = null;
-
- /**
- * Should the block tell its connections to start tracking inside the render
- * method?
- * @type {boolean}
- * @private
- */
- this.callTrackConnections_ = true;
};
Blockly.utils.object.inherits(Blockly.BlockSvg, Blockly.Block);
@@ -1525,58 +1517,45 @@ Blockly.BlockSvg.prototype.appendInput_ = function(type, name) {
};
/**
- * Tell the block to wait for an outside source to call
- * startTrackingConnections, rather than starting connection
- * tracking automatically.
+ * Sets whether this block's connections are tracked in the database or not.
*
- * Also tells children of this block to wait.
+ * Used by the deserializer to be more efficient. Setting a connection's
+ * tracked_ value to false keeps it from adding itself to the db when it
+ * gets its first moveTo call, saving expensive ops for later.
+ * @param {boolean} track If true, start tracking. If false, stop tracking.
* @package
*/
-Blockly.BlockSvg.prototype.waitToTrackConnections = function() {
- this.callTrackConnections_ = false;
- var children = this.getChildren(false);
- for (var i = 0, child; child = children[i]; i++) {
- child.waitToTrackConnections();
- }
-};
-
-/**
- * Tell this block's connections to add themselves to the connection
- * database (i.e. start tracking).
- *
- * All following/next blocks will be told to start tracking. Inner blocks
- * (i.e. blocks attached to value/statement inputs) will be told to start
- * tracking if this block is not collapsed.
- * @package
- */
-Blockly.BlockSvg.prototype.startTrackingConnections = function() {
+Blockly.BlockSvg.prototype.setConnectionTracking = function(track) {
if (this.previousConnection) {
- this.previousConnection.setTracking(true);
+ this.previousConnection.setTracking(track);
}
if (this.outputConnection) {
- this.outputConnection.setTracking(true);
+ this.outputConnection.setTracking(track);
}
if (this.nextConnection) {
- this.nextConnection.setTracking(true);
+ this.nextConnection.setTracking(track);
var child = this.nextConnection.targetBlock();
if (child) {
- child.startTrackingConnections();
+ child.setConnectionTracking(track);
}
}
if (this.collapsed_) {
+ // When track is true, we don't want to start tracking collapsed
+ // connections. When track is false, we're already not tracking
+ // collapsed connections, so no need to update.
return;
}
for (var i = 0; i < this.inputList.length; i++) {
var conn = this.inputList[i].connection;
if (conn) {
- conn.setTracking(true);
+ conn.setTracking(track);
// Pass tracking on down the chain.
var block = conn.targetBlock();
if (block) {
- block.startTrackingConnections();
+ block.setConnectionTracking(track);
}
}
}
@@ -1776,13 +1755,6 @@ Blockly.BlockSvg.prototype.render = function(opt_bubble) {
(/** @type {!Blockly.WorkspaceSvg} */ (this.workspace)).getRenderer().render(this);
// No matter how we rendered, connection locations should now be correct.
this.updateConnectionLocations_();
- // TODO: This should be handled inside a robust init method, because it would
- // make it a lot cleaner, but for now it's handled here for backwards
- // compatibility.
- if (this.callTrackConnections_) {
- this.startTrackingConnections();
- this.callTrackConnections_ = false;
- }
if (opt_bubble !== false) {
// Render all blocks above this one (propagate a reflow).
var parentBlock = this.getParent();
diff --git a/core/keyboard_nav/navigation.js b/core/keyboard_nav/navigation.js
index 7f6523554..b5d4aaa08 100644
--- a/core/keyboard_nav/navigation.js
+++ b/core/keyboard_nav/navigation.js
@@ -202,10 +202,10 @@ Blockly.navigation.insertFromFlyout = function() {
var newBlock = flyout.createBlock(curBlock);
// Render to get the sizing right.
newBlock.render();
- // Connections are hidden when the block is first created. Normally there's
- // enough time for them to become unhidden in the user's mouse movements,
- // but not here.
- newBlock.startTrackingConnections();
+ // Connections are not tracked when the block is first created. Normally
+ // there's enough time for them to become tracked in the user's mouse
+ // movements, but not here.
+ newBlock.setConnectionTracking(true);
workspace.getCursor().setCurNode(
Blockly.ASTNode.createBlockNode(newBlock));
if (!Blockly.navigation.modify_()) {
diff --git a/core/rendered_connection.js b/core/rendered_connection.js
index acce573ad..67129772e 100644
--- a/core/rendered_connection.js
+++ b/core/rendered_connection.js
@@ -65,14 +65,32 @@ Blockly.RenderedConnection = function(source, type) {
this.offsetInBlock_ = new Blockly.utils.Coordinate(0, 0);
/**
- * Whether this connections is tracked in the database or not.
- * @type {boolean}
+ * Describes the state of this connection's tracked-ness.
+ * @type {Blockly.RenderedConnection.TrackedState}
* @private
*/
- this.tracked_ = false;
+ this.trackedState_ = Blockly.RenderedConnection.TrackedState.WILL_TRACK;
};
Blockly.utils.object.inherits(Blockly.RenderedConnection, Blockly.Connection);
+/**
+ * Enum for different kinds of tracked states.
+ *
+ * WILL_TRACK means that this connection will add itself to
+ * the db on the next moveTo call it receives.
+ *
+ * UNTRACKED means that this connection will not add
+ * itself to the database until setTracking(true) is explicitly called.
+ *
+ * TRACKED means that this connection is currently being tracked.
+ * @enum {number}
+ */
+Blockly.RenderedConnection.TrackedState = {
+ WILL_TRACK: -1,
+ UNTRACKED: 0,
+ TRACKED: 1
+};
+
/**
* Dispose of this connection. Remove it from the database (if it is
* tracked) and call the super-function to deal with connected blocks.
@@ -81,7 +99,7 @@ Blockly.utils.object.inherits(Blockly.RenderedConnection, Blockly.Connection);
*/
Blockly.RenderedConnection.prototype.dispose = function() {
Blockly.RenderedConnection.superClass_.dispose.call(this);
- if (this.tracked_) {
+ if (this.trackedState_ == Blockly.RenderedConnection.TrackedState.TRACKED) {
this.db_.removeConnection(this, this.y);
}
};
@@ -174,7 +192,11 @@ Blockly.RenderedConnection.prototype.bumpAwayFrom = function(staticConnection) {
* @param {number} y New absolute y coordinate, in workspace coordinates.
*/
Blockly.RenderedConnection.prototype.moveTo = function(x, y) {
- if (this.tracked_) {
+ if (this.trackedState_ == Blockly.RenderedConnection.TrackedState.WILL_TRACK) {
+ this.db_.addConnection(this, y);
+ this.trackedState_ = Blockly.RenderedConnection.TrackedState.TRACKED;
+ } else if (this.trackedState_ == Blockly.RenderedConnection
+ .TrackedState.TRACKED) {
this.db_.removeConnection(this, this.y);
this.db_.addConnection(this, y);
}
@@ -307,7 +329,10 @@ Blockly.RenderedConnection.prototype.unhighlight = function() {
* @package
*/
Blockly.RenderedConnection.prototype.setTracking = function(doTracking) {
- if (doTracking == this.tracked_) {
+ if ((doTracking && this.trackedState_ ==
+ Blockly.RenderedConnection.TrackedState.TRACKED) ||
+ (!doTracking && this.trackedState_ ==
+ Blockly.RenderedConnection.TrackedState.UNTRACKED)) {
return;
}
if (this.sourceBlock_.isInFlyout) {
@@ -316,10 +341,14 @@ Blockly.RenderedConnection.prototype.setTracking = function(doTracking) {
}
if (doTracking) {
this.db_.addConnection(this, this.y);
- } else {
+ this.trackedState_ = Blockly.RenderedConnection.TrackedState.TRACKED;
+ return;
+ }
+ if (this.trackedState_ == Blockly.RenderedConnection
+ .TrackedState.TRACKED) {
this.db_.removeConnection(this, this.y);
}
- this.tracked_ = doTracking;
+ this.trackedState_ = Blockly.RenderedConnection.TrackedState.UNTRACKED;
};
/**
diff --git a/core/xml.js b/core/xml.js
index f581da08f..f146b7761 100644
--- a/core/xml.js
+++ b/core/xml.js
@@ -545,7 +545,7 @@ Blockly.Xml.domToBlock = function(xmlBlock, workspace) {
var blocks = topBlock.getDescendants(false);
if (workspace.rendered) {
// Wait to track connections to speed up assembly.
- topBlock.waitToTrackConnections();
+ topBlock.setConnectionTracking(false);
// Render each block.
for (var i = blocks.length - 1; i >= 0; i--) {
blocks[i].initSvg();
@@ -557,7 +557,7 @@ Blockly.Xml.domToBlock = function(xmlBlock, workspace) {
// blocks have rendered.
setTimeout(function() {
if (!topBlock.disposed) {
- topBlock.startTrackingConnections();
+ topBlock.setConnectionTracking(true);
}
}, 1);
topBlock.updateDisabled();
diff --git a/tests/mocha/block_test.js b/tests/mocha/block_test.js
index d0cc4f1a8..27b496fcb 100644
--- a/tests/mocha/block_test.js
+++ b/tests/mocha/block_test.js
@@ -19,6 +19,10 @@ suite('Blocks', function() {
setup(function() {
this.workspace = new Blockly.Workspace();
Blockly.defineBlocksWithJsonArray([
+ {
+ "type": "empty_block",
+ "message0": ""
+ },
{
"type": "stack_block",
"message0": "",
@@ -56,914 +60,1011 @@ suite('Blocks', function() {
delete Blockly.Blocks['statement_block'];
});
- suite('Connection Management', function() {
- suite('Unplug', function() {
- function assertUnpluggedNoheal(blocks) {
- // A has nothing connected to it.
- assertEquals(0, blocks.A.getChildren().length);
- // B and C are still connected.
- assertEquals(blocks.B, blocks.C.getParent());
- // B is the top of its stack.
- assertNull(blocks.B.getParent());
- }
- function assertUnpluggedHealed(blocks) {
- // A and C are connected.
- assertEquals(1, blocks.A.getChildren().length);
- assertEquals(blocks.A, blocks.C.getParent());
- // B has nothing connected to it.
- assertEquals(0, blocks.B.getChildren().length);
- // B is the top of its stack.
- assertNull(blocks.B.getParent());
- }
- function assertUnpluggedHealFailed(blocks) {
- // A has nothing connected to it.
- assertEquals(0, blocks.A.getChildren().length);
- // B has nothing connected to it.
- assertEquals(0, blocks.B.getChildren().length);
- // B is the top of its stack.
- assertNull(blocks.B.getParent());
- // C is the top of its stack.
- assertNull(blocks.C.getParent());
- }
+ suite('Unplug', function() {
+ function assertUnpluggedNoheal(blocks) {
+ // A has nothing connected to it.
+ assertEquals(0, blocks.A.getChildren().length);
+ // B and C are still connected.
+ assertEquals(blocks.B, blocks.C.getParent());
+ // B is the top of its stack.
+ assertNull(blocks.B.getParent());
+ }
+ function assertUnpluggedHealed(blocks) {
+ // A and C are connected.
+ assertEquals(1, blocks.A.getChildren().length);
+ assertEquals(blocks.A, blocks.C.getParent());
+ // B has nothing connected to it.
+ assertEquals(0, blocks.B.getChildren().length);
+ // B is the top of its stack.
+ assertNull(blocks.B.getParent());
+ }
+ function assertUnpluggedHealFailed(blocks) {
+ // A has nothing connected to it.
+ assertEquals(0, blocks.A.getChildren().length);
+ // B has nothing connected to it.
+ assertEquals(0, blocks.B.getChildren().length);
+ // B is the top of its stack.
+ assertNull(blocks.B.getParent());
+ // C is the top of its stack.
+ assertNull(blocks.C.getParent());
+ }
- suite('Row', function() {
- setup(function() {
- var blockA = this.workspace.newBlock('row_block');
- var blockB = this.workspace.newBlock('row_block');
- var blockC = this.workspace.newBlock('row_block');
-
- blockA.inputList[0].connection.connect(blockB.outputConnection);
- blockB.inputList[0].connection.connect(blockC.outputConnection);
-
- assertEquals(blockB, blockC.getParent());
-
- this.blocks = {
- A: blockA,
- B: blockB,
- C: blockC
- };
- });
-
- test('Don\'t heal', function() {
- this.blocks.B.unplug(false);
- assertUnpluggedNoheal(this.blocks);
- });
- test('Heal', function() {
- this.blocks.B.unplug(true);
- // Each block has only one input, and the types work.
- assertUnpluggedHealed(this.blocks);
- });
- test('Heal with bad checks', function() {
- var blocks = this.blocks;
-
- // A and C can't connect, but both can connect to B.
- blocks.A.inputList[0].connection.setCheck('type1');
- blocks.C.outputConnection.setCheck('type2');
-
- // Each block has only one input, but the types don't work.
- blocks.B.unplug(true);
- assertUnpluggedHealFailed(blocks);
- });
- test('A has multiple inputs', function() {
- var blocks = this.blocks;
- // Add extra input to parent
- blocks.A.appendValueInput("INPUT").setCheck(null);
- blocks.B.unplug(true);
- assertUnpluggedHealed(blocks);
- });
- test('B has multiple inputs', function() {
- var blocks = this.blocks;
- // Add extra input to middle block
- blocks.B.appendValueInput("INPUT").setCheck(null);
- blocks.B.unplug(true);
- assertUnpluggedHealed(blocks);
- });
- test('C has multiple inputs', function() {
- var blocks = this.blocks;
- // Add extra input to child block
- blocks.C.appendValueInput("INPUT").setCheck(null);
- // Child block input count doesn't matter.
- blocks.B.unplug(true);
- assertUnpluggedHealed(blocks);
- });
- test('C is Shadow', function() {
- var blocks = this.blocks;
- blocks.C.setShadow(true);
- blocks.B.unplug(true);
- // Even though we're asking to heal, it will appear as if it has not
- // healed because shadows always stay with the parent.
- assertUnpluggedNoheal(blocks);
- });
- });
- suite('Stack', function() {
- setup(function() {
- var blockA = this.workspace.newBlock('stack_block');
- var blockB = this.workspace.newBlock('stack_block');
- var blockC = this.workspace.newBlock('stack_block');
-
- blockA.nextConnection.connect(blockB.previousConnection);
- blockB.nextConnection.connect(blockC.previousConnection);
-
- assertEquals(blockB, blockC.getParent());
-
- this.blocks = {
- A: blockA,
- B: blockB,
- C: blockC
- };
- });
-
- test('Don\'t heal', function() {
- this.blocks.B.unplug();
- assertUnpluggedNoheal(this.blocks);
- });
- test('Heal', function() {
- this.blocks.B.unplug(true);
- assertUnpluggedHealed(this.blocks);
- });
- test('Heal with bad checks', function() {
- var blocks = this.blocks;
- // A and C can't connect, but both can connect to B.
- blocks.A.nextConnection.setCheck('type1');
- blocks.C.previousConnection.setCheck('type2');
-
- // The types don't work.
- blocks.B.unplug(true);
-
- assertUnpluggedHealFailed(blocks);
- });
- test('C is Shadow', function() {
- var blocks = this.blocks;
- blocks.C.setShadow(true);
- blocks.B.unplug(true);
- // Even though we're asking to heal, it will appear as if it has not
- // healed because shadows always stay with the parent.
- assertUnpluggedNoheal(blocks);
- });
- });
- });
- suite('Dispose', function() {
- function assertDisposedNoheal(blocks) {
- chai.assert.isFalse(blocks.A.disposed);
- // A has nothing connected to it.
- chai.assert.equal(0, blocks.A.getChildren().length);
- // B is disposed.
- chai.assert.isTrue(blocks.B.disposed);
- // And C is disposed.
- chai.assert.isTrue(blocks.C.disposed);
- }
- function assertDisposedHealed(blocks) {
- chai.assert.isFalse(blocks.A.disposed);
- chai.assert.isFalse(blocks.C.disposed);
- // A and C are connected.
- assertEquals(1, blocks.A.getChildren().length);
- assertEquals(blocks.A, blocks.C.getParent());
- // B is disposed.
- chai.assert.isTrue(blocks.B.disposed);
- }
- function assertDisposedHealFailed(blocks) {
- chai.assert.isFalse(blocks.A.disposed);
- chai.assert.isFalse(blocks.C.disposed);
- // A has nothing connected to it.
- chai.assert.equal(0, blocks.A.getChildren().length);
- // B is disposed.
- chai.assert.isTrue(blocks.B.disposed);
- // C is the top of its stack.
- assertNull(blocks.C.getParent());
- }
-
- suite('Row', function() {
- setup(function() {
- var blockA = this.workspace.newBlock('row_block');
- var blockB = this.workspace.newBlock('row_block');
- var blockC = this.workspace.newBlock('row_block');
-
- blockA.inputList[0].connection.connect(blockB.outputConnection);
- blockB.inputList[0].connection.connect(blockC.outputConnection);
-
- assertEquals(blockB, blockC.getParent());
-
- this.blocks = {
- A: blockA,
- B: blockB,
- C: blockC
- };
- });
-
- test('Don\'t heal', function() {
- this.blocks.B.dispose(false);
- assertDisposedNoheal(this.blocks);
- });
- test('Heal', function() {
- this.blocks.B.dispose(true);
- // Each block has only one input, and the types work.
- assertDisposedHealed(this.blocks);
- });
- test('Heal with bad checks', function() {
- var blocks = this.blocks;
-
- // A and C can't connect, but both can connect to B.
- blocks.A.inputList[0].connection.setCheck('type1');
- blocks.C.outputConnection.setCheck('type2');
-
- // Each block has only one input, but the types don't work.
- blocks.B.dispose(true);
- assertDisposedHealFailed(blocks);
- });
- test('A has multiple inputs', function() {
- var blocks = this.blocks;
- // Add extra input to parent
- blocks.A.appendValueInput("INPUT").setCheck(null);
- blocks.B.dispose(true);
- assertDisposedHealed(blocks);
- });
- test('B has multiple inputs', function() {
- var blocks = this.blocks;
- // Add extra input to middle block
- blocks.B.appendValueInput("INPUT").setCheck(null);
- blocks.B.dispose(true);
- assertDisposedHealed(blocks);
- });
- test('C has multiple inputs', function() {
- var blocks = this.blocks;
- // Add extra input to child block
- blocks.C.appendValueInput("INPUT").setCheck(null);
- // Child block input count doesn't matter.
- blocks.B.dispose(true);
- assertDisposedHealed(blocks);
- });
- test('C is Shadow', function() {
- var blocks = this.blocks;
- blocks.C.setShadow(true);
- blocks.B.dispose(true);
- // Even though we're asking to heal, it will appear as if it has not
- // healed because shadows always get destroyed.
- assertDisposedNoheal(blocks);
- });
- });
- suite('Stack', function() {
- setup(function() {
- var blockA = this.workspace.newBlock('stack_block');
- var blockB = this.workspace.newBlock('stack_block');
- var blockC = this.workspace.newBlock('stack_block');
-
- blockA.nextConnection.connect(blockB.previousConnection);
- blockB.nextConnection.connect(blockC.previousConnection);
-
- assertEquals(blockB, blockC.getParent());
-
- this.blocks = {
- A: blockA,
- B: blockB,
- C: blockC
- };
- });
-
- test('Don\'t heal', function() {
- this.blocks.B.dispose();
- assertDisposedNoheal(this.blocks);
- });
- test('Heal', function() {
- this.blocks.B.dispose(true);
- assertDisposedHealed(this.blocks);
- });
- test('Heal with bad checks', function() {
- var blocks = this.blocks;
- // A and C can't connect, but both can connect to B.
- blocks.A.nextConnection.setCheck('type1');
- blocks.C.previousConnection.setCheck('type2');
-
- // The types don't work.
- blocks.B.dispose(true);
-
- assertDisposedHealFailed(blocks);
- });
- test('C is Shadow', function() {
- var blocks = this.blocks;
- blocks.C.setShadow(true);
- blocks.B.dispose(true);
- // Even though we're asking to heal, it will appear as if it has not
- // healed because shadows always get destroyed.
- assertDisposedNoheal(blocks);
- });
- });
- });
- suite('Remove Input', function() {
+ suite('Row', function() {
setup(function() {
- Blockly.defineBlocksWithJsonArray([
- {
- "type": "value_block",
- "message0": "%1",
- "args0": [
- {
- "type": "input_value",
- "name": "VALUE"
- }
- ]
- },
- {
- "type": "statement_block",
- "message0": "%1",
- "args0": [
- {
- "type": "input_statement",
- "name": "STATEMENT"
- }
- ]
- },
- ]);
- });
- teardown(function() {
- delete Blockly.Blocks['value_block'];
- delete Blockly.Blocks['statement_block'];
+ var blockA = this.workspace.newBlock('row_block');
+ var blockB = this.workspace.newBlock('row_block');
+ var blockC = this.workspace.newBlock('row_block');
+
+ blockA.inputList[0].connection.connect(blockB.outputConnection);
+ blockB.inputList[0].connection.connect(blockC.outputConnection);
+
+ assertEquals(blockB, blockC.getParent());
+
+ this.blocks = {
+ A: blockA,
+ B: blockB,
+ C: blockC
+ };
});
- suite('Value', function() {
- setup(function() {
- this.blockA = this.workspace.newBlock('value_block');
- });
-
- test('No Connected', function() {
- this.blockA.removeInput('VALUE');
- chai.assert.isNull(this.blockA.getInput('VALUE'));
- });
- test('Block Connected', function() {
- var blockB = this.workspace.newBlock('row_block');
- this.blockA.getInput('VALUE').connection
- .connect(blockB.outputConnection);
-
- this.blockA.removeInput('VALUE');
- chai.assert.isFalse(blockB.disposed);
- chai.assert.equal(this.blockA.getChildren().length, 0);
- });
- test('Shadow Connected', function() {
- var blockB = this.workspace.newBlock('row_block');
- blockB.setShadow(true);
- this.blockA.getInput('VALUE').connection
- .connect(blockB.outputConnection);
-
- this.blockA.removeInput('VALUE');
- chai.assert.isTrue(blockB.disposed);
- chai.assert.equal(this.blockA.getChildren().length, 0);
- });
+ test('Don\'t heal', function() {
+ this.blocks.B.unplug(false);
+ assertUnpluggedNoheal(this.blocks);
});
- suite('Statement', function() {
- setup(function() {
- this.blockA = this.workspace.newBlock('statement_block');
- });
+ test('Heal', function() {
+ this.blocks.B.unplug(true);
+ // Each block has only one input, and the types work.
+ assertUnpluggedHealed(this.blocks);
+ });
+ test('Heal with bad checks', function() {
+ var blocks = this.blocks;
- test('No Connected', function() {
- this.blockA.removeInput('STATEMENT');
- chai.assert.isNull(this.blockA.getInput('STATEMENT'));
- });
- test('Block Connected', function() {
- var blockB = this.workspace.newBlock('stack_block');
- this.blockA.getInput('STATEMENT').connection
- .connect(blockB.previousConnection);
+ // A and C can't connect, but both can connect to B.
+ blocks.A.inputList[0].connection.setCheck('type1');
+ blocks.C.outputConnection.setCheck('type2');
- this.blockA.removeInput('STATEMENT');
- chai.assert.isFalse(blockB.disposed);
- chai.assert.equal(this.blockA.getChildren().length, 0);
- });
- test('Shadow Connected', function() {
- var blockB = this.workspace.newBlock('stack_block');
- blockB.setShadow(true);
- this.blockA.getInput('STATEMENT').connection
- .connect(blockB.previousConnection);
-
- this.blockA.removeInput('STATEMENT');
- chai.assert.isTrue(blockB.disposed);
- chai.assert.equal(this.blockA.getChildren().length, 0);
- });
+ // Each block has only one input, but the types don't work.
+ blocks.B.unplug(true);
+ assertUnpluggedHealFailed(blocks);
+ });
+ test('A has multiple inputs', function() {
+ var blocks = this.blocks;
+ // Add extra input to parent
+ blocks.A.appendValueInput("INPUT").setCheck(null);
+ blocks.B.unplug(true);
+ assertUnpluggedHealed(blocks);
+ });
+ test('B has multiple inputs', function() {
+ var blocks = this.blocks;
+ // Add extra input to middle block
+ blocks.B.appendValueInput("INPUT").setCheck(null);
+ blocks.B.unplug(true);
+ assertUnpluggedHealed(blocks);
+ });
+ test('C has multiple inputs', function() {
+ var blocks = this.blocks;
+ // Add extra input to child block
+ blocks.C.appendValueInput("INPUT").setCheck(null);
+ // Child block input count doesn't matter.
+ blocks.B.unplug(true);
+ assertUnpluggedHealed(blocks);
+ });
+ test('C is Shadow', function() {
+ var blocks = this.blocks;
+ blocks.C.setShadow(true);
+ blocks.B.unplug(true);
+ // Even though we're asking to heal, it will appear as if it has not
+ // healed because shadows always stay with the parent.
+ assertUnpluggedNoheal(blocks);
});
});
- suite('Connection Tracking', function() {
+ suite('Stack', function() {
setup(function() {
- this.workspace.dispose();
- // The new rendered workspace will get disposed by the parent teardown.
- this.workspace = Blockly.inject('blocklyDiv');
+ var blockA = this.workspace.newBlock('stack_block');
+ var blockB = this.workspace.newBlock('stack_block');
+ var blockC = this.workspace.newBlock('stack_block');
- this.getInputs = function() {
- return this.workspace
- .connectionDBList[Blockly.INPUT_VALUE].connections_;
- };
- this.getOutputs = function() {
- return this.workspace
- .connectionDBList[Blockly.OUTPUT_VALUE].connections_;
- };
- this.getNext = function() {
- return this.workspace
- .connectionDBList[Blockly.NEXT_STATEMENT].connections_;
- };
- this.getPrevious = function() {
- return this.workspace
- .connectionDBList[Blockly.PREVIOUS_STATEMENT].connections_;
- };
+ blockA.nextConnection.connect(blockB.previousConnection);
+ blockB.nextConnection.connect(blockC.previousConnection);
- this.assertConnectionsEmpty = function() {
- chai.assert.isEmpty(this.getInputs());
- chai.assert.isEmpty(this.getOutputs());
- chai.assert.isEmpty(this.getNext());
- chai.assert.isEmpty(this.getPrevious());
- };
+ assertEquals(blockB, blockC.getParent());
- this.clock = sinon.useFakeTimers();
+ this.blocks = {
+ A: blockA,
+ B: blockB,
+ C: blockC
+ };
});
- teardown(function() {
- this.clock.restore();
+
+ test('Don\'t heal', function() {
+ this.blocks.B.unplug();
+ assertUnpluggedNoheal(this.blocks);
});
- suite('Deserialization', function() {
- test('Stack', function() {
- Blockly.Xml.appendDomToWorkspace(Blockly.Xml.textToDom(
- '' +
- ' ' +
- ''
- ), this.workspace);
- this.assertConnectionsEmpty();
- this.clock.tick(1);
- chai.assert.equal(this.getPrevious().length, 1);
- chai.assert.equal(this.getNext().length, 1);
- });
- test('Multi-Stack', function() {
- Blockly.Xml.appendDomToWorkspace(Blockly.Xml.textToDom(
- '' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ''
- ), this.workspace);
- this.assertConnectionsEmpty();
- this.clock.tick(1);
- chai.assert.equal(this.getPrevious().length, 3);
- chai.assert.equal(this.getNext().length, 3);
- });
- test('Collapsed Stack', function() {
- Blockly.Xml.appendDomToWorkspace(Blockly.Xml.textToDom(
- '' +
- ' ' +
- ''
- ), this.workspace);
- this.assertConnectionsEmpty();
- this.clock.tick(1);
- chai.assert.equal(this.getPrevious().length, 1);
- chai.assert.equal(this.getNext().length, 1);
- });
- test('Collapsed Multi-Stack', function() {
- Blockly.Xml.appendDomToWorkspace(Blockly.Xml.textToDom(
- '' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ''
- ), this.workspace);
- this.assertConnectionsEmpty();
- this.clock.tick(1);
- chai.assert.equal(this.getPrevious().length, 3);
- chai.assert.equal(this.getNext().length, 3);
- });
- test('Row', function() {
- Blockly.Xml.appendDomToWorkspace(Blockly.Xml.textToDom(
- '' +
- ' ' +
- ''
- ), this.workspace);
- this.assertConnectionsEmpty();
- this.clock.tick(1);
- chai.assert.equal(this.getOutputs().length, 1);
- chai.assert.equal(this.getInputs().length, 1);
- });
- test('Multi-Row', function() {
- Blockly.Xml.appendDomToWorkspace(Blockly.Xml.textToDom(
- '' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ''
- ), this.workspace);
- this.assertConnectionsEmpty();
- this.clock.tick(1);
- chai.assert.equal(this.getOutputs().length, 3);
- chai.assert.equal(this.getInputs().length, 3);
- });
- test('Collapsed Row', function() {
- Blockly.Xml.appendDomToWorkspace(Blockly.Xml.textToDom(
- '' +
- ' ' +
- ''
- ), this.workspace);
- this.assertConnectionsEmpty();
- this.clock.tick(1);
- chai.assert.equal(this.getOutputs().length, 1);
- chai.assert.equal(this.getInputs().length, 0);
- });
- test('Collapsed Multi-Row', function() {
- Blockly.Xml.appendDomToWorkspace(Blockly.Xml.textToDom(
- '' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ''
- ), this.workspace);
- this.assertConnectionsEmpty();
- this.clock.tick(1);
- chai.assert.equal(this.getOutputs().length, 1);
- chai.assert.equal(this.getInputs().length, 0);
- });
- test('Collapsed Multi-Row Middle', function() {
- Blockly.Xml.appendDomToWorkspace(Blockly.Xml.textToDom(
- '' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ''
- ), this.workspace);
- this.assertConnectionsEmpty();
- this.clock.tick(1);
- chai.assert.equal(this.getOutputs().length, 2);
- chai.assert.equal(this.getInputs().length, 1);
- });
- test('Statement', function() {
- Blockly.Xml.appendDomToWorkspace(Blockly.Xml.textToDom(
- '' +
- ' ' +
- ''
- ), this.workspace);
- this.assertConnectionsEmpty();
- this.clock.tick(1);
- chai.assert.equal(this.getPrevious().length, 1);
- chai.assert.equal(this.getNext().length, 2);
- });
- test('Multi-Statement', function() {
- Blockly.Xml.appendDomToWorkspace(Blockly.Xml.textToDom(
- '' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ''
- ), this.workspace);
- this.assertConnectionsEmpty();
- this.clock.tick(1);
- chai.assert.equal(this.getPrevious().length, 3);
- chai.assert.equal(this.getNext().length, 6);
- });
- test('Collapsed Statement', function() {
- Blockly.Xml.appendDomToWorkspace(Blockly.Xml.textToDom(
- '' +
- ' ' +
- ''
- ), this.workspace);
- this.assertConnectionsEmpty();
- this.clock.tick(1);
- chai.assert.equal(this.getPrevious().length, 1);
- chai.assert.equal(this.getNext().length, 1);
- });
- test('Collapsed Multi-Statement', function() {
- Blockly.Xml.appendDomToWorkspace(Blockly.Xml.textToDom(
- '' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ''
- ), this.workspace);
- this.assertConnectionsEmpty();
- this.clock.tick(1);
- chai.assert.equal(this.getPrevious().length, 1);
- chai.assert.equal(this.getNext().length, 1);
- });
- test('Collapsed Multi-Statement Middle', function() {
- Blockly.Xml.appendDomToWorkspace(Blockly.Xml.textToDom(
- '' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ''
- ), this.workspace);
- this.assertConnectionsEmpty();
- this.clock.tick(1);
- chai.assert.equal(this.getPrevious().length, 2);
- chai.assert.equal(this.getNext().length, 3);
- });
+ test('Heal', function() {
+ this.blocks.B.unplug(true);
+ assertUnpluggedHealed(this.blocks);
});
- suite('Programmatic Block Creation', function() {
- test('Stack', function() {
- var block = this.workspace.newBlock('stack_block');
- this.assertConnectionsEmpty();
- block.initSvg();
- block.render();
+ test('Heal with bad checks', function() {
+ var blocks = this.blocks;
+ // A and C can't connect, but both can connect to B.
+ blocks.A.nextConnection.setCheck('type1');
+ blocks.C.previousConnection.setCheck('type2');
- chai.assert.equal(this.getPrevious().length, 1);
- chai.assert.equal(this.getNext().length, 1);
- });
- test('Row', function() {
- var block = this.workspace.newBlock('row_block');
- this.assertConnectionsEmpty();
- block.initSvg();
- block.render();
+ // The types don't work.
+ blocks.B.unplug(true);
- chai.assert.equal(this.getOutputs().length, 1);
- chai.assert.equal(this.getInputs().length, 1);
- });
- test('Statement', function() {
- var block = this.workspace.newBlock('statement_block');
- this.assertConnectionsEmpty();
- block.initSvg();
- block.render();
-
- chai.assert.equal(this.getPrevious().length, 1);
- chai.assert.equal(this.getNext().length, 2);
- });
+ assertUnpluggedHealFailed(blocks);
});
- suite('setCollapsed', function() {
- test('Stack', function() {
- var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
- ''
- ), this.workspace);
- this.clock.tick(1);
- chai.assert.equal(this.getPrevious().length, 1);
- chai.assert.equal(this.getNext().length, 1);
+ test('C is Shadow', function() {
+ var blocks = this.blocks;
+ blocks.C.setShadow(true);
+ blocks.B.unplug(true);
+ // Even though we're asking to heal, it will appear as if it has not
+ // healed because shadows always stay with the parent.
+ assertUnpluggedNoheal(blocks);
+ });
+ });
+ });
+ suite('Dispose', function() {
+ function assertDisposedNoheal(blocks) {
+ chai.assert.isFalse(blocks.A.disposed);
+ // A has nothing connected to it.
+ chai.assert.equal(0, blocks.A.getChildren().length);
+ // B is disposed.
+ chai.assert.isTrue(blocks.B.disposed);
+ // And C is disposed.
+ chai.assert.isTrue(blocks.C.disposed);
+ }
+ function assertDisposedHealed(blocks) {
+ chai.assert.isFalse(blocks.A.disposed);
+ chai.assert.isFalse(blocks.C.disposed);
+ // A and C are connected.
+ assertEquals(1, blocks.A.getChildren().length);
+ assertEquals(blocks.A, blocks.C.getParent());
+ // B is disposed.
+ chai.assert.isTrue(blocks.B.disposed);
+ }
+ function assertDisposedHealFailed(blocks) {
+ chai.assert.isFalse(blocks.A.disposed);
+ chai.assert.isFalse(blocks.C.disposed);
+ // A has nothing connected to it.
+ chai.assert.equal(0, blocks.A.getChildren().length);
+ // B is disposed.
+ chai.assert.isTrue(blocks.B.disposed);
+ // C is the top of its stack.
+ assertNull(blocks.C.getParent());
+ }
- block.setCollapsed(true);
- chai.assert.equal(this.getPrevious().length, 1);
- chai.assert.equal(this.getNext().length, 1);
+ suite('Row', function() {
+ setup(function() {
+ var blockA = this.workspace.newBlock('row_block');
+ var blockB = this.workspace.newBlock('row_block');
+ var blockC = this.workspace.newBlock('row_block');
- block.setCollapsed(false);
- chai.assert.equal(this.getPrevious().length, 1);
- chai.assert.equal(this.getNext().length, 1);
- });
- test('Multi-Stack', function() {
- var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
- '' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ''
- ), this.workspace);
- this.assertConnectionsEmpty();
- this.clock.tick(1);
- chai.assert.equal(this.getPrevious().length, 3);
- chai.assert.equal(this.getNext().length, 3);
+ blockA.inputList[0].connection.connect(blockB.outputConnection);
+ blockB.inputList[0].connection.connect(blockC.outputConnection);
- block.setCollapsed(true);
- chai.assert.equal(this.getPrevious().length, 3);
- chai.assert.equal(this.getNext().length, 3);
+ assertEquals(blockB, blockC.getParent());
- block.setCollapsed(false);
- chai.assert.equal(this.getPrevious().length, 3);
- chai.assert.equal(this.getNext().length, 3);
- });
- test('Row', function() {
- var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
- ''
- ), this.workspace);
- this.clock.tick(1);
- chai.assert.equal(this.getOutputs().length, 1);
- chai.assert.equal(this.getInputs().length, 1);
+ this.blocks = {
+ A: blockA,
+ B: blockB,
+ C: blockC
+ };
+ });
- block.setCollapsed(true);
- chai.assert.equal(this.getOutputs().length, 1);
- chai.assert.equal(this.getInputs().length, 0);
+ test('Don\'t heal', function() {
+ this.blocks.B.dispose(false);
+ assertDisposedNoheal(this.blocks);
+ });
+ test('Heal', function() {
+ this.blocks.B.dispose(true);
+ // Each block has only one input, and the types work.
+ assertDisposedHealed(this.blocks);
+ });
+ test('Heal with bad checks', function() {
+ var blocks = this.blocks;
- block.setCollapsed(false);
- chai.assert.equal(this.getOutputs().length, 1);
- chai.assert.equal(this.getInputs().length, 1);
- });
- test('Multi-Row', function() {
- var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
- '' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ''
- ), this.workspace);
- this.clock.tick(1);
- chai.assert.equal(this.getOutputs().length, 3);
- chai.assert.equal(this.getInputs().length, 3);
+ // A and C can't connect, but both can connect to B.
+ blocks.A.inputList[0].connection.setCheck('type1');
+ blocks.C.outputConnection.setCheck('type2');
- block.setCollapsed(true);
- chai.assert.equal(this.getOutputs().length, 1);
- chai.assert.equal(this.getInputs().length, 0);
+ // Each block has only one input, but the types don't work.
+ blocks.B.dispose(true);
+ assertDisposedHealFailed(blocks);
+ });
+ test('A has multiple inputs', function() {
+ var blocks = this.blocks;
+ // Add extra input to parent
+ blocks.A.appendValueInput("INPUT").setCheck(null);
+ blocks.B.dispose(true);
+ assertDisposedHealed(blocks);
+ });
+ test('B has multiple inputs', function() {
+ var blocks = this.blocks;
+ // Add extra input to middle block
+ blocks.B.appendValueInput("INPUT").setCheck(null);
+ blocks.B.dispose(true);
+ assertDisposedHealed(blocks);
+ });
+ test('C has multiple inputs', function() {
+ var blocks = this.blocks;
+ // Add extra input to child block
+ blocks.C.appendValueInput("INPUT").setCheck(null);
+ // Child block input count doesn't matter.
+ blocks.B.dispose(true);
+ assertDisposedHealed(blocks);
+ });
+ test('C is Shadow', function() {
+ var blocks = this.blocks;
+ blocks.C.setShadow(true);
+ blocks.B.dispose(true);
+ // Even though we're asking to heal, it will appear as if it has not
+ // healed because shadows always get destroyed.
+ assertDisposedNoheal(blocks);
+ });
+ });
+ suite('Stack', function() {
+ setup(function() {
+ var blockA = this.workspace.newBlock('stack_block');
+ var blockB = this.workspace.newBlock('stack_block');
+ var blockC = this.workspace.newBlock('stack_block');
- block.setCollapsed(false);
- chai.assert.equal(this.getOutputs().length, 3);
- chai.assert.equal(this.getInputs().length, 3);
- });
- test('Multi-Row Middle', function() {
- var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
- '' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ''
- ), this.workspace);
- this.clock.tick(1);
- chai.assert.equal(this.getOutputs().length, 3);
- chai.assert.equal(this.getInputs().length, 3);
+ blockA.nextConnection.connect(blockB.previousConnection);
+ blockB.nextConnection.connect(blockC.previousConnection);
- block = block.getInputTargetBlock('INPUT');
- block.setCollapsed(true);
- chai.assert.equal(this.getOutputs().length, 2);
- chai.assert.equal(this.getInputs().length, 1);
+ assertEquals(blockB, blockC.getParent());
- block.setCollapsed(false);
- chai.assert.equal(this.getOutputs().length, 3);
- chai.assert.equal(this.getInputs().length, 3);
- });
- test('Multi-Row Double Collapse', function() {
- // Collapse middle -> Collapse top ->
- // Uncollapse top -> Uncollapse middle
- var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
- '' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ''
- ), this.workspace);
- this.clock.tick(1);
- chai.assert.equal(this.getOutputs().length, 3);
- chai.assert.equal(this.getInputs().length, 3);
+ this.blocks = {
+ A: blockA,
+ B: blockB,
+ C: blockC
+ };
+ });
- var middleBlock = block.getInputTargetBlock('INPUT');
- middleBlock.setCollapsed(true);
- chai.assert.equal(this.getOutputs().length, 2);
- chai.assert.equal(this.getInputs().length, 1);
+ test('Don\'t heal', function() {
+ this.blocks.B.dispose();
+ assertDisposedNoheal(this.blocks);
+ });
+ test('Heal', function() {
+ this.blocks.B.dispose(true);
+ assertDisposedHealed(this.blocks);
+ });
+ test('Heal with bad checks', function() {
+ var blocks = this.blocks;
+ // A and C can't connect, but both can connect to B.
+ blocks.A.nextConnection.setCheck('type1');
+ blocks.C.previousConnection.setCheck('type2');
- block.setCollapsed(true);
- chai.assert.equal(this.getOutputs().length, 1);
- chai.assert.equal(this.getInputs().length, 0);
+ // The types don't work.
+ blocks.B.dispose(true);
- block.setCollapsed(false);
- chai.assert.equal(this.getOutputs().length, 2);
- chai.assert.equal(this.getInputs().length, 1);
+ assertDisposedHealFailed(blocks);
+ });
+ test('C is Shadow', function() {
+ var blocks = this.blocks;
+ blocks.C.setShadow(true);
+ blocks.B.dispose(true);
+ // Even though we're asking to heal, it will appear as if it has not
+ // healed because shadows always get destroyed.
+ assertDisposedNoheal(blocks);
+ });
+ });
+ });
+ suite('Remove Input', function() {
+ setup(function() {
+ Blockly.defineBlocksWithJsonArray([
+ {
+ "type": "value_block",
+ "message0": "%1",
+ "args0": [
+ {
+ "type": "input_value",
+ "name": "VALUE"
+ }
+ ]
+ },
+ {
+ "type": "statement_block",
+ "message0": "%1",
+ "args0": [
+ {
+ "type": "input_statement",
+ "name": "STATEMENT"
+ }
+ ]
+ },
+ ]);
+ });
+ teardown(function() {
+ delete Blockly.Blocks['value_block'];
+ delete Blockly.Blocks['statement_block'];
+ });
- middleBlock.setCollapsed(false);
- chai.assert.equal(this.getOutputs().length, 3);
- chai.assert.equal(this.getInputs().length, 3);
- });
- test('Statement', function() {
- var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
- ''
- ), this.workspace);
- this.clock.tick(1);
- chai.assert.equal(this.getPrevious().length, 1);
- chai.assert.equal(this.getNext().length, 2);
+ suite('Value', function() {
+ setup(function() {
+ this.blockA = this.workspace.newBlock('value_block');
+ });
- block.setCollapsed(true);
- chai.assert.equal(this.getPrevious().length, 1);
- chai.assert.equal(this.getNext().length, 1);
+ test('No Connected', function() {
+ this.blockA.removeInput('VALUE');
+ chai.assert.isNull(this.blockA.getInput('VALUE'));
+ });
+ test('Block Connected', function() {
+ var blockB = this.workspace.newBlock('row_block');
+ this.blockA.getInput('VALUE').connection
+ .connect(blockB.outputConnection);
- block.setCollapsed(false);
- chai.assert.equal(this.getPrevious().length, 1);
- chai.assert.equal(this.getNext().length, 2);
- });
- test('Multi-Statement', function() {
- var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
- '' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ''
- ), this.workspace);
- this.assertConnectionsEmpty();
- this.clock.tick(1);
- chai.assert.equal(this.getPrevious().length, 3);
- chai.assert.equal(this.getNext().length, 6);
+ this.blockA.removeInput('VALUE');
+ chai.assert.isFalse(blockB.disposed);
+ chai.assert.equal(this.blockA.getChildren().length, 0);
+ });
+ test('Shadow Connected', function() {
+ var blockB = this.workspace.newBlock('row_block');
+ blockB.setShadow(true);
+ this.blockA.getInput('VALUE').connection
+ .connect(blockB.outputConnection);
- block.setCollapsed(true);
- chai.assert.equal(this.getPrevious().length, 1);
- chai.assert.equal(this.getNext().length, 1);
+ this.blockA.removeInput('VALUE');
+ chai.assert.isTrue(blockB.disposed);
+ chai.assert.equal(this.blockA.getChildren().length, 0);
+ });
+ });
+ suite('Statement', function() {
+ setup(function() {
+ this.blockA = this.workspace.newBlock('statement_block');
+ });
- block.setCollapsed(false);
- chai.assert.equal(this.getPrevious().length, 3);
- chai.assert.equal(this.getNext().length, 6);
- });
- test('Multi-Statement Middle', function() {
- var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
- '' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ''
- ), this.workspace);
- this.assertConnectionsEmpty();
- this.clock.tick(1);
- chai.assert.equal(this.getPrevious().length, 3);
- chai.assert.equal(this.getNext().length, 6);
+ test('No Connected', function() {
+ this.blockA.removeInput('STATEMENT');
+ chai.assert.isNull(this.blockA.getInput('STATEMENT'));
+ });
+ test('Block Connected', function() {
+ var blockB = this.workspace.newBlock('stack_block');
+ this.blockA.getInput('STATEMENT').connection
+ .connect(blockB.previousConnection);
- block = block.getInputTargetBlock('STATEMENT');
- block.setCollapsed(true);
- chai.assert.equal(this.getPrevious().length, 2);
- chai.assert.equal(this.getNext().length, 3);
+ this.blockA.removeInput('STATEMENT');
+ chai.assert.isFalse(blockB.disposed);
+ chai.assert.equal(this.blockA.getChildren().length, 0);
+ });
+ test('Shadow Connected', function() {
+ var blockB = this.workspace.newBlock('stack_block');
+ blockB.setShadow(true);
+ this.blockA.getInput('STATEMENT').connection
+ .connect(blockB.previousConnection);
- block.setCollapsed(false);
- chai.assert.equal(this.getPrevious().length, 3);
- chai.assert.equal(this.getNext().length, 6);
- });
- test('Multi-Statement Double Collapse', function() {
- var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
- '' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ' +
- ''
- ), this.workspace);
- this.assertConnectionsEmpty();
- this.clock.tick(1);
- chai.assert.equal(this.getPrevious().length, 3);
- chai.assert.equal(this.getNext().length, 6);
+ this.blockA.removeInput('STATEMENT');
+ chai.assert.isTrue(blockB.disposed);
+ chai.assert.equal(this.blockA.getChildren().length, 0);
+ });
+ });
+ });
+ suite('Connection Tracking', function() {
+ setup(function() {
+ this.workspace.dispose();
+ // The new rendered workspace will get disposed by the parent teardown.
+ this.workspace = Blockly.inject('blocklyDiv');
- var middleBlock = block.getInputTargetBlock('STATEMENT');
- middleBlock.setCollapsed(true);
- chai.assert.equal(this.getPrevious().length, 2);
- chai.assert.equal(this.getNext().length, 3);
+ this.getInputs = function() {
+ return this.workspace
+ .connectionDBList[Blockly.INPUT_VALUE].connections_;
+ };
+ this.getOutputs = function() {
+ return this.workspace
+ .connectionDBList[Blockly.OUTPUT_VALUE].connections_;
+ };
+ this.getNext = function() {
+ return this.workspace
+ .connectionDBList[Blockly.NEXT_STATEMENT].connections_;
+ };
+ this.getPrevious = function() {
+ return this.workspace
+ .connectionDBList[Blockly.PREVIOUS_STATEMENT].connections_;
+ };
- block.setCollapsed(true);
- chai.assert.equal(this.getPrevious().length, 1);
- chai.assert.equal(this.getNext().length, 1);
+ this.assertConnectionsEmpty = function() {
+ chai.assert.isEmpty(this.getInputs());
+ chai.assert.isEmpty(this.getOutputs());
+ chai.assert.isEmpty(this.getNext());
+ chai.assert.isEmpty(this.getPrevious());
+ };
- block.setCollapsed(false);
- chai.assert.equal(this.getPrevious().length, 2);
- chai.assert.equal(this.getNext().length, 3);
+ this.clock = sinon.useFakeTimers();
+ });
+ teardown(function() {
+ this.clock.restore();
+ });
+ suite('Deserialization', function() {
+ test('Stack', function() {
+ Blockly.Xml.appendDomToWorkspace(Blockly.Xml.textToDom(
+ '' +
+ ' ' +
+ ''
+ ), this.workspace);
+ this.assertConnectionsEmpty();
+ this.clock.tick(1);
+ chai.assert.equal(this.getPrevious().length, 1);
+ chai.assert.equal(this.getNext().length, 1);
+ });
+ test('Multi-Stack', function() {
+ Blockly.Xml.appendDomToWorkspace(Blockly.Xml.textToDom(
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ''
+ ), this.workspace);
+ this.assertConnectionsEmpty();
+ this.clock.tick(1);
+ chai.assert.equal(this.getPrevious().length, 3);
+ chai.assert.equal(this.getNext().length, 3);
+ });
+ test('Collapsed Stack', function() {
+ Blockly.Xml.appendDomToWorkspace(Blockly.Xml.textToDom(
+ '' +
+ ' ' +
+ ''
+ ), this.workspace);
+ this.assertConnectionsEmpty();
+ this.clock.tick(1);
+ chai.assert.equal(this.getPrevious().length, 1);
+ chai.assert.equal(this.getNext().length, 1);
+ });
+ test('Collapsed Multi-Stack', function() {
+ Blockly.Xml.appendDomToWorkspace(Blockly.Xml.textToDom(
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ''
+ ), this.workspace);
+ this.assertConnectionsEmpty();
+ this.clock.tick(1);
+ chai.assert.equal(this.getPrevious().length, 3);
+ chai.assert.equal(this.getNext().length, 3);
+ });
+ test('Row', function() {
+ Blockly.Xml.appendDomToWorkspace(Blockly.Xml.textToDom(
+ '' +
+ ' ' +
+ ''
+ ), this.workspace);
+ this.assertConnectionsEmpty();
+ this.clock.tick(1);
+ chai.assert.equal(this.getOutputs().length, 1);
+ chai.assert.equal(this.getInputs().length, 1);
+ });
+ test('Multi-Row', function() {
+ Blockly.Xml.appendDomToWorkspace(Blockly.Xml.textToDom(
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ''
+ ), this.workspace);
+ this.assertConnectionsEmpty();
+ this.clock.tick(1);
+ chai.assert.equal(this.getOutputs().length, 3);
+ chai.assert.equal(this.getInputs().length, 3);
+ });
+ test('Collapsed Row', function() {
+ Blockly.Xml.appendDomToWorkspace(Blockly.Xml.textToDom(
+ '' +
+ ' ' +
+ ''
+ ), this.workspace);
+ this.assertConnectionsEmpty();
+ this.clock.tick(1);
+ chai.assert.equal(this.getOutputs().length, 1);
+ chai.assert.equal(this.getInputs().length, 0);
+ });
+ test('Collapsed Multi-Row', function() {
+ Blockly.Xml.appendDomToWorkspace(Blockly.Xml.textToDom(
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ''
+ ), this.workspace);
+ this.assertConnectionsEmpty();
+ this.clock.tick(1);
+ chai.assert.equal(this.getOutputs().length, 1);
+ chai.assert.equal(this.getInputs().length, 0);
+ });
+ test('Collapsed Multi-Row Middle', function() {
+ Blockly.Xml.appendDomToWorkspace(Blockly.Xml.textToDom(
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ''
+ ), this.workspace);
+ this.assertConnectionsEmpty();
+ this.clock.tick(1);
+ chai.assert.equal(this.getOutputs().length, 2);
+ chai.assert.equal(this.getInputs().length, 1);
+ });
+ test('Statement', function() {
+ Blockly.Xml.appendDomToWorkspace(Blockly.Xml.textToDom(
+ '' +
+ ' ' +
+ ''
+ ), this.workspace);
+ this.assertConnectionsEmpty();
+ this.clock.tick(1);
+ chai.assert.equal(this.getPrevious().length, 1);
+ chai.assert.equal(this.getNext().length, 2);
+ });
+ test('Multi-Statement', function() {
+ Blockly.Xml.appendDomToWorkspace(Blockly.Xml.textToDom(
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ''
+ ), this.workspace);
+ this.assertConnectionsEmpty();
+ this.clock.tick(1);
+ chai.assert.equal(this.getPrevious().length, 3);
+ chai.assert.equal(this.getNext().length, 6);
+ });
+ test('Collapsed Statement', function() {
+ Blockly.Xml.appendDomToWorkspace(Blockly.Xml.textToDom(
+ '' +
+ ' ' +
+ ''
+ ), this.workspace);
+ this.assertConnectionsEmpty();
+ this.clock.tick(1);
+ chai.assert.equal(this.getPrevious().length, 1);
+ chai.assert.equal(this.getNext().length, 1);
+ });
+ test('Collapsed Multi-Statement', function() {
+ Blockly.Xml.appendDomToWorkspace(Blockly.Xml.textToDom(
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ''
+ ), this.workspace);
+ this.assertConnectionsEmpty();
+ this.clock.tick(1);
+ chai.assert.equal(this.getPrevious().length, 1);
+ chai.assert.equal(this.getNext().length, 1);
+ });
+ test('Collapsed Multi-Statement Middle', function() {
+ Blockly.Xml.appendDomToWorkspace(Blockly.Xml.textToDom(
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ''
+ ), this.workspace);
+ this.assertConnectionsEmpty();
+ this.clock.tick(1);
+ chai.assert.equal(this.getPrevious().length, 2);
+ chai.assert.equal(this.getNext().length, 3);
+ });
+ });
+ suite('Programmatic Block Creation', function() {
+ test('Stack', function() {
+ var block = this.workspace.newBlock('stack_block');
+ this.assertConnectionsEmpty();
+ block.initSvg();
+ block.render();
- middleBlock.setCollapsed(false);
- chai.assert.equal(this.getPrevious().length, 3);
- chai.assert.equal(this.getNext().length, 6);
- });
+ chai.assert.equal(this.getPrevious().length, 1);
+ chai.assert.equal(this.getNext().length, 1);
+ });
+ test('Row', function() {
+ var block = this.workspace.newBlock('row_block');
+ this.assertConnectionsEmpty();
+ block.initSvg();
+ block.render();
+
+ chai.assert.equal(this.getOutputs().length, 1);
+ chai.assert.equal(this.getInputs().length, 1);
+ });
+ test('Statement', function() {
+ var block = this.workspace.newBlock('statement_block');
+ this.assertConnectionsEmpty();
+ block.initSvg();
+ block.render();
+
+ chai.assert.equal(this.getPrevious().length, 1);
+ chai.assert.equal(this.getNext().length, 2);
+ });
+ });
+ suite('setCollapsed', function() {
+ test('Stack', function() {
+ var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
+ ''
+ ), this.workspace);
+ this.clock.tick(1);
+ chai.assert.equal(this.getPrevious().length, 1);
+ chai.assert.equal(this.getNext().length, 1);
+
+ block.setCollapsed(true);
+ chai.assert.equal(this.getPrevious().length, 1);
+ chai.assert.equal(this.getNext().length, 1);
+
+ block.setCollapsed(false);
+ chai.assert.equal(this.getPrevious().length, 1);
+ chai.assert.equal(this.getNext().length, 1);
+ });
+ test('Multi-Stack', function() {
+ var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ''
+ ), this.workspace);
+ this.assertConnectionsEmpty();
+ this.clock.tick(1);
+ chai.assert.equal(this.getPrevious().length, 3);
+ chai.assert.equal(this.getNext().length, 3);
+
+ block.setCollapsed(true);
+ chai.assert.equal(this.getPrevious().length, 3);
+ chai.assert.equal(this.getNext().length, 3);
+
+ block.setCollapsed(false);
+ chai.assert.equal(this.getPrevious().length, 3);
+ chai.assert.equal(this.getNext().length, 3);
+ });
+ test('Row', function() {
+ var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
+ ''
+ ), this.workspace);
+ this.clock.tick(1);
+ chai.assert.equal(this.getOutputs().length, 1);
+ chai.assert.equal(this.getInputs().length, 1);
+
+ block.setCollapsed(true);
+ chai.assert.equal(this.getOutputs().length, 1);
+ chai.assert.equal(this.getInputs().length, 0);
+
+ block.setCollapsed(false);
+ chai.assert.equal(this.getOutputs().length, 1);
+ chai.assert.equal(this.getInputs().length, 1);
+ });
+ test('Multi-Row', function() {
+ var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ''
+ ), this.workspace);
+ this.clock.tick(1);
+ chai.assert.equal(this.getOutputs().length, 3);
+ chai.assert.equal(this.getInputs().length, 3);
+
+ block.setCollapsed(true);
+ chai.assert.equal(this.getOutputs().length, 1);
+ chai.assert.equal(this.getInputs().length, 0);
+
+ block.setCollapsed(false);
+ chai.assert.equal(this.getOutputs().length, 3);
+ chai.assert.equal(this.getInputs().length, 3);
+ });
+ test('Multi-Row Middle', function() {
+ var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ''
+ ), this.workspace);
+ this.clock.tick(1);
+ chai.assert.equal(this.getOutputs().length, 3);
+ chai.assert.equal(this.getInputs().length, 3);
+
+ block = block.getInputTargetBlock('INPUT');
+ block.setCollapsed(true);
+ chai.assert.equal(this.getOutputs().length, 2);
+ chai.assert.equal(this.getInputs().length, 1);
+
+ block.setCollapsed(false);
+ chai.assert.equal(this.getOutputs().length, 3);
+ chai.assert.equal(this.getInputs().length, 3);
+ });
+ test('Multi-Row Double Collapse', function() {
+ // Collapse middle -> Collapse top ->
+ // Uncollapse top -> Uncollapse middle
+ var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ''
+ ), this.workspace);
+ this.clock.tick(1);
+ chai.assert.equal(this.getOutputs().length, 3);
+ chai.assert.equal(this.getInputs().length, 3);
+
+ var middleBlock = block.getInputTargetBlock('INPUT');
+ middleBlock.setCollapsed(true);
+ chai.assert.equal(this.getOutputs().length, 2);
+ chai.assert.equal(this.getInputs().length, 1);
+
+ block.setCollapsed(true);
+ chai.assert.equal(this.getOutputs().length, 1);
+ chai.assert.equal(this.getInputs().length, 0);
+
+ block.setCollapsed(false);
+ chai.assert.equal(this.getOutputs().length, 2);
+ chai.assert.equal(this.getInputs().length, 1);
+
+ middleBlock.setCollapsed(false);
+ chai.assert.equal(this.getOutputs().length, 3);
+ chai.assert.equal(this.getInputs().length, 3);
+ });
+ test('Statement', function() {
+ var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
+ ''
+ ), this.workspace);
+ this.clock.tick(1);
+ chai.assert.equal(this.getPrevious().length, 1);
+ chai.assert.equal(this.getNext().length, 2);
+
+ block.setCollapsed(true);
+ chai.assert.equal(this.getPrevious().length, 1);
+ chai.assert.equal(this.getNext().length, 1);
+
+ block.setCollapsed(false);
+ chai.assert.equal(this.getPrevious().length, 1);
+ chai.assert.equal(this.getNext().length, 2);
+ });
+ test('Multi-Statement', function() {
+ var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ''
+ ), this.workspace);
+ this.assertConnectionsEmpty();
+ this.clock.tick(1);
+ chai.assert.equal(this.getPrevious().length, 3);
+ chai.assert.equal(this.getNext().length, 6);
+
+ block.setCollapsed(true);
+ chai.assert.equal(this.getPrevious().length, 1);
+ chai.assert.equal(this.getNext().length, 1);
+
+ block.setCollapsed(false);
+ chai.assert.equal(this.getPrevious().length, 3);
+ chai.assert.equal(this.getNext().length, 6);
+ });
+ test('Multi-Statement Middle', function() {
+ var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ''
+ ), this.workspace);
+ this.assertConnectionsEmpty();
+ this.clock.tick(1);
+ chai.assert.equal(this.getPrevious().length, 3);
+ chai.assert.equal(this.getNext().length, 6);
+
+ block = block.getInputTargetBlock('STATEMENT');
+ block.setCollapsed(true);
+ chai.assert.equal(this.getPrevious().length, 2);
+ chai.assert.equal(this.getNext().length, 3);
+
+ block.setCollapsed(false);
+ chai.assert.equal(this.getPrevious().length, 3);
+ chai.assert.equal(this.getNext().length, 6);
+ });
+ test('Multi-Statement Double Collapse', function() {
+ var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ''
+ ), this.workspace);
+ this.assertConnectionsEmpty();
+ this.clock.tick(1);
+ chai.assert.equal(this.getPrevious().length, 3);
+ chai.assert.equal(this.getNext().length, 6);
+
+ var middleBlock = block.getInputTargetBlock('STATEMENT');
+ middleBlock.setCollapsed(true);
+ chai.assert.equal(this.getPrevious().length, 2);
+ chai.assert.equal(this.getNext().length, 3);
+
+ block.setCollapsed(true);
+ chai.assert.equal(this.getPrevious().length, 1);
+ chai.assert.equal(this.getNext().length, 1);
+
+ block.setCollapsed(false);
+ chai.assert.equal(this.getPrevious().length, 2);
+ chai.assert.equal(this.getNext().length, 3);
+
+ middleBlock.setCollapsed(false);
+ chai.assert.equal(this.getPrevious().length, 3);
+ chai.assert.equal(this.getNext().length, 6);
+ });
+ });
+ suite('Remove Connections Programmatically', function() {
+ test('Output', function() {
+ var block = this.workspace.newBlock('row_block');
+ block.initSvg();
+ block.render();
+
+ block.setOutput(false);
+
+ chai.assert.equal(this.getOutputs().length, 0);
+ chai.assert.equal(this.getInputs().length, 1);
+ });
+ test('Value', function() {
+ var block = this.workspace.newBlock('row_block');
+ block.initSvg();
+ block.render();
+
+ block.removeInput('INPUT');
+
+ chai.assert.equal(this.getOutputs().length, 1);
+ chai.assert.equal(this.getInputs().length, 0);
+ });
+ test('Previous', function() {
+ var block = this.workspace.newBlock('stack_block');
+ block.initSvg();
+ block.render();
+
+ block.setPreviousStatement(false);
+
+ chai.assert.equal(this.getPrevious().length, 0);
+ chai.assert.equal(this.getNext().length, 1);
+ });
+ test('Next', function() {
+ var block = this.workspace.newBlock('stack_block');
+ block.initSvg();
+ block.render();
+
+ block.setNextStatement(false);
+
+ chai.assert.equal(this.getPrevious().length, 1);
+ chai.assert.equal(this.getNext().length, 0);
+ });
+ test('Statement', function() {
+ var block = this.workspace.newBlock('statement_block');
+ block.initSvg();
+ block.render();
+
+ block.removeInput('STATEMENT');
+
+ chai.assert.equal(this.getPrevious().length, 1);
+ chai.assert.equal(this.getNext().length, 1);
+ });
+ });
+ suite('Add Connections Programmatically', function() {
+ test('Output', function() {
+ var block = this.workspace.newBlock('empty_block');
+ block.initSvg();
+ block.render();
+
+ block.setOutput(true);
+
+ chai.assert.equal(this.getOutputs().length, 1);
+ });
+ test('Value', function() {
+ var block = this.workspace.newBlock('empty_block');
+ block.initSvg();
+ block.render();
+
+ block.appendValueInput('INPUT');
+
+ chai.assert.equal(this.getInputs().length, 1);
+ });
+ test('Previous', function() {
+ var block = this.workspace.newBlock('empty_block');
+ block.initSvg();
+ block.render();
+
+ block.setPreviousStatement(true);
+
+ chai.assert.equal(this.getPrevious().length, 1);
+ });
+ test('Next', function() {
+ var block = this.workspace.newBlock('empty_block');
+ block.initSvg();
+ block.render();
+
+ block.setNextStatement(true);
+
+ chai.assert.equal(this.getNext().length, 1);
+ });
+ test('Statement', function() {
+ var block = this.workspace.newBlock('empty_block');
+ block.initSvg();
+ block.render();
+
+ block.appendStatementInput('STATEMENT');
+
+ chai.assert.equal(this.getNext().length, 1);
});
});
});