Something went wrong. Try again.
Mirror of https://github.com/improsocial/improAn extensible Bluesky client for web
Something went wrong. Try again.
JavaScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697import { describe, it } from "node:test";import assert from "node:assert/strict";import { PatchStore } from "/js/dataLayer/patchStore.js";
// applyPostPatches now requires the patches array explicitly. This helper// fetches the current patches for a post URI and applies them.function applyPostPatches(patchStore, post) { const patches = patchStore.$postPatches.get(post.uri) || []; return patchStore.applyPostPatches(post, patches);}
describe("Post Patches - Patch Management", () => { const postURI = "at://did:test/app.bsky.feed.post/test"; const basePost = { uri: postURI, likeCount: 5, viewer: { like: null }, };
it("should add a post patch and return a patch ID", () => { const patchStore = new PatchStore(); const patchId = patchStore.addPostPatch(postURI, { type: "addLike" }); assert.deepEqual(typeof patchId, "number"); assert(patchId >= 0); });
it("should generate unique patch IDs", () => { const patchStore = new PatchStore(); const id1 = patchStore.addPostPatch(postURI, { type: "addLike" }); const id2 = patchStore.addPostPatch(postURI, { type: "removeLike" }); assert(id1 !== id2); });
it("should remove a post patch by ID", () => { const patchStore = new PatchStore(); const patchId = patchStore.addPostPatch(postURI, { type: "addLike" });
// Verify patch exists const patchedPost = applyPostPatches(patchStore, basePost); assert.deepEqual(patchedPost.viewer.like, "fake like");
// Remove patch patchStore.removePostPatch(postURI, patchId);
// Verify patch is removed const unpatchedPost = applyPostPatches(patchStore, basePost); assert.deepEqual(unpatchedPost.viewer.like, null); });
it("should handle removing non-existent patch ID gracefully", () => { const patchStore = new PatchStore(); patchStore.addPostPatch(postURI, { type: "addLike" }); let errorThrown = false; try { patchStore.removePostPatch(postURI, 999); } catch (e) { errorThrown = true; } assert.deepEqual(errorThrown, false); });});
describe("Post Patches - Like Patches", () => { const postURI = "at://did:test/app.bsky.feed.post/test"; const basePost = { uri: postURI, likeCount: 5, viewer: { like: null }, };
it("should apply addLike patch correctly", () => { const patchStore = new PatchStore(); patchStore.addPostPatch(postURI, { type: "addLike" }); const result = applyPostPatches(patchStore, basePost);
assert.deepEqual(result.viewer.like, "fake like"); assert.deepEqual(result.likeCount, 6); assert.deepEqual(result.uri, postURI); });
it("should apply removeLike patch correctly", () => { const patchStore = new PatchStore(); const likedPost = { ...basePost, likeCount: 6, viewer: { like: "some-like-uri" }, };
patchStore.addPostPatch(postURI, { type: "removeLike" }); const result = applyPostPatches(patchStore, likedPost);
assert.deepEqual(result.viewer.like, null); assert.deepEqual(result.likeCount, 5); });
it("should apply multiple patches in order", () => { const patchStore = new PatchStore(); // Add like, then remove like patchStore.addPostPatch(postURI, { type: "addLike" }); patchStore.addPostPatch(postURI, { type: "removeLike" });
const result = applyPostPatches(patchStore, basePost);
assert.deepEqual(result.viewer.like, null); assert.deepEqual(result.likeCount, 5); // +1 -1 = 0, so 5 + 0 = 5 });
it("should preserve original post when no patches exist", () => { const patchStore = new PatchStore(); const result = applyPostPatches(patchStore, basePost); assert.deepEqual(result, basePost); assert(result !== basePost); // Should be a copy });});
describe("Post Patches - Error Handling", () => { const postURI = "at://did:test/app.bsky.feed.post/test"; const basePost = { uri: postURI, likeCount: 5, viewer: { like: null }, };
it("should throw error for unknown patch type", () => { const patchStore = new PatchStore(); patchStore.addPostPatch(postURI, { type: "unknownPatch" });
let errorThrown = false; let errorMessage = ""; try { applyPostPatches(patchStore, basePost); } catch (e) { errorThrown = true; errorMessage = e.message; } assert.deepEqual(errorThrown, true); assert(errorMessage.includes("Unknown patch type")); });});
describe("Profile Patches - Patch Management", () => { const profileDID = "did:test:profile"; const baseProfile = { did: profileDID, viewer: { following: null }, };
it("should add a profile patch and return a patch ID", () => { const patchStore = new PatchStore(); const patchId = patchStore.addProfilePatch(profileDID, { type: "followProfile", }); assert.deepEqual(typeof patchId, "number"); assert(patchId >= 0); });
it("should remove a profile patch by ID", () => { const patchStore = new PatchStore(); const patchId = patchStore.addProfilePatch(profileDID, { type: "followProfile", });
// Verify patch exists const patchedProfile = patchStore.applyProfilePatches(baseProfile); assert.deepEqual(patchedProfile.viewer.following, "fake following");
// Remove patch patchStore.removeProfilePatch(profileDID, patchId);
// Verify patch is removed const unpatchedProfile = patchStore.applyProfilePatches(baseProfile); assert.deepEqual(unpatchedProfile.viewer.following, null); });});
describe("Profile Patches - Follow Patches", () => { const profileDID = "did:test:profile"; const baseProfile = { did: profileDID, viewer: { following: null }, };
it("should apply followProfile patch correctly", () => { const patchStore = new PatchStore(); patchStore.addProfilePatch(profileDID, { type: "followProfile" }); const result = patchStore.applyProfilePatches(baseProfile);
assert.deepEqual(result.viewer.following, "fake following"); assert.deepEqual(result.did, profileDID); });
it("should apply unfollowProfile patch correctly", () => { const patchStore = new PatchStore(); const followedProfile = { ...baseProfile, viewer: { following: "some-follow-uri" }, };
patchStore.addProfilePatch(profileDID, { type: "unfollowProfile" }); const result = patchStore.applyProfilePatches(followedProfile);
assert.deepEqual(result.viewer.following, null); });
it("should apply multiple profile patches in order", () => { const patchStore = new PatchStore(); // Follow, then unfollow patchStore.addProfilePatch(profileDID, { type: "followProfile" }); patchStore.addProfilePatch(profileDID, { type: "unfollowProfile" });
const result = patchStore.applyProfilePatches(baseProfile); assert.deepEqual(result.viewer.following, null); });});
describe("Profile Patches - Error Handling", () => { const profileDID = "did:test:profile"; const baseProfile = { did: profileDID, viewer: { following: null }, };
it("should throw error for unknown profile patch type", () => { const patchStore = new PatchStore(); patchStore.addProfilePatch(profileDID, { type: "unknownPatch" });
let errorThrown = false; let errorMessage = ""; try { patchStore.applyProfilePatches(baseProfile); } catch (e) { errorThrown = true; errorMessage = e.message; } assert.deepEqual(errorThrown, true); assert(errorMessage.includes("Unknown patch type")); });});
describe("UUID Generation", () => { it("should generate sequential IDs", () => { const patchStore = new PatchStore(); const id1 = patchStore.addPostPatch("post1", { type: "addLike" }); const id2 = patchStore.addPostPatch("post2", { type: "addLike" }); const id3 = patchStore.addProfilePatch("profile1", { type: "followProfile", });
assert.deepEqual(id2, id1 + 1); assert.deepEqual(id3, id2 + 1); });});
describe("Patch Isolation", () => { it("should isolate patches between different posts", () => { const patchStore = new PatchStore(); const post1URI = "post1"; const post2URI = "post2"; const basePost1 = { uri: post1URI, likeCount: 5, viewer: { like: null } }; const basePost2 = { uri: post2URI, likeCount: 10, viewer: { like: null } };
patchStore.addPostPatch(post1URI, { type: "addLike" });
const result1 = applyPostPatches(patchStore, basePost1); const result2 = applyPostPatches(patchStore, basePost2);
assert.deepEqual(result1.likeCount, 6); assert.deepEqual(result2.likeCount, 10); // Unchanged });
it("should isolate patches between different profiles", () => { const patchStore = new PatchStore(); const profile1URI = "profile1"; const profile2URI = "profile2"; const baseProfile1 = { did: profile1URI, viewer: { following: null } }; const baseProfile2 = { did: profile2URI, viewer: { following: null } };
patchStore.addProfilePatch(profile1URI, { type: "followProfile" });
const result1 = patchStore.applyProfilePatches(baseProfile1); const result2 = patchStore.applyProfilePatches(baseProfile2);
assert.deepEqual(result1.viewer.following, "fake following"); assert.deepEqual(result2.viewer.following, null); // Unchanged });});
describe("Preference Patches - Labeler Patches", () => { it("should apply subscribeLabeler patch correctly", () => { const patchStore = new PatchStore(); const labelerDid = "did:plc:testlabeler"; const labelerInfo = { creator: { did: labelerDid }, policies: { labelValueDefinitions: [] }, };
// Create a mock preferences object with subscribeLabeler method const mockPreferences = { clone: () => mockPreferences, subscribeLabeler: (did, info) => ({ ...mockPreferences, _subscribedLabeler: did, _labelerInfo: info, }), };
patchStore.addPreferencePatch({ type: "subscribeLabeler", did: labelerDid, labelerInfo, }); const result = patchStore.applyPreferencePatches(mockPreferences);
assert.deepEqual(result._subscribedLabeler, labelerDid); assert.deepEqual(result._labelerInfo, labelerInfo); });
it("should apply unsubscribeLabeler patch correctly", () => { const patchStore = new PatchStore(); const labelerDid = "did:plc:testlabeler";
// Create a mock preferences object with unsubscribeLabeler method const mockPreferences = { clone: () => mockPreferences, unsubscribeLabeler: (did) => ({ ...mockPreferences, _unsubscribedLabeler: did, }), };
patchStore.addPreferencePatch({ type: "unsubscribeLabeler", did: labelerDid, }); const result = patchStore.applyPreferencePatches(mockPreferences);
assert.deepEqual(result._unsubscribedLabeler, labelerDid); });
it("should apply multiple labeler patches in order", () => { const patchStore = new PatchStore(); const labelerDid1 = "did:plc:labeler1"; const labelerDid2 = "did:plc:labeler2"; const labelerInfo1 = { creator: { did: labelerDid1 }, policies: { labelValueDefinitions: [] }, }; const labelerInfo2 = { creator: { did: labelerDid2 }, policies: { labelValueDefinitions: [] }, };
// Track calls in order const calls = []; const mockPreferences = { clone: () => mockPreferences, subscribeLabeler: (did, info) => { calls.push({ type: "subscribe", did, info }); return mockPreferences; }, unsubscribeLabeler: (did) => { calls.push({ type: "unsubscribe", did }); return mockPreferences; }, };
patchStore.addPreferencePatch({ type: "subscribeLabeler", did: labelerDid1, labelerInfo: labelerInfo1, }); patchStore.addPreferencePatch({ type: "subscribeLabeler", did: labelerDid2, labelerInfo: labelerInfo2, }); patchStore.addPreferencePatch({ type: "unsubscribeLabeler", did: labelerDid1, });
patchStore.applyPreferencePatches(mockPreferences);
assert.deepEqual(calls.length, 3); assert.deepEqual(calls[0].type, "subscribe"); assert.deepEqual(calls[0].did, labelerDid1); assert.deepEqual(calls[1].type, "subscribe"); assert.deepEqual(calls[1].did, labelerDid2); assert.deepEqual(calls[2], { type: "unsubscribe", did: labelerDid1 }); });});
describe("Preference Patches - Pin Feed Patches", () => { it("should forward entryType to preferences.pinFeed", () => { const patchStore = new PatchStore(); const calls = []; const mockPreferences = { clone: () => mockPreferences, pinFeed: (feedUri, type) => { calls.push({ feedUri, type }); return mockPreferences; }, };
patchStore.addPreferencePatch({ type: "pinFeed", feedUri: "at://did:test/app.bsky.graph.list/abc", entryType: "list", }); patchStore.applyPreferencePatches(mockPreferences);
assert.deepEqual(calls.length, 1); assert.deepEqual(calls[0].feedUri, "at://did:test/app.bsky.graph.list/abc"); assert.deepEqual(calls[0].type, "list"); });
it("should pass entryType undefined when patch omits it (default 'feed' applies)", () => { const patchStore = new PatchStore(); const calls = []; const mockPreferences = { clone: () => mockPreferences, pinFeed: (feedUri, type) => { calls.push({ feedUri, type }); return mockPreferences; }, };
patchStore.addPreferencePatch({ type: "pinFeed", feedUri: "at://did:test/app.bsky.feed.generator/xyz", }); patchStore.applyPreferencePatches(mockPreferences);
assert.deepEqual(calls.length, 1); assert.deepEqual(calls[0].type, undefined); });});
describe("Preference Patches - Patch Management", () => { it("should add and remove preference patches", () => { const patchStore = new PatchStore();
const patchId1 = patchStore.addPreferencePatch({ type: "subscribeLabeler", did: "did:test1", }); const patchId2 = patchStore.addPreferencePatch({ type: "unsubscribeLabeler", did: "did:test2", });
assert.deepEqual(patchStore.$preferencePatches.get().length, 2);
patchStore.removePreferencePatch(patchId1); assert.deepEqual(patchStore.$preferencePatches.get().length, 1); assert.deepEqual( patchStore.$preferencePatches.get()[0].body.type, "unsubscribeLabeler", );
patchStore.removePreferencePatch(patchId2); assert.deepEqual(patchStore.$preferencePatches.get().length, 0); });
it("should generate unique IDs for preference patches", () => { const patchStore = new PatchStore();
const id1 = patchStore.addPreferencePatch({ type: "subscribeLabeler", did: "did:test1", }); const id2 = patchStore.addPreferencePatch({ type: "subscribeLabeler", did: "did:test2", });
assert(id1 !== id2); });});
describe("Preference Patches - Content Label Patches", () => { it("should apply setContentLabelPref patch correctly", () => { const patchStore = new PatchStore(); const labelerDid = "did:plc:testlabeler"; const label = "nsfw"; const visibility = "warn";
// Create a mock preferences object with setContentLabelPref method const mockPreferences = { clone: () => mockPreferences, setContentLabelPref: (params) => ({ ...mockPreferences, _contentLabelPref: params, }), };
patchStore.addPreferencePatch({ type: "setContentLabelPref", label, visibility, labelerDid, }); const result = patchStore.applyPreferencePatches(mockPreferences);
assert.deepEqual(result._contentLabelPref.label, label); assert.deepEqual(result._contentLabelPref.visibility, visibility); assert.deepEqual(result._contentLabelPref.labelerDid, labelerDid); });
it("should apply multiple content label patches in order", () => { const patchStore = new PatchStore(); const labelerDid = "did:plc:testlabeler";
// Track calls in order const calls = []; const mockPreferences = { clone: () => mockPreferences, setContentLabelPref: (params) => { calls.push(params); return mockPreferences; }, };
patchStore.addPreferencePatch({ type: "setContentLabelPref", label: "nsfw", visibility: "warn", labelerDid, }); patchStore.addPreferencePatch({ type: "setContentLabelPref", label: "gore", visibility: "hide", labelerDid, });
patchStore.applyPreferencePatches(mockPreferences);
assert.deepEqual(calls.length, 2); assert.deepEqual(calls[0].label, "nsfw"); assert.deepEqual(calls[0].visibility, "warn"); assert.deepEqual(calls[1].label, "gore"); assert.deepEqual(calls[1].visibility, "hide"); });
it("should mix content label patches with labeler patches", () => { const patchStore = new PatchStore(); const labelerDid = "did:plc:testlabeler"; const labelerInfo = { creator: { did: labelerDid }, policies: { labelValueDefinitions: [] }, };
const calls = []; const mockPreferences = { clone: () => mockPreferences, subscribeLabeler: (did, info) => { calls.push({ type: "subscribe", did, info }); return mockPreferences; }, setContentLabelPref: (params) => { calls.push({ type: "setContentLabelPref", ...params }); return mockPreferences; }, };
patchStore.addPreferencePatch({ type: "subscribeLabeler", did: labelerDid, labelerInfo, }); patchStore.addPreferencePatch({ type: "setContentLabelPref", label: "nsfw", visibility: "warn", labelerDid, });
patchStore.applyPreferencePatches(mockPreferences);
assert.deepEqual(calls.length, 2); assert.deepEqual(calls[0].type, "subscribe"); assert.deepEqual(calls[0].did, labelerDid); assert.deepEqual(calls[1].type, "setContentLabelPref"); assert.deepEqual(calls[1].label, "nsfw"); });});
describe("Current User Patches", () => { const baseUser = { did: "did:plc:me", handle: "me.test" };
it("should overlay pinnedPost via setPinnedPost", () => { const patchStore = new PatchStore(); patchStore.addCurrentUserPatch({ type: "setPinnedPost", pinnedPost: { uri: "at://x/y/1", cid: "c1" }, }); const patched = patchStore.applyCurrentUserPatches(baseUser); assert.deepEqual(patched.pinnedPost.uri, "at://x/y/1"); });
it("should remove pinnedPost via clearPinnedPost", () => { const patchStore = new PatchStore(); patchStore.addCurrentUserPatch({ type: "clearPinnedPost" }); const user = { ...baseUser, pinnedPost: { uri: "at://x/y/1", cid: "c1" } }; const patched = patchStore.applyCurrentUserPatches(user); assert.deepEqual(patched.pinnedPost, undefined); });
it("should return null user unchanged", () => { const patchStore = new PatchStore(); patchStore.addCurrentUserPatch({ type: "clearPinnedPost" }); assert.deepEqual(patchStore.applyCurrentUserPatches(null), null); });
it("should drop the patch after remove", () => { const patchStore = new PatchStore(); const id = patchStore.addCurrentUserPatch({ type: "setPinnedPost", pinnedPost: { uri: "at://x/y/1", cid: "c1" }, }); patchStore.removeCurrentUserPatch(id); const patched = patchStore.applyCurrentUserPatches(baseUser); assert.deepEqual(patched.pinnedPost, undefined); });});
describe("Author Feed Patches", () => { const feedURI = "did:plc:me-posts"; const targetPost = { uri: "at://did:plc:me/app.bsky.feed.post/p1", cid: "c1", };
it("should pin a post in the feed via pinPost", () => { const patchStore = new PatchStore(); const feed = { feed: [{ post: { uri: "at://other" } }, { post: targetPost }], cursor: "x", }; patchStore.addAuthorFeedPatch(feedURI, { type: "pinPost", post: targetPost, }); const patched = patchStore.applyAuthorFeedPatches(feedURI, feed); assert.deepEqual(patched.feed[0].post.uri, targetPost.uri); assert.deepEqual( patched.feed[0].reason.$type, "app.bsky.feed.defs#reasonPin", ); assert.deepEqual(patched.feed.length, 2); assert.deepEqual(patched.cursor, "x"); });
it("should clear the pin reason on the item via unpinPost", () => { const patchStore = new PatchStore(); const feed = { feed: [ { post: targetPost, reason: { $type: "app.bsky.feed.defs#reasonPin" }, }, ], cursor: "x", }; patchStore.addAuthorFeedPatch(feedURI, { type: "unpinPost", post: targetPost, }); const patched = patchStore.applyAuthorFeedPatches(feedURI, feed); assert.deepEqual(patched.feed.length, 1); assert.deepEqual(patched.feed[0].post.uri, targetPost.uri); assert.deepEqual(patched.feed[0].reason, undefined); });
it("should return null feed unchanged", () => { const patchStore = new PatchStore(); patchStore.addAuthorFeedPatch(feedURI, { type: "pinPost", post: targetPost, }); assert.deepEqual(patchStore.applyAuthorFeedPatches(feedURI, null), null); });
it("should drop the patch after remove", () => { const patchStore = new PatchStore(); const id = patchStore.addAuthorFeedPatch(feedURI, { type: "pinPost", post: targetPost, }); patchStore.removeAuthorFeedPatch(feedURI, id); const feed = { feed: [{ post: targetPost }], cursor: "" }; const patched = patchStore.applyAuthorFeedPatches(feedURI, feed); assert.deepEqual(patched.feed[0].reason, undefined); });});