- 3 radial-gradient orbs (blue 16%, cyan 7%, green 11%) + subtle 40px grid + horizontal accent line — FlexiHub cosmic background signature - H1 clamp(2.5rem,4vw,4rem) font-black with grad-text accent on 'sans risquer votre permis' tagline - Eyebrow 11px tracking-[0.18em] grad-text — 'TRANSCRIPTION IA · CONFORME LOI 25 · QUÉBEC' - Sub-headline ≤25 words declares Loi 25 compliance + 9 ordres pros - Dual CTA: Réserver une démo (primary gradient+glow) + Voir les tarifs → (ghost) — drives demo conversion + pricing self-service - Social proof microcopy above-the-fold: 5★ + 27 cabinets + Lancement printemps 2026 - Staggered tc-fade-in-up animations 0/75/150/300/400ms with animation-fill-mode: backwards (no FOIT) - 6 new tests verify H1 grad-text, dual CTA, orb opacities, social proof, staggered delays, eyebrow messaging
143 lines
5.7 KiB
Python
143 lines
5.7 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é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éserver une dé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 '27 cabinets' social proof + Lancement printemps 2026."""
|
|
client = app.test_client()
|
|
body = client.get('/').data.decode('utf-8')
|
|
assert '27 cabinets' in body, "Missing social proof '27 cabinets'"
|
|
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ÉBEC
|