All checks were successful
CI / secret-scan (pull_request) Successful in 7s
CI / php-lint (pull_request) Successful in 19s
CI / static-tests (pull_request) Successful in 39s
CI / js-tests (pull_request) Successful in 19s
CI / secret-scan (push) Successful in 8s
CI / php-lint (push) Successful in 18s
CI / static-tests (push) Successful in 39s
CI / js-tests (push) Successful in 16s
CI / auto-merge (pull_request) Successful in 4s
CI / auto-merge (push) Has been skipped
PR-C du lot P3. Icone "i" allergenes sur la borne ouvrant une modale GENERALE listant les 14 allergenes a declaration obligatoire (UE INCO 1169/2011). Info generale, PAS un calcul par produit (mapping ingredient_allergen reste differe). - data/allergens.json : les 14 INCO (liste fixe data borne). loadAllergens() ajoute a data.js avec point de swap P4 documente (-> /api/allergens). - allergens.js : module CSP-safe (DOM API, aucun handler inline). buildAllergenInfoButton (bouton "i", stopPropagation sur la carte), openAllergenModal (idempotent), fermeture par bouton / clic overlay / touche Echap. - Integration : carte produit (page-products.js) ET fiche produit simple (page-product.js) ; CSS modale + badge reutilisant le pattern overlay du composer. Harnais de tests front (premier du depot) : node:test + jsdom. tests/js/allergens.test.js couvre les 14 INCO, la construction du bouton, l'ouverture/listing/fermeture et l'idempotence (7 tests). Scoping ESM local (src/public/borne/assets/js + tests/js) pour garder la racine en CommonJS (hooks, _byan, bin inchanges). Job CI js-tests (Node 20 epingle) ajoute aux checks requis de l'auto-merge.
110 lines
4.1 KiB
JavaScript
110 lines
4.1 KiB
JavaScript
/*
|
|
* Tests du module allergens du front borne (node:test + jsdom).
|
|
*
|
|
* Couvre le contrat de PR-C : la liste fixe des 14 allergenes INCO (data borne,
|
|
* se branchera sur /api/allergens au swap P4), la construction du bouton "i", et
|
|
* la modale GENERALE (ouverture, listing des 14, fermeture par bouton/overlay/
|
|
* Escape, idempotence). DOM simule par jsdom : aucun navigateur requis.
|
|
*/
|
|
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { readFileSync } from 'node:fs';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { dirname, join } from 'node:path';
|
|
import { JSDOM } from 'jsdom';
|
|
|
|
import {
|
|
buildAllergenInfoButton,
|
|
openAllergenModal,
|
|
closeAllergenModal,
|
|
} from '../../src/public/borne/assets/js/allergens.js';
|
|
|
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
const allergensJsonPath = join(here, '../../src/public/borne/data/allergens.json');
|
|
|
|
function setupDom() {
|
|
const dom = new JSDOM('<!DOCTYPE html><html><body></body></html>');
|
|
global.window = dom.window;
|
|
global.document = dom.window.document;
|
|
return dom;
|
|
}
|
|
|
|
function loadAllergensFixture() {
|
|
return JSON.parse(readFileSync(allergensJsonPath, 'utf8'));
|
|
}
|
|
|
|
test('data/allergens.json liste exactement les 14 allergenes INCO', () => {
|
|
const list = loadAllergensFixture();
|
|
assert.ok(Array.isArray(list));
|
|
assert.equal(list.length, 14);
|
|
for (const a of list) {
|
|
assert.equal(typeof a.id, 'number');
|
|
assert.equal(typeof a.name, 'string');
|
|
assert.ok(a.name.trim().length > 0);
|
|
}
|
|
const names = list.map((a) => a.name);
|
|
assert.equal(new Set(names).size, 14, 'noms uniques');
|
|
// Quelques jalons de la liste reglementaire (UE INCO 1169/2011 annexe II).
|
|
const joined = names.join(' | ').toLowerCase();
|
|
for (const expected of ['gluten', 'lait', 'arachide', 'soja', 'mollusque']) {
|
|
assert.ok(joined.includes(expected), `attendu: ${expected}`);
|
|
}
|
|
});
|
|
|
|
test('buildAllergenInfoButton cree un bouton "i" qui declenche onOpen', () => {
|
|
setupDom();
|
|
let opened = 0;
|
|
const btn = buildAllergenInfoButton(() => { opened += 1; });
|
|
|
|
assert.equal(btn.tagName, 'BUTTON');
|
|
assert.equal(btn.type, 'button');
|
|
assert.ok(btn.className.includes('allergen-info-btn'));
|
|
assert.ok(btn.getAttribute('aria-label'));
|
|
|
|
btn.click();
|
|
assert.equal(opened, 1, 'le clic ouvre la modale');
|
|
});
|
|
|
|
test('openAllergenModal affiche une modale listant les 14 allergenes', () => {
|
|
setupDom();
|
|
const list = loadAllergensFixture();
|
|
const overlay = openAllergenModal(list);
|
|
|
|
assert.ok(document.body.contains(overlay));
|
|
assert.equal(overlay.getAttribute('role'), 'dialog');
|
|
assert.equal(overlay.getAttribute('aria-modal'), 'true');
|
|
const items = overlay.querySelectorAll('.allergen-modal-list li');
|
|
assert.equal(items.length, 14);
|
|
assert.ok(overlay.textContent.toLowerCase().includes('lait'));
|
|
});
|
|
|
|
test('la modale se ferme via le bouton de fermeture', () => {
|
|
setupDom();
|
|
openAllergenModal(loadAllergensFixture());
|
|
document.querySelector('.allergen-modal-close').click();
|
|
assert.equal(document.querySelector('.allergen-modal-overlay'), null);
|
|
});
|
|
|
|
test('la modale se ferme par clic sur l overlay (hors contenu)', () => {
|
|
const dom = setupDom();
|
|
const overlay = openAllergenModal(loadAllergensFixture());
|
|
overlay.dispatchEvent(new dom.window.MouseEvent('click', { bubbles: true }));
|
|
assert.equal(document.querySelector('.allergen-modal-overlay'), null);
|
|
});
|
|
|
|
test('la modale se ferme avec la touche Echap', () => {
|
|
const dom = setupDom();
|
|
openAllergenModal(loadAllergensFixture());
|
|
document.dispatchEvent(new dom.window.KeyboardEvent('keydown', { key: 'Escape' }));
|
|
assert.equal(document.querySelector('.allergen-modal-overlay'), null);
|
|
});
|
|
|
|
test('ouvrir deux fois ne duplique pas la modale (idempotent)', () => {
|
|
setupDom();
|
|
const list = loadAllergensFixture();
|
|
openAllergenModal(list);
|
|
openAllergenModal(list);
|
|
assert.equal(document.querySelectorAll('.allergen-modal-overlay').length, 1);
|
|
closeAllergenModal();
|
|
assert.equal(document.querySelector('.allergen-modal-overlay'), null);
|
|
});
|