171 lines
5.8 KiB
Swift
171 lines
5.8 KiB
Swift
import Foundation
|
|
import XCTest
|
|
@testable import VelodyDomain
|
|
|
|
final class OfflineLibrarySearchTests: XCTestCase {
|
|
func testSearchByTitleFiltersMatchingTracks() {
|
|
let snapshot = makeSnapshot()
|
|
|
|
let filtered = snapshot.filtered(searchText: "trap")
|
|
|
|
XCTAssertEqual(filtered.remoteTracks.map(\.remoteTrack.trackId), ["remote-light-trap"])
|
|
XCTAssertEqual(filtered.availableTracks.map(\.remoteTrackId), ["remote-light-trap"])
|
|
}
|
|
|
|
func testSearchByArtistFiltersMatchingTracks() {
|
|
let snapshot = makeSnapshot()
|
|
|
|
let filtered = snapshot.filtered(searchText: "unknown")
|
|
|
|
XCTAssertEqual(filtered.remoteTracks.map(\.remoteTrack.trackId), ["remote-light-trap"])
|
|
XCTAssertEqual(filtered.availableTracks.map(\.remoteTrackId), ["remote-light-trap"])
|
|
}
|
|
|
|
func testSearchIsCaseInsensitiveAndTrimsWhitespace() {
|
|
let snapshot = makeSnapshot()
|
|
|
|
let filtered = snapshot.filtered(searchText: " TRAP ")
|
|
|
|
XCTAssertEqual(filtered.remoteTracks.map(\.remoteTrack.trackId), ["remote-light-trap"])
|
|
XCTAssertEqual(filtered.availableTracks.map(\.remoteTrackId), ["remote-light-trap"])
|
|
}
|
|
|
|
func testEmptySearchResultReturnsEmptySections() {
|
|
let snapshot = makeSnapshot()
|
|
|
|
let filtered = snapshot.filtered(searchText: "nope")
|
|
|
|
XCTAssertTrue(filtered.remoteTracks.isEmpty)
|
|
XCTAssertTrue(filtered.availableTracks.isEmpty)
|
|
}
|
|
|
|
func testClearingSearchRestoresFullList() {
|
|
let snapshot = makeSnapshot()
|
|
|
|
let filtered = snapshot.filtered(searchText: "")
|
|
|
|
XCTAssertEqual(filtered.remoteTracks, snapshot.remoteTracks)
|
|
XCTAssertEqual(filtered.availableTracks, snapshot.availableTracks)
|
|
}
|
|
|
|
func testOfflineSectionFilteringUsesAvailableOfflineTracks() {
|
|
let snapshot = makeSnapshot()
|
|
|
|
let filtered = snapshot.filtered(searchText: "harbor")
|
|
|
|
XCTAssertEqual(filtered.remoteTracks.map(\.remoteTrack.trackId), ["remote-harbor-lights"])
|
|
XCTAssertEqual(filtered.availableTracks.map(\.remoteTrackId), ["remote-harbor-lights"])
|
|
}
|
|
|
|
func testRemoteSectionFilteringUsesRemoteLibraryTracks() {
|
|
let snapshot = makeSnapshot()
|
|
|
|
let filtered = snapshot.filtered(searchText: "shadow")
|
|
|
|
XCTAssertEqual(filtered.remoteTracks.map(\.remoteTrack.trackId), ["remote-shadow-city"])
|
|
XCTAssertTrue(filtered.availableTracks.isEmpty)
|
|
}
|
|
|
|
func testFilteringDoesNotMutateUnderlyingSnapshotData() {
|
|
let snapshot = makeSnapshot()
|
|
let originalRemoteTracks = snapshot.remoteTracks
|
|
let originalAvailableTracks = snapshot.availableTracks
|
|
|
|
_ = snapshot.filtered(searchText: "light")
|
|
|
|
XCTAssertEqual(snapshot.remoteTracks, originalRemoteTracks)
|
|
XCTAssertEqual(snapshot.availableTracks, originalAvailableTracks)
|
|
}
|
|
|
|
private func makeSnapshot() -> OfflineLibrarySnapshot {
|
|
let remoteTracks = [
|
|
makeRemoteLibraryTrack(
|
|
trackId: "remote-light-trap",
|
|
assetId: "asset-light-trap",
|
|
title: "Light Trap",
|
|
artist: "Unknown Artist",
|
|
isFileAvailable: true
|
|
),
|
|
makeRemoteLibraryTrack(
|
|
trackId: "remote-shadow-city",
|
|
assetId: "asset-shadow-city",
|
|
title: "Shadow City",
|
|
artist: "Night Runner",
|
|
isFileAvailable: false
|
|
),
|
|
makeRemoteLibraryTrack(
|
|
trackId: "remote-harbor-lights",
|
|
assetId: "asset-harbor-lights",
|
|
title: "Harbor Lights",
|
|
artist: "The Northline",
|
|
isFileAvailable: true
|
|
),
|
|
]
|
|
|
|
let availableTracks = [
|
|
makeOfflineTrack(
|
|
trackId: "remote-light-trap",
|
|
assetId: "asset-light-trap",
|
|
title: "Light Trap",
|
|
artist: "Unknown Artist"
|
|
),
|
|
makeOfflineTrack(
|
|
trackId: "remote-harbor-lights",
|
|
assetId: "asset-harbor-lights",
|
|
title: "Harbor Lights",
|
|
artist: "The Northline"
|
|
),
|
|
]
|
|
|
|
return OfflineLibrarySnapshot(
|
|
remoteTracks: remoteTracks,
|
|
availableTracks: availableTracks
|
|
)
|
|
}
|
|
|
|
private func makeRemoteLibraryTrack(
|
|
trackId: String,
|
|
assetId: String,
|
|
title: String,
|
|
artist: String,
|
|
isFileAvailable: Bool
|
|
) -> OfflineLibraryRemoteTrack {
|
|
OfflineLibraryRemoteTrack(
|
|
remoteTrack: RemoteTrack(
|
|
trackId: trackId,
|
|
title: title,
|
|
artist: artist,
|
|
durationSeconds: 180,
|
|
sha256: String(repeating: "a", count: 64),
|
|
assetId: assetId,
|
|
createdAt: "2026-05-30T10:00:00.000Z",
|
|
updatedAt: "2026-05-30T10:00:00.000Z"
|
|
),
|
|
localFilePath: isFileAvailable ? "/tmp/\(assetId).mp3" : "",
|
|
downloadedAt: isFileAvailable ? Date(timeIntervalSince1970: 1_000) : nil,
|
|
isFileAvailable: isFileAvailable,
|
|
status: isFileAvailable ? .downloaded : .notDownloaded,
|
|
lastDownloadError: nil
|
|
)
|
|
}
|
|
|
|
private func makeOfflineTrack(
|
|
trackId: String,
|
|
assetId: String,
|
|
title: String,
|
|
artist: String
|
|
) -> OfflineLibraryTrack {
|
|
OfflineLibraryTrack(
|
|
remoteTrackId: trackId,
|
|
assetId: assetId,
|
|
title: title,
|
|
artist: artist,
|
|
durationSeconds: 180,
|
|
sha256: String(repeating: "b", count: 64),
|
|
localFilePath: "/tmp/\(assetId).mp3",
|
|
downloadedAt: Date(timeIntervalSince1970: 1_000),
|
|
isFileAvailable: true
|
|
)
|
|
}
|
|
}
|