forked from
tokono.ma/diffuse
A music player that connects to your cloud/distributed storage.
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});