diff --git a/core/input.js b/core/input.js
index ddfa1d772..3da031451 100644
--- a/core/input.js
+++ b/core/input.js
@@ -106,8 +106,9 @@ Blockly.Input.prototype.insertFieldAt = function(index, field, opt_name) {
throw Error('index ' + index + ' out of bounds.');
}
- // Empty string, Null or undefined generates no field, unless field is named.
- if (!field && !opt_name) {
+ // Falsy field values don't generate a field, unless the field is an empty
+ // string and named.
+ if (!field && !(field == '' && opt_name)) {
return index;
}
// Generate a FieldLabel when given a plain text field.
diff --git a/tests/jsunit/index.html b/tests/jsunit/index.html
index 2f037075c..7510fd7eb 100644
--- a/tests/jsunit/index.html
+++ b/tests/jsunit/index.html
@@ -24,7 +24,6 @@
-
diff --git a/tests/jsunit/input_test.js b/tests/jsunit/input_test.js
deleted file mode 100644
index 35bdd4f30..000000000
--- a/tests/jsunit/input_test.js
+++ /dev/null
@@ -1,202 +0,0 @@
-/**
- * @license
- * Visual Blocks Editor
- *
- * Copyright 2017 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.
- */
-
- /**
- * @fileoverview Tests for Blockly.Input
- */
-'use strict';
-
-function test_appendField_simple() {
- var ws = new Blockly.Workspace();
- var block = new Blockly.Block(ws);
- var input = new Blockly.Input(Blockly.DUMMY_INPUT, 'INPUT', block);
- var field1 = new Blockly.FieldLabel('#1');
- var field2 = new Blockly.FieldLabel('#2');
-
- // Preconditions
- assertEquals(0, input.fieldRow.length);
-
- // Actual Tests
- input.appendField(field1, 'first');
- assertEquals(1, input.fieldRow.length);
- assertEquals(field1, input.fieldRow[0]);
- assertEquals('first', input.fieldRow[0].name);
- assertEquals(block, field1.sourceBlock_);
-
- input.appendField(field2, 'second');
- assertEquals(2, input.fieldRow.length);
- assertEquals(field2, input.fieldRow[1]);
- assertEquals('second', input.fieldRow[1].name);
- assertEquals(block, field2.sourceBlock_);
-}
-
-function test_appendField_string() {
- var ws = new Blockly.Workspace();
- var block = new Blockly.Block(ws);
- var input = new Blockly.Input(Blockly.DUMMY_INPUT, 'INPUT', block);
- var labelText = 'label';
-
- // Preconditions
- assertEquals(0, input.fieldRow.length);
-
- // Actual Tests
- input.appendField(labelText, 'name');
- assertEquals(1, input.fieldRow.length);
- assertEquals(Blockly.FieldLabel, input.fieldRow[0].constructor);
- assertEquals(labelText, input.fieldRow[0].getValue());
- assertEquals('name', input.fieldRow[0].name);
-}
-
-function test_appendField_prefix() {
- var ws = new Blockly.Workspace();
- var block = new Blockly.Block(ws);
- var input = new Blockly.Input(Blockly.DUMMY_INPUT, 'INPUT', block);
- var prefix = new Blockly.FieldLabel('prefix');
- var field = new Blockly.FieldLabel('field');
- field.prefixField = prefix;
-
- // Preconditions
- assertEquals(0, input.fieldRow.length);
-
- // Actual Tests
- input.appendField(field);
- assertEquals(2, input.fieldRow.length);
- assertEquals(prefix, input.fieldRow[0]);
- assertEquals(field, input.fieldRow[1]);
-}
-
-function test_appendField_suffix() {
- var ws = new Blockly.Workspace();
- var block = new Blockly.Block(ws);
- var input = new Blockly.Input(Blockly.DUMMY_INPUT, 'INPUT', block);
- var suffix = new Blockly.FieldLabel('suffix');
- var field = new Blockly.FieldLabel('field');
- field.suffixField = suffix;
-
- // Preconditions
- assertEquals(0, input.fieldRow.length);
-
- // Actual Tests
- input.appendField(field);
- assertEquals(2, input.fieldRow.length);
- assertEquals(field, input.fieldRow[0]);
- assertEquals(suffix, input.fieldRow[1]);
-}
-
-function test_insertFieldAt_simple() {
- var ws = new Blockly.Workspace();
- var block = new Blockly.Block(ws);
- var input = new Blockly.Input(Blockly.DUMMY_INPUT, 'INPUT', block);
- var before = new Blockly.FieldLabel('before');
- var after = new Blockly.FieldLabel('after');
- var between = new Blockly.FieldLabel('between');
- input.appendField(before);
- input.appendField(after);
-
- // Preconditions
- assertEquals(2, input.fieldRow.length);
- assertEquals(before, input.fieldRow[0]);
- assertEquals(after, input.fieldRow[1]);
-
- // Actual Tests
- input.insertFieldAt(1, between, 'name');
- assertEquals(3, input.fieldRow.length);
- assertEquals(before, input.fieldRow[0]);
- assertEquals(between, input.fieldRow[1]);
- assertEquals('name', input.fieldRow[1].name);
- assertEquals(after, input.fieldRow[2]);
-}
-
-function test_insertFieldAt_string() {
- var ws = new Blockly.Workspace();
- var block = new Blockly.Block(ws);
- var input = new Blockly.Input(Blockly.DUMMY_INPUT, 'INPUT', block);
- var before = new Blockly.FieldLabel('before');
- var after = new Blockly.FieldLabel('after');
- var labelText = 'label';
- input.appendField(before);
- input.appendField(after);
-
- // Preconditions
- assertEquals(2, input.fieldRow.length);
- assertEquals(before, input.fieldRow[0]);
- assertEquals(after, input.fieldRow[1]);
-
- // Actual Tests
- input.insertFieldAt(1, labelText, 'name');
- assertEquals(3, input.fieldRow.length);
- assertEquals(before, input.fieldRow[0]);
- assertEquals(Blockly.FieldLabel, input.fieldRow[1].constructor);
- assertEquals(labelText, input.fieldRow[1].getValue());
- assertEquals('name', input.fieldRow[1].name);
- assertEquals(after, input.fieldRow[2]);
-}
-
-function test_insertFieldAt_prefix() {
- var ws = new Blockly.Workspace();
- var block = new Blockly.Block(ws);
- var input = new Blockly.Input(Blockly.DUMMY_INPUT, 'INPUT', block);
- var before = new Blockly.FieldLabel('before');
- var after = new Blockly.FieldLabel('after');
- var prefix = new Blockly.FieldLabel('prefix');
- var between = new Blockly.FieldLabel('between');
- between.prefixField = prefix;
- input.appendField(before);
- input.appendField(after);
-
- // Preconditions
- assertEquals(2, input.fieldRow.length);
- assertEquals(before, input.fieldRow[0]);
- assertEquals(after, input.fieldRow[1]);
-
- // Actual Tests
- input.insertFieldAt(1, between);
- assertEquals(4, input.fieldRow.length);
- assertEquals(before, input.fieldRow[0]);
- assertEquals(prefix, input.fieldRow[1]);
- assertEquals(between, input.fieldRow[2]);
- assertEquals(after, input.fieldRow[3]);
-}
-
-function test_insertFieldAt_suffix() {
- var ws = new Blockly.Workspace();
- var block = new Blockly.Block(ws);
- var input = new Blockly.Input(Blockly.DUMMY_INPUT, 'INPUT', block);
- var before = new Blockly.FieldLabel('before');
- var after = new Blockly.FieldLabel('after');
- var suffix = new Blockly.FieldLabel('suffix');
- var between = new Blockly.FieldLabel('between');
- between.suffixField = suffix;
- input.appendField(before);
- input.appendField(after);
-
- // Preconditions
- assertEquals(2, input.fieldRow.length);
- assertEquals(before, input.fieldRow[0]);
- assertEquals(after, input.fieldRow[1]);
-
- // Actual Tests
- input.insertFieldAt(1, between);
- assertEquals(4, input.fieldRow.length);
- assertEquals(before, input.fieldRow[0]);
- assertEquals(between, input.fieldRow[1]);
- assertEquals(suffix, input.fieldRow[2]);
- assertEquals(after, input.fieldRow[3]);
-}
diff --git a/tests/mocha/index.html b/tests/mocha/index.html
index 862398539..49339b471 100644
--- a/tests/mocha/index.html
+++ b/tests/mocha/index.html
@@ -45,6 +45,7 @@
+
diff --git a/tests/mocha/input_test.js b/tests/mocha/input_test.js
new file mode 100644
index 000000000..b4f95f8b6
--- /dev/null
+++ b/tests/mocha/input_test.js
@@ -0,0 +1,311 @@
+/**
+ * @license
+ * Visual Blocks Editor
+ *
+ * Copyright 2019 Google Inc.
+ * https://developers.google.com/blockly/
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+suite('Inputs', function() {
+ setup(function() {
+ Blockly.defineBlocksWithJsonArray([
+ {
+ "type": "empty_block",
+ "message0": "",
+ "args0": []
+ },
+ ]);
+
+ this.workspace = Blockly.inject('blocklyDiv');
+ this.block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
+ ''
+ ), this.workspace);
+
+ this.renderStub = sinon.stub(this.block, 'render');
+ this.bumpNeighboursStub = sinon.stub(this.block, 'bumpNeighbours_');
+
+ this.dummy = this.block.appendDummyInput('DUMMY');
+ this.value = this.block.appendValueInput('VALUE');
+ this.statement = this.block.appendStatementInput('STATEMENT');
+
+ this.renderStub.resetHistory();
+ this.bumpNeighboursStub.resetHistory();
+ });
+ teardown(function() {
+ this.renderStub.restore();
+ this.bumpNeighboursStub.restore();
+
+ delete Blockly.Blocks['empty_block'];
+ this.workspace.dispose();
+ });
+ suite('Insert Field At', function() {
+ suite('Index Bounds', function() {
+ test('< 0', function() {
+ var field = new Blockly.FieldLabel('field');
+ chai.assert.throws(function() {
+ this.dummy.insertFieldAt(-1, field);
+ });
+ });
+ test('> length', function() {
+ var field = new Blockly.FieldLabel('field');
+ chai.assert.throws(function() {
+ this.dummy.insertFieldAt(1, field);
+ });
+ });
+ });
+ suite('Values', function() {
+ // We're mostly just testing that it doesn't throw errors.
+ test('Field', function() {
+ var field = new Blockly.FieldLabel('field');
+ this.dummy.insertFieldAt(0, field);
+ chai.assert.equal(this.dummy.fieldRow[0], field);
+ });
+ test('String', function() {
+ this.dummy.insertFieldAt(0, 'field');
+ chai.assert.instanceOf(this.dummy.fieldRow[0], Blockly.FieldLabel);
+ });
+ test('Empty String', function() {
+ this.dummy.insertFieldAt(0, '');
+ chai.assert.isEmpty(this.dummy.fieldRow);
+ });
+ test('Empty String W/ Name', function() {
+ this.dummy.insertFieldAt(0, '', 'NAME');
+ chai.assert.instanceOf(this.dummy.fieldRow[0], Blockly.FieldLabel);
+ });
+ test('Null', function() {
+ this.dummy.insertFieldAt(0, null);
+ chai.assert.isEmpty(this.dummy.fieldRow);
+ });
+ test('Undefined', function() {
+ this.dummy.insertFieldAt(0, undefined);
+ chai.assert.isEmpty(this.dummy.fieldRow);
+ });
+ });
+ suite('Prefixes and Suffixes', function() {
+ test('Prefix', function() {
+ var field = new Blockly.FieldLabel('field');
+ var prefix = new Blockly.FieldLabel('prefix');
+ field.prefixField = prefix;
+
+ this.dummy.appendField(field);
+ chai.assert.deepEqual(this.dummy.fieldRow, [prefix, field]);
+ });
+ test('Suffix', function() {
+ var field = new Blockly.FieldLabel('field');
+ var suffix = new Blockly.FieldLabel('suffix');
+ field.suffixField = suffix;
+
+ this.dummy.appendField(field);
+ chai.assert.deepEqual(this.dummy.fieldRow, [field, suffix]);
+ });
+ test('Prefix and Suffix', function() {
+ var field = new Blockly.FieldLabel('field');
+ var prefix = new Blockly.FieldLabel('prefix');
+ var suffix = new Blockly.FieldLabel('suffix');
+ field.prefixField = prefix;
+ field.suffixField = suffix;
+
+ this.dummy.appendField(field);
+ chai.assert.deepEqual(this.dummy.fieldRow, [prefix, field, suffix]);
+ });
+ test('Dropdown - Prefix', function() {
+ var field = new Blockly.FieldDropdown(
+ [
+ ['prefix option1', 'OPTION1'],
+ ['prefix option2', 'OPTION2']
+ ]
+ );
+
+ this.dummy.appendField(field);
+ chai.assert.equal(this.dummy.fieldRow.length, 2);
+ });
+ test('Dropdown - Suffix', function() {
+ var field = new Blockly.FieldDropdown(
+ [
+ ['option1 suffix', 'OPTION1'],
+ ['option2 suffix', 'OPTION2']
+ ]
+ );
+
+ this.dummy.appendField(field);
+ chai.assert.equal(this.dummy.fieldRow.length, 2);
+ });
+ test('Dropdown - Prefix and Suffix', function() {
+ var field = new Blockly.FieldDropdown(
+ [
+ ['prefix option1 suffix', 'OPTION1'],
+ ['prefix option2 suffix', 'OPTION2']
+ ]
+ );
+
+ this.dummy.appendField(field);
+ chai.assert.equal(this.dummy.fieldRow.length, 3);
+ });
+ });
+ suite('Field Initialization', function() {
+ test('Rendered', function() {
+ var field = new Blockly.FieldLabel('field');
+ var setBlockSpy = sinon.spy(field, 'setSourceBlock');
+ var initSpy = sinon.spy(field, 'init');
+
+ this.dummy.insertFieldAt(0, field);
+ chai.assert(setBlockSpy.calledOnce);
+ chai.assert.equal(setBlockSpy.getCall(0).args[0], this.block);
+ chai.assert(initSpy.calledOnce);
+ console.log(this.renderStub.callCount);
+ chai.assert(this.renderStub.calledOnce);
+ chai.assert(this.bumpNeighboursStub.calledOnce);
+
+ setBlockSpy.restore();
+ initSpy.restore();
+ });
+ // TODO: InsertFieldAt does not properly handle initialization in
+ // headless mode.
+ test.skip('Headless', function() {
+ var field = new Blockly.FieldLabel('field');
+ var setBlockSpy = sinon.spy(field, 'setSourceBlock');
+ var initModelSpy = sinon.spy(field, 'initModel');
+
+ this.block.rendered = false;
+
+ this.dummy.insertFieldAt(0, field);
+ chai.assert(setBlockSpy.calledOnce);
+ chai.assert.equal(setBlockSpy.getCall(0).args[0], this.block);
+ chai.assert(initModelSpy.calledOnce);
+ chai.assert(this.renderStub.notCalled);
+ chai.assert(this.bumpNeighboursStub.notCalled);
+
+ setBlockSpy.restore();
+ initModelSpy.restore();
+ });
+ });
+ });
+ suite('Remove Field', function() {
+ test('Field Not Found', function() {
+ chai.assert.throws(function() {
+ this.dummy.removeField('FIELD');
+ });
+ });
+ test('Rendered', function() {
+ var field = new Blockly.FieldLabel('field');
+ var disposeSpy = sinon.spy(field, 'dispose');
+ this.dummy.appendField(field, 'FIELD');
+
+ this.renderStub.resetHistory();
+ this.bumpNeighboursStub.resetHistory();
+
+ this.dummy.removeField('FIELD');
+ chai.assert(disposeSpy.calledOnce);
+ chai.assert(this.renderStub.calledOnce);
+ chai.assert(this.bumpNeighboursStub.calledOnce);
+ });
+ test('Headless', function() {
+ var field = new Blockly.FieldLabel('field');
+ var disposeSpy = sinon.spy(field, 'dispose');
+ this.dummy.appendField(field, 'FIELD');
+
+ this.renderStub.resetHistory();
+ this.bumpNeighboursStub.resetHistory();
+
+ this.block.rendered = false;
+
+ this.dummy.removeField('FIELD');
+ chai.assert(disposeSpy.calledOnce);
+ chai.assert(this.renderStub.notCalled);
+ chai.assert(this.bumpNeighboursStub.notCalled);
+ });
+ });
+ suite('Field Ordering/Manipulation', function() {
+ setup(function() {
+ this.a = new Blockly.FieldLabel('a');
+ this.b = new Blockly.FieldLabel('b');
+ this.c = new Blockly.FieldLabel('c');
+ });
+ test('Append A, B, C', function() {
+ this.dummy.appendField(this.a, 'A');
+ this.dummy.appendField(this.b, 'B');
+ this.dummy.appendField(this.c, 'C');
+
+ chai.assert.deepEqual(this.dummy.fieldRow, [this.a, this.b, this.c]);
+ });
+ test('Append B, C; Insert A at Start', function() {
+ this.dummy.appendField(this.b, 'B');
+ this.dummy.appendField(this.c, 'C');
+ this.dummy.insertFieldAt(0, this.a, 'A');
+
+ chai.assert.deepEqual(this.dummy.fieldRow, [this.a, this.b, this.c]);
+ });
+ test('Append A, C; Insert B Between', function() {
+ this.dummy.appendField(this.a, 'A');
+ this.dummy.appendField(this.c, 'C');
+ this.dummy.insertFieldAt(1, this.b, 'B');
+
+ chai.assert.deepEqual(this.dummy.fieldRow, [this.a, this.b, this.c]);
+ });
+ test('Append A, B; Insert C at End', function() {
+ this.dummy.appendField(this.a, 'A');
+ this.dummy.appendField(this.b, 'B');
+ this.dummy.insertFieldAt(2, this.c, 'C');
+
+ chai.assert.deepEqual(this.dummy.fieldRow, [this.a, this.b, this.c]);
+ });
+ test('Append A, B, C; Remove A, B, C', function() {
+ this.dummy.appendField(this.a, 'A');
+ this.dummy.appendField(this.b, 'B');
+ this.dummy.appendField(this.c, 'C');
+
+ this.dummy.removeField('A');
+ this.dummy.removeField('B');
+ this.dummy.removeField('C');
+
+ chai.assert.isEmpty(this.dummy.fieldRow);
+ });
+ test('Append A, B, C; Remove A', function() {
+ this.dummy.appendField(this.a, 'A');
+ this.dummy.appendField(this.b, 'B');
+ this.dummy.appendField(this.c, 'C');
+
+ this.dummy.removeField('A');
+
+ chai.assert.deepEqual(this.dummy.fieldRow, [this.b, this.c]);
+ });
+ test('Append A, B, C; Remove B', function() {
+ this.dummy.appendField(this.a, 'A');
+ this.dummy.appendField(this.b, 'B');
+ this.dummy.appendField(this.c, 'C');
+
+ this.dummy.removeField('B');
+
+ chai.assert.deepEqual(this.dummy.fieldRow, [this.a, this.c]);
+ });
+ test('Append A, B, C; Remove C', function() {
+ this.dummy.appendField(this.a, 'A');
+ this.dummy.appendField(this.b, 'B');
+ this.dummy.appendField(this.c, 'C');
+
+ this.dummy.removeField('C');
+
+ chai.assert.deepEqual(this.dummy.fieldRow, [this.a, this.b]);
+ });
+ test('Append A, B; Remove A; Append C', function() {
+ this.dummy.appendField(this.a, 'A');
+ this.dummy.appendField(this.b, 'B');
+ this.dummy.removeField('A');
+ this.dummy.appendField(this.c, 'C');
+
+ chai.assert.deepEqual(this.dummy.fieldRow, [this.b, this.c]);
+ });
+ });
+});