diff --git a/apps/web/tests/reference-composer.e2e.ts b/apps/web/tests/reference-composer.e2e.ts index 39c7786296..c447548421 100644 --- a/apps/web/tests/reference-composer.e2e.ts +++ b/apps/web/tests/reference-composer.e2e.ts @@ -47,6 +47,15 @@ async function settledSourceOption(menu: Locator): Promise { return source } +// Clear retained suggestions and insert one complete query so an intermediate +// prefix cannot satisfy the caller's wait for a ready result row. +async function replaceReferenceQuery(page: Page, input: Locator, text: string): Promise { + await writeComposerDraft(page, input, '') + await page.getByRole('listbox', { name: 'Trigger suggestions' }).waitFor({ state: 'hidden' }) + await input.click() + await page.keyboard.insertText(text) +} + /** Build one closed source session with a stable title for reference discovery. */ function sourceSessionFixture(): string { const session = Session.create(SessionId(SOURCE_SESSION_ID)) @@ -293,7 +302,7 @@ describe.skipIf(MODE === 'record')('web e2e: file and session references through // Settle: Enter on the highlighted folder row resolves the folder itself // as an atomic chip — folder glyph, no trigger character, one unit. - await writeComposerDraft(page, input, '@folderx') + await replaceReferenceQuery(page, input, '@folderx') // First folder query on this page: allow the Host index a cold start. await menu.getByRole('option', { name: /^folderx\// }).waitFor({ timeout: 60_000 }) await page.keyboard.press('Enter') @@ -304,7 +313,7 @@ describe.skipIf(MODE === 'record')('web e2e: file and session references through // Tab drills: the literal descent text stays editable and the open menu // lists the folder's children. - await writeComposerDraft(page, input, '@folderx') + await replaceReferenceQuery(page, input, '@folderx') await menu.getByRole('option', { name: /^folderx\// }).waitFor() await page.keyboard.press('Tab') await expect.poll(() => input.textContent()).toBe('@folderx/') @@ -312,7 +321,7 @@ describe.skipIf(MODE === 'record')('web e2e: file and session references through // The row chevron drills the same way by pointer, header included: a // pointer descent reaches the same listing a Tab descent does. - await writeComposerDraft(page, input, '@folderx') + await replaceReferenceQuery(page, input, '@folderx') const row = menu.getByRole('option', { name: /^folderx\// }) await row.waitFor() await row.getByRole('button', { name: 'Browse folder' }).click() @@ -337,12 +346,12 @@ describe.skipIf(MODE === 'record')('web e2e: file and session references through const crumbs = page.getByRole('navigation', { name: 'Folder navigation' }) // A path the user typed carries its own context: no header. - await writeComposerDraft(page, input, '@folderx/') + await replaceReferenceQuery(page, input, '@folderx/') await menu.getByRole('option', { name: /child\.txt/ }).waitFor({ timeout: 60_000 }) await expect.poll(() => crumbs.count()).toBe(0) // The same listing reached by drilling owes the user the way back. - await writeComposerDraft(page, input, '@folderx') + await replaceReferenceQuery(page, input, '@folderx') await menu.getByRole('option', { name: /^folderx\// }).waitFor() await page.keyboard.press('Tab') await menu.getByRole('option', { name: /child\.txt/ }).waitFor() @@ -357,7 +366,7 @@ describe.skipIf(MODE === 'record')('web e2e: file and session references through // A crumb above the current step re-lists that directory and keeps the // header, which now names the step it returned to. - await writeComposerDraft(page, input, '@folderx/nested') + await replaceReferenceQuery(page, input, '@folderx/nested') await expect.poll(() => menu.getByRole('option', { name: /child\.txt/ }).count()).toBe(0) const nested = menu.getByRole('option', { name: /^nested\// }) await nested.waitFor()