tryouts 3.1.2 → 3.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d2dea4f111f12c35d204df54313a62045bd9e8841710b19323454e5a626ac4fd
4
- data.tar.gz: 275b234c7ce6ca2debd28e9d861053bc041e825443683078fee710de2b45e0cf
3
+ metadata.gz: f66a1e1e61b8c2d5e7201db55fe494954a8ec626bcb333bfc46d0842d469d897
4
+ data.tar.gz: a6fd7107a2934b4011a7093a1f28807fc5c07e67dfbcfdf266881978b1093294
5
5
  SHA512:
6
- metadata.gz: 6c704d75d9bed36875b843b6014508df36292247e9c03bbc68892f1d7318db0cfd8ff9907c9c45d71d45441e9512dd5550e4c47b84a9075285ccf8980474c4b9
7
- data.tar.gz: 0230e782d3a214bb92e647d2beb38e3b3ce1d3bbc129f6628ca14b0b7cef1430fee94ee7746bda55e78a8236da5df1fbf7b6270a8f0a186646f5a01662d0d667
6
+ metadata.gz: 8846a206ebdebcbb816f5e9af65c7054f1719b9f511912dbf4b311811557a3124f18d026424e83a7a31150b8de6525c395e19748e45d0297f356465561ed30bb
7
+ data.tar.gz: f2affd5b231a61f9e5e5f4469c9297718c15da7637d849aad221249d1f83345b75cc17ab8098a1ea9ec9e173990f940c450ef31e3b008be84aa99b3ac9d86af4
@@ -1,118 +1,180 @@
1
1
  # lib/tryouts/cli/formatters/base.rb
2
2
 
3
- require_relative 'factory'
4
3
  require_relative 'output_manager'
5
4
 
6
5
  class Tryouts
7
6
  class CLI
8
7
  # Enhanced interface for all test output formatting
9
8
  module FormatterInterface
10
- attr_reader :current_indent
9
+ attr_reader :stdout, :stderr, :current_indent
10
+
11
+ def initialize(options = {})
12
+ @stdout = options.fetch(:stdout, $stdout)
13
+ @stderr = options.fetch(:stderr, $stderr)
14
+ @current_indent = 0
15
+ @options = options
16
+ end
11
17
 
12
18
  # Phase-level output (major sections)
13
- def phase_header(message, file_count = nil, level = 0, io = $stdout)
14
- raise NotImplementedError, "#{self.class} must implement #phase_header"
19
+ def phase_header(message, file_count: nil)
20
+ # Default: no output
15
21
  end
16
22
 
17
23
  # File-level operations
18
- def file_start(file_path, context_info = {}, io = $stdout)
19
- raise NotImplementedError, "#{self.class} must implement #file_start"
24
+ def file_start(file_path, context_info: {})
25
+ # Default: no output
20
26
  end
21
27
 
22
- def file_end(file_path, context_info = {}, io = $stdout)
23
- raise NotImplementedError, "#{self.class} must implement #file_end"
28
+ def file_end(file_path, context_info: {})
29
+ # Default: no output
24
30
  end
25
31
 
26
- def file_parsed(file_path, test_count, io = $stdout, setup_present: false, teardown_present: false)
27
- raise NotImplementedError, "#{self.class} must implement #file_parsed"
32
+ def file_parsed(file_path, test_count:, setup_present: false, teardown_present: false)
33
+ # Default: no output
28
34
  end
29
35
 
30
- def file_execution_start(file_path, test_count, context_mode, io = $stdout)
31
- raise NotImplementedError, "#{self.class} must implement #file_execution_start"
36
+ def file_execution_start(file_path, test_count:, context_mode:)
37
+ # Default: no output
32
38
  end
33
39
 
34
- def file_result(file_path, total_tests, failed_count, error_count, elapsed_time, io = $stdout)
35
- raise NotImplementedError, "#{self.class} must implement #file_result"
40
+ def file_result(file_path, total_tests:, failed_count:, error_count:, elapsed_time: nil)
41
+ # Default: no output
36
42
  end
37
43
 
38
44
  # Test-level operations
39
- def test_start(test_case, index, total, io = $stdout)
40
- raise NotImplementedError, "#{self.class} must implement #test_start"
45
+ def test_start(test_case:, index:, total:)
46
+ # Default: no output
41
47
  end
42
48
 
