Files
blockly/tests/mocha/block_json_test.js
Beka Westberg d726080eaa feat: add support for appending custom inputs (#6990)
* feat: add appendInput method

* feat: enable constructing inputs from the registry

* chore: reorganize into suites

* chore: add new input test + fixup existing

* chore: reorganize appending from registry

* chore: fix input types enum

* chore: fix tests
2023-04-21 15:58:42 -07:00

620 lines
16 KiB
JavaScript

/**
* @license
* Copyright 2019 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
goog.declareModuleId('Blockly.test.blockJson');
import {Align} from '../../build/src/core/input.js';
import {sharedTestSetup, sharedTestTeardown} from './test_helpers/setup_teardown.js';
suite('Block JSON initialization', function() {
setup(function() {
sharedTestSetup.call(this);
this.workspace = new Blockly.Workspace();
});
teardown(function() {
sharedTestTeardown.call(this);
});
suite('validateTokens_', function() {
setup(function() {
this.assertError = function(tokens, count, error) {
const block = {
type: 'test',
validateTokens_: Blockly.Block.prototype.validateTokens_,
};
chai.assert.throws(function() {
block.validateTokens_(tokens, count);
}, error);
};
this.assertNoError = function(tokens, count) {
const block = {
type: 'test',
validateTokens_: Blockly.Block.prototype.validateTokens_,
};
chai.assert.doesNotThrow(function() {
block.validateTokens_(tokens, count);
});
};
});
test('0 args, 0 tokens', function() {
this.assertNoError(['test', 'test'], 0);
});
test('0 args, 1 token', function() {
this.assertError(['test', 1, 'test'], 0,
'Block "test": Message index %1 out of range.');
});
test('1 arg, 0 tokens', function() {
this.assertError(['test', 'test'], 1,
'Block "test": Message does not reference all 1 arg(s).');
});
test('1 arg, 1 token', function() {
this.assertNoError(['test', 1, 'test'], 1);
});
test('1 arg, 2 tokens', function() {
this.assertError(['test', 1, 1, 'test'], 1,
'Block "test": Message index %1 duplicated.');
});
test('Token out of lower bound', function() {
this.assertError(['test', 0, 'test'], 1,
'Block "test": Message index %0 out of range.');
});
test('Token out of upper bound', function() {
this.assertError(['test', 2, 'test'], 1,
'Block "test": Message index %2 out of range.');
});
});
suite('interpolateArguments_', function() {
setup(function() {
this.assertInterpolation = function(tokens, args, lastAlign, elements) {
const block = {
type: 'test',
interpolateArguments_: Blockly.Block.prototype.interpolateArguments_,
stringToFieldJson_: Blockly.Block.prototype.stringToFieldJson_,
isInputKeyword_: Blockly.Block.prototype.isInputKeyword_,
};
chai.assert.deepEqual(
block.interpolateArguments_(tokens, args, lastAlign),
elements);
};
});
test('Strings to labels', function() {
this.assertInterpolation(
['test1', 'test2', 'test3', {'type': 'input_dummy'}],
[],
undefined,
[
{
'type': 'field_label',
'text': 'test1',
},
{
'type': 'field_label',
'text': 'test2',
},
{
'type': 'field_label',
'text': 'test3',
},
{
'type': 'input_dummy',
},
]);
});
test('Ignore empty strings', function() {
this.assertInterpolation(
['test1', '', ' ', {'type': 'input_dummy'}],
[],
undefined,
[
{
'type': 'field_label',
'text': 'test1',
},
{
'type': 'input_dummy',
},
]);
});
test('Insert args', function() {
this.assertInterpolation(
[1, 2, 3, {'type': 'input_dummy'}],
[
{
'type': 'field_number',
'name': 'test1',
},
{
'type': 'field_number',
'name': 'test2',
},
{
'type': 'field_number',
'name': 'test3',
},
],
undefined,
[
{
'type': 'field_number',
'name': 'test1',
},
{
'type': 'field_number',
'name': 'test2',
},
{
'type': 'field_number',
'name': 'test3',
},
{
'type': 'input_dummy',
},
]);
});
test('String args to labels', function() {
this.assertInterpolation(
[1, 2, 3, {'type': 'input_dummy'}],
['test1', 'test2', 'test3'],
undefined,
[
{
'type': 'field_label',
'text': 'test1',
},
{
'type': 'field_label',
'text': 'test2',
},
{
'type': 'field_label',
'text': 'test3',
},
{
'type': 'input_dummy',
},
]);
});
test('Ignore empty string args', function() {
this.assertInterpolation(
[1, 2, 3, {'type': 'input_dummy'}],
['test1', ' ', ' '],
undefined,
[
{
'type': 'field_label',
'text': 'test1',
},
{
'type': 'input_dummy',
},
]);
});
test('Add last dummy', function() {
this.assertInterpolation(
['test1', 'test2', 'test3'],
[],
undefined,
[
{
'type': 'field_label',
'text': 'test1',
},
{
'type': 'field_label',
'text': 'test2',
},
{
'type': 'field_label',
'text': 'test3',
},
{
'type': 'input_dummy',
},
]);
});
test('Add last dummy for no_field_prefix_field', function() {
this.assertInterpolation(
[
{
'type': 'no_field_prefix_field',
},
],
[],
undefined,
[
{
'type': 'no_field_prefix_field',
},
{
'type': 'input_dummy',
},
]);
});
test('Add last dummy for input_prefix_field', function() {
this.assertInterpolation(
[
{
'type': 'input_prefix_field',
},
],
[],
undefined,
[
{
'type': 'input_prefix_field',
},
{
'type': 'input_dummy',
},
]);
});
test('Set last dummy alignment', function() {
this.assertInterpolation(
['test1', 'test2', 'test3'],
[],
'CENTER',
[
{
'type': 'field_label',
'text': 'test1',
},
{
'type': 'field_label',
'text': 'test2',
},
{
'type': 'field_label',
'text': 'test3',
},
{
'type': 'input_dummy',
'align': 'CENTER',
},
]);
});
});
suite('fieldFromJson_', function() {
setup(function() {
this.stub = sinon.stub(Blockly.fieldRegistry.TEST_ONLY, 'fromJsonInternal')
.callsFake(function(elem) {
switch (elem['type']) {
case 'field_label':
return 'field_label';
case 'field_number':
return 'field_number';
case 'no_field_prefix_field':
return 'no_field_prefix_field';
case 'input_prefix_field':
return 'input_prefix_field';
default:
return null;
}
});
this.assertField = function(json, expectedType) {
const block = {
type: 'test',
fieldFromJson_: Blockly.Block.prototype.fieldFromJson_,
stringToFieldJson_: Blockly.Block.prototype.stringToFieldJson_,
};
chai.assert.strictEqual(block.fieldFromJson_(json), expectedType);
};
});
teardown(function() {
this.stub.restore();
});
test('Simple field', function() {
this.assertField({
'type': 'field_label',
'text': 'text',
}, 'field_label');
});
test('Bad field', function() {
this.assertField({
'type': 'field_bad',
}, null);
});
test('no_field_prefix_field', function() {
this.assertField({
'type': 'no_field_prefix_field',
}, 'no_field_prefix_field');
});
test('input_prefix_field', function() {
this.assertField({
'type': 'input_prefix_field',
}, 'input_prefix_field');
});
test('Alt string', function() {
this.assertField({
'type': 'field_undefined',
'alt': 'alt text',
}, 'field_label');
});
test('input_prefix_bad w/ alt string', function() {
this.assertField({
'type': 'input_prefix_bad',
'alt': 'alt string',
}, 'field_label');
});
test('Alt other field', function() {
this.assertField({
'type': 'field_undefined',
'alt': {
'type': 'field_number',
'name': 'FIELDNAME',
},
}, 'field_number');
});
test('Deep alt string', function() {
this.assertField({
'type': 'field_undefined1',
'alt': {
'type': 'field_undefined2',
'alt': {
'type': 'field_undefined3',
'alt': {
'type': 'field_undefined4',
'alt': {
'type': 'field_undefined5',
'alt': 'text',
},
},
},
},
}, 'field_label');
});
test('Deep alt other field', function() {
this.assertField({
'type': 'field_undefined1',
'alt': {
'type': 'field_undefined2',
'alt': {
'type': 'field_undefined3',
'alt': {
'type': 'field_undefined4',
'alt': {
'type': 'field_undefined5',
'alt': {
'type': 'field_number',
'name': 'FIELDNAME',
},
},
},
},
},
}, 'field_number');
});
test('No alt', function() {
this.assertField({
'type': 'field_undefined',
}, null);
});
test('Bad alt', function() {
this.assertField({
'type': 'field_undefined',
'alt': {
'type': 'field_undefined',
},
}, null);
});
test('Spaces string alt', function() {
this.assertField({
'type': 'field_undefined',
'alt': ' ',
}, null);
});
});
suite('inputFromJson_', function() {
setup(function() {
this.assertInput = function(json, type, check, align) {
const block = this.workspace.newBlock('test_basic_empty');
sinon.spy(block, 'appendDummyInput');
sinon.spy(block, 'appendValueInput');
sinon.spy(block, 'appendStatementInput');
const input = block.inputFromJson_(json);
switch (type) {
case 'input_dummy':
chai.assert.isTrue(block.appendDummyInput.calledOnce,
'Expected a dummy input to be created.');
break;
case 'input_value':
chai.assert.isTrue(block.appendValueInput.calledOnce,
'Expected a value input to be created.');
break;
case 'input_statement':
chai.assert.isTrue(block.appendStatementInput.calledOnce,
'Expected a statement input to be created.');
break;
default:
chai.assert.isNull(input, 'Expected input to be null');
chai.assert.isTrue(block.appendDummyInput.notCalled,
'Expected no input to be created');
chai.assert.isTrue(block.appendValueInput.notCalled,
'Expected no input to be created');
chai.assert.isTrue(block.appendStatementInput.notCalled,
'Expected no input to be created');
return;
}
if (check) {
if (Array.isArray(check)) {
chai.assert.deepEqual(check, input.connection.getCheck());
} else {
chai.assert.deepEqual([check], input.connection.getCheck());
}
}
if (align !== undefined) {
chai.assert.equal(align, input.align);
}
};
});
suite('input types', function() {
test('Dummy', function() {
this.assertInput(
{
'type': 'input_dummy',
},
'input_dummy');
});
test('Value', function() {
this.assertInput(
{
'type': 'input_value',
'name': 'NAME',
},
'input_value');
});
test('Statement', function() {
this.assertInput(
{
'type': 'input_statement',
'name': 'NAME',
},
'input_statement');
});
test('Bad input type', function() {
this.assertInput(
{
'type': 'input_bad',
},
'input_bad');
});
test('custom input types are constructed from the registry', function() {
class CustomInput extends Blockly.Input { }
Blockly.registry.register(
Blockly.registry.Type.INPUT, 'custom', CustomInput);
const block = this.workspace.newBlock('test_basic_empty');
block.inputFromJson_({'type': 'custom'});
chai.assert.instanceOf(
block.inputList[0],
CustomInput,
'Expected the registered input to be constructed');
});
});
suite('connection checks', function() {
test('String Check', function() {
this.assertInput(
{
'type': 'input_value',
'name': 'NAME',
'check': 'Integer',
},
'input_value',
'Integer');
});
test('Array check', function() {
this.assertInput(
{
'type': 'input_value',
'name': 'NAME',
'check': ['Integer', 'Number'],
},
'input_value',
['Integer', 'Number']);
});
test('Empty check', function() {
this.assertInput(
{
'type': 'input_value',
'name': 'NAME',
'check': '',
},
'input_value');
});
test('Null check', function() {
this.assertInput(
{
'type': 'input_value',
'name': 'NAME',
'check': null,
},
'input_value');
});
});
suite('alignment', function() {
test('"Left" align', function() {
this.assertInput(
{
'type': 'input_dummy',
'align': 'LEFT',
},
'input_dummy', undefined, Align.LEFT);
});
test('"Right" align', function() {
this.assertInput(
{
'type': 'input_dummy',
'align': 'RIGHT',
},
'input_dummy', undefined, Align.RIGHT);
});
test('"Center" align', function() {
this.assertInput(
{
'type': 'input_dummy',
'align': 'CENTER',
},
'input_dummy', undefined, Align.CENTRE);
});
test('"Centre" align', function() {
this.assertInput(
{
'type': 'input_dummy',
'align': 'CENTRE',
},
'input_dummy', undefined, Align.CENTRE);
});
});
});
});