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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +147 -48
- data/CONTRIBUTING.md +233 -0
- data/README.md +18 -8
- data/RELEASING.md +125 -139
- data/lib/generators/ruact/install/install_generator.rb +305 -126
- data/lib/generators/ruact/install/templates/AGENTS.md.tt +14 -13
- data/lib/generators/ruact/install/templates/initializer.rb.tt +29 -7
- data/lib/generators/ruact/install/templates/tsconfig.json.tt +3 -0
- data/lib/generators/ruact/layout/layout_generator.rb +52 -0
- data/lib/generators/ruact/scaffold/templates/controller.rb.tt +5 -3
- data/lib/ruact/configuration.rb +65 -13
- data/lib/ruact/controller/document_rendering.rb +72 -16
- data/lib/ruact/controller/page_rendering.rb +134 -0
- data/lib/ruact/controller/pages.rb +116 -0
- data/lib/ruact/controller.rb +78 -11
- data/lib/ruact/doctor.rb +233 -25
- data/lib/ruact/layout_source.rb +29 -7
- data/lib/ruact/navigation_boundary.rb +240 -0
- data/lib/ruact/packaging.rb +68 -0
- data/lib/ruact/railtie.rb +30 -0
- data/lib/ruact/server.rb +10 -1
- data/lib/ruact/version.rb +1 -1
- data/lib/ruact/view_helper.rb +158 -1
- data/lib/ruact/views/layouts/ruact.html.erb +32 -0
- data/lib/ruact.rb +29 -0
- data/vendor/javascript/vite-plugin-ruact/flight-client.test.mjs +321 -0
- data/vendor/javascript/vite-plugin-ruact/package-lock.json +11 -0
- data/vendor/javascript/vite-plugin-ruact/package.json +1 -0
- data/vendor/javascript/vite-plugin-ruact/ruact-router.test.mjs +433 -0
- data/vendor/javascript/vite-plugin-ruact/runtime/flight-client.js +14 -3
- data/vendor/javascript/vite-plugin-ruact/runtime/ruact-router.js +170 -7
- metadata +12 -107
- data/.codecov.yml +0 -31
- data/.github/workflows/ci.yml +0 -284
- data/.github/workflows/server-functions-bench.yml +0 -54
- data/.rubocop.yml +0 -107
- data/.rubocop_todo.yml +0 -63
- data/Rakefile +0 -10
- data/bench/server_functions_dispatch_bench.rb +0 -276
- data/bench/server_functions_dispatch_bench.results.md +0 -150
- data/docs/internal/README.md +0 -9
- data/docs/internal/decisions/server-functions-api.md +0 -2236
- data/spec/benchmarks/baseline.json +0 -12
- data/spec/benchmarks/render_pipeline_benchmark_spec.rb +0 -109
- data/spec/fixtures/flight/README.md +0 -136
- data/spec/fixtures/flight/array.txt +0 -1
- data/spec/fixtures/flight/as_json_object.txt +0 -2
- data/spec/fixtures/flight/bigint.txt +0 -1
- data/spec/fixtures/flight/boolean_false.txt +0 -1
- data/spec/fixtures/flight/boolean_true.txt +0 -1
- data/spec/fixtures/flight/client_component_with_props.txt +0 -2
- data/spec/fixtures/flight/client_reference.txt +0 -2
- data/spec/fixtures/flight/hash.txt +0 -1
- data/spec/fixtures/flight/infinity.txt +0 -1
- data/spec/fixtures/flight/nan.txt +0 -1
- data/spec/fixtures/flight/negative_infinity.txt +0 -1
- data/spec/fixtures/flight/nil.txt +0 -1
- data/spec/fixtures/flight/number_float.txt +0 -1
- data/spec/fixtures/flight/number_integer.txt +0 -1
- data/spec/fixtures/flight/react_element_no_props.txt +0 -1
- data/spec/fixtures/flight/redirect_row.txt +0 -1
- data/spec/fixtures/flight/serializable_object.txt +0 -2
- data/spec/fixtures/flight/string_basic.txt +0 -1
- data/spec/fixtures/flight/string_dollar_escape.txt +0 -1
- data/spec/fixtures/flight/undefined.txt +0 -1
- data/spec/fixtures/readme/children-error.html.erb +0 -3
- data/spec/fixtures/readme/children-error.txt +0 -1
- data/spec/fixtures/story_7_9_views/controller_request_spec_support/demo/show.html.erb +0 -3
- data/spec/fixtures/story_7_9_views/controller_request_spec_support/errors_demo/new.html.erb +0 -3
- data/spec/fixtures/story_7_9_views/controller_request_spec_support/exploding_layout_demo/show.html.erb +0 -3
- data/spec/fixtures/story_7_9_views/controller_request_spec_support/ghost_layout_demo/show.html.erb +0 -3
- data/spec/fixtures/story_7_9_views/controller_request_spec_support/layout_demo/show.html.erb +0 -3
- data/spec/fixtures/story_7_9_views/controller_request_spec_support/rootless_layout_demo/show.html.erb +0 -3
- data/spec/fixtures/story_7_9_views/controller_request_spec_support/unwired_layout_demo/show.html.erb +0 -3
- data/spec/fixtures/story_7_9_views/layouts/bare_host.html.erb +0 -16
- data/spec/fixtures/story_7_9_views/layouts/exploding_host.html.erb +0 -24
- data/spec/fixtures/story_7_9_views/layouts/rootless_host.html.erb +0 -15
- data/spec/fixtures/story_7_9_views/layouts/ruact_host.html.erb +0 -17
- data/spec/readme_demo_message_spec.rb +0 -67
- data/spec/readme_spec.rb +0 -282
- data/spec/ruact/client_manifest_spec.rb +0 -270
- data/spec/ruact/component_contract_spec.rb +0 -119
- data/spec/ruact/configuration_spec.rb +0 -518
- data/spec/ruact/controller_request_spec.rb +0 -671
- data/spec/ruact/controller_spec.rb +0 -343
- data/spec/ruact/doctor_spec.rb +0 -769
- data/spec/ruact/erb_preprocessor_hook_spec.rb +0 -55
- data/spec/ruact/erb_preprocessor_spec.rb +0 -361
- data/spec/ruact/errors_spec.rb +0 -93
- data/spec/ruact/flight/renderer_spec.rb +0 -133
- data/spec/ruact/flight/serializer_spec.rb +0 -494
- data/spec/ruact/html_converter_spec.rb +0 -375
- data/spec/ruact/install_generator_spec.rb +0 -1549
- data/spec/ruact/layout_source_spec.rb +0 -108
- data/spec/ruact/manifest_resolver_spec.rb +0 -174
- data/spec/ruact/query_request_spec.rb +0 -706
- data/spec/ruact/query_spec.rb +0 -105
- data/spec/ruact/railtie_spec.rb +0 -155
- data/spec/ruact/render_context_spec.rb +0 -58
- data/spec/ruact/render_pipeline_concurrency_spec.rb +0 -78
- data/spec/ruact/render_pipeline_spec.rb +0 -928
- data/spec/ruact/scaffold_generator_spec.rb +0 -1849
- data/spec/ruact/serializable_spec.rb +0 -179
- data/spec/ruact/server_bucket_request_spec.rb +0 -785
- data/spec/ruact/server_function_name_spec.rb +0 -53
- data/spec/ruact/server_functions/backtrace_cleaner_spec.rb +0 -63
- data/spec/ruact/server_functions/bucket_two_payload_spec.rb +0 -200
- data/spec/ruact/server_functions/codegen_spec.rb +0 -397
- data/spec/ruact/server_functions/error_payload_spec.rb +0 -222
- data/spec/ruact/server_functions/error_suggestion_spec.rb +0 -79
- data/spec/ruact/server_functions/introspection_spec.rb +0 -135
- data/spec/ruact/server_functions/name_bridge_spec.rb +0 -212
- data/spec/ruact/server_functions/query_context_spec.rb +0 -72
- data/spec/ruact/server_functions/query_source_spec.rb +0 -193
- data/spec/ruact/server_functions/railtie_integration_spec.rb +0 -215
- data/spec/ruact/server_functions/rake_spec.rb +0 -86
- data/spec/ruact/server_functions/route_source_spec.rb +0 -202
- data/spec/ruact/server_functions/snapshot_spec.rb +0 -96
- data/spec/ruact/server_functions/snapshot_writer_spec.rb +0 -71
- data/spec/ruact/server_rescue_request_spec.rb +0 -416
- data/spec/ruact/server_spec.rb +0 -179
- data/spec/ruact/server_upload_request_spec.rb +0 -311
- data/spec/ruact/signed_references_spec.rb +0 -164
- data/spec/ruact/string_distance_spec.rb +0 -38
- data/spec/ruact/tasks_json_introspection_spec.rb +0 -141
- data/spec/ruact/testing/have_ruact_component_spec.rb +0 -170
- data/spec/ruact/testing/no_production_load_spec.rb +0 -41
- data/spec/ruact/validation_errors_spec.rb +0 -116
- data/spec/ruact/view_helper_spec.rb +0 -131
- data/spec/spec_helper.rb +0 -77
- data/spec/support/fixtures/pixel.png +0 -0
- data/spec/support/flight_wire_parser.rb +0 -21
- data/spec/support/flight_wire_parser_spec.rb +0 -93
- data/spec/support/matchers/flight_fixture_matcher.rb +0 -130
- data/spec/support/matchers/flight_fixture_matcher_spec.rb +0 -250
- data/spec/support/rails_stub.rb +0 -115
|
@@ -1,494 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "spec_helper"
|
|
4
|
-
|
|
5
|
-
module Ruact
|
|
6
|
-
module Flight
|
|
7
|
-
# Stub bundler config — resolves any module_id/export_name to a simple metadata hash
|
|
8
|
-
class StubBundlerConfig
|
|
9
|
-
def resolve(module_id, export_name)
|
|
10
|
-
[module_id, export_name]
|
|
11
|
-
end
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
RSpec.describe "Renderer (serializer integration)" do
|
|
15
|
-
subject(:render) { ->(model) { Renderer.render(model, bundler) } }
|
|
16
|
-
|
|
17
|
-
let(:bundler) { StubBundlerConfig.new }
|
|
18
|
-
|
|
19
|
-
# --- React Element (DOM) ---
|
|
20
|
-
#
|
|
21
|
-
# Primitive scalar/collection inputs (nil, booleans, integer, float, string,
|
|
22
|
-
# `$`-escaped string, hash, array) are guarded by the fixture-mode block at
|
|
23
|
-
# the bottom of this file. The cases below assert on the *parsed shape* of
|
|
24
|
-
# ReactElement payloads — fixture mode is overkill for them since the
|
|
25
|
-
# invariant under test is "the parsed tuple is `["$", <type>, <key>, <props>]`",
|
|
26
|
-
# not the literal byte sequence.
|
|
27
|
-
|
|
28
|
-
describe "ReactElement" do
|
|
29
|
-
it "serializes a DOM element" do
|
|
30
|
-
el = ReactElement.new(type: "div", props: { className: "box", children: "hi" })
|
|
31
|
-
expected = [
|
|
32
|
-
{ id: 0, class: :model,
|
|
33
|
-
payload: ["$", "div", nil, { "className" => "box", "children" => "hi" }] }
|
|
34
|
-
]
|
|
35
|
-
expect(render.call(el)).to match_flight_structure(expected)
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
it "serializes a DOM element with a key" do
|
|
39
|
-
el = ReactElement.new(type: "li", key: "item-1", props: { children: "one" })
|
|
40
|
-
expected = [
|
|
41
|
-
{ id: 0, class: :model,
|
|
42
|
-
payload: ["$", "li", "item-1", { "children" => "one" }] }
|
|
43
|
-
]
|
|
44
|
-
expect(render.call(el)).to match_flight_structure(expected)
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
# --- Client Reference ---
|
|
49
|
-
|
|
50
|
-
describe "ClientReference" do
|
|
51
|
-
it "emits an I row for the import and references $L1 in root" do
|
|
52
|
-
ref = ClientReference.new(module_id: "./LikeButton", export_name: "LikeButton")
|
|
53
|
-
el = ReactElement.new(type: ref, props: { postId: 1, initialCount: 5 })
|
|
54
|
-
|
|
55
|
-
# Story 7.6 Decision B: collapse the original sibling regex pair plus
|
|
56
|
-
# the `out.index("1:I") < out.index("0:[")` ordering check into a
|
|
57
|
-
# single structural assertion. Non-import rows compare positionally,
|
|
58
|
-
# so the import row in expected position 0 + the model row in
|
|
59
|
-
# expected position 1 encode the ordering invariant.
|
|
60
|
-
expected = [
|
|
61
|
-
{ id: 1, class: :import, payload: ["./LikeButton", "LikeButton"] },
|
|
62
|
-
{ id: 0, class: :model,
|
|
63
|
-
payload: ["$", "$L1", nil, { "postId" => 1, "initialCount" => 5 }] }
|
|
64
|
-
]
|
|
65
|
-
expect(render.call(el)).to match_flight_structure(expected)
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
it "deduplicates: same ClientReference object emits only one I row" do
|
|
69
|
-
ref = ClientReference.new(module_id: "./Button", export_name: "Button")
|
|
70
|
-
el1 = ReactElement.new(type: ref, props: { label: "Save" })
|
|
71
|
-
el2 = ReactElement.new(type: ref, props: { label: "Cancel" })
|
|
72
|
-
out = render.call([el1, el2])
|
|
73
|
-
|
|
74
|
-
i_rows = out.scan(/\d+:I/).length
|
|
75
|
-
expect(i_rows).to eq(1), "same ClientReference object should emit only one I row"
|
|
76
|
-
end
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
# --- Error handling ---
|
|
80
|
-
|
|
81
|
-
describe "unsupported type (AC#2)" do
|
|
82
|
-
# Build a class with no `as_json` method. ActiveSupport adds Object#as_json
|
|
83
|
-
# when Rails is loaded (Story 7.9), so Object.new no longer triggers the
|
|
84
|
-
# "no as_json" branch; this class explicitly undefines it.
|
|
85
|
-
let(:no_as_json_class) do
|
|
86
|
-
Class.new do
|
|
87
|
-
undef_method :as_json if method_defined?(:as_json) || private_method_defined?(:as_json)
|
|
88
|
-
|
|
89
|
-
def self.name
|
|
90
|
-
"UnsupportedType"
|
|
91
|
-
end
|
|
92
|
-
end
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
it "raises SerializationError with class name and Serializable hint" do
|
|
96
|
-
expect { render.call(no_as_json_class.new) }
|
|
97
|
-
.to raise_error(Ruact::SerializationError, /UnsupportedType/)
|
|
98
|
-
expect { render.call(no_as_json_class.new) }
|
|
99
|
-
.to raise_error(Ruact::SerializationError, /include Ruact::Serializable/)
|
|
100
|
-
end
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
# --- Story 2.2: as_json serialization ---
|
|
104
|
-
|
|
105
|
-
describe "as_json serialization (Story 2.2)" do
|
|
106
|
-
let(:model_with_as_json) do
|
|
107
|
-
Class.new do
|
|
108
|
-
def as_json
|
|
109
|
-
{ "id" => 1, "name" => "Alice" }
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
def self.name
|
|
113
|
-
"FakeModel"
|
|
114
|
-
end
|
|
115
|
-
end.new
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
let(:model_without_as_json) do
|
|
119
|
-
# ActiveSupport adds Object#as_json when Rails is loaded (Story 7.9), so
|
|
120
|
-
# Object.new no longer represents "no as_json defined". Use a class that
|
|
121
|
-
# explicitly undefines it instead.
|
|
122
|
-
Class.new do
|
|
123
|
-
undef_method :as_json if method_defined?(:as_json) || private_method_defined?(:as_json)
|
|
124
|
-
|
|
125
|
-
def self.name
|
|
126
|
-
"ModelWithoutAsJson"
|
|
127
|
-
end
|
|
128
|
-
end.new
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
context "with strict_serialization: false (default)" do
|
|
132
|
-
let(:render_loose) do
|
|
133
|
-
b = StubBundlerConfig.new
|
|
134
|
-
->(model) { Renderer.render(model, b, strict_serialization: false) }
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
it "serializes via as_json, returning a hash (AC#3)" do
|
|
138
|
-
expect(render_loose.call(model_with_as_json)).to include_flight_row(
|
|
139
|
-
class: :model,
|
|
140
|
-
payload: hash_including("id" => 1, "name" => "Alice")
|
|
141
|
-
)
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
it "calls on_as_json_warning with class name and attribute list (AC#1, #3)" do
|
|
145
|
-
warnings = []
|
|
146
|
-
b = StubBundlerConfig.new
|
|
147
|
-
Renderer.render(model_with_as_json, b,
|
|
148
|
-
strict_serialization: false,
|
|
149
|
-
on_as_json_warning: ->(cls, attrs) { warnings << [cls, attrs] })
|
|
150
|
-
expect(warnings).to eq([["FakeModel", "id, name"]])
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
it "does NOT call on_as_json_warning when callback is nil (AC#3)" do
|
|
154
|
-
expect { render_loose.call(model_with_as_json) }.not_to raise_error
|
|
155
|
-
end
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
context "with strict_serialization: true" do
|
|
159
|
-
let(:render_strict) do
|
|
160
|
-
b = StubBundlerConfig.new
|
|
161
|
-
->(model) { Renderer.render(model, b, strict_serialization: true) }
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
it "raises SerializationError with Serializable hint (AC#2)" do
|
|
165
|
-
expect { render_strict.call(model_with_as_json) }
|
|
166
|
-
.to raise_error(Ruact::SerializationError, /FakeModel/)
|
|
167
|
-
expect { render_strict.call(model_with_as_json) }
|
|
168
|
-
.to raise_error(Ruact::SerializationError,
|
|
169
|
-
/include Ruact::Serializable or set strict_serialization: false/)
|
|
170
|
-
end
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
context "with as_json returning non-Hash (e.g. array)" do
|
|
174
|
-
let(:model_returning_array) do
|
|
175
|
-
Class.new do
|
|
176
|
-
def as_json
|
|
177
|
-
[1, 2, 3]
|
|
178
|
-
end
|
|
179
|
-
|
|
180
|
-
def self.name
|
|
181
|
-
"ArrayModel"
|
|
182
|
-
end
|
|
183
|
-
end.new
|
|
184
|
-
end
|
|
185
|
-
|
|
186
|
-
it "serializes the array without crashing" do
|
|
187
|
-
b = StubBundlerConfig.new
|
|
188
|
-
output = Renderer.render(model_returning_array, b, strict_serialization: false)
|
|
189
|
-
expect(output).to include_flight_row(class: :model, payload: [1, 2, 3])
|
|
190
|
-
end
|
|
191
|
-
end
|
|
192
|
-
|
|
193
|
-
context "with as_json returning self" do
|
|
194
|
-
let(:model_returning_self) do
|
|
195
|
-
Class.new do
|
|
196
|
-
def as_json
|
|
197
|
-
self
|
|
198
|
-
end
|
|
199
|
-
|
|
200
|
-
def self.name
|
|
201
|
-
"SelfModel"
|
|
202
|
-
end
|
|
203
|
-
end.new
|
|
204
|
-
end
|
|
205
|
-
|
|
206
|
-
it "raises SerializationError about infinite recursion" do
|
|
207
|
-
b = StubBundlerConfig.new
|
|
208
|
-
expect { Renderer.render(model_returning_self, b, strict_serialization: false) }
|
|
209
|
-
.to raise_error(Ruact::SerializationError, /infinite recursion/)
|
|
210
|
-
end
|
|
211
|
-
end
|
|
212
|
-
|
|
213
|
-
# --- Story 7.7 regression suite — as_json returning self message shape ---
|
|
214
|
-
#
|
|
215
|
-
# The "with as_json returning self" context at :169 above asserts the error
|
|
216
|
-
# class plus the substring "infinite recursion". That gate passes if the
|
|
217
|
-
# message drifts to e.g. "infinite recursion detected" with no class name
|
|
218
|
-
# and no actionable hint — the user reading a Sentry issue would not learn
|
|
219
|
-
# which model misbehaved or how to fix it. The block below gates the full
|
|
220
|
-
# message shape from gem/lib/ruact/flight/serializer.rb#serialize_as_json:
|
|
221
|
-
# the offending class name + "Ruact::Serializable" + "ruact_props" must all
|
|
222
|
-
# appear so the message remains debuggable in production.
|
|
223
|
-
describe "edge cases (Story 7.7) — as_json returning self message shape", :story_7_7 do
|
|
224
|
-
let(:self_returning_model) do
|
|
225
|
-
Class.new do
|
|
226
|
-
def as_json
|
|
227
|
-
self
|
|
228
|
-
end
|
|
229
|
-
|
|
230
|
-
def self.name
|
|
231
|
-
"SelfModel"
|
|
232
|
-
end
|
|
233
|
-
end.new
|
|
234
|
-
end
|
|
235
|
-
|
|
236
|
-
it "names the offending class and points the user at Ruact::Serializable + ruact_props" do
|
|
237
|
-
b = StubBundlerConfig.new
|
|
238
|
-
expect { Renderer.render(self_returning_model, b, strict_serialization: false) }
|
|
239
|
-
.to raise_error(Ruact::SerializationError) do |error|
|
|
240
|
-
expect(error.message).to include("SelfModel")
|
|
241
|
-
expect(error.message).to include("Ruact::Serializable")
|
|
242
|
-
expect(error.message).to include("ruact_props")
|
|
243
|
-
end
|
|
244
|
-
end
|
|
245
|
-
end
|
|
246
|
-
|
|
247
|
-
context "with as_json raising an exception" do
|
|
248
|
-
let(:model_raising_error) do
|
|
249
|
-
Class.new do
|
|
250
|
-
def as_json
|
|
251
|
-
raise "broken"
|
|
252
|
-
end
|
|
253
|
-
|
|
254
|
-
def self.name
|
|
255
|
-
"BrokenModel"
|
|
256
|
-
end
|
|
257
|
-
end.new
|
|
258
|
-
end
|
|
259
|
-
|
|
260
|
-
it "wraps the exception in SerializationError" do
|
|
261
|
-
b = StubBundlerConfig.new
|
|
262
|
-
expect { Renderer.render(model_raising_error, b, strict_serialization: false) }
|
|
263
|
-
.to raise_error(Ruact::SerializationError, /BrokenModel#as_json raised RuntimeError: broken/)
|
|
264
|
-
end
|
|
265
|
-
end
|
|
266
|
-
|
|
267
|
-
context "without as_json method" do
|
|
268
|
-
it "raises SerializationError regardless of strict_serialization (AC#4)" do
|
|
269
|
-
b = StubBundlerConfig.new
|
|
270
|
-
[true, false].each do |strict|
|
|
271
|
-
expect { Renderer.render(model_without_as_json, b, strict_serialization: strict) }
|
|
272
|
-
.to raise_error(Ruact::SerializationError, /include Ruact::Serializable/)
|
|
273
|
-
end
|
|
274
|
-
end
|
|
275
|
-
end
|
|
276
|
-
end
|
|
277
|
-
|
|
278
|
-
# --- Story 2.3: Serializable serialization ---
|
|
279
|
-
|
|
280
|
-
describe "Serializable serialization (Story 2.3)" do
|
|
281
|
-
let(:serializable_obj) do
|
|
282
|
-
Class.new do
|
|
283
|
-
include Ruact::Serializable
|
|
284
|
-
|
|
285
|
-
attr_reader :id, :title
|
|
286
|
-
|
|
287
|
-
def initialize
|
|
288
|
-
@id = 1
|
|
289
|
-
@title = "Hello"
|
|
290
|
-
end
|
|
291
|
-
|
|
292
|
-
def self.name
|
|
293
|
-
"FakeSerializable"
|
|
294
|
-
end
|
|
295
|
-
|
|
296
|
-
ruact_props :id, :title
|
|
297
|
-
end.new
|
|
298
|
-
end
|
|
299
|
-
|
|
300
|
-
it "serializes via ruact_serialize — only declared props (AC#1)" do
|
|
301
|
-
expect(render.call(serializable_obj)).to include_flight_row(
|
|
302
|
-
class: :model,
|
|
303
|
-
payload: hash_including("id" => 1, "title" => "Hello")
|
|
304
|
-
)
|
|
305
|
-
end
|
|
306
|
-
|
|
307
|
-
it "works with strict_serialization: true — Serializable bypasses strict gate (AC#1)" do
|
|
308
|
-
b = StubBundlerConfig.new
|
|
309
|
-
output = Renderer.render(serializable_obj, b, strict_serialization: true)
|
|
310
|
-
expect(output).to include_flight_row(class: :model, payload: hash_including("id" => 1))
|
|
311
|
-
end
|
|
312
|
-
|
|
313
|
-
it "does NOT call on_as_json_warning (AC#3)" do
|
|
314
|
-
warnings = []
|
|
315
|
-
b = StubBundlerConfig.new
|
|
316
|
-
Renderer.render(serializable_obj, b,
|
|
317
|
-
strict_serialization: false,
|
|
318
|
-
on_as_json_warning: ->(cls, attrs) { warnings << [cls, attrs] })
|
|
319
|
-
expect(warnings).to be_empty
|
|
320
|
-
end
|
|
321
|
-
end
|
|
322
|
-
|
|
323
|
-
# --- Fixture Contracts (AC#9) ---
|
|
324
|
-
#
|
|
325
|
-
# Fixture mode is the canonical guard for the Flight wire-format invariants
|
|
326
|
-
# of every primitive type: byte-exact escape rules, scalar canonical bytes,
|
|
327
|
-
# ordering. Story 7.6 deleted the duplicate literal-`eq` blocks that lived
|
|
328
|
-
# at the top of this file; these fixture-mode blocks are now the single
|
|
329
|
-
# source of truth for those invariants.
|
|
330
|
-
|
|
331
|
-
describe "fixture contracts (AC#9)" do
|
|
332
|
-
let(:fixture_render) { ->(model) { Renderer.render(model, ClientManifest.from_hash({})) } }
|
|
333
|
-
|
|
334
|
-
it "nil serializes to fixture" do
|
|
335
|
-
expect(fixture_render.call(nil)).to match_flight_fixture("nil")
|
|
336
|
-
end
|
|
337
|
-
|
|
338
|
-
it "true serializes to fixture" do
|
|
339
|
-
expect(fixture_render.call(true)).to match_flight_fixture("boolean_true")
|
|
340
|
-
end
|
|
341
|
-
|
|
342
|
-
it "false serializes to fixture" do
|
|
343
|
-
expect(fixture_render.call(false)).to match_flight_fixture("boolean_false")
|
|
344
|
-
end
|
|
345
|
-
|
|
346
|
-
it "plain string serializes to fixture" do
|
|
347
|
-
expect(fixture_render.call("hello")).to match_flight_fixture("string_basic")
|
|
348
|
-
end
|
|
349
|
-
|
|
350
|
-
it "dollar-prefixed string serializes to fixture" do
|
|
351
|
-
expect(fixture_render.call("$danger")).to match_flight_fixture("string_dollar_escape")
|
|
352
|
-
end
|
|
353
|
-
|
|
354
|
-
it "integer serializes to fixture" do
|
|
355
|
-
expect(fixture_render.call(42)).to match_flight_fixture("number_integer")
|
|
356
|
-
end
|
|
357
|
-
|
|
358
|
-
it "float serializes to fixture" do
|
|
359
|
-
expect(fixture_render.call(3.14)).to match_flight_fixture("number_float")
|
|
360
|
-
end
|
|
361
|
-
|
|
362
|
-
it "bigint (> MAX_SAFE_INTEGER) serializes to fixture as $n<decimal>" do
|
|
363
|
-
expect(fixture_render.call(9_007_199_254_740_992)).to match_flight_fixture("bigint")
|
|
364
|
-
end
|
|
365
|
-
|
|
366
|
-
it "Float::NAN serializes to fixture as $NaN" do
|
|
367
|
-
expect(fixture_render.call(Float::NAN)).to match_flight_fixture("nan")
|
|
368
|
-
end
|
|
369
|
-
|
|
370
|
-
it "Float::INFINITY serializes to fixture as $Infinity" do
|
|
371
|
-
expect(fixture_render.call(Float::INFINITY)).to match_flight_fixture("infinity")
|
|
372
|
-
end
|
|
373
|
-
|
|
374
|
-
it "-Float::INFINITY serializes to fixture as $-Infinity" do
|
|
375
|
-
expect(fixture_render.call(-Float::INFINITY)).to match_flight_fixture("negative_infinity")
|
|
376
|
-
end
|
|
377
|
-
|
|
378
|
-
it ":undefined serializes to fixture as $undefined" do
|
|
379
|
-
expect(fixture_render.call(:undefined)).to match_flight_fixture("undefined")
|
|
380
|
-
end
|
|
381
|
-
|
|
382
|
-
it "mixed array serializes to fixture" do
|
|
383
|
-
expect(fixture_render.call([1, "a", true, nil])).to match_flight_fixture("array")
|
|
384
|
-
end
|
|
385
|
-
|
|
386
|
-
it "hash with symbol keys serializes to fixture" do
|
|
387
|
-
expect(fixture_render.call({ debug: true, count: 5, label: "x" })).to match_flight_fixture("hash")
|
|
388
|
-
end
|
|
389
|
-
end
|
|
390
|
-
|
|
391
|
-
describe "client reference fixture contract (AC#3)" do
|
|
392
|
-
let(:manifest_with_button) do
|
|
393
|
-
ClientManifest.from_hash({
|
|
394
|
-
"LikeButton" => {
|
|
395
|
-
"id" => "/LikeButton.jsx",
|
|
396
|
-
"name" => "LikeButton",
|
|
397
|
-
"chunks" => ["/LikeButton.jsx"]
|
|
398
|
-
}
|
|
399
|
-
})
|
|
400
|
-
end
|
|
401
|
-
|
|
402
|
-
it "client reference element serializes to fixture (AC#3)" do
|
|
403
|
-
ref = manifest_with_button.reference_for("LikeButton")
|
|
404
|
-
el = ReactElement.new(type: ref, props: {})
|
|
405
|
-
expect(Renderer.render(el, manifest_with_button)).to match_flight_fixture("client_reference")
|
|
406
|
-
end
|
|
407
|
-
end
|
|
408
|
-
|
|
409
|
-
describe "as_json object fixture contract (Story 2.2)" do
|
|
410
|
-
let(:postcard_manifest) do
|
|
411
|
-
ClientManifest.from_hash({
|
|
412
|
-
"PostCard" => {
|
|
413
|
-
"id" => "/PostCard.jsx",
|
|
414
|
-
"name" => "PostCard",
|
|
415
|
-
"chunks" => ["/PostCard.jsx"]
|
|
416
|
-
}
|
|
417
|
-
})
|
|
418
|
-
end
|
|
419
|
-
let(:as_json_obj) do
|
|
420
|
-
Class.new do
|
|
421
|
-
def as_json
|
|
422
|
-
{ "id" => 1, "title" => "Hello", "author" => "Alice", "likesCount" => 5 }
|
|
423
|
-
end
|
|
424
|
-
|
|
425
|
-
def self.name
|
|
426
|
-
"Post"
|
|
427
|
-
end
|
|
428
|
-
end.new
|
|
429
|
-
end
|
|
430
|
-
|
|
431
|
-
it "component with as_json prop serializes to fixture" do
|
|
432
|
-
ref = postcard_manifest.reference_for("PostCard")
|
|
433
|
-
el = ReactElement.new(type: ref, props: { post: as_json_obj })
|
|
434
|
-
output = Renderer.render(el, postcard_manifest, strict_serialization: false)
|
|
435
|
-
expect(output).to match_flight_fixture("as_json_object")
|
|
436
|
-
end
|
|
437
|
-
end
|
|
438
|
-
|
|
439
|
-
describe "serializable object fixture contract (Story 2.3)" do
|
|
440
|
-
let(:postcard_manifest_s) do
|
|
441
|
-
ClientManifest.from_hash({
|
|
442
|
-
"PostCard" => {
|
|
443
|
-
"id" => "/PostCard.jsx",
|
|
444
|
-
"name" => "PostCard",
|
|
445
|
-
"chunks" => ["/PostCard.jsx"]
|
|
446
|
-
}
|
|
447
|
-
})
|
|
448
|
-
end
|
|
449
|
-
let(:serializable_obj) do
|
|
450
|
-
Class.new do
|
|
451
|
-
include Ruact::Serializable
|
|
452
|
-
|
|
453
|
-
attr_reader :id, :title
|
|
454
|
-
|
|
455
|
-
def initialize
|
|
456
|
-
@id = 1
|
|
457
|
-
@title = "Hello"
|
|
458
|
-
end
|
|
459
|
-
|
|
460
|
-
def self.name
|
|
461
|
-
"Post"
|
|
462
|
-
end
|
|
463
|
-
|
|
464
|
-
ruact_props :id, :title
|
|
465
|
-
end.new
|
|
466
|
-
end
|
|
467
|
-
|
|
468
|
-
it "component with Serializable prop serializes to fixture (AC#4)" do
|
|
469
|
-
ref = postcard_manifest_s.reference_for("PostCard")
|
|
470
|
-
el = ReactElement.new(type: ref, props: { post: serializable_obj })
|
|
471
|
-
expect(Renderer.render(el, postcard_manifest_s)).to match_flight_fixture("serializable_object")
|
|
472
|
-
end
|
|
473
|
-
end
|
|
474
|
-
|
|
475
|
-
describe "client component with props fixture contract (Story 2.1)" do
|
|
476
|
-
let(:counter_manifest) do
|
|
477
|
-
ClientManifest.from_hash({
|
|
478
|
-
"CounterButton" => {
|
|
479
|
-
"id" => "/CounterButton.jsx",
|
|
480
|
-
"name" => "CounterButton",
|
|
481
|
-
"chunks" => ["/CounterButton.jsx"]
|
|
482
|
-
}
|
|
483
|
-
})
|
|
484
|
-
end
|
|
485
|
-
|
|
486
|
-
it "client component with integer+string+boolean props serializes to fixture" do
|
|
487
|
-
ref = counter_manifest.reference_for("CounterButton")
|
|
488
|
-
el = ReactElement.new(type: ref, props: { initialCount: 0, label: "Click me", disabled: false })
|
|
489
|
-
expect(Renderer.render(el, counter_manifest)).to match_flight_fixture("client_component_with_props")
|
|
490
|
-
end
|
|
491
|
-
end
|
|
492
|
-
end
|
|
493
|
-
end
|
|
494
|
-
end
|