mirror of
https://github.com/jakejarvis/sofa.git
synced 2026-07-14 18:15:56 -04:00
- Replace bun:test with Vitest across all 22 test files - Create @sofa/test package with shared setup and DB test helpers - setup.ts: vi.mock for @sofa/db/client, Bun.randomUUIDv7 polyfill - db.ts: in-memory SQLite via better-sqlite3, seed helpers - Add per-project vitest configs (packages/core, apps/web) - Add root vitest.config.ts with projects and v8 coverage - Set up @vitest/browser + Playwright for web component tests - Move validateBackupDatabase from core/backup.ts to @sofa/db/client - Derive required backup tables from schema instead of hard-coded list - Update CI workflow with Playwright install and Codecov upload - Update CLAUDE.md documentation for Vitest Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
189 lines
5.1 KiB
TypeScript
189 lines
5.1 KiB
TypeScript
import { beforeEach, describe, expect, test, vi } from "vitest";
|
|
|
|
import { episodes, seasons, titles } from "@sofa/db/schema";
|
|
import { clearAllTables, eq, insertTitle, testDb } from "@sofa/test/db";
|
|
|
|
interface MockSeasonEpisode {
|
|
episode_number: number;
|
|
name: string | null;
|
|
overview: string | null;
|
|
still_path: string | null;
|
|
air_date: string | null;
|
|
runtime: number | null;
|
|
}
|
|
|
|
interface MockSeasonDetails {
|
|
season_number: number;
|
|
name: string | null;
|
|
overview: string | null;
|
|
poster_path: string | null;
|
|
air_date: string | null;
|
|
episodes: MockSeasonEpisode[];
|
|
}
|
|
|
|
const defaultSeasonDetails: MockSeasonDetails = {
|
|
season_number: 1,
|
|
name: "Season 1",
|
|
overview: null,
|
|
poster_path: "/new-season.png",
|
|
air_date: null,
|
|
episodes: [
|
|
{
|
|
episode_number: 1,
|
|
name: "Episode 1",
|
|
overview: null,
|
|
still_path: "/new-still.png",
|
|
air_date: null,
|
|
runtime: null,
|
|
},
|
|
],
|
|
};
|
|
|
|
const { mockGetTvSeasonDetails } = vi.hoisted(() => ({
|
|
mockGetTvSeasonDetails: vi.fn(),
|
|
}));
|
|
|
|
vi.mock("@sofa/tmdb/client", () => ({
|
|
getMovieDetails: async () => {
|
|
throw new Error("not used");
|
|
},
|
|
getRecommendations: async () => ({ results: [] }),
|
|
getSimilar: async () => ({ results: [] }),
|
|
getTvDetails: async () => {
|
|
throw new Error("not used");
|
|
},
|
|
getTvSeasonDetails: mockGetTvSeasonDetails,
|
|
getVideos: async () => ({ results: [] }),
|
|
}));
|
|
|
|
import { refreshTvChildren, updateTitleWithArtInvalidation } from "../src/metadata";
|
|
|
|
beforeEach(() => {
|
|
clearAllTables();
|
|
mockGetTvSeasonDetails.mockImplementation(async () => ({
|
|
...defaultSeasonDetails,
|
|
episodes: [...defaultSeasonDetails.episodes],
|
|
}));
|
|
});
|
|
|
|
describe("refreshTvChildren", () => {
|
|
test("clears stale season and episode thumbhashes when artwork paths change", async () => {
|
|
insertTitle({ id: "tv-1", tmdbId: 10, type: "tv", title: "Show" });
|
|
testDb
|
|
.insert(seasons)
|
|
.values({
|
|
id: "season-1",
|
|
titleId: "tv-1",
|
|
seasonNumber: 1,
|
|
posterPath: "/old-season.png",
|
|
posterThumbHash: "stale-season-hash",
|
|
})
|
|
.run();
|
|
testDb
|
|
.insert(episodes)
|
|
.values({
|
|
id: "episode-1",
|
|
seasonId: "season-1",
|
|
episodeNumber: 1,
|
|
stillPath: "/old-still.png",
|
|
stillThumbHash: "stale-episode-hash",
|
|
})
|
|
.run();
|
|
|
|
await refreshTvChildren("tv-1", 10, 1);
|
|
|
|
const season = testDb.select().from(seasons).where(eq(seasons.id, "season-1")).get();
|
|
const episode = testDb.select().from(episodes).where(eq(episodes.id, "episode-1")).get();
|
|
|
|
expect(season?.posterPath).toBe("/new-season.png");
|
|
expect(season?.posterThumbHash).toBeNull();
|
|
expect(episode?.stillPath).toBe("/new-still.png");
|
|
expect(episode?.stillThumbHash).toBeNull();
|
|
});
|
|
|
|
test("clears an episode thumbhash when the still disappears", async () => {
|
|
insertTitle({ id: "tv-1", tmdbId: 10, type: "tv", title: "Show" });
|
|
testDb
|
|
.insert(seasons)
|
|
.values({
|
|
id: "season-1",
|
|
titleId: "tv-1",
|
|
seasonNumber: 1,
|
|
posterPath: "/season.png",
|
|
})
|
|
.run();
|
|
testDb
|
|
.insert(episodes)
|
|
.values({
|
|
id: "episode-1",
|
|
seasonId: "season-1",
|
|
episodeNumber: 1,
|
|
stillPath: "/old-still.png",
|
|
stillThumbHash: "stale-episode-hash",
|
|
})
|
|
.run();
|
|
|
|
mockGetTvSeasonDetails.mockImplementation(async () => ({
|
|
...defaultSeasonDetails,
|
|
poster_path: "/season.png",
|
|
episodes: [
|
|
{
|
|
episode_number: 1,
|
|
name: "Episode 1",
|
|
overview: null,
|
|
still_path: null,
|
|
air_date: null,
|
|
runtime: null,
|
|
},
|
|
],
|
|
}));
|
|
|
|
await refreshTvChildren("tv-1", 10, 1);
|
|
|
|
const episode = testDb.select().from(episodes).where(eq(episodes.id, "episode-1")).get();
|
|
|
|
expect(episode?.stillPath).toBeNull();
|
|
expect(episode?.stillThumbHash).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe("updateTitleWithArtInvalidation", () => {
|
|
test("clears stale title hashes and palette when artwork changes", () => {
|
|
testDb
|
|
.insert(titles)
|
|
.values({
|
|
id: "movie-1",
|
|
tmdbId: 20,
|
|
type: "movie",
|
|
title: "Movie",
|
|
posterPath: "/old-poster.png",
|
|
posterThumbHash: "stale-poster-hash",
|
|
backdropPath: "/old-backdrop.png",
|
|
backdropThumbHash: "stale-backdrop-hash",
|
|
colorPalette: JSON.stringify({ vibrant: "#fff" }),
|
|
lastFetchedAt: new Date(),
|
|
})
|
|
.run();
|
|
|
|
const existing = testDb.select().from(titles).where(eq(titles.id, "movie-1")).get();
|
|
|
|
expect(existing).not.toBeUndefined();
|
|
if (!existing) {
|
|
throw new Error("Expected seeded title row");
|
|
}
|
|
|
|
updateTitleWithArtInvalidation(existing, {
|
|
posterPath: "/new-poster.png",
|
|
backdropPath: "/new-backdrop.png",
|
|
});
|
|
|
|
const title = testDb.select().from(titles).where(eq(titles.id, "movie-1")).get();
|
|
|
|
expect(title?.posterPath).toBe("/new-poster.png");
|
|
expect(title?.backdropPath).toBe("/new-backdrop.png");
|
|
expect(title?.posterThumbHash).toBeNull();
|
|
expect(title?.backdropThumbHash).toBeNull();
|
|
expect(title?.colorPalette).toBeNull();
|
|
});
|
|
});
|