diff --git a/lib/herb/engine/slots/visitor.rb b/lib/herb/engine/slots/visitor.rb index 39cca8491..d7400fe7b 100644 --- a/lib/herb/engine/slots/visitor.rb +++ b/lib/herb/engine/slots/visitor.rb @@ -206,6 +206,7 @@ def initialize(markers: Markers.new, mode: :server, identifier: :path, mark: tru @fragment_nodes = fragment_nodes.compare_by_identity @fragment_fallbacks = fragment_fallbacks.compare_by_identity + @exposed_fallbacks = [] #: Array[untyped] @deferred_nodes = deferred_nodes.compare_by_identity @assignment_nodes = assignment_nodes.compare_by_identity end @@ -661,6 +662,7 @@ def visit_document_node(node) @states.apply_states wrap_bound_renders + expose_fallbacks(node) end #: () -> Hash[String, String]? @@ -670,6 +672,7 @@ def statics #: (untyped) -> void def finish(node) + withdraw_fallbacks(node) follow_replacements return unless @mark @@ -1982,6 +1985,29 @@ def append_statics(document_node) document_node.children.concat(nodes) end + #: (Herb::AST::DocumentNode) -> void + def expose_fallbacks(document_node) + return if @fragment_fallbacks.empty? + + @exposed_fallbacks = @fragment_fallbacks.each_value.flat_map { |body| body } #: Array[untyped] + + document_node.children.concat(@exposed_fallbacks) + + nil + end + + #: (Herb::AST::DocumentNode) -> void + def withdraw_fallbacks(document_node) + exposed = @exposed_fallbacks + + return if exposed.empty? + + document_node.children.reject! { |child| exposed.any? { |node| node.equal?(child) } } + @exposed_fallbacks = [] #: Array[untyped] + + nil + end + #: () -> Array[[String, Array[untyped]]] def fallback_statics_entries @fragment_fallbacks.filter_map { |node, body| diff --git a/sig/herb/engine/slots/visitor.rbs b/sig/herb/engine/slots/visitor.rbs index 84f2cbf2b..109a21241 100644 --- a/sig/herb/engine/slots/visitor.rbs +++ b/sig/herb/engine/slots/visitor.rbs @@ -540,6 +540,12 @@ module Herb # : (Herb::AST::DocumentNode) -> void def append_statics: (Herb::AST::DocumentNode) -> void + # : (Herb::AST::DocumentNode) -> void + def expose_fallbacks: (Herb::AST::DocumentNode) -> void + + # : (Herb::AST::DocumentNode) -> void + def withdraw_fallbacks: (Herb::AST::DocumentNode) -> void + # : () -> Array[[String, Array[untyped]]] def fallback_statics_entries: () -> Array[[ String, Array[untyped] ]] diff --git a/test/engine/scoped_style/visitor_test.rb b/test/engine/scoped_style/visitor_test.rb index 896069db3..93e38f29c 100644 --- a/test/engine/scoped_style/visitor_test.rb +++ b/test/engine/scoped_style/visitor_test.rb @@ -394,5 +394,14 @@ def hoisted(source, filename: TEMPLATE) refute_nil diagnostic assert_equal "app/views/posts/_dynamic.html.erb", diagnostic.template end + + test "carries the scope into the fallback a client stands in while content is stale" do + source = %(<%# herb:slots client %><%# herb:state (album: "") %>LoadedLoading) + + assert_compiled_snapshot( + source, + options(visitors: [Herb::Engine::Slots::Visitor.new(mode: :client)], project_path: PROJECT_PATH) + ) + end end end diff --git a/test/snapshots/engine/scoped_style_visitor_test/test_0032_carries_the_scope_into_the_fallback_a_client_stands_in_while_content_is_stale_0cc487a36bea50e642a7087683c1a917.txt b/test/snapshots/engine/scoped_style_visitor_test/test_0032_carries_the_scope_into_the_fallback_a_client_stands_in_while_content_is_stale_0cc487a36bea50e642a7087683c1a917.txt new file mode 100644 index 000000000..0797dbcc0 --- /dev/null +++ b/test/snapshots/engine/scoped_style_visitor_test/test_0032_carries_the_scope_into_the_fallback_a_client_stands_in_while_content_is_stale_0cc487a36bea50e642a7087683c1a917.txt @@ -0,0 +1,6 @@ +--- +source: "Engine::ScopedStyleVisitorTest#test_0032_carries the scope into the fallback a client stands in while content is stale" +input: "{source: \"<%# herb:slots client %><%# herb:state (album: \\\"\\\") %>LoadedLoading\", options: {filename: \"app/views/posts/index.html.erb\", escape: false, visitors: [#, #], project_path: \"test/fixtures/scoped_styles\"}}" +--- +_buf = ::String.new; ::Herb::Engine::Runtime::Session.record_compile_diagnostics("app/views/posts/index.html.erb", [{ message: "Nothing inside this `` is derived on the server, so its `` can never appear.", severity: :warning, code: "herb-slots-component", origin: "Herb Compiler", suggestion: "Compute something with a declared state inside the fragment, or unwrap it.", line: 1, column: 93, end_line: 1, end_column: 183 }].freeze); @_herb_covered ||= {} ; _herb_occurrence = ((@_herb_region_occurrences ||= ::Hash.new(0))["app/views/posts/index.html.erb"] += 1) - 1; _buf << ''.freeze; ; _herb_state_overrides = ::Herb::Engine::Slots::StateOverrides.resolve((__herb_state_overrides if defined?(__herb_state_overrides)), "app/views/posts/index.html.erb"); album = ::Herb::Engine::Slots::StateOverrides.fetch(_herb_state_overrides, "album", :string) { "" }; _herb_seeds = { }.select { |_, v| [true, false, ::Integer, ::String, ::Symbol, nil].any? { |t| t === v } }.transform_values { |v| v.is_a?(::Symbol) ? v.to_s : v }.merge({ "album" => album }.select { |_, v| [true, false, ::Integer, ::String, ::Symbol, nil].any? { |t| t === v } }.transform_values { |v| v.is_a?(::Symbol) ? v.to_s : v }.slice(*(_herb_state_overrides || {}).keys)); _buf << ::Herb::Engine.raw("") unless _herb_seeds.empty?; _buf << ''.freeze; if true; _buf << 'Loaded'.freeze; end; _buf << ''.freeze; unless @_herb_covered["app/views/posts/index.html.erb:0:0"] && @_herb_covered["app/views/posts/index.html.erb:0:1"]; _buf << ''.freeze; end; ::Herb::Engine::Slots::Manifest::Channel.record("app/views/posts/index.html.erb:aabf5393", "{\"file\":\"app\\/views\\/posts\\/index.html.erb\",\"identifier\":\"app\\/views\\/posts\\/index.html.erb\",\"version\":\"aabf5393\",\"names\":{},\"parts\":{},\"bindings\":{},\"states\":{\"version\":\"aabf5393\",\"declarations\":[{\"name\":\"album\",\"kind\":\"string\",\"default\":\"\\\"\\\"\",\"derived\":null,\"line\":1,\"column\":40,\"scope\":\"region\",\"value\":\"\"}],\"reads\":{},\"conditionals\":{},\"presence\":{},\"computed\":{},\"server\":{\"branches\":{},\"reads\":{}},\"fragments\":{}}}"); +_buf.to_s