From 051e1e477887b56f4471cc07d450442d78a39b5b Mon Sep 17 00:00:00 2001 From: Brian Pilati <brian.pilati@domo.com> Date: Wed, 19 Feb 2014 20:11:08 -0700 Subject: [PATCH 01/10] bringing the Scratch html5 project under test --- .gitignore | 7 ++ README.md | 46 ++++++++++++ config/karma.conf.js | 37 +++++++++ index.html | 51 +++++++------ js/Scratch.js | 4 +- package.json | 13 ++++ scripts/test.sh | 9 +++ test/artifacts/io-artifact.js | 9 +++ test/artifacts/scratch-artifact.js | 117 +++++++++++++++++++++++++++++ test/lib/.git-keep | 0 test/unit/ioSpec.js | 47 ++++++++++++ test/unit/scratchSpec.js | 25 ++++++ 12 files changed, 340 insertions(+), 25 deletions(-) create mode 100644 .gitignore create mode 100644 config/karma.conf.js create mode 100644 package.json create mode 100755 scripts/test.sh create mode 100644 test/artifacts/io-artifact.js create mode 100644 test/artifacts/scratch-artifact.js create mode 100644 test/lib/.git-keep create mode 100644 test/unit/ioSpec.js create mode 100644 test/unit/scratchSpec.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ca4d940 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +*.swp +test/lib/* + +logs + +npm-debug.log +node_modules diff --git a/README.md b/README.md index 5777743..909bf5c 100644 --- a/README.md +++ b/README.md @@ -19,3 +19,49 @@ Running the HTML5 player on your own website, or locally, you will need to have PHP so that the `proxy.php` file can be used to load assets from the same domain. This is done to be compatible with Javascript security models in today's browsers. To test the HTML5 player against the Flash player you can use the compare.html web page. See the file `TESTING.md` for more details. + + +Unit Tests +---------- +The tests are written using Karma and there should be a 100% passing rate in order to commit any code to the project. + +The expectation is to add a unit test for any code that you contribute to the project. + + +Install Node (NPM) (https://npmjs.org/) +--------------------------------------- + +Brew: +``` +$ brew install npm +``` + +Mac Ports: +``` +$ port install npm +``` + +``` +$ npm install +``` + +Local copy of jQuery and mock-ajax +---------------------------------- + +``` +$ cd test/lib +$ curl http://code.jquery.com/jquery-1.11.0.min.js > jquery-1.11.0.min.js +$ curl http://cloud.github.com/downloads/pivotal/jasmine-ajax/mock-ajax.js > mock-ajax.js +``` + +To Run the tests +---------------- + +``` +$ ./scripts/test.sh +``` + + +To configure the unit tests +--------------------------- +The karam.conf.js file is location in the config directory diff --git a/config/karma.conf.js b/config/karma.conf.js new file mode 100644 index 0000000..312a8a6 --- /dev/null +++ b/config/karma.conf.js @@ -0,0 +1,37 @@ +module.exports = function(config){ + config.set({ + basePath : '../', + + files : [ + 'test/artifacts/**/*.js', + 'test/lib/mock-ajax.js', + 'test/unit/**/*.js', + 'test/lib/jquery-1.11.0.min.js', + 'js/sound/SoundDecoder.js', + 'js/sound/**/*.js', + 'js/util/**/*.js', + 'js/**/*.js' + ], + + exclude : [ + ], + + autoWatch : true, + + frameworks: ['jasmine'], + + browsers : ['Chrome'], + + plugins : [ + 'karma-junit-reporter', + 'karma-chrome-launcher', + 'karma-firefox-launcher', + 'karma-jasmine' + ], + + junitReporter : { + outputFile: 'test_out/unit.xml', + suite: 'unit' + } + +})} diff --git a/index.html b/index.html index 8f25615..71f68b2 100755 --- a/index.html +++ b/index.html @@ -12,31 +12,12 @@ <script src=//ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js></script> -<script src=js/util/Timer.js></script> -<script src=js/util/OffsetBuffer.js></script> -<script src=js/util/Color.js></script> -<script src=js/util/Rectangle.js></script> -<script src=js/Sprite.js></script> -<script src=js/Reporter.js></script> -<script src=js/Stage.js></script> -<script src=js/sound/WAVFile.js></script> -<script src=js/sound/SoundDecoder.js></script> -<script src=js/sound/SoundBank.js></script> -<script src=js/sound/NotePlayer.js></script> -<script src=soundbank/Instr.js></script> -<script src=js/IO.js></script> -<script src=js/primitives/VarListPrims.js></script> -<script src=js/primitives/MotionAndPenPrims.js></script> -<script src=js/primitives/LooksPrims.js></script> -<script src=js/primitives/SensingPrims.js></script> -<script src=js/primitives/SoundPrims.js></script> -<script src=js/primitives/Primitives.js></script> -<script src=js/Interpreter.js></script> -<script src=js/Runtime.js></script> -<script src=js/Scratch.js></script> -<script> +<script type="text/javascript"> + $(document).ready(function() { var project_id = location.hash && parseInt(location.hash.substr(1)) || 10000160; + var scratch = new Scratch(project_id); + }); </script> <div id=player-container> @@ -68,3 +49,27 @@ <h1>Scratch HTML5 player</h1> <p>The Scratch HTML5 player is still in development. Feedback is welcome! Please report any bugs (or differences from the Flash player) <a href=https://github.com/LLK/scratch-html5>on Github</a>.</p> + +<script src=js/util/Timer.js></script> +<script src=js/util/OffsetBuffer.js></script> +<script src=js/util/Color.js></script> +<script src=js/util/Rectangle.js></script> +<script src=js/Sprite.js></script> +<script src=js/Reporter.js></script> +<script src=js/Stage.js></script> +<script src=js/sound/WAVFile.js></script> +<script src=js/sound/SoundDecoder.js></script> +<script src=js/sound/SoundBank.js></script> +<script src=js/sound/NotePlayer.js></script> +<script src=soundbank/Instr.js></script> +<script src=js/IO.js></script> +<script src=js/primitives/VarListPrims.js></script> +<script src=js/primitives/MotionAndPenPrims.js></script> +<script src=js/primitives/LooksPrims.js></script> +<script src=js/primitives/SensingPrims.js></script> +<script src=js/primitives/SoundPrims.js></script> +<script src=js/primitives/Primitives.js></script> +<script src=js/Interpreter.js></script> +<script src=js/Runtime.js></script> +<!-- <script src=js/Scratch.js></script> --> +<script type="text/JavaScript" src="js/Scratch.js"></script> diff --git a/js/Scratch.js b/js/Scratch.js index dc817f6..653796f 100644 --- a/js/Scratch.js +++ b/js/Scratch.js @@ -23,7 +23,7 @@ 'use strict'; var runtime, interp, io, iosAudioActive = false; -$(function() { +function Scratch(project_id) { runtime = new Runtime(); runtime.init(); @@ -151,4 +151,4 @@ $(function() { // Load the requested project and go! io = new IO(); io.loadProject(project_id); -}); +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..732b5b2 --- /dev/null +++ b/package.json @@ -0,0 +1,13 @@ +{ + "name": "scratch-html5", + "description": "HTML 5 based Scratch project player", + "repository": "https://github.com/LLK/scratch-html5", + "devDependencies": { + "phantomjs" : "~1.9", + "karma" : "~0.10", + "karma-junit-reporter" : "~0.1", + "karma-jasmine" : "~0.1", + "karma-ng-scenario" : "~0.1", + "jquery" : "~1.11.0" + } +} diff --git a/scripts/test.sh b/scripts/test.sh new file mode 100755 index 0000000..972001f --- /dev/null +++ b/scripts/test.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +BASE_DIR=`dirname $0` + +echo "" +echo "Starting Karma Server (http://karma-runner.github.io)" +echo "-------------------------------------------------------------------" + +$BASE_DIR/../node_modules/karma/bin/karma start $BASE_DIR/../config/karma.conf.js $* diff --git a/test/artifacts/io-artifact.js b/test/artifacts/io-artifact.js new file mode 100644 index 0000000..53ac670 --- /dev/null +++ b/test/artifacts/io-artifact.js @@ -0,0 +1,9 @@ +'use strict'; + +var io_base = 'proxy.php?resource=internalapi/'; +var project_base = io_base + 'project/'; +var project_suffix = '/get/'; +var asset_base = io_base + 'asset/'; +var asset_suffix = '/get/'; +var soundbank_base = 'soundbank/'; +var spriteLayerCount = 0; diff --git a/test/artifacts/scratch-artifact.js b/test/artifacts/scratch-artifact.js new file mode 100644 index 0000000..60e17a1 --- /dev/null +++ b/test/artifacts/scratch-artifact.js @@ -0,0 +1,117 @@ +'use strict'; + +var project_id = 123456789; + +var returnData = { + "objName": "Stage", + "sounds": [{ + "soundName": "pop", + "soundID": -1, + "md5": "83a9787d4cb6f3b7632b4ddfebf74367.wav", + "sampleCount": 258, + "rate": 11025, + "format": "" + }], + "costumes": [{ + "costumeName": "Scene 1", + "baseLayerID": -1, + "baseLayerMD5": "510da64cf172d53750dffd23fbf73563.png", + "rotationCenterX": 240, + "rotationCenterY": 180, + "spritesHiddenInScene": null + }], + "currentCostumeIndex": 0, + "penLayerMD5": "279467d0d49e152706ed66539b577c00.png", + "tempoBPM": 60, + "children": [{ + "objName": "Sprite1", + "scripts": [[283, + 151, + [["whenClicked"], + ["clearPenTrails"], + ["penColor:", 10485886], + ["putPenDown"], + ["doForever", + [["gotoX:y:", ["randomFrom:to:", -240, 240], ["randomFrom:to:", -180, 180]], ["changePenShadeBy:", 10]]]]]], + "sounds": [{ + "soundName": "pop", + "soundID": -1, + "md5": "83a9787d4cb6f3b7632b4ddfebf74367.wav", + "sampleCount": 258, + "rate": 11025, + "format": "" + }], + "costumes": [{ + "costumeName": "Costume1", + "baseLayerID": -1, + "baseLayerMD5": "cce61b6e9ad98ea8c8c2e9556a94b7ab.png", + "rotationCenterX": 47, + "rotationCenterY": 55, + "spritesHiddenInScene": null + }, + { + "costumeName": "Costume2", + "baseLayerID": -1, + "baseLayerMD5": "51f6fa1871f17de1a21cdfead7aad574.png", + "rotationCenterX": 47, + "rotationCenterY": 55, + "spritesHiddenInScene": null + }], + "currentCostumeIndex": 0, + "scratchX": 120, + "scratchY": -101, + "scale": 1, + "direction": 90, + "rotationStyle": "normal", + "isDraggable": false, + "indexInLibrary": 0, + "visible": true + }, + { + "objName": "fish31", + "scripts": [[181, 138, [["whenClicked"], ["nextCostume"]]]], + "sounds": [{ + "soundName": "pop", + "soundID": -1, + "md5": "83a9787d4cb6f3b7632b4ddfebf74367.wav", + "sampleCount": 258, + "rate": 11025, + "format": "" + }], + "costumes": [{ + "costumeName": "fish3", + "baseLayerID": -1, + "baseLayerMD5": "5ab571cf8c6e6bcf0ee2443b5df17dcb.png", + "rotationCenterX": 90, + "rotationCenterY": 79, + "spritesHiddenInScene": null + }, + { + "costumeName": "crab1-a", + "baseLayerID": -1, + "baseLayerMD5": "110bf75ed212eb072acec2fa2c39456d.png", + "rotationCenterX": 92, + "rotationCenterY": 62, + "spritesHiddenInScene": null + }, + { + "costumeName": "ballerina-a", + "baseLayerID": -1, + "baseLayerMD5": "4c789664cc6f69d1ef4678ac8b4cb812.png", + "rotationCenterX": 51, + "rotationCenterY": 84, + "spritesHiddenInScene": null + }], + "currentCostumeIndex": 2, + "scratchX": 108, + "scratchY": -28, + "scale": 1, + "direction": 90, + "rotationStyle": "normal", + "isDraggable": false, + "indexInLibrary": 0, + "visible": true + }], + "info": { + } +}; diff --git a/test/lib/.git-keep b/test/lib/.git-keep new file mode 100644 index 0000000..e69de29 diff --git a/test/unit/ioSpec.js b/test/unit/ioSpec.js new file mode 100644 index 0000000..f986bc8 --- /dev/null +++ b/test/unit/ioSpec.js @@ -0,0 +1,47 @@ +'use strict'; + +/* jasmine specs for IO.js go here */ + +describe('IO', function(){ + var io; + + beforeEach(function() { + io = new IO(); + }); + + it('should have "null" data', function() { + expect(io.data).toBe(null); + }); + + it('should have "null" data', function() { + expect(io.data).toBe(null); + }); + + it('should have a base', function() { + expect(io.base).toBe(io_base); + }); + + it('should have a project_base', function() { + expect(io.project_base).toBe(project_base); + }); + + it('should have a project_suffix', function() { + expect(io.project_suffix).toBe(project_suffix); + }); + + it('should have an asset_base', function() { + expect(io.asset_base).toBe(asset_base); + }); + + it('should have an asset_suffix', function() { + expect(io.asset_suffix).toBe(asset_suffix); + }); + + it('should have an soundbank_base', function() { + expect(io.soundbank_base).toBe(soundbank_base); + }); + + it('should have a spriteLayerCount', function() { + expect(io.spriteLayerCount).toBe(spriteLayerCount); + }); +}); diff --git a/test/unit/scratchSpec.js b/test/unit/scratchSpec.js new file mode 100644 index 0000000..29db9cc --- /dev/null +++ b/test/unit/scratchSpec.js @@ -0,0 +1,25 @@ +'use strict'; + +/* jasmine specs for Scratch.js go here */ + +describe('Scratch', function(){ + var getScript, request, scratch; + var uri = "http://getScript.example.com"; + var callBack = jasmine.createSpy('onSuccess'); + var testResponseText = 'This is a script'; + + var TestResponses = { status: 200, responseText: returnData}; + + beforeEach(function() { + jasmine.Ajax.useMock(); + scratch = Scratch; + scratch(project_id); + request = mostRecentAjaxRequest(); + request.promise(TestResponses, callBack); + }); + + it('should call the internalapi project', function() { + expect(request.url).toBe("proxy.php?resource=internalapi/project/" + project_id + "/get/"); + expect(callBack).toHaveBeenCalled(); + }); +}); From 66998640977bede1a48a8876e639e5648ed4a02e Mon Sep 17 00:00:00 2001 From: Brian Pilati <brian.pilati@domo.com> Date: Wed, 19 Feb 2014 20:33:55 -0700 Subject: [PATCH 02/10] oops didn't think about updating the compare.html file --- compare.html | 51 ++++++++++++++++++++++++++++----------------------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/compare.html b/compare.html index 61ef77c..b538775 100644 --- a/compare.html +++ b/compare.html @@ -8,35 +8,17 @@ <script src=//ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js></script> <script src=//ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js></script> -<script src=js/util/Timer.js></script> -<script src=js/util/OffsetBuffer.js></script> -<script src=js/util/Color.js></script> -<script src=js/util/Rectangle.js></script> -<script src=js/Sprite.js></script> -<script src=js/Reporter.js></script> -<script src=js/Stage.js></script> -<script src=js/sound/WAVFile.js></script> -<script src=js/sound/SoundDecoder.js></script> -<script src=js/sound/SoundBank.js></script> -<script src=js/sound/NotePlayer.js></script> -<script src=soundbank/Instr.js></script> -<script src=js/IO.js></script> -<script src=js/primitives/VarListPrims.js></script> -<script src=js/primitives/MotionAndPenPrims.js></script> -<script src=js/primitives/LooksPrims.js></script> -<script src=js/primitives/SensingPrims.js></script> -<script src=js/primitives/SoundPrims.js></script> -<script src=js/primitives/Primitives.js></script> -<script src=js/Interpreter.js></script> -<script src=js/Runtime.js></script> -<script src=js/Scratch.js></script> -<script> +<script type="text/javascript"> + $(document).ready(function() { var project_id = location.hash && parseInt(location.hash.substr(1)) || 10000160; + var scratch = new Scratch(project_id); + }); </script> <script> var flashLog = null; + var project_id = location.hash && parseInt(location.hash.substr(1)) || 10000160; $(function() { // The flashvars tell flash about the project data (and autostart=true) var flashvars = { @@ -133,3 +115,26 @@ </div> <textarea id=flash-debug readonly></textarea> </div> + +<script src=js/util/Timer.js></script> +<script src=js/util/OffsetBuffer.js></script> +<script src=js/util/Color.js></script> +<script src=js/util/Rectangle.js></script> +<script src=js/Sprite.js></script> +<script src=js/Reporter.js></script> +<script src=js/Stage.js></script> +<script src=js/sound/WAVFile.js></script> +<script src=js/sound/SoundDecoder.js></script> +<script src=js/sound/SoundBank.js></script> +<script src=js/sound/NotePlayer.js></script> +<script src=soundbank/Instr.js></script> +<script src=js/IO.js></script> +<script src=js/primitives/VarListPrims.js></script> +<script src=js/primitives/MotionAndPenPrims.js></script> +<script src=js/primitives/LooksPrims.js></script> +<script src=js/primitives/SensingPrims.js></script> +<script src=js/primitives/SoundPrims.js></script> +<script src=js/primitives/Primitives.js></script> +<script src=js/Interpreter.js></script> +<script src=js/Runtime.js></script> +<script src=js/Scratch.js></script> From 23cc21b7d99ab0015999122e2e84be9d3358789c Mon Sep 17 00:00:00 2001 From: Brian Pilati <brian.pilati@domo.com> Date: Tue, 4 Mar 2014 21:48:22 -0700 Subject: [PATCH 03/10] updates to bring more of the Scratch project under test ... still baby-steps --- config/karma.conf.js | 25 +++++++------ package.json | 9 ++--- test/unit/scratchSpec.js | 76 ++++++++++++++++++++++++++++++---------- 3 files changed, 72 insertions(+), 38 deletions(-) diff --git a/config/karma.conf.js b/config/karma.conf.js index 312a8a6..9fa12c0 100644 --- a/config/karma.conf.js +++ b/config/karma.conf.js @@ -4,18 +4,22 @@ module.exports = function(config){ files : [ 'test/artifacts/**/*.js', - 'test/lib/mock-ajax.js', + 'test/lib/**/*.js', 'test/unit/**/*.js', - 'test/lib/jquery-1.11.0.min.js', 'js/sound/SoundDecoder.js', 'js/sound/**/*.js', 'js/util/**/*.js', - 'js/**/*.js' + 'js/**/*.js', + 'node_modules/jasmine-jquery/lib/jasmine-jquery.js' ], exclude : [ ], + preprocessors: { + '*.html': ['html2js'] + }, + autoWatch : true, frameworks: ['jasmine'], @@ -23,15 +27,10 @@ module.exports = function(config){ browsers : ['Chrome'], plugins : [ - 'karma-junit-reporter', + 'karma-jasmine', + 'jasmine-jquery', + 'karma-html2js-preprocessor', 'karma-chrome-launcher', - 'karma-firefox-launcher', - 'karma-jasmine' - ], - - junitReporter : { - outputFile: 'test_out/unit.xml', - suite: 'unit' - } - + 'karma-firefox-launcher' + ] })} diff --git a/package.json b/package.json index 732b5b2..e746327 100644 --- a/package.json +++ b/package.json @@ -3,11 +3,8 @@ "description": "HTML 5 based Scratch project player", "repository": "https://github.com/LLK/scratch-html5", "devDependencies": { - "phantomjs" : "~1.9", - "karma" : "~0.10", - "karma-junit-reporter" : "~0.1", - "karma-jasmine" : "~0.1", - "karma-ng-scenario" : "~0.1", - "jquery" : "~1.11.0" + "karma" : "~0.10", + "jasmine-jquery" : "1.3.3", + "karma-html2js-preprocessor" : "~0.1.0" } } diff --git a/test/unit/scratchSpec.js b/test/unit/scratchSpec.js index 29db9cc..0f35ca2 100644 --- a/test/unit/scratchSpec.js +++ b/test/unit/scratchSpec.js @@ -1,25 +1,63 @@ -'use strict'; - /* jasmine specs for Scratch.js go here */ -describe('Scratch', function(){ - var getScript, request, scratch; - var uri = "http://getScript.example.com"; - var callBack = jasmine.createSpy('onSuccess'); - var testResponseText = 'This is a script'; +describe ('Scratch', function() { + describe('Scratch - Load Project', function(){ + var getScript, request, scratch; + var uri = "http://getScript.example.com"; + var callBack = jasmine.createSpy('onSuccess'); + var testResponseText = 'This is a script'; - var TestResponses = { status: 200, responseText: returnData}; + var TestResponses = { status: 200, responseText: returnData}; - beforeEach(function() { - jasmine.Ajax.useMock(); - scratch = Scratch; - scratch(project_id); - request = mostRecentAjaxRequest(); - request.promise(TestResponses, callBack); - }); + beforeEach(function() { + jasmine.Ajax.useMock(); + scratch = Scratch; + scratch(project_id); + request = mostRecentAjaxRequest(); + request.promise(TestResponses, callBack); + }); - it('should call the internalapi project', function() { - expect(request.url).toBe("proxy.php?resource=internalapi/project/" + project_id + "/get/"); - expect(callBack).toHaveBeenCalled(); - }); + it('should call the internalapi project', function() { + expect(request.url).toBe("proxy.php?resource=internalapi/project/" + project_id + "/get/"); + expect(callBack).toHaveBeenCalled(); + }); + }); + + describe('Scratch - Click Green Flag', function(){ + beforeEach(function() { + setFixtures('<button id=trigger-green-flag tabindex=2></button><div id="overlay"></div>'); + scratch = Scratch; + scratch(project_id); + }); + + it('should not click on the green flag if the project is loading', function() { + runtime.projectLoaded = false; + spyOn(runtime, 'greenFlag'); + $('#trigger-green-flag').click(); + expect(runtime.greenFlag).not.toHaveBeenCalled(); + expect($('#overlay').css('display')).toBe('block'); + }); + + it('should click on the green flag if the project is loaded', function() { + runtime.projectLoaded = true; + spyOn(runtime, 'greenFlag'); + $('#trigger-green-flag').click(); + expect(runtime.greenFlag).toHaveBeenCalled(); + expect($('#overlay').css('display')).toBe('none'); + }); + }); + + describe('Scratch - Click Stop', function(){ + beforeEach(function() { + setFixtures('<button id=trigger-stop tabindex=3></button>'); + scratch = Scratch; + scratch(project_id); + }); + + it('should not click on the green flag if the project is loading', function() { + spyOn(runtime, 'stopAll'); + $('#trigger-stop').click(); + expect(runtime.stopAll).toHaveBeenCalled(); + }); + }); }); From 3842832e52c5e16b33b6542b95f3d9cd4ad19bda Mon Sep 17 00:00:00 2001 From: Brian Pilati <brian.pilati@domo.com> Date: Fri, 7 Mar 2014 10:47:50 -0700 Subject: [PATCH 04/10] additional tests in preparation to adding the "ask" functionality --- config/karma.conf.js | 15 +++--- package.json | 3 +- test/artifacts/InterpreterMock.js | 26 ++++++++++ test/artifacts/TargetMock.js | 8 +++ test/artifacts/ask-artifact.js | 55 +++++++++++++++++++++ test/unit/looksPrimitiveSpec.js | 23 +++++++++ test/unit/scratchSpec.js | 4 +- test/unit/sensingPrimitiveSpec.js | 81 +++++++++++++++++++++++++++++++ 8 files changed, 204 insertions(+), 11 deletions(-) create mode 100644 test/artifacts/InterpreterMock.js create mode 100644 test/artifacts/TargetMock.js create mode 100644 test/artifacts/ask-artifact.js create mode 100644 test/unit/looksPrimitiveSpec.js create mode 100644 test/unit/sensingPrimitiveSpec.js diff --git a/config/karma.conf.js b/config/karma.conf.js index 9fa12c0..5c66717 100644 --- a/config/karma.conf.js +++ b/config/karma.conf.js @@ -10,7 +10,8 @@ module.exports = function(config){ 'js/sound/**/*.js', 'js/util/**/*.js', 'js/**/*.js', - 'node_modules/jasmine-jquery/lib/jasmine-jquery.js' + 'node_modules/jasmine-jquery/lib/jasmine-jquery.js', + 'node_modules/underscore/underscore.js' ], exclude : [ @@ -27,10 +28,10 @@ module.exports = function(config){ browsers : ['Chrome'], plugins : [ - 'karma-jasmine', - 'jasmine-jquery', - 'karma-html2js-preprocessor', - 'karma-chrome-launcher', - 'karma-firefox-launcher' - ] + 'karma-jasmine', + 'jasmine-jquery', + 'karma-html2js-preprocessor', + 'karma-chrome-launcher', + 'karma-firefox-launcher' + ] })} diff --git a/package.json b/package.json index e746327..7a6ba49 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "devDependencies": { "karma" : "~0.10", "jasmine-jquery" : "1.3.3", - "karma-html2js-preprocessor" : "~0.1.0" + "karma-html2js-preprocessor" : "~0.1.0", + "underscore" : "~1.6.0" } } diff --git a/test/artifacts/InterpreterMock.js b/test/artifacts/InterpreterMock.js new file mode 100644 index 0000000..7144e37 --- /dev/null +++ b/test/artifacts/InterpreterMock.js @@ -0,0 +1,26 @@ +'use strict'; + +var interpreterMock = function() { + var args = createArgs(arguments); + + function getArgs(argKey) { + return ((argKey in args) ? args[argKey] : null); + } + + function createArgs(methodArgs) { + var args = {}; + if (methodArgs.length) { + _.each(methodArgs, function(newObject) { + _.each(newObject, function(value, key) { + args[key] = value; + }); + }); + } + return args; + } + + return { + 'targetSprite' : function() { return getArgs('targetSprite'); }, + 'arg': function(block, index) { return getArgs('arg');} + } +}; diff --git a/test/artifacts/TargetMock.js b/test/artifacts/TargetMock.js new file mode 100644 index 0000000..9121a00 --- /dev/null +++ b/test/artifacts/TargetMock.js @@ -0,0 +1,8 @@ +'use strict'; + +var targetMock = function() { + return { + 'showBubble' : function() {} + }; + +} diff --git a/test/artifacts/ask-artifact.js b/test/artifacts/ask-artifact.js new file mode 100644 index 0000000..4f0852a --- /dev/null +++ b/test/artifacts/ask-artifact.js @@ -0,0 +1,55 @@ +'use strict'; + +var sensingData = { + "objName": "Stage", + "costumes": [{ + "costumeName": "backdrop1", + "baseLayerID": -1, + "baseLayerMD5": "b61b1077b0ea1931abee9dbbfa7903ff.png", + "bitmapResolution": 2, + "rotationCenterX": 480, + "rotationCenterY": 360 + }], + "currentCostumeIndex": 0, + "penLayerMD5": "5c81a336fab8be57adc039a8a2b33ca9.png", + "tempoBPM": 60, + "videoAlpha": 0.5, + "children": [{ + "objName": "Sprite1", + "scripts": [[42, 40.5, [["whenGreenFlag"], ["doAsk", "What's your name?"]]], + [44.5, + 155.5, + [["whenGreenFlag"], + ["say:", "Hello!"], + ["doIf", ["=", ["timeAndDate", "minute"], "60"], [["say:", ["timestamp"]]]]]]], + "costumes": [{ + "costumeName": "costume1", + "baseLayerID": -1, + "baseLayerMD5": "f9a1c175dbe2e5dee472858dd30d16bb.svg", + "bitmapResolution": 1, + "rotationCenterX": 47, + "rotationCenterY": 55 + }], + "currentCostumeIndex": 0, + "scratchX": 0, + "scratchY": 0, + "scale": 1, + "direction": 90, + "rotationStyle": "normal", + "isDraggable": false, + "indexInLibrary": 1, + "visible": true, + "spriteInfo": { + } + }], + "info": { + "projectID": "18926654", + "spriteCount": 1, + "flashVersion": "MAC 12,0,0,70", + "swfVersion": "v396", + "userAgent": "Mozilla\/5.0 (Macintosh; Intel Mac OS X 10.9; rv:27.0) Gecko\/20100101 Firefox\/27.0", + "videoOn": false, + "scriptCount": 2, + "hasCloudData": false + } +}; diff --git a/test/unit/looksPrimitiveSpec.js b/test/unit/looksPrimitiveSpec.js new file mode 100644 index 0000000..cadbc6e --- /dev/null +++ b/test/unit/looksPrimitiveSpec.js @@ -0,0 +1,23 @@ +/* jasmine specs for primitives/LooksPrims.js go here */ + +describe ('LooksPrims', function() { + var looksPrims; + beforeEach(function() { + looksPrims = LooksPrims; + }); + + describe('showBubble', function(){ + var sayBlock, targetSpriteMock; + beforeEach(function() { + sayBlock = {'args': ['what to say']}; + targetSpriteMock = targetMock(); + interp = interpreterMock({'targetSprite': targetSpriteMock }, {'arg': sayBlock}); + + }); + it('should return do something', function() { + spyOn(targetSpriteMock, "showBubble"); + showBubble(sayBlock, "say"); + expect(targetSpriteMock.showBubble).toHaveBeenCalled; + }); + }); +}); diff --git a/test/unit/scratchSpec.js b/test/unit/scratchSpec.js index 0f35ca2..0852e46 100644 --- a/test/unit/scratchSpec.js +++ b/test/unit/scratchSpec.js @@ -2,10 +2,8 @@ describe ('Scratch', function() { describe('Scratch - Load Project', function(){ - var getScript, request, scratch; - var uri = "http://getScript.example.com"; + var request, scratch; var callBack = jasmine.createSpy('onSuccess'); - var testResponseText = 'This is a script'; var TestResponses = { status: 200, responseText: returnData}; diff --git a/test/unit/sensingPrimitiveSpec.js b/test/unit/sensingPrimitiveSpec.js new file mode 100644 index 0000000..b75eda1 --- /dev/null +++ b/test/unit/sensingPrimitiveSpec.js @@ -0,0 +1,81 @@ +/* jasmine specs for primitives/SensingPrims.js go here */ + +describe ('SensingPrims', function() { + var sensingPrims; + beforeEach(function() { + sensingPrims = SensingPrims; + realDate = Date; + }); + + afterEach(function () { + Date = realDate; + }); + + describe('primTimestamp', function(){ + beforeEach(function() { + /* MonkeyPatching the built-in Javascript Date */ + var epochDate = new Date(2000, 0, 1); + var nowDate = new Date(2014, 5, 16); + Date = function () { + return (arguments.length ? epochDate : nowDate); + }; + }); + + it('should return the days since 2000', function() { + expect(sensingPrims.prototype.primTimestamp()).toEqual(5280.25); + }); + }); + + describe('primTimeDate', function(){ + beforeEach(function() { + /* MonkeyPatching the built-in Javascript Date */ + Date = function () { + return { + 'getFullYear' : function() { return 2014;}, + 'getMonth' : function() { return 4;}, + 'getDate' : function() { return 16;}, + 'getDay' : function() { return 4;}, + 'getHours' : function() { return 9;}, + 'getMinutes' : function() { return 18;}, + 'getSeconds' : function() { return 36;}, + 'getTime' : function() {} + }; + }; + }); + + it('should return the year', function() { + var block = {'args' : ['year']}; + expect(sensingPrims.prototype.primTimeDate(block)).toEqual(2014); + }); + + it('should return the month of the year', function() { + var block = {'args' : ['month']}; + expect(sensingPrims.prototype.primTimeDate(block)).toEqual(5); + }); + + it('should return the day of the week', function() { + var block = {'args' : ['day of week']}; + expect(sensingPrims.prototype.primTimeDate(block)).toEqual(5); + }); + + it('should return the hour of the day', function() { + var block = {'args' : ['hour']}; + expect(sensingPrims.prototype.primTimeDate(block)).toEqual(9); + }); + + it('should return the minute of the hour', function() { + var block = {'args' : ['minute']}; + expect(sensingPrims.prototype.primTimeDate(block)).toEqual(18); + }); + + it('should return the second of the minute', function() { + var block = {'args' : ['second']}; + expect(sensingPrims.prototype.primTimeDate(block)).toEqual(36); + }); + + it('should return the 0 on year', function() { + var block = {'args' : ['anythingElse']}; + expect(sensingPrims.prototype.primTimeDate(block)).toEqual(0); + }); + }); +}); From 1beed456ccaeb68095572081ab7d3cb84952c257 Mon Sep 17 00:00:00 2001 From: Brian Pilati <brian.pilati@domo.com> Date: Sat, 8 Mar 2014 00:46:59 -0700 Subject: [PATCH 05/10] updates to add "ask" functionality --- .gitignore | 1 + img/ask-bottom.png | Bin 0 -> 909 bytes img/ask-button.png | Bin 0 -> 1933 bytes img/playerStartFlag.png | Bin 8076 -> 4829 bytes js/Reporter.js | 11 +- js/Sprite.js | 70 +++++- js/Stage.js | 1 + js/primitives/LooksPrims.js | 14 ++ player.css | 47 ++++- test/artifacts/IOMock.js | 25 +++ test/artifacts/TargetMock.js | 4 +- test/artifacts/ask-artifact.js | 16 +- test/artifacts/reporterValues.js | 91 ++++++++ test/unit/looksPrimitiveSpec.js | 54 ++++- test/unit/reporterSpec.js | 96 +++++++++ test/unit/spriteSpec.js | 352 +++++++++++++++++++++++++++++++ test/unit/stageSpec.js | 51 +++++ 17 files changed, 809 insertions(+), 24 deletions(-) create mode 100644 img/ask-bottom.png create mode 100644 img/ask-button.png create mode 100644 test/artifacts/IOMock.js create mode 100644 test/artifacts/reporterValues.js create mode 100644 test/unit/reporterSpec.js create mode 100644 test/unit/spriteSpec.js create mode 100644 test/unit/stageSpec.js diff --git a/.gitignore b/.gitignore index ca4d940..1d5cbb2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ *.swp test/lib/* +.DS_Store logs diff --git a/img/ask-bottom.png b/img/ask-bottom.png new file mode 100644 index 0000000000000000000000000000000000000000..9823a430aa3477e626d124c5d8fa0e78997a1875 GIT binary patch literal 909 zcmV;819JR{P)<h;3K|Lk000e1NJLTq001fg000sQ1^@s6E$U3000004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU#A4x<(RCwBA zy!HOae-36Q23cNKMg}yn>gI?43$MNhv*Wb|8N$>B7-$7ve*Mj`=g}92J0E^AoO=EZ zLmnW2SX2er8L?@6^Zn2N6HpB@ysQk4vOF|#;LUeG8BPN2IrZWj!?!>G7`RxN;Bq=5 z91QHtj12ZNJPZH<#DdF!<Ilc<4FLgXIUWX4E*7de?#r*g3};_`XE^rs8^eQ7zZjl; z{*5B9B*4aCB*w{LD#^v5Bg_E~NPqysYWvsU|Nes<3IdYctPD<a)CtY&?|v{`e*J^t z7|`t7?|-7mX$f;MaI!!`PhX6KK?KA>0ssNTf-5vHzWxqon@DhiG*J|Kr(S*sr@IFq ze=$5p3q2J9b_O*;HU@iOV8B8T8vqC(7Hpxp?fxe)Tb!GP?9jaS77}`(wD<AnKZfUD zf1{+mzyJO-*aOqOr4$!N=wSl@0fgbu3$K2_)24|y7tputB!%8ZpySQ}Ll6|T_kkgZ z4Ag;UsQ^RIOoAH}Q;ei|93X%&9J=e_XLxA3$rBfv_dot*IPv^D!*yUNzWnwFC3Ap6 z?>~^XmF6Zo^soVd077x-*_Yq{gF*!a<apVDzGuf9dgp<mclO11aLz@~9NNIl0m{9g zSRpm^umOMoLUHKEJ0BTtK%?D9nHQ^udmny+b1kS~egzD@ho4ZR*GQa`K^GW`mQvhU zD-m)5Kmf5IhbAy1f!T(@G7J<F$YB5DFAO)|{$#ifbm$kLI`rt3045b%V9}x}#LmFY z%EU<RiUA;i;0`_U>?=Gp16BFp)!Ko_UxDS{PlmTY{s2Q0HS~;up$BrL6)^N<s2F<K z06+kN9SRJk|M#Fd6c`!|-+%vSm~`$nFx37qd;+G$$5=wo4j6hGGzvXz03d)E|NsBb zFyZX$|GOW4!K)CIIY5O!C=IIMXdloL00a=r9bjnQL5ou4(Bo%k23KnaVjTRy(EG$N zKmZ_sST^7Ngi$bq90*E(0~LDM06+k-h;y;RIo7~J85(**z|ecoFo*y^0D;mdJR>Rb jvvB~apA3T#00bBSHz77eF$;b_00000NkvXXu0mjf{(^|| literal 0 HcmV?d00001 diff --git a/img/ask-button.png b/img/ask-button.png new file mode 100644 index 0000000000000000000000000000000000000000..713927add0c1ba49aef6172c49b7243b04106660 GIT binary patch literal 1933 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 J*Bm1(`#-kDcuoKS literal 0 HcmV?d00001 diff --git a/img/playerStartFlag.png b/img/playerStartFlag.png index 11a8e72b970d14beda936bcd68e7acb1be7da02a..f2211d1ddad2a80ee4c4ad703a8df910fdbe2c2b 100644 GIT binary patch literal 4829 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 literal 8076 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 a{|^A`{-UaIXEC4v0000<MNUMnLSTZ6SUZLQ diff --git a/js/Reporter.js b/js/Reporter.js index a6acd66..c55f0d2 100644 --- a/js/Reporter.js +++ b/js/Reporter.js @@ -19,7 +19,6 @@ var Reporter = function(data) { this.cmd = data.cmd; this.color = data.color; this.isDiscrete = data.isDiscrete; - this.label = data.label; this.mode = data.mode; this.param = data.param; this.sliderMin = data.sliderMin; @@ -35,11 +34,19 @@ var Reporter = function(data) { this.slider = null; // slider jQ element }; +Reporter.prototype.determineReporterLabel = function() { + if (this.target === 'Stage') { + return this.param; + } else { + return this.target + ': ' + this.param; + } +} + Reporter.prototype.attach = function(scene) { switch (this.mode) { case 1: // Normal case 3: // Slider - this.el = $('<div class="reporter-normal">' + this.label + '</div>'); + this.el = $('<div class="reporter-normal">' + this.determineReporterLabel() + '</div>'); this.valueEl = $('<div class="reporter-inset">null</div>'); this.el.append(this.valueEl); if (this.mode == 3) { diff --git a/js/Sprite.js b/js/Sprite.js index 026d17e..07d7621 100644 --- a/js/Sprite.js +++ b/js/Sprite.js @@ -73,7 +73,14 @@ var Sprite = function(data) { this.talkBubbleStyler = null; this.talkBubbleOn = false; + // HTML element for the ask bubbles + this.askInput = null; + this.askInputField = null; + this.askInputButton = null; + this.askInputOn = false; + // Internal variables used for rendering meshes. + this.askAnswer = null; //this is a private variable this.textures = []; this.materials = []; this.geometries = []; @@ -142,14 +149,26 @@ Sprite.prototype.attach = function(scene) { this.updateVisible(); this.updateTransform(); - this.talkBubble = $('<div class="bubble-container"></div>'); - this.talkBubble.css('display', 'none'); - this.talkBubbleBox = $('<div class="bubble"></div>'); - this.talkBubbleStyler = $('<div class="bubble-say"></div>'); - this.talkBubble.append(this.talkBubbleBox); - this.talkBubble.append(this.talkBubbleStyler); + if (! this.isStage) { + this.talkBubble = $('<div class="bubble-container"></div>'); + this.talkBubble.css('display', 'none'); + this.talkBubbleBox = $('<div class="bubble"></div>'); + this.talkBubbleStyler = $('<div class="bubble-say"></div>'); + this.talkBubble.append(this.talkBubbleBox); + this.talkBubble.append(this.talkBubbleStyler); + } + + this.askInput = $('<div class="ask-container"></div>'); + this.askInput.css('display', 'none'); + this.askInputField = $('<div class="ask-field"></div>'); + this.askInputTextField = $('<input class="ask-text-field"></input>'); + this.askInputField.append(this.askInputTextField); + this.askInputButton = $('<div class="ask-button"></div>'); + this.askInput.append(this.askInputField); + this.askInput.append(this.askInputButton); runtime.scene.append(this.talkBubble); + runtime.scene.append(this.askInput); }; // Load sounds from the server and buffer them @@ -254,20 +273,22 @@ Sprite.prototype.onClick = function(evt) { }; Sprite.prototype.setVisible = function(v) { + if (v === true || v === false) { this.visible = v; this.updateVisible(); + } }; Sprite.prototype.updateLayer = function() { $(this.mesh).css('z-index', this.z); - if (this.talkBubble) this.talkBubble.css('z-index', this.z); + if (this.talkBubble) $(this.talkBubble).css('z-index', this.z); + if (this.askInput) this.askInput.css('z-index', this.z); }; Sprite.prototype.updateVisible = function() { $(this.mesh).css('display', this.visible ? 'inline' : 'none'); - if (this.talkBubbleOn) { - this.talkBubble.css('display', this.visible ? 'inline-block' : 'none'); - } + if (this.talkBubbleOn) this.talkBubble.css('display', this.visible ? 'inline-block' : 'none'); + if (this.askInputOn) this.askInput.css('display', this.visible ? 'inline-block' : 'none'); }; Sprite.prototype.updateTransform = function() { @@ -347,13 +368,21 @@ Sprite.prototype.showBubble = function(text, type) { this.talkBubble.css('left', xy[0] + 'px'); this.talkBubble.css('top', xy[1] + 'px'); - + this.talkBubble.removeClass('say-think-border'); + this.talkBubble.removeClass('ask-border'); + this.talkBubbleStyler.removeClass('bubble-say'); this.talkBubbleStyler.removeClass('bubble-think'); + this.talkBubbleStyler.removeClass('bubble-ask'); if (type == 'say') { + this.talkBubbleBox.addClass('say-think-border'); this.talkBubbleStyler.addClass('bubble-say'); } else if (type == 'think') { + this.talkBubbleBox.addClass('say-think-border'); this.talkBubbleStyler.addClass('bubble-think'); + } else if (type == 'doAsk') { + this.talkBubbleBox.addClass('ask-border'); + this.talkBubbleStyler.addClass('bubble-ask'); } if (this.visible) { @@ -367,6 +396,25 @@ Sprite.prototype.hideBubble = function() { this.talkBubble.css('display', 'none'); }; +Sprite.prototype.showAsk = function() { + this.askInputOn = true; + this.askInput.css('z-index', this.z); + this.askInput.css('left', '15px'); + this.askInput.css('right', '15px'); + this.askInput.css('bottom', '7px'); + this.askInput.css('height', '25px'); + + if (this.visible) { + this.askInput.css('display', 'inline-block'); + this.askInputTextField.focus(); + } +}; + +Sprite.prototype.hideAsk = function() { + this.askInputOn = false; + this.askInput.css('display', 'none'); +}; + Sprite.prototype.setXY = function(x, y) { this.scratchX = x; this.scratchY = y; diff --git a/js/Stage.js b/js/Stage.js index c425e10..8146e93 100644 --- a/js/Stage.js +++ b/js/Stage.js @@ -33,6 +33,7 @@ var Stage = function(data) { this.lineCanvas.width = 480; this.lineCanvas.height = 360; this.lineCache = this.lineCanvas.getContext('2d'); + this.isStage = true; Sprite.call(this, data); }; diff --git a/js/primitives/LooksPrims.js b/js/primitives/LooksPrims.js index e56f646..b63c7f2 100644 --- a/js/primitives/LooksPrims.js +++ b/js/primitives/LooksPrims.js @@ -43,6 +43,9 @@ LooksPrims.prototype.addPrimsTo = function(primTable) { primTable['setGraphicEffect:to:'] = this.primSetEffect; primTable['filterReset'] = this.primClearEffects; + primTable['doAsk'] = this.primDoAsk; + primTable['answer'] = this.primAnswer; + primTable['say:'] = function(b) { showBubble(b, 'say'); }; primTable['say:duration:elapsed:from:'] = function(b) { showBubbleAndWait(b, 'say'); }; primTable['think:'] = function(b) { showBubble(b, 'think'); }; @@ -170,6 +173,17 @@ LooksPrims.prototype.primClearEffects = function(b) { s.updateFilters(); }; +LooksPrims.prototype.primDoAsk= function(b) { + showBubble(b, "doAsk"); + var s = interp.targetSprite(); + if (s != null) s.showAsk(); +}; + +LooksPrims.prototype.primAnswer = function() { + var s = interp.targetSprite(); + return ((s != null) ? s.answer : undefined); +}; + var showBubble = function(b, type) { var s = interp.targetSprite(); if (s != null) s.showBubble(interp.arg(b, 0), type); diff --git a/player.css b/player.css index 3fcff6c..40ca47e 100644 --- a/player.css +++ b/player.css @@ -398,7 +398,7 @@ button#trigger-stop:hover { } /* Say/think bubble styles */ - .bubble-container { +.bubble-container { position: absolute; } .bubble { @@ -407,7 +407,6 @@ button#trigger-stop:hover { max-width: 120px; min-width: 40px; padding: 6px 11px 6px 11px; - border: 3px solid rgb(160, 160, 160); border-radius: 10px; background: #fff; font-family: sans-serif; @@ -416,6 +415,20 @@ button#trigger-stop:hover { color: #000; text-align: center; } +.bubble.say-think-border { + border: 3px solid rgb(160, 160, 160); +} +.bubble.ask-border, .ask-container { + border: 3px solid rgb(74, 173, 222); +} +.bubble-ask { + position: absolute; + margin-top: -3px; + margin-left: 8px; + width: 44px; + height: 18px; + background: url(img/ask-bottom.png) transparent no-repeat; +} .bubble-say { position: absolute; margin-top: -3px; @@ -432,3 +445,33 @@ button#trigger-stop:hover { height: 19px; background: url(img/think-bottom.png) transparent no-repeat; } +.ask-container { + position: absolute; + display: inline-block; + padding: 5px 0px 0px 5px; + border-radius: 5px; + background: #fff; + font-family: sans-serif; + font-weight: bold; + font-size: 14px; + color: #000; +} +.ask-container .ask-field .ask-text-field { + width: 405px; + height: 16px; + font-family: sans-serif; + font-weight: light; + font-size: 12px; + background: #EAEAEA; +} + +.ask-container .ask-button { + position: absolute; + right: 2px; + top: 2px; + width: 25px; + height: 25px; + background: url(img/ask-button.png) transparent no-repeat; +} + + diff --git a/test/artifacts/IOMock.js b/test/artifacts/IOMock.js new file mode 100644 index 0000000..6a7a118 --- /dev/null +++ b/test/artifacts/IOMock.js @@ -0,0 +1,25 @@ +'use strict'; + +var ioMock = function() { + var args = createArgs(arguments); + + function getArgs(argKey) { + return ((argKey in args) ? args[argKey] : null); + } + + function createArgs(methodArgs) { + var args = {}; + if (methodArgs.length) { + _.each(methodArgs, function(newObject) { + _.each(newObject, function(value, key) { + args[key] = value; + }); + }); + } + return args; + } + + return { + 'getCount' : function() { return getArgs('getCount'); } + } +}; diff --git a/test/artifacts/TargetMock.js b/test/artifacts/TargetMock.js index 9121a00..8071a39 100644 --- a/test/artifacts/TargetMock.js +++ b/test/artifacts/TargetMock.js @@ -2,7 +2,9 @@ var targetMock = function() { return { - 'showBubble' : function() {} + 'showBubble' : function() {}, + 'showAsk' : function() {}, + 'answer' : 22 }; } diff --git a/test/artifacts/ask-artifact.js b/test/artifacts/ask-artifact.js index 4f0852a..654a07d 100644 --- a/test/artifacts/ask-artifact.js +++ b/test/artifacts/ask-artifact.js @@ -1,7 +1,10 @@ -'use strict'; - var sensingData = { "objName": "Stage", + "variables": [{ + "name": "myAnswer", + "value": 0, + "isPersistent": false + }], "costumes": [{ "costumeName": "backdrop1", "baseLayerID": -1, @@ -16,6 +19,15 @@ var sensingData = { "videoAlpha": 0.5, "children": [{ "objName": "Sprite1", + "variables": [{ + "name": "myAnswer2", + "value": 0, + "isPersistent": false + }, { + "name": "answer", + "value": 0, + "isPersistent": false + }], "scripts": [[42, 40.5, [["whenGreenFlag"], ["doAsk", "What's your name?"]]], [44.5, 155.5, diff --git a/test/artifacts/reporterValues.js b/test/artifacts/reporterValues.js new file mode 100644 index 0000000..484b990 --- /dev/null +++ b/test/artifacts/reporterValues.js @@ -0,0 +1,91 @@ +'use Strict;' + +var ReporterValues = function() { + return { + 'getStageVariables': function() { + return { + 'cmd' : "getVar:", + 'color' : 15629590, + 'isDiscrete' : true, + 'mode' : 1, + 'param' : "myAnswer", + 'sliderMax' : 100, + 'sliderMin' : 0, + 'target' : "Stage", + 'visible' : true, + 'x' : 5, + 'y' : 5, + }; + } + } +}; +/* +cmd + "getVar:" + +color + 15629590 + +isDiscrete + true + +mode + 1 + +param + "myAnswer2" + +sliderMax + 100 + +sliderMin + 0 + +target + "Sprite1" + +visible + true + +x + 5 + +y + 32 +*/ + +/* + +cmd + "getVar:" + +color + 15629590 + +isDiscrete + true + +mode + 1 + +param + "answer" + +sliderMax + 100 + +sliderMin + 0 + +target + "Sprite1" + +visible + true + +x + 5 + +y + 59 +*/ diff --git a/test/unit/looksPrimitiveSpec.js b/test/unit/looksPrimitiveSpec.js index cadbc6e..a46f747 100644 --- a/test/unit/looksPrimitiveSpec.js +++ b/test/unit/looksPrimitiveSpec.js @@ -1,23 +1,65 @@ /* jasmine specs for primitives/LooksPrims.js go here */ describe ('LooksPrims', function() { - var looksPrims; + var looksPrims, targetSpriteMock; beforeEach(function() { looksPrims = LooksPrims; + targetSpriteMock = targetMock(); }); - describe('showBubble', function(){ - var sayBlock, targetSpriteMock; + describe('showBubble for Say', function(){ + var sayBlock; beforeEach(function() { sayBlock = {'args': ['what to say']}; - targetSpriteMock = targetMock(); interp = interpreterMock({'targetSprite': targetSpriteMock }, {'arg': sayBlock}); - }); - it('should return do something', function() { + + it('should call the showBubble method on the targetedSprite', function() { spyOn(targetSpriteMock, "showBubble"); showBubble(sayBlock, "say"); expect(targetSpriteMock.showBubble).toHaveBeenCalled; }); }); + + describe('showBubble for Think', function(){ + var thinkBlock; + beforeEach(function() { + thinkBlock = {'args': ['what to think']}; + interp = interpreterMock({'targetSprite': targetSpriteMock }, {'arg': thinkBlock}); + }); + + it('should call the showBubble method on the targetedSprite', function() { + spyOn(targetSpriteMock, "showBubble"); + showBubble(thinkBlock, "think"); + expect(targetSpriteMock.showBubble).toHaveBeenCalled; + }); + }); + + + describe('showBubble for Ask', function(){ + var askBlock; + beforeEach(function() { + askBlock = {'args': 'what to ask'}; + interp = interpreterMock({'targetSprite': targetSpriteMock }, {'arg': askBlock}); + + }); + + it('should call the showBubble method on the targetedSprite', function() { + spyOn(targetSpriteMock, "showBubble"); + spyOn(targetSpriteMock, "showAsk"); + looksPrims.prototype.primDoAsk(askBlock); + expect(targetSpriteMock.showBubble).toHaveBeenCalled; + expect(targetSpriteMock.showAsk).toHaveBeenCalled; + }); + }); + + describe('primAnswer', function(){ + beforeEach(function() { + interp = interpreterMock({'targetSprite': targetSpriteMock }); + }); + + it('should return the answer variable from the targetedSprite', function() { + expect(looksPrims.prototype.primAnswer()).toBe(22); + }); + }); }); diff --git a/test/unit/reporterSpec.js b/test/unit/reporterSpec.js new file mode 100644 index 0000000..c879f58 --- /dev/null +++ b/test/unit/reporterSpec.js @@ -0,0 +1,96 @@ +/* jasmine specs for Reporter.js go here */ + +describe ('Reporter', function() { + var reporter, reporterValues; + + beforeEach(function() { + reporter = Reporter; + reporterValues = new ReporterValues(); + }); + + describe('Instantization variables', function() { + var initReporter; + beforeEach(function() { + io = new ioMock({'getCount': 4}); + initReporter = new reporter(reporterValues.getStageVariables()); + }); + + describe('Reporter Variables', function() { + it('should have a cmd variable', function() { + expect(initReporter.cmd).toBe('getVar:'); + }); + + it('should have a color variable', function() { + expect(initReporter.color).toBe(15629590); + }); + + it('should have a isDiscrete variable', function() { + expect(initReporter.isDiscrete).toBe(true); + }); + + it('should have a mode variable', function() { + expect(initReporter.mode).toBe(1); + }); + + it('should have a param variable', function() { + expect(initReporter.param).toBe('myAnswer'); + }); + + it('should have a sliderMax variable', function() { + expect(initReporter.sliderMax).toBe(100); + }); + + it('should have a sliderMin variable', function() { + expect(initReporter.sliderMin).toBe(0); + }); + + it('should have a target variable', function() { + expect(initReporter.target).toBe('Stage'); + }); + + it('should have a visible variable', function() { + expect(initReporter.visible).toBe(true); + }); + + it('should have a x variable', function() { + expect(initReporter.x).toBe(5); + }); + + it('should have a y variable', function() { + expect(initReporter.y).toBe(5); + }); + + it('should have a z variable', function() { + expect(initReporter.z).toBe(4); + }); + + it('should have an el variable', function() { + expect(initReporter.el).toBe(null); + }); + + it('should have an valueEl variable', function() { + expect(initReporter.valueEl).toBe(null); + }); + + it('should have an slider variable', function() { + expect(initReporter.slider).toBe(null); + }); + }); + }); + describe('determineReporterLabel', function() { + + it('should return a stage variable', function() { + reporter.prototype.target = "Stage"; + reporter.prototype.param = "myAnswer"; + expect(reporter.prototype.determineReporterLabel()).toBe('myAnswer'); + }); + + it('should return a sprite variable', function() { + reporter.prototype.target = "Sprite 1"; + reporter.prototype.param = "localAnswer"; + expect(reporter.prototype.determineReporterLabel()).toBe('Sprite 1: localAnswer'); + + }); + + }); +}); diff --git a/test/unit/spriteSpec.js b/test/unit/spriteSpec.js new file mode 100644 index 0000000..feeb11c --- /dev/null +++ b/test/unit/spriteSpec.js @@ -0,0 +1,352 @@ +/* jasmine specs for Sprite.js go here */ + +describe ('Sprite', function() { + var sprite; + + + beforeEach(function() { + sprite = Sprite; + }); + + describe('Instantization variables', function() { + var initSprite; + beforeEach(function() { + var spriteObject = sensingData.children[0]; + initSprite = new sprite(spriteObject); + }); + + describe('Sprite Variables', function() { + it('should have a visible variable', function() { + expect(initSprite.visible).toBe(true); + }); + }); + + describe('Pen Variables', function() { + it('should have a penIsDown variable', function() { + expect(initSprite.penIsDown).toBe(false); + }); + + it('should have a penWidth variable', function() { + expect(initSprite.penWidth).toBe(1); + }); + + it('should have a penHue variable', function() { + expect(initSprite.penHue).toBe(120); + }); + + it('should have a penShade variable', function() { + expect(initSprite.penShade).toBe(50); + }); + + it('should have a penColorCache variable', function() { + expect(initSprite.penColorCache).toBe(0x0000FF); + }); + }); + + describe('Ask Bubble', function() { + it('should have an askInput variable', function() { + expect(initSprite.askInput).toBe(null); + }); + + it('should have an askInputBox variable', function() { + expect(initSprite.askInputField).toBe(null); + }); + + it('should have an askInputStyler variable', function() { + expect(initSprite.askInputButton).toBe(null); + }); + + it('should have an askInputOn variable', function() { + expect(initSprite.askInputOn).toBe(false); + }); + + it('should have an answer variable', function() { + expect(initSprite.askAnswer).toBe(null); + }); + }); + }) + + describe('showBubble', function() { + var spriteProto; + beforeEach(function() { + spriteProto = sprite.prototype; + spriteProto.visible = true; + setFixtures('<div class="bubble-container"></div>'); + spriteProto.talkBubble = $('.bubble-container'); + spriteProto.talkBubble.css('display', 'none'); + spriteProto.talkBubbleBox = $('<div class="bubble"></div>'); + spriteProto.talkBubbleStyler = $('<div class="bubble-say"></div>'); + spriteProto.talkBubble.append(spriteProto.talkBubbleBox); + spriteProto.talkBubble.append(spriteProto.talkBubbleStyler); + }); + + describe('Say', function(){ + it('should call the showBubble method on the Sprite', function() { + var text = "What to say"; + spyOn(spriteProto, "getTalkBubbleXY").andReturn([50,50]);; + spriteProto.showBubble(text, "say"); + expect($('.bubble').html()).toBe(text); + expect($('.bubble-say').hasClass('bubble-say')).toBe(true); + expect($('.bubble').hasClass('say-think-border')).toBe(true); + expect($('.bubble-container').css('display')).toBe('inline-block'); + }); + }); + + describe('Think', function(){ + it('should call the showBubble method on the Sprite', function() { + var text = "What to think"; + spyOn(spriteProto, "getTalkBubbleXY").andReturn([50,50]);; + spriteProto.showBubble(text, "think"); + expect($('.bubble').html()).toBe(text); + expect($('.bubble-think').hasClass('bubble-think')).toBe(true); + expect($('.bubble').hasClass('say-think-border')).toBe(true); + expect($('.bubble-container').css('display')).toBe('inline-block'); + }); + }); + + describe('Ask', function(){ + it('should call the showBubble method on the Sprite', function() { + var text = "What to Ask"; + spyOn(spriteProto, "getTalkBubbleXY").andReturn([50,50]);; + spriteProto.showBubble(text, "doAsk"); + expect($('.bubble').html()).toBe(text); + expect($('.bubble-ask').hasClass('bubble-ask')).toBe(true); + expect($('.bubble').hasClass('ask-border')).toBe(true); + expect($('.bubble-container').css('display')).toBe('inline-block'); + }); + }); + + describe('Any Bubble with visible false', function(){ + it('should call the showBubble method on the Sprite and not display it', function() { + spriteProto.visible = false; + var text = "What to Ask"; + spyOn(spriteProto, "getTalkBubbleXY").andReturn([50,50]);; + spriteProto.showBubble(text, "doAsk"); + expect($('.bubble').html()).toBe(text); + expect($('.bubble-ask').hasClass('bubble-ask')).toBe(true); + expect($('.bubble').hasClass('ask-border')).toBe(true); + expect($('.bubble-container').css('display')).toBe('none'); + }); + }); + }); + + describe('hideBubble', function() { + var spriteProto; + beforeEach(function() { + spriteProto = sprite.prototype; + setFixtures('<div class="bubble-container"></div>'); + spriteProto.talkBubble = $('.bubble-container'); + spriteProto.talkBubble.css('display', 'inline'); + }); + + it('should hide the bubble', function() { + spriteProto.hideBubble(); + expect($('.bubble-container').css('display')).toBe('none'); + expect(spriteProto.talkBubbleOn).toBe(false); + + }); + }); + + describe('showAsk', function() { + var spriteProto; + beforeEach(function() { + spriteProto = sprite.prototype; + spriteProto.visible = true; + spriteProto.z = 22; + setFixtures('<div class="ask-container"></div>'); + spriteProto.askInput= $('.ask-container'); + spriteProto.askInput.css('display','none'); + spriteProto.askInput.css('position','relative'); + spriteProto.askInputField = $('<div class="ask-input"></div>'); + spriteProto.askInputTextField = $('<input class="ask-text-field"></input>'); + spriteProto.askInputField.append(spriteProto.askInputTextField); + spriteProto.askInputButton = $('<div class="ask-button"></div>'); + spriteProto.askInput.append(spriteProto.askInputField); + spriteProto.askInput.append(spriteProto.askInputButton); + }); + + it('should show the ask input if visible is true', function() { + spriteProto.showAsk(); + expect($('.ask-container').css('display')).toBe('inline-block'); + expect($('.ask-container').css('z-index')).toBe('22'); + expect($('.ask-container').css('left')).toBe('15px'); + expect($('.ask-container').css('right')).toBe('15px'); + expect($('.ask-container').css('bottom')).toBe('7px'); + expect($('.ask-container').css('height')).toBe('25px'); + expect($('.ask-container').css('height')).toBe('25px'); + expect($('.ask-text-field').is(':focus')).toBe(true); + expect(spriteProto.askInputOn).toBe(true); + }); + + it('should not show the ask input if visible is false', function() { + spriteProto.visible = false; + spriteProto.showAsk(); + expect($('.ask-container').css('display')).toBe('none'); + expect($('.ask-text-field').is(':focus')).toBe(false); + }); + }); + + describe('hideAsk', function() { + var spriteProto; + beforeEach(function() { + spriteProto = sprite.prototype; + setFixtures('<div class="ask-container"></div>'); + spriteProto.askInput = $('.ask-container'); + spriteProto.askInput.css('display', 'inline'); + }); + + it('should hide the ask input', function() { + spriteProto.hideAsk(); + expect($('.ask-container').css('display')).toBe('none'); + expect(spriteProto.askInputOn).toBe(false); + + }); + }); + + describe('updateLayer', function() { + var spriteProto; + beforeEach(function() { + spriteProto = sprite.prototype; + setFixtures('<img class="mesh"></img><div class="bubble-container"></div><div class="ask-container"></div>'); + spriteProto.talkBubble = $('.bubble-container'); + spriteProto.talkBubble.css('position', 'relative'); + spriteProto.askInput = $('.ask-container'); + spriteProto.askInput.css('position', 'relative'); + spriteProto.mesh = $('.mesh'); + spriteProto.mesh.css('position', 'relative'); + spriteProto.z = 22; + }); + + it('should update the mesh z-index', function() { + expect($('.mesh').css('z-index')).toBe('auto'); + spriteProto.updateLayer(); + expect($('.mesh').css('z-index')).toBe('22'); + }); + + it('should update the talkBubble z-index', function() { + expect($('.bubble-container').css('z-index')).toBe('auto'); + spriteProto.updateLayer(); + expect($('.bubble-container').css('z-index')).toBe('22'); + }); + + it('should update the askInput z-index', function() { + expect($('.ask-container').css('z-index')).toBe('auto'); + spriteProto.updateLayer(); + expect($('.ask-container').css('z-index')).toBe('22'); + }); + }); + + describe('updateVisible', function() { + var spriteProto; + beforeEach(function() { + spriteProto = sprite.prototype; + setFixtures('<img class="mesh"></img><div class="bubble-container"></div><div class="ask-container"></div>'); + spriteProto.talkBubble = $('.bubble-container'); + spriteProto.talkBubble.css('display', 'none'); + spriteProto.askInput = $('.ask-container'); + spriteProto.askInput.css('display', 'none'); + spriteProto.mesh = $('.mesh'); + spriteProto.mesh.css('display', 'none'); + }); + + describe('mesh', function() { + it('should update the mesh display on false', function() { + expect($('.mesh').css('display')).toBe('none'); + spriteProto.visible = false; + spriteProto.updateVisible(); + expect($('.mesh').css('display')).toBe('none'); + }); + + it('should update the mesh display on true', function() { + expect($('.mesh').css('display')).toBe('none'); + spriteProto.visible = true; + spriteProto.updateVisible(); + expect($('.mesh').css('display')).toBe('inline'); + }); + + }); + + describe('talkBubble', function() { + it('should update the talkBubble on talkBubble true and visible true', function() { + expect($('.bubble-container').css('display')).toBe('none'); + spriteProto.talkBubbleOn = true; + spriteProto.visible = true; + spriteProto.updateVisible(); + expect($('.bubble-container').css('display')).toBe('inline-block'); + }); + + it('should update the talkBubble on talkBubble false and visible true', function() { + expect($('.bubble-container').css('display')).toBe('none'); + spriteProto.talkBubbleOn = false; + spriteProto.visible = true; + spriteProto.updateVisible(); + expect($('.bubble-container').css('display')).toBe('none'); + }); + + it('should update the talkBubble on talkBubble true and visible false', function() { + expect($('.bubble-container').css('display')).toBe('none'); + spriteProto.talkBubbleOn = true; + spriteProto.visible = false; + spriteProto.updateVisible(); + expect($('.bubble-container').css('display')).toBe('none'); + }); + }); + + describe('askContainer', function() { + it('should update the askInput on askInput true and visible true', function() { + expect($('.ask-container').css('display')).toBe('none'); + spriteProto.askInputOn = true; + spriteProto.visible = true; + spriteProto.updateVisible(); + expect($('.ask-container').css('display')).toBe('inline-block'); + }); + + it('should update the askInput on askInput false and visible true', function() { + expect($('.ask-container').css('display')).toBe('none'); + spriteProto.askInputOn = false; + spriteProto.visible = true; + spriteProto.updateVisible(); + expect($('.ask-container').css('display')).toBe('none'); + }); + + it('should update the askInput on askInput true and visible false', function() { + expect($('.ask-container').css('display')).toBe('none'); + spriteProto.askInputOn = true; + spriteProto.visible = false; + spriteProto.updateVisible(); + expect($('.ask-container').css('display')).toBe('none'); + }); + }); + + }); + + describe('setVisible', function() { + var spriteProto; + beforeEach(function() { + spriteProto = sprite.prototype; + spyOn(spriteProto, "updateVisible"); + }); + + it('should set visible to true', function() { + expect(spriteProto.visible).toBe(false); + spriteProto.setVisible(true); + expect(spriteProto.visible).toBe(true); + expect(spriteProto.updateVisible).toHaveBeenCalled(); + }); + + it('should set visible to false', function() { + spriteProto.visible = true; + spriteProto.setVisible(false); + expect(spriteProto.visible).toBe(false); + expect(spriteProto.updateVisible).toHaveBeenCalled(); + }); + + it('should set take no action on an invalid character', function() { + spriteProto.visible = true; + spriteProto.setVisible('hello'); + expect(spriteProto.visible).toBe(true); + expect(spriteProto.updateVisible).not.toHaveBeenCalled(); + }); + + }); +}); diff --git a/test/unit/stageSpec.js b/test/unit/stageSpec.js new file mode 100644 index 0000000..330a451 --- /dev/null +++ b/test/unit/stageSpec.js @@ -0,0 +1,51 @@ +/* jasmine specs for Stage.js go here */ + +describe ('Stage', function() { + var stage; + + beforeEach(function() { + stage = Stage; + }); + + describe('Instantization variables', function() { + var initStage, lineCanvas; + beforeEach(function() { + spyOn(Sprite, "call"); + initStage = new stage(sensingData); + }); + + describe('Stage Variables', function() { + it('should have a z variable', function() { + expect(initStage.z).toBe(-2); + }); + + it('should have a penLayerLoaded variable', function() { + expect(initStage.penLayerLoaded).toBe(false); + }); + + it('should have a lineCanvas element', function() { + expect(initStage.lineCanvas).toBeDefined(); + }); + + it('should have a lineCanvas width', function() { + expect(initStage.lineCanvas.width).toBe(480); + }); + + it('should have a lineCanvas height', function() { + expect(initStage.lineCanvas.height).toBe(360); + }); + + it('should have a lineCache variable', function() { + expect(initStage.lineCache).toBeDefined(); + }); + + it('should have a isStage variable', function() { + expect(initStage.isStage).toBe(true); + }); + + it('should have called Sprite.call', function() { + expect(Sprite.call).toHaveBeenCalled(); + }); + }); + }); +}); From 6f54f52f3c99516ad64769159333ae48260c87c8 Mon Sep 17 00:00:00 2001 From: Brian Pilati <brian.pilati@domo.com> Date: Sat, 8 Mar 2014 09:41:01 -0700 Subject: [PATCH 06/10] Added functionality to hide the doAsk prompt on a stopAll call with tests --- js/Runtime.js | 13 ++-- test/artifacts/InterpreterMock.js | 3 +- test/artifacts/RuntimeMock.js | 29 ++++++++ test/artifacts/SpriteMock.js | 27 ++++++++ test/artifacts/StageMock.js | 25 +++++++ test/artifacts/ThreadMock.js | 24 +++++++ test/unit/runTimeSpec.js | 107 ++++++++++++++++++++++++++++++ 7 files changed, 218 insertions(+), 10 deletions(-) create mode 100644 test/artifacts/RuntimeMock.js create mode 100644 test/artifacts/SpriteMock.js create mode 100644 test/artifacts/StageMock.js create mode 100644 test/artifacts/ThreadMock.js create mode 100644 test/unit/runTimeSpec.js diff --git a/js/Runtime.js b/js/Runtime.js index b222af9..2b59751 100644 --- a/js/Runtime.js +++ b/js/Runtime.js @@ -88,19 +88,14 @@ Runtime.prototype.stopAll = function() { interp.activeThread = new Thread(null); interp.threads = []; stopAllSounds(); - // Hide reporters + // Hide sprite bubbles, resetFilters and doAsk prompts for (var s = 0; s < runtime.sprites.length; s++) { - if (runtime.sprites[s].hideBubble) { - runtime.sprites[s].hideBubble(); - } + if (runtime.sprites[s].hideBubble) runtime.sprites[s].hideBubble(); + if (runtime.sprites[s].resetFilters) runtime.sprites[s].resetFilters(); + if (runtime.sprites[s].hideAsk) runtime.sprites[s].hideAsk(); } // Reset graphic effects runtime.stage.resetFilters(); - for (var s = 0; s < runtime.sprites.length; s++) { - if (runtime.sprites[s].resetFilters) { - runtime.sprites[s].resetFilters(); - } - } }; // Step method for execution - called every 33 milliseconds diff --git a/test/artifacts/InterpreterMock.js b/test/artifacts/InterpreterMock.js index 7144e37..f48749e 100644 --- a/test/artifacts/InterpreterMock.js +++ b/test/artifacts/InterpreterMock.js @@ -21,6 +21,7 @@ var interpreterMock = function() { return { 'targetSprite' : function() { return getArgs('targetSprite'); }, - 'arg': function(block, index) { return getArgs('arg');} + 'arg': function(block, index) { return getArgs('arg');}, + 'activeThread': undefined } }; diff --git a/test/artifacts/RuntimeMock.js b/test/artifacts/RuntimeMock.js new file mode 100644 index 0000000..16200c0 --- /dev/null +++ b/test/artifacts/RuntimeMock.js @@ -0,0 +1,29 @@ +'use strict'; + +var runtimeMock = function() { + var args = createArgs(arguments); + + function getArgs(argKey) { + return ((argKey in args) ? args[argKey] : null); + } + + function createArgs(methodArgs) { + var args = {}; + if (methodArgs.length) { + _.each(methodArgs, function(newObject) { + _.each(newObject, function(value, key) { + args[key] = value; + }); + }); + } + return args; + } + + return { + 'sprites' : [ + new spriteMock() + ], + 'stage': new stageMock() + + } +}; diff --git a/test/artifacts/SpriteMock.js b/test/artifacts/SpriteMock.js new file mode 100644 index 0000000..935fe9d --- /dev/null +++ b/test/artifacts/SpriteMock.js @@ -0,0 +1,27 @@ +'use strict'; + +var spriteMock = function() { + var args = createArgs(arguments); + + function getArgs(argKey) { + return ((argKey in args) ? args[argKey] : null); + } + + function createArgs(methodArgs) { + var args = {}; + if (methodArgs.length) { + _.each(methodArgs, function(newObject) { + _.each(newObject, function(value, key) { + args[key] = value; + }); + }); + } + return args; + } + + return { + 'hideBubble' : function() { return getArgs('hideBubble'); }, + 'hideAsk': function() { return getArgs('hideAsk');}, + 'resetFilters': function() { return getArgs('resetFilters');} + } +}; diff --git a/test/artifacts/StageMock.js b/test/artifacts/StageMock.js new file mode 100644 index 0000000..3b76ced --- /dev/null +++ b/test/artifacts/StageMock.js @@ -0,0 +1,25 @@ +'use strict'; + +var stageMock = function() { + var args = createArgs(arguments); + + function getArgs(argKey) { + return ((argKey in args) ? args[argKey] : null); + } + + function createArgs(methodArgs) { + var args = {}; + if (methodArgs.length) { + _.each(methodArgs, function(newObject) { + _.each(newObject, function(value, key) { + args[key] = value; + }); + }); + } + return args; + } + + return { + 'resetFilters' : function() { return getArgs('resetFilters'); } + } +}; diff --git a/test/artifacts/ThreadMock.js b/test/artifacts/ThreadMock.js new file mode 100644 index 0000000..2d8dd30 --- /dev/null +++ b/test/artifacts/ThreadMock.js @@ -0,0 +1,24 @@ +'use strict'; + +var threadMock = function() { + var args = createArgs(arguments); + + function getArgs(argKey) { + return ((argKey in args) ? args[argKey] : null); + } + + function createArgs(methodArgs) { + var args = {}; + if (methodArgs.length) { + _.each(methodArgs, function(newObject) { + _.each(newObject, function(value, key) { + args[key] = value; + }); + }); + } + return args; + } + + return { + } +}; diff --git a/test/unit/runTimeSpec.js b/test/unit/runTimeSpec.js new file mode 100644 index 0000000..906247b --- /dev/null +++ b/test/unit/runTimeSpec.js @@ -0,0 +1,107 @@ +/* jasmine specs for Runtime.js go here */ + +describe ('Runtime', function() { + var runtimeObj; + + beforeEach(function() { + runtimeObj = Runtime; + }); + + describe('Instantization variables', function() { + var initRuntime, lineCanvas; + beforeEach(function() { + initRuntime = new runtimeObj(); + }); + + describe('Runtime Variables', function() { + it('should have a scene variable', function() { + expect(initRuntime.scene).toBe(null); + }); + + it('should have a sprites array', function() { + expect(initRuntime.sprites).toEqual([]); + }); + + it('should have a reporters array', function() { + expect(initRuntime.reporters).toEqual([]); + }); + + it('should have a keysDown array', function() { + expect(initRuntime.keysDown).toEqual([]); + }); + + it('should have a mouseDown variable', function() { + expect(initRuntime.mouseDown).toBe(false); + }); + + it('should have a mousePos array', function() { + expect(initRuntime.mousePos).toEqual([0,0]); + }); + + it('should have an audioContext variable', function() { + expect(initRuntime.audioContext).toBe(null); + }); + + it('should have an audoGain variable', function() { + expect(initRuntime.audioGain).toBe(null); + }); + + it('should have an audioPlaying array', function() { + expect(initRuntime.audioPlaying).toEqual([]); + }); + + it('should have a notesPlaying array', function() { + expect(initRuntime.notesPlaying).toEqual([]); + }); + + it('should have a projectLoaded variable', function() { + expect(initRuntime.projectLoaded).toBe(false); + }); + }); + }); + + describe('Stop All', function() { + beforeEach(function() { + runtime = new runtimeMock + spyOn(window, "stopAllSounds"); + spyOn(runtime.stage, "resetFilters"); + spyOn(runtime.sprites[0], "hideBubble"); + spyOn(runtime.sprites[0], "resetFilters"); + spyOn(runtime.sprites[0], "hideAsk"); + Thread = threadMock; + interp = new interpreterMock(); + }); + + it('should call a new Thread Object', function() { + runtimeObj.prototype.stopAll(); + expect(interp.activeThread).toEqual({}); + }); + + it('should call a blank thread array ', function() { + runtimeObj.prototype.stopAll(); + expect(interp.activeThread).toEqual([]); + }); + + it('should call stopAllSounds', function() { + runtimeObj.prototype.stopAll(); + expect(window.stopAllSounds).toHaveBeenCalled(); + }); + + it('should call sprites.hideBubble', function() { + runtimeObj.prototype.stopAll(); + expect(runtime.sprites[0].hideBubble).toHaveBeenCalled(); + }); + + it('should call sprites.resetFilters', function() { + runtimeObj.prototype.stopAll(); + expect(runtime.sprites[0].resetFilters).toHaveBeenCalled(); + }); + + it('should call sprites.hideAsk', function() { + runtimeObj.prototype.stopAll(); + expect(runtime.sprites[0].hideAsk).toHaveBeenCalled(); + }); + + }); + +}); From e59b279be11bd20bdfce0b7cb4a8578d838f8102 Mon Sep 17 00:00:00 2001 From: Brian Pilati <brian.pilati@domo.com> Date: Sun, 9 Mar 2014 11:59:38 -0600 Subject: [PATCH 07/10] updates to finish doAsk --- js/Interpreter.js | 14 +++++ js/Reporter.js | 12 +++- js/Sprite.js | 23 ++++++-- js/Stage.js | 1 + js/primitives/LooksPrims.js | 18 +----- js/primitives/SensingPrims.js | 18 ++++++ test/artifacts/InterpreterMock.js | 3 +- test/artifacts/StageMock.js | 3 +- test/artifacts/TargetMock.js | 2 +- test/artifacts/TestHelpers.js | 5 ++ test/artifacts/ThreadMock.js | 1 + test/unit/interpreterSpec.js | 94 +++++++++++++++++++++++++++++++ test/unit/looksPrimitiveSpec.js | 24 ++------ test/unit/reporterSpec.js | 14 ++++- test/unit/runTimeSpec.js | 10 +++- test/unit/sensingPrimitiveSpec.js | 31 ++++++++++ test/unit/spriteSpec.js | 76 ++++++++++++++++++++----- test/unit/stageSpec.js | 4 ++ test/unit/threadSpec.js | 50 ++++++++++++++++ 19 files changed, 341 insertions(+), 62 deletions(-) create mode 100644 test/artifacts/TestHelpers.js create mode 100644 test/unit/interpreterSpec.js create mode 100644 test/unit/threadSpec.js diff --git a/js/Interpreter.js b/js/Interpreter.js index c566e0f..310599f 100644 --- a/js/Interpreter.js +++ b/js/Interpreter.js @@ -40,6 +40,7 @@ var Thread = function(block, target) { this.tmp = null; // used for thread operations like Timer this.tmpObj = []; // used for Sprite operations like glide this.firstTime = true; + this.paused = false; }; var Interpreter = function() { @@ -126,6 +127,13 @@ Interpreter.prototype.stepThreads = function() { } }; +Interpreter.prototype.pauseActiveThread = function() { + var self = this; + var timeoutId = setTimeout(function () { + (self.activeThread.paused) ? self.pauseActiveThread() : clearTimeout(timeoutId); + }, 1000); +} + Interpreter.prototype.stepActiveThread = function() { // Run the active thread until it yields. if (typeof(this.activeThread) == 'undefined') { @@ -135,6 +143,8 @@ Interpreter.prototype.stepActiveThread = function() { if (b == null) return; this.yield = false; while (true) { + if (this.activeThread.paused) return; + ++this.opCount; // Advance the "program counter" to the next block before running the primitive. // Control flow primitives (e.g. if) may change activeThread.nextBlock. @@ -247,6 +257,10 @@ Interpreter.prototype.targetSprite = function() { return this.activeThread.target; }; +Interpreter.prototype.targetStage = function() { + return runtime.stage; +}; + // Timer Interpreter.prototype.startTimer = function(secs) { var waitMSecs = 1000 * secs; diff --git a/js/Reporter.js b/js/Reporter.js index c55f0d2..f0f58f6 100644 --- a/js/Reporter.js +++ b/js/Reporter.js @@ -29,14 +29,19 @@ var Reporter = function(data) { this.y = data.y; this.z = io.getCount(); + //Set the label after hydrating the cmd and param variables + this.label = this.determineReporterLabel(); + this.el = null; // jQuery Element for the outer box this.valueEl = null; // jQ element containing the reporter value this.slider = null; // slider jQ element }; Reporter.prototype.determineReporterLabel = function() { - if (this.target === 'Stage') { + if (this.target === 'Stage' && this.cmd === "getVar:") { return this.param; + } else if (this.target === 'Stage' && this.param === null) { + return this.cmd; } else { return this.target + ': ' + this.param; } @@ -46,7 +51,7 @@ Reporter.prototype.attach = function(scene) { switch (this.mode) { case 1: // Normal case 3: // Slider - this.el = $('<div class="reporter-normal">' + this.determineReporterLabel() + '</div>'); + this.el = $('<div class="reporter-normal">' + this.label + '</div>'); this.valueEl = $('<div class="reporter-inset">null</div>'); this.el.append(this.valueEl); if (this.mode == 3) { @@ -84,6 +89,9 @@ Reporter.prototype.update = function() { var newValue = ''; var target = runtime.spriteNamed(this.target); switch (this.cmd) { + case 'answer': + newValue = target.askAnswer; + break; case 'getVar:': newValue = target.variables[this.param]; break; diff --git a/js/Sprite.js b/js/Sprite.js index 07d7621..b00065d 100644 --- a/js/Sprite.js +++ b/js/Sprite.js @@ -80,7 +80,6 @@ var Sprite = function(data) { this.askInputOn = false; // Internal variables used for rendering meshes. - this.askAnswer = null; //this is a private variable this.textures = []; this.materials = []; this.geometries = []; @@ -161,9 +160,10 @@ Sprite.prototype.attach = function(scene) { this.askInput = $('<div class="ask-container"></div>'); this.askInput.css('display', 'none'); this.askInputField = $('<div class="ask-field"></div>'); - this.askInputTextField = $('<input class="ask-text-field"></input>'); + this.askInputTextField = $('<input type="text" class="ask-text-field"></input>'); this.askInputField.append(this.askInputTextField); this.askInputButton = $('<div class="ask-button"></div>'); + this.bindDoAskButton(); this.askInput.append(this.askInputField); this.askInput.append(this.askInputButton); @@ -368,8 +368,8 @@ Sprite.prototype.showBubble = function(text, type) { this.talkBubble.css('left', xy[0] + 'px'); this.talkBubble.css('top', xy[1] + 'px'); - this.talkBubble.removeClass('say-think-border'); - this.talkBubble.removeClass('ask-border'); + this.talkBubbleBox.removeClass('say-think-border'); + this.talkBubbleBox.removeClass('ask-border'); this.talkBubbleStyler.removeClass('bubble-say'); this.talkBubbleStyler.removeClass('bubble-think'); @@ -412,9 +412,24 @@ Sprite.prototype.showAsk = function() { Sprite.prototype.hideAsk = function() { this.askInputOn = false; + this.askInputTextField.val(''); this.askInput.css('display', 'none'); }; +Sprite.prototype.bindDoAskButton = function() { + var self = this; + this.askInputButton.on("keypress click", function(e){ + var eType = e.type; + if (eType === 'click' || (eType === 'keypress' && e.which === 13)) { + var stage = interp.targetStage(); + stage.askAnswer = $(self.askInputTextField).val(); + self.hideBubble(); + self.hideAsk(); + interp.activeThread.paused = false; + } + }); +}; + Sprite.prototype.setXY = function(x, y) { this.scratchX = x; this.scratchY = y; diff --git a/js/Stage.js b/js/Stage.js index 8146e93..e433955 100644 --- a/js/Stage.js +++ b/js/Stage.js @@ -34,6 +34,7 @@ var Stage = function(data) { this.lineCanvas.height = 360; this.lineCache = this.lineCanvas.getContext('2d'); this.isStage = true; + this.askAnswer = ""; //this is a private variable and should be blank Sprite.call(this, data); }; diff --git a/js/primitives/LooksPrims.js b/js/primitives/LooksPrims.js index b63c7f2..47a7823 100644 --- a/js/primitives/LooksPrims.js +++ b/js/primitives/LooksPrims.js @@ -43,9 +43,6 @@ LooksPrims.prototype.addPrimsTo = function(primTable) { primTable['setGraphicEffect:to:'] = this.primSetEffect; primTable['filterReset'] = this.primClearEffects; - primTable['doAsk'] = this.primDoAsk; - primTable['answer'] = this.primAnswer; - primTable['say:'] = function(b) { showBubble(b, 'say'); }; primTable['say:duration:elapsed:from:'] = function(b) { showBubbleAndWait(b, 'say'); }; primTable['think:'] = function(b) { showBubble(b, 'think'); }; @@ -173,25 +170,14 @@ LooksPrims.prototype.primClearEffects = function(b) { s.updateFilters(); }; -LooksPrims.prototype.primDoAsk= function(b) { - showBubble(b, "doAsk"); - var s = interp.targetSprite(); - if (s != null) s.showAsk(); -}; - -LooksPrims.prototype.primAnswer = function() { - var s = interp.targetSprite(); - return ((s != null) ? s.answer : undefined); -}; - var showBubble = function(b, type) { var s = interp.targetSprite(); - if (s != null) s.showBubble(interp.arg(b, 0), type); + if (s !== null) s.showBubble(interp.arg(b, 0), type); }; var showBubbleAndWait = function(b, type) { var s = interp.targetSprite(); - if (s == null) return; + if (s === null) return; if (interp.activeThread.firstTime) { var text = interp.arg(b, 0); var secs = interp.numarg(b, 1); diff --git a/js/primitives/SensingPrims.js b/js/primitives/SensingPrims.js index 81e0414..325cbb5 100644 --- a/js/primitives/SensingPrims.js +++ b/js/primitives/SensingPrims.js @@ -22,6 +22,9 @@ SensingPrims.prototype.addPrimsTo = function(primTable) { primTable['touchingColor:'] = this.primTouchingColor; primTable['color:sees:'] = this.primColorTouchingColor; + primTable['doAsk'] = this.primDoAsk; + primTable['answer'] = this.primAnswer; + primTable['keyPressed:'] = this.primKeyPressed; primTable['mousePressed'] = function(b) { return runtime.mouseDown; }; primTable['mouseX'] = function(b) { return runtime.mousePos[0]; }; @@ -174,6 +177,21 @@ var stageColorByColorHitTest = function(target, myColor, otherColor) { return false; }; +SensingPrims.prototype.primDoAsk= function(b) { + showBubble(b, "doAsk"); + var s = interp.targetSprite(); + if (s !== null) { + interp.activeThread.paused = true; + s.showAsk(); + } +}; + +SensingPrims.prototype.primAnswer = function(b) { + var s = interp.targetStage(); + return (s !== null ? s.askAnswer : undefined); +}; + + SensingPrims.prototype.primKeyPressed = function(b) { var key = interp.arg(b, 0); var ch = key.charCodeAt(0); diff --git a/test/artifacts/InterpreterMock.js b/test/artifacts/InterpreterMock.js index f48749e..bd2058f 100644 --- a/test/artifacts/InterpreterMock.js +++ b/test/artifacts/InterpreterMock.js @@ -22,6 +22,7 @@ var interpreterMock = function() { return { 'targetSprite' : function() { return getArgs('targetSprite'); }, 'arg': function(block, index) { return getArgs('arg');}, - 'activeThread': undefined + 'activeThread': new threadMock(), + 'targetStage': function() { var rtMock = new runtimeMock(); return rtMock.stage} } }; diff --git a/test/artifacts/StageMock.js b/test/artifacts/StageMock.js index 3b76ced..2db827a 100644 --- a/test/artifacts/StageMock.js +++ b/test/artifacts/StageMock.js @@ -20,6 +20,7 @@ var stageMock = function() { } return { - 'resetFilters' : function() { return getArgs('resetFilters'); } + 'resetFilters' : function() { return getArgs('resetFilters'); }, + 'askAnswer' : 12 } }; diff --git a/test/artifacts/TargetMock.js b/test/artifacts/TargetMock.js index 8071a39..e017a9d 100644 --- a/test/artifacts/TargetMock.js +++ b/test/artifacts/TargetMock.js @@ -4,7 +4,7 @@ var targetMock = function() { return { 'showBubble' : function() {}, 'showAsk' : function() {}, - 'answer' : 22 + 'askAnswer' : 22 }; } diff --git a/test/artifacts/TestHelpers.js b/test/artifacts/TestHelpers.js new file mode 100644 index 0000000..d6fe08f --- /dev/null +++ b/test/artifacts/TestHelpers.js @@ -0,0 +1,5 @@ +'use strict'; + +var deepCopy = function(object) { + return jQuery.extend(true, {}, object); +} diff --git a/test/artifacts/ThreadMock.js b/test/artifacts/ThreadMock.js index 2d8dd30..fe506c3 100644 --- a/test/artifacts/ThreadMock.js +++ b/test/artifacts/ThreadMock.js @@ -20,5 +20,6 @@ var threadMock = function() { } return { + 'paused' : getArgs('paused') } }; diff --git a/test/unit/interpreterSpec.js b/test/unit/interpreterSpec.js new file mode 100644 index 0000000..802db99 --- /dev/null +++ b/test/unit/interpreterSpec.js @@ -0,0 +1,94 @@ +/* jasmine specs for Interpreter.js go here */ + +describe ('Interpreter', function() { + var interp; + + beforeEach(function() { + interp = Interpreter; + }); + + describe('Instantization variables', function() { + var initInterp, realThread, realTimer; + beforeEach(function() { + realThread = Thread; + realTimer = Timer; + Thread = threadMock; + Timer = function() {}; + initInterp = new interp(); + }); + + afterEach(function() { + Thread = realThread; + Timer = realTimer; + }); + + describe('Interpreter Variables', function() { + it('should have a primitiveTable collection', function() { + expect(initInterp.primitiveTable).toEqual({}); + }); + + it('should have a variables collection', function() { + expect(initInterp.variables).toEqual({}); + }); + + it('should have a threads array', function() { + expect(initInterp.threads).toEqual([]); + }); + + it('should have an activeThread variable', function() { + expect(initInterp.activeThread).toEqual(threadMock()); + }); + + it('should have a WorkTime variable', function() { + expect(initInterp.WorkTime).toBe(30); + }); + + it('should have a currentMSecs variable', function() { + expect(initInterp.currentMSecs).toBe(null); + }); + + it('should have a timer variable', function() { + expect(initInterp.timer).toEqual({}); + }); + + it('should have a yield variable', function() { + expect(initInterp.yield).toBe(false); + }); + + it('should have a doRedraw variable', function() { + expect(initInterp.doRedraw).toBe(false); + }); + + it('should have an opCount variable', function() { + expect(initInterp.opCount).toBe(0); + }); + + it('should have a debugOps variable', function() { + expect(initInterp.debugOps).toBe(false); + }); + + it('should have a debugFunc variable', function() { + expect(initInterp.debugFunc).toBe(null); + }); + + it('should have an opCount2 variable', function() { + expect(initInterp.opCount2).toBe(0); + }); + }); + }); + + describe('PauseActiveThread', function() { + it('should call clearTimeout', function() { + spyOn(window, "setTimeout"); + interp.prototype.pauseActiveThread(); + expect(window.setTimeout).toHaveBeenCalled(); + }); + }); + + describe('TargetStage', function() { + it('should return the target.stage object', function() { + runtime = new runtimeMock(); + expect(interp.prototype.targetStage()).toEqual(runtime.stage); + }); + }); +}); diff --git a/test/unit/looksPrimitiveSpec.js b/test/unit/looksPrimitiveSpec.js index a46f747..fab10d5 100644 --- a/test/unit/looksPrimitiveSpec.js +++ b/test/unit/looksPrimitiveSpec.js @@ -17,7 +17,7 @@ describe ('LooksPrims', function() { it('should call the showBubble method on the targetedSprite', function() { spyOn(targetSpriteMock, "showBubble"); showBubble(sayBlock, "say"); - expect(targetSpriteMock.showBubble).toHaveBeenCalled; + expect(targetSpriteMock.showBubble).toHaveBeenCalledWith({args:['what to say']}, 'say'); }); }); @@ -31,35 +31,21 @@ describe ('LooksPrims', function() { it('should call the showBubble method on the targetedSprite', function() { spyOn(targetSpriteMock, "showBubble"); showBubble(thinkBlock, "think"); - expect(targetSpriteMock.showBubble).toHaveBeenCalled; + expect(targetSpriteMock.showBubble).toHaveBeenCalledWith({args:['what to think']}, 'think'); }); }); - describe('showBubble for Ask', function(){ var askBlock; beforeEach(function() { - askBlock = {'args': 'what to ask'}; + askBlock = {'args': ['what to ask']}; interp = interpreterMock({'targetSprite': targetSpriteMock }, {'arg': askBlock}); - }); it('should call the showBubble method on the targetedSprite', function() { spyOn(targetSpriteMock, "showBubble"); - spyOn(targetSpriteMock, "showAsk"); - looksPrims.prototype.primDoAsk(askBlock); - expect(targetSpriteMock.showBubble).toHaveBeenCalled; - expect(targetSpriteMock.showAsk).toHaveBeenCalled; - }); - }); - - describe('primAnswer', function(){ - beforeEach(function() { - interp = interpreterMock({'targetSprite': targetSpriteMock }); - }); - - it('should return the answer variable from the targetedSprite', function() { - expect(looksPrims.prototype.primAnswer()).toBe(22); + showBubble(askBlock, "ask"); + expect(targetSpriteMock.showBubble).toHaveBeenCalledWith({args:['what to ask']}, 'ask'); }); }); }); diff --git a/test/unit/reporterSpec.js b/test/unit/reporterSpec.js index c879f58..7a51ebd 100644 --- a/test/unit/reporterSpec.js +++ b/test/unit/reporterSpec.js @@ -64,6 +64,10 @@ describe ('Reporter', function() { expect(initReporter.z).toBe(4); }); + it('should have a label variable', function() { + expect(initReporter.label).toBe('myAnswer'); + }); + it('should have an el variable', function() { expect(initReporter.el).toBe(null); }); @@ -77,19 +81,27 @@ describe ('Reporter', function() { }); }); }); - describe('determineReporterLabel', function() { + describe('determineReporterLabel', function() { it('should return a stage variable', function() { reporter.prototype.target = "Stage"; reporter.prototype.param = "myAnswer"; + reporter.prototype.cmd = "getVar:"; expect(reporter.prototype.determineReporterLabel()).toBe('myAnswer'); }); it('should return a sprite variable', function() { reporter.prototype.target = "Sprite 1"; reporter.prototype.param = "localAnswer"; + reporter.prototype.cmd = "getVar:"; expect(reporter.prototype.determineReporterLabel()).toBe('Sprite 1: localAnswer'); + }); + it('should return a stage answer variable', function() { + reporter.prototype.target = "Stage"; + reporter.prototype.param = null; + reporter.prototype.cmd = "answer"; + expect(reporter.prototype.determineReporterLabel()).toBe('answer'); }); }); diff --git a/test/unit/runTimeSpec.js b/test/unit/runTimeSpec.js index 906247b..e30f600 100644 --- a/test/unit/runTimeSpec.js +++ b/test/unit/runTimeSpec.js @@ -61,6 +61,7 @@ describe ('Runtime', function() { }); describe('Stop All', function() { + var realThread; beforeEach(function() { runtime = new runtimeMock spyOn(window, "stopAllSounds"); @@ -68,18 +69,23 @@ describe ('Runtime', function() { spyOn(runtime.sprites[0], "hideBubble"); spyOn(runtime.sprites[0], "resetFilters"); spyOn(runtime.sprites[0], "hideAsk"); + realThread = Thread; Thread = threadMock; interp = new interpreterMock(); }); + afterEach(function() { + Thread = realThread; + }); + it('should call a new Thread Object', function() { runtimeObj.prototype.stopAll(); - expect(interp.activeThread).toEqual({}); + expect(interp.activeThread).toEqual(new threadMock()); }); it('should call a blank thread array ', function() { runtimeObj.prototype.stopAll(); - expect(interp.activeThread).toEqual([]); + expect(interp.activeThread).toEqual(new threadMock()); }); it('should call stopAllSounds', function() { diff --git a/test/unit/sensingPrimitiveSpec.js b/test/unit/sensingPrimitiveSpec.js index b75eda1..4b3ac4d 100644 --- a/test/unit/sensingPrimitiveSpec.js +++ b/test/unit/sensingPrimitiveSpec.js @@ -78,4 +78,35 @@ describe ('SensingPrims', function() { expect(sensingPrims.prototype.primTimeDate(block)).toEqual(0); }); }); + + describe('primAnswer', function(){ + beforeEach(function() { + interp = interpreterMock({'targetSprite': new targetMock()}); + }); + + it('should return the answer variable from the targetedSprite', function() { + expect(sensingPrims.prototype.primAnswer()).toBe(12); + }); + }); + + describe('primDoAsk', function(){ + var askBlock, targetSpriteMock; + beforeEach(function() { + targetSpriteMock = targetMock(); + askBlock = {'args': 'what to ask'}; + interp = interpreterMock({'targetSprite': targetSpriteMock}, {'arg': askBlock}); + + }); + + it('should call the showBubble method on the targetedSprite', function() { + spyOn(window, "showBubble"); + spyOn(targetSpriteMock, "showAsk"); + sensingPrims.prototype.primDoAsk(askBlock); + expect(window.showBubble).toHaveBeenCalledWith({args:'what to ask'}, 'doAsk'); + expect(targetSpriteMock.showAsk).toHaveBeenCalled; + expect(interp.activeThread.paused).toBe(true); + }); + }); + + }); diff --git a/test/unit/spriteSpec.js b/test/unit/spriteSpec.js index feeb11c..5d5016d 100644 --- a/test/unit/spriteSpec.js +++ b/test/unit/spriteSpec.js @@ -3,7 +3,6 @@ describe ('Sprite', function() { var sprite; - beforeEach(function() { sprite = Sprite; }); @@ -59,17 +58,13 @@ describe ('Sprite', function() { it('should have an askInputOn variable', function() { expect(initSprite.askInputOn).toBe(false); }); - - it('should have an answer variable', function() { - expect(initSprite.askAnswer).toBe(null); - }); }); }) describe('showBubble', function() { var spriteProto; beforeEach(function() { - spriteProto = sprite.prototype; + spriteProto = deepCopy(sprite.prototype); spriteProto.visible = true; setFixtures('<div class="bubble-container"></div>'); spriteProto.talkBubble = $('.bubble-container'); @@ -133,7 +128,7 @@ describe ('Sprite', function() { describe('hideBubble', function() { var spriteProto; beforeEach(function() { - spriteProto = sprite.prototype; + spriteProto = deepCopy(sprite.prototype); setFixtures('<div class="bubble-container"></div>'); spriteProto.talkBubble = $('.bubble-container'); spriteProto.talkBubble.css('display', 'inline'); @@ -150,7 +145,7 @@ describe ('Sprite', function() { describe('showAsk', function() { var spriteProto; beforeEach(function() { - spriteProto = sprite.prototype; + spriteProto = deepCopy(sprite.prototype); spriteProto.visible = true; spriteProto.z = 22; setFixtures('<div class="ask-container"></div>'); @@ -158,7 +153,7 @@ describe ('Sprite', function() { spriteProto.askInput.css('display','none'); spriteProto.askInput.css('position','relative'); spriteProto.askInputField = $('<div class="ask-input"></div>'); - spriteProto.askInputTextField = $('<input class="ask-text-field"></input>'); + spriteProto.askInputTextField = $('<input type="text" class="ask-text-field"></input>'); spriteProto.askInputField.append(spriteProto.askInputTextField); spriteProto.askInputButton = $('<div class="ask-button"></div>'); spriteProto.askInput.append(spriteProto.askInputField); @@ -189,9 +184,11 @@ describe ('Sprite', function() { describe('hideAsk', function() { var spriteProto; beforeEach(function() { - spriteProto = sprite.prototype; + spriteProto = deepCopy(sprite.prototype); setFixtures('<div class="ask-container"></div>'); spriteProto.askInput = $('.ask-container'); + spriteProto.askInputTextField = $('<input type="text" class="ask-text-field"></input>'); + spriteProto.askInputTextField.val("Delete Me"); spriteProto.askInput.css('display', 'inline'); }); @@ -199,14 +196,63 @@ describe ('Sprite', function() { spriteProto.hideAsk(); expect($('.ask-container').css('display')).toBe('none'); expect(spriteProto.askInputOn).toBe(false); + expect(spriteProto.askInputTextField.val()).toBe(''); + }); + }); + describe('bindAsk', function() { + beforeEach(function() { + spriteProto = deepCopy(sprite.prototype); + spriteProto.askInputTextField = $('<input type="text" class="ask-text-field"></input>'); + spriteProto.askInputButton = $('<div class="ask-button"></div>'); + spyOn(spriteProto, "hideBubble"); + spyOn(spriteProto, "hideAsk"); + }); + + it('should bind to the askInputButton and handle a click', function() { + interp = new interpreterMock(); + spyOn(interp, "targetStage").andCallThrough(); + $(spriteProto.askInputTextField).val('Hellow World'); + spriteProto.bindDoAskButton(); + $(spriteProto.askInputButton).click(); + expect(interp.targetStage).toHaveBeenCalled(); + }); + + it('should bind to the askInputButton and handle a enter/return', function() { + interp = new interpreterMock(); + spyOn(interp, "targetStage").andCallThrough(); + spriteProto.bindDoAskButton(); + var e = $.Event( "keypress", { which: 13 } ); + $(spriteProto.askInputButton).trigger(e); + expect(interp.targetStage).toHaveBeenCalled(); + }); + + + it('should call hideBubble', function() { + spriteProto.bindDoAskButton(); + $(spriteProto.askInputButton).click(); + expect(spriteProto.hideBubble).toHaveBeenCalled(); + expect(spriteProto.hideAsk).toHaveBeenCalled(); + }); + + it('should call hideAsk', function() { + spriteProto.bindDoAskButton(); + $(spriteProto.askInputButton).click(); + expect(spriteProto.hideAsk).toHaveBeenCalled(); + }); + + it('should have interp.activeThread.paused be false', function() { + interp = new interpreterMock(); + spriteProto.bindDoAskButton(); + $(spriteProto.askInputButton).click(); + expect(interp.activeThread.paused).toBe(false); }); }); describe('updateLayer', function() { var spriteProto; beforeEach(function() { - spriteProto = sprite.prototype; + spriteProto = deepCopy(sprite.prototype); setFixtures('<img class="mesh"></img><div class="bubble-container"></div><div class="ask-container"></div>'); spriteProto.talkBubble = $('.bubble-container'); spriteProto.talkBubble.css('position', 'relative'); @@ -239,7 +285,7 @@ describe ('Sprite', function() { describe('updateVisible', function() { var spriteProto; beforeEach(function() { - spriteProto = sprite.prototype; + spriteProto = deepCopy(sprite.prototype); setFixtures('<img class="mesh"></img><div class="bubble-container"></div><div class="ask-container"></div>'); spriteProto.talkBubble = $('.bubble-container'); spriteProto.talkBubble.css('display', 'none'); @@ -323,12 +369,12 @@ describe ('Sprite', function() { describe('setVisible', function() { var spriteProto; beforeEach(function() { - spriteProto = sprite.prototype; + spriteProto = deepCopy(sprite.prototype); spyOn(spriteProto, "updateVisible"); }); it('should set visible to true', function() { - expect(spriteProto.visible).toBe(false); + expect(spriteProto.visible).toBe(undefined); spriteProto.setVisible(true); expect(spriteProto.visible).toBe(true); expect(spriteProto.updateVisible).toHaveBeenCalled(); @@ -347,6 +393,6 @@ describe ('Sprite', function() { expect(spriteProto.visible).toBe(true); expect(spriteProto.updateVisible).not.toHaveBeenCalled(); }); - }); + }); diff --git a/test/unit/stageSpec.js b/test/unit/stageSpec.js index 330a451..32a7d2e 100644 --- a/test/unit/stageSpec.js +++ b/test/unit/stageSpec.js @@ -43,6 +43,10 @@ describe ('Stage', function() { expect(initStage.isStage).toBe(true); }); + it('should have an askAnswer variable', function() { + expect(initStage.askAnswer).toBe(""); + }); + it('should have called Sprite.call', function() { expect(Sprite.call).toHaveBeenCalled(); }); diff --git a/test/unit/threadSpec.js b/test/unit/threadSpec.js new file mode 100644 index 0000000..ab7f24b --- /dev/null +++ b/test/unit/threadSpec.js @@ -0,0 +1,50 @@ +/* jasmine specs for Interpreter.js -> Thread go here */ + +describe ('Thread', function() { + var thread; + + beforeEach(function() { + thread = Thread; + }); + + describe('Instantization variables', function() { + var initThread; + beforeEach(function() { + initThread = new thread('block', 'target'); + }); + + describe('Thread Variables', function() { + it('should have a nextBlock variable', function() { + expect(initThread.nextBlock).toBe('block'); + }); + + it('should have a firstBlock variable', function() { + expect(initThread.firstBlock).toBe('block'); + }); + + it('should have a stack variable', function() { + expect(initThread.stack).toEqual([]); + }); + + it('should have a target variable', function() { + expect(initThread.target).toBe('target'); + }); + + it('should have a tmp variable', function() { + expect(initThread.tmp).toBe(null); + }); + + it('should have a tmpObj variable', function() { + expect(initThread.tmpObj).toEqual([]); + }); + + it('should have a firstTime variable', function() { + expect(initThread.firstTime).toBe(true); + }); + + it('should have a paused variable', function() { + expect(initThread.paused).toBe(false); + }); + }); + }); +}); From b6e9ecf0b5c2ece5c6ff54491dbe5f5c743c3464 Mon Sep 17 00:00:00 2001 From: Brian Pilati <brian.pilati@domo.com> Date: Sun, 9 Mar 2014 12:00:49 -0600 Subject: [PATCH 08/10] oops forgot to delete an unneeded method --- js/Interpreter.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/js/Interpreter.js b/js/Interpreter.js index 310599f..077ba16 100644 --- a/js/Interpreter.js +++ b/js/Interpreter.js @@ -127,13 +127,6 @@ Interpreter.prototype.stepThreads = function() { } }; -Interpreter.prototype.pauseActiveThread = function() { - var self = this; - var timeoutId = setTimeout(function () { - (self.activeThread.paused) ? self.pauseActiveThread() : clearTimeout(timeoutId); - }, 1000); -} - Interpreter.prototype.stepActiveThread = function() { // Run the active thread until it yields. if (typeof(this.activeThread) == 'undefined') { From 365e93f16a31d757f10a8c406971703013e8d0ea Mon Sep 17 00:00:00 2001 From: Brian Pilati <brian.pilati@domo.com> Date: Mon, 10 Mar 2014 10:16:31 -0600 Subject: [PATCH 09/10] some final touches to the doAsk/answer project --- README.md | 2 ++ test/unit/interpreterSpec.js | 8 -------- todo.txt | 2 -- 3 files changed, 2 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 909bf5c..45474e9 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,8 @@ Mac Ports: $ port install npm ``` +In your local scratch directory + ``` $ npm install ``` diff --git a/test/unit/interpreterSpec.js b/test/unit/interpreterSpec.js index 802db99..7ad7ed4 100644 --- a/test/unit/interpreterSpec.js +++ b/test/unit/interpreterSpec.js @@ -77,14 +77,6 @@ describe ('Interpreter', function() { }); }); - describe('PauseActiveThread', function() { - it('should call clearTimeout', function() { - spyOn(window, "setTimeout"); - interp.prototype.pauseActiveThread(); - expect(window.setTimeout).toHaveBeenCalled(); - }); - }); - describe('TargetStage', function() { it('should return the target.stage object', function() { runtime = new runtimeMock(); diff --git a/todo.txt b/todo.txt index 856d91f..c8bf282 100644 --- a/todo.txt +++ b/todo.txt @@ -15,8 +15,6 @@ To do: -Trigger hats (e.g. When Scene Starts?) - -Ask block - -SVGs/Clicks/Correct collisions and bounce (see Chromium bug https://code.google.com/p/chromium/issues/detail?id=249037). From 6e6524e4813131293c5d385383b86ec2cb7e3a08 Mon Sep 17 00:00:00 2001 From: Brian Pilati <brian.pilati@domo.com> Date: Mon, 10 Mar 2014 21:04:56 -0600 Subject: [PATCH 10/10] updates to elevate my code to scratch standards --- README.md | 5 +- compare.html | 2 +- js/Sprite.js | 8 ++- test/artifacts/reporterValues.js | 90 ++++++++----------------------- test/unit/interpreterSpec.js | 4 +- test/unit/ioSpec.js | 4 -- test/unit/looksPrimitiveSpec.js | 8 +-- test/unit/reporterSpec.js | 4 +- test/unit/runTimeSpec.js | 8 +-- test/unit/scratchSpec.js | 35 ++++++------ test/unit/sensingPrimitiveSpec.js | 2 +- test/unit/spriteSpec.js | 12 +---- test/unit/stageSpec.js | 4 +- test/unit/threadSpec.js | 4 +- 14 files changed, 68 insertions(+), 122 deletions(-) diff --git a/README.md b/README.md index 45474e9..099de17 100644 --- a/README.md +++ b/README.md @@ -47,13 +47,12 @@ In your local scratch directory $ npm install ``` -Local copy of jQuery and mock-ajax ----------------------------------- +Local copy of jQuery +-------------------- ``` $ cd test/lib $ curl http://code.jquery.com/jquery-1.11.0.min.js > jquery-1.11.0.min.js -$ curl http://cloud.github.com/downloads/pivotal/jasmine-ajax/mock-ajax.js > mock-ajax.js ``` To Run the tests diff --git a/compare.html b/compare.html index b538775..0f3a26a 100644 --- a/compare.html +++ b/compare.html @@ -9,7 +9,7 @@ <script src=//ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js></script> -<script type="text/javascript"> +<script> $(document).ready(function() { var project_id = location.hash && parseInt(location.hash.substr(1)) || 10000160; var scratch = new Scratch(project_id); diff --git a/js/Sprite.js b/js/Sprite.js index b00065d..3a376f7 100644 --- a/js/Sprite.js +++ b/js/Sprite.js @@ -273,15 +273,13 @@ Sprite.prototype.onClick = function(evt) { }; Sprite.prototype.setVisible = function(v) { - if (v === true || v === false) { - this.visible = v; - this.updateVisible(); - } + this.visible = v; + this.updateVisible(); }; Sprite.prototype.updateLayer = function() { $(this.mesh).css('z-index', this.z); - if (this.talkBubble) $(this.talkBubble).css('z-index', this.z); + if (this.talkBubble) this.talkBubble.css('z-index', this.z); if (this.askInput) this.askInput.css('z-index', this.z); }; diff --git a/test/artifacts/reporterValues.js b/test/artifacts/reporterValues.js index 484b990..8fdc1b4 100644 --- a/test/artifacts/reporterValues.js +++ b/test/artifacts/reporterValues.js @@ -20,72 +20,28 @@ var ReporterValues = function() { } }; /* -cmd - "getVar:" +Additional Reporter examples +cmd : "getVar:" +color : 15629590 +isDiscrete : true +mode : 1 +param : "myAnswer2" +sliderMax : 100 +sliderMin : 0 +target : "Sprite1" +visible : true +x : 5 +y : 32 -color - 15629590 - -isDiscrete - true - -mode - 1 - -param - "myAnswer2" - -sliderMax - 100 - -sliderMin - 0 - -target - "Sprite1" - -visible - true - -x - 5 - -y - 32 -*/ - -/* - -cmd - "getVar:" - -color - 15629590 - -isDiscrete - true - -mode - 1 - -param - "answer" - -sliderMax - 100 - -sliderMin - 0 - -target - "Sprite1" - -visible - true - -x - 5 - -y - 59 +cmd : "getVar:" +color : 15629590 +isDiscrete : true +mode : 1 +param : "answer" +sliderMax : 100 +sliderMin : 0 +target : "Sprite1" +visible : true +x : 5 +y : 59 */ diff --git a/test/unit/interpreterSpec.js b/test/unit/interpreterSpec.js index 7ad7ed4..0aa3468 100644 --- a/test/unit/interpreterSpec.js +++ b/test/unit/interpreterSpec.js @@ -1,13 +1,13 @@ /* jasmine specs for Interpreter.js go here */ -describe ('Interpreter', function() { +describe('Interpreter', function() { var interp; beforeEach(function() { interp = Interpreter; }); - describe('Instantization variables', function() { + describe('Initialized variables', function() { var initInterp, realThread, realTimer; beforeEach(function() { realThread = Thread; diff --git a/test/unit/ioSpec.js b/test/unit/ioSpec.js index f986bc8..c869bdb 100644 --- a/test/unit/ioSpec.js +++ b/test/unit/ioSpec.js @@ -13,10 +13,6 @@ describe('IO', function(){ expect(io.data).toBe(null); }); - it('should have "null" data', function() { - expect(io.data).toBe(null); - }); - it('should have a base', function() { expect(io.base).toBe(io_base); }); diff --git a/test/unit/looksPrimitiveSpec.js b/test/unit/looksPrimitiveSpec.js index fab10d5..86a5777 100644 --- a/test/unit/looksPrimitiveSpec.js +++ b/test/unit/looksPrimitiveSpec.js @@ -1,13 +1,13 @@ /* jasmine specs for primitives/LooksPrims.js go here */ -describe ('LooksPrims', function() { +describe('LooksPrims', function() { var looksPrims, targetSpriteMock; beforeEach(function() { looksPrims = LooksPrims; targetSpriteMock = targetMock(); }); - describe('showBubble for Say', function(){ + describe('showBubble for say', function(){ var sayBlock; beforeEach(function() { sayBlock = {'args': ['what to say']}; @@ -21,7 +21,7 @@ describe ('LooksPrims', function() { }); }); - describe('showBubble for Think', function(){ + describe('showBubble for think', function(){ var thinkBlock; beforeEach(function() { thinkBlock = {'args': ['what to think']}; @@ -35,7 +35,7 @@ describe ('LooksPrims', function() { }); }); - describe('showBubble for Ask', function(){ + describe('showBubble for ask', function(){ var askBlock; beforeEach(function() { askBlock = {'args': ['what to ask']}; diff --git a/test/unit/reporterSpec.js b/test/unit/reporterSpec.js index 7a51ebd..4eb36ac 100644 --- a/test/unit/reporterSpec.js +++ b/test/unit/reporterSpec.js @@ -1,6 +1,6 @@ /* jasmine specs for Reporter.js go here */ -describe ('Reporter', function() { +describe('Reporter', function() { var reporter, reporterValues; beforeEach(function() { @@ -8,7 +8,7 @@ describe ('Reporter', function() { reporterValues = new ReporterValues(); }); - describe('Instantization variables', function() { + describe('Initialized variables', function() { var initReporter; beforeEach(function() { io = new ioMock({'getCount': 4}); diff --git a/test/unit/runTimeSpec.js b/test/unit/runTimeSpec.js index e30f600..16b7d6c 100644 --- a/test/unit/runTimeSpec.js +++ b/test/unit/runTimeSpec.js @@ -1,13 +1,13 @@ /* jasmine specs for Runtime.js go here */ -describe ('Runtime', function() { +describe('Runtime', function() { var runtimeObj; beforeEach(function() { runtimeObj = Runtime; }); - describe('Instantization variables', function() { + describe('Initialized variables', function() { var initRuntime, lineCanvas; beforeEach(function() { initRuntime = new runtimeObj(); @@ -83,9 +83,9 @@ describe ('Runtime', function() { expect(interp.activeThread).toEqual(new threadMock()); }); - it('should call a blank thread array ', function() { + it('should intitialize an empty threads array', function() { runtimeObj.prototype.stopAll(); - expect(interp.activeThread).toEqual(new threadMock()); + expect(interp.threads).toEqual([]); }); it('should call stopAllSounds', function() { diff --git a/test/unit/scratchSpec.js b/test/unit/scratchSpec.js index 0852e46..e7213ce 100644 --- a/test/unit/scratchSpec.js +++ b/test/unit/scratchSpec.js @@ -1,30 +1,36 @@ /* jasmine specs for Scratch.js go here */ -describe ('Scratch', function() { +describe('Scratch', function() { + var scratch; + + beforeEach(function() { + spyOn(IO.prototype, "loadProject"); + spyOn(Runtime.prototype, "init"); + spyOn(Interpreter.prototype, "initPrims"); + scratch = Scratch; + }); + describe('Scratch - Load Project', function(){ - var request, scratch; - var callBack = jasmine.createSpy('onSuccess'); - - var TestResponses = { status: 200, responseText: returnData}; - beforeEach(function() { - jasmine.Ajax.useMock(); - scratch = Scratch; scratch(project_id); - request = mostRecentAjaxRequest(); - request.promise(TestResponses, callBack); }); - it('should call the internalapi project', function() { - expect(request.url).toBe("proxy.php?resource=internalapi/project/" + project_id + "/get/"); - expect(callBack).toHaveBeenCalled(); + it('should call the IO loadProject Method', function() { + expect(IO.prototype.loadProject).toHaveBeenCalled(); + }); + + it('should call the Runtime init method', function() { + expect(Runtime.prototype.init).toHaveBeenCalled(); + }); + + it('should call the Interpreter initPrims method', function() { + expect(Interpreter.prototype.initPrims).toHaveBeenCalled(); }); }); describe('Scratch - Click Green Flag', function(){ beforeEach(function() { setFixtures('<button id=trigger-green-flag tabindex=2></button><div id="overlay"></div>'); - scratch = Scratch; scratch(project_id); }); @@ -48,7 +54,6 @@ describe ('Scratch', function() { describe('Scratch - Click Stop', function(){ beforeEach(function() { setFixtures('<button id=trigger-stop tabindex=3></button>'); - scratch = Scratch; scratch(project_id); }); diff --git a/test/unit/sensingPrimitiveSpec.js b/test/unit/sensingPrimitiveSpec.js index 4b3ac4d..570f771 100644 --- a/test/unit/sensingPrimitiveSpec.js +++ b/test/unit/sensingPrimitiveSpec.js @@ -1,6 +1,6 @@ /* jasmine specs for primitives/SensingPrims.js go here */ -describe ('SensingPrims', function() { +describe('SensingPrims', function() { var sensingPrims; beforeEach(function() { sensingPrims = SensingPrims; diff --git a/test/unit/spriteSpec.js b/test/unit/spriteSpec.js index 5d5016d..ebb867b 100644 --- a/test/unit/spriteSpec.js +++ b/test/unit/spriteSpec.js @@ -1,13 +1,13 @@ /* jasmine specs for Sprite.js go here */ -describe ('Sprite', function() { +describe('Sprite', function() { var sprite; beforeEach(function() { sprite = Sprite; }); - describe('Instantization variables', function() { + describe('Initialized variables', function() { var initSprite; beforeEach(function() { var spriteObject = sensingData.children[0]; @@ -386,13 +386,5 @@ describe ('Sprite', function() { expect(spriteProto.visible).toBe(false); expect(spriteProto.updateVisible).toHaveBeenCalled(); }); - - it('should set take no action on an invalid character', function() { - spriteProto.visible = true; - spriteProto.setVisible('hello'); - expect(spriteProto.visible).toBe(true); - expect(spriteProto.updateVisible).not.toHaveBeenCalled(); - }); }); - }); diff --git a/test/unit/stageSpec.js b/test/unit/stageSpec.js index 32a7d2e..a6ee5e1 100644 --- a/test/unit/stageSpec.js +++ b/test/unit/stageSpec.js @@ -1,13 +1,13 @@ /* jasmine specs for Stage.js go here */ -describe ('Stage', function() { +describe('Stage', function() { var stage; beforeEach(function() { stage = Stage; }); - describe('Instantization variables', function() { + describe('Initialized variables', function() { var initStage, lineCanvas; beforeEach(function() { spyOn(Sprite, "call"); diff --git a/test/unit/threadSpec.js b/test/unit/threadSpec.js index ab7f24b..556f8e5 100644 --- a/test/unit/threadSpec.js +++ b/test/unit/threadSpec.js @@ -1,13 +1,13 @@ /* jasmine specs for Interpreter.js -> Thread go here */ -describe ('Thread', function() { +describe('Thread', function() { var thread; beforeEach(function() { thread = Thread; }); - describe('Instantization variables', function() { + describe('Initialized variables', function() { var initThread; beforeEach(function() { initThread = new thread('block', 'target');