mirror of
https://github.com/google/blockly.git
synced 2026-01-06 08:30:13 +01:00
fix: cereal backwards compatibility (#5421)
* fix: remove duplicate serialization hook implementations * feat: add backwards compatibility to field serialization * feat: add support for serializing old mutator hooks * fix: build * fix: refactor field changes into helpers * fix: typo * fix: removing xmlns * tests: add tests for serialization and deserialization of mutator hooks * fix: switch to early returns
This commit is contained in:
committed by
alschmiedt
parent
448c433abe
commit
96935c2502
@@ -45,11 +45,13 @@ const Tooltip = goog.require('Blockly.Tooltip');
|
||||
const WidgetDiv = goog.require('Blockly.WidgetDiv');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const WorkspaceSvg = goog.requireType('Blockly.WorkspaceSvg');
|
||||
const Xml = goog.require('Blockly.Xml');
|
||||
const dom = goog.require('Blockly.utils.dom');
|
||||
const browserEvents = goog.require('Blockly.browserEvents');
|
||||
const style = goog.require('Blockly.utils.style');
|
||||
const userAgent = goog.require('Blockly.utils.userAgent');
|
||||
const utils = goog.require('Blockly.utils');
|
||||
const utilsXml = goog.require('Blockly.utils.xml');
|
||||
/** @suppress {extraRequire} */
|
||||
goog.require('Blockly.Events.BlockChange');
|
||||
/** @suppress {extraRequire} */
|
||||
@@ -439,6 +441,10 @@ Field.prototype.toXml = function(fieldElement) {
|
||||
* @package
|
||||
*/
|
||||
Field.prototype.saveState = function() {
|
||||
const legacyState = this.saveLegacyState(Field);
|
||||
if (legacyState !== null) {
|
||||
return legacyState;
|
||||
}
|
||||
return this.getValue();
|
||||
};
|
||||
|
||||
@@ -449,9 +455,56 @@ Field.prototype.saveState = function() {
|
||||
* @package
|
||||
*/
|
||||
Field.prototype.loadState = function(state) {
|
||||
if (this.loadLegacyState(Field, state)) {
|
||||
return;
|
||||
}
|
||||
this.setValue(state);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line valid-jsdoc
|
||||
/**
|
||||
* Returns a stringified version of the XML state, if it should be used.
|
||||
* Otherwise this returns null, to signal the field should use its own
|
||||
* serialization.
|
||||
* @param {?} callingClass The class calling this method.
|
||||
* Used to see if `this` has overridden any relevant hooks.
|
||||
* @return {?string} The stringified version of the XML state, or null.
|
||||
* @protected
|
||||
*/
|
||||
Field.prototype.saveLegacyState = function(callingClass) {
|
||||
if (callingClass.prototype.saveState === this.saveState &&
|
||||
callingClass.prototype.toXml !== this.toXml) {
|
||||
const elem = utilsXml.createElement("field");
|
||||
elem.setAttribute("name", this.name || '');
|
||||
const text = Xml.domToText(this.toXml(elem));
|
||||
return text.replace(
|
||||
' xmlns="https://developers.google.com/blockly/xml"', '');
|
||||
}
|
||||
// Either they called this on purpose from their saveState, or they have
|
||||
// no implementations of either hook. Just do our thing.
|
||||
return null;
|
||||
};
|
||||
|
||||
// eslint-disable-next-line valid-jsdoc
|
||||
/**
|
||||
* Loads the given state using either the old XML hoooks, if they should be
|
||||
* used. Returns true to indicate loading has been handled, false otherwise.
|
||||
* @param {?} callingClass The class calling this method.
|
||||
* Used to see if `this` has overridden any relevant hooks.
|
||||
* @param {*} state The state to apply to the field.
|
||||
* @return {boolean} Whether the state was applied or not.
|
||||
*/
|
||||
Field.prototype.loadLegacyState = function(callingClass, state) {
|
||||
if (callingClass.prototype.loadState === this.loadState &&
|
||||
callingClass.prototype.fromXml !== this.fromXml) {
|
||||
this.fromXml(Xml.textToDom(/** @type {string} */ (state)));
|
||||
return true;
|
||||
}
|
||||
// Either they called this on purpose from their loadState, or they have
|
||||
// no implementations of either hook. Just do our thing.
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Dispose of all DOM objects and events belonging to this editable field.
|
||||
* @package
|
||||
|
||||
@@ -254,26 +254,6 @@ 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
|
||||
|
||||
@@ -104,22 +104,16 @@ FieldCheckbox.prototype.configure_ = function(config) {
|
||||
|
||||
/**
|
||||
* Saves this field's value.
|
||||
* @return {boolean} The boolean value held by this field.
|
||||
* @return {*} 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);
|
||||
const legacyState = this.saveLegacyState(FieldCheckbox);
|
||||
if (legacyState !== null) {
|
||||
return legacyState;
|
||||
}
|
||||
return this.getValueBoolean();
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -188,26 +188,6 @@ 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
|
||||
*/
|
||||
|
||||
@@ -170,16 +170,6 @@ FieldDropdown.prototype.fromXml = function(fieldElement) {
|
||||
this.setValue(fieldElement.textContent);
|
||||
};
|
||||
|
||||
/**
|
||||
* Saves this field's value.
|
||||
* @return {*} The dropdown value held by this field.
|
||||
* @override
|
||||
* @package
|
||||
*/
|
||||
FieldDropdown.prototype.saveState = function() {
|
||||
return this.getValue();
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the field's value based on the given state.
|
||||
* @param {*} state The state to apply to the dropdown field.
|
||||
@@ -187,6 +177,9 @@ FieldDropdown.prototype.saveState = function() {
|
||||
* @package
|
||||
*/
|
||||
FieldDropdown.prototype.loadState = function(state) {
|
||||
if (this.loadLegacyState(FieldDropdown, state)) {
|
||||
return;
|
||||
}
|
||||
if (this.isOptionListDynamic()) {
|
||||
this.getOptions(false);
|
||||
}
|
||||
|
||||
@@ -68,26 +68,6 @@ 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;
|
||||
|
||||
@@ -124,21 +124,27 @@ FieldMultilineInput.prototype.fromXml = function(fieldElement) {
|
||||
|
||||
/**
|
||||
* Saves this field's value.
|
||||
* @return {string} The text value held by this field.
|
||||
* @override
|
||||
* @return {*} The state of this field.
|
||||
* @package
|
||||
*/
|
||||
FieldMultilineInput.prototype.saveState = function() {
|
||||
return /** @type {string} */ (this.getValue());
|
||||
const legacyState = this.saveLegacyState(FieldMultilineInput);
|
||||
if (legacyState !== null) {
|
||||
return legacyState;
|
||||
}
|
||||
return this.getValue();
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the field's value based on the given state.
|
||||
* @param {*} state The state to apply to the multiline input field.
|
||||
* @param {*} state The state of the variable to assign to this variable field.
|
||||
* @override
|
||||
* @package
|
||||
*/
|
||||
FieldMultilineInput.prototype.loadState = function(state) {
|
||||
if (this.loadLegacyState(Field, state)) {
|
||||
return;
|
||||
}
|
||||
this.setValue(state);
|
||||
};
|
||||
|
||||
|
||||
@@ -118,26 +118,6 @@ 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.
|
||||
|
||||
@@ -182,26 +182,6 @@ 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.
|
||||
|
||||
@@ -199,11 +199,15 @@ FieldVariable.prototype.toXml = function(fieldElement) {
|
||||
|
||||
/**
|
||||
* Saves this field's value.
|
||||
* @return {{id: string}} The ID of the variable referenced by this field.
|
||||
* @return {*} The ID of the variable referenced by this field.
|
||||
* @override
|
||||
* @package
|
||||
*/
|
||||
FieldVariable.prototype.saveState = function() {
|
||||
const legacyState = this.saveLegacyState(FieldVariable);
|
||||
if (legacyState !== null) {
|
||||
return legacyState;
|
||||
}
|
||||
// Make sure the variable is initialized.
|
||||
this.initModel();
|
||||
return {
|
||||
@@ -218,6 +222,9 @@ FieldVariable.prototype.saveState = function() {
|
||||
* @package
|
||||
*/
|
||||
FieldVariable.prototype.loadState = function(state) {
|
||||
if (this.loadLegacyState(FieldVariable, state)) {
|
||||
return;
|
||||
}
|
||||
// This is necessary so that blocks in the flyout can have custom var names.
|
||||
const variable = Variables.getOrCreateVariablePackage(
|
||||
this.sourceBlock_.workspace,
|
||||
|
||||
@@ -24,6 +24,7 @@ const {ISerializer} = goog.requireType('Blockly.serialization.ISerializer');
|
||||
const Size = goog.require('Blockly.utils.Size');
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const Workspace = goog.requireType('Blockly.Workspace');
|
||||
const Xml = goog.require('Blockly.Xml');
|
||||
const inputTypes = goog.require('Blockly.inputTypes');
|
||||
const priorities = goog.require('Blockly.serialization.priorities');
|
||||
const serializationRegistry = goog.require('Blockly.serialization.registry');
|
||||
@@ -162,6 +163,12 @@ const saveExtraState = function(block, state) {
|
||||
if (extraState !== null) {
|
||||
state['extraState'] = extraState;
|
||||
}
|
||||
} else if (block.mutationToDom) {
|
||||
const extraState = block.mutationToDom();
|
||||
if (extraState !== null) {
|
||||
state['extraState'] = Xml.domToText(extraState).replace(
|
||||
' xmlns="https://developers.google.com/blockly/xml"', '');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -427,7 +434,11 @@ const loadExtraState = function(block, state) {
|
||||
if (!state['extraState']) {
|
||||
return;
|
||||
}
|
||||
block.loadExtraState(state['extraState']);
|
||||
if (block.loadExtraState) {
|
||||
block.loadExtraState(state['extraState']);
|
||||
} else {
|
||||
block.domToMutation(Xml.textToDom(state['extraState']));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -65,7 +65,7 @@ goog.addDependency('../../core/events/events_var_rename.js', ['Blockly.Events.Va
|
||||
goog.addDependency('../../core/events/events_viewport.js', ['Blockly.Events.ViewportChange'], ['Blockly.Events', 'Blockly.Events.UiBase', 'Blockly.registry', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'});
|
||||
goog.addDependency('../../core/events/workspace_events.js', ['Blockly.Events.FinishedLoading'], ['Blockly.Events', 'Blockly.Events.Abstract', 'Blockly.registry', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'});
|
||||
goog.addDependency('../../core/extensions.js', ['Blockly.Extensions'], ['Blockly.utils'], {'lang': 'es6', 'module': 'goog'});
|
||||
goog.addDependency('../../core/field.js', ['Blockly.Field'], ['Blockly.DropDownDiv', 'Blockly.Events', 'Blockly.Events.BlockChange', 'Blockly.Gesture', 'Blockly.MarkerManager', 'Blockly.Tooltip', 'Blockly.WidgetDiv', 'Blockly.browserEvents', 'Blockly.utils', 'Blockly.utils.Rect', 'Blockly.utils.Size', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.style', 'Blockly.utils.userAgent'], {'lang': 'es6', 'module': 'goog'});
|
||||
goog.addDependency('../../core/field.js', ['Blockly.Field'], ['Blockly.DropDownDiv', 'Blockly.Events', 'Blockly.Events.BlockChange', 'Blockly.Gesture', 'Blockly.MarkerManager', 'Blockly.Tooltip', 'Blockly.WidgetDiv', 'Blockly.Xml', 'Blockly.browserEvents', 'Blockly.utils', 'Blockly.utils.Rect', 'Blockly.utils.Size', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.style', 'Blockly.utils.userAgent', 'Blockly.utils.xml'], {'lang': 'es6', 'module': 'goog'});
|
||||
goog.addDependency('../../core/field_angle.js', ['Blockly.FieldAngle'], ['Blockly.Css', 'Blockly.DropDownDiv', 'Blockly.FieldTextInput', 'Blockly.WidgetDiv', 'Blockly.browserEvents', 'Blockly.fieldRegistry', 'Blockly.utils.KeyCodes', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.math', 'Blockly.utils.object', 'Blockly.utils.userAgent']);
|
||||
goog.addDependency('../../core/field_checkbox.js', ['Blockly.FieldCheckbox'], ['Blockly.Events.BlockChange', 'Blockly.Field', 'Blockly.fieldRegistry', 'Blockly.utils.dom', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'});
|
||||
goog.addDependency('../../core/field_colour.js', ['Blockly.FieldColour'], ['Blockly.Css', 'Blockly.DropDownDiv', 'Blockly.Events.BlockChange', 'Blockly.Field', 'Blockly.browserEvents', 'Blockly.fieldRegistry', 'Blockly.utils.KeyCodes', 'Blockly.utils.Size', 'Blockly.utils.aria', 'Blockly.utils.colour', 'Blockly.utils.dom', 'Blockly.utils.idGenerator', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'});
|
||||
@@ -197,7 +197,7 @@ goog.addDependency('../../core/renderers/zelos/renderer.js', ['Blockly.zelos.Ren
|
||||
goog.addDependency('../../core/requires.js', ['Blockly.requires'], ['Blockly', 'Blockly.Comment', 'Blockly.ContextMenuItems', 'Blockly.FieldAngle', 'Blockly.FieldCheckbox', 'Blockly.FieldColour', 'Blockly.FieldDropdown', 'Blockly.FieldImage', 'Blockly.FieldLabelSerializable', 'Blockly.FieldMultilineInput', 'Blockly.FieldNumber', 'Blockly.FieldTextInput', 'Blockly.FieldVariable', 'Blockly.FlyoutButton', 'Blockly.Generator', 'Blockly.HorizontalFlyout', 'Blockly.Mutator', 'Blockly.ShortcutItems', 'Blockly.Themes.Classic', 'Blockly.Toolbox', 'Blockly.Trashcan', 'Blockly.VariablesDynamic', 'Blockly.VerticalFlyout', 'Blockly.Warning', 'Blockly.ZoomControls', 'Blockly.geras.Renderer', 'Blockly.serialization.blocks', 'Blockly.serialization.registry', 'Blockly.serialization.variables', 'Blockly.serialization.workspaces', 'Blockly.thrasos.Renderer', 'Blockly.zelos.Renderer']);
|
||||
goog.addDependency('../../core/scrollbar.js', ['Blockly.Scrollbar'], ['Blockly.Touch', 'Blockly.browserEvents', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.Svg', 'Blockly.utils.dom'], {'lang': 'es6', 'module': 'goog'});
|
||||
goog.addDependency('../../core/scrollbar_pair.js', ['Blockly.ScrollbarPair'], ['Blockly.Events', 'Blockly.Scrollbar', 'Blockly.utils.Svg', 'Blockly.utils.dom'], {'lang': 'es6', 'module': 'goog'});
|
||||
goog.addDependency('../../core/serialization/blocks.js', ['Blockly.serialization.blocks'], ['Blockly.Events', 'Blockly.inputTypes', 'Blockly.serialization.exceptions', 'Blockly.serialization.priorities', 'Blockly.serialization.registry', 'Blockly.utils.Size'], {'lang': 'es6', 'module': 'goog'});
|
||||
goog.addDependency('../../core/serialization/blocks.js', ['Blockly.serialization.blocks'], ['Blockly.Events', 'Blockly.Xml', 'Blockly.inputTypes', 'Blockly.serialization.exceptions', 'Blockly.serialization.priorities', 'Blockly.serialization.registry', 'Blockly.utils.Size'], {'lang': 'es6', 'module': 'goog'});
|
||||
goog.addDependency('../../core/serialization/exceptions.js', ['Blockly.serialization.exceptions'], [], {'lang': 'es6', 'module': 'goog'});
|
||||
goog.addDependency('../../core/serialization/priorities.js', ['Blockly.serialization.priorities'], [], {'module': 'goog'});
|
||||
goog.addDependency('../../core/serialization/registry.js', ['Blockly.serialization.registry'], ['Blockly.registry'], {'lang': 'es6', 'module': 'goog'});
|
||||
|
||||
@@ -17,9 +17,11 @@ suite('Abstract Fields', function() {
|
||||
// console logs.
|
||||
createDeprecationWarningStub();
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
sharedTestTeardown.call(this);
|
||||
});
|
||||
|
||||
suite('Is Serializable', function() {
|
||||
// Both EDITABLE and SERIALIZABLE are default.
|
||||
function FieldDefault() {
|
||||
@@ -73,6 +75,269 @@ suite('Abstract Fields', function() {
|
||||
chai.assert.isTrue(field.isSerializable());
|
||||
});
|
||||
});
|
||||
|
||||
suite('Serialization', function() {
|
||||
class DefaultSerializationField extends Blockly.Field {
|
||||
constructor(value, validator = undefined, config = undefined) {
|
||||
super(value, validator, config);
|
||||
this.SERIALIZABLE = true;
|
||||
}
|
||||
}
|
||||
|
||||
class CustomXmlField extends Blockly.Field {
|
||||
constructor(value, validator = undefined, config = undefined) {
|
||||
super(value, validator, config);
|
||||
this.SERIALIZABLE = true;
|
||||
}
|
||||
|
||||
toXml(fieldElement) {
|
||||
fieldElement.textContent = 'custom value';
|
||||
return fieldElement;
|
||||
}
|
||||
|
||||
fromXml(fieldElement) {
|
||||
this.someProperty = fieldElement.textContent;
|
||||
}
|
||||
}
|
||||
|
||||
class CustomXmlCallSuperField extends Blockly.Field {
|
||||
constructor(value, validator = undefined, config = undefined) {
|
||||
super(value, validator, config);
|
||||
this.SERIALIZABLE = true;
|
||||
}
|
||||
|
||||
toXml(fieldElement) {
|
||||
super.toXml(fieldElement);
|
||||
fieldElement.setAttribute('attribute', 'custom value');
|
||||
return fieldElement;
|
||||
}
|
||||
|
||||
fromXml(fieldElement) {
|
||||
super.fromXml(fieldElement);
|
||||
this.someProperty = fieldElement.getAttribute('attribute');
|
||||
}
|
||||
}
|
||||
|
||||
class CustomJsoField extends Blockly.Field {
|
||||
constructor(value, validator = undefined, config = undefined) {
|
||||
super(value, validator, config);
|
||||
this.SERIALIZABLE = true;
|
||||
}
|
||||
|
||||
saveState() {
|
||||
return 'custom value';
|
||||
}
|
||||
|
||||
loadState(state) {
|
||||
this.someProperty = state;
|
||||
}
|
||||
}
|
||||
|
||||
class CustomJsoCallSuperField extends Blockly.Field {
|
||||
constructor(value, validator = undefined, config = undefined) {
|
||||
super(value, validator, config);
|
||||
this.SERIALIZABLE = true;
|
||||
}
|
||||
|
||||
saveState() {
|
||||
return {
|
||||
default: super.saveState(),
|
||||
val: 'custom value'
|
||||
};
|
||||
}
|
||||
|
||||
loadState(state) {
|
||||
super.loadState(state.default);
|
||||
this.someProperty = state.val;
|
||||
}
|
||||
}
|
||||
|
||||
class CustomXmlAndJsoField extends Blockly.Field {
|
||||
constructor(value, validator = undefined, config = undefined) {
|
||||
super(value, validator, config);
|
||||
this.SERIALIZABLE = true;
|
||||
}
|
||||
|
||||
toXml(fieldElement) {
|
||||
fieldElement.textContent = 'custom value';
|
||||
return fieldElement;
|
||||
}
|
||||
|
||||
fromXml(fieldElement) {
|
||||
this.someProperty = fieldElement.textContent;
|
||||
}
|
||||
|
||||
saveState() {
|
||||
return 'custom value';
|
||||
}
|
||||
|
||||
loadState(state) {
|
||||
this.someProperty = state;
|
||||
}
|
||||
}
|
||||
|
||||
suite('Save', function() {
|
||||
suite('JSO', function() {
|
||||
test('No implementations', function() {
|
||||
const field = new DefaultSerializationField('test value');
|
||||
const value = field.saveState();
|
||||
chai.assert.equal(value, 'test value');
|
||||
});
|
||||
|
||||
test('Xml implementations', function() {
|
||||
const field = new CustomXmlField('test value');
|
||||
const value = field.saveState();
|
||||
chai.assert.equal(value, '<field name="">custom value</field>');
|
||||
});
|
||||
|
||||
test('Xml super implementation', function() {
|
||||
const field = new CustomXmlCallSuperField('test value');
|
||||
const value = field.saveState();
|
||||
chai.assert.equal(
|
||||
value,
|
||||
'<field name="" attribute="custom value">test value</field>');
|
||||
});
|
||||
|
||||
test('JSO implementations', function() {
|
||||
const field = new CustomJsoField('test value');
|
||||
const value = field.saveState();
|
||||
chai.assert.equal(value, 'custom value');
|
||||
});
|
||||
|
||||
test('JSO super implementations', function() {
|
||||
const field = new CustomJsoCallSuperField('test value');
|
||||
const value = field.saveState();
|
||||
chai.assert.deepEqual(
|
||||
value, {default: 'test value', val: 'custom value'});
|
||||
});
|
||||
|
||||
test('Xml and JSO implementations', function() {
|
||||
const field = new CustomXmlAndJsoField('test value');
|
||||
const value = field.saveState();
|
||||
chai.assert.equal(value, 'custom value');
|
||||
});
|
||||
});
|
||||
|
||||
suite('Xml', function() {
|
||||
test('No implementations', function() {
|
||||
const field = new DefaultSerializationField('test value');
|
||||
const element = document.createElement('field');
|
||||
const value = Blockly.Xml.domToText(field.toXml(element));
|
||||
chai.assert.equal(
|
||||
value,
|
||||
'<field xmlns="http://www.w3.org/1999/xhtml">test value</field>');
|
||||
});
|
||||
|
||||
test('Xml implementations', function() {
|
||||
const field = new CustomXmlField('test value');
|
||||
const element = document.createElement('field');
|
||||
const value = Blockly.Xml.domToText(field.toXml(element));
|
||||
chai.assert.equal(
|
||||
value,
|
||||
'<field xmlns="http://www.w3.org/1999/xhtml">custom value</field>'
|
||||
);
|
||||
});
|
||||
|
||||
test('Xml super implementation', function() {
|
||||
const field = new CustomXmlCallSuperField('test value');
|
||||
const element = document.createElement('field');
|
||||
const value = Blockly.Xml.domToText(field.toXml(element));
|
||||
chai.assert.equal(
|
||||
value,
|
||||
'<field xmlns="http://www.w3.org/1999/xhtml" ' +
|
||||
'attribute="custom value">test value</field>');
|
||||
});
|
||||
|
||||
test('Xml and JSO implementations', function() {
|
||||
const field = new CustomXmlAndJsoField('test value');
|
||||
const element = document.createElement('field');
|
||||
const value = Blockly.Xml.domToText(field.toXml(element));
|
||||
chai.assert.equal(
|
||||
value,
|
||||
'<field xmlns="http://www.w3.org/1999/xhtml">custom value</field>'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('Load', function() {
|
||||
suite('JSO', function() {
|
||||
test('No implementations', function() {
|
||||
const field = new DefaultSerializationField('');
|
||||
field.loadState('test value');
|
||||
chai.assert.equal(field.getValue(), 'test value');
|
||||
});
|
||||
|
||||
test('Xml implementations', function() {
|
||||
const field = new CustomXmlField('');
|
||||
field.loadState('<field name="">custom value</field>');
|
||||
chai.assert.equal(field.someProperty, 'custom value');
|
||||
});
|
||||
|
||||
test('Xml super implementation', function() {
|
||||
const field = new CustomXmlCallSuperField('');
|
||||
field.loadState(
|
||||
'<field attribute="custom value" name="">test value</field>');
|
||||
chai.assert.equal(field.getValue(), 'test value');
|
||||
chai.assert.equal(field.someProperty, 'custom value');
|
||||
});
|
||||
|
||||
test('JSO implementations', function() {
|
||||
const field = new CustomJsoField('');
|
||||
field.loadState('custom value');
|
||||
chai.assert.equal(field.someProperty, 'custom value');
|
||||
});
|
||||
|
||||
test('JSO super implementations', function() {
|
||||
const field = new CustomJsoCallSuperField('');
|
||||
field.loadState({default: 'test value', val: 'custom value'});
|
||||
chai.assert.equal(field.getValue(), 'test value');
|
||||
chai.assert.equal(field.someProperty, 'custom value');
|
||||
});
|
||||
|
||||
test('Xml and JSO implementations', function() {
|
||||
const field = new CustomXmlAndJsoField('');
|
||||
field.loadState('custom value');
|
||||
chai.assert.equal(field.someProperty, 'custom value');
|
||||
});
|
||||
});
|
||||
|
||||
suite('Xml', function() {
|
||||
test('No implementations', function() {
|
||||
const field = new DefaultSerializationField('');
|
||||
field.fromXml(
|
||||
Blockly.Xml.textToDom('<field name="">test value</field>'));
|
||||
chai.assert.equal(field.getValue(), 'test value');
|
||||
});
|
||||
|
||||
test('Xml implementations', function() {
|
||||
const field = new CustomXmlField('');
|
||||
field.fromXml(
|
||||
Blockly.Xml.textToDom('<field name="">custom value</field>'));
|
||||
chai.assert.equal(field.someProperty, 'custom value');
|
||||
});
|
||||
|
||||
test('Xml super implementation', function() {
|
||||
const field = new CustomXmlCallSuperField('');
|
||||
field.fromXml(
|
||||
Blockly.Xml.textToDom(
|
||||
'<field attribute="custom value" name="">test value</field>'
|
||||
)
|
||||
);
|
||||
chai.assert.equal(field.getValue(), 'test value');
|
||||
chai.assert.equal(field.someProperty, 'custom value');
|
||||
});
|
||||
|
||||
test('XML andd JSO implementations', function() {
|
||||
const field = new CustomXmlAndJsoField('');
|
||||
field.fromXml(
|
||||
Blockly.Xml.textToDom('<field name="">custom value</field>'));
|
||||
chai.assert.equal(field.someProperty, 'custom value');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('setValue', function() {
|
||||
function addSpies(field, excludeSpies = []) {
|
||||
if (!excludeSpies.includes('doValueInvalid_')) {
|
||||
@@ -321,6 +586,7 @@ suite('Abstract Fields', function() {
|
||||
sinon.assert.calledOnce(this.field.doValueUpdate_);
|
||||
});
|
||||
});
|
||||
|
||||
suite('Customization', function() {
|
||||
// All this field does is wrap the abstract field.
|
||||
function CustomField(opt_config) {
|
||||
|
||||
@@ -681,4 +681,31 @@ suite('JSO Deserialization', function() {
|
||||
'third-load'
|
||||
]);
|
||||
});
|
||||
|
||||
suite('Extra state', function() {
|
||||
// Most of this is covered by our round-trip tests. But we need one test
|
||||
// for old xml hooks.
|
||||
test('Xml hooks', function() {
|
||||
Blockly.Blocks['test_block'] = {
|
||||
init: function() { },
|
||||
|
||||
mutationToDom: function() { },
|
||||
|
||||
domToMutation: function(element) {
|
||||
this.someProperty = element.getAttribute('value');
|
||||
}
|
||||
};
|
||||
|
||||
const block = Blockly.serialization.blocks.load(
|
||||
{
|
||||
'type': 'test_block',
|
||||
'extraState': '<mutation value="some value"></mutation>',
|
||||
},
|
||||
this.workspace);
|
||||
|
||||
delete Blockly.Blocks['test_block'];
|
||||
|
||||
chai.assert.equal(block.someProperty, 'some value');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -208,6 +208,18 @@ suite('JSO Serialization', function() {
|
||||
const jso = Blockly.serialization.blocks.save(block);
|
||||
assertProperty(jso, 'extraState', ['state1', 42, true]);
|
||||
});
|
||||
|
||||
test('Xml hooks', function() {
|
||||
const block = this.workspace.newBlock('row_block');
|
||||
block.mutationToDom = function() {
|
||||
var container = Blockly.utils.xml.createElement('mutation');
|
||||
container.setAttribute('value', 'some value');
|
||||
return container;
|
||||
};
|
||||
const jso = Blockly.serialization.blocks.save(block);
|
||||
assertProperty(
|
||||
jso, 'extraState', '<mutation value="some value"></mutation>');
|
||||
});
|
||||
});
|
||||
|
||||
suite('Icons', function() {
|
||||
|
||||
Reference in New Issue
Block a user