delinquo 0.0.3

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 (45) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +39 -0
  6. data/Rakefile +65 -0
  7. data/bin/delinquo +14 -0
  8. data/config/database.rb +18 -0
  9. data/config.ru +7 -0
  10. data/db/migrate/20130925071434_create_projects.rb +13 -0
  11. data/db/migrate/20130927101642_create_projects.rb +13 -0
  12. data/db/migrate/20130927101721_create_runs.rb +14 -0
  13. data/db/migrate/20130927104041_create_fails.rb +17 -0
  14. data/db/migrate/20130927161238_change_run_total_to_int.rb +5 -0
  15. data/db/migrate/20130927161545_change_run_total_to_string.rb +5 -0
  16. data/db/schema.rb +44 -0
  17. data/delinquo.gemspec +31 -0
  18. data/lib/delinquo/version.rb +3 -0
  19. data/lib/delinquo.rb +112 -0
  20. data/lib/public/css/bootstrap.css +4692 -0
  21. data/lib/public/css/bootstrap.min.css +9 -0
  22. data/lib/public/css/main.css +132 -0
  23. data/lib/public/images/changeable.svg +51 -0
  24. data/lib/public/images/cloud.svg +3 -0
  25. data/lib/public/images/rain.svg +7 -0
  26. data/lib/public/images/storm.svg +3 -0
  27. data/lib/public/images/sun.svg +3 -0
  28. data/lib/public/images/umbrella.svg +3 -0
  29. data/lib/public/index.html +9 -0
  30. data/lib/public/js/bootstrap.js +1966 -0
  31. data/lib/public/js/bootstrap.min.js +6 -0
  32. data/lib/views/index.erb +56 -0
  33. data/lib/views/layout.erb +16 -0
  34. data/lib/views/no_runs.erb +1 -0
  35. data/lib/views/not_a_project.erb +4 -0
  36. data/models/fail.rb +4 -0
  37. data/models/project.rb +38 -0
  38. data/models/run.rb +47 -0
  39. data/spec/api_spec.rb +141 -0
  40. data/spec/models/fail_spec.rb +18 -0
  41. data/spec/models/project_spec.rb +74 -0
  42. data/spec/models/run_spec.rb +82 -0
  43. data/spec/report_spec.rb +207 -0
  44. data/spec/spec_helper.rb +79 -0
  45. metadata +206 -0
