feat: introduce a shared config with an optional local override
The mairie form needs an organiser, an email and the café defaults. Ship the defaults in the repo and let each volunteer override the personal bits in config.local.json (gitignored, optional: a fresh clone still works). config.js carries neither import nor export on purpose, so that it loads both as a classic script (content script, background) and as an ESM module. That is what lets presentation.js drop its duplicated café address. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
342fda9a0b
commit
b103ab78ec
@@ -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("");
|
||||
});
|
||||
});
|
||||
@@ -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 };
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"organisateur": "L'Atelier du Huit",
|
||||
"email": "contact@atelier-huit.fr"
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user