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: engine components tests

+573
+181
tests/components/engine/audio/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/engine/audio", () => { 7 + it("has default volume of 0.75", async () => { 8 + const result = await testWeb(async () => { 9 + const mod = await import("~/components/engine/audio/element.js"); 10 + const engine = new mod.CLASS(); 11 + document.body.append(engine); 12 + return engine.volume(); 13 + }); 14 + 15 + expect(result).toBe(0.75); 16 + }); 17 + 18 + it("adjustVolume updates the global volume signal", async () => { 19 + const result = await testWeb(async () => { 20 + const mod = await import("~/components/engine/audio/element.js"); 21 + const engine = new mod.CLASS(); 22 + document.body.append(engine); 23 + engine.adjustVolume({ volume: 0.5 }); 24 + return engine.volume(); 25 + }); 26 + 27 + expect(result).toBe(0.5); 28 + }); 29 + 30 + it("adjustVolume clamps to the provided value", async () => { 31 + const result = await testWeb(async () => { 32 + const mod = await import("~/components/engine/audio/element.js"); 33 + const engine = new mod.CLASS(); 34 + document.body.append(engine); 35 + engine.adjustVolume({ volume: 1.0 }); 36 + return engine.volume(); 37 + }); 38 + 39 + expect(result).toBe(1.0); 40 + }); 41 + 42 + it("isPlaying returns false with no items", async () => { 43 + const result = await testWeb(async () => { 44 + const mod = await import("~/components/engine/audio/element.js"); 45 + const engine = new mod.CLASS(); 46 + document.body.append(engine); 47 + return engine.isPlaying(); 48 + }); 49 + 50 + expect(result).toBe(false); 51 + }); 52 + 53 + it("supply with URL items updates the items signal", async () => { 54 + const result = await testWeb(async () => { 55 + const mod = await import("~/components/engine/audio/element.js"); 56 + const { trackA, trackB } = await import("~/testing/sample/tracks.js"); 57 + const engine = new mod.CLASS(); 58 + document.body.append(engine); 59 + 60 + engine.supply({ 61 + audio: [ 62 + { id: "audio-a", url: trackA.uri, isPreload: false, track: trackA }, 63 + { id: "audio-b", url: trackB.uri, isPreload: false, track: trackB }, 64 + ], 65 + }); 66 + 67 + return engine.items().map((i) => i.id); 68 + }); 69 + 70 + expect(result).toEqual(["audio-a", "audio-b"]); 71 + }); 72 + 73 + it("supply with same IDs does not update items signal", async () => { 74 + const result = await testWeb(async () => { 75 + const mod = await import("~/components/engine/audio/element.js"); 76 + const { trackA } = await import("~/testing/sample/tracks.js"); 77 + const engine = new mod.CLASS(); 78 + document.body.append(engine); 79 + 80 + const item = { id: "audio-a", url: trackA.uri, isPreload: false, track: trackA }; 81 + 82 + engine.supply({ audio: [item] }); 83 + const itemsAfterFirst = engine.items(); 84 + 85 + engine.supply({ audio: [item] }); 86 + const itemsAfterSecond = engine.items(); 87 + 88 + // Same reference means the signal was not updated 89 + return itemsAfterFirst === itemsAfterSecond; 90 + }); 91 + 92 + expect(result).toBe(true); 93 + }); 94 + 95 + it("supply replaces items when IDs change", async () => { 96 + const result = await testWeb(async () => { 97 + const mod = await import("~/components/engine/audio/element.js"); 98 + const { trackA, trackB } = await import("~/testing/sample/tracks.js"); 99 + const engine = new mod.CLASS(); 100 + document.body.append(engine); 101 + 102 + engine.supply({ 103 + audio: [ 104 + { id: "audio-a", url: trackA.uri, isPreload: false, track: trackA }, 105 + ], 106 + }); 107 + 108 + engine.supply({ 109 + audio: [ 110 + { id: "audio-b", url: trackB.uri, isPreload: false, track: trackB }, 111 + ], 112 + }); 113 + 114 + return engine.items().map((i) => i.id); 115 + }); 116 + 117 + expect(result).toEqual(["audio-b"]); 118 + }); 119 + 120 + it("supply with isPreload change triggers items update", async () => { 121 + const result = await testWeb(async () => { 122 + const mod = await import("~/components/engine/audio/element.js"); 123 + const { trackA } = await import("~/testing/sample/tracks.js"); 124 + const engine = new mod.CLASS(); 125 + document.body.append(engine); 126 + 127 + engine.supply({ 128 + audio: [ 129 + { id: "audio-a", url: trackA.uri, isPreload: true, track: trackA }, 130 + ], 131 + }); 132 + 133 + engine.supply({ 134 + audio: [ 135 + { id: "audio-a", url: trackA.uri, isPreload: false, track: trackA }, 136 + ], 137 + }); 138 + 139 + return engine.items()[0]?.isPreload; 140 + }); 141 + 142 + expect(result).toBe(false); 143 + }); 144 + 145 + it("persists volume to localStorage", async () => { 146 + const stored = await testWeb(async () => { 147 + const mod = await import("~/components/engine/audio/element.js"); 148 + const engine = new mod.CLASS(); 149 + document.body.append(engine); 150 + engine.adjustVolume({ volume: 0.3 }); 151 + 152 + for (let i = 0; i < localStorage.length; i++) { 153 + const key = localStorage.key(i)!; 154 + if (key.includes("engine/audio") && key.endsWith("/volume")) { 155 + return localStorage.getItem(key); 156 + } 157 + } 158 + return null; 159 + }); 160 + 161 + expect(stored).toBe("0.3"); 162 + }); 163 + 164 + it("restores volume from localStorage on connect", async () => { 165 + const result = await testWeb(async () => { 166 + const mod = await import("~/components/engine/audio/element.js"); 167 + 168 + // Set volume with first engine instance 169 + const engine1 = new mod.CLASS(); 170 + document.body.append(engine1); 171 + engine1.adjustVolume({ volume: 0.4 }); 172 + 173 + // Second instance reads from localStorage 174 + const engine2 = new mod.CLASS(); 175 + document.body.append(engine2); 176 + return engine2.volume(); 177 + }); 178 + 179 + expect(result).toBe(0.4); 180 + }); 181 + });
+176
tests/components/engine/repeat-shuffle/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/engine/repeat-shuffle", () => { 7 + it("defaults to false for both repeat and shuffle", async () => { 8 + const result = await testWeb(async () => { 9 + const mod = await import( 10 + "~/components/engine/repeat-shuffle/element.js" 11 + ); 12 + const engine = new mod.CLASS(); 13 + document.body.append(engine); 14 + return { repeat: engine.repeat(), shuffle: engine.shuffle() }; 15 + }); 16 + 17 + expect(result.repeat).toBe(false); 18 + expect(result.shuffle).toBe(false); 19 + }); 20 + 21 + it("setRepeat sets repeat to true", async () => { 22 + const result = await testWeb(async () => { 23 + const mod = await import( 24 + "~/components/engine/repeat-shuffle/element.js" 25 + ); 26 + const engine = new mod.CLASS(); 27 + document.body.append(engine); 28 + await engine.setRepeat(true); 29 + return engine.repeat(); 30 + }); 31 + 32 + expect(result).toBe(true); 33 + }); 34 + 35 + it("setRepeat sets repeat back to false", async () => { 36 + const result = await testWeb(async () => { 37 + const mod = await import( 38 + "~/components/engine/repeat-shuffle/element.js" 39 + ); 40 + const engine = new mod.CLASS(); 41 + document.body.append(engine); 42 + await engine.setRepeat(true); 43 + await engine.setRepeat(false); 44 + return engine.repeat(); 45 + }); 46 + 47 + expect(result).toBe(false); 48 + }); 49 + 50 + it("setShuffle sets shuffle to true", async () => { 51 + const result = await testWeb(async () => { 52 + const mod = await import( 53 + "~/components/engine/repeat-shuffle/element.js" 54 + ); 55 + const engine = new mod.CLASS(); 56 + document.body.append(engine); 57 + await engine.setShuffle(true); 58 + return engine.shuffle(); 59 + }); 60 + 61 + expect(result).toBe(true); 62 + }); 63 + 64 + it("setShuffle sets shuffle back to false", async () => { 65 + const result = await testWeb(async () => { 66 + const mod = await import( 67 + "~/components/engine/repeat-shuffle/element.js" 68 + ); 69 + const engine = new mod.CLASS(); 70 + document.body.append(engine); 71 + await engine.setShuffle(true); 72 + await engine.setShuffle(false); 73 + return engine.shuffle(); 74 + }); 75 + 76 + expect(result).toBe(false); 77 + }); 78 + 79 + it("repeat and shuffle are independent", async () => { 80 + const result = await testWeb(async () => { 81 + const mod = await import( 82 + "~/components/engine/repeat-shuffle/element.js" 83 + ); 84 + const engine = new mod.CLASS(); 85 + document.body.append(engine); 86 + await engine.setRepeat(true); 87 + return { repeat: engine.repeat(), shuffle: engine.shuffle() }; 88 + }); 89 + 90 + expect(result.repeat).toBe(true); 91 + expect(result.shuffle).toBe(false); 92 + }); 93 + 94 + it("persists repeat to localStorage", async () => { 95 + const stored = await testWeb(async () => { 96 + const mod = await import( 97 + "~/components/engine/repeat-shuffle/element.js" 98 + ); 99 + const engine = new mod.CLASS(); 100 + document.body.append(engine); 101 + await engine.setRepeat(true); 102 + 103 + // Find the key by iterating localStorage 104 + for (let i = 0; i < localStorage.length; i++) { 105 + const key = localStorage.key(i)!; 106 + if (key.includes("repeat-shuffle") && key.endsWith("/repeat")) { 107 + return localStorage.getItem(key); 108 + } 109 + } 110 + return null; 111 + }); 112 + 113 + expect(stored).toBe("true"); 114 + }); 115 + 116 + it("persists shuffle to localStorage", async () => { 117 + const stored = await testWeb(async () => { 118 + const mod = await import( 119 + "~/components/engine/repeat-shuffle/element.js" 120 + ); 121 + const engine = new mod.CLASS(); 122 + document.body.append(engine); 123 + await engine.setShuffle(true); 124 + 125 + for (let i = 0; i < localStorage.length; i++) { 126 + const key = localStorage.key(i)!; 127 + if (key.includes("repeat-shuffle") && key.endsWith("/shuffle")) { 128 + return localStorage.getItem(key); 129 + } 130 + } 131 + return null; 132 + }); 133 + 134 + expect(stored).toBe("true"); 135 + }); 136 + 137 + it("restores repeat state from localStorage on connect", async () => { 138 + const result = await testWeb(async () => { 139 + const mod = await import( 140 + "~/components/engine/repeat-shuffle/element.js" 141 + ); 142 + 143 + // Set state with first engine instance 144 + const engine1 = new mod.CLASS(); 145 + document.body.append(engine1); 146 + await engine1.setRepeat(true); 147 + 148 + // Second instance reads same localStorage key 149 + const engine2 = new mod.CLASS(); 150 + document.body.append(engine2); 151 + return engine2.repeat(); 152 + }); 153 + 154 + expect(result).toBe(true); 155 + }); 156 + 157 + it("restores shuffle state from localStorage on connect", async () => { 158 + const result = await testWeb(async () => { 159 + const mod = await import( 160 + "~/components/engine/repeat-shuffle/element.js" 161 + ); 162 + 163 + // Set state with first engine instance 164 + const engine1 = new mod.CLASS(); 165 + document.body.append(engine1); 166 + await engine1.setShuffle(true); 167 + 168 + // Second instance reads same localStorage key 169 + const engine2 = new mod.CLASS(); 170 + document.body.append(engine2); 171 + return engine2.shuffle(); 172 + }); 173 + 174 + expect(result).toBe(true); 175 + }); 176 + });
+216
tests/components/engine/scope/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/engine/scope", () => { 7 + it("has default sortBy of ['createdAt']", async () => { 8 + const result = await testWeb(async () => { 9 + const mod = await import("~/components/engine/scope/element.js"); 10 + const engine = new mod.CLASS(); 11 + document.body.append(engine); 12 + return engine.sortBy(); 13 + }); 14 + 15 + expect(result).toEqual(["createdAt"]); 16 + }); 17 + 18 + it("has default sortDirection of 'desc'", async () => { 19 + const result = await testWeb(async () => { 20 + const mod = await import("~/components/engine/scope/element.js"); 21 + const engine = new mod.CLASS(); 22 + document.body.append(engine); 23 + return engine.sortDirection(); 24 + }); 25 + 26 + expect(result).toBe("desc"); 27 + }); 28 + 29 + it("has default groupBy of undefined", async () => { 30 + const result = await testWeb(async () => { 31 + const mod = await import("~/components/engine/scope/element.js"); 32 + const engine = new mod.CLASS(); 33 + document.body.append(engine); 34 + return engine.groupBy() ?? null; 35 + }); 36 + 37 + expect(result).toBe(null); 38 + }); 39 + 40 + it("has default playlist of undefined", async () => { 41 + const result = await testWeb(async () => { 42 + const mod = await import("~/components/engine/scope/element.js"); 43 + const engine = new mod.CLASS(); 44 + document.body.append(engine); 45 + return engine.playlist() ?? null; 46 + }); 47 + 48 + expect(result).toBe(null); 49 + }); 50 + 51 + it("has default searchTerm of undefined", async () => { 52 + const result = await testWeb(async () => { 53 + const mod = await import("~/components/engine/scope/element.js"); 54 + const engine = new mod.CLASS(); 55 + document.body.append(engine); 56 + return engine.searchTerm() ?? null; 57 + }); 58 + 59 + expect(result).toBe(null); 60 + }); 61 + 62 + it("setGroupBy updates groupBy", async () => { 63 + const result = await testWeb(async () => { 64 + const mod = await import("~/components/engine/scope/element.js"); 65 + const engine = new mod.CLASS(); 66 + document.body.append(engine); 67 + await engine.setGroupBy("artist"); 68 + return engine.groupBy(); 69 + }); 70 + 71 + expect(result).toBe("artist"); 72 + }); 73 + 74 + it("setGroupBy with undefined clears groupBy", async () => { 75 + const result = await testWeb(async () => { 76 + const mod = await import("~/components/engine/scope/element.js"); 77 + const engine = new mod.CLASS(); 78 + document.body.append(engine); 79 + await engine.setGroupBy("artist"); 80 + await engine.setGroupBy(undefined); 81 + return engine.groupBy() ?? null; 82 + }); 83 + 84 + expect(result).toBe(null); 85 + }); 86 + 87 + it("setPlaylist updates playlist", async () => { 88 + const result = await testWeb(async () => { 89 + const mod = await import("~/components/engine/scope/element.js"); 90 + const engine = new mod.CLASS(); 91 + document.body.append(engine); 92 + await engine.setPlaylist("playlist-123"); 93 + return engine.playlist(); 94 + }); 95 + 96 + expect(result).toBe("playlist-123"); 97 + }); 98 + 99 + it("setSearchTerm updates searchTerm", async () => { 100 + const result = await testWeb(async () => { 101 + const mod = await import("~/components/engine/scope/element.js"); 102 + const engine = new mod.CLASS(); 103 + document.body.append(engine); 104 + await engine.setSearchTerm("hello world"); 105 + return engine.searchTerm(); 106 + }); 107 + 108 + expect(result).toBe("hello world"); 109 + }); 110 + 111 + it("setSortBy updates sortBy", async () => { 112 + const result = await testWeb(async () => { 113 + const mod = await import("~/components/engine/scope/element.js"); 114 + const engine = new mod.CLASS(); 115 + document.body.append(engine); 116 + await engine.setSortBy(["title", "artist"]); 117 + return engine.sortBy(); 118 + }); 119 + 120 + expect(result).toEqual(["title", "artist"]); 121 + }); 122 + 123 + it("setSortDirection updates sortDirection", async () => { 124 + const result = await testWeb(async () => { 125 + const mod = await import("~/components/engine/scope/element.js"); 126 + const engine = new mod.CLASS(); 127 + document.body.append(engine); 128 + await engine.setSortDirection("asc"); 129 + return engine.sortDirection(); 130 + }); 131 + 132 + expect(result).toBe("asc"); 133 + }); 134 + 135 + it("revertToDefaultSort resets sortBy and sortDirection", async () => { 136 + const result = await testWeb(async () => { 137 + const mod = await import("~/components/engine/scope/element.js"); 138 + const engine = new mod.CLASS(); 139 + document.body.append(engine); 140 + await engine.setSortBy(["title"]); 141 + await engine.setSortDirection("asc"); 142 + await engine.revertToDefaultSort(); 143 + return { sortBy: engine.sortBy(), sortDirection: engine.sortDirection() }; 144 + }); 145 + 146 + expect(result.sortBy).toEqual(["createdAt"]); 147 + expect(result.sortDirection).toBe("desc"); 148 + }); 149 + 150 + it("persists groupBy to localStorage", async () => { 151 + const stored = await testWeb(async () => { 152 + const mod = await import("~/components/engine/scope/element.js"); 153 + const engine = new mod.CLASS(); 154 + document.body.append(engine); 155 + await engine.setGroupBy("album"); 156 + 157 + for (let i = 0; i < localStorage.length; i++) { 158 + const key = localStorage.key(i)!; 159 + if (key.includes("scope") && key.endsWith("/groupBy")) { 160 + return localStorage.getItem(key); 161 + } 162 + } 163 + return null; 164 + }); 165 + 166 + expect(stored).toBe("album"); 167 + }); 168 + 169 + it("persists sortBy to localStorage as JSON", async () => { 170 + const stored = await testWeb(async () => { 171 + const mod = await import("~/components/engine/scope/element.js"); 172 + const engine = new mod.CLASS(); 173 + document.body.append(engine); 174 + await engine.setSortBy(["title", "artist"]); 175 + 176 + for (let i = 0; i < localStorage.length; i++) { 177 + const key = localStorage.key(i)!; 178 + if (key.includes("scope") && key.endsWith("/sortBy")) { 179 + return localStorage.getItem(key); 180 + } 181 + } 182 + return null; 183 + }); 184 + 185 + expect(stored).toBe('["title","artist"]'); 186 + }); 187 + 188 + it("restores values from localStorage on connect", async () => { 189 + const result = await testWeb(async () => { 190 + const mod = await import("~/components/engine/scope/element.js"); 191 + 192 + // Set state with first engine instance 193 + const engine1 = new mod.CLASS(); 194 + document.body.append(engine1); 195 + await engine1.setGroupBy("artist"); 196 + await engine1.setSearchTerm("test"); 197 + await engine1.setSortBy(["title"]); 198 + await engine1.setSortDirection("asc"); 199 + 200 + // Second instance reads same localStorage keys 201 + const engine2 = new mod.CLASS(); 202 + document.body.append(engine2); 203 + return { 204 + groupBy: engine2.groupBy(), 205 + searchTerm: engine2.searchTerm(), 206 + sortBy: engine2.sortBy(), 207 + sortDirection: engine2.sortDirection(), 208 + }; 209 + }); 210 + 211 + expect(result.groupBy).toBe("artist"); 212 + expect(result.searchTerm).toBe("test"); 213 + expect(result.sortBy).toEqual(["title"]); 214 + expect(result.sortDirection).toBe("asc"); 215 + }); 216 + });