ruact 0.0.4 → 0.0.6
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 +4 -4
- data/.github/workflows/ci.yml +54 -2
- data/.rubocop_todo.yml +3 -115
- data/CHANGELOG.md +68 -17
- data/bench/server_functions_dispatch_bench.rb +109 -142
- data/bench/server_functions_dispatch_bench.results.md +29 -0
- data/docs/internal/decisions/server-functions-api.md +402 -0
- data/lib/generators/ruact/install/install_generator.rb +310 -25
- data/lib/generators/ruact/install/templates/Procfile.dev.tt +2 -0
- data/lib/generators/ruact/install/templates/dev.tt +16 -0
- data/lib/generators/ruact/install/templates/package.json.tt +17 -0
- data/lib/generators/ruact/install/templates/vite.config.js.tt +3 -1
- data/lib/generators/ruact/scaffold/scaffold_attribute.rb +114 -0
- data/lib/generators/ruact/scaffold/scaffold_form_helpers.rb +114 -0
- data/lib/generators/ruact/scaffold/scaffold_generator.rb +491 -0
- data/lib/generators/ruact/scaffold/scaffold_shadcn_preflight.rb +229 -0
- data/lib/generators/ruact/scaffold/templates/components/DeleteDialog.tsx.tt +104 -0
- data/lib/generators/ruact/scaffold/templates/components/Form.tsx.tt +212 -0
- data/lib/generators/ruact/scaffold/templates/components/List.tsx.tt +338 -0
- data/lib/generators/ruact/scaffold/templates/components/agnostic/DeleteDialog.tsx.tt +92 -0
- data/lib/generators/ruact/scaffold/templates/components/agnostic/Form.tsx.tt +174 -0
- data/lib/generators/ruact/scaffold/templates/components/agnostic/List.tsx.tt +253 -0
- data/lib/generators/ruact/scaffold/templates/controller.rb.tt +130 -0
- data/lib/generators/ruact/scaffold/templates/queries/application_query.rb.tt +13 -0
- data/lib/generators/ruact/scaffold/templates/queries/query.rb.tt +59 -0
- data/lib/generators/ruact/scaffold/templates/request_spec.rb.tt +77 -0
- data/lib/generators/ruact/scaffold/templates/views/edit.html.erb.tt +7 -0
- data/lib/generators/ruact/scaffold/templates/views/index.html.erb.tt +6 -0
- data/lib/generators/ruact/scaffold/templates/views/new.html.erb.tt +5 -0
- data/lib/generators/ruact/scaffold/templates/views/show.html.erb.tt +16 -0
- data/lib/ruact/client_manifest.rb +37 -36
- data/lib/ruact/component_contract.rb +115 -0
- data/lib/ruact/configuration.rb +80 -28
- data/lib/ruact/controller.rb +69 -421
- data/lib/ruact/doctor.rb +133 -4
- data/lib/ruact/erb_preprocessor.rb +65 -12
- data/lib/ruact/erb_preprocessor_hook.rb +4 -1
- data/lib/ruact/errors.rb +30 -44
- data/lib/ruact/html_converter.rb +22 -1
- data/lib/ruact/railtie.rb +56 -200
- data/lib/ruact/server.rb +28 -6
- data/lib/ruact/server_functions/codegen.rb +49 -188
- data/lib/ruact/server_functions/codegen_v2.rb +23 -6
- data/lib/ruact/server_functions/codegen_v2_query_params.rb +140 -0
- data/lib/ruact/server_functions/error_payload.rb +1 -1
- data/lib/ruact/server_functions/error_rendering.rb +22 -29
- data/lib/ruact/server_functions/name_bridge.rb +14 -16
- data/lib/ruact/server_functions/query_source.rb +35 -8
- data/lib/ruact/server_functions/route_source.rb +3 -4
- data/lib/ruact/server_functions/snapshot.rb +12 -139
- data/lib/ruact/server_functions/validation_errors.rb +70 -0
- data/lib/ruact/server_functions.rb +21 -25
- data/lib/ruact/signed_references.rb +162 -0
- data/lib/ruact/string_distance.rb +72 -0
- data/lib/ruact/validation_errors_collector.rb +139 -0
- data/lib/ruact/version.rb +1 -1
- data/lib/ruact/view_helper.rb +102 -0
- data/lib/ruact.rb +19 -19
- data/lib/tasks/ruact.rake +10 -53
- data/spec/fixtures/story_7_9_views/controller_request_spec_support/errors_demo/new.html.erb +3 -0
- data/spec/ruact/client_manifest_spec.rb +36 -0
- data/spec/ruact/component_contract_spec.rb +119 -0
- data/spec/ruact/configuration_spec.rb +51 -34
- data/spec/ruact/controller_request_spec.rb +264 -0
- data/spec/ruact/controller_spec.rb +63 -326
- data/spec/ruact/doctor_spec.rb +201 -0
- data/spec/ruact/erb_preprocessor_hook_spec.rb +4 -1
- data/spec/ruact/erb_preprocessor_spec.rb +127 -0
- data/spec/ruact/errors_spec.rb +0 -45
- data/spec/ruact/html_converter_spec.rb +50 -0
- data/spec/ruact/install_generator_spec.rb +591 -4
- data/spec/ruact/query_request_spec.rb +109 -1
- data/spec/ruact/scaffold_generator_spec.rb +1835 -0
- data/spec/ruact/server_bucket_request_spec.rb +142 -0
- data/spec/ruact/server_function_name_spec.rb +1 -1
- data/spec/ruact/server_functions/codegen_spec.rb +158 -269
- data/spec/ruact/server_functions/name_bridge_spec.rb +9 -9
- data/spec/ruact/server_functions/query_source_spec.rb +51 -0
- data/spec/ruact/server_functions/railtie_integration_spec.rb +71 -268
- data/spec/ruact/server_functions/rake_spec.rb +29 -29
- data/spec/ruact/server_functions/snapshot_spec.rb +53 -213
- data/spec/ruact/server_rescue_request_spec.rb +6 -6
- data/spec/ruact/server_spec.rb +8 -9
- data/spec/ruact/signed_references_spec.rb +164 -0
- data/spec/ruact/string_distance_spec.rb +38 -0
- data/spec/ruact/validation_errors_spec.rb +116 -0
- data/spec/ruact/view_helper_spec.rb +79 -0
- data/spec/spec_helper.rb +0 -5
- data/vendor/javascript/ruact-server-functions-runtime/index.d.ts +44 -47
- data/vendor/javascript/ruact-server-functions-runtime/index.js +151 -107
- data/vendor/javascript/ruact-server-functions-runtime/index.test.mjs +72 -75
- data/vendor/javascript/ruact-server-functions-runtime/package.json +2 -2
- data/vendor/javascript/ruact-server-functions-runtime/usequery.test.mjs +187 -0
- data/vendor/javascript/vite-plugin-ruact/bootstrap.test.mjs +96 -0
- data/vendor/javascript/vite-plugin-ruact/index.js +353 -7
- data/vendor/javascript/vite-plugin-ruact/manifest-contract.test.mjs +182 -0
- data/vendor/javascript/vite-plugin-ruact/package-lock.json +16 -0
- data/vendor/javascript/vite-plugin-ruact/package.json +3 -1
- data/vendor/javascript/vite-plugin-ruact/registry.test.mjs +199 -0
- data/vendor/javascript/vite-plugin-ruact/runtime/bootstrap.jsx +68 -0
- data/vendor/javascript/vite-plugin-ruact/runtime/flight-client.js +235 -0
- data/vendor/javascript/vite-plugin-ruact/runtime/ruact-router.js +473 -0
- data/vendor/javascript/vite-plugin-ruact/server-functions-codegen.mjs +114 -139
- data/vendor/javascript/vite-plugin-ruact/server-functions-codegen.test.mjs +199 -262
- data/vendor/javascript/vite-plugin-ruact/tsconfig.json +18 -0
- data/vendor/javascript/vite-plugin-ruact/tsconfig.scaffold-agnostic.json +18 -0
- data/vendor/javascript/vite-plugin-ruact/tsconfig.scaffold.json +20 -0
- data/vendor/javascript/vite-plugin-ruact/type-tests/emitted-module.test-d.ts +23 -0
- data/vendor/javascript/vite-plugin-ruact/type-tests/scaffold/PostDeleteDialog.tsx +90 -0
- data/vendor/javascript/vite-plugin-ruact/type-tests/scaffold/PostForm.tsx +238 -0
- data/vendor/javascript/vite-plugin-ruact/type-tests/scaffold/PostList.tsx +339 -0
- data/vendor/javascript/vite-plugin-ruact/type-tests/scaffold/agnostic/PostDeleteDialog.tsx +85 -0
- data/vendor/javascript/vite-plugin-ruact/type-tests/scaffold/agnostic/PostForm.tsx +216 -0
- data/vendor/javascript/vite-plugin-ruact/type-tests/scaffold/agnostic/PostList.tsx +269 -0
- data/vendor/javascript/vite-plugin-ruact/type-tests/scaffold/agnostic/ambient.d.ts +78 -0
- data/vendor/javascript/vite-plugin-ruact/type-tests/scaffold/ambient.d.ts +166 -0
- data/vendor/javascript/vite-plugin-ruact/type-tests/typed-query.test-d.ts +48 -0
- data/vendor/javascript/vite-plugin-ruact/type-tests/usequery.test-d.ts +26 -0
- metadata +55 -15
- data/lib/generators/ruact/install/templates/application.jsx.tt +0 -51
- data/lib/ruact/server_action.rb +0 -131
- data/lib/ruact/server_functions/endpoint_controller.rb +0 -237
- data/lib/ruact/server_functions/registry.rb +0 -148
- data/lib/ruact/server_functions/registry_entry.rb +0 -26
- data/lib/ruact/server_functions/standalone_context.rb +0 -103
- data/lib/ruact/server_functions/standalone_dispatcher.rb +0 -178
- data/spec/ruact/server_functions/csrf_request_spec.rb +0 -380
- data/spec/ruact/server_functions/dispatch_request_spec.rb +0 -819
- data/spec/ruact/server_functions/registry_spec.rb +0 -199
- data/spec/ruact/server_functions/standalone_action_spec.rb +0 -224
- data/spec/ruact/server_functions/standalone_context_spec.rb +0 -142
- data/spec/ruact/server_functions/standalone_dispatcher_spec.rb +0 -273
|
@@ -1,199 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "spec_helper"
|
|
4
|
-
|
|
5
|
-
module Ruact
|
|
6
|
-
module ServerFunctions
|
|
7
|
-
RSpec.describe Registry, :story_8_0a do
|
|
8
|
-
subject(:registry) { described_class.new }
|
|
9
|
-
|
|
10
|
-
let(:posts_controller) do
|
|
11
|
-
Class.new { def self.name = "PostsController" }
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
let(:bar_controller) do
|
|
15
|
-
Class.new { def self.name = "BarController" }
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
describe "#register (Story 8.0a)" do
|
|
19
|
-
it "stores a single entry keyed by its Ruby symbol", :aggregate_failures do
|
|
20
|
-
entry = registry.register(:create_post, kind: :action, controller: posts_controller)
|
|
21
|
-
|
|
22
|
-
expect(entry).to be_a(RegistryEntry)
|
|
23
|
-
expect(entry.ruby_symbol).to eq(:create_post)
|
|
24
|
-
expect(entry.js_identifier).to eq("createPost")
|
|
25
|
-
expect(entry.kind).to eq(:action)
|
|
26
|
-
expect(entry.controller).to eq(posts_controller)
|
|
27
|
-
expect(registry.entries.keys).to eq([:create_post])
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
it "captures the implementation block verbatim for downstream invocation" do
|
|
31
|
-
block = -> { :pong }
|
|
32
|
-
entry = registry.register(:demo_ping, kind: :action, controller: posts_controller, &block)
|
|
33
|
-
expect(entry.block).to be(block)
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
it "allows registering an action and a query with the same symbol in separate registries" do
|
|
37
|
-
actions = described_class.new
|
|
38
|
-
queries = described_class.new
|
|
39
|
-
actions.register(:create_post, kind: :action, controller: posts_controller)
|
|
40
|
-
queries.register(:create_post, kind: :query, controller: posts_controller)
|
|
41
|
-
expect(actions.entries.keys).to eq([:create_post])
|
|
42
|
-
expect(queries.entries.keys).to eq([:create_post])
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
it "raises Ruact::ConfigurationError for SCREAMING_SNAKE symbols (Story 8.0a)" do
|
|
46
|
-
expect { registry.register(:RECALCULATE, kind: :action, controller: posts_controller) }
|
|
47
|
-
.to raise_error(Ruact::ConfigurationError) do |error|
|
|
48
|
-
expect(error.message).to include(":RECALCULATE")
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
it "raises Ruact::ConfigurationError on JS-identifier collision and names both " \
|
|
53
|
-
"Ruby symbols and both controllers (Story 8.0a)", :aggregate_failures do
|
|
54
|
-
registry.register(:foo_bar, kind: :action, controller: posts_controller)
|
|
55
|
-
expect { registry.register(:foo__bar, kind: :action, controller: bar_controller) }
|
|
56
|
-
.to raise_error(Ruact::ConfigurationError) do |error|
|
|
57
|
-
expect(error.message).to include(":foo_bar")
|
|
58
|
-
expect(error.message).to include(":foo__bar")
|
|
59
|
-
expect(error.message).to include("PostsController")
|
|
60
|
-
expect(error.message).to include("BarController")
|
|
61
|
-
expect(error.message).to include('"fooBar"')
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
it "allows re-registering the same Ruby symbol (replace semantics, dev reload)" do
|
|
66
|
-
registry.register(:create_post, kind: :action, controller: posts_controller)
|
|
67
|
-
expect do
|
|
68
|
-
registry.register(:create_post, kind: :action, controller: posts_controller)
|
|
69
|
-
end.not_to raise_error
|
|
70
|
-
expect(registry.size).to eq(1)
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
it "tolerates a nil controller (Rails-console registration path)" do
|
|
74
|
-
expect { registry.register(:create_post, kind: :action) }.not_to raise_error
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
it "rejects kinds other than :action / :query (Chunk1 Major 2026-05-13)" do
|
|
78
|
-
expect { registry.register(:create_post, kind: :wat, controller: posts_controller) }
|
|
79
|
-
.to raise_error(Ruact::ConfigurationError) do |error|
|
|
80
|
-
expect(error.message).to include(":create_post")
|
|
81
|
-
expect(error.message).to include("PostsController")
|
|
82
|
-
expect(error.message).to include(":wat")
|
|
83
|
-
expect(error.message).to include("[:action, :query]")
|
|
84
|
-
end
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
it "wraps NameBridge symbol-shape failures with AC7 'invalid server-function " \
|
|
88
|
-
"symbol :SYMBOL in CONTROLLER' framing (Re-run patch m5)" do
|
|
89
|
-
expect { registry.register(:RECALCULATE, kind: :action, controller: posts_controller) }
|
|
90
|
-
.to raise_error(Ruact::ConfigurationError) do |error|
|
|
91
|
-
expect(error.message).to start_with("invalid server-function symbol :RECALCULATE in PostsController")
|
|
92
|
-
end
|
|
93
|
-
end
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
describe "#entries (Story 8.0a)" do
|
|
97
|
-
it "returns a frozen snapshot independent of subsequent mutations" do
|
|
98
|
-
registry.register(:create_post, kind: :action, controller: posts_controller)
|
|
99
|
-
snapshot = registry.entries
|
|
100
|
-
expect(snapshot).to be_frozen
|
|
101
|
-
registry.register(:list_posts, kind: :query, controller: posts_controller)
|
|
102
|
-
expect(snapshot.keys).to eq([:create_post])
|
|
103
|
-
end
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
describe "#clear! (Story 8.0a)" do
|
|
107
|
-
it "wipes all entries and returns self" do
|
|
108
|
-
registry.register(:create_post, kind: :action, controller: posts_controller)
|
|
109
|
-
expect(registry.clear!).to be(registry)
|
|
110
|
-
expect(registry).to be_empty
|
|
111
|
-
end
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
describe "Story 8.3 — mixed controller+standalone collision", :story_8_3 do
|
|
115
|
-
let(:posts_controller_class) do
|
|
116
|
-
Class.new { def self.name = "PostsController" }
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
let(:standalone_create_post_module) do
|
|
120
|
-
Module.new do
|
|
121
|
-
extend Ruact::ServerAction
|
|
122
|
-
|
|
123
|
-
def self.name
|
|
124
|
-
"CreatePost"
|
|
125
|
-
end
|
|
126
|
-
end
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
it "raises Ruact::ConfigurationError when the same Ruby symbol is declared in a controller " \
|
|
130
|
-
"AND in a standalone module — message names BOTH hosts" do
|
|
131
|
-
registry.register(:create_post, kind: :action, controller: posts_controller_class)
|
|
132
|
-
|
|
133
|
-
expect do
|
|
134
|
-
registry.register(:create_post, kind: :action, controller: standalone_create_post_module)
|
|
135
|
-
end.to raise_error(Ruact::ConfigurationError) do |error|
|
|
136
|
-
expect(error.message).to include(":create_post")
|
|
137
|
-
expect(error.message).to include("PostsController")
|
|
138
|
-
expect(error.message).to include("CreatePost")
|
|
139
|
-
expect(error.message).to include("declared in BOTH")
|
|
140
|
-
end
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
it "raises Ruact::ConfigurationError when the same symbol is declared in standalone " \
|
|
144
|
-
"first, then in a controller (order-independent)" do
|
|
145
|
-
registry.register(:create_post, kind: :action, controller: standalone_create_post_module)
|
|
146
|
-
|
|
147
|
-
expect do
|
|
148
|
-
registry.register(:create_post, kind: :action, controller: posts_controller_class)
|
|
149
|
-
end.to raise_error(Ruact::ConfigurationError) do |error|
|
|
150
|
-
expect(error.message).to include("PostsController")
|
|
151
|
-
expect(error.message).to include("CreatePost")
|
|
152
|
-
end
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
it "describe_controller names a Module host correctly (no inspection fallback) " \
|
|
156
|
-
"when one side of the collision is a Module" do
|
|
157
|
-
registry.register(:create_post, kind: :action, controller: standalone_create_post_module)
|
|
158
|
-
another_module = Module.new do
|
|
159
|
-
extend Ruact::ServerAction
|
|
160
|
-
|
|
161
|
-
def self.name
|
|
162
|
-
"AdminCreatePost"
|
|
163
|
-
end
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
# Cross-bridge JS-identifier collision: two DIFFERENT Ruby symbols
|
|
167
|
-
# producing the SAME JS identifier — bridges into `js_identifier ==`
|
|
168
|
-
# branch of detect_collision!. The bridge collapses underscores,
|
|
169
|
-
# so `:create_post` and `:create__post` both → "createPost".
|
|
170
|
-
expect do
|
|
171
|
-
registry.register(:create__post, kind: :action, controller: another_module)
|
|
172
|
-
end.to raise_error(Ruact::ConfigurationError) do |error|
|
|
173
|
-
expect(error.message).to include("CreatePost")
|
|
174
|
-
expect(error.message).to include("AdminCreatePost")
|
|
175
|
-
expect(error.message).to include('"createPost"')
|
|
176
|
-
end
|
|
177
|
-
end
|
|
178
|
-
end
|
|
179
|
-
|
|
180
|
-
describe "Ruact module-level accessors (Story 8.0a)" do
|
|
181
|
-
it "returns two independent Registry singletons" do
|
|
182
|
-
expect(Ruact.action_registry).to be_a(described_class)
|
|
183
|
-
expect(Ruact.query_registry).to be_a(described_class)
|
|
184
|
-
expect(Ruact.action_registry).not_to equal(Ruact.query_registry)
|
|
185
|
-
end
|
|
186
|
-
|
|
187
|
-
it "memoizes the same instance across calls" do
|
|
188
|
-
expect(Ruact.action_registry).to equal(Ruact.action_registry)
|
|
189
|
-
expect(Ruact.query_registry).to equal(Ruact.query_registry)
|
|
190
|
-
end
|
|
191
|
-
|
|
192
|
-
it "both registries are empty at boot (Story 8.1 / 9.1 populate them)" do
|
|
193
|
-
expect(Ruact.action_registry).to be_empty
|
|
194
|
-
expect(Ruact.query_registry).to be_empty
|
|
195
|
-
end
|
|
196
|
-
end
|
|
197
|
-
end
|
|
198
|
-
end
|
|
199
|
-
end
|
|
@@ -1,224 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# Story 8.3 — covers `Ruact::ServerAction` extended onto a Module: AC1
|
|
4
|
-
# (registration shape + no method defined on host module) and AC6 (guard-
|
|
5
|
-
# rail matrix mirroring the controller-DSL path adapted to module context).
|
|
6
|
-
|
|
7
|
-
require "spec_helper"
|
|
8
|
-
|
|
9
|
-
RSpec.describe Ruact::ServerAction, :story_8_3 do
|
|
10
|
-
describe "AC1 — extend Ruact::ServerAction + ruact_action registers a standalone host" do
|
|
11
|
-
it "registers a standalone host in Ruact.action_registry with controller: <the Module>" do
|
|
12
|
-
mod = Module.new do
|
|
13
|
-
extend Ruact::ServerAction
|
|
14
|
-
|
|
15
|
-
def self.name
|
|
16
|
-
"AC1RegistrationModule"
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
ruact_action(:standalone_create_post) { |_params| { ok: true } }
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
entries = Ruact.action_registry.entries
|
|
23
|
-
expect(entries).to include(:standalone_create_post)
|
|
24
|
-
|
|
25
|
-
entry = entries[:standalone_create_post]
|
|
26
|
-
expect(entry).to be_a(Ruact::ServerFunctions::RegistryEntry)
|
|
27
|
-
expect(entry.kind).to eq(:action)
|
|
28
|
-
expect(entry.controller).to be(mod)
|
|
29
|
-
expect(entry.controller).to be_a(Module)
|
|
30
|
-
expect(entry.controller).not_to be_a(Class)
|
|
31
|
-
expect(entry.js_identifier).to eq("standaloneCreatePost")
|
|
32
|
-
expect(entry.block).to be_a(Proc)
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
it "does NOT define an instance method on the host module — the block is reachable " \
|
|
36
|
-
"only through the gem endpoint, never as a Ruby method" do
|
|
37
|
-
mod = Module.new do
|
|
38
|
-
extend Ruact::ServerAction
|
|
39
|
-
|
|
40
|
-
def self.name
|
|
41
|
-
"AC1NoMethodModule"
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
ruact_action(:no_method_exposed) { |_params| nil }
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
expect(mod.respond_to?(:no_method_exposed)).to be(false)
|
|
48
|
-
expect(mod.instance_methods).not_to include(:no_method_exposed)
|
|
49
|
-
expect(mod.singleton_methods).not_to include(:no_method_exposed)
|
|
50
|
-
# Direct dispatch attempts (someone trying to `Mod.send(:no_method_exposed)`
|
|
51
|
-
# should fail with NoMethodError) — proves the surface is endpoint-only.
|
|
52
|
-
expect { mod.send(:no_method_exposed, {}) }.to raise_error(NoMethodError)
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
describe "AC6 — guard-rail matrix (mirror controller-DSL adapted to module context)" do
|
|
57
|
-
let(:host) do
|
|
58
|
-
Module.new do
|
|
59
|
-
extend Ruact::ServerAction
|
|
60
|
-
|
|
61
|
-
def self.name
|
|
62
|
-
"GuardRailModule"
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
it "raises ArgumentError when given a String instead of a Symbol" do
|
|
68
|
-
expect do
|
|
69
|
-
host.module_eval { ruact_action("create_post") { |_p| nil } }
|
|
70
|
-
end.to raise_error(ArgumentError, /ruact_action requires a Symbol/)
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
it "raises ArgumentError when the block is missing" do
|
|
74
|
-
expect { host.module_eval { ruact_action(:create_post) } }
|
|
75
|
-
.to raise_error(ArgumentError, /requires a block/)
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
it "raises ArgumentError when the block accepts no positional argument" do
|
|
79
|
-
expect do
|
|
80
|
-
host.module_eval { ruact_action(:create_post) {} } # no positional arg
|
|
81
|
-
end.to raise_error(ArgumentError, /exactly one positional parameter/)
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
it "raises ArgumentError when the block accepts more than one positional argument" do
|
|
85
|
-
expect do
|
|
86
|
-
host.module_eval { ruact_action(:create_post) { |_a, _b| nil } }
|
|
87
|
-
end.to raise_error(ArgumentError, /exactly one positional parameter/)
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
it "raises ArgumentError when the block has a required keyword argument" do
|
|
91
|
-
block_with_kwarg = ->(_p, required:) { required }
|
|
92
|
-
expect do
|
|
93
|
-
host.module_eval { ruact_action(:create_post, &block_with_kwarg) }
|
|
94
|
-
end.to raise_error(ArgumentError, /no required keyword arguments/)
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
it "ACCEPTS a block with a single positional arg" do
|
|
98
|
-
expect do
|
|
99
|
-
host.module_eval { ruact_action(:create_post) { |_p| nil } }
|
|
100
|
-
end.not_to raise_error
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
it "ACCEPTS a block with a splat positional arg" do
|
|
104
|
-
another_host = Module.new do
|
|
105
|
-
extend Ruact::ServerAction
|
|
106
|
-
|
|
107
|
-
def self.name
|
|
108
|
-
"SplatHost"
|
|
109
|
-
end
|
|
110
|
-
end
|
|
111
|
-
expect do
|
|
112
|
-
another_host.module_eval { ruact_action(:create_post) { |*_args| nil } }
|
|
113
|
-
end.not_to raise_error
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
it "ACCEPTS a block with optional keyword args" do
|
|
117
|
-
another_host = Module.new do
|
|
118
|
-
extend Ruact::ServerAction
|
|
119
|
-
|
|
120
|
-
def self.name
|
|
121
|
-
"OptKwHost"
|
|
122
|
-
end
|
|
123
|
-
end
|
|
124
|
-
expect do
|
|
125
|
-
another_host.module_eval { ruact_action(:create_post) { |_p, key: nil| key } }
|
|
126
|
-
end.not_to raise_error
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
it "raises Ruact::ConfigurationError for a bad naming-bridge symbol (:Create_Post)" do
|
|
130
|
-
expect do
|
|
131
|
-
host.module_eval { ruact_action(:Create_Post) { |_p| nil } }
|
|
132
|
-
end.to raise_error(Ruact::ConfigurationError) do |error|
|
|
133
|
-
expect(error.message).to include(":Create_Post")
|
|
134
|
-
expect(error.message).to include("GuardRailModule")
|
|
135
|
-
end
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
it "raises Ruact::ConfigurationError for a JS-reserved-word target (:class → \"class\")" do
|
|
139
|
-
expect do
|
|
140
|
-
host.module_eval { ruact_action(:class) { |_p| nil } }
|
|
141
|
-
end.to raise_error(Ruact::ConfigurationError, /JS reserved word/)
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
it "raises Ruact::ConfigurationError for a ruact-runtime-reserved target (:revalidate)" do
|
|
145
|
-
expect do
|
|
146
|
-
host.module_eval { ruact_action(:revalidate) { |_p| nil } }
|
|
147
|
-
end.to raise_error(Ruact::ConfigurationError) do |error|
|
|
148
|
-
expect(error.message).to include("revalidate")
|
|
149
|
-
end
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
it "raises Ruact::ConfigurationError for the second standalone host declaring the same symbol" do
|
|
153
|
-
host.module_eval { ruact_action(:dup_symbol) { |_p| nil } }
|
|
154
|
-
second_host = Module.new do
|
|
155
|
-
extend Ruact::ServerAction
|
|
156
|
-
|
|
157
|
-
def self.name
|
|
158
|
-
"SecondHostModule"
|
|
159
|
-
end
|
|
160
|
-
end
|
|
161
|
-
expect do
|
|
162
|
-
second_host.module_eval { ruact_action(:dup_symbol) { |_p| nil } }
|
|
163
|
-
end.to raise_error(Ruact::ConfigurationError) do |error|
|
|
164
|
-
expect(error.message).to include(":dup_symbol")
|
|
165
|
-
expect(error.message).to include("GuardRailModule")
|
|
166
|
-
expect(error.message).to include("SecondHostModule")
|
|
167
|
-
end
|
|
168
|
-
end
|
|
169
|
-
|
|
170
|
-
it "does NOT have a FRAMEWORK_RESERVED_METHODS check — standalone modules can use " \
|
|
171
|
-
"names that would clobber an ActionController method (e.g., :params), since the " \
|
|
172
|
-
"module has no ActionController surface" do
|
|
173
|
-
expect do
|
|
174
|
-
Module.new do
|
|
175
|
-
extend Ruact::ServerAction
|
|
176
|
-
|
|
177
|
-
def self.name
|
|
178
|
-
"FrameworkResvHost"
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
ruact_action(:params) { |_p| nil }
|
|
182
|
-
end
|
|
183
|
-
end.not_to raise_error
|
|
184
|
-
end
|
|
185
|
-
|
|
186
|
-
it "Story 8.3 review R4 — rejects Class hosts at first ruact_action call with a " \
|
|
187
|
-
"documented Ruact::ConfigurationError pointing the dev to `include Ruact::Controller`" do
|
|
188
|
-
klass = Class.new do
|
|
189
|
-
extend Ruact::ServerAction
|
|
190
|
-
|
|
191
|
-
def self.name
|
|
192
|
-
"WronglyExtendedClass"
|
|
193
|
-
end
|
|
194
|
-
end
|
|
195
|
-
|
|
196
|
-
expect do
|
|
197
|
-
klass.class_eval { ruact_action(:bad_host) { |_p| nil } }
|
|
198
|
-
end.to raise_error(Ruact::ConfigurationError) do |error|
|
|
199
|
-
expect(error.message).to include("WronglyExtendedClass")
|
|
200
|
-
expect(error.message).to include("standalone HOST MODULES")
|
|
201
|
-
expect(error.message).to include("include Ruact::Controller")
|
|
202
|
-
end
|
|
203
|
-
end
|
|
204
|
-
|
|
205
|
-
it "does NOT install a method_added hook — a later `def` on the host module does not " \
|
|
206
|
-
"raise (standalone modules don't define action methods at all)" do
|
|
207
|
-
expect do
|
|
208
|
-
Module.new do
|
|
209
|
-
extend Ruact::ServerAction
|
|
210
|
-
|
|
211
|
-
def self.name
|
|
212
|
-
"MethodAddedHost"
|
|
213
|
-
end
|
|
214
|
-
|
|
215
|
-
ruact_action(:registered_action) { |_p| nil }
|
|
216
|
-
|
|
217
|
-
# A later method definition on the module would not trigger anything
|
|
218
|
-
# — the registry holds the block; the module surface is irrelevant.
|
|
219
|
-
define_method(:registered_action) { :unrelated_def }
|
|
220
|
-
end
|
|
221
|
-
end.not_to raise_error
|
|
222
|
-
end
|
|
223
|
-
end
|
|
224
|
-
end
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# Story 8.3 — `Ruact::ServerFunctions::StandaloneContext`: AC3 surface
|
|
4
|
-
# (exposes params/request/session/cookies/headers; render/redirect_to/head
|
|
5
|
-
# raise NoMethodError; current_user memoizes + falls back to env key +
|
|
6
|
-
# raises CurrentUserNotConfiguredError when neither path produces a value).
|
|
7
|
-
|
|
8
|
-
require "spec_helper"
|
|
9
|
-
require "action_controller"
|
|
10
|
-
require "action_dispatch"
|
|
11
|
-
|
|
12
|
-
module Ruact
|
|
13
|
-
module ServerFunctions
|
|
14
|
-
RSpec.describe StandaloneContext, :story_8_3 do
|
|
15
|
-
let(:env) { {} }
|
|
16
|
-
let(:request) do
|
|
17
|
-
ActionDispatch::Request.new(env.merge(
|
|
18
|
-
"REQUEST_METHOD" => "POST",
|
|
19
|
-
"rack.input" => StringIO.new("")
|
|
20
|
-
))
|
|
21
|
-
end
|
|
22
|
-
let(:params) { ActionController::Parameters.new("title" => "Hello") }
|
|
23
|
-
let(:context) { described_class.new(params: params, request: request) }
|
|
24
|
-
|
|
25
|
-
describe "exposed surface (AC3)" do
|
|
26
|
-
it "exposes params (the action-call args, as ActionController::Parameters)" do
|
|
27
|
-
expect(context.params).to be_a(ActionController::Parameters)
|
|
28
|
-
expect(context.params[:title]).to eq("Hello")
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
it "exposes the live request" do
|
|
32
|
-
expect(context.request).to be(request)
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
it "exposes headers via request.headers" do
|
|
36
|
-
expect(context.headers).to be(request.headers)
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
describe "blocked surface — render / redirect_to / head (AC3)" do
|
|
41
|
-
it "render raises NoMethodError with the documented hint" do
|
|
42
|
-
expect { context.render(json: { ok: true }) }
|
|
43
|
-
.to raise_error(NoMethodError, /does not expose `render`/)
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
it "redirect_to raises NoMethodError with the documented hint" do
|
|
47
|
-
expect { context.redirect_to("/login") }
|
|
48
|
-
.to raise_error(NoMethodError, /does not expose `redirect_to`/)
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
it "head raises NoMethodError with the documented hint" do
|
|
52
|
-
expect { context.head(:no_content) }
|
|
53
|
-
.to raise_error(NoMethodError, /does not expose `head`/)
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
describe "current_user resolver path (AC3)" do
|
|
58
|
-
around do |example|
|
|
59
|
-
Ruact.instance_variable_set(:@config, nil)
|
|
60
|
-
Ruact.instance_variable_set(:@configured_at_least_once, false)
|
|
61
|
-
example.run
|
|
62
|
-
ensure
|
|
63
|
-
Ruact.instance_variable_set(:@config, nil)
|
|
64
|
-
Ruact.instance_variable_set(:@configured_at_least_once, false)
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
before do
|
|
68
|
-
# Silence the warn-on-reconfigure noise.
|
|
69
|
-
allow(Rails).to receive(:logger).and_return(Logger.new(IO::NULL))
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
it "raises Ruact::CurrentUserNotConfiguredError when no resolver is configured AND no env key is set" do
|
|
73
|
-
expect { context.current_user }.to raise_error(Ruact::CurrentUserNotConfiguredError) do |err|
|
|
74
|
-
expect(err.message).to include("Ruact.current_user requires Ruact.config.current_user_resolver")
|
|
75
|
-
expect(err.message).to include("Devise")
|
|
76
|
-
expect(err.message).to include("hand-rolled session")
|
|
77
|
-
end
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
it "returns the configured resolver's value, passing request.env to the lambda" do
|
|
81
|
-
user = Struct.new(:id).new(42)
|
|
82
|
-
Ruact.configure do |c|
|
|
83
|
-
c.current_user_resolver = lambda { |env_arg|
|
|
84
|
-
expect(env_arg).to be(request.env)
|
|
85
|
-
user
|
|
86
|
-
}
|
|
87
|
-
end
|
|
88
|
-
expect(context.current_user).to be(user)
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
it "memoizes current_user across multiple calls (resolver invoked once)" do
|
|
92
|
-
call_count = 0
|
|
93
|
-
Ruact.configure do |c|
|
|
94
|
-
c.current_user_resolver = lambda { |_env|
|
|
95
|
-
call_count += 1
|
|
96
|
-
:user
|
|
97
|
-
}
|
|
98
|
-
end
|
|
99
|
-
context.current_user
|
|
100
|
-
context.current_user
|
|
101
|
-
context.current_user
|
|
102
|
-
expect(call_count).to eq(1)
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
it "prefers an upstream-set request.env['ruact.current_user'] over the resolver" do
|
|
106
|
-
env["ruact.current_user"] = :upstream_user
|
|
107
|
-
resolver_called = false
|
|
108
|
-
Ruact.configure do |c|
|
|
109
|
-
c.current_user_resolver = lambda { |_env|
|
|
110
|
-
resolver_called = true
|
|
111
|
-
:resolver_user
|
|
112
|
-
}
|
|
113
|
-
end
|
|
114
|
-
expect(context.current_user).to eq(:upstream_user)
|
|
115
|
-
expect(resolver_called).to be(false)
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
it "falls back to the resolver when the env key is absent (not just nil)" do
|
|
119
|
-
# Pitfall: env.key? differs from env.fetch — a `nil` value should still
|
|
120
|
-
# prefer the env path. Verify by setting nil explicitly.
|
|
121
|
-
env["ruact.current_user"] = nil
|
|
122
|
-
Ruact.configure do |c|
|
|
123
|
-
c.current_user_resolver = ->(_env) { :resolver_user }
|
|
124
|
-
end
|
|
125
|
-
expect(context.current_user).to be_nil
|
|
126
|
-
end
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
describe "__ruact_current_user_read? (Pitfall #4 dev warning flag)" do
|
|
130
|
-
it "is false when the block never reads current_user" do
|
|
131
|
-
expect(context.__ruact_current_user_read?).to be(false)
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
it "flips to true when the block reads current_user" do
|
|
135
|
-
Ruact.configure { |c| c.current_user_resolver = ->(_env) { :u } }
|
|
136
|
-
context.current_user
|
|
137
|
-
expect(context.__ruact_current_user_read?).to be(true)
|
|
138
|
-
end
|
|
139
|
-
end
|
|
140
|
-
end
|
|
141
|
-
end
|
|
142
|
-
end
|