Bonsai-Chat-WebGPU / src /app /components /ChatSurface.test.tsx
WaveCut's picture
Render ordered agent flow and edit messages
f3ad26c verified
Raw
History Blame Contribute Delete
8.03 kB
import { renderToStaticMarkup } from 'react-dom/server';
import { describe, expect, it } from 'vitest';
import type { ModelLoadProgress, ModelOption } from '../../lib/contracts';
import { ChatSurface } from './ChatSurface';
const model: ModelOption = {
id: 'bonsai-1.7b',
manifestId: '1_7b',
label: 'Bonsai 1.7B',
architectureLabel: 'Qwen3 dense',
parameters: '1.7B',
runtimeLabel: 'WebGPU · WASM fallback',
footprint: '237 MiB',
availability: 'loaded',
contextLimit: 32_768,
defaultContext: 4_096,
};
const progress: ModelLoadProgress = {
modelId: model.id,
modelLabel: model.label,
stage: 'ready',
stageProgress: 1,
downloadedBytes: 1,
totalBytes: 1,
residentBytes: 1,
nativeStage: null,
shards: [],
};
describe('ChatSurface message actions', () => {
it('puts copy and role-specific regeneration controls under messages', () => {
const html = renderToStaticMarkup(
<ChatSurface
activeModel={model}
loadedModelId={model.id}
messages={[
{ id: 'user', role: 'user', content: 'Write code', timestamp: '10:00' },
{ id: 'assistant', role: 'assistant', content: 'Done', timestamp: '10:01' },
]}
streamState="complete"
modelLoadProgress={progress}
toolsEnabled={false}
runtimeStatus="Ready"
error={null}
shardRetry={null}
theme="light"
onLoadModel={() => undefined}
onDismissError={() => undefined}
onRetryShard={() => undefined}
onSend={() => undefined}
onRegenerate={() => undefined}
onEditUserMessage={() => undefined}
onCancel={() => undefined}
onToolsEnabledChange={() => undefined}
onOpenConversationList={() => undefined}
onOpenSettings={() => undefined}
/>,
);
expect(html.match(/aria-label="Copy message"/g)).toHaveLength(2);
expect(html).toContain('aria-label="Regenerate from this message"');
expect(html).toContain('aria-label="Regenerate this response"');
expect(html).toContain('aria-label="Edit message and regenerate response"');
expect(html).not.toContain('Regenerate last');
expect(html).toContain('aria-label="Show available tools"');
expect(html).toContain('<code>html_artifact</code>');
expect(html).toContain('<code>artifact_update</code>');
expect(html).toContain('<code>bar_chart</code>');
expect(html).toContain('<code>pie_chart</code>');
expect(html).toContain('<code>web_search</code>');
expect(html).toContain('Network tools ask before sending a query.');
});
it('keeps text visible and identifies a structured tool call while streaming', () => {
const html = renderToStaticMarkup(
<ChatSurface
activeModel={model}
loadedModelId={model.id}
messages={[
{
id: 'text',
role: 'assistant',
content: 'Visible streamed text',
timestamp: '10:00',
state: 'streaming',
},
{
id: 'tool',
role: 'assistant',
content: '',
timestamp: '10:01',
state: 'streaming',
toolActivity: { name: 'html_artifact', argumentCharacters: 12_345 },
},
]}
streamState="streaming"
modelLoadProgress={progress}
toolsEnabled
runtimeStatus="Generating"
error={null}
shardRetry={null}
theme="light"
onLoadModel={() => undefined}
onDismissError={() => undefined}
onRetryShard={() => undefined}
onSend={() => undefined}
onRegenerate={() => undefined}
onEditUserMessage={() => undefined}
onCancel={() => undefined}
onToolsEnabledChange={() => undefined}
onOpenConversationList={() => undefined}
onOpenSettings={() => undefined}
/>,
);
expect(html).toContain('Visible streamed text');
expect(html).toContain('Generating html_artifact tool call');
expect(html).toContain('<code>html_artifact</code>');
expect(html).toContain('12,345 chars');
expect(html.match(/stream-cursor/g)).toHaveLength(2);
});
it('renders generated artifacts inline with HTML and Rendered tabs', () => {
const html = renderToStaticMarkup(
<ChatSurface
activeModel={model}
loadedModelId={model.id}
messages={[{
id: 'assistant',
role: 'assistant',
content: 'Here is the app.',
timestamp: '10:01',
artifacts: [{
id: 'artifact-one',
title: 'Counter app',
source: '<main>Counter</main>',
sandboxedSource: '<!doctype html><main>Counter</main>',
runtimeToken: 'artifact-one-runtime',
createdAt: 1,
}],
}]}
streamState="complete"
modelLoadProgress={progress}
toolsEnabled
runtimeStatus="Ready"
error={null}
shardRetry={null}
theme="light"
onLoadModel={() => undefined}
onDismissError={() => undefined}
onRetryShard={() => undefined}
onSend={() => undefined}
onRegenerate={() => undefined}
onEditUserMessage={() => undefined}
onCancel={() => undefined}
onToolsEnabledChange={() => undefined}
onOpenConversationList={() => undefined}
onOpenSettings={() => undefined}
/>,
);
expect(html).toContain('id="artifact-artifact-one"');
expect(html).toContain('role="tab" aria-selected="true"');
expect(html).toContain('>HTML</button>');
expect(html).toContain('>Rendered</button>');
expect(html).toContain('&lt;main&gt;Counter&lt;/main&gt;');
expect(html).not.toContain('<iframe');
});
it('keeps text, tool results, artifacts, and later text in chronological order', () => {
const html = renderToStaticMarkup(
<ChatSurface
activeModel={model}
loadedModelId={model.id}
messages={[{
id: 'assistant',
role: 'assistant',
content: 'Before the chart. After the chart.',
timestamp: '10:01',
segments: [
{ id: 'before', kind: 'text', content: 'Before the chart.' },
{ id: 'chart-call', kind: 'tool', toolCallId: 'tool-one' },
{ id: 'after', kind: 'text', content: 'After the chart.' },
],
tools: [{
id: 'tool-one',
name: 'bar_chart',
input: '{"title":"Quarterly sales"}',
output: '{"ok":true,"evaluation":{"status":"passed","errors":[]}}',
state: 'complete',
artifactId: 'artifact-one',
}],
artifacts: [{
id: 'artifact-one',
title: 'Quarterly sales',
source: '<main>Quarterly sales</main>',
sandboxedSource: '<!doctype html><main>Quarterly sales</main>',
runtimeToken: 'artifact-one-runtime',
createdAt: 1,
}],
}]}
streamState="complete"
modelLoadProgress={progress}
toolsEnabled
runtimeStatus="Ready"
error={null}
shardRetry={null}
theme="light"
onLoadModel={() => undefined}
onDismissError={() => undefined}
onRetryShard={() => undefined}
onSend={() => undefined}
onRegenerate={() => undefined}
onEditUserMessage={() => undefined}
onCancel={() => undefined}
onToolsEnabledChange={() => undefined}
onOpenConversationList={() => undefined}
onOpenSettings={() => undefined}
/>,
);
const before = html.indexOf('Before the chart.');
const tool = html.indexOf('<code>bar_chart</code>');
const artifact = html.indexOf('id="artifact-artifact-one"');
const after = html.indexOf('After the chart.');
expect(before).toBeGreaterThan(-1);
expect(tool).toBeGreaterThan(before);
expect(artifact).toBeGreaterThan(tool);
expect(after).toBeGreaterThan(artifact);
});
});