新版本并没有添加新功能,只是修bug与进行代码重构!详细用法请看abut v2的介绍。
//abut v3 annotations-based unit testing,基于注释的单元测试工具 by 司徒正美 //http://www.cnblogs.com/rubylouvre/archive/2011/03/06/1972250.html (function(WIN,DOM){ var addEvent = (function () { if (DOM.addEventListener) { return function (el, type, fn) { el.addEventListener(type, fn, false); }; } else { return function (el, type, fn) { el.attachEvent('on' + type, function () { return fn.call(el, WIN.event); }); } } })(); //////////////////////////////////////////////////////////////////////////////////////////////// var A_slice = Array.prototype.slice; var metaOne = { '\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '\\': '\\\\' } WIN.dom = WIN.dom || { mix: function(target, source ,override) { var i, ride = (override === void 0) || override; for (i in source) { if (ride || !(i in target)) { target[i] = source[i]; } } return target; } } dom.mix(dom,{ // http://www.cnblogs.com/rubylouvre/archive/2010/01/20/1652646.html type : (function(){ var reg = /^(\w)/, regFn = function($,$1){ return $1.toUpperCase() }, to_s = Object.prototype.toString; return function(obj,str){ var result = (typeof obj).replace(reg,regFn); if(result === 'Object' || (result === 'Function' && obj.exec) ){//safari chrome中 type /i/ 为function if(obj===null) result = 'Null';//Object,Function,Null,Undefined,Window,Arguments等等都为其构造器名称 else if(obj.window==obj) result = 'Window'; else if(obj.callee) result = 'Arguments'; else if(obj.nodeType === 9) result = 'Document'; else if(obj.nodeName) result = (obj.nodeName+'').replace('#',''); //处理元素节点 else if(!obj.constructor || !(obj instanceof Object)){ if("send" in obj && "setRequestHeader" in obj){//处理IE5-8的宿主对象与节点集合 result = "XMLHttpRequest" }else if("length" in obj && "item" in obj){ result = "namedItem" in obj ? 'HTMLCollection' :'NodeList'; }else{ result = 'Unknown'; } }else result = to_s.call(obj).slice(8,-1); } if(result === "Number" && isNaN(obj)) result = "NaN"; //safari chrome中 对 HTMLCollection与NodeList的to_s都为 "NodeList",此bug暂时无解 if(str){ return str === result; } return result; } })(), oneObject : function(array,val){ var result = {},value = val !== void 0 ? val :1; for(var i=0,n=array.length;i < n;i++) result[array[i]] = value; return result; }, quote : String.quote || function (str) { str = str.replace(/[\x00-\x1f\\]/g, function (chr) { var special = metaOne[chr]; return special ? special : '\\u' + ('0000'+chr.charCodeAt(0).toString(16)).slice(-4); }); return '"' + str.replace(/"/g, '\\"') + '"'; } },false); //http://www.cnblogs.com/rubylouvre/archive/2010/04/20/1716486.html (function(w,s){ s = ["XMLHttpRequest", "ActiveXObject('Msxml2.XMLHTTP.6.0')", "ActiveXObject('Msxml2.XMLHTTP.3.0')", "ActiveXObject('Msxml2.XMLHTTP')", "ActiveXObject('Microsoft.XMLHTTP')"]; if( !-[1,] && w.ScriptEngineMinorVersion() === 7 && location.protocol === "file:"){ s.shift(); } for(var i = 0 ,el;el=s[i++];){ try{ if(eval("new "+el)){ dom.xhr = new Function( "return new "+el); break; } }catch(e){} } })(WIN); /////////////////////////////////////////////////////////////////////////// var rcomments = /\/\/([^\r\n]+)|\/\*([\s\S]+?)\*\//g; var rltrim = /^\s+/ var rsign = /(\$|#){4}(\w*)\(([\s\S]+)\);?/ if ( rltrim.test( "\xA0" ) ) { rltrim = /^[\s\xA0]+/; } var rstar = /^\*+/;//多行注释中的前置*号 var rlt4 = /<<<</ var rgt4 = />>>>/ var rtest = /try{dom._should/ var rCRFL = /\r?\n/ //回车符与换行符 var ropacity = /opacity:\s*(\d?\.\d+)/g; var trimCode = function(source){ var results = [], flag_mul , flag_push, line, m, num = 0 while((m = rcomments.exec(source))){//取得所有注释 var comment = m[1] || m[2] , lines = comment.split(rCRFL) , result = [] for(var i=0, n = lines.length; i < n ;i++){ line = lines[i].replace(rltrim,'').replace(rstar,' '); if(rlt4.test(line)){//在使用了夹具的情况,使用<<<<作为左界定符 line = "try{" flag_mul = flag_push = true; } if( flag_mul){//夹具 flag_push = true } if( rgt4.test(line)){//在使用了夹具的情况,使用>>>>作为左界定符 flag_mul = false; line = "}catch(e){};" flag_push = true; } if(rtest.test(line)){//用于统计一共有多少个测试样例 num++ flag_push = true } if( flag_push ){ result.push(line) } flag_push = false; } if(/\S+/.test(result.join(""))) results = results.concat(result) } return { code:results.join("\n"), num:num } } //将源码分行并添加上行号 var cleanCode = function (source) { var lines = source.split(rCRFL) ,line for(var i=0,n = lines.length; i < n ;i++){ line = lines[i].replace(rltrim, ''); lines[i] = line.replace(rsign,function(code,$1,$2,$3){ if($1 == "####") dom.abut.isModify = true; code = window.uneval && uneval(code) || dom.quote(code) return "try{dom._should("+$3+","+i+"," + dom.quote($2 || "ok") +","+ code +')}catch(e){dom.abut.render("dom-abut-error","第'+i+'行测试代码发生错误",'+code+'+"<br/>"+ e)};' }); } return lines.join('\n'); }; dom.mix(dom,{ abut:function(str,appendTo,text){ appendTo = appendTo || DOM.body if(str.indexOf("url(")=== 0){ var url = str.slice(4,-1), xhr = dom.xhr(); xhr.open("GET",url+"?"+(new Date-0),false); xhr.send(null); text = xhr.responseText || ""; if (!text) throw "the target file does not exist"; }else{ var el = DOM.getElementById(str); if (!el) throw "can not find the target element"; text = el.text; } var self = arguments.callee; self.isModify = false; var purified_code = cleanCode(text); var obj = trimCode(purified_code); if(!self._first){//保证这里的代码只执行一次 self._first = true; el = self.componentElement = DOM.createElement("ul"); el.className = "dom-abut-result"; self.render("dom-abut-title",'<span>一共有'+obj.num+'个测试</span> <span id=dom-abut-pass>0</span>done <span id=dom-abut-unpass>0</span>errors'); appendTo.appendChild(el); dom.addSheet(".dom-abut-result {\ border:5px solid #00a7ea;padding:10px;background:#03c9fa;list-style-type:none;\ }\ .dom-abut-result li{\ padding:5px ;margin-bottom:1px;font-size:14px;\ }\ .dom-abut-result li blockquote{\ padding:5px;display:none;\ }\ .dom-abut-title{\ background:#008000;\ }\ .dom-abut-pass{\ background:#a9ea00;\ }\ .dom-abut-unpass{\ background:red;color:#fff;\ }\ .dom-abut-error{\ background:#000;color:#fff;\ }\ .dom-abut-log{\ background:#c0c0c0;\ }\ .dom-abut-log blockquote{\ font-weight: normal;background:#808080;\ }"); addEvent(el,"click",function(e){ e = e || WIN.event; var target = e.target || e.srcElement; var blockquote = target.getElementsByTagName("blockquote")[0]; if(blockquote){ blockquote.style.display = !!(blockquote.offsetHeight || blockquote.offestWidth) ? "none": "block"; } }); } try { self.isModify && eval(purified_code); eval(obj.code); } catch (e) { self.render("dom-abut-error","解析编译测试代码失败!"); } }, addSheet : function(css,appendTo){ var self = arguments.callee,style ,el = appendTo || dom.insertion || DOM.body if(!self.style){ var styles = DOM.getElementsByTagName("style"), i = 0, reg = /screen|all/i, media while(style = styles[i++]){ media = style.getAttribute("media"); if(media === null || reg.test(media)){ self.style = style; break; } } if(!self.style){ style = DOM.createElement('style'); el.parentNode.insertBefore(style,el); self.style = style; } } if(!-[1,] && el.filters){//IE6-8 css = css.replace(ropacity,function($,$1){ $1 = parseFloat($1) * 100; if($1 < 0 || $1 > 100) return ""; return "filter:alpha(opacity="+ $1 +");" }); } css += "\n";//增加末尾的换行符,方便在firebug下的查看。 if(style.styleSheet){ //ie style.styleSheet.cssText += css;//添加新的内部样式 }else if(WIN.Components){ style.innerHTML += css;//火狐支持直接innerHTML添加样式表字串 }else{ style.appendChild(DOM.createTextNode(css)) } }, _should:function(actual, expected, line, method, source){ var args = Array.apply([],arguments); source = args.pop(); method = args.pop(); line = args.pop(); if(args.length === 1){ expected = void 0; } var bool, context switch(method){ case "ok"://布尔真测试 bool = !!actual break; case "ng"://布尔非测试 bool = !!!actual break; case "eq"://同一性真测试 bool = actual == expected; break; case "not"://同一性非测试 bool = actual != expected; break; case "same": bool = dom.isEqual(actual, expected) break case "log": bool = "<pre>"+dom.dump(actual)+"</pre>"; break; } if(typeof bool === "string"){ context = '第' + line+"行日志记录"; dom.abut.render("dom-abut-log",context,bool); }else{ context = '第'+ line+'行测试代码: '+(bool ? '通过' :'不通过' ) ; var id = bool ? 'dom-abut-pass' : 'dom-abut-unpass'; var el = DOM.getElementById(id); var count = ~~el.innerHTML el.innerHTML = ++count; source = "actual : "+actual+"<br/> expected : "+expected+"<hr/>"+source dom.abut.render(id,context,source); } }, //比较对象是否相等或相似 isEqual: function(a, b) { if (a === b) { return true; } else if (a === null || b === null || typeof a === "undefined" || typeof b === "undefined" || dom.type(a) !== dom.type(b)) { return false; // don't lose time with error prone cases } else { switch(dom.type(a)){ case "String": case "Boolean": case "Number": case "Null": case "Undefined": //处理简单类型的伪对象与字面值相比较的情况,如1 v new Number(1) if (b instanceof a.constructor || a instanceof b.constructor) { return a == b; } return a === b; case "NaN": return isNaN(b); case "Date": return a.valueOf() === b.valueOf(); case "Array": var len = a.length; if (len !== b.length) return false; for (var i = 0; i < len; i++) { if (!this.isEqual(a[i], b[i])) { return false; } } return true; default: for (var key in b) { if (!this.isEqual(a[key], b[key])) { return false } } return true; } } }, dump : function(obj, indent) { indent = indent || ""; if (obj === null) return indent + "null"; if (obj === void 0) return indent + "undefined"; if (obj.nodeType === 9) return indent + "[object Document]"; if (obj.nodeType) return indent + "[object " + (obj.tagName || "Node") +"]"; var arr = [],type = dom.type(obj),self = arguments.callee,next = indent + "\t"; switch (type) { case "Boolean": case "Number": case "NaN": case "RegExp": return indent + obj; case "String": return indent + dom.quote(obj); case "Function": return (indent + obj).replace(/\n/g, "\n" + indent); case "Date": return indent + '(new Date(' + obj.valueOf() + '))'; case "Unknown": case "XMLHttpRequest" : case "Window" : return indent + "[object "+type +"]"; case "NodeList": case "HTMLCollection": case "Arguments": case "Array": for (var i = 0, n = obj.length; i < n; ++i) arr.push(self(obj[i], next).replace(/^\s* /g, next)); return indent + "[\n" + arr.join(",\n") + "\n" + indent + "]"; default: for (var i in obj) { arr.push(next + self(i) + ": " + self(obj[i], next).replace(/^\s+/g, "") ); } return indent + "{\n" + arr.join(",\n") + "\n" + indent + "}"; } } }); dom.mix(dom.abut,{ render : function(className,context,code){ var li = DOM.createElement("li"); li.className = className; this.componentElement.appendChild(li); var blockquote = DOM.createElement("blockquote") li.innerHTML = context; if(code){ li.appendChild(blockquote); blockquote.innerHTML = code; } } }); })(this,this.document);
比如我页面上有一个id为test3的script元素节点,其innerHTML如下:
var Person = function(name,sex){ this.name = name; this.sex = sex; //####log(this.name); //####log(this.sex); } var p = new Person("ruby","louvre");
想对其测试,只要引用abut.js,在页面上运行如下脚本即可:
dom.abut("test3",document.body); //v2的使用方法,太笨重,已废弃 // dom.abut({selector:"test3", target:document.body });
如果我们想对远程的JS文件(只限于同域,因为使用XMLHttpRequest对象),则使用方法如下:
dom.abut("url(http://aaa.js)",document.body); //v2的使用方法,太笨重,已废弃 // dom.abut({url:"http://aaa.js", target:document.body });
另,第二个参数是可以,默认就是把测试结果添加到document.body的底部!
对复杂对象的查看:
/*<<<< var h = { a : { x : 10, y : "hoge", z : [ { hoge : "fuga"} , { hoge : "foo" }, { hoge : "bar" } ] }, b : { x : "piyo" } }; $$$$log(h) >>>> */
var flatten = function(arr) { var result = [],self = arguments.callee; for(var i=0,n=arr.length,el;i < n;i++){ el = arr[i]; if (dom.type(el,"Array")) { result = result.concat(self(el)); } else { result.push(el); } } return result; } /*<<<< * var a =['frank', ['bob', 'lisa'], ['jill', ['tom', 'sally']]]; * $$$$same(flatten(a),['frank', 'bob', 'lisa', 'jill', 'tom', 'sally']); * >>>> */
例子5(截取自我框架的base模块)
var PROTO = "prototype", CTOR = "constructor", hasOwn = Object[PROTO].hasOwnProperty; //用于取得数据的类型或判定数据的类型 // $$$$(dom.type(1,"Number")); // $$$$(dom.type(NaN,"NaN")); // $$$$(dom.type(void(0),"Undefined")); // $$$$(dom.type("aaa","String")); // $$$$(dom.type([1,2,3],"Array")); // $$$$(dom.type(/i/,"RegExp")); // $$$$(dom.type({},"Object")); // $$$$(dom.type(document,"Document")); // $$$$(dom.type(document.body,"BODY")); // $$$$(dom.type(window,"Window")); // $$$$(dom.type(true,"Boolean")); // $$$$(dom.type(document.getElementsByTagName('script'),"HTMLCollection")); dom.type = (function(){ var reg = /^(\w)/, regFn = function($,$1){ return $1.toUpperCase() }, to_s = Object[PROTO].toString; return function(obj,str){ var result = (typeof obj).replace(reg,regFn); if(result === 'Object' || (result === 'Function' && obj.exec) ){//safari chrome中 type /i/ 为function if(obj===null) result = 'Null'; else if(obj.window==obj) result = 'Window'; //返回Window的构造器名字 else if(obj.callee) result = 'Arguments'; else if(obj.nodeType === 9) result = 'Document'; else if(obj.nodeName) result = (obj.nodeName+'').replace('#',''); //处理元素节点 else if(!obj.constructor || !(obj instanceof Object)){ if("send" in obj && "setRequestHeader" in obj){//处理IE5-8的宿主对象与节点集合 result = "XMLHttpRequest" }else if("length" in obj && "item" in obj){ result = "namedItem" in obj ? 'HTMLCollection' :'NodeList'; }else{ result = 'Unknown'; } }else result = to_s.call(obj).slice(8,-1); } if(result === "Number" && isNaN(obj)) result = "NaN"; //safari chrome中 对 HTMLCollection与NodeList的to_s都为 "NodeList",此bug暂时无解 if(str){ return str === result; } return result; } })() //生成键值统一的对象,用于高速化判定 // $$$$same(dom.oneObject(["aa","bb","cc"]),{"aa":1,"bb":1,"cc":1}) dom.oneObject = function(array,val){ var result = {},value = val !== void 0 ? val :1; for(var i=0,n=array.length;i < n;i++) result[array[i]] = value; return result; }