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

Configure Feed

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

chore: orchestrator components tests

+282
+84
tests/components/orchestrator/favourites/test.ts
··· 244 244 expect(result.afterRemoveCount).toBe(0); 245 245 }); 246 246 247 + it("isFavourite returns false before include", async () => { 248 + const result = await testWeb(async () => { 249 + const Output = await import( 250 + "~/components/configurator/output/element.js" 251 + ); 252 + const Favourites = await import( 253 + "~/components/orchestrator/favourites/element.js" 254 + ); 255 + const { tracks } = await import("~/testing/sample/tracks.js"); 256 + 257 + const output = new Output.CLASS(); 258 + output.id = "test-output"; 259 + document.body.append(output); 260 + 261 + const fav = new Favourites.CLASS(); 262 + fav.setAttribute("output-selector", "#test-output"); 263 + document.body.append(fav); 264 + 265 + await customElements.whenDefined(output.localName); 266 + await customElements.whenDefined(fav.localName); 267 + 268 + return fav.isFavourite(tracks[0]); 269 + }); 270 + 271 + expect(result).toBe(false); 272 + }); 273 + 274 + it("isFavourite returns true after include", async () => { 275 + const result = await testWeb(async () => { 276 + const Output = await import( 277 + "~/components/configurator/output/element.js" 278 + ); 279 + const Favourites = await import( 280 + "~/components/orchestrator/favourites/element.js" 281 + ); 282 + const { tracks } = await import("~/testing/sample/tracks.js"); 283 + 284 + const output = new Output.CLASS(); 285 + output.id = "test-output"; 286 + document.body.append(output); 287 + 288 + const fav = new Favourites.CLASS(); 289 + fav.setAttribute("output-selector", "#test-output"); 290 + document.body.append(fav); 291 + 292 + await customElements.whenDefined(output.localName); 293 + await customElements.whenDefined(fav.localName); 294 + 295 + await fav.include(tracks[0]); 296 + return fav.isFavourite(tracks[0]); 297 + }); 298 + 299 + expect(result).toBe(true); 300 + }); 301 + 302 + it("isFavourite returns false after expel", async () => { 303 + const result = await testWeb(async () => { 304 + const Output = await import( 305 + "~/components/configurator/output/element.js" 306 + ); 307 + const Favourites = await import( 308 + "~/components/orchestrator/favourites/element.js" 309 + ); 310 + const { tracks } = await import("~/testing/sample/tracks.js"); 311 + 312 + const output = new Output.CLASS(); 313 + output.id = "test-output"; 314 + document.body.append(output); 315 + 316 + const fav = new Favourites.CLASS(); 317 + fav.setAttribute("output-selector", "#test-output"); 318 + document.body.append(fav); 319 + 320 + await customElements.whenDefined(output.localName); 321 + await customElements.whenDefined(fav.localName); 322 + 323 + await fav.include(tracks[0]); 324 + await fav.expel(tracks[0]); 325 + return fav.isFavourite(tracks[0]); 326 + }); 327 + 328 + expect(result).toBe(false); 329 + }); 330 + 247 331 it("toggles mixed tracks (some already favourited)", async () => { 248 332 const favourites = await testWeb(async () => { 249 333 const Output = await import(
+198
tests/components/orchestrator/path-collections/test.ts
··· 1 + import { describe, it } from "@std/testing/bdd"; 2 + import { expect } from "@std/expect"; 3 + 4 + import { testWeb } from "@tests/common/index.ts"; 5 + 6 + describe("components/orchestrator/path-collections", () => { 7 + // Disconnected defaults 8 + 9 + it("ready returns false when no output is connected", async () => { 10 + const result = await testWeb(async () => { 11 + const mod = await import( 12 + "~/components/orchestrator/path-collections/element.js" 13 + ); 14 + const t = new mod.CLASS(); 15 + return t.ready(); 16 + }); 17 + 18 + expect(result).toBe(false); 19 + }); 20 + 21 + it("playlistItems.collection is loading when no output is connected", async () => { 22 + const result = await testWeb(async () => { 23 + const mod = await import( 24 + "~/components/orchestrator/path-collections/element.js" 25 + ); 26 + const t = new mod.CLASS(); 27 + return t.playlistItems.collection().state; 28 + }); 29 + 30 + expect(result).toBe("loading"); 31 + }); 32 + 33 + // Ephemeral item generation 34 + 35 + it("generates one ephemeral item per unique path segment from loaded tracks", async () => { 36 + const result = await testWeb(async () => { 37 + const idbMod = await import( 38 + "~/components/output/polymorphic/indexed-db/element.js" 39 + ); 40 + const mod = await import( 41 + "~/components/orchestrator/path-collections/element.js" 42 + ); 43 + 44 + const output = new idbMod.CLASS(); 45 + output.id = "test-idb-paths"; 46 + document.body.append(output); 47 + 48 + const t = new mod.CLASS(); 49 + t.setAttribute("output-selector", "#test-idb-paths"); 50 + document.body.append(t); 51 + 52 + await t.tracks.save([ 53 + { 54 + $type: "sh.diffuse.output.track", 55 + id: "t1", 56 + uri: "https://example.com/music/track1.mp3", 57 + }, 58 + { 59 + $type: "sh.diffuse.output.track", 60 + id: "t2", 61 + uri: "https://example.com/podcasts/ep1.mp3", 62 + }, 63 + ]); 64 + await output.playlistItems.save([]); 65 + 66 + const col = t.playlistItems.collection(); 67 + if (col.state !== "loaded") return null; 68 + const ephemeral = (col.data as Array<{ ephemeral?: boolean; playlist: string }>) 69 + .filter((p) => p.ephemeral) 70 + .map((p) => p.playlist) 71 + .sort(); 72 + return ephemeral; 73 + }); 74 + 75 + expect(result).toEqual(["music", "podcasts"]); 76 + }); 77 + 78 + it("deduplicates tracks that share the same path segment", async () => { 79 + const result = await testWeb(async () => { 80 + const idbMod = await import( 81 + "~/components/output/polymorphic/indexed-db/element.js" 82 + ); 83 + const mod = await import( 84 + "~/components/orchestrator/path-collections/element.js" 85 + ); 86 + 87 + const output = new idbMod.CLASS(); 88 + output.id = "test-idb-dedup"; 89 + document.body.append(output); 90 + 91 + const t = new mod.CLASS(); 92 + t.setAttribute("output-selector", "#test-idb-dedup"); 93 + document.body.append(t); 94 + 95 + await t.tracks.save([ 96 + { 97 + $type: "sh.diffuse.output.track", 98 + id: "t1", 99 + uri: "https://example.com/music/track1.mp3", 100 + }, 101 + { 102 + $type: "sh.diffuse.output.track", 103 + id: "t2", 104 + uri: "https://example.com/music/track2.mp3", 105 + }, 106 + ]); 107 + await output.playlistItems.save([]); 108 + 109 + const col = t.playlistItems.collection(); 110 + if (col.state !== "loaded") return null; 111 + return (col.data as Array<{ ephemeral?: boolean; playlist: string }>) 112 + .filter((p) => p.ephemeral) 113 + .map((p) => p.playlist); 114 + }); 115 + 116 + expect(result).toEqual(["music"]); 117 + }); 118 + 119 + it("ephemeral items do not include tracks with URI that has no path segment", async () => { 120 + const result = await testWeb(async () => { 121 + const idbMod = await import( 122 + "~/components/output/polymorphic/indexed-db/element.js" 123 + ); 124 + const mod = await import( 125 + "~/components/orchestrator/path-collections/element.js" 126 + ); 127 + 128 + const output = new idbMod.CLASS(); 129 + output.id = "test-idb-nopath"; 130 + document.body.append(output); 131 + 132 + const t = new mod.CLASS(); 133 + t.setAttribute("output-selector", "#test-idb-nopath"); 134 + document.body.append(t); 135 + 136 + // URI with no path segments (root only) 137 + await t.tracks.save([ 138 + { 139 + $type: "sh.diffuse.output.track", 140 + id: "t1", 141 + uri: "https://example.com/", 142 + }, 143 + ]); 144 + await output.playlistItems.save([]); 145 + 146 + const col = t.playlistItems.collection(); 147 + if (col.state !== "loaded") return null; 148 + return (col.data as Array<{ ephemeral?: boolean }>).filter((p) => 149 + p.ephemeral 150 + ).length; 151 + }); 152 + 153 + expect(result).toBe(0); 154 + }); 155 + 156 + // save filters ephemeral items 157 + 158 + it("save strips ephemeral items before writing to the backing output", async () => { 159 + const result = await testWeb(async () => { 160 + const idbMod = await import( 161 + "~/components/output/polymorphic/indexed-db/element.js" 162 + ); 163 + const mod = await import( 164 + "~/components/orchestrator/path-collections/element.js" 165 + ); 166 + 167 + const output = new idbMod.CLASS(); 168 + output.id = "test-idb-save"; 169 + document.body.append(output); 170 + 171 + const t = new mod.CLASS(); 172 + t.setAttribute("output-selector", "#test-idb-save"); 173 + document.body.append(t); 174 + 175 + await t.playlistItems.save([ 176 + { 177 + $type: "sh.diffuse.output.playlistItem", 178 + id: "kept", 179 + playlist: "Manual", 180 + criteria: [], 181 + }, 182 + { 183 + $type: "sh.diffuse.output.playlistItem", 184 + id: "dropped", 185 + playlist: "Generated", 186 + criteria: [], 187 + ephemeral: true, 188 + }, 189 + ]); 190 + 191 + const backingCol = output.playlistItems.collection(); 192 + if (backingCol.state !== "loaded") return null; 193 + return (backingCol.data as Array<{ id: string }>).map((p) => p.id); 194 + }); 195 + 196 + expect(result).toEqual(["kept"]); 197 + }); 198 + });