test: remove redundant cases and repair audit assertions (#33334)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
This commit is contained in:
OrcaWin
2026-10-24 18:26:35 -07:00
committed by GitHub
co-authored by OrcaWin
parent 874a251cb4
commit 8eea0c0d5b
30 changed files with 274 additions and 586 deletions
@@ -309,12 +309,8 @@ describe('isPrSidebarDetailsPlaceholder / prSidebarDetailsNeedFetch', () => {
})
describe('buildMobilePrSidebarIdentity', () => {
it('keys identity on worktree + branch, not head SHA', () => {
it('keys identity on worktree and branch', () => {
expect(buildMobilePrSidebarIdentity({ worktreeId: 'w1', branch: 'feat' })).toBe('w1\u0000feat')
// Head advances on commit must not form a new identity — soft refresh keeps ready UI.
expect(buildMobilePrSidebarIdentity({ worktreeId: 'w1', branch: 'feat' })).toBe(
buildMobilePrSidebarIdentity({ worktreeId: 'w1', branch: 'feat' })
)
})
it('is null without a branch and changes when branch or worktree changes', () => {
@@ -22,7 +22,6 @@ import {
} from './pending-session-selection'
import { NOTES_UNCONFIRMED_MESSAGE, PROMPT_UNCONFIRMED_MESSAGE } from './new-tab-agent-host-launch'
import { AGENT_PROMPT_NOT_SENT_MESSAGE } from './pr-ai-triage-launch'
import { releaseTerminalCreateLock } from './terminal-create-lock'
import { useMobileSessionTerminalCreateActions } from './use-mobile-session-terminal-create-actions'
vi.mock('../platform/haptics', () => ({ triggerSuccess: vi.fn(), triggerError: vi.fn() }))
@@ -457,36 +456,6 @@ describe('launches that carry a prompt', () => {
})
})
it('lands on the tab and frees the + lock before a paste-after-start agent replies', async () => {
const reply = launchReply(
{ kind: 'terminal', handle: 'term_7' },
{ delivery: 'submit', outcome: 'handed-to-terminal' }
)
const state = scope(scriptedClient(reply).client)
let landedBeforeReply: PendingSessionSelection | null = null
state.client = requestPortRpcClient(async (_method, params) => {
// The host lists the tab as soon as it exists, then waits for the agent to paste into it.
const pending = state.pendingSelectionRef.current
const landed = resolveLaunchedSelection(pending, [reservedTab(params, 'term_7')])
state.pendingSelectionRef.current = landed.selection
if (landed.landedTabId && pending?.kind === 'launched') {
releaseTerminalCreateLock(state, pending.lock)
}
landedBeforeReply = state.pendingSelectionRef.current
expect(state.creatingTerminalRef.current).toBeNull()
return reply
})
await create_(state, 'aider', { agentPrompt: 'run the tests' })
expect(landedBeforeReply).toEqual(
expect.objectContaining({ kind: 'terminal', handle: 'term_7' })
)
// The late reply leaves the landed pick alone.
expect(state.pendingSelectionRef.current).toBe(landedBeforeReply)
expect(state.setCreating).toHaveBeenCalledTimes(2)
})
it("keeps a second launch's + lock when the first one's reply comes in", async () => {
const reply = launchReply(
{ kind: 'terminal', handle: 'term_7' },
@@ -380,32 +380,6 @@ describe('useMobileSessionViewMode', () => {
expect(readSessionViewOverridesPreference).toHaveBeenCalledTimes(2)
})
it('finishes an early toggle save after the route unmounts', async () => {
const overridesLoad = deferred<Map<string, MobileSessionView>>()
vi.mocked(readSessionViewOverridesPreference).mockReturnValue(
overridesLoad.promise.then((overrides) => ({ overrides, loaded: true }))
)
function Harness(): null {
controller = useMobileSessionViewMode({ hostId: 'h', worktreeId: 'w' })
return null
}
await act(async () => {
renderer = create(createElement(Harness))
await Promise.resolve()
})
act(() => controller?.toggleTabChatView('new-tab'))
act(() => renderer?.unmount())
renderer = null
await act(async () => {
overridesLoad.resolve(new Map([['saved-tab', 'terminal']]))
await Promise.resolve()
await Promise.resolve()
})
expect(updateSessionViewOverride).toHaveBeenCalledWith('h', 'w', 'new-tab', 'chat')
})
it('does not reload a failed override after the route unmounts', async () => {
const failedWrite = deferred<void>()
vi.mocked(updateSessionViewOverride).mockReturnValue(failedWrite.promise)
@@ -36,7 +36,7 @@ const SNAPSHOT: AgentSessionSubscribeEvent = {
window: {
oldest: { epoch: 'epoch-1', sequence: 1 },
newest: { epoch: 'epoch-1', sequence: 1 },
nextCursor: { epoch: 'epoch-1', sequence: 2 }
nextCursor: { epoch: 'epoch-1', sequence: 1 }
},
liveCursor: { epoch: 'epoch-1', sequence: 1 },
hasOlder: false,
@@ -83,10 +83,7 @@ async function sentDelivery(items: AgentJournalRenderItem[]): Promise<unknown> {
renderer = create(createElement(Harness))
})
await vi.waitFor(() => expect(listener).toEqual(expect.any(Function)))
const event = snapshotEvent()
if (event.type === 'snapshot') {
event.page.items = items
}
const event = snapshotEvent({ items })
act(() => listener?.(event))
await act(async () => {
await hook!.sendWithOutcome('carry on')
@@ -149,10 +146,7 @@ async function afterStop(items: AgentJournalRenderItem[]): Promise<unknown> {
renderer = create(createElement(Harness))
})
await vi.waitFor(() => expect(listener).toEqual(expect.any(Function)))
const event = snapshotEvent()
if (event.type === 'snapshot') {
event.page.items = [RUNNING_TURN, ...items]
}
const event = snapshotEvent({ items: [...items, RUNNING_TURN] })
act(() => listener?.(event))
act(() => hook?.cancel())
expect(hook?.turnIndicator.stopping).toBe(true)
@@ -1,7 +1,10 @@
// The published shapes the queued-message hook tests feed the mobile session: host answers,
// drafts, submissions and stream frames.
import type { AgentJournalSubmission } from '../../../src/shared/agent-session-journal-types'
import type {
AgentJournalRenderItem,
AgentJournalSubmission
} from '../../../src/shared/agent-session-journal-types'
import type {
AgentSessionQueuedMessage,
AgentSessionQueuePause,
@@ -88,33 +91,45 @@ export function queuedDraft(
export function snapshotEvent(input?: {
queuedMessages?: AgentSessionQueuedMessage[] | null
items?: AgentJournalRenderItem[]
fence?: number
runningTurn?: boolean
submissions?: AgentJournalSubmission[]
}): AgentSessionSubscribeEvent {
}): Extract<AgentSessionSubscribeEvent, { type: 'snapshot' }> {
const items: AgentJournalRenderItem[] =
input?.items ??
(input?.runningTurn
? [
{
itemId: 'turn-item-1',
revision: 1,
body: { kind: 'turn', turnId: 'turn-1', state: 'running' },
sequence: 1,
observedAt: 1
}
]
: [])
const oldest = items[0]
const newest = items.at(-1)
const fence = input?.fence ?? 3
return {
type: 'snapshot',
sessionId: SESSION_ID,
fence: 3,
fence,
page: {
sessionId: SESSION_ID,
epoch: 'epoch-1',
fence: 3,
fence,
direction: 'tail',
items: input?.runningTurn
? [
{
itemId: 'turn-item-1',
revision: 1,
body: { kind: 'turn', turnId: 'turn-1', state: 'running' },
sequence: 1,
observedAt: 1
}
]
: [],
items,
removedItemIds: [],
submissions: input?.submissions ?? [],
window: { oldest: null, newest: null, nextCursor: { epoch: 'epoch-1', sequence: 0 } },
liveCursor: { epoch: 'epoch-1', sequence: 0 },
window: {
oldest: oldest ? { epoch: 'epoch-1', sequence: oldest.sequence } : null,
newest: newest ? { epoch: 'epoch-1', sequence: newest.sequence } : null,
nextCursor: { epoch: 'epoch-1', sequence: oldest?.sequence ?? 0 }
},
liveCursor: { epoch: 'epoch-1', sequence: newest?.sequence ?? 0 },
hasOlder: false,
hasNewer: false
},
@@ -6,6 +6,7 @@
// Older hosts retain the copy-before-delete fallback; capable hosts edit in place.
import { createElement } from 'react'
import { agentJournalSubmissionKey } from '../../../src/shared/agent-session-journal-item-key'
import { act, create, type ReactTestRenderer } from 'react-test-renderer'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { structuredAgentSessionPayloadFingerprint } from '../../../src/shared/structured-agent-session-mutation'
@@ -320,6 +321,19 @@ describe('mobile structured queued messages', () => {
queuedDraft({ messageId: 'drained' }),
queuedDraft({ messageId: 'still-waiting' })
],
items: [
{
itemId: agentJournalSubmissionKey('fresh-hand-off-id'),
revision: 1,
sequence: 1,
observedAt: 10,
body: {
kind: 'message',
role: 'user',
blocks: [{ type: 'text', text: 'sent message' }]
}
}
],
submissions: [acceptedSubmission('fresh-hand-off-id', 'drained')]
})
)
@@ -331,6 +345,19 @@ describe('mobile structured queued messages', () => {
CAPABLE,
snapshotEvent({
queuedMessages: [queuedDraft({ messageId: 'same-id' })],
items: [
{
itemId: agentJournalSubmissionKey('same-id'),
revision: 1,
sequence: 1,
observedAt: 10,
body: {
kind: 'message',
role: 'user',
blocks: [{ type: 'text', text: 'sent message' }]
}
}
],
submissions: [acceptedSubmission('same-id')]
})
)
@@ -1,4 +1,5 @@
import { createElement } from 'react'
import { snapshotEvent } from './use-mobile-structured-agent-session-queued.test-fixture'
import { act, create, type ReactTestRenderer } from 'react-test-renderer'
import { afterEach, expect, it, vi } from 'vitest'
import { agentSessionFailureWords } from '../../../src/shared/agent-session-failure-words'
@@ -33,29 +34,7 @@ function retry(sequence: number, attempt: number): AgentJournalRenderItem {
}
function snapshot(items: AgentJournalRenderItem[]): AgentSessionSubscribeEvent {
const newest = items.length
return {
type: 'snapshot',
sessionId: 'session-1',
fence: 3,
page: {
sessionId: 'session-1',
epoch: 'epoch-1',
fence: 3,
direction: 'tail',
items,
removedItemIds: [],
submissions: [],
window: {
oldest: { epoch: 'epoch-1', sequence: 1 },
newest: { epoch: 'epoch-1', sequence: newest },
nextCursor: { epoch: 'epoch-1', sequence: newest + 1 }
},
liveCursor: { epoch: 'epoch-1', sequence: newest },
hasOlder: false,
hasNewer: false
}
}
return snapshotEvent({ items })
}
// Module scope: the harness assigns these, which a test body's own `let` would narrow to null.
@@ -3,9 +3,12 @@ import { act, create, type ReactTestRenderer } from 'react-test-renderer'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type { AgentJournalDispatchState } from '../../../src/shared/agent-session-journal-types'
import { DISPATCH_REJECTED_CANCELLED } from '../../../src/shared/structured-agent-session-dispatch-rejection'
import type { AgentSessionSubscribeEvent } from '../../../src/shared/agent-session-wire'
import type { RpcClient } from '../transport/rpc-client'
import { fieldsOf, ok } from './use-mobile-structured-agent-session-queued.test-fixture'
import {
fieldsOf,
ok,
snapshotEvent
} from './use-mobile-structured-agent-session-queued.test-fixture'
import { markRpcDeliveryUnknown } from '../transport/rpc-delivery-ambiguity'
import { structuredSendResultFixture } from './structured-agent-send-result.test-fixture'
import { useMobileStructuredAgentSession } from './use-mobile-structured-agent-session'
@@ -40,31 +43,6 @@ function sendResult(dispatchState: AgentJournalDispatchState, reason: string | n
})
}
function snapshotEvent(): Extract<AgentSessionSubscribeEvent, { type: 'snapshot' }> {
return {
type: 'snapshot',
sessionId: 'session-1',
fence: 3,
page: {
sessionId: 'session-1',
epoch: 'epoch-1',
fence: 3,
direction: 'tail',
items: [],
removedItemIds: [],
submissions: [],
window: {
oldest: null,
newest: null,
nextCursor: { epoch: 'epoch-1', sequence: 0 }
},
liveCursor: { epoch: 'epoch-1', sequence: 0 },
hasOlder: false,
hasNewer: false
}
}
}
describe('mobile structured send actions', () => {
let renderer: ReactTestRenderer | null = null
let hook: ReturnType<typeof useMobileStructuredAgentSession> | null = null
@@ -277,20 +255,38 @@ describe('mobile structured send actions', () => {
throw new Error('expected a submission answer')
}
original.submission.recovered = true
const event = snapshotEvent()
event.page.submissions = [original.submission]
event.page.items = [
{
itemId: agentJournalSubmissionKey(original.submission.clientMessageId),
revision: 1,
sequence: 1,
observedAt: 10,
body: { kind: 'message', role: 'user', blocks: [{ type: 'text', text: 'same text' }] }
const event = snapshotEvent({
items: [
{
itemId: agentJournalSubmissionKey(original.submission.clientMessageId),
revision: 1,
sequence: 1,
observedAt: 10,
body: { kind: 'message', role: 'user', blocks: [{ type: 'text', text: 'same text' }] }
}
],
submissions: [original.submission]
})
sendRequest.mockImplementation(async (method, params) => {
if (method !== 'agentSession.send') {
return ok({ models: [], current: {} })
}
]
sendRequest.mockImplementation(async (method) =>
method === 'agentSession.send' ? sendResult('accepted') : ok({ models: [], current: {} })
)
const id = String(fieldsOf(fieldsOf(params).envelope).clientOperationId)
const value = structuredSendResultFixture('accepted')
if (!('submission' in value)) {
throw new Error('expected a submission answer')
}
value.clientMessageId = id
value.submission.clientMessageId = id
return ok({
ok: true,
replayed: false,
fence: 3,
cursor: { epoch: 'epoch-1', sequence: 2 },
value
})
})
await mountSession()
act(() => listener?.(event))
const messages = hook!.session.messages
@@ -313,24 +309,25 @@ describe('mobile structured send actions', () => {
const newId = sentIds()[0]!
expect(newId).not.toBe(original.submission.clientMessageId)
followUp.submission.clientMessageId = newId
const reply = snapshotEvent()
reply.page.submissions = [original.submission, followUp.submission]
reply.page.items = [
...event.page.items,
{ ...event.page.items[0]!, itemId: sent, sequence: 2, observedAt: 20 },
{
itemId: 'assistant-reply',
revision: 1,
sequence: 3,
observedAt: 30,
body: {
kind: 'message',
role: 'assistant',
blocks: [{ type: 'text', text: 'Reply to the new send' }]
const reply = snapshotEvent({
items: [
...event.page.items,
{ ...event.page.items[0]!, itemId: sent, sequence: 2, observedAt: 20 },
{
itemId: 'assistant-reply',
revision: 1,
sequence: 3,
observedAt: 30,
body: {
kind: 'message',
role: 'assistant',
blocks: [{ type: 'text', text: 'Reply to the new send' }]
}
}
}
]
reply.page.liveCursor = { epoch: 'epoch-1', sequence: 3 }
],
submissions: [original.submission, followUp.submission]
})
act(() => listener?.(reply))
expect(hook!.session.messages.map((message) => message.id)).toEqual([
messages[0]?.id,
@@ -356,25 +353,27 @@ describe('mobile structured send actions', () => {
throw new Error('expected submission')
}
value.submission.rejection = fact
const event = snapshotEvent()
const sentRow = agentJournalSubmissionKey(value.submission.clientMessageId)
event.page.items = [
{
itemId: 'auth-row',
sequence: 1,
revision: 1,
observedAt: 1,
body: { kind: 'status', tone: 'error', ...words }
},
{
itemId: sentRow,
sequence: 2,
revision: 1,
observedAt: 2,
body: { kind: 'message', role: 'user', blocks: [{ type: 'text', text: 'my message' }] }
}
]
event.page.submissions = [value.submission]
const event = snapshotEvent({
items: [
{
itemId: 'auth-row',
sequence: 1,
revision: 1,
observedAt: 1,
body: { kind: 'status', tone: 'error', ...words }
},
{
itemId: sentRow,
sequence: 2,
revision: 1,
observedAt: 2,
body: { kind: 'message', role: 'user', blocks: [{ type: 'text', text: 'my message' }] }
}
],
submissions: [value.submission]
})
sendRequest.mockImplementation(async (method) => {
if (method !== 'agentSession.send') {
return ok({ models: [], current: {} })
@@ -1,4 +1,5 @@
import { createElement } from 'react'
import { snapshotEvent } from './use-mobile-structured-agent-session-queued.test-fixture'
import { act, create, type ReactTestRenderer } from 'react-test-renderer'
import { afterEach, describe, expect, it, vi } from 'vitest'
import type { AgentJournalRenderItem } from '../../../src/shared/agent-session-journal-types'
@@ -48,28 +49,7 @@ const ITEMS: AgentJournalRenderItem[] = [
]
function snapshot(items: AgentJournalRenderItem[]): AgentSessionSubscribeEvent {
return {
type: 'snapshot',
sessionId: 'session-1',
fence: 3,
page: {
sessionId: 'session-1',
epoch: 'epoch-1',
fence: 3,
direction: 'tail',
items,
removedItemIds: [],
submissions: [],
window: {
oldest: { epoch: 'epoch-1', sequence: 1 },
newest: { epoch: 'epoch-1', sequence: 3 },
nextCursor: { epoch: 'epoch-1', sequence: 4 }
},
liveCursor: { epoch: 'epoch-1', sequence: 3 },
hasOlder: false,
hasNewer: false
}
}
return snapshotEvent({ items })
}
describe('useMobileStructuredAgentSession transcript', () => {
@@ -1,4 +1,5 @@
import { createElement } from 'react'
import { snapshotEvent as publishedSnapshot } from './use-mobile-structured-agent-session-queued.test-fixture'
import { act, create, type ReactTestRenderer } from 'react-test-renderer'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type {
@@ -6,7 +7,6 @@ import type {
AgentJournalRenderItem,
AgentJournalResolution
} from '../../../src/shared/agent-session-journal-types'
import type { AgentSessionSubscribeEvent } from '../../../src/shared/agent-session-wire'
import type { SessionOptionDescriptor } from '../../../src/shared/native-chat-session-options'
import type { RpcClient } from '../transport/rpc-client'
import { markRpcDeliveryUnknown } from '../transport/rpc-delivery-ambiguity'
@@ -32,58 +32,24 @@ function ok(result: unknown) {
return { ok: true, result, _meta: { runtimeId: 'runtime-1' } }
}
function snapshotEvent(fence = 3): AgentSessionSubscribeEvent {
return {
type: 'snapshot',
sessionId: 'session-1',
fence,
page: {
sessionId: 'session-1',
epoch: 'epoch-1',
fence,
direction: 'tail',
items: [],
removedItemIds: [],
submissions: [],
window: {
oldest: null,
newest: null,
nextCursor: { epoch: 'epoch-1', sequence: 0 }
},
liveCursor: { epoch: 'epoch-1', sequence: 0 },
hasOlder: false,
hasNewer: false
}
}
function snapshotEvent(fence = 3, items: AgentJournalRenderItem[] = []) {
return publishedSnapshot({ fence, items })
}
function snapshotWithMessage(): AgentSessionSubscribeEvent {
const event = snapshotEvent()
return {
...event,
page: {
...event.page,
items: [
{
itemId: 'msg-1',
revision: 1,
sequence: 1,
observedAt: 10,
body: {
kind: 'message',
role: 'user',
blocks: [{ type: 'text', text: 'sent before the blip' }]
}
}
],
window: {
oldest: { epoch: 'epoch-1', sequence: 1 },
newest: { epoch: 'epoch-1', sequence: 1 },
nextCursor: { epoch: 'epoch-1', sequence: 2 }
},
liveCursor: { epoch: 'epoch-1', sequence: 1 }
function snapshotWithMessage() {
return snapshotEvent(3, [
{
itemId: 'msg-1',
revision: 1,
sequence: 1,
observedAt: 10,
body: {
kind: 'message',
role: 'user',
blocks: [{ type: 'text', text: 'sent before the blip' }]
}
}
} as AgentSessionSubscribeEvent
])
}
function pendingResolution(): AgentJournalResolution {
@@ -322,7 +288,7 @@ describe('useMobileStructuredAgentSession', () => {
)
})
it('re-holds after a reconnect that outlives the host release grace', async () => {
it('re-holds before resubscribing after a reconnect', async () => {
act(() => {
renderer = create(createElement(Harness, { connected: true }))
})
@@ -333,8 +299,7 @@ describe('useMobileStructuredAgentSession', () => {
)
await vi.waitFor(() => expect(subscribe).toHaveBeenCalledTimes(1))
// A transport loss retires the connection-scoped hold; after the host's 15s grace
// it may evict the provider child. Reconnect must acquire before replaying the stream.
// Reconnect must acquire the connection-scoped hold before replaying the stream.
await act(async () => {
renderer?.update(createElement(Harness, { connected: false }))
})
@@ -402,15 +367,7 @@ describe('useMobileStructuredAgentSession', () => {
await vi.waitFor(() => expect(listener).toEqual(expect.any(Function)))
act(() => listener?.(snapshotEvent(3)))
act(() => listener?.(snapshotEvent(3)))
act(() =>
listener?.({
...snapshotEvent(3),
page: {
...snapshotEvent(3).page,
items: [approvalItem(), questionItem()]
}
})
)
act(() => listener?.(snapshotEvent(3, [approvalItem(), questionItem()])))
if (!hook) {
throw new Error('hook not ready')
@@ -569,32 +526,24 @@ describe('useMobileStructuredAgentSession', () => {
})
await vi.waitFor(() => expect(listener).toEqual(expect.any(Function)))
act(() =>
listener?.({
...snapshotEvent(3),
page: {
...snapshotEvent(3).page,
items: [
approvalItemWithIdentity('approval-old', 4),
questionItemWithIdentity('question-old', 8)
]
}
})
listener?.(
snapshotEvent(3, [
approvalItemWithIdentity('approval-old', 4),
questionItemWithIdentity('question-old', 8)
])
)
)
const approvalToken = hook!.permission!.options[0]!.send
const questionToken = hook!.question!.optionTokens[0]!
const freeText = formatQuestionFreeTextAnswer(hook!.question!, 'old answer')
act(() =>
listener?.({
...snapshotEvent(3),
page: {
...snapshotEvent(3).page,
items: [
approvalItemWithIdentity('approval-new', 9),
questionItemWithIdentity('question-new', 10)
]
}
})
listener?.(
snapshotEvent(3, [
approvalItemWithIdentity('approval-new', 9),
questionItemWithIdentity('question-new', 10)
])
)
)
sendRequest.mockClear()
@@ -638,15 +587,7 @@ describe('useMobileStructuredAgentSession', () => {
renderer = create(createElement(Harness))
})
await vi.waitFor(() => expect(listener).toEqual(expect.any(Function)))
act(() =>
listener?.({
...snapshotEvent(3),
page: {
...snapshotEvent(3).page,
items: [approvalItem(), questionItem()]
}
})
)
act(() => listener?.(snapshotEvent(3, [approvalItem(), questionItem()])))
sendRequest.mockImplementation(async (method, params) => {
if (method === 'agentSession.respondToApproval') {
throw markRpcDeliveryUnknown(new Error('Connection closed'))
@@ -679,12 +620,7 @@ describe('useMobileStructuredAgentSession', () => {
renderer = create(createElement(Harness))
})
await vi.waitFor(() => expect(listener).toEqual(expect.any(Function)))
act(() =>
listener?.({
...snapshotEvent(3),
page: { ...snapshotEvent(3).page, items: [approvalItem()] }
})
)
act(() => listener?.(snapshotEvent(3, [approvalItem()])))
let attempts = 0
sendRequest.mockImplementation(async (method, params) => {
if (method === 'agentSession.respondToApproval' && attempts++ === 0) {
@@ -749,15 +685,7 @@ describe('useMobileStructuredAgentSession', () => {
renderer = create(createElement(Harness))
})
await vi.waitFor(() => expect(listener).toEqual(expect.any(Function)))
act(() =>
listener?.({
...snapshotEvent(3),
page: {
...snapshotEvent(3).page,
items: [runningStatusItem()]
}
})
)
act(() => listener?.(snapshotEvent(3, [runningStatusItem()])))
sendRequest.mockImplementation(async (method, params) => {
if (method === 'agentSession.cancel') {
throw markRpcDeliveryUnknown(new Error('Connection closed'))
@@ -120,7 +120,7 @@ describe('useMobileStructuredAgentState older history', () => {
type: 'snapshot',
sessionId: 'session-1',
fence: 1,
page: page(newest, true)
page: { ...page(newest, true), direction: 'tail' }
})
})
await vi.waitFor(() => expect(hook.current!.state.status).toBe('ready'))
@@ -241,7 +241,7 @@ describe('useMobileStructuredAgentState live feed', () => {
type: 'snapshot',
sessionId: 'session-1',
fence: 1,
page: { ...page([said(58), said(59)], false), liveCursor: cursorAt(59) }
page: { ...page([said(58), said(59)], false), direction: 'tail', liveCursor: cursorAt(59) }
})
)
await act(async () => {
@@ -52,11 +52,7 @@ function userRow(clientMessageId: string, text: string, sequence: number): Agent
}
function journal(items: AgentJournalRenderItem[], submissions: AgentJournalSubmission[]) {
const event = snapshotEvent({ submissions })
if (event.type === 'snapshot') {
event.page.items = items
}
return event
return snapshotEvent({ items, submissions })
}
describe('a structured send in the open chat', () => {
@@ -1,4 +1,5 @@
import { createElement } from 'react'
import { snapshotEvent } from './use-mobile-structured-agent-session-queued.test-fixture'
import { act, create, type ReactTestRenderer } from 'react-test-renderer'
import { afterEach, beforeEach, describe, expect, it, onTestFinished, vi } from 'vitest'
import type { AgentJournalRenderItem } from '../../../src/shared/agent-session-journal-types'
@@ -21,30 +22,7 @@ function snapshot(
fence: number,
activity?: AgentSessionTurnActivity
): AgentSessionSubscribeEvent {
const newest = items.length
return {
type: 'snapshot',
sessionId: 'session-1',
fence,
...(activity ? { activity } : {}),
page: {
sessionId: 'session-1',
epoch: 'epoch-1',
fence,
direction: 'tail',
items,
removedItemIds: [],
submissions: [],
window: {
oldest: { epoch: 'epoch-1', sequence: 1 },
newest: { epoch: 'epoch-1', sequence: newest },
nextCursor: { epoch: 'epoch-1', sequence: newest + 1 }
},
liveCursor: { epoch: 'epoch-1', sequence: newest },
hasOlder: false,
hasNewer: false
}
}
return { ...snapshotEvent({ items, fence }), ...(activity ? { activity } : {}) }
}
/** What the one live indicator row reads, resolved off the session journal. */
@@ -3,6 +3,7 @@ import { act, create, type ReactTestRenderer } from 'react-test-renderer'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type { RpcClient } from '../transport/rpc-client'
import {
type MobileTerminalInventoryRefreshOptions,
useMobileTerminalInventoryRecovery,
useMobileTerminalInventoryRecoveryBridge
} from './use-mobile-terminal-inventory-recovery'
@@ -30,7 +31,9 @@ function deferred<T>() {
const client = {
getState: () => 'connected'
} as unknown as RpcClient
const fetchTerminals = vi.fn(async () => true)
const fetchTerminals = vi.fn<(options?: MobileTerminalInventoryRefreshOptions) => Promise<boolean>>(
async () => true
)
type RecoveryActions = ReturnType<typeof useMobileTerminalInventoryRecovery>
let actions: RecoveryActions | null = null
@@ -163,7 +163,7 @@ describe('the page pane hook', () => {
expect(bridge.cleared).toHaveLength(2)
})
it('takes the tap that arrived while it was applying the one before it', () => {
it('applies successive pane requests and names each value when clearing', () => {
render(true)
deliver(`tab-a:${LEAF}`)
deliver(`tab-b:${LEAF}`)
@@ -172,13 +172,6 @@ describe('the page pane hook', () => {
expect(bridge.cleared.map((entry) => entry.value)).toEqual([`tab-a:${LEAF}`, `tab-b:${LEAF}`])
})
it('takes a different pane as its own request', () => {
render(true)
deliver(`tab-a:${LEAF}`)
deliver(`tab-b:${LEAF}`)
expect(switched.map((tab) => tab.id)).toEqual(['first', 'second'])
})
it('asks for nothing and applies nothing when the route carries no pane', () => {
// Load-bearing rather than defensive: the shell's own erase comes back as a route that moved,
// and so does every `ready` answered after it.
@@ -1,17 +1,11 @@
import { describe, expect, expectTypeOf, it } from 'vitest'
import type { GitBranchCompareResult } from '../../../src/shared/git-diff-compare-types'
import { describe, expect, it } from 'vitest'
import {
buildMobileBranchCompareSection,
canOpenMobileBranchCompareDiff,
formatMobileBranchCompareSummary,
type MobileGitBranchCompareResult
formatMobileBranchCompareSummary
} from './mobile-branch-compare'
describe('mobile branch compare helpers', () => {
it('keeps the mobile branch compare type in lockstep with the runtime contract', () => {
expectTypeOf<MobileGitBranchCompareResult>().toEqualTypeOf<GitBranchCompareResult>()
})
it('sorts committed branch entries by path', () => {
const section = buildMobileBranchCompareSection([
{ path: 'zeta.ts', status: 'modified' },
@@ -32,6 +26,7 @@ describe('mobile branch compare helpers', () => {
mergeBase: 'c'.repeat(40),
changedFiles: 2,
commitsAhead: 1,
errorMessage: undefined,
status: 'ready'
})
).toBe('2 files - 1 commit - vs origin/main')
@@ -46,6 +41,8 @@ describe('mobile branch compare helpers', () => {
headOid: 'b'.repeat(40),
mergeBase: 'c'.repeat(40),
changedFiles: 1,
commitsAhead: undefined,
errorMessage: undefined,
status: 'ready'
})
).toBe(true)
@@ -58,6 +55,8 @@ describe('mobile branch compare helpers', () => {
headOid: null,
mergeBase: null,
changedFiles: 0,
commitsAhead: undefined,
errorMessage: undefined,
status: 'unborn-head'
})
).toBe(false)
-2
View File
@@ -58,12 +58,10 @@ src/session/use-mobile-pr-sidebar-controller.test.ts
src/session/use-mobile-session-terminal-create-actions.test.ts
src/session/use-mobile-structured-agent-session.test.tsx
src/session/use-mobile-structured-prompt-responses.test.tsx
src/session/use-mobile-terminal-inventory-recovery.test.ts
src/settings/native-notification-delivery-settings.test.tsx
src/settings/notification-display-test.test.tsx
src/settings/settings-screen-state.test.tsx
src/settings/voice-settings-poller-refresh.test.tsx
src/source-control/mobile-branch-compare.test.ts
src/source-control/mobile-create-pr-action.test.ts
src/source-control/mobile-git-status.test.ts
src/source-control/mobile-hosted-review-create-intent-runner.test.ts
@@ -201,20 +201,23 @@ describe('Store', () => {
expect(store.getSettings().terminalMacOptionAsAltMigrated).toBe(true)
})
it('preserves explicit "left" / "right" terminalMacOptionAsAlt through migration', async () => {
writeDataFile({
schemaVersion: 1,
repos: [],
worktreeMeta: {},
settings: { terminalMacOptionAsAlt: 'left' },
ui: {},
githubCache: { pr: {}, issue: {} },
workspaceSession: {}
})
const store = await createStore()
expect(store.getSettings().terminalMacOptionAsAlt).toBe('left')
expect(store.getSettings().terminalMacOptionAsAltMigrated).toBe(true)
})
it.each(['left', 'right'] as const)(
'preserves explicit "%s" terminalMacOptionAsAlt through migration',
async (side) => {
writeDataFile({
schemaVersion: 1,
repos: [],
worktreeMeta: {},
settings: { terminalMacOptionAsAlt: side },
ui: {},
githubCache: { pr: {}, issue: {} },
workspaceSession: {}
})
const store = await createStore()
expect(store.getSettings().terminalMacOptionAsAlt).toBe(side)
expect(store.getSettings().terminalMacOptionAsAltMigrated).toBe(true)
}
)
it('respects already-migrated settings with explicit "true"', async () => {
writeDataFile({
@@ -8,10 +8,7 @@ import {
makeRepo,
makeWorktreeLineage
} from '../persistence-test-harness'
import {
makeTerminalTab,
makeTerminalTab as makeSessionTerminalTab
} from '../persistence-session-fixtures'
import { makeTerminalTab } from '../persistence-session-fixtures'
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { rmSync, mkdtempSync } from 'node:fs'
import { join } from 'node:path'
@@ -86,9 +83,6 @@ describe('Store', () => {
await closeTestStores()
rmSync(testState.dir, { recursive: true, force: true })
})
it('keeps the persistence harness terminal-tab fixture as the same public function', () => {
expect(makeTerminalTab).toBe(makeSessionTerminalTab)
})
it('addRepo stores a repo retrievable by getRepo', async () => {
const store = await createStore()
@@ -170,57 +164,6 @@ describe('Store', () => {
expect(store.getRepo('sibling')?.projectGroupId).toBe(sibling.id)
})
it('adapts flat folder-scan groups into sparse nested folder scopes on load', async () => {
writeDataFile({
schemaVersion: 1,
repos: [
makeRepo({ id: 'api', path: '/workspace/platform/api', projectGroupId: 'root' }),
makeRepo({ id: 'web', path: '/workspace/platform/web', projectGroupId: 'root' }),
makeRepo({
id: 'repo1',
path: '/workspace/platform/packages/shared/repo1',
projectGroupId: 'root'
}),
makeRepo({
id: 'repo2',
path: '/workspace/platform/packages/shared/repo2',
projectGroupId: 'root'
})
],
worktreeMeta: {},
settings: {},
ui: {},
githubCache: { pr: {}, issue: {} },
projectGroups: [
{
id: 'root',
name: 'Platform',
parentPath: '/workspace/platform',
parentGroupId: null,
createdFrom: 'folder-scan',
tabOrder: 0,
isCollapsed: false,
color: null,
createdAt: 1,
updatedAt: 1
}
]
})
const store = await createStore()
const groups = store.getProjectGroups()
const shared = groups.find((group) => group.name === 'packages/shared')
expect(groups.map((group) => [group.name, group.parentGroupId, group.parentPath])).toEqual([
['Platform', null, '/workspace/platform'],
['packages/shared', 'root', '/workspace/platform/packages/shared']
])
expect(store.getRepo('api')?.projectGroupId).toBe('root')
expect(store.getRepo('web')?.projectGroupId).toBe('root')
expect(store.getRepo('repo1')?.projectGroupId).toBe(shared?.id)
expect(store.getRepo('repo2')?.projectGroupId).toBe(shared?.id)
})
it('creates a project group when persisted group history is very large', async () => {
const projectGroups: ProjectGroup[] = Array.from({ length: 130_000 }, (_, index) => ({
id: `group-${index}`,
@@ -466,40 +409,6 @@ describe('Store', () => {
])
})
it('removeProjectForHost keeps the surviving host session for a shared repo id + path', async () => {
const store = await createStore()
// Identical repo IDs and paths require host-scoped session pruning.
store.addRepo(makeRepo({ id: 'shared', path: '/repo' }))
store.addRepo(
makeRepo({
id: 'shared',
path: '/repo',
connectionId: 'ssh-a',
executionHostId: 'ssh:ssh-a'
})
)
store.setWorktreeMeta(at('shared::/repo', 'local'), { displayName: 'local' })
store.setWorkspaceSession({
...getDefaultWorkspaceSession(),
lastVisitedAtByWorktreeId: { 'shared::/repo': 111 }
})
store.setWorkspaceSession(
{
...getDefaultWorkspaceSession(),
lastVisitedAtByWorktreeId: { 'shared::/repo': 222 }
},
'ssh:ssh-a'
)
store.removeProjectForHost({ id: 'shared', executionHostId: 'ssh:ssh-a' })
expect(
store.getWorkspaceSession('ssh:ssh-a').lastVisitedAtByWorktreeId?.['shared::/repo']
).toBeUndefined()
expect(store.getWorkspaceSession().lastVisitedAtByWorktreeId?.['shared::/repo']).toBe(111)
})
it('removeProjectForHost on the local host keeps a surviving SSH host session', async () => {
const store = await createStore()
store.addRepo(makeRepo({ id: 'shared', path: '/repo' }))
@@ -665,29 +574,6 @@ describe('Store', () => {
expect(store.getRepos().map((repo) => repo.path)).toEqual(originalPaths)
})
it('removeProjectForHost prunes the SSH host meta (tagged hostId) for a shared id', async () => {
const store = await createStore()
store.addRepo(makeRepo({ id: 'shared', path: '/local/repo' }))
store.addRepo(
makeRepo({
id: 'shared',
path: '/remote/repo',
connectionId: 'ssh-old',
executionHostId: 'ssh:ssh-old'
})
)
store.setWorktreeMeta(at('shared::/remote/repo/wt', 'ssh:ssh-old'), {
displayName: 'remote-wt'
})
// Hostless legacy metadata belongs to local; remote removal must preserve it.
store.setWorktreeMeta(at('shared::/local/repo/wt', 'local'), { displayName: 'local-wt' })
store.removeProjectForHost({ id: 'shared', executionHostId: 'ssh:ssh-old' })
expect(store.getWorktreeMeta('shared::/remote/repo/wt')).toBeUndefined()
expect(store.getWorktreeMeta('shared::/local/repo/wt')).toBeDefined()
})
it('removeProjectForHost prunes everything when the id exists on no other host', async () => {
const store = await createStore()
store.addRepo(makeRepo({ id: 'only', connectionId: 'ssh-x', executionHostId: 'ssh:ssh-x' }))
@@ -707,38 +707,6 @@ export function registerPtyConnectionInterruptInferenceTests(): void {
}
})
it('does not request interrupt inference from a selection-copy keydown without terminal input', async () => {
const { connectPanePty } = await import('./pty-connection')
const transport = createMockTransport()
transportFactoryQueue.push(transport)
vi.useFakeTimers()
vi.setSystemTime(1_100)
const paneKey = makePaneKey('tab-1', LEAF_1)
mockStoreState.agentStatusByPaneKey[paneKey] = {
state: 'working',
prompt: 'copy selection',
updatedAt: 1_000,
stateStartedAt: 900,
agentType: 'codex',
paneKey,
terminalTitle: 'Codex',
stateHistory: []
}
const terminalTarget = createKeyboardEventTarget()
const pane = createPane(1)
;(pane.terminal as { element?: unknown; hasSelection: ReturnType<typeof vi.fn> }).element =
terminalTarget.target
;(pane.terminal as { hasSelection: ReturnType<typeof vi.fn> }).hasSelection.mockReturnValue(
true
)
connectPanePty(pane as never, createManager(1) as never, createDeps() as never)
terminalTarget.dispatch(keyEvent({ key: 'c', ctrlKey: true }))
vi.advanceTimersByTime(500)
expect(window.api.agentStatus.inferInterrupt).not.toHaveBeenCalled()
})
it('removes the terminal key listener on dispose before a remount adds another', async () => {
const { connectPanePty } = await import('./pty-connection')
const firstTransport = createMockTransport()
@@ -157,7 +157,7 @@ export function registerPtyConnectionNotificationSettingsGatingTests(): void {
)
})
it('raises terminal attention for agent completion when OS completion notifications are disabled', async () => {
it('forwards agent completion to notification policy when desktop banners are disabled', async () => {
const { connectPanePty } = await import('./pty-connection')
const transport = createMockTransport('pty-codex')
transportFactoryQueue.push(transport)
@@ -113,7 +113,7 @@ describe('connectPanePty', () => {
})
const pane = createPane(1)
captureCallbackTerminalWrites(pane)
const { writes } = captureCallbackTerminalWrites(pane)
// Why a counting getter: the restore task's `.finally` reads isVisibleRef.current
// on every re-arm, so read count is a direct, deterministic measure of how many
@@ -143,12 +143,16 @@ describe('connectPanePty', () => {
})
await flushAsyncTicks(20)
expect(getMainBufferSnapshot).toHaveBeenCalledTimes(1)
expect(writes).toContain('snapshot-state\r\n')
disposable.dispose()
const readsAtDispose = visibilityReads
await flushAsyncTicks(200)
// A terminated chain settles in a handful of turns; the self-feeding loop grew
// once per microtask turn and would blow past this by orders of magnitude.
expect(visibilityReads - readsAtDispose).toBeLessThan(50)
try {
await flushAsyncTicks(200)
expect(visibilityReads - readsAtDispose).toBeLessThan(50)
} finally {
// Stop a regressed re-arm loop after measuring it so failures can finish.
visible = false
}
})
})
@@ -131,7 +131,7 @@ export function registerPtyConnectionRemotePauseDropNoticeTests(): void {
binding.dispose()
})
it('still restores on every reveal when the host cannot report drops', async () => {
it('restores on reveal when the host cannot report drops', async () => {
const { transport, binding, setVisible } = await connectRemotePane(false)
await setVisible(false)
@@ -1,6 +1,6 @@
import type * as React from 'react'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { flushAsyncTicks, renderHeadlessBuffer } from './pty-connection-test-async'
import { flushAsyncTicks } from './pty-connection-test-async'
import {
terminalWriteText,
createMockTransport,
@@ -233,16 +233,4 @@ describe('pushed remote snapshot replay grid', () => {
expect(session.pane.terminal.resize).not.toHaveBeenCalledWith(HOST_COLS, HOST_ROWS)
session.dispose()
})
it('only reproduces the host frame when it is parsed at the host grid', async () => {
const atHostGrid = await renderHeadlessBuffer([HOST_FRAME], HOST_COLS, HOST_ROWS)
const atPaneGrid = await renderHeadlessBuffer([HOST_FRAME], PANE_COLS, HOST_ROWS - 4)
// Why this is the user-visible failure: the alternate screen has no
// scrollback, so rows scrolled off by a shorter grid are gone for good and
// an idle TUI never repaints them.
expect(atHostGrid.filter((line) => line.startsWith('host row'))).toHaveLength(HOST_ROWS)
expect(atPaneGrid.filter((line) => line.startsWith('host row')).length).toBeLessThan(HOST_ROWS)
expect(atPaneGrid).not.toContain('host row 1')
})
})
@@ -271,7 +271,7 @@ export function registerPtyConnectionRuntimeOwnerSpawnRoutingTests(): void {
expect(deps.syncPanePtyLayoutBinding).toHaveBeenCalledWith(2, 'remote:env-1@@terminal-2')
})
it('persists a restarted pane PTY id and uses it on the next remount', async () => {
it('publishes a restarted pane PTY id and reuses its restored hint on remount', async () => {
const { connectPanePty } = await import('./pty-connection')
const restartedTransport = createMockTransport()
@@ -325,8 +325,7 @@ export function registerPtyConnectionRuntimeOwnerSpawnRoutingTests(): void {
expect(remountDeps.syncPanePtyLayoutBinding).toHaveBeenCalledWith(1, 'pty-restarted')
})
// Why: BEL (0x07) is the attention signal — onBell raises the bell event + an OS notification.
it('wires onBell to the bell attention event and OS notification', async () => {
it('forwards onBell to pane attention and notification policy', async () => {
const { connectPanePty } = await import('./pty-connection')
const transport = createMockTransport()
transportFactoryQueue.push(transport)
@@ -322,7 +322,7 @@ describe('connectPanePty: a restore the host kept asleep', () => {
binding.dispose()
})
it('stops the background retry when the pane is opened or closed', async () => {
it('stops the background retry when the pane is disposed', async () => {
const { connectPanePty } = await import('./pty-connection')
const transport = deferringTransport()
transportFactoryQueue.push(transport)
@@ -138,7 +138,7 @@ describe('fresh spawn leaves a local pane unbound', () => {
return buildPaneConnectionDeps(() => mockStoreState, overrides)
}
it('remounts the pane when the spawn resolves without a PTY id', async () => {
it('requests pane recovery when the spawn resolves without a PTY id', async () => {
const { connectPanePty } = await import('./pty-connection')
const transport = createMockTransport()
// A spawn that produced nothing: no id returned and nothing bound after it.
@@ -75,24 +75,30 @@ const TEXT_FIELDS: Record<string, TextField> = {
lastMessage: { cap: 512, carry: (lastMessage) => ({ lastMessage }), read: (c) => c?.lastMessage }
}
function hostileText(cap: number): [string, string][] {
function hostileText(cap: number): [string, string, string | undefined][] {
return [
['a tab', 'col1\tcol2'],
['a CRLF', 'line one\r\nline two'],
['a line separator', 'one\u2028two'],
['a paragraph separator', 'one\u2029two'],
['a next-line control', 'one\u0085two'],
['a no-break space inside and around', '\u00a0keep\u00a0this\u00a0'],
['an escape sequence', 'plain \u001b[31mred'],
['a delete', 'rub\u007fout'],
['whitespace only', ' \t\r\n\u2028 '],
// A space at character cap - 1, cap and cap + 1: just inside the cut, on it, and past it.
...[-1, 0, 1].map((offset): [string, string] => [
`a space at character cap${offset < 0 ? '' : '+'}${offset}`,
`${'x'.repeat(cap + offset - 1)} tail`
]),
['a surrogate pair split by the cut', `${'x'.repeat(cap - 1)}\u{1f600}tail`],
['a surrogate pair ending at the cut', `${'x'.repeat(cap - 2)}\u{1f600}tail`]
['a tab', 'col1\tcol2', 'col1 col2'],
['a CRLF', 'line one\r\nline two', 'line one line two'],
['a line separator', 'one\u2028two', 'one two'],
['a paragraph separator', 'one\u2029two', 'one two'],
['a next-line control', 'one\u0085two', 'one two'],
['a no-break space inside and around', '\u00a0keep\u00a0this\u00a0', 'keep\u00a0this'],
['an escape sequence', 'plain \u001b[31mred', 'plain [31mred'],
['a delete', 'rub\u007fout', 'rub out'],
['whitespace only', ' \t\r\n\u2028 ', undefined],
['a space at character cap-1', `${'x'.repeat(cap - 2)} tail`, `${'x'.repeat(cap - 2)} t`],
['a space at character cap+0', `${'x'.repeat(cap - 1)} tail`, 'x'.repeat(cap - 1)],
['a space at character cap+1', `${'x'.repeat(cap)} tail`, 'x'.repeat(cap)],
[
'a surrogate pair split by the cut',
`${'x'.repeat(cap - 1)}\u{1f600}tail`,
'x'.repeat(cap - 1)
],
[
'a surrogate pair ending at the cut',
`${'x'.repeat(cap - 2)}\u{1f600}tail`,
`${'x'.repeat(cap - 2)}\u{1f600}`
]
]
}
@@ -105,25 +111,30 @@ function breaksOneLine(text: string): boolean {
describe('child-work admission parses provider text into what the codec stores', () => {
const cases = Object.entries(TEXT_FIELDS).flatMap(([field, spec]) =>
hostileText(spec.cap).map(([label, raw]) => [field, label, raw, spec] as const)
hostileText(spec.cap).map(
([label, raw, expected]) => [field, label, raw, spec, expected] as const
)
)
it.each(cases)('%s with %s is admitted at a fixed point', (_field, _label, raw, spec) => {
const { store, admission } = setup()
expect(admission.announce(observation(spec.carry(raw)))).toMatchObject({ accepted: true })
const stored = spec.read(store.getChild('child-1'))
expect(stored).toBe(normalizeChildWorkText(raw, spec.cap))
if (stored === undefined) {
return
it.each(cases)(
'%s with %s is admitted at a fixed point',
(_field, _label, raw, spec, expected) => {
const { store, admission } = setup()
expect(admission.announce(observation(spec.carry(raw)))).toMatchObject({ accepted: true })
const stored = spec.read(store.getChild('child-1'))
expect(stored).toBe(expected)
if (stored === undefined) {
return
}
expect(normalizeChildWorkText(stored, spec.cap)).toBe(stored)
// Independent of the normalizer: one line, trimmed, within the cap, no half pair at the cut.
expect(breaksOneLine(stored)).toBe(false)
expect(stored).toBe(stored.trim())
expect(stored.length).toBeLessThanOrEqual(spec.cap)
const last = stored.charCodeAt(stored.length - 1)
expect(last >= 0xd800 && last <= 0xdbff).toBe(false)
}
expect(normalizeChildWorkText(stored, spec.cap)).toBe(stored)
// Independent of the normalizer: one line, trimmed, within the cap, no half pair at the cut.
expect(breaksOneLine(stored)).toBe(false)
expect(stored).toBe(stored.trim())
expect(stored.length).toBeLessThanOrEqual(spec.cap)
const last = stored.charCodeAt(stored.length - 1)
expect(last >= 0xd800 && last <= 0xdbff).toBe(false)
})
)
it('keeps a surrogate pair whole when it ends exactly at the cap', () => {
const { store, admission } = setup()
+3 -2
View File
@@ -93,10 +93,11 @@ describe('AgentChildWorkRecord', () => {
})
it('parses a copied record with outcome, tokens, timing and bounded invocation history', () => {
const parsed = parseAgentChildWorkRecord({ ...child(), revision: 7 })
const value = { ...child(), revision: 7 }
const parsed = parseAgentChildWorkRecord(value)
expect(parsed).toEqual({ ...child(), revision: 7 })
expect(parsed).not.toBe(child())
expect(parsed).not.toBe(value)
expect(parsed?.parent).not.toBe(parent)
})