tng 0.3.0 → 0.3.3

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.
data/bin/tng CHANGED
@@ -11,30 +11,13 @@ require "tng/services/direct_generation"
11
11
  require "tng/services/extract_methods"
12
12
  require "tng/services/file_type_detector"
13
13
 
14
- require "tty-prompt"
15
- require "tty-spinner"
16
- require "tty-box"
17
- require "tty-table"
18
- require "tty-progressbar"
19
14
  require "tty-screen"
20
15
  require "tty-option"
21
16
  require "pastel"
22
- require "tng/ui/show_help"
23
- require "tng/ui/post_install_box"
24
17
  require "tng/ui/theme"
25
- require "tng/ui/system_status_display"
26
- require "tng/ui/configuration_display"
27
- require "tng/ui/authentication_warning_display"
28
- require "tng/ui/display_banner"
29
- require "tng/ui/user_stats_display"
30
- require "tng/ui/about_display"
31
18
  require "tng/services/testng"
32
- require "tng/ui/goodbye_display"
19
+ require "tng/ui/go_ui_session"
33
20
  require "tng/api/http_client"
34
- require "tng/ui/controller_test_flow_display"
35
- require "tng/ui/model_test_flow_display"
36
- require "tng/ui/service_test_flow_display"
37
- require "tng/ui/other_test_flow_display"
38
21
  require "tng/analyzers/controller"
39
22
  require "tng/analyzers/model"
40
23
  require "tng/analyzers/service"
@@ -81,8 +64,8 @@ class CLI
81
64
  end
82
65
 
83
66
  def initialize
84
- @prompt = TTY::Prompt.new
85
67
  @pastel = Pastel.new
68
+ @go_ui = Tng::UI::GoUISession.new
86
69
  @terminal_width = begin
87
70
  TTY::Screen.width
88
71
  rescue StandardError
@@ -99,8 +82,7 @@ class CLI
99
82
  parse(normalized_argv)
100
83
 
101
84
  if params[:help]
102
- help_display = ShowHelp.new(@pastel, Tng::VERSION)
103
- puts help_display.render
85
+ @go_ui.show_help(Tng::VERSION)
104
86
  return
105
87
  end
106
88
 
@@ -121,8 +103,10 @@ class CLI
121
103
  return unless check_configuration
122
104
 
123
105
  check_system_status
106
+ return unless check_authentication
107
+
124
108
  clear_screen
125
- puts DisplayBanner.new(@pastel, Tng::VERSION).render
109
+ @go_ui.show_banner(Tng::VERSION)
126
110
  main_menu
127
111
  end
128
112
  end
@@ -179,309 +163,193 @@ class CLI
179
163
  end
180
164
 
181
165
  def main_menu
182
- loop do
183
- clear_screen
184
-
185
- puts DisplayBanner.new(@pastel, Tng::VERSION).render
166
+ @go_ui.start
186
167
 
187
- begin
188
- choice = @prompt.select(
189
- center_text("Select an option:"),
190
- cycle: true,
191
- per_page: 5,
192
- symbols: { marker: "→" }
193
- ) do |menu|
194
- menu.choice "Generate tests", :tests
195
- menu.choice "User stats", :stats
196
- menu.choice "About", :about
197
- menu.choice "Exit", :exit, key: "q"
198
- end
168
+ loop do
169
+ choice = @go_ui.show_menu
199
170
 
200
- handle_choice(choice)
201
- rescue TTY::Reader::InputInterrupt, Interrupt
202
- choice = :exit
171
+ case choice
172
+ when "tests"
173
+ handle_test_generation
174
+ when "stats"
175
+ user_stats
176
+ when "about"
177
+ show_about
178
+ when "exit"
179
+ show_goodbye
180
+ break
181
+ else
182
+ break
203
183
  end
204
-
205
- break if choice == :exit
206
184
  end
207
- end
208
185
 
209
- def handle_choice(choice)
210
- case choice
211
- when :tests
212
- handle_test_generation
213
- when :stats
214
- user_stats
215
- when :about
216
- show_about
217
- when :exit
218
- show_goodbye
219
- end
186
+ @go_ui.stop
220
187
  end
221
188
 
222
189
  def handle_test_generation
223
- choice = @prompt.select(
224
- center_text("What would you like to test?"),
225
- cycle: true,
226
- per_page: 5,
227
- symbols: { marker: "→" }
228
- ) do |menu|
229
- menu.choice "Controller", :controller
230
- menu.choice "Model", :model
231
- menu.choice "Service", :service
232
- menu.choice "Other", :other
233
- menu.choice @pastel.decorate("#{Tng::UI::Theme.icon(:back)} Back", Tng::UI::Theme.color(:info)), :back
234
- end
190
+ choice = @go_ui.show_test_type_menu
235
191
 
236
192
  case choice
237
- when :controller
193
+ when "controller"
238
194
  generate_controller_tests
239
- when :model
195
+ when "model"
240
196
  generate_model_tests
241
- when :service
197
+ when "service"
242
198
  generate_service_tests
243
- when :other
199
+ when "other"
244
200
  generate_other_tests
245
201
  end
246
202
  end
247
203
 
248
204
  def generate_controller_tests
249
- header = @pastel.decorate("Scanning for controllers...", Tng::UI::Theme.color(:primary))
250
- puts center_text(header)
251
-
252
- spinner = TTY::Spinner.new(
253
- center_text("[:spinner] Analyzing controllers..."),
254
- format: :dots,
255
- success_mark: @pastel.decorate(Tng::UI::Theme.icon(:success), Tng::UI::Theme.color(:success)),
256
- error_mark: @pastel.decorate(Tng::UI::Theme.icon(:error), Tng::UI::Theme.color(:error))
257
- )
258
- spinner.auto_spin
259
- controllers = Tng::Analyzers::Controller.files_in_dir("app/controllers").map do |file|
260
- relative_path = file[:path].gsub(%r{^.*app/controllers/}, "").gsub(".rb", "")
261
- namespaced_name = relative_path.split("/").map(&:camelize).join("::")
205
+ controllers = nil
262
206
 