43
- def test_end(test_case, index, total, io = $stdout)
44
- raise NotImplementedError, "#{self.class} must implement #test_end"
49
+ def test_end(test_case:, index:, total:)
50
+ # Default: no output
45
51
  end
46
52
 
47
- def test_result(result_packet, io = $stdout)
48
- raise NotImplementedError, "#{self.class} must implement #test_result"
53
+ def test_result(result_packet)
54
+ # Default: no output
49
55
  end
50
56
 
51
- def test_output(test_case, output_text, io = $stdout)
52
- raise NotImplementedError, "#{self.class} must implement #test_output"
57
+ def test_output(test_case:, output_text:, result_packet:)
58
+ # Default: no output
53
59
  end
54
60
 
55
61
  # Setup/teardown operations
56
- def setup_start(line_range, io = $stdout)
57
- raise NotImplementedError, "#{self.class} must implement #setup_start"
62
+ def setup_start(line_range:)
63
+ # Default: no output
58
64
  end
59
65
 
60
- def setup_output(output_text, io = $stdout)
61
- raise NotImplementedError, "#{self.class} must implement #setup_output"
66
+ def setup_output(output_text)
67
+ # Default: no output
62
68
  end
63
69
 
64
- def teardown_start(line_range, io = $stdout)
65
- raise NotImplementedError, "#{self.class} must implement #teardown_start"
70
+ def teardown_start(line_range:)
71
+ # Default: no output
66
72
  end
67
73
 
68
- def teardown_output(output_text, io = $stdout)
69
- raise NotImplementedError, "#{self.class} must implement #teardown_output"
74
+ def teardown_output(output_text)
75
+ # Default: no output
70
76
  end
71
77
 
72
78
  # Summary operations
73
- def batch_summary(total_tests, failed_count, elapsed_time, io = $stdout)
74
- raise NotImplementedError, "#{self.class} must implement #batch_summary"
79
+ def batch_summary(failure_collector)
80
+ # Default: no output
75
81
  end
76
82
 
77
- def grand_total(total_tests, failed_count, error_count, successful_files, total_files, elapsed_time, io = $stdout)
78
- raise NotImplementedError, "#{self.class} must implement #grand_total"
83
+ def grand_total(total_tests:, failed_count:, error_count:, successful_files:, total_files:, elapsed_time:)
84
+ # Default: no output
79
85
  end
80
86
 
81
87
  # Debug and diagnostic output
82
- def debug_info(message, level = 0, io = $stdout)
83
- raise NotImplementedError, "#{self.class} must implement #debug_info"
88
+ def debug_info(message, level: 0)
89
+ # Default: no output
90
+ end
91
+
92
+ def trace_info(message, level: 0)
93
+ # Default: no output
84
94
  end
85
95
 
86
- def trace_info(message, level = 0, io = $stdout)
87
- raise NotImplementedError, "#{self.class} must implement #trace_info"
96
+ def error_message(message, backtrace: nil)
97
+ # Default: no output
88
98
  end
89
99
 
90
- def error_message(message, details = nil, io = $stdout)
91
- raise NotImplementedError, "#{self.class} must implement #error_message"
100
+ # Live status capability negotiation
101
+ def live_status_capabilities
102
+ {
103
+ supports_coordination: false, # Can work with coordinated output
104
+ output_frequency: :medium, # :low, :medium, :high
105
+ requires_tty: false, # Must have TTY to function
106
+ }
92
107
  end
93
108
 
94
- # Utility methods
95
- def raw_output(text, io = $stdout)
96
- raise NotImplementedError, "#{self.class} must implement #raw_output"
109
+ # Live status integration (optional methods)
110
+ def set_live_status_manager(manager)
111
+ @live_status_manager = manager
97
112
  end
98
113
 
99
- def separator(style = :light, io = $stdout)
100
- raise NotImplementedError, "#{self.class} must implement #separator"
114
+ def live_status_manager
115
+ @live_status_manager
101
116
  end
102
117
 
118
+ # Standard output methods that coordinate with live status automatically
119
+ def write(text)
120
+ if @live_status_manager&.enabled?
121
+ @live_status_manager.write_string(text)
122
+ else
123
+ @stdout.print(text)
124
+ end
125
+ end
126
+
127
+ def puts(text = '')
128
+ write("#{text}\n")
129
+ end
130
+
131
+ # Optional: formatters can implement this to provide custom live status updates
132
+ def update_live_status(state_updates = {})
133
+ @live_status_manager&.update_status(state_updates)
134
+ end
135
+
136
+ protected
137
+
138
+ # Utility methods for formatters to use
103
139
  def indent_text(text, level = nil)
