import { describe, expect, test } from "bun:test"; import "./extension/config.js"; import "./extension/formulaire-valeurs.js"; import { CLE_EVENEMENT_EN_ATTENTE, serialiserPourFormulaire } from "./extension/transfert.js"; const { DEFAUTS } = (globalThis as any).EchoConfig; const { calculerValeurs } = (globalThis as any).EchoFormulaire; // Payload = ce que dépose transfert.js (debut/fin en ISO), pas un Event. function payload(surcharges: Record = {}) { return { uid: "uid-1", titre: "Atelier", description: "Venez nombreux", lieu: "", debut: new Date("2026-07-18T08:30:00Z").toISOString(), fin: new Date("2026-07-18T10:30:00Z").toISOString(), categories: [], journeeEntiere: false, ...surcharges, }; } // Une journée entière est une date FLOTTANTE : fixture en composantes locales, // jamais un instant absolu (cf. DECISIONS.md 2026-07-14). function payloadJourneeEntiere(debut: Date, fin: Date, surcharges = {}) { return payload({ debut: debut.toISOString(), fin: fin.toISOString(), journeeEntiere: true, ...surcharges, }); } describe("journée entière", () => { test("un seul jour (DTEND = J+1) → dateFin === dateDebut", () => { const valeurs = calculerValeurs( payloadJourneeEntiere(new Date(2026, 6, 10), new Date(2026, 6, 11)), DEFAUTS, ); expect(valeurs.dateDebut).toBe("10/07/2026"); expect(valeurs.dateFin).toBe("10/07/2026"); }); test("lit les composantes locales, pas un instant absolu", () => { const valeurs = calculerValeurs( payloadJourneeEntiere(new Date(2026, 6, 10), new Date(2026, 6, 11)), DEFAUTS, ); expect(valeurs.dateDebut).toBe("10/07/2026"); }); // Festival 18→21 exclusif : il se termine réellement le 20. Sans ce recul, // une date FAUSSE part à une mairie qui modère. test("plusieurs jours (18 → DTEND 21) → dateFin = 20/07/2026", () => { const valeurs = calculerValeurs( payloadJourneeEntiere(new Date(2026, 6, 18), new Date(2026, 6, 21)), DEFAUTS, ); expect(valeurs.dateDebut).toBe("18/07/2026"); expect(valeurs.dateFin).toBe("20/07/2026"); }); test("DTEND absent (fin === debut) → dateFin === dateDebut", () => { const valeurs = calculerValeurs( payloadJourneeEntiere(new Date(2026, 6, 10), new Date(2026, 6, 10)), DEFAUTS, ); expect(valeurs.dateFin).toBe("10/07/2026"); }); // Poser « 00:00 » serait le mensonge que presentation.js refuse déjà. test("aucune heure : poser 00:00 serait un mensonge", () => { const valeurs = calculerValeurs( payloadJourneeEntiere(new Date(2026, 6, 10), new Date(2026, 6, 11)), DEFAUTS, ); expect(valeurs.heureDebut).toBeNull(); expect(valeurs.heureFin).toBeNull(); }); test("l'heure manquante est signalée", () => { const valeurs = calculerValeurs( payloadJourneeEntiere(new Date(2026, 6, 10), new Date(2026, 6, 11)), DEFAUTS, ); expect(valeurs.aCompleter).toContain("l'heure"); }); }); describe("événement horodaté", () => { test("heures à Paris, zéro-paddées", () => { const valeurs = calculerValeurs( payload({ debut: new Date("2026-07-18T07:05:00Z").toISOString(), // 09:05 à Paris fin: new Date("2026-07-18T19:00:00Z").toISOString(), // 21:00 à Paris }), DEFAUTS, ); expect(valeurs.heureDebut).toEqual(["09", "05"]); expect(valeurs.heureFin).toEqual(["21", "00"]); expect(valeurs.dateDebut).toBe("18/07/2026"); expect(valeurs.dateFin).toBe("18/07/2026"); }); test("DTEND absent (fin === debut) → pas d'heure de fin, mais le jour reste vrai", () => { const debut = new Date("2026-07-18T08:30:00Z").toISOString(); const valeurs = calculerValeurs(payload({ debut, fin: debut }), DEFAUTS); expect(valeurs.heureDebut).toEqual(["10", "30"]); expect(valeurs.heureFin).toBeNull(); expect(valeurs.dateFin).toBe("18/07/2026"); expect(valeurs.aCompleter).toContain("l'heure"); }); // 22:00 → 02:00 : annoncer la fin le même jour serait un mensonge. test("franchit minuit → dateFin est le lendemain", () => { const valeurs = calculerValeurs( payload({ debut: new Date("2026-07-18T20:00:00Z").toISOString(), // 22:00 à Paris fin: new Date("2026-07-19T00:00:00Z").toISOString(), // 02:00 le 19 à Paris }), DEFAUTS, ); expect(valeurs.dateDebut).toBe("18/07/2026"); expect(valeurs.heureDebut).toEqual(["22", "00"]); expect(valeurs.dateFin).toBe("19/07/2026"); expect(valeurs.heureFin).toEqual(["02", "00"]); }); test("heures complètes → l'heure n'est pas dans aCompleter", () => { const valeurs = calculerValeurs(payload(), DEFAUTS); expect(valeurs.aCompleter).not.toContain("l'heure"); }); }); describe("lieu", () => { test("lieu vide (cas majoritaire) → adresse et coordonnées du café", () => { const valeurs = calculerValeurs(payload({ lieu: "" }), DEFAUTS); expect(valeurs.adresse).toBe("8 Rue du Pré Vicinal 31270 Cugnaux"); expect(valeurs.coordonnees).toBe( "43.53753132806989|1.342981890597508|8 Rue du Pré Vicinal 31270 Cugnaux", ); expect(valeurs.aCompleter).not.toContain("le lieu exact sur la carte"); }); test("lieu en blancs → café", () => { const valeurs = calculerValeurs(payload({ lieu: " " }), DEFAUTS); expect(valeurs.adresse).toBe("8 Rue du Pré Vicinal 31270 Cugnaux"); }); // On ne sait pas géocoder : inventer un lat|lng serait pire que le laisser vide. test("lieu renseigné → pas de coordonnées inventées, manque signalé", () => { const valeurs = calculerValeurs(payload({ lieu: "Salle des fêtes" }), DEFAUTS); expect(valeurs.adresse).toBe("Salle des fêtes"); expect(valeurs.coordonnees).toBe(""); expect(valeurs.aCompleter).toContain("le lieu exact sur la carte"); }); }); describe("champs de config", () => { test("thème et tarifs viennent de la config", () => { const valeurs = calculerValeurs(payload(), DEFAUTS); expect(valeurs.theme).toBe("Culture"); expect(valeurs.tarifs).toBe("Gratuit"); }); test("organisateur et email surchargés sont posés et non signalés", () => { const config = { ...DEFAUTS, organisateur: "L'Atelier du Huit", email: "a@b.fr" }; const valeurs = calculerValeurs(payload(), config); expect(valeurs.organisateur).toBe("L'Atelier du Huit"); expect(valeurs.email).toBe("a@b.fr"); expect(valeurs.aCompleter).not.toContain("l'organisateur et l'email (config.local.json)"); }); test("sans config.local.json → organisateur/email vides et signalés", () => { const valeurs = calculerValeurs(payload(), DEFAUTS); expect(valeurs.organisateur).toBe(""); expect(valeurs.email).toBe(""); expect(valeurs.aCompleter).toContain("l'organisateur et l'email (config.local.json)"); }); }); describe("aCompleter", () => { test("signale toujours l'image et le thème", () => { const config = { ...DEFAUTS, organisateur: "X", email: "a@b.fr" }; const valeurs = calculerValeurs(payload({ lieu: "" }), config); expect(valeurs.aCompleter).toContain("l'image (obligatoire)"); expect(valeurs.aCompleter).toContain("le thème"); }); test("titre vide → signalé", () => { expect(calculerValeurs(payload({ titre: "" }), DEFAUTS).aCompleter).toContain("le titre"); }); test("titre renseigné → non signalé", () => { expect(calculerValeurs(payload(), DEFAUTS).aCompleter).not.toContain("le titre"); }); }); describe("texte", () => { test("titre et description sont repris tels quels", () => { const valeurs = calculerValeurs(payload(), DEFAUTS); expect(valeurs.titre).toBe("Atelier"); expect(valeurs.description).toBe("Venez nombreux"); }); // Cas majoritaire (142/473 ont une DESCRIPTION) : le poseur n'écrira pas "". test("description vide reste vide", () => { expect(calculerValeurs(payload({ description: "" }), DEFAUTS).description).toBe(""); }); }); // La constante est dupliquée dans formulaire-valeurs.js (un content script // classique ne peut pas importer transfert.js, qui est en ESM). Ce test est le // garde-fou contre la dérive. describe("couture avec transfert.js", () => { test("la clé dupliquée est identique à l'originale", () => { expect((globalThis as any).EchoFormulaire.CLE_EVENEMENT_EN_ATTENTE).toBe( CLE_EVENEMENT_EN_ATTENTE, ); }); // Le contrat de couture vérifié bout en bout, pas supposé. test("aller-retour réel : serialiserPourFormulaire → calculerValeurs", () => { const evenement = { uid: "uid-1", titre: "Concert", description: "Au café", lieu: "", debut: new Date("2026-07-18T18:00:00Z"), // 20:00 à Paris fin: new Date("2026-07-18T20:00:00Z"), // 22:00 à Paris categories: [], journeeEntiere: false, }; const valeurs = calculerValeurs(serialiserPourFormulaire(evenement), DEFAUTS); expect(valeurs.titre).toBe("Concert"); expect(valeurs.dateDebut).toBe("18/07/2026"); expect(valeurs.heureDebut).toEqual(["20", "00"]); expect(valeurs.heureFin).toEqual(["22", "00"]); expect(valeurs.adresse).toBe("8 Rue du Pré Vicinal 31270 Cugnaux"); }); test("aller-retour réel d'une journée entière (date flottante)", () => { const evenement = { uid: "uid-2", titre: "Festival", description: "", lieu: "", debut: new Date(2026, 6, 18), fin: new Date(2026, 6, 21), categories: [], journeeEntiere: true, }; const valeurs = calculerValeurs(serialiserPourFormulaire(evenement), DEFAUTS); expect(valeurs.dateDebut).toBe("18/07/2026"); expect(valeurs.dateFin).toBe("20/07/2026"); expect(valeurs.heureDebut).toBeNull(); }); });