Skip to content

Commit 204561c

Browse files
Update helper spec tests to use async primitives instead of fibers
Replace @rorp_rendering_fibers with @async_barrier and @main_output_queue in test setup. Update all tests to: - Wrap in Sync blocks to provide async context - Use @async_barrier.wait and @main_output_queue.close for synchronization - Collect chunks from queue instead of calling fiber.resume - Remove timing-dependent assertions on chunks_read.count This completes the migration from manual Fiber management to async gem primitives for component streaming tests. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 6c48628 commit 204561c

File tree

1 file changed

+80
-40
lines changed

1 file changed

+80
-40
lines changed

react_on_rails_pro/spec/dummy/spec/helpers/react_on_rails_pro_helper_spec.rb

Lines changed: 80 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
require "async"
44
require "async/queue"
5+
require "async/barrier"
56
require "rails_helper"
67
require "support/script_tag_utils"
78

@@ -360,13 +361,19 @@ def mock_request_and_response(mock_chunks = chunks, count: 1)
360361
end
361362

362363
describe "#stream_react_component" do
363-
before do
364-
# Initialize @rorp_rendering_fibers to mock the behavior of stream_view_containing_react_components.
365-
# This instance variable is normally set by stream_view_containing_react_components method.
366-
# By setting it here, we simulate that the view is being rendered using that method.
367-
# This setup is necessary because stream_react_component relies on @rorp_rendering_fibers
368-
# to function correctly within the streaming context.
369-
@rorp_rendering_fibers = []
364+
around do |example|
365+
# Wrap each test in Sync block to provide async context
366+
Sync do
367+
# Initialize async primitives to mock the behavior of stream_view_containing_react_components.
368+
# These instance variables are normally set by stream_view_containing_react_components method.
369+
# By setting them here, we simulate that the view is being rendered using that method.
370+
# This setup is necessary because stream_react_component relies on @async_barrier and @main_output_queue
371+
# to function correctly within the streaming context.
372+
@async_barrier = Async::Barrier.new
373+
@main_output_queue = Async::Queue.new
374+
375+
example.run
376+
end
370377
end
371378

372379
it "returns the component shell that exist in the initial chunk with the consoleReplayScript" do
@@ -380,35 +387,39 @@ def mock_request_and_response(mock_chunks = chunks, count: 1)
380387
expect(initial_result).to include(wrapped)
381388
end
382389
expect(initial_result).not_to include("More content", "Final content")
383-
expect(chunks_read.count).to eq(1)
390+
# Note: With async architecture, chunks are consumed in background immediately,
391+
expect(chunks_read.count).to eq(3)
384392
end
385393

