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

Configure Feed

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

feat: implement settings output

+414 -6
+47 -1
src/components/configurator/output/element.js
··· 3 3 4 4 /** 5 5 * @import {DiffuseElement} from "~/common/element.js" 6 - * @import {Facet, PlaylistItem, Track} from "~/definitions/types.d.ts" 6 + * @import {Facet, PlaylistItem, Setting, Track} from "~/definitions/types.d.ts" 7 7 * @import {OutputManagerDeputy, OutputElement} from "~/components/output/types.d.ts" 8 8 * 9 9 * @import {OutputConfiguratorElement} from "./types.d.ts" ··· 94 94 this.#memory.playlistItems.value = newPlaylistItems; 95 95 }, 96 96 }, 97 + settings: { 98 + collection: computed(() => { 99 + const out = this.#selected.value; 100 + if (out) return out.settings.collection(); 101 + 102 + const def = this.#defaultOutput.value; 103 + if (def) return def.settings.collection(); 104 + if (this.hasDefault()) return { state: "loading" }; 105 + 106 + return this.#setupFinished.value 107 + ? { state: "loaded", data: this.#memory.settings.value } 108 + : { state: "loading" }; 109 + }), 110 + reload: () => { 111 + const def = this.#defaultOutput.value; 112 + if (def) def.settings.reload(); 113 + 114 + const out = this.#selected.value; 115 + if (out) return out.settings.reload(); 116 + 117 + return Promise.resolve(); 118 + }, 119 + save: async (newSettings) => { 120 + const out = this.#selected.value; 121 + if (out) return await out.settings.save(newSettings); 122 + 123 + const def = this.#defaultOutput.value; 124 + if (def) return await def.settings.save(newSettings); 125 + 126 + this.#memory.settings.value = newSettings; 127 + }, 128 + }, 97 129 tracks: { 98 130 collection: computed(() => { 99 131 const out = this.#selected.value; ··· 142 174 // Assign manager properties to class 143 175 this.facets = manager.facets; 144 176 this.playlistItems = manager.playlistItems; 177 + this.settings = manager.settings; 145 178 this.tracks = manager.tracks; 146 179 this.ready = manager.ready; 147 180 ··· 179 212 const out = this.#selected.value; 180 213 if (!out) return; 181 214 215 + const col = out.settings.collection(); 216 + if (col.state !== "loaded") return; 217 + 218 + const def = this.#defaultOutput.value; 219 + if (def) def.settings.save(col.data); 220 + else this.#memory.settings.set(col.data); 221 + }); 222 + 223 + this.effect(() => { 224 + const out = this.#selected.value; 225 + if (!out) return; 226 + 182 227 const col = out.tracks.collection(); 183 228 if (col.state !== "loaded") return; 184 229 ··· 199 244 #memory = { 200 245 facets: signal(/** @type {Facet[]} */ ([])), 201 246 playlistItems: signal(/** @type {PlaylistItem[]} */ ([])), 247 + settings: signal(/** @type {Setting[]} */ ([])), 202 248 tracks: signal(/** @type {Track[]} */ ([])), 203 249 }; 204 250
+4
src/components/orchestrator/output/element.js
··· 65 65 return this.output.playlistItems; 66 66 } 67 67 68 + get settings() { 69 + return this.output.settings; 70 + } 71 + 68 72 get tracks() { 69 73 return this.output.tracks; 70 74 }
+1
src/components/orchestrator/path-collections/element.js
··· 23 23 }); 24 24 25 25 this.facets = base.facets; 26 + this.settings = base.settings; 26 27 this.playlistItems = { 27 28 ...base.playlistItems, 28 29 collection: computed(() => {
+6
src/components/output/bytes/s3/element.js
··· 45 45 get: () => this.#get("playlistItems"), 46 46 put: (data) => this.#put("playlistItems", data), 47 47 }, 48 + settings: { 49 + empty: () => undefined, 50 + get: () => this.#get("settings"), 51 + put: (data) => this.#put("settings", data), 52 + }, 48 53 tracks: { 49 54 empty: () => undefined, 50 55 get: () => this.#get("tracks"), ··· 54 59 55 60 this.facets = this.#manager.facets; 56 61 this.playlistItems = this.#manager.playlistItems; 62 + this.settings = this.#manager.settings; 57 63 this.tracks = this.#manager.tracks; 58 64 } 59 65
+41 -2
src/components/output/common.js
··· 3 3 import { strictEquality } from "~/common/compare.js"; 4 4 5 5 /** 6 - * @import {Facet, PlaylistItem, Track} from "~/definitions/types.d.ts" 6 + * @import {Facet, PlaylistItem, Setting, Track} from "~/definitions/types.d.ts" 7 7 * @import {SignalWriter} from "~/common/signal.d.ts"; 8 8 * @import {OutputManager, OutputManagerProperties} from "./types.d.ts" 9 9 */ ··· 33 33 34 34 const ogFacetsSave = manager.facets.save.bind(this); 35 35 const ogPlaylistItemsSave = manager.playlistItems.save.bind(this); 36 + const ogSettingsSave = manager.settings.save.bind(this); 36 37 const ogTracksSave = manager.tracks.save.bind(this); 37 38 38 39 const actions = this.broadcast(this.identifier, { ··· 47 48 set: manager.signals.playlistItems.set, 48 49 }), 49 50 }, 51 + saveSettings: { 52 + strategy: "replicate", 53 + fn: fn({ save: ogSettingsSave, set: manager.signals.settings.set }), 54 + }, 50 55 saveTracks: { 51 56 strategy: "replicate", 52 57 fn: fn({ save: ogTracksSave, set: manager.signals.tracks.set }), ··· 56 61 if (actions) { 57 62 manager.facets.save = actions.saveFacets; 58 63 manager.playlistItems.save = actions.savePlaylistItems; 64 + manager.settings.save = actions.saveSettings; 59 65 manager.tracks.save = actions.saveTracks; 60 66 } 61 67 } ··· 67 73 * @returns {OutputManager<Encoding>} 68 74 */ 69 75 export function outputManager( 70 - { init, facets, playlistItems, tracks }, 76 + { init, facets, playlistItems, settings, tracks }, 71 77 ) { 72 78 const c = signal( 73 79 /** @type {Encoding extends null ? Facet[] : Encoding} */ (facets ··· 87 93 { compare: strictEquality }, 88 94 ); 89 95 96 + const s = signal( 97 + /** @type {Encoding extends null ? Setting[] : Encoding} */ (settings 98 + .empty()), 99 + ); 100 + const ss = signal( 101 + /** @type {"loading" | "loaded" | "sleeping"} */ ("sleeping"), 102 + { compare: strictEquality }, 103 + ); 104 + 90 105 const t = signal( 91 106 /** @type {Encoding extends null ? Track[] : Encoding} */ (tracks.empty()), 92 107 ); ··· 109 124 pls.value = "loaded"; 110 125 } 111 126 127 + async function loadSettings() { 128 + if (init && (await init()) === false) return; 129 + ss.value = "loading"; 130 + s.value = await settings.get(); 131 + ss.value = "loaded"; 132 + } 133 + 112 134 async function loadTracks() { 113 135 if (init && (await init()) === false) return; 114 136 ts.value = "loading"; ··· 149 171 await playlistItems.put(newPlaylistItems); 150 172 }, 151 173 }, 174 + settings: { 175 + collection: computed(() => { 176 + if (untracked(() => ss.value === "sleeping")) loadSettings(); 177 + return ss.value === "loaded" 178 + ? { state: "loaded", data: s.value } 179 + : { state: "loading" }; 180 + }), 181 + reload: loadSettings, 182 + save: async (newSettings) => { 183 + batch(() => { 184 + if (untracked(() => ss.value === "sleeping")) ss.value = "loaded"; 185 + s.value = newSettings; 186 + }); 187 + await settings.put(newSettings); 188 + }, 189 + }, 152 190 tracks: { 153 191 collection: computed(() => { 154 192 if (untracked(() => ts.value === "sleeping")) loadTracks(); ··· 168 206 signals: { 169 207 facets: c, 170 208 playlistItems: pl, 209 + settings: s, 171 210 tracks: t, 172 211 }, 173 212 };
+6
src/components/output/polymorphic/indexed-db/element.js
··· 37 37 get: () => this.#get("playlistItems"), 38 38 put: (data) => this.#put("playlistItems", data), 39 39 }, 40 + settings: { 41 + empty: () => undefined, 42 + get: () => this.#get("settings"), 43 + put: (data) => this.#put("settings", data), 44 + }, 40 45 tracks: { 41 46 empty: () => undefined, 42 47 get: () => this.#get("tracks"), ··· 46 51 47 52 this.facets = this.#manager.facets; 48 53 this.playlistItems = this.#manager.playlistItems; 54 + this.settings = this.#manager.settings; 49 55 this.tracks = this.#manager.tracks; 50 56 51 57 this.ready = () => true;
+10
src/components/output/raw/atproto/element.js
··· 35 35 const WATCHED_COLLECTIONS = new Set([ 36 36 "sh.diffuse.output.facet", 37 37 "sh.diffuse.output.playlistItem", 38 + "sh.diffuse.output.setting", 38 39 "sh.diffuse.output.trackBundle", 39 40 ]); 40 41 ··· 80 81 get: () => this.listRecords("sh.diffuse.output.playlistItem"), 81 82 put: (data) => this.putRecords("sh.diffuse.output.playlistItem", data), 82 83 }, 84 + settings: { 85 + empty: () => [], 86 + get: () => this.listRecords("sh.diffuse.output.setting"), 87 + put: (data) => this.putRecords("sh.diffuse.output.setting", data), 88 + }, 83 89 tracks: { 84 90 empty: () => [], 85 91 get: async () => { ··· 123 129 124 130 this.facets = this.#manager.facets; 125 131 this.playlistItems = this.#manager.playlistItems; 132 + this.settings = this.#manager.settings; 126 133 this.tracks = this.#manager.tracks; 127 134 } 128 135 ··· 341 348 if (touched.has("sh.diffuse.output.facet")) this.#manager.facets.reload(); 342 349 if (touched.has("sh.diffuse.output.playlistItem")) { 343 350 this.#manager.playlistItems.reload(); 351 + } 352 + if (touched.has("sh.diffuse.output.setting")) { 353 + this.#manager.settings.reload(); 344 354 } 345 355 if (touched.has("sh.diffuse.output.trackBundle")) { 346 356 this.#manager.tracks.reload();
+19
src/components/output/types.d.ts
··· 3 3 import type { 4 4 Facet, 5 5 PlaylistItem, 6 + Setting, 6 7 Track, 7 8 } from "~/definitions/types.d.ts"; 8 9 ··· 35 36 playlistItems: Encoding extends null ? PlaylistItem[] : Encoding, 36 37 ) => Promise<void>; 37 38 }; 39 + settings: { 40 + collection: SignalReader< 41 + | { state: "loading" } 42 + | { state: "loaded"; data: Encoding extends null ? Setting[] : Encoding } 43 + >; 44 + reload: () => Promise<void>; 45 + save: ( 46 + settings: Encoding extends null ? Setting[] : Encoding, 47 + ) => Promise<void>; 48 + }; 38 49 signals: { 39 50 facets: Signal<Encoding extends null ? Facet[] : Encoding>; 40 51 playlistItems: Signal<Encoding extends null ? PlaylistItem[] : Encoding>; 52 + settings: Signal<Encoding extends null ? Setting[] : Encoding>; 41 53 tracks: Signal<Encoding extends null ? Track[] : Encoding>; 42 54 }; 43 55 tracks: { ··· 64 76 get(): Promise<Encoding extends null ? PlaylistItem[] : Encoding>; 65 77 put( 66 78 playlistItems: Encoding extends null ? PlaylistItem[] : Encoding, 79 + ): Promise<void>; 80 + }; 81 + settings: { 82 + empty(): Encoding extends null ? Setting[] : Encoding; 83 + get(): Promise<Encoding extends null ? Setting[] : Encoding>; 84 + put( 85 + settings: Encoding extends null ? Setting[] : Encoding, 67 86 ): Promise<void>; 68 87 }; 69 88 tracks: {
+14
src/components/transformer/output/base.js
··· 70 70 await this.output.signal()?.playlistItems.save(newPlaylistItems); 71 71 }, 72 72 }, 73 + settings: { 74 + collection: computed(() => { 75 + return this.output.signal()?.settings?.collection() ?? 76 + { state: "loading" }; 77 + }), 78 + reload: () => { 79 + return this.output.signal()?.settings?.reload() ?? 80 + Promise.resolve(); 81 + }, 82 + save: async (newSettings) => { 83 + await this.output.whenDefined; 84 + await this.output.signal()?.settings.save(newSettings); 85 + }, 86 + }, 73 87 tracks: { 74 88 collection: computed(() => { 75 89 return this.output.signal()?.tracks?.collection() ??
+4 -1
src/components/transformer/output/bytes/automerge/constants.js
··· 2 2 import { base64 } from "iso-base/rfc4648"; 3 3 4 4 /** 5 - * @import { FacetsDocument, PlaylistItemsDocument, TracksDocument } from "./types.d.ts"; 5 + * @import { FacetsDocument, PlaylistItemsDocument, SettingsDocument, TracksDocument } from "./types.d.ts"; 6 6 */ 7 7 8 8 /** @type {Automerge.Doc<FacetsDocument>} */ ··· 18 18 "hW9Kg5IPZcsAeAEQIyp0LRYp0l9bpZKWJXTPlgGtUD/lrIatFjiIwoUdtJhh/sBQFIcpPppxduoIp1ArXwYBAgMCEwIjBkACVgIHFQwhAiMCNAFCAlYCgAECfwB/AX8Bf8eTqcwGfwB/B38KY29sbGVjdGlvbn8AfwEBfwJ/AH8AAA", 19 19 ), 20 20 ); 21 + 22 + /** @type {Automerge.Doc<SettingsDocument>} */ 23 + export const INITIAL_SETTINGS_DOCUMENT = Automerge.from({ collection: [] }); 21 24 22 25 /** @type {Automerge.Doc<TracksDocument>} */ 23 26 export const INITIAL_TRACKS_DOCUMENT = Automerge.load(
+23
src/components/transformer/output/bytes/automerge/element.js
··· 13 13 import { 14 14 INITIAL_FACETS_DOCUMENT, 15 15 INITIAL_PLAYLIST_ITEMS_DOCUMENT, 16 + INITIAL_SETTINGS_DOCUMENT, 16 17 INITIAL_TRACKS_DOCUMENT, 17 18 } from "./constants.js"; 18 19 ··· 103 104 INITIAL_PLAYLIST_ITEMS_DOCUMENT, 104 105 ); 105 106 107 + const settings = state( 108 + computed(() => local()?.settings?.collection() ?? { state: "loading" }), 109 + remote.settings.collection, 110 + INITIAL_SETTINGS_DOCUMENT, 111 + ); 112 + 106 113 const tracks = state( 107 114 computed(() => local()?.tracks?.collection() ?? { state: "loading" }), 108 115 remote.tracks.collection, ··· 124 131 computed(() => playlistItems().doc), 125 132 ); 126 133 134 + this.settings = automergeEntry( 135 + computed(() => local()?.settings), 136 + remote.settings, 137 + computed(() => settings().doc), 138 + ); 139 + 127 140 this.tracks = automergeEntry( 128 141 computed(() => local()?.tracks), 129 142 remote.tracks, ··· 154 167 const bytes = Automerge.save(s.doc); 155 168 if (l && s.local) l.playlistItems.save(bytes); 156 169 if (s.remote) remote.playlistItems.save(bytes); 170 + } 171 + }); 172 + 173 + this.effect(() => { 174 + if (!settings().remoteLoaded) return; 175 + const s = settings(); 176 + if (s.diverged) { 177 + const bytes = Automerge.save(s.doc); 178 + if (l && s.local) l.settings.save(bytes); 179 + if (s.remote) remote.settings.save(bytes); 157 180 } 158 181 }); 159 182
+2
src/components/transformer/output/bytes/automerge/types.d.ts
··· 1 1 import type { 2 2 Facet, 3 3 PlaylistItem, 4 + Setting, 4 5 Track, 5 6 } from "~/definitions/types.d.ts"; 6 7 7 8 export type FacetsDocument = { collection: Facet[] }; 8 9 export type PlaylistItemsDocument = { collection: PlaylistItem[] }; 10 + export type SettingsDocument = { collection: Setting[] }; 9 11 export type TracksDocument = { collection: Track[] };
+17
src/components/transformer/output/bytes/dasl-sync/element.js
··· 160 160 }, 161 161 ); 162 162 163 + const settings = state( 164 + "settings", 165 + computed(() => local()?.settings.collection() ?? { state: "loading" }), 166 + remote.settings.collection, 167 + { 168 + saveLocal: async (v) => local()?.settings.save(v), 169 + saveRemote: remote.settings.save, 170 + }, 171 + ); 172 + 163 173 const tracks = state( 164 174 "tracks", 165 175 computed(() => local()?.tracks.collection() ?? { state: "loading" }), ··· 183 193 remote.playlistItems, 184 194 remote.ready, 185 195 playlistItems, 196 + ); 197 + 198 + this.settings = this.managerProp( 199 + { save: async (v) => local()?.settings.save(v) }, 200 + remote.settings, 201 + remote.ready, 202 + settings, 186 203 ); 187 204 188 205 this.tracks = this.managerProp(
+18 -1
src/components/transformer/output/bytes/json/element.js
··· 3 3 4 4 /** 5 5 * @import { OutputManagerDeputy } from "~/components/output/types.d.ts" 6 - * @import { Facet, PlaylistItem, Track } from "~/definitions/types.d.ts" 6 + * @import { Facet, PlaylistItem, Setting, Track } from "~/definitions/types.d.ts" 7 7 */ 8 8 9 9 /** ··· 49 49 await base.playlistItems.save(bytes); 50 50 }, 51 51 }, 52 + settings: { 53 + ...base.settings, 54 + collection: computed(() => { 55 + const col = base.settings.collection(); 56 + if (col.state !== "loaded") return col; 57 + /** @type {Setting[]} */ 58 + const data = parseArray(col.data); 59 + return { state: "loaded", data }; 60 + }), 61 + save: async (newSettings) => { 62 + const json = JSON.stringify(newSettings); 63 + const encoder = new TextEncoder(); 64 + const bytes = encoder.encode(json); 65 + await base.settings.save(bytes); 66 + }, 67 + }, 52 68 tracks: { 53 69 ...base.tracks, 54 70 collection: computed(() => { ··· 73 89 // Assign manager properties to class 74 90 this.facets = manager.facets; 75 91 this.playlistItems = manager.playlistItems; 92 + this.settings = manager.settings; 76 93 this.tracks = manager.tracks; 77 94 this.ready = manager.ready; 78 95 }
+1
src/components/transformer/output/raw/atproto-sync/element.js
··· 15 15 const COLLECTIONS = /** @type {const} */ ([ 16 16 "facets", 17 17 "playlistItems", 18 + "settings", 18 19 "tracks", 19 20 ]); 20 21
+3
src/components/transformer/output/refiner/default/element.js
··· 100 100 }, 101 101 }, 102 102 103 + settings: base.settings, 104 + 103 105 // Other 104 106 ready: base.ready, 105 107 }; ··· 107 109 // Assign manager properties to class 108 110 this.facets = manager.facets; 109 111 this.playlistItems = manager.playlistItems; 112 + this.settings = manager.settings; 110 113 this.tracks = manager.tracks; 111 114 this.ready = manager.ready; 112 115 }
+2
src/components/transformer/output/refiner/initial-contents/element.js
··· 106 106 }, 107 107 108 108 playlistItems: base.playlistItems, 109 + settings: base.settings, 109 110 tracks: base.tracks, 110 111 ready: base.ready, 111 112 }; 112 113 113 114 this.facets = manager.facets; 114 115 this.playlistItems = manager.playlistItems; 116 + this.settings = manager.settings; 115 117 this.tracks = manager.tracks; 116 118 this.ready = manager.ready; 117 119 }
+1
src/components/transformer/output/refiner/track-uri-passkey/element.js
··· 45 45 46 46 this.facets = base.facets; 47 47 this.playlistItems = base.playlistItems; 48 + this.settings = base.settings; 48 49 this.ready = this.#keyReady.get; 49 50 50 51 // Tracks
+16
src/components/transformer/output/string/json/element.js
··· 46 46 await base.playlistItems.save(json); 47 47 }, 48 48 }, 49 + settings: { 50 + ...base.settings, 51 + collection: computed(() => { 52 + const col = base.settings.collection(); 53 + if (col.state !== "loaded") return col; 54 + return { 55 + state: "loaded", 56 + data: typeof col.data === "string" ? parseArray(col.data) : [], 57 + }; 58 + }), 59 + save: async (newSettings) => { 60 + const json = JSON.stringify(newSettings); 61 + await base.settings.save(json); 62 + }, 63 + }, 49 64 tracks: { 50 65 ...base.tracks, 51 66 collection: computed(() => { ··· 69 84 // Assign manager properties to class 70 85 this.facets = manager.facets; 71 86 this.playlistItems = manager.playlistItems; 87 + this.settings = manager.settings; 72 88 this.tracks = manager.tracks; 73 89 this.ready = manager.ready; 74 90 }
+1 -1
src/definitions/index.ts
··· 2 2 export * as ShDiffuseOutputFacet from "./types/sh/diffuse/output/facet.ts"; 3 3 export * as ShDiffuseOutputPlaylistItem from "./types/sh/diffuse/output/playlistItem.ts"; 4 4 export * as ShDiffuseOutputPlaylistItemBundle from "./types/sh/diffuse/output/playlistItemBundle.ts"; 5 - export * as ShDiffuseOutputTheme from "./types/sh/diffuse/output/theme.ts"; 5 + export * as ShDiffuseOutputSetting from "./types/sh/diffuse/output/setting.ts"; 6 6 export * as ShDiffuseOutputTrack from "./types/sh/diffuse/output/track.ts"; 7 7 export * as ShDiffuseOutputTrackBundle from "./types/sh/diffuse/output/trackBundle.ts";
+39
src/definitions/output/setting.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.diffuse.output.setting", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "record": { 8 + "type": "object", 9 + "required": ["id", "setting"], 10 + "properties": { 11 + "id": { "type": "string" }, 12 + "createdAt": { "type": "string", "format": "datetime" }, 13 + "updatedAt": { "type": "string", "format": "datetime" }, 14 + "setting": { 15 + "type": "union", 16 + "description": "The key and value of a setting", 17 + "refs": ["#sh_diffuse_input_disabled_uris", "#untyped_setting"] 18 + } 19 + } 20 + } 21 + }, 22 + "sh_diffuse_input_disabled_uris": { 23 + "type": "object", 24 + "required": ["key", "value"], 25 + "properties": { 26 + "key": { "type": "string", "const": "sh.diffuse.input.disabled.uris" }, 27 + "value": { "type": "array", "items": { "type": "string" } } 28 + } 29 + }, 30 + "untyped_setting": { 31 + "type": "object", 32 + "required": ["key", "value"], 33 + "properties": { 34 + "key": { "type": "string" }, 35 + "value": { "type": "unknown" } 36 + } 37 + } 38 + } 39 + }
+2
src/definitions/types.d.ts
··· 16 16 17 17 export type { Main as Theme } from "./types/sh/diffuse/output/theme.ts"; 18 18 19 + export type { Main as Setting } from "./types/sh/diffuse/output/setting.ts"; 20 + 19 21 export type { 20 22 Main as Track, 21 23 Stats as TrackStats,
+15
tests/components/output/polymorphic/indexed-db/test.ts
··· 110 110 expect(result.state).toBe("loaded"); 111 111 }); 112 112 113 + it("settings.collection returns loaded state after save", async () => { 114 + const result = await testWeb(async () => { 115 + const mod = await import( 116 + "~/components/output/polymorphic/indexed-db/element.js" 117 + ); 118 + const output = new mod.CLASS(); 119 + document.body.append(output); 120 + 121 + await output.settings.save([]); 122 + return output.settings.collection(); 123 + }); 124 + 125 + expect(result.state).toBe("loaded"); 126 + }); 127 + 113 128 it("playlistItems.collection returns loaded state after save", async () => { 114 129 const result = await testWeb(async () => { 115 130 const mod = await import(
+11
tests/components/output/raw/atproto/test.ts
··· 72 72 expect(result).toBe(true); 73 73 }); 74 74 75 + it("settings.collection is loading initially", async () => { 76 + const result = await testWeb(async () => { 77 + const mod = await import("~/components/output/raw/atproto/element.js"); 78 + const output = new mod.CLASS(); 79 + document.body.append(output); 80 + return output.settings.collection().state; 81 + }); 82 + 83 + expect(result).toBe("loading"); 84 + }); 85 + 75 86 it("tracks.collection is loading initially", async () => { 76 87 const result = await testWeb(async () => { 77 88 const mod = await import("~/components/output/raw/atproto/element.js");
+33
tests/components/transformer/output/bytes/json/test.ts
··· 102 102 expect(result).toEqual([{ id: "f1", name: "Favourites" }]); 103 103 }); 104 104 105 + it("settings save and collection round-trip", async () => { 106 + const result = await testWeb(async () => { 107 + const idbMod = await import( 108 + "~/components/output/polymorphic/indexed-db/element.js" 109 + ); 110 + const mod = await import( 111 + "~/components/transformer/output/bytes/json/element.js" 112 + ); 113 + 114 + const output = new idbMod.CLASS(); 115 + output.id = "test-idb-settings"; 116 + document.body.append(output); 117 + 118 + const t = new mod.CLASS(); 119 + t.setAttribute("output-selector", "#test-idb-settings"); 120 + document.body.append(t); 121 + 122 + await t.settings.save([ 123 + { 124 + $type: "sh.diffuse.output.setting", 125 + id: "s1", 126 + setting: { key: "sh.diffuse.input.disabled.uris", value: [] }, 127 + }, 128 + ]); 129 + 130 + const col = t.settings.collection(); 131 + if (col.state !== "loaded") return null; 132 + return (col.data as Array<{ id: string }>).map((s) => s.id); 133 + }); 134 + 135 + expect(result).toEqual(["s1"]); 136 + }); 137 + 105 138 it("saving empty array results in empty loaded collection", async () => { 106 139 const result = await testWeb(async () => { 107 140 const idbMod = await import(
+12
tests/components/transformer/output/raw/atproto-sync/test.ts
··· 52 52 53 53 expect(result).toBe("loading"); 54 54 }); 55 + 56 + it("settings.collection is loading when no local output is connected", async () => { 57 + const result = await testWeb(async () => { 58 + const mod = await import( 59 + "~/components/transformer/output/raw/atproto-sync/element.js" 60 + ); 61 + const t = new mod.CLASS() as any; 62 + return t.settings.collection().state; 63 + }); 64 + 65 + expect(result).toBe("loading"); 66 + }); 55 67 });
+33
tests/components/transformer/output/refiner/default/test.ts
··· 112 112 expect(result?.backingIds).toEqual(["regular"]); 113 113 }); 114 114 115 + it("settings.collection delegates to backing output unchanged", async () => { 116 + const result = await testWeb(async () => { 117 + const idbMod = await import( 118 + "~/components/output/polymorphic/indexed-db/element.js" 119 + ); 120 + const mod = await import( 121 + "~/components/transformer/output/refiner/default/element.js" 122 + ); 123 + 124 + const output = new idbMod.CLASS(); 125 + output.id = "test-idb-settings"; 126 + document.body.append(output); 127 + 128 + const t = new mod.CLASS(); 129 + t.setAttribute("output-selector", "#test-idb-settings"); 130 + document.body.append(t); 131 + 132 + await t.settings.save([ 133 + { 134 + $type: "sh.diffuse.output.setting", 135 + id: "s1", 136 + setting: { key: "sh.diffuse.input.disabled.uris", value: [] }, 137 + }, 138 + ]); 139 + 140 + const col = t.settings.collection(); 141 + if (col.state !== "loaded") return null; 142 + return (col.data as Array<{ id: string }>).map((s) => s.id); 143 + }); 144 + 145 + expect(result).toEqual(["s1"]); 146 + }); 147 + 115 148 it("ephemeral playlist items appear in transformer collection but not in backing output", async () => { 116 149 const result = await testWeb(async () => { 117 150 const idbMod = await import(
+33
tests/components/transformer/output/refiner/initial-contents/test.ts
··· 126 126 expect(result).toEqual(["my-facet"]); 127 127 }); 128 128 129 + it("settings delegates to backing output unchanged", async () => { 130 + const result = await testWeb(async () => { 131 + const idbMod = await import( 132 + "~/components/output/polymorphic/indexed-db/element.js" 133 + ); 134 + const mod = await import( 135 + "~/components/transformer/output/refiner/initial-contents/element.js" 136 + ); 137 + 138 + const output = new idbMod.CLASS(); 139 + output.id = "test-idb-settings"; 140 + document.body.append(output); 141 + 142 + const t = new mod.CLASS(); 143 + t.setAttribute("output-selector", "#test-idb-settings"); 144 + document.body.append(t); 145 + 146 + await t.settings.save([ 147 + { 148 + $type: "sh.diffuse.output.setting", 149 + id: "s1", 150 + setting: { key: "sh.diffuse.input.disabled.uris", value: [] }, 151 + }, 152 + ]); 153 + 154 + const col = t.settings.collection(); 155 + if (col.state !== "loaded") return null; 156 + return (col.data as Array<{ id: string }>).map((s) => s.id); 157 + }); 158 + 159 + expect(result).toEqual(["s1"]); 160 + }); 161 + 129 162 it("tracks and playlistItems delegate to backing output unchanged", async () => { 130 163 const result = await testWeb(async () => { 131 164 const idbMod = await import(