#!/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