feat(client): surface pending session interactions
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
// content from the keyless FixtureApiClient transport.
|
||||
//
|
||||
// Component behavior remains owned by per-package suites (SlotTestRuntime
|
||||
// benches over src). This smoke additionally pins the resident approval
|
||||
// benches over src). This smoke additionally pins the resident interaction
|
||||
// fixture's cross-plugin projection because only the built connection/runtime/
|
||||
// workspace graph can prove that transport-to-row path end to end.
|
||||
import { readFileSync } from 'node:fs'
|
||||
@@ -105,14 +105,15 @@ it('boots the built plugin graph and renders a fixture session end to end', asyn
|
||||
const tree = await screen.findByRole('tree', { name: 'Sessions' }, { timeout: 10_000 })
|
||||
await within(tree).findByText('4 sessions')
|
||||
|
||||
// The resident approval fixture proves the assembled workspace plugin
|
||||
// distinguishes a blocked running session from an ordinarily busy one.
|
||||
// The resident fixture has both a question and an approval; composer routing
|
||||
// exposes the question first, and the assembled workspace plugin mirrors that
|
||||
// actionable wait instead of the underlying running state.
|
||||
const waitingTitle = await within(tree).findByText('Fixture 历史会话')
|
||||
const waitingRow = waitingTitle.closest<HTMLElement>('[role="treeitem"]')
|
||||
if (waitingRow === null) throw new Error('fixture Session title must belong to a tree row')
|
||||
expect(waitingRow.querySelector('[data-state="warning"]')).not.toBeNull()
|
||||
expect(waitingRow.querySelector('[data-state="ongoing"]')).toBeNull()
|
||||
within(waitingRow).getByText('Waiting for approval')
|
||||
within(waitingRow).getByText('Waiting for answer')
|
||||
|
||||
// Opening a session reaches chat content through the fixture transport.
|
||||
fireEvent.click(waitingTitle)
|
||||
|
||||
@@ -25,6 +25,7 @@ const FIXTURE = join(SNAPSHOT_DIR, 'session.jsonl')
|
||||
// The waiting golden owns the decision card; the approved golden owns the
|
||||
// transcript the approval leaves behind — the state the card cannot see.
|
||||
const REVIEW_EXPECTED = join(SNAPSHOT_DIR, 'review.expected.md')
|
||||
const SIDEBAR_EXPECTED = join(SNAPSHOT_DIR, 'sidebar.expected.md')
|
||||
const APPROVED_EXPECTED = join(SNAPSHOT_DIR, 'approved.expected.md')
|
||||
const MODE = webSnapshotMode()
|
||||
|
||||
@@ -82,9 +83,15 @@ describe('web e2e: plan review takeover round trip', () => {
|
||||
expect(await page.locator('[data-question-key]').count()).toBe(0)
|
||||
await expect.poll(() => card.getByText('Plan review').count(), { timeout: 10_000 }).toBeGreaterThan(0)
|
||||
|
||||
const selectedRow = page.locator('[role="treeitem"][aria-selected="true"]')
|
||||
await expect.poll(() => selectedRow.locator('[data-state="warning"]').count(), { timeout: 10_000 }).toBe(1)
|
||||
await expect.poll(() => selectedRow.getByText('Plan awaiting review', { exact: true }).count(), { timeout: 10_000 }).toBe(1)
|
||||
|
||||
if (MODE !== 'record') {
|
||||
const snapshot = await captureStableAria(page, '[data-plan-review-key]', scaffold.workspaceCwd)
|
||||
await compareOrRefreshGolden(REVIEW_EXPECTED, snapshot, MODE)
|
||||
const sidebar = await captureStableAria(page, '[role="treeitem"][aria-selected="true"]', scaffold.workspaceCwd)
|
||||
await compareOrRefreshGolden(SIDEBAR_EXPECTED, sidebar, MODE)
|
||||
}
|
||||
|
||||
await card.getByRole('button', { name: 'Approve' }).click()
|
||||
@@ -100,6 +107,7 @@ describe('web e2e: plan review takeover round trip', () => {
|
||||
await expect.poll(() => page.getByText('DONE', { exact: true }).count(), { timeout: 15_000 }).toBeGreaterThanOrEqual(1)
|
||||
// Card gone; regular input restored.
|
||||
expect(await page.locator('[data-plan-review-key]').count()).toBe(0)
|
||||
expect(await selectedRow.locator('[data-state="warning"]').count()).toBe(0)
|
||||
await expect.poll(() => page.locator('textarea').first().isEnabled(), { timeout: 10_000 }).toBe(true)
|
||||
const snapshot = await captureStableAria(page, '[class*="centerCol"]', scaffold.workspaceCwd)
|
||||
await compareOrRefreshGolden(APPROVED_EXPECTED, snapshot, MODE)
|
||||
@@ -108,6 +116,8 @@ describe('web e2e: plan review takeover round trip', () => {
|
||||
}, 200_000)
|
||||
|
||||
it.skipIf(MODE === 'record')('keeps the fixture inventory closed', async () => {
|
||||
await assertFixtureInventory(SNAPSHOT_DIR, ['session.jsonl', 'review.expected.md', 'approved.expected.md'])
|
||||
await assertFixtureInventory(SNAPSHOT_DIR, [
|
||||
'session.jsonl', 'review.expected.md', 'sidebar.expected.md', 'approved.expected.md',
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -23,6 +23,7 @@ import { connectFreshWorkspace, newEnglishPage, saveFailureShot } from './suppor
|
||||
const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/question-composer', import.meta.url))
|
||||
const FIXTURE = join(SNAPSHOT_DIR, 'session.jsonl')
|
||||
const UI_EXPECTED = join(SNAPSHOT_DIR, 'ui.expected.md')
|
||||
const SIDEBAR_EXPECTED = join(SNAPSHOT_DIR, 'sidebar.expected.md')
|
||||
const COMPOSED_EXPECTED = join(SNAPSHOT_DIR, 'composed.expected.md')
|
||||
// Final golden: the answered transcript — the question resolved into its tool
|
||||
// round trip and the final reply, the state the composer goldens cannot see.
|
||||
@@ -76,11 +77,17 @@ describe('web e2e: resident question composer round trip', () => {
|
||||
await composer.waitFor({ timeout: MODE === 'record' ? 120_000 : 30_000 })
|
||||
await expect.poll(() => composer.getByText('Which color do you prefer?').count(), { timeout: 10_000 }).toBeGreaterThan(0)
|
||||
|
||||
const selectedRow = page.locator('[role="treeitem"][aria-selected="true"]')
|
||||
await expect.poll(() => selectedRow.locator('[data-state="warning"]').count(), { timeout: 10_000 }).toBe(1)
|
||||
await expect.poll(() => selectedRow.getByText('Waiting for answer', { exact: true }).count(), { timeout: 10_000 }).toBe(1)
|
||||
|
||||
if (MODE !== 'record') {
|
||||
// This golden owns the stable question surface; the answered-state
|
||||
// golden below owns the resulting transcript.
|
||||
const snapshot = await captureStableAria(page, '[data-question-key]', scaffold.workspaceCwd)
|
||||
await compareOrRefreshGolden(UI_EXPECTED, snapshot, MODE)
|
||||
const sidebar = await captureStableAria(page, '[role="treeitem"][aria-selected="true"]', scaffold.workspaceCwd)
|
||||
await compareOrRefreshGolden(SIDEBAR_EXPECTED, sidebar, MODE)
|
||||
}
|
||||
|
||||
// Squeezed card: the option rows are the capped card's scroll content, so
|
||||
@@ -155,6 +162,7 @@ describe('web e2e: resident question composer round trip', () => {
|
||||
await expect.poll(() => page.getByText('DONE', { exact: true }).count(), { timeout: 15_000 }).toBeGreaterThanOrEqual(1)
|
||||
// Composer gone; regular input restored.
|
||||
expect(await page.locator('[data-question-key]').count()).toBe(0)
|
||||
expect(await selectedRow.locator('[data-state="warning"]').count()).toBe(0)
|
||||
await expect.poll(() => page.locator('textarea').first().isEnabled(), { timeout: 10_000 }).toBe(true)
|
||||
// Golden of the answered transcript: the ask_user_question round trip
|
||||
// rendered as history (question tool row + DONE), composer takeover gone.
|
||||
@@ -168,6 +176,7 @@ describe('web e2e: resident question composer round trip', () => {
|
||||
await assertFixtureInventory(SNAPSHOT_DIR, [
|
||||
'session.jsonl',
|
||||
'ui.expected.md',
|
||||
'sidebar.expected.md',
|
||||
'composed.expected.md',
|
||||
'answered.expected.md',
|
||||
])
|
||||
|
||||
1
apps/web/tests/snapshots/plan-review/sidebar.expected.md
Normal file
1
apps/web/tests/snapshots/plan-review/sidebar.expected.md
Normal file
@@ -0,0 +1 @@
|
||||
- 'treeitem "Plan awaiting review Plan a small change: add now" [selected]'
|
||||
@@ -0,0 +1 @@
|
||||
- treeitem "Waiting for answer Use the ask_user_question tool to now" [selected]
|
||||
Reference in New Issue
Block a user