├── .gitignore ├── Gruntfile.js ├── README.md ├── bower.json ├── build.js ├── build.min.js ├── build ├── ash.js └── ash.min.js ├── lib ├── brejep │ ├── class.js │ └── dictionary.js └── vendor │ ├── almond.js │ ├── require.min.js │ └── signals.js ├── package.json ├── src └── ash │ ├── ash-framework.js │ └── core │ ├── componentmatchingfamily.js │ ├── engine.js │ ├── entity.js │ ├── entitylist.js │ ├── family.js │ ├── node.js │ ├── nodelist.js │ ├── nodepool.js │ ├── system.js │ └── systemlist.js └── test ├── lib ├── qunit.css └── qunit.js ├── runner.html ├── spec ├── class.js ├── componentmatchingfamily.js ├── engine.js ├── entity.js ├── nodelist.js └── system.js ├── test_build.html ├── test_build_min.html ├── test_build_min_require.html ├── test_build_require.html └── utils ├── point.js └── point3.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | node_modules 4 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | grunt.initConfig({ 3 | pkg: grunt.file.readJSON('package.json'), 4 | jshint: { 5 | files: [ 6 | 'Gruntfile.js', 'build.js', 'build.min.js', 7 | 'src/**/*.js', 8 | 'test/spec/*.js' 9 | ], 10 | options: { 11 | browser: true, 12 | white: false 13 | } 14 | }, 15 | qunit: { 16 | // test using AMD 17 | files: ['test/runner.html'], 18 | 19 | // test the build & minified version at build 20 | builds: [ 'test/test_build.html', 'test/test_build_min.html', 21 | 'test/test_build_require.html', 'test/test_build_min_require.html' 22 | ] 23 | }, 24 | uglify: { 25 | dist: { 26 | files: [ 27 | { dest: 'build/ash.min.js', src: 'build/ash.js' } 28 | ] 29 | } 30 | }, 31 | requirejs: { 32 | compile: { 33 | options: { 34 | mainConfigFile: "build.js" 35 | } 36 | }, 37 | minified: { 38 | options: { 39 | mainConfigFile: "build.min.js" 40 | } 41 | } 42 | }, 43 | connect: { 44 | server: { 45 | options: { 46 | port: 9001, 47 | base: '.', 48 | keepalive: true 49 | } 50 | } 51 | } 52 | }); 53 | 54 | grunt.registerTask('test', ['jshint', 'qunit:files']); 55 | grunt.registerTask('default', ['jshint', 'qunit:files', 'requirejs', 'qunit:builds']); 56 | 57 | grunt.loadNpmTasks('grunt-contrib-uglify'); 58 | grunt.loadNpmTasks('grunt-contrib-jshint'); 59 | grunt.loadNpmTasks('grunt-contrib-qunit'); 60 | grunt.loadNpmTasks('grunt-contrib-requirejs'); 61 | grunt.loadNpmTasks('grunt-contrib-connect'); 62 | }; 63 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ash-js 2 | A JavaScript port of [Ash Framework](https://github.com/richardlord/Ash), an Actionscript 3 entity framework for game development 3 | 4 | ## Ash Framework 5 | ![Ash Framework](http://www.ashframework.org/images/logo.png "Ash Framework") 6 | 7 | Ash Framework is a high-performance entity system framework for game development, developed by Richard Lord in ActionScript 3. 8 | 9 | For more in-depth introduction and examples, please check out its official page at http://www.ashframework.org. 10 | 11 | ## Features 12 | - All core classes from the original Ash Framework are ported 13 | - Signal system from [JS-Signal](https://github.com/millermedeiros/js-signals) 14 | - A utility class for [simple Class Inheritance](https://github.com/rauschma/class-js) 15 | 16 | ## Usage 17 | On folder `build`, you can found the built & ready-to-use library file. 18 | 19 | There are two versions you can choose from: 20 | 21 | * `ash.js`, the un-minified version, good for development & debugging 22 | * `ash.min.js`, the minified version, good for release 23 | 24 | You can use the library file as inline script (by using script tag on your HTML file) or 25 | using AMD (e.g [RequireJS](http://requirejs.org/)). 26 | 27 | ## Building your own 28 | This library uses [Grunt](http://www.gruntjs.com) for building. 29 | 30 | ### Dependencies 31 | * Node.js 32 | * Grunt's CLI installed using `npm install -g grunt-cli` 33 | * Go to `ash-js` folder & do `npm install`. This will automatically download & install the required modules for building the library 34 | * Additionally, you might want to have [PhantomJS](phantomjs) installed on your system to enjoy automatic unit testings. 35 | However, you don't have to install PhantomJS and can still do unit testing directly from your browser 36 | (see instruction [below](#unit-testings-without-phantomjs)). 37 | 38 | ### Command lines 39 | * `grunt` will run jshint, build your library files (both minified & non-minified version), and do automatic unit testings using PhantomJS 40 | * `grunt requirejs:compile` if you just want to build `ash.js` (non-minifed version) and skip the unit testing 41 | * `grunt requirejs:minifed` if you just want to build `ash.min.js` (minified version) and skip the unit testing 42 | 43 | ### Unit Testings without PhantomJS 44 | You can still do unit testing without having PhantomJS installed on your system. 45 | 46 | * Do `grunt connect` to start a local webserver. 47 | * From your browser, go to `http://localhost:9001/test/` and manually click each html files there. 48 | 49 | List of unit test files: 50 | 51 | * *runner.html* : test each Ash Framework module separately 52 | * *test_build.html* : test the build version `build/ash.js` as inline script ( *WIP* ) 53 | * *test_build_min.html* : test the minified & build version `build/ash.min.js` as inline script ( *WIP* ) 54 | * *test_build_require.html* : test the build version `build/ash.js` with requirejs ( *WIP* ) 55 | * *test_build_min_require.html* : test the minified & build version `build/ash.js` with requirejs ( *WIP* ) 56 | 57 | ## Example on how to use ash-js 58 | * Ashteroids, a simple asteroids: https://github.com/brejep/ashjs-asteroids-example 59 | 60 | ## License 61 | MIT License 62 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ash-js", 3 | "version": "1.0.0", 4 | "homepage": "https://github.com/brejep/ash-js", 5 | "authors": [ 6 | "Brett Jephson " 7 | ], 8 | "description": "A Javascript port of Ash Framework, an entity-based framework for game development", 9 | "main": "build/ash.js", 10 | "keywords": [ 11 | "game", 12 | "development", 13 | "entity", 14 | "components", 15 | "framework", 16 | "Ash" 17 | ], 18 | "license": "MIT", 19 | "ignore": [ 20 | "**/.*", 21 | "node_modules", 22 | "bower_components", 23 | "test", 24 | "tests" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /build.js: -------------------------------------------------------------------------------- 1 | // see a complete list of options here: 2 | // https://github.com/jrburke/r.js/blob/master/build/example.build.js 3 | requirejs.config({ 4 | // all modules loaded are relative to this path 5 | // e.g. require(["grid/core"]) would grab /lib/grid/core.js 6 | baseUrl: ".", 7 | 8 | // specify custom module name paths 9 | paths: { 10 | "ash": "src/ash/", 11 | "ash-core": "src/ash/core", 12 | "brejep": "lib/brejep", 13 | "signals": "lib/vendor/signals" 14 | }, 15 | 16 | // target amd loader shim as the main module, path is relative to baseUrl. 17 | name: "lib/vendor/almond", 18 | 19 | optimize: "none", 20 | 21 | // files to include along with almond. only lib/skeleton.js is defined, as 22 | // it pulls in the rest of the dependencies automatically. 23 | include: [ "ash/ash-framework" ], 24 | 25 | // code to wrap around the start / end of the resulting build file 26 | // the global variable used to expose the API is defined here 27 | wrap: { 28 | start: "(function(global, define) {\n"+ 29 | // check for amd loader on global namespace 30 | " var globalDefine = global.define;\n", 31 | 32 | end: " var library = require('ash/ash-framework');\n"+ 33 | " if(typeof module !== 'undefined' && module.exports) {\n"+ 34 | // export library for node 35 | " module.exports = library;\n"+ 36 | " } else if(globalDefine) {\n"+ 37 | // define library for global amd loader that is already present 38 | " (function (define) {\n"+ 39 | " define(function () { return library; });\n"+ 40 | " }(globalDefine));\n"+ 41 | " } else {\n"+ 42 | // define library on global namespace for inline script loading 43 | " global['Ash'] = library;\n"+ 44 | " }\n"+ 45 | "}(this));\n" 46 | }, 47 | 48 | // build file destination, relative to the build file itself 49 | out: "./build/ash.js" 50 | }); 51 | -------------------------------------------------------------------------------- /build.min.js: -------------------------------------------------------------------------------- 1 | // see a complete list of options here: 2 | // https://github.com/jrburke/r.js/blob/master/build/example.build.js 3 | requirejs.config({ 4 | // all modules loaded are relative to this path 5 | // e.g. require(["grid/core"]) would grab /lib/grid/core.js 6 | baseUrl: ".", 7 | 8 | // specify custom module name paths 9 | paths: { 10 | "ash": "src/ash/", 11 | "ash-core": "src/ash/core", 12 | "brejep": "lib/brejep", 13 | "signals": "lib/vendor/signals" 14 | }, 15 | 16 | // target amd loader shim as the main module, path is relative to baseUrl. 17 | name: "lib/vendor/almond", 18 | 19 | optimize: "uglify", 20 | 21 | // files to include along with almond. only lib/skeleton.js is defined, as 22 | // it pulls in the rest of the dependencies automatically. 23 | include: [ "ash/ash-framework" ], 24 | 25 | // code to wrap around the start / end of the resulting build file 26 | // the global variable used to expose the API is defined here 27 | wrap: { 28 | start: "(function(global, define) {\n"+ 29 | // check for amd loader on global namespace 30 | " var globalDefine = global.define;\n", 31 | 32 | end: " var library = require('ash/ash-framework');\n"+ 33 | " if(typeof module !== 'undefined' && module.exports) {\n"+ 34 | // export library for node 35 | " module.exports = library;\n"+ 36 | " } else if(globalDefine) {\n"+ 37 | // define library for global amd loader that is already present 38 | " (function (define) {\n"+ 39 | " define(function () { return library; });\n"+ 40 | " }(globalDefine));\n"+ 41 | " } else {\n"+ 42 | // define library on global namespace for inline script loading 43 | " global['Ash'] = library;\n"+ 44 | " }\n"+ 45 | "}(this));\n" 46 | }, 47 | 48 | // build file destination, relative to the build file itself 49 | out: "./build/ash.min.js" 50 | }); 51 | -------------------------------------------------------------------------------- /build/ash.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * almond 0.2.3 Copyright (c) 2011-2012, The Dojo Foundation All Rights Reserved. 3 | * Available via the MIT or new BSD license. 4 | * see: http://github.com/jrburke/almond for details 5 | */ 6 | 7 | /** @license 8 | * JS Signals 9 | * Released under the MIT license 10 | * Author: Miller Medeiros 11 | * Version: 0.7.4 - Build: 252 (2012/02/24 10:30 PM) 12 | */ 13 | 14 | (function(e,t){var n=e.define,r,i,t;(function(e){function d(e,t){return h.call(e,t)}function v(e,t){var n,r,i,s,o,u,a,f,c,h,p=t&&t.split("/"),d=l.map,v=d&&d["*"]||{};if(e&&e.charAt(0)===".")if(t){p=p.slice(0,p.length-1),e=p.concat(e.split("/"));for(f=0;f0&&(e.splice(f-1,2),f-=2)}}e=e.join("/")}else e.indexOf("./")===0&&(e=e.substring(2));if((p||v)&&d){n=e.split("/");for(f=n.length;f>0;f-=1){r=n.slice(0,f).join("/");if(p)for(c=p.length;c>0;c-=1){i=d[p.slice(0,c).join("/")];if(i){i=i[r];if(i){s=i,o=f;break}}}if(s)break;!u&&v&&v[r]&&(u=v[r],a=f)}!s&&u&&(s=u,o=a),s&&(n.splice(0,o,s),e=n.join("/"))}return e}function m(t,n){return function(){return s.apply(e,p.call(arguments,0).concat([t,n]))}}function g(e){return function(t){return v(t,e)}}function y(e){return function(t){a[e]=t}}function b(t){if(d(f,t)){var r=f[t];delete f[t],c[t]=!0,n.apply(e,r)}if(!d(a,t)&&!d(c,t))throw new Error("No "+t);return a[t]}function w(e){var t,n=e?e.indexOf("!"):-1;return n>-1&&(t=e.substring(0,n),e=e.substring(n+1,e.length)),[t,e]}function E(e){return function(){return l&&l.config&&l.config[e]||{}}}var n,s,o,u,a={},f={},l={},c={},h=Object.prototype.hasOwnProperty,p=[].slice;o=function(e,t){var n,r=w(e),i=r[0];return e=r[1],i&&(i=v(i,t),n=b(i)),i?n&&n.normalize?e=n.normalize(e,g(t)):e=v(e,t):(e=v(e,t),r=w(e),i=r[0],e=r[1],i&&(n=b(i))),{f:i?i+"!"+e:e,n:e,pr:i,p:n}},u={require:function(e){return m(e)},exports:function(e){var t=a[e];return typeof t!="undefined"?t:a[e]={}},module:function(e){return{id:e,uri:"",exports:a[e],config:E(e)}}},n=function(t,n,r,i){var s,l,h,p,v,g=[],w;i=i||t;if(typeof r=="function"){n=!n.length&&r.length?["require","exports","module"]:n;for(v=0;v=0){n!=r.next&&(this.tail==n&&(this.tail=n.previous),n.previous.next=n.next,n.next&&(n.next.previous=n.previous),n.next=r.next,n.previous=r,n.next.previous=n,r.next=n);break}r||(this.tail==n&&(this.tail=n.previous),n.previous.next=n.next,n.next&&(n.next.previous=n.previous),n.next=this.head,this.head.previous=n,n.previous=null,this.head=n)}},mergeSort:function(e){if(this.head==this.tail)return;var t=[],n=this.head,r;while(n){r=n;while(r.next&&e(r,r.next)<=0)r=r.next;var i=r.next;n.previous=r.next=null,t.push(n),n=i}while(t.length>1)t.push(this.merge(t.shift(),t.shift(),e));this.tail=this.head=t[0];while(this.tail.next)this.tail=this.tail.next},merge:function(e,t,n){var r,i;n(e,t)<=0?(i=r=e,e=e.next):(i=r=t,t=t.next);while(e&&t)n(e,t)<=0?(r.next=e,e.previous=r,r=e,e=e.next):(r.next=t,t.previous=r,r=t,t=t.next);return e?(r.next=e,e.previous=r):(r.next=t,t.previous=r),i}});return n}),t("brejep/dictionary",["brejep/class"],function(e){var t=e.extend({VERSION:"0.1.0",keys:null,values:null,constructor:function(){return this.keys=[],this.values=[],this},add:function(e,t){var n=this.getIndex(e);n>=0?this.values[n]=t:(this.keys.push(e),this.values.push(t))},remove:function(e){var t=this.getIndex(e);if(!(t>=0))throw"Key does not exist";this.keys.splice(t,1),this.values.splice(t,1)},retrieve:function(e){var t=null,n=this.getIndex(e);return n>=0&&(t=this.values[n]),t},getIndex:function(e){var t=0,n=this.keys.length,r;for(;t must refer to "Class" via its global name (and not via "this") 41 | Class.copyOwnTo(properties, proto); 42 | 43 | var constr = proto.constructor; 44 | if (!(constr instanceof Function)) { 45 | throw new Error("You must define a method 'constructor'"); 46 | } 47 | // Set up the constructor 48 | constr.prototype = proto; 49 | constr.super = superProto; 50 | constr.extend = this.extend; // inherit class method 51 | return constr; 52 | }, 53 | 54 | copyOwnTo: function(source, target) { 55 | Object.getOwnPropertyNames(source).forEach(function(propName) { 56 | Object.defineProperty(target, propName, 57 | Object.getOwnPropertyDescriptor(source, propName)); 58 | }); 59 | return target; 60 | } 61 | }; 62 | 63 | return Class; 64 | }); 65 | -------------------------------------------------------------------------------- /lib/brejep/dictionary.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Dictionary 3 | * 4 | * @author Brett Jephson 5 | */ 6 | define([ 7 | 'brejep/class' 8 | ], function (Class) { 9 | 'use strict'; 10 | 11 | var Dictionary = Class.extend({ 12 | VERSION: '0.1.0', 13 | keys: null, 14 | values: null, 15 | 16 | constructor: function () { 17 | this.keys = []; 18 | this.values = []; 19 | return this; 20 | }, 21 | 22 | add: function (key, value) { 23 | var keyIndex = this.getIndex(key); 24 | if(keyIndex >= 0) { 25 | this.values[keyIndex] = value; 26 | } else { 27 | this.keys.push(key); 28 | this.values.push(value); 29 | } 30 | }, 31 | 32 | remove: function (key) { 33 | var keyIndex = this.getIndex(key); 34 | if(keyIndex >= 0) { 35 | this.keys.splice(keyIndex, 1); 36 | this.values.splice(keyIndex, 1); 37 | } else { 38 | throw 'Key does not exist'; 39 | } 40 | }, 41 | 42 | retrieve: function (key) { 43 | var value = null; 44 | var keyIndex = this.getIndex(key); 45 | if(keyIndex >= 0) { 46 | value = this.values[ keyIndex ]; 47 | } 48 | return value; 49 | }, 50 | 51 | getIndex: function (testKey) { 52 | var i = 0, 53 | len = this.keys.length, 54 | key; 55 | for(; i 0) { 71 | name.splice(i - 1, 2); 72 | i -= 2; 73 | } 74 | } 75 | } 76 | //end trimDots 77 | 78 | name = name.join("/"); 79 | } else if (name.indexOf('./') === 0) { 80 | // No baseName, so this is ID is resolved relative 81 | // to baseUrl, pull off the leading dot. 82 | name = name.substring(2); 83 | } 84 | } 85 | 86 | //Apply map config if available. 87 | if ((baseParts || starMap) && map) { 88 | nameParts = name.split('/'); 89 | 90 | for (i = nameParts.length; i > 0; i -= 1) { 91 | nameSegment = nameParts.slice(0, i).join("/"); 92 | 93 | if (baseParts) { 94 | //Find the longest baseName segment match in the config. 95 | //So, do joins on the biggest to smallest lengths of baseParts. 96 | for (j = baseParts.length; j > 0; j -= 1) { 97 | mapValue = map[baseParts.slice(0, j).join('/')]; 98 | 99 | //baseName segment has config, find if it has one for 100 | //this name. 101 | if (mapValue) { 102 | mapValue = mapValue[nameSegment]; 103 | if (mapValue) { 104 | //Match, update name to the new value. 105 | foundMap = mapValue; 106 | foundI = i; 107 | break; 108 | } 109 | } 110 | } 111 | } 112 | 113 | if (foundMap) { 114 | break; 115 | } 116 | 117 | //Check for a star map match, but just hold on to it, 118 | //if there is a shorter segment match later in a matching 119 | //config, then favor over this star map. 120 | if (!foundStarMap && starMap && starMap[nameSegment]) { 121 | foundStarMap = starMap[nameSegment]; 122 | starI = i; 123 | } 124 | } 125 | 126 | if (!foundMap && foundStarMap) { 127 | foundMap = foundStarMap; 128 | foundI = starI; 129 | } 130 | 131 | if (foundMap) { 132 | nameParts.splice(0, foundI, foundMap); 133 | name = nameParts.join('/'); 134 | } 135 | } 136 | 137 | return name; 138 | } 139 | 140 | function makeRequire(relName, forceSync) { 141 | return function () { 142 | //A version of a require function that passes a moduleName 143 | //value for items that may need to 144 | //look up paths relative to the moduleName 145 | return req.apply(undef, aps.call(arguments, 0).concat([relName, forceSync])); 146 | }; 147 | } 148 | 149 | function makeNormalize(relName) { 150 | return function (name) { 151 | return normalize(name, relName); 152 | }; 153 | } 154 | 155 | function makeLoad(depName) { 156 | return function (value) { 157 | defined[depName] = value; 158 | }; 159 | } 160 | 161 | function callDep(name) { 162 | if (hasProp(waiting, name)) { 163 | var args = waiting[name]; 164 | delete waiting[name]; 165 | defining[name] = true; 166 | main.apply(undef, args); 167 | } 168 | 169 | if (!hasProp(defined, name) && !hasProp(defining, name)) { 170 | throw new Error('No ' + name); 171 | } 172 | return defined[name]; 173 | } 174 | 175 | //Turns a plugin!resource to [plugin, resource] 176 | //with the plugin being undefined if the name 177 | //did not have a plugin prefix. 178 | function splitPrefix(name) { 179 | var prefix, 180 | index = name ? name.indexOf('!') : -1; 181 | if (index > -1) { 182 | prefix = name.substring(0, index); 183 | name = name.substring(index + 1, name.length); 184 | } 185 | return [prefix, name]; 186 | } 187 | 188 | /** 189 | * Makes a name map, normalizing the name, and using a plugin 190 | * for normalization if necessary. Grabs a ref to plugin 191 | * too, as an optimization. 192 | */ 193 | makeMap = function (name, relName) { 194 | var plugin, 195 | parts = splitPrefix(name), 196 | prefix = parts[0]; 197 | 198 | name = parts[1]; 199 | 200 | if (prefix) { 201 | prefix = normalize(prefix, relName); 202 | plugin = callDep(prefix); 203 | } 204 | 205 | //Normalize according 206 | if (prefix) { 207 | if (plugin && plugin.normalize) { 208 | name = plugin.normalize(name, makeNormalize(relName)); 209 | } else { 210 | name = normalize(name, relName); 211 | } 212 | } else { 213 | name = normalize(name, relName); 214 | parts = splitPrefix(name); 215 | prefix = parts[0]; 216 | name = parts[1]; 217 | if (prefix) { 218 | plugin = callDep(prefix); 219 | } 220 | } 221 | 222 | //Using ridiculous property names for space reasons 223 | return { 224 | f: prefix ? prefix + '!' + name : name, //fullName 225 | n: name, 226 | pr: prefix, 227 | p: plugin 228 | }; 229 | }; 230 | 231 | function makeConfig(name) { 232 | return function () { 233 | return (config && config.config && config.config[name]) || {}; 234 | }; 235 | } 236 | 237 | handlers = { 238 | require: function (name) { 239 | return makeRequire(name); 240 | }, 241 | exports: function (name) { 242 | var e = defined[name]; 243 | if (typeof e !== 'undefined') { 244 | return e; 245 | } else { 246 | return (defined[name] = {}); 247 | } 248 | }, 249 | module: function (name) { 250 | return { 251 | id: name, 252 | uri: '', 253 | exports: defined[name], 254 | config: makeConfig(name) 255 | }; 256 | } 257 | }; 258 | 259 | main = function (name, deps, callback, relName) { 260 | var cjsModule, depName, ret, map, i, 261 | args = [], 262 | usingExports; 263 | 264 | //Use name if no relName 265 | relName = relName || name; 266 | 267 | //Call the callback to define the module, if necessary. 268 | if (typeof callback === 'function') { 269 | 270 | //Pull out the defined dependencies and pass the ordered 271 | //values to the callback. 272 | //Default to [require, exports, module] if no deps 273 | deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps; 274 | for (i = 0; i < deps.length; i += 1) { 275 | map = makeMap(deps[i], relName); 276 | depName = map.f; 277 | 278 | //Fast path CommonJS standard dependencies. 279 | if (depName === "require") { 280 | args[i] = handlers.require(name); 281 | } else if (depName === "exports") { 282 | //CommonJS module spec 1.1 283 | args[i] = handlers.exports(name); 284 | usingExports = true; 285 | } else if (depName === "module") { 286 | //CommonJS module spec 1.1 287 | cjsModule = args[i] = handlers.module(name); 288 | } else if (hasProp(defined, depName) || 289 | hasProp(waiting, depName) || 290 | hasProp(defining, depName)) { 291 | args[i] = callDep(depName); 292 | } else if (map.p) { 293 | map.p.load(map.n, makeRequire(relName, true), makeLoad(depName), {}); 294 | args[i] = defined[depName]; 295 | } else { 296 | throw new Error(name + ' missing ' + depName); 297 | } 298 | } 299 | 300 | ret = callback.apply(defined[name], args); 301 | 302 | if (name) { 303 | //If setting exports via "module" is in play, 304 | //favor that over return value and exports. After that, 305 | //favor a non-undefined return value over exports use. 306 | if (cjsModule && cjsModule.exports !== undef && 307 | cjsModule.exports !== defined[name]) { 308 | defined[name] = cjsModule.exports; 309 | } else if (ret !== undef || !usingExports) { 310 | //Use the return value from the function. 311 | defined[name] = ret; 312 | } 313 | } 314 | } else if (name) { 315 | //May just be an object definition for the module. Only 316 | //worry about defining if have a module name. 317 | defined[name] = callback; 318 | } 319 | }; 320 | 321 | requirejs = require = req = function (deps, callback, relName, forceSync, alt) { 322 | if (typeof deps === "string") { 323 | if (handlers[deps]) { 324 | //callback in this case is really relName 325 | return handlers[deps](callback); 326 | } 327 | //Just return the module wanted. In this scenario, the 328 | //deps arg is the module name, and second arg (if passed) 329 | //is just the relName. 330 | //Normalize module name, if it contains . or .. 331 | return callDep(makeMap(deps, callback).f); 332 | } else if (!deps.splice) { 333 | //deps is a config object, not an array. 334 | config = deps; 335 | if (callback.splice) { 336 | //callback is an array, which means it is a dependency list. 337 | //Adjust args if there are dependencies 338 | deps = callback; 339 | callback = relName; 340 | relName = null; 341 | } else { 342 | deps = undef; 343 | } 344 | } 345 | 346 | //Support require(['a']) 347 | callback = callback || function () {}; 348 | 349 | //If relName is a function, it is an errback handler, 350 | //so remove it. 351 | if (typeof relName === 'function') { 352 | relName = forceSync; 353 | forceSync = alt; 354 | } 355 | 356 | //Simulate async callback; 357 | if (forceSync) { 358 | main(undef, deps, callback, relName); 359 | } else { 360 | setTimeout(function () { 361 | main(undef, deps, callback, relName); 362 | }, 15); 363 | } 364 | 365 | return req; 366 | }; 367 | 368 | /** 369 | * Just drops the config on the floor, but returns req in case 370 | * the config return value is used. 371 | */ 372 | req.config = function (cfg) { 373 | config = cfg; 374 | return req; 375 | }; 376 | 377 | define = function (name, deps, callback) { 378 | 379 | //This module may not have dependencies 380 | if (!deps.splice) { 381 | //deps is not an array, so probably means 382 | //an object literal or factory function for 383 | //the value. Adjust args. 384 | callback = deps; 385 | deps = []; 386 | } 387 | 388 | if (!hasProp(defined, name) && !hasProp(waiting, name)) { 389 | waiting[name] = [name, deps, callback]; 390 | } 391 | }; 392 | 393 | define.amd = { 394 | jQuery: true 395 | }; 396 | }()); 397 | -------------------------------------------------------------------------------- /lib/vendor/require.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | RequireJS 2.0.4 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved. 3 | Available via the MIT or new BSD license. 4 | see: http://github.com/jrburke/requirejs for details 5 | */ 6 | var requirejs,require,define; 7 | (function(Y){function x(b){return J.call(b)==="[object Function]"}function G(b){return J.call(b)==="[object Array]"}function q(b,c){if(b){var e;for(e=0;e-1;e-=1)if(b[e]&&c(b[e],e,b))break}}function y(b,c){for(var e in b)if(b.hasOwnProperty(e)&&c(b[e],e))break}function K(b,c,e,i){c&&y(c,function(c,j){if(e||!b.hasOwnProperty(j))i&&typeof c!=="string"?(b[j]||(b[j]={}),K(b[j],c,e,i)):b[j]=c});return b}function s(b, 8 | c){return function(){return c.apply(b,arguments)}}function Z(b){if(!b)return b;var c=Y;q(b.split("."),function(b){c=c[b]});return c}function $(b,c,e){return function(){var i=fa.call(arguments,0),g;if(e&&x(g=i[i.length-1]))g.__requireJsBuild=!0;i.push(c);return b.apply(null,i)}}function aa(b,c,e){q([["toUrl"],["undef"],["defined","requireDefined"],["specified","requireSpecified"]],function(i){var g=i[1]||i[0];b[i[0]]=c?$(c[g],e):function(){var b=z[O];return b[g].apply(b,arguments)}})}function H(b, 9 | c,e,i){c=Error(c+"\nhttp://requirejs.org/docs/errors.html#"+b);c.requireType=b;c.requireModules=i;if(e)c.originalError=e;return c}function ga(){if(I&&I.readyState==="interactive")return I;N(document.getElementsByTagName("script"),function(b){if(b.readyState==="interactive")return I=b});return I}var ha=/(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/mg,ia=/[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g,ba=/\.js$/,ja=/^\.\//,J=Object.prototype.toString,A=Array.prototype,fa=A.slice,ka=A.splice,w=!!(typeof window!== 10 | "undefined"&&navigator&&document),ca=!w&&typeof importScripts!=="undefined",la=w&&navigator.platform==="PLAYSTATION 3"?/^complete$/:/^(complete|loaded)$/,O="_",S=typeof opera!=="undefined"&&opera.toString()==="[object Opera]",z={},p={},P=[],L=!1,j,t,C,u,D,I,E,da,ea;if(typeof define==="undefined"){if(typeof requirejs!=="undefined"){if(x(requirejs))return;p=requirejs;requirejs=void 0}typeof require!=="undefined"&&!x(require)&&(p=require,require=void 0);j=requirejs=function(b,c,e,i){var g=O,r;!G(b)&& 11 | typeof b!=="string"&&(r=b,G(c)?(b=c,c=e,e=i):b=[]);if(r&&r.context)g=r.context;(i=z[g])||(i=z[g]=j.s.newContext(g));r&&i.configure(r);return i.require(b,c,e)};j.config=function(b){return j(b)};require||(require=j);j.version="2.0.4";j.jsExtRegExp=/^\/|:|\?|\.js$/;j.isBrowser=w;A=j.s={contexts:z,newContext:function(b){function c(a,d,o){var l=d&&d.split("/"),f=l,b=k.map,c=b&&b["*"],e,g,h;if(a&&a.charAt(0)===".")if(d){f=k.pkgs[d]?l=[d]:l.slice(0,l.length-1);d=a=f.concat(a.split("/"));for(f=0;d[f];f+= 12 | 1)if(e=d[f],e===".")d.splice(f,1),f-=1;else if(e==="..")if(f===1&&(d[2]===".."||d[0]===".."))break;else f>0&&(d.splice(f-1,2),f-=2);f=k.pkgs[d=a[0]];a=a.join("/");f&&a===d+"/"+f.main&&(a=d)}else a.indexOf("./")===0&&(a=a.substring(2));if(o&&(l||c)&&b){d=a.split("/");for(f=d.length;f>0;f-=1){g=d.slice(0,f).join("/");if(l)for(e=l.length;e>0;e-=1)if(o=b[l.slice(0,e).join("/")])if(o=o[g]){h=o;break}!h&&c&&c[g]&&(h=c[g]);if(h){d.splice(0,f,h);a=d.join("/");break}}}return a}function e(a){w&&q(document.getElementsByTagName("script"), 13 | function(d){if(d.getAttribute("data-requiremodule")===a&&d.getAttribute("data-requirecontext")===h.contextName)return d.parentNode.removeChild(d),!0})}function i(a){var d=k.paths[a];if(d&&G(d)&&d.length>1)return e(a),d.shift(),h.undef(a),h.require([a]),!0}function g(a,d,o,b){var f=a?a.indexOf("!"):-1,v=null,e=d?d.name:null,g=a,i=!0,j="",k,m;a||(i=!1,a="_@r"+(N+=1));f!==-1&&(v=a.substring(0,f),a=a.substring(f+1,a.length));v&&(v=c(v,e,b),m=n[v]);a&&(v?j=m&&m.normalize?m.normalize(a,function(a){return c(a, 14 | e,b)}):c(a,e,b):(j=c(a,e,b),k=h.nameToUrl(j)));a=v&&!m&&!o?"_unnormalized"+(O+=1):"";return{prefix:v,name:j,parentMap:d,unnormalized:!!a,url:k,originalName:g,isDefine:i,id:(v?v+"!"+j:j)+a}}function r(a){var d=a.id,o=m[d];o||(o=m[d]=new h.Module(a));return o}function p(a,d,o){var b=a.id,f=m[b];if(n.hasOwnProperty(b)&&(!f||f.defineEmitComplete))d==="defined"&&o(n[b]);else r(a).on(d,o)}function B(a,d){var b=a.requireModules,l=!1;if(d)d(a);else if(q(b,function(d){if(d=m[d])d.error=a,d.events.error&&(l= 15 | !0,d.emit("error",a))}),!l)j.onError(a)}function u(){P.length&&(ka.apply(F,[F.length-1,0].concat(P)),P=[])}function t(a,d,b){a=a&&a.map;d=$(b||h.require,a,d);aa(d,h,a);d.isBrowser=w;return d}function z(a){delete m[a];q(M,function(d,b){if(d.map.id===a)return M.splice(b,1),d.defined||(h.waitCount-=1),!0})}function A(a,d){var b=a.map.id,l=a.depMaps,f;if(a.inited){if(d[b])return a;d[b]=!0;q(l,function(a){if(a=m[a.id])return!a.inited||!a.enabled?(f=null,delete d[b],!0):f=A(a,K({},d))});return f}}function C(a, 16 | d,b){var l=a.map.id,f=a.depMaps;if(a.inited&&a.map.isDefine){if(d[l])return n[l];d[l]=a;q(f,function(f){var f=f.id,c=m[f];!Q[f]&&c&&(!c.inited||!c.enabled?b[l]=!0:(c=C(c,d,b),b[f]||a.defineDepById(f,c)))});a.check(!0);return n[l]}}function D(a){a.check()}function E(){var a=k.waitSeconds*1E3,d=a&&h.startTime+a<(new Date).getTime(),b=[],l=!1,f=!0,c,g,j;if(!T){T=!0;y(m,function(a){c=a.map;g=c.id;if(a.enabled&&!a.error)if(!a.inited&&d)i(g)?l=j=!0:(b.push(g),e(g));else if(!a.inited&&a.fetched&&c.isDefine&& 17 | (l=!0,!c.prefix))return f=!1});if(d&&b.length)return a=H("timeout","Load timeout for modules: "+b,null,b),a.contextName=h.contextName,B(a);f&&(q(M,function(a){if(!a.defined){var a=A(a,{}),d={};a&&(C(a,d,{}),y(d,D))}}),y(m,D));if((!d||j)&&l)if((w||ca)&&!U)U=setTimeout(function(){U=0;E()},50);T=!1}}function V(a){r(g(a[0],null,!0)).init(a[1],a[2])}function J(a){var a=a.currentTarget||a.srcElement,d=h.onScriptLoad;a.detachEvent&&!S?a.detachEvent("onreadystatechange",d):a.removeEventListener("load",d, 18 | !1);d=h.onScriptError;a.detachEvent&&!S||a.removeEventListener("error",d,!1);return{node:a,id:a&&a.getAttribute("data-requiremodule")}}var k={waitSeconds:7,baseUrl:"./",paths:{},pkgs:{},shim:{}},m={},W={},F=[],n={},R={},N=1,O=1,M=[],T,X,h,Q,U;Q={require:function(a){return t(a)},exports:function(a){a.usingExports=!0;if(a.map.isDefine)return a.exports=n[a.map.id]={}},module:function(a){return a.module={id:a.map.id,uri:a.map.url,config:function(){return k.config&&k.config[a.map.id]||{}},exports:n[a.map.id]}}}; 19 | X=function(a){this.events=W[a.id]||{};this.map=a;this.shim=k.shim[a.id];this.depExports=[];this.depMaps=[];this.depMatched=[];this.pluginMaps={};this.depCount=0};X.prototype={init:function(a,d,b,l){l=l||{};if(!this.inited){this.factory=d;if(b)this.on("error",b);else this.events.error&&(b=s(this,function(a){this.emit("error",a)}));this.depMaps=a&&a.slice(0);this.depMaps.rjsSkipMap=a.rjsSkipMap;this.errback=b;this.inited=!0;this.ignore=l.ignore;l.enabled||this.enabled?this.enable():this.check()}},defineDepById:function(a, 20 | d){var b;q(this.depMaps,function(d,f){if(d.id===a)return b=f,!0});return this.defineDep(b,d)},defineDep:function(a,d){this.depMatched[a]||(this.depMatched[a]=!0,this.depCount-=1,this.depExports[a]=d)},fetch:function(){if(!this.fetched){this.fetched=!0;h.startTime=(new Date).getTime();var a=this.map;if(this.shim)t(this,!0)(this.shim.deps||[],s(this,function(){return a.prefix?this.callPlugin():this.load()}));else return a.prefix?this.callPlugin():this.load()}},load:function(){var a=this.map.url;R[a]|| 21 | (R[a]=!0,h.load(this.map.id,a))},check:function(a){if(this.enabled&&!this.enabling){var d=this.map.id,b=this.depExports,c=this.exports,f=this.factory,e;if(this.inited)if(this.error)this.emit("error",this.error);else{if(!this.defining){this.defining=!0;if(this.depCount<1&&!this.defined){if(x(f)){if(this.events.error)try{c=h.execCb(d,f,b,c)}catch(g){e=g}else c=h.execCb(d,f,b,c);if(this.map.isDefine)if((b=this.module)&&b.exports!==void 0&&b.exports!==this.exports)c=b.exports;else if(c===void 0&&this.usingExports)c= 22 | this.exports;if(e)return e.requireMap=this.map,e.requireModules=[this.map.id],e.requireType="define",B(this.error=e)}else c=f;this.exports=c;if(this.map.isDefine&&!this.ignore&&(n[d]=c,j.onResourceLoad))j.onResourceLoad(h,this.map,this.depMaps);delete m[d];this.defined=!0;h.waitCount-=1;h.waitCount===0&&(M=[])}this.defining=!1;if(!a&&this.defined&&!this.defineEmitted)this.defineEmitted=!0,this.emit("defined",this.exports),this.defineEmitComplete=!0}}else this.fetch()}},callPlugin:function(){var a= 23 | this.map,d=a.id,b=g(a.prefix,null,!1,!0);p(b,"defined",s(this,function(b){var f=this.map.name,e=this.map.parentMap?this.map.parentMap.name:null;if(this.map.unnormalized){if(b.normalize&&(f=b.normalize(f,function(a){return c(a,e,!0)})||""),b=g(a.prefix+"!"+f,this.map.parentMap,!1,!0),p(b,"defined",s(this,function(a){this.init([],function(){return a},null,{enabled:!0,ignore:!0})})),b=m[b.id]){if(this.events.error)b.on("error",s(this,function(a){this.emit("error",a)}));b.enable()}}else f=s(this,function(a){this.init([], 24 | function(){return a},null,{enabled:!0})}),f.error=s(this,function(a){this.inited=!0;this.error=a;a.requireModules=[d];y(m,function(a){a.map.id.indexOf(d+"_unnormalized")===0&&z(a.map.id)});B(a)}),f.fromText=function(a,d){var b=L;b&&(L=!1);r(g(a));j.exec(d);b&&(L=!0);h.completeLoad(a)},b.load(a.name,t(a.parentMap,!0,function(a,d){a.rjsSkipMap=!0;return h.require(a,d)}),f,k)}));h.enable(b,this);this.pluginMaps[b.id]=b},enable:function(){this.enabled=!0;if(!this.waitPushed)M.push(this),h.waitCount+= 25 | 1,this.waitPushed=!0;this.enabling=!0;q(this.depMaps,s(this,function(a,d){var b,c;if(typeof a==="string"){a=g(a,this.map.isDefine?this.map:this.map.parentMap,!1,!this.depMaps.rjsSkipMap);this.depMaps[d]=a;if(b=Q[a.id]){this.depExports[d]=b(this);return}this.depCount+=1;p(a,"defined",s(this,function(a){this.defineDep(d,a);this.check()}));this.errback&&p(a,"error",this.errback)}b=a.id;c=m[b];!Q[b]&&c&&!c.enabled&&h.enable(a,this)}));y(this.pluginMaps,s(this,function(a){var b=m[a.id];b&&!b.enabled&& 26 | h.enable(a,this)}));this.enabling=!1;this.check()},on:function(a,b){var c=this.events[a];c||(c=this.events[a]=[]);c.push(b)},emit:function(a,b){q(this.events[a],function(a){a(b)});a==="error"&&delete this.events[a]}};return h={config:k,contextName:b,registry:m,defined:n,urlFetched:R,waitCount:0,defQueue:F,Module:X,makeModuleMap:g,configure:function(a){a.baseUrl&&a.baseUrl.charAt(a.baseUrl.length-1)!=="/"&&(a.baseUrl+="/");var b=k.pkgs,c=k.shim,e=k.paths,f=k.map;K(k,a,!0);k.paths=K(e,a.paths,!0);if(a.map)k.map= 27 | K(f||{},a.map,!0,!0);if(a.shim)y(a.shim,function(a,b){G(a)&&(a={deps:a});if(a.exports&&!a.exports.__buildReady)a.exports=h.makeShimExports(a.exports);c[b]=a}),k.shim=c;if(a.packages)q(a.packages,function(a){a=typeof a==="string"?{name:a}:a;b[a.name]={name:a.name,location:a.location||a.name,main:(a.main||"main").replace(ja,"").replace(ba,"")}}),k.pkgs=b;y(m,function(a,b){a.map=g(b)});if(a.deps||a.callback)h.require(a.deps||[],a.callback)},makeShimExports:function(a){var b;return typeof a==="string"? 28 | (b=function(){return Z(a)},b.exports=a,b):function(){return a.apply(Y,arguments)}},requireDefined:function(a,b){var c=g(a,b,!1,!0).id;return n.hasOwnProperty(c)},requireSpecified:function(a,b){a=g(a,b,!1,!0).id;return n.hasOwnProperty(a)||m.hasOwnProperty(a)},require:function(a,d,c,e){var f;if(typeof a==="string"){if(x(d))return B(H("requireargs","Invalid require call"),c);if(j.get)return j.get(h,a,d);a=g(a,d,!1,!0);a=a.id;return!n.hasOwnProperty(a)?B(H("notloaded",'Module name "'+a+'" has not been loaded yet for context: '+ 29 | b)):n[a]}c&&!x(c)&&(e=c,c=void 0);d&&!x(d)&&(e=d,d=void 0);for(u();F.length;)if(f=F.shift(),f[0]===null)return B(H("mismatch","Mismatched anonymous define() module: "+f[f.length-1]));else V(f);r(g(null,e)).init(a,d,c,{enabled:!0});E();return h.require},undef:function(a){var b=g(a,null,!0),c=m[a];delete n[a];delete R[b.url];delete W[a];if(c){if(c.events.defined)W[a]=c.events;z(a)}},enable:function(a){m[a.id]&&r(a).enable()},completeLoad:function(a){var b=k.shim[a]||{},c=b.exports&&b.exports.exports, 30 | e,f;for(u();F.length;){f=F.shift();if(f[0]===null){f[0]=a;if(e)break;e=!0}else f[0]===a&&(e=!0);V(f)}f=m[a];if(!e&&!n[a]&&f&&!f.inited)if(k.enforceDefine&&(!c||!Z(c)))if(i(a))return;else return B(H("nodefine","No define call for "+a,null,[a]));else V([a,b.deps||[],b.exports]);E()},toUrl:function(a,b){var e=a.lastIndexOf("."),g=null;e!==-1&&(g=a.substring(e,a.length),a=a.substring(0,e));return h.nameToUrl(c(a,b&&b.id,!0),g)},nameToUrl:function(a,b){var c,e,f,g,h,i;if(j.jsExtRegExp.test(a))g=a+(b|| 31 | "");else{c=k.paths;e=k.pkgs;g=a.split("/");for(h=g.length;h>0;h-=1)if(i=g.slice(0,h).join("/"),f=e[i],i=c[i]){G(i)&&(i=i[0]);g.splice(0,h,i);break}else if(f){c=a===f.name?f.location+"/"+f.main:f.location;g.splice(0,h,c);break}g=g.join("/")+(b||".js");g=(g.charAt(0)==="/"||g.match(/^[\w\+\.\-]+:/)?"":k.baseUrl)+g}return k.urlArgs?g+((g.indexOf("?")===-1?"?":"&")+k.urlArgs):g},load:function(a,b){j.load(h,a,b)},execCb:function(a,b,c,e){return b.apply(e,c)},onScriptLoad:function(a){if(a.type==="load"|| 32 | la.test((a.currentTarget||a.srcElement).readyState))I=null,a=J(a),h.completeLoad(a.id)},onScriptError:function(a){var b=J(a);if(!i(b.id))return B(H("scripterror","Script error",a,[b.id]))}}}};j({});aa(j);if(w&&(t=A.head=document.getElementsByTagName("head")[0],C=document.getElementsByTagName("base")[0]))t=A.head=C.parentNode;j.onError=function(b){throw b;};j.load=function(b,c,e){var i=b&&b.config||{},g;if(w)return g=i.xhtml?document.createElementNS("http://www.w3.org/1999/xhtml","html:script"):document.createElement("script"), 33 | g.type=i.scriptType||"text/javascript",g.charset="utf-8",g.async=!0,g.setAttribute("data-requirecontext",b.contextName),g.setAttribute("data-requiremodule",c),g.attachEvent&&!(g.attachEvent.toString&&g.attachEvent.toString().indexOf("[native code")<0)&&!S?(L=!0,g.attachEvent("onreadystatechange",b.onScriptLoad)):(g.addEventListener("load",b.onScriptLoad,!1),g.addEventListener("error",b.onScriptError,!1)),g.src=e,E=g,C?t.insertBefore(g,C):t.appendChild(g),E=null,g;else ca&&(importScripts(e),b.completeLoad(c))}; 34 | w&&N(document.getElementsByTagName("script"),function(b){if(!t)t=b.parentNode;if(u=b.getAttribute("data-main")){if(!p.baseUrl)D=u.split("/"),da=D.pop(),ea=D.length?D.join("/")+"/":"./",p.baseUrl=ea,u=da;u=u.replace(ba,"");p.deps=p.deps?p.deps.concat(u):[u];return!0}});define=function(b,c,e){var i,g;typeof b!=="string"&&(e=c,c=b,b=null);G(c)||(e=c,c=[]);!c.length&&x(e)&&e.length&&(e.toString().replace(ha,"").replace(ia,function(b,e){c.push(e)}),c=(e.length===1?["require"]:["require","exports","module"]).concat(c)); 35 | if(L&&(i=E||ga()))b||(b=i.getAttribute("data-requiremodule")),g=z[i.getAttribute("data-requirecontext")];(g?g.defQueue:P).push([b,c,e])};define.amd={jQuery:!0};j.exec=function(b){return eval(b)};j(p)}})(this); -------------------------------------------------------------------------------- /lib/vendor/signals.js: -------------------------------------------------------------------------------- 1 | /*jslint onevar:true, undef:true, newcap:true, regexp:true, bitwise:true, maxerr:50, indent:4, white:false, nomen:false, plusplus:false */ 2 | /*global define:false, require:false, exports:false, module:false*/ 3 | 4 | /** @license 5 | * JS Signals 6 | * Released under the MIT license 7 | * Author: Miller Medeiros 8 | * Version: 0.7.4 - Build: 252 (2012/02/24 10:30 PM) 9 | */ 10 | 11 | (function(global){ 12 | 13 | /** 14 | * @namespace Signals Namespace - Custom event/messaging system based on AS3 Signals 15 | * @name signals 16 | */ 17 | var signals = /** @lends signals */{ 18 | /** 19 | * Signals Version Number 20 | * @type String 21 | * @const 22 | */ 23 | VERSION : '0.7.4' 24 | }; 25 | 26 | 27 | // SignalBinding ------------------------------------------------- 28 | //================================================================ 29 | 30 | /** 31 | * Object that represents a binding between a Signal and a listener function. 32 | *
- This is an internal constructor and shouldn't be called by regular users. 33 | *
- inspired by Joa Ebert AS3 SignalBinding and Robert Penner's Slot classes. 34 | * @author Miller Medeiros 35 | * @constructor 36 | * @internal 37 | * @name signals.SignalBinding 38 | * @param {signals.Signal} signal Reference to Signal object that listener is currently bound to. 39 | * @param {Function} listener Handler function bound to the signal. 40 | * @param {boolean} isOnce If binding should be executed just once. 41 | * @param {Object} [listenerContext] Context on which listener will be executed (object that should represent the `this` variable inside listener function). 42 | * @param {Number} [priority] The priority level of the event listener. (default = 0). 43 | */ 44 | function SignalBinding(signal, listener, isOnce, listenerContext, priority) { 45 | 46 | /** 47 | * Handler function bound to the signal. 48 | * @type Function 49 | * @private 50 | */ 51 | this._listener = listener; 52 | 53 | /** 54 | * If binding should be executed just once. 55 | * @type boolean 56 | * @private 57 | */ 58 | this._isOnce = isOnce; 59 | 60 | /** 61 | * Context on which listener will be executed (object that should represent the `this` variable inside listener function). 62 | * @memberOf signals.SignalBinding.prototype 63 | * @name context 64 | * @type Object|undefined|null 65 | */ 66 | this.context = listenerContext; 67 | 68 | /** 69 | * Reference to Signal object that listener is currently bound to. 70 | * @type signals.Signal 71 | * @private 72 | */ 73 | this._signal = signal; 74 | 75 | /** 76 | * Listener priority 77 | * @type Number 78 | * @private 79 | */ 80 | this._priority = priority || 0; 81 | } 82 | 83 | SignalBinding.prototype = /** @lends signals.SignalBinding.prototype */ { 84 | 85 | /** 86 | * If binding is active and should be executed. 87 | * @type boolean 88 | */ 89 | active : true, 90 | 91 | /** 92 | * Default parameters passed to listener during `Signal.dispatch` and `SignalBinding.execute`. (curried parameters) 93 | * @type Array|null 94 | */ 95 | params : null, 96 | 97 | /** 98 | * Call listener passing arbitrary parameters. 99 | *

If binding was added using `Signal.addOnce()` it will be automatically removed from signal dispatch queue, this method is used internally for the signal dispatch.

100 | * @param {Array} [paramsArr] Array of parameters that should be passed to the listener 101 | * @return {*} Value returned by the listener. 102 | */ 103 | execute : function (paramsArr) { 104 | var handlerReturn, params; 105 | if (this.active && !!this._listener) { 106 | params = this.params? this.params.concat(paramsArr) : paramsArr; 107 | handlerReturn = this._listener.apply(this.context, params); 108 | if (this._isOnce) { 109 | this.detach(); 110 | } 111 | } 112 | return handlerReturn; 113 | }, 114 | 115 | /** 116 | * Detach binding from signal. 117 | * - alias to: mySignal.remove(myBinding.getListener()); 118 | * @return {Function|null} Handler function bound to the signal or `null` if binding was previously detached. 119 | */ 120 | detach : function () { 121 | return this.isBound()? this._signal.remove(this._listener, this.context) : null; 122 | }, 123 | 124 | /** 125 | * @return {Boolean} `true` if binding is still bound to the signal and have a listener. 126 | */ 127 | isBound : function () { 128 | return (!!this._signal && !!this._listener); 129 | }, 130 | 131 | /** 132 | * @return {Function} Handler function bound to the signal. 133 | */ 134 | getListener : function () { 135 | return this._listener; 136 | }, 137 | 138 | /** 139 | * Delete instance properties 140 | * @private 141 | */ 142 | _destroy : function () { 143 | delete this._signal; 144 | delete this._listener; 145 | delete this.context; 146 | }, 147 | 148 | /** 149 | * @return {boolean} If SignalBinding will only be executed once. 150 | */ 151 | isOnce : function () { 152 | return this._isOnce; 153 | }, 154 | 155 | /** 156 | * @return {string} String representation of the object. 157 | */ 158 | toString : function () { 159 | return '[SignalBinding isOnce:' + this._isOnce +', isBound:'+ this.isBound() +', active:' + this.active + ']'; 160 | } 161 | 162 | }; 163 | 164 | 165 | /*global signals:false, SignalBinding:false*/ 166 | 167 | // Signal -------------------------------------------------------- 168 | //================================================================ 169 | 170 | function validateListener(listener, fnName) { 171 | if (typeof listener !== 'function') { 172 | throw new Error( 'listener is a required param of {fn}() and should be a Function.'.replace('{fn}', fnName) ); 173 | } 174 | } 175 | 176 | /** 177 | * Custom event broadcaster 178 | *
- inspired by Robert Penner's AS3 Signals. 179 | * @author Miller Medeiros 180 | * @constructor 181 | */ 182 | signals.Signal = function () { 183 | /** 184 | * @type Array. 185 | * @private 186 | */ 187 | this._bindings = []; 188 | this._prevParams = null; 189 | }; 190 | 191 | signals.Signal.prototype = { 192 | 193 | /** 194 | * If Signal should keep record of previously dispatched parameters and 195 | * automatically execute listener during `add()`/`addOnce()` if Signal was 196 | * already dispatched before. 197 | * @type boolean 198 | */ 199 | memorize : false, 200 | 201 | /** 202 | * @type boolean 203 | * @private 204 | */ 205 | _shouldPropagate : true, 206 | 207 | /** 208 | * If Signal is active and should broadcast events. 209 | *

IMPORTANT: Setting this property during a dispatch will only affect the next dispatch, if you want to stop the propagation of a signal use `halt()` instead.

210 | * @type boolean 211 | */ 212 | active : true, 213 | 214 | /** 215 | * @param {Function} listener 216 | * @param {boolean} isOnce 217 | * @param {Object} [listenerContext] 218 | * @param {Number} [priority] 219 | * @return {SignalBinding} 220 | * @private 221 | */ 222 | _registerListener : function (listener, isOnce, listenerContext, priority) { 223 | 224 | var prevIndex = this._indexOfListener(listener, listenerContext), 225 | binding; 226 | 227 | if (prevIndex !== -1) { 228 | binding = this._bindings[prevIndex]; 229 | if (binding.isOnce() !== isOnce) { 230 | throw new Error('You cannot add'+ (isOnce? '' : 'Once') +'() then add'+ (!isOnce? '' : 'Once') +'() the same listener without removing the relationship first.'); 231 | } 232 | } else { 233 | binding = new SignalBinding(this, listener, isOnce, listenerContext, priority); 234 | this._addBinding(binding); 235 | } 236 | 237 | if(this.memorize && this._prevParams){ 238 | binding.execute(this._prevParams); 239 | } 240 | 241 | return binding; 242 | }, 243 | 244 | /** 245 | * @param {SignalBinding} binding 246 | * @private 247 | */ 248 | _addBinding : function (binding) { 249 | //simplified insertion sort 250 | var n = this._bindings.length; 251 | do { --n; } while (this._bindings[n] && binding._priority <= this._bindings[n]._priority); 252 | this._bindings.splice(n + 1, 0, binding); 253 | }, 254 | 255 | /** 256 | * @param {Function} listener 257 | * @return {number} 258 | * @private 259 | */ 260 | _indexOfListener : function (listener, context) { 261 | var n = this._bindings.length, 262 | cur; 263 | while (n--) { 264 | cur = this._bindings[n]; 265 | if (cur._listener === listener && cur.context === context) { 266 | return n; 267 | } 268 | } 269 | return -1; 270 | }, 271 | 272 | /** 273 | * Check if listener was attached to Signal. 274 | * @param {Function} listener 275 | * @param {Object} [context] 276 | * @return {boolean} if Signal has the specified listener. 277 | */ 278 | has : function (listener, context) { 279 | return this._indexOfListener(listener, context) !== -1; 280 | }, 281 | 282 | /** 283 | * Add a listener to the signal. 284 | * @param {Function} listener Signal handler function. 285 | * @param {Object} [listenerContext] Context on which listener will be executed (object that should represent the `this` variable inside listener function). 286 | * @param {Number} [priority] The priority level of the event listener. Listeners with higher priority will be executed before listeners with lower priority. Listeners with same priority level will be executed at the same order as they were added. (default = 0) 287 | * @return {SignalBinding} An Object representing the binding between the Signal and listener. 288 | */ 289 | add : function (listener, listenerContext, priority) { 290 | validateListener(listener, 'add'); 291 | return this._registerListener(listener, false, listenerContext, priority); 292 | }, 293 | 294 | /** 295 | * Add listener to the signal that should be removed after first execution (will be executed only once). 296 | * @param {Function} listener Signal handler function. 297 | * @param {Object} [listenerContext] Context on which listener will be executed (object that should represent the `this` variable inside listener function). 298 | * @param {Number} [priority] The priority level of the event listener. Listeners with higher priority will be executed before listeners with lower priority. Listeners with same priority level will be executed at the same order as they were added. (default = 0) 299 | * @return {SignalBinding} An Object representing the binding between the Signal and listener. 300 | */ 301 | addOnce : function (listener, listenerContext, priority) { 302 | validateListener(listener, 'addOnce'); 303 | return this._registerListener(listener, true, listenerContext, priority); 304 | }, 305 | 306 | /** 307 | * Remove a single listener from the dispatch queue. 308 | * @param {Function} listener Handler function that should be removed. 309 | * @param {Object} [context] Execution context (since you can add the same handler multiple times if executing in a different context). 310 | * @return {Function} Listener handler function. 311 | */ 312 | remove : function (listener, context) { 313 | validateListener(listener, 'remove'); 314 | 315 | var i = this._indexOfListener(listener, context); 316 | if (i !== -1) { 317 | this._bindings[i]._destroy(); //no reason to a SignalBinding exist if it isn't attached to a signal 318 | this._bindings.splice(i, 1); 319 | } 320 | return listener; 321 | }, 322 | 323 | /** 324 | * Remove all listeners from the Signal. 325 | */ 326 | removeAll : function () { 327 | var n = this._bindings.length; 328 | while (n--) { 329 | this._bindings[n]._destroy(); 330 | } 331 | this._bindings.length = 0; 332 | }, 333 | 334 | /** 335 | * @return {number} Number of listeners attached to the Signal. 336 | */ 337 | getNumListeners : function () { 338 | return this._bindings.length; 339 | }, 340 | 341 | /** 342 | * Stop propagation of the event, blocking the dispatch to next listeners on the queue. 343 | *

IMPORTANT: should be called only during signal dispatch, calling it before/after dispatch won't affect signal broadcast.

344 | * @see signals.Signal.prototype.disable 345 | */ 346 | halt : function () { 347 | this._shouldPropagate = false; 348 | }, 349 | 350 | /** 351 | * Dispatch/Broadcast Signal to all listeners added to the queue. 352 | * @param {...*} [params] Parameters that should be passed to each handler. 353 | */ 354 | dispatch : function (params) { 355 | if (! this.active) { 356 | return; 357 | } 358 | 359 | var paramsArr = Array.prototype.slice.call(arguments), 360 | n = this._bindings.length, 361 | bindings; 362 | 363 | if (this.memorize) { 364 | this._prevParams = paramsArr; 365 | } 366 | 367 | if (! n) { 368 | //should come after memorize 369 | return; 370 | } 371 | 372 | bindings = this._bindings.slice(); //clone array in case add/remove items during dispatch 373 | this._shouldPropagate = true; //in case `halt` was called before dispatch or during the previous dispatch. 374 | 375 | //execute all callbacks until end of the list or until a callback returns `false` or stops propagation 376 | //reverse loop since listeners with higher priority will be added at the end of the list 377 | do { n--; } while (bindings[n] && this._shouldPropagate && bindings[n].execute(paramsArr) !== false); 378 | }, 379 | 380 | /** 381 | * Forget memorized arguments. 382 | * @see signals.Signal.memorize 383 | */ 384 | forget : function(){ 385 | this._prevParams = null; 386 | }, 387 | 388 | /** 389 | * Remove all bindings from signal and destroy any reference to external objects (destroy Signal object). 390 | *

IMPORTANT: calling any method on the signal instance after calling dispose will throw errors.

391 | */ 392 | dispose : function () { 393 | this.removeAll(); 394 | delete this._bindings; 395 | delete this._prevParams; 396 | }, 397 | 398 | /** 399 | * @return {string} String representation of the object. 400 | */ 401 | toString : function () { 402 | return '[Signal active:'+ this.active +' numListeners:'+ this.getNumListeners() +']'; 403 | } 404 | 405 | }; 406 | 407 | 408 | //exports to multiple environments 409 | if(typeof define === 'function' && define.amd){ //AMD 410 | define(signals); 411 | } else if (typeof module !== 'undefined' && module.exports){ //node 412 | module.exports = signals; 413 | } else { //browser 414 | //use string because of Google closure compiler ADVANCED_MODE 415 | global['signals'] = signals; 416 | } 417 | 418 | }(this)); 419 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ash-framework", 3 | "version": "0.2.0", 4 | "description": "JavaScript port of Ash framework", 5 | "private": true, 6 | "devDependencies" : { 7 | "grunt": "~0.4.0", 8 | "grunt-contrib-uglify": "~0.1.2", 9 | "grunt-contrib-jshint": "~0.2.0", 10 | "grunt-contrib-qunit": "~0.2.0", 11 | "grunt-contrib-requirejs": "~0.4.0", 12 | "grunt-contrib-connect": "~0.2.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/ash/ash-framework.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Ash framework core 3 | * 4 | * @author Brett Jephson 5 | */ 6 | define(function (require) { 7 | var core = { 8 | VERSION: '0.2.0' 9 | }; 10 | 11 | core.Engine = require('ash-core/engine'); 12 | core.ComponentMatchingFamily = require('ash-core/componentmatchingfamily'); 13 | core.Entity = require('ash-core/entity'); 14 | core.EntityList = require('ash-core/entitylist'); 15 | core.Family = require('ash-core/family'); 16 | core.Node = require('ash-core/node'); 17 | core.NodeList = require('ash-core/nodelist'); 18 | core.NodePool = require('ash-core/nodepool'); 19 | core.System = require('ash-core/system'); 20 | core.SystemList = require('ash-core/systemlist'); 21 | 22 | // util classes 23 | // TODO separate this? 24 | core.Class = require('brejep/class'); 25 | core.Signals = require('signals'); 26 | 27 | return core; 28 | }); 29 | -------------------------------------------------------------------------------- /src/ash/core/componentmatchingfamily.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Ash-js Component matching family 3 | * 4 | */ 5 | define([ 6 | 'ash-core/family', 7 | 'ash-core/nodepool', 8 | 'ash-core/nodelist', 9 | 'brejep/dictionary' 10 | ], function (Family, NodePool, NodeList, Dictionary) { 11 | 'use strict'; 12 | 13 | var ComponentMatchingFamily = Family.extend({ 14 | constructor: function (nodeClass, engine) { 15 | this.nodeClass = nodeClass; 16 | this.engine = engine; 17 | this.__defineGetter__("nodeList", function() { 18 | return this.nodes; 19 | }); 20 | 21 | this.nodes = new NodeList(); 22 | this.entities = new Dictionary(); 23 | this.components = new Dictionary(); 24 | this.nodePool = new NodePool( this.nodeClass, this.components ); 25 | 26 | this.nodePool.dispose( this.nodePool.get() ); 27 | 28 | var nodeClassPrototype = this.nodeClass.prototype; 29 | 30 | for(var property in nodeClassPrototype) { 31 | ///TODO - tidy this up... 32 | if(nodeClassPrototype.hasOwnProperty(property) && 33 | property != "types" && 34 | property != "next" && 35 | property != "previous" && 36 | property != "constructor" && 37 | property != "super" && 38 | property != "extend" && 39 | property != "entity") { 40 | var componentObject = nodeClassPrototype.types[property]; 41 | this.components.add(componentObject, property); 42 | } 43 | } 44 | }, 45 | 46 | newEntity: function (entity) { 47 | this.addIfMatch(entity); 48 | }, 49 | 50 | componentAddedToEntity: function (entity, componentClass) { 51 | this.addIfMatch(entity); 52 | }, 53 | 54 | componentRemovedFromEntity: function (entity, componentClass) { 55 | if (this.components.has(componentClass)) { 56 | this.removeIfMatch(entity); 57 | } 58 | }, 59 | 60 | removeEntity: function (entity) { 61 | this.removeIfMatch(entity); 62 | }, 63 | 64 | cleanUp: function () { 65 | for (var node = this.nodes.head; node; node = node.next) { 66 | this.entities.remove(node.entity); 67 | } 68 | this.nodes.removeAll(); 69 | }, 70 | 71 | addIfMatch: function (entity) { 72 | if (!this.entities.has(entity)) { 73 | var componentClass; 74 | if ( 75 | !this.components.forEach(function(componentClass, componentName) { 76 | if(!entity.has(componentClass)) { 77 | return "return"; 78 | } 79 | }) 80 | ) { return; } 81 | var node = this.nodePool.get(); 82 | node.entity = entity; 83 | this.components.forEach(function (componentClass, componentName) { 84 | node[componentName] = entity.get(componentClass); 85 | }); 86 | this.entities.add(entity, node); 87 | entity.componentRemoved.add(this.componentRemovedFromEntity, this); 88 | this.nodes.add(node); 89 | } 90 | }, 91 | 92 | removeIfMatch: function (entity) { 93 | var entities = this.entities, 94 | nodes = this.nodes, 95 | engine = this.engine, 96 | nodePool = this.nodePool; 97 | 98 | if (entities.has(entity)) 99 | { 100 | var node = entities.retrieve(entity); 101 | entity.componentRemoved.remove(this.componentRemovedFromEntity, this); 102 | entities.remove(entity); 103 | nodes.remove(node); 104 | if (engine.updating) { 105 | nodePool.cache(node); 106 | engine.updateComplete.add(this.releaseNodePoolCache, this); 107 | } else { 108 | nodePool.dispose(node); 109 | } 110 | } 111 | }, 112 | 113 | releaseNodePoolCache: function () { 114 | this.engine.updateComplete.remove(this.releaseNodePoolCache); 115 | this.nodePool.releaseCache(); 116 | } 117 | }); 118 | 119 | return ComponentMatchingFamily; 120 | }); 121 | -------------------------------------------------------------------------------- /src/ash/core/engine.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Ash-js engine 3 | * 4 | */ 5 | define([ 6 | 'ash-core/componentmatchingfamily', 7 | 'ash-core/entitylist', 8 | 'ash-core/systemlist', 9 | 'signals', 10 | 'brejep/dictionary', 11 | 'brejep/class' 12 | ], function (ComponentMatchingFamily, EntityList, SystemList, signals, Dictionary, Class) { 13 | 'use strict'; 14 | 15 | var Engine = Class.extend({ 16 | familyClass: ComponentMatchingFamily, 17 | families: null, 18 | entityList: null, 19 | systemList: null, 20 | updating: false, 21 | updateComplete: new signals.Signal(), 22 | 23 | constructor: function () { 24 | this.entityList = new EntityList(), 25 | this.systemList = new SystemList(); 26 | this.families = new Dictionary(); 27 | 28 | this.__defineGetter__('entities', function() { 29 | var tmpEntities = []; 30 | for( var entity = this.entityList.head; entity; entity = entity.next ) 31 | { 32 | tmpEntities.push( entity ); 33 | } 34 | return tmpEntities; 35 | }); 36 | 37 | this.__defineGetter__('systems', function() { 38 | var tmpSystems = []; 39 | for( var system = this.systemList.head; system; system = system.next ) 40 | { 41 | tmpSystems.push( system ); 42 | } 43 | return tmpSystems; 44 | }); 45 | }, 46 | 47 | addEntity: function (entity) { 48 | this.entityList.add( entity ); 49 | entity.componentAdded.add( this.componentAdded, this ); 50 | this.families.forEach( function( nodeObject, family ) { 51 | family.newEntity( entity ); 52 | }); 53 | }, 54 | 55 | removeEntity: function (entity) { 56 | entity.componentAdded.remove( this.componentAdded, this ); 57 | this.families.forEach( function( nodeObject, family ) { 58 | family.removeEntity( entity ); 59 | }); 60 | this.entityList.remove( entity ); 61 | }, 62 | 63 | removeAllEntities: function () { 64 | while( this.entityList.head ) { 65 | this.removeEntity( this.entityList.head ); 66 | } 67 | }, 68 | 69 | componentAdded: function (entity, componentClass) { 70 | this.families.forEach( function( nodeObject, family ) { 71 | family.componentAddedToEntity( entity, componentClass ); 72 | }); 73 | }, 74 | 75 | getNodeList: function (nodeObject) { 76 | if( this.families.has( nodeObject ) ) { 77 | return this.families.retrieve( nodeObject ).nodes; 78 | } 79 | var family = new this.familyClass( nodeObject, this ); 80 | this.families.add( nodeObject, family ); 81 | for( var entity = this.entityList.head; entity; entity = entity.next ) { 82 | family.newEntity( entity ); 83 | } 84 | return family.nodes; 85 | }, 86 | 87 | releaseNodeList : function( nodeObject ) { 88 | if( this.families.has( nodeObject ) ) { 89 | this.families.retrieve( nodeObject ).cleanUp(); 90 | } 91 | this.families.remove( nodeObject ); 92 | }, 93 | 94 | addSystem : function( system, priority ) { 95 | system.priority = priority; 96 | system.addToEngine( this ); 97 | this.systemList.add( system ); 98 | }, 99 | 100 | getSystem : function( type ) { 101 | return this.systemList.get( type ); 102 | }, 103 | 104 | removeSystem : function( system ) { 105 | this.systemList.remove( system ); 106 | system.removeFromEngine( this ); 107 | }, 108 | 109 | removeAllSystems : function() { 110 | while( this.systemList.head ) { 111 | this.removeSystem( this.systemList.head ); 112 | } 113 | }, 114 | 115 | update : function( time ) { 116 | this.updating = true; 117 | for( var system = this.systemList.head; system; system = system.next ) { 118 | system.update( time ); 119 | } 120 | this.updating = false; 121 | this.updateComplete.dispatch(); 122 | } 123 | }); 124 | 125 | return Engine; 126 | }); 127 | -------------------------------------------------------------------------------- /src/ash/core/entity.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Ash-js Entity 3 | * 4 | */ 5 | define([ 6 | 'signals', 7 | 'brejep/dictionary', 8 | 'brejep/class' 9 | ], function (signals, Dictionary, Class) { 10 | 'use strict'; 11 | 12 | var Entity = Class.extend({ 13 | previous:null, /* Entity */ 14 | next: null, /* Entity */ 15 | components: null, 16 | 17 | constructor: function () { 18 | this.components = new Dictionary(); 19 | this.componentAdded = new signals.Signal(); 20 | this.componentRemoved = new signals.Signal(); 21 | }, 22 | 23 | add: function (component, componentClass ) { 24 | if( typeof componentClass === "undefined" ) 25 | { 26 | componentClass = component.constructor; 27 | } 28 | if ( this.components.has( componentClass ) ) 29 | { 30 | this.remove( componentClass ); 31 | } 32 | this.components.add(componentClass, component); 33 | this.componentAdded.dispatch( this, componentClass ); 34 | return this; 35 | }, 36 | 37 | remove: function ( componentClass ) { 38 | var component = this.components.retrieve( componentClass ); 39 | if ( component ) { 40 | this.components.remove( componentClass ); 41 | this.componentRemoved.dispatch( this, componentClass ); 42 | return component; 43 | } 44 | return null; 45 | }, 46 | 47 | get: function (componentClass) { 48 | return this.components.retrieve( componentClass ); 49 | }, 50 | 51 | /** 52 | * Get all components from the entity. 53 | * @return {Array} Contains all the components on the entity 54 | */ 55 | getAll: function () { 56 | var componentArray = []; 57 | this.components.forEach(function( componentObject, component ) { 58 | componentArray.push(component); 59 | }); 60 | return componentArray; 61 | }, 62 | 63 | has: function (componentClass) { 64 | return this.components.has( componentClass ); 65 | } 66 | }); 67 | 68 | return Entity; 69 | }); 70 | -------------------------------------------------------------------------------- /src/ash/core/entitylist.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Ash-js EntityList 3 | */ 4 | define([ 5 | 'brejep/class' 6 | ], function (Class) { 7 | 'use strict'; 8 | 9 | var EntityList = Class.extend({ 10 | head: null, /* Entity */ 11 | tail: null, /* Entity */ 12 | 13 | constructor: function () { }, 14 | 15 | add: function( entity ) { 16 | if( !this.head ) { 17 | this.head = this.tail = entity; 18 | } else { 19 | this.tail.next = entity; 20 | entity.previous = this.tail; 21 | this.tail = entity; 22 | } 23 | }, 24 | 25 | remove: function( entity ) { 26 | if ( this.head == entity ) { 27 | this.head = this.head.next; 28 | } 29 | if ( this.tail == entity ) { 30 | this.tail = this.tail.previous; 31 | } 32 | if ( entity.previous ) { 33 | entity.previous.next = entity.next; 34 | } 35 | if ( entity.next ) { 36 | entity.next.previous = entity.previous; 37 | } 38 | }, 39 | 40 | removeAll: function() { 41 | while( this.head ) { 42 | var entity = this.head; 43 | this.head = this.head.next; 44 | entity.previous = null; 45 | entity.next = null; 46 | } 47 | this.tail = null; 48 | } 49 | }); 50 | 51 | return EntityList; 52 | }); 53 | -------------------------------------------------------------------------------- /src/ash/core/family.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Ash-js Family 3 | */ 4 | define([ 5 | 'brejep/class' 6 | ], function (Class) { 7 | 'use strict'; 8 | 9 | var Family = Class.extend({ 10 | nodes: null, 11 | 12 | constructor: function (nodeObject, engine) { 13 | this.__defineGetter__("nodeList", function() { 14 | return this.nodes; 15 | }); 16 | }, 17 | 18 | newEntity: function (entity) { 19 | throw new Error( 'should be overriden' ); 20 | }, 21 | 22 | removeEntity: function (entity) { 23 | throw new Error( 'should be overriden' ); 24 | }, 25 | 26 | componentAddedToEntity: function (entity, componentClass) { 27 | throw new Error( 'should be overriden' ); 28 | }, 29 | 30 | componentRemovedFromEntity: function (entity, componentClass) { 31 | throw new Error( 'should be overriden' ); 32 | }, 33 | 34 | cleanUp: function () { 35 | throw new Error( 'should be overriden' ); 36 | } 37 | }); 38 | 39 | return Family; 40 | }); 41 | -------------------------------------------------------------------------------- /src/ash/core/node.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Ash-js Node 3 | */ 4 | define([ 5 | 'brejep/class' 6 | ], function (Class) { 7 | 'use strict'; 8 | 9 | var Node = Class.extend({ 10 | entity: null, 11 | previous: null, 12 | next: null, 13 | 14 | constructor: function () { } 15 | }); 16 | 17 | /** 18 | * A simpler way to create a node. 19 | * 20 | * Example: creating a node for component classes Point & energy: 21 | * 22 | * var PlayerNode = Ash.Node.create({ 23 | * point: Point, 24 | * energy: Energy 25 | * }); 26 | * 27 | * This is the simpler version from: 28 | * 29 | * var PlayerNode = Ash.Node.extend({ 30 | * point: null, 31 | * energy: null, 32 | * 33 | * types: { 34 | * point: Point, 35 | * energy: Energy 36 | * } 37 | * }); 38 | */ 39 | Node.create = function (schema) { 40 | var processedSchema = { 41 | types: {}, 42 | constructor: function () { } 43 | }; 44 | 45 | // process schema 46 | for (var propertyName in schema) { 47 | if (schema.hasOwnProperty(propertyName)) { 48 | var propertyType = schema[propertyName]; 49 | if (propertyType) { 50 | processedSchema.types[propertyName] = propertyType; 51 | } 52 | processedSchema[propertyName] = null; 53 | } 54 | } 55 | 56 | return Node.extend(processedSchema); 57 | }; 58 | 59 | return Node; 60 | }); 61 | -------------------------------------------------------------------------------- /src/ash/core/nodelist.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Ash-js Node List 3 | */ 4 | define([ 5 | 'signals', 6 | 'brejep/class' 7 | ], function (signals, Class) { 8 | 'use strict'; 9 | 10 | var NodeList = Class.extend({ 11 | constructor: function () { 12 | this.head = null; 13 | this.tail = null; 14 | this.nodeAdded = new signals.Signal(); 15 | this.nodeRemoved = new signals.Signal(); 16 | }, 17 | 18 | add: function( node ) { 19 | if( !this.head ) { 20 | this.head = this.tail = node; 21 | } else { 22 | this.tail.next = node; 23 | node.previous = this.tail; 24 | this.tail = node; 25 | } 26 | this.nodeAdded.dispatch( node ); 27 | }, 28 | 29 | remove: function( node ) { 30 | if( this.head == node ) { 31 | this.head = this.head.next; 32 | } 33 | if( this.tail == node ) { 34 | this.tail = this.tail.previous; 35 | } 36 | if( node.previous ) { 37 | node.previous.next = node.next; 38 | } 39 | if( node.next ) { 40 | node.next.previous = node.previous; 41 | } 42 | this.nodeRemoved.dispatch( node ); 43 | }, 44 | 45 | removeAll: function() { 46 | while( this.head ) { 47 | var node = this.head; 48 | this.head = node.next; 49 | node.previous = null; 50 | node.next = null; 51 | this.nodeRemoved.dispatch( node ); 52 | } 53 | this.tail = null; 54 | }, 55 | 56 | empty: function() { 57 | return this.head === null; 58 | }, 59 | 60 | swap: function( node1, node2 ) { 61 | if( node1.previous == node2 ) { 62 | node1.previous = node2.previous; 63 | node2.previous = node1; 64 | node2.next = node1.next; 65 | node1.next = node2; 66 | } else if( node2.previous == node1 ) { 67 | node2.previous = node1.previous; 68 | node1.previous = node2; 69 | node1.next = node2.next; 70 | node2.next = node1; 71 | } else { 72 | var temp = node1.previous; 73 | node1.previous = node2.previous; 74 | node2.previous = temp; 75 | temp = node1.next; 76 | node1.next = node2.next; 77 | node2.next = temp; 78 | } 79 | if( this.head == node1 ) { 80 | this.head = node2; 81 | } else if( this.head == node2 ) { 82 | this.head = node1; 83 | } 84 | if( this.tail == node1 ) { 85 | this.tail = node2; 86 | } else if( this.tail == node2 ) { 87 | this.tail = node1; 88 | } 89 | if( node1.previous ) { 90 | node1.previous.next = node1; 91 | } 92 | if( node2.previous ) { 93 | node2.previous.next = node2; 94 | } 95 | if( node1.next ) { 96 | node1.next.previous = node1; 97 | } 98 | if( node2.next ) { 99 | node2.next.previous = node2; 100 | } 101 | }, 102 | 103 | insertionSort: function( sortFunction ) { 104 | if( this.head == this.tail ) { 105 | return; 106 | } 107 | var remains = this.head.next; 108 | for( var node = remains; node; node = remains ) { 109 | remains = node.next; 110 | for( var other = node.previous; other; other = other.previous ) { 111 | if( sortFunction( node, other ) >= 0 ) { 112 | if( node != other.next ) { 113 | if( this.tail == node ) { 114 | this.tail = node.previous; 115 | } 116 | node.previous.next = node.next; 117 | if( node.next ) { 118 | node.next.previous = node.previous; 119 | } 120 | node.next = other.next; 121 | node.previous = other; 122 | node.next.previous = node; 123 | other.next = node; 124 | } 125 | break; 126 | } 127 | } 128 | if( !other ) { 129 | if( this.tail == node ) { 130 | this.tail = node.previous; 131 | } 132 | node.previous.next = node.next; 133 | if( node.next ) { 134 | node.next.previous = node.previous; 135 | } 136 | node.next = this.head; 137 | this.head.previous = node; 138 | node.previous = null; 139 | this.head = node; 140 | } 141 | } 142 | }, 143 | 144 | mergeSort: function( sortFunction ) { 145 | if( this.head == this.tail ) { 146 | return; 147 | } 148 | var lists = [], 149 | start = this.head, 150 | end; 151 | while( start ) { 152 | end = start; 153 | while( end.next && sortFunction( end, end.next ) <= 0 ) { 154 | end = end.next; 155 | } 156 | var next = end.next; 157 | start.previous = end.next = null; 158 | lists.push( start ); 159 | start = next; 160 | } 161 | while( lists.length > 1 ) { 162 | lists.push( this.merge( lists.shift(), lists.shift(), sortFunction ) ); 163 | } 164 | this.tail = this.head = lists[0]; 165 | while( this.tail.next ) { 166 | this.tail = this.tail.next; 167 | } 168 | }, 169 | 170 | merge: function( head1, head2, sortFunction ) { 171 | var node, 172 | head; 173 | if( sortFunction( head1, head2 ) <= 0 ) { 174 | head = node = head1; 175 | head1 = head1.next; 176 | } else { 177 | head = node = head2; 178 | head2 = head2.next; 179 | } 180 | while( head1 && head2 ) { 181 | if( sortFunction( head1, head2 ) <= 0 ) { 182 | node.next = head1; 183 | head1.previous = node; 184 | node = head1; 185 | head1 = head1.next; 186 | } else { 187 | node.next = head2; 188 | head2.previous = node; 189 | node = head2; 190 | head2 = head2.next; 191 | } 192 | } 193 | if( head1 ) { 194 | node.next = head1; 195 | head1.previous = node; 196 | } else { 197 | node.next = head2; 198 | head2.previous = node; 199 | } 200 | return head; 201 | } 202 | }); 203 | 204 | return NodeList; 205 | }); 206 | -------------------------------------------------------------------------------- /src/ash/core/nodepool.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Ash-js Node Pool 3 | */ 4 | define([ 5 | 'brejep/class' 6 | ], function (Class) { 7 | 'use strict'; 8 | 9 | var NodePool = Class.extend({ 10 | tail: null, 11 | cacheTail: null, 12 | nodeClass: null, 13 | components : null, 14 | 15 | constructor: function (nodeClass, components) { 16 | this.nodeClass = nodeClass; 17 | this.components = components; 18 | }, 19 | 20 | get: function() { 21 | if( this.tail ) { 22 | var node = this.tail; 23 | this.tail = this.tail.previous; 24 | node.previous = null; 25 | return node; 26 | } else { 27 | return new this.nodeClass(); 28 | } 29 | }, 30 | 31 | dispose: function( node ) { 32 | this.components.forEach(function(componentClass, componentName) { 33 | node[componentName] = null; 34 | }); 35 | node.entity = null; 36 | node.next = null; 37 | node.previous = this.tail; 38 | this.tail = node; 39 | }, 40 | 41 | cache: function( node ) { 42 | node.previous = this.cacheTail; 43 | this.cacheTail = node; 44 | }, 45 | 46 | releaseCache: function() { 47 | while( this.cacheTail ) { 48 | var node = this.cacheTail; 49 | this.cacheTail = node.previous; 50 | this.dispose( node ); 51 | } 52 | } 53 | }); 54 | 55 | return NodePool; 56 | }); 57 | -------------------------------------------------------------------------------- /src/ash/core/system.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Ash-js System 3 | */ 4 | define([ 5 | 'brejep/class' 6 | ], function (Class) { 7 | 'use strict'; 8 | 9 | var System = Class.extend({ 10 | previous: null, /* System */ 11 | next: null, /* System */ 12 | priority: 0, 13 | 14 | constructor: function () { }, 15 | 16 | addToEngine: function (engine) { 17 | /* Left deliberately blank */ 18 | }, 19 | 20 | removeFromEngine: function (engine) { 21 | /* Left deliberately blank */ 22 | }, 23 | 24 | update: function (time) { 25 | /* Left deliberately blank */ 26 | }, 27 | 28 | is: function (type) { 29 | return type.prototype.isPrototypeOf(this); 30 | } 31 | }); 32 | 33 | return System; 34 | }); 35 | -------------------------------------------------------------------------------- /src/ash/core/systemlist.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Ash-js System List 3 | */ 4 | define([ 5 | 'brejep/class' 6 | ], function (Class) { 7 | 'use strict'; 8 | 9 | var SystemList = Class.extend({ 10 | head: null, /* System */ 11 | tail: null, /* System */ 12 | 13 | constructor: function () { }, 14 | 15 | add: function( system ) { 16 | if( !this.head ) { 17 | this.head = this.tail = system; 18 | system.next = system.previous = null; 19 | } else { 20 | for( var node = this.tail; node; node = node.previous ) { 21 | if( node.priority <= system.priority ) { 22 | break; 23 | } 24 | } 25 | if( node === this.tail ) { 26 | this.tail.next = system; 27 | system.previous = this.tail; 28 | system.next = null; 29 | this.tail = system; 30 | } else if( !node ) { 31 | system.next = this.head; 32 | system.previous = null; 33 | this.head.previous = system; 34 | this.head = system; 35 | } else { 36 | system.next = node.next; 37 | system.previous = node; 38 | node.next.previous = system; 39 | node.next = system; 40 | } 41 | } 42 | }, 43 | 44 | remove: function( system ) { 45 | if ( this.head === system ) { 46 | this.head = this.head.next; 47 | } 48 | if ( this.tail === system ) { 49 | this.tail = this.tail.previous; 50 | } 51 | if ( system.previous ) { 52 | system.previous.next = system.next; 53 | } 54 | if ( system.next ) { 55 | system.next.previous = system.previous; 56 | } 57 | }, 58 | 59 | removeAll: function() { 60 | while( this.head ) 61 | { 62 | var system = this.head; 63 | this.head = this.head.next; 64 | system.previous = null; 65 | system.next = null; 66 | } 67 | this.tail = null; 68 | }, 69 | 70 | get: function( type ) { 71 | for( var system = this.head; system; system = system.next ) { 72 | if ( system.is( type ) ) { 73 | return system; 74 | } 75 | } 76 | return null; 77 | } 78 | }); 79 | 80 | return SystemList; 81 | }); 82 | -------------------------------------------------------------------------------- /test/lib/qunit.css: -------------------------------------------------------------------------------- 1 | /** 2 | * QUnit v1.3.0pre - A JavaScript Unit Testing Framework 3 | * 4 | * http://docs.jquery.com/QUnit 5 | * 6 | * Copyright (c) 2011 John Resig, Jörn Zaefferer 7 | * Dual licensed under the MIT (MIT-LICENSE.txt) 8 | * or GPL (GPL-LICENSE.txt) licenses. 9 | * Pulled Live from Git Thu Dec 1 08:35:01 UTC 2011 10 | * Last Commit: c319c356cb4942a16ea62e5b9020044e7d97c1b9 11 | */ 12 | 13 | /** Font Family and Sizes */ 14 | 15 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult { 16 | font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif; 17 | } 18 | 19 | #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } 20 | #qunit-tests { font-size: smaller; } 21 | 22 | 23 | /** Resets */ 24 | 25 | #qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult { 26 | margin: 0; 27 | padding: 0; 28 | } 29 | 30 | 31 | /** Header */ 32 | 33 | #qunit-header { 34 | padding: 0.5em 0 0.5em 1em; 35 | 36 | color: #8699a4; 37 | background-color: #0d3349; 38 | 39 | font-size: 1.5em; 40 | line-height: 1em; 41 | font-weight: normal; 42 | 43 | border-radius: 15px 15px 0 0; 44 | -moz-border-radius: 15px 15px 0 0; 45 | -webkit-border-top-right-radius: 15px; 46 | -webkit-border-top-left-radius: 15px; 47 | } 48 | 49 | #qunit-header a { 50 | text-decoration: none; 51 | color: #c2ccd1; 52 | } 53 | 54 | #qunit-header a:hover, 55 | #qunit-header a:focus { 56 | color: #fff; 57 | } 58 | 59 | #qunit-banner { 60 | height: 5px; 61 | } 62 | 63 | #qunit-testrunner-toolbar { 64 | padding: 0.5em 0 0.5em 2em; 65 | color: #5E740B; 66 | background-color: #eee; 67 | } 68 | 69 | #qunit-userAgent { 70 | padding: 0.5em 0 0.5em 2.5em; 71 | background-color: #2b81af; 72 | color: #fff; 73 | text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; 74 | } 75 | 76 | 77 | /** Tests: Pass/Fail */ 78 | 79 | #qunit-tests { 80 | list-style-position: inside; 81 | } 82 | 83 | #qunit-tests li { 84 | padding: 0.4em 0.5em 0.4em 2.5em; 85 | border-bottom: 1px solid #fff; 86 | list-style-position: inside; 87 | } 88 | 89 | #qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running { 90 | display: none; 91 | } 92 | 93 | #qunit-tests li strong { 94 | cursor: pointer; 95 | } 96 | 97 | #qunit-tests li a { 98 | padding: 0.5em; 99 | color: #c2ccd1; 100 | text-decoration: none; 101 | } 102 | #qunit-tests li a:hover, 103 | #qunit-tests li a:focus { 104 | color: #000; 105 | } 106 | 107 | #qunit-tests ol { 108 | margin-top: 0.5em; 109 | padding: 0.5em; 110 | 111 | background-color: #fff; 112 | 113 | border-radius: 15px; 114 | -moz-border-radius: 15px; 115 | -webkit-border-radius: 15px; 116 | 117 | box-shadow: inset 0px 2px 13px #999; 118 | -moz-box-shadow: inset 0px 2px 13px #999; 119 | -webkit-box-shadow: inset 0px 2px 13px #999; 120 | } 121 | 122 | #qunit-tests table { 123 | border-collapse: collapse; 124 | margin-top: .2em; 125 | } 126 | 127 | #qunit-tests th { 128 | text-align: right; 129 | vertical-align: top; 130 | padding: 0 .5em 0 0; 131 | } 132 | 133 | #qunit-tests td { 134 | vertical-align: top; 135 | } 136 | 137 | #qunit-tests pre { 138 | margin: 0; 139 | white-space: pre-wrap; 140 | word-wrap: break-word; 141 | } 142 | 143 | #qunit-tests del { 144 | background-color: #e0f2be; 145 | color: #374e0c; 146 | text-decoration: none; 147 | } 148 | 149 | #qunit-tests ins { 150 | background-color: #ffcaca; 151 | color: #500; 152 | text-decoration: none; 153 | } 154 | 155 | /*** Test Counts */ 156 | 157 | #qunit-tests b.counts { color: black; } 158 | #qunit-tests b.passed { color: #5E740B; } 159 | #qunit-tests b.failed { color: #710909; } 160 | 161 | #qunit-tests li li { 162 | margin: 0.5em; 163 | padding: 0.4em 0.5em 0.4em 0.5em; 164 | background-color: #fff; 165 | border-bottom: none; 166 | list-style-position: inside; 167 | } 168 | 169 | /*** Passing Styles */ 170 | 171 | #qunit-tests li li.pass { 172 | color: #5E740B; 173 | background-color: #fff; 174 | border-left: 26px solid #C6E746; 175 | } 176 | 177 | #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } 178 | #qunit-tests .pass .test-name { color: #366097; } 179 | 180 | #qunit-tests .pass .test-actual, 181 | #qunit-tests .pass .test-expected { color: #999999; } 182 | 183 | #qunit-banner.qunit-pass { background-color: #C6E746; } 184 | 185 | /*** Failing Styles */ 186 | 187 | #qunit-tests li li.fail { 188 | color: #710909; 189 | background-color: #fff; 190 | border-left: 26px solid #EE5757; 191 | white-space: pre; 192 | } 193 | 194 | #qunit-tests > li:last-child { 195 | border-radius: 0 0 15px 15px; 196 | -moz-border-radius: 0 0 15px 15px; 197 | -webkit-border-bottom-right-radius: 15px; 198 | -webkit-border-bottom-left-radius: 15px; 199 | } 200 | 201 | #qunit-tests .fail { color: #000000; background-color: #EE5757; } 202 | #qunit-tests .fail .test-name, 203 | #qunit-tests .fail .module-name { color: #000000; } 204 | 205 | #qunit-tests .fail .test-actual { color: #EE5757; } 206 | #qunit-tests .fail .test-expected { color: green; } 207 | 208 | #qunit-banner.qunit-fail { background-color: #EE5757; } 209 | 210 | 211 | /** Result */ 212 | 213 | #qunit-testresult { 214 | padding: 0.5em 0.5em 0.5em 2.5em; 215 | 216 | color: #2b81af; 217 | background-color: #D2E0E6; 218 | 219 | border-bottom: 1px solid white; 220 | } 221 | 222 | /** Fixture */ 223 | 224 | #qunit-fixture { 225 | position: absolute; 226 | top: -10000px; 227 | left: -10000px; 228 | } 229 | -------------------------------------------------------------------------------- /test/lib/qunit.js: -------------------------------------------------------------------------------- 1 | /** 2 | * QUnit v1.3.0pre - A JavaScript Unit Testing Framework 3 | * 4 | * http://docs.jquery.com/QUnit 5 | * 6 | * Copyright (c) 2011 John Resig, Jörn Zaefferer 7 | * Dual licensed under the MIT (MIT-LICENSE.txt) 8 | * or GPL (GPL-LICENSE.txt) licenses. 9 | * Pulled Live from Git Thu Dec 1 08:35:01 UTC 2011 10 | * Last Commit: c319c356cb4942a16ea62e5b9020044e7d97c1b9 11 | */ 12 | 13 | (function(window) { 14 | 15 | var defined = { 16 | setTimeout: typeof window.setTimeout !== "undefined", 17 | sessionStorage: (function() { 18 | try { 19 | return !!sessionStorage.getItem; 20 | } catch(e) { 21 | return false; 22 | } 23 | })() 24 | }; 25 | 26 | var testId = 0, 27 | toString = Object.prototype.toString, 28 | hasOwn = Object.prototype.hasOwnProperty; 29 | 30 | var Test = function(name, testName, expected, testEnvironmentArg, async, callback) { 31 | this.name = name; 32 | this.testName = testName; 33 | this.expected = expected; 34 | this.testEnvironmentArg = testEnvironmentArg; 35 | this.async = async; 36 | this.callback = callback; 37 | this.assertions = []; 38 | }; 39 | Test.prototype = { 40 | init: function() { 41 | var tests = id("qunit-tests"); 42 | if (tests) { 43 | var b = document.createElement("strong"); 44 | b.innerHTML = "Running " + this.name; 45 | var li = document.createElement("li"); 46 | li.appendChild( b ); 47 | li.className = "running"; 48 | li.id = this.id = "test-output" + testId++; 49 | tests.appendChild( li ); 50 | } 51 | }, 52 | setup: function() { 53 | if (this.module != config.previousModule) { 54 | if ( config.previousModule ) { 55 | runLoggingCallbacks('moduleDone', QUnit, { 56 | name: config.previousModule, 57 | failed: config.moduleStats.bad, 58 | passed: config.moduleStats.all - config.moduleStats.bad, 59 | total: config.moduleStats.all 60 | } ); 61 | } 62 | config.previousModule = this.module; 63 | config.moduleStats = { all: 0, bad: 0 }; 64 | runLoggingCallbacks( 'moduleStart', QUnit, { 65 | name: this.module 66 | } ); 67 | } 68 | 69 | config.current = this; 70 | this.testEnvironment = extend({ 71 | setup: function() {}, 72 | teardown: function() {} 73 | }, this.moduleTestEnvironment); 74 | if (this.testEnvironmentArg) { 75 | extend(this.testEnvironment, this.testEnvironmentArg); 76 | } 77 | 78 | runLoggingCallbacks( 'testStart', QUnit, { 79 | name: this.testName, 80 | module: this.module 81 | }); 82 | 83 | // allow utility functions to access the current test environment 84 | // TODO why?? 85 | QUnit.current_testEnvironment = this.testEnvironment; 86 | 87 | try { 88 | if ( !config.pollution ) { 89 | saveGlobal(); 90 | } 91 | 92 | this.testEnvironment.setup.call(this.testEnvironment); 93 | } catch(e) { 94 | QUnit.ok( false, "Setup failed on " + this.testName + ": " + e.message ); 95 | } 96 | }, 97 | run: function() { 98 | config.current = this; 99 | if ( this.async ) { 100 | QUnit.stop(); 101 | } 102 | 103 | if ( config.notrycatch ) { 104 | this.callback.call(this.testEnvironment); 105 | return; 106 | } 107 | try { 108 | this.callback.call(this.testEnvironment); 109 | } catch(e) { 110 | fail("Test " + this.testName + " died, exception and test follows", e, this.callback); 111 | QUnit.ok( false, "Died on test #" + (this.assertions.length + 1) + ": " + e.message + " - " + QUnit.jsDump.parse(e) ); 112 | // else next test will carry the responsibility 113 | saveGlobal(); 114 | 115 | // Restart the tests if they're blocking 116 | if ( config.blocking ) { 117 | QUnit.start(); 118 | } 119 | } 120 | }, 121 | teardown: function() { 122 | config.current = this; 123 | try { 124 | this.testEnvironment.teardown.call(this.testEnvironment); 125 | checkPollution(); 126 | } catch(e) { 127 | QUnit.ok( false, "Teardown failed on " + this.testName + ": " + e.message ); 128 | } 129 | }, 130 | finish: function() { 131 | config.current = this; 132 | if ( this.expected != null && this.expected != this.assertions.length ) { 133 | QUnit.ok( false, "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run" ); 134 | } 135 | 136 | var good = 0, bad = 0, 137 | tests = id("qunit-tests"); 138 | 139 | config.stats.all += this.assertions.length; 140 | config.moduleStats.all += this.assertions.length; 141 | 142 | if ( tests ) { 143 | var ol = document.createElement("ol"); 144 | 145 | for ( var i = 0; i < this.assertions.length; i++ ) { 146 | var assertion = this.assertions[i]; 147 | 148 | var li = document.createElement("li"); 149 | li.className = assertion.result ? "pass" : "fail"; 150 | li.innerHTML = assertion.message || (assertion.result ? "okay" : "failed"); 151 | ol.appendChild( li ); 152 | 153 | if ( assertion.result ) { 154 | good++; 155 | } else { 156 | bad++; 157 | config.stats.bad++; 158 | config.moduleStats.bad++; 159 | } 160 | } 161 | 162 | // store result when possible 163 | if ( QUnit.config.reorder && defined.sessionStorage ) { 164 | if (bad) { 165 | sessionStorage.setItem("qunit-" + this.module + "-" + this.testName, bad); 166 | } else { 167 | sessionStorage.removeItem("qunit-" + this.module + "-" + this.testName); 168 | } 169 | } 170 | 171 | if (bad == 0) { 172 | ol.style.display = "none"; 173 | } 174 | 175 | var b = document.createElement("strong"); 176 | b.innerHTML = this.name + " (" + bad + ", " + good + ", " + this.assertions.length + ")"; 177 | 178 | var a = document.createElement("a"); 179 | a.innerHTML = "Rerun"; 180 | a.href = QUnit.url({ filter: getText([b]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") }); 181 | 182 | addEvent(b, "click", function() { 183 | var next = b.nextSibling.nextSibling, 184 | display = next.style.display; 185 | next.style.display = display === "none" ? "block" : "none"; 186 | }); 187 | 188 | addEvent(b, "dblclick", function(e) { 189 | var target = e && e.target ? e.target : window.event.srcElement; 190 | if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) { 191 | target = target.parentNode; 192 | } 193 | if ( window.location && target.nodeName.toLowerCase() === "strong" ) { 194 | window.location = QUnit.url({ filter: getText([target]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") }); 195 | } 196 | }); 197 | 198 | var li = id(this.id); 199 | li.className = bad ? "fail" : "pass"; 200 | li.removeChild( li.firstChild ); 201 | li.appendChild( b ); 202 | li.appendChild( a ); 203 | li.appendChild( ol ); 204 | 205 | } else { 206 | for ( var i = 0; i < this.assertions.length; i++ ) { 207 | if ( !this.assertions[i].result ) { 208 | bad++; 209 | config.stats.bad++; 210 | config.moduleStats.bad++; 211 | } 212 | } 213 | } 214 | 215 | try { 216 | QUnit.reset(); 217 | } catch(e) { 218 | fail("reset() failed, following Test " + this.testName + ", exception and reset fn follows", e, QUnit.reset); 219 | } 220 | 221 | runLoggingCallbacks( 'testDone', QUnit, { 222 | name: this.testName, 223 | module: this.module, 224 | failed: bad, 225 | passed: this.assertions.length - bad, 226 | total: this.assertions.length 227 | } ); 228 | }, 229 | 230 | queue: function() { 231 | var test = this; 232 | synchronize(function() { 233 | test.init(); 234 | }); 235 | function run() { 236 | // each of these can by async 237 | synchronize(function() { 238 | test.setup(); 239 | }); 240 | synchronize(function() { 241 | test.run(); 242 | }); 243 | synchronize(function() { 244 | test.teardown(); 245 | }); 246 | synchronize(function() { 247 | test.finish(); 248 | }); 249 | } 250 | // defer when previous test run passed, if storage is available 251 | var bad = QUnit.config.reorder && defined.sessionStorage && +sessionStorage.getItem("qunit-" + this.module + "-" + this.testName); 252 | if (bad) { 253 | run(); 254 | } else { 255 | synchronize(run, true); 256 | }; 257 | } 258 | 259 | }; 260 | 261 | var QUnit = { 262 | 263 | // call on start of module test to prepend name to all tests 264 | module: function(name, testEnvironment) { 265 | config.currentModule = name; 266 | config.currentModuleTestEnviroment = testEnvironment; 267 | }, 268 | 269 | asyncTest: function(testName, expected, callback) { 270 | if ( arguments.length === 2 ) { 271 | callback = expected; 272 | expected = null; 273 | } 274 | 275 | QUnit.test(testName, expected, callback, true); 276 | }, 277 | 278 | test: function(testName, expected, callback, async) { 279 | var name = '' + testName + '', testEnvironmentArg; 280 | 281 | if ( arguments.length === 2 ) { 282 | callback = expected; 283 | expected = null; 284 | } 285 | // is 2nd argument a testEnvironment? 286 | if ( expected && typeof expected === 'object') { 287 | testEnvironmentArg = expected; 288 | expected = null; 289 | } 290 | 291 | if ( config.currentModule ) { 292 | name = '' + config.currentModule + ": " + name; 293 | } 294 | 295 | if ( !validTest(config.currentModule + ": " + testName) ) { 296 | return; 297 | } 298 | 299 | var test = new Test(name, testName, expected, testEnvironmentArg, async, callback); 300 | test.module = config.currentModule; 301 | test.moduleTestEnvironment = config.currentModuleTestEnviroment; 302 | test.queue(); 303 | }, 304 | 305 | /** 306 | * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. 307 | */ 308 | expect: function(asserts) { 309 | config.current.expected = asserts; 310 | }, 311 | 312 | /** 313 | * Asserts true. 314 | * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); 315 | */ 316 | ok: function(a, msg) { 317 | a = !!a; 318 | var details = { 319 | result: a, 320 | message: msg 321 | }; 322 | msg = escapeInnerText(msg); 323 | runLoggingCallbacks( 'log', QUnit, details ); 324 | config.current.assertions.push({ 325 | result: a, 326 | message: msg 327 | }); 328 | }, 329 | 330 | /** 331 | * Checks that the first two arguments are equal, with an optional message. 332 | * Prints out both actual and expected values. 333 | * 334 | * Prefered to ok( actual == expected, message ) 335 | * 336 | * @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." ); 337 | * 338 | * @param Object actual 339 | * @param Object expected 340 | * @param String message (optional) 341 | */ 342 | equal: function(actual, expected, message) { 343 | QUnit.push(expected == actual, actual, expected, message); 344 | }, 345 | 346 | notEqual: function(actual, expected, message) { 347 | QUnit.push(expected != actual, actual, expected, message); 348 | }, 349 | 350 | deepEqual: function(actual, expected, message) { 351 | QUnit.push(QUnit.equiv(actual, expected), actual, expected, message); 352 | }, 353 | 354 | notDeepEqual: function(actual, expected, message) { 355 | QUnit.push(!QUnit.equiv(actual, expected), actual, expected, message); 356 | }, 357 | 358 | strictEqual: function(actual, expected, message) { 359 | QUnit.push(expected === actual, actual, expected, message); 360 | }, 361 | 362 | notStrictEqual: function(actual, expected, message) { 363 | QUnit.push(expected !== actual, actual, expected, message); 364 | }, 365 | 366 | raises: function(block, expected, message) { 367 | var actual, ok = false; 368 | 369 | if (typeof expected === 'string') { 370 | message = expected; 371 | expected = null; 372 | } 373 | 374 | try { 375 | block(); 376 | } catch (e) { 377 | actual = e; 378 | } 379 | 380 | if (actual) { 381 | // we don't want to validate thrown error 382 | if (!expected) { 383 | ok = true; 384 | // expected is a regexp 385 | } else if (QUnit.objectType(expected) === "regexp") { 386 | ok = expected.test(actual); 387 | // expected is a constructor 388 | } else if (actual instanceof expected) { 389 | ok = true; 390 | // expected is a validation function which returns true is validation passed 391 | } else if (expected.call({}, actual) === true) { 392 | ok = true; 393 | } 394 | } 395 | 396 | QUnit.ok(ok, message); 397 | }, 398 | 399 | start: function(count) { 400 | config.semaphore -= count || 1; 401 | if (config.semaphore > 0) { 402 | // don't start until equal number of stop-calls 403 | return; 404 | } 405 | if (config.semaphore < 0) { 406 | // ignore if start is called more often then stop 407 | config.semaphore = 0; 408 | } 409 | // A slight delay, to avoid any current callbacks 410 | if ( defined.setTimeout ) { 411 | window.setTimeout(function() { 412 | if (config.semaphore > 0) { 413 | return; 414 | } 415 | if ( config.timeout ) { 416 | clearTimeout(config.timeout); 417 | } 418 | 419 | config.blocking = false; 420 | process(true); 421 | }, 13); 422 | } else { 423 | config.blocking = false; 424 | process(true); 425 | } 426 | }, 427 | 428 | stop: function(count) { 429 | config.semaphore += count || 1; 430 | config.blocking = true; 431 | 432 | if ( config.testTimeout && defined.setTimeout ) { 433 | clearTimeout(config.timeout); 434 | config.timeout = window.setTimeout(function() { 435 | QUnit.ok( false, "Test timed out" ); 436 | config.semaphore = 1; 437 | QUnit.start(); 438 | }, config.testTimeout); 439 | } 440 | } 441 | }; 442 | 443 | //We want access to the constructor's prototype 444 | (function() { 445 | function F(){}; 446 | F.prototype = QUnit; 447 | QUnit = new F(); 448 | //Make F QUnit's constructor so that we can add to the prototype later 449 | QUnit.constructor = F; 450 | })(); 451 | 452 | // Backwards compatibility, deprecated 453 | QUnit.equals = QUnit.equal; 454 | QUnit.same = QUnit.deepEqual; 455 | 456 | // Maintain internal state 457 | var config = { 458 | // The queue of tests to run 459 | queue: [], 460 | 461 | // block until document ready 462 | blocking: true, 463 | 464 | // when enabled, show only failing tests 465 | // gets persisted through sessionStorage and can be changed in UI via checkbox 466 | hidepassed: false, 467 | 468 | // by default, run previously failed tests first 469 | // very useful in combination with "Hide passed tests" checked 470 | reorder: true, 471 | 472 | // by default, modify document.title when suite is done 473 | altertitle: true, 474 | 475 | urlConfig: ['noglobals', 'notrycatch'], 476 | 477 | //logging callback queues 478 | begin: [], 479 | done: [], 480 | log: [], 481 | testStart: [], 482 | testDone: [], 483 | moduleStart: [], 484 | moduleDone: [] 485 | }; 486 | 487 | // Load paramaters 488 | (function() { 489 | var location = window.location || { search: "", protocol: "file:" }, 490 | params = location.search.slice( 1 ).split( "&" ), 491 | length = params.length, 492 | urlParams = {}, 493 | current; 494 | 495 | if ( params[ 0 ] ) { 496 | for ( var i = 0; i < length; i++ ) { 497 | current = params[ i ].split( "=" ); 498 | current[ 0 ] = decodeURIComponent( current[ 0 ] ); 499 | // allow just a key to turn on a flag, e.g., test.html?noglobals 500 | current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true; 501 | urlParams[ current[ 0 ] ] = current[ 1 ]; 502 | } 503 | } 504 | 505 | QUnit.urlParams = urlParams; 506 | config.filter = urlParams.filter; 507 | 508 | // Figure out if we're running the tests from a server or not 509 | QUnit.isLocal = !!(location.protocol === 'file:'); 510 | })(); 511 | 512 | // Expose the API as global variables, unless an 'exports' 513 | // object exists, in that case we assume we're in CommonJS 514 | if ( typeof exports === "undefined" || typeof require === "undefined" ) { 515 | extend(window, QUnit); 516 | window.QUnit = QUnit; 517 | } else { 518 | extend(exports, QUnit); 519 | exports.QUnit = QUnit; 520 | } 521 | 522 | // define these after exposing globals to keep them in these QUnit namespace only 523 | extend(QUnit, { 524 | config: config, 525 | 526 | // Initialize the configuration options 527 | init: function() { 528 | extend(config, { 529 | stats: { all: 0, bad: 0 }, 530 | moduleStats: { all: 0, bad: 0 }, 531 | started: +new Date, 532 | updateRate: 1000, 533 | blocking: false, 534 | autostart: true, 535 | autorun: false, 536 | filter: "", 537 | queue: [], 538 | semaphore: 0 539 | }); 540 | 541 | var tests = id( "qunit-tests" ), 542 | banner = id( "qunit-banner" ), 543 | result = id( "qunit-testresult" ); 544 | 545 | if ( tests ) { 546 | tests.innerHTML = ""; 547 | } 548 | 549 | if ( banner ) { 550 | banner.className = ""; 551 | } 552 | 553 | if ( result ) { 554 | result.parentNode.removeChild( result ); 555 | } 556 | 557 | if ( tests ) { 558 | result = document.createElement( "p" ); 559 | result.id = "qunit-testresult"; 560 | result.className = "result"; 561 | tests.parentNode.insertBefore( result, tests ); 562 | result.innerHTML = 'Running...
 '; 563 | } 564 | }, 565 | 566 | /** 567 | * Resets the test setup. Useful for tests that modify the DOM. 568 | * 569 | * If jQuery is available, uses jQuery's html(), otherwise just innerHTML. 570 | */ 571 | reset: function() { 572 | if ( window.jQuery ) { 573 | jQuery( "#qunit-fixture" ).html( config.fixture ); 574 | } else { 575 | var main = id( 'qunit-fixture' ); 576 | if ( main ) { 577 | main.innerHTML = config.fixture; 578 | } 579 | } 580 | }, 581 | 582 | /** 583 | * Trigger an event on an element. 584 | * 585 | * @example triggerEvent( document.body, "click" ); 586 | * 587 | * @param DOMElement elem 588 | * @param String type 589 | */ 590 | triggerEvent: function( elem, type, event ) { 591 | if ( document.createEvent ) { 592 | event = document.createEvent("MouseEvents"); 593 | event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView, 594 | 0, 0, 0, 0, 0, false, false, false, false, 0, null); 595 | elem.dispatchEvent( event ); 596 | 597 | } else if ( elem.fireEvent ) { 598 | elem.fireEvent("on"+type); 599 | } 600 | }, 601 | 602 | // Safe object type checking 603 | is: function( type, obj ) { 604 | return QUnit.objectType( obj ) == type; 605 | }, 606 | 607 | objectType: function( obj ) { 608 | if (typeof obj === "undefined") { 609 | return "undefined"; 610 | 611 | // consider: typeof null === object 612 | } 613 | if (obj === null) { 614 | return "null"; 615 | } 616 | 617 | var type = toString.call( obj ).match(/^\[object\s(.*)\]$/)[1] || ''; 618 | 619 | switch (type) { 620 | case 'Number': 621 | if (isNaN(obj)) { 622 | return "nan"; 623 | } else { 624 | return "number"; 625 | } 626 | case 'String': 627 | case 'Boolean': 628 | case 'Array': 629 | case 'Date': 630 | case 'RegExp': 631 | case 'Function': 632 | return type.toLowerCase(); 633 | } 634 | if (typeof obj === "object") { 635 | return "object"; 636 | } 637 | return undefined; 638 | }, 639 | 640 | push: function(result, actual, expected, message) { 641 | var details = { 642 | result: result, 643 | message: message, 644 | actual: actual, 645 | expected: expected 646 | }; 647 | 648 | message = escapeInnerText(message) || (result ? "okay" : "failed"); 649 | message = '' + message + ""; 650 | expected = escapeInnerText(QUnit.jsDump.parse(expected)); 651 | actual = escapeInnerText(QUnit.jsDump.parse(actual)); 652 | var output = message + ''; 653 | if (actual != expected) { 654 | output += ''; 655 | output += ''; 656 | } 657 | if (!result) { 658 | var source = sourceFromStacktrace(); 659 | if (source) { 660 | details.source = source; 661 | output += ''; 662 | } 663 | } 664 | output += "
Expected:
' + expected + '
Result:
' + actual + '
Diff:
' + QUnit.diff(expected, actual) +'
Source:
' + escapeInnerText(source) + '
"; 665 | 666 | runLoggingCallbacks( 'log', QUnit, details ); 667 | 668 | config.current.assertions.push({ 669 | result: !!result, 670 | message: output 671 | }); 672 | }, 673 | 674 | url: function( params ) { 675 | params = extend( extend( {}, QUnit.urlParams ), params ); 676 | var querystring = "?", 677 | key; 678 | for ( key in params ) { 679 | if ( !hasOwn.call( params, key ) ) { 680 | continue; 681 | } 682 | querystring += encodeURIComponent( key ) + "=" + 683 | encodeURIComponent( params[ key ] ) + "&"; 684 | } 685 | return window.location.pathname + querystring.slice( 0, -1 ); 686 | }, 687 | 688 | extend: extend, 689 | id: id, 690 | addEvent: addEvent 691 | }); 692 | 693 | //QUnit.constructor is set to the empty F() above so that we can add to it's prototype later 694 | //Doing this allows us to tell if the following methods have been overwritten on the actual 695 | //QUnit object, which is a deprecated way of using the callbacks. 696 | extend(QUnit.constructor.prototype, { 697 | // Logging callbacks; all receive a single argument with the listed properties 698 | // run test/logs.html for any related changes 699 | begin: registerLoggingCallback('begin'), 700 | // done: { failed, passed, total, runtime } 701 | done: registerLoggingCallback('done'), 702 | // log: { result, actual, expected, message } 703 | log: registerLoggingCallback('log'), 704 | // testStart: { name } 705 | testStart: registerLoggingCallback('testStart'), 706 | // testDone: { name, failed, passed, total } 707 | testDone: registerLoggingCallback('testDone'), 708 | // moduleStart: { name } 709 | moduleStart: registerLoggingCallback('moduleStart'), 710 | // moduleDone: { name, failed, passed, total } 711 | moduleDone: registerLoggingCallback('moduleDone') 712 | }); 713 | 714 | if ( typeof document === "undefined" || document.readyState === "complete" ) { 715 | config.autorun = true; 716 | } 717 | 718 | QUnit.load = function() { 719 | runLoggingCallbacks( 'begin', QUnit, {} ); 720 | 721 | // Initialize the config, saving the execution queue 722 | var oldconfig = extend({}, config); 723 | QUnit.init(); 724 | extend(config, oldconfig); 725 | 726 | config.blocking = false; 727 | 728 | var urlConfigHtml = '', len = config.urlConfig.length; 729 | for ( var i = 0, val; i < len, val = config.urlConfig[i]; i++ ) { 730 | config[val] = QUnit.urlParams[val]; 731 | urlConfigHtml += ''; 732 | } 733 | 734 | var userAgent = id("qunit-userAgent"); 735 | if ( userAgent ) { 736 | userAgent.innerHTML = navigator.userAgent; 737 | } 738 | var banner = id("qunit-header"); 739 | if ( banner ) { 740 | banner.innerHTML = ' ' + banner.innerHTML + ' ' + urlConfigHtml; 741 | addEvent( banner, "change", function( event ) { 742 | var params = {}; 743 | params[ event.target.name ] = event.target.checked ? true : undefined; 744 | window.location = QUnit.url( params ); 745 | }); 746 | } 747 | 748 | var toolbar = id("qunit-testrunner-toolbar"); 749 | if ( toolbar ) { 750 | var filter = document.createElement("input"); 751 | filter.type = "checkbox"; 752 | filter.id = "qunit-filter-pass"; 753 | addEvent( filter, "click", function() { 754 | var ol = document.getElementById("qunit-tests"); 755 | if ( filter.checked ) { 756 | ol.className = ol.className + " hidepass"; 757 | } else { 758 | var tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " "; 759 | ol.className = tmp.replace(/ hidepass /, " "); 760 | } 761 | if ( defined.sessionStorage ) { 762 | if (filter.checked) { 763 | sessionStorage.setItem("qunit-filter-passed-tests", "true"); 764 | } else { 765 | sessionStorage.removeItem("qunit-filter-passed-tests"); 766 | } 767 | } 768 | }); 769 | if ( config.hidepassed || defined.sessionStorage && sessionStorage.getItem("qunit-filter-passed-tests") ) { 770 | filter.checked = true; 771 | var ol = document.getElementById("qunit-tests"); 772 | ol.className = ol.className + " hidepass"; 773 | } 774 | toolbar.appendChild( filter ); 775 | 776 | var label = document.createElement("label"); 777 | label.setAttribute("for", "qunit-filter-pass"); 778 | label.innerHTML = "Hide passed tests"; 779 | toolbar.appendChild( label ); 780 | } 781 | 782 | var main = id('qunit-fixture'); 783 | if ( main ) { 784 | config.fixture = main.innerHTML; 785 | } 786 | 787 | if (config.autostart) { 788 | QUnit.start(); 789 | } 790 | }; 791 | 792 | addEvent(window, "load", QUnit.load); 793 | 794 | // addEvent(window, "error") gives us a useless event object 795 | window.onerror = function( message, file, line ) { 796 | if ( QUnit.config.current ) { 797 | ok( false, message + ", " + file + ":" + line ); 798 | } else { 799 | test( "global failure", function() { 800 | ok( false, message + ", " + file + ":" + line ); 801 | }); 802 | } 803 | }; 804 | 805 | function done() { 806 | config.autorun = true; 807 | 808 | // Log the last module results 809 | if ( config.currentModule ) { 810 | runLoggingCallbacks( 'moduleDone', QUnit, { 811 | name: config.currentModule, 812 | failed: config.moduleStats.bad, 813 | passed: config.moduleStats.all - config.moduleStats.bad, 814 | total: config.moduleStats.all 815 | } ); 816 | } 817 | 818 | var banner = id("qunit-banner"), 819 | tests = id("qunit-tests"), 820 | runtime = +new Date - config.started, 821 | passed = config.stats.all - config.stats.bad, 822 | html = [ 823 | 'Tests completed in ', 824 | runtime, 825 | ' milliseconds.
', 826 | '', 827 | passed, 828 | ' tests of ', 829 | config.stats.all, 830 | ' passed, ', 831 | config.stats.bad, 832 | ' failed.' 833 | ].join(''); 834 | 835 | if ( banner ) { 836 | banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass"); 837 | } 838 | 839 | if ( tests ) { 840 | id( "qunit-testresult" ).innerHTML = html; 841 | } 842 | 843 | if ( config.altertitle && typeof document !== "undefined" && document.title ) { 844 | // show ✖ for good, ✔ for bad suite result in title 845 | // use escape sequences in case file gets loaded with non-utf-8-charset 846 | document.title = [ 847 | (config.stats.bad ? "\u2716" : "\u2714"), 848 | document.title.replace(/^[\u2714\u2716] /i, "") 849 | ].join(" "); 850 | } 851 | 852 | runLoggingCallbacks( 'done', QUnit, { 853 | failed: config.stats.bad, 854 | passed: passed, 855 | total: config.stats.all, 856 | runtime: runtime 857 | } ); 858 | } 859 | 860 | function validTest( name ) { 861 | var filter = config.filter, 862 | run = false; 863 | 864 | if ( !filter ) { 865 | return true; 866 | } 867 | 868 | var not = filter.charAt( 0 ) === "!"; 869 | if ( not ) { 870 | filter = filter.slice( 1 ); 871 | } 872 | 873 | if ( name.indexOf( filter ) !== -1 ) { 874 | return !not; 875 | } 876 | 877 | if ( not ) { 878 | run = true; 879 | } 880 | 881 | return run; 882 | } 883 | 884 | // so far supports only Firefox, Chrome and Opera (buggy) 885 | // could be extended in the future to use something like https://github.com/csnover/TraceKit 886 | function sourceFromStacktrace() { 887 | try { 888 | throw new Error(); 889 | } catch ( e ) { 890 | if (e.stacktrace) { 891 | // Opera 892 | return e.stacktrace.split("\n")[6]; 893 | } else if (e.stack) { 894 | // Firefox, Chrome 895 | return e.stack.split("\n")[4]; 896 | } else if (e.sourceURL) { 897 | // Safari, PhantomJS 898 | // TODO sourceURL points at the 'throw new Error' line above, useless 899 | //return e.sourceURL + ":" + e.line; 900 | } 901 | } 902 | } 903 | 904 | function escapeInnerText(s) { 905 | if (!s) { 906 | return ""; 907 | } 908 | s = s + ""; 909 | return s.replace(/[\&<>]/g, function(s) { 910 | switch(s) { 911 | case "&": return "&"; 912 | case "<": return "<"; 913 | case ">": return ">"; 914 | default: return s; 915 | } 916 | }); 917 | } 918 | 919 | function synchronize( callback, last ) { 920 | config.queue.push( callback ); 921 | 922 | if ( config.autorun && !config.blocking ) { 923 | process(last); 924 | } 925 | } 926 | 927 | function process( last ) { 928 | var start = new Date().getTime(); 929 | config.depth = config.depth ? config.depth + 1 : 1; 930 | 931 | while ( config.queue.length && !config.blocking ) { 932 | if ( !defined.setTimeout || config.updateRate <= 0 || ( ( new Date().getTime() - start ) < config.updateRate ) ) { 933 | config.queue.shift()(); 934 | } else { 935 | window.setTimeout( function(){ 936 | process( last ); 937 | }, 13 ); 938 | break; 939 | } 940 | } 941 | config.depth--; 942 | if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) { 943 | done(); 944 | } 945 | } 946 | 947 | function saveGlobal() { 948 | config.pollution = []; 949 | 950 | if ( config.noglobals ) { 951 | for ( var key in window ) { 952 | if ( !hasOwn.call( window, key ) ) { 953 | continue; 954 | } 955 | config.pollution.push( key ); 956 | } 957 | } 958 | } 959 | 960 | function checkPollution( name ) { 961 | var old = config.pollution; 962 | saveGlobal(); 963 | 964 | var newGlobals = diff( config.pollution, old ); 965 | if ( newGlobals.length > 0 ) { 966 | ok( false, "Introduced global variable(s): " + newGlobals.join(", ") ); 967 | } 968 | 969 | var deletedGlobals = diff( old, config.pollution ); 970 | if ( deletedGlobals.length > 0 ) { 971 | ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") ); 972 | } 973 | } 974 | 975 | // returns a new Array with the elements that are in a but not in b 976 | function diff( a, b ) { 977 | var result = a.slice(); 978 | for ( var i = 0; i < result.length; i++ ) { 979 | for ( var j = 0; j < b.length; j++ ) { 980 | if ( result[i] === b[j] ) { 981 | result.splice(i, 1); 982 | i--; 983 | break; 984 | } 985 | } 986 | } 987 | return result; 988 | } 989 | 990 | function fail(message, exception, callback) { 991 | if ( typeof console !== "undefined" && console.error && console.warn ) { 992 | console.error(message); 993 | console.error(exception); 994 | console.warn(callback.toString()); 995 | 996 | } else if ( window.opera && opera.postError ) { 997 | opera.postError(message, exception, callback.toString); 998 | } 999 | } 1000 | 1001 | function extend(a, b) { 1002 | for ( var prop in b ) { 1003 | if ( b[prop] === undefined ) { 1004 | delete a[prop]; 1005 | 1006 | // Avoid "Member not found" error in IE8 caused by setting window.constructor 1007 | } else if ( prop !== "constructor" || a !== window ) { 1008 | a[prop] = b[prop]; 1009 | } 1010 | } 1011 | 1012 | return a; 1013 | } 1014 | 1015 | function addEvent(elem, type, fn) { 1016 | if ( elem.addEventListener ) { 1017 | elem.addEventListener( type, fn, false ); 1018 | } else if ( elem.attachEvent ) { 1019 | elem.attachEvent( "on" + type, fn ); 1020 | } else { 1021 | fn(); 1022 | } 1023 | } 1024 | 1025 | function id(name) { 1026 | return !!(typeof document !== "undefined" && document && document.getElementById) && 1027 | document.getElementById( name ); 1028 | } 1029 | 1030 | function registerLoggingCallback(key){ 1031 | return function(callback){ 1032 | config[key].push( callback ); 1033 | }; 1034 | } 1035 | 1036 | // Supports deprecated method of completely overwriting logging callbacks 1037 | function runLoggingCallbacks(key, scope, args) { 1038 | //debugger; 1039 | var callbacks; 1040 | if ( QUnit.hasOwnProperty(key) ) { 1041 | QUnit[key].call(scope, args); 1042 | } else { 1043 | callbacks = config[key]; 1044 | for( var i = 0; i < callbacks.length; i++ ) { 1045 | callbacks[i].call( scope, args ); 1046 | } 1047 | } 1048 | } 1049 | 1050 | // Test for equality any JavaScript type. 1051 | // Author: Philippe Rathé 1052 | QUnit.equiv = function () { 1053 | 1054 | var innerEquiv; // the real equiv function 1055 | var callers = []; // stack to decide between skip/abort functions 1056 | var parents = []; // stack to avoiding loops from circular referencing 1057 | 1058 | // Call the o related callback with the given arguments. 1059 | function bindCallbacks(o, callbacks, args) { 1060 | var prop = QUnit.objectType(o); 1061 | if (prop) { 1062 | if (QUnit.objectType(callbacks[prop]) === "function") { 1063 | return callbacks[prop].apply(callbacks, args); 1064 | } else { 1065 | return callbacks[prop]; // or undefined 1066 | } 1067 | } 1068 | } 1069 | 1070 | var getProto = Object.getPrototypeOf || function (obj) { 1071 | return obj.__proto__; 1072 | }; 1073 | 1074 | var callbacks = function () { 1075 | 1076 | // for string, boolean, number and null 1077 | function useStrictEquality(b, a) { 1078 | if (b instanceof a.constructor || a instanceof b.constructor) { 1079 | // to catch short annotaion VS 'new' annotation of a 1080 | // declaration 1081 | // e.g. var i = 1; 1082 | // var j = new Number(1); 1083 | return a == b; 1084 | } else { 1085 | return a === b; 1086 | } 1087 | } 1088 | 1089 | return { 1090 | "string" : useStrictEquality, 1091 | "boolean" : useStrictEquality, 1092 | "number" : useStrictEquality, 1093 | "null" : useStrictEquality, 1094 | "undefined" : useStrictEquality, 1095 | 1096 | "nan" : function(b) { 1097 | return isNaN(b); 1098 | }, 1099 | 1100 | "date" : function(b, a) { 1101 | return QUnit.objectType(b) === "date" 1102 | && a.valueOf() === b.valueOf(); 1103 | }, 1104 | 1105 | "regexp" : function(b, a) { 1106 | return QUnit.objectType(b) === "regexp" 1107 | && a.source === b.source && // the regex itself 1108 | a.global === b.global && // and its modifers 1109 | // (gmi) ... 1110 | a.ignoreCase === b.ignoreCase 1111 | && a.multiline === b.multiline; 1112 | }, 1113 | 1114 | // - skip when the property is a method of an instance (OOP) 1115 | // - abort otherwise, 1116 | // initial === would have catch identical references anyway 1117 | "function" : function() { 1118 | var caller = callers[callers.length - 1]; 1119 | return caller !== Object && typeof caller !== "undefined"; 1120 | }, 1121 | 1122 | "array" : function(b, a) { 1123 | var i, j, loop; 1124 | var len; 1125 | 1126 | // b could be an object literal here 1127 | if (!(QUnit.objectType(b) === "array")) { 1128 | return false; 1129 | } 1130 | 1131 | len = a.length; 1132 | if (len !== b.length) { // safe and faster 1133 | return false; 1134 | } 1135 | 1136 | // track reference to avoid circular references 1137 | parents.push(a); 1138 | for (i = 0; i < len; i++) { 1139 | loop = false; 1140 | for (j = 0; j < parents.length; j++) { 1141 | if (parents[j] === a[i]) { 1142 | loop = true;// dont rewalk array 1143 | } 1144 | } 1145 | if (!loop && !innerEquiv(a[i], b[i])) { 1146 | parents.pop(); 1147 | return false; 1148 | } 1149 | } 1150 | parents.pop(); 1151 | return true; 1152 | }, 1153 | 1154 | "object" : function(b, a) { 1155 | var i, j, loop; 1156 | var eq = true; // unless we can proove it 1157 | var aProperties = [], bProperties = []; // collection of 1158 | // strings 1159 | 1160 | // comparing constructors is more strict than using 1161 | // instanceof 1162 | if (a.constructor !== b.constructor) { 1163 | // Allow objects with no prototype to be equivalent to 1164 | // objects with Object as their constructor. 1165 | if (!((getProto(a) === null && getProto(b) === Object.prototype) || 1166 | (getProto(b) === null && getProto(a) === Object.prototype))) 1167 | { 1168 | return false; 1169 | } 1170 | } 1171 | 1172 | // stack constructor before traversing properties 1173 | callers.push(a.constructor); 1174 | // track reference to avoid circular references 1175 | parents.push(a); 1176 | 1177 | for (i in a) { // be strict: don't ensures hasOwnProperty 1178 | // and go deep 1179 | loop = false; 1180 | for (j = 0; j < parents.length; j++) { 1181 | if (parents[j] === a[i]) 1182 | loop = true; // don't go down the same path 1183 | // twice 1184 | } 1185 | aProperties.push(i); // collect a's properties 1186 | 1187 | if (!loop && !innerEquiv(a[i], b[i])) { 1188 | eq = false; 1189 | break; 1190 | } 1191 | } 1192 | 1193 | callers.pop(); // unstack, we are done 1194 | parents.pop(); 1195 | 1196 | for (i in b) { 1197 | bProperties.push(i); // collect b's properties 1198 | } 1199 | 1200 | // Ensures identical properties name 1201 | return eq 1202 | && innerEquiv(aProperties.sort(), bProperties 1203 | .sort()); 1204 | } 1205 | }; 1206 | }(); 1207 | 1208 | innerEquiv = function() { // can take multiple arguments 1209 | var args = Array.prototype.slice.apply(arguments); 1210 | if (args.length < 2) { 1211 | return true; // end transition 1212 | } 1213 | 1214 | return (function(a, b) { 1215 | if (a === b) { 1216 | return true; // catch the most you can 1217 | } else if (a === null || b === null || typeof a === "undefined" 1218 | || typeof b === "undefined" 1219 | || QUnit.objectType(a) !== QUnit.objectType(b)) { 1220 | return false; // don't lose time with error prone cases 1221 | } else { 1222 | return bindCallbacks(a, callbacks, [ b, a ]); 1223 | } 1224 | 1225 | // apply transition with (1..n) arguments 1226 | })(args[0], args[1]) 1227 | && arguments.callee.apply(this, args.splice(1, 1228 | args.length - 1)); 1229 | }; 1230 | 1231 | return innerEquiv; 1232 | 1233 | }(); 1234 | 1235 | /** 1236 | * jsDump Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | 1237 | * http://flesler.blogspot.com Licensed under BSD 1238 | * (http://www.opensource.org/licenses/bsd-license.php) Date: 5/15/2008 1239 | * 1240 | * @projectDescription Advanced and extensible data dumping for Javascript. 1241 | * @version 1.0.0 1242 | * @author Ariel Flesler 1243 | * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html} 1244 | */ 1245 | QUnit.jsDump = (function() { 1246 | function quote( str ) { 1247 | return '"' + str.toString().replace(/"/g, '\\"') + '"'; 1248 | }; 1249 | function literal( o ) { 1250 | return o + ''; 1251 | }; 1252 | function join( pre, arr, post ) { 1253 | var s = jsDump.separator(), 1254 | base = jsDump.indent(), 1255 | inner = jsDump.indent(1); 1256 | if ( arr.join ) 1257 | arr = arr.join( ',' + s + inner ); 1258 | if ( !arr ) 1259 | return pre + post; 1260 | return [ pre, inner + arr, base + post ].join(s); 1261 | }; 1262 | function array( arr, stack ) { 1263 | var i = arr.length, ret = Array(i); 1264 | this.up(); 1265 | while ( i-- ) 1266 | ret[i] = this.parse( arr[i] , undefined , stack); 1267 | this.down(); 1268 | return join( '[', ret, ']' ); 1269 | }; 1270 | 1271 | var reName = /^function (\w+)/; 1272 | 1273 | var jsDump = { 1274 | parse:function( obj, type, stack ) { //type is used mostly internally, you can fix a (custom)type in advance 1275 | stack = stack || [ ]; 1276 | var parser = this.parsers[ type || this.typeOf(obj) ]; 1277 | type = typeof parser; 1278 | var inStack = inArray(obj, stack); 1279 | if (inStack != -1) { 1280 | return 'recursion('+(inStack - stack.length)+')'; 1281 | } 1282 | //else 1283 | if (type == 'function') { 1284 | stack.push(obj); 1285 | var res = parser.call( this, obj, stack ); 1286 | stack.pop(); 1287 | return res; 1288 | } 1289 | // else 1290 | return (type == 'string') ? parser : this.parsers.error; 1291 | }, 1292 | typeOf:function( obj ) { 1293 | var type; 1294 | if ( obj === null ) { 1295 | type = "null"; 1296 | } else if (typeof obj === "undefined") { 1297 | type = "undefined"; 1298 | } else if (QUnit.is("RegExp", obj)) { 1299 | type = "regexp"; 1300 | } else if (QUnit.is("Date", obj)) { 1301 | type = "date"; 1302 | } else if (QUnit.is("Function", obj)) { 1303 | type = "function"; 1304 | } else if (typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined") { 1305 | type = "window"; 1306 | } else if (obj.nodeType === 9) { 1307 | type = "document"; 1308 | } else if (obj.nodeType) { 1309 | type = "node"; 1310 | } else if ( 1311 | // native arrays 1312 | toString.call( obj ) === "[object Array]" || 1313 | // NodeList objects 1314 | ( typeof obj.length === "number" && typeof obj.item !== "undefined" && ( obj.length ? obj.item(0) === obj[0] : ( obj.item( 0 ) === null && typeof obj[0] === "undefined" ) ) ) 1315 | ) { 1316 | type = "array"; 1317 | } else { 1318 | type = typeof obj; 1319 | } 1320 | return type; 1321 | }, 1322 | separator:function() { 1323 | return this.multiline ? this.HTML ? '
' : '\n' : this.HTML ? ' ' : ' '; 1324 | }, 1325 | indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing 1326 | if ( !this.multiline ) 1327 | return ''; 1328 | var chr = this.indentChar; 1329 | if ( this.HTML ) 1330 | chr = chr.replace(/\t/g,' ').replace(/ /g,' '); 1331 | return Array( this._depth_ + (extra||0) ).join(chr); 1332 | }, 1333 | up:function( a ) { 1334 | this._depth_ += a || 1; 1335 | }, 1336 | down:function( a ) { 1337 | this._depth_ -= a || 1; 1338 | }, 1339 | setParser:function( name, parser ) { 1340 | this.parsers[name] = parser; 1341 | }, 1342 | // The next 3 are exposed so you can use them 1343 | quote:quote, 1344 | literal:literal, 1345 | join:join, 1346 | // 1347 | _depth_: 1, 1348 | // This is the list of parsers, to modify them, use jsDump.setParser 1349 | parsers:{ 1350 | window: '[Window]', 1351 | document: '[Document]', 1352 | error:'[ERROR]', //when no parser is found, shouldn't happen 1353 | unknown: '[Unknown]', 1354 | 'null':'null', 1355 | 'undefined':'undefined', 1356 | 'function':function( fn ) { 1357 | var ret = 'function', 1358 | name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE 1359 | if ( name ) 1360 | ret += ' ' + name; 1361 | ret += '('; 1362 | 1363 | ret = [ ret, QUnit.jsDump.parse( fn, 'functionArgs' ), '){'].join(''); 1364 | return join( ret, QUnit.jsDump.parse(fn,'functionCode'), '}' ); 1365 | }, 1366 | array: array, 1367 | nodelist: array, 1368 | arguments: array, 1369 | object:function( map, stack ) { 1370 | var ret = [ ]; 1371 | QUnit.jsDump.up(); 1372 | for ( var key in map ) { 1373 | var val = map[key]; 1374 | ret.push( QUnit.jsDump.parse(key,'key') + ': ' + QUnit.jsDump.parse(val, undefined, stack)); 1375 | } 1376 | QUnit.jsDump.down(); 1377 | return join( '{', ret, '}' ); 1378 | }, 1379 | node:function( node ) { 1380 | var open = QUnit.jsDump.HTML ? '<' : '<', 1381 | close = QUnit.jsDump.HTML ? '>' : '>'; 1382 | 1383 | var tag = node.nodeName.toLowerCase(), 1384 | ret = open + tag; 1385 | 1386 | for ( var a in QUnit.jsDump.DOMAttrs ) { 1387 | var val = node[QUnit.jsDump.DOMAttrs[a]]; 1388 | if ( val ) 1389 | ret += ' ' + a + '=' + QUnit.jsDump.parse( val, 'attribute' ); 1390 | } 1391 | return ret + close + open + '/' + tag + close; 1392 | }, 1393 | functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function 1394 | var l = fn.length; 1395 | if ( !l ) return ''; 1396 | 1397 | var args = Array(l); 1398 | while ( l-- ) 1399 | args[l] = String.fromCharCode(97+l);//97 is 'a' 1400 | return ' ' + args.join(', ') + ' '; 1401 | }, 1402 | key:quote, //object calls it internally, the key part of an item in a map 1403 | functionCode:'[code]', //function calls it internally, it's the content of the function 1404 | attribute:quote, //node calls it internally, it's an html attribute value 1405 | string:quote, 1406 | date:quote, 1407 | regexp:literal, //regex 1408 | number:literal, 1409 | 'boolean':literal 1410 | }, 1411 | DOMAttrs:{//attributes to dump from nodes, name=>realName 1412 | id:'id', 1413 | name:'name', 1414 | 'class':'className' 1415 | }, 1416 | HTML:false,//if true, entities are escaped ( <, >, \t, space and \n ) 1417 | indentChar:' ',//indentation unit 1418 | multiline:true //if true, items in a collection, are separated by a \n, else just a space. 1419 | }; 1420 | 1421 | return jsDump; 1422 | })(); 1423 | 1424 | // from Sizzle.js 1425 | function getText( elems ) { 1426 | var ret = "", elem; 1427 | 1428 | for ( var i = 0; elems[i]; i++ ) { 1429 | elem = elems[i]; 1430 | 1431 | // Get the text from text nodes and CDATA nodes 1432 | if ( elem.nodeType === 3 || elem.nodeType === 4 ) { 1433 | ret += elem.nodeValue; 1434 | 1435 | // Traverse everything else, except comment nodes 1436 | } else if ( elem.nodeType !== 8 ) { 1437 | ret += getText( elem.childNodes ); 1438 | } 1439 | } 1440 | 1441 | return ret; 1442 | }; 1443 | 1444 | //from jquery.js 1445 | function inArray( elem, array ) { 1446 | if ( array.indexOf ) { 1447 | return array.indexOf( elem ); 1448 | } 1449 | 1450 | for ( var i = 0, length = array.length; i < length; i++ ) { 1451 | if ( array[ i ] === elem ) { 1452 | return i; 1453 | } 1454 | } 1455 | 1456 | return -1; 1457 | } 1458 | 1459 | /* 1460 | * Javascript Diff Algorithm 1461 | * By John Resig (http://ejohn.org/) 1462 | * Modified by Chu Alan "sprite" 1463 | * 1464 | * Released under the MIT license. 1465 | * 1466 | * More Info: 1467 | * http://ejohn.org/projects/javascript-diff-algorithm/ 1468 | * 1469 | * Usage: QUnit.diff(expected, actual) 1470 | * 1471 | * QUnit.diff("the quick brown fox jumped over", "the quick fox jumps over") == "the quick brown fox jumped jumps over" 1472 | */ 1473 | QUnit.diff = (function() { 1474 | function diff(o, n) { 1475 | var ns = {}; 1476 | var os = {}; 1477 | 1478 | for (var i = 0; i < n.length; i++) { 1479 | if (ns[n[i]] == null) 1480 | ns[n[i]] = { 1481 | rows: [], 1482 | o: null 1483 | }; 1484 | ns[n[i]].rows.push(i); 1485 | } 1486 | 1487 | for (var i = 0; i < o.length; i++) { 1488 | if (os[o[i]] == null) 1489 | os[o[i]] = { 1490 | rows: [], 1491 | n: null 1492 | }; 1493 | os[o[i]].rows.push(i); 1494 | } 1495 | 1496 | for (var i in ns) { 1497 | if ( !hasOwn.call( ns, i ) ) { 1498 | continue; 1499 | } 1500 | if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) { 1501 | n[ns[i].rows[0]] = { 1502 | text: n[ns[i].rows[0]], 1503 | row: os[i].rows[0] 1504 | }; 1505 | o[os[i].rows[0]] = { 1506 | text: o[os[i].rows[0]], 1507 | row: ns[i].rows[0] 1508 | }; 1509 | } 1510 | } 1511 | 1512 | for (var i = 0; i < n.length - 1; i++) { 1513 | if (n[i].text != null && n[i + 1].text == null && n[i].row + 1 < o.length && o[n[i].row + 1].text == null && 1514 | n[i + 1] == o[n[i].row + 1]) { 1515 | n[i + 1] = { 1516 | text: n[i + 1], 1517 | row: n[i].row + 1 1518 | }; 1519 | o[n[i].row + 1] = { 1520 | text: o[n[i].row + 1], 1521 | row: i + 1 1522 | }; 1523 | } 1524 | } 1525 | 1526 | for (var i = n.length - 1; i > 0; i--) { 1527 | if (n[i].text != null && n[i - 1].text == null && n[i].row > 0 && o[n[i].row - 1].text == null && 1528 | n[i - 1] == o[n[i].row - 1]) { 1529 | n[i - 1] = { 1530 | text: n[i - 1], 1531 | row: n[i].row - 1 1532 | }; 1533 | o[n[i].row - 1] = { 1534 | text: o[n[i].row - 1], 1535 | row: i - 1 1536 | }; 1537 | } 1538 | } 1539 | 1540 | return { 1541 | o: o, 1542 | n: n 1543 | }; 1544 | } 1545 | 1546 | return function(o, n) { 1547 | o = o.replace(/\s+$/, ''); 1548 | n = n.replace(/\s+$/, ''); 1549 | var out = diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/)); 1550 | 1551 | var str = ""; 1552 | 1553 | var oSpace = o.match(/\s+/g); 1554 | if (oSpace == null) { 1555 | oSpace = [" "]; 1556 | } 1557 | else { 1558 | oSpace.push(" "); 1559 | } 1560 | var nSpace = n.match(/\s+/g); 1561 | if (nSpace == null) { 1562 | nSpace = [" "]; 1563 | } 1564 | else { 1565 | nSpace.push(" "); 1566 | } 1567 | 1568 | if (out.n.length == 0) { 1569 | for (var i = 0; i < out.o.length; i++) { 1570 | str += '' + out.o[i] + oSpace[i] + ""; 1571 | } 1572 | } 1573 | else { 1574 | if (out.n[0].text == null) { 1575 | for (n = 0; n < out.o.length && out.o[n].text == null; n++) { 1576 | str += '' + out.o[n] + oSpace[n] + ""; 1577 | } 1578 | } 1579 | 1580 | for (var i = 0; i < out.n.length; i++) { 1581 | if (out.n[i].text == null) { 1582 | str += '' + out.n[i] + nSpace[i] + ""; 1583 | } 1584 | else { 1585 | var pre = ""; 1586 | 1587 | for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++) { 1588 | pre += '' + out.o[n] + oSpace[n] + ""; 1589 | } 1590 | str += " " + out.n[i].text + nSpace[i] + pre; 1591 | } 1592 | } 1593 | } 1594 | 1595 | return str; 1596 | }; 1597 | })(); 1598 | 1599 | })(this); 1600 | -------------------------------------------------------------------------------- /test/runner.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Ash-js Test Runner 6 | 7 | 8 | 22 | 23 | 24 | 25 | 26 | 40 | 41 |

Ash-js Test Suite

42 |

43 |
44 |

45 |
    46 |
    test markup
    47 | 48 | 49 | -------------------------------------------------------------------------------- /test/spec/class.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Testing Class inheritance 3 | */ 4 | define ([ 5 | 'ash-framework' 6 | ], function(Ash) { 7 | 'use strict'; 8 | 9 | // base class 10 | var Person = Ash.Class.extend({ 11 | constructor: function (name) { 12 | this.name = name || 'A person'; 13 | }, 14 | 15 | dance: function () { 16 | return 'can\'t dance'; 17 | }, 18 | 19 | say: function (something) { 20 | return something || this.name; 21 | } 22 | }); 23 | 24 | var Ninja = Person.extend({ 25 | constructor: function (){ 26 | Person.super.constructor.call(this, 'A ninja'); 27 | }, 28 | 29 | dance: function (){ 30 | return Ninja.super.dance.call(this); 31 | }, 32 | 33 | swingSword: function (){ 34 | return 'swings a katana'; 35 | }, 36 | 37 | say: function () { 38 | return Ninja.super.say.call(this, '...'); 39 | } 40 | }); 41 | 42 | module('Test Class inheritance'); 43 | 44 | test('create a class & an instance', function () { 45 | var Test = Ash.Class.extend({ 46 | name: 'test', 47 | value: 1, 48 | constructor: function () { } 49 | }); 50 | 51 | var test = new Test(); 52 | equal(test.value, 1); 53 | equal(test.name, 'test'); 54 | }); 55 | 56 | test('create instances', function () { 57 | var ordinaryGuy = new Person(); 58 | ok(ordinaryGuy, 'an instance created'); 59 | equal(ordinaryGuy.say(), 'A person'); 60 | equal(ordinaryGuy.say('hello'), 'hello'); 61 | 62 | var name = 'Mr. Budi'; 63 | var businessMan = new Person(name); 64 | ok(businessMan, 'an instance created'); 65 | equal(businessMan.say(), name); 66 | 67 | ok(businessMan !== ordinaryGuy); 68 | }); 69 | 70 | test('constructors are called', 3, function () { 71 | var Test = Ash.Class.extend({ 72 | constructor: function () { 73 | ok(true, 'constructor is called'); 74 | } 75 | }); 76 | 77 | var SubTest = Test.extend({ 78 | constructor: function () { 79 | SubTest.super.constructor.call(this); 80 | ok(true, 'sub-constructor is called'); 81 | } 82 | }); 83 | 84 | var test = new Test(); 85 | var subTest = new SubTest(); 86 | }); 87 | 88 | test('check instance & multiple inheritances with instanceOf', function () { 89 | notEqual(Person, Ninja); 90 | 91 | var p = new Person(); 92 | var n = new Ninja(); 93 | 94 | ok(p instanceof Person); 95 | // Note: can't do instanceof with Class since it's an object not a function 96 | //ok(p instanceof Class); 97 | ok(n instanceof Ninja); 98 | ok(n instanceof Person); 99 | // Note: can't do instanceof with Class since it's an object not a function 100 | //ok(n instanceof Class); 101 | }); 102 | 103 | test('gets instance class & constructor', function () { 104 | var hatori = new Ninja(); 105 | strictEqual(hatori.constructor.prototype, Ninja.prototype); 106 | }); 107 | 108 | test('gets instance constructor', function () { 109 | var hatori = new Ninja(); 110 | var fuma = new hatori.constructor(); 111 | equal(hatori.say(), fuma.say()); 112 | ok(hatori !== fuma); 113 | }); 114 | 115 | test('access parent methods', function () { 116 | var Mammals = Ash.Class.extend({ 117 | makeSound: function () { 118 | return this.name; 119 | }, 120 | constructor: function () { } 121 | }); 122 | 123 | var Dog = Mammals.extend({ 124 | name: 'Dog', 125 | constructor: function () { } 126 | }); 127 | 128 | var Cat = Mammals.extend({ 129 | name: 'Cat', 130 | constructor: function (purr) { 131 | if (purr) { 132 | this.name = 'purrr' + this.name; 133 | } 134 | } 135 | }); 136 | 137 | var douglas = new Dog(); 138 | var yoda = new Cat(true); 139 | var bluesy = new Cat(false); 140 | strictEqual(douglas.constructor.prototype, Dog.prototype); 141 | strictEqual(yoda.constructor.prototype, Cat.prototype); 142 | strictEqual(bluesy.constructor.prototype, Cat.prototype); 143 | notEqual(bluesy.constructor.prototype, Dog.prototype); 144 | notEqual(douglas.constructor.prototype, Cat.prototype); 145 | equal(douglas.makeSound(), 'Dog'); 146 | equal(yoda.makeSound(), 'purrrCat'); 147 | equal(bluesy.makeSound(), 'Cat'); 148 | }); 149 | }); 150 | -------------------------------------------------------------------------------- /test/spec/componentmatchingfamily.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Testing Component Matching Family 3 | */ 4 | define ([ 5 | 'ash-framework', 6 | 'point', 7 | 'point3' 8 | ], function(Ash, Point, Point3) { 9 | 'use strict'; 10 | 11 | var engine, family; 12 | 13 | // prepare MockNode 14 | var MockNode = Ash.Node.create({ 15 | point: Point 16 | }); 17 | 18 | module("Test Component Matching Family", { 19 | setup: function() { 20 | engine = new Ash.Engine(); 21 | family = new Ash.ComponentMatchingFamily(MockNode, engine); 22 | }, 23 | teardown: function() { 24 | family = null; 25 | engine = null; 26 | } 27 | }); 28 | 29 | test("nodeListIsInitiallyEmpty", function() { 30 | var nodes = family.nodeList; 31 | strictEqual( nodes.head, null ); 32 | }); 33 | 34 | test("matchingEntityIsAddedWhenAccessNodeListFirst", function() { 35 | var nodes = family.nodeList; 36 | var entity = new Ash.Entity(); 37 | entity.add( new Point() ); 38 | family.newEntity( entity ); 39 | strictEqual( nodes.head.entity, entity ); 40 | }); 41 | 42 | test("matchingEntityIsAddedWhenAccessNodeListSecond", function() { 43 | var entity = new Ash.Entity(); 44 | entity.add( new Point() ); 45 | family.newEntity( entity ); 46 | var nodes = family.nodeList; 47 | strictEqual( nodes.head.entity, entity ); 48 | }); 49 | 50 | test("nodeContainsEntityProperties", function() { 51 | var entity = new Ash.Entity(); 52 | var point = new Point(); 53 | entity.add( point ); 54 | family.newEntity( entity ); 55 | var nodes = family.nodeList; 56 | strictEqual( nodes.head.point, point ); 57 | }); 58 | 59 | test("matchingEntityIsAddedWhenComponentAdded", function() { 60 | var nodes = family.nodeList; 61 | var entity = new Ash.Entity(); 62 | entity.add( new Point() ); 63 | family.componentAddedToEntity( entity, Point ); 64 | strictEqual( nodes.head.entity, entity ); 65 | }); 66 | 67 | test("nonMatchingEntityIsNotAdded", function() { 68 | var entity = new Ash.Entity(); 69 | family.newEntity( entity ); 70 | var nodes = family.nodeList; 71 | strictEqual( nodes.head, null ); 72 | }); 73 | 74 | test("nonMatchingEntityIsNotAddedWhenComponentAdded", function() { 75 | var entity = new Ash.Entity(); 76 | entity.add( new Point3() ); 77 | family.componentAddedToEntity( entity, Point3 ); 78 | var nodes = family.nodeList; 79 | strictEqual( nodes.head, null ); 80 | }); 81 | 82 | test("entityIsRemovedWhenAccessNodeListFirst", function() { 83 | var entity = new Ash.Entity(); 84 | entity.add( new Point() ); 85 | family.newEntity( entity ); 86 | var nodes = family.nodeList; 87 | family.removeEntity( entity ); 88 | strictEqual( nodes.head, null ); 89 | }); 90 | 91 | test("entityIsRemovedWhenAccessNodeListSecond", function() { 92 | var entity = new Ash.Entity(); 93 | entity.add( new Point() ); 94 | family.newEntity( entity ); 95 | family.removeEntity( entity ); 96 | var nodes = family.nodeList; 97 | strictEqual( nodes.head, null ); 98 | }); 99 | 100 | test("entityIsRemovedWhenComponentRemoved", function() { 101 | var entity = new Ash.Entity(); 102 | entity.add( new Point() ); 103 | family.newEntity( entity ); 104 | entity.remove( Point ); 105 | family.componentRemovedFromEntity( entity, Point ); 106 | var nodes = family.nodeList; 107 | strictEqual( nodes.head, null ); 108 | }); 109 | 110 | test("nodeListContainsOnlyMatchingEntities", function() { 111 | var entities = [], 112 | i = 0; 113 | for( i; i < 5; ++i ) 114 | { 115 | var entity = new Ash.Entity(); 116 | entity.add( new Point() ); 117 | entities.push( entity ); 118 | family.newEntity( entity ); 119 | family.newEntity( new Ash.Entity() ); 120 | } 121 | 122 | var nodes = family.nodeList, 123 | node; 124 | 125 | for( node = nodes.head; node; node = node.next ) 126 | { 127 | notEqual( entities.indexOf( node.entity ), -1 ); 128 | } 129 | }); 130 | 131 | test("nodeListContainsOnlyMatchingEntitiesWhenComponentRemoved", function() { 132 | var entity1 = new Ash.Entity(); 133 | entity1.add( new Point(), Point ); 134 | 135 | var entity2 = new Ash.Entity(); 136 | entity2.add( new Point() ); 137 | entity2.add( new Point3() ); 138 | 139 | var entity3 = new Ash.Entity(); 140 | entity3.add( new Point() ); 141 | 142 | family.newEntity( entity1 ); 143 | family.newEntity( entity2 ); 144 | family.newEntity( entity3 ); 145 | 146 | entity1.remove( Point ); 147 | entity2.remove( Point ); 148 | 149 | var nodes = family.nodeList; 150 | console.log( family.nodeList ); 151 | strictEqual( nodes.head.entity, entity3 ); 152 | strictEqual( nodes.tail.entity, entity3 ); 153 | }); 154 | 155 | test("nodeListContainsAllMatchingEntities", function() { 156 | var entities = [], 157 | i = 0; 158 | 159 | for( i; i<5; ++i ) { 160 | var entity = new Ash.Entity(); 161 | entity.add( new Point() ); 162 | entities.push( entity ); 163 | family.newEntity( entity ); 164 | family.newEntity( new Ash.Entity() ); 165 | } 166 | equal( entities.length, 5 ); 167 | var nodes = family.nodeList, 168 | node; 169 | 170 | for( node = nodes.head; node; node = node.next ) 171 | { 172 | var index = entities.indexOf( node.entity ); 173 | if( index > -1 ) { entities.splice( index, 1 ); } 174 | } 175 | equal( entities.length, 0 ); 176 | }); 177 | 178 | test("cleanUpEmptiesNodeList", function() { 179 | var entity = new Ash.Entity(); 180 | entity.add( new Point() ); 181 | family.newEntity( entity ); 182 | var nodes = family.nodeList; 183 | family.cleanUp(); 184 | strictEqual( nodes.head, null ); 185 | }); 186 | 187 | test("cleanUpSetsNextNodeToNull", function() { 188 | var entities = [], 189 | i = 0; 190 | 191 | for( i; i < 5; ++i ) { 192 | var entity = new Ash.Entity(); 193 | entity.add( new Point() ); 194 | entities.push( entity ); 195 | family.newEntity( entity ); 196 | } 197 | 198 | var nodes = family.nodeList, 199 | node = nodes.head.next; 200 | family.cleanUp(); 201 | strictEqual( node.next, null ); 202 | }); 203 | }); -------------------------------------------------------------------------------- /test/spec/engine.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Testing Component Matching Family 3 | */ 4 | define ([ 5 | 'ash-framework', 6 | 'point', 7 | 'point3', 8 | 'brejep/dictionary' 9 | ], function(Ash, Point, Point3, Dictionary) { 10 | 'use strict'; 11 | 12 | // prepare MockNodes 13 | var MockNode = Ash.Node.extend({ 14 | point: null, 15 | types: { 16 | point: Point 17 | }, 18 | constructor: function (x, y) { 19 | x = x || 0; 20 | y = y || 0; 21 | this.point = new Point(x, y); 22 | } 23 | }); 24 | 25 | var MockNode2 = MockNode.extend({ 26 | point: null, 27 | matrix: null, 28 | types: { 29 | point: Point 30 | }, 31 | constructor: function () { 32 | MockNode.super.constructor.call(this); 33 | } 34 | }); 35 | 36 | var MockFamily = Ash.Family.extend({ 37 | newEntityCalls: 0, 38 | removeEntityCalls: 0, 39 | componentAddedCalls: 0, 40 | componentRemovedCalls: 0, 41 | cleanUpCalls: 0, 42 | 43 | constructor: function (nodeObject, engine) { 44 | MockFamily.super.constructor.call(this, nodeObject, engine); 45 | 46 | this.nodes = new Ash.NodeList(); 47 | this.entities = new Dictionary(); 48 | this.components = new Dictionary(); 49 | this.nodeObject = nodeObject; 50 | this.engine = engine; 51 | this.nodePool = new Ash.NodePool( nodeObject, this.components ); 52 | this.nodePool.dispose( this.nodePool.get() ); 53 | for( var property in nodeObject ) { 54 | if(nodeObject.hasOwnProperty(property) && 55 | property != "next" && 56 | property != "previous" && 57 | property != "constructor" && 58 | property != "super" && 59 | property != "extend" && 60 | property != "entity") { 61 | var componentObject = nodeObject.types[property]; 62 | this.components.add(componentObject, property); 63 | } 64 | } 65 | 66 | MockFamily.instances.push( this ); 67 | }, 68 | 69 | nodeList: function() { 70 | return null; 71 | }, 72 | 73 | newEntity: function( entity ) { 74 | this.newEntityCalls++; 75 | }, 76 | 77 | removeEntity: function( entity ) { 78 | this.removeEntityCalls++; 79 | }, 80 | 81 | componentAddedToEntity: function( entity, componentClass ) { 82 | this.componentAddedCalls++; 83 | }, 84 | 85 | componentRemovedFromEntity: function( entity, componentClass ) { 86 | this.componentRemovedCalls++; 87 | }, 88 | 89 | cleanUp: function() { 90 | this.cleanUpCalls++; 91 | } 92 | }); 93 | 94 | // static variables & methods 95 | MockFamily.instances = []; 96 | MockFamily.reset = function() { 97 | MockFamily.instances = []; 98 | }; 99 | 100 | var engine; 101 | 102 | module("Test Engine", { 103 | setup : function() { 104 | engine = new Ash.Engine(); 105 | engine.familyClass = MockFamily; 106 | MockFamily.reset(); 107 | }, 108 | teardown : function() { 109 | engine = null; 110 | } 111 | }); 112 | 113 | test("entitiesGetterReturnsAllTheEntities", function() { 114 | var entity1 = new Ash.Entity(); 115 | engine.addEntity( entity1 ); 116 | var entity2 = new Ash.Entity(); 117 | engine.addEntity( entity2 ); 118 | equal(engine.entities.length, 2); 119 | notEqual(engine.entities.indexOf(entity1), -1); 120 | notEqual(engine.entities.indexOf(entity2), -1); 121 | }); 122 | 123 | test("addEntityChecksWithAllFamilies", function() { 124 | engine.getNodeList( MockNode ); 125 | engine.getNodeList( MockNode2 ); 126 | var entity = new Ash.Entity(); 127 | engine.addEntity( entity ); 128 | equal( MockFamily.instances[0].newEntityCalls, 1 ); 129 | equal( MockFamily.instances[1].newEntityCalls, 1 ); 130 | }); 131 | 132 | test("removeEntityChecksWithAllFamilies", function() { 133 | engine.getNodeList( MockNode ); 134 | engine.getNodeList( MockNode2 ); 135 | var entity = new Ash.Entity(); 136 | engine.addEntity( entity ); 137 | engine.removeEntity( entity ); 138 | equal( MockFamily.instances[0].removeEntityCalls, 1 ); 139 | equal( MockFamily.instances[1].removeEntityCalls, 1 ); 140 | }); 141 | 142 | test("removeAllEntitiesChecksWithAllFamilies", function() { 143 | engine.getNodeList( MockNode ); 144 | engine.getNodeList( MockNode2 ); 145 | var entity = new Ash.Entity(); 146 | var entity2 = new Ash.Entity(); 147 | engine.addEntity( entity ); 148 | engine.addEntity( entity2 ); 149 | engine.removeAllEntities(); 150 | equal( MockFamily.instances[0].removeEntityCalls, 2 ); 151 | equal( MockFamily.instances[1].removeEntityCalls, 2 ); 152 | }); 153 | 154 | test("componentAddedChecksWithAllFamilies", function() { 155 | engine.getNodeList( MockNode ); 156 | engine.getNodeList( MockNode2 ); 157 | var entity = new Ash.Entity(); 158 | engine.addEntity( entity ); 159 | entity.add( new Point() ); 160 | equal( MockFamily.instances[0].componentAddedCalls, 1 ); 161 | equal( MockFamily.instances[1].componentAddedCalls, 1 ); 162 | }); 163 | 164 | test("componentRemovedChecksWithAllFamilies", function() { 165 | engine.getNodeList( MockNode ); 166 | engine.getNodeList( MockNode2 ); 167 | var entity = new Ash.Entity(); 168 | engine.addEntity( entity ); 169 | entity.add( new Point() ); 170 | entity.remove( Point ); 171 | equal( MockFamily.instances[0].componentAddedCalls, 1 ); 172 | equal( MockFamily.instances[1].componentAddedCalls, 1 ); 173 | }); 174 | 175 | test("getNodeListCreatesFamily", function() { 176 | engine.getNodeList( MockNode ); 177 | equal( MockFamily.instances.length, 1 ); 178 | }); 179 | 180 | test("getNodeListChecksAllEntities", function() { 181 | engine.addEntity( new Ash.Entity() ); 182 | engine.addEntity( new Ash.Entity() ); 183 | engine.getNodeList( MockNode ); 184 | equal( MockFamily.instances[0].newEntityCalls, 2 ); 185 | }); 186 | 187 | test("releaseNodeListCallsCleanUp", function() { 188 | engine.getNodeList( MockNode ); 189 | engine.releaseNodeList( MockNode ); 190 | equal( MockFamily.instances[0].cleanUpCalls, 1 ); 191 | }); 192 | }); 193 | -------------------------------------------------------------------------------- /test/spec/entity.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Testing Entity 3 | */ 4 | define ([ 5 | 'ash-framework' 6 | ], function(Ash) { 7 | 'use strict'; 8 | 9 | // prepare Mock components 10 | var MockComponent = Ash.Class.extend({ 11 | constructor: function () { 12 | this.value = 0; 13 | } 14 | }); 15 | 16 | var MockComponent2 = Ash.Class.extend({ 17 | constructor: function () { 18 | this.value = ''; 19 | } 20 | }); 21 | 22 | var MockComponentExtended = MockComponent.extend({ 23 | constructor: function () { 24 | this.other = 2; 25 | } 26 | }); 27 | 28 | var entity; 29 | 30 | module("Test Entities", { 31 | setup : function() { 32 | entity = new Ash.Entity(); 33 | }, 34 | teardown : function() { 35 | entity = null; 36 | } 37 | }); 38 | 39 | test("addReturnsReferenceToEntity", function() { 40 | var component = new MockComponent(); 41 | var e = entity.add( component ); 42 | strictEqual(entity, e); 43 | }); 44 | 45 | test("willRetrieveJustAddedComponent", function() { 46 | var component = new MockComponent(); 47 | entity.add(component); 48 | var all = entity.getAll(); 49 | equal(all.length, 1); 50 | strictEqual(all[0], component); 51 | }); 52 | 53 | test("canStoreAndRetrieveComponent", function() { 54 | var component = new MockComponent(); 55 | entity.add( component ); 56 | strictEqual(entity.get( MockComponent ), component); 57 | }); 58 | 59 | test("canStoreAndRetrieveMultipleComponents", function() { 60 | var component1 = new MockComponent(); 61 | entity.add( component1 ); 62 | var component2 = new MockComponent2(); 63 | entity.add( component2 ); 64 | strictEqual(entity.get( MockComponent ), component1); 65 | strictEqual(entity.get( MockComponent2 ), component2); 66 | }); 67 | 68 | test("canReplaceComponent", function() { 69 | var component1 = new MockComponent(); 70 | entity.add( component1 ); 71 | var component2 = new MockComponent(); 72 | component2.value = 2; 73 | entity.add( component2 ); 74 | strictEqual(entity.get( MockComponent ), component2); 75 | }); 76 | 77 | test("canStoreBaseAndExtendedComponents", function() { 78 | var component1 = new MockComponent(); 79 | entity.add( component1 ); 80 | var component2 = new MockComponentExtended(); 81 | entity.add( component2 ); 82 | strictEqual( entity.get( MockComponent ), component1 ); 83 | strictEqual( entity.get( MockComponentExtended ), component2 ); 84 | }); 85 | 86 | test("canStoreExtendedComponentAsBaseType", function() { 87 | var component = new MockComponentExtended(); 88 | entity.add( component, MockComponent ); 89 | strictEqual( entity.get( MockComponent ), component ); 90 | ok( entity.has( MockComponent ) ); 91 | }); 92 | 93 | test("getReturnNullIfNoComponent", function() { 94 | strictEqual(entity.get( MockComponent ), null); 95 | }); 96 | 97 | test("willRetrieveAllComponents", function() { 98 | var component1 = new MockComponent(); 99 | entity.add( component1 ); 100 | var component2 = new MockComponent2(); 101 | entity.add( component2 ); 102 | var all = entity.getAll(); 103 | equal(all.length, 2); 104 | notEqual(all.indexOf(component1), -1); 105 | notEqual(all.indexOf(component2), -1); 106 | }); 107 | 108 | test("hasComponentIsFalseIfComponentTypeNotPresent", function() { 109 | var component2 = new MockComponent2(); 110 | entity.add( component2 ); 111 | strictEqual( entity.has( MockComponent ), false ); 112 | }); 113 | 114 | test("canRemoveComponent", function() { 115 | var component = new MockComponent(); 116 | entity.add( component ); 117 | entity.remove( MockComponent ); 118 | strictEqual( entity.get( MockComponent ), null); 119 | strictEqual( entity.has( MockComponent ), false ); 120 | }); 121 | 122 | test("storingComponentTriggersAddedSignal", 1, function() { 123 | var component = new MockComponent(); 124 | var callback = function() { 125 | ok(true, 'added signal is triggered'); 126 | // TODO check the component 127 | 128 | start(); 129 | }; 130 | entity.componentAdded.add( callback ); 131 | entity.add(component); 132 | }); 133 | 134 | test("removingComponentTriggersRemovedSignal", 1, function() { 135 | var component = new MockComponent(); 136 | var callback = function() { 137 | ok(true, 'removed signal is triggered'); 138 | // TODO check the component 139 | 140 | start(); 141 | }; 142 | entity.componentRemoved.add(callback); 143 | entity.add(component); 144 | entity.remove( MockComponent ); 145 | }); 146 | }); 147 | -------------------------------------------------------------------------------- /test/spec/nodelist.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Testing NodeList 3 | */ 4 | define ([ 5 | 'ash-framework', 6 | 'point' 7 | ], function(Ash, Point) { 8 | 'use strict'; 9 | 10 | var nodes, tempNode; 11 | 12 | // prepare mock node 13 | var MockNode = Ash.Node.extend({ 14 | point: null, 15 | types: { 16 | point: Point 17 | }, 18 | constructor: function (x, y) { 19 | x = x || 0; 20 | y = y || 0; 21 | this.point = new Point(x, y); 22 | } 23 | }); 24 | 25 | module("Test Nodelist", { 26 | setup: function() { 27 | nodes = new Ash.NodeList(); 28 | }, 29 | teardown: function() { 30 | nodes = null; 31 | } 32 | }); 33 | 34 | test("addingNodeTriggersAddedSignal", 1, function() { 35 | stop(); 36 | var node = new MockNode(); 37 | var callback = function() { 38 | ok(true); 39 | nodes.nodeAdded.remove( callback ); 40 | start(); 41 | }; 42 | nodes.nodeAdded.add( callback ); 43 | nodes.add( node ); 44 | }); 45 | 46 | test("removingNodeTriggersRemovedSignal", 1, function() { 47 | stop(); 48 | var node = new MockNode(); 49 | var callback = function() { 50 | ok(true); 51 | nodes.nodeRemoved.remove( callback ); 52 | start(); 53 | }; 54 | nodes.add( node ); 55 | nodes.nodeRemoved.add( callback ); 56 | nodes.remove( node ); 57 | }); 58 | 59 | test("allNodesAreCoveredDuringIteration", function() { 60 | var nodeArray = [], 61 | node; 62 | for( var i = 0; i<5; ++i ) 63 | { 64 | node = new MockNode(); 65 | nodeArray.push( node ); 66 | nodes.add( node ); 67 | } 68 | for( node = nodes.head; node; node = node.next ) { 69 | var index = nodeArray.indexOf( node ); 70 | if( index > -1 ) { 71 | nodeArray.splice( index, 1 ); 72 | } 73 | } 74 | equal(nodeArray.length, 0); 75 | }); 76 | 77 | test("removingCurrentNodeDuringIterationIsValid", function() { 78 | var nodeArray = [], 79 | node, 80 | count = 0; 81 | 82 | for( var i = 0; i<5; ++i ) { 83 | node = new MockNode(); 84 | nodeArray.push( node ); 85 | nodes.add( node ); 86 | } 87 | for( node = nodes.head; node; node = node.next ) 88 | { 89 | var index = nodeArray.indexOf( node ); 90 | if( index > -1 ) { 91 | nodeArray.splice( index, 1 ); 92 | } 93 | if( ++count == 2 ) { 94 | nodes.remove( node ); 95 | } 96 | } 97 | equal( nodeArray.length, 0 ); 98 | }); 99 | 100 | test("removingNextNodeDuringIterationIsValid", function() { 101 | var nodeArray = [], 102 | node, 103 | count = 0; 104 | 105 | for( var i = 0; i < 5; ++i ) { 106 | node = new MockNode(); 107 | nodeArray.push( node ); 108 | nodes.add( node ); 109 | } 110 | for( node = nodes.head; node; node = node.next ) 111 | { 112 | var index = nodeArray.indexOf( node ); 113 | if( index > -1 ) { 114 | nodeArray.splice( index, 1 ); 115 | } 116 | if( ++count == 2 ) 117 | { 118 | nodes.remove( node.next ); 119 | } 120 | } 121 | equal( nodeArray.length, 1 ); 122 | }); 123 | 124 | test("componentAddedSignalContainsCorrectParameters", 1, function() { 125 | stop(); 126 | tempNode = new MockNode(); 127 | var callback = function( signalNode ) { 128 | strictEqual( tempNode, signalNode ); 129 | nodes.nodeAdded.remove( callback ); 130 | start(); 131 | }; 132 | nodes.nodeAdded.add( callback ); 133 | nodes.add( tempNode ); 134 | }); 135 | 136 | test("componentRemovedSignalContainsCorrectParameters", 1, function() { 137 | stop(); 138 | tempNode = new MockNode(); 139 | var callback = function( signalNode ) { 140 | strictEqual( tempNode, signalNode ); 141 | nodes.nodeRemoved.remove( callback ); 142 | start(); 143 | }; 144 | nodes.add( tempNode ); 145 | nodes.nodeRemoved.add( callback ); 146 | nodes.remove( tempNode ); 147 | }); 148 | 149 | test("nodesInitiallySortedInOrderOfAddition", function() { 150 | var node1 = new MockNode(), 151 | node2 = new MockNode(), 152 | node3 = new MockNode(); 153 | nodes.add( node1 ); 154 | nodes.add( node2 ); 155 | nodes.add( node3 ); 156 | ok( testNodeOrder( nodes, [node1, node2, node3] ) ); 157 | }); 158 | 159 | test("swappingOnlyTwoNodesChangesTheirOrder", function() { 160 | var node1 = new MockNode(), 161 | node2 = new MockNode(); 162 | nodes.add( node1 ); 163 | nodes.add( node2 ); 164 | nodes.swap( node1, node2 ); 165 | ok( testNodeOrder( nodes, [node2, node1] ) ); 166 | }); 167 | 168 | test("swappingAdjacentNodesChangesTheirPositions", function() { 169 | var node1 = new MockNode(), 170 | node2 = new MockNode(), 171 | node3 = new MockNode(), 172 | node4 = new MockNode(); 173 | nodes.add( node1 ); 174 | nodes.add( node2 ); 175 | nodes.add( node3 ); 176 | nodes.add( node4 ); 177 | nodes.swap( node2, node3 ); 178 | ok( testNodeOrder( nodes, [node1, node3, node2, node4] ) ); 179 | }); 180 | 181 | test("swappingNonAdjacentNodesChangesTheirPositions", function() { 182 | var node1 = new MockNode(), 183 | node2 = new MockNode(), 184 | node3 = new MockNode(), 185 | node4 = new MockNode(), 186 | node5 = new MockNode(); 187 | nodes.add( node1 ); 188 | nodes.add( node2 ); 189 | nodes.add( node3 ); 190 | nodes.add( node4 ); 191 | nodes.add( node5 ); 192 | nodes.swap( node2, node4 ); 193 | ok( testNodeOrder( nodes, [node1, node4, node3, node2, node5] ) ); 194 | }); 195 | 196 | test("swappingEndNodesChangesTheirPositions", function() { 197 | var node1 = new MockNode(), 198 | node2 = new MockNode(), 199 | node3 = new MockNode(); 200 | nodes.add( node1 ); 201 | nodes.add( node2 ); 202 | nodes.add( node3 ); 203 | nodes.swap( node1, node3 ); 204 | ok( testNodeOrder( nodes, [node3, node2, node1] ) ); 205 | }); 206 | 207 | test("insertionSortCorrectlySortsSortedNodes", function() { 208 | var node1 = new MockNode(1, 0), 209 | node2 = new MockNode(2, 0), 210 | node3 = new MockNode(3, 0), 211 | node4 = new MockNode(4, 0); 212 | nodes.add( node1 ); 213 | nodes.add( node2 ); 214 | nodes.add( node3 ); 215 | nodes.add( node4 ); 216 | nodes.insertionSort( sortFunction ); 217 | ok( testNodeOrder( nodes, [node1, node2, node3, node4] ) ); 218 | }); 219 | 220 | test("insertionSortCorrectlySortsReversedNodes", function() { 221 | var node1 = new MockNode(1, 0), 222 | node2 = new MockNode(2, 0), 223 | node3 = new MockNode(3, 0), 224 | node4 = new MockNode(4, 0); 225 | nodes.add( node4 ); 226 | nodes.add( node3 ); 227 | nodes.add( node2 ); 228 | nodes.add( node1 ); 229 | nodes.insertionSort( sortFunction ); 230 | ok( testNodeOrder( nodes, [node1, node2, node3, node4] ) ); 231 | }); 232 | 233 | test("insertionSortCorrectlySortsMixedNodes", function() { 234 | var node1 = new MockNode(1, 0), 235 | node2 = new MockNode(2, 0), 236 | node3 = new MockNode(3, 0), 237 | node4 = new MockNode(4, 0), 238 | node5 = new MockNode(5, 0); 239 | nodes.add( node3 ); 240 | nodes.add( node4 ); 241 | nodes.add( node1 ); 242 | nodes.add( node5 ); 243 | nodes.add( node2 ); 244 | nodes.insertionSort( sortFunction ); 245 | ok( testNodeOrder( nodes, [node1, node2, node3, node4, node5] ) ); 246 | }); 247 | 248 | test("insertionSortRetainsTheOrderOfEquivalentNodes", function() { 249 | var node1 = new MockNode(1, 0), 250 | node2 = new MockNode(2, 0), 251 | node3 = new MockNode(3, 0), 252 | node4 = new MockNode(4, 0), 253 | node5 = new MockNode(4, 0); 254 | nodes.add( node3 ); 255 | nodes.add( node4 ); 256 | nodes.add( node1 ); 257 | nodes.add( node5 ); 258 | nodes.add( node2 ); 259 | nodes.insertionSort( sortFunction ); 260 | ok( testNodeOrder( nodes, [node1, node2, node3, node4, node5] ) ); 261 | }); 262 | 263 | test("mergeSortCorrectlySortsSortedNodes", function() { 264 | var node1 = new MockNode(1, 0), 265 | node2 = new MockNode(2, 0), 266 | node3 = new MockNode(3, 0), 267 | node4 = new MockNode(4, 0); 268 | nodes.add( node1 ); 269 | nodes.add( node2 ); 270 | nodes.add( node3 ); 271 | nodes.add( node4 ); 272 | nodes.mergeSort( sortFunction ); 273 | ok( testNodeOrder( nodes, [node1, node2, node3, node4] ) ); 274 | }); 275 | 276 | test("mergeSortCorrectlySortsReversedNodes", function() { 277 | var node1 = new MockNode(1, 0), 278 | node2 = new MockNode(2, 0), 279 | node3 = new MockNode(3, 0), 280 | node4 = new MockNode(4, 0); 281 | nodes.add( node4 ); 282 | nodes.add( node3 ); 283 | nodes.add( node2 ); 284 | nodes.add( node1 ); 285 | nodes.mergeSort( sortFunction ); 286 | ok( testNodeOrder( nodes, [node1, node2, node3, node4] ) ); 287 | }); 288 | 289 | test("mergeSortCorrectlySortsMixedNodes", function() { 290 | var node1 = new MockNode(1, 0), 291 | node2 = new MockNode(2, 0), 292 | node3 = new MockNode(3, 0), 293 | node4 = new MockNode(4, 0), 294 | node5 = new MockNode(5, 0); 295 | nodes.add( node3 ); 296 | nodes.add( node4 ); 297 | nodes.add( node1 ); 298 | nodes.add( node5 ); 299 | nodes.add( node2 ); 300 | nodes.mergeSort( sortFunction ); 301 | ok( testNodeOrder( nodes, [node1, node2, node3, node4, node5] ) ); 302 | }); 303 | 304 | function sortFunction( node1, node2 ) { 305 | // sort based on x value 306 | return node1.point.x - node2.point.x; 307 | } 308 | 309 | function testNodeOrder( nodes, nodeArray ) { 310 | var node, 311 | index = 0, 312 | testResult = true; 313 | for( node = nodes.head; node; node = node.next ) 314 | { 315 | if( node !== nodeArray[index] ) { 316 | testResult = false; 317 | } 318 | ++index; 319 | } 320 | return testResult; 321 | } 322 | }); 323 | -------------------------------------------------------------------------------- /test/spec/system.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Testing System 3 | */ 4 | define ([ 5 | 'ash-framework' 6 | ], function(Ash) { 7 | 'use strict'; 8 | 9 | var async, 10 | asyncCallback, 11 | engine, 12 | system1, 13 | system2; 14 | 15 | // mock system 16 | var MockSystem = Ash.System.extend({ 17 | constructor: function () { }, 18 | 19 | addToEngine: function( engine ) { 20 | if( typeof asyncCallback == "function" ) { 21 | asyncCallback( this, "added", engine ); 22 | } 23 | }, 24 | 25 | removeFromEngine: function( engine ) { 26 | if( typeof asyncCallback == "function" ) { 27 | asyncCallback( this, "removed", engine ); 28 | } 29 | }, 30 | 31 | update: function( time ) { 32 | if( typeof asyncCallback == "function" ) { 33 | asyncCallback( this, "update", time ); 34 | } 35 | } 36 | }); 37 | 38 | module("Test Systems", { 39 | setup : function() { 40 | engine = new Ash.Engine(); 41 | }, 42 | teardown : function() { 43 | engine = null; 44 | asyncCallback = null; 45 | } 46 | }); 47 | 48 | test("systemsGetterReturnsAllTheSystems", function() { 49 | var system1 = new Ash.System(); 50 | engine.addSystem( system1, 1 ); 51 | var system2 = new Ash.System(); 52 | engine.addSystem( system2, 1 ); 53 | equal( engine.systems.length, 2 ); 54 | notEqual(engine.systems.indexOf(system1), -1); 55 | notEqual(engine.systems.indexOf(system2), -1); 56 | }); 57 | 58 | test("addSystemCallsAddToEngine", 2, function() { 59 | stop(); 60 | var system = new MockSystem(); 61 | asyncCallback = addedCallbackMethod; 62 | engine.addSystem( system, 0 ); 63 | setTimeout(function(){ 64 | start(); 65 | }, 10); 66 | }); 67 | 68 | test("removeSystemCallsRemovedFromEngine", 2, function() { 69 | stop(); 70 | var system = new MockSystem(); 71 | engine.addSystem( system, 0 ); 72 | asyncCallback = removedCallbackMethod; 73 | engine.removeSystem( system ); 74 | setTimeout(function(){ 75 | start(); 76 | }, 10); 77 | }); 78 | 79 | test("engineCallsUpdateOnSystems", 2, function() { 80 | stop(); 81 | var system = new MockSystem(); 82 | engine.addSystem( system, 0 ); 83 | asyncCallback = updateCallbackMethod; 84 | engine.update( 0.1 ); 85 | setTimeout(function() { 86 | start(); 87 | }, 10); 88 | }); 89 | 90 | test("defaultPriorityIsZero", function() { 91 | var system = new MockSystem(); 92 | equal( system.priority, 0 ); 93 | }); 94 | 95 | test("canSetPriorityWhenAddingSystem", function() { 96 | var system = new MockSystem(); 97 | engine.addSystem( system, 10 ); 98 | equal( system.priority, 10 ); 99 | }); 100 | 101 | test("systemsUpdatedInPriorityOrderIfSameAsAddOrder", 2, function() { 102 | system1 = new MockSystem(); 103 | engine.addSystem( system1, 10 ); 104 | system2 = new MockSystem(); 105 | engine.addSystem( system2, 20 ); 106 | asyncCallback = updateCallbackMethod1; 107 | engine.update( 0.1 ); 108 | }); 109 | 110 | test("systemsUpdatedInPriorityOrderIfReverseOfAddOrder", 2, function() { 111 | system2 = new MockSystem(); 112 | engine.addSystem( system2, 20 ); 113 | system1 = new MockSystem(); 114 | engine.addSystem( system1, 10 ); 115 | asyncCallback = updateCallbackMethod1; 116 | engine.update( 0.1 ); 117 | }); 118 | 119 | test("systemsUpdatedInPriorityOrderIfPrioritiesAreNegative", 2, function() { 120 | system2 = new MockSystem(); 121 | engine.addSystem( system2, 10 ); 122 | system1 = new MockSystem(); 123 | engine.addSystem( system1, -20 ); 124 | asyncCallback = updateCallbackMethod1; 125 | engine.update( 0.1 ); 126 | }); 127 | 128 | test("updatingIsFalseBeforeUpdate", function() { 129 | ok( engine.updating === false ); 130 | }); 131 | 132 | test("updatingIsTrueDuringUpdate", 1, function() { 133 | stop(); 134 | var system = new MockSystem(); 135 | engine.addSystem( system, 0 ); 136 | asyncCallback = assertsUpdatingIsTrue; 137 | engine.update( 0.1 ); 138 | start(); 139 | }); 140 | 141 | test("updatingIsFalseAfterUpdate", function() { 142 | engine.update(0.1); 143 | ok( engine.updating === false ); 144 | }); 145 | 146 | test("completeSignalIsDispatchedAfterUpdate", 1, function() { 147 | var system = new MockSystem(); 148 | engine.addSystem( system, 0 ); 149 | asyncCallback = listensForUpdateComplete; 150 | engine.update(0.1); 151 | }); 152 | 153 | test("getSystemReturnsTheSystem", function(){ 154 | var system1 = new MockSystem(); 155 | engine.addSystem( system1, 0 ); 156 | engine.addSystem( new Ash.System(), 0 ); 157 | strictEqual( engine.getSystem( MockSystem ), system1 ); 158 | }); 159 | 160 | test("getSystemReturnsNullIfNoSuchSystem", function() { 161 | engine.addSystem( new Ash.System(), 0 ); 162 | strictEqual( engine.getSystem( MockSystem ), null ); 163 | }); 164 | 165 | test("removeAllSystemsDoesWhatItSays", function() { 166 | engine.addSystem( new Ash.System(), 0 ); 167 | engine.addSystem( new MockSystem(), 0 ); 168 | engine.removeAllSystems(); 169 | strictEqual( engine.getSystem( MockSystem ), null ); 170 | strictEqual( engine.getSystem( Ash.System ), null ); 171 | }); 172 | 173 | test("removeSystemAndAddItAgainDoesNotCauseInvalidLinkedList", function() { 174 | var systemB = new Ash.System(); 175 | var systemC = new Ash.System(); 176 | engine.addSystem( systemB, 0 ); 177 | engine.addSystem( systemC, 0 ); 178 | engine.removeSystem( systemB ); 179 | engine.addSystem( systemB, 0 ); 180 | strictEqual( systemC.previous, null ); 181 | strictEqual( systemB.next, null ); 182 | }); 183 | 184 | function addedCallbackMethod( system, action, systemEngine ) { 185 | equal( action, "added" ); 186 | strictEqual( systemEngine, engine ); 187 | } 188 | 189 | function removedCallbackMethod( system, action, systemEngine ) { 190 | equal( action, "removed" ); 191 | strictEqual( systemEngine, engine ); 192 | } 193 | 194 | function updateCallbackMethod( system, action, time ) { 195 | equal( action, "update" ); 196 | equal( time, 0.1 ); 197 | } 198 | 199 | function updateCallbackMethod1( system, action, time ) { 200 | asyncCallback = updateCallbackMethod2; 201 | strictEqual( system, system1 ); 202 | } 203 | 204 | function updateCallbackMethod2( system, action, time ) { 205 | strictEqual( system, system2 ); 206 | } 207 | 208 | function assertsUpdatingIsTrue( system, action, time ) { 209 | ok( engine.updating === true ); 210 | } 211 | 212 | function updateComplete() { 213 | ok( true ); 214 | engine.updateComplete.remove(updateComplete); 215 | } 216 | 217 | function listensForUpdateComplete( system, action, time ) { 218 | engine.updateComplete.add(updateComplete); 219 | } 220 | }); 221 | -------------------------------------------------------------------------------- /test/test_build.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Ash-js Test Build 6 | 7 | 8 | 9 | 10 | 11 | 12 | 19 | 20 |

    Ash framework Build Test Suite

    21 |

    22 |
    23 |

    24 |
      25 |
      test markup
      26 | 27 | 28 | -------------------------------------------------------------------------------- /test/test_build_min.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Ash Framework Test 6 | 7 | 8 | 9 | 10 | 11 | 12 | 19 | 20 |

      Minified Build Test Suite

      21 |

      22 |
      23 |

      24 |
        25 |
        test markup
        26 | 27 | 28 | -------------------------------------------------------------------------------- /test/test_build_min_require.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Ash Framework Test 6 | 7 | 8 | 9 | 10 | 11 | 12 | 22 | 23 |

        Minified Build (with RequireJS) Test Suite

        24 |

        25 |
        26 |

        27 |
          28 |
          test markup
          29 | 30 | 31 | -------------------------------------------------------------------------------- /test/test_build_require.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Ash-js Test Build with RequireJS 6 | 7 | 8 | 9 | 10 | 11 | 12 | 22 | 23 |

          Ash framework Build (with RequireJS) Test Suite

          24 |

          25 |
          26 |

          27 |
            28 |
            test markup
            29 | 30 | 31 | -------------------------------------------------------------------------------- /test/utils/point.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 2D point 3 | * 4 | * @author Brett Jephson 5 | */ 6 | define(function () { 7 | 'use strict'; 8 | 9 | var Point = function (x, y) { 10 | this.x = x || 0; 11 | this.y = y || 0; 12 | }; 13 | Point.VERSION = "0.1.0"; 14 | Point.prototype.x = null; 15 | Point.prototype.y = null; 16 | Point.prototype.distanceSquaredTo = function( targetPoint ) { 17 | var dx = this.x - targetPoint.x, 18 | dy = this.y - targetPoint.y; 19 | return dx * dx + dy * dy; 20 | }; 21 | Point.prototype.distanceTo = function( targetPoint ) { 22 | var dx = this.x - targetPoint.x, 23 | dy = this.y - targetPoint.y; 24 | return Math.sqrt( dx * dx + dy * dy ); 25 | }; 26 | 27 | return Point; 28 | }); 29 | -------------------------------------------------------------------------------- /test/utils/point3.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3D point 3 | * 4 | * @author Brett Jephson 5 | */ 6 | define(function () { 7 | 'use strict'; 8 | 9 | var Point3 = function (x, y, z) { 10 | this.x = x || 0; 11 | this.y = y || 0; 12 | this.z = z || 0; 13 | }; 14 | Point3.VERSION = "0.1.0"; 15 | Point3.prototype.x = null; 16 | Point3.prototype.y = null; 17 | Point3.prototype.z = null; 18 | 19 | return Point3; 20 | }); 21 | --------------------------------------------------------------------------------