Files
bowenliang123 87a2893190 refactor(comments): clean up the test-suite comments to the guideline
Apply the AGENTS.md comments guideline to every .ts under tests/: remove
what the code already expresses, keep only what it cannot.

Removed: spec-header coverage inventories the describe/test tree already
names; comments restating the next line's assertion or its message; helper
JSDoc whose body already says it; within-file duplicates; five stale
claims (trendChart's "six aggregated turns" against agg.length === 8,
agentGraph's "six categories" against CATS' seven, fold-timing's "cheapest
call LAST" against a loop that feeds it first, fold-surface's unreachable
plain-JSON note, contextView's "two turns" for a one-turn fixture).

Kept and condensed: the projection-freeze class, strict wire/state-schema
hazards, retention and pricing windows, pairing rules, hostile-input
rationale, fixture and expectation-array ground truth, jsdom mechanics,
and issue-numbered regressions.

Comment lines 2604 -> 2014 (-22%); 85 module headers kept, 30 coverage
inventories dropped; every surviving comment line is within the 140-column
limit, with 36 wrapped lines joined onto fuller ones.

No logic, string, identifier, import or directive changed: a comment-strip
gate proves each file's comment-free code is byte-identical to HEAD, and a
line-level proof pairs all 845 changed lines with an unchanged code part.
pnpm run lint && pnpm run test (106 files / 2061 tests, per-file 100%
coverage) && pnpm run build all pass.
2026-10-10 00:50:55 +08:00

195 lines
8.8 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { createElement as h } from 'react'
import assert from 'node:assert/strict'
import { describe, test } from 'vitest'
import { makeContextJumpButton } from '../../../src/client/components/contextJump'
import type { ConversationNodeLike, UseChatLike } from '../../../src/client/services'
import { takeContextFocus } from '../../../src/client/viewFocus'
import { DICT_EN } from '../../../src/client/i18n'
import { asClientCtx, TestClientCtx } from '../helpers/harness'
import { click, makeKit, mount, query, queryAll } from '../helpers/kit'
const kit = makeKit()
const Jump = makeContextJumpButton(asClientCtx(new TestClientCtx()), kit)
const SESSION = 'sv-jump'
/** The chat node seat: the ChatSnapshot arm carrying assistant nodes with message ids (hostile shapes forced past the seat type). */
function chatWith(nodes: unknown): { useChat?: UseChatLike } {
return { useChat: (sel => sel({ legacy: { nodes: nodes as readonly ConversationNodeLike[] } })) as UseChatLike }
}
const reply = { kind: 'assistant', seq: 4, messageId: 'msg-4' }
const toolNode = { kind: 'tool', seq: 3, messageId: 'msg-tool' }
describe('ContextJump — seat guards', () => {
test('a non-string or empty message id renders nothing', async () => {
const m1 = await mount(h(Jump, {}))
assert.equal(queryAll(m1.container, 'button').length, 0)
await m1.unmount()
const m2 = await mount(h(Jump, { messageId: '' }))
assert.equal(queryAll(m2.container, 'button').length, 0)
await m2.unmount()
const m3 = await mount(h(Jump, { messageId: 42 }))
assert.equal(queryAll(m3.container, 'button').length, 0)
await m3.unmount()
})
test('renders the labelled icon button for a finalized reply', async () => {
const m = await mount(h(Jump, { messageId: 'msg-4' }))
const btn = query(m.container, 'button.lc-jump')
assert.equal(btn.getAttribute('aria-label'), DICT_EN['jump.title'])
assert.ok(btn.querySelector('svg') !== null)
await m.unmount()
})
test('a click with no served node seat switches tabs and mints no focus', async () => {
const m = await mount(h(Jump, { messageId: 'msg-4', sessionId: SESSION }))
await click(query(m.container, 'button.lc-jump'))
assert.equal(takeContextFocus(SESSION), null)
await m.unmount()
})
})
describe('ContextJump — click flow', () => {
test('no sidebar face: resolves the reply seq, records the relay, and falls back to the tab', async () => {
const bar = document.createElement('div')
const chat = document.createElement('button')
chat.setAttribute('role', 'tab')
chat.setAttribute('aria-selected', 'true')
chat.textContent = 'Chat'
const context = document.createElement('button')
context.setAttribute('role', 'tab')
context.textContent = 'Context'
let contextClicks = 0
context.addEventListener('click', () => { contextClicks++ })
bar.append(chat, context)
document.body.appendChild(bar)
try {
const m = await mount(h(Jump, { messageId: 'msg-4', sessionId: SESSION, ...chatWith([reply, toolNode]) }))
await click(query(m.container, 'button.lc-jump'))
assert.equal(takeContextFocus(SESSION), 4, 'the clicked reply’s request seq rides the relay')
assert.equal(contextClicks, 1, 'the Context tab button was clicked')
assert.equal(takeContextFocus(SESSION), null)
await m.unmount()
} finally {
bar.remove()
}
})
test('the seq resolves at render and the click never re-reads the seat (a real hook is render-only)', async () => {
let reads = 0
const useChat = ((sel: (s: unknown) => unknown) => {
reads++
return sel({ legacy: { nodes: [reply] as readonly ConversationNodeLike[] } })
}) as UseChatLike
const m = await mount(h(Jump, { messageId: 'msg-4', sessionId: SESSION, useChat }))
const before = reads
await click(query(m.container, 'button.lc-jump'))
assert.equal(reads, before, 'no click-time seat read — the production seat throws off-render')
assert.equal(takeContextFocus(SESSION), 4, 'the render-resolved seq rides the relay')
await m.unmount()
})
test('the seq resolution prefers the matching assistant node and drops non-finite seqs', async () => {
const m = await mount(h(Jump, { messageId: 'msg-tool', sessionId: SESSION, ...chatWith([reply, toolNode]) }))
await click(query(m.container, 'button.lc-jump'))
assert.equal(takeContextFocus(SESSION), null, 'a tool node never mints a focus')
await m.unmount()
const broken = { kind: 'assistant', seq: Number.NaN, messageId: 'msg-x' }
const m2 = await mount(h(Jump, { messageId: 'msg-x', sessionId: SESSION, ...chatWith([broken]) }))
await click(query(m2.container, 'button.lc-jump'))
assert.equal(takeContextFocus(SESSION), null, 'a non-finite seq never mints a focus')
await m2.unmount()
})
test('a hostile node that throws on property access is skipped, not fatal', async () => {
const hostile = { get kind(): string { throw new Error('boom') } }
const m = await mount(h(Jump, { messageId: 'msg-4', sessionId: SESSION, ...chatWith([hostile, reply]) }))
await click(query(m.container, 'button.lc-jump'))
assert.equal(takeContextFocus(SESSION), 4, 'the real reply after the hostile element still resolves')
await m.unmount()
const hostileMatch = {
get kind(): string { return 'assistant' },
get messageId(): string { throw new Error('boom') },
}
const m2 = await mount(h(Jump, { messageId: 'msg-9', sessionId: SESSION, ...chatWith([hostileMatch]) }))
await click(query(m2.container, 'button.lc-jump'))
assert.equal(takeContextFocus(SESSION), null, 'a hostile match candidate degrades to no focus')
await m2.unmount()
})
test('an unknown reply and a missing session still switch tabs, minting no relay entry', async () => {
const bar = document.createElement('div')
const context = document.createElement('button')
context.setAttribute('role', 'tab')
context.textContent = 'Context'
let contextClicks = 0
context.addEventListener('click', () => { contextClicks++ })
bar.appendChild(context)
document.body.appendChild(bar)
try {
const m = await mount(h(Jump, { messageId: 'msg-none', sessionId: SESSION, ...chatWith([reply]) }))
await click(query(m.container, 'button.lc-jump'))
assert.equal(takeContextFocus(SESSION), null)
assert.equal(contextClicks, 1, 'the tab still opens, just without a pinned step')
await m.unmount()
const m2 = await mount(h(Jump, { messageId: 'msg-4', sessionId: 7, ...chatWith([reply]) }))
await click(query(m2.container, 'button.lc-jump'))
assert.equal(contextClicks, 2)
await m2.unmount()
} finally {
bar.remove()
}
})
})
describe('ContextJump — sidebar landing', () => {
test('a served sidebarRight face opens the Context tab there, leaving the conversation tab alone', async () => {
const opened: string[] = []
const ctx = new TestClientCtx({ services: { sidebarRight: { openTab: (kind: string) => { opened.push(kind) } } } })
const SidebarJump = makeContextJumpButton(asClientCtx(ctx), kit)
const bar = document.createElement('div')
const context = document.createElement('button')
context.setAttribute('role', 'tab')
context.textContent = 'Context'
let contextClicks = 0
context.addEventListener('click', () => { contextClicks++ })
bar.appendChild(context)
document.body.appendChild(bar)
try {
const m = await mount(h(SidebarJump, { messageId: 'msg-4', sessionId: SESSION, ...chatWith([reply]) }))
await click(query(m.container, 'button.lc-jump'))
assert.deepEqual(opened, ['dsh-context'], 'the sidebar Context tab was opened by kind')
assert.equal(takeContextFocus(SESSION), 4, 'the clicked reply’s request seq rides the relay')
assert.equal(contextClicks, 0, 'the conversation tab was not touched')
await m.unmount()
} finally {
bar.remove()
}
})
test('an openTab that refuses (no mounted surface, unregistered kind) falls back to the tab', async () => {
const ctx = new TestClientCtx({ services: { sidebarRight: { openTab: () => { throw new Error('refused') } } } })
const SidebarJump = makeContextJumpButton(asClientCtx(ctx), kit)
const bar = document.createElement('div')
const context = document.createElement('button')
context.setAttribute('role', 'tab')
context.textContent = 'Context'
let contextClicks = 0
context.addEventListener('click', () => { contextClicks++ })
bar.appendChild(context)
document.body.appendChild(bar)
try {
const m = await mount(h(SidebarJump, { messageId: 'msg-4', sessionId: SESSION, ...chatWith([reply]) }))
await click(query(m.container, 'button.lc-jump'))
assert.equal(contextClicks, 1, 'the refused open falls back to the conversation tab')
await m.unmount()
} finally {
bar.remove()
}
})
})