This repository is a self-contained repro for a test-ordering bug in
ViewComponent 4.x when Slim
templates are rendered via a Temple::Generators::RailsOutputBuffer handler
with streaming: true.
When a spec/components/ example that calls render_inline runs before a
spec/controllers/ example that uses render_views, the controller spec
produces response.body == "" instead of the expected HTML.
The bug does not appear when the controller spec runs in isolation.
bundle install
bundle exec rspec spec/components/null_component_spec.rb \
spec/controllers/search_controller_spec.rb2 examples, 0 failures
Failures:
1) SearchController GET #index responds with rendered HTML
Failure/Error: expect(response.body).to include('search results')
expected "" to include "search results"
The second spec passes when run alone:
bundle exec rspec spec/controllers/search_controller_spec.rb # passes| Gem | Version |
|---|---|
rails |
~> 8.1 |
view_component |
~> 4.2 (confirmed 4.12.0) |
slim |
5.2.1 |
slim-rails |
~> 4.0 |
temple |
~> 0.10 |
ViewComponent::Base#render_in calls output_buffer.with_buffer { render_template }.
with_buffer temporarily swaps @raw_buffer on the current ActionView::OutputBuffer
for a new empty buffer, yields, then restores the original.
The Slim template handler configured with streaming: true (see
config/initializers/view_component_slim.rb) generates the following at the top
of every .html.slim template:
@output_buffer = output_buffer || ActionView::OutputBuffer.newWhen this line executes inside with_buffer, @output_buffer is reassigned to
the captured reference (the pre-swap buffer). The interaction between this
reassignment and ViewComponent's buffer-swap mechanism leaves @raw_buffer
pointing at the temporary buffer after the component spec teardown. Subsequent
controller specs then write rendered HTML into the now-orphaned temporary buffer
rather than the response buffer, producing an empty response.body.
Save and restore @raw_buffer explicitly around every render_in call:
# spec/support/view_component_output_buffer.rb
module ViewComponent
module OutputBufferFix
def render_in(view_context, &block)
saved = view_context.output_buffer.instance_variable_get(:@raw_buffer)
result = super
view_context.output_buffer.instance_variable_set(:@raw_buffer, saved)
result
end
end
end
ViewComponent::Base.prepend(ViewComponent::OutputBufferFix)Add require 'support/view_component_output_buffer' to spec/rails_helper.rb
and the ordering dependency disappears.
config/initializers/view_component_slim.rb mirrors the
view_component_slim_compatibility internal gem used in the G2 application:
ActiveSupport.on_load(:view_component) do
slim_handler_factory = Temple::Templates::Rails(
Slim::Engine,
register_as: :slim,
generator: Temple::Generators::RailsOutputBuffer,
disable_capture: true,
streaming: true
)
ActionView::Template.register_template_handler(:slim, slim_handler_factory.new)
endThe bug disappears when streaming: false is passed (or when the Slim template
handler is not re-registered and the default slim-rails handler is used
instead), because the generated template code then creates a fresh
ActionView::OutputBuffer.new rather than capturing the ambient one.