263
- {
264
- name: namespaced_name,
265
- path: file[:path]
266
- }
267
- end
207
+ @go_ui.show_spinner("Analyzing controllers...") do
208
+ controllers = Tng::Analyzers::Controller.files_in_dir("app/controllers").map do |file|
209
+ relative_path = file[:path].gsub(%r{^.*app/controllers/}, "").gsub(".rb", "")
210
+ namespaced_name = relative_path.split("/").map(&:camelize).join("::")
268
211
 
269
- flow_display = ControllerTestFlowDisplay.new(@prompt, @pastel)
212
+ { name: namespaced_name, path: file[:path] }
213
+ end
214
+ { success: true, message: "Found #{controllers.length} controllers" }
215
+ end
270
216
 
271
217
  if controllers.empty?
272
- spinner.error
273
- flow_display.show_no_controllers_message
218
+ @go_ui.show_no_items("controllers")
274
219
  return
275
220
  end
276
221
 
277
- success_msg = @pastel.decorate("Found #{controllers.length} controllers", Tng::UI::Theme.color(:success))
278
- spinner.success(center_text(success_msg))
222
+ items = controllers.map { |c| { name: c[:name], path: c[:path] } }
223
+ selected_name = @go_ui.show_list_view("Select Controller", items)
224
+ return if selected_name == "back"
279
225
 
280
- controller_choice = flow_display.select_controller(controllers)
281
- return if controller_choice == :back
226
+ controller_choice = controllers.find { |c| c[:name] == selected_name }
227
+ return unless controller_choice
282
228
 
283
229
  show_controller_test_options(controller_choice)
284
230
  end
285
231
 
286
232
  def generate_model_tests
287
- header = @pastel.decorate("Scanning for models...", Tng::UI::Theme.color(:primary))
288
- puts center_text(header)
289
-
290
- spinner = TTY::Spinner.new(
291
- center_text("[:spinner] Analyzing models..."),
292
- format: :dots,
293
- success_mark: @pastel.decorate(Tng::UI::Theme.icon(:success), Tng::UI::Theme.color(:success)),
294
- error_mark: @pastel.decorate(Tng::UI::Theme.icon(:error), Tng::UI::Theme.color(:error))
295
- )
296
- spinner.auto_spin
233
+ models = nil
297
234
 
298
- models = Tng::Analyzers::Model.files_in_dir("app/models").map do |file|
299
- relative_path = file[:path].gsub(%r{^.*app/models/}, "").gsub(".rb", "")
300
- namespaced_name = relative_path.split("/").map(&:camelize).join("::")
235
+ @go_ui.show_spinner("Analyzing models...") do
236
+ models = Tng::Analyzers::Model.files_in_dir("app/models").map do |file|
237
+ relative_path = file[:path].gsub(%r{^.*app/models/}, "").gsub(".rb", "")
238
+ namespaced_name = relative_path.split("/").map(&:camelize).join("::")
301
239
 
302
- {
303
- name: namespaced_name,
304
- path: file[:path]
305
- }
240
+ { name: namespaced_name, path: file[:path] }
241
+ end
242
+ { success: true, message: "Found #{models.length} models" }
306
243
  end
307
244
 
308
- flow_display = ModelTestFlowDisplay.new(@prompt, @pastel)
309
-
310
245
  if models.empty?
311
- spinner.error
312
- flow_display.show_no_models_message
246
+ @go_ui.show_no_items("models")
313
247
  return
314
248
  end
315
249
 
316
- success_msg = @pastel.decorate("Found #{models.length} models", Tng::UI::Theme.color(:success))
317
- spinner.success(center_text(success_msg))
250
+ items = models.map { |m| { name: m[:name], path: m[:path] } }
251
+ selected_name = @go_ui.show_list_view("Select Model", items)
252
+ return if selected_name == "back"
318
253
 
319
- model_choice = flow_display.select_model(models)
320
- return if model_choice == :back
254
+ model_choice = models.find { |m| m[:name] == selected_name }
255
+ return unless model_choice
321
256
 
322
257
  show_model_test_options(model_choice)
323
258
  end
324
259
 
325
260
  def generate_service_tests
326
- header = @pastel.decorate("Scanning for services...", Tng::UI::Theme.color(:primary))
327
- puts center_text(header)
328
-
329
- spinner = TTY::Spinner.new(
330
- center_text("[:spinner] Analyzing services..."),
331
- format: :dots,
332
- success_mark: @pastel.decorate(Tng::UI::Theme.icon(:success), Tng::UI::Theme.color(:success)),
333
- error_mark: @pastel.decorate(Tng::UI::Theme.icon(:error), Tng::UI::Theme.color(:error))
334
- )
335
- spinner.auto_spin
261
+ services = nil
336
262
 
337
- services = Tng::Analyzers::Service.files_in_dir.map do |file|
338
- relative_path = file[:path].gsub(%r{^.*app/services?/}, "").gsub(".rb", "")
339
- namespaced_name = relative_path.split("/").map(&:camelize).join("::")
263
+ @go_ui.show_spinner("Analyzing services...") do
264
+ services = Tng::Analyzers::Service.files_in_dir.map do |file|
265
+ relative_path = file[:path].gsub(%r{^.*app/services?/}, "").gsub(".rb", "")
266
+ namespaced_name = relative_path.split("/").map(&:camelize).join("::")
340
267
 
341
- {
342
- name: namespaced_name,
343
- path: file[:path]
344
- }
268
+ { name: namespaced_name, path: file[:path] }
269
+ end
270
+ { success: true, message: "Found #{services.length} services" }
345
271
  end
346
272
 
347
- flow_display = ServiceTestFlowDisplay.new(@prompt, @pastel)
348
-
349
273
  if services.empty?
350
- spinner.error
351
- flow_display.show_no_services_message
274
+ @go_ui.show_no_items("services")
352
275
  return
353
276
  end
354
277
 
355
- success_msg = @pastel.decorate("Found #{services.length} services", Tng::UI::Theme.color(:success))
356
- spinner.success(center_text(success_msg))
278
+ items = services.map { |s| { name: s[:name], path: s[:path] } }
279
+ selected_name = @go_ui.show_list_view("Select Service", items)
280
+ return if selected_name == "back"
357
281
 
