simplecov-mcp 1.0.0 → 2.0.0

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 (164) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +98 -50
  3. data/docs/{ARCHITECTURE.md → dev/ARCHITECTURE.md} +11 -10
  4. data/docs/dev/BRANCH_ONLY_COVERAGE.md +158 -0
  5. data/docs/{DEVELOPMENT.md → dev/DEVELOPMENT.md} +2 -1
  6. data/docs/dev/README.md +10 -0
  7. data/docs/dev/RELEASING.md +146 -0
  8. data/docs/{arch-decisions → dev/arch-decisions}/001-x-arch-decision.md +3 -1
  9. data/docs/{arch-decisions → dev/arch-decisions}/002-x-arch-decision.md +7 -5
  10. data/docs/{arch-decisions → dev/arch-decisions}/003-x-arch-decision.md +2 -0
  11. data/docs/{arch-decisions → dev/arch-decisions}/004-x-arch-decision.md +6 -2
  12. data/docs/{arch-decisions → dev/arch-decisions}/005-x-arch-decision.md +4 -2
  13. data/docs/{arch-decisions → dev/arch-decisions}/README.md +3 -3
  14. data/docs/{presentations → dev/presentations}/simplecov-mcp-presentation.md +28 -22
  15. data/docs/fixtures/demo_project/README.md +9 -0
  16. data/docs/{ADVANCED_USAGE.md → user/ADVANCED_USAGE.md} +129 -319
  17. data/docs/user/CLI_FALLBACK_FOR_LLMS.md +34 -0
  18. data/docs/user/CLI_USAGE.md +750 -0
  19. data/docs/{ERROR_HANDLING.md → user/ERROR_HANDLING.md} +12 -12
  20. data/docs/user/EXAMPLES.md +588 -0
  21. data/docs/user/INSTALLATION.md +130 -0
  22. data/docs/{LIBRARY_API.md → user/LIBRARY_API.md} +90 -32
  23. data/docs/{MCP_INTEGRATION.md → user/MCP_INTEGRATION.md} +36 -34
  24. data/docs/user/README.md +14 -0
  25. data/docs/{TROUBLESHOOTING.md → user/TROUBLESHOOTING.md} +21 -100
  26. data/docs/user/V2-BREAKING-CHANGES.md +472 -0
  27. data/exe/simplecov-mcp +1 -1
  28. data/lib/simplecov_mcp/{cli_config.rb → app_config.rb} +12 -12
  29. data/lib/simplecov_mcp/app_context.rb +1 -1
  30. data/lib/simplecov_mcp/base_tool.rb +66 -38
  31. data/lib/simplecov_mcp/cli.rb +67 -123
  32. data/lib/simplecov_mcp/commands/base_command.rb +16 -27
  33. data/lib/simplecov_mcp/commands/command_factory.rb +8 -2
  34. data/lib/simplecov_mcp/commands/detailed_command.rb +16 -2
  35. data/lib/simplecov_mcp/commands/list_command.rb +1 -1
  36. data/lib/simplecov_mcp/commands/raw_command.rb +18 -2
  37. data/lib/simplecov_mcp/commands/summary_command.rb +20 -3
  38. data/lib/simplecov_mcp/commands/totals_command.rb +53 -0
  39. data/lib/simplecov_mcp/commands/uncovered_command.rb +24 -5
  40. data/lib/simplecov_mcp/commands/validate_command.rb +60 -0
  41. data/lib/simplecov_mcp/commands/version_command.rb +19 -4
  42. data/lib/simplecov_mcp/config_parser.rb +32 -0
  43. data/lib/simplecov_mcp/constants.rb +3 -3
  44. data/lib/simplecov_mcp/coverage_reporter.rb +31 -0
  45. data/lib/simplecov_mcp/error_handler.rb +81 -40
  46. data/lib/simplecov_mcp/error_handler_factory.rb +2 -2
  47. data/lib/simplecov_mcp/errors.rb +32 -20
  48. data/lib/simplecov_mcp/formatters/source_formatter.rb +23 -19
  49. data/lib/simplecov_mcp/formatters.rb +51 -0
  50. data/lib/simplecov_mcp/mcp_server.rb +9 -7
  51. data/lib/simplecov_mcp/mode_detector.rb +6 -5
  52. data/lib/simplecov_mcp/model.rb +122 -83
  53. data/lib/simplecov_mcp/option_normalizers.rb +39 -18
  54. data/lib/simplecov_mcp/option_parser_builder.rb +82 -65
  55. data/lib/simplecov_mcp/option_parsers/env_options_parser.rb +3 -5
  56. data/lib/simplecov_mcp/option_parsers/error_helper.rb +18 -17
  57. data/lib/simplecov_mcp/path_relativizer.rb +17 -14
  58. data/lib/simplecov_mcp/predicate_evaluator.rb +72 -0
  59. data/lib/simplecov_mcp/presenters/base_coverage_presenter.rb +1 -3
  60. data/lib/simplecov_mcp/presenters/coverage_detailed_presenter.rb +1 -3
  61. data/lib/simplecov_mcp/presenters/coverage_raw_presenter.rb +1 -3
  62. data/lib/simplecov_mcp/presenters/coverage_summary_presenter.rb +1 -3
  63. data/lib/simplecov_mcp/presenters/coverage_uncovered_presenter.rb +1 -3
  64. data/lib/simplecov_mcp/presenters/project_coverage_presenter.rb +1 -3
  65. data/lib/simplecov_mcp/presenters/project_totals_presenter.rb +27 -0
  66. data/lib/simplecov_mcp/resolvers/coverage_line_resolver.rb +14 -18
  67. data/lib/simplecov_mcp/resolvers/resultset_path_resolver.rb +7 -9
  68. data/lib/simplecov_mcp/resultset_loader.rb +20 -25
  69. data/lib/simplecov_mcp/staleness_checker.rb +50 -46
  70. data/lib/simplecov_mcp/table_formatter.rb +64 -0
  71. data/lib/simplecov_mcp/tools/all_files_coverage_tool.rb +20 -50
  72. data/lib/simplecov_mcp/tools/coverage_detailed_tool.rb +13 -7
  73. data/lib/simplecov_mcp/tools/coverage_raw_tool.rb +12 -7
  74. data/lib/simplecov_mcp/tools/coverage_summary_tool.rb +13 -8
  75. data/lib/simplecov_mcp/tools/coverage_table_tool.rb +20 -60
  76. data/lib/simplecov_mcp/tools/coverage_totals_tool.rb +44 -0
  77. data/lib/simplecov_mcp/tools/help_tool.rb +38 -66
  78. data/lib/simplecov_mcp/tools/uncovered_lines_tool.rb +13 -8
  79. data/lib/simplecov_mcp/tools/validate_tool.rb +72 -0
  80. data/lib/simplecov_mcp/tools/version_tool.rb +7 -14
  81. data/lib/simplecov_mcp/util.rb +18 -12
  82. data/lib/simplecov_mcp/version.rb +1 -1
  83. data/lib/simplecov_mcp.rb +23 -29
  84. data/spec/all_files_coverage_tool_spec.rb +4 -3
  85. data/spec/{cli_config_spec.rb → app_config_spec.rb} +31 -26
  86. data/spec/base_tool_spec.rb +17 -14
  87. data/spec/cli/show_default_report_spec.rb +2 -2
  88. data/spec/cli_enumerated_options_spec.rb +31 -9
  89. data/spec/cli_error_spec.rb +46 -23
  90. data/spec/cli_format_spec.rb +123 -0
  91. data/spec/cli_json_options_spec.rb +50 -0
  92. data/spec/cli_source_spec.rb +11 -63
  93. data/spec/cli_spec.rb +82 -97
  94. data/spec/cli_usage_spec.rb +15 -15
  95. data/spec/commands/base_command_spec.rb +12 -92
  96. data/spec/commands/command_factory_spec.rb +7 -3
  97. data/spec/commands/detailed_command_spec.rb +10 -24
  98. data/spec/commands/list_command_spec.rb +28 -0
  99. data/spec/commands/raw_command_spec.rb +43 -20
  100. data/spec/commands/summary_command_spec.rb +10 -23
  101. data/spec/commands/totals_command_spec.rb +34 -0
  102. data/spec/commands/uncovered_command_spec.rb +29 -23
  103. data/spec/commands/validate_command_spec.rb +213 -0
  104. data/spec/commands/version_command_spec.rb +38 -0
  105. data/spec/constants_spec.rb +3 -3
  106. data/spec/coverage_reporter_spec.rb +102 -0
  107. data/spec/coverage_table_tool_spec.rb +21 -10
  108. data/spec/coverage_totals_tool_spec.rb +37 -0
  109. data/spec/error_handler_spec.rb +120 -4
  110. data/spec/error_mode_spec.rb +18 -22
  111. data/spec/errors_edge_cases_spec.rb +101 -28
  112. data/spec/errors_stale_spec.rb +34 -0
  113. data/spec/file_based_mcp_tools_spec.rb +6 -6
  114. data/spec/fixtures/project1/lib/bar.rb +2 -0
  115. data/spec/fixtures/project1/lib/foo.rb +2 -0
  116. data/spec/help_tool_spec.rb +2 -18
  117. data/spec/integration_spec.rb +114 -170
  118. data/spec/logging_fallback_spec.rb +3 -3
  119. data/spec/mcp_server_integration_spec.rb +1 -1
  120. data/spec/mcp_server_spec.rb +70 -53
  121. data/spec/mode_detector_spec.rb +46 -41
  122. data/spec/model_error_handling_spec.rb +141 -82
  123. data/spec/model_staleness_spec.rb +13 -13
  124. data/spec/option_normalizers_spec.rb +111 -112
  125. data/spec/option_parsers/env_options_parser_spec.rb +25 -37
  126. data/spec/option_parsers/error_helper_spec.rb +56 -56
  127. data/spec/path_relativizer_spec.rb +15 -0
  128. data/spec/presenters/coverage_detailed_presenter_spec.rb +1 -1
  129. data/spec/presenters/coverage_summary_presenter_spec.rb +1 -1
  130. data/spec/presenters/coverage_uncovered_presenter_spec.rb +1 -1
  131. data/spec/presenters/project_coverage_presenter_spec.rb +9 -8
  132. data/spec/presenters/project_totals_presenter_spec.rb +144 -0
  133. data/spec/resolvers/coverage_line_resolver_spec.rb +261 -36
  134. data/spec/resolvers/resultset_path_resolver_spec.rb +13 -8
  135. data/spec/shared_examples/file_based_mcp_tools.rb +23 -18
  136. data/spec/shared_examples/formatted_command_examples.rb +64 -0
  137. data/spec/simple_cov_mcp_module_spec.rb +24 -3
  138. data/spec/simplecov_mcp/formatters/source_formatter_spec.rb +267 -0
  139. data/spec/simplecov_mcp/formatters_spec.rb +76 -0
  140. data/spec/simplecov_mcp/presenters/base_coverage_presenter_spec.rb +79 -0
  141. data/spec/simplecov_mcp_model_spec.rb +99 -49
  142. data/spec/simplecov_mcp_opts_spec.rb +42 -39
  143. data/spec/spec_helper.rb +27 -92
  144. data/spec/staleness_checker_spec.rb +10 -9
  145. data/spec/staleness_more_spec.rb +4 -4
  146. data/spec/support/cli_helpers.rb +22 -0
  147. data/spec/support/control_flow_helpers.rb +20 -0
  148. data/spec/support/fake_mcp.rb +40 -0
  149. data/spec/support/io_helpers.rb +29 -0
  150. data/spec/support/mcp_helpers.rb +35 -0
  151. data/spec/support/mcp_runner.rb +10 -8
  152. data/spec/support/mocking_helpers.rb +30 -0
  153. data/spec/table_format_spec.rb +70 -0
  154. data/spec/tools/validate_tool_spec.rb +132 -0
  155. data/spec/tools_error_handling_spec.rb +34 -48
  156. data/spec/util_spec.rb +5 -4
  157. data/spec/version_spec.rb +7 -7
  158. data/spec/version_tool_spec.rb +20 -22
  159. metadata +90 -23
  160. data/docs/BRANCH_ONLY_COVERAGE.md +0 -81
  161. data/docs/CLI_USAGE.md +0 -637
  162. data/docs/EXAMPLES.md +0 -430
  163. data/docs/INSTALLATION.md +0 -352
  164. data/spec/cli_success_predicate_spec.rb +0 -141
