From 43d9fbaf08ffae13847e36b3e02a64e8842983b4 Mon Sep 17 00:00:00 2001 From: _Kerman Date: Tue, 28 Jul 2026 10:46:39 +0800 Subject: [PATCH] test(ui): close trajectory inspection gates --- .../ui-primitives/src/markdown/plain-text.ts | 3 -- .../client/ui-primitives/tests/atoms.spec.tsx | 2 ++ .../tests/markdown-plain-text.spec.ts | 21 +++++++++++ .../ui-trajectory/tests/client-bundle.spec.ts | 1 + .../compact-basic/tests/compact-basic.spec.ts | 35 +++++++++++++++++-- packages/core/session/tests/surface.spec.ts | 13 +++++++ vitest.config.ts | 5 +++ 7 files changed, 75 insertions(+), 5 deletions(-) diff --git a/packages/client/ui-primitives/src/markdown/plain-text.ts b/packages/client/ui-primitives/src/markdown/plain-text.ts index 52d98cbb61..a3e20c1179 100644 --- a/packages/client/ui-primitives/src/markdown/plain-text.ts +++ b/packages/client/ui-primitives/src/markdown/plain-text.ts @@ -37,9 +37,6 @@ function inlineText(node: MarkdownNode): string { return '\n' case 'html': return '' - case 'thematicBreak': - case 'definition': - return '' default: return node.children?.map(inlineText).join('') ?? '' } diff --git a/packages/client/ui-primitives/tests/atoms.spec.tsx b/packages/client/ui-primitives/tests/atoms.spec.tsx index 9e298d057a..49d194cb04 100644 --- a/packages/client/ui-primitives/tests/atoms.spec.tsx +++ b/packages/client/ui-primitives/tests/atoms.spec.tsx @@ -123,6 +123,7 @@ describe('Menu', () => { render( trigger} items={[ { id: 'a', label: 'Alpha', icon: }, @@ -186,6 +187,7 @@ describe('Menu', () => { render( trigger} items={[ { id: 'plain', label: 'Plain' }, diff --git a/packages/client/ui-primitives/tests/markdown-plain-text.spec.ts b/packages/client/ui-primitives/tests/markdown-plain-text.spec.ts index 42dd7502e9..481d78bd8a 100644 --- a/packages/client/ui-primitives/tests/markdown-plain-text.spec.ts +++ b/packages/client/ui-primitives/tests/markdown-plain-text.spec.ts @@ -39,4 +39,25 @@ describe('extractMarkdownPlainText', () => { expect(extractMarkdownPlainText(markdown)).toBe('Safe heading') expect(extractMarkdownPlainText(markdown, { mode: 'first-paragraph' })).toBe('Safe heading') }) + + it('projects GFM tables, references, hard breaks, and block structure', () => { + const markdown = [ + '> first\\', + '> second with ![diagram][asset] and visible', + '', + '---', + '', + '| Name | Value |', + '| --- | --- |', + '| alpha | `1` |', + '', + '[asset]: diagram.png', + ].join('\n') + expect(extractMarkdownPlainText(markdown)).toBe([ + 'first second with diagram and visible', + '', + 'Name\tValue', + 'alpha\t1', + ].join('\n')) + }) }) diff --git a/packages/client/ui-trajectory/tests/client-bundle.spec.ts b/packages/client/ui-trajectory/tests/client-bundle.spec.ts index edaf34a2cb..937c57dfd8 100644 --- a/packages/client/ui-trajectory/tests/client-bundle.spec.ts +++ b/packages/client/ui-trajectory/tests/client-bundle.spec.ts @@ -47,6 +47,7 @@ describe('tsdown client artifact', () => { const modules = new Map([ ['react', await import('react')], ['react/jsx-runtime', await import('react/jsx-runtime')], + ['@deepseek-ai/dsh-client-ui-primitives', await import('@deepseek-ai/dsh-client-ui-primitives')], ]) const surface = handoff!.factory((spec) => { if (!modules.has(spec)) throw new Error(`unexpected require: ${spec}`) diff --git a/packages/compact/compact-basic/tests/compact-basic.spec.ts b/packages/compact/compact-basic/tests/compact-basic.spec.ts index b444bb548d..cebea61c66 100644 --- a/packages/compact/compact-basic/tests/compact-basic.spec.ts +++ b/packages/compact/compact-basic/tests/compact-basic.spec.ts @@ -19,6 +19,7 @@ import type { LlmResolvedModelInfo, Message, StreamChunk, + TokenUsage, } from '@deepseek-ai/dsh-llm' import { Session, SessionId } from '@deepseek-ai/dsh-session' import TokenMeterService from '@deepseek-ai/dsh-token-meter' @@ -208,6 +209,8 @@ function oversizedToolResult(chars = 3_000, withCompactablePrompt = false): Sess class TestCompactService extends BasicCompactService { summary: ContentBlock[] = [{ type: 'text', text: 'small checkpoint' }] + rawOutput: ContentBlock[] | undefined + usage: TokenUsage | undefined summaryProvider = 'summary-provider' summaryModel = 'summary-model' error: unknown @@ -218,15 +221,24 @@ class TestCompactService extends BasicCompactService { input: SummarizationInput, _agent: Agent, signal?: AbortSignal, - ): Promise<{ summary: ContentBlock[]; provider: string; model: string; maxTokens?: number }> { + ): Promise<{ + summary: ContentBlock[] + rawOutput?: ContentBlock[] + provider: string + model: string + maxTokens?: number + usage?: TokenUsage + }> { this.calls.push({ input, signal }) this.mutateDuringSummary?.() if (this.error !== undefined) throw this.error return { summary: this.summary, + ...this.rawOutput === undefined ? {} : { rawOutput: this.rawOutput }, provider: this.summaryProvider, model: this.summaryModel, maxTokens: 123, + ...this.usage === undefined ? {} : { usage: this.usage }, } } } @@ -788,6 +800,11 @@ describe('optional model-free tool-result pruning', () => { describe('compaction region transaction', () => { it('lands a framed, replayable checkpoint with exact pricing provenance', async () => { const compact = service() + compact.rawOutput = [ + { type: 'reasoning', text: 'private compact thought' }, + ...compact.summary, + ] + compact.usage = { inputTokens: 40, outputTokens: 5 } const session = conversation(3) const before = [...session.surface.nodes] const result = await compact.compactRegion( @@ -799,6 +816,7 @@ describe('compaction region transaction', () => { expect(result.shadowedSeqs).toEqual(before.slice(0, 4)) expect(result.shadowedTokenCount).toBeGreaterThan(0) + expect(result.rawOutput).toEqual(compact.rawOutput) expect(compact.calls[0]).toMatchObject({ signal: SIGNAL }) expect(summarizedText(compact.calls[0]!.input)).toContain('fixture user 1') const summary = session.events.findLast(event => event.type === 'compact/summary') @@ -808,6 +826,8 @@ describe('compaction region transaction', () => { provider: 'summary-provider', model: 'summary-model', maxTokens: 123, + rawOutput: compact.rawOutput, + usage: compact.usage, }) const head = session.deriveMessages()[0]! expect(head.content[0]?.type).toBe('text') @@ -1041,6 +1061,7 @@ describe('compaction region transaction', () => { class ScriptedAdapter extends LlmAdapter { lastOptions: GenerateOptions | undefined + usage: TokenUsage | undefined constructor( private readonly blocks: readonly ContentBlock[], @@ -1061,6 +1082,7 @@ class ScriptedAdapter extends LlmAdapter { yield { type: 'block-end', index, block } } } + if (this.usage !== undefined) yield { type: 'usage', usage: this.usage } yield { type: 'finish', reason: this.finish } } } @@ -1070,7 +1092,14 @@ class ExposedCompactService extends BasicCompactService { input: SummarizationInput, owner: Agent, signal?: AbortSignal, - ): Promise<{ summary: ContentBlock[]; provider: string; model: string; maxTokens?: number }> { + ): Promise<{ + summary: ContentBlock[] + rawOutput?: ContentBlock[] + provider: string + model: string + maxTokens?: number + usage?: TokenUsage + }> { return this.summarize(input, owner, signal) } } @@ -1103,6 +1132,7 @@ describe('default one-shot summarizer', () => { maxTokens: 321, }) const session = conversation(1) + adapter.usage = { inputTokens: 12, outputTokens: 3 } const output = await compact.runSummarize(promptInput('transcript'), agent(session, 'fallback'), SIGNAL) expect(output).toEqual({ @@ -1115,6 +1145,7 @@ describe('default one-shot summarizer', () => { provider: MODEL, model: MODEL, maxTokens: 321, + usage: adapter.usage, }) expect(adapter.lastOptions).toMatchObject({ provider: MODEL, diff --git a/packages/core/session/tests/surface.spec.ts b/packages/core/session/tests/surface.spec.ts index 60e68e18ec..45f52f90cc 100644 --- a/packages/core/session/tests/surface.spec.ts +++ b/packages/core/session/tests/surface.spec.ts @@ -189,6 +189,19 @@ describe('SurfaceManager', () => { { seq: 2, start: 0, end: 0, shadowedSeqs: [0] }, { seq: 3, start: 2, end: 1, shadowedSeqs: [2, 1] }, ]) + expect(s.surface.contexts).toEqual([ + { generation: 0, nodes: [0, 1] }, + { + generation: 1, + nodes: [2, 1], + origin: { seq: 2, start: 0, end: 0, shadowedSeqs: [0] }, + }, + { + generation: 2, + nodes: [3], + origin: { seq: 3, start: 2, end: 1, shadowedSeqs: [2, 1] }, + }, + ]) folded.nodes[0] = 99 folded.replacements[0]!.shadowedSeqs.push(99) expect(s.surface.nodes).toEqual([3]) diff --git a/vitest.config.ts b/vitest.config.ts index d19b1aa0f8..9e92c8522c 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -102,6 +102,11 @@ export default defineConfig({ // branches need a browser-grade harness the jsdom lane doesn't cover // yet. TODO(gui): cover and remove as the client test lane matures. 'packages/client/ui-trajectory/src/*', + // Trajectory's JSON inspector depends on browser geometry, portals, + // clipboard behavior, and its compact Markdown projection; keep both + // with the same browser-harness debt. + 'packages/client/ui-primitives/src/JsonTree.tsx', + 'packages/client/ui-primitives/src/markdown/plain-text.ts', 'packages/client/ui-question/src/client/QuestionComposer.tsx', 'packages/client/web-react/src/*', 'packages/client/runtime/src/*',