qunited 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. data/.gitignore +13 -0
  2. data/Gemfile +4 -0
  3. data/Rakefile +9 -0
  4. data/bin/qunited +23 -0
  5. data/lib/qunited/js_runner/base.rb +34 -0
  6. data/lib/qunited/js_runner/rhino/js/env.rhino.js +14006 -0
  7. data/lib/qunited/js_runner/rhino/js/js.jar +0 -0
  8. data/lib/qunited/js_runner/rhino/js/qunit-runner.js +168 -0
  9. data/lib/qunited/js_runner/rhino/js/qunit.js +1838 -0
  10. data/lib/qunited/js_runner/rhino/js/yaml.js +22 -0
  11. data/lib/qunited/js_runner/rhino.rb +65 -0
  12. data/lib/qunited/js_runner.rb +2 -0
  13. data/lib/qunited/rake_task.rb +87 -0
  14. data/lib/qunited/results.rb +164 -0
  15. data/lib/qunited/runner.rb +23 -0
  16. data/lib/qunited/version.rb +3 -0
  17. data/lib/qunited.rb +4 -0
  18. data/qunited.gemspec +20 -0
  19. data/test/fixtures/basic_project/app/assets/javascripts/application.js +6 -0
  20. data/test/fixtures/basic_project/test/javascripts/test_basics.js +6 -0
  21. data/test/fixtures/basic_project/test/javascripts/test_math.js +12 -0
  22. data/test/fixtures/dom_project/app/assets/javascripts/application.js +6 -0
  23. data/test/fixtures/dom_project/test/javascripts/test_misc.js +5 -0
  24. data/test/fixtures/errors_project/app/assets/javascripts/no_error.js +5 -0
  25. data/test/fixtures/errors_project/app/assets/javascripts/syntax_error.js +7 -0
  26. data/test/fixtures/errors_project/app/assets/javascripts/undefined_error.js +9 -0
  27. data/test/fixtures/errors_project/test/javascripts/this_test_has_no_errors_in_it.js +5 -0
  28. data/test/fixtures/errors_project/test/javascripts/this_test_has_no_tests.js +4 -0
  29. data/test/fixtures/errors_project/test/javascripts/this_test_has_syntax_error.js +10 -0
  30. data/test/fixtures/errors_project/test/javascripts/this_test_has_undefined_error.js +10 -0
  31. data/test/fixtures/failures_project/app/assets/javascripts/application.js +5 -0
  32. data/test/fixtures/failures_project/test/javascripts/test_basics.js +11 -0
  33. data/test/fixtures/failures_project/test/javascripts/test_math.js +12 -0
  34. data/test/test_helper.rb +5 -0
  35. data/test/unit/test_results.rb +338 -0
  36. data/test/unit/test_rhino_runner.rb +114 -0
  37. metadata +100 -0