@@ -14,12 +14,8 @@ RSpec.describe SimpleCovMcp::Commands::BaseCommand do
14
14
  end
15
15
 
16
16
  # Expose protected methods for testing
17
- def public_handle_with_path(args, name, &block)
18
- handle_with_path(args, name, &block)
19
- end
20
-
21
- def public_fetch_raw(model, path)
22
- fetch_raw(model, path)
17
+ def public_handle_with_path(args, name, &)
18
+ handle_with_path(args, name, &)
23
19
  end
24
20
  end
25
21
  end
@@ -33,8 +29,8 @@ RSpec.describe SimpleCovMcp::Commands::BaseCommand do
33
29
 
34
30
  # Stub the block to raise Errno::ENOENT
35
31
  expect do
36
- test_command.public_handle_with_path(args, 'test') do |path|
37
- raise Errno::ENOENT.new('No such file or directory')
32
+ test_command.public_handle_with_path(args, 'test') do |_path|
33
+ raise Errno::ENOENT, 'No such file or directory'
38
34
  end
39
35
  end.to raise_error(SimpleCovMcp::FileNotFoundError, 'File not found: lib/missing.rb')
40
36
  end
@@ -43,8 +39,8 @@ RSpec.describe SimpleCovMcp::Commands::BaseCommand do
43
39
  args = ['some/other/path.rb']
