Files
siftlode/frontend/src/lib/notifications.actionTtl.dom.test.ts
T
peter 1b946113c6 feat(design): a channel's and a Plex playlist's one action list (C1) — the row's "…", Delete + undo, an action toast's 8 s floor (E28 S3e2)
- Channels: the row's full-history, hide and unsubscribe buttons become its "…" at the row end
  (useChannelActions: open, pin, YouTube, copy name / link, full history or the admin reset,
  hide with an undo toast, unsubscribe behind its danger confirm); the card layout docks the "…"
  beside the name (ChannelLayoutGrid moreKey). The admin reset's confirm now names the reset.
- Plex playlist page: a title, a show group and a season each open the page's one row menu (a
  title's is its usePlexCardActions list plus the removal); no X at rest; removals take an undo
  toast whose Undo puts every row back at its own index in one request (bulk add `positions`);
  j/k walk the visible rows, Delete removes the walked one; rename and delete in the header's "…".
- notify: a toast with an action lives at least 8 s (the owner's call), whatever the toast setting.
2026-09-26 05:56:07 +02:00

53 lines
1.8 KiB
TypeScript

// @vitest-environment jsdom
//
// A toast with an action (the list page's "Undo") outlives a short toast setting: its button is the
// only way back, so it gets ACTION_TTL_MIN at least (owner, E28 S3e2). The setting still wins when it
// is longer, "never" still means never, and a plain toast keeps the reader's time.
import { describe, it, expect, afterEach, beforeEach, vi } from "vitest";
import {
ACTION_TTL_MIN,
clearAll,
configureNotifications,
getActiveToasts,
notify,
} from "./notifications";
beforeEach(() => vi.useFakeTimers());
afterEach(() => {
clearAll();
configureNotifications({ durationMs: 6000 });
vi.useRealTimers();
});
const undo = { label: "Undo", onClick: () => {} };
const shown = (id: number) => getActiveToasts().some((n) => n.id === id);
describe("an action toast's lifetime", () => {
it("stays for the floor when the reader's toasts last 3 s", () => {
configureNotifications({ durationMs: 3000 });
const plain = notify({ message: "plain" });
const withUndo = notify({ message: "removed", action: undo });
vi.advanceTimersByTime(3001);
expect(shown(plain)).toBe(false);
expect(shown(withUndo)).toBe(true);
vi.advanceTimersByTime(ACTION_TTL_MIN - 3001);
expect(shown(withUndo)).toBe(false);
});
it("keeps a longer setting", () => {
configureNotifications({ durationMs: 12000 });
const id = notify({ message: "removed", action: undo });
vi.advanceTimersByTime(ACTION_TTL_MIN + 1);
expect(shown(id)).toBe(true);
vi.advanceTimersByTime(12000 - ACTION_TTL_MIN);
expect(shown(id)).toBe(false);
});
it("never dismisses when the reader chose never", () => {
configureNotifications({ durationMs: 0 });
const id = notify({ message: "removed", action: undo });
vi.advanceTimersByTime(60000);
expect(shown(id)).toBe(true);
});
});