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>
73 lines
3.5 KiB
Swift
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"))
|
|
}
|
|
}
|