You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
181 lines
5.2 KiB
181 lines
5.2 KiB
import { describe, expect, test } from "bun:test";
|
|
import {
|
|
buildMarkdownExportFileName,
|
|
downloadMarkdownFile,
|
|
normalizeMarkdownImageUrls,
|
|
} from "./markdown-export";
|
|
|
|
describe("normalizeMarkdownImageUrls", () => {
|
|
test("converts /public/assets image links to absolute URLs", () => {
|
|
const markdown = "";
|
|
const result = normalizeMarkdownImageUrls(markdown, "https://example.com");
|
|
|
|
expect(result).toBe("");
|
|
});
|
|
|
|
test("keeps absolute http/https image links unchanged", () => {
|
|
const markdown = [
|
|
"",
|
|
"",
|
|
].join("\n");
|
|
|
|
const result = normalizeMarkdownImageUrls(markdown, "https://example.com");
|
|
|
|
expect(result).toBe(markdown);
|
|
});
|
|
|
|
test("keeps protocol-relative and data URI image links unchanged", () => {
|
|
const markdown = [
|
|
"",
|
|
"",
|
|
].join("\n");
|
|
|
|
const result = normalizeMarkdownImageUrls(markdown, "https://example.com");
|
|
|
|
expect(result).toBe(markdown);
|
|
});
|
|
|
|
test("does not change normal markdown links", () => {
|
|
const markdown = "[read more](/public/assets/posts/cover.png)";
|
|
const result = normalizeMarkdownImageUrls(markdown, "https://example.com");
|
|
|
|
expect(result).toBe(markdown);
|
|
});
|
|
});
|
|
|
|
describe("buildMarkdownExportFileName", () => {
|
|
test("prefers slug when slug is not empty", () => {
|
|
const result = buildMarkdownExportFileName({ slug: "hello-world", id: 42 });
|
|
|
|
expect(result).toBe("hello-world.md");
|
|
});
|
|
|
|
test("falls back to post-<id>.md when slug is empty", () => {
|
|
const result = buildMarkdownExportFileName({ slug: "", id: 42 });
|
|
|
|
expect(result).toBe("post-42.md");
|
|
});
|
|
});
|
|
|
|
describe("downloadMarkdownFile", () => {
|
|
test("does not throw when window/document are unavailable", () => {
|
|
const windowDescriptor = Object.getOwnPropertyDescriptor(globalThis, "window");
|
|
const documentDescriptor = Object.getOwnPropertyDescriptor(globalThis, "document");
|
|
|
|
Object.defineProperty(globalThis, "window", {
|
|
value: undefined,
|
|
configurable: true,
|
|
writable: true,
|
|
});
|
|
Object.defineProperty(globalThis, "document", {
|
|
value: undefined,
|
|
configurable: true,
|
|
writable: true,
|
|
});
|
|
|
|
try {
|
|
expect(() => downloadMarkdownFile("post.md", "# hello")).not.toThrow();
|
|
} finally {
|
|
if (windowDescriptor) {
|
|
Object.defineProperty(globalThis, "window", windowDescriptor);
|
|
} else {
|
|
Reflect.deleteProperty(globalThis, "window");
|
|
}
|
|
|
|
if (documentDescriptor) {
|
|
Object.defineProperty(globalThis, "document", documentDescriptor);
|
|
} else {
|
|
Reflect.deleteProperty(globalThis, "document");
|
|
}
|
|
}
|
|
});
|
|
|
|
test("creates object URL, clicks anchor, and revokes URL in browser flow", () => {
|
|
const windowDescriptor = Object.getOwnPropertyDescriptor(globalThis, "window");
|
|
const documentDescriptor = Object.getOwnPropertyDescriptor(globalThis, "document");
|
|
const urlDescriptor = Object.getOwnPropertyDescriptor(globalThis, "URL");
|
|
|
|
let createElementArg = "";
|
|
let appendedNode: unknown;
|
|
let clicked = false;
|
|
let removed = false;
|
|
let revokedArg = "";
|
|
const blobUrl = "blob:test-url";
|
|
|
|
const anchor = {
|
|
href: "",
|
|
download: "",
|
|
style: { display: "" },
|
|
click: () => {
|
|
clicked = true;
|
|
},
|
|
remove: () => {
|
|
removed = true;
|
|
},
|
|
};
|
|
|
|
Object.defineProperty(globalThis, "window", {
|
|
value: {},
|
|
configurable: true,
|
|
writable: true,
|
|
});
|
|
|
|
Object.defineProperty(globalThis, "document", {
|
|
value: {
|
|
createElement: (tag: string) => {
|
|
createElementArg = tag;
|
|
return anchor;
|
|
},
|
|
body: {
|
|
appendChild: (node: unknown) => {
|
|
appendedNode = node;
|
|
},
|
|
},
|
|
},
|
|
configurable: true,
|
|
writable: true,
|
|
});
|
|
|
|
Object.defineProperty(globalThis, "URL", {
|
|
value: {
|
|
createObjectURL: (_blob: Blob) => blobUrl,
|
|
revokeObjectURL: (url: string) => {
|
|
revokedArg = url;
|
|
},
|
|
},
|
|
configurable: true,
|
|
writable: true,
|
|
});
|
|
|
|
try {
|
|
downloadMarkdownFile("hello.md", "# hello");
|
|
|
|
expect(createElementArg).toBe("a");
|
|
expect(appendedNode).toBe(anchor);
|
|
expect(anchor.href).toBe(blobUrl);
|
|
expect(anchor.download).toBe("hello.md");
|
|
expect(anchor.style.display).toBe("none");
|
|
expect(clicked).toBe(true);
|
|
expect(removed).toBe(true);
|
|
expect(revokedArg).toBe(blobUrl);
|
|
} finally {
|
|
if (windowDescriptor) {
|
|
Object.defineProperty(globalThis, "window", windowDescriptor);
|
|
} else {
|
|
Reflect.deleteProperty(globalThis, "window");
|
|
}
|
|
|
|
if (documentDescriptor) {
|
|
Object.defineProperty(globalThis, "document", documentDescriptor);
|
|
} else {
|
|
Reflect.deleteProperty(globalThis, "document");
|
|
}
|
|
|
|
if (urlDescriptor) {
|
|
Object.defineProperty(globalThis, "URL", urlDescriptor);
|
|
} else {
|
|
Reflect.deleteProperty(globalThis, "URL");
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|