@@ -0,0 +1,22 @@
1
+ /*
2
+ Copyright (c) 2010 Jeremy Faivre
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ of this software and associated documentation files (the "Software"), to deal
6
+ in the Software without restriction, including without limitation the rights
7
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is furnished
9
+ to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all
12
+ copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ THE SOFTWARE.
21
+ */
22
+ var Yaml=function(){};Yaml.prototype={spec:"1.2",setSpecVersion:function(a){if(a!="1.1"&&a!="1.2"){throw new InvalidArgumentException("Version "+a+" of the YAML specifications is not supported")}this.spec=a},getSpecVersion:function(){return this.spec},loadFile:function(a,b){if(b==undefined){input=this.getFileContents(a);return this.load(input)}this.getFileContents(a,function(c){b(new Yaml().load(c))})},load:function(a){var c=new YamlParser();var b=null;try{b=c.parse(a)}catch(d){if(d.name!=undefined&&d.name.toString=="TypeError"){throw d}throw"Syntax error: "+d.message}return b},dump:function(b,a){if(a==undefined){a=2}yaml=new YamlDumper();return yaml.dump(b,a)},getXHR:function(){if(window.XMLHttpRequest){return new XMLHttpRequest()}if(window.ActiveXObject){var c=["Msxml2.XMLHTTP.6.0","Msxml2.XMLHTTP.3.0","Msxml2.XMLHTTP","Microsoft.XMLHTTP"];for(var a=0;a<4;a++){try{return new ActiveXObject(c[a])}catch(b){}}}return null},getFileContents:function(a,c){var b=this.getXHR();if(c==undefined){b.open("GET",a,false);b.send(null);if(b.status==200||b.status==0){return b.responseText}return null}b.onreadystatechange=function(){if(b.readyState==4){if(b.status==200||b.status==0){c(b.responseText)}else{c(null)}}};b.open("GET",a,true);b.send(null)}};var YAML={encode:function(a){return new Yaml().dump(a)},decode:function(a){return new Yaml().load(a)},load:function(a,b){return new Yaml().loadFile(a,b)}};if(typeof(InvalidArgumentException)=="undefined"){InvalidArgumentException=function(a){this.name="InvalidArgumentException";this.message=a}};var YamlInline=function(){};YamlInline.prototype={i:null,load:function(b){var a=null;b=this.trim(b);if(0==b.length){return""}switch(b.charAt(0)){case"[":a=this.parseSequence(b);break;case"{":a=this.parseMapping(b);break;default:a=this.parseScalar(b)}return a},dump:function(d){var b;var a;var c=new Yaml();if("1.1"==c.getSpecVersion()){b=["true","on","+","yes","y"];a=["false","off","-","no","n"]}else{b=["true"];a=["false"]}if(typeof(d)=="object"&&null!=d){return this.dumpObject(d)}if(undefined==d||null==d){return"null"}if(typeof(d)=="boolean"){return d?"true":"false"}if(/^\d+/.test(d)){return typeof(d)=="string"?"'"+d+"'":parseInt(d)}if(this.isNumeric(d)){return typeof(d)=="string"?"'"+d+"'":parseFloat(d)}if(typeof(d)=="number"){return d==Infinity?".Inf":(d==-Infinity?"-.Inf":(isNaN(d)?".NAN":d))}if((d+"").indexOf("\n")!=-1||(d+"").indexOf("\r")!=-1){return'"'+d.split('"').join('\\"').split("\n").join("\\n").split("\r").join("\\r")+'"'}if((/[\s\'"\:\{\}\[\],&\*\#\?]/.test(d))||(/^[-?|<>=!%@`]/.test(d))){return"'"+d.split("'").join("''")+"'"}if(""==d){return"''"}if(this.getTimestampRegex().test(d)){return"'"+d+"'"}if(this.inArray(d.toLowerCase(),b)){return"'"+d+"'"}if(this.inArray(d.toLowerCase(),a)){return"'"+d+"'"}if(this.inArray(d.toLowerCase(),["null","~"])){return"'"+d+"'"}return d},dumpObject:function(e){var d=this.getKeys(e);var b=null;var c;var a=d.length;if(e instanceof Array){b=[];for(c=0;c<a;c++){b.push(this.dump(e[d[c]]))}return"["+b.join(", ")+"]"}b=[];for(c=0;c<a;c++){b.push(this.dump(d[c])+": "+this.dump(e[d[c]]))}return"{ "+b.join(", ")+" }"},parseScalar:function(b,g,e,d,f){if(g==undefined){g=null}if(e==undefined){e=['"',"'"]}if(d==undefined){d=0}if(f==undefined){f=true}var a=null;var h=null;var c=null;if(this.inArray(b[d],e)){a=this.parseQuotedScalar(b,d);d=this.i}else{if(!g){a=(b+"").substring(d);d+=a.length;h=a.indexOf(" #");if(h!=-1){a=a.substr(0,h).replace(/\s+$/g,"")}}else{if(c=new RegExp("^(.+?)("+g.join("|")+")").exec((b+"").substring(d))){a=c[1];d+=a.length}else{throw new InvalidArgumentException("Malformed inline YAML string ("+b+").")}}a=f?this.evaluateScalar(a):a}this.i=d;return a},parseQuotedScalar:function(b,d){var c=null;if(!(c=new RegExp("^"+YamlInline.REGEX_QUOTED_STRING).exec((b+"").substring(d)))){throw new InvalidArgumentException("Malformed inline YAML string ("+(b+"").substring(d)+").")}var a=c[0].substr(1,c[0].length-2);if('"'==(b+"").charAt(d)){a=a.split('\\"').join('"').split("\\n").join("\n").split("\\r").join("\r")}else{a=a.split("''").join("'")}d+=c[0].length;this.i=d;return a},parseSequence:function(g,c){if(c==undefined){c=0}var b=[];var a=g.length;c+=1;while(c<a){switch(g.charAt(c)){case"[":b.push(this.parseSequence(g,c));c=this.i;break;case"{":b.push(this.parseMapping(g,c));c=this.i;break;case"]":this.i=c;return b;case",":case" ":break;default:isQuoted=this.inArray(g.charAt(c),['"',"'"]);var d=this.parseScalar(g,[",","]"],['"',"'"],c);c=this.i;if(!isQuoted&&(d+"").indexOf(": ")!=-1){try{d=this.parseMapping("{"+d+"}")}catch(f){if(!(f instanceof InvalidArgumentException)){throw f}}}b.push(d);c--}c++}throw new InvalidArgumentException("Malformed inline YAML string "+g)},parseMapping:function(d,f){if(f==undefined){f=0}var c={};var a=d.length;f+=1;var b=false;var g=false;while(f<a){g=false;switch(d.charAt(f)){case" ":case",":f++;g=true;break;case"}":this.i=f;return c}if(g){continue}var e=this.parseScalar(d,[":"," "],['"',"'"],f,false);f=this.i;b=false;while(f<a){switch(d.charAt(f)){case"[":c[e]=this.parseSequence(d,f);f=this.i;b=true;break;case"{":c[e]=this.parseMapping(d,f);f=this.i;b=true;break;case":":case" ":break;default:c[e]=this.parseScalar(d,[",","}"],['"',"'"],f);f=this.i;b=true;f--}++f;if(b){g=true;break}}if(g){continue}}throw new InvalidArgumentException("Malformed inline YAML string "+d)},evaluateScalar:function(b){b=this.trim(b);var e;var d;var f=new Yaml();if("1.1"==f.getSpecVersion()){e=["true","on","+","yes","y"];d=["false","off","-","no","n"]}else{e=["true"];d=["false"]}var c=null;var a=null;if(("null"==b.toLowerCase())||(""==b)||("~"==b)){return null}if((b+"").indexOf("!str")!=-1){return(""+b).substring(5)}if((b+"").indexOf("! ")!=-1){return parseInt(this.parseScalar((b+"").substring(2)))}if(/^\d+/.test(b)){c=b;a=parseInt(b);return"0"==b.charAt(0)?this.octdec(b):((""+c==""+a)?a:c)}if(this.inArray(b.toLowerCase(),e)){return true}if(this.inArray(b.toLowerCase(),d)){return false}if(this.isNumeric(b)){return"0x"==(b+"").substr(0,2)?hexdec($scalar):floatval($scalar)}if(b.toLowerCase()==".inf"){return Infinity}if(b.toLowerCase()==".nan"){return NaN}if(b.toLowerCase()=="-.inf"){return -Infinity}if(/^(-|\+)?[0-9,]+(\.[0-9]+)?$/.test(b)){return parseFloat(b.split(",").join(""))}if(this.getTimestampRegex().test(b)){return this.strtodate(b)}return""+b},getTimestampRegex:function(){return new RegExp("^([0-9][0-9][0-9][0-9])-([0-9][0-9]?)-([0-9][0-9]?)(?:(?:[Tt]|[ \t]+)([0-9][0-9]?):([0-9][0-9]):([0-9][0-9])(?:.([0-9]*))?(?:[ \t]*(Z|([-+])([0-9][0-9]?)(?::([0-9][0-9]))?))?)?$","gi")},trim:function(a){return(a+"").replace(/^\s+/,"").replace(/\s+$/,"")},isNumeric:function(a){return(a-0)==a&&a.length>0&&a.replace(/\s+/g,"")!=""},inArray:function(c,d){var b;var a=d.length;for(b=0;b<a;b++){if(c==d[b]){return true}}return false},getKeys:function(c){var b=[];for(var a in c){if(c.hasOwnProperty(a)){b.push(a)}}return b},octdec:function(a){return parseInt((a+"").replace(/[^0-7]/gi,""),8)},hexdec:function(a){a=this.trim(a);if((a+"").substr(0,2)=="0x"){a=(a+"").substring(2)}return parseInt((a+"").replace(/[^a-f0-9]/gi,""),16)},strtodate:function(a){var b=new Date();b.setTime(this.strtotime(a,new Date().getTime()));return b},strtotime:function(o,t){var q,p,i,m="",s="";m=o;m=m.replace(/\s{2,}|^\s|\s$/g," ");m=m.replace(/[\t\r\n]/g,"");if(m=="now"){return(new Date()).getTime()/1000}else{if(!isNaN(s=Date.parse(m))){return(s/1000)}else{if(t){t=new Date(t*1000)}else{t=new Date()}}}m=m.toLowerCase();var r={day:{sun:0,mon:1,tue:2,wed:3,thu:4,fri:5,sat:6},mon:{jan:0,feb:1,mar:2,apr:3,may:4,jun:5,jul:6,aug:7,sep:8,oct:9,nov:10,dec:11}};var v=this.strtotime;var u=function(a){var c=(a[2]&&a[2]=="ago");var d=(d=a[0]=="last"?-1:1)*(c?-1:1);switch(a[0]){case"last":case"next":switch(a[1].substring(0,3)){case"yea":t.setFullYear(t.getFullYear()+d);break;case"mon":t.setMonth(t.getMonth()+d);break;case"wee":t.setDate(t.getDate()+(d*7));break;case"day":t.setDate(t.getDate()+d);break;case"hou":t.setHours(t.getHours()+d);break;case"min":t.setMinutes(t.getMinutes()+d);break;case"sec":t.setSeconds(t.getSeconds()+d);break;default:var e;if(typeof(e=r.day[a[1].substring(0,3)])!="undefined"){var b=e-t.getDay();if(b==0){b=7*d}else{if(b>0){if(a[0]=="last"){b-=7}}else{if(a[0]=="next"){b+=7}}}t.setDate(t.getDate()+b)}}break;default:if(/\d+/.test(a[0])){d*=parseInt(a[0],10);switch(a[1].substring(0,3)){case"yea":t.setFullYear(t.getFullYear()+d);break;case"mon":t.setMonth(t.getMonth()+d);break;case"wee":t.setDate(t.getDate()+(d*7));break;case"day":t.setDate(t.getDate()+d);break;case"hou":t.setHours(t.getHours()+d);break;case"min":t.setMinutes(t.getMinutes()+d);break;case"sec":t.setSeconds(t.getSeconds()+d);break}}else{return false}break}return true};p=m.match(/^(\d{2,4}-\d{2}-\d{2})(?:\s(\d{1,2}:\d{2}(:\d{2})?)?(?:\.(\d+))?)?$/);if(p!=null){if(!p[2]){p[2]="00:00:00"}else{if(!p[3]){p[2]+=":00"}}i=p[1].split(/-/g);for(q in r.mon){if(r.mon[q]==i[1]-1){i[1]=q}}i[0]=parseInt(i[0],10);i[0]=(i[0]>=0&&i[0]<=69)?"20"+(i[0]<10?"0"+i[0]:i[0]+""):(i[0]>=70&&i[0]<=99)?"19"+i[0]:i[0]+"";return parseInt(v(i[2]+" "+i[1]+" "+i[0]+" "+p[2])+(p[4]?p[4]/1000:""),10)}var n="([+-]?\\d+\\s(years?|months?|weeks?|days?|hours?|min|minutes?|sec|seconds?|sun\\.?|sunday|mon\\.?|monday|tue\\.?|tuesday|wed\\.?|wednesday|thu\\.?|thursday|fri\\.?|friday|sat\\.?|saturday)|(last|next)\\s(years?|months?|weeks?|days?|hours?|min|minutes?|sec|seconds?|sun\\.?|sunday|mon\\.?|monday|tue\\.?|tuesday|wed\\.?|wednesday|thu\\.?|thursday|fri\\.?|friday|sat\\.?|saturday))(\\sago)?";p=m.match(new RegExp(n,"gi"));if(p==null){return false}for(q=0;q<p.length;q++){if(!u(p[q].split(" "))){return false}}return(t.getTime()/1000)}};YamlInline.REGEX_QUOTED_STRING="(?:\"(?:[^\"\\\\]*(?:\\\\.[^\"\\\\]*)*)\"|'(?:[^']*(?:''[^']*)*)')";var YamlParser=function(a){this.offset=this.isDefined(a)?a:0};YamlParser.prototype={offset:0,lines:[],currentLineNb:-1,currentLine:"",refs:{},parse:function(m){this.currentLineNb=-1;this.currentLine="";this.lines=this.cleanup(m).split("\n");var u=null;while(this.moveToNextLine()){if(this.isCurrentLineEmpty()){continue}if(/^\t+/.test(this.currentLine)){throw new InvalidArgumentException("A YAML file cannot contain tabs as indentation at line "+(this.getRealCurrentLineNb()+1)+" ("+this.currentLine+")")}var j=false;var r=false;var q=false;var b=null;var a=null;var t=null;var d=null;var e=null;var v=null;var h=null;var p=null;var f=null;if(b=/^\-((\s+)(.+?))?\s*$/.exec(this.currentLine)){if(!this.isDefined(u)){u=[]}if(!(u instanceof Array)){throw new InvalidArgumentException("Non array entry at line "+(this.getRealCurrentLineNb()+1)+".")}b={leadspaces:b[2],value:b[3]};if(this.isDefined(b.value)&&(a=/^&([^ ]+) *(.*)/.exec(b.value))){a={ref:a[1],value:a[2]};j=a.ref;b.value=a.value}if(!this.isDefined(b.value)||""==b.value.split(" ").join("")||this.trim(b.value).charAt(0)=="#"){t=this.getRealCurrentLineNb()+1;d=new YamlParser(t);d.refs=this.refs;u.push(d.parse(this.getNextEmbedBlock()));this.refs=d.refs}else{if(this.isDefined(b.leadspaces)&&" "==b.leadspaces&&(a=new RegExp("^("+YamlInline.REGEX_QUOTED_STRING+"|[^ '\"{].*?) *:(\\s+(.+?))?\\s*$").exec(b.value))){a={key:a[1],value:a[3]};t=this.getRealCurrentLineNb();d=new YamlParser(t);d.refs=this.refs;e=b.value;if(!this.isNextLineIndented()){e+="\n"+this.getNextEmbedBlock(this.getCurrentLineIndentation()+2)}u.push(d.parse(e));this.refs=d.refs}else{u.push(this.parseValue(b.value))}}}else{if(b=new RegExp("^("+YamlInline.REGEX_QUOTED_STRING+"|[^ '\"].*?) *:(\\s+(.+?))?\\s*$").exec(this.currentLine)){if(!this.isDefined(u)){u={}}if(u instanceof Array){throw new InvalidArgumentException("Non mapped entry at line "+(this.getRealCurrentLineNb()+1)+".")}b={key:b[1],value:b[3]};v=(new YamlInline()).parseScalar(b.key);if("<<"==v){if(this.isDefined(b.value)&&"*"==(b.value+"").charAt(0)){r=b.value.substring(1)}else{if(this.isDefined(b.value)&&b.value!=""){m=b.value}else{m=this.getNextEmbedBlock()}t=this.getRealCurrentLineNb()+1;d=new YamlParser(t);d.refs=this.refs;h=d.parse(m);this.refs=d.refs;var s=[];if(!this.isObject(h)){throw new InvalidArgumentException("YAML merge keys used with a scalar value instead of an array at line "+(this.getRealCurrentLineNb()+1)+" ("+this.currentLine+")")}else{if(this.isDefined(h[0])){f=this.reverseArray(h);p=f.length;for(var o=0;o<p;o++){var l=f[o];if(!this.isObject(f[o])){throw new InvalidArgumentException("Merge items must be arrays at line "+(this.getRealCurrentLineNb()+1)+" ("+f[o]+").")}s=this.mergeObject(f[o],s)}}else{s=this.mergeObject(s,h)}}q=s}}else{if(this.isDefined(b.value)&&(a=/^&([^ ]+) *(.*)/.exec(b.value))){a={ref:a[1],value:a[2]};j=a.ref;b.value=a.value}}if(q){u=q}else{if(!this.isDefined(b.value)||""==b.value.split(" ").join("")||this.trim(b.value).charAt(0)=="#"){if(this.isNextLineIndented()){u[v]=null}else{t=this.getRealCurrentLineNb()+1;d=new YamlParser(t);d.refs=this.refs;u[v]=d.parse(this.getNextEmbedBlock());this.refs=d.refs}}else{if(r){u=this.refs[r]}else{u[v]=this.parseValue(b.value)}}}}else{if(2==this.lines.length&&this.isEmpty(this.lines[1])){m=(new YamlInline()).load(this.lines[0]);if(this.isObject(m)){first=m[0];if("*"==(first+"").substr(0,1)){u=[];p=m.length;for(var o=0;o<p;o++){u.push(this.refs[m[o].substring(1)])}m=u}}return m}throw new InvalidArgumentException('"'+this.currentLine+'" at line '+(this.getRealCurrentLineNb()+1))}}if(j){if(u instanceof Array){this.refs[j]=u[u.length-1]}else{var g=null;for(var n in u){if(u.hasOwnProperty(n)){g=n}}this.refs[j]=u[n]}}}return this.isEmpty(u)?null:u},getRealCurrentLineNb:function(){return this.currentLineNb+this.offset},getCurrentLineIndentation:function(){return this.currentLine.length-this.currentLine.replace(/^ +/g,"").length},getNextEmbedBlock:function(d){this.moveToNextLine();var b=null;var a=null;if(!this.isDefined(d)){b=this.getCurrentLineIndentation();if(!this.isCurrentLineEmpty()&&0==b){throw new InvalidArgumentException("A Indentation problem at line "+(this.getRealCurrentLineNb()+1)+" ("+this.currentLine+")")}}else{b=d}var e=[this.currentLine.substring(b)];while(this.moveToNextLine()){if(this.isCurrentLineEmpty()){if(this.isCurrentLineBlank()){e.push(this.currentLine.substring(b))}continue}a=this.getCurrentLineIndentation();var c;if(c=/^( *)$/.exec(this.currentLine)){e.push(c[1])}else{if(a>=b){e.push(this.currentLine.substring(b))}else{if(0==a){this.moveToPreviousLine();break}else{throw new InvalidArgumentException("B Indentation problem at line "+(this.getRealCurrentLineNb()+1)+" ("+this.currentLine+")")}}}}return e.join("\n")},moveToNextLine:function(){if(this.currentLineNb>=this.lines.length-1){return false}this.currentLineNb++;this.currentLine=this.lines[this.currentLineNb];return true},moveToPreviousLine:function(){this.currentLineNb--;this.currentLine=this.lines[this.currentLineNb]},parseValue:function(c){if("*"==(c+"").charAt(0)){if(this.trim(c).charAt(0)=="#"){c=(c+"").substr(1,c.indexOf("#")-2)}else{c=(c+"").substring(1)}if(this.refs[c]==undefined){throw new InvalidArgumentException('Reference "'+c+'" does not exist ('+this.currentLine+").")}return this.refs[c]}var b=null;if(b=/^(\||>)(\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?( +#.*)?$/.exec(c)){b={separator:b[1],modifiers:b[2],comments:b[3]};var a=this.isDefined(b.modifiers)?b.modifiers:"";return this.parseFoldedScalar(b.separator,a.replace(/\d+/g,""),Math.abs(parseInt(a)))}else{return(new YamlInline()).load(c)}},parseFoldedScalar:function(c,h,f){if(h==undefined){h=""}if(f==undefined){f=0}c="|"==c?"\n":" ";var j="";var g=null;var b=this.moveToNextLine();while(b&&this.isCurrentLineBlank()){j+="\n";b=this.moveToNextLine()}if(!b){return""}var d=null;if(!(d=new RegExp("^("+(f?this.strRepeat(" ",f):" +")+")(.*)$").exec(this.currentLine))){this.moveToPreviousLine();return""}d={indent:d[1],text:d[2]};var a=d.indent;var e=0;j+=d.text+c;while(this.currentLineNb+1<this.lines.length){this.moveToNextLine();if(d=new RegExp("^( {"+a.length+",})(.+)$").exec(this.currentLine)){d={indent:d[1],text:d[2]};if(" "==c&&e!=d.indent){j=j.substr(0,j.length-1)+"\n"}e=d.indent;g=d.indent.length-a.length;j+=this.strRepeat(" ",g)+d.text+(g!=0?"\n":c)}else{if(d=/^( *)$/.exec(this.currentLine)){j+=d[1].replace(new RegExp("^ {1,"+a.length+"}","g"),"",d[1])+"\n"}else{this.moveToPreviousLine();break}}}if(" "==c){j=j.replace(/ (\n*)$/g,"\n$1")}switch(h){case"":j=j.replace(/\n+$/g,"\n");break;case"+":break;case"-":j=j.replace(/\n+$/g,"");break}return j},isNextLineIndented:function(){var b=this.getCurrentLineIndentation();var c=this.moveToNextLine();while(c&&this.isCurrentLineEmpty()){c=this.moveToNextLine()}if(false==c){return false}var a=false;if(this.getCurrentLineIndentation()<=b){a=true}this.moveToPreviousLine();return a},isCurrentLineEmpty:function(){return this.isCurrentLineBlank()||this.isCurrentLineComment()},isCurrentLineBlank:function(){return""==this.currentLine.split(" ").join("")},isCurrentLineComment:function(){var a=this.currentLine.replace(/^ +/g,"");return a.charAt(0)=="#"},cleanup:function(c){c=c.split("\r\n").join("\n").split("\r").join("\n");if(!/\n$/.test(c)){c+="\n"}var b=0;var a=/^\%YAML[: ][\d\.]+.*\n/;while(a.test(c)){c=c.replace(a,"");b++}this.offset+=b;a=/^(#.*?\n)+/;if(a.test(c)){trimmedValue=c.replace(a,"");this.offset+=this.subStrCount(c,"\n")-this.subStrCount(trimmedValue,"\n");c=trimmedValue}a=/^\-\-\-.*?\n/;if(a.test(c)){trimmedValue=c.replace(a,"");this.offset+=this.subStrCount(c,"\n")-this.subStrCount(trimmedValue,"\n");c=trimmedValue;c=c.replace(/\.\.\.\s*$/g,"")}return c},isObject:function(a){return typeof(a)=="object"&&this.isDefined(a)},isEmpty:function(a){return a==undefined||a==null||a==""||a==0||a=="0"||a==false},isDefined:function(a){return a!=undefined&&a!=null},reverseArray:function(c){var b=[];var a=c.length;for(var d=a-1;d>=0;d--){b.push(c[d])}return b},merge:function(e,d){var f={};for(i in e){if(/^\d+$/.test(i)){f.push(e)}else{f[i]=e[i]}}for(i in d){if(/^\d+$/.test(i)){f.push(d)}else{f[i]=d[i]}}return f},strRepeat:function(d,c){var b;var a="";for(b=0;b<c;b++){a+=d}return d},subStrCount:function(d,b,j,f){var h=0;d=""+d;b=""+b;if(j!=undefined){d=d.substr(j)}if(f!=undefined){d=d.substr(0,f)}var a=d.length;var g=b.length;for(var e=0;e<a;e++){if(b==d.substr(e,g)){h++}}return h},trim:function(a){return(a+"").replace(/^\s+/,"").replace(/\s+$/,"")}};YamlDumper=function(){};YamlDumper.prototype={dump:function(g,f,c){if(f==undefined){f=0}if(c==undefined){c=0}var b="";var e=c?this.strRepeat(" ",c):"";var i;if(f<=0||!this.isObject(g)||this.isEmpty(g)){i=new YamlInline();b+=e+i.dump(g)}else{var d=!this.arrayEquals(this.getKeys(g),this.range(0,g.length-1));var a;for(var h in g){if(g.hasOwnProperty(h)){a=f-1<=0||!this.isObject(g[h])||this.isEmpty(g[h]);if(d){i=new YamlInline()}b+=e+""+(d?i.dump(h)+":":"-")+""+(a?" ":"\n")+""+this.dump(g[h],f-1,(a?0:c+2))+""+(a?"\n":"")}}}return b},strRepeat:function(d,c){var b;var a="";for(b=0;b<c;b++){a+=d}return d},isObject:function(a){return typeof(a)=="object"&&this.isDefined(a)},isEmpty:function(a){return a==undefined||a==null||a==""||a==0||a=="0"||a==false},isDefined:function(a){return a!=undefined&&a!=null},getKeys:function(c){var b=[];for(var a in c){if(c.hasOwnProperty(a)){b.push(a)}}return b},range:function(d,a){if(d>a){return[]}var b=[];for(var c=d;c<=a;c++){b.push(c)}return b},arrayEquals:function(e,d){if(e.length!=d.length){return false}var c=e.length;for(var f=0;f<c;f++){if(e[f]!=d[f]){return false}}return true}};
@@ -0,0 +1,65 @@
1
+ require 'tempfile'
2
+ require 'fileutils'
3
+ require 'yaml'
4
+ require 'open3'
5
+
6
+ module QUnited
7
+ module JsRunner
8
+ class Rhino < Base
9
+ def can_run?
10
+ # TODO: test that you have Java
11
+ end
12
+
13
+ def run
14
+ js_dir = File.expand_path('../rhino/js', __FILE__)
15
+
16
+ js_jar, runner = File.join(js_dir, 'js.jar'), File.join(js_dir, 'qunit-runner.js')
17
+
18
+ source_files_args = @source_files.map { |sf| %{"#{sf}"} }.join(' ')
19
+ test_files_args = @test_files.map { |tf| %{"#{tf}"} }.join(' ')
20
+
21
+ tmp_file = Tempfile.new('qunited_results')
22
+ tmp_file.close
23
+
24
+ cmd = %{java -jar "#{js_jar}" -opt -1 "#{runner}" "#{js_dir}" "#{tmp_file.path}"}
25
+ cmd << " #{source_files_args} -- #{test_files_args}"
26
+
27
+ # Swallow stdout but allow stderr to get blasted out to console - if there are uncaught
28
+ # exceptions or anything else that goes wrong with the JavaScript interpreter the user
29
+ # will probably want to know but we are not particularly interested in it.
30
+ Open3.popen3(cmd) do |stdin, stdout, stderr|
31
+ stdout.each {||} # Ignore; this is just here to make sure we block
32
+ # while waiting for tests to finish
33
+ unless (err = stderr.read).strip.empty? then $stderr.puts(err) end
34
+ end
35
+
36
+ @raw_results = clean_up_results(YAML.load(IO.read(tmp_file)))
37
+
38
+ @results = ::QUnited::Results.new @raw_results
39
+ self
40
+ end
41
+
42
+ private
43
+
44
+ def clean_up_results(results)
45
+ results.map! { |mod_results| symbolize_keys mod_results }
46
+ results.each do |mod_results|
47
+ mod_results[:tests].map! { |test| clean_up_test_results(symbolize_keys(test)) }
48
+ end
49
+ end
50
+
51
+ def clean_up_test_results(test_results)
52
+ test_results[:start] = DateTime.parse(test_results[:start])
53
+ test_results[:duration] = Float(test_results[:duration])
54
+ test_results[:assertion_data].map! { |data| symbolize_keys data }
55
+ test_results
56
+ end
57
+
58
+ def symbolize_keys(hash)
59
+ new_hash = {}
60
+ hash.keys.each { |key| new_hash[key.to_sym] = hash[key] }
61
+ new_hash
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,2 @@
1
+ require 'qunited/js_runner/base'
2
+ require 'qunited/js_runner/rhino'
@@ -0,0 +1,87 @@
1
+ module QUnited
2
+ class RakeTask < ::Rake::TaskLib
3
+ include ::Rake::DSL if defined?(::Rake::DSL)
4
+
5
+ # Name of task.
6
+ #
7
+ # default:
8
+ # :qunited
9
+ attr_accessor :name
10
+
11
+ # Glob pattern to match JavaScript source files (and any dependencies). Note that the
12
+ # order will be indeterminate so if your JavaScript files must be included in a particular
13
+ # order you will have to use source_files=(files_array).
14
+ #
15
+ # If an array of files is set with source_files=(files_array) then this will be ignored.
16
+ #
17
+ # default (unless source_files array is set):
18
+ # 'app/assets/javascripts/**/*.js'
19
+ attr_accessor :source_files_pattern
20
+
21
+ # Array of JavaScript source files (and any dependencies). These will be loaded in order
22
+ # before loading the QUnit tests.
23
+ attr_accessor :source_files
24
+
25
+ # Glob pattern to match QUnit test files.
26
+ #
27
+ # If an array of test files is set with test_files=(files) then this will be ignored.
28
+ #
29
+ # default:
30
+ # 'test/javascripts/**/*.js'
31
+ attr_accessor :test_files_pattern
32
+
33
+ # Array of QUnit test files.
34
+ attr_accessor :test_files
35
+
36
+ # Use verbose output. If this is true, the task will print the QUnited command to stdout.
37
+ #
38
+ # default:
39
+ # true
40
+ attr_accessor :verbose
41
+
42
+ def initialize(*args)
43
+ @name = args.shift || :qunited
44
+
45
+ yield self if block_given?
46
+
47
+ desc('Run QUnit JavaScript tests') unless ::Rake.application.last_comment
48
+
49
+ task name do
50
+ RakeFileUtils.send(:verbose, verbose) do
51
+ if source_files_to_include.empty?
52
+ msg = "No JavaScript source files specified"
53
+ msg << " with pattern #{source_files_pattern}" if source_files_pattern
54
+ puts msg
55
+ elsif test_files_to_run.empty?
56
+ puts "No QUnit test files matching #{test_files_pattern} could be found"
57
+ else
58
+ begin
59
+ puts command if verbose
60
+ success = system(command)
61
+ rescue
62
+ end
63
+ raise "#{command} failed" unless success
64
+ end
65
+ end
66
+ end
67
+ end
68
+
69
+ private
70
+
71
+ def command
72
+ "qunited #{source_files_to_include.join(' ')} -- #{test_files_to_run.join(' ')}"
73
+ end
74
+
75
+ def source_files_to_include
76
+ source_files || pattern_to_filelist(source_files_pattern)
77
+ end
78
+
79
+ def test_files_to_run
80
+ test_files || pattern_to_filelist(test_files_pattern)
81
+ end
82
+
83
+ def pattern_to_filelist(pattern)
84
+ FileList[pattern].map { |f| f.gsub(/"/, '\"').gsub(/'/, "\\\\'") }
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,164 @@
1
+ module QUnited
2
+
3
+ # Simple tests results compiler. Takes a raw results hash that was produced by a runner.
4
+ class Results
5
+ class ModuleResults
6
+ def initialize(data)
7
+ @data = data
8
+ end
9
+
10
+ def tests
11
+ @tests ||= @data[:tests].map { |test_data| TestResults.new test_data, @data[:name] }
12
+ end
13
+ end
14
+
15
+ class TestResults
16
+ def initialize(data, module_name)
17
+ @data, @module_name = data, module_name
18
+ end
19
+
20
+ def assertions
21
+ @data[:assertion_data].map do |assertion_data|
22
+ AssertionResults.new assertion_data, @data[:name], @module_name, @data[:file]
23
+ end
24
+ end
25
+
26
+ def passed?; result == :passed end
27
+ def failed?; result == :failed end
28
+ def error?; result == :error end
29
+
30
+ def result
31
+ @result ||= if assertions.find { |a| a.error? }
32
+ :error
33
+ else
34
+ assertions.find { |a| a.failed? } ? :failed : :passed
35
+ end
36
+ end
37
+
38
+ def duration; @data[:duration] end
39
+
40
+ def to_s; passed? ? '.' : (error? ? 'E' : 'F') end
41
+ end
42
+
43
+ class AssertionResults
44
+ def initialize(data, test_name, module_name, file)
45
+ @data, @test_name, @module_name, @file = data, test_name, module_name, file
46
+ end
47
+
48
+ def message
49
+ @data[:message]
50
+ end
51
+
52
+ def result
53
+ if @data[:result]
54
+ :passed
55
+ else
56
+ @data[:message] =~ /^Died on test/ ? :error : :failed
57
+ end
58
+ end
59
+
60
+ def passed?; result == :passed end
61
+ def failed?; result == :failed end
62
+ def error?; result == :error end
63
+
64
+ def output(count)
65
+ return "" if passed?
66
+ msg = " " + (count ? "#{count.to_s}) " : "")
67
+ msg << "#{error? ? 'Error' : 'Failure'}:\n"
68
+ msg << "#{@test_name} (#{@module_name}) [#{@file}]\n"
69
+ msg << "#{@data[:message] || 'Failed assertion, no message given.'}\n"
70
+
71
+ # Results can be nil. Also, JavaScript nulls will be converted, by the YAML serializer, to
72
+ # Ruby nil. Convert that back to 'null' for the output.
73
+ if @data.key? :expected
74
+ expected, actual = @data[:expected], @data[:actual]
75
+ msg << "Expected: #{expected.nil? ? 'null' : expected.inspect}\n"
76
+ msg << " Actual: #{actual.nil? ? 'null' : actual.inspect}\n"
77
+ end
78
+ msg
79
+ end
80
+ end
81
+
82
+ def initialize(modules_results_array)
83
+ @data = modules_results_array.freeze
84
+ @module_results = @data.map { |module_data| ModuleResults.new module_data }
85
+ end
86
+
87
+ def to_s
88
+ return <<-OUTPUT
89
+ #{dots}
90
+ #{"\n\n#{failures_output}\n\n" unless failures_output.empty?}
91
+ #{times_line}
92
+
93
+ #{bottom_line}
94
+ OUTPUT
95
+ end
96
+
97
+ def to_i
98
+ passed? ? 0 : 1
99
+ end
100
+
101
+ def passed?
102
+ total_failures.zero? && total_errors.zero?
103
+ end
104
+
105
+ def failed?
106
+ !passed?
107
+ end
108
+
109
+ def dots
110
+ tests.map { |test| test.to_s }.join
111
+ end
112
+
113
+ def bottom_line
114
+ "#{total_tests} tests, #{total_assertions} assertions, " +
115
+ "#{total_failures} failures, #{total_errors} errors, 0 skips"
116
+ end
117
+
118
+ def times_line
119
+ "Finished in #{"%.6g" % total_time} seconds, #{"%.6g" % (total_tests/total_time)} tests/s, " +
120
+ "#{"%.6g" % (total_assertions/total_time)} assertions/s."
121
+ end
122
+
123
+ def failures_output
124
+ failures_output_array.join("\n")
125
+ end
126
+
127
+ # Array of failure output block strings
128
+ def failures_output_array
129
+ return @failures_output_array if @failures_output_array
130
+ count = 0
131
+ @failures_output_array = (failures + errors).map { |failure| failure.output(count += 1) }
132
+ end
133
+
134
+ def total_tests
135
+ @total_tests ||= @module_results.inject(0) { |count, mod| count += mod.tests.size }
136
+ end
137
+
138
+ def total_assertions; assertions.size end
139
+ def total_failures; failures.size end
140
+ def total_errors; errors.size end
141
+
142
+ def total_time
143
+ @total_time ||= tests.inject(0) { |total, test| total += test.duration }
144
+ end
145
+
146
+ private
147
+
148
+ def tests
149
+ @tests ||= @module_results.inject([]) { |tests, mod| tests += mod.tests }
150
+ end
151
+
152
+ def assertions
153
+ @assertions ||= tests.inject([]) { |asserts, test| asserts += test.assertions }
154
+ end
155
+
156
+ def failures
157
+ @failures ||= assertions.select { |assert| assert.failed? }
158
+ end
159
+
160
+ def errors
161
+ @errors ||= assertions.select { |assert| assert.error? }
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,23 @@
1
+ module QUnited
2
+ class Runner
3
+ def self.run(js_source_files, js_test_files)
4
+ js_runner_klass = self.js_runner
5
+ # TODO: test that this JsRunner can run with current environment
6
+ runner = js_runner_klass.new(js_source_files, js_test_files)
7
+
8
+ puts "\n# Running JavaScript tests with #{runner.name}:\n\n"
9
+
10
+ results = runner.run.results
11
+ puts results
12
+ results.to_i
13
+ end
14
+
15
+ # Get the runner that we will be using to run the JavaScript tests.
16
+ #
17
+ # Right now we only have one JavaScript runner, but when we have multiple we will have to
18
+ # determine which one we will used unless explicitly configured.
19
+ def self.js_runner
20
+ ::QUnited::JsRunner::Rhino
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,3 @@
1
+ module QUnited
2
+ VERSION = "0.0.1"
3
+ end
data/lib/qunited.rb ADDED
@@ -0,0 +1,4 @@
1
+ require 'qunited/version'
2
+ require 'qunited/runner'
3
+ require 'qunited/results'
4
+ require 'qunited/js_runner'
data/qunited.gemspec ADDED
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "qunited/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "qunited"
7
+ s.version = QUnited::VERSION
8
+ s.authors = ["Aaron Royer"]
9
+ s.email = ["aaronroyer@gmail.com"]
10
+ s.homepage = "https://github.com/aaronroyer/qunited"
11
+ s.summary = %q{QUnit tests in your build}
12
+ s.description = %q{QUnited runs headless QUnit tests as part of your normal build}
13
+
14
+ s.rubyforge_project = "qunited"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+ end
@@ -0,0 +1,6 @@
1
+
2
+ var APP = {};
3
+
4
+ APP.one = function() {
5
+ return 1;
6
+ };
@@ -0,0 +1,6 @@
1
+ // Leave in default module
2
+
3
+ test("The source code was loaded", function() {
4
+ expect(1);
5
+ equal(APP.one(), 1, "We have loaded it");
6
+ });
@@ -0,0 +1,12 @@
1
+ module("Math");
2
+
3
+ test("Addition works", function() {
4
+ expect(2);
5
+ equal(1 + 1, 2, "One plus one does equal two");
6
+ equal(2 + 2, 4, "Two plus two does equal four");
7
+ });
8
+
9
+ test("Subtraction works", function() {
10
+ expect(1);
11
+ equal(2 - 1, 1, "Two minus one equals one");
12
+ });
@@ -0,0 +1,6 @@
1
+ // Put a div on it
2
+ var div = document.createElement('div');
3
+ var attr = document.createAttribute('id');
4
+ attr.nodeValue = 'the-only-div';
5
+ div.setAttributeNode(attr);
6
+ document.body.appendChild(div);
@@ -0,0 +1,5 @@
1
+ module("Miscellaneous")
2
+ test("Test that we have a body and that one element", function() {
3
+ equal(document.getElementsByTagName("body").length, 1, "We have a body");
4
+ ok(document.getElementById('the-only-div'), "Found our element");
5
+ });
@@ -0,0 +1,5 @@
1
+ var APP = {};
2
+
3
+ APP.one = function() {
4
+ return 1;
5
+ };
@@ -0,0 +1,7 @@
1
+
2
+ var APP = {
3
+ // Syntax error
4
+
5
+ APP.one = function() {
6
+ return 1;
7
+ };
@@ -0,0 +1,9 @@
1
+ var DEFINED = {
2
+ gotIt: true
3
+ };
4
+
5
+ bogusFunction(); // Undefined and will stop interpretation
6
+
7
+ var UNDEFINED = {
8
+ gotIt: false
9
+ };
@@ -0,0 +1,5 @@
1
+ module("Test has no error");
2
+
3
+ test("Test is fine", function() {
4
+ ok(true);
5
+ });
@@ -0,0 +1,4 @@
1
+ // No tests in here
2
+ (function() {
3
+ var two = 1 + 1;
4
+ })();
@@ -0,0 +1,10 @@
1
+ module("Math");
2
+
3
+ test("Addition is hard", function() {
4
+ var obj = {key = "value"}; // Doing it wrong
5
+ });
6
+
7
+ test("This expects the wrong number of assertions", function() {
8
+ expect(2);
9
+ equal(2 - 1, 1, "Two minus one equals one");
10
+ });
@@ -0,0 +1,10 @@
1
+ module("Basics");
2
+
3
+ test("This has one failure", function() {
4
+ bogusFunction(); // undefined error
5
+ });
6
+
7
+ test("This has no failures", function() {
8
+ expect(1);
9
+ equal(APP.one(), 1, "It is 1");
10
+ });
@@ -0,0 +1,5 @@
1
+ var APP = {};
2
+
3
+ APP.one = function() {
4
+ return 1;
5
+ };
@@ -0,0 +1,11 @@
1
+ module("Basics");
2
+
3
+ test("This has one failure", function() {
4
+ expect(1);
5
+ ok(false); // No message
6
+ });
7
+
8
+ test("This has no failures", function() {
9
+ expect(1);
10
+ equal(APP.one(), 1, "It is 1");
11
+ });
@@ -0,0 +1,12 @@
1
+ module("Math");
2
+
3
+ test("Addition is hard", function() {
4
+ expect(2);
5
+ equal(1 + 1, 3, "This math is wrong");
6
+ equal(2 + 2, 5, "This math is also wrong");
7
+ });
8
+
9
+ test("This expects the wrong number of assertions", function() {
10
+ expect(2);
11
+ equal(2 - 1, 1, "Two minus one equals one");
12
+ });
@@ -0,0 +1,5 @@
1
+ require 'minitest/autorun'
2
+
3
+ require File.join(File.dirname(__FILE__), *%w[.. lib qunited])
4
+
5
+ FIXTURES_DIR = File.expand_path('../fixtures', __FILE__)