Skip to content

Commit 707867a

Browse files
authored
migrate to swift testing (#6)
1 parent 011856f commit 707867a

28 files changed

+2519
-1909
lines changed

Kaset.xcodeproj/project.pbxproj

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,8 @@
103103
E50000010000000000000091 /* ExploreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E50000020000000000000091 /* ExploreView.swift */; };
104104
E50000010000000000000092 /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E50000020000000000000092 /* OnboardingView.swift */; };
105105
E50000010000000000000093 /* AccentBackground.swift in Sources */ = {isa = PBXBuildFile; fileRef = E50000020000000000000093 /* AccentBackground.swift */; };
106-
E50000010000000000000100 /* KasetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E50000020000000000000100 /* KasetTests.swift */; };
107106
E50000010000000000000101 /* WebKitManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E50000020000000000000101 /* WebKitManagerTests.swift */; };
107+
E50000010000000000000600 /* Tags.swift in Sources */ = {isa = PBXBuildFile; fileRef = E50000020000000000000600 /* Tags.swift */; };
108108
E50000010000000000000102 /* AuthServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E50000020000000000000102 /* AuthServiceTests.swift */; };
109109
E50000010000000000000103 /* YTMusicClientTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E50000020000000000000103 /* YTMusicClientTests.swift */; };
110110
E50000010000000000000104 /* PlayerServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E50000020000000000000104 /* PlayerServiceTests.swift */; };
@@ -269,8 +269,8 @@
269269
E50000020000000000000092 /* OnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingView.swift; sourceTree = "<group>"; };
270270
E50000020000000000000093 /* AccentBackground.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccentBackground.swift; sourceTree = "<group>"; };
271271
E50000020000000000000099 /* KasetTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = KasetTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
272-
E50000020000000000000100 /* KasetTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KasetTests.swift; sourceTree = "<group>"; };
273272
E50000020000000000000101 /* WebKitManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebKitManagerTests.swift; sourceTree = "<group>"; };
273+
E50000020000000000000600 /* Tags.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tags.swift; sourceTree = "<group>"; };
274274
E50000020000000000000102 /* AuthServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthServiceTests.swift; sourceTree = "<group>"; };
275275
E50000020000000000000103 /* YTMusicClientTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YTMusicClientTests.swift; sourceTree = "<group>"; };
276276
E50000020000000000000104 /* PlayerServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerServiceTests.swift; sourceTree = "<group>"; };
@@ -696,8 +696,8 @@
696696
E50000060000000000000051 /* KasetTests */ = {
697697
isa = PBXGroup;
698698
children = (
699-
E50000020000000000000100 /* KasetTests.swift */,
700699
E50000020000000000000101 /* WebKitManagerTests.swift */,
700+
E50000060000000000000054 /* SwiftTestingHelpers */,
701701
E50000020000000000000102 /* AuthServiceTests.swift */,
702702
E50000020000000000000103 /* YTMusicClientTests.swift */,
703703
E50000020000000000000104 /* PlayerServiceTests.swift */,
@@ -726,6 +726,14 @@
726726
path = KasetTests;
727727
sourceTree = "<group>";
728728
};
729+
E50000060000000000000054 /* SwiftTestingHelpers */ = {
730+
isa = PBXGroup;
731+
children = (
732+
E50000020000000000000600 /* Tags.swift */,
733+
);
734+
path = SwiftTestingHelpers;
735+
sourceTree = "<group>";
736+
};
729737
E50000060000000000000052 /* Helpers */ = {
730738
isa = PBXGroup;
731739
children = (
@@ -993,8 +1001,8 @@
9931001
isa = PBXSourcesBuildPhase;
9941002
buildActionMask = 2147483647;
9951003
files = (
996-
E50000010000000000000100 /* KasetTests.swift in Sources */,
9971004
E50000010000000000000101 /* WebKitManagerTests.swift in Sources */,
1005+
E50000010000000000000600 /* Tags.swift in Sources */,
9981006
E50000010000000000000102 /* AuthServiceTests.swift in Sources */,
9991007
E50000010000000000000103 /* YTMusicClientTests.swift in Sources */,
10001008
E50000010000000000000104 /* PlayerServiceTests.swift in Sources */,
Lines changed: 78 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,143 +1,138 @@
1-
import XCTest
1+
import Foundation
2+
import Testing
23
@testable import Kaset
34

45
/// Tests for APICache.
6+
@Suite(.serialized)
57
@MainActor
6-
final class APICacheTests: XCTestCase {
7-
var cache: APICache!
8+
struct APICacheTests {
9+
var cache: APICache
810

9-
override func setUp() async throws {
11+
init() {
1012
self.cache = APICache.shared
1113
self.cache.invalidateAll()
1214
}
1315

14-
override func tearDown() async throws {
15-
self.cache.invalidateAll()
16-
self.cache = nil
17-
}
18-
19-
func testCacheSetAndGet() {
16+
@Test("Cache set and get")
17+
func cacheSetAndGet() {
2018
let data: [String: Any] = ["key": "value", "number": 42]
21-
self.cache.set(key: "test_key", data: data, ttl: 60)
19+
cache.set(key: "test_key", data: data, ttl: 60)
2220

23-
let retrieved = self.cache.get(key: "test_key")
24-
XCTAssertNotNil(retrieved)
25-
XCTAssertEqual(retrieved?["key"] as? String, "value")
26-
XCTAssertEqual(retrieved?["number"] as? Int, 42)
21+
let retrieved = cache.get(key: "test_key")
22+
#expect(retrieved != nil)
23+
#expect(retrieved?["key"] as? String == "value")
24+
#expect(retrieved?["number"] as? Int == 42)
2725
}
2826

29-
func testCacheGetNonexistent() {
30-
let retrieved = self.cache.get(key: "nonexistent_key")
31-
XCTAssertNil(retrieved)
27+
@Test("Cache get nonexistent returns nil")
28+
func cacheGetNonexistent() {
29+
let retrieved = cache.get(key: "nonexistent_key")
30+
#expect(retrieved == nil)
3231
}
3332

34-
func testCacheInvalidateAll() {
35-
self.cache.set(key: "key1", data: ["a": 1], ttl: 60)
36-
self.cache.set(key: "key2", data: ["b": 2], ttl: 60)
33+
@Test("Cache invalidate all")
34+
func cacheInvalidateAll() {
35+
cache.set(key: "key1", data: ["a": 1], ttl: 60)
36+
cache.set(key: "key2", data: ["b": 2], ttl: 60)
3737

38-
XCTAssertNotNil(self.cache.get(key: "key1"))
39-
XCTAssertNotNil(self.cache.get(key: "key2"))
38+
#expect(cache.get(key: "key1") != nil)
39+
#expect(cache.get(key: "key2") != nil)
4040

41-
self.cache.invalidateAll()
41+
cache.invalidateAll()
4242

43-
XCTAssertNil(self.cache.get(key: "key1"))
44-
XCTAssertNil(self.cache.get(key: "key2"))
43+
#expect(cache.get(key: "key1") == nil)
44+
#expect(cache.get(key: "key2") == nil)
4545
}
4646

47-
func testCacheInvalidateMatchingPrefix() {
48-
self.cache.set(key: "home_section1", data: ["a": 1], ttl: 60)
49-
self.cache.set(key: "home_section2", data: ["b": 2], ttl: 60)
50-
self.cache.set(key: "search_results", data: ["c": 3], ttl: 60)
47+
@Test("Cache invalidate matching prefix")
48+
func cacheInvalidateMatchingPrefix() {
49+
cache.set(key: "home_section1", data: ["a": 1], ttl: 60)
50+
cache.set(key: "home_section2", data: ["b": 2], ttl: 60)
51+
cache.set(key: "search_results", data: ["c": 3], ttl: 60)
5152

52-
self.cache.invalidate(matching: "home_")
53+
cache.invalidate(matching: "home_")
5354

54-
XCTAssertNil(self.cache.get(key: "home_section1"))
55-
XCTAssertNil(self.cache.get(key: "home_section2"))
56-
XCTAssertNotNil(self.cache.get(key: "search_results"))
55+
#expect(cache.get(key: "home_section1") == nil)
56+
#expect(cache.get(key: "home_section2") == nil)
57+
#expect(cache.get(key: "search_results") != nil)
5758
}
5859

59-
func testCacheEntryExpiration() async throws {
60-
// Set with a very short TTL
61-
self.cache.set(key: "short_lived", data: ["test": true], ttl: 0.1)
60+
@Test("Cache entry expiration")
61+
func cacheEntryExpiration() async throws {
62+
cache.set(key: "short_lived", data: ["test": true], ttl: 0.1)
6263

63-
// Should exist immediately
64-
XCTAssertNotNil(self.cache.get(key: "short_lived"))
64+
#expect(cache.get(key: "short_lived") != nil)
6565

66-
// Wait for expiration
6766
try await Task.sleep(for: .milliseconds(150))
6867

69-
// Should be expired
70-
XCTAssertNil(self.cache.get(key: "short_lived"))
68+
#expect(cache.get(key: "short_lived") == nil)
7169
}
7270

73-
func testCacheOverwrite() {
74-
self.cache.set(key: "key", data: ["value": 1], ttl: 60)
75-
XCTAssertEqual(self.cache.get(key: "key")?["value"] as? Int, 1)
71+
@Test("Cache overwrite")
72+
func cacheOverwrite() {
73+
cache.set(key: "key", data: ["value": 1], ttl: 60)
74+
#expect(cache.get(key: "key")?["value"] as? Int == 1)
7675

77-
self.cache.set(key: "key", data: ["value": 2], ttl: 60)
78-
XCTAssertEqual(self.cache.get(key: "key")?["value"] as? Int, 2)
76+
cache.set(key: "key", data: ["value": 2], ttl: 60)
77+
#expect(cache.get(key: "key")?["value"] as? Int == 2)
7978
}
8079

81-
func testCacheTTLConstants() {
82-
XCTAssertEqual(APICache.TTL.home, 5 * 60) // 5 minutes
83-
XCTAssertEqual(APICache.TTL.playlist, 30 * 60) // 30 minutes
84-
XCTAssertEqual(APICache.TTL.artist, 60 * 60) // 1 hour
85-
XCTAssertEqual(APICache.TTL.search, 2 * 60) // 2 minutes
86-
XCTAssertEqual(APICache.TTL.library, 5 * 60) // 5 minutes
87-
XCTAssertEqual(APICache.TTL.lyrics, 24 * 60 * 60) // 24 hours
88-
XCTAssertEqual(APICache.TTL.songMetadata, 30 * 60) // 30 minutes
80+
@Test("Cache TTL constants are correct")
81+
func cacheTTLConstants() {
82+
#expect(APICache.TTL.home == 5 * 60) // 5 minutes
83+
#expect(APICache.TTL.playlist == 30 * 60) // 30 minutes
84+
#expect(APICache.TTL.artist == 60 * 60) // 1 hour
85+
#expect(APICache.TTL.search == 2 * 60) // 2 minutes
86+
#expect(APICache.TTL.library == 5 * 60) // 5 minutes
87+
#expect(APICache.TTL.lyrics == 24 * 60 * 60) // 24 hours
88+
#expect(APICache.TTL.songMetadata == 30 * 60) // 30 minutes
8989
}
9090

91-
func testLyricsCacheNotInvalidatedByMutations() {
92-
// Lyrics use browse: prefix but should NOT be invalidated by mutation operations
93-
// This test verifies that invalidating next: prefix doesn't affect lyrics
94-
self.cache.set(key: "browse:lyrics_abc123", data: ["text": "lyrics content"], ttl: APICache.TTL.lyrics)
95-
self.cache.set(key: "next:song_abc123", data: ["title": "song"], ttl: APICache.TTL.songMetadata)
91+
@Test("Lyrics cache not invalidated by mutations")
92+
func lyricsCacheNotInvalidatedByMutations() {
93+
cache.set(key: "browse:lyrics_abc123", data: ["text": "lyrics content"], ttl: APICache.TTL.lyrics)
94+
cache.set(key: "next:song_abc123", data: ["title": "song"], ttl: APICache.TTL.songMetadata)
9695

97-
// Simulate mutation invalidation (like rateSong would do)
98-
self.cache.invalidate(matching: "next:")
96+
cache.invalidate(matching: "next:")
9997

100-
// Lyrics should still be cached (browse: prefix not invalidated)
101-
XCTAssertNotNil(self.cache.get(key: "browse:lyrics_abc123"))
102-
// Song metadata should be invalidated
103-
XCTAssertNil(self.cache.get(key: "next:song_abc123"))
98+
#expect(cache.get(key: "browse:lyrics_abc123") != nil)
99+
#expect(cache.get(key: "next:song_abc123") == nil)
104100
}
105101

106-
func testSongMetadataCacheInvalidatedByMutations() {
107-
// Song metadata uses next: prefix and should be invalidated by mutations
108-
self.cache.set(key: "next:song_abc123", data: ["title": "song"], ttl: APICache.TTL.songMetadata)
109-
self.cache.set(key: "browse:home_section", data: ["section": "home"], ttl: APICache.TTL.home)
102+
@Test("Song metadata cache invalidated by mutations")
103+
func songMetadataCacheInvalidatedByMutations() {
104+
cache.set(key: "next:song_abc123", data: ["title": "song"], ttl: APICache.TTL.songMetadata)
105+
cache.set(key: "browse:home_section", data: ["section": "home"], ttl: APICache.TTL.home)
110106

111-
// Simulate mutation invalidation for both prefixes
112-
self.cache.invalidate(matching: "browse:")
113-
self.cache.invalidate(matching: "next:")
107+
cache.invalidate(matching: "browse:")
108+
cache.invalidate(matching: "next:")
114109

115-
// Both should be invalidated
116-
XCTAssertNil(self.cache.get(key: "next:song_abc123"))
117-
XCTAssertNil(self.cache.get(key: "browse:home_section"))
110+
#expect(cache.get(key: "next:song_abc123") == nil)
111+
#expect(cache.get(key: "browse:home_section") == nil)
118112
}
119113

120-
func testCacheEntryIsExpired() {
114+
@Test("Cache entry isExpired property")
115+
func cacheEntryIsExpired() {
121116
let freshEntry = APICache.CacheEntry(
122117
data: [:],
123118
timestamp: Date(),
124119
ttl: 60
125120
)
126-
XCTAssertFalse(freshEntry.isExpired)
121+
#expect(freshEntry.isExpired == false)
127122

128123
let expiredEntry = APICache.CacheEntry(
129124
data: [:],
130125
timestamp: Date().addingTimeInterval(-120),
131126
ttl: 60
132127
)
133-
XCTAssertTrue(expiredEntry.isExpired)
128+
#expect(expiredEntry.isExpired == true)
134129
}
135130

136-
func testCacheSharedInstance() {
137-
XCTAssertNotNil(APICache.shared)
138-
// Test that it's truly a singleton
131+
@Test("Cache shared instance is singleton")
132+
func cacheSharedInstance() {
133+
#expect(APICache.shared != nil)
139134
let instance1 = APICache.shared
140135
let instance2 = APICache.shared
141-
XCTAssertTrue(instance1 === instance2)
136+
#expect(instance1 === instance2)
142137
}
143138
}
Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1-
import XCTest
1+
import Foundation
2+
import Testing
23
@testable import Kaset
34

45
/// Tests for ArtistDetail.
5-
final class ArtistDetailTests: XCTestCase {
6-
func testArtistDetailInit() {
6+
@Suite
7+
struct ArtistDetailTests {
8+
@Test("ArtistDetail initialization")
9+
func artistDetailInit() {
710
let artist = Artist(id: "UC123", name: "Test Artist", thumbnailURL: URL(string: "https://example.com/a.jpg"))
811
let songs = [
912
Song(id: "s1", title: "Song 1", artists: [artist], album: nil, duration: 180, thumbnailURL: nil, videoId: "s1"),
@@ -21,45 +24,50 @@ final class ArtistDetailTests: XCTestCase {
2124
thumbnailURL: URL(string: "https://example.com/large.jpg")
2225
)
2326

24-
XCTAssertEqual(detail.id, "UC123")
25-
XCTAssertEqual(detail.name, "Test Artist")
26-
XCTAssertEqual(detail.description, "A great artist")
27-
XCTAssertEqual(detail.songs.count, 2)
28-
XCTAssertEqual(detail.albums.count, 1)
29-
XCTAssertEqual(detail.thumbnailURL?.absoluteString, "https://example.com/large.jpg")
27+
#expect(detail.id == "UC123")
28+
#expect(detail.name == "Test Artist")
29+
#expect(detail.description == "A great artist")
30+
#expect(detail.songs.count == 2)
31+
#expect(detail.albums.count == 1)
32+
#expect(detail.thumbnailURL?.absoluteString == "https://example.com/large.jpg")
3033
}
3134

32-
func testArtistDetailIdComputedProperty() {
35+
@Test("ArtistDetail id computed property")
36+
func artistDetailIdComputedProperty() {
3337
let artist = Artist(id: "artist_id_123", name: "Artist")
3438
let detail = ArtistDetail(artist: artist, description: nil, songs: [], albums: [], thumbnailURL: nil)
35-
XCTAssertEqual(detail.id, "artist_id_123")
39+
#expect(detail.id == "artist_id_123")
3640
}
3741

38-
func testArtistDetailNameComputedProperty() {
42+
@Test("ArtistDetail name computed property")
43+
func artistDetailNameComputedProperty() {
3944
let artist = Artist(id: "1", name: "Famous Artist Name")
4045
let detail = ArtistDetail(artist: artist, description: nil, songs: [], albums: [], thumbnailURL: nil)
41-
XCTAssertEqual(detail.name, "Famous Artist Name")
46+
#expect(detail.name == "Famous Artist Name")
4247
}
4348

44-
func testArtistDetailWithNoDescription() {
49+
@Test("ArtistDetail with no description")
50+
func artistDetailWithNoDescription() {
4551
let artist = Artist(id: "1", name: "Artist")
4652
let detail = ArtistDetail(artist: artist, description: nil, songs: [], albums: [], thumbnailURL: nil)
47-
XCTAssertNil(detail.description)
53+
#expect(detail.description == nil)
4854
}
4955

50-
func testArtistDetailWithEmptySongsAndAlbums() {
56+
@Test("ArtistDetail with empty songs and albums")
57+
func artistDetailWithEmptySongsAndAlbums() {
5158
let artist = Artist(id: "1", name: "New Artist")
5259
let detail = ArtistDetail(artist: artist, description: "Just starting out", songs: [], albums: [], thumbnailURL: nil)
53-
XCTAssertTrue(detail.songs.isEmpty)
54-
XCTAssertTrue(detail.albums.isEmpty)
60+
#expect(detail.songs.isEmpty)
61+
#expect(detail.albums.isEmpty)
5562
}
5663

57-
func testArtistDetailArtistProperty() {
64+
@Test("ArtistDetail artist property")
65+
func artistDetailArtistProperty() {
5866
let artist = Artist(id: "UC123", name: "Artist", thumbnailURL: URL(string: "https://example.com/thumb.jpg"))
5967
let detail = ArtistDetail(artist: artist, description: nil, songs: [], albums: [], thumbnailURL: nil)
6068

61-
XCTAssertEqual(detail.artist.id, "UC123")
62-
XCTAssertEqual(detail.artist.name, "Artist")
63-
XCTAssertNotNil(detail.artist.thumbnailURL)
69+
#expect(detail.artist.id == "UC123")
70+
#expect(detail.artist.name == "Artist")
71+
#expect(detail.artist.thumbnailURL != nil)
6472
}
6573
}

0 commit comments

Comments
 (0)