ruact 0.0.11 → 0.0.13

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 (137) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +147 -48
  3. data/CONTRIBUTING.md +233 -0
  4. data/README.md +18 -8
  5. data/RELEASING.md +125 -139
  6. data/lib/generators/ruact/install/install_generator.rb +305 -126
  7. data/lib/generators/ruact/install/templates/AGENTS.md.tt +14 -13
  8. data/lib/generators/ruact/install/templates/initializer.rb.tt +29 -7
  9. data/lib/generators/ruact/install/templates/tsconfig.json.tt +3 -0
  10. data/lib/generators/ruact/layout/layout_generator.rb +52 -0
  11. data/lib/generators/ruact/scaffold/templates/controller.rb.tt +5 -3
  12. data/lib/ruact/configuration.rb +65 -13
  13. data/lib/ruact/controller/document_rendering.rb +72 -16
  14. data/lib/ruact/controller/page_rendering.rb +134 -0
  15. data/lib/ruact/controller/pages.rb +116 -0
  16. data/lib/ruact/controller.rb +78 -11
  17. data/lib/ruact/doctor.rb +233 -25
  18. data/lib/ruact/layout_source.rb +29 -7
  19. data/lib/ruact/navigation_boundary.rb +240 -0
  20. data/lib/ruact/packaging.rb +68 -0
  21. data/lib/ruact/railtie.rb +30 -0
  22. data/lib/ruact/server.rb +10 -1
  23. data/lib/ruact/version.rb +1 -1
  24. data/lib/ruact/view_helper.rb +158 -1
  25. data/lib/ruact/views/layouts/ruact.html.erb +32 -0
  26. data/lib/ruact.rb +29 -0
  27. data/vendor/javascript/vite-plugin-ruact/flight-client.test.mjs +321 -0
  28. data/vendor/javascript/vite-plugin-ruact/package-lock.json +11 -0
  29. data/vendor/javascript/vite-plugin-ruact/package.json +1 -0
  30. data/vendor/javascript/vite-plugin-ruact/ruact-router.test.mjs +433 -0
  31. data/vendor/javascript/vite-plugin-ruact/runtime/flight-client.js +14 -3
  32. data/vendor/javascript/vite-plugin-ruact/runtime/ruact-router.js +170 -7
  33. metadata +12 -107
  34. data/.codecov.yml +0 -31
  35. data/.github/workflows/ci.yml +0 -284
  36. data/.github/workflows/server-functions-bench.yml +0 -54
  37. data/.rubocop.yml +0 -107
  38. data/.rubocop_todo.yml +0 -63
  39. data/Rakefile +0 -10
  40. data/bench/server_functions_dispatch_bench.rb +0 -276
  41. data/bench/server_functions_dispatch_bench.results.md +0 -150
  42. data/docs/internal/README.md +0 -9
  43. data/docs/internal/decisions/server-functions-api.md +0 -2236
  44. data/spec/benchmarks/baseline.json +0 -12
  45. data/spec/benchmarks/render_pipeline_benchmark_spec.rb +0 -109
  46. data/spec/fixtures/flight/README.md +0 -136
  47. data/spec/fixtures/flight/array.txt +0 -1
  48. data/spec/fixtures/flight/as_json_object.txt +0 -2
  49. data/spec/fixtures/flight/bigint.txt +0 -1
  50. data/spec/fixtures/flight/boolean_false.txt +0 -1
  51. data/spec/fixtures/flight/boolean_true.txt +0 -1
  52. data/spec/fixtures/flight/client_component_with_props.txt +0 -2
  53. data/spec/fixtures/flight/client_reference.txt +0 -2
  54. data/spec/fixtures/flight/hash.txt +0 -1
  55. data/spec/fixtures/flight/infinity.txt +0 -1
  56. data/spec/fixtures/flight/nan.txt +0 -1
  57. data/spec/fixtures/flight/negative_infinity.txt +0 -1
  58. data/spec/fixtures/flight/nil.txt +0 -1
  59. data/spec/fixtures/flight/number_float.txt +0 -1
  60. data/spec/fixtures/flight/number_integer.txt +0 -1
  61. data/spec/fixtures/flight/react_element_no_props.txt +0 -1
  62. data/spec/fixtures/flight/redirect_row.txt +0 -1
  63. data/spec/fixtures/flight/serializable_object.txt +0 -2
  64. data/spec/fixtures/flight/string_basic.txt +0 -1
  65. data/spec/fixtures/flight/string_dollar_escape.txt +0 -1
  66. data/spec/fixtures/flight/undefined.txt +0 -1
  67. data/spec/fixtures/readme/children-error.html.erb +0 -3
  68. data/spec/fixtures/readme/children-error.txt +0 -1
  69. data/spec/fixtures/story_7_9_views/controller_request_spec_support/demo/show.html.erb +0 -3
  70. data/spec/fixtures/story_7_9_views/controller_request_spec_support/errors_demo/new.html.erb +0 -3
  71. data/spec/fixtures/story_7_9_views/controller_request_spec_support/exploding_layout_demo/show.html.erb +0 -3
  72. data/spec/fixtures/story_7_9_views/controller_request_spec_support/ghost_layout_demo/show.html.erb +0 -3
  73. data/spec/fixtures/story_7_9_views/controller_request_spec_support/layout_demo/show.html.erb +0 -3
  74. data/spec/fixtures/story_7_9_views/controller_request_spec_support/rootless_layout_demo/show.html.erb +0 -3
  75. data/spec/fixtures/story_7_9_views/controller_request_spec_support/unwired_layout_demo/show.html.erb +0 -3
  76. data/spec/fixtures/story_7_9_views/layouts/bare_host.html.erb +0 -16
  77. data/spec/fixtures/story_7_9_views/layouts/exploding_host.html.erb +0 -24
  78. data/spec/fixtures/story_7_9_views/layouts/rootless_host.html.erb +0 -15
  79. data/spec/fixtures/story_7_9_views/layouts/ruact_host.html.erb +0 -17
  80. data/spec/readme_demo_message_spec.rb +0 -67
  81. data/spec/readme_spec.rb +0 -282
  82. data/spec/ruact/client_manifest_spec.rb +0 -270
  83. data/spec/ruact/component_contract_spec.rb +0 -119
  84. data/spec/ruact/configuration_spec.rb +0 -518
  85. data/spec/ruact/controller_request_spec.rb +0 -671
  86. data/spec/ruact/controller_spec.rb +0 -343
  87. data/spec/ruact/doctor_spec.rb +0 -769
  88. data/spec/ruact/erb_preprocessor_hook_spec.rb +0 -55
  89. data/spec/ruact/erb_preprocessor_spec.rb +0 -361
  90. data/spec/ruact/errors_spec.rb +0 -93
  91. data/spec/ruact/flight/renderer_spec.rb +0 -133
  92. data/spec/ruact/flight/serializer_spec.rb +0 -494
  93. data/spec/ruact/html_converter_spec.rb +0 -375
  94. data/spec/ruact/install_generator_spec.rb +0 -1549
  95. data/spec/ruact/layout_source_spec.rb +0 -108
  96. data/spec/ruact/manifest_resolver_spec.rb +0 -174
  97. data/spec/ruact/query_request_spec.rb +0 -706
  98. data/spec/ruact/query_spec.rb +0 -105
  99. data/spec/ruact/railtie_spec.rb +0 -155
  100. data/spec/ruact/render_context_spec.rb +0 -58
  101. data/spec/ruact/render_pipeline_concurrency_spec.rb +0 -78
  102. data/spec/ruact/render_pipeline_spec.rb +0 -928
  103. data/spec/ruact/scaffold_generator_spec.rb +0 -1849
  104. data/spec/ruact/serializable_spec.rb +0 -179
  105. data/spec/ruact/server_bucket_request_spec.rb +0 -785
  106. data/spec/ruact/server_function_name_spec.rb +0 -53
  107. data/spec/ruact/server_functions/backtrace_cleaner_spec.rb +0 -63
  108. data/spec/ruact/server_functions/bucket_two_payload_spec.rb +0 -200
  109. data/spec/ruact/server_functions/codegen_spec.rb +0 -397
  110. data/spec/ruact/server_functions/error_payload_spec.rb +0 -222
  111. data/spec/ruact/server_functions/error_suggestion_spec.rb +0 -79
  112. data/spec/ruact/server_functions/introspection_spec.rb +0 -135
  113. data/spec/ruact/server_functions/name_bridge_spec.rb +0 -212
  114. data/spec/ruact/server_functions/query_context_spec.rb +0 -72
  115. data/spec/ruact/server_functions/query_source_spec.rb +0 -193
  116. data/spec/ruact/server_functions/railtie_integration_spec.rb +0 -215
  117. data/spec/ruact/server_functions/rake_spec.rb +0 -86
  118. data/spec/ruact/server_functions/route_source_spec.rb +0 -202
  119. data/spec/ruact/server_functions/snapshot_spec.rb +0 -96
  120. data/spec/ruact/server_functions/snapshot_writer_spec.rb +0 -71
  121. data/spec/ruact/server_rescue_request_spec.rb +0 -416
  122. data/spec/ruact/server_spec.rb +0 -179
  123. data/spec/ruact/server_upload_request_spec.rb +0 -311
  124. data/spec/ruact/signed_references_spec.rb +0 -164
  125. data/spec/ruact/string_distance_spec.rb +0 -38
  126. data/spec/ruact/tasks_json_introspection_spec.rb +0 -141
  127. data/spec/ruact/testing/have_ruact_component_spec.rb +0 -170
  128. data/spec/ruact/testing/no_production_load_spec.rb +0 -41
  129. data/spec/ruact/validation_errors_spec.rb +0 -116
  130. data/spec/ruact/view_helper_spec.rb +0 -131
  131. data/spec/spec_helper.rb +0 -77
  132. data/spec/support/fixtures/pixel.png +0 -0
  133. data/spec/support/flight_wire_parser.rb +0 -21
  134. data/spec/support/flight_wire_parser_spec.rb +0 -93
  135. data/spec/support/matchers/flight_fixture_matcher.rb +0 -130
  136. data/spec/support/matchers/flight_fixture_matcher_spec.rb +0 -250
  137. data/spec/support/rails_stub.rb +0 -115
