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