ruact 0.0.4 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +54 -2
- data/.rubocop_todo.yml +3 -115
- data/CHANGELOG.md +68 -17
- data/bench/server_functions_dispatch_bench.rb +109 -142
- data/bench/server_functions_dispatch_bench.results.md +29 -0
- data/docs/internal/decisions/server-functions-api.md +402 -0
- data/lib/generators/ruact/install/install_generator.rb +310 -25
- data/lib/generators/ruact/install/templates/Procfile.dev.tt +2 -0
- data/lib/generators/ruact/install/templates/dev.tt +16 -0
- data/lib/generators/ruact/install/templates/package.json.tt +17 -0
- data/lib/generators/ruact/install/templates/vite.config.js.tt +3 -1
- data/lib/generators/ruact/scaffold/scaffold_attribute.rb +114 -0
- data/lib/generators/ruact/scaffold/scaffold_form_helpers.rb +114 -0
- data/lib/generators/ruact/scaffold/scaffold_generator.rb +491 -0
- data/lib/generators/ruact/scaffold/scaffold_shadcn_preflight.rb +229 -0
- data/lib/generators/ruact/scaffold/templates/components/DeleteDialog.tsx.tt +104 -0
- data/lib/generators/ruact/scaffold/templates/components/Form.tsx.tt +212 -0
- data/lib/generators/ruact/scaffold/templates/components/List.tsx.tt +338 -0
- data/lib/generators/ruact/scaffold/templates/components/agnostic/DeleteDialog.tsx.tt +92 -0
- data/lib/generators/ruact/scaffold/templates/components/agnostic/Form.tsx.tt +174 -0
- data/lib/generators/ruact/scaffold/templates/components/agnostic/List.tsx.tt +253 -0
- data/lib/generators/ruact/scaffold/templates/controller.rb.tt +130 -0
- data/lib/generators/ruact/scaffold/templates/queries/application_query.rb.tt +13 -0
- data/lib/generators/ruact/scaffold/templates/queries/query.rb.tt +59 -0
- data/lib/generators/ruact/scaffold/templates/request_spec.rb.tt +77 -0
- data/lib/generators/ruact/scaffold/templates/views/edit.html.erb.tt +7 -0
- data/lib/generators/ruact/scaffold/templates/views/index.html.erb.tt +6 -0
- data/lib/generators/ruact/scaffold/templates/views/new.html.erb.tt +5 -0
- data/lib/generators/ruact/scaffold/templates/views/show.html.erb.tt +16 -0
- data/lib/ruact/client_manifest.rb +37 -36
- data/lib/ruact/component_contract.rb +115 -0
- data/lib/ruact/configuration.rb +80 -28
- data/lib/ruact/controller.rb +69 -421
- data/lib/ruact/doctor.rb +133 -4
- data/lib/ruact/erb_preprocessor.rb +65 -12
- data/lib/ruact/erb_preprocessor_hook.rb +4 -1
- data/lib/ruact/errors.rb +30 -44
- data/lib/ruact/html_converter.rb +22 -1
- data/lib/ruact/railtie.rb +56 -200
- data/lib/ruact/server.rb +28 -6
- data/lib/ruact/server_functions/codegen.rb +49 -188
- data/lib/ruact/server_functions/codegen_v2.rb +23 -6
- data/lib/ruact/server_functions/codegen_v2_query_params.rb +140 -0
- data/lib/ruact/server_functions/error_payload.rb +1 -1
- data/lib/ruact/server_functions/error_rendering.rb +22 -29
- data/lib/ruact/server_functions/name_bridge.rb +14 -16
- data/lib/ruact/server_functions/query_source.rb +35 -8
- data/lib/ruact/server_functions/route_source.rb +3 -4
- data/lib/ruact/server_functions/snapshot.rb +12 -139
- data/lib/ruact/server_functions/validation_errors.rb +70 -0
- data/lib/ruact/server_functions.rb +21 -25
- data/lib/ruact/signed_references.rb +162 -0
- data/lib/ruact/string_distance.rb +72 -0
- data/lib/ruact/validation_errors_collector.rb +139 -0
- data/lib/ruact/version.rb +1 -1
- data/lib/ruact/view_helper.rb +102 -0
- data/lib/ruact.rb +19 -19
- data/lib/tasks/ruact.rake +10 -53
- data/spec/fixtures/story_7_9_views/controller_request_spec_support/errors_demo/new.html.erb +3 -0
- data/spec/ruact/client_manifest_spec.rb +36 -0
- data/spec/ruact/component_contract_spec.rb +119 -0
- data/spec/ruact/configuration_spec.rb +51 -34
- data/spec/ruact/controller_request_spec.rb +264 -0
- data/spec/ruact/controller_spec.rb +63 -326
- data/spec/ruact/doctor_spec.rb +201 -0
- data/spec/ruact/erb_preprocessor_hook_spec.rb +4 -1
- data/spec/ruact/erb_preprocessor_spec.rb +127 -0
- data/spec/ruact/errors_spec.rb +0 -45
- data/spec/ruact/html_converter_spec.rb +50 -0
- data/spec/ruact/install_generator_spec.rb +591 -4
- data/spec/ruact/query_request_spec.rb +109 -1
- data/spec/ruact/scaffold_generator_spec.rb +1835 -0
- data/spec/ruact/server_bucket_request_spec.rb +142 -0
- data/spec/ruact/server_function_name_spec.rb +1 -1
- data/spec/ruact/server_functions/codegen_spec.rb +158 -269
- data/spec/ruact/server_functions/name_bridge_spec.rb +9 -9
- data/spec/ruact/server_functions/query_source_spec.rb +51 -0
- data/spec/ruact/server_functions/railtie_integration_spec.rb +71 -268
- data/spec/ruact/server_functions/rake_spec.rb +29 -29
- data/spec/ruact/server_functions/snapshot_spec.rb +53 -213
- data/spec/ruact/server_rescue_request_spec.rb +6 -6
- data/spec/ruact/server_spec.rb +8 -9
- data/spec/ruact/signed_references_spec.rb +164 -0
- data/spec/ruact/string_distance_spec.rb +38 -0
- data/spec/ruact/validation_errors_spec.rb +116 -0
- data/spec/ruact/view_helper_spec.rb +79 -0
- data/spec/spec_helper.rb +0 -5
- data/vendor/javascript/ruact-server-functions-runtime/index.d.ts +44 -47
- data/vendor/javascript/ruact-server-functions-runtime/index.js +151 -107
- data/vendor/javascript/ruact-server-functions-runtime/index.test.mjs +72 -75
- data/vendor/javascript/ruact-server-functions-runtime/package.json +2 -2
- data/vendor/javascript/ruact-server-functions-runtime/usequery.test.mjs +187 -0
- data/vendor/javascript/vite-plugin-ruact/bootstrap.test.mjs +96 -0
- data/vendor/javascript/vite-plugin-ruact/index.js +353 -7
- data/vendor/javascript/vite-plugin-ruact/manifest-contract.test.mjs +182 -0
- data/vendor/javascript/vite-plugin-ruact/package-lock.json +16 -0
- data/vendor/javascript/vite-plugin-ruact/package.json +3 -1
- data/vendor/javascript/vite-plugin-ruact/registry.test.mjs +199 -0
- data/vendor/javascript/vite-plugin-ruact/runtime/bootstrap.jsx +68 -0
- data/vendor/javascript/vite-plugin-ruact/runtime/flight-client.js +235 -0
- data/vendor/javascript/vite-plugin-ruact/runtime/ruact-router.js +473 -0
- data/vendor/javascript/vite-plugin-ruact/server-functions-codegen.mjs +114 -139
- data/vendor/javascript/vite-plugin-ruact/server-functions-codegen.test.mjs +199 -262
- data/vendor/javascript/vite-plugin-ruact/tsconfig.json +18 -0
- data/vendor/javascript/vite-plugin-ruact/tsconfig.scaffold-agnostic.json +18 -0
- data/vendor/javascript/vite-plugin-ruact/tsconfig.scaffold.json +20 -0
- data/vendor/javascript/vite-plugin-ruact/type-tests/emitted-module.test-d.ts +23 -0
- data/vendor/javascript/vite-plugin-ruact/type-tests/scaffold/PostDeleteDialog.tsx +90 -0
- data/vendor/javascript/vite-plugin-ruact/type-tests/scaffold/PostForm.tsx +238 -0
- data/vendor/javascript/vite-plugin-ruact/type-tests/scaffold/PostList.tsx +339 -0
- data/vendor/javascript/vite-plugin-ruact/type-tests/scaffold/agnostic/PostDeleteDialog.tsx +85 -0
- data/vendor/javascript/vite-plugin-ruact/type-tests/scaffold/agnostic/PostForm.tsx +216 -0
- data/vendor/javascript/vite-plugin-ruact/type-tests/scaffold/agnostic/PostList.tsx +269 -0
- data/vendor/javascript/vite-plugin-ruact/type-tests/scaffold/agnostic/ambient.d.ts +78 -0
- data/vendor/javascript/vite-plugin-ruact/type-tests/scaffold/ambient.d.ts +166 -0
- data/vendor/javascript/vite-plugin-ruact/type-tests/typed-query.test-d.ts +48 -0
- data/vendor/javascript/vite-plugin-ruact/type-tests/usequery.test-d.ts +26 -0
- metadata +55 -15
- data/lib/generators/ruact/install/templates/application.jsx.tt +0 -51
- data/lib/ruact/server_action.rb +0 -131
- data/lib/ruact/server_functions/endpoint_controller.rb +0 -237
- data/lib/ruact/server_functions/registry.rb +0 -148
- data/lib/ruact/server_functions/registry_entry.rb +0 -26
- data/lib/ruact/server_functions/standalone_context.rb +0 -103
- data/lib/ruact/server_functions/standalone_dispatcher.rb +0 -178
- data/spec/ruact/server_functions/csrf_request_spec.rb +0 -380
- data/spec/ruact/server_functions/dispatch_request_spec.rb +0 -819
- data/spec/ruact/server_functions/registry_spec.rb +0 -199
- data/spec/ruact/server_functions/standalone_action_spec.rb +0 -224
- data/spec/ruact/server_functions/standalone_context_spec.rb +0 -142
- data/spec/ruact/server_functions/standalone_dispatcher_spec.rb +0 -273
|
@@ -85,5 +85,132 @@ module Ruact
|
|
|
85
85
|
expect(result).to match(/__ruact_component__\("LikeButton"/)
|
|
86
86
|
end
|
|
87
87
|
end
|
|
88
|
+
|
|
89
|
+
# Bugfix (Sprint Change Proposal 2026-06-16 §4.5): `<Suspense delay="2.5">`
|
|
90
|
+
# used to be ignored — the preprocessor never carried the attribute, so the
|
|
91
|
+
# SuspenseElement always took its default delay. It now forwards an optional
|
|
92
|
+
# `delay` as `data-ruact-delay` on the emitted <ruact-suspense> element.
|
|
93
|
+
describe "Suspense delay attribute" do
|
|
94
|
+
it "forwards delay to the ruact-suspense element as data-ruact-delay" do
|
|
95
|
+
result = transform.call(%(<Suspense fallback="loading" delay="2.5"><Spinner /></Suspense>))
|
|
96
|
+
expect(result).to include(%(data-ruact-delay="2.5"))
|
|
97
|
+
expect(result).to include(%(data-ruact-fallback="loading"))
|
|
98
|
+
expect(result).to include("</ruact-suspense>")
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
it "omits data-ruact-delay when no delay attribute is present" do
|
|
102
|
+
result = transform.call(%(<Suspense fallback="loading"><Spinner /></Suspense>))
|
|
103
|
+
expect(result).to include(%(data-ruact-fallback="loading"))
|
|
104
|
+
expect(result).not_to include("data-ruact-delay")
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
it "extracts delay regardless of attribute order" do
|
|
108
|
+
result = transform.call(%(<Suspense delay="0.75" fallback="wait"><X /></Suspense>))
|
|
109
|
+
expect(result).to include(%(data-ruact-delay="0.75"))
|
|
110
|
+
expect(result).to include(%(data-ruact-fallback="wait"))
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
it "accepts a single-quoted delay attribute" do
|
|
114
|
+
result = transform.call(%(<Suspense fallback='loading' delay='1.5'><X /></Suspense>))
|
|
115
|
+
expect(result).to include(%(data-ruact-delay="1.5"))
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Story 13.5 (FR100) — preprocess-time component-contract validation, wired
|
|
120
|
+
# through an injectable registry seam (a stub responding to +contract_for+).
|
|
121
|
+
describe "component contract validation", :story_13_5 do
|
|
122
|
+
# A minimal stub registry: maps component name → contract Hash (or nil).
|
|
123
|
+
def registry_for(contracts)
|
|
124
|
+
Class.new do
|
|
125
|
+
def initialize(contracts) = (@contracts = contracts)
|
|
126
|
+
def contract_for(name, **) = @contracts[name]
|
|
127
|
+
end.new(contracts)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
let(:contract) do
|
|
131
|
+
{ "props" => { "postId" => "required", "initialCount" => "optional" } }
|
|
132
|
+
end
|
|
133
|
+
let(:registry) { registry_for("LikeButton" => contract) }
|
|
134
|
+
|
|
135
|
+
def run(source, identifier: "app/views/posts/show.html.erb")
|
|
136
|
+
described_class.transform(source, identifier: identifier, registry: registry)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
it "raises on a missing required prop, naming component + file:line + fix (AC#1, AC#3)" do
|
|
140
|
+
expect { run("<LikeButton initialCount={5} />") }
|
|
141
|
+
.to raise_error(ComponentContractError) do |e|
|
|
142
|
+
expect(e.message).to include("LikeButton")
|
|
143
|
+
expect(e.message).to include("app/views/posts/show.html.erb:1")
|
|
144
|
+
expect(e.message).to include("postId")
|
|
145
|
+
expect(e.message).to include("add the required prop")
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
it "raises on an unknown prop with a did-you-mean suggestion (AC#3)" do
|
|
150
|
+
expect { run("<LikeButton postId={1} postID={2} />") }
|
|
151
|
+
.to raise_error(ComponentContractError, /did you mean "postId"\?/)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
it "computes the correct line for a call site lower in the template" do
|
|
155
|
+
source = "line1\nline2\n<LikeButton initialCount={5} />"
|
|
156
|
+
expect { run(source) }.to raise_error(ComponentContractError, /show\.html\.erb:3/)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
it "passes a valid call and emits the normal placeholder (AC#1)" do
|
|
160
|
+
result = run("<LikeButton postId={@post.id} initialCount={5} />")
|
|
161
|
+
expect(result)
|
|
162
|
+
.to eq(%(<%= __ruact_component__("LikeButton", { "postId" => @post.id, "initialCount" => 5 }) %>))
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
it "does NOT re-wrap the contract error with the generic line/snippet tail" do
|
|
166
|
+
expect { run("<LikeButton initialCount={5} />") }
|
|
167
|
+
.to raise_error(ComponentContractError) { |e| expect(e.message).not_to match(/at line \d+:/) }
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# AC#2 — opt-in / byte-identity: a component with no contract entry is
|
|
171
|
+
# validated NOT AT ALL and emits the exact same placeholder as pre-13.5.
|
|
172
|
+
describe "opt-in fail-open (AC#2)" do
|
|
173
|
+
it "emits byte-identical output for a contract-less component" do
|
|
174
|
+
source = "<NavBar foo={1} bar={2} />"
|
|
175
|
+
with_contract = run(source) # registry has NO "NavBar" entry → fail open
|
|
176
|
+
without_registry = described_class.transform(source, registry: nil)
|
|
177
|
+
expected = %(<%= __ruact_component__("NavBar", { "foo" => 1, "bar" => 2 }) %>)
|
|
178
|
+
expect(with_contract).to eq(expected)
|
|
179
|
+
expect(without_registry).to eq(expected)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
it "never consults the registry for a no-tag source (fast path)" do
|
|
183
|
+
spy_registry = registry_for({})
|
|
184
|
+
allow(spy_registry).to receive(:contract_for).and_call_original
|
|
185
|
+
result = described_class.transform("<div><p>plain</p></div>", registry: spy_registry)
|
|
186
|
+
expect(result).to eq("<div><p>plain</p></div>")
|
|
187
|
+
expect(spy_registry).not_to have_received(:contract_for)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Codex review (Patch 2) — the DEFAULT registry (`Ruact.manifest`) must
|
|
191
|
+
# not even be read when the source has no component tags.
|
|
192
|
+
it "never reads Ruact.manifest for a no-tag source (default registry)" do
|
|
193
|
+
allow(Ruact).to receive(:manifest)
|
|
194
|
+
described_class.transform("<div><p>plain</p></div>")
|
|
195
|
+
expect(Ruact).not_to have_received(:manifest)
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
describe "slots (AC#5)" do
|
|
200
|
+
let(:contract) do
|
|
201
|
+
{ "props" => { "title" => "required" }, "slots" => { "header" => "required" } }
|
|
202
|
+
end
|
|
203
|
+
let(:registry) { registry_for("Card" => contract) }
|
|
204
|
+
|
|
205
|
+
it "raises when a required slot attribute is omitted at the call site" do
|
|
206
|
+
expect { run("<Card title={@t} />") }
|
|
207
|
+
.to raise_error(ComponentContractError, /missing required slot.*header/m)
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
it "passes when the declared slot is supplied as an attribute" do
|
|
211
|
+
expect { run("<Card title={@t} header={@h} />") }.not_to raise_error
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
end
|
|
88
215
|
end
|
|
89
216
|
end
|
data/spec/ruact/errors_spec.rb
CHANGED
|
@@ -60,25 +60,6 @@ module Ruact
|
|
|
60
60
|
end
|
|
61
61
|
end
|
|
62
62
|
|
|
63
|
-
describe "Ruact::CurrentUserNotConfiguredError", :story_8_3 do
|
|
64
|
-
it "is a subclass of Ruact::Error" do
|
|
65
|
-
expect(CurrentUserNotConfiguredError.ancestors).to include(Error)
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
it "carries a default message that names BOTH Devise and hand-rolled-session worked examples" do
|
|
69
|
-
message = CurrentUserNotConfiguredError.new.message
|
|
70
|
-
expect(message).to include("Ruact.current_user requires Ruact.config.current_user_resolver to be set")
|
|
71
|
-
expect(message).to include("Devise")
|
|
72
|
-
expect(message).to include("env['warden']")
|
|
73
|
-
expect(message).to include("hand-rolled session")
|
|
74
|
-
expect(message).to include("rack.session")
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
it "accepts a custom message" do
|
|
78
|
-
expect(CurrentUserNotConfiguredError.new("explicit").message).to eq("explicit")
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
|
|
82
63
|
describe "Ruact::UploadTooLargeError", :story_8_5 do
|
|
83
64
|
it "is a subclass of Ruact::Error (so Story 8.4 rescue_from StandardError catches it)" do
|
|
84
65
|
expect(UploadTooLargeError.ancestors).to include(Error)
|
|
@@ -108,31 +89,5 @@ module Ruact
|
|
|
108
89
|
.to raise_error(StandardError)
|
|
109
90
|
end
|
|
110
91
|
end
|
|
111
|
-
|
|
112
|
-
describe "Ruact::ActionError", :story_8_3 do
|
|
113
|
-
it "is a subclass of Ruact::Error" do
|
|
114
|
-
expect(ActionError.ancestors).to include(Error)
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
it "carries status + body so the dispatcher can render without `render` access" do
|
|
118
|
-
error = ActionError.new(status: 422, body: { error: "invalid" })
|
|
119
|
-
expect(error.status).to eq(422)
|
|
120
|
-
expect(error.body).to eq(error: "invalid")
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
it "accepts a Symbol status" do
|
|
124
|
-
error = ActionError.new(status: :unprocessable_entity, body: nil)
|
|
125
|
-
expect(error.status).to eq(:unprocessable_entity)
|
|
126
|
-
expect(error.body).to be_nil
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
it "synthesises a legible message when none is given" do
|
|
130
|
-
expect(ActionError.new(status: 401, body: nil).message).to include("status=401")
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
it "honours an explicit custom message" do
|
|
134
|
-
expect(ActionError.new(status: 500, body: {}, message: "explicit").message).to eq("explicit")
|
|
135
|
-
end
|
|
136
|
-
end
|
|
137
92
|
end
|
|
138
93
|
end
|
|
@@ -12,6 +12,56 @@ module Ruact
|
|
|
12
12
|
end
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
+
# Bugfix (Sprint Change Proposal 2026-06-16 §4.5): the ERB `delay` attribute
|
|
16
|
+
# is carried as `data-ruact-delay` and must reach SuspenseElement#delay; an
|
|
17
|
+
# absent, blank, or unparseable value falls back to the element's default.
|
|
18
|
+
describe "Suspense element conversion (delay)" do
|
|
19
|
+
let(:default_delay) do
|
|
20
|
+
Ruact::Flight::SuspenseElement.new(fallback: nil, children: "x").delay
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it "parses data-ruact-delay into SuspenseElement#delay as a Float" do
|
|
24
|
+
html = %(<ruact-suspense data-ruact-fallback="loading" data-ruact-delay="2.5"><p>content</p></ruact-suspense>)
|
|
25
|
+
result = convert.call(html)
|
|
26
|
+
expect(result).to be_a(Ruact::Flight::SuspenseElement)
|
|
27
|
+
expect(result.delay).to eq(2.5)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it "falls back to the default delay when the attribute is absent" do
|
|
31
|
+
html = %(<ruact-suspense data-ruact-fallback="loading"><p>content</p></ruact-suspense>)
|
|
32
|
+
result = convert.call(html)
|
|
33
|
+
expect(result).to be_a(Ruact::Flight::SuspenseElement)
|
|
34
|
+
expect(result.delay).to eq(default_delay)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it "falls back to the default delay when the attribute is unparseable" do
|
|
38
|
+
html = %(<ruact-suspense data-ruact-fallback="loading" data-ruact-delay="soon"><p>content</p></ruact-suspense>)
|
|
39
|
+
expect(convert.call(html).delay).to eq(default_delay)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it "falls back to the default delay when the attribute is blank" do
|
|
43
|
+
html = %(<ruact-suspense data-ruact-fallback="loading" data-ruact-delay=""><p>content</p></ruact-suspense>)
|
|
44
|
+
expect(convert.call(html).delay).to eq(default_delay)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it "treats delay=\"0\" as an explicit zero delay, not absent" do
|
|
48
|
+
html = %(<ruact-suspense data-ruact-fallback="loading" data-ruact-delay="0"><p>content</p></ruact-suspense>)
|
|
49
|
+
expect(convert.call(html).delay).to eq(0.0)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it "parses a delay value with surrounding whitespace" do
|
|
53
|
+
html = %(<ruact-suspense data-ruact-fallback="loading" data-ruact-delay=" 2.5 "><p>content</p></ruact-suspense>)
|
|
54
|
+
expect(convert.call(html).delay).to eq(2.5)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it "falls back to the default delay when the value overflows to a non-finite Float" do
|
|
58
|
+
# Float("1e309") => Infinity (Float() does not raise on overflow); a
|
|
59
|
+
# non-finite delay must not reach the renderer's sleep (RangeError).
|
|
60
|
+
html = %(<ruact-suspense data-ruact-fallback="loading" data-ruact-delay="1e309"><p>content</p></ruact-suspense>)
|
|
61
|
+
expect(convert.call(html).delay).to eq(default_delay)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
15
65
|
describe "single DOM element" do
|
|
16
66
|
it "converts a div with class and text child" do
|
|
17
67
|
result = convert.call('<div class="box">hi</div>')
|