A music player that connects to your cloud/distributed storage.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at v4 379 lines 11 kB view raw
1import { describe, it } from "@std/testing/bdd"; 2import { expect } from "@std/expect"; 3 4import { testWeb } from "@tests/common/index.ts"; 5import type { Track } from "~/definitions/types.d.ts"; 6 7describe("components/input/ephemeral-cache", () => { 8 it("has correct SCHEME property", async () => { 9 const scheme = await testWeb(async () => { 10 const mod = await import( 11 "~/components/input/ephemeral-cache/element.js" 12 ); 13 const input = new mod.CLASS(); 14 document.body.append(input); 15 return input.SCHEME; 16 }); 17 18 expect(scheme).toBe("ephemeral+cache"); 19 }); 20 21 it("artwork returns null", async () => { 22 const result = await testWeb(async () => { 23 const mod = await import( 24 "~/components/input/ephemeral-cache/element.js" 25 ); 26 const input = new mod.CLASS(); 27 document.body.append(input); 28 return await input.artwork("ephemeral+cache://bafktest"); 29 }); 30 31 expect(result).toBe(null); 32 }); 33 34 it("consult returns undetermined for scheme only", async () => { 35 const result = await testWeb(async () => { 36 const mod = await import( 37 "~/components/input/ephemeral-cache/element.js" 38 ); 39 const input = new mod.CLASS(); 40 document.body.append(input); 41 return await input.consult("ephemeral+cache"); 42 }); 43 44 expect(result.supported).toBe(true); 45 if (result.supported) { 46 expect(result.consult).toBe("undetermined"); 47 } 48 }); 49 50 it("consult returns false for an uncached URI", async () => { 51 const result = await testWeb(async () => { 52 const mod = await import( 53 "~/components/input/ephemeral-cache/element.js" 54 ); 55 const input = new mod.CLASS(); 56 document.body.append(input); 57 return await input.consult("ephemeral+cache://bafknotcached"); 58 }); 59 60 expect(result.supported).toBe(true); 61 if (result.supported) { 62 expect(result.consult).toBe(false); 63 } 64 }); 65 66 it("consult returns true for a cached URI", async () => { 67 const result = await testWeb(async () => { 68 const IDB = await import("idb-keyval"); 69 const { CACHE_KEY_PREFIX } = await import( 70 "~/components/input/ephemeral-cache/constants.js" 71 ); 72 const mod = await import( 73 "~/components/input/ephemeral-cache/element.js" 74 ); 75 const input = new mod.CLASS(); 76 document.body.append(input); 77 78 const uri = "ephemeral+cache://bafkcacheduri"; 79 await IDB.set( 80 CACHE_KEY_PREFIX + uri, 81 new Blob(["audio"], { type: "audio/mpeg" }), 82 ); 83 84 const result = await input.consult(uri); 85 await IDB.del(CACHE_KEY_PREFIX + uri); 86 return result; 87 }); 88 89 expect(result.supported).toBe(true); 90 if (result.supported) { 91 expect(result.consult).toBe(true); 92 } 93 }); 94 95 it("groupConsult returns only cached URIs as available", async () => { 96 const result = await testWeb(async () => { 97 const IDB = await import("idb-keyval"); 98 const { CACHE_KEY_PREFIX } = await import( 99 "~/components/input/ephemeral-cache/constants.js" 100 ); 101 const mod = await import( 102 "~/components/input/ephemeral-cache/element.js" 103 ); 104 const input = new mod.CLASS(); 105 document.body.append(input); 106 107 const cachedUri = "ephemeral+cache://bafkgroupcached"; 108 const uncachedUri = "ephemeral+cache://bafkgroupuncached"; 109 await IDB.set( 110 CACHE_KEY_PREFIX + cachedUri, 111 new Blob(["audio"], { type: "audio/mpeg" }), 112 ); 113 114 const result = await input.groupConsult([cachedUri, uncachedUri]); 115 await IDB.del(CACHE_KEY_PREFIX + cachedUri); 116 return result; 117 }); 118 119 expect(result["ephemeral+cache"]?.available).toBe(true); 120 expect(result["ephemeral+cache"]?.uris).toEqual([ 121 "ephemeral+cache://bafkgroupcached", 122 ]); 123 }); 124 125 it("groupConsult with no cached URIs returns empty uris list", async () => { 126 const result = await testWeb(async () => { 127 const mod = await import( 128 "~/components/input/ephemeral-cache/element.js" 129 ); 130 const input = new mod.CLASS(); 131 document.body.append(input); 132 133 return await input.groupConsult([ 134 "ephemeral+cache://bafkuncached1", 135 "ephemeral+cache://bafkuncached2", 136 ]); 137 }); 138 139 expect(result["ephemeral+cache"]?.available).toBe(true); 140 expect(result["ephemeral+cache"]?.uris).toEqual([]); 141 }); 142 143 it("resolve returns undefined for an uncached URI", async () => { 144 const result = await testWeb(async () => { 145 const mod = await import( 146 "~/components/input/ephemeral-cache/element.js" 147 ); 148 const input = new mod.CLASS(); 149 document.body.append(input); 150 const r = await input.resolve({ uri: "ephemeral+cache://bafknotcached" }); 151 return r ?? null; 152 }); 153 154 expect(result).toBe(null); 155 }); 156 157 it("resolve returns a blob URL with Infinity expiry for a cached URI", async () => { 158 const result = await testWeb(async () => { 159 const IDB = await import("idb-keyval"); 160 const { CACHE_KEY_PREFIX } = await import( 161 "~/components/input/ephemeral-cache/constants.js" 162 ); 163 const mod = await import( 164 "~/components/input/ephemeral-cache/element.js" 165 ); 166 const input = new mod.CLASS(); 167 document.body.append(input); 168 169 const uri = "ephemeral+cache://bafkresolvetest"; 170 await IDB.set( 171 CACHE_KEY_PREFIX + uri, 172 new Blob(["audio"], { type: "audio/mpeg" }), 173 ); 174 175 const resolved = await input.resolve({ uri }); 176 await IDB.del(CACHE_KEY_PREFIX + uri); 177 if (!resolved || !("url" in resolved)) return null; 178 return { url: resolved.url, neverExpires: resolved.expiresAt === Infinity }; 179 }); 180 181 expect(result).not.toBe(null); 182 if (result) { 183 expect(result.url).toMatch(/^blob:/); 184 expect(result.neverExpires).toBe(true); 185 } 186 }); 187 188 it("resolve returns the same blob URL on repeated calls", async () => { 189 const [url1, url2] = await testWeb(async () => { 190 const IDB = await import("idb-keyval"); 191 const { CACHE_KEY_PREFIX } = await import( 192 "~/components/input/ephemeral-cache/constants.js" 193 ); 194 const mod = await import( 195 "~/components/input/ephemeral-cache/element.js" 196 ); 197 const input = new mod.CLASS(); 198 document.body.append(input); 199 200 const uri = "ephemeral+cache://bafkresolvetwice"; 201 await IDB.set( 202 CACHE_KEY_PREFIX + uri, 203 new Blob(["audio"], { type: "audio/mpeg" }), 204 ); 205 206 const r1 = await input.resolve({ uri }); 207 const r2 = await input.resolve({ uri }); 208 await IDB.del(CACHE_KEY_PREFIX + uri); 209 return [ 210 r1 && "url" in r1 ? r1.url : null, 211 r2 && "url" in r2 ? r2.url : null, 212 ]; 213 }); 214 215 expect(url1).not.toBe(null); 216 expect(url1).toBe(url2); 217 }); 218 219 it("list returns tracks unchanged", async () => { 220 const result = await testWeb(async () => { 221 const mod = await import( 222 "~/components/input/ephemeral-cache/element.js" 223 ); 224 const input = new mod.CLASS(); 225 document.body.append(input); 226 227 const tracks: Track[] = [ 228 { 229 $type: "sh.diffuse.output.track", 230 id: "t1", 231 uri: "ephemeral+cache://bafktrack1", 232 }, 233 { 234 $type: "sh.diffuse.output.track", 235 id: "t2", 236 uri: "ephemeral+cache://bafktrack2", 237 }, 238 ]; 239 240 return await input.list(tracks); 241 }); 242 243 expect(result.map((t) => t.id)).toEqual(["t1", "t2"]); 244 }); 245 246 it("detaches all tracks when given the scheme", async () => { 247 const remaining = await testWeb(async () => { 248 const IDB = await import("idb-keyval"); 249 const { CACHE_KEY_PREFIX } = await import( 250 "~/components/input/ephemeral-cache/constants.js" 251 ); 252 const mod = await import( 253 "~/components/input/ephemeral-cache/element.js" 254 ); 255 const input = new mod.CLASS(); 256 document.body.append(input); 257 258 const tracks: Track[] = [ 259 { 260 $type: "sh.diffuse.output.track", 261 id: "t1", 262 uri: "ephemeral+cache://bafkdetach1", 263 }, 264 { 265 $type: "sh.diffuse.output.track", 266 id: "t2", 267 uri: "ephemeral+cache://bafkdetach2", 268 }, 269 ]; 270 271 for (const t of tracks) { 272 await IDB.set(CACHE_KEY_PREFIX + t.uri, new Blob(["audio"])); 273 } 274 275 return await input.detach({ fileUriOrScheme: "ephemeral+cache", tracks }); 276 }); 277 278 expect(remaining.length).toBe(0); 279 }); 280 281 it("detaches a specific track when given a URI", async () => { 282 const remaining = await testWeb(async () => { 283 const IDB = await import("idb-keyval"); 284 const { CACHE_KEY_PREFIX } = await import( 285 "~/components/input/ephemeral-cache/constants.js" 286 ); 287 const mod = await import( 288 "~/components/input/ephemeral-cache/element.js" 289 ); 290 const input = new mod.CLASS(); 291 document.body.append(input); 292 293 const tracks: Track[] = [ 294 { 295 $type: "sh.diffuse.output.track", 296 id: "t1", 297 uri: "ephemeral+cache://bafkremove", 298 }, 299 { 300 $type: "sh.diffuse.output.track", 301 id: "t2", 302 uri: "ephemeral+cache://bafkkeep", 303 }, 304 ]; 305 306 for (const t of tracks) { 307 await IDB.set(CACHE_KEY_PREFIX + t.uri, new Blob(["audio"])); 308 } 309 310 return await input.detach({ 311 fileUriOrScheme: "ephemeral+cache://bafkremove", 312 tracks, 313 }); 314 }); 315 316 expect(remaining.length).toBe(1); 317 expect(remaining[0].id).toBe("t2"); 318 }); 319 320 it("detach with non-matching scheme returns tracks unchanged", async () => { 321 const remaining = await testWeb(async () => { 322 const mod = await import( 323 "~/components/input/ephemeral-cache/element.js" 324 ); 325 const input = new mod.CLASS(); 326 document.body.append(input); 327 328 const tracks: Track[] = [ 329 { 330 $type: "sh.diffuse.output.track", 331 id: "t1", 332 uri: "ephemeral+cache://bafk1", 333 }, 334 { 335 $type: "sh.diffuse.output.track", 336 id: "t2", 337 uri: "ephemeral+cache://bafk2", 338 }, 339 ]; 340 341 return await input.detach({ fileUriOrScheme: "https", tracks }); 342 }); 343 344 expect(remaining.map((t) => t.id)).toEqual(["t1", "t2"]); 345 }); 346 347 it("sources returns tracks mapped by URI as label", async () => { 348 const sources = await testWeb(async () => { 349 const mod = await import( 350 "~/components/input/ephemeral-cache/element.js" 351 ); 352 const input = new mod.CLASS(); 353 document.body.append(input); 354 355 const tracks: Track[] = [ 356 { 357 $type: "sh.diffuse.output.track", 358 id: "t1", 359 uri: "ephemeral+cache://bafksrc1", 360 }, 361 { 362 $type: "sh.diffuse.output.track", 363 id: "t2", 364 uri: "ephemeral+cache://bafksrc2", 365 tags: { title: "My Song" }, 366 }, 367 ]; 368 369 return input.sources(tracks); 370 }); 371 372 expect(sources.length).toBe(2); 373 expect(sources[0].uri).toBe("ephemeral+cache://bafksrc1"); 374 expect(sources[0].label).toBe("ephemeral+cache://bafksrc1"); 375 // label is always the URI regardless of tags, to keep sources stable across processing 376 expect(sources[1].uri).toBe("ephemeral+cache://bafksrc2"); 377 expect(sources[1].label).toBe("ephemeral+cache://bafksrc2"); 378 }); 379});