test(web): assert ordinary fork identity without source parents
This commit is contained in:
1 parent
b096659245
commit
63d3504b14
5 files changed
+29
-15
No files matched your search
@@ -307,15 +307,17 @@ describe('web e2e: long Chat interaction contract', () => {
|
||||
await expect.poll(() => page.evaluate(() => navigator.clipboard.readText()), { timeout: 5_000 })
|
||||
.toBe(expectedUserText)
|
||||
|
||||
const existing = new Set(scaffold.ctx.agents.list().map(agent => agent.id))
|
||||
await turnTailRow.hover()
|
||||
await turnTailRow.getByRole('button', { name: 'Branch into a new conversation', exact: true }).click()
|
||||
await expect.poll(
|
||||
() => scaffold.ctx.agents.list().find(agent => agent.session.header.parentSession === SessionId(SESSION_ID)),
|
||||
() => scaffold.ctx.agents.list().find(agent => !existing.has(agent.id)),
|
||||
{ timeout: 15_000 },
|
||||
).toBeDefined()
|
||||
const child = scaffold.ctx.agents.list()
|
||||
.find(agent => agent.session.header.parentSession === SessionId(SESSION_ID))
|
||||
.find(agent => !existing.has(agent.id))
|
||||
if (child === undefined) throw new Error('message branch did not create a child session')
|
||||
expect(child.session.header.parentSession).toBeUndefined()
|
||||
expect(child.session.inheritedEventCount).toBe(boundary.seq + 1)
|
||||
expect(child.session.snapshotEvents().some(event => carries(event, branchAssistantMarker))).toBe(true)
|
||||
expect(child.session.snapshotEvents().some(event => carries(event, FIXTURE.markers.user(BRANCH_TURN + 1)))).toBe(false)
|
||||
|
||||
@@ -114,7 +114,7 @@ describe('web e2e: exact-boundary fork seeds branch closers and continues', () =
|
||||
|
||||
const child = scaffold.ctx.sessions.get(childId)
|
||||
if (child === undefined) throw new Error('fork did not publish a live child session')
|
||||
expect(child.header.parentSession).toBe(SessionId(SEED_ID))
|
||||
expect(child.header.parentSession).toBeUndefined()
|
||||
// inheritedEventCount counts only the copied parent prefix; the closers are child work.
|
||||
expect(child.inheritedEventCount).toBe(forkAt + 1)
|
||||
expect(child.snapshotEvents()[forkAt]?.type).toBe('tool/call')
|
||||
|
||||
@@ -371,13 +371,15 @@ describe('web e2e: message IconActions and clocks on settled history', () => {
|
||||
it.skipIf(MODE === 'record')('forks through the settled-message and session-row actions', async () => {
|
||||
onTestFailed(() => saveFailureShot(page, 'web-e2e-message-fork'))
|
||||
// The second answer is followed by another completed user turn in the source.
|
||||
const existing = new Set(scaffold.ctx.agents.list().map(agent => agent.id))
|
||||
await page.getByRole('button', { name: 'Branch into a new conversation' }).nth(1).click()
|
||||
await expect.poll(
|
||||
() => scaffold.ctx.agents.list().find(agent => agent.session.header.parentSession === SessionId(SEED_ID)),
|
||||
() => scaffold.ctx.agents.list().find(agent => !existing.has(agent.id)),
|
||||
{ timeout: 15_000 },
|
||||
).toBeDefined()
|
||||
const child = scaffold.ctx.agents.list().find(agent =>
|
||||
agent.session.header.parentSession === SessionId(SEED_ID))!
|
||||
!existing.has(agent.id))!
|
||||
expect(child.session.header.parentSession).toBeUndefined()
|
||||
expect(child.inbox.nextTurn).toEqual([])
|
||||
expect(child.session.snapshotEvents().some(event => event.type === 'agent/inbox/spliced'
|
||||
&& event.data.inserted.some(message => message.content.some(part =>
|
||||
@@ -405,11 +407,13 @@ describe('web e2e: message IconActions and clocks on settled history', () => {
|
||||
const buttonBox = await actionButton.boundingBox()
|
||||
if (buttonBox === null) throw new Error('fork source row action has no layout box')
|
||||
await page.mouse.click(buttonBox.x + buttonBox.width / 2, buttonBox.y + buttonBox.height / 2)
|
||||
const beforeNested = new Set(scaffold.ctx.agents.list().map(agent => agent.id))
|
||||
await page.getByRole('menuitem', { name: 'Fork session' }).click()
|
||||
await expect.poll(
|
||||
() => scaffold.ctx.agents.list().filter(agent => agent.session.header.parentSession !== undefined).length,
|
||||
() => scaffold.ctx.agents.list().filter(agent => !beforeNested.has(agent.id)).length,
|
||||
{ timeout: 15_000 },
|
||||
).toBe(2)
|
||||
).toBe(1)
|
||||
expect(scaffold.ctx.agents.list().find(agent => !beforeNested.has(agent.id))!.session.header.parentSession).toBeUndefined()
|
||||
await expect.poll(
|
||||
() => page.locator('[role="treeitem"]').count(),
|
||||
{ timeout: 10_000 },
|
||||
@@ -445,8 +449,10 @@ describe('web e2e: message IconActions and clocks on settled history', () => {
|
||||
expect(scaffold.ctx.agents.get(id)).toBeUndefined()
|
||||
await row.hover()
|
||||
await row.locator('button[aria-label^="Session actions for "]').click()
|
||||
const existing = new Set(scaffold.ctx.agents.list().map(agent => agent.id))
|
||||
await page.getByRole('menuitem', { name: 'Fork session', exact: true }).click()
|
||||
await expect.poll(() => scaffold.ctx.agents.list().some(agent => agent.session.header.parentSession === id)).toBe(true)
|
||||
await expect.poll(() => scaffold.ctx.agents.list().some(agent => !existing.has(agent.id))).toBe(true)
|
||||
expect(scaffold.ctx.agents.list().find(agent => !existing.has(agent.id))!.session.header.parentSession).toBeUndefined()
|
||||
expect(scaffold.ctx.agents.get(id)).toBeUndefined()
|
||||
expect(await row.getAttribute('aria-selected')).not.toBe('true')
|
||||
})
|
||||
@@ -481,6 +487,7 @@ describe('web e2e: message IconActions and clocks on settled history', () => {
|
||||
if (path === '/api/session/fork') forkRequests.push(path)
|
||||
}
|
||||
page.on('request', trackFork)
|
||||
const existing = new Set(scaffold.ctx.agents.list().map(agent => agent.id))
|
||||
try {
|
||||
await row.hover()
|
||||
await row.locator('button[aria-label^="Session actions for "]').click()
|
||||
@@ -501,7 +508,7 @@ describe('web e2e: message IconActions and clocks on settled history', () => {
|
||||
expect(await readdir(directory)).toContain(`session.v${SESSION_FORMAT_VERSION}.jsonl.zstd`)
|
||||
expect(await readFile(predecessor)).toEqual(source)
|
||||
expect(forkRequests).toEqual(['/api/session/fork'])
|
||||
expect(scaffold.ctx.agents.list().filter(agent => agent.session.header.parentSession === id)).toEqual([])
|
||||
expect(scaffold.ctx.agents.list().filter(agent => !existing.has(agent.id) && agent.id !== id)).toEqual([])
|
||||
} finally {
|
||||
page.off('request', trackFork)
|
||||
}
|
||||
|
||||
@@ -1209,7 +1209,7 @@ async function assertReplaySession(
|
||||
let expectedPath = fixturePath
|
||||
const userPrompts = fixtureUserPrompts(expected)
|
||||
const candidates = sessions.filter((session) => {
|
||||
if (session.header.parentSession !== undefined) return false
|
||||
if (session.header.origin === 'subagent' || session.header.isSeeded) return false
|
||||
const actual = session.snapshotEvents().flatMap((event) => {
|
||||
if (event.type !== 'user/message' || event.data.source.kind !== 'user') return []
|
||||
const text = event.data.content.filter(block => block.type === 'text').map(block => block.text).join('')
|
||||
|
||||
@@ -93,14 +93,17 @@ describe.skipIf(mode === 'record')('web e2e: workspace shortcuts', () => {
|
||||
await bind('Fork session')
|
||||
const original = await readPersistedEvents(scaffold, sourceId)
|
||||
const lastEnd = original.findLastIndex(event => event.type === 'turn/end')
|
||||
const existing = new Set(scaffold.ctx.agents.list().map(agent => agent.id))
|
||||
await page.keyboard.press('Meta+Shift+Comma')
|
||||
await expect.poll(() => scaffold.ctx.agents.list().find(agent => agent.session.header.parentSession === sourceId)).toBeDefined()
|
||||
const child = scaffold.ctx.agents.list().find(agent => agent.session.header.parentSession === sourceId)!
|
||||
await expect.poll(() => scaffold.ctx.agents.list().find(agent => !existing.has(agent.id))).toBeDefined()
|
||||
const child = scaffold.ctx.agents.list().find(agent => !existing.has(agent.id))!
|
||||
expect(child.session.header.parentSession).toBeUndefined()
|
||||
const childId = child.session.header.id
|
||||
await expect.poll(async () => (await readPersistedEvents(scaffold, childId)).some(event => event.type === 'session/title' && event.data.title === 'T4 source (1)')).toBe(true)
|
||||
const copied = await readPersistedEvents(scaffold, childId)
|
||||
expect(copied.slice(0, lastEnd + 1)).toEqual(original.slice(0, lastEnd + 1))
|
||||
const sourceRow = page.getByRole('treeitem').filter({ has: page.getByText('T4 source', { exact: true }) })
|
||||
expect(child.session.header.parentSession).toBeUndefined()
|
||||
const childRow = page.getByRole('treeitem').filter({ has: page.getByText('T4 source (1)', { exact: true }) })
|
||||
await childRow.waitFor()
|
||||
expect(await sourceRow.getAttribute('aria-selected')).toBe('true')
|
||||
@@ -218,6 +221,7 @@ it.skipIf(mode === 'record')('explains a first-turn fork refusal and retries fro
|
||||
await connectFreshWorkspace(page, scaffold.workspaceCwd)
|
||||
const source = scaffold.ctx.agents.list().find(agent => !before.has(agent.id))
|
||||
if (source === undefined) throw new Error('The workspace did not create a Session')
|
||||
const existing = new Set(scaffold.ctx.agents.list().map(agent => agent.id))
|
||||
source.session.append('turn/start', { turn: 1 })
|
||||
const user = source.session.append('user/message', createUserMessage({
|
||||
content: [{ type: 'text', text: 'The first turn has not ended.' }], source: { kind: 'user' },
|
||||
@@ -240,7 +244,7 @@ it.skipIf(mode === 'record')('explains a first-turn fork refusal and retries fro
|
||||
await compareOrRefreshGolden(join(root, 'fork-unavailable.expected.md'),
|
||||
await unavailable.ariaSnapshot(), mode)
|
||||
expect(await selected.textContent()).toContain('First turn')
|
||||
expect(scaffold.ctx.agents.list().filter(agent => agent.session.header.parentSession === source.id)).toHaveLength(0)
|
||||
expect(scaffold.ctx.agents.list().filter(agent => !existing.has(agent.id))).toHaveLength(0)
|
||||
expect(forkWarnings).toEqual([])
|
||||
|
||||
source.session.append('turn/end', { turn: 1, reason: { kind: 'completed' } })
|
||||
@@ -252,8 +256,9 @@ it.skipIf(mode === 'record')('explains a first-turn fork refusal and retries fro
|
||||
await scaffold.ctx.sessions.flush(source.session)
|
||||
await page.getByText('The second turn has not ended.', { exact: true }).waitFor()
|
||||
await page.keyboard.press('Meta+Shift+Comma')
|
||||
await expect.poll(() => scaffold.ctx.agents.list().find(agent => agent.session.header.parentSession === source.id)).toBeDefined()
|
||||
const child = scaffold.ctx.agents.list().find(agent => agent.session.header.parentSession === source.id)!
|
||||
await expect.poll(() => scaffold.ctx.agents.list().find(agent => !existing.has(agent.id))).toBeDefined()
|
||||
const child = scaffold.ctx.agents.list().find(agent => !existing.has(agent.id))!
|
||||
expect(child.session.header.parentSession).toBeUndefined()
|
||||
const childRow = page.getByRole('treeitem').filter({ has: page.getByText('First turn (1)', { exact: true }) })
|
||||
await childRow.waitFor()
|
||||
expect(await childRow.getAttribute('aria-selected')).toBe('false')
|
||||
|
||||
Reference in new issue
Block a user