44
40
 
45
41
  expect do
46
- test_command.public_handle_with_path(args, 'test') do |path|
47
- raise Errno::ENOENT.new('No such file or directory')
42
+ test_command.public_handle_with_path(args, 'test') do |_path|
43
+ raise Errno::ENOENT, 'No such file or directory'
48
44
  end
49
45
  end.to raise_error(SimpleCovMcp::FileNotFoundError, /some\/other\/path\.rb/)
50
46
  end
@@ -56,8 +52,8 @@ RSpec.describe SimpleCovMcp::Commands::BaseCommand do
56
52
 
57
53
  # Stub the block to raise Errno::EACCES
58
54
  expect do
59
- test_command.public_handle_with_path(args, 'test') do |path|
60
- raise Errno::EACCES.new('Permission denied')
55
+ test_command.public_handle_with_path(args, 'test') do |_path|
56
+ raise Errno::EACCES, 'Permission denied'
61
57
  end
62
58
  end.to raise_error(SimpleCovMcp::FilePermissionError, 'Permission denied: lib/secret.rb')
63
59
  end
@@ -66,8 +62,8 @@ RSpec.describe SimpleCovMcp::Commands::BaseCommand do
66
62
  args = ['/root/protected.rb']
67
63
 
68
64
  expect do
69
- test_command.public_handle_with_path(args, 'test') do |path|
70
- raise Errno::EACCES.new('Permission denied')
65
+ test_command.public_handle_with_path(args, 'test') do |_path|
66
+ raise Errno::EACCES, 'Permission denied'
71
67
  end
