gestion_table_tournante_libre/test/machine.test.js
Mathieu Benoit 3028fca2ed [FIX] interface: ghost labels, keyboard, valid HTML, closed data-* list
The drag ghost names what releasing at the point changes, floor included.
Escape on a seat returns to its table, a refused Tab stays put, the legend
toggle exposes aria-expanded, and the header markup is valid HTML. Every
data-* attribute the interface sets belongs to one closed list that a guard
reads. The tree guards follow helpers a test imports, and the reporter says
the whole series stays watched under coverage on a small machine.
Checked: every new test red first; 3001 node, 635 browser and 63 long tests
green from the index alone.

--- FR ---

[FIX] interface : fantôme nommé, clavier, HTML valide, data-* fermés

Le fantôme du glisser nomme ce que relâcher au point modifie, sol compris.
Échap sur un siège remonte à sa table, un Tab refusé reste en place, la
bascule de la légende expose aria-expanded, et la tête est du HTML valide.
Chaque attribut data-* que l'interface pose appartient à une liste fermée
qu'une garde lit. Les gardes de l'arbre suivent les aides qu'une épreuve
importe, et le rapporteur dit la série entière surveillée sous couverture.
Vérifié : chaque épreuve rouge d'abord ; 3001 node, 635 navigateur et 63
longues vertes depuis l'index seul.

Assisted-by: Claude Opus 5.5
2026-10-09 04:02:03 -04:00

