tv-anarchy/Tests/TVAnarchyCoreTests/ContinueWatchingTests.swift
Natalie 4a2ceb9781 feat(offline): inline star-to-keep and trash-to-cull on cache rows
Surface the existing pin (keep-from-cull) and per-file delete actions as
visible inline buttons on each offline cache row instead of context-menu-only:
a star toggles protection from auto-cull (and restore-if-missing), a trash
culls that file early. Aligns wording/icons to the star metaphor.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 00:12:41 -04:00

73 lines
3.5 KiB
Swift

import XCTest
@testable import TVAnarchyCore
@MainActor
final class ContinueWatchingTests: XCTestCase {
private func daria() -> CachedShow {
let eps = [
CachedEpisode(path: "/p/D/s1e1.mkv", season: 1, episode: 1, label: "S1E1"),
CachedEpisode(path: "/p/D/s1e2.mkv", season: 1, episode: 2, label: "S1E2"),
CachedEpisode(path: "/p/D/s2e1.mkv", season: 2, episode: 1, label: "S2E1"),
CachedEpisode(path: "/p/D/movie.mkv", season: 0, episode: 1, label: "The Movie"),
]
return CachedShow(name: "Daria", rootDir: "/r/D", category: "cartoons", kind: .series, episodes: eps)
}
private func prog(_ show: String, _ path: String, s: Int, e: Int) -> WatchHistory.ShowProgress {
.init(show: show, path: path, season: s, episode: e, lastSeen: nil)
}
/// ONE entry per show (not one per watched episode), pointing at the NEXT episode.
func testRailIsPerShowAndPointsAtNext() {
let rail = LibraryController.continueRail(
shows: [daria()], progress: [prog("Daria", "/p/D/s1e1.mkv", s: 1, e: 1)])
XCTAssertEqual(rail.count, 1)
XCTAssertEqual(rail[0].path, "/p/D/s1e2.mkv") // next after S1E1
XCTAssertEqual(rail[0].show, "Daria")
}
/// The library join crosses the season boundary the watchlog's naive +1 can't.
func testCrossesSeasonBoundary() {
let rail = LibraryController.continueRail(
shows: [daria()], progress: [prog("Daria", "/p/D/s1e2.mkv", s: 1, e: 2)])
XCTAssertEqual(rail[0].path, "/p/D/s2e1.mkv") // S1E2 S2E1, not "S1E3"
}
/// After the last numbered season, "next" is the season-0 movie (specials last).
func testContinuesIntoMoviesAfterTheSeries() {
let rail = LibraryController.continueRail(
shows: [daria()], progress: [prog("Daria", "/p/D/s2e1.mkv", s: 2, e: 1)])
XCTAssertEqual(rail[0].path, "/p/D/movie.mkv")
}
/// A fully-watched show (current = the last ordered episode) drops off the rail.
func testFinishedShowDrops() {
let rail = LibraryController.continueRail(
shows: [daria()], progress: [prog("Daria", "/p/D/movie.mkv", s: 0, e: 1)])
XCTAssertTrue(rail.isEmpty)
}
/// Resolve the show by path when the watchlog name doesn't match the library name.
func testResolvesShowByPathWhenNameDiffers() {
let rail = LibraryController.continueRail(
shows: [daria()], progress: [prog("Daria (1997)", "/p/D/s1e1.mkv", s: 1, e: 1)])
XCTAssertEqual(rail.first?.path, "/p/D/s1e2.mkv")
}
/// One show logged under several names (cleaned title + raw release-folder
/// name) that resolve to the same merged library entry ONE card, the
/// newest progress (first in the list) winning.
func testSameShowUnderTwoLogNamesDedupes() {
let rail = LibraryController.continueRail(
shows: [daria()],
progress: [prog("Daria", "/p/D/s2e1.mkv", s: 2, e: 1), // newest
prog("Daria 1997 Season 1 Complete 720p", "/p/D/s1e1.mkv", s: 1, e: 1)]) // older alias
XCTAssertEqual(rail.count, 1)
XCTAssertEqual(rail[0].path, "/p/D/movie.mkv") // next after the NEWEST progress (S2E1)
}
func testJunkPathsRejected() {
XCTAssertTrue(WatchHistory.isRealVideo("/m/cartoons/Futurama/S02E20.mp4"))
XCTAssertFalse(WatchHistory.isRealVideo("/m/cartoons/Futurama/S02E20.mp4:Zone.Identifier"))
XCTAssertFalse(WatchHistory.isRealVideo("/m/cartoons/Futurama/notes.txt"))
}
}