72
68
  end.to raise_error(SimpleCovMcp::FilePermissionError, /\/root\/protected\.rb/)
73
69
  end
@@ -78,7 +74,7 @@ RSpec.describe SimpleCovMcp::Commands::BaseCommand do
78
74
  args = []
79
75
 
80
76
  expect do
81
- test_command.public_handle_with_path(args, 'summary') do |path|
77
+ test_command.public_handle_with_path(args, 'summary') do |_path|
82
78
  # Should not reach here
83
79
  end
84
80
  end.to raise_error(SimpleCovMcp::UsageError, /summary <path>/)
@@ -100,7 +96,7 @@ RSpec.describe SimpleCovMcp::Commands::BaseCommand do
100
96
  it 'shifts the path from args' do
101
97
  args = ['lib/foo.rb', 'extra', 'args']
102
98
 
103
- test_command.public_handle_with_path(args, 'test') do |path|
99
+ test_command.public_handle_with_path(args, 'test') do |_path|
104
100
  # Block execution
105
101
  end
106
102
 
@@ -108,80 +104,4 @@ RSpec.describe SimpleCovMcp::Commands::BaseCommand do
108
104
  end
109
105
  end
110
106
  end
111
-
112
- describe '#fetch_raw' do
113
- let(:model) { SimpleCovMcp::CoverageModel.new(root: root, resultset: 'coverage') }
114
-
115
- context 'when model.raw_for raises an exception' do
116
- it 'returns nil instead of propagating the error' do
117
- # Stub model.raw_for to raise an exception
118
- allow(model).to receive(:raw_for).and_raise(StandardError, 'Something went wrong')
119
-
120
- result = test_command.public_fetch_raw(model, 'lib/nonexistent.rb')
121
-
122
- expect(result).to be_nil
123
- end
124
-
125
- it 'handles RuntimeError' do
126
- allow(model).to receive(:raw_for).and_raise(RuntimeError, 'Runtime error')
127
-
128
- result = test_command.public_fetch_raw(model, 'lib/foo.rb')
129
-
130
- expect(result).to be_nil
131
- end
132
-
133
- it 'handles ArgumentError' do
134
- allow(model).to receive(:raw_for).and_raise(ArgumentError, 'Invalid argument')
135
-
136
- result = test_command.public_fetch_raw(model, 'lib/foo.rb')
137
-
138
- expect(result).to be_nil
139
- end
140
- end
141
-
142
- context 'when successful' do
143
- it 'returns the raw coverage data' do
144
- result = test_command.public_fetch_raw(model, 'lib/foo.rb')
145
-
146
- expect(result).to be_a(Hash)
147
- expect(result).to have_key('lines')
148
- expect(result['lines']).to be_an(Array)
149
- end
150
-
151
- it 'caches the result for subsequent calls' do
152
- # First call should hit the model
153
- expect(model).to receive(:raw_for).with('lib/foo.rb').once.and_call_original
154
-
155
- result1 = test_command.public_fetch_raw(model, 'lib/foo.rb')
156
- result2 = test_command.public_fetch_raw(model, 'lib/foo.rb')
157
-
158
- expect(result1).to eq(result2)
159
- end
160
-
161
- it 'caches different paths separately' do
162
- result1 = test_command.public_fetch_raw(model, 'lib/foo.rb')
163
- result2 = test_command.public_fetch_raw(model, 'lib/bar.rb')
164
-
165
- expect(result1).not_to eq(result2)
166
- end
167
-
168
- it 'does not cache nil results from exceptions' do
169
- # Set up the stub to raise an error
170
- call_count = 0
171
- allow(model).to receive(:raw_for).with('lib/missing.rb') do
172
- call_count += 1
173
- raise StandardError, 'File not found'
174
- end
175
-
176
- result1 = test_command.public_fetch_raw(model, 'lib/missing.rb')
177
- result2 = test_command.public_fetch_raw(model, 'lib/missing.rb')
178
-
179
- expect(result1).to be_nil
180
- expect(result2).to be_nil
181
- # Note: Due to current implementation, nil results are NOT cached,
182
- # so raw_for is called each time an exception occurs
183
- expect(call_count).to eq(2)
184
- end
185
- end
186
- end
187
107
  end
