tv-anarchy/Tests/TVAnarchyCoreTests/SettingsStoreTests.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

128 lines
4.9 KiB
Swift

import XCTest
@testable import TVAnarchyCore
/// Guards the adult-config schema and the load-modify-save contract. The bug this
/// exists for: a per-field save (`AppSettings(surfaceAdultOnHome:)`) silently
/// reset every OTHER field to its default so toggling the adult gate wiped
/// `hoverPreviews`. The store is redirected via `TV_ANARCHY_STATE_DIR` so the
/// real settings file is never touched.
final class SettingsStoreTests: XCTestCase {
private var tmp: URL!
override func setUpWithError() throws {
tmp = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
.appendingPathComponent("settings-\(UUID().uuidString)", isDirectory: true)
try FileManager.default.createDirectory(at: tmp, withIntermediateDirectories: true)
setenv("TV_ANARCHY_STATE_DIR", tmp.path, 1)
}
override func tearDownWithError() throws {
unsetenv("TV_ANARCHY_STATE_DIR")
try? FileManager.default.removeItem(at: tmp)
}
func testDefaultsAreAllOff() {
let s = AppSettings()
XCTAssertFalse(s.pornFeature)
XCTAssertFalse(s.surfaceAdultOnHome)
XCTAssertFalse(s.switchToAdultOnlyHome)
XCTAssertFalse(s.hoverPreviews)
XCTAssertEqual(s.adultQueueCount, 25)
XCTAssertEqual(s.playbackMode, .stream)
XCTAssertEqual(s.adultPlaybackMode, .stream)
XCTAssertEqual(s.appTheme, .standard)
}
/// A sibling field must survive a single-field update (the clobber regression).
func testSingleFieldUpdateKeepsSiblings() {
var s = SettingsStore.load()
s.hoverPreviews = true
SettingsStore.save(s)
// Simulate the LibraryController setter path: reload, change one field, save.
var t = SettingsStore.load()
t.surfaceAdultOnHome = true
SettingsStore.save(t)
let final = SettingsStore.load()
XCTAssertTrue(final.surfaceAdultOnHome)
XCTAssertTrue(final.hoverPreviews, "hoverPreviews must survive a surfaceAdultOnHome write")
}
/// Tolerant decode: an old file missing the new keys loads with them defaulted.
func testTolerantDecodeOfLegacyFile() throws {
let url = tmp.appendingPathComponent("settings.json")
try #"{"surfaceAdultOnHome":true,"hoverPreviews":true}"#
.write(to: url, atomically: true, encoding: .utf8)
let s = SettingsStore.load()
XCTAssertTrue(s.surfaceAdultOnHome)
XCTAssertTrue(s.hoverPreviews)
XCTAssertFalse(s.pornFeature) // absent default
XCTAssertFalse(s.switchToAdultOnlyHome) // absent default
XCTAssertEqual(s.adultQueueCount, 25) // absent default
XCTAssertEqual(s.playbackMode, .stream) // absent default
}
func testPlaybackModePersists() {
var s = SettingsStore.load()
s.playbackMode = .offline
SettingsStore.save(s)
XCTAssertEqual(SettingsStore.load().playbackMode, .offline)
}
func testAdultPlaybackModePersists() {
var s = SettingsStore.load()
s.adultPlaybackMode = .offline
SettingsStore.save(s)
XCTAssertEqual(SettingsStore.load().adultPlaybackMode, .offline)
}
func testForwardVolumeKeysTolerantDecode() throws {
let url = tmp.appendingPathComponent("settings.json")
try #"{"forwardMediaKeys":false}"#.write(to: url, atomically: true, encoding: .utf8)
let s = SettingsStore.load()
XCTAssertFalse(s.forwardMediaKeys)
XCTAssertTrue(s.forwardVolumeKeys)
}
func testForwardVolumeKeysPersists() {
var s = SettingsStore.load()
s.forwardVolumeKeys = false
SettingsStore.save(s)
XCTAssertFalse(SettingsStore.load().forwardVolumeKeys)
}
func testAdultQueueCountPersists() {
var s = SettingsStore.load()
s.adultQueueCount = 40
SettingsStore.save(s)
XCTAssertEqual(SettingsStore.load().adultQueueCount, 40)
}
func testAppThemePersists() {
var s = SettingsStore.load()
s.appTheme = .winampClassic
SettingsStore.save(s)
XCTAssertEqual(SettingsStore.load().appTheme, .winampClassic)
}
func testAppThemeTolerantDecode() throws {
let url = tmp.appendingPathComponent("settings.json")
try #"{"appTheme":"winamp-modern"}"#.write(to: url, atomically: true, encoding: .utf8)
XCTAssertEqual(SettingsStore.load().appTheme, .winampModern)
}
@MainActor
func testLibraryControllerPatchSettingsKeepsSiblings() {
var s = SettingsStore.load()
s.hoverPreviews = true
SettingsStore.save(s)
let lib = LibraryController(watchHistory: WatchHistoryController())
lib.patchSettings(AppSettingsPatch(notifyDownloads: false))
let disk = SettingsStore.load()
XCTAssertFalse(disk.notifyDownloads)
XCTAssertTrue(disk.hoverPreviews)
XCTAssertTrue(lib.appSettings.hoverPreviews)
}
}