对于一个框架的成长来说,测试是必不可少的,像用手来点来点去的测试基本不算测试,只有基于代码的单元测试才能让人安心。因此我不断地升级我的测试工具,从早期的abut到现在的spec模块。它不断地添加测试函数:eq, ok, same,contains, match...最近添加的方法是near,是专门用于测试CSS样式值,像width, height, opacity, border-top-width等值在各浏览器下总差1,2像素,更有甚者,webkit系的连浮点数也出来了。由于一个框架拥有许多模块,因此测试显示区也分为许多版块,如何处理同步与异步的关系非常重要,前一版本专门搞了个Deferred异步列队,但我发现其实mass的模块加载模块中的ready列队就是现成的异步列队,因此这版本就省下许多代码。本版本还处理了中文显示问题,这主要是因为FF私自对函体内的中文进行转码,我搞了uni2hanzi把它还原回来了。
CSS部分
@CHARSET "UTF-8"; #dom-spec-result { border:5px solid #00a7ea; padding:10px; background:#03c9fa; list-style-type:none; } .dom-spec-summary { height: 2em; line-height: 2em; margin: 0; font-size: 13px; font-weight: bold; text-indent: 2em; background:#008000; color:#fff; } .dom-spec-detail{ list-style: none; margin: 0; padding: 0; } .dom-spec-detail li{ margin: 0; padding:0; border: 2px solid #03c9fa; text-indent: 1em; } .dom-spec-pass{ background:#a9ea00; } .dom-spec-unpass{ background:#cd0000; color:#fff; } .dom-spec-detail pre{ margin: 1em; text-indent: 0; font-style: normal; background: #F0F8FF; padding: 2px; color:#000; border:2px outset #c0c0c0; } .dom-spec-error{ background: #000; color:#fff; } .dom-spec-log{ background: #cc9!important; } /*用于点击展开*/ .dom-spec-slide { background:#a9ea00; text-indent: 2em; line-height: 1.4em; height: 1.4em; margin: 0; } .dom-spec-diff { background: red; margin: 1em; } .dom-spec-diff div{ 45%; float: left; } .dom-spec-diff pre{ background: #00cc00; } /* new clearfix */ .clearfix:after { visibility: hidden; display: block; font-size: 0; content: " "; clear: both; height: 0; } * html .clearfix { zoom: 1; } /* IE6 */ *:first-child+html .clearfix { zoom: 1; } /* IE7 */
JS部分
//================================================== // 测试模块 //================================================== (function(global,DOC){ var dom = global[DOC.URL.replace(/(#.+|\W)/g,'')]; dom.define("first/spec", function(){ dom.log("已加载spec模块") //模块为dom添加如下方法: //quote isEqual dump Deferred runTest addTestModule //在全局命名空间下多添加一个函数 expect dom.mix(dom,{ //在字符串两端加上引号,并对其内部一些字符进行转义,用于JSON与引用 quote :global.JSON && JSON.stringify || String.quote || (function(){ var meta = { '\t':'t', '\n':'n', '\v':'v', 'f':'f', '\r':'\r', '\'':'\'', '\"':'\"', '\\':'\\' }, reg = /[\x00-\x1F\'\"\\\u007F-\uFFFF]/g, regFn = function(c){ if (c in meta) return '\\' + meta[c]; var ord = c.charCodeAt(0); return ord < 0x20 ? '\\x0' + ord.toString(16) : ord < 0x7F ? '\\' + c : ord < 0x100 ? '\\x' + ord.toString(16) : ord < 0x1000 ? '\\u0' + ord.toString(16) : '\\u' + ord.toString(16) }; return function (str) { return '"' + str.replace(reg, regFn)+ '"'; } })(), //比较对象是否相等或相似 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 "NodeList": case "Arguments": 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 "XMLHttpRequest" : case "Window" : return indent + "[object "+type +"]"; case "NodeList": 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 ( i in obj) { arr.push(next + self(i) + ": " + self(obj[i], next).replace(/^\s+/g, "")); } return indent + "{\n" + arr.join(",\n") + "\n" + indent + "}"; } } }); //这里尽量不依赖其他核主模块 var $ = function(id) { return DOC.getElementById(id); }; var parseHTML = function() {//用于生成元素节点,注意第一层只能用一个元素 var div = DOC.createElement("div"); return function(html) { div.innerHTML = html; return div.firstChild; }; }(); //在字符串嵌入表达式 http://www.cnblogs.com/rubylouvre/archive/2011/03/06/1972176.html var reg_format = /\\?\#{([^{}]+)\}/gm; var format = function(str, object){ var array = dom.slice(arguments,1); return str.replace(reg_format, function(match, name){ if (match.charAt(0) == '\\') return match.slice(1); var index = Number(name) if(index >=0 ) return array[index]; if(object && object[name] !== void 0) return object[name]; return '' ; ; }); } var Expect = function(actual){ return this instanceof Expect ? this.init(actual) : new Expect(actual); } function getUnpassExpect(str){ var boolIndex = 1,ret = "error!",section = 0, qualifier = "(" for(var j=1;j < str.length;j++){ if(str.charAt(j) == "("){ boolIndex++ }else if(str.charAt(j) == ")"){ boolIndex-- }else if(str.charAt(j) != qualifier && boolIndex == 0){ section++ if(section == 1){ qualifier = ")"//取得expect(...)中的部分 boolIndex = -1 }else if(section == 2){ boolIndex = 1;//取得ok,eq,match,log等函数名 qualifier = ")" }else if(section == 3){//取得最后的函数体,并返回整个匹配项 ret = "expect" + str.slice(0,j) break } } } return ret; } dom.require("ready",function(){ var html = ['<div id="dom-spec-result"><p class="dom-spec-summary">', '<span id="dom-spec-failures" title="0">0</span> failures ', '<span id="dom-spec-errors" title="0">0</span> errors ', '<span id="dom-spec-done" title="0">0</span>% done ', '<span id="dom-spec-time" title="0">0</span>ms </p>', '<p class="dom-spec-summary">',global.navigator.userAgent, '</p><div id="dom-spec-cases"><div id="loading">正在加载测试数据中,请耐心等特</div></div></div>']; //div#dom-spec-result为整个系统的容器 //div#dom-spec-summary用于放置各种统计 //div#dom-spec-cases用于放置测试模块 DOC.body.appendChild(parseHTML(html.join(""))); }); dom.mix(Expect,{ refreshTime : function(){//刷新花费时间 var el = $("dom-spec-time"); var times = parseInt(el.title,10) + (new Date - Expect.now); el.title = times; el.innerHTML = times }, addTestModule : function(title, cases) { dom.require("ready",function(){ var moduleId = "dom-spec-"+title, names = []; if(!$(moduleId)){//在主显示区中添加一个版块 /** =================每个模块大抵是下面的样子=============== <div class="dom-spec-case" id="dom-spec-dom.js"> <p><a href="javascript:void(0)">JS文件名字</a></p> <ul style="display: none;" class="dom-spec-detail"> 测试结果 </ul> </div> */ var html = ['<div id="#{0}" class="dom-spec-case">', '<p class="dom-spec-slide"><a href="javascript:void(0)">#{1}</a></p>', '<ul class="dom-spec-detail" style="display:none;"></ul></div>'].join(''); $("dom-spec-cases").appendChild(parseHTML(format(html, moduleId, title))); } for(var name in cases){//取得describe第二个参数的那个对象所包含的所有函数,并放到异步列队中逐一执行它们 if(cases.hasOwnProperty(name)){ names.push(name); } }; (function runTest(){ if((name = names.shift())){ var suite = cases[name],//测试函数 caseId = "dom-spec-case-"+name.replace(/\./g,"-"); if(!Expect.removeLoading){ var loading = $("loading"); loading.parentNode.removeChild(loading); Expect.removeLoading = 1 } if(!$(caseId)){//对应一个方法 var parentNode = $(moduleId).getElementsByTagName("ul")[0]; //处理函数体的显示 var safe = (suite+"").replace(/</g,"<").replace(/>/g,">"); //从函数体内分解出所有测试单元 Expect.expectArray = safe.split("expect"); //函数体本身 var node = parseHTML(format('<li id="#{0}">#{1}<pre>#{2}</pre></li>',caseId,name,uni2hanzi(safe))); parentNode.appendChild(node); } Expect.Client = $(caseId);//对应一个LI元素 Expect.PASS = 1;//用于判定此测试套件有没有通过 Expect.boolIndex = 0;//用于记录当前是执行到第几条测试 Expect.totalIndex = 0; Expect.now = new Date; try{ suite();//执行测试套件 }catch(err){ Expect.PASS = 2; var htm = ["第",Expect.boolIndex,"行测试发生错误\n",Expect.Msgs[Expect.boolIndex],"\n"]; for(var e in err){ htm.push(e+" "+(err[e]+"").slice(0,100)+"\n"); } htm = '<pre title="error">'+htm.join("").replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>')+"</pre>"; Expect.Client.appendChild(parseHTML(htm)); var errors = $("dom-spec-errors"); errors.title++; errors.innerHTML = errors.title; } $(caseId).className = Expect.CLASS[Expect.PASS]; Expect.refreshTime();//更新测试所花的时间 setTimeout(runTest,16); } })(); }); }, CLASS : { 0:"dom-spec-unpass", 1:"dom-spec-pass", 2:"dom-spec-error" }, Msgs:{}, prototype:{ init:function(actual){//传入一个目标值以进行比较或打印 this.actual = actual; return this; }, ok:function(msg){//判定是否返回true this._should("ok",void 0,msg); }, ng:function(msg){//判定是否返回false this._should("ng",void 0,msg); }, log:function(msg){//不做判断,只打印结果,用于随机数等肉眼验证 this._should("log",msg); }, eq:function(expected,msg){//判定目标值与expected是否全等 this._should("eq", expected, msg); }, near:function(expected, msg){ this._should("near", expected, msg); }, match:function(fn,msg){//判定目标值与expected是否全等 this._should("match", fn, msg); }, not:function(expected,msg){//判定目标值与expected是否非全等 this._should("not", expected,msg); }, has:function(prop,msg){//判定目标值是否包含prop属性 this._should("has", prop,msg); }, contains:function(el,msg){//判定目标值是否包含el这个元素(用于数组或类数组) this._should("contains", el,msg); }, same: function(expected,msg){//判定结果是否与expected相似(用于数组或对象或函数等复合类型) this._should("same", expected,msg); }, _should:function(method,expected, msg){//上面方法的内部实现,比较真伪,并渲染结果到页面 var actual = this.actual,bool = false; if(method != "log"){ Expect.Msgs[Expect.boolIndex] = msg; Expect.boolIndex++; } Expect.totalIndex++ switch(method){ case "ok"://布尔真测试 bool = actual === true; expected = true; break; case "ng"://布尔非测试 bool = actual === false; expected = false; break; case "eq"://同一性真测试 bool = actual == expected; break; case "near": var threshold = arguments[3] | 0; return Math.abs(parseFloat(this.actual) - parseFloat(expected)) <= threshold; break; case "not"://同一性非测试 bool = actual != expected; break; case "same": bool = dom.isEqual(actual, expected); break case "has": bool = Object.prototype.hasOwnProperty.call(actual, expected); break; case "match": bool = expected(actual); break; case "contains": for(var i = 0,n = actual.length; i < n ;i++ ){ if(actual === expected ){ bool = true; break; } } break; case "log": bool = ""; Expect.Client.appendChild(parseHTML('<pre class="dom-spec-log" title="log">'+(expected||"")+" "+dom.dump(actual)+'</pre>')); break; } // Expect.Msgs[Expect.boolIndex] = msg; //修改统计栏的数值 var done = $("dom-spec-done"); var errors = $("dom-spec-errors"); var failures = $("dom-spec-failures"); if(typeof bool === "boolean"){ Expect.PASS = ~~bool; if(!bool){//如果没有通过 failures.title++; failures.innerHTML = failures.title; var statement = getUnpassExpect((Expect.expectArray[Expect.totalIndex] || "")) var html = ['<div class="dom-spec-diff clearfix">'+(msg ? "<p>"+msg+"</p>" : "")+'<p>本测试套件中第',Expect.boolIndex, '条测试出错: ',statement,'</p><div>actual:<pre title="actual">'+dom.type(actual)+" : "+dom.dump(actual)+'</pre></div>', '<div>expected:<pre title="expected">'+dom.type(expected)+" : "+dom.dump(expected)+'</pre></div>', '</div>']; Expect.Client.appendChild(parseHTML(html.join(''))); } done.title++; done.innerHTML = (((done.title-errors.title-failures.title)/done.title)*100).toFixed(0); } } } }); //用于收起或展开详细测试结果 dom.bind(DOC,"click",function(e){ var target = e && e.target || event.srcElement; if(target.tagName === "A"){ var parent = target.parentNode.parentNode; if(parent.className== "dom-spec-case"){//用于切换详情面板 var ul = parent.getElementsByTagName("ul")[0]; var display = ul.style.display; ul.style.display = display === "none" ? "" : "none"; } } }); //shortcut //暴露到全局作用域 global.expect = Expect; dom.addTestModule = Expect.addTestModule; //此函数是解决FF无法显示函数体内的汉字问题 var uni2hanzi = global.netscape ? function(s){ return unescape(s.replace(/\\u/g,'%u')); }: function(s){ return s } }) })(this,this.document); //2011.8.9 增加getUnpassExpect函数,用于取得没有通过的expect并显示出来 //2011.10.26 优化format与quote //2011.10.27 runTest添加参数,用于等特一定做量的测试模块都加载完毕才执行 //2011.10.31 去掉deferred模块的依赖,依靠ready列队自行添加测试的模块
用法:
//主页面引用 dom.require("ready,test/dom,test/lang", dom.noop);
测试用的JS文件:
// test/dom.js dom.define("test/dom","first/spec",function(){ dom.isWindow = function(obj){//单独提出来,专门用于测试对window的判定 return dom.type(obj,"Window") }; dom.addTestModule('模块加载模块-dom', { 'type': function() { expect(dom.type("string")).eq("String"); expect(dom.type(1)).eq("Number"); expect(dom.type(!1)).eq("Boolean"); expect(dom.type(NaN)).eq("NaN"); expect(dom.type(/test/i)).eq("RegExp"); expect(dom.type(dom.noop)).eq("Function"); expect(dom.type(null)).eq("Null"); expect(dom.type({})).eq("Object"); expect(dom.type([])).eq("Array"); expect(dom.type(new Date)).eq("Date"); expect(dom.type(window)).eq("Window"); expect(dom.type(document)).eq("Document"); expect(dom.type(document.documentElement)).eq("HTML"); expect(dom.type(document.body)).eq("BODY"); expect(dom.type(document.childNodes)).eq("NodeList"); expect(dom.type(document.getElementsByTagName("*"))).eq("NodeList"); expect(dom.type(arguments)).eq("Arguments"); expect(dom.type(1,"Number")).eq(true); }, "isWindow" : function(){ var test1 = {}; test1.window = test1; test1.document = document; expect(dom.isWindow(test1)).ng(); var test2 = {}; test2.window = window; test2.document = document; expect(dom.isWindow(test1)).ng(); expect(dom.isWindow(window)).ok(); var iframe = document.createElement("iframe"); document.body.appendChild(iframe); var iwin = iframe.contentWindow || iframe.contentDocument.parentWindow; expect(dom.isWindow(iwin)).ok(); document.body.removeChild(iframe); var wg = { document : {} }, wgdoc = wg.document; wg.window = wg; wgdoc.createElement = function(){ return wg; }; wgdoc.getElementsByTagName = function(){ return [wg]; }; wgdoc.parentWindow = wg; wg.insertBefore = function(){}; wg.firstChild = wg.firstChild; wg.removeChild = function(){}; expect(dom.isWindow(wg)).ng();//false }, "oneObject":function(){ expect(dom.oneObject("aa,bb,cc")).same({ "aa":1, "bb":1, "cc":1 }); expect(dom.oneObject([1,2,3],false)).same({ "1":false, "2":false, "3":false }); } }); });
// test/lang.js dom.define("test/lang","lang,first/spec",function(dom, $$){ dom.addTestModule("语言扩展模块-lang",{ "dom.isPlainObject": function() { expect(dom.isPlainObject([])).ng();//false expect(dom.isPlainObject(1)).ng();//false expect(dom.isPlainObject(null)).ng();//false expect(dom.isPlainObject(void 0)).ng();//false expect(dom.isPlainObject(window)).ng();//false expect(dom.isPlainObject(document.body)).ng();//false expect(window.location).log(); expect(dom.isPlainObject(window.location)).ng();//false var fn = function(){} expect(dom.isPlainObject(fn)).ng();//false fn.prototype = { someMethod: function(){} }; expect(dom.isPlainObject(new fn)).ng();//false expect(dom.isPlainObject({})).ok();//true expect(dom.isPlainObject({ aa:"aa", bb:"bb", cc:"cc" })).ok();//true expect(dom.isPlainObject(new Object)).ok();//true }, "dom.isArrayLike":function(){ expect(dom.isArrayLike(arguments)).ok();//1 expect(dom.isArrayLike(document.links)).ok();//2 expect(dom.isArrayLike(document.documentElement.childNodes)).ok(); expect(dom.isArrayLike({ 0:"a", 1:"b", length:2 })).ok(); var tag = dom.tag var html = tag("select",tag("option","aaa") + tag("option","bbb")+ tag("option","ccc")) var div = document.createElement("div"); div.innerHTML = html; var select = div.firstChild; expect(dom.isArrayLike(select)).ng(); }, "dom.isNative":function(){ expect(dom.isNative(Array.prototype,"slice")).ok(); expect(dom.isNative(Array.prototype,"indexOf")).log(); expect(dom.isNative(Array.prototype,"forEach")).log(); expect(dom.isNative(String.prototype,"quote")).log(); expect(dom.isNative(String.prototype,"trim")).log(); expect(dom.isNative(Function.prototype,"bind")).log(); }, "dom.range":function(){ expect(dom.range(10)).same([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); expect(dom.range(1, 11)).same([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); expect(dom.range(0, 30, 5)).same([0, 5, 10, 15, 20, 25]); expect(dom.range(0, -10, -1)).same([0, -1, -2, -3, -4, -5, -6, -7, -8, -9]); expect(dom.range(0)).same([]); }, "dom.format":function(){ expect(dom.format("pi is #{0}", Math.PI)).eq("pi is 3.141592653589793"); var a = dom.format("style.#{name}=((isEnd ? #{end} : adapter.#{type}( #{from}, #{change},'#{easing}',per ))|0)+'#{unit}';",{ name:"width", end:"0", type:"_default", from:"200", change:"200", easing:"linear", unit:"px" }) ; expect(a).eq("style.width=((isEnd ? 0 : adapter._default( 200, 200,'linear',per ))|0)+'px';"); }, "dom.tag":function(){ var tag = dom.tag var html = tag("h1 title='aaa'","sss") ('a href=#' , tag("img src='http://www.google.com.hk/images/nav_logo83.png'") ('br') ('' ,"View larger image") ); expect(html+"").eq("<h1 title='aaa'>sss</h1><a href=#><img src='http://www.google.com.hk/images/nav_logo83.png'><br>View larger image</a>"); }, "dom.parseXML":function(){ var str = "<note><to>Tove</to><from>Jani</from><heading>Reminder</heading><body>Don't forget me this weekend!</body></note>" expect(dom.parseXML(str).nodeType).eq(9)//[object XMLDocument] }, "String2":function(){ expect($$("aaabbbcc").contains("bbb")).ok(); expect($$('http://index').startsWith('http')).ok(); expect($$('image.gif').endsWith('.gif')).ok(); expect($$('image.gif').endsWith('.GIF')).ng(); expect($$('image.gif').endsWith(".GIF",true)).ok(); expect($$('司徒正美').byteLen()).eq(8); expect($$('').empty()).ok(); expect($$(' ').empty()).ng(); expect($$(' ').blank()).ok(); expect($$('').blank()).ok(); expect($$('\n').blank()).ok(); expect($$(' a').blank()).ng(); expect($$("this is a test test").truncate(10)).eq("this is..."); expect($$("foo-bar").camelize()).eq("fooBar"); expect($$("boo boo boo").capitalize()).eq("Boo boo boo"); expect($$("fooBar").underscored()).eq("foo_bar"); expect($$("foo-bar").underscored()).eq("foo_bar"); expect($$("10.23").toInt()).eq(10); expect($$("1.23").toFloat()).eq(1.23); expect($$("animals.sheep[1]").escapeRegExp()).eq("animals\\.sheep\\[1\\]"); expect($$("2").padLeft(4)).eq("0002"); expect($$("2").padRight(4," ")).eq("2 "); expect($$("ruby").times(2)).eq("rubyruby"); }, "Array2":function(){ var a = ["aaa",1,2,undefined,3,4,null,{ 2:2 }]; //复制一个副本 var b = $$(a).clone(); expect(a).same(b);//1 expect(a).not(b);//2 expect($$(a).first()).eq('aaa');//3 expect($$(a).first(function(el){//4 return el >1 })).eq(2); expect($$(a).last()).same({//5 2:2 }); expect($$(a).last(function(el){//6 return el >1 })).eq(4); expect($$(a).contains(2)).ok();//7 expect($$(a).diff([1,2,3])).same(["aaa",undefined,4,null,{ 2:2 }]); expect($$(b).remove(1)).same([1]); expect($$(b).removeAt(1)).same([2]); expect($$(a).shuffle()).log(); expect($$(a).random()).log(); expect($$(a).compact()).same(["aaa",1,2,3,4,{ 2:2 }]); expect($$(a).ensure(3,4,5)).same(["aaa",1,2,undefined,3,4,null,{ 2:2 },5]); var c = [3,4,6,1,45,9,5,3,4,22,3]; expect($$(c).min()).eq(1); expect($$(c).max()).eq(45); expect($$(c).unique()).same([6,1,45,9,5,4,22,3]); expect($$([1, 2, 1, 3, 1, 4]).unique()).same([2,3,1,4]); var d =['frank', ['bob', 'lisa'], ['jill', ['tom', 'sally']]]; expect($$(d).flatten()).same(['frank', 'bob', 'lisa', 'jill', 'tom', 'sally']); var e = ['hello', 'world', 'this', 'is', 'nice']; expect($$(e).pluck("length")).same([5, 5, 4, 2, 4]); expect($$(e).sortBy(function(s) { return s.length; })).same(["is","this","nice","hello","world"]); var f = [0,1,2,9]; var g = [0,5,2]; expect($$(f).diff(g)).same([1,9]); var h = [1,2,3]; h = $$(h).union([2,3,4,5,6]);//取并集 expect(h).same([1,2,3,4,5,6]); var j = [1, 2, 3, "a"]; j = $$(j).intersect([1, "a", 2]);//取交集 expect(j).same([1, 2, "a"]); }, "Number2" : function(){ var a = []; $$(8).downto(4, function(i) { a.push(i); }); expect(a).same([8,7,6,5,4]); expect($$(4.444).round(1)).eq(4.4); expect($$(4.444).round(2)).eq(4.44); expect($$(4.444).round(3)).eq(4.444); expect($$(7).constrain(3,5)).eq(5); expect($$(4).constrain(3,5)).eq(4); expect($$(4).nearer(3,9)).eq(3); expect($$(454).nearer(-4543,6576)).eq(-4543); }, "Object2":function(){ var a = { a:"one", b:"two", c:"three" }; expect($$(a).subset(["a","c"])).same({ a: 'one', c: 'three' }); a = { first: 'Sunday', second: 'Monday', third: 'Tuesday' }; var b = []; $$(a).forEach(function(value,key){ b.push(value); }); expect(b).same(["Sunday","Monday","Tuesday"]); a = { e:1, b:"aaa", c:[1,2,3], d:{ bb:"bb" } }; expect($$(a).clone()).same(a); var obj1 = { a: 0, b: 1 }; var obj2 = { c: 2, d: 3 }; var obj3 = { a: 4, d: 5 }; var merged = $$(obj1).merge(obj2, obj3); expect(obj1).same(merged); var nestedObj1 = { a: { b: 1, c: 1 } }; var nestedObj2 = { a: { b: 2 } }; var nested = $$(nestedObj1).merge(nestedObj2); expect(nested).same({ a: { b: 2, c: 1 } }); a = { a:1, b:2, c:3 }; expect($$(a).without("a")).same({ b:2, c:3 }); } }); });
相关链接