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,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