ruby_raider 2.0.0 → 3.0.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.
Files changed (228) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/e2e_tests.yml +58 -0
  3. data/.github/workflows/steep.yml +21 -0
  4. data/.gitignore +1 -1
  5. data/.reek.yml +46 -4
  6. data/.ruby-version +1 -1
  7. data/README.md +138 -77
  8. data/Steepfile +22 -0
  9. data/assets/ruby_raider_logo.svg +51 -0
  10. data/lib/adopter/adopt_menu.rb +11 -15
  11. data/lib/adopter/converters/base_converter.rb +1 -2
  12. data/lib/adopter/converters/identity_converter.rb +3 -6
  13. data/lib/adopter/migration_plan.rb +0 -1
  14. data/lib/adopter/plan_builder.rb +2 -5
  15. data/lib/adopter/project_analyzer.rb +1 -5
  16. data/lib/adopter/project_detector.rb +3 -5
  17. data/lib/commands/adopt_commands.rb +0 -1
  18. data/lib/commands/plugin_commands.rb +0 -2
  19. data/lib/commands/scaffolding_commands.rb +246 -38
  20. data/lib/commands/utility_commands.rb +92 -3
  21. data/lib/generators/automation/automation_generator.rb +0 -7
  22. data/lib/generators/automation/templates/partials/element.tt +1 -1
  23. data/lib/generators/automation/templates/partials/initialize_selector.tt +0 -7
  24. data/lib/generators/automation/templates/partials/url_methods.tt +0 -1
  25. data/lib/generators/common_generator.rb +12 -0
  26. data/lib/generators/cucumber/cucumber_generator.rb +36 -0
  27. data/lib/generators/cucumber/templates/accessibility_feature.tt +5 -0
  28. data/lib/generators/cucumber/templates/accessibility_steps.tt +21 -0
  29. data/lib/generators/cucumber/templates/cucumber.tt +8 -1
  30. data/lib/generators/cucumber/templates/feature.tt +0 -4
  31. data/lib/generators/cucumber/templates/partials/appium_env.tt +5 -0
  32. data/lib/generators/cucumber/templates/partials/capybara_env.tt +19 -1
  33. data/lib/generators/cucumber/templates/partials/driver_world.tt +1 -4
  34. data/lib/generators/cucumber/templates/partials/selenium_env.tt +22 -35
  35. data/lib/generators/cucumber/templates/partials/watir_env.tt +20 -1
  36. data/lib/generators/cucumber/templates/partials/web_steps.tt +6 -12
  37. data/lib/generators/cucumber/templates/performance_feature.tt +5 -0
  38. data/lib/generators/cucumber/templates/performance_steps.tt +17 -0
  39. data/lib/generators/cucumber/templates/visual_feature.tt +5 -0
  40. data/lib/generators/cucumber/templates/visual_steps.tt +19 -0
  41. data/lib/generators/generator.rb +38 -7
  42. data/lib/generators/helper_generator.rb +24 -7
  43. data/lib/generators/infrastructure/templates/github.tt +1 -1
  44. data/lib/generators/infrastructure/templates/github_appium.tt +2 -2
  45. data/lib/generators/infrastructure/templates/gitlab.tt +1 -1
  46. data/lib/generators/invoke_generators.rb +42 -9
  47. data/lib/generators/menu_generator.rb +120 -11
  48. data/lib/generators/minitest/minitest_generator.rb +16 -4
  49. data/lib/generators/minitest/templates/accessibility_test.tt +26 -0
  50. data/lib/generators/minitest/templates/performance_test.tt +18 -0
  51. data/lib/generators/minitest/templates/test.tt +5 -34
  52. data/lib/generators/minitest/templates/visual_test.tt +23 -0
  53. data/lib/generators/rspec/rspec_generator.rb +16 -4
  54. data/lib/generators/rspec/templates/accessibility_spec.tt +25 -0
  55. data/lib/generators/rspec/templates/performance_spec.tt +18 -0
  56. data/lib/generators/rspec/templates/spec.tt +5 -35
  57. data/lib/generators/rspec/templates/visual_spec.tt +20 -0
  58. data/lib/generators/template_renderer/partial_cache.rb +11 -1
  59. data/lib/generators/template_renderer/partial_resolver.rb +17 -10
  60. data/lib/generators/template_renderer.rb +17 -1
  61. data/lib/generators/templates/common/gemfile.tt +21 -6
  62. data/lib/generators/templates/common/git_ignore.tt +6 -1
  63. data/lib/generators/templates/common/partials/mobile_config.tt +5 -1
  64. data/lib/generators/templates/common/partials/web_config.tt +16 -7
  65. data/lib/generators/templates/common/rakefile.tt +36 -0
  66. data/lib/generators/templates/common/read_me.tt +41 -91
  67. data/lib/generators/templates/common/rspec.tt +3 -0
  68. data/lib/generators/templates/common/ruby_version.tt +1 -0
  69. data/lib/generators/templates/helpers/allure_helper.tt +11 -0
  70. data/lib/generators/templates/helpers/browser_helper.tt +13 -3
  71. data/lib/generators/templates/helpers/capybara_helper.tt +6 -2
  72. data/lib/generators/templates/helpers/debug_helper.tt +190 -0
  73. data/lib/generators/templates/helpers/driver_helper.tt +2 -10
  74. data/lib/generators/templates/helpers/partials/appium_driver.tt +0 -2
  75. data/lib/generators/templates/helpers/partials/debug_diagnostics.tt +7 -0
  76. data/lib/generators/templates/helpers/partials/debug_start.tt +7 -0
  77. data/lib/generators/templates/helpers/partials/driver_and_options.tt +1 -3
  78. data/lib/generators/templates/helpers/partials/selenium_driver.tt +9 -8
  79. data/lib/generators/templates/helpers/partials/video_start.tt +9 -0
  80. data/lib/generators/templates/helpers/partials/video_stop.tt +4 -0
  81. data/lib/generators/templates/helpers/performance_helper.tt +57 -0
  82. data/lib/generators/templates/helpers/spec_helper.tt +57 -8
  83. data/lib/generators/templates/helpers/test_helper.tt +69 -1
  84. data/lib/generators/templates/helpers/video_helper.tt +270 -0
  85. data/lib/generators/templates/helpers/visual_helper.tt +39 -46
  86. data/lib/llm/client.rb +79 -0
  87. data/lib/llm/config.rb +57 -0
  88. data/lib/llm/prompts.rb +84 -0
  89. data/lib/llm/provider.rb +27 -0
  90. data/lib/llm/providers/anthropic_provider.rb +43 -0
  91. data/lib/llm/providers/ollama_provider.rb +56 -0
  92. data/lib/llm/providers/openai_provider.rb +42 -0
  93. data/lib/llm/response_parser.rb +67 -0
  94. data/lib/plugin/plugin.rb +22 -20
  95. data/lib/plugin/plugin_exposer.rb +16 -38
  96. data/lib/ruby_raider.rb +47 -12
  97. data/lib/scaffolding/crud_generator.rb +94 -0
  98. data/lib/scaffolding/dry_run_presenter.rb +16 -0
  99. data/lib/scaffolding/name_normalizer.rb +63 -0
  100. data/lib/scaffolding/page_introspector.rb +45 -0
  101. data/lib/scaffolding/project_detector.rb +96 -0
  102. data/lib/scaffolding/scaffold_menu.rb +103 -0
  103. data/lib/scaffolding/scaffolding.rb +158 -11
  104. data/lib/scaffolding/templates/component.tt +30 -0
  105. data/lib/scaffolding/templates/feature.tt +4 -4
  106. data/lib/scaffolding/templates/helper.tt +15 -1
  107. data/lib/scaffolding/templates/page_from_url.tt +75 -0
  108. data/lib/scaffolding/templates/page_object.tt +50 -1
  109. data/lib/scaffolding/templates/spec.tt +33 -2
  110. data/lib/scaffolding/templates/spec_from_page.tt +31 -0
  111. data/lib/scaffolding/templates/spec_from_url.tt +46 -0
  112. data/lib/scaffolding/templates/steps.tt +17 -5
  113. data/lib/scaffolding/url_analyzer.rb +179 -0
  114. data/lib/utilities/desktop_downloader.rb +177 -0
  115. data/lib/utilities/logo.rb +83 -0
  116. data/lib/utilities/utilities.rb +61 -20
  117. data/lib/version +1 -1
  118. data/ruby_raider.gemspec +1 -0
  119. data/sig/adopter/adopt_menu.rbs +25 -0
  120. data/sig/adopter/converters/base_converter.rbs +23 -0
  121. data/sig/adopter/converters/identity_converter.rbs +16 -0
  122. data/sig/adopter/migration_plan.rbs +34 -0
  123. data/sig/adopter/migrator.rbs +21 -0
  124. data/sig/adopter/plan_builder.rbs +38 -0
  125. data/sig/adopter/project_analyzer.rbs +39 -0
  126. data/sig/adopter/project_detector.rbs +26 -0
  127. data/sig/commands/adopt_commands.rbs +8 -0
  128. data/sig/commands/loaded_commands.rbs +5 -0
  129. data/sig/commands/plugin_commands.rbs +9 -0
  130. data/sig/commands/scaffolding_commands.rbs +28 -0
  131. data/sig/commands/utility_commands.rbs +21 -0
  132. data/sig/generators/automation/automation_generator.rbs +20 -0
  133. data/sig/generators/common_generator.rbs +12 -0
  134. data/sig/generators/cucumber/cucumber_generator.rbs +16 -0
  135. data/sig/generators/generator.rbs +40 -0
  136. data/sig/generators/helper_generator.rbs +18 -0
  137. data/sig/generators/infrastructure/github_generator.rbs +5 -0
  138. data/sig/generators/infrastructure/gitlab_generator.rbs +4 -0
  139. data/sig/generators/invoke_generators.rbs +10 -0
  140. data/sig/generators/menu_generator.rbs +29 -0
  141. data/sig/generators/minitest/minitest_generator.rbs +8 -0
  142. data/sig/generators/rspec/rspec_generator.rbs +8 -0
  143. data/sig/generators/template_renderer/partial_cache.rbs +20 -0
  144. data/sig/generators/template_renderer/partial_resolver.rbs +20 -0
  145. data/sig/generators/template_renderer/template_error.rbs +19 -0
  146. data/sig/generators/template_renderer.rbs +10 -0
  147. data/sig/llm/client.rbs +15 -0
  148. data/sig/llm/config.rbs +20 -0
  149. data/sig/llm/prompts.rbs +8 -0
  150. data/sig/llm/provider.rbs +12 -0
  151. data/sig/llm/providers/anthropic_provider.rbs +16 -0
  152. data/sig/llm/providers/ollama_provider.rbs +18 -0
  153. data/sig/llm/providers/openai_provider.rbs +16 -0
  154. data/sig/llm/response_parser.rbs +13 -0
  155. data/sig/plugin/plugin.rbs +24 -0
  156. data/sig/plugin/plugin_exposer.rbs +20 -0
  157. data/sig/ruby_raider.rbs +15 -0
  158. data/sig/scaffolding/crud_generator.rbs +16 -0
  159. data/sig/scaffolding/dry_run_presenter.rbs +4 -0
  160. data/sig/scaffolding/name_normalizer.rbs +17 -0
  161. data/sig/scaffolding/page_introspector.rbs +14 -0
  162. data/sig/scaffolding/project_detector.rbs +14 -0
  163. data/sig/scaffolding/scaffold_menu.rbs +18 -0
  164. data/sig/scaffolding/scaffolding.rbs +55 -0
  165. data/sig/scaffolding/url_analyzer.rbs +28 -0
  166. data/sig/utilities/desktop_downloader.rbs +23 -0
  167. data/sig/utilities/logger.rbs +13 -0
  168. data/sig/utilities/logo.rbs +16 -0
  169. data/sig/utilities/utilities.rbs +30 -0
  170. data/sig/vendor/thor.rbs +34 -0
  171. data/sig/vendor/tty_prompt.rbs +15 -0
  172. data/spec/adopter/adopt_menu_spec.rb +12 -12
  173. data/spec/adopter/migration_plan_spec.rb +1 -1
  174. data/spec/adopter/migrator_spec.rb +2 -2
  175. data/spec/adopter/project_detector_spec.rb +1 -1
  176. data/spec/commands/raider_commands_spec.rb +129 -0
  177. data/spec/commands/scaffolding_commands_spec.rb +22 -0
  178. data/spec/generators/generator_spec.rb +23 -0
  179. data/spec/integration/commands/scaffolding_commands_spec.rb +1 -1
  180. data/spec/integration/commands/utility_commands_spec.rb +29 -9
  181. data/spec/integration/content/ci_content_spec.rb +119 -0
  182. data/spec/integration/content/common_content_spec.rb +288 -0
  183. data/spec/integration/content/config_content_spec.rb +175 -0
  184. data/spec/integration/content/content_helper.rb +32 -0
  185. data/spec/integration/content/gemfile_content_spec.rb +209 -0
  186. data/spec/integration/content/helper_content_spec.rb +485 -0
  187. data/spec/integration/content/page_content_spec.rb +259 -0
  188. data/spec/integration/content/reporter_content_spec.rb +236 -0
  189. data/spec/integration/content/skip_flags_content_spec.rb +206 -0
  190. data/spec/integration/content/syntax_validation_spec.rb +30 -0
  191. data/spec/integration/content/test_content_spec.rb +266 -0
  192. data/spec/integration/end_to_end_features_spec.rb +690 -0
  193. data/spec/integration/end_to_end_spec.rb +52 -16
  194. data/spec/integration/generators/automation_generator_spec.rb +0 -12
  195. data/spec/integration/generators/axe_addon_spec.rb +150 -0
  196. data/spec/integration/generators/common_generator_spec.rb +12 -13
  197. data/spec/integration/generators/config_features_spec.rb +155 -0
  198. data/spec/integration/generators/debug_helper_spec.rb +68 -0
  199. data/spec/integration/generators/helpers_generator_spec.rb +0 -12
  200. data/spec/integration/generators/lighthouse_addon_spec.rb +132 -0
  201. data/spec/integration/generators/minitest_generator_spec.rb +0 -6
  202. data/spec/integration/generators/reporter_spec.rb +159 -0
  203. data/spec/integration/generators/skip_flags_spec.rb +134 -0
  204. data/spec/integration/generators/visual_addon_spec.rb +148 -0
  205. data/spec/integration/scaffolding_e2e_spec.rb +775 -0
  206. data/spec/integration/settings_helper.rb +0 -3
  207. data/spec/integration/spec_helper.rb +30 -13
  208. data/spec/llm/client_spec.rb +79 -0
  209. data/spec/llm/config_spec.rb +92 -0
  210. data/spec/llm/prompts_spec.rb +49 -0
  211. data/spec/llm/response_parser_spec.rb +92 -0
  212. data/spec/menus/adopter_adopt_menu_spec.rb +97 -0
  213. data/spec/menus/menu_generator_spec.rb +263 -0
  214. data/spec/scaffolding/name_normalizer_spec.rb +113 -0
  215. data/spec/scaffolding/page_introspector_spec.rb +82 -0
  216. data/spec/scaffolding/scaffold_project_detector_spec.rb +142 -0
  217. data/spec/scaffolding/scaffolding_features_spec.rb +311 -0
  218. data/spec/scaffolding/url_analyzer_spec.rb +110 -0
  219. data/spec/system/adopt_matrix_spec.rb +537 -0
  220. data/spec/system/adopt_spec.rb +225 -0
  221. data/spec/system/support/system_test_helper.rb +0 -2
  222. data/spec/utilities/desktop_downloader_spec.rb +92 -0
  223. data/spec/utilities/headless_config_spec.rb +89 -0
  224. data/spec/utilities/utilities_spec.rb +105 -0
  225. metadata +154 -5
  226. data/lib/generators/automation/templates/visual_options.tt +0 -16
  227. data/lib/generators/templates/helpers/partials/axe_driver.tt +0 -10
  228. data/lib/generators/templates/helpers/visual_spec_helper.tt +0 -35
