From bdd4b1654eb90f2884e43c1af0946aff582f353f Mon Sep 17 00:00:00 2001 From: Hubert Hoelzl Date: Tue, 28 Oct 2025 12:38:50 +0100 Subject: [PATCH 1/7] Allow I18n in render lifecycle --- lib/view_component/base.rb | 20 +++++++++++++++---- test/sandbox/config/locales/en.yml | 4 ++++ test/sandbox/test/rendering_test.rb | 31 +++++++++++++++++++++++++++-- 3 files changed, 49 insertions(+), 6 deletions(-) diff --git a/lib/view_component/base.rb b/lib/view_component/base.rb index 8f8619c67..a36de7881 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,14 @@ 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) + 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 +171,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/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..cd9d6afb5 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, "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,31 @@ 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 = I18nTestComponent.new + component.setup_render(vc_test_view_context) + assert_equal(component.message, I18n.t("rendering_test.i18n_test_component.message")) + end end From 4be19147a97baf2c3f4ce028560b84d208c78539 Mon Sep 17 00:00:00 2001 From: Hubert Hoelzl Date: Tue, 28 Oct 2025 12:48:46 +0100 Subject: [PATCH 2/7] added changelog --- docs/CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index cae2fe1cf..bb8811e17 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -10,6 +10,11 @@ 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 + + *23tux* + * Resolve deprecation warning for `ActiveSupport::Configurable`. *Simon Fish* From 727c07d4182290159842e9f29b399d64e33e04a1 Mon Sep 17 00:00:00 2001 From: Hubert Hoelzl Date: Tue, 28 Oct 2025 20:52:15 +0100 Subject: [PATCH 3/7] added #setup_render test helper --- lib/view_component/base.rb | 1 + lib/view_component/test_helpers.rb | 4 ++++ test/sandbox/test/rendering_test.rb | 3 +-- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/view_component/base.rb b/lib/view_component/base.rb index a36de7881..1af31c2e5 100644 --- a/lib/view_component/base.rb +++ b/lib/view_component/base.rb @@ -141,6 +141,7 @@ def setup_render(view_context, &block) @__vc_render_in_block = block @view_context.instance_variable_set(:@virtual_path, virtual_path) + self end def perform_render 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/test/rendering_test.rb b/test/sandbox/test/rendering_test.rb index cd9d6afb5..8e8ab3e93 100644 --- a/test/sandbox/test/rendering_test.rb +++ b/test/sandbox/test/rendering_test.rb @@ -1345,8 +1345,7 @@ def test_i18n_in_render_hook end def test_render_lifecycle_hooks - component = I18nTestComponent.new - component.setup_render(vc_test_view_context) + component = setup_render(I18nTestComponent.new) assert_equal(component.message, I18n.t("rendering_test.i18n_test_component.message")) end end From d9321cea5750e655935fb266082a7524cd108e3a Mon Sep 17 00:00:00 2001 From: 23tux Date: Tue, 28 Oct 2025 20:52:55 +0100 Subject: [PATCH 4/7] Update docs/CHANGELOG.md Co-authored-by: Hans Lemuet --- docs/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index bb8811e17..b01f4269a 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -13,7 +13,7 @@ nav_order: 6 * Allow I18n calls in render? * Split render lifecycle into separate methods, to be able to test component methods that rely on `t(...)` directly - *23tux* + *23tux* * Resolve deprecation warning for `ActiveSupport::Configurable`. From ee51aafae55e25790eba35bf52bde315058e9708 Mon Sep 17 00:00:00 2001 From: Hubert Hoelzl Date: Tue, 28 Oct 2025 20:59:00 +0100 Subject: [PATCH 5/7] makes allocation spec more robust --- test/sandbox/test/rendering_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/sandbox/test/rendering_test.rb b/test/sandbox/test/rendering_test.rb index 8e8ab3e93..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" => 71..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 From daba8a962083e8362ef42deec18b1133df352119 Mon Sep 17 00:00:00 2001 From: Hubert Hoelzl Date: Tue, 28 Oct 2025 21:07:15 +0100 Subject: [PATCH 6/7] added docs --- docs/CHANGELOG.md | 5 +++-- docs/api.md | 17 +++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index b01f4269a..ad24925a2 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -10,8 +10,9 @@ 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 +* 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* 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`, From 42f71cb38fd6e6533c02697fffce2757e26ef678 Mon Sep 17 00:00:00 2001 From: 23tux Date: Wed, 29 Oct 2025 06:02:21 +0100 Subject: [PATCH 7/7] Update docs/CHANGELOG.md Co-authored-by: Hans Lemuet --- docs/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index ad24925a2..11149ea8d 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -10,7 +10,7 @@ nav_order: 6 ## main -* Allow I18n calls in render?. +* 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.