feat(marketing): A-2.8b /conformite + /contact standalone pages
- /conformite page: extends base.html, page H1 with cosmic orb header, 4 pillar cards on white (mirrors landing's Conformite section content with same hedges 'Mappe' 'concue avec' 'Compatible'), 3 Loi 25 article detail cards (art. 3.3 EFVP, art. 3.5 Audit trail, art. 14 Consentement) with grad-bg article-number badges, AGPL v3 transparency CTA section with external links to Gitea + gnu.org (rel=noopener), generic CTA section - /contact page: extends base.html, 3 method cards (email, phone tel:link, postal address with <address>), 6 pre-filled mailto subject shortcuts with focus-visible WCAG 2.2 AA, pre-launch disclaimer that online form ships at launch (B-2.x). NO <form> tag - mailto only - POST returns 405 until B-2.x adds the form handler. - routes.py: add /conformite and /contact routes; preserves existing landing/tarifs/fonctionnalites views and TESTIMONIALS/FAQ data. - tests: append 13 new tests to test_marketing_secondary_pages.py covering routes 200, single H1, 4 pillars + Loi 25 articles + AGPL externals on /conformite, 3 contact methods + 6 shortcuts + 405 on POST + pre-launch note + OQLF typography on /contact. - Apply established WCAG 2.2 AA, FlexiHub, OQLF, LPC art. 219 disciplines. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -176,3 +176,143 @@ def test_secondary_pages_have_canonical_meta():
|
||||
assert 'rel="canonical"' in body
|
||||
assert 'og:type' in body
|
||||
assert 'twitter:card' in body
|
||||
|
||||
|
||||
# === /conformite ===
|
||||
|
||||
def test_conformite_route_returns_200():
|
||||
client = app.test_client()
|
||||
response = client.get('/conformite')
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_conformite_extends_base_with_h1():
|
||||
client = app.test_client()
|
||||
body = client.get('/conformite').data.decode('utf-8')
|
||||
assert '<!DOCTYPE html>' in body
|
||||
assert 'lang="fr-CA"' in body
|
||||
assert 'page-title' in body
|
||||
assert body.count('<h1') == 1, "Must have exactly one H1"
|
||||
|
||||
|
||||
def test_conformite_4_pillars_present():
|
||||
client = app.test_client()
|
||||
body = client.get('/conformite').data.decode('utf-8')
|
||||
assert 'pillars-title' in body
|
||||
# 4 pillars (same anchors as landing's conformité section)
|
||||
pillar_keywords = ['OVH Beauharnois', 'LPRPSP', 'LGGRI', 'AGPL']
|
||||
for kw in pillar_keywords:
|
||||
assert kw in body, f"Conformité pillar missing: {kw}"
|
||||
# Soft hedges (LPC art. 219)
|
||||
assert 'Mapp' in body, "Must use 'Mappé' (not 'Certifié')"
|
||||
assert 'selon le périmètre' in body or 'selon le périmètre' in body, \
|
||||
"SOC 2 must be hedged"
|
||||
|
||||
|
||||
def test_conformite_loi25_3_articles():
|
||||
client = app.test_client()
|
||||
body = client.get('/conformite').data.decode('utf-8')
|
||||
assert 'loi25-title' in body
|
||||
# 3 LPRPSP articles
|
||||
for art in ['Art. 3.3', 'Art. 3.5', 'Art. 14']:
|
||||
assert art in body, f"Conformité missing article: {art}"
|
||||
# Article topics
|
||||
for topic in ['EFVP', 'Audit trail', 'Consentement']:
|
||||
assert topic in body, f"Conformité missing Loi 25 topic: {topic}"
|
||||
|
||||
|
||||
def test_conformite_agpl_section_with_external_links():
|
||||
client = app.test_client()
|
||||
body = client.get('/conformite').data.decode('utf-8')
|
||||
assert 'agpl-title' in body
|
||||
assert 'AGPL' in body
|
||||
# External Gitea link with rel=noopener
|
||||
assert 'gitea.innova-ai.ca/Innova-AI/dictia-public' in body
|
||||
assert 'rel="noopener"' in body
|
||||
# Link to GNU AGPL v3 official text
|
||||
assert 'gnu.org/licenses/agpl-3.0' in body
|
||||
|
||||
|
||||
def test_conformite_uses_oqlf_typography():
|
||||
client = app.test_client()
|
||||
body = client.get('/conformite').data.decode('utf-8')
|
||||
assert 'Loi 25' in body
|
||||
assert 'AGPL v3' in body
|
||||
assert 'art. 3.5' in body or 'Art. 3.5' in body
|
||||
assert '&nbsp;' not in body, "Macro | safe regression"
|
||||
|
||||
|
||||
# === /contact ===
|
||||
|
||||
def test_contact_route_returns_200():
|
||||
client = app.test_client()
|
||||
response = client.get('/contact')
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_contact_route_does_not_accept_post_yet():
|
||||
"""POST handler is B-2.x territory — for now, /contact is GET-only."""
|
||||
client = app.test_client()
|
||||
response = client.post('/contact', data={'name': 'test'})
|
||||
assert response.status_code == 405, "POST must return 405 until B-2.x form handler ships"
|
||||
|
||||
|
||||
def test_contact_extends_base_with_h1():
|
||||
client = app.test_client()
|
||||
body = client.get('/contact').data.decode('utf-8')
|
||||
assert '<!DOCTYPE html>' in body
|
||||
assert 'page-title' in body
|
||||
assert body.count('<h1') == 1
|
||||
|
||||
|
||||
def test_contact_3_methods_email_phone_address():
|
||||
client = app.test_client()
|
||||
body = client.get('/contact').data.decode('utf-8')
|
||||
assert 'methods-title' in body
|
||||
# Email
|
||||
assert 'href="mailto:info@dictia.ca"' in body
|
||||
# Phone (tel: link)
|
||||
assert 'href="tel:+15819968471"' in body
|
||||
assert '(581) 996-8471' in body
|
||||
# Address (postal code)
|
||||
assert 'G0S 1K0' in body
|
||||
assert 'Inverness' in body
|
||||
assert '<address' in body
|
||||
|
||||
|
||||
def test_contact_6_subject_shortcuts():
|
||||
"""Pre-filled mailto subject shortcuts for common requests."""
|
||||
client = app.test_client()
|
||||
body = client.get('/contact').data.decode('utf-8')
|
||||
assert 'shortcuts-title' in body
|
||||
# 6 shortcut anchors with mailto + subject
|
||||
expected_subjects = [
|
||||
'Pr%C3%A9-inscription%20DictIA',
|
||||
'Devis%20multi-sites',
|
||||
'Demande%20de%20d%C3%A9monstration',
|
||||
'Dossier%20conformit%C3%A9%20Loi%2025',
|
||||
'Partenariat',
|
||||
'Question%20m%C3%A9dia',
|
||||
]
|
||||
for subj in expected_subjects:
|
||||
assert subj in body, f"Missing mailto subject shortcut: {subj}"
|
||||
# Each shortcut is a focusable link with focus-visible
|
||||
assert 'focus-visible:outline-2' in body
|
||||
assert 'focus-visible:outline-brand-b1' in body
|
||||
|
||||
|
||||
def test_contact_pre_launch_form_disclaimer():
|
||||
"""Until B-2.x ships the form handler, the page must clearly disclose mailto-only."""
|
||||
client = app.test_client()
|
||||
body = client.get('/contact').data.decode('utf-8')
|
||||
assert 'Formulaire en ligne' in body
|
||||
assert 'lancement' in body or 'printemps' in body, \
|
||||
"Must indicate online form is coming at launch"
|
||||
|
||||
|
||||
def test_contact_uses_oqlf_typography():
|
||||
client = app.test_client()
|
||||
body = client.get('/contact').data.decode('utf-8')
|
||||
assert '2 jours' in body
|
||||
assert '9 h' in body
|
||||
assert '&nbsp;' not in body
|
||||
|
||||
Reference in New Issue
Block a user