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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +123 -48
- data/CONTRIBUTING.md +233 -0
- data/README.md +16 -6
- data/RELEASING.md +125 -139
- data/lib/ruact/packaging.rb +68 -0
- data/lib/ruact/version.rb +1 -1
- 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/runtime/flight-client.js +14 -3
- metadata +6 -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 -1
- data/spec/benchmarks/render_pipeline_benchmark_spec.rb +0 -92
- 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 +0,0 @@
|
|
|
1
|
-
{"typical_allocations":1623,"heavy_allocations":7764}
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "spec_helper"
|
|
4
|
-
require "rspec-benchmark"
|
|
5
|
-
require "memory_profiler"
|
|
6
|
-
require "json"
|
|
7
|
-
|
|
8
|
-
BENCHMARK_BASELINE_FILE = File.expand_path("baseline.json", __dir__)
|
|
9
|
-
|
|
10
|
-
RSpec.describe "RenderPipeline benchmark" do
|
|
11
|
-
include RSpec::Benchmark::Matchers
|
|
12
|
-
|
|
13
|
-
let(:baseline_file) { BENCHMARK_BASELINE_FILE }
|
|
14
|
-
|
|
15
|
-
let(:manifest) do
|
|
16
|
-
entries = (1..20).to_h do |i|
|
|
17
|
-
["Component#{i}", {
|
|
18
|
-
"id" => "/assets/Component#{i}.js",
|
|
19
|
-
"name" => "Component#{i}",
|
|
20
|
-
"chunks" => ["/assets/Component#{i}.js"]
|
|
21
|
-
}]
|
|
22
|
-
end
|
|
23
|
-
Ruact::ClientManifest.from_hash(entries)
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
let(:pipeline) { Ruact::RenderPipeline.new(manifest) }
|
|
27
|
-
|
|
28
|
-
def make_erb(count)
|
|
29
|
-
components = (1..count).map { |i| "<Component#{i} index={#{i}} />" }.join("\n")
|
|
30
|
-
"<div>\n#{components}\n</div>"
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def render_erb(erb_source, active_pipeline = pipeline)
|
|
34
|
-
ctx = Object.new
|
|
35
|
-
active_pipeline.render({ erb: erb_source, binding: ctx.instance_eval { binding } }, mode: :string)
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
describe "typical view (20 components)" do
|
|
39
|
-
let(:erb_typical) { make_erb(20) }
|
|
40
|
-
|
|
41
|
-
it "allocates fewer than the baseline × 1.20 objects" do
|
|
42
|
-
report = MemoryProfiler.report { render_erb(erb_typical) }
|
|
43
|
-
allocations = report.total_allocated
|
|
44
|
-
|
|
45
|
-
if File.exist?(baseline_file)
|
|
46
|
-
baseline = JSON.parse(File.read(baseline_file))
|
|
47
|
-
limit = (baseline["typical_allocations"] * 1.20).ceil
|
|
48
|
-
expect(allocations).to be <= limit,
|
|
49
|
-
"Typical view allocations #{allocations} exceed baseline limit #{limit} " \
|
|
50
|
-
"(baseline: #{baseline['typical_allocations']})"
|
|
51
|
-
else
|
|
52
|
-
# First run — establish baseline
|
|
53
|
-
File.write(baseline_file, JSON.generate("typical_allocations" => allocations,
|
|
54
|
-
"heavy_allocations" => nil))
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
describe "heavy view (100 components)" do
|
|
60
|
-
let(:manifest_heavy) do
|
|
61
|
-
entries = (1..100).to_h do |i|
|
|
62
|
-
["Component#{i}", {
|
|
63
|
-
"id" => "/assets/Component#{i}.js",
|
|
64
|
-
"name" => "Component#{i}",
|
|
65
|
-
"chunks" => ["/assets/Component#{i}.js"]
|
|
66
|
-
}]
|
|
67
|
-
end
|
|
68
|
-
Ruact::ClientManifest.from_hash(entries)
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
let(:pipeline_heavy) { Ruact::RenderPipeline.new(manifest_heavy) }
|
|
72
|
-
let(:erb_heavy) { make_erb(100) }
|
|
73
|
-
|
|
74
|
-
it "allocates fewer than the baseline × 1.20 objects" do
|
|
75
|
-
report = MemoryProfiler.report { render_erb(erb_heavy, pipeline_heavy) }
|
|
76
|
-
allocations = report.total_allocated
|
|
77
|
-
|
|
78
|
-
if File.exist?(baseline_file)
|
|
79
|
-
data = JSON.parse(File.read(baseline_file))
|
|
80
|
-
if data["heavy_allocations"]
|
|
81
|
-
limit = (data["heavy_allocations"] * 1.20).ceil
|
|
82
|
-
expect(allocations).to be <= limit,
|
|
83
|
-
"Heavy view allocations #{allocations} exceed baseline limit #{limit} " \
|
|
84
|
-
"(baseline: #{data['heavy_allocations']})"
|
|
85
|
-
else
|
|
86
|
-
data["heavy_allocations"] = allocations
|
|
87
|
-
File.write(baseline_file, JSON.generate(data))
|
|
88
|
-
end
|
|
89
|
-
end
|
|
90
|
-
end
|
|
91
|
-
end
|
|
92
|
-
end
|
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
# Flight Wire Format Fixtures
|
|
2
|
-
|
|
3
|
-
This directory contains fixture files used to test the Flight wire format serializer. Each `.txt` file contains the exact byte output that `Ruact::Flight::Renderer.render` is expected to produce for a given Ruby input.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## Wire Format Reference
|
|
8
|
-
|
|
9
|
-
The React Flight wire protocol encodes a component tree as a series of newline-terminated rows:
|
|
10
|
-
|
|
11
|
-
```
|
|
12
|
-
<hex-id>:<payload>\n # model row — a JSON value at position <hex-id>
|
|
13
|
-
<hex-id>:I<json-array>\n # import row — registers a client module: [moduleId, exportName, chunks]
|
|
14
|
-
<hex-id>:E<json-error>\n # error row — encodes a serialized error object
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
- **Row `0`** is always the root — the main React element tree returned to the client.
|
|
18
|
-
- **Import rows (`I` rows)** always appear *before* the model rows that reference them.
|
|
19
|
-
- Hex IDs start at `0` for the root and increment (`1`, `2`, `a`, `b`, …) for each additional row.
|
|
20
|
-
|
|
21
|
-
### Worked Example — `client_reference.txt`
|
|
22
|
-
|
|
23
|
-
Ruby input:
|
|
24
|
-
```ruby
|
|
25
|
-
manifest = Ruact::ClientManifest.new({"LikeButton" => {moduleId: "/LikeButton.jsx", chunks: ["/LikeButton.jsx"]}})
|
|
26
|
-
ref = manifest.reference_for("LikeButton")
|
|
27
|
-
Ruact::Flight::Renderer.render(ref, manifest)
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
Expected output (`client_reference.txt`):
|
|
31
|
-
```
|
|
32
|
-
1:I["/LikeButton.jsx","LikeButton",["/LikeButton.jsx"]]
|
|
33
|
-
0:["$","$L1",null,{}]
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
- Row `1` is the import row — it tells React where to find `LikeButton`.
|
|
37
|
-
- Row `0` is the root element — `["$","$L1",null,{}]` is a React element whose type is `$L1` (a reference to import row 1), with `null` key and empty props `{}`.
|
|
38
|
-
|
|
39
|
-
---
|
|
40
|
-
|
|
41
|
-
## How to assert on Flight output
|
|
42
|
-
|
|
43
|
-
Three RSpec matcher modes live in `spec/support/matchers/flight_fixture_matcher.rb`. Pick the mode that names what you actually care about — defaulting to `match_flight_structure` for new specs avoids the cosmetic-change brittleness that bit Phase 1 (a `JSON.generate` tweak broke 30+ specs at once).
|
|
44
|
-
|
|
45
|
-
| Mode | When to use | What it tolerates | Failure shape |
|
|
46
|
-
|---|---|---|---|
|
|
47
|
-
| `match_flight_fixture(name)` | The wire format itself is the contract — bytes-for-bytes guard. Use for serializer escape rules, ordering invariants, or any test where "the output looks exactly like this" is the assertion. | Nothing — exact `==` against the fixture file. | Inspected expected vs. actual strings (whitespace and escapes visible). |
|
|
48
|
-
| `match_flight_structure(expected)` | The parsed semantics are the contract — change-tolerant. Use for round-tripping tests, scaffolding, end-to-end render tests, and any case where JSON key reordering or whitespace tweaks should not break the spec. | JSON key insertion order inside payload hashes; whitespace inside JSON; relative ordering among `:import` rows (which Flight treats as an unordered set). Cross-class row order (imports → models → deferred → errors) is **preserved** because the wire protocol depends on it. | Row-indexed diff naming the differing field path (e.g. `Row 0 (model) differs at .payload[0]:`). |
|
|
49
|
-
| `include_flight_row(predicate)` | A specific shape must appear regardless of order or siblings. Use for concurrency tests, partial assertions in multi-row outputs, or "this row exists somewhere" checks. | Sibling rows; ordering. Predicate is subset-matched (only the keys you list are compared). | Predicate inspect + a list of every parsed row with id/class/payload preview. |
|
|
50
|
-
|
|
51
|
-
### Worked examples
|
|
52
|
-
|
|
53
|
-
Each example is a complete RSpec assertion you can paste into a spec file (above `RSpec.describe` blocks where the matchers are autoloaded by `spec_helper`).
|
|
54
|
-
|
|
55
|
-
**`match_flight_fixture` — wire-format contract guard:**
|
|
56
|
-
```ruby
|
|
57
|
-
serialized = "0:\"$$danger\"\n"
|
|
58
|
-
expect(serialized).to match_flight_fixture("string_dollar_escape")
|
|
59
|
-
# Asserts byte-for-byte against `string_dollar_escape.txt` — proves the `$` → `$$` escape rule.
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
**`match_flight_structure` — parsed-semantics assertion:**
|
|
63
|
-
```ruby
|
|
64
|
-
wire = %(1:I["/LikeButton.jsx","LikeButton",["/LikeButton.jsx"]]\n) +
|
|
65
|
-
%(0:["$","$L1",null,{"postId":42}]\n)
|
|
66
|
-
|
|
67
|
-
expect(wire).to match_flight_structure([
|
|
68
|
-
{ id: 1, class: :import, payload: ["/LikeButton.jsx", "LikeButton", ["/LikeButton.jsx"]] },
|
|
69
|
-
{ id: 0, class: :model, payload: ["$", "$L1", nil, { "postId" => 42 }] }
|
|
70
|
-
])
|
|
71
|
-
# Passes even if `JSON.generate` re-orders props later — what matters is that postId=42 round-trips.
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
**`include_flight_row` — ordering-independent presence check:**
|
|
75
|
-
```ruby
|
|
76
|
-
wire = %(0:["$","CounterButton",null,{"initialCount":0}]\n)
|
|
77
|
-
|
|
78
|
-
expect(wire).to include_flight_row(
|
|
79
|
-
class: :model,
|
|
80
|
-
payload: hash_including("initialCount" => 0)
|
|
81
|
-
)
|
|
82
|
-
# Used in concurrency specs where multiple threads emit interleaved rows; only the row's shape matters.
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
### ❌ Avoid / ✅ Prefer
|
|
86
|
-
|
|
87
|
-
```ruby
|
|
88
|
-
❌ expect(output).to include('"postId":42') # cosmetic-change brittle
|
|
89
|
-
✅ expect(output).to include_flight_row(class: :model, payload: hash_including("postId" => 42))
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
A future `JSON.generate` change that emits keys in different insertion order (`{"label":"x","postId":42}` instead of `{"postId":42,"label":"x"}`) breaks the literal substring match. The structural form parses the row and compares hashes by key-set, so it survives the cosmetic change.
|
|
93
|
-
|
|
94
|
-
### When a fixture spec fails after a serializer change
|
|
95
|
-
|
|
96
|
-
If `match_flight_fixture` fails after touching `Ruact::Flight::Serializer`, that's by design — the wire format **is** the contract for those fixtures. Either the change is intentional (update the fixture file and the CHANGELOG) or it isn't (revert). Don't switch the spec to `match_flight_structure` to "make it green"; that hides the regression that fixture mode is designed to catch.
|
|
97
|
-
|
|
98
|
-
---
|
|
99
|
-
|
|
100
|
-
## Fixture File Inventory
|
|
101
|
-
|
|
102
|
-
| File | What it tests |
|
|
103
|
-
|------|---------------|
|
|
104
|
-
| `nil.txt` | Ruby `nil` serializes to the JSON `null` literal in row 0 |
|
|
105
|
-
| `boolean_true.txt` | Ruby `true` serializes to the JSON `true` literal |
|
|
106
|
-
| `boolean_false.txt` | Ruby `false` serializes to the JSON `false` literal |
|
|
107
|
-
| `number_integer.txt` | Ruby integer (e.g. `42`) serializes to a bare JSON number |
|
|
108
|
-
| `number_float.txt` | Ruby float (e.g. `3.14`) serializes to a bare JSON float |
|
|
109
|
-
| `bigint.txt` | Ruby integer above `MAX_SAFE_INTEGER` (`2**53`) serializes to `"$n<decimal>"` so the JS client can rebuild a `BigInt` |
|
|
110
|
-
| `nan.txt` | `Float::NAN` serializes to `"$NaN"` (JSON has no NaN literal; Flight uses the `$`-prefix sentinel) |
|
|
111
|
-
| `infinity.txt` | `Float::INFINITY` serializes to `"$Infinity"` |
|
|
112
|
-
| `negative_infinity.txt` | `-Float::INFINITY` serializes to `"$-Infinity"` |
|
|
113
|
-
| `undefined.txt` | The `:undefined` symbol sentinel serializes to `"$undefined"` so the JS client decodes it as `undefined` (JSON has no undefined literal) |
|
|
114
|
-
| `string_basic.txt` | Plain Ruby string serializes to a JSON double-quoted string |
|
|
115
|
-
| `string_dollar_escape.txt` | Strings starting with `$` are escaped to `$$…` to avoid collision with Flight's `$L` reference syntax |
|
|
116
|
-
| `array.txt` | Ruby array serializes to a JSON array in row 0 |
|
|
117
|
-
| `hash.txt` | Ruby hash serializes to a JSON object in row 0 |
|
|
118
|
-
| `client_reference.txt` | A `ClientReference` (no props) produces an import row (`I`) + root element referencing `$L1` |
|
|
119
|
-
| `client_component_with_props.txt` | A `ClientReference` with props passes them as the fourth element of the root array |
|
|
120
|
-
| `react_element_no_props.txt` | A `ReactElement` with no props produces `["$","<tag>",null,{}]` in row 0 |
|
|
121
|
-
| `as_json_object.txt` | An object responding to `as_json` is serialized via that method; if it resolves to a `ClientReference`, import + root rows are emitted |
|
|
122
|
-
| `serializable_object.txt` | An object including `Ruact::Serializable` and declaring `ruact_props` serializes only the declared props |
|
|
123
|
-
| `redirect_row.txt` | A redirect instruction serializes to a JSON object with `redirectUrl` and `redirectType` keys in row 0 |
|
|
124
|
-
|
|
125
|
-
---
|
|
126
|
-
|
|
127
|
-
## Adding a New Fixture
|
|
128
|
-
|
|
129
|
-
See the [Fixture-First Workflow](../../../CONTRIBUTING.md#fixture-first-workflow-adding-a-new-serializable-type) section in `CONTRIBUTING.md` for the full four-step process.
|
|
130
|
-
|
|
131
|
-
Quick reference:
|
|
132
|
-
|
|
133
|
-
1. Create `spec/fixtures/flight/<type_name>.txt` with the expected wire bytes.
|
|
134
|
-
2. Write a failing spec using `match_flight_fixture("<type_name>")`.
|
|
135
|
-
3. Implement the type handler in `flight/serializer.rb`.
|
|
136
|
-
4. Run `bundle exec rspec` — the new spec must pass, full suite must have no regressions.
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0:[1,"a",true,null]
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0:"$n9007199254740992"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0:false
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0:true
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0:{"debug":true,"count":5,"label":"x"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0:"$Infinity"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0:"$NaN"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0:"$-Infinity"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0:null
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0:3.14
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0:42
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0:["$","div",null,{}]
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0:{"redirectUrl":"/posts/1","redirectType":"push"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0:"hello"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0:"$$danger"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0:"$undefined"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
ruact: <LikeButton> at app/views/home/index.html.erb:3 children are not supported — pass content as a prop, e.g. `<LikeButton content={...} />`.
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
<%# An UNMIGRATED host layout: it has the React root but never calls
|
|
2
|
-
`ruact_js_assets`, so rendering the page through it would produce a
|
|
3
|
-
document with no bootstrap entry and no Flight payload — a blank page.
|
|
4
|
-
ruact must detect that and fall back to its built-in shell instead. %>
|
|
5
|
-
<!DOCTYPE html>
|
|
6
|
-
<html>
|
|
7
|
-
<head>
|
|
8
|
-
<link rel="stylesheet" href="/host-app.css" />
|
|
9
|
-
<title>Host App Title</title>
|
|
10
|
-
</head>
|
|
11
|
-
<body>
|
|
12
|
-
<%= yield %>
|
|
13
|
-
<%# ruact: root %>
|
|
14
|
-
<div id="root"></div>
|
|
15
|
-
</body>
|
|
16
|
-
</html>
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
<%# An UNMIGRATED layout that cannot survive being rendered on a ruact page.
|
|
2
|
-
It reads an ivar that only a plain Rails action would have set — exactly the
|
|
3
|
-
shape of a real pre-migration app, whose layout has never once run on a
|
|
4
|
-
ruact-rendered page. If ruact renders this speculatively just to discover it
|
|
5
|
-
lacks `ruact_js_assets`, the app 500s on a page that used to work.
|
|
6
|
-
|
|
7
|
-
NOTE: this comment MENTIONS the helper name on purpose — a readiness check
|
|
8
|
-
that greps for the bare string counts this layout as wired and renders it. %>
|
|
9
|
-
<%# <%= ruact_js_assets %> %>
|
|
10
|
-
<%# ...and the line above is a genuinely COMMENTED-OUT call, which is the
|
|
11
|
-
sharper version of the same trap: ERB comments do not nest, so it emits
|
|
12
|
-
nothing, but a check that only looks for `<%=` sees a call. Both shapes must
|
|
13
|
-
read as UNMIGRATED, or this layout gets rendered and 500s below. %>
|
|
14
|
-
<!DOCTYPE html>
|
|
15
|
-
<html>
|
|
16
|
-
<head>
|
|
17
|
-
<title><%= @page_title.upcase %></title>
|
|
18
|
-
</head>
|
|
19
|
-
<body>
|
|
20
|
-
<%= yield %>
|
|
21
|
-
<%# ruact: root %>
|
|
22
|
-
<div id="root"></div>
|
|
23
|
-
</body>
|
|
24
|
-
</html>
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
<%# A layout that calls `ruact_js_assets` but never got the React root div.
|
|
2
|
-
The payload and the bootstrap both ship, so an assets-only readiness check
|
|
3
|
-
accepts it — and the browser boots React with nothing to mount into,
|
|
4
|
-
producing a silently blank page. %>
|
|
5
|
-
<!DOCTYPE html>
|
|
6
|
-
<html>
|
|
7
|
-
<head>
|
|
8
|
-
<link rel="stylesheet" href="/host-app.css" />
|
|
9
|
-
<title>Host App Title</title>
|
|
10
|
-
</head>
|
|
11
|
-
<body>
|
|
12
|
-
<%= yield %>
|
|
13
|
-
<%= ruact_js_assets %>
|
|
14
|
-
</body>
|
|
15
|
-
</html>
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
<%# A MIGRATED host layout: the app owns the document. The stylesheet link is
|
|
2
|
-
the whole point — it is the thing ruact's built-in shell has no slot for,
|
|
3
|
-
so a spec asserting it appears in the response is asserting that the host
|
|
4
|
-
app's CSS actually reaches a ruact page. %>
|
|
5
|
-
<!DOCTYPE html>
|
|
6
|
-
<html>
|
|
7
|
-
<head>
|
|
8
|
-
<link rel="stylesheet" href="/host-app.css" />
|
|
9
|
-
<title>Host App Title</title>
|
|
10
|
-
</head>
|
|
11
|
-
<body>
|
|
12
|
-
<%= yield %>
|
|
13
|
-
<%# ruact: root %>
|
|
14
|
-
<div id="root"></div>
|
|
15
|
-
<%= ruact_js_assets %>
|
|
16
|
-
</body>
|
|
17
|
-
</html>
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "spec_helper"
|
|
4
|
-
require "pathname"
|
|
5
|
-
|
|
6
|
-
# Story 5.2 — the anti-rot gate for the README's write→verify demo.
|
|
7
|
-
#
|
|
8
|
-
# ⚠️ THE README DEMO SHOWS THIS MESSAGE. If this spec goes red, the recording
|
|
9
|
-
# at https://ruact.dev/readme-write-verify.gif is now showing a message the
|
|
10
|
-
# gem no longer produces — RE-RECORD IT.
|
|
11
|
-
# Sources: design-artifacts/E-Assets/readme-write-verify/ in the planning
|
|
12
|
-
# repository (`./build-app.sh && ./record.sh && ./assemble.sh`).
|
|
13
|
-
#
|
|
14
|
-
# The fixture is PRODUCED BY THE GEM, never transcribed. It was written by
|
|
15
|
-
# running the preprocessor over the committed `.html.erb` beside it:
|
|
16
|
-
#
|
|
17
|
-
# ruby -Ilib -rruact -e 'begin
|
|
18
|
-
# Ruact::ErbPreprocessor.transform(
|
|
19
|
-
# File.read("spec/fixtures/readme/children-error.html.erb"),
|
|
20
|
-
# identifier: "app/views/home/index.html.erb")
|
|
21
|
-
# rescue Ruact::ChildrenNotSupportedError => e
|
|
22
|
-
# File.write("spec/fixtures/readme/children-error.txt", e.message + "\n")
|
|
23
|
-
# end'
|
|
24
|
-
#
|
|
25
|
-
# The identifier is the guide's own template path, so the fixture is verbatim
|
|
26
|
-
# the line the recording shows (modulo the absolute prefix Rails prepends in a
|
|
27
|
-
# real app). The monorepo owns the other half of this gate: the same failure
|
|
28
|
-
# reached through a real Rails render, in
|
|
29
|
-
# docs/examples/getting-started/harness/spec/requests/getting_started_spec.rb.
|
|
30
|
-
# That one cannot live here — the gem repository is public and reads nothing
|
|
31
|
-
# from the private planning repository — which is exactly why this spec exists:
|
|
32
|
-
# the published artifact stays verifiable in its own repository.
|
|
33
|
-
RSpec.describe "the README demo's error message", :story_5_2 do
|
|
34
|
-
let(:root) { Pathname.new(File.expand_path("..", __dir__)) }
|
|
35
|
-
let(:fixtures) { root.join("spec/fixtures/readme") }
|
|
36
|
-
let(:source) { fixtures.join("children-error.html.erb").read }
|
|
37
|
-
let(:expected) { fixtures.join("children-error.txt").read.strip }
|
|
38
|
-
|
|
39
|
-
# The exact call the recording's third beat makes, one layer down: a template
|
|
40
|
-
# whose PascalCase tag has children, compiled with the guide's template path.
|
|
41
|
-
def message
|
|
42
|
-
Ruact::ErbPreprocessor.transform(source, identifier: "app/views/home/index.html.erb")
|
|
43
|
-
raise "expected Ruact::ChildrenNotSupportedError, none was raised"
|
|
44
|
-
rescue Ruact::ChildrenNotSupportedError => e
|
|
45
|
-
e.message
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
it "is byte-for-byte what the gem produces today" do
|
|
49
|
-
expect(message).to eq(expected),
|
|
50
|
-
"the loud-children message changed. The README demo at " \
|
|
51
|
-
"https://ruact.dev/readme-write-verify.gif shows the old one — " \
|
|
52
|
-
"regenerate the fixture, then RE-RECORD the demo."
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
it "names the component, the file:line and the fix — the three things the demo is about" do
|
|
56
|
-
expect(expected).to include("<LikeButton>")
|
|
57
|
-
expect(expected).to include("app/views/home/index.html.erb:3")
|
|
58
|
-
expect(expected).to include("<LikeButton content={...} />")
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
it "is the message the README's demo is claimed to show" do
|
|
62
|
-
readme = root.join("README.md").read
|
|
63
|
-
|
|
64
|
-
expect(readme).to include("https://ruact.dev/readme-write-verify.gif")
|
|
65
|
-
expect(readme).to include("Ruact::ChildrenNotSupportedError")
|
|
66
|
-
end
|
|
67
|
-
end
|