From 1a4f0a75f243eea010bf23b1ecd933e223df6783 Mon Sep 17 00:00:00 2001 From: Karishma Chadha Date: Sun, 21 Oct 2018 00:22:39 -0400 Subject: [PATCH 1/8] Serialize and deserialize monitors. Obtain monitor block information from runtime. Fix issue where stage monitors weren't getting loaded correctly. --- src/engine/blocks.js | 19 +++++++++++- src/engine/runtime.js | 4 +++ src/serialization/sb3.js | 66 ++++++++++++++++++++++++++++++++++++++-- src/virtual-machine.js | 2 +- 4 files changed, 87 insertions(+), 4 deletions(-) diff --git a/src/engine/blocks.js b/src/engine/blocks.js index 00cc5176131..69672d00418 100644 --- a/src/engine/blocks.js +++ b/src/engine/blocks.js @@ -282,8 +282,10 @@ class Blocks { * runtime interface. * @param {object} e Blockly "block" or "variable" event * @param {?Runtime} optRuntime Optional runtime to forward click events to. + * @param {boolean=} forMonitors Whether the event being listened to pertains to a + * monitor block. */ - blocklyListen (e, optRuntime) { + blocklyListen (e, optRuntime, forMonitors) { // Validate event if (typeof e !== 'object') return; if (typeof e.blockId !== 'string' && typeof e.varId !== 'string' && @@ -305,6 +307,21 @@ class Blocks { switch (e.type) { case 'create': { const newBlocks = adapter(e); + + if (forMonitors) { + // If this is the monitor block container, + // add the appropriate info to the monitorBlock looking it up + // in the runtime monitorState + const topLevelBlock = newBlocks[0]; + if (optRuntime && optRuntime.getMonitorState().has(topLevelBlock.id)) { + + const monitorData = optRuntime.getMonitorState().get(topLevelBlock.id); + + topLevelBlock.isMonitored = monitorData.visible; + topLevelBlock.targetId = monitorData.targetId; + } + } + // A create event can create many blocks. Add them all. for (let i = 0; i < newBlocks.length; i++) { this.createBlock(newBlocks[i]); diff --git a/src/engine/runtime.js b/src/engine/runtime.js index 942c0e61255..66d6dee3fe7 100644 --- a/src/engine/runtime.js +++ b/src/engine/runtime.js @@ -659,6 +659,10 @@ class Runtime extends EventEmitter { } } + getMonitorState () { + return this._monitorState; + } + /** * Generate an extension-specific menu ID. * @param {string} menuName - the name of the menu. diff --git a/src/serialization/sb3.js b/src/serialization/sb3.js index 668305bcf06..b04ac8ebc1c 100644 --- a/src/serialization/sb3.js +++ b/src/serialization/sb3.js @@ -9,6 +9,7 @@ const Blocks = require('../engine/blocks'); const Sprite = require('../sprites/sprite'); const Variable = require('../engine/variable'); const Comment = require('../engine/comment'); +const MonitorRecord = require('../engine/monitor-record'); const StageLayering = require('../engine/stage-layering'); const log = require('../util/log'); const uid = require('../util/uid'); @@ -480,6 +481,29 @@ const getSimplifiedLayerOrdering = function (targets) { return MathUtil.reducedSortOrdering(layerOrders); }; +const serializeMonitors = function (monitors) { + return monitors.valueSeq().map(monitorData => { + const serializedMonitor = { + id: monitorData.id, + mode: monitorData.mode, + opcode: monitorData.opcode, + params: monitorData.params, + spriteName: monitorData.spriteName, + value: monitorData.value, + width: monitorData.width, + height: monitorData.height, + x: monitorData.x, + y: monitorData.y, + visible: monitorData.visible + }; + if (monitorData.mode !== 'list') { + serializedMonitor.min = monitorData.sliderMin; + serializedMonitor.max = monitorData.sliderMax; + } + return serializedMonitor; + }); +}; + /** * Serializes the specified VM runtime. * @param {!Runtime} runtime VM runtime instance to be serialized. @@ -516,8 +540,7 @@ const serialize = function (runtime, targetId) { obj.targets = serializedTargets; - - // TODO Serialize monitors + obj.monitors = serializeMonitors(runtime.getMonitorState()); // Assemble extension list obj.extensions = Array.from(extensions); @@ -1015,6 +1038,39 @@ const parseScratchObject = function (object, runtime, extensions, zip) { return Promise.all(costumePromises.concat(soundPromises)).then(() => target); }; +const deserializeMonitor = function (monitorData, runtime, targets) { + const monitorBlockInfo = runtime.monitorBlockInfo[monitorData.opcode]; + if (monitorData.spriteName) { + const filteredTargets = targets.filter(t => t.sprite.name === monitorData.spriteName); + if (!filteredTargets || filteredTargets.length !== 1) { + log.error(`Could not deserialize monitor ${monitorData.opcode} for sprite ${ + monitorData.spriteName} because no such sprite could be found.`); + } + monitorData.targetId = filteredTargets[0].id; + } + + if (monitorData.opcode !== 'data_variable' && monitorData.opcode !== 'data_listcontents') { + // Variables and lists already have their ID serialized in the monitorData, + // find the correct id for all other monitors. monitorBlockInfo.getId should + // ignore the given parameters if the monitor in question is not target specific. + monitorData.id = monitorBlockInfo.getId( + monitorData.targetId, Object.values(monitorData.params)[0]); + } + + const existingMonitorBlock = runtime.monitorBlocks._blocks[monitorData.id]; + if (existingMonitorBlock) { + // A monitor block already exists if the toolbox has been loaded and + // the monitor block is not target specific (because the block gets recycled). + // Update the existing block with the relevant monitor information. + existingMonitorBlock.isMonitored = monitorData.visible; + existingMonitorBlock.targetId = monitorData.targetId; + } + // Otherwise, the monitor block will get created when the toolbox updates + // after the target has been installed. + + runtime.requestAddMonitor(MonitorRecord(monitorData)); +}; + /** * Deserialize the specified representation of a VM runtime and loads it into the provided runtime instance. * @param {object} json - JSON representation of a VM runtime. @@ -1037,6 +1093,8 @@ const deserialize = function (json, runtime, zip, isSingleSprite) { .map((t, i) => Object.assign(t, {targetPaneOrder: i})) .sort((a, b) => a.layerOrder - b.layerOrder); + const monitorObjects = json.monitors || []; + return Promise.all( targetObjects.map(target => parseScratchObject(target, runtime, extensions, zip)) @@ -1050,6 +1108,10 @@ const deserialize = function (json, runtime, zip, isSingleSprite) { delete t.layerOrder; return t; })) + .then(targets => { + monitorObjects.map(monitorDesc => deserializeMonitor(monitorDesc, runtime, targets)); + return targets; + }) .then(targets => ({ targets, extensions diff --git a/src/virtual-machine.js b/src/virtual-machine.js index 764242d31f3..606dbb25019 100644 --- a/src/virtual-machine.js +++ b/src/virtual-machine.js @@ -1060,7 +1060,7 @@ class VirtualMachine extends EventEmitter { // Filter events by type, since monitor blocks only need to listen to these events. // Monitor blocks shouldn't be destroyed when flyout blocks are deleted. if (['create', 'change'].indexOf(e.type) !== -1) { - this.runtime.monitorBlocks.blocklyListen(e, this.runtime); + this.runtime.monitorBlocks.blocklyListen(e, this.runtime, true); } } From d690c76a34b1fa6e72ccfd3d55ed60d3f3a6314f Mon Sep 17 00:00:00 2001 From: Karishma Chadha Date: Sun, 21 Oct 2018 00:23:08 -0400 Subject: [PATCH 2/8] Update sb2 deserialization of monitors. --- src/serialization/sb2.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/serialization/sb2.js b/src/serialization/sb2.js index d45fbffe166..cab31f7e12b 100644 --- a/src/serialization/sb2.js +++ b/src/serialization/sb2.js @@ -287,7 +287,7 @@ const parseMonitorObject = (object, runtime, targets, extensions) => { {}, null, // `comments`, not needed for monitor blocks null // `commentIndex`, not needed for monitor blocks - ); + ); // This block is needed for the `getBlockParams` call down below // Monitor blocks have special IDs to match the toolbox obtained from the getId // function in the runtime.monitorBlocksInfo. Variable monitors, however, @@ -326,6 +326,9 @@ const parseMonitorObject = (object, runtime, targets, extensions) => { runtime.monitorBlocks.createBlock(newBlocks[i]); } } + // In the case where a monitor block does not already exist, one will get created + // when the target is installed because the toolbox will reload. + // Convert numbered mode into strings for better understandability. switch (object.mode) { From ebdd548c8faf15652c0cf714ad30a44186914d80 Mon Sep 17 00:00:00 2001 From: Karishma Chadha Date: Sun, 21 Oct 2018 00:25:50 -0400 Subject: [PATCH 3/8] Update for auto-positioning monitors so that having just one of x or y be 0 (e.g. a monitor placed in the top right corner of the stage) does not result in the monitor being treated as a new monitor and get auto-positioned. --- src/engine/monitor-record.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/engine/monitor-record.js b/src/engine/monitor-record.js index 925b7c5f25d..daa6f9db124 100644 --- a/src/engine/monitor-record.js +++ b/src/engine/monitor-record.js @@ -12,8 +12,8 @@ const MonitorRecord = Record({ mode: 'default', sliderMin: 0, sliderMax: 100, - x: 0, - y: 0, + x: null, // (x: null, y: null) Indicates that the monitor should be auto-positioned + y: null, width: 0, height: 0, visible: true From 1b9b3ac909bb89d0ac570d31121d2ba342970555 Mon Sep 17 00:00:00 2001 From: Karishma Chadha Date: Thu, 8 Nov 2018 14:59:14 -0500 Subject: [PATCH 4/8] Revert changes that were made to leverage the monitor blocks getting created when the toolbox is loaded. This approach does not work when viewing the project detached from scratch-blocks (e.g. player mode). --- src/engine/blocks.js | 19 +------------------ src/serialization/sb2.js | 5 +---- src/virtual-machine.js | 2 +- 3 files changed, 3 insertions(+), 23 deletions(-) diff --git a/src/engine/blocks.js b/src/engine/blocks.js index 69672d00418..00cc5176131 100644 --- a/src/engine/blocks.js +++ b/src/engine/blocks.js @@ -282,10 +282,8 @@ class Blocks { * runtime interface. * @param {object} e Blockly "block" or "variable" event * @param {?Runtime} optRuntime Optional runtime to forward click events to. - * @param {boolean=} forMonitors Whether the event being listened to pertains to a - * monitor block. */ - blocklyListen (e, optRuntime, forMonitors) { + blocklyListen (e, optRuntime) { // Validate event if (typeof e !== 'object') return; if (typeof e.blockId !== 'string' && typeof e.varId !== 'string' && @@ -307,21 +305,6 @@ class Blocks { switch (e.type) { case 'create': { const newBlocks = adapter(e); - - if (forMonitors) { - // If this is the monitor block container, - // add the appropriate info to the monitorBlock looking it up - // in the runtime monitorState - const topLevelBlock = newBlocks[0]; - if (optRuntime && optRuntime.getMonitorState().has(topLevelBlock.id)) { - - const monitorData = optRuntime.getMonitorState().get(topLevelBlock.id); - - topLevelBlock.isMonitored = monitorData.visible; - topLevelBlock.targetId = monitorData.targetId; - } - } - // A create event can create many blocks. Add them all. for (let i = 0; i < newBlocks.length; i++) { this.createBlock(newBlocks[i]); diff --git a/src/serialization/sb2.js b/src/serialization/sb2.js index cab31f7e12b..d45fbffe166 100644 --- a/src/serialization/sb2.js +++ b/src/serialization/sb2.js @@ -287,7 +287,7 @@ const parseMonitorObject = (object, runtime, targets, extensions) => { {}, null, // `comments`, not needed for monitor blocks null // `commentIndex`, not needed for monitor blocks - ); // This block is needed for the `getBlockParams` call down below + ); // Monitor blocks have special IDs to match the toolbox obtained from the getId // function in the runtime.monitorBlocksInfo. Variable monitors, however, @@ -326,9 +326,6 @@ const parseMonitorObject = (object, runtime, targets, extensions) => { runtime.monitorBlocks.createBlock(newBlocks[i]); } } - // In the case where a monitor block does not already exist, one will get created - // when the target is installed because the toolbox will reload. - // Convert numbered mode into strings for better understandability. switch (object.mode) { diff --git a/src/virtual-machine.js b/src/virtual-machine.js index 606dbb25019..764242d31f3 100644 --- a/src/virtual-machine.js +++ b/src/virtual-machine.js @@ -1060,7 +1060,7 @@ class VirtualMachine extends EventEmitter { // Filter events by type, since monitor blocks only need to listen to these events. // Monitor blocks shouldn't be destroyed when flyout blocks are deleted. if (['create', 'change'].indexOf(e.type) !== -1) { - this.runtime.monitorBlocks.blocklyListen(e, this.runtime, true); + this.runtime.monitorBlocks.blocklyListen(e, this.runtime); } } From df24ca7b46aed6ee993967bbb5ec03881b51a993 Mon Sep 17 00:00:00 2001 From: Karishma Chadha Date: Fri, 9 Nov 2018 23:32:50 -0500 Subject: [PATCH 5/8] Refactor sb3 monitor deserialization. Create monitor blocks with the relevant info if they don't exist. --- src/serialization/sb3.js | 73 ++++++++++++++++++++++++++++++++++------ 1 file changed, 62 insertions(+), 11 deletions(-) diff --git a/src/serialization/sb3.js b/src/serialization/sb3.js index b04ac8ebc1c..63ef3235ff9 100644 --- a/src/serialization/sb3.js +++ b/src/serialization/sb3.js @@ -1039,31 +1039,82 @@ const parseScratchObject = function (object, runtime, extensions, zip) { }; const deserializeMonitor = function (monitorData, runtime, targets) { - const monitorBlockInfo = runtime.monitorBlockInfo[monitorData.opcode]; + // If the serialized monitor has spriteName defined, look up the sprite + // by name in the given list of targets and update the monitor's targetId + // to match the sprite's id. if (monitorData.spriteName) { const filteredTargets = targets.filter(t => t.sprite.name === monitorData.spriteName); - if (!filteredTargets || filteredTargets.length !== 1) { - log.error(`Could not deserialize monitor ${monitorData.opcode} for sprite ${ - monitorData.spriteName} because no such sprite could be found.`); + if (filteredTargets && filteredTargets.length > 0) { + monitorData.targetId = filteredTargets[0].id; + } else { + log.warn(`Tried to deserialize sprite specific monitor ${ + monitorData.opcode} but could not find sprite ${monitorData.spriteName}.`); } - monitorData.targetId = filteredTargets[0].id; } - if (monitorData.opcode !== 'data_variable' && monitorData.opcode !== 'data_listcontents') { - // Variables and lists already have their ID serialized in the monitorData, - // find the correct id for all other monitors. monitorBlockInfo.getId should - // ignore the given parameters if the monitor in question is not target specific. + // Get information about this monitor, if it exists, given the monitor's opcode. + // This will be undefined for extension blocks + const monitorBlockInfo = runtime.monitorBlockInfo[monitorData.opcode]; + + // Convert the serialized monitorData params into the block fields structure + const fields = {}; + for (const paramKey in monitorData.params) { + const field = { + name: paramKey, + value: monitorData.params[paramKey] + }; + fields[paramKey] = field; + } + + // Variables, lists, and non-sprite-specific monitors, including any extension + // monitors should already have the correct monitor ID serialized in the monitorData, + // find the correct id for all other monitors. + if (monitorData.opcode !== 'data_variable' && monitorData.opcode !== 'data_listcontents' && + monitorBlockInfo && monitorBlockInfo.isSpriteSpecific) { monitorData.id = monitorBlockInfo.getId( - monitorData.targetId, Object.values(monitorData.params)[0]); + monitorData.targetId, fields); } + // If the runtime already has a monitor block for this monitor's id, + // update the existing block with the relevant monitor information. const existingMonitorBlock = runtime.monitorBlocks._blocks[monitorData.id]; if (existingMonitorBlock) { // A monitor block already exists if the toolbox has been loaded and // the monitor block is not target specific (because the block gets recycled). - // Update the existing block with the relevant monitor information. existingMonitorBlock.isMonitored = monitorData.visible; existingMonitorBlock.targetId = monitorData.targetId; + } else { + // If a monitor block doens't already exist for this monitor, + // construct a monitor block to add to the monitor blocks container + const monitorBlock = { + id: monitorData.id, + opcode: monitorData.opcode, + inputs: {}, // Assuming that monitor blocks don't have droppable fields + fields: fields, + topLevel: true, + next: null, + parent: null, + shadow: false, + x: 0, + y: 0, + isMonitored: monitorData.visible, + targetId: monitorData.targetId + }; + + // Variables and lists have additional properties + // stored in their fields, update this info in the + // monitor block fields + if (monitorData.opcode === 'data_variable') { + const field = monitorBlock.fields.VARIABLE; + field.id = monitorData.id; + field.variableType = Variable.SCALAR_TYPE; + } else if (monitorData.opcode === 'data_listcontents') { + const field = monitorBlock.fields.LIST; + field.id = monitorData.id; + field.variableType = Variable.LIST_TYPE; + } + + runtime.monitorBlocks.createBlock(monitorBlock); } // Otherwise, the monitor block will get created when the toolbox updates // after the target has been installed. From 14bee14d8c268185e14afbb5ff9fa29a958a6934 Mon Sep 17 00:00:00 2001 From: Karishma Chadha Date: Fri, 9 Nov 2018 23:34:06 -0500 Subject: [PATCH 6/8] Update extensions to load when deserializing monitors. This is necessary when the monitor is the only part of the project that uses the extension. Update extension monitor color category. --- src/engine/runtime.js | 4 ++-- src/serialization/sb3.js | 10 ++++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/engine/runtime.js b/src/engine/runtime.js index 66d6dee3fe7..b635bbd1fea 100644 --- a/src/engine/runtime.js +++ b/src/engine/runtime.js @@ -2102,9 +2102,9 @@ class Runtime extends EventEmitter { const block = categoryInfo.blocks.find(b => b.info.opcode === opcode); if (!block) return; - // TODO: should this use some other category? Also, we may want to format the label in a locale-specific way. + // TODO: we may want to format the label in a locale-specific way. return { - category: 'data', + category: 'extension', // This assumes that all extensions have the same monitor color. label: `${categoryInfo.name}: ${block.info.text}` }; } diff --git a/src/serialization/sb3.js b/src/serialization/sb3.js index 63ef3235ff9..66d7e033ab3 100644 --- a/src/serialization/sb3.js +++ b/src/serialization/sb3.js @@ -1038,7 +1038,7 @@ const parseScratchObject = function (object, runtime, extensions, zip) { return Promise.all(costumePromises.concat(soundPromises)).then(() => target); }; -const deserializeMonitor = function (monitorData, runtime, targets) { +const deserializeMonitor = function (monitorData, runtime, targets, extensions) { // If the serialized monitor has spriteName defined, look up the sprite // by name in the given list of targets and update the monitor's targetId // to match the sprite's id. @@ -1115,6 +1115,12 @@ const deserializeMonitor = function (monitorData, runtime, targets) { } runtime.monitorBlocks.createBlock(monitorBlock); + + // If the block is from an extension, record it. + const extensionID = getExtensionIdForOpcode(monitorBlock.opcode); + if (extensionID) { + extensions.extensionIDs.add(extensionID); + } } // Otherwise, the monitor block will get created when the toolbox updates // after the target has been installed. @@ -1160,7 +1166,7 @@ const deserialize = function (json, runtime, zip, isSingleSprite) { return t; })) .then(targets => { - monitorObjects.map(monitorDesc => deserializeMonitor(monitorDesc, runtime, targets)); + monitorObjects.map(monitorDesc => deserializeMonitor(monitorDesc, runtime, targets, extensions)); return targets; }) .then(targets => ({ From 6dfc0b678eede58b085bb75968e41db547434674 Mon Sep 17 00:00:00 2001 From: Karishma Chadha Date: Fri, 9 Nov 2018 23:45:23 -0500 Subject: [PATCH 7/8] Add integration test for monitor save/load. --- test/fixtures/monitors.sb3 | Bin 0 -> 50213 bytes .../{monitors.js => monitors_sb2.js} | 0 test/integration/monitors_sb3.js | 264 ++++++++++++++++++ 3 files changed, 264 insertions(+) create mode 100644 test/fixtures/monitors.sb3 rename test/integration/{monitors.js => monitors_sb2.js} (100%) create mode 100644 test/integration/monitors_sb3.js diff --git a/test/fixtures/monitors.sb3 b/test/fixtures/monitors.sb3 new file mode 100644 index 0000000000000000000000000000000000000000..1343243ddd84bdacfcc94c6424440998d2454b1b GIT binary patch literal 50213 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 E7Yis(Gynhq literal 0 HcmV?d00001 diff --git a/test/integration/monitors.js b/test/integration/monitors_sb2.js similarity index 100% rename from test/integration/monitors.js rename to test/integration/monitors_sb2.js diff --git a/test/integration/monitors_sb3.js b/test/integration/monitors_sb3.js new file mode 100644 index 00000000000..60a280e98f5 --- /dev/null +++ b/test/integration/monitors_sb3.js @@ -0,0 +1,264 @@ +const path = require('path'); +const test = require('tap').test; +const makeTestStorage = require('../fixtures/make-test-storage'); +const readFileToBuffer = require('../fixtures/readProjectFile').readFileToBuffer; +const VirtualMachine = require('../../src/index'); +const Variable = require('../../src/engine/variable'); + +const projectUri = path.resolve(__dirname, '../fixtures/monitors.sb3'); +const project = readFileToBuffer(projectUri); + +test('importing sb2 project with monitors', t => { + const vm = new VirtualMachine(); + vm.attachStorage(makeTestStorage()); + + // Evaluate playground data and exit + vm.on('playgroundData', e => { + const threads = JSON.parse(e.threads); + // All monitors should create threads that finish during the step and + // are revoved from runtime.threads. + t.equal(threads.length, 0); + t.equal(vm.runtime._lastStepDoneThreads.length, 17); + // There should be one additional hidden monitor that is in the monitorState but + // does not start a thread. + t.equal(vm.runtime._monitorState.size, 18); + + const stage = vm.runtime.targets[0]; + const shirtSprite = vm.runtime.targets[1]; + const heartSprite = vm.runtime.targets[2]; + + // Global variable named "my variable" exists + let variableId = Object.keys(stage.variables).filter(k => stage.variables[k].name === 'my variable')[0]; + let monitorRecord = vm.runtime._monitorState.get(variableId); + let monitorBlock = vm.runtime.monitorBlocks.getBlock(variableId); + t.equal(monitorRecord.opcode, 'data_variable'); + t.equal(monitorRecord.mode, 'default'); + // The following few properties are imported for all monitors, just check once. + // sliderMin and sliderMax are currently not implemented, + // but should still get default values serialized and deserialized correctly + t.equal(monitorRecord.sliderMin, 0); + t.equal(monitorRecord.sliderMax, 100); + t.equal(monitorRecord.x, 10); + t.equal(monitorRecord.y, 62); + // Height and width are only used for list monitors and should default to 0 + // for all other monitors + t.equal(monitorRecord.width, 0); + t.equal(monitorRecord.height, 0); + t.equal(monitorRecord.visible, true); + t.type(monitorRecord.params, 'object'); + // The variable name should be stored in the monitor params + t.equal(monitorRecord.params.VARIABLE, 'my variable'); + // Test that the monitor block and its fields were constructed correctly + t.equal(monitorBlock.fields.VARIABLE.value, 'my variable'); + t.equal(monitorBlock.fields.VARIABLE.name, 'VARIABLE'); + t.equal(monitorBlock.fields.VARIABLE.id, variableId); + t.equal(monitorBlock.fields.VARIABLE.variableType, Variable.SCALAR_TYPE); + + // There is a global variable named 'secret_slide' which has a hidden monitor + variableId = Object.keys(stage.variables).filter(k => stage.variables[k].name === 'secret_slide')[0]; + monitorRecord = vm.runtime._monitorState.get(variableId); + monitorBlock = vm.runtime.monitorBlocks.getBlock(variableId); + t.equal(monitorRecord.opcode, 'data_variable'); + t.equal(monitorRecord.mode, 'slider'); + t.equal(monitorRecord.visible, false); + t.equal(monitorRecord.sliderMin, 0); + t.equal(monitorRecord.sliderMax, 100); + t.type(monitorRecord.params, 'object'); + t.equal(monitorRecord.params.VARIABLE, 'secret_slide'); + // Test that the monitor block and its fields were constructed correctly + t.equal(monitorBlock.fields.VARIABLE.value, 'secret_slide'); + t.equal(monitorBlock.fields.VARIABLE.name, 'VARIABLE'); + t.equal(monitorBlock.fields.VARIABLE.id, variableId); + t.equal(monitorBlock.fields.VARIABLE.variableType, Variable.SCALAR_TYPE); + + + // Shirt sprite has a local list named "fashion" + variableId = Object.keys(shirtSprite.variables).filter(k => shirtSprite.variables[k].name === 'fashion')[0]; + monitorRecord = vm.runtime._monitorState.get(variableId); + monitorBlock = vm.runtime.monitorBlocks.getBlock(variableId); + t.equal(monitorRecord.opcode, 'data_listcontents'); + t.equal(monitorRecord.mode, 'list'); + t.equal(monitorRecord.visible, true); + t.equal(monitorRecord.height, 122); + t.equal(monitorRecord.width, 104); + t.type(monitorRecord.params, 'object'); + t.equal(monitorRecord.params.LIST, 'fashion'); // The list name should be stored in the monitor params + // Test that the monitor block and its fields were constructed correctly + t.equal(monitorBlock.fields.LIST.value, 'fashion'); + t.equal(monitorBlock.fields.LIST.name, 'LIST'); + t.equal(monitorBlock.fields.LIST.id, variableId); + t.equal(monitorBlock.fields.LIST.variableType, Variable.LIST_TYPE); + + // Shirt sprite has a local variable named "tee" + variableId = Object.keys(shirtSprite.variables).filter(k => shirtSprite.variables[k].name === 'tee')[0]; + monitorRecord = vm.runtime._monitorState.get(variableId); + monitorBlock = vm.runtime.monitorBlocks.getBlock(variableId); + t.equal(monitorRecord.opcode, 'data_variable'); + t.equal(monitorRecord.mode, 'slider'); + t.equal(monitorRecord.visible, true); + t.equal(monitorRecord.sliderMin, 0); + t.equal(monitorRecord.sliderMax, 100); + t.type(monitorRecord.params, 'object'); + t.equal(monitorRecord.params.VARIABLE, 'tee'); + // Test that the monitor block and its fields were constructed correctly + t.equal(monitorBlock.fields.VARIABLE.value, 'tee'); + t.equal(monitorBlock.fields.VARIABLE.name, 'VARIABLE'); + t.equal(monitorBlock.fields.VARIABLE.id, variableId); + t.equal(monitorBlock.fields.VARIABLE.variableType, Variable.SCALAR_TYPE); + + // Heart sprite has a local list named "hearty" + variableId = Object.keys(heartSprite.variables).filter(k => heartSprite.variables[k].name === 'hearty')[0]; + monitorRecord = vm.runtime._monitorState.get(variableId); + monitorBlock = vm.runtime.monitorBlocks.getBlock(variableId); + t.equal(monitorRecord.opcode, 'data_variable'); + t.equal(monitorRecord.mode, 'default'); + t.equal(monitorRecord.visible, true); + t.type(monitorRecord.params, 'object'); + t.equal(monitorRecord.params.VARIABLE, 'hearty'); // The variable name should be stored in the monitor params + // Test that the monitor block and its fields were constructed correctly + t.equal(monitorBlock.fields.VARIABLE.value, 'hearty'); + t.equal(monitorBlock.fields.VARIABLE.name, 'VARIABLE'); + t.equal(monitorBlock.fields.VARIABLE.id, variableId); + t.equal(monitorBlock.fields.VARIABLE.variableType, Variable.SCALAR_TYPE); + + // Backdrop name monitor is visible, not sprite specific + // should get imported with id that references the name parameter + // via '_name' at the end since the 3.0 block has a dropdown. + let monitorId = 'backdropnumbername_name'; + monitorRecord = vm.runtime._monitorState.get(monitorId); + monitorBlock = vm.runtime.monitorBlocks.getBlock(monitorId); + t.equal(monitorRecord.opcode, 'looks_backdropnumbername'); + t.equal(monitorRecord.mode, 'default'); + t.equal(monitorRecord.visible, true); + t.equal(monitorRecord.spriteName, null); + t.equal(monitorRecord.targetId, null); + // Test that the monitor block and its fields were constructed correctly + t.equal(monitorBlock.fields.NUMBER_NAME.value, 'name'); + + // Backdrop name monitor is visible, not sprite specific + // should get imported with id that references the name parameter + // via '_number' at the end since the 3.0 block has a dropdown. + monitorId = 'backdropnumbername_number'; + monitorRecord = vm.runtime._monitorState.get(monitorId); + monitorBlock = vm.runtime.monitorBlocks.getBlock(monitorId); + t.equal(monitorRecord.opcode, 'looks_backdropnumbername'); + t.equal(monitorRecord.mode, 'default'); + t.equal(monitorRecord.visible, true); + t.equal(monitorRecord.spriteName, null); + t.equal(monitorRecord.targetId, null); + // Test that the monitor block and its fields were constructed correctly + t.equal(monitorBlock.fields.NUMBER_NAME.value, 'number'); + + // x position monitor is in large mode, specific to shirt sprite + monitorId = `${shirtSprite.id}_xposition`; + monitorRecord = vm.runtime._monitorState.get(monitorId); + monitorBlock = vm.runtime.monitorBlocks.getBlock(monitorId); + t.equal(monitorRecord.opcode, 'motion_xposition'); + t.equal(monitorRecord.mode, 'large'); + t.equal(monitorRecord.visible, true); + t.equal(monitorRecord.spriteName, 'Shirt-T'); + t.equal(monitorRecord.targetId, shirtSprite.id); + + // y position monitor is in large mode, specific to shirt sprite + monitorId = `${shirtSprite.id}_yposition`; + monitorRecord = vm.runtime._monitorState.get(monitorId); + monitorBlock = vm.runtime.monitorBlocks.getBlock(monitorId); + t.equal(monitorRecord.opcode, 'motion_yposition'); + t.equal(monitorRecord.mode, 'large'); + t.equal(monitorRecord.visible, true); + t.equal(monitorRecord.spriteName, 'Shirt-T'); + t.equal(monitorRecord.targetId, shirtSprite.id); + + // direction monitor is in large mode, specific to shirt sprite + monitorId = `${shirtSprite.id}_direction`; + monitorRecord = vm.runtime._monitorState.get(monitorId); + monitorBlock = vm.runtime.monitorBlocks.getBlock(monitorId); + t.equal(monitorRecord.opcode, 'motion_direction'); + t.equal(monitorRecord.mode, 'large'); + t.equal(monitorRecord.visible, true); + t.equal(monitorRecord.spriteName, 'Shirt-T'); + t.equal(monitorRecord.targetId, shirtSprite.id); + + monitorId = `${shirtSprite.id}_size`; + monitorRecord = vm.runtime._monitorState.get(monitorId); + monitorBlock = vm.runtime.monitorBlocks.getBlock(monitorId); + t.equal(monitorRecord.opcode, 'looks_size'); + t.equal(monitorRecord.mode, 'large'); + t.equal(monitorRecord.visible, true); + t.equal(monitorRecord.spriteName, 'Shirt-T'); + t.equal(monitorRecord.targetId, shirtSprite.id); + + // The monitor IDs for the sensing_current block should be unique + // to the parameter that is selected on the block being monitored. + // The paramater portion of the id should be lowercase even + // though the field value on the block is uppercase. + monitorId = 'current_date'; + monitorRecord = vm.runtime._monitorState.get(monitorId); + t.equal(monitorRecord.opcode, 'sensing_current'); + monitorBlock = vm.runtime.monitorBlocks.getBlock(monitorId); + t.equal(monitorBlock.fields.CURRENTMENU.value, 'DATE'); + t.equal(monitorRecord.mode, 'default'); + t.equal(monitorRecord.visible, true); + t.equal(monitorRecord.spriteName, null); + t.equal(monitorRecord.targetId, null); + + monitorId = 'current_year'; + monitorRecord = vm.runtime._monitorState.get(monitorId); + t.equal(monitorRecord.opcode, 'sensing_current'); + monitorBlock = vm.runtime.monitorBlocks.getBlock(monitorId); + t.equal(monitorBlock.fields.CURRENTMENU.value, 'YEAR'); + t.equal(monitorRecord.mode, 'default'); + t.equal(monitorRecord.visible, true); + t.equal(monitorRecord.spriteName, null); + t.equal(monitorRecord.targetId, null); + + monitorId = 'current_month'; + monitorRecord = vm.runtime._monitorState.get(monitorId); + t.equal(monitorRecord.opcode, 'sensing_current'); + monitorBlock = vm.runtime.monitorBlocks.getBlock(monitorId); + t.equal(monitorBlock.fields.CURRENTMENU.value, 'MONTH'); + t.equal(monitorRecord.mode, 'default'); + t.equal(monitorRecord.visible, true); + t.equal(monitorRecord.spriteName, null); + t.equal(monitorRecord.targetId, null); + + // Extension Monitors + monitorId = 'music_getTempo'; + monitorRecord = vm.runtime._monitorState.get(monitorId); + t.equal(monitorRecord.opcode, 'music_getTempo'); + monitorBlock = vm.runtime.monitorBlocks.getBlock(monitorId); + t.equal(monitorRecord.mode, 'default'); + t.equal(monitorRecord.visible, true); + t.equal(monitorRecord.spriteName, null); + t.equal(monitorRecord.targetId, null); + t.equal(vm.extensionManager.isExtensionLoaded('music'), true); + + monitorId = 'ev3_getDistance'; + monitorRecord = vm.runtime._monitorState.get(monitorId); + t.equal(monitorRecord.opcode, 'ev3_getDistance'); + monitorBlock = vm.runtime.monitorBlocks.getBlock(monitorId); + t.equal(monitorRecord.mode, 'default'); + t.equal(monitorRecord.visible, true); + t.equal(monitorRecord.spriteName, null); + t.equal(monitorRecord.targetId, null); + t.equal(vm.extensionManager.isExtensionLoaded('ev3'), true); + + t.end(); + process.nextTick(process.exit); + }); + + // Start VM, load project, and run + t.doesNotThrow(() => { + vm.start(); + vm.clear(); + vm.setCompatibilityMode(false); + vm.setTurboMode(false); + vm.loadProject(project).then(() => { + vm.greenFlag(); + setTimeout(() => { + vm.getPlaygroundData(); + vm.stopAll(); + }, 100); + }); + }); +}); From 5b2667556c9cb512eebaa41853910f54b0cdcd63 Mon Sep 17 00:00:00 2001 From: Karishma Chadha Date: Wed, 14 Nov 2018 13:15:36 -0500 Subject: [PATCH 8/8] Comment cleanup --- src/serialization/sb3.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/serialization/sb3.js b/src/serialization/sb3.js index 66d7e033ab3..864252a775b 100644 --- a/src/serialization/sb3.js +++ b/src/serialization/sb3.js @@ -1084,7 +1084,7 @@ const deserializeMonitor = function (monitorData, runtime, targets, extensions) existingMonitorBlock.isMonitored = monitorData.visible; existingMonitorBlock.targetId = monitorData.targetId; } else { - // If a monitor block doens't already exist for this monitor, + // If a monitor block doesn't already exist for this monitor, // construct a monitor block to add to the monitor blocks container const monitorBlock = { id: monitorData.id, @@ -1122,8 +1122,6 @@ const deserializeMonitor = function (monitorData, runtime, targets, extensions) extensions.extensionIDs.add(extensionID); } } - // Otherwise, the monitor block will get created when the toolbox updates - // after the target has been installed. runtime.requestAddMonitor(MonitorRecord(monitorData)); };