Files
next-tjwater-drainage-frontend/features/agent/streaming-token-response-state.test.ts
T

133 lines
4.1 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { readFileSync } from "node:fs";
import { join } from "node:path";
import {
createStreamingMarkdownBufferState,
isMarkdownOpenerBoundary,
resolveStreamingMarkdownDisplayContent,
splitStreamingText,
splitStreamingTextFallback,
takeNextStreamingMarkdownChunk,
} from "./components/streaming-token-response-state";
describe("streaming token response state", () => {
it("splits streaming text without dropping content", () => {
const content = "管网压力异常需要继续分析";
const chunks = splitStreamingText(content, "zh");
expect(chunks.length).toBeGreaterThan(0);
expect(chunks.join("")).toBe(content);
});
it("uses bounded code point chunks for fallback segmentation", () => {
expect(splitStreamingTextFallback("管网压力")).toEqual(["管网", "压力"]);
});
it("emits continuous Chinese text without waiting for markdown block closure", () => {
let state = createStreamingMarkdownBufferState("正在分析管网压力异常需要继续分析");
const chunks: string[] = [];
for (let index = 0; index < 20; index += 1) {
const next = takeNextStreamingMarkdownChunk(state);
state = next.state;
if (!next.chunk) {
break;
}
chunks.push(next.chunk);
}
expect(chunks.length).toBeGreaterThan(0);
expect(chunks.join("")).toBe("正在分析管网压力异常需要继续分析");
});
it("flushes a heading opener atomically", () => {
const next = takeNextStreamingMarkdownChunk(
createStreamingMarkdownBufferState("### 压力分析")
);
expect(next.chunk).toBe("### 压");
expect(isMarkdownOpenerBoundary("### 压力分析")).toBe(true);
});
it("waits for a heading opener to include text", () => {
const next = takeNextStreamingMarkdownChunk(
createStreamingMarkdownBufferState("### ")
);
expect(next.chunk).toBe("");
expect(next.state.displayContent).toBe("");
});
it("flushes list and quote openers atomically", () => {
expect(
takeNextStreamingMarkdownChunk(
createStreamingMarkdownBufferState("- 节点 A")
).chunk
).toBe("- 节");
expect(
takeNextStreamingMarkdownChunk(
createStreamingMarkdownBufferState("> 注意压力")
).chunk
).toBe("> 注");
});
it("flushes code and mermaid fence openers as complete lines", () => {
expect(
takeNextStreamingMarkdownChunk(
createStreamingMarkdownBufferState("```ts\nconst pressure = 1;")
).chunk
).toBe("```ts\n");
expect(
takeNextStreamingMarkdownChunk(
createStreamingMarkdownBufferState("```mermaid\ngraph TD")
).chunk
).toBe("```mermaid\n");
});
it("waits for fenced code openers to reach the line ending", () => {
const next = takeNextStreamingMarkdownChunk(
createStreamingMarkdownBufferState("```ts")
);
expect(next.chunk).toBe("");
expect(next.state.displayContent).toBe("");
});
it("flushes table header and separator rows atomically", () => {
const content = "| 节点 | 压力 |\n| --- | --- |\n| J1 | 0.32 |\n\n后续";
expect(
takeNextStreamingMarkdownChunk(
createStreamingMarkdownBufferState(content)
).chunk
).toBe("| 节点 | 压力 |\n| --- | --- |\n");
});
it("grows table rows after the table opener is visible", () => {
const tableOpener = "| 节点 | 压力 |\n| --- | --- |\n";
const state = createStreamingMarkdownBufferState(
`${tableOpener}| J1 | 0.32 |`,
tableOpener
);
expect(takeNextStreamingMarkdownChunk(state).chunk).not.toBe("");
});
it("flushes all display content after streaming is done", () => {
const content = "前文\n\n```ts\nconst pressure = 1;";
expect(resolveStreamingMarkdownDisplayContent(content, "前文", true)).toBe(content);
});
it("StreamingTokenResponse does not use closed-block markdown gating", () => {
const source = readFileSync(
join(process.cwd(), "features/agent/components/streaming-token-response.tsx"),
"utf8"
);
expect(source).not.toContain("splitStreamingMarkdownContent");
});
});