webOS: a TV app with the views of arr-tui
A TypeScript web app for LG webOS TVs (webOS 5 / Chromium 68 and newer), driven by the remote: Movies and Series search with the Trakt lists, Library with status, title and file-property filters and download marks, seasons and episodes, release search and grab, Downloads, Calendar, Settings and Log. It talks to Radarr, Sonarr, SABnzbd and Trakt directly (they answer cross-origin requests); the arr-tui config is baked in at build time and can be changed on the TV. dev/screenshots/server.py answers cross-origin requests and accepts writes for it; webos/dev/drive.mjs drives the app in Chromium for screenshots. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
import { deepStrictEqual, strictEqual } from "node:assert";
|
||||
import { test } from "node:test";
|
||||
import { errorMessage, queueName, queueProgress, scaledVariant, tmdbSized, type QueueItem } from "../src/api/arr.ts";
|
||||
import { compact, decodeList, movieLists, refKey } from "../src/api/trakt.ts";
|
||||
import { formatReleaseAge, formatSize } from "../src/format.ts";
|
||||
import { episodeStatus, summarizeSeasons } from "../src/seasons.ts";
|
||||
import type { Episode } from "../src/api/sonarr.ts";
|
||||
|
||||
test("poster URLs", () => {
|
||||
strictEqual(scaledVariant("/MediaCover/1/poster.jpg?lastWrite=5", 500), "/MediaCover/1/poster-500.jpg?lastWrite=5");
|
||||
strictEqual(scaledVariant("/MediaCover/1/fanart.jpg", 500), "/MediaCover/1/fanart.jpg");
|
||||
strictEqual(tmdbSized("https://image.tmdb.org/t/p/original/abc.jpg", true), "https://image.tmdb.org/t/p/w780/abc.jpg");
|
||||
strictEqual(tmdbSized("https://image.tmdb.org/t/p/original/abc.jpg", false), "https://image.tmdb.org/t/p/w342/abc.jpg");
|
||||
strictEqual(tmdbSized("https://artworks.thetvdb.com/banners/1.jpg", true), "https://artworks.thetvdb.com/banners/1.jpg");
|
||||
});
|
||||
|
||||
test("error messages", () => {
|
||||
strictEqual(errorMessage('{"message":"Movie exists"}'), "Movie exists");
|
||||
strictEqual(errorMessage('[{"propertyName":"Path","errorMessage":"Bad path"},{"errorMessage":"Other"}]'), "Bad path; Other");
|
||||
strictEqual(errorMessage(" plain "), "plain");
|
||||
});
|
||||
|
||||
test("queue items", () => {
|
||||
const q = { id: 1, title: "rel", status: "", size: 100, sizeleft: 25, series: { title: "Show" }, episode: { seasonNumber: 1, episodeNumber: 2, title: "x" } } as QueueItem;
|
||||
strictEqual(queueName(q), "Show S01E02");
|
||||
strictEqual(queueProgress(q), 75);
|
||||
});
|
||||
|
||||
test("trakt", () => {
|
||||
strictEqual(compact(950), "950");
|
||||
strictEqual(compact(1234), "1.2k");
|
||||
strictEqual(compact(2000), "2k");
|
||||
strictEqual(compact(34567), "34k");
|
||||
strictEqual(compact(1_500_000), "1.5M");
|
||||
strictEqual(compact(48_000_000), "48M");
|
||||
const entries = decodeList([{ watchers: 1234, movie: { title: "Dune", year: 2021, ids: { trakt: 1, tmdb: 438631 } } }], movieLists[0]);
|
||||
deepStrictEqual(entries, [{ title: "Dune", year: 2021, ids: { trakt: 1, tmdb: 438631 }, count: 1234 }]);
|
||||
const bare = decodeList([{ title: "Heat", year: 1995, ids: { trakt: 2, tmdb: 949 } }], movieLists[3]);
|
||||
strictEqual(bare[0].title, "Heat");
|
||||
strictEqual(refKey({ kind: "show", tvdb: 81189 }), "show:tvdb:81189");
|
||||
});
|
||||
|
||||
test("formatting", () => {
|
||||
strictEqual(formatSize(1.5 * 1024 ** 3), "1.5 GB");
|
||||
strictEqual(formatSize(0), "–");
|
||||
strictEqual(formatReleaseAge(30), "30m");
|
||||
strictEqual(formatReleaseAge(300), "5h");
|
||||
strictEqual(formatReleaseAge(5000), "3d");
|
||||
});
|
||||
|
||||
test("seasons", () => {
|
||||
const ep = (s: number, n: number, hasFile: boolean) => ({ id: s * 100 + n, seriesId: 1, seasonNumber: s, episodeNumber: n, title: "", hasFile, monitored: true }) as Episode;
|
||||
deepStrictEqual(summarizeSeasons([ep(0, 1, false), ep(1, 1, true), ep(2, 1, false), ep(2, 2, true)]).map((s) => [s.number, s.have, s.total]), [[2, 1, 2], [1, 1, 1], [0, 0, 1]]);
|
||||
deepStrictEqual(episodeStatus({ ...ep(1, 1, false), airDate: "2026-01-01" }, "2026-02-01"), ["missing", "warn"]);
|
||||
deepStrictEqual(episodeStatus({ ...ep(1, 1, false), airDate: "2026-03-01" }, "2026-02-01"), ["unaired", "muted"]);
|
||||
});
|
||||
Reference in New Issue
Block a user