fix: make timeline activation durable and visible

This commit is contained in:
ekklesia
2026-09-29 18:15:08 -05:00
parent 92bef4c7aa
commit 5760059ee3
5 changed files with 156 additions and 11 deletions
+82
View File
@@ -120,6 +120,16 @@ const PLAYBACK_DETAIL = {
snapshot: GROUP.playback,
};
const TIMELINE = {
description: null,
eligibleFrom: null,
eligibleUntil: null,
id: "timeline-1",
name: "Sunday morning",
revision: 3,
updatedAt: "2026-08-30T15:00:00.000Z",
};
beforeEach(() => {
container = document.createElement("div");
document.body.append(container);
@@ -194,6 +204,78 @@ describe("Signage dashboard", () => {
expect(container.querySelector("[data-testid='signage-controls']")).toBeNull();
});
it("shows which timeline remains active after the dashboard data is reloaded", async () => {
const host = createFakeHost({
permissions: ["signage.view", "signage.playback.control"],
routes: [
{ method: "GET", path: "settings", respond: () => jsonResponse(FAKE_SETTINGS) },
{ method: "GET", path: "channel-groups", respond: () => jsonResponse({ groups: [GROUP] }) },
{
method: "GET",
path: "timelines",
respond: () => jsonResponse({ timelines: [TIMELINE] }),
},
{ method: "GET", path: "playback/group-1", respond: () => jsonResponse(PLAYBACK_DETAIL) },
{ method: "GET", path: "providers", respond: () => jsonResponse({ providers: [] }) },
],
});
await render(<SignageDashboard context={host.context} />);
expect(container.textContent).toContain("Active: Sunday morning");
await click(
[...container.querySelectorAll("[role='tab']")].find(
(candidate) => candidate.textContent === "Timelines",
),
);
expect(container.textContent).toContain("Active in Main");
expect(buttonNamed("Active")?.disabled).toBe(true);
});
it("keeps a failed activation visible instead of pretending it succeeded", async () => {
const inactiveGroup = {
...GROUP,
playback: { ...GROUP.playback, activeTimelineId: null, currentSelectionId: null },
};
const host = createFakeHost({
permissions: ["signage.view", "signage.playback.control"],
routes: [
{ method: "GET", path: "settings", respond: () => jsonResponse(FAKE_SETTINGS) },
{
method: "GET",
path: "channel-groups",
respond: () => jsonResponse({ groups: [inactiveGroup] }),
},
{
method: "GET",
path: "timelines",
respond: () => jsonResponse({ timelines: [TIMELINE] }),
},
{ method: "GET", path: "playback/group-1", respond: () => jsonResponse(PLAYBACK_DETAIL) },
{ method: "GET", path: "providers", respond: () => jsonResponse({ providers: [] }) },
{
method: "POST",
path: "playback/group-1/activate",
respond: () =>
errorResponse("SIGNAGE_CONFLICT", "The request conflicts with the current state.", 409),
},
],
});
await render(<SignageDashboard context={host.context} />);
await click(
[...container.querySelectorAll("[role='tab']")].find(
(candidate) => candidate.textContent === "Timelines",
),
);
await click(buttonNamed("Activate"));
expect(container.textContent).toContain("Sunday morning");
expect(container.querySelector("[role='alert']")?.textContent).toContain(
"The request conflicts with the current state.",
);
});
it("shows Signage's own sanitized reason when a save is refused", async () => {
const host = createFakeHost({
permissions: ["signage.view", "signage.timelines.manage"],