├── .gitignore ├── src ├── brook │ ├── dom │ │ ├── gateway.js │ │ └── compat.js │ ├── mobile │ │ └── dom │ │ │ └── event.js │ ├── model.js │ ├── lambda.js │ ├── widget.js │ ├── channel.js │ ├── view │ │ └── htmltemplate.js │ └── util.js └── brook.js ├── README.md ├── t ├── brook.gateway.html ├── brook.html ├── brook.view.htmltemplate.html ├── brook.widget.html ├── tlib │ ├── run.js │ └── qunit │ │ ├── qunit.css │ │ └── qunit.js ├── brook.widget.unit.js ├── spec │ └── BrookSpec.js ├── brook.gateway.unit.js ├── brook.unit.js └── brook.view.htmltemplate.unit.js ├── package.json ├── Gruntfile.js └── lib ├── namespace.js └── html-template-core.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .tern-port 3 | -------------------------------------------------------------------------------- /src/brook/dom/gateway.js: -------------------------------------------------------------------------------- 1 | /*global Namespace*/ 2 | 3 | Namespace('brook.dom.gateway') 4 | .define(function(ns){ 5 | 6 | ns.provide({}); 7 | }); 8 | -------------------------------------------------------------------------------- /src/brook/mobile/dom/event.js: -------------------------------------------------------------------------------- 1 | /*global Namespace*/ 2 | 3 | Namespace('brook.dom.event') 4 | .use('brook promise') 5 | .define(function(ns){ 6 | 7 | }); 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 中/大規模開発のためのJavaScript Framework "brook" 2 | 3 | brookは、複数人でWebアプリケーションを開発する為に必要なパーツを提供するJavaScriptフレームワークです。 4 | prototype.jsやjQueryのようなDOMの足回りを補完するためのライブラリと同居する事ができます。 5 | 簡易なMVC機構とプロミスという概念で非同期処理をモジュラに取り扱う為の仕組みを提供しています。 6 | -------------------------------------------------------------------------------- /t/brook.gateway.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Brook Core Test 5 | 6 | 15 | 16 | 17 | 18 | 19 | 20 |

Brook-Core

21 |

22 |

