diff --git a/core/field_angle.js b/core/field_angle.js
index 778ebe066..c4c292799 100644
--- a/core/field_angle.js
+++ b/core/field_angle.js
@@ -241,8 +241,6 @@ Blockly.FieldAngle.prototype.configure_ = function(config) {
}
};
-
-
/**
* Create the block UI for this field.
* @package
@@ -256,6 +254,26 @@ Blockly.FieldAngle.prototype.initView = function() {
this.textElement_.appendChild(this.symbol_);
};
+/**
+ * Saves this field's value.
+ * @return {number} The angle value held by this field.
+ * @override
+ * @package
+ */
+Blockly.FieldAngle.prototype.saveState = function() {
+ return /** @type {number} */ (this.getValue());
+};
+
+/**
+ * Sets the field's value based on the given state.
+ * @param {*} state The state to apply to the angle field.
+ * @override
+ * @package
+ */
+Blockly.FieldAngle.prototype.loadState = function(state) {
+ this.setValue(state);
+};
+
/**
* Updates the graph when the field rerenders.
* @protected
diff --git a/core/field_checkbox.js b/core/field_checkbox.js
index 207b29f7f..3214c97ae 100644
--- a/core/field_checkbox.js
+++ b/core/field_checkbox.js
@@ -100,6 +100,26 @@ Blockly.FieldCheckbox.prototype.configure_ = function(config) {
}
};
+/**
+ * Saves this field's value.
+ * @return {boolean} The boolean value held by this field.
+ * @override
+ * @package
+ */
+Blockly.FieldCheckbox.prototype.saveState = function() {
+ return /** @type {boolean} */ (this.getValueBoolean());
+};
+
+/**
+ * Sets the field's value based on the given state.
+ * @param {*} state The state to apply to the checkbox field.
+ * @override
+ * @package
+ */
+Blockly.FieldCheckbox.prototype.loadState = function(state) {
+ this.setValue(state);
+};
+
/**
* Create the block UI for this checkbox.
* @package
diff --git a/core/field_colour.js b/core/field_colour.js
index 17d593f0e..fad70d9ad 100644
--- a/core/field_colour.js
+++ b/core/field_colour.js
@@ -185,6 +185,26 @@ Blockly.FieldColour.prototype.initView = function() {
}
};
+/**
+ * Saves this field's value.
+ * @return {string} The colour value held by this field.
+ * @override
+ * @package
+ */
+Blockly.FieldColour.prototype.saveState = function() {
+ return /** @type {string} */ (this.getValue());
+};
+
+/**
+ * Sets the field's value based on the given state.
+ * @param {*} state The state to apply to the colour field.
+ * @override
+ * @package
+ */
+Blockly.FieldColour.prototype.loadState = function(state) {
+ this.setValue(state);
+};
+
/**
* @override
*/
diff --git a/core/field_dropdown.js b/core/field_dropdown.js
index 5dad752d8..8a3461596 100644
--- a/core/field_dropdown.js
+++ b/core/field_dropdown.js
@@ -168,6 +168,29 @@ Blockly.FieldDropdown.prototype.fromXml = function(fieldElement) {
this.setValue(fieldElement.textContent);
};
+/**
+ * Saves this field's value.
+ * @return {string} The dropdown value held by this field.
+ * @override
+ * @package
+ */
+Blockly.FieldDropdown.prototype.saveState = function() {
+ return /** @type {string} */ (this.getValue());
+};
+
+/**
+ * Sets the field's value based on the given state.
+ * @param {*} state The state to apply to the dropdown field.
+ * @override
+ * @package
+ */
+Blockly.FieldDropdown.prototype.loadState = function(state) {
+ if (this.isOptionListDynamic()) {
+ this.getOptions(false);
+ }
+ this.setValue(state);
+};
+
/**
* Serializable fields are saved by the XML renderer, non-serializable fields
* are not. Editable fields should also be serializable.
diff --git a/core/field_label_serializable.js b/core/field_label_serializable.js
index 0201db130..5774ba776 100644
--- a/core/field_label_serializable.js
+++ b/core/field_label_serializable.js
@@ -67,5 +67,25 @@ Blockly.FieldLabelSerializable.prototype.EDITABLE = false;
*/
Blockly.FieldLabelSerializable.prototype.SERIALIZABLE = true;
+/**
+ * Saves this field's value.
+ * @return {string} The text value held by this field.
+ * @override
+ * @package
+ */
+Blockly.FieldLabelSerializable.prototype.saveState = function() {
+ return /** @type {string} */ (this.getValue());
+};
+
+/**
+ * Sets the field's value based on the given state.
+ * @param {*} state The state to apply to the label field.
+ * @override
+ * @package
+ */
+Blockly.FieldLabelSerializable.prototype.loadState = function(state) {
+ this.setValue(state);
+};
+
Blockly.fieldRegistry.register(
'field_label_serializable', Blockly.FieldLabelSerializable);
diff --git a/core/field_multilineinput.js b/core/field_multilineinput.js
index 887d4007f..14a5640d7 100644
--- a/core/field_multilineinput.js
+++ b/core/field_multilineinput.js
@@ -121,6 +121,26 @@ Blockly.FieldMultilineInput.prototype.fromXml = function(fieldElement) {
this.setValue(fieldElement.textContent.replace(/
/g, '\n'));
};
+/**
+ * Saves this field's value.
+ * @return {string} The text value held by this field.
+ * @override
+ * @package
+ */
+Blockly.FieldMultilineInput.prototype.saveState = function() {
+ return /** @type {string} */ (this.getValue());
+};
+
+/**
+ * Sets the field's value based on the given state.
+ * @param {*} state The state to apply to the multiline input field.
+ * @override
+ * @package
+ */
+Blockly.FieldMultilineInput.prototype.loadState = function(state) {
+ this.setValue(state);
+};
+
/**
* Create the block UI for this field.
* @package
diff --git a/core/field_number.js b/core/field_number.js
index 369a161f8..bb11a5121 100644
--- a/core/field_number.js
+++ b/core/field_number.js
@@ -117,6 +117,26 @@ Blockly.FieldNumber.prototype.configure_ = function(config) {
this.setPrecisionInternal_(config['precision']);
};
+/**
+ * Saves this field's value.
+ * @return {number} The number value held by this field.
+ * @override
+ * @package
+ */
+Blockly.FieldNumber.prototype.saveState = function() {
+ return /** @type {number} */ (this.getValue());
+};
+
+/**
+ * Sets the field's value based on the given state.
+ * @param {*} state The state to apply to the nuber field.
+ * @override
+ * @package
+ */
+Blockly.FieldNumber.prototype.loadState = function(state) {
+ this.setValue(state);
+};
+
/**
* Set the maximum, minimum and precision constraints on this field.
* Any of these properties may be undefined or NaN to be disabled.
diff --git a/core/field_textinput.js b/core/field_textinput.js
index b2153b65e..a2000303e 100644
--- a/core/field_textinput.js
+++ b/core/field_textinput.js
@@ -178,6 +178,26 @@ Blockly.FieldTextInput.prototype.initView = function() {
this.createTextElement_();
};
+/**
+ * Saves this field's value.
+ * @return {*} The text value held by this field.
+ * @override
+ * @package
+ */
+Blockly.FieldTextInput.prototype.saveState = function() {
+ return this.getValue();
+};
+
+/**
+ * Sets the field's value based on the given state.
+ * @param {*} state The state to apply to the text input field.
+ * @override
+ * @package
+ */
+Blockly.FieldTextInput.prototype.loadState = function(state) {
+ this.setValue(state);
+};
+
/**
* Ensure that the input value casts to a valid string.
* @param {*=} opt_newValue The input value.
diff --git a/core/field_variable.js b/core/field_variable.js
index 18f2d20b4..51c27ec39 100644
--- a/core/field_variable.js
+++ b/core/field_variable.js
@@ -194,6 +194,28 @@ Blockly.FieldVariable.prototype.toXml = function(fieldElement) {
return fieldElement;
};
+/**
+ * Saves this field's value.
+ * @return {string} The id of the variable referenced by this field.
+ * @override
+ * @package
+ */
+Blockly.FieldVariable.prototype.saveState = function() {
+ // Make sure the variable is initialized.
+ this.initModel();
+ return this.variable_.getId();
+};
+
+/**
+ * Sets the field's value based on the given state.
+ * @param {*} id The id of the variable to assign to this variable field.
+ * @override
+ * @package
+ */
+Blockly.FieldVariable.prototype.loadState = function(id) {
+ this.setValue(id);
+};
+
/**
* Attach this field to a block.
* @param {!Blockly.Block} block The block containing this field.
diff --git a/tests/mocha/field_angle_test.js b/tests/mocha/field_angle_test.js
index 7a1eb928c..1b2e5926f 100644
--- a/tests/mocha/field_angle_test.js
+++ b/tests/mocha/field_angle_test.js
@@ -310,4 +310,35 @@ suite('Angle Fields', function() {
});
});
});
+
+ suite('Serialization', function() {
+ setup(function() {
+ this.workspace = new Blockly.Workspace();
+ defineRowBlock();
+
+ this.assertValue = (value) => {
+ const block = this.workspace.newBlock('row_block');
+ const field = new Blockly.FieldAngle(value);
+ block.getInput('INPUT').appendField(field, 'ANGLE');
+ const jso = Blockly.serialization.blocks.save(block);
+ chai.assert.deepEqual(jso['fields'], {'ANGLE': value});
+ };
+ });
+
+ teardown(function() {
+ workspaceTeardown.call(this, this.workspace);
+ });
+
+ test('Simple', function() {
+ this.assertValue(90);
+ });
+
+ test('Max precision', function() {
+ this.assertValue(1.000000000000001);
+ });
+
+ test('Smallest number', function() {
+ this.assertValue(5e-324);
+ });
+ });
});
diff --git a/tests/mocha/field_checkbox_test.js b/tests/mocha/field_checkbox_test.js
index 5a10ef57f..3f0fb5426 100644
--- a/tests/mocha/field_checkbox_test.js
+++ b/tests/mocha/field_checkbox_test.js
@@ -204,4 +204,31 @@ suite('Checkbox Fields', function() {
});
});
});
+
+ suite('Serialization', function() {
+ setup(function() {
+ this.workspace = new Blockly.Workspace();
+ defineRowBlock();
+
+ this.assertValue = (value) => {
+ const block = this.workspace.newBlock('row_block');
+ const field = new Blockly.FieldCheckbox(value);
+ block.getInput('INPUT').appendField(field, 'CHECK');
+ const jso = Blockly.serialization.blocks.save(block);
+ chai.assert.deepEqual(jso['fields'], {'CHECK': value});
+ };
+ });
+
+ teardown(function() {
+ workspaceTeardown.call(this, this.workspace);
+ });
+
+ test('True', function() {
+ this.assertValue(true);
+ });
+
+ test('False', function() {
+ this.assertValue(false);
+ });
+ });
});
diff --git a/tests/mocha/field_colour_test.js b/tests/mocha/field_colour_test.js
index e779bf831..b3c36f962 100644
--- a/tests/mocha/field_colour_test.js
+++ b/tests/mocha/field_colour_test.js
@@ -277,4 +277,31 @@ suite('Colour Fields', function() {
});
});
});
+
+ suite('Serialization', function() {
+ setup(function() {
+ this.workspace = new Blockly.Workspace();
+ defineRowBlock();
+
+ this.assertValue = (value) => {
+ const block = this.workspace.newBlock('row_block');
+ const field = new Blockly.FieldColour(value);
+ block.getInput('INPUT').appendField(field, 'COLOUR');
+ const jso = Blockly.serialization.blocks.save(block);
+ chai.assert.deepEqual(jso['fields'], {'COLOUR': value});
+ };
+ });
+
+ teardown(function() {
+ workspaceTeardown.call(this, this.workspace);
+ });
+
+ test('Three char', function() {
+ this.assertValue('#001122');
+ });
+
+ test('Six char', function() {
+ this.assertValue('#012345');
+ });
+ });
});
diff --git a/tests/mocha/field_dropdown_test.js b/tests/mocha/field_dropdown_test.js
index 95ad8eacf..c166b20d8 100644
--- a/tests/mocha/field_dropdown_test.js
+++ b/tests/mocha/field_dropdown_test.js
@@ -152,4 +152,36 @@ suite('Dropdown Fields', function() {
});
});
});
+
+ suite('Serialization', function() {
+ setup(function() {
+ this.workspace = new Blockly.Workspace();
+ defineRowBlock();
+
+
+ this.assertValue = (value, field) => {
+ const block = this.workspace.newBlock('row_block');
+ field.setValue(value);
+ block.getInput('INPUT').appendField(field, 'DROPDOWN');
+ const jso = Blockly.serialization.blocks.save(block);
+ chai.assert.deepEqual(jso['fields'], {'DROPDOWN': value});
+ };
+ });
+
+ teardown(function() {
+ workspaceTeardown.call(this, this.workspace);
+ });
+
+ test('Simple', function() {
+ const field = new Blockly.FieldDropdown(
+ [['apple', 'A'], ['ball', 'B'], ['carrot', 'C']]);
+ this.assertValue('C', field);
+ });
+
+ test('Dynamic', function() {
+ const field = new Blockly.FieldDropdown(
+ () => [['apple', 'A'], ['ball', 'B'], ['carrot', 'C']]);
+ this.assertValue('C', field);
+ });
+ });
});
diff --git a/tests/mocha/field_label_serializable_test.js b/tests/mocha/field_label_serializable_test.js
index 330241503..67eaadc14 100644
--- a/tests/mocha/field_label_serializable_test.js
+++ b/tests/mocha/field_label_serializable_test.js
@@ -181,4 +181,27 @@ suite('Label Serializable Fields', function() {
});
});
});
+
+ suite('Serialization', function() {
+ setup(function() {
+ this.workspace = new Blockly.Workspace();
+ defineRowBlock();
+
+ this.assertValue = (value) => {
+ const block = this.workspace.newBlock('row_block');
+ const field = new Blockly.FieldLabelSerializable(value);
+ block.getInput('INPUT').appendField(field, 'LABEL');
+ const jso = Blockly.serialization.blocks.save(block);
+ chai.assert.deepEqual(jso['fields'], {'LABEL': value});
+ };
+ });
+
+ teardown(function() {
+ workspaceTeardown.call(this, this.workspace);
+ });
+
+ test('Simple', function() {
+ this.assertValue('test label');
+ });
+ });
});
diff --git a/tests/mocha/field_multilineinput_test.js b/tests/mocha/field_multilineinput_test.js
index 844593f86..cf952cab2 100644
--- a/tests/mocha/field_multilineinput_test.js
+++ b/tests/mocha/field_multilineinput_test.js
@@ -153,4 +153,31 @@ suite('Multiline Input Fields', function() {
];
testHelpers.runCodeGenerationTestSuites(testSuites);
});
+
+ suite('Serialization', function() {
+ setup(function() {
+ this.workspace = new Blockly.Workspace();
+ defineRowBlock();
+
+ this.assertValue = (value) => {
+ const block = this.workspace.newBlock('row_block');
+ const field = new Blockly.FieldMultilineInput(value);
+ block.getInput('INPUT').appendField(field, 'MULTILINE');
+ const jso = Blockly.serialization.blocks.save(block);
+ chai.assert.deepEqual(jso['fields'], {'MULTILINE': value});
+ };
+ });
+
+ teardown(function() {
+ workspaceTeardown.call(this, this.workspace);
+ });
+
+ test('Single line', function() {
+ this.assertValue('this is a single line');
+ });
+
+ test('Multiple lines', function() {
+ this.assertValue('this\nis\n multiple\n lines');
+ });
+ });
});
diff --git a/tests/mocha/field_number_test.js b/tests/mocha/field_number_test.js
index 338aa4ad0..7c14e8637 100644
--- a/tests/mocha/field_number_test.js
+++ b/tests/mocha/field_number_test.js
@@ -332,4 +332,43 @@ suite('Number Fields', function() {
});
});
});
+
+ suite('Serialization', function() {
+ setup(function() {
+ this.workspace = new Blockly.Workspace();
+ defineRowBlock();
+
+ this.assertValue = (value) => {
+ const block = this.workspace.newBlock('row_block');
+ const field = new Blockly.FieldNumber(value);
+ block.getInput('INPUT').appendField(field, 'NUMBER');
+ const jso = Blockly.serialization.blocks.save(block);
+ chai.assert.deepEqual(jso['fields'], {'NUMBER': value});
+ };
+ });
+
+ teardown(function() {
+ workspaceTeardown.call(this, this.workspace);
+ });
+
+ test('Simple', function() {
+ this.assertValue(10);
+ });
+
+ test('Max precision small', function() {
+ this.assertValue(1.000000000000001);
+ });
+
+ test('Max precision large', function() {
+ this.assertValue(1000000000000001);
+ });
+
+ test('Smallest', function() {
+ this.assertValue(5e-324);
+ });
+
+ test('Largest', function() {
+ this.assertValue(1.7976931348623157e+308);
+ });
+ });
});
diff --git a/tests/mocha/field_textinput_test.js b/tests/mocha/field_textinput_test.js
index 1a1c1ed2a..ec55c1622 100644
--- a/tests/mocha/field_textinput_test.js
+++ b/tests/mocha/field_textinput_test.js
@@ -215,4 +215,27 @@ suite('Text Input Fields', function() {
});
});
});
+
+ suite('Serialization', function() {
+ setup(function() {
+ this.workspace = new Blockly.Workspace();
+ defineRowBlock();
+
+ this.assertValue = (value) => {
+ const block = this.workspace.newBlock('row_block');
+ const field = new Blockly.FieldTextInput(value);
+ block.getInput('INPUT').appendField(field, 'TEXT');
+ const jso = Blockly.serialization.blocks.save(block);
+ chai.assert.deepEqual(jso['fields'], {'TEXT': value});
+ };
+ });
+
+ teardown(function() {
+ workspaceTeardown.call(this, this.workspace);
+ });
+
+ test('Simple', function() {
+ this.assertValue('test text');
+ });
+ });
});
diff --git a/tests/mocha/field_variable_test.js b/tests/mocha/field_variable_test.js
index 0048b8e09..dff437f8d 100644
--- a/tests/mocha/field_variable_test.js
+++ b/tests/mocha/field_variable_test.js
@@ -377,4 +377,33 @@ suite('Variable Fields', function() {
chai.assert.equal(this.variableField.getValue(), 'id2');
});
});
+
+ suite('Serialization', function() {
+ setup(function() {
+ this.workspace = new Blockly.Workspace();
+ defineRowBlock();
+ createGenUidStubWithReturns(new Array(10).fill().map((_, i) => 'id' + i));
+ });
+
+ teardown(function() {
+ workspaceTeardown.call(this, this.workspace);
+ });
+
+ test('Untyped', function() {
+ const block = this.workspace.newBlock('row_block');
+ const field = new Blockly.FieldVariable('x');
+ block.getInput('INPUT').appendField(field, 'VAR');
+ const jso = Blockly.serialization.blocks.save(block);
+ chai.assert.deepEqual(jso['fields'], {'VAR': 'id2'});
+ });
+
+ test('Typed', function() {
+ const block = this.workspace.newBlock('row_block');
+ const field =
+ new Blockly.FieldVariable('x', undefined, undefined, ['String']);
+ block.getInput('INPUT').appendField(field, 'VAR');
+ const jso = Blockly.serialization.blocks.save(block);
+ chai.assert.deepEqual(jso['fields'], {'VAR': 'id2'});
+ });
+ });
});
diff --git a/tests/mocha/serializer_test.js b/tests/mocha/serializer_test.js
index cfac118ae..8a77e704d 100644
--- a/tests/mocha/serializer_test.js
+++ b/tests/mocha/serializer_test.js
@@ -276,9 +276,17 @@ Serializer.Fields.Dropdown.NotDefault = new SerializerTestCase('NotDefault',
'ITEM32' +
'' +
'');
+Serializer.Fields.Dropdown.Dynamic = new SerializerTestCase(
+ 'Dynamic',
+ '' +
+ '' +
+ '0' +
+ '' +
+ '');
Serializer.Fields.Dropdown.testCases = [
Serializer.Fields.Dropdown.Default,
Serializer.Fields.Dropdown.NotDefault,
+ Serializer.Fields.Dropdown.Dynamic,
];