├── images └── wtf.jpg ├── LICENSE ├── index.html ├── javascripts ├── cronwtf.js ├── diff_match_patch.js ├── mootools.js └── jsspec.js ├── stylesheets └── jsspec.css └── test.html /images/wtf.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raster/cronwtf/master/images/wtf.jpg -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2008-* Rick Olson 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CronWTF! 6 | 7 | 8 | 26 | 45 | 46 | 47 |
48 |
WTF?
49 |
50 |
51 | 52 |
    53 |

    instructions, tests, code

    54 |
    55 |
    56 |
    57 | 58 | -------------------------------------------------------------------------------- /javascripts/cronwtf.js: -------------------------------------------------------------------------------- 1 | var CronWTF = { 2 | // parse multiple cron lines, returns an array of messages. 3 | parse: function(s) { 4 | lines = s.split("\n") 5 | messages = [] 6 | len = lines.length 7 | for(i = 0; i < len; i++) { 8 | var line = lines[i] 9 | if(line.length > 0 && !line.match(/^#/)) 10 | messages.push(this.entry(line).message) 11 | } 12 | return messages 13 | }, 14 | 15 | // parses a single cron line, returns an object 16 | entry: function(line) { 17 | pieces = line.replace(/^\s+|\s+$/g, '').split(/\s+/) 18 | e = { 19 | minutes: this.parseAttribute(pieces[0], 60), 20 | hours: this.parseAttribute(pieces[1], 24), 21 | days: this.parseAttribute(pieces[2], 31), 22 | months: this.parseAttribute(pieces[3], 12), 23 | week_days: this.parseAttribute(pieces[4], 8), 24 | command: pieces.slice(5, pieces.length).join(" ") 25 | } 26 | e.message = this.generateMessage(e); 27 | return e; 28 | }, 29 | 30 | // parses an individual time attribute into an array of numbers. 31 | // * - every increment (returns '*') 32 | // \d+ - that value 33 | // 1,2,3 - those values 34 | // 1-3 - range of values 35 | // */3 - steps 36 | parseAttribute: function(value, upperBound) { 37 | if(value == '*') return value; 38 | 39 | if(value.match(/^\*\/\d+$/)) { 40 | step = parseInt(value.match(/^\*\/(\d+)$/)[1]) 41 | range = [] 42 | for(i = 0; i < upperBound; i++) { 43 | if(i % step == 0) range.push(i) 44 | } 45 | return range 46 | } 47 | 48 | if(value.match(/^\d+\-\d+$/)) { 49 | matches = value.match(/^(\d+)\-(\d+)$/) 50 | lower = parseInt(matches[1]) 51 | upper = parseInt(matches[2]) 52 | range = [] 53 | for(var i = lower; i <= upper; i++) { 54 | range.push(i); 55 | } 56 | return range 57 | } 58 | 59 | return value.split(",") 60 | }, 61 | 62 | // on minute :00, every hour, on months July, August, every week day 63 | generateMessage: function(entry) { 64 | var attribs = ['minute', 'hour', 'day', 'month', 'week_day'] 65 | var attribLen = attribs.length; 66 | var msg = [] 67 | for(var i = 0; i < attribLen; i++) { 68 | var key = attribs[i] + 's' 69 | var prev = msg[msg.length -1] 70 | var values = entry[key] 71 | if(values == '*') { 72 | if(!prev || !prev.match(/^every/)) 73 | msg.push("every " + attribs[i].replace('_', ' ')) 74 | } else { 75 | func = this[key + 'Message'] 76 | if(func) msg.push(func(values)) 77 | } 78 | } 79 | return "Runs `" + entry.command + "` " + msg.join(", ") + "." 80 | }, 81 | 82 | minutesMessage: function(values) { 83 | var m = 'at minute' 84 | var v = [] 85 | var len = values.length; 86 | for(var j = 0; j < len; j++) { 87 | num = values[j].toString(); 88 | if(num.length == 1) num = "0" + num 89 | v.push(":" + num) 90 | } 91 | if(len > 1) m += 's' 92 | return m + " " + v.join(", ") 93 | }, 94 | 95 | hoursMessage: function(values) { 96 | var m = 'on hour' 97 | if(values.length > 1) m += 's' 98 | return m + " " + values.join(", ") 99 | }, 100 | 101 | daysMessage: function(values) { 102 | var m = 'on day' 103 | if(values.length > 1) m += 's' 104 | return m + " " + values.join(", ") 105 | }, 106 | 107 | months: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], 108 | monthsMessage: function(values) { 109 | var v = [] 110 | var len = values.length; 111 | for(var j = 0; j < len; j++) { 112 | v.push(CronWTF.months[values[j]]) 113 | } 114 | return "in " + v.join(", ") 115 | }, 116 | 117 | week_days: ['Sun', 'Mon', "Tue", 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], 118 | week_daysMessage: function(values) { 119 | var v = [] 120 | var len = values.length; 121 | for(var j = 0; j < len; j++) { 122 | v.push(CronWTF.week_days[values[j]]) 123 | } 124 | return "on " + v.join(", ") 125 | } 126 | } -------------------------------------------------------------------------------- /stylesheets/jsspec.css: -------------------------------------------------------------------------------- 1 | @CHARSET "UTF-8"; 2 | 3 | /* -------------------- 4 | * @Layout 5 | */ 6 | 7 | html { 8 | overflow: hidden; 9 | } 10 | 11 | body, #jsspec_container { 12 | overflow: hidden; 13 | padding: 0; 14 | margin: 0; 15 | width: 100%; 16 | height: 100%; 17 | } 18 | 19 | #title { 20 | padding: 0; 21 | margin: 0; 22 | position: absolute; 23 | top: 0px; 24 | left: 0px; 25 | width: 100%; 26 | height: 40px; 27 | overflow: hidden; 28 | } 29 | 30 | #list { 31 | padding: 0; 32 | margin: 0; 33 | position: absolute; 34 | top: 40px; 35 | left: 0px; 36 | bottom: 0px; 37 | overflow: auto; 38 | width: 250px; 39 | _height:expression(document.body.clientHeight-40); 40 | } 41 | 42 | #log { 43 | padding: 0; 44 | margin: 0; 45 | position: absolute; 46 | top: 40px; 47 | left: 250px; 48 | right: 0px; 49 | bottom: 0px; 50 | overflow: auto; 51 | _height:expression(document.body.clientHeight-40); 52 | _width:expression(document.body.clientWidth-250); 53 | } 54 | 55 | 56 | 57 | /* -------------------- 58 | * @Decorations and colors 59 | */ 60 | * { 61 | padding: 0; 62 | margin: 0; 63 | font-family: "Lucida Grande", Helvetica, sans-serif; 64 | } 65 | 66 | li { 67 | list-style: none; 68 | } 69 | 70 | /* hiding subtitles */ 71 | h2 { 72 | display: none; 73 | } 74 | 75 | /* title section */ 76 | div#title { 77 | padding: 0em 0.5em; 78 | } 79 | 80 | div#title h1 { 81 | font-size: 1.5em; 82 | float: left; 83 | } 84 | 85 | div#title ul li { 86 | float: left; 87 | padding: 0.5em 0em 0.5em 0.75em; 88 | } 89 | 90 | div#title p { 91 | float:right; 92 | margin-right:1em; 93 | font-size: 0.75em; 94 | } 95 | 96 | /* spec container */ 97 | ul.specs { 98 | margin: 0.5em; 99 | } 100 | ul.specs li { 101 | margin-bottom: 0.1em; 102 | } 103 | 104 | /* spec title */ 105 | ul.specs li h3 { 106 | font-weight: bold; 107 | font-size: 0.75em; 108 | padding: 0.2em 1em; 109 | } 110 | 111 | /* example container */ 112 | ul.examples li { 113 | border-style: solid; 114 | border-width: 1px 1px 1px 5px; 115 | margin: 0.2em 0em 0.2em 1em; 116 | } 117 | 118 | /* example title */ 119 | ul.examples li h4 { 120 | font-weight: normal; 121 | font-size: 0.75em; 122 | margin-left: 1em; 123 | } 124 | 125 | /* example explaination */ 126 | ul.examples li div { 127 | padding: 1em 2em; 128 | font-size: 0.75em; 129 | } 130 | 131 | /* styles for ongoing, success, failure, error */ 132 | div.success, div.success a { 133 | color: #FFFFFF; 134 | background-color: #65C400; 135 | } 136 | 137 | ul.specs li.success h3, ul.specs li.success h3 a { 138 | color: #FFFFFF; 139 | background-color: #65C400; 140 | } 141 | 142 | ul.examples li.success, ul.examples li.success a { 143 | color: #3D7700; 144 | background-color: #DBFFB4; 145 | border-color: #65C400; 146 | } 147 | 148 | div.exception, div.exception a { 149 | color: #FFFFFF; 150 | background-color: #C20000; 151 | } 152 | 153 | ul.specs li.exception h3, ul.specs li.exception h3 a { 154 | color: #FFFFFF; 155 | background-color: #C20000; 156 | } 157 | 158 | ul.examples li.exception, ul.examples li.exception a { 159 | color: #C20000; 160 | background-color: #FFFBD3; 161 | border-color: #C20000; 162 | } 163 | 164 | div.ongoing, div.ongoing a { 165 | color: #000000; 166 | background-color: #FFFF80; 167 | } 168 | 169 | ul.specs li.ongoing h3, ul.specs li.ongoing h3 a { 170 | color: #000000; 171 | background-color: #FFFF80; 172 | } 173 | 174 | ul.examples li.ongoing, ul.examples li.ongoing a { 175 | color: #000000; 176 | background-color: #FFFF80; 177 | border-color: #DDDD00; 178 | } 179 | 180 | 181 | 182 | /* -------------------- 183 | * values 184 | */ 185 | .number_value, .string_value, .regexp_value, .boolean_value, .dom_value { 186 | font-family: monospace; 187 | color: blue; 188 | } 189 | .object_value, .array_value { 190 | line-height: 2em; 191 | padding: 0.1em 0.2em; 192 | margin: 0.1em 0; 193 | } 194 | .date_value { 195 | font-family: monospace; 196 | color: olive; 197 | } 198 | .undefined_value, .null_value { 199 | font-style: italic; 200 | color: blue; 201 | } 202 | .dom_attr_name { 203 | } 204 | .dom_attr_value { 205 | color: red; 206 | } 207 | .dom_path { 208 | font-size: 0.75em; 209 | color: gray; 210 | } 211 | strong { 212 | font-weight: normal; 213 | background-color: #FFC6C6; 214 | } -------------------------------------------------------------------------------- /test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CronWTF: JSSpec results 6 | 7 | 8 | 9 | 10 | 152 | 153 |

    A

    B

    154 | -------------------------------------------------------------------------------- /javascripts/diff_match_patch.js: -------------------------------------------------------------------------------- 1 | function diff_match_patch(){this.Diff_Timeout=1.0;this.Diff_EditCost=4;this.Diff_DualThreshold=32;this.Match_Balance=0.5;this.Match_Threshold=0.5;this.Match_MinLength=100;this.Match_MaxLength=1000;this.Patch_Margin=4;function getMaxBits(){var maxbits=0;var oldi=1;var newi=2;while(oldi!=newi){maxbits++;oldi=newi;newi=newi<<1}return maxbits}this.Match_MaxBits=getMaxBits()}var DIFF_DELETE=-1;var DIFF_INSERT=1;var DIFF_EQUAL=0;diff_match_patch.prototype.diff_main=function(text1,text2,opt_checklines){if(text1==text2){return[[DIFF_EQUAL,text1]]}if(typeof opt_checklines=='undefined'){opt_checklines=true}var checklines=opt_checklines;var commonlength=this.diff_commonPrefix(text1,text2);var commonprefix=text1.substring(0,commonlength);text1=text1.substring(commonlength);text2=text2.substring(commonlength);commonlength=this.diff_commonSuffix(text1,text2);var commonsuffix=text1.substring(text1.length-commonlength);text1=text1.substring(0,text1.length-commonlength);text2=text2.substring(0,text2.length-commonlength);var diffs=this.diff_compute(text1,text2,checklines);if(commonprefix){diffs.unshift([DIFF_EQUAL,commonprefix])}if(commonsuffix){diffs.push([DIFF_EQUAL,commonsuffix])}this.diff_cleanupMerge(diffs);return diffs};diff_match_patch.prototype.diff_compute=function(text1,text2,checklines){var diffs;if(!text1){return[[DIFF_INSERT,text2]]}if(!text2){return[[DIFF_DELETE,text1]]}var longtext=text1.length>text2.length?text1:text2;var shorttext=text1.length>text2.length?text2:text1;var i=longtext.indexOf(shorttext);if(i!=-1){diffs=[[DIFF_INSERT,longtext.substring(0,i)],[DIFF_EQUAL,shorttext],[DIFF_INSERT,longtext.substring(i+shorttext.length)]];if(text1.length>text2.length){diffs[0][0]=diffs[2][0]=DIFF_DELETE}return diffs}longtext=shorttext=null;var hm=this.diff_halfMatch(text1,text2);if(hm){var text1_a=hm[0];var text1_b=hm[1];var text2_a=hm[2];var text2_b=hm[3];var mid_common=hm[4];var diffs_a=this.diff_main(text1_a,text2_a,checklines);var diffs_b=this.diff_main(text1_b,text2_b,checklines);return diffs_a.concat([[DIFF_EQUAL,mid_common]],diffs_b)}if(checklines&&text1.length+text2.length<250){checklines=false}var linearray;if(checklines){var a=this.diff_linesToChars(text1,text2);text1=a[0];text2=a[1];linearray=a[2]}diffs=this.diff_map(text1,text2);if(!diffs){diffs=[[DIFF_DELETE,text1],[DIFF_INSERT,text2]]}if(checklines){this.diff_charsToLines(diffs,linearray);this.diff_cleanupSemantic(diffs);diffs.push([DIFF_EQUAL,'']);var pointer=0;var count_delete=0;var count_insert=0;var text_delete='';var text_insert='';while(pointer=1&&count_insert>=1){var a=this.diff_main(text_delete,text_insert,false);diffs.splice(pointer-count_delete-count_insert,count_delete+count_insert);pointer=pointer-count_delete-count_insert;for(var j=a.length-1;j>=0;j--){diffs.splice(pointer,0,a[j])}pointer=pointer+a.length}count_insert=0;count_delete=0;text_delete='';text_insert=''}pointer++}diffs.pop()}return diffs};diff_match_patch.prototype.diff_linesToChars=function(text1,text2){var linearray=[];var linehash={};linearray.push('');function diff_linesToCharsMunge(text){var chars='';while(text){var i=text.indexOf('\n');if(i==-1){i=text.length}var line=text.substring(0,i+1);text=text.substring(i+1);if(linehash.hasOwnProperty?linehash.hasOwnProperty(line):(linehash[line]!==undefined)){chars+=String.fromCharCode(linehash[line])}else{linearray.push(line);linehash[line]=linearray.length-1;chars+=String.fromCharCode(linearray.length-1)}}return chars}var chars1=diff_linesToCharsMunge(text1);var chars2=diff_linesToCharsMunge(text2);return[chars1,chars2,linearray]};diff_match_patch.prototype.diff_charsToLines=function(diffs,linearray){for(var x=0;x0&&(new Date()).getTime()>ms_end){return null}v_map1[d]={};for(var k=-d;k<=d;k+=2){if(k==-d||k!=d&&v1[k-1]=0;d--){while(1){if(v_map[d].hasOwnProperty?v_map[d].hasOwnProperty((x-1)+','+y):(v_map[d][(x-1)+','+y]!==undefined)){x--;if(last_op===DIFF_DELETE){path[0][1]=text1.charAt(x)+path[0][1]}else{path.unshift([DIFF_DELETE,text1.charAt(x)])}last_op=DIFF_DELETE;break}else if(v_map[d].hasOwnProperty?v_map[d].hasOwnProperty(x+','+(y-1)):(v_map[d][x+','+(y-1)]!==undefined)){y--;if(last_op===DIFF_INSERT){path[0][1]=text2.charAt(y)+path[0][1]}else{path.unshift([DIFF_INSERT,text2.charAt(y)])}last_op=DIFF_INSERT;break}else{x--;y--;if(last_op===DIFF_EQUAL){path[0][1]=text1.charAt(x)+path[0][1]}else{path.unshift([DIFF_EQUAL,text1.charAt(x)])}last_op=DIFF_EQUAL}}}return path};diff_match_patch.prototype.diff_path2=function(v_map,text1,text2){var path=[];var x=text1.length;var y=text2.length;var last_op=null;for(var d=v_map.length-2;d>=0;d--){while(1){if(v_map[d].hasOwnProperty?v_map[d].hasOwnProperty((x-1)+','+y):(v_map[d][(x-1)+','+y]!==undefined)){x--;if(last_op===DIFF_DELETE){path[path.length-1][1]+=text1.charAt(text1.length-x-1)}else{path.push([DIFF_DELETE,text1.charAt(text1.length-x-1)])}last_op=DIFF_DELETE;break}else if(v_map[d].hasOwnProperty?v_map[d].hasOwnProperty(x+','+(y-1)):(v_map[d][x+','+(y-1)]!==undefined)){y--;if(last_op===DIFF_INSERT){path[path.length-1][1]+=text2.charAt(text2.length-y-1)}else{path.push([DIFF_INSERT,text2.charAt(text2.length-y-1)])}last_op=DIFF_INSERT;break}else{x--;y--;if(last_op===DIFF_EQUAL){path[path.length-1][1]+=text1.charAt(text1.length-x-1)}else{path.push([DIFF_EQUAL,text1.charAt(text1.length-x-1)])}last_op=DIFF_EQUAL}}}return path};diff_match_patch.prototype.diff_commonPrefix=function(text1,text2){if(!text1||!text2||text1.charCodeAt(0)!==text2.charCodeAt(0)){return 0}var pointermin=0;var pointermax=Math.min(text1.length,text2.length);var pointermid=pointermax;var pointerstart=0;while(pointermintext2.length?text1:text2;var shorttext=text1.length>text2.length?text2:text1;if(longtext.length<10||shorttext.length<1){return null}var dmp=this;function diff_halfMatchI(longtext,shorttext,i){var seed=longtext.substring(i,i+Math.floor(longtext.length/4));var j=-1;var best_common='';var best_longtext_a,best_longtext_b,best_shorttext_a,best_shorttext_b;while((j=shorttext.indexOf(seed,j+1))!=-1){var prefixLength=dmp.diff_commonPrefix(longtext.substring(i),shorttext.substring(j));var suffixLength=dmp.diff_commonSuffix(longtext.substring(0,i),shorttext.substring(0,j));if(best_common.length=longtext.length/2){return[best_longtext_a,best_longtext_b,best_shorttext_a,best_shorttext_b,best_common]}else{return null}}var hm1=diff_halfMatchI(longtext,shorttext,Math.ceil(longtext.length/4));var hm2=diff_halfMatchI(longtext,shorttext,Math.ceil(longtext.length/2));var hm;if(!hm1&&!hm2){return null}else if(!hm2){hm=hm1}else if(!hm1){hm=hm2}else{hm=hm1[4].length>hm2[4].length?hm1:hm2}var text1_a,text1_b,text2_a,text2_b;if(text1.length>text2.length){text1_a=hm[0];text1_b=hm[1];text2_a=hm[2];text2_b=hm[3]}else{text2_a=hm[0];text2_b=hm[1];text1_a=hm[2];text1_b=hm[3]}var mid_common=hm[4];return[text1_a,text1_b,text2_a,text2_b,mid_common]};diff_match_patch.prototype.diff_cleanupSemantic=function(diffs){var changes=false;var equalities=[];var lastequality=null;var pointer=0;var length_changes1=0;var length_changes2=0;while(pointer=bestScore){bestScore=score;bestEquality1=equality1;bestEdit=edit;bestEquality2=equality2}}if(diffs[pointer-1][1]!=bestEquality1){diffs[pointer-1][1]=bestEquality1;diffs[pointer][1]=bestEdit;diffs[pointer+1][1]=bestEquality2}}pointer++}};diff_match_patch.prototype.diff_cleanupEfficiency=function(diffs){var changes=false;var equalities=[];var lastequality='';var pointer=0;var pre_ins=false;var pre_del=false;var post_ins=false;var post_del=false;while(pointer0&&diffs[pointer-count_delete-count_insert-1][0]==DIFF_EQUAL){diffs[pointer-count_delete-count_insert-1][1]+=text_insert.substring(0,commonlength)}else{diffs.splice(0,0,[DIFF_EQUAL,text_insert.substring(0,commonlength)]);pointer++}text_insert=text_insert.substring(commonlength);text_delete=text_delete.substring(commonlength)}commonlength=this.diff_commonSuffix(text_insert,text_delete);if(commonlength!==0){diffs[pointer][1]=text_insert.substring(text_insert.length-commonlength)+diffs[pointer][1];text_insert=text_insert.substring(0,text_insert.length-commonlength);text_delete=text_delete.substring(0,text_delete.length-commonlength)}}if(count_delete===0){diffs.splice(pointer-count_delete-count_insert,count_delete+count_insert,[DIFF_INSERT,text_insert])}else if(count_insert===0){diffs.splice(pointer-count_delete-count_insert,count_delete+count_insert,[DIFF_DELETE,text_delete])}else{diffs.splice(pointer-count_delete-count_insert,count_delete+count_insert,[DIFF_DELETE,text_delete],[DIFF_INSERT,text_insert])}pointer=pointer-count_delete-count_insert+(count_delete?1:0)+(count_insert?1:0)+1}else if(pointer!==0&&diffs[pointer-1][0]==DIFF_EQUAL){diffs[pointer-1][1]+=diffs[pointer][1];diffs.splice(pointer,1)}else{pointer++}count_insert=0;count_delete=0;text_delete='';text_insert=''}}if(diffs[diffs.length-1][1]===''){diffs.pop()}var changes=false;pointer=1;while(pointerloc){break}last_chars1=chars1;last_chars2=chars2}if(diffs.length!=x&&diffs[x][0]===DIFF_DELETE){return last_chars2}return last_chars2+(loc-last_chars1)};diff_match_patch.prototype.diff_prettyHtml=function(diffs){this.diff_addIndex(diffs);var html='';for(var x=0;x/g,'>');t=t.replace(/\n/g,'¶
    ');if(m===DIFF_DELETE){html+=''+t+''}else if(m===DIFF_INSERT){html+=''+t+''}else{html+=''+t+''}}return html};diff_match_patch.prototype.match_main=function(text,pattern,loc){loc=Math.max(0,Math.min(loc,text.length-pattern.length));if(text==pattern){return 0}else if(text.length===0){return null}else if(text.substring(loc,loc+pattern.length)==pattern){return loc}else{return this.match_bitap(text,pattern,loc)}};diff_match_patch.prototype.match_bitap=function(text,pattern,loc){if(pattern.length>this.Match_MaxBits){return alert('Pattern too long for this browser.')}var s=this.match_alphabet(pattern);var score_text_length=text.length;score_text_length=Math.max(score_text_length,this.Match_MinLength);score_text_length=Math.min(score_text_length,this.Match_MaxLength);var dmp=this;function match_bitapScore(e,x){var d=Math.abs(loc-x);return(e/pattern.length/dmp.Match_Balance)+(d/score_text_length/(1.0-dmp.Match_Balance))}var score_threshold=this.Match_Threshold;var best_loc=text.indexOf(pattern,loc);if(best_loc!=-1){score_threshold=Math.min(match_bitapScore(0,best_loc),score_threshold)}best_loc=text.lastIndexOf(pattern,loc+pattern.length);if(best_loc!=-1){score_threshold=Math.min(match_bitapScore(0,best_loc),score_threshold)}var matchmask=1<<(pattern.length-1);best_loc=null;var bin_min,bin_mid;var bin_max=Math.max(loc+loc,text.length);var last_rd;for(var d=0;d=start;j--){if(d===0){rd[j]=((rd[j+1]<<1)|1)&s[text.charAt(j)]}else{rd[j]=((rd[j+1]<<1)|1)&s[text.charAt(j)]|((last_rd[j+1]<<1)|1)|((last_rd[j]<<1)|1)|last_rd[j+1]}if(rd[j]&matchmask){var score=match_bitapScore(d,j);if(score<=score_threshold){score_threshold=score;best_loc=j;if(j>loc){start=Math.max(0,loc-(j-loc))}else{break}}}}if(match_bitapScore(d+1,loc)>score_threshold){break}last_rd=rd}return best_loc};diff_match_patch.prototype.match_alphabet=function(pattern){var s=Object();for(var i=0;i2){this.diff_cleanupSemantic(diffs);this.diff_cleanupEfficiency(diffs)}}if(diffs.length===0){return[]}var patches=[];var patch=new patch_obj();var char_count1=0;var char_count2=0;var prepatch_text=text1;var postpatch_text=text1;for(var x=0;x=2*this.Patch_Margin){if(patch.diffs.length!==0){this.patch_addContext(patch,prepatch_text);patches.push(patch);patch=new patch_obj();prepatch_text=postpatch_text}}if(diff_type!==DIFF_INSERT){char_count1+=diff_text.length}if(diff_type!==DIFF_DELETE){char_count2+=diff_text.length}}if(patch.diffs.length!==0){this.patch_addContext(patch,prepatch_text);patches.push(patch)}return patches};diff_match_patch.prototype.patch_apply=function(patches,text){this.patch_splitMax(patches);var results=[];var delta=0;for(var x=0;xthis.Match_MaxBits){var bigpatch=patches[x];patches.splice(x,1);var patch_size=this.Match_MaxBits;var start1=bigpatch.start1;var start2=bigpatch.start2;var precontext='';while(bigpatch.diffs.length!==0){var patch=new patch_obj();var empty=true;patch.start1=start1-precontext.length;patch.start2=start2-precontext.length;if(precontext!==''){patch.length1=patch.length2=precontext.length;patch.diffs.push([DIFF_EQUAL,precontext])}while(bigpatch.diffs.length!==0&&patch.length1, My Object Oriented (JavaScript) Tools. Copyright (c) 2006-2008 Valerio Proietti, , MIT Style License. 2 | 3 | var MooTools={version:"1.2.1",build:"0d4845aab3d9a4fdee2f0d4a6dd59210e4b697cf"};var Native=function(K){K=K||{};var A=K.name;var I=K.legacy;var B=K.protect; 4 | var C=K.implement;var H=K.generics;var F=K.initialize;var G=K.afterImplement||function(){};var D=F||I;H=H!==false;D.constructor=Native;D.$family={name:"native"}; 5 | if(I&&F){D.prototype=I.prototype;}D.prototype.constructor=D;if(A){var E=A.toLowerCase();D.prototype.$family={name:E};Native.typize(D,E);}var J=function(N,L,O,M){if(!B||M||!N.prototype[L]){N.prototype[L]=O; 6 | }if(H){Native.genericize(N,L,B);}G.call(N,L,O);return N;};D.alias=function(N,L,O){if(typeof N=="string"){if((N=this.prototype[N])){return J(this,L,N,O); 7 | }}for(var M in N){this.alias(M,N[M],L);}return this;};D.implement=function(M,L,O){if(typeof M=="string"){return J(this,M,L,O);}for(var N in M){J(this,N,M[N],L); 8 | }return this;};if(C){D.implement(C);}return D;};Native.genericize=function(B,C,A){if((!A||!B[C])&&typeof B.prototype[C]=="function"){B[C]=function(){var D=Array.prototype.slice.call(arguments); 9 | return B.prototype[C].apply(D.shift(),D);};}};Native.implement=function(D,C){for(var B=0,A=D.length;B-1:this.indexOf(A)>-1;},trim:function(){return this.replace(/^\s+|\s+$/g,"");},clean:function(){return this.replace(/\s+/g," ").trim(); 65 | },camelCase:function(){return this.replace(/-\D/g,function(A){return A.charAt(1).toUpperCase();});},hyphenate:function(){return this.replace(/[A-Z]/g,function(A){return("-"+A.charAt(0).toLowerCase()); 66 | });},capitalize:function(){return this.replace(/\b[a-z]/g,function(A){return A.toUpperCase();});},escapeRegExp:function(){return this.replace(/([-.*+?^${}()|[\]\/\\])/g,"\\$1"); 67 | },toInt:function(A){return parseInt(this,A||10);},toFloat:function(){return parseFloat(this);},hexToRgb:function(B){var A=this.match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/); 68 | return(A)?A.slice(1).hexToRgb(B):null;},rgbToHex:function(B){var A=this.match(/\d{1,3}/g);return(A)?A.rgbToHex(B):null;},stripScripts:function(B){var A=""; 69 | var C=this.replace(/]*>([\s\S]*?)<\/script>/gi,function(){A+=arguments[1]+"\n";return"";});if(B===true){$exec(A);}else{if($type(B)=="function"){B(A,C); 70 | }}return C;},substitute:function(A,B){return this.replace(B||(/\\?\{([^{}]+)\}/g),function(D,C){if(D.charAt(0)=="\\"){return D.slice(1);}return(A[C]!=undefined)?A[C]:""; 71 | });}});Hash.implement({has:Object.prototype.hasOwnProperty,keyOf:function(B){for(var A in this){if(this.hasOwnProperty(A)&&this[A]===B){return A;}}return null; 72 | },hasValue:function(A){return(Hash.keyOf(this,A)!==null);},extend:function(A){Hash.each(A,function(C,B){Hash.set(this,B,C);},this);return this;},combine:function(A){Hash.each(A,function(C,B){Hash.include(this,B,C); 73 | },this);return this;},erase:function(A){if(this.hasOwnProperty(A)){delete this[A];}return this;},get:function(A){return(this.hasOwnProperty(A))?this[A]:null; 74 | },set:function(A,B){if(!this[A]||this.hasOwnProperty(A)){this[A]=B;}return this;},empty:function(){Hash.each(this,function(B,A){delete this[A];},this); 75 | return this;},include:function(B,C){var A=this[B];if(A==undefined){this[B]=C;}return this;},map:function(B,C){var A=new Hash;Hash.each(this,function(E,D){A.set(D,B.call(C,E,D,this)); 76 | },this);return A;},filter:function(B,C){var A=new Hash;Hash.each(this,function(E,D){if(B.call(C,E,D,this)){A.set(D,E);}},this);return A;},every:function(B,C){for(var A in this){if(this.hasOwnProperty(A)&&!B.call(C,this[A],A)){return false; 77 | }}return true;},some:function(B,C){for(var A in this){if(this.hasOwnProperty(A)&&B.call(C,this[A],A)){return true;}}return false;},getKeys:function(){var A=[]; 78 | Hash.each(this,function(C,B){A.push(B);});return A;},getValues:function(){var A=[];Hash.each(this,function(B){A.push(B);});return A;},toQueryString:function(A){var B=[]; 79 | Hash.each(this,function(F,E){if(A){E=A+"["+E+"]";}var D;switch($type(F)){case"object":D=Hash.toQueryString(F,E);break;case"array":var C={};F.each(function(H,G){C[G]=H; 80 | });D=Hash.toQueryString(C,E);break;default:D=E+"="+encodeURIComponent(F);}if(F!=undefined){B.push(D);}});return B.join("&");}});Hash.alias({keyOf:"indexOf",hasValue:"contains"}); 81 | var Event=new Native({name:"Event",initialize:function(A,F){F=F||window;var K=F.document;A=A||F.event;if(A.$extended){return A;}this.$extended=true;var J=A.type; 82 | var G=A.target||A.srcElement;while(G&&G.nodeType==3){G=G.parentNode;}if(J.test(/key/)){var B=A.which||A.keyCode;var M=Event.Keys.keyOf(B);if(J=="keydown"){var D=B-111; 83 | if(D>0&&D<13){M="f"+D;}}M=M||String.fromCharCode(B).toLowerCase();}else{if(J.match(/(click|mouse|menu)/i)){K=(!K.compatMode||K.compatMode=="CSS1Compat")?K.html:K.body; 84 | var I={x:A.pageX||A.clientX+K.scrollLeft,y:A.pageY||A.clientY+K.scrollTop};var C={x:(A.pageX)?A.pageX-F.pageXOffset:A.clientX,y:(A.pageY)?A.pageY-F.pageYOffset:A.clientY}; 85 | if(J.match(/DOMMouseScroll|mousewheel/)){var H=(A.wheelDelta)?A.wheelDelta/120:-(A.detail||0)/3;}var E=(A.which==3)||(A.button==2);var L=null;if(J.match(/over|out/)){switch(J){case"mouseover":L=A.relatedTarget||A.fromElement; 86 | break;case"mouseout":L=A.relatedTarget||A.toElement;}if(!(function(){while(L&&L.nodeType==3){L=L.parentNode;}return true;}).create({attempt:Browser.Engine.gecko})()){L=false; 87 | }}}}return $extend(this,{event:A,type:J,page:I,client:C,rightClick:E,wheel:H,relatedTarget:L,target:G,code:B,key:M,shift:A.shiftKey,control:A.ctrlKey,alt:A.altKey,meta:A.metaKey}); 88 | }});Event.Keys=new Hash({enter:13,up:38,down:40,left:37,right:39,esc:27,space:32,backspace:8,tab:9,"delete":46});Event.implement({stop:function(){return this.stopPropagation().preventDefault(); 89 | },stopPropagation:function(){if(this.event.stopPropagation){this.event.stopPropagation();}else{this.event.cancelBubble=true;}return this;},preventDefault:function(){if(this.event.preventDefault){this.event.preventDefault(); 90 | }else{this.event.returnValue=false;}return this;}});var Element=new Native({name:"Element",legacy:window.Element,initialize:function(A,B){var C=Element.Constructors.get(A); 91 | if(C){return C(B);}if(typeof A=="string"){return document.newElement(A,B);}return $(A).set(B);},afterImplement:function(A,B){Element.Prototype[A]=B;if(Array[A]){return ; 92 | }Elements.implement(A,function(){var C=[],G=true;for(var E=0,D=this.length;E";}return $.element(this.createElement(A)).set(B);},newTextNode:function(A){return this.createTextNode(A); 101 | },getDocument:function(){return this;},getWindow:function(){return this.window;}});Window.implement({$:function(B,C){if(B&&B.$family&&B.uid){return B;}var A=$type(B); 102 | return($[A])?$[A](B,C,this.document):null;},$$:function(A){if(arguments.length==1&&typeof A=="string"){return this.document.getElements(A);}var F=[];var C=Array.flatten(arguments); 103 | for(var D=0,B=C.length;D1);A.each(function(E){var F=this.getElementsByTagName(E.trim());(B)?C.extend(F):C=F;},this);return new Elements(C,{ddup:B,cash:!D}); 109 | }});(function(){var H={},F={};var I={input:"checked",option:"selected",textarea:(Browser.Engine.webkit&&Browser.Engine.version<420)?"innerHTML":"value"}; 110 | var C=function(L){return(F[L]||(F[L]={}));};var G=function(N,L){if(!N){return ;}var M=N.uid;if(Browser.Engine.trident){if(N.clearAttributes){var P=L&&N.cloneNode(false); 111 | N.clearAttributes();if(P){N.mergeAttributes(P);}}else{if(N.removeEvents){N.removeEvents();}}if((/object/i).test(N.tagName)){for(var O in N){if(typeof N[O]=="function"){N[O]=$empty; 112 | }}Element.dispose(N);}}if(!M){return ;}H[M]=F[M]=null;};var D=function(){Hash.each(H,G);if(Browser.Engine.trident){$A(document.getElementsByTagName("object")).each(G); 113 | }if(window.CollectGarbage){CollectGarbage();}H=F=null;};var J=function(N,L,S,M,P,R){var O=N[S||L];var Q=[];while(O){if(O.nodeType==1&&(!M||Element.match(O,M))){if(!P){return $(O,R); 114 | }Q.push(O);}O=O[L];}return(P)?new Elements(Q,{ddup:false,cash:!R}):null;};var E={html:"innerHTML","class":"className","for":"htmlFor",text:(Browser.Engine.trident||(Browser.Engine.webkit&&Browser.Engine.version<420))?"innerText":"textContent"}; 115 | var B=["compact","nowrap","ismap","declare","noshade","checked","disabled","readonly","multiple","selected","noresize","defer"];var K=["value","accessKey","cellPadding","cellSpacing","colSpan","frameBorder","maxLength","readOnly","rowSpan","tabIndex","useMap"]; 116 | Hash.extend(E,B.associate(B));Hash.extend(E,K.associate(K.map(String.toLowerCase)));var A={before:function(M,L){if(L.parentNode){L.parentNode.insertBefore(M,L); 117 | }},after:function(M,L){if(!L.parentNode){return ;}var N=L.nextSibling;(N)?L.parentNode.insertBefore(M,N):L.parentNode.appendChild(M);},bottom:function(M,L){L.appendChild(M); 118 | },top:function(M,L){var N=L.firstChild;(N)?L.insertBefore(M,N):L.appendChild(M);}};A.inside=A.bottom;Hash.each(A,function(L,M){M=M.capitalize();Element.implement("inject"+M,function(N){L(this,$(N,true)); 119 | return this;});Element.implement("grab"+M,function(N){L($(N,true),this);return this;});});Element.implement({set:function(O,M){switch($type(O)){case"object":for(var N in O){this.set(N,O[N]); 120 | }break;case"string":var L=Element.Properties.get(O);(L&&L.set)?L.set.apply(this,Array.slice(arguments,1)):this.setProperty(O,M);}return this;},get:function(M){var L=Element.Properties.get(M); 121 | return(L&&L.get)?L.get.apply(this,Array.slice(arguments,1)):this.getProperty(M);},erase:function(M){var L=Element.Properties.get(M);(L&&L.erase)?L.erase.apply(this):this.removeProperty(M); 122 | return this;},setProperty:function(M,N){var L=E[M];if(N==undefined){return this.removeProperty(M);}if(L&&B[M]){N=!!N;}(L)?this[L]=N:this.setAttribute(M,""+N); 123 | return this;},setProperties:function(L){for(var M in L){this.setProperty(M,L[M]);}return this;},getProperty:function(M){var L=E[M];var N=(L)?this[L]:this.getAttribute(M,2); 124 | return(B[M])?!!N:(L)?N:N||null;},getProperties:function(){var L=$A(arguments);return L.map(this.getProperty,this).associate(L);},removeProperty:function(M){var L=E[M]; 125 | (L)?this[L]=(L&&B[M])?false:"":this.removeAttribute(M);return this;},removeProperties:function(){Array.each(arguments,this.removeProperty,this);return this; 126 | },hasClass:function(L){return this.className.contains(L," ");},addClass:function(L){if(!this.hasClass(L)){this.className=(this.className+" "+L).clean(); 127 | }return this;},removeClass:function(L){this.className=this.className.replace(new RegExp("(^|\\s)"+L+"(?:\\s|$)"),"$1");return this;},toggleClass:function(L){return this.hasClass(L)?this.removeClass(L):this.addClass(L); 128 | },adopt:function(){Array.flatten(arguments).each(function(L){L=$(L,true);if(L){this.appendChild(L);}},this);return this;},appendText:function(M,L){return this.grab(this.getDocument().newTextNode(M),L); 129 | },grab:function(M,L){A[L||"bottom"]($(M,true),this);return this;},inject:function(M,L){A[L||"bottom"](this,$(M,true));return this;},replaces:function(L){L=$(L,true); 130 | L.parentNode.replaceChild(this,L);return this;},wraps:function(M,L){M=$(M,true);return this.replaces(M).grab(M,L);},getPrevious:function(L,M){return J(this,"previousSibling",null,L,false,M); 131 | },getAllPrevious:function(L,M){return J(this,"previousSibling",null,L,true,M);},getNext:function(L,M){return J(this,"nextSibling",null,L,false,M);},getAllNext:function(L,M){return J(this,"nextSibling",null,L,true,M); 132 | },getFirst:function(L,M){return J(this,"nextSibling","firstChild",L,false,M);},getLast:function(L,M){return J(this,"previousSibling","lastChild",L,false,M); 133 | },getParent:function(L,M){return J(this,"parentNode",null,L,false,M);},getParents:function(L,M){return J(this,"parentNode",null,L,true,M);},getChildren:function(L,M){return J(this,"nextSibling","firstChild",L,true,M); 134 | },getWindow:function(){return this.ownerDocument.window;},getDocument:function(){return this.ownerDocument;},getElementById:function(O,N){var M=this.ownerDocument.getElementById(O); 135 | if(!M){return null;}for(var L=M.parentNode;L!=this;L=L.parentNode){if(!L){return null;}}return $.element(M,N);},getSelected:function(){return new Elements($A(this.options).filter(function(L){return L.selected; 136 | }));},getComputedStyle:function(M){if(this.currentStyle){return this.currentStyle[M.camelCase()];}var L=this.getDocument().defaultView.getComputedStyle(this,null); 137 | return(L)?L.getPropertyValue([M.hyphenate()]):null;},toQueryString:function(){var L=[];this.getElements("input, select, textarea",true).each(function(M){if(!M.name||M.disabled){return ; 138 | }var N=(M.tagName.toLowerCase()=="select")?Element.getSelected(M).map(function(O){return O.value;}):((M.type=="radio"||M.type=="checkbox")&&!M.checked)?null:M.value; 139 | $splat(N).each(function(O){if(typeof O!="undefined"){L.push(M.name+"="+encodeURIComponent(O));}});});return L.join("&");},clone:function(O,L){O=O!==false; 140 | var R=this.cloneNode(O);var N=function(V,U){if(!L){V.removeAttribute("id");}if(Browser.Engine.trident){V.clearAttributes();V.mergeAttributes(U);V.removeAttribute("uid"); 141 | if(V.options){var W=V.options,S=U.options;for(var T=W.length;T--;){W[T].selected=S[T].selected;}}}var X=I[U.tagName.toLowerCase()];if(X&&U[X]){V[X]=U[X]; 142 | }};if(O){var P=R.getElementsByTagName("*"),Q=this.getElementsByTagName("*");for(var M=P.length;M--;){N(P[M],Q[M]);}}N(R,this);return $(R);},destroy:function(){Element.empty(this); 143 | Element.dispose(this);G(this,true);return null;},empty:function(){$A(this.childNodes).each(function(L){Element.destroy(L);});return this;},dispose:function(){return(this.parentNode)?this.parentNode.removeChild(this):this; 144 | },hasChild:function(L){L=$(L,true);if(!L){return false;}if(Browser.Engine.webkit&&Browser.Engine.version<420){return $A(this.getElementsByTagName(L.tagName)).contains(L); 145 | }return(this.contains)?(this!=L&&this.contains(L)):!!(this.compareDocumentPosition(L)&16);},match:function(L){return(!L||(L==this)||(Element.get(this,"tag")==L)); 146 | }});Native.implement([Element,Window,Document],{addListener:function(O,N){if(O=="unload"){var L=N,M=this;N=function(){M.removeListener("unload",N);L(); 147 | };}else{H[this.uid]=this;}if(this.addEventListener){this.addEventListener(O,N,false);}else{this.attachEvent("on"+O,N);}return this;},removeListener:function(M,L){if(this.removeEventListener){this.removeEventListener(M,L,false); 148 | }else{this.detachEvent("on"+M,L);}return this;},retrieve:function(M,L){var O=C(this.uid),N=O[M];if(L!=undefined&&N==undefined){N=O[M]=L;}return $pick(N); 149 | },store:function(M,L){var N=C(this.uid);N[M]=L;return this;},eliminate:function(L){var M=C(this.uid);delete M[L];return this;}});window.addListener("unload",D); 150 | })();Element.Properties=new Hash;Element.Properties.style={set:function(A){this.style.cssText=A;},get:function(){return this.style.cssText;},erase:function(){this.style.cssText=""; 151 | }};Element.Properties.tag={get:function(){return this.tagName.toLowerCase();}};Element.Properties.html=(function(){var C=document.createElement("div"); 152 | var A={table:[1,"","
    "],select:[1,""],tbody:[2,"","
    "],tr:[3,"","
    "]}; 153 | A.thead=A.tfoot=A.tbody;var B={set:function(){var E=Array.flatten(arguments).join("");var F=Browser.Engine.trident&&A[this.get("tag")];if(F){var G=C;G.innerHTML=F[1]+E+F[2]; 154 | for(var D=F[0];D--;){G=G.firstChild;}this.empty().adopt(G.childNodes);}else{this.innerHTML=E;}}};B.erase=B.set;return B;})();if(Browser.Engine.webkit&&Browser.Engine.version<420){Element.Properties.text={get:function(){if(this.innerText){return this.innerText; 155 | }var A=this.ownerDocument.newElement("div",{html:this.innerHTML}).inject(this.ownerDocument.body);var B=A.innerText;A.destroy();return B;}};}Element.Properties.events={set:function(A){this.addEvents(A); 156 | }};Native.implement([Element,Window,Document],{addEvent:function(E,G){var H=this.retrieve("events",{});H[E]=H[E]||{keys:[],values:[]};if(H[E].keys.contains(G)){return this; 157 | }H[E].keys.push(G);var F=E,A=Element.Events.get(E),C=G,I=this;if(A){if(A.onAdd){A.onAdd.call(this,G);}if(A.condition){C=function(J){if(A.condition.call(this,J)){return G.call(this,J); 158 | }return true;};}F=A.base||F;}var D=function(){return G.call(I);};var B=Element.NativeEvents[F];if(B){if(B==2){D=function(J){J=new Event(J,I.getWindow()); 159 | if(C.call(I,J)===false){J.stop();}};}this.addListener(F,D);}H[E].values.push(D);return this;},removeEvent:function(C,B){var A=this.retrieve("events");if(!A||!A[C]){return this; 160 | }var F=A[C].keys.indexOf(B);if(F==-1){return this;}A[C].keys.splice(F,1);var E=A[C].values.splice(F,1)[0];var D=Element.Events.get(C);if(D){if(D.onRemove){D.onRemove.call(this,B); 161 | }C=D.base||C;}return(Element.NativeEvents[C])?this.removeListener(C,E):this;},addEvents:function(A){for(var B in A){this.addEvent(B,A[B]);}return this; 162 | },removeEvents:function(A){if($type(A)=="object"){for(var C in A){this.removeEvent(C,A[C]);}return this;}var B=this.retrieve("events");if(!B){return this; 163 | }if(!A){for(var C in B){this.removeEvents(C);}this.eliminate("events");}else{if(B[A]){while(B[A].keys[0]){this.removeEvent(A,B[A].keys[0]);}B[A]=null;}}return this; 164 | },fireEvent:function(D,B,A){var C=this.retrieve("events");if(!C||!C[D]){return this;}C[D].keys.each(function(E){E.create({bind:this,delay:A,"arguments":B})(); 165 | },this);return this;},cloneEvents:function(D,A){D=$(D);var C=D.retrieve("events");if(!C){return this;}if(!A){for(var B in C){this.cloneEvents(D,B);}}else{if(C[A]){C[A].keys.each(function(E){this.addEvent(A,E); 166 | },this);}}return this;}});Element.NativeEvents={click:2,dblclick:2,mouseup:2,mousedown:2,contextmenu:2,mousewheel:2,DOMMouseScroll:2,mouseover:2,mouseout:2,mousemove:2,selectstart:2,selectend:2,keydown:2,keypress:2,keyup:2,focus:2,blur:2,change:2,reset:2,select:2,submit:2,load:1,unload:1,beforeunload:2,resize:1,move:1,DOMContentLoaded:1,readystatechange:1,error:1,abort:1,scroll:1}; 167 | (function(){var A=function(B){var C=B.relatedTarget;if(C==undefined){return true;}if(C===false){return false;}return($type(this)!="document"&&C!=this&&C.prefix!="xul"&&!this.hasChild(C)); 168 | };Element.Events=new Hash({mouseenter:{base:"mouseover",condition:A},mouseleave:{base:"mouseout",condition:A},mousewheel:{base:(Browser.Engine.gecko)?"DOMMouseScroll":"mousewheel"}}); 169 | })(); -------------------------------------------------------------------------------- /javascripts/jsspec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * JSSpec 3 | * 4 | * Copyright 2007 Alan Kang 5 | * - mailto:jania902@gmail.com 6 | * - http://jania.pe.kr 7 | * 8 | * http://jania.pe.kr/aw/moin.cgi/JSSpec 9 | * 10 | * Dependencies: 11 | * - diff_match_patch.js ( http://code.google.com/p/diff_match_patch ) 12 | * 13 | * This library is free software; you can redistribute it and/or 14 | * modify it under the terms of the GNU Lesser General Public 15 | * License as published by the Free Software Foundation; either 16 | * version 2.1 of the License, or (at your option) any later version. 17 | * 18 | * This library is distributed in the hope that it will be useful, 19 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 21 | * Lesser General Public License for more details. 22 | * 23 | * You should have received a copy of the GNU Lesser General Public 24 | * License along with this library; if not, write to the Free Software 25 | * Foundation, Inc, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 26 | */ 27 | 28 | /** 29 | * Namespace 30 | */ 31 | JSSpec = { 32 | specs: [], 33 | 34 | EMPTY_FUNCTION: function() {}, 35 | 36 | Browser: { 37 | Trident: navigator.appName == "Microsoft Internet Explorer", 38 | Webkit: navigator.userAgent.indexOf('AppleWebKit/') > -1, 39 | Gecko: navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1, 40 | Presto: navigator.appName == "Opera" 41 | } 42 | } 43 | 44 | 45 | 46 | /** 47 | * Executor 48 | */ 49 | JSSpec.Executor = function(target, onSuccess, onException) { 50 | this.target = target; 51 | this.onSuccess = typeof onSuccess == 'function' ? onSuccess : JSSpec.EMPTY_FUNCTION; 52 | this.onException = typeof onException == 'function' ? onException : JSSpec.EMPTY_FUNCTION; 53 | 54 | if(JSSpec.Browser.Trident) { 55 | // Exception handler for Trident. It helps to collect exact line number where exception occured. 56 | window.onerror = function(message, fileName, lineNumber) { 57 | var self = window._curExecutor; 58 | var ex = {message:message, fileName:fileName, lineNumber:lineNumber}; 59 | 60 | if(JSSpec._secondPass) { 61 | ex = self.mergeExceptions(JSSpec._assertionFailure, ex); 62 | delete JSSpec._secondPass; 63 | delete JSSpec._assertionFailure; 64 | 65 | ex.type = "failure"; 66 | self.onException(self, ex); 67 | } else if(JSSpec._assertionFailure) { 68 | JSSpec._secondPass = true; 69 | self.run(); 70 | } else { 71 | self.onException(self, ex); 72 | } 73 | 74 | return true; 75 | } 76 | } 77 | } 78 | JSSpec.Executor.prototype.mergeExceptions = function(assertionFailure, normalException) { 79 | var merged = { 80 | message:assertionFailure.message, 81 | fileName:normalException.fileName, 82 | lineNumber:normalException.lineNumber 83 | }; 84 | 85 | return merged; 86 | } 87 | JSSpec.Executor.prototype.run = function() { 88 | var self = this; 89 | var target = this.target; 90 | var onSuccess = this.onSuccess; 91 | var onException = this.onException; 92 | 93 | window.setTimeout( 94 | function() { 95 | if(JSSpec.Browser.Trident) { 96 | window._curExecutor = self; 97 | 98 | var result = self.target(); 99 | self.onSuccess(self, result); 100 | } else { 101 | try { 102 | var result = self.target(); 103 | self.onSuccess(self, result); 104 | } catch(ex) { 105 | if(JSSpec.Browser.Webkit) ex = {message:ex.message, fileName:ex.sourceURL, lineNumber:ex.line} 106 | 107 | if(JSSpec._secondPass) { 108 | ex = self.mergeExceptions(JSSpec._assertionFailure, ex); 109 | delete JSSpec._secondPass; 110 | delete JSSpec._assertionFailure; 111 | 112 | ex.type = "failure"; 113 | self.onException(self, ex); 114 | } else if(JSSpec._assertionFailure) { 115 | JSSpec._secondPass = true; 116 | self.run(); 117 | } else { 118 | self.onException(self, ex); 119 | } 120 | } 121 | } 122 | }, 123 | 0 124 | ); 125 | } 126 | 127 | 128 | 129 | /** 130 | * CompositeExecutor composites one or more executors and execute them sequencially. 131 | */ 132 | JSSpec.CompositeExecutor = function(onSuccess, onException, continueOnException) { 133 | this.queue = []; 134 | this.onSuccess = typeof onSuccess == 'function' ? onSuccess : JSSpec.EMPTY_FUNCTION; 135 | this.onException = typeof onException == 'function' ? onException : JSSpec.EMPTY_FUNCTION; 136 | this.continueOnException = !!continueOnException; 137 | } 138 | JSSpec.CompositeExecutor.prototype.addFunction = function(func) { 139 | this.addExecutor(new JSSpec.Executor(func)); 140 | } 141 | JSSpec.CompositeExecutor.prototype.addExecutor = function(executor) { 142 | var last = this.queue.length == 0 ? null : this.queue[this.queue.length - 1]; 143 | if(last) { 144 | last.next = executor; 145 | } 146 | 147 | executor.parent = this; 148 | executor.onSuccessBackup = executor.onSuccess; 149 | executor.onSuccess = function(result) { 150 | this.onSuccessBackup(result); 151 | if(this.next) { 152 | this.next.run() 153 | } else { 154 | this.parent.onSuccess(); 155 | } 156 | } 157 | executor.onExceptionBackup = executor.onException; 158 | executor.onException = function(executor, ex) { 159 | this.onExceptionBackup(executor, ex); 160 | 161 | if(this.parent.continueOnException) { 162 | if(this.next) { 163 | this.next.run() 164 | } else { 165 | this.parent.onSuccess(); 166 | } 167 | } else { 168 | this.parent.onException(executor, ex); 169 | } 170 | } 171 | 172 | this.queue.push(executor); 173 | } 174 | JSSpec.CompositeExecutor.prototype.run = function() { 175 | if(this.queue.length > 0) { 176 | this.queue[0].run(); 177 | } 178 | } 179 | 180 | 181 | 182 | /** 183 | * Spec is a set of Examples in a specific context 184 | */ 185 | JSSpec.Spec = function(context, entries) { 186 | this.id = JSSpec.Spec.id++; 187 | this.context = context; 188 | this.url = location.href; 189 | 190 | this.filterEntriesByEmbeddedExpressions(entries); 191 | this.extractOutSpecialEntries(entries); 192 | this.examples = this.makeExamplesFromEntries(entries); 193 | this.examplesMap = this.makeMapFromExamples(this.examples); 194 | } 195 | JSSpec.Spec.id = 0; 196 | JSSpec.Spec.prototype.getExamples = function() { 197 | return this.examples; 198 | } 199 | JSSpec.Spec.prototype.hasException = function() { 200 | return this.getTotalFailures() > 0 || this.getTotalErrors() > 0; 201 | } 202 | JSSpec.Spec.prototype.getTotalFailures = function() { 203 | var examples = this.examples; 204 | var failures = 0; 205 | for(var i = 0; i < examples.length; i++) { 206 | if(examples[i].isFailure()) failures++; 207 | } 208 | return failures; 209 | } 210 | JSSpec.Spec.prototype.getTotalErrors = function() { 211 | var examples = this.examples; 212 | var errors = 0; 213 | for(var i = 0; i < examples.length; i++) { 214 | if(examples[i].isError()) errors++; 215 | } 216 | return errors; 217 | } 218 | JSSpec.Spec.prototype.filterEntriesByEmbeddedExpressions = function(entries) { 219 | var isTrue; 220 | for(name in entries) { 221 | var m = name.match(/\[\[(.+)\]\]/); 222 | if(m && m[1]) { 223 | eval("isTrue = (" + m[1] + ")"); 224 | if(!isTrue) delete entries[name]; 225 | } 226 | } 227 | } 228 | JSSpec.Spec.prototype.extractOutSpecialEntries = function(entries) { 229 | this.beforeEach = JSSpec.EMPTY_FUNCTION; 230 | this.beforeAll = JSSpec.EMPTY_FUNCTION; 231 | this.afterEach = JSSpec.EMPTY_FUNCTION; 232 | this.afterAll = JSSpec.EMPTY_FUNCTION; 233 | 234 | for(name in entries) { 235 | if(name == 'before' || name == 'before each') { 236 | this.beforeEach = entries[name]; 237 | } else if(name == 'before all') { 238 | this.beforeAll = entries[name]; 239 | } else if(name == 'after' || name == 'after each') { 240 | this.afterEach = entries[name]; 241 | } else if(name == 'after all') { 242 | this.afterAll = entries[name]; 243 | } 244 | } 245 | 246 | delete entries['before']; 247 | delete entries['before each']; 248 | delete entries['before all']; 249 | delete entries['after']; 250 | delete entries['after each']; 251 | delete entries['after all']; 252 | } 253 | JSSpec.Spec.prototype.makeExamplesFromEntries = function(entries) { 254 | var examples = []; 255 | for(name in entries) { 256 | examples.push(new JSSpec.Example(name, entries[name], this.beforeEach, this.afterEach)); 257 | } 258 | return examples; 259 | } 260 | JSSpec.Spec.prototype.makeMapFromExamples = function(examples) { 261 | var map = {}; 262 | for(var i = 0; i < examples.length; i++) { 263 | var example = examples[i]; 264 | map[example.id] = examples[i]; 265 | } 266 | return map; 267 | } 268 | JSSpec.Spec.prototype.getExampleById = function(id) { 269 | return this.examplesMap[id]; 270 | } 271 | JSSpec.Spec.prototype.getExecutor = function() { 272 | var self = this; 273 | var onException = function(executor, ex) {self.exception = ex} 274 | 275 | var composite = new JSSpec.CompositeExecutor(); 276 | composite.addFunction(function() {JSSpec.log.onSpecStart(self)}); 277 | composite.addExecutor(new JSSpec.Executor(this.beforeAll, null, function(exec, ex) { 278 | self.exception = ex; 279 | JSSpec.log.onSpecEnd(self); 280 | })); 281 | 282 | var exampleAndAfter = new JSSpec.CompositeExecutor(null,null,true); 283 | for(var i = 0; i < this.examples.length; i++) { 284 | exampleAndAfter.addExecutor(this.examples[i].getExecutor()); 285 | } 286 | exampleAndAfter.addExecutor(new JSSpec.Executor(this.afterAll, null, onException)); 287 | exampleAndAfter.addFunction(function() {JSSpec.log.onSpecEnd(self)}); 288 | composite.addExecutor(exampleAndAfter); 289 | 290 | return composite; 291 | } 292 | 293 | 294 | 295 | /** 296 | * Example 297 | */ 298 | JSSpec.Example = function(name, target, before, after) { 299 | this.id = JSSpec.Example.id++; 300 | this.name = name; 301 | this.target = target; 302 | this.before = before; 303 | this.after = after; 304 | } 305 | JSSpec.Example.id = 0; 306 | JSSpec.Example.prototype.isFailure = function() { 307 | return this.exception && this.exception.type == "failure"; 308 | } 309 | JSSpec.Example.prototype.isError = function() { 310 | return this.exception && !this.exception.type; 311 | } 312 | JSSpec.Example.prototype.getExecutor = function() { 313 | var self = this; 314 | var onException = function(executor, ex) { 315 | self.exception = ex 316 | } 317 | 318 | var composite = new JSSpec.CompositeExecutor(); 319 | composite.addFunction(function() {JSSpec.log.onExampleStart(self)}); 320 | composite.addExecutor(new JSSpec.Executor(this.before, null, function(exec, ex) { 321 | self.exception = ex; 322 | JSSpec.log.onExampleEnd(self); 323 | })); 324 | 325 | var targetAndAfter = new JSSpec.CompositeExecutor(null,null,true); 326 | 327 | targetAndAfter.addExecutor(new JSSpec.Executor(this.target, null, onException)); 328 | targetAndAfter.addExecutor(new JSSpec.Executor(this.after, null, onException)); 329 | targetAndAfter.addFunction(function() {JSSpec.log.onExampleEnd(self)}); 330 | 331 | composite.addExecutor(targetAndAfter); 332 | 333 | return composite; 334 | } 335 | 336 | 337 | 338 | /** 339 | * Runner 340 | */ 341 | JSSpec.Runner = function(specs, logger) { 342 | JSSpec.log = logger; 343 | 344 | this.totalExamples = 0; 345 | this.specs = []; 346 | this.specsMap = {}; 347 | this.addAllSpecs(specs); 348 | } 349 | JSSpec.Runner.prototype.addAllSpecs = function(specs) { 350 | for(var i = 0; i < specs.length; i++) { 351 | this.addSpec(specs[i]); 352 | } 353 | } 354 | JSSpec.Runner.prototype.addSpec = function(spec) { 355 | this.specs.push(spec); 356 | this.specsMap[spec.id] = spec; 357 | this.totalExamples += spec.getExamples().length; 358 | } 359 | JSSpec.Runner.prototype.getSpecById = function(id) { 360 | return this.specsMap[id]; 361 | } 362 | JSSpec.Runner.prototype.getSpecByContext = function(context) { 363 | for(var i = 0; i < this.specs.length; i++) { 364 | if(this.specs[i].context == context) return this.specs[i]; 365 | } 366 | return null; 367 | } 368 | JSSpec.Runner.prototype.getSpecs = function() { 369 | return this.specs; 370 | } 371 | JSSpec.Runner.prototype.hasException = function() { 372 | return this.getTotalFailures() > 0 || this.getTotalErrors() > 0; 373 | } 374 | JSSpec.Runner.prototype.getTotalFailures = function() { 375 | var specs = this.specs; 376 | var failures = 0; 377 | for(var i = 0; i < specs.length; i++) { 378 | failures += specs[i].getTotalFailures(); 379 | } 380 | return failures; 381 | } 382 | JSSpec.Runner.prototype.getTotalErrors = function() { 383 | var specs = this.specs; 384 | var errors = 0; 385 | for(var i = 0; i < specs.length; i++) { 386 | errors += specs[i].getTotalErrors(); 387 | } 388 | return errors; 389 | } 390 | 391 | JSSpec.Runner.prototype.run = function() { 392 | JSSpec.log.onRunnerStart(); 393 | var executor = new JSSpec.CompositeExecutor(function() {JSSpec.log.onRunnerEnd()},null,true); 394 | for(var i = 0; i < this.specs.length; i++) { 395 | executor.addExecutor(this.specs[i].getExecutor()); 396 | } 397 | executor.run(); 398 | } 399 | 400 | JSSpec.Runner.prototype.rerun = function(context) { 401 | JSSpec.runner = new JSSpec.Runner([this.getSpecByContext(context)], JSSpec.log); 402 | JSSpec.runner.run(); 403 | } 404 | 405 | /** 406 | * Logger 407 | */ 408 | JSSpec.Logger = function() { 409 | this.finishedExamples = 0; 410 | this.startedAt = null; 411 | } 412 | 413 | JSSpec.Logger.prototype.onRunnerStart = function() { 414 | this.startedAt = new Date(); 415 | var container = document.getElementById('jsspec_container'); 416 | if(container) { 417 | container.innerHTML = ""; 418 | } else { 419 | container = document.createElement("DIV"); 420 | container.id = "jsspec_container"; 421 | document.body.appendChild(container); 422 | } 423 | 424 | var title = document.createElement("DIV"); 425 | title.id = "title"; 426 | title.innerHTML = [ 427 | '

    JSSpec runner

    ', 428 | '
      ', 429 | '
    • ' + JSSpec.runner.totalExamples + ' examples
    • ', 430 | '
    • 0 failures
    • ', 431 | '
    • 0 errors
    • ', 432 | '
    • 0% done
    • ', 433 | '
    • 0 secs
    • ', 434 | '
    ', 435 | '

    JSSpec homepage

    ', 436 | ].join(""); 437 | container.appendChild(title); 438 | 439 | var list = document.createElement("DIV"); 440 | list.id = "list"; 441 | list.innerHTML = [ 442 | '

    List

    ', 443 | '
      ', 444 | function() { 445 | var specs = JSSpec.runner.getSpecs(); 446 | var sb = []; 447 | for(var i = 0; i < specs.length; i++) { 448 | var spec = specs[i]; 449 | sb.push('
    • ' + specs[i].context + ' [rerun]

    • '); 450 | } 451 | return sb.join(""); 452 | }(), 453 | '
    ' 454 | ].join(""); 455 | container.appendChild(list); 456 | 457 | var log = document.createElement("DIV"); 458 | log.id = "log"; 459 | log.innerHTML = [ 460 | '

    Log

    ', 461 | '
      ', 462 | function() { 463 | var specs = JSSpec.runner.getSpecs(); 464 | var sb = []; 465 | for(var i = 0; i < specs.length; i++) { 466 | var spec = specs[i]; 467 | sb.push('
    • '); 468 | sb.push('

      ' + specs[i].context + ' [rerun]

      '); 469 | sb.push('
        '); 470 | for(var j = 0; j < spec.examples.length; j++) { 471 | var example = spec.examples[j]; 472 | sb.push('
      • ') 473 | sb.push('

        ' + example.name + '

        ') 474 | sb.push('
      • ') 475 | } 476 | sb.push('
      '); 477 | sb.push('
    • '); 478 | } 479 | return sb.join(""); 480 | }(), 481 | '
    ' 482 | ].join(""); 483 | container.appendChild(log); 484 | } 485 | JSSpec.Logger.prototype.onRunnerEnd = function() { 486 | 487 | } 488 | JSSpec.Logger.prototype.onSpecStart = function(spec) { 489 | var spec_list = document.getElementById("spec_" + spec.id + "_list"); 490 | var spec_log = document.getElementById("spec_" + spec.id); 491 | 492 | spec_list.className = "ongoing"; 493 | spec_log.className = "ongoing"; 494 | } 495 | JSSpec.Logger.prototype.onSpecEnd = function(spec) { 496 | var spec_list = document.getElementById("spec_" + spec.id + "_list"); 497 | var spec_log = document.getElementById("spec_" + spec.id); 498 | var examples = document.getElementById("spec_" + spec.id + "_examples"); 499 | var className = spec.hasException() ? "exception" : "success"; 500 | 501 | spec_list.className = className; 502 | spec_log.className = className; 503 | 504 | if(JSSpec.options.autocollapse && !spec.hasException()) examples.style.display = "none"; 505 | 506 | if(spec.exception) { 507 | heading.appendChild(document.createTextNode(" - " + spec.exception.message)); 508 | } 509 | } 510 | JSSpec.Logger.prototype.onExampleStart = function(example) { 511 | var li = document.getElementById("example_" + example.id); 512 | li.className = "ongoing"; 513 | } 514 | JSSpec.Logger.prototype.onExampleEnd = function(example) { 515 | var li = document.getElementById("example_" + example.id); 516 | li.className = example.exception ? "exception" : "success"; 517 | 518 | if(example.exception) { 519 | var div = document.createElement("DIV"); 520 | div.innerHTML = example.exception.message + "


    " + " at " + example.exception.fileName + ", line " + example.exception.lineNumber + "

    "; 521 | li.appendChild(div); 522 | } 523 | 524 | var title = document.getElementById("title"); 525 | var runner = JSSpec.runner; 526 | 527 | title.className = runner.hasException() ? "exception" : "success"; 528 | 529 | this.finishedExamples++; 530 | document.getElementById("total_failures").innerHTML = runner.getTotalFailures(); 531 | document.getElementById("total_errors").innerHTML = runner.getTotalErrors(); 532 | document.getElementById("progress").innerHTML = parseInt(this.finishedExamples / runner.totalExamples * 100); 533 | document.getElementById("total_elapsed").innerHTML = (new Date().getTime() - this.startedAt.getTime()) / 1000; 534 | } 535 | 536 | 537 | 538 | /** 539 | * IncludeMatcher 540 | */ 541 | JSSpec.IncludeMatcher = function(actual, expected, condition) { 542 | this.actual = actual; 543 | this.expected = expected; 544 | this.condition = condition; 545 | this.match = false; 546 | this.explaination = this.makeExplain(); 547 | } 548 | JSSpec.IncludeMatcher.createInstance = function(actual, expected, condition) { 549 | return new JSSpec.IncludeMatcher(actual, expected, condition); 550 | } 551 | JSSpec.IncludeMatcher.prototype.matches = function() { 552 | return this.match; 553 | } 554 | JSSpec.IncludeMatcher.prototype.explain = function() { 555 | return this.explaination; 556 | } 557 | JSSpec.IncludeMatcher.prototype.makeExplain = function() { 558 | if(typeof this.actual.length == 'undefined') { 559 | return this.makeExplainForNotArray(); 560 | } else { 561 | return this.makeExplainForArray(); 562 | } 563 | } 564 | JSSpec.IncludeMatcher.prototype.makeExplainForNotArray = function() { 565 | var sb = []; 566 | sb.push('

    actual value:

    '); 567 | sb.push('

    ' + JSSpec.util.inspect(this.actual) + '

    '); 568 | sb.push('

    should ' + (this.condition ? '' : 'not') + ' include:

    '); 569 | sb.push('

    ' + JSSpec.util.inspect(this.expected) + '

    '); 570 | sb.push('

    but since it\s not an array, include or not doesn\'t make any sense.

    '); 571 | return sb.join(""); 572 | } 573 | JSSpec.IncludeMatcher.prototype.makeExplainForArray = function() { 574 | if(this.condition) { 575 | for(var i = 0; i < this.actual.length; i++) { 576 | var matches = JSSpec.EqualityMatcher.createInstance(this.expected, this.actual[i]).matches(); 577 | if(matches) { 578 | this.match = true; 579 | break; 580 | } 581 | } 582 | } else { 583 | for(var i = 0; i < this.actual.length; i++) { 584 | var matches = JSSpec.EqualityMatcher.createInstance(this.expected, this.actual[i]).matches(); 585 | if(matches) { 586 | this.match = false; 587 | break; 588 | } 589 | } 590 | } 591 | 592 | if(this.match) return ""; 593 | 594 | var sb = []; 595 | sb.push('

    actual value:

    '); 596 | sb.push('

    ' + JSSpec.util.inspect(this.actual, false, this.condition ? null : i) + '

    '); 597 | sb.push('

    should ' + (this.condition ? '' : 'not') + ' include:

    '); 598 | sb.push('

    ' + JSSpec.util.inspect(this.expected) + '

    '); 599 | return sb.join(""); 600 | } 601 | 602 | 603 | 604 | 605 | /** 606 | * PropertyLengthMatcher 607 | */ 608 | JSSpec.PropertyLengthMatcher = function(num, property, o, condition) { 609 | this.num = num; 610 | this.o = o; 611 | this.property = property; 612 | if((property == 'characters' || property == 'items') && typeof o.length != 'undefined') { 613 | this.property = 'length'; 614 | } 615 | 616 | this.condition = condition; 617 | this.conditionMet = function(x) { 618 | if(condition == 'exactly') return x.length == num; 619 | if(condition == 'at least') return x.length >= num; 620 | if(condition == 'at most') return x.length <= num; 621 | 622 | throw "Unknown condition '" + condition + "'"; 623 | }; 624 | this.match = false; 625 | this.explaination = this.makeExplain(); 626 | } 627 | JSSpec.PropertyLengthMatcher.prototype.makeExplain = function() { 628 | if(this.o._type == 'String' && this.property == 'length') { 629 | this.match = this.conditionMet(this.o); 630 | return this.match ? '' : this.makeExplainForString(); 631 | } else if(typeof this.o.length != 'undefined' && this.property == "length") { 632 | this.match = this.conditionMet(this.o); 633 | return this.match ? '' : this.makeExplainForArray(); 634 | } else if(typeof this.o[this.property] != 'undefined' && this.o[this.property] != null) { 635 | this.match = this.conditionMet(this.o[this.property]); 636 | return this.match ? '' : this.makeExplainForObject(); 637 | } else if(typeof this.o[this.property] == 'undefined' || this.o[this.property] == null) { 638 | this.match = false; 639 | return this.makeExplainForNoProperty(); 640 | } 641 | 642 | this.match = true; 643 | } 644 | JSSpec.PropertyLengthMatcher.prototype.makeExplainForString = function() { 645 | var sb = []; 646 | 647 | var exp = this.num == 0 ? 648 | 'be an empty string' : 649 | 'have ' + this.condition + ' ' + this.num + ' characters'; 650 | 651 | sb.push('

    actual value has ' + this.o.length + ' characters:

    '); 652 | sb.push('

    ' + JSSpec.util.inspect(this.o) + '

    '); 653 | sb.push('

    but it should ' + exp + '.

    '); 654 | 655 | return sb.join(""); 656 | } 657 | JSSpec.PropertyLengthMatcher.prototype.makeExplainForArray = function() { 658 | var sb = []; 659 | 660 | var exp = this.num == 0 ? 661 | 'be an empty array' : 662 | 'have ' + this.condition + ' ' + this.num + ' items'; 663 | 664 | sb.push('

    actual value has ' + this.o.length + ' items:

    '); 665 | sb.push('

    ' + JSSpec.util.inspect(this.o) + '

    '); 666 | sb.push('

    but it should ' + exp + '.

    '); 667 | 668 | return sb.join(""); 669 | } 670 | JSSpec.PropertyLengthMatcher.prototype.makeExplainForObject = function() { 671 | var sb = []; 672 | 673 | var exp = this.num == 0 ? 674 | 'be empty' : 675 | 'have ' + this.condition + ' ' + this.num + ' ' + this.property + '.'; 676 | 677 | sb.push('

    actual value has ' + this.o[this.property].length + ' ' + this.property + ':

    '); 678 | sb.push('

    ' + JSSpec.util.inspect(this.o, false, this.property) + '

    '); 679 | sb.push('

    but it should ' + exp + '.

    '); 680 | 681 | return sb.join(""); 682 | } 683 | JSSpec.PropertyLengthMatcher.prototype.makeExplainForNoProperty = function() { 684 | var sb = []; 685 | 686 | sb.push('

    actual value:

    '); 687 | sb.push('

    ' + JSSpec.util.inspect(this.o) + '

    '); 688 | sb.push('

    should have ' + this.condition + ' ' + this.num + ' ' + this.property + ' but there\'s no such property.

    '); 689 | 690 | return sb.join(""); 691 | } 692 | JSSpec.PropertyLengthMatcher.prototype.matches = function() { 693 | return this.match; 694 | } 695 | JSSpec.PropertyLengthMatcher.prototype.explain = function() { 696 | return this.explaination; 697 | } 698 | JSSpec.PropertyLengthMatcher.createInstance = function(num, property, o, condition) { 699 | return new JSSpec.PropertyLengthMatcher(num, property, o, condition); 700 | } 701 | 702 | 703 | 704 | /** 705 | * EqualityMatcher 706 | */ 707 | JSSpec.EqualityMatcher = {} 708 | JSSpec.EqualityMatcher.createInstance = function(expected, actual) { 709 | if(expected == null || actual == null) { 710 | return new JSSpec.NullEqualityMatcher(expected, actual); 711 | } else if(expected._type == actual._type) { 712 | if(expected._type == "String") { 713 | return new JSSpec.StringEqualityMatcher(expected, actual); 714 | } else if(expected._type == "Date") { 715 | return new JSSpec.DateEqualityMatcher(expected, actual); 716 | } else if(expected._type == "Number") { 717 | return new JSSpec.NumberEqualityMatcher(expected, actual); 718 | } else if(expected._type == "Array") { 719 | return new JSSpec.ArrayEqualityMatcher(expected, actual); 720 | } else if(expected._type == "Boolean") { 721 | return new JSSpec.BooleanEqualityMatcher(expected, actual); 722 | } 723 | } 724 | 725 | return new JSSpec.ObjectEqualityMatcher(expected, actual); 726 | } 727 | JSSpec.EqualityMatcher.basicExplain = function(expected, actual, expectedDesc, actualDesc) { 728 | var sb = []; 729 | 730 | sb.push(actualDesc || '

    actual value:

    '); 731 | sb.push('

    ' + JSSpec.util.inspect(actual) + '

    '); 732 | sb.push(expectedDesc || '

    should be:

    '); 733 | sb.push('

    ' + JSSpec.util.inspect(expected) + '

    '); 734 | 735 | return sb.join(""); 736 | } 737 | JSSpec.EqualityMatcher.diffExplain = function(expected, actual) { 738 | var sb = []; 739 | 740 | sb.push('

    diff:

    '); 741 | sb.push('

    '); 742 | 743 | var dmp = new diff_match_patch(); 744 | var diff = dmp.diff_main(expected, actual); 745 | dmp.diff_cleanupEfficiency(diff); 746 | 747 | sb.push(JSSpec.util.inspect(dmp.diff_prettyHtml(diff), true)); 748 | 749 | sb.push('

    '); 750 | 751 | return sb.join(""); 752 | } 753 | 754 | 755 | 756 | /** 757 | * BooleanEqualityMatcher 758 | */ 759 | JSSpec.BooleanEqualityMatcher = function(expected, actual) { 760 | this.expected = expected; 761 | this.actual = actual; 762 | } 763 | JSSpec.BooleanEqualityMatcher.prototype.explain = function() { 764 | var sb = []; 765 | 766 | sb.push('

    actual value:

    '); 767 | sb.push('

    ' + JSSpec.util.inspect(this.actual) + '

    '); 768 | sb.push('

    should be:

    '); 769 | sb.push('

    ' + JSSpec.util.inspect(this.expected) + '

    '); 770 | 771 | return sb.join(""); 772 | } 773 | JSSpec.BooleanEqualityMatcher.prototype.matches = function() { 774 | return this.expected == this.actual; 775 | } 776 | 777 | 778 | 779 | /** 780 | * NullEqualityMatcher 781 | */ 782 | JSSpec.NullEqualityMatcher = function(expected, actual) { 783 | this.expected = expected; 784 | this.actual = actual; 785 | } 786 | JSSpec.NullEqualityMatcher.prototype.matches = function() { 787 | return this.expected == this.actual && typeof this.expected == typeof this.actual; 788 | } 789 | JSSpec.NullEqualityMatcher.prototype.explain = function() { 790 | return JSSpec.EqualityMatcher.basicExplain(this.expected, this.actual); 791 | } 792 | 793 | 794 | 795 | JSSpec.DateEqualityMatcher = function(expected, actual) { 796 | this.expected = expected; 797 | this.actual = actual; 798 | } 799 | JSSpec.DateEqualityMatcher.prototype.matches = function() { 800 | return this.expected.getTime() == this.actual.getTime(); 801 | } 802 | JSSpec.DateEqualityMatcher.prototype.explain = function() { 803 | var sb = []; 804 | 805 | sb.push(JSSpec.EqualityMatcher.basicExplain(this.expected, this.actual)); 806 | sb.push(JSSpec.EqualityMatcher.diffExplain(this.expected.toString(), this.actual.toString())); 807 | 808 | return sb.join(""); 809 | } 810 | 811 | 812 | 813 | /** 814 | * ObjectEqualityMatcher 815 | */ 816 | JSSpec.ObjectEqualityMatcher = function(expected, actual) { 817 | this.expected = expected; 818 | this.actual = actual; 819 | this.match = this.expected == this.actual; 820 | this.explaination = this.makeExplain(); 821 | } 822 | JSSpec.ObjectEqualityMatcher.prototype.matches = function() {return this.match} 823 | JSSpec.ObjectEqualityMatcher.prototype.explain = function() {return this.explaination} 824 | JSSpec.ObjectEqualityMatcher.prototype.makeExplain = function() { 825 | if(this.expected == this.actual) { 826 | this.match = true; 827 | return ""; 828 | } 829 | 830 | if(JSSpec.util.isDomNode(this.expected)) { 831 | return this.makeExplainForDomNode(); 832 | } 833 | 834 | for(var key in this.expected) { 835 | var expectedHasItem = this.expected[key] != null && typeof this.expected[key] != 'undefined'; 836 | var actualHasItem = this.actual[key] != null && typeof this.actual[key] != 'undefined'; 837 | if(expectedHasItem && !actualHasItem) return this.makeExplainForMissingItem(key); 838 | } 839 | for(var key in this.actual) { 840 | var expectedHasItem = this.expected[key] != null && typeof this.expected[key] != 'undefined'; 841 | var actualHasItem = this.actual[key] != null && typeof this.actual[key] != 'undefined'; 842 | if(actualHasItem && !expectedHasItem) return this.makeExplainForUnknownItem(key); 843 | } 844 | 845 | for(var key in this.expected) { 846 | var matcher = JSSpec.EqualityMatcher.createInstance(this.expected[key], this.actual[key]); 847 | if(!matcher.matches()) return this.makeExplainForItemMismatch(key); 848 | } 849 | 850 | this.match = true; 851 | } 852 | JSSpec.ObjectEqualityMatcher.prototype.makeExplainForDomNode = function(key) { 853 | var sb = []; 854 | 855 | sb.push(JSSpec.EqualityMatcher.basicExplain(this.expected, this.actual)); 856 | 857 | return sb.join(""); 858 | } 859 | JSSpec.ObjectEqualityMatcher.prototype.makeExplainForMissingItem = function(key) { 860 | var sb = []; 861 | 862 | sb.push('

    actual value has no item named ' + JSSpec.util.inspect(key) + '

    '); 863 | sb.push('

    ' + JSSpec.util.inspect(this.actual, false, key) + '

    '); 864 | sb.push('

    but it should have the item whose value is ' + JSSpec.util.inspect(this.expected[key]) + '

    '); 865 | sb.push('

    ' + JSSpec.util.inspect(this.expected, false, key) + '

    '); 866 | 867 | return sb.join(""); 868 | } 869 | JSSpec.ObjectEqualityMatcher.prototype.makeExplainForUnknownItem = function(key) { 870 | var sb = []; 871 | 872 | sb.push('

    actual value has item named ' + JSSpec.util.inspect(key) + '

    '); 873 | sb.push('

    ' + JSSpec.util.inspect(this.actual, false, key) + '

    '); 874 | sb.push('

    but there should be no such item

    '); 875 | sb.push('

    ' + JSSpec.util.inspect(this.expected, false, key) + '

    '); 876 | 877 | return sb.join(""); 878 | } 879 | JSSpec.ObjectEqualityMatcher.prototype.makeExplainForItemMismatch = function(key) { 880 | var sb = []; 881 | 882 | sb.push('

    actual value has an item named ' + JSSpec.util.inspect(key) + ' whose value is ' + JSSpec.util.inspect(this.actual[key]) + '

    '); 883 | sb.push('

    ' + JSSpec.util.inspect(this.actual, false, key) + '

    '); 884 | sb.push('

    but it\'s value should be ' + JSSpec.util.inspect(this.expected[key]) + '

    '); 885 | sb.push('

    ' + JSSpec.util.inspect(this.expected, false, key) + '

    '); 886 | 887 | return sb.join(""); 888 | } 889 | 890 | 891 | 892 | /** 893 | * ArrayEqualityMatcher 894 | */ 895 | JSSpec.ArrayEqualityMatcher = function(expected, actual) { 896 | this.expected = expected; 897 | this.actual = actual; 898 | this.match = this.expected == this.actual; 899 | this.explaination = this.makeExplain(); 900 | } 901 | JSSpec.ArrayEqualityMatcher.prototype.matches = function() {return this.match} 902 | JSSpec.ArrayEqualityMatcher.prototype.explain = function() {return this.explaination} 903 | JSSpec.ArrayEqualityMatcher.prototype.makeExplain = function() { 904 | if(this.expected.length != this.actual.length) return this.makeExplainForLengthMismatch(); 905 | 906 | for(var i = 0; i < this.expected.length; i++) { 907 | var matcher = JSSpec.EqualityMatcher.createInstance(this.expected[i], this.actual[i]); 908 | if(!matcher.matches()) return this.makeExplainForItemMismatch(i); 909 | } 910 | 911 | this.match = true; 912 | } 913 | JSSpec.ArrayEqualityMatcher.prototype.makeExplainForLengthMismatch = function() { 914 | return JSSpec.EqualityMatcher.basicExplain( 915 | this.expected, 916 | this.actual, 917 | '

    but it should be ' + this.expected.length + '

    ', 918 | '

    actual value has ' + this.actual.length + ' items

    ' 919 | ); 920 | } 921 | JSSpec.ArrayEqualityMatcher.prototype.makeExplainForItemMismatch = function(index) { 922 | var postfix = ["th", "st", "nd", "rd", "th"][Math.min((index + 1) % 10,4)]; 923 | 924 | var sb = []; 925 | 926 | sb.push('

    ' + (index + 1) + postfix + ' item (index ' + index + ') of actual value is ' + JSSpec.util.inspect(this.actual[index]) + ':

    '); 927 | sb.push('

    ' + JSSpec.util.inspect(this.actual, false, index) + '

    '); 928 | sb.push('

    but it should be ' + JSSpec.util.inspect(this.expected[index]) + ':

    '); 929 | sb.push('

    ' + JSSpec.util.inspect(this.expected, false, index) + '

    '); 930 | 931 | return sb.join(""); 932 | } 933 | 934 | 935 | 936 | /** 937 | * NumberEqualityMatcher 938 | */ 939 | JSSpec.NumberEqualityMatcher = function(expected, actual) { 940 | this.expected = expected; 941 | this.actual = actual; 942 | } 943 | JSSpec.NumberEqualityMatcher.prototype.matches = function() { 944 | if(this.expected == this.actual) return true; 945 | } 946 | JSSpec.NumberEqualityMatcher.prototype.explain = function() { 947 | return JSSpec.EqualityMatcher.basicExplain(this.expected, this.actual); 948 | } 949 | 950 | 951 | 952 | /** 953 | * StringEqualityMatcher 954 | */ 955 | JSSpec.StringEqualityMatcher = function(expected, actual) { 956 | this.expected = expected; 957 | this.actual = actual; 958 | } 959 | JSSpec.StringEqualityMatcher.prototype.matches = function() { 960 | if(this.expected == this.actual) return true; 961 | } 962 | JSSpec.StringEqualityMatcher.prototype.explain = function() { 963 | var sb = []; 964 | 965 | sb.push(JSSpec.EqualityMatcher.basicExplain(this.expected, this.actual)); 966 | sb.push(JSSpec.EqualityMatcher.diffExplain(this.expected, this.actual)); 967 | return sb.join(""); 968 | } 969 | 970 | 971 | 972 | /** 973 | * PatternMatcher 974 | */ 975 | JSSpec.PatternMatcher = function(actual, pattern, condition) { 976 | this.actual = actual; 977 | this.pattern = pattern; 978 | this.condition = condition; 979 | this.match = false; 980 | this.explaination = this.makeExplain(); 981 | } 982 | JSSpec.PatternMatcher.createInstance = function(actual, pattern, condition) { 983 | return new JSSpec.PatternMatcher(actual, pattern, condition); 984 | } 985 | JSSpec.PatternMatcher.prototype.makeExplain = function() { 986 | if(this.actual == null || this.actual._type != 'String') { 987 | var sb = []; 988 | sb.push('

    actual value:

    '); 989 | sb.push('

    ' + JSSpec.util.inspect(this.actual) + '

    '); 990 | sb.push('

    should ' + (this.condition ? '' : 'not') + ' match with pattern:

    '); 991 | sb.push('

    ' + JSSpec.util.inspect(this.pattern) + '

    '); 992 | sb.push('

    but pattern matching cannot be performed.

    '); 993 | return sb.join(""); 994 | } else { 995 | this.match = this.condition == !!this.actual.match(this.pattern); 996 | if(this.match) return ""; 997 | 998 | var sb = []; 999 | sb.push('

    actual value:

    '); 1000 | sb.push('

    ' + JSSpec.util.inspect(this.actual) + '

    '); 1001 | sb.push('

    should ' + (this.condition ? '' : 'not') + ' match with pattern:

    '); 1002 | sb.push('

    ' + JSSpec.util.inspect(this.pattern) + '

    '); 1003 | return sb.join(""); 1004 | } 1005 | 1006 | } 1007 | JSSpec.PatternMatcher.prototype.matches = function() { 1008 | return this.match; 1009 | } 1010 | JSSpec.PatternMatcher.prototype.explain = function() { 1011 | return this.explaination; 1012 | } 1013 | 1014 | 1015 | 1016 | /** 1017 | * Domain Specific Languages 1018 | */ 1019 | JSSpec.DSL = {}; 1020 | 1021 | JSSpec.DSL.forString = { 1022 | asHtml: function() { 1023 | var html = this; 1024 | 1025 | // Uniformize quotation, turn tag names and attribute names into lower case 1026 | html = html.replace(/<(\/?)(\w+)([^>]*?)>/img, function(str, closingMark, tagName, attrs) { 1027 | var sortedAttrs = JSSpec.util.sortHtmlAttrs(JSSpec.util.correctHtmlAttrQuotation(attrs).toLowerCase()) 1028 | return "<" + closingMark + tagName.toLowerCase() + sortedAttrs + ">" 1029 | }) 1030 | 1031 | // validation self-closing tags 1032 | html = html.replace(/]*?)>/mg, function(str, attrs) { 1033 | return "" 1034 | }) 1035 | html = html.replace(/]*?)>/mg, function(str, attrs) { 1036 | return "" 1037 | }) 1038 | html = html.replace(/]*?)>/mg, function(str, attrs) { 1039 | return "" 1040 | }) 1041 | 1042 | // append semi-colon at the end of style value 1043 | html = html.replace(/style="(.*)"/mg, function(str, styleStr) { 1044 | styleStr = JSSpec.util.sortStyleEntries(styleStr.strip()); // for Safari 1045 | if(styleStr.charAt(styleStr.length - 1) != ';') styleStr += ";" 1046 | 1047 | return 'style="' + styleStr + '"' 1048 | }) 1049 | 1050 | // sort style entries 1051 | 1052 | // remove empty style attributes 1053 | html = html.replace(/ style=";"/mg, "") 1054 | 1055 | // remove new-lines 1056 | html = html.replace(/\r/mg, '') 1057 | html = html.replace(/\n/mg, '') 1058 | 1059 | // TODO remove this? 1060 | //html = html.replace(/(>[^<>]*?)\s+([^<>]*?<)/mg, '$1$2') 1061 | 1062 | return html; 1063 | } 1064 | } 1065 | 1066 | 1067 | 1068 | JSSpec.DSL.describe = function(context, entries) { 1069 | JSSpec.specs.push(new JSSpec.Spec(context, entries)); 1070 | } 1071 | JSSpec.DSL.expect = function(target) { 1072 | if(JSSpec._secondPass) return {} 1073 | 1074 | var subject = new JSSpec.DSL.Subject(target); 1075 | return subject; 1076 | } 1077 | JSSpec.DSL.Subject = function(target) { 1078 | this.target = target; 1079 | } 1080 | JSSpec.DSL.Subject.prototype._type = 'Subject'; 1081 | JSSpec.DSL.Subject.prototype.should_fail = function(message) { 1082 | JSSpec._assertionFailure = {message:message}; 1083 | throw JSSpec._assertionFailure; 1084 | } 1085 | JSSpec.DSL.Subject.prototype.should_be = function(expected) { 1086 | var matcher = JSSpec.EqualityMatcher.createInstance(expected, this.target); 1087 | if(!matcher.matches()) { 1088 | JSSpec._assertionFailure = {message:matcher.explain()}; 1089 | throw JSSpec._assertionFailure; 1090 | } 1091 | } 1092 | JSSpec.DSL.Subject.prototype.should_not_be = function(expected) { 1093 | // TODO JSSpec.EqualityMatcher should support 'condition' 1094 | var matcher = JSSpec.EqualityMatcher.createInstance(expected, this.target); 1095 | if(matcher.matches()) { 1096 | JSSpec._assertionFailure = {message:"'" + this.target + "' should not be '" + expected + "'"}; 1097 | throw JSSpec._assertionFailure; 1098 | } 1099 | } 1100 | JSSpec.DSL.Subject.prototype.should_be_empty = function() { 1101 | this.should_have(0, this.getType() == 'String' ? 'characters' : 'items'); 1102 | } 1103 | JSSpec.DSL.Subject.prototype.should_not_be_empty = function() { 1104 | this.should_have_at_least(1, this.getType() == 'String' ? 'characters' : 'items'); 1105 | } 1106 | JSSpec.DSL.Subject.prototype.should_be_true = function() { 1107 | this.should_be(true); 1108 | } 1109 | JSSpec.DSL.Subject.prototype.should_be_false = function() { 1110 | this.should_be(false); 1111 | } 1112 | JSSpec.DSL.Subject.prototype.should_be_null = function() { 1113 | this.should_be(null); 1114 | } 1115 | JSSpec.DSL.Subject.prototype.should_be_undefined = function() { 1116 | this.should_be(undefined); 1117 | } 1118 | JSSpec.DSL.Subject.prototype.should_not_be_null = function() { 1119 | this.should_not_be(null); 1120 | } 1121 | JSSpec.DSL.Subject.prototype.should_not_be_undefined = function() { 1122 | this.should_not_be(undefined); 1123 | } 1124 | JSSpec.DSL.Subject.prototype._should_have = function(num, property, condition) { 1125 | var matcher = JSSpec.PropertyLengthMatcher.createInstance(num, property, this.target, condition); 1126 | if(!matcher.matches()) { 1127 | JSSpec._assertionFailure = {message:matcher.explain()}; 1128 | throw JSSpec._assertionFailure; 1129 | } 1130 | } 1131 | JSSpec.DSL.Subject.prototype.should_have = function(num, property) { 1132 | this._should_have(num, property, "exactly"); 1133 | } 1134 | JSSpec.DSL.Subject.prototype.should_have_exactly = function(num, property) { 1135 | this._should_have(num, property, "exactly"); 1136 | } 1137 | JSSpec.DSL.Subject.prototype.should_have_at_least = function(num, property) { 1138 | this._should_have(num, property, "at least"); 1139 | } 1140 | JSSpec.DSL.Subject.prototype.should_have_at_most = function(num, property) { 1141 | this._should_have(num, property, "at most"); 1142 | } 1143 | JSSpec.DSL.Subject.prototype.should_include = function(expected) { 1144 | var matcher = JSSpec.IncludeMatcher.createInstance(this.target, expected, true); 1145 | if(!matcher.matches()) { 1146 | JSSpec._assertionFailure = {message:matcher.explain()}; 1147 | throw JSSpec._assertionFailure; 1148 | } 1149 | } 1150 | JSSpec.DSL.Subject.prototype.should_not_include = function(expected) { 1151 | var matcher = JSSpec.IncludeMatcher.createInstance(this.target, expected, false); 1152 | if(!matcher.matches()) { 1153 | JSSpec._assertionFailure = {message:matcher.explain()}; 1154 | throw JSSpec._assertionFailure; 1155 | } 1156 | } 1157 | JSSpec.DSL.Subject.prototype.should_match = function(pattern) { 1158 | var matcher = JSSpec.PatternMatcher.createInstance(this.target, pattern, true); 1159 | if(!matcher.matches()) { 1160 | JSSpec._assertionFailure = {message:matcher.explain()}; 1161 | throw JSSpec._assertionFailure; 1162 | } 1163 | } 1164 | JSSpec.DSL.Subject.prototype.should_not_match = function(pattern) { 1165 | var matcher = JSSpec.PatternMatcher.createInstance(this.target, pattern, false); 1166 | if(!matcher.matches()) { 1167 | JSSpec._assertionFailure = {message:matcher.explain()}; 1168 | throw JSSpec._assertionFailure; 1169 | } 1170 | } 1171 | 1172 | JSSpec.DSL.Subject.prototype.getType = function() { 1173 | if(typeof this.target == 'undefined') { 1174 | return 'undefined'; 1175 | } else if(this.target == null) { 1176 | return 'null'; 1177 | } else if(this.target._type) { 1178 | return this.target._type; 1179 | } else if(JSSpec.util.isDomNode(this.target)) { 1180 | return 'DomNode'; 1181 | } else { 1182 | return 'object'; 1183 | } 1184 | } 1185 | 1186 | 1187 | 1188 | /** 1189 | * Utilities 1190 | */ 1191 | JSSpec.util = { 1192 | parseOptions: function(defaults) { 1193 | var options = defaults; 1194 | 1195 | var url = location.href; 1196 | var queryIndex = url.indexOf('?'); 1197 | if(queryIndex == -1) return options; 1198 | 1199 | var query = url.substring(queryIndex + 1); 1200 | var pairs = query.split('&'); 1201 | for(var i = 0; i < pairs.length; i++) { 1202 | var tokens = pairs[i].split('='); 1203 | options[tokens[0]] = tokens[1]; 1204 | } 1205 | 1206 | return options; 1207 | }, 1208 | correctHtmlAttrQuotation: function(html) { 1209 | html = html.replace(/(\w+)=['"]([^'"]+)['"]/mg,function (str, name, value) {return name + '=' + '"' + value + '"'}); 1210 | html = html.replace(/(\w+)=([^ '"]+)/mg,function (str, name, value) {return name + '=' + '"' + value + '"'}); 1211 | html = html.replace(/'/mg, '"'); 1212 | 1213 | return html; 1214 | }, 1215 | sortHtmlAttrs: function(html) { 1216 | var attrs = [] 1217 | html.replace(/((\w+)="[^"]+")/mg, function(str, matched) { 1218 | attrs.push(matched); 1219 | }) 1220 | return attrs.length == 0 ? "" : " " + attrs.sort().join(" "); 1221 | }, 1222 | sortStyleEntries: function(styleText) { 1223 | var entries = styleText.split(/; /); 1224 | return entries.sort().join("; "); 1225 | }, 1226 | escapeHtml: function(str) { 1227 | if(!this._div) { 1228 | this._div = document.createElement("DIV"); 1229 | this._text = document.createTextNode(''); 1230 | this._div.appendChild(this._text); 1231 | } 1232 | this._text.data = str; 1233 | return this._div.innerHTML; 1234 | }, 1235 | isDomNode: function(o) { 1236 | // TODO: make it more stricter 1237 | return (typeof o.nodeName == 'string') && (typeof o.nodeType == 'number'); 1238 | }, 1239 | inspectDomPath: function(o) { 1240 | var sb = []; 1241 | while(o && o.nodeName != '#document' && o.parent) { 1242 | var siblings = o.parentNode.childNodes; 1243 | for(var i = 0; i < siblings.length; i++) { 1244 | if(siblings[i] == o) { 1245 | sb.push(o.nodeName + (i == 0 ? '' : '[' + i + ']')); 1246 | break; 1247 | } 1248 | } 1249 | o = o.parentNode; 1250 | } 1251 | return sb.join(" > "); 1252 | }, 1253 | inspectDomNode: function(o) { 1254 | if(o.nodeType == 1) { 1255 | var sb = []; 1256 | sb.push(''); 1257 | sb.push("<"); 1258 | sb.push(o.nodeName); 1259 | 1260 | var attrs = o.attributes; 1261 | for(var i = 0; i < attrs.length; i++) { 1262 | if( 1263 | attrs[i].nodeValue && 1264 | attrs[i].nodeName != 'contentEditable' && 1265 | attrs[i].nodeName != 'style' && 1266 | typeof attrs[i].nodeValue != 'function' 1267 | ) sb.push(' ' + attrs[i].nodeName + '="' + attrs[i].nodeValue + '"'); 1268 | } 1269 | if(o.style && o.style.cssText) { 1270 | sb.push(' style="' + o.style.cssText + '"'); 1271 | } 1272 | sb.push('> (' + JSSpec.util.inspectDomPath(o) + ')' ); 1273 | sb.push(''); 1274 | return sb.join(""); 1275 | } else if(o.nodeType == 3) { 1276 | return '#text ' + o.nodeValue + ''; 1277 | } else { 1278 | return 'UnknownDomNode'; 1279 | } 1280 | }, 1281 | inspect: function(o, dontEscape, emphasisKey) { 1282 | if(typeof o == 'undefined') return 'undefined'; 1283 | if(o == null) return 'null'; 1284 | if(o._type == 'String') return '"' + (dontEscape ? o : JSSpec.util.escapeHtml(o)) + '"'; 1285 | 1286 | if(o._type == 'Date') { 1287 | return '"' + o.toString() + '"'; 1288 | } 1289 | 1290 | if(o._type == 'Number') return '' + (dontEscape ? o : JSSpec.util.escapeHtml(o)) + ''; 1291 | 1292 | if(o._type == 'Boolean') return '' + o + ''; 1293 | 1294 | if(o._type == 'RegExp') return '' + JSSpec.util.escapeHtml(o.toString()) + ''; 1295 | 1296 | if(JSSpec.util.isDomNode(o)) return JSSpec.util.inspectDomNode(o); 1297 | 1298 | if(o._type == 'Array' || typeof o.length != 'undefined') { 1299 | var sb = []; 1300 | for(var i = 0; i < o.length; i++) { 1301 | var inspected = JSSpec.util.inspect(o[i]); 1302 | sb.push(i == emphasisKey ? ('' + inspected + '') : inspected); 1303 | } 1304 | return '[' + sb.join(', ') + ']'; 1305 | } 1306 | 1307 | // object 1308 | var sb = []; 1309 | for(var key in o) { 1310 | if(key == 'should') continue; 1311 | 1312 | var inspected = JSSpec.util.inspect(key) + ":" + JSSpec.util.inspect(o[key]); 1313 | sb.push(key == emphasisKey ? ('' + inspected + '') : inspected); 1314 | } 1315 | return '{' + sb.join(', ') + '}'; 1316 | } 1317 | } 1318 | 1319 | describe = JSSpec.DSL.describe; 1320 | expect = JSSpec.DSL.expect; 1321 | 1322 | String.prototype._type = "String"; 1323 | Number.prototype._type = "Number"; 1324 | Date.prototype._type = "Date"; 1325 | Array.prototype._type = "Array"; 1326 | Boolean.prototype._type = "Boolean"; 1327 | RegExp.prototype._type = "RegExp"; 1328 | 1329 | var targets = [Array.prototype, Date.prototype, Number.prototype, String.prototype, Boolean.prototype, RegExp.prototype]; 1330 | 1331 | String.prototype.asHtml = JSSpec.DSL.forString.asHtml; 1332 | 1333 | 1334 | 1335 | /** 1336 | * Main 1337 | */ 1338 | JSSpec.defaultOptions = { 1339 | autorun: 1, 1340 | specIdBeginsWith: 0, 1341 | exampleIdBeginsWith: 0, 1342 | autocollapse: 1 1343 | }; 1344 | JSSpec.options = JSSpec.util.parseOptions(JSSpec.defaultOptions); 1345 | 1346 | JSSpec.Spec.id = JSSpec.options.specIdBeginsWith; 1347 | JSSpec.Example.id = JSSpec.options.exampleIdBeginsWith; 1348 | 1349 | 1350 | 1351 | window.onload = function() { 1352 | if(JSSpec.specs.length > 0) { 1353 | if(!JSSpec.options.inSuite) { 1354 | JSSpec.runner = new JSSpec.Runner(JSSpec.specs, new JSSpec.Logger()); 1355 | if(JSSpec.options.rerun) { 1356 | JSSpec.runner.rerun(decodeURIComponent(JSSpec.options.rerun)); 1357 | } else { 1358 | JSSpec.runner.run(); 1359 | } 1360 | } else { 1361 | // in suite, send all specs to parent 1362 | var parentWindow = window.frames.parent.window; 1363 | for(var i = 0; i < JSSpec.specs.length; i++) { 1364 | parentWindow.JSSpec.specs.push(JSSpec.specs[i]); 1365 | } 1366 | } 1367 | } else { 1368 | var links = document.getElementById('list').getElementsByTagName('A'); 1369 | var frameContainer = document.createElement('DIV'); 1370 | frameContainer.style.display = 'none'; 1371 | document.body.appendChild(frameContainer); 1372 | 1373 | for(var i = 0; i < links.length; i++) { 1374 | var frame = document.createElement('IFRAME'); 1375 | frame.src = links[i].href + '?inSuite=0&specIdBeginsWith=' + (i * 10000) + '&exampleIdBeginsWith=' + (i * 10000); 1376 | frameContainer.appendChild(frame); 1377 | } 1378 | } 1379 | } --------------------------------------------------------------------------------