ruact 0.0.11 → 0.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +110 -48
  3. data/CONTRIBUTING.md +233 -0
  4. data/README.md +14 -4
  5. data/RELEASING.md +125 -139
  6. data/lib/ruact/packaging.rb +68 -0
  7. data/lib/ruact/version.rb +1 -1
  8. data/vendor/javascript/vite-plugin-ruact/flight-client.test.mjs +321 -0
  9. data/vendor/javascript/vite-plugin-ruact/package-lock.json +11 -0
  10. data/vendor/javascript/vite-plugin-ruact/package.json +1 -0
  11. data/vendor/javascript/vite-plugin-ruact/runtime/flight-client.js +14 -3
  12. metadata +6 -107
  13. data/.codecov.yml +0 -31
  14. data/.github/workflows/ci.yml +0 -284
  15. data/.github/workflows/server-functions-bench.yml +0 -54
  16. data/.rubocop.yml +0 -107
  17. data/.rubocop_todo.yml +0 -63
  18. data/Rakefile +0 -10
  19. data/bench/server_functions_dispatch_bench.rb +0 -276
  20. data/bench/server_functions_dispatch_bench.results.md +0 -150
  21. data/docs/internal/README.md +0 -9
  22. data/docs/internal/decisions/server-functions-api.md +0 -2236
  23. data/spec/benchmarks/baseline.json +0 -12
  24. data/spec/benchmarks/render_pipeline_benchmark_spec.rb +0 -109
  25. data/spec/fixtures/flight/README.md +0 -136
  26. data/spec/fixtures/flight/array.txt +0 -1
  27. data/spec/fixtures/flight/as_json_object.txt +0 -2
  28. data/spec/fixtures/flight/bigint.txt +0 -1
  29. data/spec/fixtures/flight/boolean_false.txt +0 -1
  30. data/spec/fixtures/flight/boolean_true.txt +0 -1
  31. data/spec/fixtures/flight/client_component_with_props.txt +0 -2
  32. data/spec/fixtures/flight/client_reference.txt +0 -2
  33. data/spec/fixtures/flight/hash.txt +0 -1
  34. data/spec/fixtures/flight/infinity.txt +0 -1
  35. data/spec/fixtures/flight/nan.txt +0 -1
  36. data/spec/fixtures/flight/negative_infinity.txt +0 -1
  37. data/spec/fixtures/flight/nil.txt +0 -1
  38. data/spec/fixtures/flight/number_float.txt +0 -1
  39. data/spec/fixtures/flight/number_integer.txt +0 -1
  40. data/spec/fixtures/flight/react_element_no_props.txt +0 -1
  41. data/spec/fixtures/flight/redirect_row.txt +0 -1
  42. data/spec/fixtures/flight/serializable_object.txt +0 -2
  43. data/spec/fixtures/flight/string_basic.txt +0 -1
  44. data/spec/fixtures/flight/string_dollar_escape.txt +0 -1
  45. data/spec/fixtures/flight/undefined.txt +0 -1
  46. data/spec/fixtures/readme/children-error.html.erb +0 -3
  47. data/spec/fixtures/readme/children-error.txt +0 -1
  48. data/spec/fixtures/story_7_9_views/controller_request_spec_support/demo/show.html.erb +0 -3
  49. data/spec/fixtures/story_7_9_views/controller_request_spec_support/errors_demo/new.html.erb +0 -3
  50. data/spec/fixtures/story_7_9_views/controller_request_spec_support/exploding_layout_demo/show.html.erb +0 -3
  51. data/spec/fixtures/story_7_9_views/controller_request_spec_support/ghost_layout_demo/show.html.erb +0 -3
  52. data/spec/fixtures/story_7_9_views/controller_request_spec_support/layout_demo/show.html.erb +0 -3
  53. data/spec/fixtures/story_7_9_views/controller_request_spec_support/rootless_layout_demo/show.html.erb +0 -3
  54. data/spec/fixtures/story_7_9_views/controller_request_spec_support/unwired_layout_demo/show.html.erb +0 -3
  55. data/spec/fixtures/story_7_9_views/layouts/bare_host.html.erb +0 -16
  56. data/spec/fixtures/story_7_9_views/layouts/exploding_host.html.erb +0 -24
  57. data/spec/fixtures/story_7_9_views/layouts/rootless_host.html.erb +0 -15
  58. data/spec/fixtures/story_7_9_views/layouts/ruact_host.html.erb +0 -17
  59. data/spec/readme_demo_message_spec.rb +0 -67
  60. data/spec/readme_spec.rb +0 -282
  61. data/spec/ruact/client_manifest_spec.rb +0 -270
  62. data/spec/ruact/component_contract_spec.rb +0 -119
  63. data/spec/ruact/configuration_spec.rb +0 -518
  64. data/spec/ruact/controller_request_spec.rb +0 -671
  65. data/spec/ruact/controller_spec.rb +0 -343
  66. data/spec/ruact/doctor_spec.rb +0 -769
  67. data/spec/ruact/erb_preprocessor_hook_spec.rb +0 -55
  68. data/spec/ruact/erb_preprocessor_spec.rb +0 -361
  69. data/spec/ruact/errors_spec.rb +0 -93
  70. data/spec/ruact/flight/renderer_spec.rb +0 -133
  71. data/spec/ruact/flight/serializer_spec.rb +0 -494
  72. data/spec/ruact/html_converter_spec.rb +0 -375
  73. data/spec/ruact/install_generator_spec.rb +0 -1549
  74. data/spec/ruact/layout_source_spec.rb +0 -108
  75. data/spec/ruact/manifest_resolver_spec.rb +0 -174
  76. data/spec/ruact/query_request_spec.rb +0 -706
  77. data/spec/ruact/query_spec.rb +0 -105
  78. data/spec/ruact/railtie_spec.rb +0 -155
  79. data/spec/ruact/render_context_spec.rb +0 -58
  80. data/spec/ruact/render_pipeline_concurrency_spec.rb +0 -78
  81. data/spec/ruact/render_pipeline_spec.rb +0 -928
  82. data/spec/ruact/scaffold_generator_spec.rb +0 -1849
  83. data/spec/ruact/serializable_spec.rb +0 -179
  84. data/spec/ruact/server_bucket_request_spec.rb +0 -785
  85. data/spec/ruact/server_function_name_spec.rb +0 -53
  86. data/spec/ruact/server_functions/backtrace_cleaner_spec.rb +0 -63
  87. data/spec/ruact/server_functions/bucket_two_payload_spec.rb +0 -200
  88. data/spec/ruact/server_functions/codegen_spec.rb +0 -397
  89. data/spec/ruact/server_functions/error_payload_spec.rb +0 -222
  90. data/spec/ruact/server_functions/error_suggestion_spec.rb +0 -79
  91. data/spec/ruact/server_functions/introspection_spec.rb +0 -135
  92. data/spec/ruact/server_functions/name_bridge_spec.rb +0 -212
  93. data/spec/ruact/server_functions/query_context_spec.rb +0 -72
  94. data/spec/ruact/server_functions/query_source_spec.rb +0 -193
  95. data/spec/ruact/server_functions/railtie_integration_spec.rb +0 -215
  96. data/spec/ruact/server_functions/rake_spec.rb +0 -86
  97. data/spec/ruact/server_functions/route_source_spec.rb +0 -202
  98. data/spec/ruact/server_functions/snapshot_spec.rb +0 -96
  99. data/spec/ruact/server_functions/snapshot_writer_spec.rb +0 -71
  100. data/spec/ruact/server_rescue_request_spec.rb +0 -416
  101. data/spec/ruact/server_spec.rb +0 -179
  102. data/spec/ruact/server_upload_request_spec.rb +0 -311
  103. data/spec/ruact/signed_references_spec.rb +0 -164
  104. data/spec/ruact/string_distance_spec.rb +0 -38
  105. data/spec/ruact/tasks_json_introspection_spec.rb +0 -141
  106. data/spec/ruact/testing/have_ruact_component_spec.rb +0 -170
  107. data/spec/ruact/testing/no_production_load_spec.rb +0 -41
  108. data/spec/ruact/validation_errors_spec.rb +0 -116
  109. data/spec/ruact/view_helper_spec.rb +0 -131
  110. data/spec/spec_helper.rb +0 -77
  111. data/spec/support/fixtures/pixel.png +0 -0
  112. data/spec/support/flight_wire_parser.rb +0 -21
  113. data/spec/support/flight_wire_parser_spec.rb +0 -93
  114. data/spec/support/matchers/flight_fixture_matcher.rb +0 -130
  115. data/spec/support/matchers/flight_fixture_matcher_spec.rb +0 -250
  116. data/spec/support/rails_stub.rb +0 -115
