react-native-audio-browser 0.2.0-next.32 → 0.2.0-next.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -109,4 +109,120 @@ struct RequestLayerTests {
109
109
  #expect(RequestLayer.mergeDicts(["a": "1"], nil) == ["a": "1"])
110
110
  #expect(RequestLayer.mergeDicts(nil, ["b": "2"]) == ["b": "2"])
111
111
  }
112
+
113
+ @Test func baseSetsOnlyThePath() {
114
+ #expect(Config.base(path: "/a") == Config(path: "/a"))
115
+ }
116
+
117
+ // MARK: - artwork
118
+
119
+ private func artwork(
120
+ _ artwork: Config = Config(),
121
+ over base: Config = Config(path: "/art.png"),
122
+ resolve: (nonisolated(nonsending)() async throws -> Config?)? = nil,
123
+ trackHasArtwork: Bool = true,
124
+ sizeQuery: [String: String] = [:],
125
+ transforms: [nonisolated(nonsending)(Config) async throws -> Config] = [],
126
+ id: String? = nil,
127
+ ) async throws -> Config? {
128
+ try await RequestLayer.artwork(
129
+ artwork, over: base, resolve: resolve, trackHasArtwork: trackHasArtwork,
130
+ sizeQuery: sizeQuery, transforms: transforms, id: id,
131
+ )
132
+ }
133
+
134
+ @Test func artworkStaticFieldsMergeOverTheBasePathIncluded() async throws {
135
+ let result = try await artwork(
136
+ Config(path: "/artwork/{id}", baseUrl: "https://img", headers: ["B": "2"]),
137
+ over: Config(path: "/art.png", baseUrl: "https://api", headers: ["A": "1"]),
138
+ id: "42",
139
+ )
140
+ #expect(result == Config(path: "/artwork/42", baseUrl: "https://img", headers: ["A": "1", "B": "2"]))
141
+ }
142
+
143
+ @Test func theResolvedConfigWinsOverTheStaticFields() async throws {
144
+ let result = try await artwork(
145
+ Config(baseUrl: "https://static", userAgent: "static"),
146
+ resolve: { Config(baseUrl: "https://resolved") },
147
+ )
148
+ #expect(result == Config(path: "/art.png", baseUrl: "https://resolved", userAgent: "static"))
149
+ }
150
+
151
+ @Test func aResolverThatProducesNothingKeepsTheTracksArtwork() async throws {
152
+ let result = try await artwork(Config(baseUrl: "https://img"), resolve: { nil })
153
+ #expect(result == Config(path: "/art.png", baseUrl: "https://img"))
154
+ }
155
+
156
+ @Test func aResolverThatProducesNothingForATrackWithoutArtworkIsNoArtwork() async throws {
157
+ let result = try await artwork(
158
+ Config(path: "/fallback.png"), over: Config(), resolve: { nil }, trackHasArtwork: false,
159
+ )
160
+ #expect(result == nil)
161
+ }
162
+
163
+ @Test func theSizeQueryMergesBeforeTheTransformsRun() async throws {
164
+ var seen: Config?
165
+ let result = try await artwork(
166
+ over: Config(path: "/art.png", query: ["v": "1"]),
167
+ sizeQuery: ["w": "300"],
168
+ transforms: [{ seen = $0; return $0 }],
169
+ )
170
+ #expect(seen?.query == ["v": "1", "w": "300"])
171
+ #expect(result?.query == ["v": "1", "w": "300"])
172
+ }
173
+
174
+ @Test func anEmptySizeQueryLeavesNoQuery() async throws {
175
+ #expect(try await artwork()?.query == nil)
176
+ }
177
+
178
+ @Test func transformsRunInOrderBeforeTheIdIsFilled() async throws {
179
+ let result = try await artwork(
180
+ transforms: [
181
+ { Config(path: "/t/{id}", baseUrl: $0.baseUrl) },
182
+ { Config(path: $0.path, baseUrl: "https://second", headers: ["X-Id": "{id}"], query: ["id": "{id}"]) },
183
+ ],
184
+ id: "7",
185
+ )
186
+ #expect(result == Config(path: "/t/7", baseUrl: "https://second", headers: ["X-Id": "7"], query: ["id": "7"]))
187
+ }
188
+
189
+ @Test func withoutAnIdTheTokenStays() async throws {
190
+ let result = try await artwork(Config(path: "/artwork/{id}"), id: nil)
191
+ #expect(result?.path == "/artwork/{id}")
192
+ #expect(RequestLayer.hasUnfilledIdToken(result!))
193
+ #expect(!RequestLayer.hasUnfilledIdToken(Config(path: "/artwork/1")))
194
+ #expect(RequestLayer.hasUnfilledIdToken(Config(headers: ["X": "{id}"])))
195
+ }
196
+
197
+ @Test func noPathLeftIsNoArtwork() async throws {
198
+ #expect(try await artwork(over: Config(baseUrl: "https://img")) == nil)
199
+ #expect(try await artwork(transforms: [{ _ in Config(baseUrl: "https://img") }]) == nil)
200
+ }
201
+
202
+ @Test func sizeQueryNamesTheContextsWholePixels() {
203
+ let params = ImageQueryParams(width: "w", height: "h")
204
+ #expect(RequestLayer.sizeQuery(params, ImageContext(width: 300.7, height: 200)) == ["w": "300", "h": "200"])
205
+ #expect(RequestLayer.sizeQuery(ImageQueryParams(width: "w"), ImageContext(width: 300, height: 200)) == ["w": "300"])
206
+ #expect(RequestLayer.sizeQuery(params, ImageContext(width: nil, height: 200)) == ["h": "200"])
207
+ #expect(RequestLayer.sizeQuery(params, nil) == [:])
208
+ #expect(RequestLayer.sizeQuery(nil, ImageContext(width: 300, height: 200)) == [:])
209
+ }
210
+
211
+ @Test func imageHeadersAddTheUserAgentUnlessAHeaderSetsIt() {
212
+ #expect(RequestLayer.imageHeaders(Config()) == nil)
213
+ #expect(RequestLayer.imageHeaders(Config(userAgent: "UA")) == ["User-Agent": "UA"])
214
+ #expect(RequestLayer.imageHeaders(Config(headers: ["User-Agent": "mine"], userAgent: "UA")) == ["User-Agent": "mine"])
215
+ }
216
+
217
+ @Test func imageHeadersAddTheContentTypeUnlessAHeaderSetsIt() {
218
+ #expect(RequestLayer.imageHeaders(Config(contentType: "image/png")) == ["Content-Type": "image/png"])
219
+ #expect(
220
+ RequestLayer.imageHeaders(Config(headers: ["Content-Type": "mine"], contentType: "image/png"))
221
+ == ["Content-Type": "mine"],
222
+ )
223
+ #expect(
224
+ RequestLayer.imageHeaders(Config(contentType: "image/png", userAgent: "UA"))
225
+ == ["Content-Type": "image/png", "User-Agent": "UA"],
226
+ )
227
+ }
112
228
  }