169 lines
8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// © 2026 TechnoLibre (http://www.technolibre.ca)
// License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
// Les réglages des séries node selon le nombre de cœurs (§ 14.14), sous la
// mesure de la couverture comme hors d'elle (§ 14.4, § 14.13), et leur
// application dans vitest.config.js.
import assert from 'node:assert/strict';
import { execFile } from 'node:child_process';
import { promisify } from 'node:util';
import { existsSync } from 'node:fs';
import { availableParallelism } from 'node:os';
import { join } from 'node:path';
import { fileURLToPath } from 'node:url';
import configuration from '../vitest.config.js';
import { describe, test } from './lanceur.js';
import { LOURDES, SEUIL_COEURS, coeursDisponibles, ligneMachine, reglagesSeries } from './machine.js';
const RACINE = fileURLToPath(new URL('..', import.meta.url));
const projet = (nom) => configuration.test.projects.find(({ test: { name } }) => name === nom).test;
describe('machine : les réglages selon le nombre de cœurs (§ 14.14)', () => {
test('à partir de quatre cœurs, des processus complets et la série surveillée entière', () => {
for (const coeurs of [SEUIL_COEURS, 8, 16, 64]) {
assert.deepEqual(reglagesSeries(coeurs), {
petiteMachine: false,
coeurs,
pool: 'forks',
maxWorkers: undefined,
horsSerieSurveillee: [],
}, `${coeurs} cœurs`);
}
});
test('en deçà de quatre cœurs, des threads sur tous les cœurs, et les épreuves lourdes en série longue', () => {
for (const coeurs of [1, 2, 3]) {
assert.deepEqual(reglagesSeries(coeurs), {
petiteMachine: true,
coeurs,
pool: 'threads',
maxWorkers: coeurs,
horsSerieSurveillee: [...LOURDES],
}, `${coeurs} cœurs`);
}
});
test('sous la mesure de la couverture, aucune épreuve ne quitte la série node, sur une petite machine comme sur une grande ; le reste des réglages ne change pas', () => {
for (const coeurs of [1, 2, 3, SEUIL_COEURS, 16]) {
assert.deepEqual(
reglagesSeries(coeurs, { couverture: true }),
{ ...reglagesSeries(coeurs), horsSerieSurveillee: [] },
`${coeurs} cœurs`,
);
assert.deepEqual(reglagesSeries(coeurs, { couverture: false }), reglagesSeries(coeurs), `${coeurs} cœurs`);
}
assert.deepEqual(reglagesSeries(2).horsSerieSurveillee, [...LOURDES]);
});
test('un nombre de cœurs qui n’est pas un entier ≥ 1 lève RangeError', () => {
for (const coeurs of [0, -1, 2.5, Number.NaN, '4', undefined]) {
assert.throws(() => reglagesSeries(coeurs), RangeError, String(coeurs));
}
});
test('les épreuves lourdes existent, sont de la série surveillée, et la liste n’est pas vide', () => {
assert.ok(LOURDES.length > 0, 'aucune épreuve lourde nommée');
for (const fichier of LOURDES) {
assert.ok(existsSync(join(RACINE, fichier)), `${fichier} absent`);
assert.match(fichier, /\.test\.js$/);
assert.doesNotMatch(fichier, /\.(long|navigateur)\.test\.js$/, `${fichier} n'est pas de la série surveillée`);
}
});
test('les cœurs disponibles suivent l’affinité du processus, comme availableParallelism', () => {
assert.equal(coeursDisponibles(), availableParallelism());
assert.ok(coeursDisponibles() >= 1);
});
test('la ligne annoncée dit le mode, sur une petite machine comme sur une grande', () => {
assert.equal(
ligneMachine(reglagesSeries(2)),
'Machine : 2 cœurs, en deçà de 4 — threads sur les 2 cœurs ; '
+ 'en série longue : src/moteur/integration.test.js.',
);
assert.equal(
ligneMachine(reglagesSeries(1)),
'Machine : 1 cœur, en deçà de 4 — threads sur son seul cœur ; '
+ 'en série longue : src/moteur/integration.test.js.',
);
assert.equal(ligneMachine(reglagesSeries(16)), 'Machine : 16 cœurs — processus complets, série surveillée entière.');
// Sous la mesure de la couverture, la petite machine garde tout en série node.
assert.equal(
ligneMachine(reglagesSeries(2, { couverture: true })),
'Machine : 2 cœurs, en deçà de 4 — threads sur les 2 cœurs ; série surveillée entière.',
);
});
});
describe('machine : vitest.config.js applique les réglages de cette machine', () => {
// Les réglages de ce processus : ses cœurs, et la mesure de la couverture
// quand ses arguments portent --coverage, la règle de vitest.config.js.
const reglages = reglagesSeries(coeursDisponibles(), { couverture: process.argv.includes('--coverage') });
test('les projets node et node-long prennent le pool et le nombre de processus retenus', () => {
for (const nom of ['node', 'node-long']) {
assert.equal(projet(nom).pool, reglages.pool, nom);
assert.equal(projet(nom).maxWorkers, reglages.maxWorkers, nom);
}
});
test('une épreuve hors de la série surveillée entre dans la série longue, et nulle part ailleurs', () => {
for (const fichier of reglages.horsSerieSurveillee) {
assert.ok(projet('node').exclude.includes(fichier), `${fichier} reste dans node`);
assert.ok(projet('node-long').include.includes(fichier), `${fichier} manque à node-long`);
}
for (const fichier of LOURDES.filter((f) => !reglages.horsSerieSurveillee.includes(f))) {
assert.ok(!projet('node').exclude.includes(fichier), `${fichier} sort de node sur cette machine`);
assert.ok(!projet('node-long').include.includes(fichier), `${fichier} entre dans node-long sur cette machine`);
}
});
test('le projet navigateur ne prend aucun de ces réglages', () => {
const navigateur = projet('navigateur');
assert.equal(navigateur.pool, undefined);
assert.equal(navigateur.maxWorkers, undefined);
});
});
// Projets node et node-long de vitest.config.js, chargé dans un processus à
// part dont la machine n'a que deux cœurs — availableParallelism remplacé
// avant tout import — et dont process.argv porte les arguments donnés, comme
// celui de npm run couverture porte --coverage. Rend la promesse de
// { node, long } : exclude du premier, include du second.
async function projetsSurDeuxCoeurs(argumentsDuLanceur) {
const deuxCoeurs = [
"import os from 'node:os';",
"import { syncBuiltinESMExports } from 'node:module';",
'os.availableParallelism = () => 2;',
'syncBuiltinESMExports();',
].join('\n');
const lire = [
"const { default: configuration } = await import('./vitest.config.js');",
'const projet = (nom) => configuration.test.projects.find(({ test: { name } }) => name === nom).test;',
"console.log(JSON.stringify({ node: projet('node').exclude, long: projet('node-long').include }));",
].join('\n');
const { stdout } = await promisify(execFile)(
process.execPath,
['--import', `data:text/javascript,${encodeURIComponent(deuxCoeurs)}`, '--input-type=module', '-e', lire, '--', ...argumentsDuLanceur],
{ cwd: RACINE, encoding: 'utf8' },
);
return JSON.parse(stdout);
}
describe('machine : vitest.config.js garde la série node entière sous la mesure de la couverture (§ 14.13)', () => {
test('sur deux cœurs, --coverage garde les épreuves lourdes dans node, où se mesurent les seuils d’indicateurs.js et de plafond.js ; sans lui, elles passent dans node-long', async () => {
// Les deux processus partent ensemble : la durée de l'épreuve est celle
// d'un seul.
const [mesure, surveillee] = await Promise.all([
projetsSurDeuxCoeurs(['--project', 'node', '--coverage']),
projetsSurDeuxCoeurs(['--project', 'node']),
]);
assert.ok(LOURDES.length > 0, 'aucune épreuve lourde nommée');
for (const fichier of LOURDES) {
assert.ok(!mesure.node.includes(fichier), `${fichier} sort de node sous --coverage`);
assert.ok(!mesure.long.includes(fichier), `${fichier} entre dans node-long sous --coverage`);
assert.ok(surveillee.node.includes(fichier), `${fichier} reste dans node sur deux cœurs`);
assert.ok(surveillee.long.includes(fichier), `${fichier} manque à node-long sur deux cœurs`);
}
});
});