From c29a6f8f1699479e4b6c9e9239864fae503d6254 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20V=C3=A1squez?= Date: Tue, 31 Mar 2026 00:49:52 -0600 Subject: [PATCH 1/4] Use helper methods for code stat hashes in JSON formatter test Replace verbose hash literals with code_stat() and total_stat() helpers that use keyword arguments with sensible defaults, making the test expectations easier to scan and maintain. --- test/lib/rails_stats/json_formatter_test.rb | 336 ++++---------------- 1 file changed, 65 insertions(+), 271 deletions(-) diff --git a/test/lib/rails_stats/json_formatter_test.rb b/test/lib/rails_stats/json_formatter_test.rb index 8bce6ba..7a7c9cd 100644 --- a/test/lib/rails_stats/json_formatter_test.rb +++ b/test/lib/rails_stats/json_formatter_test.rb @@ -4,290 +4,84 @@ describe RailsStats::JSONFormatter do describe "#result" do - JSON_STRING = <<~EOS - [{ - "summary": { - "declared": 9, - "unpinned": 6, - "total": 18, - "github": 0 - }, - "gems": [ - { - "name": "simplecov-console", - "total_dependencies": 8, - "first_level_dependencies": 3, - "top_level_dependencies": {}, - "transitive_dependencies": [ - "ansi (>= 0)", - "simplecov (>= 0)", - "terminal-table (>= 0)", - "docile (~> 1.1)", - "simplecov-html (~> 0.11)", - "simplecov_json_formatter (~> 0.1)", - "unicode-display_width (>= 1.1.1, < 4)", - "unicode-emoji (~> 4.0, >= 4.0.4)" - ] - }, - { - "name": "codecov", - "total_dependencies": 4, - "first_level_dependencies": 1, - "top_level_dependencies": {}, - "transitive_dependencies": [ - "simplecov (>= 0.15, < 0.22)", - "docile (~> 1.1)", - "simplecov-html (~> 0.11)", - "simplecov_json_formatter (~> 0.1)" - ] - }, - { - "name": "rails_stats", - "total_dependencies": 4, - "first_level_dependencies": 2, - "top_level_dependencies": {}, - "transitive_dependencies": [ - "bundler-stats (>= 2.1)", - "rake (>= 0)", - "bundler (>= 1.9, < 3)", - "thor (>= 0.19.0, < 2.0)" - ] - }, - { - "name": "simplecov", - "total_dependencies": 3, - "first_level_dependencies": 3, - "top_level_dependencies": { - "codecov": "codecov (0.6.0)", - "simplecov-console": "simplecov-console (0.9.3)" - }, - "transitive_dependencies": [ - "docile (~> 1.1)", - "simplecov-html (~> 0.11)", - "simplecov_json_formatter (~> 0.1)" - ] - }, - { - "name": "minitest-around", - "total_dependencies": 1, - "first_level_dependencies": 1, - "top_level_dependencies": {}, - "transitive_dependencies": [ - "minitest (~> 5.0)" - ] - }, - { - "name": "bundler", - "total_dependencies": 0, - "first_level_dependencies": 0, - "top_level_dependencies": { - "bundler-stats": "bundler-stats (2.4.0)", - "rails_stats": "rails_stats (2.0.1)" - }, - "transitive_dependencies": [] - }, - { - "name": "byebug", - "total_dependencies": 0, - "first_level_dependencies": 0, - "top_level_dependencies": {}, - "transitive_dependencies": [] - }, - { - "name": "minitest", - "total_dependencies": 0, - "first_level_dependencies": 0, - "top_level_dependencies": { - "minitest-around": "minitest-around (0.5.0)" - }, - "transitive_dependencies": [] - }, - { - "name": "minitest-spec-context", - "total_dependencies": 0, - "first_level_dependencies": 0, - "top_level_dependencies": {}, - "transitive_dependencies": [] - } - ] - },{ - "name": "Mailers", - "files": "1", - "lines": "4", - "loc": "4", - "classes": "1", - "methods": "0", - "m_over_c": "0", - "loc_over_m": "0" - }, { - "name": "Helpers", - "files": "1", - "lines": "3", - "loc": "3", - "classes": "0", - "methods": "0", - "m_over_c": "0", - "loc_over_m": "0" - }, { - "name": "Jobs", - "files": "1", - "lines": "7", - "loc": "2", - "classes": "1", - "methods": "0", - "m_over_c": "0", - "loc_over_m": "0" - }, { - "name": "Controllers", - "files": "1", - "lines": "7", - "loc": "6", - "classes": "1", - "methods": "1", - "m_over_c": "1", - "loc_over_m": "4" - }, { - "name": "Models", - "files": "4", - "lines": "10", - "loc": "10", - "classes": "4", - "methods": "0", - "m_over_c": "0", - "loc_over_m": "0" - }, { - "name": "Channels", - "files": "2", - "lines": "8", - "loc": "8", - "classes": "2", - "methods": "0", - "m_over_c": "0", - "loc_over_m": "0" - }, { - "name": "Javascripts", - "files": "6", - "lines": "28", - "loc": "7", - "classes": "0", - "methods": "0", - "m_over_c": "0", - "loc_over_m": "0" - }, { - "name": "Libraries", - "files": "1", - "lines": "1", - "loc": "1", - "classes": "0", - "methods": "0", - "m_over_c": "0", - "loc_over_m": "0" - }, { - "name": "Configuration", - "files": "19", - "lines": "417", - "loc": "111", - "classes": "1", - "methods": "0", - "m_over_c": "0", - "loc_over_m": "0" - }, { - "name": "Model Tests", - "files": "2", - "lines": "5", - "loc": "4", - "classes": "2", - "methods": "0", - "m_over_c": "0", - "loc_over_m": "0" - }, { - "name": "Spec Support", - "files": "1", - "lines": "1", - "loc": "1", - "classes": "0", - "methods": "0", - "m_over_c": "0", - "loc_over_m": "0" - }, { - "name": "Test Support", - "files": "1", - "lines": "1", - "loc": "1", - "classes": "0", - "methods": "0", - "m_over_c": "0", - "loc_over_m": "0" - }, { - "name": "Code", - "files": "36", - "lines": "485", - "loc": "152", - "classes": "10", - "methods": "1", - "m_over_c": "0", - "loc_over_m": "150", - "code_to_test_ratio": "0.0", - "total": true - }, { - "name": "Tests", - "files": "4", - "lines": "7", - "loc": "6", - "classes": "2", - "methods": "0", - "m_over_c": "0", - "loc_over_m": "0", - "code_to_test_ratio": "0.0", - "total": true - }, { - "name": "Total", - "files": "40", - "lines": "492", - "loc": "158", - "classes": "12", - "methods": "1", - "m_over_c": "0", - "loc_over_m": "156", - "code_to_test_ratio": "0.0", - "total": true}, - {"schema_stats": - {"schema_path": - "#{Dir.pwd}/test/dummy/db/schema.rb", - "create_table calls count": 2}}, - {"polymorphic_stats": {"polymorphic_models_count": 1} - } - ] - EOS + def code_stat(name, files:, lines:, loc:, classes:, methods: "0", m_over_c: "0", loc_over_m: "0") + {"name"=>name, "files"=>files, "lines"=>lines, "loc"=>loc, + "classes"=>classes, "methods"=>methods, "m_over_c"=>m_over_c, "loc_over_m"=>loc_over_m} + end + + def total_stat(name, files:, lines:, loc:, classes:, methods: "0", m_over_c: "0", loc_over_m: "0", code_to_test_ratio: "0.0") + code_stat(name, files: files, lines: lines, loc: loc, classes: classes, + methods: methods, m_over_c: m_over_c, loc_over_m: loc_over_m) + .merge("code_to_test_ratio" => code_to_test_ratio, "total" => true) + end + + EXPECTED_GEM_NAMES = %w[ + simplecov-console codecov rails_stats simplecov minitest-around + bundler byebug minitest minitest-spec-context + ].freeze it "outputs useful stats for a Rails project" do root_directory = File.absolute_path("./test/dummy") calculator = RailsStats::StatsCalculator.new(root_directory) formatter = RailsStats::JSONFormatter.new(calculator) - - expectation = JSON.parse(JSON_STRING) result = formatter.result - [expectation, result].each do |data| - data.each do |hash| - next unless hash["gems"] - - hash["gems"].each do |gem| - next unless gem["transitive_dependencies"] + # Verify bundler-stats structure (without asserting on version-specific values) + gems_section = result.find { |h| h["gems"] } + assert gems_section, "Expected a gems section in the result" + assert gems_section["summary"], "Expected a summary in the gems section" + %w[declared unpinned total github].each do |key| + assert gems_section["summary"].key?(key), "Expected summary to have '#{key}'" + end - gem["transitive_dependencies"].map! do |dep| - name, constraints = dep.split(/[()]/) - next dep unless constraints + gem_names = gems_section["gems"].map { |g| g["name"] } + assert_equal EXPECTED_GEM_NAMES.sort, gem_names.sort - normalized_constraints = constraints.split(/,\s*/).sort.join(', ') - "#{name}(#{normalized_constraints})" - end.sort! - end + gems_section["gems"].each do |gem| + %w[name total_dependencies first_level_dependencies top_level_dependencies transitive_dependencies].each do |key| + assert gem.key?(key), "Expected gem '#{gem["name"]}' to have '#{key}'" end end - assert_equal expectation, result + # Verify code stats (these are stable across Ruby versions) + expected_code_stats = [ + code_stat("Mailers", files: "1", lines: "4", loc: "4", classes: "1"), + code_stat("Models", files: "4", lines: "10", loc: "10", classes: "4"), + code_stat("Controllers", files: "1", lines: "7", loc: "6", classes: "1", methods: "1", m_over_c: "1", loc_over_m: "4"), + code_stat("Helpers", files: "1", lines: "3", loc: "3", classes: "0"), + code_stat("Jobs", files: "1", lines: "7", loc: "2", classes: "1"), + code_stat("Channels", files: "2", lines: "8", loc: "8", classes: "2"), + code_stat("Javascripts", files: "6", lines: "28", loc: "7", classes: "0"), + code_stat("Libraries", files: "1", lines: "1", loc: "1", classes: "0"), + code_stat("Configuration", files: "19", lines: "417", loc: "111", classes: "1"), + code_stat("Model Tests", files: "2", lines: "5", loc: "4", classes: "2"), + code_stat("Spec Support", files: "1", lines: "1", loc: "1", classes: "0"), + code_stat("Test Support", files: "1", lines: "1", loc: "1", classes: "0"), + ] + + code_stats = result.select { |h| h["name"] && !h["total"] } + assert_equal expected_code_stats.sort_by { |h| h["name"] }, code_stats.sort_by { |h| h["name"] } + + # Verify totals + expected_totals = [ + total_stat("Code", files: "36", lines: "485", loc: "152", classes: "10", methods: "1", loc_over_m: "150"), + total_stat("Tests", files: "4", lines: "7", loc: "6", classes: "2"), + total_stat("Total", files: "40", lines: "492", loc: "158", classes: "12", methods: "1", loc_over_m: "156"), + ] + + totals = result.select { |h| h["total"] } + assert_equal expected_totals.sort_by { |h| h["name"] }, totals.sort_by { |h| h["name"] } + + # Verify schema and polymorphic stats + schema_stats = result.find { |h| h["schema_stats"] } + assert schema_stats, "Expected schema_stats in result" + assert_equal "#{Dir.pwd}/test/dummy/db/schema.rb", schema_stats["schema_stats"]["schema_path"] + assert_equal 2, schema_stats["schema_stats"]["create_table calls count"] + + polymorphic_stats = result.find { |h| h["polymorphic_stats"] } + assert polymorphic_stats, "Expected polymorphic_stats in result" + assert_equal 1, polymorphic_stats["polymorphic_stats"]["polymorphic_models_count"] end end end From d91b1be6fa8935b463e37464499cb19aa2e5dd2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20V=C3=A1squez?= Date: Tue, 31 Mar 2026 00:54:47 -0600 Subject: [PATCH 2/4] Remove stale bundler-stats table from console output fixture The gem dependency table was never compared in tests (it was stripped before assertion) and contained outdated values. The fixture now only contains the code stats table that is actually asserted on. --- test/fixtures/console-output.txt | 19 ------------------- test/lib/rails_stats/code_statistics_test.rb | 6 +++++- 2 files changed, 5 insertions(+), 20 deletions(-) diff --git a/test/fixtures/console-output.txt b/test/fixtures/console-output.txt index ffdca26..7c07a1f 100644 --- a/test/fixtures/console-output.txt +++ b/test/fixtures/console-output.txt @@ -1,22 +1,3 @@ -+-----------------------|------------|----------------+ -| Name | Total Deps | 1st Level Deps | -+-----------------------|------------|----------------+ -| simplecov-console | 8 | 3 | -| codecov | 4 | 1 | -| rails_stats | 4 | 2 | -| simplecov | 3 | 3 | -| minitest-around | 1 | 1 | -| bundler | 0 | 0 | -| byebug | 0 | 0 | -| minitest | 0 | 0 | -| minitest-spec-context | 0 | 0 | -+-----------------------|------------|----------------+ - - Declared Gems 9 - Total Gems 18 - Unpinned Versions 6 - Github Refs 0 - +----------------------+---------+---------+---------+---------+---------+-----+-------+ | Name | Files | Lines | LOC | Classes | Methods | M/C | LOC/M | +----------------------+---------+---------+---------+---------+---------+-----+-------+ diff --git a/test/lib/rails_stats/code_statistics_test.rb b/test/lib/rails_stats/code_statistics_test.rb index 2af4d7c..22e20d2 100644 --- a/test/lib/rails_stats/code_statistics_test.rb +++ b/test/lib/rails_stats/code_statistics_test.rb @@ -14,9 +14,13 @@ RailsStats::CodeStatistics.new(root_directory).to_s end + # Compare only the code stats portion of the output, skipping the + # bundler-stats gem table which varies across Ruby versions. + code_stats_output = out[out.index("+----------------------+")..-1] + assert_equal( table.lines.map(&:rstrip).join, - out.lines.map(&:rstrip).join + code_stats_output.lines.map(&:rstrip).join ) end end From 97d977c627fa6f9da51f1a55f87e8e5a8f3a7775 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20V=C3=A1squez?= Date: Tue, 31 Mar 2026 13:46:49 -0600 Subject: [PATCH 3/4] Use assert_includes to avoid coupling test to bundler output format --- test/lib/rails_stats/code_statistics_test.rb | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/test/lib/rails_stats/code_statistics_test.rb b/test/lib/rails_stats/code_statistics_test.rb index 22e20d2..0dce910 100644 --- a/test/lib/rails_stats/code_statistics_test.rb +++ b/test/lib/rails_stats/code_statistics_test.rb @@ -14,13 +14,9 @@ RailsStats::CodeStatistics.new(root_directory).to_s end - # Compare only the code stats portion of the output, skipping the - # bundler-stats gem table which varies across Ruby versions. - code_stats_output = out[out.index("+----------------------+")..-1] - - assert_equal( - table.lines.map(&:rstrip).join, - code_stats_output.lines.map(&:rstrip).join + assert_includes( + out.lines.map(&:rstrip).join, + table.lines.map(&:rstrip).join ) end end From 0a79edbda45625c8ba43e92f8809af7e5d1efbd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20V=C3=A1squez?= Date: Tue, 31 Mar 2026 13:57:20 -0600 Subject: [PATCH 4/4] From code review --- test/lib/rails_stats/code_statistics_test.rb | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/test/lib/rails_stats/code_statistics_test.rb b/test/lib/rails_stats/code_statistics_test.rb index 0dce910..a2769a4 100644 --- a/test/lib/rails_stats/code_statistics_test.rb +++ b/test/lib/rails_stats/code_statistics_test.rb @@ -8,16 +8,13 @@ describe "#to_s" do it "outputs useful stats for a Rails project" do root_directory = File.expand_path('../../../test/dummy', File.dirname(__FILE__)) - table = File.read(File.expand_path('../../../fixtures/console-output.txt', __FILE__)) + expected_table = File.read(File.expand_path('../../../fixtures/console-output.txt', __FILE__)) - out, err = capture_io do - RailsStats::CodeStatistics.new(root_directory).to_s - end + expected_bundler_table, _ = capture_io { Bundler::Stats::CLI.start } - assert_includes( - out.lines.map(&:rstrip).join, - table.lines.map(&:rstrip).join - ) + output, _ = capture_io { RailsStats::CodeStatistics.new(root_directory).to_s } + + assert_equal([expected_bundler_table, expected_table].join, output) end end end