@@ -0,0 +1,48 @@
1
+ import Testing
2
+
3
+ @testable import AudioBrowserTestable
4
+
5
+ struct SearchParamsQueryTests {
6
+ private func params(
7
+ _ query: String,
8
+ mode: SearchMode? = nil,
9
+ genre: String? = nil,
10
+ artist: String? = nil,
11
+ album: String? = nil,
12
+ title: String? = nil,
13
+ playlist: String? = nil,
14
+ reference: MediaReference = .unknown,
15
+ ) -> SearchParams {
16
+ SearchParams(
17
+ mode: mode, query: query, genre: genre, artist: artist, album: album,
18
+ title: title, playlist: playlist, reference: reference,
19
+ )
20
+ }
21
+
22
+ @Test func aPlainSearchSendsOnlyQ() {
23
+ #expect(params("jazz").queryParams == ["q": "jazz"])
24
+ }
25
+
26
+ @Test func everySetFilterIsSent() {
27
+ let all = params(
28
+ "x", mode: .musicVideo, genre: "g", artist: "a", album: "al", title: "t", playlist: "p", reference: .my,
29
+ )
30
+ #expect(all.queryParams == [
31
+ "q": "x", "mode": "music-video", "genre": "g", "artist": "a", "album": "al",
32
+ "title": "t", "playlist": "p", "reference": "my",
33
+ ])
34
+ }
35
+
36
+ @Test func anUnknownReferenceIsNotSent() {
37
+ #expect(params("x", reference: .unknown).queryParams["reference"] == nil)
38
+ }
39
+
40
+ @Test func theCacheKeyIsSortedAndDelimited() {
41
+ #expect(params("jazz", genre: "jazz").cacheKey == "genre=jazz\u{1}q=jazz")
42
+ }
43
+
44
+ @Test func searchesWithTheSameQueryButDifferentFiltersHaveDifferentKeys() {
45
+ #expect(params("jazz").cacheKey != params("jazz", genre: "jazz").cacheKey)
46
+ #expect(params("a", artist: "b").cacheKey != params("a", album: "b").cacheKey)
47
+ }
48
+ }
@@ -111,4 +111,57 @@ struct SectionScopeTests {
111
111
  @Test func childlessPageHasNoSections() {
112
112
  #expect(ResolvedTrack(path: "/p", title: "P").normalizedSections == nil)
113
113
  }
114
+
115
+ // MARK: - Queue
116
+
117
+ private func queue(_ sections: [Section], _ path: String, singleTrack: Bool = false) -> ([String?], Int)? {
118
+ SectionScope.queue(in: sections, path: path, singleTrack: singleTrack).map { ($0.tracks.map(\.src), $0.selectedIndex) }
119
+ }
120
+
121
+ @Test func queuesThePlayableTracksOfTheTappedSection() {
122
+ let page = [
123
+ section("First", [track("a"), Track(id: "off", src: "off", disabled: true), Track(path: "/browse"), track("b")]),
124
+ section("Second", [track("c")]),
125
+ ]
126
+ let result = queue(page, "/p?__trackId=b&__index=3")
127
+ #expect(result?.0 == ["a", "b"])
128
+ #expect(result?.1 == 1)
129
+ }
130
+
131
+ @Test func thePinnedCopyIsTheSelectedOne() {
132
+ let page = [section(nil, [track("a"), track("b"), track("a")])]
133
+ #expect(queue(page, "/p?__trackId=a&__index=2")?.1 == 2)
134
+ #expect(queue(page, "/p?__trackId=a")?.1 == 0)
135
+ }
136
+
137
+ @Test func aStaleIndexSelectsTheFirstIdentityMatch() {
138
+ let page = [section(nil, [track("x"), track("a"), track("a")])]
139
+ #expect(queue(page, "/p?__trackId=a&__index=0")?.1 == 1)
140
+ }
141
+
142
+ @Test func singleTrackQueuesOnlyTheTappedTrack() {
143
+ let page = [section(nil, [track("a"), track("b"), track("c")])]
144
+ let result = queue(page, "/p?__trackId=b&__index=1", singleTrack: true)
145
+ #expect(result?.0 == ["b"])
146
+ #expect(result?.1 == 0)
147
+ }
148
+
149
+ @Test func aDisabledTappedTrackQueuesNothing() {
150
+ let page = [section(nil, [track("a"), Track(id: "off", src: "off", disabled: true)])]
151
+ #expect(queue(page, "/p?__trackId=off&__index=1") == nil)
152
+ #expect(queue(page, "/p?__trackId=off") == nil)
153
+ }
154
+
155
+ @Test func aVanishedIdentityOrAPlainPathQueuesNothing() {
156
+ let page = [section(nil, [track("a")])]
157
+ #expect(queue(page, "/p?__trackId=gone") == nil)
158
+ #expect(queue(page, "/p") == nil)
159
+ }
160
+
161
+ @Test func isPlayableNeedsASrcAndNoDisabled() {
162
+ #expect(Track(src: "s").isPlayable)
163
+ #expect(Track(src: "s", disabled: false).isPlayable)
164
+ #expect(!Track(src: "s", disabled: true).isPlayable)
165
+ #expect(!Track(path: "/p").isPlayable)
166
+ }
114
167
  }
