Files
blockly/tests/mocha/blocks/procedures_test.js
Christopher Allen 6f20ac290d refactor(tests): Use import instead of goog.bootstrap to load Blockly in mocha tests (#7406)
* fix(build): Have buildShims clean up up after itself

  We need to create a build/package.json file to allow node.js to
  load build/src/core/blockly.js and the other chunk entry points
  as ES modules (it forcibly assumes .js means CJS even if one is
  trying to import, unless package.json says {"type": "module"}),
  but this interferes with scripts/migration/js2ts doing a
  require('build/deps.js'), which is _not_ an ES module.

  Specific error message was:

  /Users/cpcallen/src/blockly/scripts/migration/js2ts:56
  require(path.resolve(__dirname, '../../build/deps.js'));
  ^

  Error [ERR_REQUIRE_ESM]: require() of ES Module
  /Users/cpcallen/src/blockly/build/deps.js from /Users/cpcallen/src/blockly/scripts/migration/js2ts
  not supported.
  deps.js is treated as an ES module file as it is a .js file whose
  nearest parent package.json contains "type": "module" which
  declares all .js files in that package scope as ES modules.
  Instead rename deps.js to end in .cjs, change the requiring code
  to use dynamic import() which is available in all CommonJS
  modules, or change "type": "module" to "type": "commonjs" in
  /Users/cpcallen/src/blockly/build/package.json to treat all .js
  files as CommonJS (using .mjs for all ES modules instead).

      at Object.<anonymous> (/Users/cpcallen/src/blockly/scripts/migration/js2ts:56:1) {
    code: 'ERR_REQUIRE_ESM'
  }

* chore(tests): Reorder to put interesting script nearer top of file

* chore(tests): Add missing imports of closure/goog/goog.js

  These modules were depending on being loaded via the
  debug module loader, which cannot be used without first loading
  base.js as a script, and thereby defining goog.declareModuleId
  as a side effect—but if they are to be loaded via direct import
  statements then they need to actually import their own
  dependencies.

  This is a temporary measure as soon the goog.declareMouleId
  calls can themselves be deleted.

* refactor(tests): Use import instead of bootstrap to load Blockly

* chores(build): Stop generating deps.mocha.js

  This file was only needed by tests/mocha/index.html's use of
  the debug module loader (via bootstrap.js), which has now been
  removed.

* chore(tests): Remove unneeded goog.declareModuleId calls

  These were only needed because these modules were previously
  being loaded by goog.require and/or goog.bootstrap.

* chores(tests): Remove dead code

  We are fully committed to proper modules now.
2023-08-18 18:06:52 +01:00

2455 lines
86 KiB
JavaScript

/**
* @license
* Copyright 2019 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import * as Blockly from '../../../build/src/core/blockly.js';
import {
assertCallBlockStructure,
assertDefBlockStructure,
createProcDefBlock,
createProcCallBlock,
MockProcedureModel,
} from '../test_helpers/procedures.js';
import {runSerializationTestSuite} from '../test_helpers/serialization.js';
import {
createGenUidStubWithReturns,
sharedTestSetup,
sharedTestTeardown,
workspaceTeardown,
} from '../test_helpers/setup_teardown.js';
import {defineRowBlock} from '../test_helpers/block_definitions.js';
suite('Procedures', function () {
setup(function () {
sharedTestSetup.call(this, {fireEventsNow: false});
this.workspace = Blockly.inject('blocklyDiv', {});
this.workspace.createVariable('preCreatedVar', '', 'preCreatedVarId');
this.workspace.createVariable(
'preCreatedTypedVar',
'type',
'preCreatedTypedVarId',
);
defineRowBlock();
});
teardown(function () {
sharedTestTeardown.call(this);
});
suite('renaming procedures', function () {
test('callers are updated to have the new name', function () {
const defBlock = createProcDefBlock(this.workspace);
const callBlock = createProcCallBlock(this.workspace);
defBlock.setFieldValue('new name', 'NAME');
chai.assert.equal(
callBlock.getFieldValue('NAME'),
'new name',
'Expected the procedure block to be renamed',
);
});
test(
'setting an illegal name results in both the ' +
'procedure and the caller getting the legal name',
function () {
createProcDefBlock(this.workspace, undefined, undefined, 'procA');
const defBlockB = createProcDefBlock(
this.workspace,
undefined,
undefined,
'procB',
);
const callBlockB = createProcCallBlock(
this.workspace,
undefined,
'procB',
);
defBlockB.setFieldValue('procA', 'NAME');
chai.assert.notEqual(
defBlockB.getFieldValue('NAME'),
'procA',
'Expected the procedure def block to have a legal name',
);
chai.assert.notEqual(
callBlockB.getFieldValue('NAME'),
'procA',
'Expected the procedure call block to have a legal name',
);
},
);
});
suite('adding procedure parameters', function () {
test('the mutator flyout updates to avoid parameter name conflicts', function () {
const defBlock = createProcDefBlock(this.workspace);
const mutatorIcon = defBlock.getIcon(Blockly.icons.MutatorIcon.TYPE);
mutatorIcon.setBubbleVisible(true);
const mutatorWorkspace = mutatorIcon.getWorkspace();
const origFlyoutParamName = mutatorWorkspace
.getFlyout()
.getWorkspace()
.getTopBlocks(true)[0]
.getFieldValue('NAME');
Blockly.serialization.blocks.append(
{
'type': 'procedures_mutatorarg',
'fields': {
'NAME': origFlyoutParamName,
},
},
mutatorWorkspace,
);
this.clock.runAll();
const newFlyoutParamName = mutatorWorkspace
.getFlyout()
.getWorkspace()
.getTopBlocks(true)[0]
.getFieldValue('NAME');
chai.assert.notEqual(
newFlyoutParamName,
origFlyoutParamName,
'Expected the flyout param to have updated to not conflict',
);
});
test('adding a parameter to the procedure updates procedure defs', function () {
// Create a stack of container, parameter.
const defBlock = createProcDefBlock(this.workspace);
const mutatorIcon = defBlock.getIcon(Blockly.icons.MutatorIcon.TYPE);
mutatorIcon.setBubbleVisible(true);
const mutatorWorkspace = mutatorIcon.getWorkspace();
const containerBlock = mutatorWorkspace.getTopBlocks()[0];
const paramBlock = mutatorWorkspace.newBlock('procedures_mutatorarg');
paramBlock.setFieldValue('param1', 'NAME');
containerBlock
.getInput('STACK')
.connection.connect(paramBlock.previousConnection);
this.clock.runAll();
chai.assert.isNotNull(
defBlock.getField('PARAMS'),
'Expected the params field to exist',
);
chai.assert.isTrue(
defBlock.getFieldValue('PARAMS').includes('param1'),
'Expected the params field to contain the name of the new param',
);
});
test('adding a parameter to the procedure updates procedure callers', function () {
// Create a stack of container, parameter.
const defBlock = createProcDefBlock(this.workspace);
const callBlock = createProcCallBlock(this.workspace);
const mutatorIcon = defBlock.getIcon(Blockly.icons.MutatorIcon.TYPE);
mutatorIcon.setBubbleVisible(true);
const mutatorWorkspace = mutatorIcon.getWorkspace();
const containerBlock = mutatorWorkspace.getTopBlocks()[0];
const paramBlock = mutatorWorkspace.newBlock('procedures_mutatorarg');
paramBlock.setFieldValue('param1', 'NAME');
containerBlock
.getInput('STACK')
.connection.connect(paramBlock.previousConnection);
this.clock.runAll();
chai.assert.isNotNull(
callBlock.getInput('ARG0'),
'Expected the param input to exist',
);
chai.assert.equal(
callBlock.getFieldValue('ARGNAME0'),
'param1',
'Expected the params field to match the name of the new param',
);
});
test('undoing adding a procedure parameter removes it', function () {
// Create a stack of container, parameter.
const defBlock = createProcDefBlock(this.workspace);
const mutatorIcon = defBlock.getIcon(Blockly.icons.MutatorIcon.TYPE);
mutatorIcon.setBubbleVisible(true);
const mutatorWorkspace = mutatorIcon.getWorkspace();
const containerBlock = mutatorWorkspace.getTopBlocks()[0];
const paramBlock = mutatorWorkspace.newBlock('procedures_mutatorarg');
paramBlock.setFieldValue('param1', 'NAME');
containerBlock
.getInput('STACK')
.connection.connect(paramBlock.previousConnection);
this.clock.runAll();
this.workspace.undo();
chai.assert.isFalse(
defBlock.getFieldValue('PARAMS').includes('param1'),
'Expected the params field to not contain the name of the new param',
);
});
test(
'undoing and redoing adding a procedure parameter maintains ' +
'the same state',
function () {
// Create a stack of container, parameter.
const defBlock = createProcDefBlock(this.workspace);
const mutatorIcon = defBlock.getIcon(Blockly.icons.MutatorIcon.TYPE);
mutatorIcon.setBubbleVisible(true);
const mutatorWorkspace = mutatorIcon.getWorkspace();
const containerBlock = mutatorWorkspace.getTopBlocks()[0];
const paramBlock = mutatorWorkspace.newBlock('procedures_mutatorarg');
paramBlock.setFieldValue('param1', 'NAME');
containerBlock
.getInput('STACK')
.connection.connect(paramBlock.previousConnection);
this.clock.runAll();
this.workspace.undo();
this.workspace.undo(/* redo= */ true);
chai.assert.isNotNull(
defBlock.getField('PARAMS'),
'Expected the params field to exist',
);
chai.assert.isTrue(
defBlock.getFieldValue('PARAMS').includes('param1'),
'Expected the params field to contain the name of the new param',
);
},
);
});
suite('deleting procedure parameters', function () {
test('deleting a parameter from the procedure updates procedure defs', function () {
// Create a stack of container, parameter.
const defBlock = createProcDefBlock(this.workspace);
const mutatorIcon = defBlock.getIcon(Blockly.icons.MutatorIcon.TYPE);
mutatorIcon.setBubbleVisible(true);
const mutatorWorkspace = mutatorIcon.getWorkspace();
const containerBlock = mutatorWorkspace.getTopBlocks()[0];
const paramBlock = mutatorWorkspace.newBlock('procedures_mutatorarg');
paramBlock.setFieldValue('param1', 'NAME');
containerBlock
.getInput('STACK')
.connection.connect(paramBlock.previousConnection);
this.clock.runAll();
paramBlock.checkAndDelete();
this.clock.runAll();
chai.assert.isFalse(
defBlock.getFieldValue('PARAMS').includes('param1'),
'Expected the params field to not contain the name of the new param',
);
});
test('deleting a parameter from the procedure udpates procedure callers', function () {
// Create a stack of container, parameter.
const defBlock = createProcDefBlock(this.workspace);
const callBlock = createProcCallBlock(this.workspace);
const mutatorIcon = defBlock.getIcon(Blockly.icons.MutatorIcon.TYPE);
mutatorIcon.setBubbleVisible(true);
const mutatorWorkspace = mutatorIcon.getWorkspace();
const containerBlock = mutatorWorkspace.getTopBlocks()[0];
const paramBlock = mutatorWorkspace.newBlock('procedures_mutatorarg');
paramBlock.setFieldValue('param1', 'NAME');
containerBlock
.getInput('STACK')
.connection.connect(paramBlock.previousConnection);
this.clock.runAll();
paramBlock.checkAndDelete();
this.clock.runAll();
chai.assert.isNull(
callBlock.getInput('ARG0'),
'Expected the param input to not exist',
);
});
test('undoing deleting a procedure parameter adds it', function () {
// Create a stack of container, parameter.
const defBlock = createProcDefBlock(this.workspace);
const mutatorIcon = defBlock.getIcon(Blockly.icons.MutatorIcon.TYPE);
mutatorIcon.setBubbleVisible(true);
const mutatorWorkspace = mutatorIcon.getWorkspace();
const containerBlock = mutatorWorkspace.getTopBlocks()[0];
const paramBlock = mutatorWorkspace.newBlock('procedures_mutatorarg');
paramBlock.setFieldValue('param1', 'NAME');
containerBlock
.getInput('STACK')
.connection.connect(paramBlock.previousConnection);
this.clock.runAll();
paramBlock.checkAndDelete();
this.clock.runAll();
this.workspace.undo();
chai.assert.isTrue(
defBlock.getFieldValue('PARAMS').includes('param1'),
'Expected the params field to contain the name of the new param',
);
});
test(
'undoing and redoing deleting a procedure parameter maintains ' +
'the same state',
function () {
// Create a stack of container, parameter.
const defBlock = createProcDefBlock(this.workspace);
const mutatorIcon = defBlock.getIcon(Blockly.icons.MutatorIcon.TYPE);
mutatorIcon.setBubbleVisible(true);
const mutatorWorkspace = mutatorIcon.getWorkspace();
const containerBlock = mutatorWorkspace.getTopBlocks()[0];
const paramBlock = mutatorWorkspace.newBlock('procedures_mutatorarg');
paramBlock.setFieldValue('param1', 'NAME');
containerBlock
.getInput('STACK')
.connection.connect(paramBlock.previousConnection);
this.clock.runAll();
paramBlock.checkAndDelete();
this.clock.runAll();
this.workspace.undo();
this.workspace.undo(/* redo= */ true);
chai.assert.isFalse(
defBlock.getFieldValue('PARAMS').includes('param1'),
'Expected the params field to not contain the name of the new param',
);
},
);
});
suite('renaming procedure parameters', function () {
test('defs are updated for parameter renames', function () {
// Create a stack of container, parameter.
const defBlock = createProcDefBlock(this.workspace);
const mutatorIcon = defBlock.getIcon(Blockly.icons.MutatorIcon.TYPE);
mutatorIcon.setBubbleVisible(true);
const mutatorWorkspace = mutatorIcon.getWorkspace();
const containerBlock = mutatorWorkspace.getTopBlocks()[0];
const paramBlock = mutatorWorkspace.newBlock('procedures_mutatorarg');
paramBlock.setFieldValue('param1', 'NAME');
containerBlock
.getInput('STACK')
.connection.connect(paramBlock.previousConnection);
this.clock.runAll();
paramBlock.setFieldValue('new name', 'NAME');
this.clock.runAll();
chai.assert.isNotNull(
defBlock.getField('PARAMS'),
'Expected the params field to exist',
);
chai.assert.isTrue(
defBlock.getFieldValue('PARAMS').includes('new name'),
'Expected the params field to contain the new name of the param',
);
});
test('defs are updated for parameter renames when two params exist', function () {
// Create a stack of container, parameter.
const defBlock = createProcDefBlock(this.workspace);
const mutatorIcon = defBlock.getIcon(Blockly.icons.MutatorIcon.TYPE);
mutatorIcon.setBubbleVisible(true);
const mutatorWorkspace = mutatorIcon.getWorkspace();
const containerBlock = mutatorWorkspace.getTopBlocks()[0];
const paramBlock1 = mutatorWorkspace.newBlock('procedures_mutatorarg');
paramBlock1.setFieldValue('param1', 'NAME');
const paramBlock2 = mutatorWorkspace.newBlock('procedures_mutatorarg');
paramBlock2.setFieldValue('param2', 'NAME');
containerBlock
.getInput('STACK')
.connection.connect(paramBlock1.previousConnection);
paramBlock1.nextConnection.connect(paramBlock2.previousConnection);
this.clock.runAll();
paramBlock1.setFieldValue('new name', 'NAME');
this.clock.runAll();
chai.assert.isNotNull(
defBlock.getField('PARAMS'),
'Expected the params field to exist',
);
chai.assert.isTrue(
defBlock.getFieldValue('PARAMS').includes('new name'),
'Expected the params field to contain the new name of the param',
);
});
test('callers are updated for parameter renames', function () {
// Create a stack of container, parameter.
const defBlock = createProcDefBlock(this.workspace);
const callBlock = createProcCallBlock(this.workspace);
const mutatorIcon = defBlock.getIcon(Blockly.icons.MutatorIcon.TYPE);
mutatorIcon.setBubbleVisible(true);
const mutatorWorkspace = mutatorIcon.getWorkspace();
const containerBlock = mutatorWorkspace.getTopBlocks()[0];
const paramBlock = mutatorWorkspace.newBlock('procedures_mutatorarg');
paramBlock.setFieldValue('param1', 'NAME');
containerBlock
.getInput('STACK')
.connection.connect(paramBlock.previousConnection);
this.clock.runAll();
paramBlock.setFieldValue('new name', 'NAME');
this.clock.runAll();
chai.assert.isNotNull(
callBlock.getInput('ARG0'),
'Expected the param input to exist',
);
chai.assert.equal(
callBlock.getFieldValue('ARGNAME0'),
'new name',
'Expected the params field to match the name of the new param',
);
});
test('variables associated with procedure parameters are not renamed', function () {
// Create a stack of container, parameter.
const defBlock = createProcDefBlock(this.workspace);
const callBlock = createProcCallBlock(this.workspace);
const mutatorIcon = defBlock.getIcon(Blockly.icons.MutatorIcon.TYPE);
mutatorIcon.setBubbleVisible(true);
const mutatorWorkspace = mutatorIcon.getWorkspace();
const containerBlock = mutatorWorkspace.getTopBlocks()[0];
const paramBlock = mutatorWorkspace.newBlock('procedures_mutatorarg');
paramBlock.setFieldValue('param1', 'NAME');
containerBlock
.getInput('STACK')
.connection.connect(paramBlock.previousConnection);
this.clock.runAll();
paramBlock.setFieldValue('param2', 'NAME');
this.clock.runAll();
chai.assert.isNotNull(
this.workspace.getVariable('param1', ''),
'Expected the old variable to continue to exist',
);
});
test('renaming a variable associated with a parameter updates procedure defs', function () {
// Create a stack of container, parameter.
const defBlock = createProcDefBlock(this.workspace);
const mutatorIcon = defBlock.getIcon(Blockly.icons.MutatorIcon.TYPE);
mutatorIcon.setBubbleVisible(true);
const mutatorWorkspace = mutatorIcon.getWorkspace();
const containerBlock = mutatorWorkspace.getTopBlocks()[0];
const paramBlock = mutatorWorkspace.newBlock('procedures_mutatorarg');
paramBlock.setFieldValue('param1', 'NAME');
containerBlock
.getInput('STACK')
.connection.connect(paramBlock.previousConnection);
this.clock.runAll();
mutatorIcon.setBubbleVisible(false);
const variable = this.workspace.getVariable('param1', '');
this.workspace.renameVariableById(variable.getId(), 'new name');
chai.assert.isNotNull(
defBlock.getField('PARAMS'),
'Expected the params field to exist',
);
chai.assert.isTrue(
defBlock.getFieldValue('PARAMS').includes('new name'),
'Expected the params field to contain the new name of the param',
);
});
test('renaming a variable associated with a parameter updates mutator parameters', function () {
// Create a stack of container, parameter.
const defBlock = createProcDefBlock(this.workspace);
const mutatorIcon = defBlock.getIcon(Blockly.icons.MutatorIcon.TYPE);
mutatorIcon.setBubbleVisible(true);
const mutatorWorkspace = mutatorIcon.getWorkspace();
const containerBlock = mutatorWorkspace.getTopBlocks()[0];
const paramBlock = mutatorWorkspace.newBlock('procedures_mutatorarg');
paramBlock.setFieldValue('param1', 'NAME');
containerBlock
.getInput('STACK')
.connection.connect(paramBlock.previousConnection);
this.clock.runAll();
const variable = this.workspace.getVariable('param1', '');
this.workspace.renameVariableById(variable.getId(), 'new name');
chai.assert.equal(
paramBlock.getFieldValue('NAME'),
'new name',
'Expected the params field to contain the new name of the param',
);
});
test('renaming a variable associated with a parameter updates procedure callers', function () {
// Create a stack of container, parameter.
const defBlock = createProcDefBlock(this.workspace);
const callBlock = createProcCallBlock(this.workspace);
const mutatorIcon = defBlock.getIcon(Blockly.icons.MutatorIcon.TYPE);
mutatorIcon.setBubbleVisible(true);
const mutatorWorkspace = mutatorIcon.getWorkspace();
const containerBlock = mutatorWorkspace.getTopBlocks()[0];
const paramBlock = mutatorWorkspace.newBlock('procedures_mutatorarg');
paramBlock.setFieldValue('param1', 'NAME');
containerBlock
.getInput('STACK')
.connection.connect(paramBlock.previousConnection);
this.clock.runAll();
mutatorIcon.setBubbleVisible(false);
const variable = this.workspace.getVariable('param1', '');
this.workspace.renameVariableById(variable.getId(), 'new name');
chai.assert.isNotNull(
callBlock.getInput('ARG0'),
'Expected the param input to exist',
);
chai.assert.equal(
callBlock.getFieldValue('ARGNAME0'),
'new name',
'Expected the params field to match the name of the new param',
);
});
test('coalescing a variable associated with a parameter updates procedure defs', function () {
// Create a stack of container, parameter.
const defBlock = createProcDefBlock(this.workspace);
const mutatorIcon = defBlock.getIcon(Blockly.icons.MutatorIcon.TYPE);
mutatorIcon.setBubbleVisible(true);
const mutatorWorkspace = mutatorIcon.getWorkspace();
const containerBlock = mutatorWorkspace.getTopBlocks()[0];
const paramBlock = mutatorWorkspace.newBlock('procedures_mutatorarg');
paramBlock.setFieldValue('param1', 'NAME');
containerBlock
.getInput('STACK')
.connection.connect(paramBlock.previousConnection);
this.clock.runAll();
mutatorIcon.setBubbleVisible(false);
const variable = this.workspace.getVariable('param1', '');
this.workspace.renameVariableById(variable.getId(), 'preCreatedVar');
chai.assert.isNotNull(
defBlock.getField('PARAMS'),
'Expected the params field to exist',
);
chai.assert.isTrue(
defBlock.getFieldValue('PARAMS').includes('preCreatedVar'),
'Expected the params field to contain the new name of the param',
);
});
test('coalescing a variable associated with a parameter updates mutator parameters', function () {
// Create a stack of container, parameter.
const defBlock = createProcDefBlock(this.workspace);
const mutatorIcon = defBlock.getIcon(Blockly.icons.MutatorIcon.TYPE);
mutatorIcon.setBubbleVisible(true);
const mutatorWorkspace = mutatorIcon.getWorkspace();
const containerBlock = mutatorWorkspace.getTopBlocks()[0];
const paramBlock = mutatorWorkspace.newBlock('procedures_mutatorarg');
paramBlock.setFieldValue('param1', 'NAME');
containerBlock
.getInput('STACK')
.connection.connect(paramBlock.previousConnection);
this.clock.runAll();
const variable = this.workspace.getVariable('param1', '');
this.workspace.renameVariableById(variable.getId(), 'preCreatedVar');
chai.assert.equal(
paramBlock.getFieldValue('NAME'),
'preCreatedVar',
'Expected the params field to contain the new name of the param',
);
});
test('coalescing a variable associated with a parameter updates procedure callers', function () {
// Create a stack of container, parameter.
const defBlock = createProcDefBlock(this.workspace);
const callBlock = createProcCallBlock(this.workspace);
const mutatorIcon = defBlock.getIcon(Blockly.icons.MutatorIcon.TYPE);
mutatorIcon.setBubbleVisible(true);
const mutatorWorkspace = mutatorIcon.getWorkspace();
const containerBlock = mutatorWorkspace.getTopBlocks()[0];
const paramBlock = mutatorWorkspace.newBlock('procedures_mutatorarg');
paramBlock.setFieldValue('param1', 'NAME');
containerBlock
.getInput('STACK')
.connection.connect(paramBlock.previousConnection);
this.clock.runAll();
mutatorIcon.setBubbleVisible(false);
const variable = this.workspace.getVariable('param1', '');
this.workspace.renameVariableById(variable.getId(), 'preCreatedVar');
chai.assert.isNotNull(
callBlock.getInput('ARG0'),
'Expected the param input to exist',
);
chai.assert.equal(
callBlock.getFieldValue('ARGNAME0'),
'preCreatedVar',
'Expected the params field to match the name of the new param',
);
});
test.skip(
'renaming a variable such that you get a parameter ' +
'conflict does... something!',
function () {},
);
test('undoing renaming a procedure parameter reverts the change', function () {
// Create a stack of container, parameter.
const defBlock = createProcDefBlock(this.workspace);
const mutatorIcon = defBlock.getIcon(Blockly.icons.MutatorIcon.TYPE);
mutatorIcon.setBubbleVisible(true);
const mutatorWorkspace = mutatorIcon.getWorkspace();
const containerBlock = mutatorWorkspace.getTopBlocks()[0];
const paramBlock = mutatorWorkspace.newBlock('procedures_mutatorarg');
paramBlock.setFieldValue('param1', 'NAME');
containerBlock
.getInput('STACK')
.connection.connect(paramBlock.previousConnection);
this.clock.runAll();
Blockly.Events.setGroup(true);
paramBlock.setFieldValue('n', 'NAME');
this.clock.runAll();
paramBlock.setFieldValue('ne', 'NAME');
this.clock.runAll();
paramBlock.setFieldValue('new', 'NAME');
this.clock.runAll();
Blockly.Events.setGroup(false);
this.workspace.undo();
this.clock.runAll();
chai.assert.isTrue(
defBlock.getFieldValue('PARAMS').includes('param1'),
'Expected the params field to contain the old name of the param',
);
});
test('undoing and redoing renaming a procedure maintains the same state', function () {
// Create a stack of container, parameter.
const defBlock = createProcDefBlock(this.workspace);
const mutatorIcon = defBlock.getIcon(Blockly.icons.MutatorIcon.TYPE);
mutatorIcon.setBubbleVisible(true);
const mutatorWorkspace = mutatorIcon.getWorkspace();
const containerBlock = mutatorWorkspace.getTopBlocks()[0];
const paramBlock = mutatorWorkspace.newBlock('procedures_mutatorarg');
paramBlock.setFieldValue('param1', 'NAME');
containerBlock
.getInput('STACK')
.connection.connect(paramBlock.previousConnection);
this.clock.runAll();
Blockly.Events.setGroup(true);
paramBlock.setFieldValue('n', 'NAME');
this.clock.runAll();
paramBlock.setFieldValue('ne', 'NAME');
this.clock.runAll();
paramBlock.setFieldValue('new', 'NAME');
this.clock.runAll();
Blockly.Events.setGroup(false);
this.workspace.undo();
this.workspace.undo(/* redo= */ true);
chai.assert.isTrue(
defBlock.getFieldValue('PARAMS').includes('new'),
'Expected the params field to contain the new name of the param',
);
});
});
suite('reordering procedure parameters', function () {
test('reordering procedure parameters updates procedure blocks', function () {
// Create a stack of container, parameter, parameter.
const defBlock = createProcDefBlock(this.workspace);
const mutatorIcon = defBlock.getIcon(Blockly.icons.MutatorIcon.TYPE);
mutatorIcon.setBubbleVisible(true);
const mutatorWorkspace = mutatorIcon.getWorkspace();
const containerBlock = mutatorWorkspace.getTopBlocks()[0];
const paramBlock1 = mutatorWorkspace.newBlock('procedures_mutatorarg');
paramBlock1.setFieldValue('param1', 'NAME');
const paramBlock2 = mutatorWorkspace.newBlock('procedures_mutatorarg');
paramBlock2.setFieldValue('param2', 'NAME');
containerBlock
.getInput('STACK')
.connection.connect(paramBlock1.previousConnection);
paramBlock1.nextConnection.connect(paramBlock2.previousConnection);
this.clock.runAll();
// Reorder the parameters.
paramBlock2.previousConnection.disconnect();
paramBlock1.previousConnection.disconnect();
containerBlock
.getInput('STACK')
.connection.connect(paramBlock2.previousConnection);
paramBlock2.nextConnection.connect(paramBlock1.previousConnection);
this.clock.runAll();
chai.assert.isNotNull(
defBlock.getField('PARAMS'),
'Expected the params field to exist',
);
chai.assert.isTrue(
defBlock.getFieldValue('PARAMS').includes('param2, param1'),
'Expected the params field order to match the parameter order',
);
});
test('reordering procedure parameters updates caller blocks', function () {
// Create a stack of container, parameter, parameter.
const defBlock = createProcDefBlock(this.workspace);
const callBlock = createProcCallBlock(this.workspace);
const mutatorIcon = defBlock.getIcon(Blockly.icons.MutatorIcon.TYPE);
mutatorIcon.setBubbleVisible(true);
const mutatorWorkspace = mutatorIcon.getWorkspace();
const containerBlock = mutatorWorkspace.getTopBlocks()[0];
const paramBlock1 = mutatorWorkspace.newBlock('procedures_mutatorarg');
paramBlock1.setFieldValue('param1', 'NAME');
const paramBlock2 = mutatorWorkspace.newBlock('procedures_mutatorarg');
paramBlock2.setFieldValue('param2', 'NAME');
containerBlock
.getInput('STACK')
.connection.connect(paramBlock1.previousConnection);
paramBlock1.nextConnection.connect(paramBlock2.previousConnection);
this.clock.runAll();
// Reorder the parameters.
paramBlock2.previousConnection.disconnect();
paramBlock1.previousConnection.disconnect();
containerBlock
.getInput('STACK')
.connection.connect(paramBlock2.previousConnection);
paramBlock2.nextConnection.connect(paramBlock1.previousConnection);
this.clock.runAll();
chai.assert.isNotNull(
callBlock.getInput('ARG0'),
'Expected the param input to exist',
);
chai.assert.equal(
callBlock.getFieldValue('ARGNAME0'),
'param2',
'Expected the params field to match the name of the second param',
);
chai.assert.isNotNull(
callBlock.getInput('ARG1'),
'Expected the param input to exist',
);
chai.assert.equal(
callBlock.getFieldValue('ARGNAME1'),
'param1',
'Expected the params field to match the name of the first param',
);
});
test(
'reordering procedure parameters reorders the blocks ' +
'attached to caller inputs',
function () {
// Create a stack of container, parameter, parameter.
const defBlock = createProcDefBlock(this.workspace);
const callBlock = createProcCallBlock(this.workspace);
const mutatorIcon = defBlock.getIcon(Blockly.icons.MutatorIcon.TYPE);
mutatorIcon.setBubbleVisible(true);
const mutatorWorkspace = mutatorIcon.getWorkspace();
const containerBlock = mutatorWorkspace.getTopBlocks()[0];
const paramBlock1 = mutatorWorkspace.newBlock('procedures_mutatorarg');
paramBlock1.setFieldValue('param1', 'NAME');
const paramBlock2 = mutatorWorkspace.newBlock('procedures_mutatorarg');
paramBlock2.setFieldValue('param2', 'NAME');
containerBlock
.getInput('STACK')
.connection.connect(paramBlock1.previousConnection);
paramBlock1.nextConnection.connect(paramBlock2.previousConnection);
this.clock.runAll();
// Add args to the parameter inputs on the caller.
const block1 = this.workspace.newBlock('text');
const block2 = this.workspace.newBlock('text');
callBlock.getInput('ARG0').connection.connect(block1.outputConnection);
callBlock.getInput('ARG1').connection.connect(block2.outputConnection);
// Reorder the parameters.
paramBlock2.previousConnection.disconnect();
paramBlock1.previousConnection.disconnect();
containerBlock
.getInput('STACK')
.connection.connect(paramBlock2.previousConnection);
paramBlock2.nextConnection.connect(paramBlock1.previousConnection);
this.clock.runAll();
chai.assert.equal(
callBlock.getInputTargetBlock('ARG0'),
block2,
'Expected the second block to be in the first slot',
);
chai.assert.equal(
callBlock.getInputTargetBlock('ARG1'),
block1,
'Expected the first block to be in the second slot',
);
},
);
});
suite('enabling and disabling procedure blocks', function () {
test(
'if a procedure definition is disabled, the procedure caller ' +
'is also disabled',
function () {
const defBlock = createProcDefBlock(this.workspace);
const callBlock = createProcCallBlock(this.workspace);
defBlock.setEnabled(false);
this.clock.runAll();
chai.assert.isFalse(
callBlock.isEnabled(),
'Expected the caller block to be disabled',
);
},
);
test(
'if a procedure definition is enabled, the procedure caller ' +
'is also enabled',
function () {
const defBlock = createProcDefBlock(this.workspace);
const callBlock = createProcCallBlock(this.workspace);
defBlock.setEnabled(false);
this.clock.runAll();
defBlock.setEnabled(true);
this.clock.runAll();
chai.assert.isTrue(
callBlock.isEnabled(),
'Expected the caller block to be enabled',
);
},
);
test(
'if a procedure caller block was already disabled before ' +
'its definition was disabled, it is not reenabled',
function () {
const defBlock = createProcDefBlock(this.workspace);
const callBlock = createProcCallBlock(this.workspace);
this.clock.runAll();
callBlock.setEnabled(false);
this.clock.runAll();
defBlock.setEnabled(false);
this.clock.runAll();
defBlock.setEnabled(true);
this.clock.runAll();
chai.assert.isFalse(
callBlock.isEnabled(),
'Expected the caller block to continue to be disabled',
);
},
);
});
suite('deleting procedure blocks', function () {
test(
'when the procedure definition block is deleted, all of its ' +
'associated callers are deleted as well',
function () {
const defBlock = createProcDefBlock(this.workspace);
const callBlock1 = createProcCallBlock(this.workspace);
const callBlock2 = createProcCallBlock(this.workspace);
this.clock.runAll();
defBlock.dispose();
this.clock.runAll();
chai.assert.isTrue(
callBlock1.disposed,
'Expected the first caller to be disposed',
);
chai.assert.isTrue(
callBlock2.disposed,
'Expected the second caller to be disposed',
);
},
);
});
suite('caller blocks creating new def blocks', function () {
setup(function () {
this.TEST_VAR_ID = 'test-id';
this.genUidStub = createGenUidStubWithReturns(this.TEST_VAR_ID);
});
suite('xml', function () {
test('callers without defs create new defs', function () {
const callBlock = Blockly.Xml.domToBlock(
Blockly.utils.xml.textToDom(`
<block type="procedures_callreturn">
<mutation name="do something"/>
</block>`),
this.workspace,
);
this.clock.runAll();
assertDefBlockStructure(
this.workspace.getBlocksByType('procedures_defreturn')[0],
true,
);
assertCallBlockStructure(callBlock, [], [], 'do something');
});
test('callers without mutations create unnamed defs', function () {
const callBlock = Blockly.Xml.domToBlock(
Blockly.utils.xml.textToDom(
'<block type="procedures_callreturn"></block>',
),
this.workspace,
);
this.clock.runAll();
assertDefBlockStructure(
this.workspace.getBlocksByType('procedures_defreturn')[0],
true,
);
assertCallBlockStructure(callBlock, [], [], 'unnamed');
});
test('callers with missing args create new defs', function () {
const defBlock = Blockly.Xml.domToBlock(
Blockly.utils.xml.textToDom(`
<block type="procedures_defreturn">
<field name="NAME">do something</field>
<mutation>
<arg name="x" varid="arg"></arg>
</mutation>
</block>
`),
this.workspace,
);
const callBlock = Blockly.Xml.domToBlock(
Blockly.utils.xml.textToDom(
'<block type="procedures_callreturn">' +
' <mutation name="do something"/>' +
'</block>',
),
this.workspace,
);
this.clock.runAll();
assertDefBlockStructure(defBlock, true, ['x'], ['arg']);
assertCallBlockStructure(callBlock, [], [], 'do something2');
});
test('callers with mismatched args create new defs', function () {
const defBlock = Blockly.Xml.domToBlock(
Blockly.utils.xml.textToDom(`
<block type="procedures_defreturn">
<field name="NAME">do something</field>
<mutation>
<arg name="x" varid="arg"></arg>
</mutation>
</block>
`),
this.workspace,
);
const callBlock = Blockly.Xml.domToBlock(
Blockly.utils.xml.textToDom(`
<block type="procedures_callreturn">
<mutation name="do something">
<arg name="y"></arg>
</mutation>
</block>
`),
this.workspace,
);
this.clock.runAll();
assertDefBlockStructure(defBlock, true, ['x'], ['arg']);
assertCallBlockStructure(
callBlock,
['y'],
[this.TEST_VAR_ID],
'do something2',
);
});
test.skip('callers whose defs are deserialized later do not create defs', function () {
Blockly.Xml.domToWorkspace(
Blockly.utils.xml.textToDom(`
<xml>
<block type="procedures_callreturn">
<mutation name="do something">
<arg name="x"></arg>
</mutation>
</block>
<block type="procedures_defreturn">
<field name="NAME">do something</field>
<mutation>
<arg name="x" varid="arg"></arg>
</mutation>
</block>
</xml>
`),
this.workspace,
);
this.clock.runAll();
const defBlock = this.workspace.getBlocksByType(
'procedures_defreturn',
)[0];
const callBlock = this.workspace.getBlocksByType(
'procedures_callreturn',
)[0];
// TODO: Currently the callers are creating variables with different
// IDs than those serialized to XML, so these assertions fail.
assertDefBlockStructure(defBlock, true, ['x'], ['arg']);
assertCallBlockStructure(callBlock, ['x'], ['arg'], 'do something');
});
});
suite('json', function () {
test('callers without defs create new defs', function () {
const callBlock = Blockly.serialization.blocks.append(
{
'type': 'procedures_callreturn',
'extraState': {
'name': 'do something',
},
},
this.workspace,
{recordUndo: true},
);
this.clock.runAll();
assertDefBlockStructure(
this.workspace.getBlocksByType('procedures_defreturn')[0],
true,
);
assertCallBlockStructure(callBlock, [], [], 'do something');
});
test('callers without extra state create unamed defs', function () {
// recordUndo must be true to trigger change listener.
const callBlock = Blockly.serialization.blocks.append(
{
'type': 'procedures_callreturn',
},
this.workspace,
{recordUndo: true},
);
this.clock.runAll();
assertDefBlockStructure(
this.workspace.getBlocksByType('procedures_defreturn')[0],
true,
);
assertCallBlockStructure(callBlock, [], [], 'unnamed');
});
test('callers with missing args create new defs', function () {
const defBlock = Blockly.serialization.blocks.append(
{
'type': 'procedures_defreturn',
'fields': {
'NAME': 'do something',
},
'extraState': {
'params': [
{
'name': 'x',
'id': 'arg',
},
],
},
},
this.workspace,
);
const callBlock = Blockly.serialization.blocks.append(
{
'type': 'procedures_callreturn',
'extraState': {
'name': 'do something',
},
},
this.workspace,
{recordUndo: true},
);
this.clock.runAll();
assertDefBlockStructure(defBlock, true, ['x'], ['arg']);
assertCallBlockStructure(callBlock, [], [], 'do something2');
});
test('callers with mismatched args create new defs', function () {
const defBlock = Blockly.serialization.blocks.append(
{
'type': 'procedures_defreturn',
'fields': {
'NAME': 'do something',
},
'extraState': {
'params': [
{
'name': 'x',
'id': 'arg',
},
],
},
},
this.workspace,
);
const callBlock = Blockly.serialization.blocks.append(
{
'type': 'procedures_callreturn',
'extraState': {
'name': 'do something',
'params': ['y'],
},
},
this.workspace,
{recordUndo: true},
);
this.clock.runAll();
assertDefBlockStructure(defBlock, true, ['x'], ['arg']);
assertCallBlockStructure(
callBlock,
['y'],
[this.TEST_VAR_ID],
'do something2',
);
});
test.skip('callers whose defs are deserialized later do not create defs', function () {
Blockly.serialization.workspaces.load(
{
'blocks': {
'languageVersion': 0,
'blocks': [
{
'type': 'procedures_callreturn',
'extraState': {
'params': ['x'],
},
},
{
'type': 'procedures_defreturn',
'fields': {
'NAME': 'do something',
},
'extraState': {
'params': [
{
'name': 'x',
'id': 'arg',
},
],
},
},
],
},
},
this.workspace,
);
this.clock.runAll();
const defBlock = this.workspace.getBlocksByType(
'procedures_defreturn',
)[0];
const callBlock = this.workspace.getBlocksByType(
'procedures_callreturn',
)[0];
// TODO: Currently the callers are creating variables with different
// IDs than those serialized to JSON, so these assertions fail.
assertDefBlockStructure(defBlock, true, ['x'], ['arg']);
assertCallBlockStructure(callBlock, ['x'], ['arg'], 'do something');
});
});
});
suite('definition block context menu', function () {
test('the context menu includes an option for creating the caller', function () {
const def = Blockly.serialization.blocks.append(
{
'type': 'procedures_defnoreturn',
'fields': {
'NAME': 'test name',
},
},
this.workspace,
);
const options = [];
def.customContextMenu(options);
chai.assert.isTrue(
options[0].text.includes('test name'),
'Expected the context menu to have an option to create the caller',
);
});
test('the context menu includes an option for each parameter', function () {
const def = Blockly.serialization.blocks.append(
{
'type': 'procedures_defnoreturn',
'fields': {
'NAME': 'test name',
},
'extraState': {
'params': [
{
'name': 'testParam1',
'id': 'varId1',
'paramId': 'paramId1',
},
{
'name': 'testParam2',
'id': 'varId2',
'paramId': 'paramId2',
},
],
},
},
this.workspace,
);
const options = [];
def.customContextMenu(options);
chai.assert.isTrue(
options[1].text.includes('testParam1'),
'Expected the context menu to have an option to create the first param',
);
chai.assert.isTrue(
options[2].text.includes('testParam2'),
'Expected the context menu to have an option to create the second param',
);
});
});
suite('allProcedures', function () {
test('Only Procedures', function () {
const noReturnBlock = this.workspace.newBlock('procedures_defnoreturn');
noReturnBlock.setFieldValue('no return', 'NAME');
const returnBlock = this.workspace.newBlock('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 = this.workspace.newBlock('procedures_defnoreturn');
noReturnBlock.setFieldValue('no return', 'NAME');
const returnBlock = this.workspace.newBlock('procedures_defreturn');
returnBlock.setFieldValue('return', 'NAME');
const returnBlock2 = this.workspace.newBlock('procedures_defreturn');
returnBlock2.setFieldValue('return2', 'NAME');
const _ = this.workspace.newBlock('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 _ = this.workspace.newBlock('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('returns false if no blocks or models exists', function () {
chai.assert.isFalse(
Blockly.Procedures.isNameUsed('proc name', this.workspace),
);
});
test('returns true if an associated block exists', function () {
createProcDefBlock(this.workspace, false, [], 'proc name');
chai.assert.isTrue(
Blockly.Procedures.isNameUsed('proc name', this.workspace),
);
});
test('return false if an associated block does not exist', function () {
createProcDefBlock(this.workspace, false, [], 'proc name');
chai.assert.isFalse(
Blockly.Procedures.isNameUsed('other proc name', this.workspace),
);
});
test('returns true if an associated procedure model exists', function () {
this.workspace
.getProcedureMap()
.add(new MockProcedureModel().setName('proc name'));
chai.assert.isTrue(
Blockly.Procedures.isNameUsed('proc name', this.workspace),
);
});
test('returns false if an associated procedure model exists', function () {
this.workspace
.getProcedureMap()
.add(new MockProcedureModel().setName('proc name'));
chai.assert.isFalse(
Blockly.Procedures.isNameUsed('other proc name', this.workspace),
);
});
});
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,
`Expected the number of no return blocks to be ${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,
`Expected there to be one caller of the ${expectedName} block`,
);
}
}
const defReturnBlocks = allProcedures[1];
chai.assert.lengthOf(
defReturnBlocks,
returnNames.length,
`Expected the number of return blocks to be ${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,
`Expected there to be one caller of the ${expectedName} block`,
);
}
}
// 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.utils.xml.textToDom(`
<xml xmlns="https://developers.google.com/blockly/xml">
<block type="procedures_defnoreturn"/>
<block type="procedures_defreturn"/>
</xml>`);
Blockly.Xml.domToWorkspace(xml, this.workspace);
this.clock.runAll();
assertDefAndCallBlocks(
this.workspace,
['unnamed'],
['unnamed2'],
false,
);
});
test('defreturn and defnoreturn', function () {
const xml = Blockly.utils.xml.textToDom(`
<xml xmlns="https://developers.google.com/blockly/xml">
<block type="procedures_defreturn"/>
<block type="procedures_defnoreturn"/>
</xml>`);
Blockly.Xml.domToWorkspace(xml, this.workspace);
this.clock.runAll();
assertDefAndCallBlocks(
this.workspace,
['unnamed2'],
['unnamed'],
false,
);
});
test('callreturn (no def in xml)', function () {
const xml = Blockly.utils.xml.textToDom(`
<xml xmlns="https://developers.google.com/blockly/xml">
<block type="procedures_callreturn"/>
</xml>`);
Blockly.Xml.domToWorkspace(xml, this.workspace);
this.clock.runAll();
assertDefAndCallBlocks(this.workspace, [], ['unnamed'], true);
});
test('callnoreturn and callreturn (no def in xml)', function () {
const xml = Blockly.utils.xml.textToDom(`
<xml xmlns="https://developers.google.com/blockly/xml">
<block type="procedures_callnoreturn" id="first"/>
<block type="procedures_callreturn" id="second"/>
</xml>`);
Blockly.Xml.domToWorkspace(xml, this.workspace);
this.clock.runAll();
assertDefAndCallBlocks(this.workspace, ['unnamed'], ['unnamed2'], true);
});
test('callreturn and callnoreturn (no def in xml)', function () {
const xml = Blockly.utils.xml.textToDom(`
<xml xmlns="https://developers.google.com/blockly/xml">
<block type="procedures_callreturn"/>
<block type="procedures_callnoreturn"/>
</xml>`);
Blockly.Xml.domToWorkspace(xml, this.workspace);
this.clock.runAll();
assertDefAndCallBlocks(this.workspace, ['unnamed2'], ['unnamed'], true);
});
});
});
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 = this.workspace.newBlock('new_proc');
const def = Blockly.Procedures.getDefinition('test', this.workspace);
chai.assert.equal(def, defBlock);
});
test('Stacked procedures', function () {
const blockA = this.workspace.newBlock('nested_proc');
const blockB = this.workspace.newBlock('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 = this.workspace.newBlock(testSuite.defType);
this.defBlock.setFieldValue('proc name', 'NAME');
});
test('Definition block', function () {
assertDefBlockStructure(this.defBlock, testSuite.hasReturn);
});
test('Call block', function () {
this.callBlock = Blockly.serialization.blocks.append(
{
'type': testSuite.callType,
},
this.workspace,
{recordUndo: true},
);
this.callBlock.setFieldValue('proc name', 'NAME');
this.clock.runAll();
assertCallBlockStructure(this.callBlock);
});
});
suite('rename', function () {
setup(function () {
this.defBlock = Blockly.serialization.blocks.append(
{
'type': testSuite.defType,
'fields': {
'NAME': 'proc name',
},
},
this.workspace,
);
this.callBlock = Blockly.serialization.blocks.append(
{
'type': testSuite.callType,
'fields': {
'NAME': 'proc name',
},
},
this.workspace,
);
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-untyped-default-value',
'proc name',
);
defInput.htmlInput_.value = 'proc name2';
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-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-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-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 then Text', function () {
const defInput = this.defBlock.getField('NAME');
defInput.htmlInput_ = document.createElement('input');
defInput.htmlInput_.setAttribute(
'data-untyped-default-value',
'proc name',
);
defInput.htmlInput_.value = 'proc name ';
defInput.onHtmlInputChange_(null);
defInput.htmlInput_.value = 'proc name 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-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-untyped-default-value',
'proc name',
);
defInput.htmlInput_.value = '';
defInput.onHtmlInputChange_(null);
const newDefBlock = this.workspace.newBlock(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 = Blockly.serialization.blocks.append(
{
'type': testSuite.defType,
'fields': {
'NAME': 'proc name',
},
},
this.workspace,
);
this.callBlock = Blockly.serialization.blocks.append(
{
'type': testSuite.callType,
'fields': {
'NAME': 'proc name',
},
},
this.workspace,
);
});
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 = this.workspace.newBlock(testSuite.callType);
caller2.setFieldValue('proc name', 'NAME');
const caller3 = this.workspace.newBlock(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 = this.workspace.newBlock(testSuite.defType);
def2.setFieldValue('proc name2', 'NAME');
const caller2 = this.workspace.newBlock(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 = workspace.newBlock(testSuite.defType);
def2.setFieldValue('proc name', 'NAME');
const caller2 = workspace.newBlock(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 = Blockly.serialization.blocks.append(
{
'type': testSuite.defType,
'fields': {
'NAME': 'proc name',
},
},
this.workspace,
);
this.callBlock = Blockly.serialization.blocks.append(
{
'type': testSuite.callType,
'fields': {
'NAME': 'proc name',
},
},
this.workspace,
);
});
test('Simple', function () {
const def = Blockly.Procedures.getDefinition(
'proc name',
this.workspace,
);
chai.assert.equal(def, this.defBlock);
});
test('Multiple Procedures', function () {
const def2 = this.workspace.newBlock(testSuite.defType);
def2.setFieldValue('proc name2', 'NAME');
const caller2 = this.workspace.newBlock(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 = workspace.newBlock(testSuite.defType);
def2.setFieldValue('proc name', 'NAME');
const caller2 = workspace.newBlock(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('Mutation', function () {
setup(function () {
this.defBlock = Blockly.serialization.blocks.append(
{
'type': testSuite.defType,
'fields': {
'NAME': 'proc name',
},
},
this.workspace,
);
this.callBlock = Blockly.serialization.blocks.append(
{
'type': testSuite.callType,
'extraState': {
'name': 'proc name',
},
},
this.workspace,
);
});
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.utils.xml.textToDom(
'<block type="procedures_defreturn">' +
' <statement name="STACK">' +
' <block type="procedures_ifreturn" id="test"></block>' +
' </statement> ' +
'</block>',
);
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) {
const mutatorIcon = this.defBlock.getIcon(
Blockly.icons.MutatorIcon.TYPE,
);
mutatorIcon.setBubbleVisible(true);
this.mutatorWorkspace = mutatorIcon.getWorkspace();
this.containerBlock = this.mutatorWorkspace.getTopBlocks()[0];
this.connection =
this.containerBlock.getInput('STACK').connection;
for (let i = 0; i < argArray.length; i++) {
this.argBlock = this.mutatorWorkspace.newBlock(
'procedures_mutatorarg',
);
this.argBlock.setFieldValue(argArray[i], 'NAME');
this.connection.connect(this.argBlock.previousConnection);
this.connection = this.argBlock.nextConnection;
}
this.clock.runAll();
}
function assertArgs(argArray) {
chai.assert.equal(
this.defBlock.getVars().length,
argArray.length,
'Expected the def to have the right number of arguments',
);
for (let i = 0; i < argArray.length; i++) {
chai.assert.equal(this.defBlock.getVars()[i], argArray[i]);
}
chai.assert.equal(
this.callBlock.getVars().length,
argArray.length,
'Expected the call to have the right number of arguments',
);
for (let i = 0; i < argArray.length; i++) {
chai.assert.equal(this.callBlock.getVars()[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);
});
}
});
});
});
/**
* Test cases for serialization tests.
* @type {Array<SerializationTestCase>}
*/
const testCases = [
{
title: 'XML - Minimal definition',
xml: '<block type="' + testSuite.defType + '"/>',
expectedXml:
'<block xmlns="https://developers.google.com/blockly/xml" ' +
'type="' +
testSuite.defType +
'" id="1">\n' +
' <field name="NAME">unnamed</field>\n' +
'</block>',
assertBlockStructure: (block) => {
assertDefBlockStructure(block, testSuite.hasReturn);
},
},
{
title: 'XML - Common definition',
xml:
'<block type="' +
testSuite.defType +
'">' +
' <field name="NAME">do something</field>' +
'</block>',
expectedXml:
'<block xmlns="https://developers.google.com/blockly/xml" ' +
'type="' +
testSuite.defType +
'" id="1">\n' +
' <field name="NAME">do something</field>\n' +
'</block>',
assertBlockStructure: (block) => {
assertDefBlockStructure(block, testSuite.hasReturn);
},
},
{
title: 'XML - With vars definition',
xml:
'<block type="' +
testSuite.defType +
'">\n' +
' <mutation>\n' +
' <arg name="x" varid="arg1"></arg>\n' +
' <arg name="y" varid="arg2"></arg>\n' +
' </mutation>\n' +
' <field name="NAME">do something</field>\n' +
'</block>',
expectedXml:
'<block xmlns="https://developers.google.com/blockly/xml" ' +
'type="' +
testSuite.defType +
'" id="1">\n' +
' <mutation>\n' +
' <arg name="x" varid="arg1"></arg>\n' +
' <arg name="y" varid="arg2"></arg>\n' +
' </mutation>\n' +
' <field name="NAME">do something</field>\n' +
'</block>',
assertBlockStructure: (block) => {
assertDefBlockStructure(
block,
testSuite.hasReturn,
['x', 'y'],
['arg1', 'arg2'],
);
},
},
{
title: 'XML - With pre-created vars definition',
xml:
'<block type="' +
testSuite.defType +
'">\n' +
' <mutation>\n' +
' <arg name="preCreatedVar" varid="preCreatedVarId"></arg>\n' +
' </mutation>\n' +
' <field name="NAME">do something</field>\n' +
'</block>',
expectedXml:
'<block xmlns="https://developers.google.com/blockly/xml" ' +
'type="' +
testSuite.defType +
'" id="1">\n' +
' <mutation>\n' +
' <arg name="preCreatedVar" varid="preCreatedVarId"></arg>\n' +
' </mutation>\n' +
' <field name="NAME">do something</field>\n' +
'</block>',
assertBlockStructure: (block) => {
assertDefBlockStructure(
block,
testSuite.hasReturn,
['preCreatedVar'],
['preCreatedVarId'],
);
},
},
{
title: 'XML - No statements definition',
xml:
'<block type="procedures_defreturn">\n' +
' <mutation statements="false"></mutation>\n' +
' <field name="NAME">do something</field>\n' +
'</block>',
expectedXml:
'<block xmlns="https://developers.google.com/blockly/xml" ' +
'type="procedures_defreturn" id="1">\n' +
' <mutation statements="false"></mutation>\n' +
' <field name="NAME">do something</field>\n' +
'</block>',
assertBlockStructure: (block) => {
assertDefBlockStructure(block, true, [], [], false);
},
},
{
title: 'XML - Minimal caller',
xml: '<block type="' + testSuite.callType + '"/>',
expectedXml:
'<block xmlns="https://developers.google.com/blockly/xml" ' +
'type="' +
testSuite.callType +
'" id="1">\n' +
' <mutation name="unnamed"></mutation>\n' +
'</block>',
assertBlockStructure: (block) => {
assertCallBlockStructure(block);
},
},
{
title: 'XML - Common caller',
xml:
'<block type="' +
testSuite.callType +
'">\n' +
' <mutation name="do something"/>\n' +
'</block>',
expectedXml:
'<block xmlns="https://developers.google.com/blockly/xml" ' +
'type="' +
testSuite.callType +
'" id="1">\n' +
' <mutation name="do something"></mutation>\n' +
'</block>',
assertBlockStructure: (block) => {
assertCallBlockStructure(block);
},
},
{
title: 'XML - With pre-created vars caller',
xml:
'<block type="' +
testSuite.callType +
'">\n' +
' <mutation name="do something">\n' +
' <arg name="preCreatedVar"></arg>\n' +
' </mutation>\n' +
'</block>',
expectedXml:
'<block xmlns="https://developers.google.com/blockly/xml" ' +
'type="' +
testSuite.callType +
'" id="1">\n' +
' <mutation name="do something">\n' +
' <arg name="preCreatedVar"></arg>\n' +
' </mutation>\n' +
'</block>',
assertBlockStructure: (block) => {
assertCallBlockStructure(
block,
['preCreatedVar'],
['preCreatedVarId'],
);
},
},
{
title: 'JSON - Minimal definition',
json: {
'type': testSuite.defType,
},
expectedJson: {
'type': testSuite.defType,
'id': '1',
'fields': {
'NAME': 'unnamed',
},
},
assertBlockStructure: (block) => {
assertDefBlockStructure(block, testSuite.hasReturn);
},
},
{
title: 'JSON - Common definition',
json: {
'type': testSuite.defType,
'fields': {
'NAME': 'do something',
},
},
expectedJson: {
'type': testSuite.defType,
'id': '1',
'fields': {
'NAME': 'do something',
},
},
assertBlockStructure: (block) => {
assertDefBlockStructure(block, testSuite.hasReturn);
},
},
{
title: 'JSON - With vars definition',
json: {
'type': testSuite.defType,
'fields': {
'NAME': 'do something',
},
'extraState': {
'params': [
{
'name': 'x',
'id': 'arg1',
},
{
'name': 'y',
'id': 'arg2',
},
],
},
},
expectedJson: {
'type': testSuite.defType,
'id': '1',
'fields': {
'NAME': 'do something',
},
'extraState': {
'params': [
{
'name': 'x',
'id': 'arg1',
},
{
'name': 'y',
'id': 'arg2',
},
],
},
},
assertBlockStructure: (block) => {
assertDefBlockStructure(
block,
testSuite.hasReturn,
['x', 'y'],
['arg1', 'arg2'],
);
},
},
{
title: 'JSON - With pre-created vars definition',
json: {
'type': testSuite.defType,
'extraState': {
'params': [
{
'name': 'preCreatedVar',
'id': 'preCreatedVarId',
},
],
},
'fields': {
'NAME': 'do something',
},
},
expectedJson: {
'type': testSuite.defType,
'id': '1',
'fields': {
'NAME': 'do something',
},
'extraState': {
'params': [
{
'name': 'preCreatedVar',
'id': 'preCreatedVarId',
},
],
},
},
assertBlockStructure: (block) => {
assertDefBlockStructure(
block,
testSuite.hasReturn,
['preCreatedVar'],
['preCreatedVarId'],
);
},
},
{
title: 'JSON - No statements definition',
json: {
'type': 'procedures_defreturn',
'fields': {
'NAME': 'do something',
},
'extraState': {
'hasStatements': false,
},
},
expectedJson: {
'type': 'procedures_defreturn',
'id': '1',
'fields': {
'NAME': 'do something',
},
'extraState': {
'hasStatements': false,
},
},
assertBlockStructure: (block) => {
assertDefBlockStructure(block, true, [], [], false);
},
},
{
title: 'JSON - Minimal caller',
json: {
'type': testSuite.callType,
},
expectedJson: {
'type': testSuite.callType,
'id': '1',
'extraState': {
'name': 'unnamed',
},
},
assertBlockStructure: (block) => {
assertCallBlockStructure(block);
},
},
{
title: 'JSON - Common caller',
json: {
'type': testSuite.callType,
'extraState': {
'name': 'do something',
},
},
expectedJson: {
'type': testSuite.callType,
'id': '1',
'extraState': {
'name': 'do something',
},
},
assertBlockStructure: (block) => {
assertCallBlockStructure(block);
},
},
{
title: 'JSON - With pre-created vars caller',
json: {
'type': testSuite.callType,
'extraState': {
'name': 'do something',
'params': ['preCreatedVar'],
},
},
expectedJson: {
'type': testSuite.callType,
'id': '1',
'extraState': {
'name': 'do something',
'params': ['preCreatedVar'],
},
},
assertBlockStructure: (block) => {
assertCallBlockStructure(
block,
['preCreatedVar'],
['preCreatedVarId'],
);
},
},
];
runSerializationTestSuite(testCases);
});
});
});