@@ -0,0 +1,311 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+ require 'pathname'
5
+ require_relative '../../lib/commands/scaffolding_commands'
6
+ require_relative '../../lib/scaffolding/scaffolding'
7
+
8
+ RSpec.describe 'Scaffolding features' do
9
+ let(:scaffold) { ScaffoldingCommands }
10
+ let(:tmp_dir) { 'tmp_scaffold_features_test' }
11
+
12
+ before do
13
+ FileUtils.mkdir_p("#{tmp_dir}/page_objects/pages")
14
+ FileUtils.mkdir_p("#{tmp_dir}/page_objects/abstract")
15
+ FileUtils.mkdir_p("#{tmp_dir}/page_objects/components")
16
+ FileUtils.mkdir_p("#{tmp_dir}/spec")
17
+ FileUtils.mkdir_p("#{tmp_dir}/helpers")
18
+ FileUtils.mkdir_p("#{tmp_dir}/config")
19
+ FileUtils.mkdir_p("#{tmp_dir}/models/data")
20
+
21
+ File.write("#{tmp_dir}/Gemfile", "gem 'rspec'\ngem 'selenium-webdriver'\n")
22
+ File.write("#{tmp_dir}/config/config.yml", "browser: chrome\nurl: http://localhost:3000\n")
23
+ File.write("#{tmp_dir}/page_objects/abstract/page.rb", "class Page; end\n")
24
+ File.write("#{tmp_dir}/page_objects/abstract/component.rb", "class Component; end\n")
25
+
26
+ Dir.chdir(tmp_dir)
27
+ end
28
+
29
+ after do
30
+ Dir.chdir('..')
31
+ FileUtils.rm_rf(tmp_dir)
32
+ end
33
+
34
+ # --- Feature 7: Better name handling ---
35
+
36
+ describe 'name normalization' do
37
+ it 'strips _page suffix from page names' do
38
+ scaffold.new.invoke(:page, nil, %w[login_page])
39
+ expect(Pathname.new('page_objects/pages/login.rb')).to be_file
40
+ end
41
+
42
+ it 'handles CamelCase input' do
43
+ scaffold.new.invoke(:page, nil, %w[UserProfile])
44
+ expect(Pathname.new('page_objects/pages/user_profile.rb')).to be_file
45
+ end
46
+
47
+ it 'handles CamelCase with Page suffix' do
48
+ scaffold.new.invoke(:page, nil, %w[LoginPage])
49
+ expect(Pathname.new('page_objects/pages/login.rb')).to be_file
50
+ end
51
+ end
52
+
53
+ # --- Feature 6: Dry run ---
54
+
55
+ describe 'dry run' do
56
+ it 'does not create files with --dry-run' do
57
+ expect do
58
+ scaffold.new.invoke(:page, nil, %w[checkout --dry-run])
59
+ end.to output(/checkout/).to_stdout
60
+
61
+ expect(Pathname.new('page_objects/pages/checkout.rb')).not_to be_file
62
+ end
63
+
64
+ it 'shows planned file path' do
65
+ expect do
66
+ scaffold.new.invoke(:spec, nil, %w[checkout --dry-run])
67
+ end.to output(/checkout/).to_stdout
68
+ end
69
+ end
70
+
71
+ # --- Feature 8: Component scaffolding ---
72
+
73
+ describe 'component scaffolding' do
74
+ it 'creates a component file' do
75
+ scaffold.new.invoke(:component, nil, %w[sidebar])
76
+ expect(Pathname.new('page_objects/components/sidebar.rb')).to be_file
77
+ end
78
+
79
+ it 'component inherits from Component' do
80
+ scaffold.new.invoke(:component, nil, %w[sidebar])
81
+ content = File.read('page_objects/components/sidebar.rb')
82
+ expect(content).to include('class Sidebar < Component')
83
+ end
84
+
85
+ it 'component has frozen_string_literal' do
86
+ scaffold.new.invoke(:component, nil, %w[sidebar])
87
+ content = File.read('page_objects/components/sidebar.rb')
88
+ expect(content).to include('# frozen_string_literal: true')
89
+ end
90
+
91
+ it 'deletes a component' do
92
+ scaffold.new.invoke(:component, nil, %w[sidebar])
93
+ scaffold.new.invoke(:component, nil, %w[sidebar --delete])
94
+ expect(Pathname.new('page_objects/components/sidebar.rb')).not_to be_file
95
+ end
96
+ end
97
+
98
+ # --- Feature 10: Config-aware templates ---
99
+
100
+ describe 'config-aware templates' do
101
+ it 'page object includes url method when config has url' do
102
+ scaffold.new.invoke(:page, nil, %w[checkout])
103
+ content = File.read('page_objects/pages/checkout.rb')
104
+ expect(content).to include('def url(_page)')
105
+ end
106
+
107
+ it 'page object includes selenium-specific comments' do
108
+ scaffold.new.invoke(:page, nil, %w[checkout])
109
+ content = File.read('page_objects/pages/checkout.rb')
110
+ expect(content).to include('driver.find_element')
111
+ end
112
+
113
+ it 'spec includes let for page with driver' do
114
+ scaffold.new.invoke(:spec, nil, %w[checkout])
115
+ content = File.read('spec/checkout_page_spec.rb')
116
+ expect(content).to include('CheckoutPage.new(driver)')
117
+ end
118
+ end
119
+
120
+ # --- Feature 9: Relationships ---
121
+
122
+ describe 'relationships (--uses)' do
123
+ before do
124
+ File.write('page_objects/pages/login.rb', "class LoginPage < Page; end\n")
125
+ end
126
+
127
+ it 'adds require_relative for dependent pages' do
128
+ scaffold.new.invoke(:page, nil, %w[dashboard --uses login])
129
+ content = File.read('page_objects/pages/dashboard.rb')
130
+ expect(content).to include("require_relative 'login'")
131
+ end
132
+
133
+ it 'adds uses to spec as let declarations' do
134
+ scaffold.new.invoke(:spec, nil, %w[dashboard --uses login])
135
+ content = File.read('spec/dashboard_page_spec.rb')
136
+ expect(content).to include('LoginPage')
137
+ end
138
+ end
139
+
140
+ # --- Feature 1: Batch scaffold ---
141
+
142
+ describe 'batch scaffolding' do
143
+ it 'generates multiple scaffolds' do
144
+ scaffold.new.invoke(:scaffold, nil, %w[login dashboard])
145
+ expect(Pathname.new('page_objects/pages/login.rb')).to be_file
146
+ expect(Pathname.new('page_objects/pages/dashboard.rb')).to be_file
147
+ expect(Pathname.new('spec/login_page_spec.rb')).to be_file
148
+ expect(Pathname.new('spec/dashboard_page_spec.rb')).to be_file
149
+ end
150
+ end
151
+
152
+ # --- Feature 1: Selective --with ---
153
+
154
+ describe 'selective scaffolding (--with)' do
155
+ it 'generates only selected components' do
156
+ scaffold.new.invoke(:scaffold, nil, %w[checkout --with page])
157
+ expect(Pathname.new('page_objects/pages/checkout.rb')).to be_file
158
+ expect(Pathname.new('spec/checkout_page_spec.rb')).not_to be_file
159
+ end
160
+
161
+ it 'generates page and helper with --with' do
162
+ scaffold.new.invoke(:scaffold, nil, %w[checkout --with page helper])
163
+ expect(Pathname.new('page_objects/pages/checkout.rb')).to be_file
164
+ expect(Pathname.new('helpers/checkout_helper.rb')).to be_file
165
+ end
166
+
167
+ it 'generates model data with --with model' do
168
+ scaffold.new.invoke(:scaffold, nil, %w[user --with model])
169
+ expect(Pathname.new('models/data/user.yml')).to be_file
170
+ end
171
+ end
172
+
173
+ # --- Feature 5: CRUD ---
174
+
175
+ describe 'CRUD scaffolding' do
176
+ it 'generates CRUD pages' do
177
+ scaffold.new.invoke(:scaffold, nil, %w[user --crud])
178
+ %w[user_list user_create user_detail user_edit].each do |page|
179
+ expect(Pathname.new("page_objects/pages/#{page}.rb")).to be_file
180
+ end
181
+ end
182
+
183
+ it 'generates CRUD specs' do
184
+ scaffold.new.invoke(:scaffold, nil, %w[user --crud])
185
+ %w[user_list user_create user_detail user_edit].each do |page|
186
+ expect(Pathname.new("spec/#{page}_page_spec.rb")).to be_file
187
+ end
188
+ end
189
+
190
+ it 'generates model data file' do
191
+ scaffold.new.invoke(:scaffold, nil, %w[user --crud])
192
+ expect(Pathname.new('models/data/user.yml')).to be_file
193
+ end
194
+
195
+ it 'model data has expected structure' do
196
+ scaffold.new.invoke(:scaffold, nil, %w[user --crud])
197
+ content = File.read('models/data/user.yml')
198
+ expect(content).to include('default:')
199
+ expect(content).to include('valid:')
200
+ expect(content).to include('invalid:')
201
+ end
202
+ end
203
+
204
+ # --- Feature: Destroy command ---
205
+
206
+ describe 'destroy command' do
207
+ it 'removes page and spec files' do
208
+ scaffold.new.invoke(:scaffold, nil, %w[login])
209
+ expect(Pathname.new('page_objects/pages/login.rb')).to be_file
210
+ expect(Pathname.new('spec/login_page_spec.rb')).to be_file
211
+
212
+ scaffold.new.invoke(:destroy, nil, %w[login])
213
+ expect(Pathname.new('page_objects/pages/login.rb')).not_to be_file
214
+ expect(Pathname.new('spec/login_page_spec.rb')).not_to be_file
215
+ end
216
+
217
+ it 'removes only specified components with --with' do # rubocop:disable RSpec/MultipleExpectations
218
+ scaffold.new.invoke(:scaffold, nil, %w[checkout --with page spec helper])
219
+ expect(Pathname.new('page_objects/pages/checkout.rb')).to be_file
220
+ expect(Pathname.new('spec/checkout_page_spec.rb')).to be_file
221
+ expect(Pathname.new('helpers/checkout_helper.rb')).to be_file
222
+
223
+ scaffold.new.invoke(:destroy, nil, %w[checkout --with page helper])
224
+ expect(Pathname.new('page_objects/pages/checkout.rb')).not_to be_file
225
+ expect(Pathname.new('helpers/checkout_helper.rb')).not_to be_file
226
+ expect(Pathname.new('spec/checkout_page_spec.rb')).to be_file
227
+ end
228
+
229
+ it 'destroys multiple names at once' do
230
+ scaffold.new.invoke(:scaffold, nil, %w[login dashboard])
231
+ scaffold.new.invoke(:destroy, nil, %w[login dashboard])
232
+ expect(Pathname.new('page_objects/pages/login.rb')).not_to be_file
233
+ expect(Pathname.new('page_objects/pages/dashboard.rb')).not_to be_file
234
+ end
235
+ end
236
+
237
+ # --- Feature: Template overrides ---
238
+
239
+ describe 'template overrides' do
240
+ before do
241
+ FileUtils.mkdir_p('.ruby_raider/templates')
242
+ end
243
+
244
+ after do
245
+ FileUtils.rm_rf('.ruby_raider')
246
+ end
247
+
248
+ it 'uses override template when present' do
249
+ File.write('.ruby_raider/templates/page_object.tt', <<~ERB)
250
+ # Custom page for <%= class_name %>
251
+ class <%= page_class_name %> < Page
252
+ # CUSTOM OVERRIDE
253
+ end
254
+ ERB
255
+
256
+ Scaffolding.new(%w[widget]).generate_page
257
+ content = File.read('page_objects/pages/widget.rb')
258
+ expect(content).to include('CUSTOM OVERRIDE')
259
+ expect(content).to include('class WidgetPage < Page')
260
+ end
261
+
262
+ it 'falls back to default template when no override exists' do
263
+ Scaffolding.new(%w[gadget]).generate_page
264
+ content = File.read('page_objects/pages/gadget.rb')
265
+ expect(content).not_to include('CUSTOM OVERRIDE')
266
+ expect(content).to include('class GadgetPage < Page')
267
+ end
268
+ end
269
+
270
+ # --- Feature 3: Spec from page ---
271
+
272
+ describe 'spec from page (--from)' do
273
+ before do
274
+ File.write('page_objects/pages/login.rb', <<~RUBY)
275
+ class LoginPage < Page
276
+ def login(username, password)
277
+ username_field.send_keys username
278
+ end
279
+
280
+ def welcome_message
281
+ driver.find_element(css: '.welcome').text
282
+ end
283
+
284
+ private
285
+
286
+ def username_field
287
+ driver.find_element(id: 'username')
288
+ end
289
+ end
290
+ RUBY
291
+ end
292
+
293
+ it 'generates spec with method stubs' do
294
+ scaffold.new.invoke(:spec, nil, %w[login --from page_objects/pages/login.rb])
295
+ expect(Pathname.new('spec/login_spec.rb')).to be_file
296
+ end
297
+
298
+ it 'spec includes describe blocks for each public method' do
299
+ scaffold.new.invoke(:spec, nil, %w[login --from page_objects/pages/login.rb])
300
+ content = File.read('spec/login_spec.rb')
301
+ expect(content).to include("describe '#login'")
302
+ expect(content).to include("describe '#welcome_message'")
303
+ end
304
+
305
+ it 'spec does not include private methods' do
306
+ scaffold.new.invoke(:spec, nil, %w[login --from page_objects/pages/login.rb])
307
+ content = File.read('spec/login_spec.rb')
308
+ expect(content).not_to include('username_field')
309
+ end
310
+ end
311
+ end
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../lib/scaffolding/url_analyzer'
4
+
5
+ RSpec.describe UrlAnalyzer do
6
+ subject(:analyzer) { described_class.new('https://example.com/login') }
7
+
8
+ let(:sample_html) do
9
+ <<~HTML
10
+ <html>
11
+ <body>
12
+ <form>
13
+ <input type="text" id="username" name="username" />
14
+ <input type="password" id="password" name="password" />
15
+ <input type="hidden" name="csrf" value="token123" />
16
+ <select id="role" name="role"><option>Admin</option></select>
17
+ <textarea id="bio" name="bio"></textarea>
18
+ <button id="login-btn">Log In</button>
19
+ <input type="submit" id="submit-btn" value="Submit" />
20
+ <a href="/forgot">Forgot Password</a>
21
+ </form>
22
+ </body>
23
+ </html>
24
+ HTML
25
+ end
26
+
27
+ before do
28
+ stub_request = instance_double(Net::HTTPSuccess, body: sample_html)
29
+ allow(Net::HTTP).to receive(:get_response).and_return(stub_request)
30
+ end
31
+
32
+ describe '#analyze' do
33
+ before { analyzer.analyze }
34
+
35
+ it 'parses input elements (excluding hidden)' do
36
+ inputs = analyzer.elements.select { |e| e[:type] == :input }
37
+ expect(inputs.length).to eq(2)
38
+ names = inputs.map { |e| e[:name] }
39
+ expect(names).to include('username', 'password')
40
+ end
41
+
42
+ it 'parses select elements' do
43
+ selects = analyzer.elements.select { |e| e[:type] == :select }
44
+ expect(selects.length).to eq(1)
45
+ expect(selects.first[:name]).to eq('role')
46
+ end
47
+
48
+ it 'parses textarea elements' do
49
+ textareas = analyzer.elements.select { |e| e[:type] == :textarea }
50
+ expect(textareas.length).to eq(1)
51
+ end
52
+
53
+ it 'parses button elements' do
54
+ buttons = analyzer.elements.select { |e| e[:type] == :button }
55
+ expect(buttons.length).to eq(1)
56
+ expect(buttons.first[:text]).to eq('Log In')
57
+ end
58
+
59
+ it 'parses submit inputs' do
60
+ submits = analyzer.elements.select { |e| e[:type] == :submit }
61
+ expect(submits.length).to eq(1)
62
+ end
63
+
64
+ it 'parses link elements' do
65
+ links = analyzer.elements.select { |e| e[:type] == :link }
66
+ expect(links.length).to eq(1)
67
+ expect(links.first[:name]).to eq('forgot_password')
68
+ end
69
+
70
+ it 'prefers id locators' do
71
+ username = analyzer.elements.find { |e| e[:name] == 'username' }
72
+ expect(username[:locator]).to eq(type: :id, value: 'username')
73
+ end
74
+ end
75
+
76
+ describe '#page_name' do
77
+ it 'derives name from URL path' do
78
+ expect(analyzer.page_name).to eq('login')
79
+ end
80
+
81
+ it 'uses name override when provided' do
82
+ custom = described_class.new('https://example.com/login', name_override: 'auth')
83
+ expect(custom.page_name).to eq('auth')
84
+ end
85
+
86
+ it 'defaults to home for root URL' do
87
+ root = described_class.new('https://example.com/')
88
+ expect(root.page_name).to eq('home')
89
+ end
90
+ end
91
+
92
+ describe '#to_h' do
93
+ it 'returns structured hash' do
94
+ analyzer.analyze
95
+ result = analyzer.to_h
96
+ expect(result).to include(:page_name, :url, :url_path, :elements)
97
+ expect(result[:url_path]).to eq('/login')
98
+ end
99
+ end
100
+
101
+ describe 'AI mode' do
102
+ let(:ai_analyzer) { described_class.new('https://example.com/login', ai: true) }
103
+
104
+ it 'falls back to regex parsing when LLM is unavailable' do
105
+ allow_any_instance_of(described_class).to receive(:analyze_with_llm).and_return(nil)
106
+ ai_analyzer.analyze
107
+ expect(ai_analyzer.elements).not_to be_empty
108
+ end
109
+ end
110
+ end