Files
Socialflow/shared/agenda.test.ts
T
Claude 53ab6f489a Afficher les publications déjà prévues par jour et par magasin
Le sélecteur de date des Reels ne montrait rien de ce qui était déjà
programmé, et celui des posts ne marquait qu'un jour « occupé » sans
dire pour quel magasin ni à quelle heure.

- GET /api/scheduled-posts/agenda : publications planifiées et Reels
  encore en montage (qui n'ont pas de scheduled_posts avant la fin du
  rendu), limités aux magasins accessibles.
- Calendrier : pastille par jour, en rouge si un magasin coché a déjà
  une publication, en gris s'il s'agit d'autres magasins.
- Heures : badge sur les heures déjà prises sur les magasins cochés.
- Panneau « Déjà prévu ce jour-là » groupé par magasin, avec une
  alerte non bloquante en cas de doublon.
- Branché sur Nouveau post, Reel et Programmer un Reel.
- Icône Reel dans la vue calendrier.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TMU8FG2v2EE54YWHhqehPw
2026-10-12 10:19:51 +00:00

89 lines
3.6 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
agendaItemFromScheduledPost,
agendaItemsFromReelJob,
busyHours,
conflictsFor,
groupByDay,
groupByPage,
type AgendaItem,
type AgendaPage,
} from "./agenda";
const storeA: AgendaPage = { id: "a", pageName: "Magasin A", platform: "facebook", avatarUrl: null };
const storeB: AgendaPage = { id: "b", pageName: "Magasin B", platform: "tiktok", avatarUrl: null };
const pagesById = new Map([[storeA.id, storeA], [storeB.id, storeB]]);
function item(overrides: Partial<AgendaItem>): AgendaItem {
return {
id: "x",
postId: "p",
pageId: "a",
pageName: "Magasin A",
platform: "facebook",
avatarUrl: null,
postType: "reel",
scheduledAt: new Date(2026, 9, 14, 18, 0).toISOString(),
status: "scheduled",
preview: "",
...overrides,
};
}
describe("agendaItemFromScheduledPost", () => {
it("déduit le statut des colonnes de publication", () => {
const base = { id: "s", postId: "p", pageId: "a", postType: "reel", scheduledAt: new Date(), page: storeA, post: { content: "Promo" } };
expect(agendaItemFromScheduledPost({ ...base, publishedAt: null, error: null }).status).toBe("scheduled");
expect(agendaItemFromScheduledPost({ ...base, publishedAt: new Date(), error: null }).status).toBe("published");
expect(agendaItemFromScheduledPost({ ...base, publishedAt: null, error: "boom" }).status).toBe("failed");
});
});
describe("agendaItemsFromReelJob", () => {
const when = new Date(2026, 9, 14, 18, 0);
it("crée une entrée « en montage » par magasin visible", () => {
const items = agendaItemsFromReelJob(
{ id: "j", postId: "p", createdAt: new Date(), params: { pageIds: ["a", "b", "inconnu"], scheduledFor: when.toISOString() } },
pagesById,
);
expect(items.map((i) => i.pageId)).toEqual(["a", "b"]);
expect(items.every((i) => i.status === "rendering" && i.postType === "reel")).toBe(true);
expect(items[0].scheduledAt).toBe(when.toISOString());
});
it("place un Reel sans date à sa création et respecte la période", () => {
const createdAt = new Date(2026, 9, 12, 9, 0);
const job = { id: "j", postId: "p", createdAt, params: { pageIds: ["a"] } };
expect(agendaItemsFromReelJob(job, pagesById)[0].scheduledAt).toBe(createdAt.toISOString());
expect(agendaItemsFromReelJob(job, pagesById, new Date(2026, 9, 13))).toEqual([]);
});
});
describe("regroupements", () => {
const items = [
item({ id: "1", pageId: "b", pageName: "Magasin B", scheduledAt: new Date(2026, 9, 14, 12, 0).toISOString() }),
item({ id: "2", pageId: "a", scheduledAt: new Date(2026, 9, 14, 18, 0).toISOString() }),
item({ id: "3", pageId: "a", scheduledAt: new Date(2026, 9, 14, 18, 30).toISOString(), status: "failed" }),
item({ id: "4", pageId: "a", scheduledAt: new Date(2026, 9, 15, 8, 0).toISOString() }),
];
it("range les publications par jour local", () => {
const days = groupByDay(items);
expect(days.get("2026-10-14")?.map((i) => i.id)).toEqual(["1", "2", "3"]);
expect(days.get("2026-10-15")?.map((i) => i.id)).toEqual(["4"]);
});
it("met les magasins choisis en tête", () => {
const groups = groupByPage(groupByDay(items).get("2026-10-14")!, ["b"]);
expect(groups.map((g) => [g.page.id, g.selected])).toEqual([["b", true], ["a", false]]);
});
it("ne signale que les magasins choisis et ignore les échecs", () => {
const day = groupByDay(items).get("2026-10-14")!;
expect(conflictsFor(day, ["a"]).map((i) => i.id)).toEqual(["2"]);
expect(conflictsFor(day, [])).toEqual([]);
expect(Array.from(busyHours(day, ["a", "b"]).entries())).toEqual([[12, 1], [18, 1]]);
});
});