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: more tests

+1926
+79
tests/common/cid/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("common/cid", () => { 7 + describe("create", () => { 8 + it("returns a non-empty CID string", async () => { 9 + const result = await testWeb(async () => { 10 + const { create } = await import("~/common/cid.js"); 11 + return create(0x55, new TextEncoder().encode("hello")); 12 + }); 13 + expect(typeof result).toBe("string"); 14 + expect(result.length).toBeGreaterThan(0); 15 + }); 16 + 17 + it("returns consistent CID for same input", async () => { 18 + const result = await testWeb(async () => { 19 + const { create } = await import("~/common/cid.js"); 20 + const data = new TextEncoder().encode("hello world"); 21 + const a = await create(0x55, data); 22 + const b = await create(0x55, data); 23 + return a === b; 24 + }); 25 + expect(result).toBe(true); 26 + }); 27 + 28 + it("returns different CIDs for different inputs", async () => { 29 + const result = await testWeb(async () => { 30 + const { create } = await import("~/common/cid.js"); 31 + const a = await create(0x55, new TextEncoder().encode("hello")); 32 + const b = await create(0x55, new TextEncoder().encode("world")); 33 + return a === b; 34 + }); 35 + expect(result).toBe(false); 36 + }); 37 + 38 + it("returns different CIDs for different codecs", async () => { 39 + const result = await testWeb(async () => { 40 + const { create } = await import("~/common/cid.js"); 41 + const data = new TextEncoder().encode("hello"); 42 + const a = await create(0x55, data); 43 + const b = await create(0x71, data); 44 + return a === b; 45 + }); 46 + expect(result).toBe(false); 47 + }); 48 + 49 + it("returns a CID starting with 'bafy'", async () => { 50 + const result = await testWeb(async () => { 51 + const { create } = await import("~/common/cid.js"); 52 + return create(0x55, new TextEncoder().encode("test")); 53 + }); 54 + expect(result.startsWith("bafy")).toBe(true); 55 + }); 56 + }); 57 + 58 + describe("verify", () => { 59 + it("returns true for matching data and CID", async () => { 60 + const result = await testWeb(async () => { 61 + const { create, verify } = await import("~/common/cid.js"); 62 + const data = new TextEncoder().encode("hello"); 63 + const cid = await create(0x55, data); 64 + return verify(data, cid); 65 + }); 66 + expect(result).toBe(true); 67 + }); 68 + 69 + it("returns false for mismatched data", async () => { 70 + const result = await testWeb(async () => { 71 + const { create, verify } = await import("~/common/cid.js"); 72 + const data = new TextEncoder().encode("hello"); 73 + const cid = await create(0x55, data); 74 + return verify(new TextEncoder().encode("world"), cid); 75 + }); 76 + expect(result).toBe(false); 77 + }); 78 + }); 79 + });
+107
tests/common/compare/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("common/compare", () => { 7 + describe("diff", () => { 8 + it("returns true for identical primitives", async () => { 9 + const result = await testWeb(async () => { 10 + const { diff } = await import("~/common/compare.js"); 11 + return diff(1, 1); 12 + }); 13 + expect(result).toBe(true); 14 + }); 15 + 16 + it("returns false for different primitives", async () => { 17 + const result = await testWeb(async () => { 18 + const { diff } = await import("~/common/compare.js"); 19 + return diff(1, 2); 20 + }); 21 + expect(result).toBe(false); 22 + }); 23 + 24 + it("returns true for deeply equal objects", async () => { 25 + const result = await testWeb(async () => { 26 + const { diff } = await import("~/common/compare.js"); 27 + return diff({ a: 1, b: { c: 2 } }, { a: 1, b: { c: 2 } }); 28 + }); 29 + expect(result).toBe(true); 30 + }); 31 + 32 + it("returns false for objects with different values", async () => { 33 + const result = await testWeb(async () => { 34 + const { diff } = await import("~/common/compare.js"); 35 + return diff({ a: 1 }, { a: 2 }); 36 + }); 37 + expect(result).toBe(false); 38 + }); 39 + 40 + it("returns false for objects with different keys", async () => { 41 + const result = await testWeb(async () => { 42 + const { diff } = await import("~/common/compare.js"); 43 + return diff({ a: 1 }, { b: 1 }); 44 + }); 45 + expect(result).toBe(false); 46 + }); 47 + 48 + it("returns true for identical arrays", async () => { 49 + const result = await testWeb(async () => { 50 + const { diff } = await import("~/common/compare.js"); 51 + return diff([1, 2, 3], [1, 2, 3]); 52 + }); 53 + expect(result).toBe(true); 54 + }); 55 + 56 + it("returns false for arrays with different elements", async () => { 57 + const result = await testWeb(async () => { 58 + const { diff } = await import("~/common/compare.js"); 59 + return diff([1, 2], [1, 3]); 60 + }); 61 + expect(result).toBe(false); 62 + }); 63 + }); 64 + 65 + describe("strictEquality", () => { 66 + it("returns true for same primitive value", async () => { 67 + const result = await testWeb(async () => { 68 + const { strictEquality } = await import("~/common/compare.js"); 69 + return strictEquality(42, 42); 70 + }); 71 + expect(result).toBe(true); 72 + }); 73 + 74 + it("returns false for different primitive values", async () => { 75 + const result = await testWeb(async () => { 76 + const { strictEquality } = await import("~/common/compare.js"); 77 + return strictEquality(42, 43); 78 + }); 79 + expect(result).toBe(false); 80 + }); 81 + 82 + it("returns false for same-content objects (reference inequality)", async () => { 83 + const result = await testWeb(async () => { 84 + const { strictEquality } = await import("~/common/compare.js"); 85 + return strictEquality({ a: 1 }, { a: 1 }); 86 + }); 87 + expect(result).toBe(false); 88 + }); 89 + 90 + it("returns true for same object reference", async () => { 91 + const result = await testWeb(async () => { 92 + const { strictEquality } = await import("~/common/compare.js"); 93 + const obj = { a: 1 }; 94 + return strictEquality(obj, obj); 95 + }); 96 + expect(result).toBe(true); 97 + }); 98 + 99 + it("returns false for null vs undefined", async () => { 100 + const result = await testWeb(async () => { 101 + const { strictEquality } = await import("~/common/compare.js"); 102 + return strictEquality(null, undefined); 103 + }); 104 + expect(result).toBe(false); 105 + }); 106 + }); 107 + });
+297
tests/common/facets/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("common/facets/category", () => { 7 + it("color returns accent-twist-4 for prelude kind", async () => { 8 + const result = await testWeb(async () => { 9 + const { color } = await import("~/common/facets/category.js"); 10 + return color({ $type: "sh.diffuse.output.facet", id: "1", name: "x", kind: "prelude" }); 11 + }); 12 + expect(result).toBe("var(--accent-twist-4)"); 13 + }); 14 + 15 + it("color returns accent-twist-2 for interactive kind", async () => { 16 + const result = await testWeb(async () => { 17 + const { color } = await import("~/common/facets/category.js"); 18 + return color({ $type: "sh.diffuse.output.facet", id: "1", name: "x", kind: "interactive" }); 19 + }); 20 + expect(result).toBe("var(--accent-twist-2)"); 21 + }); 22 + 23 + it("color returns accent-twist-2 for undefined kind", async () => { 24 + const result = await testWeb(async () => { 25 + const { color } = await import("~/common/facets/category.js"); 26 + return color({ $type: "sh.diffuse.output.facet", id: "1", name: "x" }); 27 + }); 28 + expect(result).toBe("var(--accent-twist-2)"); 29 + }); 30 + 31 + it("name returns 'feature' for prelude kind", async () => { 32 + const result = await testWeb(async () => { 33 + const { name } = await import("~/common/facets/category.js"); 34 + return name({ $type: "sh.diffuse.output.facet", id: "1", name: "x", kind: "prelude" }); 35 + }); 36 + expect(result).toBe("feature"); 37 + }); 38 + 39 + it("name returns 'interface' for interactive kind", async () => { 40 + const result = await testWeb(async () => { 41 + const { name } = await import("~/common/facets/category.js"); 42 + return name({ $type: "sh.diffuse.output.facet", id: "1", name: "x", kind: "interactive" }); 43 + }); 44 + expect(result).toBe("interface"); 45 + }); 46 + 47 + it("name returns 'interface' for undefined kind", async () => { 48 + const result = await testWeb(async () => { 49 + const { name } = await import("~/common/facets/category.js"); 50 + return name({ $type: "sh.diffuse.output.facet", id: "1", name: "x" }); 51 + }); 52 + expect(result).toBe("interface"); 53 + }); 54 + }); 55 + 56 + describe("common/facets/constants", () => { 57 + it("TYPE is sh.diffuse.output.facet", async () => { 58 + const result = await testWeb(async () => { 59 + const { TYPE } = await import("~/common/facets/constants.js"); 60 + return TYPE; 61 + }); 62 + expect(result).toBe("sh.diffuse.output.facet"); 63 + }); 64 + 65 + it("STARTING_SET_URIS includes connect facet", async () => { 66 + const result = await testWeb(async () => { 67 + const { STARTING_SET_URIS } = await import("~/common/facets/constants.js"); 68 + return STARTING_SET_URIS; 69 + }); 70 + expect(result).toContain("facets/connect/index.html"); 71 + }); 72 + 73 + it("STARTING_SET_URIS includes blur artwork controller", async () => { 74 + const result = await testWeb(async () => { 75 + const { STARTING_SET_URIS } = await import("~/common/facets/constants.js"); 76 + return STARTING_SET_URIS; 77 + }); 78 + expect(result).toContain("themes/blur/artwork-controller/facet/index.html"); 79 + }); 80 + 81 + it("STARTING_SET_URIS has 9 entries", async () => { 82 + const result = await testWeb(async () => { 83 + const { STARTING_SET_URIS } = await import("~/common/facets/constants.js"); 84 + return STARTING_SET_URIS.length; 85 + }); 86 + expect(result).toBe(9); 87 + }); 88 + }); 89 + 90 + describe("common/facets/utils", () => { 91 + it("facetFromURI sets $type", async () => { 92 + const result = await testWeb(async () => { 93 + const { facetFromURI } = await import("~/common/facets/utils.js"); 94 + const facet = await facetFromURI( 95 + { name: "Test", uri: "test.html", kind: undefined, description: undefined }, 96 + { fetchHTML: false }, 97 + ); 98 + return facet.$type; 99 + }); 100 + expect(result).toBe("sh.diffuse.output.facet"); 101 + }); 102 + 103 + it("facetFromURI sets name and uri", async () => { 104 + const result = await testWeb(async () => { 105 + const { facetFromURI } = await import("~/common/facets/utils.js"); 106 + const facet = await facetFromURI( 107 + { name: "My Facet", uri: "facets/test/index.html", kind: undefined, description: undefined }, 108 + { fetchHTML: false }, 109 + ); 110 + return { name: facet.name, uri: facet.uri }; 111 + }); 112 + expect(result.name).toBe("My Facet"); 113 + expect(result.uri).toBe("facets/test/index.html"); 114 + }); 115 + 116 + it("facetFromURI with fetchHTML false leaves html and cid undefined", async () => { 117 + const result = await testWeb(async () => { 118 + const { facetFromURI } = await import("~/common/facets/utils.js"); 119 + const facet = await facetFromURI( 120 + { name: "Test", uri: "test.html", kind: undefined, description: undefined }, 121 + { fetchHTML: false }, 122 + ); 123 + return { html: facet.html ?? null, cid: facet.cid ?? null }; 124 + }); 125 + expect(result.html).toBe(null); 126 + expect(result.cid).toBe(null); 127 + }); 128 + 129 + it("facetFromURI sets prelude kind", async () => { 130 + const result = await testWeb(async () => { 131 + const { facetFromURI } = await import("~/common/facets/utils.js"); 132 + const facet = await facetFromURI( 133 + { name: "Test", uri: "test.html", kind: "prelude", description: undefined }, 134 + { fetchHTML: false }, 135 + ); 136 + return facet.kind; 137 + }); 138 + expect(result).toBe("prelude"); 139 + }); 140 + 141 + it("facetFromURI sets interactive kind", async () => { 142 + const result = await testWeb(async () => { 143 + const { facetFromURI } = await import("~/common/facets/utils.js"); 144 + const facet = await facetFromURI( 145 + { name: "Test", uri: "test.html", kind: "interactive", description: undefined }, 146 + { fetchHTML: false }, 147 + ); 148 + return facet.kind; 149 + }); 150 + expect(result).toBe("interactive"); 151 + }); 152 + 153 + it("facetFromURI sets kind to undefined for unrecognised kind", async () => { 154 + const result = await testWeb(async () => { 155 + const { facetFromURI } = await import("~/common/facets/utils.js"); 156 + const facet = await facetFromURI( 157 + { name: "Test", uri: "test.html", kind: "unknown", description: undefined }, 158 + { fetchHTML: false }, 159 + ); 160 + return facet.kind ?? null; 161 + }); 162 + expect(result).toBe(null); 163 + }); 164 + 165 + it("facetFromURI sets description", async () => { 166 + const result = await testWeb(async () => { 167 + const { facetFromURI } = await import("~/common/facets/utils.js"); 168 + const facet = await facetFromURI( 169 + { name: "Test", uri: "test.html", kind: undefined, description: "A test facet" }, 170 + { fetchHTML: false }, 171 + ); 172 + return facet.description; 173 + }); 174 + expect(result).toBe("A test facet"); 175 + }); 176 + 177 + it("facetFromURI sets createdAt and updatedAt as matching ISO strings", async () => { 178 + const result = await testWeb(async () => { 179 + const { facetFromURI } = await import("~/common/facets/utils.js"); 180 + const facet = await facetFromURI( 181 + { name: "Test", uri: "test.html", kind: undefined, description: undefined }, 182 + { fetchHTML: false }, 183 + ); 184 + return { createdAt: facet.createdAt, updatedAt: facet.updatedAt }; 185 + }); 186 + expect(result.createdAt).toBeTruthy(); 187 + expect(result.createdAt).toBe(result.updatedAt); 188 + expect(new Date(result.createdAt!).toISOString()).toBe(result.createdAt); 189 + }); 190 + 191 + it("facetFromURI generates a non-empty string id", async () => { 192 + const result = await testWeb(async () => { 193 + const { facetFromURI } = await import("~/common/facets/utils.js"); 194 + const facet = await facetFromURI( 195 + { name: "Test", uri: "test.html", kind: undefined, description: undefined }, 196 + { fetchHTML: false }, 197 + ); 198 + return facet.id; 199 + }); 200 + expect(typeof result).toBe("string"); 201 + expect(result.length).toBeGreaterThan(0); 202 + }); 203 + 204 + it("facetFromURI generates unique ids", async () => { 205 + const result = await testWeb(async () => { 206 + const { facetFromURI } = await import("~/common/facets/utils.js"); 207 + const a = await facetFromURI( 208 + { name: "A", uri: "a.html", kind: undefined, description: undefined }, 209 + { fetchHTML: false }, 210 + ); 211 + const b = await facetFromURI( 212 + { name: "B", uri: "b.html", kind: undefined, description: undefined }, 213 + { fetchHTML: false }, 214 + ); 215 + return a.id === b.id; 216 + }); 217 + expect(result).toBe(false); 218 + }); 219 + }); 220 + 221 + describe("common/facets/prelude", () => { 222 + it("insertPreludes only inserts prelude kind facets", async () => { 223 + const result = await testWeb(async () => { 224 + const { insertPreludes } = await import("~/common/facets/prelude.js"); 225 + const container = document.createElement("div"); 226 + document.body.append(container); 227 + 228 + await insertPreludes( 229 + [ 230 + { $type: "sh.diffuse.output.facet", id: "1", name: "A", kind: "interactive", html: "<span id='fp-interactive'></span>" }, 231 + { $type: "sh.diffuse.output.facet", id: "2", name: "B", kind: "prelude", html: "<span id='fp-prelude'></span>" }, 232 + ], 233 + container, 234 + ); 235 + 236 + return { 237 + hasInteractive: !!container.querySelector("#fp-interactive"), 238 + hasPrelude: !!container.querySelector("#fp-prelude"), 239 + }; 240 + }); 241 + 242 + expect(result.hasInteractive).toBe(false); 243 + expect(result.hasPrelude).toBe(true); 244 + }); 245 + 246 + it("insertPreludes sorts preludes alphabetically by name", async () => { 247 + const result = await testWeb(async () => { 248 + const { insertPreludes } = await import("~/common/facets/prelude.js"); 249 + const container = document.createElement("div"); 250 + document.body.append(container); 251 + 252 + await insertPreludes( 253 + [ 254 + { $type: "sh.diffuse.output.facet", id: "1", name: "Zebra", kind: "prelude", html: "<span class='fp-order'>Zebra</span>" }, 255 + { $type: "sh.diffuse.output.facet", id: "2", name: "Alpha", kind: "prelude", html: "<span class='fp-order'>Alpha</span>" }, 256 + { $type: "sh.diffuse.output.facet", id: "3", name: "Mango", kind: "prelude", html: "<span class='fp-order'>Mango</span>" }, 257 + ], 258 + container, 259 + ); 260 + 261 + return Array.from(container.querySelectorAll(".fp-order")).map((el) => el.textContent); 262 + }); 263 + 264 + expect(result).toEqual(["Alpha", "Mango", "Zebra"]); 265 + }); 266 + 267 + it("insertPreludes defaults to document.body", async () => { 268 + const result = await testWeb(async () => { 269 + const { insertPreludes } = await import("~/common/facets/prelude.js"); 270 + 271 + await insertPreludes([ 272 + { $type: "sh.diffuse.output.facet", id: "1", name: "Test", kind: "prelude", html: "<span id='fp-body-test'></span>" }, 273 + ]); 274 + 275 + return !!document.body.querySelector("#fp-body-test"); 276 + }); 277 + 278 + expect(result).toBe(true); 279 + }); 280 + 281 + it("insertPreludes skips prelude with no html and no uri", async () => { 282 + const result = await testWeb(async () => { 283 + const { insertPreludes } = await import("~/common/facets/prelude.js"); 284 + const container = document.createElement("div"); 285 + document.body.append(container); 286 + 287 + await insertPreludes( 288 + [{ $type: "sh.diffuse.output.facet", id: "1", name: "Empty", kind: "prelude" }], 289 + container, 290 + ); 291 + 292 + return container.childNodes.length; 293 + }); 294 + 295 + expect(result).toBe(0); 296 + }); 297 + });
+94
tests/common/loader/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("common/loader", () => { 7 + describe("renderError", () => { 8 + it("sets innerHTML on the container", async () => { 9 + const result = await testWeb(async () => { 10 + const { renderError } = await import("~/common/loader.js"); 11 + const container = document.createElement("div"); 12 + renderError(container, "Something went wrong"); 13 + return container.innerHTML.length > 0; 14 + }); 15 + expect(result).toBe(true); 16 + }); 17 + 18 + it("includes the error message in the output", async () => { 19 + const result = await testWeb(async () => { 20 + const { renderError } = await import("~/common/loader.js"); 21 + const container = document.createElement("div"); 22 + renderError(container, "Track not found"); 23 + return container.innerHTML; 24 + }); 25 + expect(result).toContain("Track not found"); 26 + }); 27 + 28 + it("does not throw when options.throw is false", async () => { 29 + const result = await testWeb(async () => { 30 + const { renderError } = await import("~/common/loader.js"); 31 + const container = document.createElement("div"); 32 + try { 33 + renderError(container, "oops", { throw: false }); 34 + return "no-throw"; 35 + } catch { 36 + return "threw"; 37 + } 38 + }); 39 + expect(result).toBe("no-throw"); 40 + }); 41 + 42 + it("throws when options.throw is true", async () => { 43 + const result = await testWeb(async () => { 44 + const { renderError } = await import("~/common/loader.js"); 45 + const container = document.createElement("div"); 46 + try { 47 + renderError(container, "fatal error", { throw: true }); 48 + return "no-throw"; 49 + } catch (e) { 50 + return (e as Error).message; 51 + } 52 + }); 53 + expect(result).toBe("fatal error"); 54 + }); 55 + 56 + it("throws the provided context error", async () => { 57 + const result = await testWeb(async () => { 58 + const { renderError } = await import("~/common/loader.js"); 59 + const container = document.createElement("div"); 60 + const ctx = new Error("context error"); 61 + try { 62 + renderError(container, "label", { context: ctx, throw: true }); 63 + return null; 64 + } catch (e) { 65 + return (e as Error).message; 66 + } 67 + }); 68 + expect(result).toBe("context error"); 69 + }); 70 + }); 71 + 72 + describe("ensureHTML", () => { 73 + it("returns item unchanged when html is already set", async () => { 74 + const result = await testWeb(async () => { 75 + const { ensureHTML } = await import("~/common/loader.js"); 76 + const item = { html: "<p>existing</p>", uri: "https://example.com/facet.html" }; 77 + const returned = await ensureHTML(item); 78 + return { sameRef: returned === item, html: returned.html }; 79 + }); 80 + expect(result.sameRef).toBe(true); 81 + expect(result.html).toBe("<p>existing</p>"); 82 + }); 83 + 84 + it("returns item unchanged when uri is absent", async () => { 85 + const result = await testWeb(async () => { 86 + const { ensureHTML } = await import("~/common/loader.js"); 87 + const item = { html: undefined, uri: undefined }; 88 + const returned = await ensureHTML(item); 89 + return returned.html ?? null; 90 + }); 91 + expect(result).toBe(null); 92 + }); 93 + }); 94 + });
+42
tests/common/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("common/output", () => { 7 + describe("data", () => { 8 + it("resolves immediately when collection is already loaded", async () => { 9 + const result = await testWeb(async () => { 10 + const { data } = await import("~/common/output.js"); 11 + const { signal } = await import("~/common/signal.js"); 12 + 13 + const col = signal<{ state: "loading" } | { state: "loaded"; data: string[] }>( 14 + { state: "loaded", data: ["a", "b"] }, 15 + ); 16 + 17 + return data({ collection: col.get }); 18 + }); 19 + expect(result).toEqual(["a", "b"]); 20 + }); 21 + 22 + it("waits for collection to become loaded", async () => { 23 + const result = await testWeb(async () => { 24 + const { data } = await import("~/common/output.js"); 25 + const { signal } = await import("~/common/signal.js"); 26 + 27 + const col = signal<{ state: "loading" } | { state: "loaded"; data: number[] }>( 28 + { state: "loading" }, 29 + ); 30 + 31 + const promise = data({ collection: col.get }); 32 + 33 + // Transition to loaded after a microtask 34 + await Promise.resolve(); 35 + col.set({ state: "loaded", data: [1, 2, 3] }); 36 + 37 + return promise; 38 + }); 39 + expect(result).toEqual([1, 2, 3]); 40 + }); 41 + }); 42 + });
+310
tests/common/playlist/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 + // Minimal test fixtures 7 + const trackA = { 8 + $type: "sh.diffuse.output.track", 9 + id: "track-a", 10 + uri: "http://example.com/a.mp3", 11 + tags: { artist: "Artist A", title: "Song A" }, 12 + }; 13 + 14 + const trackB = { 15 + $type: "sh.diffuse.output.track", 16 + id: "track-b", 17 + uri: "http://example.com/b.mp3", 18 + tags: { artist: "Artist B", title: "Song B" }, 19 + }; 20 + 21 + describe("common/playlist", () => { 22 + describe("match", () => { 23 + it("matches a track that satisfies all criteria", async () => { 24 + const result = await testWeb(async () => { 25 + const { match } = await import("~/common/playlist.js"); 26 + const track = { 27 + $type: "sh.diffuse.output.track", 28 + id: "t", 29 + uri: "http://x.com/t.mp3", 30 + tags: { artist: "Artist A", title: "Song A" }, 31 + }; 32 + const item = { 33 + $type: "sh.diffuse.output.playlistItem", 34 + id: "i", 35 + playlist: "Favourites", 36 + criteria: [ 37 + { field: "tags.artist", value: "Artist A" }, 38 + { field: "tags.title", value: "Song A" }, 39 + ], 40 + }; 41 + return match(track as never, item as never); 42 + }); 43 + expect(result).toBe(true); 44 + }); 45 + 46 + it("does not match when one criterion fails", async () => { 47 + const result = await testWeb(async () => { 48 + const { match } = await import("~/common/playlist.js"); 49 + const track = { 50 + $type: "sh.diffuse.output.track", 51 + id: "t", 52 + uri: "http://x.com/t.mp3", 53 + tags: { artist: "Artist A", title: "Wrong Title" }, 54 + }; 55 + const item = { 56 + $type: "sh.diffuse.output.playlistItem", 57 + id: "i", 58 + playlist: "Favourites", 59 + criteria: [ 60 + { field: "tags.artist", value: "Artist A" }, 61 + { field: "tags.title", value: "Song A" }, 62 + ], 63 + }; 64 + return match(track as never, item as never); 65 + }); 66 + expect(result).toBe(false); 67 + }); 68 + 69 + it("applies transformations before comparing", async () => { 70 + const result = await testWeb(async () => { 71 + const { match } = await import("~/common/playlist.js"); 72 + const track = { 73 + $type: "sh.diffuse.output.track", 74 + id: "t", 75 + uri: "http://x.com/t.mp3", 76 + tags: { artist: "Artist A" }, 77 + }; 78 + const item = { 79 + $type: "sh.diffuse.output.playlistItem", 80 + id: "i", 81 + playlist: "Favourites", 82 + criteria: [ 83 + { field: "tags.artist", value: "ARTIST A", transformations: ["toLowerCase"] }, 84 + ], 85 + }; 86 + return match(track as never, item as never); 87 + }); 88 + expect(result).toBe(true); 89 + }); 90 + 91 + it("does not match when transformations result in inequality", async () => { 92 + const result = await testWeb(async () => { 93 + const { match } = await import("~/common/playlist.js"); 94 + const track = { 95 + $type: "sh.diffuse.output.track", 96 + id: "t", 97 + uri: "http://x.com/t.mp3", 98 + tags: { artist: "Artist A" }, 99 + }; 100 + const item = { 101 + $type: "sh.diffuse.output.playlistItem", 102 + id: "i", 103 + playlist: "Favourites", 104 + criteria: [ 105 + { field: "tags.artist", value: "Artist B", transformations: ["toLowerCase"] }, 106 + ], 107 + }; 108 + return match(track as never, item as never); 109 + }); 110 + expect(result).toBe(false); 111 + }); 112 + }); 113 + 114 + describe("gather", () => { 115 + it("groups items by playlist name", async () => { 116 + const result = await testWeb(async () => { 117 + const { gather } = await import("~/common/playlist.js"); 118 + const items = [ 119 + { $type: "sh.diffuse.output.playlistItem", id: "1", playlist: "Rock", criteria: [], positionedAfter: "prev" }, 120 + { $type: "sh.diffuse.output.playlistItem", id: "2", playlist: "Pop", criteria: [], positionedAfter: "prev" }, 121 + { $type: "sh.diffuse.output.playlistItem", id: "3", playlist: "Rock", criteria: [], positionedAfter: "prev" }, 122 + ] as never[]; 123 + const map = gather(items); 124 + return { rockCount: map.get("Rock")!.items.length, popCount: map.get("Pop")!.items.length }; 125 + }); 126 + expect(result.rockCount).toBe(2); 127 + expect(result.popCount).toBe(1); 128 + }); 129 + 130 + it("marks playlist as unordered when all items lack positionedAfter", async () => { 131 + const result = await testWeb(async () => { 132 + const { gather } = await import("~/common/playlist.js"); 133 + const items = [ 134 + { $type: "sh.diffuse.output.playlistItem", id: "1", playlist: "Mix", criteria: [] }, 135 + { $type: "sh.diffuse.output.playlistItem", id: "2", playlist: "Mix", criteria: [] }, 136 + ] as never[]; 137 + const map = gather(items); 138 + return map.get("Mix")!.unordered; 139 + }); 140 + expect(result).toBe(true); 141 + }); 142 + 143 + it("marks playlist as ordered when any item has positionedAfter", async () => { 144 + const result = await testWeb(async () => { 145 + const { gather } = await import("~/common/playlist.js"); 146 + const items = [ 147 + { $type: "sh.diffuse.output.playlistItem", id: "1", playlist: "Mix", criteria: [], positionedAfter: null }, 148 + { $type: "sh.diffuse.output.playlistItem", id: "2", playlist: "Mix", criteria: [], positionedAfter: "1" }, 149 + ] as never[]; 150 + const map = gather(items); 151 + return map.get("Mix")!.unordered; 152 + }); 153 + expect(result).toBe(false); 154 + }); 155 + 156 + it("preserves playlist name", async () => { 157 + const result = await testWeb(async () => { 158 + const { gather } = await import("~/common/playlist.js"); 159 + const items = [ 160 + { $type: "sh.diffuse.output.playlistItem", id: "1", playlist: "My Playlist", criteria: [] }, 161 + ] as never[]; 162 + const map = gather(items); 163 + return map.get("My Playlist")!.name; 164 + }); 165 + expect(result).toBe("My Playlist"); 166 + }); 167 + }); 168 + 169 + describe("sort", () => { 170 + it("returns single-item array unchanged", async () => { 171 + const result = await testWeb(async () => { 172 + const { sort } = await import("~/common/playlist.js"); 173 + const items = [ 174 + { $type: "sh.diffuse.output.playlistItem", id: "a", playlist: "p", criteria: [], positionedAfter: null }, 175 + ] as never[]; 176 + return sort(items).map((i: { id: string }) => i.id); 177 + }); 178 + expect(result).toEqual(["a"]); 179 + }); 180 + 181 + it("sorts a simple linked list in order", async () => { 182 + const result = await testWeb(async () => { 183 + const { sort } = await import("~/common/playlist.js"); 184 + const items = [ 185 + { $type: "sh.diffuse.output.playlistItem", id: "c", playlist: "p", criteria: [], positionedAfter: "b" }, 186 + { $type: "sh.diffuse.output.playlistItem", id: "a", playlist: "p", criteria: [], positionedAfter: null }, 187 + { $type: "sh.diffuse.output.playlistItem", id: "b", playlist: "p", criteria: [], positionedAfter: "a" }, 188 + ] as never[]; 189 + return sort(items).map((i: { id: string }) => i.id); 190 + }); 191 + expect(result).toEqual(["a", "b", "c"]); 192 + }); 193 + 194 + it("places head items (positionedAfter null) first", async () => { 195 + const result = await testWeb(async () => { 196 + const { sort } = await import("~/common/playlist.js"); 197 + const items = [ 198 + { $type: "sh.diffuse.output.playlistItem", id: "b", playlist: "p", criteria: [], positionedAfter: "a" }, 199 + { $type: "sh.diffuse.output.playlistItem", id: "a", playlist: "p", criteria: [], positionedAfter: null }, 200 + ] as never[]; 201 + return sort(items).map((i: { id: string }) => i.id); 202 + }); 203 + expect(result[0]).toBe("a"); 204 + }); 205 + 206 + it("appends unreachable items at the end", async () => { 207 + const result = await testWeb(async () => { 208 + const { sort } = await import("~/common/playlist.js"); 209 + const items = [ 210 + { $type: "sh.diffuse.output.playlistItem", id: "a", playlist: "p", criteria: [], positionedAfter: null }, 211 + { $type: "sh.diffuse.output.playlistItem", id: "b", playlist: "p", criteria: [], positionedAfter: "a" }, 212 + { $type: "sh.diffuse.output.playlistItem", id: "orphan", playlist: "p", criteria: [], positionedAfter: "missing" }, 213 + ] as never[]; 214 + const sorted = sort(items).map((i: { id: string }) => i.id); 215 + return sorted[sorted.length - 1]; 216 + }); 217 + expect(result).toBe("orphan"); 218 + }); 219 + 220 + it("sorts multiple heads by updatedAt ascending", async () => { 221 + const result = await testWeb(async () => { 222 + const { sort } = await import("~/common/playlist.js"); 223 + const items = [ 224 + { $type: "sh.diffuse.output.playlistItem", id: "b", playlist: "p", criteria: [], positionedAfter: null, updatedAt: "2024-06-01T00:00:00.000Z" }, 225 + { $type: "sh.diffuse.output.playlistItem", id: "a", playlist: "p", criteria: [], positionedAfter: null, updatedAt: "2024-01-01T00:00:00.000Z" }, 226 + ] as never[]; 227 + return sort(items).map((i: { id: string }) => i.id); 228 + }); 229 + expect(result[0]).toBe("a"); 230 + expect(result[1]).toBe("b"); 231 + }); 232 + }); 233 + 234 + describe("filterByPlaylist", () => { 235 + it("returns tracks matching any playlist item criteria", async () => { 236 + const result = await testWeb(async () => { 237 + const { filterByPlaylist } = await import("~/common/playlist.js"); 238 + const tracks = [ 239 + { $type: "sh.diffuse.output.track", id: "a", uri: "http://x.com/a.mp3", tags: { artist: "A", title: "T1" } }, 240 + { $type: "sh.diffuse.output.track", id: "b", uri: "http://x.com/b.mp3", tags: { artist: "B", title: "T2" } }, 241 + ] as never[]; 242 + const items = [ 243 + { $type: "sh.diffuse.output.playlistItem", id: "i1", playlist: "p", criteria: [{ field: "tags.artist", value: "A" }] }, 244 + ] as never[]; 245 + return filterByPlaylist(tracks, items).map((t: { id: string }) => t.id); 246 + }); 247 + expect(result).toEqual(["a"]); 248 + }); 249 + 250 + it("returns empty array when no tracks match", async () => { 251 + const result = await testWeb(async () => { 252 + const { filterByPlaylist } = await import("~/common/playlist.js"); 253 + const tracks = [ 254 + { $type: "sh.diffuse.output.track", id: "a", uri: "http://x.com/a.mp3", tags: { artist: "A", title: "T1" } }, 255 + ] as never[]; 256 + const items = [ 257 + { $type: "sh.diffuse.output.playlistItem", id: "i1", playlist: "p", criteria: [{ field: "tags.artist", value: "Z" }] }, 258 + ] as never[]; 259 + return filterByPlaylist(tracks, items).length; 260 + }); 261 + expect(result).toBe(0); 262 + }); 263 + 264 + it("applies transformations when matching", async () => { 265 + const result = await testWeb(async () => { 266 + const { filterByPlaylist } = await import("~/common/playlist.js"); 267 + const tracks = [ 268 + { $type: "sh.diffuse.output.track", id: "a", uri: "http://x.com/a.mp3", tags: { artist: "Artist" } }, 269 + ] as never[]; 270 + const items = [ 271 + { 272 + $type: "sh.diffuse.output.playlistItem", 273 + id: "i1", 274 + playlist: "p", 275 + criteria: [{ field: "tags.artist", value: "ARTIST", transformations: ["toLowerCase"] }], 276 + }, 277 + ] as never[]; 278 + return filterByPlaylist(tracks, items).length; 279 + }); 280 + expect(result).toBe(1); 281 + }); 282 + 283 + it("returns all tracks when multiple criteria match different tracks", async () => { 284 + const result = await testWeb(async () => { 285 + const { filterByPlaylist } = await import("~/common/playlist.js"); 286 + const tracks = [ 287 + { $type: "sh.diffuse.output.track", id: "a", uri: "http://x.com/a.mp3", tags: { artist: "A" } }, 288 + { $type: "sh.diffuse.output.track", id: "b", uri: "http://x.com/b.mp3", tags: { artist: "B" } }, 289 + ] as never[]; 290 + const items = [ 291 + { $type: "sh.diffuse.output.playlistItem", id: "i1", playlist: "p", criteria: [{ field: "tags.artist", value: "A" }] }, 292 + { $type: "sh.diffuse.output.playlistItem", id: "i2", playlist: "p", criteria: [{ field: "tags.artist", value: "B" }] }, 293 + ] as never[]; 294 + return filterByPlaylist(tracks, items).length; 295 + }); 296 + expect(result).toBe(2); 297 + }); 298 + 299 + it("returns empty array for empty playlist items", async () => { 300 + const result = await testWeb(async () => { 301 + const { filterByPlaylist } = await import("~/common/playlist.js"); 302 + const tracks = [ 303 + { $type: "sh.diffuse.output.track", id: "a", uri: "http://x.com/a.mp3", tags: { artist: "A" } }, 304 + ] as never[]; 305 + return filterByPlaylist(tracks, []).length; 306 + }); 307 + expect(result).toBe(0); 308 + }); 309 + }); 310 + });
+156
tests/common/signal/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("common/signal", () => { 7 + describe("signal", () => { 8 + it("get returns the initial value", async () => { 9 + const result = await testWeb(async () => { 10 + const { signal } = await import("~/common/signal.js"); 11 + const s = signal(42); 12 + return s.get(); 13 + }); 14 + expect(result).toBe(42); 15 + }); 16 + 17 + it("value getter returns the initial value", async () => { 18 + const result = await testWeb(async () => { 19 + const { signal } = await import("~/common/signal.js"); 20 + const s = signal("hello"); 21 + return s.value; 22 + }); 23 + expect(result).toBe("hello"); 24 + }); 25 + 26 + it("set updates the value", async () => { 27 + const result = await testWeb(async () => { 28 + const { signal } = await import("~/common/signal.js"); 29 + const s = signal(1); 30 + s.set(99); 31 + return s.get(); 32 + }); 33 + expect(result).toBe(99); 34 + }); 35 + 36 + it("value setter updates the value", async () => { 37 + const result = await testWeb(async () => { 38 + const { signal } = await import("~/common/signal.js"); 39 + const s = signal("a"); 40 + s.value = "b"; 41 + return s.value; 42 + }); 43 + expect(result).toBe("b"); 44 + }); 45 + 46 + it("with compare option skips update when values are equal", async () => { 47 + const result = await testWeb(async () => { 48 + const { signal, effect } = await import("~/common/signal.js"); 49 + let runCount = 0; 50 + const s = signal({ x: 1 }, { compare: (a, b) => a.x === b.x }); 51 + 52 + effect(() => { 53 + s.get(); 54 + runCount++; 55 + }); 56 + 57 + const before = runCount; 58 + s.set({ x: 1 }); // same by compare 59 + return { before, after: runCount }; 60 + }); 61 + expect(result.before).toBe(1); 62 + expect(result.after).toBe(1); // effect not re-run 63 + }); 64 + 65 + it("with compare option triggers update when values differ", async () => { 66 + const result = await testWeb(async () => { 67 + const { signal, effect } = await import("~/common/signal.js"); 68 + let runCount = 0; 69 + const s = signal({ x: 1 }, { compare: (a, b) => a.x === b.x }); 70 + 71 + effect(() => { 72 + s.get(); 73 + runCount++; 74 + }); 75 + 76 + const before = runCount; 77 + s.set({ x: 2 }); // different by compare 78 + return { before, after: runCount }; 79 + }); 80 + expect(result.before).toBe(1); 81 + expect(result.after).toBe(2); 82 + }); 83 + }); 84 + 85 + describe("batch", () => { 86 + it("defers effect execution until batch completes", async () => { 87 + const result = await testWeb(async () => { 88 + const { signal, effect, batch } = await import("~/common/signal.js"); 89 + const a = signal(0); 90 + const b = signal(0); 91 + const values: number[] = []; 92 + 93 + effect(() => { 94 + values.push(a.get() + b.get()); 95 + }); 96 + 97 + const before = [...values]; // [0] 98 + batch(() => { 99 + a.set(1); 100 + b.set(2); 101 + }); 102 + 103 + return { before, after: values }; 104 + }); 105 + expect(result.before).toEqual([0]); 106 + expect(result.after).toEqual([0, 3]); // only one update, not two 107 + }); 108 + }); 109 + 110 + describe("untracked", () => { 111 + it("reads signal value without tracking it as a dependency", async () => { 112 + const result = await testWeb(async () => { 113 + const { signal, effect, untracked } = await import( 114 + "~/common/signal.js" 115 + ); 116 + const a = signal(1); 117 + const b = signal(10); 118 + let runCount = 0; 119 + 120 + effect(() => { 121 + a.get(); // tracked 122 + untracked(() => b.get()); // not tracked 123 + runCount++; 124 + }); 125 + 126 + const before = runCount; // 1 127 + b.set(20); // should NOT re-run effect 128 + const afterB = runCount; 129 + a.set(2); // SHOULD re-run effect 130 + return { before, afterB, afterA: runCount }; 131 + }); 132 + expect(result.before).toBe(1); 133 + expect(result.afterB).toBe(1); 134 + expect(result.afterA).toBe(2); 135 + }); 136 + 137 + it("returns the value from the callback", async () => { 138 + const result = await testWeb(async () => { 139 + const { signal, untracked } = await import("~/common/signal.js"); 140 + const s = signal(42); 141 + return untracked(() => s.get()); 142 + }); 143 + expect(result).toBe(42); 144 + }); 145 + }); 146 + 147 + describe("untrackedAsync", () => { 148 + it("returns the resolved value", async () => { 149 + const result = await testWeb(async () => { 150 + const { untrackedAsync } = await import("~/common/signal.js"); 151 + return untrackedAsync(async () => 99); 152 + }); 153 + expect(result).toBe(99); 154 + }); 155 + }); 156 + });
+52
tests/common/temporal/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("common/temporal", () => { 7 + describe("compareTimestamps", () => { 8 + it("returns 0 for equal timestamps", async () => { 9 + const result = await testWeb(async () => { 10 + const { compareTimestamps } = await import("~/common/temporal.js"); 11 + return compareTimestamps( 12 + "2024-01-01T00:00:00.000Z", 13 + "2024-01-01T00:00:00.000Z", 14 + ); 15 + }); 16 + expect(result).toBe(0); 17 + }); 18 + 19 + it("returns negative when first is earlier", async () => { 20 + const result = await testWeb(async () => { 21 + const { compareTimestamps } = await import("~/common/temporal.js"); 22 + return compareTimestamps( 23 + "2024-01-01T00:00:00.000Z", 24 + "2024-06-01T00:00:00.000Z", 25 + ); 26 + }); 27 + expect(result).toBeLessThan(0); 28 + }); 29 + 30 + it("returns positive when first is later", async () => { 31 + const result = await testWeb(async () => { 32 + const { compareTimestamps } = await import("~/common/temporal.js"); 33 + return compareTimestamps( 34 + "2024-06-01T00:00:00.000Z", 35 + "2024-01-01T00:00:00.000Z", 36 + ); 37 + }); 38 + expect(result).toBeGreaterThan(0); 39 + }); 40 + 41 + it("handles millisecond precision", async () => { 42 + const result = await testWeb(async () => { 43 + const { compareTimestamps } = await import("~/common/temporal.js"); 44 + return compareTimestamps( 45 + "2024-01-01T00:00:00.001Z", 46 + "2024-01-01T00:00:00.000Z", 47 + ); 48 + }); 49 + expect(result).toBeGreaterThan(0); 50 + }); 51 + }); 52 + });
+76
tests/common/track/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("common/track", () => { 7 + describe("trackURIBase", () => { 8 + it("strips path from URI", async () => { 9 + const result = await testWeb(async () => { 10 + const { trackURIBase } = await import("~/common/track.js"); 11 + return trackURIBase("https://example.com/music/track.mp3"); 12 + }); 13 + expect(result).toBe("https://example.com"); 14 + }); 15 + 16 + it("strips query string from URI", async () => { 17 + const result = await testWeb(async () => { 18 + const { trackURIBase } = await import("~/common/track.js"); 19 + return trackURIBase("https://example.com/track.mp3?token=abc"); 20 + }); 21 + expect(result).toBe("https://example.com"); 22 + }); 23 + 24 + it("handles URIs with no path", async () => { 25 + const result = await testWeb(async () => { 26 + const { trackURIBase } = await import("~/common/track.js"); 27 + return trackURIBase("https://example.com"); 28 + }); 29 + expect(result).toBe("https://example.com"); 30 + }); 31 + 32 + it("preserves scheme and host", async () => { 33 + const result = await testWeb(async () => { 34 + const { trackURIBase } = await import("~/common/track.js"); 35 + return trackURIBase("s3://my-bucket/path/to/track.flac"); 36 + }); 37 + expect(result).toBe("s3://my-bucket"); 38 + }); 39 + }); 40 + 41 + describe("uniqueTrackURIs", () => { 42 + it("returns a set of base URIs", async () => { 43 + const result = await testWeb(async () => { 44 + const { uniqueTrackURIs } = await import("~/common/track.js"); 45 + const tracks = [ 46 + { $type: "sh.diffuse.output.track", id: "1", uri: "https://example.com/a.mp3" }, 47 + { $type: "sh.diffuse.output.track", id: "2", uri: "https://example.com/b.mp3" }, 48 + ] as never[]; 49 + const set = uniqueTrackURIs(tracks); 50 + return [...set]; 51 + }); 52 + expect(result).toEqual(["https://example.com"]); 53 + }); 54 + 55 + it("deduplicates tracks from the same source", async () => { 56 + const result = await testWeb(async () => { 57 + const { uniqueTrackURIs } = await import("~/common/track.js"); 58 + const tracks = [ 59 + { $type: "sh.diffuse.output.track", id: "1", uri: "https://a.com/1.mp3" }, 60 + { $type: "sh.diffuse.output.track", id: "2", uri: "https://a.com/2.mp3" }, 61 + { $type: "sh.diffuse.output.track", id: "3", uri: "https://b.com/3.mp3" }, 62 + ] as never[]; 63 + return uniqueTrackURIs(tracks).size; 64 + }); 65 + expect(result).toBe(2); 66 + }); 67 + 68 + it("returns empty set for empty input", async () => { 69 + const result = await testWeb(async () => { 70 + const { uniqueTrackURIs } = await import("~/common/track.js"); 71 + return uniqueTrackURIs([]).size; 72 + }); 73 + expect(result).toBe(0); 74 + }); 75 + }); 76 + });
+313
tests/common/utils/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("common/utils", () => { 7 + describe("arrayShuffle", () => { 8 + it("returns empty array for empty input", async () => { 9 + const result = await testWeb(async () => { 10 + const { arrayShuffle } = await import("~/common/utils.js"); 11 + return arrayShuffle([]); 12 + }); 13 + expect(result).toEqual([]); 14 + }); 15 + 16 + it("returns array with same elements", async () => { 17 + const result = await testWeb(async () => { 18 + const { arrayShuffle } = await import("~/common/utils.js"); 19 + return arrayShuffle([1, 2, 3, 4, 5]).sort((a, b) => a - b); 20 + }); 21 + expect(result).toEqual([1, 2, 3, 4, 5]); 22 + }); 23 + 24 + it("does not mutate the original array", async () => { 25 + const result = await testWeb(async () => { 26 + const { arrayShuffle } = await import("~/common/utils.js"); 27 + const original = [1, 2, 3]; 28 + arrayShuffle(original); 29 + return original; 30 + }); 31 + expect(result).toEqual([1, 2, 3]); 32 + }); 33 + 34 + it("returns a single-element array unchanged", async () => { 35 + const result = await testWeb(async () => { 36 + const { arrayShuffle } = await import("~/common/utils.js"); 37 + return arrayShuffle([42]); 38 + }); 39 + expect(result).toEqual([42]); 40 + }); 41 + }); 42 + 43 + describe("boolAttr", () => { 44 + it("returns true for empty string (present attribute)", async () => { 45 + const result = await testWeb(async () => { 46 + const { boolAttr } = await import("~/common/utils.js"); 47 + return boolAttr(""); 48 + }); 49 + expect(result).toBe(true); 50 + }); 51 + 52 + it("returns false for null", async () => { 53 + const result = await testWeb(async () => { 54 + const { boolAttr } = await import("~/common/utils.js"); 55 + return boolAttr(null); 56 + }); 57 + expect(result).toBe(false); 58 + }); 59 + 60 + it("returns false for undefined", async () => { 61 + const result = await testWeb(async () => { 62 + const { boolAttr } = await import("~/common/utils.js"); 63 + return boolAttr(undefined); 64 + }); 65 + expect(result).toBe(false); 66 + }); 67 + 68 + it("returns false for non-empty string", async () => { 69 + const result = await testWeb(async () => { 70 + const { boolAttr } = await import("~/common/utils.js"); 71 + return boolAttr("true"); 72 + }); 73 + expect(result).toBe(false); 74 + }); 75 + }); 76 + 77 + describe("groupTracksPerScheme", () => { 78 + it("groups tracks by URI scheme", async () => { 79 + const result = await testWeb(async () => { 80 + const { groupTracksPerScheme } = await import("~/common/utils.js"); 81 + const tracks = [ 82 + { $type: "sh.diffuse.output.track", id: "1", uri: "http://example.com/a.mp3" }, 83 + { $type: "sh.diffuse.output.track", id: "2", uri: "s3://bucket/b.mp3" }, 84 + { $type: "sh.diffuse.output.track", id: "3", uri: "http://example.com/c.mp3" }, 85 + ] as never[]; 86 + const groups = groupTracksPerScheme(tracks); 87 + return { httpCount: groups["http"].length, s3Count: groups["s3"].length }; 88 + }); 89 + expect(result.httpCount).toBe(2); 90 + expect(result.s3Count).toBe(1); 91 + }); 92 + 93 + it("merges into provided initial object", async () => { 94 + const result = await testWeb(async () => { 95 + const { groupTracksPerScheme } = await import("~/common/utils.js"); 96 + const initial = { http: [{ $type: "sh.diffuse.output.track", id: "0", uri: "http://existing.com/x.mp3" }] } as never; 97 + const tracks = [{ $type: "sh.diffuse.output.track", id: "1", uri: "http://example.com/a.mp3" }] as never[]; 98 + const groups = groupTracksPerScheme(tracks, initial); 99 + return groups["http"].length; 100 + }); 101 + expect(result).toBe(2); 102 + }); 103 + }); 104 + 105 + describe("groupUrisPerScheme", () => { 106 + it("groups URIs by scheme", async () => { 107 + const result = await testWeb(async () => { 108 + const { groupUrisPerScheme } = await import("~/common/utils.js"); 109 + const groups = groupUrisPerScheme([ 110 + "http://a.com/track.mp3", 111 + "s3://bucket/track.flac", 112 + "http://b.com/track.mp3", 113 + ]); 114 + return { httpCount: groups["http"].length, s3Count: groups["s3"].length }; 115 + }); 116 + expect(result.httpCount).toBe(2); 117 + expect(result.s3Count).toBe(1); 118 + }); 119 + 120 + it("returns empty object for empty input", async () => { 121 + const result = await testWeb(async () => { 122 + const { groupUrisPerScheme } = await import("~/common/utils.js"); 123 + return Object.keys(groupUrisPerScheme([])).length; 124 + }); 125 + expect(result).toBe(0); 126 + }); 127 + }); 128 + 129 + describe("isPrimitive", () => { 130 + it("returns true for number", async () => { 131 + const result = await testWeb(async () => { 132 + const { isPrimitive } = await import("~/common/utils.js"); 133 + return isPrimitive(42); 134 + }); 135 + expect(result).toBe(true); 136 + }); 137 + 138 + it("returns true for string", async () => { 139 + const result = await testWeb(async () => { 140 + const { isPrimitive } = await import("~/common/utils.js"); 141 + return isPrimitive("hello"); 142 + }); 143 + expect(result).toBe(true); 144 + }); 145 + 146 + it("returns true for boolean", async () => { 147 + const result = await testWeb(async () => { 148 + const { isPrimitive } = await import("~/common/utils.js"); 149 + return isPrimitive(true); 150 + }); 151 + expect(result).toBe(true); 152 + }); 153 + 154 + it("returns true for null", async () => { 155 + const result = await testWeb(async () => { 156 + const { isPrimitive } = await import("~/common/utils.js"); 157 + return isPrimitive(null); 158 + }); 159 + expect(result).toBe(true); 160 + }); 161 + 162 + it("returns false for object", async () => { 163 + const result = await testWeb(async () => { 164 + const { isPrimitive } = await import("~/common/utils.js"); 165 + return isPrimitive({ a: 1 }); 166 + }); 167 + expect(result).toBe(false); 168 + }); 169 + 170 + it("returns false for array", async () => { 171 + const result = await testWeb(async () => { 172 + const { isPrimitive } = await import("~/common/utils.js"); 173 + return isPrimitive([1, 2]); 174 + }); 175 + expect(result).toBe(false); 176 + }); 177 + }); 178 + 179 + describe("jsonEncode / jsonDecode", () => { 180 + it("round-trips a plain object", async () => { 181 + const result = await testWeb(async () => { 182 + const { jsonEncode, jsonDecode } = await import("~/common/utils.js"); 183 + const original = { a: 1, b: "hello", c: [1, 2, 3] }; 184 + return jsonDecode(jsonEncode(original)); 185 + }); 186 + expect(result).toEqual({ a: 1, b: "hello", c: [1, 2, 3] }); 187 + }); 188 + 189 + it("jsonEncode returns a Uint8Array", async () => { 190 + const result = await testWeb(async () => { 191 + const { jsonEncode } = await import("~/common/utils.js"); 192 + return jsonEncode({ x: 1 }) instanceof Uint8Array; 193 + }); 194 + expect(result).toBe(true); 195 + }); 196 + }); 197 + 198 + describe("hash", () => { 199 + it("returns a string", async () => { 200 + const result = await testWeb(async () => { 201 + const { hash } = await import("~/common/utils.js"); 202 + return typeof hash({ a: 1 }); 203 + }); 204 + expect(result).toBe("string"); 205 + }); 206 + 207 + it("returns the same hash for equal objects", async () => { 208 + const result = await testWeb(async () => { 209 + const { hash } = await import("~/common/utils.js"); 210 + return hash({ a: 1, b: 2 }) === hash({ a: 1, b: 2 }); 211 + }); 212 + expect(result).toBe(true); 213 + }); 214 + 215 + it("returns different hashes for different objects", async () => { 216 + const result = await testWeb(async () => { 217 + const { hash } = await import("~/common/utils.js"); 218 + return hash({ a: 1 }) === hash({ a: 2 }); 219 + }); 220 + expect(result).toBe(false); 221 + }); 222 + }); 223 + 224 + describe("removeUndefinedValuesFromRecord", () => { 225 + it("removes keys with undefined values", async () => { 226 + const result = await testWeb(async () => { 227 + const { removeUndefinedValuesFromRecord } = await import( 228 + "~/common/utils.js" 229 + ); 230 + return removeUndefinedValuesFromRecord({ a: 1, b: undefined, c: "x" }); 231 + }); 232 + expect(result).toEqual({ a: 1, c: "x" }); 233 + }); 234 + 235 + it("does not mutate the original record", async () => { 236 + const result = await testWeb(async () => { 237 + const { removeUndefinedValuesFromRecord } = await import( 238 + "~/common/utils.js" 239 + ); 240 + const original = { a: 1, b: undefined }; 241 + removeUndefinedValuesFromRecord(original); 242 + return "b" in original; 243 + }); 244 + expect(result).toBe(true); 245 + }); 246 + 247 + it("returns record unchanged when no undefined values", async () => { 248 + const result = await testWeb(async () => { 249 + const { removeUndefinedValuesFromRecord } = await import( 250 + "~/common/utils.js" 251 + ); 252 + return removeUndefinedValuesFromRecord({ a: 1, b: 2 }); 253 + }); 254 + expect(result).toEqual({ a: 1, b: 2 }); 255 + }); 256 + }); 257 + 258 + describe("recursivelyCloneRecords", () => { 259 + it("shallow-clones a flat record", async () => { 260 + const result = await testWeb(async () => { 261 + const { recursivelyCloneRecords } = await import("~/common/utils.js"); 262 + const original = { a: 1, b: "x" }; 263 + const clone = recursivelyCloneRecords(original); 264 + return clone !== original && clone.a === 1 && clone.b === "x"; 265 + }); 266 + expect(result).toBe(true); 267 + }); 268 + 269 + it("deep-clones nested objects", async () => { 270 + const result = await testWeb(async () => { 271 + const { recursivelyCloneRecords } = await import("~/common/utils.js"); 272 + const original = { outer: { inner: 42 } }; 273 + const clone = recursivelyCloneRecords(original); 274 + return clone.outer !== original.outer && clone.outer.inner === 42; 275 + }); 276 + expect(result).toBe(true); 277 + }); 278 + }); 279 + 280 + describe("safeDecodeURIComponent", () => { 281 + it("decodes standard percent-encoded characters", async () => { 282 + const result = await testWeb(async () => { 283 + const { safeDecodeURIComponent } = await import("~/common/utils.js"); 284 + return safeDecodeURIComponent("hello%20world"); 285 + }); 286 + expect(result).toBe("hello world"); 287 + }); 288 + 289 + it("decodes %u unicode escapes", async () => { 290 + const result = await testWeb(async () => { 291 + const { safeDecodeURIComponent } = await import("~/common/utils.js"); 292 + return safeDecodeURIComponent("%u0041"); // 'A' 293 + }); 294 + expect(result).toBe("A"); 295 + }); 296 + 297 + it("leaves plain strings unchanged", async () => { 298 + const result = await testWeb(async () => { 299 + const { safeDecodeURIComponent } = await import("~/common/utils.js"); 300 + return safeDecodeURIComponent("plain-string"); 301 + }); 302 + expect(result).toBe("plain-string"); 303 + }); 304 + 305 + it("decodes mixed encoded and plain text", async () => { 306 + const result = await testWeb(async () => { 307 + const { safeDecodeURIComponent } = await import("~/common/utils.js"); 308 + return safeDecodeURIComponent("hello%2Fworld"); 309 + }); 310 + expect(result).toBe("hello/world"); 311 + }); 312 + }); 313 + });
+100
tests/facets/data/artwork-bundle/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("facets/data/artwork-bundle", () => { 7 + it("inputArtwork prepends an input artwork element", async () => { 8 + const result = await testWeb(async () => { 9 + const { inputArtwork } = await import( 10 + "~/facets/data/artwork-bundle/index.inline.js" 11 + ); 12 + const artwork = document.createElement("div"); 13 + const input = { selector: "#test-input" }; 14 + inputArtwork(artwork as never, input as never); 15 + return artwork.children[0]?.localName ?? null; 16 + }); 17 + expect(result).toBe("da-input"); 18 + }); 19 + 20 + it("inputArtwork sets input-selector attribute", async () => { 21 + const result = await testWeb(async () => { 22 + const { inputArtwork } = await import( 23 + "~/facets/data/artwork-bundle/index.inline.js" 24 + ); 25 + const artwork = document.createElement("div"); 26 + const input = { selector: "#my-input" }; 27 + inputArtwork(artwork as never, input as never); 28 + return artwork.children[0]?.getAttribute("input-selector") ?? null; 29 + }); 30 + expect(result).toBe("#my-input"); 31 + }); 32 + 33 + it("audioMetadata appends an audio-metadata element", async () => { 34 + const result = await testWeb(async () => { 35 + const { audioMetadata } = await import( 36 + "~/facets/data/artwork-bundle/index.inline.js" 37 + ); 38 + const artwork = document.createElement("div"); 39 + const input = { selector: "#test-input" }; 40 + audioMetadata(artwork as never, input as never); 41 + return artwork.children[0]?.localName ?? null; 42 + }); 43 + expect(result).toBe("da-audio-metadata"); 44 + }); 45 + 46 + it("audioMetadata sets input-selector attribute", async () => { 47 + const result = await testWeb(async () => { 48 + const { audioMetadata } = await import( 49 + "~/facets/data/artwork-bundle/index.inline.js" 50 + ); 51 + const artwork = document.createElement("div"); 52 + const input = { selector: "#my-input" }; 53 + audioMetadata(artwork as never, input as never); 54 + return artwork.children[0]?.getAttribute("input-selector") ?? null; 55 + }); 56 + expect(result).toBe("#my-input"); 57 + }); 58 + 59 + it("lastFm appends a Last.fm artwork element", async () => { 60 + const result = await testWeb(async () => { 61 + const { lastFm } = await import( 62 + "~/facets/data/artwork-bundle/index.inline.js" 63 + ); 64 + const artwork = document.createElement("div"); 65 + lastFm(artwork as never); 66 + return artwork.children[0]?.localName ?? null; 67 + }); 68 + expect(result).toBe("da-lastfm"); 69 + }); 70 + 71 + it("musicBrainz appends a MusicBrainz artwork element", async () => { 72 + const result = await testWeb(async () => { 73 + const { musicBrainz } = await import( 74 + "~/facets/data/artwork-bundle/index.inline.js" 75 + ); 76 + const artwork = document.createElement("div"); 77 + musicBrainz(artwork as never); 78 + return artwork.children[0]?.localName ?? null; 79 + }); 80 + expect(result).toBe("da-musicbrainz"); 81 + }); 82 + 83 + it("inputArtwork is prepended before audioMetadata", async () => { 84 + const result = await testWeb(async () => { 85 + const { inputArtwork, audioMetadata } = await import( 86 + "~/facets/data/artwork-bundle/index.inline.js" 87 + ); 88 + const artwork = document.createElement("div"); 89 + const input = { selector: "#test-input" }; 90 + 91 + // audioMetadata appended first, then inputArtwork prepended 92 + audioMetadata(artwork as never, input as never); 93 + inputArtwork(artwork as never, input as never); 94 + 95 + return Array.from(artwork.children).map((el) => el.localName); 96 + }); 97 + expect(result[0]).toBe("da-input"); 98 + expect(result[1]).toBe("da-audio-metadata"); 99 + }); 100 + });
+97
tests/facets/data/input-bundle/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("facets/data/input-bundle", () => { 7 + it("ephemeralCache appends an ephemeral-cache element", async () => { 8 + const result = await testWeb(async () => { 9 + const { ephemeralCache } = await import( 10 + "~/facets/data/input-bundle/index.inline.js" 11 + ); 12 + const container = document.createElement("div"); 13 + ephemeralCache(container as never); 14 + return container.children[0]?.localName ?? null; 15 + }); 16 + expect(result).toBe("di-ephemeral-cache"); 17 + }); 18 + 19 + it("https appends an https element", async () => { 20 + const result = await testWeb(async () => { 21 + const { https } = await import( 22 + "~/facets/data/input-bundle/index.inline.js" 23 + ); 24 + const container = document.createElement("div"); 25 + https(container as never); 26 + return container.children[0]?.localName ?? null; 27 + }); 28 + expect(result).toBe("di-https"); 29 + }); 30 + 31 + it("icecast appends an icecast element", async () => { 32 + const result = await testWeb(async () => { 33 + const { icecast } = await import( 34 + "~/facets/data/input-bundle/index.inline.js" 35 + ); 36 + const container = document.createElement("div"); 37 + icecast(container as never); 38 + return container.children[0]?.localName ?? null; 39 + }); 40 + expect(result).toBe("di-icecast"); 41 + }); 42 + 43 + it("local appends a local element", async () => { 44 + const result = await testWeb(async () => { 45 + const { local } = await import( 46 + "~/facets/data/input-bundle/index.inline.js" 47 + ); 48 + const container = document.createElement("div"); 49 + local(container as never); 50 + return container.children[0]?.localName ?? null; 51 + }); 52 + expect(result).toBe("di-local"); 53 + }); 54 + 55 + it("opensubsonic appends an opensubsonic element", async () => { 56 + const result = await testWeb(async () => { 57 + const { opensubsonic } = await import( 58 + "~/facets/data/input-bundle/index.inline.js" 59 + ); 60 + const container = document.createElement("div"); 61 + opensubsonic(container as never); 62 + return container.children[0]?.localName ?? null; 63 + }); 64 + expect(result).toBe("di-opensubsonic"); 65 + }); 66 + 67 + it("s3 appends an s3 element", async () => { 68 + const result = await testWeb(async () => { 69 + const { s3 } = await import( 70 + "~/facets/data/input-bundle/index.inline.js" 71 + ); 72 + const container = document.createElement("div"); 73 + s3(container as never); 74 + return container.children[0]?.localName ?? null; 75 + }); 76 + expect(result).toBe("di-s3"); 77 + }); 78 + 79 + it("each function appends exactly one element", async () => { 80 + const result = await testWeb(async () => { 81 + const { ephemeralCache, https, icecast, local, opensubsonic, s3 } = 82 + await import("~/facets/data/input-bundle/index.inline.js"); 83 + 84 + const counts: Record<string, number> = {}; 85 + for (const [name, fn] of Object.entries({ ephemeralCache, https, icecast, local, opensubsonic, s3 })) { 86 + const container = document.createElement("div"); 87 + fn(container as never); 88 + counts[name] = container.children.length; 89 + } 90 + return counts; 91 + }); 92 + 93 + for (const count of Object.values(result)) { 94 + expect(count).toBe(1); 95 + } 96 + }); 97 + });
+45
tests/facets/data/metadata-bundle/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("facets/data/metadata-bundle", () => { 7 + it("audioFile appends an audio-file element to metadata", async () => { 8 + const result = await testWeb(async () => { 9 + const { audioFile } = await import( 10 + "~/facets/data/metadata-bundle/index.inline.js" 11 + ); 12 + const metadata = document.createElement("div"); 13 + const input = { selector: "#test-input" }; 14 + audioFile(metadata as never, input as never); 15 + return metadata.children[0]?.localName ?? null; 16 + }); 17 + expect(result).toBe("dm-audio-file"); 18 + }); 19 + 20 + it("audioFile sets input-selector attribute from input.selector", async () => { 21 + const result = await testWeb(async () => { 22 + const { audioFile } = await import( 23 + "~/facets/data/metadata-bundle/index.inline.js" 24 + ); 25 + const metadata = document.createElement("div"); 26 + const input = { selector: "#my-input-configurator" }; 27 + audioFile(metadata as never, input as never); 28 + return metadata.children[0]?.getAttribute("input-selector") ?? null; 29 + }); 30 + expect(result).toBe("#my-input-configurator"); 31 + }); 32 + 33 + it("audioFile appends exactly one element", async () => { 34 + const result = await testWeb(async () => { 35 + const { audioFile } = await import( 36 + "~/facets/data/metadata-bundle/index.inline.js" 37 + ); 38 + const metadata = document.createElement("div"); 39 + const input = { selector: "#test-input" }; 40 + audioFile(metadata as never, input as never); 41 + return metadata.children.length; 42 + }); 43 + expect(result).toBe(1); 44 + }); 45 + });
+158
tests/facets/data/output-bundle/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("facets/data/output-bundle", () => { 7 + it("atproto appends an atproto output element to output", async () => { 8 + const result = await testWeb(async () => { 9 + const { atproto } = await import( 10 + "~/facets/data/output-bundle/index.inline.js" 11 + ); 12 + 13 + const outputChildren: string[] = []; 14 + const configuratorChildren: string[] = []; 15 + const mockOutput = { 16 + append: (el: Element) => outputChildren.push(el.localName), 17 + outputConfigurator: { 18 + append: (el: Element) => configuratorChildren.push(el.localName), 19 + }, 20 + }; 21 + 22 + atproto(mockOutput as never); 23 + 24 + return { outputChildren, configuratorChildren }; 25 + }); 26 + 27 + expect(result.outputChildren).toContain("dor-atproto"); 28 + expect(result.outputChildren).toContain("dtor-atproto-sync"); 29 + }); 30 + 31 + it("atproto appends a passkey refiner to outputConfigurator", async () => { 32 + const result = await testWeb(async () => { 33 + const { atproto } = await import( 34 + "~/facets/data/output-bundle/index.inline.js" 35 + ); 36 + 37 + const configuratorChildren: string[] = []; 38 + const mockOutput = { 39 + append: (_el: Element) => {}, 40 + outputConfigurator: { 41 + append: (el: Element) => configuratorChildren.push(el.localName), 42 + }, 43 + }; 44 + 45 + atproto(mockOutput as never); 46 + 47 + return configuratorChildren; 48 + }); 49 + 50 + expect(result).toContain("dtor-track-uri-passkey"); 51 + }); 52 + 53 + it("atproto sets correct ids and attributes on its elements", async () => { 54 + const result = await testWeb(async () => { 55 + const { atproto } = await import( 56 + "~/facets/data/output-bundle/index.inline.js" 57 + ); 58 + 59 + const appended: { id: string; localName: string }[] = []; 60 + const configuratorAppended: { id: string; localName: string }[] = []; 61 + 62 + const mockOutput = { 63 + append: (el: Element) => 64 + appended.push({ id: el.id, localName: el.localName }), 65 + outputConfigurator: { 66 + append: (el: Element) => 67 + configuratorAppended.push({ id: el.id, localName: el.localName }), 68 + }, 69 + }; 70 + 71 + atproto(mockOutput as never); 72 + 73 + return { appended, configuratorAppended }; 74 + }); 75 + 76 + const atprotoOut = result.appended.find((e) => e.localName === "dor-atproto"); 77 + const atprotoSync = result.appended.find((e) => e.localName === "dtor-atproto-sync"); 78 + const passkey = result.configuratorAppended.find((e) => e.localName === "dtor-track-uri-passkey"); 79 + 80 + expect(atprotoOut?.id).toBe("do-output__dor-atproto"); 81 + expect(atprotoSync?.id).toBe("do-output__dtor-atproto-sync"); 82 + expect(passkey?.id).toBe("do-output__dc-output__atproto"); 83 + }); 84 + 85 + it("s3 appends an s3 output element to output", async () => { 86 + const result = await testWeb(async () => { 87 + const { s3 } = await import( 88 + "~/facets/data/output-bundle/index.inline.js" 89 + ); 90 + 91 + const outputChildren: string[] = []; 92 + const mockOutput = { 93 + append: (el: Element) => outputChildren.push(el.localName), 94 + outputConfigurator: { 95 + append: (_el: Element) => {}, 96 + }, 97 + }; 98 + 99 + s3(mockOutput as never); 100 + 101 + return outputChildren; 102 + }); 103 + 104 + expect(result).toContain("dob-s3"); 105 + }); 106 + 107 + it("s3 appends a dasl-sync transformer to outputConfigurator", async () => { 108 + const result = await testWeb(async () => { 109 + const { s3 } = await import( 110 + "~/facets/data/output-bundle/index.inline.js" 111 + ); 112 + 113 + const configuratorChildren: string[] = []; 114 + const mockOutput = { 115 + append: (_el: Element) => {}, 116 + outputConfigurator: { 117 + append: (el: Element) => configuratorChildren.push(el.localName), 118 + }, 119 + }; 120 + 121 + s3(mockOutput as never); 122 + 123 + return configuratorChildren; 124 + }); 125 + 126 + expect(result).toContain("dtob-dasl-sync"); 127 + }); 128 + 129 + it("s3 sets correct ids on its elements", async () => { 130 + const result = await testWeb(async () => { 131 + const { s3 } = await import( 132 + "~/facets/data/output-bundle/index.inline.js" 133 + ); 134 + 135 + const appended: { id: string; localName: string }[] = []; 136 + const configuratorAppended: { id: string; localName: string }[] = []; 137 + 138 + const mockOutput = { 139 + append: (el: Element) => 140 + appended.push({ id: el.id, localName: el.localName }), 141 + outputConfigurator: { 142 + append: (el: Element) => 143 + configuratorAppended.push({ id: el.id, localName: el.localName }), 144 + }, 145 + }; 146 + 147 + s3(mockOutput as never); 148 + 149 + return { appended, configuratorAppended }; 150 + }); 151 + 152 + const s3Out = result.appended.find((e) => e.localName === "dob-s3"); 153 + const s3Sync = result.configuratorAppended.find((e) => e.localName === "dtob-dasl-sync"); 154 + 155 + expect(s3Out?.id).toBe("do-output__dob-s3"); 156 + expect(s3Sync?.id).toBe("do-output__dc-output__s3"); 157 + }); 158 + });