Files
dictia-public/tests/test_marketing_landing_template.py
Allison 54168e443b fix(marketing): trust bar accuracy + WCAG AA contrast + LPC art. 219 hygiene
- Critical (C1): align 9-ordre list with dictia.ca canonical (Barreau, CNQ,
  CPA, ChAD, OACIQ, CMQ, OIIQ, OPQ, OEQ). Drop ambiguous OPPQ; replace M/P
  short monograms with disambiguated 3-5 char abbreviations (BAR, CNQ, CPA,
  ChAD, OACIQ, CMQ, OIIQ, OPQ, OEQ). Tooltips show full disambiguating names.
- Critical (C2): raise text-brand-navy/40 -> /70 on footnote (2.69:1 -> 9:1
  contrast, passes WCAG AAA) and text-[10px] navy/50 -> text-xs navy/70 on
  monogram captions (12px minimum + AA contrast). Critical for legal
  disclosure legibility.
- Critical (C3): drop unverifiable '50 heures' specific number from
  methodology footnote — replaced with 'methodologie disponible sur demande'
  (defensible without committing to numbers we can't verify).
- Important (I1): use   before %/$ in KPI numbers per OQLF French
  typography rules + |safe filter to render entities.
- Important (I2): replace fragile substring-strip test with explicit
  forbidden-phrase list (RECONNU PAR, ENDOSSÉ PAR, etc.). Update ordre
  list test + footnote test to match new wording.
2026-04-27 17:35:43 -04:00

195 lines
8.1 KiB
Python

"""Verify the marketing landing template renders correctly."""
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
os.environ.setdefault('SQLALCHEMY_DATABASE_URI', 'sqlite:///:memory:')
os.environ.setdefault('SECRET_KEY', 'test-secret-key')
from src.app import app # noqa: E402
def test_landing_renders_template_not_inline_html():
"""GET / renders templates/marketing/landing.html (not inline HTML from Phase 1)."""
client = app.test_client()
response = client.get('/', follow_redirects=False)
assert response.status_code == 200
body = response.data.decode('utf-8')
# Phase 2 template hallmarks
assert '<!DOCTYPE html>' in body, "Missing DOCTYPE — base.html not rendering"
assert 'lang="fr-CA"' in body, "Missing lang=fr-CA"
assert '/static/css/marketing.css' in body, "Missing marketing.css link"
assert '/static/fonts/Inter-Variable.woff2' in body, "Missing Inter font preload"
assert '/static/js/alpine.min.js' in body, "Missing Alpine.js script"
def test_landing_has_canonical_url():
"""OG + canonical metadata present."""
client = app.test_client()
response = client.get('/')
body = response.data.decode('utf-8')
assert 'rel="canonical"' in body
assert 'og:type' in body
assert 'og:locale' in body and 'fr_CA' in body
assert 'twitter:card' in body
def test_landing_has_glassmorphism_header():
"""FlexiHub-style header present (navy + backdrop-blur)."""
client = app.test_client()
response = client.get('/')
body = response.data.decode('utf-8')
assert 'bg-brand-navy/[0.97]' in body or 'bg-brand-navy' in body
assert 'backdrop-blur-xl' in body
assert 'border-white/[0.045]' in body, "Missing FlexiHub-style 0.045 border opacity"
def test_landing_has_main_nav():
"""Main nav has 5 links: Fonctionnalités, Conformité, Tarifs, Blog, Contact."""
client = app.test_client()
response = client.get('/')
body = response.data.decode('utf-8')
for link in ['/fonctionnalites', '/conformite', '/tarifs', '/blog', '/contact']:
assert f'href="{link}"' in body, f"Missing nav link: {link}"
def test_landing_has_login_and_signup_ctas():
"""Login + Signup CTAs present in header."""
client = app.test_client()
response = client.get('/')
body = response.data.decode('utf-8')
assert 'href="/login"' in body
assert 'href="/signup"' in body
assert 'Démarrer' in body or 'D&eacute;marrer' in body
def test_landing_footer_has_legal_links():
"""Footer placeholder includes legal links (full footer in A-2.7)."""
client = app.test_client()
response = client.get('/')
body = response.data.decode('utf-8')
assert '/legal/conditions' in body
assert '/legal/confidentialite' in body
assert 'info@dictia.ca' in body, "Missing canonical email info@dictia.ca"
assert 'Inverness' in body, "Missing Inverness QC address"
def test_landing_no_login_redirect_for_anonymous():
"""Anonymous user GET / must see template (regression check from B-1.3)."""
client = app.test_client()
response = client.get('/', follow_redirects=False)
assert response.status_code == 200, \
f"Expected 200, got {response.status_code} — possibly login_required regression"
def test_hero_has_h1_with_grad_text_accent():
"""Hero H1 contains grad-text span on the brand tagline."""
client = app.test_client()
body = client.get('/').data.decode('utf-8')
assert 'id="hero-title"' in body, "Missing hero-title id on H1"
assert 'grad-text' in body, "Missing grad-text class somewhere"
assert 'sans risquer votre permis' in body, "Missing key brand tagline"
def test_hero_has_dual_cta():
"""Hero has both primary (Réserver une démo) and ghost (Voir les tarifs) CTAs."""
client = app.test_client()
body = client.get('/').data.decode('utf-8')
assert 'href="/contact"' in body
assert 'href="/tarifs"' in body
assert 'Réserver une démo' in body or 'R&eacute;server une d&eacute;mo' in body
assert 'Voir les tarifs' in body
def test_hero_has_cosmic_orbs_background():
"""Hero has 3 radial gradient orbs (FlexiHub signature)."""
client = app.test_client()
body = client.get('/').data.decode('utf-8')
# Look for the 3 orb opacities (16% blue, 7% cyan, 11% green)
assert 'rgba(0,98,255,0.16)' in body, "Missing primary blue orb"
assert 'rgba(0,189,216,0.07)' in body, "Missing cyan orb"
assert 'rgba(0,200,150,0.11)' in body, "Missing green accent orb"
def test_hero_has_social_proof_microcopy():
"""Hero has defensible social proof: 9 ordres pros + waitlist + launch date."""
client = app.test_client()
body = client.get('/').data.decode('utf-8')
assert '9 ordres professionnels' in body, "Missing factual ordres pros count"
assert 'Pré-inscription' in body or 'Pr&eacute;-inscription' in body, "Missing waitlist mention"
assert 'Lancement printemps 2026' in body, "Missing launch date"
def test_hero_has_staggered_animations():
"""Hero elements use tc-fade-in-up with staggered delays."""
client = app.test_client()
body = client.get('/').data.decode('utf-8')
assert 'animate-tc-fade-in-up' in body, "Missing fade-in animation"
assert 'animation-delay: 0ms' in body
assert 'animation-delay: 75ms' in body
assert 'animation-delay: 150ms' in body
assert 'animation-delay: 300ms' in body
assert 'animation-delay: 400ms' in body
assert 'animation-fill-mode: backwards' in body, \
"Missing animation-fill-mode (causes flash before delay fires)"
def test_hero_eyebrow_has_brand_messaging():
"""Hero eyebrow declares the 3 brand pillars."""
client = app.test_client()
body = client.get('/').data.decode('utf-8')
assert 'TRANSCRIPTION IA' in body
assert 'CONFORME LOI 25' in body
assert 'QU' in body # Either QUÉBEC or QU&Eacute;BEC
def test_trust_bar_has_9_ordres_pros():
"""Trust bar lists all 9 canonical Quebec ordres pros (matches dictia.ca)."""
client = app.test_client()
body = client.get('/').data.decode('utf-8')
for ordre in ['Barreau', 'Chambre des notaires', 'CPA Québec', 'ChAD', 'OACIQ', 'CMQ', 'OIIQ', 'OPQ', 'OEQ']:
assert ordre in body, f"Missing ordre pro: {ordre}"
# Note: OPPQ deliberately removed (ambiguous abbrev — replaced with OPQ for Pharmaciens)
def test_trust_bar_has_eyebrow_factual_phrasing():
"""Trust bar avoids false-endorsement language (LPC art. 219 / Competition Act s. 52)."""
client = app.test_client()
body = client.get('/').data.decode('utf-8')
assert 'MAPP' in body and '9 ORDRES PROFESSIONNELS' in body, "Missing factual eyebrow"
# Forbidden marketing phrases that imply official endorsement we don't have
forbidden = [
'CERTIFIÉ PAR',
'CERTIFIE PAR',
'ENDOSSÉ PAR',
'APPROUVÉ PAR',
'RECONNU PAR',
'AVALISÉ PAR',
]
body_upper = body.upper()
for phrase in forbidden:
assert phrase not in body_upper, f"Forbidden marketing claim found: {phrase}"
def test_trust_bar_has_4_kpis_with_grad_text():
"""Trust bar has 4 KPI metrics rendered with grad-text (NBSP per OQLF typography)."""
client = app.test_client()
body = client.get('/').data.decode('utf-8')
assert '~5 min' in body
# OQLF: non-breaking space before %/$ via &nbsp; entity
assert '95&nbsp;%+' in body, "Missing NBSP-separated 95%+ KPI"
assert '0&nbsp;$' in body, "Missing NBSP-separated 0$ KPI"
assert '100&nbsp;%' in body, "Missing NBSP-separated 100% KPI"
# Verify grad-text on KPI numbers
assert 'grad-text mb-2' in body, "Missing grad-text on KPI numbers"
def test_trust_bar_has_methodology_footnote():
"""95%+ claim has a defensible methodology footnote (LPC art. 219 hygiene)."""
client = app.test_client()
body = client.get('/').data.decode('utf-8')
# Verifiable wording: no specific hour count, methodology available on request
assert 'm&eacute;thodologie disponible sur demande' in body or 'méthodologie disponible sur demande' in body
assert 'audio professionnel qu&eacute;b&eacute;cois' in body or 'audio professionnel québécois' in body
assert 'info@dictia.ca' in body