gestion_table_tournante_libre/test/construction.long.test.js
Mathieu Benoit 9fc104d0e9 [ADD] skeleton: Capacitor web app that shows its dated version
The engine to come must be proven against oracles under node in seconds,
so the skeleton sets three test projects: node, watched; node-long; and a
real Chromium that reads computed styles in both themes. The version
lives in version.json alone; a check refuses eight divergences — technical
form, generated module, changelog head, stray version strings — and a
build guard keeps test files and fixture assets out of the bundle.

Checked: on this commit alone, 73 node tests, 7 browser tests and the
version check pass; the guard fails on a fixture joined as an asset.

--- FR ---

[ADD] squelette : application web Capacitor qui affiche sa version datée

Le moteur à venir doit s'éprouver contre des oracles sous node en
quelques secondes : le squelette pose trois projets d'épreuve — node,
sous surveillance ; node-long ; et un vrai Chromium qui lit le style
calculé dans les deux thèmes. La version vit dans version.json seul ; un
contrôle refuse huit divergences — forme technique, module engendré,
tête du changelog, chaînes de version égarées — et une garde de
construction tient fichiers et données d'épreuve hors du paquet.

Vérifié : sur ce commit seul, 73 épreuves node, 7 navigateur et le
contrôle de version passent ; la garde échoue sur une donnée jointe.

Assisted-by: Claude Opus 5.5
2026-10-05 13:13:13 -04:00

109 lines
4.6 KiB
JavaScript

// © 2026 TechnoLibre (http://www.technolibre.ca)
// License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
// Le paquet livré n'emporte rien de l'outillage d'épreuve (§ 14.9, § 14.15) :
// ni fichier de test, ni le lanceur, ni donnée d'épreuve, ni paquet d'un outil
// d'épreuve. Le test construit la page par l'API de Vite, avec la
// configuration et le mode de la livraison, dans un répertoire temporaire ;
// il relit les modules que contient le paquet, les noms d'origine de chaque
// actif, puis le chemin et le contenu de chaque fichier écrit.
import assert from 'node:assert/strict';
import { mkdtempSync, readdirSync, readFileSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join, relative, resolve, sep } from 'node:path';
import { fileURLToPath } from 'node:url';
import { build } from 'vite';
import { describe, test } from './lanceur.js';
const RACINE = fileURLToPath(new URL('..', import.meta.url));
// Paquets qui ne servent qu'aux épreuves.
const OUTILS_EPREUVE =
/[\\/]node_modules[\\/](?:vitest|@vitest|playwright|playwright-core|fast-check)[\\/]/;
// Vrai quand un module vient de l'outillage d'épreuve : l'arbre test/
// (lanceur, données d'épreuve), un fichier *.test.js rangé à côté de son
// module, un paquet d'épreuve, ou un module de Node que Vite remplace pour le
// navigateur, comme node:assert.
function moduleDEpreuve(id) {
return (
id.startsWith(join(RACINE, 'test') + sep) ||
/\.test\.[cm]?js(?:\?|$)/.test(id) ||
OUTILS_EPREUVE.test(id) ||
id.includes('__vite-browser-external')
);
}
// Traces de l'outillage dans le chemin ou le contenu d'un fichier écrit. Les
// motifs portent l'extension : le domaine emploie le mot « lanceur » (lanceur
// portable, § 8.6), et un code minifié peut écrire « x.test.call ».
const TRACES_EPREUVE = [/vitest/i, /node:assert/, /lanceur\.js/, /\.test\.[cm]?js/];
// Construit la page comme la livraison : vite.config.js, mode livraison,
// NODE_ENV=production. Vitest pose NODE_ENV=test, dont Vite déduirait une
// construction de développement — composants compilés en mode dev, paquets
// résolus sous la condition « development ».
async function construireLivraison(sortie) {
const nodeEnv = process.env.NODE_ENV;
process.env.NODE_ENV = 'production';
try {
return await build({
root: RACINE,
configFile: join(RACINE, 'vite.config.js'),
mode: 'livraison',
logLevel: 'error',
// Sans incrustation, chaque actif sort en fichier et garde ses noms
// d'origine ; incrusté en data:, il ne laisserait aucune trace lisible.
build: { outDir: sortie, emptyOutDir: true, assetsInlineLimit: 0 },
});
} finally {
if (nodeEnv === undefined) delete process.env.NODE_ENV;
else process.env.NODE_ENV = nodeEnv;
}
}
describe('construction', () => {
test("le paquet livré n'emporte aucun fichier ni module d'épreuve (§ 14.9)", async () => {
const sortie = mkdtempSync(join(tmpdir(), 'construction-'));
try {
const resultat = await construireLivraison(sortie);
const sorties = (Array.isArray(resultat) ? resultat : [resultat]).flatMap(
(r) => r.output,
);
const modules = sorties
.filter((s) => s.type === 'chunk')
.flatMap((s) => s.moduleIds);
assert.ok(
modules.some((id) => id.startsWith(join(RACINE, 'src') + sep)),
`aucun module de src/ parmi ${modules.length} modules examinés`,
);
assert.deepEqual(modules.filter(moduleDEpreuve), []);
// Un actif n'est pas un module : moduleIds ne le voit pas. Une donnée
// jointe par new URL(…, import.meta.url) n'apparaît que là.
const actifs = sorties
.filter((s) => s.type === 'asset')
.flatMap((s) => (s.originalFileNames ?? []).map((n) => resolve(RACINE, n)));
assert.ok(actifs.length > 0, `aucun actif d'origine connue parmi ${sorties.length} sorties`);
assert.deepEqual(actifs.filter(moduleDEpreuve), []);
const fichiers = readdirSync(sortie, { recursive: true, withFileTypes: true })
.filter((e) => e.isFile())
.map((e) => join(e.parentPath, e.name));
assert.ok(
fichiers.some((f) => f.endsWith('.js')),
`aucun script parmi ${fichiers.length} fichiers écrits`,
);
const traces = fichiers.flatMap((f) => {
const nom = relative(sortie, f);
const contenu = readFileSync(f, 'utf8');
return TRACES_EPREUVE.filter((m) => m.test(nom) || m.test(contenu)).map(
(m) => `${nom} : ${m}`,
);
});
assert.deepEqual(traces, []);
} finally {
rmSync(sortie, { recursive: true, force: true });
}
});
});