@@ -7,9 +7,7 @@ private struct TestRoute: RouteEntry {
7
7
  }
8
8
 
9
9
  private func match(_ path: String, _ patterns: [String]) -> (route: TestRoute, match: RouteMatch)? {
10
- let router = SimpleRouter()
11
- let routes = patterns.map { TestRoute(path: $0) }
12
- return router.findBestMatch(path: path, routes: routes)
10
+ SimpleRouter.findBestMatch(path: path, routes: patterns.map { TestRoute(path: $0) })
13
11
  }
14
12
 
15
13
  // MARK: - exact matches
@@ -137,3 +135,40 @@ private func match(_ path: String, _ patterns: [String]) -> (route: TestRoute, m
137
135
  let result = match("/api/v1/users", ["/api/{version}/**", "/api/v1/**"])
138
136
  #expect(result!.route.path == "/api/v1/**")
139
137
  }
138
+
139
+ @Test func treatsADoubleStarBeforeTheEndAsAConstant() {
140
+ #expect(match("/a/**/b", ["/a/**/b"]) != nil)
141
+ #expect(match("/a/x/b", ["/a/**/b"]) == nil)
142
+ }
143
+
144
+ @Test func theFirstRouteWinsATie() {
145
+ let result = match("/artists/123", ["/artists/{id}", "/artists/{name}"])
146
+ #expect(result!.route.path == "/artists/{id}")
147
+ }
148
+
149
+ // MARK: - browse route
150
+
151
+ private func browseRoute(_ path: String, _ patterns: [String]) -> (route: TestRoute, match: RouteMatch)? {
152
+ SimpleRouter.browseRoute(for: path, in: patterns.map { TestRoute(path: $0) })
153
+ }
154
+
155
+ @Test func theSpecialRoutesNeverMatchABrowsePath() {
156
+ #expect(browseRoute("/__tabs__", ["__tabs__", "__search__"]) == nil)
157
+ #expect(browseRoute("__search__", ["__search__"]) == nil)
158
+ }
159
+
160
+ @Test func aMatchingRouteWinsOverTheDefault() {
161
+ let result = browseRoute("/artists/1", ["__default__", "/artists/{id}"])
162
+ #expect(result!.route.path == "/artists/{id}")
163
+ #expect(result!.match.params == ["id": "1"])
164
+ }
165
+
166
+ @Test func withoutAMatchTheDefaultRouteBrowsesThePath() {
167
+ let result = browseRoute("/a/b", ["/artists", "__default__"])
168
+ #expect(result!.route.path == "__default__")
169
+ #expect(result!.match.params == ["path": "/a/b"])
170
+ }
171
+
172
+ @Test func withoutAMatchOrADefaultThereIsNoBrowseRoute() {
173
+ #expect(browseRoute("/a/b", ["/artists", "__tabs__"]) == nil)
174
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-audio-browser",
3
- "version": "0.2.0-next.32",
3
+ "version": "0.2.0-next.33",
4
4
  "description": "react-native-audio-browser is a react native package built with Nitro",
5
5
  "keywords": [
6
6
  "react-native",