diff --git a/desktop/tests/e2e/community-rail.spec.ts b/desktop/tests/e2e/community-rail.spec.ts index dc733ed234..938402437f 100644 --- a/desktop/tests/e2e/community-rail.spec.ts +++ b/desktop/tests/e2e/community-rail.spec.ts @@ -934,7 +934,16 @@ test.describe("community rail", () => { ), ).toBe(1); await page.getByTestId(`community-rail-button-${COMMUNITY_B.id}`).click(); - await expect(page).not.toHaveURL(/#\/channels\/general$/); + // The switch runs inside a view transition, so its update (which writes the + // remembered channel into the route, then activates the community) lands + // frames after the click returns. Wait for it to finish before judging the + // route; checking the URL right after the click only passes when it has not + // run yet. The remembered route is kept until the live read validates it, + // so the channel absent from the cached list is not repaired away. + await expect( + page.getByTestId(`community-rail-button-${COMMUNITY_B.id}`), + ).toHaveAttribute("aria-current", "true"); + await expect(page).toHaveURL(/#\/channels\/general$/); await expect .poll(() => page.evaluate((communityId) => { @@ -1021,6 +1030,8 @@ test.describe("community rail", () => { }, COMMUNITY_B.id), ) .toEqual({ kind: "home" }); + // Live validation succeeded, so the missing channel is now repaired to Home. + await expect(page).toHaveURL(/#\/$/); }); test("does not restore a remembered destination on cold boot", async ({ diff --git a/desktop/tests/e2e/persistent-agent-audience.spec.ts b/desktop/tests/e2e/persistent-agent-audience.spec.ts index 1b402c993c..23f0c1d145 100644 --- a/desktop/tests/e2e/persistent-agent-audience.spec.ts +++ b/desktop/tests/e2e/persistent-agent-audience.spec.ts @@ -90,6 +90,23 @@ function threadComposer(page: Page) { return page.getByTestId("thread-composer-overlay"); } +/** + * Returns the thread composer's editor once the app has finished placing + * focus on its own. Opening a thread by URL mounts the channel and thread + * composers, and whichever mounts first autofocuses itself a frame later + * (TipTap's `focus()` is deferred). A click that lands before that frame is + * undone by the late autofocus, which blurs the editor the test just typed in + * and unmounts its focus-gated mention menu. + */ +async function readyThreadComposerInput(page: Page) { + const input = threadComposer(page).getByTestId("message-input"); + await expect(input).toHaveAttribute("contenteditable", "true"); + await expect(page.locator('[data-testid="message-input"]:focus')).toHaveCount( + 1, + ); + return input; +} + async function readComposerCaret(input: Locator) { return input.evaluate((element) => { const selection = window.getSelection(); @@ -833,7 +850,7 @@ test("the mention setting is reachable and operable by keyboard", async ({ await openThread(page); const mainComposer = threadComposer(page); - const mainInput = mainComposer.getByTestId("message-input"); + const mainInput = await readyThreadComposerInput(page); await mainInput.click(); await mainInput.fill("@Mor"); const list = mainComposer.getByTestId("mention-autocomplete"); @@ -934,7 +951,7 @@ test("a manual mention persists when automatic mentions are enabled", async ({ await openThread(page); const composer = threadComposer(page); - const input = composer.getByTestId("message-input"); + const input = await readyThreadComposerInput(page); await input.fill("@Mor"); await expect(composer.getByTestId("mention-autocomplete")).toBeVisible(); await input.press("Tab"); @@ -987,6 +1004,18 @@ test("a manual mention persists when automatic mentions are enabled", async ({ await input.type("hello"); await input.press("Enter"); + // The editor locks one frame after Enter (isSending disables it, blurring + // it) and unlocks when the delayed send settles. Wait for that whole cycle + // before asserting on the editor; its focus and editability checks all hold + // vacuously in the frame before the lock lands. + const composerForm = composer.getByTestId("message-composer"); + await expect(composerForm).toHaveAttribute("data-submit-locked", "true"); + await expect(input).toHaveAttribute("contenteditable", "false"); + await expect(composerForm).toHaveAttribute("data-submit-locked", "false", { + timeout: 5_000, + }); + await expect(input).toHaveAttribute("contenteditable", "true"); + await expect(input).toHaveText("@Morgarita ", { timeout: 2_500 }); await expect(input.locator("[data-placeholder]")).toHaveCount(0); await expect(input).toBeFocused();