104
140
  level ||= current_indent || 0
105
- indent = ' ' * level
141
+ indent = ' ' * level
106
142
  "#{indent}#{text}"
107
143
  end
108
144
 
109
145
  def with_indent(level)
110
- old_indent = @current_indent
146
+ old_indent = @current_indent
111
147
  @current_indent = level
112
148
  yield
113
149
  ensure
114
150
  @current_indent = old_indent
115
151
  end
152
+
153
+ def separator(style = :light)
154
+ width = @options.fetch(:line_width, 70)
155
+ case style
156
+ when :heavy
157
+ '=' * width
158
+ when :light
159
+ '-' * width
160
+ when :dotted
161
+ '.' * width
162
+ else
163
+ '-' * width
164
+ end
165
+ end
166
+
167
+ def format_timing(elapsed_time)
168
+ return '' unless elapsed_time
169
+
170
+ if elapsed_time < 0.001
171
+ " (#{(elapsed_time * 1_000_000).round}μs)"
172
+ elsif elapsed_time < 1
173
+ " (#{(elapsed_time * 1000).round}ms)"
174
+ else
175
+ " (#{elapsed_time.round(2)}s)"
176
+ end
177
+ end
116
178
  end
117
179
  end
118
180
  end
@@ -7,38 +7,31 @@ class Tryouts
7
7
  include FormatterInterface
8
8
 
9
9
  def initialize(options = {})
10
- @show_debug = options.fetch(:debug, false)
11
- @show_trace = options.fetch(:trace, false)
12
- @show_passed = options.fetch(:show_passed, true)
10
+ super
11
+ @show_debug = options.fetch(:debug, false)
12
+ @show_trace = options.fetch(:trace, false)
13
+ @show_passed = options.fetch(:show_passed, true)
13
14
  end
14
15
 
15
16
  # Phase-level output - minimal for compact mode
16
- def phase_header(message, file_count = nil, level = 0, io = $stderr)
17
+ def phase_header(message, file_count: nil)
17
18
  # Show processing header but skip execution phase headers to avoid empty lines
18
- case level
19
- when 0
19
+ if message.include?('PROCESSING')
20
20
  # Main processing header
21
21
  text = file_count ? "#{message}" : "#{message}..."
22
- io.puts text
23
- when 1
24
- # Skip execution phase headers - they create unwanted empty lines
25
- nil
26
- else
22
+ @stderr.puts text
23
+ elsif !message.include?('EXECUTING')
27
24
  # Other phase headers with minimal formatting
28
- io.puts indent_text(message, level - 1)
25
+ @stderr.puts message
29
26
  end
30
27
  end
31
28
 
32
29
  # File-level operations - compact single lines
33
- def file_start(file_path, _context_info = {}, io = $stderr)
34
- # See file_execution_start
35
- end
36
-
37
- def file_end(file_path, context_info = {}, io = $stderr)
38
- # No output in compact mode
30
+ def file_start(file_path, context_info: {})
31
+ # Output handled in file_execution_start for compact mode
39
32
  end
40
33
 
41
- def file_parsed(_file_path, test_count, io = $stderr, setup_present: false, teardown_present: false)
34
+ def file_parsed(_file_path, test_count:, setup_present: false, teardown_present: false)
42
35
  # Don't show parsing info in compact mode unless debug
43
36
  return unless @show_debug
44
37
 
@@ -47,25 +40,46 @@ class Tryouts
47
40
  extras << 'teardown' if teardown_present
48
41
  suffix = extras.empty? ? '' : " +#{extras.join(',')}"
49
42
 
50
- io.puts indent_text("Parsed #{test_count} tests#{suffix}", 1)
43
+ @stderr.puts indent_text("Parsed #{test_count} tests#{suffix}", 1)
51
44
  end
52
45
 
53
- def file_execution_start(file_path, test_count, _context_mode, io = $stderr)
46
+ def file_execution_start(file_path, test_count:, context_mode:)
54
47
  pretty_path = Console.pretty_path(file_path)
55
- io.puts "#{pretty_path}: #{test_count} tests"
48
+ @stderr.puts "#{pretty_path}: #{test_count} tests"
56
49
  end
57
50
 