@@ -0,0 +1,6 @@
1
+ /**
2
+ * bootstrap.js v3.0.0 by @fat and @mdo
3
+ * Copyright 2013 Twitter Inc.
4
+ * http://www.apache.org/licenses/LICENSE-2.0
5
+ */
6
+ if(!jQuery)throw new Error("Bootstrap requires jQuery");+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]}}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one(a.support.transition.end,function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b()})}(window.jQuery),+function(a){"use strict";var b='[data-dismiss="alert"]',c=function(c){a(c).on("click",b,this.close)};c.prototype.close=function(b){function c(){f.trigger("closed.bs.alert").remove()}var d=a(this),e=d.attr("data-target");e||(e=d.attr("href"),e=e&&e.replace(/.*(?=#[^\s]*$)/,""));var f=a(e);b&&b.preventDefault(),f.length||(f=d.hasClass("alert")?d:d.parent()),f.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(f.removeClass("in"),a.support.transition&&f.hasClass("fade")?f.one(a.support.transition.end,c).emulateTransitionEnd(150):c())};var d=a.fn.alert;a.fn.alert=function(b){return this.each(function(){var d=a(this),e=d.data("bs.alert");e||d.data("bs.alert",e=new c(this)),"string"==typeof b&&e[b].call(d)})},a.fn.alert.Constructor=c,a.fn.alert.noConflict=function(){return a.fn.alert=d,this},a(document).on("click.bs.alert.data-api",b,c.prototype.close)}(window.jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d)};b.DEFAULTS={loadingText:"loading..."},b.prototype.setState=function(a){var b="disabled",c=this.$element,d=c.is("input")?"val":"html",e=c.data();a+="Text",e.resetText||c.data("resetText",c[d]()),c[d](e[a]||this.options[a]),setTimeout(function(){"loadingText"==a?c.addClass(b).attr(b,b):c.removeClass(b).removeAttr(b)},0)},b.prototype.toggle=function(){var a=this.$element.closest('[data-toggle="buttons"]');if(a.length){var b=this.$element.find("input").prop("checked",!this.$element.hasClass("active"));"radio"===b.prop("type")&&a.find(".active").removeClass("active")}this.$element.toggleClass("active")};var c=a.fn.button;a.fn.button=function(c){return this.each(function(){var d=a(this),e=d.data("button"),f="object"==typeof c&&c;e||d.data("bs.button",e=new b(this,f)),"toggle"==c?e.toggle():c&&e.setState(c)})},a.fn.button.Constructor=b,a.fn.button.noConflict=function(){return a.fn.button=c,this},a(document).on("click.bs.button.data-api","[data-toggle^=button]",function(b){var c=a(b.target);c.hasClass("btn")||(c=c.closest(".btn")),c.button("toggle"),b.preventDefault()})}(window.jQuery),+function(a){"use strict";var b=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=this.sliding=this.interval=this.$active=this.$items=null,"hover"==this.options.pause&&this.$element.on("mouseenter",a.proxy(this.pause,this)).on("mouseleave",a.proxy(this.cycle,this))};b.DEFAULTS={interval:5e3,pause:"hover"},b.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},b.prototype.getActiveIndex=function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},b.prototype.to=function(b){var c=this,d=this.getActiveIndex();return b>this.$items.length-1||0>b?void 0:this.sliding?this.$element.one("slid",function(){c.to(b)}):d==b?this.pause().cycle():this.slide(b>d?"next":"prev",a(this.$items[b]))},b.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition.end&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},b.prototype.next=function(){return this.sliding?void 0:this.slide("next")},b.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},b.prototype.slide=function(b,c){var d=this.$element.find(".item.active"),e=c||d[b](),f=this.interval,g="next"==b?"left":"right",h="next"==b?"first":"last",i=this;this.sliding=!0,f&&this.pause(),e=e.length?e:this.$element.find(".item")[h]();var j=a.Event("slide.bs.carousel",{relatedTarget:e[0],direction:g});if(!e.hasClass("active")){if(this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid",function(){var b=a(i.$indicators.children()[i.getActiveIndex()]);b&&b.addClass("active")})),a.support.transition&&this.$element.hasClass("slide")){if(this.$element.trigger(j),j.isDefaultPrevented())return;e.addClass(b),e[0].offsetWidth,d.addClass(g),e.addClass(g),d.one(a.support.transition.end,function(){e.removeClass([b,g].join(" ")).addClass("active"),d.removeClass(["active",g].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger("slid")},0)}).emulateTransitionEnd(600)}else{if(this.$element.trigger(j),j.isDefaultPrevented())return;d.removeClass("active"),e.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return f&&this.cycle(),this}};var c=a.fn.carousel;a.fn.carousel=function(c){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c),g="string"==typeof c?c:f.slide;e||d.data("bs.carousel",e=new b(this,f)),"number"==typeof c?e.to(c):g?e[g]():f.interval&&e.pause().cycle()})},a.fn.carousel.Constructor=b,a.fn.carousel.noConflict=function(){return a.fn.carousel=c,this},a(document).on("click.bs.carousel.data-api","[data-slide], [data-slide-to]",function(b){var c,d=a(this),e=a(d.attr("data-target")||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"")),f=a.extend({},e.data(),d.data()),g=d.attr("data-slide-to");g&&(f.interval=!1),e.carousel(f),(g=d.attr("data-slide-to"))&&e.data("bs.carousel").to(g),b.preventDefault()}),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var b=a(this);b.carousel(b.data())})})}(window.jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d),this.transitioning=null,this.options.parent&&(this.$parent=a(this.options.parent)),this.options.toggle&&this.toggle()};b.DEFAULTS={toggle:!0},b.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},b.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b=a.Event("show.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.$parent&&this.$parent.find("> .accordion-group > .in");if(c&&c.length){var d=c.data("bs.collapse");if(d&&d.transitioning)return;c.collapse("hide"),d||c.data("bs.collapse",null)}var e=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[e](0),this.transitioning=1;var f=function(){this.$element.removeClass("collapsing").addClass("in")[e]("auto"),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return f.call(this);var g=a.camelCase(["scroll",e].join("-"));this.$element.one(a.support.transition.end,a.proxy(f,this)).emulateTransitionEnd(350)[e](this.$element[0][g])}}},b.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse").removeClass("in"),this.transitioning=1;var d=function(){this.transitioning=0,this.$element.trigger("hidden.bs.collapse").removeClass("collapsing").addClass("collapse")};return a.support.transition?(this.$element[c](0).one(a.support.transition.end,a.proxy(d,this)).emulateTransitionEnd(350),void 0):d.call(this)}}},b.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()};var c=a.fn.collapse;a.fn.collapse=function(c){return this.each(function(){var d=a(this),e=d.data("bs.collapse"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c);e||d.data("bs.collapse",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.collapse.Constructor=b,a.fn.collapse.noConflict=function(){return a.fn.collapse=c,this},a(document).on("click.bs.collapse.data-api","[data-toggle=collapse]",function(b){var c,d=a(this),e=d.attr("data-target")||b.preventDefault()||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,""),f=a(e),g=f.data("bs.collapse"),h=g?"toggle":d.data(),i=d.attr("data-parent"),j=i&&a(i);g&&g.transitioning||(j&&j.find("[data-toggle=collapse][data-parent="+i+"]").not(d).addClass("collapsed"),d[f.hasClass("in")?"addClass":"removeClass"]("collapsed")),f.collapse(h)})}(window.jQuery),+function(a){"use strict";function b(){a(d).remove(),a(e).each(function(b){var d=c(a(this));d.hasClass("open")&&(d.trigger(b=a.Event("hide.bs.dropdown")),b.isDefaultPrevented()||d.removeClass("open").trigger("hidden.bs.dropdown"))})}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}var d=".dropdown-backdrop",e="[data-toggle=dropdown]",f=function(b){a(b).on("click.bs.dropdown",this.toggle)};f.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){if("ontouchstart"in document.documentElement&&a('<div class="dropdown-backdrop"/>').insertAfter(a(this)).on("click",b),f.trigger(d=a.Event("show.bs.dropdown")),d.isDefaultPrevented())return;f.toggleClass("open").trigger("shown.bs.dropdown")}return e.focus(),!1}},f.prototype.keydown=function(b){if(/(38|40|27)/.test(b.keyCode)){var d=a(this);if(b.preventDefault(),b.stopPropagation(),!d.is(".disabled, :disabled")){var f=c(d),g=f.hasClass("open");if(!g||g&&27==b.keyCode)return 27==b.which&&f.find(e).focus(),d.click();var h=a("[role=menu] li:not(.divider):visible a",f);if(h.length){var i=h.index(h.filter(":focus"));38==b.keyCode&&i>0&&i--,40==b.keyCode&&i<h.length-1&&i++,~i||(i=0),h.eq(i).focus()}}}};var g=a.fn.dropdown;a.fn.dropdown=function(b){return this.each(function(){var c=a(this),d=c.data("dropdown");d||c.data("dropdown",d=new f(this)),"string"==typeof b&&d[b].call(c)})},a.fn.dropdown.Constructor=f,a.fn.dropdown.noConflict=function(){return a.fn.dropdown=g,this},a(document).on("click.bs.dropdown.data-api",b).on("click.bs.dropdown.data-api",".dropdown form",function(a){a.stopPropagation()}).on("click.bs.dropdown.data-api",e,f.prototype.toggle).on("keydown.bs.dropdown.data-api",e+", [role=menu]",f.prototype.keydown)}(window.jQuery),+function(a){"use strict";var b=function(b,c){this.options=c,this.$element=a(b).on("click.dismiss.modal",'[data-dismiss="modal"]',a.proxy(this.hide,this)),this.$backdrop=this.isShown=null,this.options.remote&&this.$element.find(".modal-body").load(this.options.remote)};b.DEFAULTS={backdrop:!0,keyboard:!0,show:!0},b.prototype.toggle=function(){return this[this.isShown?"hide":"show"]()},b.prototype.show=function(){var b=this,c=a.Event("show.bs.modal");this.$element.trigger(c),this.isShown||c.isDefaultPrevented()||(this.isShown=!0,this.escape(),this.backdrop(function(){var c=a.support.transition&&b.$element.hasClass("fade");b.$element.parent().length||b.$element.appendTo(document.body),b.$element.show(),c&&b.$element[0].offsetWidth,b.$element.addClass("in").attr("aria-hidden",!1),b.enforceFocus(),c?b.$element.one(a.support.transition.end,function(){b.$element.focus().trigger("shown.bs.modal")}).emulateTransitionEnd(300):b.$element.focus().trigger("shown.bs.modal")}))},b.prototype.hide=function(b){b&&b.preventDefault(),b=a.Event("hide.bs.modal"),this.$element.trigger(b),this.isShown&&!b.isDefaultPrevented()&&(this.isShown=!1,this.escape(),a(document).off("focusin.bs.modal"),this.$element.removeClass("in").attr("aria-hidden",!0),a.support.transition&&this.$element.hasClass("fade")?this.$element.one(a.support.transition.end,a.proxy(this.hideModal,this)).emulateTransitionEnd(300):this.hideModal())},b.prototype.enforceFocus=function(){a(document).off("focusin.bs.modal").on("focusin.bs.modal",a.proxy(function(a){this.$element[0]===a.target||this.$element.has(a.target).length||this.$element.focus()},this))},b.prototype.escape=function(){this.isShown&&this.options.keyboard?this.$element.on("keyup.dismiss.bs.modal",a.proxy(function(a){27==a.which&&this.hide()},this)):this.isShown||this.$element.off("keyup.dismiss.bs.modal")},b.prototype.hideModal=function(){var a=this;this.$element.hide(),this.backdrop(function(){a.removeBackdrop(),a.$element.trigger("hidden.bs.modal")})},b.prototype.removeBackdrop=function(){this.$backdrop&&this.$backdrop.remove(),this.$backdrop=null},b.prototype.backdrop=function(b){var c=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var d=a.support.transition&&c;if(this.$backdrop=a('<div class="modal-backdrop '+c+'" />').appendTo(document.body),this.$element.on("click",a.proxy(function(a){a.target===a.currentTarget&&("static"==this.options.backdrop?this.$element[0].focus.call(this.$element[0]):this.hide.call(this))},this)),d&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in"),!b)return;d?this.$backdrop.one(a.support.transition.end,b).emulateTransitionEnd(150):b()}else!this.isShown&&this.$backdrop?(this.$backdrop.removeClass("in"),a.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one(a.support.transition.end,b).emulateTransitionEnd(150):b()):b&&b()};var c=a.fn.modal;a.fn.modal=function(c){return this.each(function(){var d=a(this),e=d.data("bs.modal"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c);e||d.data("bs.modal",e=new b(this,f)),"string"==typeof c?e[c]():f.show&&e.show()})},a.fn.modal.Constructor=b,a.fn.modal.noConflict=function(){return a.fn.modal=c,this},a(document).on("click.bs.modal.data-api",'[data-toggle="modal"]',function(b){var c=a(this),d=c.attr("href"),e=a(c.attr("data-target")||d&&d.replace(/.*(?=#[^\s]+$)/,"")),f=e.data("modal")?"toggle":a.extend({remote:!/#/.test(d)&&d},e.data(),c.data());b.preventDefault(),e.modal(f).one("hide",function(){c.is(":visible")&&c.focus()})}),a(function(){var b=a(document.body).on("shown.bs.modal",".modal",function(){b.addClass("modal-open")}).on("hidden.bs.modal",".modal",function(){b.removeClass("modal-open")})})}(window.jQuery),+function(a){"use strict";var b=function(a,b){this.type=this.options=this.enabled=this.timeout=this.hoverState=this.$element=null,this.init("tooltip",a,b)};b.DEFAULTS={animation:!0,placement:"top",selector:!1,template:'<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',trigger:"hover focus",title:"",delay:0,html:!1,container:!1},b.prototype.init=function(b,c,d){this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d);for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focus",i="hover"==g?"mouseleave":"blur";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},b.prototype.getDefaults=function(){return b.DEFAULTS},b.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},b.prototype.enter=function(b){var c=this.getDefaults(),d={};this._options&&a.each(this._options,function(a,b){c[a]!=b&&(d[a]=b)});var e=b instanceof this.constructor?b:a(b.currentTarget)[this.type](d).data("bs."+this.type);return clearTimeout(e.timeout),e.options.delay&&e.options.delay.show?(e.hoverState="in",e.timeout=setTimeout(function(){"in"==e.hoverState&&e.show()},e.options.delay.show),void 0):e.show()},b.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget)[this.type](this._options).data("bs."+this.type);return clearTimeout(c.timeout),c.options.delay&&c.options.delay.hide?(c.hoverState="out",c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide),void 0):c.hide()},b.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){if(this.$element.trigger(b),b.isDefaultPrevented())return;var c=this.tip();this.setContent(),this.options.animation&&c.addClass("fade");var d="function"==typeof this.options.placement?this.options.placement.call(this,c[0],this.$element[0]):this.options.placement,e=/\s?auto?\s?/i,f=e.test(d);f&&(d=d.replace(e,"")||"top"),c.detach().css({top:0,left:0,display:"block"}).addClass(d),this.options.container?c.appendTo(this.options.container):c.insertAfter(this.$element);var g=this.getPosition(),h=c[0].offsetWidth,i=c[0].offsetHeight;if(f){var j=this.$element.parent(),k=d,l=document.documentElement.scrollTop||document.body.scrollTop,m="body"==this.options.container?window.innerWidth:j.outerWidth(),n="body"==this.options.container?window.innerHeight:j.outerHeight(),o="body"==this.options.container?0:j.offset().left;d="bottom"==d&&g.top+g.height+i-l>n?"top":"top"==d&&g.top-l-i<0?"bottom":"right"==d&&g.right+h>m?"left":"left"==d&&g.left-h<o?"right":d,c.removeClass(k).addClass(d)}var p="bottom"==d?{top:g.top+g.height,left:g.left+g.width/2-h/2}:"top"==d?{top:g.top-i,left:g.left+g.width/2-h/2}:"left"==d?{top:g.top+g.height/2-i/2,left:g.left-h}:{top:g.top+g.height/2-i/2,left:g.left+g.width};this.applyPlacement(p,d),this.$element.trigger("shown.bs."+this.type)}},b.prototype.applyPlacement=function(a,b){var c,d=this.tip(),e=d[0].offsetWidth,f=d[0].offsetHeight;a.top=a.top+parseInt(d.css("margin-top"),10),a.left=a.left+parseInt(d.css("margin-left"),10),d.offset(a).addClass("in");var g=d[0].offsetWidth,h=d[0].offsetHeight;if("top"==b&&h!=f&&(c=!0,a.top=a.top+f-h),"bottom"==b||"top"==b){var i=0;a.left<0&&(i=-2*a.left,a.left=0,d.offset(a),g=d[0].offsetWidth,h=d[0].offsetHeight),this.replaceArrow(i-e+g,g,"left")}else this.replaceArrow(h-f,h,"top");c&&d.offset(a)},b.prototype.replaceArrow=function(a,b,c){this.arrow().css(c,a?50*(1-a/b)+"%":"")},b.prototype.setContent=function(){var a=this.tip(),b=this.getTitle();a.find(".tooltip-inner")[this.options.html?"html":"text"](b),a.removeClass("fade in top bottom left right")},b.prototype.hide=function(){var b=this.tip(),c=a.Event("hide.bs."+this.type);return this.$element.trigger(c),c.isDefaultPrevented()?void 0:(b.removeClass("in"),a.support.transition&&this.$tip.hasClass("fade")?b.one(a.support.transition.end,b.detach).emulateTransitionEnd(150):b.detach(),this.$element.trigger("hidden.bs."+this.type),this)},b.prototype.fixTitle=function(){var a=this.$element;(a.attr("title")||"string"!=typeof a.attr("data-original-title"))&&a.attr("data-original-title",a.attr("title")||"").attr("title","")},b.prototype.hasContent=function(){return this.getTitle()},b.prototype.getPosition=function(){var b=this.$element[0];return a.extend({},"function"==typeof b.getBoundingClientRect?b.getBoundingClientRect():{width:b.offsetWidth,height:b.offsetHeight},this.$element.offset())},b.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},b.prototype.tip=function(){return this.$tip=this.$tip||a(this.options.template)},b.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},b.prototype.validate=function(){this.$element[0].parentNode||(this.hide(),this.$element=null,this.options=null)},b.prototype.enable=function(){this.enabled=!0},b.prototype.disable=function(){this.enabled=!1},b.prototype.toggleEnabled=function(){this.enabled=!this.enabled},b.prototype.toggle=function(b){var c=b?a(b.currentTarget)[this.type](this._options).data("bs."+this.type):this;c.tip().hasClass("in")?c.leave(c):c.enter(c)},b.prototype.destroy=function(){this.hide().$element.off("."+this.type).removeData("bs."+this.type)};var c=a.fn.tooltip;a.fn.tooltip=function(c){return this.each(function(){var d=a(this),e=d.data("bs.tooltip"),f="object"==typeof c&&c;e||d.data("bs.tooltip",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.tooltip.Constructor=b,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=c,this}}(window.jQuery),+function(a){"use strict";var b=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");b.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:'<div class="popover"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'}),b.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),b.prototype.constructor=b,b.prototype.getDefaults=function(){return b.DEFAULTS},b.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content")[this.options.html?"html":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title:empty").hide()},b.prototype.hasContent=function(){return this.getTitle()||this.getContent()},b.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},b.prototype.tip=function(){return this.$tip||(this.$tip=a(this.options.template)),this.$tip},b.prototype.destroy=function(){this.hide().$element.off("."+this.type).removeData(this.type)};var c=a.fn.popover;a.fn.popover=function(c){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof c&&c;e||d.data("bs.popover",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.popover.Constructor=b,a.fn.popover.noConflict=function(){return a.fn.popover=c,this}}(window.jQuery),+function(a){"use strict";function b(c,d){var e,f=a.proxy(this.process,this);this.$element=a(c).is("body")?a(window):a(c),this.$body=a("body"),this.$scrollElement=this.$element.on("scroll.bs.scroll-spy.data-api",f),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||(e=a(c).attr("href"))&&e.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.offsets=a([]),this.targets=a([]),this.activeTarget=null,this.refresh(),this.process()}b.DEFAULTS={offset:10},b.prototype.refresh=function(){var b=this.$element[0]==window?"offset":"position";this.offsets=a([]),this.targets=a([]);var c=this;this.$body.find(this.selector).map(function(){var d=a(this),e=d.data("target")||d.attr("href"),f=/^#\w/.test(e)&&a(e);return f&&f.length&&[[f[b]().top+(!a.isWindow(c.$scrollElement.get(0))&&c.$scrollElement.scrollTop()),e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){c.offsets.push(this[0]),c.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,d=c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(b>=d)return g!=(a=f.last()[0])&&this.activate(a);for(a=e.length;a--;)g!=f[a]&&b>=e[a]&&(!e[a+1]||b<=e[a+1])&&this.activate(f[a])},b.prototype.activate=function(b){this.activeTarget=b,a(this.selector).parents(".active").removeClass("active");var c=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',d=a(c).parents("li").addClass("active");d.parent(".dropdown-menu").length&&(d=d.closest("li.dropdown").addClass("active")),d.trigger("activate")};var c=a.fn.scrollspy;a.fn.scrollspy=function(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.scrollspy.Constructor=b,a.fn.scrollspy.noConflict=function(){return a.fn.scrollspy=c,this},a(window).on("load",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);b.scrollspy(b.data())})})}(window.jQuery),+function(a){"use strict";var b=function(b){this.element=a(b)};b.prototype.show=function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.attr("data-target");if(d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),!b.parent("li").hasClass("active")){var e=c.find(".active:last a")[0],f=a.Event("show.bs.tab",{relatedTarget:e});if(b.trigger(f),!f.isDefaultPrevented()){var g=a(d);this.activate(b.parent("li"),c),this.activate(g,g.parent(),function(){b.trigger({type:"shown.bs.tab",relatedTarget:e})})}}},b.prototype.activate=function(b,c,d){function e(){f.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),b.addClass("active"),g?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu")&&b.closest("li.dropdown").addClass("active"),d&&d()}var f=c.find("> .active"),g=d&&a.support.transition&&f.hasClass("fade");g?f.one(a.support.transition.end,e).emulateTransitionEnd(150):e(),f.removeClass("in")};var c=a.fn.tab;a.fn.tab=function(c){return this.each(function(){var d=a(this),e=d.data("bs.tab");e||d.data("bs.tab",e=new b(this)),"string"==typeof c&&e[c]()})},a.fn.tab.Constructor=b,a.fn.tab.noConflict=function(){return a.fn.tab=c,this},a(document).on("click.bs.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(b){b.preventDefault(),a(this).tab("show")})}(window.jQuery),+function(a){"use strict";var b=function(c,d){this.options=a.extend({},b.DEFAULTS,d),this.$window=a(window).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(c),this.affixed=this.unpin=null,this.checkPosition()};b.RESET="affix affix-top affix-bottom",b.DEFAULTS={offset:0},b.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},b.prototype.checkPosition=function(){if(this.$element.is(":visible")){var c=a(document).height(),d=this.$window.scrollTop(),e=this.$element.offset(),f=this.options.offset,g=f.top,h=f.bottom;"object"!=typeof f&&(h=g=f),"function"==typeof g&&(g=f.top()),"function"==typeof h&&(h=f.bottom());var i=null!=this.unpin&&d+this.unpin<=e.top?!1:null!=h&&e.top+this.$element.height()>=c-h?"bottom":null!=g&&g>=d?"top":!1;this.affixed!==i&&(this.unpin&&this.$element.css("top",""),this.affixed=i,this.unpin="bottom"==i?e.top-d:null,this.$element.removeClass(b.RESET).addClass("affix"+(i?"-"+i:"")),"bottom"==i&&this.$element.offset({top:document.body.offsetHeight-h-this.$element.height()}))}};var c=a.fn.affix;a.fn.affix=function(c){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof c&&c;e||d.data("bs.affix",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.affix.Constructor=b,a.fn.affix.noConflict=function(){return a.fn.affix=c,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var b=a(this),c=b.data();c.offset=c.offset||{},c.offsetBottom&&(c.offset.bottom=c.offsetBottom),c.offsetTop&&(c.offset.top=c.offsetTop),b.affix(c)})})}(window.jQuery);
@@ -0,0 +1,56 @@
1
+ <div id="forecast-wrapper">
2
+ <img id="forecast" src="/images/<%=@forecast%>.svg"/>
3
+ </div>
4
+ <div class="accordion" id="runs">
5
+ <div id="top_fail" class="alert alert-danger">
6
+ <button type="button" class="close" data-dismiss="alert">&times;</button>
7
+ <strong>Top failing test in the last ten runs.</strong>
8
+ <br />
9
+ <%=@top_fail%>
10
+ </div>
11
+ <% @runs.reverse.each_with_index do |run,index| %>
12
+ <div class="accordion-group">
13
+ <div class="accordion-heading">
14
+ <a class="accordion-toggle " data-toggle="collapse" data-parent="#runs" href="#run_<%=index+1%>">
15
+ <h3><span class="label <%=run.fails.count == 0 ? "label-success" : "label-danger" %>">
16
+ <%=run.fails.count == 0 ? "Passed" : "Failed" %> Run <%=index+1%></span>
17
+ <small class="date-time text-muted"><%=run.date_time%></small>
18
+ </h3>
19
+ Passed
20
+ <span class="total-number-of-passes badge"><%=run.total_passed%></span>
21
+ <span class="pull-right">
22
+ Failed
23
+ <span class="total-number-of-fails badge"><%=run.total_failed%></span>
24
+ </span>
25
+ <div class="progress progress-striped">
26
+ <div class="progress-bar progress-bar-success" style="width: <%=run.percentage_passed%>%"></div>
27
+ <div class="progress-bar progress-bar-danger" style="width: <%=run.percentage_failed%>%"></div>
28
+ </div>
29
+ </a>
30
+
31
+ </div>
32
+ <% if run.fails.count > 0 %>
33
+ <div id="run_<%=index+1%>" class="accordion-body collapse">
34
+ <div class="panel panel-info run-fails">
35
+ <div class="panel-heading">Run the failing tests locally!</div>
36
+ bundle exec cucumber <%=run.unique_failing_lines.join(' ') %>
37
+ </div>
38
+ <div class="accordion-inner">
39
+ <% run.fails.each do | fail |%>
40
+ <dl class="dl-horizontal well">
41
+ <dt>Feature:</dt>
42
+ <dd><%=fail.feature %></dd>
43
+ <dt>Scenario:</dt>
44
+ <dd><%=fail.scenario %></dd>
45
+ <dt>Step:</dt>
46
+ <dd><%=fail.step %></dd>
47
+ <dt>Line:</dt>
48
+ <dd class="fail-line"><%=fail.line %></dd>
49
+ </dl>
50
+ <% end %>
51
+ </div>
52
+ </div>
53
+ <% end %>
54
+ </div>
55
+ <% end %>
56
+ </div>
@@ -0,0 +1,16 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <title>Delinquo</title>
5
+ <link href="/css/bootstrap.min.css" rel="stylesheet">
6
+ <link href="/css/main.css" rel="stylesheet">
7
+ <link rel="apple-touch-icon" href="/images/umbrella.svg" />
8
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
9
+ </head>
10
+ <body>
11
+ <h1><%= @name %></h1>
12
+ <%=yield %>
13
+ <script src="http://code.jquery.com/jquery-latest.min.js" type="text/javascript"></script>
14
+ <script src="/js/bootstrap.min.js"></script>
15
+ </body>
16
+ </html>
@@ -0,0 +1 @@
1
+ <p class="no_runs">There are no runs.</p>
@@ -0,0 +1,4 @@
1
+ <div id="warning-wrapper">
2
+ <img id="warning" src="/images/umbrella.svg"/>
3
+ </div>
4
+ <h3 class="not_a_project">That is not a project.</h3>
data/models/fail.rb ADDED
@@ -0,0 +1,4 @@
1
+ class Fail < ActiveRecord::Base
2
+ belongs_to :run
3
+ validates :feature, :scenario, :step, :line, presence: true
4
+ end
data/models/project.rb ADDED
@@ -0,0 +1,38 @@
1
+ class Project < ActiveRecord::Base
2
+ has_many :runs
3
+ validates :name, presence: true, uniqueness: true
4
+
5
+ def failing_runs
6
+ failing_runs = 0
7
+ runs.each do | run |
8
+ failing_runs += 1 if run.fails.count > 0
9
+ end
10
+ failing_runs
11
+ end
12
+
13
+ def top_fail
14
+ all_fails = []
15
+ runs.each do |run|
16
+ run.fails.each do |fail|
17
+ all_fails << fail.line
18
+ end
19
+ end
20
+ freq = all_fails.inject(Hash.new(0)) { |hash, value| hash[value] += 1; hash }
21
+ all_fails.sort_by { |v| freq[v] }.last
22
+ end
23
+
24
+ def forecast
25
+ case failing_runs
26
+ when 0
27
+ "sun"
28
+ when 1..3
29
+ "changeable"
30
+ when 4..6
31
+ "cloud"
32
+ when 7..9
33
+ "rain"
34
+ when 10
35
+ "storm"
36
+ end
37
+ end
38
+ end
data/models/run.rb ADDED
@@ -0,0 +1,47 @@
1
+ class Run < ActiveRecord::Base
2
+ belongs_to :project
3
+ has_many :fails
4
+ validates :total, presence: true
5
+
6
+ def total_number_of_tests
7
+ total.to_i
8
+ end
9
+
10
+ def total_failed
11
+ fails.count
12
+ end
13
+
14
+ def total_passed
15
+ (total_number_of_tests - total_failed)
16
+ end
17
+
18
+ def percentage_failed
19
+ if total_number_of_tests > 0
20
+ ((total_failed.to_f / total_number_of_tests.to_f) * 100).ceil
21
+ else
22
+ 0
23
+ end
24
+ end
25
+
26
+ def percentage_passed
27
+ 100 - percentage_failed
28
+ end
29
+
30
+ def date_time
31
+ format = "%d/%m/%Y %H.%M"
32
+ Time.at(created_at).to_datetime.strftime(format)
33
+ end
34
+
35
+ def unique_failing_lines
36
+ collated_failing_lines.uniq
37
+ end
38
+
39
+ def collated_failing_lines
40
+ lines = []
41
+ fails.each do |fail|
42
+ lines << fail.line
43
+ end
44
+ lines
45
+ end
46
+
47
+ end
data/spec/api_spec.rb ADDED
@@ -0,0 +1,141 @@
1
+ require 'spec_helper'
2
+
3
+ describe "API" do
4
+
5
+ before(:each) do
6
+ @project = Project.create!(name: 'delinquo')
7
+ end
8
+
9
+ after(:each) do
10
+ Project.delete_all
11
+ end
12
+
13
+ describe "Creating and listing projects" do
14
+
15
+ it "can add a new project" do
16
+ post '/project/cookiescrumbs'
17
+ check_status 201
18
+ end
19
+
20
+ it "returns an error adding the same project twice" do
21
+ post '/project/cookiescrumbs'
22
+ post '/project/cookiescrumbs'
23
+ check_status 409
24
+ last_response.header['Warning'].should == 'project already exists with the name cookiescrumbs'
25
+ end
26
+
27
+ it "can list current projects" do
28
+ get '/projects'
29
+ check_status 200
30
+ last_response.body.should == ["delinquo"].to_json
31
+ end
32
+
33
+ it "returns an error if a project doesnt exist" do
34
+ get '/project/notaproject'
35
+ check_status 404
36
+ last_response.header['Warning'].should == 'project not found with the name notaproject'
37
+ end
38
+
39
+ end
40
+
41
+ describe "Adding runs to a project" do
42
+
43
+ it "needs to have a project name" do
44
+ post '/project/james/run', json_for_a_run_with_two_fails
45
+ check_status 404
46
+ last_response.header['Warning'].should == 'project not found with the name james'
47
+ end
48
+
49
+ it "can add a run to a project" do
50
+ add_run 'failing'
51
+ @project.runs.where(total: '500').count.should == 1
52
+ end
53
+
54
+ it "can add more than one run to a project" do
55
+ add_runs 2, 'failing'
56
+ @project.runs.count.should == 2
57
+ end
58
+
59
+ it "can add a set of fails to an existing run" do
60
+ add_run 'failing'
61
+ @project.runs.first.fails.count.should == 2
62
+ end
63
+
64
+ it "can add a passing run" do
65
+ add_run 'passing'
66
+ @project.runs.first.fails.should == []
67
+ end
68
+
69
+ end
70
+
71
+ describe "Returning runs from a project" do
72
+
73
+ it "returns all runs for a project" do
74
+ project = Project.where(name: 'delinquo').first
75
+ 10.times do
76
+ project.runs.create! total: '500'
77
+ end
78
+
79
+ get '/project/delinquo'
80
+ check_status 200
81
+ JSON.parse(last_response.body).count.should == 10
82
+ end
83
+
84
+ it "can have a run with more than one fail" do
85
+ post '/project/delinquo/run', json_for_a_run_with_two_fails
86
+ check_status 201
87
+
88
+ get '/project/delinquo'
89
+ check_status 200
90
+
91
+ json = JSON.parse(last_response.body)
92
+ json[0]['fails'].count.should == 2
93
+ end
94
+
95
+ it "has the correct fields" do
96
+
97
+ post '/project/delinquo/run', json_for_a_run_with_two_fails
98
+ check_status 201
99
+
100
+ get '/project/delinquo'
101
+ check_status 200
102
+
103
+ json = JSON.parse(last_response.body)
104
+
105
+ json[0]['total'].should == '500'
106
+ json[0]['fails'].first['feature'].should == 'a feature'
107
+ json[0]['fails'].first['scenario'].should == 'a scenario'
108
+ json[0]['fails'].first['line'].should == 'a line'
109
+ json[0]['fails'].first['step'].should == 'a step'
110
+
111
+ json[0]['id'].should be_nil
112
+ json[0]['project_id'].should be_nil
113
+
114
+ json[0]['fails'].first['id'].should be_nil
115
+ json[0]['fails'].first['run_id'].should be_nil
116
+ end
117
+
118
+ it "rejects an empty run" do
119
+ post '/project/delinquo/run', {}.to_json
120
+ Project.where(name: 'delinquo').first.runs.count.should == 0
121
+ check_status 400
122
+ end
123
+
124
+ end
125
+
126
+ describe "numbers of runs per project" do
127
+
128
+ it "contains a maximum of ten runs per project" do
129
+ add_runs 11, 'failing'
130
+ Project.where(name: 'delinquo').first.runs.count.should == 10
131
+ end
132
+
133
+ it "removes the oldest run when the limit is reached" do
134
+ add_runs 10, 'failing'
135
+ oldest_run_id = Project.where(name: 'delinquo').first.runs.first.id
136
+ add_run 'failing'
137
+ Run.where(id:oldest_run_id).should be_empty
138
+ end
139
+ end
140
+
141
+ end
@@ -0,0 +1,18 @@
1
+ require 'spec_helper'
2
+
3
+ describe Fail do
4
+
5
+ after(:each) do
6
+ Fail.delete_all
7
+ end
8
+
9
+ it "should not be valid without all required fields" do
10
+ Fail.new.should_not be_valid
11
+ end
12
+
13
+ it "should be valid with all require fields" do
14
+ fail = Fail.new feature: "a feature", scenario: "a scenario", step: "a step", line: "a line"
15
+ fail.should be_valid
16
+ end
17
+
18
+ end
@@ -0,0 +1,74 @@
1
+ require 'spec_helper'
2
+
3
+ describe Project do
4
+
5
+ before(:each) do
6
+ @project = Project.create!(name: 'delinquo')
7
+ end
8
+
9
+ after(:each) do
10
+ Project.delete_all
11
+ end
12
+
13
+ it "is not valid without a project name" do
14
+ project = Project.new
15
+ project.name = nil
16
+ project.should_not be_valid
17
+ end
18
+
19
+ it "is not valid if the name is not unique" do
20
+ Project.create!(name: 'cookie').should be_valid
21
+ project = Project.new(name: 'cookie')
22
+ project.should_not be_valid
23
+ end
24
+
25
+ describe ".failing_runs" do
26
+ it "should return the number of failing runs" do
27
+ @project.runs.create(total: '500')
28
+ add_runs 3, 'failing'
29
+ @project.failing_runs.should == 3
30
+ end
31
+ end
32
+
33
+ describe ".top_fail" do
34
+ it "should return the projects most common failing test" do
35
+ add_runs 1, 'failing'
36
+ @project.runs.create(total: '500').fails.create! feature: 'a feature', scenario: 'a scenario', step: 'a step', line: 'this is the most fequently failing test'
37
+ @project.runs.create(total: '500').fails.create! feature: 'a feature', scenario: 'a scenario', step: 'a step', line: 'this is the most fequently failing test'
38
+ @project.runs.create(total: '500').fails.create! feature: 'a feature', scenario: 'a scenario', step: 'a step', line: 'this is the most fequently failing test'
39
+ @project.top_fail.should == 'this is the most fequently failing test'
40
+ end
41
+ end
42
+
43
+ describe ".forcast" do
44
+ context "should return the current state of the build as a weather forcast" do
45
+
46
+ it "returns sun, when the number of failing runs is 0" do
47
+ @project.runs.create(total: '500')
48
+ @project.forecast.should == "sun"
49
+ end
50
+
51
+ it "returns changeable, when the number of failinng runs is between 1 and 3" do
52
+ add_runs 3, 'failing'
53
+ @project.forecast.should == "changeable"
54
+ end
55
+
56
+ it "returns cloud, when the number of failing runs is between 4 and 6" do
57
+ add_runs 6, 'failing'
58
+ @project.forecast.should == "cloud"
59
+ end
60
+
61
+ it "returns rain, when the number of failing runs is between 7 and 9" do
62
+ add_runs 9, 'failing'
63
+ @project.forecast.should == "rain"
64
+ end
65
+
66
+ it "returns storm, when the number of failing runs is 10" do
67
+ add_runs 10, 'failing'
68
+ @project.forecast.should == "storm"
69
+ end
70
+
71
+ end
72
+ end
73
+
74
+ end
@@ -0,0 +1,82 @@
1
+ require 'spec_helper'
2
+
3
+ describe Run do
4
+
5
+ before(:each) do
6
+ @run = Run.create!(total: '100')
7
+ end
8
+
9
+ after(:each ) do
10
+ Run.delete_all
11
+ end
12
+
13
+ it "should return the total number of tests" do
14
+ @run.total_number_of_tests.should == 100
15
+ end
16
+
17
+ it "should return the total number of failed tests" do
18
+ add_fails 3
19
+ @run.total_failed.should == 3
20
+ end
21
+
22
+ it "should return the total number of passes" do
23
+ @run.total = 100
24
+ add_fails 3
25
+ @run.total_passed.should == 97
26
+ end
27
+
28
+ it "should return the percentage of fails" do
29
+ @run.total = 100
30
+ add_fail
31
+
32
+ @run.percentage_failed.should == 1
33
+ end
34
+
35
+ it "should return the percentage fails rounded up to a whole number" do
36
+ @run.total = 200
37
+ add_fails 25
38
+
39
+ @run.percentage_failed.should == 13
40
+ end
41
+
42
+ it "should return 0%, if total number of tests is 0" do
43
+ @run.total = 0
44
+ @run.percentage_failed.should == 0
45
+ end
46
+
47
+ it "should return the percentage of passes" do
48
+ @run.total = 100
49
+ add_fail
50
+ @run.percentage_passed.should == 99
51
+ end
52
+
53
+ it "should return 100% passed when there are no tests" do
54
+ @run.total = 0
55
+ @run.percentage_passed.should == 100
56
+ end
57
+
58
+ it "should return a formatted date and time" do
59
+ @run.created_at ='2013-10-02 07:34:49'
60
+ @run.date_time.should == "02/10/2013 08.34"
61
+ end
62
+
63
+ it "should return all the fail lines in a run" do
64
+ fails = [
65
+ Fail.new(feature: 'a feature', scenario: 'a scenario', step: 'a step', line: 'a line 1'),
66
+ Fail.new(feature: 'a feature', scenario: 'a scenario', step: 'a step', line: 'a line 2')
67
+ ]
68
+ @run.fails = fails
69
+ @run.unique_failing_lines.should == ['a line 1', 'a line 2']
70
+ end
71
+
72
+ end
73
+
74
+ def add_fails(number=1)
75
+ number.times do
76
+ @run.fails.create! feature: 'a feature', scenario: 'a scenario', step: 'a step', line: 'a line'
77
+ end
78
+ end
79
+
80
+ def add_fail
81
+ @run.fails.create! feature: 'a feature', scenario: 'a scenario', step: 'a step', line: 'a line'
82
+ end