fix(tests/standalone): keep core specs passing under happy-dom

Patch DOMParser.parseFromString in the standalone vitest setup to strip
the leading LF after <pre>/<listing>/<textarea>, matching the HTML spec
behavior that turnish relies on in Node (domino). Decode attachment
content via decodeUtf8 in the ENEX spec so binary bytes don't get
comma-stringified as a Uint8Array.
This commit is contained in:
Elian Doran
2026-04-21 00:14:24 +03:00
parent ca4e10bd66
commit eb5e4fdd37
2 changed files with 14 additions and 20 deletions

View File

@@ -4,7 +4,6 @@ import { fileURLToPath } from "node:url";
import { initializeCore, options } from "@triliumnext/core";
import schemaSql from "@triliumnext/core/src/assets/schema.sql?raw";
import HappyDomHtmlParser from "happy-dom/lib/html-parser/HTMLParser.js";
import serverEnTranslations from "../../server/src/assets/translations/en/server.json";
import { beforeAll } from "vitest";
@@ -71,23 +70,17 @@ WebAssembly.instantiateStreaming = (async (source, importObject) => {
// Per HTML5 parsing spec, a single U+000A LINE FEED immediately after a <pre>,
// <listing>, or <textarea> start tag must be ignored ("newlines at the start
// of pre blocks are ignored as an authoring convenience"). Real browsers and
// domino (which the server runtime uses via turnish) both implement this;
// happy-dom (as of 20.8.9) does not — it keeps the LF as a text node.
//
// That difference makes turnish's markdown export produce different output
// under happy-dom vs. production, breaking markdown.spec.ts > "exports jQuery
// code in table properly". Patch HTMLParser.parse to pre-process the string.
// domino (which turnish uses in Node) both implement this; happy-dom does not.
// Patch at the DOMParser boundary since turnish prefers DOMParser when it's
// available — patching via module-level HTMLParser import hits a different
// happy-dom copy than the vitest env loaded.
const LEADING_LF_IN_PRE_RE = /(<(?:pre|listing|textarea)\b[^>]*>)(\r\n|\r|\n)/gi;
const originalHtmlParserParse = (HappyDomHtmlParser as unknown as {
prototype: { parse(html: string, rootNode?: unknown): unknown };
}).prototype.parse;
(HappyDomHtmlParser as unknown as {
prototype: { parse(html: string, rootNode?: unknown): unknown };
}).prototype.parse = function (html: string, rootNode?: unknown) {
const patched = typeof html === "string"
? html.replace(LEADING_LF_IN_PRE_RE, "$1")
: html;
return originalHtmlParserParse.call(this, patched, rootNode);
const originalParseFromString = DOMParser.prototype.parseFromString;
DOMParser.prototype.parseFromString = function (source: string, type: DOMParserSupportedType) {
const patched = typeof source === "string"
? source.replace(LEADING_LF_IN_PRE_RE, "$1")
: source;
return originalParseFromString.call(this, patched, type);
};
// =============================================================================

View File

@@ -8,6 +8,7 @@ import type BNote from "../../becca/entities/bnote.js";
import { getContext } from "../context.js";
import sql_init from "../sql_init.js";
import TaskContext from "../task_context.js";
import { decodeUtf8 } from "../utils/binary.js";
import enex from "./enex.js";
const scriptDir = dirname(fileURLToPath(import.meta.url));
@@ -61,15 +62,15 @@ describe("importEnex", () => {
const txt = attachments.find(a => a.title === "attachments1.txt");
expect(txt).toBeTruthy();
expect(txt!.mime).toBe("text/plain");
expect(txt!.getContent().toString()).toBe("111");
expect(decodeUtf8(txt!.getContent())).toBe("111");
const bin = attachments.find(a => a.title === "attachments2");
expect(bin).toBeTruthy();
expect(bin!.mime).toBe("application/octet-stream");
expect(bin!.getContent().toString()).toBe("222");
expect(decodeUtf8(bin!.getContent())).toBe("222");
// The note content should contain reference links to the attachments
const content = test1!.getContent().toString();
const content = decodeUtf8(test1!.getContent());
expect(content).toContain(`class="reference-link" href="#root/${test1!.noteId}?viewMode=attachments&amp;attachmentId=${txt!.attachmentId}"`);
expect(content).toContain(`class="reference-link" href="#root/${test1!.noteId}?viewMode=attachments&amp;attachmentId=${bin!.attachmentId}"`);
});