358
- service_choice = flow_display.select_service(services)
359
- return if service_choice == :back
282
+ service_choice = services.find { |s| s[:name] == selected_name }
283
+ return unless service_choice
360
284
 
361
285
  show_service_test_options(service_choice)
362
286
  end
363
287
 
364
288
  def generate_other_tests
365
- header = @pastel.decorate("Scanning for other files...", Tng::UI::Theme.color(:primary))
366
- puts center_text(header)
367
-
368
- spinner = TTY::Spinner.new(
369
- center_text("[:spinner] Analyzing other files..."),
370
- format: :dots,
371
- success_mark: @pastel.decorate(Tng::UI::Theme.icon(:success), Tng::UI::Theme.color(:success)),
372
- error_mark: @pastel.decorate(Tng::UI::Theme.icon(:error), Tng::UI::Theme.color(:error))
373
- )
374
- spinner.auto_spin
289
+ other_files = nil
375
290
 
376
- other_files = Tng::Analyzers::Other.files_in_dir.map do |file|
377
- relative_path = file[:relative_path].gsub(".rb", "")
378
- namespaced_name = relative_path.split("/").map(&:camelize).join("::")
291
+ @go_ui.show_spinner("Analyzing other files...") do
292
+ other_files = Tng::Analyzers::Other.files_in_dir.map do |file|
293
+ relative_path = file[:relative_path].gsub(".rb", "")
294
+ namespaced_name = relative_path.split("/").map(&:camelize).join("::")
379
295
 
380
- {
381
- name: namespaced_name,
382
- path: file[:path],
383
- type: file[:type]
384
- }
296
+ { name: namespaced_name, path: file[:path], type: file[:type] }
297
+ end
298
+ { success: true, message: "Found #{other_files.length} other files" }
385
299
  end
386
300
 
387
- flow_display = OtherTestFlowDisplay.new(@prompt, @pastel)
388
-
389
301
  if other_files.empty?
390
- spinner.error
391
- flow_display.show_no_other_files_message
302
+ @go_ui.show_no_items("other files")
392
303
  return
393
304
  end
394
305
 
395
- success_msg = @pastel.decorate("Found #{other_files.length} other files", Tng::UI::Theme.color(:success))
396
- spinner.success(center_text(success_msg))
306
+ items = other_files.map { |f| { name: f[:name], path: f[:path] } }
307
+ selected_name = @go_ui.show_list_view("Select File", items)
308
+ return if selected_name == "back"
397
309
 
398
- other_choice = flow_display.select_other_file(other_files)
399
- return if other_choice == :back
310
+ other_choice = other_files.find { |f| f[:name] == selected_name }
311
+ return unless other_choice
400
312
 
401
313
  show_other_test_options(other_choice)
402
314
  end
403
315
 
404
316
  def show_controller_method_selection(controller)
405
- header = @pastel.decorate("#{Tng::UI::Theme.icon(:info)} Analyzing methods for #{controller[:name]}", Tng::UI::Theme.color(:primary))
406
- puts center_text(header)
407
-
408
317
  methods = extract_controller_methods(controller)
409
- flow_display = ControllerTestFlowDisplay.new(@prompt, @pastel)
410
318
 
411
319
  if methods.empty?
412
- flow_display.show_no_methods_message(controller)
320
+ @go_ui.show_no_items("methods in #{controller[:name]}")
413
321
  return
414
322
  end
415
323
 
416
- method_choice = flow_display.select_controller_method(controller, methods)
324
+ items = methods.map { |m| { name: m[:name], path: controller[:name] } }
325
+ selected_name = @go_ui.show_list_view("Select Method in #{controller[:name]}", items)
326
+ return if selected_name == "back"
417
327
 
418
- return if method_choice == :back
328
+ method_choice = methods.find { |m| m[:name] == selected_name }
329
+ return unless method_choice
419
330
 
420
331
  generate_test_for_controller_method(controller, method_choice)
421
332
  end
422
333
 
423
334
  def generate_test_for_controller_method(controller, method_info)
424
- header = @pastel.decorate(
425
- "#{Tng::UI::Theme.icon(:marker)} Generating test for #{controller[:name]}##{method_info[:name]}", Tng::UI::Theme.color(:primary)
426
- )
427
- puts center_text(header)
428
-
429
- analysis_msg = @pastel.decorate("Analyzing method structure...", Tng::UI::Theme.color(:muted))
430
- puts center_text(analysis_msg)
431
-
432
- progress_padding = [(@terminal_width - 56) / 2, 0].max # Account for progress bar width
433
- progress_bar = TTY::ProgressBar.new(
434
- "#{" " * progress_padding}[:bar] :percent :current/:total",
435
- total: 5,
436
- bar_format: :box,
437
- complete: @pastel.decorate("ā–ˆ", Tng::UI::Theme.color(:success)),
438
- incomplete: @pastel.decorate("ā–‘", Tng::UI::Theme.color(:muted)),
439
- width: 40
440
- )
335
+ result = nil
441
336
 
442
- step_msg = @pastel.decorate("Parsing method details...", Tng::UI::Theme.color(:muted))
443
- puts center_text(step_msg)
444
- progress_bar.advance(1)
445
- sleep(0.3)
446
-
447
- step_msg = @pastel.decorate("Analyzing method dependencies...", Tng::UI::Theme.color(:muted))
448
- puts center_text(step_msg)
449
- progress_bar.advance(1)
450
- sleep(0.3)
451
-
452
- step_msg = @pastel.decorate("Extracting method context...", Tng::UI::Theme.color(:muted))
453
- puts center_text(step_msg)
454
- progress_bar.advance(1)
455
- sleep(0.3)
456
-
457
- step_msg = @pastel.decorate("Generating method test code...", Tng::UI::Theme.color(:muted))
458
- puts center_text(step_msg)
459
- progress_bar.advance(1)
460
- result = Tng::Services::TestGenerator.new(@http_client).run_for_controller_method(controller, method_info)
461
- sleep(0.5)
462
-
463
- step_msg = @pastel.decorate("Writing test file...", Tng::UI::Theme.color(:muted))
464
- puts center_text(step_msg)
465
- progress_bar.advance(1)
466
- sleep(0.2)
467
-
468
- # Show completion message with test count and timing
469
- if result && result[:test_count]
470
- count_msg = result[:test_count] == 1 ? "1 test" : "#{result[:test_count]} tests"
471
- time_msg = result[:generation_time] ? " in #{Tng::Utils.format_generation_time(result[:generation_time])}" : ""
472
- completion_msg = @pastel.decorate(
473
- "#{Tng::UI::Theme.icon(:success)} Generated #{count_msg} for controller method#{time_msg}!", Tng::UI::Theme.color(:success)
474
- )
475
- else
476
- completion_msg = @pastel.decorate("#{Tng::UI::Theme.icon(:success)} Method test generation completed!", Tng::UI::Theme.color(:success))
477
- end
478
- puts center_text(completion_msg)
337
+ @go_ui.show_progress("Generating test for #{controller[:name]}##{method_info[:name]}") do |progress|
338
+ progress.update("Parsing method details...", 20)
339
+ progress.update("Analyzing method dependencies...", 40)
340
+ progress.update("Extracting method context...", 60)
341
+ progress.update("Generating method test code...", 80)
479
342
 
