// @vitest-environment jsdom // // jsdom only because the module graph reaches `lib/api` -> `i18n`, which reads localStorage at // import time; nothing here touches the DOM. // The two pure rules behind the comfort shelf (E24 S3). Both are one-liners that eight call sites // depend on, and both are exactly the kind of mapping that rots silently: a wrong pin kind is a 404 // nobody sees until they try that one surface, and a move rule that disagrees with the menu row's // disabled state shows an enabled row that no-ops. import { describe, it, expect } from "vitest"; import { pinTargetForCard, shelfMoveTarget } from "./comfortPin"; import type { DiscoverCard } from "../lib/api"; const card = (over: Partial): DiscoverCard => ({ source: "youtube", kind: "video", id: "x", title: "t", subtitle: null, thumb: null, aspect: "wide", duration_seconds: null, position_seconds: 0, remaining_seconds: null, status: "new", video: null, plex: null, ...over, }) as DiscoverCard; describe("pinTargetForCard", () => { it("pins a Plex SHOW as a show and every other Plex tile as a leaf item", () => { // The distinction the whole shelf turns on: "put that series on" has to resolve to an episode, // so a show may not be stored as the item it is browsed as. expect(pinTargetForCard(card({ source: "plex", kind: "show", id: "1234" }))).toEqual({ kind: "plex_show", ref: "1234", }); expect(pinTargetForCard(card({ source: "plex", kind: "movie", id: "99" }))).toEqual({ kind: "plex_item", ref: "99", }); expect(pinTargetForCard(card({ source: "plex", kind: "episode", id: "77" }))).toEqual({ kind: "plex_item", ref: "77", }); }); it("maps the three YouTube kinds to their own columns", () => { expect(pinTargetForCard(card({ kind: "video", id: "vid" }))).toEqual({ kind: "video", ref: "vid", }); expect(pinTargetForCard(card({ kind: "channel", id: "UC1" }))).toEqual({ kind: "channel", ref: "UC1", }); expect(pinTargetForCard(card({ kind: "playlist", id: "12" }))).toEqual({ kind: "playlist", ref: "12", }); }); }); describe("shelfMoveTarget", () => { it("moves within the shelf", () => { expect(shelfMoveTarget(2, "first", 4)).toBe(0); expect(shelfMoveTarget(2, "left", 4)).toBe(1); expect(shelfMoveTarget(2, "right", 4)).toBe(3); expect(shelfMoveTarget(2, "last", 4)).toBe(3); }); it("reports -1 for every move that would go nowhere", () => { // This is what the menu reads to disable a row, so each of these is a row the user must not be // able to press onto a no-op. expect(shelfMoveTarget(0, "first", 4)).toBe(-1); expect(shelfMoveTarget(0, "left", 4)).toBe(-1); expect(shelfMoveTarget(3, "right", 4)).toBe(-1); expect(shelfMoveTarget(3, "last", 4)).toBe(-1); // A single-item shelf can do none of the four. expect(shelfMoveTarget(0, "right", 1)).toBe(-1); expect(shelfMoveTarget(0, "last", 1)).toBe(-1); }); it("reports -1 for an index that is not on the shelf", () => { // `findIndex` returns -1 when the shelf has already been refetched without that pin — the menu // may still be open over the tile that was there a moment ago. expect(shelfMoveTarget(-1, "right", 4)).toBe(-1); expect(shelfMoveTarget(9, "left", 4)).toBe(-1); }); });