├── .README ├── rspec_reports_overview.png └── rspec_reports_report.png ├── .document ├── .gitignore ├── .rspec ├── Gemfile ├── Gemfile.lock ├── LICENSE.txt ├── README.md ├── Rakefile ├── VERSION ├── lib └── rspec_html_formatter.rb ├── resources ├── bootstrap-3.2.0-dist │ ├── css │ │ ├── bootstrap-theme-yeti.min.css │ │ └── bootstrap.min.css │ ├── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.svg │ │ ├── glyphicons-halflings-regular.ttf │ │ └── glyphicons-halflings-regular.woff │ └── js │ │ └── bootstrap.min.js ├── jquery-1.11.1.min.js └── jscharts.js ├── rspec_html_formatter.gemspec ├── spec ├── penders_spec.rb ├── rspec_html_formatter_spec.rb └── test2_spec.rb └── templates ├── overview.erb └── report.erb /.README/rspec_reports_overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingsleyh/rspec_reports_formatter/b41275ffbd33b4d4776aa70784a655438f6cc622/.README/rspec_reports_overview.png -------------------------------------------------------------------------------- /.README/rspec_reports_report.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingsleyh/rspec_reports_formatter/b41275ffbd33b4d4776aa70784a655438f6cc622/.README/rspec_reports_report.png -------------------------------------------------------------------------------- /.document: -------------------------------------------------------------------------------- 1 | lib/**/*.rb 2 | bin/* 3 | - 4 | features/**/*.feature 5 | LICENSE.txt 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # rcov generated 2 | coverage 3 | coverage.data 4 | 5 | .idea 6 | rspec_html_reports 7 | # rdoc generated 8 | rdoc 9 | 10 | # yard generated 11 | doc 12 | .yardoc 13 | 14 | # bundler 15 | .bundle 16 | 17 | # jeweler generated 18 | pkg 19 | 20 | # Have editor/IDE/OS specific files you need to ignore? Consider using a global gitignore: 21 | # 22 | # * Create a file at ~/.gitignore 23 | # * Include files you want ignored 24 | # * Run: git config --global core.excludesfile ~/.gitignore 25 | # 26 | # After doing this, these files will be ignored in all your git projects, 27 | # saving you from having to 'pollute' every project you touch with them 28 | # 29 | # Not sure what to needs to be ignored for particular editors/OSes? Here's some ideas to get you started. (Remember, remove the leading # of the line) 30 | # 31 | # For MacOS: 32 | # 33 | #.DS_Store 34 | 35 | # For TextMate 36 | #*.tmproj 37 | #tmtags 38 | 39 | # For emacs: 40 | #*~ 41 | #\#* 42 | #.\#* 43 | 44 | # For vim: 45 | #*.swp 46 | 47 | # For redcar: 48 | #.redcar 49 | 50 | # For rubinius: 51 | #*.rbc 52 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | # Add dependencies required to use your gem here. 3 | gem 'rspec-core', '>= 3.0.3' 4 | gem 'rouge', '1.6.1' 5 | gem 'activesupport', '>= 4.1.4' 6 | 7 | # Add dependencies to develop your gem here. 8 | # Include everything needed to run rake, tests, features, etc. 9 | group :development do 10 | gem "rspec", "~> 3.0.0" 11 | gem "rdoc", "~> 3.12" 12 | gem "bundler", "~> 1.0" 13 | gem "jeweler", "~> 2.0.1" 14 | end 15 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: http://rubygems.org/ 3 | specs: 4 | activesupport (4.1.4) 5 | i18n (~> 0.6, >= 0.6.9) 6 | json (~> 1.7, >= 1.7.7) 7 | minitest (~> 5.1) 8 | thread_safe (~> 0.1) 9 | tzinfo (~> 1.1) 10 | addressable (2.3.6) 11 | builder (3.2.2) 12 | descendants_tracker (0.0.4) 13 | thread_safe (~> 0.3, >= 0.3.1) 14 | diff-lcs (1.2.5) 15 | faraday (0.9.0) 16 | multipart-post (>= 1.2, < 3) 17 | git (1.2.8) 18 | github_api (0.12.0) 19 | addressable (~> 2.3) 20 | descendants_tracker (~> 0.0.4) 21 | faraday (~> 0.8, < 0.10) 22 | hashie (>= 3.2) 23 | multi_json (>= 1.7.5, < 2.0) 24 | nokogiri (~> 1.6.3) 25 | oauth2 26 | hashie (3.2.0) 27 | highline (1.6.21) 28 | i18n (0.6.11) 29 | jeweler (2.0.1) 30 | builder 31 | bundler (>= 1.0) 32 | git (>= 1.2.5) 33 | github_api 34 | highline (>= 1.6.15) 35 | nokogiri (>= 1.5.10) 36 | rake 37 | rdoc 38 | json (1.8.1) 39 | jwt (1.0.0) 40 | mini_portile (0.6.0) 41 | minitest (5.4.0) 42 | multi_json (1.10.1) 43 | multi_xml (0.5.5) 44 | multipart-post (2.0.0) 45 | nokogiri (1.6.3.1) 46 | mini_portile (= 0.6.0) 47 | nokogiri (1.6.3.1-x86-mingw32) 48 | mini_portile (= 0.6.0) 49 | oauth2 (1.0.0) 50 | faraday (>= 0.8, < 0.10) 51 | jwt (~> 1.0) 52 | multi_json (~> 1.3) 53 | multi_xml (~> 0.5) 54 | rack (~> 1.2) 55 | rack (1.5.2) 56 | rake (10.3.2) 57 | rdoc (3.12.2) 58 | json (~> 1.4) 59 | rouge (1.6.1) 60 | rspec (3.0.0) 61 | rspec-core (~> 3.0.0) 62 | rspec-expectations (~> 3.0.0) 63 | rspec-mocks (~> 3.0.0) 64 | rspec-core (3.0.3) 65 | rspec-support (~> 3.0.0) 66 | rspec-expectations (3.0.3) 67 | diff-lcs (>= 1.2.0, < 2.0) 68 | rspec-support (~> 3.0.0) 69 | rspec-mocks (3.0.3) 70 | rspec-support (~> 3.0.0) 71 | rspec-support (3.0.3) 72 | thread_safe (0.3.4) 73 | tzinfo (1.2.1) 74 | thread_safe (~> 0.1) 75 | 76 | PLATFORMS 77 | ruby 78 | x86-mingw32 79 | 80 | DEPENDENCIES 81 | activesupport (>= 4.1.4) 82 | bundler (~> 1.0) 83 | jeweler (~> 2.0.1) 84 | rdoc (~> 3.12) 85 | rouge (= 1.6.1) 86 | rspec (~> 3.0.0) 87 | rspec-core (>= 3.0.3) 88 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Kingsley Hendrickse 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PLEASE NOTE - This project is not being actively maintained at the moment - I am taking a break - not sure when I will return.I'm unlikely to do do any more work on this project but will leave it here for posterity. 2 | 3 | * Please consider using a fork of this project such as [https://github.com/vbanthia/rspec_html_reporter](https://github.com/vbanthia/rspec_html_reporter) 4 | 5 | # Publish pretty [rspec](http://rspec.info/) reports 6 | 7 | This is a ruby Rspec custom formatter which generates pretty html reports showing the results of rspec tests. This gem was build to use Rspec 3.x.x If you want to use it with older 8 | versions of Rspec then you should use the rspec_reports_formatter 0.2.x (2.8.0 branch) 9 | 10 | * For Rspec 2.x.x please use rspec_reports_formatter version starting with 0.2.x 11 | * For Rspec 3.x.x please use the rspec_reports_formatter version starting with 0.3.x 12 | 13 | 14 | ## Install 15 | 16 | ``` 17 | gem install rspec_html_formatter -v 0.3.1 18 | ``` 19 | 20 | ideally just add it to your bundler Gemfile as follows: 21 | 22 | ```ruby 23 | gem 'rspec_html_formatter','~> 0.3.1' 24 | ``` 25 | 26 | ## Use 27 | When running your rspec tests with rspec 3.0.0 just use the custom formatter: 28 | 29 | This should work: 30 | 31 | ``` 32 | rspec -f RspecHtmlFormatter spec 33 | ``` 34 | 35 | If not you can explicitly add in a require as follows: 36 | 37 | ``` 38 | rspec --require rspec_html_formatter.rb --format RspecHtmlFormatter spec 39 | ``` 40 | 41 | ![example overview report] 42 | (https://raw.githubusercontent.com/kingsleyh/rspec_reports_formatter/master/.README/rspec_reports_overview.png) 43 | 44 | ![example report] 45 | (https://raw.githubusercontent.com/kingsleyh/rspec_reports_formatter/master/.README/rspec_reports_report.png) 46 | 47 | If you want to provide some generated documentation for the tests you can put comments in the rspec tests like this: 48 | 49 | ```ruby 50 | #-> Given I have ordered a vegetarian pizza 51 | #-> When I eat the pizza 52 | #-> Then my tummy is full 53 | 54 | ``` 55 | 56 | The #-> notation is picked up and passed through a Gherkin syntax highlighter. So it was designed to use with Given,When,Then. But in theory you can put other text there too. 57 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'rubygems' 4 | require 'bundler' 5 | begin 6 | Bundler.setup(:default, :development) 7 | rescue Bundler::BundlerError => e 8 | $stderr.puts e.message 9 | $stderr.puts "Run `bundle install` to install missing gems" 10 | exit e.status_code 11 | end 12 | require 'rake' 13 | 14 | require 'jeweler' 15 | Jeweler::Tasks.new do |gem| 16 | # gem is a Gem::Specification... see http://guides.rubygems.org/specification-reference/ for more options 17 | gem.name = "rspec_html_formatter" 18 | gem.homepage = "http://github.com/kingsleyh/rspec_reports_formatter" 19 | gem.license = "MIT" 20 | gem.summary = %Q{Rspec custom formatter to generate pretty html results} 21 | gem.description = %Q{Rspec custom formatter to generate pretty html results} 22 | gem.email = "kingsleyhendrickse@me.com" 23 | gem.authors = ["Kingsley Hendrickse"] 24 | # dependencies defined in Gemfile 25 | end 26 | Jeweler::RubygemsDotOrgTasks.new 27 | 28 | require 'rspec/core' 29 | require 'rspec/core/rake_task' 30 | RSpec::Core::RakeTask.new(:spec) do |spec| 31 | spec.pattern = FileList['spec/**/*_spec.rb'] 32 | end 33 | 34 | desc "Code coverage detail" 35 | task :simplecov do 36 | ENV['COVERAGE'] = "true" 37 | Rake::Task['spec'].execute 38 | end 39 | 40 | task :default => :spec 41 | 42 | require 'rdoc/task' 43 | Rake::RDocTask.new do |rdoc| 44 | version = File.exist?('VERSION') ? File.read('VERSION') : "" 45 | 46 | rdoc.rdoc_dir = 'rdoc' 47 | rdoc.title = "rspec_html_formatter #{version}" 48 | rdoc.rdoc_files.include('README*') 49 | rdoc.rdoc_files.include('lib/**/*.rb') 50 | end 51 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 0.3.1 2 | -------------------------------------------------------------------------------- /lib/rspec_html_formatter.rb: -------------------------------------------------------------------------------- 1 | require 'rspec/core/formatters/base_formatter' 2 | require 'active_support' 3 | require 'active_support/core_ext/numeric' 4 | require 'active_support/inflector' 5 | require 'fileutils' 6 | require 'rouge' 7 | require 'erb' 8 | require 'rbconfig' 9 | 10 | I18n.enforce_available_locales = false 11 | 12 | class Oopsy 13 | attr_reader :klass, :message, :backtrace, :highlighted_source, :explanation, :backtrace_message 14 | 15 | def initialize(exception, file_path) 16 | @exception = exception 17 | @file_path = file_path 18 | unless @exception.nil? 19 | @klass = @exception.class 20 | @message = @exception.message.encode('utf-8') 21 | @backtrace = @exception.backtrace 22 | @backtrace_message = @backtrace.select { |r| r.match(@file_path) }.join('').encode('utf-8') 23 | @highlighted_source = process_source 24 | @explanation = process_message 25 | end 26 | end 27 | 28 | private 29 | 30 | def os 31 | @os ||= ( 32 | host_os = RbConfig::CONFIG['host_os'] 33 | case host_os 34 | when /mswin|msys|mingw|cygwin|bccwin|wince|emc/ 35 | :windows 36 | when /darwin|mac os/ 37 | :macosx 38 | when /linux/ 39 | :linux 40 | when /solaris|bsd/ 41 | :unix 42 | else 43 | raise Exception, "unknown os: #{host_os.inspect}" 44 | end 45 | ) 46 | end 47 | 48 | def process_source 49 | data = @backtrace_message.split(':') 50 | unless data.empty? 51 | if os == :windows 52 | file_path = data[0] + ':' + data[1] 53 | line_number = data[2].to_i 54 | else 55 | file_path = data.first 56 | line_number = data[1].to_i 57 | end 58 | lines = File.readlines(file_path) 59 | start_line = line_number-2 60 | end_line = line_number+3 61 | source = lines[start_line..end_line].join("").sub(lines[line_number-1].chomp, "--->#{lines[line_number-1].chomp}") 62 | 63 | formatter = Rouge::Formatters::HTML.new(css_class: 'highlight', line_numbers: true, start_line: start_line+1) 64 | lexer = Rouge::Lexers::Ruby.new 65 | formatter.format(lexer.lex(source.encode('utf-8'))) 66 | end 67 | end 68 | 69 | def process_message 70 | formatter = Rouge::Formatters::HTML.new(css_class: 'highlight') 71 | lexer = Rouge::Lexers::Ruby.new 72 | formatter.format(lexer.lex(@message)) 73 | end 74 | 75 | end 76 | 77 | class Example 78 | 79 | attr_reader :description, :full_description, :run_time, :duration, :status, :exception, :file_path, :metadata, :spec 80 | 81 | def initialize(example) 82 | @description = example.description 83 | @full_description = example.full_description 84 | @execution_result = example.execution_result 85 | @run_time = (@execution_result.run_time).round(5) 86 | @duration = @execution_result.run_time.to_s(:rounded, precision: 5) 87 | @status = @execution_result.status.to_s 88 | @metadata = example.metadata 89 | @file_path = @metadata[:file_path] 90 | @exception = Oopsy.new(example.exception, @file_path) 91 | @spec = nil 92 | end 93 | 94 | def has_exception? 95 | !@exception.klass.nil? 96 | end 97 | 98 | def has_spec? 99 | !@spec.nil? 100 | end 101 | 102 | def set_spec(spec) 103 | @spec = spec 104 | end 105 | 106 | def klass(prefix='label-') 107 | class_map = {passed: "#{prefix}success", failed: "#{prefix}danger", pending: "#{prefix}warning"} 108 | class_map[@status.to_sym] 109 | end 110 | 111 | end 112 | 113 | class Specify 114 | 115 | def initialize(examples) 116 | @examples = examples 117 | end 118 | 119 | def process 120 | lines = File.readlines(@examples.first.file_path) 121 | @examples.each_with_index do |e, i| 122 | start_line = e.metadata[:line_number] 123 | end_line = @examples[i+1].nil? ? lines.size : @examples[i+1].metadata[:line_number] - 1 124 | code_block = lines[start_line..end_line] 125 | spec = code_block.select { |l| l.match(/#->/) }.join('') 126 | if !spec.split.empty? 127 | formatter = Rouge::Formatters::HTML.new(css_class: 'highlight') 128 | lexer = Rouge::Lexers::Gherkin.new 129 | formatted_spec = formatter.format(lexer.lex(spec.gsub('#->', ''))) 130 | e.set_spec(formatted_spec) 131 | end 132 | end 133 | @examples 134 | end 135 | end 136 | 137 | class RspecHtmlFormatter < RSpec::Core::Formatters::BaseFormatter 138 | 139 | RSpec::Core::Formatters.register self, :example_started, :example_passed, :example_failed, :example_pending, :example_group_finished 140 | 141 | REPORT_PATH = ENV['REPORT_PATH'] || './rspec_html_reports' 142 | 143 | def initialize(io_standard_out) 144 | create_reports_dir 145 | create_resources_dir 146 | copy_resources 147 | @all_groups = {} 148 | end 149 | 150 | def example_group_started(notification) 151 | @example_group = {} 152 | @group_examples = [] 153 | @group_example_count = 0 154 | @group_example_success_count = 0 155 | @group_example_failure_count = 0 156 | @group_example_pending_count = 0 157 | end 158 | 159 | def example_started(notification) 160 | @group_example_count += 1 161 | end 162 | 163 | def example_passed(notification) 164 | @group_example_success_count += 1 165 | @group_examples << Example.new(notification.example) 166 | end 167 | 168 | def example_failed(notification) 169 | @group_example_failure_count += 1 170 | @group_examples << Example.new(notification.example) 171 | end 172 | 173 | def example_pending(notification) 174 | @group_example_pending_count += 1 175 | @group_examples << Example.new(notification.example) 176 | end 177 | 178 | def example_group_finished(notification) 179 | File.open("#{REPORT_PATH}/#{notification.group.description.parameterize}.html", 'w') do |f| 180 | 181 | @passed = @group_example_success_count 182 | @failed = @group_example_failure_count 183 | @pending = @group_example_pending_count 184 | 185 | duration_values = @group_examples.map { |e| e.run_time } 186 | 187 | duration_keys = duration_values.size.times.to_a 188 | if duration_values.size < 2 and duration_values.size > 0 189 | duration_values.unshift(duration_values.first) 190 | duration_keys = duration_keys << 1 191 | end 192 | 193 | @title = notification.group.description 194 | @durations = duration_keys.zip(duration_values) 195 | 196 | @summary_duration = duration_values.inject(0) { |sum, i| sum + i }.to_s(:rounded, precision: 5) 197 | @examples = Specify.new(@group_examples).process 198 | 199 | class_map = {passed: 'success', failed: 'danger', pending: 'warning'} 200 | statuses = @examples.map { |e| e.status } 201 | status = statuses.include?('failed') ? 'failed' : (statuses.include?('passed') ? 'passed' : 'pending') 202 | @all_groups[notification.group.description.parameterize] = { 203 | group: notification.group.description, 204 | examples: @examples.size, 205 | status: status, 206 | klass: class_map[status.to_sym], 207 | passed: statuses.select { |s| s == 'passed' }, 208 | failed: statuses.select { |s| s == 'failed' }, 209 | pending: statuses.select { |s| s == 'pending' }, 210 | duration: @summary_duration 211 | } 212 | 213 | template_file = File.read(File.dirname(__FILE__) + '/../templates/report.erb') 214 | 215 | f.puts ERB.new(template_file).result(binding) 216 | 217 | end 218 | 219 | end 220 | 221 | def close(notification) 222 | File.open("#{REPORT_PATH}/overview.html", 'w') do |f| 223 | @overview = @all_groups 224 | 225 | @passed = @overview.values.map { |g| g[:passed].size }.inject(0) { |sum, i| sum + i } 226 | @failed = @overview.values.map { |g| g[:failed].size }.inject(0) { |sum, i| sum + i } 227 | @pending = @overview.values.map { |g| g[:pending].size }.inject(0) { |sum, i| sum + i } 228 | 229 | duration_values = @overview.values.map { |e| e[:duration] } 230 | 231 | duration_keys = duration_values.size.times.to_a 232 | if duration_values.size < 2 233 | duration_values.unshift(duration_values.first) 234 | duration_keys = duration_keys << 1 235 | end 236 | 237 | @durations = duration_keys.zip(duration_values.map{|d| d.to_f.round(5)}) 238 | @summary_duration = duration_values.map{|d| d.to_f.round(5)}.inject(0) { |sum, i| sum + i }.to_s(:rounded, precision: 5) 239 | @total_examples = @passed + @failed + @pending 240 | template_file = File.read(File.dirname(__FILE__) + '/../templates/overview.erb') 241 | f.puts ERB.new(template_file).result(binding) 242 | end 243 | end 244 | 245 | 246 | private 247 | def create_reports_dir 248 | FileUtils.rm_rf(REPORT_PATH) if File.exists?(REPORT_PATH) 249 | FileUtils.mkpath(REPORT_PATH) 250 | end 251 | 252 | def create_resources_dir 253 | file_path = REPORT_PATH + '/resources' 254 | FileUtils.mkdir_p file_path unless File.exists?(file_path) 255 | end 256 | 257 | def copy_resources 258 | FileUtils.cp_r(File.dirname(__FILE__) + '/../resources', REPORT_PATH) 259 | end 260 | 261 | end 262 | -------------------------------------------------------------------------------- /resources/bootstrap-3.2.0-dist/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingsleyh/rspec_reports_formatter/b41275ffbd33b4d4776aa70784a655438f6cc622/resources/bootstrap-3.2.0-dist/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /resources/bootstrap-3.2.0-dist/fonts/glyphicons-halflings-regular.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | -------------------------------------------------------------------------------- /resources/bootstrap-3.2.0-dist/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingsleyh/rspec_reports_formatter/b41275ffbd33b4d4776aa70784a655438f6cc622/resources/bootstrap-3.2.0-dist/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /resources/bootstrap-3.2.0-dist/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingsleyh/rspec_reports_formatter/b41275ffbd33b4d4776aa70784a655438f6cc622/resources/bootstrap-3.2.0-dist/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /resources/bootstrap-3.2.0-dist/js/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.2.0 (http://getbootstrap.com) 3 | * Copyright 2011-2014 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */ 6 | if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript 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]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",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(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.2.0",d.prototype.close=function(b){function c(){f.detach().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("bsTransitionEnd",c).emulateTransitionEnd(150):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.2.0",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),d[e](null==f[b]?this.options[b]:f[b]),setTimeout(a.proxy(function(){"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")&&(c.prop("checked")&&this.$element.hasClass("active")?a=!1:b.find(".active").removeClass("active")),a&&c.prop("checked",!this.$element.hasClass("active")).trigger("change")}a&&this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),c.preventDefault()})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b).on("keydown.bs.carousel",a.proxy(this.keydown,this)),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.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.2.0",c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0},c.prototype.keydown=function(a){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()},c.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},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.to=function(b){var c=this,d=this.getItemIndex(this.$active=this.$element.find(".item.active"));return b>this.$items.length-1||0>b?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){c.to(b)}):d==b?this.pause().cycle():this.slide(b>d?"next":"prev",a(this.$items[b]))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.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;if(!e.length){if(!this.options.wrap)return;e=this.$element.find(".item")[h]()}if(e.hasClass("active"))return this.sliding=!1;var j=e[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:g});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,f&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(e)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:g});return a.support.transition&&this.$element.hasClass("slide")?(e.addClass(b),e[0].offsetWidth,d.addClass(g),e.addClass(g),d.one("bsTransitionEnd",function(){e.removeClass([b,g].join(" ")).addClass("active"),d.removeClass(["active",g].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(1e3*d.css("transition-duration").slice(0,-1))):(d.removeClass("active"),e.addClass("active"),this.sliding=!1,this.$element.trigger(m)),f&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this},a(document).on("click.bs.carousel.data-api","[data-slide], [data-slide-to]",function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}}),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.collapse"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b);!e&&f.toggle&&"show"==b&&(b=!b),e||d.data("bs.collapse",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.transitioning=null,this.options.parent&&(this.$parent=a(this.options.parent)),this.options.toggle&&this.toggle()};c.VERSION="3.2.0",c.DEFAULTS={toggle:!0},c.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},c.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var c=a.Event("show.bs.collapse");if(this.$element.trigger(c),!c.isDefaultPrevented()){var d=this.$parent&&this.$parent.find("> .panel > .in");if(d&&d.length){var e=d.data("bs.collapse");if(e&&e.transitioning)return;b.call(d,"hide"),e||d.data("bs.collapse",null)}var f=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[f](0),this.transitioning=1;var g=function(){this.$element.removeClass("collapsing").addClass("collapse in")[f](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return g.call(this);var h=a.camelCase(["scroll",f].join("-"));this.$element.one("bsTransitionEnd",a.proxy(g,this)).emulateTransitionEnd(350)[f](this.$element[0][h])}}},c.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?void this.$element[c](0).one("bsTransitionEnd",a.proxy(d,this)).emulateTransitionEnd(350):d.call(this)}}},c.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()};var d=a.fn.collapse;a.fn.collapse=b,a.fn.collapse.Constructor=c,a.fn.collapse.noConflict=function(){return a.fn.collapse=d,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(c){var d,e=a(this),f=e.attr("data-target")||c.preventDefault()||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""),g=a(f),h=g.data("bs.collapse"),i=h?"toggle":e.data(),j=e.attr("data-parent"),k=j&&a(j);h&&h.transitioning||(k&&k.find('[data-toggle="collapse"][data-parent="'+j+'"]').not(e).addClass("collapsed"),e[g.hasClass("in")?"addClass":"removeClass"]("collapsed")),b.call(g,i)})}(jQuery),+function(a){"use strict";function b(b){b&&3===b.which||(a(e).remove(),a(f).each(function(){var d=c(a(this)),e={relatedTarget:this};d.hasClass("open")&&(d.trigger(b=a.Event("hide.bs.dropdown",e)),b.isDefaultPrevented()||d.removeClass("open").trigger("hidden.bs.dropdown",e))}))}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.2.0",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a('