480
- if result && result[:file_path]
481
- show_post_generation_menu(result)
482
- else
483
- @prompt.keypress(center_text(@pastel.decorate("Press any key to continue...", Tng::UI::Theme.color(:muted))))
343
+ result = Tng::Services::TestGenerator.new(@http_client).run_for_controller_method(controller, method_info, progress: progress)
344
+
345
+ progress.update("Writing test file...", 100)
346
+
347
+ handle_generation_result(result)
484
348
  end
349
+
350
+ return @go_ui.show_auth_error(result[:message] || "Generation failed") if result&.dig(:error)
351
+
352
+ show_post_generation_menu(result) if result && result[:file_path]
485
353
  end
486
354
 
487
355
  def show_controller_test_options(controller_choice)
@@ -503,157 +371,77 @@ class CLI
503
371
  end
504
372
 
505
373
  def show_model_method_selection(model)
506
- header = @pastel.decorate("#{Tng::UI::Theme.icon(:info)} Analyzing methods for #{model[:name]}", Tng::UI::Theme.color(:primary))
507
- puts center_text(header)
508
-
509
374
  methods = extract_model_methods(model)
510
- flow_display = ModelTestFlowDisplay.new(@prompt, @pastel)
511
375
 
512
376
  if methods.empty?
513
- flow_display.show_no_methods_message(model)
377
+ @go_ui.show_no_items("methods in #{model[:name]}")
514
378
  return
515
379
  end
516
380
 
517
- method_choice = flow_display.select_model_method(model, methods)
381
+ items = methods.map { |m| { name: m[:name], path: model[:name] } }
382
+ selected_name = @go_ui.show_list_view("Select Method in #{model[:name]}", items)
383
+ return if selected_name == "back"
518
384
 
519
- return if method_choice == :back
385
+ method_choice = methods.find { |m| m[:name] == selected_name }
386
+ return unless method_choice
520
387
 
521
388
  generate_test_for_model_method(model, method_choice)
522
389
  end
523
390
 
524
391
  def generate_test_for_model_method(model, method_info)
525
- header = @pastel.decorate(
526
- "#{Tng::UI::Theme.icon(:marker)} Generating test for #{model[:name]}##{method_info[:name]}", Tng::UI::Theme.color(:primary)
527
- )
528
- puts center_text(header)
529
-
530
- analysis_msg = @pastel.decorate("Analyzing method structure...", Tng::UI::Theme.color(:muted))
531
- puts center_text(analysis_msg)
532
-
533
- progress_padding = [(@terminal_width - 56) / 2, 0].max # Account for progress bar width
534
- progress_bar = TTY::ProgressBar.new(
535
- "#{" " * progress_padding}[:bar] :percent :current/:total",
536
- total: 4,
537
- bar_format: :box,
538
- complete: @pastel.decorate("ā–ˆ", Tng::UI::Theme.color(:success)),
539
- incomplete: @pastel.decorate("ā–‘", Tng::UI::Theme.color(:muted)),
540
- width: 40
541
- )
392
+ result = nil
542
393
 
543
- step_msg = @pastel.decorate("Parsing method details...", Tng::UI::Theme.color(:muted))
544
- puts center_text(step_msg)
545
- progress_bar.advance(1)
546
- sleep(0.3)
547
-
548
- step_msg = @pastel.decorate("Analyzing method dependencies...", Tng::UI::Theme.color(:muted))
549
- puts center_text(step_msg)
550
- progress_bar.advance(1)
551
- sleep(0.3)
552
-
553
- step_msg = @pastel.decorate("Generating method test code...", Tng::UI::Theme.color(:muted))
554
- puts center_text(step_msg)
555
- progress_bar.advance(1)
556
- result = Tng::Services::TestGenerator.new(@http_client).run_for_model_method(model, method_info)
557
- sleep(0.5)
558
-
559
- step_msg = @pastel.decorate("Writing test file...", Tng::UI::Theme.color(:muted))
560
- puts center_text(step_msg)
561
- progress_bar.advance(1)
562
- sleep(0.2)
563
-
564
- # Show completion message with test count and timing
565
- if result && result[:test_count]
566
- count_msg = result[:test_count] == 1 ? "1 test" : "#{result[:test_count]} tests"
567
- time_msg = result[:generation_time] ? " in #{Tng::Utils.format_generation_time(result[:generation_time])}" : ""
568
- completion_msg = @pastel.decorate(
569
- "#{Tng::UI::Theme.icon(:success)} Generated #{count_msg} for model method#{time_msg}!", Tng::UI::Theme.color(:success)
570
- )
571
- else
572
- completion_msg = @pastel.decorate("#{Tng::UI::Theme.icon(:success)} Method test generation completed!", Tng::UI::Theme.color(:success))
573
- end
574
- puts center_text(completion_msg)
394
+ @go_ui.show_progress("Generating test for #{model[:name]}##{method_info[:name]}") do |progress|
395
+ progress.update("Parsing method details...", 25)
396
+ progress.update("Analyzing method dependencies...", 50)
397
+ progress.update("Generating method test code...", 75)
575
398
 