386-
it "creates a fiber to read subsequent chunks" do
394+
it "streams subsequent chunks to the output queue" do
387395
mock_request_and_response
388-
stream_react_component(component_name, props: props, **component_options)
389-
expect(@rorp_rendering_fibers.count).to eq(1) # rubocop:disable RSpec/InstanceVariable
390-
fiber = @rorp_rendering_fibers.first # rubocop:disable RSpec/InstanceVariable
391-
expect(fiber).to be_alive
392-
393-
second_result = fiber.resume
394-
# regex that matches the html and wrapped consoleReplayScript
395-
# Note: consoleReplayScript is now wrapped in a script tag with id="consoleReplayLog"
396+
initial_result = stream_react_component(component_name, props: props, **component_options)
397+
398+
# First chunk is returned synchronously
399+
expect(initial_result).to include(react_component_div_with_initial_chunk)
400+
401+
# Wait for async task to complete
402+
@async_barrier.wait
403+
@main_output_queue.close
404+
405+
# Subsequent chunks should be in the output queue
406+
collected_chunks = []
407+
while (chunk = @main_output_queue.dequeue)
408+
collected_chunks << chunk
409+
end
410+
411+
# Should have received the remaining chunks (chunks 2 and 3)
412+
expect(collected_chunks.length).to eq(2)
413+
414+
# Verify second chunk content
396415
script = chunks[1][:consoleReplayScript]
397416
wrapped = script.present? ? "<script id=\"consoleReplayLog\">#{script}</script>" : ""
398-
expect(second_result).to match(
417+
expect(collected_chunks[0]).to match(
399418
/#{Regexp.escape(chunks[1][:html])}\s+#{Regexp.escape(wrapped)}/
400419
)
401-
expect(second_result).not_to include("Stream React Server Components", "Final content")
402-
expect(chunks_read.count).to eq(2)
403-
404-
third_result = fiber.resume
405-
expect(third_result).to eq(chunks[2][:html].to_s)
406-
expect(third_result).not_to include("Stream React Server Components", "More content")
407-
expect(chunks_read.count).to eq(3)
408420

409-
expect(fiber.resume).to be_nil
410-
expect(fiber).not_to be_alive
411-
expect(chunks_read.count).to eq(chunks.count)
421+
# Verify third chunk content
422+
expect(collected_chunks[1]).to eq(chunks[2][:html].to_s)
412423
end
413424

414425
it "does not trim whitespaces from html" do
@@ -420,12 +431,24 @@ def mock_request_and_response(mock_chunks = chunks, count: 1)
420431
{ html: "\t\t\t<div>Chunk 4: with mixed whitespaces</div> \n\n\n" }
421432
].map { |chunk| chunk.merge(consoleReplayScript: "") }
422433
mock_request_and_response(chunks_with_whitespaces)
434+
423435
initial_result = stream_react_component(component_name, props: props, **component_options)
424436
expect(initial_result).to include(chunks_with_whitespaces.first[:html])
425-
fiber = @rorp_rendering_fibers.first # rubocop:disable RSpec/InstanceVariable
426-
expect(fiber.resume).to include(chunks_with_whitespaces[1][:html])
427-
expect(fiber.resume).to include(chunks_with_whitespaces[2][:html])
428-
expect(fiber.resume).to include(chunks_with_whitespaces[3][:html])
437+
438+
# Wait for async task to complete
439+
@async_barrier.wait
440+
@main_output_queue.close
441+
442+
# Collect remaining chunks from queue
443+
collected_chunks = []
444+
while (chunk = @main_output_queue.dequeue)
445+
collected_chunks << chunk
446+
end
447+
448+
# Verify whitespaces are preserved in all chunks
449+
expect(collected_chunks[0]).to include(chunks_with_whitespaces[1][:html])
450+
expect(collected_chunks[1]).to include(chunks_with_whitespaces[2][:html])
451+
expect(collected_chunks[2]).to include(chunks_with_whitespaces[3][:html])
429452
end
430453
end
431454

@@ -698,15 +721,25 @@ def run_stream
698721
# we need this setup because we can't use the helper outside of stream_view_containing_react_components
699722
def render_cached_random_value(cache_key)
700723
# Streaming helpers require this context normally provided by stream_view_containing_react_components
701-
@rorp_rendering_fibers = []
724+
result = nil
725+
Sync do
726+
@async_barrier = Async::Barrier.new
727+
@main_output_queue = Async::Queue.new
702728

703-
result = cached_stream_react_component("RandomValue", cache_key: cache_key,
704-
id: "RandomValue-react-component-0") do
705-
{ a: 1, b: 2 }
706-
end
729+
result = cached_stream_react_component("RandomValue", cache_key: cache_key,
730+
id: "RandomValue-react-component-0") do
731+
{ a: 1, b: 2 }
732+
end
733+
734+
# Complete the streaming lifecycle to trigger cache writes
735+
@async_barrier.wait
736+
@main_output_queue.close
707737

708-
# Complete the streaming lifecycle to trigger cache writes
709-
@rorp_rendering_fibers.each { |fiber| fiber.resume while fiber.alive? } # rubocop:disable RSpec/InstanceVariable
738+
# Drain the queue
739+
while (chunk = @main_output_queue.dequeue)
740+
# Just consume all remaining chunks
741+
end
742+
end
710743

711744
result
712745
end
@@ -746,8 +779,15 @@ def render_cached_random_value(cache_key)
746779
]
747780
end
748781

782+
around do |example|
783+
Sync do
784+
@async_barrier = Async::Barrier.new
785+
@main_output_queue = Async::Queue.new
786+
example.run
787+
end
788+
end
789+
749790
before do
750-
@rorp_rendering_fibers = []
751791
ReactOnRailsPro::Request.instance_variable_set(:@connection, nil)
752792
original_httpx_plugin = HTTPX.method(:plugin)
753793
allow(HTTPX).to receive(:plugin) do |*args|

0 commit comments

Comments
 (0)