@@ -13,7 +13,9 @@ RSpec.describe SimpleCovMcp::Commands::CommandFactory do
13
13
  ['summary', SimpleCovMcp::Commands::SummaryCommand],
14
14
  ['raw', SimpleCovMcp::Commands::RawCommand],
15
15
  ['uncovered', SimpleCovMcp::Commands::UncoveredCommand],
16
- ['detailed', SimpleCovMcp::Commands::DetailedCommand]
16
+ ['detailed', SimpleCovMcp::Commands::DetailedCommand],
17
+ ['totals', SimpleCovMcp::Commands::TotalsCommand],
18
+ ['total', SimpleCovMcp::Commands::TotalsCommand] # Alias
17
19
  ].each do |command_name, command_class|
18
20
  it "creates a #{command_class.name.split('::').last} for \"#{command_name}\"" do
19
21
  command = described_class.create(command_name, cli_context)
@@ -27,7 +29,7 @@ RSpec.describe SimpleCovMcp::Commands::CommandFactory do
27
29
  [
28
30
  'invalid_cmd',
29
31
  'invalid command',
30
- /list | summary <path> | raw <path> | uncovered <path> | detailed <path> | version/
32
+ /list \| summary <path> \| raw <path> \| uncovered <path>/
31
33
  ],
32
34
  [nil, 'nil command', nil],
33
35
  ['', 'empty string command', nil],
@@ -47,7 +49,7 @@ RSpec.describe SimpleCovMcp::Commands::CommandFactory do
47
49
  commands = described_class.available_commands
48
50
  expect(commands).to be_an(Array)
49
51
  expect(commands).to contain_exactly('list', 'version', 'summary', 'raw', 'uncovered',
50
- 'detailed')
52
+ 'detailed', 'totals', 'total', 'validate')
51
53
  end
52
54
 
53
55
  it 'returns the keys from COMMAND_MAP' do
@@ -67,6 +69,8 @@ RSpec.describe SimpleCovMcp::Commands::CommandFactory do
67
69
  expect(described_class::COMMAND_MAP['raw']).to eq(SimpleCovMcp::Commands::RawCommand)
68
70
  expect(described_class::COMMAND_MAP['uncovered']).to eq(SimpleCovMcp::Commands::UncoveredCommand)
69
71
  expect(described_class::COMMAND_MAP['detailed']).to eq(SimpleCovMcp::Commands::DetailedCommand)
72
+ expect(described_class::COMMAND_MAP['totals']).to eq(SimpleCovMcp::Commands::TotalsCommand)
73
+ expect(described_class::COMMAND_MAP['total']).to eq(SimpleCovMcp::Commands::TotalsCommand)
70
74
  end
71
75
  end
72
76
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'spec_helper'
4
+ require_relative '../shared_examples/formatted_command_examples'
4
5
 
5
6
  RSpec.describe SimpleCovMcp::Commands::DetailedCommand do
