diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d2183cdee..def8b94c7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,6 +29,7 @@ jobs: matrix: rails_version: ['5.2.6', '6.0.4.4', '6.1.4.4', '7.0.1', 'main'] ruby_version: ['2.5', '2.6', '2.7', '3.0', '3.1'] + output_buffer: ['global_buffer', 'local_buffer'] exclude: - rails_version: '5.2.6' ruby_version: '3.0' @@ -64,6 +65,7 @@ jobs: env: MEASURE_COVERAGE: true RAILS_VERSION: ${{ matrix.rails_version }} + VIEW_COMPONENT_USE_GLOBAL_OUTPUT_BUFFER: ${{ matrix.output_buffer == 'global_buffer' && 'true' || 'false' }} - name: Upload coverage results uses: actions/upload-artifact@master if: always() diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 44fa5b783..f2f168153 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -29,6 +29,10 @@ title: Changelog *Cameron Dutro* +* Add the option to use a "global" output buffer so `form_for` and friends can be used with view components. + + *Cameron Dutro*, *Blake Williams* + ## 2.50.0 * Add tests for `layout` usage when rendering via controller. diff --git a/docs/known_issues.md b/docs/known_issues.md index 5d9c29568..dacb0f186 100644 --- a/docs/known_issues.md +++ b/docs/known_issues.md @@ -7,7 +7,23 @@ title: Known issues ## form_for compatibility -ViewComponent [isn't currently compatible](https://github.com/github/view_component/issues/241) with `form_for` helpers. +ViewComponent [isn't compatible](https://github.com/github/view_component/issues/241) with `form_for` helpers by default. + +### Using a Global Output Buffer (Experimental) + +One possible solution to the form helpers problem is to use a single, global output buffer. For details, please refer to [this pull request](https://github.com/github/view_component/pull/1307). + +The global output buffer behavior is opt-in. Prepend the `ViewComponent::GlobalOutputBuffer` module into individual component classes to use it. + +For example: + +```ruby +class MyComponent < ViewComponent::Base + prepend ViewComponent::GlobalOutputBuffer +end +``` + +It is also possible to enable the global output buffer globally by setting the `config.view_component.use_global_output_buffer` setting to `true` in your Rails config. ## Inconsistent controller rendering behavior between Rails versions diff --git a/lib/view_component.rb b/lib/view_component.rb index 050b5507c..b291fdf93 100644 --- a/lib/view_component.rb +++ b/lib/view_component.rb @@ -11,7 +11,9 @@ module ViewComponent autoload :CompileCache autoload :ComponentError autoload :Deprecation + autoload :GlobalOutputBuffer autoload :Instrumentation + autoload :OutputBufferStack autoload :Preview autoload :PreviewTemplateError autoload :TestHelpers diff --git a/lib/view_component/base.rb b/lib/view_component/base.rb index 335660499..e16782a64 100644 --- a/lib/view_component/base.rb +++ b/lib/view_component/base.rb @@ -69,6 +69,8 @@ def render_in(view_context, &block) @view_context = view_context self.__vc_original_view_context ||= view_context + @output_buffer = ActionView::OutputBuffer.new unless @global_buffer_in_use + @lookup_context ||= view_context.lookup_context # required for path helpers in older Rails versions @@ -102,7 +104,7 @@ def render_in(view_context, &block) before_render if render? - render_template_for(@__vc_variant).to_s + _output_postamble + perform_render else "" end @@ -110,6 +112,10 @@ def render_in(view_context, &block) @current_template = old_current_template end + def perform_render + render_template_for(@__vc_variant).to_s + _output_postamble + end + # :nocov: def render_template_for(variant = nil) # Force compilation here so the compiler always redefines render_template_for. diff --git a/lib/view_component/compiler.rb b/lib/view_component/compiler.rb index bc08254b2..11d3f07e0 100644 --- a/lib/view_component/compiler.rb +++ b/lib/view_component/compiler.rb @@ -68,9 +68,8 @@ def compile(raise_errors: false, force: false) component_class.send(:remove_method, method_name.to_sym) end - component_class.class_eval <<-RUBY, template[:path], -1 + component_class.class_eval <<-RUBY, template[:path], 0 def #{method_name} - @output_buffer = ActionView::OutputBuffer.new #{compiled_template(template[:path])} end RUBY diff --git a/lib/view_component/engine.rb b/lib/view_component/engine.rb index 31e22a1f7..6aab1132f 100644 --- a/lib/view_component/engine.rb +++ b/lib/view_component/engine.rb @@ -20,6 +20,7 @@ class Engine < Rails::Engine # :nodoc: options.instrumentation_enabled = false if options.instrumentation_enabled.nil? options.preview_route ||= ViewComponent::Base.preview_route options.preview_controller ||= ViewComponent::Base.preview_controller + options.use_global_output_buffer = false if options.use_global_output_buffer.nil? if options.show_previews options.preview_paths << "#{Rails.root}/test/components/previews" if defined?(Rails.root) && Dir.exist?( @@ -57,6 +58,21 @@ class Engine < Rails::Engine # :nodoc: end end + initializer "view_component.enable_global_output_buffer" do |app| + ActiveSupport.on_load(:view_component) do + env_use_gob = ENV.fetch("VIEW_COMPONENT_USE_GLOBAL_OUTPUT_BUFFER", "false") == "true" + config_use_gob = app.config.view_component.use_global_output_buffer + + if config_use_gob || env_use_gob + # :nocov: + app.config.view_component.use_global_output_buffer = true + ViewComponent::Base.prepend(ViewComponent::GlobalOutputBuffer) + ActionView::Base.prepend(ViewComponent::GlobalOutputBuffer::ActionViewMods) + # :nocov: + end + end + end + initializer "view_component.set_autoload_paths" do |app| options = app.config.view_component diff --git a/lib/view_component/global_output_buffer.rb b/lib/view_component/global_output_buffer.rb new file mode 100644 index 000000000..8192bcfc7 --- /dev/null +++ b/lib/view_component/global_output_buffer.rb @@ -0,0 +1,89 @@ +# frozen_string_literal: true + +module ViewComponent + module GlobalOutputBuffer + def render_in(view_context, &block) + unless view_context.output_buffer.is_a?(OutputBufferStack) + # use instance_variable_set here to avoid triggering the code in the #output_buffer= method below + view_context.instance_variable_set(:@output_buffer, OutputBufferStack.new(view_context.output_buffer)) + end + + @output_buffer = view_context.output_buffer + @global_buffer_in_use = true + + super(view_context, &block) + end + + def perform_render + # HAML unhelpfully assigns to @output_buffer directly, so we hold onto a reference to + # it and restore @output_buffer when the HAML engine is finished. In non-HAML cases, + # @output_buffer and orig_buf will point to the same object, making the reassignment + # statements no-ops. + orig_buf = @output_buffer + @output_buffer.push + result = render_template_for(@__vc_variant).to_s + _output_postamble + @output_buffer = orig_buf + @output_buffer.pop + result + end + + def output_buffer=(other_buffer) + @output_buffer.replace(other_buffer) + end + + def with_output_buffer(buf = nil) + unless buf + buf = ActionView::OutputBuffer.new + if output_buffer && output_buffer.respond_to?(:encoding) + buf.force_encoding(output_buffer.encoding) + end + end + + output_buffer.push(buf) + result = nil + + begin + yield + ensure + # assign result here to avoid a return statement, which will + # immediately return to the caller and swallow any errors + result = output_buffer.pop + end + + result + end + + module ActionViewMods + def with_output_buffer(buf = nil) + # save a reference to the stack-based buffer + orig_buf = output_buffer + result = nil + + super do + if orig_buf.respond_to?(:push) + # super will have set self.output_buffer to the new buffer + new_buf = self.output_buffer + orig_buf.push(new_buf) + + # set output_buffer back to the original, stack-based buffer we + # saved a reference to earlier + self.output_buffer = orig_buf + + begin + yield + ensure + result = orig_buf.pop + end + else + # :nocov: + yield + result = output_buffer + # :nocov: + end + end + + result + end + end + end +end diff --git a/lib/view_component/output_buffer_stack.rb b/lib/view_component/output_buffer_stack.rb new file mode 100644 index 000000000..4fb4938e4 --- /dev/null +++ b/lib/view_component/output_buffer_stack.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +module ViewComponent + class OutputBufferStack + delegate_missing_to :@current_buffer + delegate :presence, :present?, :html_safe?, to: :@current_buffer + + attr_reader :buffer_stack + + def self.make_frame(*args) + ActionView::OutputBuffer.new(*args) + end + + def initialize(initial_buffer = nil) + if initial_buffer.is_a?(self.class) + @current_buffer = self.class.make_frame(initial_buffer.current) + @buffer_stack = [*initial_buffer.buffer_stack[0..-2], @current_buffer] + else + @current_buffer = initial_buffer || self.class.make_frame + @buffer_stack = [@current_buffer] + end + end + + def replace(buffer) + return if self == buffer + + @current_buffer = buffer.current + @buffer_stack = buffer.buffer_stack + end + + def append=(arg) + @current_buffer.append = arg + end + + def safe_append=(arg) + @current_buffer.safe_append = arg + end + + def safe_concat(arg) + # rubocop:disable Rails/OutputSafety + @current_buffer.safe_concat(arg) + # rubocop:enable Rails/OutputSafety + end + + def length + @current_buffer.length + end + + def push(buffer = nil) + buffer ||= self.class.make_frame + @buffer_stack.push(buffer) + @current_buffer = buffer + end + + def pop + @buffer_stack.pop.tap do + @current_buffer = @buffer_stack.last + end + end + + def to_s + @current_buffer + end + + alias_method :current, :to_s + end +end diff --git a/lib/view_component/slot_v2.rb b/lib/view_component/slot_v2.rb index 302c6e656..c0b854a9b 100644 --- a/lib/view_component/slot_v2.rb +++ b/lib/view_component/slot_v2.rb @@ -45,18 +45,12 @@ def to_s if defined?(@__vc_content_set_by_with_content) @__vc_component_instance.with_content(@__vc_content_set_by_with_content) - view_context.capture do - @__vc_component_instance.render_in(view_context) - end + @__vc_component_instance.render_in(view_context) elsif defined?(@__vc_content_block) - view_context.capture do - # render_in is faster than `parent.render` - @__vc_component_instance.render_in(view_context, &@__vc_content_block) - end + # render_in is faster than `parent.render` + @__vc_component_instance.render_in(view_context, &@__vc_content_block) else - view_context.capture do - @__vc_component_instance.render_in(view_context) - end + @__vc_component_instance.render_in(view_context) end elsif defined?(@__vc_content) @__vc_content diff --git a/performance/components/name_component_with_gob.html.erb b/performance/components/name_component_with_gob.html.erb new file mode 100644 index 000000000..a4f1b39d9 --- /dev/null +++ b/performance/components/name_component_with_gob.html.erb @@ -0,0 +1,5 @@ +

