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.

chore: configurator components tests

+499
+147
tests/components/configurator/input/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 + import type { Track } from "~/definitions/types.d.ts"; 6 + 7 + describe("components/configurator/input", () => { 8 + it("listCached returns an empty array initially", async () => { 9 + const result = await testWeb(async () => { 10 + const mod = await import( 11 + "~/components/configurator/input/element.js" 12 + ); 13 + const configurator = new mod.CLASS(); 14 + document.body.append(configurator); 15 + return configurator.listCached(); 16 + }); 17 + 18 + expect(result).toEqual([]); 19 + }); 20 + 21 + it("consult with an unsupported scheme returns supported: false", async () => { 22 + const result = await testWeb(async () => { 23 + const mod = await import( 24 + "~/components/configurator/input/element.js" 25 + ); 26 + const configurator = new mod.CLASS(); 27 + document.body.append(configurator); 28 + return configurator.consult("ipfs://QmSomeCid"); 29 + }); 30 + 31 + expect(result.supported).toBe(false); 32 + }); 33 + 34 + it("resolve with an unsupported scheme returns undefined", async () => { 35 + const result = await testWeb(async () => { 36 + const mod = await import( 37 + "~/components/configurator/input/element.js" 38 + ); 39 + const configurator = new mod.CLASS(); 40 + document.body.append(configurator); 41 + const r = await configurator.resolve({ uri: "ipfs://QmSomeCid" }); 42 + return r ?? null; 43 + }); 44 + 45 + expect(result).toBe(null); 46 + }); 47 + 48 + it("detach with no matching input returns all tracks unchanged", async () => { 49 + const result = await testWeb(async () => { 50 + const mod = await import( 51 + "~/components/configurator/input/element.js" 52 + ); 53 + const configurator = new mod.CLASS(); 54 + document.body.append(configurator); 55 + 56 + const tracks: Track[] = [ 57 + { $type: "sh.diffuse.output.track", id: "t1", uri: "ipfs://Qm1" }, 58 + { $type: "sh.diffuse.output.track", id: "t2", uri: "ipfs://Qm2" }, 59 + ]; 60 + 61 + return configurator.detach({ fileUriOrScheme: "ipfs", tracks }); 62 + }); 63 + 64 + expect(result.map((t) => t.id)).toEqual(["t1", "t2"]); 65 + }); 66 + 67 + it("groupConsult marks unsupported schemes as unavailable", async () => { 68 + const result = await testWeb(async () => { 69 + const mod = await import( 70 + "~/components/configurator/input/element.js" 71 + ); 72 + const configurator = new mod.CLASS(); 73 + document.body.append(configurator); 74 + 75 + return configurator.groupConsult([ 76 + "ipfs://QmA", 77 + "ipfs://QmB", 78 + ]); 79 + }); 80 + 81 + expect(result["ipfs"]?.available).toBe(false); 82 + expect(result["ipfs"]?.uris).toEqual(["ipfs://QmA", "ipfs://QmB"]); 83 + }); 84 + 85 + it("inputs maps child input elements by their SCHEME", async () => { 86 + const result = await testWeb(async () => { 87 + const mod = await import( 88 + "~/components/configurator/input/element.js" 89 + ); 90 + const HttpsInput = await import( 91 + "~/components/input/https/element.js" 92 + ); 93 + 94 + const configurator = new mod.CLASS(); 95 + const httpsInput = new HttpsInput.CLASS(); 96 + configurator.appendChild(httpsInput); 97 + document.body.append(configurator); 98 + 99 + return Object.keys(configurator.inputs()); 100 + }); 101 + 102 + expect(result).toContain("https"); 103 + }); 104 + 105 + it("consult with an HTTPS URI delegates to the HTTPS input", async () => { 106 + const result = await testWeb(async () => { 107 + const mod = await import( 108 + "~/components/configurator/input/element.js" 109 + ); 110 + const HttpsInput = await import( 111 + "~/components/input/https/element.js" 112 + ); 113 + 114 + const configurator = new mod.CLASS(); 115 + const httpsInput = new HttpsInput.CLASS(); 116 + configurator.appendChild(httpsInput); 117 + document.body.append(configurator); 118 + 119 + return configurator.consult("https://example.com/audio.mp3"); 120 + }); 121 + 122 + expect(result.supported).toBe(true); 123 + }); 124 + 125 + it("resolve with an HTTPS URI returns a url via the HTTPS input", async () => { 126 + const result = await testWeb(async () => { 127 + const mod = await import( 128 + "~/components/configurator/input/element.js" 129 + ); 130 + const HttpsInput = await import( 131 + "~/components/input/https/element.js" 132 + ); 133 + 134 + const configurator = new mod.CLASS(); 135 + const httpsInput = new HttpsInput.CLASS(); 136 + configurator.appendChild(httpsInput); 137 + document.body.append(configurator); 138 + 139 + return configurator.resolve({ uri: "https://example.com/audio.mp3" }); 140 + }); 141 + 142 + expect(result).not.toBe(null); 143 + if (result && "url" in result) { 144 + expect(result.url).toBe("https://example.com/audio.mp3"); 145 + } 146 + }); 147 + });
+178
tests/components/configurator/output/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/configurator/output", () => { 7 + it("selected is null initially", async () => { 8 + const result = await testWeb(async () => { 9 + const mod = await import( 10 + "~/components/configurator/output/element.js" 11 + ); 12 + const configurator = new mod.CLASS(); 13 + document.body.append(configurator); 14 + return configurator.selected(); 15 + }); 16 + 17 + expect(result).toBe(null); 18 + }); 19 + 20 + it("activated is empty initially", async () => { 21 + const result = await testWeb(async () => { 22 + const mod = await import( 23 + "~/components/configurator/output/element.js" 24 + ); 25 + const configurator = new mod.CLASS(); 26 + document.body.append(configurator); 27 + return Array.from(configurator.activated()); 28 + }); 29 + 30 + expect(result).toEqual([]); 31 + }); 32 + 33 + it("hasSelected returns false when nothing is stored in localStorage", async () => { 34 + const result = await testWeb(async () => { 35 + const mod = await import( 36 + "~/components/configurator/output/element.js" 37 + ); 38 + const configurator = new mod.CLASS(); 39 + document.body.append(configurator); 40 + return configurator.hasSelected(); 41 + }); 42 + 43 + expect(result).toBe(false); 44 + }); 45 + 46 + it("hasDefault returns false without a default attribute", async () => { 47 + const result = await testWeb(async () => { 48 + const mod = await import( 49 + "~/components/configurator/output/element.js" 50 + ); 51 + const configurator = new mod.CLASS(); 52 + document.body.append(configurator); 53 + return configurator.hasDefault(); 54 + }); 55 + 56 + expect(result).toBe(false); 57 + }); 58 + 59 + it("hasDefault returns true when the default attribute is set", async () => { 60 + const result = await testWeb(async () => { 61 + const mod = await import( 62 + "~/components/configurator/output/element.js" 63 + ); 64 + const configurator = new mod.CLASS(); 65 + configurator.setAttribute("default", "my-output"); 66 + document.body.append(configurator); 67 + return configurator.hasDefault(); 68 + }); 69 + 70 + expect(result).toBe(true); 71 + }); 72 + 73 + it("select persists the id to localStorage", async () => { 74 + const stored = await testWeb(async () => { 75 + const mod = await import( 76 + "~/components/configurator/output/element.js" 77 + ); 78 + const configurator = new mod.CLASS(); 79 + document.body.append(configurator); 80 + await configurator.select("my-output"); 81 + return localStorage.getItem("diffuse/configurator/output/selected/id"); 82 + }); 83 + 84 + expect(stored).toBe("my-output"); 85 + }); 86 + 87 + it("hasSelected returns true after select", async () => { 88 + const result = await testWeb(async () => { 89 + const mod = await import( 90 + "~/components/configurator/output/element.js" 91 + ); 92 + const configurator = new mod.CLASS(); 93 + document.body.append(configurator); 94 + await configurator.select("my-output"); 95 + return configurator.hasSelected(); 96 + }); 97 + 98 + expect(result).toBe(true); 99 + }); 100 + 101 + it("activated includes id after select", async () => { 102 + const result = await testWeb(async () => { 103 + const mod = await import( 104 + "~/components/configurator/output/element.js" 105 + ); 106 + const configurator = new mod.CLASS(); 107 + document.body.append(configurator); 108 + await configurator.select("my-output"); 109 + return Array.from(configurator.activated()); 110 + }); 111 + 112 + expect(result).toContain("my-output"); 113 + }); 114 + 115 + it("deselect removes the id from localStorage", async () => { 116 + const stored = await testWeb(async () => { 117 + const mod = await import( 118 + "~/components/configurator/output/element.js" 119 + ); 120 + const configurator = new mod.CLASS(); 121 + document.body.append(configurator); 122 + await configurator.select("my-output"); 123 + await configurator.deselect(); 124 + return localStorage.getItem("diffuse/configurator/output/selected/id"); 125 + }); 126 + 127 + expect(stored).toBe(null); 128 + }); 129 + 130 + it("hasSelected returns false after deselect", async () => { 131 + const result = await testWeb(async () => { 132 + const mod = await import( 133 + "~/components/configurator/output/element.js" 134 + ); 135 + const configurator = new mod.CLASS(); 136 + document.body.append(configurator); 137 + await configurator.select("my-output"); 138 + await configurator.deselect(); 139 + return configurator.hasSelected(); 140 + }); 141 + 142 + expect(result).toBe(false); 143 + }); 144 + 145 + it("options lists child elements by id and label", async () => { 146 + const result = await testWeb(async () => { 147 + const mod = await import( 148 + "~/components/configurator/output/element.js" 149 + ); 150 + const configurator = new mod.CLASS(); 151 + 152 + const child = document.createElement("div"); 153 + child.id = "output-a"; 154 + child.setAttribute("label", "Output A"); 155 + configurator.appendChild(child); 156 + 157 + document.body.append(configurator); 158 + 159 + const opts = await configurator.options(); 160 + return opts.map((o) => ({ id: o.id, label: o.label })); 161 + }); 162 + 163 + expect(result).toEqual([{ id: "output-a", label: "Output A" }]); 164 + }); 165 + 166 + it("options returns an empty array when there are no children", async () => { 167 + const result = await testWeb(async () => { 168 + const mod = await import( 169 + "~/components/configurator/output/element.js" 170 + ); 171 + const configurator = new mod.CLASS(); 172 + document.body.append(configurator); 173 + return configurator.options(); 174 + }); 175 + 176 + expect(result).toEqual([]); 177 + }); 178 + });
+174
tests/components/configurator/scrobbles/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/configurator/scrobbles", () => { 7 + it("scrobblers returns empty array when there are no children", async () => { 8 + const result = await testWeb(async () => { 9 + const mod = await import( 10 + "~/components/configurator/scrobbles/element.js" 11 + ); 12 + const configurator = new mod.CLASS(); 13 + document.body.append(configurator); 14 + return configurator.scrobblers().length; 15 + }); 16 + 17 + expect(result).toBe(0); 18 + }); 19 + 20 + it("nowPlaying resolves without throwing when there are no active scrobblers", async () => { 21 + const result = await testWeb(async () => { 22 + const mod = await import( 23 + "~/components/configurator/scrobbles/element.js" 24 + ); 25 + const configurator = new mod.CLASS(); 26 + document.body.append(configurator); 27 + 28 + const track = { 29 + $type: "sh.diffuse.output.track" as const, 30 + id: "t1", 31 + uri: "https://example.com/audio.mp3", 32 + }; 33 + 34 + await configurator.nowPlaying(track); 35 + return true; 36 + }); 37 + 38 + expect(result).toBe(true); 39 + }); 40 + 41 + it("scrobble resolves without throwing when there are no active scrobblers", async () => { 42 + const result = await testWeb(async () => { 43 + const mod = await import( 44 + "~/components/configurator/scrobbles/element.js" 45 + ); 46 + const configurator = new mod.CLASS(); 47 + document.body.append(configurator); 48 + 49 + const track = { 50 + $type: "sh.diffuse.output.track" as const, 51 + id: "t1", 52 + uri: "https://example.com/audio.mp3", 53 + }; 54 + 55 + await configurator.scrobble(track, Date.now()); 56 + return true; 57 + }); 58 + 59 + expect(result).toBe(true); 60 + }); 61 + 62 + it("scrobblers includes child elements that have the scrobble interface", async () => { 63 + const result = await testWeb(async () => { 64 + const mod = await import( 65 + "~/components/configurator/scrobbles/element.js" 66 + ); 67 + const configurator = new mod.CLASS(); 68 + 69 + // Mock scrobbler with the required interface 70 + const mock = document.createElement("div"); 71 + (mock as any).isAuthenticated = () => true; 72 + (mock as any).nowPlaying = async () => {}; 73 + (mock as any).scrobble = async () => {}; 74 + configurator.appendChild(mock); 75 + 76 + document.body.append(configurator); 77 + 78 + return configurator.scrobblers().length; 79 + }); 80 + 81 + expect(result).toBe(1); 82 + }); 83 + 84 + it("scrobblers excludes children that do not have the scrobble interface", async () => { 85 + const result = await testWeb(async () => { 86 + const mod = await import( 87 + "~/components/configurator/scrobbles/element.js" 88 + ); 89 + const configurator = new mod.CLASS(); 90 + 91 + // Plain element without scrobble methods 92 + const plain = document.createElement("div"); 93 + configurator.appendChild(plain); 94 + 95 + document.body.append(configurator); 96 + 97 + return configurator.scrobblers().length; 98 + }); 99 + 100 + expect(result).toBe(0); 101 + }); 102 + 103 + it("nowPlaying calls all authenticated scrobblers", async () => { 104 + const result = await testWeb(async () => { 105 + const mod = await import( 106 + "~/components/configurator/scrobbles/element.js" 107 + ); 108 + const configurator = new mod.CLASS(); 109 + 110 + const calls: string[] = []; 111 + 112 + const auth = document.createElement("div"); 113 + (auth as any).isAuthenticated = () => true; 114 + (auth as any).nowPlaying = async () => { calls.push("auth"); }; 115 + (auth as any).scrobble = async () => {}; 116 + 117 + const unauth = document.createElement("div"); 118 + (unauth as any).isAuthenticated = () => false; 119 + (unauth as any).nowPlaying = async () => { calls.push("unauth"); }; 120 + (unauth as any).scrobble = async () => {}; 121 + 122 + configurator.appendChild(auth); 123 + configurator.appendChild(unauth); 124 + document.body.append(configurator); 125 + 126 + const track = { 127 + $type: "sh.diffuse.output.track" as const, 128 + id: "t1", 129 + uri: "https://example.com/audio.mp3", 130 + }; 131 + 132 + await configurator.nowPlaying(track); 133 + return calls; 134 + }); 135 + 136 + expect(result).toEqual(["auth"]); 137 + }); 138 + 139 + it("scrobble calls all authenticated scrobblers", async () => { 140 + const result = await testWeb(async () => { 141 + const mod = await import( 142 + "~/components/configurator/scrobbles/element.js" 143 + ); 144 + const configurator = new mod.CLASS(); 145 + 146 + const calls: string[] = []; 147 + 148 + const auth = document.createElement("div"); 149 + (auth as any).isAuthenticated = () => true; 150 + (auth as any).nowPlaying = async () => {}; 151 + (auth as any).scrobble = async () => { calls.push("auth"); }; 152 + 153 + const unauth = document.createElement("div"); 154 + (unauth as any).isAuthenticated = () => false; 155 + (unauth as any).nowPlaying = async () => {}; 156 + (unauth as any).scrobble = async () => { calls.push("unauth"); }; 157 + 158 + configurator.appendChild(auth); 159 + configurator.appendChild(unauth); 160 + document.body.append(configurator); 161 + 162 + const track = { 163 + $type: "sh.diffuse.output.track" as const, 164 + id: "t1", 165 + uri: "https://example.com/audio.mp3", 166 + }; 167 + 168 + await configurator.scrobble(track, Date.now()); 169 + return calls; 170 + }); 171 + 172 + expect(result).toEqual(["auth"]); 173 + }); 174 + });