From c85c58c435ca6080de207f71342d18f64abce63c Mon Sep 17 00:00:00 2001 From: Bryan Berry Date: Tue, 03 Nov 2009 03:27:50 +0000 Subject: added files for using qunit --- diff --git a/css/qunit.css b/css/qunit.css new file mode 100644 index 0000000..4542933 --- /dev/null +++ b/css/qunit.css @@ -0,0 +1,17 @@ +h1#qunit-header { padding: 15px; font-size: large; background-color: #06b; color: white; font-family: 'trebuchet ms', verdana, arial; margin: 0; } +h1#qunit-header a { color: white; } + +h2#qunit-banner { height: 2em; border-bottom: 1px solid white; background-color: #eee; margin: 0; font-family: 'trebuchet ms', verdana, arial; } +h2#qunit-banner.pass { background-color: green; } +h2#qunit-banner.fail { background-color: red; } + +h2#qunit-userAgent { padding: 10px; background-color: #eee; color: black; margin: 0; font-size: small; font-weight: normal; font-family: 'trebuchet ms', verdana, arial; font-size: 10pt; } + +div#qunit-testrunner-toolbar { background: #eee; border-top: 1px solid black; padding: 10px; font-family: 'trebuchet ms', verdana, arial; margin: 0; font-size: 10pt; } + +ol#qunit-tests { font-family: 'trebuchet ms', verdana, arial; font-size: 10pt; } +ol#qunit-tests li strong { cursor:pointer; } +ol#qunit-tests .pass { color: green; } +ol#qunit-tests .fail { color: red; } + +p#qunit-testresult { margin-left: 1em; font-size: 10pt; font-family: 'trebuchet ms', verdana, arial; } diff --git a/js/pure2.js b/js/pure2.js deleted file mode 100755 index 1e0958f..0000000 --- a/js/pure2.js +++ /dev/null @@ -1,710 +0,0 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * - - PURE Unobtrusive Rendering Engine for HTML - - Licensed under the MIT licenses. - More information at: http://www.opensource.org - - Copyright (c) 2009 Michael Cvilic - BeeBole.com - - Thanks to Rog Peppe for the functional JS jump - revision: 2.18 - -* * * * * * * * * * * * * * * * * * * * * * * * * */ - -var $p, pure = $p = function(){ - var sel = arguments[0], - ctxt = false; - - if(typeof sel === 'string'){ - ctxt = arguments[1] || false; - } - return $p.core(sel, ctxt); -}; - -$p.core = function(sel, ctxt, plugins){ - //get an instance of the plugins - var plugins = getPlugins(), - templates = []; - - //search for the template node(s) - if(typeof sel === 'string'){ - templates = plugins.find(ctxt || document, sel); - }else if(typeof sel === 'object'){ - templates = [sel]; - }else{ - error('No templates found. Review your selector'); - } - - for(var i = 0, ii = templates.length; i < ii; i++){ - plugins[i] = templates[i]; - } - plugins.length = ii; - - // set the signature string that will be replaced at render time - var Sig = '_s' + Math.floor( Math.random() * 1000000 ) + '_', - // another signature to prepend to attributes and avoid checks: style, height, on[events]... - attPfx = '_a' + Math.floor( Math.random() * 1000000 ) + '_', - // rx to parse selectors, e.g. "+tr.foo[class]" - selRx = /^(\+)?([^\@\+]+)?\@?([^\+]+)?(\+)?$/, - // set automatically attributes for some tags - autoAttr = { - IMG:'src', - INPUT:'value' - }; - - return plugins; - - - /* * * * * * * * * * * * * * * * * * * * * * * * * * - core functions - * * * * * * * * * * * * * * * * * * * * * * * * * */ - - - // error utility - function error(e){ - alert(e); - if(typeof console !== 'undefined'){ - console.log(e); - debugger; - } - throw('pure error: ' + e); - } - - //return a new instance of plugins - function getPlugins(){ - var plugins = $p.plugins, - f = function(){}; - f.prototype = plugins; - - // do not overwrite functions if external definition - f.prototype.compile = plugins.compile || compile; - f.prototype.render = plugins.render || render; - f.prototype.autoRender = plugins.autoRender || autoRender; - f.prototype.find = plugins.find || find; - - // give the compiler and the error handling to the plugin context - f.prototype._compiler = compiler; - f.prototype._error = error; - - return new f(); - } - - // returns the outer HTML of a node - function outerHTML(node){ - // if IE take the internal method otherwise build one - return node.outerHTML || ( - function(n){ - var div = document.createElement('div'), h; - div.appendChild( n.cloneNode(true) ); - h = div.innerHTML; - div = null; - return h; - })(node); - } - - // check if the argument is an array - function isArray(o){ - return Object.prototype.toString.call( o ) === "[object Array]"; - } - - // returns the string generator function - function wrapquote(qfn, f){ - return function(ctxt){ - return qfn('' + f(ctxt)); - }; - } - - // convert a JSON HTML structure to a dom node and returns the leaf - function domify(ns, pa){ - pa = pa || document.createDocumentFragment(); - var nn, leaf; - for(var n in ns){ - nn = document.createElement(n); - pa.appendChild(nn); - if(typeof ns[n] === 'object'){ - leaf = domify(ns[n], nn); - }else{ - leaf = document.createElement(ns[n]); - nn.appendChild(leaf); - } - } - return leaf; - }; - - // default find using querySelector when available on the browser - function find(n, sel){ - if(typeof n === 'string'){ - sel = n; - n = false; - } - if(typeof document.querySelectorAll !== 'undefined'){ - return (n||document).querySelectorAll( sel ); - }else{ - error('You can test PURE standalone with: iPhone, FF3.5+, Safari4+ and IE8+\n\nTo run PURE on your browser, you need a JS library/framework with a CSS selector engine'); - } - } - - // create a function that concatenates constant string - // sections (given in parts) and the results of called - // functions to fill in the gaps between parts (fns). - // fns[n] fills in the gap between parts[n-1] and parts[n]; - // fns[0] is unused. - // this is the inner template evaluation loop. - function concatenator(parts, fns){ - return function(ctxt){ - var strs = [ parts[ 0 ] ], - n = parts.length, - fnVal, pVal, attLine, pos; - - for(var i = 1; i < n; i++){ - fnVal = fns[i]( ctxt ); - pVal = parts[i]; - - // if the value is empty and attribute, remove it - if(fnVal === ''){ - attLine = strs[ strs.length - 1 ]; - if( ( pos = attLine.search( /[\w]+=\"?$/ ) ) > -1){ - strs[ strs.length - 1 ] = attLine.substring( 0, pos ); - pVal = pVal.substr( 1 ); - } - } - - strs[ strs.length ] = fnVal; - strs[ strs.length ] = pVal; - } - return strs.join(''); - }; - } - - // parse and check the loop directive - function parseloopspec(p){ - var m = p.match( /^(\w+)\s*<-\s*(\S+)?$/ ); - if(m === null){ - error('bad loop spec: "' + p + '"'); - } - if(m[1] === 'item'){ - error('"item<-..." is a reserved word for the current running iteration.\n\nPlease choose another name for your loop.'); - } - if( !m[2] ){ //undefined or space(IE) - m[2] = function(ctxt){return ctxt.data;}; - } - return {name: m[1], sel: m[2]}; - } - - // parse a data selector and return a function that - // can traverse the data accordingly, given a context. - function dataselectfn(sel){ - if(typeof(sel) === 'function'){ - return sel; - } - //check for a valid js variable name with hyphen(for properties only) and $ - var m = sel.match(/^[a-zA-Z$_][\w$]*(\.[\w$-]*[^\.])*$/); - if(m === null){ - var found = false, s = sel, parts = [], pfns = [], i = 0, retStr; - // check if literal - if(/\'|\"/.test( s.charAt(0) )){ - if(/\'|\"/.test( s.charAt(s.length-1) )){ - retStr = s.substring(1, s.length-1); - return function(){ return retStr; }; - } - }else{ - // check if literal + #{var} - while((m = s.match(/#\{([^{}]+)\}/)) !== null){ - found = true; - parts[i++] = s.slice(0, m.index); - pfns[i] = dataselectfn(m[1]); - s = s.slice(m.index + m[0].length, s.length); - } - } - if(!found){ - error('bad data selector syntax: ' + sel); - } - parts[i] = s; - return concatenator(parts, pfns); - } - m = sel.split('.'); - return function(ctxt){ - var data = ctxt.data; - if(!data){ - return ''; - } - var v = ctxt[m[0]], - i = 0; - if(v){ - data = v.item; - i += 1; - } - var n = m.length; - for(; i < n; i++){ - if(!data){break;} - data = data[m[i]]; - } - return (!data && data !== 0) ? '':data; - }; - } - - // wrap in an object the target node/attr and their properties - function gettarget(dom, sel, isloop){ - var osel, prepend, selector, attr, append, target = []; - if( typeof sel === 'string' ){ - osel = sel; - var m = sel.match(selRx); - if( !m ){ - error( 'bad selector syntax: ' + sel ); - } - - prepend = m[1]; - selector = m[2]; - attr = m[3]; - append = m[4]; - - if(selector === '.' || ( !selector && attr ) ){ - target[0] = dom; - }else{ - target = plugins.find(dom, selector); - } - if(!target || target.length === 0){ - return {attr: null, nodes: target, set: null, sel: osel}; - } - }else{ - // autoRender node - prepend = sel.prepend; - attr = sel.attr; - append = sel.append; - target = [dom]; - } - - if( prepend || append ){ - if( prepend && append ){ - error('append/prepend cannot take place at the same time'); - }else if( isloop ){ - error('no append/prepend/replace modifiers allowed for loop target'); - }else if( append && isloop ){ - error('cannot append with loop (sel: ' + osel + ')'); - } - } - var setstr, getstr, quotefn, isStyle, isClass, an; - if(attr){ - isStyle = (/^style$/i).test(attr); - isClass = (/^class$/i).test(attr); - attName = isClass ? 'className' : attr; - setstr = function(node, s){ - node.setAttribute( attPfx + attr, s ); - if(attName in node && !isStyle){ - node[ attName ] = '' ; - } - if(node.nodeType === 1){ - node.removeAttribute( attr ); - } - }; - if( isStyle ){ - getstr = function(node){ return node.style.cssText;}; - }else{ - getstr = function(node){ return node.getAttribute(attr);}; - } - if( isStyle || isClass ){//IE no quotes special care - quotefn = function(s){ return s.replace(/\"/g, '"');}; - }else{ - quotefn = function(s){ return s.replace(/\"/g, '"').replace(/\s/g, ' ');}; - } - }else{ - if(isloop){ - setstr = function(node, s){ - // we can have a null parent node - // if we get overlapping targets. - var pn = node.parentNode; - if(pn){ - //replace node with s - var t = document.createTextNode(s); - node.parentNode.insertBefore(t, node.nextSibling); - node.parentNode.removeChild(node); - } - }; - }else{ - getstr = function(node){ return node.innerHTML; }; - setstr = function(node, s){ node.innerHTML = s; }; - } - quotefn = function(s){ return s; }; - } - var setfn; - if(prepend){ - setfn = function(node, s){ setstr( node, s + getstr( node ) );}; - }else if(append){ - setfn = function(node, s){ setstr( node, getstr( node ) + s );}; - }else{ - setfn = function(node, s){ setstr( node, s );}; - } - return {attr: attr, nodes: target, set: setfn, sel: osel, quotefn: quotefn}; - } - - function setsig(target, n){ - var sig = Sig + n + ':'; - for(var i = 0; i < target.nodes.length; i++){ - // could check for overlapping targets here. - target.set( target.nodes[i], sig ); - } - } - - // read de loop data, and pass it to the inner rendering function - function loopfn(name, dselect, inner){ - return function(ctxt){ - var a = dselect(ctxt), - old = ctxt[name], - temp = { items : a }, - strs = [], - buildArg = function(idx){ - ctxt.items = a; - ctxt.pos = temp.pos = idx; - ctxt.item = temp.item = a[ idx ]; - strs.push( inner( ctxt ) ); - }; - ctxt[name] = temp; - if( isArray(a) ){ - //loop on array - for(var i = 0, ii = a.length || 0; i < ii; i++){ - buildArg(i); - } - }else{ - //loop on collections - for(var prop in a){ - a.hasOwnProperty( prop ) && buildArg(prop); - } - } - typeof old !== 'undefined' ? ctxt[name] = old : delete ctxt[name]; - return strs.join(''); - }; - } - // generate the template for a loop node - function loopgen(dom, sel, loop, fns){ - var already = false; - var p; - for(var i in loop){ - if(loop.hasOwnProperty(i)){ - if(already){ - error('cannot have more than one loop on a target'); - } - p = i; - already = true; - } - } - if(!p){ - error('no loop spec'); - } - var dsel = loop[p]; - // if it's a simple data selector then we default to contents, not replacement. - if(typeof(dsel) === 'string' || typeof(dsel) === 'function'){ - loop = {}; - loop[p] = {root: dsel}; - return loopgen(dom, sel, loop, fns); - } - var spec = parseloopspec(p), - itersel = dataselectfn(spec.sel), - target = gettarget(dom, sel, true), - nodes = target.nodes; - - for(i = 0; i < nodes.length; i++){ - // could check for overlapping loop targets here by checking that - // root is still ancestor of node. - var node = nodes[i], - inner = compiler(node, dsel); - fns[fns.length] = wrapquote(target.quotefn, loopfn(spec.name, itersel, inner)); - target.nodes = [node]; // N.B. side effect on target. - setsig(target, fns.length - 1); - } - } - - function getAutoNodes(n, data){ - var ns = n.getElementsByTagName('*'), - an = [], - openLoops = {a:[],l:{}}, - cspec, - isNodeValue, - i, ii, j, jj, ni, cs, cj; - //for each node found in the template - for(i = -1, ii = ns.length; i < ii; i++){ - ni = i > -1 ?ns[i]:n; - if(ni.nodeType === 1 && ni.className !== ''){ - //when a className is found - cs = ni.className.split(' '); - // for each className - for(j = 0, jj=cs.length;j -1 || isNodeValue){ - ni.className = ni.className.replace('@'+cspec.attr, ''); - if(isNodeValue){ - cspec.attr = false; - } - } - an.push({n:ni, cspec:cspec}); - } - } - } - } - return an; - - function checkClass(c, tagName){ - // read the class - var ca = c.match(selRx), - attr = ca[3] || autoAttr[tagName], - cspec = {prepend:!!ca[1], prop:ca[2], attr:attr, append:!!ca[4], sel:c}, - val = isArray(data) ? data[0][cspec.prop] : data[cspec.prop], - i, ii, loopi; - // if first level of data is found - if(typeof val === 'undefined'){ - // check in existing open loops - for(i = openLoops.a.length-1; i >= 0; i--){ - loopi = openLoops.a[i]; - val = loopi.l[0][cspec.prop]; - if(typeof val !== 'undefined'){ - cspec.prop = loopi.p + '.' + cspec.prop; - if(openLoops.l[cspec.prop] === true){ - val = val[0]; - } - break; - } - } - } - // nothing found return - if(typeof val === 'undefined'){ - return false; - } - // set the data type and details - if(isArray(val)){ - openLoops.a.push( {l:val, p:cspec.prop} ); - openLoops.l[cspec.prop] = true; - cspec.t = 'loop'; - }else{ - cspec.t = 'str'; - } - return cspec; - } - } - - // returns a function that, given a context argument, - // will render the template defined by dom and directive. - function compiler(dom, directive, data, ans){ - var fns = []; - // autoRendering nodes parsing -> auto-nodes - ans = ans || data && getAutoNodes(dom, data); - if(data){ - var j, jj, cspec, n, target, nodes, itersel, node, inner; - // for each auto-nodes - while(ans.length > 0){ - cspec = ans[0].cspec; - n = ans[0].n; - ans.splice(0, 1); - if(cspec.t === 'str'){ - // if the target is a value - target = gettarget(n, cspec, false); - setsig(target, fns.length); - fns[fns.length] = wrapquote(target.quotefn, dataselectfn(cspec.prop)); - }else{ - // if the target is a loop - itersel = dataselectfn(cspec.sel); - target = gettarget(n, cspec, true); - nodes = target.nodes; - for(j = 0, jj = nodes.length; j < jj; j++){ - node = nodes[j]; - inner = compiler(node, false, data, ans); - fns[fns.length] = wrapquote(target.quotefn, loopfn(cspec.sel, itersel, inner)); - target.nodes = [node]; - setsig(target, fns.length - 1); - } - } - } - } - // read directives - var target, dsel; - for(var sel in directive){ - if(directive.hasOwnProperty(sel)){ - dsel = directive[sel]; - if(typeof(dsel) === 'function' || typeof(dsel) === 'string'){ - // set the value for the node/attr - target = gettarget(dom, sel, false); - setsig(target, fns.length); - fns[fns.length] = wrapquote(target.quotefn, dataselectfn(dsel)); - }else{ - // loop on node - loopgen(dom, sel, dsel, fns); - } - } - } - // convert node to a string - var h = outerHTML(dom), pfns = []; - // IE adds an unremovable "selected" attribute - // hard replace while waiting for a better solution - if (dom.tagName === 'OPTION' && (new RegExp(attPfx + 'selected', 'i')).test(h)) { - h = h.replace(/\sselected\s/, ' '); - } - // remove attribute prefix - h = h.split(attPfx).join(''); - - // slice the html string at "Sig" - var parts = h.split( Sig ), p; - // for each slice add the return string of - for(var i = 1; i < parts.length; i++){ - p = parts[i]; - // part is of the form "fn-number:..." as placed there by setsig. - pfns[i] = fns[ parseInt(p, 10) ]; - parts[i] = p.substring( p.indexOf(':') + 1 ); - } - return concatenator(parts, pfns); - } - // compile the template with directive - // if a context is passed, the autoRendering is triggered automatically - // return a function waiting the data as argument - function compile(directive, ctxt, template){ - var rfn = compiler( ( template || this[0] ).cloneNode(true), directive, ctxt); - return function(data, context){ - context = context || data; - return rfn({data: data, context:context}); - }; - } - //compile with the directive as argument - // run the template function on the context argument - // return an HTML string - // should replace the template and return this - function render(ctxt, directive){ - var fn = typeof directive === 'function' ? directive : plugins.compile( directive, false, this[0] ); - for(var i = 0, ii = this.length; i < ii; i++){ - this[i] = replaceWith( this[i], fn( ctxt, false )); - } - context = null; - return this; - } - - // compile the template with autoRender - // run the template function on the context argument - // return an HTML string - function autoRender(ctxt, directive){ - var fn = plugins.compile( directive, ctxt, this[0] ); - for(var i = 0, ii = this.length; i < ii; i++){ - this[i] = replaceWith( this[i], fn( ctxt, false)); - } - context = null; - return this; - } - - function replaceWith(elm, html){ - var div = document.createElement('DIV'), - tagName = elm.tagName.toLowerCase(), - ne, pa; - if((/td|tr|th/).test(tagName)){ - var parents = { tr:{table:'tbody'}, td:{table:{tbody:'tr'}}, th:{table:{thead:'tr'}} }; - pa = domify( parents[ tagName ] ); - }else if( ( /tbody|thead|tfoot/ ).test( tagName )){ - pa = document.createElement('table'); - }else{ - pa = document.createElement('div'); - } - - var ep = elm.parentNode; - // avoid IE mem leak - ep.insertBefore(pa, elm); - ep.removeChild(elm); - pa.innerHTML = html; - ne = pa.firstChild; - ep.insertBefore(ne, pa); - ep.removeChild(pa); - elm = ne; - - pa = ne = ep = null; - return elm; - } -}; - -$p.plugins = {}; - -$p.libs = { - dojo:function(){ - if(typeof document.querySelector === 'undefined'){ - $p.plugins.find = function(n, sel){ - return dojo.query(sel, n); - }; - } - }, - domassistant:function(){ - if(typeof document.querySelector === 'undefined'){ - $p.plugins.find = function(n, sel){ - return $(n).cssSelect(sel); - }; - } - DOMAssistant.attach({ - publicMethods : [ 'compile', 'render', 'autoRender'], - compile:function(directive, ctxt){ return $p(this).compile(directive, ctxt); }, - render:function(ctxt, directive){ return $( $p(this).render(ctxt, directive) )[0]; }, - autoRender:function(ctxt, directive){ return $( $p(this).autoRender(ctxt, directive) )[0]; } - }); - }, - jquery:function(){ - if(typeof document.querySelector === 'undefined'){ - $p.plugins.find = function(n, sel){ - return $(n).find(sel); - }; - } - jQuery.fn.extend({ - compile:function(directive, ctxt){ return $p(this[0]).compile(directive, ctxt); }, - render:function(ctxt, directive){ return jQuery( $p( this[0] ).render( ctxt, directive ) ); }, - autoRender:function(ctxt, directive){ return jQuery( $p( this[0] ).autoRender( ctxt, directive ) ); } - }); - }, - mootools:function(){ - if(typeof document.querySelector === 'undefined'){ - $p.plugins.find = function(n, sel){ - return $(n).getElements(sel); - }; - } - Element.implement({ - compile:function(directive, ctxt){ return $p(this).compile(directive, ctxt); }, - render:function(ctxt, directive){ return $p(this).render(ctxt, directive); }, - autoRender:function(ctxt, directive){ return $p(this).autoRender(ctxt, directive); } - }); - }, - prototype:function(){ - if(typeof document.querySelector === 'undefined'){ - $p.plugins.find = function(n, sel){ - n = n === document ? n.body : n; - return typeof n === 'string' ? $$(n) : $(n).select(sel); - }; - } - Element.addMethods({ - compile:function(element, directive, ctxt){ return $p(element).compile(directive, ctxt); }, - render:function(element, ctxt, directive){ return $p(element).render(ctxt, directive); }, - autoRender:function(element, ctxt, directive){ return $p(element).autoRender(ctxt, directive); } - }); - }, - sizzle:function(){ - if(typeof document.querySelector === 'undefined'){ - $p.plugins.find = function(n, sel){ - return Sizzle(sel, n); - }; - } - }, - sly:function(){ - if(typeof document.querySelector === 'undefined'){ - $p.plugins.find = function(n, sel){ - return Sly(sel, n); - }; - } - } -}; - -// get lib specifics if available -(function(){ - var libkey = - typeof dojo !== 'undefined' && 'dojo' || - typeof DOMAssistant !== 'undefined' && 'domassistant' || - typeof jQuery !== 'undefined' && 'jquery' || - typeof MooTools !== 'undefined' && 'mootools' || - typeof Prototype !== 'undefined' && 'prototype' || - typeof Sizzle !== 'undefined' && 'sizzle' || - typeof Sly !== 'undefined' && 'sly'; - - libkey && $p.libs[libkey](); -})(); \ No newline at end of file diff --git a/js/qunit.js b/js/qunit.js new file mode 100644 index 0000000..2656698 --- /dev/null +++ b/js/qunit.js @@ -0,0 +1,953 @@ +/* + * QUnit - A JavaScript Unit Testing Framework + * + * http://docs.jquery.com/QUnit + * + * Copyright (c) 2009 John Resig, Jörn Zaefferer + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + */ + +(function(window) { + +var QUnit = { + + // Initialize the configuration options + init: function init() { + config = { + stats: { all: 0, bad: 0 }, + moduleStats: { all: 0, bad: 0 }, + started: +new Date, + blocking: false, + assertions: [], + pollution: [], + filters: [], + queue: [] + }; + + var tests = id("qunit-tests"), + banner = id("qunit-banner"), + result = id("qunit-testresult"); + + if ( tests ) { + tests.innerHTML = ""; + } + + if ( banner ) { + banner.className = ""; + } + + if ( result ) { + result.parentNode.removeChild( result ); + } + }, + + // call on start of module test to prepend name to all tests + module: function module(name, lifecycle) { + config.currentModule = name; + + synchronize(function() { + if ( config.currentModule ) { + QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all ); + } + + config.currentModule = name; + config.moduleLifecycle = lifecycle; + config.moduleStats = { all: 0, bad: 0 }; + + QUnit.moduleStart( name ); + }); + }, + + test: function test(testName, callback) { + var name = testName, lifecycle, testEnvironment = {}; + + if ( config.currentModule ) { + name = config.currentModule + " module: " + name; + } + + if ( !validTest(name) ) { + return; + } + + synchronize(function() { + QUnit.testStart( testName ); + + lifecycle = extend({ + setup: function() {}, + teardown: function() {} + }, config.moduleLifecycle); + + config.assertions = []; + config.expected = null; + try { + if ( !config.pollution ) { + saveGlobal(); + } + + lifecycle.setup.call(testEnvironment); + } catch(e) { + QUnit.ok( false, "Setup failed on " + name + ": " + e.message ); + } + + try { + callback.call(testEnvironment); + } catch(e) { + fail("Test " + name + " died, exception and test follows", e, callback); + QUnit.ok( false, "Died on test #" + (config.assertions.length + 1) + ": " + e.message ); + // else next test will carry the responsibility + saveGlobal(); + + // Restart the tests if they're blocking + if ( config.blocking ) { + start(); + } + } + }); + + synchronize(function() { + try { + checkPollution(); + lifecycle.teardown.call(testEnvironment); + } catch(e) { + QUnit.ok( false, "Teardown failed on " + name + ": " + e.message ); + } + + try { + reset(); + } catch(e) { + fail("reset() failed, following Test " + name + ", exception and reset fn follows", e, reset); + } + + if ( config.expected && config.expected != config.assertions.length ) { + QUnit.ok( false, "Expected " + config.expected + " assertions, but " + config.assertions.length + " were run" ); + } + + var good = 0, bad = 0, + tests = id("qunit-tests"); + + config.stats.all += config.assertions.length; + config.moduleStats.all += config.assertions.length; + + if ( tests ) { + var ol = document.createElement("ol"); + ol.style.display = "none"; + + for ( var i = 0; i < config.assertions.length; i++ ) { + var assertion = config.assertions[i]; + + var li = document.createElement("li"); + li.className = assertion.result ? "pass" : "fail"; + li.innerHTML = assertion.message || "(no message)"; + ol.appendChild( li ); + + if ( assertion.result ) { + good++; + } else { + bad++; + config.stats.bad++; + config.moduleStats.bad++; + } + } + + var b = document.createElement("strong"); + b.innerHTML = name + " (" + bad + ", " + good + ", " + config.assertions.length + ")"; + + addEvent(b, "click", function() { + var next = b.nextSibling, display = next.style.display; + next.style.display = display === "none" ? "block" : "none"; + }); + + addEvent(b, "dblclick", function(e) { + var target = (e || window.event).target; + if ( target.nodeName.toLowerCase() === "strong" ) { + var text = "", node = target.firstChild; + + while ( node.nodeType === 3 ) { + text += node.nodeValue; + node = node.nextSibling; + } + + text = text.replace(/(^\s*|\s*$)/g, ""); + + if ( window.location ) { + window.location.href = window.location.href.match(/^(.+?)(\?.*)?$/)[1] + "?" + encodeURIComponent(text); + } + } + }); + + var li = document.createElement("li"); + li.className = bad ? "fail" : "pass"; + li.appendChild( b ); + li.appendChild( ol ); + tests.appendChild( li ); + + if ( bad ) { + var toolbar = id("qunit-testrunner-toolbar"); + if ( toolbar ) { + toolbar.style.display = "block"; + id("qunit-filter-pass").disabled = null; + id("qunit-filter-missing").disabled = null; + } + } + + } else { + for ( var i = 0; i < config.assertions.length; i++ ) { + if ( !config.assertions[i].result ) { + config.stats.bad++; + config.moduleStats.bad++; + } + } + } + + QUnit.testDone( testName, bad, config.assertions.length ); + + if ( !window.setTimeout && !config.queue.length ) { + done(); + } + }); + + if ( window.setTimeout && !config.doneTimer ) { + config.doneTimer = window.setTimeout(function(){ + if ( !config.queue.length ) { + done(); + } else { + synchronize( done ); + } + }, 13); + } + }, + + /** + * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. + */ + expect: function expect(asserts) { + config.expected = asserts; + }, + + /** + * Asserts true. + * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); + */ + ok: function ok(a, msg) { + QUnit.log(a, msg); + + config.assertions.push({ + result: !!a, + message: msg + }); + }, + + /** + * Checks that the first two arguments are equal, with an optional message. + * Prints out both actual and expected values. + * + * Prefered to ok( actual == expected, message ) + * + * @example equals( format("Received {0} bytes.", 2), "Received 2 bytes." ); + * + * @param Object actual + * @param Object expected + * @param String message (optional) + */ + equals: function equals(actual, expected, message) { + push(expected == actual, actual, expected, message); + }, + + same: function(a, b, message) { + push(QUnit.equiv(a, b), a, b, message); + }, + + start: function start() { + // A slight delay, to avoid any current callbacks + if ( window.setTimeout ) { + window.setTimeout(function() { + if ( config.timeout ) { + clearTimeout(config.timeout); + } + + config.blocking = false; + process(); + }, 13); + } else { + config.blocking = false; + process(); + } + }, + + stop: function stop(timeout) { + config.blocking = true; + + if ( timeout && window.setTimeout ) { + config.timeout = window.setTimeout(function() { + QUnit.ok( false, "Test timed out" ); + start(); + }, timeout); + } + }, + + /** + * Resets the test setup. Useful for tests that modify the DOM. + */ + reset: function reset() { + if ( window.jQuery ) { + jQuery("#main").html( config.fixture ); + jQuery.event.global = {}; + jQuery.ajaxSettings = extend({}, config.ajaxSettings); + } + }, + + /** + * Trigger an event on an element. + * + * @example triggerEvent( document.body, "click" ); + * + * @param DOMElement elem + * @param String type + */ + triggerEvent: function triggerEvent( elem, type, event ) { + if ( document.createEvent ) { + event = document.createEvent("MouseEvents"); + event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView, + 0, 0, 0, 0, 0, false, false, false, false, 0, null); + elem.dispatchEvent( event ); + + } else if ( elem.fireEvent ) { + elem.fireEvent("on"+type); + } + }, + + // Logging callbacks + done: function done(failures, total) {}, + log: function log(result, message) {}, + testStart: function testStart(name) {}, + testDone: function testDone(name, failures, total) {}, + moduleStart: function moduleStart(name) {}, + moduleDone: function moduleDone(name, failures, total) {} +}; + +// Maintain internal state +var config = { + // The queue of tests to run + queue: [], + + // block until document ready + blocking: true +}; + +// Load paramaters +(function() { + var location = window.location || { search: "", protocol: "file:" }, + GETParams = location.search.slice(1).split('&'); + + for ( var i = 0; i < GETParams.length; i++ ) { + GETParams[i] = decodeURIComponent( GETParams[i] ); + if ( GETParams[i] === "noglobals" ) { + GETParams.splice( i, 1 ); + i--; + config.noglobals = true; + } + } + + // restrict modules/tests by get parameters + config.filters = GETParams; + + // Figure out if we're running the tests from a server or not + QUnit.isLocal = !!(location.protocol === 'file:'); +})(); + +// Expose the API as global variables, unless an 'exports' +// object exists, in that case we assume we're in CommonJS +if ( typeof exports === "undefined" || typeof require === "undefined" ) { + extend(window, QUnit); + window.QUnit = QUnit; +} else { + extend(exports, QUnit); + exports.QUnit = QUnit; +} + +addEvent(window, "load", function() { + // Initialize the config, saving the execution queue + var queue = config.queue; + QUnit.init(); + config.queue = queue; + + var userAgent = id("qunit-userAgent"); + if ( userAgent ) { + userAgent.innerHTML = navigator.userAgent; + } + + var toolbar = id("qunit-testrunner-toolbar"); + if ( toolbar ) { + toolbar.style.display = "none"; + + var filter = document.createElement("input"); + filter.type = "checkbox"; + filter.id = "qunit-filter-pass"; + filter.disabled = true; + addEvent( filter, "click", function() { + var li = document.getElementsByTagName("li"); + for ( var i = 0; i < li.length; i++ ) { + if ( li[i].className.indexOf("pass") > -1 ) { + li[i].style.display = filter.checked ? "none" : "block"; + } + } + }); + toolbar.appendChild( filter ); + + var label = document.createElement("label"); + label.setAttribute("for", "filter-pass"); + label.innerHTML = "Hide passed tests"; + toolbar.appendChild( label ); + + var missing = document.createElement("input"); + missing.type = "checkbox"; + missing.id = "qunit-filter-missing"; + missing.disabled = true; + addEvent( missing, "click", function() { + var li = document.getElementsByTagName("li"); + for ( var i = 0; i < li.length; i++ ) { + if ( li[i].className.indexOf("fail") > -1 && li[i].innerHTML.indexOf('missing test - untested code is broken code') > - 1 ) { + li[i].parentNode.parentNode.style.display = missing.checked ? "none" : "block"; + } + } + }); + toolbar.appendChild( missing ); + + label = document.createElement("label"); + label.setAttribute("for", "filter-missing"); + label.innerHTML = "Hide missing tests (untested code is broken code)"; + toolbar.appendChild( label ); + } + + var main = id('main'); + if ( main ) { + config.fixture = main.innerHTML; + } + + if ( window.jQuery ) { + config.ajaxSettings = window.jQuery.ajaxSettings; + } + + start(); +}); + +function done() { + if ( config.doneTimer && window.clearTimeout ) { + window.clearTimeout( config.doneTimer ); + config.doneTimer = null; + } + + if ( config.queue.length ) { + config.doneTimer = window.setTimeout(function(){ + if ( !config.queue.length ) { + done(); + } else { + synchronize( done ); + } + }, 13); + + return; + } + + // Log the last module results + if ( config.currentModule ) { + QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all ); + } + + var banner = id("qunit-banner"), + tests = id("qunit-tests"), + html = ['Tests completed in ', + +new Date - config.started, ' milliseconds.
', + '', config.stats.all - config.stats.bad, ' tests of ', config.stats.all, ' passed, ', config.stats.bad,' failed.'].join(''); + + if ( banner ) { + banner.className += " " + (config.stats.bad ? "fail" : "pass"); + } + + if ( tests ) { + var result = id("qunit-testresult"); + + if ( !result ) { + result = document.createElement("p"); + result.id = "qunit-testresult"; + result.className = "result"; + tests.parentNode.insertBefore( result, tests.nextSibling ); + } + + result.innerHTML = html; + } + + QUnit.done( config.stats.bad, config.stats.all ); +} + +function validTest( name ) { + var i = config.filters.length, + run = false; + + if ( !i ) { + return true; + } + + while ( i-- ) { + var filter = config.filters[i], + not = filter.charAt(0) == '!'; + + if ( not ) { + filter = filter.slice(1); + } + + if ( name.indexOf(filter) !== -1 ) { + return !not; + } + + if ( not ) { + run = true; + } + } + + return run; +} + +function push(result, actual, expected, message) { + message = message || (result ? "okay" : "failed"); + QUnit.ok( result, result ? message + ": " + expected : message + ", expected: " + QUnit.jsDump.parse(expected) + " result: " + QUnit.jsDump.parse(actual) ); +} + +function synchronize( callback ) { + config.queue.push( callback ); +} + +function process() { + while ( config.queue.length && !config.blocking ) { + config.queue.shift()(); + } +} + +function saveGlobal() { + config.pollution = []; + + if ( config.noglobals ) { + for ( var key in window ) { + config.pollution.push(key); + } + } +} + +function checkPollution( name ) { + var old = config.pollution; + saveGlobal(); + + if ( config.pollution.length > old.length ) { + ok( false, "Introduced global variable(s): " + diff(old, config.pollution).join(", ") ); + config.expected++; + } +} + +function diff( clean, dirty ) { + var results = []; + + for ( var i = 0; i < dirty.length; i++ ) { + for ( var c = 0; c < clean.length; c++ ) { + if ( clean[c] === dirty[i] ) { + results.push( clean[c] ); + } + } + } + + return results; +} + +function fail(message, exception, callback) { + if ( typeof console !== "undefined" && console.error && console.warn ) { + console.error(message); + console.error(exception); + console.warn(callback.toString()); + + } else if ( window.opera && opera.postError ) { + opera.postError(message, exception, callback.toString); + } +} + +function extend(a, b) { + for ( var prop in b ) { + a[prop] = b[prop]; + } + + return a; +} + +function addEvent(elem, type, fn) { + if ( elem.addEventListener ) { + elem.addEventListener( type, fn, false ); + } else if ( elem.attachEvent ) { + elem.attachEvent( "on" + type, fn ); + } else { + fn(); + } +} + +function id(name) { + return !!(typeof document !== "undefined" && document && document.getElementById) && + document.getElementById( name ); +} + +// Test for equality any JavaScript type. +// Discussions and reference: http://philrathe.com/articles/equiv +// Test suites: http://philrathe.com/tests/equiv +// Author: Philippe Rathé +QUnit.equiv = function () { + + var innerEquiv; // the real equiv function + var callers = []; // stack to decide between skip/abort functions + + + // Determine what is o. + function hoozit(o) { + if (o.constructor === String) { + return "string"; + + } else if (o.constructor === Boolean) { + return "boolean"; + + } else if (o.constructor === Number) { + + if (isNaN(o)) { + return "nan"; + } else { + return "number"; + } + + } else if (typeof o === "undefined") { + return "undefined"; + + // consider: typeof null === object + } else if (o === null) { + return "null"; + + // consider: typeof [] === object + } else if (o instanceof Array) { + return "array"; + + // consider: typeof new Date() === object + } else if (o instanceof Date) { + return "date"; + + // consider: /./ instanceof Object; + // /./ instanceof RegExp; + // typeof /./ === "function"; // => false in IE and Opera, + // true in FF and Safari + } else if (o instanceof RegExp) { + return "regexp"; + + } else if (typeof o === "object") { + return "object"; + + } else if (o instanceof Function) { + return "function"; + } else { + return undefined; + } + } + + // Call the o related callback with the given arguments. + function bindCallbacks(o, callbacks, args) { + var prop = hoozit(o); + if (prop) { + if (hoozit(callbacks[prop]) === "function") { + return callbacks[prop].apply(callbacks, args); + } else { + return callbacks[prop]; // or undefined + } + } + } + + var callbacks = function () { + + // for string, boolean, number and null + function useStrictEquality(b, a) { + if (b instanceof a.constructor || a instanceof b.constructor) { + // to catch short annotaion VS 'new' annotation of a declaration + // e.g. var i = 1; + // var j = new Number(1); + return a == b; + } else { + return a === b; + } + } + + return { + "string": useStrictEquality, + "boolean": useStrictEquality, + "number": useStrictEquality, + "null": useStrictEquality, + "undefined": useStrictEquality, + + "nan": function (b) { + return isNaN(b); + }, + + "date": function (b, a) { + return hoozit(b) === "date" && a.valueOf() === b.valueOf(); + }, + + "regexp": function (b, a) { + return hoozit(b) === "regexp" && + a.source === b.source && // the regex itself + a.global === b.global && // and its modifers (gmi) ... + a.ignoreCase === b.ignoreCase && + a.multiline === b.multiline; + }, + + // - skip when the property is a method of an instance (OOP) + // - abort otherwise, + // initial === would have catch identical references anyway + "function": function () { + var caller = callers[callers.length - 1]; + return caller !== Object && + typeof caller !== "undefined"; + }, + + "array": function (b, a) { + var i; + var len; + + // b could be an object literal here + if ( ! (hoozit(b) === "array")) { + return false; + } + + len = a.length; + if (len !== b.length) { // safe and faster + return false; + } + for (i = 0; i < len; i++) { + if ( ! innerEquiv(a[i], b[i])) { + return false; + } + } + return true; + }, + + "object": function (b, a) { + var i; + var eq = true; // unless we can proove it + var aProperties = [], bProperties = []; // collection of strings + + // comparing constructors is more strict than using instanceof + if ( a.constructor !== b.constructor) { + return false; + } + + // stack constructor before traversing properties + callers.push(a.constructor); + + for (i in a) { // be strict: don't ensures hasOwnProperty and go deep + + aProperties.push(i); // collect a's properties + + if ( ! innerEquiv(a[i], b[i])) { + eq = false; + } + } + + callers.pop(); // unstack, we are done + + for (i in b) { + bProperties.push(i); // collect b's properties + } + + // Ensures identical properties name + return eq && innerEquiv(aProperties.sort(), bProperties.sort()); + } + }; + }(); + + innerEquiv = function () { // can take multiple arguments + var args = Array.prototype.slice.apply(arguments); + if (args.length < 2) { + return true; // end transition + } + + return (function (a, b) { + if (a === b) { + return true; // catch the most you can + } else if (a === null || b === null || typeof a === "undefined" || typeof b === "undefined" || hoozit(a) !== hoozit(b)) { + return false; // don't lose time with error prone cases + } else { + return bindCallbacks(a, callbacks, [b, a]); + } + + // apply transition with (1..n) arguments + })(args[0], args[1]) && arguments.callee.apply(this, args.splice(1, args.length -1)); + }; + + return innerEquiv; + +}(); + +/** + * jsDump + * Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com + * Licensed under BSD (http://www.opensource.org/licenses/bsd-license.php) + * Date: 5/15/2008 + * @projectDescription Advanced and extensible data dumping for Javascript. + * @version 1.0.0 + * @author Ariel Flesler + * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html} + */ +QUnit.jsDump = (function() { + function quote( str ) { + return '"' + str.toString().replace(/"/g, '\\"') + '"'; + }; + function literal( o ) { + return o + ''; + }; + function join( pre, arr, post ) { + var s = jsDump.separator(), + base = jsDump.indent(), + inner = jsDump.indent(1); + if ( arr.join ) + arr = arr.join( ',' + s + inner ); + if ( !arr ) + return pre + post; + return [ pre, inner + arr, base + post ].join(s); + }; + function array( arr ) { + var i = arr.length, ret = Array(i); + this.up(); + while ( i-- ) + ret[i] = this.parse( arr[i] ); + this.down(); + return join( '[', ret, ']' ); + }; + + var reName = /^function (\w+)/; + + var jsDump = { + parse:function( obj, type ) { //type is used mostly internally, you can fix a (custom)type in advance + var parser = this.parsers[ type || this.typeOf(obj) ]; + type = typeof parser; + + return type == 'function' ? parser.call( this, obj ) : + type == 'string' ? parser : + this.parsers.error; + }, + typeOf:function( obj ) { + var type = typeof obj, + f = 'function';//we'll use it 3 times, save it + return type != 'object' && type != f ? type : + !obj ? 'null' : + obj.exec ? 'regexp' :// some browsers (FF) consider regexps functions + obj.getHours ? 'date' : + obj.scrollBy ? 'window' : + obj.nodeName == '#document' ? 'document' : + obj.nodeName ? 'node' : + obj.item ? 'nodelist' : // Safari reports nodelists as functions + obj.callee ? 'arguments' : + obj.call || obj.constructor != Array && //an array would also fall on this hack + (obj+'').indexOf(f) != -1 ? f : //IE reports functions like alert, as objects + 'length' in obj ? 'array' : + type; + }, + separator:function() { + return this.multiline ? this.HTML ? '
' : '\n' : this.HTML ? ' ' : ' '; + }, + indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing + if ( !this.multiline ) + return ''; + var chr = this.indentChar; + if ( this.HTML ) + chr = chr.replace(/\t/g,' ').replace(/ /g,' '); + return Array( this._depth_ + (extra||0) ).join(chr); + }, + up:function( a ) { + this._depth_ += a || 1; + }, + down:function( a ) { + this._depth_ -= a || 1; + }, + setParser:function( name, parser ) { + this.parsers[name] = parser; + }, + // The next 3 are exposed so you can use them + quote:quote, + literal:literal, + join:join, + // + _depth_: 1, + // This is the list of parsers, to modify them, use jsDump.setParser + parsers:{ + window: '[Window]', + document: '[Document]', + error:'[ERROR]', //when no parser is found, shouldn't happen + unknown: '[Unknown]', + 'null':'null', + undefined:'undefined', + 'function':function( fn ) { + var ret = 'function', + name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE + if ( name ) + ret += ' ' + name; + ret += '('; + + ret = [ ret, this.parse( fn, 'functionArgs' ), '){'].join(''); + return join( ret, this.parse(fn,'functionCode'), '}' ); + }, + array: array, + nodelist: array, + arguments: array, + object:function( map ) { + var ret = [ ]; + this.up(); + for ( var key in map ) + ret.push( this.parse(key,'key') + ': ' + this.parse(map[key]) ); + this.down(); + return join( '{', ret, '}' ); + }, + node:function( node ) { + var open = this.HTML ? '<' : '<', + close = this.HTML ? '>' : '>'; + + var tag = node.nodeName.toLowerCase(), + ret = open + tag; + + for ( var a in this.DOMAttrs ) { + var val = node[this.DOMAttrs[a]]; + if ( val ) + ret += ' ' + a + '=' + this.parse( val, 'attribute' ); + } + return ret + close + open + '/' + tag + close; + }, + functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function + var l = fn.length; + if ( !l ) return ''; + + var args = Array(l); + while ( l-- ) + args[l] = String.fromCharCode(97+l);//97 is 'a' + return ' ' + args.join(', ') + ' '; + }, + key:quote, //object calls it internally, the key part of an item in a map + functionCode:'[code]', //function calls it internally, it's the content of the function + attribute:quote, //node calls it internally, it's an html attribute value + string:quote, + date:quote, + regexp:literal, //regex + number:literal, + 'boolean':literal + }, + DOMAttrs:{//attributes to dump from nodes, name=>realName + id:'id', + name:'name', + 'class':'className' + }, + HTML:true,//if true, entities are escaped ( <, >, \t, space and \n ) + indentChar:' ',//indentation unit + multiline:true //if true, items in a collection, are separated by a \n, else just a space. + }; + + return jsDump; +})(); + +})(this); diff --git a/tests.html b/tests.html new file mode 100644 index 0000000..bad3b27 --- /dev/null +++ b/tests.html @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file -- cgit v0.9.1