Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/improsocial/impro
An extensible Bluesky client for web impro.social
Something went wrong. Try again.
JavaScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865import { describe, it } from "node:test";import assert from "node:assert/strict";import { Declarative } from "/js/dataLayer/declarative.js";
const sig = (getter) => ({ get: getter });// Stubs a ComputedMap: get(key) returns the derived value directly.const mapSig = (getter) => ({ get: (key) => getter(key) });
function createMockDerived(data = {}) { return { $currentUser: sig(() => data.currentUser ?? null), $hydratedDetailedProfiles: mapSig((did) => data.profiles?.[did] ?? null), $hydratedProfiles: mapSig((did) => data.basicProfiles?.[did] ?? null), $knownFollowers: mapSig((did) => data.knownFollowers?.[did] ?? null), $hydratedPostThreads: mapSig((uri) => data.postThreads?.[uri] ?? null), $hydratedPosts: mapSig((uri) => data.posts?.[uri] ?? null), $feedGenerators: mapSig((uri) => data.feedGenerators?.[uri] ?? null), $hydratedPinnedItems: sig(() => data.pinnedItems ?? null), $convoList: sig(() => data.convoList ?? null), $convos: mapSig((id) => data.convos?.[id] ?? null), $convoForProfile: mapSig((did) => data.convoForProfile?.[did] ?? null), };}
function createMockRequests(loadResults = {}) { return { loadCurrentUser: async () => loadResults.currentUser, loadDetailedProfile: async (did) => loadResults.profiles?.[did], loadDetailedProfiles: async () => {}, loadKnownFollowers: async () => {}, loadPostThread: async (uri) => loadResults.postThreads?.[uri], loadPost: async (uri) => loadResults.posts?.[uri], loadPosts: async () => {}, loadFeedGenerator: async (uri) => loadResults.feedGenerators?.[uri], loadPinnedItems: async () => loadResults.pinnedItems, loadConvoList: async () => loadResults.convoList, loadConvo: async (id) => loadResults.convos?.[id], loadConvoForProfile: async (did) => loadResults.convoForProfile?.[did], };}
describe("ensureCurrentUser", () => { it("should return existing current user without loading", async () => { const currentUser = { did: "did:test:user", handle: "test.user" }; let loadCalled = false;
const derived = createMockDerived({ currentUser }); const requests = { loadCurrentUser: async () => { loadCalled = true; }, };
const declarative = new Declarative(derived, requests); const result = await declarative.ensureCurrentUser();
assert.deepEqual(result, currentUser); assert.deepEqual(loadCalled, false); });
it("should load current user when not in cache", async () => { const currentUser = { did: "did:test:user", handle: "test.user" }; let callCount = 0;
const derived = { $currentUser: sig(() => { callCount++; return callCount > 1 ? currentUser : null; }), }; const requests = { loadCurrentUser: async () => {}, };
const declarative = new Declarative(derived, requests); const result = await declarative.ensureCurrentUser();
assert.deepEqual(result, currentUser); assert.deepEqual(callCount, 2); });
it("should throw when user not found after loading", async () => { const derived = createMockDerived({}); const requests = createMockRequests({});
const declarative = new Declarative(derived, requests);
let error = null; try { await declarative.ensureCurrentUser(); } catch (e) { error = e; }
assert(error !== null); assert.deepEqual(error.message, "Current user not found"); });});
describe("ensureProfile", () => { it("returns cached detailed profile without loading", async () => { const profileDid = "did:test:profile"; const profile = { did: profileDid, handle: "test.profile" }; let loadCalled = false;
const derived = createMockDerived({ profiles: { [profileDid]: profile } }); const requests = { loadDetailedProfile: async () => { loadCalled = true; }, };
const declarative = new Declarative(derived, requests); const result = await declarative.ensureProfile(profileDid);
assert.deepEqual(result, profile); assert.deepEqual(loadCalled, false); });
it("returns cached basic profile without loading when no detailed profile exists", async () => { const profileDid = "did:test:profile"; const basicProfile = { did: profileDid, handle: "test.profile" }; let loadCalled = false;
const derived = createMockDerived({ basicProfiles: { [profileDid]: basicProfile }, }); const requests = { loadDetailedProfile: async () => { loadCalled = true; }, };
const declarative = new Declarative(derived, requests); const result = await declarative.ensureProfile(profileDid);
assert.deepEqual(result, basicProfile); assert.deepEqual(loadCalled, false); });
it("prefers detailed profile over basic profile when both are cached", async () => { const profileDid = "did:test:profile"; const detailed = { did: profileDid, handle: "test.profile", detailed: true, }; const basic = { did: profileDid, handle: "test.profile" };
const derived = createMockDerived({ profiles: { [profileDid]: detailed }, basicProfiles: { [profileDid]: basic }, }); const requests = { loadDetailedProfile: async () => {} };
const declarative = new Declarative(derived, requests); const result = await declarative.ensureProfile(profileDid);
assert.deepEqual(result, detailed); });
it("loads detailed profile when neither cache has it", async () => { const profileDid = "did:test:profile"; const profile = { did: profileDid, handle: "test.profile" }; let callCount = 0; let loadCalled = false;
const derived = { $hydratedDetailedProfiles: mapSig(() => { callCount++; return callCount > 1 ? profile : null; }), $hydratedProfiles: mapSig(() => null), }; const requests = { loadDetailedProfile: async () => { loadCalled = true; }, };
const declarative = new Declarative(derived, requests); const result = await declarative.ensureProfile(profileDid);
assert.deepEqual(result, profile); assert.deepEqual(loadCalled, true); });
it("throws when profile not found after loading", async () => { const derived = createMockDerived({}); const requests = createMockRequests({});
const declarative = new Declarative(derived, requests);
let error = null; try { await declarative.ensureProfile("did:nonexistent"); } catch (e) { error = e; }
assert(error !== null); assert.deepEqual(error.message, "Profile not found"); });});
describe("ensureDetailedProfile", () => { it("should return existing profile without loading", async () => { const profileDid = "did:test:profile"; const profile = { did: profileDid, handle: "test.profile" }; let loadCalled = false;
const derived = createMockDerived({ profiles: { [profileDid]: profile } }); const requests = { loadDetailedProfile: async () => { loadCalled = true; }, };
const declarative = new Declarative(derived, requests); const result = await declarative.ensureDetailedProfile(profileDid);
assert.deepEqual(result, profile); assert.deepEqual(loadCalled, false); });
it("should load profile when not in cache", async () => { const profileDid = "did:test:profile"; const profile = { did: profileDid, handle: "test.profile" }; let callCount = 0;
const derived = { $hydratedDetailedProfiles: mapSig(() => { callCount++; return callCount > 1 ? profile : null; }), }; const requests = { loadDetailedProfile: async () => {}, };
const declarative = new Declarative(derived, requests); const result = await declarative.ensureDetailedProfile(profileDid);
assert.deepEqual(result, profile); });
it("should throw when profile not found after loading", async () => { const derived = createMockDerived({}); const requests = createMockRequests({});
const declarative = new Declarative(derived, requests);
let error = null; try { await declarative.ensureDetailedProfile("did:nonexistent"); } catch (e) { error = e; }
assert(error !== null); assert.deepEqual(error.message, "Profile not found"); });});
describe("ensureKnownFollowers", () => { it("should return existing known followers without loading", async () => { const profileDid = "did:test:profile"; const knownFollowers = { followers: [{ did: "did:test:follower" }] }; let loadCalled = false;
const derived = createMockDerived({ knownFollowers: { [profileDid]: knownFollowers }, }); const requests = { loadKnownFollowers: async () => { loadCalled = true; }, };
const declarative = new Declarative(derived, requests); const result = await declarative.ensureKnownFollowers(profileDid);
assert.deepEqual(result, knownFollowers); assert.deepEqual(loadCalled, false); });
it("should load known followers when not in cache", async () => { const profileDid = "did:test:profile"; const knownFollowers = { followers: [{ did: "did:test:follower" }] }; let callCount = 0;
const derived = { $knownFollowers: mapSig(() => { callCount++; return callCount > 1 ? knownFollowers : null; }), }; const requests = { loadKnownFollowers: async () => {}, };
const declarative = new Declarative(derived, requests); const result = await declarative.ensureKnownFollowers(profileDid);
assert.deepEqual(result, knownFollowers); });
it("should throw when known followers not found after loading", async () => { const derived = createMockDerived({}); const requests = createMockRequests({});
const declarative = new Declarative(derived, requests);
let error = null; try { await declarative.ensureKnownFollowers("did:nonexistent"); } catch (e) { error = e; }
assert(error !== null); assert.deepEqual(error.message, "Known followers not found"); });});
describe("ensureDetailedProfiles", () => { it("returns cached profiles in input order without loading", async () => { const profileA = { did: "did:test:a", handle: "a.test" }; const profileB = { did: "did:test:b", handle: "b.test" }; let loadCalled = false;
const derived = createMockDerived({ profiles: { [profileA.did]: profileA, [profileB.did]: profileB }, }); const requests = { loadDetailedProfiles: async () => { loadCalled = true; }, };
const declarative = new Declarative(derived, requests); const result = await declarative.ensureDetailedProfiles([ profileB.did, profileA.did, ]);
assert.deepEqual(result, [profileB, profileA]); assert.deepEqual(loadCalled, false); });
it("loads only missing profiles", async () => { const profileA = { did: "did:test:a", handle: "a.test" }; const profileB = { did: "did:test:b", handle: "b.test" }; const store = { [profileA.did]: profileA }; let loadedWith = null;
const derived = { $hydratedDetailedProfiles: mapSig((did) => store[did] ?? null), }; const requests = { loadDetailedProfiles: async (dids) => { loadedWith = dids; store[profileB.did] = profileB; }, };
const declarative = new Declarative(derived, requests); const result = await declarative.ensureDetailedProfiles([ profileA.did, profileB.did, ]);
assert.deepEqual(loadedWith, [profileB.did]); assert.deepEqual(result, [profileA, profileB]); });
it("returns null entries for profiles still missing after load", async () => { const derived = { $hydratedDetailedProfiles: mapSig(() => null) }; const requests = { loadDetailedProfiles: async () => {} };
const declarative = new Declarative(derived, requests); const result = await declarative.ensureDetailedProfiles([ "did:test:missing", ]);
assert.deepEqual(result, [null]); });});
describe("ensurePostThread", () => { it("should return existing post thread without loading", async () => { const postURI = "at://did:test/app.bsky.feed.post/123"; const postThread = { post: { uri: postURI }, replies: [] }; let loadCalled = false;
const derived = createMockDerived({ postThreads: { [postURI]: postThread }, }); const requests = { loadPostThread: async () => { loadCalled = true; }, };
const declarative = new Declarative(derived, requests); const result = await declarative.ensurePostThread(postURI);
assert.deepEqual(result, postThread); assert.deepEqual(loadCalled, false); });
it("should load post thread when not in cache", async () => { const postURI = "at://did:test/app.bsky.feed.post/123"; const postThread = { post: { uri: postURI }, replies: [] }; let callCount = 0;
const derived = { $hydratedPostThreads: mapSig(() => { callCount++; return callCount > 1 ? postThread : null; }), }; const requests = { loadPostThread: async () => {}, };
const declarative = new Declarative(derived, requests); const result = await declarative.ensurePostThread(postURI);
assert.deepEqual(result, postThread); });
it("should throw when post thread not found after loading", async () => { const derived = createMockDerived({}); const requests = createMockRequests({});
const declarative = new Declarative(derived, requests);
let error = null; try { await declarative.ensurePostThread("at://nonexistent"); } catch (e) { error = e; }
assert(error !== null); assert.deepEqual(error.message, "Post thread not found"); });});
describe("ensurePost", () => { it("should return existing post without loading", async () => { const postURI = "at://did:test/app.bsky.feed.post/123"; const post = { uri: postURI, text: "Hello" }; let loadCalled = false;
const derived = createMockDerived({ posts: { [postURI]: post } }); const requests = { loadPost: async () => { loadCalled = true; }, };
const declarative = new Declarative(derived, requests); const result = await declarative.ensurePost(postURI);
assert.deepEqual(result, post); assert.deepEqual(loadCalled, false); });
it("should load post when not in cache", async () => { const postURI = "at://did:test/app.bsky.feed.post/123"; const post = { uri: postURI, text: "Hello" }; let callCount = 0;
const derived = { $hydratedPosts: mapSig(() => { callCount++; return callCount > 1 ? post : null; }), }; const requests = { loadPost: async () => {}, };
const declarative = new Declarative(derived, requests); const result = await declarative.ensurePost(postURI);
assert.deepEqual(result, post); });
it("should throw when post not found after loading", async () => { const derived = createMockDerived({}); const requests = createMockRequests({});
const declarative = new Declarative(derived, requests);
let error = null; try { await declarative.ensurePost("at://nonexistent"); } catch (e) { error = e; }
assert(error !== null); assert.deepEqual(error.message, "Post not found"); });});
describe("ensurePosts", () => { it("returns cached posts in input order without loading", async () => { const postA = { uri: "at://a", text: "A" }; const postB = { uri: "at://b", text: "B" }; let loadCalled = false;
const derived = createMockDerived({ posts: { [postA.uri]: postA, [postB.uri]: postB }, }); const requests = { loadPosts: async () => { loadCalled = true; }, };
const declarative = new Declarative(derived, requests); const result = await declarative.ensurePosts([postB.uri, postA.uri]);
assert.deepEqual(result, [postB, postA]); assert.deepEqual(loadCalled, false); });
it("loads only missing posts", async () => { const postA = { uri: "at://a", text: "A" }; const postB = { uri: "at://b", text: "B" }; const store = { [postA.uri]: postA }; let loadedWith = null;
const derived = { $hydratedPosts: mapSig((uri) => store[uri] ?? null), }; const requests = { loadPosts: async (uris) => { loadedWith = uris; store[postB.uri] = postB; }, };
const declarative = new Declarative(derived, requests); const result = await declarative.ensurePosts([postA.uri, postB.uri]);
assert.deepEqual(loadedWith, [postB.uri]); assert.deepEqual(result, [postA, postB]); });
it("returns null entries for posts still missing after load", async () => { const derived = { $hydratedPosts: mapSig(() => null), }; const requests = { loadPosts: async () => {} };
const declarative = new Declarative(derived, requests); const result = await declarative.ensurePosts(["at://missing"]);
assert.deepEqual(result, [null]); });});
describe("ensureFeedGenerator", () => { it("should return existing feed generator without loading", async () => { const feedUri = "at://did:test/app.bsky.feed.generator/test"; const feedGenerator = { uri: feedUri, displayName: "Test Feed" }; let loadCalled = false;
const derived = createMockDerived({ feedGenerators: { [feedUri]: feedGenerator }, }); const requests = { loadFeedGenerator: async () => { loadCalled = true; }, };
const declarative = new Declarative(derived, requests); const result = await declarative.ensureFeedGenerator(feedUri);
assert.deepEqual(result, feedGenerator); assert.deepEqual(loadCalled, false); });
it("should load feed generator when not in cache", async () => { const feedUri = "at://did:test/app.bsky.feed.generator/test"; const feedGenerator = { uri: feedUri, displayName: "Test Feed" }; let callCount = 0;
const derived = { $feedGenerators: mapSig(() => { callCount++; return callCount > 1 ? feedGenerator : null; }), }; const requests = { loadFeedGenerator: async () => {}, };
const declarative = new Declarative(derived, requests); const result = await declarative.ensureFeedGenerator(feedUri);
assert.deepEqual(result, feedGenerator); });
it("should throw when feed generator not found after loading", async () => { const derived = createMockDerived({}); const requests = createMockRequests({});
const declarative = new Declarative(derived, requests);
let error = null; try { await declarative.ensureFeedGenerator("at://nonexistent"); } catch (e) { error = e; }
assert(error !== null); assert.deepEqual(error.message, "Feed generator not found"); });});
describe("ensurePinnedItems", () => { it("should return existing pinned items without loading", async () => { const pinnedItems = [ { type: "feed", data: { uri: "feed1" } }, { type: "feed", data: { uri: "feed2" } }, ]; let loadCalled = false;
const derived = createMockDerived({ pinnedItems }); const requests = { loadPinnedItems: async () => { loadCalled = true; }, };
const declarative = new Declarative(derived, requests); const result = await declarative.ensurePinnedItems();
assert.deepEqual(result, pinnedItems); assert.deepEqual(loadCalled, false); });
it("should load pinned items when not in cache", async () => { const pinnedItems = [{ type: "feed", data: { uri: "feed1" } }]; let callCount = 0;
const derived = { $hydratedPinnedItems: sig(() => { callCount++; return callCount > 1 ? pinnedItems : null; }), }; const requests = { loadPinnedItems: async () => {}, };
const declarative = new Declarative(derived, requests); const result = await declarative.ensurePinnedItems();
assert.deepEqual(result, pinnedItems); });
it("should throw when pinned items not found after loading", async () => { const derived = createMockDerived({}); const requests = createMockRequests({});
const declarative = new Declarative(derived, requests);
let error = null; try { await declarative.ensurePinnedItems(); } catch (caught) { error = caught; }
assert(error !== null); assert.deepEqual(error.message, "Pinned items not found"); });});
describe("ensureConvoList", () => { it("should return existing convo list without loading", async () => { const convoList = [{ id: "convo1" }, { id: "convo2" }]; let loadCalled = false;
const derived = createMockDerived({ convoList }); const requests = { loadConvoList: async () => { loadCalled = true; }, };
const declarative = new Declarative(derived, requests); const result = await declarative.ensureConvoList();
assert.deepEqual(result, convoList); assert.deepEqual(loadCalled, false); });
it("should load convo list when not in cache", async () => { const convoList = [{ id: "convo1" }]; let callCount = 0;
const derived = { $convoList: sig(() => { callCount++; return callCount > 1 ? convoList : null; }), }; const requests = { loadConvoList: async () => {}, };
const declarative = new Declarative(derived, requests); const result = await declarative.ensureConvoList();
assert.deepEqual(result, convoList); });
it("should throw when convo list not found after loading", async () => { const derived = createMockDerived({}); const requests = createMockRequests({});
const declarative = new Declarative(derived, requests);
let error = null; try { await declarative.ensureConvoList(); } catch (e) { error = e; }
assert(error !== null); assert.deepEqual(error.message, "Conversation list not found"); });});
describe("ensureConvo", () => { it("should return existing convo without loading", async () => { const convoId = "convo123"; const convo = { id: convoId, messages: [] }; let loadCalled = false;
const derived = createMockDerived({ convos: { [convoId]: convo } }); const requests = { loadConvo: async () => { loadCalled = true; }, };
const declarative = new Declarative(derived, requests); const result = await declarative.ensureConvo(convoId);
assert.deepEqual(result, convo); assert.deepEqual(loadCalled, false); });
it("should load convo when not in cache", async () => { const convoId = "convo123"; const convo = { id: convoId, messages: [] }; let callCount = 0;
const derived = { $convos: mapSig(() => { callCount++; return callCount > 1 ? convo : null; }), }; const requests = { loadConvo: async () => {}, };
const declarative = new Declarative(derived, requests); const result = await declarative.ensureConvo(convoId);
assert.deepEqual(result, convo); });
it("should throw when convo not found after loading", async () => { const derived = createMockDerived({}); const requests = createMockRequests({});
const declarative = new Declarative(derived, requests);
let error = null; try { await declarative.ensureConvo("nonexistent"); } catch (e) { error = e; }
assert(error !== null); assert.deepEqual(error.message, "Conversation not found"); });});
describe("ensureConvoForProfile", () => { it("should return existing convo for profile without loading", async () => { const profileDid = "did:test:profile"; const convo = { id: "convo123", members: [profileDid] }; let loadCalled = false;
const derived = createMockDerived({ convoForProfile: { [profileDid]: convo }, }); const requests = { loadConvoForProfile: async () => { loadCalled = true; }, };
const declarative = new Declarative(derived, requests); const result = await declarative.ensureConvoForProfile(profileDid);
assert.deepEqual(result, convo); assert.deepEqual(loadCalled, false); });
it("should load convo for profile when not in cache", async () => { const profileDid = "did:test:profile"; const convo = { id: "convo123", members: [profileDid] }; let callCount = 0;
const derived = { $convoForProfile: mapSig(() => { callCount++; return callCount > 1 ? convo : null; }), }; const requests = { loadConvoForProfile: async () => {}, };
const declarative = new Declarative(derived, requests); const result = await declarative.ensureConvoForProfile(profileDid);
assert.deepEqual(result, convo); });
it("should throw when convo for profile not found after loading", async () => { const derived = createMockDerived({}); const requests = createMockRequests({});
const declarative = new Declarative(derived, requests);
let error = null; try { await declarative.ensureConvoForProfile("did:nonexistent"); } catch (e) { error = e; }
assert(error !== null); assert.deepEqual(error.message, "Conversation not found"); });});