diff --git a/.rspec b/.rspec new file mode 100644 index 0000000..4e1e0d2 --- /dev/null +++ b/.rspec @@ -0,0 +1 @@ +--color diff --git a/Gemfile.lock b/Gemfile.lock index a1cd532..d0173b7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -10,47 +10,43 @@ PATH GEM remote: https://rubygems.org/ specs: - actionmailer (4.0.4) - actionpack (= 4.0.4) + actionmailer (4.1.4) + actionpack (= 4.1.4) + actionview (= 4.1.4) mail (~> 2.5.4) - actionpack (4.0.4) - activesupport (= 4.0.4) - builder (~> 3.1.0) - erubis (~> 2.7.0) + actionpack (4.1.4) + actionview (= 4.1.4) + activesupport (= 4.1.4) rack (~> 1.5.2) rack-test (~> 0.6.2) - activemodel (4.0.4) - activesupport (= 4.0.4) - builder (~> 3.1.0) - activerecord (4.0.4) - activemodel (= 4.0.4) - activerecord-deprecated_finders (~> 1.0.2) - activesupport (= 4.0.4) - arel (~> 4.0.0) - activerecord-deprecated_finders (1.0.3) - activesupport (4.0.4) + actionview (4.1.4) + activesupport (= 4.1.4) + builder (~> 3.1) + erubis (~> 2.7.0) + activemodel (4.1.4) + activesupport (= 4.1.4) + builder (~> 3.1) + activerecord (4.1.4) + activemodel (= 4.1.4) + activesupport (= 4.1.4) + arel (~> 5.0.0) + activesupport (4.1.4) i18n (~> 0.6, >= 0.6.9) - minitest (~> 4.2) - multi_json (~> 1.3) + json (~> 1.7, >= 1.7.7) + minitest (~> 5.1) thread_safe (~> 0.1) - tzinfo (~> 0.3.37) - addressable (2.3.5) - arel (4.0.2) - atomic (1.1.16) - awesome_print (1.2.0) - binding_of_caller (0.7.2) - debug_inspector (>= 0.0.1) - builder (3.1.4) - capybara (2.2.1) + tzinfo (~> 1.1) + addressable (2.3.6) + arel (5.0.1.20140414130214) + builder (3.2.2) + capybara (2.4.1) mime-types (>= 1.16) nokogiri (>= 1.3.3) rack (>= 1.0.0) rack-test (>= 0.5.4) xpath (~> 2.0) coderay (1.1.0) - colorize (0.7.0) - columnize (0.3.6) - coolline (0.4.3) + colorize (0.7.3) coveralls (0.7.0) multi_json (~> 1.3) rest-client @@ -59,130 +55,94 @@ GEM thor css_parser (1.3.5) addressable - debug_inspector (0.0.2) - debugger (1.6.6) - columnize (>= 0.3.1) - debugger-linecache (~> 1.2.0) - debugger-ruby_core_source (~> 1.3.2) - debugger-linecache (1.2.0) - debugger-ruby_core_source (1.3.2) diff-lcs (1.2.5) - diffy (3.0.3) - docile (1.1.3) + docile (1.1.5) erubis (2.7.0) - grit (2.5.0) - diff-lcs (~> 1.1) - mime-types (~> 1.15) - posix-spawn (~> 0.3.6) hike (1.2.3) - hirb (0.7.1) - i18n (0.6.9) - jazz_hands (0.5.2) - awesome_print (~> 1.2) - coolline (>= 0.4.2) - hirb (~> 0.7.1) - pry (~> 0.9.12) - pry-debugger (~> 0.2.2) - pry-doc (~> 0.4.6) - pry-git (~> 0.2.3) - pry-rails (~> 0.3.2) - pry-remote (>= 0.1.7) - pry-stack_explorer (~> 0.4.9) - railties (>= 3.0, < 5.0) + i18n (0.6.11) + json (1.8.1) mail (2.5.4) mime-types (~> 1.16) treetop (~> 1.4.8) method_source (0.8.2) mime-types (1.25.1) - mini_portile (0.5.2) - minitest (4.7.5) - multi_json (1.9.0) - nokogiri (1.6.1) - mini_portile (~> 0.5.0) - polyglot (0.3.4) - posix-spawn (0.3.8) - pry (0.9.12.6) - coderay (~> 1.0) - method_source (~> 0.8) + mini_portile (0.6.0) + minitest (5.4.0) + multi_json (1.10.1) + netrc (0.7.7) + nokogiri (1.6.3) + mini_portile (= 0.6.0) + polyglot (0.3.5) + pry (0.10.0) + coderay (~> 1.1.0) + method_source (~> 0.8.1) slop (~> 3.4) - pry-debugger (0.2.2) - debugger (~> 1.3) - pry (~> 0.9.10) - pry-doc (0.4.6) - pry (>= 0.9) - yard (>= 0.8) - pry-git (0.2.3) - diffy - grit - pry (>= 0.9.8) - pry-rails (0.3.2) - pry (>= 0.9.10) - pry-remote (0.1.8) - pry (~> 0.9) - slop (~> 3.0) - pry-stack_explorer (0.4.9.1) - binding_of_caller (>= 0.7) - pry (>= 0.9.11) rack (1.5.2) rack-test (0.6.2) rack (>= 1.0) - rails (4.0.4) - actionmailer (= 4.0.4) - actionpack (= 4.0.4) - activerecord (= 4.0.4) - activesupport (= 4.0.4) + rails (4.1.4) + actionmailer (= 4.1.4) + actionpack (= 4.1.4) + actionview (= 4.1.4) + activemodel (= 4.1.4) + activerecord (= 4.1.4) + activesupport (= 4.1.4) bundler (>= 1.3.0, < 2.0) - railties (= 4.0.4) - sprockets-rails (~> 2.0.0) - railties (4.0.4) - actionpack (= 4.0.4) - activesupport (= 4.0.4) + railties (= 4.1.4) + sprockets-rails (~> 2.0) + railties (4.1.4) + actionpack (= 4.1.4) + activesupport (= 4.1.4) rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) - rake (10.1.1) - rest-client (1.6.7) - mime-types (>= 1.16) - rspec-core (2.14.8) - rspec-expectations (2.14.5) - diff-lcs (>= 1.1.3, < 2.0) - rspec-mocks (2.14.6) - rspec-rails (2.14.1) + rake (10.3.2) + rest-client (1.7.2) + mime-types (>= 1.16, < 3.0) + netrc (~> 0.7) + rspec-core (3.0.2) + rspec-support (~> 3.0.0) + rspec-expectations (3.0.2) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.0.0) + rspec-mocks (3.0.2) + rspec-support (~> 3.0.0) + rspec-rails (3.0.1) actionpack (>= 3.0) - activemodel (>= 3.0) activesupport (>= 3.0) railties (>= 3.0) - rspec-core (~> 2.14.0) - rspec-expectations (~> 2.14.0) - rspec-mocks (~> 2.14.0) - simplecov (0.8.2) + rspec-core (~> 3.0.0) + rspec-expectations (~> 3.0.0) + rspec-mocks (~> 3.0.0) + rspec-support (~> 3.0.0) + rspec-support (3.0.2) + simplecov (0.9.0) docile (~> 1.1.0) multi_json simplecov-html (~> 0.8.0) simplecov-html (0.8.0) - slop (3.5.0) - sprockets (2.12.0) + slop (3.6.0) + sprockets (2.12.1) hike (~> 1.2) multi_json (~> 1.0) rack (~> 1.0) tilt (~> 1.1, != 1.3.0) - sprockets-rails (2.0.1) + sprockets-rails (2.1.3) actionpack (>= 3.0) activesupport (>= 3.0) sprockets (~> 2.8) term-ansicolor (1.3.0) tins (~> 1.0) - thor (0.18.1) - thread_safe (0.2.0) - atomic (>= 1.1.7, < 2) + thor (0.19.1) + thread_safe (0.3.4) tilt (1.4.1) - tins (1.0.1) + tins (1.3.0) treetop (1.4.15) polyglot polyglot (>= 0.3.1) - tzinfo (0.3.39) + tzinfo (1.2.1) + thread_safe (~> 0.1) xpath (2.0.0) nokogiri (~> 1.3) - yard (0.8.7.3) PLATFORMS ruby @@ -191,5 +151,5 @@ DEPENDENCIES capybara coveralls headhunter! - jazz_hands + pry rspec-rails diff --git a/README.md b/README.md index 91e642b..9b26a9b 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,13 @@ $ HEADHUNTER=true cucumber Headhunter doesn't keep your tests from passing if invalid HTML or unused CSS is found. Instead it displays a short statistic after the tests are run. +If you want to disable a single part of validation, start your tests with this environment variables: + +``` +$ HEADHUNTER=true HEADHUNTER_CSS=false rspec # only HTML stuff is invoked +$ HEADHUNTER=true HEADHUNTER_HTML=false rspec # only CSS stuff is invoked +``` + ![Headhunter output](docs/screenshot.png) ## How it works diff --git a/headhunter.gemspec b/headhunter.gemspec index 960008f..bd574fb 100644 --- a/headhunter.gemspec +++ b/headhunter.gemspec @@ -21,9 +21,9 @@ Gem::Specification.new do |s| s.add_dependency 'colorize' s.add_dependency 'nokogiri' + s.add_development_dependency 'pry' s.add_development_dependency 'rspec-rails' s.add_development_dependency 'capybara' - s.add_development_dependency 'jazz_hands' s.test_files = Dir['spec/**/*'] end diff --git a/lib/headhunter.rb b/lib/headhunter.rb index 81fe4c8..a2d7e37 100644 --- a/lib/headhunter.rb +++ b/lib/headhunter.rb @@ -1,10 +1,12 @@ -if ENV['HEADHUNTER'] == 'true' +if ENV['HEADHUNTER'] == 'true' || ENV['RAILS_ENV'] == 'test' require 'headhunter/engine' require 'headhunter/css_hunter' require 'headhunter/css_validator' require 'headhunter/html_validator' require 'headhunter/rails' - require 'headhunter/runner' + require 'headhunter/runners/runner' + require 'headhunter/runners/html_runner' + require 'headhunter/runners/css_runner' end module Headhunter diff --git a/lib/headhunter/css_validator.rb b/lib/headhunter/css_validator.rb index 2f0e979..5d157fa 100644 --- a/lib/headhunter/css_validator.rb +++ b/lib/headhunter/css_validator.rb @@ -23,7 +23,7 @@ def initialize(stylesheets = [], profile = 'css3', vextwarning = true) def validate(uri) Dir.chdir(VALIDATOR_DIR) do - raise "Couldn't locate uri #{uri}" unless File.exists? uri + fail "Couldn't locate uri #{uri}" unless File.exists? uri # See http://stackoverflow.com/questions/1137884/is-there-an-open-source-css-validator-that-can-be-run-locally # More config options see http://jigsaw.w3.org/css-validator/manual.html diff --git a/lib/headhunter/html_validator.rb b/lib/headhunter/html_validator.rb index f7c447d..dd6acc8 100644 --- a/lib/headhunter/html_validator.rb +++ b/lib/headhunter/html_validator.rb @@ -3,7 +3,7 @@ module Headhunter class HtmlValidator - VALIDATOR_DIR = Gem.loaded_specs['headhunter'].full_gem_path + '/lib/tidy/' + VALIDATOR_DIR = File.join Gem.loaded_specs['headhunter'].full_gem_path, '/lib/tidy' EXECUTABLE = 'tidy' attr_reader :responses @@ -13,17 +13,26 @@ def initialize end def validate(uri, html) - Dir.chdir(VALIDATOR_DIR) do - raise "Could not find tidy in #{Dir.pwd}" unless File.exists? EXECUTABLE + tidy_path = %x[which #{EXECUTABLE}].strip + tidy_path = File.join(VALIDATOR_DIR, EXECUTABLE) unless tidy_path.present? - # Docs for Tidy: http://tidy.sourceforge.net/docs/quickref.html - stdin, stdout, stderr = Open3.popen3("#{EXECUTABLE} -quiet") + fail "Could not find #{tidy_path}" unless File.exist? tidy_path + + # tidy_version = `#{executable} -v` + # puts "Using #{executable}: #{tidy_version}" + + # Docs for Tidy: http://tidy.sourceforge.net/docs/quickref.html + + begin + stdin, stdout, stderr = Open3.popen3("#{tidy_path} -quiet") stdin.puts html stdin.close stdout.close @responses << Response.new(stderr.read, uri) stderr.close + rescue Encoding::UndefinedConversionError + # not HTML, maybe something else (PDF, image, ...) end end diff --git a/lib/headhunter/rack/capturing_middleware.rb b/lib/headhunter/rack/capturing_middleware.rb index d7c1545..f7e5b5d 100755 --- a/lib/headhunter/rack/capturing_middleware.rb +++ b/lib/headhunter/rack/capturing_middleware.rb @@ -7,21 +7,22 @@ def initialize(app, headhunter) end def call(env) + url = env['PATH_INFO'] || 'unknown' response = @app.call(env) - process(response) + process(url, response) response end - def process(rack_response) + def process(url, rack_response) status, headers, response = rack_response if html = extract_html_from(response) - @hh.process('unknown', html) + @hh.process(url, html) end end def extract_html_from(response) - response[0] + response[0] if response.respond_to? :[] end end end diff --git a/lib/headhunter/runner.rb b/lib/headhunter/runners/css_runner.rb similarity index 71% rename from lib/headhunter/runner.rb rename to lib/headhunter/runners/css_runner.rb index 5bfb257..339a0cb 100644 --- a/lib/headhunter/runner.rb +++ b/lib/headhunter/runners/css_runner.rb @@ -1,45 +1,37 @@ -require 'fileutils' - module Headhunter - class Runner + class CssRunner ASSETS_PATH = 'public/assets' - attr_accessor :results - - def initialize(root) - @root = root - @temporary_assets = [] - + def initialize precompile_assets! - - @html_validator = HtmlValidator.new @css_hunter = CssHunter.new(stylesheets) - @css_validator = CssValidator.new(stylesheets) end def process(url, html) - @html_validator.validate(url, html) @css_hunter.process(html) + # TODO: maybe we should call @css_validator.validate(html) ? + end + + def results + [ + @css_hunter.statistics, + @css_validator.statistics, + ] end - def clean_up! + def clean_up print "Headhunter is removing precompiled assets...".yellow remove_assets! puts " done!".yellow end - def report - puts [ @html_validator.statistics, - @css_validator.statistics, - @css_hunter.statistics - ].join "\n\n" + private - puts + def stylesheets + Dir["#{::Rails.root}/#{ASSETS_PATH}/*.css"] end - private - def precompile_assets! print "Headhunter is removing eventually existing assets...".yellow remove_assets! # Remove existing assets! This seems to be necessary to make sure that they don't exist twice, see http://stackoverflow.com/questions/20938891 @@ -56,9 +48,5 @@ def precompile_assets! def remove_assets! FileUtils.rm_r ASSETS_PATH if File.exist?(ASSETS_PATH) end - - def stylesheets - Dir["#{::Rails.root}/#{ASSETS_PATH}/*.css"] - end end end diff --git a/lib/headhunter/runners/html_runner.rb b/lib/headhunter/runners/html_runner.rb new file mode 100644 index 0000000..4e9ab27 --- /dev/null +++ b/lib/headhunter/runners/html_runner.rb @@ -0,0 +1,17 @@ +module Headhunter + class HtmlRunner + def initialize + @html_validator = HtmlValidator.new + end + + def process(url, html) + @html_validator.validate(url, html) + end + + def results + @html_validator.statistics + end + + def clean_up ; end + end +end diff --git a/lib/headhunter/runners/runner.rb b/lib/headhunter/runners/runner.rb new file mode 100644 index 0000000..7618ff5 --- /dev/null +++ b/lib/headhunter/runners/runner.rb @@ -0,0 +1,31 @@ +require 'fileutils' + +module Headhunter + class Runner + attr_accessor :results + + def initialize(root) + @root = root + + @runners = [] + @runners << HtmlRunner.new if (ENV['HEADHUNTER_HTML'] || 'true') == 'true' + @runners << CssRunner.new if (ENV['HEADHUNTER_CSS'] || 'true') == 'true' + end + + def process(url, html) + @runners.each do |runner| + runner.process(url, html) + end + end + + def clean_up! + @runners.each do |runner| + runner.clean_up + end + end + + def report + puts @runners.map { |runner| runner.results }.compact.join "\n\n" + end + end +end diff --git a/spec/dummy/spec/features/middleware_integration_spec.rb b/spec/dummy/spec/features/middleware_integration_spec.rb index 9ce814b..5d29693 100644 --- a/spec/dummy/spec/features/middleware_integration_spec.rb +++ b/spec/dummy/spec/features/middleware_integration_spec.rb @@ -3,8 +3,8 @@ feature 'Middleware integration' do scenario "Integrating the middleware into the Rack stack" do pending "The expectation doesn't work, see http://stackoverflow.com/questions/21940082" - # More (possibly) related infos here: http://shift.mirego.com/post/68808986788/how-to-write-tests-for-rack-middleware and http://www.sinatrarb.com/testing.html - Headhunter::Rack::CapturingMiddleware.any_instance.should_receive(:call) + # More (possibly) related infos here: http://shift.mirego.com/post/68808986788/how-to-write-tests-for-rack-middleware and http://www.sinatrarb.com/testing.html + expect_any_instance_of(Headhunter::Rack::CapturingMiddleware).to receive(:call) visit posts_path end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 35bc110..4c71f26 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -2,9 +2,10 @@ require File.expand_path("../dummy/config/environment.rb", __FILE__) require 'rspec/rails' -require 'rspec/autorun' require 'pry' +require 'headhunter' + Rails.backtrace_cleaner.remove_silencers! # Load support files @@ -22,4 +23,4 @@ def path_to_file(name) def read_file(name) File.read(path_to_file(name)) -end \ No newline at end of file +end