@@ -1,343 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "spec_helper"
4
- require "active_support/concern"
5
- # Re-run-5 (2026-05-15) — the `:current_user` inherited-helper clobber
6
- # test creates a `Class.new(ActionController::Base)`, which requires
7
- # `action_controller` to be loaded. Pre-Re-run-5 this test was the
8
- # first thing in the suite to demand-load `action_controller`,
9
- # which had the side effect of triggering `Rails::Application`'s
10
- # class definition AFTER spec_helper's `require "ruact"` ran — so
11
- # `Ruact::Railtie` never got loaded into the process and downstream
12
- # specs that depend on the Railtie's `to_prepare` hook would behave
13
- # inconsistently. Loading both explicitly here makes the order
14
- # deterministic.
15
- require "action_controller"
16
- require "ruact"
17
- require "ruact/controller"
18
-
19
- module Ruact
20
- RSpec.describe Controller do
21
- let(:test_class) do
22
- Class.new do
23
- include Ruact::Controller
24
-
25
- attr_reader :request
26
-
27
- def initialize(fake_request)
28
- @request = fake_request
29
- end
30
- end
31
- end
32
-
33
- # Minimal test double — `headers`, `format`, and `accepts` needed for the
34
- # methods under test. `format` must be a public struct member (not
35
- # Kernel#format) so that verify_partial_doubles can stub it. Story 10.0:
36
- # `#default_render` now keys off `request.accepts` (HTML acceptability), so
37
- # `accepts` is a settable member too.
38
- let(:fake_request) { Struct.new(:headers, :format, :accepts).new({}, nil, []) }
39
- let(:controller) { test_class.new(fake_request) }
40
-
41
- describe "#ruact_manifest" do
42
- # Opt OUT of spec_helper's suite-wide resolver stub here so this proves the
43
- # actual delegation: `#ruact_manifest` resolves through ManifestResolver
44
- # (prod returns the boot-loaded Ruact.manifest; dev fetches over HTTP).
45
- it "delegates manifest resolution to ManifestResolver.resolve" do
46
- test_manifest = ClientManifest.from_hash({})
47
- allow(ManifestResolver).to receive(:resolve).and_return(test_manifest)
48
-
49
- expect(controller.send(:ruact_manifest)).to be test_manifest
50
- expect(ManifestResolver).to have_received(:resolve)
51
- end
52
- end
53
-
54
- describe "#ruact_request?" do
55
- it "returns true when Accept: text/x-component" do
56
- fake_request.headers["Accept"] = "text/x-component"
57
- expect(controller.send(:ruact_request?)).to be true
58
- end
59
-
60
- it "returns true when Accept header includes text/x-component alongside other types" do
61
- fake_request.headers["Accept"] = "text/x-component, */*"
62
- expect(controller.send(:ruact_request?)).to be true
63
- end
64
-
65
- it "returns true when Ruact-Request: 1 header is set" do
66
- fake_request.headers["Ruact-Request"] = "1"
67
- expect(controller.send(:ruact_request?)).to be true
68
- end
69
-
70
- it "returns false when Accept: text/html" do
71
- fake_request.headers["Accept"] = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
72
- expect(controller.send(:ruact_request?)).to be false
73
- end
74
-
75
- it "returns false when no Accept header is set" do
76
- expect(controller.send(:ruact_request?)).to be false
77
- end
78
- end
79
-
80
- describe "#default_render" do
81
- # Story 10.0 — the activation predicate keys off HTML *acceptability*
82
- # (`request.accepts`), not `request.format.html?`. `request.format.html?`
83
- # is false for a `*/*` wildcard, which 500'd those requests pre-10.0.
84
- before do
85
- allow(controller).to receive(:ruact_template_exists?).and_return(true)
86
- allow(controller).to receive(:ruact_render)
87
- end
88
-
89
- it "calls ruact_render when the client accepts text/html and template exists (AC#1)" do
90
- fake_request.accepts = [Mime[:html]]
91
- controller.send(:default_render)
92
- expect(controller).to have_received(:ruact_render)
93
- end
94
-
95
- it "calls ruact_render for a */* wildcard Accept — Mime::ALL (Story 10.0 AC#1)" do
96
- fake_request.accepts = [Mime::ALL]
97
- controller.send(:default_render)
98
- expect(controller).to have_received(:ruact_render)
99
- end
100
-
101
- it "calls ruact_render when Accept is blank (defaults to HTML) (Story 10.0 AC#1)" do
102
- fake_request.accepts = []
103
- controller.send(:default_render)
104
- expect(controller).to have_received(:ruact_render)
105
- end
106
-
107
- it "calls ruact_render for RSC requests (text/x-component) even when HTML is unacceptable (AC#1)" do
108
- fake_request.accepts = [Mime[:json]]
109
- fake_request.headers["Ruact-Request"] = "1"
110
- controller.send(:default_render)
111
- expect(controller).to have_received(:ruact_render)
112
- end
113
-
114
- it "does NOT call ruact_render for a concrete non-HTML Accept that is not RSC (AC#4 — FR26)" do
115
- fake_request.accepts = [Mime[:json]]
116
- begin
117
- controller.send(:default_render)
118
- rescue StandardError
119
- nil
120
- end
121
- expect(controller).not_to have_received(:ruact_render)
122
- end
123
-
124
- it "does NOT call ruact_render when no template exists" do
125
- allow(controller).to receive(:ruact_template_exists?).and_return(false)
126
- fake_request.accepts = [Mime[:html]]
127
- begin
128
- controller.send(:default_render)
129
- rescue StandardError
130
- nil
131
- end
132
- expect(controller).not_to have_received(:ruact_render)
133
- end
134
- end
135
-
136
- describe "#redirect_to" do
137
- # Build a class hierarchy so `super` inside the override can call a base implementation.
138
- # url_for and render must be defined for verify_partial_doubles to allow stubbing them.
139
- let(:redirect_test_class) do
140
- base = Class.new do
141
- def redirect_to(*)
142
- :super_called
143
- end
144
-
145
- def url_for(options)
146
- options.is_a?(String) ? options : "/"
147
- end
148
-
149
- def render(**_opts); end
150
- end
151
- Class.new(base) do
152
- include Ruact::Controller
153
-
154
- attr_reader :request
155
-
156
- def initialize(req)
157
- super()
158
- @request = req
159
- end
160
- end
161
- end
162
-
163
- let(:ruact_ctrl) do
164
- redirect_test_class.new(Struct.new(:headers, :host).new({ "Accept" => "text/x-component" }, "localhost"))
165
- end
166
- let(:html_ctrl) do
167
- redirect_test_class.new(Struct.new(:headers, :host).new({}, "localhost"))
168
- end
169
-
170
- context "when RSC request with same-origin URL (AC #1, #5)" do
171
- before { allow(ruact_ctrl).to receive(:url_for).and_return("/posts/1") }
172
-
173
- it "calls render with a Flight redirect row (not a 302)" do
174
- allow(ruact_ctrl).to receive(:render)
175
- ruact_ctrl.send(:redirect_to, "/posts/1")
176
- expect(ruact_ctrl).to have_received(:render).with(
177
- plain: "0:{\"redirectUrl\":\"/posts/1\",\"redirectType\":\"push\"}\n",
178
- content_type: "text/x-component"
179
- )
180
- end
181
-
182
- it "redirect row matches the flight fixture (AC #5)" do
183
- rendered_plain = nil
184
- allow(ruact_ctrl).to receive(:render) { |opts| rendered_plain = opts[:plain] }
185
- ruact_ctrl.send(:redirect_to, "/posts/1")
186
- expect(rendered_plain).to match_flight_fixture("redirect_row")
187
- end
188
- end
189
-
190
- context "when RSC request with external URL (AC #3)" do
191
- before { allow(ruact_ctrl).to receive(:url_for).and_return("https://external.com/page") }
192
-
193
- it "does NOT emit a redirect row (falls back to super)" do
194
- allow(ruact_ctrl).to receive(:render)
195
- ruact_ctrl.send(:redirect_to, "https://external.com/page")
196
- expect(ruact_ctrl).not_to have_received(:render)
197
- end
198
- end
199
-
200
- context "when non-RSC request (AC #4)" do
201
- before { allow(html_ctrl).to receive(:url_for).and_return("/posts/1") }
202
-
203
- it "does NOT emit a redirect row (falls back to super)" do
204
- allow(html_ctrl).to receive(:render)
205
- html_ctrl.send(:redirect_to, "/posts/1")
206
- expect(html_ctrl).not_to have_received(:render)
207
- end
208
- end
209
- end
210
-
211
- describe "#ruact_html_shell" do
212
- # Story 14.2 — the entry `<script>` tags are emitted by the mixed-in
213
- # ViewHelper#ruact_vite_tags (which needs Rails.env / a live Vite probe).
214
- # Stub it to "" so we test the shell structure + the __FLIGHT_DATA script
215
- # (still emitted by ruact_js_assets) in isolation.
216
- before { allow(controller).to receive(:ruact_vite_tags).and_return("") }
217
-
218
- let(:payload) { "0:[\"$\",\"div\",null,{}]\n" }
219
-
220
- it "returns a string containing window.__FLIGHT_DATA" do
221
- html = controller.send(:ruact_html_shell, payload)
222
- expect(html).to include("__FLIGHT_DATA")
223
- end
224
-
225
- it "wraps the payload in an IIFE push" do
226
- html = controller.send(:ruact_html_shell, payload)
227
- expect(html).to include("d.push(")
228
- end
229
-
230
- it "contains a root div#root element" do
231
- html = controller.send(:ruact_html_shell, payload)
232
- expect(html).to include('<div id="root">')
233
- end
234
-
235
- it "escapes </script> in the payload to prevent XSS breakout" do
236
- dangerous_payload = "0:\"</script><script>alert(1)</script>\"\n"
237
- html = controller.send(:ruact_html_shell, dangerous_payload)
238
- # The HTML must contain exactly ONE </script> — the real closing tag of the script block.
239
- # If the payload's </script> leaked through, there would be more than one.
240
- occurrences = html.scan("</script>")
241
- count = occurrences.length
242
- expect(count).to eq(1), "Expected 1 </script> (closing tag), found #{count}"
243
- end
244
-
245
- # Story 8.3 review R7 — the shell must surface the host's CSRF token
246
- # as a `<meta name="csrf-token">` tag so the JS runtime can forward
247
- # it as `X-CSRF-Token` on every `_makeRef` call. Without this,
248
- # standalone server actions (Story 8.3 AC5) — for which the gem
249
- # enforces CSRF itself via `protect_from_forgery with: :exception,
250
- # if: :dispatching_standalone?` — can never authenticate, because
251
- # the document has no token for the runtime to read.
252
- context "Story 8.3 — CSRF meta tag injection", :story_8_3 do
253
- # The bare `test_class` above is a minimal `include Ruact::Controller`
254
- # consumer with no `form_authenticity_token` surface (no Rails
255
- # request-forgery-protection module mixed in). Define a real method
256
- # on the class so `respond_to?(:form_authenticity_token, true)` is
257
- # true AND `verify_partial_doubles` lets us stub the return value.
258
- let(:csrf_test_class) do
259
- Class.new(test_class) do
260
- def form_authenticity_token
261
- "stub-default-token"
262
- end
263
- end
264
- end
265
- let(:csrf_controller) { csrf_test_class.new(fake_request) }
266
-
267
- # Story 14.2 — csrf_controller is a separate instance from `controller`,
268
- # so the outer `before` stub does not reach it. Stub its entry-tag
269
- # emission too (the real path would hit Rails.root in a non-booted env),
270
- # keeping these CSRF tests order-independent.
271
- before { allow(csrf_controller).to receive(:ruact_vite_tags).and_return("") }
272
-
273
- it "embeds <meta name=\"csrf-token\" content=\"...\"> when the host exposes form_authenticity_token" do
274
- allow(csrf_controller).to receive(:form_authenticity_token).and_return("test-csrf-token-value")
275
- html = csrf_controller.send(:ruact_html_shell, payload)
276
- expect(html).to include('<meta name="csrf-token" content="test-csrf-token-value" />')
277
- end
278
-
279
- it "HTML-escapes the token value (defense against accidental quote injection)" do
280
- allow(csrf_controller).to receive(:form_authenticity_token).and_return('"quoted" & <evil>')
281
- html = csrf_controller.send(:ruact_html_shell, payload)
282
- expect(html).to include("&quot;quoted&quot; &amp; &lt;evil&gt;")
283
- expect(html).not_to include('"quoted" & <evil>')
284
- end
285
-
286
- it "omits the meta tag (renders empty string) when form_authenticity_token is not available " \
287
- "(e.g., a non-Rails spec context)" do
288
- # `controller` is the bare test_class without `form_authenticity_token`.
289
- html = controller.send(:ruact_html_shell, payload)
290
- expect(html).not_to include('name="csrf-token"')
291
- end
292
-
293
- it "omits the meta tag when form_authenticity_token returns nil/empty" do
294
- allow(csrf_controller).to receive(:form_authenticity_token).and_return("")
295
- html = csrf_controller.send(:ruact_html_shell, payload)
296
- expect(html).not_to include('name="csrf-token"')
297
- end
298
-
299
- it "silently omits the meta tag if form_authenticity_token raises " \
300
- "(e.g., session middleware missing in a stripped-down test env)" do
301
- allow(csrf_controller).to receive(:form_authenticity_token).and_raise(StandardError, "no session")
302
- expect { csrf_controller.send(:ruact_html_shell, payload) }.not_to raise_error
303
- html = csrf_controller.send(:ruact_html_shell, payload)
304
- expect(html).not_to include('name="csrf-token"')
305
- end
306
- end
307
- end
308
-
309
- # Story 14.2 (FR104) — the controller's HTML shell delegates its JS asset
310
- # markup (entry tags + __FLIGHT_DATA) to the single
311
- # Ruact::ViewHelper#ruact_js_assets implementation. No duplicated tag logic;
312
- # controller output == a view helper's output (parity guards against drift).
313
- describe "ruact_js_assets delegation + parity", :story_14_2 do
314
- let(:payload) { "0:[\"$\",\"div\",null,{}]\n" }
315
-
316
- it "delegates the JS block to ruact_js_assets (single implementation)" do
317
- allow(controller).to receive(:ruact_js_assets).with(payload).and_return("<!--RUACT-JS-->".html_safe)
318
- html = controller.send(:ruact_html_shell, payload)
319
- expect(html).to include("<!--RUACT-JS-->")
320
- expect(controller).to have_received(:ruact_js_assets).with(payload)
321
- end
322
-
323
- it "emits markup byte-identical to a standalone view helper (no drift)" do
324
- allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new("production"))
325
- manifest_entry = { "file" => "bootstrap-xyz789.js" }
326
- allow(controller).to receive(:vite_manifest_entry).and_return(manifest_entry)
327
-
328
- view = Object.new
329
- view.extend(Ruact::ViewHelper)
330
- allow(view).to receive(:vite_manifest_entry).and_return(manifest_entry)
331
-
332
- expect(controller.send(:ruact_js_assets, payload)).to eq(view.ruact_js_assets(payload))
333
- end
334
-
335
- it "exposes ruact_js_assets as PRIVATE on the controller (never a routable action)", :aggregate_failures do
336
- expect(controller.class.public_method_defined?(:ruact_js_assets)).to be false
337
- expect(controller.class.private_method_defined?(:ruact_js_assets)).to be true
338
- # __ruact_component__ is likewise demoted so it is not exposed as an action.
339
- expect(controller.class.public_method_defined?(:__ruact_component__)).to be false
340
- end
341
- end
342
- end
343
- end