1import { describe, it, expect } from "vitest";
2import * as markodown from "./mod.ts";
3
4describe("transform", () => {
5 describe("success cases", () => {
6 it("transforms basic markdown", () => {
7 const result = markodown.transform({ source: "**hello world**" });
8 expect(result.success).toBe(true);
9 expect(result.errors).toEqual([]);
10 if (result.success) {
11 expect(result.text).toContain("<strong>hello world</strong>");
12 }
13 });
14
15 it("transforms headings", () => {
16 const result = markodown.transform({ source: "# Hello" });
17 expect(result.success).toBe(true);
18 if (result.success) {
19 expect(result.text).toContain("<h1>Hello</h1>");
20 }
21 });
22
23 it("transforms paragraphs", () => {
24 const result = markodown.transform({ source: "Hello\n\nWorld" });
25 expect(result.success).toBe(true);
26 if (result.success) {
27 expect(result.text).toContain("<p>Hello</p>");
28 expect(result.text).toContain("<p>World</p>");
29 }
30 });
31 });
32
33 describe("error handling", () => {
34 it("reports unclosed tag error with line and column", () => {
35 const result = markodown.transform({ source: "<div>\nhello" });
36 expect(result.success).toBe(false);
37 if (!result.success) {
38 expect(result.errors.length).toBeGreaterThan(0);
39 const error = result.errors[0];
40 expect(error.message).toContain("Unclosed tag");
41 expect(error.line).toBe(1);
42 expect(error.column).toBe(1); // at '<'
43 }
44 });
45
46 it("reports mismatched closing tag", () => {
47 const result = markodown.transform({ source: "<div>\nhello\n</span>" });
48 expect(result.success).toBe(false);
49 if (!result.success) {
50 expect(result.errors.length).toBeGreaterThan(0);
51 const error = result.errors[0];
52 expect(error.message).toContain("Mismatched closing tag");
53 // Should have labels for both open and close positions
54 expect(error.labels.length).toBe(2);
55 // First label: opened here
56 expect(error.labels[0].message).toBe("opened here");
57 expect(error.labels[0].line).toBe(1);
58 // Second label: closed here
59 expect(error.labels[1].message).toBe("closed here");
60 expect(error.labels[1].line).toBe(3);
61 }
62 });
63
64 it("reports closing tag without open", () => {
65 const result = markodown.transform({ source: "</div>" });
66 expect(result.success).toBe(false);
67 if (!result.success) {
68 expect(result.errors.length).toBeGreaterThan(0);
69 const error = result.errors[0];
70 expect(error.message).toContain("without matching open");
71 expect(error.line).toBe(1);
72 }
73 });
74
75 it("reports error on correct line for multiline input", () => {
76 const result = markodown.transform({
77 source: "line 1\nline 2\nline 3\n<unclosed>",
78 });
79 expect(result.success).toBe(false);
80 if (!result.success) {
81 expect(result.errors.length).toBeGreaterThan(0);
82 const error = result.errors[0];
83 expect(error.line).toBe(4); // error is on line 4
84 }
85 });
86
87 it("absorbs first label position into root error", () => {
88 // When there's a single label without message, its position
89 // should be absorbed into the root error's line/column
90 const result = markodown.transform({ source: "hello\n\n</>" });
91 expect(result.success).toBe(false);
92 if (!result.success) {
93 expect(result.errors.length).toBeGreaterThan(0);
94 const error = result.errors[0];
95 expect(error.line).toBe(3); // The </> is on line 3
96 // Single label without message should be absorbed (empty labels array)
97 expect(error.labels).toEqual([]);
98 }
99 });
100
101 it("forwards labels with messages", () => {
102 // When labels have messages, they should be forwarded
103 const result = markodown.transform({ source: "<foo>\n\n</bar>" });
104 expect(result.success).toBe(false);
105 if (!result.success) {
106 const error = result.errors[0];
107 // Labels with "opened here" and "closed here" should be forwarded
108 expect(error.labels.length).toBe(2);
109 expect(error.labels[0].message).toBeTruthy();
110 expect(error.labels[1].message).toBeTruthy();
111 }
112 });
113
114 it("includes width in labels", () => {
115 const result = markodown.transform({ source: "<div>\n</span>" });
116 expect(result.success).toBe(false);
117 if (!result.success) {
118 const error = result.errors[0];
119 expect(error.labels.length).toBe(2);
120 // "<div" has width 4 (includes < for better error highlighting)
121 expect(error.labels[0].width).toBe(4);
122 // "span" has width 4 (close tags only highlight the name)
123 expect(error.labels[1].width).toBe(4);
124 }
125 });
126 });
127});