From c09b6618671c0e41b7123b24628af0dce32396a1 Mon Sep 17 00:00:00 2001 From: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@buzz.block.builderlab.xyz> Date: Tue, 1 Sep 2026 11:44:43 -0700 Subject: [PATCH] test(desktop): bind quick-tray sharing to real action bars Mount the production MessageActionBar with its real query/provider/tooltip tree and assert rendered quick buttons, constant palette reads, one query observer and one storage listener across row growth, rerender and remount. Verify teardown releases both resources. Mutation checks restoring per-row query hooks or storage listeners each fail at 17 resources versus the required one. No production behavior changes. Signed-off-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@buzz.block.builderlab.xyz> --- .../ui/QuickReactionProvider.test.mjs | 106 ++++++++++++++++-- 1 file changed, 99 insertions(+), 7 deletions(-) diff --git a/desktop/src/features/messages/ui/QuickReactionProvider.test.mjs b/desktop/src/features/messages/ui/QuickReactionProvider.test.mjs index f8ec55b7c8..087099c122 100644 --- a/desktop/src/features/messages/ui/QuickReactionProvider.test.mjs +++ b/desktop/src/features/messages/ui/QuickReactionProvider.test.mjs @@ -14,7 +14,12 @@ const emojiNames = (items) => items.map((item) => item.emoji); async function harness( t, - { scope = "community-a", palette = [], recents = [] } = {}, + { + scope = "community-a", + palette = [], + recents = [], + actionBars = false, + } = {}, ) { const dom = new JSDOM( "
", @@ -66,6 +71,33 @@ async function harness( const { recordQuickReactionEmoji } = await import( "./useQuickReactionEmojis.ts" ); + // Real action bars keep their query hooks, quick buttons and closed menus. + // Only fetching is disabled below; the production observer path is not mocked. + const { MessageActionBar } = actionBars + ? await import("./MessageActionBar.tsx") + : {}; + const { TooltipProvider } = actionBars + ? await import("@/shared/ui/tooltip.tsx") + : {}; + const selectReaction = async () => {}; + function ActionBar({ id }) { + return React.createElement(MessageActionBar, { + message: { + id: `message-${id}`, + author: "Test author", + pubkey: "a".repeat(64), + createdAt: 1, + time: "12:00", + body: "Quick-reaction sharing regression", + depth: 0, + pending: false, + kind: 9, + tags: [], + }, + reactions: [], + onReactionSelect: selectReaction, + }); + } const makeClient = (data) => { const client = new QueryClient({ defaultOptions: { @@ -142,12 +174,16 @@ async function harness( React.createElement( QuickReactionProvider, { communityScope: options.scope }, - Array.from({ length: options.count }, (_, id) => - React.createElement(Consumer, { - id, - key: id, - tick: options.tick, - }), + React.createElement( + actionBars ? TooltipProvider : React.Fragment, + null, + Array.from({ length: options.count }, (_, id) => + React.createElement(actionBars ? ActionBar : Consumer, { + id, + key: id, + tick: options.tick, + }), + ), ), ), ), @@ -246,6 +282,62 @@ test("many quick-reaction consumers reuse one preparation, query observer and st assert.equal(h.storageListeners.size, 0); }); +test("real MessageActionBars share quick-tray preparation, observer and storage listener", async (t) => { + let paletteReads = 0; + const palette = Array.from({ length: 128 }, (_, index) => ({ + get shortcode() { + paletteReads += 1; + return index === 0 ? "shipit" : `custom_${index}`; + }, + url: `https://cdn.example.test/emoji/${index}.png`, + })); + const h = await harness(t, { + actionBars: true, + palette, + recents: [entry(":shipit:", 4)], + }); + await h.render(); + const readsAfterPreparation = paletteReads; + assert.ok(readsAfterPreparation > 0); + for (const count of [16, 16, 1, 16]) { + await h.render({ count }); + const bars = h.dom.window.document.querySelectorAll( + '[data-testid^="message-action-bar-"]', + ); + assert.equal(bars.length, count, "mount the real production action bars"); + for (const bar of bars) { + const buttons = bar.querySelectorAll('button[aria-label^="React with "]'); + assert.equal(buttons.length, 3, "render the actual quick buttons"); + assert.equal( + buttons[0].querySelector("img")?.getAttribute("alt"), + ":shipit:", + ); + assert.equal( + buttons[0].querySelector("img")?.getAttribute("src"), + palette[0].url, + ); + } + assert.equal( + h.query().getObserversCount(), + 1, + "real rows must not add palette observers", + ); + assert.equal( + h.storageListeners.size, + 1, + "real rows must not add storage listeners", + ); + assert.equal( + paletteReads, + readsAfterPreparation, + "real row mounts/rerenders must not prepare the palette", + ); + } + await h.unmount(); + assert.equal(h.query().getObserversCount(), 0); + assert.equal(h.storageListeners.size, 0); +}); + test("recording persists recents without reshuffling mounted or newly mounted trays", async (t) => { const h = await harness(t, { palette: [{ shortcode: "shipit", url: "https://cdn.example.test/old.png" }],