All checks were successful
CI / static-tests (pull_request) Successful in 51s
CI / secret-scan (push) Successful in 12s
CI / js-tests (push) Successful in 29s
CI / secret-scan (pull_request) Successful in 12s
CI / php-lint (pull_request) Successful in 24s
CI / js-tests (pull_request) Successful in 31s
CI / php-lint (push) Successful in 28s
CI / static-tests (push) Successful in 56s
Cliquer un produit ouvre desormais une modale au-dessus de la grille (paradigme maquette : ecran de commande unique + modales) au lieu de naviguer vers product.html. Menu -> composeur (L2) ; produit simple -> modale quantite+ajout. A l'ajout, le panneau de commande persistant (L1) est re-rendu : la commande se met a jour sans navigation. product.html reste un repli (lien direct). - product-options.js : modale (image, prix unitaire, stepper quantite, total) ; productCartItem pur ; a11y (role=dialog, focus-trap, ESC, fond aria-hidden, overflow restaure, aria-live sur quantite ET total). - page-products.js : clic carte -> modale (preventDefault sur le <a>) ; bouton 'i' allergenes garde son stopPropagation. - style.css : .product-options + .qty-control (reutilise .qty-btn/.qty-value). - tests : product-options.test.js (productCartItem + rendu/stepper/ajout). 56/56 verts. Taille 30/50Cl de la maquette differee : absente du modele produit (un seul price_cents) -> necessitera des variantes produit cote API. Revue adversariale : 1 finding MEDIUM (aria-live du total) corrige.
90 lines
3.9 KiB
JavaScript
90 lines
3.9 KiB
JavaScript
/*
|
|
* Tests de la modale d'options produit (P5 L3), node:test + jsdom.
|
|
*
|
|
* product-options.js importe order-panel.js + nav.js (DOM au chargement) -> import
|
|
* dynamique apres globals jsdom. Cible : productCartItem (PUR) + openProductOptions
|
|
* (rendu jsdom : stepper quantite, ajout au panier, fermeture).
|
|
*/
|
|
import { test, before, beforeEach } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { JSDOM } from 'jsdom';
|
|
|
|
let productCartItem, openProductOptions;
|
|
|
|
before(async () => {
|
|
const dom = new JSDOM('<!DOCTYPE html><html><body></body></html>', { url: 'https://kiosk.test/products.html' });
|
|
global.window = dom.window;
|
|
global.document = dom.window.document;
|
|
global.localStorage = dom.window.localStorage;
|
|
global.requestAnimationFrame = (cb) => cb();
|
|
({ productCartItem, openProductOptions } =
|
|
await import('../../src/public/borne/assets/js/product-options.js'));
|
|
});
|
|
|
|
beforeEach(() => {
|
|
global.localStorage.clear();
|
|
document.body.innerHTML = '';
|
|
});
|
|
|
|
const product = { id: 14, nom: 'Coca', prix: 190, image: 'c.png', categorie: 'boissons' };
|
|
|
|
/* --- productCartItem (pur) ----------------------------------------------- */
|
|
|
|
test('productCartItem: forme item, quantite et categorie du produit', () => {
|
|
const it = productCartItem(product, 'boissons', 3);
|
|
assert.deepEqual(it, {
|
|
id: 14, type: 'produit', categorie: 'boissons', libelle: 'Coca',
|
|
prix_cents: 190, quantite: 3, image: 'c.png',
|
|
});
|
|
});
|
|
|
|
test('productCartItem: quantite bornee a [1,99], categorie de repli = slug', () => {
|
|
assert.equal(productCartItem({ id: 1, nom: 'X', prix: 100, image: 'x.png' }, 'frites', 0).quantite, 1);
|
|
assert.equal(productCartItem(product, 'boissons', 9999).quantite, 99);
|
|
assert.equal(productCartItem({ id: 1, nom: 'X', prix: 100, image: 'x.png' }, 'frites', 2).categorie, 'frites');
|
|
});
|
|
|
|
/* --- openProductOptions (jsdom) ------------------------------------------ */
|
|
|
|
test('openProductOptions: rend la modale (dialog) avec total = prix unitaire', () => {
|
|
openProductOptions(product, 'boissons');
|
|
const dialog = document.querySelector('.composer-overlay [role="dialog"]');
|
|
assert.ok(dialog);
|
|
assert.match(document.querySelector('#po-total').textContent, /1,90/);
|
|
assert.equal(document.querySelector('#po-qty').textContent, '1');
|
|
});
|
|
|
|
test('openProductOptions: le stepper met a jour quantite et total', () => {
|
|
openProductOptions(product, 'boissons');
|
|
document.querySelector('.qty-btn--plus').click();
|
|
document.querySelector('.qty-btn--plus').click();
|
|
assert.equal(document.querySelector('#po-qty').textContent, '3');
|
|
assert.match(document.querySelector('#po-total').textContent, /5,70/); // 1,90 x 3
|
|
document.querySelector('.qty-btn--minus').click();
|
|
assert.equal(document.querySelector('#po-qty').textContent, '2');
|
|
});
|
|
|
|
test('openProductOptions: quantite plancher a 1', () => {
|
|
openProductOptions(product, 'boissons');
|
|
const minus = document.querySelector('.qty-btn--minus');
|
|
minus.click(); minus.click(); minus.click();
|
|
assert.equal(document.querySelector('#po-qty').textContent, '1');
|
|
});
|
|
|
|
test('openProductOptions: Ajouter met l item (avec quantite) au panier et ferme la modale', () => {
|
|
openProductOptions(product, 'boissons');
|
|
document.querySelector('.qty-btn--plus').click(); // qty 2
|
|
document.querySelector('#po-add').click();
|
|
const cart = JSON.parse(localStorage.getItem('wakdo_cart'));
|
|
assert.equal(cart.length, 1);
|
|
assert.equal(cart[0].id, 14);
|
|
assert.equal(cart[0].quantite, 2);
|
|
assert.equal(document.querySelector('.composer-overlay'), null); // modale fermee
|
|
});
|
|
|
|
test('openProductOptions: Annuler ferme sans rien ajouter', () => {
|
|
openProductOptions(product, 'boissons');
|
|
document.querySelector('#po-cancel').click();
|
|
assert.equal(document.querySelector('.composer-overlay'), null);
|
|
assert.equal(localStorage.getItem('wakdo_cart'), null);
|
|
});
|