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.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910import { describe, it } from "node:test";import assert from "node:assert/strict";import { CDN_URL } from "/js/config.js";import { avatarThumbnailUrl, buildProfileFromRecord, cdnImageUrl, getPostNumberingForPostThread, createNestedThreadViewPost, insertReplyIntoThread, getRKey, getIsLiked, isListFeed, isReferenceList, isCurateList, getQuotedPost, getImagesFromDraftPost, getImagesFromPost, getLocalRefsFromDraft, getBlockedQuote, createEmbedFromPost, embedViewRecordToPostView, replaceTopParent, isAutomatedAccount, isLabelerProfile, getLabelNameAndDescription, getLabelerForLabel, getDefinitionForLabel, isBadgeLabel, addFeedItemToFeed, pinPostInFeed, unpinPostInFeed, getDisplayName, normalizeThreadgateAllowSettings, buildThreadgateAllowFromNormalized, hasDisableEmbeddingRule, buildPostgateEmbeddingRules, isEmptyPost, createUnavailablePost, hasValidHandle, INVALID_HANDLE, MISSING_HANDLE, canReplyToPost, transformNestedQuotes, getInteractionTimestamp, getConvoPreviewText, getInteractionProfileDids, getGroupConvoDetails, getGroupConvoOwner, getSystemMessageDisplayText, groupReactions, isGroupConvo, isInviteLinkUrl, isInAppLinkHostname, getInviteCodeFromUrl, isVideoLink, isAvailableJoinLinkPreview, getJoinLinkCodeFromEmbed, getJoinLinkCodesFromPosts, getJoinLinkCodesFromMessages, attachJoinLinkPreviewToEmbed, getPostsFromPostThread, getPostsFromFeed,} from "/js/dataHelpers.js";import { IN_APP_LINK_DOMAINS } from "/js/config.js";import { createPost, createThreadViewPost } from "../../shared/factories.js";
describe("buildProfileFromRecord", () => { const did = "did:plc:me"; const blob = (cid) => ({ $type: "blob", ref: { $link: cid }, mimeType: "image/jpeg", size: 1000, });
it("should map record fields and build CDN urls for blobs", () => { const profile = buildProfileFromRecord({ did, handle: "me.test", record: { uri: `at://${did}/app.bsky.actor.profile/self`, value: { displayName: "Me", description: "hello", avatar: blob("avatarcid"), banner: blob("bannercid"), pinnedPost: { uri: `at://${did}/app.bsky.feed.post/1`, cid: "abc" }, createdAt: "2024-01-01T00:00:00.000Z", }, }, });
assert.deepEqual(profile, { did, handle: "me.test", displayName: "Me", description: "hello", avatar: `${CDN_URL}/img/avatar/plain/${did}/avatarcid@jpeg`, banner: `${CDN_URL}/img/banner/plain/${did}/bannercid@jpeg`, pinnedPost: { uri: `at://${did}/app.bsky.feed.post/1`, cid: "abc" }, createdAt: "2024-01-01T00:00:00.000Z", labels: [], isPartial: true, }); });
it("should null out missing fields when there is no record", () => { const profile = buildProfileFromRecord({ did, handle: "me.test", record: null, });
assert.deepEqual(profile.did, did); assert.deepEqual(profile.handle, "me.test"); assert.deepEqual(profile.displayName, null); assert.deepEqual(profile.avatar, null); assert.deepEqual(profile.banner, null); assert.deepEqual(profile.pinnedPost, null); assert.deepEqual(profile.isPartial, true); });});
describe("avatarThumbnailUrl", () => { it("should convert plain avatar URL to thumbnail URL", () => { const avatarUrl = "https://cdn.bsky.app/img/avatar/plain/did:plc:123/image@jpeg"; const expected = "https://cdn.bsky.app/img/avatar_thumbnail/plain/did:plc:123/image@jpeg"; assert.deepEqual(avatarThumbnailUrl(avatarUrl), expected); });
it("should handle URL without /img/avatar/plain/", () => { const avatarUrl = "https://cdn.bsky.app/img/other/plain/image.jpg"; assert.deepEqual(avatarThumbnailUrl(avatarUrl), avatarUrl); });
it("should handle empty string", () => { assert.deepEqual(avatarThumbnailUrl(""), ""); });});
describe("cdnImageUrl", () => { it("should rewrite the bsky CDN origin to the configured CDN", () => { assert.deepEqual( cdnImageUrl( "https://cdn.bsky.app/img/feed_thumbnail/plain/did:plc:123/imagecid@jpeg", ), `${CDN_URL}/img/feed_thumbnail/plain/did:plc:123/imagecid@jpeg`, ); });
it("should leave other hosts alone", () => { const videoThumb = "https://video.bsky.app/watch/did:plc:123/videocid/thumbnail.jpg"; assert.deepEqual(cdnImageUrl(videoThumb), videoThumb); const ogCard = "https://ogcard.cdn.bsky.app/start/did:plc:123/rkey"; assert.deepEqual(cdnImageUrl(ogCard), ogCard); });
it("should leave non-absolute and non-URL values alone", () => { assert.deepEqual( cdnImageUrl("/img/avatar-fallback.svg"), "/img/avatar-fallback.svg", ); assert.deepEqual( cdnImageUrl("data:image/png;base64,abc"), "data:image/png;base64,abc", ); });
it("should pass through empty values", () => { assert.deepEqual(cdnImageUrl(""), ""); assert.deepEqual(cdnImageUrl(null), null); assert.deepEqual(cdnImageUrl(undefined), undefined); });});
describe("getRKey", () => { it("should extract rkey from post URI", () => { const post = { uri: "at://did:plc:123/app.bsky.feed.post/3l7q2wm5ws22k" }; assert.deepEqual(getRKey(post), "3l7q2wm5ws22k"); });
it("should handle URI with different path structure", () => { const post = { uri: "at://did:plc:456/collection/another-rkey" }; assert.deepEqual(getRKey(post), "another-rkey"); });
it("should handle URI with single path segment", () => { const post = { uri: "single-segment" }; assert.deepEqual(getRKey(post), "single-segment"); });});
describe("isListFeed", () => { it("should return true for a list URI", () => { assert.deepEqual( isListFeed("at://did:plc:123/app.bsky.graph.list/3ltcvl4ver723"), true, ); });
it("should return false for a feed generator URI", () => { assert.deepEqual( isListFeed("at://did:plc:123/app.bsky.feed.generator/whats-hot"), false, ); });
it("should return false for the following feed", () => { assert.deepEqual(isListFeed("following"), false); });});
describe("getIsLiked", () => { it("should return true when post has viewer like", () => { const post = { viewer: { like: "at://did:plc:123/like/abc123" } }; assert.deepEqual(getIsLiked(post), true); });
it("should return false when viewer like is empty string", () => { const post = { viewer: { like: "" } }; assert.deepEqual(getIsLiked(post), false); });
it("should return false when viewer like is null", () => { const post = { viewer: { like: null } }; assert.deepEqual(getIsLiked(post), false); });
it("should return false when viewer like is undefined", () => { const post = { viewer: { like: undefined } }; assert.deepEqual(getIsLiked(post), false); });
it("should return false when viewer is undefined", () => { const post = {}; assert.deepEqual(getIsLiked(post), false); });
it("should return false when post has no viewer property", () => { const post = { uri: "test" }; assert.deepEqual(getIsLiked(post), false); });});
describe("getQuotedPost", () => { it("should return record for app.bsky.embed.record#view", () => { const post = { embed: { $type: "app.bsky.embed.record#view", record: { uri: "quoted-post-uri", author: { displayName: "Test" } }, }, }; assert.deepEqual(getQuotedPost(post), post.embed.record); });
it("should return nested record for app.bsky.embed.recordWithMedia#view", () => { const post = { embed: { $type: "app.bsky.embed.recordWithMedia#view", record: { record: { uri: "quoted-post-uri", author: { displayName: "Test" } }, }, }, }; assert.deepEqual(getQuotedPost(post), post.embed.record.record); });
it("should return null for embed with different $type", () => { const post = { embed: { $type: "app.bsky.embed.images#view", images: [], }, }; assert.deepEqual(getQuotedPost(post), null); });
it("should return null when embed is undefined", () => { const post = {}; assert.deepEqual(getQuotedPost(post), null); });
it("should return null when embed is null", () => { const post = { embed: null }; assert.deepEqual(getQuotedPost(post), null); });
it("should return null when post has no embed property", () => { const post = { uri: "test" }; assert.deepEqual(getQuotedPost(post), null); });
it("should use embeds array when available", () => { const post = { embeds: [ { $type: "app.bsky.embed.record#view", record: { uri: "quoted-post-uri", author: { displayName: "Test" } }, }, ], }; assert.deepEqual(getQuotedPost(post), post.embeds[0].record); });
it("should use embeds array for recordWithMedia", () => { const post = { embeds: [ { $type: "app.bsky.embed.recordWithMedia#view", record: { record: { uri: "quoted-post-uri", author: { displayName: "Test" }, }, }, }, ], }; assert.deepEqual(getQuotedPost(post), post.embeds[0].record.record); });
it("should prefer embeds array over embed property", () => { const post = { embeds: [ { $type: "app.bsky.embed.record#view", record: { uri: "from-embeds-array" }, }, ], embed: { $type: "app.bsky.embed.record#view", record: { uri: "from-embed-prop" }, }, }; assert.deepEqual(getQuotedPost(post), post.embeds[0].record); });
it("should fall back to embed when embeds is empty", () => { const post = { embeds: [], embed: { $type: "app.bsky.embed.record#view", record: { uri: "from-embed-prop" }, }, }; assert.deepEqual(getQuotedPost(post), null); });});
describe("getBlockedQuote", () => { it("should return blocked quote when quoted post is blocked", () => { const post = { embed: { $type: "app.bsky.embed.record#view", record: { $type: "app.bsky.embed.record#viewBlocked", uri: "blocked-uri", blocked: true, }, }, }; assert.deepEqual(getBlockedQuote(post), post.embed.record); });
it("should return null when quoted post is not blocked", () => { const post = { embed: { $type: "app.bsky.embed.record#view", record: { $type: "app.bsky.embed.record#view", uri: "normal-uri", }, }, }; assert.deepEqual(getBlockedQuote(post), null); });
it("should return null when no quoted post exists", () => { const post = { embed: { $type: "app.bsky.embed.images#view", images: [], }, }; assert.deepEqual(getBlockedQuote(post), null); });
it("should return null when post has no embed", () => { const post = {}; assert.deepEqual(getBlockedQuote(post), null); });});
describe("createEmbedFromPost", () => { it("should create embed from post with all required fields", () => { const post = { author: { did: "did:plc:123", displayName: "Test User" }, record: { text: "Hello world", createdAt: "2024-01-01" }, uri: "at://did:plc:123/app.bsky.feed.post/abc123", cid: "cid123", indexedAt: "2024-01-01T00:00:00Z", labels: [{ val: "test" }], likeCount: 5, replyCount: 2, repostCount: 1, quoteCount: 3, };
const result = createEmbedFromPost(post);
assert.deepEqual(result, { $type: "app.bsky.embed.record#viewRecord", author: { did: "did:plc:123", displayName: "Test User" }, value: { text: "Hello world", createdAt: "2024-01-01" }, uri: "at://did:plc:123/app.bsky.feed.post/abc123", cid: "cid123", indexedAt: "2024-01-01T00:00:00Z", labels: [{ val: "test" }], likeCount: 5, replyCount: 2, repostCount: 1, quoteCount: 3, }); });
it("should create separate copies of author and record objects", () => { const post = { author: { did: "did:plc:123" }, record: { text: "Hello" }, uri: "test-uri", };
const result = createEmbedFromPost(post);
assert(result.author !== post.author); assert(result.value !== post.record); assert.deepEqual(result.author, post.author); assert.deepEqual(result.value, post.record); });
it("should handle post with minimal data", () => { const post = { author: {}, record: {}, uri: "minimal-uri", };
const result = createEmbedFromPost(post);
assert.deepEqual(result, { $type: "app.bsky.embed.record#viewRecord", author: {}, value: {}, uri: "minimal-uri", cid: undefined, indexedAt: undefined, labels: undefined, likeCount: undefined, replyCount: undefined, repostCount: undefined, quoteCount: undefined, }); });
it("should include embeds when post has an embed", () => { const post = { author: { did: "did:plc:123" }, record: { text: "Hello" }, uri: "test-uri", cid: "cid456", indexedAt: "2024-02-01T00:00:00Z", labels: [], likeCount: 0, replyCount: 0, repostCount: 0, quoteCount: 0, embed: { $type: "app.bsky.embed.images#view", images: [{ thumb: "thumb.jpg" }], }, };
const result = createEmbedFromPost(post);
assert.deepEqual(result, { $type: "app.bsky.embed.record#viewRecord", author: { did: "did:plc:123" }, value: { text: "Hello" }, uri: "test-uri", cid: "cid456", indexedAt: "2024-02-01T00:00:00Z", labels: [], likeCount: 0, replyCount: 0, repostCount: 0, quoteCount: 0, embeds: [ { $type: "app.bsky.embed.images#view", images: [{ thumb: "thumb.jpg" }], }, ], }); });
it("should not include embeds when post has no embed", () => { const post = { author: { did: "did:plc:456" }, record: { text: "No embed" }, uri: "no-embed-uri", };
const result = createEmbedFromPost(post);
assert(!("embeds" in result)); });});
describe("embedViewRecordToPostView", () => { it("should convert a ViewRecord to a PostView", () => { const viewRecord = { uri: "at://did:plc:123/app.bsky.feed.post/abc", cid: "cid123", author: { did: "did:plc:123", handle: "test.user" }, value: { text: "Hello world", createdAt: "2024-01-01" }, embeds: [{ $type: "app.bsky.embed.images#view", images: [] }], labels: [{ val: "test" }], likeCount: 5, replyCount: 2, repostCount: 1, quoteCount: 3, indexedAt: "2024-01-01T00:00:00Z", };
const result = embedViewRecordToPostView(viewRecord);
assert.deepEqual(result, { uri: "at://did:plc:123/app.bsky.feed.post/abc", cid: "cid123", author: { did: "did:plc:123", handle: "test.user" }, record: { text: "Hello world", createdAt: "2024-01-01" }, embed: { $type: "app.bsky.embed.images#view", images: [] }, labels: [{ val: "test" }], likeCount: 5, replyCount: 2, repostCount: 1, quoteCount: 3, indexedAt: "2024-01-01T00:00:00Z", }); });
it("should map value to record and embeds[0] to embed", () => { const viewRecord = { uri: "test-uri", cid: "test-cid", author: {}, value: { text: "test" }, embeds: [{ $type: "embed1" }, { $type: "embed2" }], indexedAt: "2024-01-01T00:00:00Z", };
const result = embedViewRecordToPostView(viewRecord);
assert.deepEqual(result.record, viewRecord.value); assert.deepEqual(result.embed, viewRecord.embeds[0]); });
it("should handle missing embeds", () => { const viewRecord = { uri: "test-uri", cid: "test-cid", author: {}, value: { text: "test" }, indexedAt: "2024-01-01T00:00:00Z", };
const result = embedViewRecordToPostView(viewRecord);
assert.deepEqual(result.embed, undefined); });
it("should handle empty embeds array", () => { const viewRecord = { uri: "test-uri", cid: "test-cid", author: {}, value: { text: "test" }, embeds: [], indexedAt: "2024-01-01T00:00:00Z", };
const result = embedViewRecordToPostView(viewRecord);
assert.deepEqual(result.embed, undefined); });
it("should handle missing optional count fields", () => { const viewRecord = { uri: "test-uri", cid: "test-cid", author: {}, value: {}, indexedAt: "2024-01-01T00:00:00Z", };
const result = embedViewRecordToPostView(viewRecord);
assert.deepEqual(result.likeCount, undefined); assert.deepEqual(result.replyCount, undefined); assert.deepEqual(result.repostCount, undefined); assert.deepEqual(result.quoteCount, undefined); });});
describe("replaceTopParent", () => { it("should throw error when postThread has no parent", () => { const postThread = { post: { uri: "post-uri" } }; let threw = false; try { replaceTopParent(postThread, { post: { uri: "new-parent" } }); } catch (e) { threw = true; assert.deepEqual(e.message, "No parent found"); } assert(threw, "Expected replaceTopParent to throw"); });
it("should replace immediate parent when it is the top", () => { const postThread = { post: { uri: "child-uri" }, parent: { post: { uri: "parent-uri" } }, }; const newParent = { post: { uri: "new-parent-uri" } };
const result = replaceTopParent(postThread, newParent);
assert.deepEqual(result.parent, newParent); assert.deepEqual(result.post, postThread.post); });
it("should return new object when immediate parent is the top", () => { const postThread = { post: { uri: "child-uri" }, parent: { post: { uri: "parent-uri" } }, }; const newParent = { post: { uri: "new-parent-uri" } };
const result = replaceTopParent(postThread, newParent);
assert(result !== postThread, "Should return a new object"); });
it("should replace top parent when there are multiple levels", () => { const postThread = { post: { uri: "child-uri" }, parent: { post: { uri: "parent-uri" }, parent: { post: { uri: "grandparent-uri" }, }, }, }; const newParent = { post: { uri: "new-grandparent-uri" } };
const result = replaceTopParent(postThread, newParent);
assert.deepEqual(result.parent.parent, newParent); assert.deepEqual(result.parent.post.uri, "parent-uri"); });
it("should replace top parent when there are three levels", () => { const postThread = { post: { uri: "child-uri" }, parent: { post: { uri: "parent-uri" }, parent: { post: { uri: "grandparent-uri" }, parent: { post: { uri: "great-grandparent-uri" }, }, }, }, }; const newParent = { post: { uri: "new-top-uri" } };
const result = replaceTopParent(postThread, newParent);
assert.deepEqual(result.parent.parent.parent, newParent); assert.deepEqual(result.parent.parent.post.uri, "grandparent-uri"); });});
describe("isAutomatedAccount", () => { it("should return false for profile without labels", () => { const profile = { did: "did:plc:123", handle: "user.bsky.social" }; assert.deepEqual(isAutomatedAccount(profile), false); });
it("should return false for profile with empty labels", () => { const profile = { did: "did:plc:123", labels: [] }; assert.deepEqual(isAutomatedAccount(profile), false); });
it("should return false for profile with non-bot labels", () => { const profile = { did: "did:plc:123", labels: [{ val: "!no-unauthenticated" }], }; assert.deepEqual(isAutomatedAccount(profile), false); });
it("should return true for profile with bot label", () => { const profile = { did: "did:plc:123", labels: [{ val: "bot" }], }; assert.deepEqual(isAutomatedAccount(profile), true); });
it("should return true when bot label is among other labels", () => { const profile = { did: "did:plc:123", labels: [{ val: "!no-unauthenticated" }, { val: "bot" }], }; assert.deepEqual(isAutomatedAccount(profile), true); });});
describe("isLabelerProfile", () => { it("should return true when profile has associated labeler", () => { const profile = { associated: { labeler: true } }; assert.deepEqual(isLabelerProfile(profile), true); });
it("should return false when profile has no associated labeler", () => { const profile = { associated: { labeler: false } }; assert.deepEqual(isLabelerProfile(profile), false); });
it("should return undefined when profile has no associated property", () => { const profile = {}; assert.deepEqual(isLabelerProfile(profile), undefined); });
it("should return undefined when associated has no labeler property", () => { const profile = { associated: {} }; assert.deepEqual(isLabelerProfile(profile), undefined); });});
describe("getLabelNameAndDescription", () => { it("should return identifier as name when no locales", () => { const labelDefinition = { identifier: "test-label" }; const result = getLabelNameAndDescription(labelDefinition);
assert.deepEqual(result.name, "test-label"); assert.deepEqual(result.description, ""); });
it("should return identifier as name when locales is empty", () => { const labelDefinition = { identifier: "test-label", locales: [] }; const result = getLabelNameAndDescription(labelDefinition);
assert.deepEqual(result.name, "test-label"); assert.deepEqual(result.description, ""); });
it("should return preferred language locale", () => { const labelDefinition = { identifier: "test-label", locales: [ { lang: "es", name: "Etiqueta", description: "Descripción" }, { lang: "en", name: "Label", description: "Description" }, ], }; const result = getLabelNameAndDescription(labelDefinition, "en");
assert.deepEqual(result.name, "Label"); assert.deepEqual(result.description, "Description"); });
it("should fall back to first locale when preferred not found", () => { const labelDefinition = { identifier: "test-label", locales: [ { lang: "es", name: "Etiqueta", description: "Descripción" }, { lang: "fr", name: "Étiquette", description: "La description" }, ], }; const result = getLabelNameAndDescription(labelDefinition, "en");
assert.deepEqual(result.name, "Etiqueta"); assert.deepEqual(result.description, "Descripción"); });
it("should use identifier when locale name is missing", () => { const labelDefinition = { identifier: "test-label", locales: [{ lang: "en", description: "Description only" }], }; const result = getLabelNameAndDescription(labelDefinition, "en");
assert.deepEqual(result.name, "test-label"); assert.deepEqual(result.description, "Description only"); });
it("should default to en as preferred language", () => { const labelDefinition = { identifier: "test-label", locales: [ { lang: "es", name: "Etiqueta", description: "Descripción" }, { lang: "en", name: "Label", description: "Description" }, ], }; const result = getLabelNameAndDescription(labelDefinition);
assert.deepEqual(result.name, "Label"); assert.deepEqual(result.description, "Description"); });});
describe("getLabelerForLabel", () => { it("should return matching labeler by src did", () => { const label = { src: "did:plc:labeler1", val: "nsfw" }; const labelers = [ { creator: { did: "did:plc:labeler1" }, policies: {} }, { creator: { did: "did:plc:labeler2" }, policies: {} }, ];
const result = getLabelerForLabel(label, labelers);
assert.deepEqual(result.creator.did, "did:plc:labeler1"); });
it("should return null when no matching labeler", () => { const label = { src: "did:plc:unknown", val: "nsfw" }; const labelers = [{ creator: { did: "did:plc:labeler1" }, policies: {} }];
const result = getLabelerForLabel(label, labelers);
assert.deepEqual(result, null); });
it("should return null when labelers is empty", () => { const label = { src: "did:plc:labeler1", val: "nsfw" };
const result = getLabelerForLabel(label, []);
assert.deepEqual(result, null); });});
describe("getDefinitionForLabel", () => { it("should return matching label definition", () => { const label = { src: "did:plc:labeler1", val: "nsfw" }; const labeler = { creator: { did: "did:plc:labeler1" }, policies: { labelValueDefinitions: [ { identifier: "spam", blurs: "none" }, { identifier: "nsfw", blurs: "media" }, ], }, };
const result = getDefinitionForLabel(label, labeler);
assert.deepEqual(result.identifier, "nsfw"); assert.deepEqual(result.blurs, "media"); });
it("should return undefined when no matching definition", () => { const label = { src: "did:plc:labeler1", val: "unknown" }; const labeler = { creator: { did: "did:plc:labeler1" }, policies: { labelValueDefinitions: [{ identifier: "nsfw", blurs: "media" }], }, };
const result = getDefinitionForLabel(label, labeler);
assert.deepEqual(result, undefined); });});
describe("isBadgeLabel", () => { it("should return true when blurs is none", () => { const labelDefinition = { blurs: "none" }; assert.deepEqual(isBadgeLabel(labelDefinition), true); });
it("should return true when blurs is undefined", () => { const labelDefinition = {}; assert.deepEqual(isBadgeLabel(labelDefinition), true); });
it("should return false when blurs is media", () => { const labelDefinition = { blurs: "media" }; assert.deepEqual(isBadgeLabel(labelDefinition), false); });
it("should return false when blurs is content", () => { const labelDefinition = { blurs: "content" }; assert.deepEqual(isBadgeLabel(labelDefinition), false); });});
describe("addFeedItemToFeed", () => { it("should add item to empty feed", () => { const feedItem = { post: { uri: "post-1" } }; const result = addFeedItemToFeed(feedItem, []);
assert.deepEqual(result.length, 1); assert.deepEqual(result[0], feedItem); });
it("should add item to beginning of feed without pinned post", () => { const existingItem = { post: { uri: "post-1" } }; const newItem = { post: { uri: "post-2" } };
const result = addFeedItemToFeed(newItem, [existingItem]);
assert.deepEqual(result.length, 2); assert.deepEqual(result[0], newItem); assert.deepEqual(result[1], existingItem); });
it("should add item after pinned post", () => { const pinnedItem = { post: { uri: "pinned-post" }, reason: { $type: "app.bsky.feed.defs#reasonPin" }, }; const existingItem = { post: { uri: "post-1" } }; const newItem = { post: { uri: "post-2" } };
const result = addFeedItemToFeed(newItem, [pinnedItem, existingItem]);
assert.deepEqual(result.length, 3); assert.deepEqual(result[0], pinnedItem); assert.deepEqual(result[1], newItem); assert.deepEqual(result[2], existingItem); });
it("should handle pinned post not at first position", () => { const pinnedItem = { post: { uri: "pinned-post" }, reason: { $type: "app.bsky.feed.defs#reasonPin" }, }; const existingItem = { post: { uri: "post-1" } }; const newItem = { post: { uri: "post-2" } };
const result = addFeedItemToFeed(newItem, [existingItem, pinnedItem]);
assert.deepEqual(result.length, 3); assert.deepEqual(result[0], pinnedItem); assert.deepEqual(result[1], newItem); assert.deepEqual(result[2], existingItem); });
it("should handle repost feed items", () => { const repostItem = { post: { uri: "post-1" }, reason: { $type: "app.bsky.feed.defs#reasonRepost", by: { did: "did:plc:123" }, uri: "at://did:plc:123/app.bsky.feed.repost/abc", indexedAt: "2024-01-01T00:00:00Z", }, };
const result = addFeedItemToFeed(repostItem, []);
assert.deepEqual(result.length, 1); assert.deepEqual(result[0].reason.$type, "app.bsky.feed.defs#reasonRepost"); });});
describe("pinPostInFeed", () => { const pinReason = { $type: "app.bsky.feed.defs#reasonPin" };
it("should add a pinned item to an empty feed", () => { const post = { uri: "post-1", cid: "cid-1" }; const result = pinPostInFeed([], post); assert.deepEqual(result.length, 1); assert.deepEqual(result[0].post, post); assert.deepEqual(result[0].reason.$type, pinReason.$type); });
it("should move an existing post to the top and mark it pinned", () => { const post = { uri: "post-2", cid: "cid-2" }; const feed = [ { post: { uri: "post-1" } }, { post }, { post: { uri: "post-3" } }, ]; const result = pinPostInFeed(feed, post); assert.deepEqual(result.length, 3); assert.deepEqual(result[0].post.uri, "post-2"); assert.deepEqual(result[0].reason.$type, pinReason.$type); assert.deepEqual(result[1].post.uri, "post-1"); assert.deepEqual(result[2].post.uri, "post-3"); });
it("should unpin a previously pinned item when pinning a new one", () => { const oldPinned = { post: { uri: "post-1" }, reason: pinReason }; const other = { post: { uri: "post-2" } }; const newPost = { uri: "post-3", cid: "cid-3" }; const result = pinPostInFeed([oldPinned, other], newPost); assert.deepEqual(result.length, 3); assert.deepEqual(result[0].post.uri, "post-3"); assert.deepEqual(result[0].reason.$type, pinReason.$type); // Old pinned item is still present, but no longer carries the pin reason. const oldInResult = result.find((item) => item.post.uri === "post-1"); assert.deepEqual(oldInResult.reason, undefined); });
it("should not duplicate the post when it is already pinned", () => { const post = { uri: "post-1", cid: "cid-1" }; const feed = [{ post, reason: pinReason }, { post: { uri: "post-2" } }]; const result = pinPostInFeed(feed, post); assert.deepEqual(result.length, 2); assert.deepEqual(result[0].post.uri, "post-1"); assert.deepEqual(result[0].reason.$type, pinReason.$type); assert.deepEqual(result[1].post.uri, "post-2"); });
it("should not mutate the input feed", () => { const post = { uri: "post-1" }; const feed = [{ post: { uri: "post-2" } }]; const before = [...feed]; pinPostInFeed(feed, post); assert.deepEqual(feed.length, before.length); assert.deepEqual(feed[0], before[0]); });});
describe("unpinPostInFeed", () => { const pinReason = { $type: "app.bsky.feed.defs#reasonPin" };
it("should clear the pin reason on the matching item but keep it in place", () => { const post = { uri: "post-1", cid: "cid-1" }; const feed = [{ post, reason: pinReason }, { post: { uri: "post-2" } }]; const result = unpinPostInFeed(feed, post); assert.deepEqual(result.length, 2); assert.deepEqual(result[0].post.uri, "post-1"); assert.deepEqual(result[0].reason, undefined); assert.deepEqual(result[1].post.uri, "post-2"); });
it("should leave a non-pinned occurrence of the post unchanged", () => { const post = { uri: "post-1", cid: "cid-1" }; const feed = [{ post }, { post: { uri: "post-2" } }]; const result = unpinPostInFeed(feed, post); assert.deepEqual(result.length, 2); assert.deepEqual(result[0].post.uri, "post-1"); assert.deepEqual(result[0].reason, undefined); });
it("should not affect another pinned item with a different uri", () => { const post = { uri: "post-1" }; const otherPinned = { post: { uri: "post-2" }, reason: pinReason }; const result = unpinPostInFeed([otherPinned], post); assert.deepEqual(result.length, 1); assert.deepEqual(result[0].post.uri, "post-2"); assert.deepEqual(result[0].reason.$type, pinReason.$type); });
it("should return an empty feed when given one", () => { assert.deepEqual(unpinPostInFeed([], { uri: "post-1" }).length, 0); });});
describe("getDisplayName", () => { it("should return displayName when present", () => { const profile = { displayName: "Alice", handle: "alice.bsky.social" }; assert.deepEqual(getDisplayName(profile), "Alice"); });
it("should trim whitespace from displayName", () => { const profile = { displayName: " Alice ", handle: "alice.bsky.social" }; assert.deepEqual(getDisplayName(profile), "Alice"); });
it("should strip check mark characters", () => { const profile = { displayName: "Alice \u2705\u2713\u2714\u2611", handle: "alice.bsky.social", }; assert.deepEqual(getDisplayName(profile), "Alice"); });
it("should strip control characters", () => { const profile = { displayName: "Ali\u0000ce\u001F", handle: "alice.bsky.social", }; assert.deepEqual(getDisplayName(profile), "Alice"); });
it("should strip bidirectional override characters", () => { const profile = { displayName: "Ali\u202Ace\u202E", handle: "alice.bsky.social", }; assert.deepEqual(getDisplayName(profile), "Alice"); });
it("should collapse multiple spaces into one", () => { const profile = { displayName: "Alice Bob", handle: "alice.bsky.social", }; assert.deepEqual(getDisplayName(profile), "Alice Bob"); });
it("should collapse spaces with zero-width spaces", () => { const profile = { displayName: "Alice \u200B Bob", handle: "alice.bsky.social", }; assert.deepEqual(getDisplayName(profile), "Alice Bob"); });
it("should handle all sanitizations together", () => { const profile = { displayName: " \u2705Alice\u0000 Bob\u202E ", handle: "alice.bsky.social", }; assert.deepEqual(getDisplayName(profile), "Alice Bob"); });
it("should return 'Deleted Account' for missing.invalid handle", () => { const profile = { handle: "missing.invalid" }; assert.deepEqual(getDisplayName(profile), "Deleted Account"); });
it("should return 'Invalid Handle' for handle.invalid handle", () => { const profile = { handle: "handle.invalid" }; assert.deepEqual(getDisplayName(profile), "Invalid Handle"); });
it("should return handle when no displayName", () => { const profile = { handle: "alice.bsky.social" }; assert.deepEqual(getDisplayName(profile), "alice.bsky.social"); });
it("should prefer displayName over special handle fallbacks", () => { const profile = { displayName: "Still Here", handle: "missing.invalid" }; assert.deepEqual(getDisplayName(profile), "Still Here"); });});
describe("hasValidHandle", () => { it("returns true for a normal handle", () => { assert(hasValidHandle({ handle: "alice.bsky.social" })); });
it("returns false for the invalid sentinel", () => { assert(!hasValidHandle({ handle: INVALID_HANDLE })); });
it("returns false for the missing sentinel", () => { assert(!hasValidHandle({ handle: MISSING_HANDLE })); });
it("returns false when handle is absent", () => { assert(!hasValidHandle({ did: "did:plc:bob" })); });});
describe("normalizeThreadgateAllowSettings", () => { it("returns everybody when allow is undefined", () => { assert.deepEqual(normalizeThreadgateAllowSettings(undefined), [ { type: "everybody" }, ]); });
it("returns everybody when allow is null", () => { assert.deepEqual(normalizeThreadgateAllowSettings(null), [ { type: "everybody" }, ]); });
it("returns nobody when allow is empty array", () => { assert.deepEqual(normalizeThreadgateAllowSettings([]), [ { type: "nobody" }, ]); });
it("maps a mention rule", () => { assert.deepEqual( normalizeThreadgateAllowSettings([ { $type: "app.bsky.feed.threadgate#mentionRule" }, ]), [{ type: "mention" }], ); });
it("maps follower and following rules", () => { assert.deepEqual( normalizeThreadgateAllowSettings([ { $type: "app.bsky.feed.threadgate#followerRule" }, { $type: "app.bsky.feed.threadgate#followingRule" }, ]), [{ type: "followers" }, { type: "following" }], ); });
it("maps a list rule to its raw uri", () => { const listUri = "at://did:plc:abc/app.bsky.graph.list/123"; assert.deepEqual( normalizeThreadgateAllowSettings([ { $type: "app.bsky.feed.threadgate#listRule", list: listUri }, ]), [{ type: "list", list: listUri }], ); });
it("keeps unknown rules verbatim for round-tripping", () => { const rule = { $type: "app.bsky.feed.threadgate#futureRule", extra: 1 }; assert.deepEqual(normalizeThreadgateAllowSettings([rule]), [ { type: "unknown", raw: rule }, ]); });});
describe("buildThreadgateAllowFromNormalized", () => { it("returns null for everybody", () => { assert.deepEqual( buildThreadgateAllowFromNormalized([{ type: "everybody" }]), null, ); });
it("returns an empty array for nobody", () => { assert.deepEqual( buildThreadgateAllowFromNormalized([{ type: "nobody" }]), [], ); });
it("encodes mention, following, and followers rules", () => { assert.deepEqual( buildThreadgateAllowFromNormalized([ { type: "mention" }, { type: "following" }, { type: "followers" }, ]), [ { $type: "app.bsky.feed.threadgate#mentionRule" }, { $type: "app.bsky.feed.threadgate#followingRule" }, { $type: "app.bsky.feed.threadgate#followerRule" }, ], ); });
it("encodes list rules from the raw uri", () => { const listUri = "at://did:plc:abc/app.bsky.graph.list/zzz"; assert.deepEqual( buildThreadgateAllowFromNormalized([{ type: "list", list: listUri }]), [{ $type: "app.bsky.feed.threadgate#listRule", list: listUri }], ); });
it("re-emits unknown rules verbatim", () => { const rule = { $type: "app.bsky.feed.threadgate#futureRule", extra: 1 }; assert.deepEqual( buildThreadgateAllowFromNormalized([{ type: "unknown", raw: rule }]), [rule], ); });
it("round-trips every wire tri-state through decode(encode)", () => { const wireValues = [ undefined, [], [ { $type: "app.bsky.feed.threadgate#mentionRule" }, { $type: "app.bsky.feed.threadgate#followerRule" }, { $type: "app.bsky.feed.threadgate#followingRule" }, { $type: "app.bsky.feed.threadgate#listRule", list: "at://did:plc:abc/app.bsky.graph.list/zzz", }, { $type: "app.bsky.feed.threadgate#futureRule", extra: 1 }, ], ]; for (const allow of wireValues) { const encoded = buildThreadgateAllowFromNormalized( normalizeThreadgateAllowSettings(allow), ); assert.deepEqual(encoded, allow === undefined ? null : allow); } });});
describe("postgate embedding rule helpers", () => { const disableRule = { $type: "app.bsky.feed.postgate#disableRule" };
it("buildPostgateEmbeddingRules maps the quotes-enabled boolean", () => { assert.deepEqual(buildPostgateEmbeddingRules(true), null); assert.deepEqual(buildPostgateEmbeddingRules(false), [disableRule]); });
it("hasDisableEmbeddingRule detects the disable rule", () => { assert.deepEqual(hasDisableEmbeddingRule(null), false); assert.deepEqual(hasDisableEmbeddingRule(undefined), false); assert.deepEqual(hasDisableEmbeddingRule([]), false); assert.deepEqual(hasDisableEmbeddingRule([disableRule]), true); assert.deepEqual( hasDisableEmbeddingRule([{ $type: "app.bsky.feed.postgate#other" }]), false, ); });});
describe("isEmptyPost", () => { it("should return true for blocked posts", () => { const post = { $type: "app.bsky.feed.defs#blockedPost", uri: "at://x" }; assert.deepEqual(isEmptyPost(post), true); });
it("should return true for not-found posts", () => { const post = { $type: "app.bsky.feed.defs#notFoundPost", uri: "at://x" }; assert.deepEqual(isEmptyPost(post), true); });
it("should return true for unavailable posts", () => { const post = createUnavailablePost("at://x"); assert.deepEqual(isEmptyPost(post), true); });
it("should return false for normal post views", () => { const post = { $type: "app.bsky.feed.defs#postView", uri: "at://x" }; assert.deepEqual(isEmptyPost(post), false); });});
describe("canReplyToPost", () => { it("should return true for a normal post view with no restrictions", () => { const post = { $type: "app.bsky.feed.defs#postView", uri: "at://x", viewer: {}, }; assert.deepEqual(canReplyToPost(post), true); });
it("should return false for a blocked post", () => { const post = { $type: "app.bsky.feed.defs#blockedPost", uri: "at://x" }; assert.deepEqual(canReplyToPost(post), false); });
it("should return false for a not-found post", () => { const post = { $type: "app.bsky.feed.defs#notFoundPost", uri: "at://x" }; assert.deepEqual(canReplyToPost(post), false); });
it("should return false for an unavailable post", () => { const post = createUnavailablePost("at://x"); assert.deepEqual(canReplyToPost(post), false); });
it("should return false when viewer.replyDisabled is true", () => { const post = { $type: "app.bsky.feed.defs#postView", uri: "at://x", viewer: { replyDisabled: true }, }; assert.deepEqual(canReplyToPost(post), false); });
it("should return true when viewer is missing", () => { const post = { $type: "app.bsky.feed.defs#postView", uri: "at://x" }; assert.deepEqual(canReplyToPost(post), true); });});
describe("transformNestedQuotes", () => { const makeQuote = (uri, nestedQuote) => { const quote = { uri }; if (nestedQuote) { quote.embeds = [ { $type: "app.bsky.embed.record#view", record: nestedQuote }, ]; } return quote; };
it("returns the post unchanged when there is no quote", () => { const post = { uri: "post" }; const result = transformNestedQuotes(post, () => ({ touched: true })); assert.deepEqual(result, post); });
it("leaves the root post untouched but transforms the direct quote", () => { const post = { uri: "post", flag: "root", embed: { $type: "app.bsky.embed.record#view", record: makeQuote("quote"), }, }; const result = transformNestedQuotes(post, (quotedPost) => ({ ...quotedPost, touched: true, })); assert.deepEqual(result.uri, "post"); assert.deepEqual(result.flag, "root"); assert.deepEqual(result.embed.record, { uri: "quote", touched: true }); });
it("transforms both the direct and nested quote (two levels)", () => { const post = { uri: "post", embed: { $type: "app.bsky.embed.record#view", record: makeQuote("quote", makeQuote("nested")), }, }; const calls = []; const result = transformNestedQuotes(post, (quotedPost) => { calls.push(quotedPost.uri); return { ...quotedPost, touched: true }; }); assert.deepEqual(calls, ["quote", "nested"]); assert.deepEqual(result.embed.record.touched, true); assert.deepEqual(result.embed.record.embeds[0].record, { uri: "nested", touched: true, }); });
it("does not recurse into a third level of nesting", () => { const deepest = makeQuote("deepest"); const nested = makeQuote("nested", deepest); const post = { uri: "post", embed: { $type: "app.bsky.embed.record#view", record: makeQuote("quote", nested), }, }; const seen = []; transformNestedQuotes(post, (quotedPost) => { seen.push(quotedPost.uri); return quotedPost; }); assert.deepEqual(seen, ["quote", "nested"]); });
it("returns the same post when the transform leaves quotes unchanged", () => { const post = { uri: "post", embed: { $type: "app.bsky.embed.record#view", record: makeQuote("quote", makeQuote("nested")), }, }; const result = transformNestedQuotes(post, (quotedPost) => quotedPost); assert(result === post); });
it("does not mutate the input post", () => { const originalNested = { uri: "nested" }; const originalQuote = { uri: "quote", embeds: [{ $type: "app.bsky.embed.record#view", record: originalNested }], }; const post = { uri: "post", embed: { $type: "app.bsky.embed.record#view", record: originalQuote }, }; transformNestedQuotes(post, (quotedPost) => ({ ...quotedPost, touched: true, })); assert.deepEqual(originalQuote.touched, undefined); assert.deepEqual(originalNested.touched, undefined); assert.deepEqual(post.embed.record, originalQuote); });});
describe("getInteractionTimestamp", () => { it("should return sentAt for message views", () => { const timestamp = getInteractionTimestamp({ $type: "chat.bsky.convo.defs#messageView", sentAt: "2026-06-11T01:00:00.000Z", }); assert.deepEqual(timestamp, "2026-06-11T01:00:00.000Z"); });
it("should return sentAt for system message views", () => { const timestamp = getInteractionTimestamp({ $type: "chat.bsky.convo.defs#systemMessageView", sentAt: "2026-06-11T02:00:00.000Z", }); assert.deepEqual(timestamp, "2026-06-11T02:00:00.000Z"); });
it("should return reaction createdAt for message-and-reaction views", () => { const timestamp = getInteractionTimestamp({ $type: "chat.bsky.convo.defs#messageAndReactionView", reaction: { createdAt: "2026-06-11T03:00:00.000Z" }, }); assert.deepEqual(timestamp, "2026-06-11T03:00:00.000Z"); });});
describe("getGroupConvoDetails", () => { const groupKind = { $type: "chat.bsky.convo.defs#groupConvo", name: "Test Group", memberCount: 3, memberLimit: 10, lockStatus: "unlocked", createdAt: "2026-06-01T00:00:00.000Z", };
it("should return the kind object for group convos", () => { assert.deepEqual( getGroupConvoDetails({ id: "c1", kind: groupKind }), groupKind, ); });
it("should return null for direct convos", () => { const directConvo = { id: "c2", kind: { $type: "chat.bsky.convo.defs#directConvo" }, }; assert.deepEqual(getGroupConvoDetails(directConvo), null); });
it("should return null when kind is missing", () => { assert.deepEqual(getGroupConvoDetails({ id: "c3" }), null); });});
describe("isGroupConvo", () => { it("should detect group convos", () => { const convo = { id: "c1", kind: { $type: "chat.bsky.convo.defs#groupConvo", name: "Test Group" }, }; assert.deepEqual(isGroupConvo(convo), true); });
it("should reject direct and untyped convos", () => { assert.deepEqual( isGroupConvo({ kind: { $type: "chat.bsky.convo.defs#directConvo" } }), false, ); assert.deepEqual(isGroupConvo({}), false); });});
describe("getGroupConvoOwner", () => { function groupMember(did, role) { return { did, handle: `${did.split(":").pop()}.bsky.social`, kind: { $type: "chat.bsky.actor.defs#groupConvoMember", role }, }; }
it("should find the member with the owner role", () => { const owner = groupMember("did:plc:alice", "owner"); const convo = { members: [groupMember("did:plc:me", "standard"), owner], }; assert.deepEqual(getGroupConvoOwner(convo), owner); });
it("should return null when the owner has left", () => { const convo = { members: [ groupMember("did:plc:me", "standard"), groupMember("did:plc:bob", "standard"), ], }; assert.deepEqual(getGroupConvoOwner(convo), null); });
it("should ignore members without a group member kind", () => { const convo = { members: [ { did: "did:plc:me", handle: "me.bsky.social" }, { did: "did:plc:bob", handle: "bob.bsky.social", kind: { role: "owner" }, }, ], }; assert.deepEqual(getGroupConvoOwner(convo), null); });});
describe("getSystemMessageDisplayText", () => { function systemMessage(dataType, data = {}) { return { $type: "chat.bsky.convo.defs#systemMessageView", id: "sys-1", data: { $type: `chat.bsky.convo.defs#${dataType}`, ...data }, }; }
it("should use the member name for member events when provided", () => { assert.deepEqual( getSystemMessageDisplayText(systemMessage("systemMessageDataAddMember"), { memberName: "Alice", }), "Alice was added to the group", ); assert.deepEqual( getSystemMessageDisplayText( systemMessage("systemMessageDataMemberLeave"), { memberName: "Alice" }, ), "Alice left the group", ); });
it("should fall back to anonymous copy for member events without a name", () => { assert.deepEqual( getSystemMessageDisplayText(systemMessage("systemMessageDataAddMember")), "Someone was added to the group", ); });
it("should include the new name for edit-group events", () => { assert.deepEqual( getSystemMessageDisplayText( systemMessage("systemMessageDataEditGroup", { oldName: "Old Club", newName: "Book Club", }), ), "Chat title changed to Book Club", ); });
it("should use generic copy for edit-group events without a new name", () => { assert.deepEqual( getSystemMessageDisplayText(systemMessage("systemMessageDataEditGroup")), "Chat title changed", ); });
it("should ignore memberName for non-member events", () => { assert.deepEqual( getSystemMessageDisplayText(systemMessage("systemMessageDataLockConvo"), { memberName: "Alice", }), "Chat locked", ); });
it("should fall back to generic copy for unknown kinds", () => { assert.deepEqual( getSystemMessageDisplayText(systemMessage("systemMessageDataFuture")), "Chat updated", ); });});
describe("getConvoPreviewText", () => { const currentUser = { did: "did:plc:me", handle: "me.bsky.social" }; const alice = { did: "did:plc:alice", handle: "alice.bsky.social", displayName: "Alice", }; const groupConvo = { id: "group-1", members: [currentUser, alice], kind: { $type: "chat.bsky.convo.defs#groupConvo", name: "Book Club" }, }; const directConvo = { id: "convo-1", members: [currentUser, alice] };
function messageView({ text, senderDid }) { return { $type: "chat.bsky.convo.defs#messageView", id: "msg-1", text, sender: { did: senderDid }, }; }
it("should prefix group messages with the sender name", () => { assert.deepEqual( getConvoPreviewText(messageView({ text: "hi", senderDid: alice.did }), { currentUser, convo: groupConvo, profiles: groupConvo.members, }), "Alice: hi", ); assert.deepEqual( getConvoPreviewText( messageView({ text: "hi", senderDid: currentUser.did }), { currentUser, convo: groupConvo, profiles: groupConvo.members }, ), "You: hi", ); });
it("should fall back to Someone for unknown group senders", () => { assert.deepEqual( getConvoPreviewText( messageView({ text: "hi", senderDid: "did:plc:stranger" }), { currentUser, convo: groupConvo, profiles: groupConvo.members }, ), "Someone: hi", ); });
it("should resolve senders from the passed profiles", () => { assert.deepEqual( getConvoPreviewText( messageView({ text: "hi", senderDid: "did:plc:stranger" }), { currentUser, convo: groupConvo, profiles: [ ...groupConvo.members, { did: "did:plc:stranger", handle: "stranger.bsky.social", displayName: "Stranger", }, ], }, ), "Stranger: hi", ); });
it("should only prefix own messages in direct convos", () => { assert.deepEqual( getConvoPreviewText(messageView({ text: "hi", senderDid: alice.did }), { currentUser, convo: directConvo, profiles: directConvo.members, }), "hi", ); assert.deepEqual( getConvoPreviewText( messageView({ text: "hi", senderDid: currentUser.did }), { currentUser, convo: directConvo, profiles: directConvo.members }, ), "You: hi", ); });
it("should fall back to a generic label for embed-only messages", () => { const embedMessage = { ...messageView({ text: "", senderDid: alice.did }), embed: { $type: "app.bsky.embed.images#view" }, }; assert.deepEqual( getConvoPreviewText(embedMessage, { currentUser, convo: directConvo, profiles: directConvo.members, }), "(embedded content)", ); assert.deepEqual( getConvoPreviewText( { ...embedMessage, sender: { did: currentUser.did } }, { currentUser, convo: directConvo, profiles: directConvo.members }, ), "You: (embedded content)", ); assert.deepEqual( getConvoPreviewText(embedMessage, { currentUser, convo: groupConvo, profiles: groupConvo.members, }), "Alice: (embedded content)", ); });
it("should label quoted-post embeds distinctly", () => { const recordEmbedMessage = { ...messageView({ text: "", senderDid: alice.did }), embed: { $type: "app.bsky.embed.record#view" }, }; assert.deepEqual( getConvoPreviewText(recordEmbedMessage, { currentUser, convo: directConvo, profiles: directConvo.members, }), "(quoted post)", ); });
it("should describe reactions", () => { const reaction = { $type: "chat.bsky.convo.defs#messageAndReactionView", message: { text: "hello" }, reaction: { value: "👍", sender: { did: alice.did } }, }; assert.deepEqual( getConvoPreviewText(reaction, { currentUser, convo: groupConvo, profiles: groupConvo.members, }), 'Alice reacted 👍 to "hello"', ); });
it("should describe deleted messages", () => { assert.deepEqual( getConvoPreviewText( { $type: "chat.bsky.convo.defs#deletedMessageView" }, { currentUser, convo: directConvo, profiles: directConvo.members }, ), "Deleted message", ); });
it("should render system messages with the member name when resolvable", () => { const systemMessage = { $type: "chat.bsky.convo.defs#systemMessageView", data: { $type: "chat.bsky.convo.defs#systemMessageDataAddMember", member: { did: alice.did }, }, }; assert.deepEqual( getConvoPreviewText(systemMessage, { currentUser, convo: groupConvo, profiles: groupConvo.members, }), "Alice was added to the group", ); });
it("should render anonymous system messages for unknown members", () => { const systemMessage = { $type: "chat.bsky.convo.defs#systemMessageView", data: { $type: "chat.bsky.convo.defs#systemMessageDataMemberLeave", member: { did: "did:plc:stranger" }, }, }; assert.deepEqual( getConvoPreviewText(systemMessage, { currentUser, convo: groupConvo, profiles: groupConvo.members, }), "Someone left the group", ); });});
describe("getInteractionProfileDids", () => { it("should return an empty list for a missing interaction", () => { assert.deepEqual(getInteractionProfileDids(null), []); });
it("should extract the sender from a message", () => { const message = { $type: "chat.bsky.convo.defs#messageView", sender: { did: "did:plc:sender" }, }; assert.deepEqual(getInteractionProfileDids(message), ["did:plc:sender"]); });
it("should extract both senders from a reaction", () => { const reaction = { $type: "chat.bsky.convo.defs#messageAndReactionView", message: { sender: { did: "did:plc:author" } }, reaction: { value: "👍", sender: { did: "did:plc:reactor" } }, }; assert.deepEqual(getInteractionProfileDids(reaction), [ "did:plc:author", "did:plc:reactor", ]); });
it("should extract the member and adder from a system message", () => { const systemMessage = { $type: "chat.bsky.convo.defs#systemMessageView", data: { $type: "chat.bsky.convo.defs#systemMessageDataAddMember", member: { did: "did:plc:added" }, addedBy: { did: "did:plc:adder" }, }, }; assert.deepEqual(getInteractionProfileDids(systemMessage), [ "did:plc:added", "did:plc:adder", ]); });
it("should return an empty list for a deleted message", () => { assert.deepEqual( getInteractionProfileDids({ $type: "chat.bsky.convo.defs#deletedMessageView", }), [], ); });});
describe("groupReactions", () => { const reaction = (value, did) => ({ value, sender: { did }, createdAt: "2026-01-01T00:00:00Z", });
it("groups by emoji value, preserving first-seen order", () => { const groups = groupReactions([ reaction("❤️", "did:plc:a"), reaction("👍", "did:plc:b"), reaction("❤️", "did:plc:b"), ]); assert.deepEqual(groups.length, 2); assert.deepEqual(groups[0].value, "❤️"); assert.deepEqual(groups[0].count, 2); assert.deepEqual(groups[0].senders.length, 2); assert.deepEqual(groups[0].senders[0].did, "did:plc:a"); assert.deepEqual(groups[0].senders[1].did, "did:plc:b"); assert.deepEqual(groups[1].value, "👍"); assert.deepEqual(groups[1].count, 1); });
it("returns an empty array for empty or missing input", () => { assert.deepEqual(groupReactions([]).length, 0); assert.deepEqual(groupReactions(null).length, 0); assert.deepEqual(groupReactions(undefined).length, 0); });
it("keeps each sender entry even when the same user reacts twice with one emoji", () => { const groups = groupReactions([ reaction("❤️", "did:plc:a"), reaction("❤️", "did:plc:a"), ]); assert.deepEqual(groups.length, 1); assert.deepEqual(groups[0].count, 2); assert.deepEqual(groups[0].senders.length, 2); });});
describe("isInAppLinkHostname", () => { it("accepts the static in-app domains", () => { assert.deepEqual(isInAppLinkHostname("bsky.app"), true); assert.deepEqual(isInAppLinkHostname("impro.social"), true); assert.deepEqual(isInAppLinkHostname("dev.impro.social"), true); });
it("accepts the current hostname even when it's not in the static domain list", () => { // window.location.hostname is "localhost" in tests, which is not in // IN_APP_LINK_DOMAINS -- this exercises the same-origin fallback. assert(!IN_APP_LINK_DOMAINS.includes("localhost")); assert.deepEqual(isInAppLinkHostname("localhost"), true); });
it("rejects other hostnames", () => { assert.deepEqual(isInAppLinkHostname("example.com"), false); assert.deepEqual(isInAppLinkHostname("evil-impro.social"), false); });});
describe("getInviteCodeFromUrl", () => { it("extracts code from absolute bsky.app URL", () => { assert.deepEqual( getInviteCodeFromUrl("https://bsky.app/chat/abcd1234"), "abcd1234", ); });
it("extracts code from relative path", () => { assert.deepEqual(getInviteCodeFromUrl("/chat/abcd1234"), "abcd1234"); });
it("ignores query and hash", () => { assert.deepEqual( getInviteCodeFromUrl("/chat/abcd1234?ref=x#y"), "abcd1234", ); });
it("rejects non-bsky hosts", () => { assert.deepEqual( getInviteCodeFromUrl("https://example.com/chat/abcd1234"), null, ); });
it("accepts impro.social hosts", () => { assert.deepEqual( getInviteCodeFromUrl("https://impro.social/chat/abcd1234"), "abcd1234", ); assert.deepEqual( getInviteCodeFromUrl("https://dev.impro.social/chat/abcd1234"), "abcd1234", ); });
it("accepts a link to the current origin even when it's not in the static domain list", () => { // window.location.hostname is "localhost" in tests, which is not in // IN_APP_LINK_DOMAINS -- this exercises the same-origin fallback. assert(!IN_APP_LINK_DOMAINS.includes("localhost")); assert.deepEqual( getInviteCodeFromUrl("http://localhost/chat/abcd1234"), "abcd1234", ); });
it("rejects malformed codes", () => { assert.deepEqual(getInviteCodeFromUrl("/chat/short"), null); assert.deepEqual(getInviteCodeFromUrl("/chat/!!!!!!!!"), null); });
it("rejects unrelated paths", () => { assert.deepEqual(getInviteCodeFromUrl("/profile/foo"), null); assert.deepEqual(getInviteCodeFromUrl(""), null); assert.deepEqual(getInviteCodeFromUrl(null), null); });});
describe("isInviteLinkUrl", () => { it("is true for valid invite URLs", () => { assert.deepEqual(isInviteLinkUrl("https://bsky.app/chat/abcd1234"), true); assert.deepEqual(isInviteLinkUrl("/chat/abcd1234"), true); });
it("is false otherwise", () => { assert.deepEqual(isInviteLinkUrl("https://bsky.app/profile/x"), false); assert.deepEqual(isInviteLinkUrl(""), false); });});
describe("getJoinLinkCodeFromEmbed", () => { it("returns the code from a chat invite view embed", () => { assert.deepEqual( getJoinLinkCodeFromEmbed({ $type: "chat.bsky.embed.joinLink#view", joinLinkPreview: { code: "abcd1234" }, }), "abcd1234", ); });
it("returns null for a chat invite view embed without a code", () => { assert.deepEqual( getJoinLinkCodeFromEmbed({ $type: "chat.bsky.embed.joinLink#view", joinLinkPreview: { $type: "chat.bsky.group.defs#disabledJoinLinkPreviewView", }, }), null, ); });
it("returns the code from an external embed whose URI is an invite link", () => { assert.deepEqual( getJoinLinkCodeFromEmbed({ $type: "app.bsky.embed.external#view", external: { uri: "https://bsky.app/chat/abcd1234" }, }), "abcd1234", ); });
it("returns null for an external embed pointing elsewhere", () => { assert.deepEqual( getJoinLinkCodeFromEmbed({ $type: "app.bsky.embed.external#view", external: { uri: "https://example.com" }, }), null, ); });
it("returns null for unrelated embed types and falsy input", () => { assert.deepEqual( getJoinLinkCodeFromEmbed({ $type: "app.bsky.embed.images#view" }), null, ); assert.deepEqual(getJoinLinkCodeFromEmbed(null), null); assert.deepEqual(getJoinLinkCodeFromEmbed(undefined), null); });});
describe("getJoinLinkCodesFromPosts", () => { it("collects codes from joinLink and external invite embeds", () => { const posts = [ { embed: { $type: "chat.bsky.embed.joinLink#view", joinLinkPreview: { code: "aaaaaaa" }, }, }, { embed: { $type: "app.bsky.embed.external#view", external: { uri: "https://bsky.app/chat/bbbbbbb" }, }, }, ]; assert.deepEqual(getJoinLinkCodesFromPosts(posts), ["aaaaaaa", "bbbbbbb"]); });
it("skips posts without an embed or with unrelated embeds", () => { const posts = [ { embed: null }, { embed: { $type: "app.bsky.embed.images#view" } }, { embed: undefined }, undefined, ]; assert.deepEqual(getJoinLinkCodesFromPosts(posts), []); });
it("returns an empty array for null/undefined input", () => { assert.deepEqual(getJoinLinkCodesFromPosts(null), []); assert.deepEqual(getJoinLinkCodesFromPosts(undefined), []); });});
describe("getJoinLinkCodesFromMessages", () => { it("collects codes from message join link embeds", () => { const messages = [ { embed: { $type: "chat.bsky.embed.joinLink#view", joinLinkPreview: { code: "aaaaaaa" }, }, }, { embed: null }, { embed: { $type: "app.bsky.embed.external#view", external: { uri: "https://bsky.app/chat/bbbbbbb" }, }, }, ]; assert.deepEqual(getJoinLinkCodesFromMessages(messages), [ "aaaaaaa", "bbbbbbb", ]); });
it("returns an empty array for null/undefined input", () => { assert.deepEqual(getJoinLinkCodesFromMessages(null), []); assert.deepEqual(getJoinLinkCodesFromMessages(undefined), []); });});
describe("attachJoinLinkPreviewToEmbed", () => { const fresh = { $type: "chat.bsky.group.defs#joinLinkPreviewView", code: "abcd1234", name: "Updated", };
it("returns null for unrelated embeds", () => { assert.deepEqual( attachJoinLinkPreviewToEmbed( { $type: "app.bsky.embed.images#view" }, fresh, ), null, ); });
it("returns null when the cached preview is the same reference", () => { assert.deepEqual( attachJoinLinkPreviewToEmbed( { $type: "chat.bsky.embed.joinLink#view", joinLinkPreview: fresh }, fresh, ), null, ); });
it("attaches a fresh preview to a joinLink embed", () => { const updated = attachJoinLinkPreviewToEmbed( { $type: "chat.bsky.embed.joinLink#view", joinLinkPreview: { code: "abcd1234", name: "Stale" }, }, fresh, ); assert.deepEqual(updated.$type, "chat.bsky.embed.joinLink#view"); assert.deepEqual(updated.joinLinkPreview, fresh); });
it("upgrades an external invite embed into a joinLink embed", () => { const updated = attachJoinLinkPreviewToEmbed( { $type: "app.bsky.embed.external#view", external: { uri: "https://bsky.app/chat/abcd1234" }, }, fresh, ); assert.deepEqual(updated.$type, "chat.bsky.embed.joinLink#view"); assert.deepEqual(updated.joinLinkPreview, fresh); });});
function makeJoinLinkPreview(overrides = {}) { return { $type: "chat.bsky.group.defs#joinLinkPreviewView", code: "abcdefg", name: "Friends of Bsky", memberCount: 5, memberLimit: 50, joinRule: "open", requireApproval: false, owner: { did: "did:plc:owner", handle: "owner.test", viewer: {} }, viewer: {}, ...overrides, };}
describe("isAvailableJoinLinkPreview", () => { it("returns true only for the available variant", () => { assert(isAvailableJoinLinkPreview(makeJoinLinkPreview())); assert( !isAvailableJoinLinkPreview({ $type: "chat.bsky.group.defs#disabledJoinLinkPreviewView", }), ); });});
describe("getPostsFromPostThread", () => { it("should extract and deduplicate posts from post thread", () => { const mockPostThread = { post: { uri: "main-post", content: "Main post" }, parent: { post: { uri: "parent2", content: "Parent 2" }, parent: { post: { uri: "parent1", content: "Parent 1" }, }, }, replies: [ { $type: "app.bsky.feed.defs#threadViewPost", post: { uri: "reply1", content: "Reply 1" }, }, { $type: "app.bsky.feed.defs#threadViewPost", post: { uri: "reply2", content: "Reply 2" }, }, ], };
const result = getPostsFromPostThread(mockPostThread);
assert.deepEqual(result.length, 5); assert.deepEqual(result[0], { uri: "main-post", content: "Main post" }); assert.deepEqual(result[1], { uri: "parent1", content: "Parent 1" }); assert.deepEqual(result[2], { uri: "parent2", content: "Parent 2" }); assert.deepEqual(result[3], { uri: "reply1", content: "Reply 1" }); assert.deepEqual(result[4], { uri: "reply2", content: "Reply 2" }); });
it("should handle thread with no parents or replies", () => { const mockPostThread = { post: { uri: "lonely-post", content: "All alone" }, };
const result = getPostsFromPostThread(mockPostThread);
assert.deepEqual(result.length, 1); assert.deepEqual(result[0], { uri: "lonely-post", content: "All alone" }); });
it("should handle duplicate posts across thread parts", () => { const mockPostThread = { post: { uri: "main-post", content: "Main post" }, parent: { post: { uri: "parent1", content: "Parent 1" }, }, replies: [ { $type: "app.bsky.feed.defs#threadViewPost", post: { uri: "parent1", content: "Parent 1" }, }, { $type: "app.bsky.feed.defs#threadViewPost", post: { uri: "reply1", content: "Reply 1" }, }, ], };
const result = getPostsFromPostThread(mockPostThread);
assert.deepEqual(result.length, 3); assert.deepEqual(result[0], { uri: "main-post", content: "Main post" }); assert.deepEqual(result[1], { uri: "parent1", content: "Parent 1" }); assert.deepEqual(result[2], { uri: "reply1", content: "Reply 1" }); });
it("should filter out blocked replies", () => { const mockPostThread = { post: { uri: "main-post", content: "Main post" }, replies: [ { $type: "app.bsky.feed.defs#threadViewPost", post: { uri: "reply1", content: "Reply 1" }, }, { $type: "app.bsky.feed.defs#blockedPost", uri: "blocked-reply", }, { $type: "app.bsky.feed.defs#threadViewPost", post: { uri: "reply2", content: "Reply 2" }, }, ], };
const result = getPostsFromPostThread(mockPostThread);
assert.deepEqual(result.length, 3); assert.deepEqual(result[0], { uri: "main-post", content: "Main post" }); assert.deepEqual(result[1], { uri: "reply1", content: "Reply 1" }); assert.deepEqual(result[2], { uri: "reply2", content: "Reply 2" }); });});
describe("getPostsFromFeed", () => { it("should extract posts from simple feed", () => { const mockFeed = { feed: [ { post: { uri: "post1", content: "Post 1" } }, { post: { uri: "post2", content: "Post 2" } }, ], };
const result = getPostsFromFeed(mockFeed);
assert.deepEqual(result.length, 2); assert.deepEqual(result[0], { uri: "post1", content: "Post 1" }); assert.deepEqual(result[1], { uri: "post2", content: "Post 2" }); });
it("should extract posts from feed with reply context", () => { const mockFeed = { feed: [ { post: { uri: "post1", content: "Post 1" } }, { post: { uri: "post2", content: "Reply post" }, reply: { root: { $type: "app.bsky.feed.defs#postView", uri: "root1", content: "Root post", }, parent: { $type: "app.bsky.feed.defs#postView", uri: "parent1", content: "Parent post", }, }, }, ], };
const result = getPostsFromFeed(mockFeed);
assert.deepEqual(result.length, 4); assert.deepEqual(result[0].uri, "post1"); assert.deepEqual(result[1].uri, "post2"); assert.deepEqual(result[2].uri, "root1"); assert.deepEqual(result[3].uri, "parent1"); });
it("should handle feed items without reply context", () => { const mockFeed = { feed: [ { post: { uri: "post1", content: "Post 1" }, reply: undefined, }, { post: { uri: "post2", content: "Post 2" }, }, ], };
const result = getPostsFromFeed(mockFeed);
assert.deepEqual(result.length, 2); assert.deepEqual(result[0], { uri: "post1", content: "Post 1" }); assert.deepEqual(result[1], { uri: "post2", content: "Post 2" }); });
it("should handle empty feed", () => { const mockFeed = { feed: [] };
const result = getPostsFromFeed(mockFeed);
assert.deepEqual(result.length, 0); });
it("should handle duplicates in feed", () => { const mockFeed = { feed: [ { post: { uri: "post1", content: "Post 1" }, reply: { root: { $type: "app.bsky.feed.defs#postView", uri: "root1", content: "Root post", }, parent: { $type: "app.bsky.feed.defs#postView", uri: "post1", content: "Post 1", }, }, }, ], };
const result = getPostsFromFeed(mockFeed);
assert.deepEqual(result.length, 2); assert.deepEqual(result[0].uri, "post1"); assert.deepEqual(result[1].uri, "root1"); });
it("should handle mixed feed items", () => { const mockFeed = { feed: [ { post: { uri: "post1", content: "Simple post" } }, { post: { uri: "post2", content: "Reply post" }, reply: { root: { $type: "app.bsky.feed.defs#postView", uri: "root1", content: "Root", }, parent: { $type: "app.bsky.feed.defs#postView", uri: "parent1", content: "Parent", }, }, }, { post: { uri: "post3", content: "Another simple post" } }, ], };
const result = getPostsFromFeed(mockFeed);
assert.deepEqual(result.length, 5); assert.deepEqual(result[0].uri, "post1"); assert.deepEqual(result[1].uri, "post2"); assert.deepEqual(result[2].uri, "root1"); assert.deepEqual(result[3].uri, "parent1"); assert.deepEqual(result[4].uri, "post3"); });});
describe("isVideoLink", () => { it("returns true for youtube watch links", () => { assert(isVideoLink("https://www.youtube.com/watch?v=dQw4w9WgXcQ")); assert(isVideoLink("https://youtube.com/watch?v=dQw4w9WgXcQ")); assert(isVideoLink("https://m.youtube.com/watch?v=dQw4w9WgXcQ")); });
it("returns true for youtu.be links", () => { assert(isVideoLink("https://youtu.be/dQw4w9WgXcQ")); });
it("returns true for youtube shorts and live links", () => { assert(isVideoLink("https://www.youtube.com/shorts/dQw4w9WgXcQ")); assert(isVideoLink("https://www.youtube.com/live/dQw4w9WgXcQ")); });
it("returns false for youtube pages that are not videos", () => { assert(!isVideoLink("https://www.youtube.com/")); assert(!isVideoLink("https://www.youtube.com/results?search_query=cats")); assert(!isVideoLink("https://www.youtube.com/@somechannel")); });
it("returns false for non-youtube links and invalid input", () => { assert(!isVideoLink("https://example.com/watch?v=dQw4w9WgXcQ")); assert(!isVideoLink("not a url")); assert(!isVideoLink(null)); });});
describe("getImagesFromPost", () => { const galleryView = { $type: "app.bsky.embed.gallery#view", items: [ { $type: "app.bsky.embed.gallery#viewImage", thumbnail: "thumb1.jpg", fullsize: "full1.jpg", alt: "first", }, { $type: "app.bsky.embed.gallery#viewSomethingElse" }, ], };
it("returns legacy images embed images", () => { const images = [{ thumb: "thumb.jpg", fullsize: "full.jpg", alt: "" }]; assert.deepEqual( getImagesFromPost({ embed: { $type: "app.bsky.embed.images#view", images }, }), images, ); });
it("maps gallery items to images with a thumb field", () => { assert.deepEqual(getImagesFromPost({ embed: galleryView }), [ { thumb: "thumb1.jpg", $type: "app.bsky.embed.gallery#viewImage", fullsize: "full1.jpg", alt: "first", }, ]); });
it("reads a gallery nested in recordWithMedia", () => { const images = getImagesFromPost({ embed: { $type: "app.bsky.embed.recordWithMedia#view", media: galleryView, record: {}, }, }); assert.deepEqual( images.map((image) => image.thumb), ["thumb1.jpg"], ); });
it("returns an empty list for posts without images", () => { assert.deepEqual(getImagesFromPost({ embed: null }), []); });});
describe("getImagesFromDraftPost", () => { it("reads embedGallery items", () => { const images = getImagesFromDraftPost({ embedGallery: { items: [{ localRef: { path: "image:a" } }] }, }); assert.deepEqual(images.length, 1); });
it("reads legacy embedImages", () => { const images = getImagesFromDraftPost({ embedImages: [{ localRef: { path: "image:a" }, alt: "old" }], }); assert.deepEqual(images[0].alt, "old"); });
it("skips entries without a localRef path", () => { const images = getImagesFromDraftPost({ embedGallery: { items: [{ localRef: {} }, {}] }, }); assert.deepEqual(images, []); });});
describe("getLocalRefsFromDraft", () => { it("collects image and video refs across posts, including legacy images", () => { const refs = getLocalRefsFromDraft({ posts: [ { embedGallery: { items: [{ localRef: { path: "image:a" } }] }, embedVideos: [{ localRef: { path: "video:video/mp4:v.mp4" } }], }, { embedImages: [{ localRef: { path: "image:b" } }] }, ], }); assert.deepEqual(refs, ["image:a", "video:video/mp4:v.mp4", "image:b"]); });
it("returns an empty array for drafts with no media", () => { assert.deepEqual(getLocalRefsFromDraft({ posts: [{ text: "hi" }] }), []); });});
describe("createNestedThreadViewPost", () => { it("should nest each post under the previous one", () => { const posts = [{ uri: "p1" }, { uri: "p2" }, { uri: "p3" }]; assert.deepEqual(createNestedThreadViewPost(posts), { $type: "app.bsky.feed.defs#threadViewPost", post: posts[0], replies: [ { $type: "app.bsky.feed.defs#threadViewPost", post: posts[1], replies: [ { $type: "app.bsky.feed.defs#threadViewPost", post: posts[2], replies: [], }, ], }, ], }); });
it("should return null for an empty list", () => { assert.equal(createNestedThreadViewPost([]), null); });});
describe("getPostNumberingForPostThread", () => { const opDid = "did:plc:op"; const otherDid = "did:plc:other";
function threadPost(rkey, did, { parent, root, replyCount = 0 } = {}) { return createPost({ uri: `at://${did}/app.bsky.feed.post/${rkey}`, text: rkey, authorHandle: `${did}.test`, replyCount, ...(parent ? { reply: { parent: { uri: parent.uri, cid: parent.cid }, root: { uri: root.uri, cid: root.cid }, }, } : {}), }); }
function numberingOf(map) { return Object.fromEntries( [...map.entries()].map(([uri, numbering]) => [ uri.split("/").pop(), `${numbering.index}/${numbering.count}`, ]), ); }
it("numbers a root-anchored OP run above and below the anchor", () => { const root = threadPost("p1", opDid, { replyCount: 1 }); const second = threadPost("p2", opDid, { parent: root, root, replyCount: 2, }); const third = threadPost("p3", opDid, { parent: second, root, replyCount: 1, }); const otherReply = threadPost("r1", otherDid, { parent: second, root }); const fourth = threadPost("p4", opDid, { parent: third, root }); const thread = createThreadViewPost({ post: second, parent: createThreadViewPost({ post: root }), replies: [ createThreadViewPost({ post: otherReply, replies: [] }), createThreadViewPost({ post: third, replies: [createThreadViewPost({ post: fourth, replies: [] })], }), ], }); assert.deepEqual(numberingOf(getPostNumberingForPostThread(thread)), { p1: "1/4", p2: "2/4", p3: "3/4", p4: "4/4", }); });
it("does not number an OP sub-thread that starts below another author", () => { const root = threadPost("p1", otherDid, { replyCount: 1 }); const opPost = threadPost("o1", opDid, { parent: root, root, replyCount: 1, }); const opChild = threadPost("o2", opDid, { parent: opPost, root }); const thread = createThreadViewPost({ post: root, replies: [ createThreadViewPost({ post: opPost, replies: [createThreadViewPost({ post: opChild, replies: [] })], }), ], }); assert.deepEqual(numberingOf(getPostNumberingForPostThread(thread)), {}); });
it("stops the run at a reply by another author", () => { const root = threadPost("p1", opDid, { replyCount: 1 }); const other = threadPost("r1", otherDid, { parent: root, root, replyCount: 1, }); const opBelowOther = threadPost("o1", opDid, { parent: other, root }); const thread = createThreadViewPost({ post: root, replies: [ createThreadViewPost({ post: other, replies: [createThreadViewPost({ post: opBelowOther, replies: [] })], }), ], }); assert.deepEqual(numberingOf(getPostNumberingForPostThread(thread)), {}); });
it("returns nothing for a root with no OP replies", () => { const root = threadPost("p1", opDid); const thread = createThreadViewPost({ post: root, replies: [] }); assert.deepEqual(numberingOf(getPostNumberingForPostThread(thread)), {}); });
it("returns nothing when the run continues past the fetched depth", () => { const root = threadPost("p1", opDid, { replyCount: 1 }); const second = threadPost("p2", opDid, { parent: root, root, replyCount: 1, }); const thread = createThreadViewPost({ post: root, replies: [createThreadViewPost({ post: second })], }); assert.deepEqual(numberingOf(getPostNumberingForPostThread(thread)), {}); });
it("returns nothing when a parent in the chain is blocked or missing", () => { const root = threadPost("p1", opDid, { replyCount: 1 }); const second = threadPost("p2", opDid, { parent: root, root, replyCount: 1, }); const third = threadPost("p3", opDid, { parent: second, root }); const thread = createThreadViewPost({ post: third, parent: createThreadViewPost({ post: second, parent: { $type: "app.bsky.feed.defs#blockedPost", uri: root.uri }, }), replies: [], }); assert.deepEqual(numberingOf(getPostNumberingForPostThread(thread)), {}); });
it("numbers the run above a non-OP anchor only when the run provably ends", () => { const root = threadPost("p1", opDid, { replyCount: 1 }); const second = threadPost("p2", opDid, { parent: root, root, replyCount: 1, }); const other = threadPost("r1", otherDid, { parent: second, root }); const thread = createThreadViewPost({ post: other, parent: createThreadViewPost({ post: second, parent: createThreadViewPost({ post: root }), }), replies: [], }); assert.deepEqual(numberingOf(getPostNumberingForPostThread(thread)), { p1: "1/2", p2: "2/2", });
const busySecond = { ...second, replyCount: 3 }; const ambiguousThread = createThreadViewPost({ post: other, parent: createThreadViewPost({ post: busySecond, parent: createThreadViewPost({ post: root }), }), replies: [], }); assert.deepEqual( numberingOf(getPostNumberingForPostThread(ambiguousThread)), {}, ); });
it("follows the OP reply with the earliest rkey when the OP replied twice to one post", () => { const root = threadPost("p1", opDid, { replyCount: 2 }); const later = threadPost("3lb", opDid, { parent: root, root }); const earlier = threadPost("3la", opDid, { parent: root, root }); const thread = createThreadViewPost({ post: root, replies: [ createThreadViewPost({ post: later, replies: [] }), createThreadViewPost({ post: earlier, replies: [] }), ], }); assert.deepEqual(numberingOf(getPostNumberingForPostThread(thread)), { p1: "1/2", "3la": "2/2", }); });
it("returns nothing for a tombstone thread", () => { assert.deepEqual( getPostNumberingForPostThread({ $type: "app.bsky.feed.defs#notFoundPost", uri: "at://did:plc:op/app.bsky.feed.post/gone", }), new Map(), ); });});
describe("isCurateList", () => { it("returns true only for curate lists", () => { assert.deepEqual( isCurateList({ purpose: "app.bsky.graph.defs#curatelist" }), true, ); assert.deepEqual( isCurateList({ purpose: "app.bsky.graph.defs#referencelist" }), false, ); assert.deepEqual(isCurateList(null), false); });});
describe("isReferenceList", () => { it("returns true only for reference lists", () => { assert.deepEqual( isReferenceList({ purpose: "app.bsky.graph.defs#referencelist" }), true, ); assert.deepEqual( isReferenceList({ purpose: "app.bsky.graph.defs#curatelist" }), false, ); assert.deepEqual(isReferenceList(null), false); });});
describe("insertReplyIntoThread", () => { const newReply = createThreadViewPost({ post: createPost({ uri: "at://did:plc:me/app.bsky.feed.post/new" }), replies: [], });
function threadNode(id, replies = []) { return createThreadViewPost({ post: createPost({ uri: `at://did:plc:x/app.bsky.feed.post/${id}` }), replies, }); }
it("prepends the reply under a nested match and copies only the changed path", () => { const untouched = threadNode("untouched"); const target = threadNode("target", [threadNode("existing")]); const thread = threadNode("root", [untouched, threadNode("mid", [target])]); const updated = insertReplyIntoThread( thread, "at://did:plc:x/app.bsky.feed.post/target", newReply, ); assert.notEqual(updated, thread); assert.equal(updated.replies[0], untouched); const updatedTarget = updated.replies[1].replies[0]; assert.deepEqual(updatedTarget.replies.length, 2); assert.equal(updatedTarget.replies[0], newReply); assert.deepEqual(target.replies.length, 1); });
it("returns the same thread when no post matches", () => { const thread = threadNode("root", [threadNode("a")]); assert.equal( insertReplyIntoThread( thread, "at://did:plc:x/app.bsky.feed.post/nope", newReply, ), thread, ); });});