Files
deepseek-harness/packages/experimental/claude-code-mods/tests/elements.spec.ts
T
Tianyi Cui f1715c67ec refactor(experimental): make mods DSH plugins and drop the persistence change
A mod is now a plugin built with defineMod() around its register(on,
options); the bridge is the ClaudeCodeMods service (ctx.claudeCodeMods)
that keeps the loaded mods and raises their events. The plugin.json /
hooks.json loader, pluginDirs, and dynamic imports are gone.

prompt.submit context becomes text blocks appended to the human's own
prompt message and $.prompt.submit queues a user-sourced message, so the
bridge adds no message source kind: the persistence record and the
regenerated schema are removed and the artifacts match master again.

Engine correctness from the review round: next() is memoized and every
started run beneath is awaited before a hook's answer settles; a rewritten
tool argument skips the hook with a report pointing at the pre-tool input
rewrite proposal; timers belong to the session whose event started them;
$.tool.call injects a tool's deferred contexts; $.clock.sleep honors the
event signal; a built-in tool answered with { result } is validated against
its output schema before it becomes an error-shaped result; hooks on events
this host never raises are reported at load.

Examples: the three mods from the Claude Code blog post under examples/,
Token Weather and its test unchanged, Blast Radius and Replay Theater
completed from the published fragments, run by their own tests through a
claude-code/testing alias over the new test kit ($.ui.mount/find/press).
2026-10-03 01:38:21 +08:00

55 lines
3.2 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import { findAll, isUiElement, renderText, serializeTree, treeProblem, uiElements } from '../src/elements.ts'
describe('elements', () => {
const { Box, Text, Button } = uiElements()
it('builds frozen elements the host recognizes, and validates what a hook returns', () => {
const text = Text({ color: 'yellow', bold: true, children: 'Clear' })
expect(text).toMatchObject({ type: 'Text', props: { color: 'yellow', bold: true, children: 'Clear' } })
expect(Object.isFrozen(text) && Object.isFrozen(text.props)).toBe(true)
expect(isUiElement(text)).toBe(true)
expect(isUiElement({ type: 'Text', props: {} })).toBe(false)
expect(treeProblem(Box({ children: [text, 'plain', 3, null, undefined, false, [Button({ label: 'Go' })]] }))).toBeUndefined()
expect(treeProblem({ type: 'Text', props: {} } as never)).toBe('a tree node is object, not an element, text, or a list of them')
expect(treeProblem(Button({ label: 42 as never }))).toBe('a Button needs a string label')
expect(treeProblem(Box({ children: Button({ label: 'x', onPress: 'nope' as never }) }))).toBe('a Button onPress must be a function')
expect(treeProblem(null)).toBeUndefined()
})
it('serializes scalars and children, replacing each onPress with a held action id', () => {
const held: (() => unknown)[] = []
const onPress = (): void => {}
const tree = Box({
flexDirection: 'row', paddingX: 1, border: true,
children: [Text({ dimColor: true, color: undefined, children: ['a', 'b'] }), Button({ label: 'Proceed', hotkey: '1', onPress }), Button({ label: 'Plain' }), '', 'tail'],
})
expect(serializeTree(tree, (callback) => { held.push(callback); return `action-${held.length}` })).toEqual([{
type: 'Box',
props: { flexDirection: 'row', paddingX: 1, border: true },
children: [
{ type: 'Text', props: { dimColor: true }, children: ['a', 'b'] },
{ type: 'Button', props: { label: 'Proceed', hotkey: '1' }, children: [], actionId: 'action-1' },
{ type: 'Button', props: { label: 'Plain' }, children: [] },
'tail',
],
}])
expect(held).toEqual([onPress])
})
it('finds elements by type, drawn text, and button label, and renders the drawn text', () => {
const tree = [
Box({ children: [Text({ children: '☀ Clear' }), Text({ children: ' 18% of context' })] }),
Box({ flexDirection: 'row', children: [Button({ label: 'Proceed', hotkey: '1' }), Button({ label: 'Cancel', hotkey: '2' })] }),
]
expect(findAll(tree, { type: 'Text', text: /Clear/ }).map(element => element.props['children'])).toEqual(['☀ Clear'])
expect(findAll(tree, { type: 'Text', text: '18%' })).toHaveLength(1)
expect(findAll(tree, { label: /Cancel/ }).map(element => element.props['hotkey'])).toEqual(['2'])
expect(findAll(tree, { type: 'Button', label: 'Proceed' })).toHaveLength(1)
expect(findAll(tree, { type: 'Box' })).toHaveLength(2)
expect(findAll(tree, { text: 'Clear' }).map(element => element.type)).toEqual(['Box', 'Text'])
expect(renderText(tree)).toBe('☀ Clear 18% of context\nProceedCancel')
expect(renderText('just text')).toBe('just text')
})
})