576
- if result && result[:file_path]
577
- show_post_generation_menu(result)
578
- else
579
- @prompt.keypress(center_text(@pastel.decorate("Press any key to continue...", Tng::UI::Theme.color(:muted))))
399
+ result = Tng::Services::TestGenerator.new(@http_client).run_for_model_method(model, method_info, progress: progress)
400
+
401
+ progress.update("Writing test file...", 100)
402
+
403
+ handle_generation_result(result)
580
404
  end
405
+
406
+ return @go_ui.show_auth_error(result[:message] || "Generation failed") if result&.dig(:error)
407
+
408
+ show_post_generation_menu(result) if result && result[:file_path]
581
409
  end
582
410
 
583
411
  def generate_test_for_service_method(service, method_info)
584
- header = @pastel.decorate(
585
- "#{Tng::UI::Theme.icon(:marker)} Generating test for #{service[:name]}##{method_info[:name]}", Tng::UI::Theme.color(:primary)
586
- )
587
- puts center_text(header)
588
-
589
- analysis_msg = @pastel.decorate("Analyzing method structure...", Tng::UI::Theme.color(:muted))
590
- puts center_text(analysis_msg)
591
-
592
- progress_padding = [(@terminal_width - 56) / 2, 0].max # Account for progress bar width
593
- progress_bar = TTY::ProgressBar.new(
594
- "#{" " * progress_padding}[:bar] :percent :current/:total",
595
- total: 4,
596
- bar_format: :box,
597
- complete: @pastel.decorate("ā–ˆ", Tng::UI::Theme.color(:success)),
598
- incomplete: @pastel.decorate("ā–‘", Tng::UI::Theme.color(:muted)),
599
- width: 40
600
- )
412
+ result = nil
601
413
 
602
- step_msg = @pastel.decorate("Parsing method details...", Tng::UI::Theme.color(:muted))
603
- puts center_text(step_msg)
604
- progress_bar.advance(1)
605
- sleep(0.3)
606
-
607
- step_msg = @pastel.decorate("Analyzing method dependencies...", Tng::UI::Theme.color(:muted))
608
- puts center_text(step_msg)
609
- progress_bar.advance(1)
610
- sleep(0.3)
611
-
612
- step_msg = @pastel.decorate("Generating method test code...", Tng::UI::Theme.color(:muted))
613
- puts center_text(step_msg)
614
- progress_bar.advance(1)
615
- result = Tng::Services::TestGenerator.new(@http_client).run_for_service_method(service, method_info)
616
- sleep(0.5)
617
-
618
- step_msg = @pastel.decorate("Writing test file...", Tng::UI::Theme.color(:muted))
619
- puts center_text(step_msg)
620
- progress_bar.advance(1)
621
- sleep(0.2)
622
-
623
- # Show completion message with test count and timing
624
- if result && result[:test_count]
625
- count_msg = result[:test_count] == 1 ? "1 test" : "#{result[:test_count]} tests"
626
- time_msg = result[:generation_time] ? " in #{Tng::Utils.format_generation_time(result[:generation_time])}" : ""
627
- completion_msg = @pastel.decorate(
628
- "#{Tng::UI::Theme.icon(:success)} Generated #{count_msg} for service method#{time_msg}!", Tng::UI::Theme.color(:success)
629
- )
630
- else
631
- completion_msg = @pastel.decorate("#{Tng::UI::Theme.icon(:success)} Method test generation completed!", Tng::UI::Theme.color(:success))
632
- end
633
- puts center_text(completion_msg)
414
+ @go_ui.show_progress("Generating test for #{service[:name]}##{method_info[:name]}") do |progress|
415
+ progress.update("Parsing method details...", 25)
416
+ progress.update("Analyzing method dependencies...", 50)
417
+ progress.update("Generating method test code...", 75)
634
418
 
635
- if result && result[:file_path]
636
- show_post_generation_menu(result)
637
- else
638
- @prompt.keypress(center_text(@pastel.decorate("Press any key to continue...", Tng::UI::Theme.color(:muted))))
419
+ result = Tng::Services::TestGenerator.new(@http_client).run_for_service_method(service, method_info, progress: progress)
420
+
421
+ progress.update("Writing test file...", 100)
422
+
423
+ handle_generation_result(result)
639
424
  end
425
+
426
+ return @go_ui.show_auth_error(result[:message] || "Generation failed") if result&.dig(:error)
427
+
428
+ show_post_generation_menu(result) if result && result[:file_path]
640
429
  end
641
430
 
642
431
  def show_service_method_selection(service)
643
- header = @pastel.decorate("#{Tng::UI::Theme.icon(:info)} Analyzing methods for #{service[:name]}", Tng::UI::Theme.color(:primary))
644
- puts center_text(header)
645
-
646
432
  methods = extract_service_methods(service)
647
- flow_display = ServiceTestFlowDisplay.new(@prompt, @pastel)
648
433
 
649
434
  if methods.empty?
650
- flow_display.show_no_methods_message(service)
435
+ @go_ui.show_no_items("methods in #{service[:name]}")
651
436
  return
652
437
  end
653
438
 
654
- method_choice = flow_display.select_service_method(service, methods)
439
+ items = methods.map { |m| { name: m[:name], path: service[:name] } }
440
+ selected_name = @go_ui.show_list_view("Select Method in #{service[:name]}", items)
441
+ return if selected_name == "back"
655
442
 
656
- return if method_choice == :back
443
+ method_choice = methods.find { |m| m[:name] == selected_name }
444
+ return unless method_choice
657
445
 
658
446
  generate_test_for_service_method(service, method_choice)
659
447
  end
@@ -665,87 +453,58 @@ class CLI
665
453
  end
666
454
 
667
455
  def show_other_method_selection(other_file)
668
- header = @pastel.decorate("#{Tng::UI::Theme.icon(:info)} Analyzing methods for #{other_file[:name]}", Tng::UI::Theme.color(:primary))
669
- puts center_text(header)
670
-
671
456
  methods = extract_other_methods(other_file)
672
- flow_display = OtherTestFlowDisplay.new(@prompt, @pastel)
673
457
 
