mirror of
https://github.com/jakejarvis/sofa.git
synced 2026-08-29 01:35:39 -04:00
refactor: consolidate watchlist states (#18)
- Rename `watchlist` → `in_watchlist` and `in_progress` → `watching` across the full stack (DB migration, Drizzle schema, core services, API contract, web, native) - Add a new `caught_up` status for TV shows where all aired episodes are watched but the show is still airing - Add `titles.watchAll` procedure and `markAllWatched` action to mark every episode of a TV show as watched in one step; replace the old "Mark as Watching" / "Mark as Completed" context-menu actions with "Mark All Watched" (TV) and "Mark as Watched" (movie) - Extract `display-status.ts` in `@sofa/api` to share status → display label/color logic between web and native - Add `getDisplayStatusesByTitleIds` to `@sofa/core/tracking` and wire it into the dashboard library feed so clients receive resolved display statuses - Show a destructive Alert confirmation before removing a title from the library (native) - Remove "Mark as Completed" from the continue-watching card context menu - Update i18n catalogs for all 6 locales (de, en, es, fr, it, pt)
This commit is contained in:
@@ -193,11 +193,12 @@ describe("getSonarrList", () => {
|
||||
title: "Show B",
|
||||
});
|
||||
insertStatus("user-1", "tv1", "watchlist");
|
||||
insertStatus("user-1", "tv2", "completed");
|
||||
insertStatus("user-1", "tv2", "in_progress");
|
||||
|
||||
const watchlist = await getSonarrList("user-1", ["watchlist"]);
|
||||
expect(watchlist).toEqual([{ TvdbId: 111, Title: "Show A" }]);
|
||||
|
||||
// 'completed' maps to 'in_progress' for TV (completion is derived)
|
||||
const all = await getSonarrList("user-1", ["watchlist", "completed"]);
|
||||
expect(all).toHaveLength(2);
|
||||
});
|
||||
|
||||
@@ -235,7 +235,7 @@ describe("logEpisodeWatch", () => {
|
||||
expect(row?.status).toBe("completed");
|
||||
});
|
||||
|
||||
test("auto-completes when all episodes are watched", () => {
|
||||
test("stays in_progress when all episodes are watched", () => {
|
||||
insertUser();
|
||||
const { titleId, episodeIds } = insertTvShow("tv-1", 99999, 1, 3);
|
||||
|
||||
@@ -248,7 +248,7 @@ describe("logEpisodeWatch", () => {
|
||||
.from(userTitleStatus)
|
||||
.where(and(eq(userTitleStatus.userId, "user-1"), eq(userTitleStatus.titleId, titleId)))
|
||||
.get();
|
||||
expect(row?.status).toBe("completed");
|
||||
expect(row?.status).toBe("in_progress");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -281,7 +281,7 @@ describe("logEpisodeWatchBatch", () => {
|
||||
expect(row?.status).toBe("in_progress");
|
||||
});
|
||||
|
||||
test("auto-completes if batch covers all episodes", () => {
|
||||
test("stays in_progress if batch covers all episodes", () => {
|
||||
insertUser();
|
||||
const { titleId, episodeIds } = insertTvShow("tv-1", 99999, 1, 3);
|
||||
logEpisodeWatchBatch("user-1", episodeIds);
|
||||
@@ -291,7 +291,7 @@ describe("logEpisodeWatchBatch", () => {
|
||||
.from(userTitleStatus)
|
||||
.where(and(eq(userTitleStatus.userId, "user-1"), eq(userTitleStatus.titleId, titleId)))
|
||||
.get();
|
||||
expect(row?.status).toBe("completed");
|
||||
expect(row?.status).toBe("in_progress");
|
||||
});
|
||||
|
||||
test("no-op for empty array", () => {
|
||||
@@ -310,7 +310,7 @@ describe("logEpisodeWatchBatch", () => {
|
||||
// ── markAllEpisodesWatched ──────────────────────────────────────────
|
||||
|
||||
describe("markAllEpisodesWatched", () => {
|
||||
test("marks all episodes as watched and sets completed", () => {
|
||||
test("marks all episodes as watched and sets in_progress", () => {
|
||||
insertUser();
|
||||
const { titleId, episodeIds } = insertTvShow("tv-1", 99999, 1, 3);
|
||||
markAllEpisodesWatched("user-1", titleId);
|
||||
@@ -327,7 +327,7 @@ describe("markAllEpisodesWatched", () => {
|
||||
.from(userTitleStatus)
|
||||
.where(and(eq(userTitleStatus.userId, "user-1"), eq(userTitleStatus.titleId, titleId)))
|
||||
.get();
|
||||
expect(row?.status).toBe("completed");
|
||||
expect(row?.status).toBe("in_progress");
|
||||
});
|
||||
|
||||
test("skips already-watched episodes (no duplicates)", () => {
|
||||
@@ -379,23 +379,15 @@ describe("unwatchEpisode", () => {
|
||||
expect(watches).toHaveLength(0);
|
||||
});
|
||||
|
||||
test("downgrades completed to in_progress", () => {
|
||||
test("keeps in_progress when some episodes remain watched", () => {
|
||||
insertUser();
|
||||
const { titleId, episodeIds } = insertTvShow("tv-1", 99999, 1, 3);
|
||||
markAllEpisodesWatched("user-1", titleId);
|
||||
|
||||
// Verify completed first
|
||||
let row = testDb
|
||||
.select()
|
||||
.from(userTitleStatus)
|
||||
.where(and(eq(userTitleStatus.userId, "user-1"), eq(userTitleStatus.titleId, titleId)))
|
||||
.get();
|
||||
expect(row?.status).toBe("completed");
|
||||
|
||||
// Unwatch one episode
|
||||
// Unwatch one episode — still has 2 watched
|
||||
unwatchEpisode("user-1", episodeIds[0]);
|
||||
|
||||
row = testDb
|
||||
const row = testDb
|
||||
.select()
|
||||
.from(userTitleStatus)
|
||||
.where(and(eq(userTitleStatus.userId, "user-1"), eq(userTitleStatus.titleId, titleId)))
|
||||
@@ -403,7 +395,23 @@ describe("unwatchEpisode", () => {
|
||||
expect(row?.status).toBe("in_progress");
|
||||
});
|
||||
|
||||
test("doesn't change in_progress status", () => {
|
||||
test("downgrades to watchlist when all episodes unwatched", () => {
|
||||
insertUser();
|
||||
const { titleId, episodeIds } = insertTvShow("tv-1", 99999, 1, 3);
|
||||
|
||||
// Watch one episode then unwatch it
|
||||
logEpisodeWatch("user-1", episodeIds[0]);
|
||||
unwatchEpisode("user-1", episodeIds[0]);
|
||||
|
||||
const row = testDb
|
||||
.select()
|
||||
.from(userTitleStatus)
|
||||
.where(and(eq(userTitleStatus.userId, "user-1"), eq(userTitleStatus.titleId, titleId)))
|
||||
.get();
|
||||
expect(row?.status).toBe("watchlist");
|
||||
});
|
||||
|
||||
test("doesn't change in_progress status when episodes remain", () => {
|
||||
insertUser();
|
||||
const { titleId, episodeIds } = insertTvShow("tv-1", 99999, 1, 3);
|
||||
logEpisodeWatch("user-1", episodeIds[0]);
|
||||
@@ -448,30 +456,40 @@ describe("unwatchSeason", () => {
|
||||
expect(watches).toHaveLength(0);
|
||||
});
|
||||
|
||||
test("downgrades completed to in_progress", () => {
|
||||
test("keeps in_progress when other seasons have watches", () => {
|
||||
insertUser();
|
||||
const { titleId } = insertTvShow("tv-1", 99999, 2, 2);
|
||||
|
||||
// Watch all episodes across both seasons
|
||||
markAllEpisodesWatched("user-1", titleId);
|
||||
|
||||
let row = testDb
|
||||
.select()
|
||||
.from(userTitleStatus)
|
||||
.where(and(eq(userTitleStatus.userId, "user-1"), eq(userTitleStatus.titleId, titleId)))
|
||||
.get();
|
||||
expect(row?.status).toBe("completed");
|
||||
|
||||
// Unwatch season 1
|
||||
// Unwatch season 1 — season 2 still has watches
|
||||
unwatchSeason("user-1", "tv-1-s1");
|
||||
|
||||
row = testDb
|
||||
const row = testDb
|
||||
.select()
|
||||
.from(userTitleStatus)
|
||||
.where(and(eq(userTitleStatus.userId, "user-1"), eq(userTitleStatus.titleId, titleId)))
|
||||
.get();
|
||||
expect(row?.status).toBe("in_progress");
|
||||
});
|
||||
|
||||
test("downgrades to watchlist when all episodes unwatched", () => {
|
||||
insertUser();
|
||||
const { titleId } = insertTvShow("tv-1", 99999, 1, 2);
|
||||
|
||||
markAllEpisodesWatched("user-1", titleId);
|
||||
|
||||
// Unwatch the only season — no episodes remain
|
||||
unwatchSeason("user-1", "tv-1-s1");
|
||||
|
||||
const row = testDb
|
||||
.select()
|
||||
.from(userTitleStatus)
|
||||
.where(and(eq(userTitleStatus.userId, "user-1"), eq(userTitleStatus.titleId, titleId)))
|
||||
.get();
|
||||
expect(row?.status).toBe("watchlist");
|
||||
});
|
||||
});
|
||||
|
||||
// ── rateTitleStars ──────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user