58
- # Summary operations
59
- def batch_summary(total_tests, failed_count, elapsed_time)
60
- # Skip - file_result already shows this information with better alignment
51
+ # Summary operations - show failure summary
52
+ def batch_summary(failure_collector)
53
+ return unless failure_collector.any_failures?
54
+
55
+ puts
56
+ puts separator
57
+ puts Console.color(:red, 'Failed Tests:')
58
+ puts
59
+
60
+ failure_collector.failures_by_file.each do |file_path, failures|
61
+ failures.each do |failure|
62
+ pretty_path = Console.pretty_path(file_path)
63
+
64
+ # Include line number with file path for easy copying/clicking
65
+ location = if failure.line_number > 0
66
+ "#{pretty_path}:#{failure.line_number}"
67
+ else
68
+ pretty_path
69
+ end
70
+
71
+ puts " #{location}"
72
+ puts " #{Console.color(:red, '✗')} #{failure.description}"
73
+ puts " #{failure.failure_reason}"
74
+ puts
75
+ end
76
+ end
61
77
  end
62
78
 
63
- def file_result(_file_path, total_tests, failed_count, error_count, elapsed_time, io = $stdout)
79
+ def file_result(_file_path, total_tests:, failed_count:, error_count:, elapsed_time: nil)
64
80
  issues_count = failed_count + error_count
65
81
  passed_count = total_tests - issues_count
66
- details = [
67
- # "#{passed_count} passed",
68
- ]
82
+ details = []
69
83
 
70
84
  if issues_count > 0
71
85
  status = Console.color(:red, '✗')
@@ -76,57 +90,47 @@ class Tryouts
76
90
  end
77
91
 
78
92
  if error_count > 0
79
- status = Console.color(:yellow, '⚠') if error_count == 0
80
93
  details << "#{error_count} errors"
81
94
  end
82
95
 
83
96
  if failed_count > 0
84
- status = Console.color(:yellow, '⚠') if failed_count == 0
85
97
  details << "#{failed_count} failed"
86
98
  end
87
99
 
88
- time_str = if elapsed_time
89
- format_timing(elapsed_time)
90
- else
91
- ''
92
- end
93
- io.puts " #{status} #{details.join(', ')}#{time_str}"
100
+ time_str = format_timing(elapsed_time)
101
+ puts " #{status} #{details.join(', ')}#{time_str}"
94
102
  end
95
103
 
96
104
  # Test-level operations - only show in debug mode for compact
97
- def test_start(test_case, index, _total, io = $stdout)
105
+ def test_start(test_case:, index:, total:)
98
106
  return unless @show_debug
99
107
 
100
108
  desc = test_case.description.to_s
101
109
  desc = "test #{index}" if desc.empty?
102
110
 
103
- io.puts " Running: #{desc}"
104
- end
105
-
106
- def test_end(test_case, index, _total, io = $stdout)
107
- # No output for test end
111
+ puts " Running: #{desc}"
108
112
  end
109
113
 
110
- def test_result(result_packet, io = $stdout)
114
+ def test_result(result_packet)
111
115
  # Only show failed tests in compact mode unless show_passed is true
112
116
  return if result_packet.passed? && !@show_passed
113
117
 
114
118
  test_case = result_packet.test_case
115
- desc = test_case.description.to_s
116
- desc = 'unnamed test' if desc.empty?
119
+ desc = test_case.description.to_s
120
+ desc = 'unnamed test' if desc.empty?
117
121
 
118
122
  case result_packet.status
119
123
  when :passed
120
124
  status = Console.color(:green, '✓')
121
- io.puts indent_text("#{status} #{desc}", 1)
125
+ puts indent_text("#{status} #{desc}", 1)
122
126
  when :failed
123
127
  status = Console.color(:red, '✗')
124
- io.puts indent_text("#{status} #{desc}", 1)
128
+ puts indent_text("#{status} #{desc}", 1)
125
129
 
126
130
  # Show minimal context for failures
127
131
  if result_packet.actual_results.any?
128
132
  failure_info = "got: #{result_packet.first_actual.inspect}"
129
- io.puts indent_text(" #{failure_info}", 1)
133
+ puts indent_text(" #{failure_info}", 1)
130
134
  end
131
135
 
132
136
  # Show 1-2 lines of test context if available
@@ -134,137 +138,116 @@ class Tryouts
134
138
  test_case.source_lines.each do |line|
