quasar-ui-danx 0.5.0 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +8 -0
- package/dist/danx.es.js +16119 -10641
- package/dist/danx.es.js.map +1 -1
- package/dist/danx.umd.js +202 -123
- package/dist/danx.umd.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +8 -1
- package/src/components/Utility/Buttons/ActionButton.vue +15 -5
- package/src/components/Utility/Code/CodeViewer.vue +41 -16
- package/src/components/Utility/Code/CodeViewerCollapsed.vue +2 -0
- package/src/components/Utility/Code/CodeViewerFooter.vue +3 -1
- package/src/components/Utility/Code/LanguageBadge.vue +278 -5
- package/src/components/Utility/Code/MarkdownContent.vue +31 -163
- package/src/components/Utility/Code/index.ts +3 -0
- package/src/components/Utility/Markdown/ContextMenu.vue +314 -0
- package/src/components/Utility/Markdown/HotkeyHelpPopover.vue +259 -0
- package/src/components/Utility/Markdown/LineTypeMenu.vue +226 -0
- package/src/components/Utility/Markdown/LinkPopover.vue +331 -0
- package/src/components/Utility/Markdown/MarkdownEditor.vue +233 -0
- package/src/components/Utility/Markdown/MarkdownEditorContent.vue +296 -0
- package/src/components/Utility/Markdown/MarkdownEditorFooter.vue +50 -0
- package/src/components/Utility/Markdown/TablePopover.vue +420 -0
- package/src/components/Utility/Markdown/index.ts +11 -0
- package/src/components/Utility/Markdown/types.ts +27 -0
- package/src/components/Utility/Widgets/LabelPillWidget.vue +20 -0
- package/src/components/Utility/index.ts +1 -0
- package/src/composables/index.ts +1 -0
- package/src/composables/markdown/features/useBlockquotes.spec.ts +428 -0
- package/src/composables/markdown/features/useBlockquotes.ts +248 -0
- package/src/composables/markdown/features/useCodeBlockManager.ts +369 -0
- package/src/composables/markdown/features/useCodeBlocks.spec.ts +805 -0
- package/src/composables/markdown/features/useCodeBlocks.ts +774 -0
- package/src/composables/markdown/features/useContextMenu.ts +444 -0
- package/src/composables/markdown/features/useFocusTracking.ts +116 -0
- package/src/composables/markdown/features/useHeadings.spec.ts +834 -0
- package/src/composables/markdown/features/useHeadings.ts +290 -0
- package/src/composables/markdown/features/useInlineFormatting.spec.ts +705 -0
- package/src/composables/markdown/features/useInlineFormatting.ts +402 -0
- package/src/composables/markdown/features/useLineTypeMenu.ts +285 -0
- package/src/composables/markdown/features/useLinks.spec.ts +388 -0
- package/src/composables/markdown/features/useLinks.ts +374 -0
- package/src/composables/markdown/features/useLists.spec.ts +834 -0
- package/src/composables/markdown/features/useLists.ts +747 -0
- package/src/composables/markdown/features/usePopoverManager.ts +181 -0
- package/src/composables/markdown/features/useTables.spec.ts +1601 -0
- package/src/composables/markdown/features/useTables.ts +1107 -0
- package/src/composables/markdown/index.ts +16 -0
- package/src/composables/markdown/useMarkdownEditor.spec.ts +332 -0
- package/src/composables/markdown/useMarkdownEditor.ts +1077 -0
- package/src/composables/markdown/useMarkdownHotkeys.spec.ts +791 -0
- package/src/composables/markdown/useMarkdownHotkeys.ts +266 -0
- package/src/composables/markdown/useMarkdownSelection.ts +219 -0
- package/src/composables/markdown/useMarkdownSync.ts +549 -0
- package/src/composables/useCodeFormat.ts +17 -10
- package/src/composables/useCodeViewerEditor.spec.ts +655 -0
- package/src/composables/useCodeViewerEditor.ts +174 -20
- package/src/helpers/formats/highlightCSS.ts +236 -0
- package/src/helpers/formats/highlightHTML.ts +483 -0
- package/src/helpers/formats/highlightJavaScript.ts +346 -0
- package/src/helpers/formats/highlightSyntax.ts +15 -4
- package/src/helpers/formats/index.ts +3 -0
- package/src/helpers/formats/markdown/htmlToMarkdown/convertHeadings.ts +41 -0
- package/src/helpers/formats/markdown/htmlToMarkdown/index.spec.ts +489 -0
- package/src/helpers/formats/markdown/htmlToMarkdown/index.ts +425 -0
- package/src/helpers/formats/markdown/index.ts +7 -0
- package/src/helpers/formats/markdown/linePatterns.spec.ts +498 -0
- package/src/helpers/formats/markdown/linePatterns.ts +172 -0
- package/src/styles/danx.scss +3 -3
- package/src/styles/index.scss +5 -5
- package/src/styles/themes/danx/code.scss +257 -1
- package/src/styles/themes/danx/index.scss +10 -10
- package/src/styles/themes/danx/markdown.scss +59 -0
- package/src/test/helpers/editorTestUtils.spec.ts +296 -0
- package/src/test/helpers/editorTestUtils.ts +253 -0
- package/src/test/helpers/index.ts +1 -0
- package/src/test/highlighters.test.ts +153 -0
- package/src/test/setup.test.ts +12 -0
- package/src/test/setup.ts +12 -0
- package/src/types/widgets.d.ts +2 -2
- package/vite.config.js +5 -1
- package/vitest.config.ts +19 -0
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { htmlToMarkdown, escapeMarkdownChars, isHeadingElement, getHeadingLevel, convertHeading } from "./index";
|
|
3
|
+
|
|
4
|
+
describe("htmlToMarkdown", () => {
|
|
5
|
+
describe("headings", () => {
|
|
6
|
+
it("converts h1 to markdown", () => {
|
|
7
|
+
expect(htmlToMarkdown("<h1>Title</h1>")).toBe("# Title");
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it("converts h2 to markdown", () => {
|
|
11
|
+
expect(htmlToMarkdown("<h2>Subtitle</h2>")).toBe("## Subtitle");
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("converts h3 to markdown", () => {
|
|
15
|
+
expect(htmlToMarkdown("<h3>Section</h3>")).toBe("### Section");
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("converts h4 to markdown", () => {
|
|
19
|
+
expect(htmlToMarkdown("<h4>Subsection</h4>")).toBe("#### Subsection");
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("converts h5 to markdown", () => {
|
|
23
|
+
expect(htmlToMarkdown("<h5>Minor Section</h5>")).toBe("##### Minor Section");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("converts h6 to markdown", () => {
|
|
27
|
+
expect(htmlToMarkdown("<h6>Smallest Heading</h6>")).toBe("###### Smallest Heading");
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("handles empty headings", () => {
|
|
31
|
+
expect(htmlToMarkdown("<h1></h1>")).toBe("");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("handles headings with whitespace only", () => {
|
|
35
|
+
expect(htmlToMarkdown("<h1> </h1>")).toBe("");
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe("paragraphs", () => {
|
|
40
|
+
it("converts single paragraph", () => {
|
|
41
|
+
expect(htmlToMarkdown("<p>Text</p>")).toBe("Text");
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("converts multiple paragraphs", () => {
|
|
45
|
+
expect(htmlToMarkdown("<p>First paragraph</p><p>Second paragraph</p>"))
|
|
46
|
+
.toBe("First paragraph\n\nSecond paragraph");
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("handles empty paragraphs", () => {
|
|
50
|
+
expect(htmlToMarkdown("<p></p>")).toBe("");
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe("inline formatting", () => {
|
|
55
|
+
it("converts strong to bold", () => {
|
|
56
|
+
expect(htmlToMarkdown("<p><strong>bold</strong></p>")).toBe("**bold**");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("converts b to bold", () => {
|
|
60
|
+
expect(htmlToMarkdown("<p><b>bold</b></p>")).toBe("**bold**");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("converts em to italic", () => {
|
|
64
|
+
expect(htmlToMarkdown("<p><em>italic</em></p>")).toBe("*italic*");
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("converts i to italic", () => {
|
|
68
|
+
expect(htmlToMarkdown("<p><i>italic</i></p>")).toBe("*italic*");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("converts code to inline code", () => {
|
|
72
|
+
expect(htmlToMarkdown("<p><code>code</code></p>")).toBe("`code`");
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("converts del to strikethrough", () => {
|
|
76
|
+
expect(htmlToMarkdown("<p><del>strike</del></p>")).toBe("~~strike~~");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("converts s to strikethrough", () => {
|
|
80
|
+
expect(htmlToMarkdown("<p><s>strike</s></p>")).toBe("~~strike~~");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("converts mark to highlight", () => {
|
|
84
|
+
expect(htmlToMarkdown("<p><mark>highlight</mark></p>")).toBe("==highlight==");
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("converts sup to superscript", () => {
|
|
88
|
+
expect(htmlToMarkdown("<p><sup>superscript</sup></p>")).toBe("^superscript^");
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("converts sub to subscript", () => {
|
|
92
|
+
expect(htmlToMarkdown("<p><sub>subscript</sub></p>")).toBe("~subscript~");
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("handles nested bold and italic", () => {
|
|
96
|
+
expect(htmlToMarkdown("<p><strong><em>bold italic</em></strong></p>")).toBe("***bold italic***");
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("handles italic inside bold", () => {
|
|
100
|
+
expect(htmlToMarkdown("<p><strong>bold <em>and italic</em> text</strong></p>"))
|
|
101
|
+
.toBe("**bold *and italic* text**");
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("handles bold inside italic", () => {
|
|
105
|
+
expect(htmlToMarkdown("<p><em>italic <strong>and bold</strong> text</em></p>"))
|
|
106
|
+
.toBe("*italic **and bold** text*");
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("handles mixed formatting in paragraph", () => {
|
|
110
|
+
expect(htmlToMarkdown("<p>Normal <strong>bold</strong> and <em>italic</em> text</p>"))
|
|
111
|
+
.toBe("Normal **bold** and *italic* text");
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("skips empty formatting elements", () => {
|
|
115
|
+
expect(htmlToMarkdown("<p><strong></strong></p>")).toBe("");
|
|
116
|
+
expect(htmlToMarkdown("<p><em></em></p>")).toBe("");
|
|
117
|
+
expect(htmlToMarkdown("<p><del></del></p>")).toBe("");
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
describe("lists", () => {
|
|
122
|
+
describe("unordered lists", () => {
|
|
123
|
+
it("converts simple unordered list", () => {
|
|
124
|
+
const result = htmlToMarkdown("<ul><li>item</li></ul>");
|
|
125
|
+
expect(result).toBe("- item");
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("converts unordered list with multiple items", () => {
|
|
129
|
+
const result = htmlToMarkdown("<ul><li>first</li><li>second</li><li>third</li></ul>");
|
|
130
|
+
expect(result).toContain("- first");
|
|
131
|
+
expect(result).toContain("- second");
|
|
132
|
+
expect(result).toContain("- third");
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("handles list items with inline formatting", () => {
|
|
136
|
+
const result = htmlToMarkdown("<ul><li><strong>bold</strong> item</li></ul>");
|
|
137
|
+
expect(result).toBe("- **bold** item");
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
describe("ordered lists", () => {
|
|
142
|
+
it("converts simple ordered list", () => {
|
|
143
|
+
const result = htmlToMarkdown("<ol><li>item</li></ol>");
|
|
144
|
+
expect(result).toBe("1. item");
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("converts ordered list with multiple items", () => {
|
|
148
|
+
const result = htmlToMarkdown("<ol><li>first</li><li>second</li><li>third</li></ol>");
|
|
149
|
+
expect(result).toContain("1. first");
|
|
150
|
+
expect(result).toContain("2. second");
|
|
151
|
+
expect(result).toContain("3. third");
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
describe("nested lists", () => {
|
|
156
|
+
it("handles nested unordered list", () => {
|
|
157
|
+
const html = "<ul><li>parent<ul><li>child</li></ul></li></ul>";
|
|
158
|
+
const result = htmlToMarkdown(html);
|
|
159
|
+
expect(result).toContain("- parent");
|
|
160
|
+
expect(result).toContain(" - child");
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it("handles nested ordered list inside unordered", () => {
|
|
164
|
+
const html = "<ul><li>parent<ol><li>numbered child</li></ol></li></ul>";
|
|
165
|
+
const result = htmlToMarkdown(html);
|
|
166
|
+
expect(result).toContain("- parent");
|
|
167
|
+
expect(result).toContain(" 1. numbered child");
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
describe("links and images", () => {
|
|
173
|
+
it("converts link with text", () => {
|
|
174
|
+
expect(htmlToMarkdown("<p><a href=\"https://example.com\">link text</a></p>"))
|
|
175
|
+
.toBe("[link text](https://example.com)");
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it("handles link with empty href", () => {
|
|
179
|
+
expect(htmlToMarkdown("<p><a href=\"\">link text</a></p>")).toBe("[link text]()");
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it("handles link with formatted text", () => {
|
|
183
|
+
expect(htmlToMarkdown("<p><a href=\"https://example.com\"><strong>bold link</strong></a></p>"))
|
|
184
|
+
.toBe("[**bold link**](https://example.com)");
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("converts image with alt text", () => {
|
|
188
|
+
expect(htmlToMarkdown("<img src=\"https://example.com/image.png\" alt=\"alt text\">"))
|
|
189
|
+
.toBe("");
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it("handles image with empty alt", () => {
|
|
193
|
+
expect(htmlToMarkdown("<img src=\"https://example.com/image.png\" alt=\"\">"))
|
|
194
|
+
.toBe("");
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it("handles image with no alt attribute", () => {
|
|
198
|
+
expect(htmlToMarkdown("<img src=\"https://example.com/image.png\">"))
|
|
199
|
+
.toBe("");
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
describe("code blocks", () => {
|
|
204
|
+
it("converts pre code block", () => {
|
|
205
|
+
const result = htmlToMarkdown("<pre><code>const x = 1;</code></pre>");
|
|
206
|
+
expect(result).toBe("```\nconst x = 1;\n```");
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it("converts pre code block with language class", () => {
|
|
210
|
+
const result = htmlToMarkdown("<pre><code class=\"language-javascript\">const x = 1;</code></pre>");
|
|
211
|
+
expect(result).toBe("```javascript\nconst x = 1;\n```");
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it("handles multiline code block", () => {
|
|
215
|
+
const result = htmlToMarkdown("<pre><code>line1\nline2\nline3</code></pre>");
|
|
216
|
+
expect(result).toBe("```\nline1\nline2\nline3\n```");
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it("handles pre without code element", () => {
|
|
220
|
+
const result = htmlToMarkdown("<pre>plain preformatted text</pre>");
|
|
221
|
+
expect(result).toBe("```\nplain preformatted text\n```");
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
describe("blockquotes", () => {
|
|
226
|
+
it("converts simple blockquote", () => {
|
|
227
|
+
const result = htmlToMarkdown("<blockquote><p>quote text</p></blockquote>");
|
|
228
|
+
expect(result).toContain("> quote text");
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it("handles blockquote with multiple paragraphs", () => {
|
|
232
|
+
const result = htmlToMarkdown("<blockquote><p>line 1</p><p>line 2</p></blockquote>");
|
|
233
|
+
expect(result).toContain("> line 1");
|
|
234
|
+
expect(result).toContain("> line 2");
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
describe("horizontal rules", () => {
|
|
239
|
+
it("converts hr element", () => {
|
|
240
|
+
expect(htmlToMarkdown("<p>before</p><hr><p>after</p>")).toContain("---");
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
describe("line breaks", () => {
|
|
245
|
+
it("converts br to markdown line break", () => {
|
|
246
|
+
const result = htmlToMarkdown("<p>line1<br>line2</p>");
|
|
247
|
+
expect(result).toContain("line1");
|
|
248
|
+
expect(result).toContain("line2");
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
describe("tables", () => {
|
|
253
|
+
it("converts simple table", () => {
|
|
254
|
+
const html = `
|
|
255
|
+
<table>
|
|
256
|
+
<thead>
|
|
257
|
+
<tr><th>Header 1</th><th>Header 2</th></tr>
|
|
258
|
+
</thead>
|
|
259
|
+
<tbody>
|
|
260
|
+
<tr><td>Cell 1</td><td>Cell 2</td></tr>
|
|
261
|
+
</tbody>
|
|
262
|
+
</table>
|
|
263
|
+
`;
|
|
264
|
+
const result = htmlToMarkdown(html);
|
|
265
|
+
expect(result).toContain("| Header 1 | Header 2 |");
|
|
266
|
+
expect(result).toContain("| --- | --- |");
|
|
267
|
+
expect(result).toContain("| Cell 1 | Cell 2 |");
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it("handles table with center alignment", () => {
|
|
271
|
+
const html = `
|
|
272
|
+
<table>
|
|
273
|
+
<thead>
|
|
274
|
+
<tr><th style="text-align: center">Centered</th></tr>
|
|
275
|
+
</thead>
|
|
276
|
+
<tbody>
|
|
277
|
+
<tr><td>Cell</td></tr>
|
|
278
|
+
</tbody>
|
|
279
|
+
</table>
|
|
280
|
+
`;
|
|
281
|
+
const result = htmlToMarkdown(html);
|
|
282
|
+
expect(result).toContain(":---:");
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it("handles table with right alignment", () => {
|
|
286
|
+
const html = `
|
|
287
|
+
<table>
|
|
288
|
+
<thead>
|
|
289
|
+
<tr><th style="text-align: right">Right</th></tr>
|
|
290
|
+
</thead>
|
|
291
|
+
<tbody>
|
|
292
|
+
<tr><td>Cell</td></tr>
|
|
293
|
+
</tbody>
|
|
294
|
+
</table>
|
|
295
|
+
`;
|
|
296
|
+
const result = htmlToMarkdown(html);
|
|
297
|
+
expect(result).toContain("---:");
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
it("handles table with inline formatting in cells", () => {
|
|
301
|
+
const html = `
|
|
302
|
+
<table>
|
|
303
|
+
<thead>
|
|
304
|
+
<tr><th><strong>Bold Header</strong></th></tr>
|
|
305
|
+
</thead>
|
|
306
|
+
<tbody>
|
|
307
|
+
<tr><td><em>Italic cell</em></td></tr>
|
|
308
|
+
</tbody>
|
|
309
|
+
</table>
|
|
310
|
+
`;
|
|
311
|
+
const result = htmlToMarkdown(html);
|
|
312
|
+
expect(result).toContain("**Bold Header**");
|
|
313
|
+
expect(result).toContain("*Italic cell*");
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
describe("zero-width space stripping", () => {
|
|
318
|
+
it("removes zero-width spaces from text", () => {
|
|
319
|
+
expect(htmlToMarkdown("<p>text\u200Bwith\u200Bspaces</p>")).toBe("textwithspaces");
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
it("removes zero-width spaces from formatted text", () => {
|
|
323
|
+
expect(htmlToMarkdown("<p><strong>bold\u200B</strong> text</p>")).toBe("**bold** text");
|
|
324
|
+
});
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
describe("div and span containers", () => {
|
|
328
|
+
it("processes content inside div", () => {
|
|
329
|
+
expect(htmlToMarkdown("<div><p>text in div</p></div>")).toBe("text in div");
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
it("processes content inside span", () => {
|
|
333
|
+
expect(htmlToMarkdown("<p><span>text in span</span></p>")).toBe("text in span");
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
describe("complex document structure", () => {
|
|
338
|
+
it("handles mixed content types", () => {
|
|
339
|
+
const html = `
|
|
340
|
+
<h1>Document Title</h1>
|
|
341
|
+
<p>Introduction paragraph with <strong>bold</strong> text.</p>
|
|
342
|
+
<h2>Section</h2>
|
|
343
|
+
<ul>
|
|
344
|
+
<li>Item 1</li>
|
|
345
|
+
<li>Item 2</li>
|
|
346
|
+
</ul>
|
|
347
|
+
<p>Conclusion.</p>
|
|
348
|
+
`;
|
|
349
|
+
const result = htmlToMarkdown(html);
|
|
350
|
+
expect(result).toContain("# Document Title");
|
|
351
|
+
expect(result).toContain("**bold**");
|
|
352
|
+
expect(result).toContain("## Section");
|
|
353
|
+
expect(result).toContain("- Item 1");
|
|
354
|
+
expect(result).toContain("Conclusion");
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
it("normalizes excessive newlines", () => {
|
|
358
|
+
const html = "<p>First</p><p></p><p></p><p>Second</p>";
|
|
359
|
+
const result = htmlToMarkdown(html);
|
|
360
|
+
// Should not have more than 2 consecutive newlines
|
|
361
|
+
expect(result).not.toMatch(/\n{3,}/);
|
|
362
|
+
});
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
describe("HTMLElement input", () => {
|
|
366
|
+
it("accepts HTMLElement directly", () => {
|
|
367
|
+
const container = document.createElement("div");
|
|
368
|
+
container.innerHTML = "<p>Hello <strong>World</strong></p>";
|
|
369
|
+
expect(htmlToMarkdown(container)).toBe("Hello **World**");
|
|
370
|
+
});
|
|
371
|
+
});
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
describe("escapeMarkdownChars", () => {
|
|
375
|
+
it("escapes backslash", () => {
|
|
376
|
+
expect(escapeMarkdownChars("test\\test")).toBe("test\\\\test");
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
it("escapes backtick", () => {
|
|
380
|
+
expect(escapeMarkdownChars("test`test")).toBe("test\\`test");
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
it("escapes asterisk", () => {
|
|
384
|
+
expect(escapeMarkdownChars("test*test")).toBe("test\\*test");
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
it("escapes underscore", () => {
|
|
388
|
+
expect(escapeMarkdownChars("test_test")).toBe("test\\_test");
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
it("escapes square brackets", () => {
|
|
392
|
+
expect(escapeMarkdownChars("[test]")).toBe("\\[test\\]");
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
it("escapes parentheses", () => {
|
|
396
|
+
expect(escapeMarkdownChars("(test)")).toBe("\\(test\\)");
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
it("escapes hash", () => {
|
|
400
|
+
expect(escapeMarkdownChars("#test")).toBe("\\#test");
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
it("escapes plus", () => {
|
|
404
|
+
expect(escapeMarkdownChars("+test")).toBe("\\+test");
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
it("escapes hyphen", () => {
|
|
408
|
+
expect(escapeMarkdownChars("-test")).toBe("\\-test");
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
it("escapes period", () => {
|
|
412
|
+
expect(escapeMarkdownChars("1.test")).toBe("1\\.test");
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
it("escapes exclamation mark", () => {
|
|
416
|
+
expect(escapeMarkdownChars("!test")).toBe("\\!test");
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
it("escapes curly braces", () => {
|
|
420
|
+
expect(escapeMarkdownChars("{test}")).toBe("\\{test\\}");
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
it("handles multiple special characters", () => {
|
|
424
|
+
expect(escapeMarkdownChars("*bold* and _italic_")).toBe("\\*bold\\* and \\_italic\\_");
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
it("handles text without special characters", () => {
|
|
428
|
+
expect(escapeMarkdownChars("plain text")).toBe("plain text");
|
|
429
|
+
});
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
describe("heading utilities", () => {
|
|
433
|
+
describe("isHeadingElement", () => {
|
|
434
|
+
it("returns true for h1-h6", () => {
|
|
435
|
+
for (let i = 1; i <= 6; i++) {
|
|
436
|
+
const el = document.createElement(`h${i}`);
|
|
437
|
+
expect(isHeadingElement(el)).toBe(true);
|
|
438
|
+
}
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
it("returns false for non-heading elements", () => {
|
|
442
|
+
const elements = ["p", "div", "span", "h7", "header"];
|
|
443
|
+
for (const tag of elements) {
|
|
444
|
+
const el = document.createElement(tag);
|
|
445
|
+
expect(isHeadingElement(el)).toBe(false);
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
describe("getHeadingLevel", () => {
|
|
451
|
+
it("returns correct level for h1-h6", () => {
|
|
452
|
+
for (let i = 1; i <= 6; i++) {
|
|
453
|
+
const el = document.createElement(`h${i}`);
|
|
454
|
+
expect(getHeadingLevel(el)).toBe(i);
|
|
455
|
+
}
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
it("returns 0 for non-heading elements", () => {
|
|
459
|
+
const el = document.createElement("p");
|
|
460
|
+
expect(getHeadingLevel(el)).toBe(0);
|
|
461
|
+
});
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
describe("convertHeading", () => {
|
|
465
|
+
it("converts heading with proper prefix", () => {
|
|
466
|
+
const h2 = document.createElement("h2");
|
|
467
|
+
h2.textContent = "Test Heading";
|
|
468
|
+
expect(convertHeading(h2)).toBe("## Test Heading\n\n");
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
it("returns empty string for non-heading", () => {
|
|
472
|
+
const p = document.createElement("p");
|
|
473
|
+
p.textContent = "Not a heading";
|
|
474
|
+
expect(convertHeading(p)).toBe("");
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
it("returns empty string for empty heading", () => {
|
|
478
|
+
const h1 = document.createElement("h1");
|
|
479
|
+
h1.textContent = "";
|
|
480
|
+
expect(convertHeading(h1)).toBe("");
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
it("trims whitespace from heading content", () => {
|
|
484
|
+
const h1 = document.createElement("h1");
|
|
485
|
+
h1.textContent = " Trimmed ";
|
|
486
|
+
expect(convertHeading(h1)).toBe("# Trimmed\n\n");
|
|
487
|
+
});
|
|
488
|
+
});
|
|
489
|
+
});
|