From 49405a88bde1e0a958a587313a23769a702fcaea Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Fri, 2 Aug 2019 09:27:25 -0700 Subject: [PATCH 01/10] Changed connections to handle disposing of connected blocks. --- core/connection.js | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/core/connection.js b/core/connection.js index 52e5f9f11..4fafba222 100644 --- a/core/connection.js +++ b/core/connection.js @@ -233,15 +233,29 @@ Blockly.Connection.prototype.connect_ = function(childConnection) { }; /** - * Sever all links to this connection (not including from the source object). + * Dispose of this connection. Deal with connected blocks and remove this + * connection from the database. */ Blockly.Connection.prototype.dispose = function() { + + // isConnected returns true for shadows and non-shadows. if (this.isConnected()) { - throw Error('Disconnect connection before disposing of it.'); + this.setShadowDom(null); + var targetBlock = this.targetBlock(); + if (targetBlock.isShadow()) { + // Destroy the attached shadow block & its children. + targetBlock.dispose(); + } else { + // Disconnect the attached normal block. + targetBlock.unplug(); + } } + if (this.inDB_) { this.db_.removeConnection_(this); } + + this.disposed = true; }; /** From 8921fa96a9b714fbd80ba238a4bd5100342892e2 Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Fri, 2 Aug 2019 09:37:02 -0700 Subject: [PATCH 02/10] Removed redundant connection disposal code from block. --- core/block.js | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/core/block.js b/core/block.js index 44bef90e3..6eb1f3628 100644 --- a/core/block.js +++ b/core/block.js @@ -316,12 +316,8 @@ Blockly.Block.prototype.dispose = function(healStack) { this.inputList.length = 0; // Dispose of any remaining connections (next/previous/output). var connections = this.getConnections_(true); - for (var i = 0; i < connections.length; i++) { - var connection = connections[i]; - if (connection.isConnected()) { - connection.disconnect(); - } - connections[i].dispose(); + for (var i = 0, connection; connection = connections[i]; i++) { + connection.dispose(); } } finally { Blockly.Events.enable(); @@ -1759,17 +1755,6 @@ Blockly.Block.prototype.moveNumberedInputBefore = function( Blockly.Block.prototype.removeInput = function(name, opt_quiet) { for (var i = 0, input; input = this.inputList[i]; i++) { if (input.name == name) { - if (input.connection && input.connection.isConnected()) { - input.connection.setShadowDom(null); - var block = input.connection.targetBlock(); - if (block.isShadow()) { - // Destroy any attached shadow block. - block.dispose(); - } else { - // Disconnect any attached normal block. - block.unplug(); - } - } input.dispose(); this.inputList.splice(i, 1); return; From a4b4f090714d65b48199a85a6e829733f172235a Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Fri, 2 Aug 2019 09:37:26 -0700 Subject: [PATCH 03/10] Fixed connection dispose jsdoc. --- core/connection.js | 1 + 1 file changed, 1 insertion(+) diff --git a/core/connection.js b/core/connection.js index 4fafba222..d727f97c8 100644 --- a/core/connection.js +++ b/core/connection.js @@ -235,6 +235,7 @@ Blockly.Connection.prototype.connect_ = function(childConnection) { /** * Dispose of this connection. Deal with connected blocks and remove this * connection from the database. + * @package */ Blockly.Connection.prototype.dispose = function() { From b28788fad6e60fab57eb79f3d6c28667d1336c09 Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Fri, 2 Aug 2019 09:57:32 -0700 Subject: [PATCH 04/10] Added licenses to tests and changed some formatting. --- tests/mocha/block_test.js | 32 +++++++++++++++++++------------- tests/mocha/connection_test.js | 21 ++++++++++++++++++++- 2 files changed, 39 insertions(+), 14 deletions(-) diff --git a/tests/mocha/block_test.js b/tests/mocha/block_test.js index 82ea90490..79bb776f0 100644 --- a/tests/mocha/block_test.js +++ b/tests/mocha/block_test.js @@ -1,7 +1,24 @@ - +/** + * @license + * Visual Blocks Editor + * + * Copyright 2019 Google Inc. + * https://developers.google.com/blockly/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ suite('Blocks', function() { - suite('Unplug', function() { function assertUnpluggedNoheal(blocks) { // A has nothing connected to it. @@ -11,7 +28,6 @@ suite('Blocks', function() { // 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); @@ -43,7 +59,6 @@ suite('Blocks', function() { this.workspace = new Blockly.Workspace(); }); - teardown(function() { delete Blockly.Blocks['stack_block']; delete Blockly.Blocks['row_block']; @@ -73,13 +88,11 @@ suite('Blocks', 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; @@ -91,7 +104,6 @@ suite('Blocks', function() { blocks.B.unplug(true); assertUnpluggedNoheal(blocks); }); - test('Parent has multiple inputs', function() { var blocks = this.blocks; // Add extra input to parent @@ -99,7 +111,6 @@ suite('Blocks', function() { blocks.B.unplug(true); assertUnpluggedHealed(blocks); }); - test('Middle block has multiple inputs', function() { var blocks = this.blocks; // Add extra input to middle block @@ -107,7 +118,6 @@ suite('Blocks', function() { blocks.B.unplug(true); assertUnpluggedHealed(blocks); }); - test('Child block has multiple inputs', function() { var blocks = this.blocks; // Add extra input to child block @@ -117,8 +127,6 @@ suite('Blocks', function() { assertUnpluggedHealed(blocks); }); }); - - suite('Stack', function() { setup(function() { var blockA = this.workspace.newBlock('stack_block'); @@ -141,12 +149,10 @@ suite('Blocks', 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. diff --git a/tests/mocha/connection_test.js b/tests/mocha/connection_test.js index b4d171927..0e8884160 100644 --- a/tests/mocha/connection_test.js +++ b/tests/mocha/connection_test.js @@ -1,7 +1,26 @@ - +/** + * @license + * Visual Blocks Editor + * + * Copyright 2019 Google Inc. + * https://developers.google.com/blockly/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ suite('Connections', function() { + suite.skip('Rendered', function() { function assertAllConnectionsHiddenState(block, hidden) { var connections = block.getConnections_(true); From 995a2658fea144d9f2a2b46852b6d84484eb12f3 Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Fri, 2 Aug 2019 10:14:42 -0700 Subject: [PATCH 05/10] Added shadow unplug tests. --- tests/mocha/block_test.js | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/tests/mocha/block_test.js b/tests/mocha/block_test.js index 79bb776f0..8a173ad53 100644 --- a/tests/mocha/block_test.js +++ b/tests/mocha/block_test.js @@ -104,21 +104,21 @@ suite('Blocks', function() { blocks.B.unplug(true); assertUnpluggedNoheal(blocks); }); - test('Parent has multiple inputs', function() { + 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('Middle block has multiple inputs', function() { + 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('Child block has multiple inputs', function() { + test('C has multiple inputs', function() { var blocks = this.blocks; // Add extra input to child block blocks.C.appendValueInput("INPUT").setCheck(null); @@ -126,6 +126,14 @@ suite('Blocks', function() { 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() { @@ -177,6 +185,14 @@ suite('Blocks', function() { // C is the top of its stack. assertNull(blocks.C.getParent()); }); + 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); + }); }); }); }); From 057cee51f8c1687c7fff711ebb25cf9448c73882 Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Fri, 2 Aug 2019 10:44:56 -0700 Subject: [PATCH 06/10] Added disposed property to blocks (for use in unit tests). --- core/block.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/block.js b/core/block.js index 6eb1f3628..9a5b32200 100644 --- a/core/block.js +++ b/core/block.js @@ -322,6 +322,8 @@ Blockly.Block.prototype.dispose = function(healStack) { } finally { Blockly.Events.enable(); } + + this.disposed = true; }; /** From 2f88a40f9bfa956927887f779c326c667ce64049 Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Fri, 2 Aug 2019 10:45:54 -0700 Subject: [PATCH 07/10] Added block disposal tests. --- tests/mocha/block_test.js | 418 +++++++++++++++++++++++++------------- 1 file changed, 275 insertions(+), 143 deletions(-) diff --git a/tests/mocha/block_test.js b/tests/mocha/block_test.js index 8a173ad53..f9f97c32b 100644 --- a/tests/mocha/block_test.js +++ b/tests/mocha/block_test.js @@ -19,25 +19,14 @@ */ suite('Blocks', 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()); - } + setup(function() { + this.workspace = new Blockly.Workspace(); + }); + teardown(function() { + this.workspace.dispose(); + }); + suite('Connection Management', function() { setup(function() { Blockly.defineBlocksWithJsonArray([{ "type": "stack_block", @@ -56,142 +45,285 @@ suite('Blocks', function() { ], "output": null }]); - - this.workspace = new Blockly.Workspace(); }); teardown(function() { delete Blockly.Blocks['stack_block']; delete Blockly.Blocks['row_block']; - - this.workspace.dispose(); }); - 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); - assertUnpluggedNoheal(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); - - // Stack blocks unplug before checking whether the types match. - // TODO (#1994): Check types before unplugging. + suite('Unplug', function() { + function assertUnpluggedNoheal(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); - // C has nothing connected to it. - assertEquals(0, blocks.C.getChildren().length); - // A is the top of its stack. - assertNull(blocks.A.getParent()); + // B and C are still connected. + assertEquals(blocks.B, blocks.C.getParent()); // B is the top of its stack. assertNull(blocks.B.getParent()); - // C is the top of its stack. - assertNull(blocks.C.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()); + } + + 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); + assertUnpluggedNoheal(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); + }); }); - 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.skip('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); + + // TODO (#1994): Check types before unplugging. Currently + // everything disconnects, when C should stick with B + assertUnpluggedNoheal(); + }); + 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.isNotOk(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.isNotOk(blocks.A.disposed); + chai.assert.isNotOk(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); + } + + 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); + assertDisposedNoheal(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.skip('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); + + // TODO (#1994): Check types before unplugging. Current C gets + // left behind when it should get disposed with B. + assertDisposedNoheal(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); + }); }); }); }); From 56f424d8edb1606f03b549f735ee4e3cb2e3653e Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Fri, 2 Aug 2019 12:03:24 -0700 Subject: [PATCH 08/10] Added removeInput tests. --- tests/mocha/block_test.js | 90 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/tests/mocha/block_test.js b/tests/mocha/block_test.js index f9f97c32b..f22560a83 100644 --- a/tests/mocha/block_test.js +++ b/tests/mocha/block_test.js @@ -326,5 +326,95 @@ suite('Blocks', function() { }); }); }); + 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']; + }); + + 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.isNotOk(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); + }); + }); + suite('Statement', function() { + setup(function() { + this.blockA = this.workspace.newBlock('statement_block'); + }); + + 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); + + this.blockA.removeInput('STATEMENT'); + chai.assert.isNotOk(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'); + console.log(blockB.disposed, blockB); + chai.assert.isTrue(blockB.disposed); + chai.assert.equal(this.blockA.getChildren().length, 0); + }); + }); + }); }); }); From 6f03634730c5da4811406fc97be95b256746b002 Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Wed, 7 Aug 2019 07:11:17 -0700 Subject: [PATCH 09/10] Moved disposed into finally block. --- core/block.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/block.js b/core/block.js index 9a5b32200..a6bea0c58 100644 --- a/core/block.js +++ b/core/block.js @@ -321,9 +321,8 @@ Blockly.Block.prototype.dispose = function(healStack) { } } finally { Blockly.Events.enable(); + this.disposed = true; } - - this.disposed = true; }; /** From 654467e9ac656c71fb9accf0e23219d5d61cbe16 Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Wed, 7 Aug 2019 07:17:41 -0700 Subject: [PATCH 10/10] Added disposed = false properties to fields, connections, and blocks. Changed applicable unit tests from isNotOk -> isFalse. --- core/block.js | 7 +++++++ core/connection.js | 7 +++++++ core/field.js | 8 ++++++++ tests/mocha/block_test.js | 10 +++++----- tests/mocha/connection_test.js | 1 - 5 files changed, 27 insertions(+), 6 deletions(-) diff --git a/core/block.js b/core/block.js index a6bea0c58..a3d794a8c 100644 --- a/core/block.js +++ b/core/block.js @@ -226,6 +226,13 @@ Blockly.Block.obtain = function(workspace, prototypeName) { */ Blockly.Block.prototype.data = null; +/** + * Has this block been disposed of? + * @type {boolean} + * @package + */ +Blockly.Block.prototype.disposed = false; + /** * Colour of the block as HSV hue value (0-360) * This may be null if the block colour was not set via a hue number. diff --git a/core/connection.js b/core/connection.js index d727f97c8..7594642a2 100644 --- a/core/connection.js +++ b/core/connection.js @@ -71,6 +71,13 @@ Blockly.Connection.REASON_SHADOW_PARENT = 6; */ Blockly.Connection.prototype.targetConnection = null; +/** + * Has this connection been disposed of? + * @type {boolean} + * @package + */ +Blockly.Connection.prototype.disposed = false; + /** * List of compatible value types. Null if all types are compatible. * @type {Array} diff --git a/core/field.js b/core/field.js index 09bb1f1df..51bde7c69 100644 --- a/core/field.js +++ b/core/field.js @@ -149,6 +149,7 @@ Blockly.Field.X_PADDING = 10; * @type {[type]} */ Blockly.Field.DEFAULT_TEXT_OFFSET = Blockly.Field.X_PADDING / 2; + /** * Name of field. Unique within each block. * Static labels are usually unnamed. @@ -156,6 +157,13 @@ Blockly.Field.DEFAULT_TEXT_OFFSET = Blockly.Field.X_PADDING / 2; */ Blockly.Field.prototype.name = undefined; +/** + * Has this field been disposed of? + * @type {boolean} + * @package + */ +Blockly.Field.prototype.disposed = false; + /** * Maximum characters of text to display before adding an ellipsis. * @type {number} diff --git a/tests/mocha/block_test.js b/tests/mocha/block_test.js index f22560a83..b0d87ed4f 100644 --- a/tests/mocha/block_test.js +++ b/tests/mocha/block_test.js @@ -190,7 +190,7 @@ suite('Blocks', function() { }); suite('Dispose', function() { function assertDisposedNoheal(blocks) { - chai.assert.isNotOk(blocks.A.disposed); + chai.assert.isFalse(blocks.A.disposed); // A has nothing connected to it. chai.assert.equal(0, blocks.A.getChildren().length); // B is disposed. @@ -199,8 +199,8 @@ suite('Blocks', function() { chai.assert.isTrue(blocks.C.disposed); } function assertDisposedHealed(blocks) { - chai.assert.isNotOk(blocks.A.disposed); - chai.assert.isNotOk(blocks.C.disposed); + 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()); @@ -371,7 +371,7 @@ suite('Blocks', function() { .connect(blockB.outputConnection); this.blockA.removeInput('VALUE'); - chai.assert.isNotOk(blockB.disposed); + chai.assert.isFalse(blockB.disposed); chai.assert.equal(this.blockA.getChildren().length, 0); }); test('Shadow Connected', function() { @@ -400,7 +400,7 @@ suite('Blocks', function() { .connect(blockB.previousConnection); this.blockA.removeInput('STATEMENT'); - chai.assert.isNotOk(blockB.disposed); + chai.assert.isFalse(blockB.disposed); chai.assert.equal(this.blockA.getChildren().length, 0); }); test('Shadow Connected', function() { diff --git a/tests/mocha/connection_test.js b/tests/mocha/connection_test.js index 0e8884160..9f7f726c6 100644 --- a/tests/mocha/connection_test.js +++ b/tests/mocha/connection_test.js @@ -20,7 +20,6 @@ suite('Connections', function() { - suite.skip('Rendered', function() { function assertAllConnectionsHiddenState(block, hidden) { var connections = block.getConnections_(true);