import { describe, expect, it } from "vitest"; import { parseZalouserTextStyles } from "./text-styles.js"; import { TextStyle } from "./zca-client.js"; describe("parseZalouserTextStyles", () => { it("renders inline markdown emphasis as Zalo style ranges", () => { expect(parseZalouserTextStyles("**bold** *italic* ~~strike~~")).toEqual({ text: "bold italic strike", styles: [ { start: 0, len: 4, st: TextStyle.Bold }, { start: 5, len: 6, st: TextStyle.Italic }, { start: 12, len: 6, st: TextStyle.StrikeThrough }, ], }); }); it("keeps inline code and plain math markers literal", () => { expect(parseZalouserTextStyles("before `inline *code*` after\n2 * 3 * 4")).toEqual({ text: "before `inline *code*` after\n2 * 3 * 4", styles: [], }); }); it("preserves backslash escapes inside code spans and fenced code blocks", () => { expect(parseZalouserTextStyles("before `\\*` after\n```ts\n\\*\\_\\\\\n```")).toEqual({ text: "before `\\*` after\n\\*\\_\\\\", styles: [], }); }); it("closes fenced code blocks when the input uses CRLF newlines", () => { expect(parseZalouserTextStyles("```\r\n*code*\r\n```\r\n**after**")).toEqual({ text: "*code*\nafter", styles: [{ start: 7, len: 5, st: TextStyle.Bold }], }); }); it("maps headings, block quotes, and lists into line styles", () => { expect(parseZalouserTextStyles(["# Title", "> quoted", " - nested"].join("\n"))).toEqual({ text: "Title\nquoted\nnested", styles: [ { start: 0, len: 5, st: TextStyle.Bold }, { start: 0, len: 5, st: TextStyle.Big }, { start: 6, len: 6, st: TextStyle.Indent, indentSize: 1 }, { start: 13, len: 6, st: TextStyle.UnorderedList }, ], }); }); it("treats 1-3 leading spaces as markdown padding for headings and lists", () => { expect(parseZalouserTextStyles(" # Title\n 1. item\n - bullet")).toEqual({ text: "Title\nitem\nbullet", styles: [ { start: 0, len: 5, st: TextStyle.Bold }, { start: 0, len: 5, st: TextStyle.Big }, { start: 6, len: 4, st: TextStyle.OrderedList }, { start: 11, len: 6, st: TextStyle.UnorderedList }, ], }); }); it("strips fenced code markers and preserves leading indentation with nbsp", () => { expect(parseZalouserTextStyles("```ts\n const x = 1\n\treturn x\n```")).toEqual({ text: "\u00A0\u00A0const x = 1\n\u00A0\u00A0\u00A0\u00A0return x", styles: [], }); }); it("treats tilde fences as literal code blocks", () => { expect(parseZalouserTextStyles("~~~bash\n*cmd*\n~~~")).toEqual({ text: "*cmd*", styles: [], }); }); it("treats fences indented under list items as literal code blocks", () => { expect(parseZalouserTextStyles(" ```\n*cmd*\n ```")).toEqual({ text: "*cmd*", styles: [], }); }); it("treats quoted backtick fences as literal code blocks", () => { expect(parseZalouserTextStyles("> ```js\n> *cmd*\n> ```")).toEqual({ text: "*cmd*", styles: [], }); }); it("treats quoted tilde fences as literal code blocks", () => { expect(parseZalouserTextStyles("> ~~~\n> *cmd*\n> ~~~")).toEqual({ text: "*cmd*", styles: [], }); }); it("preserves quote-prefixed lines inside normal fenced code blocks", () => { expect(parseZalouserTextStyles("```\n> prompt\n```")).toEqual({ text: "> prompt", styles: [], }); }); it("does not treat quote-prefixed fence text inside code as a closing fence", () => { expect(parseZalouserTextStyles("```\n> ```\n*still code*\n```")).toEqual({ text: "> ```\n*still code*", styles: [], }); }); it("treats indented blockquotes as quoted lines", () => { expect(parseZalouserTextStyles(" > quoted")).toEqual({ text: "quoted", styles: [{ start: 0, len: 6, st: TextStyle.Indent, indentSize: 1 }], }); }); it("treats spaced nested blockquotes as deeper quoted lines", () => { expect(parseZalouserTextStyles("> > quoted")).toEqual({ text: "quoted", styles: [{ start: 0, len: 6, st: TextStyle.Indent, indentSize: 2 }], }); }); it("treats indented quoted fences as literal code blocks", () => { expect(parseZalouserTextStyles(" > ```\n > *cmd*\n > ```")).toEqual({ text: "*cmd*", styles: [], }); }); it("treats spaced nested quoted fences as literal code blocks", () => { expect(parseZalouserTextStyles("> > ```\n> > code\n> > ```")).toEqual({ text: "code", styles: [], }); }); it("preserves inner quote markers inside quoted fenced code blocks", () => { expect(parseZalouserTextStyles("> ```\n>> prompt\n> ```")).toEqual({ text: "> prompt", styles: [], }); }); it("keeps quote indentation on heading lines", () => { expect(parseZalouserTextStyles("> # Title")).toEqual({ text: "Title", styles: [ { start: 0, len: 5, st: TextStyle.Bold }, { start: 0, len: 5, st: TextStyle.Big }, { start: 0, len: 5, st: TextStyle.Indent, indentSize: 1 }, ], }); }); it("keeps unmatched fences literal", () => { expect(parseZalouserTextStyles("```python")).toEqual({ text: "```python", styles: [], }); }); it("keeps unclosed fenced blocks literal until eof", () => { expect(parseZalouserTextStyles("```python\n\\*not italic*\n_next_")).toEqual({ text: "```python\n\\*not italic*\n_next_", styles: [], }); }); it("supports nested markdown and tag styles regardless of order", () => { expect(parseZalouserTextStyles("**{red}x{/red}** {red}**y**{/red}")).toEqual({ text: "x y", styles: [ { start: 0, len: 1, st: TextStyle.Bold }, { start: 0, len: 1, st: TextStyle.Red }, { start: 2, len: 1, st: TextStyle.Red }, { start: 2, len: 1, st: TextStyle.Bold }, ], }); }); it("treats small text tags as normal text", () => { expect(parseZalouserTextStyles("{small}tiny{/small}")).toEqual({ text: "tiny", styles: [], }); }); it("keeps escaped markers literal", () => { expect(parseZalouserTextStyles("\\*literal\\* \\{underline}tag{/underline}")).toEqual({ text: "*literal* {underline}tag{/underline}", styles: [], }); }); it("keeps indented code blocks literal", () => { expect(parseZalouserTextStyles(" *cmd*")).toEqual({ text: "\u00A0\u00A0\u00A0\u00A0*cmd*", styles: [], }); }); });