135
139
  next if line.strip.empty? || line.strip.start_with?('#')
136
140
 
137
- io.puts indent_text(" #{line.strip}", 1)
141
+ puts indent_text(" #{line.strip}", 1)
138
142
  break # Only show first relevant line
139
143
  end
140
144
  end
141
145
  when :skipped
142
146
  status = Console.color(:yellow, '-')
143
- io.puts indent_text("#{status} #{desc}", 1)
147
+ puts indent_text("#{status} #{desc}", 1)
144
148
  else
145
149
  status = '?'
146
- io.puts indent_text("#{status} #{desc}", 1)
150
+ puts indent_text("#{status} #{desc}", 1)
147
151
  end
148
152
  end
149
153
 
150
- def test_output(_test_case, output_text, io = $stdout)
154
+ def test_output(test_case:, output_text:, result_packet:)
151
155
  # In compact mode, only show output for failed tests and only if debug mode is enabled
152
156
  return if output_text.nil? || output_text.strip.empty?
153
157
  return unless @show_debug
158
+ return if result_packet.passed?
154
159
 
155
- io.puts " Output: #{output_text.lines.count} lines"
160
+ puts " Output: #{output_text.lines.count} lines"
156
161
  if output_text.lines.count <= 3
157
162
  output_text.lines.each do |line|
158
- io.puts " #{line.chomp}"
163
+ puts " #{line.chomp}"
159
164
  end
160
165
  else
161
- io.puts " #{output_text.lines.first.chomp}"
162
- io.puts " ... (#{output_text.lines.count - 2} more lines)"
163
- io.puts " #{output_text.lines.last.chomp}"
166
+ puts " #{output_text.lines.first.chomp}"
167
+ puts " ... (#{output_text.lines.count - 2} more lines)"
168
+ puts " #{output_text.lines.last.chomp}"
164
169
  end
165
170
  end
166
171
 
167
172
  # Setup/teardown operations - minimal output
168
- def setup_start(_line_range)
173
+ def setup_start(line_range:)
169
174
  # No file setup start output for compact
170
175
  end
171
176
 
172
- def setup_output(output_text, io = $stderr)
177
+ def setup_output(output_text)
173
178
  return if output_text.strip.empty?
174
179
  return unless @show_debug
175
180
 
176
181
  # In compact mode, just show that there was output
177
182
  lines = output_text.lines.count
178
- io.puts " Setup output (#{lines} lines)"
183
+ @stderr.puts " Setup output (#{lines} lines)"
179
184
  end
180
185
 
181
- def teardown_start(_line_range, io = $stderr)
186
+ def teardown_start(line_range:)
182
187
  return unless @show_debug
183
188
 
184
- io.puts ' Teardown...'
189
+ @stderr.puts ' Teardown...'
185
190
  end
186
191
 
187
- def teardown_output(output_text, io = $stderr)
192
+ def teardown_output(output_text)
188
193
  return if output_text.strip.empty?
189
194
  return unless @show_debug
190
195
 
191
196
  # In compact mode, just show that there was output
192
197
  lines = output_text.lines.count
193
- io.puts " Teardown output (#{lines} lines)"
198
+ @stderr.puts " Teardown output (#{lines} lines)"
194
199
  end
195
200
 
196
- def grand_total(total_tests, failed_count, error_count, successful_files, total_files, elapsed_time, io = $stderr)
197
- io.puts
198
- io.puts '=' * 50
201
+ def grand_total(total_tests:, failed_count:, error_count:, successful_files:, total_files:, elapsed_time:)
202
+ @stderr.puts
203
+ @stderr.puts '=' * 50
199
204
 
200
205
  issues_count = failed_count + error_count
201
206
  if issues_count > 0
202
- passed = [total_tests - issues_count, 0].max # Ensure passed never goes negative
207
+ passed = [total_tests - issues_count, 0].max # Ensure passed never goes negative
203
208
  details = []
204
209
  details << "#{failed_count} failed" if failed_count > 0
205
210
  details << "#{error_count} errors" if error_count > 0
206
- result = Console.color(:red, "#{details.join(', ')}, #{passed} passed")
211
+ result = Console.color(:red, "#{details.join(', ')}, #{passed} passed")
207
212
  else
208
213
  result = Console.color(:green, "#{total_tests} tests passed")
209
214
  end
210
215
 
211
216
  time_str = format_timing(elapsed_time)
212
217
 
