From 8faa360b74b69fab23a0a421b407c6e50ebfffad Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Fri, 6 Aug 2021 06:54:54 -0700 Subject: [PATCH] feat: upgrade fields to use new JSO hooks (#5077) * Upgrade field angle to use new serialization * Upgrade field checkbox to use new serialization * Upgrade field colour to use new serialization * Upgrade field dropdown to use new serialization * Upgrade serializable label field to use new serialization * Upgrade field multiline input to use new serialization * Upgrade field number to use new serialization * Upgrade field text input to use new serialization * Upgrade variable field to use new serialization * Fix type casts * Feedback from PR * Switch to use getValue() --- core/field_angle.js | 22 ++++++++++- core/field_checkbox.js | 20 ++++++++++ core/field_colour.js | 20 ++++++++++ core/field_dropdown.js | 23 +++++++++++ core/field_label_serializable.js | 20 ++++++++++ core/field_multilineinput.js | 20 ++++++++++ core/field_number.js | 20 ++++++++++ core/field_textinput.js | 20 ++++++++++ core/field_variable.js | 22 +++++++++++ tests/mocha/field_angle_test.js | 33 +++++++++++++++- tests/mocha/field_checkbox_test.js | 29 +++++++++++++- tests/mocha/field_colour_test.js | 29 +++++++++++++- tests/mocha/field_dropdown_test.js | 34 +++++++++++++++- tests/mocha/field_label_serializable_test.js | 25 +++++++++++- tests/mocha/field_multilineinput_test.js | 29 +++++++++++++- tests/mocha/field_number_test.js | 41 +++++++++++++++++++- tests/mocha/field_textinput_test.js | 25 +++++++++++- tests/mocha/field_variable_test.js | 31 ++++++++++++++- tests/mocha/serializer_test.js | 8 ++++ 19 files changed, 460 insertions(+), 11 deletions(-) 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 67362fd63..274123995 100644 --- a/core/field_checkbox.js +++ b/core/field_checkbox.js @@ -102,6 +102,26 @@ FieldCheckbox.prototype.configure_ = function(config) { } }; +/** + * Saves this field's value. + * @return {boolean} The boolean value held by this field. + * @override + * @package + */ +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 + */ +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 b05290460..a8ac41e8f 100644 --- a/core/field_colour.js +++ b/core/field_colour.js @@ -188,6 +188,26 @@ FieldColour.prototype.initView = function() { } }; +/** + * Saves this field's value. + * @return {string} The colour value held by this field. + * @override + * @package + */ +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 + */ +FieldColour.prototype.loadState = function(state) { + this.setValue(state); +}; + /** * @override */ diff --git a/core/field_dropdown.js b/core/field_dropdown.js index effd6a31d..0a1a1013a 100644 --- a/core/field_dropdown.js +++ b/core/field_dropdown.js @@ -170,6 +170,29 @@ 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 + */ +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 + */ +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 dee9da1ef..df2119c15 100644 --- a/core/field_label_serializable.js +++ b/core/field_label_serializable.js @@ -68,6 +68,26 @@ FieldLabelSerializable.prototype.EDITABLE = false; */ FieldLabelSerializable.prototype.SERIALIZABLE = true; +/** + * Saves this field's value. + * @return {string} The text value held by this field. + * @override + * @package + */ +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 + */ +FieldLabelSerializable.prototype.loadState = function(state) { + this.setValue(state); +}; + fieldRegistry.register('field_label_serializable', FieldLabelSerializable); exports = FieldLabelSerializable; diff --git a/core/field_multilineinput.js b/core/field_multilineinput.js index 31df9a6fc..c72f9983d 100644 --- a/core/field_multilineinput.js +++ b/core/field_multilineinput.js @@ -122,6 +122,26 @@ 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 + */ +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 + */ +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 4ed5ac5aa..50cbc7fe7 100644 --- a/core/field_number.js +++ b/core/field_number.js @@ -118,6 +118,26 @@ 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 + */ +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 + */ +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 789bc0a96..e07ac22e4 100644 --- a/core/field_textinput.js +++ b/core/field_textinput.js @@ -182,6 +182,26 @@ FieldTextInput.prototype.initView = function() { this.createTextElement_(); }; +/** + * Saves this field's value. + * @return {*} The text value held by this field. + * @override + * @package + */ +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 + */ +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 e98db6f86..eb67b0bf9 100644 --- a/core/field_variable.js +++ b/core/field_variable.js @@ -197,6 +197,28 @@ 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 + */ +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 + */ +FieldVariable.prototype.loadState = function(id) { + this.setValue(id); +}; + /** * Attach this field to a block. * @param {!Block} block The block containing this field. diff --git a/tests/mocha/field_angle_test.js b/tests/mocha/field_angle_test.js index 0ff4c4d15..7ec50fade 100644 --- a/tests/mocha/field_angle_test.js +++ b/tests/mocha/field_angle_test.js @@ -6,7 +6,7 @@ goog.module('Blockly.test.fieldAngle'); -const {createTestBlock, sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers'); +const {createTestBlock, defineRowBlock, sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers'); suite('Angle Fields', function() { @@ -315,4 +315,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 e8478fac3..379c9e389 100644 --- a/tests/mocha/field_checkbox_test.js +++ b/tests/mocha/field_checkbox_test.js @@ -6,7 +6,7 @@ goog.module('Blockly.test.fieldCheckbox'); -const {sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers'); +const {defineRowBlock, sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers'); suite('Checkbox Fields', function() { @@ -209,4 +209,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 883e7d388..016d96185 100644 --- a/tests/mocha/field_colour_test.js +++ b/tests/mocha/field_colour_test.js @@ -6,7 +6,7 @@ goog.module('Blockly.test.fieldColour'); -const {createTestBlock, sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers'); +const {createTestBlock, defineRowBlock, sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers'); suite('Colour Fields', function() { @@ -282,4 +282,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 2923e8830..2f3e782bb 100644 --- a/tests/mocha/field_dropdown_test.js +++ b/tests/mocha/field_dropdown_test.js @@ -6,7 +6,7 @@ goog.module('Blockly.test.fieldDropdown'); -const {createTestBlock, sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers'); +const {createTestBlock, defineRowBlock, sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers'); suite('Dropdown Fields', function() { @@ -157,4 +157,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 99631a38d..0dc769f41 100644 --- a/tests/mocha/field_label_serializable_test.js +++ b/tests/mocha/field_label_serializable_test.js @@ -6,7 +6,7 @@ goog.module('Blockly.test.fieldLabelSerialization'); -const {createTestBlock, sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers'); +const {createTestBlock, defineRowBlock, sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers'); suite('Label Serializable Fields', function() { @@ -186,4 +186,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 7180b493b..51426c694 100644 --- a/tests/mocha/field_multilineinput_test.js +++ b/tests/mocha/field_multilineinput_test.js @@ -6,7 +6,7 @@ goog.module('Blockly.test.fieldMultiline'); -const {createTestBlock, sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers'); +const {createTestBlock, defineRowBlock, sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers'); suite('Multiline Input Fields', function() { @@ -158,4 +158,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 2cb5e62d1..598e482ca 100644 --- a/tests/mocha/field_number_test.js +++ b/tests/mocha/field_number_test.js @@ -6,7 +6,7 @@ goog.module('Blockly.test.fieldNumber'); -const {sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers'); +const {defineRowBlock, sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers'); suite('Number Fields', function() { @@ -337,4 +337,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 bbb4dd88f..496f1ce6f 100644 --- a/tests/mocha/field_textinput_test.js +++ b/tests/mocha/field_textinput_test.js @@ -6,7 +6,7 @@ goog.module('Blockly.test.fieldTextInput'); -const {createTestBlock, sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers'); +const {createTestBlock, defineRowBlock, sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers'); suite('Text Input Fields', function() { @@ -220,4 +220,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 06a0912c2..d12188f9a 100644 --- a/tests/mocha/field_variable_test.js +++ b/tests/mocha/field_variable_test.js @@ -6,7 +6,7 @@ goog.module('Blockly.test.fieldVariable'); -const {createGenUidStubWithReturns, createTestBlock, sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers'); +const {createGenUidStubWithReturns, createTestBlock, defineRowBlock, sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers'); suite('Variable Fields', function() { @@ -382,4 +382,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 d1109a26b..0a5c77bbd 100644 --- a/tests/mocha/serializer_test.js +++ b/tests/mocha/serializer_test.js @@ -280,9 +280,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, ];