Something went wrong. Try again.
Bluesky app fork with some witchin' additions 💫
Something went wrong. Try again.
6.1 kB · 177 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178import {QueryClient} from '@tanstack/react-query'
import {type FeedPageUnselected} from '#/state/queries/post-feed'import {type app} from '#/lexicons'import { completeRedraftedPost, replacePostInData, replaceRedraftedPost,} from './replace-redrafted-post'
const original = { uri: 'at://did:plc:alice/app.bsky.feed.post/old', author: {did: 'did:plc:alice', handle: 'alice.test'}, cid: 'bafyreia', indexedAt: '2026-09-09T00:00:00Z', record: {text: 'Original'},} as app.bsky.feed.defs.PostViewconst replacement = { ...original, uri: 'at://did:plc:alice/app.bsky.feed.post/new', record: {text: 'Edited'},} as app.bsky.feed.defs.PostView
it('replaces a feed slot while retaining order, cursor, and unrelated references', () => { const other = {...original, uri: 'at://did:plc:bob/app.bsky.feed.post/other'} const data = { pages: [{feed: [{post: other}, {post: original}], cursor: 'next'}], pageParams: [undefined], } const result = replacePostInData( data, original.uri, replacement, ) as typeof data expect(result.pages[0].feed[0]).toBe(data.pages[0].feed[0]) expect(result.pages[0].feed[1].post).toBe(replacement) expect(result.pages[0].cursor).toBe('next') expect(result.pageParams).toBe(data.pageParams) expect(data.pages[0].feed[1].post).toBe(original)})
it('updates the thread item URI alongside its post', () => { const data = {thread: [{uri: original.uri, value: {post: original}}]} expect(replacePostInData(data, original.uri, replacement)).toEqual({ thread: [{uri: replacement.uri, value: {post: replacement}}], })})
it('preserves immutable quote embeds and record references', () => { const data = {embed: {post: original}, record: {reply: original}} expect(replacePostInData(data, original.uri, replacement)).toBe(data)})
function setupFeed(feeds: app.bsky.feed.defs.FeedViewPost[][]) { const client = new QueryClient() const key = ['post-feed', 'following', {}] const data = { pages: feeds.map((feed, index) => ({ feed, cursor: `cursor-${index}`, fetchedAt: index, page: index + 1, })) as FeedPageUnselected[], pageParams: [undefined, {cursor: 'cursor-0'}], } client.setQueryData(key, data) replaceRedraftedPost(client, original.uri, replacement) return {data, result: client.getQueryData<typeof data>(key)!}}
it('replaces a redrafted post at the top of the feed and removes older copies', () => { const {data, result} = setupFeed([[{post: original}], [{post: original}]]) expect(result.pages[0].feed).toEqual([{post: replacement}]) expect(result.pages[1].feed).toEqual([]) expect(result.pages[1].cursor).toBe(data.pages[1].cursor) expect(result.pageParams).toBe(data.pageParams) expect(data.pages[0].feed[0].post).toBe(original)})
it('removes a redrafted post when there are posts above it on a previous page', () => { const other: app.bsky.feed.defs.PostView = { ...original, uri: 'at://did:plc:bob/app.bsky.feed.post/other', } const {data, result} = setupFeed([ [{post: other}], [{post: original}, {post: other}], ]) expect(result.pages[0]).toBe(data.pages[0]) expect(result.pages[1].feed).toEqual([{post: other}]) expect(result.pages[1].feed[0]).toEqual(data.pages[1].feed[1])})
it('does not duplicate a replacement that is already in the feed', () => { const {result} = setupFeed([[{post: original}], [{post: replacement}]]) expect(result.pages[0].feed).toEqual([]) expect(result.pages[1].feed).toEqual([{post: replacement}])})
it('preserves unrelated feed pages and quoted records', () => { const quoted = { ...replacement, embed: {record: original}, } as unknown as app.bsky.feed.defs.PostView const {data, result} = setupFeed([[{post: quoted}]]) expect(result).toBe(data)})
it('uses the post loaded by the composer without another appview lookup', async () => { const client = new QueryClient() const key = ['post-feed', 'following', {}] client.setQueryData(key, {pages: [{feed: [{post: original}]}]}) const getPost = jest.fn().mockRejectedValue(new Error('not indexed')) const deletePost = jest.fn().mockResolvedValue(undefined) await completeRedraftedPost({ queryClient: client, uri: original.uri, replacement, getPost, deletePost, }) expect(getPost).not.toHaveBeenCalled() expect(deletePost).toHaveBeenCalledTimes(1) expect(client.getQueryData(key)).toEqual({ pages: [{feed: [{post: replacement}]}], })})
it('waits for appview indexing before deleting the original', async () => { jest.useFakeTimers() try { const getPost = jest .fn() .mockRejectedValueOnce(new Error('not indexed')) .mockResolvedValue(replacement) const deletePost = jest.fn().mockResolvedValue(undefined) const pending = completeRedraftedPost({ queryClient: new QueryClient(), uri: original.uri, getPost, deletePost, }) expect(deletePost).not.toHaveBeenCalled() await jest.advanceTimersByTimeAsync(1000) await pending expect(getPost).toHaveBeenCalledTimes(2) expect(deletePost).toHaveBeenCalledTimes(1) } finally { jest.useRealTimers() }})
it('keeps the original when the replacement cannot be loaded', async () => { jest.useFakeTimers() try { const client = new QueryClient() const key = ['post-feed', 'following', {}] const data = {pages: [{feed: [{post: original}]}]} client.setQueryData(key, data) const getPost = jest.fn().mockRejectedValue(new Error('not indexed')) const deletePost = jest.fn().mockResolvedValue(undefined) const pending = completeRedraftedPost({ queryClient: client, uri: original.uri, getPost, deletePost, }) const assertion = expect(pending).rejects.toThrow('not indexed') await jest.advanceTimersByTimeAsync(5000) await assertion expect(getPost).toHaveBeenCalledTimes(5) expect(deletePost).not.toHaveBeenCalled() expect(client.getQueryData(key)).toBe(data) } finally { jest.useRealTimers() }})