|
|
@@ -47,6 +47,15 @@ async function settledSourceOption(menu: Locator): Promise<Locator> {
|
|
|
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<void> {
|
|
|
+ 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()
|