23 |
    24 | 25 | 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "brook", 3 | "version": "0.0.1", 4 | "description": "中/大規模開発のためのJavaScript Framework", 5 | "main": "build/node-brook.js", 6 | "scripts": { 7 | "test": "grunt qunit" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git://github.com/hirokidaichi/brook.git" 12 | }, 13 | "keywords": [ 14 | "javascript", 15 | "framework" 16 | ], 17 | "author": "hirokidaichi", 18 | "license": "none", 19 | "devDependencies": { 20 | "grunt": "~0.4.1", 21 | "grunt-contrib-qunit": "~0.2.2", 22 | "grunt-contrib-jshint": "~0.6.4", 23 | "grunt-contrib-concat": "~0.3.0", 24 | "grunt-contrib-uglify": "~0.2.4", 25 | "grunt-jasmine-node": "~0.1.0" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /t/brook.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Brook Core Test 5 | 6 | 16 | 17 | 18 | 19 | 20 | 21 |

    Brook-Core

    22 |

    23 |

    24 |
      25 | 26 | 27 | -------------------------------------------------------------------------------- /t/brook.view.htmltemplate.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Brook Core Test 5 | 6 | 7 | 17 | 18 | 19 | 20 | 21 | 22 |
      23 |
      24 |
      25 |
      26 |

      Brook-Core

      27 |

      28 |

      29 |
        30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /t/brook.widget.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Brook Widget Test 5 | 6 | 16 | 17 | 18 | 19 | 20 | 21 |

        Brook-Widget

        22 |

        23 |

        24 |
          25 |
          26 |
          27 |
          28 |
          29 |
          30 |
          31 |
          32 |
          33 |
          34 | 35 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/brook/model.js: -------------------------------------------------------------------------------- 1 | /** 2 | @fileOverview brook/model.js 3 | @author daichi.hiroki 4 | */ 5 | 6 | /*global Namespace*/ 7 | 8 | /** 9 | @name brook.model 10 | @namespace mvcにおけるmodelインタフェースを提供します。 11 | */ 12 | Namespace('brook.model') 13 | .use('brook promise') 14 | .use('brook.util *') 15 | .use('brook.channel *') 16 | .use('brook.lambda *') 17 | .define(function(ns){ 18 | /** 19 | * @class brook.model.createModelで生成されるインスタンスのインナークラス 20 | * @name _Model 21 | * @memberOf brook.model 22 | * @description 23 | * mvcにおけるmodelインタフェースをもったクラス 24 | */ 25 | var Model = function(obj){ 26 | this.methods = {}; 27 | this.channels= {}; 28 | for( var prop in obj ){ 29 | if( !obj.hasOwnProperty(prop) ) 30 | continue; 31 | this.addMethod( prop,obj[prop]); 32 | } 33 | }; 34 | Model.prototype.addMethod = function(method,promise){ 35 | if( this.methods[method] ) 36 | throw('already '+ method +' defined'); 37 | var channel = ns.createChannel(); 38 | this.methods[method] = promise.bind( channel.send() ); 39 | this.channels[method] = channel; 40 | return this; 41 | }; 42 | Model.prototype.notify = function(method){ 43 | return ns.promise().bind( this.methods[method] ); 44 | }; 45 | Model.prototype.method = function(method){ 46 | if( !this.channels[method] ) 47 | throw('do not observe undefined method'); 48 | return this.channels[method]; 49 | }; 50 | var createModel = function(obj){ 51 | return new Model(obj); 52 | }; 53 | ns.provide({ 54 | createModel : createModel 55 | }); 56 | }); 57 | 58 | -------------------------------------------------------------------------------- /src/brook/lambda.js: -------------------------------------------------------------------------------- 1 | /** 2 | @fileOverview brook.lambda 3 | @author daichi.hiroki 4 | */ 5 | 6 | /*global Namespace*/ 7 | 8 | /** 9 | @name brook.lambda 10 | @namespace 簡単に小さな関数を作る為のテンプレートを提供します。 11 | */ 12 | 13 | Namespace('brook.lambda') 14 | .define(function(ns){ 15 | var cache = {}; 16 | var hasArg = function(expression){ 17 | return expression.indexOf('->') >= 0; 18 | }; 19 | var parseExpression = function(expression){ 20 | var fixed = hasArg( expression ) ? expression : "$->"+expression; 21 | var splitted = fixed.split("->"); 22 | var argsExp = splitted.shift(); 23 | var bodyExp = splitted.join('->'); 24 | return { 25 | argumentNames : argsExp.split(','), 26 | body : hasArg(bodyExp) ? lambda( bodyExp ).toString() : bodyExp 27 | }; 28 | }; 29 | /** 30 | * @name lambda 31 | * @function 32 | * @memberOf brook.lambda 33 | * @param {string} expression 34 | * @return {function} 35 | * @description 36 | * 文字列表現を受け取り、シンプルな関数を生成します。 37 | * @example 38 | * var f = lambda('$ * $'); // 第一引数を二乗する関数 39 | * @example 40 | * var f = lambda('x,y-> x + y'); // xとyを受け取って、x+yを返す 41 | * @example 42 | * var f = lambda('x->y->z-> x+y+z'); // 部分適用できる関数を作る 43 | */ 44 | var lambda = function(expression){ 45 | if( cache[expression] ) 46 | return cache[expression]; 47 | var parsed = parseExpression(expression); 48 | /*jshint evil: true */ 49 | var func = new Function( parsed.argumentNames,"return ("+ parsed.body + ");"); 50 | cache[expression] = func; 51 | return func; 52 | }; 53 | ns.provide({ 54 | lambda : lambda 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /t/tlib/run.js: -------------------------------------------------------------------------------- 1 | function setState(obj) { 2 | phantom.state = JSON.stringify(obj); 3 | } 4 | 5 | function getState() { 6 | if (phantom.state) { 7 | return JSON.parse(phantom.state); 8 | } else { 9 | return null; 10 | } 11 | } 12 | 13 | function nextTest(passed, failed) { 14 | var state = getState(); 15 | var files = state.files; 16 | 17 | files.shift(); 18 | var state = { 19 | files: files, 20 | passed: state.passed + passed, 21 | failed: state.failed + failed 22 | }; 23 | 24 | if (files[0]) { 25 | setState(state); 26 | phantom.open(files[0]); 27 | } else { 28 | console.log([ state.passed, 'tests of', 29 | state.passed + state.failed, 30 | 'passed,', 31 | state.failed, 32 | 'failed.' ].join(' ')); 33 | phantom.exit(state.failed > 0); 34 | } 35 | } 36 | 37 | function qunitWatcher() { 38 | var el = document.getElementById('qunit-testresult'); 39 | var passed, total, failed; 40 | 41 | if (el && el.innerText.match('completed')) { 42 | try { 43 | failed = el.getElementsByClassName('failed')[0].innerHTML; 44 | } catch (e) { 45 | ; 46 | } 47 | 48 | var argv = ['passed', 'failed'].map(function (s) { 49 | var html = el.getElementsByClassName(s)[0].innerHTML; 50 | return parseInt(html, 10); 51 | }); 52 | console.log(argv.join('\t') + '\t' + location.href); 53 | nextTest.apply(null, argv); 54 | } 55 | } 56 | 57 | if (! getState()) { 58 | var files = phantom.args; 59 | setState({ files: files }); 60 | 61 | console.log('Passed\tFailed'); 62 | phantom.open(files[0]); 63 | } else { 64 | setInterval(qunitWatcher, 100); 65 | } 66 | -------------------------------------------------------------------------------- /t/brook.widget.unit.js: -------------------------------------------------------------------------------- 1 | /* global HTMLTemplate:false, Namespace:false, test:false, expect:false, ok:false, equal:false, stop:false, start:false */ 2 | 3 | Namespace('test') 4 | .use('brook.dom.compat classList,dataset') 5 | .define(function(ns){ 6 | var counter = 0; 7 | var isValidElement= function(element,namespace){ 8 | test('test'+counter++,function(){ 9 | ok(element, 'element exists'); 10 | ok(! ns.classList(element).contains('widget'),'element contains class-name "widget"'); 11 | equal( ns.dataset(element).widgetNamespace , namespace ); 12 | }); 13 | }; 14 | ns.provide({ 15 | isValidElement : isValidElement 16 | }); 17 | }); 18 | 19 | Namespace('widget.testerror') 20 | .use('brook *') 21 | .use('test *') 22 | .use('brook.channel *') 23 | .define(function(ns){ 24 | var errorHandler = ns.promise(function(n,v) { 25 | test('testerror',function(){ 26 | equal(v, 'registerElement error'); 27 | }); 28 | }); 29 | ns.observeChannel('error', errorHandler); 30 | ns.provide({ 31 | registerElement:function(element, dataset){ 32 | ns.isValidElement(element,'widget.testerror'); 33 | throw('registerElement error'); 34 | } 35 | }); 36 | }); 37 | 38 | Namespace('widget.test01') 39 | .use('test *') 40 | .use('brook.widget *') 41 | .define(function(ns){ 42 | ns.provide({ 43 | registerElement : function(element){ 44 | ns.isValidElement(element,'widget.test01'); 45 | element.innerHTML = "
          hello
          "; 46 | ns.bindAllWidget.run(); 47 | } 48 | }); 49 | }); 50 | Namespace('widget.test02') 51 | .use('test *') 52 | .define(function(ns){ 53 | ns.provide({ 54 | registerElements : function(elements){ 55 | for (var i=0,l= elements.length;i 4 | */ 5 | 6 | /*global Namespace*/ 7 | 8 | /** 9 | @name brook.widget 10 | @namespace details here 11 | */ 12 | Namespace('brook.widget') 13 | .use('brook promise') 14 | .use('brook.channel *') 15 | .use('brook.util *') 16 | .use('brook.dom.compat *') 17 | .define(function(ns){ 18 | var TARGET_CLASS_NAME = 'widget'; 19 | 20 | var classList = ns.classList; 21 | var dataset = ns.dataset; 22 | var widgetChannel = ns.channel('widget'); 23 | var errorChannel = ns.channel('error'); 24 | 25 | var removeClassName = function(className,element){ 26 | classList(element).remove(className); 27 | }; 28 | var elementsByClassName = ns.promise(function(n,v){ 29 | v = v || TARGET_CLASS_NAME; 30 | n([v,Array.prototype.slice.call(ns.getElementsByClassName(v))]); 31 | }); 32 | var mapByNamespace = ns.promise(function(n,val){ 33 | var targetClassName = val[0]; 34 | var widgetElements = val[1]; 35 | var map = {}; 36 | for( var i = 0,l = widgetElements.length;i 4 | */ 5 | 6 | /*global Namespace sendChannel*/ 7 | 8 | /** 9 | @name brook.channel 10 | @namespace promiseをベースとしたobserver patternのシンプルな実装を提供します。 11 | */ 12 | Namespace('brook.channel') 13 | .use('brook promise') 14 | .use('brook.util scatter') 15 | .define(function(ns){ 16 | var indexOf = function(list, value) { 17 | for (var i = 0, l = list.length; i < l; i++) 18 | if (list[i] === value) return i; 19 | return -1; 20 | }; 21 | /** 22 | * @class brook.channel.createChannelで生成されるインスタンスのインナークラス 23 | * @name _Channel 24 | * @memberOf brook.channel 25 | * @description 26 | * promiseを登録できるobserverクラス 27 | */ 28 | var Channel = function(){ 29 | this.queue = []; 30 | this.promises = []; 31 | }; 32 | (function(proto){ 33 | /**#@+ 34 | * @methodOf brook.channel._Channel.prototype 35 | */ 36 | var through = function(k){return k;}; 37 | /** 38 | * @name send 39 | */ 40 | proto.send = function(func){ 41 | func = ( func ) ? func : through; 42 | var _self = this; 43 | return ns.promise(function(next,val){ 44 | _self.sendMessage(func(val)); 45 | next(val); 46 | }); 47 | }; 48 | /** 49 | * @name sendMessage 50 | */ 51 | proto.sendMessage = function(msg){ 52 | var scatter = ns.scatter(1000); 53 | var sendError = sendChannel('error'); 54 | 55 | this.queue.push(msg); 56 | var makeRunner = function(message) { 57 | return ns.promise(function(next, promise) { 58 | promise.run(message); 59 | }); 60 | }; 61 | while( this.queue.length ){ 62 | var message = this.queue.shift(); 63 | var runner = makeRunner(message); 64 | runner.setErrorHandler(sendError); 65 | scatter.bind(runner).run(this.promises); 66 | } 67 | }; 68 | /** 69 | * @name observe 70 | */ 71 | proto.observe = function(promise){ 72 | //do not register same promise twice 73 | if (indexOf(this.promises, promise) > -1) return; 74 | this.promises.push(promise); 75 | }; 76 | 77 | proto.stopObserving = function(promise){ 78 | var index = indexOf(this.promises, promise); 79 | if (index > -1) this.promises.splice(index, 1); 80 | }; 81 | /**#@-*/ 82 | })(Channel.prototype); 83 | 84 | var channel = function(name){ 85 | if( name ) { 86 | return getNamedChannel(name); 87 | } 88 | return new Channel(); 89 | }; 90 | 91 | var NAMED_CHANNEL = {}; 92 | var getNamedChannel = function(name){ 93 | if( NAMED_CHANNEL[name] ) { 94 | return NAMED_CHANNEL[name]; 95 | } 96 | NAMED_CHANNEL[name] = new Channel(); 97 | return NAMED_CHANNEL[name]; 98 | }; 99 | var observeChannel = function(name,promise){ 100 | getNamedChannel( name ).observe( promise ); 101 | }; 102 | var stopObservingChannel = function(name,promise){ 103 | getNamedChannel( name ).stopObserving( promise ); 104 | }; 105 | var sendChannel = function(name,func){ 106 | var channel = getNamedChannel( name ); 107 | return channel.send(func); 108 | }; 109 | ns.provide({ 110 | channel : channel, 111 | sendChannel : sendChannel, 112 | observeChannel : observeChannel, 113 | stopObservingChannel : stopObservingChannel, 114 | createChannel : function(){ return new Channel();} 115 | }); 116 | }); 117 | 118 | 119 | -------------------------------------------------------------------------------- /src/brook.js: -------------------------------------------------------------------------------- 1 | /** 2 | @fileOverview brook 3 | @author daichi.hiroki 4 | */ 5 | 6 | /*global Namespace*/ 7 | 8 | 9 | /** 10 | @name brook 11 | @namespace brookライブラリ群のルートとなる名前空間です。promiseの生成処理を持っています。 12 | */ 13 | Namespace('brook').define(function(ns){ 14 | var VERSION = "0.01"; 15 | /** 16 | * @class brook.promiseで生成されるインスタンスのインナークラス 17 | * @name _Promise 18 | * @memberOf brook 19 | * @description 20 | * 実行する前の次の処理をもつオブジェクトです。 21 | * Promiseインスタンスはbind関数で結合する事ができます。連続した非同期/同期の処理をデータの流れとして抽象化します。 22 | * subscribe/forEach/runなどの処理を実行するまでは、結合した処理が実行される事はありません。 23 | * また、コンストラクタは公開されていません。brook.promiseがファクトリとなっています。 24 | */ 25 | var k = function(next,val){ next(val); }; 26 | var lift = function(f){ return ( f instanceof Promise ) ? f : new Promise( f ); }; 27 | var Promise = function(next,errorHandler){ 28 | this.next = next || k; 29 | if (errorHandler) 30 | this.setErrorHandler(errorHandler); 31 | }; 32 | (function(proto){ 33 | /**#@+ 34 | * @methodOf brook._Promise.prototype 35 | */ 36 | 37 | /** 38 | * @name concat 39 | * @param {Promise} promise 40 | */ 41 | proto.concat = function(after){ 42 | var before = this; 43 | var next = function(n,val){ 44 | before.subscribe(after.ready(n),val); 45 | }; 46 | return new Promise(next); 47 | }; 48 | /** 49 | * @name bind 50 | * @param {Promise} promise 51 | */ 52 | proto.bind = function(){ 53 | var r = this; 54 | for( var i = 0,l = arguments.length;i 4 | */ 5 | 6 | /*global Namespace window document Node*/ 7 | 8 | /** 9 | @name brook.view.htmltemplate 10 | @namespace 簡易なHTMLTemplate実装を提供します。 11 | */ 12 | Namespace('brook.view.htmltemplate') 13 | .use('brook.view.htmltemplate.core *') 14 | .define(function(ns){ 15 | var merge = function(origin,target){ 16 | for(var prop in target ){ 17 | if( !target.hasOwnProperty(prop) ) 18 | continue; 19 | origin[prop] = target[prop]; 20 | } 21 | }; 22 | var meta = { 23 | '\b': '\\b', 24 | '\t': '\\t', 25 | '\n': '\\n', 26 | '\f': '\\f', 27 | '\r': '\\r', 28 | '"' : '\\"', 29 | '\\': '\\\\' 30 | }; 31 | var _map = function(list,mapper){ 32 | var result = []; 33 | for( var i=0,l= list.length;i/g,'>') 48 | .replace(/'/g, ''') 49 | .replace(/"/g, '"'); 50 | }, 51 | __escapeJS:function(str){ 52 | return quote(str); 53 | }, 54 | __escapeURL:function(str){ 55 | return encodeURI(str); 56 | }, 57 | __escapeNONE:function(str){ 58 | return str; 59 | }, 60 | __include : function(name,param,func){ 61 | var tmpl = Klass.getByElementId(name); 62 | if( !tmpl ){ 63 | return; 64 | } 65 | tmpl.param(param); 66 | tmpl.registerFunction(func); 67 | return tmpl.output(); 68 | } 69 | }; 70 | var Klass = function _HTMLTemplate(func){ 71 | this._param = {}; 72 | this._funcs = {};merge(this._funcs,GLOBAL_FUNC); 73 | this._output = func; 74 | }; 75 | merge( Klass.prototype , { 76 | param : function(obj){ 77 | merge( this._param , obj ); 78 | }, 79 | registerFunction :function(name,func){ 80 | this._funcs[name] = func; 81 | }, 82 | output : function(){ 83 | return this._output( this._param , this._funcs ); 84 | } 85 | } ); 86 | var COMMENT_NODE = ( 'Node' in window )? Node.COMMENT_NODE : 8; 87 | var _getSourceFromElement = function(element){ 88 | var children = element.childNodes || [] ; 89 | var result = []; 90 | for ( var i =0,l=children.length;i 4 | */ 5 | 6 | /*global Namespace window HTMLElement document*/ 7 | 8 | /** 9 | @name brook.dom.compat 10 | @namespace details here 11 | */ 12 | Namespace('brook.dom.compat') 13 | .define(function(ns){ 14 | /** 15 | * Returns HTMLElement.dataset by the specified HTMLElement. 16 | * 17 | * NOTE: This function is preceding upgraded to fix 18 | * https://github.com/hirokidaichi/brook/pull/22. 19 | * 20 | * NOTE: You should care an undefined attribute value to keep compatible 21 | * for Android default browser. 22 | * 23 | * In Android default broswer: 24 | * dataset[somethingUnexistentAttr] === ''. 25 | * 26 | * But in other browsers: 27 | * dataset[somethingUnexistentAttr] === undefined. 28 | * 29 | * @name brook.dom.compat.dataset 30 | * @function 31 | */ 32 | var dataset = (function(){ 33 | var camelize = function(string){ 34 | return string.replace(/-+(.)?/g, function(match, chr) { 35 | return chr ? chr.toUpperCase() : ''; 36 | }); 37 | }; 38 | var datasetNative = function(element){ 39 | return element.dataset; 40 | }; 41 | var datasetCompat = function(element){ 42 | var sets = {}; 43 | for(var i=0,a=element.attributes,l=a.length;i li:last-child { 206 | border-radius: 0 0 5px 5px; 207 | -moz-border-radius: 0 0 5px 5px; 208 | -webkit-border-bottom-right-radius: 5px; 209 | -webkit-border-bottom-left-radius: 5px; 210 | } 211 | 212 | #qunit-tests .fail { color: #000000; background-color: #EE5757; } 213 | #qunit-tests .fail .test-name, 214 | #qunit-tests .fail .module-name { color: #000000; } 215 | 216 | #qunit-tests .fail .test-actual { color: #EE5757; } 217 | #qunit-tests .fail .test-expected { color: green; } 218 | 219 | #qunit-banner.qunit-fail { background-color: #EE5757; } 220 | 221 | 222 | /** Result */ 223 | 224 | #qunit-testresult { 225 | padding: 0.5em 0.5em 0.5em 2.5em; 226 | 227 | color: #2b81af; 228 | background-color: #D2E0E6; 229 | 230 | border-bottom: 1px solid white; 231 | } 232 | #qunit-testresult .module-name { 233 | font-weight: bold; 234 | } 235 | 236 | /** Fixture */ 237 | 238 | #qunit-fixture { 239 | position: absolute; 240 | top: -10000px; 241 | left: -10000px; 242 | width: 1000px; 243 | height: 1000px; 244 | } 245 | -------------------------------------------------------------------------------- /src/brook/util.js: -------------------------------------------------------------------------------- 1 | /** 2 | @fileOverview brook.util 3 | @author daichi.hiroki 4 | */ 5 | 6 | /*global Namespace setTimeout console setInterval clearInterval*/ 7 | 8 | /** 9 | @name brook.util 10 | @namespace details here 11 | */ 12 | Namespace('brook.util') 13 | .use('brook promise') 14 | .define(function(ns){ 15 | /**#@+ 16 | * @methodOf brook.util 17 | */ 18 | /** 19 | * @name mapper 20 | * @param {Promise} promise 21 | */ 22 | var mapper = function(f){ 23 | return ns.promise(function(next,val){ 24 | return next(f(val)); 25 | }); 26 | }; 27 | /** 28 | * @name through 29 | * @param {Promise} promise 30 | */ 31 | var through = function(f){ 32 | return ns.promise(function(next,val){ 33 | f(val); 34 | return next(val); 35 | }); 36 | }; 37 | /** 38 | * @name filter 39 | * @param {Promise} promise 40 | */ 41 | var filter = function(f){ 42 | return ns.promise(function(next,val){ 43 | if( f(val) ) return next(val); 44 | }); 45 | }; 46 | /** 47 | * @name takeBy 48 | */ 49 | var takeBy = function(by){ 50 | var num = 1; 51 | var queue = []; 52 | return ns.promise(function(next,val){ 53 | queue.push( val ); 54 | if( num++ % (by) === 0){ 55 | next(queue); 56 | queue = []; 57 | } 58 | }); 59 | }; 60 | var now = Date.now ? function() { return Date.now(); } 61 | : function() { return +new Date(); }; 62 | var _arrayWalk = function(list,func) { 63 | for (var i = 0, l = list.length; i < l; i++) { 64 | func(list[i]); 65 | } 66 | }; 67 | var _arrayWalkWithLimit = function (list, func, limit) { 68 | var index = 0, length = list.length; 69 | (function() { 70 | var startTime = now(); 71 | while (length > index && limit > (now() - startTime)) 72 | func(list[index++]); 73 | 74 | if (length > index) 75 | setTimeout(arguments.callee, 10); 76 | })(); 77 | }; 78 | var _getArrayWalkWithLimit = function(limit) { 79 | return function (list, func) { 80 | _arrayWalkWithLimit(list, func, limit); 81 | }; 82 | }; 83 | /** 84 | * @name scatter 85 | */ 86 | var scatter = function(limit){ 87 | var func = limit ? _getArrayWalkWithLimit(limit) : _arrayWalk; 88 | return ns.promise(function(next,list){ 89 | func(list,next); 90 | }); 91 | }; 92 | /** 93 | * @name wait 94 | */ 95 | var wait = function(msec){ 96 | var msecFunc 97 | = ( typeof msec == 'function' ) ? 98 | msec : function(){return msec;}; 99 | return ns.promise(function(next,val){ 100 | setTimeout(function(){ 101 | next(val); 102 | },msecFunc()); 103 | }); 104 | }; 105 | var waitUntil = function(f){ 106 | var p = function(next,val){ 107 | if( f() ){ 108 | return next(val); 109 | } 110 | setTimeout(function(){ p(next,val);},100); 111 | }; 112 | return ns.promise(p); 113 | }; 114 | var debug = function(sig){ 115 | sig = sig ? sig : "debug"; 116 | return through(function(val) { 117 | console.log(sig + ":",val); 118 | }); 119 | }; 120 | var cond = function(f,promise){ 121 | return ns.promise(function(next,val){ 122 | if( !f(val) ) 123 | return next( val ); 124 | promise.subscribe(function(val){ 125 | return next( val ); 126 | },val); 127 | }); 128 | }; 129 | var match = function(dispatchTable, matcher){ 130 | return ns.promise(function(next,val){ 131 | var promise; 132 | if(matcher) 133 | promise = dispatchTable[matcher(val)]; 134 | if(!promise) 135 | promise = dispatchTable[val] || dispatchTable.__default__ || ns.promise(); 136 | promise.subscribe(function(v){ 137 | next(v); 138 | },val); 139 | }); 140 | }; 141 | var LOCK_MAP = {}; 142 | var unlock = function(name){ 143 | return ns.promise(function(next,val){ 144 | LOCK_MAP[name] = false; 145 | next(val); 146 | }); 147 | }; 148 | var lock = function(name){ 149 | var tryLock = (function(next,val){ 150 | if( !LOCK_MAP[name] ){ 151 | LOCK_MAP[name] = true; 152 | return next(val); 153 | } 154 | setTimeout(function(){ 155 | tryLock(next,val); 156 | },100); 157 | }); 158 | return ns.promise(tryLock); 159 | }; 160 | var from = function(value){ 161 | if( value && value.observe ){ 162 | return ns.promise(function(next,val){ 163 | value.observe(ns.promise(function(n,v){ 164 | next(v); 165 | })); 166 | }); 167 | } 168 | return ns.promise(function(next,val){ 169 | next(value); 170 | }); 171 | }; 172 | var EMIT_INTERVAL_MAP = {}; 173 | var emitInterval = function(msec, name){ 174 | var msecFunc 175 | = ( typeof msec == 'function' ) ? 176 | msec : function(){return msec;}; 177 | 178 | return ns.promise(function(next,val){ 179 | var id = setInterval(function(){ 180 | next(val); 181 | },msecFunc()); 182 | if (name) { 183 | EMIT_INTERVAL_MAP[name] = id; 184 | } 185 | }); 186 | }; 187 | var stopEmitInterval = function(name) { 188 | return ns.promise(function(next, value) { 189 | clearInterval(EMIT_INTERVAL_MAP[name]); 190 | next(value); 191 | }); 192 | }; 193 | /**#@-*/ 194 | ns.provide({ 195 | mapper : mapper, 196 | through : through, 197 | filter : filter, 198 | scatter : scatter, 199 | takeBy : takeBy, 200 | wait : wait, 201 | cond : cond, 202 | match : match, 203 | debug : debug, 204 | lock : lock, 205 | unlock : unlock, 206 | from : from, 207 | waitUntil : waitUntil, 208 | emitInterval: emitInterval, 209 | stopEmitInterval: stopEmitInterval 210 | }); 211 | }); 212 | 213 | 214 | 215 | -------------------------------------------------------------------------------- /t/brook.gateway.unit.js: -------------------------------------------------------------------------------- 1 | /* global Namespace:false, test:false, expect:false, ok:false, equal:false, stop:false, start:false*/ 2 | Namespace() 3 | .use('brook *') 4 | .use('brook.util *') 5 | .use('brook.lambda *') 6 | .use('brook.channel *') 7 | .use('brook.model *') 8 | .apply(function(ns){ 9 | 10 | test('exports',function(){ 11 | var exports = "promise wait mapper debug cond match filter lambda VERSION"; 12 | expect( exports.split(/ /g).length); 13 | exports.split(/ /g).forEach(function(e){ 14 | ok(ns[e],e); 15 | }); 16 | }); 17 | 18 | test('promise',function(){ 19 | expect(3); 20 | var p = ns.promise(function(n,val){ 21 | ok(true,'pass'); 22 | n(val); 23 | }).bind(ns.mapper(ns.lambda("$*$"))); 24 | 25 | p.bind(p).subscribe(function(val){ 26 | ok( val == 10000 ,'val'); 27 | },10); 28 | }); 29 | 30 | test('lambda',function(){ 31 | expect(4); 32 | equal( ns.lambda('$*$')(2),4); 33 | equal( ns.lambda('$*$')(2),4); 34 | equal( ns.lambda('x,y->x*y')(2,3),6); 35 | equal( ns.lambda('x,y->z->x*y*z')(2,3)(2),12); 36 | }); 37 | 38 | test('promise defer',function(){ 39 | expect(3); 40 | stop(); 41 | var p = ns.promise(function(n,val){ 42 | ok(true,'pass'); 43 | n(val); 44 | }).bind(ns.mapper(ns.lambda("$*$"))).bind(ns.wait(100)); 45 | p.bind(p).subscribe(function(val){ 46 | equal( val, 10000 ,'val'); 47 | start(); 48 | },10); 49 | }); 50 | 51 | test('cond',function(){ 52 | expect(6); 53 | var p = ns.promise().bind( 54 | ns.cond(ns.lambda('$ == 10'),ns.promise(function(n,v){ 55 | ok(true); 56 | equal( v,10); 57 | n(v); 58 | })), 59 | ns.cond(ns.lambda('$ % 2 ==0'),ns.promise(function(n,v){ 60 | ok( v%2 === 0); 61 | n(v); 62 | })), 63 | ns.cond(ns.lambda('$ == 11'),ns.promise(function(n,v){ 64 | ok(true); 65 | equal(v,11); 66 | n(v); 67 | })) 68 | ); 69 | ns.scatter().bind(p).run([10,11,12]); 70 | }); 71 | 72 | test('match',function(){ 73 | expect(9); 74 | var p = ns.promise().bind( 75 | ns.match({ 76 | 10 : ns.promise(function(n,v){ equal(v,10);n(v);}), 77 | 11 : ns.promise(function(n,v){ equal(v,11);}), 78 | 12 : ns.promise(function(n,v){ equal(v,12);}) 79 | }).bind(function(n,v){ 80 | equal(v,10); 81 | }) 82 | ); 83 | ns.scatter().bind(p).run([10,11,12]); 84 | 85 | var dispatchTable = { 86 | mode_should_be_foo : ns.promise(function(n,v){ equal(v.mode, 'foo'); n(v);}), 87 | mode_should_be_bar : ns.promise(function(n,v){ equal(v.mode, 'bar'); }), 88 | mode_should_be_baz : ns.promise(function(n,v){ equal(v.mode, 'baz'); }) 89 | }; 90 | var before = ns.promise(function(n,v){ 91 | var ret = { 92 | mode : v, 93 | value : 'before_value' 94 | }; 95 | n(ret); 96 | }); 97 | var matcher = function(v){ 98 | var ret; 99 | switch (v.mode) { 100 | case 'foo' : 101 | ret = 'mode_should_be_foo'; 102 | break; 103 | case 'bar' : 104 | ret = 'mode_should_be_bar'; 105 | break; 106 | case 'baz' : 107 | ret = 'mode_should_be_baz'; 108 | break; 109 | } 110 | return ret; 111 | }; 112 | var after = ns.promise(function(n,v){ 113 | equal(v.mode, 'foo'); 114 | equal(v.value, 'before_value'); 115 | }); 116 | ns.scatter().bind( 117 | before.bind(ns.match(dispatchTable, matcher), after) 118 | ).run(['foo','bar','baz']); 119 | }); 120 | 121 | test('named channel',function(){ 122 | expect(13); 123 | ns.observeChannel('test-channel', 124 | ns.promise(function(n,v){ 125 | ok(n); 126 | equal( v.length , 3); 127 | }).bind(ns.debug()) 128 | ); 129 | ns.observeChannel('test-channel', 130 | ns.promise(function(n,v){ 131 | ok(v); 132 | equal( v.length , 3); 133 | }).bind(ns.debug()) 134 | ); 135 | 136 | var l = ns.scatter() 137 | .bind( ns.mapper( ns.lambda('$*$'))) 138 | .bind( ns.takeBy(3) ) 139 | .bind( ns.sendChannel('test-channel')); 140 | 141 | l.run([1,2,3,4,5,6,7,8,9]); 142 | 143 | var promise = ns.promise(function(n, v) { 144 | equal(v, 'ok'); 145 | }); 146 | 147 | ns.observeChannel('test-channel-2', promise); 148 | ns.observeChannel('test-channel-2', promise); 149 | ns.observeChannel('test-channel-2', promise); 150 | ns.sendChannel('test-channel-2').run('ok'); 151 | 152 | ns.stopObservingChannel('test-channel-2', promise); 153 | ns.sendChannel('test-channel-2').run('ok');//not run 154 | }); 155 | 156 | test('channel',function(){ 157 | expect(13); 158 | var channel = ns.createChannel(); 159 | 160 | channel.observe( 161 | ns.promise(function(n,v){ 162 | ok(n); 163 | equal( v.length , 3); 164 | }).bind(ns.debug()) 165 | ); 166 | channel.observe( 167 | ns.promise(function(n,v){ 168 | ok(v); 169 | equal( v.length , 3); 170 | }).bind(ns.debug()) 171 | ); 172 | 173 | var l = ns.scatter() 174 | .bind( ns.mapper( ns.lambda('$*$'))) 175 | .bind( ns.takeBy(3) ) 176 | .bind( channel.send()); 177 | 178 | l.run([1,2,3,4,5,6,7,8,9]); 179 | 180 | var promise = ns.promise(function(n, v) { 181 | equal(v, 'ok'); 182 | }); 183 | 184 | channel = ns.createChannel(); 185 | channel.observe(promise); 186 | channel.send().run('ok'); 187 | 188 | channel.stopObserving(promise); 189 | channel.send().run('ok');//not run 190 | }); 191 | 192 | test('model',function(){ 193 | expect(4); 194 | stop(); 195 | var network = ns.wait(100).bind(function(n,v){ 196 | n({ result : 'helloworld' ,args : v}); 197 | }); 198 | var view1 = ns.promise(function(n,v){ 199 | equal( v.result , 'helloworld' ); 200 | equal( v.args , 20 ); 201 | }); 202 | var view2 = ns.promise(function(n,v){ 203 | equal( v.result , 'helloworld' ); 204 | equal( v.args , 20 ); 205 | start(); 206 | }); 207 | var model = ns.createModel(); 208 | model.addMethod('create', 209 | ns.mapper(ns.lambda('$*2')).bind( 210 | network 211 | ) 212 | ); 213 | model.method('create').observe(view1); 214 | model.method('create').observe(view2); 215 | 216 | ns.promise().bind(model.notify('create')).run(10); 217 | }); 218 | 219 | test('lock',function(){ 220 | stop(); 221 | var counter = 0; 222 | var inc = function(next,val){ 223 | counter++; 224 | next(val); 225 | }; 226 | var sync = ns.lock('test') 227 | .bind(ns.wait(10)) 228 | .bind(inc,inc,inc) 229 | .bind(ns.wait(10)) 230 | .bind(inc,inc,inc) 231 | .bind(function(n,v){ ok(counter%6===0);n(v);}) 232 | .bind(ns.unlock('test')); 233 | 234 | var randWait = ns.scatter().bind( ns.wait( ns.lambda('Math.random()*400'))); 235 | 236 | randWait.bind(sync).bind(ns.takeBy(6),function(n,v){ 237 | start(); 238 | n(v); 239 | }).run([1,2,3,4,5,6]); 240 | 241 | }); 242 | 243 | 244 | }); 245 | -------------------------------------------------------------------------------- /t/brook.unit.js: -------------------------------------------------------------------------------- 1 | /* global Namespace:false, test:false, expect:false, ok:false, equal:false, stop:false, start:false, console:true */ 2 | Namespace() 3 | .use('brook *') 4 | .use('brook.util *') 5 | .use('brook.lambda *') 6 | .use('brook.channel *') 7 | .use('brook.model *') 8 | .apply(function(ns){ 9 | 10 | test('exports',function(){ 11 | var exports = "promise wait mapper debug cond match filter lambda VERSION".split(/ /g); 12 | 13 | expect( exports.length); 14 | for( var i = 0,l=exports.length;ix*y')(2,3),6); 96 | equal( ns.lambda('x,y->z->x*y*z')(2,3)(2),12); 97 | }); 98 | 99 | test('promise defer',function(){ 100 | expect(3); 101 | stop(); 102 | var p = ns.promise(function(n,val){ 103 | ok(true,'pass'); 104 | n(val); 105 | }).bind(ns.mapper(ns.lambda("$*$"))).bind(ns.wait(100)); 106 | 107 | ns.from(10).bind(p).bind(p).subscribe(function(val){ 108 | equal( val, 10000 ,'val'); 109 | start(); 110 | }); 111 | }); 112 | 113 | test('cond',function(){ 114 | expect(6); 115 | var p = ns.promise().bind( 116 | ns.cond(ns.lambda('$ == 10'),ns.promise(function(n,v){ 117 | ok(true); 118 | equal( v,10); 119 | n(v); 120 | })), 121 | ns.cond(ns.lambda('$ % 2 ==0'),ns.promise(function(n,v){ 122 | ok( v%2 === 0); 123 | n(v); 124 | })), 125 | ns.cond(ns.lambda('$ == 11'),ns.promise(function(n,v){ 126 | ok(true); 127 | equal(v,11); 128 | n(v); 129 | })) 130 | ); 131 | ns.scatter().bind(p).run([10,11,12]); 132 | }); 133 | 134 | test('match',function(){ 135 | expect(9); 136 | var p = ns.promise().bind( 137 | ns.match({ 138 | 10 : ns.promise(function(n,v){ equal(v,10);n(v);}), 139 | 11 : ns.promise(function(n,v){ equal(v,11);}), 140 | 12 : ns.promise(function(n,v){ equal(v,12);}) 141 | }).bind(function(n,v){ 142 | equal(v,10); 143 | }) 144 | ); 145 | ns.scatter().bind(p).run([10,11,12]); 146 | 147 | var dispatchTable = { 148 | mode_should_be_foo : ns.promise(function(n,v){ equal(v.mode, 'foo'); n(v);}), 149 | mode_should_be_bar : ns.promise(function(n,v){ equal(v.mode, 'bar'); }), 150 | mode_should_be_baz : ns.promise(function(n,v){ equal(v.mode, 'baz'); }) 151 | }; 152 | var before = ns.promise(function(n,v){ 153 | var ret = { 154 | mode : v, 155 | value : 'before_value' 156 | }; 157 | n(ret); 158 | }); 159 | var matcher = function(v){ 160 | var ret; 161 | switch (v.mode) { 162 | case 'foo' : 163 | ret = 'mode_should_be_foo'; 164 | break; 165 | case 'bar' : 166 | ret = 'mode_should_be_bar'; 167 | break; 168 | case 'baz' : 169 | ret = 'mode_should_be_baz'; 170 | break; 171 | } 172 | return ret; 173 | }; 174 | var after = ns.promise(function(n,v){ 175 | equal(v.mode, 'foo'); 176 | equal(v.value, 'before_value'); 177 | }); 178 | ns.scatter().bind( 179 | before.bind(ns.match(dispatchTable, matcher), after) 180 | ).run(['foo','bar','baz']); 181 | }); 182 | 183 | test('named channel',function(){ 184 | expect(12); 185 | var test = ns.promise(function(n,v){ 186 | ok(n); 187 | equal( v.length , 3); 188 | }); 189 | 190 | ns.from(ns.channel('test-channel')) 191 | .bind(test).subscribe(); 192 | ns.from(ns.channel('test-channel')) 193 | .bind(test).subscribe(); 194 | var l = ns.scatter() 195 | .bind( ns.mapper( ns.lambda('$*$'))) 196 | .bind( ns.takeBy(3) ) 197 | .bind( ns.sendChannel('test-channel')); 198 | 199 | l.run([1,2,3,4,5,6,7,8,9]); 200 | 201 | }); 202 | 203 | test('channel',function(){ 204 | expect(12); 205 | var channel = ns.channel(); 206 | var test = ns.promise(function(n,v){ 207 | ok(n); 208 | equal( v.length , 3); 209 | }); 210 | ns.from( channel ).bind( test ).subscribe(); 211 | 212 | ns.from( channel ).bind( test ).subscribe(); 213 | 214 | var l = ns.scatter() 215 | .bind( ns.mapper( ns.lambda('$*$'))) 216 | .bind( ns.takeBy(3) ) 217 | .bind( channel.send()); 218 | 219 | l.run([1,2,3,4,5,6,7,8,9]); 220 | }); 221 | 222 | 223 | test('model',function(){ 224 | expect(6); 225 | stop(); 226 | var network = ns.wait(100).bind(function(n,v){ 227 | n({ result : 'helloworld' ,args : v}); 228 | }); 229 | var view1 = ns.promise(function(n,v){ 230 | equal( v.result , 'helloworld' ); 231 | equal( v.args , 20 ); 232 | }); 233 | var view2 = ns.promise(function(n,v){ 234 | equal( v.result , 'helloworld' ); 235 | equal( v.args , 20 ); 236 | start(); 237 | }); 238 | var model = ns.createModel({ 239 | create: ns.mapper(ns.lambda('$*2')).bind(network) 240 | }); 241 | model.addMethod('read', network); 242 | 243 | ns.from( model.method('read') ).bind( view1 ).subscribe(); 244 | model.notify('read').run(20); 245 | 246 | ns.from( model.method('create') ).bind( view1 ).subscribe(); 247 | ns.from( model.method('create') ).bind( view2 ).subscribe(); 248 | model.notify('create').run(10); 249 | }); 250 | 251 | 252 | 253 | test('lock',function(){ 254 | stop(); 255 | var counter = 0; 256 | var inc = function(next,val){ 257 | counter++; 258 | next(val); 259 | }; 260 | var sync = ns.lock('test') 261 | .bind(ns.wait(10)) 262 | .bind(inc,inc,inc) 263 | .bind(ns.wait(10)) 264 | .bind(inc,inc,inc) 265 | .bind(function(n,v){ ok(counter%6===0);n(v);}) 266 | .bind(ns.unlock('test')); 267 | 268 | var randWait = ns.scatter().bind( ns.wait( ns.lambda('Math.random()*400'))); 269 | 270 | randWait.bind(sync).bind(ns.takeBy(6),function(n,v){ 271 | start(); 272 | n(v); 273 | }).run([1,2,3,4,5,6]); 274 | 275 | }); 276 | 277 | }); 278 | -------------------------------------------------------------------------------- /t/brook.view.htmltemplate.unit.js: -------------------------------------------------------------------------------- 1 | /* global HTMLTemplate:false, Namespace:false, test:false, expect:false, ok:false, equal:false, stop:false, start:false */ 2 | 3 | var COMPLEX_TMPL =[ 4 | '

          Test

          ', 5 | '

          ', 6 | '', 7 | '', 8 | 'Case-1

          xx
          ', 9 | '', 10 | '', 11 | 'Case-2
          xx
          ', 12 | '', 13 | '', 14 | 'Case-3
          xx
          ', 15 | '', 16 | 'Other cases
          xx
          ', 17 | '
          ', 18 | '
          ', 19 | '', 20 | '', 21 | '', 22 | '

          ' 23 | ].join('\n'); 24 | 25 | 26 | Namespace('main') 27 | .use('brook.view.htmltemplate HTMLTemplate') 28 | .apply(function(ns){ 29 | 30 | test('var',function(){ 31 | var x = ns.HTMLTemplate.get(''); 32 | x.param({test1:1,test2:2,test3:3}); 33 | equal(x.output(),'123'); 34 | x = ns.HTMLTemplate.get('::::'); 35 | x.param({test1:1,test2:2,test3:3}); 36 | equal(x.output(),':1:2:3:'); 37 | x = ns.HTMLTemplate.get('\n::::\n'); 38 | x.param({test1:1,test2:2,test3:3}); 39 | equal(x.output(),'\n:1:2:3:\n'); 40 | 41 | x = ns.HTMLTemplate.get(':'); 42 | x.param({}); 43 | equal(x.output(),':'); 44 | }); 45 | 46 | test('if',function(){ 47 | var x = ns.HTMLTemplate.get('hogehoge'); 48 | x.param({test:1}); 49 | equal(x.output(),'hogehoge'); 50 | x.param({test:0}); 51 | equal(x.output(),''); 52 | x.param({}); 53 | equal(x.output(),''); 54 | }); 55 | 56 | 57 | test('else',function(){ 58 | var x = ns.HTMLTemplate.get('いいいいああああ'); 59 | x.param({test:1}); 60 | equal(x.output(),'いいいい'); 61 | x.param({test:0}); 62 | equal(x.output(),'ああああ'); 63 | 64 | x = ns.HTMLTemplate.get('いいいいぬぬぬおおお'); 65 | x.param({test:0,test2:true}); 66 | equal(x.output(),'ぬぬぬ'); 67 | x.param({test:0,test2:false}); 68 | equal(x.output(),'おおお'); 69 | }); 70 | 71 | test('unless',function(){ 72 | var x = ns.HTMLTemplate.get('いいいいああああ'); 73 | x.param({test:1}); 74 | equal(x.output(),'ああああ'); 75 | x.param({test:0}); 76 | equal(x.output(),'いいいい'); 77 | 78 | x = ns.HTMLTemplate.get('いいいぬぬぬおおお'); 79 | x.param({test:0,test2:true}); 80 | equal(x.output(),'いいい'); 81 | x.param({test:1,test2:false}); 82 | equal(x.output(),'ぬぬぬ'); 83 | }); 84 | 85 | test('loop',function(){ 86 | var x = ns.HTMLTemplate.get(''); 87 | x.param({test:[1,2,3,4,5]}); 88 | equal(x.output(),'あああああ'); 89 | var y = ns.HTMLTemplate.get(''); 90 | y.param({test1:[1,2,3,4,5]}); 91 | equal(y.output(),''); 92 | 93 | var z = ns.HTMLTemplate.get(''); 94 | z.param({ 95 | level1:[ 96 | {var1:'hello',level2:[{var2:'world'}]}, 97 | {var1:'hello2',level2:[{var2:'world1'},{var2:'world2'}]} 98 | ] 99 | }); 100 | equal(z.output(),'helloworldhello2world1world2'); 101 | }); 102 | 103 | test('default',function(){ 104 | var tmpl=ns.HTMLTemplate.get(''); 105 | equal('a',tmpl.output()); 106 | var tmplB=ns.HTMLTemplate.get(''); 107 | equal('a',tmplB.output()); 108 | }); 109 | 110 | test('escape',function(){ 111 | var tmpl=ns.HTMLTemplate.get(''); 112 | tmpl.param({ 113 | aaa:"
          hoge
          " 114 | }); 115 | equal('<div>hoge</div>',tmpl.output()); 116 | 117 | tmpl=ns.HTMLTemplate.get(''); 118 | tmpl.param({ 119 | aaa:"aaa\n\n" 120 | }); 121 | equal('"aaa\\n\\n"',tmpl.output()); 122 | 123 | tmpl=ns.HTMLTemplate.get(''); 124 | tmpl.param({ 125 | aaa:"http://www.js/ ほげ" 126 | }); 127 | equal('http://www.js/%20%E3%81%BB%E3%81%92',tmpl.output()); 128 | }); 129 | 130 | test('expr',function(){ 131 | var x = ns.HTMLTemplate.get(''); 132 | x.param({ 133 | test:'hogehoge' 134 | }); 135 | x.registerFunction('func',function(t){return t+'::::';}); 136 | equal(x.output(),'hogehoge::::'); 137 | 138 | ns.HTMLTemplate.registerFunction('moremore',function(){ 139 | return 'HELP!'; 140 | }); 141 | 142 | x = ns.HTMLTemplate.get(''); 143 | 144 | equal(x.output(),'HELP!'); 145 | 146 | x = ns.HTMLTemplate.get('i'); 147 | x.registerFunction('func',function(t){return [t,t,t,t,t,t,t,t,t,t];}); 148 | x.param({ 149 | test:10 150 | }); 151 | equal(x.output(),'iiiiiiiiii'); 152 | 153 | var y = ns.HTMLTemplate.get('ilove'); 154 | y.registerFunction('func',function(t){return !t;}); 155 | y.param({ 156 | test:false 157 | }); 158 | equal(y.output(),'i'); 159 | 160 | y.param({ 161 | test:true 162 | }); 163 | equal(y.output(),'love'); 164 | var z = ns.HTMLTemplate.get('love'); 165 | z.param({ 166 | test:true 167 | }); 168 | z.registerFunction('func',function(t){return !t;}); 169 | equal(z.output(),'love'); 170 | }); 171 | 172 | test('ex-expr',function(){ 173 | var test04 = ns.HTMLTemplate.get(''); 174 | test04.param({ 175 | aaa :[ 176 | {bbb:[ 177 | {ccc:[ 178 | {a:'hoge'} 179 | ]} 180 | ]} 181 | ] 182 | }); 183 | equal('hoge',test04.output()); 184 | var test05 = ns.HTMLTemplate.get(''); 185 | test05.param({ 186 | aaa :[ 187 | {bbb:[ 188 | {ccc:[ 189 | {a:'hoge'} 190 | ]} 191 | ]} 192 | ], 193 | a :'huga' 194 | }); 195 | equal('huga',test05.output()); 196 | var test06 = ns.HTMLTemplate.get(''); 197 | test06.param({ 198 | aaa :[ 199 | {bbb:[ 200 | {ccc:[ 201 | {a:'hoge'} 202 | ],a:'piyo'} 203 | ],a:'moga'} 204 | ], 205 | a :'huga' 206 | }); 207 | equal('piyo',test06.output()); 208 | var test07 = ns.HTMLTemplate.get(''); 209 | test07.param({ 210 | aaa :[ 211 | {bbb:[ 212 | {ccc:[ 213 | {a:'hoge'} 214 | ],a:'piyo'} 215 | ],a:'moga'} 216 | ], 217 | a :'huga' 218 | }); 219 | equal('moga',test07.output()); 220 | }); 221 | 222 | test('include',function(){ 223 | var tmpl = ns.HTMLTemplate.getByElementId('test02_tmpl'); 224 | tmpl.param({ 225 | outer:[ 226 | {loop:[ 227 | {test:1}, 228 | {test:2}, 229 | {test:3} 230 | ]}, 231 | {loop:[ 232 | {test:1}, 233 | {test:2}, 234 | {test:3} 235 | ]} 236 | ] 237 | }); 238 | equal('hello123*123*hello',tmpl.output()); 239 | }); 240 | 241 | 242 | }); 243 | -------------------------------------------------------------------------------- /lib/namespace.js: -------------------------------------------------------------------------------- 1 | /* namespace-js Copyright (c) 2010 @hiroki_daichi */ 2 | var Namespace = (function(){ 3 | /* utility */ 4 | var merge = function(aObj,bObj){ 5 | for( var p in bObj ){ 6 | if( bObj.hasOwnProperty( p ) ){ 7 | aObj[p] = bObj[p]; 8 | } 9 | } 10 | return aObj; 11 | }; 12 | var _assertValidFQN = function(fqn){ 13 | if(!(/^[a-z0-9_.]+/).test(fqn)) 14 | throw('invalid namespace'); 15 | }; 16 | 17 | var Proc = (function(){ 18 | /* Namespace Class */ 19 | var Proc = (function(){ 20 | /* constructor*/ 21 | var Klass = function _Private_Class_Of_Proc(){ 22 | this.state = {}; 23 | this._status = 'init'; 24 | this.steps = []; 25 | }; 26 | (function(){ 27 | this.next = function(state){ 28 | if(state) this.enqueue(state); 29 | return this; 30 | }; 31 | this.isRunning = function(){ 32 | return (this._status === 'running'); 33 | }; 34 | this.enqueue = function(state){ 35 | this.steps.push(state); 36 | }; 37 | this.dequeue = function(){ 38 | return this.steps.shift(); 39 | }; 40 | this.call = function(initialState,callback){ 41 | if( this.isRunning() ) { 42 | throw("do not run twice"); 43 | } 44 | this.state = initialState || {}; 45 | this.enqueue(function($c){ 46 | $c(); 47 | if(callback)callback(this); 48 | }); 49 | this._status = 'running'; 50 | this._invoke(); 51 | }; 52 | this._invoke = function(){ 53 | var _self = this; 54 | var step = _self.dequeue(); 55 | if( !step ){ 56 | this._status = 'finished'; 57 | return; 58 | } 59 | if( step.call ) { 60 | return step.call( _self.state,function _cont(state){ 61 | if( state ){ 62 | _self.state = state; 63 | } 64 | _self._invoke(); 65 | }); 66 | } 67 | var finishedProcess = 0; 68 | if( step.length === 0 ){ 69 | _self._invoke(); 70 | } 71 | for(var i =0,l=step.length;i"; 107 | }; 108 | this.merge = function(obj){ 109 | merge(this.stash,obj); 110 | return this; 111 | }; 112 | this.getExport = function(importNames){ 113 | var retStash = {}; 114 | for(var i = 0,l=importNames.length;i uses :" + this.useList.join(','); 216 | }; 217 | this.apply = function(callback){ 218 | var nsDef = this; 219 | var nsObj = this.namespaceObject; 220 | Proc(this.requires).next(this.defineFunc).call(this,function(){ 221 | callback( nsDef.getStash() ); 222 | }); 223 | }; 224 | }).apply(Klass.prototype); 225 | return Klass; 226 | })(); 227 | 228 | 229 | var namespaceFactory = function(nsString){ 230 | return new NamespaceDefinition(NamespaceObject.create(nsString || 'main')); 231 | }; 232 | namespaceFactory.Object = NamespaceObject; 233 | namespaceFactory.Definition = NamespaceDefinition; 234 | namespaceFactory.Proc = Proc; 235 | 236 | namespaceFactory('namespace').define(function(ns){ 237 | ns.provide({ 238 | Proc : Proc 239 | }); 240 | }); 241 | return namespaceFactory; 242 | })(); 243 | 244 | 245 | Namespace.use = function(useSyntax){ return Namespace().use(useSyntax); } 246 | Namespace.fromInternal = (function(){ 247 | var get = (function(){ 248 | var createRequester = function() { 249 | var xhr; 250 | try { xhr = new XMLHttpRequest() } catch(e) { 251 | try { xhr = new ActiveXObject("Msxml2.XMLHTTP.6.0") } catch(e) { 252 | try { xhr = new ActiveXObject("Msxml2.XMLHTTP.3.0") } catch(e) { 253 | try { xhr = new ActiveXObject("Msxml2.XMLHTTP") } catch(e) { 254 | try { xhr = new ActiveXObject("Microsoft.XMLHTTP") } catch(e) { 255 | throw new Error( "This browser does not support XMLHttpRequest." ) 256 | } 257 | } 258 | } 259 | } 260 | } 261 | return xhr; 262 | }; 263 | var isSuccessStatus = function(status) { 264 | return (status >= 200 && status < 300) || 265 | status == 304 || 266 | status == 1223 || 267 | (!status && (location.protocol == "file:" || location.protocol == "chrome:") ); 268 | }; 269 | 270 | return function(url,callback){ 271 | var xhr = createRequester(); 272 | xhr.open('GET',url,true); 273 | xhr.onreadystatechange = function(){ 274 | if(xhr.readyState === 4){ 275 | if( isSuccessStatus( xhr.status || 0 )){ 276 | callback(true,xhr.responseText); 277 | }else{ 278 | callback(false); 279 | } 280 | } 281 | }; 282 | xhr.send('') 283 | }; 284 | })(); 285 | 286 | return function(url,isManualProvide){ 287 | return function(ns){ 288 | get(url,function(isSuccess,responseText){ 289 | if( isSuccess ){ 290 | if( isManualProvide ) 291 | return eval(responseText); 292 | else 293 | return ns.provide( eval( responseText ) ); 294 | }else{ 295 | var pub = {}; 296 | pub[url] = 'loading error'; 297 | ns.provide(pub); 298 | } 299 | }); 300 | }; 301 | }; 302 | })(); 303 | 304 | Namespace.GET = Namespace.fromInternal; 305 | Namespace.fromExternal = (function(){ 306 | var callbacks = {}; 307 | var createScriptElement = function(url,callback){ 308 | var scriptElement = document.createElement('script'); 309 | 310 | scriptElement.loaded = false; 311 | 312 | scriptElement.onload = function(){ 313 | this.loaded = true; 314 | callback(); 315 | }; 316 | scriptElement.onreadystatechange = function(){ 317 | if( !/^(loaded|complete)$/.test( this.readyState )) return; 318 | if( this.loaded ) return; 319 | scriptElement.loaded = true; 320 | callback(); 321 | }; 322 | scriptElement.src = url; 323 | document.body.appendChild( scriptElement ); 324 | return scriptElement.src; 325 | }; 326 | var domSrc = function(url){ 327 | return function(ns){ 328 | var src = createScriptElement(url,function(){ 329 | var name = ns.CURRENT_NAMESPACE; 330 | var cb = callbacks[name]; 331 | delete callbacks[name]; 332 | cb( ns ); 333 | }); 334 | } 335 | }; 336 | domSrc.registerCallback = function(namespace,callback) { 337 | callbacks[namespace] = callback; 338 | }; 339 | return domSrc; 340 | })(); 341 | 342 | try{ 343 | if( module ){ 344 | module.exports = Namespace; 345 | } 346 | }catch(e){} 347 | -------------------------------------------------------------------------------- /lib/html-template-core.js: -------------------------------------------------------------------------------- 1 | // 2 | /* 2008 Daichi Hiroki 3 | * html-template-core.js is freely distributable under the terms of MIT-style license. 4 | * ( latest infomation :https://github.com/hirokidaichi/html-template ) 5 | *-----------------------------------------------------------------------*/ 6 | /*global module*/ 7 | var util = {}; 8 | util.defineClass = function(obj,superClass){ 9 | var klass = function Klass(){ 10 | this.initialize.apply(this,arguments); 11 | }; 12 | 13 | if(superClass) klass.prototype = new superClass(); 14 | for(var prop in obj ){ 15 | if( !obj.hasOwnProperty(prop) ) 16 | continue; 17 | klass.prototype[prop] = obj[prop]; 18 | } 19 | if( !klass.prototype.initialize ) 20 | klass.prototype.initalize = function(){}; 21 | return klass; 22 | }; 23 | util.merge = function(origin,target){ 24 | for(var prop in target ){ 25 | if( !target.hasOwnProperty(prop) ) 26 | continue; 27 | origin[prop] = target[prop]; 28 | } 29 | }; 30 | util.k = function(k){return k;}; 31 | util.emptyFunction = function(){}; 32 | util.listToArray = function(list){ 33 | return Array.prototype.slice.call(list); 34 | }; 35 | util.curry = function() { 36 | var args = util.listToArray(arguments); 37 | var f = args.shift(); 38 | return function() { 39 | return f.apply(this, args.concat(util.listToArray(arguments))); 40 | }; 41 | }; 42 | 43 | util.merge(util,{ 44 | isArray: function(object) { 45 | return object !== null && typeof object == "object" && 46 | 'splice' in object && 'join' in object; 47 | }, 48 | isFunction: function(object) { 49 | return typeof object == "function"; 50 | }, 51 | isString: function(object) { 52 | return typeof object == "string"; 53 | }, 54 | isNumber: function(object) { 55 | return typeof object == "number"; 56 | }, 57 | isUndefined: function(object) { 58 | return typeof object == "undefined"; 59 | } 60 | }); 61 | util.createRegexMatcher = function(escapeChar,expArray){ 62 | function _escape( regText){ 63 | return (regText + '').replace(new RegExp(escapeChar,'g'), "\\"); 64 | } 65 | var count = 0; 66 | var e; 67 | var regValues = { mapping : { 'fullText' : [0]},text:[]}; 68 | for( var i =0,l= expArray.length;i]*)'|",{map:'default'}, 120 | '"([^">]*)"|',{map:'default'}, 121 | "([^%s=>]*)" ,{map:'default'}, 122 | ")", 123 | ")?", 124 | "%s*", 125 | "(?:", 126 | "(?:ESCAPE)=", 127 | "(?:", 128 | "(JS|URL|HTML|0|1|NONE)",{map:'escape'}, 129 | ")", 130 | ")?", 131 | "%s*", 132 | "(?:", 133 | "(?:DEFAULT)=", 134 | "(?:", 135 | "'([^'>]*)'|",{map:'default'}, 136 | '"([^">]*)"|',{map:'default'}, 137 | "([^%s=>]*)" ,{map:'default'}, 138 | ")", 139 | ")?", 140 | "%s*", 141 | /* 142 | NAME or EXPR 143 | */ 144 | "(?:", 145 | "(NAME|EXPR)=",{map:'attribute_name'}, 146 | "(?:", 147 | "'([^'>]*)'|",{map:'attribute_value'}, 148 | '"([^">]*)"|',{map:'attribute_value'}, 149 | "([^%s=>]*)" ,{map:'attribute_value'}, 150 | ")", 151 | ")?", 152 | /* 153 | DEFAULT or ESCAPE 154 | */ 155 | '%s*', 156 | "(?:", 157 | "(?:DEFAULT)=", 158 | "(?:", 159 | "'([^'>]*)'|",{map:'default'}, 160 | '"([^">]*)"|',{map:'default'}, 161 | "([^%s=>]*)" ,{map:'default'}, 162 | ")", 163 | ")?", 164 | "%s*", 165 | "(?:", 166 | "(?:ESCAPE)=", 167 | "(?:", 168 | "(JS|URL|HTML|0|1|NONE)",{map:'escape'}, 169 | ")", 170 | ")?", 171 | "%s*", 172 | "(?:", 173 | "(?:DEFAULT)=", 174 | "(?:", 175 | "'([^'>]*)'|",{map:'default'}, 176 | '"([^">]*)"|',{map:'default'}, 177 | "([^%s=>]*)" ,{map:'default'}, 178 | ")", 179 | ")?", 180 | "%s*", 181 | ">" 182 | ]); 183 | 184 | var element = {}; 185 | element.Base = util.defineClass({ 186 | initialize: function(option) { 187 | this.mergeOption(option); 188 | }, 189 | mergeOption : function(option){ 190 | util.merge(this,option); 191 | this.isCloseTag = (this.isCloseTag) ? true: false; 192 | }, 193 | isParent : util.emptyFunction, 194 | execute : util.emptyFunction, 195 | getCode: function(e) { 196 | return "void(0);"; 197 | }, 198 | toString: function() { 199 | return [ 200 | '<' , 201 | ((this.isCloseTag) ? '/': '') , 202 | this.type , 203 | ((this.hasName) ? ' NAME=': '') , 204 | ((this.name) ? this.name: '') , 205 | '>' 206 | ].join(''); 207 | }, 208 | // HTML::Template::Pro shigeki morimoto's extension 209 | _pathLike: function(attribute , matched){ 210 | var pos = (matched == '/')?'0':'$_C.length -'+(matched.split('..').length-1); 211 | return [ 212 | "(($_C["+pos+"]['" , 213 | attribute , 214 | "']) ? $_C["+pos+"]['" , 215 | attribute , 216 | "'] : undefined )" 217 | ].join(''); 218 | 219 | }, 220 | getParam: function() { 221 | var ret = ""; 222 | if (this.attributes.name) { 223 | var matched = this.attributes.name.match(/^(\/|(?:\.\.\/)+)(\w+)/); 224 | if(matched){ 225 | return this._pathLike(matched[2],matched[1]); 226 | } 227 | var _default = ( this.attributes['default'] )? "'"+this.attributes['default']+"'":"undefined"; 228 | ret = [ 229 | "(($_T['" , 230 | this.attributes.name , 231 | "']) ? $_T['" , 232 | this.attributes.name , 233 | "'] : ", 234 | _default, 235 | " )" 236 | ].join(''); 237 | } 238 | if (this.attributes.expr) { 239 | var operators = { 240 | 'gt' :'>', 241 | 'lt' :'<', 242 | 'eq' :'==', 243 | 'ne' :'!=', 244 | 'ge' :'>=', 245 | 'le' :'<=' 246 | }; 247 | var replaced = this.attributes.expr.replace(/\{(\/|(?:\.\.\/)+)(\w+)\}/g,function(full,matched,param){ 248 | return [ 249 | '$_C[', 250 | (matched == '/')?'0':'$_C.length -'+(matched.split('..').length-1), 251 | ']["',param,'"]' 252 | ].join(''); 253 | }).replace(/\s+(gt|lt|eq|ne|ge|le|cmp)\s+/g,function(full,match){ 254 | return " "+operators[match]+" "; 255 | }); 256 | ret = [ 257 | "(function(){", 258 | " with($_F){", 259 | " with($_T){", 260 | " return (", replaced ,');', 261 | "}}})()" 262 | ].join(''); 263 | } 264 | if(this.attributes.escape){ 265 | var _escape = { 266 | NONE: 'NONE', 267 | 0 : 'NONE', 268 | 1 : 'HTML', 269 | HTML: 'HTML', 270 | JS : 'JS', 271 | URL : 'URL' 272 | }[this.attributes.escape]; 273 | ret = [ 274 | '$_F.__escape'+_escape+'(', 275 | ret, 276 | ')' 277 | ].join(''); 278 | } 279 | return ret; 280 | } 281 | }); 282 | 283 | var cache = { 284 | STRING_FRAGMENT : [] 285 | }; 286 | 287 | 288 | util.merge( element , { 289 | ROOTElement: util.defineClass({ 290 | type: 'root', 291 | getCode: function() { 292 | if (this.isCloseTag) { 293 | return 'return $_R.join("");'; 294 | } else { 295 | return [ 296 | 'var $_R = [];', 297 | 'var $_C = [param];', 298 | 'var $_F = funcs||{};', 299 | 'var $_T = param||{};', 300 | 'var $_S = cache.STRING_FRAGMENT;' 301 | ].join(''); 302 | } 303 | } 304 | },element.Base), 305 | 306 | LOOPElement: util.defineClass({ 307 | type: 'loop', 308 | initialize:function(option){ 309 | this.mergeOption(option); 310 | }, 311 | getLoopId : function(){ 312 | if( this._ID ) { 313 | return this._ID; 314 | } 315 | if( !element.LOOPElement.instanceId ){ 316 | element.LOOPElement.instanceId = 0; 317 | } 318 | var id = element.LOOPElement.instanceId++; 319 | this._ID = '$'+id.toString(16); 320 | return this._ID; 321 | }, 322 | getCode: function() { 323 | if (this.isCloseTag) { 324 | return ['}','$_T = $_C.pop();'].join(''); 325 | } else { 326 | var id = this.getLoopId(); 327 | return [ 328 | 'var $_L_'+id+' =' + this.getParam() + '|| [];', 329 | 'var $_LL_'+id+' = $_L_'+id+'.length;', 330 | '$_C.push($_T);', 331 | 'for(var i_'+id+'=0;i_'+id+'<$_LL_'+id+';i_'+id+'++){', 332 | ' $_T = (typeof $_L_'+id+'[i_'+id+'] == "object")?', 333 | ' $_L_'+id+'[i_'+id+'] : {};', 334 | "$_T['__first__'] = (i_"+id+" == 0) ? true: false;", 335 | "$_T['__counter__'] = i_"+id+"+1;", 336 | "$_T['__odd__'] = ((i_"+id+"+1)% 2) ? true: false;", 337 | "$_T['__last__'] = (i_"+id+" == ($_LL_"+id+" - 1)) ? true: false;", 338 | "$_T['__inner__'] = ($_T['__first__']||$_T['__last__'])?false:true;" 339 | ].join(''); 340 | } 341 | } 342 | },element.Base), 343 | 344 | VARElement: util.defineClass({ 345 | type: 'var', 346 | getCode: function() { 347 | if (this.isCloseTag) { 348 | throw(new Error('HTML.Template ParseError TMPL_VAR')); 349 | } else { 350 | return '$_R.push(' + this.getParam() + ');'; 351 | } 352 | } 353 | },element.Base), 354 | 355 | IFElement: util.defineClass({ 356 | type: 'if', 357 | getCondition: function(param) { 358 | return "!!" + this.getParam(param); 359 | }, 360 | getCode: function() { 361 | if (this.isCloseTag) { 362 | return '}'; 363 | } else { 364 | return 'if(' + this.getCondition() + '){'; 365 | } 366 | } 367 | },element.Base), 368 | 369 | ELSEElement: util.defineClass( { 370 | type: 'else', 371 | getCode: function() { 372 | if (this.isCloseTag) { 373 | throw(new Error('HTML.Template ParseError No Close Tag for TMPL_ELSE')); 374 | } else { 375 | return '}else{'; 376 | } 377 | } 378 | },element.Base), 379 | 380 | INCLUDEElement: util.defineClass({ 381 | type: 'include', 382 | getCode: function() { 383 | if (this.isCloseTag) { 384 | throw(new Error('HTML.Template ParseError No Close Tag for TMPL_INCLUDE')); 385 | } else { 386 | var name = '"'+(this.attributes.name)+'"'; 387 | return [ 388 | '$_R.push($_F.__include(',name,',$_T,$_F));' 389 | ].join('\n'); 390 | } 391 | } 392 | },element.Base), 393 | 394 | TEXTElement: util.defineClass({ 395 | type: 'text', 396 | isCloseTag: false, 397 | initialize : function(option){this.value = option;}, 398 | getCode: function() { 399 | if (this.isCloseTag) { 400 | throw(new Error('HTML.Template ParseError No Close Tag for TEXT')); 401 | } else { 402 | cache.STRING_FRAGMENT.push(this.value); 403 | return '$_R.push($_S['+(cache.STRING_FRAGMENT.length-1)+']);'; 404 | } 405 | } 406 | },element.Base) 407 | }); 408 | 409 | element.ELSIFElement = util.defineClass({ 410 | type: 'elsif', 411 | getCode: function() { 412 | if (this.isCloseTag) { 413 | throw(new Error('HTML.Template ParseError No Close Tag for TMPL_ELSIF')); 414 | } else { 415 | return '}else if(' + this.getCondition() + '){'; 416 | } 417 | } 418 | },element.IFElement); 419 | 420 | element.UNLESSElement = util.defineClass({ 421 | type: 'unless', 422 | getCondition: function(param) { 423 | return "!" + this.getParam(param); 424 | } 425 | },element.IFElement); 426 | 427 | 428 | element.createElement = function(type, option) { 429 | return new element[type + 'Element'](option); 430 | }; 431 | 432 | var parseHTMLTemplate = function(source) { 433 | var chunks = []; 434 | var createElement = element.createElement; 435 | var root = createElement('ROOT', { 436 | isCloseTag: false 437 | }); 438 | var matcher = CHUNK_REGEXP_ATTRIBUTE; 439 | chunks.push(root); 440 | 441 | while (source.length > 0) { 442 | var results = matcher(source); 443 | if (!results) { 444 | chunks.push(createElement('TEXT', source)); 445 | source = ''; 446 | break; 447 | } 448 | var index = 0; 449 | var fullText = results.fullText; 450 | if ((index = source.indexOf(fullText)) > 0) { 451 | var text = source.slice(0, index); 452 | chunks.push(createElement('TEXT', text)); 453 | source = source.slice(index); 454 | } 455 | var attr,name,value; 456 | if ( results.attribute_name ) { 457 | name = results.attribute_name.toLowerCase(); 458 | value = results.attribute_value; 459 | attr = {}; 460 | attr[name] = value; 461 | attr['default'] = results['default']; 462 | attr.escape = results.escape; 463 | } else { 464 | attr = undefined; 465 | } 466 | chunks.push(createElement(results.tag_name, { 467 | 'attributes': attr, 468 | 'isCloseTag' : results.close, 469 | 'parent' : this 470 | })); 471 | source = source.slice(fullText.length); 472 | } 473 | chunks.push(createElement('ROOT', { 474 | isCloseTag: true 475 | })); 476 | return chunks; 477 | }; 478 | 479 | module.exports.getFunctionText = function(chunksOrSource){ 480 | var chunks = util.isString(chunksOrSource) ? parseHTMLTemplate( chunksOrSource ) : chunksOrSource; 481 | var codes = []; 482 | for(var i=0,l=chunks.length;i(" + bad + ", " + good + ", " + this.assertions.length + ")"; 279 | 280 | addEvent(b, "click", function() { 281 | var next = b.parentNode.lastChild, 282 | collapsed = hasClass( next, "qunit-collapsed" ); 283 | ( collapsed ? removeClass : addClass )( next, "qunit-collapsed" ); 284 | }); 285 | 286 | addEvent(b, "dblclick", function( e ) { 287 | var target = e && e.target ? e.target : window.event.srcElement; 288 | if ( target.nodeName.toLowerCase() === "span" || target.nodeName.toLowerCase() === "b" ) { 289 | target = target.parentNode; 290 | } 291 | if ( window.location && target.nodeName.toLowerCase() === "strong" ) { 292 | window.location = QUnit.url({ testNumber: test.testNumber }); 293 | } 294 | }); 295 | 296 | // `time` initialized at top of scope 297 | time = document.createElement( "span" ); 298 | time.className = "runtime"; 299 | time.innerHTML = this.runtime + " ms"; 300 | 301 | // `li` initialized at top of scope 302 | li = id( this.id ); 303 | li.className = bad ? "fail" : "pass"; 304 | li.removeChild( li.firstChild ); 305 | a = li.firstChild; 306 | li.appendChild( b ); 307 | li.appendChild( a ); 308 | li.appendChild( time ); 309 | li.appendChild( ol ); 310 | 311 | } else { 312 | for ( i = 0; i < this.assertions.length; i++ ) { 313 | if ( !this.assertions[i].result ) { 314 | bad++; 315 | config.stats.bad++; 316 | config.moduleStats.bad++; 317 | } 318 | } 319 | } 320 | 321 | runLoggingCallbacks( "testDone", QUnit, { 322 | name: this.testName, 323 | module: this.module, 324 | failed: bad, 325 | passed: this.assertions.length - bad, 326 | total: this.assertions.length, 327 | duration: this.runtime 328 | }); 329 | 330 | QUnit.reset(); 331 | 332 | config.current = undefined; 333 | }, 334 | 335 | queue: function() { 336 | var bad, 337 | test = this; 338 | 339 | synchronize(function() { 340 | test.init(); 341 | }); 342 | function run() { 343 | // each of these can by async 344 | synchronize(function() { 345 | test.setup(); 346 | }); 347 | synchronize(function() { 348 | test.run(); 349 | }); 350 | synchronize(function() { 351 | test.teardown(); 352 | }); 353 | synchronize(function() { 354 | test.finish(); 355 | }); 356 | } 357 | 358 | // `bad` initialized at top of scope 359 | // defer when previous test run passed, if storage is available 360 | bad = QUnit.config.reorder && defined.sessionStorage && 361 | +sessionStorage.getItem( "qunit-test-" + this.module + "-" + this.testName ); 362 | 363 | if ( bad ) { 364 | run(); 365 | } else { 366 | synchronize( run, true ); 367 | } 368 | } 369 | }; 370 | 371 | // Root QUnit object. 372 | // `QUnit` initialized at top of scope 373 | QUnit = { 374 | 375 | // call on start of module test to prepend name to all tests 376 | module: function( name, testEnvironment ) { 377 | config.currentModule = name; 378 | config.currentModuleTestEnvironment = testEnvironment; 379 | config.modules[name] = true; 380 | }, 381 | 382 | asyncTest: function( testName, expected, callback ) { 383 | if ( arguments.length === 2 ) { 384 | callback = expected; 385 | expected = null; 386 | } 387 | 388 | QUnit.test( testName, expected, callback, true ); 389 | }, 390 | 391 | test: function( testName, expected, callback, async ) { 392 | var test, 393 | nameHtml = "" + escapeText( testName ) + ""; 394 | 395 | if ( arguments.length === 2 ) { 396 | callback = expected; 397 | expected = null; 398 | } 399 | 400 | if ( config.currentModule ) { 401 | nameHtml = "" + escapeText( config.currentModule ) + ": " + nameHtml; 402 | } 403 | 404 | test = new Test({ 405 | nameHtml: nameHtml, 406 | testName: testName, 407 | expected: expected, 408 | async: async, 409 | callback: callback, 410 | module: config.currentModule, 411 | moduleTestEnvironment: config.currentModuleTestEnvironment, 412 | stack: sourceFromStacktrace( 2 ) 413 | }); 414 | 415 | if ( !validTest( test ) ) { 416 | return; 417 | } 418 | 419 | test.queue(); 420 | }, 421 | 422 | // Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. 423 | expect: function( asserts ) { 424 | if (arguments.length === 1) { 425 | config.current.expected = asserts; 426 | } else { 427 | return config.current.expected; 428 | } 429 | }, 430 | 431 | start: function( count ) { 432 | // QUnit hasn't been initialized yet. 433 | // Note: RequireJS (et al) may delay onLoad 434 | if ( config.semaphore === undefined ) { 435 | QUnit.begin(function() { 436 | // This is triggered at the top of QUnit.load, push start() to the event loop, to allow QUnit.load to finish first 437 | setTimeout(function() { 438 | QUnit.start( count ); 439 | }); 440 | }); 441 | return; 442 | } 443 | 444 | config.semaphore -= count || 1; 445 | // don't start until equal number of stop-calls 446 | if ( config.semaphore > 0 ) { 447 | return; 448 | } 449 | // ignore if start is called more often then stop 450 | if ( config.semaphore < 0 ) { 451 | config.semaphore = 0; 452 | QUnit.pushFailure( "Called start() while already started (QUnit.config.semaphore was 0 already)", null, sourceFromStacktrace(2) ); 453 | return; 454 | } 455 | // A slight delay, to avoid any current callbacks 456 | if ( defined.setTimeout ) { 457 | window.setTimeout(function() { 458 | if ( config.semaphore > 0 ) { 459 | return; 460 | } 461 | if ( config.timeout ) { 462 | clearTimeout( config.timeout ); 463 | } 464 | 465 | config.blocking = false; 466 | process( true ); 467 | }, 13); 468 | } else { 469 | config.blocking = false; 470 | process( true ); 471 | } 472 | }, 473 | 474 | stop: function( count ) { 475 | config.semaphore += count || 1; 476 | config.blocking = true; 477 | 478 | if ( config.testTimeout && defined.setTimeout ) { 479 | clearTimeout( config.timeout ); 480 | config.timeout = window.setTimeout(function() { 481 | QUnit.ok( false, "Test timed out" ); 482 | config.semaphore = 1; 483 | QUnit.start(); 484 | }, config.testTimeout ); 485 | } 486 | } 487 | }; 488 | 489 | // `assert` initialized at top of scope 490 | // Asssert helpers 491 | // All of these must either call QUnit.push() or manually do: 492 | // - runLoggingCallbacks( "log", .. ); 493 | // - config.current.assertions.push({ .. }); 494 | // We attach it to the QUnit object *after* we expose the public API, 495 | // otherwise `assert` will become a global variable in browsers (#341). 496 | assert = { 497 | /** 498 | * Asserts rough true-ish result. 499 | * @name ok 500 | * @function 501 | * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); 502 | */ 503 | ok: function( result, msg ) { 504 | if ( !config.current ) { 505 | throw new Error( "ok() assertion outside test context, was " + sourceFromStacktrace(2) ); 506 | } 507 | result = !!result; 508 | 509 | var source, 510 | details = { 511 | module: config.current.module, 512 | name: config.current.testName, 513 | result: result, 514 | message: msg 515 | }; 516 | 517 | msg = escapeText( msg || (result ? "okay" : "failed" ) ); 518 | msg = "" + msg + ""; 519 | 520 | if ( !result ) { 521 | source = sourceFromStacktrace( 2 ); 522 | if ( source ) { 523 | details.source = source; 524 | msg += "
          Source:
          " + escapeText( source ) + "
          "; 525 | } 526 | } 527 | runLoggingCallbacks( "log", QUnit, details ); 528 | config.current.assertions.push({ 529 | result: result, 530 | message: msg 531 | }); 532 | }, 533 | 534 | /** 535 | * Assert that the first two arguments are equal, with an optional message. 536 | * Prints out both actual and expected values. 537 | * @name equal 538 | * @function 539 | * @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes.", "format() replaces {0} with next argument" ); 540 | */ 541 | equal: function( actual, expected, message ) { 542 | /*jshint eqeqeq:false */ 543 | QUnit.push( expected == actual, actual, expected, message ); 544 | }, 545 | 546 | /** 547 | * @name notEqual 548 | * @function 549 | */ 550 | notEqual: function( actual, expected, message ) { 551 | /*jshint eqeqeq:false */ 552 | QUnit.push( expected != actual, actual, expected, message ); 553 | }, 554 | 555 | /** 556 | * @name propEqual 557 | * @function 558 | */ 559 | propEqual: function( actual, expected, message ) { 560 | actual = objectValues(actual); 561 | expected = objectValues(expected); 562 | QUnit.push( QUnit.equiv(actual, expected), actual, expected, message ); 563 | }, 564 | 565 | /** 566 | * @name notPropEqual 567 | * @function 568 | */ 569 | notPropEqual: function( actual, expected, message ) { 570 | actual = objectValues(actual); 571 | expected = objectValues(expected); 572 | QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message ); 573 | }, 574 | 575 | /** 576 | * @name deepEqual 577 | * @function 578 | */ 579 | deepEqual: function( actual, expected, message ) { 580 | QUnit.push( QUnit.equiv(actual, expected), actual, expected, message ); 581 | }, 582 | 583 | /** 584 | * @name notDeepEqual 585 | * @function 586 | */ 587 | notDeepEqual: function( actual, expected, message ) { 588 | QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message ); 589 | }, 590 | 591 | /** 592 | * @name strictEqual 593 | * @function 594 | */ 595 | strictEqual: function( actual, expected, message ) { 596 | QUnit.push( expected === actual, actual, expected, message ); 597 | }, 598 | 599 | /** 600 | * @name notStrictEqual 601 | * @function 602 | */ 603 | notStrictEqual: function( actual, expected, message ) { 604 | QUnit.push( expected !== actual, actual, expected, message ); 605 | }, 606 | 607 | "throws": function( block, expected, message ) { 608 | var actual, 609 | expectedOutput = expected, 610 | ok = false; 611 | 612 | // 'expected' is optional 613 | if ( typeof expected === "string" ) { 614 | message = expected; 615 | expected = null; 616 | } 617 | 618 | config.current.ignoreGlobalErrors = true; 619 | try { 620 | block.call( config.current.testEnvironment ); 621 | } catch (e) { 622 | actual = e; 623 | } 624 | config.current.ignoreGlobalErrors = false; 625 | 626 | if ( actual ) { 627 | // we don't want to validate thrown error 628 | if ( !expected ) { 629 | ok = true; 630 | expectedOutput = null; 631 | // expected is a regexp 632 | } else if ( QUnit.objectType( expected ) === "regexp" ) { 633 | ok = expected.test( errorString( actual ) ); 634 | // expected is a constructor 635 | } else if ( actual instanceof expected ) { 636 | ok = true; 637 | // expected is a validation function which returns true is validation passed 638 | } else if ( expected.call( {}, actual ) === true ) { 639 | expectedOutput = null; 640 | ok = true; 641 | } 642 | 643 | QUnit.push( ok, actual, expectedOutput, message ); 644 | } else { 645 | QUnit.pushFailure( message, null, 'No exception was thrown.' ); 646 | } 647 | } 648 | }; 649 | 650 | /** 651 | * @deprecate since 1.8.0 652 | * Kept assertion helpers in root for backwards compatibility. 653 | */ 654 | extend( QUnit, assert ); 655 | 656 | /** 657 | * @deprecated since 1.9.0 658 | * Kept root "raises()" for backwards compatibility. 659 | * (Note that we don't introduce assert.raises). 660 | */ 661 | QUnit.raises = assert[ "throws" ]; 662 | 663 | /** 664 | * @deprecated since 1.0.0, replaced with error pushes since 1.3.0 665 | * Kept to avoid TypeErrors for undefined methods. 666 | */ 667 | QUnit.equals = function() { 668 | QUnit.push( false, false, false, "QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead" ); 669 | }; 670 | QUnit.same = function() { 671 | QUnit.push( false, false, false, "QUnit.same has been deprecated since 2009 (e88049a0), use QUnit.deepEqual instead" ); 672 | }; 673 | 674 | // We want access to the constructor's prototype 675 | (function() { 676 | function F() {} 677 | F.prototype = QUnit; 678 | QUnit = new F(); 679 | // Make F QUnit's constructor so that we can add to the prototype later 680 | QUnit.constructor = F; 681 | }()); 682 | 683 | /** 684 | * Config object: Maintain internal state 685 | * Later exposed as QUnit.config 686 | * `config` initialized at top of scope 687 | */ 688 | config = { 689 | // The queue of tests to run 690 | queue: [], 691 | 692 | // block until document ready 693 | blocking: true, 694 | 695 | // when enabled, show only failing tests 696 | // gets persisted through sessionStorage and can be changed in UI via checkbox 697 | hidepassed: false, 698 | 699 | // by default, run previously failed tests first 700 | // very useful in combination with "Hide passed tests" checked 701 | reorder: true, 702 | 703 | // by default, modify document.title when suite is done 704 | altertitle: true, 705 | 706 | // when enabled, all tests must call expect() 707 | requireExpects: false, 708 | 709 | // add checkboxes that are persisted in the query-string 710 | // when enabled, the id is set to `true` as a `QUnit.config` property 711 | urlConfig: [ 712 | { 713 | id: "noglobals", 714 | label: "Check for Globals", 715 | tooltip: "Enabling this will test if any test introduces new properties on the `window` object. Stored as query-strings." 716 | }, 717 | { 718 | id: "notrycatch", 719 | label: "No try-catch", 720 | tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging exceptions in IE reasonable. Stored as query-strings." 721 | } 722 | ], 723 | 724 | // Set of all modules. 725 | modules: {}, 726 | 727 | // logging callback queues 728 | begin: [], 729 | done: [], 730 | log: [], 731 | testStart: [], 732 | testDone: [], 733 | moduleStart: [], 734 | moduleDone: [] 735 | }; 736 | 737 | // Export global variables, unless an 'exports' object exists, 738 | // in that case we assume we're in CommonJS (dealt with on the bottom of the script) 739 | if ( typeof exports === "undefined" ) { 740 | extend( window, QUnit ); 741 | 742 | // Expose QUnit object 743 | window.QUnit = QUnit; 744 | } 745 | 746 | // Initialize more QUnit.config and QUnit.urlParams 747 | (function() { 748 | var i, 749 | location = window.location || { search: "", protocol: "file:" }, 750 | params = location.search.slice( 1 ).split( "&" ), 751 | length = params.length, 752 | urlParams = {}, 753 | current; 754 | 755 | if ( params[ 0 ] ) { 756 | for ( i = 0; i < length; i++ ) { 757 | current = params[ i ].split( "=" ); 758 | current[ 0 ] = decodeURIComponent( current[ 0 ] ); 759 | // allow just a key to turn on a flag, e.g., test.html?noglobals 760 | current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true; 761 | urlParams[ current[ 0 ] ] = current[ 1 ]; 762 | } 763 | } 764 | 765 | QUnit.urlParams = urlParams; 766 | 767 | // String search anywhere in moduleName+testName 768 | config.filter = urlParams.filter; 769 | 770 | // Exact match of the module name 771 | config.module = urlParams.module; 772 | 773 | config.testNumber = parseInt( urlParams.testNumber, 10 ) || null; 774 | 775 | // Figure out if we're running the tests from a server or not 776 | QUnit.isLocal = location.protocol === "file:"; 777 | }()); 778 | 779 | // Extend QUnit object, 780 | // these after set here because they should not be exposed as global functions 781 | extend( QUnit, { 782 | assert: assert, 783 | 784 | config: config, 785 | 786 | // Initialize the configuration options 787 | init: function() { 788 | extend( config, { 789 | stats: { all: 0, bad: 0 }, 790 | moduleStats: { all: 0, bad: 0 }, 791 | started: +new Date(), 792 | updateRate: 1000, 793 | blocking: false, 794 | autostart: true, 795 | autorun: false, 796 | filter: "", 797 | queue: [], 798 | semaphore: 1 799 | }); 800 | 801 | var tests, banner, result, 802 | qunit = id( "qunit" ); 803 | 804 | if ( qunit ) { 805 | qunit.innerHTML = 806 | "

          " + escapeText( document.title ) + "

          " + 807 | "

          " + 808 | "
          " + 809 | "

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