674
458
  if methods.empty?
675
- flow_display.show_no_methods_message(other_file)
459
+ @go_ui.show_no_items("methods in #{other_file[:name]}")
676
460
  return
677
461
  end
678
462
 
679
- method_choice = flow_display.select_other_method(other_file, methods)
463
+ items = methods.map { |m| { name: m[:name], path: other_file[:name] } }
464
+ selected_name = @go_ui.show_list_view("Select Method in #{other_file[:name]}", items)
465
+ return if selected_name == "back"
680
466
 
681
- return if method_choice == :back
467
+ method_choice = methods.find { |m| m[:name] == selected_name }
468
+ return unless method_choice
682
469
 
683
470
  generate_test_for_other_method(other_file, method_choice)
684
471
  end
685
472
 
686
473
  def generate_test_for_other_method(other_file, method_info)
687
- header = @pastel.decorate(
688
- "#{Tng::UI::Theme.icon(:marker)} Generating test for #{other_file[:name]}##{method_info[:name]}", Tng::UI::Theme.color(:primary)
689
- )
690
- puts center_text(header)
691
-
692
- analysis_msg = @pastel.decorate("Analyzing method structure...", Tng::UI::Theme.color(:muted))
693
- puts center_text(analysis_msg)
694
-
695
- progress_padding = [(@terminal_width - 56) / 2, 0].max # Account for progress bar width
696
- progress_bar = TTY::ProgressBar.new(
697
- "#{" " * progress_padding}[:bar] :percent :current/:total",
698
- total: 4,
699
- bar_format: :box,
700
- complete: @pastel.decorate("ā–ˆ", Tng::UI::Theme.color(:success)),
701
- incomplete: @pastel.decorate("ā–‘", Tng::UI::Theme.color(:muted)),
702
- width: 40
703
- )
474
+ result = nil
704
475
 
705
- step_msg = @pastel.decorate("Parsing method details...", Tng::UI::Theme.color(:muted))
706
- puts center_text(step_msg)
707
- progress_bar.advance(1)
708
- sleep(0.3)
709
-
710
- step_msg = @pastel.decorate("Analyzing method dependencies...", Tng::UI::Theme.color(:muted))
711
- puts center_text(step_msg)
712
- progress_bar.advance(1)
713
- sleep(0.3)
714
-
715
- step_msg = @pastel.decorate("Generating method test code...", Tng::UI::Theme.color(:muted))
716
- puts center_text(step_msg)
717
- progress_bar.advance(1)
718
- result = Tng::Services::TestGenerator.new(@http_client).run_for_other_method(other_file, method_info)
719
- sleep(0.5)
720
-
721
- step_msg = @pastel.decorate("Writing test file...", Tng::UI::Theme.color(:muted))
722
- puts center_text(step_msg)
723
- progress_bar.advance(1)
724
- sleep(0.2)
725
-
726
- # Show completion message with test count and timing
727
- if result && result[:test_count]
728
- count_msg = result[:test_count] == 1 ? "1 test" : "#{result[:test_count]} tests"
729
- time_msg = result[:generation_time] ? " in #{Tng::Utils.format_generation_time(result[:generation_time])}" : ""
730
- completion_msg = @pastel.decorate(
731
- "#{Tng::UI::Theme.icon(:success)} Generated #{count_msg} for other method#{time_msg}!", Tng::UI::Theme.color(:success)
732
- )
733
- else
734
- completion_msg = @pastel.decorate("#{Tng::UI::Theme.icon(:success)} Method test generation completed!", Tng::UI::Theme.color(:success))
476
+ @go_ui.show_progress("Generating test for #{other_file[:name]}##{method_info[:name]}") do |progress|
477
+ progress.update("Parsing method details...", 25)
478
+ progress.update("Analyzing method dependencies...", 50)
479
+ progress.update("Generating method test code...", 75)
480
+
481
+ result = Tng::Services::TestGenerator.new(@http_client).run_for_other_method(other_file, method_info, progress: progress)
482
+
483
+ progress.update("Writing test file...", 100)
484
+
485
+ handle_generation_result(result)
735
486
  end
736
- puts center_text(completion_msg)
737
487
 
738
- if result && result[:file_path]
739
- show_post_generation_menu(result)
488
+ return @go_ui.show_auth_error(result[:message] || "Generation failed") if result&.dig(:error)
489
+
490
+ show_post_generation_menu(result) if result && result[:file_path]
491
+ end
492
+
493
+ def handle_generation_result(result)
494
+ if result&.dig(:error)
495
+ { message: result[:message] || "Generation failed", error: result[:error], result: result }
496
+ elsif result && result[:test_count]
497
+ count_msg = result[:test_count] == 1 ? "1 test" : "#{result[:test_count]} tests"
498
+ { message: "Generated #{count_msg}!", result: result }
740
499
  else
741
- @prompt.keypress(center_text(@pastel.decorate("Press any key to continue...", Tng::UI::Theme.color(:muted))))
500
+ { message: "Test generation completed!", result: result }
742
501
  end
743
502
  end
744
503
 
745
504
  # STATS
746
505
  def user_stats
747
506
  stats_data = @testng.get_user_stats
748
- UserStatsDisplay.new(@pastel, @prompt).display(stats_data)
507
+ @go_ui.show_stats(stats_data)
749
508
  end
750
509
 
751
510
  def check_system_status
@@ -755,13 +514,17 @@ class CLI
755
514
  end
756
515
 
757
516
  status = @testng.check_system_status
758
- status_ok = SystemStatusDisplay.new(@pastel, params).display(status)
759
517
 
760
- return if status_ok
518
+ if status[:status] == :ok
519
+ unless params[:file]
520
+ puts @pastel.decorate("#{Tng::UI::Theme.icon(:success)} System operational",
521
+ Tng::UI::Theme.color(:success))
522
+ end
523
+ return
524
+ end
761
525
 
762
- puts
763
- puts @pastel.decorate("Press any key to exit...", Tng::UI::Theme.color(:muted))
764
- $stdin.getch
526
+ # Show error via Go UI
527
+ @go_ui.show_system_status(status)
765
528
  exit(1)
