diff --git a/config.test.ts b/config.test.ts new file mode 100644 index 0000000..e08fd84 --- /dev/null +++ b/config.test.ts @@ -0,0 +1,63 @@ +import { describe, expect, test } from "bun:test"; +// config.js n'exporte RIEN (chargeable en script classique ET en module) : +// on l'importe pour son effet de bord et on lit le namespace global. +import "./extension/config.js"; + +const { DEFAUTS, fusionner, CLE_CONFIG_LOCALE } = (globalThis as any).EchoConfig; + +describe("DEFAUTS", () => { + test("themeDefaut est une valeur exacte du dropdown mairie (RECHERCHE §3)", () => { + expect(DEFAUTS.themeDefaut).toBe("Culture"); + }); + + test("organisateur et email sont vides : jamais inventés, signalés à l'utilisateur", () => { + expect(DEFAUTS.organisateur).toBe(""); + expect(DEFAUTS.email).toBe(""); + }); + + test("le café porte l'adresse et les coordonnées de RECHERCHE §2", () => { + expect(DEFAUTS.cafe.adresse).toBe("8 Rue du Pré Vicinal 31270 Cugnaux"); + expect(DEFAUTS.cafe.coords).toBe("43.53753132806989|1.342981890597508"); + }); + + test("tarifs par défaut : Gratuit", () => { + expect(DEFAUTS.tarifs).toBe("Gratuit"); + }); +}); + +describe("CLE_CONFIG_LOCALE", () => { + test("est la clé de storage partagée background → content script", () => { + expect(CLE_CONFIG_LOCALE).toBe("config-locale"); + }); +}); + +describe("fusionner", () => { + // Un clone frais n'a pas de config.local.json : le repli doit être total, + // jamais une exception. + test.each([ + ["absente", undefined], + ["null", null], + ["une chaîne", "n'importe quoi"], + ["un tableau vide", []], + ])("locale %s → défauts nus", (_libelle, locale) => { + expect(fusionner(DEFAUTS, locale)).toEqual(DEFAUTS); + }); + + test("organisateur et email surchargent, le reste survit", () => { + const fusion = fusionner(DEFAUTS, { + organisateur: "L'Atelier du Huit", + email: "contact@atelier-huit.fr", + }); + + expect(fusion.organisateur).toBe("L'Atelier du Huit"); + expect(fusion.email).toBe("contact@atelier-huit.fr"); + expect(fusion.themeDefaut).toBe("Culture"); + expect(fusion.cafe).toEqual(DEFAUTS.cafe); + }); + + test("ne mute pas DEFAUTS (le module est partagé par tout le monde)", () => { + fusionner(DEFAUTS, { organisateur: "Autre" }); + + expect(DEFAUTS.organisateur).toBe(""); + }); +}); diff --git a/extension/config.js b/extension/config.js new file mode 100644 index 0000000..bb03a75 --- /dev/null +++ b/extension/config.js @@ -0,0 +1,39 @@ +// Défauts committés de l'extension + fusion avec la surcharge locale. +// +// ⚠️ Ce fichier n'a NI `import` NI `export`, DÉLIBÉRÉMENT (ne pas « corriger ») : +// c'est ce qui le rend chargeable à la fois comme content script / script de +// background (scripts classiques : un `export` y lève une SyntaxError) et comme +// module ESM (`import "./config.js"` depuis presentation.js et bun test). +// Il communique donc par `globalThis`. Cf. DECISIONS.md 2026-07-17. + +globalThis.EchoConfig = { + // Clé de storage : le background écrit, le content script lit. + CLE_CONFIG_LOCALE: "config-locale", + + DEFAUTS: { + // Vides par défaut : sans config.local.json, on préfère un champ vide + // signalé dans le bandeau à un organisateur inventé. + organisateur: "", + email: "", + + // RECHERCHE.md §3 : valeur exacte du dropdown #input_6_20. + themeDefaut: "Culture", + tarifs: "Gratuit", + + // RECHERCHE.md §2. `input_18` se compose `${coords}|${adresse}`. + cafe: { + adresse: "8 Rue du Pré Vicinal 31270 Cugnaux", + coords: "43.53753132806989|1.342981890597508", + }, + }, + + // Fusion de surface : la locale ne porte que des champs plats + // (organisateur, email…). Tolérante par nécessité — `config.local.json` est + // optionnel et éditable à la main : absent, `null` ou invalide → défauts nus, + // jamais d'exception. + fusionner(defauts, locale) { + const surcharge = + locale && typeof locale === "object" && !Array.isArray(locale) ? locale : {}; + return { ...defauts, ...surcharge }; + }, +}; diff --git a/extension/config.local.json.example b/extension/config.local.json.example new file mode 100644 index 0000000..dc767b5 --- /dev/null +++ b/extension/config.local.json.example @@ -0,0 +1,4 @@ +{ + "organisateur": "L'Atelier du Huit", + "email": "contact@atelier-huit.fr" +} diff --git a/extension/presentation.js b/extension/presentation.js index b357d53..0f107f8 100644 --- a/extension/presentation.js +++ b/extension/presentation.js @@ -2,11 +2,16 @@ // Fonctions pures, sans DOM (testables). Locale et fuseau sont des constantes : // l'appli est mono-locale (bénévoles de Cugnaux). +// config.js n'exporte rien : il pose globalThis.EchoConfig (cf. DECISIONS.md +// 2026-07-17). L'import ne sert qu'à son effet de bord. +import "./config.js"; + const LOCALE = "fr-FR"; const FUSEAU = "Europe/Paris"; -// RECHERCHE.md §2 : adresse du café de l'Atelier du Huit. -export const LIEU_PAR_DEFAUT = "8 Rue du Pré Vicinal 31270 Cugnaux"; +// Source unique : le formulaire mairie a besoin de la même adresse (et de ses +// coordonnées) que la liste. +export const LIEU_PAR_DEFAUT = globalThis.EchoConfig.DEFAUTS.cafe.adresse; // Un événement horodaté porte un vrai instant : on l'affiche à l'heure de // Cugnaux, quel que soit le fuseau de la machine.