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

+705
+131
tests/components/input/common/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/input/common", () => { 8 + it("isAudioFile returns truthy for audio extensions", async () => { 9 + const results = await testWeb(async () => { 10 + const mod = await import("~/components/input/common.js"); 11 + return [ 12 + !!mod.isAudioFile("track.mp3"), 13 + !!mod.isAudioFile("track.flac"), 14 + !!mod.isAudioFile("track.ogg"), 15 + !!mod.isAudioFile("track.opus"), 16 + !!mod.isAudioFile("track.wav"), 17 + !!mod.isAudioFile("track.m4a"), 18 + !!mod.isAudioFile("track.webm"), 19 + ]; 20 + }); 21 + 22 + expect(results).toEqual([true, true, true, true, true, true, true]); 23 + }); 24 + 25 + it("isAudioFile returns falsy for non-audio extensions", async () => { 26 + const results = await testWeb(async () => { 27 + const mod = await import("~/components/input/common.js"); 28 + return [ 29 + !!mod.isAudioFile("track.txt"), 30 + !!mod.isAudioFile("track.jpg"), 31 + !!mod.isAudioFile("track.pdf"), 32 + !!mod.isAudioFile("track"), 33 + ]; 34 + }); 35 + 36 + expect(results).toEqual([false, false, false, false]); 37 + }); 38 + 39 + it("groupKey returns scheme://groupId", async () => { 40 + const result = await testWeb(async () => { 41 + const mod = await import("~/components/input/common.js"); 42 + return mod.groupKey("https", "example.com"); 43 + }); 44 + 45 + expect(result).toBe("https://example.com"); 46 + }); 47 + 48 + it("detach with matching scheme removes all tracks", async () => { 49 + const result = await testWeb(async () => { 50 + const mod = await import("~/components/input/common.js"); 51 + 52 + const tracks: Track[] = [ 53 + { $type: "sh.diffuse.output.track", id: "1", uri: "https://a.com/1.mp3" }, 54 + { $type: "sh.diffuse.output.track", id: "2", uri: "https://b.com/2.mp3" }, 55 + ]; 56 + 57 + return mod.detach({ 58 + fileUriOrScheme: "https", 59 + inputScheme: "https", 60 + handleFileUri: () => [], 61 + tracks, 62 + }); 63 + }); 64 + 65 + expect(result).toEqual([]); 66 + }); 67 + 68 + it("detach with non-matching scheme returns all tracks unchanged", async () => { 69 + const result = await testWeb(async () => { 70 + const mod = await import("~/components/input/common.js"); 71 + 72 + const tracks: Track[] = [ 73 + { $type: "sh.diffuse.output.track", id: "1", uri: "https://a.com/1.mp3" }, 74 + { $type: "sh.diffuse.output.track", id: "2", uri: "https://b.com/2.mp3" }, 75 + ]; 76 + 77 + return mod.detach({ 78 + fileUriOrScheme: "ftp", 79 + inputScheme: "https", 80 + handleFileUri: () => [], 81 + tracks, 82 + }); 83 + }); 84 + 85 + expect(result.map((t: Track) => t.id)).toEqual(["1", "2"]); 86 + }); 87 + 88 + it("detach delegates to handleFileUri when a full URI is given", async () => { 89 + const result = await testWeb(async () => { 90 + const mod = await import("~/components/input/common.js"); 91 + 92 + const tracks: Track[] = [ 93 + { $type: "sh.diffuse.output.track", id: "1", uri: "https://a.com/1.mp3" }, 94 + { $type: "sh.diffuse.output.track", id: "2", uri: "https://b.com/2.mp3" }, 95 + ]; 96 + 97 + return mod.detach({ 98 + fileUriOrScheme: "https://a.com/1.mp3", 99 + inputScheme: "https", 100 + handleFileUri: ({ tracks }) => tracks.filter((t) => t.id !== "1"), 101 + tracks, 102 + }); 103 + }); 104 + 105 + expect(result.map((t: Track) => t.id)).toEqual(["2"]); 106 + }); 107 + 108 + it("cachedConsult caches and returns result", async () => { 109 + const result = await testWeb(async () => { 110 + const mod = await import("~/components/input/common.js"); 111 + 112 + let callCount = 0; 113 + const cached = mod.cachedConsult( 114 + async (_uri: string) => { 115 + callCount++; 116 + return true; 117 + }, 118 + (uri: string) => uri, 119 + ); 120 + 121 + const r1 = await cached("https://example.com/stream"); 122 + const r2 = await cached("https://example.com/stream"); 123 + 124 + return { r1, r2, callCount }; 125 + }); 126 + 127 + expect(result.r1).toBe(true); 128 + expect(result.r2).toBe(true); 129 + expect(result.callCount).toBe(1); 130 + }); 131 + });
+169
tests/components/input/icecast/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/input/icecast", () => { 8 + it("has correct SCHEME property", async () => { 9 + const scheme = await testWeb(async () => { 10 + const mod = await import("~/components/input/icecast/element.js"); 11 + const input = new mod.CLASS(); 12 + document.body.append(input); 13 + return input.SCHEME; 14 + }); 15 + 16 + expect(scheme).toBe("icecast"); 17 + }); 18 + 19 + it("consult returns undetermined for scheme only", async () => { 20 + const result = await testWeb(async () => { 21 + const mod = await import("~/components/input/icecast/element.js"); 22 + const input = new mod.CLASS(); 23 + document.body.append(input); 24 + return await input.consult("icecast"); 25 + }); 26 + 27 + expect(result.supported).toBe(true); 28 + if (result.supported) { 29 + expect(result.consult).toBe("undetermined"); 30 + } 31 + }); 32 + 33 + it("consult returns unsupported for a non-icecast URI", async () => { 34 + const result = await testWeb(async () => { 35 + const mod = await import("~/components/input/icecast/element.js"); 36 + const input = new mod.CLASS(); 37 + document.body.append(input); 38 + return await input.consult("https://example.com/stream.mp3"); 39 + }); 40 + 41 + expect(result.supported).toBe(false); 42 + }); 43 + 44 + it("resolve returns a URL containing the host and path for an icecast URI", async () => { 45 + const result = await testWeb(async () => { 46 + const mod = await import("~/components/input/icecast/element.js"); 47 + const input = new mod.CLASS(); 48 + document.body.append(input); 49 + return await input.resolve({ uri: "icecast://radio.example.com/stream.mp3" }); 50 + }); 51 + 52 + // Chrome's URL parser treats non-special schemes (icecast://) with empty 53 + // host, so the reconstructed streamUrl embeds the authority in the path. 54 + // Test the observable contract: the url contains the domain and path and 55 + // uses the https: protocol. 56 + expect(result).not.toBe(null); 57 + if (result && "url" in result) { 58 + expect(result.url).toContain("radio.example.com"); 59 + expect(result.url).toContain("stream.mp3"); 60 + expect(result.url).toContain("https:"); 61 + expect(result.expiresAt).toBeGreaterThan(Date.now() / 1000); 62 + } 63 + }); 64 + 65 + it("resolve uses http: for an icecast URI with tls=0", async () => { 66 + const result = await testWeb(async () => { 67 + const mod = await import("~/components/input/icecast/element.js"); 68 + const input = new mod.CLASS(); 69 + document.body.append(input); 70 + return await input.resolve({ 71 + uri: "icecast://radio.example.com:8000/live?tls=0", 72 + }); 73 + }); 74 + 75 + expect(result).not.toBe(null); 76 + if (result && "url" in result) { 77 + expect(result.url).toContain("radio.example.com"); 78 + expect(result.url).toContain("/live"); 79 + expect(result.url).toContain("http:"); 80 + } 81 + }); 82 + 83 + it("resolve returns undefined for a non-icecast URI", async () => { 84 + const result = await testWeb(async () => { 85 + const mod = await import("~/components/input/icecast/element.js"); 86 + const input = new mod.CLASS(); 87 + document.body.append(input); 88 + const r = await input.resolve({ uri: "https://example.com/stream.mp3" }); 89 + return r ?? null; 90 + }); 91 + 92 + expect(result).toBe(null); 93 + }); 94 + 95 + it("detach with icecast scheme removes all icecast tracks", async () => { 96 + const remaining = await testWeb(async () => { 97 + const mod = await import("~/components/input/icecast/element.js"); 98 + const input = new mod.CLASS(); 99 + document.body.append(input); 100 + 101 + const tracks: Track[] = [ 102 + { 103 + $type: "sh.diffuse.output.track", 104 + id: "1", 105 + uri: "icecast://radio.example.com/stream.mp3", 106 + }, 107 + { 108 + $type: "sh.diffuse.output.track", 109 + id: "2", 110 + uri: "icecast://other.example.com/live", 111 + }, 112 + ]; 113 + 114 + return await input.detach({ fileUriOrScheme: "icecast", tracks }); 115 + }); 116 + 117 + expect(remaining.length).toBe(0); 118 + }); 119 + 120 + it("detach with a non-icecast URI returns all tracks unchanged", async () => { 121 + const remaining = await testWeb(async () => { 122 + const mod = await import("~/components/input/icecast/element.js"); 123 + const input = new mod.CLASS(); 124 + document.body.append(input); 125 + 126 + const tracks: Track[] = [ 127 + { 128 + $type: "sh.diffuse.output.track", 129 + id: "1", 130 + uri: "icecast://radio.example.com/stream.mp3", 131 + }, 132 + { 133 + $type: "sh.diffuse.output.track", 134 + id: "2", 135 + uri: "icecast://other.example.com/live", 136 + }, 137 + ]; 138 + 139 + return await input.detach({ 140 + fileUriOrScheme: "https://example.com/something.mp3", 141 + tracks, 142 + }); 143 + }); 144 + 145 + // parseURI returns undefined for non-icecast URIs, so all tracks are kept 146 + expect(remaining.length).toBe(2); 147 + }); 148 + 149 + it("sources returns an entry with icecast:// URI for each track", async () => { 150 + const sources = await testWeb(async () => { 151 + const mod = await import("~/components/input/icecast/element.js"); 152 + const input = new mod.CLASS(); 153 + document.body.append(input); 154 + 155 + const tracks: Track[] = [ 156 + { 157 + $type: "sh.diffuse.output.track", 158 + id: "1", 159 + uri: "icecast://radio.example.com/stream.mp3", 160 + }, 161 + ]; 162 + 163 + return input.sources(tracks); 164 + }); 165 + 166 + expect(sources.length).toBeGreaterThan(0); 167 + expect(sources[0].uri).toContain("icecast://"); 168 + }); 169 + });
+141
tests/components/input/local/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/input/local", () => { 8 + it("has correct SCHEME property", async () => { 9 + const scheme = await testWeb(async () => { 10 + const mod = await import("~/components/input/local/element.js"); 11 + const input = new mod.CLASS(); 12 + document.body.append(input); 13 + return input.SCHEME; 14 + }); 15 + 16 + expect(scheme).toBe("local"); 17 + }); 18 + 19 + it("consult returns unsupported for an unknown TID URI", async () => { 20 + const result = await testWeb(async () => { 21 + const mod = await import("~/components/input/local/element.js"); 22 + const input = new mod.CLASS(); 23 + document.body.append(input); 24 + return await input.consult("local://unknown-tid-abc123/"); 25 + }); 26 + 27 + // Either no browser support or unknown handle — both return supported: false 28 + expect(result.supported).toBe(false); 29 + }); 30 + 31 + it("listCached returns empty array initially", async () => { 32 + const result = await testWeb(async () => { 33 + const mod = await import("~/components/input/local/element.js"); 34 + const input = new mod.CLASS(); 35 + document.body.append(input); 36 + return await input.list([]); 37 + }); 38 + 39 + expect(result).toEqual([]); 40 + }); 41 + 42 + it("detach with local scheme removes all local tracks", async () => { 43 + const remaining = await testWeb(async () => { 44 + const mod = await import("~/components/input/local/element.js"); 45 + const input = new mod.CLASS(); 46 + document.body.append(input); 47 + 48 + const tracks: Track[] = [ 49 + { 50 + $type: "sh.diffuse.output.track", 51 + id: "1", 52 + uri: "local://tid-aaa/track1.mp3", 53 + }, 54 + { 55 + $type: "sh.diffuse.output.track", 56 + id: "2", 57 + uri: "local://tid-bbb/track2.mp3", 58 + }, 59 + ]; 60 + 61 + return await input.detach({ fileUriOrScheme: "local", tracks }); 62 + }); 63 + 64 + expect(remaining.length).toBe(0); 65 + }); 66 + 67 + it("detach with non-matching scheme returns all tracks", async () => { 68 + const remaining = await testWeb(async () => { 69 + const mod = await import("~/components/input/local/element.js"); 70 + const input = new mod.CLASS(); 71 + document.body.append(input); 72 + 73 + const tracks: Track[] = [ 74 + { 75 + $type: "sh.diffuse.output.track", 76 + id: "1", 77 + uri: "local://tid-aaa/track1.mp3", 78 + }, 79 + ]; 80 + 81 + return await input.detach({ fileUriOrScheme: "https", tracks }); 82 + }); 83 + 84 + expect(remaining.length).toBe(1); 85 + }); 86 + 87 + it("detach with a non-local URI returns all tracks unchanged", async () => { 88 + const remaining = await testWeb(async () => { 89 + const mod = await import("~/components/input/local/element.js"); 90 + const input = new mod.CLASS(); 91 + document.body.append(input); 92 + 93 + const tracks: Track[] = [ 94 + { 95 + $type: "sh.diffuse.output.track", 96 + id: "1", 97 + uri: "local://tid-aaa/track1.mp3", 98 + }, 99 + { 100 + $type: "sh.diffuse.output.track", 101 + id: "2", 102 + uri: "local://tid-bbb/track2.mp3", 103 + }, 104 + ]; 105 + 106 + return await input.detach({ 107 + fileUriOrScheme: "icecast://some-host/stream", 108 + tracks, 109 + }); 110 + }); 111 + 112 + // fileUriOrScheme has "://" but scheme doesn't match "local" → tracks unchanged 113 + expect(remaining.length).toBe(2); 114 + }); 115 + 116 + it("resolve returns undefined for an unknown TID", async () => { 117 + const result = await testWeb(async () => { 118 + const mod = await import("~/components/input/local/element.js"); 119 + const input = new mod.CLASS(); 120 + document.body.append(input); 121 + const r = await input.resolve({ uri: "local://unknown-tid/track.mp3" }); 122 + return r ?? null; 123 + }); 124 + 125 + expect(result).toBe(null); 126 + }); 127 + 128 + it("groupConsult returns empty object for unknown TIDs", async () => { 129 + const result = await testWeb(async () => { 130 + const mod = await import("~/components/input/local/element.js"); 131 + const input = new mod.CLASS(); 132 + document.body.append(input); 133 + return await input.groupConsult([ 134 + "local://tid-unknown-1/track.mp3", 135 + "local://tid-unknown-2/song.flac", 136 + ]); 137 + }); 138 + 139 + expect(Object.keys(result).length).toBe(0); 140 + }); 141 + });
+121
tests/components/input/opensubsonic/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/input/opensubsonic", () => { 8 + it("has correct SCHEME property", async () => { 9 + const scheme = await testWeb(async () => { 10 + const mod = await import("~/components/input/opensubsonic/element.js"); 11 + const input = new mod.CLASS(); 12 + document.body.append(input); 13 + return input.SCHEME; 14 + }); 15 + 16 + expect(scheme).toBe("opensubsonic"); 17 + }); 18 + 19 + it("consult returns undetermined for scheme only", async () => { 20 + const result = await testWeb(async () => { 21 + const mod = await import("~/components/input/opensubsonic/element.js"); 22 + const input = new mod.CLASS(); 23 + document.body.append(input); 24 + return await input.consult("opensubsonic"); 25 + }); 26 + 27 + expect(result.supported).toBe(true); 28 + if (result.supported) { 29 + expect(result.consult).toBe("undetermined"); 30 + } 31 + }); 32 + 33 + it("consult returns undetermined for a full URI (defers to network check)", async () => { 34 + const result = await testWeb(async () => { 35 + const mod = await import("~/components/input/opensubsonic/element.js"); 36 + const input = new mod.CLASS(); 37 + document.body.append(input); 38 + return await input.consult( 39 + "opensubsonic://user:pass@localhost:4040?tls=0", 40 + ); 41 + }); 42 + 43 + // consultServerCached will fail to reach the server and return false 44 + expect(result.supported).toBe(true); 45 + }); 46 + 47 + it("detach with opensubsonic scheme removes all opensubsonic tracks", async () => { 48 + const remaining = await testWeb(async () => { 49 + const mod = await import("~/components/input/opensubsonic/element.js"); 50 + const input = new mod.CLASS(); 51 + document.body.append(input); 52 + 53 + const tracks: Track[] = [ 54 + { 55 + $type: "sh.diffuse.output.track", 56 + id: "1", 57 + uri: "opensubsonic://user:pass@subsonic.example.com?tls=t", 58 + }, 59 + { 60 + $type: "sh.diffuse.output.track", 61 + id: "2", 62 + uri: "opensubsonic://user:pass@other.example.com?tls=t", 63 + }, 64 + ]; 65 + 66 + return await input.detach({ fileUriOrScheme: "opensubsonic", tracks }); 67 + }); 68 + 69 + expect(remaining.length).toBe(0); 70 + }); 71 + 72 + it("detach with non-matching scheme returns all tracks", async () => { 73 + const remaining = await testWeb(async () => { 74 + const mod = await import("~/components/input/opensubsonic/element.js"); 75 + const input = new mod.CLASS(); 76 + document.body.append(input); 77 + 78 + const tracks: Track[] = [ 79 + { 80 + $type: "sh.diffuse.output.track", 81 + id: "1", 82 + uri: "opensubsonic://user:pass@subsonic.example.com?tls=t", 83 + }, 84 + ]; 85 + 86 + return await input.detach({ fileUriOrScheme: "https", tracks }); 87 + }); 88 + 89 + expect(remaining.length).toBe(1); 90 + }); 91 + 92 + it("detach with specific server URI removes only matching tracks", async () => { 93 + const remaining = await testWeb(async () => { 94 + const mod = await import("~/components/input/opensubsonic/element.js"); 95 + const input = new mod.CLASS(); 96 + document.body.append(input); 97 + 98 + const tracks: Track[] = [ 99 + { 100 + $type: "sh.diffuse.output.track", 101 + id: "1", 102 + uri: "opensubsonic://user:pass@server-a.example.com?tls=t", 103 + }, 104 + { 105 + $type: "sh.diffuse.output.track", 106 + id: "2", 107 + uri: "opensubsonic://user:pass@server-b.example.com?tls=t", 108 + }, 109 + ]; 110 + 111 + return await input.detach({ 112 + fileUriOrScheme: 113 + "opensubsonic://user:pass@server-a.example.com?tls=t", 114 + tracks, 115 + }); 116 + }); 117 + 118 + expect(remaining.length).toBe(1); 119 + expect(remaining[0].id).toBe("2"); 120 + }); 121 + });
+143
tests/components/input/s3/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/input/s3", () => { 8 + it("has correct SCHEME property", async () => { 9 + const scheme = await testWeb(async () => { 10 + const mod = await import("~/components/input/s3/element.js"); 11 + const input = new mod.CLASS(); 12 + document.body.append(input); 13 + return input.SCHEME; 14 + }); 15 + 16 + expect(scheme).toBe("s3"); 17 + }); 18 + 19 + it("consult returns undetermined for scheme only", async () => { 20 + const result = await testWeb(async () => { 21 + const mod = await import("~/components/input/s3/element.js"); 22 + const input = new mod.CLASS(); 23 + document.body.append(input); 24 + return await input.consult("s3"); 25 + }); 26 + 27 + expect(result.supported).toBe(true); 28 + if (result.supported) { 29 + expect(result.consult).toBe("undetermined"); 30 + } 31 + }); 32 + 33 + it("consult returns undetermined for an invalid S3 URI", async () => { 34 + const result = await testWeb(async () => { 35 + const mod = await import("~/components/input/s3/element.js"); 36 + const input = new mod.CLASS(); 37 + document.body.append(input); 38 + return await input.consult("s3://notvalid"); 39 + }); 40 + 41 + expect(result.supported).toBe(true); 42 + if (result.supported) { 43 + expect(result.consult).toBe("undetermined"); 44 + } 45 + }); 46 + 47 + it("detach with s3 scheme removes all s3 tracks", async () => { 48 + const remaining = await testWeb(async () => { 49 + const mod = await import("~/components/input/s3/element.js"); 50 + const input = new mod.CLASS(); 51 + document.body.append(input); 52 + 53 + const tracks: Track[] = [ 54 + { 55 + $type: "sh.diffuse.output.track", 56 + id: "1", 57 + uri: "s3://key:secret@s3.amazonaws.com/music/track1.mp3?bucketName=my-bucket&region=us-east-1", 58 + }, 59 + { 60 + $type: "sh.diffuse.output.track", 61 + id: "2", 62 + uri: "s3://key:secret@s3.amazonaws.com/music/track2.mp3?bucketName=my-bucket&region=us-east-1", 63 + }, 64 + ]; 65 + 66 + return await input.detach({ fileUriOrScheme: "s3", tracks }); 67 + }); 68 + 69 + expect(remaining.length).toBe(0); 70 + }); 71 + 72 + it("detach with non-matching scheme returns all tracks", async () => { 73 + const remaining = await testWeb(async () => { 74 + const mod = await import("~/components/input/s3/element.js"); 75 + const input = new mod.CLASS(); 76 + document.body.append(input); 77 + 78 + const tracks: Track[] = [ 79 + { 80 + $type: "sh.diffuse.output.track", 81 + id: "1", 82 + uri: "s3://key:secret@s3.amazonaws.com/music/track1.mp3?bucketName=my-bucket&region=us-east-1", 83 + }, 84 + ]; 85 + 86 + return await input.detach({ fileUriOrScheme: "https", tracks }); 87 + }); 88 + 89 + expect(remaining.length).toBe(1); 90 + }); 91 + 92 + it("detach with specific bucket URI removes only matching tracks", async () => { 93 + const remaining = await testWeb(async () => { 94 + const mod = await import("~/components/input/s3/element.js"); 95 + const input = new mod.CLASS(); 96 + document.body.append(input); 97 + 98 + // bucketId is keyed by accessKey:secretKey@host — use different hosts 99 + const tracks: Track[] = [ 100 + { 101 + $type: "sh.diffuse.output.track", 102 + id: "1", 103 + uri: "s3://key:secret@s3-host-a.amazonaws.com/track1.mp3?bucketName=bucket-a&region=us-east-1", 104 + }, 105 + { 106 + $type: "sh.diffuse.output.track", 107 + id: "2", 108 + uri: "s3://key:secret@s3-host-b.amazonaws.com/track2.mp3?bucketName=bucket-b&region=us-east-1", 109 + }, 110 + ]; 111 + 112 + return await input.detach({ 113 + fileUriOrScheme: 114 + "s3://key:secret@s3-host-a.amazonaws.com/?bucketName=bucket-a&region=us-east-1", 115 + tracks, 116 + }); 117 + }); 118 + 119 + expect(remaining.length).toBe(1); 120 + expect(remaining[0].id).toBe("2"); 121 + }); 122 + 123 + it("sources returns labels with bucket name and host", async () => { 124 + const sources = await testWeb(async () => { 125 + const mod = await import("~/components/input/s3/element.js"); 126 + const input = new mod.CLASS(); 127 + document.body.append(input); 128 + 129 + const tracks: Track[] = [ 130 + { 131 + $type: "sh.diffuse.output.track", 132 + id: "1", 133 + uri: "s3://key:secret@s3.amazonaws.com/music/track1.mp3?bucketName=my-bucket&region=us-east-1", 134 + }, 135 + ]; 136 + 137 + return input.sources(tracks); 138 + }); 139 + 140 + expect(sources.length).toBe(1); 141 + expect(sources[0].label).toContain("my-bucket"); 142 + }); 143 + });