tng 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of tng might be problematic. Click here for more details.
- checksums.yaml +7 -0
- data/Gemfile +15 -0
- data/LICENSE.md +32 -0
- data/README.md +427 -0
- data/Rakefile +124 -0
- data/bin/load_dev +22 -0
- data/bin/tng +1122 -0
- data/binaries/tng.bundle +0 -0
- data/binaries/tng.so +0 -0
- data/lib/generators/tng/install_generator.rb +236 -0
- data/lib/tng/analyzers/controller.rb +114 -0
- data/lib/tng/analyzers/model.rb +122 -0
- data/lib/tng/analyzers/service.rb +149 -0
- data/lib/tng/api/http_client.rb +102 -0
- data/lib/tng/railtie.rb +11 -0
- data/lib/tng/services/test_generator.rb +159 -0
- data/lib/tng/services/testng.rb +100 -0
- data/lib/tng/services/user_app_config.rb +77 -0
- data/lib/tng/ui/about_display.rb +64 -0
- data/lib/tng/ui/authentication_warning_display.rb +172 -0
- data/lib/tng/ui/configuration_display.rb +52 -0
- data/lib/tng/ui/controller_test_flow_display.rb +96 -0
- data/lib/tng/ui/display_banner.rb +44 -0
- data/lib/tng/ui/goodbye_display.rb +41 -0
- data/lib/tng/ui/model_test_flow_display.rb +97 -0
- data/lib/tng/ui/post_install_box.rb +82 -0
- data/lib/tng/ui/service_test_flow_display.rb +95 -0
- data/lib/tng/ui/show_help.rb +124 -0
- data/lib/tng/ui/system_status_display.rb +128 -0
- data/lib/tng/ui/theme.rb +248 -0
- data/lib/tng/ui/user_stats_display.rb +160 -0
- data/lib/tng/utils.rb +263 -0
- data/lib/tng/version.rb +5 -0
- data/lib/tng.rb +308 -0
- data/tng.gemspec +56 -0
- metadata +289 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "tty-box"
|
|
4
|
+
require "pastel"
|
|
5
|
+
require "tty-screen"
|
|
6
|
+
require_relative "theme"
|
|
7
|
+
|
|
8
|
+
class ConfigurationDisplay
|
|
9
|
+
def initialize(pastel)
|
|
10
|
+
@pastel = pastel
|
|
11
|
+
@terminal_width = begin
|
|
12
|
+
TTY::Screen.width
|
|
13
|
+
rescue StandardError
|
|
14
|
+
80
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def display_missing_config(missing_config)
|
|
19
|
+
config_content = [
|
|
20
|
+
@pastel.public_send(Tng::UI::Theme.color(:error)).bold("Configuration Required"),
|
|
21
|
+
"",
|
|
22
|
+
@pastel.public_send(Tng::UI::Theme.color(:accent), "Missing required configuration:"),
|
|
23
|
+
missing_config.map do |key|
|
|
24
|
+
@pastel.public_send(Tng::UI::Theme.color(:secondary), "#{Tng::UI::Theme.icon(:bullet)} #{key}")
|
|
25
|
+
end.join("\n"),
|
|
26
|
+
"",
|
|
27
|
+
@pastel.public_send(Tng::UI::Theme.color(:secondary), "Quick fix:"),
|
|
28
|
+
@pastel.public_send(Tng::UI::Theme.color(:muted),
|
|
29
|
+
"#{Tng::UI::Theme.icon(:bullet)} Edit config/initializers/tng.rb"),
|
|
30
|
+
@pastel.public_send(Tng::UI::Theme.color(:muted),
|
|
31
|
+
"#{Tng::UI::Theme.icon(:bullet)} Set your API key and other required values"),
|
|
32
|
+
"",
|
|
33
|
+
@pastel.public_send(Tng::UI::Theme.color(:success), "Press Enter to continue after fixing"),
|
|
34
|
+
@pastel.public_send(Tng::UI::Theme.color(:error), "Press Ctrl+C to exit")
|
|
35
|
+
].join("\n")
|
|
36
|
+
|
|
37
|
+
box_width = Tng::UI::Theme.calculate_box_width(@terminal_width)
|
|
38
|
+
style = Tng::UI::Theme.box_style(:error)
|
|
39
|
+
|
|
40
|
+
config_box = TTY::Box.frame(
|
|
41
|
+
title: { top_left: " Config Required " },
|
|
42
|
+
style: style[:style],
|
|
43
|
+
padding: style[:padding],
|
|
44
|
+
width: box_width
|
|
45
|
+
) do
|
|
46
|
+
config_content
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
puts Tng::UI::Theme.center_box(config_box, box_width, @terminal_width)
|
|
50
|
+
puts
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
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 ControllerTestFlowDisplay
|
|
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_controller(controllers)
|
|
21
|
+
header = @pastel.public_send(Tng::UI::Theme.color(:primary)).bold("#{Tng::UI::Theme.icon(:config)} Select controller 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
|
+
controllers.each do |controller|
|
|
32
|
+
display_name = "#{controller[:name]} #{@pastel.public_send(Tng::UI::Theme.color(:muted),
|
|
33
|
+
"(#{controller[:path]})")}"
|
|
34
|
+
menu.choice display_name, controller
|
|
35
|
+
end
|
|
36
|
+
menu.choice @pastel.public_send(Tng::UI::Theme.color(:secondary), "#{Tng::UI::Theme.icon(:back)} Back"), :back
|
|
37
|
+
end
|
|
38
|
+
end
|
|
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
|
+
def show_no_controllers_message
|
|
58
|
+
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
|
+
Tng::UI::Theme.color(:muted), "Make sure you have controllers in app/controllers/"
|
|
60
|
+
)}"
|
|
61
|
+
puts Tng::UI::Theme.center_text(error_msg, @terminal_width)
|
|
62
|
+
@prompt.keypress(Tng::UI::Theme.center_text(
|
|
63
|
+
@pastel.public_send(Tng::UI::Theme.color(:muted),
|
|
64
|
+
"Press any key to continue..."), @terminal_width
|
|
65
|
+
))
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def select_controller_method(controller, methods)
|
|
69
|
+
header = @pastel.public_send(Tng::UI::Theme.color(:primary)).bold("#{Tng::UI::Theme.icon(:rocket)} Select method to test in #{controller[:name]}")
|
|
70
|
+
puts Tng::UI::Theme.center_text(header, @terminal_width)
|
|
71
|
+
|
|
72
|
+
@prompt.select(
|
|
73
|
+
"",
|
|
74
|
+
cycle: true,
|
|
75
|
+
per_page: 10,
|
|
76
|
+
filter: true,
|
|
77
|
+
symbols: { marker: Tng::UI::Theme.icon(:marker) }
|
|
78
|
+
) do |menu|
|
|
79
|
+
methods.each do |method|
|
|
80
|
+
menu.choice method[:name], method
|
|
81
|
+
end
|
|
82
|
+
menu.choice @pastel.public_send(Tng::UI::Theme.color(:secondary), "#{Tng::UI::Theme.icon(:back)} Back"), :back
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def show_no_methods_message(controller)
|
|
87
|
+
error_msg = "#{@pastel.public_send(Tng::UI::Theme.color(:error)).bold("#{Tng::UI::Theme.icon(:error)} No methods found in #{controller[:name]}")}\n#{@pastel.public_send(
|
|
88
|
+
Tng::UI::Theme.color(:muted), "Controller may be empty or have syntax errors"
|
|
89
|
+
)}"
|
|
90
|
+
puts Tng::UI::Theme.center_text(error_msg, @terminal_width)
|
|
91
|
+
@prompt.keypress(Tng::UI::Theme.center_text(
|
|
92
|
+
@pastel.public_send(Tng::UI::Theme.color(:muted),
|
|
93
|
+
"Press any key to continue..."), @terminal_width
|
|
94
|
+
))
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
require "tty-box"
|
|
2
|
+
require "pastel"
|
|
3
|
+
require "tty-screen"
|
|
4
|
+
require_relative "theme"
|
|
5
|
+
|
|
6
|
+
class DisplayBanner
|
|
7
|
+
attr_reader :pastel, :version, :terminal_width
|
|
8
|
+
|
|
9
|
+
def initialize(pastel, version)
|
|
10
|
+
@pastel = pastel
|
|
11
|
+
@version = version
|
|
12
|
+
@terminal_width = begin
|
|
13
|
+
TTY::Screen.width
|
|
14
|
+
rescue StandardError
|
|
15
|
+
80
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def render
|
|
20
|
+
banner_content = [
|
|
21
|
+
pastel.public_send(Tng::UI::Theme.color(:secondary)).bold("Tng - LLM-Powered Rails Test Generator"),
|
|
22
|
+
pastel.public_send(Tng::UI::Theme.color(:muted), "Version: #{version}"),
|
|
23
|
+
"",
|
|
24
|
+
pastel.public_send(Tng::UI::Theme.color(:accent), "Generate comprehensive tests for your Rails application"),
|
|
25
|
+
pastel.public_send(Tng::UI::Theme.color(:muted), "Powered by LLM and static analysis")
|
|
26
|
+
].join("\n")
|
|
27
|
+
|
|
28
|
+
box_width = 64
|
|
29
|
+
box_width = terminal_width if box_width > terminal_width
|
|
30
|
+
style = Tng::UI::Theme.box_style(:default)
|
|
31
|
+
|
|
32
|
+
box = TTY::Box.frame(
|
|
33
|
+
title: { top_left: " Tng ", bottom_right: " Ready " },
|
|
34
|
+
style: style[:style],
|
|
35
|
+
padding: style[:padding],
|
|
36
|
+
align: :center,
|
|
37
|
+
width: box_width
|
|
38
|
+
) do
|
|
39
|
+
banner_content
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
"#{Tng::UI::Theme.center_box(box, box_width, terminal_width)}\n"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "tty-box"
|
|
4
|
+
require "pastel"
|
|
5
|
+
require "tty-screen"
|
|
6
|
+
require_relative "theme"
|
|
7
|
+
|
|
8
|
+
class GoodbyeDisplay
|
|
9
|
+
def initialize(pastel)
|
|
10
|
+
@pastel = pastel
|
|
11
|
+
@terminal_width = begin
|
|
12
|
+
TTY::Screen.width
|
|
13
|
+
rescue StandardError
|
|
14
|
+
80
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def display
|
|
19
|
+
box_width = Tng::UI::Theme.calculate_box_width(@terminal_width)
|
|
20
|
+
style = Tng::UI::Theme.box_style(:success)
|
|
21
|
+
|
|
22
|
+
goodbye_box = TTY::Box.frame(
|
|
23
|
+
title: { top_left: " Thank You " },
|
|
24
|
+
style: style[:style],
|
|
25
|
+
padding: style[:padding],
|
|
26
|
+
align: :center,
|
|
27
|
+
width: box_width
|
|
28
|
+
) do
|
|
29
|
+
[
|
|
30
|
+
@pastel.public_send(Tng::UI::Theme.color(:success)).bold("Thanks for using Tng!"),
|
|
31
|
+
"",
|
|
32
|
+
@pastel.public_send(Tng::UI::Theme.color(:secondary), "Happy testing!"),
|
|
33
|
+
"",
|
|
34
|
+
@pastel.public_send(Tng::UI::Theme.color(:muted), "Your Rails tests are now supercharged with LLM")
|
|
35
|
+
].join("\n")
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
puts Tng::UI::Theme.center_box(goodbye_box, box_width, @terminal_width)
|
|
39
|
+
puts
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
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 ModelTestFlowDisplay
|
|
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_model(models)
|
|
21
|
+
header = @pastel.public_send(Tng::UI::Theme.color(:primary)).bold("#{Tng::UI::Theme.icon(:config)} Select model 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
|
+
models.each do |model|
|
|
32
|
+
display_name = "#{model[:name]} #{@pastel.public_send(Tng::UI::Theme.color(:muted), "(#{model[:path]})")}"
|
|
33
|
+
menu.choice display_name, model
|
|
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 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
|
+
def show_no_models_message
|
|
57
|
+
puts
|
|
58
|
+
puts Tng::UI::Theme.center_text(
|
|
59
|
+
@pastel.public_send(Tng::UI::Theme.color(:warning),
|
|
60
|
+
"#{Tng::UI::Theme.icon(:warning)} No models found in app/models"), @terminal_width
|
|
61
|
+
)
|
|
62
|
+
puts Tng::UI::Theme.center_text(
|
|
63
|
+
@pastel.public_send(Tng::UI::Theme.color(:muted),
|
|
64
|
+
"Make sure you're in a Rails project with models"), @terminal_width
|
|
65
|
+
)
|
|
66
|
+
puts
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def select_model_method(model, methods)
|
|
70
|
+
header = @pastel.public_send(Tng::UI::Theme.color(:primary)).bold("#{Tng::UI::Theme.icon(:rocket)} Select method to test in #{model[:name]}")
|
|
71
|
+
puts Tng::UI::Theme.center_text(header, @terminal_width)
|
|
72
|
+
|
|
73
|
+
@prompt.select(
|
|
74
|
+
"",
|
|
75
|
+
cycle: true,
|
|
76
|
+
per_page: 10,
|
|
77
|
+
filter: true,
|
|
78
|
+
symbols: { marker: Tng::UI::Theme.icon(:marker) }
|
|
79
|
+
) do |menu|
|
|
80
|
+
methods.each do |method|
|
|
81
|
+
menu.choice method[:name], method
|
|
82
|
+
end
|
|
83
|
+
menu.choice @pastel.public_send(Tng::UI::Theme.color(:secondary), "#{Tng::UI::Theme.icon(:back)} Back"), :back
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def show_no_methods_message(model)
|
|
88
|
+
error_msg = "#{@pastel.public_send(Tng::UI::Theme.color(:error)).bold("#{Tng::UI::Theme.icon(:error)} No methods found in #{model[:name]}")}\n#{@pastel.public_send(
|
|
89
|
+
Tng::UI::Theme.color(:muted), "Model may be empty or have syntax errors"
|
|
90
|
+
)}"
|
|
91
|
+
puts Tng::UI::Theme.center_text(error_msg, @terminal_width)
|
|
92
|
+
@prompt.keypress(Tng::UI::Theme.center_text(
|
|
93
|
+
@pastel.public_send(Tng::UI::Theme.color(:muted),
|
|
94
|
+
"Press any key to continue..."), @terminal_width
|
|
95
|
+
))
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
require "tty-box"
|
|
2
|
+
require "pastel"
|
|
3
|
+
require_relative "theme"
|
|
4
|
+
|
|
5
|
+
class PostInstallBox
|
|
6
|
+
attr_reader :version, :pastel
|
|
7
|
+
|
|
8
|
+
def initialize(version)
|
|
9
|
+
@version = version
|
|
10
|
+
@pastel = Pastel.new
|
|
11
|
+
@terminal_width = begin
|
|
12
|
+
TTY::Screen.width
|
|
13
|
+
rescue StandardError
|
|
14
|
+
80
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def content
|
|
19
|
+
[
|
|
20
|
+
pastel.public_send(Tng::UI::Theme.color(:success)).bold("#{Tng::UI::Theme.icon(:success)} Tng installed successfully!"),
|
|
21
|
+
"",
|
|
22
|
+
pastel.public_send(Tng::UI::Theme.color(:warning)).bold("#{Tng::UI::Theme.icon(:config)} SETUP REQUIRED"),
|
|
23
|
+
"",
|
|
24
|
+
pastel.public_send(Tng::UI::Theme.color(:primary), "1. Generate configuration:"),
|
|
25
|
+
pastel.public_send(Tng::UI::Theme.color(:secondary), " rails g tng:install"),
|
|
26
|
+
"",
|
|
27
|
+
pastel.public_send(Tng::UI::Theme.color(:primary), "2. Edit configuration file:"),
|
|
28
|
+
pastel.public_send(Tng::UI::Theme.color(:secondary), " config/initializers/tng.rb"),
|
|
29
|
+
"",
|
|
30
|
+
pastel.public_send(Tng::UI::Theme.color(:primary), "3. Add your license key:"),
|
|
31
|
+
pastel.public_send(Tng::UI::Theme.color(:secondary), " config.api_key = 'your-license-key-here'"),
|
|
32
|
+
"",
|
|
33
|
+
pastel.public_send(Tng::UI::Theme.color(:primary),
|
|
34
|
+
"#{Tng::UI::Theme.icon(:config)} Check documentation for the correct authentication setup"),
|
|
35
|
+
"",
|
|
36
|
+
pastel.public_send(Tng::UI::Theme.color(:accent)).bold("#{Tng::UI::Theme.icon(:rocket)} Usage:"),
|
|
37
|
+
"",
|
|
38
|
+
pastel.public_send(Tng::UI::Theme.color(:primary), "Interactive mode:"),
|
|
39
|
+
pastel.public_send(Tng::UI::Theme.color(:success),
|
|
40
|
+
"#{Tng::UI::Theme.icon(:bullet)} bundle exec tng") + pastel.public_send(Tng::UI::Theme.color(:muted),
|
|
41
|
+
" - Full interactive CLI"),
|
|
42
|
+
"",
|
|
43
|
+
pastel.public_send(Tng::UI::Theme.color(:primary), "Direct generation:"),
|
|
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"),
|
|
47
|
+
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"),
|
|
50
|
+
pastel.public_send(Tng::UI::Theme.color(:success),
|
|
51
|
+
"#{Tng::UI::Theme.icon(:bullet)} bundle exec tng --type=controller --file=ping"),
|
|
52
|
+
"",
|
|
53
|
+
pastel.public_send(Tng::UI::Theme.color(:primary), "Help:"),
|
|
54
|
+
pastel.public_send(Tng::UI::Theme.color(:success),
|
|
55
|
+
"#{Tng::UI::Theme.icon(:bullet)} bundle exec tng --help") + pastel.public_send(Tng::UI::Theme.color(:muted),
|
|
56
|
+
" - Show all options"),
|
|
57
|
+
"",
|
|
58
|
+
pastel.public_send(Tng::UI::Theme.color(:muted),
|
|
59
|
+
"#{Tng::UI::Theme.icon(:lightbulb)} Use -t c for controller, -t m for model")
|
|
60
|
+
].join("\n")
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def render
|
|
64
|
+
# Use a reasonable width that won't cause issues
|
|
65
|
+
box_width = [@terminal_width, 70].min
|
|
66
|
+
|
|
67
|
+
style = Tng::UI::Theme.box_style(:default)
|
|
68
|
+
TTY::Box.frame(
|
|
69
|
+
title: { top_left: " TNG ", bottom_right: " v#{@version} " },
|
|
70
|
+
style: style[:style],
|
|
71
|
+
padding: style[:padding],
|
|
72
|
+
align: :left,
|
|
73
|
+
width: box_width
|
|
74
|
+
) do
|
|
75
|
+
content
|
|
76
|
+
end
|
|
77
|
+
rescue StandardError
|
|
78
|
+
"TNG v#{@version} installed successfully!\n" +
|
|
79
|
+
"Run 'rails g tng:install' to get started.\n" +
|
|
80
|
+
"Use 'bundle exec tng --help' for usage information."
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
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 ServiceTestFlowDisplay
|
|
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_service(services)
|
|
21
|
+
header = @pastel.public_send(Tng::UI::Theme.color(:primary)).bold("#{Tng::UI::Theme.icon(:config)} Select service 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
|
+
services.each do |service|
|
|
32
|
+
display_name = "#{service[:name]} #{@pastel.public_send(Tng::UI::Theme.color(:muted), "(#{service[:path]})")}"
|
|
33
|
+
menu.choice display_name, service
|
|
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 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
|
+
def show_no_services_message
|
|
57
|
+
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
|
+
Tng::UI::Theme.color(:muted), "Make sure you have services in app/services/ or app/service/"
|
|
59
|
+
)}"
|
|
60
|
+
puts Tng::UI::Theme.center_text(error_msg, @terminal_width)
|
|
61
|
+
@prompt.keypress(Tng::UI::Theme.center_text(
|
|
62
|
+
@pastel.public_send(Tng::UI::Theme.color(:muted),
|
|
63
|
+
"Press any key to continue..."), @terminal_width
|
|
64
|
+
))
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def select_service_method(service, methods)
|
|
68
|
+
header = @pastel.public_send(Tng::UI::Theme.color(:primary)).bold("#{Tng::UI::Theme.icon(:rocket)} Select method to test for #{service[:name]}")
|
|
69
|
+
puts Tng::UI::Theme.center_text(header, @terminal_width)
|
|
70
|
+
|
|
71
|
+
@prompt.select(
|
|
72
|
+
"",
|
|
73
|
+
cycle: true,
|
|
74
|
+
per_page: 12,
|
|
75
|
+
filter: true,
|
|
76
|
+
symbols: { marker: Tng::UI::Theme.icon(:marker) }
|
|
77
|
+
) do |menu|
|
|
78
|
+
methods.each do |method|
|
|
79
|
+
menu.choice "#{method[:name]}", method
|
|
80
|
+
end
|
|
81
|
+
menu.choice @pastel.public_send(Tng::UI::Theme.color(:secondary), "#{Tng::UI::Theme.icon(:back)} Back"), :back
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def show_no_methods_message(service)
|
|
86
|
+
error_msg = "#{@pastel.public_send(Tng::UI::Theme.color(:error)).bold("#{Tng::UI::Theme.icon(:error)} No methods found in #{service[:name]}")}\n#{@pastel.public_send(
|
|
87
|
+
Tng::UI::Theme.color(:muted), "The service might not have any public methods or could not be analyzed."
|
|
88
|
+
)}"
|
|
89
|
+
puts Tng::UI::Theme.center_text(error_msg, @terminal_width)
|
|
90
|
+
@prompt.keypress(Tng::UI::Theme.center_text(
|
|
91
|
+
@pastel.public_send(Tng::UI::Theme.color(:muted),
|
|
92
|
+
"Press any key to continue..."), @terminal_width
|
|
93
|
+
))
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
require "tty-box"
|
|
2
|
+
require "pastel"
|
|
3
|
+
require "tty-screen"
|
|
4
|
+
require_relative "theme"
|
|
5
|
+
|
|
6
|
+
class ShowHelp
|
|
7
|
+
attr_reader :pastel
|
|
8
|
+
|
|
9
|
+
def initialize(pastel, version)
|
|
10
|
+
@pastel = pastel
|
|
11
|
+
@version = version
|
|
12
|
+
@terminal_width = begin
|
|
13
|
+
TTY::Screen.width
|
|
14
|
+
rescue StandardError
|
|
15
|
+
80
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def content
|
|
20
|
+
[
|
|
21
|
+
@pastel.public_send(Tng::UI::Theme.color(:secondary)).bold("TNG - LLM-Powered Rails Test Generator"),
|
|
22
|
+
@pastel.public_send(Tng::UI::Theme.color(:muted), "Version: #{@version}"),
|
|
23
|
+
"",
|
|
24
|
+
@pastel.public_send(Tng::UI::Theme.color(:accent)).bold("Usage:"),
|
|
25
|
+
@pastel.public_send(Tng::UI::Theme.color(:primary),
|
|
26
|
+
" bundle exec tng") + @pastel.public_send(Tng::UI::Theme.color(:muted),
|
|
27
|
+
" # Interactive mode"),
|
|
28
|
+
@pastel.public_send(Tng::UI::Theme.color(:primary),
|
|
29
|
+
" bundle exec tng --type=TYPE --file=FILE") + @pastel.public_send(
|
|
30
|
+
Tng::UI::Theme.color(:muted), " # Direct mode"
|
|
31
|
+
),
|
|
32
|
+
@pastel.public_send(Tng::UI::Theme.color(:primary),
|
|
33
|
+
" bundle exec tng -t TYPE -f FILE") + @pastel.public_send(Tng::UI::Theme.color(:muted),
|
|
34
|
+
" # Short aliases"),
|
|
35
|
+
@pastel.public_send(Tng::UI::Theme.color(:primary),
|
|
36
|
+
" bundle exec tng -t=TYPE f=FILE") + @pastel.public_send(Tng::UI::Theme.color(:muted),
|
|
37
|
+
" # Mixed format"),
|
|
38
|
+
"",
|
|
39
|
+
@pastel.public_send(Tng::UI::Theme.color(:accent)).bold("Examples:"),
|
|
40
|
+
@pastel.public_send(Tng::UI::Theme.color(:success),
|
|
41
|
+
" bundle exec tng -t c -f ping") + @pastel.public_send(Tng::UI::Theme.color(:muted),
|
|
42
|
+
" # Controller test"),
|
|
43
|
+
@pastel.public_send(Tng::UI::Theme.color(:success),
|
|
44
|
+
" bundle exec tng -t=c f=ping") + @pastel.public_send(Tng::UI::Theme.color(:muted),
|
|
45
|
+
" # Mixed format"),
|
|
46
|
+
@pastel.public_send(Tng::UI::Theme.color(:success),
|
|
47
|
+
" bundle exec tng -t m -f user") + @pastel.public_send(Tng::UI::Theme.color(:muted),
|
|
48
|
+
" # Model test"),
|
|
49
|
+
@pastel.public_send(Tng::UI::Theme.color(:success),
|
|
50
|
+
" bundle exec tng --type=controller --file=users_controller"),
|
|
51
|
+
@pastel.public_send(Tng::UI::Theme.color(:success),
|
|
52
|
+
" bundle exec tng -t c -f \"admin/users_controller\"") + @pastel.public_send(Tng::UI::Theme.color(:muted),
|
|
53
|
+
" # Namespaced"),
|
|
54
|
+
"",
|
|
55
|
+
@pastel.public_send(Tng::UI::Theme.color(:accent)).bold("Options:"),
|
|
56
|
+
@pastel.public_send(Tng::UI::Theme.color(:primary),
|
|
57
|
+
" -t, --type=TYPE") + @pastel.public_send(Tng::UI::Theme.color(:muted),
|
|
58
|
+
" Test type (controller, model)"),
|
|
59
|
+
@pastel.public_send(Tng::UI::Theme.color(:primary),
|
|
60
|
+
" -f, --file=FILE") + @pastel.public_send(Tng::UI::Theme.color(:muted),
|
|
61
|
+
" File name (without .rb extension)"),
|
|
62
|
+
@pastel.public_send(Tng::UI::Theme.color(:primary),
|
|
63
|
+
" -m, --method=METHOD") + @pastel.public_send(Tng::UI::Theme.color(:muted),
|
|
64
|
+
" Method name (for per-method tests)"),
|
|
65
|
+
@pastel.public_send(Tng::UI::Theme.color(:primary),
|
|
66
|
+
" -h, --help") + @pastel.public_send(Tng::UI::Theme.color(:muted),
|
|
67
|
+
" Show this help message"),
|
|
68
|
+
"",
|
|
69
|
+
@pastel.public_send(Tng::UI::Theme.color(:accent)).bold("Type Aliases:"),
|
|
70
|
+
@pastel.public_send(Tng::UI::Theme.color(:muted),
|
|
71
|
+
" #{Tng::UI::Theme.icon(:bullet)} Controllers: ") + @pastel.public_send(Tng::UI::Theme.color(:secondary),
|
|
72
|
+
"c, controller"),
|
|
73
|
+
@pastel.public_send(Tng::UI::Theme.color(:muted),
|
|
74
|
+
" #{Tng::UI::Theme.icon(:bullet)} Models: ") + @pastel.public_send(
|
|
75
|
+
Tng::UI::Theme.color(:secondary), "m, mo, model"
|
|
76
|
+
),
|
|
77
|
+
"",
|
|
78
|
+
@pastel.public_send(Tng::UI::Theme.color(:accent)).bold("File Format:"),
|
|
79
|
+
@pastel.public_send(Tng::UI::Theme.color(:muted),
|
|
80
|
+
" #{Tng::UI::Theme.icon(:bullet)} Controllers: ") + @pastel.public_send(Tng::UI::Theme.color(:success),
|
|
81
|
+
"users_controller, api/v1/posts_controller"),
|
|
82
|
+
@pastel.public_send(Tng::UI::Theme.color(:muted),
|
|
83
|
+
" #{Tng::UI::Theme.icon(:bullet)} Models: ") + @pastel.public_send(
|
|
84
|
+
Tng::UI::Theme.color(:success), "user, blog/post"
|
|
85
|
+
),
|
|
86
|
+
@pastel.public_send(Tng::UI::Theme.color(:muted),
|
|
87
|
+
" #{Tng::UI::Theme.icon(:bullet)} Don't include .rb extension"),
|
|
88
|
+
"",
|
|
89
|
+
@pastel.public_send(Tng::UI::Theme.color(:accent)).bold("Pro Tips:"),
|
|
90
|
+
@pastel.public_send(Tng::UI::Theme.color(:muted),
|
|
91
|
+
" #{Tng::UI::Theme.icon(:bullet)} Mix formats: ") + @pastel.public_send(Tng::UI::Theme.color(:secondary),
|
|
92
|
+
"-t=c f=ping") + @pastel.public_send(
|
|
93
|
+
Tng::UI::Theme.color(:muted), " works perfectly"
|
|
94
|
+
),
|
|
95
|
+
@pastel.public_send(Tng::UI::Theme.color(:muted),
|
|
96
|
+
" #{Tng::UI::Theme.icon(:bullet)} Use filtering in interactive mode for large projects"),
|
|
97
|
+
@pastel.public_send(Tng::UI::Theme.color(:muted),
|
|
98
|
+
" #{Tng::UI::Theme.icon(:bullet)} All formats are equivalent - use what feels natural"),
|
|
99
|
+
"",
|
|
100
|
+
@pastel.public_send(Tng::UI::Theme.color(:secondary)).bold("Happy testing! ") + @pastel.public_send(
|
|
101
|
+
Tng::UI::Theme.color(:accent), "★"
|
|
102
|
+
)
|
|
103
|
+
].join("\n")
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def render
|
|
107
|
+
# Use a reasonable width that matches other UI components
|
|
108
|
+
box_width = Tng::UI::Theme.calculate_box_width(@terminal_width)
|
|
109
|
+
box_width = [box_width, 80].min # Allow wider for help content
|
|
110
|
+
style = Tng::UI::Theme.box_style(:default)
|
|
111
|
+
|
|
112
|
+
box = TTY::Box.frame(
|
|
113
|
+
title: { top_left: " TNG Help ", bottom_right: " v#{@version} " },
|
|
114
|
+
style: style[:style],
|
|
115
|
+
padding: style[:padding],
|
|
116
|
+
align: :left,
|
|
117
|
+
width: box_width
|
|
118
|
+
) do
|
|
119
|
+
content
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
Tng::UI::Theme.center_box(box, box_width, @terminal_width)
|
|
123
|
+
end
|
|
124
|
+
end
|