feat: list upcoming events with a create button
The list page now parses the calendar feed and renders each upcoming event (title, date, place) in chronological order, with a button that hands the event over to the town hall form. Sorting, date formatting and the café fallback live in a pure presentation module rather than in liste.js, which touches the DOM and cannot be imported under bun test. The seam to the sibling task's content script lives in transfert.js: a single storage slot, since a content script running on the town hall site has no way to learn a uid. The deposit is awaited before opening the tab, otherwise the content script could read before the write lands. Test timezone is pinned to Europe/Paris: date formatting is the core logic here and would otherwise pass only on machines set to Paris. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
d1a1c194bb
commit
3d86330894
@@ -0,0 +1,143 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { join } from "node:path";
|
||||
import {
|
||||
LIEU_PAR_DEFAUT,
|
||||
formaterDate,
|
||||
formaterLieu,
|
||||
trierParDebut,
|
||||
} from "./extension/presentation.js";
|
||||
|
||||
// Fixtures = Event littéraux : le parsing est testé dans evenements.test.ts.
|
||||
function evenement(surcharges: Record<string, unknown> = {}) {
|
||||
return {
|
||||
uid: "uid-1",
|
||||
titre: "Atelier",
|
||||
description: "",
|
||||
lieu: "",
|
||||
debut: new Date("2026-07-10T16:00:00Z"),
|
||||
fin: new Date("2026-07-10T18:00:00Z"),
|
||||
categories: [],
|
||||
journeeEntiere: false,
|
||||
...surcharges,
|
||||
};
|
||||
}
|
||||
|
||||
describe("trierParDebut", () => {
|
||||
test("ordre mêlé → croissant sur debut", () => {
|
||||
const tardif = evenement({ uid: "tardif", debut: new Date("2026-08-01T10:00:00Z") });
|
||||
const tot = evenement({ uid: "tot", debut: new Date("2026-07-01T10:00:00Z") });
|
||||
const median = evenement({ uid: "median", debut: new Date("2026-07-15T10:00:00Z") });
|
||||
|
||||
const tries = trierParDebut([tardif, tot, median]);
|
||||
|
||||
expect(tries.map((e) => e.uid)).toEqual(["tot", "median", "tardif"]);
|
||||
});
|
||||
|
||||
test("ne mute pas le tableau d'entrée", () => {
|
||||
const tardif = evenement({ uid: "tardif", debut: new Date("2026-08-01T10:00:00Z") });
|
||||
const tot = evenement({ uid: "tot", debut: new Date("2026-07-01T10:00:00Z") });
|
||||
const entree = [tardif, tot];
|
||||
|
||||
trierParDebut(entree);
|
||||
|
||||
expect(entree.map((e) => e.uid)).toEqual(["tardif", "tot"]);
|
||||
});
|
||||
|
||||
test("[] → []", () => {
|
||||
expect(trierParDebut([])).toEqual([]);
|
||||
});
|
||||
|
||||
test("deux debut identiques → 2 éléments, pas de plantage", () => {
|
||||
const meme = new Date("2026-07-10T16:00:00Z");
|
||||
const a = evenement({ uid: "a", debut: meme });
|
||||
const b = evenement({ uid: "b", debut: meme });
|
||||
|
||||
expect(trierParDebut([a, b])).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
|
||||
// Fixture UNIQUE de journée entière, partagée par le test en processus et ceux
|
||||
// en sous-processus : ils doivent décrire le même Event, sinon l'un des deux
|
||||
// ment. Composantes LOCALES, jamais un instant absolu ("...T00:00+02:00") : une
|
||||
// date flottante (VALUE=DATE) n'a pas de fuseau et ical.js la matérialise à
|
||||
// minuit local, donc un instant figé ne serait juste qu'à +02:00.
|
||||
const JOURNEE_ENTIERE = [2026, 6, 10] as const;
|
||||
const JOUR_ATTENDU = "10 juillet 2026";
|
||||
|
||||
function evenementJourneeEntiere() {
|
||||
return evenement({ debut: new Date(...JOURNEE_ENTIERE), journeeEntiere: true });
|
||||
}
|
||||
|
||||
describe("formaterDate", () => {
|
||||
test("événement horodaté → date et heure", () => {
|
||||
const rendu = formaterDate(evenement({ debut: new Date("2026-07-10T16:00:00Z") }));
|
||||
|
||||
expect(rendu).toContain("10 juillet 2026");
|
||||
expect(rendu).toContain("18:00");
|
||||
});
|
||||
|
||||
test("journée entière → date sans aucune heure", () => {
|
||||
const rendu = formaterDate(evenementJourneeEntiere());
|
||||
|
||||
expect(rendu).toContain(JOUR_ATTENDU);
|
||||
expect(rendu).not.toMatch(/\d{1,2}:\d{2}/);
|
||||
});
|
||||
|
||||
test("instant à 11:00 UTC → affiché à l'heure de Paris (13:00)", () => {
|
||||
const rendu = formaterDate(evenement({ debut: new Date("2026-07-10T11:00:00Z") }));
|
||||
|
||||
expect(rendu).toContain("13:00");
|
||||
});
|
||||
|
||||
// Le fuseau des tests est épinglé à Europe/Paris : c'est justement le seul où
|
||||
// une journée entière mal formatée reste invisible. On sous-traite donc à des
|
||||
// processus réglés ailleurs.
|
||||
describe.each(["Asia/Tokyo", "Pacific/Auckland", "America/New_York", "Europe/Paris"])(
|
||||
"sous TZ=%s",
|
||||
(fuseau) => {
|
||||
test("journée entière → la date ne glisse pas d'un jour", async () => {
|
||||
expect(await formaterDansLeFuseau(fuseau)).toContain(JOUR_ATTENDU);
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
// Le fixture traverse la frontière de processus en code source : c'est la même
|
||||
// constante JOURNEE_ENTIERE, donc les deux tests ne peuvent pas diverger.
|
||||
async function formaterDansLeFuseau(fuseau: string): Promise<string> {
|
||||
const module = JSON.stringify(join(import.meta.dir, "extension/presentation.js"));
|
||||
const processus = Bun.spawn(
|
||||
[
|
||||
"bun",
|
||||
"-e",
|
||||
`import { formaterDate } from ${module};
|
||||
console.log(formaterDate({
|
||||
debut: new Date(${JOURNEE_ENTIERE.join(", ")}),
|
||||
journeeEntiere: true,
|
||||
}));`,
|
||||
],
|
||||
{ env: { ...process.env, TZ: fuseau }, stdout: "pipe", stderr: "pipe" },
|
||||
);
|
||||
|
||||
const [sortie, erreur, code] = await Promise.all([
|
||||
new Response(processus.stdout).text(),
|
||||
new Response(processus.stderr).text(),
|
||||
processus.exited,
|
||||
]);
|
||||
if (code !== 0) throw new Error(`bun -e a échoué (${code}) : ${erreur}`);
|
||||
return sortie;
|
||||
}
|
||||
|
||||
describe("formaterLieu", () => {
|
||||
test("lieu renseigné → rendu tel quel", () => {
|
||||
expect(formaterLieu(evenement({ lieu: "Salle des fêtes" }))).toBe("Salle des fêtes");
|
||||
});
|
||||
|
||||
test("lieu vide → adresse du café", () => {
|
||||
expect(formaterLieu(evenement({ lieu: "" }))).toBe(LIEU_PAR_DEFAUT);
|
||||
});
|
||||
|
||||
test("lieu en blancs → adresse du café", () => {
|
||||
expect(formaterLieu(evenement({ lieu: " " }))).toBe(LIEU_PAR_DEFAUT);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user