tng 0.1.6 → 0.2.2
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/LICENSE.md +2 -2
- data/README.md +10 -20
- data/bin/tng +238 -472
- data/binaries/tng.bundle +0 -0
- data/binaries/tng.so +0 -0
- data/lib/tng/analyzers/model.rb +9 -0
- data/lib/tng/analyzers/other.rb +277 -0
- data/lib/tng/analyzers/service.rb +1 -0
- data/lib/tng/api/http_client.rb +21 -23
- data/lib/tng/services/direct_generation.rb +320 -0
- data/lib/tng/services/extract_methods.rb +39 -0
- data/lib/tng/services/test_generator.rb +232 -104
- data/lib/tng/services/testng.rb +2 -2
- data/lib/tng/services/user_app_config.rb +5 -6
- data/lib/tng/ui/about_display.rb +7 -5
- data/lib/tng/ui/controller_test_flow_display.rb +0 -17
- data/lib/tng/ui/model_test_flow_display.rb +0 -17
- data/lib/tng/ui/other_test_flow_display.rb +78 -0
- data/lib/tng/ui/post_install_box.rb +7 -9
- data/lib/tng/ui/service_test_flow_display.rb +0 -17
- data/lib/tng/ui/show_help.rb +11 -68
- data/lib/tng/utils.rb +68 -6
- data/lib/tng/version.rb +1 -1
- metadata +15 -11
@@ -3,148 +3,276 @@
|
|
3
3
|
require "zlib"
|
4
4
|
require "tng/utils"
|
5
5
|
require "tng/services/user_app_config"
|
6
|
+
require "tng/ui/theme"
|
7
|
+
require "pastel"
|
6
8
|
|
7
9
|
module Tng
|
8
10
|
module Services
|
9
11
|
class TestGenerator
|
10
12
|
GENERATE_TESTS_PATH = "cli/tng_rails/contents/generate_tests"
|
13
|
+
CONTENT_RESPONSES_PATH = "cli/tng_rails/content_responses"
|
14
|
+
POLL_INTERVAL_SECONDS = 5 # Poll every 5 seconds
|
15
|
+
MAX_POLL_DURATION_SECONDS = 360 # 6 minutes total
|
11
16
|
|
12
17
|
def initialize(http_client)
|
13
18
|
@http_client = http_client
|
14
19
|
@machine_info = Tng.machine_info
|
20
|
+
@pastel = Pastel.new
|
15
21
|
end
|
16
22
|
|
17
|
-
def
|
18
|
-
|
19
|
-
controller_test: Tng::Analyzers::Controller.read_test_file_for_controller(controller[:path]),
|
20
|
-
object_fixture_data: Tng::Utils.fixture_content,
|
21
|
-
machine_info: @machine_info,
|
22
|
-
ast: Tng::Analyzers::Controller.value_for_controller(controller[:path]),
|
23
|
-
test_type: "controller",
|
24
|
-
controller: controller,
|
25
|
-
routes: Tng::Analyzers::Controller.routes_for_controller(controller[:path]),
|
26
|
-
auth_patterns: Tng::Services::UserAppConfig.config_with_source,
|
27
|
-
model_info: Tng::Analyzers::Controller.model_info_for_controller(controller[:path]),
|
28
|
-
parents: Tng::Analyzers::Controller.parents_for_controller(controller)
|
29
|
-
}
|
30
|
-
|
31
|
-
send_request_and_save_test(payload)
|
32
|
-
end
|
23
|
+
def run_for_controller_method(controller, method_info)
|
24
|
+
start_time = Time.now
|
33
25
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
model_connections: Tng::Analyzers::Model.model_connections(model)
|
44
|
-
}
|
45
|
-
|
46
|
-
send_request_and_save_test(payload)
|
47
|
-
end
|
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
|
+
)
|
48
35
|
|
49
|
-
|
50
|
-
|
51
|
-
service_test: Tng::Analyzers::Service.read_test_file_for_service(service[:path]),
|
52
|
-
machine_info: @machine_info,
|
53
|
-
object_fixture_data: Tng::Utils.fixture_content,
|
54
|
-
ast: Tng::Analyzers::Service.value_for_service(service[:path]),
|
55
|
-
test_type: "service",
|
56
|
-
service: service
|
57
|
-
}
|
58
|
-
send_request_and_save_test(payload)
|
59
|
-
end
|
36
|
+
job_data = JSON.parse(response.body)
|
37
|
+
job_id = job_data["job_id"]
|
60
38
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
}
|
75
|
-
|
76
|
-
send_request_and_save_test(payload)
|
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)
|
77
52
|
end
|
78
53
|
|
79
54
|
def run_for_model_method(model, method_info)
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
55
|
+
start_time = Time.now
|
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
|
78
|
+
|
79
|
+
file_result = Tng::Utils.save_test_file(result.to_json)
|
80
|
+
return unless file_result
|
81
|
+
|
82
|
+
file_result.merge(generation_time: generation_time)
|
93
83
|
end
|
94
84
|
|
95
85
|
def run_for_service_method(service, method_info)
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
86
|
+
start_time = Time.now
|
87
|
+
|
88
|
+
response = Tng.send_request_for_service(
|
89
|
+
service[:name],
|
90
|
+
service[:path],
|
91
|
+
method_info[:name],
|
92
|
+
Tng::Utils.fixture_content,
|
93
|
+
Tng::Services::UserAppConfig.config_with_source,
|
94
|
+
Tng::Services::UserAppConfig.base_url,
|
95
|
+
Tng::Services::UserAppConfig.api_key
|
96
|
+
)
|
97
|
+
|
98
|
+
job_data = JSON.parse(response.body)
|
99
|
+
job_id = job_data["job_id"]
|
100
|
+
|
101
|
+
return unless job_id
|
102
|
+
|
103
|
+
result = poll_for_completion(job_id)
|
104
|
+
|
105
|
+
return unless result
|
106
|
+
|
107
|
+
end_time = Time.now
|
108
|
+
generation_time = end_time - start_time
|
109
|
+
|
110
|
+
file_result = Tng::Utils.save_test_file(result.to_json)
|
111
|
+
return unless file_result
|
112
|
+
|
113
|
+
file_result.merge(generation_time: generation_time)
|
114
|
+
end
|
115
|
+
|
116
|
+
def run_for_other_method(other_file, method_info)
|
117
|
+
start_time = Time.now
|
118
|
+
|
119
|
+
response = Tng.send_request_for_other(
|
120
|
+
other_file[:name] || File.basename(other_file[:path], ".rb"),
|
121
|
+
other_file[:path],
|
122
|
+
method_info[:name],
|
123
|
+
Tng::Utils.fixture_content,
|
124
|
+
Tng::Services::UserAppConfig.config_with_source,
|
125
|
+
Tng::Services::UserAppConfig.base_url,
|
126
|
+
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)
|
108
145
|
end
|
109
146
|
|
110
147
|
private
|
111
148
|
|
112
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)
|
113
164
|
marshaled = Marshal.dump(payload)
|
114
165
|
compressed = Zlib::Deflate.deflate(marshaled)
|
115
166
|
response = @http_client.post_binary(GENERATE_TESTS_PATH, compressed)
|
116
167
|
|
117
168
|
if response.is_a?(HTTPX::ErrorResponse)
|
118
|
-
debug_log("
|
119
|
-
|
169
|
+
debug_log("Processing test failed") if debug_enabled?
|
170
|
+
puts "❌ Failed to submit test generation job: #{response.error&.message}"
|
171
|
+
return
|
120
172
|
end
|
121
173
|
|
122
|
-
|
174
|
+
job_data = JSON.parse(response.body)
|
175
|
+
job_id = job_data["job_id"]
|
123
176
|
|
124
|
-
|
125
|
-
if test_content.nil? || test_content.empty?
|
126
|
-
debug_log("Empty response body received") if debug_enabled?
|
127
|
-
return handle_empty_response
|
128
|
-
end
|
177
|
+
debug_log("Processing test with id: #{job_id}") if debug_enabled?
|
129
178
|
|
130
|
-
|
131
|
-
Tng::Utils.save_test_file(test_content)
|
179
|
+
job_id
|
132
180
|
rescue JSON::ParserError => e
|
133
|
-
debug_log("
|
134
|
-
puts "❌ Failed to parse
|
135
|
-
nil
|
181
|
+
debug_log("Failed to parse API response: #{e.message}") if debug_enabled?
|
182
|
+
puts "❌ Failed to parse API response. Please retry."
|
136
183
|
end
|
137
184
|
|
138
|
-
def
|
139
|
-
|
140
|
-
puts "❌ Request failed: #{response.error&.message}"
|
141
|
-
nil
|
142
|
-
end
|
185
|
+
def poll_for_completion(job_id)
|
186
|
+
start_time = Time.current
|
143
187
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
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
|
+
)
|
213
|
+
|
214
|
+
loop do
|
215
|
+
seconds_elapsed = (Time.current - start_time).to_i
|
216
|
+
|
217
|
+
if seconds_elapsed > MAX_POLL_DURATION_SECONDS
|
218
|
+
progress_bar.finish
|
219
|
+
puts "❌ Test generation timed out after #{MAX_POLL_DURATION_SECONDS} seconds (6 minutes)"
|
220
|
+
return
|
221
|
+
end
|
222
|
+
|
223
|
+
status_text = case seconds_elapsed
|
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
|
233
|
+
progress_bar.advance(1, status: status_text)
|
234
|
+
|
235
|
+
sleep(POLL_INTERVAL_SECONDS)
|
236
|
+
|
237
|
+
status_response = @http_client.get("#{CONTENT_RESPONSES_PATH}/#{job_id}")
|
238
|
+
|
239
|
+
if status_response.is_a?(HTTPX::ErrorResponse)
|
240
|
+
debug_log("Status check failed: #{status_response.error&.message}") if debug_enabled?
|
241
|
+
next # Continue polling on network errors
|
242
|
+
end
|
243
|
+
|
244
|
+
begin
|
245
|
+
status_data = JSON.parse(status_response.body.to_s)
|
246
|
+
status = status_data["status"]
|
247
|
+
|
248
|
+
case status
|
249
|
+
when "completed"
|
250
|
+
debug_log("Test generation completed!") if debug_enabled?
|
251
|
+
progress_bar.finish
|
252
|
+
success_icon = Tng::UI::Theme.icon(:success)
|
253
|
+
success_color = Tng::UI::Theme.color(:success)
|
254
|
+
puts "#{success_icon} #{@pastel.decorate("Test generation completed!", success_color)}"
|
255
|
+
return status_data["result"]
|
256
|
+
when "failed"
|
257
|
+
debug_log("Test generation failed: #{status_data["error"]}") if debug_enabled?
|
258
|
+
progress_bar.finish
|
259
|
+
error_icon = Tng::UI::Theme.icon(:error)
|
260
|
+
error_color = Tng::UI::Theme.color(:error)
|
261
|
+
puts "#{error_icon} #{@pastel.decorate("Test generation failed:",
|
262
|
+
error_color)} #{status_data["error"] || "Unknown error"}"
|
263
|
+
return
|
264
|
+
when "pending", "processing"
|
265
|
+
# Progress bar updates smoothly above
|
266
|
+
next
|
267
|
+
else
|
268
|
+
debug_log("Unknown test generation status: #{status}") if debug_enabled?
|
269
|
+
next
|
270
|
+
end
|
271
|
+
rescue JSON::ParserError => e
|
272
|
+
debug_log("Failed to parse response status: #{e.message}") if debug_enabled?
|
273
|
+
next
|
274
|
+
end
|
275
|
+
end
|
148
276
|
end
|
149
277
|
|
150
278
|
def debug_enabled?
|
data/lib/tng/services/testng.rb
CHANGED
@@ -21,7 +21,7 @@ module Services
|
|
21
21
|
when 200
|
22
22
|
begin
|
23
23
|
data = JSON.parse(response.body)
|
24
|
-
current_version = data["current_version"]
|
24
|
+
current_version = data["current_version"]["gem_version"]
|
25
25
|
server_base_url = data["base_url"]
|
26
26
|
user_base_url = Tng::Services::UserAppConfig.base_url
|
27
27
|
|
@@ -80,7 +80,7 @@ module Services
|
|
80
80
|
|
81
81
|
response = @http_client.get(STATS_PATH, headers: headers)
|
82
82
|
|
83
|
-
return
|
83
|
+
return if response.is_a?(HTTPX::ErrorResponse)
|
84
84
|
|
85
85
|
if response.status == 200
|
86
86
|
begin
|
@@ -38,7 +38,7 @@ module Tng
|
|
38
38
|
end
|
39
39
|
|
40
40
|
def self.find_method_in_ast(node, method_name)
|
41
|
-
return
|
41
|
+
return unless node.is_a?(Prism::Node)
|
42
42
|
|
43
43
|
return node if node.is_a?(Prism::DefNode) && node.name == method_name.to_sym
|
44
44
|
|
@@ -51,19 +51,18 @@ module Tng
|
|
51
51
|
end
|
52
52
|
|
53
53
|
def self.extract_method_source(file_path, method_name)
|
54
|
-
return
|
54
|
+
return unless file_path && method_name
|
55
55
|
|
56
56
|
full_path = Rails.root.join(file_path)
|
57
|
-
return
|
57
|
+
return unless File.exist?(full_path)
|
58
58
|
|
59
59
|
file_content = File.read(full_path)
|
60
60
|
result = Prism.parse(file_content)
|
61
61
|
|
62
62
|
method_node = find_method_in_ast(result.value, method_name)
|
63
|
-
return
|
63
|
+
return unless method_node
|
64
64
|
|
65
|
-
|
66
|
-
start_line = method_node.location.start_line - 1 # Convert to 0-based index
|
65
|
+
start_line = method_node.location.start_line - 1
|
67
66
|
end_line = method_node.location.end_line - 1
|
68
67
|
|
69
68
|
lines = file_content.lines
|
data/lib/tng/ui/about_display.rb
CHANGED
@@ -19,15 +19,17 @@ class AboutDisplay
|
|
19
19
|
|
20
20
|
def display
|
21
21
|
about_content = [
|
22
|
-
@pastel.public_send(Tng::UI::Theme.color(:secondary)).bold("About Tng"),
|
23
22
|
"",
|
24
|
-
@pastel.public_send(Tng::UI::Theme.color(:
|
23
|
+
@pastel.public_send(Tng::UI::Theme.color(:secondary),
|
25
24
|
"Tng is an LLM-powered test generation tool for Rails applications."),
|
26
25
|
"",
|
27
26
|
@pastel.public_send(Tng::UI::Theme.color(:accent), "Features:"),
|
28
|
-
@pastel.public_send(Tng::UI::Theme.color(:muted),
|
29
|
-
|
30
|
-
@pastel.public_send(Tng::UI::Theme.color(:muted),
|
27
|
+
@pastel.public_send(Tng::UI::Theme.color(:muted),
|
28
|
+
"#{Tng::UI::Theme.icon(:bullet)} 20+ Rails file types supported"),
|
29
|
+
@pastel.public_send(Tng::UI::Theme.color(:muted),
|
30
|
+
"#{Tng::UI::Theme.icon(:bullet)} Core: Controllers, Models, Services"),
|
31
|
+
@pastel.public_send(Tng::UI::Theme.color(:muted),
|
32
|
+
"#{Tng::UI::Theme.icon(:bullet)} Extended: Jobs, Helpers, Lib, Policies, Presenters, Mailers, GraphQL + more"),
|
31
33
|
@pastel.public_send(Tng::UI::Theme.color(:muted), "#{Tng::UI::Theme.icon(:bullet)} LLM-powered test suggestions"),
|
32
34
|
@pastel.public_send(Tng::UI::Theme.color(:muted), "#{Tng::UI::Theme.icon(:bullet)} Test coverage analysis"),
|
33
35
|
"",
|
@@ -37,23 +37,6 @@ class ControllerTestFlowDisplay
|
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
40
|
-
def select_test_option(controller_choice)
|
41
|
-
header = @pastel.public_send(Tng::UI::Theme.color(:primary)).bold("#{Tng::UI::Theme.icon(:rocket)} Test Generation Options for #{controller_choice[:name]}")
|
42
|
-
puts Tng::UI::Theme.center_text(header, @terminal_width)
|
43
|
-
puts
|
44
|
-
|
45
|
-
@prompt.select(
|
46
|
-
@pastel.public_send(Tng::UI::Theme.color(:primary), "Choose test generation type:"),
|
47
|
-
cycle: true,
|
48
|
-
symbols: { marker: Tng::UI::Theme.icon(:marker) }
|
49
|
-
) do |menu|
|
50
|
-
menu.choice @pastel.public_send(Tng::UI::Theme.color(:success), "Generate all possible tests"),
|
51
|
-
:all_possible_tests
|
52
|
-
menu.choice @pastel.public_send(Tng::UI::Theme.color(:info), "Generate per method"), :per_method
|
53
|
-
menu.choice @pastel.public_send(Tng::UI::Theme.color(:secondary), "#{Tng::UI::Theme.icon(:back)} Back"), :back
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
40
|
def show_no_controllers_message
|
58
41
|
error_msg = "#{@pastel.public_send(Tng::UI::Theme.color(:error)).bold("#{Tng::UI::Theme.icon(:error)} No controllers found in your application")}\n#{@pastel.public_send(
|
59
42
|
Tng::UI::Theme.color(:muted), "Make sure you have controllers in app/controllers/"
|
@@ -36,23 +36,6 @@ class ModelTestFlowDisplay
|
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
39
|
-
def select_test_option(model_choice)
|
40
|
-
header = @pastel.public_send(Tng::UI::Theme.color(:primary)).bold("#{Tng::UI::Theme.icon(:rocket)} Test Generation Options for #{model_choice[:name]}")
|
41
|
-
puts Tng::UI::Theme.center_text(header, @terminal_width)
|
42
|
-
puts
|
43
|
-
|
44
|
-
@prompt.select(
|
45
|
-
@pastel.public_send(Tng::UI::Theme.color(:primary), "Choose test generation type:"),
|
46
|
-
cycle: true,
|
47
|
-
symbols: { marker: Tng::UI::Theme.icon(:marker) }
|
48
|
-
) do |menu|
|
49
|
-
menu.choice @pastel.public_send(Tng::UI::Theme.color(:success), "Generate all possible tests"),
|
50
|
-
:all_possible_tests
|
51
|
-
menu.choice @pastel.public_send(Tng::UI::Theme.color(:info), "Generate per method"), :per_method
|
52
|
-
menu.choice @pastel.public_send(Tng::UI::Theme.color(:secondary), "#{Tng::UI::Theme.icon(:back)} Back"), :back
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
39
|
def show_no_models_message
|
57
40
|
puts
|
58
41
|
puts Tng::UI::Theme.center_text(
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "tty-prompt"
|
4
|
+
require "tty-spinner"
|
5
|
+
require "pastel"
|
6
|
+
require "tty-screen"
|
7
|
+
require_relative "theme"
|
8
|
+
|
9
|
+
class OtherTestFlowDisplay
|
10
|
+
def initialize(prompt, pastel)
|
11
|
+
@prompt = prompt
|
12
|
+
@pastel = pastel
|
13
|
+
@terminal_width = begin
|
14
|
+
TTY::Screen.width
|
15
|
+
rescue StandardError
|
16
|
+
80
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def select_other_file(other_files)
|
21
|
+
header = @pastel.public_send(Tng::UI::Theme.color(:primary)).bold("#{Tng::UI::Theme.icon(:config)} Select file to test:")
|
22
|
+
puts Tng::UI::Theme.center_text(header, @terminal_width)
|
23
|
+
|
24
|
+
@prompt.select(
|
25
|
+
"",
|
26
|
+
cycle: true,
|
27
|
+
per_page: 12,
|
28
|
+
filter: true,
|
29
|
+
symbols: { marker: Tng::UI::Theme.icon(:marker) }
|
30
|
+
) do |menu|
|
31
|
+
other_files.each do |file|
|
32
|
+
display_name = "#{file[:name]} #{@pastel.public_send(Tng::UI::Theme.color(:muted), "(#{file[:path]})")}"
|
33
|
+
menu.choice display_name, file
|
34
|
+
end
|
35
|
+
menu.choice @pastel.public_send(Tng::UI::Theme.color(:secondary), "#{Tng::UI::Theme.icon(:back)} Back"), :back
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def show_no_other_files_message
|
40
|
+
error_msg = "#{@pastel.public_send(Tng::UI::Theme.color(:error)).bold("#{Tng::UI::Theme.icon(:error)} No other files found in your application")}\n#{@pastel.public_send(
|
41
|
+
Tng::UI::Theme.color(:muted), "Make sure you have files in supported directories (app/jobs, app/helpers, lib/, app/policies, app/presenters, app/mailers, app/graphql, etc.)"
|
42
|
+
)}"
|
43
|
+
puts Tng::UI::Theme.center_text(error_msg, @terminal_width)
|
44
|
+
@prompt.keypress(Tng::UI::Theme.center_text(
|
45
|
+
@pastel.public_send(Tng::UI::Theme.color(:muted),
|
46
|
+
"Press any key to continue..."), @terminal_width
|
47
|
+
))
|
48
|
+
end
|
49
|
+
|
50
|
+
def select_other_method(other_file, methods)
|
51
|
+
header = @pastel.public_send(Tng::UI::Theme.color(:primary)).bold("#{Tng::UI::Theme.icon(:rocket)} Select method to test in #{other_file[:name]}")
|
52
|
+
puts Tng::UI::Theme.center_text(header, @terminal_width)
|
53
|
+
|
54
|
+
@prompt.select(
|
55
|
+
"",
|
56
|
+
cycle: true,
|
57
|
+
per_page: 10,
|
58
|
+
filter: true,
|
59
|
+
symbols: { marker: Tng::UI::Theme.icon(:marker) }
|
60
|
+
) do |menu|
|
61
|
+
methods.each do |method|
|
62
|
+
menu.choice method[:name], method
|
63
|
+
end
|
64
|
+
menu.choice @pastel.public_send(Tng::UI::Theme.color(:secondary), "#{Tng::UI::Theme.icon(:back)} Back"), :back
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def show_no_methods_message(other_file)
|
69
|
+
error_msg = "#{@pastel.public_send(Tng::UI::Theme.color(:error)).bold("#{Tng::UI::Theme.icon(:error)} No methods found in #{other_file[:name]}")}\n#{@pastel.public_send(
|
70
|
+
Tng::UI::Theme.color(:muted), "File may be empty or have syntax errors"
|
71
|
+
)}"
|
72
|
+
puts Tng::UI::Theme.center_text(error_msg, @terminal_width)
|
73
|
+
@prompt.keypress(Tng::UI::Theme.center_text(
|
74
|
+
@pastel.public_send(Tng::UI::Theme.color(:muted),
|
75
|
+
"Press any key to continue..."), @terminal_width
|
76
|
+
))
|
77
|
+
end
|
78
|
+
end
|
@@ -38,25 +38,23 @@ class PostInstallBox
|
|
38
38
|
pastel.public_send(Tng::UI::Theme.color(:primary), "Interactive mode:"),
|
39
39
|
pastel.public_send(Tng::UI::Theme.color(:success),
|
40
40
|
"#{Tng::UI::Theme.icon(:bullet)} bundle exec tng") + pastel.public_send(Tng::UI::Theme.color(:muted),
|
41
|
-
" -
|
41
|
+
" - Interactive CLI with method selection"),
|
42
42
|
"",
|
43
|
-
pastel.public_send(Tng::UI::Theme.color(:primary), "
|
43
|
+
pastel.public_send(Tng::UI::Theme.color(:primary), "Features:"),
|
44
44
|
pastel.public_send(Tng::UI::Theme.color(:success),
|
45
|
-
"#{Tng::UI::Theme.icon(:bullet)}
|
46
|
-
" - Short"),
|
45
|
+
"#{Tng::UI::Theme.icon(:bullet)} Test 20+ file types: Controllers, Models, Services + Jobs, Helpers, Lib, Policies, Presenters, Mailers, GraphQL, and more"),
|
47
46
|
pastel.public_send(Tng::UI::Theme.color(:success),
|
48
|
-
"#{Tng::UI::Theme.icon(:bullet)}
|
49
|
-
" - Mixed"),
|
47
|
+
"#{Tng::UI::Theme.icon(:bullet)} Select specific methods to test"),
|
50
48
|
pastel.public_send(Tng::UI::Theme.color(:success),
|
51
|
-
"#{Tng::UI::Theme.icon(:bullet)}
|
49
|
+
"#{Tng::UI::Theme.icon(:bullet)} Search and filter through methods"),
|
52
50
|
"",
|
53
51
|
pastel.public_send(Tng::UI::Theme.color(:primary), "Help:"),
|
54
52
|
pastel.public_send(Tng::UI::Theme.color(:success),
|
55
53
|
"#{Tng::UI::Theme.icon(:bullet)} bundle exec tng --help") + pastel.public_send(Tng::UI::Theme.color(:muted),
|
56
|
-
" - Show
|
54
|
+
" - Show help information"),
|
57
55
|
"",
|
58
56
|
pastel.public_send(Tng::UI::Theme.color(:muted),
|
59
|
-
"#{Tng::UI::Theme.icon(:lightbulb)}
|
57
|
+
"#{Tng::UI::Theme.icon(:lightbulb)} Generate tests for individual methods with precision")
|
60
58
|
].join("\n")
|
61
59
|
end
|
62
60
|
|
@@ -36,23 +36,6 @@ class ServiceTestFlowDisplay
|
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
39
|
-
def select_test_option(service_choice)
|
40
|
-
header = @pastel.public_send(Tng::UI::Theme.color(:primary)).bold("#{Tng::UI::Theme.icon(:rocket)} Test Generation Options for #{service_choice[:name]}")
|
41
|
-
puts Tng::UI::Theme.center_text(header, @terminal_width)
|
42
|
-
puts
|
43
|
-
|
44
|
-
@prompt.select(
|
45
|
-
@pastel.public_send(Tng::UI::Theme.color(:primary), "Choose test generation type:"),
|
46
|
-
cycle: true,
|
47
|
-
symbols: { marker: Tng::UI::Theme.icon(:marker) }
|
48
|
-
) do |menu|
|
49
|
-
menu.choice @pastel.public_send(Tng::UI::Theme.color(:success), "Generate all possible tests"),
|
50
|
-
:all_possible_tests
|
51
|
-
menu.choice @pastel.public_send(Tng::UI::Theme.color(:info), "Generate per method"), :per_method
|
52
|
-
menu.choice @pastel.public_send(Tng::UI::Theme.color(:secondary), "#{Tng::UI::Theme.icon(:back)} Back"), :back
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
39
|
def show_no_services_message
|
57
40
|
error_msg = "#{@pastel.public_send(Tng::UI::Theme.color(:error)).bold("#{Tng::UI::Theme.icon(:error)} No services found in your application")}\n#{@pastel.public_send(
|
58
41
|
Tng::UI::Theme.color(:muted), "Make sure you have services in app/services/ or app/service/"
|