diff --git a/desktop/tests/e2e/mentions.spec.ts b/desktop/tests/e2e/mentions.spec.ts index e392eda5c0..d430231bda 100644 --- a/desktop/tests/e2e/mentions.spec.ts +++ b/desktop/tests/e2e/mentions.spec.ts @@ -549,7 +549,7 @@ test("relay-only shared agents emit an outbound mention tag when selected", asyn await expect .poll(() => readOutgoingMentionPubkeys(page, content)) - .toContain(TEST_IDENTITIES.alice.pubkey); + .toEqual([TEST_IDENTITIES.alice.pubkey]); }); test("typing an exact agent name and Space commits its chip and mention tag", async ({ @@ -580,7 +580,7 @@ test("typing an exact agent name and Space commits its chip and mention tag", as await page.getByTestId("send-message").click(); await expect .poll(() => readOutgoingMentionPubkeys(page, content)) - .toContain(TEST_IDENTITIES.alice.pubkey); + .toEqual([TEST_IDENTITIES.alice.pubkey]); }); test("Shift+Space leaves an exact agent name plain and emits no mention tag", async ({ diff --git a/desktop/tests/e2e/persistent-agent-audience.spec.ts b/desktop/tests/e2e/persistent-agent-audience.spec.ts index 6cc2f22a88..39e8c1004d 100644 --- a/desktop/tests/e2e/persistent-agent-audience.spec.ts +++ b/desktop/tests/e2e/persistent-agent-audience.spec.ts @@ -137,6 +137,76 @@ async function readOutgoingMentionPubkeys(page: Page, content: string) { }, content); } +// Exercise the persistence/identity boundary after the duplicate-chip check. +async function assertAuthoredDuplicateIdentity(page: Page) { + await expect + .poll(() => + page.evaluate((channelId) => { + for (const key of Object.keys(localStorage)) { + if (!key.startsWith("buzz-drafts.v2:")) continue; + const draft = JSON.parse(localStorage.getItem(key) ?? "{}")[ + channelId + ]; + if (draft) return draft.mentionRefs; + } + return null; + }, CHANNEL_ID), + ) + .toEqual([{ displayName: "Morgarita", pubkey: AGENT_A, isAgent: true }]); + expect( + await page.evaluate(() => + (window.__BUZZ_E2E_SIGNED_EVENTS__ ?? []).filter( + (event) => event.kind === 9, + ), + ), + ).toEqual([]); + + await openGeneral(page); + const composer = channelComposer(page); + const input = composer.getByTestId("message-input"); + await expect(input).toHaveText("@Morgarita authored duplicate"); + await expect(input.locator(".agent-mention-highlight")).toHaveCount(1); + await expect( + composer.getByTestId(`composer-address-lock-${AGENT_B}`), + ).toHaveCount(0); + await composer.getByTestId("send-message").click(); + const expected = [{ content: "@Morgarita authored duplicate", p: [AGENT_A] }]; + await expect + .poll(() => + page.evaluate(() => + (window.__BUZZ_E2E_SIGNED_EVENTS__ ?? []) + .filter((event) => event.kind === 9) + .map((event) => ({ + content: event.content, + p: event.tags.filter((tag) => tag[0] === "p").map((tag) => tag[1]), + })), + ), + ) + .toEqual(expected); + await expect + .poll(() => + page.evaluate(() => { + const events: { content: string; p: string[] }[] = []; + for (const entry of window.__BUZZ_E2E_COMMAND_LOG__ ?? []) { + if (entry.command !== "plugin:websocket|send") continue; + const data = (entry.payload as { message?: { data?: string } }) + ?.message?.data; + if (!data) continue; + const frame = JSON.parse(data); + if (frame[0] !== "EVENT" || frame[1]?.kind !== 9) continue; + events.push({ + content: frame[1].content, + p: frame[1].tags + .filter((tag: string[]) => tag[0] === "p") + .map((tag: string[]) => tag[1]), + }); + } + return events; + }), + ) + .toEqual(expected); +} + async function emitMockMessage( page: Page, content: string, @@ -1318,6 +1388,7 @@ test("an authored duplicate leading mention survives draft restoration", async ( }, CHANNEL_ID), ) .toBe("@Morgarita authored duplicate"); + await assertAuthoredDuplicateIdentity(page); }); test("typed deletion preserves an identical authored mention in drafts", async ({ @@ -1469,6 +1540,12 @@ test("re-enabling an automatic mention preserves an authored duplicate after dra }, CHANNEL_ID), ) .toBe("@Morgarita authored duplicate"); + await openGeneral(page); + await expect(input).toHaveText("@Morgarita authored duplicate"); + await expect(input.locator(".agent-mention-highlight")).toHaveCount(1); + await expect( + composer.getByTestId(`composer-address-lock-${AGENT_B}`), + ).toHaveCount(0); }); test("a restored multi-word automatic mention remains a chip with the caret after its space", async ({ diff --git a/desktop/tests/e2e/send-channel-binding.spec.ts b/desktop/tests/e2e/send-channel-binding.spec.ts index 3a7fdf7613..3c3cd048a7 100644 --- a/desktop/tests/e2e/send-channel-binding.spec.ts +++ b/desktop/tests/e2e/send-channel-binding.spec.ts @@ -53,6 +53,15 @@ test("message with agent mention lands in compose-time channel despite mid-send }) => { const MESSAGE_TEXT = `send-binding-repro-${Date.now()}`; + // This case exercises a persistent address; ordinary Enter completion is + // one-time by default. Opt in before the bridge mounts the composer. + await page.addInitScript(() => { + window.localStorage.setItem( + "buzz.messages.keepMentionedAgentsPinned", + "true", + ); + }); + // Install bridge with: // - a managed agent that is NOT in general (forces add_channel_members path) // - a 500ms delay on add_channel_members to open the race window @@ -81,7 +90,7 @@ test("message with agent mention lands in compose-time channel despite mid-send await expect(botRow).toBeVisible(); await expect(botRow.getByText("not in channel")).toBeVisible(); // Select BotA from the autocomplete - await input.press("Enter"); + await botRow.click(); await page.keyboard.type(` ${MESSAGE_TEXT}`); // Verify the inline mention and persistent address are present before submitting. @@ -127,6 +136,31 @@ test("message with agent mention lands in compose-time channel despite mid-send MESSAGE_TEXT, ); + // The signed event must retain both the compose-time channel and exact key. + await expect + .poll(() => + page.evaluate( + (text) => + (window.__BUZZ_E2E_SIGNED_EVENTS__ ?? []) + .filter((event) => event.content.includes(text)) + .map((event) => ({ + channels: event.tags + .filter((tag) => tag[0] === "h") + .map((tag) => tag[1]), + recipients: event.tags + .filter((tag) => tag[0] === "p") + .map((tag) => tag[1]), + })), + MESSAGE_TEXT, + ), + ) + .toEqual([ + { + channels: ["9a1657ac-f7aa-5db0-b632-d8bbeb6dfb50"], + recipients: [OUT_OF_CHANNEL_BOT_PUBKEY], + }, + ]); + // --- Assert message did NOT land in agents (switched-to channel) --- await page.getByTestId("channel-agents").click(); await expect(page.getByTestId("chat-title")).toHaveText("agents");