@@ -1,108 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "spec_helper"
4
- require "ruact"
5
-
6
- # The single definition of "is this layout migrated?", shared by the runtime and
7
- # by `ruact:install`. Every example here is a shape that fooled an earlier,
8
- # looser check — a NAME where only a CALL counts, or a substring where only an
9
- # attribute counts.
10
- RSpec.describe Ruact::LayoutSource do
11
- describe ".wired?" do
12
- it "accepts a plain output tag" do
13
- expect(described_class.wired?("<%= ruact_js_assets %>")).to be true
14
- end
15
-
16
- it "accepts the raw-output form and tight whitespace" do
17
- expect(described_class.wired?("<%==ruact_js_assets%>")).to be true
18
- end
19
-
20
- it "accepts a call with an argument" do
21
- expect(described_class.wired?("<%= ruact_js_assets(payload) %>")).to be true
22
- end
23
-
24
- # Round-1 finding: a mention in prose read as wired.
25
- it "rejects a mention inside an ERB comment" do
26
- expect(described_class.wired?("<%# remember to add ruact_js_assets here %>")).to be false
27
- end
28
-
29
- # Round-2 finding, and the sharper version of the same mistake: a genuinely
30
- # COMMENTED-OUT call. ERB comments do not nest, so this emits nothing — but
31
- # a regex looking only for `<%=` sees a call and reports the layout ready,
32
- # which sends the runtime off to render an unmigrated layout.
33
- it "rejects a commented-out call" do
34
- expect(described_class.wired?("<%# <%= ruact_js_assets %> %>")).to be false
35
- end
36
-
37
- it "rejects a multi-line comment that wraps a call" do
38
- source = <<~ERB
39
- <%#
40
- disabled for now:
41
- <%= ruact_js_assets %>
42
- %>
43
- ERB
44
- expect(described_class.wired?(source)).to be false
45
- end
46
-
47
- it "still sees a real call that follows a comment mentioning it" do
48
- expect(described_class.wired?("<%# ruact_js_assets %>\n<%= ruact_js_assets %>")).to be true
49
- end
50
-
51
- # Round-3 finding: ERB's trim-mode comment (`<%-# ... -%>`) is a comment in
52
- # Erubi too — verified by compiling it — but the stripper only knew `<%#`,
53
- # so a call disabled this way read as wired.
54
- it "rejects a call disabled with a trim-mode comment" do
55
- expect(described_class.wired?("<%-# <%= ruact_js_assets %> -%>")).to be false
56
- end
57
-
58
- # Round-3 finding, the other direction: forbidding `%` before the name to
59
- # avoid crossing tag boundaries also rejected legitimate calls. A false
60
- # "unwired" is safer than a false "wired", but it is still a wrong answer —
61
- # the app would silently keep ruact's CSS-less shell.
62
- it "accepts a call whose expression contains a percent sign" do
63
- expect(described_class.wired?(%(<%= raw("100%") + ruact_js_assets %>))).to be true
64
- expect(described_class.wired?("<%= ruact_js_assets if 50 % 2 == 0 %>")).to be true
65
- end
66
-
67
- it "still refuses to match across a tag boundary" do
68
- expect(described_class.wired?("<%= something %> then a bare ruact_js_assets mention")).to be false
69
- end
70
-
71
- it "rejects a layout that never names it" do
72
- expect(described_class.wired?("<html><body><%= yield %></body></html>")).to be false
73
- end
74
- end
75
-
76
- describe ".root?" do
77
- it "accepts the emitted form" do
78
- expect(described_class.root?(%(<div id="root"></div>))).to be true
79
- end
80
-
81
- it "accepts single quotes, extra attributes and unquoted values" do
82
- expect(described_class.root?(%(<div id='root'></div>))).to be true
83
- expect(described_class.root?(%(<div class="a" id="root" data-x="1"></div>))).to be true
84
- expect(described_class.root?(%(<div id=root></div>))).to be true
85
- end
86
-
87
- # Round-2 finding: `data-id="root"` is not a mount point, and a document
88
- # carrying one but no real root gives React nothing to mount into.
89
- it "rejects a look-alike attribute" do
90
- expect(described_class.root?(%(<body data-id="root"></body>))).to be false
91
- end
92
-
93
- it "rejects a different id that merely starts with root" do
94
- expect(described_class.root?(%(<div id="rootish"></div>))).to be false
95
- end
96
- end
97
-
98
- # The anchor `ruact:install` injects after.
99
- describe "::ROOT_ELEMENT" do
100
- it "anchors on a whole empty root div" do
101
- expect(described_class::ROOT_ELEMENT).to match(%(<div id="root"></div>))
102
- end
103
-
104
- it "does not anchor on a look-alike attribute" do
105
- expect(described_class::ROOT_ELEMENT).not_to match(%(<div data-id="root"></div>))
106
- end
107
- end
108
- end
@@ -1,174 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "spec_helper"
4
- require "net/http"
5
- require "tmpdir"
6
-
7
- module Ruact
8
- RSpec.describe ManifestResolver do
9
- # spec_helper pins resolve/resolve_soft to the boot manifest suite-wide for
10
- # hermeticity; this spec is the one place that exercises the REAL paths.
11
- before do
12
- allow(described_class).to receive(:resolve).and_call_original
13
- allow(described_class).to receive(:resolve_soft).and_call_original
14
- end
15
-
16
- let(:manifest_hash) do
17
- {
18
- "LikeButton" => {
19
- "id" => "/LikeButton.jsx",
20
- "name" => "LikeButton",
21
- "chunks" => ["/LikeButton.jsx"]
22
- }
23
- }
24
- end
25
- let(:manifest_json) { JSON.generate(manifest_hash) }
26
-
27
- # A real Net::HTTPSuccess (HTTPOK < HTTPSuccess) whose body we override, so
28
- # the resolver's `response.is_a?(Net::HTTPSuccess)` branch is exercised for
29
- # real without touching the network.
30
- def http_ok(body)
31
- response = Net::HTTPOK.new("1.1", "200", "OK")
32
- response.define_singleton_method(:body) { body }
33
- response
34
- end
35
-
36
- def in_dev
37
- allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new("development"))
38
- end
39
-
40
- describe ".resolve in development (the boot-race)" do
41
- before do
42
- in_dev
43
- # Simulate the race: boot read the missing file, so the cached manifest is nil.
44
- allow(Ruact).to receive(:manifest).and_return(nil)
45
- end
46
-
47
- it "resolves from the Vite dev server over HTTP (no 500, ref resolvable)" do
48
- allow(Net::HTTP).to receive(:start).and_return(http_ok(manifest_json))
49
-
50
- manifest = described_class.resolve
51
-
52
- expect(manifest).to be_a(ClientManifest)
53
- ref = manifest.reference_for("LikeButton")
54
- expect(ref.module_id).to eq("/LikeButton.jsx")
55
- end
56
-
57
- it "fetches the manifest from the configured vite_dev_server URL" do
58
- allow(Net::HTTP).to receive(:start).and_return(http_ok(manifest_json))
59
-
60
- described_class.resolve
61
-
62
- expect(Net::HTTP).to have_received(:start).with(
63
- "localhost", 5173, hash_including(:open_timeout, :read_timeout)
64
- )
65
- end
66
-
67
- it "does not double-slash the path when vite_dev_server has a trailing slash" do
68
- allow(described_class).to receive(:base_url).and_return("http://localhost:5173/")
69
- http = instance_double(Net::HTTP)
70
- allow(http).to receive(:get).and_return(http_ok(manifest_json))
71
- allow(Net::HTTP).to receive(:start).and_yield(http).and_return(http_ok(manifest_json))
72
-
73
- described_class.resolve
74
-
75
- expect(http).to have_received(:get).with("/__ruact/manifest")
76
- end
77
-
78
- it "speaks TLS when vite_dev_server is https" do
79
- allow(described_class).to receive(:base_url).and_return("https://localhost:5173")
80
- allow(Net::HTTP).to receive(:start).and_return(http_ok(manifest_json))
81
-
82
- described_class.resolve
83
-
84
- expect(Net::HTTP).to have_received(:start).with(
85
- "localhost", 5173, hash_including(use_ssl: true)
86
- )
87
- end
88
-
89
- it "falls back to the on-disk file when the dev server is unreachable" do
90
- allow(Net::HTTP).to receive(:start).and_raise(Errno::ECONNREFUSED)
91
-
92
- Dir.mktmpdir do |dir|
93
- path = File.join(dir, "react-client-manifest.json")
94
- File.write(path, manifest_json)
95
- allow(described_class).to receive(:file_path).and_return(path)
96
-
97
- manifest = described_class.resolve
98
- expect(manifest).to be_a(ClientManifest)
99
- expect(manifest.reference_for("LikeButton").module_id).to eq("/LikeButton.jsx")
100
- end
101
- end
102
-
103
- it "raises a clear, actionable error (not NoMethodError) when neither HTTP nor file is available" do
104
- allow(Net::HTTP).to receive(:start).and_raise(Errno::ECONNREFUSED)
105
- allow(described_class).to receive(:file_path).and_return("/no/such/manifest.json")
106
-
107
- expect { described_class.resolve }.to raise_error(ManifestError, %r{Vite dev server.*bin/dev}m)
108
- end
109
-
110
- # Story 15.0 (F8) — an error message is a prompt: agents (and humans)
111
- # regex-match error text, so the ENGLISH wording and its stable tokens
112
- # (`Vite dev server`, `react-client-manifest.json`, `bin/dev`) are pinned.
113
- it "raises the ENGLISH message naming the dev-server URL, the manifest path and the bin/dev fix " \
114
- "(Story 15.0)", :story_15_0 do
115
- allow(Net::HTTP).to receive(:start).and_raise(Errno::ECONNREFUSED)
116
- allow(described_class).to receive(:file_path).and_return("/no/such/manifest.json")
117
-
118
- expect { described_class.resolve }.to raise_error(ManifestError) do |error|
119
- expect(error.message).to include("[ruact] Vite dev server unreachable at http://localhost:5173")
120
- expect(error.message).to include("no react-client-manifest.json found at /no/such/manifest.json")
121
- expect(error.message).to include("run `bin/dev`")
122
- end
123
- end
124
-
125
- it "ignores a non-2xx dev-server response and falls back" do
126
- allow(Net::HTTP).to receive(:start).and_return(Net::HTTPNotFound.new("1.1", "404", "Not Found"))
127
- allow(described_class).to receive(:file_path).and_return("/no/such/manifest.json")
128
-
129
- expect { described_class.resolve }.to raise_error(ManifestError)
130
- end
131
- end
132
-
133
- describe ".resolve_soft in development (fail-open for FR100)" do
134
- before do
135
- in_dev
136
- allow(Ruact).to receive(:manifest).and_return(nil)
137
- end
138
-
139
- it "returns nil (no raise) when nothing is resolvable" do
140
- allow(Net::HTTP).to receive(:start).and_raise(Errno::ECONNREFUSED)
141
- allow(described_class).to receive(:file_path).and_return("/no/such/manifest.json")
142
-
143
- expect(described_class.resolve_soft).to be_nil
144
- end
145
-
146
- it "returns the dev manifest when the server is up" do
147
- allow(Net::HTTP).to receive(:start).and_return(http_ok(manifest_json))
148
- expect(described_class.resolve_soft).to be_a(ClientManifest)
149
- end
150
- end
151
-
152
- describe "production / non-development (untouched)" do
153
- it "returns Ruact.manifest verbatim without any HTTP call" do
154
- allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new("production"))
155
- allow(Net::HTTP).to receive(:start)
156
- boot_manifest = ClientManifest.from_hash(manifest_hash)
157
- allow(Ruact).to receive(:manifest).and_return(boot_manifest)
158
-
159
- expect(described_class.resolve).to be(boot_manifest)
160
- expect(Net::HTTP).not_to have_received(:start)
161
- end
162
-
163
- it "returns Ruact.manifest in the test environment too (no dev fetch)" do
164
- allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new("test"))
165
- allow(Net::HTTP).to receive(:start)
166
- boot_manifest = ClientManifest.from_hash(manifest_hash)
167
- allow(Ruact).to receive(:manifest).and_return(boot_manifest)
168
-
169
- expect(described_class.resolve).to be(boot_manifest)
170
- expect(Net::HTTP).not_to have_received(:start)
171
- end
172
- end
173
- end
174
- end