[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
|
|
|
|
// © 2026 TechnoLibre (http://www.technolibre.ca)
|
|
|
|
|
|
// License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
|
|
|
|
|
|
|
|
|
|
|
// Épreuves de la version datée (§ 18). Ce fichier cite des versions en clair
|
|
|
|
|
|
// par nécessité : le balayage du point 6 du § 18.5 l'exclut, et lui seul.
|
|
|
|
|
|
import assert from 'node:assert/strict';
|
|
|
|
|
|
import { spawnSync } from 'node:child_process';
|
|
|
|
|
|
import {
|
|
|
|
|
|
copyFileSync,
|
|
|
|
|
|
mkdirSync,
|
|
|
|
|
|
mkdtempSync,
|
|
|
|
|
|
readFileSync,
|
|
|
|
|
|
rmSync,
|
|
|
|
|
|
unlinkSync,
|
|
|
|
|
|
writeFileSync,
|
|
|
|
|
|
} from 'node:fs';
|
|
|
|
|
|
import { tmpdir } from 'node:os';
|
|
|
|
|
|
import { dirname, join } from 'node:path';
|
[ADD] interface: application shell — banner, sections, theme, about
The window showed only a banner; it now holds the application. App mounts
the session, the facade and the generation, settles the working folder
and lists its events before it reports ready, re-reading the list once a
question is answered or a refused determination succeeds. The banner
carries version, mode and the event list command; an open event shows its
sections in their first form; the theme follows the system at each start
and the operator's choice lasts the session; À propos names the build.
Errors are collected for a report, and Electron removes the session's
locks at exit.
Checked: 2189 node, 193 browser, 60 node-long; the Electron start check.
--- FR ---
[ADD] interface : coquille — bandeau, sections, thème, À propos
La fenêtre ne montrait qu'un bandeau ; elle tient désormais l'application.
App monte la séance, la façade et la génération, règle le dossier de
travail et liste ses événements avant de se dire prête, et relit la liste
une fois une question répondue ou une détermination refusée réussie. Le
bandeau porte version, mode et la commande de la liste ; un événement
ouvert montre ses sections dans leur première forme ; le thème suit le
système à chaque démarrage et le choix de l'opérateur vaut pour la séance ;
À propos nomme la construction. Les erreurs se collectent pour un
signalement, et Electron efface à la sortie les verrous de la séance.
Vérifié : 2189 node, 193 navigateur, 60 node-long ; démarrage d'Electron.
Assisted-by: Claude Opus 5.5
2026-10-07 07:22:05 -04:00
|
|
|
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
[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
|
|
|
|
import { describe, test } from '../test/lanceur.js';
|
|
|
|
|
|
import {
|
|
|
|
|
|
controler,
|
|
|
|
|
|
dateCivileLocale,
|
|
|
|
|
|
dateEnLettres,
|
|
|
|
|
|
deriver,
|
|
|
|
|
|
engendrer,
|
|
|
|
|
|
engendrerModule,
|
|
|
|
|
|
redecomposer,
|
|
|
|
|
|
} from './version.js';
|
|
|
|
|
|
|
|
|
|
|
|
const RACINE = fileURLToPath(new URL('..', import.meta.url));
|
|
|
|
|
|
const SCRIPT = fileURLToPath(new URL('./version.js', import.meta.url));
|
|
|
|
|
|
|
|
|
|
|
|
const UN_JOUR_MS = 86_400_000;
|
|
|
|
|
|
|
|
|
|
|
|
// Les jours d'une année, énumérés par le calendrier de Date.UTC : l'oracle ne
|
|
|
|
|
|
// partage rien avec la règle des mois et des années bissextiles du module
|
|
|
|
|
|
// éprouvé.
|
|
|
|
|
|
function joursDe(annee) {
|
|
|
|
|
|
const jours = [];
|
|
|
|
|
|
for (
|
|
|
|
|
|
let instant = Date.UTC(annee, 0, 1);
|
|
|
|
|
|
new Date(instant).getUTCFullYear() === annee;
|
|
|
|
|
|
instant += UN_JOUR_MS
|
|
|
|
|
|
) {
|
|
|
|
|
|
const date = new Date(instant);
|
|
|
|
|
|
jours.push({ annee, mois: date.getUTCMonth() + 1, jour: date.getUTCDate() });
|
|
|
|
|
|
}
|
|
|
|
|
|
return jours;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Dernier jour du mois, lu dans le même calendrier : le jour 0 d'un mois est
|
|
|
|
|
|
// la veille de son premier jour.
|
|
|
|
|
|
function dernierJour(annee, mois) {
|
|
|
|
|
|
return new Date(Date.UTC(annee, mois, 0)).getUTCDate();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const deux = (n) => String(n).padStart(2, '0');
|
|
|
|
|
|
const forme = ({ annee, mois, jour }, rang) =>
|
|
|
|
|
|
`${annee}.${deux(mois)}.${deux(jour)}.${deux(rang)}`;
|
|
|
|
|
|
const champs = (technique) => technique.split('.').map(Number);
|
|
|
|
|
|
|
|
|
|
|
|
// Vrai quand a précède strictement b, comparés champ à champ comme des
|
|
|
|
|
|
// entiers.
|
|
|
|
|
|
function precede(a, b) {
|
|
|
|
|
|
for (let i = 0; i < a.length; i += 1) {
|
|
|
|
|
|
if (a[i] !== b[i]) return a[i] < b[i];
|
|
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const MOIS = [
|
|
|
|
|
|
'janvier', 'février', 'mars', 'avril', 'mai', 'juin',
|
|
|
|
|
|
'juillet', 'août', 'septembre', 'octobre', 'novembre', 'décembre',
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
describe('version : dérivation (§ 18.1, § 18.2)', () => {
|
|
|
|
|
|
test('une version affichée donne ses formes technique et Windows et le nom du livrable', () => {
|
|
|
|
|
|
assert.deepEqual(deriver('2026.10.05.01'), {
|
|
|
|
|
|
affichee: '2026.10.05.01',
|
|
|
|
|
|
technique: '2026.1005.1',
|
|
|
|
|
|
windows: '2026.1005.1.0',
|
|
|
|
|
|
nomFichier: 'gestion_table_tournante_libre_v2026_10_05_01.exe',
|
|
|
|
|
|
annee: 2026,
|
|
|
|
|
|
mois: 10,
|
|
|
|
|
|
jour: 5,
|
|
|
|
|
|
rang: 1,
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test('le mois garde son zéro de tête dans la forme affichée et le perd dans la technique', () => {
|
|
|
|
|
|
assert.equal(deriver('2026.01.05.01').technique, '2026.105.1');
|
|
|
|
|
|
assert.equal(redecomposer('2026.105.1'), '2026.01.05.01');
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test('la forme technique croît avec la date, champ à champ', () => {
|
|
|
|
|
|
const suite = ['2026.01.05.01', '2026.10.05.01', '2026.12.31.01', '2027.01.05.01'].map(
|
|
|
|
|
|
(version) => champs(deriver(version).technique),
|
|
|
|
|
|
);
|
|
|
|
|
|
assert.deepEqual(suite, [
|
|
|
|
|
|
[2026, 105, 1],
|
|
|
|
|
|
[2026, 1005, 1],
|
|
|
|
|
|
[2026, 1231, 1],
|
|
|
|
|
|
[2027, 105, 1],
|
|
|
|
|
|
]);
|
|
|
|
|
|
for (let i = 1; i < suite.length; i += 1) {
|
|
|
|
|
|
assert.ok(precede(suite[i - 1], suite[i]), `${suite[i - 1]} < ${suite[i]}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
assert.ok(
|
|
|
|
|
|
precede(champs(deriver('2026.10.05.01').technique), champs(deriver('2026.10.05.02').technique)),
|
|
|
|
|
|
);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test('aller-retour sur chaque jour de 2026 et de 2028, sur les 99 rangs', () => {
|
|
|
|
|
|
const jours = [...joursDe(2026), ...joursDe(2028)];
|
|
|
|
|
|
assert.equal(jours.length, 365 + 366);
|
|
|
|
|
|
const affichees = [];
|
|
|
|
|
|
const noms = [];
|
|
|
|
|
|
let veille = null;
|
|
|
|
|
|
jours.forEach((date, i) => {
|
|
|
|
|
|
const rang = 1 + (i % 99);
|
|
|
|
|
|
const version = forme(date, rang);
|
|
|
|
|
|
const derivee = deriver(version);
|
|
|
|
|
|
assert.equal(derivee.technique, `${date.annee}.${date.mois * 100 + date.jour}.${rang}`);
|
|
|
|
|
|
assert.equal(derivee.windows, `${derivee.technique}.0`);
|
|
|
|
|
|
assert.equal(
|
|
|
|
|
|
derivee.nomFichier,
|
|
|
|
|
|
`gestion_table_tournante_libre_v${version.replaceAll('.', '_')}.exe`,
|
|
|
|
|
|
);
|
|
|
|
|
|
assert.equal(redecomposer(derivee.technique), version);
|
|
|
|
|
|
// Chaque champ de la ressource de version Windows tient sur 16 bits.
|
|
|
|
|
|
for (const champ of champs(derivee.windows)) assert.ok(champ <= 0xffff, derivee.windows);
|
|
|
|
|
|
const jour = champs(derivee.technique).slice(0, 2);
|
|
|
|
|
|
if (veille !== null) assert.ok(precede(veille, jour), `${veille} < ${jour}`);
|
|
|
|
|
|
veille = jour;
|
|
|
|
|
|
affichees.push(forme(date, 1));
|
|
|
|
|
|
noms.push(deriver(forme(date, 1)).nomFichier);
|
|
|
|
|
|
});
|
|
|
|
|
|
// L'ordre alphabétique des formes affichées et des noms de fichiers est
|
|
|
|
|
|
// l'ordre chronologique.
|
|
|
|
|
|
assert.deepEqual([...affichees].sort(), affichees);
|
|
|
|
|
|
assert.deepEqual([...noms].sort(), noms);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test('le dernier jour de chaque mois passe, le lendemain est refusé', () => {
|
|
|
|
|
|
let mois = 0;
|
|
|
|
|
|
for (const annee of [2026, 2028]) {
|
|
|
|
|
|
for (let m = 1; m <= 12; m += 1) {
|
|
|
|
|
|
const dernier = dernierJour(annee, m);
|
|
|
|
|
|
assert.doesNotThrow(() => deriver(forme({ annee, mois: m, jour: dernier }, 1)));
|
|
|
|
|
|
assert.throws(() => deriver(forme({ annee, mois: m, jour: dernier + 1 }, 1)), /jour/);
|
|
|
|
|
|
assert.throws(() => deriver(forme({ annee, mois: m, jour: 0 }, 1)), /jour/);
|
|
|
|
|
|
mois += 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
assert.equal(mois, 24);
|
|
|
|
|
|
assert.equal(dernierJour(2026, 2), 28);
|
|
|
|
|
|
assert.equal(dernierJour(2028, 2), 29);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test('refuse une forme non conforme, une date invalide, un rang hors de 01 à 99', () => {
|
|
|
|
|
|
const refus = [
|
|
|
|
|
|
['2026.1.5.1', /forme/],
|
|
|
|
|
|
['2026.13.01.01', /mois/],
|
|
|
|
|
|
['2026.00.10.01', /mois/],
|
|
|
|
|
|
['2026.02.30.01', /jour/],
|
|
|
|
|
|
['2026.10.05.00', /rang/],
|
|
|
|
|
|
['2026.10.05.100', /rang.*99/],
|
|
|
|
|
|
['2026.10.05.001', /forme/], // zéro de tête : une faute de forme, pas de plafond
|
|
|
|
|
|
['2026.13.45.100', /mois/], // la date se contrôle avant le plafond du rang
|
|
|
|
|
|
['2026.10.05.1', /forme/],
|
|
|
|
|
|
['2026.10.05', /forme/],
|
|
|
|
|
|
['2026.10.05.01 ', /forme/],
|
|
|
|
|
|
['v2026.10.05.01', /forme/],
|
|
|
|
|
|
['2026-10-05-01', /forme/],
|
|
|
|
|
|
['0999.10.05.01', /année/],
|
|
|
|
|
|
];
|
|
|
|
|
|
for (const [version, motif] of refus) {
|
|
|
|
|
|
assert.throws(() => deriver(version), motif, version);
|
|
|
|
|
|
}
|
|
|
|
|
|
assert.throws(() => deriver(20261005), TypeError);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test('accepte le rang 99', () => {
|
|
|
|
|
|
assert.equal(deriver('2026.10.05.99').technique, '2026.1005.99');
|
|
|
|
|
|
assert.equal(redecomposer('2026.1005.99'), '2026.10.05.99');
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test('redecomposer refuse ce qui ne désigne pas exactement une version', () => {
|
|
|
|
|
|
const refus = [
|
|
|
|
|
|
['2026.0105.1', /forme/], // zéro de tête
|
|
|
|
|
|
['2026.1005.01', /forme/], // zéro de tête
|
|
|
|
|
|
['2026.1005.0', /forme/], // rang 0
|
|
|
|
|
|
['2026.1005.100', /forme/], // rang au-delà de 99
|
|
|
|
|
|
['2026.99.1', /forme/], // mois et jour sur deux chiffres
|
|
|
|
|
|
['2026.1005', /forme/], // deux champs
|
|
|
|
|
|
['2026.10.05.01', /forme/], // forme affichée
|
|
|
|
|
|
['26.1005.1', /forme/], // année sur deux chiffres
|
|
|
|
|
|
['2026.1305.1', /mois/], // mois 13
|
|
|
|
|
|
['2026.230.1', /jour/], // 30 février
|
|
|
|
|
|
['2026.1000.1', /jour/], // jour 0
|
|
|
|
|
|
];
|
|
|
|
|
|
for (const [technique, motif] of refus) {
|
|
|
|
|
|
assert.throws(() => redecomposer(technique), motif, technique);
|
|
|
|
|
|
}
|
|
|
|
|
|
assert.throws(() => redecomposer(2026.1005), TypeError);
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
describe('version : date en lettres (§ 18.4)', () => {
|
|
|
|
|
|
test('écrit le jour, le mois en lettres et l’année ; le premier du mois est « 1er »', () => {
|
|
|
|
|
|
assert.equal(dateEnLettres({ annee: 2026, mois: 10, jour: 5 }), '5 octobre 2026');
|
|
|
|
|
|
assert.equal(dateEnLettres({ annee: 2027, mois: 1, jour: 1 }), '1er janvier 2027');
|
|
|
|
|
|
assert.equal(dateEnLettres({ annee: 2028, mois: 2, jour: 29 }), '29 février 2028');
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test('nomme les douze mois', () => {
|
|
|
|
|
|
assert.deepEqual(
|
|
|
|
|
|
MOIS.map((_, i) => dateEnLettres({ annee: 2026, mois: i + 1, jour: 2 })),
|
|
|
|
|
|
MOIS.map((nom) => `2 ${nom} 2026`),
|
|
|
|
|
|
);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test('refuse une date hors du calendrier', () => {
|
|
|
|
|
|
assert.throws(() => dateEnLettres({ annee: 2026, mois: 2, jour: 29 }), /jour/);
|
|
|
|
|
|
assert.throws(() => dateEnLettres({ annee: 2026, mois: 13, jour: 1 }), /mois/);
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// --- Arbres d'épreuve -------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
const VERSION = '2026.10.05.01';
|
|
|
|
|
|
const TECHNIQUE = '2026.1005.1';
|
|
|
|
|
|
const DATE = '2026-10-05';
|
|
|
|
|
|
|
|
|
|
|
|
// Le module engendré pour VERSION, octet pour octet.
|
|
|
|
|
|
const MODULE = [
|
|
|
|
|
|
'// © 2026 TechnoLibre (http://www.technolibre.ca)',
|
|
|
|
|
|
'// License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)',
|
|
|
|
|
|
'',
|
|
|
|
|
|
'// Engendré par scripts/version.js depuis version.json — ne pas modifier.',
|
|
|
|
|
|
'export const VERSION = Object.freeze({',
|
|
|
|
|
|
" affichee: '2026.10.05.01',",
|
|
|
|
|
|
" technique: '2026.1005.1',",
|
[ADD] interface: application shell — banner, sections, theme, about
The window showed only a banner; it now holds the application. App mounts
the session, the facade and the generation, settles the working folder
and lists its events before it reports ready, re-reading the list once a
question is answered or a refused determination succeeds. The banner
carries version, mode and the event list command; an open event shows its
sections in their first form; the theme follows the system at each start
and the operator's choice lasts the session; À propos names the build.
Errors are collected for a report, and Electron removes the session's
locks at exit.
Checked: 2189 node, 193 browser, 60 node-long; the Electron start check.
--- FR ---
[ADD] interface : coquille — bandeau, sections, thème, À propos
La fenêtre ne montrait qu'un bandeau ; elle tient désormais l'application.
App monte la séance, la façade et la génération, règle le dossier de
travail et liste ses événements avant de se dire prête, et relit la liste
une fois une question répondue ou une détermination refusée réussie. Le
bandeau porte version, mode et la commande de la liste ; un événement
ouvert montre ses sections dans leur première forme ; le thème suit le
système à chaque démarrage et le choix de l'opérateur vaut pour la séance ;
À propos nomme la construction. Les erreurs se collectent pour un
signalement, et Electron efface à la sortie les verrous de la séance.
Vérifié : 2189 node, 193 navigateur, 60 node-long ; démarrage d'Electron.
Assisted-by: Claude Opus 5.5
2026-10-07 07:22:05 -04:00
|
|
|
|
" windows: '2026.1005.1.0',",
|
[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
|
|
|
|
'});',
|
|
|
|
|
|
'',
|
|
|
|
|
|
].join('\n');
|
|
|
|
|
|
|
|
|
|
|
|
const TITRE_CHANGELOG = '# Changelog — Gestion table tournante Libre';
|
|
|
|
|
|
|
|
|
|
|
|
// Une section de changelog complète : titre, une rubrique, les deux lignes
|
|
|
|
|
|
// obligatoires.
|
|
|
|
|
|
const section = (titre) =>
|
|
|
|
|
|
[
|
|
|
|
|
|
`## ${titre}`,
|
|
|
|
|
|
'',
|
|
|
|
|
|
'**Ce qui change pour vous**',
|
|
|
|
|
|
'- Le bandeau affiche la version.',
|
|
|
|
|
|
'',
|
|
|
|
|
|
'**Version de format des fichiers** : inchangée.',
|
|
|
|
|
|
'',
|
|
|
|
|
|
'**En remplaçant** : rien à faire.',
|
|
|
|
|
|
'',
|
|
|
|
|
|
].join('\n');
|
|
|
|
|
|
|
|
|
|
|
|
const changelog = (...titres) => [TITRE_CHANGELOG, '', ...titres.map(section)].join('\n');
|
|
|
|
|
|
|
|
|
|
|
|
const enJson = (valeur) => `${JSON.stringify(valeur, null, 2)}\n`;
|
|
|
|
|
|
|
|
|
|
|
|
function ecrire(racine, chemin, contenu) {
|
|
|
|
|
|
const complet = join(racine, chemin);
|
|
|
|
|
|
mkdirSync(dirname(complet), { recursive: true });
|
|
|
|
|
|
writeFileSync(complet, contenu);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const lire = (racine, chemin) => readFileSync(join(racine, chemin), 'utf8');
|
|
|
|
|
|
|
|
|
|
|
|
function modifierJson(racine, chemin, modification) {
|
|
|
|
|
|
const valeur = JSON.parse(lire(racine, chemin));
|
|
|
|
|
|
modification(valeur);
|
|
|
|
|
|
ecrire(racine, chemin, enJson(valeur));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Arbre conforme à VERSION et à DATE : la source, ses trois recopies, un
|
|
|
|
|
|
// changelog, un README qui cite le gabarit du nom, une source et une
|
|
|
|
|
|
// configuration à la racine.
|
|
|
|
|
|
function arbreConforme() {
|
|
|
|
|
|
const racine = mkdtempSync(join(tmpdir(), 'version-'));
|
|
|
|
|
|
ecrire(racine, 'version.json', `{ "version": "${VERSION}" }\n`);
|
|
|
|
|
|
ecrire(racine, 'package.json', enJson({ name: 'essai', version: TECHNIQUE, type: 'module' }));
|
|
|
|
|
|
ecrire(
|
|
|
|
|
|
racine,
|
|
|
|
|
|
'package-lock.json',
|
|
|
|
|
|
enJson({
|
|
|
|
|
|
name: 'essai',
|
|
|
|
|
|
version: TECHNIQUE,
|
|
|
|
|
|
lockfileVersion: 3,
|
|
|
|
|
|
requires: true,
|
|
|
|
|
|
packages: { '': { name: 'essai', version: TECHNIQUE } },
|
|
|
|
|
|
}),
|
|
|
|
|
|
);
|
|
|
|
|
|
ecrire(racine, 'src/version.genere.js', MODULE);
|
|
|
|
|
|
ecrire(
|
|
|
|
|
|
racine,
|
|
|
|
|
|
'src/main.js',
|
|
|
|
|
|
"import { VERSION } from './version.genere.js';\nexport const titre = VERSION.affichee;\n",
|
|
|
|
|
|
);
|
|
|
|
|
|
ecrire(racine, 'vite.config.js', 'export default {};\n');
|
|
|
|
|
|
ecrire(racine, 'CHANGELOG.md', changelog(`${VERSION} — 5 octobre 2026`));
|
|
|
|
|
|
ecrire(
|
|
|
|
|
|
racine,
|
|
|
|
|
|
'README.md',
|
|
|
|
|
|
"# Essai\n\nL'exécutable se nomme `gestion_table_tournante_libre_v<AAAA>_<MM>_<JJ>_<NN>.exe`.\n",
|
|
|
|
|
|
);
|
|
|
|
|
|
return racine;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Exécute l'épreuve sur un arbre conforme neuf, supprimé ensuite.
|
|
|
|
|
|
function surArbre(epreuve) {
|
|
|
|
|
|
const racine = arbreConforme();
|
|
|
|
|
|
try {
|
|
|
|
|
|
return epreuve(racine);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
rmSync(racine, { recursive: true, force: true });
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Numéros de point des échecs, sans doublon, croissants. Un échec sans
|
|
|
|
|
|
// numéro fait échouer l'épreuve.
|
|
|
|
|
|
function points(echecs) {
|
|
|
|
|
|
const numeros = echecs.map((echec) => {
|
|
|
|
|
|
const numero = /^([1-8])\. /.exec(echec);
|
|
|
|
|
|
assert.ok(numero, `échec sans numéro de point : ${echec}`);
|
|
|
|
|
|
return Number(numero[1]);
|
|
|
|
|
|
});
|
|
|
|
|
|
return [...new Set(numeros)].sort((a, b) => a - b);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Contrôle l'arbre et exige que les échecs nomment exactement les points
|
|
|
|
|
|
// attendus, et que chaque motif se lise dans l'un d'eux.
|
|
|
|
|
|
function exigerEchecs(racine, attendus, motifs, dateCivile = DATE) {
|
|
|
|
|
|
const echecs = controler(racine, { dateCivile });
|
|
|
|
|
|
const liste = echecs.join('\n');
|
|
|
|
|
|
assert.deepEqual(points(echecs), attendus, liste);
|
|
|
|
|
|
for (const motif of motifs) {
|
|
|
|
|
|
assert.ok(
|
|
|
|
|
|
echecs.some((echec) => motif.test(echec)),
|
|
|
|
|
|
`${motif} ne se lit dans aucun échec :\n${liste}`,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
return echecs;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
describe('version : contrôle du § 18.5, un défaut à la fois', () => {
|
|
|
|
|
|
test("l'arbre de référence est conforme", () =>
|
|
|
|
|
|
surArbre((racine) => {
|
|
|
|
|
|
assert.deepEqual(controler(racine, { dateCivile: DATE }), []);
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
test('point 1 : package.json porte une autre version technique', () =>
|
|
|
|
|
|
surArbre((racine) => {
|
|
|
|
|
|
modifierJson(racine, 'package.json', (paquet) => {
|
|
|
|
|
|
paquet.version = '2026.1006.1';
|
|
|
|
|
|
});
|
|
|
|
|
|
exigerEchecs(racine, [1], [/^1\. package\.json .*2026\.1006\.1.*2026\.1005\.1/]);
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
test('point 1 : le champ version du verrou porte une autre version', () =>
|
|
|
|
|
|
surArbre((racine) => {
|
|
|
|
|
|
modifierJson(racine, 'package-lock.json', (verrou) => {
|
|
|
|
|
|
verrou.version = '2026.1004.1';
|
|
|
|
|
|
});
|
|
|
|
|
|
exigerEchecs(racine, [1], [/^1\. package-lock\.json, champ « version » .*2026\.1004\.1/]);
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
test('point 1 : le champ packages[""].version du verrou porte une autre version', () =>
|
|
|
|
|
|
surArbre((racine) => {
|
|
|
|
|
|
modifierJson(racine, 'package-lock.json', (verrou) => {
|
|
|
|
|
|
verrou.packages[''].version = '2026.1004.1';
|
|
|
|
|
|
});
|
|
|
|
|
|
exigerEchecs(
|
|
|
|
|
|
racine,
|
|
|
|
|
|
[1],
|
|
|
|
|
|
[/^1\. package-lock\.json, champ « packages\[""\]\.version » .*2026\.1004\.1/],
|
|
|
|
|
|
);
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
test('point 1 : le verrou manque', () =>
|
|
|
|
|
|
surArbre((racine) => {
|
|
|
|
|
|
unlinkSync(join(racine, 'package-lock.json'));
|
|
|
|
|
|
exigerEchecs(racine, [1], [/^1\. package-lock\.json absent/]);
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
test('point 1 : version.json manque', () =>
|
|
|
|
|
|
surArbre((racine) => {
|
|
|
|
|
|
unlinkSync(join(racine, 'version.json'));
|
|
|
|
|
|
exigerEchecs(racine, [1], [/^1\. version\.json absent/]);
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
test('point 1 : version.json porte une version non conforme', () =>
|
|
|
|
|
|
surArbre((racine) => {
|
|
|
|
|
|
ecrire(racine, 'version.json', '{ "version": "2026.10.05.100" }\n');
|
|
|
|
|
|
exigerEchecs(racine, [1], [/^1\. version\.json : .*rang 100.*99/]);
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
test('point 2 : le module engendré est modifié à la main', () =>
|
|
|
|
|
|
surArbre((racine) => {
|
|
|
|
|
|
ecrire(racine, 'src/version.genere.js', MODULE.replace("'2026.1005.1'", "'2026.1005.2'"));
|
|
|
|
|
|
exigerEchecs(racine, [2], [/^2\. src\/version\.genere\.js, ligne 7 /]);
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
test('point 2 : le module engendré manque', () =>
|
|
|
|
|
|
surArbre((racine) => {
|
|
|
|
|
|
unlinkSync(join(racine, 'src/version.genere.js'));
|
|
|
|
|
|
exigerEchecs(racine, [2], [/^2\. src\/version\.genere\.js absent/]);
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
test('point 3 : le titre de tête ne porte pas la version courante', () =>
|
|
|
|
|
|
surArbre((racine) => {
|
|
|
|
|
|
ecrire(racine, 'CHANGELOG.md', changelog('2026.10.04.01 — 4 octobre 2026'));
|
|
|
|
|
|
exigerEchecs(racine, [3], [/^3\. CHANGELOG\.md, ligne 3 : .*2026\.10\.04\.01.*2026\.10\.05\.01/]);
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
test('point 3 : la date en lettres du titre de tête est fausse', () =>
|
|
|
|
|
|
surArbre((racine) => {
|
|
|
|
|
|
ecrire(racine, 'CHANGELOG.md', changelog(`${VERSION} — 6 octobre 2026`));
|
|
|
|
|
|
exigerEchecs(racine, [3], [/^3\. CHANGELOG\.md, ligne 3 : .*6 octobre 2026.*5 octobre 2026/]);
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
test('point 3 : la ligne de la version de format manque', () =>
|
|
|
|
|
|
surArbre((racine) => {
|
|
|
|
|
|
const texte = lire(racine, 'CHANGELOG.md');
|
|
|
|
|
|
ecrire(racine, 'CHANGELOG.md', texte.replace('**Version de format des fichiers** : inchangée.\n', ''));
|
|
|
|
|
|
exigerEchecs(racine, [3], [/^3\. CHANGELOG\.md.*Version de format des fichiers/]);
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
test('point 3 : la ligne « En remplaçant » manque', () =>
|
|
|
|
|
|
surArbre((racine) => {
|
|
|
|
|
|
const texte = lire(racine, 'CHANGELOG.md');
|
|
|
|
|
|
ecrire(racine, 'CHANGELOG.md', texte.replace('**En remplaçant** : rien à faire.\n', ''));
|
|
|
|
|
|
exigerEchecs(racine, [3], [/^3\. CHANGELOG\.md.*En remplaçant/]);
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
test('point 3 : le changelog manque', () =>
|
|
|
|
|
|
surArbre((racine) => {
|
|
|
|
|
|
unlinkSync(join(racine, 'CHANGELOG.md'));
|
|
|
|
|
|
exigerEchecs(racine, [3], [/^3\. CHANGELOG\.md absent/]);
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
test('point 3 : une ligne obligatoire absente de la section de tête échoue, même portée par une section plus ancienne', () => {
|
|
|
|
|
|
const lignes = [
|
|
|
|
|
|
['**Version de format des fichiers** : inchangée.\n', /^3\. CHANGELOG\.md, ligne 3 : .*Version de format des fichiers/],
|
|
|
|
|
|
['**En remplaçant** : rien à faire.\n', /^3\. CHANGELOG\.md, ligne 3 : .*En remplaçant/],
|
|
|
|
|
|
];
|
|
|
|
|
|
for (const [ligne, motif] of lignes) {
|
|
|
|
|
|
surArbre((racine) => {
|
|
|
|
|
|
const texte = changelog(`${VERSION} — 5 octobre 2026`, '2026.10.04.01 — 4 octobre 2026');
|
|
|
|
|
|
// replace ne retire que la première occurrence : celle de la section de tête.
|
|
|
|
|
|
ecrire(racine, 'CHANGELOG.md', texte.replace(ligne, ''));
|
|
|
|
|
|
exigerEchecs(racine, [3], [motif]);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test('point 3 : une ligne obligatoire citée dans un bloc de code ne compte pas', () =>
|
|
|
|
|
|
surArbre((racine) => {
|
|
|
|
|
|
const texte = lire(racine, 'CHANGELOG.md');
|
|
|
|
|
|
ecrire(
|
|
|
|
|
|
racine,
|
|
|
|
|
|
'CHANGELOG.md',
|
|
|
|
|
|
texte.replace('**En remplaçant** : rien à faire.\n', '```\n**En remplaçant** : rien à faire.\n```\n'),
|
|
|
|
|
|
);
|
|
|
|
|
|
exigerEchecs(racine, [3], [/^3\. CHANGELOG\.md, ligne 3 : .*En remplaçant/]);
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
test('points 3 et 4 : une section « À venir » en tête', () =>
|
|
|
|
|
|
surArbre((racine) => {
|
|
|
|
|
|
// La section « À venir » porte ses deux lignes obligatoires : seul son
|
|
|
|
|
|
// titre fait tomber le point 3.
|
|
|
|
|
|
ecrire(racine, 'CHANGELOG.md', changelog('À venir', `${VERSION} — 5 octobre 2026`));
|
|
|
|
|
|
exigerEchecs(racine, [3, 4], [
|
|
|
|
|
|
/^3\. CHANGELOG\.md, ligne 3 : le titre « ## À venir » n'a pas la forme/,
|
|
|
|
|
|
/^4\. CHANGELOG\.md, ligne 3 : le titre « ## À venir » ne s'ouvre pas sur une version/,
|
|
|
|
|
|
]);
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
test('point 4 : une section « À venir » entre deux versions', () =>
|
|
|
|
|
|
surArbre((racine) => {
|
|
|
|
|
|
ecrire(
|
|
|
|
|
|
racine,
|
|
|
|
|
|
'CHANGELOG.md',
|
|
|
|
|
|
changelog(`${VERSION} — 5 octobre 2026`, 'À venir', '2026.10.04.01 — 4 octobre 2026'),
|
|
|
|
|
|
);
|
|
|
|
|
|
exigerEchecs(racine, [4], [
|
|
|
|
|
|
/^4\. CHANGELOG\.md, ligne 12 : le titre « ## À venir » ne s'ouvre pas sur une version/,
|
|
|
|
|
|
]);
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
test('points 3 et 4 : un titre « ## » cité dans un bloc de code ne découpe pas la section', () =>
|
|
|
|
|
|
surArbre((racine) => {
|
|
|
|
|
|
const texte = lire(racine, 'CHANGELOG.md');
|
|
|
|
|
|
ecrire(
|
|
|
|
|
|
racine,
|
|
|
|
|
|
'CHANGELOG.md',
|
|
|
|
|
|
texte.replace(
|
|
|
|
|
|
'- Le bandeau affiche la version.\n',
|
|
|
|
|
|
'- Le bandeau affiche la version.\n\n```markdown\n## Notes de la soirée\n```\n',
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
assert.deepEqual(controler(racine, { dateCivile: DATE }), []);
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
test('points 3 et 4 : un sous-titre « ### » ne découpe pas la section', () =>
|
|
|
|
|
|
surArbre((racine) => {
|
|
|
|
|
|
const texte = lire(racine, 'CHANGELOG.md');
|
|
|
|
|
|
ecrire(
|
|
|
|
|
|
racine,
|
|
|
|
|
|
'CHANGELOG.md',
|
|
|
|
|
|
texte.replace('**Ce qui change pour vous**\n', '**Ce qui change pour vous**\n\n### Le bandeau\n'),
|
|
|
|
|
|
);
|
|
|
|
|
|
assert.deepEqual(controler(racine, { dateCivile: DATE }), []);
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
test('point 4 : deux sections portent la même version', () =>
|
|
|
|
|
|
surArbre((racine) => {
|
|
|
|
|
|
ecrire(
|
|
|
|
|
|
racine,
|
|
|
|
|
|
'CHANGELOG.md',
|
|
|
|
|
|
changelog(`${VERSION} — 5 octobre 2026`, `${VERSION} — 5 octobre 2026`),
|
|
|
|
|
|
);
|
|
|
|
|
|
exigerEchecs(racine, [4], [/^4\. CHANGELOG\.md, lignes 3 et 12 : .*2026\.10\.05\.01/]);
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
test('point 4 : une section plus récente suit une plus ancienne', () =>
|
|
|
|
|
|
surArbre((racine) => {
|
|
|
|
|
|
ecrire(
|
|
|
|
|
|
racine,
|
|
|
|
|
|
'CHANGELOG.md',
|
|
|
|
|
|
changelog(`${VERSION} — 5 octobre 2026`, '2026.10.06.01 — 6 octobre 2026'),
|
|
|
|
|
|
);
|
|
|
|
|
|
exigerEchecs(racine, [4], [/^4\. CHANGELOG\.md, ligne 12 : .*2026\.10\.06\.01.*2026\.10\.05\.01/]);
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
test('point 4 : des sections en ordre strictement décroissant sont admises', () =>
|
|
|
|
|
|
surArbre((racine) => {
|
|
|
|
|
|
ecrire(
|
|
|
|
|
|
racine,
|
|
|
|
|
|
'CHANGELOG.md',
|
|
|
|
|
|
changelog(
|
|
|
|
|
|
`${VERSION} — 5 octobre 2026`,
|
|
|
|
|
|
'2026.10.04.02 — 4 octobre 2026',
|
|
|
|
|
|
'2026.10.04.01 — 4 octobre 2026',
|
|
|
|
|
|
'2025.12.31.01 — 31 décembre 2025',
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
assert.deepEqual(controler(racine, { dateCivile: DATE }), []);
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
test("point 5 : la version est datée de plus d'un jour après la date civile", () =>
|
|
|
|
|
|
surArbre((racine) => {
|
|
|
|
|
|
exigerEchecs(racine, [5], [/^5\. version\.json : .*5 octobre 2026.*3 octobre 2026/], '2026-10-03');
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
test("point 5 : un jour d'avance sur la date civile est toléré", () =>
|
|
|
|
|
|
surArbre((racine) => {
|
|
|
|
|
|
assert.deepEqual(controler(racine, { dateCivile: '2026-10-04' }), []);
|
|
|
|
|
|
assert.deepEqual(controler(racine, { dateCivile: '2027-03-01' }), []);
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
test('point 6 : une forme affichée dans src/ hors du module engendré', () =>
|
|
|
|
|
|
surArbre((racine) => {
|
|
|
|
|
|
ecrire(racine, 'src/interface/bandeau.js', "// Bandeau.\n\nexport const v = 'v2026.10.05.01';\n");
|
|
|
|
|
|
exigerEchecs(racine, [6], [/^6\. src\/interface\/bandeau\.js, ligne 3 : « 2026\.10\.05\.01 », forme affichée/]);
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
test('point 6 : une forme technique hors de package.json et du module engendré', () =>
|
|
|
|
|
|
surArbre((racine) => {
|
|
|
|
|
|
ecrire(racine, 'vite.config.js', "export default { define: { V: '2026.1005.1' } };\n");
|
|
|
|
|
|
ecrire(racine, 'scripts/construire.sh', '#!/bin/sh\nWINDOWS=2026.1005.1.0\n');
|
|
|
|
|
|
exigerEchecs(racine, [6], [
|
|
|
|
|
|
/^6\. vite\.config\.js, ligne 1 : « 2026\.1005\.1 », forme technique/,
|
|
|
|
|
|
/^6\. scripts\/construire\.sh, ligne 2 : « 2026\.1005\.1 », forme technique/,
|
|
|
|
|
|
]);
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
test('point 6 : une forme affichée dans package.json', () =>
|
|
|
|
|
|
surArbre((racine) => {
|
|
|
|
|
|
modifierJson(racine, 'package.json', (paquet) => {
|
|
|
|
|
|
paquet.description = 'version 2026.10.05.01';
|
|
|
|
|
|
});
|
|
|
|
|
|
exigerEchecs(racine, [6], [/^6\. package\.json, ligne \d+ : « 2026\.10\.05\.01 », forme affichée/]);
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
test('point 6 : les épreuves de test/ sont dans le périmètre', () =>
|
|
|
|
|
|
surArbre((racine) => {
|
|
|
|
|
|
ecrire(racine, 'test/bandeau.test.js', "export const attendu = '2026.10.05.01';\n");
|
|
|
|
|
|
exigerEchecs(racine, [6], [/^6\. test\/bandeau\.test\.js, ligne 1 /]);
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
test('point 6 : la coquille electron/ est dans le périmètre', () =>
|
|
|
|
|
|
surArbre((racine) => {
|
|
|
|
|
|
ecrire(racine, 'electron/main.js', "const titre = 'Gestion table tournante Libre — 2026.10.05.01';\n");
|
|
|
|
|
|
exigerEchecs(racine, [6], [/^6\. electron\/main\.js, ligne 1 : « 2026\.10\.05\.01 », forme affichée/]);
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
test('point 6 : une version ne se lit pas dans un nombre plus long qui la contient', () =>
|
|
|
|
|
|
surArbre((racine) => {
|
|
|
|
|
|
// Un chiffre ou un point devant, un chiffre derrière : chaque ligne
|
|
|
|
|
|
// prolonge une version d'un côté, sous l'une des deux formes.
|
|
|
|
|
|
ecrire(
|
|
|
|
|
|
racine,
|
|
|
|
|
|
'src/donnees.js',
|
|
|
|
|
|
[
|
|
|
|
|
|
"export const a = '12026.10.05.01';",
|
|
|
|
|
|
"export const b = '1.2026.10.05.01';",
|
|
|
|
|
|
"export const c = '2026.10.05.012';",
|
|
|
|
|
|
"export const d = '12026.1005.1';",
|
|
|
|
|
|
"export const e = '1.2026.1005.1';",
|
|
|
|
|
|
"export const f = '2026.1005.123';",
|
|
|
|
|
|
'',
|
|
|
|
|
|
].join('\n'),
|
|
|
|
|
|
);
|
|
|
|
|
|
assert.deepEqual(controler(racine, { dateCivile: DATE }), []);
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
test("point 6 : un fichier binaire, qui porte un octet nul, n'est pas lu comme du texte", () =>
|
|
|
|
|
|
surArbre((racine) => {
|
|
|
|
|
|
ecrire(racine, 'src/interface/icone.png', `PNG\u0000${VERSION} ${TECHNIQUE}\n`);
|
|
|
|
|
|
assert.deepEqual(controler(racine, { dateCivile: DATE }), []);
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
[ADD] demo: delivered event files, example CSVs, payload fingerprint
The four demonstrations ship as event files written once by a script,
the source of truth of § 15.5: a test replays the generator and compares
SHA-256 fingerprints of the payload alone, naming the paths that differ,
so a new build version never forces a regeneration. The affiliation
profile is read from the delivered file. The two demonstration CSVs are
the export of those files; the edge-case CSV is written by hand, and
its windows-1252 variant derived. Version and forged-name guards skip
these generated files, as § 18.5 and § 15.6 allow.
Checked: node and long series; the script writes only its seven files.
--- FR ---
[ADD] démo : fichiers d'événement livrés, CSV d'exemple, empreinte
Les quatre démonstrations se livrent en fichiers d'événement qu'un
script écrit une fois, source de vérité du § 15.5 : une épreuve rejoue
le générateur et compare les empreintes SHA-256 de la seule charge, en
nommant les chemins qui diffèrent ; une nouvelle version n'impose donc
aucune régénération. Le profil d'appartenances se lit dans le fichier
livré. Les deux CSV de démonstration sont l'export de ces fichiers ;
celui des cas limites s'écrit à la main, sa variante windows-1252 en
dérive. Les gardes de version et de noms forgés passent ces fichiers.
Vérifié : séries node et longue ; le script n'écrit que ses sept fichiers.
Assisted-by: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01EUXSGcwCLSC69FWEdCtWSb
2026-10-06 17:03:15 -04:00
|
|
|
|
test('point 6 : seul le répertoire des démonstrations livrées est exclu, pas un fichier qui porte son nom', () =>
|
|
|
|
|
|
surArbre((racine) => {
|
|
|
|
|
|
ecrire(racine, 'src/demo/livrees.js', "export const v = '2026.10.05.01';\n");
|
|
|
|
|
|
ecrire(racine, 'src/demo/livrees/sous/grande.gtt.json', '{"produitVersion":"2026.10.05.01"}\n');
|
|
|
|
|
|
const echecs = exigerEchecs(racine, [6], [/^6\. src\/demo\/livrees\.js, ligne 1 : « 2026\.10\.05\.01 », forme affichée/]);
|
|
|
|
|
|
assert.equal(echecs.length, 1, echecs.join('\n'));
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
[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
|
|
|
|
test('point 6 : données, guides, dépendances et sorties restent hors du périmètre', () =>
|
|
|
|
|
|
surArbre((racine) => {
|
|
|
|
|
|
const versions = `${VERSION} ${TECHNIQUE} ${TECHNIQUE}.0\n`;
|
|
|
|
|
|
for (const chemin of [
|
|
|
|
|
|
'scripts/version.test.js',
|
|
|
|
|
|
'test/fixtures/demonstration.json',
|
[ADD] demo: delivered event files, example CSVs, payload fingerprint
The four demonstrations ship as event files written once by a script,
the source of truth of § 15.5: a test replays the generator and compares
SHA-256 fingerprints of the payload alone, naming the paths that differ,
so a new build version never forces a regeneration. The affiliation
profile is read from the delivered file. The two demonstration CSVs are
the export of those files; the edge-case CSV is written by hand, and
its windows-1252 variant derived. Version and forged-name guards skip
these generated files, as § 18.5 and § 15.6 allow.
Checked: node and long series; the script writes only its seven files.
--- FR ---
[ADD] démo : fichiers d'événement livrés, CSV d'exemple, empreinte
Les quatre démonstrations se livrent en fichiers d'événement qu'un
script écrit une fois, source de vérité du § 15.5 : une épreuve rejoue
le générateur et compare les empreintes SHA-256 de la seule charge, en
nommant les chemins qui diffèrent ; une nouvelle version n'impose donc
aucune régénération. Le profil d'appartenances se lit dans le fichier
livré. Les deux CSV de démonstration sont l'export de ces fichiers ;
celui des cas limites s'écrit à la main, sa variante windows-1252 en
dérive. Les gardes de version et de noms forgés passent ces fichiers.
Vérifié : séries node et longue ; le script n'écrit que ses sept fichiers.
Assisted-by: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01EUXSGcwCLSC69FWEdCtWSb
2026-10-06 17:03:15 -04:00
|
|
|
|
'src/demo/livrees/grande.gtt.json',
|
[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
|
|
|
|
'GUIDE-USAGE.md',
|
|
|
|
|
|
'docs/notes.md',
|
|
|
|
|
|
'node_modules/paquet/index.js',
|
|
|
|
|
|
'src/node_modules/paquet/index.js',
|
|
|
|
|
|
'www/assets/index.js',
|
|
|
|
|
|
'dist/sortie.js',
|
|
|
|
|
|
'dist-electron/main.js',
|
|
|
|
|
|
'android/app/build.gradle',
|
|
|
|
|
|
]) {
|
|
|
|
|
|
ecrire(racine, chemin, versions);
|
|
|
|
|
|
}
|
|
|
|
|
|
ecrire(racine, 'README.md', `${lire(racine, 'README.md')}\nVersion ${versions}`);
|
|
|
|
|
|
assert.deepEqual(controler(racine, { dateCivile: DATE }), []);
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
test('point 7 : un document porte le littéral #_#', () =>
|
|
|
|
|
|
surArbre((racine) => {
|
|
|
|
|
|
ecrire(racine, 'README.md', `${lire(racine, 'README.md')}\nVersion : #_#\n`);
|
|
|
|
|
|
exigerEchecs(racine, [7], [/^7\. README\.md, ligne 5 : .*#_#/]);
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
test("point 7 : un document cite un nom d'exécutable hors du gabarit", () =>
|
|
|
|
|
|
surArbre((racine) => {
|
|
|
|
|
|
const fautifs = [
|
|
|
|
|
|
'gestion_table_tournante_libre_v2026.10.05.01.exe',
|
|
|
|
|
|
'gestion_table_tournante_libre_v2026_10_05_1.exe',
|
|
|
|
|
|
'gestion_table_tournante_libre_v2026_13_05_01.exe',
|
|
|
|
|
|
'gestion_table_tournante_libre_v<AAAA>.exe',
|
|
|
|
|
|
'gestion_table_tournante_libre.exe',
|
|
|
|
|
|
'Gestion_Table_Tournante_Libre_v2026_10_05_01.EXE',
|
|
|
|
|
|
];
|
|
|
|
|
|
ecrire(racine, 'GUIDE-WINDOWS.md', fautifs.map((nom) => `Lancer \`${nom}\`.\n`).join(''));
|
|
|
|
|
|
const echecs = exigerEchecs(
|
|
|
|
|
|
racine,
|
|
|
|
|
|
[7],
|
|
|
|
|
|
fautifs.map((nom, i) => new RegExp(`^7\\. GUIDE-WINDOWS\\.md, ligne ${i + 1} : « ${nom.replace(/[.<>]/g, '\\$&')} »`)),
|
|
|
|
|
|
);
|
|
|
|
|
|
assert.equal(echecs.length, fautifs.length, echecs.join('\n'));
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
test('point 7 : le gabarit, un nom conforme et le préfixe seul sont admis', () =>
|
|
|
|
|
|
surArbre((racine) => {
|
|
|
|
|
|
ecrire(
|
|
|
|
|
|
racine,
|
|
|
|
|
|
'GUIDE-WINDOWS.md',
|
|
|
|
|
|
[
|
|
|
|
|
|
'Le fichier dont le nom commence par `gestion_table_tournante_libre_v`.',
|
|
|
|
|
|
'Gabarit : `gestion_table_tournante_libre_v<AAAA>_<MM>_<JJ>_<NN>.exe`.',
|
|
|
|
|
|
'Par exemple gestion_table_tournante_libre_v2026_10_05_01.exe.',
|
|
|
|
|
|
'',
|
|
|
|
|
|
].join('\n'),
|
|
|
|
|
|
);
|
|
|
|
|
|
assert.deepEqual(controler(racine, { dateCivile: DATE }), []);
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
test('point 7 : le changelog est balayé', () =>
|
|
|
|
|
|
surArbre((racine) => {
|
|
|
|
|
|
const texte = lire(racine, 'CHANGELOG.md');
|
|
|
|
|
|
ecrire(
|
|
|
|
|
|
racine,
|
|
|
|
|
|
'CHANGELOG.md',
|
|
|
|
|
|
texte.replace(
|
|
|
|
|
|
'- Le bandeau affiche la version.\n',
|
|
|
|
|
|
'- Le bandeau affiche la version.\n- Lancer `gestion_table_tournante_libre.exe`.\n- Version #_#.\n',
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
const echecs = exigerEchecs(racine, [7], [
|
|
|
|
|
|
/^7\. CHANGELOG\.md, ligne 7 : « gestion_table_tournante_libre\.exe »/,
|
|
|
|
|
|
/^7\. CHANGELOG\.md, ligne 8 : littéral « #_# »/,
|
|
|
|
|
|
]);
|
|
|
|
|
|
assert.equal(echecs.length, 2, echecs.join('\n'));
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
test('point 8 : un balayage qui ne trouve aucune source échoue', () =>
|
|
|
|
|
|
surArbre((racine) => {
|
|
|
|
|
|
rmSync(join(racine, 'src'), { recursive: true });
|
|
|
|
|
|
// Sans src/, le module engendré manque aussi : le point 2 tombe avec.
|
|
|
|
|
|
exigerEchecs(racine, [2, 8], [/^8\. .*src\//]);
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
test('point 8 : un src/ qui ne porte que le module engendré ne compte aucune source', () =>
|
|
|
|
|
|
surArbre((racine) => {
|
|
|
|
|
|
unlinkSync(join(racine, 'src/main.js'));
|
|
|
|
|
|
const echecs = exigerEchecs(racine, [8], [/^8\. le balayage du point 6 n'examine aucun fichier sous src\//]);
|
|
|
|
|
|
assert.equal(echecs.length, 1, echecs.join('\n'));
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
test('point 8 : un balayage qui ne trouve aucun document échoue', () =>
|
|
|
|
|
|
surArbre((racine) => {
|
|
|
|
|
|
unlinkSync(join(racine, 'README.md'));
|
|
|
|
|
|
unlinkSync(join(racine, 'CHANGELOG.md'));
|
|
|
|
|
|
// Sans changelog, le point 3 tombe avec.
|
|
|
|
|
|
exigerEchecs(racine, [3, 8], [/^8\. le balayage du point 7 n'examine aucun document/]);
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
test("modifier version.json seul fait tomber les points 1, 2 et 3, chacun nommant l'endroit resté en arrière", () =>
|
|
|
|
|
|
surArbre((racine) => {
|
|
|
|
|
|
ecrire(racine, 'version.json', '{ "version": "2026.10.05.02" }\n');
|
|
|
|
|
|
const echecs = exigerEchecs(racine, [1, 2, 3], [
|
|
|
|
|
|
/^1\. package\.json /,
|
|
|
|
|
|
/^1\. package-lock\.json, champ « version » /,
|
|
|
|
|
|
/^1\. package-lock\.json, champ « packages\[""\]\.version » /,
|
|
|
|
|
|
/^2\. src\/version\.genere\.js, ligne 6 /,
|
|
|
|
|
|
/^3\. CHANGELOG\.md, ligne 3 : /,
|
|
|
|
|
|
]);
|
|
|
|
|
|
assert.equal(echecs.length, 5, echecs.join('\n'));
|
|
|
|
|
|
engendrer(racine);
|
|
|
|
|
|
exigerEchecs(racine, [3], [/^3\. CHANGELOG\.md, ligne 3 : /]);
|
|
|
|
|
|
const texte = lire(racine, 'CHANGELOG.md');
|
|
|
|
|
|
ecrire(
|
|
|
|
|
|
racine,
|
|
|
|
|
|
'CHANGELOG.md',
|
|
|
|
|
|
texte.replace(`${TITRE_CHANGELOG}\n\n`, `${TITRE_CHANGELOG}\n\n${section('2026.10.05.02 — 5 octobre 2026')}\n`),
|
|
|
|
|
|
);
|
|
|
|
|
|
assert.deepEqual(controler(racine, { dateCivile: DATE }), []);
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
test('refuse une date civile mal formée', () => {
|
|
|
|
|
|
assert.throws(() => controler(RACINE, { dateCivile: '2026-10-5' }), TypeError);
|
|
|
|
|
|
assert.throws(() => controler(RACINE, { dateCivile: '2026-02-30' }), /jour/);
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
describe('version : engendrement', () => {
|
|
|
|
|
|
test("engendrerModule rend le module, l'en-tête de licence en tête", () => {
|
|
|
|
|
|
assert.equal(engendrerModule(VERSION), MODULE);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
[ADD] interface: application shell — banner, sections, theme, about
The window showed only a banner; it now holds the application. App mounts
the session, the facade and the generation, settles the working folder
and lists its events before it reports ready, re-reading the list once a
question is answered or a refused determination succeeds. The banner
carries version, mode and the event list command; an open event shows its
sections in their first form; the theme follows the system at each start
and the operator's choice lasts the session; À propos names the build.
Errors are collected for a report, and Electron removes the session's
locks at exit.
Checked: 2189 node, 193 browser, 60 node-long; the Electron start check.
--- FR ---
[ADD] interface : coquille — bandeau, sections, thème, À propos
La fenêtre ne montrait qu'un bandeau ; elle tient désormais l'application.
App monte la séance, la façade et la génération, règle le dossier de
travail et liste ses événements avant de se dire prête, et relit la liste
une fois une question répondue ou une détermination refusée réussie. Le
bandeau porte version, mode et la commande de la liste ; un événement
ouvert montre ses sections dans leur première forme ; le thème suit le
système à chaque démarrage et le choix de l'opérateur vaut pour la séance ;
À propos nomme la construction. Les erreurs se collectent pour un
signalement, et Electron efface à la sortie les verrous de la séance.
Vérifié : 2189 node, 193 navigateur, 60 node-long ; démarrage d'Electron.
Assisted-by: Claude Opus 5.5
2026-10-07 07:22:05 -04:00
|
|
|
|
test('le module engendré porte les formes affichée, technique et Windows de deriver, et elles seules', async () => {
|
|
|
|
|
|
const dossier = mkdtempSync(join(tmpdir(), 'version-module-'));
|
|
|
|
|
|
try {
|
|
|
|
|
|
const chemin = join(dossier, 'version.genere.js');
|
|
|
|
|
|
writeFileSync(chemin, engendrerModule('2026.10.06.02'));
|
|
|
|
|
|
const { VERSION: engendree } = await import(pathToFileURL(chemin).href);
|
|
|
|
|
|
const { affichee, technique, windows } = deriver('2026.10.06.02');
|
|
|
|
|
|
assert.deepEqual(engendree, { affichee, technique, windows });
|
|
|
|
|
|
assert.ok(Object.isFrozen(engendree));
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
rmSync(dossier, { recursive: true, force: true });
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
[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
|
|
|
|
test('engendrer réécrit le module, package.json et les deux champs du verrou', () =>
|
|
|
|
|
|
surArbre((racine) => {
|
|
|
|
|
|
ecrire(racine, 'version.json', '{ "version": "2026.10.06.02" }\n');
|
|
|
|
|
|
const paquet = JSON.parse(lire(racine, 'package.json'));
|
|
|
|
|
|
const verrou = JSON.parse(lire(racine, 'package-lock.json'));
|
|
|
|
|
|
const reecrits = engendrer(racine);
|
|
|
|
|
|
assert.deepEqual(reecrits, ['src/version.genere.js', 'package.json', 'package-lock.json']);
|
|
|
|
|
|
assert.equal(lire(racine, 'src/version.genere.js'), engendrerModule('2026.10.06.02'));
|
|
|
|
|
|
assert.equal(lire(racine, 'package.json'), enJson({ ...paquet, version: '2026.1006.2' }));
|
|
|
|
|
|
verrou.version = '2026.1006.2';
|
|
|
|
|
|
verrou.packages[''].version = '2026.1006.2';
|
|
|
|
|
|
assert.equal(lire(racine, 'package-lock.json'), enJson(verrou));
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
test('sur un arbre à jour, engendrer ne réécrit rien', () =>
|
|
|
|
|
|
surArbre((racine) => {
|
|
|
|
|
|
const fichiers = ['src/version.genere.js', 'package.json', 'package-lock.json'];
|
|
|
|
|
|
const avant = fichiers.map((chemin) => lire(racine, chemin));
|
|
|
|
|
|
assert.deepEqual(engendrer(racine), []);
|
|
|
|
|
|
assert.deepEqual(fichiers.map((chemin) => lire(racine, chemin)), avant);
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
test('engendrer lève quand le verrou manque, sans rien écrire', () =>
|
|
|
|
|
|
surArbre((racine) => {
|
|
|
|
|
|
ecrire(racine, 'version.json', '{ "version": "2026.10.06.02" }\n');
|
|
|
|
|
|
unlinkSync(join(racine, 'package-lock.json'));
|
|
|
|
|
|
assert.throws(() => engendrer(racine), /package-lock\.json absent.*npm install/);
|
|
|
|
|
|
assert.equal(lire(racine, 'src/version.genere.js'), MODULE);
|
|
|
|
|
|
assert.equal(JSON.parse(lire(racine, 'package.json')).version, TECHNIQUE);
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
test('engendrer refuse le centième rang de la journée, sans rien écrire', () =>
|
|
|
|
|
|
surArbre((racine) => {
|
|
|
|
|
|
ecrire(racine, 'version.json', '{ "version": "2026.10.05.100" }\n');
|
|
|
|
|
|
assert.throws(() => engendrer(racine), /^Error: version\.json : .*99/);
|
|
|
|
|
|
assert.equal(lire(racine, 'src/version.genere.js'), MODULE);
|
|
|
|
|
|
}));
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
describe('version : date civile de la machine', () => {
|
|
|
|
|
|
test('dateCivileLocale écrit la date locale sous la forme AAAA-MM-JJ', () => {
|
|
|
|
|
|
assert.equal(dateCivileLocale(new Date(2026, 9, 5, 23, 59)), '2026-10-05');
|
|
|
|
|
|
assert.equal(dateCivileLocale(new Date(2027, 0, 1, 0, 0)), '2027-01-01');
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
describe('version : ligne de commande', () => {
|
|
|
|
|
|
// Le script copié dans l'arbre d'épreuve prend pour racine le parent de
|
|
|
|
|
|
// son répertoire, comme dans le projet.
|
|
|
|
|
|
function lancer(racine, ...arguments_) {
|
|
|
|
|
|
const script = join(racine, 'scripts', 'version.js');
|
|
|
|
|
|
mkdirSync(dirname(script), { recursive: true });
|
|
|
|
|
|
copyFileSync(SCRIPT, script);
|
|
|
|
|
|
return spawnSync(process.execPath, [script, ...arguments_], { encoding: 'utf8' });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
test('controler sort en 0 sur un arbre conforme, en 1 en imprimant les échecs sinon', () =>
|
|
|
|
|
|
surArbre((racine) => {
|
|
|
|
|
|
const conforme = lancer(racine, 'controler');
|
|
|
|
|
|
assert.equal(conforme.status, 0, conforme.stderr);
|
|
|
|
|
|
modifierJson(racine, 'package.json', (paquet) => {
|
|
|
|
|
|
paquet.version = '2026.1006.1';
|
|
|
|
|
|
});
|
|
|
|
|
|
const fautif = lancer(racine, 'controler');
|
|
|
|
|
|
assert.equal(fautif.status, 1, fautif.stdout);
|
|
|
|
|
|
assert.match(fautif.stderr, /^1\. package\.json /m);
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
test('engendrer réécrit les recopies, et sort en 1 quand le verrou manque', () =>
|
|
|
|
|
|
surArbre((racine) => {
|
|
|
|
|
|
ecrire(racine, 'version.json', '{ "version": "2026.10.05.02" }\n');
|
|
|
|
|
|
const reussi = lancer(racine, 'engendrer');
|
|
|
|
|
|
assert.equal(reussi.status, 0, reussi.stderr);
|
|
|
|
|
|
assert.equal(lire(racine, 'src/version.genere.js'), engendrerModule('2026.10.05.02'));
|
|
|
|
|
|
unlinkSync(join(racine, 'package-lock.json'));
|
|
|
|
|
|
const echoue = lancer(racine, 'engendrer');
|
|
|
|
|
|
assert.equal(echoue.status, 1, echoue.stdout);
|
|
|
|
|
|
assert.match(echoue.stderr, /package-lock\.json absent/);
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
test("une commande inconnue sort en 2 et rappelle l'usage", () =>
|
|
|
|
|
|
surArbre((racine) => {
|
|
|
|
|
|
const inconnue = lancer(racine, 'publier');
|
|
|
|
|
|
assert.equal(inconnue.status, 2);
|
|
|
|
|
|
assert.match(inconnue.stderr, /engendrer.*controler/);
|
|
|
|
|
|
}));
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
describe('version : arbre réel', () => {
|
|
|
|
|
|
test("l'arbre réel du projet est conforme, à la date civile de la machine", () => {
|
|
|
|
|
|
assert.deepEqual(controler(RACINE, { dateCivile: dateCivileLocale() }), []);
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|