tng 0.1.6 → 0.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.
@@ -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 run_for_controller(controller)
18
- payload = {
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
- def run_for_model(model)
35
- payload = {
36
- model_test: Tng::Analyzers::Model.read_test_file_for_model(model[:path]),
37
- machine_info: @machine_info,
38
- object_fixture_data: Tng::Utils.fixture_content,
39
- ast: Tng::Analyzers::Model.value_for_model(model[:path]),
40
- test_type: "model",
41
- model: model,
42
- auth_patterns: Tng::Services::UserAppConfig.config_with_source,
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
- def run_for_service(service)
50
- payload = {
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
- def run_for_controller_method(controller, method_info)
62
- payload = {
63
- controller_test: Tng::Analyzers::Controller.read_test_file_for_controller(controller[:path]),
64
- object_fixture_data: Tng::Utils.fixture_content,
65
- machine_info: @machine_info,
66
- ast: Tng::Analyzers::Controller.value_for_controller(controller[:path]),
67
- test_type: "controller_method",
68
- controller: controller,
69
- method: method_info,
70
- routes: Tng::Analyzers::Controller.routes_for_controller(controller[:path]),
71
- auth_patterns: Tng::Services::UserAppConfig.config_with_source,
72
- model_info: Tng::Analyzers::Controller.model_info_for_controller(controller[:path]),
73
- parents: Tng::Analyzers::Controller.parents_for_controller(controller)
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
- payload = {
81
- model_test: Tng::Analyzers::Model.read_test_file_for_model(model[:path]),
82
- machine_info: @machine_info,
83
- object_fixture_data: Tng::Utils.fixture_content,
84
- ast: Tng::Analyzers::Model.value_for_model(model[:path]),
85
- test_type: "model_method",
86
- model: model,
87
- method: method_info,
88
- auth_patterns: Tng::Services::UserAppConfig.config_with_source,
89
- model_connections: Tng::Analyzers::Model.model_connections(model)
90
- }
91
-
92
- send_request_and_save_test(payload)
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
- payload = {
97
- service_test: Tng::Analyzers::Service.read_test_file_for_service(service[:path]),
98
- machine_info: @machine_info,
99
- object_fixture_data: Tng::Utils.fixture_content,
100
- ast: Tng::Analyzers::Service.value_for_service(service[:path]),
101
- test_type: "service_method",
102
- service: service,
103
- method: method_info,
104
- auth_patterns: Tng::Services::UserAppConfig.config_with_source
105
- }
106
-
107
- send_request_and_save_test(payload)
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("Request failed with error response") if debug_enabled?
119
- return handle_request_error(response)
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
- debug_log("Request successful, status: #{response.status}") if debug_enabled?
174
+ job_data = JSON.parse(response.body)
175
+ job_id = job_data["job_id"]
123
176
 
124
- test_content = response.body.to_s
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
- debug_log("Received test content, length: #{test_content.length}") if debug_enabled?
131
- Tng::Utils.save_test_file(test_content)
179
+ job_id
132
180
  rescue JSON::ParserError => e
133
- debug_log("JSON parsing failed: #{e.message}") if debug_enabled?
134
- puts "❌ Failed to parse generated tests via API: #{e.message}"
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 handle_request_error(response)
139
- debug_log("Handling request error: #{response.error&.message}") if debug_enabled?
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
- def handle_empty_response
145
- debug_log("Handling empty response") if debug_enabled?
146
- puts "❌ Failed to generate test via API"
147
- nil
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?
@@ -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 nil if response.is_a?(HTTPX::ErrorResponse)
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 nil unless node.is_a?(Prism::Node)
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 nil unless file_path && method_name
54
+ return unless file_path && method_name
55
55
 
56
56
  full_path = Rails.root.join(file_path)
57
- return nil unless File.exist?(full_path)
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 nil unless method_node
63
+ return unless method_node
64
64
 
65
- # Extract the method source from the original file content
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
@@ -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(:primary),
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), "#{Tng::UI::Theme.icon(:bullet)} Controller test generation"),
29
- @pastel.public_send(Tng::UI::Theme.color(:muted), "#{Tng::UI::Theme.icon(:bullet)} Model test generation"),
30
- @pastel.public_send(Tng::UI::Theme.color(:muted), "#{Tng::UI::Theme.icon(:bullet)} Service test generation"),
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
- " - Full interactive CLI"),
41
+ " - Interactive CLI with method selection"),
42
42
  "",
43
- pastel.public_send(Tng::UI::Theme.color(:primary), "Direct generation:"),
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)} bundle exec tng -t c -f ping") + pastel.public_send(Tng::UI::Theme.color(:muted),
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)} bundle exec tng -t=c f=ping") + pastel.public_send(Tng::UI::Theme.color(:muted),
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)} bundle exec tng --type=controller --file=ping"),
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 all options"),
54
+ " - Show help information"),
57
55
  "",
58
56
  pastel.public_send(Tng::UI::Theme.color(:muted),
59
- "#{Tng::UI::Theme.icon(:lightbulb)} Use -t c for controller, -t m for model")
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/"