react_on_rails 16.2.0.beta.4 → 16.2.0.beta.8
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 +27 -8
- data/CONTRIBUTING.md +1 -1
- data/Gemfile.development_dependencies +0 -1
- data/Gemfile.lock +1 -9
- data/bin/ci-rerun-failures +39 -16
- data/bin/ci-run-failed-specs +1 -1
- data/bin/ci-switch-config +8 -2
- data/bin/lefthook/ruby-autofix +2 -1
- data/knip.ts +35 -9
- data/lib/generators/react_on_rails/templates/base/base/config/initializers/react_on_rails.rb.tt +32 -52
- data/lib/generators/react_on_rails/templates/base/base/config/shakapacker.yml +5 -1
- data/lib/react_on_rails/dev/server_manager.rb +11 -4
- data/lib/react_on_rails/doctor.rb +245 -0
- data/lib/react_on_rails/helper.rb +9 -0
- data/lib/react_on_rails/version.rb +1 -1
- data/react_on_rails_pro/CHANGELOG.md +7 -0
- data/react_on_rails_pro/CONTRIBUTING.md +2 -13
- data/react_on_rails_pro/Gemfile.lock +21 -3
- data/react_on_rails_pro/docs/code-splitting-loadable-components.md +1 -1
- data/react_on_rails_pro/docs/contributors-info/releasing.md +2 -2
- data/react_on_rails_pro/docs/installation.md +106 -104
- data/react_on_rails_pro/docs/node-renderer/basics.md +3 -3
- data/react_on_rails_pro/docs/node-renderer/error-reporting-and-tracing.md +8 -8
- data/react_on_rails_pro/docs/node-renderer/js-configuration.md +1 -1
- data/react_on_rails_pro/docs/updating.md +209 -15
- data/react_on_rails_pro/lib/react_on_rails_pro/concerns/stream.rb +58 -4
- data/react_on_rails_pro/lib/react_on_rails_pro/configuration.rb +17 -3
- data/react_on_rails_pro/lib/react_on_rails_pro/license_public_key.rb +9 -9
- data/react_on_rails_pro/lib/react_on_rails_pro/request.rb +41 -25
- data/react_on_rails_pro/lib/react_on_rails_pro/stream_request.rb +27 -7
- data/react_on_rails_pro/lib/react_on_rails_pro/utils.rb +3 -3
- data/react_on_rails_pro/lib/react_on_rails_pro/version.rb +1 -1
- data/react_on_rails_pro/package-scripts.yml +1 -1
- data/react_on_rails_pro/package.json +5 -8
- data/react_on_rails_pro/packages/node-renderer/src/integrations/api.ts +1 -1
- data/react_on_rails_pro/rakelib/public_key_management.rake +6 -5
- data/react_on_rails_pro/react_on_rails_pro.gemspec +1 -0
- data/react_on_rails_pro/spec/dummy/Gemfile.lock +20 -3
- data/react_on_rails_pro/spec/dummy/app/controllers/pages_controller.rb +3 -3
- data/react_on_rails_pro/spec/dummy/bin/dev +4 -8
- data/react_on_rails_pro/spec/dummy/client/node-renderer.js +3 -3
- data/react_on_rails_pro/spec/dummy/config/environments/production.rb +1 -1
- data/react_on_rails_pro/spec/dummy/config/initializers/react_on_rails.rb +28 -12
- data/react_on_rails_pro/spec/dummy/config.ru +1 -1
- data/react_on_rails_pro/spec/dummy/package.json +2 -2
- data/react_on_rails_pro/spec/dummy/spec/helpers/react_on_rails_pro_helper_spec.rb +40 -11
- data/react_on_rails_pro/spec/dummy/spec/rails_helper.rb +1 -1
- data/react_on_rails_pro/spec/dummy/spec/requests/renderer_console_logging_spec.rb +5 -5
- data/react_on_rails_pro/spec/dummy/spec/system/integration_spec.rb +20 -14
- data/react_on_rails_pro/spec/dummy/spec/system/renderer_integration_spec.rb +3 -3
- data/react_on_rails_pro/spec/dummy/yarn.lock +4 -4
- data/react_on_rails_pro/spec/execjs-compatible-dummy/config/environments/production.rb +1 -1
- data/react_on_rails_pro/spec/execjs-compatible-dummy/config/initializers/react_on_rails.rb +16 -43
- data/react_on_rails_pro/spec/react_on_rails_pro/assets_precompile_spec.rb +15 -18
- data/react_on_rails_pro/spec/react_on_rails_pro/cache_spec.rb +1 -1
- data/react_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rb +5 -3
- data/react_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rb +27 -12
- data/react_on_rails_pro/spec/react_on_rails_pro/request_spec.rb +0 -27
- data/react_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb +1 -1
- data/react_on_rails_pro/spec/react_on_rails_pro/stream_decorator_spec.rb +89 -0
- data/react_on_rails_pro/spec/react_on_rails_pro/stream_spec.rb +144 -0
- data/react_on_rails_pro/spec/react_on_rails_pro/support/caching.rb +1 -1
- data/react_on_rails_pro/spec/react_on_rails_pro/support/mock_block_helper.rb +4 -2
- metadata +2 -3
- data/react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/TestingStreamableComponent.jsx +0 -15
|
@@ -75,26 +75,30 @@ RSpec.describe ReactOnRailsPro::LicenseValidator do
|
|
|
75
75
|
ENV["REACT_ON_RAILS_PRO_LICENSE"] = expired_token
|
|
76
76
|
end
|
|
77
77
|
|
|
78
|
-
context "in development/test environment" do
|
|
78
|
+
context "when in development/test environment" do
|
|
79
79
|
before do
|
|
80
80
|
allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new("development"))
|
|
81
81
|
end
|
|
82
82
|
|
|
83
83
|
it "raises error immediately" do
|
|
84
|
-
expect
|
|
84
|
+
expect do
|
|
85
|
+
described_class.validated_license_data!
|
|
86
|
+
end.to raise_error(ReactOnRailsPro::Error, /License has expired/)
|
|
85
87
|
end
|
|
86
88
|
|
|
87
89
|
it "includes FREE license information in error message" do
|
|
88
|
-
expect
|
|
90
|
+
expect do
|
|
91
|
+
described_class.validated_license_data!
|
|
92
|
+
end.to raise_error(ReactOnRailsPro::Error, /FREE evaluation license/)
|
|
89
93
|
end
|
|
90
94
|
end
|
|
91
95
|
|
|
92
|
-
context "in production environment" do
|
|
96
|
+
context "when in production environment" do
|
|
93
97
|
before do
|
|
94
98
|
allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new("production"))
|
|
95
99
|
end
|
|
96
100
|
|
|
97
|
-
context "
|
|
101
|
+
context "with grace period (expired < 1 month ago)" do
|
|
98
102
|
let(:expired_within_grace) do
|
|
99
103
|
{
|
|
100
104
|
sub: "test@example.com",
|
|
@@ -113,7 +117,8 @@ RSpec.describe ReactOnRailsPro::LicenseValidator do
|
|
|
113
117
|
end
|
|
114
118
|
|
|
115
119
|
it "logs warning with grace period remaining" do
|
|
116
|
-
expect(mock_logger).to receive(:error)
|
|
120
|
+
expect(mock_logger).to receive(:error)
|
|
121
|
+
.with(/WARNING:.*License has expired.*Grace period:.*day\(s\) remaining/)
|
|
117
122
|
described_class.validated_license_data!
|
|
118
123
|
end
|
|
119
124
|
|
|
@@ -123,7 +128,7 @@ RSpec.describe ReactOnRailsPro::LicenseValidator do
|
|
|
123
128
|
end
|
|
124
129
|
end
|
|
125
130
|
|
|
126
|
-
context "outside grace period (expired > 1 month ago)" do
|
|
131
|
+
context "when outside grace period (expired > 1 month ago)" do
|
|
127
132
|
let(:expired_outside_grace) do
|
|
128
133
|
{
|
|
129
134
|
sub: "test@example.com",
|
|
@@ -138,11 +143,15 @@ RSpec.describe ReactOnRailsPro::LicenseValidator do
|
|
|
138
143
|
end
|
|
139
144
|
|
|
140
145
|
it "raises error" do
|
|
141
|
-
expect
|
|
146
|
+
expect do
|
|
147
|
+
described_class.validated_license_data!
|
|
148
|
+
end.to raise_error(ReactOnRailsPro::Error, /License has expired/)
|
|
142
149
|
end
|
|
143
150
|
|
|
144
151
|
it "includes FREE license information in error message" do
|
|
145
|
-
expect
|
|
152
|
+
expect do
|
|
153
|
+
described_class.validated_license_data!
|
|
154
|
+
end.to raise_error(ReactOnRailsPro::Error, /FREE evaluation license/)
|
|
146
155
|
end
|
|
147
156
|
end
|
|
148
157
|
end
|
|
@@ -168,7 +177,9 @@ RSpec.describe ReactOnRailsPro::LicenseValidator do
|
|
|
168
177
|
end
|
|
169
178
|
|
|
170
179
|
it "includes FREE license information in error message" do
|
|
171
|
-
expect
|
|
180
|
+
expect do
|
|
181
|
+
described_class.validated_license_data!
|
|
182
|
+
end.to raise_error(ReactOnRailsPro::Error, /FREE evaluation license/)
|
|
172
183
|
end
|
|
173
184
|
end
|
|
174
185
|
|
|
@@ -180,11 +191,15 @@ RSpec.describe ReactOnRailsPro::LicenseValidator do
|
|
|
180
191
|
end
|
|
181
192
|
|
|
182
193
|
it "raises error" do
|
|
183
|
-
expect
|
|
194
|
+
expect do
|
|
195
|
+
described_class.validated_license_data!
|
|
196
|
+
end.to raise_error(ReactOnRailsPro::Error, /Invalid license signature/)
|
|
184
197
|
end
|
|
185
198
|
|
|
186
199
|
it "includes FREE license information in error message" do
|
|
187
|
-
expect
|
|
200
|
+
expect do
|
|
201
|
+
described_class.validated_license_data!
|
|
202
|
+
end.to raise_error(ReactOnRailsPro::Error, /FREE evaluation license/)
|
|
188
203
|
end
|
|
189
204
|
end
|
|
190
205
|
|
|
@@ -194,32 +194,5 @@ describe ReactOnRailsPro::Request do
|
|
|
194
194
|
expect(mocked_block).not_to have_received(:call)
|
|
195
195
|
end
|
|
196
196
|
end
|
|
197
|
-
|
|
198
|
-
it "does not use HTTPx retries plugin for streaming requests to prevent body duplication" do
|
|
199
|
-
# This test verifies the fix for https://github.com/shakacode/react_on_rails/issues/1895
|
|
200
|
-
# When streaming requests encounter connection errors mid-transmission, HTTPx retries
|
|
201
|
-
# would cause body duplication because partial chunks are already sent to the client.
|
|
202
|
-
# The StreamRequest class handles retries properly by starting fresh requests.
|
|
203
|
-
|
|
204
|
-
# Reset connections to ensure we're using a fresh connection
|
|
205
|
-
described_class.reset_connection
|
|
206
|
-
|
|
207
|
-
# Trigger a streaming request
|
|
208
|
-
mock_streaming_response(render_full_url, 200) do |yielder|
|
|
209
|
-
yielder.call("Test chunk\n")
|
|
210
|
-
end
|
|
211
|
-
|
|
212
|
-
stream = described_class.render_code_as_stream("/render", "console.log('test');", is_rsc_payload: false)
|
|
213
|
-
chunks = []
|
|
214
|
-
stream.each_chunk { |chunk| chunks << chunk }
|
|
215
|
-
|
|
216
|
-
# Verify that the streaming request completed successfully
|
|
217
|
-
expect(chunks).to eq(["Test chunk"])
|
|
218
|
-
|
|
219
|
-
# Verify that the connection_without_retries was created
|
|
220
|
-
# by checking that a connection was created with retries disabled
|
|
221
|
-
connection_without_retries = described_class.send(:connection_without_retries)
|
|
222
|
-
expect(connection_without_retries).to be_a(HTTPX::Session)
|
|
223
|
-
end
|
|
224
197
|
end
|
|
225
198
|
end
|
|
@@ -15,7 +15,7 @@ require "rails"
|
|
|
15
15
|
require "rails/test_help"
|
|
16
16
|
Rails.backtrace_cleaner.remove_silencers!
|
|
17
17
|
|
|
18
|
-
require_relative "
|
|
18
|
+
require_relative "simplecov_helper"
|
|
19
19
|
# prevent Test::Unit's AutoRunner from executing during RSpec's rake task
|
|
20
20
|
Test::Unit.run = true if defined?(Test::Unit) && Test::Unit.respond_to?(:run=)
|
|
21
21
|
|
|
@@ -62,4 +62,93 @@ RSpec.describe ReactOnRailsPro::StreamDecorator do
|
|
|
62
62
|
expect(chunks.last).to end_with("-end")
|
|
63
63
|
end
|
|
64
64
|
end
|
|
65
|
+
|
|
66
|
+
describe "#rescue" do
|
|
67
|
+
it "catches the error happens inside the component" do
|
|
68
|
+
allow(mock_component).to receive(:each_chunk).and_raise(StandardError.new("Fake Error"))
|
|
69
|
+
mocked_block = mock_block
|
|
70
|
+
|
|
71
|
+
stream_decorator.rescue(&mocked_block.block)
|
|
72
|
+
chunks = []
|
|
73
|
+
expect { stream_decorator.each_chunk { |chunk| chunks << chunk } }.not_to raise_error
|
|
74
|
+
|
|
75
|
+
expect(mocked_block).to have_received(:call) do |error|
|
|
76
|
+
expect(error).to be_a(StandardError)
|
|
77
|
+
expect(error.message).to eq("Fake Error")
|
|
78
|
+
end
|
|
79
|
+
expect(chunks).to eq([])
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it "catches the error happens inside subsequent component calls" do
|
|
83
|
+
allow(mock_component).to receive(:each_chunk).and_yield("Chunk1").and_raise(ArgumentError.new("Fake Error"))
|
|
84
|
+
mocked_block = mock_block
|
|
85
|
+
|
|
86
|
+
stream_decorator.rescue(&mocked_block.block)
|
|
87
|
+
chunks = []
|
|
88
|
+
expect { stream_decorator.each_chunk { |chunk| chunks << chunk } }.not_to raise_error
|
|
89
|
+
|
|
90
|
+
expect(mocked_block).to have_received(:call) do |error|
|
|
91
|
+
expect(chunks).to eq(["Chunk1"])
|
|
92
|
+
expect(error).to be_a(ArgumentError)
|
|
93
|
+
expect(error.message).to eq("Fake Error")
|
|
94
|
+
end
|
|
95
|
+
expect(chunks).to eq(["Chunk1"])
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
it "can yield values to the stream" do
|
|
99
|
+
allow(mock_component).to receive(:each_chunk).and_yield("Chunk1").and_raise(ArgumentError.new("Fake Error"))
|
|
100
|
+
mocked_block = mock_block
|
|
101
|
+
|
|
102
|
+
stream_decorator.rescue(&mocked_block.block)
|
|
103
|
+
chunks = []
|
|
104
|
+
expect { stream_decorator.each_chunk { |chunk| chunks << chunk } }.not_to raise_error
|
|
105
|
+
|
|
106
|
+
expect(mocked_block).to have_received(:call) do |error, &inner_block|
|
|
107
|
+
expect(chunks).to eq(["Chunk1"])
|
|
108
|
+
expect(error).to be_a(ArgumentError)
|
|
109
|
+
expect(error.message).to eq("Fake Error")
|
|
110
|
+
|
|
111
|
+
inner_block.call "Chunk from rescue block"
|
|
112
|
+
inner_block.call "Chunk2 from rescue block"
|
|
113
|
+
end
|
|
114
|
+
expect(chunks).to eq(["Chunk1", "Chunk from rescue block", "Chunk2 from rescue block"])
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
it "can convert the error into another error" do
|
|
118
|
+
allow(mock_component).to receive(:each_chunk).and_raise(StandardError.new("Fake Error"))
|
|
119
|
+
mocked_block = mock_block do |error|
|
|
120
|
+
expect(error).to be_a(StandardError)
|
|
121
|
+
expect(error.message).to eq("Fake Error")
|
|
122
|
+
raise ArgumentError, "Another Error"
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
stream_decorator.rescue(&mocked_block.block)
|
|
126
|
+
chunks = []
|
|
127
|
+
expect { stream_decorator.each_chunk { |chunk| chunks << chunk } }.to raise_error(ArgumentError, "Another Error")
|
|
128
|
+
expect(chunks).to eq([])
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
it "chains multiple rescue blocks" do
|
|
132
|
+
allow(mock_component).to receive(:each_chunk).and_yield("Chunk1").and_raise(StandardError.new("Fake Error"))
|
|
133
|
+
fist_rescue_block = mock_block do |error, &block|
|
|
134
|
+
expect(error).to be_a(StandardError)
|
|
135
|
+
expect(error.message).to eq("Fake Error")
|
|
136
|
+
block.call "Chunk from first rescue block"
|
|
137
|
+
raise ArgumentError, "Another Error"
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
second_rescue_block = mock_block do |error, &block|
|
|
141
|
+
expect(error).to be_a(ArgumentError)
|
|
142
|
+
expect(error.message).to eq("Another Error")
|
|
143
|
+
block.call "Chunk from second rescue block"
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
stream_decorator.rescue(&fist_rescue_block.block)
|
|
147
|
+
stream_decorator.rescue(&second_rescue_block.block)
|
|
148
|
+
chunks = []
|
|
149
|
+
expect { stream_decorator.each_chunk { |chunk| chunks << chunk } }.not_to raise_error
|
|
150
|
+
|
|
151
|
+
expect(chunks).to eq(["Chunk1", "Chunk from first rescue block", "Chunk from second rescue block"])
|
|
152
|
+
end
|
|
153
|
+
end
|
|
65
154
|
end
|
|
@@ -1,7 +1,35 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "async"
|
|
4
|
+
require "async/queue"
|
|
3
5
|
require_relative "spec_helper"
|
|
4
6
|
|
|
7
|
+
class StreamController
|
|
8
|
+
include ReactOnRailsPro::Stream
|
|
9
|
+
|
|
10
|
+
attr_reader :response
|
|
11
|
+
|
|
12
|
+
def initialize(component_queues:, initial_response: "TEMPLATE")
|
|
13
|
+
@component_queues = component_queues
|
|
14
|
+
@initial_response = initial_response
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def render_to_string(**_opts)
|
|
18
|
+
@rorp_rendering_fibers = @component_queues.map do |queue|
|
|
19
|
+
Fiber.new do
|
|
20
|
+
loop do
|
|
21
|
+
chunk = queue.dequeue
|
|
22
|
+
break if chunk.nil?
|
|
23
|
+
|
|
24
|
+
Fiber.yield chunk
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
@initial_response
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
5
33
|
RSpec.describe "Streaming API" do
|
|
6
34
|
let(:origin) { "http://api.example.com" }
|
|
7
35
|
let(:path) { "/stream" }
|
|
@@ -342,4 +370,120 @@ RSpec.describe "Streaming API" do
|
|
|
342
370
|
expect(mocked_block).to have_received(:call).with("First chunk")
|
|
343
371
|
end
|
|
344
372
|
end
|
|
373
|
+
|
|
374
|
+
describe "Component streaming concurrency" do
|
|
375
|
+
def run_stream(controller, template: "ignored")
|
|
376
|
+
Sync do |parent|
|
|
377
|
+
parent.async { controller.stream_view_containing_react_components(template: template) }
|
|
378
|
+
yield(parent)
|
|
379
|
+
end
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
def setup_stream_test(component_count: 2)
|
|
383
|
+
component_queues = Array.new(component_count) { Async::Queue.new }
|
|
384
|
+
controller = StreamController.new(component_queues: component_queues)
|
|
385
|
+
|
|
386
|
+
mocked_response = instance_double(ActionController::Live::Response)
|
|
387
|
+
mocked_stream = instance_double(ActionController::Live::Buffer)
|
|
388
|
+
allow(mocked_response).to receive(:stream).and_return(mocked_stream)
|
|
389
|
+
allow(mocked_stream).to receive(:write)
|
|
390
|
+
allow(mocked_stream).to receive(:close)
|
|
391
|
+
allow(controller).to receive(:response).and_return(mocked_response)
|
|
392
|
+
|
|
393
|
+
[component_queues, controller, mocked_stream]
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
it "streams components concurrently" do
|
|
397
|
+
queues, controller, stream = setup_stream_test
|
|
398
|
+
|
|
399
|
+
run_stream(controller) do |_parent|
|
|
400
|
+
queues[1].enqueue("B1")
|
|
401
|
+
sleep 0.05
|
|
402
|
+
expect(stream).to have_received(:write).with("B1")
|
|
403
|
+
|
|
404
|
+
queues[0].enqueue("A1")
|
|
405
|
+
sleep 0.05
|
|
406
|
+
expect(stream).to have_received(:write).with("A1")
|
|
407
|
+
|
|
408
|
+
queues[1].enqueue("B2")
|
|
409
|
+
queues[1].close
|
|
410
|
+
sleep 0.05
|
|
411
|
+
|
|
412
|
+
queues[0].enqueue("A2")
|
|
413
|
+
queues[0].close
|
|
414
|
+
sleep 0.1
|
|
415
|
+
end
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
it "maintains per-component ordering" do
|
|
419
|
+
queues, controller, stream = setup_stream_test
|
|
420
|
+
|
|
421
|
+
run_stream(controller) do |_parent|
|
|
422
|
+
queues[0].enqueue("X1")
|
|
423
|
+
queues[0].enqueue("X2")
|
|
424
|
+
queues[0].enqueue("X3")
|
|
425
|
+
queues[0].close
|
|
426
|
+
|
|
427
|
+
queues[1].enqueue("Y1")
|
|
428
|
+
queues[1].enqueue("Y2")
|
|
429
|
+
queues[1].close
|
|
430
|
+
|
|
431
|
+
sleep 0.2
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
# Verify all chunks were written
|
|
435
|
+
expect(stream).to have_received(:write).with("X1")
|
|
436
|
+
expect(stream).to have_received(:write).with("X2")
|
|
437
|
+
expect(stream).to have_received(:write).with("X3")
|
|
438
|
+
expect(stream).to have_received(:write).with("Y1")
|
|
439
|
+
expect(stream).to have_received(:write).with("Y2")
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
it "handles empty component list" do
|
|
443
|
+
_queues, controller, stream = setup_stream_test(component_count: 0)
|
|
444
|
+
|
|
445
|
+
run_stream(controller) do |_parent|
|
|
446
|
+
sleep 0.1
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
expect(stream).to have_received(:write).with("TEMPLATE")
|
|
450
|
+
expect(stream).to have_received(:close)
|
|
451
|
+
end
|
|
452
|
+
|
|
453
|
+
it "handles single component" do
|
|
454
|
+
queues, controller, stream = setup_stream_test(component_count: 1)
|
|
455
|
+
|
|
456
|
+
run_stream(controller) do |_parent|
|
|
457
|
+
queues[0].enqueue("Single1")
|
|
458
|
+
queues[0].enqueue("Single2")
|
|
459
|
+
queues[0].close
|
|
460
|
+
|
|
461
|
+
sleep 0.1
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
expect(stream).to have_received(:write).with("Single1")
|
|
465
|
+
expect(stream).to have_received(:write).with("Single2")
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
it "applies backpressure with slow writer" do
|
|
469
|
+
queues, controller, stream = setup_stream_test(component_count: 1)
|
|
470
|
+
|
|
471
|
+
write_timestamps = []
|
|
472
|
+
allow(stream).to receive(:write) do |_data|
|
|
473
|
+
write_timestamps << Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
474
|
+
sleep 0.05
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
run_stream(controller) do |_parent|
|
|
478
|
+
5.times { |i| queues[0].enqueue("Chunk#{i}") }
|
|
479
|
+
queues[0].close
|
|
480
|
+
|
|
481
|
+
sleep 1
|
|
482
|
+
end
|
|
483
|
+
|
|
484
|
+
expect(write_timestamps.length).to be >= 2
|
|
485
|
+
gaps = write_timestamps.each_cons(2).map { |a, b| b - a }
|
|
486
|
+
expect(gaps.all? { |gap| gap >= 0.04 }).to be true
|
|
487
|
+
end
|
|
488
|
+
end
|
|
345
489
|
end
|
|
@@ -4,7 +4,7 @@ RSpec.configure do |config|
|
|
|
4
4
|
config.before(:each, :caching) do
|
|
5
5
|
cache_store = ActiveSupport::Cache::MemoryStore.new
|
|
6
6
|
allow(controller).to receive(:cache_store).and_return(cache_store) if defined?(controller) && controller
|
|
7
|
-
allow(
|
|
7
|
+
allow(Rails).to receive(:cache).and_return(cache_store)
|
|
8
8
|
ReactOnRailsPro::Cache.instance_variable_set(:@serializer_checksum, nil)
|
|
9
9
|
Rails.cache.clear
|
|
10
10
|
end
|
|
@@ -9,9 +9,11 @@ module MockBlockHelper
|
|
|
9
9
|
# mocked_block = mock_block
|
|
10
10
|
# testing_method_taking_block(&mocked_block.block)
|
|
11
11
|
# expect(mocked_block).to have_received(:call).with(1, 2, 3)
|
|
12
|
-
def mock_block(
|
|
12
|
+
def mock_block(&block)
|
|
13
13
|
double("BlockMock").tap do |mock| # rubocop:disable RSpec/VerifiedDoubles
|
|
14
|
-
allow(mock).to receive(:call)
|
|
14
|
+
allow(mock).to receive(:call) do |*args, &inner_block|
|
|
15
|
+
block&.call(*args, &inner_block)
|
|
16
|
+
end
|
|
15
17
|
def mock.block
|
|
16
18
|
method(:call).to_proc
|
|
17
19
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: react_on_rails
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 16.2.0.beta.
|
|
4
|
+
version: 16.2.0.beta.8
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Justin Gordon
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-11-
|
|
11
|
+
date: 2025-11-17 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: addressable
|
|
@@ -756,7 +756,6 @@ files:
|
|
|
756
756
|
- react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/SetTimeoutLoggingApp.server.jsx
|
|
757
757
|
- react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/SimpleComponent.jsx
|
|
758
758
|
- react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/StreamAsyncComponents.jsx
|
|
759
|
-
- react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/TestingStreamableComponent.jsx
|
|
760
759
|
- react_on_rails_pro/spec/dummy/client/app/routes/routes.jsx
|
|
761
760
|
- react_on_rails_pro/spec/dummy/client/app/ssr-computations/userQuery.ssr-computation.ts
|
|
762
761
|
- react_on_rails_pro/spec/dummy/client/app/store/composeInitialState.js
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import React from 'react';
|
|
4
|
-
|
|
5
|
-
// Simple test component for streaming SSR tests
|
|
6
|
-
function TestingStreamableComponent({ helloWorldData }) {
|
|
7
|
-
return (
|
|
8
|
-
<div>
|
|
9
|
-
<div>Chunk 1: Stream React Server Components</div>
|
|
10
|
-
<div>Hello, {helloWorldData.name}!</div>
|
|
11
|
-
</div>
|
|
12
|
-
);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export default TestingStreamableComponent;
|