├── .gitmodules
├── LICENSE.txt
├── Namespace.js
├── example
├── com
│ └── sandbox
│ │ ├── MyNameIsClass.js
│ │ ├── SayHiClass.js
│ │ └── classes.js
├── index.html
└── sandbox.js
└── test
├── assets
├── jsunittest.js
└── unittest.css
└── unit
├── fixtures
└── a.js
├── index.html
└── namespace_test.js
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "vendor/jsunittest"]
2 | path = vendor/jsunittest
3 | url = git://github.com/drnic/jsunittest.git
4 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | NAMESPACE.JS
2 |
3 | The MIT License
4 |
5 | Copyright (c) 2009 Maxime Bouroumeau-Fuseau
6 |
7 | Permission is hereby granted, free of charge, to any person obtaining a copy
8 | of this software and associated documentation files (the "Software"), to deal
9 | in the Software without restriction, including without limitation the rights
10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | copies of the Software, and to permit persons to whom the Software is
12 | furnished to do so, subject to the following conditions:
13 |
14 | The above copyright notice and this permission notice shall be included in
15 | all copies or substantial portions of the Software.
16 |
17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | THE SOFTWARE.
24 |
25 |
--------------------------------------------------------------------------------
/Namespace.js:
--------------------------------------------------------------------------------
1 | /*
2 | Script: Namespace.js
3 | Namespace utility
4 |
5 | Copyright:
6 | Copyright (c) 2009 Maxime Bouroumeau-Fuseau
7 |
8 | License:
9 | MIT-style license.
10 |
11 | Version:
12 | 1.1
13 | */
14 |
15 | /*jslint evil : true */
16 | /*global Namespace, XMLHttpRequest, ActiveXObject, window, document */
17 |
18 | var Namespace = (function() {
19 |
20 | var _listeners = {};
21 | var _includedIdentifiers = [];
22 |
23 | /**
24 | * Returns an object in an array unless the object is an array
25 | *
26 | * @param mixed obj
27 | * @return Array
28 | */
29 | var _toArray = function(obj) {
30 | // checks if it's an array
31 | if (typeof(obj) == 'object' && obj.sort) {
32 | return obj;
33 | }
34 | return Array(obj);
35 | };
36 |
37 | /**
38 | * Creates an XMLHttpRequest object
39 | *
40 | * @return XMLHttpRequest
41 | */
42 | var _createXmlHttpRequest = function() {
43 | var xhr;
44 | try { xhr = new XMLHttpRequest(); } catch(e) {
45 | try { xhr = new ActiveXObject("Msxml2.XMLHTTP.6.0"); } catch(e2) {
46 | try { xhr = new ActiveXObject("Msxml2.XMLHTTP.3.0"); } catch(e3) {
47 | try { xhr = new ActiveXObject("Msxml2.XMLHTTP"); } catch(e4) {
48 | try { xhr = new ActiveXObject("Microsoft.XMLHTTP"); } catch(e5) {
49 | throw new Error( "This browser does not support XMLHttpRequest." );
50 | }
51 | }
52 | }
53 | }
54 | }
55 | return xhr;
56 | };
57 |
58 | /**
59 | * Checks if an http request is successful based on its status code.
60 | * Borrowed from dojo (http://www.dojotoolkit.org).
61 | *
62 | * @param Integer status Http status code
63 | * @return Boolean
64 | */
65 | var _isHttpRequestSuccessful = function(status) {
66 | return (status >= 200 && status < 300) || // Boolean
67 | status == 304 || // allow any 2XX response code
68 | status == 1223 || // get it out of the cache
69 | (!status && (window.location.protocol == "file:" || window.location.protocol == "chrome:") ); // Internet Explorer mangled the status code
70 | };
71 |
72 | /**
73 | * Creates a script tag with the specified data as content
74 | *
75 | * @param String data The content of the script
76 | */
77 | var _createScript = function(data) {
78 | var script = document.createElement('script');
79 | script.type = 'text/javascript';
80 | script.text = data;
81 |
82 | if (typeof window.execScript === "object") { // According to IE
83 | window.execScript(data);
84 | } else {
85 | try { // Attempt body insertion
86 | document.body.appendChild(script);
87 | } catch (e) { // Fall back on eval
88 | window['eval'](data);
89 | }
90 | }
91 | };
92 |
93 | /**
94 | * Dispatches an event
95 | *
96 | * @param String eventName
97 | * @param Object properties
98 | */
99 | var _dispatchEvent = function(eventName, properties) {
100 | if (!_listeners[eventName]) { return; }
101 | properties.event = eventName;
102 | for (var i = 0; i < _listeners[eventName].length; i++) {
103 | _listeners[eventName][i](properties);
104 | }
105 | };
106 |
107 | /**
108 | * Creates an Object following the specified namespace identifier.
109 | *
110 | * @public
111 | * @param String identifier The namespace string
112 | * @param Object klasses (OPTIONAL) An object which properties will be added to the namespace
113 | * @return Object The most inner object
114 | */
115 | var _namespace = function(identifier) {
116 | var klasses = arguments[1] || false;
117 | var ns = window;
118 |
119 | if (identifier !== '') {
120 | var parts = identifier.split(Namespace.separator);
121 | for (var i = 0; i < parts.length; i++) {
122 | if (!ns[parts[i]]) {
123 | ns[parts[i]] = {};
124 | }
125 | ns = ns[parts[i]];
126 | }
127 | }
128 |
129 | if (klasses) {
130 | for (var klass in klasses) {
131 | if (klasses.hasOwnProperty(klass)) {
132 | ns[klass] = klasses[klass];
133 | }
134 | }
135 | }
136 |
137 | _dispatchEvent('create', { 'identifier': identifier });
138 | return ns;
139 | };
140 |
141 | /**
142 | * Checks if the specified identifier is defined
143 | *
144 | * @public
145 | * @param String identifier The namespace string
146 | * @return Boolean
147 | */
148 | _namespace.exist = function(identifier) {
149 | if (identifier === '') { return true; }
150 |
151 | var parts = identifier.split(Namespace.separator);
152 | var ns = window;
153 | for (var i = 0; i < parts.length; i++) {
154 | if (!ns[parts[i]]) {
155 | return false;
156 | }
157 | ns = ns[parts[i]];
158 | }
159 |
160 | return true;
161 | };
162 |
163 | /**
164 | * Maps an identifier to a uri. Is public so it can be overriden by custom scripts.
165 | *
166 | * @public
167 | * @param String identifier The namespace identifier
168 | * @return String The uri
169 | */
170 | _namespace.mapIdentifierToUri = function(identifier) {
171 | var regexp = new RegExp('\\' + Namespace.separator, 'g');
172 | return Namespace.baseUri + identifier.replace(regexp, '/') + '.js';
173 | };
174 |
175 | /**
176 | * Loads a remote script atfer mapping the identifier to an uri
177 | *
178 | * @param String identifier The namespace identifier
179 | * @param Function successCallback When set, the file will be loaded asynchronously. Will be called when the file is loaded
180 | * @param Function errorCallback Callback to be called when an error occurs
181 | * @return Boolean Success of failure when loading synchronously
182 | */
183 | var _loadScript = function(identifier) {
184 | var successCallback = arguments[1];
185 | var errorCallback = arguments[2];
186 | var async = typeof successCallback === "function";
187 | var uri = _namespace.mapIdentifierToUri(identifier);
188 | var event = { 'identifier': identifier, 'uri': uri, 'async': async, 'callback': successCallback };
189 |
190 | var xhr = _createXmlHttpRequest();
191 | xhr.open("GET", uri, async);
192 |
193 | if (async) {
194 | xhr.onreadystatechange = function() {
195 | if (xhr.readyState == 4) {
196 | if (_isHttpRequestSuccessful(xhr.status || 0)) {
197 | _createScript(xhr.responseText);
198 | _dispatchEvent('include', event);
199 | successCallback();
200 | return;
201 | }
202 | event.status = xhr.status;
203 | _dispatchEvent('includeError', event);
204 | if (typeof errorCallback === "function") {
205 | errorCallback();
206 | }
207 | }
208 | };
209 | }
210 |
211 | xhr.send(null);
212 |
213 | if (!async) {
214 | if (_isHttpRequestSuccessful(xhr.status || 0)) {
215 | _createScript(xhr.responseText);
216 | _dispatchEvent('include', event);
217 | return true;
218 | }
219 | event.status = xhr.status;
220 | _dispatchEvent('includeError', event);
221 | return false;
222 | }
223 | };
224 |
225 | /**
226 | * Includes a remote javascript file identified by the specified namespace string. The identifier
227 | * must point to a class. Separators in the string will be converted to slashes and the .js extension will be appended.
228 | *
229 | * @public
230 | * @param String identifier The namespace string
231 | * @param Function callback (OPTIONAL) A function to call when the remote script has been included
232 | */
233 | _namespace.include = function(identifier) {
234 | var successCallback = arguments[1] || false;
235 | var errorCallback = arguments[2] || false;
236 |
237 | // checks if the identifier is not already included
238 | if (_includedIdentifiers[identifier]) {
239 | if (typeof successCallback === "function") { successCallback(); }
240 | return true;
241 | }
242 |
243 | if (successCallback) {
244 | _loadScript(identifier, function() {
245 | _includedIdentifiers[identifier] = true;
246 | successCallback();
247 | }, errorCallback);
248 | } else {
249 | if (_loadScript(identifier)) {
250 | _includedIdentifiers[identifier] = true;
251 | return true;
252 | }
253 | return false;
254 | }
255 | };
256 |
257 | /**
258 | * Imports properties from the specified namespace to the global space (ie. under window)
259 | *
260 | * The identifier string can contain the * wildcard character as its last segment (eg: com.test.*)
261 | * which will import all properties from the namespace.
262 | *
263 | * If not, the targeted namespace will be imported (ie. if com.test is imported, the test object
264 | * will now be global). If the targeted object is not found, it will be included using include().
265 | *
266 | * @public
267 | * @param String identifier The namespace string
268 | * @param Function callback (OPTIONAL) A function to call when the process is completed (including the include() if used)
269 | * @param Boolean autoInclude (OPTIONAL) Whether to automatically auto include the targeted object is not found. Default is Namespace.autoInclude
270 | */
271 | _namespace.use = function(identifier) {
272 | var identifiers = _toArray(identifier);
273 | var callback = arguments[1] || false;
274 | var autoInclude = arguments.length > 2 ? arguments[2] : Namespace.autoInclude;
275 | var event = { 'identifier': identifier };
276 | var parts, target, ns;
277 |
278 | for (var i = 0; i < identifiers.length; i++) {
279 | identifier = identifiers[i];
280 |
281 | parts = identifier.split(Namespace.separator);
282 | target = parts.pop();
283 | ns = _namespace(parts.join(Namespace.separator));
284 |
285 | if (target == '*') {
286 | // imports all objects from the identifier, can't use include() in that case
287 | for (var objectName in ns) {
288 | if (ns.hasOwnProperty(objectName)) {
289 | window[objectName] = ns[objectName];
290 | }
291 | }
292 | } else {
293 | // imports only one object
294 | if (ns[target]) {
295 | // the object exists, import it
296 | window[target] = ns[target];
297 | } else {
298 | // the object does not exist
299 | if (autoInclude) {
300 | // try to auto include it
301 | if (callback) {
302 | _namespace.include(identifier, function() {
303 | window[target] = ns[target];
304 |
305 | if (i + 1 < identifiers.length) {
306 | // we continue to unpack the rest from here
307 | _namespace.unpack(identifiers.slice(i + 1), callback, autoInclude);
308 | } else {
309 | // no more identifiers to unpack
310 | _dispatchEvent('use', event);
311 | if (typeof callback === "function") {
312 | callback();
313 | }
314 | }
315 | });
316 | return;
317 | } else {
318 | _namespace.include(identifier);
319 | window[target] = ns[target];
320 | }
321 | }
322 | }
323 | }
324 |
325 | }
326 |
327 | // all identifiers have been unpacked
328 | _dispatchEvent('use', event);
329 | if (typeof callback === "function") { callback(); }
330 | };
331 |
332 | /**
333 | * Binds the include() and unpack() method to a specified identifier
334 | *
335 | * @public
336 | * @param String identifier The namespace identifier
337 | * @return Object
338 | */
339 | _namespace.from = function(identifier) {
340 | return {
341 | /**
342 | * Includes the identifier specified in from()
343 | *
344 | * @see Namespace.include()
345 | */
346 | include: function() {
347 | var callback = arguments[0] || false;
348 | _namespace.include(identifier, callback);
349 | },
350 | /**
351 | * Includes the identifier specified in from() and unpack
352 | * the specified indentifier in _identifier
353 | *
354 | * @see Namespace.use()
355 | */
356 | use: function(_identifier) {
357 | var callback = arguments[1] || false;
358 | if (_identifier.charAt(0) == '.') {
359 | _identifier = identifier + _identifier;
360 | }
361 |
362 | if (callback) {
363 | _namespace.include(identifier, function() {
364 | _namespace.use(_identifier, callback, false);
365 | });
366 | } else {
367 | _namespace.include(identifier);
368 | _namespace.use(_identifier, callback, false);
369 | }
370 | }
371 | };
372 | };
373 |
374 | /**
375 | * Registers a namespace so it won't be included
376 | *
377 | * Idea and code submitted by Nathan Smith (http://github.com/smith)
378 | *
379 | * @param String|Array identifier
380 | */
381 | _namespace.provide = function(identifier) {
382 | var identifiers = _toArray(identifier);
383 |
384 | for (var i = 0; i < identifiers.length; i++) {
385 | if (!(identifier in _includedIdentifiers)) {
386 | _dispatchEvent('provide', { 'identifier': identifier });
387 | _includedIdentifiers[identifier] = true;
388 | }
389 | }
390 | };
391 |
392 | /**
393 | * Registers a function to be called when the specified event is dispatched
394 | *
395 | * @param String eventName
396 | * @param Function callback
397 | */
398 | _namespace.addEventListener = function(eventName, callback) {
399 | if (!_listeners[eventName]) { _listeners[eventName] = []; }
400 | _listeners[eventName].push(callback);
401 | };
402 |
403 | /**
404 | * Unregisters an event listener
405 | *
406 | * @param String eventName
407 | * @param Function callback
408 | */
409 | _namespace.removeEventListener = function(eventName, callback) {
410 | if (!_listeners[eventName]) { return; }
411 | for (var i = 0; i < _listeners[eventName].length; i++) {
412 | if (_listeners[eventName][i] == callback) {
413 | delete _listeners[eventName][i];
414 | return;
415 | }
416 | }
417 | };
418 |
419 | /**
420 | * Adds methods to javascript native's object
421 | * Inspired by http://thinkweb2.com/projects/prototype/namespacing-made-easy/
422 | *
423 | * @public
424 | */
425 | _namespace.registerNativeExtensions = function() {
426 | /**
427 | * @see Namespace()
428 | */
429 | String.prototype.namespace = function() {
430 | var klasses = arguments[0] || {};
431 | return _namespace(this.valueOf(), klasses);
432 | };
433 | /**
434 | * @see Namespace.include()
435 | */
436 | String.prototype.include = function() {
437 | var callback = arguments[0] || false;
438 | return _namespace.include(this.valueOf(), callback);
439 | };
440 | /**
441 | * @see Namespace.use()
442 | */
443 | String.prototype.use = function() {
444 | var callback = arguments[0] || false;
445 | return _namespace.use(this.valueOf(), callback);
446 | };
447 | /**
448 | * @see Namespace.from()
449 | */
450 | String.prototype.from = function() {
451 | return _namespace.from(this.valueOf());
452 | };
453 | /**
454 | * @see Namespace.provide()
455 | * Idea and code submitted by Nathan Smith (http://github.com/smith)
456 | */
457 | String.prototype.provide = function() {
458 | return _namespace.provide(this.valueOf());
459 | };
460 | /**
461 | * @see Namespace.use()
462 | */
463 | Array.prototype.use = function() {
464 | var callback = arguments[0] || false;
465 | return _namespace.use(this, callback);
466 | };
467 | /**
468 | * @see Namespace.provide()
469 | */
470 | Array.prototype.provide = function() {
471 | return _namespace.provide(this);
472 | };
473 | };
474 |
475 | return _namespace;
476 | })();
477 |
478 | /**
479 | * Namespace segment separator
480 | *
481 | * @var String
482 | */
483 | Namespace.separator = '.';
484 |
485 | /**
486 | * Base uri when using Namespace.include()
487 | * Must end with a slash
488 | *
489 | * @var String
490 | */
491 | Namespace.baseUri = './';
492 |
493 | /**
494 | * Whether to automatically call Namespace.include() when Namespace.import()
495 | * does not find the targeted object.
496 | *
497 | * @var Boolean
498 | */
499 | Namespace.autoInclude = true;
500 |
501 |
--------------------------------------------------------------------------------
/example/com/sandbox/MyNameIsClass.js:
--------------------------------------------------------------------------------
1 |
2 | Namespace('com.sandbox', {
3 |
4 | MyNameIsClass: function(name) {
5 | com.sandbox.Console.log('my name is ' + name);
6 | return {};
7 | }
8 |
9 | });
10 |
--------------------------------------------------------------------------------
/example/com/sandbox/SayHiClass.js:
--------------------------------------------------------------------------------
1 |
2 | Namespace('com.sandbox', {
3 |
4 | SayHiClass: function() {
5 | com.sandbox.Console.log('hi');
6 | return {};
7 | }
8 |
9 | });
10 |
--------------------------------------------------------------------------------
/example/com/sandbox/classes.js:
--------------------------------------------------------------------------------
1 |
2 | Namespace('com.sandbox.classes', {
3 |
4 | MyClass1: function() {
5 | com.sandbox.Console.log('MyClass1');
6 | return {};
7 | },
8 |
9 | MyClass2: function() {
10 | com.sandbox.Console.log('MyClass2');
11 | return {};
12 | }
13 |
14 | });
15 |
--------------------------------------------------------------------------------
/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | sandbox
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/example/sandbox.js:
--------------------------------------------------------------------------------
1 |
2 | // creates an empty namespace
3 | Namespace('com.sandbox');
4 |
5 | // creates or use a namespace and fill it with the specified properties
6 | Namespace('com.sandbox', {
7 |
8 | Console: {
9 | log: function(message) {
10 | var element = document.getElementById('console');
11 | element.innerHTML = element.innerHTML + message + '
';
12 | }
13 | }
14 |
15 | });
16 |
17 | // calls Console using the fully qualified name (fqn)
18 | com.sandbox.Console.log('hello world');
19 |
20 | // imports all properties from com.sandbox into the global space
21 | Namespace.use('com.sandbox.*');
22 | Console.log('unpacked hello world');
23 |
24 | // includes com/sandbox/SayHiClass.js
25 | // as we use a callback, file is loaded asynchronously
26 | Namespace.include('com.sandbox.SayHiClass', function() {
27 | new com.sandbox.SayHiClass();
28 | });
29 |
30 | // auto includes com/sandbox/MyNameIsClass.js file and imports MyNameIsClass into the global space
31 | // unpack() will use include(). will be async as we use a callback
32 | Namespace.use('com.sandbox.MyNameIsClass', function() {
33 | new MyNameIsClass('peter');
34 | });
35 |
36 | // imports all properties from com.sandbox.classes after including the file com/sandbox/classes.js
37 | // the use() identifier can also be relative to the identifier used in from() by starting
38 | // with a dot (would be .* in this case)
39 | Namespace.from('com.sandbox.classes').use('com.sandbox.classes.*', function() {
40 | new MyClass1();
41 | new MyClass2();
42 | });
43 |
44 | // register a listener for the includeError event
45 | Namespace.addEventListener('includeError', function(event) {
46 | Console.log('an error occured loading ' + event.identifier);
47 | });
48 | // try to include an inexistant file
49 | Namespace.include('com.sandbox.toto');
50 |
51 |
--------------------------------------------------------------------------------
/test/assets/jsunittest.js:
--------------------------------------------------------------------------------
1 | /* Jsunittest, version 0.7.2
2 | * (c) 2008 Dr Nic Williams
3 | *
4 | * Jsunittest is freely distributable under
5 | * the terms of an MIT-style license.
6 | * For details, see the web site: http://jsunittest.rubyforge.org
7 | *
8 | *--------------------------------------------------------------------------*/
9 |
10 | var JsUnitTest = {
11 | Unit: {},
12 | inspect: function(object) {
13 | try {
14 | if (typeof object == "undefined") return 'undefined';
15 | if (object === null) return 'null';
16 | if (typeof object == "string") {
17 | var useDoubleQuotes = arguments[1];
18 | var escapedString = this.gsub(object, /[\x00-\x1f\\]/, function(match) {
19 | var character = String.specialChar[match[0]];
20 | return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16);
21 | });
22 | if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"';
23 | return "'" + escapedString.replace(/'/g, '\\\'') + "'";
24 | };
25 | return String(object);
26 | } catch (e) {
27 | if (e instanceof RangeError) return '...';
28 | throw e;
29 | }
30 | },
31 | $: function(element) {
32 | if (arguments.length > 1) {
33 | for (var i = 0, elements = [], length = arguments.length; i < length; i++)
34 | elements.push(this.$(arguments[i]));
35 | return elements;
36 | }
37 | if (typeof element == "string")
38 | element = document.getElementById(element);
39 | return element;
40 | },
41 | gsub: function(source, pattern, replacement) {
42 | var result = '', match;
43 | replacement = arguments.callee.prepareReplacement(replacement);
44 |
45 | while (source.length > 0) {
46 | if (match = source.match(pattern)) {
47 | result += source.slice(0, match.index);
48 | result += JsUnitTest.String.interpret(replacement(match));
49 | source = source.slice(match.index + match[0].length);
50 | } else {
51 | result += source, source = '';
52 | }
53 | }
54 | return result;
55 | },
56 | scan: function(source, pattern, iterator) {
57 | this.gsub(source, pattern, iterator);
58 | return String(source);
59 | },
60 | escapeHTML: function(data) {
61 | return data.replace(/&/g,'&').replace(//g,'>');
62 | },
63 | arrayfromargs: function(args) {
64 | var myarray = new Array();
65 | var i;
66 |
67 | for (i=0;i\s*/,
99 | adjacent: /^\s*\+\s*/,
100 | descendant: /^\s/,
101 |
102 | // selectors follow
103 | tagName: /^\s*(\*|[\w\-]+)(\b|$)?/,
104 | id: /^#([\w\-\*]+)(\b|$)/,
105 | className: /^\.([\w\-\*]+)(\b|$)/,
106 | pseudo:
107 | /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/,
108 | attrPresence: /^\[((?:[\w]+:)?[\w]+)\]/,
109 | attr: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/
110 | };
111 |
112 | var assertions = {
113 | tagName: function(element, matches) {
114 | return matches[1].toUpperCase() == element.tagName.toUpperCase();
115 | },
116 |
117 | className: function(element, matches) {
118 | return Element.hasClassName(element, matches[1]);
119 | },
120 |
121 | id: function(element, matches) {
122 | return element.id === matches[1];
123 | },
124 |
125 | attrPresence: function(element, matches) {
126 | return Element.hasAttribute(element, matches[1]);
127 | },
128 |
129 | attr: function(element, matches) {
130 | var nodeValue = Element.readAttribute(element, matches[1]);
131 | return nodeValue && operators[matches[2]](nodeValue, matches[5] || matches[6]);
132 | }
133 | };
134 | var e = this.expression, ps = patterns, as = assertions;
135 | var le, p, m;
136 |
137 | while (e && le !== e && (/\S/).test(e)) {
138 | le = e;
139 | for (var i in ps) {
140 | p = ps[i];
141 | if (m = e.match(p)) {
142 | // use the Selector.assertions methods unless the selector
143 | // is too complex.
144 | if (as[i]) {
145 | tokens.push([i, Object.clone(m)]);
146 | e = e.replace(m[0], '');
147 | }
148 | }
149 | }
150 | }
151 |
152 | var match = true, name, matches;
153 | for (var i = 0, token; token = tokens[i]; i++) {
154 | name = token[0], matches = token[1];
155 | if (!assertions[name](element, matches)) {
156 | match = false; break;
157 | }
158 | }
159 |
160 | return match;
161 | },
162 | toQueryParams: function(query, separator) {
163 | var query = query || window.location.search;
164 | var match = query.replace(/^\s+/, '').replace(/\s+$/, '').match(/([^?#]*)(#.*)?$/);
165 | if (!match) return { };
166 |
167 | var hash = {};
168 | var parts = match[1].split(separator || '&');
169 | for (var i=0; i < parts.length; i++) {
170 | var pair = parts[i].split('=');
171 | if (pair[0]) {
172 | var key = decodeURIComponent(pair.shift());
173 | var value = pair.length > 1 ? pair.join('=') : pair[0];
174 | if (value != undefined) value = decodeURIComponent(value);
175 |
176 | if (key in hash) {
177 | var object = hash[key];
178 | var isArray = object != null && typeof object == "object" &&
179 | 'splice' in object && 'join' in object
180 | if (!isArray) hash[key] = [hash[key]];
181 | hash[key].push(value);
182 | }
183 | else hash[key] = value;
184 | }
185 | };
186 | return hash;
187 | },
188 |
189 | String: {
190 | interpret: function(value) {
191 | return value == null ? '' : String(value);
192 | }
193 | }
194 | };
195 |
196 | JsUnitTest.gsub.prepareReplacement = function(replacement) {
197 | if (typeof replacement == "function") return replacement;
198 | var template = new Template(replacement);
199 | return function(match) { return template.evaluate(match) };
200 | };
201 |
202 | JsUnitTest.Version = '0.7.2';
203 |
204 | JsUnitTest.Template = function(template, pattern) {
205 | this.template = template; //template.toString();
206 | this.pattern = pattern || JsUnitTest.Template.Pattern;
207 | };
208 |
209 | JsUnitTest.Template.prototype.evaluate = function(object) {
210 | if (typeof object.toTemplateReplacements == "function")
211 | object = object.toTemplateReplacements();
212 |
213 | return JsUnitTest.gsub(this.template, this.pattern, function(match) {
214 | if (object == null) return '';
215 |
216 | var before = match[1] || '';
217 | if (before == '\\') return match[2];
218 |
219 | var ctx = object, expr = match[3];
220 | var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/;
221 | match = pattern.exec(expr);
222 | if (match == null) return before;
223 |
224 | while (match != null) {
225 | var comp = (match[1].indexOf('[]') === 0) ? match[2].gsub('\\\\]', ']') : match[1];
226 | ctx = ctx[comp];
227 | if (null == ctx || '' == match[3]) break;
228 | expr = expr.substring('[' == match[3] ? match[1].length : match[0].length);
229 | match = pattern.exec(expr);
230 | }
231 |
232 | return before + JsUnitTest.String.interpret(ctx);
233 | });
234 | }
235 |
236 | JsUnitTest.Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
237 | JsUnitTest.Event = {};
238 | // written by Dean Edwards, 2005
239 | // with input from Tino Zijdel, Matthias Miller, Diego Perini
240 | // namespaced by Dr Nic Williams 2008
241 |
242 | // http://dean.edwards.name/weblog/2005/10/add-event/
243 | // http://dean.edwards.name/weblog/2005/10/add-event2/
244 | JsUnitTest.Event.addEvent = function(element, type, handler) {
245 | if (element.addEventListener) {
246 | element.addEventListener(type, handler, false);
247 | } else {
248 | // assign each event handler a unique ID
249 | if (!handler.$$guid) handler.$$guid = JsUnitTest.Event.addEvent.guid++;
250 | // create a hash table of event types for the element
251 | if (!element.events) element.events = {};
252 | // create a hash table of event handlers for each element/event pair
253 | var handlers = element.events[type];
254 | if (!handlers) {
255 | handlers = element.events[type] = {};
256 | // store the existing event handler (if there is one)
257 | if (element["on" + type]) {
258 | handlers[0] = element["on" + type];
259 | }
260 | }
261 | // store the event handler in the hash table
262 | handlers[handler.$$guid] = handler;
263 | // assign a global event handler to do all the work
264 | element["on" + type] = this.handleEvent;
265 | }
266 | };
267 | // a counter used to create unique IDs
268 | JsUnitTest.Event.addEvent.guid = 1;
269 |
270 | JsUnitTest.Event.removeEvent = function(element, type, handler) {
271 | if (element.removeEventListener) {
272 | element.removeEventListener(type, handler, false);
273 | } else {
274 | // delete the event handler from the hash table
275 | if (element.events && element.events[type]) {
276 | delete element.events[type][handler.$$guid];
277 | }
278 | }
279 | };
280 |
281 | JsUnitTest.Event.handleEvent = function(event) {
282 | var returnValue = true;
283 | // grab the event object (IE uses a global event object)
284 | event = event || JsUnitTest.Event.fixEvent(((this.ownerDocument || this.document || this).parentWindow || window).event);
285 | // get a reference to the hash table of event handlers
286 | var handlers = this.events[event.type];
287 | // execute each event handler
288 | for (var i in handlers) {
289 | this.$$handleEvent = handlers[i];
290 | if (this.$$handleEvent(event) === false) {
291 | returnValue = false;
292 | }
293 | }
294 | return returnValue;
295 | };
296 |
297 | JsUnitTest.Event.fixEvent = function(event) {
298 | // add W3C standard event methods
299 | event.preventDefault = this.fixEvent.preventDefault;
300 | event.stopPropagation = this.fixEvent.stopPropagation;
301 | return event;
302 | };
303 | JsUnitTest.Event.fixEvent.preventDefault = function() {
304 | this.returnValue = false;
305 | };
306 | JsUnitTest.Event.fixEvent.stopPropagation = function() {
307 | this.cancelBubble = true;
308 | };
309 |
310 | JsUnitTest.Unit.Logger = function(element) {
311 | this.element = JsUnitTest.$(element);
312 | if (this.element) this._createLogTable();
313 | };
314 |
315 | JsUnitTest.Unit.Logger.prototype.start = function(testName) {
316 | if (!this.element) return;
317 | var tbody = this.element.getElementsByTagName('tbody')[0];
318 |
319 | var tr = document.createElement('tr');
320 | var td;
321 |
322 | //testname
323 | td = document.createElement('td');
324 | td.appendChild(document.createTextNode(testName));
325 | tr.appendChild(td)
326 |
327 | tr.appendChild(document.createElement('td'));//status
328 | tr.appendChild(document.createElement('td'));//message
329 |
330 | tbody.appendChild(tr);
331 | };
332 |
333 | JsUnitTest.Unit.Logger.prototype.setStatus = function(status) {
334 | var logline = this.getLastLogLine();
335 | logline.className = status;
336 | var statusCell = logline.getElementsByTagName('td')[1];
337 | statusCell.appendChild(document.createTextNode(status));
338 | };
339 |
340 | JsUnitTest.Unit.Logger.prototype.finish = function(status, summary) {
341 | if (!this.element) return;
342 | this.setStatus(status);
343 | this.message(summary);
344 | };
345 |
346 | JsUnitTest.Unit.Logger.prototype.message = function(message) {
347 | if (!this.element) return;
348 | var cell = this.getMessageCell();
349 |
350 | // cell.appendChild(document.createTextNode(this._toHTML(message)));
351 | cell.innerHTML = this._toHTML(message);
352 | };
353 |
354 | JsUnitTest.Unit.Logger.prototype.summary = function(summary) {
355 | if (!this.element) return;
356 | var div = this.element.getElementsByTagName('div')[0];
357 | div.innerHTML = this._toHTML(summary);
358 | };
359 |
360 | JsUnitTest.Unit.Logger.prototype.getLastLogLine = function() {
361 | var tbody = this.element.getElementsByTagName('tbody')[0];
362 | var loglines = tbody.getElementsByTagName('tr');
363 | return loglines[loglines.length - 1];
364 | };
365 |
366 | JsUnitTest.Unit.Logger.prototype.getMessageCell = function() {
367 | var logline = this.getLastLogLine();
368 | return logline.getElementsByTagName('td')[2];
369 | };
370 |
371 | JsUnitTest.Unit.Logger.prototype._createLogTable = function() {
372 | var html = 'running...
' +
373 | '' +
374 | 'Status | Test | Message |
' +
375 | '' +
376 | '
';
377 | this.element.innerHTML = html;
378 | };
379 |
380 | JsUnitTest.Unit.Logger.prototype.appendActionButtons = function(actions) {
381 | // actions = $H(actions);
382 | // if (!actions.any()) return;
383 | // var div = new Element("div", {className: 'action_buttons'});
384 | // actions.inject(div, function(container, action) {
385 | // var button = new Element("input").setValue(action.key).observe("click", action.value);
386 | // button.type = "button";
387 | // return container.insert(button);
388 | // });
389 | // this.getMessageCell().insert(div);
390 | };
391 |
392 | JsUnitTest.Unit.Logger.prototype._toHTML = function(txt) {
393 | return JsUnitTest.escapeHTML(txt).replace(/\n/g,"
");
394 | };
395 | JsUnitTest.Unit.MessageTemplate = function(string) {
396 | var parts = [];
397 | var str = JsUnitTest.scan((string || ''), /(?=[^\\])\?|(?:\\\?|[^\?])+/, function(part) {
398 | parts.push(part[0]);
399 | });
400 | this.parts = parts;
401 | };
402 |
403 | JsUnitTest.Unit.MessageTemplate.prototype.evaluate = function(params) {
404 | var results = [];
405 | for (var i=0; i < this.parts.length; i++) {
406 | var part = this.parts[i];
407 | var result = (part == '?') ? JsUnitTest.inspect(params.shift()) : part.replace(/\\\?/, '?');
408 | results.push(result);
409 | };
410 | return results.join('');
411 | };
412 | // A generic function for performming AJAX requests
413 | // It takes one argument, which is an object that contains a set of options
414 | // All of which are outline in the comments, below
415 | // From John Resig's book Pro JavaScript Techniques
416 | // published by Apress, 2006-8
417 | JsUnitTest.ajax = function( options ) {
418 |
419 | // Load the options object with defaults, if no
420 | // values were provided by the user
421 | options = {
422 | // The type of HTTP Request
423 | type: options.type || "POST",
424 |
425 | // The URL the request will be made to
426 | url: options.url || "",
427 |
428 | // How long to wait before considering the request to be a timeout
429 | timeout: options.timeout || 5000,
430 |
431 | // Functions to call when the request fails, succeeds,
432 | // or completes (either fail or succeed)
433 | onComplete: options.onComplete || function(){},
434 | onError: options.onError || function(){},
435 | onSuccess: options.onSuccess || function(){},
436 |
437 | // The data type that'll be returned from the server
438 | // the default is simply to determine what data was returned from the
439 | // and act accordingly.
440 | data: options.data || ""
441 | };
442 |
443 | // Create the request object
444 | var xml = window.ActiveXObject ? new ActiveXObject('Microsoft.XMLHTTP') : new XMLHttpRequest();
445 |
446 | // Open the asynchronous POST request
447 | xml.open(options.type, options.url, true);
448 |
449 | // We're going to wait for a request for 5 seconds, before giving up
450 | var timeoutLength = 5000;
451 |
452 | // Keep track of when the request has been succesfully completed
453 | var requestDone = false;
454 |
455 | // Initalize a callback which will fire 5 seconds from now, cancelling
456 | // the request (if it has not already occurred).
457 | setTimeout(function(){
458 | requestDone = true;
459 | }, timeoutLength);
460 |
461 | // Watch for when the state of the document gets updated
462 | xml.onreadystatechange = function(){
463 | // Wait until the data is fully loaded,
464 | // and make sure that the request hasn't already timed out
465 | if ( xml.readyState == 4 && !requestDone ) {
466 |
467 | // Check to see if the request was successful
468 | if ( httpSuccess( xml ) ) {
469 |
470 | // Execute the success callback with the data returned from the server
471 | options.onSuccess( httpData( xml, options.type ) );
472 |
473 | // Otherwise, an error occurred, so execute the error callback
474 | } else {
475 | options.onError();
476 | }
477 |
478 | // Call the completion callback
479 | options.onComplete();
480 |
481 | // Clean up after ourselves, to avoid memory leaks
482 | xml = null;
483 | }
484 | };
485 |
486 | // Establish the connection to the server
487 | xml.send(null);
488 |
489 | // Determine the success of the HTTP response
490 | function httpSuccess(r) {
491 | try {
492 | // If no server status is provided, and we're actually
493 | // requesting a local file, then it was successful
494 | return !r.status && location.protocol == "file:" ||
495 |
496 | // Any status in the 200 range is good
497 | ( r.status >= 200 && r.status < 300 ) ||
498 |
499 | // Successful if the document has not been modified
500 | r.status == 304 ||
501 |
502 | // Safari returns an empty status if the file has not been modified
503 | navigator.userAgent.indexOf("Safari") >= 0 && typeof r.status == "undefined";
504 | } catch(e){}
505 |
506 | // If checking the status failed, then assume that the request failed too
507 | return false;
508 | }
509 |
510 | // Extract the correct data from the HTTP response
511 | function httpData(r,type) {
512 | // Get the content-type header
513 | var ct = r.getResponseHeader("content-type");
514 |
515 | // If no default type was provided, determine if some
516 | // form of XML was returned from the server
517 | var data = !type && ct && ct.indexOf("xml") >= 0;
518 |
519 | // Get the XML Document object if XML was returned from
520 | // the server, otherwise return the text contents returned by the server
521 | data = type == "xml" || data ? r.responseXML : r.responseText;
522 |
523 | // If the specified type is "script", execute the returned text
524 | // response as if it was JavaScript
525 | if ( type == "script" )
526 | eval.call( window, data );
527 |
528 | // Return the response data (either an XML Document or a text string)
529 | return data;
530 | }
531 |
532 | };
533 | JsUnitTest.Unit.Assertions = {
534 | buildMessage: function(message, template) {
535 | var args = JsUnitTest.arrayfromargs(arguments).slice(2);
536 | return (message ? message + '\n' : '') +
537 | new JsUnitTest.Unit.MessageTemplate(template).evaluate(args);
538 | },
539 |
540 | flunk: function(message) {
541 | this.assertBlock(message || 'Flunked', function() { return false });
542 | },
543 |
544 | assertBlock: function(message, block) {
545 | try {
546 | block.call(this) ? this.pass() : this.fail(message);
547 | } catch(e) { this.error(e) }
548 | },
549 |
550 | assert: function(expression, message) {
551 | message = this.buildMessage(message || 'assert', 'got >', expression);
552 | this.assertBlock(message, function() { return expression });
553 | },
554 |
555 | assertEqual: function(expected, actual, message) {
556 | message = this.buildMessage(message || 'assertEqual', 'expected >, actual: >', expected, actual);
557 | this.assertBlock(message, function() { return expected == actual });
558 | },
559 |
560 | assertNotEqual: function(expected, actual, message) {
561 | message = this.buildMessage(message || 'assertNotEqual', 'expected >, actual: >', expected, actual);
562 | this.assertBlock(message, function() { return expected != actual });
563 | },
564 |
565 | assertEnumEqual: function(expected, actual, message) {
566 | message = this.buildMessage(message || 'assertEnumEqual', 'expected >, actual: >', expected, actual);
567 | var expected_array = JsUnitTest.flattenArray(expected);
568 | var actual_array = JsUnitTest.flattenArray(actual);
569 | this.assertBlock(message, function() {
570 | if (expected_array.length == actual_array.length) {
571 | for (var i=0; i < expected_array.length; i++) {
572 | if (expected_array[i] != actual_array[i]) return false;
573 | };
574 | return true;
575 | }
576 | return false;
577 | });
578 | },
579 |
580 | assertEnumNotEqual: function(expected, actual, message) {
581 | message = this.buildMessage(message || 'assertEnumNotEqual', '> was the same as >', expected, actual);
582 | var expected_array = JsUnitTest.flattenArray(expected);
583 | var actual_array = JsUnitTest.flattenArray(actual);
584 | this.assertBlock(message, function() {
585 | if (expected_array.length == actual_array.length) {
586 | for (var i=0; i < expected_array.length; i++) {
587 | if (expected_array[i] != actual_array[i]) return true;
588 | };
589 | return false;
590 | }
591 | return true;
592 | });
593 | },
594 |
595 | assertHashEqual: function(expected, actual, message) {
596 | message = this.buildMessage(message || 'assertHashEqual', 'expected >, actual: >', expected, actual);
597 | var expected_array = JsUnitTest.flattenArray(JsUnitTest.hashToSortedArray(expected));
598 | var actual_array = JsUnitTest.flattenArray(JsUnitTest.hashToSortedArray(actual));
599 | var block = function() {
600 | if (expected_array.length == actual_array.length) {
601 | for (var i=0; i < expected_array.length; i++) {
602 | if (expected_array[i] != actual_array[i]) return false;
603 | };
604 | return true;
605 | }
606 | return false;
607 | };
608 | this.assertBlock(message, block);
609 | },
610 |
611 | assertHashNotEqual: function(expected, actual, message) {
612 | message = this.buildMessage(message || 'assertHashNotEqual', '> was the same as >', expected, actual);
613 | var expected_array = JsUnitTest.flattenArray(JsUnitTest.hashToSortedArray(expected));
614 | var actual_array = JsUnitTest.flattenArray(JsUnitTest.hashToSortedArray(actual));
615 | // from now we recursively zip & compare nested arrays
616 | var block = function() {
617 | if (expected_array.length == actual_array.length) {
618 | for (var i=0; i < expected_array.length; i++) {
619 | if (expected_array[i] != actual_array[i]) return true;
620 | };
621 | return false;
622 | }
623 | return true;
624 | };
625 | this.assertBlock(message, block);
626 | },
627 |
628 | assertIdentical: function(expected, actual, message) {
629 | message = this.buildMessage(message || 'assertIdentical', 'expected >, actual: >', expected, actual);
630 | this.assertBlock(message, function() { return expected === actual });
631 | },
632 |
633 | assertNotIdentical: function(expected, actual, message) {
634 | message = this.buildMessage(message || 'assertNotIdentical', 'expected >, actual: >', expected, actual);
635 | this.assertBlock(message, function() { return expected !== actual });
636 | },
637 |
638 | assertNull: function(obj, message) {
639 | message = this.buildMessage(message || 'assertNull', 'got >', obj);
640 | this.assertBlock(message, function() { return obj === null });
641 | },
642 |
643 | assertNotNull: function(obj, message) {
644 | message = this.buildMessage(message || 'assertNotNull', 'got >', obj);
645 | this.assertBlock(message, function() { return obj !== null });
646 | },
647 |
648 | assertUndefined: function(obj, message) {
649 | message = this.buildMessage(message || 'assertUndefined', 'got >', obj);
650 | this.assertBlock(message, function() { return typeof obj == "undefined" });
651 | },
652 |
653 | assertNotUndefined: function(obj, message) {
654 | message = this.buildMessage(message || 'assertNotUndefined', 'got >', obj);
655 | this.assertBlock(message, function() { return typeof obj != "undefined" });
656 | },
657 |
658 | assertNullOrUndefined: function(obj, message) {
659 | message = this.buildMessage(message || 'assertNullOrUndefined', 'got >', obj);
660 | this.assertBlock(message, function() { return obj == null });
661 | },
662 |
663 | assertNotNullOrUndefined: function(obj, message) {
664 | message = this.buildMessage(message || 'assertNotNullOrUndefined', 'got >', obj);
665 | this.assertBlock(message, function() { return obj != null });
666 | },
667 |
668 | assertMatch: function(expected, actual, message) {
669 | message = this.buildMessage(message || 'assertMatch', 'regex > did not match >', expected, actual);
670 | this.assertBlock(message, function() { return new RegExp(expected).exec(actual) });
671 | },
672 |
673 | assertNoMatch: function(expected, actual, message) {
674 | message = this.buildMessage(message || 'assertNoMatch', 'regex > matched >', expected, actual);
675 | this.assertBlock(message, function() { return !(new RegExp(expected).exec(actual)) });
676 | },
677 |
678 | assertHasClass: function(element, klass, message) {
679 | element = JsUnitTest.$(element);
680 | message = this.buildMessage(message || 'assertHasClass', '? doesn\'t have class >.', element, klass);
681 | this.assertBlock(message, function() {
682 | return !!element.className.match(new RegExp(klass))
683 | });
684 | },
685 |
686 | assertHidden: function(element, message) {
687 | element = JsUnitTest.$(element);
688 | message = this.buildMessage(message || 'assertHidden', '? isn\'t hidden.', element);
689 | this.assertBlock(message, function() { return !element.style.display || element.style.display == 'none' });
690 | },
691 |
692 | assertInstanceOf: function(expected, actual, message) {
693 | message = this.buildMessage(message || 'assertInstanceOf', '> was not an instance of the expected type', actual);
694 | this.assertBlock(message, function() { return actual instanceof expected });
695 | },
696 |
697 | assertNotInstanceOf: function(expected, actual, message) {
698 | message = this.buildMessage(message || 'assertNotInstanceOf', '> was an instance of the expected type', actual);
699 | this.assertBlock(message, function() { return !(actual instanceof expected) });
700 | },
701 |
702 | assertRespondsTo: function(method, obj, message) {
703 | message = this.buildMessage(message || 'assertRespondsTo', 'object doesn\'t respond to >', method);
704 | this.assertBlock(message, function() { return (method in obj && typeof obj[method] == 'function') });
705 | },
706 |
707 | assertRaise: function(exceptionName, method, message) {
708 | message = this.buildMessage(message || 'assertRaise', '> exception expected but none was raised', exceptionName);
709 | var block = function() {
710 | try {
711 | method();
712 | return false;
713 | } catch(e) {
714 | if (e.name == exceptionName) return true;
715 | else throw e;
716 | }
717 | };
718 | this.assertBlock(message, block);
719 | },
720 |
721 | assertNothingRaised: function(method, message) {
722 | try {
723 | method();
724 | this.assert(true, "Expected nothing to be thrown");
725 | } catch(e) {
726 | message = this.buildMessage(message || 'assertNothingRaised', '> was thrown when nothing was expected.', e);
727 | this.flunk(message);
728 | }
729 | },
730 |
731 | _isVisible: function(element) {
732 | element = JsUnitTest.$(element);
733 | if(!element.parentNode) return true;
734 | this.assertNotNull(element);
735 | if(element.style && (element.style.display == 'none'))
736 | return false;
737 |
738 | return arguments.callee.call(this, element.parentNode);
739 | },
740 |
741 | assertVisible: function(element, message) {
742 | message = this.buildMessage(message, '? was not visible.', element);
743 | this.assertBlock(message, function() { return this._isVisible(element) });
744 | },
745 |
746 | assertNotVisible: function(element, message) {
747 | message = this.buildMessage(message, '? was not hidden and didn\'t have a hidden parent either.', element);
748 | this.assertBlock(message, function() { return !this._isVisible(element) });
749 | },
750 |
751 | assertElementsMatch: function() {
752 | var pass = true, expressions = JsUnitTest.arrayfromargs(arguments);
753 | var elements = expressions.shift();
754 | if (elements.length != expressions.length) {
755 | message = this.buildMessage('assertElementsMatch', 'size mismatch: ? elements, ? expressions (?).', elements.length, expressions.length, expressions);
756 | this.flunk(message);
757 | pass = false;
758 | }
759 | for (var i=0; i < expressions.length; i++) {
760 | var expression = expressions[i];
761 | var element = JsUnitTest.$(elements[i]);
762 | if (JsUnitTest.selectorMatch(expression, element)) {
763 | pass = true;
764 | break;
765 | }
766 | message = this.buildMessage('assertElementsMatch', 'In index >: expected > but got ?', index, expression, element);
767 | this.flunk(message);
768 | pass = false;
769 | };
770 | this.assert(pass, "Expected all elements to match.");
771 | },
772 |
773 | assertElementMatches: function(element, expression, message) {
774 | this.assertElementsMatch([element], expression);
775 | }
776 | };
777 | JsUnitTest.Unit.Runner = function(testcases) {
778 | var argumentOptions = arguments[1] || {};
779 | var options = this.options = {};
780 | options.testLog = ('testLog' in argumentOptions) ? argumentOptions.testLog : 'testlog';
781 | options.resultsURL = this.queryParams.resultsURL;
782 | options.testLog = JsUnitTest.$(options.testLog);
783 |
784 | this.tests = this.getTests(testcases);
785 | this.currentTest = 0;
786 | this.logger = new JsUnitTest.Unit.Logger(options.testLog);
787 |
788 | var self = this;
789 | JsUnitTest.Event.addEvent(window, "load", function() {
790 | setTimeout(function() {
791 | self.runTests();
792 | }, 0.1);
793 | });
794 | };
795 |
796 | JsUnitTest.Unit.Runner.prototype.queryParams = JsUnitTest.toQueryParams();
797 |
798 | JsUnitTest.Unit.Runner.prototype.portNumber = function() {
799 | if (window.location.search.length > 0) {
800 | var matches = window.location.search.match(/\:(\d{3,5})\//);
801 | if (matches) {
802 | return parseInt(matches[1]);
803 | }
804 | }
805 | return null;
806 | };
807 |
808 | JsUnitTest.Unit.Runner.prototype.getTests = function(testcases) {
809 | var tests = [], options = this.options;
810 | if (this.queryParams.tests) tests = this.queryParams.tests.split(',');
811 | else if (options.tests) tests = options.tests;
812 | else if (options.test) tests = [option.test];
813 | else {
814 | for (testname in testcases) {
815 | if (testname.match(/^test/)) tests.push(testname);
816 | }
817 | }
818 | var results = [];
819 | for (var i=0; i < tests.length; i++) {
820 | var test = tests[i];
821 | if (testcases[test])
822 | results.push(
823 | new JsUnitTest.Unit.Testcase(test, testcases[test], testcases.setup, testcases.teardown)
824 | );
825 | };
826 | return results;
827 | };
828 |
829 | JsUnitTest.Unit.Runner.prototype.getResult = function() {
830 | var results = {
831 | tests: this.tests.length,
832 | assertions: 0,
833 | failures: 0,
834 | errors: 0,
835 | warnings: 0
836 | };
837 |
838 | for (var i=0; i < this.tests.length; i++) {
839 | var test = this.tests[i];
840 | results.assertions += test.assertions;
841 | results.failures += test.failures;
842 | results.errors += test.errors;
843 | results.warnings += test.warnings;
844 | };
845 | return results;
846 | };
847 |
848 | JsUnitTest.Unit.Runner.prototype.postResults = function() {
849 | if (this.options.resultsURL) {
850 | // new Ajax.Request(this.options.resultsURL,
851 | // { method: 'get', parameters: this.getResult(), asynchronous: false });
852 | var results = this.getResult();
853 | var url = this.options.resultsURL + "?";
854 | url += "tests="+ this.tests.length + "&";
855 | url += "assertions="+ results.assertions + "&";
856 | url += "warnings=" + results.warnings + "&";
857 | url += "failures=" + results.failures + "&";
858 | url += "errors=" + results.errors;
859 | JsUnitTest.ajax({
860 | url: url,
861 | type: 'GET'
862 | })
863 | }
864 | };
865 |
866 | JsUnitTest.Unit.Runner.prototype.runTests = function() {
867 | var test = this.tests[this.currentTest], actions;
868 |
869 | if (!test) return this.finish();
870 | if (!test.isWaiting) this.logger.start(test.name);
871 | test.run();
872 | var self = this;
873 | if(test.isWaiting) {
874 | this.logger.message("Waiting for " + test.timeToWait + "ms");
875 | // setTimeout(this.runTests.bind(this), test.timeToWait || 1000);
876 | setTimeout(function() {
877 | self.runTests();
878 | }, test.timeToWait || 1000);
879 | return;
880 | }
881 |
882 | this.logger.finish(test.status(), test.summary());
883 | if (actions = test.actions) this.logger.appendActionButtons(actions);
884 | this.currentTest++;
885 | // tail recursive, hopefully the browser will skip the stackframe
886 | this.runTests();
887 | };
888 |
889 | JsUnitTest.Unit.Runner.prototype.finish = function() {
890 | this.postResults();
891 | this.logger.summary(this.summary());
892 | };
893 |
894 | JsUnitTest.Unit.Runner.prototype.summary = function() {
895 | return new JsUnitTest.Template('#{tests} tests, #{assertions} assertions, #{failures} failures, #{errors} errors, #{warnings} warnings').evaluate(this.getResult());
896 | };
897 | JsUnitTest.Unit.Testcase = function(name, test, setup, teardown) {
898 | this.name = name;
899 | this.test = test || function() {};
900 | this.setup = setup || function() {};
901 | this.teardown = teardown || function() {};
902 | this.messages = [];
903 | this.actions = {};
904 | };
905 | // import JsUnitTest.Unit.Assertions
906 |
907 | for (method in JsUnitTest.Unit.Assertions) {
908 | JsUnitTest.Unit.Testcase.prototype[method] = JsUnitTest.Unit.Assertions[method];
909 | }
910 |
911 | JsUnitTest.Unit.Testcase.prototype.isWaiting = false;
912 | JsUnitTest.Unit.Testcase.prototype.timeToWait = 1000;
913 | JsUnitTest.Unit.Testcase.prototype.assertions = 0;
914 | JsUnitTest.Unit.Testcase.prototype.failures = 0;
915 | JsUnitTest.Unit.Testcase.prototype.errors = 0;
916 | JsUnitTest.Unit.Testcase.prototype.warnings = 0;
917 | JsUnitTest.Unit.Testcase.prototype.isRunningFromRake = window.location.port;
918 |
919 | // JsUnitTest.Unit.Testcase.prototype.isRunningFromRake = window.location.port == 4711;
920 |
921 | JsUnitTest.Unit.Testcase.prototype.wait = function(time, nextPart) {
922 | this.isWaiting = true;
923 | this.test = nextPart;
924 | this.timeToWait = time;
925 | };
926 |
927 | JsUnitTest.Unit.Testcase.prototype.run = function(rethrow) {
928 | try {
929 | try {
930 | if (!this.isWaiting) this.setup();
931 | this.isWaiting = false;
932 | this.test();
933 | } finally {
934 | if(!this.isWaiting) {
935 | this.teardown();
936 | }
937 | }
938 | }
939 | catch(e) {
940 | if (rethrow) throw e;
941 | this.error(e, this);
942 | }
943 | };
944 |
945 | JsUnitTest.Unit.Testcase.prototype.summary = function() {
946 | var msg = '#{assertions} assertions, #{failures} failures, #{errors} errors, #{warnings} warnings\n';
947 | return new JsUnitTest.Template(msg).evaluate(this) +
948 | this.messages.join("\n");
949 | };
950 |
951 | JsUnitTest.Unit.Testcase.prototype.pass = function() {
952 | this.assertions++;
953 | };
954 |
955 | JsUnitTest.Unit.Testcase.prototype.fail = function(message) {
956 | this.failures++;
957 | var line = "";
958 | try {
959 | throw new Error("stack");
960 | } catch(e){
961 | line = (/\.html:(\d+)/.exec(e.stack || '') || ['',''])[1];
962 | }
963 | this.messages.push("Failure: " + message + (line ? " Line #" + line : ""));
964 | };
965 |
966 | JsUnitTest.Unit.Testcase.prototype.warning = function(message) {
967 | this.warnings++;
968 | var line = "";
969 | try {
970 | throw new Error("stack");
971 | } catch(e){
972 | line = (/\.html:(\d+)/.exec(e.stack || '') || ['',''])[1];
973 | }
974 | this.messages.push("Warning: " + message + (line ? " Line #" + line : ""));
975 | };
976 | JsUnitTest.Unit.Testcase.prototype.warn = JsUnitTest.Unit.Testcase.prototype.warning;
977 |
978 | JsUnitTest.Unit.Testcase.prototype.info = function(message) {
979 | this.messages.push("Info: " + message);
980 | };
981 |
982 | JsUnitTest.Unit.Testcase.prototype.error = function(error, test) {
983 | this.errors++;
984 | this.actions['retry with throw'] = function() { test.run(true) };
985 | this.messages.push(error.name + ": "+ error.message + "(" + JsUnitTest.inspect(error) + ")");
986 | };
987 |
988 | JsUnitTest.Unit.Testcase.prototype.status = function() {
989 | if (this.failures > 0) return 'failed';
990 | if (this.errors > 0) return 'error';
991 | if (this.warnings > 0) return 'warning';
992 | return 'passed';
993 | };
994 |
995 | JsUnitTest.Unit.Testcase.prototype.benchmark = function(operation, iterations) {
996 | var startAt = new Date();
997 | (iterations || 1).times(operation);
998 | var timeTaken = ((new Date())-startAt);
999 | this.info((arguments[2] || 'Operation') + ' finished ' +
1000 | iterations + ' iterations in ' + (timeTaken/1000)+'s' );
1001 | return timeTaken;
1002 | };
1003 |
1004 | Test = JsUnitTest
--------------------------------------------------------------------------------
/test/assets/unittest.css:
--------------------------------------------------------------------------------
1 | body, div, p, h1, h2, h3, ul, ol, span, a, table, td, form, img, li {
2 | font-family: sans-serif;
3 | }
4 |
5 | body {
6 | font-size:0.8em;
7 | }
8 |
9 | #log {
10 | padding-bottom: 1em;
11 | border-bottom: 2px solid #000;
12 | margin-bottom: 2em;
13 | }
14 |
15 | .logsummary {
16 | margin-top: 1em;
17 | margin-bottom: 1em;
18 | padding: 1ex;
19 | border: 1px solid #000;
20 | font-weight: bold;
21 | }
22 |
23 | .logtable {
24 | width:100%;
25 | border-collapse: collapse;
26 | border: 1px dotted #666;
27 | }
28 |
29 | .logtable td, .logtable th {
30 | text-align: left;
31 | padding: 3px 8px;
32 | border: 1px dotted #666;
33 | }
34 |
35 | .logtable .passed {
36 | background-color: #cfc;
37 | }
38 |
39 | .logtable .failed, .logtable .error {
40 | background-color: #fcc;
41 | }
42 |
43 | .logtable .warning {
44 | background-color: #FC6;
45 | }
46 |
47 | .logtable td div.action_buttons {
48 | display: inline;
49 | }
50 |
51 | .logtable td div.action_buttons input {
52 | margin: 0 5px;
53 | font-size: 10px;
54 | }
55 |
--------------------------------------------------------------------------------
/test/unit/fixtures/a.js:
--------------------------------------------------------------------------------
1 | a = {};
2 |
--------------------------------------------------------------------------------
/test/unit/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Namespace.js unit test file
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/test/unit/namespace_test.js:
--------------------------------------------------------------------------------
1 | /*global window, document, Test, Namespace, a */
2 |
3 | var t = new Test.Unit.Runner({
4 | setup : function () {
5 | Namespace.separator = ".";
6 | Namespace.baseUri = "./";
7 | },
8 |
9 | teardown : function () {
10 | // Delete any global objects that were created
11 | // IE has problems here
12 | try {
13 | delete window.a;
14 | } catch (e) { window.a = undefined; }
15 |
16 | // Remove script tags other than Namespace.js, namespace_test.js
17 | // and unittest.js
18 | var tags = document.getElementsByTagName("SCRIPT"), tag;
19 | for (var i = 0; i < tags.length; i += 1) {
20 | tag = tags[i];
21 | if (tag.src.match("amespace") === null &&
22 | tag.src.match("unittest") === null) {
23 | tag.parentNode.removeChild(tag);
24 | }
25 | }
26 | },
27 |
28 | testNamespace : function () {
29 | this.assert(typeof Namespace === "function");
30 | // Create some objects using namespace
31 | Namespace("a.b.c");
32 |
33 | this.assert(typeof a === "object");
34 | this.assert(typeof a.b === "object");
35 | this.assert(typeof a.b.c === "object");
36 |
37 | // Create some properties and make sure they are preserved
38 | a.b.foo = true;
39 | a.b.bar = "hello";
40 | Namespace("a.b.baz");
41 |
42 | this.assert(a.b.foo);
43 | this.assertIdentical(a.b.bar, "hello");
44 | this.assert(typeof a.b.baz === "object");
45 | },
46 |
47 | testExist : function () {
48 | this.assert(typeof Namespace.exist === "function");
49 |
50 | this.assert(!Namespace.exist("a"));
51 |
52 | a = {};
53 | a.b = function () {};
54 | a.b.c = "something";
55 |
56 | this.assert(Namespace.exist("a"));
57 | this.assert(Namespace.exist("a.b"));
58 | this.assert(Namespace.exist("a.b.c"));
59 |
60 | try {
61 | delete window.a;
62 | } catch (e) { window.a = undefined; }
63 |
64 | this.assert(!Namespace.exist("a"));
65 | },
66 |
67 | testMapIdentifierToUri : function () {
68 | this.assert(typeof Namespace.mapIdentifierToUri === "function");
69 |
70 | this.assertIdentical(Namespace.mapIdentifierToUri("a.b.c"), "./a/b/c.js");
71 |
72 | Namespace.separator = ":";
73 |
74 | this.assertIdentical(Namespace.mapIdentifierToUri("a:b:c"), "./a/b/c.js");
75 |
76 | Namespace.separator = ".";
77 | Namespace.baseUri = "/foo/bar/";
78 |
79 | this.assertIdentical(Namespace.mapIdentifierToUri("a.b.c"), "/foo/bar/a/b/c.js");
80 | },
81 |
82 | testInclude : function () {
83 | this.assert(typeof Namespace.include === "function");
84 |
85 | Namespace.include("fixtures/a");
86 |
87 | this.assert(typeof a === "object");
88 | },
89 |
90 | testUse : function () {
91 |
92 | },
93 |
94 | testFrom : function () {
95 |
96 | },
97 |
98 | testProvide : function () {
99 |
100 | },
101 |
102 | testAddEventListener : function () {
103 |
104 | },
105 |
106 | testRemoveEventListener : function () {
107 |
108 | },
109 |
110 | testRegisterNativeExtensions : function () {
111 |
112 | }
113 | });
114 |
--------------------------------------------------------------------------------