Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/improsocial/impro
An extensible Bluesky client for web impro.social
Something went wrong. Try again.
JavaScript
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545import { describe, it } from "node:test";import assert from "node:assert/strict";import { CDN_URL } from "/js/config.js";import { avatarThumbnailUrl, buildProfileFromRecord, cdnImageUrl, getRKey, getIsLiked, isListFeed, getQuotedPost, getImagesFromDraftPost, 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";
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("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" }] }), []); });});