766
529
  end
767
530
 
@@ -770,28 +533,74 @@ class CLI
770
533
 
771
534
  missing = Tng::Services::UserAppConfig.missing_config
772
535
 
773
- ConfigurationDisplay.new(@pastel).display_missing_config(missing)
536
+ @go_ui.show_config_missing(missing)
774
537
  false
775
538
  end
776
539
 
777
540
  def check_authentication_configuration
778
- auth_warning = AuthenticationWarningDisplay.new(@pastel)
779
- auth_missing = auth_warning.display_if_missing
541
+ return true unless authentication_config_missing?
780
542
 
781
- return false if auth_missing
543
+ choice = @go_ui.show_auth_warning(build_auth_missing_summary)
544
+
545
+ # "continue" means user wants to proceed, "cancel" means abort
546
+ choice == "continue"
547
+ end
782
548
 
783
- true
549
+ def authentication_config_missing?
550
+ return true if Tng.authentication_enabled && authentication_methods_empty?
551
+ return true if !authentication_methods_empty? && !authentication_methods_valid?
552
+
553
+ false
554
+ end
555
+
556
+ def authentication_methods_empty?
557
+ methods = Tng.authentication_methods
558
+ methods.nil? || methods.empty?
559
+ end
560
+
561
+ def authentication_methods_valid?
562
+ methods = Tng.authentication_methods
563
+ return false if methods.nil? || methods.empty?
564
+
565
+ methods.all? do |method|
566
+ method.is_a?(Hash) &&
567
+ method.key?(:method) && !method[:method].to_s.strip.empty? &&
568
+ method.key?(:file_location) && !method[:file_location].to_s.strip.empty? &&
569
+ method.key?(:auth_type) && !method[:auth_type].to_s.strip.empty?
570
+ end
571
+ end
572
+
573
+ def build_auth_missing_summary
574
+ issues = []
575
+ issues << "empty auth methods" if Tng.authentication_enabled && authentication_methods_empty?
576
+ issues << "invalid auth config" if !authentication_methods_empty? && !authentication_methods_valid?
577
+ issues << "auth disabled" unless Tng.authentication_enabled
578
+ issues.empty? ? "unknown issue" : issues.join(", ")
579
+ end
580
+
581
+ def check_authentication
582
+ puts @pastel.decorate("#{Tng::UI::Theme.icon(:info)} Verifying authentication...",
583
+ Tng::UI::Theme.color(:muted))
584
+
585
+ result = @http_client.verify_auth
586
+
587
+ if result[:valid]
588
+ true
589
+ else
590
+ @go_ui.show_auth_error(result[:error])
591
+ false
592
+ end
784
593
  end
785
594
 
786
595
  # ABOUT
787
596
  def show_about
788
- AboutDisplay.new(@pastel, @prompt, Tng::VERSION).display
597
+ @go_ui.show_about
789
598
  end
790
599
 
791
600
  # GOODBYE
792
601
  def show_goodbye
793
602
  clear_screen
794
- GoodbyeDisplay.new(@pastel).display
603
+ @go_ui.show_goodbye
795
604
  end
796
605
 
797
606
  def run_direct_generation
@@ -800,8 +609,8 @@ class CLI
800
609
  return unless check_authentication_configuration
801
610
 
802
611
  status = @testng.check_system_status
803
- unless SystemStatusDisplay.new(@pastel, params).display(status)
804
- puts @pastel.red("āŒ System status check failed")
612
+ unless status[:status] == :ok
613
+ @go_ui.show_system_status(status)
805
614
  return
806
615
  end
807
616
 
@@ -819,160 +628,57 @@ class CLI
819
628
  def show_post_generation_menu(result)
820
629
  is_direct_mode = params[:file]
821
630
 
631
+ # Direct mode - just print info and exit
822
632
  if is_direct_mode
823
- puts
824
- header = @pastel.decorate("#{Tng::UI::Theme.icon(:rocket)} Test Generated Successfully!", Tng::UI::Theme.color(:primary))
825
- puts center_text(header)
826
- puts
827
-
828
- info_msg = [
829
- @pastel.decorate("#{Tng::UI::Theme.icon(:config)} File: #{result[:file_path]}", Tng::UI::Theme.color(:info)),
830
- @pastel.decorate("#{Tng::UI::Theme.icon(:marker)} Run: #{result[:run_command]}", Tng::UI::Theme.color(:secondary))
831
- ].join("\n")
832
- puts center_text(info_msg)
833
- puts
834
-
835
- puts center_text(@pastel.decorate("#{Tng::UI::Theme.icon(:success)} Test generation completed successfully!", Tng::UI::Theme.color(:success)))
633
+ puts "\nšŸš€ Test Generated Successfully!"
634
+ puts "šŸ“ File: #{result[:file_path]}"
635
+ puts "ā–¶ļø Run: #{result[:run_command]}"
636
+ puts "āœ… Test generation completed successfully!"
836
637
  return
837
638
  end
838
639
 
839
- # Show menu regardless of test results
640
+ # Interactive mode - show Go UI menu
840
641
  loop do
841
- puts
842
- header = @pastel.decorate("#{Tng::UI::Theme.icon(:rocket)} Test Generated Successfully!", Tng::UI::Theme.color(:primary))
843
- puts center_text(header)
844
- puts
845
-
846
- info_msg = [
847
- @pastel.decorate("#{Tng::UI::Theme.icon(:config)} File: #{result[:file_path]}", Tng::UI::Theme.color(:info)),
848
- @pastel.decorate("#{Tng::UI::Theme.icon(:marker)} Run: #{result[:run_command]}", Tng::UI::Theme.color(:secondary))
849
- ].join("\n")
850
- puts center_text(info_msg)
851
- puts
852
-
853
- choice = @prompt.select(
854
- @pastel.decorate("What would you like to do?", Tng::UI::Theme.color(:primary)),
855
- cycle: true,
856
- symbols: { marker: Tng::UI::Theme.icon(:marker) }
857
- ) do |menu|
858
- menu.choice @pastel.decorate("#{Tng::UI::Theme.icon(:run)} Run tests", Tng::UI::Theme.color(:success)),
859
- :run_tests
860
- menu.choice @pastel.decorate("#{Tng::UI::Theme.icon(:config)} Copy run command", Tng::UI::Theme.color(:warning)),
861
- :copy_command
862
- menu.choice @pastel.decorate("#{Tng::UI::Theme.icon(:back)} Back to main menu", Tng::UI::Theme.color(:info)),
863
- :back
864
- end
642
+ choice = @go_ui.show_post_generation_menu(result[:file_path], result[:run_command])
865
643
 
