From 55352e88b102f71309991e935444ee792e3952ee Mon Sep 17 00:00:00 2001 From: Karishma Chadha Date: Thu, 3 May 2018 16:19:29 -0400 Subject: [PATCH] Tests and cleanup of addSprite3 so that .sprite3 format does not need to be nested inside an extra {targets: ...} --- src/serialization/sb3.js | 6 +- src/virtual-machine.js | 21 +++---- test/fixtures/example_sprite.sprite2 | Bin 0 -> 3895 bytes test/integration/addSprite.js | 79 +++++++++++++++++++++++++++ test/integration/complex.js | 2 +- test/unit/spec.js | 2 +- test/unit/virtual-machine.js | 9 +++ 7 files changed, 105 insertions(+), 14 deletions(-) create mode 100644 test/fixtures/example_sprite.sprite2 create mode 100644 test/integration/addSprite.js diff --git a/src/serialization/sb3.js b/src/serialization/sb3.js index 0bcaf1f30..6f292399b 100644 --- a/src/serialization/sb3.js +++ b/src/serialization/sb3.js @@ -871,15 +871,17 @@ const parseScratchObject = function (object, runtime, extensions, zip) { * @param {object} json - JSON representation of a VM runtime. * @param {Runtime} runtime - Runtime instance * @param {JSZip} zip - Sb3 file describing this project (to load assets from) + * @param {boolean} isSingleSprite - If true treat as single sprite, else treat as whole project * @returns {Promise.} Promise that resolves to the list of targets after the project is deserialized */ -const deserialize = function (json, runtime, zip) { +const deserialize = function (json, runtime, zip, isSingleSprite) { const extensions = { extensionIDs: new Set(), extensionURLs: new Map() }; return Promise.all( - (json.targets || []).map(target => parseScratchObject(target, runtime, extensions, zip)) + ((isSingleSprite ? [json] : json.targets) || []).map(target => + parseScratchObject(target, runtime, extensions, zip)) ).then(targets => ({ targets, extensions diff --git a/src/virtual-machine.js b/src/virtual-machine.js index a078b98a8..43918199b 100644 --- a/src/virtual-machine.js +++ b/src/virtual-machine.js @@ -385,6 +385,7 @@ class VirtualMachine extends EventEmitter { * @return {!Promise} Promise that resolves after targets are installed. */ addSprite (input) { + const errorPrefix = 'Sprite Upload Error:'; if (typeof input === 'object' && !(input instanceof ArrayBuffer) && !ArrayBuffer.isView(input)) { // If the input is an object and not any ArrayBuffer @@ -411,47 +412,47 @@ class VirtualMachine extends EventEmitter { .then(validatedInput => { const projectVersion = validatedInput[0].projectVersion; if (projectVersion === 2) { - return this.addSprite2(validatedInput[0], validatedInput[1]); + return this._addSprite2(validatedInput[0], validatedInput[1]); } if (projectVersion === 3) { - return this.addSprite3(validatedInput[0], validatedInput[1]); + return this._addSprite3(validatedInput[0], validatedInput[1]); } - return Promise.reject('Unable to verify sprite version.'); + return Promise.reject(`${errorPrefix} Unable to verify sprite version.`); }) .catch(error => { // Intentionally rejecting here (want errors to be handled by caller) if (error.hasOwnProperty('validationError')) { return Promise.reject(JSON.stringify(error)); } - return Promise.reject(error); + return Promise.reject(`${errorPrefix} ${error}`); }); } /** * Add a single sprite from the "Sprite2" (i.e., SB2 sprite) format. - * @param {string} json JSON string representing the sprite. + * @param {object} sprite Object representing 2.0 sprite to be added. * @param {?ArrayBuffer} zip Optional zip of assets being referenced by json * @returns {Promise} Promise that resolves after the sprite is added */ - addSprite2 (json, zip) { + _addSprite2 (sprite, zip) { // Validate & parse - return sb2.deserialize(json, this.runtime, true, zip) + return sb2.deserialize(sprite, this.runtime, true, zip) .then(({targets, extensions}) => this.installTargets(targets, extensions, false)); } /** * Add a single sb3 sprite. - * @param {string} target JSON string representing the sprite/target. + * @param {object} sprite Object rperesenting 3.0 sprite to be added. * @param {?ArrayBuffer} zip Optional zip of assets being referenced by target json * @returns {Promise} Promise that resolves after the sprite is added */ - addSprite3 (target, zip) { + _addSprite3 (sprite, zip) { // Validate & parse return sb3 - .deserialize(target, this.runtime, zip) + .deserialize(sprite, this.runtime, zip, true) .then(({targets, extensions}) => this.installTargets(targets, extensions, false)); } diff --git a/test/fixtures/example_sprite.sprite2 b/test/fixtures/example_sprite.sprite2 new file mode 100644 index 0000000000000000000000000000000000000000..5336124e67a608ed658847a08d34826c5fc9fdbf GIT binary patch literal 3895 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 { + t.type(vm.addSprite, 'function'); + t.end(); +}); + +test('default cat', t => { + // Get default cat from .sprite2 + const uri = path.resolve(__dirname, '../fixtures/example_sprite.sprite2'); + const sprite = readFileToBuffer(uri); + + vm.attachStorage(makeTestStorage()); + + // Evaluate playground data and exit + vm.on('playgroundData', e => { + const threads = JSON.parse(e.threads); + t.ok(threads.length === 0); + t.end(); + process.nextTick(process.exit); + }); + + vm.start(); + vm.clear(); + vm.setCompatibilityMode(false); + vm.setTurboMode(false); + t.doesNotThrow(() => { + vm.loadProject(project).then(() => { + + t.equal(vm.runtime.targets.length, 2); // stage and default sprite + + // Add another sprite + vm.addSprite(sprite).then(() => { + const targets = vm.runtime.targets; + + // Test + t.type(targets, 'object'); + t.equal(targets.length, 3); + + const newTarget = targets[2]; + + t.ok(newTarget instanceof RenderedTarget); + t.type(newTarget.id, 'string'); + t.type(newTarget.blocks, 'object'); + t.type(newTarget.variables, 'object'); + const varIds = Object.keys(newTarget.variables); + t.type(varIds.length, 1); + const variable = newTarget.variables[varIds[0]]; + t.equal(variable.name, 'foo'); + t.equal(variable.value, 0); + + t.equal(newTarget.isOriginal, true); + t.equal(newTarget.currentCostume, 0); + t.equal(newTarget.isOriginal, true); + t.equal(newTarget.isStage, false); + t.equal(newTarget.sprite.name, 'Apple'); + + vm.greenFlag(); + + setTimeout(() => { + t.equal(variable.value, 10); + vm.getPlaygroundData(); + vm.stopAll(); + }, 1000); + }); + }); + }); +}); diff --git a/test/integration/complex.js b/test/integration/complex.js index 4ae5de48d..bce19ca67 100644 --- a/test/integration/complex.js +++ b/test/integration/complex.js @@ -73,7 +73,7 @@ test('complex', t => { }); // Add sprite - vm.addSprite2(sprite); + vm.addSprite(sprite); // Add backdrop vm.addBackdrop( diff --git a/test/unit/spec.js b/test/unit/spec.js index 48b0fd6ae..88bad1587 100644 --- a/test/unit/spec.js +++ b/test/unit/spec.js @@ -15,7 +15,7 @@ test('interface', t => { t.type(vm.postIOData, 'function'); t.type(vm.loadProject, 'function'); - t.type(vm.addSprite2, 'function'); + t.type(vm.addSprite, 'function'); t.type(vm.addCostume, 'function'); t.type(vm.addBackdrop, 'function'); t.type(vm.addSound, 'function'); diff --git a/test/unit/virtual-machine.js b/test/unit/virtual-machine.js index c8cb65967..f6731b194 100644 --- a/test/unit/virtual-machine.js +++ b/test/unit/virtual-machine.js @@ -2,6 +2,15 @@ const test = require('tap').test; const VirtualMachine = require('../../src/virtual-machine.js'); const Sprite = require('../../src/sprites/sprite.js'); +test('addSprite throws on invalid string', t => { + const vm = new VirtualMachine(); + vm.addSprite('this is not a sprite') + .catch(e => { + t.equal(e.startsWith('Sprite Upload Error:'), true); + t.end(); + }); +}); + test('renameSprite throws when there is no sprite with that id', t => { const vm = new VirtualMachine(); vm.runtime.getTargetById = () => null;