hello <%= @name %>

+ +<% 50.times do %> + <%= render Performance::NestedNameComponentWithGOB.new(name: @name) %> +<% end %> diff --git a/performance/components/name_component_with_gob.rb b/performance/components/name_component_with_gob.rb new file mode 100644 index 000000000..3c710533a --- /dev/null +++ b/performance/components/name_component_with_gob.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class Performance::NameComponentWithGOB < Performance::NameComponent + prepend ViewComponent::GlobalOutputBuffer +end diff --git a/performance/components/nested_name_component_with_gob.html.erb b/performance/components/nested_name_component_with_gob.html.erb new file mode 100644 index 000000000..94d827179 --- /dev/null +++ b/performance/components/nested_name_component_with_gob.html.erb @@ -0,0 +1 @@ +

nested hello <%= @name %>

diff --git a/performance/components/nested_name_component_with_gob.rb b/performance/components/nested_name_component_with_gob.rb new file mode 100644 index 000000000..5be653bef --- /dev/null +++ b/performance/components/nested_name_component_with_gob.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class Performance::NestedNameComponentWithGOB < Performance::NestedNameComponent + prepend ViewComponent::GlobalOutputBuffer +end diff --git a/performance/global_output_buffer_benchmark.rb b/performance/global_output_buffer_benchmark.rb new file mode 100644 index 000000000..7989f187a --- /dev/null +++ b/performance/global_output_buffer_benchmark.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +# Run `bundle exec rake benchmark` to execute benchmark. +# This is very much a work-in-progress. Please feel free to make/suggest improvements! + +require "benchmark/ips" + +# Configure Rails Environment +ENV["RAILS_ENV"] = "production" +require File.expand_path("../test/sandbox/config/environment.rb", __dir__) + +module Performance + require_relative "components/name_component.rb" + require_relative "components/nested_name_component.rb" + require_relative "components/name_component_with_gob.rb" + require_relative "components/nested_name_component_with_gob.rb" +end + +class BenchmarksController < ActionController::Base +end + +BenchmarksController.view_paths = [File.expand_path("./views", __dir__)] +controller_view = BenchmarksController.new.view_context + +Benchmark.ips do |x| + x.time = 10 + x.warmup = 2 + + x.report("component") { controller_view.render(Performance::NameComponent.new(name: "Fox Mulder")) } + x.report("component with GOB") { controller_view.render(Performance::NameComponentWithGOB.new(name: "Fox Mulder")) } + + x.compare! +end diff --git a/test/sandbox/app/components/content_tag_component.html.erb b/test/sandbox/app/components/content_tag_component.html.erb new file mode 100644 index 000000000..6c6cc11da --- /dev/null +++ b/test/sandbox/app/components/content_tag_component.html.erb @@ -0,0 +1,3 @@ +<%= helpers.my_helper_method({ foo: :bar }) do %> +