6
7
  let(:root) { (FIXTURES_DIR / 'project1').to_s }
@@ -10,39 +11,24 @@ RSpec.describe SimpleCovMcp::Commands::DetailedCommand do
10
11
  before do
11
12
  cli_context.config.root = root
12
13
  cli_context.config.resultset = 'coverage'
13
- cli_context.config.json = false
14
+ cli_context.config.format = :table
14
15
  cli_context.config.source_mode = nil
15
16
  end
16
17
 
17
18
  describe '#execute' do
18
- it 'prints the detailed coverage table' do
19
- output = nil
19
+ context 'with table format' do
20
+ it 'prints the detailed coverage table' do
21
+ output = capture_command_output(command, ['lib/foo.rb'])
20
22
 
21
- silence_output do |stdout, _stderr|
22
- command.execute(['lib/foo.rb'])
23
- output = stdout.string
23
+ expect(output).to include('File: lib/foo.rb', 'Line', 'Covered')
24
24
  end
25
-
26
- expect(output).to include('File: lib/foo.rb')
27
- expect(output).to include('Line')
28
- expect(output).to include('Covered')
29
25
  end
30
26
 
31
- it 'emits JSON when requested, including stale metadata' do
32
- cli_context.config.json = true
33
- stub_staleness_check('L')
34
-
35
- json_output = nil
36
- silence_output do |stdout, _stderr|
37
- command.execute(['lib/foo.rb'])
38
- json_output = stdout.string
39
- end
27
+ context 'with stale data' do
28
+ before { stub_staleness_check('L') }
40
29
 
41
- payload = JSON.parse(json_output)
42
- expect(payload['file']).to eq('lib/foo.rb')
43
- expect(payload['lines']).to be_an(Array)
44
- expect(payload['summary']).to include('covered' => 2, 'total' => 3, 'pct' => 66.67)
45
- expect(payload['stale']).to eq('L')
30
+ it_behaves_like 'a command with formatted output', ['lib/foo.rb'],
31
+ { 'file' => 'lib/foo.rb', 'lines' => nil, 'summary' => nil, 'stale' => 'L' }
46
32
  end
47
33
  end
48
34
  end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require_relative '../shared_examples/formatted_command_examples'
5
+
6
+ RSpec.describe SimpleCovMcp::Commands::ListCommand do
7
+ let(:root) { (FIXTURES_DIR / 'project1').to_s }
8
+ let(:cli_context) { SimpleCovMcp::CoverageCLI.new }
9
+ let(:command) { described_class.new(cli_context) }
10
+
11
+ before do
12
+ cli_context.config.root = root
13
+ cli_context.config.resultset = 'coverage'
14
+ cli_context.config.format = :table
15
+ end
16
+
17
+ describe '#execute' do
18
+ context 'with table format' do
19
+ it 'outputs a formatted table' do
20
+ output = capture_command_output(command, [])
21
+
22
+ expect(output).to include('│', 'lib/foo.rb', 'lib/bar.rb')
23
+ end
24
+ end
25
+
26
+ it_behaves_like 'a command with formatted output', [], ['files', 'counts']
27
+ end
28
+ end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'spec_helper'
4
+ require_relative '../shared_examples/formatted_command_examples'
4
5
 
5
6
  RSpec.describe SimpleCovMcp::Commands::RawCommand do
6
7
  let(:root) { (FIXTURES_DIR / 'project1').to_s }
@@ -10,37 +11,59 @@ RSpec.describe SimpleCovMcp::Commands::RawCommand do
10
11
  before do
11
12
  cli_context.config.root = root
12
13
  cli_context.config.resultset = 'coverage'
13
- cli_context.config.json = false
14
+ cli_context.config.format = :table
14
15
  end
15
16
 
16
17
  describe '#execute' do
17
- it 'prints the raw coverage lines for the requested file' do
18
- output = nil
18
+ context 'with table format' do
19
+ it 'prints the raw coverage lines for the requested file' do
20
+ output = capture_command_output(command, ['lib/foo.rb'])
19
21
 
20
- silence_output do |stdout, _stderr|
21
- command.execute(['lib/foo.rb'])
22
- output = stdout.string
22
+ expect(output).to include('│', 'lib/foo.rb', 'Line', 'Coverage')
23
23
  end
24
+ end
25
+
26
+ context 'when the file is fully covered' do
27
+ it 'still prints the raw table' do
28
+ mock_presenter(
29
+ SimpleCovMcp::Presenters::CoverageRawPresenter,
30
+ absolute_payload: {
31
+ 'file' => 'lib/perfect.rb',
32
+ 'lines' => [1, 1, 1],
33
+ 'stale' => false
34
+ },
35
+ relative_path: 'lib/perfect.rb'
36
+ )
24
37
 
