tng 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.
- checksums.yaml +7 -0
- data/Gemfile +15 -0
- data/LICENSE.md +32 -0
- data/README.md +413 -0
- data/Rakefile +124 -0
- data/bin/load_dev +22 -0
- data/bin/tng +888 -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 +131 -0
- data/lib/tng/analyzers/other.rb +277 -0
- data/lib/tng/analyzers/service.rb +150 -0
- data/lib/tng/api/http_client.rb +100 -0
- data/lib/tng/railtie.rb +11 -0
- data/lib/tng/services/direct_generation.rb +320 -0
- data/lib/tng/services/extract_methods.rb +39 -0
- data/lib/tng/services/test_generator.rb +287 -0
- data/lib/tng/services/testng.rb +100 -0
- data/lib/tng/services/user_app_config.rb +76 -0
- data/lib/tng/ui/about_display.rb +66 -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 +79 -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 +80 -0
- data/lib/tng/ui/other_test_flow_display.rb +78 -0
- data/lib/tng/ui/post_install_box.rb +80 -0
- data/lib/tng/ui/service_test_flow_display.rb +78 -0
- data/lib/tng/ui/show_help.rb +78 -0
- data/lib/tng/ui/system_status_display.rb +128 -0
- data/lib/tng/ui/theme.rb +258 -0
- data/lib/tng/ui/user_stats_display.rb +160 -0
- data/lib/tng/utils.rb +325 -0
- data/lib/tng/version.rb +5 -0
- data/lib/tng.rb +308 -0
- data/tng.gemspec +56 -0
- metadata +293 -0
@@ -0,0 +1,100 @@
|
|
1
|
+
module Services
|
2
|
+
class Testng
|
3
|
+
STATS_PATH = "cli/tng_rails/stats"
|
4
|
+
|
5
|
+
def initialize(http_client)
|
6
|
+
@http_client = http_client
|
7
|
+
end
|
8
|
+
|
9
|
+
def check_system_status
|
10
|
+
response = @http_client.ping
|
11
|
+
|
12
|
+
if response.is_a?(HTTPX::ErrorResponse)
|
13
|
+
return {
|
14
|
+
status: :error,
|
15
|
+
message: "Unable to connect to TNG service: #{response.error&.message}",
|
16
|
+
error_type: :connection_error
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
case response.status
|
21
|
+
when 200
|
22
|
+
begin
|
23
|
+
data = JSON.parse(response.body)
|
24
|
+
current_version = data["current_version"]["gem_version"]
|
25
|
+
server_base_url = data["base_url"]
|
26
|
+
user_base_url = Tng::Services::UserAppConfig.base_url
|
27
|
+
|
28
|
+
if current_version > Tng::VERSION
|
29
|
+
return {
|
30
|
+
status: :version_mismatch,
|
31
|
+
message: "Version mismatch detected",
|
32
|
+
current_version: current_version,
|
33
|
+
gem_version: Tng::VERSION,
|
34
|
+
error_type: :version_mismatch
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
# Check for base URL mismatch
|
39
|
+
if server_base_url && user_base_url && server_base_url != user_base_url
|
40
|
+
return {
|
41
|
+
status: :base_url_mismatch,
|
42
|
+
message: "Base URL mismatch detected",
|
43
|
+
server_base_url: server_base_url,
|
44
|
+
user_base_url: user_base_url,
|
45
|
+
current_version: current_version,
|
46
|
+
gem_version: Tng::VERSION,
|
47
|
+
error_type: :base_url_mismatch
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
{
|
52
|
+
status: :ok,
|
53
|
+
message: data["message"] || "System is operational",
|
54
|
+
current_version: current_version,
|
55
|
+
gem_version: Tng::VERSION,
|
56
|
+
server_base_url: server_base_url
|
57
|
+
}
|
58
|
+
rescue JSON::ParserError => e
|
59
|
+
{
|
60
|
+
status: :error,
|
61
|
+
message: "Invalid response from server: #{e.message}",
|
62
|
+
error_type: :parse_error
|
63
|
+
}
|
64
|
+
end
|
65
|
+
else
|
66
|
+
{
|
67
|
+
status: :service_down,
|
68
|
+
message: "TestNG service is currently unavailable (HTTP #{response.status})",
|
69
|
+
error_type: :service_unavailable
|
70
|
+
}
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def get_user_stats
|
75
|
+
headers = {
|
76
|
+
"Authorization" => "Bearer #{Tng::Services::UserAppConfig.api_key}",
|
77
|
+
"Content-Type" => "application/json",
|
78
|
+
"User-Agent" => "Tng/#{Tng::VERSION} Ruby/#{RUBY_VERSION}"
|
79
|
+
}
|
80
|
+
|
81
|
+
response = @http_client.get(STATS_PATH, headers: headers)
|
82
|
+
|
83
|
+
return if response.is_a?(HTTPX::ErrorResponse)
|
84
|
+
|
85
|
+
if response.status == 200
|
86
|
+
begin
|
87
|
+
stats_data = JSON.parse(response.body)
|
88
|
+
puts "✅ Statistics fetched successfully!"
|
89
|
+
stats_data
|
90
|
+
rescue JSON::ParserError => e
|
91
|
+
puts "❌ Failed to parse statistics response: #{e.message}"
|
92
|
+
nil
|
93
|
+
end
|
94
|
+
else
|
95
|
+
puts "❌ Failed to fetch statistics (#{response.status})"
|
96
|
+
nil
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Tng
|
4
|
+
module Services
|
5
|
+
class UserAppConfig
|
6
|
+
def self.api_key
|
7
|
+
Tng.api_key
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.base_url
|
11
|
+
Tng.base_url
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.configured?
|
15
|
+
Tng.api_key && Tng.base_url
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.missing_config
|
19
|
+
missing = []
|
20
|
+
missing << "API key" unless api_key
|
21
|
+
missing << "Base URL" unless base_url
|
22
|
+
missing
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.config_with_source
|
26
|
+
auth_entry_points_with_source = []
|
27
|
+
methods = Tng.authentication_methods
|
28
|
+
methods.each do |method|
|
29
|
+
auth_entry_points_with_source << {
|
30
|
+
method: method[:method],
|
31
|
+
file: method[:file_location],
|
32
|
+
auth_type: method[:auth_type],
|
33
|
+
source: extract_method_source(method[:file_location], method[:method])
|
34
|
+
}
|
35
|
+
end
|
36
|
+
Tng.config[:authentication_entry_points_with_source] = auth_entry_points_with_source
|
37
|
+
Tng.config
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.find_method_in_ast(node, method_name)
|
41
|
+
return unless node.is_a?(Prism::Node)
|
42
|
+
|
43
|
+
return node if node.is_a?(Prism::DefNode) && node.name == method_name.to_sym
|
44
|
+
|
45
|
+
node.child_nodes.each do |child|
|
46
|
+
result = find_method_in_ast(child, method_name)
|
47
|
+
return result if result
|
48
|
+
end
|
49
|
+
|
50
|
+
nil
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.extract_method_source(file_path, method_name)
|
54
|
+
return unless file_path && method_name
|
55
|
+
|
56
|
+
full_path = Rails.root.join(file_path)
|
57
|
+
return unless File.exist?(full_path)
|
58
|
+
|
59
|
+
file_content = File.read(full_path)
|
60
|
+
result = Prism.parse(file_content)
|
61
|
+
|
62
|
+
method_node = find_method_in_ast(result.value, method_name)
|
63
|
+
return unless method_node
|
64
|
+
|
65
|
+
start_line = method_node.location.start_line - 1
|
66
|
+
end_line = method_node.location.end_line - 1
|
67
|
+
|
68
|
+
lines = file_content.lines
|
69
|
+
method_source = lines[start_line..end_line].join
|
70
|
+
method_source.strip
|
71
|
+
rescue StandardError
|
72
|
+
nil
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "tty-box"
|
4
|
+
require "pastel"
|
5
|
+
require "tty-screen"
|
6
|
+
require_relative "theme"
|
7
|
+
|
8
|
+
class AboutDisplay
|
9
|
+
def initialize(pastel, prompt, version)
|
10
|
+
@pastel = pastel
|
11
|
+
@prompt = prompt
|
12
|
+
@version = version
|
13
|
+
@terminal_width = begin
|
14
|
+
TTY::Screen.width
|
15
|
+
rescue StandardError
|
16
|
+
80
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def display
|
21
|
+
about_content = [
|
22
|
+
"",
|
23
|
+
@pastel.public_send(Tng::UI::Theme.color(:secondary),
|
24
|
+
"Tng is an LLM-powered test generation tool for Rails applications."),
|
25
|
+
"",
|
26
|
+
@pastel.public_send(Tng::UI::Theme.color(:accent), "Features:"),
|
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"),
|
33
|
+
@pastel.public_send(Tng::UI::Theme.color(:muted), "#{Tng::UI::Theme.icon(:bullet)} LLM-powered test suggestions"),
|
34
|
+
@pastel.public_send(Tng::UI::Theme.color(:muted), "#{Tng::UI::Theme.icon(:bullet)} Test coverage analysis"),
|
35
|
+
"",
|
36
|
+
@pastel.public_send(Tng::UI::Theme.color(:accent), "Technology:"),
|
37
|
+
@pastel.public_send(Tng::UI::Theme.color(:muted),
|
38
|
+
"#{Tng::UI::Theme.icon(:bullet)} Static code analysis with Prism"),
|
39
|
+
@pastel.public_send(Tng::UI::Theme.color(:muted),
|
40
|
+
"#{Tng::UI::Theme.icon(:bullet)} Intelligent pattern recognition"),
|
41
|
+
@pastel.public_send(Tng::UI::Theme.color(:muted), "#{Tng::UI::Theme.icon(:bullet)} Rails-specific optimizations"),
|
42
|
+
"",
|
43
|
+
@pastel.public_send(Tng::UI::Theme.color(:muted), "Version: #{@version}"),
|
44
|
+
@pastel.public_send(Tng::UI::Theme.color(:muted),
|
45
|
+
"Built with #{Tng::UI::Theme.icon(:heart)} for Rails developers")
|
46
|
+
].join("\n")
|
47
|
+
|
48
|
+
box_width = Tng::UI::Theme.calculate_box_width(@terminal_width)
|
49
|
+
style = Tng::UI::Theme.box_style(:default)
|
50
|
+
|
51
|
+
about_box = TTY::Box.frame(
|
52
|
+
title: { top_left: " About Tng " },
|
53
|
+
style: style[:style],
|
54
|
+
padding: style[:padding],
|
55
|
+
width: box_width
|
56
|
+
) do
|
57
|
+
about_content
|
58
|
+
end
|
59
|
+
|
60
|
+
puts Tng::UI::Theme.center_box(about_box, box_width, @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
|
+
end
|
@@ -0,0 +1,172 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "tty-box"
|
4
|
+
require "pastel"
|
5
|
+
require "tty-screen"
|
6
|
+
require_relative "theme"
|
7
|
+
|
8
|
+
class AuthenticationWarningDisplay
|
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_if_missing
|
19
|
+
return false unless authentication_config_missing?
|
20
|
+
|
21
|
+
display_warning
|
22
|
+
handle_user_choice
|
23
|
+
end
|
24
|
+
|
25
|
+
def display_warning
|
26
|
+
warning_content = [
|
27
|
+
@pastel.public_send(Tng::UI::Theme.color(:error)).bold("Authentication Configuration Missing"),
|
28
|
+
"",
|
29
|
+
@pastel.public_send(Tng::UI::Theme.color(:primary), "TNG needs authentication setup to generate proper tests."),
|
30
|
+
"",
|
31
|
+
@pastel.public_send(Tng::UI::Theme.color(:accent), "Missing: ") + build_missing_items_summary,
|
32
|
+
"",
|
33
|
+
@pastel.public_send(Tng::UI::Theme.color(:secondary), "Quick fix:"),
|
34
|
+
@pastel.public_send(Tng::UI::Theme.color(:muted),
|
35
|
+
"#{Tng::UI::Theme.icon(:bullet)} Edit config/initializers/tng.rb"),
|
36
|
+
@pastel.public_send(Tng::UI::Theme.color(:muted),
|
37
|
+
"#{Tng::UI::Theme.icon(:bullet)} Uncomment authentication_methods array"),
|
38
|
+
@pastel.public_send(Tng::UI::Theme.color(:muted), "#{Tng::UI::Theme.icon(:bullet)} Add your auth method details"),
|
39
|
+
"",
|
40
|
+
@pastel.public_send(Tng::UI::Theme.color(:success), "Press Enter to continue anyway"),
|
41
|
+
@pastel.public_send(Tng::UI::Theme.color(:error), "Press Esc to cancel and fix configuration")
|
42
|
+
].join("\n")
|
43
|
+
|
44
|
+
box_width = Tng::UI::Theme.calculate_box_width(@terminal_width)
|
45
|
+
style = Tng::UI::Theme.box_style(:warning)
|
46
|
+
|
47
|
+
warning_box = TTY::Box.frame(
|
48
|
+
title: { top_left: " Auth Warning " },
|
49
|
+
style: style[:style],
|
50
|
+
padding: style[:padding],
|
51
|
+
width: box_width
|
52
|
+
) do
|
53
|
+
warning_content
|
54
|
+
end
|
55
|
+
|
56
|
+
puts Tng::UI::Theme.center_box(warning_box, box_width, @terminal_width)
|
57
|
+
puts
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def handle_user_choice
|
63
|
+
require "io/console"
|
64
|
+
|
65
|
+
loop do
|
66
|
+
key = $stdin.getch
|
67
|
+
case key
|
68
|
+
when "\r", "\n" # Enter key
|
69
|
+
return false # Continue anyway
|
70
|
+
when "\e" # Escape key
|
71
|
+
return true # Cancel (authentication missing)
|
72
|
+
when "\u0003" # Ctrl+C
|
73
|
+
puts "\n#{@pastel.public_send(Tng::UI::Theme.color(:warning), "Operation cancelled.")}"
|
74
|
+
exit(0)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
rescue StandardError
|
78
|
+
# Fallback if getch is not available
|
79
|
+
puts @pastel.public_send(Tng::UI::Theme.color(:muted), "Press Enter to continue or Ctrl+C to cancel...")
|
80
|
+
gets
|
81
|
+
false
|
82
|
+
end
|
83
|
+
|
84
|
+
def build_missing_items_summary
|
85
|
+
issues = []
|
86
|
+
|
87
|
+
issues << "empty auth methods" if Tng.authentication_enabled && authentication_methods_empty?
|
88
|
+
|
89
|
+
issues << "invalid auth config" if !authentication_methods_empty? && !authentication_methods_valid?
|
90
|
+
|
91
|
+
issues << "auth disabled" unless Tng.authentication_enabled
|
92
|
+
|
93
|
+
issues.empty? ? "unknown issue" : issues.join(", ")
|
94
|
+
end
|
95
|
+
|
96
|
+
def authentication_config_missing?
|
97
|
+
# Check if authentication is enabled but methods are not configured
|
98
|
+
return true if Tng.authentication_enabled && authentication_methods_empty?
|
99
|
+
|
100
|
+
# Check if authentication methods exist but are invalid
|
101
|
+
return true if !authentication_methods_empty? && !authentication_methods_valid?
|
102
|
+
|
103
|
+
false
|
104
|
+
end
|
105
|
+
|
106
|
+
def authentication_methods_empty?
|
107
|
+
methods = Tng.authentication_methods
|
108
|
+
methods.nil? || methods.empty?
|
109
|
+
end
|
110
|
+
|
111
|
+
def authentication_methods_valid?
|
112
|
+
methods = Tng.authentication_methods
|
113
|
+
return false if methods.nil? || methods.empty?
|
114
|
+
|
115
|
+
methods.all? do |method|
|
116
|
+
method.is_a?(Hash) &&
|
117
|
+
method.key?(:method) && !method[:method].to_s.strip.empty? &&
|
118
|
+
method.key?(:file_location) && !method[:file_location].to_s.strip.empty? &&
|
119
|
+
method.key?(:auth_type) && !method[:auth_type].to_s.strip.empty?
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def build_missing_items_list
|
124
|
+
issues = []
|
125
|
+
|
126
|
+
if Tng.authentication_enabled && authentication_methods_empty?
|
127
|
+
issues << @pastel.public_send(Tng::UI::Theme.color(:error), " • authentication_methods array is empty")
|
128
|
+
end
|
129
|
+
|
130
|
+
if !authentication_methods_empty? && !authentication_methods_valid?
|
131
|
+
issues << @pastel.public_send(Tng::UI::Theme.color(:error), " • authentication_methods contains invalid entries")
|
132
|
+
|
133
|
+
Tng.authentication_methods.each_with_index do |method, index|
|
134
|
+
next if method.is_a?(Hash)
|
135
|
+
|
136
|
+
issues << @pastel.public_send(Tng::UI::Theme.color(:muted), " - Entry #{index + 1}: not a valid hash")
|
137
|
+
next
|
138
|
+
end
|
139
|
+
|
140
|
+
Tng.authentication_methods.each_with_index do |method, index|
|
141
|
+
next unless method.is_a?(Hash)
|
142
|
+
|
143
|
+
if !method.key?(:method) || method[:method].to_s.strip.empty?
|
144
|
+
issues << @pastel.public_send(Tng::UI::Theme.color(:muted),
|
145
|
+
" - Entry #{index + 1}: missing or empty 'method'")
|
146
|
+
end
|
147
|
+
|
148
|
+
if !method.key?(:file_location) || method[:file_location].to_s.strip.empty?
|
149
|
+
issues << @pastel.public_send(Tng::UI::Theme.color(:muted),
|
150
|
+
" - Entry #{index + 1}: missing or empty 'file_location'")
|
151
|
+
end
|
152
|
+
|
153
|
+
if !method.key?(:auth_type) || method[:auth_type].to_s.strip.empty?
|
154
|
+
issues << @pastel.public_send(Tng::UI::Theme.color(:muted),
|
155
|
+
" - Entry #{index + 1}: missing or empty 'auth_type'")
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
unless Tng.authentication_enabled
|
161
|
+
issues << @pastel.public_send(Tng::UI::Theme.color(:warning),
|
162
|
+
" • authentication_enabled is set to false")
|
163
|
+
end
|
164
|
+
|
165
|
+
if issues.empty?
|
166
|
+
@pastel.public_send(Tng::UI::Theme.color(:muted),
|
167
|
+
" • Configuration appears valid")
|
168
|
+
else
|
169
|
+
issues.join("\n")
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
@@ -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,79 @@
|
|
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 show_no_controllers_message
|
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(
|
42
|
+
Tng::UI::Theme.color(:muted), "Make sure you have controllers in app/controllers/"
|
43
|
+
)}"
|
44
|
+
puts Tng::UI::Theme.center_text(error_msg, @terminal_width)
|
45
|
+
@prompt.keypress(Tng::UI::Theme.center_text(
|
46
|
+
@pastel.public_send(Tng::UI::Theme.color(:muted),
|
47
|
+
"Press any key to continue..."), @terminal_width
|
48
|
+
))
|
49
|
+
end
|
50
|
+
|
51
|
+
def select_controller_method(controller, methods)
|
52
|
+
header = @pastel.public_send(Tng::UI::Theme.color(:primary)).bold("#{Tng::UI::Theme.icon(:rocket)} Select method to test in #{controller[:name]}")
|
53
|
+
puts Tng::UI::Theme.center_text(header, @terminal_width)
|
54
|
+
|
55
|
+
@prompt.select(
|
56
|
+
"",
|
57
|
+
cycle: true,
|
58
|
+
per_page: 10,
|
59
|
+
filter: true,
|
60
|
+
symbols: { marker: Tng::UI::Theme.icon(:marker) }
|
61
|
+
) do |menu|
|
62
|
+
methods.each do |method|
|
63
|
+
menu.choice method[:name], method
|
64
|
+
end
|
65
|
+
menu.choice @pastel.public_send(Tng::UI::Theme.color(:secondary), "#{Tng::UI::Theme.icon(:back)} Back"), :back
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def show_no_methods_message(controller)
|
70
|
+
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(
|
71
|
+
Tng::UI::Theme.color(:muted), "Controller may be empty or have syntax errors"
|
72
|
+
)}"
|
73
|
+
puts Tng::UI::Theme.center_text(error_msg, @terminal_width)
|
74
|
+
@prompt.keypress(Tng::UI::Theme.center_text(
|
75
|
+
@pastel.public_send(Tng::UI::Theme.color(:muted),
|
76
|
+
"Press any key to continue..."), @terminal_width
|
77
|
+
))
|
78
|
+
end
|
79
|
+
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
|