|
1 | 1 | import { describe, expect, test } from "bun:test"; |
2 | | -import { defined } from "../../testkit/defined.js"; |
3 | 2 | import { |
4 | | - createMemoizedParseMarkdown, |
5 | 3 | nextStreamMarkdownState, |
6 | 4 | parseMarkdown, |
7 | 5 | splitAtSettledBlock, |
@@ -116,74 +114,6 @@ describe("block elements", () => { |
116 | 114 | expect(segs[0]?.rule).toBe(true); |
117 | 115 | expect(segs[0]?.text).not.toContain("-"); |
118 | 116 | }); |
119 | | - |
120 | | - test("fenced code block renders inner lines as code without inline parsing", () => { |
121 | | - const lines = parseMarkdown("```\nconst x = **not bold**\n```"); |
122 | | - const body = lines.flat().find((s) => s.text.includes("const x")); |
123 | | - expect(body?.code).toBe(true); |
124 | | - expect(body?.text).toContain("const x = **not bold**"); |
125 | | - expect(lines[0]?.[0]?.text).toContain("╭"); |
126 | | - }); |
127 | | - |
128 | | - test("a fenced block keeps its language cap with a plain body", () => { |
129 | | - const lines = parseMarkdown('```js\nconst x = "hi";\n```'); |
130 | | - const body = lines.flat().find((s) => s.text.includes("const x")); |
131 | | - expect(body?.code).toBe(true); |
132 | | - expect(body?.color).toBeUndefined(); |
133 | | - expect(lines[0]?.map((s) => s.text).join("")).toContain("js"); |
134 | | - }); |
135 | | - |
136 | | - test("an unclosed streaming fence keeps a plain body", () => { |
137 | | - const lines = parseMarkdown("```js\nconst x = 1;"); |
138 | | - const body = lines.flat().find((s) => s.text.includes("const x")); |
139 | | - expect(body?.code).toBe(true); |
140 | | - expect(body?.color).toBeUndefined(); |
141 | | - }); |
142 | | - |
143 | | - test("drops a half-typed closing fence from the streaming tail", () => { |
144 | | - const lines = parseMarkdown("```js\nconst x = 1;\n``"); |
145 | | - const rendered = lines |
146 | | - .flat() |
147 | | - .map((s) => s.text) |
148 | | - .join(""); |
149 | | - expect(rendered).not.toContain("``"); |
150 | | - expect(rendered).toContain("const"); |
151 | | - }); |
152 | | - |
153 | | - test("closing fence streamed one character at a time never shrinks the block", () => { |
154 | | - // The newline after the body starts a fresh, empty line that could become |
155 | | - // the closing fence. Streaming ` then `` then ``` across it must never |
156 | | - // remove an already-visible line (shrink reads as flicker) — only hold |
157 | | - // steady or grow as the fence completes. |
158 | | - const base = "```js\nconst x = 1;\n"; |
159 | | - const steps = [base, `${base}\``, `${base}\`\``, `${base}\`\`\``]; |
160 | | - const lineCounts = steps.map((content) => parseMarkdown(content).length); |
161 | | - for (let i = 1; i < lineCounts.length; i++) { |
162 | | - expect(lineCounts[i]).toBeGreaterThanOrEqual(defined(lineCounts[i - 1])); |
163 | | - } |
164 | | - }); |
165 | | - |
166 | | - test("blank lines inside a fenced block keep a continuous gutter", () => { |
167 | | - const lines = parseMarkdown("```ts\nconst a = 1;\n\nconst b = 2;\n```"); |
168 | | - // Cap, body, blank, body, foot — every body row carries the gutter so the |
169 | | - // frame does not fragment. |
170 | | - const gutterLines = lines.filter((line) => |
171 | | - line.some((s) => s.text.includes("▏")), |
172 | | - ); |
173 | | - expect(gutterLines.length).toBeGreaterThanOrEqual(3); |
174 | | - // The blank body line is not an empty segment array — it still paints ▏. |
175 | | - const blankBody = lines.find( |
176 | | - (line) => |
177 | | - line.length === 1 && |
178 | | - line[0]?.text === "▏ " && |
179 | | - line[0]?.codeFence === true, |
180 | | - ); |
181 | | - expect(blankBody).toBeDefined(); |
182 | | - expect(lines[0]?.some((s) => s.text.includes("╭"))).toBe(true); |
183 | | - expect(lines[lines.length - 1]?.some((s) => s.text.includes("╰"))).toBe( |
184 | | - true, |
185 | | - ); |
186 | | - }); |
187 | 117 | }); |
188 | 118 |
|
189 | 119 | describe("bullets", () => { |
@@ -495,54 +425,6 @@ test("link with an empty URL still renders as styled text, not raw characters", |
495 | 425 | expect(segs.some((s) => s.text.includes("("))).toBe(false); |
496 | 426 | }); |
497 | 427 |
|
498 | | -describe("createMemoizedParseMarkdown", () => { |
499 | | - test("returns the same array reference for a repeated (text, width) call", () => { |
500 | | - const memoized = createMemoizedParseMarkdown(); |
501 | | - const first = memoized("hello **world**", 80); |
502 | | - const second = memoized("hello **world**", 80); |
503 | | - expect(second).toBe(first); |
504 | | - }); |
505 | | - |
506 | | - test("misses on a different width even with identical text", () => { |
507 | | - const memoized = createMemoizedParseMarkdown(); |
508 | | - const at80 = memoized("some prose", 80); |
509 | | - const at40 = memoized("some prose", 40); |
510 | | - expect(at40).not.toBe(at80); |
511 | | - expect(at40).toEqual(parseMarkdown("some prose", 40)); |
512 | | - }); |
513 | | - |
514 | | - test("misses on different text at the same width", () => { |
515 | | - const memoized = createMemoizedParseMarkdown(); |
516 | | - const a = memoized("first", 80); |
517 | | - const b = memoized("second", 80); |
518 | | - expect(b).not.toBe(a); |
519 | | - }); |
520 | | - |
521 | | - test("evicts the least recently used entry once over capacity", () => { |
522 | | - const memoized = createMemoizedParseMarkdown(2); |
523 | | - const a = memoized("a", 80); |
524 | | - const b = memoized("b", 80); |
525 | | - // Touch "a" again so "b" becomes the least recently used entry. |
526 | | - expect(memoized("a", 80)).toBe(a); |
527 | | - const c = memoized("c", 80); // over capacity: evicts "b" |
528 | | - |
529 | | - // "b" was evicted: same content, but freshly parsed (not identical). |
530 | | - expect(memoized("b", 80)).not.toBe(b); |
531 | | - // "c" is still warm — inserted more recently than "a", which the re-fetch |
532 | | - // of "b" evicted. |
533 | | - expect(memoized("c", 80)).toBe(c); |
534 | | - }); |
535 | | - |
536 | | - test("clear() drops every cached entry", () => { |
537 | | - const memoized = createMemoizedParseMarkdown(); |
538 | | - const first = memoized("hello", 80); |
539 | | - memoized.clear(); |
540 | | - const second = memoized("hello", 80); |
541 | | - expect(second).not.toBe(first); |
542 | | - expect(second).toEqual(first); |
543 | | - }); |
544 | | -}); |
545 | | - |
546 | 428 | describe("withholdIncompleteHeading", () => { |
547 | 429 | test("strips a trailing bare heading marker while streaming", () => { |
548 | 430 | expect(withholdIncompleteHeading("hello\n####")).toBe("hello\n"); |
|
0 commit comments