From 093467aed1ed5ad386ed09de00f323893e96ad5d Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Wed, 18 Sep 2019 16:14:06 -0700 Subject: [PATCH] Added More Procedure Unit Tests (#2630) * Added more procedure unit tests. * Cleanup from rebase. * Cleanup. --- blocks/procedures.js | 3 + tests/mocha/index.html | 1 + tests/mocha/procedures_test.js | 331 ++++++++++++++++++++--------- tests/mocha/xml_procedures_test.js | 320 ++++++++++++++++++++++++++++ 4 files changed, 555 insertions(+), 100 deletions(-) create mode 100644 tests/mocha/xml_procedures_test.js diff --git a/blocks/procedures.js b/blocks/procedures.js index bc4933a99..040636142 100644 --- a/blocks/procedures.js +++ b/blocks/procedures.js @@ -483,6 +483,9 @@ Blockly.Blocks['procedures_mutatorcontainer'] = { this.setTooltip(Blockly.Msg['PROCEDURES_MUTATORCONTAINER_TOOLTIP']); this.contextMenu = false; }, + + // TODO: Move this to a validator on the arg blocks, that way it can be + // tested. /** * This will create & delete variables and in dialogs workspace to ensure * that when a new block is dragged out it will have a unique parameter name. diff --git a/tests/mocha/index.html b/tests/mocha/index.html index 49339b471..d254233ad 100644 --- a/tests/mocha/index.html +++ b/tests/mocha/index.html @@ -55,6 +55,7 @@ +
diff --git a/tests/mocha/procedures_test.js b/tests/mocha/procedures_test.js index 800521b04..b6fc418cb 100644 --- a/tests/mocha/procedures_test.js +++ b/tests/mocha/procedures_test.js @@ -123,7 +123,6 @@ suite('Procedures', function() { defInput.htmlInput_.value = 'start name'; defInput.onHtmlInputChange_(null); - console.log(this.defType); chai.assert.equal(this.defBlock.getFieldValue('NAME'), 'start name'); chai.assert.equal(this.callBlock.getFieldValue('NAME'), 'start name'); }, 'START NAME'); @@ -312,7 +311,7 @@ suite('Procedures', function() { }, 'name'); }); }); - suite('Arguments', function() { + suite('Mutation', function() { setup(function() { this.findParentStub = sinon.stub(Blockly.Mutator, 'findParentWs') .returns(this.workspace); @@ -320,110 +319,242 @@ suite('Procedures', function() { teardown(function() { this.findParentStub.restore(); }); - suite('Untyped Arguments', function() { - function createMutator(argArray) { - this.mutatorWorkspace = new Blockly.Workspace(); - this.containerBlock = this.defBlock.decompose(this.mutatorWorkspace); - this.connection = this.containerBlock.getInput('STACK').connection; - for (var 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; + suite('Composition', function() { + suite('Statements', function() { + function setStatementValue(defBlock, value) { + var mutatorWorkspace = new Blockly.Workspace(); + defBlock.decompose(mutatorWorkspace); + var containerBlock = mutatorWorkspace.getTopBlocks()[0]; + var statementField = containerBlock.getField('STATEMENTS'); + statementField.setValue(value); + defBlock.compose(containerBlock); } - this.defBlock.compose(this.containerBlock); - } - function assertArgs(argArray) { - chai.assert.equal(this.defBlock.arguments_.length, argArray.length); - for (var i = 0; i < argArray.length; i++) { - chai.assert.equal(this.defBlock.arguments_[i], argArray[i]); + test('Has Statements', function() { + var defBlock = new Blockly.Block(this.workspace, 'procedures_defreturn'); + setStatementValue(defBlock, true); + chai.assert.isTrue(defBlock.hasStatements_); + }); + test('Has No Statements', function() { + var defBlock = new Blockly.Block(this.workspace, 'procedures_defreturn'); + setStatementValue(defBlock, false); + chai.assert.isFalse(defBlock.hasStatements_); + }); + test('Saving Statements', function() { + var blockXml = Blockly.Xml.textToDom( + '' + + ' ' + + ' ' + + ' ' + + '' + ); + var defBlock = Blockly.Xml.domToBlock(blockXml, this.workspace); + setStatementValue(defBlock, false); + chai.assert.isNull(defBlock.getInput('STACK')); + setStatementValue(defBlock, true); + chai.assert.isNotNull(defBlock.getInput('STACK')); + var statementBlocks = defBlock.getChildren(); + chai.assert.equal(statementBlocks.length, 1); + var 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(); + this.containerBlock = this.defBlock.decompose(this.mutatorWorkspace); + this.connection = this.containerBlock.getInput('STACK').connection; + for (var 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); } - chai.assert.equal(this.callBlock.arguments_.length, argArray.length); - for (var i = 0; i < argArray.length; i++) { - chai.assert.equal(this.callBlock.arguments_[i], argArray[i]); + function assertArgs(argArray) { + chai.assert.equal(this.defBlock.arguments_.length, argArray.length); + for (var 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 (var i = 0; i < argArray.length; i++) { + chai.assert.equal(this.callBlock.arguments_[i], argArray[i]); + } } - } - function clearVariables() { - // TODO: Update this for typed vars. - var variables = this.workspace.getVariablesOfType(''); - var variableMap = this.workspace.getVariableMap(); - for (var i = 0, variable; variable = variables[i]; i++) { - variableMap.deleteVariable(variable); + function clearVariables() { + // TODO: Update this for typed vars. + var variables = this.workspace.getVariablesOfType(''); + var variableMap = this.workspace.getVariableMap(); + for (var i = 0, variable; variable = variables[i]; i++) { + variableMap.deleteVariable(variable); + } } - } - test('Simple Add Arg', function() { - this.callForAllTypes(function() { - var args = ['arg1']; - createMutator.call(this, args); - assertArgs.call(this, args); - clearVariables.call(this); - }, 'name'); + test('Simple Add Arg', function() { + this.callForAllTypes(function() { + var args = ['arg1']; + createMutator.call(this, args); + assertArgs.call(this, args); + clearVariables.call(this); + }, 'name'); + }); + // TODO: Reenable the following two once arg name validation has been + // moved to the arg blocks. + test.skip('Add Identical Arg', function() { + this.callForAllTypes(function() { + var args = ['x', 'x']; + createMutator.call(this, args); + assertArgs.call(this, ['x', 'i']); + clearVariables.call(this); + }); + }); + test.skip('Add Identical (except case) Arg', function() { + this.callForAllTypes(function() { + var args = ['x', 'X']; + createMutator.call(this, args); + assertArgs.call(this, ['x', 'i']); + clearVariables.call(this); + }); + }); + test('Multiple Args', function() { + this.callForAllTypes(function() { + var args = ['arg1', 'arg2', 'arg3']; + createMutator.call(this, args); + assertArgs.call(this, args); + clearVariables.call(this); + }, 'name'); + }); + test('Simple Change Arg', function() { + this.callForAllTypes(function() { + createMutator.call(this, ['arg1']); + this.argBlock.setFieldValue('arg2', 'NAME'); + this.defBlock.compose(this.containerBlock); + assertArgs.call(this, ['arg2']); + clearVariables.call(this); + }, 'name'); + }); + test('lower -> CAPS', function() { + this.callForAllTypes(function() { + createMutator.call(this, ['arg']); + this.argBlock.setFieldValue('ARG', 'NAME'); + this.defBlock.compose(this.containerBlock); + assertArgs.call(this, ['ARG']); + clearVariables.call(this); + }, 'name'); + }); + test('CAPS -> lower', function() { + this.callForAllTypes(function() { + createMutator.call(this, ['ARG']); + this.argBlock.setFieldValue('arg', 'NAME'); + this.defBlock.compose(this.containerBlock); + assertArgs.call(this, ['arg']); + clearVariables.call(this); + }, 'name'); + }); + // Test case for #1958 + test('Set Arg Empty', function() { + this.callForAllTypes(function() { + var args = ['arg1']; + createMutator.call(this, args); + this.argBlock.setFieldValue('', 'NAME'); + this.defBlock.compose(this.containerBlock); + assertArgs.call(this, args); + clearVariables.call(this); + }, 'name'); + }); + test('Whitespace', function() { + this.callForAllTypes(function() { + var args = ['arg1']; + createMutator.call(this, args); + this.argBlock.setFieldValue(' ', 'NAME'); + this.defBlock.compose(this.containerBlock); + assertArgs.call(this, args); + clearVariables.call(this); + }, 'name'); + }); + test('Whitespace and Text', function() { + this.callForAllTypes(function() { + createMutator.call(this, ['arg1']); + this.argBlock.setFieldValue(' text ', 'NAME'); + this.defBlock.compose(this.containerBlock); + assertArgs.call(this, ['text']); + clearVariables.call(this); + }, 'name'); + }); + test('<>', function() { + this.callForAllTypes(function() { + var args = ['<>']; + createMutator.call(this, args); + assertArgs.call(this, args); + clearVariables.call(this); + }, 'name'); + }); }); - test('Multiple Args', function() { - this.callForAllTypes(function() { - var args = ['arg1', 'arg2', 'arg3']; - createMutator.call(this, args); - assertArgs.call(this, args); - clearVariables.call(this); - }, 'name'); + }); + suite('Decomposition', function() { + suite('Statements', function() { + test('Has Statement Input', function() { + this.callForAllTypes(function() { + var mutatorWorkspace = new Blockly.Workspace(); + this.defBlock.decompose(mutatorWorkspace); + var statementInput = mutatorWorkspace.getTopBlocks()[0] + .getInput('STATEMENT_INPUT'); + if (this.defType == 'procedures_defreturn') { + chai.assert.isNotNull(statementInput); + } else { + chai.assert.isNull(statementInput); + } + }, 'name'); + }); + test('Has Statements', function() { + var defBlock = new Blockly.Block(this.workspace, 'procedures_defreturn'); + defBlock.hasStatements_ = true; + var mutatorWorkspace = new Blockly.Workspace(); + defBlock.decompose(mutatorWorkspace); + var statementValue = mutatorWorkspace.getTopBlocks()[0] + .getField('STATEMENTS').getValueBoolean(); + chai.assert.isTrue(statementValue); + }); + test('No Has Statements', function() { + var defBlock = new Blockly.Block(this.workspace, 'procedures_defreturn'); + defBlock.hasStatements_ = false; + var mutatorWorkspace = new Blockly.Workspace(); + defBlock.decompose(mutatorWorkspace); + var statementValue = mutatorWorkspace.getTopBlocks()[0] + .getField('STATEMENTS').getValueBoolean(); + chai.assert.isFalse(statementValue); + }); }); - test('Simple Change Arg', function() { - this.callForAllTypes(function() { - createMutator.call(this, ['arg1']); - this.argBlock.setFieldValue('arg2', 'NAME'); - this.defBlock.compose(this.containerBlock); - assertArgs.call(this, ['arg2']); - clearVariables.call(this); - }, 'name'); - }); - test('lower -> CAPS', function() { - this.callForAllTypes(function() { - createMutator.call(this, ['arg']); - this.argBlock.setFieldValue('ARG', 'NAME'); - this.defBlock.compose(this.containerBlock); - assertArgs.call(this, ['ARG']); - clearVariables.call(this); - }, 'name'); - }); - test('CAPS -> lower', function() { - this.callForAllTypes(function() { - createMutator.call(this, ['ARG']); - this.argBlock.setFieldValue('arg', 'NAME'); - this.defBlock.compose(this.containerBlock); - assertArgs.call(this, ['arg']); - clearVariables.call(this); - }, 'name'); - }); - // Test case for #1958 - test('Set Arg Empty', function() { - this.callForAllTypes(function() { - var args = ['arg1']; - createMutator.call(this, args); - this.argBlock.setFieldValue('', 'NAME'); - this.defBlock.compose(this.containerBlock); - assertArgs.call(this, args); - clearVariables.call(this); - }, 'name'); - }); - test('Whitespace', function() { - this.callForAllTypes(function() { - var args = ['arg1']; - createMutator.call(this, args); - this.argBlock.setFieldValue(' ', 'NAME'); - this.defBlock.compose(this.containerBlock); - assertArgs.call(this, args); - clearVariables.call(this); - }, 'name'); - }); - test('Whitespace and Text', function() { - this.callForAllTypes(function() { - createMutator.call(this, ['arg1']); - this.argBlock.setFieldValue(' text ', 'NAME'); - this.defBlock.compose(this.containerBlock); - assertArgs.call(this, ['text']); - clearVariables.call(this); - }, 'name'); + suite('Untyped Arguments', function() { + function assertArguments(argumentsArray) { + this.defBlock.arguments_ = argumentsArray; + var mutatorWorkspace = new Blockly.Workspace(); + this.defBlock.decompose(mutatorWorkspace); + var argBlocks = mutatorWorkspace.getBlocksByType('procedures_mutatorarg'); + chai.assert.equal(argBlocks.length, argumentsArray.length); + + for (var i = 0; i < argumentsArray.length; i++) { + var argString = argumentsArray[i]; + var argBlockValue = argBlocks[i].getFieldValue('NAME'); + chai.assert.equal(argBlockValue, argString); + } + } + test('Simple Single Arg', function() { + this.callForAllTypes(function() { + assertArguments.call(this, ['arg']); + }, 'name'); + }); + test('Multiple Args', function() { + this.callForAllTypes(function() { + assertArguments.call(this, ['arg1', 'arg2']); + }, 'name'); + }); + test('<>', function() { + this.callForAllTypes(function() { + assertArguments.call(this, ['<>']); + }, 'name'); + }); }); }); }); diff --git a/tests/mocha/xml_procedures_test.js b/tests/mocha/xml_procedures_test.js new file mode 100644 index 000000000..f720c9f82 --- /dev/null +++ b/tests/mocha/xml_procedures_test.js @@ -0,0 +1,320 @@ +/** + * @license + * Blockly Tests + * + * Copyright 2019 Google Inc. + * https://developers.google.com/blockly/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +goog.require('Blockly.Blocks.procedures'); +goog.require('Blockly.Msg.en'); + +suite('Procedures XML', function() { + suite('Deserialization', function() { + setup(function() { + Blockly.setTheme(new Blockly.Theme({ + "procedure_blocks": { + "colourPrimary": "290" + } + })); + this.workspace = new Blockly.Workspace(); + + this.callForAllTypes = function(func) { + var typesArray = [ + ['procedures_defnoreturn', 'procedures_callnoreturn'], + ['procedures_defreturn', 'procedures_callreturn'] + ]; + + for (var i = 0, types; types = typesArray[i]; i++) { + var context = Object.create(null); + context.workspace = this.workspace; + context.defType = types[0]; + context.callType = types[1]; + + func.call(context); + + this.workspace.clear(); + } + }; + }); + teardown(function() { + this.workspace.dispose(); + }); + + suite('Definition Blocks', function() { + test('Minimal', function() { + this.callForAllTypes(function() { + var xml = Blockly.Xml.textToDom( + '' + ); + var block = Blockly.Xml.domToBlock(xml, this.workspace); + + // TODO: Is this how you want this to work? or do you want it to + // be 'unnamed'? + chai.assert.equal(block.getFieldValue('NAME'), ''); + chai.assert.isArray(block.arguments_); + chai.assert.isEmpty(block.arguments_); + chai.assert.isArray(block.argumentVarModels_); + chai.assert.isEmpty(block.argumentVarModels_); + chai.assert.isNotNull(block.getInput('STACK')); + }); + }); + // This is like what's in the toolbox. + test('Common', function() { + this.callForAllTypes(function() { + var xml = Blockly.Xml.textToDom( + '' + + ' do something' + + '' + ); + var block = Blockly.Xml.domToBlock(xml, this.workspace); + + chai.assert.equal( + block.getFieldValue('NAME'), + 'do something'); + chai.assert.isArray(block.arguments_); + chai.assert.isEmpty(block.arguments_); + chai.assert.isArray(block.argumentVarModels_); + chai.assert.isEmpty(block.argumentVarModels_); + chai.assert.isNotNull(block.getInput('STACK')); + }); + }); + test('Arg Vars Pre-Created', function() { + this.callForAllTypes(function() { + this.workspace.createVariable('x', '', 'arg'); + var xml = Blockly.Xml.textToDom( + '' + + ' do something' + + ' ' + + ' ' + + ' ' + + '' + ); + var block = Blockly.Xml.domToBlock(xml, this.workspace); + + chai.assert.equal( + block.getFieldValue('NAME'), + 'do something'); + chai.assert.deepEqual(block.arguments_, ['x']); + chai.assert.deepEqual(block.argumentVarModels_, + [this.workspace.getVariableById('arg')]); + chai.assert.isNotNull(block.getInput('STACK')); + }); + }); + test('Arg Vars Not Created', function() { + this.callForAllTypes(function() { + var xml = Blockly.Xml.textToDom( + '' + + ' do something' + + ' ' + + ' ' + + ' ' + + '' + ); + var block = Blockly.Xml.domToBlock(xml, this.workspace); + + chai.assert.equal( + block.getFieldValue('NAME'), + 'do something'); + chai.assert.deepEqual(block.arguments_, ['x']); + chai.assert.deepEqual(block.argumentVarModels_, + [this.workspace.getVariableById('arg')]); + chai.assert.isNotNull(block.getInput('STACK')); + }); + }); + // TODO: I don't know a lot about typing vars, and even less out it in + // this context. Is allowing typed vars to be args the correct behavior? + test('Arg Vars Pre-Created Typed', function() { + this.callForAllTypes(function() { + this.workspace.createVariable('x', 'type', 'arg'); + var xml = Blockly.Xml.textToDom( + '' + + ' do something' + + ' ' + + ' ' + + ' ' + + '' + ); + var block = Blockly.Xml.domToBlock(xml, this.workspace); + + chai.assert.equal( + block.getFieldValue('NAME'), + 'do something'); + chai.assert.deepEqual(block.arguments_, ['x']); + chai.assert.deepEqual(block.argumentVarModels_, + [this.workspace.getVariableById('arg')]); + chai.assert.isNotNull(block.getInput('STACK')); + }); + }); + test('Statements False', function() { + var xml = Blockly.Xml.textToDom( + '' + + ' do something' + + ' ' + + '' + ); + var block = Blockly.Xml.domToBlock(xml, this.workspace); + + chai.assert.equal( + block.getFieldValue('NAME'), + 'do something'); + chai.assert.isArray(block.arguments_); + chai.assert.isEmpty(block.arguments_); + chai.assert.isArray(block.argumentVarModels_); + chai.assert.isEmpty(block.argumentVarModels_); + chai.assert.isNull(block.getInput('STACK')); + }); + test('Statements True', function() { + var xml = Blockly.Xml.textToDom( + '' + + ' do something' + + ' ' + + '' + ); + var block = Blockly.Xml.domToBlock(xml, this.workspace); + + chai.assert.equal( + block.getFieldValue('NAME'), + 'do something'); + chai.assert.isArray(block.arguments_); + chai.assert.isEmpty(block.arguments_); + chai.assert.isArray(block.argumentVarModels_); + chai.assert.isEmpty(block.argumentVarModels_); + chai.assert.isNotNull(block.getInput('STACK')); + }); + }); + suite('Call Blocks', function() { + test('Caller W/ Def', function() { + this.callForAllTypes(function() { + Blockly.Xml.domToBlock(Blockly.Xml.textToDom( + '' + + ' do something' + + '' + ), this.workspace); + var callerXML = Blockly.Xml.textToDom( + '' + + ' ' + + '' + ); + var block = Blockly.Xml.domToBlock(callerXML, this.workspace); + + chai.assert.equal( + block.getFieldValue('NAME'), + 'do something'); + chai.assert.isArray(block.arguments_); + chai.assert.isEmpty(block.arguments_); + // TODO: argumentVarModels_ is undefined for call_return, but + // defined for call_noreturn. Make it defined for both. + /*chai.assert.isArray(block.argumentVarModels_); + chai.assert.isEmpty(block.argumentVarModels_);*/ + }); + }); + // TODO: I couldn't get this test (of creating a definition) to work + // b/c of the events delay. + test.skip('Caller No Def', function() { + this.callForAllTypes(function() { + var callerXML = Blockly.Xml.textToDom( + '' + + ' ' + + '' + ); + var block = Blockly.Xml.domToBlock(callerXML, this.workspace); + + chai.assert.equal( + block.getFieldValue('NAME'), + 'do something'); + chai.assert.isArray(block.arguments_); + chai.assert.isEmpty(block.arguments_); + // TODO: argumentVarModels_ is undefined for call_return, but + // defined for call_noreturn. Make it defined for both. + /*chai.assert.isArray(block.argumentVarModels_); + chai.assert.isEmpty(block.argumentVarModels_);*/ + chai.assert.equal(this.workspace.getAllBlocks().count, 2); + }); + }); + test('Caller W/ Params', function() { + this.callForAllTypes(function() { + Blockly.Xml.domToBlock(Blockly.Xml.textToDom( + '' + + ' do something' + + ' ' + + ' ' + + ' ' + + '' + ), this.workspace); + var callerXML = Blockly.Xml.textToDom( + '' + + ' ' + + ' ' + + ' ' + + '' + ); + var block = Blockly.Xml.domToBlock(callerXML, this.workspace); + + chai.assert.equal( + block.getFieldValue('NAME'), + 'do something'); + chai.assert.deepEqual(block.arguments_, ['x']); + chai.assert.deepEqual(block.argumentVarModels_, + [this.workspace.getVariableById('arg')]); + }); + }); + // TODO: How do you want it to behave in this situation? + test.skip('Caller W/out Params', function() { + this.callForAllTypes(function() { + Blockly.Xml.domToBlock(Blockly.Xml.textToDom( + '' + + ' do something' + + ' ' + + ' ' + + ' ' + + '' + ), this.workspace); + var callerXML = Blockly.Xml.textToDom( + '' + + ' ' + + '' + ); + // TODO: Remove this when you fix this test. + // eslint-disable-next-line no-unused-vars + var block = Blockly.Xml.domToBlock(callerXML, this.workspace); + }); + }); + // TODO: How do you want it to behave in this situation? + test.skip('Caller W/ Bad Params', function() { + this.callForAllTypes(function() { + Blockly.Xml.domToBlock(Blockly.Xml.textToDom( + '' + + ' do something' + + ' ' + + ' ' + + ' ' + + '' + ), this.workspace); + var callerXML = Blockly.Xml.textToDom( + '' + + ' ' + + ' ' + + ' ' + + '' + ); + // TODO: Remove this when you fix this test. + // eslint-disable-next-line no-unused-vars + var block = Blockly.Xml.domToBlock(callerXML, this.workspace); + }); + }); + }); + }); +});