convertor/tests/converters/pandoc.test.ts
Ryo 143ade3d36 fix: add CJK font support for PDF conversion
When converting documents containing Chinese, Japanese, or Korean
characters to PDF via pandoc/xelatex, the output was blank or missing
glyphs because no CJK-capable font was installed or configured.

Changes:
- Dockerfile: Add fonts-noto-cjk and texlive-lang-chinese packages
- pandoc.ts: Pass -V CJKmainfont=Noto Sans CJK SC to pandoc when
  converting to pdf/latex, so xelatex uses a CJK-capable font
- pandoc.test.ts: Add 3 tests verifying CJK font argument is present
  for pdf/latex output and absent for other formats

Fixes #547
2026-07-02 09:55:01 +08:00

107 lines
3.6 KiB
TypeScript

import { beforeEach, expect, test, describe } from "bun:test";
import { convert } from "../../src/converters/pandoc";
import type { ExecFileFn } from "../../src/converters/types";
describe("convert", () => {
let mockExecFile: ExecFileFn;
beforeEach(() => {
mockExecFile = (cmd, args, callback) => callback(null, "output-data", "");
});
test("should call pandoc with correct arguments (normal)", async () => {
let calledArgs: Parameters<ExecFileFn> = ["", [], () => {}];
mockExecFile = (cmd, args, callback) => {
calledArgs = [cmd, args, callback];
callback(null, "output-data", "");
};
const result = await convert(
"input.md",
"markdown",
"html",
"output.html",
undefined,
mockExecFile,
);
expect(calledArgs[0]).toBe("pandoc");
expect(calledArgs[1]).toEqual([
"input.md",
"-f",
"markdown",
"-t",
"html",
"-o",
"output.html",
]);
expect(result).toBe("Done");
});
test("should add xelatex argument for pdf/latex", async () => {
let calledArgs: Parameters<ExecFileFn> = ["", [], () => {}];
mockExecFile = (cmd, args, callback) => {
calledArgs = [cmd, args, callback];
callback(null, "output-data", "");
};
await convert("input.md", "markdown", "pdf", "output.pdf", undefined, mockExecFile);
expect(calledArgs[1][0]).toBe("--pdf-engine=xelatex");
expect(calledArgs[1]).toContain("input.md");
expect(calledArgs[1]).toContain("-f");
expect(calledArgs[1]).toContain("markdown");
expect(calledArgs[1]).toContain("-t");
expect(calledArgs[1]).toContain("pdf");
expect(calledArgs[1]).toContain("-o");
expect(calledArgs[1]).toContain("output.pdf");
});
test("should add CJK mainfont argument for pdf output", async () => {
let calledArgs: Parameters<ExecFileFn> = ["", [], () => {}];
mockExecFile = (cmd, args, callback) => {
calledArgs = [cmd, args, callback];
callback(null, "output-data", "");
};
await convert("input.md", "markdown", "pdf", "output.pdf", undefined, mockExecFile);
const cjkFontIndex = calledArgs[1].indexOf("-V");
expect(cjkFontIndex).toBeGreaterThan(-1);
expect(calledArgs[1][cjkFontIndex + 1]).toBe("CJKmainfont=Noto Sans CJK SC");
});
test("should add CJK mainfont argument for latex output", async () => {
let calledArgs: Parameters<ExecFileFn> = ["", [], () => {}];
mockExecFile = (cmd, args, callback) => {
calledArgs = [cmd, args, callback];
callback(null, "output-data", "");
};
await convert("input.md", "markdown", "latex", "output.tex", undefined, mockExecFile);
expect(calledArgs[1][0]).toBe("--pdf-engine=xelatex");
const cjkFontIndex = calledArgs[1].indexOf("-V");
expect(cjkFontIndex).toBeGreaterThan(-1);
expect(calledArgs[1][cjkFontIndex + 1]).toBe("CJKmainfont=Noto Sans CJK SC");
});
test("should not add CJK mainfont for non-pdf/latex output", async () => {
let calledArgs: Parameters<ExecFileFn> = ["", [], () => {}];
mockExecFile = (cmd, args, callback) => {
calledArgs = [cmd, args, callback];
callback(null, "output-data", "");
};
await convert("input.md", "markdown", "html", "output.html", undefined, mockExecFile);
expect(calledArgs[1].some((arg) => arg.startsWith("CJKmainfont"))).toBe(false);
});
test("should reject if execFile returns an error", async () => {
mockExecFile = (cmd, args, callback) => callback(new Error("fail"), "", "");
await expect(
convert("input.md", "markdown", "html", "output.html", undefined, mockExecFile),
).rejects.toMatch(/error: Error: fail/);
});
});