feat(marketing): pricing 3 forfaits + ROI calculator Alpine.js

This commit is contained in:
Allison
2026-04-27 18:50:33 -04:00
parent b87f35ea4a
commit 0ae4053faa
5 changed files with 270 additions and 0 deletions

View File

@@ -300,3 +300,106 @@ def test_bento_renders_nbsp_entities_not_escaped():
# Q&R card title: French ampersand must survive as & in HTML, not &
assert 'Q&R' in body, "Q&R title should appear single-escaped"
assert 'Q&R' not in body, "Q&R title must not be double-escaped"
def test_pricing_section_present():
"""Pricing section is present after bento section, with eyebrow + H2 + tax disclaimer."""
client = app.test_client()
body = client.get('/').data.decode('utf-8')
assert 'pricing-title' in body, "Missing pricing section anchor"
assert 'Choisissez votre formule' in body, "Missing pricing H2"
# Tax disclaimer must be visible (LPC art. 219 — total cost transparency)
assert 'TPS' in body and 'TVQ' in body, "Missing tax disclaimer (TPS/TVQ)"
def test_pricing_3_tiers_with_canonical_amounts():
"""Pricing has 3 tiers: DictIA 8 (3450/173), DictIA 16 (5750/201), DictIA Cloud (0/369)."""
client = app.test_client()
body = client.get('/').data.decode('utf-8')
# Names
for name in ['DictIA 8', 'DictIA 16', 'DictIA Cloud']:
assert name in body, f"Missing pricing tier: {name}"
# Canonical prices with NBSP per OQLF
assert '3 450 $' in body, "Missing DictIA 8 setup price"
assert '173 $' in body, "Missing DictIA 8 monthly price"
assert '5 750 $' in body, "Missing DictIA 16 setup price"
assert '201 $' in body, "Missing DictIA 16 monthly price"
assert '369 $' in body, "Missing DictIA Cloud monthly price (canonical 369$)"
def test_pricing_recommended_tier_is_dictia_16():
"""DictIA 16 is the visually-recommended tier (RECOMMANDÉ badge + grad-bg frame)."""
client = app.test_client()
body = client.get('/').data.decode('utf-8')
assert 'RECOMMAND' in body, "Missing RECOMMANDÉ badge"
# The recommended tier wraps in grad-bg p-[1.5px] rounded-[20px] FlexiHub style
assert 'grad-bg p-[1.5px] rounded-[20px]' in body, "Missing FlexiHub gradient frame on recommended tier"
def test_pricing_cta_uses_reserver_pre_launch_wording():
"""CTAs say 'Réserver' not 'Choisir' — pre-launch LPC art. 219 hygiene."""
client = app.test_client()
body = client.get('/').data.decode('utf-8')
for slug in ['dictia-8', 'dictia-16', 'dictia-cloud']:
assert f'href="/checkout/{slug}"' in body, f"Missing checkout link for {slug}"
assert 'Réserver DictIA 8' in body or 'Réserver DictIA 8' in body, "CTA must use 'Réserver' wording (pre-launch)"
def test_pricing_features_use_safe_filter_no_double_escape():
"""Pricing card features piped through | safe — ' ' must render single-escaped, not double."""
client = app.test_client()
body = client.get('/').data.decode('utf-8')
# GPU sizes use NBSP
assert 'GPU 8 Go RTX' in body, "GPU 8 Go feature missing or NBSP double-escaped"
assert 'GPU 16 Go RTX' in body, "GPU 16 Go feature missing or NBSP double-escaped"
# Q&R card must use French Q&R, not English Q&A
assert 'Q&R' in body, "DictIA 16 must mention Q&R (French), not Q&A (English)"
assert 'Q&A' not in body, "Must use French Q&R consistently — no English Q&A"
# Loi 25 with NBSP
assert 'Conforme Loi 25' in body, "Conforme Loi 25 must use NBSP"
# SLA must be hedged ('visé') not absolute claim
assert 'SLA visé 99,9' in body, "SLA must be hedged 'visé' (pre-launch LPC art. 219 hygiene)"
# Negative: NO double-escape
assert ' ' not in body, "NBSP must not be double-escaped — | safe missing on pricing macro?"
def test_pricing_uses_wcag_safe_text_on_white():
"""Pricing card text uses text-brand-navy/70 or /80 minimum (WCAG AA on white)."""
client = app.test_client()
body = client.get('/').data.decode('utf-8')
# No regression to weak opacities like /40 or /50 in pricing area
assert 'text-brand-navy/70' in body
# The features list uses /80 in our impl
assert 'text-brand-navy/80' in body, "Feature text should use /80 for WCAG AA"
def test_roi_calculator_present_with_alpine_bindings():
"""ROI calculator section present with Alpine.js bindings + transparent hypotheses footnote."""
client = app.test_client()
body = client.get('/').data.decode('utf-8')
assert 'CALCULATEUR ROI' in body
assert 'roi-title' in body, "ROI calculator must have aria-labelledby anchor"
assert 'x-data="roiCalculator()"' in body
# Three sliders with x-model.number for type coercion
assert 'x-model.number="users"' in body
assert 'x-model.number="hours"' in body
assert 'x-model.number="rate"' in body
# Live output bindings
assert 'x-text="savings' in body
assert 'x-text="\'Payback' in body
# Transparent hypothesis footnote — LPC art. 219 hygiene
assert '80 %' in body and 'jours ouvrables' in body, "Missing transparent hypothesis footnote"
# Sliders accessible (aria-label on each input)
assert 'aria-label="Nombre d' in body
def test_roi_calculator_script_loaded():
"""roi_calculator.js loaded via {% block scripts %} (deferred after Alpine.js)."""
client = app.test_client()
body = client.get('/').data.decode('utf-8')
assert '/static/js/roi_calculator.js' in body, "ROI script must be referenced"
# Must come AFTER alpine.min.js in the document order
alpine_pos = body.find('alpine.min.js')
roi_pos = body.find('roi_calculator.js')
assert alpine_pos != -1 and roi_pos != -1
assert alpine_pos < roi_pos, "Alpine.js must load before roi_calculator.js"