tng 0.2.3 → 0.2.4
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/README.md +29 -2
- data/Rakefile +41 -7
- data/bin/tng +155 -51
- data/lib/tng/analyzers/controller.rb +0 -31
- data/lib/tng/analyzers/model.rb +0 -31
- data/lib/tng/analyzers/other.rb +0 -27
- data/lib/tng/analyzers/service.rb +0 -31
- data/lib/tng/services/direct_generation.rb +52 -250
- data/lib/tng/services/extract_methods.rb +4 -8
- data/lib/tng/services/file_type_detector.rb +124 -0
- data/lib/tng/services/test_generator.rb +83 -163
- data/lib/tng/ui/authentication_warning_display.rb +0 -50
- data/lib/tng/ui/theme.rb +2 -1
- data/lib/tng/utils.rb +139 -0
- data/lib/tng/version.rb +1 -1
- metadata +3 -2
@@ -12,7 +12,7 @@ module Tng
|
|
12
12
|
GENERATE_TESTS_PATH = "cli/tng_rails/contents/generate_tests"
|
13
13
|
CONTENT_RESPONSES_PATH = "cli/tng_rails/content_responses"
|
14
14
|
POLL_INTERVAL_SECONDS = 5 # Poll every 5 seconds
|
15
|
-
MAX_POLL_DURATION_SECONDS =
|
15
|
+
MAX_POLL_DURATION_SECONDS = 420 # 7 minutes total
|
16
16
|
|
17
17
|
def initialize(http_client)
|
18
18
|
@http_client = http_client
|
@@ -21,79 +21,36 @@ module Tng
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def run_for_controller_method(controller, method_info)
|
24
|
-
|
25
|
-
|
26
|
-
response = Tng.send_request_for_controller(
|
27
|
-
controller[:name],
|
28
|
-
controller[:path],
|
29
|
-
method_info[:name],
|
30
|
-
Tng::Utils.fixture_content,
|
31
|
-
Tng::Services::UserAppConfig.config_with_source,
|
32
|
-
Tng::Services::UserAppConfig.base_url,
|
33
|
-
Tng::Services::UserAppConfig.api_key
|
34
|
-
)
|
35
|
-
|
36
|
-
job_data = JSON.parse(response.body)
|
37
|
-
job_id = job_data["job_id"]
|
38
|
-
|
39
|
-
return unless job_id
|
40
|
-
|
41
|
-
result = poll_for_completion(job_id)
|
42
|
-
|
43
|
-
return unless result
|
44
|
-
|
45
|
-
end_time = Time.now
|
46
|
-
generation_time = end_time - start_time
|
47
|
-
|
48
|
-
file_result = Tng::Utils.save_test_file(result.to_json)
|
49
|
-
return unless file_result
|
50
|
-
|
51
|
-
file_result.merge(generation_time: generation_time)
|
24
|
+
generate_test_for_type(controller, method_info, :controller)
|
52
25
|
end
|
53
26
|
|
54
27
|
def run_for_model_method(model, method_info)
|
55
|
-
|
56
|
-
|
57
|
-
response = Tng.send_request_for_model(
|
58
|
-
model[:name],
|
59
|
-
model[:path],
|
60
|
-
method_info[:name],
|
61
|
-
Tng::Utils.fixture_content,
|
62
|
-
Tng::Services::UserAppConfig.config_with_source,
|
63
|
-
Tng::Services::UserAppConfig.base_url,
|
64
|
-
Tng::Services::UserAppConfig.api_key
|
65
|
-
)
|
66
|
-
|
67
|
-
job_data = JSON.parse(response.body)
|
68
|
-
job_id = job_data["job_id"]
|
69
|
-
|
70
|
-
return unless job_id
|
71
|
-
|
72
|
-
result = poll_for_completion(job_id)
|
73
|
-
|
74
|
-
return unless result
|
75
|
-
|
76
|
-
end_time = Time.now
|
77
|
-
generation_time = end_time - start_time
|
28
|
+
generate_test_for_type(model, method_info, :model)
|
29
|
+
end
|
78
30
|
|
79
|
-
|
80
|
-
|
31
|
+
def run_for_service_method(service, method_info)
|
32
|
+
generate_test_for_type(service, method_info, :service)
|
33
|
+
end
|
81
34
|
|
82
|
-
|
35
|
+
def run_for_other_method(other_file, method_info)
|
36
|
+
generate_test_for_type(other_file, method_info, :other)
|
83
37
|
end
|
84
38
|
|
85
|
-
|
39
|
+
private
|
40
|
+
|
41
|
+
def generate_test_for_type(file_object, method_info, type)
|
86
42
|
start_time = Time.now
|
87
43
|
|
88
|
-
response =
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
Tng::
|
93
|
-
Tng::
|
94
|
-
|
95
|
-
|
96
|
-
|
44
|
+
response = send_request_for_type(file_object, method_info, type)
|
45
|
+
return unless response
|
46
|
+
|
47
|
+
if response.is_a?(HTTPX::ErrorResponse)
|
48
|
+
error_icon = Tng::UI::Theme.icon(:error)
|
49
|
+
error_color = Tng::UI::Theme.color(:error)
|
50
|
+
puts "#{error_icon} #{@pastel.decorate("Request failed:",
|
51
|
+
error_color)} #{response.error&.message || "Unknown error"}"
|
52
|
+
return
|
53
|
+
end
|
97
54
|
|
98
55
|
job_data = JSON.parse(response.body)
|
99
56
|
job_id = job_data["job_id"]
|
@@ -113,103 +70,34 @@ module Tng
|
|
113
70
|
file_result.merge(generation_time: generation_time)
|
114
71
|
end
|
115
72
|
|
116
|
-
def
|
117
|
-
|
73
|
+
def send_request_for_type(file_object, method_info, type)
|
74
|
+
config = request_config
|
75
|
+
name = file_object[:name] || File.basename(file_object[:path], ".rb")
|
76
|
+
|
77
|
+
case type
|
78
|
+
when :controller
|
79
|
+
Tng.send_request_for_controller(name, file_object[:path], method_info[:name], *config)
|
80
|
+
when :model
|
81
|
+
Tng.send_request_for_model(name, file_object[:path], method_info[:name], *config)
|
82
|
+
when :service
|
83
|
+
Tng.send_request_for_service(name, file_object[:path], method_info[:name], *config)
|
84
|
+
when :other
|
85
|
+
Tng.send_request_for_other(name, file_object[:path], method_info[:name], *config)
|
86
|
+
end
|
87
|
+
end
|
118
88
|
|
119
|
-
|
120
|
-
|
121
|
-
other_file[:path],
|
122
|
-
method_info[:name],
|
89
|
+
def request_config
|
90
|
+
[
|
123
91
|
Tng::Utils.fixture_content,
|
124
92
|
Tng::Services::UserAppConfig.config_with_source,
|
125
93
|
Tng::Services::UserAppConfig.base_url,
|
126
94
|
Tng::Services::UserAppConfig.api_key
|
127
|
-
|
128
|
-
|
129
|
-
job_data = JSON.parse(response.body)
|
130
|
-
job_id = job_data["job_id"]
|
131
|
-
|
132
|
-
return unless job_id
|
133
|
-
|
134
|
-
result = poll_for_completion(job_id)
|
135
|
-
|
136
|
-
return unless result
|
137
|
-
|
138
|
-
end_time = Time.now
|
139
|
-
generation_time = end_time - start_time
|
140
|
-
|
141
|
-
file_result = Tng::Utils.save_test_file(result.to_json)
|
142
|
-
return unless file_result
|
143
|
-
|
144
|
-
file_result.merge(generation_time: generation_time)
|
145
|
-
end
|
146
|
-
|
147
|
-
private
|
148
|
-
|
149
|
-
def send_request_and_save_test(payload)
|
150
|
-
job_id = submit_async_job(payload)
|
151
|
-
return unless job_id
|
152
|
-
|
153
|
-
result = poll_for_completion(job_id)
|
154
|
-
return unless result
|
155
|
-
|
156
|
-
Tng::Utils.save_test_file(result.to_json)
|
157
|
-
rescue StandardError => e
|
158
|
-
debug_log("Async request failed: #{e.message}") if debug_enabled?
|
159
|
-
puts "❌ Failed to generate test: #{e.message}"
|
160
|
-
nil
|
161
|
-
end
|
162
|
-
|
163
|
-
def submit_async_job(payload)
|
164
|
-
marshaled = Marshal.dump(payload)
|
165
|
-
compressed = Zlib::Deflate.deflate(marshaled)
|
166
|
-
response = @http_client.post_binary(GENERATE_TESTS_PATH, compressed)
|
167
|
-
|
168
|
-
if response.is_a?(HTTPX::ErrorResponse)
|
169
|
-
debug_log("Processing test failed") if debug_enabled?
|
170
|
-
puts "❌ Failed to submit test generation job: #{response.error&.message}"
|
171
|
-
return
|
172
|
-
end
|
173
|
-
|
174
|
-
job_data = JSON.parse(response.body)
|
175
|
-
job_id = job_data["job_id"]
|
176
|
-
|
177
|
-
debug_log("Processing test with id: #{job_id}") if debug_enabled?
|
178
|
-
|
179
|
-
job_id
|
180
|
-
rescue JSON::ParserError => e
|
181
|
-
debug_log("Failed to parse API response: #{e.message}") if debug_enabled?
|
182
|
-
puts "❌ Failed to parse API response. Please retry."
|
95
|
+
]
|
183
96
|
end
|
184
97
|
|
185
98
|
def poll_for_completion(job_id)
|
186
99
|
start_time = Time.current
|
187
|
-
|
188
|
-
rocket_icon = Tng::UI::Theme.icon(:rocket)
|
189
|
-
success_color = Tng::UI::Theme.color(:success)
|
190
|
-
primary_color = Tng::UI::Theme.color(:primary)
|
191
|
-
muted_color = Tng::UI::Theme.color(:muted)
|
192
|
-
|
193
|
-
complete_char = @pastel.decorate("▓", success_color)
|
194
|
-
incomplete_char = @pastel.decorate("░", muted_color)
|
195
|
-
head_char = @pastel.decorate("▶", success_color)
|
196
|
-
|
197
|
-
progress_bar = TTY::ProgressBar.new(
|
198
|
-
"#{rocket_icon} #{@pastel.decorate("Generating tests",
|
199
|
-
primary_color)} #{@pastel.decorate("[:bar]",
|
200
|
-
:white)} #{@pastel.decorate(":status",
|
201
|
-
muted_color)} #{@pastel.decorate(
|
202
|
-
"(:elapsed)", muted_color
|
203
|
-
)}",
|
204
|
-
total: nil,
|
205
|
-
complete: complete_char,
|
206
|
-
incomplete: incomplete_char,
|
207
|
-
head: head_char,
|
208
|
-
width: 40,
|
209
|
-
clear: true,
|
210
|
-
frequency: 10, # Update 10 times per second for smooth animation
|
211
|
-
interval: 1 # Show elapsed time updates every second
|
212
|
-
)
|
100
|
+
progress_bar = create_progress_bar
|
213
101
|
|
214
102
|
loop do
|
215
103
|
seconds_elapsed = (Time.current - start_time).to_i
|
@@ -220,16 +108,7 @@ module Tng
|
|
220
108
|
return
|
221
109
|
end
|
222
110
|
|
223
|
-
status_text =
|
224
|
-
when 0..15 then "initializing..."
|
225
|
-
when 16..45 then "analyzing code structure..."
|
226
|
-
when 46..90 then "generating test cases..."
|
227
|
-
when 91..150 then "optimizing test logic..."
|
228
|
-
when 151..210 then "refining assertions..."
|
229
|
-
when 211..270 then "formatting output..."
|
230
|
-
when 271..330 then "finalizing tests..."
|
231
|
-
else "completing generation..."
|
232
|
-
end
|
111
|
+
status_text = determine_status_text(seconds_elapsed)
|
233
112
|
progress_bar.advance(1, status: status_text)
|
234
113
|
|
235
114
|
sleep(POLL_INTERVAL_SECONDS)
|
@@ -275,6 +154,47 @@ module Tng
|
|
275
154
|
end
|
276
155
|
end
|
277
156
|
|
157
|
+
def create_progress_bar
|
158
|
+
rocket_icon = Tng::UI::Theme.icon(:rocket)
|
159
|
+
success_color = Tng::UI::Theme.color(:success)
|
160
|
+
primary_color = Tng::UI::Theme.color(:primary)
|
161
|
+
muted_color = Tng::UI::Theme.color(:muted)
|
162
|
+
|
163
|
+
complete_char = @pastel.decorate("\u2593", success_color)
|
164
|
+
incomplete_char = @pastel.decorate("\u2591", muted_color)
|
165
|
+
head_char = @pastel.decorate("\u25B6", success_color)
|
166
|
+
|
167
|
+
TTY::ProgressBar.new(
|
168
|
+
"#{rocket_icon} #{@pastel.decorate("Generating tests",
|
169
|
+
primary_color)} #{@pastel.decorate("[:bar]",
|
170
|
+
:white)} #{@pastel.decorate(":status",
|
171
|
+
muted_color)} #{@pastel.decorate(
|
172
|
+
"(:elapsed)", muted_color
|
173
|
+
)}",
|
174
|
+
total: nil,
|
175
|
+
complete: complete_char,
|
176
|
+
incomplete: incomplete_char,
|
177
|
+
head: head_char,
|
178
|
+
width: 40,
|
179
|
+
clear: true,
|
180
|
+
frequency: 10, # Update 10 times per second for smooth animation
|
181
|
+
interval: 1 # Show elapsed time updates every second
|
182
|
+
)
|
183
|
+
end
|
184
|
+
|
185
|
+
def determine_status_text(seconds_elapsed)
|
186
|
+
case seconds_elapsed
|
187
|
+
when 0..15 then "initializing..."
|
188
|
+
when 16..45 then "analyzing code structure..."
|
189
|
+
when 46..90 then "generating test cases..."
|
190
|
+
when 91..150 then "optimizing test logic..."
|
191
|
+
when 151..210 then "refining assertions..."
|
192
|
+
when 211..270 then "formatting output..."
|
193
|
+
when 271..330 then "finalizing tests..."
|
194
|
+
else "completing generation..."
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
278
198
|
def debug_enabled?
|
279
199
|
ENV["DEBUG"] == "1"
|
280
200
|
end
|
@@ -119,54 +119,4 @@ class AuthenticationWarningDisplay
|
|
119
119
|
method.key?(:auth_type) && !method[:auth_type].to_s.strip.empty?
|
120
120
|
end
|
121
121
|
end
|
122
|
-
|
123
|
-
def build_missing_items_list
|
124
|
-
issues = []
|
125
|
-
|
126
|
-
if Tng.authentication_enabled && authentication_methods_empty?
|
127
|
-
issues << @pastel.public_send(Tng::UI::Theme.color(:error), " • authentication_methods array is empty")
|
128
|
-
end
|
129
|
-
|
130
|
-
if !authentication_methods_empty? && !authentication_methods_valid?
|
131
|
-
issues << @pastel.public_send(Tng::UI::Theme.color(:error), " • authentication_methods contains invalid entries")
|
132
|
-
|
133
|
-
Tng.authentication_methods.each_with_index do |method, index|
|
134
|
-
next if method.is_a?(Hash)
|
135
|
-
|
136
|
-
issues << @pastel.public_send(Tng::UI::Theme.color(:muted), " - Entry #{index + 1}: not a valid hash")
|
137
|
-
next
|
138
|
-
end
|
139
|
-
|
140
|
-
Tng.authentication_methods.each_with_index do |method, index|
|
141
|
-
next unless method.is_a?(Hash)
|
142
|
-
|
143
|
-
if !method.key?(:method) || method[:method].to_s.strip.empty?
|
144
|
-
issues << @pastel.public_send(Tng::UI::Theme.color(:muted),
|
145
|
-
" - Entry #{index + 1}: missing or empty 'method'")
|
146
|
-
end
|
147
|
-
|
148
|
-
if !method.key?(:file_location) || method[:file_location].to_s.strip.empty?
|
149
|
-
issues << @pastel.public_send(Tng::UI::Theme.color(:muted),
|
150
|
-
" - Entry #{index + 1}: missing or empty 'file_location'")
|
151
|
-
end
|
152
|
-
|
153
|
-
if !method.key?(:auth_type) || method[:auth_type].to_s.strip.empty?
|
154
|
-
issues << @pastel.public_send(Tng::UI::Theme.color(:muted),
|
155
|
-
" - Entry #{index + 1}: missing or empty 'auth_type'")
|
156
|
-
end
|
157
|
-
end
|
158
|
-
end
|
159
|
-
|
160
|
-
unless Tng.authentication_enabled
|
161
|
-
issues << @pastel.public_send(Tng::UI::Theme.color(:warning),
|
162
|
-
" • authentication_enabled is set to false")
|
163
|
-
end
|
164
|
-
|
165
|
-
if issues.empty?
|
166
|
-
@pastel.public_send(Tng::UI::Theme.color(:muted),
|
167
|
-
" • Configuration appears valid")
|
168
|
-
else
|
169
|
-
issues.join("\n")
|
170
|
-
end
|
171
|
-
end
|
172
122
|
end
|
data/lib/tng/ui/theme.rb
CHANGED
@@ -13,6 +13,7 @@ module Tng
|
|
13
13
|
|
14
14
|
# Action icons
|
15
15
|
rocket: "🚀",
|
16
|
+
run: "▶",
|
16
17
|
wave: "👋",
|
17
18
|
stats: "📊",
|
18
19
|
config: "📋",
|
@@ -174,7 +175,7 @@ module Tng
|
|
174
175
|
return false if ENV["BUNDLE_GEMFILE"]
|
175
176
|
return false if $PROGRAM_NAME&.include?("bundle")
|
176
177
|
return false if $PROGRAM_NAME&.include?("rails") && ARGV.any? { |arg| %w[server console runner].include?(arg) }
|
177
|
-
|
178
|
+
|
178
179
|
ENV["TNG_CLI"] == "true" || $PROGRAM_NAME&.include?("tng")
|
179
180
|
end
|
180
181
|
|
data/lib/tng/utils.rb
CHANGED
@@ -321,5 +321,144 @@ module Tng
|
|
321
321
|
false
|
322
322
|
end
|
323
323
|
end
|
324
|
+
|
325
|
+
# Test result parsing methods
|
326
|
+
def self.parse_rspec_json_results(output, exit_code, pastel, terminal_width)
|
327
|
+
# Parse RSpec JSON output
|
328
|
+
json_data = JSON.parse(output)
|
329
|
+
summary = json_data["summary"] || json_data
|
330
|
+
|
331
|
+
total = summary["example_count"] || summary["total_examples"] || 0
|
332
|
+
failures = summary["failure_count"] || summary["failures"] || 0
|
333
|
+
pending = summary["pending_count"] || summary["pending"] || 0
|
334
|
+
errors = summary["errors_outside_of_examples_count"] || 0
|
335
|
+
|
336
|
+
passed = total - failures - pending - errors
|
337
|
+
|
338
|
+
display_test_counts(passed, failures + errors, pending, total, exit_code.zero?, pastel, terminal_width)
|
339
|
+
rescue JSON::ParserError
|
340
|
+
# Fallback to text parsing if JSON fails
|
341
|
+
puts center_text_static(
|
342
|
+
pastel.decorate("JSON parsing failed, falling back to text parsing",
|
343
|
+
Tng::UI::Theme.color(:warning)), terminal_width
|
344
|
+
)
|
345
|
+
parse_rspec_results(output, exit_code, pastel, terminal_width)
|
346
|
+
end
|
347
|
+
|
348
|
+
def self.parse_rspec_results(output, exit_code, pastel, terminal_width)
|
349
|
+
# RSpec output example: "7 examples, 2 failures"
|
350
|
+
# or "Finished in 0.12345 seconds (files took 0.01234 seconds to load)"
|
351
|
+
# "7 examples, 2 failures, 1 pending"
|
352
|
+
|
353
|
+
lines = output.lines
|
354
|
+
summary_line = lines.find { |line| line.match?(/\d+ examples?,/) }
|
355
|
+
|
356
|
+
if summary_line
|
357
|
+
# Extract numbers from summary
|
358
|
+
match = summary_line.match(/(\d+) examples?, (\d+) failures?(?:, (\d+) pending)?/)
|
359
|
+
if match
|
360
|
+
total = match[1].to_i
|
361
|
+
failures = match[2].to_i
|
362
|
+
pending = match[3].to_i || 0
|
363
|
+
passed = total - failures - pending
|
364
|
+
|
365
|
+
display_test_counts(passed, failures, pending, total, exit_code == 0, pastel, terminal_width)
|
366
|
+
else
|
367
|
+
puts center_text_static(pastel.decorate("Could not parse RSpec results", Tng::UI::Theme.color(:error)),
|
368
|
+
terminal_width)
|
369
|
+
puts center_text_static(output.lines.last.strip, terminal_width) if output.lines.any?
|
370
|
+
end
|
371
|
+
else
|
372
|
+
puts center_text_static(pastel.decorate("No test results found", Tng::UI::Theme.color(:warning)),
|
373
|
+
terminal_width)
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
def self.parse_minitest_results(output, exit_code, pastel, terminal_width)
|
378
|
+
# Minitest output example: "7 tests, 15 assertions, 2 failures, 1 errors, 0 skips"
|
379
|
+
# or "Run options: --seed 12345"
|
380
|
+
# "7 tests, 2 assertions, 0 failures, 0 errors, 0 skips"
|
381
|
+
|
382
|
+
lines = output.lines
|
383
|
+
# Look for summary line - could be "X tests" or "X runs"
|
384
|
+
summary_line = lines.find { |line| line.match?(/\d+ (?:tests?|runs?),/) }
|
385
|
+
|
386
|
+
if summary_line
|
387
|
+
# Extract numbers from summary - handle both "tests" and "runs" format
|
388
|
+
match = summary_line.match(/(\d+) (?:tests?|runs?), (\d+) assertions?, (\d+) failures?, (\d+) errors?, (\d+) skips?/)
|
389
|
+
if match
|
390
|
+
total = match[1].to_i
|
391
|
+
failures = match[3].to_i
|
392
|
+
errors = match[4].to_i
|
393
|
+
skips = match[5].to_i
|
394
|
+
passed = total - failures - errors - skips
|
395
|
+
|
396
|
+
display_test_counts(passed, failures + errors, skips, total, exit_code.zero?, pastel, terminal_width)
|
397
|
+
else
|
398
|
+
puts center_text_static(pastel.decorate("Could not parse Minitest results", Tng::UI::Theme.color(:error)),
|
399
|
+
terminal_width)
|
400
|
+
puts center_text_static("Expected format: 'X tests, Y assertions, Z failures, A errors, B skips'",
|
401
|
+
terminal_width)
|
402
|
+
puts center_text_static("Got: #{summary_line.strip}", terminal_width)
|
403
|
+
end
|
404
|
+
else
|
405
|
+
puts center_text_static(pastel.decorate("No test results found", Tng::UI::Theme.color(:warning)),
|
406
|
+
terminal_width)
|
407
|
+
puts center_text_static("Looking for line with test counts...", terminal_width)
|
408
|
+
# Show last few lines for debugging
|
409
|
+
last_lines = output.lines.last(3).map(&:strip).join(" | ")
|
410
|
+
puts center_text_static("Last lines: #{last_lines}", terminal_width) if output.lines.any?
|
411
|
+
end
|
412
|
+
end
|
413
|
+
|
414
|
+
def self.display_test_counts(passed, failed, skipped, total, success, pastel, terminal_width)
|
415
|
+
passed_icon = pastel.decorate(Tng::UI::Theme.icon(:success), Tng::UI::Theme.color(:primary))
|
416
|
+
failed_icon = pastel.decorate(Tng::UI::Theme.icon(:error), Tng::UI::Theme.color(:primary))
|
417
|
+
skipped_icon = pastel.decorate("⏭️", Tng::UI::Theme.color(:accent))
|
418
|
+
total_icon = pastel.decorate(Tng::UI::Theme.icon(:marker), Tng::UI::Theme.color(:primary))
|
419
|
+
|
420
|
+
passed_text = pastel.decorate("#{passed_icon} #{passed} passed", Tng::UI::Theme.color(:success))
|
421
|
+
failed_text = pastel.decorate("#{failed_icon} #{failed} failed", Tng::UI::Theme.color(:error))
|
422
|
+
skipped_text = if skipped.positive?
|
423
|
+
pastel.decorate("#{skipped_icon} #{skipped} skipped",
|
424
|
+
Tng::UI::Theme.color(:warning))
|
425
|
+
else
|
426
|
+
nil
|
427
|
+
end
|
428
|
+
total_text = pastel.decorate("#{total_icon} #{total} total", Tng::UI::Theme.color(:secondary))
|
429
|
+
|
430
|
+
results = [passed_text, failed_text, skipped_text, total_text].compact.join(", ")
|
431
|
+
puts center_text_static(results, terminal_width)
|
432
|
+
|
433
|
+
# Overall result
|
434
|
+
overall_msg = if success
|
435
|
+
pastel.decorate("#{Tng::UI::Theme.icon(:success)} All tests passed!", Tng::UI::Theme.color(:success))
|
436
|
+
else
|
437
|
+
pastel.decorate("#{Tng::UI::Theme.icon(:error)} Some tests failed", Tng::UI::Theme.color(:error))
|
438
|
+
end
|
439
|
+
puts center_text_static(overall_msg, terminal_width)
|
440
|
+
end
|
441
|
+
|
442
|
+
def self.center_text_static(text, width = 80)
|
443
|
+
lines = text.split("\n")
|
444
|
+
lines.map do |line|
|
445
|
+
# Remove ANSI color codes for length calculation
|
446
|
+
clean_line = line.gsub(/\e\[[0-9;]*m/, "")
|
447
|
+
padding = [(width - clean_line.length) / 2, 0].max
|
448
|
+
" " * padding + line
|
449
|
+
end.join("\n")
|
450
|
+
end
|
451
|
+
|
452
|
+
def self.format_generation_time(seconds)
|
453
|
+
if seconds < 1
|
454
|
+
"#{(seconds * 1000).round}ms"
|
455
|
+
elsif seconds < 60
|
456
|
+
"#{seconds.round(1)}s"
|
457
|
+
else
|
458
|
+
minutes = (seconds / 60).floor
|
459
|
+
remaining_seconds = (seconds % 60).round
|
460
|
+
"#{minutes}m #{remaining_seconds}s"
|
461
|
+
end
|
462
|
+
end
|
324
463
|
end
|
325
464
|
end
|
data/lib/tng/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tng
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- ralucab
|
@@ -219,6 +219,7 @@ files:
|
|
219
219
|
- lib/tng/railtie.rb
|
220
220
|
- lib/tng/services/direct_generation.rb
|
221
221
|
- lib/tng/services/extract_methods.rb
|
222
|
+
- lib/tng/services/file_type_detector.rb
|
222
223
|
- lib/tng/services/test_generator.rb
|
223
224
|
- lib/tng/services/testng.rb
|
224
225
|
- lib/tng/services/user_app_config.rb
|
@@ -271,7 +272,7 @@ post_install_message: "┌ TNG ────────────────
|
|
271
272
|
\ │\n│ • bundle exec
|
272
273
|
tng --help - Show help information │\n│ │\n│
|
273
274
|
\ \U0001F4A1 Generate tests for individual methods with precision │\n└────────────────────────────────────────────────────────────
|
274
|
-
v0.2.
|
275
|
+
v0.2.4 ┘\n"
|
275
276
|
rdoc_options: []
|
276
277
|
require_paths:
|
277
278
|
- lib
|