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 { tracks } from "~/testing/sample/tracks.js";
6
7describe("components/engine/queue", () => {
8 it("adds tracks", async () => {
9 const items = await testWeb(async () => {
10 const QueueEngine = await import("~/components/engine/queue/element.js");
11 const engine = new QueueEngine.CLASS();
12
13 document.body.append(engine);
14
15 const { tracks } = await import("~/testing/sample/tracks.js");
16
17 await engine.add({ trackIds: tracks.map((t) => t.id) });
18 return engine.future();
19 });
20
21 const futureIds = items.map((i) => i.id).join("/");
22 const sampleIds = tracks.map((t) => t.id).join("/");
23
24 expect(futureIds).toEqual(sampleIds);
25 });
26
27 it("pools + fills tracks and shifts the queue", async () => {
28 const item = await testWeb(async () => {
29 const QueueEngine = await import("~/components/engine/queue/element.js");
30 const engine = new QueueEngine.CLASS();
31
32 document.body.append(engine);
33
34 const { tracks } = await import("~/testing/sample/tracks.js");
35
36 await engine.supply({ trackIds: tracks.map((t) => t.id) });
37 await engine.fill({ amount: 1, shuffled: false });
38 await engine.shift();
39
40 return engine.now();
41 });
42
43 expect(item?.id).toBe(tracks[0].id);
44 });
45
46 it("[shared worker] adds tracks and shifts + unshifts the queue", async () => {
47 const item = await testWeb(async () => {
48 const QueueEngine = await import("~/components/engine/queue/element.js");
49 const engine = new QueueEngine.CLASS();
50 engine.setAttribute("group", "tests");
51
52 document.body.append(engine);
53
54 const { tracks } = await import("~/testing/sample/tracks.js");
55
56 await engine.add({ trackIds: tracks.map((t) => t.id) });
57 await engine.shift();
58 await engine.shift();
59 await engine.unshift();
60
61 return engine.now();
62 });
63
64 expect(item?.id).toBe(tracks[0].id);
65 });
66
67 it("adds tracks to the front with inFront: true", async () => {
68 const items = await testWeb(async () => {
69 const QueueEngine = await import("~/components/engine/queue/element.js");
70 const engine = new QueueEngine.CLASS();
71
72 document.body.append(engine);
73
74 const { tracks } = await import("~/testing/sample/tracks.js");
75
76 await engine.add({ trackIds: [tracks[1].id] });
77 await engine.add({ inFront: true, trackIds: [tracks[0].id] });
78 return engine.future();
79 });
80
81 expect(items[0].id).toBe(tracks[0].id);
82 expect(items[1].id).toBe(tracks[1].id);
83 });
84
85 it("clears only auto-filled items when keepManual is true", async () => {
86 const items = await testWeb(async () => {
87 const QueueEngine = await import("~/components/engine/queue/element.js");
88 const engine = new QueueEngine.CLASS();
89
90 document.body.append(engine);
91
92 const { tracks } = await import("~/testing/sample/tracks.js");
93
94 await engine.supply({ trackIds: tracks.map((t) => t.id) });
95 await engine.add({ trackIds: [tracks[0].id] });
96 await engine.fill({ amount: 2, shuffled: false });
97 await engine.clear({ keepManual: true });
98 return engine.future();
99 });
100
101 expect(items.length).toBe(1);
102 expect(items[0].manualEntry).toBe(true);
103 });
104
105 it("clears all items when keepManual is false", async () => {
106 const count = await testWeb(async () => {
107 const QueueEngine = await import("~/components/engine/queue/element.js");
108 const engine = new QueueEngine.CLASS();
109
110 document.body.append(engine);
111
112 const { tracks } = await import("~/testing/sample/tracks.js");
113
114 await engine.add({ trackIds: tracks.map((t) => t.id) });
115 await engine.clear({ keepManual: false });
116 return (await engine.future()).length;
117 });
118
119 expect(count).toBe(0);
120 });
121
122 it("supply fingerprint is consistent for the same track IDs", async () => {
123 const [fp1, fp2, fp3] = await testWeb(async () => {
124 const QueueEngine = await import("~/components/engine/queue/element.js");
125 const engine = new QueueEngine.CLASS();
126
127 document.body.append(engine);
128
129 const { tracks } = await import("~/testing/sample/tracks.js");
130 const trackIds = tracks.map((t) => t.id);
131
132 await engine.supply({ trackIds });
133 const fp1 = await engine.supplyFingerprint();
134
135 await engine.supply({ trackIds });
136 const fp2 = await engine.supplyFingerprint();
137
138 await engine.supply({ trackIds: [trackIds[0]] });
139 const fp3 = await engine.supplyFingerprint();
140
141 return [fp1, fp2, fp3];
142 });
143
144 expect(fp1).toBe(fp2);
145 expect(fp1).not.toBe(fp3);
146 });
147
148 it("fill with augment adds on top of existing auto items", async () => {
149 const count = await testWeb(async () => {
150 const QueueEngine = await import("~/components/engine/queue/element.js");
151 const engine = new QueueEngine.CLASS();
152
153 document.body.append(engine);
154
155 const { tracks } = await import("~/testing/sample/tracks.js");
156
157 await engine.supply({ trackIds: tracks.map((t) => t.id) });
158 await engine.fill({ amount: 2, shuffled: true });
159 await engine.fill({ augment: true, amount: 1, shuffled: true });
160 return (await engine.future()).length;
161 });
162
163 expect(count).toBe(3);
164 });
165
166 it("moves a future item to a new position", async () => {
167 const ids = await testWeb(async () => {
168 const QueueEngine = await import("~/components/engine/queue/element.js");
169 const engine = new QueueEngine.CLASS();
170
171 document.body.append(engine);
172
173 const { tracks } = await import("~/testing/sample/tracks.js");
174
175 await engine.add({ trackIds: tracks.slice(0, 3).map((t) => t.id) });
176 await engine.move({ from: 0, to: 2 });
177 return engine.future().map((i) => i.id);
178 });
179
180 expect(ids[0]).toBe(tracks[1].id);
181 expect(ids[1]).toBe(tracks[2].id);
182 expect(ids[2]).toBe(tracks[0].id);
183 });
184
185 it("move preserves now when reordering around it", async () => {
186 const result = await testWeb(async () => {
187 const QueueEngine = await import("~/components/engine/queue/element.js");
188 const engine = new QueueEngine.CLASS();
189
190 document.body.append(engine);
191
192 const { tracks } = await import("~/testing/sample/tracks.js");
193
194 await engine.add({ trackIds: tracks.slice(0, 3).map((t) => t.id) });
195 await engine.shift();
196 // flat list: [past[0]=tracks[0]], now=tracks[1], future=[tracks[2]]
197 // move future item (idx 2) before now (idx 1)
198 await engine.move({ from: 2, to: 0 });
199
200 return {
201 now: engine.now()?.id,
202 past: engine.past().map((i) => i.id),
203 future: engine.future().map((i) => i.id),
204 };
205 });
206
207 // shift() moved tracks[0] to now; move({ from:2, to:0 }) put tracks[2]
208 // before now, so: past=[tracks[2]], now=tracks[0], future=[tracks[1]]
209 expect(result.now).toBe(tracks[0].id);
210 expect(result.past[0]).toBe(tracks[2].id);
211 expect(result.future[0]).toBe(tracks[1].id);
212 });
213
214 it("move does nothing when from === to", async () => {
215 const ids = await testWeb(async () => {
216 const QueueEngine = await import("~/components/engine/queue/element.js");
217 const engine = new QueueEngine.CLASS();
218
219 document.body.append(engine);
220
221 const { tracks } = await import("~/testing/sample/tracks.js");
222
223 await engine.add({ trackIds: tracks.slice(0, 3).map((t) => t.id) });
224 await engine.move({ from: 1, to: 1 });
225 return engine.future().map((i) => i.id);
226 });
227
228 expect(ids[0]).toBe(tracks[0].id);
229 expect(ids[1]).toBe(tracks[1].id);
230 expect(ids[2]).toBe(tracks[2].id);
231 });
232
233 it("move does nothing for out-of-bounds indices", async () => {
234 const ids = await testWeb(async () => {
235 const QueueEngine = await import("~/components/engine/queue/element.js");
236 const engine = new QueueEngine.CLASS();
237
238 document.body.append(engine);
239
240 const { tracks } = await import("~/testing/sample/tracks.js");
241
242 await engine.add({ trackIds: tracks.slice(0, 2).map((t) => t.id) });
243 await engine.move({ from: 0, to: 99 });
244 return engine.future().map((i) => i.id);
245 });
246
247 expect(ids[0]).toBe(tracks[0].id);
248 expect(ids[1]).toBe(tracks[1].id);
249 });
250
251 it("[shared worker] has the correct past", async () => {
252 const item = await testWeb(async () => {
253 const QueueEngine = await import("~/components/engine/queue/element.js");
254 const engine = new QueueEngine.CLASS();
255 engine.setAttribute("group", "tests");
256
257 document.body.append(engine);
258
259 const { tracks } = await import("~/testing/sample/tracks.js");
260
261 await engine.add({ trackIds: tracks.map((t) => t.id) });
262 await engine.shift();
263 await engine.shift();
264
265 return engine.past()[0];
266 });
267
268 expect(item?.id).toBe(tracks[0].id);
269 });
270});