convertor/tests/converters/inkscape.test.ts
Your Name b5992a84e0 fix(inkscape): use xvfb-run for headless execution
- Inkscape requires X11 display connection, causing 'GtkStyleContext without display' error
- Solution: use xvfb-run to create virtual X11 display
- Fallback: try direct inkscape if xvfb-run fails
- Add xvfb package to Dockerfile
- Update inkscape tests for new xvfb-run behavior

Fixes: Gtk-ERROR: Can't create a GtkStyleContext without a display connection
2026-01-22 23:56:47 +08:00

117 lines
3.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { test, expect } from "bun:test";
import { convert } from "../../src/converters/inkscape";
import type { ExecFileException } from "node:child_process";
import { ExecFileFn } from "../../src/converters/types";
// Inkscape 使用 xvfb-run 包裝,需要自訂測試
// 模擬 xvfb-run 成功執行的情況
test("convert resolves when xvfb-run succeeds", async () => {
const mockExecFile: ExecFileFn = (
cmd: string,
_args: string[],
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
) => {
// xvfb-run 成功
if (cmd === "xvfb-run") {
callback(null, "Conversion complete", "");
}
};
const result = await convert("input.svg", "svg", "png", "output.png", undefined, mockExecFile);
expect(result).toBe("Done");
});
test("convert falls back to direct inkscape when xvfb-run fails", async () => {
let callCount = 0;
const mockExecFile: ExecFileFn = (
cmd: string,
_args: string[],
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
) => {
callCount++;
if (cmd === "xvfb-run") {
// xvfb-run 失敗
callback(new Error("xvfb-run not found"), "", "");
} else if (cmd === "inkscape") {
// 直接呼叫 inkscape 成功
callback(null, "Direct inkscape success", "");
}
};
const result = await convert("input.svg", "svg", "png", "output.png", undefined, mockExecFile);
expect(result).toBe("Done");
expect(callCount).toBe(2); // 應該呼叫兩次xvfb-run + inkscape
});
test("convert rejects when both xvfb-run and inkscape fail", async () => {
const mockExecFile: ExecFileFn = (
cmd: string,
_args: string[],
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
) => {
if (cmd === "xvfb-run") {
callback(new Error("xvfb-run failed"), "", "");
} else if (cmd === "inkscape") {
callback(new Error("inkscape failed"), "", "");
}
};
await expect(
convert("input.svg", "svg", "png", "output.png", undefined, mockExecFile),
).rejects.toMatch(/error:/);
});
test("convert logs stdout when present", async () => {
const originalConsoleLog = console.log;
let loggedMessage = "";
console.log = (msg: string) => {
if (msg.startsWith("stdout:")) {
loggedMessage = msg;
}
};
const mockExecFile: ExecFileFn = (
cmd: string,
_args: string[],
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
) => {
if (cmd === "xvfb-run") {
callback(null, "Fake stdout", "");
}
};
await convert("input.svg", "svg", "png", "output.png", undefined, mockExecFile);
console.log = originalConsoleLog;
expect(loggedMessage).toBe("stdout: Fake stdout");
});
test("convert logs stderr when present (non-fatal warning)", async () => {
const originalConsoleLog = console.log;
let loggedMessage = "";
console.log = (msg: string) => {
if (msg.startsWith("stderr:")) {
loggedMessage = msg;
}
};
const mockExecFile: ExecFileFn = (
cmd: string,
_args: string[],
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
) => {
if (cmd === "xvfb-run") {
// Inkscape 經常輸出警告到 stderr但轉換仍成功
callback(null, "", "Gtk-WARNING: some warning");
}
};
await convert("input.svg", "svg", "png", "output.png", undefined, mockExecFile);
console.log = originalConsoleLog;
expect(loggedMessage).toBe("stderr: Gtk-WARNING: some warning");
});
test.skip("dummy - required to trigger test detection", () => {});