From c139ce049fb8ca44df4d019ddc76c3339cc19960 Mon Sep 17 00:00:00 2001 From: Martin Morissette Date: Fri, 31 Jul 2026 13:02:36 -0400 Subject: [PATCH] Add specs: HTML comments round-trip verbatim through capture/split HTML comments are not Liquid syntax and must pass through the parser, {% capture %}, and the split filter byte-for-byte. Horizon's resource-list-carousel relies on this to encode slide arrays as ''-delimited strings. Four progressive specs (c=121-124), all validated against reference liquid: comment survives capture verbatim, split on a comment-shaped delimiter, the full capture->split round trip, and the realistic loop-built list with a trailing delimiter. Ports the storefront integration test removed from shop/world#964845. Closes https://github.com/shop/issues-merchant-workflows/issues/3882 Assisted-By: devx/e5357c56-2306-4c88-97a2-5a8854545719 --- README.md | 2 +- specs/liquid_ruby/html_comment_round_trip.yml | 63 +++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 specs/liquid_ruby/html_comment_round_trip.yml diff --git a/README.md b/README.md index f62285b..b3fba8c 100644 --- a/README.md +++ b/README.md @@ -326,7 +326,7 @@ out of regular runs. | Suite | Tests | Description | |-------|-------|-------------| | **basics** | 945 | Essential Liquid features - start here! Ordered by complexity with implementation hints | -| **liquid_ruby** | 2,097 | Core Liquid specs from [Shopify/liquid](https://github.com/Shopify/liquid) integration tests | +| **liquid_ruby** | 2,101 | Core Liquid specs from [Shopify/liquid](https://github.com/Shopify/liquid) integration tests | | **liquid_ruby_lax** | 121 | Lax-mode reference behavior | | **parser_errors** | 1,905 | Strict parser error compatibility and mutation matrices | | **partials** | 12 | Include/render focused compatibility specs and timings | diff --git a/specs/liquid_ruby/html_comment_round_trip.yml b/specs/liquid_ruby/html_comment_round_trip.yml new file mode 100644 index 0000000..60e1d81 --- /dev/null +++ b/specs/liquid_ruby/html_comment_round_trip.yml @@ -0,0 +1,63 @@ +--- +# HTML comments are not Liquid syntax. `` and everything between +# them are ordinary literal text: they must pass through the parser, through +# {% capture %}, and through string filters completely unmodified. +# +# Themes rely on this to build string-encoded arrays: Horizon's +# resource-list-carousel captures slides separated by a literal +# `` marker, then recovers the array with +# `| split: ''`. An implementation that strips, escapes, +# or otherwise rewrites HTML comments anywhere in that pipeline breaks the +# round trip. All expectations validated verbatim against reference liquid. +# +# Ported from a storefront integration test per +# https://github.com/shop/issues-merchant-workflows/issues/3882. + +specs: + +- name: html_comment_survives_capture_verbatim + template: "{% capture x %}AB{% endcapture %}{{ x }}" + expected: "AB" + complexity: 121 + hint: | + HTML comments have no meaning to Liquid. `` is plain literal + text, so capture must store it byte-for-byte and output it unchanged. + Do not strip, escape, or special-case `` sequences anywhere + in the parse or render pipeline — only `{% %}` and `{{ }}` are Liquid + syntax. + +- name: split_on_html_comment_delimiter + template: "{% assign parts = \"AB\" | split: \"\" %}{{ parts | size }}:{{ parts[0] }}:{{ parts[1] }}" + expected: "2:A:B" + complexity: 122 + hint: | + The split filter takes an arbitrary literal delimiter string — including + one that looks like an HTML comment. Splitting "AB" on + "" yields exactly two elements: "A" and "B". If your + implementation rewrites or strips HTML comments in string values or + filter arguments, the delimiter never matches and this fails. + +- name: html_comment_capture_split_round_trip + template: "{% capture x %}AB{% endcapture %}{% assign parts = x | split: \"\" %}{{ parts | size }}:{{ parts[0] }}:{{ parts[1] }}" + expected: "2:A:B" + complexity: 123 + hint: | + The full round trip: an HTML comment written inside a capture body must + survive verbatim so a later `split` on the exact same comment text can + find it. Capture stores raw rendered text; split matches it literally; + the result is two elements "A" and "B". Themes use this pattern to + encode arrays as delimiter-joined strings, so any HTML-comment + rewriting in capture or split breaks real templates. + +- name: html_comment_delimited_list_built_in_loop_round_trips + template: "{% capture slides %}{% for i in (1..3) %}slide{{ i }}{% endfor %}{% endcapture %}{% assign parts = slides | split: \"\" %}{{ parts | size }}:{{ parts | join: \",\" }}" + expected: "3:slide1,slide2,slide3" + complexity: 124 + hint: | + The realistic theme pattern: a loop appends each item plus a trailing + `` marker into a capture, then split recovers the + items. Note the captured string ENDS with the delimiter, and split + (like Ruby's String#split) drops trailing empty strings, so three + items with three trailing markers yield exactly 3 elements — not 4 + with an empty tail. The comment marker must round-trip verbatim + through capture, the for loop body, and split.