From f10e3d12e6ff676e834030c7e4cd67e60b65e920 Mon Sep 17 00:00:00 2001 From: bruno Date: Wed, 28 Oct 2026 15:59:18 -0400 Subject: [PATCH] recherche: tests du theme de la page (defaut 4) --- tests/test_recherche_theme.py | 167 ++++++++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 tests/test_recherche_theme.py diff --git a/tests/test_recherche_theme.py b/tests/test_recherche_theme.py new file mode 100644 index 0000000..4b3e8c1 --- /dev/null +++ b/tests/test_recherche_theme.py @@ -0,0 +1,167 @@ +"""Tests — thème de la page /recherche ObsiGate (défaut 4). + +Mécanisme audité (ne pas réinventer) : le thème ObsiGate n'est PAS une +préférence serveur. La SPA le lit dans localStorage au démarrage — +``frontend/js/ui.js`` ``initTheme`` (mode seul, pour la première +peinture) puis ``frontend/js/sync.js`` ``init`` -> ``themes.js`` +``initThemes`` (clé + mode, palette complète en variables inline sur +``:root``). Clés : ``obsigate-theme`` (clé, ex. ``'spark'``, +``'dracula'``, ou historiquement un mode) et ``obsigate-theme-mode`` +(``'dark'`` | ``'light'`` | ``'high-contrast'`` | ``'sepia'``). + +La page /recherche, servie hors SPA avec un ``data-theme="dark"`` en +dur et aucun script de thème, restait donc sombre quel que soit le +réglage. Correctif verrouillé ici, en deux étapes dans +``frontend/recherche.html`` (aucun fichier partagé touché : +``js/recherche.js`` reste identique sur les 4 hôtes, ``draco-barre.css`` +reste le fichier commun vendangé, ``backend/main.py`` et sa CSP +strictes sont inchangés) : + +- étape 1 : script synchrone en tête, avant les feuilles — pose + ``data-theme`` = mode persisté avant la première peinture ; +- étape 2 : module différé — ``initThemes()`` de ``themes.js`` pose la + palette complète (16 thèmes, modes générés, thèmes personnalisés). + +``frontend/recherche.css`` décline la barre pour les modes que +``draco-barre.css`` ne connaît pas et branche bordures/accents sur +``var(--border)`` / ``var(--accent)`` (anciennes valeurs en repli). +""" + +from __future__ import annotations + +import re +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent +FRONTEND = ROOT / "frontend" +MODES = ["dark", "light", "high-contrast", "sepia"] +NONCE = "TESTNONCE1234567890" + + +def _html() -> str: + return (FRONTEND / "recherche.html").read_text(encoding="utf-8") + + +def _css() -> str: + return (FRONTEND / "recherche.css").read_text(encoding="utf-8") + + +def _inline_scripts(html: str) -> list[str]: + """Contenu des scripts inline exécutables (sans src).""" + return re.findall( + r"(.*?)", html, re.DOTALL + ) + + +def test_etape1_avant_les_feuilles_et_mode_valide(): + html = _html() + scripts = _inline_scripts(html) + assert scripts, "aucun script inline dans recherche.html" + etape1 = scripts[0] + assert "obsigate-theme-mode" in etape1 + assert "obsigate-theme" in etape1 + assert "setAttribute('data-theme'" in etape1 or 'setAttribute("data-theme"' in etape1 + for mode in MODES: + assert f"'{mode}'" in etape1, f"mode {mode} absent de l'étape 1" + # Le script étape 1 précède la première feuille de style. + assert html.index("', html, re.DOTALL + ) + assert len(modules) == 1, "exactement un module inline attendu" + etape2 = modules[0] + assert "import('/static/js/themes.js')" in etape2 + assert "initThemes" in etape2 + # Garde historique : 'obsigate-theme' valant encore un mode sans + # 'obsigate-theme-mode' -> on garde le mode de l'étape 1. + assert "savedMode === null" in etape2 + assert "saved === 'dark'" in etape2 and "saved === 'light'" in etape2 + # Échec de chargement silencieux : la page garde le mode étape 1. + assert ".catch(function () {})" in etape2 + # Le module est après recherche.js (différé : il tourne avant + # l'init DOMContentLoaded de recherche.js, sans le modifier). + assert html.index("/static/js/recherche.js") < html.index( + '', + 1, + ) + out = inject_csp_nonce(html, NONCE) + assert not re.findall(r"' in html + # Aucune police externe ajoutée : le repli système imposé par la + # CSP stricte de /recherche (font-src 'self' data:) est inchangé. + assert "fonts.googleapis.com" not in html + assert "fonts.gstatic.com" not in html + # Ordre des feuilles inchangé (recherche.css après la barre). + assert html.index("draco-barre.css") < html.index("recherche.css")