25
- expect(output).to include('File: lib/foo.rb')
26
- # Example match: "[1, 0, nil, 2]"
27
- expect(output).to match(/\[1,\s0,\snil,\s2\]/)
38
+ output = capture_command_output(command, ['lib/perfect.rb'])
39
+
40
+ expect(output).to include('│', '│ 1 │ 1 │')
41
+ expect(output).not_to include('All lines covered!')
42
+ end
28
43
  end
29
44
 
30
- it 'emits JSON when requested, including stale metadata' do
31
- cli_context.config.json = true
32
- stub_staleness_check('L')
45
+ context 'with JSON output' do
46
+ before { cli_context.config.format = :json }
33
47
 
34
- json_output = nil
35
- silence_output do |stdout, _stderr|
36
- command.execute(['lib/foo.rb'])
37
- json_output = stdout.string
48
+ it 'emits JSON with specific line data' do
49
+ stub_staleness_check('L') # Needed for stale data
50
+
51
+ output = capture_command_output(command, ['lib/foo.rb'])
52
+
53
+ payload = JSON.parse(output)
54
+ expect(payload['file']).to eq('lib/foo.rb')
55
+ expect(payload['lines']).to be_an(Array)
56
+ expect(payload['lines'][0]).to eq(1) # specific value
57
+ expect(payload['lines'][1]).to eq(0) # specific value
58
+ expect(payload['stale']).to eq('L')
38
59
  end
60
+ end
61
+
62
+ context 'with stale data (other formats)' do
63
+ before { stub_staleness_check('L') }
39
64
 
40
- payload = JSON.parse(json_output)
41
- expect(payload['file']).to eq('lib/foo.rb')
42
- expect(payload['lines']).to eq([1, 0, nil, 2])
43
- expect(payload['stale']).to eq('L')
65
+ # Use an array for expected_json_keys as we don't need exact value matching for these generic format tests
66
+ it_behaves_like 'a command with formatted output', ['lib/foo.rb'], ['file', 'lines', 'stale']
44
67
  end
45
68
  end
46
69
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'spec_helper'
4
+ require_relative '../shared_examples/formatted_command_examples'
4
5
 
5
6
  RSpec.describe SimpleCovMcp::Commands::SummaryCommand do
6
7
  let(:root) { (FIXTURES_DIR / 'project1').to_s }
@@ -10,38 +11,24 @@ RSpec.describe SimpleCovMcp::Commands::SummaryCommand do
10
11
  before do
11
12
  cli_context.config.root = root
12
13
  cli_context.config.resultset = 'coverage'
13
- cli_context.config.json = false
14
+ cli_context.config.format = :table
14
15
  cli_context.config.source_mode = nil
15
16
  end
16
17
 
17
18
  describe '#execute' do
18
- it 'prints a coverage summary line with a relative path' do
19
- output = nil
19
+ context 'with table format' do
20
+ it 'prints a coverage summary line with a relative path' do
21
+ output = capture_command_output(command, ['lib/foo.rb'])
20
22
 
21
- silence_output do |stdout, _stderr|
22
- command.execute(['lib/foo.rb'])
23
- output = stdout.string
23
+ expect(output).to include('│', '66.67%', 'lib/foo.rb')
24
24
  end
25
-
26
- # Example match: " 66.67% 2/3 lib/foo.rb"
27
- expect(output).to match(/66\.67%.*2\/3.*lib\/foo\.rb/)
28
25
  end
29
26
 
30
- it 'emits JSON when requested, including stale metadata' do
31
- cli_context.config.json = true
32
- stub_staleness_check('L')
33
-
34
- json_output = nil
35
- silence_output do |stdout, _stderr|
36
- command.execute(['lib/foo.rb'])
37
- json_output = stdout.string
38
- end
27
+ context 'with stale data' do
28
+ before { stub_staleness_check('L') }
39
29
 
40
- payload = JSON.parse(json_output)
41
- expect(payload['file']).to eq('lib/foo.rb')
42
- expect(payload['summary']).to include('covered' => 2, 'total' => 3, 'pct' => 66.67)
43
- expect(payload).to have_key('stale')
44
- expect(payload['stale']).to eq('L')
30
+ it_behaves_like 'a command with formatted output', ['lib/foo.rb'],
31
+ { 'file' => 'lib/foo.rb', 'summary' => nil, 'stale' => 'L' }
45
32
  end
