ruact 0.0.10 → 0.0.12

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 (116) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +123 -48
  3. data/CONTRIBUTING.md +233 -0
  4. data/README.md +16 -6
  5. data/RELEASING.md +125 -139
  6. data/lib/ruact/packaging.rb +68 -0
  7. data/lib/ruact/version.rb +1 -1
  8. data/vendor/javascript/vite-plugin-ruact/flight-client.test.mjs +321 -0
  9. data/vendor/javascript/vite-plugin-ruact/package-lock.json +11 -0
  10. data/vendor/javascript/vite-plugin-ruact/package.json +1 -0
  11. data/vendor/javascript/vite-plugin-ruact/runtime/flight-client.js +14 -3
  12. metadata +6 -107
  13. data/.codecov.yml +0 -31
  14. data/.github/workflows/ci.yml +0 -284
  15. data/.github/workflows/server-functions-bench.yml +0 -54
  16. data/.rubocop.yml +0 -107
  17. data/.rubocop_todo.yml +0 -63
  18. data/Rakefile +0 -10
  19. data/bench/server_functions_dispatch_bench.rb +0 -276
  20. data/bench/server_functions_dispatch_bench.results.md +0 -150
  21. data/docs/internal/README.md +0 -9
  22. data/docs/internal/decisions/server-functions-api.md +0 -2236
  23. data/spec/benchmarks/baseline.json +0 -1
  24. data/spec/benchmarks/render_pipeline_benchmark_spec.rb +0 -92
  25. data/spec/fixtures/flight/README.md +0 -136
  26. data/spec/fixtures/flight/array.txt +0 -1
  27. data/spec/fixtures/flight/as_json_object.txt +0 -2
  28. data/spec/fixtures/flight/bigint.txt +0 -1
  29. data/spec/fixtures/flight/boolean_false.txt +0 -1
  30. data/spec/fixtures/flight/boolean_true.txt +0 -1
  31. data/spec/fixtures/flight/client_component_with_props.txt +0 -2
  32. data/spec/fixtures/flight/client_reference.txt +0 -2
  33. data/spec/fixtures/flight/hash.txt +0 -1
  34. data/spec/fixtures/flight/infinity.txt +0 -1
  35. data/spec/fixtures/flight/nan.txt +0 -1
  36. data/spec/fixtures/flight/negative_infinity.txt +0 -1
  37. data/spec/fixtures/flight/nil.txt +0 -1
  38. data/spec/fixtures/flight/number_float.txt +0 -1
  39. data/spec/fixtures/flight/number_integer.txt +0 -1
  40. data/spec/fixtures/flight/react_element_no_props.txt +0 -1
  41. data/spec/fixtures/flight/redirect_row.txt +0 -1
  42. data/spec/fixtures/flight/serializable_object.txt +0 -2
  43. data/spec/fixtures/flight/string_basic.txt +0 -1
  44. data/spec/fixtures/flight/string_dollar_escape.txt +0 -1
  45. data/spec/fixtures/flight/undefined.txt +0 -1
  46. data/spec/fixtures/readme/children-error.html.erb +0 -3
  47. data/spec/fixtures/readme/children-error.txt +0 -1
  48. data/spec/fixtures/story_7_9_views/controller_request_spec_support/demo/show.html.erb +0 -3
  49. data/spec/fixtures/story_7_9_views/controller_request_spec_support/errors_demo/new.html.erb +0 -3
  50. data/spec/fixtures/story_7_9_views/controller_request_spec_support/exploding_layout_demo/show.html.erb +0 -3
  51. data/spec/fixtures/story_7_9_views/controller_request_spec_support/ghost_layout_demo/show.html.erb +0 -3
  52. data/spec/fixtures/story_7_9_views/controller_request_spec_support/layout_demo/show.html.erb +0 -3
  53. data/spec/fixtures/story_7_9_views/controller_request_spec_support/rootless_layout_demo/show.html.erb +0 -3
  54. data/spec/fixtures/story_7_9_views/controller_request_spec_support/unwired_layout_demo/show.html.erb +0 -3
  55. data/spec/fixtures/story_7_9_views/layouts/bare_host.html.erb +0 -16
  56. data/spec/fixtures/story_7_9_views/layouts/exploding_host.html.erb +0 -24
  57. data/spec/fixtures/story_7_9_views/layouts/rootless_host.html.erb +0 -15
  58. data/spec/fixtures/story_7_9_views/layouts/ruact_host.html.erb +0 -17
  59. data/spec/readme_demo_message_spec.rb +0 -67
  60. data/spec/readme_spec.rb +0 -282
  61. data/spec/ruact/client_manifest_spec.rb +0 -270
  62. data/spec/ruact/component_contract_spec.rb +0 -119
  63. data/spec/ruact/configuration_spec.rb +0 -518
  64. data/spec/ruact/controller_request_spec.rb +0 -671
  65. data/spec/ruact/controller_spec.rb +0 -343
  66. data/spec/ruact/doctor_spec.rb +0 -769
  67. data/spec/ruact/erb_preprocessor_hook_spec.rb +0 -55
  68. data/spec/ruact/erb_preprocessor_spec.rb +0 -361
  69. data/spec/ruact/errors_spec.rb +0 -93
  70. data/spec/ruact/flight/renderer_spec.rb +0 -133
  71. data/spec/ruact/flight/serializer_spec.rb +0 -494
  72. data/spec/ruact/html_converter_spec.rb +0 -375
  73. data/spec/ruact/install_generator_spec.rb +0 -1549
  74. data/spec/ruact/layout_source_spec.rb +0 -108
  75. data/spec/ruact/manifest_resolver_spec.rb +0 -174
  76. data/spec/ruact/query_request_spec.rb +0 -706
  77. data/spec/ruact/query_spec.rb +0 -105
  78. data/spec/ruact/railtie_spec.rb +0 -155
  79. data/spec/ruact/render_context_spec.rb +0 -58
  80. data/spec/ruact/render_pipeline_concurrency_spec.rb +0 -78
  81. data/spec/ruact/render_pipeline_spec.rb +0 -928
  82. data/spec/ruact/scaffold_generator_spec.rb +0 -1849
  83. data/spec/ruact/serializable_spec.rb +0 -179
  84. data/spec/ruact/server_bucket_request_spec.rb +0 -785
  85. data/spec/ruact/server_function_name_spec.rb +0 -53
  86. data/spec/ruact/server_functions/backtrace_cleaner_spec.rb +0 -63
  87. data/spec/ruact/server_functions/bucket_two_payload_spec.rb +0 -200
  88. data/spec/ruact/server_functions/codegen_spec.rb +0 -397
  89. data/spec/ruact/server_functions/error_payload_spec.rb +0 -222
  90. data/spec/ruact/server_functions/error_suggestion_spec.rb +0 -79
  91. data/spec/ruact/server_functions/introspection_spec.rb +0 -135
  92. data/spec/ruact/server_functions/name_bridge_spec.rb +0 -212
  93. data/spec/ruact/server_functions/query_context_spec.rb +0 -72
  94. data/spec/ruact/server_functions/query_source_spec.rb +0 -193
  95. data/spec/ruact/server_functions/railtie_integration_spec.rb +0 -215
  96. data/spec/ruact/server_functions/rake_spec.rb +0 -86
  97. data/spec/ruact/server_functions/route_source_spec.rb +0 -202
  98. data/spec/ruact/server_functions/snapshot_spec.rb +0 -96
  99. data/spec/ruact/server_functions/snapshot_writer_spec.rb +0 -71
  100. data/spec/ruact/server_rescue_request_spec.rb +0 -416
  101. data/spec/ruact/server_spec.rb +0 -179
  102. data/spec/ruact/server_upload_request_spec.rb +0 -311
  103. data/spec/ruact/signed_references_spec.rb +0 -164
  104. data/spec/ruact/string_distance_spec.rb +0 -38
  105. data/spec/ruact/tasks_json_introspection_spec.rb +0 -141
  106. data/spec/ruact/testing/have_ruact_component_spec.rb +0 -170
  107. data/spec/ruact/testing/no_production_load_spec.rb +0 -41
  108. data/spec/ruact/validation_errors_spec.rb +0 -116
  109. data/spec/ruact/view_helper_spec.rb +0 -131
  110. data/spec/spec_helper.rb +0 -77
  111. data/spec/support/fixtures/pixel.png +0 -0
  112. data/spec/support/flight_wire_parser.rb +0 -21
  113. data/spec/support/flight_wire_parser_spec.rb +0 -93
  114. data/spec/support/matchers/flight_fixture_matcher.rb +0 -130
  115. data/spec/support/matchers/flight_fixture_matcher_spec.rb +0 -250
  116. data/spec/support/rails_stub.rb +0 -115
