├── .gitignore
├── test
├── js
│ ├── .eslintrc
│ ├── test.js
│ └── qunit.js
├── pubsub.html
└── css
│ └── qunit.css
├── .travis.yml
├── bower.json
├── package.json
├── .eslintrc
├── pubsub.js
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | npm-debug.log
2 | node_modules*
--------------------------------------------------------------------------------
/test/js/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "globals": {
3 | "test" : true,
4 | "asyncTest" : true,
5 | "ok" : true,
6 | "start" : true,
7 | "equal" : true,
8 | "deepEqual" : true
9 | }
10 | }
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false # http://docs.travis-ci.com/user/workers/container-based-infrastructure/
2 | language: node_js
3 | node_js:
4 | - 0.12
5 | - 4
6 | - 5
7 |
8 | before_install:
9 | - mkdir node_modules;
10 |
11 | notifications:
12 | email:
13 | - wdsahadar@gmail.com
14 |
15 | script: "npm test"
--------------------------------------------------------------------------------
/test/pubsub.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Pubsub qunit test
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pubsub-advanced",
3 | "main": "pubsub-advanced",
4 | "version": "1.5.1",
5 | "homepage": "https://github.com/Sahadar/pubsub.js",
6 | "authors": [
7 | "Wojciech Dłubacz "
8 | ],
9 | "description": "JavaScript pubsub implementation with wildcards, inheritance and multisubscriptions",
10 | "keywords": [
11 | "pubsub",
12 | "listener",
13 | "vanilla",
14 | "pub/sub",
15 | "publish/subscribe",
16 | "publish",
17 | "event",
18 | "emitter",
19 | "eventemitter",
20 | "subscribe"
21 | ],
22 | "license": "MIT",
23 | "ignore": [
24 | "**/.*",
25 | "node_modules",
26 | "bower_components",
27 | "test",
28 | "tests"
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pubsub.js",
3 | "description": "Vanilla JS Pubsub implementation with wildcards and inheritance",
4 | "version": "1.5.1",
5 | "author": "Wojciech Dłubacz ",
6 | "directories" : {
7 | "lib" : ".",
8 | "test" : "test"
9 | },
10 | "contributors": [
11 | "nehachaudhary "
12 | ],
13 | "repository": {
14 | "type": "git",
15 | "url": "git@github.com:Sahadar/pubsub.js.git"
16 | },
17 | "keywords": [
18 | "pubsub",
19 | "listener",
20 | "vanilla",
21 | "pub/sub",
22 | "publish/subscribe",
23 | "publish",
24 | "event",
25 | "emitter",
26 | "eventemitter",
27 | "subscribe"
28 | ],
29 | "devDependencies": {
30 | "qunit": "~0.7.7",
31 | "qunit-phantomjs-runner": "~2.1.0"
32 | },
33 | "engines": {"node": ">= 0.6.x"},
34 | "main": "pubsub.js",
35 | "scripts": {
36 | "test": "phantomjs node_modules/qunit-phantomjs-runner/runner.js test/pubsub.html"
37 | },
38 | "license": "MIT"
39 | }
40 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es6": true
5 | },
6 | "globals": {
7 | "define": true,
8 | "require": true,
9 | "jQuery": true,
10 | "$" : true,
11 | "module" : true,
12 | "pubsub" : true,
13 | "global" : true
14 | },
15 | "extends": "eslint:recommended",
16 | "rules": {
17 | "strict": 1,
18 | "block-scoped-var": 2,
19 | "curly": [2,"all"],
20 | "dot-location": [2,"property"],
21 | "eqeqeq": [2,"smart"],
22 | "no-alert": 2,
23 | "no-console": 0,
24 | "no-caller": 2,
25 | "no-eval": 2,
26 | "no-extend-native": 1,
27 | "no-extra-bind": 2,
28 | "no-fallthrough": 1,
29 | "no-floating-decimal": 1,
30 | "no-implicit-coercion": [1,{"allow": ["!!", "+"]}],
31 | "no-implied-eval": 2,
32 | "no-labels": 2,
33 | "no-lone-blocks": 1,
34 | "no-loop-func": 2,
35 | "no-multi-spaces": [2,{ "exceptions": { "Property": true, "VariableDeclarator": true }}],
36 | "no-multi-str": 2,
37 | "no-native-reassign": 2,
38 | "no-new-func": 2,
39 | "no-new-wrappers": 2,
40 | "no-octal-escape": 2,
41 | "no-proto": 1,
42 | "no-return-assign": [2,"always"],
43 | "no-script-url": 2,
44 | "no-self-compare": 2,
45 | "no-sequences": 2,
46 | "no-throw-literal": 2,
47 | "no-unused-expressions": 2,
48 | "no-unused-vars": 1,
49 | "no-unsafe-negation": 2,
50 | "no-useless-call": 2,
51 | "no-void": 2,
52 | "no-warning-comments": 1,
53 | "no-with": 2,
54 | "radix": 2,
55 | "vars-on-top": 0,
56 | "wrap-iife": [2,"inside"],
57 | "yoda": 2,
58 | "no-shadow-restricted-names": 2,
59 | "no-shadow": 1,
60 | "no-undef-init": 2,
61 | "no-undefined": 2,
62 | "array-bracket-spacing": [2,"never",{}],
63 | "brace-style": [2,"1tbs",{}],
64 | "comma-spacing": 2,
65 | "comma-style": 2,
66 | "computed-property-spacing": 2,
67 | "consistent-this": [1,"self"],
68 | "func-style": [2,"declaration", { "allowArrowFunctions": true }],
69 | "indent": [2,"tab"],
70 | "linebreak-style": [2,"unix"],
71 | "new-parens": 2,
72 | "no-array-constructor": 2,
73 | "no-continue": 1,
74 | "no-lonely-if": 1,
75 | "no-nested-ternary": 2,
76 | "no-new-object": 2,
77 | "no-spaced-func": 2,
78 | "no-trailing-spaces": 2,
79 | "no-unneeded-ternary": 2,
80 | "object-curly-spacing": [2,"always",{}],
81 | "operator-linebreak": [2,"after"],
82 | "padded-blocks": [2,"never"],
83 | "quote-props": [1,"as-needed"],
84 | "semi-spacing": 2,
85 | "semi": [2,"always"],
86 | "keyword-spacing": [2, { "overrides": { "if": { "after": false }, "for": { "after": false }, "while": { "after": false }, "catch": { "after": false} } }],
87 | "space-before-blocks": [2,"always"],
88 | "space-before-function-paren": [2,"never"],
89 | "space-in-parens": [2,"never"],
90 | "space-infix-ops": 2,
91 | "space-unary-ops": [2,{"words":true}],
92 | "arrow-parens": [2,"always"],
93 | "arrow-spacing": [2,{"before":true,"after":true}],
94 | "no-const-assign": 2,
95 | "object-shorthand": [2,"never"],
96 | "no-bitwise": 2
97 | }
98 | }
--------------------------------------------------------------------------------
/test/css/qunit.css:
--------------------------------------------------------------------------------
1 | /**
2 | * QUnit v1.12.0pre-7d65b6b7dea05b3cb1d1d9caebe3ccf5f162126d 2013-02-02 - A JavaScript Unit Testing Framework
3 | *
4 | * http://qunitjs.com
5 | *
6 | * Copyright 2012 jQuery Foundation and other contributors
7 | * Released under the MIT license.
8 | * http://jquery.org/license
9 | */
10 |
11 | /** Font Family and Sizes */
12 |
13 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult {
14 | font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif;
15 | }
16 |
17 | #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
18 | #qunit-tests { font-size: smaller; }
19 |
20 |
21 | /** Resets */
22 |
23 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter {
24 | margin: 0;
25 | padding: 0;
26 | }
27 |
28 |
29 | /** Header */
30 |
31 | #qunit-header {
32 | padding: 0.5em 0 0.5em 1em;
33 |
34 | color: #8699a4;
35 | background-color: #0d3349;
36 |
37 | font-size: 1.5em;
38 | line-height: 1em;
39 | font-weight: normal;
40 |
41 | border-radius: 5px 5px 0 0;
42 | -moz-border-radius: 5px 5px 0 0;
43 | -webkit-border-top-right-radius: 5px;
44 | -webkit-border-top-left-radius: 5px;
45 | }
46 |
47 | #qunit-header a {
48 | text-decoration: none;
49 | color: #c2ccd1;
50 | }
51 |
52 | #qunit-header a:hover,
53 | #qunit-header a:focus {
54 | color: #fff;
55 | }
56 |
57 | #qunit-testrunner-toolbar label {
58 | display: inline-block;
59 | padding: 0 .5em 0 .1em;
60 | }
61 |
62 | #qunit-banner {
63 | height: 5px;
64 | }
65 |
66 | #qunit-testrunner-toolbar {
67 | padding: 0.5em 0 0.5em 2em;
68 | color: #5E740B;
69 | background-color: #eee;
70 | overflow: hidden;
71 | }
72 |
73 | #qunit-userAgent {
74 | padding: 0.5em 0 0.5em 2.5em;
75 | background-color: #2b81af;
76 | color: #fff;
77 | text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
78 | }
79 |
80 | #qunit-modulefilter-container {
81 | float: right;
82 | }
83 |
84 | /** Tests: Pass/Fail */
85 |
86 | #qunit-tests {
87 | list-style-position: inside;
88 | }
89 |
90 | #qunit-tests li {
91 | padding: 0.4em 0.5em 0.4em 2.5em;
92 | border-bottom: 1px solid #fff;
93 | list-style-position: inside;
94 | }
95 |
96 | #qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running {
97 | display: none;
98 | }
99 |
100 | #qunit-tests li strong {
101 | cursor: pointer;
102 | }
103 |
104 | #qunit-tests li a {
105 | padding: 0.5em;
106 | color: #c2ccd1;
107 | text-decoration: none;
108 | }
109 | #qunit-tests li a:hover,
110 | #qunit-tests li a:focus {
111 | color: #000;
112 | }
113 |
114 | #qunit-tests li .runtime {
115 | float: right;
116 | font-size: smaller;
117 | }
118 |
119 | .qunit-assert-list {
120 | margin-top: 0.5em;
121 | padding: 0.5em;
122 |
123 | background-color: #fff;
124 |
125 | border-radius: 5px;
126 | -moz-border-radius: 5px;
127 | -webkit-border-radius: 5px;
128 | }
129 |
130 | .qunit-collapsed {
131 | display: none;
132 | }
133 |
134 | #qunit-tests table {
135 | border-collapse: collapse;
136 | margin-top: .2em;
137 | }
138 |
139 | #qunit-tests th {
140 | text-align: right;
141 | vertical-align: top;
142 | padding: 0 .5em 0 0;
143 | }
144 |
145 | #qunit-tests td {
146 | vertical-align: top;
147 | }
148 |
149 | #qunit-tests pre {
150 | margin: 0;
151 | white-space: pre-wrap;
152 | word-wrap: break-word;
153 | }
154 |
155 | #qunit-tests del {
156 | background-color: #e0f2be;
157 | color: #374e0c;
158 | text-decoration: none;
159 | }
160 |
161 | #qunit-tests ins {
162 | background-color: #ffcaca;
163 | color: #500;
164 | text-decoration: none;
165 | }
166 |
167 | /*** Test Counts */
168 |
169 | #qunit-tests b.counts { color: black; }
170 | #qunit-tests b.passed { color: #5E740B; }
171 | #qunit-tests b.failed { color: #710909; }
172 |
173 | #qunit-tests li li {
174 | padding: 5px;
175 | background-color: #fff;
176 | border-bottom: none;
177 | list-style-position: inside;
178 | }
179 |
180 | /*** Passing Styles */
181 |
182 | #qunit-tests li li.pass {
183 | color: #3c510c;
184 | background-color: #fff;
185 | border-left: 10px solid #C6E746;
186 | }
187 |
188 | #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; }
189 | #qunit-tests .pass .test-name { color: #366097; }
190 |
191 | #qunit-tests .pass .test-actual,
192 | #qunit-tests .pass .test-expected { color: #999999; }
193 |
194 | #qunit-banner.qunit-pass { background-color: #C6E746; }
195 |
196 | /*** Failing Styles */
197 |
198 | #qunit-tests li li.fail {
199 | color: #710909;
200 | background-color: #fff;
201 | border-left: 10px solid #EE5757;
202 | white-space: pre;
203 | }
204 |
205 | #qunit-tests > li:last-child {
206 | border-radius: 0 0 5px 5px;
207 | -moz-border-radius: 0 0 5px 5px;
208 | -webkit-border-bottom-right-radius: 5px;
209 | -webkit-border-bottom-left-radius: 5px;
210 | }
211 |
212 | #qunit-tests .fail { color: #000000; background-color: #EE5757; }
213 | #qunit-tests .fail .test-name,
214 | #qunit-tests .fail .module-name { color: #000000; }
215 |
216 | #qunit-tests .fail .test-actual { color: #EE5757; }
217 | #qunit-tests .fail .test-expected { color: green; }
218 |
219 | #qunit-banner.qunit-fail { background-color: #EE5757; }
220 |
221 |
222 | /** Result */
223 |
224 | #qunit-testresult {
225 | padding: 0.5em 0.5em 0.5em 2.5em;
226 |
227 | color: #2b81af;
228 | background-color: #D2E0E6;
229 |
230 | border-bottom: 1px solid white;
231 | }
232 | #qunit-testresult .module-name {
233 | font-weight: bold;
234 | }
235 |
236 | /** Fixture */
237 |
238 | #qunit-fixture {
239 | position: absolute;
240 | top: -10000px;
241 | left: -10000px;
242 | width: 1000px;
243 | height: 1000px;
244 | }
245 |
--------------------------------------------------------------------------------
/pubsub.js:
--------------------------------------------------------------------------------
1 | (function(scope) {
2 | 'use strict';
3 | var pubsubInstance = null;
4 | var pubsubConfig = null;
5 |
6 | if(typeof pubsub === 'object') {
7 | pubsubConfig = pubsub;
8 | //node.js config from global
9 | } else if(typeof global === 'object' && typeof global.pubsubConfig === 'object') {
10 | pubsubConfig = global.pubsubConfig;
11 | }
12 |
13 | function Pubsub(config) {
14 | var _eventObject = {};
15 | var options = {
16 | separator : (config && config.separator) ? config.separator : '/',
17 | recurrent : (config && typeof config.recurrent === 'boolean') ? config.recurrent : (false),
18 | depth : (config && typeof config.depth === 'number') ? config.depth : null,
19 | async : (config && typeof config.async === 'boolean') ? config.async : (false),
20 | context : (config && config.context) ? config.context : null,
21 | log : (config && config.log) ? config.log : (false)
22 | };
23 |
24 | function forEach(dataArray, callback) {
25 | var i = 0,
26 | arrayLength = dataArray.length;
27 |
28 | for(i = 0; i < arrayLength; i++) {
29 | callback(i, dataArray[i]);
30 | }
31 | }
32 |
33 | function executeCallback(subscriptions, args, async) {
34 | async = (typeof async === 'boolean') ? async : options.async;
35 | if(!subscriptions.length) {
36 | return;
37 | }
38 |
39 | // clone array - callbacks can unsubscribe other subscriptions
40 | // reduces a lot performance but is safe
41 | var executedSubscriptions = subscriptions.slice();
42 |
43 | forEach(executedSubscriptions, function(subscriptionId, subscription) {
44 | if(typeof subscription === 'object' && executedSubscriptions.hasOwnProperty(subscriptionId)) {
45 | if(async) {
46 | setTimeout(function() {
47 | subscription.callback.apply(subscription.context, args);
48 | }, 4);
49 | } else {
50 | subscription.callback.apply(subscription.context, args);
51 | }
52 | }
53 | });
54 | }
55 |
56 | function executePublishWildcard(nsObject, args) {
57 | var nsElement;
58 | for(nsElement in nsObject) {
59 | if(nsElement[0] !== '_' && nsObject.hasOwnProperty(nsElement)) {
60 | executeCallback(nsObject[nsElement]._events, args);
61 | }
62 | }
63 | }
64 |
65 | function publish(nsObject, args, parts, params) {
66 | // work on copy - not on reference
67 | parts = parts.slice();
68 |
69 | var iPart = parts.shift();
70 | var depth = params.depth;
71 | var async = params.async;
72 | var partsLength = params.partsLength;
73 | var recurrent = params.recurrent;
74 | var partNumber = (partsLength - parts.length);
75 |
76 | // parts is empty
77 | if(!iPart) {
78 | executeCallback(nsObject._events, args, async);
79 | return;
80 | }
81 | // handle subscribe wildcard
82 | if(typeof nsObject['*'] !== 'undefined') {
83 | publish(nsObject['*'], args, parts, params);
84 | }
85 |
86 | // handle publish wildcard
87 | if(iPart === '*') {
88 | executePublishWildcard(nsObject, args, async);
89 | }
90 |
91 | // no namespace = leave publish
92 | if(typeof nsObject[iPart] === "undefined") {
93 | if(params.log) {
94 | console.warn('There is no ' + params.nsString + ' subscription');
95 | }
96 | return;
97 | }
98 |
99 | nsObject = nsObject[iPart];
100 |
101 | if(recurrent === true && typeof depth !== 'number') { //depth is not defined
102 | executeCallback(nsObject._events, args, async);
103 | if(parts.length === 0) {
104 | return;
105 | }
106 | } else if(recurrent === true && typeof depth === 'number' && partNumber >= (partsLength - depth)) { //if depth is defined
107 | executeCallback(nsObject._events, args, async);
108 | }
109 |
110 | publish(nsObject, args, parts, params);
111 | }
112 |
113 | function executeSubscribeWildcard(nsObject, args, params) {
114 | var parts = params.parts;
115 | var async = params.async;
116 | var nextPart = null;
117 |
118 | if(parts.length === 0) {
119 | executeCallback(nsObject._events, args, async);
120 | } else {
121 | nextPart = parts.shift();
122 |
123 | if(nsObject[nextPart]) {
124 | executeSubscribeWildcard(nsObject[nextPart], args, {
125 | parts : parts,
126 | async : async,
127 | nsString : params.nsString
128 | });
129 | }
130 | }
131 | }
132 |
133 | function subscribe(nsString, callback, params) {
134 | var parts = nsString.split(options.separator),
135 | nsObject, //Namespace object to which we attach event
136 | context = (params && typeof params.context !== 'undefined') ? params.context : options.context,
137 | eventObject = null,
138 | i = 0;
139 |
140 | if(!context) {
141 | context = callback;
142 | }
143 |
144 | //Iterating through _eventObject to find proper nsObject
145 | nsObject = _eventObject;
146 | for(i = 0; i < parts.length; i += 1) {
147 | if(typeof nsObject[parts[i]] === "undefined") {
148 | nsObject[parts[i]] = {};
149 | nsObject[parts[i]]._events = [];
150 | }
151 | nsObject = nsObject[parts[i]];
152 | }
153 |
154 | eventObject = {
155 | callback : callback,
156 | context : context // "this" parameter in executed function
157 | };
158 |
159 | nsObject._events.push(eventObject);
160 | return { namespace : parts.join(options.separator),
161 | event : eventObject };
162 | }
163 |
164 | function unsubscribe(subscribeObject) {
165 | if(subscribeObject === null || typeof subscribeObject === 'undefined') {
166 | return null;
167 | }
168 | var nsString = subscribeObject.namespace,
169 | eventObject = subscribeObject.event,
170 | parts = nsString.split(options.separator),
171 | nsObject,
172 | i = 0;
173 |
174 | //Iterating through _eventObject to find proper nsObject
175 | nsObject = _eventObject;
176 | for(i = 0; i < parts.length; i += 1) {
177 | if(typeof nsObject[parts[i]] === "undefined") {
178 | if(options.log) {
179 | console.error('There is no ' + nsString + ' subscription');
180 | }
181 | return null;
182 | }
183 | nsObject = nsObject[parts[i]];
184 | }
185 |
186 | forEach(nsObject._events, function(eventId) {
187 | if(nsObject._events[eventId] === eventObject) {
188 | nsObject._events.splice(eventId, 1);
189 | }
190 | });
191 | }
192 |
193 | return {
194 | /**
195 | * Publish event
196 | * @param nsString string namespace string splited by dots
197 | * @param args array of arguments given to callbacks
198 | * @param params paramaters possible:
199 | * @param recurrent bool should execution be bubbled throught namespace
200 | * @param depth integer how many namespaces separated by dots will be executed
201 | */
202 | publish : function(nsString, args, params) {
203 | var parts = nsString.split(options.separator),
204 | recurrent = (typeof params === 'object' && params.recurrent) ? params.recurrent : options.recurrent, // bubbles event throught namespace if true
205 | depth = (typeof params === 'object' && params.depth) ? params.depth : options.depth,
206 | async = (typeof params === 'object' && params.async) ? params.async : options.async,
207 | partsLength = parts.length;
208 |
209 | if(!parts.length) {
210 | if(options.log) {
211 | console.error('Wrong namespace provided ' + nsString);
212 | }
213 | return;
214 | }
215 |
216 | publish(_eventObject, args, parts, {
217 | recurrent : recurrent,
218 | depth : depth,
219 | async : async,
220 | parts : parts,
221 | nsString : nsString,
222 | partsLength : partsLength
223 | });
224 | },
225 | /**
226 | * Subscribe event
227 | * @param nsString string namespace string splited by dots
228 | * @param callback function function executed after publishing event
229 | * @param params given params
230 | * @param context object/nothing Optional object which will be used as "this" in callback
231 | */
232 | subscribe : function(nsString, callback, params) {
233 | var self = this,
234 | subscriptions = [];
235 |
236 | // array of callbacks - multiple subscription
237 | if(typeof callback === 'object' && callback instanceof Array) {
238 | forEach(callback, function(number) {
239 | var oneCallback = callback[number];
240 |
241 | subscriptions = subscriptions.concat(self.subscribe(nsString, oneCallback, params));
242 | });
243 | // array of namespaces - multiple subscription
244 | } else if(typeof nsString === 'object' && nsString instanceof Array) {
245 | forEach(nsString, function(number) {
246 | var namespace = nsString[number];
247 |
248 | subscriptions = subscriptions.concat(self.subscribe(namespace, callback, params));
249 | });
250 | } else {
251 | return subscribe.apply(self, arguments);
252 | }
253 | return subscriptions;
254 | },
255 | /**
256 | * subscribeOnce event - subscribe once to some event, then unsubscribe immadiately
257 | * @param nsString string namespace string splited by dots
258 | * @param callback function function executed after publishing event
259 | * @param params given params
260 | * @param context object/nothing Optional object which will be used as "this" in callback
261 | */
262 | subscribeOnce : function(nsString, callback, params) {
263 | var self = this,
264 | subscription = null;
265 |
266 | function subscriptionCallback() {
267 | var context = this;
268 |
269 | callback.apply(context, arguments);
270 | self.unsubscribe(subscription);
271 | }
272 |
273 | subscription = self.subscribe(nsString, subscriptionCallback, params);
274 | return subscription;
275 | },
276 | /**
277 | * Unsubscribe from given subscription
278 | * @param subscribeObject subscription object given on subscribe (returned from subscription)
279 | */
280 | unsubscribe : function(subscribeObject) {
281 | var self = this;
282 |
283 | //if we have array of callbacks - multiple subscription
284 | if(subscribeObject instanceof Array) {
285 | forEach(subscribeObject, function(number) {
286 | var oneSubscribtion = subscribeObject[number];
287 |
288 | unsubscribe.apply(self, [oneSubscribtion]);
289 | });
290 | } else {
291 | unsubscribe.apply(self, arguments);
292 | }
293 | },
294 | /**
295 | * newInstance - makes new instance of pubsub object with its own config
296 | * @param params instance configuration
297 | * @param separator separator (default is "/")
298 | * @param recurrent should publish events be bubbled through namespace
299 | * @param async should publish events be asynchronous - not blocking function execution
300 | * @param log console.warn/error every problem
301 | */
302 | newInstance : function(params) {
303 | return new Pubsub(params);
304 | }
305 | }; //return block
306 | }
307 | pubsubInstance = new Pubsub(pubsubConfig);
308 |
309 | //if sbd's using requirejs library to load pubsub.js
310 | if(typeof define === 'function') {
311 | define(pubsubInstance);
312 | }
313 |
314 | //node.js
315 | if(typeof module === 'object' && module.exports) {
316 | module.exports = pubsubInstance;
317 | }
318 |
319 | if(typeof window === 'object') {
320 | window.pubsub = pubsubInstance;
321 | if(window !== scope) {
322 | scope.pubsub = pubsubInstance;
323 | }
324 | }
325 | })(this);
326 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | pubsub.js
2 | =========
3 |
4 | Dependency free JavaScript pubsub implementation with wildcards, inheritance and multisubscriptions.
5 |
6 | Working smoothly both on frontend and backend side.
7 |
8 | Read documentation, check tests file, be inspired and feel free to use it and/or contribute.
9 |
10 | [](https://travis-ci.org/Sahadar/pubsub.js)
11 | [](https://david-dm.org/Sahadar/pubsub.js)
12 | [](http://opensource.org/licenses/MIT)
13 |
14 | [Web page](https://sahadar.github.io/pubsub/)
15 |
16 | [NPM pubsub.js link](https://npmjs.org/package/pubsub.js)
17 |
18 | ## Features
19 |
20 | * Currently it is the most advanced dependency-free pub/sub library on npm
21 | * Very fast
22 | * Easy to understand
23 | * Dependency free = using native JavaScript code
24 | * Configurable [link](#change_config)
25 | * Works on server and browser side smoothly [link](#change_config)
26 | * Event inheritance [link](#event_inheritance)
27 | * Wildcards [publish wildcard](#publish_wildcard) [subscribe wildcard](#subscribe_wildcard)
28 | * subscribeOnce method [link](#subscribeOnce)
29 | * Multiple subscriptions [link](#multiple_subscriptions)
30 | * Possibility to make new instances of pubsub with private namespaces scope [link](#new_instance)
31 | * Possibility to publish async events [link](#async_events)
32 | * Possibility to define context for all callbacks by providing pubsub|newInstance "context" param [link](#new_instance_context)
33 | * Possibility to define context for each callback [link](#subscribe_context)
34 | * Controll under event bubbling depth
35 | * Works with *require.js* library
36 | * Written with TDD
37 | * Compiled + gzipped weighs only 1kB, less than 320 lines of code
38 | * Works also on IE 6+
39 |
40 | ## Installation
41 | * download from Github
42 | * npm: `npm install pubsub.js`
43 | * bower: `bower install pubsub-advanced`
44 |
45 | Default pubsub.js configuration:
46 | ```javascript
47 | separator : '/' // defined namespace separator
48 | context // has dynamic value - when not defined it will be always reference to used callback in subscribe method
49 | recurrent : false // defines inheritance of publish event
50 | async : false // if true - publish events will be asynchronous
51 | log : false // set to true will log unsubscribed namespaces to which You publish event
52 | ```
53 |
54 | **Using pubsub inside node.js**
55 | ```javascript
56 | var pubsub = require('pubsub.js');
57 |
58 | pubsub.subscribe('hello/world', function(text) {
59 | console.log(text);
60 | });
61 | pubsub.publish('hello/world', ['my text']);
62 | ```
63 |
64 | ## Examples
65 |
66 | ### Basic example
67 |
68 | ```javascript
69 | //subscribe to 'hello/world' namespace
70 | pubsub.subscribe('hello/world', function() {
71 | console.log('hello world!');
72 | });
73 | //publish event on 'hello/world' namespace
74 | pubsub.publish('hello/world');
75 | //prints "hello world" inside console
76 | ```
77 |
78 | ### Publish with param
79 |
80 | ```javascript
81 | //subscribe to 'hello/world' namespace
82 | pubsub.subscribe('hello/world', function(data) {
83 | console.log(data);
84 | });
85 | //publish event on 'hello/world' namespace
86 | pubsub.publish('hello/world', ['hello!']); // second parameter is an array of arguments
87 | //prints "hello!" inside console
88 | ```
89 |
90 | ### Unsubscribe
91 |
92 | ```javascript
93 | //subscribe to 'hello/world' namespace
94 | var subscription = pubsub.subscribe('hello/world', function() {
95 | console.log('hello world!');
96 | });
97 | //publish event on 'hello/world' namespace
98 | pubsub.publish('hello/world');
99 | //prints "hello world" inside console
100 |
101 | //unsubscribe
102 | pubsub.unsubscribe(subscription);
103 | //publish event on 'hello/world' namespace
104 | pubsub.publish('hello/world');
105 | //nothing happen - we've previously unsubscribed that subscription
106 | ```
107 |
108 | ### Changing default configuration
109 |
110 | ***Browser***
111 |
112 | **Before pubsub script loader** - make global variable named "pubsub" with your default configuration
113 | ```javascript
114 | pubsub = {
115 | separator : '.'
116 | context : referenceToContext
117 | }
118 | ```
119 | **After pubsub load - use it with your configuration, pubsub.js will replace that previous "pubsub" global variable with its own instance**
120 | ```javascript
121 | //subscribe to 'hello.world' namespace
122 | var subscription = pubsub.subscribe('hello.world', function() {
123 | console.log('hello world!');
124 | });
125 | //publish event on 'hello.world' namespace
126 | pubsub.publish('hello.world');
127 | //prints "hello world" inside console
128 |
129 | //unsubscribe
130 | pubsub.unsubscribe(subscription);
131 | //publish event on 'hello.world' namespace
132 | pubsub.publish('hello.world');
133 | //nothing happen - we've previously unsubscribed that subscription
134 | ```
135 |
136 | ***Node.js***
137 |
138 | **Before pubsub require execution** - set global.pubsubConfig variable
139 | ```javascript
140 | global.pubsubConfig = {
141 | separator : '.'
142 | }
143 | ```
144 | **After pubsub load, it'll have your configuration as in browser example**
145 |
146 |
147 | ### Event inheritance
148 |
149 | ```javascript
150 | //subscribe to 'hello' namespace
151 | var subscription = pubsub.subscribe('hello', function() {
152 | console.log('hello world!');
153 | });
154 | //publish event on 'hello/world' namespace
155 | pubsub.publish('hello/world', [], {
156 | recurrent : true
157 | });
158 | //prints "hello world" inside console
159 | //first event goes to "hello" namespace
160 | //then it tries to execute on "hello/world" but nothing is listening on it
161 | ```
162 |
163 | ### Method: subscribeOnce
164 |
165 | ```javascript
166 | var iterator = 0;
167 | var data = null;
168 |
169 | pubsub.subscribeOnce('hello/world', function(param) {
170 | data = param;
171 | iterator++;
172 | });
173 | pubsub.publish('hello/world', ['hello']);
174 | pubsub.publish('hello/world', ['world']);
175 | console.log(iterator); //1
176 | console.log(data); //'hello'
177 | ```
178 |
179 | ### Publish wildcard "*"
180 |
181 | ```javascript
182 | var number = 0;
183 |
184 | //subscribe to "hello/world" namespace
185 | pubsub.subscribe('hello/world', function() {
186 | number++;
187 | });
188 | //subscribe to "hello/earth" namespace
189 | pubsub.subscribe('hello/earth', function() {
190 | number++;
191 | });
192 | //subscribe to "hello/galaxy" namespace
193 | pubsub.subscribe('hello/galaxy', function() {
194 | number++;
195 | });
196 | //subscribe to "hello/world/inner" namespace
197 | pubsub.subscribe('hello/world/inner', function() {
198 | number++;
199 | });
200 |
201 | pubsub.publish('hello/*');
202 | //hello/* executes:
203 | // hello/world, hello/earth, hello/galaxy
204 | // namespace, hello/world/inner is not executed
205 | //
206 | // "*" goes only one namespace deeper
207 | console.log(number); //3
208 | ```
209 |
210 | ### Subscribe wildcard "*"
211 |
212 | ```javascript
213 | var number = 0;
214 |
215 | var subscription = pubsub.subscribe('hello/*/world', function() {
216 | number += 1;
217 | });
218 |
219 | pubsub.publish('hello'); // won't handle
220 | pubsub.publish('hello/my'); // won't handle
221 | pubsub.publish('hello/great/galaxy'); // won't handle
222 |
223 | pubsub.publish('hello/my/world'); // handles
224 | pubsub.publish('hello/huge/world'); // handles
225 | pubsub.publish('hello/great/world'); // handles
226 |
227 | console.log(number); // 3
228 | ```
229 |
230 | ### Multiple subscriptions
231 |
232 | **many namespaces, one callback**
233 | ```javascript
234 | var number = 0;
235 |
236 | var subscription = pubsub.subscribe(['hello/world', 'goodbye/world'], function() {
237 | number++;
238 | });
239 |
240 | pubsub.publish('hello/world');
241 | console.log(number); //1
242 | pubsub.publish('goodbye/world');
243 | console.log(number); //2
244 | pubsub.unsubscribe(subscription);
245 |
246 | pubsub.publish('hello/world');
247 | console.log(number); //2
248 | pubsub.publish('goodbye/world');
249 | console.log(number); //2
250 | ```
251 |
252 | **one namespace, many callbacks**
253 | ```javascript
254 | var number1 = 0;
255 | var number2 = 0;
256 |
257 | var subscription = pubsub.subscribe('hello/world', [function() {
258 | number1++;
259 | }, function() {
260 | number2 += 2;
261 | }]);
262 |
263 | pubsub.publish('hello/world');
264 | console.log(number1 + ',' + number2); //1,2
265 | pubsub.unsubscribe(subscription);
266 |
267 | pubsub.publish('hello/world');
268 | console.log(number1 + ',' + number2); //2,4
269 | ```
270 |
271 | **many namespaces, many callbacks**
272 | ```javascript
273 | var number1 = 0;
274 | var number2 = 0;
275 |
276 | var subscription = pubsub.subscribe(['hello/world', 'goodbye/world'], [function() {
277 | number1++;
278 | }, function() {
279 | number2 += 2;
280 | }]);
281 |
282 | pubsub.publish('hello/world');
283 | console.log(number1 + ',' + number2); //1,2
284 | pubsub.publish('goodbye/world');
285 | console.log(number1 + ',' + number2); //2,4
286 | pubsub.unsubscribe(subscription);
287 |
288 | pubsub.publish('hello/world');
289 | console.log(number1 + ',' + number2); //2,4
290 | pubsub.publish('goodbye/world');
291 | console.log(number1 + ',' + number2); //2,4
292 | ```
293 |
294 | ###making new instances with own namespaces scope
295 | ```javascript
296 | var number1 = 0;
297 | var number2 = 0;
298 |
299 | var privatePubsub = pubsub.newInstance();
300 |
301 | pubsub.subscribe('hello/world', function() {
302 | number1++;
303 | });
304 |
305 | privatePubsub.subscribe('hello/world', function() {
306 | number2++;
307 | });
308 |
309 | pubsub.publish('hello/world');
310 | console.log(number1 + ',' + number2); //1,0
311 |
312 | privatePubsub.publish('hello/world');
313 | console.log(number1 + ',' + number2); //1,1
314 | ```
315 |
316 | **making new instances with own context **
317 | ```javascript
318 | var contextArgument = ["object"];
319 | var privatePubsub = pubsub.newInstance({
320 | context : contextArgument
321 | });
322 |
323 | privatePubsub.subscribe('hello/context', function() {
324 | var that = this;
325 |
326 | console.log(that === contextArgument); //true
327 | });
328 | privatePubsub.publish('hello/context');
329 | ```
330 |
331 | ###Using pubsub asynchronously
332 | ```javascript
333 | var number1 = 0;
334 |
335 | var asyncPubsub = pubsub.newInstance({
336 | async : true
337 | });
338 |
339 | asyncPubsub.subscribeOnce('hello/world', function() {
340 | number1++;
341 | console.log(number1); //2
342 | });
343 |
344 | asyncPubsub.publish('hello/world'); // asynchronous call to 'hello/world'
345 |
346 | number1++;
347 | console.log(number1); //1
348 | ```
349 |
350 | ###Using context param in subscribe method
351 | ```javascript
352 | var contextArgument = ["object"];
353 | var privatePubsub = pubsub.newInstance();
354 |
355 | function callbackFirst() {
356 | var that = this;
357 |
358 | console.log(that === callbackFirst); // true
359 | }
360 | function callbackSecond() {
361 | var that = this;
362 |
363 | console.log(that === contextArgument); // true
364 | }
365 |
366 | var privateSubscribtion1 = privatePubsub.subscribe('hello/context', callbackFirst);
367 | var privateSubscribtion2 = privatePubsub.subscribe('hello/that', callbackSecond, {
368 | context : contextArgument
369 | });
370 | ```
371 |
372 | ## Changelog
373 | * v1.5.1
374 | * Fix "context" option for subscribeOnce
375 | * .eslintrc style guide added + code stylistic fixes
376 | * v1.5.0
377 | * Fix issue #8 - "context" option, new test cases
378 | * v1.4.3
379 | * Fix issue #7 - "recurrent" option, new test cases
380 | * v1.4.2
381 | * Documentation changes
382 | * v1.4.1
383 | * Travis integration
384 | * v1.4.0
385 | * Added subscription wildcard "*"
386 | * v1.3.1
387 | * Fixed problem with "window reference error" in node.js environment
388 | * v1.3.0
389 | * Changed the way of using context parameter in subscribe method - API changes to subscribe method!
390 | * v1.2.0
391 | * Changed the way of using event inheritance - API changes to publish method!
392 | * Added possibility to use "publish" asynchronously
393 | * v1.1.0
394 | * reworked core
395 | * changed the way of setting own config
396 | * implemented "newInstance" method
397 | * v1.0.6
398 | * Fixed bug with unsubscription - subscription during publish of the same namespace (test case 3)
399 | * v1.0.5
400 | * Added multisubscription possibilities
401 | * v1.0.4
402 | * Added subscribeOnce method
403 | * v1.0.3
404 | * Changed scope binding in pubsub
405 | * v1.0.2
406 | * Publish wildcard "*" added
407 | * v1.0.1
408 | * Improved performance - about 350% on chrome, 20% on firefox
409 | * v1.0.0
410 | * Every basic test passing
411 |
412 | ## License
413 |
414 | MIT
415 |
--------------------------------------------------------------------------------
/test/js/test.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 | test("Presence test", function() {
4 | ok(typeof pubsub === 'object' && pubsub !== null, "pubsub present");
5 | ok(typeof pubsub.publish === 'function', "pubsub has method publish");
6 | ok(typeof pubsub.subscribe === 'function', "pubsub has method subscribe");
7 | ok(typeof pubsub.subscribeOnce === 'function', "pubsub has method subscribeOnce");
8 | ok(typeof pubsub.unsubscribe === 'function', "pubsub has method unsubscribe");
9 | });
10 |
11 | test("Unsubscribe test (basic)", function() {
12 | var values = {};
13 | var param1 = "some param1";
14 | var param2 = "some param2";
15 |
16 | var subscription = pubsub.subscribe('hello/world4', function(param1, param2) {
17 | values = {
18 | param1 : param1,
19 | param2 : param2
20 | };
21 | });
22 | pubsub.publish('hello/world4', [param1, param2]);
23 | ok(values.param1 === param1 && values.param2 === param2, 'Values has proper value');
24 | pubsub.unsubscribe(subscription);
25 | pubsub.publish('hello/world4', [null, null]);
26 | ok(values.param1 === param1 && values.param2 === param2, 'Values has proper value');
27 | });
28 |
29 | test("Unsubscribe test (chained unsubscribe)", function() {
30 | var iterator = 0;
31 |
32 | var subscription1 = pubsub.subscribe('hello/world1', function() {
33 | iterator++;
34 | pubsub.unsubscribe(subscription1);
35 | });
36 | var subscription2 = pubsub.subscribe('hello/world1', function() {
37 | iterator++;
38 | });
39 | var subscription3 = pubsub.subscribe('hello/world2', function() {
40 | iterator++;
41 | pubsub.unsubscribe(subscription3);
42 | });
43 |
44 | pubsub.publish('hello/world1');
45 | ok(iterator === 2, 'Second subscription executed properly');
46 | pubsub.unsubscribe(subscription2);
47 | });
48 |
49 | test("Publish test (flat)", function() {
50 | var iterator = 0;
51 | var subscription = pubsub.subscribe('hello', function() {
52 | iterator += 1;
53 | });
54 | pubsub.publish('hello');
55 | pubsub.publish('world');
56 | ok(iterator === 1, 'Done has proper value');
57 | pubsub.unsubscribe(subscription);
58 | pubsub.publish('hello');
59 | ok(iterator === 1, 'Done has proper value');
60 | });
61 |
62 | test("Publish test (basic)", function() {
63 | var done = false;
64 | var subscription = pubsub.subscribe('hello/world1', function() {
65 | done = true;
66 | });
67 | pubsub.publish('hello/world1');
68 | ok(done === true, 'Done has proper value');
69 | pubsub.unsubscribe(subscription);
70 | });
71 |
72 | test("Publish test (param)", function() {
73 | var done = false;
74 | var param = "some param";
75 | var subscription = pubsub.subscribe('hello/world2', function(param) {
76 | done = param;
77 | });
78 | pubsub.publish('hello/world2', [param]);
79 | ok(done === param, 'Done has proper value');
80 | pubsub.unsubscribe(subscription);
81 | });
82 |
83 | test("Method: subscribeOnce test (with param)", function() {
84 | var iterator = 0;
85 | var done = null;
86 |
87 | ok(typeof pubsub.subscribeOnce === 'function', 'Pubsub has method subscribeOnce');
88 | pubsub.subscribeOnce('hello/world', function(param) {
89 | done = param;
90 | iterator++;
91 | });
92 | pubsub.publish('hello/world', ['hello']);
93 | pubsub.publish('hello/world', ['world']);
94 | ok(done === 'hello', 'Done has proper value');
95 | ok(iterator === 1, 'Subscribtion executed only once');
96 | });
97 |
98 | test("Publish test (many params)", function() {
99 | var values = {};
100 | var param1 = "some param1";
101 | var param2 = "some param2";
102 |
103 | var subscription = pubsub.subscribe('hello/world3', function(param1, param2) {
104 | values = {
105 | param1 : param1,
106 | param2 : param2
107 | };
108 | });
109 | pubsub.publish('hello/world3', [param1, param2]);
110 | ok(values.param1 === param1 && values.param2 === param2, 'Values has proper values');
111 | pubsub.unsubscribe(subscription);
112 | });
113 |
114 | test("Inheritance test (basic)", function() {
115 | var values = {};
116 | var param1 = "some param1";
117 | var param2 = "some param2";
118 |
119 | var subscription = pubsub.subscribe('hello', function(param1, param2) {
120 | values = {
121 | param1 : param1,
122 | param2 : param2
123 | };
124 | });
125 | pubsub.publish('hello/world5', [param1, param2], {
126 | recurrent : true
127 | });
128 | ok(values.param1 === param1 && values.param2 === param2, 'Values has proper value');
129 | pubsub.unsubscribe(subscription);
130 | });
131 |
132 | test("Inheritance test (Issue #7)", function() {
133 | var instance = pubsub.newInstance({
134 | recurrent: true
135 | });
136 | var counter = 0;
137 |
138 | var subscription = instance.subscribe('hello', function() {
139 | counter += 1;
140 | });
141 |
142 | instance.publish('hello', []);
143 |
144 | equal(counter, 1, 'Counter executed once');
145 | instance.unsubscribe(subscription);
146 | });
147 |
148 | test("Inheritance test2 (Issue #7)", function() {
149 | var instance = pubsub.newInstance({
150 | recurrent: true
151 | });
152 | var counter = 0;
153 |
154 | var subscription = instance.subscribe('hello/world', function() {
155 | counter += 1;
156 | });
157 |
158 | instance.publish('hello/world');
159 |
160 | equal(counter, 1, 'Counter executed once');
161 | instance.unsubscribe(subscription);
162 | });
163 |
164 | test("Inheritance test (Depth)", function() {
165 | var values = {};
166 | var param1 = "some param1";
167 | var param2 = "some param2";
168 | var param3 = "some param3";
169 |
170 | var instance = pubsub.newInstance({
171 | recurrent : true,
172 | depth : 2
173 | });
174 |
175 | var subscription1 = instance.subscribe('hello/world', function(param1, param2) {
176 | values.param1 = param1;
177 | });
178 | var subscription2 = instance.subscribe('hello/world/one', function(param1, param2) {
179 | values.param2 = param2;
180 | });
181 | var subscriptionShouldNotExecute = instance.subscribe('hello', function(param1, param2, param3) {
182 | values.param3 = param3;
183 | });
184 |
185 | instance.publish('hello/world/one/two', [param1, param2, param3]);
186 |
187 | deepEqual(values, { param1 : param1, param2 : param2 }, 'Values has proper value');
188 | instance.unsubscribe(subscription1);
189 | instance.unsubscribe(subscription2);
190 | instance.unsubscribe(subscriptionShouldNotExecute);
191 | });
192 |
193 | test("Publish wildcard test (*)", function() {
194 | var number = 0;
195 |
196 | var subscription1 = pubsub.subscribe('hello', function() {
197 | number++;
198 | });
199 | var subscription2 = pubsub.subscribe('earth', function() {
200 | number++;
201 | });
202 | var subscription3 = pubsub.subscribe('galaxy', function() {
203 | number++;
204 | });
205 | var subscription4 = pubsub.subscribe('hello/world', function() {
206 | number++;
207 | });
208 |
209 | pubsub.publish('*');
210 |
211 | ok(number === 3, 'Wildcard (*) is working properly');
212 | pubsub.unsubscribe(subscription1);
213 | pubsub.unsubscribe(subscription2);
214 | pubsub.unsubscribe(subscription3);
215 | pubsub.unsubscribe(subscription4);
216 | });
217 |
218 | test("Publish wildcard test (hello/*)", function() {
219 | var number = 0;
220 |
221 | var subscription1 = pubsub.subscribe('hello/world', function() {
222 | number++;
223 | });
224 | var subscription2 = pubsub.subscribe('hello/earth', function() {
225 | number++;
226 | });
227 | var subscription3 = pubsub.subscribe('hello/galaxy', function() {
228 | number++;
229 | });
230 | var subscription4 = pubsub.subscribe('hello/world/inner', function() {
231 | number++;
232 | });
233 |
234 | pubsub.publish('hello/*');
235 |
236 | ok(number === 3, 'Wildcard (*) is working properly');
237 | pubsub.unsubscribe(subscription1);
238 | pubsub.unsubscribe(subscription2);
239 | pubsub.unsubscribe(subscription3);
240 | pubsub.unsubscribe(subscription4);
241 | });
242 |
243 | test("Multiple subscription1 (one namespace, many callbacks)", function() {
244 | var number = 0;
245 |
246 | var subscription = pubsub.subscribe('hello/world', [
247 | function() {
248 | number++;
249 | },
250 | function() {
251 | number++;
252 | },
253 | function() {
254 | number++;
255 | }
256 | ]);
257 |
258 | pubsub.publish('hello/world');
259 | ok(number === 3, 'Multiple subscription before unsubscribe is working properly');
260 | pubsub.unsubscribe(subscription);
261 |
262 | pubsub.publish('hello/world');
263 | ok(number === 3, 'Multiple subscription after unsubscribe is working properly');
264 | });
265 |
266 |
267 | test("Multiple subscription2 (many namespaces, one callback)", function() {
268 | var number = 0;
269 |
270 | var subscription = pubsub.subscribe(['hello/world', 'goodbye/world'], function() {
271 | number++;
272 | });
273 |
274 | pubsub.publish('hello/world');
275 | ok(number === 1, 'Subscribtion to hello/world before unsubscribe is working properly');
276 | pubsub.publish('goodbye/world');
277 | ok(number === 2, 'Subscribtion to goodbye/world before unsubscribe is working properly');
278 | pubsub.unsubscribe(subscription);
279 |
280 | pubsub.publish('hello/world');
281 | ok(number === 2, 'Subscribtion to hello/world after unsubscribe is working properly');
282 | pubsub.publish('goodbye/world');
283 | ok(number === 2, 'Subscribtion to goodbye/world after unsubscribe is working properly');
284 | });
285 |
286 | test("Multiple subscription3 (many namespaces, many callbacks)", function() {
287 | var number1 = 0;
288 | var number2 = 0;
289 |
290 | var subscription = pubsub.subscribe(['hello/world', 'goodbye/world'], [function() {
291 | number1++;
292 | }, function() {
293 | number2 += 2;
294 | }]);
295 |
296 | pubsub.publish('hello/world');
297 | ok(number1 === 1, 'Subscribtion to hello/world before unsubscribe is working properly (number1)');
298 | ok(number2 === 2, 'Subscribtion to hello/world before unsubscribe is working properly (number2)');
299 | pubsub.publish('goodbye/world');
300 | ok(number1 === 2, 'Subscribtion to goodbye/world before unsubscribe is working properly (number1)');
301 | ok(number2 === 4, 'Subscribtion to goodbye/world before unsubscribe is working properly (number2)');
302 | pubsub.unsubscribe(subscription);
303 |
304 | pubsub.publish('hello/world');
305 | ok(number1 === 2, 'Subscribtion to hello/world after unsubscribe is working properly (number1)');
306 | ok(number2 === 4, 'Subscribtion to hello/world after unsubscribe is working properly (number2)');
307 | pubsub.publish('goodbye/world');
308 | ok(number1 === 2, 'Subscribtion to goodbye/world after unsubscribe is working properly (number1)');
309 | ok(number2 === 4, 'Subscribtion to goodbye/world after unsubscribe is working properly (number2)');
310 | });
311 |
312 | test("Pubsub newInstance with own namespaces scope", function() {
313 | ok(typeof pubsub.newInstance === 'function', "pubsub has method newInstance");
314 | var number1 = 0;
315 | var number2 = 0;
316 |
317 | var privatePubsub = pubsub.newInstance();
318 |
319 | var subscription = pubsub.subscribe('hello/world', function() {
320 | number1++;
321 | });
322 | var privateSubscribtion = privatePubsub.subscribe('hello/world', function() {
323 | number2++;
324 | });
325 | pubsub.publish('hello/world');
326 | ok(number1 === 1 && number2 === 0, "Global pubsub publish worked properly");
327 | privatePubsub.publish('hello/world');
328 | ok(number1 === 1 && number2 === 1, "Private pubsub publish worked properly");
329 | privatePubsub.unsubscribe(privateSubscribtion);
330 | privatePubsub.publish('hello/world');
331 | ok(number1 === 1 && number2 === 1, "Private unsubscribe worked properly");
332 | pubsub.unsubscribe(subscription);
333 | pubsub.publish('hello/world');
334 | ok(number1 === 1 && number2 === 1, "Public unsubscribe worked properly");
335 | });
336 |
337 | test("Switching config", function() {
338 | ok(typeof pubsub.newInstance === 'function', "pubsub has method newInstance");
339 | var number1 = 0;
340 | var number2 = 0;
341 |
342 | var privatePubsub = pubsub.newInstance({
343 | separator : '.'
344 | });
345 |
346 | var subscription = pubsub.subscribe('hello/world', function() {
347 | number1++;
348 | });
349 | var privateSubscribtion = privatePubsub.subscribe('hello.world', function() {
350 | number2++;
351 | });
352 | pubsub.publish('hello/world');
353 | ok(number1 === 1 && number2 === 0, "Global pubsub publish worked properly");
354 | privatePubsub.publish('hello.world');
355 | ok(number1 === 1 && number2 === 1, "Private pubsub publish worked properly");
356 | privatePubsub.unsubscribe(privateSubscribtion);
357 | privatePubsub.publish('hello.world');
358 | ok(number1 === 1 && number2 === 1, "Private unsubscribe worked properly");
359 | pubsub.unsubscribe(subscription);
360 | pubsub.publish('hello/world');
361 | ok(number1 === 1 && number2 === 1, "Public unsubscribe worked properly");
362 | });
363 |
364 | test("Subscription wildcard test (*)", function() {
365 | var number = 0;
366 |
367 | var subscription = pubsub.subscribe('*', function() {
368 | number += 1;
369 | });
370 |
371 | pubsub.publish('hello');
372 | pubsub.publish('world');
373 | pubsub.publish('lord');
374 | pubsub.publish('globe');
375 |
376 | ok(number === 4, 'Subscription wildcard is working properly');
377 | pubsub.unsubscribe(subscription);
378 |
379 | pubsub.publish('hello');
380 | pubsub.publish('hello/world');
381 | ok(number === 4, 'Unsubscribe test');
382 | });
383 |
384 | test("Subscription wildcard test (hello/*)", function() {
385 | var number = 0;
386 |
387 | var subscription = pubsub.subscribe('hello/*', function() {
388 | number += 1;
389 | });
390 |
391 | pubsub.publish('hello/world');
392 | pubsub.publish('hello/globe');
393 | pubsub.publish('hello/galaxy');
394 |
395 | ok(number === 3, 'Subscription wildcard is working properly');
396 | pubsub.unsubscribe(subscription);
397 |
398 | pubsub.publish('hello/world');
399 | ok(number === 3, 'Subscription wildcard is working properly');
400 | });
401 |
402 | test("Subscription wildcard test (hello/*/world)", function() {
403 | var number = 0;
404 |
405 | var subscription = pubsub.subscribe('hello/*/world', function() {
406 | number += 1;
407 | });
408 |
409 | pubsub.publish('hello');
410 | pubsub.publish('hello/my');
411 | pubsub.publish('hello/my/world');
412 | pubsub.publish('hello/huge/world');
413 | pubsub.publish('hello/great/world');
414 |
415 | ok(number === 3, 'Subscription wildcard is working properly');
416 |
417 | pubsub.publish('hello/great/galaxy');
418 | ok(number === 3, 'Subscription wildcard is working properly');
419 | pubsub.unsubscribe(subscription);
420 |
421 | pubsub.publish('hello/great/world');
422 | ok(number === 3, 'Subscription wildcard is working properly');
423 | });
424 |
425 | test("Subscription wildcard test (hello/*/*/world)", function() {
426 | var number = 0;
427 |
428 | var subscription = pubsub.subscribe('hello/*/*/world', function() {
429 | number += 1;
430 | });
431 |
432 | pubsub.publish('hello/my/green/world');
433 | pubsub.publish('hello/huge/yellow/world');
434 | pubsub.publish('hello/great/blue/world');
435 |
436 | ok(number === 3, 'Subscription wildcard is working properly');
437 |
438 | pubsub.publish('hello/great/black/galaxy');
439 | ok(number === 3, 'Subscription wildcard is working properly');
440 | pubsub.unsubscribe(subscription);
441 |
442 | pubsub.publish('hello/great/green/world');
443 | ok(number === 3, 'Subscription wildcard is working properly');
444 | });
445 |
446 | asyncTest("Async pubsub test (differences)", function() {
447 | ok(typeof pubsub.newInstance === 'function', "pubsub has method newInstance");
448 | var number1 = 0;
449 |
450 | var asyncPubsub = pubsub.newInstance({
451 | async : true
452 | });
453 |
454 | asyncPubsub.subscribeOnce('hello/world', function() {
455 | number1++;
456 |
457 | ok(number1 === 2, "Async pubsub publish worked properly");
458 | start();
459 | });
460 |
461 | asyncPubsub.publish('hello/world', []);
462 | number1++;
463 | ok(number1 === 1, "Async pubsub publish worked properly");
464 | });
465 |
466 | asyncTest("Sync pubsub test (differences)", function() {
467 | ok(typeof pubsub.newInstance === 'function', "pubsub has method newInstance");
468 | var number1 = 0;
469 |
470 | var syncPubsub = pubsub.newInstance();
471 |
472 | syncPubsub.subscribeOnce('hello/world', function() {
473 | number1++;
474 |
475 | ok(number1 === 1, "Sync pubsub publish worked properly");
476 | start();
477 | });
478 |
479 | syncPubsub.publish('hello/world', []);
480 | number1++;
481 | ok(number1 === 2, "Sync pubsub publish worked properly");
482 | });
483 |
484 | test("Pubsub testing context argument in options", function() {
485 | var contextArgument = ["object"];
486 | var privatePubsub = pubsub.newInstance({
487 | context : contextArgument
488 | });
489 | var onceReturn = 0;
490 |
491 | privatePubsub.subscribe('hello/context', function() {
492 | var self = this;
493 |
494 | ok(self === contextArgument, "Context argument in options work correctly for every subscription");
495 | });
496 | privatePubsub.subscribe('hello/self', function() {
497 | var self = this;
498 |
499 | ok(self === contextArgument, "Context argument in options work correctly for every subscription");
500 | });
501 | privatePubsub.subscribeOnce('hello/once', function() {
502 | var self = this;
503 |
504 | onceReturn++;
505 | ok(self === contextArgument, "Context argument in options work correctly for every subscription");
506 | });
507 |
508 | privatePubsub.publish('hello/context');
509 | privatePubsub.publish('hello/self');
510 | privatePubsub.publish('hello/once');
511 | privatePubsub.publish('hello/once');
512 | equal(onceReturn, 1, "subscribeOnce callback published only once");
513 | });
514 |
515 | test("Pubsub testing override of context argument in options", function() {
516 | var contextArgument = ["object"];
517 | var contextArgument2 = ["object2"];
518 | var privatePubsub = pubsub.newInstance({
519 | context : contextArgument
520 | });
521 | var onceReturn = 0;
522 |
523 | privatePubsub.subscribe('hello/context', function() {
524 | var self = this;
525 |
526 | ok(self === contextArgument, "Context argument in options work correctly for every subscription");
527 | });
528 | privatePubsub.subscribe('hello/self', function() {
529 | var self = this;
530 |
531 | ok(self === contextArgument2, "Context argument in private subscription options work correctly");
532 | }, {
533 | context : contextArgument2
534 | });
535 | privatePubsub.subscribeOnce('hello/once', function() {
536 | var self = this;
537 |
538 | onceReturn++;
539 | ok(self === contextArgument2, "Context argument in private subscription options work correctly");
540 | }, {
541 | context : contextArgument2
542 | });
543 |
544 | privatePubsub.publish('hello/context');
545 | privatePubsub.publish('hello/self');
546 | privatePubsub.publish('hello/once');
547 | privatePubsub.publish('hello/once');
548 | ok(onceReturn === 1, "subscribeOnce callback published only once");
549 | });
550 |
551 | test("Pubsub testing use of private context for one subscribe", function() {
552 | var contextArgument = ["object"];
553 | var privatePubsub = pubsub.newInstance();
554 |
555 | function callbackFirst() {
556 | var self = this;
557 |
558 | ok(self === callbackFirst, "No context definition = context is same function");
559 | }
560 | function callbackSecond() {
561 | var self = this;
562 |
563 | ok(self === contextArgument, "Context argument in subscribe config works correctly");
564 | }
565 |
566 | privatePubsub.subscribe('hello/context', callbackFirst);
567 | privatePubsub.subscribe('hello/self', callbackSecond, {
568 | context : contextArgument
569 | });
570 |
571 | privatePubsub.publish('hello/context');
572 | privatePubsub.publish('hello/self');
573 | });
574 | })();
--------------------------------------------------------------------------------
/test/js/qunit.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * QUnit 1.17.1
3 | * http://qunitjs.com/
4 | *
5 | * Copyright jQuery Foundation and other contributors
6 | * Released under the MIT license
7 | * http://jquery.org/license
8 | *
9 | * Date: 2015-01-20T19:39Z
10 | */
11 |
12 | (function( window ) {
13 |
14 | var QUnit,
15 | config,
16 | onErrorFnPrev,
17 | loggingCallbacks = {},
18 | fileName = ( sourceFromStacktrace( 0 ) || "" ).replace( /(:\d+)+\)?/, "" ).replace( /.+\//, "" ),
19 | toString = Object.prototype.toString,
20 | hasOwn = Object.prototype.hasOwnProperty,
21 | // Keep a local reference to Date (GH-283)
22 | Date = window.Date,
23 | now = Date.now || function() {
24 | return new Date().getTime();
25 | },
26 | globalStartCalled = false,
27 | runStarted = false,
28 | setTimeout = window.setTimeout,
29 | clearTimeout = window.clearTimeout,
30 | defined = {
31 | document: window.document !== undefined,
32 | setTimeout: window.setTimeout !== undefined,
33 | sessionStorage: (function() {
34 | var x = "qunit-test-string";
35 | try {
36 | sessionStorage.setItem( x, x );
37 | sessionStorage.removeItem( x );
38 | return true;
39 | } catch ( e ) {
40 | return false;
41 | }
42 | }())
43 | },
44 | /**
45 | * Provides a normalized error string, correcting an issue
46 | * with IE 7 (and prior) where Error.prototype.toString is
47 | * not properly implemented
48 | *
49 | * Based on http://es5.github.com/#x15.11.4.4
50 | *
51 | * @param {String|Error} error
52 | * @return {String} error message
53 | */
54 | errorString = function( error ) {
55 | var name, message,
56 | errorString = error.toString();
57 | if ( errorString.substring( 0, 7 ) === "[object" ) {
58 | name = error.name ? error.name.toString() : "Error";
59 | message = error.message ? error.message.toString() : "";
60 | if ( name && message ) {
61 | return name + ": " + message;
62 | } else if ( name ) {
63 | return name;
64 | } else if ( message ) {
65 | return message;
66 | } else {
67 | return "Error";
68 | }
69 | } else {
70 | return errorString;
71 | }
72 | },
73 | /**
74 | * Makes a clone of an object using only Array or Object as base,
75 | * and copies over the own enumerable properties.
76 | *
77 | * @param {Object} obj
78 | * @return {Object} New object with only the own properties (recursively).
79 | */
80 | objectValues = function( obj ) {
81 | var key, val,
82 | vals = QUnit.is( "array", obj ) ? [] : {};
83 | for ( key in obj ) {
84 | if ( hasOwn.call( obj, key ) ) {
85 | val = obj[ key ];
86 | vals[ key ] = val === Object( val ) ? objectValues( val ) : val;
87 | }
88 | }
89 | return vals;
90 | };
91 |
92 | QUnit = {};
93 |
94 | /**
95 | * Config object: Maintain internal state
96 | * Later exposed as QUnit.config
97 | * `config` initialized at top of scope
98 | */
99 | config = {
100 | // The queue of tests to run
101 | queue: [],
102 |
103 | // block until document ready
104 | blocking: true,
105 |
106 | // by default, run previously failed tests first
107 | // very useful in combination with "Hide passed tests" checked
108 | reorder: true,
109 |
110 | // by default, modify document.title when suite is done
111 | altertitle: true,
112 |
113 | // by default, scroll to top of the page when suite is done
114 | scrolltop: true,
115 |
116 | // when enabled, all tests must call expect()
117 | requireExpects: false,
118 |
119 | // add checkboxes that are persisted in the query-string
120 | // when enabled, the id is set to `true` as a `QUnit.config` property
121 | urlConfig: [
122 | {
123 | id: "hidepassed",
124 | label: "Hide passed tests",
125 | tooltip: "Only show tests and assertions that fail. Stored as query-strings."
126 | },
127 | {
128 | id: "noglobals",
129 | label: "Check for Globals",
130 | tooltip: "Enabling this will test if any test introduces new properties on the " +
131 | "`window` object. Stored as query-strings."
132 | },
133 | {
134 | id: "notrycatch",
135 | label: "No try-catch",
136 | tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging " +
137 | "exceptions in IE reasonable. Stored as query-strings."
138 | }
139 | ],
140 |
141 | // Set of all modules.
142 | modules: [],
143 |
144 | // The first unnamed module
145 | currentModule: {
146 | name: "",
147 | tests: []
148 | },
149 |
150 | callbacks: {}
151 | };
152 |
153 | // Push a loose unnamed module to the modules collection
154 | config.modules.push( config.currentModule );
155 |
156 | // Initialize more QUnit.config and QUnit.urlParams
157 | (function() {
158 | var i, current,
159 | location = window.location || { search: "", protocol: "file:" },
160 | params = location.search.slice( 1 ).split( "&" ),
161 | length = params.length,
162 | urlParams = {};
163 |
164 | if ( params[ 0 ] ) {
165 | for ( i = 0; i < length; i++ ) {
166 | current = params[ i ].split( "=" );
167 | current[ 0 ] = decodeURIComponent( current[ 0 ] );
168 |
169 | // allow just a key to turn on a flag, e.g., test.html?noglobals
170 | current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true;
171 | if ( urlParams[ current[ 0 ] ] ) {
172 | urlParams[ current[ 0 ] ] = [].concat( urlParams[ current[ 0 ] ], current[ 1 ] );
173 | } else {
174 | urlParams[ current[ 0 ] ] = current[ 1 ];
175 | }
176 | }
177 | }
178 |
179 | if ( urlParams.filter === true ) {
180 | delete urlParams.filter;
181 | }
182 |
183 | QUnit.urlParams = urlParams;
184 |
185 | // String search anywhere in moduleName+testName
186 | config.filter = urlParams.filter;
187 |
188 | config.testId = [];
189 | if ( urlParams.testId ) {
190 |
191 | // Ensure that urlParams.testId is an array
192 | urlParams.testId = [].concat( urlParams.testId );
193 | for ( i = 0; i < urlParams.testId.length; i++ ) {
194 | config.testId.push( urlParams.testId[ i ] );
195 | }
196 | }
197 |
198 | // Figure out if we're running the tests from a server or not
199 | QUnit.isLocal = location.protocol === "file:";
200 | }());
201 |
202 | // Root QUnit object.
203 | // `QUnit` initialized at top of scope
204 | extend( QUnit, {
205 |
206 | // call on start of module test to prepend name to all tests
207 | module: function( name, testEnvironment ) {
208 | var currentModule = {
209 | name: name,
210 | testEnvironment: testEnvironment,
211 | tests: []
212 | };
213 |
214 | // DEPRECATED: handles setup/teardown functions,
215 | // beforeEach and afterEach should be used instead
216 | if ( testEnvironment && testEnvironment.setup ) {
217 | testEnvironment.beforeEach = testEnvironment.setup;
218 | delete testEnvironment.setup;
219 | }
220 | if ( testEnvironment && testEnvironment.teardown ) {
221 | testEnvironment.afterEach = testEnvironment.teardown;
222 | delete testEnvironment.teardown;
223 | }
224 |
225 | config.modules.push( currentModule );
226 | config.currentModule = currentModule;
227 | },
228 |
229 | // DEPRECATED: QUnit.asyncTest() will be removed in QUnit 2.0.
230 | asyncTest: function( testName, expected, callback ) {
231 | if ( arguments.length === 2 ) {
232 | callback = expected;
233 | expected = null;
234 | }
235 |
236 | QUnit.test( testName, expected, callback, true );
237 | },
238 |
239 | test: function( testName, expected, callback, async ) {
240 | var test;
241 |
242 | if ( arguments.length === 2 ) {
243 | callback = expected;
244 | expected = null;
245 | }
246 |
247 | test = new Test({
248 | testName: testName,
249 | expected: expected,
250 | async: async,
251 | callback: callback
252 | });
253 |
254 | test.queue();
255 | },
256 |
257 | skip: function( testName ) {
258 | var test = new Test({
259 | testName: testName,
260 | skip: true
261 | });
262 |
263 | test.queue();
264 | },
265 |
266 | // DEPRECATED: The functionality of QUnit.start() will be altered in QUnit 2.0.
267 | // In QUnit 2.0, invoking it will ONLY affect the `QUnit.config.autostart` blocking behavior.
268 | start: function( count ) {
269 | var globalStartAlreadyCalled = globalStartCalled;
270 |
271 | if ( !config.current ) {
272 | globalStartCalled = true;
273 |
274 | if ( runStarted ) {
275 | throw new Error( "Called start() outside of a test context while already started" );
276 | } else if ( globalStartAlreadyCalled || count > 1 ) {
277 | throw new Error( "Called start() outside of a test context too many times" );
278 | } else if ( config.autostart ) {
279 | throw new Error( "Called start() outside of a test context when " +
280 | "QUnit.config.autostart was true" );
281 | } else if ( !config.pageLoaded ) {
282 |
283 | // The page isn't completely loaded yet, so bail out and let `QUnit.load` handle it
284 | config.autostart = true;
285 | return;
286 | }
287 | } else {
288 |
289 | // If a test is running, adjust its semaphore
290 | config.current.semaphore -= count || 1;
291 |
292 | // Don't start until equal number of stop-calls
293 | if ( config.current.semaphore > 0 ) {
294 | return;
295 | }
296 |
297 | // throw an Error if start is called more often than stop
298 | if ( config.current.semaphore < 0 ) {
299 | config.current.semaphore = 0;
300 |
301 | QUnit.pushFailure(
302 | "Called start() while already started (test's semaphore was 0 already)",
303 | sourceFromStacktrace( 2 )
304 | );
305 | return;
306 | }
307 | }
308 |
309 | resumeProcessing();
310 | },
311 |
312 | // DEPRECATED: QUnit.stop() will be removed in QUnit 2.0.
313 | stop: function( count ) {
314 |
315 | // If there isn't a test running, don't allow QUnit.stop() to be called
316 | if ( !config.current ) {
317 | throw new Error( "Called stop() outside of a test context" );
318 | }
319 |
320 | // If a test is running, adjust its semaphore
321 | config.current.semaphore += count || 1;
322 |
323 | pauseProcessing();
324 | },
325 |
326 | config: config,
327 |
328 | // Safe object type checking
329 | is: function( type, obj ) {
330 | return QUnit.objectType( obj ) === type;
331 | },
332 |
333 | objectType: function( obj ) {
334 | if ( typeof obj === "undefined" ) {
335 | return "undefined";
336 | }
337 |
338 | // Consider: typeof null === object
339 | if ( obj === null ) {
340 | return "null";
341 | }
342 |
343 | var match = toString.call( obj ).match( /^\[object\s(.*)\]$/ ),
344 | type = match && match[ 1 ] || "";
345 |
346 | switch ( type ) {
347 | case "Number":
348 | if ( isNaN( obj ) ) {
349 | return "nan";
350 | }
351 | return "number";
352 | case "String":
353 | case "Boolean":
354 | case "Array":
355 | case "Date":
356 | case "RegExp":
357 | case "Function":
358 | return type.toLowerCase();
359 | }
360 | if ( typeof obj === "object" ) {
361 | return "object";
362 | }
363 | return undefined;
364 | },
365 |
366 | extend: extend,
367 |
368 | load: function() {
369 | config.pageLoaded = true;
370 |
371 | // Initialize the configuration options
372 | extend( config, {
373 | stats: { all: 0, bad: 0 },
374 | moduleStats: { all: 0, bad: 0 },
375 | started: 0,
376 | updateRate: 1000,
377 | autostart: true,
378 | filter: ""
379 | }, true );
380 |
381 | config.blocking = false;
382 |
383 | if ( config.autostart ) {
384 | resumeProcessing();
385 | }
386 | }
387 | });
388 |
389 | // Register logging callbacks
390 | (function() {
391 | var i, l, key,
392 | callbacks = [ "begin", "done", "log", "testStart", "testDone",
393 | "moduleStart", "moduleDone" ];
394 |
395 | function registerLoggingCallback( key ) {
396 | var loggingCallback = function( callback ) {
397 | if ( QUnit.objectType( callback ) !== "function" ) {
398 | throw new Error(
399 | "QUnit logging methods require a callback function as their first parameters."
400 | );
401 | }
402 |
403 | config.callbacks[ key ].push( callback );
404 | };
405 |
406 | // DEPRECATED: This will be removed on QUnit 2.0.0+
407 | // Stores the registered functions allowing restoring
408 | // at verifyLoggingCallbacks() if modified
409 | loggingCallbacks[ key ] = loggingCallback;
410 |
411 | return loggingCallback;
412 | }
413 |
414 | for ( i = 0, l = callbacks.length; i < l; i++ ) {
415 | key = callbacks[ i ];
416 |
417 | // Initialize key collection of logging callback
418 | if ( QUnit.objectType( config.callbacks[ key ] ) === "undefined" ) {
419 | config.callbacks[ key ] = [];
420 | }
421 |
422 | QUnit[ key ] = registerLoggingCallback( key );
423 | }
424 | })();
425 |
426 | // `onErrorFnPrev` initialized at top of scope
427 | // Preserve other handlers
428 | onErrorFnPrev = window.onerror;
429 |
430 | // Cover uncaught exceptions
431 | // Returning true will suppress the default browser handler,
432 | // returning false will let it run.
433 | window.onerror = function( error, filePath, linerNr ) {
434 | var ret = false;
435 | if ( onErrorFnPrev ) {
436 | ret = onErrorFnPrev( error, filePath, linerNr );
437 | }
438 |
439 | // Treat return value as window.onerror itself does,
440 | // Only do our handling if not suppressed.
441 | if ( ret !== true ) {
442 | if ( QUnit.config.current ) {
443 | if ( QUnit.config.current.ignoreGlobalErrors ) {
444 | return true;
445 | }
446 | QUnit.pushFailure( error, filePath + ":" + linerNr );
447 | } else {
448 | QUnit.test( "global failure", extend(function() {
449 | QUnit.pushFailure( error, filePath + ":" + linerNr );
450 | }, { validTest: true } ) );
451 | }
452 | return false;
453 | }
454 |
455 | return ret;
456 | };
457 |
458 | function done() {
459 | var runtime, passed;
460 |
461 | config.autorun = true;
462 |
463 | // Log the last module results
464 | if ( config.previousModule ) {
465 | runLoggingCallbacks( "moduleDone", {
466 | name: config.previousModule.name,
467 | tests: config.previousModule.tests,
468 | failed: config.moduleStats.bad,
469 | passed: config.moduleStats.all - config.moduleStats.bad,
470 | total: config.moduleStats.all,
471 | runtime: now() - config.moduleStats.started
472 | });
473 | }
474 | delete config.previousModule;
475 |
476 | runtime = now() - config.started;
477 | passed = config.stats.all - config.stats.bad;
478 |
479 | runLoggingCallbacks( "done", {
480 | failed: config.stats.bad,
481 | passed: passed,
482 | total: config.stats.all,
483 | runtime: runtime
484 | });
485 | }
486 |
487 | // Doesn't support IE6 to IE9
488 | // See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
489 | function extractStacktrace( e, offset ) {
490 | offset = offset === undefined ? 4 : offset;
491 |
492 | var stack, include, i;
493 |
494 | if ( e.stacktrace ) {
495 |
496 | // Opera 12.x
497 | return e.stacktrace.split( "\n" )[ offset + 3 ];
498 | } else if ( e.stack ) {
499 |
500 | // Firefox, Chrome, Safari 6+, IE10+, PhantomJS and Node
501 | stack = e.stack.split( "\n" );
502 | if ( /^error$/i.test( stack[ 0 ] ) ) {
503 | stack.shift();
504 | }
505 | if ( fileName ) {
506 | include = [];
507 | for ( i = offset; i < stack.length; i++ ) {
508 | if ( stack[ i ].indexOf( fileName ) !== -1 ) {
509 | break;
510 | }
511 | include.push( stack[ i ] );
512 | }
513 | if ( include.length ) {
514 | return include.join( "\n" );
515 | }
516 | }
517 | return stack[ offset ];
518 | } else if ( e.sourceURL ) {
519 |
520 | // Safari < 6
521 | // exclude useless self-reference for generated Error objects
522 | if ( /qunit.js$/.test( e.sourceURL ) ) {
523 | return;
524 | }
525 |
526 | // for actual exceptions, this is useful
527 | return e.sourceURL + ":" + e.line;
528 | }
529 | }
530 |
531 | function sourceFromStacktrace( offset ) {
532 | var e = new Error();
533 | if ( !e.stack ) {
534 | try {
535 | throw e;
536 | } catch ( err ) {
537 | // This should already be true in most browsers
538 | e = err;
539 | }
540 | }
541 | return extractStacktrace( e, offset );
542 | }
543 |
544 | function synchronize( callback, last ) {
545 | if ( QUnit.objectType( callback ) === "array" ) {
546 | while ( callback.length ) {
547 | synchronize( callback.shift() );
548 | }
549 | return;
550 | }
551 | config.queue.push( callback );
552 |
553 | if ( config.autorun && !config.blocking ) {
554 | process( last );
555 | }
556 | }
557 |
558 | function process( last ) {
559 | function next() {
560 | process( last );
561 | }
562 | var start = now();
563 | config.depth = ( config.depth || 0 ) + 1;
564 |
565 | while ( config.queue.length && !config.blocking ) {
566 | if ( !defined.setTimeout || config.updateRate <= 0 ||
567 | ( ( now() - start ) < config.updateRate ) ) {
568 | if ( config.current ) {
569 |
570 | // Reset async tracking for each phase of the Test lifecycle
571 | config.current.usedAsync = false;
572 | }
573 | config.queue.shift()();
574 | } else {
575 | setTimeout( next, 13 );
576 | break;
577 | }
578 | }
579 | config.depth--;
580 | if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) {
581 | done();
582 | }
583 | }
584 |
585 | function begin() {
586 | var i, l,
587 | modulesLog = [];
588 |
589 | // If the test run hasn't officially begun yet
590 | if ( !config.started ) {
591 |
592 | // Record the time of the test run's beginning
593 | config.started = now();
594 |
595 | verifyLoggingCallbacks();
596 |
597 | // Delete the loose unnamed module if unused.
598 | if ( config.modules[ 0 ].name === "" && config.modules[ 0 ].tests.length === 0 ) {
599 | config.modules.shift();
600 | }
601 |
602 | // Avoid unnecessary information by not logging modules' test environments
603 | for ( i = 0, l = config.modules.length; i < l; i++ ) {
604 | modulesLog.push({
605 | name: config.modules[ i ].name,
606 | tests: config.modules[ i ].tests
607 | });
608 | }
609 |
610 | // The test run is officially beginning now
611 | runLoggingCallbacks( "begin", {
612 | totalTests: Test.count,
613 | modules: modulesLog
614 | });
615 | }
616 |
617 | config.blocking = false;
618 | process( true );
619 | }
620 |
621 | function resumeProcessing() {
622 | runStarted = true;
623 |
624 | // A slight delay to allow this iteration of the event loop to finish (more assertions, etc.)
625 | if ( defined.setTimeout ) {
626 | setTimeout(function() {
627 | if ( config.current && config.current.semaphore > 0 ) {
628 | return;
629 | }
630 | if ( config.timeout ) {
631 | clearTimeout( config.timeout );
632 | }
633 |
634 | begin();
635 | }, 13 );
636 | } else {
637 | begin();
638 | }
639 | }
640 |
641 | function pauseProcessing() {
642 | config.blocking = true;
643 |
644 | if ( config.testTimeout && defined.setTimeout ) {
645 | clearTimeout( config.timeout );
646 | config.timeout = setTimeout(function() {
647 | if ( config.current ) {
648 | config.current.semaphore = 0;
649 | QUnit.pushFailure( "Test timed out", sourceFromStacktrace( 2 ) );
650 | } else {
651 | throw new Error( "Test timed out" );
652 | }
653 | resumeProcessing();
654 | }, config.testTimeout );
655 | }
656 | }
657 |
658 | function saveGlobal() {
659 | config.pollution = [];
660 |
661 | if ( config.noglobals ) {
662 | for ( var key in window ) {
663 | if ( hasOwn.call( window, key ) ) {
664 | // in Opera sometimes DOM element ids show up here, ignore them
665 | if ( /^qunit-test-output/.test( key ) ) {
666 | continue;
667 | }
668 | config.pollution.push( key );
669 | }
670 | }
671 | }
672 | }
673 |
674 | function checkPollution() {
675 | var newGlobals,
676 | deletedGlobals,
677 | old = config.pollution;
678 |
679 | saveGlobal();
680 |
681 | newGlobals = diff( config.pollution, old );
682 | if ( newGlobals.length > 0 ) {
683 | QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join( ", " ) );
684 | }
685 |
686 | deletedGlobals = diff( old, config.pollution );
687 | if ( deletedGlobals.length > 0 ) {
688 | QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join( ", " ) );
689 | }
690 | }
691 |
692 | // returns a new Array with the elements that are in a but not in b
693 | function diff( a, b ) {
694 | var i, j,
695 | result = a.slice();
696 |
697 | for ( i = 0; i < result.length; i++ ) {
698 | for ( j = 0; j < b.length; j++ ) {
699 | if ( result[ i ] === b[ j ] ) {
700 | result.splice( i, 1 );
701 | i--;
702 | break;
703 | }
704 | }
705 | }
706 | return result;
707 | }
708 |
709 | function extend( a, b, undefOnly ) {
710 | for ( var prop in b ) {
711 | if ( hasOwn.call( b, prop ) ) {
712 |
713 | // Avoid "Member not found" error in IE8 caused by messing with window.constructor
714 | if ( !( prop === "constructor" && a === window ) ) {
715 | if ( b[ prop ] === undefined ) {
716 | delete a[ prop ];
717 | } else if ( !( undefOnly && typeof a[ prop ] !== "undefined" ) ) {
718 | a[ prop ] = b[ prop ];
719 | }
720 | }
721 | }
722 | }
723 |
724 | return a;
725 | }
726 |
727 | function runLoggingCallbacks( key, args ) {
728 | var i, l, callbacks;
729 |
730 | callbacks = config.callbacks[ key ];
731 | for ( i = 0, l = callbacks.length; i < l; i++ ) {
732 | callbacks[ i ]( args );
733 | }
734 | }
735 |
736 | // DEPRECATED: This will be removed on 2.0.0+
737 | // This function verifies if the loggingCallbacks were modified by the user
738 | // If so, it will restore it, assign the given callback and print a console warning
739 | function verifyLoggingCallbacks() {
740 | var loggingCallback, userCallback;
741 |
742 | for ( loggingCallback in loggingCallbacks ) {
743 | if ( QUnit[ loggingCallback ] !== loggingCallbacks[ loggingCallback ] ) {
744 |
745 | userCallback = QUnit[ loggingCallback ];
746 |
747 | // Restore the callback function
748 | QUnit[ loggingCallback ] = loggingCallbacks[ loggingCallback ];
749 |
750 | // Assign the deprecated given callback
751 | QUnit[ loggingCallback ]( userCallback );
752 |
753 | if ( window.console && window.console.warn ) {
754 | window.console.warn(
755 | "QUnit." + loggingCallback + " was replaced with a new value.\n" +
756 | "Please, check out the documentation on how to apply logging callbacks.\n" +
757 | "Reference: http://api.qunitjs.com/category/callbacks/"
758 | );
759 | }
760 | }
761 | }
762 | }
763 |
764 | // from jquery.js
765 | function inArray( elem, array ) {
766 | if ( array.indexOf ) {
767 | return array.indexOf( elem );
768 | }
769 |
770 | for ( var i = 0, length = array.length; i < length; i++ ) {
771 | if ( array[ i ] === elem ) {
772 | return i;
773 | }
774 | }
775 |
776 | return -1;
777 | }
778 |
779 | function Test( settings ) {
780 | var i, l;
781 |
782 | ++Test.count;
783 |
784 | extend( this, settings );
785 | this.assertions = [];
786 | this.semaphore = 0;
787 | this.usedAsync = false;
788 | this.module = config.currentModule;
789 | this.stack = sourceFromStacktrace( 3 );
790 |
791 | // Register unique strings
792 | for ( i = 0, l = this.module.tests; i < l.length; i++ ) {
793 | if ( this.module.tests[ i ].name === this.testName ) {
794 | this.testName += " ";
795 | }
796 | }
797 |
798 | this.testId = generateHash( this.module.name, this.testName );
799 |
800 | this.module.tests.push({
801 | name: this.testName,
802 | testId: this.testId
803 | });
804 |
805 | if ( settings.skip ) {
806 |
807 | // Skipped tests will fully ignore any sent callback
808 | this.callback = function() {};
809 | this.async = false;
810 | this.expected = 0;
811 | } else {
812 | this.assert = new Assert( this );
813 | }
814 | }
815 |
816 | Test.count = 0;
817 |
818 | Test.prototype = {
819 | before: function() {
820 | if (
821 |
822 | // Emit moduleStart when we're switching from one module to another
823 | this.module !== config.previousModule ||
824 |
825 | // They could be equal (both undefined) but if the previousModule property doesn't
826 | // yet exist it means this is the first test in a suite that isn't wrapped in a
827 | // module, in which case we'll just emit a moduleStart event for 'undefined'.
828 | // Without this, reporters can get testStart before moduleStart which is a problem.
829 | !hasOwn.call( config, "previousModule" )
830 | ) {
831 | if ( hasOwn.call( config, "previousModule" ) ) {
832 | runLoggingCallbacks( "moduleDone", {
833 | name: config.previousModule.name,
834 | tests: config.previousModule.tests,
835 | failed: config.moduleStats.bad,
836 | passed: config.moduleStats.all - config.moduleStats.bad,
837 | total: config.moduleStats.all,
838 | runtime: now() - config.moduleStats.started
839 | });
840 | }
841 | config.previousModule = this.module;
842 | config.moduleStats = { all: 0, bad: 0, started: now() };
843 | runLoggingCallbacks( "moduleStart", {
844 | name: this.module.name,
845 | tests: this.module.tests
846 | });
847 | }
848 |
849 | config.current = this;
850 |
851 | this.testEnvironment = extend( {}, this.module.testEnvironment );
852 | delete this.testEnvironment.beforeEach;
853 | delete this.testEnvironment.afterEach;
854 |
855 | this.started = now();
856 | runLoggingCallbacks( "testStart", {
857 | name: this.testName,
858 | module: this.module.name,
859 | testId: this.testId
860 | });
861 |
862 | if ( !config.pollution ) {
863 | saveGlobal();
864 | }
865 | },
866 |
867 | run: function() {
868 | var promise;
869 |
870 | config.current = this;
871 |
872 | if ( this.async ) {
873 | QUnit.stop();
874 | }
875 |
876 | this.callbackStarted = now();
877 |
878 | if ( config.notrycatch ) {
879 | promise = this.callback.call( this.testEnvironment, this.assert );
880 | this.resolvePromise( promise );
881 | return;
882 | }
883 |
884 | try {
885 | promise = this.callback.call( this.testEnvironment, this.assert );
886 | this.resolvePromise( promise );
887 | } catch ( e ) {
888 | this.pushFailure( "Died on test #" + ( this.assertions.length + 1 ) + " " +
889 | this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) );
890 |
891 | // else next test will carry the responsibility
892 | saveGlobal();
893 |
894 | // Restart the tests if they're blocking
895 | if ( config.blocking ) {
896 | QUnit.start();
897 | }
898 | }
899 | },
900 |
901 | after: function() {
902 | checkPollution();
903 | },
904 |
905 | queueHook: function( hook, hookName ) {
906 | var promise,
907 | test = this;
908 | return function runHook() {
909 | config.current = test;
910 | if ( config.notrycatch ) {
911 | promise = hook.call( test.testEnvironment, test.assert );
912 | test.resolvePromise( promise, hookName );
913 | return;
914 | }
915 | try {
916 | promise = hook.call( test.testEnvironment, test.assert );
917 | test.resolvePromise( promise, hookName );
918 | } catch ( error ) {
919 | test.pushFailure( hookName + " failed on " + test.testName + ": " +
920 | ( error.message || error ), extractStacktrace( error, 0 ) );
921 | }
922 | };
923 | },
924 |
925 | // Currently only used for module level hooks, can be used to add global level ones
926 | hooks: function( handler ) {
927 | var hooks = [];
928 |
929 | // Hooks are ignored on skipped tests
930 | if ( this.skip ) {
931 | return hooks;
932 | }
933 |
934 | if ( this.module.testEnvironment &&
935 | QUnit.objectType( this.module.testEnvironment[ handler ] ) === "function" ) {
936 | hooks.push( this.queueHook( this.module.testEnvironment[ handler ], handler ) );
937 | }
938 |
939 | return hooks;
940 | },
941 |
942 | finish: function() {
943 | config.current = this;
944 | if ( config.requireExpects && this.expected === null ) {
945 | this.pushFailure( "Expected number of assertions to be defined, but expect() was " +
946 | "not called.", this.stack );
947 | } else if ( this.expected !== null && this.expected !== this.assertions.length ) {
948 | this.pushFailure( "Expected " + this.expected + " assertions, but " +
949 | this.assertions.length + " were run", this.stack );
950 | } else if ( this.expected === null && !this.assertions.length ) {
951 | this.pushFailure( "Expected at least one assertion, but none were run - call " +
952 | "expect(0) to accept zero assertions.", this.stack );
953 | }
954 |
955 | var i,
956 | bad = 0;
957 |
958 | this.runtime = now() - this.started;
959 | config.stats.all += this.assertions.length;
960 | config.moduleStats.all += this.assertions.length;
961 |
962 | for ( i = 0; i < this.assertions.length; i++ ) {
963 | if ( !this.assertions[ i ].result ) {
964 | bad++;
965 | config.stats.bad++;
966 | config.moduleStats.bad++;
967 | }
968 | }
969 |
970 | runLoggingCallbacks( "testDone", {
971 | name: this.testName,
972 | module: this.module.name,
973 | skipped: !!this.skip,
974 | failed: bad,
975 | passed: this.assertions.length - bad,
976 | total: this.assertions.length,
977 | runtime: this.runtime,
978 |
979 | // HTML Reporter use
980 | assertions: this.assertions,
981 | testId: this.testId,
982 |
983 | // DEPRECATED: this property will be removed in 2.0.0, use runtime instead
984 | duration: this.runtime
985 | });
986 |
987 | // QUnit.reset() is deprecated and will be replaced for a new
988 | // fixture reset function on QUnit 2.0/2.1.
989 | // It's still called here for backwards compatibility handling
990 | QUnit.reset();
991 |
992 | config.current = undefined;
993 | },
994 |
995 | queue: function() {
996 | var bad,
997 | test = this;
998 |
999 | if ( !this.valid() ) {
1000 | return;
1001 | }
1002 |
1003 | function run() {
1004 |
1005 | // each of these can by async
1006 | synchronize([
1007 | function() {
1008 | test.before();
1009 | },
1010 |
1011 | test.hooks( "beforeEach" ),
1012 |
1013 | function() {
1014 | test.run();
1015 | },
1016 |
1017 | test.hooks( "afterEach" ).reverse(),
1018 |
1019 | function() {
1020 | test.after();
1021 | },
1022 | function() {
1023 | test.finish();
1024 | }
1025 | ]);
1026 | }
1027 |
1028 | // `bad` initialized at top of scope
1029 | // defer when previous test run passed, if storage is available
1030 | bad = QUnit.config.reorder && defined.sessionStorage &&
1031 | +sessionStorage.getItem( "qunit-test-" + this.module.name + "-" + this.testName );
1032 |
1033 | if ( bad ) {
1034 | run();
1035 | } else {
1036 | synchronize( run, true );
1037 | }
1038 | },
1039 |
1040 | push: function( result, actual, expected, message ) {
1041 | var source,
1042 | details = {
1043 | module: this.module.name,
1044 | name: this.testName,
1045 | result: result,
1046 | message: message,
1047 | actual: actual,
1048 | expected: expected,
1049 | testId: this.testId,
1050 | runtime: now() - this.started
1051 | };
1052 |
1053 | if ( !result ) {
1054 | source = sourceFromStacktrace();
1055 |
1056 | if ( source ) {
1057 | details.source = source;
1058 | }
1059 | }
1060 |
1061 | runLoggingCallbacks( "log", details );
1062 |
1063 | this.assertions.push({
1064 | result: !!result,
1065 | message: message
1066 | });
1067 | },
1068 |
1069 | pushFailure: function( message, source, actual ) {
1070 | if ( !this instanceof Test ) {
1071 | throw new Error( "pushFailure() assertion outside test context, was " +
1072 | sourceFromStacktrace( 2 ) );
1073 | }
1074 |
1075 | var details = {
1076 | module: this.module.name,
1077 | name: this.testName,
1078 | result: false,
1079 | message: message || "error",
1080 | actual: actual || null,
1081 | testId: this.testId,
1082 | runtime: now() - this.started
1083 | };
1084 |
1085 | if ( source ) {
1086 | details.source = source;
1087 | }
1088 |
1089 | runLoggingCallbacks( "log", details );
1090 |
1091 | this.assertions.push({
1092 | result: false,
1093 | message: message
1094 | });
1095 | },
1096 |
1097 | resolvePromise: function( promise, phase ) {
1098 | var then, message,
1099 | test = this;
1100 | if ( promise != null ) {
1101 | then = promise.then;
1102 | if ( QUnit.objectType( then ) === "function" ) {
1103 | QUnit.stop();
1104 | then.call(
1105 | promise,
1106 | QUnit.start,
1107 | function( error ) {
1108 | message = "Promise rejected " +
1109 | ( !phase ? "during" : phase.replace( /Each$/, "" ) ) +
1110 | " " + test.testName + ": " + ( error.message || error );
1111 | test.pushFailure( message, extractStacktrace( error, 0 ) );
1112 |
1113 | // else next test will carry the responsibility
1114 | saveGlobal();
1115 |
1116 | // Unblock
1117 | QUnit.start();
1118 | }
1119 | );
1120 | }
1121 | }
1122 | },
1123 |
1124 | valid: function() {
1125 | var include,
1126 | filter = config.filter,
1127 | module = QUnit.urlParams.module && QUnit.urlParams.module.toLowerCase(),
1128 | fullName = ( this.module.name + ": " + this.testName ).toLowerCase();
1129 |
1130 | // Internally-generated tests are always valid
1131 | if ( this.callback && this.callback.validTest ) {
1132 | return true;
1133 | }
1134 |
1135 | if ( config.testId.length > 0 && inArray( this.testId, config.testId ) < 0 ) {
1136 | return false;
1137 | }
1138 |
1139 | if ( module && ( !this.module.name || this.module.name.toLowerCase() !== module ) ) {
1140 | return false;
1141 | }
1142 |
1143 | if ( !filter ) {
1144 | return true;
1145 | }
1146 |
1147 | include = filter.charAt( 0 ) !== "!";
1148 | if ( !include ) {
1149 | filter = filter.toLowerCase().slice( 1 );
1150 | }
1151 |
1152 | // If the filter matches, we need to honour include
1153 | if ( fullName.indexOf( filter ) !== -1 ) {
1154 | return include;
1155 | }
1156 |
1157 | // Otherwise, do the opposite
1158 | return !include;
1159 | }
1160 |
1161 | };
1162 |
1163 | // Resets the test setup. Useful for tests that modify the DOM.
1164 | /*
1165 | DEPRECATED: Use multiple tests instead of resetting inside a test.
1166 | Use testStart or testDone for custom cleanup.
1167 | This method will throw an error in 2.0, and will be removed in 2.1
1168 | */
1169 | QUnit.reset = function() {
1170 |
1171 | // Return on non-browser environments
1172 | // This is necessary to not break on node tests
1173 | if ( typeof window === "undefined" ) {
1174 | return;
1175 | }
1176 |
1177 | var fixture = defined.document && document.getElementById &&
1178 | document.getElementById( "qunit-fixture" );
1179 |
1180 | if ( fixture ) {
1181 | fixture.innerHTML = config.fixture;
1182 | }
1183 | };
1184 |
1185 | QUnit.pushFailure = function() {
1186 | if ( !QUnit.config.current ) {
1187 | throw new Error( "pushFailure() assertion outside test context, in " +
1188 | sourceFromStacktrace( 2 ) );
1189 | }
1190 |
1191 | // Gets current test obj
1192 | var currentTest = QUnit.config.current;
1193 |
1194 | return currentTest.pushFailure.apply( currentTest, arguments );
1195 | };
1196 |
1197 | // Based on Java's String.hashCode, a simple but not
1198 | // rigorously collision resistant hashing function
1199 | function generateHash( module, testName ) {
1200 | var hex,
1201 | i = 0,
1202 | hash = 0,
1203 | str = module + "\x1C" + testName,
1204 | len = str.length;
1205 |
1206 | for ( ; i < len; i++ ) {
1207 | hash = ( ( hash << 5 ) - hash ) + str.charCodeAt( i );
1208 | hash |= 0;
1209 | }
1210 |
1211 | // Convert the possibly negative integer hash code into an 8 character hex string, which isn't
1212 | // strictly necessary but increases user understanding that the id is a SHA-like hash
1213 | hex = ( 0x100000000 + hash ).toString( 16 );
1214 | if ( hex.length < 8 ) {
1215 | hex = "0000000" + hex;
1216 | }
1217 |
1218 | return hex.slice( -8 );
1219 | }
1220 |
1221 | function Assert( testContext ) {
1222 | this.test = testContext;
1223 | }
1224 |
1225 | // Assert helpers
1226 | QUnit.assert = Assert.prototype = {
1227 |
1228 | // Specify the number of expected assertions to guarantee that failed test
1229 | // (no assertions are run at all) don't slip through.
1230 | expect: function( asserts ) {
1231 | if ( arguments.length === 1 ) {
1232 | this.test.expected = asserts;
1233 | } else {
1234 | return this.test.expected;
1235 | }
1236 | },
1237 |
1238 | // Increment this Test's semaphore counter, then return a single-use function that
1239 | // decrements that counter a maximum of once.
1240 | async: function() {
1241 | var test = this.test,
1242 | popped = false;
1243 |
1244 | test.semaphore += 1;
1245 | test.usedAsync = true;
1246 | pauseProcessing();
1247 |
1248 | return function done() {
1249 | if ( !popped ) {
1250 | test.semaphore -= 1;
1251 | popped = true;
1252 | resumeProcessing();
1253 | } else {
1254 | test.pushFailure( "Called the callback returned from `assert.async` more than once",
1255 | sourceFromStacktrace( 2 ) );
1256 | }
1257 | };
1258 | },
1259 |
1260 | // Exports test.push() to the user API
1261 | push: function( /* result, actual, expected, message */ ) {
1262 | var assert = this,
1263 | currentTest = ( assert instanceof Assert && assert.test ) || QUnit.config.current;
1264 |
1265 | // Backwards compatibility fix.
1266 | // Allows the direct use of global exported assertions and QUnit.assert.*
1267 | // Although, it's use is not recommended as it can leak assertions
1268 | // to other tests from async tests, because we only get a reference to the current test,
1269 | // not exactly the test where assertion were intended to be called.
1270 | if ( !currentTest ) {
1271 | throw new Error( "assertion outside test context, in " + sourceFromStacktrace( 2 ) );
1272 | }
1273 |
1274 | if ( currentTest.usedAsync === true && currentTest.semaphore === 0 ) {
1275 | currentTest.pushFailure( "Assertion after the final `assert.async` was resolved",
1276 | sourceFromStacktrace( 2 ) );
1277 |
1278 | // Allow this assertion to continue running anyway...
1279 | }
1280 |
1281 | if ( !( assert instanceof Assert ) ) {
1282 | assert = currentTest.assert;
1283 | }
1284 | return assert.test.push.apply( assert.test, arguments );
1285 | },
1286 |
1287 | /**
1288 | * Asserts rough true-ish result.
1289 | * @name ok
1290 | * @function
1291 | * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
1292 | */
1293 | ok: function( result, message ) {
1294 | message = message || ( result ? "okay" : "failed, expected argument to be truthy, was: " +
1295 | QUnit.dump.parse( result ) );
1296 | this.push( !!result, result, true, message );
1297 | },
1298 |
1299 | /**
1300 | * Assert that the first two arguments are equal, with an optional message.
1301 | * Prints out both actual and expected values.
1302 | * @name equal
1303 | * @function
1304 | * @example equal( format( "{0} bytes.", 2), "2 bytes.", "replaces {0} with next argument" );
1305 | */
1306 | equal: function( actual, expected, message ) {
1307 | /*jshint eqeqeq:false */
1308 | this.push( expected == actual, actual, expected, message );
1309 | },
1310 |
1311 | /**
1312 | * @name notEqual
1313 | * @function
1314 | */
1315 | notEqual: function( actual, expected, message ) {
1316 | /*jshint eqeqeq:false */
1317 | this.push( expected != actual, actual, expected, message );
1318 | },
1319 |
1320 | /**
1321 | * @name propEqual
1322 | * @function
1323 | */
1324 | propEqual: function( actual, expected, message ) {
1325 | actual = objectValues( actual );
1326 | expected = objectValues( expected );
1327 | this.push( QUnit.equiv( actual, expected ), actual, expected, message );
1328 | },
1329 |
1330 | /**
1331 | * @name notPropEqual
1332 | * @function
1333 | */
1334 | notPropEqual: function( actual, expected, message ) {
1335 | actual = objectValues( actual );
1336 | expected = objectValues( expected );
1337 | this.push( !QUnit.equiv( actual, expected ), actual, expected, message );
1338 | },
1339 |
1340 | /**
1341 | * @name deepEqual
1342 | * @function
1343 | */
1344 | deepEqual: function( actual, expected, message ) {
1345 | this.push( QUnit.equiv( actual, expected ), actual, expected, message );
1346 | },
1347 |
1348 | /**
1349 | * @name notDeepEqual
1350 | * @function
1351 | */
1352 | notDeepEqual: function( actual, expected, message ) {
1353 | this.push( !QUnit.equiv( actual, expected ), actual, expected, message );
1354 | },
1355 |
1356 | /**
1357 | * @name strictEqual
1358 | * @function
1359 | */
1360 | strictEqual: function( actual, expected, message ) {
1361 | this.push( expected === actual, actual, expected, message );
1362 | },
1363 |
1364 | /**
1365 | * @name notStrictEqual
1366 | * @function
1367 | */
1368 | notStrictEqual: function( actual, expected, message ) {
1369 | this.push( expected !== actual, actual, expected, message );
1370 | },
1371 |
1372 | "throws": function( block, expected, message ) {
1373 | var actual, expectedType,
1374 | expectedOutput = expected,
1375 | ok = false;
1376 |
1377 | // 'expected' is optional unless doing string comparison
1378 | if ( message == null && typeof expected === "string" ) {
1379 | message = expected;
1380 | expected = null;
1381 | }
1382 |
1383 | this.test.ignoreGlobalErrors = true;
1384 | try {
1385 | block.call( this.test.testEnvironment );
1386 | } catch (e) {
1387 | actual = e;
1388 | }
1389 | this.test.ignoreGlobalErrors = false;
1390 |
1391 | if ( actual ) {
1392 | expectedType = QUnit.objectType( expected );
1393 |
1394 | // we don't want to validate thrown error
1395 | if ( !expected ) {
1396 | ok = true;
1397 | expectedOutput = null;
1398 |
1399 | // expected is a regexp
1400 | } else if ( expectedType === "regexp" ) {
1401 | ok = expected.test( errorString( actual ) );
1402 |
1403 | // expected is a string
1404 | } else if ( expectedType === "string" ) {
1405 | ok = expected === errorString( actual );
1406 |
1407 | // expected is a constructor, maybe an Error constructor
1408 | } else if ( expectedType === "function" && actual instanceof expected ) {
1409 | ok = true;
1410 |
1411 | // expected is an Error object
1412 | } else if ( expectedType === "object" ) {
1413 | ok = actual instanceof expected.constructor &&
1414 | actual.name === expected.name &&
1415 | actual.message === expected.message;
1416 |
1417 | // expected is a validation function which returns true if validation passed
1418 | } else if ( expectedType === "function" && expected.call( {}, actual ) === true ) {
1419 | expectedOutput = null;
1420 | ok = true;
1421 | }
1422 |
1423 | this.push( ok, actual, expectedOutput, message );
1424 | } else {
1425 | this.test.pushFailure( message, null, "No exception was thrown." );
1426 | }
1427 | }
1428 | };
1429 |
1430 | // Provide an alternative to assert.throws(), for enviroments that consider throws a reserved word
1431 | // Known to us are: Closure Compiler, Narwhal
1432 | (function() {
1433 | /*jshint sub:true */
1434 | Assert.prototype.raises = Assert.prototype[ "throws" ];
1435 | }());
1436 |
1437 | // Test for equality any JavaScript type.
1438 | // Author: Philippe Rathé
1439 | QUnit.equiv = (function() {
1440 |
1441 | // Call the o related callback with the given arguments.
1442 | function bindCallbacks( o, callbacks, args ) {
1443 | var prop = QUnit.objectType( o );
1444 | if ( prop ) {
1445 | if ( QUnit.objectType( callbacks[ prop ] ) === "function" ) {
1446 | return callbacks[ prop ].apply( callbacks, args );
1447 | } else {
1448 | return callbacks[ prop ]; // or undefined
1449 | }
1450 | }
1451 | }
1452 |
1453 | // the real equiv function
1454 | var innerEquiv,
1455 |
1456 | // stack to decide between skip/abort functions
1457 | callers = [],
1458 |
1459 | // stack to avoiding loops from circular referencing
1460 | parents = [],
1461 | parentsB = [],
1462 |
1463 | getProto = Object.getPrototypeOf || function( obj ) {
1464 | /* jshint camelcase: false, proto: true */
1465 | return obj.__proto__;
1466 | },
1467 | callbacks = (function() {
1468 |
1469 | // for string, boolean, number and null
1470 | function useStrictEquality( b, a ) {
1471 |
1472 | /*jshint eqeqeq:false */
1473 | if ( b instanceof a.constructor || a instanceof b.constructor ) {
1474 |
1475 | // to catch short annotation VS 'new' annotation of a
1476 | // declaration
1477 | // e.g. var i = 1;
1478 | // var j = new Number(1);
1479 | return a == b;
1480 | } else {
1481 | return a === b;
1482 | }
1483 | }
1484 |
1485 | return {
1486 | "string": useStrictEquality,
1487 | "boolean": useStrictEquality,
1488 | "number": useStrictEquality,
1489 | "null": useStrictEquality,
1490 | "undefined": useStrictEquality,
1491 |
1492 | "nan": function( b ) {
1493 | return isNaN( b );
1494 | },
1495 |
1496 | "date": function( b, a ) {
1497 | return QUnit.objectType( b ) === "date" && a.valueOf() === b.valueOf();
1498 | },
1499 |
1500 | "regexp": function( b, a ) {
1501 | return QUnit.objectType( b ) === "regexp" &&
1502 |
1503 | // the regex itself
1504 | a.source === b.source &&
1505 |
1506 | // and its modifiers
1507 | a.global === b.global &&
1508 |
1509 | // (gmi) ...
1510 | a.ignoreCase === b.ignoreCase &&
1511 | a.multiline === b.multiline &&
1512 | a.sticky === b.sticky;
1513 | },
1514 |
1515 | // - skip when the property is a method of an instance (OOP)
1516 | // - abort otherwise,
1517 | // initial === would have catch identical references anyway
1518 | "function": function() {
1519 | var caller = callers[ callers.length - 1 ];
1520 | return caller !== Object && typeof caller !== "undefined";
1521 | },
1522 |
1523 | "array": function( b, a ) {
1524 | var i, j, len, loop, aCircular, bCircular;
1525 |
1526 | // b could be an object literal here
1527 | if ( QUnit.objectType( b ) !== "array" ) {
1528 | return false;
1529 | }
1530 |
1531 | len = a.length;
1532 | if ( len !== b.length ) {
1533 | // safe and faster
1534 | return false;
1535 | }
1536 |
1537 | // track reference to avoid circular references
1538 | parents.push( a );
1539 | parentsB.push( b );
1540 | for ( i = 0; i < len; i++ ) {
1541 | loop = false;
1542 | for ( j = 0; j < parents.length; j++ ) {
1543 | aCircular = parents[ j ] === a[ i ];
1544 | bCircular = parentsB[ j ] === b[ i ];
1545 | if ( aCircular || bCircular ) {
1546 | if ( a[ i ] === b[ i ] || aCircular && bCircular ) {
1547 | loop = true;
1548 | } else {
1549 | parents.pop();
1550 | parentsB.pop();
1551 | return false;
1552 | }
1553 | }
1554 | }
1555 | if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
1556 | parents.pop();
1557 | parentsB.pop();
1558 | return false;
1559 | }
1560 | }
1561 | parents.pop();
1562 | parentsB.pop();
1563 | return true;
1564 | },
1565 |
1566 | "object": function( b, a ) {
1567 |
1568 | /*jshint forin:false */
1569 | var i, j, loop, aCircular, bCircular,
1570 | // Default to true
1571 | eq = true,
1572 | aProperties = [],
1573 | bProperties = [];
1574 |
1575 | // comparing constructors is more strict than using
1576 | // instanceof
1577 | if ( a.constructor !== b.constructor ) {
1578 |
1579 | // Allow objects with no prototype to be equivalent to
1580 | // objects with Object as their constructor.
1581 | if ( !( ( getProto( a ) === null && getProto( b ) === Object.prototype ) ||
1582 | ( getProto( b ) === null && getProto( a ) === Object.prototype ) ) ) {
1583 | return false;
1584 | }
1585 | }
1586 |
1587 | // stack constructor before traversing properties
1588 | callers.push( a.constructor );
1589 |
1590 | // track reference to avoid circular references
1591 | parents.push( a );
1592 | parentsB.push( b );
1593 |
1594 | // be strict: don't ensure hasOwnProperty and go deep
1595 | for ( i in a ) {
1596 | loop = false;
1597 | for ( j = 0; j < parents.length; j++ ) {
1598 | aCircular = parents[ j ] === a[ i ];
1599 | bCircular = parentsB[ j ] === b[ i ];
1600 | if ( aCircular || bCircular ) {
1601 | if ( a[ i ] === b[ i ] || aCircular && bCircular ) {
1602 | loop = true;
1603 | } else {
1604 | eq = false;
1605 | break;
1606 | }
1607 | }
1608 | }
1609 | aProperties.push( i );
1610 | if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
1611 | eq = false;
1612 | break;
1613 | }
1614 | }
1615 |
1616 | parents.pop();
1617 | parentsB.pop();
1618 | callers.pop(); // unstack, we are done
1619 |
1620 | for ( i in b ) {
1621 | bProperties.push( i ); // collect b's properties
1622 | }
1623 |
1624 | // Ensures identical properties name
1625 | return eq && innerEquiv( aProperties.sort(), bProperties.sort() );
1626 | }
1627 | };
1628 | }());
1629 |
1630 | innerEquiv = function() { // can take multiple arguments
1631 | var args = [].slice.apply( arguments );
1632 | if ( args.length < 2 ) {
1633 | return true; // end transition
1634 | }
1635 |
1636 | return ( (function( a, b ) {
1637 | if ( a === b ) {
1638 | return true; // catch the most you can
1639 | } else if ( a === null || b === null || typeof a === "undefined" ||
1640 | typeof b === "undefined" ||
1641 | QUnit.objectType( a ) !== QUnit.objectType( b ) ) {
1642 |
1643 | // don't lose time with error prone cases
1644 | return false;
1645 | } else {
1646 | return bindCallbacks( a, callbacks, [ b, a ] );
1647 | }
1648 |
1649 | // apply transition with (1..n) arguments
1650 | }( args[ 0 ], args[ 1 ] ) ) &&
1651 | innerEquiv.apply( this, args.splice( 1, args.length - 1 ) ) );
1652 | };
1653 |
1654 | return innerEquiv;
1655 | }());
1656 |
1657 | // Based on jsDump by Ariel Flesler
1658 | // http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html
1659 | QUnit.dump = (function() {
1660 | function quote( str ) {
1661 | return "\"" + str.toString().replace( /"/g, "\\\"" ) + "\"";
1662 | }
1663 | function literal( o ) {
1664 | return o + "";
1665 | }
1666 | function join( pre, arr, post ) {
1667 | var s = dump.separator(),
1668 | base = dump.indent(),
1669 | inner = dump.indent( 1 );
1670 | if ( arr.join ) {
1671 | arr = arr.join( "," + s + inner );
1672 | }
1673 | if ( !arr ) {
1674 | return pre + post;
1675 | }
1676 | return [ pre, inner + arr, base + post ].join( s );
1677 | }
1678 | function array( arr, stack ) {
1679 | var i = arr.length,
1680 | ret = new Array( i );
1681 |
1682 | if ( dump.maxDepth && dump.depth > dump.maxDepth ) {
1683 | return "[object Array]";
1684 | }
1685 |
1686 | this.up();
1687 | while ( i-- ) {
1688 | ret[ i ] = this.parse( arr[ i ], undefined, stack );
1689 | }
1690 | this.down();
1691 | return join( "[", ret, "]" );
1692 | }
1693 |
1694 | var reName = /^function (\w+)/,
1695 | dump = {
1696 |
1697 | // objType is used mostly internally, you can fix a (custom) type in advance
1698 | parse: function( obj, objType, stack ) {
1699 | stack = stack || [];
1700 | var res, parser, parserType,
1701 | inStack = inArray( obj, stack );
1702 |
1703 | if ( inStack !== -1 ) {
1704 | return "recursion(" + ( inStack - stack.length ) + ")";
1705 | }
1706 |
1707 | objType = objType || this.typeOf( obj );
1708 | parser = this.parsers[ objType ];
1709 | parserType = typeof parser;
1710 |
1711 | if ( parserType === "function" ) {
1712 | stack.push( obj );
1713 | res = parser.call( this, obj, stack );
1714 | stack.pop();
1715 | return res;
1716 | }
1717 | return ( parserType === "string" ) ? parser : this.parsers.error;
1718 | },
1719 | typeOf: function( obj ) {
1720 | var type;
1721 | if ( obj === null ) {
1722 | type = "null";
1723 | } else if ( typeof obj === "undefined" ) {
1724 | type = "undefined";
1725 | } else if ( QUnit.is( "regexp", obj ) ) {
1726 | type = "regexp";
1727 | } else if ( QUnit.is( "date", obj ) ) {
1728 | type = "date";
1729 | } else if ( QUnit.is( "function", obj ) ) {
1730 | type = "function";
1731 | } else if ( obj.setInterval !== undefined &&
1732 | obj.document !== undefined &&
1733 | obj.nodeType === undefined ) {
1734 | type = "window";
1735 | } else if ( obj.nodeType === 9 ) {
1736 | type = "document";
1737 | } else if ( obj.nodeType ) {
1738 | type = "node";
1739 | } else if (
1740 |
1741 | // native arrays
1742 | toString.call( obj ) === "[object Array]" ||
1743 |
1744 | // NodeList objects
1745 | ( typeof obj.length === "number" && obj.item !== undefined &&
1746 | ( obj.length ? obj.item( 0 ) === obj[ 0 ] : ( obj.item( 0 ) === null &&
1747 | obj[ 0 ] === undefined ) ) )
1748 | ) {
1749 | type = "array";
1750 | } else if ( obj.constructor === Error.prototype.constructor ) {
1751 | type = "error";
1752 | } else {
1753 | type = typeof obj;
1754 | }
1755 | return type;
1756 | },
1757 | separator: function() {
1758 | return this.multiline ? this.HTML ? " " : "\n" : this.HTML ? " " : " ";
1759 | },
1760 | // extra can be a number, shortcut for increasing-calling-decreasing
1761 | indent: function( extra ) {
1762 | if ( !this.multiline ) {
1763 | return "";
1764 | }
1765 | var chr = this.indentChar;
1766 | if ( this.HTML ) {
1767 | chr = chr.replace( /\t/g, " " ).replace( / /g, " " );
1768 | }
1769 | return new Array( this.depth + ( extra || 0 ) ).join( chr );
1770 | },
1771 | up: function( a ) {
1772 | this.depth += a || 1;
1773 | },
1774 | down: function( a ) {
1775 | this.depth -= a || 1;
1776 | },
1777 | setParser: function( name, parser ) {
1778 | this.parsers[ name ] = parser;
1779 | },
1780 | // The next 3 are exposed so you can use them
1781 | quote: quote,
1782 | literal: literal,
1783 | join: join,
1784 | //
1785 | depth: 1,
1786 | maxDepth: 5,
1787 |
1788 | // This is the list of parsers, to modify them, use dump.setParser
1789 | parsers: {
1790 | window: "[Window]",
1791 | document: "[Document]",
1792 | error: function( error ) {
1793 | return "Error(\"" + error.message + "\")";
1794 | },
1795 | unknown: "[Unknown]",
1796 | "null": "null",
1797 | "undefined": "undefined",
1798 | "function": function( fn ) {
1799 | var ret = "function",
1800 |
1801 | // functions never have name in IE
1802 | name = "name" in fn ? fn.name : ( reName.exec( fn ) || [] )[ 1 ];
1803 |
1804 | if ( name ) {
1805 | ret += " " + name;
1806 | }
1807 | ret += "( ";
1808 |
1809 | ret = [ ret, dump.parse( fn, "functionArgs" ), "){" ].join( "" );
1810 | return join( ret, dump.parse( fn, "functionCode" ), "}" );
1811 | },
1812 | array: array,
1813 | nodelist: array,
1814 | "arguments": array,
1815 | object: function( map, stack ) {
1816 | var keys, key, val, i, nonEnumerableProperties,
1817 | ret = [];
1818 |
1819 | if ( dump.maxDepth && dump.depth > dump.maxDepth ) {
1820 | return "[object Object]";
1821 | }
1822 |
1823 | dump.up();
1824 | keys = [];
1825 | for ( key in map ) {
1826 | keys.push( key );
1827 | }
1828 |
1829 | // Some properties are not always enumerable on Error objects.
1830 | nonEnumerableProperties = [ "message", "name" ];
1831 | for ( i in nonEnumerableProperties ) {
1832 | key = nonEnumerableProperties[ i ];
1833 | if ( key in map && !( key in keys ) ) {
1834 | keys.push( key );
1835 | }
1836 | }
1837 | keys.sort();
1838 | for ( i = 0; i < keys.length; i++ ) {
1839 | key = keys[ i ];
1840 | val = map[ key ];
1841 | ret.push( dump.parse( key, "key" ) + ": " +
1842 | dump.parse( val, undefined, stack ) );
1843 | }
1844 | dump.down();
1845 | return join( "{", ret, "}" );
1846 | },
1847 | node: function( node ) {
1848 | var len, i, val,
1849 | open = dump.HTML ? "<" : "<",
1850 | close = dump.HTML ? ">" : ">",
1851 | tag = node.nodeName.toLowerCase(),
1852 | ret = open + tag,
1853 | attrs = node.attributes;
1854 |
1855 | if ( attrs ) {
1856 | for ( i = 0, len = attrs.length; i < len; i++ ) {
1857 | val = attrs[ i ].nodeValue;
1858 |
1859 | // IE6 includes all attributes in .attributes, even ones not explicitly
1860 | // set. Those have values like undefined, null, 0, false, "" or
1861 | // "inherit".
1862 | if ( val && val !== "inherit" ) {
1863 | ret += " " + attrs[ i ].nodeName + "=" +
1864 | dump.parse( val, "attribute" );
1865 | }
1866 | }
1867 | }
1868 | ret += close;
1869 |
1870 | // Show content of TextNode or CDATASection
1871 | if ( node.nodeType === 3 || node.nodeType === 4 ) {
1872 | ret += node.nodeValue;
1873 | }
1874 |
1875 | return ret + open + "/" + tag + close;
1876 | },
1877 |
1878 | // function calls it internally, it's the arguments part of the function
1879 | functionArgs: function( fn ) {
1880 | var args,
1881 | l = fn.length;
1882 |
1883 | if ( !l ) {
1884 | return "";
1885 | }
1886 |
1887 | args = new Array( l );
1888 | while ( l-- ) {
1889 |
1890 | // 97 is 'a'
1891 | args[ l ] = String.fromCharCode( 97 + l );
1892 | }
1893 | return " " + args.join( ", " ) + " ";
1894 | },
1895 | // object calls it internally, the key part of an item in a map
1896 | key: quote,
1897 | // function calls it internally, it's the content of the function
1898 | functionCode: "[code]",
1899 | // node calls it internally, it's an html attribute value
1900 | attribute: quote,
1901 | string: quote,
1902 | date: quote,
1903 | regexp: literal,
1904 | number: literal,
1905 | "boolean": literal
1906 | },
1907 | // if true, entities are escaped ( <, >, \t, space and \n )
1908 | HTML: false,
1909 | // indentation unit
1910 | indentChar: " ",
1911 | // if true, items in a collection, are separated by a \n, else just a space.
1912 | multiline: true
1913 | };
1914 |
1915 | return dump;
1916 | }());
1917 |
1918 | // back compat
1919 | QUnit.jsDump = QUnit.dump;
1920 |
1921 | // For browser, export only select globals
1922 | if ( typeof window !== "undefined" ) {
1923 |
1924 | // Deprecated
1925 | // Extend assert methods to QUnit and Global scope through Backwards compatibility
1926 | (function() {
1927 | var i,
1928 | assertions = Assert.prototype;
1929 |
1930 | function applyCurrent( current ) {
1931 | return function() {
1932 | var assert = new Assert( QUnit.config.current );
1933 | current.apply( assert, arguments );
1934 | };
1935 | }
1936 |
1937 | for ( i in assertions ) {
1938 | QUnit[ i ] = applyCurrent( assertions[ i ] );
1939 | }
1940 | })();
1941 |
1942 | (function() {
1943 | var i, l,
1944 | keys = [
1945 | "test",
1946 | "module",
1947 | "expect",
1948 | "asyncTest",
1949 | "start",
1950 | "stop",
1951 | "ok",
1952 | "equal",
1953 | "notEqual",
1954 | "propEqual",
1955 | "notPropEqual",
1956 | "deepEqual",
1957 | "notDeepEqual",
1958 | "strictEqual",
1959 | "notStrictEqual",
1960 | "throws"
1961 | ];
1962 |
1963 | for ( i = 0, l = keys.length; i < l; i++ ) {
1964 | window[ keys[ i ] ] = QUnit[ keys[ i ] ];
1965 | }
1966 | })();
1967 |
1968 | window.QUnit = QUnit;
1969 | }
1970 |
1971 | // For nodejs
1972 | if ( typeof module !== "undefined" && module && module.exports ) {
1973 | module.exports = QUnit;
1974 |
1975 | // For consistency with CommonJS environments' exports
1976 | module.exports.QUnit = QUnit;
1977 | }
1978 |
1979 | // For CommonJS with exports, but without module.exports, like Rhino
1980 | if ( typeof exports !== "undefined" && exports ) {
1981 | exports.QUnit = QUnit;
1982 | }
1983 |
1984 | // Get a reference to the global object, like window in browsers
1985 | }( (function() {
1986 | return this;
1987 | })() ));
1988 |
1989 | /*istanbul ignore next */
1990 | // jscs:disable maximumLineLength
1991 | /*
1992 | * Javascript Diff Algorithm
1993 | * By John Resig (http://ejohn.org/)
1994 | * Modified by Chu Alan "sprite"
1995 | *
1996 | * Released under the MIT license.
1997 | *
1998 | * More Info:
1999 | * http://ejohn.org/projects/javascript-diff-algorithm/
2000 | *
2001 | * Usage: QUnit.diff(expected, actual)
2002 | *
2003 | * QUnit.diff( "the quick brown fox jumped over", "the quick fox jumps over" ) == "the quick brown fox jumped jumps over"
2004 | */
2005 | QUnit.diff = (function() {
2006 | var hasOwn = Object.prototype.hasOwnProperty;
2007 |
2008 | /*jshint eqeqeq:false, eqnull:true */
2009 | function diff( o, n ) {
2010 | var i,
2011 | ns = {},
2012 | os = {};
2013 |
2014 | for ( i = 0; i < n.length; i++ ) {
2015 | if ( !hasOwn.call( ns, n[ i ] ) ) {
2016 | ns[ n[ i ] ] = {
2017 | rows: [],
2018 | o: null
2019 | };
2020 | }
2021 | ns[ n[ i ] ].rows.push( i );
2022 | }
2023 |
2024 | for ( i = 0; i < o.length; i++ ) {
2025 | if ( !hasOwn.call( os, o[ i ] ) ) {
2026 | os[ o[ i ] ] = {
2027 | rows: [],
2028 | n: null
2029 | };
2030 | }
2031 | os[ o[ i ] ].rows.push( i );
2032 | }
2033 |
2034 | for ( i in ns ) {
2035 | if ( hasOwn.call( ns, i ) ) {
2036 | if ( ns[ i ].rows.length === 1 && hasOwn.call( os, i ) && os[ i ].rows.length === 1 ) {
2037 | n[ ns[ i ].rows[ 0 ] ] = {
2038 | text: n[ ns[ i ].rows[ 0 ] ],
2039 | row: os[ i ].rows[ 0 ]
2040 | };
2041 | o[ os[ i ].rows[ 0 ] ] = {
2042 | text: o[ os[ i ].rows[ 0 ] ],
2043 | row: ns[ i ].rows[ 0 ]
2044 | };
2045 | }
2046 | }
2047 | }
2048 |
2049 | for ( i = 0; i < n.length - 1; i++ ) {
2050 | if ( n[ i ].text != null && n[ i + 1 ].text == null && n[ i ].row + 1 < o.length && o[ n[ i ].row + 1 ].text == null &&
2051 | n[ i + 1 ] == o[ n[ i ].row + 1 ] ) {
2052 |
2053 | n[ i + 1 ] = {
2054 | text: n[ i + 1 ],
2055 | row: n[ i ].row + 1
2056 | };
2057 | o[ n[ i ].row + 1 ] = {
2058 | text: o[ n[ i ].row + 1 ],
2059 | row: i + 1
2060 | };
2061 | }
2062 | }
2063 |
2064 | for ( i = n.length - 1; i > 0; i-- ) {
2065 | if ( n[ i ].text != null && n[ i - 1 ].text == null && n[ i ].row > 0 && o[ n[ i ].row - 1 ].text == null &&
2066 | n[ i - 1 ] == o[ n[ i ].row - 1 ] ) {
2067 |
2068 | n[ i - 1 ] = {
2069 | text: n[ i - 1 ],
2070 | row: n[ i ].row - 1
2071 | };
2072 | o[ n[ i ].row - 1 ] = {
2073 | text: o[ n[ i ].row - 1 ],
2074 | row: i - 1
2075 | };
2076 | }
2077 | }
2078 |
2079 | return {
2080 | o: o,
2081 | n: n
2082 | };
2083 | }
2084 |
2085 | return function( o, n ) {
2086 | o = o.replace( /\s+$/, "" );
2087 | n = n.replace( /\s+$/, "" );
2088 |
2089 | var i, pre,
2090 | str = "",
2091 | out = diff( o === "" ? [] : o.split( /\s+/ ), n === "" ? [] : n.split( /\s+/ ) ),
2092 | oSpace = o.match( /\s+/g ),
2093 | nSpace = n.match( /\s+/g );
2094 |
2095 | if ( oSpace == null ) {
2096 | oSpace = [ " " ];
2097 | } else {
2098 | oSpace.push( " " );
2099 | }
2100 |
2101 | if ( nSpace == null ) {
2102 | nSpace = [ " " ];
2103 | } else {
2104 | nSpace.push( " " );
2105 | }
2106 |
2107 | if ( out.n.length === 0 ) {
2108 | for ( i = 0; i < out.o.length; i++ ) {
2109 | str += "" + out.o[ i ] + oSpace[ i ] + "";
2110 | }
2111 | } else {
2112 | if ( out.n[ 0 ].text == null ) {
2113 | for ( n = 0; n < out.o.length && out.o[ n ].text == null; n++ ) {
2114 | str += "" + out.o[ n ] + oSpace[ n ] + "";
2115 | }
2116 | }
2117 |
2118 | for ( i = 0; i < out.n.length; i++ ) {
2119 | if ( out.n[ i ].text == null ) {
2120 | str += "" + out.n[ i ] + nSpace[ i ] + " ";
2121 | } else {
2122 |
2123 | // `pre` initialized at top of scope
2124 | pre = "";
2125 |
2126 | for ( n = out.n[ i ].row + 1; n < out.o.length && out.o[ n ].text == null; n++ ) {
2127 | pre += "" + out.o[ n ] + oSpace[ n ] + "";
2128 | }
2129 | str += " " + out.n[ i ].text + nSpace[ i ] + pre;
2130 | }
2131 | }
2132 | }
2133 |
2134 | return str;
2135 | };
2136 | }());
2137 | // jscs:enable
2138 |
2139 | (function() {
2140 |
2141 | // Deprecated QUnit.init - Ref #530
2142 | // Re-initialize the configuration options
2143 | QUnit.init = function() {
2144 | var tests, banner, result, qunit,
2145 | config = QUnit.config;
2146 |
2147 | config.stats = { all: 0, bad: 0 };
2148 | config.moduleStats = { all: 0, bad: 0 };
2149 | config.started = 0;
2150 | config.updateRate = 1000;
2151 | config.blocking = false;
2152 | config.autostart = true;
2153 | config.autorun = false;
2154 | config.filter = "";
2155 | config.queue = [];
2156 |
2157 | // Return on non-browser environments
2158 | // This is necessary to not break on node tests
2159 | if ( typeof window === "undefined" ) {
2160 | return;
2161 | }
2162 |
2163 | qunit = id( "qunit" );
2164 | if ( qunit ) {
2165 | qunit.innerHTML =
2166 | "" +
2167 | " " +
2168 | "
" +
2169 | " " +
2170 | " ";
2171 | }
2172 |
2173 | tests = id( "qunit-tests" );
2174 | banner = id( "qunit-banner" );
2175 | result = id( "qunit-testresult" );
2176 |
2177 | if ( tests ) {
2178 | tests.innerHTML = "";
2179 | }
2180 |
2181 | if ( banner ) {
2182 | banner.className = "";
2183 | }
2184 |
2185 | if ( result ) {
2186 | result.parentNode.removeChild( result );
2187 | }
2188 |
2189 | if ( tests ) {
2190 | result = document.createElement( "p" );
2191 | result.id = "qunit-testresult";
2192 | result.className = "result";
2193 | tests.parentNode.insertBefore( result, tests );
2194 | result.innerHTML = "Running... ";
2195 | }
2196 | };
2197 |
2198 | // Don't load the HTML Reporter on non-Browser environments
2199 | if ( typeof window === "undefined" ) {
2200 | return;
2201 | }
2202 |
2203 | var config = QUnit.config,
2204 | hasOwn = Object.prototype.hasOwnProperty,
2205 | defined = {
2206 | document: window.document !== undefined,
2207 | sessionStorage: (function() {
2208 | var x = "qunit-test-string";
2209 | try {
2210 | sessionStorage.setItem( x, x );
2211 | sessionStorage.removeItem( x );
2212 | return true;
2213 | } catch ( e ) {
2214 | return false;
2215 | }
2216 | }())
2217 | },
2218 | modulesList = [];
2219 |
2220 | /**
2221 | * Escape text for attribute or text content.
2222 | */
2223 | function escapeText( s ) {
2224 | if ( !s ) {
2225 | return "";
2226 | }
2227 | s = s + "";
2228 |
2229 | // Both single quotes and double quotes (for attributes)
2230 | return s.replace( /['"<>&]/g, function( s ) {
2231 | switch ( s ) {
2232 | case "'":
2233 | return "'";
2234 | case "\"":
2235 | return """;
2236 | case "<":
2237 | return "<";
2238 | case ">":
2239 | return ">";
2240 | case "&":
2241 | return "&";
2242 | }
2243 | });
2244 | }
2245 |
2246 | /**
2247 | * @param {HTMLElement} elem
2248 | * @param {string} type
2249 | * @param {Function} fn
2250 | */
2251 | function addEvent( elem, type, fn ) {
2252 | if ( elem.addEventListener ) {
2253 |
2254 | // Standards-based browsers
2255 | elem.addEventListener( type, fn, false );
2256 | } else if ( elem.attachEvent ) {
2257 |
2258 | // support: IE <9
2259 | elem.attachEvent( "on" + type, fn );
2260 | }
2261 | }
2262 |
2263 | /**
2264 | * @param {Array|NodeList} elems
2265 | * @param {string} type
2266 | * @param {Function} fn
2267 | */
2268 | function addEvents( elems, type, fn ) {
2269 | var i = elems.length;
2270 | while ( i-- ) {
2271 | addEvent( elems[ i ], type, fn );
2272 | }
2273 | }
2274 |
2275 | function hasClass( elem, name ) {
2276 | return ( " " + elem.className + " " ).indexOf( " " + name + " " ) >= 0;
2277 | }
2278 |
2279 | function addClass( elem, name ) {
2280 | if ( !hasClass( elem, name ) ) {
2281 | elem.className += ( elem.className ? " " : "" ) + name;
2282 | }
2283 | }
2284 |
2285 | function toggleClass( elem, name ) {
2286 | if ( hasClass( elem, name ) ) {
2287 | removeClass( elem, name );
2288 | } else {
2289 | addClass( elem, name );
2290 | }
2291 | }
2292 |
2293 | function removeClass( elem, name ) {
2294 | var set = " " + elem.className + " ";
2295 |
2296 | // Class name may appear multiple times
2297 | while ( set.indexOf( " " + name + " " ) >= 0 ) {
2298 | set = set.replace( " " + name + " ", " " );
2299 | }
2300 |
2301 | // trim for prettiness
2302 | elem.className = typeof set.trim === "function" ? set.trim() : set.replace( /^\s+|\s+$/g, "" );
2303 | }
2304 |
2305 | function id( name ) {
2306 | return defined.document && document.getElementById && document.getElementById( name );
2307 | }
2308 |
2309 | function getUrlConfigHtml() {
2310 | var i, j, val,
2311 | escaped, escapedTooltip,
2312 | selection = false,
2313 | len = config.urlConfig.length,
2314 | urlConfigHtml = "";
2315 |
2316 | for ( i = 0; i < len; i++ ) {
2317 | val = config.urlConfig[ i ];
2318 | if ( typeof val === "string" ) {
2319 | val = {
2320 | id: val,
2321 | label: val
2322 | };
2323 | }
2324 |
2325 | escaped = escapeText( val.id );
2326 | escapedTooltip = escapeText( val.tooltip );
2327 |
2328 | if ( config[ val.id ] === undefined ) {
2329 | config[ val.id ] = QUnit.urlParams[ val.id ];
2330 | }
2331 |
2332 | if ( !val.value || typeof val.value === "string" ) {
2333 | urlConfigHtml += "" + val.label + " ";
2339 | } else {
2340 | urlConfigHtml += "" + val.label +
2342 | ": ";
2344 |
2345 | if ( QUnit.is( "array", val.value ) ) {
2346 | for ( j = 0; j < val.value.length; j++ ) {
2347 | escaped = escapeText( val.value[ j ] );
2348 | urlConfigHtml += "" + escaped + " ";
2352 | }
2353 | } else {
2354 | for ( j in val.value ) {
2355 | if ( hasOwn.call( val.value, j ) ) {
2356 | urlConfigHtml += "" + escapeText( val.value[ j ] ) + " ";
2360 | }
2361 | }
2362 | }
2363 | if ( config[ val.id ] && !selection ) {
2364 | escaped = escapeText( config[ val.id ] );
2365 | urlConfigHtml += "" + escaped + " ";
2367 | }
2368 | urlConfigHtml += " ";
2369 | }
2370 | }
2371 |
2372 | return urlConfigHtml;
2373 | }
2374 |
2375 | // Handle "click" events on toolbar checkboxes and "change" for select menus.
2376 | // Updates the URL with the new state of `config.urlConfig` values.
2377 | function toolbarChanged() {
2378 | var updatedUrl, value,
2379 | field = this,
2380 | params = {};
2381 |
2382 | // Detect if field is a select menu or a checkbox
2383 | if ( "selectedIndex" in field ) {
2384 | value = field.options[ field.selectedIndex ].value || undefined;
2385 | } else {
2386 | value = field.checked ? ( field.defaultValue || true ) : undefined;
2387 | }
2388 |
2389 | params[ field.name ] = value;
2390 | updatedUrl = setUrl( params );
2391 |
2392 | if ( "hidepassed" === field.name && "replaceState" in window.history ) {
2393 | config[ field.name ] = value || false;
2394 | if ( value ) {
2395 | addClass( id( "qunit-tests" ), "hidepass" );
2396 | } else {
2397 | removeClass( id( "qunit-tests" ), "hidepass" );
2398 | }
2399 |
2400 | // It is not necessary to refresh the whole page
2401 | window.history.replaceState( null, "", updatedUrl );
2402 | } else {
2403 | window.location = updatedUrl;
2404 | }
2405 | }
2406 |
2407 | function setUrl( params ) {
2408 | var key,
2409 | querystring = "?";
2410 |
2411 | params = QUnit.extend( QUnit.extend( {}, QUnit.urlParams ), params );
2412 |
2413 | for ( key in params ) {
2414 | if ( hasOwn.call( params, key ) ) {
2415 | if ( params[ key ] === undefined ) {
2416 | continue;
2417 | }
2418 | querystring += encodeURIComponent( key );
2419 | if ( params[ key ] !== true ) {
2420 | querystring += "=" + encodeURIComponent( params[ key ] );
2421 | }
2422 | querystring += "&";
2423 | }
2424 | }
2425 | return location.protocol + "//" + location.host +
2426 | location.pathname + querystring.slice( 0, -1 );
2427 | }
2428 |
2429 | function applyUrlParams() {
2430 | var selectBox = id( "qunit-modulefilter" ),
2431 | selection = decodeURIComponent( selectBox.options[ selectBox.selectedIndex ].value ),
2432 | filter = id( "qunit-filter-input" ).value;
2433 |
2434 | window.location = setUrl({
2435 | module: ( selection === "" ) ? undefined : selection,
2436 | filter: ( filter === "" ) ? undefined : filter,
2437 |
2438 | // Remove testId filter
2439 | testId: undefined
2440 | });
2441 | }
2442 |
2443 | function toolbarUrlConfigContainer() {
2444 | var urlConfigContainer = document.createElement( "span" );
2445 |
2446 | urlConfigContainer.innerHTML = getUrlConfigHtml();
2447 | addClass( urlConfigContainer, "qunit-url-config" );
2448 |
2449 | // For oldIE support:
2450 | // * Add handlers to the individual elements instead of the container
2451 | // * Use "click" instead of "change" for checkboxes
2452 | addEvents( urlConfigContainer.getElementsByTagName( "input" ), "click", toolbarChanged );
2453 | addEvents( urlConfigContainer.getElementsByTagName( "select" ), "change", toolbarChanged );
2454 |
2455 | return urlConfigContainer;
2456 | }
2457 |
2458 | function toolbarLooseFilter() {
2459 | var filter = document.createElement( "form" ),
2460 | label = document.createElement( "label" ),
2461 | input = document.createElement( "input" ),
2462 | button = document.createElement( "button" );
2463 |
2464 | addClass( filter, "qunit-filter" );
2465 |
2466 | label.innerHTML = "Filter: ";
2467 |
2468 | input.type = "text";
2469 | input.value = config.filter || "";
2470 | input.name = "filter";
2471 | input.id = "qunit-filter-input";
2472 |
2473 | button.innerHTML = "Go";
2474 |
2475 | label.appendChild( input );
2476 |
2477 | filter.appendChild( label );
2478 | filter.appendChild( button );
2479 | addEvent( filter, "submit", function( ev ) {
2480 | applyUrlParams();
2481 |
2482 | if ( ev && ev.preventDefault ) {
2483 | ev.preventDefault();
2484 | }
2485 |
2486 | return false;
2487 | });
2488 |
2489 | return filter;
2490 | }
2491 |
2492 | function toolbarModuleFilterHtml() {
2493 | var i,
2494 | moduleFilterHtml = "";
2495 |
2496 | if ( !modulesList.length ) {
2497 | return false;
2498 | }
2499 |
2500 | modulesList.sort(function( a, b ) {
2501 | return a.localeCompare( b );
2502 | });
2503 |
2504 | moduleFilterHtml += "Module: " +
2505 | "< All Modules > ";
2508 |
2509 | for ( i = 0; i < modulesList.length; i++ ) {
2510 | moduleFilterHtml += "" + escapeText( modulesList[ i ] ) + " ";
2514 | }
2515 | moduleFilterHtml += " ";
2516 |
2517 | return moduleFilterHtml;
2518 | }
2519 |
2520 | function toolbarModuleFilter() {
2521 | var toolbar = id( "qunit-testrunner-toolbar" ),
2522 | moduleFilter = document.createElement( "span" ),
2523 | moduleFilterHtml = toolbarModuleFilterHtml();
2524 |
2525 | if ( !toolbar || !moduleFilterHtml ) {
2526 | return false;
2527 | }
2528 |
2529 | moduleFilter.setAttribute( "id", "qunit-modulefilter-container" );
2530 | moduleFilter.innerHTML = moduleFilterHtml;
2531 |
2532 | addEvent( moduleFilter.lastChild, "change", applyUrlParams );
2533 |
2534 | toolbar.appendChild( moduleFilter );
2535 | }
2536 |
2537 | function appendToolbar() {
2538 | var toolbar = id( "qunit-testrunner-toolbar" );
2539 |
2540 | if ( toolbar ) {
2541 | toolbar.appendChild( toolbarUrlConfigContainer() );
2542 | toolbar.appendChild( toolbarLooseFilter() );
2543 | }
2544 | }
2545 |
2546 | function appendHeader() {
2547 | var header = id( "qunit-header" );
2548 |
2549 | if ( header ) {
2550 | header.innerHTML = "" + header.innerHTML + " ";
2553 | }
2554 | }
2555 |
2556 | function appendBanner() {
2557 | var banner = id( "qunit-banner" );
2558 |
2559 | if ( banner ) {
2560 | banner.className = "";
2561 | }
2562 | }
2563 |
2564 | function appendTestResults() {
2565 | var tests = id( "qunit-tests" ),
2566 | result = id( "qunit-testresult" );
2567 |
2568 | if ( result ) {
2569 | result.parentNode.removeChild( result );
2570 | }
2571 |
2572 | if ( tests ) {
2573 | tests.innerHTML = "";
2574 | result = document.createElement( "p" );
2575 | result.id = "qunit-testresult";
2576 | result.className = "result";
2577 | tests.parentNode.insertBefore( result, tests );
2578 | result.innerHTML = "Running... ";
2579 | }
2580 | }
2581 |
2582 | function storeFixture() {
2583 | var fixture = id( "qunit-fixture" );
2584 | if ( fixture ) {
2585 | config.fixture = fixture.innerHTML;
2586 | }
2587 | }
2588 |
2589 | function appendUserAgent() {
2590 | var userAgent = id( "qunit-userAgent" );
2591 | if ( userAgent ) {
2592 | userAgent.innerHTML = "";
2593 | userAgent.appendChild( document.createTextNode( navigator.userAgent ) );
2594 | }
2595 | }
2596 |
2597 | function appendTestsList( modules ) {
2598 | var i, l, x, z, test, moduleObj;
2599 |
2600 | for ( i = 0, l = modules.length; i < l; i++ ) {
2601 | moduleObj = modules[ i ];
2602 |
2603 | if ( moduleObj.name ) {
2604 | modulesList.push( moduleObj.name );
2605 | }
2606 |
2607 | for ( x = 0, z = moduleObj.tests.length; x < z; x++ ) {
2608 | test = moduleObj.tests[ x ];
2609 |
2610 | appendTest( test.name, test.testId, moduleObj.name );
2611 | }
2612 | }
2613 | }
2614 |
2615 | function appendTest( name, testId, moduleName ) {
2616 | var title, rerunTrigger, testBlock, assertList,
2617 | tests = id( "qunit-tests" );
2618 |
2619 | if ( !tests ) {
2620 | return;
2621 | }
2622 |
2623 | title = document.createElement( "strong" );
2624 | title.innerHTML = getNameHtml( name, moduleName );
2625 |
2626 | rerunTrigger = document.createElement( "a" );
2627 | rerunTrigger.innerHTML = "Rerun";
2628 | rerunTrigger.href = setUrl({ testId: testId });
2629 |
2630 | testBlock = document.createElement( "li" );
2631 | testBlock.appendChild( title );
2632 | testBlock.appendChild( rerunTrigger );
2633 | testBlock.id = "qunit-test-output-" + testId;
2634 |
2635 | assertList = document.createElement( "ol" );
2636 | assertList.className = "qunit-assert-list";
2637 |
2638 | testBlock.appendChild( assertList );
2639 |
2640 | tests.appendChild( testBlock );
2641 | }
2642 |
2643 | // HTML Reporter initialization and load
2644 | QUnit.begin(function( details ) {
2645 | var qunit = id( "qunit" );
2646 |
2647 | // Fixture is the only one necessary to run without the #qunit element
2648 | storeFixture();
2649 |
2650 | if ( qunit ) {
2651 | qunit.innerHTML =
2652 | "" +
2653 | " " +
2654 | "
" +
2655 | " " +
2656 | " ";
2657 | }
2658 |
2659 | appendHeader();
2660 | appendBanner();
2661 | appendTestResults();
2662 | appendUserAgent();
2663 | appendToolbar();
2664 | appendTestsList( details.modules );
2665 | toolbarModuleFilter();
2666 |
2667 | if ( qunit && config.hidepassed ) {
2668 | addClass( qunit.lastChild, "hidepass" );
2669 | }
2670 | });
2671 |
2672 | QUnit.done(function( details ) {
2673 | var i, key,
2674 | banner = id( "qunit-banner" ),
2675 | tests = id( "qunit-tests" ),
2676 | html = [
2677 | "Tests completed in ",
2678 | details.runtime,
2679 | " milliseconds. ",
2680 | "",
2681 | details.passed,
2682 | " assertions of ",
2683 | details.total,
2684 | " passed, ",
2685 | details.failed,
2686 | " failed."
2687 | ].join( "" );
2688 |
2689 | if ( banner ) {
2690 | banner.className = details.failed ? "qunit-fail" : "qunit-pass";
2691 | }
2692 |
2693 | if ( tests ) {
2694 | id( "qunit-testresult" ).innerHTML = html;
2695 | }
2696 |
2697 | if ( config.altertitle && defined.document && document.title ) {
2698 |
2699 | // show ✖ for good, ✔ for bad suite result in title
2700 | // use escape sequences in case file gets loaded with non-utf-8-charset
2701 | document.title = [
2702 | ( details.failed ? "\u2716" : "\u2714" ),
2703 | document.title.replace( /^[\u2714\u2716] /i, "" )
2704 | ].join( " " );
2705 | }
2706 |
2707 | // clear own sessionStorage items if all tests passed
2708 | if ( config.reorder && defined.sessionStorage && details.failed === 0 ) {
2709 | for ( i = 0; i < sessionStorage.length; i++ ) {
2710 | key = sessionStorage.key( i++ );
2711 | if ( key.indexOf( "qunit-test-" ) === 0 ) {
2712 | sessionStorage.removeItem( key );
2713 | }
2714 | }
2715 | }
2716 |
2717 | // scroll back to top to show results
2718 | if ( config.scrolltop && window.scrollTo ) {
2719 | window.scrollTo( 0, 0 );
2720 | }
2721 | });
2722 |
2723 | function getNameHtml( name, module ) {
2724 | var nameHtml = "";
2725 |
2726 | if ( module ) {
2727 | nameHtml = "" + escapeText( module ) + " : ";
2728 | }
2729 |
2730 | nameHtml += "" + escapeText( name ) + " ";
2731 |
2732 | return nameHtml;
2733 | }
2734 |
2735 | QUnit.testStart(function( details ) {
2736 | var running, testBlock;
2737 |
2738 | testBlock = id( "qunit-test-output-" + details.testId );
2739 | if ( testBlock ) {
2740 | testBlock.className = "running";
2741 | } else {
2742 |
2743 | // Report later registered tests
2744 | appendTest( details.name, details.testId, details.module );
2745 | }
2746 |
2747 | running = id( "qunit-testresult" );
2748 | if ( running ) {
2749 | running.innerHTML = "Running: " + getNameHtml( details.name, details.module );
2750 | }
2751 |
2752 | });
2753 |
2754 | QUnit.log(function( details ) {
2755 | var assertList, assertLi,
2756 | message, expected, actual,
2757 | testItem = id( "qunit-test-output-" + details.testId );
2758 |
2759 | if ( !testItem ) {
2760 | return;
2761 | }
2762 |
2763 | message = escapeText( details.message ) || ( details.result ? "okay" : "failed" );
2764 | message = "" + message + " ";
2765 | message += "@ " + details.runtime + " ms ";
2766 |
2767 | // pushFailure doesn't provide details.expected
2768 | // when it calls, it's implicit to also not show expected and diff stuff
2769 | // Also, we need to check details.expected existence, as it can exist and be undefined
2770 | if ( !details.result && hasOwn.call( details, "expected" ) ) {
2771 | expected = escapeText( QUnit.dump.parse( details.expected ) );
2772 | actual = escapeText( QUnit.dump.parse( details.actual ) );
2773 | message += "Expected: " +
2774 | expected +
2775 | " ";
2776 |
2777 | if ( actual !== expected ) {
2778 | message += "Result: " +
2779 | actual + " " +
2780 | "Diff: " +
2781 | QUnit.diff( expected, actual ) + " ";
2782 | }
2783 |
2784 | if ( details.source ) {
2785 | message += "Source: " +
2786 | escapeText( details.source ) + " ";
2787 | }
2788 |
2789 | message += "
";
2790 |
2791 | // this occours when pushFailure is set and we have an extracted stack trace
2792 | } else if ( !details.result && details.source ) {
2793 | message += "" +
2794 | "Source: " +
2795 | escapeText( details.source ) + " " +
2796 | "
";
2797 | }
2798 |
2799 | assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
2800 |
2801 | assertLi = document.createElement( "li" );
2802 | assertLi.className = details.result ? "pass" : "fail";
2803 | assertLi.innerHTML = message;
2804 | assertList.appendChild( assertLi );
2805 | });
2806 |
2807 | QUnit.testDone(function( details ) {
2808 | var testTitle, time, testItem, assertList,
2809 | good, bad, testCounts, skipped,
2810 | tests = id( "qunit-tests" );
2811 |
2812 | if ( !tests ) {
2813 | return;
2814 | }
2815 |
2816 | testItem = id( "qunit-test-output-" + details.testId );
2817 |
2818 | assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
2819 |
2820 | good = details.passed;
2821 | bad = details.failed;
2822 |
2823 | // store result when possible
2824 | if ( config.reorder && defined.sessionStorage ) {
2825 | if ( bad ) {
2826 | sessionStorage.setItem( "qunit-test-" + details.module + "-" + details.name, bad );
2827 | } else {
2828 | sessionStorage.removeItem( "qunit-test-" + details.module + "-" + details.name );
2829 | }
2830 | }
2831 |
2832 | if ( bad === 0 ) {
2833 | addClass( assertList, "qunit-collapsed" );
2834 | }
2835 |
2836 | // testItem.firstChild is the test name
2837 | testTitle = testItem.firstChild;
2838 |
2839 | testCounts = bad ?
2840 | "" + bad + " , " + "" + good + " , " :
2841 | "";
2842 |
2843 | testTitle.innerHTML += " (" + testCounts +
2844 | details.assertions.length + ") ";
2845 |
2846 | if ( details.skipped ) {
2847 | testItem.className = "skipped";
2848 | skipped = document.createElement( "em" );
2849 | skipped.className = "qunit-skipped-label";
2850 | skipped.innerHTML = "skipped";
2851 | testItem.insertBefore( skipped, testTitle );
2852 | } else {
2853 | addEvent( testTitle, "click", function() {
2854 | toggleClass( assertList, "qunit-collapsed" );
2855 | });
2856 |
2857 | testItem.className = bad ? "fail" : "pass";
2858 |
2859 | time = document.createElement( "span" );
2860 | time.className = "runtime";
2861 | time.innerHTML = details.runtime + " ms";
2862 | testItem.insertBefore( time, assertList );
2863 | }
2864 | });
2865 |
2866 | if ( !defined.document || document.readyState === "complete" ) {
2867 | config.pageLoaded = true;
2868 | config.autorun = true;
2869 | }
2870 |
2871 | if ( defined.document ) {
2872 | addEvent( window, "load", QUnit.load );
2873 | }
2874 |
2875 | })();
--------------------------------------------------------------------------------