trilium/apps/server/src/share/content_renderer.spec.ts
Elian Doran 8b3afc1f49
Some checks are pending
Checks / main (push) Waiting to run
CodeQL Advanced / Analyze (actions) (push) Waiting to run
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
Dev / Test development (push) Waiting to run
Dev / Build Docker image (push) Blocked by required conditions
Dev / Check Docker build (Dockerfile) (push) Blocked by required conditions
Dev / Check Docker build (Dockerfile.alpine) (push) Blocked by required conditions
/ Check Docker build (Dockerfile) (push) Waiting to run
/ Check Docker build (Dockerfile.alpine) (push) Waiting to run
/ Build Docker images (Dockerfile, ubuntu-24.04-arm, linux/arm64) (push) Blocked by required conditions
/ Build Docker images (Dockerfile.alpine, ubuntu-latest, linux/amd64) (push) Blocked by required conditions
/ Build Docker images (Dockerfile.legacy, ubuntu-24.04-arm, linux/arm/v7) (push) Blocked by required conditions
/ Build Docker images (Dockerfile.legacy, ubuntu-24.04-arm, linux/arm/v8) (push) Blocked by required conditions
/ Merge manifest lists (push) Blocked by required conditions
playwright / E2E tests on linux-arm64 (push) Waiting to run
playwright / E2E tests on linux-x64 (push) Waiting to run
fix(share): reference links outside share appear as [missing note]
2025-12-03 22:22:10 +02:00

210 lines
8.4 KiB
TypeScript

import { describe, it, expect } from "vitest";
import { getContent, renderCode, type Result } from "./content_renderer.js";
import { trimIndentation } from "@triliumnext/commons";
import { buildShareNote, buildShareNotes } from "../test/shaca_mocking.js";
describe("content_renderer", () => {
beforeAll(() => {
vi.mock("../becca/becca_loader.js", () => ({
default: {
load: vi.fn(),
loaded: Promise.resolve()
}
}));
});
it("Reports protected notes not being renderable", () => {
const note = buildShareNote({ isProtected: true });
const result = getContent(note);
expect(result.content).toStrictEqual("<p>Protected note cannot be displayed</p>");
});
describe("Text note", () => {
it("parses simple note", () => {
const content = trimIndentation`\
<figure class="image image-style-align-right image_resized" style="width:29.84%;">
<img style="aspect-ratio:150/150;" src="api/attachments/TnyuBzEXJZln/image/Trilium Demo_icon-color.svg" width="150" height="150">
</figure>
<p>
<strong>
Welcome to Trilium Notes!
</strong>
</p>`;
const note = buildShareNote({ content });
const result = getContent(note);
expect(result.content).toStrictEqual(content);
});
it("renders included notes", () => {
buildShareNotes([
{ id: "subnote1", content: `<p>Foo</p><div>Bar</div>` },
{ id: "subnote2", content: `<strong>Baz</strong>` }
]);
const note = buildShareNote({
id: "note1",
content: trimIndentation`\
<p>Before</p>
<section class="include-note" data-note-id="subnote1" data-box-size="small">&nbsp;</section>
<section class="include-note" data-note-id="subnote2" data-box-size="small">&nbsp;</section>
<p>After</p>
`
});
const result = getContent(note);
expect(result.content).toStrictEqual(trimIndentation`\
<p>Before</p>
<p>Foo</p><div>Bar</div>
<strong>Baz</strong>
<p>After</p>
`);
});
it("handles syntax highlight for code blocks with escaped syntax", () => {
const note = buildShareNote({
id: "note",
content: trimIndentation`\
<h2>
Defining the options
</h2>
<pre>
<code class="language-text-x-trilium-auto">&lt;t t-name="module.SectionWidthOption"&gt;
&lt;BuilderRow label.translate="Section Width"&gt;
&lt;/BuilderRow&gt;
&lt;/t&gt;</code>
</pre>
`
});
const result = getContent(note);
expect(result.content).toStrictEqual(trimIndentation`\
<h2>
Defining the options
</h2>
<pre>
<code class="language-text-x-trilium-auto hljs"><span class="hljs-tag">&lt;<span class="hljs-name">t</span> <span class="hljs-attr">t-name</span>=<span class="hljs-string">&quot;module.SectionWidthOption&quot;</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">BuilderRow</span> <span class="hljs-attr">label.translate</span>=<span class="hljs-string">&quot;Section Width&quot;</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">BuilderRow</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">t</span>&gt;</span></code>
</pre>
`)
});
describe("Reference links", () => {
it("handles attachment link", () => {
const content = trimIndentation`\
<h1>Test</h1>
<p>
<a class="reference-link" href="#root/iwTmeWnqBG5Q?viewMode=attachments&amp;attachmentId=q14s2Id7V6pp">
5863845791835102555.mp4
</a>
&nbsp;
</p>
`;
const note = buildShareNote({
content,
attachments: [ { id: "q14s2Id7V6pp", title: "5863845791835102555.mp4" } ]
});
const result = getContent(note);
expect(result.content).toStrictEqual(trimIndentation`\
<h1>Test</h1>
<p>
<a class="reference-link attachment-link role-file" href="api/attachments/q14s2Id7V6pp/download">5863845791835102555.mp4</a>
&nbsp;
</p>
`);
});
it("handles protected notes", () => {
buildShareNote({
id: "MSkxxCFbBsYP",
title: "Foo",
isProtected: true
});
const note = buildShareNote({
id: "note",
content: trimIndentation`\
<p>
<a class="reference-link" href="#root/zaIItd4TM5Ly/MSkxxCFbBsYP">
Foo
</a>
</p>
`
});
const result = getContent(note);
expect(result.content).toStrictEqual(trimIndentation`\
<p>
<a class="reference-link type-text" href="./MSkxxCFbBsYP">[protected]</a>
</p>
`);
});
it("handles missing notes", () => {
const note = buildShareNote({
id: "note",
content: trimIndentation`\
<p>
<a class="reference-link" href="#root/zaIItd4TM5Ly/AsKxyCFbBsYp">
Foo
</a>
</p>
`
});
const result = getContent(note);
const content = (result.content as string).replaceAll(/\s/g, "");
expect(content).toStrictEqual("<p>Foo</p>");
});
it("properly escapes note title", () => {
buildShareNote({
id: "MSkxxCFbBsYP",
title: "The quick <strong>brown</strong> fox"
});
const note = buildShareNote({
id: "note",
content: trimIndentation`\
<p>
<a class="reference-link" href="#root/zaIItd4TM5Ly/MSkxxCFbBsYP">
Hi
</a>
</p>
`
});
const result = getContent(note);
expect(result.content).toStrictEqual(trimIndentation`\
<p>
<a class="reference-link type-text" href="./MSkxxCFbBsYP"><span><span class="bx bx-note"></span>The quick &lt;strong&gt;brown&lt;/strong&gt; fox</span></a>
</p>
`);
});
});
});
describe("renderCode", () => {
it("identifies empty content", () => {
const emptyResult: Result = {
header: "",
content: " "
};
renderCode(emptyResult);
expect(emptyResult.isEmpty).toBeTruthy();
});
it("identifies unsupported content type", () => {
const emptyResult: Result = {
header: "",
content: Buffer.from("Hello world")
};
renderCode(emptyResult);
expect(emptyResult.isEmpty).toBeTruthy();
});
it("wraps code in <pre>", () => {
const result: Result = {
header: "",
content: "\tHello\nworld"
};
renderCode(result);
expect(result.isEmpty).toBeFalsy();
expect(result.content).toBe("<pre>\tHello\nworld</pre>");
});
});
});