From 0a2fcc7b1238375bafa3c512905bff70566c0a6e Mon Sep 17 00:00:00 2001 From: Joel Hawksley Date: Wed, 4 Nov 2026 12:55:47 -0700 Subject: [PATCH 1/3] Preserve request routing context when rendering previews --- docs/CHANGELOG.md | 4 ++ docs/guide/testing.md | 9 ++++ lib/view_component/test_helpers.rb | 12 +++-- .../app/components/request_url_component.rb | 5 +- test/sandbox/config/routes.rb | 1 + .../previews/request_url_component_preview.rb | 7 +++ .../test/components/render_preview_test.rb | 48 +++++++++++++++++++ 7 files changed, 80 insertions(+), 6 deletions(-) create mode 100644 test/sandbox/test/components/previews/request_url_component_preview.rb diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index c2c997177..896660b4e 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -10,6 +10,10 @@ nav_order: 6 ## main +* Restore request-dependent URL generation when using `render_preview` inside `with_request_url`. + + *Joel Hawksley* + * Speed up cold cache digests by skipping the `# Template Dependency:` scan for sources that declare none, and by not translating class names in synthetic templates, which only declare paths. *Erik Axel Nielsen* diff --git a/docs/guide/testing.md b/docs/guide/testing.md index 8463495bd..394509011 100644 --- a/docs/guide/testing.md +++ b/docs/guide/testing.md @@ -231,6 +231,15 @@ class ExampleComponentTest < ViewComponent::TestCase end ``` +`with_request_url` also applies to request-dependent URL generation inside `render_preview`: + +```ruby +with_request_url "/products/42" do + render_preview(:default, from: ExampleComponentPreview) + assert_link "French", href: "/products/42?locale=fr" +end +``` + ## Setting `request.host` Since 3.3.0 diff --git a/lib/view_component/test_helpers.rb b/lib/view_component/test_helpers.rb index b267a3e7e..30766dd19 100644 --- a/lib/view_component/test_helpers.rb +++ b/lib/view_component/test_helpers.rb @@ -89,14 +89,16 @@ def render_preview(name, from: __vc_test_helpers_preview_class, params: {}) controller_class = Rails.application.config.view_component.previews.controller.constantize request = ActionDispatch::TestRequest.new(vc_test_request.env.dup) query_parameters = vc_test_request.query_parameters.deep_dup.merge(params.stringify_keys) - request.path_parameters = vc_test_request.path_parameters.except(*params.keys.map(&:to_sym)).merge( - controller: controller_class.controller_path, - action: "previews", - path: "#{from.preview_name}/#{name}" - ) + request.path_parameters = vc_test_request.path_parameters.deep_dup request.set_header("action_dispatch.request.query_parameters", query_parameters) request.set_header(Rack::QUERY_STRING, query_parameters.to_query) previews_controller = __vc_test_helpers_build_controller(controller_class, request: request) + # Dispatch the preview without replacing the request's URL generation context. + previews_controller.params = request.parameters.merge(params.stringify_keys).merge( + "controller" => controller_class.controller_path, + "action" => "previews", + "path" => "#{from.preview_name}/#{name}" + ) previews_controller.set_response!(ActionDispatch::Response.new) previews_controller.process(:previews) @rendered_content = previews_controller.response.body diff --git a/test/sandbox/app/components/request_url_component.rb b/test/sandbox/app/components/request_url_component.rb index e96b4e996..b54c09fbc 100644 --- a/test/sandbox/app/components/request_url_component.rb +++ b/test/sandbox/app/components/request_url_component.rb @@ -2,6 +2,9 @@ class RequestUrlComponent < ViewComponent::Base def call - content_tag(:span, request.path, class: "path") + content_tag(:span, request.fullpath, class: "fullpath") + content_tag(:span, request.path, class: "path") + + content_tag(:span, request.fullpath, class: "fullpath") + + link_to("Request-dependent link", url_for(bar: "baz")) + + link_to("Absolute request-dependent link", url_for(bar: "baz", only_path: false)) end end diff --git a/test/sandbox/config/routes.rb b/test/sandbox/config/routes.rb index d0d7c5073..2c6ed95bc 100644 --- a/test/sandbox/config/routes.rb +++ b/test/sandbox/config/routes.rb @@ -8,6 +8,7 @@ get :content, to: "integration_examples#content" get :variants, to: "integration_examples#variants" get :products, to: "integration_examples#products" + get "products/:id", to: "integration_examples#products" get :inline_products, to: "integration_examples#inline_products" get :cached, to: "integration_examples#cached" get :render_check, to: "integration_examples#render_check" diff --git a/test/sandbox/test/components/previews/request_url_component_preview.rb b/test/sandbox/test/components/previews/request_url_component_preview.rb new file mode 100644 index 000000000..bb32a83a4 --- /dev/null +++ b/test/sandbox/test/components/previews/request_url_component_preview.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class RequestUrlComponentPreview < ViewComponent::Preview + def default + render(RequestUrlComponent.new) + end +end diff --git a/test/sandbox/test/components/render_preview_test.rb b/test/sandbox/test/components/render_preview_test.rb index 80b4a84a4..3931ae0dc 100644 --- a/test/sandbox/test/components/render_preview_test.rb +++ b/test/sandbox/test/components/render_preview_test.rb @@ -63,6 +63,54 @@ def test_render_preview_preserves_the_original_request_parameters end end + def test_render_preview_uses_the_request_url_for_request_dependent_links + with_request_url("/products") do + fragment = render_preview(:default, from: RequestUrlComponentPreview) + + assert_equal "/products", vc_test_request.path + assert_selector(".path", text: "/products") + assert_equal "/products?bar=baz", fragment.at_css("a")["href"] + end + end + + def test_render_preview_preserves_route_parameters_when_preview_parameters_overlap + with_request_url("/products/42?id=query-id") do + render_args = RequestUrlComponentPreview.method(:render_args) + preview_parameters = nil + capture_parameters = lambda do |name, **options| + preview_parameters = options[:params].to_h + render_args.call(name, **options) + end + + RequestUrlComponentPreview.stub(:render_args, capture_parameters) do + render_preview(:default, from: RequestUrlComponentPreview, params: {id: "preview-id"}) + end + + assert_equal "preview-id", preview_parameters["id"] + assert_link "Request-dependent link", href: "/products/42?bar=baz", exact: true + assert_equal "42", vc_test_request.path_parameters[:id] + assert_equal "query-id", vc_test_request.query_parameters["id"] + end + end + + def test_render_preview_parameters_override_request_query_parameters + with_request_url("/products?title=Query+title") do + render_preview(:with_params, from: PreviewComponentPreview, params: {title: "Preview title"}) + + assert_text "Preview title" + assert_no_text "Query title" + assert_equal "Query title", vc_test_request.query_parameters["title"] + end + end + + def test_render_preview_preserves_the_request_host_and_protocol_for_links + with_request_url("/products/42", host: "app.example.com:8443", protocol: :https) do + render_preview(:default, from: RequestUrlComponentPreview) + + assert_link "Absolute request-dependent link", href: "https://app.example.com:8443/products/42?bar=baz" + end + end + def test_render_preview_runs_locale_callbacks_and_restores_the_locale render_args = PreviewComponentPreview.method(:render_args) locales = [] From ac4183b9de166501402d4513f4845a66575b3122 Mon Sep 17 00:00:00 2001 From: Joel Hawksley Date: Wed, 4 Nov 2026 12:59:13 -0700 Subject: [PATCH 2/3] Exercise preview parameter precedence through rendered output --- .../sandbox/app/components/request_url_component.rb | 1 + .../previews/request_url_component_preview.rb | 4 ++++ test/sandbox/test/components/render_preview_test.rb | 13 ++----------- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/test/sandbox/app/components/request_url_component.rb b/test/sandbox/app/components/request_url_component.rb index b54c09fbc..e8ea2abf1 100644 --- a/test/sandbox/app/components/request_url_component.rb +++ b/test/sandbox/app/components/request_url_component.rb @@ -4,6 +4,7 @@ class RequestUrlComponent < ViewComponent::Base def call content_tag(:span, request.path, class: "path") + content_tag(:span, request.fullpath, class: "fullpath") + + content_tag(:span, content, class: "content") + link_to("Request-dependent link", url_for(bar: "baz")) + link_to("Absolute request-dependent link", url_for(bar: "baz", only_path: false)) end diff --git a/test/sandbox/test/components/previews/request_url_component_preview.rb b/test/sandbox/test/components/previews/request_url_component_preview.rb index bb32a83a4..1faadbb5d 100644 --- a/test/sandbox/test/components/previews/request_url_component_preview.rb +++ b/test/sandbox/test/components/previews/request_url_component_preview.rb @@ -4,4 +4,8 @@ class RequestUrlComponentPreview < ViewComponent::Preview def default render(RequestUrlComponent.new) end + + def with_id(id:) + render(RequestUrlComponent.new) { id } + end end diff --git a/test/sandbox/test/components/render_preview_test.rb b/test/sandbox/test/components/render_preview_test.rb index 3931ae0dc..183dd92d3 100644 --- a/test/sandbox/test/components/render_preview_test.rb +++ b/test/sandbox/test/components/render_preview_test.rb @@ -75,18 +75,9 @@ def test_render_preview_uses_the_request_url_for_request_dependent_links def test_render_preview_preserves_route_parameters_when_preview_parameters_overlap with_request_url("/products/42?id=query-id") do - render_args = RequestUrlComponentPreview.method(:render_args) - preview_parameters = nil - capture_parameters = lambda do |name, **options| - preview_parameters = options[:params].to_h - render_args.call(name, **options) - end - - RequestUrlComponentPreview.stub(:render_args, capture_parameters) do - render_preview(:default, from: RequestUrlComponentPreview, params: {id: "preview-id"}) - end + render_preview(:with_id, from: RequestUrlComponentPreview, params: {id: "preview-id"}) - assert_equal "preview-id", preview_parameters["id"] + assert_selector(".content", exact_text: "preview-id") assert_link "Request-dependent link", href: "/products/42?bar=baz", exact: true assert_equal "42", vc_test_request.path_parameters[:id] assert_equal "query-id", vc_test_request.query_parameters["id"] From e93c3f544d211cd3f6b251b34b9480455c295d5a Mon Sep 17 00:00:00 2001 From: Joel Hawksley Date: Wed, 4 Nov 2026 13:38:14 -0700 Subject: [PATCH 3/3] Restore compiler override on the original reloaded class --- test/sandbox/test/integration_test.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/sandbox/test/integration_test.rb b/test/sandbox/test/integration_test.rb index 628dc92d7..82d06ef66 100644 --- a/test/sandbox/test/integration_test.rb +++ b/test/sandbox/test/integration_test.rb @@ -134,8 +134,8 @@ def test_inherited_component_with_call_method_does_not_recompile_superclass assert_select "div", "hello world" assert_response :success ensure - UncompilableComponent.singleton_class.silence_redefinition_of_method(:__vc_compile) - UncompilableComponent.define_singleton_method(:__vc_compile, original_method) + original_method.receiver.singleton_class.silence_redefinition_of_method(:__vc_compile) + original_method.receiver.define_singleton_method(:__vc_compile, original_method) end end