Merge pull request #462 from Laertes87/AddMoreUnitTests
test: add more unit tests
This commit is contained in:
commit
8f898be20e
4 changed files with 357 additions and 0 deletions
72
tests/converters/dasel.test.ts
Normal file
72
tests/converters/dasel.test.ts
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
import fs from "fs";
|
||||||
|
import { beforeEach, afterEach, expect, test, describe } from "bun:test";
|
||||||
|
import { convert } from "../../src/converters/dasel";
|
||||||
|
import type { ExecFileFn } from "../../src/converters/types";
|
||||||
|
|
||||||
|
const originalWriteFile = fs.writeFile;
|
||||||
|
|
||||||
|
describe("convert", () => {
|
||||||
|
let mockExecFile: ExecFileFn;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// mock fs.writeFile
|
||||||
|
// @ts-expect-error: property __promisify__ is missing
|
||||||
|
fs.writeFile = (path, data, cb) => cb(null);
|
||||||
|
// mock execFile
|
||||||
|
mockExecFile = (cmd, args, callback) => callback(null, "output-data", "");
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
// reset fs.writeFile
|
||||||
|
fs.writeFile = originalWriteFile;
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should call dasel with correct arguments and write output", async () => {
|
||||||
|
let calledArgs: Parameters<ExecFileFn> = ["", [], () => {}];
|
||||||
|
mockExecFile = (cmd, args, callback) => {
|
||||||
|
calledArgs = [cmd, args, callback];
|
||||||
|
callback(null, "output-data", "");
|
||||||
|
};
|
||||||
|
|
||||||
|
let writeFileCalled = false;
|
||||||
|
// @ts-expect-error: property __promisify__ is missing
|
||||||
|
fs.writeFile = (path, data, cb) => {
|
||||||
|
writeFileCalled = true;
|
||||||
|
expect(path).toBe("output.json");
|
||||||
|
expect(data).toBe("output-data");
|
||||||
|
// @ts-expect-error: could not be callable with null
|
||||||
|
cb(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await convert(
|
||||||
|
"input.yaml",
|
||||||
|
"yaml",
|
||||||
|
"json",
|
||||||
|
"output.json",
|
||||||
|
undefined,
|
||||||
|
mockExecFile,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(calledArgs[0]).toBe("dasel");
|
||||||
|
expect(calledArgs[1]).toEqual(["--file", "input.yaml", "--read", "yaml", "--write", "json"]);
|
||||||
|
expect(writeFileCalled).toBe(true);
|
||||||
|
expect(result).toBe("Done");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should reject if execFile returns an error", async () => {
|
||||||
|
mockExecFile = (cmd, args, callback) => callback(new Error("fail"), "", "");
|
||||||
|
await expect(
|
||||||
|
convert("input.yaml", "yaml", "json", "output.json", undefined, mockExecFile),
|
||||||
|
).rejects.toMatch(/error: Error: fail/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should reject if writeFile fails", async () => {
|
||||||
|
// @ts-expect-error: property __promisify__ is missing
|
||||||
|
fs.writeFile = (path, data, cb) => cb(new Error("write fail"));
|
||||||
|
await expect(
|
||||||
|
convert("input.yaml", "yaml", "json", "output.json", undefined, (cmd, args, cb) =>
|
||||||
|
cb(null, "output-data", ""),
|
||||||
|
),
|
||||||
|
).rejects.toMatch(/Failed to write output/);
|
||||||
|
});
|
||||||
|
});
|
||||||
161
tests/converters/libreoffice.test.ts
Normal file
161
tests/converters/libreoffice.test.ts
Normal file
|
|
@ -0,0 +1,161 @@
|
||||||
|
import { afterEach, beforeEach, expect, test } from "bun:test";
|
||||||
|
import { convert } from "../../src/converters/libreoffice";
|
||||||
|
import type { ExecFileFn } from "../../src/converters/types";
|
||||||
|
|
||||||
|
function requireDefined<T>(value: T, msg: string): NonNullable<T> {
|
||||||
|
if (value === undefined || value === null) throw new Error(msg);
|
||||||
|
return value as NonNullable<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- capture/inspect execFile calls -----------------------------------------
|
||||||
|
type Call = { cmd: string; args: string[] };
|
||||||
|
let calls: Call[] = [];
|
||||||
|
|
||||||
|
let behavior:
|
||||||
|
| { kind: "success"; stdout?: string; stderr?: string }
|
||||||
|
| { kind: "error"; message?: string; stderr?: string } = { kind: "success" };
|
||||||
|
|
||||||
|
const mockExecFile: ExecFileFn = (cmd, args, cb) => {
|
||||||
|
calls.push({ cmd, args });
|
||||||
|
if (behavior.kind === "error") {
|
||||||
|
cb(new Error(behavior.message ?? "mock failure"), "", behavior.stderr ?? "");
|
||||||
|
} else {
|
||||||
|
cb(null, behavior.stdout ?? "ok", behavior.stderr ?? "");
|
||||||
|
}
|
||||||
|
// We don't return a real ChildProcess in tests.
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- capture console output (no terminal noise) ------------------------------
|
||||||
|
let logs: string[] = [];
|
||||||
|
let errors: string[] = [];
|
||||||
|
|
||||||
|
const originalLog = console.log;
|
||||||
|
const originalError = console.error;
|
||||||
|
|
||||||
|
// Use Console["log"] for typing; avoids explicit `any`
|
||||||
|
const makeSink =
|
||||||
|
(sink: string[]): Console["log"] =>
|
||||||
|
(...data) => {
|
||||||
|
sink.push(data.map(String).join(" "));
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
calls = [];
|
||||||
|
behavior = { kind: "success" };
|
||||||
|
|
||||||
|
logs = [];
|
||||||
|
errors = [];
|
||||||
|
console.log = makeSink(logs);
|
||||||
|
console.error = makeSink(errors);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
console.log = originalLog;
|
||||||
|
console.error = originalError;
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- core behavior -----------------------------------------------------------
|
||||||
|
test("invokes soffice with --headless and outdir derived from targetPath", async () => {
|
||||||
|
await convert("in.docx", "docx", "odt", "out/out.odt", undefined, mockExecFile);
|
||||||
|
|
||||||
|
const { cmd, args } = requireDefined(calls[0], "Expected at least one execFile call");
|
||||||
|
expect(cmd).toBe("soffice");
|
||||||
|
expect(args).toEqual([
|
||||||
|
"--headless",
|
||||||
|
`--infilter="MS Word 2007 XML"`,
|
||||||
|
"--convert-to",
|
||||||
|
"odt:writer8",
|
||||||
|
"--outdir",
|
||||||
|
"out",
|
||||||
|
"in.docx",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("uses only outFilter when input has no filter (e.g., pdf -> txt)", async () => {
|
||||||
|
await convert("in.pdf", "pdf", "txt", "out/out.txt", undefined, mockExecFile);
|
||||||
|
|
||||||
|
const { args } = requireDefined(calls[0], "Expected at least one execFile call");
|
||||||
|
|
||||||
|
expect(args).not.toContainEqual(expect.stringMatching(/^--infilter=/));
|
||||||
|
expect(args).toEqual(["--headless", "--convert-to", "txt", "--outdir", "out", "in.pdf"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("uses only infilter when convertTo has no out filter (e.g., docx -> pdf)", async () => {
|
||||||
|
await convert("in.docx", "docx", "pdf", "out/out.pdf", undefined, mockExecFile);
|
||||||
|
|
||||||
|
const { args } = requireDefined(calls[0], "Expected at least one execFile call");
|
||||||
|
|
||||||
|
// If docx has an infilter, it should be present
|
||||||
|
expect(args).toEqual(["--headless", "--convert-to", "pdf", "--outdir", "out", "in.docx"]);
|
||||||
|
|
||||||
|
const i = args.indexOf("--convert-to");
|
||||||
|
expect(i).toBeGreaterThanOrEqual(0);
|
||||||
|
expect(args[i + 1]).toBe("pdf");
|
||||||
|
expect(args.slice(-2)).toEqual(["out", "in.docx"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("strips leading './' from outdir", async () => {
|
||||||
|
await convert("in.txt", "txt", "docx", "./out/out.docx", undefined, mockExecFile);
|
||||||
|
|
||||||
|
const { args } = requireDefined(calls[0], "Expected at least one execFile call");
|
||||||
|
|
||||||
|
const outDirIdx = args.indexOf("--outdir");
|
||||||
|
expect(outDirIdx).toBeGreaterThanOrEqual(0);
|
||||||
|
expect(args[outDirIdx + 1]).toBe("out");
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- promise settlement ------------------------------------------------------
|
||||||
|
test("resolves with 'Done' when execFile succeeds", async () => {
|
||||||
|
behavior = { kind: "success", stdout: "fine", stderr: "" };
|
||||||
|
await expect(
|
||||||
|
convert("in.txt", "txt", "docx", "out/out.docx", undefined, mockExecFile),
|
||||||
|
).resolves.toBe("Done");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("rejects when execFile returns an error", async () => {
|
||||||
|
behavior = { kind: "error", message: "convert failed", stderr: "oops" };
|
||||||
|
await expect(
|
||||||
|
convert("in.txt", "txt", "docx", "out/out.docx", undefined, mockExecFile),
|
||||||
|
).rejects.toMatch(/error: Error: convert failed/);
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- logging behavior --------------------------------------------------------
|
||||||
|
test("logs stdout when present", async () => {
|
||||||
|
behavior = { kind: "success", stdout: "hello", stderr: "" };
|
||||||
|
|
||||||
|
await convert("in.txt", "txt", "docx", "out/out.docx", undefined, mockExecFile);
|
||||||
|
|
||||||
|
expect(logs).toContain("stdout: hello");
|
||||||
|
expect(errors).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("logs stderr when present", async () => {
|
||||||
|
behavior = { kind: "success", stdout: "", stderr: "uh-oh" };
|
||||||
|
|
||||||
|
await convert("in.txt", "txt", "docx", "out/out.docx", undefined, mockExecFile);
|
||||||
|
|
||||||
|
expect(errors).toContain("stderr: uh-oh");
|
||||||
|
// When stdout is empty, no stdout log
|
||||||
|
expect(logs.find((l) => l.startsWith("stdout:"))).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("logs both stdout and stderr when both are present", async () => {
|
||||||
|
behavior = { kind: "success", stdout: "alpha", stderr: "beta" };
|
||||||
|
|
||||||
|
await convert("in.txt", "txt", "docx", "out/out.docx", undefined, mockExecFile);
|
||||||
|
|
||||||
|
expect(logs).toContain("stdout: alpha");
|
||||||
|
expect(errors).toContain("stderr: beta");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("logs stderr on exec error as well", async () => {
|
||||||
|
behavior = { kind: "error", message: "boom", stderr: "EPIPE" };
|
||||||
|
|
||||||
|
expect(convert("in.txt", "txt", "docx", "out/out.docx", undefined, mockExecFile)).rejects.toMatch(
|
||||||
|
/error: Error: boom/,
|
||||||
|
);
|
||||||
|
|
||||||
|
// The callback still provided stderr; your implementation logs it before settling
|
||||||
|
expect(errors).toContain("stderr: EPIPE");
|
||||||
|
});
|
||||||
66
tests/converters/pandoc.test.ts
Normal file
66
tests/converters/pandoc.test.ts
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
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 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/);
|
||||||
|
});
|
||||||
|
});
|
||||||
58
tests/converters/vtracer.test.ts
Normal file
58
tests/converters/vtracer.test.ts
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
import { beforeEach, expect, test, describe } from "bun:test";
|
||||||
|
import { convert } from "../../src/converters/vtracer";
|
||||||
|
import type { ExecFileFn } from "../../src/converters/types";
|
||||||
|
|
||||||
|
describe("convert", () => {
|
||||||
|
let mockExecFile: ExecFileFn;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mockExecFile = (cmd, args, callback) => callback(null, "output-data", "");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should call vtracer with correct arguments (minimal)", async () => {
|
||||||
|
let calledArgs: Parameters<ExecFileFn> = ["", [], () => {}];
|
||||||
|
mockExecFile = (cmd, args, callback) => {
|
||||||
|
calledArgs = [cmd, args, callback];
|
||||||
|
callback(null, "output-data", "");
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await convert("input.png", "png", "svg", "output.svg", undefined, mockExecFile);
|
||||||
|
|
||||||
|
expect(calledArgs[0]).toBe("vtracer");
|
||||||
|
expect(calledArgs[1]).toEqual(["--input", "input.png", "--output", "output.svg"]);
|
||||||
|
expect(result).toBe("Done");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should add options as arguments", async () => {
|
||||||
|
let calledArgs: Parameters<ExecFileFn> = ["", [], () => {}];
|
||||||
|
mockExecFile = (cmd, args, callback) => {
|
||||||
|
calledArgs = [cmd, args, callback];
|
||||||
|
callback(null, "output-data", "");
|
||||||
|
};
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
colormode: "color",
|
||||||
|
hierarchical: "true",
|
||||||
|
filter_speckle: 5,
|
||||||
|
path_precision: 0.8,
|
||||||
|
};
|
||||||
|
|
||||||
|
await convert("input.png", "png", "svg", "output.svg", options, mockExecFile);
|
||||||
|
|
||||||
|
expect(calledArgs[1]).toContain("--colormode");
|
||||||
|
expect(calledArgs[1]).toContain("color");
|
||||||
|
expect(calledArgs[1]).toContain("--hierarchical");
|
||||||
|
expect(calledArgs[1]).toContain("true");
|
||||||
|
expect(calledArgs[1]).toContain("--filter_speckle");
|
||||||
|
expect(calledArgs[1]).toContain("5");
|
||||||
|
expect(calledArgs[1]).toContain("--path_precision");
|
||||||
|
expect(calledArgs[1]).toContain("0.8");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should reject if execFile returns an error", async () => {
|
||||||
|
mockExecFile = (cmd, args, callback) => callback(new Error("fail"), "", "stderr output");
|
||||||
|
await expect(
|
||||||
|
convert("input.png", "png", "svg", "output.svg", undefined, mockExecFile),
|
||||||
|
).rejects.toMatch(/error: Error: fail\nstderr: stderr output/);
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Add table
Add a link
Reference in a new issue