137 lines
5.7 KiB
JavaScript
137 lines
5.7 KiB
JavaScript
|
|
// Diff-card rendering must survive the wire meta shape today's runtime emits.
|
||
|
|
//
|
||
|
|
// # Why this test exists
|
||
|
|
//
|
||
|
|
// Two facts about the current runtime (see docs/upstream-ledger.md
|
||
|
|
// "runtime should emit presented view"):
|
||
|
|
//
|
||
|
|
// 1. `agent-loop` persists the tool's raw `execute()` meta verbatim on the
|
||
|
|
// `tool/result` event. For `fs.edit`, that shape is `{diffs: [...]}` —
|
||
|
|
// NO `card` field. (packages/fs/tool-fs/src/edit.ts:92-96,
|
||
|
|
// packages/core/agent-loop/src/loop.ts around the tool/result emit.)
|
||
|
|
// 2. The tool's `presentResult()` — which WOULD add `card: 'diff'` — is a
|
||
|
|
// display-time callback the runtime does not invoke. So the renderer
|
||
|
|
// never sees a `card: 'diff'` for fs.edit on the wire today.
|
||
|
|
//
|
||
|
|
// The dispatch at src/renderer/renderer.js:4744 primarily routes by
|
||
|
|
// `view.card === 'diff'`; without a fallback the diff card is unreachable
|
||
|
|
// on the shipped default profile — as observed in lane-showcase 12/12 run
|
||
|
|
// (check 5_diff_card_fs = fail, 2026-07-18).
|
||
|
|
//
|
||
|
|
// # What this test locks
|
||
|
|
//
|
||
|
|
// - Given the real wire shape (fixture pinned in-repo — see the fixture's
|
||
|
|
// own header for provenance), driving `tool/call` + `tool/result` through
|
||
|
|
// the renderer's reducer must produce a `.card-diff` element inside the
|
||
|
|
// result box.
|
||
|
|
// - The primary path (`view.card === 'diff'`) still works — a second
|
||
|
|
// assertion drives the same fs.edit callId but with the `card:'diff'`
|
||
|
|
// shape a future runtime seam would emit. Both routes render the card.
|
||
|
|
//
|
||
|
|
// If the wire shape ever gains a `card` discriminant natively, the fallback
|
||
|
|
// becomes dead code but this test still passes via the primary branch, so
|
||
|
|
// there is no rush to prune it.
|
||
|
|
|
||
|
|
'use strict'
|
||
|
|
|
||
|
|
const test = require('node:test')
|
||
|
|
const assert = require('node:assert/strict')
|
||
|
|
const fs = require('node:fs')
|
||
|
|
const path = require('node:path')
|
||
|
|
const { loadRenderer } = require('./renderer-harness')
|
||
|
|
|
||
|
|
const FIXTURE = JSON.parse(
|
||
|
|
fs.readFileSync(path.join(__dirname, 'fixtures', 'fs-edit-wire-shape.json'), 'utf8'),
|
||
|
|
)
|
||
|
|
|
||
|
|
// Locate a descendant element by CSS class name. Mirrors the renderer's
|
||
|
|
// probe hook approach (see docs/renderer-probe.md); the shim in
|
||
|
|
// renderer-harness.js only supports simple class selectors so we walk
|
||
|
|
// the tree by hand to be robust across nested structures.
|
||
|
|
function findByClass(root, className) {
|
||
|
|
if (!root) return null
|
||
|
|
const classList = root.classList
|
||
|
|
if (classList && typeof classList.contains === 'function' && classList.contains(className)) {
|
||
|
|
return root
|
||
|
|
}
|
||
|
|
if (Array.isArray(root.children)) {
|
||
|
|
for (const child of root.children) {
|
||
|
|
const hit = findByClass(child, className)
|
||
|
|
if (hit) return hit
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return null
|
||
|
|
}
|
||
|
|
|
||
|
|
test('diff card renders when the wire meta has {diffs} but no card discriminant (fs family fallback)', async () => {
|
||
|
|
const { renderer, window, document: doc } = await loadRenderer()
|
||
|
|
// tool-cards.js is preloaded as a CommonJS module (see renderer-harness.js
|
||
|
|
// preloadPure). Its `renderDiffCard` reads `document` from the module's
|
||
|
|
// enclosing scope; when triggered from a Node test rather than inside the
|
||
|
|
// renderer wrapper, that reference is the global `document`. The
|
||
|
|
// convention (see tool-cards.test.js) is to set it globally per-test.
|
||
|
|
global.document = doc
|
||
|
|
renderer.ensureSession('s1', { title: 's', header: {} })
|
||
|
|
await renderer.selectSession('s1')
|
||
|
|
|
||
|
|
// The real runtime always emits tool/call before tool/result — the
|
||
|
|
// renderer allocates the resBox on tool/call. Drive that sequence.
|
||
|
|
renderer.onSessionEvent('s1', FIXTURE.toolCall)
|
||
|
|
renderer.onSessionEvent('s1', FIXTURE.toolResult)
|
||
|
|
|
||
|
|
const meta = renderer.getSessionMeta('s1')
|
||
|
|
const resBox = meta.toolCalls.get(FIXTURE.toolCall.data.callId)
|
||
|
|
assert.ok(resBox, 'renderer must allocate a result box on tool/call')
|
||
|
|
|
||
|
|
const diffCard = findByClass(resBox, 'card-diff')
|
||
|
|
assert.ok(
|
||
|
|
diffCard,
|
||
|
|
'fs.edit tool/result with {diffs:[...]} (no card field) must still render '
|
||
|
|
+ 'a .card-diff — see docs/upstream-ledger.md "runtime should emit presented view"',
|
||
|
|
)
|
||
|
|
|
||
|
|
// Sanity: the raw text fallback branch must NOT have swallowed the box.
|
||
|
|
// If the fallback misfires, resBox.textContent gets the flat text content
|
||
|
|
// instead of the diff card being appended.
|
||
|
|
assert.doesNotMatch(
|
||
|
|
(resBox.textContent || ''),
|
||
|
|
/Edited \/tmp\/dsh-showcase\/seed-3lines\.txt/,
|
||
|
|
'fallback text must not fire when the diff card renders',
|
||
|
|
)
|
||
|
|
|
||
|
|
// Reference the window stub so the harness knows it's live — silences
|
||
|
|
// the lint about the unused destructured var and documents that this
|
||
|
|
// test intentionally observes DOM state through the shared shim.
|
||
|
|
assert.ok(window)
|
||
|
|
})
|
||
|
|
|
||
|
|
test('diff card renders on the primary path when the wire ever emits card:"diff" natively', async () => {
|
||
|
|
const { renderer, document: doc } = await loadRenderer()
|
||
|
|
global.document = doc
|
||
|
|
renderer.ensureSession('s1', { title: 's', header: {} })
|
||
|
|
await renderer.selectSession('s1')
|
||
|
|
|
||
|
|
renderer.onSessionEvent('s1', FIXTURE.toolCall)
|
||
|
|
// Same fixture, but simulate the future-good wire shape by re-wrapping
|
||
|
|
// the meta with the discriminant a presentResult-emitting runtime would
|
||
|
|
// author. If the primary branch ever regresses, this catches it before
|
||
|
|
// the fallback ever runs.
|
||
|
|
const primaryResult = {
|
||
|
|
...FIXTURE.toolResult,
|
||
|
|
data: {
|
||
|
|
...FIXTURE.toolResult.data,
|
||
|
|
meta: {
|
||
|
|
card: 'diff',
|
||
|
|
title: 'Edit /tmp/dsh-showcase/seed-3lines.txt',
|
||
|
|
diffs: FIXTURE.toolResult.data.meta.diffs,
|
||
|
|
},
|
||
|
|
},
|
||
|
|
}
|
||
|
|
renderer.onSessionEvent('s1', primaryResult)
|
||
|
|
|
||
|
|
const meta = renderer.getSessionMeta('s1')
|
||
|
|
const resBox = meta.toolCalls.get(FIXTURE.toolCall.data.callId)
|
||
|
|
const diffCard = findByClass(resBox, 'card-diff')
|
||
|
|
assert.ok(diffCard, 'primary card:"diff" dispatch must render .card-diff')
|
||
|
|
})
|