From b87f35ea4a24d015a4dda4bbace991af021dcdbe Mon Sep 17 00:00:00 2001 From: Allison Date: Mon, 27 Apr 2026 18:19:56 -0400 Subject: [PATCH] fix(marketing): bento autoescape + dead col-span + test gaps - Pipe macro title/description through | safe to render NBSP/& correctly (autoescape was producing literal '95 %+' and 'Q&R' text on screen) - Replace dynamic col-span-{{ span }} with static lookup table so Tailwind scanner generates the utilities for A-2.7+ reuse - Replace inline border style with border-white/[0.045] utility (codebase consistency) - Add explicit Q&R assertion + autoescape regression guard test --- static/css/marketing.css | 13 +++++++++++++ templates/macros/bento.html | 12 +++++++----- tests/test_marketing_landing_template.py | 19 +++++++++++++++++++ 3 files changed, 39 insertions(+), 5 deletions(-) diff --git a/static/css/marketing.css b/static/css/marketing.css index 820b94f..72b6535 100644 --- a/static/css/marketing.css +++ b/static/css/marketing.css @@ -497,6 +497,9 @@ .z-\[9999\] { z-index: 9999; } + .col-span-1 { + grid-column: span 1 / span 1; + } .container { width: 100%; @media (width >= 40rem) { @@ -3669,6 +3672,11 @@ opacity: 50%; } } + .sm\:col-span-2 { + @media (width >= 40rem) { + grid-column: span 2 / span 2; + } + } .sm\:-mx-6 { @media (width >= 40rem) { margin-inline: calc(var(--spacing) * -6); @@ -4000,6 +4008,11 @@ } } } + .md\:col-span-3 { + @media (width >= 48rem) { + grid-column: span 3 / span 3; + } + } .md\:mb-2 { @media (width >= 48rem) { margin-bottom: calc(var(--spacing) * 2); diff --git a/templates/macros/bento.html b/templates/macros/bento.html index 3823e80..d4cd692 100644 --- a/templates/macros/bento.html +++ b/templates/macros/bento.html @@ -1,12 +1,14 @@ -{# Reusable bento card macro. FlexiHub style: dark navy2 surface, decorative watermark number, gradient icon corner. #} +{# Reusable bento card macro. FlexiHub style: dark navy2 surface, decorative watermark number, gradient icon corner. + `span` controls column span via a static lookup table (Tailwind's content scanner only sees literal class strings, + so dynamic `col-span-{{ span }}` would produce dead classes — the lookup keeps the utilities discoverable). #} {% macro bento_card(number, title, description, icon='✦', span='1') %} -
+{%- set span_classes = {'1': 'col-span-1', '2': 'sm:col-span-2', '3': 'sm:col-span-2 md:col-span-3'} -%} +
{{ icon }}
-

{{ title }}

-

{{ description }}

+

{{ title | safe }}

+

{{ description | safe }}

{% endmacro %} diff --git a/tests/test_marketing_landing_template.py b/tests/test_marketing_landing_template.py index 8049332..7af2430 100644 --- a/tests/test_marketing_landing_template.py +++ b/tests/test_marketing_landing_template.py @@ -255,6 +255,8 @@ def test_bento_has_6_features(): # Watermark numbers 01..06 for n in ['01', '02', '03', '04', '05', '06']: assert f'>{n}<' in body, f"Missing bento watermark number {n}" + # Card 04 must use French Q&R, not English Q&A — primary identifier check + assert 'Q&R' in body or 'Q&R' in body, "Card 04 must use French Q&R, not Q&A" def test_bento_uses_flexihub_styling(): @@ -281,3 +283,20 @@ def test_bento_uses_wcag_safe_text_on_dark(): client = app.test_client() body = client.get('/').data.decode('utf-8') assert 'text-white/70' in body, "Missing WCAG-safe /70 text opacity on dark cards" + + +def test_bento_renders_nbsp_entities_not_escaped(): + """Card 01 '95 %+' NBSP must render as a non-breaking space, not as literal ' ' text. + + Regression guard: if the bento macro stops piping description through `| safe`, + Jinja autoescape will double-escape ' ' to '&nbsp;' and users see the + raw entity. The HTML response must contain the literal '95 %+' once + (single escape), never '95&nbsp;%+'. + """ + client = app.test_client() + body = client.get('/').data.decode('utf-8') + assert '95 %+' in body, "NBSP entity should appear single-escaped in card 01" + assert '95&nbsp;' not in body, "NBSP entity must not be double-escaped (missing | safe?)" + # Q&R card title: French ampersand must survive as & in HTML, not &amp; + assert 'Q&R' in body, "Q&R title should appear single-escaped" + assert 'Q&amp;R' not in body, "Q&R title must not be double-escaped"