test(web-cards): update card specs and snapshots for collapsed ToolRow
Rewrite the read/search/web/diff card spec suites for the collapsed-by-default ToolRow composition: each row now needs the conversation `t`, and the card is absent from the DOM until the row's [data-expandable] toggle is clicked. Refresh the built-boot and search-card assembled snapshots (they expand the row before shaping the card) and the code-mode-round golden. Fix a fixture turn-number collision the 985/986 merge introduced (grep reused turn 67, already the multi-hunk edit's, so both shared one callId) by renumbering search/web/todo to 68-72. Document the refactor's behavior in the ui-conversation README (both languages) and add the Agent Note.
This commit is contained in:
@@ -112,11 +112,19 @@ it('boots the built plugin graph and renders a fixture session end to end', asyn
|
||||
}, { timeout: 10_000 })
|
||||
|
||||
// The write/edit turns render a real diff card through the assembled graph
|
||||
// (the keyed FileMutationRow + DiffBlock), not just the fixture's raw text.
|
||||
// The write turn's `hello fixture\n` proves the terminator rule end to end: a
|
||||
// trailing newline terminates its line, so the footer reads `+1` (not a
|
||||
// phantom `+2`) and one distinct file. The `+ ` prefix is a CSS ::before, so
|
||||
// it is absent from textContent — assert on the line body and the footer.
|
||||
// (the keyed FileMutationRow composing ToolRow + DiffBlock), not just the
|
||||
// fixture's raw text. The card is collapsed by default, so expand each edit/
|
||||
// write row first. The write turn's `hello fixture\n` proves the terminator
|
||||
// rule end to end: a trailing newline terminates its line, so the footer reads
|
||||
// `+1` (not a phantom `+2`) and one distinct file. The `+ ` prefix is a CSS
|
||||
// ::before, so it is absent from textContent — assert on the line body and the
|
||||
// footer.
|
||||
const mutationRows = [...document.querySelectorAll('[data-variant="write"],[data-variant="edit"]')]
|
||||
expect(mutationRows.length).toBeGreaterThan(0)
|
||||
for (const row of mutationRows) {
|
||||
const toggle = row.querySelector('[data-expandable]')
|
||||
if (toggle !== null) act(() => { fireEvent.click(toggle) })
|
||||
}
|
||||
const diffCards = [...document.querySelectorAll('[data-diff]')]
|
||||
expect(diffCards.length).toBeGreaterThan(0)
|
||||
const footers = diffCards.map(card => card.textContent ?? '')
|
||||
@@ -125,13 +133,20 @@ it('boots the built plugin graph and renders a fixture session end to end', asyn
|
||||
// The web render intent reaches the assembled boot graph: the fixture's
|
||||
// web_search / web_fetch turns render their keyed WebRow cards, proving the
|
||||
// registration, wire projection, and card rendering survive the real bundle
|
||||
// path (not just the per-package src benches). The selector pins the KEYED
|
||||
// WebRow (its own `data-variant="web"` wrapper), not the `[data-web]` attribute
|
||||
// WebBlock draws — the generic fallback renders the same WebBlock, so a silent
|
||||
// keyed-registration failure would still satisfy a bare `[data-web]` check.
|
||||
// path (not just the per-package src benches). WebRow composes ToolRow, so the
|
||||
// card is collapsed behind the row; the keyed row is pinned by its `data-tool`
|
||||
// (ToolRow sets it from the wire tool name).
|
||||
const webSearchRow = await waitFor(() => {
|
||||
const row = document.querySelector('[data-tool="web_search"]')
|
||||
expect(row).not.toBeNull()
|
||||
expect(document.querySelector('[data-tool="web_fetch"]')).not.toBeNull()
|
||||
return row!
|
||||
}, { timeout: 10_000 })
|
||||
// Expand the web_search row to prove its WebBlock card renders end to end.
|
||||
const webToggle = webSearchRow.querySelector('[data-expandable]')
|
||||
if (webToggle !== null) act(() => { fireEvent.click(webToggle) })
|
||||
await waitFor(() => {
|
||||
expect(document.querySelector('[data-variant="web"][data-tool="web_search"]')).not.toBeNull()
|
||||
expect(document.querySelector('[data-variant="web"][data-tool="web_fetch"]')).not.toBeNull()
|
||||
expect(webSearchRow.querySelector('[data-web]')).not.toBeNull()
|
||||
}, { timeout: 10_000 })
|
||||
|
||||
// Every bundle injected its plugin-owned style tag (the loader's CSS path).
|
||||
|
||||
@@ -145,15 +145,21 @@ describe('assembled search card', () => {
|
||||
await waitFor(() => {
|
||||
expect(document.querySelector('[data-sample="bash-global"]')).not.toBeNull()
|
||||
}, { timeout: 10_000 })
|
||||
// The grep turn's keyed SearchRow renders the card resident: wait for it.
|
||||
// The grep turn's keyed SearchRow composes ToolRow: the card is collapsed
|
||||
// by default, so wait for the summary row, then expand it to reach the card.
|
||||
await waitFor(() => {
|
||||
const tools = [...document.querySelectorAll('[data-tool]')].map(el => el.getAttribute('data-tool'))
|
||||
expect(tools, `tools present: ${tools.join(', ')}`).toContain('grep')
|
||||
}, { timeout: 10_000 })
|
||||
|
||||
// `data-tool` sits on the summary row; the card and recovery footer are its
|
||||
// siblings inside the SearchRow wrapper, so shape the wrapper (its parent).
|
||||
const grepRow = document.querySelector('[data-tool="grep"]')!.parentElement!
|
||||
// `data-tool` sits on the ToolRow root; the collapsed row is the expand
|
||||
// toggle. Click it so the card and its recovery footer mount, then shape the
|
||||
// whole row (the card lives inside ToolRow's body wrapper).
|
||||
const grepRow = document.querySelector('[data-tool="grep"]')!
|
||||
act(() => { fireEvent.click(grepRow.querySelector('[data-expandable]') ?? grepRow) })
|
||||
await waitFor(() => {
|
||||
expect(grepRow.querySelector('[data-search]')).not.toBeNull()
|
||||
}, { timeout: 10_000 })
|
||||
const shape = cardShape(grepRow)
|
||||
if (refreshing) {
|
||||
mkdirSync(dirname(EXPECTED), { recursive: true })
|
||||
|
||||
@@ -20,8 +20,10 @@
|
||||
- img
|
||||
- text: Code Run bash echo and catch missing file read
|
||||
- img
|
||||
- text: Bash Echo CODE_ROUND_OK 失败 Read
|
||||
- button "missing.txt"
|
||||
- text: Bash Echo CODE_ROUND_OK
|
||||
- 'button "Read Error: cannot read \"{{cwd}}/workspace/missing.txt\": not found"':
|
||||
- img
|
||||
- text: "Read Error: cannot read \"{{cwd}}/workspace/missing.txt\": not found"
|
||||
- button "Think The program ran successfully. Let me now reply DONE as instructed.":
|
||||
- img
|
||||
- img
|
||||
|
||||
@@ -8,4 +8,20 @@ line=141: const [collapsed, setCollapsed] = useState<ReadonlySet<number>>(() =
|
||||
line=73: const search = searchCardModel(block)
|
||||
line=90: <SearchBlock {...search.card} maxLines={CHAT_SEARCH_MAX_LINES} className={css.search} />
|
||||
line=113: ctx.slots.register({ name: 'conversation.chat.toolview', key: 'grep' }, SearchRow)
|
||||
expand=… 其余 4 行
|
||||
expand=… 其余 4 行
|
||||
recovery=Found 9 of 42 matches
|
||||
|
||||
packages/client/ui-primitives/src/SearchBlock.tsx
|
||||
Line 16: export const DEFAULT_SEARCH_MAX_LINES = 16
|
||||
Line 138: export function SearchBlock(props: SearchBlockProps) {
|
||||
Line 141: const [collapsed, setCollapsed] = useState<ReadonlySet<number>>(() => new Set())
|
||||
packages/client/ui-conversation/src/client/contract/search-card-model.ts
|
||||
Line 24: export const CHAT_SEARCH_MAX_LINES = 8
|
||||
Line 60: export function searchCardModel(block: ToolCallBlock): SearchCardModel | null {
|
||||
packages/client/ui-conversation/src/client/toolviews/search-row.tsx
|
||||
Line 71: export function SearchRow({ toolName, block }: ToolRowProps) {
|
||||
Line 73: const search = searchCardModel(block)
|
||||
Line 90: <SearchBlock {...search.card} maxLines={CHAT_SEARCH_MAX_LINES} className={css.search} />
|
||||
Line 113: ctx.slots.register({ name: 'conversation.chat.toolview', key: 'grep' }, SearchRow)
|
||||
|
||||
(Full grep result stored at: fixture://spill/grep-68. Read it to see every match.)
|
||||
Reference in New Issue
Block a user