46
33
  end
47
34
  end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require_relative '../shared_examples/formatted_command_examples'
5
+
6
+ RSpec.describe SimpleCovMcp::Commands::TotalsCommand do
7
+ let(:root) { (FIXTURES_DIR / 'project1').to_s }
8
+ let(:cli_context) { SimpleCovMcp::CoverageCLI.new }
9
+ let(:command) { described_class.new(cli_context) }
10
+
11
+ before do
12
+ cli_context.config.root = root
13
+ cli_context.config.resultset = 'coverage'
14
+ cli_context.config.format = :table
15
+ end
16
+
17
+ describe '#execute' do
18
+ context 'with table format' do
19
+ it 'prints aggregated totals for the project' do
20
+ output = capture_command_output(command, [])
21
+
22
+ expect(output).to include('│', 'Lines', '50.00%')
23
+ end
24
+ end
25
+
26
+ it_behaves_like 'a command with formatted output', [], ['lines', 'files', 'percentage']
27
+
28
+ it 'raises when unexpected arguments are provided' do
29
+ expect do
30
+ command.execute(['extra'])
31
+ end.to raise_error(SimpleCovMcp::UsageError, include('totals'))
32
+ end
33
+ end
34
+ end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'spec_helper'
4
+ require_relative '../shared_examples/formatted_command_examples'
4
5
 
5
6
  RSpec.describe SimpleCovMcp::Commands::UncoveredCommand do
6
7
  let(:root) { (FIXTURES_DIR / 'project1').to_s }
@@ -10,40 +11,45 @@ RSpec.describe SimpleCovMcp::Commands::UncoveredCommand do
10
11
  before do
11
12
  cli_context.config.root = root
12
13
  cli_context.config.resultset = 'coverage'
13
- cli_context.config.json = false
14
+ cli_context.config.format = :table
14
15
  cli_context.config.source_mode = nil
15
16
  end
16
17
 
17
18
  describe '#execute' do
18
- it 'prints uncovered line numbers with the summary' do
19
- output = nil
19
+ context 'with table format' do
20
+ it 'prints uncovered line numbers with the summary' do
21
+ output = capture_command_output(command, ['lib/bar.rb'])
20
22
 
21
- silence_output do |stdout, _stderr|
22
- command.execute(['lib/foo.rb'])
23
- output = stdout.string
23
+ expect(output).to include('│', 'lib/bar.rb', '33.33%')
24
24
  end
25
-
26
- expect(output).to include('File: lib/foo.rb')
27
- expect(output).to include('Uncovered lines: 2')
28
- # Example match: "Summary: 66.67% 2/3"
29
- expect(output).to match(/Summary:\s+66\.67%.*2\/3/)
30
25
  end
31
26
 
32
- it 'emits JSON when requested, including stale metadata' do
33
- cli_context.config.json = true
34
- stub_staleness_check('L')
27
+ context 'when the file is fully covered' do
28
+ before do
29
+ mock_presenter(
30
+ SimpleCovMcp::Presenters::CoverageUncoveredPresenter,
31
+ absolute_payload: {
32
+ 'file' => 'lib/perfect.rb',
33
+ 'uncovered' => [],
34
+ 'summary' => { 'covered' => 10, 'total' => 10, 'percentage' => 100.0 }
35
+ },
36
+ relative_path: 'lib/perfect.rb'
37
+ )
38
+ end
39
+
40
+ it 'prints a success message instead of a table' do
41
+ output = capture_command_output(command, ['lib/perfect.rb'])
35
42
 
36
- json_output = nil
37
- silence_output do |stdout, _stderr|
38
- command.execute(['lib/foo.rb'])
39
- json_output = stdout.string
43
+ expect(output).to include('All lines covered!', '100.00%')
44
+ expect(output).not_to include('│')
40
45
  end
46
+ end
47
+
48
+ context 'with stale data' do
49
+ before { stub_staleness_check('L') }
41
50
 
42
- payload = JSON.parse(json_output)
43
- expect(payload['file']).to eq('lib/foo.rb')
44
- expect(payload['uncovered']).to eq([2])
45
- expect(payload['summary']).to include('covered' => 2, 'total' => 3, 'pct' => 66.67)
46
- expect(payload['stale']).to eq('L')
51
+ it_behaves_like 'a command with formatted output', ['lib/foo.rb'],
52
+ { 'file' => 'lib/foo.rb', 'uncovered' => [2], 'summary' => nil, 'stale' => 'L' }
47
53
  end
48
54
  end
49
55
  end