import assert from "node:assert/strict"; import test from "node:test"; import { readFile } from "node:fs/promises"; async function importSource(path) { const source = await readFile(new URL(path, import.meta.url), "utf8"); return import(`data:text/javascript;base64,${Buffer.from(source).toString("base64")}`); } class FakeEditor { constructor(value, start = 0, end = start) { this.value = value; this.selectionStart = start; this.selectionEnd = end; this.selectionDirection = "none"; this.readOnly = false; this.inputEvents = 0; } setRangeText(replacement, start, end, mode) { this.value = `${this.value.slice(0, start)}${replacement}${this.value.slice(end)}`; const caret = mode === "end" ? start + replacement.length : start; this.selectionStart = caret; this.selectionEnd = caret; } setSelectionRange(start, end, direction = "none") { this.selectionStart = start; this.selectionEnd = end; this.selectionDirection = direction; } dispatchEvent(event) { if (event.type === "input") this.inputEvents += 1; return true; } } const { applyIndentation } = await importSource("../static/js/editor-format.js"); const markdownSource = await readFile(new URL("../static/js/markdown.js", import.meta.url), "utf8"); const alignSource = markdownSource.match(/export function alignPreviewLineNumbers\(root\) \{[\s\S]*?\n\}/)?.[0]; assert.ok(alignSource, "alignPreviewLineNumbers source must exist"); const alignPreviewLineNumbers = new Function(`${alignSource.replace("export ", "")}; return alignPreviewLineNumbers;`)(); test("Tab inserts two spaces at the caret", () => { const editor = new FakeEditor("abcd", 2); assert.equal(applyIndentation(editor), true); assert.equal(editor.value, "ab cd"); assert.deepEqual([editor.selectionStart, editor.selectionEnd], [4, 4]); assert.equal(editor.inputEvents, 1); }); test("Tab indents every selected line", () => { const editor = new FakeEditor("alpha\nbeta\ngamma", 0, 10); assert.equal(applyIndentation(editor), true); assert.equal(editor.value, " alpha\n beta\ngamma"); assert.deepEqual([editor.selectionStart, editor.selectionEnd], [2, 14]); }); test("Shift+Tab removes up to two spaces from selected lines", () => { const editor = new FakeEditor(" alpha\n beta", 2, 13); assert.equal(applyIndentation(editor, { outdent: true }), true); assert.equal(editor.value, "alpha\nbeta"); assert.deepEqual([editor.selectionStart, editor.selectionEnd], [0, 10]); }); test("Shift+Tab removes a leading tab", () => { const editor = new FakeEditor("\talpha", 4); assert.equal(applyIndentation(editor, { outdent: true }), true); assert.equal(editor.value, "alpha"); assert.deepEqual([editor.selectionStart, editor.selectionEnd], [3, 3]); }); test("preview line-number alignment ignores horizontal scroll", () => { const previousGetComputedStyle = globalThis.getComputedStyle; globalThis.getComputedStyle = () => ({ paddingLeft: "62px" }); let assigned; const line = { getBoundingClientRect: () => ({ left: 70 }), style: { setProperty: (name, value) => { assigned = [name, value]; } }, }; const root = { scrollLeft: 80, getBoundingClientRect: () => ({ left: 100 }), querySelectorAll: () => [line], }; try { alignPreviewLineNumbers(root); assert.deepEqual(assigned, ["--preview-line-left", "-38px"]); } finally { globalThis.getComputedStyle = previousGetComputedStyle; } }); test("compact editor layout starts below 1500px", async () => { const css = await readFile(new URL("../static/css/styles.css", import.meta.url), "utf8"); const editorSource = await readFile(new URL("../static/js/note-editor.js", import.meta.url), "utf8"); assert.equal((css.match(/@media \(max-width: 1499px\)/g) || []).length, 4); assert.doesNotMatch(css, /@media \(max-width: 1920px\)/); assert.match(editorSource, /compactLayoutQuery = window\.matchMedia\("\(max-width: 1499px\)"\)/); assert.match(editorSource, /compactBubbleQuery = matchMedia\("\(max-width: 1499px\)"\)/); }); test("read-only mode blocks every document write path", async () => { const editorHtml = await readFile(new URL("../static/editor.html", import.meta.url), "utf8"); const editorSource = await readFile(new URL("../static/js/note-editor.js", import.meta.url), "utf8"); assert.match(editorHtml, /