866
644
  case choice
867
- when :run_tests
868
- run_and_show_test_results(result[:run_command])
869
- when :copy_command
645
+
646
+ when "copy_command"
870
647
  copy_to_clipboard(result[:run_command])
871
- when :back
648
+ when "back"
872
649
  break
873
- end
874
- end
875
- end
876
-
877
- def run_and_show_test_results(command)
878
- puts
879
- header = @pastel.decorate("#{Tng::UI::Theme.icon(:run)} Running tests...", Tng::UI::Theme.color(:primary))
880
- puts center_text(header)
881
-
882
- begin
883
- # Check if test file exists before running
884
- test_file = extract_test_file_from_command(command)
885
- unless test_file && File.exist?(test_file)
886
- error_msg = @pastel.decorate("#{Tng::UI::Theme.icon(:error)} Test file not found!", Tng::UI::Theme.color(:error))
887
- puts center_text(error_msg)
888
-
889
- if test_file
890
- file_msg = @pastel.decorate("File: #{test_file}", Tng::UI::Theme.color(:muted))
891
- puts center_text(file_msg)
892
- end
893
-
894
- suggestion_msg = @pastel.decorate("The test file may have been moved or deleted.", Tng::UI::Theme.color(:warning))
895
- puts center_text(suggestion_msg)
896
-
897
- @prompt.keypress(center_text(@pastel.decorate("Press any key to continue...", Tng::UI::Theme.color(:muted))))
898
- return
899
- end
900
-
901
- # Modify command to get cleaner output
902
- if command.include?("rspec")
903
- # Use JSON format for RSpec - much cleaner parsing
904
- json_command = command.sub(/rspec/, "rspec --format json --out /dev/null")
905
- output = `#{json_command} 2>&1`
906
- exit_code = $?.exitstatus
907
-
908
- # Check for common errors before parsing
909
- return if handle_test_execution_errors(output, exit_code)
910
-
911
- Tng::Utils.parse_rspec_json_results(output, exit_code, @pastel, @terminal_width)
912
650
  else
913
- # For Rails test, use current approach but with cleaner output
914
- output = `#{command} 2>&1`
915
- exit_code = $?.exitstatus
916
-
917
- # Check for common errors before parsing
918
- return if handle_test_execution_errors(output, exit_code)
919
-
920
- Tng::Utils.parse_minitest_results(output, exit_code, @pastel, @terminal_width)
651
+ break
921
652
  end
922
- rescue StandardError => e
923
- error_msg = @pastel.decorate("#{Tng::UI::Theme.icon(:error)} Error running tests!", Tng::UI::Theme.color(:error))
924
- puts center_text(error_msg)
925
-
926
- details_msg = @pastel.decorate("#{e.message}", Tng::UI::Theme.color(:muted))
927
- puts center_text(details_msg)
928
653
  end
929
-
930
- @prompt.keypress(center_text(@pastel.decorate("Press any key to continue...", Tng::UI::Theme.color(:muted))))
931
654
  end
932
655
 
933
- def handle_test_execution_errors(output, exit_code)
934
- # Return true if we handled an error (caller should return early)
935
- # Return false if no error was handled (caller should continue)
936
656
 
937
- # If tests failed to run (non-zero exit code or common error patterns)
938
- if !exit_code.zero? || output.include?("LoadError") || output.include?("cannot load such file") ||
939
- (output.include?("bundler") && output.include?("Could not find")) ||
940
- (output.include?("bin/rails") && (output.include?("require") || output.include?("load")))
941
657
 
942
- error_msg = @pastel.decorate("#{Tng::UI::Theme.icon(:error)} Tests failed to run", Tng::UI::Theme.color(:error))
943
- puts center_text(error_msg)
944
-
945
- @prompt.keypress(center_text(@pastel.decorate("Press any key to continue...", Tng::UI::Theme.color(:muted))))
946
- return true
658
+ def copy_to_clipboard(command)
659
+ if RUBY_PLATFORM.include?("darwin")
660
+ IO.popen("pbcopy", "w") { |io| io.print command }
661
+ @go_ui.show_clipboard_success(command)
662
+ elsif RUBY_PLATFORM.include?("linux")
663
+ begin
664
+ IO.popen("xclip -selection clipboard", "w") { |io| io.print command }
665
+ @go_ui.show_clipboard_success(command)
666
+ rescue Errno::ENOENT
667
+ puts "\nšŸ“‹ Copy this command:\n#{command}\n"
668
+ end
669
+ else
670
+ puts "\nšŸ“‹ Copy this command:\n#{command}\n"
947
671
  end
948
-
949
- false
950
672
  end
951
673
 
952
- def extract_test_file_from_command(command)
953
- # Extract test file path from commands like:
954
- # "bundle exec rails test test/controllers/foo_test.rb"
955
- # "bundle exec rspec spec/controllers/foo_spec.rb"
956
674
 
957
- if command.include?("rails test")
958
- # For Rails test commands
959
- match = command.match(/rails test\s+(.+?)(?:\s|$)/)
960
- match[1] if match
961
- elsif command.include?("rspec")
962
- # For RSpec commands
963
- match = command.match(/rspec\s+(.+?)(?:\s|$)/)
964
- match[1] if match
965
- else
966
- nil
967
- end
968
- end
969
675
 
970
676
  def initialize_config_and_clients
971
677
  @config_initialized = true
972
678
 
973
679
  unless Tng::Services::UserAppConfig.configured?
974
680
  missing = Tng::Services::UserAppConfig.missing_config
975
- ConfigurationDisplay.new(@pastel).display_missing_config(missing)
681
+ @go_ui.show_config_missing(missing)
976
682
  exit 1
977
683
  end
978
684