import { describe, it, expect, beforeEach, vi } from "vitest"; import MicronParser from "@/js/MicronParser"; describe("MicronParser.js", () => { let parser; beforeEach(() => { parser = new MicronParser(true, false); // darkTheme = true, enableForceMonospace = false }); describe("PARTIAL_LINE_REGEX", () => { it("matches partial without refresh", () => { const m = "`{f64a846313b874ee4a357040807f8c77:/page/partial.mu}" .trim() .match(MicronParser.PARTIAL_LINE_REGEX); expect(m).not.toBeNull(); expect(m[1]).toBe("f64a846313b874ee4a357040807f8c77"); expect(m[2]).toBe("/page/partial.mu"); expect(m[3]).toBeUndefined(); }); it("matches partial with refresh", () => { const m = "`{f64a846313b874ee4a357040807f8c77:/page/ref.mu`30}" .trim() .match(MicronParser.PARTIAL_LINE_REGEX); expect(m).not.toBeNull(); expect(m[3]).toBe("30"); }); it("does not match without leading backtick", () => { expect("{f64a846313b874ee4a357040807f8c77:/page/x.mu}".match(MicronParser.PARTIAL_LINE_REGEX)).toBeNull(); }); it("does not match short hash", () => { expect("`{f64a846313b874ee4a35704:/page/x.mu}".match(MicronParser.PARTIAL_LINE_REGEX)).toBeNull(); }); }); describe("formatNomadnetworkUrl", () => { it("formats nomadnetwork URL correctly", () => { expect(MicronParser.formatNomadnetworkUrl("example.com")).toBe("nomadnetwork://example.com"); }); }); describe("isWideMonospaceCell", () => { it("detects Han, kana, Hangul syllables", () => { expect(MicronParser.isWideMonospaceCell("中")).toBe(true); expect(MicronParser.isWideMonospaceCell("あ")).toBe(true); expect(MicronParser.isWideMonospaceCell("ア")).toBe(true); expect(MicronParser.isWideMonospaceCell("한")).toBe(true); }); it("detects CJK punctuation and fullwidth ASCII", () => { expect(MicronParser.isWideMonospaceCell("\u3000")).toBe(true); expect(MicronParser.isWideMonospaceCell("\uff01")).toBe(true); }); it("treats Latin as narrow", () => { expect(MicronParser.isWideMonospaceCell("A")).toBe(false); expect(MicronParser.isWideMonospaceCell("9")).toBe(false); }); }); describe("forceMonospace wide cells", () => { it("uses Mu-mnt-full for CJK and Mu-mnt for Latin in the same line", () => { const monoParser = new MicronParser(true, true); const html = monoParser.splitAtSpaces("Hi \u4e2d\u6587"); expect(html).toContain("class='Mu-mnt'>H"); expect(html).toContain("class='Mu-mnt'>i"); expect(html).toContain("class='Mu-mnt-full'>\u4e2d"); expect(html).toContain("class='Mu-mnt-full'>\u6587"); }); }); describe("convertMicronToHtml", () => { it("converts simple text to HTML", () => { const markup = "Hello World"; const html = parser.convertMicronToHtml(markup); expect(html).toContain("Hello World"); expect(html).toContain(" { const markup = "> Heading 1\n>> Heading 2"; const html = parser.convertMicronToHtml(markup); expect(html).toContain("Heading 1"); expect(html).toContain("Heading 2"); // Check for styles applied to headings (in dark theme) expect(html).toContain("background-color: rgb(187, 187, 187)"); // #bbb expect(html).toContain("background-color: rgb(153, 153, 153)"); // #999 }); it("converts horizontal dividers", () => { const markup = "-"; const html = parser.convertMicronToHtml(markup); expect(html).toContain(" { const markup = "`!Bold Text`!"; const html = parser.convertMicronToHtml(markup); expect(html).toContain("font-weight: bold"); expect(html).toContain("Bold Text"); }); it("handles italic formatting", () => { const markup = "`*Italic Text`*"; const html = parser.convertMicronToHtml(markup); expect(html).toContain("font-style: italic"); expect(html).toContain("Italic Text"); }); it("handles underline formatting", () => { const markup = "`_Underlined Text`_"; const html = parser.convertMicronToHtml(markup); expect(html).toContain("text-decoration: underline"); expect(html).toContain("Underlined Text"); }); it("handles combined formatting", () => { const markup = "`!`_Bold Underlined Text`_`!"; const html = parser.convertMicronToHtml(markup); expect(html).toContain("font-weight: bold"); expect(html).toContain("text-decoration: underline"); expect(html).toContain("Bold Underlined Text"); }); describe("foreground color", () => { it("handles 3-char F format", () => { const markup = "`FabcRed Text`"; const html = parser.convertMicronToHtml(markup); expect(html).toContain("rgb(170, 187, 204)"); expect(html).toContain("Red Text"); }); }); describe("background color", () => { it("handles 3-char B format", () => { const markup = "`BfffYellow BG`"; const html = parser.convertMicronToHtml(markup); expect(html).toContain("rgb(255, 255, 255)"); expect(html).toContain("Yellow BG"); }); }); it("handles literal mode", () => { const markup = "`=\n`*Not Italic`*\n`="; const html = parser.convertMicronToHtml(markup); expect(html).not.toContain("font-style: italic"); expect(html).toContain("`*Not Italic`*"); }); it("handles links correctly", () => { const markup = "`[Label`example.com]"; const html = parser.convertMicronToHtml(markup); expect(html).toContain(" { const markup = "`<24|field_name`Initial Value>"; const html = parser.convertMicronToHtml(markup); expect(html).toContain(" { const markup = "`"; const html = parser.convertMicronToHtml(markup); expect(html).toContain('type="checkbox"'); expect(html).toContain('name="checkbox_name"'); expect(html).toContain('value="val"'); expect(html).toContain("Checkbox Label"); }); describe("partials", () => { it("emits placeholder for partial line without refresh", () => { const dest = "f64a846313b874ee4a357040807f8c77"; const path = "/page/partial_1.mu"; const markup = "`{" + dest + ":" + path + "}"; const html = parser.convertMicronToHtml(markup); expect(html).toContain('class="mu-partial"'); expect(html).toContain('data-partial-id="partial-0"'); expect(html).toContain('data-dest="' + dest + '"'); expect(html).toContain('data-path="' + path + '"'); expect(html).not.toContain("data-refresh"); expect(html).toContain("Loading..."); }); it("emits placeholder for partial line with refresh seconds", () => { const dest = "f64a846313b874ee4a357040807f8c77"; const path = "/page/refreshing_partial.mu"; const markup = "`{" + dest + ":" + path + "`10}"; const html = parser.convertMicronToHtml(markup); expect(html).toContain('class="mu-partial"'); expect(html).toContain('data-partial-id="partial-0"'); expect(html).toContain('data-dest="' + dest + '"'); expect(html).toContain('data-path="' + path + '"'); expect(html).toContain('data-refresh="10"'); expect(html).toContain("Loading..."); }); it("injects partialContents when provided", () => { const dest = "a".repeat(32); const path = "/page/partial.mu"; const markup = "`{" + dest + ":" + path + "}"; const injected = "Injected partial content"; const html = parser.convertMicronToHtml(markup, { "partial-0": injected }); expect(html).toContain(injected); expect(html).not.toContain("Loading..."); expect(html).not.toContain("mu-partial"); }); it("assigns unique partial ids for multiple partials", () => { const dest = "b".repeat(32); const markup = "`{" + dest + ":/page/a.mu}\n`{" + dest + ":/page/b.mu}"; const html = parser.convertMicronToHtml(markup); expect(html).toContain('data-partial-id="partial-0"'); expect(html).toContain('data-partial-id="partial-1"'); expect(html).toContain('data-path="/page/a.mu"'); expect(html).toContain('data-path="/page/b.mu"'); }); it("does not interpret partial syntax inside literal block", () => { const dest = "c".repeat(32); const markup = "`=\n`{" + dest + ":/page/partial.mu}\n`="; const html = parser.convertMicronToHtml(markup); expect(html).not.toContain("mu-partial"); expect(html).toContain("`{" + dest + ":/page/partial.mu}"); }); it("does not treat similar-looking line as partial without backtick", () => { const markup = "{f64a846313b874ee4a357040807f8c77:/page/partial.mu}"; const html = parser.convertMicronToHtml(markup); expect(html).not.toContain("mu-partial"); }); }); }); describe("colorToCss", () => { it("expands 3-digit hex", () => { expect(parser.colorToCss("abc")).toBe("#abc"); }); it("returns 6-digit hex", () => { expect(parser.colorToCss("abcdef")).toBe("#abcdef"); }); it("handles grayscale format", () => { expect(parser.colorToCss("g50")).toBe("#7f7f7f"); // 50 * 2.55 = 127.5 -> 7f }); it("returns null for unknown formats", () => { expect(parser.colorToCss("invalid")).toBeNull(); }); }); describe("risky: XSS and injection", () => { it("output contains no raw script tag for script-like markup", () => { const markup = " hello"; const html = parser.convertMicronToHtml(markup); expect(html).not.toMatch(/]/i); }); it("output contains no javascript: in href for link-like markup", () => { const markup = "`[click`javascript:alert(1)]"; const html = parser.convertMicronToHtml(markup); expect(html).not.toMatch(/\bhref\s*=\s*["']?\s*javascript:/i); }); it("output contains no data: html in href", () => { const markup = "`[x`data:text/html,]"; const html = parser.convertMicronToHtml(markup); expect(html).not.toMatch(/\bhref\s*=\s*["']?\s*data\s*:\s*text\/html/i); }); it("does not produce executable event handler attributes", () => { const markup = 'x'; const html = parser.convertMicronToHtml(markup); expect(html).not.toMatch(/<[^>]*\bonclick\s*=\s*["']?\s*alert/i); }); }); describe("risky: stability and edge input", () => { it("does not throw on null or undefined markup", () => { expect(() => parser.convertMicronToHtml(null)).not.toThrow(); expect(() => parser.convertMicronToHtml(undefined)).not.toThrow(); }); it("handles very long input without hanging", () => { const long = "> ".repeat(5000) + "x"; const start = Date.now(); const html = parser.convertMicronToHtml(long); expect(Date.now() - start).toBeLessThan(500); expect(typeof html).toBe("string"); }); it("handles repeated backticks (ReDoS-prone pattern) quickly", () => { const markup = "`".repeat(3000); const start = Date.now(); parser.convertMicronToHtml(markup); expect(Date.now() - start).toBeLessThan(200); }); it("handles control chars and null byte", () => { const markup = "hello\x00world\x07\n\t"; expect(() => parser.convertMicronToHtml(markup)).not.toThrow(); const html = parser.convertMicronToHtml(markup); expect(typeof html).toBe("string"); }); it("handles unicode and RTL override", () => { const markup = "\u202eRTL `!bold`! text \ufffd"; const html = parser.convertMicronToHtml(markup); expect(typeof html).toBe("string"); expect(html).not.toMatch(/]/i); }); }); describe("adversarial: deeply nested tags", () => { it("handles 500 levels of nested bold without stack overflow", () => { const open = "`!".repeat(500); const close = "`!".repeat(500); const markup = open + "deep" + close; expect(() => parser.convertMicronToHtml(markup)).not.toThrow(); const html = parser.convertMicronToHtml(markup); expect(typeof html).toBe("string"); expect(html).toContain("deep"); }); it("handles 500 levels of mixed nesting (bold+italic+underline)", () => { const tags = ["`!", "`*", "`_"]; let markup = ""; for (let i = 0; i < 500; i++) markup += tags[i % 3]; markup += "payload"; for (let i = 499; i >= 0; i--) markup += tags[i % 3]; expect(() => parser.convertMicronToHtml(markup)).not.toThrow(); const html = parser.convertMicronToHtml(markup); expect(html).toContain("payload"); }); it("handles deeply nested headings", () => { const markup = Array.from({ length: 200 }, (_, i) => ">".repeat(i + 1) + " H").join("\n"); expect(() => parser.convertMicronToHtml(markup)).not.toThrow(); }); }); describe("adversarial: unclosed and mismatched tags", () => { it("handles unclosed bold tag", () => { const markup = "`!unclosed bold without end"; const html = parser.convertMicronToHtml(markup); expect(typeof html).toBe("string"); expect(html).not.toMatch(/]/i); }); it("handles unclosed italic tag", () => { const markup = "`*unclosed italic"; const html = parser.convertMicronToHtml(markup); expect(typeof html).toBe("string"); }); it("handles mismatched open/close order", () => { const markup = "`!`*bold-italic`!`*"; const html = parser.convertMicronToHtml(markup); expect(typeof html).toBe("string"); }); it("handles unclosed link", () => { const markup = "`[link text without closing"; const html = parser.convertMicronToHtml(markup); expect(typeof html).toBe("string"); expect(html).not.toMatch(/\bhref\s*=\s*["']?\s*javascript:/i); }); it("handles unclosed input", () => { const markup = "`<24|field_name`value without closing"; const html = parser.convertMicronToHtml(markup); expect(typeof html).toBe("string"); }); it("handles unclosed literal mode", () => { const markup = "`=\nsome literal text that never closes"; const html = parser.convertMicronToHtml(markup); expect(typeof html).toBe("string"); }); }); describe("adversarial: malformed UTF-8 and encoding attacks", () => { it("handles UTF-8 overlong encoding sequences", () => { const markup = "test\xC0\xAFpath\xE0\x80\xAFmore"; expect(() => parser.convertMicronToHtml(markup)).not.toThrow(); }); it("handles surrogate halves", () => { const markup = "text\uD800alone\uDC00orphan"; expect(() => parser.convertMicronToHtml(markup)).not.toThrow(); }); it("handles BOM characters", () => { const markup = "\uFEFF> Heading with BOM"; const html = parser.convertMicronToHtml(markup); expect(typeof html).toBe("string"); }); it("handles zero-width joiner/non-joiner", () => { const markup = "`!\u200Bbold\u200Dtext`!"; const html = parser.convertMicronToHtml(markup); expect(typeof html).toBe("string"); }); it("handles full-width characters in tag positions", () => { const markup = "\uFF40\uFF01fullwidth backtick-bang\uFF40\uFF01"; expect(() => parser.convertMicronToHtml(markup)).not.toThrow(); }); }); describe("adversarial: null bytes and control characters", () => { it("handles null bytes between tag markers", () => { const markup = "`\x00!bold\x00`!"; expect(() => parser.convertMicronToHtml(markup)).not.toThrow(); }); it("handles line of only null bytes", () => { const markup = "\x00".repeat(100); expect(() => parser.convertMicronToHtml(markup)).not.toThrow(); }); it("handles ASCII control characters 0x01-0x1F", () => { let markup = ""; for (let i = 1; i < 32; i++) markup += String.fromCharCode(i); expect(() => parser.convertMicronToHtml(markup)).not.toThrow(); const html = parser.convertMicronToHtml(markup); expect(html).not.toMatch(/]/i); }); it("handles escape sequences in links", () => { const markup = "`[label`\x1B[31mred\x1B[0m]"; const html = parser.convertMicronToHtml(markup); expect(typeof html).toBe("string"); }); it("handles DEL character (0x7F)", () => { const markup = "hello\x7Fworld"; expect(() => parser.convertMicronToHtml(markup)).not.toThrow(); }); }); describe("adversarial: ReDoS patterns", () => { it("handles alternating backtick-bracket pattern quickly", () => { const markup = "`[".repeat(1000) + "x" + "]".repeat(1000); const start = Date.now(); parser.convertMicronToHtml(markup); expect(Date.now() - start).toBeLessThan(500); }); it("handles alternating backtick-angle pattern quickly", () => { const markup = "`<".repeat(1000) + "y" + ">".repeat(1000); const start = Date.now(); parser.convertMicronToHtml(markup); expect(Date.now() - start).toBeLessThan(500); }); it("handles pathological color code pattern quickly", () => { const markup = "`Fabc".repeat(500); const start = Date.now(); parser.convertMicronToHtml(markup); expect(Date.now() - start).toBeLessThan(500); }); it("handles massive number of newlines quickly", () => { const markup = "\n".repeat(10000); const start = Date.now(); parser.convertMicronToHtml(markup); expect(Date.now() - start).toBeLessThan(1500); }); it("handles rapid format toggle (open/close/open/close) quickly", () => { const markup = "`!x`!".repeat(2000); const start = Date.now(); parser.convertMicronToHtml(markup); expect(Date.now() - start).toBeLessThan(1500); }); }); describe("adversarial: XSS bypass attempts", () => { it("blocks SVG onload XSS", () => { const markup = ''; const html = parser.convertMicronToHtml(markup); const div = document.createElement("div"); div.innerHTML = html; const svgs = div.querySelectorAll("svg"); for (const svg of svgs) { expect(svg.getAttribute("onload")).toBeNull(); } }); it("blocks img onerror XSS", () => { const markup = ''; const html = parser.convertMicronToHtml(markup); const div = document.createElement("div"); div.innerHTML = html; const imgs = div.querySelectorAll("img"); for (const img of imgs) { expect(img.getAttribute("onerror")).toBeNull(); } }); it("blocks iframe injection", () => { const markup = ''; const html = parser.convertMicronToHtml(markup); expect(html).not.toMatch(/]/i); }); it("blocks style-based data exfiltration", () => { const markup = ''; const html = parser.convertMicronToHtml(markup); expect(html).not.toMatch(/]/i); }); it("blocks javascript: in link with encoding", () => { const markup = "`[click`java\tscript:alert(1)]"; const html = parser.convertMicronToHtml(markup); expect(html).not.toMatch(/\bhref\s*=\s*["']?\s*java\s*script:/i); }); it("blocks vbscript: protocol", () => { const markup = "`[click`vbscript:MsgBox(1)]"; const html = parser.convertMicronToHtml(markup); expect(html).not.toMatch(/\bhref\s*=\s*["']?\s*vbscript:/i); }); it("blocks meta refresh injection", () => { const markup = ''; const html = parser.convertMicronToHtml(markup); expect(html).not.toMatch(/]/i); }); it("blocks object/embed tags", () => { const markup = ''; const html = parser.convertMicronToHtml(markup); expect(html).not.toMatch(/]/i); expect(html).not.toMatch(/]/i); }); it("blocks form action injection", () => { const markup = '
'; const html = parser.convertMicronToHtml(markup); expect(html).not.toMatch(/]/i); }); it("blocks base tag hijacking", () => { const markup = ''; const html = parser.convertMicronToHtml(markup); expect(html).not.toMatch(/]/i); }); }); describe("adversarial: injection through micron features", () => { it("link URL with attribute injection is entity-encoded", () => { const markup = '`[click`" onclick="alert(1)" data-x="]'; const html = parser.convertMicronToHtml(markup); const div = document.createElement("div"); div.innerHTML = html; const anchors = div.querySelectorAll("a"); for (const a of anchors) { expect(a.getAttribute("onclick")).toBeNull(); } }); it("input field name cannot inject HTML", () => { const markup = '`<24|">`val>'; const html = parser.convertMicronToHtml(markup); expect(html).not.toMatch(/]/i); }); it("checkbox value with attribute injection is entity-encoded", () => { const markup = '`|`label>'; const html = parser.convertMicronToHtml(markup); const div = document.createElement("div"); div.innerHTML = html; const imgs = div.querySelectorAll("img"); for (const img of imgs) { expect(img.getAttribute("onerror")).toBeNull(); } }); it("color code cannot inject styles or HTML", () => { const markup = '`F">text'; const html = parser.convertMicronToHtml(markup); expect(html).not.toMatch(/]/i); }); it("partial hash cannot inject HTML", () => { const hash = 'a">'; const markup = "`{" + hash + ":/page/test.mu}"; const html = parser.convertMicronToHtml(markup); expect(html).not.toMatch(/]/i); }); }); describe("adversarial: large and extreme payloads", () => { it("handles 100KB of plain text", () => { const markup = "A".repeat(100_000); const start = Date.now(); const html = parser.convertMicronToHtml(markup); expect(Date.now() - start).toBeLessThan(2000); expect(typeof html).toBe("string"); }); it("handles 10K lines of formatted text", () => { const lines = Array.from({ length: 10_000 }, (_, i) => `\`!line ${i}\`!`); const markup = lines.join("\n"); const start = Date.now(); parser.convertMicronToHtml(markup); expect(Date.now() - start).toBeLessThan(15000); }, 15000); it("handles single line of 50KB", () => { const markup = "`!" + "X".repeat(50_000) + "`!"; expect(() => parser.convertMicronToHtml(markup)).not.toThrow(); }); it("handles empty string", () => { const html = parser.convertMicronToHtml(""); expect(typeof html).toBe("string"); }); it("handles only whitespace", () => { const html = parser.convertMicronToHtml(" \t\t\n\n "); expect(typeof html).toBe("string"); }); }); describe("adversarial: polyglot and mixed content", () => { it("handles HTML/micron polyglot", () => { const markup = "> `!Heading`!\n\n`[link`http://safe.com]"; const html = parser.convertMicronToHtml(markup); expect(html).not.toMatch(/]/i); expect(html).toContain("Heading"); }); it("handles markdown-like input", () => { const markup = "# Not a heading\n**not bold**\n[not a link](http://evil.com)"; expect(() => parser.convertMicronToHtml(markup)).not.toThrow(); }); it("handles ANSI escape codes", () => { const markup = "\x1B[31mred text\x1B[0m normal"; expect(() => parser.convertMicronToHtml(markup)).not.toThrow(); }); it("handles mixed valid/invalid UTF-8 with micron", () => { const markup = "`!\uFFFD\uFFFE\uFFFF bold\uD83D\uDE00`!"; expect(() => parser.convertMicronToHtml(markup)).not.toThrow(); }); }); describe("resilience: parse failures", () => { it("renders other lines when parseLine throws for one line", () => { const spy = vi.spyOn(parser, "parseLine").mockImplementation(function (line, state) { if (line === "BAD") { throw new Error("forced parse failure"); } return MicronParser.prototype.parseLine.call(this, line, state); }); const html = parser.convertMicronToHtml("good line\nBAD\nanother"); spy.mockRestore(); expect(html).toContain("good line"); expect(html).toContain("mu-line-parse-fallback"); expect(html).toContain("BAD"); expect(html).toContain("another"); }); it("convertMicronToFragment returns a fragment when parseLine throws", () => { const spy = vi.spyOn(parser, "parseLine").mockImplementation(function (line, state) { if (line === "BAD") { throw new Error("forced"); } return MicronParser.prototype.parseLine.call(this, line, state); }); const frag = parser.convertMicronToFragment("ok\nBAD\nok2"); spy.mockRestore(); expect(frag.childNodes.length).toBeGreaterThan(0); }); }); });