import { describe, expect, it } from "vitest"; import { readSources } from "../../lib/rawSources.testkit"; // docs/DESIGN.md "Text stays selectable": a click surface whose text is a title or a name (a card, a // row, a person, a channel, a playlist, a log line) is `; /** file → [how many plain Pressables, why they are not titles]. */ const PLAIN: Record = { "modules/settings/ApiTokens.tsx": [ 1, "a token kind's tile in the new-token dialog (an option: its text is its label)", ], "features/downloads/DownloadTray.tsx": [ 2, "the folded pill (its summary is its label); a failure's reason, which opens its details", ], "modules/files/FileCard.tsx": [ 1, "a card's picture (named by aria-label; its title is the caption)", ], "modules/files/FilesRail.tsx": [2, "the load-error retry; the New-folder command"], "modules/home/Home.tsx": [ 2, "the hero's picture and a shelf tile's picture (each named by aria-label; its title sits outside)", ], "components/ui/LanguageSwitcher.tsx": [1, "an option row"], "shell/NavSidebar.tsx": [3, "the account trigger and its menu rows"], "features/header/PaletteLauncher.tsx": [2, "launcher controls"], "modules/playlists/PlaylistsRail.tsx": [1, "the load-error retry"], "modules/plex/PlexCollectionEditor.tsx": [1, "a checkbox row in a picker"], "modules/plex/PlexInfo.tsx": [1, "the rating filter (a number)"], "modules/plex/PlexPlayerSettings.tsx": [1, "the clock's date-style options"], "features/plexCards/PlexPlaylistAdd.tsx": [1, "a checkbox row in a picker"], "modules/plex/PlexPlaylistView.tsx": [1, "a show's poster stack (aria-hidden)"], "modules/plex/PlexSearchBox.tsx": [1, "a listbox option"], "features/plexCards/plexCards.tsx": [ 1, "a card's picture (named by aria-label; its title is the caption)", ], "modules/scheduler/Scheduler.tsx": [1, "the interval editor trigger"], "modules/settings/SettingsPanel.tsx": [1, "a colour swatch"], "modules/channels/SyncStatus.tsx": [1, "the status pill trigger"], "features/channel/TagManager.tsx": [1, "a suggestion in a picker"], "components/ui/DropZone.tsx": [1, "the drop zone (its text is its own prompt)"], "modules/files/VideoEditor.tsx": [2, "timeline glyphs"], "shell/Welcome.tsx": [1, "a screenshot (zoom)"], "modules/playlists/playlistColumns.tsx": [1, "the thumbnail"], "modules/plex/plexDetailUi.tsx": [2, "a switch row and the poster"], "modules/plex/plexViews.tsx": [1, "the rating filter (a number)"], "components/ui/ActionMenu.tsx": [1, "a menu row"], "components/ui/BackToTop.tsx": [1, "a control"], "components/ui/CommandPalette.tsx": [1, "a command row"], "components/ui/NavRail.tsx": [ 2, "the action rows (About, Sign out) and the group row that opens its panel; a page row is a link", ], "components/ui/PanelGroup.tsx": [2, "a group's collapse toggle and its label"], "components/ui/SidePanel.tsx": [2, "panel controls"], "components/ui/tableHeader.tsx": [1, "a column's sort header"], }; function scan(): { plain: Record; selectable: number } { const plain: Record = {}; let selectable = 0; for (const [path, code] of Object.entries(files)) { // Glob keys are relative to this folder (`./x.tsx` is ui/); the table keys them from src/. const rel = path.startsWith("./") ? `components/ui/${path.slice(2)}` : path.replace(/^(\.\.\/)+/, ""); for (const m of code.matchAll(/` outside any `{…}` expression, quoted string or * line comment (an arrow's `=>` sits inside braces, a class like `[&>svg]` inside quotes). */ function openingTag(code: string, from: number): string { let depth = 0; let quote: string | null = null; for (let i = from + 1; i < code.length; i++) { const c = code[i]; if (quote) { if (c === quote) quote = null; } else if (c === "/" && code[i + 1] === "/") { i = code.indexOf("\n", i); // a comment between the props: its apostrophes are not quotes if (i < 0) break; } else if (c === '"' || c === "'" || c === "`") quote = c; else if (c === "{") depth++; else if (c === "}") depth--; else if (c === ">" && depth === 0) return code.slice(from, i + 1); } return code.slice(from); } describe("every Pressable decides whether its text is a title", () => { it("finds the selectable ones, and no plain one outside the pinned list", () => { const { plain, selectable } = scan(); // A scan that stopped matching would be green over nothing. expect(selectable).toBeGreaterThan(15); const pinned = Object.fromEntries(Object.entries(PLAIN).map(([f, [n]]) => [f, n])); expect(plain).toEqual(pinned); }); });