tryouts 3.1.2 → 3.2.1
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 +4 -4
- data/lib/tryouts/cli/formatters/base.rb +109 -47
- data/lib/tryouts/cli/formatters/compact.rb +98 -107
- data/lib/tryouts/cli/formatters/factory.rb +8 -2
- data/lib/tryouts/cli/formatters/live_status_manager.rb +138 -0
- data/lib/tryouts/cli/formatters/output_manager.rb +78 -66
- data/lib/tryouts/cli/formatters/quiet.rb +53 -101
- data/lib/tryouts/cli/formatters/test_run_state.rb +122 -0
- data/lib/tryouts/cli/formatters/tty_status_display.rb +273 -0
- data/lib/tryouts/cli/formatters/verbose.rb +99 -103
- data/lib/tryouts/cli/formatters.rb +3 -0
- data/lib/tryouts/cli/opts.rb +17 -8
- data/lib/tryouts/cli/tty_detector.rb +92 -0
- data/lib/tryouts/failure_collector.rb +109 -0
- data/lib/tryouts/test_batch.rb +9 -5
- data/lib/tryouts/test_runner.rb +11 -0
- data/lib/tryouts/version.rb +1 -1
- metadata +78 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 95b7078a356f605ba8df53a52afcb2209d260c473787692c4b70c0d88e8c5620
|
4
|
+
data.tar.gz: f6d47e78b51ee13d754c03525329a1857a44767324abc6237a4896f81688ecc2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e49a47fd56ba73dc0a80deeb0d3559558b4dbefcc31904c21358a906e7f542d6a8caa7479b8d95e66af6e52f7ea4760d3df76e7e0b310f037f3a369adc9f3cbf
|
7
|
+
data.tar.gz: 8de293b074a3599af4c543b05b3d49d58508b154eb4cce396e55027343042fb5289020cbdd8277d03abf667303ec935d59d3ece8f3e50ec308e150b9541dbda8
|
@@ -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
|
14
|
-
|
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
|
19
|
-
|
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
|
23
|
-
|
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
|
27
|
-
|
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
|
31
|
-
|
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
|
35
|
-
|
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
|
40
|
-
|
45
|
+
def test_start(test_case:, index:, total:)
|
46
|
+
# Default: no output
|
41
47
|
end
|
42
48
|
|
43
|
-
def test_end(test_case
|
44
|
-
|
49
|
+
def test_end(test_case:, index:, total:)
|
50
|
+
# Default: no output
|
45
51
|
end
|
46
52
|
|
47
|
-
def test_result(result_packet
|
48
|
-
|
53
|
+
def test_result(result_packet)
|
54
|
+
# Default: no output
|
49
55
|
end
|
50
56
|
|
51
|
-
def test_output(test_case
|
52
|
-
|
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
|
57
|
-
|
62
|
+
def setup_start(line_range:)
|
63
|
+
# Default: no output
|
58
64
|
end
|
59
65
|
|
60
|
-
def setup_output(output_text
|
61
|
-
|
66
|
+
def setup_output(output_text)
|
67
|
+
# Default: no output
|
62
68
|
end
|
63
69
|
|
64
|
-
def teardown_start(line_range
|
65
|
-
|
70
|
+
def teardown_start(line_range:)
|
71
|
+
# Default: no output
|
66
72
|
end
|
67
73
|
|
68
|
-
def teardown_output(output_text
|
69
|
-
|
74
|
+
def teardown_output(output_text)
|
75
|
+
# Default: no output
|
70
76
|
end
|
71
77
|
|
72
78
|
# Summary operations
|
73
|
-
def batch_summary(
|
74
|
-
|
79
|
+
def batch_summary(failure_collector)
|
80
|
+
# Default: no output
|
75
81
|
end
|
76
82
|
|
77
|
-
def grand_total(total_tests
|
78
|
-
|
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
|
83
|
-
|
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
|
87
|
-
|
96
|
+
def error_message(message, backtrace: nil)
|
97
|
+
# Default: no output
|
88
98
|
end
|
89
99
|
|
90
|
-
|
91
|
-
|
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
|
-
#
|
95
|
-
def
|
96
|
-
|
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
|
100
|
-
|
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
|
141
|
+
indent = ' ' * level
|
106
142
|
"#{indent}#{text}"
|
107
143
|
end
|
108
144
|
|
109
145
|
def with_indent(level)
|
110
|
-
old_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
|
-
|
11
|
-
@
|
12
|
-
@
|
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
|
17
|
+
def phase_header(message, file_count: nil)
|
17
18
|
# Show processing header but skip execution phase headers to avoid empty lines
|
18
|
-
|
19
|
-
when 0
|
19
|
+
if message.include?('PROCESSING')
|
20
20
|
# Main processing header
|
21
21
|
text = file_count ? "#{message}" : "#{message}..."
|
22
|
-
|
23
|
-
|
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
|
-
|
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,
|
34
|
-
#
|
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
|
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
|
-
|
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
|
46
|
+
def file_execution_start(file_path, test_count:, context_mode:)
|
54
47
|
pretty_path = Console.pretty_path(file_path)
|
55
|
-
|
48
|
+
@stderr.puts "#{pretty_path}: #{test_count} tests"
|
56
49
|
end
|
57
50
|
|
58
|
-
# Summary operations
|
59
|
-
def batch_summary(
|
60
|
-
|
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
|
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 =
|
89
|
-
|
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
|
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
|
-
|
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
|
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
|
116
|
-
desc
|
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
|
-
|
125
|
+
puts indent_text("#{status} #{desc}", 1)
|
122
126
|
when :failed
|
123
127
|
status = Console.color(:red, '✗')
|
124
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
147
|
+
puts indent_text("#{status} #{desc}", 1)
|
144
148
|
else
|
145
149
|
status = '?'
|
146
|
-
|
150
|
+
puts indent_text("#{status} #{desc}", 1)
|
147
151
|
end
|
148
152
|
end
|
149
153
|
|
150
|
-
def test_output(
|
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
|
-
|
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
|
-
|
163
|
+
puts " #{line.chomp}"
|
159
164
|
end
|
160
165
|
else
|
161
|
-
|
162
|
-
|
163
|
-
|
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(
|
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
|
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
|
-
|
183
|
+
@stderr.puts " Setup output (#{lines} lines)"
|
179
184
|
end
|
180
185
|
|
181
|
-
def teardown_start(
|
186
|
+
def teardown_start(line_range:)
|
182
187
|
return unless @show_debug
|
183
188
|
|
184
|
-
|
189
|
+
@stderr.puts ' Teardown...'
|
185
190
|
end
|
186
191
|
|
187
|
-
def teardown_output(output_text
|
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
|
-
|
198
|
+
@stderr.puts " Teardown output (#{lines} lines)"
|
194
199
|
end
|
195
200
|
|
196
|
-
def grand_total(total_tests
|
197
|
-
|
198
|
-
|
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
|
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
|
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
|
-
|
214
|
-
|
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
|
223
|
+
def debug_info(message, level: 0)
|
219
224
|
return unless @show_debug
|
220
225
|
|
221
|
-
|
226
|
+
@stderr.puts indent_text("DEBUG: #{message}", level)
|
222
227
|
end
|
223
228
|
|
224
|
-
def trace_info(message, level
|
229
|
+
def trace_info(message, level: 0)
|
225
230
|
return unless @show_trace
|
226
231
|
|
227
|
-
|
232
|
+
@stderr.puts indent_text("TRACE: #{message}", level)
|
228
233
|
end
|
229
234
|
|
230
|
-
def error_message(message, backtrace
|
231
|
-
|
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
|
-
|
241
|
+
@stderr.puts indent_text(line.chomp, 1)
|
237
242
|
end
|
238
243
|
end
|
239
244
|
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
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
|
-
|
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
|