gestion_table_tournante_libre/src/geometrie/designation.test.js

189 lines
8.1 KiB
JavaScript
Raw Normal View History

// © 2026 TechnoLibre (http://www.technolibre.ca)
// License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
// Épreuves de la désignation (§ 7.3, § 14.10) : le régime des cibles, décidé
// table par table sur le pas de chacune, et le siège que désigne un point de
// dessin, qui est celui que geometrieTable pose — sur les quatre
// démonstrations livrées et sur des carrées, à un zoom où les sièges sont
// des cibles et à un zoom où la table l'est.
import assert from 'node:assert/strict';
import { describe, test } from '../../test/lanceur.js';
import { LIVREES } from '../demo/livrees.js';
import { analyser, capacite } from '../stockage/document.js';
import { CIBLE_MIN_PX, designer, regimeCibles, surLeSol } from './designation.js';
import { englobant, geometrieTable } from './tables.js';
const table = (id, forme, x, y) => ({ id, numero: id, sieges: null, forme, position: { x, y } });
const milieu = (a, b) => ({ x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 });
describe('regimeCibles (§ 7.3)', () => {
test('le seuil est de 24 px', () => {
assert.equal(CIBLE_MIN_PX, 24);
});
test('ronde de 8, pas 60 : 0,40 px/cm et au-dessus, les sièges', () => {
const g = geometrieTable(table(1, 'ronde', 0, 0), 8);
assert.equal(regimeCibles(0.41, g), 'sieges');
assert.equal(regimeCibles(0.4, g), 'sieges');
assert.equal(regimeCibles(0.39, g), 'table');
});
test('ronde de 3, pas 70π/3 : bascule entre 0,33 et 0,32', () => {
const g = geometrieTable(table(1, 'ronde', 0, 0), 3);
assert.equal(regimeCibles(0.33, g), 'sieges');
assert.equal(regimeCibles(0.32, g), 'table');
});
test('carrée de 3, pas 70 : bascule entre 0,35 et 0,34', () => {
const g = geometrieTable(table(1, 'carree', 0, 0), 3);
assert.equal(regimeCibles(0.35, g), 'sieges');
assert.equal(regimeCibles(0.34, g), 'table');
});
test('ronde de 7, pas 60 exactement : sièges à 0,40', () => {
assert.equal(regimeCibles(0.4, geometrieTable(table(1, 'ronde', 0, 0), 7)), 'sieges');
});
});
describe('l’emprise d’une carrée est un carré', () => {
// Carrée de 12 au centre (0, 0) : côté 180, emprise 90 + 44 = 134 ; le
// siège 6, dernier du côté droit, est en (112, 60).
const geometries = [geometrieTable(table(7, 'carree', 0, 0), 12)];
test('(125, 125) : hors du plateau et des chaises, dans le coin de l’emprise', () => {
const point = { x: 125, y: 125 };
assert.deepEqual(designer(point, geometries, 0.2), { table: 7 });
assert.equal(designer(point, geometries, 1), null);
assert.equal(surLeSol(point, geometries), false);
});
test('(130, 60) : dans la chaise du siège 6, à 143 cm du centre', () => {
const point = { x: 130, y: 60 };
assert.deepEqual(designer(point, geometries, 0.2), { table: 7 });
assert.deepEqual(designer(point, geometries, 1), { table: 7, siege: 6 });
assert.equal(surLeSol(point, geometries), false);
});
test('(140, 0) est le sol', () => {
const point = { x: 140, y: 0 };
assert.equal(designer(point, geometries, 0.2), null);
assert.equal(designer(point, geometries, 1), null);
assert.equal(surLeSol(point, geometries), true);
});
});
// Les géométries de chaque démonstration livrée, telles que le dessin les
// pose, puis trois carrées de 3, 8 et 12 : les démonstrations n'ont que des
// rondes.
const JEUX = [
...LIVREES.map(({ cle, texte }) => {
const { charge } = analyser(texte);
return { nom: cle, geometries: charge.tables.map((t) => geometrieTable(t, capacite(charge, t))) };
}),
{
nom: 'carrées',
geometries: [
geometrieTable(table(1, 'carree', 0, 0), 3),
geometrieTable(table(2, 'carree', 1000, 0), 8),
geometrieTable(table(3, 'carree', 2000, 0), 12),
],
},
];
describe('le siège désigné est celui que le dessin pose (§ 7.3, § 14.10)', () => {
const ATTENDUES = { grande: 260, 'grande-sans-exception': 264, petite: 12, 'petite-conflit': 12, 'carrées': 23 };
test('cinq jeux, dans l’ordre', () => {
assert.deepEqual(JEUX.map(({ nom }) => nom), ['grande', 'grande-sans-exception', 'petite', 'petite-conflit', 'carrées']);
});
for (const { nom, geometries } of JEUX) {
test(`${nom}, k = 1 : chaque chaise rend sa table et son siège`, () => {
let chaises = 0;
for (const g of geometries) {
assert.equal(regimeCibles(1, g), 'sieges');
for (const chaise of g.chaises) {
assert.deepEqual(designer(chaise, geometries, 1), { table: g.id, siege: chaise.siege });
chaises += 1;
}
}
assert.ok(ATTENDUES[nom] > 0);
assert.equal(chaises, ATTENDUES[nom]);
});
test(`${nom}, k = 1 : ni le milieu de deux chaises voisines, ni le centre d’une table ne désigne rien`, () => {
let milieux = 0;
for (const g of geometries) {
const n = g.chaises.length;
for (let i = 0; i < n; i += 1) {
const point = milieu(g.chaises[i], g.chaises[(i + 1) % n]);
assert.equal(designer(point, geometries, 1), null, `table ${g.id}, sièges ${i + 1} et ${((i + 1) % n) + 1}`);
assert.equal(surLeSol(point, geometries), false);
milieux += 1;
}
assert.equal(designer(g.centre, geometries, 1), null, `centre de la table ${g.id}`);
assert.equal(surLeSol(g.centre, geometries), false);
}
assert.equal(milieux, ATTENDUES[nom]);
});
test(`${nom} : un point à 10 m de tout est le sol`, () => {
const e = englobant(geometries);
const loin = { x: e.maxX + 1000, y: e.maxY + 1000 };
assert.equal(surLeSol(loin, geometries), true);
assert.equal(designer(loin, geometries, 1), null);
assert.equal(designer(loin, geometries, 0.2), null);
});
test(`${nom}, k = 0,2 : chaque chaise, chaque milieu et chaque centre rendent la table, jamais un siège`, () => {
let points = 0;
for (const g of geometries) {
assert.equal(regimeCibles(0.2, g), 'table');
const n = g.chaises.length;
const candidats = [g.centre, ...g.chaises, ...g.chaises.map((c, i) => milieu(c, g.chaises[(i + 1) % n]))];
for (const point of candidats) {
assert.deepEqual(designer(point, geometries, 0.2), { table: g.id });
points += 1;
}
}
assert.equal(points, 2 * ATTENDUES[nom] + geometries.length);
});
}
});
describe('tables qui se recouvrent : la dernière reçue, peinte au-dessus', () => {
test('deux tables au même centre : la seconde', () => {
const geometries = [geometrieTable(table(1, 'ronde', 0, 0), 8), geometrieTable(table(2, 'ronde', 0, 0), 8)];
const chaise = geometries[0].chaises[2];
assert.deepEqual(designer(chaise, geometries, 1), { table: 2, siege: 3 });
assert.deepEqual(designer({ x: 0, y: 0 }, geometries, 0.2), { table: 2 });
});
test('régime sièges : le plateau du dessus cache la chaise de dessous', () => {
// Rondes de 8 en (0, 0) et (100, 0) : le siège 3 de la première, en
// (98,394 ; 0), tombe dans le plateau de la seconde, de rayon 76,394.
const premiere = geometrieTable(table(1, 'ronde', 0, 0), 8);
const seconde = geometrieTable(table(2, 'ronde', 100, 0), 8);
const cachee = premiere.chaises[2];
assert.equal(designer(cachee, [premiere, seconde], 1), null);
assert.deepEqual(designer(cachee, [seconde, premiere], 1), { table: 1, siege: 3 });
assert.deepEqual(designer(premiere.chaises[6], [premiere, seconde], 1), { table: 1, siege: 7 });
});
test('régimes différents : chaque table se juge sur son propre pas', () => {
// À k = 0,35, la ronde de 8 (pas 60) est en régime table, la carrée de 3
// (pas 70) en régime sièges.
const ronde = geometrieTable(table(1, 'ronde', 0, 0), 8);
const carree = geometrieTable(table(2, 'carree', 1000, 0), 3);
const geometries = [ronde, carree];
assert.deepEqual(designer(ronde.chaises[0], geometries, 0.35), { table: 1 });
assert.deepEqual(designer(carree.chaises[0], geometries, 0.35), { table: 2, siege: 1 });
});
test('sans table, aucun point n’est désigné et tout est le sol', () => {
assert.equal(designer({ x: 0, y: 0 }, [], 1), null);
assert.equal(surLeSol({ x: 0, y: 0 }, []), true);
});
});