trilium/src/services/export/markdown.spec.ts
2025-04-17 18:34:59 +03:00

346 lines
14 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { describe, it, expect } from "vitest";
import markdownExportService from "./markdown.js";
import { trimIndentation } from "../../../spec/support/utils.js";
describe("Markdown export", () => {
it("exports correct language tag for known languages", () => {
const conversionTable = {
"language-text-x-nginx-conf": "nginx",
"language-text-x-diff": "diff",
"language-application-javascript-env-frontend": "javascript",
"language-application-javascript-env-backend": "javascript",
"language-text-x-asm-mips": "mips"
};
for (const [ input, output ] of Object.entries(conversionTable)) {
const html = trimIndentation`\
<p>A diff:</p>
<pre><code class="${input}">Hello
-world
+worldy
</code></pre>`;
const expected = trimIndentation`\
A diff:
\`\`\`${output}
Hello
-world
+worldy
\`\`\``;
expect(markdownExportService.toMarkdown(html)).toBe(expected);
}
});
it("removes auto tag for code blocks", () => {
const html = trimIndentation`\
<pre><code class="language-text-x-trilium-auto">Hello
-world
+worldy
</code></pre>`;
const expected = trimIndentation`\
\`\`\`
Hello
-world
+worldy
\`\`\``;
expect(markdownExportService.toMarkdown(html)).toBe(expected);
});
it("supports code block with no language tag", () => {
const html = trimIndentation`\
<pre><code>Hello</code></pre>`;
const expected = trimIndentation`\
\`\`\`
Hello
\`\`\``;
expect(markdownExportService.toMarkdown(html)).toBe(expected);
});
it("exports strikethrough text correctly", () => {
const html = "<s>hello</s>Hello <s>world</s>";
const expected = "~~hello~~Hello ~~world~~";
expect(markdownExportService.toMarkdown(html)).toBe(expected);
});
it("exports headings properly", () => {
const html = trimIndentation`\
<h1>Heading 1</h1>
<h2>Heading 2</h2>
<h3>Heading 3</h3>
<h4>Heading 4</h4>
<h5>Heading 5</h5>
<h6>Heading 6</h6>
`;
const expected = trimIndentation`\
# Heading 1
## Heading 2
### Heading 3
#### Heading 4
##### Heading 5
###### Heading 6`;
expect(markdownExportService.toMarkdown(html)).toBe(expected);
});
it("rewrites image URL with spaces", () => {
const html = `<img src="Hello world .png"/>`;
const expected = `![](Hello%20world%20%20.png)`;
expect(markdownExportService.toMarkdown(html)).toBe(expected);
});
it("supports keyboard shortcuts", () => {
const html = "<kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>Delete</kbd>";
expect(markdownExportService.toMarkdown(html)).toBe(html);
});
it("exports admonitions properly", () => {
const html = trimIndentation`\
<p>
Before
</p>
<aside class="admonition note">
<p>
This is a note.
</p>
</aside>
<aside class="admonition tip">
<p>
This is a tip.
</p>
</aside>
<aside class="admonition important">
<p>
This is a very important information.
</p>
<figure class="table">
<table>
<tbody>
<tr>
<td>
1
</td>
<td>
2
</td>
</tr>
<tr>
<td>
3
</td>
<td>
4
</td>
</tr>
</tbody>
</table>
</figure>
</aside>
<aside class="admonition caution">
<p>
This is a caution.
</p>
</aside>
<aside class="admonition warning">
<h2>
Title goes here
</h2>
<p>
This is a warning.
</p>
</aside>
<p>
After
</p>
`;
const space = " "; // editor config trimming space.
const expected = trimIndentation`\
Before
> [!NOTE]
> This is a note.
> [!TIP]
> This is a tip.
> [!IMPORTANT]
> This is a very important information.
>${space}
> <figure class="table"><table><tbody><tr><td>1</td><td>2</td></tr><tr><td>3</td><td>4</td></tr></tbody></table></figure>
> [!CAUTION]
> This is a caution.
> [!WARNING]
> ## Title goes here
>${space}
> This is a warning.
After`;
expect(markdownExportService.toMarkdown(html)).toBe(expected);
});
it("exports code in tables properly", () => {
const html = trimIndentation`\
<table>
<tr>
<td>
Row 1
</td>
<td>
<p>Allows displaying the value of one or more attributes in the calendar
like this:&nbsp;</p>
<p>
<img src="13_Calendar View_image.png" alt="">
</p>
<pre><code class="language-text-x-trilium-auto">#weight="70"
#Mood="Good"
#calendar:displayedAttributes="weight,Mood"</code></pre>
<p>It can also be used with relations, case in which it will display the
title of the target note:</p><pre><code class="language-text-x-trilium-auto">~assignee=@My assignee
#calendar:displayedAttributes="assignee"</code></pre>
</td>
</tr>
</table>
`;
const expected = trimIndentation`\
<table><tbody><tr><td>Row 1</td><td><p>Allows displaying the value of one or more attributes in the calendar like this:&nbsp;</p><p><img src="13_Calendar View_image.png" alt=""></p><pre><code class="language-text-x-trilium-auto">#weight="70"
#Mood="Good"
#calendar:displayedAttributes="weight,Mood"</code></pre><p>It can also be used with relations, case in which it will display the title of the target note:</p><pre><code class="language-text-x-trilium-auto">~assignee=@My assignee
#calendar:displayedAttributes="assignee"</code></pre></td></tr></tbody></table>`;
expect(markdownExportService.toMarkdown(html)).toBe(expected);
});
it("converts &nbsp; to character", () => {
const html = /*html*/`<p>Hello&nbsp;world.</p>`;
const expected = `Hello\u00a0world.`;
expect(markdownExportService.toMarkdown(html)).toBe(expected);
});
it("preserves non-breaking space character", () => {
const html = /*html*/`<p>Hello\u00adworld.</p>`;
const expected = `Hello\u00adworld.`;
expect(markdownExportService.toMarkdown(html)).toBe(expected);
});
it("exports normal links verbatim", () => {
const html = /*html*/`<p><a href="https://www.google.com">Google</a></p>`;
const expected = `[Google](https://www.google.com)`;
expect(markdownExportService.toMarkdown(html)).toBe(expected);
});
it("exports reference links verbatim", () => {
const html = /*html*/`<p><a class="reference-link" href="../../Canvas.html">Canvas</a></p>`;
const expected = `<a class="reference-link" href="../../Canvas.html">Canvas</a>`;
expect(markdownExportService.toMarkdown(html)).toBe(expected);
});
it("converts image if it has no custom properties", () => {
const html = /*html*/`<p><img src="Include Note_image.png"></p>`;
const expected = `![](Include%20Note_image.png)`;
expect(markdownExportService.toMarkdown(html)).toBe(expected);
});
it("preserves image verbatim if it has a width or height attribute", () => {
const scenarios = [
/*html*/`<img src="Include Note_image.png" width="16" height="16">`,
/*html*/`<img src="Include Note_image.png" width="16">`,
/*html*/`<img src="Include Note_image.png" height="16">`,
/*html*/`<img class="image_resized" style="aspect-ratio:853/315;width:50%;" src="6_File_image.png" width="853" height="315">`
];
for (const expected of scenarios) {
const html = /*html*/`<p>${expected}</p>`;
expect(markdownExportService.toMarkdown(html)).toBe(expected);
}
});
it("preserves figures", () => {
const html = /*html*/trimIndentation`\
<figure class="image" style="width:53.44%;">
<img style="aspect-ratio:991/403;" src="Jump to Note_image.png" width="991"
height="403">
</figure>
`;
const expected = `<figure class="image" style="width:53.44%;"><img style="aspect-ratio:991/403;" src="Jump to Note_image.png" width="991" height="403"></figure>`;
expect(markdownExportService.toMarkdown(html)).toBe(expected);
});
it("converts inline math expressions into proper Markdown syntax", () => {
const html = /*html*/String.raw`<span class="math-tex">\(H(X, Y) = \sum_{i=1}^{M} \sum_{j=1}^{L} p(x_i, y_j) \log_2 \frac{1}{p(x_i, y_j)} = - \sum_{i=1}^{M} \sum_{j=1}^{L} p(x_i, y_j) \log_2 p(x_i, y_j) \frac{\text{bits}}{\text{symbol}}\)</span></span>`;
const expected = String.raw`$H(X, Y) = \sum_{i=1}^{M} \sum_{j=1}^{L} p(x_i, y_j) \log_2 \frac{1}{p(x_i, y_j)} = - \sum_{i=1}^{M} \sum_{j=1}^{L} p(x_i, y_j) \log_2 p(x_i, y_j) \frac{\text{bits}}{\text{symbol}}$`;
expect(markdownExportService.toMarkdown(html)).toBe(expected);
});
it("converts display math expressions into proper Markdown syntax", () => {
const html = /*html*/String.raw`<span class="math-tex">\[H(X, Y) = \sum_{i=1}^{M} \sum_{j=1}^{L} p(x_i, y_j) \log_2 \frac{1}{p(x_i, y_j)} = - \sum_{i=1}^{M} \sum_{j=1}^{L} p(x_i, y_j) \log_2 p(x_i, y_j) \frac{\text{bits}}{\text{symbol}}\]</span></span>`;
const expected = String.raw`$$H(X, Y) = \sum_{i=1}^{M} \sum_{j=1}^{L} p(x_i, y_j) \log_2 \frac{1}{p(x_i, y_j)} = - \sum_{i=1}^{M} \sum_{j=1}^{L} p(x_i, y_j) \log_2 p(x_i, y_j) \frac{\text{bits}}{\text{symbol}}$$`;
expect(markdownExportService.toMarkdown(html)).toBe(expected);
});
it("does not generate additional spacing when exporting lists with paragraph", () => {
const html = trimIndentation`\
<ul>
<li><a href="https://github.com/JYC333">@JYC333</a> made their first contribution
in <a href="https://github.com/TriliumNext/Notes/pull/294">#294</a>
</li>
<li>
<p><a href="https://github.com/TriliumNext/Notes/issues/375">Note Tooltip isn't removed when clicking on internal trilium link in read-only mode</a>
</p>
</li>
<li>
<p><a href="https://github.com/TriliumNext/Notes/issues/384">Calendar dropdown won't close if click/right-click other button that open notes from launcher bar</a>
</p>
</li>
</ul>
`;
const expected = trimIndentation`\
* [@JYC333](https://github.com/JYC333) made their first contribution in [#294](https://github.com/TriliumNext/Notes/pull/294)
* [Note Tooltip isn't removed when clicking on internal trilium link in read-only mode](https://github.com/TriliumNext/Notes/issues/375)
* [Calendar dropdown won't close if click/right-click other button that open notes from launcher bar](https://github.com/TriliumNext/Notes/issues/384)`;
expect(markdownExportService.toMarkdown(html)).toBe(expected);
});
it("preserves include note", () => {
const html = /*html*/`<section class="include-note" data-note-id="i4A5g9iOg9I0" data-box-size="full"> </section>`;
const expected = /*markdown*/`<section class="include-note" data-note-id="i4A5g9iOg9I0" data-box-size="full">&nbsp;</section>`;
expect(markdownExportService.toMarkdown(html)).toBe(expected);
});
it("exports todo lists properly", () => {
const html = trimIndentation/*html*/`\
<ul class="todo-list">
<li>
<label class="todo-list__label">
<input type="checkbox" checked="checked" disabled="disabled"><span class="todo-list__label__description">Hello</span>
</label>
</li>
<li>
<label class="todo-list__label">
<input type="checkbox" disabled="disabled"><span class="todo-list__label__description">World</span>
</label>
</li>
</ul>
`;
const expected = trimIndentation`\
- [x] Hello
- [ ] World`;
expect(markdownExportService.toMarkdown(html)).toBe(expected);
});
});