213
- io.puts "Total: #{result} #{time_str}"
214
- io.puts "Files: #{successful_files} of #{total_files} successful"
218
+ @stderr.puts "Total: #{result}#{time_str}"
219
+ @stderr.puts "Files: #{successful_files} of #{total_files} successful"
215
220
  end
216
221
 
217
222
  # Debug and diagnostic output - minimal in compact mode
218
- def debug_info(message, level = 0, io = $stderr)
223
+ def debug_info(message, level: 0)
219
224
  return unless @show_debug
220
225
 
221
- io.puts indent_text("DEBUG: #{message}", level)
226
+ @stderr.puts indent_text("DEBUG: #{message}", level)
222
227
  end
223
228
 
224
- def trace_info(message, level = 0, io = $stderr)
229
+ def trace_info(message, level: 0)
225
230
  return unless @show_trace
226
231
 
227
- io.puts indent_text("TRACE: #{message}", level)
232
+ @stderr.puts indent_text("TRACE: #{message}", level)
228
233
  end
229
234
 
230
- def error_message(message, backtrace = nil, io = $stderr)
231
- io.puts Console.color(:red, "ERROR: #{message}")
235
+ def error_message(message, backtrace: nil)
236
+ @stderr.puts Console.color(:red, "ERROR: #{message}")
232
237
 
233
238
  return unless backtrace && @show_debug
234
239
 
235
240
  backtrace.first(3).each do |line|
236
- io.puts indent_text(line.chomp, 1)
241
+ @stderr.puts indent_text(line.chomp, 1)
237
242
  end
238
243
  end
239
244
 
240
- # Utility methods
241
- def raw_output(text, io = $stdout)
242
- io.puts text
243
- end
244
-
245
- def separator(style = :light, io = $stdout)
246
- case style
247
- when :heavy
248
- io.puts '=' * 50
249
- when :light
250
- io.puts '-' * 50
251
- when :dotted
252
- io.puts '.' * 50
253
- else
254
- io.puts '-' * 50
255
- end
256
- end
257
-
258
- private
259
-
260
- def format_timing(elapsed_time)
261
- if elapsed_time < 0.001
262
- " (#{(elapsed_time * 1_000_000).round}μs)"
263
- elsif elapsed_time < 1
264
- " (#{(elapsed_time * 1000).round}ms)"
265
- else
266
- " (#{elapsed_time.round(2)}s)"
267
- end
245
+ def live_status_capabilities
246
+ {
247
+ supports_coordination: true, # Compact can work with coordinated output
248
+ output_frequency: :medium, # Outputs at medium frequency
249
+ requires_tty: false, # Works without TTY
250
+ }
268
251
  end
269
252
  end
270
253
 
@@ -280,6 +263,14 @@ class Tryouts
280
263
 
281
264
  super
282
265
  end
266
+
267
+ def live_status_capabilities
268
+ {
269
+ supports_coordination: true, # Compact can work with coordinated output
270
+ output_frequency: :low, # Outputs infrequently, mainly summaries
271
+ requires_tty: false, # Works without TTY
272
+ }
273
+ end
283
274
  end
284
275
  end
285
276
  end
@@ -1,19 +1,22 @@
1
1
  # lib/tryouts/cli/formatters/factory.rb
2
2
 
3
+ require_relative '../tty_detector'
4
+
3
5
  class Tryouts
4
6
  class CLI
5
7
  # Factory for creating formatters and output managers
6
8
  class FormatterFactory
7
9
  def self.create_output_manager(options = {})
8
10
  formatter = create_formatter(options)
9
- OutputManager.new(formatter)
11
+ OutputManager.new(formatter, options)
10
12
  end
11
13
 
12
14
  def self.create_formatter(options = {})
13
15
  # Map boolean flags to format symbols if format not explicitly set
14
16
  format = options[:format]&.to_sym || determine_format_from_flags(options)
15
17
 
16
- case format
18
+ # Create base formatter first
19
+ base_formatter = case format
17
20
  when :verbose
18
21
  if options[:fails_only]
19
22
  VerboseFailsFormatter.new(options)
@@ -35,6 +38,9 @@ class Tryouts
35
38
  else
36
39
  CompactFormatter.new(options) # Default to compact
37
40
  end
41
+
42
+ # Return base formatter - live status is now handled by OutputManager/LiveStatusManager
43
+ base_formatter
38
44
  end
39
45
 
40
46
  class << self