From d6f9dfa2f9ab86c82b5200ce57ea550259aa7e0b Mon Sep 17 00:00:00 2001 From: Faisal N Jawdat Date: Sun, 24 May 2026 16:02:25 +0100 Subject: [PATCH 1/5] Replace Aruba with direct API calls Aruba is a recurring source of dependency conflicts when updating Rubycritic dependencies*. Update Aruba call sites to call the underlying command line calls directly. Call with Open3 so as to capture results and return status. * Most recently, being able to update to diff-lcs 2.0, which we do in this commit. We ran into it more before when Rubycritic relied on Cucumber, though it doesn't any more. --- CHANGELOG.md | 1 + rubycritic.gemspec | 3 +- test/integration/integration_test_helper.rb | 51 ++++++++++++++++----- test/integration/minimum_score_spec.rb | 28 +++++------ test/integration/options_spec.rb | 24 ++++------ test/integration/rake_task_spec.rb | 32 ++++++------- 6 files changed, 77 insertions(+), 62 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d9d05a3..484719bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * [CHORE] ... * [FEATURE] ... +* [CHANGE] Replace Aruba with direct API calls in specs (by [@faisal][]) * [CHANGE] Replace all Cucumber features with Minitest/Spec specs (by [@faisal][]) # v5.0.0 / 2026-01-26 [(commits)](https://github.com/whitesmith/rubycritic/compare/v4.12.0...v5.0.0) diff --git a/rubycritic.gemspec b/rubycritic.gemspec index 88051dca..5763f156 100644 --- a/rubycritic.gemspec +++ b/rubycritic.gemspec @@ -45,7 +45,6 @@ Gem::Specification.new do |spec| spec.add_dependency 'tty-which', '~> 0.5.0' spec.add_dependency 'virtus', '~> 2.0' - spec.add_development_dependency 'aruba', '~> 2.3.1', '>= 2.3.1' spec.add_development_dependency 'bundler', '>= 2.0.0' if RUBY_PLATFORM == 'java' spec.add_development_dependency 'jar-dependencies', '~> 0.5.5' @@ -53,7 +52,7 @@ Gem::Specification.new do |spec| else spec.add_development_dependency 'byebug', '~> 13.0', '>= 10.0' end - spec.add_development_dependency 'diff-lcs', '~> 1.3' + spec.add_development_dependency 'diff-lcs', '~> 2.0' spec.add_development_dependency 'fakefs', '~> 3.2.0' spec.add_development_dependency 'mdl', '~> 0.15.0', '>= 0.12.0' spec.add_development_dependency 'minitest', '~> 6.0.0' diff --git a/test/integration/integration_test_helper.rb b/test/integration/integration_test_helper.rb index 6c356880..62c6fb35 100644 --- a/test/integration/integration_test_helper.rb +++ b/test/integration/integration_test_helper.rb @@ -1,21 +1,41 @@ # frozen_string_literal: true require_relative '../test_helper' -require 'aruba/api' +require 'open3' +require 'timeout' +require 'tmpdir' +require 'fileutils' require_relative '../../lib/rubycritic' require_relative '../../lib/rubycritic/cli/application' require_relative '../../lib/rubycritic/commands/status_reporter' -Aruba.configure { |config| config.exit_timeout = 30 } - module IntegrationTestHelper - include Aruba::Api + GEM_ROOT = File.expand_path('../..', __dir__) + COMMAND_TIMEOUT = 30 + + CommandResult = Struct.new(:stdout, :stderr, :exit_status) + + def before_setup + super + @sandbox_dir = Dir.mktmpdir('rubycritic-spec-') + @original_pwd = Dir.pwd + Dir.chdir(@sandbox_dir) + end + + def after_teardown + Dir.chdir(@original_pwd) if @original_pwd + FileUtils.remove_entry(@sandbox_dir) if @sandbox_dir && File.directory?(@sandbox_dir) + @sandbox_dir = nil + @original_pwd = nil + super + end + + def write_file(name, contents) + File.write(File.join(Dir.pwd, name), contents) + end def rubycritic(args) - run_command_and_stop( - "rubycritic #{args} --no-browser", - fail_on_error: false - ) + run_command("rubycritic #{args} --no-browser") end def rake(name, task_def) @@ -25,10 +45,7 @@ def rake(name, task_def) RUBY write_file 'Rakefile', header + task_def - run_command_and_stop( - "rake #{name}", - fail_on_error: false - ) + run_command("rake #{name}") end def create_smelly_file @@ -61,4 +78,14 @@ def foo; end def create_empty_file write_file('empty.rb', '') end + + private + + def run_command(command) + env = { 'PATH' => "#{File.join(GEM_ROOT, 'bin')}#{File::PATH_SEPARATOR}#{ENV.fetch('PATH', '')}" } + stdout, stderr, status = Timeout.timeout(COMMAND_TIMEOUT) do + Open3.capture3(env, command) + end + CommandResult.new(stdout, stderr, status.exitstatus) + end end diff --git a/test/integration/minimum_score_spec.rb b/test/integration/minimum_score_spec.rb index 48b79c0e..7e8e7c9d 100644 --- a/test/integration/minimum_score_spec.rb +++ b/test/integration/minimum_score_spec.rb @@ -5,61 +5,57 @@ describe 'Minimum score' do include IntegrationTestHelper - before do - setup_aruba - end - describe 'Status indicates a success when not using --minimum-score' do it 'succeeds without minimum score' do create_smelly_file - rubycritic('smelly.rb') + result = rubycritic('smelly.rb') - _(last_command_started.exit_status).must_equal RubyCritic::Command::StatusReporter::SUCCESS + _(result.exit_status).must_equal RubyCritic::Command::StatusReporter::SUCCESS end end describe 'Status indicates an error when score below the minimum' do it 'fails when score below minimum' do create_smelly_file - rubycritic('--minimum-score 100 smelly.rb') + result = rubycritic('--minimum-score 100 smelly.rb') - _(last_command_started.exit_status).must_equal RubyCritic::Command::StatusReporter::SCORE_BELOW_MINIMUM + _(result.exit_status).must_equal RubyCritic::Command::StatusReporter::SCORE_BELOW_MINIMUM end end describe 'Status indicates a success when score is above the minimum' do it 'succeeds when score above minimum' do create_smelly_file - rubycritic('--minimum-score 93 smelly.rb') + result = rubycritic('--minimum-score 93 smelly.rb') - _(last_command_started.exit_status).must_equal RubyCritic::Command::StatusReporter::SUCCESS + _(result.exit_status).must_equal RubyCritic::Command::StatusReporter::SUCCESS end end describe 'Status indicates a success when score is equal the minimum' do it 'succeeds when score equals minimum' do create_clean_file - rubycritic('--minimum-score 100 clean.rb') + result = rubycritic('--minimum-score 100 clean.rb') - _(last_command_started.exit_status).must_equal RubyCritic::Command::StatusReporter::SUCCESS + _(result.exit_status).must_equal RubyCritic::Command::StatusReporter::SUCCESS end end describe 'Prints the score on output' do it 'prints score' do create_smelly_file - rubycritic('smelly.rb') + result = rubycritic('smelly.rb') - _(last_command_started.stdout).must_include 'Score: 93.19' + _(result.stdout).must_include 'Score: 93.19' end end describe 'Prints a message informing the score is below the minimum' do it 'prints below minimum message' do create_empty_file - rubycritic('--minimum-score 101 empty.rb') + result = rubycritic('--minimum-score 101 empty.rb') - _(last_command_started.stdout).must_include 'Score (100.0) is below the minimum 101.0' + _(result.stdout).must_include 'Score (100.0) is below the minimum 101.0' end end end diff --git a/test/integration/options_spec.rb b/test/integration/options_spec.rb index 9f5bb09a..5db4bcea 100644 --- a/test/integration/options_spec.rb +++ b/test/integration/options_spec.rb @@ -5,34 +5,30 @@ describe 'Command line options' do include IntegrationTestHelper - before do - setup_aruba - end - describe 'return non-zero status on bad option' do it 'fails on bad option' do - rubycritic('--no-such-option') + result = rubycritic('--no-such-option') - _(last_command_started.exit_status).must_equal RubyCritic::Command::StatusReporter::SCORE_BELOW_MINIMUM - _(last_command_started.stderr).must_include 'Error: invalid option: --no-such-option' - _(last_command_started.stdout).must_equal '' + _(result.exit_status).must_equal RubyCritic::Command::StatusReporter::SCORE_BELOW_MINIMUM + _(result.stderr).must_include 'Error: invalid option: --no-such-option' + _(result.stdout).must_equal '' end end describe 'display the current version number' do it 'shows version' do - rubycritic('--version') + result = rubycritic('--version') - _(last_command_started.exit_status).must_equal RubyCritic::Command::StatusReporter::SUCCESS - _(last_command_started.stdout).must_include "RubyCritic #{RubyCritic::VERSION}" + _(result.exit_status).must_equal RubyCritic::Command::StatusReporter::SUCCESS + _(result.stdout).must_include "RubyCritic #{RubyCritic::VERSION}" end end describe 'display the help information' do it 'shows help' do - rubycritic('--help') + result = rubycritic('--help') - _(last_command_started.exit_status).must_equal RubyCritic::Command::StatusReporter::SUCCESS + _(result.exit_status).must_equal RubyCritic::Command::StatusReporter::SUCCESS expected_help = <<~HELP Usage: rubycritic [options] [paths] -p, --path [PATH] Set path where report will be saved (tmp/rubycritic by default) @@ -67,7 +63,7 @@ -h, --help Show this message HELP - _(last_command_started.stdout).must_include expected_help + _(result.stdout).must_include expected_help end end end diff --git a/test/integration/rake_task_spec.rb b/test/integration/rake_task_spec.rb index 53b1b4f9..0a9661a5 100644 --- a/test/integration/rake_task_spec.rb +++ b/test/integration/rake_task_spec.rb @@ -5,29 +5,25 @@ describe 'Rake task' do include IntegrationTestHelper - before do - setup_aruba - end - describe 'paths attribute is respected' do it 'runs rake rubycritic with paths' do create_smelly_file - rake('rubycritic', <<~RUBY) + result = rake('rubycritic', <<~RUBY) RubyCritic::RakeTask.new do |t| t.paths = FileList['smelly.*'] t.options = '--no-browser -f console' end RUBY - _(last_command_started.stdout).must_include '(HighComplexity) AllTheMethods#method_missing has a flog score of 27' - _(last_command_started.exit_status).must_equal RubyCritic::Command::StatusReporter::SUCCESS + _(result.stdout).must_include '(HighComplexity) AllTheMethods#method_missing has a flog score of 27' + _(result.exit_status).must_equal RubyCritic::Command::StatusReporter::SUCCESS end end describe 'name option changes the task name' do it 'runs rake silky' do create_smelly_file - rake('silky', <<~RUBY) + result = rake('silky', <<~RUBY) RubyCritic::RakeTask.new('silky') do |t| t.paths = FileList['smelly.*'] t.verbose = true @@ -35,14 +31,14 @@ end RUBY - _(last_command_started.stdout).must_include 'Running `silky` rake command' + _(result.stdout).must_include 'Running `silky` rake command' end end describe 'verbose prints details about the execution' do it 'runs rake rubycritic with verbose' do create_smelly_file - rake('rubycritic', <<~RUBY) + result = rake('rubycritic', <<~RUBY) RubyCritic::RakeTask.new do |t| t.paths = FileList['smelly.*'] t.verbose = true @@ -50,15 +46,15 @@ end RUBY - _(last_command_started.stdout).must_include '!!! Running `rubycritic` rake command' - _(last_command_started.stdout).must_include '!!! Inspecting smelly.rb with options --no-browser' + _(result.stdout).must_include '!!! Running `rubycritic` rake command' + _(result.stdout).must_include '!!! Inspecting smelly.rb with options --no-browser' end end describe 'respect --minimum-score' do it 'fails when score below minimum' do create_smelly_file - rake('rubycritic', <<~RUBY) + result = rake('rubycritic', <<~RUBY) RubyCritic::RakeTask.new do |t| t.paths = FileList['smelly.*'] t.verbose = true @@ -66,15 +62,15 @@ end RUBY - _(last_command_started.stdout).must_include 'Score (93.19) is below the minimum 95' - _(last_command_started.exit_status).must_equal RubyCritic::Command::StatusReporter::SCORE_BELOW_MINIMUM + _(result.stdout).must_include 'Score (93.19) is below the minimum 95' + _(result.exit_status).must_equal RubyCritic::Command::StatusReporter::SCORE_BELOW_MINIMUM end end describe 'fail_on_error when false will exit 0 even when RubyCritic fails' do it 'succeeds despite low score' do create_smelly_file - rake('rubycritic', <<~RUBY) + result = rake('rubycritic', <<~RUBY) RubyCritic::RakeTask.new do |t| t.paths = FileList['smelly.*'] t.fail_on_error = false @@ -82,8 +78,8 @@ end RUBY - _(last_command_started.stdout).must_include 'Score (93.19) is below the minimum 95' - _(last_command_started.exit_status).must_equal RubyCritic::Command::StatusReporter::SUCCESS + _(result.stdout).must_include 'Score (93.19) is below the minimum 95' + _(result.exit_status).must_equal RubyCritic::Command::StatusReporter::SUCCESS end end end From aac22c84643d5ba342c2383502b94762da56af92 Mon Sep 17 00:00:00 2001 From: Faisal N Jawdat Date: Fri, 12 Jun 2026 20:08:00 +0100 Subject: [PATCH 2/5] Update test/integration/integration_test_helper.rb MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Juan Vásquez --- test/integration/integration_test_helper.rb | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/test/integration/integration_test_helper.rb b/test/integration/integration_test_helper.rb index 62c6fb35..33298f58 100644 --- a/test/integration/integration_test_helper.rb +++ b/test/integration/integration_test_helper.rb @@ -83,9 +83,21 @@ def create_empty_file def run_command(command) env = { 'PATH' => "#{File.join(GEM_ROOT, 'bin')}#{File::PATH_SEPARATOR}#{ENV.fetch('PATH', '')}" } - stdout, stderr, status = Timeout.timeout(COMMAND_TIMEOUT) do - Open3.capture3(env, command) + Open3.popen3(env, command) do |stdin, stdout, stderr, wait_thr| + stdin.close + out = err = nil + begin + Timeout.timeout(COMMAND_TIMEOUT) do + out = stdout.read + err = stderr.read + wait_thr.join + end + rescue Timeout::Error + Process.kill('KILL', wait_thr.pid) + wait_thr.join + raise + end + return CommandResult.new(out, err, wait_thr.value.exitstatus) end - CommandResult.new(stdout, stderr, status.exitstatus) end end From bf98c69c90393910f8bfc77b27ef9b5af0b0ed6d Mon Sep 17 00:00:00 2001 From: MarcusA <71977744+MarcusAl@users.noreply.github.com> Date: Tue, 9 Jun 2026 01:53:21 +0100 Subject: [PATCH 3/5] fix(a11y): structural WCAG 2.1 AA fixes on overview report (#572) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add lang="en" on so screen readers and translation features pick the correct language for the entire report. - Give the navbar menu-toggle anchor an accessible name via aria-label. - Make per-rating summary IDs unique (rating--{files,churns,smells}) so the HTML spec's unique-ID requirement holds and JS/test selectors can target individual cells. Verified via pa11y --standard WCAG2AA on overview.html: before 50 errors after 34 errors (-16: -15 dup IDs, -1 missing lang, +1 anchor label) Remaining 34 (deliberately out of scope for this PR): - 30 G18 + 3 G145 contrast violations on #A4A4A4 grays — opinionated style change, would prefer maintainer-led decision before changing. - 1 F77 duplicate id="page-content-wrapper" — cross-page (affects overview/smells_index/simple_cov_index templates), separate scope. Tests + RuboCop green. Test snapshot under test/samples/ auto-updated to reflect the new template output. --- CHANGELOG.md | 2 ++ .../generators/html/templates/layouts/application.html.erb | 4 ++-- lib/rubycritic/generators/html/templates/overview.html.erb | 6 +++--- test/samples/simple_cov_index.html | 4 ++-- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 484719bf..1316935f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ * [CHANGE] Replace Aruba with direct API calls in specs (by [@faisal][]) * [CHANGE] Replace all Cucumber features with Minitest/Spec specs (by [@faisal][]) +* [BUGFIX] Add `lang="en"` to the report's `` element, give the menu-toggle anchor an `aria-label`, and make the per-rating summary IDs unique. Fixes 17 WCAG 2.1 AA structural errors on `overview.html`. (by [@MarcusAl][]) # v5.0.0 / 2026-01-26 [(commits)](https://github.com/whitesmith/rubycritic/compare/v4.12.0...v5.0.0) @@ -505,3 +506,4 @@ [@fbuys]: https://github.com/fbuys [@exoego]: https://github.com/exoego [@raff-s]: https://github.com/raff-s +[@MarcusAl]: https://github.com/MarcusAl diff --git a/lib/rubycritic/generators/html/templates/layouts/application.html.erb b/lib/rubycritic/generators/html/templates/layouts/application.html.erb index fae2e5e8..1c1f328a 100644 --- a/lib/rubycritic/generators/html/templates/layouts/application.html.erb +++ b/lib/rubycritic/generators/html/templates/layouts/application.html.erb @@ -1,5 +1,5 @@ - + @@ -16,7 +16,7 @@