diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1d5cbb2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +*.swp +test/lib/* +.DS_Store + +logs + +npm-debug.log +node_modules diff --git a/README.md b/README.md index 5777743..099de17 100644 --- a/README.md +++ b/README.md @@ -19,3 +19,50 @@ 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 +``` + +In your local scratch directory + +``` +$ npm install +``` + +Local copy of jQuery +-------------------- + +``` +$ cd test/lib +$ curl http://code.jquery.com/jquery-1.11.0.min.js > jquery-1.11.0.min.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/compare.html b/compare.html index 61ef77c..0f3a26a 100644 --- a/compare.html +++ b/compare.html @@ -8,35 +8,17 @@ - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + diff --git a/config/karma.conf.js b/config/karma.conf.js new file mode 100644 index 0000000..5c66717 --- /dev/null +++ b/config/karma.conf.js @@ -0,0 +1,37 @@ +module.exports = function(config){ + config.set({ + basePath : '../', + + files : [ + 'test/artifacts/**/*.js', + 'test/lib/**/*.js', + 'test/unit/**/*.js', + 'js/sound/SoundDecoder.js', + 'js/sound/**/*.js', + 'js/util/**/*.js', + 'js/**/*.js', + 'node_modules/jasmine-jquery/lib/jasmine-jquery.js', + 'node_modules/underscore/underscore.js' + ], + + exclude : [ + ], + + preprocessors: { + '*.html': ['html2js'] + }, + + autoWatch : true, + + frameworks: ['jasmine'], + + browsers : ['Chrome'], + + plugins : [ + 'karma-jasmine', + 'jasmine-jquery', + 'karma-html2js-preprocessor', + 'karma-chrome-launcher', + 'karma-firefox-launcher' + ] +})} diff --git a/img/ask-bottom.png b/img/ask-bottom.png new file mode 100644 index 0000000..9823a43 Binary files /dev/null and b/img/ask-bottom.png differ diff --git a/img/ask-button.png b/img/ask-button.png new file mode 100644 index 0000000..713927a Binary files /dev/null and b/img/ask-button.png differ diff --git a/img/playerStartFlag.png b/img/playerStartFlag.png index 11a8e72..f2211d1 100644 Binary files a/img/playerStartFlag.png and b/img/playerStartFlag.png differ diff --git a/index.html b/index.html index 8f25615..71f68b2 100755 --- a/index.html +++ b/index.html @@ -12,31 +12,12 @@ - - - - - - - - - - - - - - - - - - - - - - -
The Scratch HTML5 player is still in development. Feedback is welcome! Please report any bugs (or differences from the Flash player) on Github.
+ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/js/Interpreter.js b/js/Interpreter.js index c566e0f..077ba16 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() { @@ -135,6 +136,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 +250,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 a6acd66..f0f58f6 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; @@ -30,11 +29,24 @@ 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' && this.cmd === "getVar:") { + return this.param; + } else if (this.target === 'Stage' && this.param === null) { + return this.cmd; + } else { + return this.target + ': ' + this.param; + } +} + Reporter.prototype.attach = function(scene) { switch (this.mode) { case 1: // Normal @@ -77,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/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/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/js/Sprite.js b/js/Sprite.js index 026d17e..3a376f7 100644 --- a/js/Sprite.js +++ b/js/Sprite.js @@ -73,6 +73,12 @@ 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.textures = []; this.materials = []; @@ -142,14 +148,27 @@ Sprite.prototype.attach = function(scene) { this.updateVisible(); this.updateTransform(); - this.talkBubble = $(''); - this.talkBubble.css('display', 'none'); - this.talkBubbleBox = $(''); - this.talkBubbleStyler = $(''); - this.talkBubble.append(this.talkBubbleBox); - this.talkBubble.append(this.talkBubbleStyler); + if (! this.isStage) { + this.talkBubble = $(''); + this.talkBubble.css('display', 'none'); + this.talkBubbleBox = $(''); + this.talkBubbleStyler = $(''); + this.talkBubble.append(this.talkBubbleBox); + this.talkBubble.append(this.talkBubbleStyler); + } + + this.askInput = $(''); + this.askInput.css('display', 'none'); + this.askInputField = $(''); + this.askInputTextField = $(''); + this.askInputField.append(this.askInputTextField); + this.askInputButton = $(''); + this.bindDoAskButton(); + 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,20 @@ Sprite.prototype.onClick = function(evt) { }; Sprite.prototype.setVisible = function(v) { - 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.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 +366,21 @@ Sprite.prototype.showBubble = function(text, type) { this.talkBubble.css('left', xy[0] + 'px'); this.talkBubble.css('top', xy[1] + 'px'); - + this.talkBubbleBox.removeClass('say-think-border'); + this.talkBubbleBox.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 +394,40 @@ 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.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 c425e10..e433955 100644 --- a/js/Stage.js +++ b/js/Stage.js @@ -33,6 +33,8 @@ var Stage = function(data) { this.lineCanvas.width = 480; 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 e56f646..47a7823 100644 --- a/js/primitives/LooksPrims.js +++ b/js/primitives/LooksPrims.js @@ -172,12 +172,12 @@ LooksPrims.prototype.primClearEffects = function(b) { 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/package.json b/package.json new file mode 100644 index 0000000..7a6ba49 --- /dev/null +++ b/package.json @@ -0,0 +1,11 @@ +{ + "name": "scratch-html5", + "description": "HTML 5 based Scratch project player", + "repository": "https://github.com/LLK/scratch-html5", + "devDependencies": { + "karma" : "~0.10", + "jasmine-jquery" : "1.3.3", + "karma-html2js-preprocessor" : "~0.1.0", + "underscore" : "~1.6.0" + } +} 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/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/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/InterpreterMock.js b/test/artifacts/InterpreterMock.js new file mode 100644 index 0000000..bd2058f --- /dev/null +++ b/test/artifacts/InterpreterMock.js @@ -0,0 +1,28 @@ +'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');}, + 'activeThread': new threadMock(), + 'targetStage': function() { var rtMock = new runtimeMock(); return rtMock.stage} + } +}; 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..2db827a --- /dev/null +++ b/test/artifacts/StageMock.js @@ -0,0 +1,26 @@ +'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'); }, + 'askAnswer' : 12 + } +}; diff --git a/test/artifacts/TargetMock.js b/test/artifacts/TargetMock.js new file mode 100644 index 0000000..e017a9d --- /dev/null +++ b/test/artifacts/TargetMock.js @@ -0,0 +1,10 @@ +'use strict'; + +var targetMock = function() { + return { + 'showBubble' : function() {}, + 'showAsk' : function() {}, + '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 new file mode 100644 index 0000000..fe506c3 --- /dev/null +++ b/test/artifacts/ThreadMock.js @@ -0,0 +1,25 @@ +'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 { + 'paused' : getArgs('paused') + } +}; diff --git a/test/artifacts/ask-artifact.js b/test/artifacts/ask-artifact.js new file mode 100644 index 0000000..654a07d --- /dev/null +++ b/test/artifacts/ask-artifact.js @@ -0,0 +1,67 @@ +var sensingData = { + "objName": "Stage", + "variables": [{ + "name": "myAnswer", + "value": 0, + "isPersistent": false + }], + "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", + "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, + [["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/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/reporterValues.js b/test/artifacts/reporterValues.js new file mode 100644 index 0000000..8fdc1b4 --- /dev/null +++ b/test/artifacts/reporterValues.js @@ -0,0 +1,47 @@ +'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, + }; + } + } +}; +/* +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 + +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/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/interpreterSpec.js b/test/unit/interpreterSpec.js new file mode 100644 index 0000000..0aa3468 --- /dev/null +++ b/test/unit/interpreterSpec.js @@ -0,0 +1,86 @@ +/* jasmine specs for Interpreter.js go here */ + +describe('Interpreter', function() { + var interp; + + beforeEach(function() { + interp = Interpreter; + }); + + describe('Initialized 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('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/ioSpec.js b/test/unit/ioSpec.js new file mode 100644 index 0000000..c869bdb --- /dev/null +++ b/test/unit/ioSpec.js @@ -0,0 +1,43 @@ +'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 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/looksPrimitiveSpec.js b/test/unit/looksPrimitiveSpec.js new file mode 100644 index 0000000..86a5777 --- /dev/null +++ b/test/unit/looksPrimitiveSpec.js @@ -0,0 +1,51 @@ +/* jasmine specs for primitives/LooksPrims.js go here */ + +describe('LooksPrims', function() { + var looksPrims, targetSpriteMock; + beforeEach(function() { + looksPrims = LooksPrims; + targetSpriteMock = targetMock(); + }); + + describe('showBubble for say', function(){ + var sayBlock; + beforeEach(function() { + sayBlock = {'args': ['what to say']}; + interp = interpreterMock({'targetSprite': targetSpriteMock }, {'arg': sayBlock}); + }); + + it('should call the showBubble method on the targetedSprite', function() { + spyOn(targetSpriteMock, "showBubble"); + showBubble(sayBlock, "say"); + expect(targetSpriteMock.showBubble).toHaveBeenCalledWith({args:['what to say']}, 'say'); + }); + }); + + 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).toHaveBeenCalledWith({args:['what to think']}, 'think'); + }); + }); + + 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"); + showBubble(askBlock, "ask"); + expect(targetSpriteMock.showBubble).toHaveBeenCalledWith({args:['what to ask']}, 'ask'); + }); + }); +}); diff --git a/test/unit/reporterSpec.js b/test/unit/reporterSpec.js new file mode 100644 index 0000000..4eb36ac --- /dev/null +++ b/test/unit/reporterSpec.js @@ -0,0 +1,108 @@ +/* jasmine specs for Reporter.js go here */ + +describe('Reporter', function() { + var reporter, reporterValues; + + beforeEach(function() { + reporter = Reporter; + reporterValues = new ReporterValues(); + }); + + describe('Initialized 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 a label variable', function() { + expect(initReporter.label).toBe('myAnswer'); + }); + + 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"; + 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 new file mode 100644 index 0000000..16b7d6c --- /dev/null +++ b/test/unit/runTimeSpec.js @@ -0,0 +1,113 @@ +/* jasmine specs for Runtime.js go here */ + +describe('Runtime', function() { + var runtimeObj; + + beforeEach(function() { + runtimeObj = Runtime; + }); + + describe('Initialized 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() { + var realThread; + 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"); + 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(new threadMock()); + }); + + it('should intitialize an empty threads array', function() { + runtimeObj.prototype.stopAll(); + expect(interp.threads).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(); + }); + + }); + +}); diff --git a/test/unit/scratchSpec.js b/test/unit/scratchSpec.js new file mode 100644 index 0000000..e7213ce --- /dev/null +++ b/test/unit/scratchSpec.js @@ -0,0 +1,66 @@ +/* jasmine specs for Scratch.js go here */ + +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(){ + beforeEach(function() { + scratch(project_id); + }); + + 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(''); + 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(''); + 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(); + }); + }); +}); diff --git a/test/unit/sensingPrimitiveSpec.js b/test/unit/sensingPrimitiveSpec.js new file mode 100644 index 0000000..570f771 --- /dev/null +++ b/test/unit/sensingPrimitiveSpec.js @@ -0,0 +1,112 @@ +/* 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); + }); + }); + + 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 new file mode 100644 index 0000000..ebb867b --- /dev/null +++ b/test/unit/spriteSpec.js @@ -0,0 +1,390 @@ +/* jasmine specs for Sprite.js go here */ + +describe('Sprite', function() { + var sprite; + + beforeEach(function() { + sprite = Sprite; + }); + + describe('Initialized 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); + }); + }); + }) + + describe('showBubble', function() { + var spriteProto; + beforeEach(function() { + spriteProto = deepCopy(sprite.prototype); + spriteProto.visible = true; + setFixtures(''); + spriteProto.talkBubble = $('.bubble-container'); + spriteProto.talkBubble.css('display', 'none'); + spriteProto.talkBubbleBox = $(''); + spriteProto.talkBubbleStyler = $(''); + 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 = deepCopy(sprite.prototype); + setFixtures(''); + 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 = deepCopy(sprite.prototype); + spriteProto.visible = true; + spriteProto.z = 22; + setFixtures(''); + spriteProto.askInput= $('.ask-container'); + spriteProto.askInput.css('display','none'); + spriteProto.askInput.css('position','relative'); + spriteProto.askInputField = $(''); + spriteProto.askInputTextField = $(''); + spriteProto.askInputField.append(spriteProto.askInputTextField); + spriteProto.askInputButton = $(''); + 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 = deepCopy(sprite.prototype); + setFixtures(''); + spriteProto.askInput = $('.ask-container'); + spriteProto.askInputTextField = $(''); + spriteProto.askInputTextField.val("Delete Me"); + 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); + expect(spriteProto.askInputTextField.val()).toBe(''); + }); + }); + + describe('bindAsk', function() { + beforeEach(function() { + spriteProto = deepCopy(sprite.prototype); + spriteProto.askInputTextField = $(''); + spriteProto.askInputButton = $(''); + 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 = deepCopy(sprite.prototype); + setFixtures(''); + 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 = deepCopy(sprite.prototype); + setFixtures(''); + 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 = deepCopy(sprite.prototype); + spyOn(spriteProto, "updateVisible"); + }); + + it('should set visible to true', function() { + expect(spriteProto.visible).toBe(undefined); + 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(); + }); + }); +}); diff --git a/test/unit/stageSpec.js b/test/unit/stageSpec.js new file mode 100644 index 0000000..a6ee5e1 --- /dev/null +++ b/test/unit/stageSpec.js @@ -0,0 +1,55 @@ +/* jasmine specs for Stage.js go here */ + +describe('Stage', function() { + var stage; + + beforeEach(function() { + stage = Stage; + }); + + describe('Initialized 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 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..556f8e5 --- /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('Initialized 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); + }); + }); + }); +}); 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).