diff --git a/src/extensions/scratch3_video_sensing/index.js b/src/extensions/scratch3_video_sensing/index.js index 86605f8ee..76d5a2c12 100644 --- a/src/extensions/scratch3_video_sensing/index.js +++ b/src/extensions/scratch3_video_sensing/index.js @@ -92,21 +92,22 @@ class Scratch3VideoSensingBlocks { */ this._lastUpdate = null; + /** + * A flag to determine if this extension has been installed in a project. + * It is set to false the first time getInfo is run. + * @type {boolean} + */ + this.firstInstall = true; + if (this.runtime.ioDevices) { + // Configure the video device with values from globally stored locations. + this.runtime.on(Runtime.PROJECT_LOADED, this.updateVideoDisplay.bind(this)); + // Clear target motion state values when the project starts. this.runtime.on(Runtime.PROJECT_RUN_START, this.reset.bind(this)); // Kick off looping the analysis logic. this._loop(); - - // Configure the video device with values from a globally stored - // location. - this.setVideoTransparency({ - TRANSPARENCY: this.globalVideoTransparency - }); - this.videoToggle({ - VIDEO_STATE: this.globalVideoState - }); } } @@ -179,7 +180,10 @@ class Scratch3VideoSensingBlocks { if (stage) { return stage.videoState; } - return VideoState.ON; + // Though the default value for the stage is normally 'on', we need to default + // to 'off' here to prevent the video device from briefly activating + // while waiting for stage targets to be installed that say it should be off + return VideoState.OFF; } set globalVideoState (state) { @@ -190,6 +194,19 @@ class Scratch3VideoSensingBlocks { return state; } + /** + * Get the latest values for video transparency and state, + * and set the video device to use them. + */ + updateVideoDisplay () { + this.setVideoTransparency({ + TRANSPARENCY: this.globalVideoTransparency + }); + this.videoToggle({ + VIDEO_STATE: this.globalVideoState + }); + } + /** * Reset the extension's data motion detection data. This will clear out * for example old frames, so the first analyzed frame will not be compared @@ -380,8 +397,16 @@ class Scratch3VideoSensingBlocks { * @returns {object} metadata for this extension and its blocks. */ getInfo () { - // Enable the video layer - this.runtime.ioDevices.video.enableVideo(); + // Set the video display properties to defaults the first time + // getInfo is run. This turns on the video device when it is + // first added to a project, and is overwritten by a PROJECT_LOADED + // event listener that later calls updateVideoDisplay + if (this.firstInstall) { + this.globalVideoState = VideoState.ON; + this.globalVideoTransparency = 50; + this.updateVideoDisplay(); + this.firstInstall = false; + } // Return extension definition return { diff --git a/src/io/video.js b/src/io/video.js index eac84f4d4..2e99e36cc 100644 --- a/src/io/video.js +++ b/src/io/video.js @@ -132,7 +132,8 @@ class Video { */ setPreviewGhost (ghost) { this._ghost = ghost; - if (this._drawable) { + // Confirm that the default value has been changed to a valid id for the drawable + if (this._drawable !== -1) { this.runtime.renderer.updateDrawableProperties(this._drawable, { ghost: this._forceTransparentPreview ? 100 : ghost }); diff --git a/src/serialization/sb2.js b/src/serialization/sb2.js index 10682ca82..fc1e0afde 100644 --- a/src/serialization/sb2.js +++ b/src/serialization/sb2.js @@ -659,6 +659,8 @@ const parseScratchObject = function (object, runtime, extensions, topLevel, zip) if (object.info.hasOwnProperty('videoOn')) { if (object.info.videoOn) { target.videoState = RenderedTarget.VIDEO_STATE.ON; + } else { + target.videoState = RenderedTarget.VIDEO_STATE.OFF; } } } diff --git a/test/fixtures/load-extensions/README.md b/test/fixtures/load-extensions/README.md index 437acea5b..20c63c25e 100644 --- a/test/fixtures/load-extensions/README.md +++ b/test/fixtures/load-extensions/README.md @@ -1,12 +1,12 @@ -Tests in this folder are run in scratch by integration/load-extensions.js to determine whether an extension can load properly. The test projects in this folder are examples of non-core extensions usage. Read integration/load-extensions.js for more. +Tests in this folder are run in scratch by integration/load-extensions.js to determine whether an extension can load properly. The test projects in this folder are examples of non-core extensions usage. Read `integration/load-extensions.js` for more. ### Adding new extensions -When extending Scratch with non-core extensions, save an example project to this directory with the naming convention: +When extending Scratch with non-core extensions, save an example project to this the appropiate subdirectory based on which test in `load-extensions.js` will be using that test file. The file should use the following naming convention: `[extensionID]-rest-of-file-name.[file type sb3 or sb2]` -The load-extensions.js test will automatically test this new project file since it gets a list of all files in this directory for testing and extracts the extension id from the first section of the file same separated by a dash. +The load-extensions.js test will automatically test this new project file since it gets a list of all files in its repsective subdirectories for testing and extracts the extension id from the first section of the file same separated by a dash. Each of the `[extensionID]-simple-project` test files have been made as the simplest possible cases for loading the extension. This means that only one block has been added to the project and that block is from the relevant extension. @@ -15,7 +15,4 @@ Each of the `[extensionID]-simple-project` test files have been made as the simp Sometimes we need to test more complex projects to catch cases and contexts where an extension should load and doesn't. We can save those project files using the convention [extensionID]-project-name. For example, the Dolphins 3D project (#115870836) had a pen extension that wouldn't load, whereas `pen-simple-project.sb2` and `pen-simple-project.sb3` did pass these tests. For this reason, `pen-dolphin-3d.sb2` and `pen-dolphin-3d.sb3` are now part of the test examples. ### // TO DO -The translation and videoSensing extensions don't have test projects added for them yet since they need a little more infrastructure stubbed out in the test. - - - +The translation extension doesn't have test projects added for them yet since they need a little more infrastructure stubbed out in the test. diff --git a/test/fixtures/load-extensions/ev3-simple-project.sb3 b/test/fixtures/load-extensions/confirm-load/ev3-simple-project.sb3 similarity index 100% rename from test/fixtures/load-extensions/ev3-simple-project.sb3 rename to test/fixtures/load-extensions/confirm-load/ev3-simple-project.sb3 diff --git a/test/fixtures/load-extensions/microbit-simple-project.sb3 b/test/fixtures/load-extensions/confirm-load/microbit-simple-project.sb3 similarity index 100% rename from test/fixtures/load-extensions/microbit-simple-project.sb3 rename to test/fixtures/load-extensions/confirm-load/microbit-simple-project.sb3 diff --git a/test/fixtures/load-extensions/music-simple-project.sb2 b/test/fixtures/load-extensions/confirm-load/music-simple-project.sb2 similarity index 100% rename from test/fixtures/load-extensions/music-simple-project.sb2 rename to test/fixtures/load-extensions/confirm-load/music-simple-project.sb2 diff --git a/test/fixtures/load-extensions/music-simple-project.sb3 b/test/fixtures/load-extensions/confirm-load/music-simple-project.sb3 similarity index 100% rename from test/fixtures/load-extensions/music-simple-project.sb3 rename to test/fixtures/load-extensions/confirm-load/music-simple-project.sb3 diff --git a/test/fixtures/load-extensions/pen-dolphin-3d.sb2 b/test/fixtures/load-extensions/confirm-load/pen-dolphin-3d.sb2 similarity index 100% rename from test/fixtures/load-extensions/pen-dolphin-3d.sb2 rename to test/fixtures/load-extensions/confirm-load/pen-dolphin-3d.sb2 diff --git a/test/fixtures/load-extensions/pen-dolphin-3d.sb3 b/test/fixtures/load-extensions/confirm-load/pen-dolphin-3d.sb3 similarity index 100% rename from test/fixtures/load-extensions/pen-dolphin-3d.sb3 rename to test/fixtures/load-extensions/confirm-load/pen-dolphin-3d.sb3 diff --git a/test/fixtures/load-extensions/pen-simple-project.sb2 b/test/fixtures/load-extensions/confirm-load/pen-simple-project.sb2 similarity index 100% rename from test/fixtures/load-extensions/pen-simple-project.sb2 rename to test/fixtures/load-extensions/confirm-load/pen-simple-project.sb2 diff --git a/test/fixtures/load-extensions/pen-simple-project.sb3 b/test/fixtures/load-extensions/confirm-load/pen-simple-project.sb3 similarity index 100% rename from test/fixtures/load-extensions/pen-simple-project.sb3 rename to test/fixtures/load-extensions/confirm-load/pen-simple-project.sb3 diff --git a/test/fixtures/load-extensions/text2speech-simple-project.sb3 b/test/fixtures/load-extensions/confirm-load/text2speech-simple-project.sb3 similarity index 100% rename from test/fixtures/load-extensions/text2speech-simple-project.sb3 rename to test/fixtures/load-extensions/confirm-load/text2speech-simple-project.sb3 diff --git a/test/fixtures/load-extensions/confirm-load/videoSensing-simple-project.sb2 b/test/fixtures/load-extensions/confirm-load/videoSensing-simple-project.sb2 new file mode 100644 index 000000000..36b5f500e Binary files /dev/null and b/test/fixtures/load-extensions/confirm-load/videoSensing-simple-project.sb2 differ diff --git a/test/fixtures/load-extensions/confirm-load/videoSensing-simple-project.sb3 b/test/fixtures/load-extensions/confirm-load/videoSensing-simple-project.sb3 new file mode 100644 index 000000000..435864e0a Binary files /dev/null and b/test/fixtures/load-extensions/confirm-load/videoSensing-simple-project.sb3 differ diff --git a/test/fixtures/load-extensions/wedo2-simple-project.sb2 b/test/fixtures/load-extensions/confirm-load/wedo2-simple-project.sb2 similarity index 100% rename from test/fixtures/load-extensions/wedo2-simple-project.sb2 rename to test/fixtures/load-extensions/confirm-load/wedo2-simple-project.sb2 diff --git a/test/fixtures/load-extensions/wedo2-simple-project.sb3 b/test/fixtures/load-extensions/confirm-load/wedo2-simple-project.sb3 similarity index 100% rename from test/fixtures/load-extensions/wedo2-simple-project.sb3 rename to test/fixtures/load-extensions/confirm-load/wedo2-simple-project.sb3 diff --git a/test/fixtures/load-extensions/video-state/videoState-off.sb2 b/test/fixtures/load-extensions/video-state/videoState-off.sb2 new file mode 100644 index 000000000..2d9af41d2 Binary files /dev/null and b/test/fixtures/load-extensions/video-state/videoState-off.sb2 differ diff --git a/test/fixtures/load-extensions/video-state/videoState-on-transparency-0.sb2 b/test/fixtures/load-extensions/video-state/videoState-on-transparency-0.sb2 new file mode 100644 index 000000000..f9c308dd9 Binary files /dev/null and b/test/fixtures/load-extensions/video-state/videoState-on-transparency-0.sb2 differ diff --git a/test/integration/load-extensions.js b/test/integration/load-extensions.js index fb9b42d7f..bdeafb001 100644 --- a/test/integration/load-extensions.js +++ b/test/integration/load-extensions.js @@ -1,21 +1,23 @@ const path = require('path'); -const test = require('tap').test; +const tap = require('tap'); +const {test} = tap; const fs = require('fs'); const readFileToBuffer = require('../fixtures/readProjectFile').readFileToBuffer; const VirtualMachine = require('../../src/index'); +tap.tearDown(() => process.nextTick(process.exit)); + test('Load external extensions', async t => { const vm = new VirtualMachine(); - const fileList = fs.readdirSync('./test/fixtures/load-extensions/'); - const testFiles = fileList.filter(file => path.extname(file) === '.sb2' || path.extname(file) === '.sb3'); + const testFiles = fs.readdirSync('./test/fixtures/load-extensions/confirm-load/'); // Test each example extension file for (const file of testFiles) { const ext = file.split('-')[0]; - const uri = path.resolve(__dirname, `../fixtures/load-extensions/${file}`); + const uri = path.resolve(__dirname, `../fixtures/load-extensions/confirm-load/${file}`); const project = readFileToBuffer(uri); - await t.test('Confirm expected extension is installed in example sb2 projects', extTest => { + await t.test('Confirm expected extension is installed in example sb2 and sb3 projects', extTest => { vm.loadProject(project) .then(() => { extTest.ok(vm.extensionManager.isExtensionLoaded(ext)); @@ -25,3 +27,42 @@ test('Load external extensions', async t => { } t.end(); }); + +test('Load video sensing extension and video properties', async t => { + const vm = new VirtualMachine(); + // An array of test projects and their expected video state values + const testProjects = [ + { + file: 'videoState-off.sb2', + videoState: 'off', + videoTransparency: 50, + mirror: undefined + }, + { + file: 'videoState-on-transparency-0.sb2', + videoState: 'on', + videoTransparency: 0, + mirror: true + }]; + + for (const project of testProjects) { + const uri = path.resolve(__dirname, `../fixtures/load-extensions/video-state/${project.file}`); + const projectData = readFileToBuffer(uri); + + await vm.loadProject(projectData); + + const stage = vm.runtime.getTargetForStage(); + + t.ok(vm.extensionManager.isExtensionLoaded('videoSensing')); + + // Check that the stage target has the video state values we expect + // based on the test project files, then check that the video io device + // has the expected state as well + t.equal(stage.videoState, project.videoState); + t.equal(vm.runtime.ioDevices.video.mirror, project.mirror); + t.equal(stage.videoTransparency, project.videoTransparency); + t.equal(vm.runtime.ioDevices.video._ghost, project.videoTransparency); + } + + t.end(); +});