返回 DeepSeek-Reasonix
reasoning-summary.test.ts
根目录 / desktop / frontend / src / __tests__ / reasoning-summary.test.ts
1 // Run: tsx src/__tests__/reasoning-summary.test.ts
2
3 import { reasoningSummaryText } from "../lib/reasoningSummary";
4
5 let passed = 0;
6 let failed = 0;
7
8 function eq(a: unknown, b: unknown, label: string) {
9 if (a === b) {
10 process.stdout.write(` PASS ${label}\n`);
11 passed += 1;
12 } else {
13 process.stdout.write(` FAIL ${label}: expected ${JSON.stringify(b)}, got ${JSON.stringify(a)}\n`);
14 failed += 1;
15 }
16 }
17
18 console.log("\nreasoning summary");
19
20 eq(
21 reasoningSummaryText("first thought\n\nsecond thought", { streaming: false }),
22 "first thought",
23 "completed reasoning summarizes to the first non-blank line",
24 );
25
26 eq(
27 reasoningSummaryText("first thought\n\nlatest thought", { streaming: true }),
28 "latest thought",
29 "streaming reasoning summarizes to the last non-blank line",
30 );
31
32 eq(
33 reasoningSummaryText("\n\n \nfirst thought\nrest", { streaming: false }),
34 "first thought",
35 "leading blank lines are skipped when completed",
36 );
37
38 eq(
39 reasoningSummaryText("first thought\n\nlatest thought\n\n\n", { streaming: true }),
40 "latest thought",
41 "trailing blank lines are skipped while streaming",
42 );
43
44 eq(
45 reasoningSummaryText("first thought\n\nlatest thought\n \t ", { streaming: true }),
46 "latest thought",
47 "trailing whitespace-only lines are skipped while streaming",
48 );
49
50 eq(
51 reasoningSummaryText("first thought\r\n\r\nsecond thought\r\n", { streaming: false }),
52 "first thought",
53 "handles CRLF line endings when completed",
54 );
55
56 eq(
57 reasoningSummaryText("first thought\r\n\r\nlatest thought\r\n", { streaming: true }),
58 "latest thought",
59 "handles CRLF line endings while streaming",
60 );
61
62 eq(
63 reasoningSummaryText(" spaced out \t line \nnext", { streaming: false }),
64 "spaced out line",
65 "collapses intra-line whitespace",
66 );
67
68 eq(reasoningSummaryText("", { streaming: false }), "", "empty reasoning yields an empty summary");
69 eq(reasoningSummaryText(" \n \r\n \n", { streaming: true }), "", "blank-only reasoning yields an empty summary");
70
71 const longLine = "word ".repeat(100).trim();
72 eq(
73 reasoningSummaryText(longLine, { streaming: false }).length <= 180,
74 true,
75 "summaries are bounded to the default character budget",
76 );
77 eq(
78 reasoningSummaryText(longLine, { streaming: false, maxChars: 10 }),
79 "word word…",
80 "honors a custom character budget",
81 );
82
83 const streamingTail = `${"a".repeat(220)}LATEST_TOKEN`;
84 eq(
85 reasoningSummaryText(streamingTail, { streaming: true }),
86 `…${"a".repeat(167)}LATEST_TOKEN`,
87 "long streaming lines retain the newest text within the character budget",
88 );
89 eq(
90 reasoningSummaryText("abcdef", { streaming: false, maxChars: 0 }),
91 "",
92 "a zero character budget yields an empty summary",
93 );
94 eq(
95 reasoningSummaryText("abcdef", { streaming: false, maxChars: -1 }),
96 "",
97 "a negative character budget clamps to zero",
98 );
99 eq(
100 reasoningSummaryText("abcdef", { streaming: false, maxChars: 1 }),
101 "…",
102 "a one character budget contains only the truncation marker",
103 );
104
105 const emojiLine = "🙂".repeat(200);
106 const emojiSummary = reasoningSummaryText(emojiLine, { streaming: false });
107 eq(
108 emojiSummary,
109 `${"🙂".repeat(179)}…`,
110 "truncation never splits a surrogate pair",
111 );
112
113 // The summary scans from one end of the string instead of splitting the whole
114 // input into a line array — long streaming reasoning must not allocate one
115 // per token. Forbid split() outright and run a large input through it.
116 const huge = `${Array.from({ length: 20_000 }, (_, i) => `line ${i}`).join("\n")}\n\ntail line\n`;
117 const originalSplit = String.prototype.split;
118 String.prototype.split = (() => {
119 throw new Error("split() must not be used");
120 }) as typeof String.prototype.split;
121 try {
122 eq(reasoningSummaryText(huge, { streaming: false }), "line 0", "completed summary does not split the whole input");
123 eq(reasoningSummaryText(huge, { streaming: true }), "tail line", "streaming summary does not split the whole input");
124 } finally {
125 String.prototype.split = originalSplit;
126 }
127
128 console.log(`\n${passed} passed, ${failed} failed`);
129 if (failed > 0) process.exit(1);
130
130 lines TYPESCRIPT