├── docs └── MAIN.md ├── .gitattributes ├── src ├── _outro.js ├── _intro.js ├── main.js ├── event.js ├── ajax.js ├── helpers.js ├── class.js ├── Tiled.js ├── effect.js └── template.js ├── .bowerrc ├── test ├── all.js ├── all.html └── lib │ ├── qunit.css │ └── qunit.js ├── .travis.yml ├── demo ├── images │ ├── mountain_landscape_23.png │ ├── mountain.json │ └── mountain.tmx ├── assets │ ├── main.css │ └── normalize.css └── index.html ├── specs ├── tiled-spec.js ├── class-spec.js ├── event-spec.js ├── template-spec.js ├── helpers-spec.js ├── ajax-spec.js └── effect-spec.js ├── bower.json ├── .editorconfig ├── .gitignore ├── .jshintrc ├── index.html ├── README.md ├── package.json ├── LICENSE.txt ├── .jscs.json ├── Gruntfile.js └── dist ├── Chinaware.min.js └── Chinaware.js /docs/MAIN.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /src/_outro.js: -------------------------------------------------------------------------------- 1 | }(this)); 2 | -------------------------------------------------------------------------------- /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "lib", 3 | "json": "bower.json" 4 | } -------------------------------------------------------------------------------- /src/_intro.js: -------------------------------------------------------------------------------- 1 | (function(root, undefined) { 2 | 3 | "use strict"; 4 | -------------------------------------------------------------------------------- /test/all.js: -------------------------------------------------------------------------------- 1 | test("the base function exists", function() { 2 | ok(Chinaware); 3 | }); 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '0.10' 4 | before_install: 5 | - npm install -g grunt bower 6 | -------------------------------------------------------------------------------- /demo/images/mountain_landscape_23.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phodal/chinaware/master/demo/images/mountain_landscape_23.png -------------------------------------------------------------------------------- /specs/tiled-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe("Tiled Map", function () { 4 | var L = new Chinaware(); 5 | 6 | it('respects instanceof', function () { 7 | 8 | }); 9 | 10 | }); 11 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Chinaware", 3 | "version": "0.0.0", 4 | "main": "dist/Chinaware.min.js", 5 | "ignore": [ 6 | "**/.*", 7 | "node_modules", 8 | "bower_components", 9 | "test" 10 | ], 11 | "dependencies": {} 12 | } 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bower_components/ 2 | node_modules/ 3 | temp/ 4 | tmp/ 5 | logs/ 6 | log/ 7 | results/ 8 | 9 | *.seed 10 | *.log 11 | *.csv 12 | *.dat 13 | *.out 14 | *.pid 15 | *.gz 16 | 17 | *.iml 18 | .idea/ 19 | *.ipr 20 | *.iws 21 | learning/ 22 | coverage/ 23 | report/ 24 | _SpecRunner.html 25 | .grunt 26 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "camelcase": true, 4 | "curly": true, 5 | "immed": true, 6 | "latedef": true, 7 | "newcap": true, 8 | "noarg": true, 9 | "undef": true, 10 | "unused": true, 11 | "strict": true, 12 | "trailing": true, 13 | "smarttabs": true, 14 | "browser": true 15 | } 16 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | /* Chinaware main */ 2 | 3 | // Base function. 4 | var Chinaware = function() { 5 | // Add functionality here. 6 | return true; 7 | }; 8 | 9 | 10 | // Version. 11 | Chinaware.VERSION = '0.0.0'; 12 | 13 | 14 | // Export to the root, which is probably `window`. 15 | root.Chinaware = Chinaware; 16 | -------------------------------------------------------------------------------- /demo/assets/main.css: -------------------------------------------------------------------------------- 1 | /* Styles for Chinaware demo 2 | * 3 | * Author: Phodal Huang 4 | */ 5 | 6 | /* === resets === */ 7 | 8 | /* === defaults === */ 9 | 10 | /* === generic styles */ 11 | 12 | /* === layout === */ 13 | 14 | /* === modules === */ 15 | 16 | /* === specific styles */ 17 | 18 | /* === subsections === */ 19 | 20 | /* === hacks / overrides === */ 21 | -------------------------------------------------------------------------------- /test/all.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | QUnit Example 6 | 7 | 8 | 9 |
10 |
11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Chinaware | a light weight javascript game engine for mobile 5 | 6 | 7 | 8 |

Chinaware

9 |

a light weight javascript game engine for mobile

10 |

Check out the project homepage.

11 |

Check out the source code on GitHub.

12 |

Copyright 1 Phodal Huang

13 | 14 | 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Chinaware 2 | 3 | > a light weight javascript game engine for mobile 4 | 5 | ##Demo 6 | 7 | 8 | 9 | ##Doing 10 | 11 | - TiledMap Render(@phodal) 12 | 13 | ##Done 14 | 15 | - Ajax(@phodal) 16 | - Event(@phodal) 17 | 18 | ##Todo 19 | - DOM 20 | - Render 21 | - Loader 22 | - Input(mouse,keyboard,touch) 23 | - UI 24 | - Animation 25 | - Audio 26 | 27 | 附加 28 | 29 | - Box2D 30 | - Scenes 31 | 32 | ## License 33 | 34 | © 2015 [Phodal Huang](http://www.phodal.com). This code is distributed under the MIT license. See `LICENSE.txt` in this directory. 35 | 36 | [待我代码编成,娶你为妻可好](http://www.xuntayizhan.com/person/ji-ke-ai-qing-zhi-er-shi-dai-wo-dai-ma-bian-cheng-qu-ni-wei-qi-ke-hao-wan/) 37 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Chinaware", 3 | "version": "0.0.0", 4 | "main": "dist/Chinaware.min.js", 5 | "description": "a light weight javascript game engine for mobile ", 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/phodal/Chinaware.git" 9 | }, 10 | "keywords": [], 11 | "author": "Phodal Huang", 12 | "license": "MIT", 13 | "readmeFilename": "README.md", 14 | "dependencies": {}, 15 | "devDependencies": { 16 | "grunt": "~0.4.1", 17 | "jasmine-ajax": "2.99.0", 18 | "grunt-replace": "0.8.0", 19 | "grunt-contrib-concat": "~0.3.0", 20 | "grunt-contrib-jshint": "~0.6.4", 21 | "grunt-contrib-uglify": "~0.2.4", 22 | "grunt-contrib-watch": "~0.5.3", 23 | "grunt-contrib-jasmine": "~0.8.0", 24 | "grunt-template-jasmine-istanbul": "0.3.3", 25 | "grunt-contrib-connect": "~0.9.0", 26 | "codeclimate-test-reporter": "0.0.4" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/event.js: -------------------------------------------------------------------------------- 1 | var Event = { 2 | on: function(event, callback){ 3 | this._events = this._events || {}; 4 | this._events[event] = this._events[event] || []; 5 | this._events[event].push(callback); 6 | }, 7 | off: function(event, callback){ 8 | this._events = this._events || {}; 9 | if (event in this._events === false) { 10 | return; 11 | } 12 | this._events[event].splice(this._events[event].indexOf(callback), 1); 13 | }, 14 | trigger: function(event){ 15 | this._events = this._events || {}; 16 | if (event in this._events === false) { 17 | return; 18 | } 19 | for (var i = 0; i < this._events[event].length; i++) { 20 | this._events[event][i].apply(this, Array.prototype.slice.call(arguments, 1)); 21 | } 22 | } 23 | }; 24 | 25 | var event = { 26 | Event: Event 27 | }; 28 | 29 | 30 | Chinaware.prototype = Chinaware.extend(Chinaware.prototype, event); 31 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Chinaware demo 5 | 6 | 7 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |

See the project homepage. 21 |

Check out the project repo. 22 |

Copyright 2015 Phodal Huang

23 | 24 | 25 | 26 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/ajax.js: -------------------------------------------------------------------------------- 1 | Chinaware.get = function (url, callback) { 2 | Chinaware.send(url, 'GET', callback); 3 | }; 4 | 5 | Chinaware.load = function (url, callback) { 6 | Chinaware.send(url, 'GET', callback); 7 | }; 8 | 9 | Chinaware.post = function (url, data, callback) { 10 | Chinaware.send(url, 'POST', callback, data); 11 | }; 12 | 13 | Chinaware.send = function (url, method, callback, data) { 14 | data = data || null; 15 | var request = new XMLHttpRequest(); 16 | if (callback instanceof Function) { 17 | request.onreadystatechange = function () { 18 | if (request.readyState === 4 && (request.status === 200 || request.status === 0)) { 19 | callback(request.responseText); 20 | } 21 | }; 22 | } 23 | request.open(method, url, true); 24 | if (data instanceof Object) { 25 | data = JSON.stringify(data); 26 | request.setRequestHeader('Content-Type', 'application/json'); 27 | } 28 | request.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); 29 | request.send(data); 30 | }; 31 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Phodal Huang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /specs/class-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe("Class", function () { 4 | var L, ninja, Person, killer; 5 | 6 | L = new Chinaware(); 7 | var Person = L.Class(function (person) { 8 | person.init = function (isDancing) { 9 | this.dancing = isDancing 10 | }; 11 | person.dance = function () { 12 | return this.dancing 13 | }; 14 | }); 15 | 16 | var Ninja = L.Class(Person, function (ninja, person) { 17 | ninja.init = function () { 18 | person.init.call(this, false) 19 | }; 20 | ninja.swingSword = function () { 21 | return 'swinging sword!' 22 | }; 23 | }); 24 | 25 | ninja = Ninja(); 26 | var Killer = Ninja.extend({name: "killer"}); 27 | killer = Killer(); 28 | 29 | it('respects instanceof', function () { 30 | var results = ninja instanceof Person; 31 | expect(results).toBe(true); 32 | }); 33 | 34 | it('inherits methods (also super)', function () { 35 | expect(ninja.dance).toBeUndefined(); 36 | }); 37 | 38 | it('extend methods', function () { 39 | expect(killer.name).toEqual('killer'); 40 | }); 41 | 42 | }); 43 | -------------------------------------------------------------------------------- /src/helpers.js: -------------------------------------------------------------------------------- 1 | /* (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 2 | * Underscore may be freely distributed under the MIT license. 3 | */ 4 | 5 | Chinaware.isObject = function (obj) { 6 | var type = typeof obj; 7 | return type === 'function' || type === 'object' && !!obj; 8 | }; 9 | 10 | Chinaware.isFunction = function(obj) { 11 | return typeof obj == 'function' || false; 12 | }; 13 | 14 | Chinaware.defaults = function(obj) { 15 | if (!Chinaware.isObject(obj)) { 16 | return obj; 17 | } 18 | 19 | for (var i = 1, length = arguments.length; i < length; i++) { 20 | var source = arguments[i]; 21 | for (var prop in source) { 22 | if (obj[prop] === void 0) { 23 | obj[prop] = source[prop]; 24 | } 25 | } 26 | } 27 | return obj; 28 | }; 29 | 30 | Chinaware.extend = function (obj) { 31 | if (!Chinaware.isObject(obj)) { 32 | return obj; 33 | } 34 | var source, prop; 35 | for (var i = 1, length = arguments.length; i < length; i++) { 36 | source = arguments[i]; 37 | for (prop in source) { 38 | if (hasOwnProperty.call(source, prop)) { 39 | obj[prop] = source[prop]; 40 | } 41 | } 42 | } 43 | return obj; 44 | }; 45 | -------------------------------------------------------------------------------- /specs/event-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe("Event", function() { 4 | var L; 5 | 6 | beforeEach(function() { 7 | L = new Chinaware(); 8 | }); 9 | 10 | it("should be use promise patterns", function() { 11 | expect('function').toEqual(typeof L.Event.on); 12 | expect('function').toEqual(typeof L.Event.off); 13 | expect('function').toEqual(typeof L.Event.trigger); 14 | }); 15 | 16 | it('should trigger ping event 3 times', function () { 17 | var called = 0, 18 | f1 = function () { called++ }; 19 | 20 | L.Event.on('ping', f1); 21 | L.Event.trigger('ping'); 22 | L.Event.trigger('ping'); 23 | L.Event.trigger('ping'); 24 | expect(called).toEqual(3); 25 | }); 26 | 27 | it('should find ping call 0 times when off events', function () { 28 | var called = 0, 29 | f1 = function () { called++ }; 30 | 31 | L.Event.on('ping', f1); 32 | L.Event.off('ping'); 33 | L.Event.trigger('ping'); 34 | expect(called).toEqual(0); 35 | }); 36 | 37 | it('should no return when off or trigger something no exists', function () { 38 | var called = 0, 39 | f1 = function () { called++ }; 40 | 41 | L.Event.on('ping', f1); 42 | L.Event.off('something'); 43 | L.Event.trigger('something'); 44 | expect(called).toEqual(0); 45 | }); 46 | 47 | }); 48 | -------------------------------------------------------------------------------- /specs/template-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe("Template", function() { 4 | var L; 5 | 6 | beforeEach(function() { 7 | L = new Chinaware(); 8 | var element = document.createElement('div'); 9 | element.id = "results"; 10 | document.body.appendChild(element); 11 | }); 12 | 13 | it("should be able render page", function() { 14 | var data = { 15 | "title": "JavaScript Templates" 16 | }; 17 | 18 | var result = L.Template.tmpl("

{%=o.title%}

\n!@#$%^&*()-=", data); 19 | expect("

JavaScript Templates

\n!@#$%^&*()-=").toEqual(result); 20 | 21 | var special = "\n!@#$%^&*()-=_+{}'\"'[]\|:;/.,{"; 22 | var result = L.Template.tmpl(special, data); 23 | expect(special).toEqual(result); 24 | }); 25 | 26 | it("should be able handler special char", function() { 27 | var data = { 28 | "special": '<>&"\'\x00', 29 | value: 'value', 30 | nullValue: null, 31 | falseValue: false, 32 | zeroValue: 0 33 | }; 34 | 35 | var result = L.Template.tmpl("{%=o.special%}", data); 36 | expect("<>&"'").toEqual(result); 37 | 38 | var result = L.Template.tmpl("{%#o.special%}", data); 39 | expect('<>&"\'\x00').toEqual(result); 40 | 41 | expect(L.Template.tmpl('{% print(o.special); %}',data)).toEqual('<>&"''); 42 | expect(L.Template.tmpl('{% print(o.special, true); %}',data)).toEqual('<>&"\'\x00'); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /specs/helpers-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe("Helper", function () { 4 | var l = Chinaware; 5 | 6 | it("should be a object", function () { 7 | expect(l.isObject([])).toEqual(true); 8 | expect(l.isObject([{}])).toEqual(true); 9 | }); 10 | 11 | describe("Extend", function () { 12 | it("should be able to extend object", function () { 13 | expect(l.extend({one: 1}, {two: 2})).toEqual({one: 1, two: 2}); 14 | expect(l.extend({two: 1}, {two: 2})).toEqual({two: 2}); 15 | }); 16 | 17 | it("should be unable to extend object when no a object", function () { 18 | var results = l.extend("", {two: 2}); 19 | expect(results).toEqual(""); 20 | }); 21 | }); 22 | 23 | describe("Defaults", function () { 24 | it("should be return variable when same key", function () { 25 | var origin = {one: 1}; 26 | var new_object = {one: 2}; 27 | l.defaults(origin, new_object); 28 | expect(origin).toEqual({one: 1}); 29 | }); 30 | 31 | it("should return empty string when defaults is empty string", function () { 32 | var emptyString = ""; 33 | var defaults = {two: 2}; 34 | l.defaults(emptyString, defaults); 35 | expect(emptyString).toEqual(""); 36 | }); 37 | 38 | it("should return defaults when defaults is empty object", function () { 39 | var empty = {}; 40 | var defaults = {two: 2}; 41 | l.defaults(empty, defaults); 42 | expect(empty).toEqual({two: 2}); 43 | }); 44 | }); 45 | 46 | }); 47 | -------------------------------------------------------------------------------- /src/class.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Chinaware Class 0.0.1 3 | * JavaScript Class built-in inheritance system 4 | *(c) 2015, Fengda Huang - http://www.phodal.com 5 | * 6 | * Copyright (c) 2011, 2012 Jeanine Adkisson. 7 | * MIT Licensed. 8 | * Inspired by https://github.com/munro/self, https://github.com/jneen/pjs 9 | */ 10 | 11 | Chinaware.prototype.Class = (function (prototype, ownProperty) { 12 | 13 | var ChinawareClass = function Klass(_superclass, definition) { 14 | 15 | function Class() { 16 | var self = this instanceof Class ? this : new Basic(); 17 | self.init.apply(self, arguments); 18 | return self; 19 | } 20 | 21 | function Basic() { 22 | } 23 | 24 | Class.Basic = Basic; 25 | 26 | var _super = Basic[prototype] = _superclass[prototype]; 27 | var proto = Basic[prototype] = Class[prototype] = new Basic(); 28 | 29 | proto.constructor = Class; 30 | 31 | Class.extend = function (def) { 32 | return new Klass(Class, def); 33 | }; 34 | 35 | var open = (Class.open = function (def) { 36 | if (Chinaware.isFunction(def)) { 37 | def = def.call(Class, proto, _super, Class, _superclass); 38 | } 39 | 40 | if (Chinaware.isObject(def)) { 41 | for (var key in def) { 42 | if (ownProperty.call(def, key)) { 43 | proto[key] = def[key]; 44 | } 45 | } 46 | } 47 | 48 | if (!('init' in proto)) { 49 | proto.init = _superclass; 50 | } 51 | 52 | return Class; 53 | }); 54 | 55 | return (open)(definition); 56 | }; 57 | 58 | return ChinawareClass; 59 | 60 | })('prototype', ({}).hasOwnProperty); 61 | -------------------------------------------------------------------------------- /specs/ajax-spec.js: -------------------------------------------------------------------------------- 1 | describe("Ajax", function () { 2 | beforeEach(function () { 3 | jasmine.Ajax.install(); 4 | }); 5 | afterEach(function () { 6 | jasmine.Ajax.uninstall(); 7 | }); 8 | 9 | it("specifying response when you need it", function (done) { 10 | var doneFn = jasmine.createSpy("success"); 11 | 12 | Chinaware.get('/some/cool/url', function (result) { 13 | expect(result).toEqual("awesome response"); 14 | done(); 15 | }); 16 | 17 | expect(jasmine.Ajax.requests.mostRecent().url).toBe('/some/cool/url'); 18 | expect(doneFn).not.toHaveBeenCalled(); 19 | 20 | jasmine.Ajax.requests.mostRecent().respondWith({ 21 | "status": 200, 22 | "contentType": 'text/plain', 23 | "responseText": 'awesome response' 24 | }); 25 | }); 26 | 27 | it("specifying html when you need it", function (done) { 28 | var doneFn = jasmine.createSpy("success"); 29 | 30 | Chinaware.load('/some', function (result) { 31 | expect(result).toEqual("

fsasfA

"); 32 | done(); 33 | }); 34 | 35 | expect(jasmine.Ajax.requests.mostRecent().url).toBe('/some'); 36 | expect(doneFn).not.toHaveBeenCalled(); 37 | 38 | jasmine.Ajax.requests.mostRecent().respondWith({ 39 | "status": 200, 40 | "contentType": 'text/plain', 41 | "responseText": '

fsasfA

' 42 | }); 43 | }); 44 | 45 | 46 | it("should be post to some where", function (done) { 47 | var doneFn = jasmine.createSpy("success"); 48 | 49 | Chinaware.post('/some/cool/url', [], function (result) { 50 | expect(result).toEqual("awesome response"); 51 | done(); 52 | }); 53 | 54 | expect(jasmine.Ajax.requests.mostRecent().url).toBe('/some/cool/url'); 55 | expect(doneFn).not.toHaveBeenCalled(); 56 | 57 | jasmine.Ajax.requests.mostRecent().respondWith({ 58 | "status": 200, 59 | "contentType": 'text/plain', 60 | "responseText": 'awesome response' 61 | }); 62 | }); 63 | }); 64 | 65 | -------------------------------------------------------------------------------- /src/Tiled.js: -------------------------------------------------------------------------------- 1 | 2 | function Tiled(content) { 3 | this.layers = []; 4 | var that = this; 5 | 6 | this.renderLayer = function (layer) { 7 | if (layer.type !== 'tilelayer' || !layer.opacity) { 8 | return; 9 | } 10 | var s = content.canvas.cloneNode(), 11 | size = that.data.tilewidth; 12 | s = s.getContext('2d'); 13 | if (that.layers.length < that.data.layers.length) { 14 | layer.data.forEach(function (tileIndex, i) { 15 | if (!tileIndex) { 16 | return; 17 | } 18 | var imgX, imgY, sizeX, sizeY, 19 | tile = that.data.tilesets[0]; 20 | tileIndex--; 21 | imgX = (tileIndex % (tile.imagewidth / size)) * size; 22 | imgY = ~~(tileIndex / (tile.imagewidth / size)) * size; 23 | sizeX = (i % layer.width) * size; 24 | sizeY = ~~(i / layer.width) * size; 25 | s.drawImage(that.tileset, imgX, imgY, size, size, sizeX, sizeY, size, size); 26 | }); 27 | that.layers.push(s.canvas.toDataURL()); 28 | content.drawImage(s.canvas, 0, 0); 29 | } 30 | else { 31 | that.layers.forEach(function (src) { 32 | var i = document.createElement('img'); 33 | i.src = src; 34 | content.drawImage(i, 0, 0); 35 | }); 36 | } 37 | }; 38 | 39 | this.renderLayers = function (layers) { 40 | function isObject(obj) { 41 | var type = typeof obj; 42 | return type === 'array'; 43 | } 44 | 45 | layers = isObject(layers) ? layers : this.data.layers; 46 | layers.forEach(this.renderLayer); 47 | }; 48 | 49 | this.loadTileset = function (json) { 50 | var that = this; 51 | this.data = json; 52 | this.tileset = document.createElement('img'); 53 | this.tileset.src = json.tilesets[0].image; 54 | this.tileset.onload = function () { 55 | that.renderLayers(that.tileset); 56 | }; 57 | }; 58 | 59 | this.load = function (name) { 60 | var that = this; 61 | Chinaware.get('./images/' + name + '.json', function (data) { 62 | that.loadTileset(JSON.parse(data)); 63 | }); 64 | }; 65 | } 66 | 67 | var tiled = { 68 | Tiled:Tiled 69 | }; 70 | 71 | Chinaware.prototype = Chinaware.extend(Chinaware.prototype, tiled); 72 | -------------------------------------------------------------------------------- /src/effect.js: -------------------------------------------------------------------------------- 1 | var FX = { 2 | easing: { 3 | linear: function(progress) { 4 | return progress; 5 | }, 6 | quadratic: function(progress) { 7 | return Math.pow(progress, 2); 8 | }, 9 | swing: function(progress) { 10 | return 0.5 - Math.cos(progress * Math.PI) / 2; 11 | }, 12 | circ: function(progress) { 13 | return 1 - Math.sin(Math.acos(progress)); 14 | }, 15 | back: function(progress, x) { 16 | return Math.pow(progress, 2) * ((x + 1) * progress - x); 17 | }, 18 | bounce: function(progress) { 19 | for (var a = 0, b = 1; 1; a += b, b /= 2) { 20 | if (progress >= (7 - 4 * a) / 11) { 21 | return -Math.pow((11 - 6 * a - 11 * progress) / 4, 2) + Math.pow(b, 2); 22 | } 23 | } 24 | }, 25 | elastic: function(progress, x) { 26 | return Math.pow(2, 10 * (progress - 1)) * Math.cos(20 * Math.PI * x / 3 * progress); 27 | } 28 | }, 29 | animate: function(options) { 30 | var start = new Date(); 31 | var id = setInterval(function() { 32 | var timePassed = new Date() - start; 33 | var progress = timePassed / options.duration; 34 | if (progress > 1) { 35 | progress = 1; 36 | } 37 | options.progress = progress; 38 | var delta = options.delta(progress); 39 | options.step(delta); 40 | if (progress == 1) { 41 | clearInterval(id); 42 | options.complete(); 43 | } 44 | }, options.delay || 10); 45 | }, 46 | fadeOut: function(element, options) { 47 | var to = 1; 48 | this.animate({ 49 | duration: options.duration, 50 | delta: function(progress) { 51 | progress = this.progress; 52 | return FX.easing.swing(progress); 53 | }, 54 | complete: options.complete, 55 | step: function(delta) { 56 | element.style.opacity = to - delta; 57 | } 58 | }); 59 | }, 60 | fadeIn: function(element, options) { 61 | var to = 0; 62 | this.animate({ 63 | duration: options.duration, 64 | delta: function(progress) { 65 | progress = this.progress; 66 | return FX.easing.swing(progress); 67 | }, 68 | complete: options.complete, 69 | step: function(delta) { 70 | element.style.opacity = to + delta; 71 | } 72 | }); 73 | } 74 | }; 75 | 76 | var fx = { 77 | FX: FX 78 | }; 79 | 80 | Chinaware.prototype = Chinaware.extend(Chinaware.prototype, fx); 81 | -------------------------------------------------------------------------------- /src/template.js: -------------------------------------------------------------------------------- 1 | /* 2 | * JavaScript Templates 2.4.1 3 | * https://github.com/blueimp/JavaScript-Templates 4 | * 5 | * Copyright 2011, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * Licensed under the MIT license: 9 | * http://www.opensource.org/licenses/MIT 10 | * 11 | * Inspired by John Resig's JavaScript Micro-Templating: 12 | * http://ejohn.org/blog/javascript-micro-templating/ 13 | */ 14 | 15 | /*jslint evil: true, regexp: true, unparam: true */ 16 | 17 | var Template = { 18 | regexp: /([\s'\\])(?!(?:[^{]|\{(?!%))*%\})|(?:\{%(=|#)([\s\S]+?)%\})|(\{%)|(%\})/g, 19 | encReg: /[<>&"'\x00]/g, 20 | encMap: { 21 | "<": "<", 22 | ">": ">", 23 | "&": "&", 24 | "\"": """, 25 | "'": "'" 26 | }, 27 | arg: "o", 28 | helper: ",print=function(s,e){_s+=e?(s==null?'':s):_e(s);}" + 29 | ",include=function(s,d){_s+=tmpl(s,d);}", 30 | 31 | tmpl: function (str, data){ 32 | var f = !/[^\w\-\.:]/.test(str) ? "" : this.compile(str); 33 | return f(data, this); 34 | }, 35 | 36 | compile: function (str) { 37 | var fn, variable; 38 | variable = this.arg + ',tmpl'; 39 | fn = "var _e=tmpl.encode" + this.helper + ",_s='" + str.replace(this.regexp, this.func) + "';"; 40 | fn = fn + "return _s;"; 41 | return new Function(variable, fn); 42 | }, 43 | 44 | encode: function (s) { 45 | /*jshint eqnull:true */ 46 | var encodeRegex = /[<>&"'\x00]/g, 47 | encodeMap = { 48 | "<": "<", 49 | ">": ">", 50 | "&": "&", 51 | "\"": """, 52 | "'": "'" 53 | }; 54 | return (s == null ? "" : "" + s).replace( 55 | encodeRegex, 56 | function (c) { 57 | return encodeMap[c] || ""; 58 | } 59 | ); 60 | }, 61 | 62 | func: function (s, p1, p2, p3, p4, p5) { 63 | var specialCharMAP = { 64 | "\n": "\\n", 65 | "\r": "\\r", 66 | "\t": "\\t", 67 | " ": " " 68 | }; 69 | 70 | if (p1) { // whitespace, quote and backspace in HTML context 71 | return specialCharMAP[p1] || "\\" + p1; 72 | } 73 | if (p2) { // interpolation: {%=prop%}, or unescaped: {%#prop%} 74 | if (p2 === "=") { 75 | return "'+_e(" + p3 + ")+'"; 76 | } 77 | return "'+(" + p3 + "==null?'':" + p3 + ")+'"; 78 | } 79 | if (p4) { // evaluation start tag: {% 80 | return "';"; 81 | } 82 | if (p5) { // evaluation end tag: %} 83 | return "_s+='"; 84 | } 85 | } 86 | }; 87 | 88 | var template = { 89 | Template: Template 90 | }; 91 | 92 | Chinaware.prototype = Chinaware.extend(Chinaware.prototype, template); 93 | -------------------------------------------------------------------------------- /.jscs.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | "maximumLineLength": 120, 4 | 5 | "validateJSDoc": { 6 | "checkParamNames": true, 7 | "checkRedundantParams": true, 8 | "requireParamTypes": true 9 | }, 10 | 11 | "validateLineBreaks": "LF", 12 | "validateQuoteMarks": true, 13 | 14 | "requireCurlyBraces": [ 15 | "if", 16 | "else", 17 | "for", 18 | "while", 19 | "do", 20 | "try", 21 | "catch" 22 | ], 23 | 24 | "requireSpaceAfterKeywords": [ 25 | "if", 26 | "else", 27 | "for", 28 | "while", 29 | "do", 30 | "switch", 31 | "return", 32 | "try", 33 | "catch" 34 | ], 35 | 36 | "requireOperatorBeforeLineBreak": [ 37 | "?", 38 | "+", 39 | "-", 40 | "/", 41 | "*", 42 | "=", 43 | "==", 44 | "===", 45 | "!=", 46 | "!==", 47 | ">", 48 | ">=", 49 | "<", 50 | "<=" 51 | ], 52 | 53 | "requireSpacesInConditionalExpression": { 54 | "afterTest": true, 55 | "beforeConsequent": true, 56 | "afterConsequent": true, 57 | "beforeAlternate": true 58 | }, 59 | 60 | "requireKeywordsOnNewLine": [ 61 | "catch", 62 | "while", 63 | "else" 64 | ], 65 | 66 | "requireSpacesInFunctionExpression": { 67 | "beforeOpeningRoundBrace": true, 68 | "beforeOpeningCurlyBrace": true 69 | }, 70 | 71 | "requireParenthesesAroundIIFE": true, 72 | "requireCommaBeforeLineBreak": true, 73 | "requireCamelCaseOrUpperCaseIdentifiers": true, 74 | "requireCapitalizedConstructors": true, 75 | "requireDotNotation": true, 76 | 77 | "requireSpaceBeforeBinaryOperators": [ 78 | "+", 79 | "-", 80 | "/", 81 | "*", 82 | "=", 83 | "==", 84 | "===", 85 | "!=", 86 | "!==", 87 | ">", 88 | ">=", 89 | "<", 90 | "<=" 91 | ], 92 | 93 | "disallowSpaceAfterPrefixUnaryOperators": [ 94 | "++", 95 | "--", 96 | "+", 97 | "-", 98 | "~", 99 | "!" 100 | ], 101 | 102 | "requireSpaceAfterBinaryOperators": [ 103 | "+", 104 | "-", 105 | "/", 106 | "*", 107 | "=", 108 | "==", 109 | "===", 110 | "!=", 111 | "!==", 112 | ">", 113 | ">=", 114 | "<", 115 | "<=" 116 | ], 117 | 118 | "disallowSpaceBeforePostfixUnaryOperators": [ 119 | "++", 120 | "--" 121 | ], 122 | 123 | "disallowImplicitTypeConversion": [ 124 | "numeric", 125 | "boolean", 126 | "binary", 127 | "string" 128 | ], 129 | 130 | 131 | "disallowKeywords": [ 132 | "with" 133 | ], 134 | 135 | "disallowMultipleLineStrings": true, 136 | "disallowMultipleLineBreaks": true, 137 | "disallowEmptyBlocks": true, 138 | "disallowSpacesInsideArrayBrackets": true, 139 | "disallowSpacesInsideParentheses": true, 140 | "disallowSpacesInsideObjectBrackets": true, 141 | "disallowQuotedKeysInObjects": true, 142 | "disallowSpaceAfterObjectKeys": true, 143 | "disallowTrailingWhitespace": true, 144 | "disallowMixedSpacesAndTabs": "smart", 145 | "safeContextKeyword": "self", 146 | 147 | "validateIndentation": 2, 148 | 149 | "requireSpaceAfterLineComment": true, 150 | 151 | "excludeFiles": ["node_modules/**", "app/bower_components/**" ] 152 | 153 | } 154 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | 3 | grunt.initConfig({ 4 | 5 | pkg: grunt.file.readJSON('package.json'), 6 | connect: { 7 | server: { 8 | options: { 9 | port: 8000, 10 | base: '.' 11 | } 12 | } 13 | }, 14 | 15 | jasmine : { 16 | src : 'dist/Chinaware.js', 17 | options : { 18 | host: "http://0.0.0.0:8000", 19 | vendor: ['node_modules/jasmine-ajax/lib/mock-ajax.js'], 20 | specs : 'specs/*-spec.js', 21 | template: require('grunt-template-jasmine-istanbul'), 22 | templateOptions: { 23 | coverage: 'coverage/coverage.json', 24 | report: { 25 | type: 'lcov', 26 | options: { 27 | dir: 'coverage' 28 | } 29 | }, 30 | thresholds: { 31 | lines: 90, 32 | statements: 90, 33 | branches: 75, 34 | functions: 85 35 | } 36 | } 37 | } 38 | }, 39 | concat: { 40 | options: { 41 | separator: "\n\n" 42 | }, 43 | dist: { 44 | src: [ 45 | 'src/_intro.js', 46 | 'src/main.js', 47 | 'src/helpers.js', 48 | 'src/class.js', 49 | 'src/ajax.js', 50 | 'src/event.js', 51 | 'src/template.js', 52 | 'src/effect.js', 53 | 'src/Tiled.js', 54 | 'src/router.js', 55 | 'src/_outro.js' 56 | ], 57 | dest: 'dist/<%= pkg.name.replace(".js", "") %>.js' 58 | } 59 | }, 60 | 61 | uglify: { 62 | options: { 63 | banner: '/*! <%= pkg.name.replace(".js", "") %> <%= grunt.template.today("dd-mm-yyyy") %> */\n' 64 | }, 65 | dist: { 66 | files: { 67 | 'dist/<%= pkg.name.replace(".js", "") %>.min.js': ['<%= concat.dist.dest %>'] 68 | } 69 | } 70 | }, 71 | 72 | jshint: { 73 | files: ['dist/Chinaware.js'], 74 | options: { 75 | globals: { 76 | console: true, 77 | module: true, 78 | document: true 79 | }, 80 | jshintrc: '.jshintrc' 81 | } 82 | }, 83 | 84 | watch: { 85 | files: ['<%= jshint.files %>'], 86 | tasks: ['concat', 'jshint', 'qunit'] 87 | }, 88 | 89 | replace: { 90 | dist: { 91 | options: { 92 | patterns: [ 93 | { 94 | match: 'version', 95 | replacement: '<%= pkg.version %>' 96 | } 97 | ] 98 | }, 99 | files: [ 100 | { 101 | expand: true, 102 | flatten: true, 103 | src: ['dist/Chinaware.js', 'dist/Chinaware.min.js'], 104 | dest: 'dist/' 105 | } 106 | ] 107 | } 108 | } 109 | 110 | }); 111 | 112 | grunt.loadNpmTasks('grunt-contrib-uglify'); 113 | grunt.loadNpmTasks('grunt-contrib-jshint'); 114 | grunt.loadNpmTasks('grunt-contrib-jasmine'); 115 | grunt.loadNpmTasks('grunt-contrib-watch'); 116 | grunt.loadNpmTasks('grunt-contrib-concat'); 117 | grunt.loadNpmTasks('grunt-contrib-connect'); 118 | grunt.loadNpmTasks('grunt-replace'); 119 | 120 | grunt.registerTask('test', ['replace', 'jshint', 'connect', 'jasmine']); 121 | grunt.registerTask('default', ['concat','replace', 'jshint', 'connect', 'jasmine', 'uglify']); 122 | 123 | }; 124 | -------------------------------------------------------------------------------- /dist/Chinaware.min.js: -------------------------------------------------------------------------------- 1 | /*! Chinaware 13-03-2015 */ 2 | !function(a){"use strict";var b=function(){return!0};b.VERSION="0.0.0",a.Chinaware=b,b.isObject=function(a){var b=typeof a;return"function"===b||"object"===b&&!!a},b.isFunction=function(a){return"function"==typeof a||!1},b.defaults=function(a){if(!b.isObject(a))return a;for(var c=1,d=arguments.length;d>c;c++){var e=arguments[c];for(var f in e)void 0===a[f]&&(a[f]=e[f])}return a},b.extend=function(a){if(!b.isObject(a))return a;for(var c,d,e=1,f=arguments.length;f>e;e++){c=arguments[e];for(d in c)hasOwnProperty.call(c,d)&&(a[d]=c[d])}return a},b.prototype.Class=function(a,c){var d=function e(d,f){function g(){var a=this instanceof g?this:new h;return a.init.apply(a,arguments),a}function h(){}g.Basic=h;var i=h[a]=d[a],j=h[a]=g[a]=new h;j.constructor=g,g.extend=function(a){return new e(g,a)};var k=g.open=function(a){if(b.isFunction(a)&&(a=a.call(g,j,i,g,d)),b.isObject(a))for(var e in a)c.call(a,e)&&(j[e]=a[e]);return"init"in j||(j.init=d),g};return k(f)};return d}("prototype",{}.hasOwnProperty),b.get=function(a,c){b.send(a,"GET",c)},b.load=function(a,c){b.send(a,"GET",c)},b.post=function(a,c,d){b.send(a,"POST",d,c)},b.send=function(a,b,c,d){d=d||null;var e=new XMLHttpRequest;c instanceof Function&&(e.onreadystatechange=function(){4!==e.readyState||200!==e.status&&0!==e.status||c(e.responseText)}),e.open(b,a,!0),d instanceof Object&&(d=JSON.stringify(d),e.setRequestHeader("Content-Type","application/json")),e.setRequestHeader("X-Requested-With","XMLHttpRequest"),e.send(d)};var c={on:function(a,b){this._events=this._events||{},this._events[a]=this._events[a]||[],this._events[a].push(b)},off:function(a,b){this._events=this._events||{},a in this._events!=!1&&this._events[a].splice(this._events[a].indexOf(b),1)},trigger:function(a){if(this._events=this._events||{},a in this._events!=!1)for(var b=0;b&"'\x00]/g,encMap:{"<":"<",">":">","&":"&",'"':""","'":"'"},arg:"o",helper:",print=function(s,e){_s+=e?(s==null?'':s):_e(s);},include=function(s,d){_s+=tmpl(s,d);}",tmpl:function(a,b){var c=/[^\w\-\.:]/.test(a)?this.compile(a):"";return c(b,this)},compile:function(a){var b,c;return c=this.arg+",tmpl",b="var _e=tmpl.encode"+this.helper+",_s='"+a.replace(this.regexp,this.func)+"';",b+="return _s;",new Function(c,b)},encode:function(a){var b=/[<>&"'\x00]/g,c={"<":"<",">":">","&":"&",'"':""","'":"'"};return(null==a?"":""+a).replace(b,function(a){return c[a]||""})},func:function(a,b,c,d,e,f){var g={"\n":"\\n","\r":"\\r"," ":"\\t"," ":" "};return b?g[b]||"\\"+b:c?"="===c?"'+_e("+d+")+'":"'+("+d+"==null?'':"+d+")+'":e?"';":f?"_s+='":void 0}},f={Template:e};b.prototype=b.extend(b.prototype,f);var g={easing:{linear:function(a){return a},quadratic:function(a){return Math.pow(a,2)},swing:function(a){return.5-Math.cos(a*Math.PI)/2},circ:function(a){return 1-Math.sin(Math.acos(a))},back:function(a,b){return Math.pow(a,2)*((b+1)*a-b)},bounce:function(a){for(var b=0,c=1;1;b+=c,c/=2)if(a>=(7-4*b)/11)return-Math.pow((11-6*b-11*a)/4,2)+Math.pow(c,2)},elastic:function(a,b){return Math.pow(2,10*(a-1))*Math.cos(20*Math.PI*b/3*a)}},animate:function(a){var b=new Date,c=setInterval(function(){var d=new Date-b,e=d/a.duration;e>1&&(e=1),a.progress=e;var f=a.delta(e);a.step(f),1==e&&(clearInterval(c),a.complete())},a.delay||10)},fadeOut:function(a,b){var c=1;this.animate({duration:b.duration,delta:function(a){return a=this.progress,g.easing.swing(a)},complete:b.complete,step:function(b){a.style.opacity=c-b}})},fadeIn:function(a,b){var c=0;this.animate({duration:b.duration,delta:function(a){return a=this.progress,g.easing.swing(a)},complete:b.complete,step:function(b){a.style.opacity=c+b}})}},h={FX:g};b.prototype=b.extend(b.prototype,h)}(this); -------------------------------------------------------------------------------- /specs/effect-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe("Effect", function () { 4 | var L, content, elementID = "content"; 5 | beforeEach(function () { 6 | L = new Chinaware(); 7 | jasmine.clock().install(); 8 | }); 9 | 10 | it('should be able fadein elements', function () { 11 | content = document.createElement('div'); 12 | content.setAttribute("id", elementID); 13 | document.body.appendChild(content); 14 | document.getElementById(elementID).style.height = '4px'; 15 | document.getElementById(elementID).style.opacity = 1; 16 | 17 | L.FX.fadeIn(document.getElementById(elementID), { 18 | duration: 2000, complete: function () { 19 | } 20 | }); 21 | 22 | jasmine.clock().tick(2000); 23 | var opacity = document.getElementById(elementID).style.opacity; 24 | opacity = Math.round(opacity); 25 | expect(opacity).toEqual(0); 26 | }); 27 | 28 | it('should be able fadeout elements', function () { 29 | content = document.createElement('div'); 30 | content.setAttribute("id", elementID); 31 | document.body.appendChild(content); 32 | document.getElementById(elementID).style.height = '4px'; 33 | document.getElementById(elementID).style.opacity = 0; 34 | 35 | L.FX.fadeOut(document.getElementById(elementID), { 36 | duration: 2000, complete: function () { 37 | } 38 | }); 39 | 40 | jasmine.clock().tick(2000); 41 | var opacity = document.getElementById(elementID).style.opacity; 42 | opacity = Math.round(opacity); 43 | expect(opacity).toEqual(1); 44 | }); 45 | 46 | describe(" Animation", function(){ 47 | it('should return progress equal 1', function () { 48 | var to = 2, progress, that = this; 49 | var options = { 50 | duration: 0.2, complete: function () { 51 | } 52 | }; 53 | var element = document.createElement('div'); 54 | element.setAttribute("id", elementID); 55 | document.body.appendChild(content); 56 | document.getElementById(elementID).style.height = '4px'; 57 | document.getElementById(elementID).style.opacity = 0; 58 | 59 | L.FX.animate({ 60 | duration: options.duration, 61 | delta: function (progress) { 62 | progress = this.progress; 63 | var prog = L.FX.easing.linear(progress); 64 | return prog; 65 | }, 66 | complete: options.complete, 67 | step: function (delta) { 68 | element.style.opacity = to - delta; 69 | } 70 | }); 71 | }); 72 | }); 73 | 74 | describe("Effect Easing", function () { 75 | it('linear: should return it self', function () { 76 | var origin = 1, now; 77 | now = L.FX.easing.linear(origin); 78 | expect(now).toEqual(1); 79 | }); 80 | 81 | it('quadratic: should return it self', function () { 82 | var origin = 2, now; 83 | now = L.FX.easing.quadratic(origin); 84 | expect(now).toEqual(4); 85 | }); 86 | 87 | it('circ: should return it self', function () { 88 | var origin = 1.0, now; 89 | now = L.FX.easing.circ(origin); 90 | expect(now).toEqual(1); 91 | }); 92 | 93 | it('back: should return it self', function () { 94 | var origin = 2, now; 95 | now = L.FX.easing.back(origin, origin); 96 | expect(now).toEqual(16); 97 | }); 98 | 99 | it('bounce: should return it self', function () { 100 | var origin = 2, now; 101 | now = L.FX.easing.bounce(origin); 102 | expect(now).toEqual(-6.5625); 103 | }); 104 | 105 | it('elastic: should return it self', function () { 106 | var origin = 2, now; 107 | now = L.FX.easing.elastic(origin, origin); 108 | now = Math.round(now); 109 | expect(now).toEqual(-512); 110 | }); 111 | }); 112 | }); 113 | -------------------------------------------------------------------------------- /demo/images/mountain.json: -------------------------------------------------------------------------------- 1 | { "height":15, 2 | "layers":[ 3 | { 4 | "data":[79, 80, 95, 96, 79, 80, 95, 96, 79, 80, 95, 96, 79, 28, 60, 61, 62, 30, 95, 96, 95, 96, 79, 80, 95, 96, 79, 80, 95, 96, 79, 80, 95, 28, 76, 77, 78, 30, 79, 80, 79, 80, 95, 96, 79, 80, 95, 96, 79, 80, 95, 96, 79, 28, 76, 15, 78, 30, 95, 96, 95, 96, 79, 80, 95, 96, 79, 80, 95, 96, 79, 80, 95, 28, 76, 16, 78, 30, 79, 80, 79, 80, 95, 96, 79, 80, 95, 96, 79, 80, 95, 96, 79, 28, 76, 31, 78, 30, 95, 96, 95, 96, 79, 80, 95, 156, 157, 158, 95, 96, 79, 80, 95, 28, 76, 32, 78, 112, 13, 13, 79, 80, 95, 96, 79, 172, 173, 144, 158, 80, 95, 96, 79, 28, 76, 47, 48, 61, 61, 62, 95, 96, 79, 80, 156, 143, 138, 160, 190, 96, 12, 13, 13, 111, 76, 16, 15, 16, 31, 78, 13, 13, 14, 96, 172, 138, 173, 174, 79, 80, 44, 45, 45, 127, 92, 93, 93, 93, 93, 94, 109, 110, 30, 80, 188, 189, 189, 190, 95, 96, 79, 80, 95, 44, 45, 45, 45, 45, 45, 45, 141, 142, 30, 96, 79, 80, 95, 96, 79, 80, 95, 96, 79, 80, 95, 96, 79, 80, 95, 96, 45, 45, 46, 80, 95, 96, 79, 80, 95, 96, 79, 80, 95, 96, 79, 80, 95, 96, 79, 80, 79, 80, 95, 96, 79, 80, 95, 96, 79, 80, 95, 96, 79, 80, 95, 96, 79, 80, 95, 96, 95, 96, 79, 80, 95, 96, 79, 80, 95, 96, 79, 80, 95, 96, 79, 80, 95, 96, 79, 80, 79, 80, 95, 96, 79, 80, 95, 96, 79, 80, 95, 96, 79, 80, 95, 96, 79, 80, 95, 96], 5 | "height":15, 6 | "name":"Ground", 7 | "opacity":1, 8 | "type":"tilelayer", 9 | "visible":true, 10 | "width":20, 11 | "x":0, 12 | "y":0 13 | }, 14 | { 15 | "data":[92, 93, 93, 94, 56, 52, 51, 53, 54, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 52, 52, 73, 66, 175, 176, 69, 70, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 67, 68, 73, 89, 82, 191, 192, 85, 86, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 83, 84, 85, 90, 98, 223, 224, 101, 102, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 104, 99, 101, 102, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 4, 9, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 18, 19, 20, 21, 26, 23, 22, 0, 0], 16 | "height":15, 17 | "name":"Mountain", 18 | "opacity":1, 19 | "type":"tilelayer", 20 | "visible":true, 21 | "width":20, 22 | "x":0, 23 | "y":0 24 | }, 25 | { 26 | "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 232, 233, 0, 0, 0, 0, 0, 0, 0, 165, 166, 0, 0, 0, 0, 0, 0, 0, 0, 0, 248, 249, 0, 0, 0, 0, 0, 0, 0, 181, 135, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 181, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 205, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 253, 0, 0, 0, 0, 0, 0, 202, 203, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 218, 219, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 234, 235, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 250, 251, 0, 0, 0, 0, 0, 0, 197, 198, 199, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 206, 0, 0, 0, 0, 213, 214, 215, 0, 149, 150, 151, 0, 0, 149, 151, 0, 0, 0, 205, 222, 0, 0, 0, 0, 229, 230, 231, 0, 181, 182, 107, 151, 149, 119, 120, 151, 0, 0, 0, 0, 0, 0, 0, 0, 245, 246, 247, 0, 0, 0, 181, 107, 119, 136, 135, 120, 151, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 27 | "height":15, 28 | "name":"Forest", 29 | "opacity":1, 30 | "type":"tilelayer", 31 | "visible":true, 32 | "width":20, 33 | "x":0, 34 | "y":0 35 | }], 36 | "orientation":"orthogonal", 37 | "properties": 38 | { 39 | 40 | }, 41 | "tileheight":32, 42 | "tilesets":[ 43 | { 44 | "firstgid":1, 45 | "image":".\/images\/mountain_landscape_23.png", 46 | "imageheight":512, 47 | "imagewidth":512, 48 | "margin":0, 49 | "name":"Mountain landscape", 50 | "properties": 51 | { 52 | 53 | }, 54 | "spacing":0, 55 | "tileheight":32, 56 | "tilewidth":32 57 | }], 58 | "tilewidth":32, 59 | "version":1, 60 | "width":20 61 | } 62 | -------------------------------------------------------------------------------- /demo/images/mountain.tmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | TwAAAFAAAABfAAAAYAAAAE8AAABQAAAAXwAAAGAAAABPAAAAUAAAAF8AAABgAAAATwAAABwAAAA8AAAAPQAAAD4AAAAeAAAAXwAAAGAAAABfAAAAYAAAAE8AAABQAAAAXwAAAGAAAABPAAAAUAAAAF8AAABgAAAATwAAAFAAAABfAAAAHAAAAEwAAABNAAAATgAAAB4AAABPAAAAUAAAAE8AAABQAAAAXwAAAGAAAABPAAAAUAAAAF8AAABgAAAATwAAAFAAAABfAAAAYAAAAE8AAAAcAAAATAAAAA8AAABOAAAAHgAAAF8AAABgAAAAXwAAAGAAAABPAAAAUAAAAF8AAABgAAAATwAAAFAAAABfAAAAYAAAAE8AAABQAAAAXwAAABwAAABMAAAAEAAAAE4AAAAeAAAATwAAAFAAAABPAAAAUAAAAF8AAABgAAAATwAAAFAAAABfAAAAYAAAAE8AAABQAAAAXwAAAGAAAABPAAAAHAAAAEwAAAAfAAAATgAAAB4AAABfAAAAYAAAAF8AAABgAAAATwAAAFAAAABfAAAAnAAAAJ0AAACeAAAAXwAAAGAAAABPAAAAUAAAAF8AAAAcAAAATAAAACAAAABOAAAAcAAAAA0AAAANAAAATwAAAFAAAABfAAAAYAAAAE8AAACsAAAArQAAAJAAAACeAAAAUAAAAF8AAABgAAAATwAAABwAAABMAAAALwAAADAAAAA9AAAAPQAAAD4AAABfAAAAYAAAAE8AAABQAAAAnAAAAI8AAACKAAAAoAAAAL4AAABgAAAADAAAAA0AAAANAAAAbwAAAEwAAAAQAAAADwAAABAAAAAfAAAATgAAAA0AAAANAAAADgAAAGAAAACsAAAAigAAAK0AAACuAAAATwAAAFAAAAAsAAAALQAAAC0AAAB/AAAAXAAAAF0AAABdAAAAXQAAAF0AAABeAAAAbQAAAG4AAAAeAAAAUAAAALwAAAC9AAAAvQAAAL4AAABfAAAAYAAAAE8AAABQAAAAXwAAACwAAAAtAAAALQAAAC0AAAAtAAAALQAAAC0AAACNAAAAjgAAAB4AAABgAAAATwAAAFAAAABfAAAAYAAAAE8AAABQAAAAXwAAAGAAAABPAAAAUAAAAF8AAABgAAAATwAAAFAAAABfAAAAYAAAAC0AAAAtAAAALgAAAFAAAABfAAAAYAAAAE8AAABQAAAAXwAAAGAAAABPAAAAUAAAAF8AAABgAAAATwAAAFAAAABfAAAAYAAAAE8AAABQAAAATwAAAFAAAABfAAAAYAAAAE8AAABQAAAAXwAAAGAAAABPAAAAUAAAAF8AAABgAAAATwAAAFAAAABfAAAAYAAAAE8AAABQAAAAXwAAAGAAAABfAAAAYAAAAE8AAABQAAAAXwAAAGAAAABPAAAAUAAAAF8AAABgAAAATwAAAFAAAABfAAAAYAAAAE8AAABQAAAAXwAAAGAAAABPAAAAUAAAAE8AAABQAAAAXwAAAGAAAABPAAAAUAAAAF8AAABgAAAATwAAAFAAAABfAAAAYAAAAE8AAABQAAAAXwAAAGAAAABPAAAAUAAAAF8AAABgAAAA 9 | 10 | 11 | 12 | 13 | XAAAAF0AAABdAAAAXgAAADgAAAA0AAAAMwAAADUAAAA2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzAAAANAAAADQAAABJAAAAQgAAAK8AAACwAAAARQAAAEYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEMAAABEAAAASQAAAFkAAABSAAAAvwAAAMAAAABVAAAAVgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUwAAAFQAAABVAAAAWgAAAGIAAADfAAAA4AAAAGUAAABmAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABoAAAAYwAAAGUAAABmAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAADAAAABAAAAAkAAAAKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARAAAAEgAAABMAAAAUAAAAFQAAABoAAAAXAAAAFgAAAAAAAAAAAAAA 14 | 15 | 16 | 17 | 18 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6AAAAOkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAApQAAAKYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4AAAA+QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC1AAAAhwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMoAAADLAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA2gAAANsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADqAAAA6wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPoAAAD7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAxQAAAMYAAADHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzgAAAAAAAAAAAAAAAAAAAAAAAADVAAAA1gAAANcAAAAAAAAAlQAAAJYAAACXAAAAAAAAAAAAAACVAAAAlwAAAAAAAAAAAAAAAAAAAM0AAADeAAAAAAAAAAAAAAAAAAAAAAAAAOUAAADmAAAA5wAAAAAAAAC1AAAAtgAAAGsAAACXAAAAlQAAAHcAAAB4AAAAlwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA9QAAAPYAAAD3AAAAAAAAAAAAAAAAAAAAtQAAAGsAAAB3AAAAiAAAAIcAAAB4AAAAlwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /test/lib/qunit.css: -------------------------------------------------------------------------------- 1 | /** 2 | * QUnit v1.12.0 - A JavaScript Unit Testing Framework 3 | * 4 | * http://qunitjs.com 5 | * 6 | * Copyright 2012 jQuery Foundation and other contributors 7 | * Released under the MIT license. 8 | * http://jquery.org/license 9 | */ 10 | 11 | /** Font Family and Sizes */ 12 | 13 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult { 14 | font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif; 15 | } 16 | 17 | #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } 18 | #qunit-tests { font-size: smaller; } 19 | 20 | 21 | /** Resets */ 22 | 23 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter { 24 | margin: 0; 25 | padding: 0; 26 | } 27 | 28 | 29 | /** Header */ 30 | 31 | #qunit-header { 32 | padding: 0.5em 0 0.5em 1em; 33 | 34 | color: #8699a4; 35 | background-color: #0d3349; 36 | 37 | font-size: 1.5em; 38 | line-height: 1em; 39 | font-weight: normal; 40 | 41 | border-radius: 5px 5px 0 0; 42 | -moz-border-radius: 5px 5px 0 0; 43 | -webkit-border-top-right-radius: 5px; 44 | -webkit-border-top-left-radius: 5px; 45 | } 46 | 47 | #qunit-header a { 48 | text-decoration: none; 49 | color: #c2ccd1; 50 | } 51 | 52 | #qunit-header a:hover, 53 | #qunit-header a:focus { 54 | color: #fff; 55 | } 56 | 57 | #qunit-testrunner-toolbar label { 58 | display: inline-block; 59 | padding: 0 .5em 0 .1em; 60 | } 61 | 62 | #qunit-banner { 63 | height: 5px; 64 | } 65 | 66 | #qunit-testrunner-toolbar { 67 | padding: 0.5em 0 0.5em 2em; 68 | color: #5E740B; 69 | background-color: #eee; 70 | overflow: hidden; 71 | } 72 | 73 | #qunit-userAgent { 74 | padding: 0.5em 0 0.5em 2.5em; 75 | background-color: #2b81af; 76 | color: #fff; 77 | text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; 78 | } 79 | 80 | #qunit-modulefilter-container { 81 | float: right; 82 | } 83 | 84 | /** Tests: Pass/Fail */ 85 | 86 | #qunit-tests { 87 | list-style-position: inside; 88 | } 89 | 90 | #qunit-tests li { 91 | padding: 0.4em 0.5em 0.4em 2.5em; 92 | border-bottom: 1px solid #fff; 93 | list-style-position: inside; 94 | } 95 | 96 | #qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running { 97 | display: none; 98 | } 99 | 100 | #qunit-tests li strong { 101 | cursor: pointer; 102 | } 103 | 104 | #qunit-tests li a { 105 | padding: 0.5em; 106 | color: #c2ccd1; 107 | text-decoration: none; 108 | } 109 | #qunit-tests li a:hover, 110 | #qunit-tests li a:focus { 111 | color: #000; 112 | } 113 | 114 | #qunit-tests li .runtime { 115 | float: right; 116 | font-size: smaller; 117 | } 118 | 119 | .qunit-assert-list { 120 | margin-top: 0.5em; 121 | padding: 0.5em; 122 | 123 | background-color: #fff; 124 | 125 | border-radius: 5px; 126 | -moz-border-radius: 5px; 127 | -webkit-border-radius: 5px; 128 | } 129 | 130 | .qunit-collapsed { 131 | display: none; 132 | } 133 | 134 | #qunit-tests table { 135 | border-collapse: collapse; 136 | margin-top: .2em; 137 | } 138 | 139 | #qunit-tests th { 140 | text-align: right; 141 | vertical-align: top; 142 | padding: 0 .5em 0 0; 143 | } 144 | 145 | #qunit-tests td { 146 | vertical-align: top; 147 | } 148 | 149 | #qunit-tests pre { 150 | margin: 0; 151 | white-space: pre-wrap; 152 | word-wrap: break-word; 153 | } 154 | 155 | #qunit-tests del { 156 | background-color: #e0f2be; 157 | color: #374e0c; 158 | text-decoration: none; 159 | } 160 | 161 | #qunit-tests ins { 162 | background-color: #ffcaca; 163 | color: #500; 164 | text-decoration: none; 165 | } 166 | 167 | /*** Test Counts */ 168 | 169 | #qunit-tests b.counts { color: black; } 170 | #qunit-tests b.passed { color: #5E740B; } 171 | #qunit-tests b.failed { color: #710909; } 172 | 173 | #qunit-tests li li { 174 | padding: 5px; 175 | background-color: #fff; 176 | border-bottom: none; 177 | list-style-position: inside; 178 | } 179 | 180 | /*** Passing Styles */ 181 | 182 | #qunit-tests li li.pass { 183 | color: #3c510c; 184 | background-color: #fff; 185 | border-left: 10px solid #C6E746; 186 | } 187 | 188 | #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } 189 | #qunit-tests .pass .test-name { color: #366097; } 190 | 191 | #qunit-tests .pass .test-actual, 192 | #qunit-tests .pass .test-expected { color: #999999; } 193 | 194 | #qunit-banner.qunit-pass { background-color: #C6E746; } 195 | 196 | /*** Failing Styles */ 197 | 198 | #qunit-tests li li.fail { 199 | color: #710909; 200 | background-color: #fff; 201 | border-left: 10px solid #EE5757; 202 | white-space: pre; 203 | } 204 | 205 | #qunit-tests > li:last-child { 206 | border-radius: 0 0 5px 5px; 207 | -moz-border-radius: 0 0 5px 5px; 208 | -webkit-border-bottom-right-radius: 5px; 209 | -webkit-border-bottom-left-radius: 5px; 210 | } 211 | 212 | #qunit-tests .fail { color: #000000; background-color: #EE5757; } 213 | #qunit-tests .fail .test-name, 214 | #qunit-tests .fail .module-name { color: #000000; } 215 | 216 | #qunit-tests .fail .test-actual { color: #EE5757; } 217 | #qunit-tests .fail .test-expected { color: green; } 218 | 219 | #qunit-banner.qunit-fail { background-color: #EE5757; } 220 | 221 | 222 | /** Result */ 223 | 224 | #qunit-testresult { 225 | padding: 0.5em 0.5em 0.5em 2.5em; 226 | 227 | color: #2b81af; 228 | background-color: #D2E0E6; 229 | 230 | border-bottom: 1px solid white; 231 | } 232 | #qunit-testresult .module-name { 233 | font-weight: bold; 234 | } 235 | 236 | /** Fixture */ 237 | 238 | #qunit-fixture { 239 | position: absolute; 240 | top: -10000px; 241 | left: -10000px; 242 | width: 1000px; 243 | height: 1000px; 244 | } 245 | -------------------------------------------------------------------------------- /demo/assets/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v2.1.3 | MIT License | git.io/normalize */ 2 | 3 | /* ========================================================================== 4 | HTML5 display definitions 5 | ========================================================================== */ 6 | 7 | /** 8 | * Correct `block` display not defined in IE 8/9. 9 | */ 10 | 11 | article, 12 | aside, 13 | details, 14 | figcaption, 15 | figure, 16 | footer, 17 | header, 18 | hgroup, 19 | main, 20 | nav, 21 | section, 22 | summary { 23 | display: block; 24 | } 25 | 26 | /** 27 | * Correct `inline-block` display not defined in IE 8/9. 28 | */ 29 | 30 | audio, 31 | canvas, 32 | video { 33 | display: inline-block; 34 | } 35 | 36 | /** 37 | * Prevent modern browsers from displaying `audio` without controls. 38 | * Remove excess height in iOS 5 devices. 39 | */ 40 | 41 | audio:not([controls]) { 42 | display: none; 43 | height: 0; 44 | } 45 | 46 | /** 47 | * Address `[hidden]` styling not present in IE 8/9. 48 | * Hide the `template` element in IE, Safari, and Firefox < 22. 49 | */ 50 | 51 | [hidden], 52 | template { 53 | display: none; 54 | } 55 | 56 | /* ========================================================================== 57 | Base 58 | ========================================================================== */ 59 | 60 | /** 61 | * 1. Set default font family to sans-serif. 62 | * 2. Prevent iOS text size adjust after orientation change, without disabling 63 | * user zoom. 64 | */ 65 | 66 | html { 67 | font-family: sans-serif; /* 1 */ 68 | -ms-text-size-adjust: 100%; /* 2 */ 69 | -webkit-text-size-adjust: 100%; /* 2 */ 70 | } 71 | 72 | /** 73 | * Remove default margin. 74 | */ 75 | 76 | body { 77 | margin: 0; 78 | } 79 | 80 | /* ========================================================================== 81 | Links 82 | ========================================================================== */ 83 | 84 | /** 85 | * Remove the gray background color from active links in IE 10. 86 | */ 87 | 88 | a { 89 | background: transparent; 90 | } 91 | 92 | /** 93 | * Address `outline` inconsistency between Chrome and other browsers. 94 | */ 95 | 96 | a:focus { 97 | outline: thin dotted; 98 | } 99 | 100 | /** 101 | * Improve readability when focused and also mouse hovered in all browsers. 102 | */ 103 | 104 | a:active, 105 | a:hover { 106 | outline: 0; 107 | } 108 | 109 | /* ========================================================================== 110 | Typography 111 | ========================================================================== */ 112 | 113 | /** 114 | * Address variable `h1` font-size and margin within `section` and `article` 115 | * contexts in Firefox 4+, Safari 5, and Chrome. 116 | */ 117 | 118 | h1 { 119 | font-size: 2em; 120 | margin: 0.67em 0; 121 | } 122 | 123 | /** 124 | * Address styling not present in IE 8/9, Safari 5, and Chrome. 125 | */ 126 | 127 | abbr[title] { 128 | border-bottom: 1px dotted; 129 | } 130 | 131 | /** 132 | * Address style set to `bolder` in Firefox 4+, Safari 5, and Chrome. 133 | */ 134 | 135 | b, 136 | strong { 137 | font-weight: bold; 138 | } 139 | 140 | /** 141 | * Address styling not present in Safari 5 and Chrome. 142 | */ 143 | 144 | dfn { 145 | font-style: italic; 146 | } 147 | 148 | /** 149 | * Address differences between Firefox and other browsers. 150 | */ 151 | 152 | hr { 153 | -moz-box-sizing: content-box; 154 | box-sizing: content-box; 155 | height: 0; 156 | } 157 | 158 | /** 159 | * Address styling not present in IE 8/9. 160 | */ 161 | 162 | mark { 163 | background: #ff0; 164 | color: #000; 165 | } 166 | 167 | /** 168 | * Correct font family set oddly in Safari 5 and Chrome. 169 | */ 170 | 171 | code, 172 | kbd, 173 | pre, 174 | samp { 175 | font-family: monospace, serif; 176 | font-size: 1em; 177 | } 178 | 179 | /** 180 | * Improve readability of pre-formatted text in all browsers. 181 | */ 182 | 183 | pre { 184 | white-space: pre-wrap; 185 | } 186 | 187 | /** 188 | * Set consistent quote types. 189 | */ 190 | 191 | q { 192 | quotes: "\201C" "\201D" "\2018" "\2019"; 193 | } 194 | 195 | /** 196 | * Address inconsistent and variable font size in all browsers. 197 | */ 198 | 199 | small { 200 | font-size: 80%; 201 | } 202 | 203 | /** 204 | * Prevent `sub` and `sup` affecting `line-height` in all browsers. 205 | */ 206 | 207 | sub, 208 | sup { 209 | font-size: 75%; 210 | line-height: 0; 211 | position: relative; 212 | vertical-align: baseline; 213 | } 214 | 215 | sup { 216 | top: -0.5em; 217 | } 218 | 219 | sub { 220 | bottom: -0.25em; 221 | } 222 | 223 | /* ========================================================================== 224 | Embedded content 225 | ========================================================================== */ 226 | 227 | /** 228 | * Remove border when inside `a` element in IE 8/9. 229 | */ 230 | 231 | img { 232 | border: 0; 233 | } 234 | 235 | /** 236 | * Correct overflow displayed oddly in IE 9. 237 | */ 238 | 239 | svg:not(:root) { 240 | overflow: hidden; 241 | } 242 | 243 | /* ========================================================================== 244 | Figures 245 | ========================================================================== */ 246 | 247 | /** 248 | * Address margin not present in IE 8/9 and Safari 5. 249 | */ 250 | 251 | figure { 252 | margin: 0; 253 | } 254 | 255 | /* ========================================================================== 256 | Forms 257 | ========================================================================== */ 258 | 259 | /** 260 | * Define consistent border, margin, and padding. 261 | */ 262 | 263 | fieldset { 264 | border: 1px solid #c0c0c0; 265 | margin: 0 2px; 266 | padding: 0.35em 0.625em 0.75em; 267 | } 268 | 269 | /** 270 | * 1. Correct `color` not being inherited in IE 8/9. 271 | * 2. Remove padding so people aren't caught out if they zero out fieldsets. 272 | */ 273 | 274 | legend { 275 | border: 0; /* 1 */ 276 | padding: 0; /* 2 */ 277 | } 278 | 279 | /** 280 | * 1. Correct font family not being inherited in all browsers. 281 | * 2. Correct font size not being inherited in all browsers. 282 | * 3. Address margins set differently in Firefox 4+, Safari 5, and Chrome. 283 | */ 284 | 285 | button, 286 | input, 287 | select, 288 | textarea { 289 | font-family: inherit; /* 1 */ 290 | font-size: 100%; /* 2 */ 291 | margin: 0; /* 3 */ 292 | } 293 | 294 | /** 295 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in 296 | * the UA stylesheet. 297 | */ 298 | 299 | button, 300 | input { 301 | line-height: normal; 302 | } 303 | 304 | /** 305 | * Address inconsistent `text-transform` inheritance for `button` and `select`. 306 | * All other form control elements do not inherit `text-transform` values. 307 | * Correct `button` style inheritance in Chrome, Safari 5+, and IE 8+. 308 | * Correct `select` style inheritance in Firefox 4+ and Opera. 309 | */ 310 | 311 | button, 312 | select { 313 | text-transform: none; 314 | } 315 | 316 | /** 317 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 318 | * and `video` controls. 319 | * 2. Correct inability to style clickable `input` types in iOS. 320 | * 3. Improve usability and consistency of cursor style between image-type 321 | * `input` and others. 322 | */ 323 | 324 | button, 325 | html input[type="button"], /* 1 */ 326 | input[type="reset"], 327 | input[type="submit"] { 328 | -webkit-appearance: button; /* 2 */ 329 | cursor: pointer; /* 3 */ 330 | } 331 | 332 | /** 333 | * Re-set default cursor for disabled elements. 334 | */ 335 | 336 | button[disabled], 337 | html input[disabled] { 338 | cursor: default; 339 | } 340 | 341 | /** 342 | * 1. Address box sizing set to `content-box` in IE 8/9/10. 343 | * 2. Remove excess padding in IE 8/9/10. 344 | */ 345 | 346 | input[type="checkbox"], 347 | input[type="radio"] { 348 | box-sizing: border-box; /* 1 */ 349 | padding: 0; /* 2 */ 350 | } 351 | 352 | /** 353 | * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome. 354 | * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome 355 | * (include `-moz` to future-proof). 356 | */ 357 | 358 | input[type="search"] { 359 | -webkit-appearance: textfield; /* 1 */ 360 | -moz-box-sizing: content-box; 361 | -webkit-box-sizing: content-box; /* 2 */ 362 | box-sizing: content-box; 363 | } 364 | 365 | /** 366 | * Remove inner padding and search cancel button in Safari 5 and Chrome 367 | * on OS X. 368 | */ 369 | 370 | input[type="search"]::-webkit-search-cancel-button, 371 | input[type="search"]::-webkit-search-decoration { 372 | -webkit-appearance: none; 373 | } 374 | 375 | /** 376 | * Remove inner padding and border in Firefox 4+. 377 | */ 378 | 379 | button::-moz-focus-inner, 380 | input::-moz-focus-inner { 381 | border: 0; 382 | padding: 0; 383 | } 384 | 385 | /** 386 | * 1. Remove default vertical scrollbar in IE 8/9. 387 | * 2. Improve readability and alignment in all browsers. 388 | */ 389 | 390 | textarea { 391 | overflow: auto; /* 1 */ 392 | vertical-align: top; /* 2 */ 393 | } 394 | 395 | /* ========================================================================== 396 | Tables 397 | ========================================================================== */ 398 | 399 | /** 400 | * Remove most spacing between table cells. 401 | */ 402 | 403 | table { 404 | border-collapse: collapse; 405 | border-spacing: 0; 406 | } 407 | -------------------------------------------------------------------------------- /dist/Chinaware.js: -------------------------------------------------------------------------------- 1 | (function(root, undefined) { 2 | 3 | "use strict"; 4 | 5 | 6 | /* Chinaware main */ 7 | 8 | // Base function. 9 | var Chinaware = function() { 10 | // Add functionality here. 11 | return true; 12 | }; 13 | 14 | 15 | // Version. 16 | Chinaware.VERSION = '0.0.0'; 17 | 18 | 19 | // Export to the root, which is probably `window`. 20 | root.Chinaware = Chinaware; 21 | 22 | 23 | /* (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 24 | * Underscore may be freely distributed under the MIT license. 25 | */ 26 | 27 | Chinaware.isObject = function (obj) { 28 | var type = typeof obj; 29 | return type === 'function' || type === 'object' && !!obj; 30 | }; 31 | 32 | Chinaware.isFunction = function(obj) { 33 | return typeof obj == 'function' || false; 34 | }; 35 | 36 | Chinaware.defaults = function(obj) { 37 | if (!Chinaware.isObject(obj)) { 38 | return obj; 39 | } 40 | 41 | for (var i = 1, length = arguments.length; i < length; i++) { 42 | var source = arguments[i]; 43 | for (var prop in source) { 44 | if (obj[prop] === void 0) { 45 | obj[prop] = source[prop]; 46 | } 47 | } 48 | } 49 | return obj; 50 | }; 51 | 52 | Chinaware.extend = function (obj) { 53 | if (!Chinaware.isObject(obj)) { 54 | return obj; 55 | } 56 | var source, prop; 57 | for (var i = 1, length = arguments.length; i < length; i++) { 58 | source = arguments[i]; 59 | for (prop in source) { 60 | if (hasOwnProperty.call(source, prop)) { 61 | obj[prop] = source[prop]; 62 | } 63 | } 64 | } 65 | return obj; 66 | }; 67 | 68 | 69 | /** 70 | * Chinaware Class 0.0.1 71 | * JavaScript Class built-in inheritance system 72 | *(c) 2015, Fengda Huang - http://www.phodal.com 73 | * 74 | * Copyright (c) 2011, 2012 Jeanine Adkisson. 75 | * MIT Licensed. 76 | * Inspired by https://github.com/munro/self, https://github.com/jneen/pjs 77 | */ 78 | 79 | Chinaware.prototype.Class = (function (prototype, ownProperty) { 80 | 81 | var ChinawareClass = function Klass(_superclass, definition) { 82 | 83 | function Class() { 84 | var self = this instanceof Class ? this : new Basic(); 85 | self.init.apply(self, arguments); 86 | return self; 87 | } 88 | 89 | function Basic() { 90 | } 91 | 92 | Class.Basic = Basic; 93 | 94 | var _super = Basic[prototype] = _superclass[prototype]; 95 | var proto = Basic[prototype] = Class[prototype] = new Basic(); 96 | 97 | proto.constructor = Class; 98 | 99 | Class.extend = function (def) { 100 | return new Klass(Class, def); 101 | }; 102 | 103 | var open = (Class.open = function (def) { 104 | if (Chinaware.isFunction(def)) { 105 | def = def.call(Class, proto, _super, Class, _superclass); 106 | } 107 | 108 | if (Chinaware.isObject(def)) { 109 | for (var key in def) { 110 | if (ownProperty.call(def, key)) { 111 | proto[key] = def[key]; 112 | } 113 | } 114 | } 115 | 116 | if (!('init' in proto)) { 117 | proto.init = _superclass; 118 | } 119 | 120 | return Class; 121 | }); 122 | 123 | return (open)(definition); 124 | }; 125 | 126 | return ChinawareClass; 127 | 128 | })('prototype', ({}).hasOwnProperty); 129 | 130 | 131 | Chinaware.get = function (url, callback) { 132 | Chinaware.send(url, 'GET', callback); 133 | }; 134 | 135 | Chinaware.load = function (url, callback) { 136 | Chinaware.send(url, 'GET', callback); 137 | }; 138 | 139 | Chinaware.post = function (url, data, callback) { 140 | Chinaware.send(url, 'POST', callback, data); 141 | }; 142 | 143 | Chinaware.send = function (url, method, callback, data) { 144 | data = data || null; 145 | var request = new XMLHttpRequest(); 146 | if (callback instanceof Function) { 147 | request.onreadystatechange = function () { 148 | if (request.readyState === 4 && (request.status === 200 || request.status === 0)) { 149 | callback(request.responseText); 150 | } 151 | }; 152 | } 153 | request.open(method, url, true); 154 | if (data instanceof Object) { 155 | data = JSON.stringify(data); 156 | request.setRequestHeader('Content-Type', 'application/json'); 157 | } 158 | request.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); 159 | request.send(data); 160 | }; 161 | 162 | 163 | var Event = { 164 | on: function(event, callback){ 165 | this._events = this._events || {}; 166 | this._events[event] = this._events[event] || []; 167 | this._events[event].push(callback); 168 | }, 169 | off: function(event, callback){ 170 | this._events = this._events || {}; 171 | if (event in this._events === false) { 172 | return; 173 | } 174 | this._events[event].splice(this._events[event].indexOf(callback), 1); 175 | }, 176 | trigger: function(event){ 177 | this._events = this._events || {}; 178 | if (event in this._events === false) { 179 | return; 180 | } 181 | for (var i = 0; i < this._events[event].length; i++) { 182 | this._events[event][i].apply(this, Array.prototype.slice.call(arguments, 1)); 183 | } 184 | } 185 | }; 186 | 187 | var event = { 188 | Event: Event 189 | }; 190 | 191 | 192 | Chinaware.prototype = Chinaware.extend(Chinaware.prototype, event); 193 | 194 | 195 | /* 196 | * JavaScript Templates 2.4.1 197 | * https://github.com/blueimp/JavaScript-Templates 198 | * 199 | * Copyright 2011, Sebastian Tschan 200 | * https://blueimp.net 201 | * 202 | * Licensed under the MIT license: 203 | * http://www.opensource.org/licenses/MIT 204 | * 205 | * Inspired by John Resig's JavaScript Micro-Templating: 206 | * http://ejohn.org/blog/javascript-micro-templating/ 207 | */ 208 | 209 | /*jslint evil: true, regexp: true, unparam: true */ 210 | 211 | var Template = { 212 | regexp: /([\s'\\])(?!(?:[^{]|\{(?!%))*%\})|(?:\{%(=|#)([\s\S]+?)%\})|(\{%)|(%\})/g, 213 | encReg: /[<>&"'\x00]/g, 214 | encMap: { 215 | "<": "<", 216 | ">": ">", 217 | "&": "&", 218 | "\"": """, 219 | "'": "'" 220 | }, 221 | arg: "o", 222 | helper: ",print=function(s,e){_s+=e?(s==null?'':s):_e(s);}" + 223 | ",include=function(s,d){_s+=tmpl(s,d);}", 224 | 225 | tmpl: function (str, data){ 226 | var f = !/[^\w\-\.:]/.test(str) ? "" : this.compile(str); 227 | return f(data, this); 228 | }, 229 | 230 | compile: function (str) { 231 | var fn, variable; 232 | variable = this.arg + ',tmpl'; 233 | fn = "var _e=tmpl.encode" + this.helper + ",_s='" + str.replace(this.regexp, this.func) + "';"; 234 | fn = fn + "return _s;"; 235 | return new Function(variable, fn); 236 | }, 237 | 238 | encode: function (s) { 239 | /*jshint eqnull:true */ 240 | var encodeRegex = /[<>&"'\x00]/g, 241 | encodeMap = { 242 | "<": "<", 243 | ">": ">", 244 | "&": "&", 245 | "\"": """, 246 | "'": "'" 247 | }; 248 | return (s == null ? "" : "" + s).replace( 249 | encodeRegex, 250 | function (c) { 251 | return encodeMap[c] || ""; 252 | } 253 | ); 254 | }, 255 | 256 | func: function (s, p1, p2, p3, p4, p5) { 257 | var specialCharMAP = { 258 | "\n": "\\n", 259 | "\r": "\\r", 260 | "\t": "\\t", 261 | " ": " " 262 | }; 263 | 264 | if (p1) { // whitespace, quote and backspace in HTML context 265 | return specialCharMAP[p1] || "\\" + p1; 266 | } 267 | if (p2) { // interpolation: {%=prop%}, or unescaped: {%#prop%} 268 | if (p2 === "=") { 269 | return "'+_e(" + p3 + ")+'"; 270 | } 271 | return "'+(" + p3 + "==null?'':" + p3 + ")+'"; 272 | } 273 | if (p4) { // evaluation start tag: {% 274 | return "';"; 275 | } 276 | if (p5) { // evaluation end tag: %} 277 | return "_s+='"; 278 | } 279 | } 280 | }; 281 | 282 | var template = { 283 | Template: Template 284 | }; 285 | 286 | Chinaware.prototype = Chinaware.extend(Chinaware.prototype, template); 287 | 288 | 289 | var FX = { 290 | easing: { 291 | linear: function(progress) { 292 | return progress; 293 | }, 294 | quadratic: function(progress) { 295 | return Math.pow(progress, 2); 296 | }, 297 | swing: function(progress) { 298 | return 0.5 - Math.cos(progress * Math.PI) / 2; 299 | }, 300 | circ: function(progress) { 301 | return 1 - Math.sin(Math.acos(progress)); 302 | }, 303 | back: function(progress, x) { 304 | return Math.pow(progress, 2) * ((x + 1) * progress - x); 305 | }, 306 | bounce: function(progress) { 307 | for (var a = 0, b = 1; 1; a += b, b /= 2) { 308 | if (progress >= (7 - 4 * a) / 11) { 309 | return -Math.pow((11 - 6 * a - 11 * progress) / 4, 2) + Math.pow(b, 2); 310 | } 311 | } 312 | }, 313 | elastic: function(progress, x) { 314 | return Math.pow(2, 10 * (progress - 1)) * Math.cos(20 * Math.PI * x / 3 * progress); 315 | } 316 | }, 317 | animate: function(options) { 318 | var start = new Date(); 319 | var id = setInterval(function() { 320 | var timePassed = new Date() - start; 321 | var progress = timePassed / options.duration; 322 | if (progress > 1) { 323 | progress = 1; 324 | } 325 | options.progress = progress; 326 | var delta = options.delta(progress); 327 | options.step(delta); 328 | if (progress == 1) { 329 | clearInterval(id); 330 | options.complete(); 331 | } 332 | }, options.delay || 10); 333 | }, 334 | fadeOut: function(element, options) { 335 | var to = 1; 336 | this.animate({ 337 | duration: options.duration, 338 | delta: function(progress) { 339 | progress = this.progress; 340 | return FX.easing.swing(progress); 341 | }, 342 | complete: options.complete, 343 | step: function(delta) { 344 | element.style.opacity = to - delta; 345 | } 346 | }); 347 | }, 348 | fadeIn: function(element, options) { 349 | var to = 0; 350 | this.animate({ 351 | duration: options.duration, 352 | delta: function(progress) { 353 | progress = this.progress; 354 | return FX.easing.swing(progress); 355 | }, 356 | complete: options.complete, 357 | step: function(delta) { 358 | element.style.opacity = to + delta; 359 | } 360 | }); 361 | } 362 | }; 363 | 364 | var fx = { 365 | FX: FX 366 | }; 367 | 368 | Chinaware.prototype = Chinaware.extend(Chinaware.prototype, fx); 369 | 370 | 371 | 372 | function Tiled(content) { 373 | this.layers = []; 374 | var that = this; 375 | 376 | this.renderLayer = function (layer) { 377 | if (layer.type !== 'tilelayer' || !layer.opacity) { 378 | return; 379 | } 380 | var s = content.canvas.cloneNode(), 381 | size = that.data.tilewidth; 382 | s = s.getContext('2d'); 383 | if (that.layers.length < that.data.layers.length) { 384 | layer.data.forEach(function (tileIndex, i) { 385 | if (!tileIndex) { 386 | return; 387 | } 388 | var imgX, imgY, sizeX, sizeY, 389 | tile = that.data.tilesets[0]; 390 | tileIndex--; 391 | imgX = (tileIndex % (tile.imagewidth / size)) * size; 392 | imgY = ~~(tileIndex / (tile.imagewidth / size)) * size; 393 | sizeX = (i % layer.width) * size; 394 | sizeY = ~~(i / layer.width) * size; 395 | s.drawImage(that.tileset, imgX, imgY, size, size, sizeX, sizeY, size, size); 396 | }); 397 | that.layers.push(s.canvas.toDataURL()); 398 | content.drawImage(s.canvas, 0, 0); 399 | } 400 | else { 401 | that.layers.forEach(function (src) { 402 | var i = document.createElement('img'); 403 | i.src = src; 404 | content.drawImage(i, 0, 0); 405 | }); 406 | } 407 | }; 408 | 409 | this.renderLayers = function (layers) { 410 | function isObject(obj) { 411 | var type = typeof obj; 412 | return type === 'array'; 413 | } 414 | 415 | layers = isObject(layers) ? layers : this.data.layers; 416 | layers.forEach(this.renderLayer); 417 | }; 418 | 419 | this.loadTileset = function (json) { 420 | var that = this; 421 | this.data = json; 422 | this.tileset = document.createElement('img'); 423 | this.tileset.src = json.tilesets[0].image; 424 | this.tileset.onload = function () { 425 | that.renderLayers(that.tileset); 426 | }; 427 | }; 428 | 429 | this.load = function (name) { 430 | var that = this; 431 | Chinaware.get('./images/' + name + '.json', function (data) { 432 | that.loadTileset(JSON.parse(data)); 433 | }); 434 | }; 435 | } 436 | 437 | var tiled = { 438 | Tiled:Tiled 439 | }; 440 | 441 | Chinaware.prototype = Chinaware.extend(Chinaware.prototype, tiled); 442 | 443 | 444 | }(this)); 445 | -------------------------------------------------------------------------------- /test/lib/qunit.js: -------------------------------------------------------------------------------- 1 | /** 2 | * QUnit v1.12.0 - A JavaScript Unit Testing Framework 3 | * 4 | * http://qunitjs.com 5 | * 6 | * Copyright 2013 jQuery Foundation and other contributors 7 | * Released under the MIT license. 8 | * https://jquery.org/license/ 9 | */ 10 | 11 | (function( window ) { 12 | 13 | var QUnit, 14 | assert, 15 | config, 16 | onErrorFnPrev, 17 | testId = 0, 18 | fileName = (sourceFromStacktrace( 0 ) || "" ).replace(/(:\d+)+\)?/, "").replace(/.+\//, ""), 19 | toString = Object.prototype.toString, 20 | hasOwn = Object.prototype.hasOwnProperty, 21 | // Keep a local reference to Date (GH-283) 22 | Date = window.Date, 23 | setTimeout = window.setTimeout, 24 | defined = { 25 | setTimeout: typeof window.setTimeout !== "undefined", 26 | sessionStorage: (function() { 27 | var x = "qunit-test-string"; 28 | try { 29 | sessionStorage.setItem( x, x ); 30 | sessionStorage.removeItem( x ); 31 | return true; 32 | } catch( e ) { 33 | return false; 34 | } 35 | }()) 36 | }, 37 | /** 38 | * Provides a normalized error string, correcting an issue 39 | * with IE 7 (and prior) where Error.prototype.toString is 40 | * not properly implemented 41 | * 42 | * Based on http://es5.github.com/#x15.11.4.4 43 | * 44 | * @param {String|Error} error 45 | * @return {String} error message 46 | */ 47 | errorString = function( error ) { 48 | var name, message, 49 | errorString = error.toString(); 50 | if ( errorString.substring( 0, 7 ) === "[object" ) { 51 | name = error.name ? error.name.toString() : "Error"; 52 | message = error.message ? error.message.toString() : ""; 53 | if ( name && message ) { 54 | return name + ": " + message; 55 | } else if ( name ) { 56 | return name; 57 | } else if ( message ) { 58 | return message; 59 | } else { 60 | return "Error"; 61 | } 62 | } else { 63 | return errorString; 64 | } 65 | }, 66 | /** 67 | * Makes a clone of an object using only Array or Object as base, 68 | * and copies over the own enumerable properties. 69 | * 70 | * @param {Object} obj 71 | * @return {Object} New object with only the own properties (recursively). 72 | */ 73 | objectValues = function( obj ) { 74 | // Grunt 0.3.x uses an older version of jshint that still has jshint/jshint#392. 75 | /*jshint newcap: false */ 76 | var key, val, 77 | vals = QUnit.is( "array", obj ) ? [] : {}; 78 | for ( key in obj ) { 79 | if ( hasOwn.call( obj, key ) ) { 80 | val = obj[key]; 81 | vals[key] = val === Object(val) ? objectValues(val) : val; 82 | } 83 | } 84 | return vals; 85 | }; 86 | 87 | function Test( settings ) { 88 | extend( this, settings ); 89 | this.assertions = []; 90 | this.testNumber = ++Test.count; 91 | } 92 | 93 | Test.count = 0; 94 | 95 | Test.prototype = { 96 | init: function() { 97 | var a, b, li, 98 | tests = id( "qunit-tests" ); 99 | 100 | if ( tests ) { 101 | b = document.createElement( "strong" ); 102 | b.innerHTML = this.nameHtml; 103 | 104 | // `a` initialized at top of scope 105 | a = document.createElement( "a" ); 106 | a.innerHTML = "Rerun"; 107 | a.href = QUnit.url({ testNumber: this.testNumber }); 108 | 109 | li = document.createElement( "li" ); 110 | li.appendChild( b ); 111 | li.appendChild( a ); 112 | li.className = "running"; 113 | li.id = this.id = "qunit-test-output" + testId++; 114 | 115 | tests.appendChild( li ); 116 | } 117 | }, 118 | setup: function() { 119 | if ( 120 | // Emit moduleStart when we're switching from one module to another 121 | this.module !== config.previousModule || 122 | // They could be equal (both undefined) but if the previousModule property doesn't 123 | // yet exist it means this is the first test in a suite that isn't wrapped in a 124 | // module, in which case we'll just emit a moduleStart event for 'undefined'. 125 | // Without this, reporters can get testStart before moduleStart which is a problem. 126 | !hasOwn.call( config, "previousModule" ) 127 | ) { 128 | if ( hasOwn.call( config, "previousModule" ) ) { 129 | runLoggingCallbacks( "moduleDone", QUnit, { 130 | name: config.previousModule, 131 | failed: config.moduleStats.bad, 132 | passed: config.moduleStats.all - config.moduleStats.bad, 133 | total: config.moduleStats.all 134 | }); 135 | } 136 | config.previousModule = this.module; 137 | config.moduleStats = { all: 0, bad: 0 }; 138 | runLoggingCallbacks( "moduleStart", QUnit, { 139 | name: this.module 140 | }); 141 | } 142 | 143 | config.current = this; 144 | 145 | this.testEnvironment = extend({ 146 | setup: function() {}, 147 | teardown: function() {} 148 | }, this.moduleTestEnvironment ); 149 | 150 | this.started = +new Date(); 151 | runLoggingCallbacks( "testStart", QUnit, { 152 | name: this.testName, 153 | module: this.module 154 | }); 155 | 156 | /*jshint camelcase:false */ 157 | 158 | 159 | /** 160 | * Expose the current test environment. 161 | * 162 | * @deprecated since 1.12.0: Use QUnit.config.current.testEnvironment instead. 163 | */ 164 | QUnit.current_testEnvironment = this.testEnvironment; 165 | 166 | /*jshint camelcase:true */ 167 | 168 | if ( !config.pollution ) { 169 | saveGlobal(); 170 | } 171 | if ( config.notrycatch ) { 172 | this.testEnvironment.setup.call( this.testEnvironment, QUnit.assert ); 173 | return; 174 | } 175 | try { 176 | this.testEnvironment.setup.call( this.testEnvironment, QUnit.assert ); 177 | } catch( e ) { 178 | QUnit.pushFailure( "Setup failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) ); 179 | } 180 | }, 181 | run: function() { 182 | config.current = this; 183 | 184 | var running = id( "qunit-testresult" ); 185 | 186 | if ( running ) { 187 | running.innerHTML = "Running:
" + this.nameHtml; 188 | } 189 | 190 | if ( this.async ) { 191 | QUnit.stop(); 192 | } 193 | 194 | this.callbackStarted = +new Date(); 195 | 196 | if ( config.notrycatch ) { 197 | this.callback.call( this.testEnvironment, QUnit.assert ); 198 | this.callbackRuntime = +new Date() - this.callbackStarted; 199 | return; 200 | } 201 | 202 | try { 203 | this.callback.call( this.testEnvironment, QUnit.assert ); 204 | this.callbackRuntime = +new Date() - this.callbackStarted; 205 | } catch( e ) { 206 | this.callbackRuntime = +new Date() - this.callbackStarted; 207 | 208 | QUnit.pushFailure( "Died on test #" + (this.assertions.length + 1) + " " + this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) ); 209 | // else next test will carry the responsibility 210 | saveGlobal(); 211 | 212 | // Restart the tests if they're blocking 213 | if ( config.blocking ) { 214 | QUnit.start(); 215 | } 216 | } 217 | }, 218 | teardown: function() { 219 | config.current = this; 220 | if ( config.notrycatch ) { 221 | if ( typeof this.callbackRuntime === "undefined" ) { 222 | this.callbackRuntime = +new Date() - this.callbackStarted; 223 | } 224 | this.testEnvironment.teardown.call( this.testEnvironment, QUnit.assert ); 225 | return; 226 | } else { 227 | try { 228 | this.testEnvironment.teardown.call( this.testEnvironment, QUnit.assert ); 229 | } catch( e ) { 230 | QUnit.pushFailure( "Teardown failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) ); 231 | } 232 | } 233 | checkPollution(); 234 | }, 235 | finish: function() { 236 | config.current = this; 237 | if ( config.requireExpects && this.expected === null ) { 238 | QUnit.pushFailure( "Expected number of assertions to be defined, but expect() was not called.", this.stack ); 239 | } else if ( this.expected !== null && this.expected !== this.assertions.length ) { 240 | QUnit.pushFailure( "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run", this.stack ); 241 | } else if ( this.expected === null && !this.assertions.length ) { 242 | QUnit.pushFailure( "Expected at least one assertion, but none were run - call expect(0) to accept zero assertions.", this.stack ); 243 | } 244 | 245 | var i, assertion, a, b, time, li, ol, 246 | test = this, 247 | good = 0, 248 | bad = 0, 249 | tests = id( "qunit-tests" ); 250 | 251 | this.runtime = +new Date() - this.started; 252 | config.stats.all += this.assertions.length; 253 | config.moduleStats.all += this.assertions.length; 254 | 255 | if ( tests ) { 256 | ol = document.createElement( "ol" ); 257 | ol.className = "qunit-assert-list"; 258 | 259 | for ( i = 0; i < this.assertions.length; i++ ) { 260 | assertion = this.assertions[i]; 261 | 262 | li = document.createElement( "li" ); 263 | li.className = assertion.result ? "pass" : "fail"; 264 | li.innerHTML = assertion.message || ( assertion.result ? "okay" : "failed" ); 265 | ol.appendChild( li ); 266 | 267 | if ( assertion.result ) { 268 | good++; 269 | } else { 270 | bad++; 271 | config.stats.bad++; 272 | config.moduleStats.bad++; 273 | } 274 | } 275 | 276 | // store result when possible 277 | if ( QUnit.config.reorder && defined.sessionStorage ) { 278 | if ( bad ) { 279 | sessionStorage.setItem( "qunit-test-" + this.module + "-" + this.testName, bad ); 280 | } else { 281 | sessionStorage.removeItem( "qunit-test-" + this.module + "-" + this.testName ); 282 | } 283 | } 284 | 285 | if ( bad === 0 ) { 286 | addClass( ol, "qunit-collapsed" ); 287 | } 288 | 289 | // `b` initialized at top of scope 290 | b = document.createElement( "strong" ); 291 | b.innerHTML = this.nameHtml + " (" + bad + ", " + good + ", " + this.assertions.length + ")"; 292 | 293 | addEvent(b, "click", function() { 294 | var next = b.parentNode.lastChild, 295 | collapsed = hasClass( next, "qunit-collapsed" ); 296 | ( collapsed ? removeClass : addClass )( next, "qunit-collapsed" ); 297 | }); 298 | 299 | addEvent(b, "dblclick", function( e ) { 300 | var target = e && e.target ? e.target : window.event.srcElement; 301 | if ( target.nodeName.toLowerCase() === "span" || target.nodeName.toLowerCase() === "b" ) { 302 | target = target.parentNode; 303 | } 304 | if ( window.location && target.nodeName.toLowerCase() === "strong" ) { 305 | window.location = QUnit.url({ testNumber: test.testNumber }); 306 | } 307 | }); 308 | 309 | // `time` initialized at top of scope 310 | time = document.createElement( "span" ); 311 | time.className = "runtime"; 312 | time.innerHTML = this.runtime + " ms"; 313 | 314 | // `li` initialized at top of scope 315 | li = id( this.id ); 316 | li.className = bad ? "fail" : "pass"; 317 | li.removeChild( li.firstChild ); 318 | a = li.firstChild; 319 | li.appendChild( b ); 320 | li.appendChild( a ); 321 | li.appendChild( time ); 322 | li.appendChild( ol ); 323 | 324 | } else { 325 | for ( i = 0; i < this.assertions.length; i++ ) { 326 | if ( !this.assertions[i].result ) { 327 | bad++; 328 | config.stats.bad++; 329 | config.moduleStats.bad++; 330 | } 331 | } 332 | } 333 | 334 | runLoggingCallbacks( "testDone", QUnit, { 335 | name: this.testName, 336 | module: this.module, 337 | failed: bad, 338 | passed: this.assertions.length - bad, 339 | total: this.assertions.length, 340 | duration: this.runtime 341 | }); 342 | 343 | QUnit.reset(); 344 | 345 | config.current = undefined; 346 | }, 347 | 348 | queue: function() { 349 | var bad, 350 | test = this; 351 | 352 | synchronize(function() { 353 | test.init(); 354 | }); 355 | function run() { 356 | // each of these can by async 357 | synchronize(function() { 358 | test.setup(); 359 | }); 360 | synchronize(function() { 361 | test.run(); 362 | }); 363 | synchronize(function() { 364 | test.teardown(); 365 | }); 366 | synchronize(function() { 367 | test.finish(); 368 | }); 369 | } 370 | 371 | // `bad` initialized at top of scope 372 | // defer when previous test run passed, if storage is available 373 | bad = QUnit.config.reorder && defined.sessionStorage && 374 | +sessionStorage.getItem( "qunit-test-" + this.module + "-" + this.testName ); 375 | 376 | if ( bad ) { 377 | run(); 378 | } else { 379 | synchronize( run, true ); 380 | } 381 | } 382 | }; 383 | 384 | // Root QUnit object. 385 | // `QUnit` initialized at top of scope 386 | QUnit = { 387 | 388 | // call on start of module test to prepend name to all tests 389 | module: function( name, testEnvironment ) { 390 | config.currentModule = name; 391 | config.currentModuleTestEnvironment = testEnvironment; 392 | config.modules[name] = true; 393 | }, 394 | 395 | asyncTest: function( testName, expected, callback ) { 396 | if ( arguments.length === 2 ) { 397 | callback = expected; 398 | expected = null; 399 | } 400 | 401 | QUnit.test( testName, expected, callback, true ); 402 | }, 403 | 404 | test: function( testName, expected, callback, async ) { 405 | var test, 406 | nameHtml = "" + escapeText( testName ) + ""; 407 | 408 | if ( arguments.length === 2 ) { 409 | callback = expected; 410 | expected = null; 411 | } 412 | 413 | if ( config.currentModule ) { 414 | nameHtml = "" + escapeText( config.currentModule ) + ": " + nameHtml; 415 | } 416 | 417 | test = new Test({ 418 | nameHtml: nameHtml, 419 | testName: testName, 420 | expected: expected, 421 | async: async, 422 | callback: callback, 423 | module: config.currentModule, 424 | moduleTestEnvironment: config.currentModuleTestEnvironment, 425 | stack: sourceFromStacktrace( 2 ) 426 | }); 427 | 428 | if ( !validTest( test ) ) { 429 | return; 430 | } 431 | 432 | test.queue(); 433 | }, 434 | 435 | // Specify the number of expected assertions to guarantee that failed test (no assertions are run at all) don't slip through. 436 | expect: function( asserts ) { 437 | if (arguments.length === 1) { 438 | config.current.expected = asserts; 439 | } else { 440 | return config.current.expected; 441 | } 442 | }, 443 | 444 | start: function( count ) { 445 | // QUnit hasn't been initialized yet. 446 | // Note: RequireJS (et al) may delay onLoad 447 | if ( config.semaphore === undefined ) { 448 | QUnit.begin(function() { 449 | // This is triggered at the top of QUnit.load, push start() to the event loop, to allow QUnit.load to finish first 450 | setTimeout(function() { 451 | QUnit.start( count ); 452 | }); 453 | }); 454 | return; 455 | } 456 | 457 | config.semaphore -= count || 1; 458 | // don't start until equal number of stop-calls 459 | if ( config.semaphore > 0 ) { 460 | return; 461 | } 462 | // ignore if start is called more often then stop 463 | if ( config.semaphore < 0 ) { 464 | config.semaphore = 0; 465 | QUnit.pushFailure( "Called start() while already started (QUnit.config.semaphore was 0 already)", null, sourceFromStacktrace(2) ); 466 | return; 467 | } 468 | // A slight delay, to avoid any current callbacks 469 | if ( defined.setTimeout ) { 470 | setTimeout(function() { 471 | if ( config.semaphore > 0 ) { 472 | return; 473 | } 474 | if ( config.timeout ) { 475 | clearTimeout( config.timeout ); 476 | } 477 | 478 | config.blocking = false; 479 | process( true ); 480 | }, 13); 481 | } else { 482 | config.blocking = false; 483 | process( true ); 484 | } 485 | }, 486 | 487 | stop: function( count ) { 488 | config.semaphore += count || 1; 489 | config.blocking = true; 490 | 491 | if ( config.testTimeout && defined.setTimeout ) { 492 | clearTimeout( config.timeout ); 493 | config.timeout = setTimeout(function() { 494 | QUnit.ok( false, "Test timed out" ); 495 | config.semaphore = 1; 496 | QUnit.start(); 497 | }, config.testTimeout ); 498 | } 499 | } 500 | }; 501 | 502 | // `assert` initialized at top of scope 503 | // Assert helpers 504 | // All of these must either call QUnit.push() or manually do: 505 | // - runLoggingCallbacks( "log", .. ); 506 | // - config.current.assertions.push({ .. }); 507 | // We attach it to the QUnit object *after* we expose the public API, 508 | // otherwise `assert` will become a global variable in browsers (#341). 509 | assert = { 510 | /** 511 | * Asserts rough true-ish result. 512 | * @name ok 513 | * @function 514 | * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); 515 | */ 516 | ok: function( result, msg ) { 517 | if ( !config.current ) { 518 | throw new Error( "ok() assertion outside test context, was " + sourceFromStacktrace(2) ); 519 | } 520 | result = !!result; 521 | msg = msg || (result ? "okay" : "failed" ); 522 | 523 | var source, 524 | details = { 525 | module: config.current.module, 526 | name: config.current.testName, 527 | result: result, 528 | message: msg 529 | }; 530 | 531 | msg = "" + escapeText( msg ) + ""; 532 | 533 | if ( !result ) { 534 | source = sourceFromStacktrace( 2 ); 535 | if ( source ) { 536 | details.source = source; 537 | msg += "
Source:
" + escapeText( source ) + "
"; 538 | } 539 | } 540 | runLoggingCallbacks( "log", QUnit, details ); 541 | config.current.assertions.push({ 542 | result: result, 543 | message: msg 544 | }); 545 | }, 546 | 547 | /** 548 | * Assert that the first two arguments are equal, with an optional message. 549 | * Prints out both actual and expected values. 550 | * @name equal 551 | * @function 552 | * @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes.", "format() replaces {0} with next argument" ); 553 | */ 554 | equal: function( actual, expected, message ) { 555 | /*jshint eqeqeq:false */ 556 | QUnit.push( expected == actual, actual, expected, message ); 557 | }, 558 | 559 | /** 560 | * @name notEqual 561 | * @function 562 | */ 563 | notEqual: function( actual, expected, message ) { 564 | /*jshint eqeqeq:false */ 565 | QUnit.push( expected != actual, actual, expected, message ); 566 | }, 567 | 568 | /** 569 | * @name propEqual 570 | * @function 571 | */ 572 | propEqual: function( actual, expected, message ) { 573 | actual = objectValues(actual); 574 | expected = objectValues(expected); 575 | QUnit.push( QUnit.equiv(actual, expected), actual, expected, message ); 576 | }, 577 | 578 | /** 579 | * @name notPropEqual 580 | * @function 581 | */ 582 | notPropEqual: function( actual, expected, message ) { 583 | actual = objectValues(actual); 584 | expected = objectValues(expected); 585 | QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message ); 586 | }, 587 | 588 | /** 589 | * @name deepEqual 590 | * @function 591 | */ 592 | deepEqual: function( actual, expected, message ) { 593 | QUnit.push( QUnit.equiv(actual, expected), actual, expected, message ); 594 | }, 595 | 596 | /** 597 | * @name notDeepEqual 598 | * @function 599 | */ 600 | notDeepEqual: function( actual, expected, message ) { 601 | QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message ); 602 | }, 603 | 604 | /** 605 | * @name strictEqual 606 | * @function 607 | */ 608 | strictEqual: function( actual, expected, message ) { 609 | QUnit.push( expected === actual, actual, expected, message ); 610 | }, 611 | 612 | /** 613 | * @name notStrictEqual 614 | * @function 615 | */ 616 | notStrictEqual: function( actual, expected, message ) { 617 | QUnit.push( expected !== actual, actual, expected, message ); 618 | }, 619 | 620 | "throws": function( block, expected, message ) { 621 | var actual, 622 | expectedOutput = expected, 623 | ok = false; 624 | 625 | // 'expected' is optional 626 | if ( typeof expected === "string" ) { 627 | message = expected; 628 | expected = null; 629 | } 630 | 631 | config.current.ignoreGlobalErrors = true; 632 | try { 633 | block.call( config.current.testEnvironment ); 634 | } catch (e) { 635 | actual = e; 636 | } 637 | config.current.ignoreGlobalErrors = false; 638 | 639 | if ( actual ) { 640 | // we don't want to validate thrown error 641 | if ( !expected ) { 642 | ok = true; 643 | expectedOutput = null; 644 | // expected is a regexp 645 | } else if ( QUnit.objectType( expected ) === "regexp" ) { 646 | ok = expected.test( errorString( actual ) ); 647 | // expected is a constructor 648 | } else if ( actual instanceof expected ) { 649 | ok = true; 650 | // expected is a validation function which returns true is validation passed 651 | } else if ( expected.call( {}, actual ) === true ) { 652 | expectedOutput = null; 653 | ok = true; 654 | } 655 | 656 | QUnit.push( ok, actual, expectedOutput, message ); 657 | } else { 658 | QUnit.pushFailure( message, null, "No exception was thrown." ); 659 | } 660 | } 661 | }; 662 | 663 | /** 664 | * @deprecated since 1.8.0 665 | * Kept assertion helpers in root for backwards compatibility. 666 | */ 667 | extend( QUnit, assert ); 668 | 669 | /** 670 | * @deprecated since 1.9.0 671 | * Kept root "raises()" for backwards compatibility. 672 | * (Note that we don't introduce assert.raises). 673 | */ 674 | QUnit.raises = assert[ "throws" ]; 675 | 676 | /** 677 | * @deprecated since 1.0.0, replaced with error pushes since 1.3.0 678 | * Kept to avoid TypeErrors for undefined methods. 679 | */ 680 | QUnit.equals = function() { 681 | QUnit.push( false, false, false, "QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead" ); 682 | }; 683 | QUnit.same = function() { 684 | QUnit.push( false, false, false, "QUnit.same has been deprecated since 2009 (e88049a0), use QUnit.deepEqual instead" ); 685 | }; 686 | 687 | // We want access to the constructor's prototype 688 | (function() { 689 | function F() {} 690 | F.prototype = QUnit; 691 | QUnit = new F(); 692 | // Make F QUnit's constructor so that we can add to the prototype later 693 | QUnit.constructor = F; 694 | }()); 695 | 696 | /** 697 | * Config object: Maintain internal state 698 | * Later exposed as QUnit.config 699 | * `config` initialized at top of scope 700 | */ 701 | config = { 702 | // The queue of tests to run 703 | queue: [], 704 | 705 | // block until document ready 706 | blocking: true, 707 | 708 | // when enabled, show only failing tests 709 | // gets persisted through sessionStorage and can be changed in UI via checkbox 710 | hidepassed: false, 711 | 712 | // by default, run previously failed tests first 713 | // very useful in combination with "Hide passed tests" checked 714 | reorder: true, 715 | 716 | // by default, modify document.title when suite is done 717 | altertitle: true, 718 | 719 | // when enabled, all tests must call expect() 720 | requireExpects: false, 721 | 722 | // add checkboxes that are persisted in the query-string 723 | // when enabled, the id is set to `true` as a `QUnit.config` property 724 | urlConfig: [ 725 | { 726 | id: "noglobals", 727 | label: "Check for Globals", 728 | tooltip: "Enabling this will test if any test introduces new properties on the `window` object. Stored as query-strings." 729 | }, 730 | { 731 | id: "notrycatch", 732 | label: "No try-catch", 733 | tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging exceptions in IE reasonable. Stored as query-strings." 734 | } 735 | ], 736 | 737 | // Set of all modules. 738 | modules: {}, 739 | 740 | // logging callback queues 741 | begin: [], 742 | done: [], 743 | log: [], 744 | testStart: [], 745 | testDone: [], 746 | moduleStart: [], 747 | moduleDone: [] 748 | }; 749 | 750 | // Export global variables, unless an 'exports' object exists, 751 | // in that case we assume we're in CommonJS (dealt with on the bottom of the script) 752 | if ( typeof exports === "undefined" ) { 753 | extend( window, QUnit.constructor.prototype ); 754 | 755 | // Expose QUnit object 756 | window.QUnit = QUnit; 757 | } 758 | 759 | // Initialize more QUnit.config and QUnit.urlParams 760 | (function() { 761 | var i, 762 | location = window.location || { search: "", protocol: "file:" }, 763 | params = location.search.slice( 1 ).split( "&" ), 764 | length = params.length, 765 | urlParams = {}, 766 | current; 767 | 768 | if ( params[ 0 ] ) { 769 | for ( i = 0; i < length; i++ ) { 770 | current = params[ i ].split( "=" ); 771 | current[ 0 ] = decodeURIComponent( current[ 0 ] ); 772 | // allow just a key to turn on a flag, e.g., test.html?noglobals 773 | current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true; 774 | urlParams[ current[ 0 ] ] = current[ 1 ]; 775 | } 776 | } 777 | 778 | QUnit.urlParams = urlParams; 779 | 780 | // String search anywhere in moduleName+testName 781 | config.filter = urlParams.filter; 782 | 783 | // Exact match of the module name 784 | config.module = urlParams.module; 785 | 786 | config.testNumber = parseInt( urlParams.testNumber, 10 ) || null; 787 | 788 | // Figure out if we're running the tests from a server or not 789 | QUnit.isLocal = location.protocol === "file:"; 790 | }()); 791 | 792 | // Extend QUnit object, 793 | // these after set here because they should not be exposed as global functions 794 | extend( QUnit, { 795 | assert: assert, 796 | 797 | config: config, 798 | 799 | // Initialize the configuration options 800 | init: function() { 801 | extend( config, { 802 | stats: { all: 0, bad: 0 }, 803 | moduleStats: { all: 0, bad: 0 }, 804 | started: +new Date(), 805 | updateRate: 1000, 806 | blocking: false, 807 | autostart: true, 808 | autorun: false, 809 | filter: "", 810 | queue: [], 811 | semaphore: 1 812 | }); 813 | 814 | var tests, banner, result, 815 | qunit = id( "qunit" ); 816 | 817 | if ( qunit ) { 818 | qunit.innerHTML = 819 | "

" + escapeText( document.title ) + "

" + 820 | "

" + 821 | "
" + 822 | "

" + 823 | "
    "; 824 | } 825 | 826 | tests = id( "qunit-tests" ); 827 | banner = id( "qunit-banner" ); 828 | result = id( "qunit-testresult" ); 829 | 830 | if ( tests ) { 831 | tests.innerHTML = ""; 832 | } 833 | 834 | if ( banner ) { 835 | banner.className = ""; 836 | } 837 | 838 | if ( result ) { 839 | result.parentNode.removeChild( result ); 840 | } 841 | 842 | if ( tests ) { 843 | result = document.createElement( "p" ); 844 | result.id = "qunit-testresult"; 845 | result.className = "result"; 846 | tests.parentNode.insertBefore( result, tests ); 847 | result.innerHTML = "Running...
     "; 848 | } 849 | }, 850 | 851 | // Resets the test setup. Useful for tests that modify the DOM. 852 | /* 853 | DEPRECATED: Use multiple tests instead of resetting inside a test. 854 | Use testStart or testDone for custom cleanup. 855 | This method will throw an error in 2.0, and will be removed in 2.1 856 | */ 857 | reset: function() { 858 | var fixture = id( "qunit-fixture" ); 859 | if ( fixture ) { 860 | fixture.innerHTML = config.fixture; 861 | } 862 | }, 863 | 864 | // Trigger an event on an element. 865 | // @example triggerEvent( document.body, "click" ); 866 | triggerEvent: function( elem, type, event ) { 867 | if ( document.createEvent ) { 868 | event = document.createEvent( "MouseEvents" ); 869 | event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView, 870 | 0, 0, 0, 0, 0, false, false, false, false, 0, null); 871 | 872 | elem.dispatchEvent( event ); 873 | } else if ( elem.fireEvent ) { 874 | elem.fireEvent( "on" + type ); 875 | } 876 | }, 877 | 878 | // Safe object type checking 879 | is: function( type, obj ) { 880 | return QUnit.objectType( obj ) === type; 881 | }, 882 | 883 | objectType: function( obj ) { 884 | if ( typeof obj === "undefined" ) { 885 | return "undefined"; 886 | // consider: typeof null === object 887 | } 888 | if ( obj === null ) { 889 | return "null"; 890 | } 891 | 892 | var match = toString.call( obj ).match(/^\[object\s(.*)\]$/), 893 | type = match && match[1] || ""; 894 | 895 | switch ( type ) { 896 | case "Number": 897 | if ( isNaN(obj) ) { 898 | return "nan"; 899 | } 900 | return "number"; 901 | case "String": 902 | case "Boolean": 903 | case "Array": 904 | case "Date": 905 | case "RegExp": 906 | case "Function": 907 | return type.toLowerCase(); 908 | } 909 | if ( typeof obj === "object" ) { 910 | return "object"; 911 | } 912 | return undefined; 913 | }, 914 | 915 | push: function( result, actual, expected, message ) { 916 | if ( !config.current ) { 917 | throw new Error( "assertion outside test context, was " + sourceFromStacktrace() ); 918 | } 919 | 920 | var output, source, 921 | details = { 922 | module: config.current.module, 923 | name: config.current.testName, 924 | result: result, 925 | message: message, 926 | actual: actual, 927 | expected: expected 928 | }; 929 | 930 | message = escapeText( message ) || ( result ? "okay" : "failed" ); 931 | message = "" + message + ""; 932 | output = message; 933 | 934 | if ( !result ) { 935 | expected = escapeText( QUnit.jsDump.parse(expected) ); 936 | actual = escapeText( QUnit.jsDump.parse(actual) ); 937 | output += ""; 938 | 939 | if ( actual !== expected ) { 940 | output += ""; 941 | output += ""; 942 | } 943 | 944 | source = sourceFromStacktrace(); 945 | 946 | if ( source ) { 947 | details.source = source; 948 | output += ""; 949 | } 950 | 951 | output += "
    Expected:
    " + expected + "
    Result:
    " + actual + "
    Diff:
    " + QUnit.diff( expected, actual ) + "
    Source:
    " + escapeText( source ) + "
    "; 952 | } 953 | 954 | runLoggingCallbacks( "log", QUnit, details ); 955 | 956 | config.current.assertions.push({ 957 | result: !!result, 958 | message: output 959 | }); 960 | }, 961 | 962 | pushFailure: function( message, source, actual ) { 963 | if ( !config.current ) { 964 | throw new Error( "pushFailure() assertion outside test context, was " + sourceFromStacktrace(2) ); 965 | } 966 | 967 | var output, 968 | details = { 969 | module: config.current.module, 970 | name: config.current.testName, 971 | result: false, 972 | message: message 973 | }; 974 | 975 | message = escapeText( message ) || "error"; 976 | message = "" + message + ""; 977 | output = message; 978 | 979 | output += ""; 980 | 981 | if ( actual ) { 982 | output += ""; 983 | } 984 | 985 | if ( source ) { 986 | details.source = source; 987 | output += ""; 988 | } 989 | 990 | output += "
    Result:
    " + escapeText( actual ) + "
    Source:
    " + escapeText( source ) + "
    "; 991 | 992 | runLoggingCallbacks( "log", QUnit, details ); 993 | 994 | config.current.assertions.push({ 995 | result: false, 996 | message: output 997 | }); 998 | }, 999 | 1000 | url: function( params ) { 1001 | params = extend( extend( {}, QUnit.urlParams ), params ); 1002 | var key, 1003 | querystring = "?"; 1004 | 1005 | for ( key in params ) { 1006 | if ( hasOwn.call( params, key ) ) { 1007 | querystring += encodeURIComponent( key ) + "=" + 1008 | encodeURIComponent( params[ key ] ) + "&"; 1009 | } 1010 | } 1011 | return window.location.protocol + "//" + window.location.host + 1012 | window.location.pathname + querystring.slice( 0, -1 ); 1013 | }, 1014 | 1015 | extend: extend, 1016 | id: id, 1017 | addEvent: addEvent, 1018 | addClass: addClass, 1019 | hasClass: hasClass, 1020 | removeClass: removeClass 1021 | // load, equiv, jsDump, diff: Attached later 1022 | }); 1023 | 1024 | /** 1025 | * @deprecated: Created for backwards compatibility with test runner that set the hook function 1026 | * into QUnit.{hook}, instead of invoking it and passing the hook function. 1027 | * QUnit.constructor is set to the empty F() above so that we can add to it's prototype here. 1028 | * Doing this allows us to tell if the following methods have been overwritten on the actual 1029 | * QUnit object. 1030 | */ 1031 | extend( QUnit.constructor.prototype, { 1032 | 1033 | // Logging callbacks; all receive a single argument with the listed properties 1034 | // run test/logs.html for any related changes 1035 | begin: registerLoggingCallback( "begin" ), 1036 | 1037 | // done: { failed, passed, total, runtime } 1038 | done: registerLoggingCallback( "done" ), 1039 | 1040 | // log: { result, actual, expected, message } 1041 | log: registerLoggingCallback( "log" ), 1042 | 1043 | // testStart: { name } 1044 | testStart: registerLoggingCallback( "testStart" ), 1045 | 1046 | // testDone: { name, failed, passed, total, duration } 1047 | testDone: registerLoggingCallback( "testDone" ), 1048 | 1049 | // moduleStart: { name } 1050 | moduleStart: registerLoggingCallback( "moduleStart" ), 1051 | 1052 | // moduleDone: { name, failed, passed, total } 1053 | moduleDone: registerLoggingCallback( "moduleDone" ) 1054 | }); 1055 | 1056 | if ( typeof document === "undefined" || document.readyState === "complete" ) { 1057 | config.autorun = true; 1058 | } 1059 | 1060 | QUnit.load = function() { 1061 | runLoggingCallbacks( "begin", QUnit, {} ); 1062 | 1063 | // Initialize the config, saving the execution queue 1064 | var banner, filter, i, label, len, main, ol, toolbar, userAgent, val, 1065 | urlConfigCheckboxesContainer, urlConfigCheckboxes, moduleFilter, 1066 | numModules = 0, 1067 | moduleNames = [], 1068 | moduleFilterHtml = "", 1069 | urlConfigHtml = "", 1070 | oldconfig = extend( {}, config ); 1071 | 1072 | QUnit.init(); 1073 | extend(config, oldconfig); 1074 | 1075 | config.blocking = false; 1076 | 1077 | len = config.urlConfig.length; 1078 | 1079 | for ( i = 0; i < len; i++ ) { 1080 | val = config.urlConfig[i]; 1081 | if ( typeof val === "string" ) { 1082 | val = { 1083 | id: val, 1084 | label: val, 1085 | tooltip: "[no tooltip available]" 1086 | }; 1087 | } 1088 | config[ val.id ] = QUnit.urlParams[ val.id ]; 1089 | urlConfigHtml += ""; 1095 | } 1096 | for ( i in config.modules ) { 1097 | if ( config.modules.hasOwnProperty( i ) ) { 1098 | moduleNames.push(i); 1099 | } 1100 | } 1101 | numModules = moduleNames.length; 1102 | moduleNames.sort( function( a, b ) { 1103 | return a.localeCompare( b ); 1104 | }); 1105 | moduleFilterHtml += ""; 1116 | 1117 | // `userAgent` initialized at top of scope 1118 | userAgent = id( "qunit-userAgent" ); 1119 | if ( userAgent ) { 1120 | userAgent.innerHTML = navigator.userAgent; 1121 | } 1122 | 1123 | // `banner` initialized at top of scope 1124 | banner = id( "qunit-header" ); 1125 | if ( banner ) { 1126 | banner.innerHTML = "" + banner.innerHTML + " "; 1127 | } 1128 | 1129 | // `toolbar` initialized at top of scope 1130 | toolbar = id( "qunit-testrunner-toolbar" ); 1131 | if ( toolbar ) { 1132 | // `filter` initialized at top of scope 1133 | filter = document.createElement( "input" ); 1134 | filter.type = "checkbox"; 1135 | filter.id = "qunit-filter-pass"; 1136 | 1137 | addEvent( filter, "click", function() { 1138 | var tmp, 1139 | ol = document.getElementById( "qunit-tests" ); 1140 | 1141 | if ( filter.checked ) { 1142 | ol.className = ol.className + " hidepass"; 1143 | } else { 1144 | tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " "; 1145 | ol.className = tmp.replace( / hidepass /, " " ); 1146 | } 1147 | if ( defined.sessionStorage ) { 1148 | if (filter.checked) { 1149 | sessionStorage.setItem( "qunit-filter-passed-tests", "true" ); 1150 | } else { 1151 | sessionStorage.removeItem( "qunit-filter-passed-tests" ); 1152 | } 1153 | } 1154 | }); 1155 | 1156 | if ( config.hidepassed || defined.sessionStorage && sessionStorage.getItem( "qunit-filter-passed-tests" ) ) { 1157 | filter.checked = true; 1158 | // `ol` initialized at top of scope 1159 | ol = document.getElementById( "qunit-tests" ); 1160 | ol.className = ol.className + " hidepass"; 1161 | } 1162 | toolbar.appendChild( filter ); 1163 | 1164 | // `label` initialized at top of scope 1165 | label = document.createElement( "label" ); 1166 | label.setAttribute( "for", "qunit-filter-pass" ); 1167 | label.setAttribute( "title", "Only show tests and assertions that fail. Stored in sessionStorage." ); 1168 | label.innerHTML = "Hide passed tests"; 1169 | toolbar.appendChild( label ); 1170 | 1171 | urlConfigCheckboxesContainer = document.createElement("span"); 1172 | urlConfigCheckboxesContainer.innerHTML = urlConfigHtml; 1173 | urlConfigCheckboxes = urlConfigCheckboxesContainer.getElementsByTagName("input"); 1174 | // For oldIE support: 1175 | // * Add handlers to the individual elements instead of the container 1176 | // * Use "click" instead of "change" 1177 | // * Fallback from event.target to event.srcElement 1178 | addEvents( urlConfigCheckboxes, "click", function( event ) { 1179 | var params = {}, 1180 | target = event.target || event.srcElement; 1181 | params[ target.name ] = target.checked ? true : undefined; 1182 | window.location = QUnit.url( params ); 1183 | }); 1184 | toolbar.appendChild( urlConfigCheckboxesContainer ); 1185 | 1186 | if (numModules > 1) { 1187 | moduleFilter = document.createElement( "span" ); 1188 | moduleFilter.setAttribute( "id", "qunit-modulefilter-container" ); 1189 | moduleFilter.innerHTML = moduleFilterHtml; 1190 | addEvent( moduleFilter.lastChild, "change", function() { 1191 | var selectBox = moduleFilter.getElementsByTagName("select")[0], 1192 | selectedModule = decodeURIComponent(selectBox.options[selectBox.selectedIndex].value); 1193 | 1194 | window.location = QUnit.url({ 1195 | module: ( selectedModule === "" ) ? undefined : selectedModule, 1196 | // Remove any existing filters 1197 | filter: undefined, 1198 | testNumber: undefined 1199 | }); 1200 | }); 1201 | toolbar.appendChild(moduleFilter); 1202 | } 1203 | } 1204 | 1205 | // `main` initialized at top of scope 1206 | main = id( "qunit-fixture" ); 1207 | if ( main ) { 1208 | config.fixture = main.innerHTML; 1209 | } 1210 | 1211 | if ( config.autostart ) { 1212 | QUnit.start(); 1213 | } 1214 | }; 1215 | 1216 | addEvent( window, "load", QUnit.load ); 1217 | 1218 | // `onErrorFnPrev` initialized at top of scope 1219 | // Preserve other handlers 1220 | onErrorFnPrev = window.onerror; 1221 | 1222 | // Cover uncaught exceptions 1223 | // Returning true will suppress the default browser handler, 1224 | // returning false will let it run. 1225 | window.onerror = function ( error, filePath, linerNr ) { 1226 | var ret = false; 1227 | if ( onErrorFnPrev ) { 1228 | ret = onErrorFnPrev( error, filePath, linerNr ); 1229 | } 1230 | 1231 | // Treat return value as window.onerror itself does, 1232 | // Only do our handling if not suppressed. 1233 | if ( ret !== true ) { 1234 | if ( QUnit.config.current ) { 1235 | if ( QUnit.config.current.ignoreGlobalErrors ) { 1236 | return true; 1237 | } 1238 | QUnit.pushFailure( error, filePath + ":" + linerNr ); 1239 | } else { 1240 | QUnit.test( "global failure", extend( function() { 1241 | QUnit.pushFailure( error, filePath + ":" + linerNr ); 1242 | }, { validTest: validTest } ) ); 1243 | } 1244 | return false; 1245 | } 1246 | 1247 | return ret; 1248 | }; 1249 | 1250 | function done() { 1251 | config.autorun = true; 1252 | 1253 | // Log the last module results 1254 | if ( config.currentModule ) { 1255 | runLoggingCallbacks( "moduleDone", QUnit, { 1256 | name: config.currentModule, 1257 | failed: config.moduleStats.bad, 1258 | passed: config.moduleStats.all - config.moduleStats.bad, 1259 | total: config.moduleStats.all 1260 | }); 1261 | } 1262 | delete config.previousModule; 1263 | 1264 | var i, key, 1265 | banner = id( "qunit-banner" ), 1266 | tests = id( "qunit-tests" ), 1267 | runtime = +new Date() - config.started, 1268 | passed = config.stats.all - config.stats.bad, 1269 | html = [ 1270 | "Tests completed in ", 1271 | runtime, 1272 | " milliseconds.
    ", 1273 | "", 1274 | passed, 1275 | " assertions of ", 1276 | config.stats.all, 1277 | " passed, ", 1278 | config.stats.bad, 1279 | " failed." 1280 | ].join( "" ); 1281 | 1282 | if ( banner ) { 1283 | banner.className = ( config.stats.bad ? "qunit-fail" : "qunit-pass" ); 1284 | } 1285 | 1286 | if ( tests ) { 1287 | id( "qunit-testresult" ).innerHTML = html; 1288 | } 1289 | 1290 | if ( config.altertitle && typeof document !== "undefined" && document.title ) { 1291 | // show ✖ for good, ✔ for bad suite result in title 1292 | // use escape sequences in case file gets loaded with non-utf-8-charset 1293 | document.title = [ 1294 | ( config.stats.bad ? "\u2716" : "\u2714" ), 1295 | document.title.replace( /^[\u2714\u2716] /i, "" ) 1296 | ].join( " " ); 1297 | } 1298 | 1299 | // clear own sessionStorage items if all tests passed 1300 | if ( config.reorder && defined.sessionStorage && config.stats.bad === 0 ) { 1301 | // `key` & `i` initialized at top of scope 1302 | for ( i = 0; i < sessionStorage.length; i++ ) { 1303 | key = sessionStorage.key( i++ ); 1304 | if ( key.indexOf( "qunit-test-" ) === 0 ) { 1305 | sessionStorage.removeItem( key ); 1306 | } 1307 | } 1308 | } 1309 | 1310 | // scroll back to top to show results 1311 | if ( window.scrollTo ) { 1312 | window.scrollTo(0, 0); 1313 | } 1314 | 1315 | runLoggingCallbacks( "done", QUnit, { 1316 | failed: config.stats.bad, 1317 | passed: passed, 1318 | total: config.stats.all, 1319 | runtime: runtime 1320 | }); 1321 | } 1322 | 1323 | /** @return Boolean: true if this test should be ran */ 1324 | function validTest( test ) { 1325 | var include, 1326 | filter = config.filter && config.filter.toLowerCase(), 1327 | module = config.module && config.module.toLowerCase(), 1328 | fullName = (test.module + ": " + test.testName).toLowerCase(); 1329 | 1330 | // Internally-generated tests are always valid 1331 | if ( test.callback && test.callback.validTest === validTest ) { 1332 | delete test.callback.validTest; 1333 | return true; 1334 | } 1335 | 1336 | if ( config.testNumber ) { 1337 | return test.testNumber === config.testNumber; 1338 | } 1339 | 1340 | if ( module && ( !test.module || test.module.toLowerCase() !== module ) ) { 1341 | return false; 1342 | } 1343 | 1344 | if ( !filter ) { 1345 | return true; 1346 | } 1347 | 1348 | include = filter.charAt( 0 ) !== "!"; 1349 | if ( !include ) { 1350 | filter = filter.slice( 1 ); 1351 | } 1352 | 1353 | // If the filter matches, we need to honour include 1354 | if ( fullName.indexOf( filter ) !== -1 ) { 1355 | return include; 1356 | } 1357 | 1358 | // Otherwise, do the opposite 1359 | return !include; 1360 | } 1361 | 1362 | // so far supports only Firefox, Chrome and Opera (buggy), Safari (for real exceptions) 1363 | // Later Safari and IE10 are supposed to support error.stack as well 1364 | // See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack 1365 | function extractStacktrace( e, offset ) { 1366 | offset = offset === undefined ? 3 : offset; 1367 | 1368 | var stack, include, i; 1369 | 1370 | if ( e.stacktrace ) { 1371 | // Opera 1372 | return e.stacktrace.split( "\n" )[ offset + 3 ]; 1373 | } else if ( e.stack ) { 1374 | // Firefox, Chrome 1375 | stack = e.stack.split( "\n" ); 1376 | if (/^error$/i.test( stack[0] ) ) { 1377 | stack.shift(); 1378 | } 1379 | if ( fileName ) { 1380 | include = []; 1381 | for ( i = offset; i < stack.length; i++ ) { 1382 | if ( stack[ i ].indexOf( fileName ) !== -1 ) { 1383 | break; 1384 | } 1385 | include.push( stack[ i ] ); 1386 | } 1387 | if ( include.length ) { 1388 | return include.join( "\n" ); 1389 | } 1390 | } 1391 | return stack[ offset ]; 1392 | } else if ( e.sourceURL ) { 1393 | // Safari, PhantomJS 1394 | // hopefully one day Safari provides actual stacktraces 1395 | // exclude useless self-reference for generated Error objects 1396 | if ( /qunit.js$/.test( e.sourceURL ) ) { 1397 | return; 1398 | } 1399 | // for actual exceptions, this is useful 1400 | return e.sourceURL + ":" + e.line; 1401 | } 1402 | } 1403 | function sourceFromStacktrace( offset ) { 1404 | try { 1405 | throw new Error(); 1406 | } catch ( e ) { 1407 | return extractStacktrace( e, offset ); 1408 | } 1409 | } 1410 | 1411 | /** 1412 | * Escape text for attribute or text content. 1413 | */ 1414 | function escapeText( s ) { 1415 | if ( !s ) { 1416 | return ""; 1417 | } 1418 | s = s + ""; 1419 | // Both single quotes and double quotes (for attributes) 1420 | return s.replace( /['"<>&]/g, function( s ) { 1421 | switch( s ) { 1422 | case "'": 1423 | return "'"; 1424 | case "\"": 1425 | return """; 1426 | case "<": 1427 | return "<"; 1428 | case ">": 1429 | return ">"; 1430 | case "&": 1431 | return "&"; 1432 | } 1433 | }); 1434 | } 1435 | 1436 | function synchronize( callback, last ) { 1437 | config.queue.push( callback ); 1438 | 1439 | if ( config.autorun && !config.blocking ) { 1440 | process( last ); 1441 | } 1442 | } 1443 | 1444 | function process( last ) { 1445 | function next() { 1446 | process( last ); 1447 | } 1448 | var start = new Date().getTime(); 1449 | config.depth = config.depth ? config.depth + 1 : 1; 1450 | 1451 | while ( config.queue.length && !config.blocking ) { 1452 | if ( !defined.setTimeout || config.updateRate <= 0 || ( ( new Date().getTime() - start ) < config.updateRate ) ) { 1453 | config.queue.shift()(); 1454 | } else { 1455 | setTimeout( next, 13 ); 1456 | break; 1457 | } 1458 | } 1459 | config.depth--; 1460 | if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) { 1461 | done(); 1462 | } 1463 | } 1464 | 1465 | function saveGlobal() { 1466 | config.pollution = []; 1467 | 1468 | if ( config.noglobals ) { 1469 | for ( var key in window ) { 1470 | if ( hasOwn.call( window, key ) ) { 1471 | // in Opera sometimes DOM element ids show up here, ignore them 1472 | if ( /^qunit-test-output/.test( key ) ) { 1473 | continue; 1474 | } 1475 | config.pollution.push( key ); 1476 | } 1477 | } 1478 | } 1479 | } 1480 | 1481 | function checkPollution() { 1482 | var newGlobals, 1483 | deletedGlobals, 1484 | old = config.pollution; 1485 | 1486 | saveGlobal(); 1487 | 1488 | newGlobals = diff( config.pollution, old ); 1489 | if ( newGlobals.length > 0 ) { 1490 | QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join(", ") ); 1491 | } 1492 | 1493 | deletedGlobals = diff( old, config.pollution ); 1494 | if ( deletedGlobals.length > 0 ) { 1495 | QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join(", ") ); 1496 | } 1497 | } 1498 | 1499 | // returns a new Array with the elements that are in a but not in b 1500 | function diff( a, b ) { 1501 | var i, j, 1502 | result = a.slice(); 1503 | 1504 | for ( i = 0; i < result.length; i++ ) { 1505 | for ( j = 0; j < b.length; j++ ) { 1506 | if ( result[i] === b[j] ) { 1507 | result.splice( i, 1 ); 1508 | i--; 1509 | break; 1510 | } 1511 | } 1512 | } 1513 | return result; 1514 | } 1515 | 1516 | function extend( a, b ) { 1517 | for ( var prop in b ) { 1518 | if ( hasOwn.call( b, prop ) ) { 1519 | // Avoid "Member not found" error in IE8 caused by messing with window.constructor 1520 | if ( !( prop === "constructor" && a === window ) ) { 1521 | if ( b[ prop ] === undefined ) { 1522 | delete a[ prop ]; 1523 | } else { 1524 | a[ prop ] = b[ prop ]; 1525 | } 1526 | } 1527 | } 1528 | } 1529 | 1530 | return a; 1531 | } 1532 | 1533 | /** 1534 | * @param {HTMLElement} elem 1535 | * @param {string} type 1536 | * @param {Function} fn 1537 | */ 1538 | function addEvent( elem, type, fn ) { 1539 | // Standards-based browsers 1540 | if ( elem.addEventListener ) { 1541 | elem.addEventListener( type, fn, false ); 1542 | // IE 1543 | } else { 1544 | elem.attachEvent( "on" + type, fn ); 1545 | } 1546 | } 1547 | 1548 | /** 1549 | * @param {Array|NodeList} elems 1550 | * @param {string} type 1551 | * @param {Function} fn 1552 | */ 1553 | function addEvents( elems, type, fn ) { 1554 | var i = elems.length; 1555 | while ( i-- ) { 1556 | addEvent( elems[i], type, fn ); 1557 | } 1558 | } 1559 | 1560 | function hasClass( elem, name ) { 1561 | return (" " + elem.className + " ").indexOf(" " + name + " ") > -1; 1562 | } 1563 | 1564 | function addClass( elem, name ) { 1565 | if ( !hasClass( elem, name ) ) { 1566 | elem.className += (elem.className ? " " : "") + name; 1567 | } 1568 | } 1569 | 1570 | function removeClass( elem, name ) { 1571 | var set = " " + elem.className + " "; 1572 | // Class name may appear multiple times 1573 | while ( set.indexOf(" " + name + " ") > -1 ) { 1574 | set = set.replace(" " + name + " " , " "); 1575 | } 1576 | // If possible, trim it for prettiness, but not necessarily 1577 | elem.className = typeof set.trim === "function" ? set.trim() : set.replace(/^\s+|\s+$/g, ""); 1578 | } 1579 | 1580 | function id( name ) { 1581 | return !!( typeof document !== "undefined" && document && document.getElementById ) && 1582 | document.getElementById( name ); 1583 | } 1584 | 1585 | function registerLoggingCallback( key ) { 1586 | return function( callback ) { 1587 | config[key].push( callback ); 1588 | }; 1589 | } 1590 | 1591 | // Supports deprecated method of completely overwriting logging callbacks 1592 | function runLoggingCallbacks( key, scope, args ) { 1593 | var i, callbacks; 1594 | if ( QUnit.hasOwnProperty( key ) ) { 1595 | QUnit[ key ].call(scope, args ); 1596 | } else { 1597 | callbacks = config[ key ]; 1598 | for ( i = 0; i < callbacks.length; i++ ) { 1599 | callbacks[ i ].call( scope, args ); 1600 | } 1601 | } 1602 | } 1603 | 1604 | // Test for equality any JavaScript type. 1605 | // Author: Philippe Rathé 1606 | QUnit.equiv = (function() { 1607 | 1608 | // Call the o related callback with the given arguments. 1609 | function bindCallbacks( o, callbacks, args ) { 1610 | var prop = QUnit.objectType( o ); 1611 | if ( prop ) { 1612 | if ( QUnit.objectType( callbacks[ prop ] ) === "function" ) { 1613 | return callbacks[ prop ].apply( callbacks, args ); 1614 | } else { 1615 | return callbacks[ prop ]; // or undefined 1616 | } 1617 | } 1618 | } 1619 | 1620 | // the real equiv function 1621 | var innerEquiv, 1622 | // stack to decide between skip/abort functions 1623 | callers = [], 1624 | // stack to avoiding loops from circular referencing 1625 | parents = [], 1626 | parentsB = [], 1627 | 1628 | getProto = Object.getPrototypeOf || function ( obj ) { 1629 | /*jshint camelcase:false */ 1630 | return obj.__proto__; 1631 | }, 1632 | callbacks = (function () { 1633 | 1634 | // for string, boolean, number and null 1635 | function useStrictEquality( b, a ) { 1636 | /*jshint eqeqeq:false */ 1637 | if ( b instanceof a.constructor || a instanceof b.constructor ) { 1638 | // to catch short annotation VS 'new' annotation of a 1639 | // declaration 1640 | // e.g. var i = 1; 1641 | // var j = new Number(1); 1642 | return a == b; 1643 | } else { 1644 | return a === b; 1645 | } 1646 | } 1647 | 1648 | return { 1649 | "string": useStrictEquality, 1650 | "boolean": useStrictEquality, 1651 | "number": useStrictEquality, 1652 | "null": useStrictEquality, 1653 | "undefined": useStrictEquality, 1654 | 1655 | "nan": function( b ) { 1656 | return isNaN( b ); 1657 | }, 1658 | 1659 | "date": function( b, a ) { 1660 | return QUnit.objectType( b ) === "date" && a.valueOf() === b.valueOf(); 1661 | }, 1662 | 1663 | "regexp": function( b, a ) { 1664 | return QUnit.objectType( b ) === "regexp" && 1665 | // the regex itself 1666 | a.source === b.source && 1667 | // and its modifiers 1668 | a.global === b.global && 1669 | // (gmi) ... 1670 | a.ignoreCase === b.ignoreCase && 1671 | a.multiline === b.multiline && 1672 | a.sticky === b.sticky; 1673 | }, 1674 | 1675 | // - skip when the property is a method of an instance (OOP) 1676 | // - abort otherwise, 1677 | // initial === would have catch identical references anyway 1678 | "function": function() { 1679 | var caller = callers[callers.length - 1]; 1680 | return caller !== Object && typeof caller !== "undefined"; 1681 | }, 1682 | 1683 | "array": function( b, a ) { 1684 | var i, j, len, loop, aCircular, bCircular; 1685 | 1686 | // b could be an object literal here 1687 | if ( QUnit.objectType( b ) !== "array" ) { 1688 | return false; 1689 | } 1690 | 1691 | len = a.length; 1692 | if ( len !== b.length ) { 1693 | // safe and faster 1694 | return false; 1695 | } 1696 | 1697 | // track reference to avoid circular references 1698 | parents.push( a ); 1699 | parentsB.push( b ); 1700 | for ( i = 0; i < len; i++ ) { 1701 | loop = false; 1702 | for ( j = 0; j < parents.length; j++ ) { 1703 | aCircular = parents[j] === a[i]; 1704 | bCircular = parentsB[j] === b[i]; 1705 | if ( aCircular || bCircular ) { 1706 | if ( a[i] === b[i] || aCircular && bCircular ) { 1707 | loop = true; 1708 | } else { 1709 | parents.pop(); 1710 | parentsB.pop(); 1711 | return false; 1712 | } 1713 | } 1714 | } 1715 | if ( !loop && !innerEquiv(a[i], b[i]) ) { 1716 | parents.pop(); 1717 | parentsB.pop(); 1718 | return false; 1719 | } 1720 | } 1721 | parents.pop(); 1722 | parentsB.pop(); 1723 | return true; 1724 | }, 1725 | 1726 | "object": function( b, a ) { 1727 | /*jshint forin:false */ 1728 | var i, j, loop, aCircular, bCircular, 1729 | // Default to true 1730 | eq = true, 1731 | aProperties = [], 1732 | bProperties = []; 1733 | 1734 | // comparing constructors is more strict than using 1735 | // instanceof 1736 | if ( a.constructor !== b.constructor ) { 1737 | // Allow objects with no prototype to be equivalent to 1738 | // objects with Object as their constructor. 1739 | if ( !(( getProto(a) === null && getProto(b) === Object.prototype ) || 1740 | ( getProto(b) === null && getProto(a) === Object.prototype ) ) ) { 1741 | return false; 1742 | } 1743 | } 1744 | 1745 | // stack constructor before traversing properties 1746 | callers.push( a.constructor ); 1747 | 1748 | // track reference to avoid circular references 1749 | parents.push( a ); 1750 | parentsB.push( b ); 1751 | 1752 | // be strict: don't ensure hasOwnProperty and go deep 1753 | for ( i in a ) { 1754 | loop = false; 1755 | for ( j = 0; j < parents.length; j++ ) { 1756 | aCircular = parents[j] === a[i]; 1757 | bCircular = parentsB[j] === b[i]; 1758 | if ( aCircular || bCircular ) { 1759 | if ( a[i] === b[i] || aCircular && bCircular ) { 1760 | loop = true; 1761 | } else { 1762 | eq = false; 1763 | break; 1764 | } 1765 | } 1766 | } 1767 | aProperties.push(i); 1768 | if ( !loop && !innerEquiv(a[i], b[i]) ) { 1769 | eq = false; 1770 | break; 1771 | } 1772 | } 1773 | 1774 | parents.pop(); 1775 | parentsB.pop(); 1776 | callers.pop(); // unstack, we are done 1777 | 1778 | for ( i in b ) { 1779 | bProperties.push( i ); // collect b's properties 1780 | } 1781 | 1782 | // Ensures identical properties name 1783 | return eq && innerEquiv( aProperties.sort(), bProperties.sort() ); 1784 | } 1785 | }; 1786 | }()); 1787 | 1788 | innerEquiv = function() { // can take multiple arguments 1789 | var args = [].slice.apply( arguments ); 1790 | if ( args.length < 2 ) { 1791 | return true; // end transition 1792 | } 1793 | 1794 | return (function( a, b ) { 1795 | if ( a === b ) { 1796 | return true; // catch the most you can 1797 | } else if ( a === null || b === null || typeof a === "undefined" || 1798 | typeof b === "undefined" || 1799 | QUnit.objectType(a) !== QUnit.objectType(b) ) { 1800 | return false; // don't lose time with error prone cases 1801 | } else { 1802 | return bindCallbacks(a, callbacks, [ b, a ]); 1803 | } 1804 | 1805 | // apply transition with (1..n) arguments 1806 | }( args[0], args[1] ) && innerEquiv.apply( this, args.splice(1, args.length - 1 )) ); 1807 | }; 1808 | 1809 | return innerEquiv; 1810 | }()); 1811 | 1812 | /** 1813 | * jsDump Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | 1814 | * http://flesler.blogspot.com Licensed under BSD 1815 | * (http://www.opensource.org/licenses/bsd-license.php) Date: 5/15/2008 1816 | * 1817 | * @projectDescription Advanced and extensible data dumping for Javascript. 1818 | * @version 1.0.0 1819 | * @author Ariel Flesler 1820 | * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html} 1821 | */ 1822 | QUnit.jsDump = (function() { 1823 | function quote( str ) { 1824 | return "\"" + str.toString().replace( /"/g, "\\\"" ) + "\""; 1825 | } 1826 | function literal( o ) { 1827 | return o + ""; 1828 | } 1829 | function join( pre, arr, post ) { 1830 | var s = jsDump.separator(), 1831 | base = jsDump.indent(), 1832 | inner = jsDump.indent(1); 1833 | if ( arr.join ) { 1834 | arr = arr.join( "," + s + inner ); 1835 | } 1836 | if ( !arr ) { 1837 | return pre + post; 1838 | } 1839 | return [ pre, inner + arr, base + post ].join(s); 1840 | } 1841 | function array( arr, stack ) { 1842 | var i = arr.length, ret = new Array(i); 1843 | this.up(); 1844 | while ( i-- ) { 1845 | ret[i] = this.parse( arr[i] , undefined , stack); 1846 | } 1847 | this.down(); 1848 | return join( "[", ret, "]" ); 1849 | } 1850 | 1851 | var reName = /^function (\w+)/, 1852 | jsDump = { 1853 | // type is used mostly internally, you can fix a (custom)type in advance 1854 | parse: function( obj, type, stack ) { 1855 | stack = stack || [ ]; 1856 | var inStack, res, 1857 | parser = this.parsers[ type || this.typeOf(obj) ]; 1858 | 1859 | type = typeof parser; 1860 | inStack = inArray( obj, stack ); 1861 | 1862 | if ( inStack !== -1 ) { 1863 | return "recursion(" + (inStack - stack.length) + ")"; 1864 | } 1865 | if ( type === "function" ) { 1866 | stack.push( obj ); 1867 | res = parser.call( this, obj, stack ); 1868 | stack.pop(); 1869 | return res; 1870 | } 1871 | return ( type === "string" ) ? parser : this.parsers.error; 1872 | }, 1873 | typeOf: function( obj ) { 1874 | var type; 1875 | if ( obj === null ) { 1876 | type = "null"; 1877 | } else if ( typeof obj === "undefined" ) { 1878 | type = "undefined"; 1879 | } else if ( QUnit.is( "regexp", obj) ) { 1880 | type = "regexp"; 1881 | } else if ( QUnit.is( "date", obj) ) { 1882 | type = "date"; 1883 | } else if ( QUnit.is( "function", obj) ) { 1884 | type = "function"; 1885 | } else if ( typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined" ) { 1886 | type = "window"; 1887 | } else if ( obj.nodeType === 9 ) { 1888 | type = "document"; 1889 | } else if ( obj.nodeType ) { 1890 | type = "node"; 1891 | } else if ( 1892 | // native arrays 1893 | toString.call( obj ) === "[object Array]" || 1894 | // NodeList objects 1895 | ( typeof obj.length === "number" && typeof obj.item !== "undefined" && ( obj.length ? obj.item(0) === obj[0] : ( obj.item( 0 ) === null && typeof obj[0] === "undefined" ) ) ) 1896 | ) { 1897 | type = "array"; 1898 | } else if ( obj.constructor === Error.prototype.constructor ) { 1899 | type = "error"; 1900 | } else { 1901 | type = typeof obj; 1902 | } 1903 | return type; 1904 | }, 1905 | separator: function() { 1906 | return this.multiline ? this.HTML ? "
    " : "\n" : this.HTML ? " " : " "; 1907 | }, 1908 | // extra can be a number, shortcut for increasing-calling-decreasing 1909 | indent: function( extra ) { 1910 | if ( !this.multiline ) { 1911 | return ""; 1912 | } 1913 | var chr = this.indentChar; 1914 | if ( this.HTML ) { 1915 | chr = chr.replace( /\t/g, " " ).replace( / /g, " " ); 1916 | } 1917 | return new Array( this.depth + ( extra || 0 ) ).join(chr); 1918 | }, 1919 | up: function( a ) { 1920 | this.depth += a || 1; 1921 | }, 1922 | down: function( a ) { 1923 | this.depth -= a || 1; 1924 | }, 1925 | setParser: function( name, parser ) { 1926 | this.parsers[name] = parser; 1927 | }, 1928 | // The next 3 are exposed so you can use them 1929 | quote: quote, 1930 | literal: literal, 1931 | join: join, 1932 | // 1933 | depth: 1, 1934 | // This is the list of parsers, to modify them, use jsDump.setParser 1935 | parsers: { 1936 | window: "[Window]", 1937 | document: "[Document]", 1938 | error: function(error) { 1939 | return "Error(\"" + error.message + "\")"; 1940 | }, 1941 | unknown: "[Unknown]", 1942 | "null": "null", 1943 | "undefined": "undefined", 1944 | "function": function( fn ) { 1945 | var ret = "function", 1946 | // functions never have name in IE 1947 | name = "name" in fn ? fn.name : (reName.exec(fn) || [])[1]; 1948 | 1949 | if ( name ) { 1950 | ret += " " + name; 1951 | } 1952 | ret += "( "; 1953 | 1954 | ret = [ ret, QUnit.jsDump.parse( fn, "functionArgs" ), "){" ].join( "" ); 1955 | return join( ret, QUnit.jsDump.parse(fn,"functionCode" ), "}" ); 1956 | }, 1957 | array: array, 1958 | nodelist: array, 1959 | "arguments": array, 1960 | object: function( map, stack ) { 1961 | /*jshint forin:false */ 1962 | var ret = [ ], keys, key, val, i; 1963 | QUnit.jsDump.up(); 1964 | keys = []; 1965 | for ( key in map ) { 1966 | keys.push( key ); 1967 | } 1968 | keys.sort(); 1969 | for ( i = 0; i < keys.length; i++ ) { 1970 | key = keys[ i ]; 1971 | val = map[ key ]; 1972 | ret.push( QUnit.jsDump.parse( key, "key" ) + ": " + QUnit.jsDump.parse( val, undefined, stack ) ); 1973 | } 1974 | QUnit.jsDump.down(); 1975 | return join( "{", ret, "}" ); 1976 | }, 1977 | node: function( node ) { 1978 | var len, i, val, 1979 | open = QUnit.jsDump.HTML ? "<" : "<", 1980 | close = QUnit.jsDump.HTML ? ">" : ">", 1981 | tag = node.nodeName.toLowerCase(), 1982 | ret = open + tag, 1983 | attrs = node.attributes; 1984 | 1985 | if ( attrs ) { 1986 | for ( i = 0, len = attrs.length; i < len; i++ ) { 1987 | val = attrs[i].nodeValue; 1988 | // IE6 includes all attributes in .attributes, even ones not explicitly set. 1989 | // Those have values like undefined, null, 0, false, "" or "inherit". 1990 | if ( val && val !== "inherit" ) { 1991 | ret += " " + attrs[i].nodeName + "=" + QUnit.jsDump.parse( val, "attribute" ); 1992 | } 1993 | } 1994 | } 1995 | ret += close; 1996 | 1997 | // Show content of TextNode or CDATASection 1998 | if ( node.nodeType === 3 || node.nodeType === 4 ) { 1999 | ret += node.nodeValue; 2000 | } 2001 | 2002 | return ret + open + "/" + tag + close; 2003 | }, 2004 | // function calls it internally, it's the arguments part of the function 2005 | functionArgs: function( fn ) { 2006 | var args, 2007 | l = fn.length; 2008 | 2009 | if ( !l ) { 2010 | return ""; 2011 | } 2012 | 2013 | args = new Array(l); 2014 | while ( l-- ) { 2015 | // 97 is 'a' 2016 | args[l] = String.fromCharCode(97+l); 2017 | } 2018 | return " " + args.join( ", " ) + " "; 2019 | }, 2020 | // object calls it internally, the key part of an item in a map 2021 | key: quote, 2022 | // function calls it internally, it's the content of the function 2023 | functionCode: "[code]", 2024 | // node calls it internally, it's an html attribute value 2025 | attribute: quote, 2026 | string: quote, 2027 | date: quote, 2028 | regexp: literal, 2029 | number: literal, 2030 | "boolean": literal 2031 | }, 2032 | // if true, entities are escaped ( <, >, \t, space and \n ) 2033 | HTML: false, 2034 | // indentation unit 2035 | indentChar: " ", 2036 | // if true, items in a collection, are separated by a \n, else just a space. 2037 | multiline: true 2038 | }; 2039 | 2040 | return jsDump; 2041 | }()); 2042 | 2043 | // from jquery.js 2044 | function inArray( elem, array ) { 2045 | if ( array.indexOf ) { 2046 | return array.indexOf( elem ); 2047 | } 2048 | 2049 | for ( var i = 0, length = array.length; i < length; i++ ) { 2050 | if ( array[ i ] === elem ) { 2051 | return i; 2052 | } 2053 | } 2054 | 2055 | return -1; 2056 | } 2057 | 2058 | /* 2059 | * Javascript Diff Algorithm 2060 | * By John Resig (http://ejohn.org/) 2061 | * Modified by Chu Alan "sprite" 2062 | * 2063 | * Released under the MIT license. 2064 | * 2065 | * More Info: 2066 | * http://ejohn.org/projects/javascript-diff-algorithm/ 2067 | * 2068 | * Usage: QUnit.diff(expected, actual) 2069 | * 2070 | * QUnit.diff( "the quick brown fox jumped over", "the quick fox jumps over" ) == "the quick brown fox jumped jumps over" 2071 | */ 2072 | QUnit.diff = (function() { 2073 | /*jshint eqeqeq:false, eqnull:true */ 2074 | function diff( o, n ) { 2075 | var i, 2076 | ns = {}, 2077 | os = {}; 2078 | 2079 | for ( i = 0; i < n.length; i++ ) { 2080 | if ( !hasOwn.call( ns, n[i] ) ) { 2081 | ns[ n[i] ] = { 2082 | rows: [], 2083 | o: null 2084 | }; 2085 | } 2086 | ns[ n[i] ].rows.push( i ); 2087 | } 2088 | 2089 | for ( i = 0; i < o.length; i++ ) { 2090 | if ( !hasOwn.call( os, o[i] ) ) { 2091 | os[ o[i] ] = { 2092 | rows: [], 2093 | n: null 2094 | }; 2095 | } 2096 | os[ o[i] ].rows.push( i ); 2097 | } 2098 | 2099 | for ( i in ns ) { 2100 | if ( hasOwn.call( ns, i ) ) { 2101 | if ( ns[i].rows.length === 1 && hasOwn.call( os, i ) && os[i].rows.length === 1 ) { 2102 | n[ ns[i].rows[0] ] = { 2103 | text: n[ ns[i].rows[0] ], 2104 | row: os[i].rows[0] 2105 | }; 2106 | o[ os[i].rows[0] ] = { 2107 | text: o[ os[i].rows[0] ], 2108 | row: ns[i].rows[0] 2109 | }; 2110 | } 2111 | } 2112 | } 2113 | 2114 | for ( i = 0; i < n.length - 1; i++ ) { 2115 | if ( n[i].text != null && n[ i + 1 ].text == null && n[i].row + 1 < o.length && o[ n[i].row + 1 ].text == null && 2116 | n[ i + 1 ] == o[ n[i].row + 1 ] ) { 2117 | 2118 | n[ i + 1 ] = { 2119 | text: n[ i + 1 ], 2120 | row: n[i].row + 1 2121 | }; 2122 | o[ n[i].row + 1 ] = { 2123 | text: o[ n[i].row + 1 ], 2124 | row: i + 1 2125 | }; 2126 | } 2127 | } 2128 | 2129 | for ( i = n.length - 1; i > 0; i-- ) { 2130 | if ( n[i].text != null && n[ i - 1 ].text == null && n[i].row > 0 && o[ n[i].row - 1 ].text == null && 2131 | n[ i - 1 ] == o[ n[i].row - 1 ]) { 2132 | 2133 | n[ i - 1 ] = { 2134 | text: n[ i - 1 ], 2135 | row: n[i].row - 1 2136 | }; 2137 | o[ n[i].row - 1 ] = { 2138 | text: o[ n[i].row - 1 ], 2139 | row: i - 1 2140 | }; 2141 | } 2142 | } 2143 | 2144 | return { 2145 | o: o, 2146 | n: n 2147 | }; 2148 | } 2149 | 2150 | return function( o, n ) { 2151 | o = o.replace( /\s+$/, "" ); 2152 | n = n.replace( /\s+$/, "" ); 2153 | 2154 | var i, pre, 2155 | str = "", 2156 | out = diff( o === "" ? [] : o.split(/\s+/), n === "" ? [] : n.split(/\s+/) ), 2157 | oSpace = o.match(/\s+/g), 2158 | nSpace = n.match(/\s+/g); 2159 | 2160 | if ( oSpace == null ) { 2161 | oSpace = [ " " ]; 2162 | } 2163 | else { 2164 | oSpace.push( " " ); 2165 | } 2166 | 2167 | if ( nSpace == null ) { 2168 | nSpace = [ " " ]; 2169 | } 2170 | else { 2171 | nSpace.push( " " ); 2172 | } 2173 | 2174 | if ( out.n.length === 0 ) { 2175 | for ( i = 0; i < out.o.length; i++ ) { 2176 | str += "" + out.o[i] + oSpace[i] + ""; 2177 | } 2178 | } 2179 | else { 2180 | if ( out.n[0].text == null ) { 2181 | for ( n = 0; n < out.o.length && out.o[n].text == null; n++ ) { 2182 | str += "" + out.o[n] + oSpace[n] + ""; 2183 | } 2184 | } 2185 | 2186 | for ( i = 0; i < out.n.length; i++ ) { 2187 | if (out.n[i].text == null) { 2188 | str += "" + out.n[i] + nSpace[i] + ""; 2189 | } 2190 | else { 2191 | // `pre` initialized at top of scope 2192 | pre = ""; 2193 | 2194 | for ( n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++ ) { 2195 | pre += "" + out.o[n] + oSpace[n] + ""; 2196 | } 2197 | str += " " + out.n[i].text + nSpace[i] + pre; 2198 | } 2199 | } 2200 | } 2201 | 2202 | return str; 2203 | }; 2204 | }()); 2205 | 2206 | // for CommonJS environments, export everything 2207 | if ( typeof exports !== "undefined" ) { 2208 | extend( exports, QUnit.constructor.prototype ); 2209 | } 2210 | 2211 | // get at whatever the global object is, like window in browsers 2212 | }( (function() {return this;}.call()) )); 2213 | --------------------------------------------------------------------------------