@@ -1,79 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "spec_helper"
4
- require "ruact/server_functions/error_suggestion"
5
-
6
- module Ruact
7
- module ServerFunctions
8
- RSpec.describe ErrorSuggestion, :story_8_4 do
9
- # Test fixtures using class-name spoofing so the module can be exercised
10
- # without requiring ActiveRecord or ActionController to be loaded
11
- # (Pitfall #4).
12
- def error_with_class_name(name)
13
- klass = Class.new(StandardError) do
14
- define_singleton_method(:name) { name }
15
- end
16
- klass.new("fixture")
17
- end
18
-
19
- describe "Story 8.4 — .for returns the NFR30-mandated suggestion" do
20
- it "returns the RecordInvalid suggestion for ActiveRecord::RecordInvalid" do
21
- err = error_with_class_name("ActiveRecord::RecordInvalid")
22
- expect(described_class.for(err))
23
- .to eq("Validation failed — check the model's `validates` rules")
24
- end
25
-
26
- it "returns the CSRF suggestion for ActionController::InvalidAuthenticityToken" do
27
- err = error_with_class_name("ActionController::InvalidAuthenticityToken")
28
- expect(described_class.for(err))
29
- .to eq(
30
- "CSRF token mismatch — ensure the page was rendered after the most recent server restart and the session cookie is intact"
31
- )
32
- end
33
-
34
- it "returns nil for StandardError" do
35
- expect(described_class.for(StandardError.new("boom"))).to be_nil
36
- end
37
-
38
- it "returns nil for RuntimeError" do
39
- expect(described_class.for(RuntimeError.new("boom"))).to be_nil
40
- end
41
-
42
- it "returns nil for ArgumentError" do
43
- expect(described_class.for(ArgumentError.new("boom"))).to be_nil
44
- end
45
-
46
- it "returns nil for a custom user exception class" do
47
- custom_class = Class.new(StandardError) { define_singleton_method(:name) { "MyApp::PaymentDeclined" } }
48
- expect(described_class.for(custom_class.new("declined"))).to be_nil
49
- end
50
- end
51
-
52
- describe "Story 8.5 — UploadTooLargeError suggestion", :story_8_5 do
53
- it "returns the upload-too-large suggestion for Ruact::UploadTooLargeError" do
54
- err = Ruact::UploadTooLargeError.new(received_bytes: 11_000_000, limit_bytes: 10_485_760)
55
- expect(described_class.for(err))
56
- .to eq(
57
- "Upload exceeded the configured size limit. " \
58
- "Increase Ruact.config.max_upload_bytes or use Active Storage Direct Upload / " \
59
- "a presigned S3 URL for large files."
60
- )
61
- end
62
-
63
- it "has a SUGGESTIONS entry keyed by the exception class name" do
64
- expect(described_class::SUGGESTIONS).to have_key("Ruact::UploadTooLargeError")
65
- end
66
- end
67
-
68
- describe "Story 8.4 — SUGGESTIONS constant" do
69
- it "is frozen so runtime mutation cannot extend the table" do
70
- expect(described_class::SUGGESTIONS).to be_frozen
71
- end
72
-
73
- it "uses class-name strings as keys (not constants)" do
74
- expect(described_class::SUGGESTIONS.keys).to all(be_a(String))
75
- end
76
- end
77
- end
78
- end
79
- end
@@ -1,135 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "spec_helper"
4
- require "active_support/all"
5
- require "action_dispatch"
6
- require "ruact/server_functions"
7
-
8
- module Ruact
9
- module ServerFunctions
10
- # Plain query class — only `instance_method(...).parameters` (kwargs) and
11
- # `.name` (collision origin) are read; no Rails boot needed. Defined at module
12
- # level (mirrors query_source_spec) so it is not an example-group-leaky
13
- # constant.
14
- class CatalogIntrospectQ
15
- def categories; end
16
-
17
- def search_users(term:, limit: 10); end
18
- end
19
-
20
- # Story 15.3 (FR107) — `ruact:routes --json` introspection. Exercised through
21
- # a REAL RouteSet carrying both a mutation action and mounted query routes,
22
- # with the host/query resolvers injected (no controller boot), so the document
23
- # is proven byte-derived from RouteSource + QuerySource (the same single
24
- # source of truth codegen consumes) and side-effect-free.
25
- RSpec.describe Introspection, :story_15_3 do
26
- let(:query_prefix) { QuerySource::QUERY_CONTROLLER_PREFIX }
27
-
28
- # A real RouteSet with a mutation action (createPost, updatePost) AND two
29
- # mounted query GET routes under the query dispatch controller prefix.
30
- def combined_route_set
31
- prefix = query_prefix
32
- rs = ActionDispatch::Routing::RouteSet.new
33
- rs.draw do
34
- resources :posts, only: %i[create update]
35
- get "/q/categories", to: "#{prefix}catalog_introspect_q#categories"
36
- get "/q/search_users", to: "#{prefix}catalog_introspect_q#search_users"
37
- end
38
- rs
39
- end
40
-
41
- def as_json(route_set)
42
- described_class.as_json(
43
- route_set: route_set,
44
- host_predicate: ->(_controller) { true },
45
- query_class_for: lambda { |controller|
46
- controller.start_with?(query_prefix) ? CatalogIntrospectQ : nil
47
- }
48
- )
49
- end
50
-
51
- describe ".as_json" do
52
- subject(:document) { as_json(combined_route_set) }
53
-
54
- it "gates the document with the EXPERIMENTAL schema_version (0)", :aggregate_failures do
55
- expect(document["schema_version"]).to eq(0)
56
- expect(document["schema_version"]).to eq(described_class::SCHEMA_VERSION)
57
- end
58
-
59
- it "round-trips through JSON.parse" do
60
- expect(JSON.parse(JSON.generate(document))).to eq(document)
61
- end
62
-
63
- it "reports BOTH action and query accessors sorted by name", :aggregate_failures do
64
- names = document["accessors"].map { |a| a["accessor"] }
65
- expect(names).to include("createPost", "updatePost", "categories", "searchUsers")
66
- expect(names).to eq(names.sort)
67
- end
68
-
69
- it "shapes an action with its required path segments as declared params", :aggregate_failures do
70
- update = document["accessors"].find { |a| a["accessor"] == "updatePost" }
71
- expect(update["kind"]).to eq("action")
72
- expect(update["verb"]).to eq("PATCH")
73
- expect(update["path"]).to eq("/posts/:id")
74
- expect(update["segments"]).to eq(["id"])
75
- expect(update["params"]).to eq([{ "name" => "id", "required" => true }])
76
- end
77
-
78
- it "shapes a query with its declared kwargs as params (Story 13.4)", :aggregate_failures do
79
- search = document["accessors"].find { |a| a["accessor"] == "searchUsers" }
80
- expect(search["kind"]).to eq("query")
81
- expect(search["verb"]).to eq("GET")
82
- expect(search["path"]).to eq("/q/search_users")
83
- expect(search["segments"]).to eq([])
84
- expect(search["params"]).to eq(
85
- [{ "name" => "term", "required" => true }, { "name" => "limit", "required" => false }]
86
- )
87
- end
88
-
89
- it "carries exactly the AC2 keys per accessor" do
90
- document["accessors"].each do |accessor|
91
- expect(accessor.keys).to contain_exactly("accessor", "kind", "verb", "path", "segments", "params")
92
- end
93
- end
94
-
95
- it "does NOT include a per-accessor component-contract link (D3 = report accessors faithfully)" do
96
- document["accessors"].each do |accessor|
97
- expect(accessor).not_to have_key("contract")
98
- expect(accessor).not_to have_key("has_contract")
99
- expect(accessor).not_to have_key("contract_exists")
100
- end
101
- end
102
- end
103
-
104
- describe "side-effect-free (AC2 / AC4d — writes NO bridge or TS)" do
105
- it "never writes the codegen bridge / TS module", :aggregate_failures do
106
- allow(SnapshotWriter).to receive(:write_if_changed!)
107
- allow(Snapshot).to receive(:generate_v2!)
108
- allow(Codegen).to receive(:generate_ts!)
109
- allow(File).to receive(:write).and_call_original
110
-
111
- as_json(combined_route_set)
112
-
113
- expect(SnapshotWriter).not_to have_received(:write_if_changed!)
114
- expect(Snapshot).not_to have_received(:generate_v2!)
115
- expect(Codegen).not_to have_received(:generate_ts!)
116
- expect(File).not_to have_received(:write)
117
- end
118
- end
119
-
120
- describe "single source of truth (AC2)" do
121
- it "is byte-derived from ServerFunctions.introspect (same collectors as codegen)" do
122
- # Both introspection and write_v2_snapshot! flow through .introspect;
123
- # the document's accessors mirror those entries' identifiers exactly.
124
- entries = ServerFunctions.introspect(
125
- route_set: combined_route_set,
126
- host_predicate: ->(_c) { true },
127
- query_class_for: ->(c) { c.start_with?(query_prefix) ? CatalogIntrospectQ : nil }
128
- )
129
- expect(as_json(combined_route_set)["accessors"].map { |a| a["accessor"] })
130
- .to eq(entries.map { |e| e["js_identifier"] })
131
- end
132
- end
133
- end
134
- end
135
- end
@@ -1,212 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "spec_helper"
4
-
5
- module Ruact
6
- module ServerFunctions
7
- RSpec.describe NameBridge, :story_8_0a do
8
- describe ".to_js_identifier (Story 8.0a — Ruby symbol → JS identifier)" do
9
- # Six canonical edge cases locked by the Story 8.0 ADR.
10
-
11
- it "translates standard snake_case to camelCase" do
12
- expect(described_class.to_js_identifier(:create_post)).to eq("createPost")
13
- end
14
-
15
- it "passes a single word through unchanged" do
16
- expect(described_class.to_js_identifier(:categories)).to eq("categories")
17
- end
18
-
19
- it "preserves a single leading underscore" do
20
- expect(described_class.to_js_identifier(:_internal_dump)).to eq("_internalDump")
21
- end
22
-
23
- it "collapses consecutive underscores" do
24
- expect(described_class.to_js_identifier(:foo__bar)).to eq("fooBar")
25
- end
26
-
27
- it "raises ConfigurationError for SCREAMING_SNAKE" do
28
- expect { described_class.to_js_identifier(:RECALCULATE) }
29
- .to raise_error(Ruact::ConfigurationError) do |error|
30
- expect(error.message).to include(":RECALCULATE")
31
- expect(error.message).to include("/^[a-z_][a-z0-9_]*$/")
32
- end
33
- end
34
-
35
- it "raises ConfigurationError for PascalCase" do
36
- expect { described_class.to_js_identifier(:CreatePost) }
37
- .to raise_error(Ruact::ConfigurationError) do |error|
38
- expect(error.message).to include(":CreatePost")
39
- expect(error.message).to include("ruact server-function name")
40
- end
41
- end
42
-
43
- describe "additional shape guards" do
44
- it "rejects symbols starting with a digit" do
45
- expect { described_class.to_js_identifier(:"1foo") }
46
- .to raise_error(Ruact::ConfigurationError, /must match/)
47
- end
48
-
49
- it "rejects symbols containing a dash" do
50
- expect { described_class.to_js_identifier(:"foo-bar") }
51
- .to raise_error(Ruact::ConfigurationError, /must match/)
52
- end
53
-
54
- it "rejects empty string" do
55
- expect { described_class.to_js_identifier(:"") }
56
- .to raise_error(Ruact::ConfigurationError, /must match/)
57
- end
58
-
59
- it "accepts a string argument identical to its symbol form" do
60
- expect(described_class.to_js_identifier("create_post")).to eq("createPost")
61
- end
62
-
63
- it "preserves a leading underscore followed by digits" do
64
- expect(described_class.to_js_identifier(:_2fa_check)).to eq("_2faCheck")
65
- end
66
- end
67
-
68
- describe "underscore-only symbols (Story 8.0 review patch 2026-05-13)" do
69
- it "rejects a single underscore" do
70
- expect { described_class.to_js_identifier(:_) }
71
- .to raise_error(Ruact::ConfigurationError) do |error|
72
- expect(error.message).to include(":_")
73
- expect(error.message).to include("entirely of underscores")
74
- end
75
- end
76
-
77
- it "rejects a run of underscores" do
78
- expect { described_class.to_js_identifier(:____) }
79
- .to raise_error(Ruact::ConfigurationError, /entirely of underscores/)
80
- end
81
-
82
- it "still accepts a leading underscore followed by alphanumeric" do
83
- expect(described_class.to_js_identifier(:_x)).to eq("_x")
84
- end
85
- end
86
-
87
- describe "JS reserved words (Story 8.0 review patch 2026-05-13)" do
88
- # Spot-check a handful of representative classes: keyword (`class`),
89
- # module-level reserved (`export`), strict-mode reserved (`let`),
90
- # contextually-reserved (`await`, `async`), literal (`true`).
91
- it "rejects :class" do
92
- expect { described_class.to_js_identifier(:class) }
93
- .to raise_error(Ruact::ConfigurationError) do |error|
94
- expect(error.message).to include(":class")
95
- expect(error.message).to include("JS reserved word")
96
- expect(error.message).to include('"class"')
97
- end
98
- end
99
-
100
- it "rejects :export" do
101
- expect { described_class.to_js_identifier(:export) }
102
- .to raise_error(Ruact::ConfigurationError, /JS reserved word.*"export"/)
103
- end
104
-
105
- it "rejects :let, :await, :async, :true (representative coverage)" do
106
- %i[let await async true].each do |sym|
107
- expect { described_class.to_js_identifier(sym) }
108
- .to raise_error(Ruact::ConfigurationError, /JS reserved word/),
109
- "expected :#{sym} to raise as a reserved word"
110
- end
111
- end
112
-
113
- it "rejects :eval and :arguments (strict-mode invalid binding names — " \
114
- "Story 8.0 Re-run patch 2026-05-13)" do
115
- # ES module code runs in strict mode, where `eval` and `arguments`
116
- # cannot be used as identifier names. The 8.0a codegen emits a
117
- # `"type": "module"` file, so these guards apply unconditionally.
118
- %i[eval arguments].each do |sym|
119
- expect { described_class.to_js_identifier(sym) }
120
- .to raise_error(Ruact::ConfigurationError, /JS reserved word/),
121
- "expected :#{sym} to raise as strict-mode invalid binding name"
122
- end
123
- end
124
-
125
- it "accepts multi-word symbols whose camelCased output is not reserved" do
126
- # No real Ruby snake_case maps to a single reserved word post-
127
- # camelCasing (reserved words are themselves single-word), but the
128
- # check happens AFTER camelCasing so a hypothetical degenerate
129
- # input is still caught.
130
- expect { described_class.to_js_identifier(:cl_ass) }
131
- .not_to raise_error # "clAss" is not reserved — sanity guard
132
- expect(described_class.to_js_identifier(:cl_ass)).to eq("clAss")
133
- end
134
-
135
- it "ACCEPTS reserved words that survive only with the leading-underscore prefix" do
136
- # `:_class` → "_class" — not reserved (the underscore is a literal
137
- # character); intentional escape hatch for devs whose domain
138
- # vocabulary collides with JS keywords.
139
- expect(described_class.to_js_identifier(:_class)).to eq("_class")
140
- expect(described_class.to_js_identifier(:_export)).to eq("_export")
141
- end
142
-
143
- it "ACCEPTS the suffix-shaped escape hint from the error message" do
144
- # The error message suggests `:class_action`; assert that hint
145
- # produces a valid identifier — guards against a regression where
146
- # the suggested fix would also fail validation.
147
- expect(described_class.to_js_identifier(:class_action)).to eq("classAction")
148
- end
149
- end
150
-
151
- describe "Ruact-reserved names (Story 8.2 review patch R2 — 2026-05-17)", :story_8_2 do
152
- # The codegen unconditionally re-exports certain runtime helpers
153
- # from `@/.ruact/server-functions` (e.g. `revalidate`). A server
154
- # function named `revalidate` would emit `export const revalidate`
155
- # next to the helper re-export and crash at module load with a
156
- # duplicate-export error. Reject at route-draw instead.
157
- it "rejects :revalidate because it collides with the unconditional helper re-export" do
158
- expect { described_class.to_js_identifier(:revalidate) }
159
- .to raise_error(Ruact::ConfigurationError) do |error|
160
- expect(error.message).to include(":revalidate")
161
- expect(error.message).to include("duplicate export")
162
- end
163
- end
164
-
165
- it "accepts :revalidate_post (suffix escape hatch — same convention as JS reserved-word path)" do
166
- expect(described_class.to_js_identifier(:revalidate_post)).to eq("revalidatePost")
167
- end
168
-
169
- it "accepts :_revalidate (leading-underscore escape hatch)" do
170
- expect(described_class.to_js_identifier(:_revalidate)).to eq("_revalidate")
171
- end
172
-
173
- it "rejects :_make_server_function because it collides with the codegen's runtime import" do
174
- expect { described_class.to_js_identifier(:_make_server_function) }
175
- .to raise_error(Ruact::ConfigurationError) do |error|
176
- expect(error.message).to include(":_make_server_function")
177
- expect(error.message).to include("duplicate export")
178
- end
179
- end
180
-
181
- it "accepts :_make_ref now that the demolished v1 runtime export is no longer reserved (Story 9.9)" do
182
- expect(described_class.to_js_identifier(:_make_ref)).to eq("_makeRef")
183
- end
184
-
185
- # Story 9.5 — `_makeQuery` (the v2 query import) and `useQuery` (the
186
- # query hook re-export) are new top-level bindings in the generated
187
- # module; a query/action method mapping to either would redeclare the
188
- # import / duplicate the export and crash at module load.
189
- it "Story 9.5 — rejects :use_query because it collides with the useQuery re-export" do
190
- expect { described_class.to_js_identifier(:use_query) }
191
- .to raise_error(Ruact::ConfigurationError) do |error|
192
- expect(error.message).to include(":use_query")
193
- expect(error.message).to include("duplicate export")
194
- end
195
- end
196
-
197
- it "Story 9.5 — rejects :_make_query because it collides with the codegen's runtime import" do
198
- expect { described_class.to_js_identifier(:_make_query) }
199
- .to raise_error(Ruact::ConfigurationError) do |error|
200
- expect(error.message).to include(":_make_query")
201
- expect(error.message).to include("duplicate export")
202
- end
203
- end
204
-
205
- it "Story 9.5 — accepts :use_query_results (suffix escape hatch keeps working)" do
206
- expect(described_class.to_js_identifier(:use_query_results)).to eq("useQueryResults")
207
- end
208
- end
209
- end
210
- end
211
- end
212
- end
@@ -1,72 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Story 9.4 — unit spec for the per-request query context (D3). The context
4
- # wraps the DISPATCHING controller instance and delegates to it: because the
5
- # internal dispatch controller inherits `Ruact.config.query_parent_controller`,
6
- # `controller.current_user` IS the host's own method (Devise / Pundit /
7
- # hand-rolled). No Rails boot needed here — a plain stub controller suffices.
8
- require "spec_helper"
9
-
10
- module Ruact
11
- module ServerFunctions
12
- RSpec.describe QueryContext, :story_9_4 do
13
- describe "Story 9.4 — delegation to the dispatching controller (AC3 / D3)" do
14
- let(:controller_class) do
15
- Class.new do
16
- def params = { "q" => "x" }
17
- def request = :the_request
18
- def session = { "sid" => 1 }
19
-
20
- def current_user
21
- { "id" => 7 }
22
- end
23
- end
24
- end
25
-
26
- subject(:context) { described_class.new(controller: controller_class.new) }
27
-
28
- it "delegates params" do
29
- expect(context.params).to eq("q" => "x")
30
- end
31
-
32
- it "delegates request" do
33
- expect(context.request).to eq(:the_request)
34
- end
35
-
36
- it "delegates session" do
37
- expect(context.session).to eq("sid" => 1)
38
- end
39
-
40
- it "delegates current_user to the host's own method" do
41
- expect(context.current_user).to eq("id" => 7)
42
- end
43
- end
44
-
45
- describe "Story 9.4 — current_user reaches a PRIVATE host helper too" do
46
- let(:controller_class) do
47
- Class.new do
48
- private
49
-
50
- def current_user
51
- :private_user
52
- end
53
- end
54
- end
55
-
56
- it "resolves it (hand-rolled apps commonly define current_user private)" do
57
- context = described_class.new(controller: controller_class.new)
58
- expect(context.current_user).to eq(:private_user)
59
- end
60
- end
61
-
62
- describe "Story 9.4 — clear error when the host defines no current_user (D3)" do
63
- it "raises NoMethodError naming the parent controller and the fix" do
64
- context = described_class.new(controller: Class.new.new)
65
- expect { context.current_user }.to raise_error(NoMethodError, /current_user/) do |error|
66
- expect(error.message).to include("query_parent_controller")
67
- end
68
- end
69
- end
70
- end
71
- end
72
- end