The module shows fewer lists than youtube.com, and without a word about it that read as a Siftlode bug (the owner's ask, 2026-09-03). Owner's pick from the S7 mockups (2026-09-19): one quiet line at the foot of the rail, "Missing a list? Why?", whose Why? opens three facts — YouTube's own Watch later and History are closed to its API (the Watch later list here is Siftlode's own), only lists you own sync, and an account with no YouTube channel has no playlists at all.
47 lines
1.9 KiB
TypeScript
47 lines
1.9 KiB
TypeScript
// @vitest-environment jsdom
|
|
//
|
|
// The playlists module shows fewer lists than youtube.com, and said nothing about why, so it read as
|
|
// a Siftlode bug (debt burn A3). One quiet line at the foot of the rail; "Why?" opens the three facts.
|
|
import { describe, it, expect, vi, afterEach, beforeAll } from "vitest";
|
|
import { render, screen, fireEvent, cleanup } from "@testing-library/react";
|
|
import { FloatingTree } from "@floating-ui/react";
|
|
|
|
vi.mock("react-i18next", () => ({ useTranslation: () => ({ t: (k: string) => k }) }));
|
|
|
|
import PlaylistLimitsHint from "./PlaylistLimitsHint";
|
|
|
|
// Same floating-ui recipe as ContextMenu.dom.test.tsx: stub the observers, and query with
|
|
// `hidden: true` — floating-ui never positions in jsdom, so the surface stays visibility:hidden.
|
|
beforeAll(() => {
|
|
class Stub {
|
|
observe() {}
|
|
unobserve() {}
|
|
disconnect() {}
|
|
takeRecords() {
|
|
return [];
|
|
}
|
|
}
|
|
globalThis.ResizeObserver ??= Stub as unknown as typeof ResizeObserver;
|
|
globalThis.IntersectionObserver ??= Stub as unknown as typeof IntersectionObserver;
|
|
});
|
|
afterEach(cleanup);
|
|
|
|
describe("PlaylistLimitsHint", () => {
|
|
it("keeps the facts behind Why?, and opens them as a named dialog", async () => {
|
|
render(
|
|
<FloatingTree>
|
|
<PlaylistLimitsHint />
|
|
</FloatingTree>,
|
|
);
|
|
expect(screen.getByText("playlists.limits.hint")).toBeTruthy();
|
|
expect(screen.queryByRole("dialog", { hidden: true })).toBeNull();
|
|
|
|
fireEvent.click(screen.getByRole("button", { name: "playlists.limits.why" }));
|
|
const dialog = await screen.findByRole("dialog", { hidden: true });
|
|
// Read off the attribute: jsdom computes no name for a visibility:hidden element.
|
|
expect(dialog.getAttribute("aria-label")).toBe("playlists.limits.title");
|
|
for (const fact of ["closed", "ownOnly", "noChannel"])
|
|
expect(dialog.textContent).toContain(`playlists.limits.${fact}`);
|
|
});
|
|
});
|