···615615 isAuthenticated,
616616 condensed,
617617 });
618618- // This only happens if the author is blocking the viewer
619618 case "app.bsky.embed.record#viewBlocked":
620619 return blockedQuoteTemplate();
621620 case "app.bsky.embed.record#viewDetached":
+132
tests/unit/specs/dataLayer/derived.test.js
···620620 assertEquals(result.badgeLabels, ["b"]);
621621 });
622622623623+ function makeBlockedQuotePost(viewerState) {
624624+ return {
625625+ uri: postURI,
626626+ record: { text: "quoting post" },
627627+ embed: {
628628+ $type: "app.bsky.embed.record#view",
629629+ record: {
630630+ $type: "app.bsky.embed.record#viewBlocked",
631631+ uri: "at://did:blocked/app.bsky.feed.post/q",
632632+ blocked: true,
633633+ author: { did: "did:blocked", viewer: viewerState },
634634+ },
635635+ },
636636+ };
637637+ }
638638+639639+ it("should keep a viewer-blocked quote as blocked when the quoted post is not loaded", () => {
640640+ const dataStore = new DataStore();
641641+ const { derived } = makeDerived(dataStore, {
642642+ preferences: fakePreferences(),
643643+ });
644644+ dataStore.$posts.set(
645645+ postURI,
646646+ makeBlockedQuotePost({ blocking: "at://did:me/app.bsky.graph.block/1" }),
647647+ );
648648+ const result = derived.$hydratedPosts.get(postURI);
649649+ assertEquals(
650650+ result.embed.record.$type,
651651+ "app.bsky.embed.record#viewBlocked",
652652+ );
653653+ });
654654+655655+ it("should mark a viewer-blocked quote as deleted when the post is confirmed unavailable", () => {
656656+ const dataStore = new DataStore();
657657+ const { derived } = makeDerived(dataStore, {
658658+ preferences: fakePreferences(),
659659+ });
660660+ const quotedUri = "at://did:blocked/app.bsky.feed.post/q";
661661+ dataStore.$unavailablePosts.set(quotedUri, {
662662+ $type: "social.impro.feed.defs#unavailablePost",
663663+ uri: quotedUri,
664664+ });
665665+ dataStore.$posts.set(
666666+ postURI,
667667+ makeBlockedQuotePost({ blocking: "at://did:me/app.bsky.graph.block/1" }),
668668+ );
669669+ const result = derived.$hydratedPosts.get(postURI);
670670+ assertEquals(
671671+ result.embed.record.$type,
672672+ "app.bsky.embed.record#viewNotFound",
673673+ );
674674+ });
675675+676676+ it("should mark a blocked-by quote as deleted when the post is confirmed unavailable", () => {
677677+ const dataStore = new DataStore();
678678+ const { derived } = makeDerived(dataStore, {
679679+ preferences: fakePreferences(),
680680+ });
681681+ const quotedUri = "at://did:blocked/app.bsky.feed.post/q";
682682+ dataStore.$unavailablePosts.set(quotedUri, {
683683+ $type: "social.impro.feed.defs#unavailablePost",
684684+ uri: quotedUri,
685685+ });
686686+ dataStore.$posts.set(postURI, makeBlockedQuotePost({ blockedBy: true }));
687687+ const result = derived.$hydratedPosts.get(postURI);
688688+ assertEquals(
689689+ result.embed.record.$type,
690690+ "app.bsky.embed.record#viewNotFound",
691691+ );
692692+ });
693693+694694+ it("should keep a viewer-blocked quote blocked even when the quoted post is loaded", () => {
695695+ const dataStore = new DataStore();
696696+ const { derived } = makeDerived(dataStore, {
697697+ preferences: fakePreferences(),
698698+ });
699699+ const quotedUri = "at://did:blocked/app.bsky.feed.post/q";
700700+ dataStore.$posts.set(quotedUri, {
701701+ uri: quotedUri,
702702+ cid: "cid-q",
703703+ author: { did: "did:blocked" },
704704+ record: { text: "the quoted text" },
705705+ });
706706+ dataStore.$posts.set(
707707+ postURI,
708708+ makeBlockedQuotePost({ blocking: "at://did:me/app.bsky.graph.block/1" }),
709709+ );
710710+ const result = derived.$hydratedPosts.get(postURI);
711711+ assertEquals(
712712+ result.embed.record.$type,
713713+ "app.bsky.embed.record#viewBlocked",
714714+ );
715715+ });
716716+717717+ it("should resolve a third-party-blocked quote when the quoted post is loaded", () => {
718718+ const dataStore = new DataStore();
719719+ const { derived } = makeDerived(dataStore, {
720720+ preferences: fakePreferences(),
721721+ });
722722+ const quotedUri = "at://did:blocked/app.bsky.feed.post/q";
723723+ dataStore.$posts.set(quotedUri, {
724724+ uri: quotedUri,
725725+ cid: "cid-q",
726726+ author: { did: "did:blocked" },
727727+ record: { text: "the quoted text" },
728728+ });
729729+ dataStore.$posts.set(postURI, makeBlockedQuotePost({}));
730730+ const result = derived.$hydratedPosts.get(postURI);
731731+ assertEquals(result.embed.record.$type, "app.bsky.embed.record#viewRecord");
732732+ assertEquals(result.embed.record.uri, quotedUri);
733733+ });
734734+735735+ it("should keep the quote blocked when the quoted author blocks the viewer", () => {
736736+ const dataStore = new DataStore();
737737+ const { derived } = makeDerived(dataStore, {
738738+ preferences: fakePreferences(),
739739+ });
740740+ const quotedUri = "at://did:blocked/app.bsky.feed.post/q";
741741+ dataStore.$posts.set(quotedUri, {
742742+ uri: quotedUri,
743743+ cid: "cid-q",
744744+ author: { did: "did:blocked" },
745745+ record: { text: "the quoted text" },
746746+ });
747747+ dataStore.$posts.set(postURI, makeBlockedQuotePost({ blockedBy: true }));
748748+ const result = derived.$hydratedPosts.get(postURI);
749749+ assertEquals(
750750+ result.embed.record.$type,
751751+ "app.bsky.embed.record#viewBlocked",
752752+ );
753753+ });
754754+623755 it("should return the post unchanged when there is no blocked quote to resolve", () => {
624756 const dataStore = new DataStore();
625757 const { derived } = makeDerived(dataStore, {
+62
tests/unit/specs/dataLayer/requests.test.js
···26972697 });
26982698});
2699269927002700+t.describe("_loadBlockedPosts", (it) => {
27012701+ const existingUri = "at://did:plc:blocked/app.bsky.feed.post/exists";
27022702+ const deletedUri = "at://did:plc:blocked/app.bsky.feed.post/gone";
27032703+27042704+ function setup({ getPosts = async () => [], getRecord }) {
27052705+ const mockApi = { getPosts, getRecord };
27062706+ const dataStore = new DataStore();
27072707+ const mockPreferencesProvider = {
27082708+ requirePreferences: () => Preferences.createLoggedOutPreferences(),
27092709+ };
27102710+ const requests = createRequests(
27112711+ mockApi,
27122712+ dataStore,
27132713+ mockPreferencesProvider,
27142714+ );
27152715+ return { requests, dataStore };
27162716+ }
27172717+27182718+ it("should mark a post unavailable when its record is confirmed deleted", async () => {
27192719+ const { requests, dataStore } = setup({
27202720+ getRecord: async (uri) => {
27212721+ if (uri === deletedUri) {
27222722+ throw new ApiError({
27232723+ status: 400,
27242724+ statusText: "Bad Request",
27252725+ data: { error: "RecordNotFound" },
27262726+ headers: {},
27272727+ url: "",
27282728+ });
27292729+ }
27302730+ return { uri, value: {} };
27312731+ },
27322732+ });
27332733+ await requests._loadBlockedPosts([existingUri, deletedUri]);
27342734+ assertEquals(dataStore.$unavailablePosts.get(existingUri), null);
27352735+ assert(dataStore.$unavailablePosts.get(deletedUri) !== null);
27362736+ assertEquals(dataStore.$unavailablePosts.get(deletedUri).uri, deletedUri);
27372737+ });
27382738+27392739+ it("should not mark a post unavailable when the record probe fails for other reasons", async () => {
27402740+ const { requests, dataStore } = setup({
27412741+ getRecord: async () => {
27422742+ throw new TypeError("network down");
27432743+ },
27442744+ });
27452745+ await requests._loadBlockedPosts([existingUri]);
27462746+ assertEquals(dataStore.$unavailablePosts.get(existingUri), null);
27472747+ });
27482748+27492749+ it("should not probe records for posts that getPosts returned", async () => {
27502750+ const { requests, dataStore } = setup({
27512751+ getPosts: async () => [{ uri: existingUri, record: { text: "hi" } }],
27522752+ getRecord: async () => {
27532753+ throw new Error("getRecord should not be called");
27542754+ },
27552755+ });
27562756+ await requests._loadBlockedPosts([existingUri]);
27572757+ assertEquals(dataStore.$posts.get(existingUri).record.text, "hi");
27582758+ assertEquals(dataStore.$unavailablePosts.get(existingUri), null);
27592759+ });
27602760+});
27612761+27002762await t.run();