diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index cae2fe1cf..11149ea8d 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -10,6 +10,12 @@ nav_order: 6 ## main +* Allow I18n calls in `render?`. +* Split render lifecycle into separate methods, to be able to test component methods that rely on `t(...)` directly. +* Added `setup_render` test helper to allow testing of component methods that rely on `t(...)` without rendering the component. + + *23tux* + * Resolve deprecation warning for `ActiveSupport::Configurable`. *Simon Fish* diff --git a/docs/api.md b/docs/api.md index 94aebc8d2..54596d33b 100644 --- a/docs/api.md +++ b/docs/api.md @@ -301,6 +301,23 @@ render_inline(MyComponent.new) assert_text("Hello, World!") ``` +### `#setup_render(component)` → [ViewComponent::Base] + +Triggers the render setup of a component without actually rendering it. +The method is also called internally by `#render_in` when a component is rendered. +Useful for testing methods of components that need access to the view context or the `@virtual_path` variable like I18n's `t(...)` helper. + +```ruby +class MyComponent < ViewComponent::Base + def message + t("hello.world") + end +end + +component = setup_render(MyComponent.new) +assert_equal(component.message, "Hello, World!") +``` + ### `#render_preview(name, from: __vc_test_helpers_preview_class, params: {})` → [Nokogiri::HTML5] Render a preview inline. Internally sets `page` to be a `Capybara::Node::Simple`, diff --git a/lib/view_component/base.rb b/lib/view_component/base.rb index 8f8619c67..1af31c2e5 100644 --- a/lib/view_component/base.rb +++ b/lib/view_component/base.rb @@ -102,6 +102,14 @@ def initialize # # @return [String] def render_in(view_context, &block) + setup_render(view_context, &block) + before_render + perform_render + ensure + teardown_render(view_context) + end + + def setup_render(view_context, &block) self.class.__vc_compile(raise_errors: true) @view_context = view_context @@ -123,7 +131,7 @@ def render_in(view_context, &block) # For caching, such as #cache_if @current_template = nil unless defined?(@current_template) - old_current_template = @current_template + @old_current_template = @current_template if block && defined?(@__vc_content_set_by_with_content) raise DuplicateContentError.new(self.class.name) @@ -132,13 +140,15 @@ def render_in(view_context, &block) @__vc_content_evaluated = false @__vc_render_in_block = block - before_render + @view_context.instance_variable_set(:@virtual_path, virtual_path) + self + end + def perform_render if render? value = nil @output_buffer.with_buffer do - @view_context.instance_variable_set(:@virtual_path, virtual_path) rendered_template = around_render do @@ -162,8 +172,11 @@ def render_in(view_context, &block) else "" end - ensure + end + + def teardown_render(view_context) view_context.instance_variable_set(:@virtual_path, @old_virtual_path) + old_current_template = remove_instance_variable(:@current_template) if defined?(@current_template) @current_template = old_current_template end diff --git a/lib/view_component/test_helpers.rb b/lib/view_component/test_helpers.rb index e3de34f1b..fcf2d996d 100644 --- a/lib/view_component/test_helpers.rb +++ b/lib/view_component/test_helpers.rb @@ -45,6 +45,10 @@ def render_inline(component, **args, &block) fragment end + def setup_render(component) + component.setup_render(vc_test_view_context) + end + # Returns the view context used to render components in tests. Note that the view context # is reset after each call to `render_inline`. # diff --git a/test/sandbox/config/locales/en.yml b/test/sandbox/config/locales/en.yml index 7a4c5f897..dbc82b91a 100644 --- a/test/sandbox/config/locales/en.yml +++ b/test/sandbox/config/locales/en.yml @@ -24,3 +24,7 @@ en: my_component: message: "OH NO! (you shouldn't see me)" + + rendering_test: + i18n_test_component: + message: "I can be called in render?" diff --git a/test/sandbox/test/rendering_test.rb b/test/sandbox/test/rendering_test.rb index 4bcfe1dfc..7cf1755b5 100644 --- a/test/sandbox/test/rendering_test.rb +++ b/test/sandbox/test/rendering_test.rb @@ -20,7 +20,7 @@ def test_render_inline_allocations MyComponent.__vc_ensure_compiled with_instrumentation_enabled_option(false) do - assert_allocations({"3.5" => 67, "3.4" => 72..74, "3.3" => 72, "3.2" => 75..76}) do + assert_allocations({"3.5" => 67, "3.4" => 72..74, "3.3" => 72..75, "3.2" => 71..76}) do render_inline(MyComponent.new) end end @@ -34,7 +34,7 @@ def test_render_collection_inline_allocations ViewComponent::CompileCache.cache.delete(ProductComponent) ProductComponent.__vc_ensure_compiled - allocations = {"3.5" => 66, "3.4" => 70..82, "3.3" => 86, "3.2" => 89..90} + allocations = {"3.5" => 66, "3.4" => 70..82, "3.3" => 86, "3.2" => 70..90} products = [Product.new(name: "Radio clock"), Product.new(name: "Mints")] notice = "On sale" @@ -1322,4 +1322,30 @@ def test_render_partial_with_yield render_inline(PartialWithYieldComponent.new) assert_text "hello world", exact: true, normalize_ws: true end + + class I18nTestComponent < ViewComponent::Base + def message + t(".message") + end + + def render? + message + end + + def call + content_tag :div, t(".message") + end + end + + def test_i18n_in_render_hook + vc_test_request.params[:hello] = "world" + render_inline(I18nTestComponent.new) + + assert_selector("div", text: I18n.t("rendering_test.i18n_test_component.message")) + end + + def test_render_lifecycle_hooks + component = setup_render(I18nTestComponent.new) + assert_equal(component.message, I18n.t("rendering_test.i18n_test_component.message")) + end end