Content inside helper method

+<% end %> diff --git a/test/sandbox/app/components/content_tag_component.rb b/test/sandbox/app/components/content_tag_component.rb new file mode 100644 index 000000000..c0eaa69f9 --- /dev/null +++ b/test/sandbox/app/components/content_tag_component.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +class ContentTagComponent < ViewComponent::Base +end diff --git a/test/sandbox/app/components/form_for_component.html.erb b/test/sandbox/app/components/form_for_component.html.erb new file mode 100644 index 000000000..e252302f3 --- /dev/null +++ b/test/sandbox/app/components/form_for_component.html.erb @@ -0,0 +1,3 @@ +<%= form_for Post.new, url: "/" do |form| %> + <%= render LabelComponent.new(form: form) %> +<% end %> diff --git a/test/sandbox/app/components/form_for_component.rb b/test/sandbox/app/components/form_for_component.rb new file mode 100644 index 000000000..cedf55478 --- /dev/null +++ b/test/sandbox/app/components/form_for_component.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +class FormForComponent < ViewComponent::Base + def initialize + end +end diff --git a/test/sandbox/app/components/form_with_component.html.erb b/test/sandbox/app/components/form_with_component.html.erb new file mode 100644 index 000000000..e14148304 --- /dev/null +++ b/test/sandbox/app/components/form_with_component.html.erb @@ -0,0 +1,3 @@ +<%= form_with model: Post.new, url: "/" do |form| %> + <%= render LabelComponent.new(form: form) %> +<% end %> diff --git a/test/sandbox/app/components/form_with_component.rb b/test/sandbox/app/components/form_with_component.rb new file mode 100644 index 000000000..50c334ce3 --- /dev/null +++ b/test/sandbox/app/components/form_with_component.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +class FormWithComponent < ViewComponent::Base + def initialize + end +end diff --git a/test/sandbox/app/components/incompatible_form_component.html.erb b/test/sandbox/app/components/incompatible_form_component.html.erb new file mode 100644 index 000000000..e252302f3 --- /dev/null +++ b/test/sandbox/app/components/incompatible_form_component.html.erb @@ -0,0 +1,3 @@ +<%= form_for Post.new, url: "/" do |form| %> + <%= render LabelComponent.new(form: form) %> +<% end %> diff --git a/test/sandbox/app/components/incompatible_form_component.rb b/test/sandbox/app/components/incompatible_form_component.rb new file mode 100644 index 000000000..22cf33104 --- /dev/null +++ b/test/sandbox/app/components/incompatible_form_component.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +class IncompatibleFormComponent < ViewComponent::Base + def initialize + end +end diff --git a/test/sandbox/app/components/inline_render_component.rb b/test/sandbox/app/components/inline_render_component.rb new file mode 100644 index 000000000..4b6934e4e --- /dev/null +++ b/test/sandbox/app/components/inline_render_component.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class InlineRenderComponent < ViewComponent::Base + def initialize(items:) + @items = items + end + + def call + @items.map { |c| render(c) }.join + end +end diff --git a/test/sandbox/app/components/label_component.html.erb b/test/sandbox/app/components/label_component.html.erb new file mode 100644 index 000000000..b721bce8d --- /dev/null +++ b/test/sandbox/app/components/label_component.html.erb @@ -0,0 +1,5 @@ +
+ <%= form.label :published do %> + <%= form.check_box :published %> + <% end %> +
diff --git a/test/sandbox/app/components/label_component.rb b/test/sandbox/app/components/label_component.rb new file mode 100644 index 000000000..ac0c8d3f0 --- /dev/null +++ b/test/sandbox/app/components/label_component.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class LabelComponent < ViewComponent::Base + def initialize(form:) + @form = form + end + + private + + attr_reader :form +end diff --git a/test/sandbox/app/components/link_to_tag_component.html.erb b/test/sandbox/app/components/link_to_tag_component.html.erb new file mode 100644 index 000000000..3cc19b97e --- /dev/null +++ b/test/sandbox/app/components/link_to_tag_component.html.erb @@ -0,0 +1,3 @@ +<%= helpers.link_to("/some_url") do %> + Content +<% end %> diff --git a/test/sandbox/app/components/link_to_tag_component.rb b/test/sandbox/app/components/link_to_tag_component.rb new file mode 100644 index 000000000..ff5225663 --- /dev/null +++ b/test/sandbox/app/components/link_to_tag_component.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +class LinkToTagComponent < ViewComponent::Base +end diff --git a/test/sandbox/app/components/partial_haml_component.html.haml b/test/sandbox/app/components/partial_haml_component.html.haml new file mode 100644 index 000000000..edfcb3d04 --- /dev/null +++ b/test/sandbox/app/components/partial_haml_component.html.haml @@ -0,0 +1,2 @@ +%span.bar + = render "haml_partial" diff --git a/test/sandbox/app/components/partial_haml_component.rb b/test/sandbox/app/components/partial_haml_component.rb new file mode 100644 index 000000000..e40841d3e --- /dev/null +++ b/test/sandbox/app/components/partial_haml_component.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +class PartialHamlComponent < ViewComponent::Base +end diff --git a/test/sandbox/app/components/slot_partial_helper_component.rb b/test/sandbox/app/components/slot_partial_helper_component.rb new file mode 100644 index 000000000..e25f3bd6e --- /dev/null +++ b/test/sandbox/app/components/slot_partial_helper_component.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +class SlotPartialHelperComponent < ViewComponent::Base + renders_one :header, PartialHelperComponent + + def call + content_tag :h1 do + safe_join([ + helpers.expensive_message, + header.to_s + ]) + end + end +end diff --git a/test/sandbox/app/helpers/content_tag_helper.rb b/test/sandbox/app/helpers/content_tag_helper.rb new file mode 100644 index 000000000..29ac7fb7e --- /dev/null +++ b/test/sandbox/app/helpers/content_tag_helper.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module ContentTagHelper + def my_helper_method(options, &block) + content_tag(:div, options, &block) + end +end diff --git a/test/sandbox/app/models/post.rb b/test/sandbox/app/models/post.rb index 4adc2ba71..e38472578 100644 --- a/test/sandbox/app/models/post.rb +++ b/test/sandbox/app/models/post.rb @@ -3,5 +3,5 @@ class Post include ActiveModel::Model - attr_accessor :title + attr_accessor :title, :published end diff --git a/test/sandbox/app/views/integration_examples/_erb_partial.html.erb b/test/sandbox/app/views/integration_examples/_erb_partial.html.erb new file mode 100644 index 000000000..ec3737c1d --- /dev/null +++ b/test/sandbox/app/views/integration_examples/_erb_partial.html.erb @@ -0,0 +1,4 @@ +
+ What's up! + <%= render HamlComponent.new(message: "It works!") %> +
diff --git a/test/sandbox/app/views/integration_examples/_haml_partial.html.haml b/test/sandbox/app/views/integration_examples/_haml_partial.html.haml new file mode 100644 index 000000000..629804f06 --- /dev/null +++ b/test/sandbox/app/views/integration_examples/_haml_partial.html.haml @@ -0,0 +1,3 @@ +.baz + = "Heyyy!" + = render "erb_partial" diff --git a/test/sandbox/app/views/integration_examples/link_to_helper.html.erb b/test/sandbox/app/views/integration_examples/link_to_helper.html.erb new file mode 100644 index 000000000..726fccf11 --- /dev/null +++ b/test/sandbox/app/views/integration_examples/link_to_helper.html.erb @@ -0,0 +1,3 @@ +<%= link_to("/some_url") do %> + Content +<% end %> diff --git a/test/sandbox/app/views/integration_examples/nested_haml.html.haml b/test/sandbox/app/views/integration_examples/nested_haml.html.haml new file mode 100644 index 000000000..ca83c7cda --- /dev/null +++ b/test/sandbox/app/views/integration_examples/nested_haml.html.haml @@ -0,0 +1,3 @@ +%p.foo + = "Hello, world!" + = render(PartialHamlComponent.new) diff --git a/test/sandbox/config/routes.rb b/test/sandbox/config/routes.rb index 78f8f2bab..040923c59 100644 --- a/test/sandbox/config/routes.rb +++ b/test/sandbox/config/routes.rb @@ -20,10 +20,11 @@ get :render_component, to: "integration_examples#render_component" get :controller_inline_render_component, to: "integration_examples#controller_inline_render_component" get :controller_to_string_render_component, to: "integration_examples#controller_to_string_render_component" - get :layout_default, to: "layouts#default" get :layout_global_for_action, to: "layouts#global_for_action" get :layout_explicit_in_action, to: "layouts#explicit_in_action" get :layout_disabled_in_action, to: "layouts#disabled_in_action" get :layout_with_content_for, to: "layouts#with_content_for" + get :nested_haml, to: "integration_examples#nested_haml" + get :link_to_helper, to: "integration_examples#link_to_helper" end diff --git a/test/view_component/action_view_compatibility_test.rb b/test/view_component/action_view_compatibility_test.rb new file mode 100644 index 000000000..3c885157b --- /dev/null +++ b/test/view_component/action_view_compatibility_test.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +require "test_helper" + +class ViewComponent::ActionViewCompatibilityTest < ViewComponent::TestCase + def test_renders_form_for_labels_with_block_correctly + skip unless Rails.application.config.view_component.use_global_output_buffer + + render_inline(FormForComponent.new) + + assert_selector("form > div > label > input") + refute_selector("form > div > input") + end + + def test_renders_form_with_labels_with_block_correctly + skip unless Rails.application.config.view_component.use_global_output_buffer + + render_inline(FormWithComponent.new) + + assert_selector("form > div > label > input") + refute_selector("form > div > input") + end + + def test_form_without_compatibility_does_not_raise + skip unless Rails.application.config.view_component.use_global_output_buffer + + render_inline(IncompatibleFormComponent.new) + + # Bad selector should be present, at least until fixed upstream or included by default + refute_selector("form > div > input") + end + + def test_helper_with_content_tag + skip unless Rails.application.config.view_component.use_global_output_buffer + + render_inline(ContentTagComponent.new) + assert_selector("div > p") + end +end diff --git a/test/view_component/integration_test.rb b/test/view_component/integration_test.rb index 7e4e10b8f..ba71e3cf4 100644 --- a/test/view_component/integration_test.rb +++ b/test/view_component/integration_test.rb @@ -524,6 +524,8 @@ def test_renders_the_inline_component_using_a_non_standard_located_template end def test_renders_an_inline_component_preview_using_a_haml_template + skip if Rails.application.config.view_component.use_global_output_buffer && Rails::VERSION::STRING < "6.1" + get "/rails/view_components/inline_component/with_haml" assert_select "h1", "Some HAML here" assert_select "input[name=?]", "name" @@ -535,6 +537,14 @@ def test_returns_404_when_preview_does_not_exist end end + def test_renders_a_mix_of_haml_and_erb + skip if Rails.application.config.view_component.use_global_output_buffer && Rails::VERSION::STRING < "6.1" + + get "/nested_haml" + assert_response :success + assert_select "p.foo > span.bar > div.baz > article.quux > div.haml-div" + end + def test_raises_an_error_if_the_template_is_not_present_and_the_render_with_template_method_is_used_in_the_example error = assert_raises ViewComponent::PreviewTemplateError do @@ -544,6 +554,8 @@ def test_raises_an_error_if_the_template_is_not_present_and_the_render_with_temp end def test_renders_a_preview_template_using_haml_params_from_url_custom_template_and_locals + skip if Rails.application.config.view_component.use_global_output_buffer && Rails::VERSION::STRING < "6.1" + get "/rails/view_components/inline_component/with_several_options?form_title=Title from params" assert_select "form" do @@ -583,4 +595,9 @@ def test_sets_the_compiler_mode_in_development_mode assert_equal ViewComponent::Compiler::DEVELOPMENT_MODE, ViewComponent::Compiler.mode end end + + def test_link_to_helper + get "/link_to_helper" + assert_select "a > i,span" + end end diff --git a/test/view_component/view_component_test.rb b/test/view_component/view_component_test.rb index a5ad65e4b..704f1d9fc 100644 --- a/test/view_component/view_component_test.rb +++ b/test/view_component/view_component_test.rb @@ -103,6 +103,8 @@ def test_renders_slim_template end def test_renders_haml_with_html_formatted_slot + skip if Rails.application.config.view_component.use_global_output_buffer && Rails::VERSION::STRING < "6.1" + render_inline(HamlHtmlFormattedSlotComponent.new) assert_selector("p", text: "HTML Formatted one") @@ -974,6 +976,12 @@ def test_multithread_render end end + def test_multiple_inline_renders_of_the_same_component + component = ErbComponent.new(message: "foo") + render_inline(InlineRenderComponent.new(items: [component, component])) + assert_selector("div", text: "foo", count: 2) + end + def test_deprecated_generate_mattr_accessor ViewComponent::Base._deprecated_generate_mattr_accessor(:test_accessor) assert(ViewComponent::Base.respond_to?(:generate_test_accessor))