#!/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; }; // `querySelectorAll('[data-champ]')` rend vraiment les controles du dernier rendu : le // GUI s'en sert pour valider avant d'enregistrer, et un stub qui rend toujours [] ferait // passer cette validation sur ZERO champ — le banc mesurerait alors moins que rien. Elem.prototype.querySelectorAll = function (sel) { if (!/data-champ/.test(String(sel))) { return []; } var html = this.innerHTML || ''; var out = [], re = /<(input|select)([^>]*?)data-champ="([^"]*)"([^>]*)>/g, m; while ((m = re.exec(html)) !== null) { var attrs = m[2] + m[4]; var v = /\svalue="([^"]*)"/.exec(attrs); out.push({__champ: m[3], value: v ? v[1] : '', getAttribute: function (n) { return n === 'data-champ' ? this.__champ : null; }, classList: {add: function () {}, remove: function () {}, toggle: function () {}}, focus: function () {}}); } return out; }; 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; }, // `#detail [data-champ]` : le GUI interroge le DOCUMENT, pas l'element. Un stub qui // rendait [] ici faisait croire que le formulaire n'etait pas marque — et la // sauvegarde des serveurs refusait, silencieusement, dans le banc seul. querySelectorAll: function (sel) { var m = /^#([\w-]+)\s+(.+)$/.exec(String(sel)); if (m && __elems[m[1]]) { return __elems[m[1]].querySelectorAll(m[2]); } 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); var __postsCaptures = {}; // `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, opts) { if (opts && opts.method === 'POST') { __postsCaptures[String(url)] = JSON.parse(opts.body); } // Un POST rend la MEME charge que l'inventaire : le GUI recharge apres sauvegarde, et // lui rendre {} le ferait repartir d'un plan vide — un faux vert, pas un test. var corps = (String(url).indexOf('/api/inventaire') === 0 || (opts && opts.method === 'POST')) ? __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