diff --git a/core/interfaces/i_serializer.js b/core/interfaces/i_serializer.js new file mode 100644 index 000000000..e7b75b9e4 --- /dev/null +++ b/core/interfaces/i_serializer.js @@ -0,0 +1,71 @@ +/** + * @license + * Copyright 2021 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @fileoverview The record type for an object containing functions for + * serializing part of the workspace. + */ + +'use strict'; + +goog.module('Blockly.serialization.ISerializer'); +goog.module.declareLegacyNamespace(); + +// eslint-disable-next-line no-unused-vars +const Workspace = goog.requireType('Blockly.Workspace'); + + +/** + * Serializes and deserializes a plugin. + * @interface + */ +class ISerializer { + constructor() { + /** + * A priority value used to determine the order of deserializing plugins. + * More positive priorities are deserialized before less positive + * priorities. Eg if you have priorities (0, -10, 10, 100) the order of + * deserialiation will be (100, 10, 0, -10). + * If two plugins have the same priority, they are deserialized in an + * arbitrary order relative to each other. + * @type {number} + */ + this.priority; + } + + /* eslint-disable no-unused-vars, valid-jsdoc */ + + /** + * Saves the state of the plugin. + * @param {!Workspace} workspace The workspace the plugin to serialize is + * associated with. + * @return {?} A JS object containing the plugin's state, or null if + * there is no state to record. + */ + save(workspace) {} + + /* eslint-enable valid-jsdoc */ + + /** + * Loads the state of the plugin. + * @param {?} state The state of the plugin to deserialize. This will always + * be non-null. + * @param {!Workspace} workspace The workspace the plugin to deserialize is + * associated with. + */ + load(state, workspace) {} + + /** + * Clears the state of the plugin. + * @param {!Workspace} workspace The workspace the plugin to clear the state + * of is associated with. + */ + clear(workspace) {} + + /* eslint-enable no-unused-vars */ +} + +exports.ISerializer = ISerializer; diff --git a/core/registry.js b/core/registry.js index 5752702ae..671daa631 100644 --- a/core/registry.js +++ b/core/registry.js @@ -38,6 +38,8 @@ const Renderer = goog.requireType('Blockly.blockRendering.Renderer'); const Theme = goog.requireType('Blockly.Theme'); /* eslint-disable-next-line no-unused-vars */ const ToolboxItem = goog.requireType('Blockly.ToolboxItem'); +/* eslint-disable-next-line no-unused-vars */ +const ISerializer = goog.requireType('Blockly.serialization.ISerializer'); /** @@ -45,12 +47,20 @@ const ToolboxItem = goog.requireType('Blockly.ToolboxItem'); * registering and the value being the constructor function. * e.g. {'field': {'field_angle': Blockly.FieldAngle}} * - * @type {Object>} + * @type {!Object>} */ const typeMap = Object.create(null); /** @private */ exports.typeMap_ = typeMap; +/** + * A map of maps. With the keys being the type and caseless name of the class we + * are registring, and the value being the most recent cased name for that + * registration. + * @type {!Object>} + */ +const nameMap = Object.create(null); + /** * The string used to register the default class for a type of plugin. * @type {string} @@ -118,6 +128,12 @@ Type.METRICS_MANAGER = new Type('metricsManager'); /** @type {!Type} */ Type.BLOCK_DRAGGER = new Type('blockDragger'); +/** + * @type {!Type} + * @package + */ +Type.SERIALIZER = new Type('serializer'); + /** * Registers a class based on a type and name. * @param {string|!Type} type The type of the plugin. @@ -129,7 +145,7 @@ Type.BLOCK_DRAGGER = new Type('blockDragger'); * an already registered item. * @throws {Error} if the type or name is empty, a name with the given type has * already been registered, or if the given class or object is not valid for - * it's type. + * it's type. * @template T */ const register = function(type, name, registryItem, opt_allowOverrides) { @@ -146,25 +162,29 @@ const register = function(type, name, registryItem, opt_allowOverrides) { 'Invalid name "' + name + '". The name must be a' + ' non-empty string.'); } - name = name.toLowerCase(); + const caselessName = name.toLowerCase(); if (!registryItem) { throw Error('Can not register a null value'); } let typeRegistry = typeMap[type]; + let nameRegistry = nameMap[type]; // If the type registry has not been created, create it. if (!typeRegistry) { typeRegistry = typeMap[type] = Object.create(null); + nameRegistry = nameMap[type] = Object.create(null); } // Validate that the given class has all the required properties. validate(type, registryItem); // Don't throw an error if opt_allowOverrides is true. - if (!opt_allowOverrides && typeRegistry[name]) { + if (!opt_allowOverrides && typeRegistry[caselessName]) { throw Error( - 'Name "' + name + '" with type "' + type + '" already registered.'); + 'Name "' + caselessName + '" with type "' + type + + '" already registered.'); } - typeRegistry[name] = registryItem; + typeRegistry[caselessName] = registryItem; + nameRegistry[caselessName] = name; }; exports.register = register; @@ -203,6 +223,7 @@ const unregister = function(type, name) { return; } delete typeMap[type][name]; + delete nameMap[type][name]; }; exports.unregister = unregister; @@ -288,6 +309,43 @@ const getObject = function(type, name, opt_throwIfMissing) { }; exports.getObject = getObject; +/** + * Returns a map of items registered with the given type. + * @param {string|!Type} type The type of the plugin. (e.g. Category) + * @param {boolean} opt_cased Whether or not to return a map with cased keys + * (rather than caseless keys). False by default. + * @param {boolean=} opt_throwIfMissing Whether or not to throw an error if we + * are unable to find the object. False by default. + * @return {?Object} A map of objects with + * the given type, or null if none exists. + * @template T + */ +const getAllItems = function(type, opt_cased, opt_throwIfMissing) { + type = String(type).toLowerCase(); + const typeRegistry = typeMap[type]; + if (!typeRegistry) { + const msg = `Unable to find [${type}] in the registry.`; + if (opt_throwIfMissing) { + throw new Error(`${msg} You must require or register a ${type} plugin.`); + } else { + console.warn(msg); + } + return null; + } + if (!opt_cased) { + return typeRegistry; + } + const nameRegistry = nameMap[type]; + const casedRegistry = Object.create(null); + const keys = Object.keys(typeRegistry); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + casedRegistry[nameRegistry[key]] = typeRegistry[key]; + } + return casedRegistry; +}; +exports.getAllItems = getAllItems; + /** * Gets the class from Blockly options for the given type. * This is used for plugins that override a built in feature. (e.g. Toolbox) diff --git a/core/requires.js b/core/requires.js index 8e91f2f8b..bb96f0359 100644 --- a/core/requires.js +++ b/core/requires.js @@ -82,4 +82,7 @@ goog.require('Blockly.zelos.Renderer'); // Classic is the default theme. goog.require('Blockly.Themes.Classic'); +goog.require('Blockly.serialization.blocks'); +goog.require('Blockly.serialization.registry'); +goog.require('Blockly.serialization.variables'); goog.require('Blockly.serialization.workspaces'); diff --git a/core/serialization/blocks.js b/core/serialization/blocks.js index 531b7cb77..04f175bc1 100644 --- a/core/serialization/blocks.js +++ b/core/serialization/blocks.js @@ -19,10 +19,14 @@ const Block = goog.requireType('Blockly.Block'); // eslint-disable-next-line no-unused-vars const Connection = goog.requireType('Blockly.Connection'); const Events = goog.require('Blockly.Events'); +// eslint-disable-next-line no-unused-vars +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 inputTypes = goog.require('Blockly.inputTypes'); +const priorities = goog.require('Blockly.serialization.priorities'); +const serializationRegistry = goog.require('Blockly.serialization.registry'); // TODO: Remove this once lint is fixed. @@ -577,3 +581,71 @@ const initBlock = function(block, rendered) { block.initModel(); } }; + +// Aliases to disambiguate saving/loading within the serializer. +const saveBlock = save; +const loadBlock = load; + +/** + * Serializer for saving and loading block state. + * @implements {ISerializer} + */ +class BlockSerializer { + constructor() { + /** + * The priority for deserializing blocks. + * @type {number} + */ + this.priority = priorities.BLOCKS; + } + + /** + * Serializes the blocks of the given workspace. + * @param {!Workspace} workspace The workspace to save the blocks of. + * @return {?{languageVersion: number, blocks:!Array}} The state of + * the workspace's blocks, or null if there are no blocks. + */ + save(workspace) { + const blockState = []; + for (const block of workspace.getTopBlocks(false)) { + const state = saveBlock(block, {addCoordinates: true}); + if (state) { + blockState.push(state); + } + } + if (blockState.length) { + return { + 'languageVersion': 0, // Currently unused. + 'blocks': blockState + }; + } + return null; + } + + /** + * Deserializes the blocks defined by the given state into the given + * workspace. + * @param {{languageVersion: number, blocks:!Array}} state The state + * of the blocks to deserialize. + * @param {!Workspace} workspace The workspace to deserialize into. + */ + load(state, workspace) { + const blockStates = state['blocks']; + for (const state of blockStates) { + loadBlock(state, workspace, {recordUndo: Events.getRecordUndo()}); + } + } + + /** + * Disposes of any blocks that exist on the workspace. + * @param {!Workspace} workspace The workspace to clear the blocks of. + */ + clear(workspace) { + // Cannot use workspace.clear() because that also removes variables. + for (const block of workspace.getTopBlocks(false)) { + block.dispose(false); + } + } +} + +serializationRegistry.register('blocks', new BlockSerializer()); diff --git a/core/serialization/priorities.js b/core/serialization/priorities.js new file mode 100644 index 000000000..3535d5185 --- /dev/null +++ b/core/serialization/priorities.js @@ -0,0 +1,34 @@ +/** + * @license + * Copyright 2021 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @fileoverview Includes constants for the priorities of different plugin + * serializers. Higher priorities are deserialized first. + */ + +'use strict'; + +/** + * The top level namespace for priorities of plugin serializers. + * @namespace Blockly.serialization.priorities + */ +goog.module('Blockly.serialization.priorities'); +goog.module.declareLegacyNamespace(); + + +/** + * The priority for deserializing variables. + * @type {number} + * @const + */ +exports.VARIABLES = 100; + +/** + * The priority for deserializing blocks. + * @type {number} + * @const + */ +exports.BLOCKS = 50; diff --git a/core/serialization/registry.js b/core/serialization/registry.js new file mode 100644 index 000000000..7806db29f --- /dev/null +++ b/core/serialization/registry.js @@ -0,0 +1,40 @@ +/** + * @license + * Copyright 2021 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @fileoverview Contains functions registering serializers (eg blocks, + * variables, plugins, etc). + */ +'use strict'; + +goog.module('Blockly.serialization.registry'); +goog.module.declareLegacyNamespace(); + + +// eslint-disable-next-line no-unused-vars +const {ISerializer} = goog.requireType('Blockly.serialization.ISerializer'); +const registry = goog.require('Blockly.registry'); + + +/** + * Registers the given serializer so that it can be used for serialization and + * deserialization. + * @param {string} name The name of the serializer to register. + * @param {ISerializer} serializer The serializer to register. + */ +const register = function(name, serializer) { + registry.register(registry.Type.SERIALIZER, name, serializer); +}; +exports.register = register; + +/** + * Unregisters the serializer associated with the given name. + * @param {string} name The name of the serializer to unregister. + */ +const unregister = function(name) { + registry.unregister(registry.Type.SERIALIZER, name); +}; +exports.unregister = unregister; diff --git a/core/serialization/variables.js b/core/serialization/variables.js index e071b9f3e..9606f32d2 100644 --- a/core/serialization/variables.js +++ b/core/serialization/variables.js @@ -15,9 +15,13 @@ goog.module.declareLegacyNamespace(); const Events = goog.require('Blockly.Events'); // eslint-disable-next-line no-unused-vars +const {ISerializer} = goog.requireType('Blockly.serialization.ISerializer'); +// eslint-disable-next-line no-unused-vars const VariableModel = goog.requireType('Blockly.VariableModel'); // eslint-disable-next-line no-unused-vars const Workspace = goog.requireType('Blockly.Workspace'); +const priorities = goog.require('Blockly.serialization.priorities'); +const serializationRegistry = goog.require('Blockly.serialization.registry'); /** @@ -36,7 +40,7 @@ exports.State = State; * @param {!VariableModel} variableModel The variable to serialize. * @return {!State} The serialized state of the variable. */ -const save = function(variableModel) { +const saveVariable = function(variableModel) { const state = { 'name': variableModel.name, 'id': variableModel.getId() @@ -46,12 +50,9 @@ const save = function(variableModel) { } return state; }; -/** @package */ -exports.save = save; /** * Loads the variable represented by the given state into the given workspace. - * Do not call this directly, use workspace.createVariable instead. * @param {!State} state The state of a variable to deserialize into the * workspace. * @param {!Workspace} workspace The workspace to add the variable to. @@ -59,7 +60,7 @@ exports.save = save; * recordUndo: If true, events triggered by this function will be undo-able * by the user. False by default. */ -const load = function(state, workspace, {recordUndo = false} = {}) { +const loadVariable = function(state, workspace, {recordUndo = false} = {}) { const prevRecordUndo = Events.getRecordUndo(); Events.setRecordUndo(recordUndo); const existingGroup = Events.getGroup(); @@ -72,5 +73,54 @@ const load = function(state, workspace, {recordUndo = false} = {}) { Events.setGroup(existingGroup); Events.setRecordUndo(prevRecordUndo); }; -/** @package */ -exports.load = load; + +/** + * Serializer for saving and loading variable state. + * @implements {ISerializer} + */ +class VariableSerializer { + constructor() { + /** + * The priority for deserializing variables. + * @type {number} + */ + this.priority = priorities.VARIABLES; + } + + /** + * Serializes the variables of the given workspace. + * @param {!Workspace} workspace The workspace to save the variables of. + * @return {?Array} The state of the workspace's variables, or null + * if there are no variables. + */ + save(workspace) { + const variableStates = []; + for (const variable of workspace.getAllVariables()) { + variableStates.push(saveVariable(variable)); + } + return variableStates.length ? variableStates : null; + } + + /** + * Deserializes the variable defined by the given state into the given + * workspace. + * @param {!Array} state The state of the variables to deserialize. + * @param {!Workspace} workspace The workspace to deserialize into. + */ + load(state, workspace) { + for (const varState of state) { + loadVariable(varState, workspace, {recordUndo: Events.getRecordUndo()}); + } + } + + /** + * Disposes of any variables or potential variables that exist on the + * workspace. + * @param {!Workspace} workspace The workspace to clear the variables of. + */ + clear(workspace) { + workspace.getVariableMap().clear(); + } +} + +serializationRegistry.register('variables', new VariableSerializer()); diff --git a/core/serialization/workspaces.js b/core/serialization/workspaces.js index 581ca867a..2c180099b 100644 --- a/core/serialization/workspaces.js +++ b/core/serialization/workspaces.js @@ -16,9 +16,8 @@ goog.module.declareLegacyNamespace(); const Events = goog.require('Blockly.Events'); // eslint-disable-next-line no-unused-vars const Workspace = goog.require('Blockly.Workspace'); -const blocks = goog.require('Blockly.serialization.blocks'); const dom = goog.require('Blockly.utils.dom'); -const variables = goog.require('Blockly.serialization.variables'); +const registry = goog.require('Blockly.registry'); /** @@ -28,31 +27,13 @@ const variables = goog.require('Blockly.serialization.variables'); */ const save = function(workspace) { const state = Object.create(null); - - // TODO: Switch this to use plugin serialization system (once it is built). - const variableStates = []; - const vars = workspace.getAllVariables(); - for (let i = 0; i < vars.length; i++) { - variableStates.push(variables.save(vars[i])); - } - if (variableStates.length) { - state['variables'] = variableStates; - } - - const blockStates = []; - for (let block of workspace.getTopBlocks(false)) { - const blockState = blocks.save(block, {addCoordinates: true}); - if (blockState) { - blockStates.push(blockState); + const serializerMap = registry.getAllItems(registry.Type.SERIALIZER, true); + for (const key in serializerMap) { + const save = serializerMap[key].save(workspace); + if (save) { + state[key] = save; } } - if (blockStates.length) { - // This is an object to support adding language version later. - state['blocks'] = { - 'blocks': blockStates - }; - } - return state; }; exports.save = save; @@ -67,8 +48,14 @@ exports.save = save; * by the user. False by default. */ const load = function(state, workspace, {recordUndo = false} = {}) { - // TODO: Switch this to use plugin serialization system (once it is built). - // TODO: Add something for clearing the state before deserializing. + const serializerMap = registry.getAllItems(registry.Type.SERIALIZER, true); + if (!serializerMap) { + return; + } + + const deserializers = Object.entries(serializerMap) + .sort(([, {priority: priorityA}], [, {priority: priorityB}]) => + priorityB - priorityA); const prevRecordUndo = Events.getRecordUndo(); Events.setRecordUndo(recordUndo); @@ -81,18 +68,19 @@ const load = function(state, workspace, {recordUndo = false} = {}) { if (workspace.setResizesEnabled) { workspace.setResizesEnabled(false); } - - if (state['variables']) { - const variableStates = state['variables']; - for (let i = 0; i < variableStates.length; i++) { - variables.load(variableStates[i], workspace, {recordUndo}); - } + + // We want to trigger clearing in reverse priority order so plugins don't end + // up missing dependencies. + for (const [, deserializer] of deserializers.reverse()) { + deserializer.clear(workspace); } - if (state['blocks']) { - const blockStates = state['blocks']['blocks']; - for (let i = 0; i < blockStates.length; i++) { - blocks.load(blockStates[i], workspace, {recordUndo}); + // reverse() is destructive, so we have to re-reverse to correct the order. + for (let [name, deserializer] of deserializers.reverse()) { + name = /** @type {string} */ (name); + const pluginState = state[name]; + if (pluginState) { + deserializer.load(state[name], workspace); } } diff --git a/tests/deps.js b/tests/deps.js index 8850112c2..9e2eab19c 100644 --- a/tests/deps.js +++ b/tests/deps.js @@ -116,6 +116,7 @@ goog.addDependency('../../core/interfaces/i_registrable.js', ['Blockly.IRegistra goog.addDependency('../../core/interfaces/i_registrable_field.js', ['Blockly.IRegistrableField'], [], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/interfaces/i_selectable.js', ['Blockly.ISelectable'], [], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/interfaces/i_selectable_toolbox_item.js', ['Blockly.ISelectableToolboxItem'], [], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/interfaces/i_serializer.js', ['Blockly.serialization.ISerializer'], [], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/interfaces/i_styleable.js', ['Blockly.IStyleable'], [], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/interfaces/i_toolbox.js', ['Blockly.IToolbox'], [], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/interfaces/i_toolbox_item.js', ['Blockly.IToolboxItem'], [], {'lang': 'es6', 'module': 'goog'}); @@ -193,13 +194,15 @@ goog.addDependency('../../core/renderers/zelos/measurables/row_elements.js', ['B goog.addDependency('../../core/renderers/zelos/measurables/top_row.js', ['Blockly.zelos.TopRow'], ['Blockly.blockRendering.TopRow', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/renderers/zelos/path_object.js', ['Blockly.zelos.PathObject'], ['Blockly.blockRendering.PathObject', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/renderers/zelos/renderer.js', ['Blockly.zelos.Renderer'], ['Blockly.InsertionMarkerManager', 'Blockly.blockRendering', 'Blockly.blockRendering.Renderer', 'Blockly.connectionTypes', 'Blockly.utils.object', 'Blockly.zelos.ConstantProvider', 'Blockly.zelos.Drawer', 'Blockly.zelos.MarkerSvg', 'Blockly.zelos.PathObject', 'Blockly.zelos.RenderInfo'], {'lang': 'es6', 'module': 'goog'}); -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.workspaces', 'Blockly.thrasos.Renderer', 'Blockly.zelos.Renderer']); +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.utils.Size'], {'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/exceptions.js', ['Blockly.serialization.exceptions'], [], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/serialization/variables.js', ['Blockly.serialization.variables'], ['Blockly.Events'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/serialization/workspaces.js', ['Blockly.serialization.workspaces'], ['Blockly.Events', 'Blockly.Workspace', 'Blockly.serialization.blocks', 'Blockly.serialization.variables', 'Blockly.utils.dom'], {'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'}); +goog.addDependency('../../core/serialization/variables.js', ['Blockly.serialization.variables'], ['Blockly.Events', 'Blockly.serialization.priorities', 'Blockly.serialization.registry'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/serialization/workspaces.js', ['Blockly.serialization.workspaces'], ['Blockly.Events', 'Blockly.Workspace', 'Blockly.registry', 'Blockly.utils.dom'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/shortcut_items.js', ['Blockly.ShortcutItems'], ['Blockly.Gesture', 'Blockly.ShortcutRegistry', 'Blockly.clipboard', 'Blockly.common', 'Blockly.utils.KeyCodes'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/shortcut_registry.js', ['Blockly.ShortcutRegistry'], ['Blockly.utils.KeyCodes', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/theme.js', ['Blockly.Theme'], ['Blockly.registry', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); diff --git a/tests/deps.mocha.js b/tests/deps.mocha.js index c89219406..06a879fa1 100644 --- a/tests/deps.mocha.js +++ b/tests/deps.mocha.js @@ -116,6 +116,7 @@ goog.addDependency('../../core/interfaces/i_registrable.js', ['Blockly.IRegistra goog.addDependency('../../core/interfaces/i_registrable_field.js', ['Blockly.IRegistrableField'], [], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/interfaces/i_selectable.js', ['Blockly.ISelectable'], [], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/interfaces/i_selectable_toolbox_item.js', ['Blockly.ISelectableToolboxItem'], [], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/interfaces/i_serializer.js', ['Blockly.serialization.ISerializer'], [], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/interfaces/i_styleable.js', ['Blockly.IStyleable'], [], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/interfaces/i_toolbox.js', ['Blockly.IToolbox'], [], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/interfaces/i_toolbox_item.js', ['Blockly.IToolboxItem'], [], {'lang': 'es6', 'module': 'goog'}); @@ -193,13 +194,15 @@ goog.addDependency('../../core/renderers/zelos/measurables/row_elements.js', ['B goog.addDependency('../../core/renderers/zelos/measurables/top_row.js', ['Blockly.zelos.TopRow'], ['Blockly.blockRendering.TopRow', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/renderers/zelos/path_object.js', ['Blockly.zelos.PathObject'], ['Blockly.blockRendering.PathObject', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/renderers/zelos/renderer.js', ['Blockly.zelos.Renderer'], ['Blockly.InsertionMarkerManager', 'Blockly.blockRendering', 'Blockly.blockRendering.Renderer', 'Blockly.connectionTypes', 'Blockly.utils.object', 'Blockly.zelos.ConstantProvider', 'Blockly.zelos.Drawer', 'Blockly.zelos.MarkerSvg', 'Blockly.zelos.PathObject', 'Blockly.zelos.RenderInfo'], {'lang': 'es6', 'module': 'goog'}); -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.workspaces', 'Blockly.thrasos.Renderer', 'Blockly.zelos.Renderer']); +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.utils.Size'], {'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/exceptions.js', ['Blockly.serialization.exceptions'], [], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/serialization/variables.js', ['Blockly.serialization.variables'], ['Blockly.Events'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/serialization/workspaces.js', ['Blockly.serialization.workspaces'], ['Blockly.Events', 'Blockly.Workspace', 'Blockly.serialization.blocks', 'Blockly.serialization.variables', 'Blockly.utils.dom'], {'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'}); +goog.addDependency('../../core/serialization/variables.js', ['Blockly.serialization.variables'], ['Blockly.Events', 'Blockly.serialization.priorities', 'Blockly.serialization.registry'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/serialization/workspaces.js', ['Blockly.serialization.workspaces'], ['Blockly.Events', 'Blockly.Workspace', 'Blockly.registry', 'Blockly.utils.dom'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/shortcut_items.js', ['Blockly.ShortcutItems'], ['Blockly.Gesture', 'Blockly.ShortcutRegistry', 'Blockly.clipboard', 'Blockly.common', 'Blockly.utils.KeyCodes'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/shortcut_registry.js', ['Blockly.ShortcutRegistry'], ['Blockly.utils.KeyCodes', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/theme.js', ['Blockly.Theme'], ['Blockly.registry', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); diff --git a/tests/mocha/jso_deserialization_test.js b/tests/mocha/jso_deserialization_test.js index 2f9cf59f2..24d74fbf3 100644 --- a/tests/mocha/jso_deserialization_test.js +++ b/tests/mocha/jso_deserialization_test.js @@ -94,188 +94,128 @@ suite('JSO Deserialization', function() { }); suite('Var create', function() { - suite('Top-level call', function() { - test('Just var', function() { - const state = { - 'variables': [ - { - 'name': 'test', - 'id': 'testId', - } - ] - }; - Blockly.serialization.workspaces.load(state, this.workspace); - assertEventFired( - this.eventsFireStub, - Blockly.Events.VarCreate, - { - 'varName': 'test', - 'varId': 'testId', - 'varType': '', - 'recordUndo': false - }, - this.workspace.id); - }); - - test('Record undo', function() { - const state = { - 'variables': [ - { - 'name': 'test', - 'id': 'testId', - } - ] - }; - Blockly.serialization.workspaces.load(state, this.workspace, {recordUndo: true}); - assertEventFired( - this.eventsFireStub, - Blockly.Events.VarCreate, - { - 'varName': 'test', - 'varId': 'testId', - 'varType': '', - 'recordUndo': true - }, - this.workspace.id); - }); - - test('Grouping', function() { - const state = { - 'variables': [ - { - 'name': 'test', - 'id': 'testId', - } - ] - }; - Blockly.Events.setGroup('my group'); - Blockly.serialization.workspaces.load(state, this.workspace); - assertEventFired( - this.eventsFireStub, - Blockly.Events.VarCreate, - { - 'varName': 'test', - 'varId': 'testId', - 'varType': '', - 'group': 'my group' - }, - this.workspace.id); - }); - - test('Multiple vars grouped', function() { - const state = { - 'variables': [ - { - 'name': 'test', - 'id': 'testId', - }, - { - 'name': 'test2', - 'id': 'testId2', - } - ] - }; - Blockly.serialization.workspaces.load(state, this.workspace); - const calls = this.eventsFireStub.getCalls(); - const group = calls[0].args[0].group; - chai.assert.isTrue(calls.every(call => call.args[0].group == group)); - }); - - test('Var with block', function() { - const state = { - 'variables': [ - { - 'name': 'test', - 'id': 'testId', - } - ], - 'blocks': { - 'blocks': [ - { - 'type': 'variables_get', - 'id': 'blockId', - 'x': 42, - 'y': 42, - 'fields': { - 'VAR': 'testId' - } - }, - ] + test('Just var', function() { + const state = { + 'variables': [ + { + 'name': 'test', + 'id': 'testId', } - }; - Blockly.serialization.workspaces.load(state, this.workspace); - const calls = this.eventsFireStub.getCalls(); - const count = calls.reduce((acc, call) => { - if (call.args[0] instanceof Blockly.Events.VarCreate) { - return acc + 1; - } - return acc; - }, 0); - chai.assert.equal(count, 1); - assertEventFired( - this.eventsFireStub, - Blockly.Events.VarCreate, - {'varName': 'test', 'varId': 'testId', 'varType': ''}, - this.workspace.id); - }); + ] + }; + Blockly.serialization.workspaces.load(state, this.workspace); + assertEventFired( + this.eventsFireStub, + Blockly.Events.VarCreate, + { + 'varName': 'test', + 'varId': 'testId', + 'varType': '', + 'recordUndo': false + }, + this.workspace.id); }); - suite('Direct call', function() { - test('Just var', function() { - const state = { - 'name': 'test', - 'id': 'testId', - }; - Blockly.serialization.variables.load(state, this.workspace); - assertEventFired( - this.eventsFireStub, - Blockly.Events.VarCreate, - { - 'varName': 'test', - 'varId': 'testId', - 'varType': '', - 'recordUndo': false - }, - this.workspace.id); - }); + test('Record undo', function() { + const state = { + 'variables': [ + { + 'name': 'test', + 'id': 'testId', + } + ] + }; + Blockly.serialization.workspaces.load(state, this.workspace, {recordUndo: true}); + assertEventFired( + this.eventsFireStub, + Blockly.Events.VarCreate, + { + 'varName': 'test', + 'varId': 'testId', + 'varType': '', + 'recordUndo': true + }, + this.workspace.id); + }); - test('Record undo', function() { - const state = { - 'name': 'test', - 'id': 'testId', - }; - Blockly.serialization.variables - .load(state, this.workspace, {recordUndo: true}); - assertEventFired( - this.eventsFireStub, - Blockly.Events.VarCreate, - { - 'varName': 'test', - 'varId': 'testId', - 'varType': '', - 'recordUndo': true - }, - this.workspace.id); - }); + test('Grouping', function() { + const state = { + 'variables': [ + { + 'name': 'test', + 'id': 'testId', + } + ] + }; + Blockly.Events.setGroup('my group'); + Blockly.serialization.workspaces.load(state, this.workspace); + assertEventFired( + this.eventsFireStub, + Blockly.Events.VarCreate, + { + 'varName': 'test', + 'varId': 'testId', + 'varType': '', + 'group': 'my group' + }, + this.workspace.id); + }); - test('Grouping', function() { - const state = { - 'name': 'test', - 'id': 'testId', - }; - Blockly.Events.setGroup('my group'); - Blockly.serialization.variables.load(state, this.workspace); - assertEventFired( - this.eventsFireStub, - Blockly.Events.VarCreate, + test('Multiple vars grouped', function() { + const state = { + 'variables': [ + { + 'name': 'test', + 'id': 'testId', + }, + { + 'name': 'test2', + 'id': 'testId2', + } + ] + }; + Blockly.serialization.workspaces.load(state, this.workspace); + const calls = this.eventsFireStub.getCalls(); + const group = calls[0].args[0].group; + chai.assert.isTrue(calls.every(call => call.args[0].group == group)); + }); + + test('Var with block', function() { + const state = { + 'variables': [ + { + 'name': 'test', + 'id': 'testId', + } + ], + 'blocks': { + 'blocks': [ { - 'varName': 'test', - 'varId': 'testId', - 'varType': '', - 'group': 'my group' + 'type': 'variables_get', + 'id': 'blockId', + 'x': 42, + 'y': 42, + 'fields': { + 'VAR': 'testId' + } }, - this.workspace.id); - }); + ] + } + }; + Blockly.serialization.workspaces.load(state, this.workspace); + const calls = this.eventsFireStub.getCalls(); + const count = calls.reduce((acc, call) => { + if (call.args[0] instanceof Blockly.Events.VarCreate) { + return acc + 1; + } + return acc; + }, 0); + chai.assert.equal(count, 1); + assertEventFired( + this.eventsFireStub, + Blockly.Events.VarCreate, + {'varName': 'test', 'varId': 'testId', 'varType': ''}, + this.workspace.id); }); }); @@ -681,4 +621,60 @@ suite('JSO Deserialization', function() { }); }); }); + + test('Priority', function() { + const blocksSerializer = Blockly.registry.getClass( + Blockly.registry.Type.SERIALIZER, 'blocks'); + const variablesSerializer = Blockly.registry.getClass( + Blockly.registry.Type.SERIALIZER, 'variables'); + + Blockly.serialization.registry.unregister('blocks'); + Blockly.serialization.registry.unregister('variables'); + + const calls = []; + + const first = { + priority: 100, + save: () => null, + load: () => calls.push('first-load'), + clear: () => calls.push('first-clear'), + }; + const second = { + priority: 0, + save: () => null, + load: () => calls.push('second-load'), + clear: () => calls.push('second-clear'), + }; + const third = { + priority: -10, + save: () => null, + load: () => calls.push('third-load'), + clear: () => calls.push('third-clear'), + }; + + Blockly.serialization.registry.register('third', third); + Blockly.serialization.registry.register('first', first); + Blockly.serialization.registry.register('second', second); + + Blockly.serialization.workspaces.load( + {'first': {}, 'third': {}, 'second': {}}, this.workspace); + + Blockly.serialization.registry.unregister('first'); + Blockly.serialization.registry.unregister('second'); + Blockly.serialization.registry.unregister('third'); + + Blockly.serialization.registry.register('blocks', blocksSerializer); + Blockly.serialization.registry.register('variables', variablesSerializer); + + chai.assert.deepEqual( + calls, + [ + 'third-clear', + 'second-clear', + 'first-clear', + 'first-load', + 'second-load', + 'third-load' + ]); + }); }); diff --git a/tests/mocha/jso_serialization_test.js b/tests/mocha/jso_serialization_test.js index 0d4aeb5ce..836089320 100644 --- a/tests/mocha/jso_serialization_test.js +++ b/tests/mocha/jso_serialization_test.js @@ -699,20 +699,21 @@ suite('JSO Serialization', function() { suite('Variables', function() { test('Without type', function() { - const variable = this.workspace.createVariable('testVar', '', 'testId'); - const jso = Blockly.serialization.variables.save(variable); - assertProperty(jso, 'name', 'testVar'); - assertProperty(jso, 'id', 'testId'); - assertNoProperty(jso, 'type'); + this.workspace.createVariable('testVar', '', 'testId'); + const jso = Blockly.serialization.workspaces.save(this.workspace); + const variable = jso['variables'][0]; + assertProperty(variable, 'name', 'testVar'); + assertProperty(variable, 'id', 'testId'); + assertNoProperty(variable, 'type'); }); test('With type', function() { - const variable = this.workspace - .createVariable('testVar', 'testType', 'testId'); - const jso = Blockly.serialization.variables.save(variable); - assertProperty(jso, 'name', 'testVar'); - assertProperty(jso, 'id', 'testId'); - assertProperty(jso, 'type', 'testType'); + this.workspace.createVariable('testVar', 'testType', 'testId'); + const jso = Blockly.serialization.workspaces.save(this.workspace); + const variable = jso['variables'][0]; + assertProperty(variable, 'name', 'testVar'); + assertProperty(variable, 'id', 'testId'); + assertProperty(variable, 'type', 'testType'); }); }); }); diff --git a/tests/mocha/registry_test.js b/tests/mocha/registry_test.js index 10f01d958..fef243d86 100644 --- a/tests/mocha/registry_test.js +++ b/tests/mocha/registry_test.js @@ -9,6 +9,7 @@ goog.module('Blockly.test.registry'); const {sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers'); + suite('Registry', function() { var TestClass = function() {}; TestClass.prototype.testMethod = function() { @@ -18,39 +19,195 @@ suite('Registry', function() { setup(function() { sharedTestSetup.call(this); }); + teardown(function() { sharedTestTeardown.call(this); - if (Blockly.registry.typeMap_['test'] && - Blockly.registry.typeMap_['test']['test_name']) { - delete Blockly.registry.typeMap_['test']['test_name']; + if (Blockly.registry.hasItem('test', 'test_name')) { + Blockly.registry.unregister('test', 'test_name'); } }); + suite('Registration', function() { test('Simple', function() { Blockly.registry.register('test', 'test_name', TestClass); }); + test('Empty String Key', function() { chai.assert.throws(function() { Blockly.registry.register('test', '', TestClass); }, 'Invalid name'); }); + test('Class as Key', function() { chai.assert.throws(function() { Blockly.registry.register('test', TestClass, ''); }, 'Invalid name'); }); + test('Overwrite a Key', function() { Blockly.registry.register('test', 'test_name', TestClass); chai.assert.throws(function() { Blockly.registry.register('test', 'test_name', TestClass); }, 'already registered'); }); + test('Null Value', function() { chai.assert.throws(function() { Blockly.registry.register('test', 'field_custom_test', null); }, 'Can not register a null value'); }); }); + + suite('hasItem', function() { + setup(function() { + Blockly.registry.register('test', 'test_name', TestClass); + }); + + test('Has', function() { + chai.assert.isTrue(Blockly.registry.hasItem('test', 'test_name')); + }); + + suite('Does not have', function() { + test('Type', function() { + chai.assert.isFalse(Blockly.registry.hasItem('bad_type', 'test_name')); + }); + + test('Name', function() { + chai.assert.isFalse(Blockly.registry.hasItem('test', 'bad_name')); + }); + }); + + suite('Case', function() { + test('Caseless type', function() { + chai.assert.isTrue(Blockly.registry.hasItem('TEST', 'test_name')); + }); + + test('Caseless name', function() { + chai.assert.isTrue(Blockly.registry.hasItem('test', 'TEST_NAME')); + }); + }); + }); + + suite('getClass', function() { + setup(function() { + Blockly.registry.register('test', 'test_name', TestClass); + }); + + test('Has', function() { + chai.assert.isNotNull(Blockly.registry.getClass('test', 'test_name')); + }); + + suite('Does not have', function() { + test('Type', function() { + chai.assert.isNull(Blockly.registry.getClass('bad_type', 'test_name')); + }); + + test('Name', function() { + chai.assert.isNull(Blockly.registry.getClass('test', 'bad_name')); + }); + + test('Throw if missing', function() { + chai.assert.throws(function() { + Blockly.registry.getClass('test', 'bad_name', true); + }); + }); + }); + + suite('Case', function() { + test('Caseless type', function() { + chai.assert.isNotNull(Blockly.registry.getClass('TEST', 'test_name')); + }); + + test('Caseless name', function() { + chai.assert.isNotNull(Blockly.registry.getClass('test', 'TEST_NAME')); + }); + }); + }); + + suite('getObject', function() { + setup(function() { + Blockly.registry.register('test', 'test_name', {}); + }); + + test('Has', function() { + chai.assert.isNotNull(Blockly.registry.getObject('test', 'test_name')); + }); + + suite('Does not have', function() { + test('Type', function() { + chai.assert.isNull(Blockly.registry.getObject('bad_type', 'test_name')); + }); + + test('Name', function() { + chai.assert.isNull(Blockly.registry.getObject('test', 'bad_name')); + }); + + test('Throw if missing', function() { + chai.assert.throws(function() { + Blockly.registry.getObject('test', 'bad_name', true); + }); + }); + }); + + suite('Case', function() { + test('Caseless type', function() { + chai.assert.isNotNull(Blockly.registry.getObject('TEST', 'test_name')); + }); + + test('Caseless name', function() { + chai.assert.isNotNull(Blockly.registry.getObject('test', 'TEST_NAME')); + }); + }); + }); + + suite('getAllItems', function() { + setup(function() { + Blockly.registry.register('test', 'test_name', {}); + Blockly.registry.register('test', 'casedNAME', {}); + }); + + teardown(function() { + Blockly.registry.unregister('test', 'casedname'); + }); + + test('Has', function() { + chai.assert.isNotNull(Blockly.registry.getAllItems('test')); + }); + + test('Does not have', function() { + chai.assert.isNull(Blockly.registry.getAllItems('bad_type')); + }); + + test('Throw if missing', function() { + chai.assert.throws(function() { + Blockly.registry.getAllItems('bad_type', false, true); + }); + }); + + test('Ignore type case', function() { + chai.assert.isNotNull(Blockly.registry.getAllItems('TEST')); + }); + + test('Respect name case', function() { + chai.assert.deepEqual( + Blockly.registry.getAllItems('test', true), + { + 'test_name': {}, + 'casedNAME': {} + }); + }); + + test('Respect overwriting name case', function() { + Blockly.registry.register('test', 'CASEDname', {}, true); + chai.assert.deepEqual( + Blockly.registry.getAllItems('test', true), + { + 'test_name': {}, + 'CASEDname': {} + }); + }); + }); + suite('getClassFromOptions', function() { setup(function() { this.defaultClass = function() {}; @@ -62,25 +219,31 @@ suite('Registry', function() { 'test' : 'test_name' } }; - Blockly.registry.typeMap_['test'] = { - 'test_name': TestClass, - 'default': this.defaultClass - }; + Blockly.registry.register('test', 'test_name', TestClass); + Blockly.registry.register('test', 'default', this.defaultClass); }); + + teardown(function() { + Blockly.registry.unregister('test', 'default'); + }); + test('Simple - Plugin name given', function() { var testClass = Blockly.registry.getClassFromOptions('test', this.options); chai.assert.instanceOf(new testClass(), TestClass); }); + test('Simple - Plugin class given', function() { this.options.plugins['test'] = TestClass; var testClass = Blockly.registry.getClassFromOptions('test', this.options); chai.assert.instanceOf(new testClass(), TestClass); }); + test('No Plugin Name Given', function() { delete this.options['plugins']['test']; var testClass = Blockly.registry.getClassFromOptions('test', this.options); chai.assert.instanceOf(new testClass(), this.defaultClass); }); + test('Incorrect Plugin Name', function() { this.options['plugins']['test'] = 'random'; var testClass;