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 = { "DownloadTray.tsx": [ 2, "the folded pill (its summary is its label); a failure's reason, which opens its details", ], "FileCard.tsx": [1, "a card's picture (named by aria-label; its title is the caption)"], "FilesRail.tsx": [2, "the load-error retry; the New-folder command"], "Home.tsx": [ 2, "the hero's picture and a shelf tile's picture (each named by aria-label; its title sits outside)", ], "LanguageSwitcher.tsx": [1, "an option row"], "NavSidebar.tsx": [4, "the account trigger and its menu rows"], "PaletteLauncher.tsx": [2, "launcher controls"], "PlaylistsRail.tsx": [1, "the load-error retry"], "PlexCollectionEditor.tsx": [1, "a checkbox row in a picker"], "PlexInfo.tsx": [1, "the rating filter (a number)"], "PlexPlayerSettings.tsx": [1, "the clock's date-style options"], "PlexPlaylistAdd.tsx": [1, "a checkbox row in a picker"], "PlexPlaylistView.tsx": [1, "a show's poster stack (aria-hidden)"], "PlexSearchBox.tsx": [1, "a listbox option"], "plexCards.tsx": [1, "a card's picture (named by aria-label; its title is the caption)"], "Scheduler.tsx": [1, "the interval editor trigger"], "SettingsPanel.tsx": [1, "a colour swatch"], "SyncStatus.tsx": [1, "the status pill trigger"], "TagManager.tsx": [1, "a suggestion in a picker"], "UploadDialog.tsx": [1, "the drop zone"], "VideoEditor.tsx": [2, "timeline glyphs"], "Welcome.tsx": [1, "a screenshot (zoom)"], "playlistColumns.tsx": [1, "the thumbnail"], "plexDetailUi.tsx": [2, "a switch row and the poster"], "plexViews.tsx": [1, "the rating filter (a number)"], "ui/ActionMenu.tsx": [1, "a menu row"], "ui/BackToTop.tsx": [1, "a control"], "ui/CommandPalette.tsx": [1, "a command row"], "ui/NavRail.tsx": [ 2, "the action rows (About, Sign out) and the group row that opens its panel; a page row is a link", ], "ui/PanelGroup.tsx": [2, "a group's collapse toggle and its label"], "ui/SidePanel.tsx": [2, "panel controls"], "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/, `../x.tsx` is components/. const rel = path.startsWith("./") ? `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); }); });