From 100e85f259c88f14870b4062f7114f298e43681c Mon Sep 17 00:00:00 2001 From: Grace Kind Date: Mon, 20 Jul 2026 17:34:20 -0500 Subject: [PATCH] Handle empty string feedContexts --- src/js/dataLayer/mutations.js | 4 +-- src/js/postSeenObserver.js | 2 +- src/js/templates/postFeed.template.js | 1 - src/js/views/home.view.js | 15 ++++++++- tests/unit/specs/dataLayer/mutations.test.js | 31 +++++++++++++++++++ tests/unit/specs/postSeenObserver.test.js | 10 ++++++ .../specs/templates/postFeed.template.test.js | 12 ------- 7 files changed, 58 insertions(+), 17 deletions(-) diff --git a/src/js/dataLayer/mutations.js b/src/js/dataLayer/mutations.js index 42941a74..98194be5 100644 --- a/src/js/dataLayer/mutations.js +++ b/src/js/dataLayer/mutations.js @@ -333,7 +333,7 @@ export class Mutations { const showLessInteraction = { item: postURI, event: "app.bsky.feed.defs#requestLess", - ...(feedContext ? { feedContext } : {}), + ...(feedContext != null ? { feedContext } : {}), }; this.dataStore.$showLessInteractions.set([ ...this.dataStore.$showLessInteractions.get(), @@ -351,7 +351,7 @@ export class Mutations { const showMoreInteraction = { item: postURI, event: "app.bsky.feed.defs#requestMore", - ...(feedContext ? { feedContext } : {}), + ...(feedContext != null ? { feedContext } : {}), }; // Note, we don't really need to store this interaction because we don't use it in the UI (yet). // But, let's do it anyway for consistency. diff --git a/src/js/postSeenObserver.js b/src/js/postSeenObserver.js index b0e85cf7..cc4d6a52 100644 --- a/src/js/postSeenObserver.js +++ b/src/js/postSeenObserver.js @@ -115,7 +115,7 @@ export class PostSeenObserver { await this.interactionsDispatch.sendInteraction({ item: postUri, event: "app.bsky.feed.defs#interactionSeen", - feedContext, + ...(feedContext != null ? { feedContext } : {}), }); this.seenPosts.add(postUri); } catch (error) { diff --git a/src/js/templates/postFeed.template.js b/src/js/templates/postFeed.template.js index 978104c0..ecfd44d6 100644 --- a/src/js/templates/postFeed.template.js +++ b/src/js/templates/postFeed.template.js @@ -226,7 +226,6 @@ export function postFeedTemplate({ const content = html`
diff --git a/src/js/views/home.view.js b/src/js/views/home.view.js index 10e9fde2..f62a71e7 100644 --- a/src/js/views/home.view.js +++ b/src/js/views/home.view.js @@ -211,6 +211,17 @@ class HomeView extends View { ); const currentUser = dataLayer.derived.$currentUser.get(); const pinnedItems = dataLayer.derived.$hydratedPinnedItems.get() ?? []; + // Map of feed items -> feedContexts for postSeenObserver + const feedContextsByFeedUri = new Map( + pinnedItems.map((item) => [ + item.uri, + new Map( + (dataLayer.derived.$hydratedFeeds.get(item.uri)?.feed ?? []).map( + (feedItem) => [feedItem.post.uri, feedItem.feedContext ?? null], + ), + ), + ]), + ); const currentFeedUri = state.$currentFeedUri.get(); const currentFeedRequestStatus = dataLayer.requests.statusStore.$statuses.get( @@ -291,10 +302,12 @@ class HomeView extends View { ); const feedItems = document.querySelectorAll(".feed-item"); feedItems.forEach((feedItem) => { - const { feedGeneratorUri, feedContext, postUri } = feedItem.dataset; + const { feedGeneratorUri, postUri } = feedItem.dataset; if (feedGeneratorUri) { const postSeenObserver = postSeenObservers.get(feedGeneratorUri); if (postSeenObserver) { + const feedContext = + feedContextsByFeedUri.get(feedGeneratorUri)?.get(postUri) ?? null; postSeenObserver.register(feedItem, postUri, feedContext); } } diff --git a/tests/unit/specs/dataLayer/mutations.test.js b/tests/unit/specs/dataLayer/mutations.test.js index 9fd5e3ea..05a888cd 100644 --- a/tests/unit/specs/dataLayer/mutations.test.js +++ b/tests/unit/specs/dataLayer/mutations.test.js @@ -3054,6 +3054,37 @@ describe("sendShowLessInteraction", () => { assert.deepEqual(stored.length, 2); assert.deepEqual(stored[1].item, postURI); }); + + it("should omit feedContext when null but keep an empty string", async () => { + const dataStore = new DataStore(); + const patchStore = new PatchStore(dataStore); + const mockPreferencesProvider = { + requirePreferences: () => Preferences.createLoggedOutPreferences(), + }; + const sentInteractions = []; + const mutations = makeMutations( + { + sendInteractions: async (interactions) => { + sentInteractions.push(...interactions); + }, + }, + dataStore, + patchStore, + mockPreferencesProvider, + ); + + await mutations.sendShowLessInteraction(postURI, null, feedProxyUrl); + await mutations.sendShowLessInteraction(postURI, "", feedProxyUrl); + + assert.deepEqual(sentInteractions, [ + { item: postURI, event: "app.bsky.feed.defs#requestLess" }, + { + item: postURI, + event: "app.bsky.feed.defs#requestLess", + feedContext: "", + }, + ]); + }); }); describe("sendShowMoreInteraction", () => { diff --git a/tests/unit/specs/postSeenObserver.test.js b/tests/unit/specs/postSeenObserver.test.js index f5648767..1ee9baca 100644 --- a/tests/unit/specs/postSeenObserver.test.js +++ b/tests/unit/specs/postSeenObserver.test.js @@ -115,6 +115,16 @@ describe("PostSeenObserver - seen tracking", () => { assert(observer.seenPosts.has(postUriA)); }); + it("omits feedContext from the interaction when it is null", async () => { + const { element } = createTrackedElement(); + observer.register(element, postUriA, null); + await flushTimers(); + assert.deepEqual(sendInteractions.mock.callCount(), 1); + assert.deepEqual(sendInteractions.mock.calls[0].arguments[0], [ + { item: postUriA, event: "app.bsky.feed.defs#interactionSeen" }, + ]); + }); + it("does not send an interaction for an off-screen post", async () => { const { element } = createTrackedElement({ top: 2000, bottom: 2100 }); observer.register(element, postUriA, null); diff --git a/tests/unit/specs/templates/postFeed.template.test.js b/tests/unit/specs/templates/postFeed.template.test.js index 729a1bda..661a0419 100644 --- a/tests/unit/specs/templates/postFeed.template.test.js +++ b/tests/unit/specs/templates/postFeed.template.test.js @@ -115,18 +115,6 @@ describe("postFeedTemplate - feed with posts", () => { const feedItem = container.querySelector("[data-testid='feed-item']"); assert(feedItem.getAttribute("data-post-uri") !== null); }); - - it("should set data-feed-context attribute on feed items", () => { - const result = postFeedTemplate({ - feed: { feed: feed.slice(0, 1), cursor: null }, - currentUser: mockUser, - postInteractionHandler, - }); - const container = document.createElement("div"); - render(result, container); - const feedItem = container.querySelector("[data-testid='feed-item']"); - assert(feedItem.hasAttribute("data-feed-context")); - }); }); describe("postFeedTemplate - pagination", () => { -- 2.51.2