#!/usr/bin/env python3 """Le GUI REND-IL ? Chaque vue est dessinee pour de vrai, dans un DOM simule. CE QUE `verifier_gui.py` NE POUVAIT PAS VOIR (2026-09-08). Il passe le JS a `node --check` : la syntaxe, donc, et rien d'autre. Le formulaire genere des bases lisait `data.schema` — or il n'existe aucun `data` global dans cette page ; c'est une const locale de `charger()`. Syntaxe parfaite, `ReferenceError` a l'ouverture. Pire dans `sauvegarderBases`, ou `const data` est declare PLUS BAS dans la meme fonction : zone morte temporelle, l'enregistrement jetait avant meme d'envoyer. Le defaut a survecu a une verification sous node parce que le banc APPELAIT la fonction en lui passant `data` — il reproduisait la fonction, pas sa PORTEE. Une garantie qu'on n'a jamais vue echouer n'est pas une garantie. Ce banc-ci charge le JS entier, tel que la page le recoit, dans un DOM simule, appelle `charger()` sur la VRAIE reponse de l'API, puis dessine CHAQUE vue. Toute reference a un identifiant qui n'existe pas leve, et le test echoue. Ce qu'il ne fait PAS : juger l'apparence. Il dit que ca rend, pas que c'est beau. """ from __future__ import annotations import json import re import shutil import subprocess import sys import tempfile import unittest from pathlib import Path RACINE = Path(__file__).resolve().parents[2] sys.path.insert(0, str(RACINE / "scripts")) import inventory_gui as g # noqa: E402 # Un DOM juste assez reel : tout element repond, retient son innerHTML, et n'invente # rien. Un `getElementById` qui rendrait `null` masquerait les fautes qu'on cherche. BANC = r""" 'use strict'; function Elem(id) { this.id = id; this.innerHTML = ''; this.textContent = ''; this.value = ''; this.hidden = false; this.disabled = false; this.title = ''; this.className = ''; this.style = {}; this.dataset = {}; this.children = []; this.classList = {add(){}, remove(){}, toggle(){}, contains(){ return false; }}; } Elem.prototype.appendChild = function (e) { this.children.push(e); return e; }; Elem.prototype.removeChild = function (e) { return e; }; Elem.prototype.setAttribute = function () {}; Elem.prototype.getAttribute = function () { return ''; }; Elem.prototype.addEventListener = function () {}; Elem.prototype.querySelector = function () { return null; }; Elem.prototype.querySelectorAll = function () { return []; }; Elem.prototype.focus = function () {}; Elem.prototype.remove = function () {}; Elem.prototype.closest = function () { return null; }; Elem.prototype.scrollIntoView = function () {}; var __elems = {}; var document = { getElementById: function (id) { return __elems[id] || (__elems[id] = new Elem(id)); }, createElement: function () { return new Elem('cree'); }, querySelector: function () { return null; }, querySelectorAll: function () { return []; }, addEventListener: function () {}, body: new Elem('body'), documentElement: new Elem('html'), }; var window = {addEventListener: function () {}, location: {href: '', reload: function () {}}, matchMedia: function () { return {matches: false, addEventListener: function () {}}; }}; var navigator = {clipboard: {writeText: function () { return Promise.resolve(); }}}; function MutationObserver(f) { this.observe = function () {}; this.disconnect = function () {}; } function requestAnimationFrame(f) { return 0; } function cancelAnimationFrame() {} function EventSource() { this.addEventListener = function () {}; this.close = function () {}; } function alert() {} function confirm() { return false; } function prompt() { return null; } var localStorage = {getItem: function () { return null; }, setItem: function () {}, removeItem: function () {}}; var __PAYLOAD__ = JSON.parse(process.env.PAYLOAD); // `fetch` rend la VRAIE reponse de /api/inventaire ; les autres routes rendent un objet // vide plutot que de jeter, pour que les sondes de fond ne masquent pas l'erreur cherchee. function fetch(url) { var corps = String(url).indexOf('/api/inventaire') === 0 ? __PAYLOAD__ : {}; return Promise.resolve({ok: true, status: 200, json: function () { return Promise.resolve(corps); }, text: function () { return Promise.resolve(''); }}); } var __erreurs = []; function __essayer(quoi, f) { try { f(); } catch (e) { __erreurs.push(quoi + ' : ' + e); } } """ APRES = r""" // --- L'EPREUVE ----------------------------------------------------------------- charger().then(function () { ['serveurs', 'applications', 'bases', 'domaines', 'nomenclature', 'integrations', 'flux', 'couches', 'reseau'].forEach(function (v) { __essayer('setVue(' + v + ')', function () { setVue(v); }); }); // Les vues a maitre-detail ne montrent leur formulaire QU'UNE FOIS une entree // choisie : dessiner la grille seule ne prouve rien du formulaire genere. __essayer('bases + selection', function () { setVue('bases'); if (typeof bdApplis !== 'undefined' && bdApplis.length) { choisirBase('bd:0'); } if (typeof bdServeurs !== 'undefined' && bdServeurs.length) { choisirBase('srv:0'); } }); __essayer('nomenclature + selection', function () { setVue('nomenclature'); var f = Object.keys(nomEdit.fonctions || {}); var c = Object.keys(nomEdit.categories || {}); if (f.length) { choisirNom('fon:' + f[0]); } if (c.length) { choisirNom('cat:' + c[0]); } }); __essayer('domaines + selection', function () { setVue('domaines'); if (domaines.length) choisirDomaine(0); }); __essayer('applications + selection', function () { setVue('applications'); if (applications.length) ouvrirApp(applications[0].id); }); console.log('__RESULTAT__' + JSON.stringify(__erreurs)); }).catch(function (e) { console.log('__RESULTAT__' + JSON.stringify(['charger() : ' + (e && e.stack || e)])); }); """ class TestRenduGui(unittest.TestCase): """Chaque vue du GUI se dessine sans lever.""" @classmethod def setUpClass(cls): if not shutil.which("node"): raise unittest.SkipTest("node absent") html = g.HTML.replace("__JETON__", "TEST") blocs = re.findall(r"", html, re.S) assert blocs, "aucun bloc