綜合眾人的意見,此版本做了許多改進,如對注釋抽取的最佳化,增加對script標籤的支援,即時性的測試等等。
// dom.abut v2 (annotations-based unit testing by 司徒正美)// http://www.cnblogs.com/rubylouvre/archive/2010/11/08/1868638.html(function(){ //ecma262新擴充 if(!Object.keys){ var _dontEnum = ['propertyIsEnumerable', 'isPrototypeOf','hasOwnProperty','toLocaleString', 'toString', 'valueOf', 'constructor']; for (var i in { toString: 1 }) _dontEnum = false; Object.keys = function(obj){//ecma262v5 15.2.3.14 var result = [],dontEnum = _dontEnum,length = dontEnum.length; for(var key in obj ) if(obj.hasOwnProperty(key)){ result.push(key) } if(dontEnum){ while(length){ key = dontEnum[--length]; if(obj.hasOwnProperty(key)){ result.push(key); } } } return result; } } if(!String.prototype.trim){ String.prototype.trim = function(){ return this.replace(/^[\s\xa0]+|[\s\xa0]+$/g, ''); } } if(!String.prototype.quote){ String.prototype.quote = (function () { var meta = { '\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '"' : '\\"', '\\': '\\\\' }, reg = /[\\\"\x00-\x1f]/g, regFn = function (a) { var c = meta[a]; return typeof c === 'string' ? c : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); }; return function(){ return '"' + this.replace(reg, regFn) + '"'; } })(); } var addEvent = (function () { if (document.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, window.event); }); } } })(); var applyIf = function(target,source){ for(var name in source) if(!target[name] ){ target[name] = source[name]; } return target; } //釋出命名空間對象 window.dom = window.dom || {}; applyIf(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 >>>"); if(segment.indexOf(">>>>") !== -1){//這裡不使用el.length === 2是為了避開IE的split bug bigClosure(els[0],resolving,obj); if(els[1]){ smartClosure(els[1],resolving,obj); } }else{ smartClosure(els[0],resolving,obj); } } //構築單元測試系統的UI var UL = document.createElement("UL"); abut.el = UL; target.appendChild(UL); UL.className ="dom-abut-result"; abut.render("dom-abut-title",'一共有'+obj.count+'個測試'); abut.recoder = document.getElementById( abut.ULID); if(!arguments.callee.first){//保證這裡的代碼只執行一次 arguments.callee.first = true; addEvent(target,"click",function(e){ var target = e.target || e.srcElement; if(target.className ==="dom-abut-slide"){ var blockquote = target.parentNode.getElementsByTagName("blockquote")[0]; if(blockquote){ blockquote.style.display = !!(blockquote.offsetHeight || blockquote.offestWidth) ? "none": "block"; } } }); //添加樣式 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-slide{\ cursor: pointer;\ }\ .dom-abut-result li blockquote{\ margin:0;\ padding:5px;\ display:none;\ }\ .dom-abut-title{\ background:#008000;\ }\ .dom-abut-pass{\ background:#a9ea00;\ }\ .dom-abut-unpass{\ background:red;\ color:#fff;\ }\ .dom-abut-log{\ background:#c0c0c0;\ }\ .dom-abut-log blockquote{\ background:#808080;\ }"); } try { abut.isModify && eval(uneval); eval(resolving.join("")); } catch (e) { return abut.render("dom-abut-unpass","解析編譯測試代碼失敗"); } for(var i=0,fn;fn= abut.closures[i++];){ try { fn(); } catch (e) { abut.render("dom-abut-unpass","第"+fn.lineNumber +"行測試代碼執行失敗"); } } } applyIf(dom,{ abut:function(obj){ var key = obj.selector || obj.url, target = obj.target || document.body,str; if(dom.type(target,"String")){ target = document.getElementById(target); } if(obj.selector){ var el = document.getElementById(key); if (!el) throw "can not find the target element"; str = el.text; }else { var xhr = dom.xhr(); xhr.open("GET",key+"?"+(new Date-0),false); xhr.send(null); str = xhr.responseText || ""; if (!str) throw "the target file does not exist"; } evalCode(str,target) }, getComments : function(text){ var m , result = []; while(m = rcomments.exec(text)){ result.push((m[1] || m[2]).replace(rstar,function($,$1){ return $1 })); } return result.join('\n'); }, addSheet : function(css){ if(!-[1,]){ css = css.replace(ropacity,function($,$1){ $1 = parseFloat($1) * 100; if($1 100) return ""; return "filter:alpha(opacity="+ $1 +");" }); } css += "\n";//增加末尾的分行符號,方便在firebug下的查看。 var doc = document, head = doc.getElementsByTagName("head")[0], styles = head.getElementsByTagName("style"),style,media; if(!styles.length){//如果不存在style元素則建立 style = doc.createElement('style'); style.setAttribute("type", "text/css"); head.insertBefore(style,null) } style = styles[0]; media = style.getAttribute("media"); if(media === null && !/screen/i.test(media) ){ style.setAttribute("media","all"); } if(style.styleSheet){ //ie style.styleSheet.cssText += css;//添加新的內部樣式 }else if(doc.getBoxObjectFor){ style.innerHTML += css;//Firefox支援直接innerHTML添加樣式表字串 }else{ style.appendChild(doc.createTextNode(css)) } }, //比較對象是否相等或相似 isEqual: function(a, b) { if (a === b) return true; var atype = typeof(a), btype = typeof(b); if (atype != btype) return false; if (a == b) return true; if ((!a && b) || (a && !b)) return false; if (a.isEqual) return a.isEqual(b); if (dom.type(a,"Date") && dom.type(b,"Date")) return a.valueOf() === b.valueOf(); if (dom.type(a,"NaN") && dom.type(b,"NaN")) return false; if (dom.type(a,"RegExp") && dom.type(b,"RegExp")) return a.source === b.source && a.global === b.global && a.ignoreCase === b.ignoreCase && a.multiline === b.multiline; if (atype !== 'object') return false; if (a.length && (a.length !== b.length)) return false; var aKeys = Object.keys(a), bKeys = Object.keys(b); if (aKeys.length != bKeys.length) return false; for (var key in a) if (!(key in b) || !dom.isEqual(a[key], b[key])) return false; return true; }, inspect : 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 + obj.quote(); 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 第' + arguments.callee.lineNumber+"行日誌記錄 "+ (message || "") + ""; var testCode = ""+dom.inspect(obj)+"
";
this.render("dom-abut-log",context,testCode);
},
prepareRender : function(bool,lineNumber,testCode){
var className = bool ? 'dom-abut-pass' : 'dom-abut-unpass',
context = '第'+ lineNumber+'行測試代碼: '+(bool ? '通過' :'不通過' )+"" ;
this.recoder.innerHTML = " 已完成第"+(++this.time)+"個測試";
this.render(className,context,testCode);
},
render : function(className,context,code){
var li = document.createElement("li");
li.className = className;
this.el.appendChild(li);
var blockquote = document.createElement("blockquote")
li.innerHTML = context;
if(code){
li.appendChild(blockquote);
blockquote.innerHTML = code;
}
}
});
})();
測試例子1:
var test1 = 1;/* * <<<< * $$$$log(a) * >>>> *//* dsfds */// frgtretr /** * erwwer * */
<br /> var source = document.getElementById("test1").innerHTML;<br /> alert(source);<br /> alert(dom.getComments(source))<br />
運行代碼
測試例子2:
var p = function(){} // comment /* not-a-nested-comment p('not-a-comment'); // comment */* still-a-comment p('not-a-comment'); /* alert('commented-out-code');// still-a-comment */ p('not-a-comment'); var re= /\/* not-a-comment */; //* comment
<br /> var source = document.getElementById("test2").innerHTML;<br /> alert(source);<br /> alert(dom.getComments(source))<br />
運行代碼
abut v2增加了對script的支援,為此方法的參數由一個字串改為一個雜湊,雜湊有三個值,selector,url與target。selector就是指script標籤的id,通過來擷取其innerHTML進行測試。url指JS檔案的路徑,我們通過同步AJAX請求擷取responseText進行測試,由於是AJAX請求,就會受到同源策略的限制,因此v1我就無法放出樣本,因此以後樣本都使用selector方法。target是指將測試結果放到哪一個元素節點中,預設是document.body,此參數可選,並且如果是字串,它會自動當成標籤的ID進行尋找。
比如我頁面上有一個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({ selector:id,//要測試的script標籤 //url:url,//只限於同域的路徑 target:document.body //把測試結果添加到的位置(可以是元素節點,也可以是其id值) });
運行代碼
在此例子中,我們也可以看到新的標識符####,它將取代原先的@@@@。v1中的@@@@目的是將閉包中或內建函式中的資料釋出全域範圍下,然後再用$$$$一系列方法對它進行測試。然後,實踐證明,這有點麻煩,為何不直接在原先的內部範圍中執行它呢?!####就是基於此目的開發出來,相對應,@@@@就沒有用了,被我廢棄掉了,移除了。####會對源碼進行修改再進行解釋執行,而$$$$則先解析執行源碼再對注釋中的測試範例進行修葺後執行,執行時機是完全不一樣的。$$$$可以跟eq,same,ok,log這四個函數名,####也完全一樣,但####暫時不支援寫在>>>之間。
例子4,對多行測試注釋的使用。
var flatten = function(arr) { var result = [],self = arguments.callee; for(var i=0,n=arr.length,el;i >>> */
運行代碼
例子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 運行代碼