From 9e8739e82197cf8339f5fa17ee7a0c9c4b2307bb Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Thu, 11 Aug 2022 19:58:52 +0000 Subject: [PATCH 01/18] feat!: allow blocks to receive their own delete events --- core/block.ts | 10 +++++----- tests/mocha/index.html | 1 + 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/core/block.ts b/core/block.ts index 9e345e1d8e1..0322d95006e 100644 --- a/core/block.ts +++ b/core/block.ts @@ -316,18 +316,18 @@ export class Block implements IASTNodeLocation, IDeletable { */ dispose(healStack: boolean) { if (this.disposed) { - // Already deleted. return; } - // Terminate onchange event calls. - if (this.onchangeWrapper_) { - this.workspace.removeChangeListener(this.onchangeWrapper_); - } this.unplug(healStack); if (eventUtils.isEnabled()) { eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_DELETE))!(this)); } + + if (this.onchangeWrapper_) { + this.workspace.removeChangeListener(this.onchangeWrapper_); + } + eventUtils.disable(); try { diff --git a/tests/mocha/index.html b/tests/mocha/index.html index 10fe5ed4559..c68ea1e2549 100644 --- a/tests/mocha/index.html +++ b/tests/mocha/index.html @@ -51,6 +51,7 @@ // Test modules. 'Blockly.test.astNode', 'Blockly.test.blockChangeEvent', + 'Blockly.test.blockDeleteEvent', 'Blockly.test.blockCreateEvent', 'Blockly.test.blockJson', 'Blockly.test.blocks', From 7f475c074c528f6e7ba51afea3b71e2dd38c5740 Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Thu, 11 Aug 2022 20:41:36 +0000 Subject: [PATCH 02/18] fix: move block tests back into main directory --- tests/mocha/{blocks => }/lists_test.js | 6 +++--- tests/mocha/{blocks => }/logic_ternary_test.js | 0 tests/mocha/{blocks => }/procedures_test.js | 0 tests/mocha/{blocks => }/variables_test.js | 0 4 files changed, 3 insertions(+), 3 deletions(-) rename tests/mocha/{blocks => }/lists_test.js (95%) rename tests/mocha/{blocks => }/logic_ternary_test.js (100%) rename tests/mocha/{blocks => }/procedures_test.js (100%) rename tests/mocha/{blocks => }/variables_test.js (100%) diff --git a/tests/mocha/blocks/lists_test.js b/tests/mocha/lists_test.js similarity index 95% rename from tests/mocha/blocks/lists_test.js rename to tests/mocha/lists_test.js index 166fef5ad18..119d7701a47 100644 --- a/tests/mocha/blocks/lists_test.js +++ b/tests/mocha/lists_test.js @@ -6,10 +6,10 @@ goog.declareModuleId('Blockly.test.lists'); -import {runSerializationTestSuite} from '../test_helpers/serialization.js'; -import {sharedTestSetup, sharedTestTeardown} from '../test_helpers/setup_teardown.js'; +import {runSerializationTestSuite} from './test_helpers/serialization.js'; +import {sharedTestSetup, sharedTestTeardown} from './test_helpers/setup_teardown.js'; import {ConnectionType} from '../../build/src/core/connection_type.js'; -import {defineStatementBlock} from '../test_helpers/block_definitions.js'; +import {defineStatementBlock} from './test_helpers/block_definitions.js'; suite('Lists', function() { diff --git a/tests/mocha/blocks/logic_ternary_test.js b/tests/mocha/logic_ternary_test.js similarity index 100% rename from tests/mocha/blocks/logic_ternary_test.js rename to tests/mocha/logic_ternary_test.js diff --git a/tests/mocha/blocks/procedures_test.js b/tests/mocha/procedures_test.js similarity index 100% rename from tests/mocha/blocks/procedures_test.js rename to tests/mocha/procedures_test.js diff --git a/tests/mocha/blocks/variables_test.js b/tests/mocha/variables_test.js similarity index 100% rename from tests/mocha/blocks/variables_test.js rename to tests/mocha/variables_test.js From 7c35e2c88c990b52245eafff924910e9d90a050b Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Thu, 11 Aug 2022 21:56:03 +0000 Subject: [PATCH 03/18] chore: add a test for disposing of callers --- blocks/procedures.js | 2 +- tests/mocha/logic_ternary_test.js | 4 +-- tests/mocha/procedures_test.js | 46 ++++++++++++++++++++++++++++--- 3 files changed, 45 insertions(+), 7 deletions(-) diff --git a/blocks/procedures.js b/blocks/procedures.js index 011c6322803..6078e55a315 100644 --- a/blocks/procedures.js +++ b/blocks/procedures.js @@ -980,7 +980,7 @@ const PROCEDURE_CALL_COMMON = { Xml.domToWorkspace(xml, this.workspace); Events.setGroup(false); } - } else if (event.type === Events.BLOCK_DELETE) { + } else if (event.type === Events.BLOCK_DELETE && event.blockId != this.id) { // Look for the case where a procedure definition has been deleted, // leaving this block (a procedure call) orphaned. In this case, delete // the orphan. diff --git a/tests/mocha/logic_ternary_test.js b/tests/mocha/logic_ternary_test.js index a539124a667..5bb9f0f09c6 100644 --- a/tests/mocha/logic_ternary_test.js +++ b/tests/mocha/logic_ternary_test.js @@ -7,8 +7,8 @@ goog.declareModuleId('Blockly.test.logicTernary'); import * as eventUtils from '../../build/src/core/events/utils.js'; -import {runSerializationTestSuite} from '../test_helpers/serialization.js'; -import {sharedTestSetup, sharedTestTeardown} from '../test_helpers/setup_teardown.js'; +import {runSerializationTestSuite} from './test_helpers/serialization.js'; +import {sharedTestSetup, sharedTestTeardown} from './test_helpers/setup_teardown.js'; suite('Logic ternary', function() { diff --git a/tests/mocha/procedures_test.js b/tests/mocha/procedures_test.js index 64d48d95859..2000e3c4816 100644 --- a/tests/mocha/procedures_test.js +++ b/tests/mocha/procedures_test.js @@ -7,10 +7,9 @@ goog.declareModuleId('Blockly.test.procedures'); import * as Blockly from '../../build/src/core/blockly.js'; -import {assertCallBlockStructure, assertDefBlockStructure, createProcDefBlock, createProcCallBlock} from '../test_helpers/procedures.js'; -import {runSerializationTestSuite} from '../test_helpers/serialization.js'; -import {createGenUidStubWithReturns, sharedTestSetup, sharedTestTeardown, workspaceTeardown} from '../test_helpers/setup_teardown.js'; - +import {assertCallBlockStructure, assertDefBlockStructure, createProcDefBlock, createProcCallBlock} from './test_helpers/procedures.js'; +import {runSerializationTestSuite} from './test_helpers/serialization.js'; +import {createGenUidStubWithReturns, sharedTestSetup, sharedTestTeardown, workspaceTeardown} from './test_helpers/setup_teardown.js'; suite('Procedures', function() { setup(function() { @@ -1033,6 +1032,7 @@ suite('Procedures', function() { }); }); }); + /** * Test cases for serialization tests. * @type {Array} @@ -1210,3 +1210,41 @@ suite('Procedures', function() { }); }); }); + +suite('Procedures, dont auto fire events', function() { + setup(function() { + sharedTestSetup.call(this, {fireEventsNow: false}); + this.workspace = new Blockly.Workspace(); + }); + teardown(function() { + sharedTestTeardown.call(this); + }); + + const testSuites = [ + {title: 'procedures_defreturn', hasReturn: true, + defType: 'procedures_defreturn', callType: 'procedures_callreturn'}, + {title: 'procedures_defnoreturn', hasReturn: false, + defType: 'procedures_defnoreturn', callType: 'procedures_callnoreturn'}, + ]; + + testSuites.forEach((testSuite) => { + suite(testSuite.title, function() { + suite('Disposal', function() { + test('callers are disposed when definitions are disposed', function() { + this.defBlock = new Blockly.Block(this.workspace, testSuite.defType); + this.defBlock.setFieldValue('proc name', 'NAME'); + this.callerBlock = new Blockly.Block( + this.workspace, testSuite.callType); + this.callerBlock.setFieldValue('proc name', 'NAME'); + + + this.clock.runAll(); + this.defBlock.dispose(); + this.clock.runAll(); + + chai.assert.isTrue(this.callerBlock.disposed); + }); + }); + }); + }); +}); From a4a7a50960de176f4f39ce4c6bd2b77cdffba741 Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Thu, 11 Aug 2022 21:57:32 +0000 Subject: [PATCH 04/18] chore: add test for delete being received --- tests/mocha/block_delete_event_test.js | 39 ++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 tests/mocha/block_delete_event_test.js diff --git a/tests/mocha/block_delete_event_test.js b/tests/mocha/block_delete_event_test.js new file mode 100644 index 00000000000..c4bef949d24 --- /dev/null +++ b/tests/mocha/block_delete_event_test.js @@ -0,0 +1,39 @@ +/** + * @license + * Copyright 2021 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +goog.declareModuleId('Blockly.test.blockDeleteEvent'); + +import * as eventUtils from '../../build/src/core/events/utils.js'; +import {sharedTestSetup, sharedTestTeardown} from './test_helpers/setup_teardown.js'; + +suite('Block Delete Event', function() { + setup(function() { + sharedTestSetup.call(this); + this.workspace = new Blockly.Workspace(); + }); + + teardown(function() { + sharedTestTeardown.call(this); + }); + + suite('Receiving', function() { + test('blocks receive their own delete events', function() { + Blockly.Blocks['test'] = { + onchange: function(e) {}, + }; + // Need to stub the definition, because the property on the definition is + // what gets registered as an event listener. + const spy = sinon.spy(Blockly.Blocks['test'], 'onchange'); + const testBlock = this.workspace.newBlock('test'); + + testBlock.dispose(); + + const deleteClass = eventUtils.get(eventUtils.BLOCK_DELETE); + chai.assert.isTrue(spy.calledOnce); + chai.assert.isTrue(spy.getCall(0).args[0] instanceof deleteClass); + }); + }); +}); From 76e850ea68d02afbb1744ded9fcd075739800adf Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Thu, 11 Aug 2022 21:58:36 +0000 Subject: [PATCH 05/18] chore: add comment about why we have to run the clock twice --- tests/mocha/procedures_test.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/mocha/procedures_test.js b/tests/mocha/procedures_test.js index 2000e3c4816..90d4d373136 100644 --- a/tests/mocha/procedures_test.js +++ b/tests/mocha/procedures_test.js @@ -1237,7 +1237,9 @@ suite('Procedures, dont auto fire events', function() { this.workspace, testSuite.callType); this.callerBlock.setFieldValue('proc name', 'NAME'); - + // Run the clock now so that the create events get fired. If we fire + // it after disposing, a new procedure def will get created when + // the caller create event is heard. this.clock.runAll(); this.defBlock.dispose(); this.clock.runAll(); From 0f4abe932e5c7cefc6bd8521faa4a87250f38c5e Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Fri, 12 Aug 2022 21:17:18 +0000 Subject: [PATCH 06/18] chore: fix whitespace --- tests/mocha/index.html | 172 ++++++++++++++++++++--------------------- 1 file changed, 86 insertions(+), 86 deletions(-) diff --git a/tests/mocha/index.html b/tests/mocha/index.html index c68ea1e2549..0c58062447e 100644 --- a/tests/mocha/index.html +++ b/tests/mocha/index.html @@ -19,8 +19,8 @@ + goog.require() the test modules that make calls to (e.g.) + suite() at the top level. --> @@ -31,91 +31,91 @@ }); var BLOCKLY_BOOTSTRAP_OPTIONS = { - loadCompressed: false, - depsFiles: ['build/deps.js', 'build/deps.mocha.js'], - requires: [ - // Blockly modules needed by tests. - 'Blockly', - 'Blockly.libraryBlocks', - 'Blockly.Dart', - 'Blockly.Dart.texts', - 'Blockly.JavaScript', - 'Blockly.JavaScript.texts', - 'Blockly.Lua', - 'Blockly.Lua.texts', - 'Blockly.PHP', - 'Blockly.PHP.texts', - 'Blockly.Python', - 'Blockly.Python.texts', + loadCompressed: false, + depsFiles: ['build/deps.js', 'build/deps.mocha.js'], + requires: [ + // Blockly modules needed by tests. + 'Blockly', + 'Blockly.libraryBlocks', + 'Blockly.Dart', + 'Blockly.Dart.texts', + 'Blockly.JavaScript', + 'Blockly.JavaScript.texts', + 'Blockly.Lua', + 'Blockly.Lua.texts', + 'Blockly.PHP', + 'Blockly.PHP.texts', + 'Blockly.Python', + 'Blockly.Python.texts', - // Test modules. - 'Blockly.test.astNode', - 'Blockly.test.blockChangeEvent', - 'Blockly.test.blockDeleteEvent', - 'Blockly.test.blockCreateEvent', - 'Blockly.test.blockJson', - 'Blockly.test.blocks', - 'Blockly.test.comments', - 'Blockly.test.commentDeserialization', - 'Blockly.test.connectionChecker', - 'Blockly.test.connectionDb', - 'Blockly.test.connection', - 'Blockly.test.contextMenuItem', - 'Blockly.test.cursor', - 'Blockly.test.dropdown', - 'Blockly.test.event', - 'Blockly.test.extensions', - 'Blockly.test.fieldAngle', - 'Blockly.test.fieldCheckbox', - 'Blockly.test.fieldColour', - 'Blockly.test.fieldDropdown', - 'Blockly.test.fieldImage', - 'Blockly.test.fieldLabelSerialization', - 'Blockly.test.fieldLabel', - 'Blockly.test.fieldMultiline', - 'Blockly.test.fieldNumber', - 'Blockly.test.fieldRegistry', - 'Blockly.test.fieldTest', - 'Blockly.test.fieldTextInput', - 'Blockly.test.fieldVariable', - 'Blockly.test.flyout', - 'Blockly.test.generator', - 'Blockly.test.gesture', - 'Blockly.test.input', - 'Blockly.test.insertionMarker', - 'Blockly.test.jsoDeserialization', - 'Blockly.test.jsoSerialization', - 'Blockly.test.json', - 'Blockly.test.keydown', - 'Blockly.test.lists', - 'Blockly.test.logicTernary', - 'Blockly.test.metrics', - 'Blockly.test.mutator', - 'Blockly.test.names', - 'Blockly.test.procedures', - 'Blockly.test.registry', - 'Blockly.test.serialization', - 'Blockly.test.shortcutRegistry', - 'Blockly.test.theme', - 'Blockly.test.toolbox', - 'Blockly.test.tooltip', - 'Blockly.test.trashcan', - 'Blockly.test.utils', - 'Blockly.test.variableMap', - 'Blockly.test.variableModel', - 'Blockly.test.variables', - 'Blockly.test.widgetDiv', - 'Blockly.test.workspaceComment', - 'Blockly.test.workspaceSvg', - 'Blockly.test.workspace', - 'Blockly.test.xml', - 'Blockly.test.zoomControls', - ], - additionalScripts: [ - 'msg/messages.js', - 'tests/playgrounds/screenshot.js', - 'node_modules/@blockly/dev-tools/dist/index.js', - ], + // Test modules. + 'Blockly.test.astNode', + 'Blockly.test.blockChangeEvent', + 'Blockly.test.blockDeleteEvent', + 'Blockly.test.blockCreateEvent', + 'Blockly.test.blockJson', + 'Blockly.test.blocks', + 'Blockly.test.comments', + 'Blockly.test.commentDeserialization', + 'Blockly.test.connectionChecker', + 'Blockly.test.connectionDb', + 'Blockly.test.connection', + 'Blockly.test.contextMenuItem', + 'Blockly.test.cursor', + 'Blockly.test.dropdown', + 'Blockly.test.event', + 'Blockly.test.extensions', + 'Blockly.test.fieldAngle', + 'Blockly.test.fieldCheckbox', + 'Blockly.test.fieldColour', + 'Blockly.test.fieldDropdown', + 'Blockly.test.fieldImage', + 'Blockly.test.fieldLabelSerialization', + 'Blockly.test.fieldLabel', + 'Blockly.test.fieldMultiline', + 'Blockly.test.fieldNumber', + 'Blockly.test.fieldRegistry', + 'Blockly.test.fieldTest', + 'Blockly.test.fieldTextInput', + 'Blockly.test.fieldVariable', + 'Blockly.test.flyout', + 'Blockly.test.generator', + 'Blockly.test.gesture', + 'Blockly.test.input', + 'Blockly.test.insertionMarker', + 'Blockly.test.jsoDeserialization', + 'Blockly.test.jsoSerialization', + 'Blockly.test.json', + 'Blockly.test.keydown', + 'Blockly.test.lists', + 'Blockly.test.logicTernary', + 'Blockly.test.metrics', + 'Blockly.test.mutator', + 'Blockly.test.names', + 'Blockly.test.procedures', + 'Blockly.test.registry', + 'Blockly.test.serialization', + 'Blockly.test.shortcutRegistry', + 'Blockly.test.theme', + 'Blockly.test.toolbox', + 'Blockly.test.tooltip', + 'Blockly.test.trashcan', + 'Blockly.test.utils', + 'Blockly.test.variableMap', + 'Blockly.test.variableModel', + 'Blockly.test.variables', + 'Blockly.test.widgetDiv', + 'Blockly.test.workspaceComment', + 'Blockly.test.workspaceSvg', + 'Blockly.test.workspace', + 'Blockly.test.xml', + 'Blockly.test.zoomControls', + ], + additionalScripts: [ + 'msg/messages.js', + 'tests/playgrounds/screenshot.js', + 'node_modules/@blockly/dev-tools/dist/index.js', + ], } From 43b900e85e1da0b5863cc1146f872994583e707c Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Fri, 12 Aug 2022 21:19:08 +0000 Subject: [PATCH 07/18] chore: fix whitespace --- tests/mocha/procedures_test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/mocha/procedures_test.js b/tests/mocha/procedures_test.js index 90d4d373136..33efe6cf753 100644 --- a/tests/mocha/procedures_test.js +++ b/tests/mocha/procedures_test.js @@ -1032,7 +1032,6 @@ suite('Procedures', function() { }); }); }); - /** * Test cases for serialization tests. * @type {Array} From 4a488f7e82c8193d222e44e251a8ad2bbb13c6e7 Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Mon, 15 Aug 2022 18:04:46 +0000 Subject: [PATCH 08/18] chore: fix imports in tests --- tests/mocha/lists_test.js | 194 ----- tests/mocha/logic_ternary_test.js | 253 ------ tests/mocha/procedures_test.js | 1251 ----------------------------- tests/mocha/variables_test.js | 142 ---- 4 files changed, 1840 deletions(-) delete mode 100644 tests/mocha/lists_test.js delete mode 100644 tests/mocha/logic_ternary_test.js delete mode 100644 tests/mocha/procedures_test.js delete mode 100644 tests/mocha/variables_test.js diff --git a/tests/mocha/lists_test.js b/tests/mocha/lists_test.js deleted file mode 100644 index 119d7701a47..00000000000 --- a/tests/mocha/lists_test.js +++ /dev/null @@ -1,194 +0,0 @@ -/** - * @license - * Copyright 2022 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -goog.declareModuleId('Blockly.test.lists'); - -import {runSerializationTestSuite} from './test_helpers/serialization.js'; -import {sharedTestSetup, sharedTestTeardown} from './test_helpers/setup_teardown.js'; -import {ConnectionType} from '../../build/src/core/connection_type.js'; -import {defineStatementBlock} from './test_helpers/block_definitions.js'; - - -suite('Lists', function() { - setup(function() { - sharedTestSetup.call(this); - defineStatementBlock(); - this.workspace = new Blockly.Workspace(); - }); - - teardown(function() { - sharedTestTeardown.call(this); - }); - - suite('ListsGetIndex', function() { - /** - * Test cases for serialization tests. - * @type {Array} - */ - const testCases = [ - { - title: 'JSON not requiring mutations', - json: { - type: 'lists_getIndex', - id: '1', - fields: {MODE: 'GET', WHERE: 'FIRST'}, - }, - assertBlockStructure: (block) => { - chai.assert.equal(block.type, 'lists_getIndex'); - chai.assert.exists(block.outputConnection); - }, - }, - { - title: 'JSON requiring mutations', - json: { - type: 'lists_getIndex', - id: '1', - extraState: {isStatement: true}, - fields: {MODE: 'REMOVE', WHERE: 'FROM_START'}, - }, - assertBlockStructure: (block) => { - chai.assert.equal(block.type, 'lists_getIndex'); - chai.assert.isNotTrue(block.outputConnection); - chai.assert.isTrue( - block.getInput('AT').type === ConnectionType.INPUT_VALUE - ); - }, - }, - { - title: - 'JSON requiring mutations and extra state for previous connection', - json: { - type: 'statement_block', - id: '1', - next: { - block: { - type: 'lists_getIndex', - id: '2', - extraState: {isStatement: true}, - fields: {MODE: 'REMOVE', WHERE: 'FROM_START'}, - }, - }, - }, - assertBlockStructure: (block) => {}, - }, - { - title: - 'JSON requiring mutations with XML extra state', - json: { - type: 'statement_block', - id: '1', - next: { - block: { - type: 'lists_getIndex', - id: '2', - extraState: '', - fields: {MODE: 'REMOVE', WHERE: 'FROM_START'}, - }, - }, - }, - expectedJson: { - type: 'statement_block', - id: '1', - next: { - block: { - type: 'lists_getIndex', - id: '2', - extraState: {isStatement: true}, - fields: {MODE: 'REMOVE', WHERE: 'FROM_START'}, - }, - }, - }, - assertBlockStructure: (block) => {}, - }, - ]; - runSerializationTestSuite(testCases); - }); - - /** - * Test cases for serialization where JSON hooks should have null - * implementation to avoid serializing xml mutations in json. - * @param {!Object} serializedJson basic serialized json - * @param {!string} xmlMutation xml mutation that should be ignored/not reserialized in round trip - * @return {Array} test cases - */ - function makeTestCasesForBlockNotNeedingExtraState_(serializedJson, xmlMutation) { - return [ - { - title: 'JSON not requiring mutations', - json: serializedJson, - assertBlockStructure: (block) => { - chai.assert.equal(block.type, serializedJson.type); - }, - }, - { - title: - 'JSON with XML extra state', - json: { - ...serializedJson, - "extraState": xmlMutation, - }, - expectedJson: serializedJson, - assertBlockStructure: (block) => {}, - }, - ]; - } - - suite('ListsSetIndex', function() { - /** - * Test cases for serialization tests. - * @type {Array} - */ - const testCases = makeTestCasesForBlockNotNeedingExtraState_( - { - "type": "lists_setIndex", - "id": "1", - "fields": { - "MODE": "SET", - "WHERE": "FROM_START", - }, - }, - "" - ); - runSerializationTestSuite(testCases); - }); - - suite('ListsGetSubList', function() { - /** - * Test cases for serialization tests. - * @type {Array} - */ - const testCases = makeTestCasesForBlockNotNeedingExtraState_( - { - "type": "lists_getSublist", - "id": "1", - "fields": { - "WHERE1": "FROM_START", - "WHERE2": "FROM_START", - }, - }, - "" - ); - runSerializationTestSuite(testCases); - }); - - suite('ListsSplit', function() { - /** - * Test cases for serialization tests. - * @type {Array} - */ - const testCases = makeTestCasesForBlockNotNeedingExtraState_( - { - "type": "lists_split", - "id": "1", - "fields": { - "MODE": "SPLIT", - }, - }, - "" - ); - runSerializationTestSuite(testCases); - }); -}); diff --git a/tests/mocha/logic_ternary_test.js b/tests/mocha/logic_ternary_test.js deleted file mode 100644 index 5bb9f0f09c6..00000000000 --- a/tests/mocha/logic_ternary_test.js +++ /dev/null @@ -1,253 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -goog.declareModuleId('Blockly.test.logicTernary'); - -import * as eventUtils from '../../build/src/core/events/utils.js'; -import {runSerializationTestSuite} from './test_helpers/serialization.js'; -import {sharedTestSetup, sharedTestTeardown} from './test_helpers/setup_teardown.js'; - - -suite('Logic ternary', function() { - setup(function() { - sharedTestSetup.call(this); - this.workspace = new Blockly.Workspace(); - }); - - teardown(function() { - sharedTestTeardown.call(this); - }); - - /** - * Asserts that the logic ternary block has the expected inputs and fields. - * @param {!Blockly.Block} block The block to check. - * @param {boolean=} inputsInline Whether the inputs are expected to be - * inline. - */ - function assertBlockStructure(block, inputsInline = false) { - chai.assert.equal(block.type, 'logic_ternary'); - const inputs = block.inputList; - chai.assert.exists(inputs, 'Has inputList'); - chai.assert.lengthOf(inputs, 3); - const ifInput = block.getInput('IF'); - chai.assert.exists(ifInput, 'Has "IF" input'); - chai.assert.equal(ifInput.connection.check_.length, 1); - chai.assert.equal(ifInput.connection.check_[0], 'Boolean'); - chai.assert.exists(block.onchangeWrapper_, 'Has onchange handler'); - if (inputsInline) { - chai.assert.isTrue(block.inputsInline); - } else { - // inputsInline can be undefined - chai.assert.isNotTrue(block.inputsInline); - } - } - - test('Structure', function() { - const block = this.workspace.newBlock('logic_ternary'); - assertBlockStructure(block); - }); - - /** - * Test cases for serialization tests. - * @type {Array} - */ - const testCases = [ - {title: 'Empty XML', xml: '', - expectedXml: - '', - assertBlockStructure: - (block) => { - assertBlockStructure(block); - }, - }, - {title: 'Inputs inline', - xml: - '', - assertBlockStructure: - (block) => { - assertBlockStructure(block, true); - }, - }, - ]; - runSerializationTestSuite(testCases); - - suite('Connections', function() { - function connectParentAndCheckConnections( - block, parent, parentInputName, opt_thenInput, opt_elseInput) { - parent.getInput(parentInputName).connection.connect(block.outputConnection); - eventUtils.TEST_ONLY.fireNow(); // Force synchronous onchange() call. - chai.assert.equal(block.getParent(), parent, - 'Successful connection to parent'); - if (opt_thenInput) { - chai.assert.equal(opt_thenInput.getParent(), block, - 'Input THEN still connected after connecting parent'); - } - if (opt_elseInput) { - chai.assert.equal(opt_elseInput.getParent(), block, - 'Input ELSE still connected after connecting parent'); - } - } - function connectThenInputAndCheckConnections( - block, thenInput, opt_elseInput, opt_parent) { - block.getInput('THEN').connection.connect(thenInput.outputConnection); - eventUtils.TEST_ONLY.fireNow(); // Force synchronous onchange() call. - chai.assert.equal(thenInput.getParent(), block, 'THEN is connected'); - if (opt_parent) { - chai.assert.equal(block.getParent(), opt_parent, - 'Still connected to parent after connecting THEN'); - } - if (opt_elseInput) { - chai.assert.equal(opt_elseInput.getParent(), block, - 'Input ELSE still connected after connecting THEN'); - } - } - function connectElseInputAndCheckConnections( - block, elseInput, opt_thenInput, opt_parent) { - block.getInput('ELSE').connection.connect(elseInput.outputConnection); - eventUtils.TEST_ONLY.fireNow(); // Force synchronous onchange() call. - chai.assert.equal(elseInput.getParent(), block, 'ELSE is connected'); - if (opt_parent) { - chai.assert.equal(block.getParent(), opt_parent, - 'Still connected to parent after connecting ELSE'); - } - if (opt_thenInput) { - chai.assert.equal(opt_thenInput.getParent(), block, - 'Input THEN still connected after connecting ELSE'); - } - } - function connectInputsAndCheckConnections( - block, thenInput, elseInput, opt_parent) { - connectThenInputAndCheckConnections(block, thenInput, null, opt_parent); - connectElseInputAndCheckConnections(block, elseInput, thenInput, opt_parent); - } - setup(function() { - this.block = this.workspace.newBlock('logic_ternary'); - }); - suite('No parent', function() { - test('Attach inputs same type', function() { - const string1 = this.workspace.newBlock('text'); - const string2 = this.workspace.newBlock('text_charAt'); - - connectInputsAndCheckConnections(this.block, string1, string2); - }); - test('Attach inputs different types', function() { - const string = this.workspace.newBlock('text'); - const number = this.workspace.newBlock('math_number'); - - connectInputsAndCheckConnections(this.block, string, number); - }); - }); - suite('With parent already attached', function() { - test('Attach inputs same type with matching parent', function() { - const parent = this.workspace.newBlock('text_trim'); - - connectParentAndCheckConnections(this.block, parent, 'TEXT'); - - const string1 = this.workspace.newBlock('text'); - const string2 = this.workspace.newBlock('text_charAt'); - - connectInputsAndCheckConnections(this.block, string1, string2, parent); - }); - test('Attach inputs different types with unchecked parent', function() { - const parent = this.workspace.newBlock('text_print'); - - connectParentAndCheckConnections(this.block, parent, 'TEXT'); - - const string = this.workspace.newBlock('text'); - const number = this.workspace.newBlock('math_number'); - - connectInputsAndCheckConnections(this.block, string, number, parent); - }); - test('Attach inputs different types with permissive parent', function() { - const parent = this.workspace.newBlock('text_length'); // Allows String or Array - - connectParentAndCheckConnections(this.block, parent, 'VALUE'); - - const string = this.workspace.newBlock('text'); - const array = this.workspace.newBlock('lists_create_empty'); - - connectInputsAndCheckConnections(this.block, string, array, parent); - }); - test('Attach mismatch type to then causes break with parent', function() { - const parent = this.workspace.newBlock('text_length'); // Allows String or Array - - connectParentAndCheckConnections(this.block, parent, 'VALUE'); - - const string = this.workspace.newBlock('text'); - const number = this.workspace.newBlock('math_number'); - - connectElseInputAndCheckConnections(this.block, string, null, parent); - - // Adding mismatching number. - connectThenInputAndCheckConnections(this.block, number, string); - chai.assert.equal(this.block.getRootBlock(), this.block, - 'Disconnected from parent'); - }); - test('Attach mismatch type to else causes break with parent', function() { - const parent = this.workspace.newBlock('text_length'); // Allows String or Array - - connectParentAndCheckConnections(this.block, parent, 'VALUE'); - - const string = this.workspace.newBlock('text'); - const number = this.workspace.newBlock('math_number'); - - connectThenInputAndCheckConnections(this.block, string, null, parent); - - // Adding mismatching number. - connectElseInputAndCheckConnections(this.block, number, string); - chai.assert.equal(this.block.getRootBlock(), this.block, - 'Disconnected from parent'); - }); - }); - suite('Attaching parent after inputs', function() { - test('Unchecked parent with inputs different types', function() { - const string = this.workspace.newBlock('text'); - const number = this.workspace.newBlock('math_number'); - - connectInputsAndCheckConnections(this.block, string, number); - - const parent = this.workspace.newBlock('text_print'); - connectParentAndCheckConnections( - this.block, parent, 'TEXT', string, number); - }); - test('Permissive parent with inputs different types', function() { - const string = this.workspace.newBlock('text'); - const array = this.workspace.newBlock('lists_create_empty'); - - connectInputsAndCheckConnections(this.block, string, array); - - const parent = this.workspace.newBlock('text_print'); - connectParentAndCheckConnections( - this.block, parent, 'TEXT', string, array); - }); - test('Mismatch with then causes break with then', function() { - const number = this.workspace.newBlock('math_number'); - const string = this.workspace.newBlock('text'); - - connectInputsAndCheckConnections(this.block, number, string); - - const parent = this.workspace.newBlock('text_trim'); - connectParentAndCheckConnections( - this.block, parent, 'TEXT', null, string); - chai.assert.equal(number.getRootBlock(), number, - 'Input THEN disconnected'); - }); - test('Mismatch with else causes break with else', function() { - const string = this.workspace.newBlock('text'); - const number = this.workspace.newBlock('math_number'); - - connectInputsAndCheckConnections(this.block, string, number); - - const parent = this.workspace.newBlock('text_trim'); - connectParentAndCheckConnections(this.block, parent, 'TEXT', string); - chai.assert.equal(number.getRootBlock(), number, - 'Input ELSE disconnected'); - }); - }); - }); -}); diff --git a/tests/mocha/procedures_test.js b/tests/mocha/procedures_test.js deleted file mode 100644 index 33efe6cf753..00000000000 --- a/tests/mocha/procedures_test.js +++ /dev/null @@ -1,1251 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -goog.declareModuleId('Blockly.test.procedures'); - -import * as Blockly from '../../build/src/core/blockly.js'; -import {assertCallBlockStructure, assertDefBlockStructure, createProcDefBlock, createProcCallBlock} from './test_helpers/procedures.js'; -import {runSerializationTestSuite} from './test_helpers/serialization.js'; -import {createGenUidStubWithReturns, sharedTestSetup, sharedTestTeardown, workspaceTeardown} from './test_helpers/setup_teardown.js'; - -suite('Procedures', function() { - setup(function() { - sharedTestSetup.call(this); - this.workspace = new Blockly.Workspace(); - this.workspace.createVariable('preCreatedVar', '', 'preCreatedVarId'); - this.workspace.createVariable( - 'preCreatedTypedVar', 'type', 'preCreatedTypedVarId'); - }); - teardown(function() { - sharedTestTeardown.call(this); - }); - - suite('allProcedures', function() { - test('Only Procedures', function() { - const noReturnBlock = new Blockly.Block(this.workspace, 'procedures_defnoreturn'); - noReturnBlock.setFieldValue('no return', 'NAME'); - const returnBlock = new Blockly.Block(this.workspace, 'procedures_defreturn'); - returnBlock.setFieldValue('return', 'NAME'); - - const allProcedures = Blockly.Procedures.allProcedures(this.workspace); - chai.assert.lengthOf(allProcedures, 2); - - chai.assert.lengthOf(allProcedures[0], 1); - chai.assert.equal(allProcedures[0][0][0], 'no return'); - - chai.assert.lengthOf(allProcedures[1], 1); - chai.assert.equal(allProcedures[1][0][0], 'return'); - }); - test('Multiple Blocks', function() { - const noReturnBlock = new Blockly.Block(this.workspace, 'procedures_defnoreturn'); - noReturnBlock.setFieldValue('no return', 'NAME'); - const returnBlock = new Blockly.Block(this.workspace, 'procedures_defreturn'); - returnBlock.setFieldValue('return', 'NAME'); - const returnBlock2 = new Blockly.Block(this.workspace, 'procedures_defreturn'); - returnBlock2.setFieldValue('return2', 'NAME'); - const _ = new Blockly.Block(this.workspace, 'controls_if'); - - const allProcedures = Blockly.Procedures.allProcedures(this.workspace); - chai.assert.lengthOf(allProcedures, 2); - - chai.assert.lengthOf(allProcedures[0], 1); - chai.assert.equal(allProcedures[0][0][0], 'no return'); - - chai.assert.lengthOf(allProcedures[1], 2); - chai.assert.equal(allProcedures[1][0][0], 'return'); - chai.assert.equal(allProcedures[1][1][0], 'return2'); - }); - test('No Procedures', function() { - const _ = new Blockly.Block(this.workspace, 'controls_if'); - const allProcedures = Blockly.Procedures.allProcedures(this.workspace); - chai.assert.lengthOf(allProcedures, 2); - chai.assert.lengthOf(allProcedures[0], 0, 'No procedures_defnoreturn blocks expected'); - chai.assert.lengthOf(allProcedures[1], 0, 'No procedures_defreturn blocks expected'); - }); - }); - - suite('isNameUsed', function() { - test('No Blocks', function() { - chai.assert.isFalse( - Blockly.Procedures.isNameUsed('name1', this.workspace) - ); - }); - }); - - suite('Enable/Disable', function() { - setup(function() { - const toolbox = document.getElementById('toolbox-categories'); - this.workspaceSvg = Blockly.inject('blocklyDiv', {toolbox: toolbox}); - }); - teardown(function() { - workspaceTeardown.call(this, this.workspaceSvg); - sinon.restore(); - }); - suite('Inherited disabled', function() { - setup(function() { - const dom = Blockly.Xml.textToDom( - '' + - '' + - 'bar' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - 'foo' + - '' + - '' + - 'baz' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - ''); - Blockly.Events.disable(); - Blockly.Xml.appendDomToWorkspace(dom, this.workspaceSvg); - Blockly.Events.enable(); - - this.barDef = this.workspaceSvg.getBlockById('bar-def'); - this.fooDef = this.workspaceSvg.getBlockById('foo-def'); - this.bazDef = this.workspaceSvg.getBlockById('baz-def'); - - this.barCalls = [ - this.workspaceSvg.getBlockById('bar-c1'), - this.workspaceSvg.getBlockById('bar-c2')]; - this.fooCalls = [ - this.workspaceSvg.getBlockById('foo-c1'), - this.workspaceSvg.getBlockById('foo-c2')]; - this.bazCall = this.workspaceSvg.getBlockById('baz-c1'); - }); - test('Nested caller', function() { - this.barDef.setEnabled(false); - - for (let i = 0; i < 2; i++) { - chai.assert.isFalse(this.barCalls[i].isEnabled(), - 'Callers are disabled when their definition is disabled ' + - '(bar call ' + i + ')'); - } - chai.assert.isTrue(this.fooCalls[0].isEnabled(), - 'Callers in definitions are disabled by inheritance'); - chai.assert.isTrue(this.fooCalls[0].getInheritedDisabled(), - 'Callers in definitions are disabled by inheritance'); - - this.fooDef.setEnabled(false); - - for (let i = 0; i < 2; i++) { - chai.assert.isFalse(this.fooCalls[i].isEnabled(), - 'Callers are disabled when their definition is disabled ' + - '(foo call ' + i + ')'); - } - - this.barDef.setEnabled(true); - - for (let i = 0; i < 2; i++) { - chai.assert.isTrue(this.barCalls[i].isEnabled(), - 'Callers are reenabled with their definition ' + - '(bar call ' + i + ')'); - } - chai.assert.isFalse(this.fooCalls[0].isEnabled(), - 'Nested disabled callers remain disabled'); - chai.assert.isFalse(this.fooCalls[0].getInheritedDisabled(), - 'Nested disabled callers remain disabled, not by inheritance'); - }); - test('Caller in return', function() { - this.bazDef.setEnabled(false); - - chai.assert.isFalse(this.bazCall.isEnabled(), - 'Caller is disabled with its definition'); - - chai.assert.isTrue(this.barCalls[1].isEnabled(), - 'Caller in the return is disabled by inheritance'); - chai.assert.isTrue(this.barCalls[1].getInheritedDisabled(), - 'Caller in the return is disabled by inheritance'); - - this.barDef.setEnabled(false); - - for (let i = 0; i < 2; i++) { - chai.assert.isFalse(this.barCalls[i].isEnabled(), - 'Callers are disabled when their definition is disabled ' + - '(bar call ' + i + ')'); - } - - this.bazDef.setEnabled(true); - - chai.assert.isFalse(this.barCalls[1].isEnabled(), - 'Caller in return remains disabled'); - chai.assert.isFalse(this.barCalls[1].getInheritedDisabled(), - 'Caller in return remains disabled, not by inheritance'); - }); - }); - }); - - suite('Multiple block serialization', function() { - function assertDefAndCallBlocks(workspace, noReturnNames, returnNames, hasCallers) { - const allProcedures = Blockly.Procedures.allProcedures(workspace); - const defNoReturnBlocks = allProcedures[0]; - chai.assert.lengthOf(defNoReturnBlocks, noReturnNames.length); - for (let i = 0; i < noReturnNames.length; i++) { - const expectedName = noReturnNames[i]; - chai.assert.equal(defNoReturnBlocks[i][0], expectedName); - if (hasCallers) { - const callers = - Blockly.Procedures.getCallers(expectedName, workspace); - chai.assert.lengthOf(callers, 1); - } - } - const defReturnBlocks = allProcedures[1]; - chai.assert.lengthOf(defReturnBlocks, returnNames.length); - for (let i = 0; i < returnNames.length; i++) { - const expectedName = returnNames[i]; - chai.assert.equal(defReturnBlocks[i][0], expectedName); - if (hasCallers) { - const callers = - Blockly.Procedures.getCallers(expectedName, workspace); - chai.assert.lengthOf(callers, 1); - } - } - - // Expecting def and caller blocks are the only blocks on workspace - let expectedCount = noReturnNames.length + returnNames.length; - if (hasCallers) { - expectedCount *= 2; - } - const blocks = workspace.getAllBlocks(false); - chai.assert.lengthOf(blocks, expectedCount); - } - suite('no name renamed to unnamed', function() { - test('defnoreturn and defreturn', function() { - const xml = Blockly.Xml.textToDom(` - - - - `); - Blockly.Xml.domToWorkspace(xml, this.workspace); - assertDefAndCallBlocks( - this.workspace, ['unnamed'], ['unnamed2'], false); - }); - test('defreturn and defnoreturn', function() { - const xml = Blockly.Xml.textToDom(` - - - - `); - Blockly.Xml.domToWorkspace(xml, this.workspace); - assertDefAndCallBlocks( - this.workspace, ['unnamed2'], ['unnamed'], false); - }); - test('callnoreturn (no def in xml)', function() { - const xml = Blockly.Xml.textToDom(` - - - `); - Blockly.Xml.domToWorkspace(xml, this.workspace); - assertDefAndCallBlocks( - this.workspace, ['unnamed'], [], true); - }); - test('callreturn (no def in xml)', function() { - const xml = Blockly.Xml.textToDom(` - - - `); - Blockly.Xml.domToWorkspace(xml, this.workspace); - assertDefAndCallBlocks( - this.workspace, [], ['unnamed'], true); - }); - test('callnoreturn and callreturn (no def in xml)', function() { - const xml = Blockly.Xml.textToDom(` - - - - `); - Blockly.Xml.domToWorkspace(xml, this.workspace); - assertDefAndCallBlocks( - this.workspace, ['unnamed'], ['unnamed2'], true); - }); - test('callreturn and callnoreturn (no def in xml)', function() { - const xml = Blockly.Xml.textToDom(` - - - - `); - Blockly.Xml.domToWorkspace(xml, this.workspace); - assertDefAndCallBlocks( - this.workspace, ['unnamed2'], ['unnamed'], true); - }); - }); - suite('caller param mismatch', function() { - setup(function() { - this.TEST_VAR_ID = 'test-id'; - this.genUidStub = createGenUidStubWithReturns(this.TEST_VAR_ID); - }); - - test('callreturn with missing args', function() { - const defBlock = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(` - - do something - - - - - `), this.workspace); - const callBlock = Blockly.Xml.domToBlock(Blockly.Xml.textToDom( - '' + - ' ' + - '' - ), this.workspace); - assertDefBlockStructure(defBlock, true, ['x'], ['arg']); - assertCallBlockStructure(callBlock, [], [], 'do something2'); - }); - test('callreturn with bad args', function() { - const defBlock = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(` - - do something - - - - - `), this.workspace); - const callBlock = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(` - - - - - - `), this.workspace); - assertDefBlockStructure(defBlock, true, ['x'], ['arg']); - assertCallBlockStructure( - callBlock, ['y'], [this.TEST_VAR_ID], 'do something2'); - }); - test('callnoreturn with missing args', function() { - const defBlock = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(` - - do something - - - - - `), this.workspace); - const callBlock = Blockly.Xml.domToBlock(Blockly.Xml.textToDom( - '' + - ' ' + - '' - ), this.workspace); - assertDefBlockStructure(defBlock, false, ['x'], ['arg']); - assertCallBlockStructure(callBlock, [], [], 'do something2'); - }); - test('callnoreturn with bad args', function() { - const defBlock = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(` - - do something - - - - - `), this.workspace); - const callBlock = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(` - - - - - - `), this.workspace); - assertDefBlockStructure(defBlock, false, ['x'], ['arg']); - assertCallBlockStructure( - callBlock, ['y'], [this.TEST_VAR_ID], 'do something2'); - }); - }); - }); - - suite('getDefinition - Modified cases', function() { - setup(function() { - Blockly.Blocks['new_proc'] = { - init: function() { }, - getProcedureDef: function() { - return [this.name, [], false]; - }, - name: 'test', - }; - - Blockly.Blocks['nested_proc'] = { - init: function() { - this.setPreviousStatement(true, null); - this.setNextStatement(true, null); - }, - getProcedureDef: function() { - return [this.name, [], false]; - }, - name: 'test', - }; - }); - - teardown(function() { - delete Blockly.Blocks['new_proc']; - delete Blockly.Blocks['nested_proc']; - }); - - test('Custom procedure block', function() { - // Do not require procedures to be the built-in procedures. - const defBlock = new Blockly.Block(this.workspace, 'new_proc'); - const def = Blockly.Procedures.getDefinition('test', this.workspace); - chai.assert.equal(def, defBlock); - }); - - test('Stacked procedures', function() { - const blockA = new Blockly.Block(this.workspace, 'nested_proc'); - const blockB = new Blockly.Block(this.workspace, 'nested_proc'); - blockA.name = 'a'; - blockB.name = 'b'; - blockA.nextConnection.connect(blockB.previousConnection); - const def = Blockly.Procedures.getDefinition('b', this.workspace); - chai.assert.equal(def, blockB); - }); - }); - - const testSuites = [ - {title: 'procedures_defreturn', hasReturn: true, - defType: 'procedures_defreturn', callType: 'procedures_callreturn'}, - {title: 'procedures_defnoreturn', hasReturn: false, - defType: 'procedures_defnoreturn', callType: 'procedures_callnoreturn'}, - ]; - - testSuites.forEach((testSuite) => { - suite(testSuite.title, function() { - suite('Structure', function() { - setup(function() { - this.defBlock = new Blockly.Block(this.workspace, testSuite.defType); - this.defBlock.setFieldValue('proc name', 'NAME'); - }); - test('Definition block', function() { - assertDefBlockStructure(this.defBlock, testSuite.hasReturn); - }); - - test('Call block', function() { - this.callBlock = new Blockly.Block( - this.workspace, testSuite.callType); - this.callBlock.setFieldValue('proc name', 'NAME'); - assertCallBlockStructure(this.callBlock); - }); - }); - suite('isNameUsed', function() { - setup(function() { - this.defBlock = new Blockly.Block(this.workspace, testSuite.defType); - this.defBlock.setFieldValue('proc name', 'NAME'); - this.callBlock = new Blockly.Block( - this.workspace, testSuite.callType); - this.callBlock.setFieldValue('proc name', 'NAME'); - }); - test('True', function() { - chai.assert.isTrue( - Blockly.Procedures.isNameUsed('proc name', this.workspace)); - }); - test('False', function() { - chai.assert.isFalse( - Blockly.Procedures.isNameUsed('unused proc name', this.workspace)); - }); - }); - suite('rename', function() { - setup(function() { - this.defBlock = new Blockly.Block(this.workspace, testSuite.defType); - this.defBlock.setFieldValue('proc name', 'NAME'); - this.callBlock = new Blockly.Block( - this.workspace, testSuite.callType); - this.callBlock.setFieldValue('proc name', 'NAME'); - sinon.stub(this.defBlock.getField('NAME'), 'resizeEditor_'); - }); - test('Simple, Programmatic', function() { - this.defBlock.setFieldValue( - this.defBlock.getFieldValue('NAME') + '2', - 'NAME' - ); - chai.assert.equal( - this.defBlock.getFieldValue('NAME'), 'proc name2'); - chai.assert.equal( - this.callBlock.getFieldValue('NAME'), 'proc name2'); - }); - test('Simple, Input', function() { - const defInput = this.defBlock.getField('NAME'); - defInput.htmlInput_ = document.createElement('input'); - defInput.htmlInput_.setAttribute('data-old-value', 'proc name'); - defInput.htmlInput_.setAttribute('data-untyped-default-value', 'proc name'); - - defInput.htmlInput_.value = defInput.htmlInput_.getAttribute('data-old-value') + '2'; - defInput.onHtmlInputChange_(null); - chai.assert.equal( - this.defBlock.getFieldValue('NAME'), 'proc name2'); - chai.assert.equal( - this.callBlock.getFieldValue('NAME'), 'proc name2'); - }); - test('lower -> CAPS', function() { - const defInput = this.defBlock.getField('NAME'); - defInput.htmlInput_ = document.createElement('input'); - defInput.htmlInput_.setAttribute('data-old-value', 'proc name'); - defInput.htmlInput_.setAttribute('data-untyped-default-value', 'proc name'); - - defInput.htmlInput_.value = 'PROC NAME'; - defInput.onHtmlInputChange_(null); - chai.assert.equal( - this.defBlock.getFieldValue('NAME'), 'PROC NAME'); - chai.assert.equal( - this.callBlock.getFieldValue('NAME'), 'PROC NAME'); - }); - test('CAPS -> lower', function() { - this.defBlock.setFieldValue('PROC NAME', 'NAME'); - this.callBlock.setFieldValue('PROC NAME', 'NAME'); - const defInput = this.defBlock.getField('NAME'); - defInput.htmlInput_ = document.createElement('input'); - defInput.htmlInput_.setAttribute('data-old-value', 'PROC NAME'); - defInput.htmlInput_.setAttribute('data-untyped-default-value', 'PROC NAME'); - - defInput.htmlInput_.value = 'proc name'; - defInput.onHtmlInputChange_(null); - chai.assert.equal( - this.defBlock.getFieldValue('NAME'), 'proc name'); - chai.assert.equal( - this.callBlock.getFieldValue('NAME'), 'proc name'); - }); - test('Whitespace', function() { - const defInput = this.defBlock.getField('NAME'); - defInput.htmlInput_ = document.createElement('input'); - defInput.htmlInput_.setAttribute('data-old-value', 'proc name'); - defInput.htmlInput_.setAttribute('data-untyped-default-value', 'proc name'); - - defInput.htmlInput_.value = defInput.htmlInput_.getAttribute('data-old-value') + ' '; - defInput.onHtmlInputChange_(null); - chai.assert.equal( - this.defBlock.getFieldValue('NAME'), 'proc name'); - chai.assert.equal( - this.callBlock.getFieldValue('NAME'), 'proc name'); - }); - test('Whitespace then Text', function() { - const defInput = this.defBlock.getField('NAME'); - defInput.htmlInput_ = document.createElement('input'); - defInput.htmlInput_.setAttribute('data-old-value', 'proc name'); - defInput.htmlInput_.setAttribute('data-untyped-default-value', 'proc name'); - - defInput.htmlInput_.value = defInput.htmlInput_.getAttribute('data-old-value') + ' '; - defInput.onHtmlInputChange_(null); - defInput.htmlInput_.value = defInput.htmlInput_.getAttribute('data-old-value') + '2'; - defInput.onHtmlInputChange_(null); - chai.assert.equal( - this.defBlock.getFieldValue('NAME'), 'proc name 2'); - chai.assert.equal( - this.callBlock.getFieldValue('NAME'), 'proc name 2'); - }); - test('Set Empty', function() { - const defInput = this.defBlock.getField('NAME'); - defInput.htmlInput_ = document.createElement('input'); - defInput.htmlInput_.setAttribute('data-old-value', 'proc name'); - defInput.htmlInput_.setAttribute('data-untyped-default-value', 'proc name'); - - defInput.htmlInput_.value = ''; - defInput.onHtmlInputChange_(null); - chai.assert.equal( - this.defBlock.getFieldValue('NAME'), - Blockly.Msg['UNNAMED_KEY']); - chai.assert.equal( - this.callBlock.getFieldValue('NAME'), - Blockly.Msg['UNNAMED_KEY']); - }); - test('Set Empty, and Create New', function() { - const defInput = this.defBlock.getField('NAME'); - defInput.htmlInput_ = document.createElement('input'); - defInput.htmlInput_.setAttribute('data-old-value', 'proc name'); - defInput.htmlInput_.setAttribute('data-untyped-default-value', 'proc name'); - - defInput.htmlInput_.value = ''; - defInput.onHtmlInputChange_(null); - const newDefBlock = new Blockly.Block(this.workspace, testSuite.defType); - newDefBlock.setFieldValue('new name', 'NAME'); - chai.assert.equal( - this.defBlock.getFieldValue('NAME'), - Blockly.Msg['UNNAMED_KEY']); - chai.assert.equal( - this.callBlock.getFieldValue('NAME'), - Blockly.Msg['UNNAMED_KEY']); - }); - }); - suite('getCallers', function() { - setup(function() { - this.defBlock = new Blockly.Block(this.workspace, testSuite.defType); - this.defBlock.setFieldValue('proc name', 'NAME'); - this.callBlock = new Blockly.Block( - this.workspace, testSuite.callType); - this.callBlock.setFieldValue('proc name', 'NAME'); - }); - test('Simple', function() { - const callers = - Blockly.Procedures.getCallers('proc name', this.workspace); - chai.assert.equal(callers.length, 1); - chai.assert.equal(callers[0], this.callBlock); - }); - test('Multiple Callers', function() { - const caller2 = new Blockly.Block(this.workspace, testSuite.callType); - caller2.setFieldValue('proc name', 'NAME'); - const caller3 = new Blockly.Block(this.workspace, testSuite.callType); - caller3.setFieldValue('proc name', 'NAME'); - - const callers = - Blockly.Procedures.getCallers('proc name', this.workspace); - chai.assert.equal(callers.length, 3); - chai.assert.equal(callers[0], this.callBlock); - chai.assert.equal(callers[1], caller2); - chai.assert.equal(callers[2], caller3); - }); - test('Multiple Procedures', function() { - const def2 = new Blockly.Block(this.workspace, testSuite.defType); - def2.setFieldValue('proc name2', 'NAME'); - const caller2 = new Blockly.Block(this.workspace, testSuite.callType); - caller2.setFieldValue('proc name2', 'NAME'); - - const callers = - Blockly.Procedures.getCallers('proc name', this.workspace); - chai.assert.equal(callers.length, 1); - chai.assert.equal(callers[0], this.callBlock); - }); - // This can occur if you: - // 1) Create an uppercase definition and call block. - // 2) Delete both blocks. - // 3) Create a lowercase definition and call block. - // 4) Retrieve the uppercase call block from the trashcan. - // (And vise versa for creating lowercase blocks first) - // When converted to code all function names will be lowercase, so a - // caller should still be returned for a differently-cased procedure. - test('Call Different Case', function() { - this.callBlock.setFieldValue('PROC NAME', 'NAME'); - const callers = - Blockly.Procedures.getCallers('proc name', this.workspace); - chai.assert.equal(callers.length, 1); - chai.assert.equal(callers[0], this.callBlock); - }); - test('Multiple Workspaces', function() { - const workspace = new Blockly.Workspace(); - try { - const def2 = new Blockly.Block(workspace, testSuite.defType); - def2.setFieldValue('proc name', 'NAME'); - const caller2 = new Blockly.Block(workspace, testSuite.callType); - caller2.setFieldValue('proc name', 'NAME'); - - let callers = - Blockly.Procedures.getCallers('proc name', this.workspace); - chai.assert.equal(callers.length, 1); - chai.assert.equal(callers[0], this.callBlock); - - callers = Blockly.Procedures.getCallers('proc name', workspace); - chai.assert.equal(callers.length, 1); - chai.assert.equal(callers[0], caller2); - } finally { - workspaceTeardown.call(this, workspace); - } - }); - }); - suite('getDefinition', function() { - setup(function() { - this.defBlock = new Blockly.Block(this.workspace, testSuite.defType); - this.defBlock.setFieldValue('proc name', 'NAME'); - this.callBlock = new Blockly.Block( - this.workspace, testSuite.callType); - this.callBlock.setFieldValue('proc name', 'NAME'); - }); - test('Simple', function() { - const def = - Blockly.Procedures.getDefinition('proc name', this.workspace); - chai.assert.equal(def, this.defBlock); - }); - test('Multiple Procedures', function() { - const def2 = new Blockly.Block(this.workspace, testSuite.defType); - def2.setFieldValue('proc name2', 'NAME'); - const caller2 = new Blockly.Block(this.workspace, testSuite.callType); - caller2.setFieldValue('proc name2', 'NAME'); - - const def = - Blockly.Procedures.getDefinition('proc name', this.workspace); - chai.assert.equal(def, this.defBlock); - }); - test('Multiple Workspaces', function() { - const workspace = new Blockly.Workspace(); - try { - const def2 = new Blockly.Block(workspace, testSuite.defType); - def2.setFieldValue('proc name', 'NAME'); - const caller2 = new Blockly.Block(workspace, testSuite.callType); - caller2.setFieldValue('proc name', 'NAME'); - - let def = - Blockly.Procedures.getDefinition('proc name', this.workspace); - chai.assert.equal(def, this.defBlock); - - def = Blockly.Procedures.getDefinition('proc name', workspace); - chai.assert.equal(def, def2); - } finally { - workspaceTeardown.call(this, workspace); - } - }); - }); - - suite('Enable/Disable', function() { - setup(function() { - const toolbox = document.getElementById('toolbox-categories'); - this.workspaceSvg = Blockly.inject('blocklyDiv', {toolbox: toolbox}); - }); - teardown(function() { - workspaceTeardown.call(this, this.workspaceSvg); - }); - const domText = (testSuite.defType === 'procedures_defreturn') ? - ('' + - '' + - 'bar' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '') : - ('' + - '' + - 'bar' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - ''); - setup(function() { - const dom = Blockly.Xml.textToDom(domText); - - Blockly.Xml.appendDomToWorkspace(dom, this.workspaceSvg); - this.barDef = this.workspaceSvg.getBlockById('bar-def'); - this.barCalls = [ - this.workspaceSvg.getBlockById('bar-c1'), - this.workspaceSvg.getBlockById('bar-c2'), - ]; - }); - - test('Set disabled updates callers', function() { - this.workspaceSvg.clearUndo(); - Blockly.Events.setGroup('g1'); - this.barDef.setEnabled(false); - Blockly.Events.setGroup(false); - - for (let i = 0; i < 2; i++) { - chai.assert.isFalse(this.barCalls[i].isEnabled(), - 'Callers are disabled when their definition is disabled (call ' + - i + ')'); - } - const firedEvents = this.workspaceSvg.undoStack_; - chai.assert.equal(firedEvents.length, 3, - 'An event was fired for the definition and each caller'); - for (let i = 0; i < 3; i++) { - chai.assert.equal(firedEvents[i].group, 'g1', - 'Disable events are in the same group (event ' + i + ')'); - } - - this.workspaceSvg.clearUndo(); - Blockly.Events.setGroup('g2'); - this.barDef.setEnabled(true); - Blockly.Events.setGroup(false); - - for (let i = 0; i < 2; i++) { - chai.assert.isTrue(this.barCalls[i].isEnabled(), - 'Callers are enabled when their definition is enabled (call ' + - i + ')'); - } - chai.assert.equal(firedEvents.length, 3, - 'An event was fired for the definition and each caller'); - for (let i = 0; i < 3; i++) { - chai.assert.equal(firedEvents[i].group, 'g2', - 'Enable events are in the same group (event ' + i + ')'); - } - }); - test('Set disabled updates callers while remembering old caller state', function() { - this.barCalls[0].setEnabled(false); - this.workspaceSvg.clearUndo(); - Blockly.Events.setGroup('g1'); - this.barDef.setEnabled(false); - Blockly.Events.setGroup(false); - - for (let i = 0; i < 2; i++) { - chai.assert.isFalse(this.barCalls[i].isEnabled(), - 'Callers are disabled when their definition is disabled (call ' + - i + ')'); - } - const firedEvents = this.workspaceSvg.undoStack_; - chai.assert.equal(firedEvents.length, 2, - 'An event was fired for the definition and the enabled caller'); - for (let i = 0; i < 2; i++) { - chai.assert.equal(firedEvents[i].group, 'g1', - 'Disable events are in the same group (event ' + i + ')'); - } - - this.workspaceSvg.clearUndo(); - Blockly.Events.setGroup('g2'); - this.barDef.setEnabled(true); - Blockly.Events.setGroup(false); - - chai.assert.isFalse(this.barCalls[0].isEnabled(), - 'Caller remains in disabled state when the definition is enabled'); - chai.assert.isTrue(this.barCalls[1].isEnabled(), - 'Caller returns to previous enabled state when the definition is enabled'); - chai.assert.equal(firedEvents.length, 2, - 'An event was fired for the definition and the enabled caller'); - for (let i = 0; i < 2; i++) { - chai.assert.equal(firedEvents[i].group, 'g2', - 'Enable events are in the same group (event ' + i + ')'); - } - }); - }); - suite('Mutation', function() { - setup(function() { - this.defBlock = new Blockly.Block(this.workspace, testSuite.defType); - this.defBlock.setFieldValue('proc name', 'NAME'); - this.callBlock = new Blockly.Block( - this.workspace, testSuite.callType); - this.callBlock.setFieldValue('proc name', 'NAME'); - this.findParentStub = sinon.stub(Blockly.Mutator, 'findParentWs') - .returns(this.workspace); - }); - teardown(function() { - this.findParentStub.restore(); - }); - suite('Composition', function() { - suite('Statements', function() { - function setStatementValue(mainWorkspace, defBlock, value) { - const mutatorWorkspace = new Blockly.Workspace( - new Blockly.Options({ - parentWorkspace: mainWorkspace, - })); - defBlock.decompose(mutatorWorkspace); - const containerBlock = mutatorWorkspace.getTopBlocks()[0]; - const statementField = containerBlock.getField('STATEMENTS'); - statementField.setValue(value); - defBlock.compose(containerBlock); - } - if (testSuite.defType === 'procedures_defreturn') { - test('Has Statements', function() { - setStatementValue(this.workspace, this.defBlock, true); - chai.assert.isTrue(this.defBlock.hasStatements_); - }); - test('Has No Statements', function() { - setStatementValue(this.workspace, this.defBlock, false); - chai.assert.isFalse(this.defBlock.hasStatements_); - }); - test('Saving Statements', function() { - const blockXml = Blockly.Xml.textToDom( - '' + - ' ' + - ' ' + - ' ' + - '' - ); - const defBlock = Blockly.Xml.domToBlock(blockXml, this.workspace); - setStatementValue(this.workspace, defBlock, false); - chai.assert.isNull(defBlock.getInput('STACK')); - setStatementValue(this.workspace, defBlock, true); - chai.assert.isNotNull(defBlock.getInput('STACK')); - const statementBlocks = defBlock.getChildren(); - chai.assert.equal(statementBlocks.length, 1); - const block = statementBlocks[0]; - chai.assert.equal(block.type, 'procedures_ifreturn'); - chai.assert.equal(block.id, 'test'); - }); - } - }); - suite('Untyped Arguments', function() { - function createMutator(argArray) { - this.mutatorWorkspace = new Blockly.Workspace( - new Blockly.Options({ - parentWorkspace: this.workspace, - })); - this.containerBlock = this.defBlock.decompose(this.mutatorWorkspace); - this.connection = this.containerBlock.getInput('STACK').connection; - for (let i = 0; i < argArray.length; i++) { - this.argBlock = new Blockly.Block( - this.mutatorWorkspace, 'procedures_mutatorarg'); - this.argBlock.setFieldValue(argArray[i], 'NAME'); - this.connection.connect(this.argBlock.previousConnection); - this.connection = this.argBlock.nextConnection; - } - this.defBlock.compose(this.containerBlock); - } - function assertArgs(argArray) { - chai.assert.equal(this.defBlock.arguments_.length, argArray.length); - for (let i = 0; i < argArray.length; i++) { - chai.assert.equal(this.defBlock.arguments_[i], argArray[i]); - } - chai.assert.equal(this.callBlock.arguments_.length, argArray.length); - for (let i = 0; i < argArray.length; i++) { - chai.assert.equal(this.callBlock.arguments_[i], argArray[i]); - } - } - test('Simple Add Arg', function() { - const args = ['arg1']; - createMutator.call(this, args); - assertArgs.call(this, args); - }); - test('Multiple Args', function() { - const args = ['arg1', 'arg2', 'arg3']; - createMutator.call(this, args); - assertArgs.call(this, args); - }); - test('Simple Change Arg', function() { - createMutator.call(this, ['arg1']); - this.argBlock.setFieldValue('arg2', 'NAME'); - this.defBlock.compose(this.containerBlock); - assertArgs.call(this, ['arg2']); - }); - test('lower -> CAPS', function() { - createMutator.call(this, ['arg']); - this.argBlock.setFieldValue('ARG', 'NAME'); - this.defBlock.compose(this.containerBlock); - assertArgs.call(this, ['ARG']); - }); - test('CAPS -> lower', function() { - createMutator.call(this, ['ARG']); - this.argBlock.setFieldValue('arg', 'NAME'); - this.defBlock.compose(this.containerBlock); - assertArgs.call(this, ['arg']); - }); - // Test case for #1958 - test('Set Arg Empty', function() { - const args = ['arg1']; - createMutator.call(this, args); - this.argBlock.setFieldValue('', 'NAME'); - this.defBlock.compose(this.containerBlock); - assertArgs.call(this, args); - }); - test('Whitespace', function() { - const args = ['arg1']; - createMutator.call(this, args); - this.argBlock.setFieldValue(' ', 'NAME'); - this.defBlock.compose(this.containerBlock); - assertArgs.call(this, args); - }); - test('Whitespace and Text', function() { - createMutator.call(this, ['arg1']); - this.argBlock.setFieldValue(' text ', 'NAME'); - this.defBlock.compose(this.containerBlock); - assertArgs.call(this, ['text']); - }); - test('<>', function() { - const args = ['<>']; - createMutator.call(this, args); - assertArgs.call(this, args); - }); - }); - }); - suite('Decomposition', function() { - suite('Statements', function() { - if (testSuite.defType === 'procedures_defreturn') { - test('Has Statement Input', function() { - const mutatorWorkspace = new Blockly.Workspace( - new Blockly.Options({ - parentWorkspace: this.workspace, - })); - this.defBlock.decompose(mutatorWorkspace); - const statementInput = mutatorWorkspace.getTopBlocks()[0] - .getInput('STATEMENT_INPUT'); - chai.assert.isNotNull(statementInput); - }); - test('Has Statements', function() { - this.defBlock.hasStatements_ = true; - const mutatorWorkspace = new Blockly.Workspace( - new Blockly.Options({ - parentWorkspace: this.workspace, - })); - this.defBlock.decompose(mutatorWorkspace); - const statementValue = mutatorWorkspace.getTopBlocks()[0] - .getField('STATEMENTS').getValueBoolean(); - chai.assert.isTrue(statementValue); - }); - test('No Has Statements', function() { - this.defBlock.hasStatements_ = false; - const mutatorWorkspace = new Blockly.Workspace( - new Blockly.Options({ - parentWorkspace: this.workspace, - })); - this.defBlock.decompose(mutatorWorkspace); - const statementValue = mutatorWorkspace.getTopBlocks()[0] - .getField('STATEMENTS').getValueBoolean(); - chai.assert.isFalse(statementValue); - }); - } else { - test('Has no Statement Input', function() { - const mutatorWorkspace = new Blockly.Workspace( - new Blockly.Options({ - parentWorkspace: this.workspace, - })); - this.defBlock.decompose(mutatorWorkspace); - const statementInput = mutatorWorkspace.getTopBlocks()[0] - .getInput('STATEMENT_INPUT'); - chai.assert.isNull(statementInput); - }); - } - }); - suite('Untyped Arguments', function() { - function assertArguments(argumentsArray) { - this.defBlock.arguments_ = argumentsArray; - const mutatorWorkspace = new Blockly.Workspace( - new Blockly.Options({ - parentWorkspace: this.workspace, - })); - this.defBlock.decompose(mutatorWorkspace); - const argBlocks = mutatorWorkspace.getBlocksByType('procedures_mutatorarg'); - chai.assert.equal(argBlocks.length, argumentsArray.length); - - for (let i = 0; i < argumentsArray.length; i++) { - const argString = argumentsArray[i]; - const argBlockValue = argBlocks[i].getFieldValue('NAME'); - chai.assert.equal(argBlockValue, argString); - } - } - test('Simple Single Arg', function() { - assertArguments.call(this, ['arg']); - }); - test('Multiple Args', function() { - assertArguments.call(this, ['arg1', 'arg2']); - }); - test('<>', function() { - assertArguments.call(this, ['<>']); - }); - }); - }); - }); - /** - * Test cases for serialization tests. - * @type {Array} - */ - const testCases = [ - { - title: 'Minimal definition', - xml: '', - expectedXml: - '\n' + - ' unnamed\n' + - '', - assertBlockStructure: - (block) => { - assertDefBlockStructure(block, testSuite.hasReturn); - }, - }, - { - title: 'Common definition', - xml: - '' + - ' do something' + - '', - expectedXml: - '\n' + - ' do something\n' + - '', - assertBlockStructure: - (block) => { - assertDefBlockStructure(block, testSuite.hasReturn); - }, - }, - { - title: 'With vars definition', - xml: - '\n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' do something\n' + - '', - expectedXml: - '\n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' do something\n' + - '', - assertBlockStructure: - (block) => { - assertDefBlockStructure( - block, testSuite.hasReturn, ['x', 'y'], ['arg1', 'arg2']); - }, - }, - { - title: 'With pre-created vars definition', - xml: - '\n' + - ' \n' + - ' \n' + - ' \n' + - ' do something\n' + - '', - expectedXml: - '\n' + - ' \n' + - ' \n' + - ' \n' + - ' do something\n' + - '', - assertBlockStructure: - (block) => { - assertDefBlockStructure(block, testSuite.hasReturn, - ['preCreatedVar'], ['preCreatedVarId']); - }, - }, - { - title: 'With pre-created typed vars definition', - xml: - '\n' + - ' \n' + - ' \n' + - ' \n' + - ' do something\n' + - '', - expectedXml: - '\n' + - ' \n' + - ' \n' + - ' \n' + - ' do something\n' + - '', - assertBlockStructure: - (block) => { - assertDefBlockStructure(block, testSuite.hasReturn, - ['preCreatedTypedVar'], ['preCreatedTypedVarId']); - }, - }, - { - title: 'No statements definition', - xml: - '\n' + - ' \n' + - ' do something\n' + - '', - expectedXml: - '\n' + - ' \n' + - ' do something\n' + - '', - assertBlockStructure: - (block) => { - assertDefBlockStructure(block, true, [], [], false); - }, - }, - { - title: 'Minimal caller', - xml: '', - expectedXml: - '\n' + - ' \n' + - '', - assertBlockStructure: - (block) => { - assertCallBlockStructure(block); - }, - }, - { - title: 'Common caller', - xml: - '\n' + - ' \n' + - '', - expectedXml: - '\n' + - ' \n' + - '', - assertBlockStructure: - (block) => { - assertCallBlockStructure(block); - }, - }, - { - title: 'With pre-created vars caller', - xml: - '\n' + - ' \n' + - ' \n' + - ' \n' + - '', - expectedXml: - '\n' + - ' \n' + - ' \n' + - ' \n' + - '', - assertBlockStructure: - (block) => { - assertCallBlockStructure(block, ['preCreatedVar'], ['preCreatedVarId']); - }, - }, - ]; - runSerializationTestSuite(testCases); - }); - }); -}); - -suite('Procedures, dont auto fire events', function() { - setup(function() { - sharedTestSetup.call(this, {fireEventsNow: false}); - this.workspace = new Blockly.Workspace(); - }); - teardown(function() { - sharedTestTeardown.call(this); - }); - - const testSuites = [ - {title: 'procedures_defreturn', hasReturn: true, - defType: 'procedures_defreturn', callType: 'procedures_callreturn'}, - {title: 'procedures_defnoreturn', hasReturn: false, - defType: 'procedures_defnoreturn', callType: 'procedures_callnoreturn'}, - ]; - - testSuites.forEach((testSuite) => { - suite(testSuite.title, function() { - suite('Disposal', function() { - test('callers are disposed when definitions are disposed', function() { - this.defBlock = new Blockly.Block(this.workspace, testSuite.defType); - this.defBlock.setFieldValue('proc name', 'NAME'); - this.callerBlock = new Blockly.Block( - this.workspace, testSuite.callType); - this.callerBlock.setFieldValue('proc name', 'NAME'); - - // Run the clock now so that the create events get fired. If we fire - // it after disposing, a new procedure def will get created when - // the caller create event is heard. - this.clock.runAll(); - this.defBlock.dispose(); - this.clock.runAll(); - - chai.assert.isTrue(this.callerBlock.disposed); - }); - }); - }); - }); -}); diff --git a/tests/mocha/variables_test.js b/tests/mocha/variables_test.js deleted file mode 100644 index 73747122086..00000000000 --- a/tests/mocha/variables_test.js +++ /dev/null @@ -1,142 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -goog.declareModuleId('Blockly.test.variables'); - -import {sharedTestSetup, sharedTestTeardown} from '../test_helpers/setup_teardown.js'; - - -suite('Variables', function() { - setup(function() { - sharedTestSetup.call(this); - this.workspace = new Blockly.Workspace(); - Blockly.defineBlocksWithJsonArray([{ - "type": "get_var_block", - "message0": "%1", - "args0": [ - { - "type": "field_variable", - "name": "VAR", - "variableTypes": ["", "type1", "type2"], - }, - ], - }]); - this.workspace.createVariable('foo', 'type1', '1'); - this.workspace.createVariable('bar', 'type1', '2'); - this.workspace.createVariable('baz', 'type1', '3'); - }); - - teardown(function() { - sharedTestTeardown.call(this); - }); - - /** - * Create a test get_var_block. - * Will fail if get_var_block isn't defined. - * @param {!Blockly.Workspace} workspace The workspace on which to create the - * block. - * @param {!string} variableId The id of the variable to reference. - * @return {!Blockly.Block} The created block. - */ - function createTestVarBlock(workspace, variableId) { - // Turn off events to avoid testing XML at the same time. - Blockly.Events.disable(); - const block = new Blockly.Block(workspace, 'get_var_block'); - block.inputList[0].fieldRow[0].setValue(variableId); - Blockly.Events.enable(); - return block; - } - - suite('allUsedVarModels', function() { - test('All used', function() { - createTestVarBlock(this.workspace, '1'); - createTestVarBlock(this.workspace, '2'); - createTestVarBlock(this.workspace, '3'); - - const result = Blockly.Variables.allUsedVarModels(this.workspace); - chai.assert.equal(result.length, 3, - 'Expected three variables in the list of used variables'); - }); - - test('Some unused', function() { - createTestVarBlock(this.workspace, '2'); - - const result = Blockly.Variables.allUsedVarModels(this.workspace); - chai.assert.equal(result.length, 1, - 'Expected one variable in the list of used variables'); - chai.assert.equal(result[0].getId(), '2', - 'Expected variable with ID 2 in the list of used variables'); - }); - - test('Var used twice', function() { - createTestVarBlock(this.workspace, '2'); - createTestVarBlock(this.workspace, '2'); - - const result = Blockly.Variables.allUsedVarModels(this.workspace); - // Using the same variable multiple times should not change the number of - // elements in the list. - chai.assert.equal(result.length, 1, - 'Expected one variable in the list of used variables'); - chai.assert.equal(result[0].getId(), '2', - 'Expected variable with ID 2 in the list of used variables'); - }); - - test('All unused', function() { - const result = Blockly.Variables.allUsedVarModels(this.workspace); - chai.assert.equal(result.length, 0, - 'Expected no variables in the list of used variables'); - }); - }); - - suite('getVariable', function() { - test('By id', function() { - const var1 = this.workspace.createVariable('name1', 'type1', 'id1'); - const var2 = this.workspace.createVariable('name2', 'type1', 'id2'); - const var3 = this.workspace.createVariable('name3', 'type2', 'id3'); - const result1 = Blockly.Variables.getVariable(this.workspace, 'id1'); - const result2 = Blockly.Variables.getVariable(this.workspace, 'id2'); - const result3 = Blockly.Variables.getVariable(this.workspace, 'id3'); - - chai.assert.equal(var1, result1); - chai.assert.equal(var2, result2); - chai.assert.equal(var3, result3); - }); - - test('By name and type', function() { - const var1 = this.workspace.createVariable('name1', 'type1', 'id1'); - const var2 = this.workspace.createVariable('name2', 'type1', 'id2'); - const var3 = this.workspace.createVariable('name3', 'type2', 'id3'); - const result1 = - Blockly.Variables.getVariable(this.workspace, null, 'name1', 'type1'); - const result2 = - Blockly.Variables.getVariable(this.workspace, null, 'name2', 'type1'); - const result3 = - Blockly.Variables.getVariable(this.workspace, null, 'name3', 'type2'); - - // Searching by name + type is correct. - chai.assert.equal(var1, result1); - chai.assert.equal(var2, result2); - chai.assert.equal(var3, result3); - }); - - test('Bad id with name and type fallback', function() { - const var1 = this.workspace.createVariable('name1', 'type1', 'id1'); - const var2 = this.workspace.createVariable('name2', 'type1', 'id2'); - const var3 = this.workspace.createVariable('name3', 'type2', 'id3'); - const result1 = - Blockly.Variables.getVariable(this.workspace, 'badId', 'name1', 'type1'); - const result2 = - Blockly.Variables.getVariable(this.workspace, 'badId', 'name2', 'type1'); - const result3 = - Blockly.Variables.getVariable(this.workspace, 'badId', 'name3', 'type2'); - - // Searching by ID failed, but falling back onto name + type is correct. - chai.assert.equal(var1, result1); - chai.assert.equal(var2, result2); - chai.assert.equal(var3, result3); - }); - }); -}); From 69abf3f0ac71d2f130dc68ecbf58d13dd4a1bc7a Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Mon, 15 Aug 2022 18:12:27 +0000 Subject: [PATCH 09/18] chore: bump mocha timeout --- tests/mocha/run_mocha_tests_in_browser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/mocha/run_mocha_tests_in_browser.js b/tests/mocha/run_mocha_tests_in_browser.js index 217c9992bc6..2095bed31b1 100644 --- a/tests/mocha/run_mocha_tests_in_browser.js +++ b/tests/mocha/run_mocha_tests_in_browser.js @@ -55,7 +55,7 @@ async function runMochaTestsInBrowser() { var text = await elem.getAttribute('tests_failed'); return text != 'unset'; }, { - timeout: 50000 + timeout: 100000 }); const elem = await browser.$('#failureCount'); From 80d164da2a3ecb2022a660078effc665e5a580ca Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Mon, 15 Aug 2022 18:19:52 +0000 Subject: [PATCH 10/18] chore: bump timeout again? --- tests/mocha/run_mocha_tests_in_browser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/mocha/run_mocha_tests_in_browser.js b/tests/mocha/run_mocha_tests_in_browser.js index 2095bed31b1..3b4c6b67b04 100644 --- a/tests/mocha/run_mocha_tests_in_browser.js +++ b/tests/mocha/run_mocha_tests_in_browser.js @@ -55,7 +55,7 @@ async function runMochaTestsInBrowser() { var text = await elem.getAttribute('tests_failed'); return text != 'unset'; }, { - timeout: 100000 + timeout: 150000 }); const elem = await browser.$('#failureCount'); From 579c3bc44f1526cbb26a683fbdc0ce8591c57a46 Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Mon, 15 Aug 2022 18:35:42 +0000 Subject: [PATCH 11/18] chore: eliminate the possibility that tests are actually timing out --- tests/mocha/run_mocha_tests_in_browser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/mocha/run_mocha_tests_in_browser.js b/tests/mocha/run_mocha_tests_in_browser.js index 3b4c6b67b04..c7725ddd1c4 100644 --- a/tests/mocha/run_mocha_tests_in_browser.js +++ b/tests/mocha/run_mocha_tests_in_browser.js @@ -55,7 +55,7 @@ async function runMochaTestsInBrowser() { var text = await elem.getAttribute('tests_failed'); return text != 'unset'; }, { - timeout: 150000 + timeout: 1500000 }); const elem = await browser.$('#failureCount'); From 52520a0674f15f72a3fccf4047d046254de48e04 Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Mon, 15 Aug 2022 18:54:53 +0000 Subject: [PATCH 12/18] chore: change timeout back --- tests/mocha/run_mocha_tests_in_browser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/mocha/run_mocha_tests_in_browser.js b/tests/mocha/run_mocha_tests_in_browser.js index c7725ddd1c4..217c9992bc6 100644 --- a/tests/mocha/run_mocha_tests_in_browser.js +++ b/tests/mocha/run_mocha_tests_in_browser.js @@ -55,7 +55,7 @@ async function runMochaTestsInBrowser() { var text = await elem.getAttribute('tests_failed'); return text != 'unset'; }, { - timeout: 1500000 + timeout: 50000 }); const elem = await browser.$('#failureCount'); From 71e160a683253b42fc97da347682b2984e405576 Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Mon, 15 Aug 2022 19:53:01 +0000 Subject: [PATCH 13/18] chore: remove tests that might be the problematic ones --- tests/mocha/index.html | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/mocha/index.html b/tests/mocha/index.html index 0c58062447e..c0e42b56804 100644 --- a/tests/mocha/index.html +++ b/tests/mocha/index.html @@ -51,7 +51,7 @@ // Test modules. 'Blockly.test.astNode', 'Blockly.test.blockChangeEvent', - 'Blockly.test.blockDeleteEvent', + //'Blockly.test.blockDeleteEvent', 'Blockly.test.blockCreateEvent', 'Blockly.test.blockJson', 'Blockly.test.blocks', @@ -87,12 +87,12 @@ 'Blockly.test.jsoSerialization', 'Blockly.test.json', 'Blockly.test.keydown', - 'Blockly.test.lists', - 'Blockly.test.logicTernary', + //'Blockly.test.lists', + //'Blockly.test.logicTernary', 'Blockly.test.metrics', 'Blockly.test.mutator', 'Blockly.test.names', - 'Blockly.test.procedures', + //'Blockly.test.procedures', 'Blockly.test.registry', 'Blockly.test.serialization', 'Blockly.test.shortcutRegistry', @@ -103,7 +103,7 @@ 'Blockly.test.utils', 'Blockly.test.variableMap', 'Blockly.test.variableModel', - 'Blockly.test.variables', + //'Blockly.test.variables', 'Blockly.test.widgetDiv', 'Blockly.test.workspaceComment', 'Blockly.test.workspaceSvg', From b3fd4b9fd8314a0ac028b77971c9104750ffe75f Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Tue, 16 Aug 2022 22:26:28 +0000 Subject: [PATCH 14/18] chore: attempt enabling delete event test --- tests/mocha/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/mocha/index.html b/tests/mocha/index.html index c0e42b56804..967fbb9aaea 100644 --- a/tests/mocha/index.html +++ b/tests/mocha/index.html @@ -51,7 +51,7 @@ // Test modules. 'Blockly.test.astNode', 'Blockly.test.blockChangeEvent', - //'Blockly.test.blockDeleteEvent', + 'Blockly.test.blockDeleteEvent', 'Blockly.test.blockCreateEvent', 'Blockly.test.blockJson', 'Blockly.test.blocks', From 6f63a4be5483322b5ac4abc55e602ddc6c57c1f1 Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Tue, 16 Aug 2022 22:32:37 +0000 Subject: [PATCH 15/18] chore: enable lists tests --- tests/mocha/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/mocha/index.html b/tests/mocha/index.html index 967fbb9aaea..c803dcc4cb2 100644 --- a/tests/mocha/index.html +++ b/tests/mocha/index.html @@ -87,7 +87,7 @@ 'Blockly.test.jsoSerialization', 'Blockly.test.json', 'Blockly.test.keydown', - //'Blockly.test.lists', + 'Blockly.test.lists', //'Blockly.test.logicTernary', 'Blockly.test.metrics', 'Blockly.test.mutator', From 4b3472a73345d5280a22c843f8df04d8963d836e Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Tue, 16 Aug 2022 22:35:35 +0000 Subject: [PATCH 16/18] chore: try ternary test as well --- tests/mocha/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/mocha/index.html b/tests/mocha/index.html index c803dcc4cb2..c8bae8f5437 100644 --- a/tests/mocha/index.html +++ b/tests/mocha/index.html @@ -87,8 +87,8 @@ 'Blockly.test.jsoSerialization', 'Blockly.test.json', 'Blockly.test.keydown', - 'Blockly.test.lists', - //'Blockly.test.logicTernary', + //'Blockly.test.lists', + 'Blockly.test.logicTernary', 'Blockly.test.metrics', 'Blockly.test.mutator', 'Blockly.test.names', From d48cec8bf53064b7b6f5f5545d37c222bce99e82 Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Tue, 16 Aug 2022 22:41:07 +0000 Subject: [PATCH 17/18] chore: actually add block test files --- tests/mocha/blocks/lists_test.js | 194 ++++ tests/mocha/blocks/logic_ternary_test.js | 253 +++++ tests/mocha/blocks/procedures_test.js | 1251 ++++++++++++++++++++++ tests/mocha/blocks/variables_test.js | 142 +++ 4 files changed, 1840 insertions(+) create mode 100644 tests/mocha/blocks/lists_test.js create mode 100644 tests/mocha/blocks/logic_ternary_test.js create mode 100644 tests/mocha/blocks/procedures_test.js create mode 100644 tests/mocha/blocks/variables_test.js diff --git a/tests/mocha/blocks/lists_test.js b/tests/mocha/blocks/lists_test.js new file mode 100644 index 00000000000..b1609aebe5a --- /dev/null +++ b/tests/mocha/blocks/lists_test.js @@ -0,0 +1,194 @@ +/** + * @license + * Copyright 2022 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +goog.declareModuleId('Blockly.test.lists'); + +import {runSerializationTestSuite} from '../test_helpers/serialization.js'; +import {sharedTestSetup, sharedTestTeardown} from '../test_helpers/setup_teardown.js'; +import {ConnectionType} from '../../../build/src/core/connection_type.js'; +import {defineStatementBlock} from '../test_helpers/block_definitions.js'; + + +suite('Lists', function() { + setup(function() { + sharedTestSetup.call(this); + defineStatementBlock(); + this.workspace = new Blockly.Workspace(); + }); + + teardown(function() { + sharedTestTeardown.call(this); + }); + + suite('ListsGetIndex', function() { + /** + * Test cases for serialization tests. + * @type {Array} + */ + const testCases = [ + { + title: 'JSON not requiring mutations', + json: { + type: 'lists_getIndex', + id: '1', + fields: {MODE: 'GET', WHERE: 'FIRST'}, + }, + assertBlockStructure: (block) => { + chai.assert.equal(block.type, 'lists_getIndex'); + chai.assert.exists(block.outputConnection); + }, + }, + { + title: 'JSON requiring mutations', + json: { + type: 'lists_getIndex', + id: '1', + extraState: {isStatement: true}, + fields: {MODE: 'REMOVE', WHERE: 'FROM_START'}, + }, + assertBlockStructure: (block) => { + chai.assert.equal(block.type, 'lists_getIndex'); + chai.assert.isNotTrue(block.outputConnection); + chai.assert.isTrue( + block.getInput('AT').type === ConnectionType.INPUT_VALUE + ); + }, + }, + { + title: + 'JSON requiring mutations and extra state for previous connection', + json: { + type: 'statement_block', + id: '1', + next: { + block: { + type: 'lists_getIndex', + id: '2', + extraState: {isStatement: true}, + fields: {MODE: 'REMOVE', WHERE: 'FROM_START'}, + }, + }, + }, + assertBlockStructure: (block) => {}, + }, + { + title: + 'JSON requiring mutations with XML extra state', + json: { + type: 'statement_block', + id: '1', + next: { + block: { + type: 'lists_getIndex', + id: '2', + extraState: '', + fields: {MODE: 'REMOVE', WHERE: 'FROM_START'}, + }, + }, + }, + expectedJson: { + type: 'statement_block', + id: '1', + next: { + block: { + type: 'lists_getIndex', + id: '2', + extraState: {isStatement: true}, + fields: {MODE: 'REMOVE', WHERE: 'FROM_START'}, + }, + }, + }, + assertBlockStructure: (block) => {}, + }, + ]; + runSerializationTestSuite(testCases); + }); + + /** + * Test cases for serialization where JSON hooks should have null + * implementation to avoid serializing xml mutations in json. + * @param {!Object} serializedJson basic serialized json + * @param {!string} xmlMutation xml mutation that should be ignored/not reserialized in round trip + * @return {Array} test cases + */ + function makeTestCasesForBlockNotNeedingExtraState_(serializedJson, xmlMutation) { + return [ + { + title: 'JSON not requiring mutations', + json: serializedJson, + assertBlockStructure: (block) => { + chai.assert.equal(block.type, serializedJson.type); + }, + }, + { + title: + 'JSON with XML extra state', + json: { + ...serializedJson, + "extraState": xmlMutation, + }, + expectedJson: serializedJson, + assertBlockStructure: (block) => {}, + }, + ]; + } + + suite('ListsSetIndex', function() { + /** + * Test cases for serialization tests. + * @type {Array} + */ + const testCases = makeTestCasesForBlockNotNeedingExtraState_( + { + "type": "lists_setIndex", + "id": "1", + "fields": { + "MODE": "SET", + "WHERE": "FROM_START", + }, + }, + "" + ); + runSerializationTestSuite(testCases); + }); + + suite('ListsGetSubList', function() { + /** + * Test cases for serialization tests. + * @type {Array} + */ + const testCases = makeTestCasesForBlockNotNeedingExtraState_( + { + "type": "lists_getSublist", + "id": "1", + "fields": { + "WHERE1": "FROM_START", + "WHERE2": "FROM_START", + }, + }, + "" + ); + runSerializationTestSuite(testCases); + }); + + suite('ListsSplit', function() { + /** + * Test cases for serialization tests. + * @type {Array} + */ + const testCases = makeTestCasesForBlockNotNeedingExtraState_( + { + "type": "lists_split", + "id": "1", + "fields": { + "MODE": "SPLIT", + }, + }, + "" + ); + runSerializationTestSuite(testCases); + }); +}); diff --git a/tests/mocha/blocks/logic_ternary_test.js b/tests/mocha/blocks/logic_ternary_test.js new file mode 100644 index 00000000000..db2de13bbc8 --- /dev/null +++ b/tests/mocha/blocks/logic_ternary_test.js @@ -0,0 +1,253 @@ +/** + * @license + * Copyright 2020 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +goog.declareModuleId('Blockly.test.logicTernary'); + +import * as eventUtils from '../../../build/src/core/events/utils.js'; +import {runSerializationTestSuite} from '../test_helpers/serialization.js'; +import {sharedTestSetup, sharedTestTeardown} from '../test_helpers/setup_teardown.js'; + + +suite('Logic ternary', function() { + setup(function() { + sharedTestSetup.call(this); + this.workspace = new Blockly.Workspace(); + }); + + teardown(function() { + sharedTestTeardown.call(this); + }); + + /** + * Asserts that the logic ternary block has the expected inputs and fields. + * @param {!Blockly.Block} block The block to check. + * @param {boolean=} inputsInline Whether the inputs are expected to be + * inline. + */ + function assertBlockStructure(block, inputsInline = false) { + chai.assert.equal(block.type, 'logic_ternary'); + const inputs = block.inputList; + chai.assert.exists(inputs, 'Has inputList'); + chai.assert.lengthOf(inputs, 3); + const ifInput = block.getInput('IF'); + chai.assert.exists(ifInput, 'Has "IF" input'); + chai.assert.equal(ifInput.connection.check_.length, 1); + chai.assert.equal(ifInput.connection.check_[0], 'Boolean'); + chai.assert.exists(block.onchangeWrapper_, 'Has onchange handler'); + if (inputsInline) { + chai.assert.isTrue(block.inputsInline); + } else { + // inputsInline can be undefined + chai.assert.isNotTrue(block.inputsInline); + } + } + + test('Structure', function() { + const block = this.workspace.newBlock('logic_ternary'); + assertBlockStructure(block); + }); + + /** + * Test cases for serialization tests. + * @type {Array} + */ + const testCases = [ + {title: 'Empty XML', xml: '', + expectedXml: + '', + assertBlockStructure: + (block) => { + assertBlockStructure(block); + }, + }, + {title: 'Inputs inline', + xml: + '', + assertBlockStructure: + (block) => { + assertBlockStructure(block, true); + }, + }, + ]; + runSerializationTestSuite(testCases); + + suite('Connections', function() { + function connectParentAndCheckConnections( + block, parent, parentInputName, opt_thenInput, opt_elseInput) { + parent.getInput(parentInputName).connection.connect(block.outputConnection); + eventUtils.TEST_ONLY.fireNow(); // Force synchronous onchange() call. + chai.assert.equal(block.getParent(), parent, + 'Successful connection to parent'); + if (opt_thenInput) { + chai.assert.equal(opt_thenInput.getParent(), block, + 'Input THEN still connected after connecting parent'); + } + if (opt_elseInput) { + chai.assert.equal(opt_elseInput.getParent(), block, + 'Input ELSE still connected after connecting parent'); + } + } + function connectThenInputAndCheckConnections( + block, thenInput, opt_elseInput, opt_parent) { + block.getInput('THEN').connection.connect(thenInput.outputConnection); + eventUtils.TEST_ONLY.fireNow(); // Force synchronous onchange() call. + chai.assert.equal(thenInput.getParent(), block, 'THEN is connected'); + if (opt_parent) { + chai.assert.equal(block.getParent(), opt_parent, + 'Still connected to parent after connecting THEN'); + } + if (opt_elseInput) { + chai.assert.equal(opt_elseInput.getParent(), block, + 'Input ELSE still connected after connecting THEN'); + } + } + function connectElseInputAndCheckConnections( + block, elseInput, opt_thenInput, opt_parent) { + block.getInput('ELSE').connection.connect(elseInput.outputConnection); + eventUtils.TEST_ONLY.fireNow(); // Force synchronous onchange() call. + chai.assert.equal(elseInput.getParent(), block, 'ELSE is connected'); + if (opt_parent) { + chai.assert.equal(block.getParent(), opt_parent, + 'Still connected to parent after connecting ELSE'); + } + if (opt_thenInput) { + chai.assert.equal(opt_thenInput.getParent(), block, + 'Input THEN still connected after connecting ELSE'); + } + } + function connectInputsAndCheckConnections( + block, thenInput, elseInput, opt_parent) { + connectThenInputAndCheckConnections(block, thenInput, null, opt_parent); + connectElseInputAndCheckConnections(block, elseInput, thenInput, opt_parent); + } + setup(function() { + this.block = this.workspace.newBlock('logic_ternary'); + }); + suite('No parent', function() { + test('Attach inputs same type', function() { + const string1 = this.workspace.newBlock('text'); + const string2 = this.workspace.newBlock('text_charAt'); + + connectInputsAndCheckConnections(this.block, string1, string2); + }); + test('Attach inputs different types', function() { + const string = this.workspace.newBlock('text'); + const number = this.workspace.newBlock('math_number'); + + connectInputsAndCheckConnections(this.block, string, number); + }); + }); + suite('With parent already attached', function() { + test('Attach inputs same type with matching parent', function() { + const parent = this.workspace.newBlock('text_trim'); + + connectParentAndCheckConnections(this.block, parent, 'TEXT'); + + const string1 = this.workspace.newBlock('text'); + const string2 = this.workspace.newBlock('text_charAt'); + + connectInputsAndCheckConnections(this.block, string1, string2, parent); + }); + test('Attach inputs different types with unchecked parent', function() { + const parent = this.workspace.newBlock('text_print'); + + connectParentAndCheckConnections(this.block, parent, 'TEXT'); + + const string = this.workspace.newBlock('text'); + const number = this.workspace.newBlock('math_number'); + + connectInputsAndCheckConnections(this.block, string, number, parent); + }); + test('Attach inputs different types with permissive parent', function() { + const parent = this.workspace.newBlock('text_length'); // Allows String or Array + + connectParentAndCheckConnections(this.block, parent, 'VALUE'); + + const string = this.workspace.newBlock('text'); + const array = this.workspace.newBlock('lists_create_empty'); + + connectInputsAndCheckConnections(this.block, string, array, parent); + }); + test('Attach mismatch type to then causes break with parent', function() { + const parent = this.workspace.newBlock('text_length'); // Allows String or Array + + connectParentAndCheckConnections(this.block, parent, 'VALUE'); + + const string = this.workspace.newBlock('text'); + const number = this.workspace.newBlock('math_number'); + + connectElseInputAndCheckConnections(this.block, string, null, parent); + + // Adding mismatching number. + connectThenInputAndCheckConnections(this.block, number, string); + chai.assert.equal(this.block.getRootBlock(), this.block, + 'Disconnected from parent'); + }); + test('Attach mismatch type to else causes break with parent', function() { + const parent = this.workspace.newBlock('text_length'); // Allows String or Array + + connectParentAndCheckConnections(this.block, parent, 'VALUE'); + + const string = this.workspace.newBlock('text'); + const number = this.workspace.newBlock('math_number'); + + connectThenInputAndCheckConnections(this.block, string, null, parent); + + // Adding mismatching number. + connectElseInputAndCheckConnections(this.block, number, string); + chai.assert.equal(this.block.getRootBlock(), this.block, + 'Disconnected from parent'); + }); + }); + suite('Attaching parent after inputs', function() { + test('Unchecked parent with inputs different types', function() { + const string = this.workspace.newBlock('text'); + const number = this.workspace.newBlock('math_number'); + + connectInputsAndCheckConnections(this.block, string, number); + + const parent = this.workspace.newBlock('text_print'); + connectParentAndCheckConnections( + this.block, parent, 'TEXT', string, number); + }); + test('Permissive parent with inputs different types', function() { + const string = this.workspace.newBlock('text'); + const array = this.workspace.newBlock('lists_create_empty'); + + connectInputsAndCheckConnections(this.block, string, array); + + const parent = this.workspace.newBlock('text_print'); + connectParentAndCheckConnections( + this.block, parent, 'TEXT', string, array); + }); + test('Mismatch with then causes break with then', function() { + const number = this.workspace.newBlock('math_number'); + const string = this.workspace.newBlock('text'); + + connectInputsAndCheckConnections(this.block, number, string); + + const parent = this.workspace.newBlock('text_trim'); + connectParentAndCheckConnections( + this.block, parent, 'TEXT', null, string); + chai.assert.equal(number.getRootBlock(), number, + 'Input THEN disconnected'); + }); + test('Mismatch with else causes break with else', function() { + const string = this.workspace.newBlock('text'); + const number = this.workspace.newBlock('math_number'); + + connectInputsAndCheckConnections(this.block, string, number); + + const parent = this.workspace.newBlock('text_trim'); + connectParentAndCheckConnections(this.block, parent, 'TEXT', string); + chai.assert.equal(number.getRootBlock(), number, + 'Input ELSE disconnected'); + }); + }); + }); +}); diff --git a/tests/mocha/blocks/procedures_test.js b/tests/mocha/blocks/procedures_test.js new file mode 100644 index 00000000000..898bdf27a92 --- /dev/null +++ b/tests/mocha/blocks/procedures_test.js @@ -0,0 +1,1251 @@ +/** + * @license + * Copyright 2019 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +goog.declareModuleId('Blockly.test.procedures'); + +import * as Blockly from '../../../build/src/core/blockly.js'; +import {assertCallBlockStructure, assertDefBlockStructure, createProcDefBlock, createProcCallBlock} from '../test_helpers/procedures.js'; +import {runSerializationTestSuite} from '../test_helpers/serialization.js'; +import {createGenUidStubWithReturns, sharedTestSetup, sharedTestTeardown, workspaceTeardown} from '../test_helpers/setup_teardown.js'; + +suite('Procedures', function() { + setup(function() { + sharedTestSetup.call(this); + this.workspace = new Blockly.Workspace(); + this.workspace.createVariable('preCreatedVar', '', 'preCreatedVarId'); + this.workspace.createVariable( + 'preCreatedTypedVar', 'type', 'preCreatedTypedVarId'); + }); + teardown(function() { + sharedTestTeardown.call(this); + }); + + suite('allProcedures', function() { + test('Only Procedures', function() { + const noReturnBlock = new Blockly.Block(this.workspace, 'procedures_defnoreturn'); + noReturnBlock.setFieldValue('no return', 'NAME'); + const returnBlock = new Blockly.Block(this.workspace, 'procedures_defreturn'); + returnBlock.setFieldValue('return', 'NAME'); + + const allProcedures = Blockly.Procedures.allProcedures(this.workspace); + chai.assert.lengthOf(allProcedures, 2); + + chai.assert.lengthOf(allProcedures[0], 1); + chai.assert.equal(allProcedures[0][0][0], 'no return'); + + chai.assert.lengthOf(allProcedures[1], 1); + chai.assert.equal(allProcedures[1][0][0], 'return'); + }); + test('Multiple Blocks', function() { + const noReturnBlock = new Blockly.Block(this.workspace, 'procedures_defnoreturn'); + noReturnBlock.setFieldValue('no return', 'NAME'); + const returnBlock = new Blockly.Block(this.workspace, 'procedures_defreturn'); + returnBlock.setFieldValue('return', 'NAME'); + const returnBlock2 = new Blockly.Block(this.workspace, 'procedures_defreturn'); + returnBlock2.setFieldValue('return2', 'NAME'); + const _ = new Blockly.Block(this.workspace, 'controls_if'); + + const allProcedures = Blockly.Procedures.allProcedures(this.workspace); + chai.assert.lengthOf(allProcedures, 2); + + chai.assert.lengthOf(allProcedures[0], 1); + chai.assert.equal(allProcedures[0][0][0], 'no return'); + + chai.assert.lengthOf(allProcedures[1], 2); + chai.assert.equal(allProcedures[1][0][0], 'return'); + chai.assert.equal(allProcedures[1][1][0], 'return2'); + }); + test('No Procedures', function() { + const _ = new Blockly.Block(this.workspace, 'controls_if'); + const allProcedures = Blockly.Procedures.allProcedures(this.workspace); + chai.assert.lengthOf(allProcedures, 2); + chai.assert.lengthOf(allProcedures[0], 0, 'No procedures_defnoreturn blocks expected'); + chai.assert.lengthOf(allProcedures[1], 0, 'No procedures_defreturn blocks expected'); + }); + }); + + suite('isNameUsed', function() { + test('No Blocks', function() { + chai.assert.isFalse( + Blockly.Procedures.isNameUsed('name1', this.workspace) + ); + }); + }); + + suite('Enable/Disable', function() { + setup(function() { + const toolbox = document.getElementById('toolbox-categories'); + this.workspaceSvg = Blockly.inject('blocklyDiv', {toolbox: toolbox}); + }); + teardown(function() { + workspaceTeardown.call(this, this.workspaceSvg); + sinon.restore(); + }); + suite('Inherited disabled', function() { + setup(function() { + const dom = Blockly.Xml.textToDom( + '' + + '' + + 'bar' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + 'foo' + + '' + + '' + + 'baz' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + ''); + Blockly.Events.disable(); + Blockly.Xml.appendDomToWorkspace(dom, this.workspaceSvg); + Blockly.Events.enable(); + + this.barDef = this.workspaceSvg.getBlockById('bar-def'); + this.fooDef = this.workspaceSvg.getBlockById('foo-def'); + this.bazDef = this.workspaceSvg.getBlockById('baz-def'); + + this.barCalls = [ + this.workspaceSvg.getBlockById('bar-c1'), + this.workspaceSvg.getBlockById('bar-c2')]; + this.fooCalls = [ + this.workspaceSvg.getBlockById('foo-c1'), + this.workspaceSvg.getBlockById('foo-c2')]; + this.bazCall = this.workspaceSvg.getBlockById('baz-c1'); + }); + test('Nested caller', function() { + this.barDef.setEnabled(false); + + for (let i = 0; i < 2; i++) { + chai.assert.isFalse(this.barCalls[i].isEnabled(), + 'Callers are disabled when their definition is disabled ' + + '(bar call ' + i + ')'); + } + chai.assert.isTrue(this.fooCalls[0].isEnabled(), + 'Callers in definitions are disabled by inheritance'); + chai.assert.isTrue(this.fooCalls[0].getInheritedDisabled(), + 'Callers in definitions are disabled by inheritance'); + + this.fooDef.setEnabled(false); + + for (let i = 0; i < 2; i++) { + chai.assert.isFalse(this.fooCalls[i].isEnabled(), + 'Callers are disabled when their definition is disabled ' + + '(foo call ' + i + ')'); + } + + this.barDef.setEnabled(true); + + for (let i = 0; i < 2; i++) { + chai.assert.isTrue(this.barCalls[i].isEnabled(), + 'Callers are reenabled with their definition ' + + '(bar call ' + i + ')'); + } + chai.assert.isFalse(this.fooCalls[0].isEnabled(), + 'Nested disabled callers remain disabled'); + chai.assert.isFalse(this.fooCalls[0].getInheritedDisabled(), + 'Nested disabled callers remain disabled, not by inheritance'); + }); + test('Caller in return', function() { + this.bazDef.setEnabled(false); + + chai.assert.isFalse(this.bazCall.isEnabled(), + 'Caller is disabled with its definition'); + + chai.assert.isTrue(this.barCalls[1].isEnabled(), + 'Caller in the return is disabled by inheritance'); + chai.assert.isTrue(this.barCalls[1].getInheritedDisabled(), + 'Caller in the return is disabled by inheritance'); + + this.barDef.setEnabled(false); + + for (let i = 0; i < 2; i++) { + chai.assert.isFalse(this.barCalls[i].isEnabled(), + 'Callers are disabled when their definition is disabled ' + + '(bar call ' + i + ')'); + } + + this.bazDef.setEnabled(true); + + chai.assert.isFalse(this.barCalls[1].isEnabled(), + 'Caller in return remains disabled'); + chai.assert.isFalse(this.barCalls[1].getInheritedDisabled(), + 'Caller in return remains disabled, not by inheritance'); + }); + }); + }); + + suite('Multiple block serialization', function() { + function assertDefAndCallBlocks(workspace, noReturnNames, returnNames, hasCallers) { + const allProcedures = Blockly.Procedures.allProcedures(workspace); + const defNoReturnBlocks = allProcedures[0]; + chai.assert.lengthOf(defNoReturnBlocks, noReturnNames.length); + for (let i = 0; i < noReturnNames.length; i++) { + const expectedName = noReturnNames[i]; + chai.assert.equal(defNoReturnBlocks[i][0], expectedName); + if (hasCallers) { + const callers = + Blockly.Procedures.getCallers(expectedName, workspace); + chai.assert.lengthOf(callers, 1); + } + } + const defReturnBlocks = allProcedures[1]; + chai.assert.lengthOf(defReturnBlocks, returnNames.length); + for (let i = 0; i < returnNames.length; i++) { + const expectedName = returnNames[i]; + chai.assert.equal(defReturnBlocks[i][0], expectedName); + if (hasCallers) { + const callers = + Blockly.Procedures.getCallers(expectedName, workspace); + chai.assert.lengthOf(callers, 1); + } + } + + // Expecting def and caller blocks are the only blocks on workspace + let expectedCount = noReturnNames.length + returnNames.length; + if (hasCallers) { + expectedCount *= 2; + } + const blocks = workspace.getAllBlocks(false); + chai.assert.lengthOf(blocks, expectedCount); + } + suite('no name renamed to unnamed', function() { + test('defnoreturn and defreturn', function() { + const xml = Blockly.Xml.textToDom(` + + + + `); + Blockly.Xml.domToWorkspace(xml, this.workspace); + assertDefAndCallBlocks( + this.workspace, ['unnamed'], ['unnamed2'], false); + }); + test('defreturn and defnoreturn', function() { + const xml = Blockly.Xml.textToDom(` + + + + `); + Blockly.Xml.domToWorkspace(xml, this.workspace); + assertDefAndCallBlocks( + this.workspace, ['unnamed2'], ['unnamed'], false); + }); + test('callnoreturn (no def in xml)', function() { + const xml = Blockly.Xml.textToDom(` + + + `); + Blockly.Xml.domToWorkspace(xml, this.workspace); + assertDefAndCallBlocks( + this.workspace, ['unnamed'], [], true); + }); + test('callreturn (no def in xml)', function() { + const xml = Blockly.Xml.textToDom(` + + + `); + Blockly.Xml.domToWorkspace(xml, this.workspace); + assertDefAndCallBlocks( + this.workspace, [], ['unnamed'], true); + }); + test('callnoreturn and callreturn (no def in xml)', function() { + const xml = Blockly.Xml.textToDom(` + + + + `); + Blockly.Xml.domToWorkspace(xml, this.workspace); + assertDefAndCallBlocks( + this.workspace, ['unnamed'], ['unnamed2'], true); + }); + test('callreturn and callnoreturn (no def in xml)', function() { + const xml = Blockly.Xml.textToDom(` + + + + `); + Blockly.Xml.domToWorkspace(xml, this.workspace); + assertDefAndCallBlocks( + this.workspace, ['unnamed2'], ['unnamed'], true); + }); + }); + suite('caller param mismatch', function() { + setup(function() { + this.TEST_VAR_ID = 'test-id'; + this.genUidStub = createGenUidStubWithReturns(this.TEST_VAR_ID); + }); + + test('callreturn with missing args', function() { + const defBlock = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(` + + do something + + + + + `), this.workspace); + const callBlock = Blockly.Xml.domToBlock(Blockly.Xml.textToDom( + '' + + ' ' + + '' + ), this.workspace); + assertDefBlockStructure(defBlock, true, ['x'], ['arg']); + assertCallBlockStructure(callBlock, [], [], 'do something2'); + }); + test('callreturn with bad args', function() { + const defBlock = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(` + + do something + + + + + `), this.workspace); + const callBlock = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(` + + + + + + `), this.workspace); + assertDefBlockStructure(defBlock, true, ['x'], ['arg']); + assertCallBlockStructure( + callBlock, ['y'], [this.TEST_VAR_ID], 'do something2'); + }); + test('callnoreturn with missing args', function() { + const defBlock = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(` + + do something + + + + + `), this.workspace); + const callBlock = Blockly.Xml.domToBlock(Blockly.Xml.textToDom( + '' + + ' ' + + '' + ), this.workspace); + assertDefBlockStructure(defBlock, false, ['x'], ['arg']); + assertCallBlockStructure(callBlock, [], [], 'do something2'); + }); + test('callnoreturn with bad args', function() { + const defBlock = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(` + + do something + + + + + `), this.workspace); + const callBlock = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(` + + + + + + `), this.workspace); + assertDefBlockStructure(defBlock, false, ['x'], ['arg']); + assertCallBlockStructure( + callBlock, ['y'], [this.TEST_VAR_ID], 'do something2'); + }); + }); + }); + + suite('getDefinition - Modified cases', function() { + setup(function() { + Blockly.Blocks['new_proc'] = { + init: function() { }, + getProcedureDef: function() { + return [this.name, [], false]; + }, + name: 'test', + }; + + Blockly.Blocks['nested_proc'] = { + init: function() { + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + }, + getProcedureDef: function() { + return [this.name, [], false]; + }, + name: 'test', + }; + }); + + teardown(function() { + delete Blockly.Blocks['new_proc']; + delete Blockly.Blocks['nested_proc']; + }); + + test('Custom procedure block', function() { + // Do not require procedures to be the built-in procedures. + const defBlock = new Blockly.Block(this.workspace, 'new_proc'); + const def = Blockly.Procedures.getDefinition('test', this.workspace); + chai.assert.equal(def, defBlock); + }); + + test('Stacked procedures', function() { + const blockA = new Blockly.Block(this.workspace, 'nested_proc'); + const blockB = new Blockly.Block(this.workspace, 'nested_proc'); + blockA.name = 'a'; + blockB.name = 'b'; + blockA.nextConnection.connect(blockB.previousConnection); + const def = Blockly.Procedures.getDefinition('b', this.workspace); + chai.assert.equal(def, blockB); + }); + }); + + const testSuites = [ + {title: 'procedures_defreturn', hasReturn: true, + defType: 'procedures_defreturn', callType: 'procedures_callreturn'}, + {title: 'procedures_defnoreturn', hasReturn: false, + defType: 'procedures_defnoreturn', callType: 'procedures_callnoreturn'}, + ]; + + testSuites.forEach((testSuite) => { + suite(testSuite.title, function() { + suite('Structure', function() { + setup(function() { + this.defBlock = new Blockly.Block(this.workspace, testSuite.defType); + this.defBlock.setFieldValue('proc name', 'NAME'); + }); + test('Definition block', function() { + assertDefBlockStructure(this.defBlock, testSuite.hasReturn); + }); + + test('Call block', function() { + this.callBlock = new Blockly.Block( + this.workspace, testSuite.callType); + this.callBlock.setFieldValue('proc name', 'NAME'); + assertCallBlockStructure(this.callBlock); + }); + }); + suite('isNameUsed', function() { + setup(function() { + this.defBlock = new Blockly.Block(this.workspace, testSuite.defType); + this.defBlock.setFieldValue('proc name', 'NAME'); + this.callBlock = new Blockly.Block( + this.workspace, testSuite.callType); + this.callBlock.setFieldValue('proc name', 'NAME'); + }); + test('True', function() { + chai.assert.isTrue( + Blockly.Procedures.isNameUsed('proc name', this.workspace)); + }); + test('False', function() { + chai.assert.isFalse( + Blockly.Procedures.isNameUsed('unused proc name', this.workspace)); + }); + }); + suite('rename', function() { + setup(function() { + this.defBlock = new Blockly.Block(this.workspace, testSuite.defType); + this.defBlock.setFieldValue('proc name', 'NAME'); + this.callBlock = new Blockly.Block( + this.workspace, testSuite.callType); + this.callBlock.setFieldValue('proc name', 'NAME'); + sinon.stub(this.defBlock.getField('NAME'), 'resizeEditor_'); + }); + test('Simple, Programmatic', function() { + this.defBlock.setFieldValue( + this.defBlock.getFieldValue('NAME') + '2', + 'NAME' + ); + chai.assert.equal( + this.defBlock.getFieldValue('NAME'), 'proc name2'); + chai.assert.equal( + this.callBlock.getFieldValue('NAME'), 'proc name2'); + }); + test('Simple, Input', function() { + const defInput = this.defBlock.getField('NAME'); + defInput.htmlInput_ = document.createElement('input'); + defInput.htmlInput_.setAttribute('data-old-value', 'proc name'); + defInput.htmlInput_.setAttribute('data-untyped-default-value', 'proc name'); + + defInput.htmlInput_.value = defInput.htmlInput_.getAttribute('data-old-value') + '2'; + defInput.onHtmlInputChange_(null); + chai.assert.equal( + this.defBlock.getFieldValue('NAME'), 'proc name2'); + chai.assert.equal( + this.callBlock.getFieldValue('NAME'), 'proc name2'); + }); + test('lower -> CAPS', function() { + const defInput = this.defBlock.getField('NAME'); + defInput.htmlInput_ = document.createElement('input'); + defInput.htmlInput_.setAttribute('data-old-value', 'proc name'); + defInput.htmlInput_.setAttribute('data-untyped-default-value', 'proc name'); + + defInput.htmlInput_.value = 'PROC NAME'; + defInput.onHtmlInputChange_(null); + chai.assert.equal( + this.defBlock.getFieldValue('NAME'), 'PROC NAME'); + chai.assert.equal( + this.callBlock.getFieldValue('NAME'), 'PROC NAME'); + }); + test('CAPS -> lower', function() { + this.defBlock.setFieldValue('PROC NAME', 'NAME'); + this.callBlock.setFieldValue('PROC NAME', 'NAME'); + const defInput = this.defBlock.getField('NAME'); + defInput.htmlInput_ = document.createElement('input'); + defInput.htmlInput_.setAttribute('data-old-value', 'PROC NAME'); + defInput.htmlInput_.setAttribute('data-untyped-default-value', 'PROC NAME'); + + defInput.htmlInput_.value = 'proc name'; + defInput.onHtmlInputChange_(null); + chai.assert.equal( + this.defBlock.getFieldValue('NAME'), 'proc name'); + chai.assert.equal( + this.callBlock.getFieldValue('NAME'), 'proc name'); + }); + test('Whitespace', function() { + const defInput = this.defBlock.getField('NAME'); + defInput.htmlInput_ = document.createElement('input'); + defInput.htmlInput_.setAttribute('data-old-value', 'proc name'); + defInput.htmlInput_.setAttribute('data-untyped-default-value', 'proc name'); + + defInput.htmlInput_.value = defInput.htmlInput_.getAttribute('data-old-value') + ' '; + defInput.onHtmlInputChange_(null); + chai.assert.equal( + this.defBlock.getFieldValue('NAME'), 'proc name'); + chai.assert.equal( + this.callBlock.getFieldValue('NAME'), 'proc name'); + }); + test('Whitespace then Text', function() { + const defInput = this.defBlock.getField('NAME'); + defInput.htmlInput_ = document.createElement('input'); + defInput.htmlInput_.setAttribute('data-old-value', 'proc name'); + defInput.htmlInput_.setAttribute('data-untyped-default-value', 'proc name'); + + defInput.htmlInput_.value = defInput.htmlInput_.getAttribute('data-old-value') + ' '; + defInput.onHtmlInputChange_(null); + defInput.htmlInput_.value = defInput.htmlInput_.getAttribute('data-old-value') + '2'; + defInput.onHtmlInputChange_(null); + chai.assert.equal( + this.defBlock.getFieldValue('NAME'), 'proc name 2'); + chai.assert.equal( + this.callBlock.getFieldValue('NAME'), 'proc name 2'); + }); + test('Set Empty', function() { + const defInput = this.defBlock.getField('NAME'); + defInput.htmlInput_ = document.createElement('input'); + defInput.htmlInput_.setAttribute('data-old-value', 'proc name'); + defInput.htmlInput_.setAttribute('data-untyped-default-value', 'proc name'); + + defInput.htmlInput_.value = ''; + defInput.onHtmlInputChange_(null); + chai.assert.equal( + this.defBlock.getFieldValue('NAME'), + Blockly.Msg['UNNAMED_KEY']); + chai.assert.equal( + this.callBlock.getFieldValue('NAME'), + Blockly.Msg['UNNAMED_KEY']); + }); + test('Set Empty, and Create New', function() { + const defInput = this.defBlock.getField('NAME'); + defInput.htmlInput_ = document.createElement('input'); + defInput.htmlInput_.setAttribute('data-old-value', 'proc name'); + defInput.htmlInput_.setAttribute('data-untyped-default-value', 'proc name'); + + defInput.htmlInput_.value = ''; + defInput.onHtmlInputChange_(null); + const newDefBlock = new Blockly.Block(this.workspace, testSuite.defType); + newDefBlock.setFieldValue('new name', 'NAME'); + chai.assert.equal( + this.defBlock.getFieldValue('NAME'), + Blockly.Msg['UNNAMED_KEY']); + chai.assert.equal( + this.callBlock.getFieldValue('NAME'), + Blockly.Msg['UNNAMED_KEY']); + }); + }); + suite('getCallers', function() { + setup(function() { + this.defBlock = new Blockly.Block(this.workspace, testSuite.defType); + this.defBlock.setFieldValue('proc name', 'NAME'); + this.callBlock = new Blockly.Block( + this.workspace, testSuite.callType); + this.callBlock.setFieldValue('proc name', 'NAME'); + }); + test('Simple', function() { + const callers = + Blockly.Procedures.getCallers('proc name', this.workspace); + chai.assert.equal(callers.length, 1); + chai.assert.equal(callers[0], this.callBlock); + }); + test('Multiple Callers', function() { + const caller2 = new Blockly.Block(this.workspace, testSuite.callType); + caller2.setFieldValue('proc name', 'NAME'); + const caller3 = new Blockly.Block(this.workspace, testSuite.callType); + caller3.setFieldValue('proc name', 'NAME'); + + const callers = + Blockly.Procedures.getCallers('proc name', this.workspace); + chai.assert.equal(callers.length, 3); + chai.assert.equal(callers[0], this.callBlock); + chai.assert.equal(callers[1], caller2); + chai.assert.equal(callers[2], caller3); + }); + test('Multiple Procedures', function() { + const def2 = new Blockly.Block(this.workspace, testSuite.defType); + def2.setFieldValue('proc name2', 'NAME'); + const caller2 = new Blockly.Block(this.workspace, testSuite.callType); + caller2.setFieldValue('proc name2', 'NAME'); + + const callers = + Blockly.Procedures.getCallers('proc name', this.workspace); + chai.assert.equal(callers.length, 1); + chai.assert.equal(callers[0], this.callBlock); + }); + // This can occur if you: + // 1) Create an uppercase definition and call block. + // 2) Delete both blocks. + // 3) Create a lowercase definition and call block. + // 4) Retrieve the uppercase call block from the trashcan. + // (And vise versa for creating lowercase blocks first) + // When converted to code all function names will be lowercase, so a + // caller should still be returned for a differently-cased procedure. + test('Call Different Case', function() { + this.callBlock.setFieldValue('PROC NAME', 'NAME'); + const callers = + Blockly.Procedures.getCallers('proc name', this.workspace); + chai.assert.equal(callers.length, 1); + chai.assert.equal(callers[0], this.callBlock); + }); + test('Multiple Workspaces', function() { + const workspace = new Blockly.Workspace(); + try { + const def2 = new Blockly.Block(workspace, testSuite.defType); + def2.setFieldValue('proc name', 'NAME'); + const caller2 = new Blockly.Block(workspace, testSuite.callType); + caller2.setFieldValue('proc name', 'NAME'); + + let callers = + Blockly.Procedures.getCallers('proc name', this.workspace); + chai.assert.equal(callers.length, 1); + chai.assert.equal(callers[0], this.callBlock); + + callers = Blockly.Procedures.getCallers('proc name', workspace); + chai.assert.equal(callers.length, 1); + chai.assert.equal(callers[0], caller2); + } finally { + workspaceTeardown.call(this, workspace); + } + }); + }); + suite('getDefinition', function() { + setup(function() { + this.defBlock = new Blockly.Block(this.workspace, testSuite.defType); + this.defBlock.setFieldValue('proc name', 'NAME'); + this.callBlock = new Blockly.Block( + this.workspace, testSuite.callType); + this.callBlock.setFieldValue('proc name', 'NAME'); + }); + test('Simple', function() { + const def = + Blockly.Procedures.getDefinition('proc name', this.workspace); + chai.assert.equal(def, this.defBlock); + }); + test('Multiple Procedures', function() { + const def2 = new Blockly.Block(this.workspace, testSuite.defType); + def2.setFieldValue('proc name2', 'NAME'); + const caller2 = new Blockly.Block(this.workspace, testSuite.callType); + caller2.setFieldValue('proc name2', 'NAME'); + + const def = + Blockly.Procedures.getDefinition('proc name', this.workspace); + chai.assert.equal(def, this.defBlock); + }); + test('Multiple Workspaces', function() { + const workspace = new Blockly.Workspace(); + try { + const def2 = new Blockly.Block(workspace, testSuite.defType); + def2.setFieldValue('proc name', 'NAME'); + const caller2 = new Blockly.Block(workspace, testSuite.callType); + caller2.setFieldValue('proc name', 'NAME'); + + let def = + Blockly.Procedures.getDefinition('proc name', this.workspace); + chai.assert.equal(def, this.defBlock); + + def = Blockly.Procedures.getDefinition('proc name', workspace); + chai.assert.equal(def, def2); + } finally { + workspaceTeardown.call(this, workspace); + } + }); + }); + + suite('Enable/Disable', function() { + setup(function() { + const toolbox = document.getElementById('toolbox-categories'); + this.workspaceSvg = Blockly.inject('blocklyDiv', {toolbox: toolbox}); + }); + teardown(function() { + workspaceTeardown.call(this, this.workspaceSvg); + }); + const domText = (testSuite.defType === 'procedures_defreturn') ? + ('' + + '' + + 'bar' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '') : + ('' + + '' + + 'bar' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + ''); + setup(function() { + const dom = Blockly.Xml.textToDom(domText); + + Blockly.Xml.appendDomToWorkspace(dom, this.workspaceSvg); + this.barDef = this.workspaceSvg.getBlockById('bar-def'); + this.barCalls = [ + this.workspaceSvg.getBlockById('bar-c1'), + this.workspaceSvg.getBlockById('bar-c2'), + ]; + }); + + test('Set disabled updates callers', function() { + this.workspaceSvg.clearUndo(); + Blockly.Events.setGroup('g1'); + this.barDef.setEnabled(false); + Blockly.Events.setGroup(false); + + for (let i = 0; i < 2; i++) { + chai.assert.isFalse(this.barCalls[i].isEnabled(), + 'Callers are disabled when their definition is disabled (call ' + + i + ')'); + } + const firedEvents = this.workspaceSvg.undoStack_; + chai.assert.equal(firedEvents.length, 3, + 'An event was fired for the definition and each caller'); + for (let i = 0; i < 3; i++) { + chai.assert.equal(firedEvents[i].group, 'g1', + 'Disable events are in the same group (event ' + i + ')'); + } + + this.workspaceSvg.clearUndo(); + Blockly.Events.setGroup('g2'); + this.barDef.setEnabled(true); + Blockly.Events.setGroup(false); + + for (let i = 0; i < 2; i++) { + chai.assert.isTrue(this.barCalls[i].isEnabled(), + 'Callers are enabled when their definition is enabled (call ' + + i + ')'); + } + chai.assert.equal(firedEvents.length, 3, + 'An event was fired for the definition and each caller'); + for (let i = 0; i < 3; i++) { + chai.assert.equal(firedEvents[i].group, 'g2', + 'Enable events are in the same group (event ' + i + ')'); + } + }); + test('Set disabled updates callers while remembering old caller state', function() { + this.barCalls[0].setEnabled(false); + this.workspaceSvg.clearUndo(); + Blockly.Events.setGroup('g1'); + this.barDef.setEnabled(false); + Blockly.Events.setGroup(false); + + for (let i = 0; i < 2; i++) { + chai.assert.isFalse(this.barCalls[i].isEnabled(), + 'Callers are disabled when their definition is disabled (call ' + + i + ')'); + } + const firedEvents = this.workspaceSvg.undoStack_; + chai.assert.equal(firedEvents.length, 2, + 'An event was fired for the definition and the enabled caller'); + for (let i = 0; i < 2; i++) { + chai.assert.equal(firedEvents[i].group, 'g1', + 'Disable events are in the same group (event ' + i + ')'); + } + + this.workspaceSvg.clearUndo(); + Blockly.Events.setGroup('g2'); + this.barDef.setEnabled(true); + Blockly.Events.setGroup(false); + + chai.assert.isFalse(this.barCalls[0].isEnabled(), + 'Caller remains in disabled state when the definition is enabled'); + chai.assert.isTrue(this.barCalls[1].isEnabled(), + 'Caller returns to previous enabled state when the definition is enabled'); + chai.assert.equal(firedEvents.length, 2, + 'An event was fired for the definition and the enabled caller'); + for (let i = 0; i < 2; i++) { + chai.assert.equal(firedEvents[i].group, 'g2', + 'Enable events are in the same group (event ' + i + ')'); + } + }); + }); + suite('Mutation', function() { + setup(function() { + this.defBlock = new Blockly.Block(this.workspace, testSuite.defType); + this.defBlock.setFieldValue('proc name', 'NAME'); + this.callBlock = new Blockly.Block( + this.workspace, testSuite.callType); + this.callBlock.setFieldValue('proc name', 'NAME'); + this.findParentStub = sinon.stub(Blockly.Mutator, 'findParentWs') + .returns(this.workspace); + }); + teardown(function() { + this.findParentStub.restore(); + }); + suite('Composition', function() { + suite('Statements', function() { + function setStatementValue(mainWorkspace, defBlock, value) { + const mutatorWorkspace = new Blockly.Workspace( + new Blockly.Options({ + parentWorkspace: mainWorkspace, + })); + defBlock.decompose(mutatorWorkspace); + const containerBlock = mutatorWorkspace.getTopBlocks()[0]; + const statementField = containerBlock.getField('STATEMENTS'); + statementField.setValue(value); + defBlock.compose(containerBlock); + } + if (testSuite.defType === 'procedures_defreturn') { + test('Has Statements', function() { + setStatementValue(this.workspace, this.defBlock, true); + chai.assert.isTrue(this.defBlock.hasStatements_); + }); + test('Has No Statements', function() { + setStatementValue(this.workspace, this.defBlock, false); + chai.assert.isFalse(this.defBlock.hasStatements_); + }); + test('Saving Statements', function() { + const blockXml = Blockly.Xml.textToDom( + '' + + ' ' + + ' ' + + ' ' + + '' + ); + const defBlock = Blockly.Xml.domToBlock(blockXml, this.workspace); + setStatementValue(this.workspace, defBlock, false); + chai.assert.isNull(defBlock.getInput('STACK')); + setStatementValue(this.workspace, defBlock, true); + chai.assert.isNotNull(defBlock.getInput('STACK')); + const statementBlocks = defBlock.getChildren(); + chai.assert.equal(statementBlocks.length, 1); + const block = statementBlocks[0]; + chai.assert.equal(block.type, 'procedures_ifreturn'); + chai.assert.equal(block.id, 'test'); + }); + } + }); + suite('Untyped Arguments', function() { + function createMutator(argArray) { + this.mutatorWorkspace = new Blockly.Workspace( + new Blockly.Options({ + parentWorkspace: this.workspace, + })); + this.containerBlock = this.defBlock.decompose(this.mutatorWorkspace); + this.connection = this.containerBlock.getInput('STACK').connection; + for (let i = 0; i < argArray.length; i++) { + this.argBlock = new Blockly.Block( + this.mutatorWorkspace, 'procedures_mutatorarg'); + this.argBlock.setFieldValue(argArray[i], 'NAME'); + this.connection.connect(this.argBlock.previousConnection); + this.connection = this.argBlock.nextConnection; + } + this.defBlock.compose(this.containerBlock); + } + function assertArgs(argArray) { + chai.assert.equal(this.defBlock.arguments_.length, argArray.length); + for (let i = 0; i < argArray.length; i++) { + chai.assert.equal(this.defBlock.arguments_[i], argArray[i]); + } + chai.assert.equal(this.callBlock.arguments_.length, argArray.length); + for (let i = 0; i < argArray.length; i++) { + chai.assert.equal(this.callBlock.arguments_[i], argArray[i]); + } + } + test('Simple Add Arg', function() { + const args = ['arg1']; + createMutator.call(this, args); + assertArgs.call(this, args); + }); + test('Multiple Args', function() { + const args = ['arg1', 'arg2', 'arg3']; + createMutator.call(this, args); + assertArgs.call(this, args); + }); + test('Simple Change Arg', function() { + createMutator.call(this, ['arg1']); + this.argBlock.setFieldValue('arg2', 'NAME'); + this.defBlock.compose(this.containerBlock); + assertArgs.call(this, ['arg2']); + }); + test('lower -> CAPS', function() { + createMutator.call(this, ['arg']); + this.argBlock.setFieldValue('ARG', 'NAME'); + this.defBlock.compose(this.containerBlock); + assertArgs.call(this, ['ARG']); + }); + test('CAPS -> lower', function() { + createMutator.call(this, ['ARG']); + this.argBlock.setFieldValue('arg', 'NAME'); + this.defBlock.compose(this.containerBlock); + assertArgs.call(this, ['arg']); + }); + // Test case for #1958 + test('Set Arg Empty', function() { + const args = ['arg1']; + createMutator.call(this, args); + this.argBlock.setFieldValue('', 'NAME'); + this.defBlock.compose(this.containerBlock); + assertArgs.call(this, args); + }); + test('Whitespace', function() { + const args = ['arg1']; + createMutator.call(this, args); + this.argBlock.setFieldValue(' ', 'NAME'); + this.defBlock.compose(this.containerBlock); + assertArgs.call(this, args); + }); + test('Whitespace and Text', function() { + createMutator.call(this, ['arg1']); + this.argBlock.setFieldValue(' text ', 'NAME'); + this.defBlock.compose(this.containerBlock); + assertArgs.call(this, ['text']); + }); + test('<>', function() { + const args = ['<>']; + createMutator.call(this, args); + assertArgs.call(this, args); + }); + }); + }); + suite('Decomposition', function() { + suite('Statements', function() { + if (testSuite.defType === 'procedures_defreturn') { + test('Has Statement Input', function() { + const mutatorWorkspace = new Blockly.Workspace( + new Blockly.Options({ + parentWorkspace: this.workspace, + })); + this.defBlock.decompose(mutatorWorkspace); + const statementInput = mutatorWorkspace.getTopBlocks()[0] + .getInput('STATEMENT_INPUT'); + chai.assert.isNotNull(statementInput); + }); + test('Has Statements', function() { + this.defBlock.hasStatements_ = true; + const mutatorWorkspace = new Blockly.Workspace( + new Blockly.Options({ + parentWorkspace: this.workspace, + })); + this.defBlock.decompose(mutatorWorkspace); + const statementValue = mutatorWorkspace.getTopBlocks()[0] + .getField('STATEMENTS').getValueBoolean(); + chai.assert.isTrue(statementValue); + }); + test('No Has Statements', function() { + this.defBlock.hasStatements_ = false; + const mutatorWorkspace = new Blockly.Workspace( + new Blockly.Options({ + parentWorkspace: this.workspace, + })); + this.defBlock.decompose(mutatorWorkspace); + const statementValue = mutatorWorkspace.getTopBlocks()[0] + .getField('STATEMENTS').getValueBoolean(); + chai.assert.isFalse(statementValue); + }); + } else { + test('Has no Statement Input', function() { + const mutatorWorkspace = new Blockly.Workspace( + new Blockly.Options({ + parentWorkspace: this.workspace, + })); + this.defBlock.decompose(mutatorWorkspace); + const statementInput = mutatorWorkspace.getTopBlocks()[0] + .getInput('STATEMENT_INPUT'); + chai.assert.isNull(statementInput); + }); + } + }); + suite('Untyped Arguments', function() { + function assertArguments(argumentsArray) { + this.defBlock.arguments_ = argumentsArray; + const mutatorWorkspace = new Blockly.Workspace( + new Blockly.Options({ + parentWorkspace: this.workspace, + })); + this.defBlock.decompose(mutatorWorkspace); + const argBlocks = mutatorWorkspace.getBlocksByType('procedures_mutatorarg'); + chai.assert.equal(argBlocks.length, argumentsArray.length); + + for (let i = 0; i < argumentsArray.length; i++) { + const argString = argumentsArray[i]; + const argBlockValue = argBlocks[i].getFieldValue('NAME'); + chai.assert.equal(argBlockValue, argString); + } + } + test('Simple Single Arg', function() { + assertArguments.call(this, ['arg']); + }); + test('Multiple Args', function() { + assertArguments.call(this, ['arg1', 'arg2']); + }); + test('<>', function() { + assertArguments.call(this, ['<>']); + }); + }); + }); + }); + /** + * Test cases for serialization tests. + * @type {Array} + */ + const testCases = [ + { + title: 'Minimal definition', + xml: '', + expectedXml: + '\n' + + ' unnamed\n' + + '', + assertBlockStructure: + (block) => { + assertDefBlockStructure(block, testSuite.hasReturn); + }, + }, + { + title: 'Common definition', + xml: + '' + + ' do something' + + '', + expectedXml: + '\n' + + ' do something\n' + + '', + assertBlockStructure: + (block) => { + assertDefBlockStructure(block, testSuite.hasReturn); + }, + }, + { + title: 'With vars definition', + xml: + '\n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' do something\n' + + '', + expectedXml: + '\n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' do something\n' + + '', + assertBlockStructure: + (block) => { + assertDefBlockStructure( + block, testSuite.hasReturn, ['x', 'y'], ['arg1', 'arg2']); + }, + }, + { + title: 'With pre-created vars definition', + xml: + '\n' + + ' \n' + + ' \n' + + ' \n' + + ' do something\n' + + '', + expectedXml: + '\n' + + ' \n' + + ' \n' + + ' \n' + + ' do something\n' + + '', + assertBlockStructure: + (block) => { + assertDefBlockStructure(block, testSuite.hasReturn, + ['preCreatedVar'], ['preCreatedVarId']); + }, + }, + { + title: 'With pre-created typed vars definition', + xml: + '\n' + + ' \n' + + ' \n' + + ' \n' + + ' do something\n' + + '', + expectedXml: + '\n' + + ' \n' + + ' \n' + + ' \n' + + ' do something\n' + + '', + assertBlockStructure: + (block) => { + assertDefBlockStructure(block, testSuite.hasReturn, + ['preCreatedTypedVar'], ['preCreatedTypedVarId']); + }, + }, + { + title: 'No statements definition', + xml: + '\n' + + ' \n' + + ' do something\n' + + '', + expectedXml: + '\n' + + ' \n' + + ' do something\n' + + '', + assertBlockStructure: + (block) => { + assertDefBlockStructure(block, true, [], [], false); + }, + }, + { + title: 'Minimal caller', + xml: '', + expectedXml: + '\n' + + ' \n' + + '', + assertBlockStructure: + (block) => { + assertCallBlockStructure(block); + }, + }, + { + title: 'Common caller', + xml: + '\n' + + ' \n' + + '', + expectedXml: + '\n' + + ' \n' + + '', + assertBlockStructure: + (block) => { + assertCallBlockStructure(block); + }, + }, + { + title: 'With pre-created vars caller', + xml: + '\n' + + ' \n' + + ' \n' + + ' \n' + + '', + expectedXml: + '\n' + + ' \n' + + ' \n' + + ' \n' + + '', + assertBlockStructure: + (block) => { + assertCallBlockStructure(block, ['preCreatedVar'], ['preCreatedVarId']); + }, + }, + ]; + runSerializationTestSuite(testCases); + }); + }); +}); + +suite('Procedures, dont auto fire events', function() { + setup(function() { + sharedTestSetup.call(this, {fireEventsNow: false}); + this.workspace = new Blockly.Workspace(); + }); + teardown(function() { + sharedTestTeardown.call(this); + }); + + const testSuites = [ + {title: 'procedures_defreturn', hasReturn: true, + defType: 'procedures_defreturn', callType: 'procedures_callreturn'}, + {title: 'procedures_defnoreturn', hasReturn: false, + defType: 'procedures_defnoreturn', callType: 'procedures_callnoreturn'}, + ]; + + testSuites.forEach((testSuite) => { + suite(testSuite.title, function() { + suite('Disposal', function() { + test('callers are disposed when definitions are disposed', function() { + this.defBlock = new Blockly.Block(this.workspace, testSuite.defType); + this.defBlock.setFieldValue('proc name', 'NAME'); + this.callerBlock = new Blockly.Block( + this.workspace, testSuite.callType); + this.callerBlock.setFieldValue('proc name', 'NAME'); + + // Run the clock now so that the create events get fired. If we fire + // it after disposing, a new procedure def will get created when + // the caller create event is heard. + this.clock.runAll(); + this.defBlock.dispose(); + this.clock.runAll(); + + chai.assert.isTrue(this.callerBlock.disposed); + }); + }); + }); + }); +}); diff --git a/tests/mocha/blocks/variables_test.js b/tests/mocha/blocks/variables_test.js new file mode 100644 index 00000000000..73747122086 --- /dev/null +++ b/tests/mocha/blocks/variables_test.js @@ -0,0 +1,142 @@ +/** + * @license + * Copyright 2020 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +goog.declareModuleId('Blockly.test.variables'); + +import {sharedTestSetup, sharedTestTeardown} from '../test_helpers/setup_teardown.js'; + + +suite('Variables', function() { + setup(function() { + sharedTestSetup.call(this); + this.workspace = new Blockly.Workspace(); + Blockly.defineBlocksWithJsonArray([{ + "type": "get_var_block", + "message0": "%1", + "args0": [ + { + "type": "field_variable", + "name": "VAR", + "variableTypes": ["", "type1", "type2"], + }, + ], + }]); + this.workspace.createVariable('foo', 'type1', '1'); + this.workspace.createVariable('bar', 'type1', '2'); + this.workspace.createVariable('baz', 'type1', '3'); + }); + + teardown(function() { + sharedTestTeardown.call(this); + }); + + /** + * Create a test get_var_block. + * Will fail if get_var_block isn't defined. + * @param {!Blockly.Workspace} workspace The workspace on which to create the + * block. + * @param {!string} variableId The id of the variable to reference. + * @return {!Blockly.Block} The created block. + */ + function createTestVarBlock(workspace, variableId) { + // Turn off events to avoid testing XML at the same time. + Blockly.Events.disable(); + const block = new Blockly.Block(workspace, 'get_var_block'); + block.inputList[0].fieldRow[0].setValue(variableId); + Blockly.Events.enable(); + return block; + } + + suite('allUsedVarModels', function() { + test('All used', function() { + createTestVarBlock(this.workspace, '1'); + createTestVarBlock(this.workspace, '2'); + createTestVarBlock(this.workspace, '3'); + + const result = Blockly.Variables.allUsedVarModels(this.workspace); + chai.assert.equal(result.length, 3, + 'Expected three variables in the list of used variables'); + }); + + test('Some unused', function() { + createTestVarBlock(this.workspace, '2'); + + const result = Blockly.Variables.allUsedVarModels(this.workspace); + chai.assert.equal(result.length, 1, + 'Expected one variable in the list of used variables'); + chai.assert.equal(result[0].getId(), '2', + 'Expected variable with ID 2 in the list of used variables'); + }); + + test('Var used twice', function() { + createTestVarBlock(this.workspace, '2'); + createTestVarBlock(this.workspace, '2'); + + const result = Blockly.Variables.allUsedVarModels(this.workspace); + // Using the same variable multiple times should not change the number of + // elements in the list. + chai.assert.equal(result.length, 1, + 'Expected one variable in the list of used variables'); + chai.assert.equal(result[0].getId(), '2', + 'Expected variable with ID 2 in the list of used variables'); + }); + + test('All unused', function() { + const result = Blockly.Variables.allUsedVarModels(this.workspace); + chai.assert.equal(result.length, 0, + 'Expected no variables in the list of used variables'); + }); + }); + + suite('getVariable', function() { + test('By id', function() { + const var1 = this.workspace.createVariable('name1', 'type1', 'id1'); + const var2 = this.workspace.createVariable('name2', 'type1', 'id2'); + const var3 = this.workspace.createVariable('name3', 'type2', 'id3'); + const result1 = Blockly.Variables.getVariable(this.workspace, 'id1'); + const result2 = Blockly.Variables.getVariable(this.workspace, 'id2'); + const result3 = Blockly.Variables.getVariable(this.workspace, 'id3'); + + chai.assert.equal(var1, result1); + chai.assert.equal(var2, result2); + chai.assert.equal(var3, result3); + }); + + test('By name and type', function() { + const var1 = this.workspace.createVariable('name1', 'type1', 'id1'); + const var2 = this.workspace.createVariable('name2', 'type1', 'id2'); + const var3 = this.workspace.createVariable('name3', 'type2', 'id3'); + const result1 = + Blockly.Variables.getVariable(this.workspace, null, 'name1', 'type1'); + const result2 = + Blockly.Variables.getVariable(this.workspace, null, 'name2', 'type1'); + const result3 = + Blockly.Variables.getVariable(this.workspace, null, 'name3', 'type2'); + + // Searching by name + type is correct. + chai.assert.equal(var1, result1); + chai.assert.equal(var2, result2); + chai.assert.equal(var3, result3); + }); + + test('Bad id with name and type fallback', function() { + const var1 = this.workspace.createVariable('name1', 'type1', 'id1'); + const var2 = this.workspace.createVariable('name2', 'type1', 'id2'); + const var3 = this.workspace.createVariable('name3', 'type2', 'id3'); + const result1 = + Blockly.Variables.getVariable(this.workspace, 'badId', 'name1', 'type1'); + const result2 = + Blockly.Variables.getVariable(this.workspace, 'badId', 'name2', 'type1'); + const result3 = + Blockly.Variables.getVariable(this.workspace, 'badId', 'name3', 'type2'); + + // Searching by ID failed, but falling back onto name + type is correct. + chai.assert.equal(var1, result1); + chai.assert.equal(var2, result2); + chai.assert.equal(var3, result3); + }); + }); +}); From 1268f4075c145118b7a13a6371035b147b1e54bd Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Tue, 16 Aug 2022 22:45:53 +0000 Subject: [PATCH 18/18] chore: enable remaining tests --- tests/mocha/index.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/mocha/index.html b/tests/mocha/index.html index c8bae8f5437..0c58062447e 100644 --- a/tests/mocha/index.html +++ b/tests/mocha/index.html @@ -87,12 +87,12 @@ 'Blockly.test.jsoSerialization', 'Blockly.test.json', 'Blockly.test.keydown', - //'Blockly.test.lists', + 'Blockly.test.lists', 'Blockly.test.logicTernary', 'Blockly.test.metrics', 'Blockly.test.mutator', 'Blockly.test.names', - //'Blockly.test.procedures', + 'Blockly.test.procedures', 'Blockly.test.registry', 'Blockly.test.serialization', 'Blockly.test.shortcutRegistry', @@ -103,7 +103,7 @@ 'Blockly.test.utils', 'Blockly.test.variableMap', 'Blockly.test.variableModel', - //'Blockly.test.variables', + 'Blockly.test.variables', 'Blockly.test.widgetDiv', 'Blockly.test.workspaceComment', 'Blockly.test.workspaceSvg',