Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions bunfig.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
[test]
preload = ["./tests/preload.ts"]
coverage = true
coveragePathIgnorePatterns = [
"tests/converters/helpers/commonTests.ts"
Expand Down
24 changes: 22 additions & 2 deletions tests/converters/assimp.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,27 @@
import { test } from "bun:test";
import { expect, test } from "bun:test";
import { convert } from "../../src/converters/assimp";
import { runCommonTests } from "./helpers/commonTests";
import { createMockExecFile } from "./helpers/converters";

runCommonTests(convert);

test.skip("dummy - required to trigger test detection", () => {});
test("invokes assimp export with the target format id", async () => {
const { execFile, calls } = createMockExecFile();

await convert("in/scene.obj", "obj", "stl", "out/scene.stl", undefined, execFile);

expect(calls).toEqual([
{ cmd: "assimp", args: ["export", "in/scene.obj", "out/scene.stl", "-fstl"] },
]);
});

test("passes format ids that are not file extensions through unchanged", async () => {
// e.g. glb2 is written to a .glb file, but assimp still needs -fglb2
const { execFile, calls } = createMockExecFile();

await convert("in/scene.obj", "obj", "glb2", "out/scene.glb", undefined, execFile);

expect(calls).toEqual([
{ cmd: "assimp", args: ["export", "in/scene.obj", "out/scene.glb", "-fglb2"] },
]);
});
36 changes: 34 additions & 2 deletions tests/converters/calibre.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,39 @@
import { test } from "bun:test";
import { expect, test } from "bun:test";
import { convert } from "../../src/converters/calibre";
import { runCommonTests } from "./helpers/commonTests";
import { createMockExecFile } from "./helpers/converters";

runCommonTests(convert);

test.skip("dummy - required to trigger test detection", () => {});
test("invokes ebook-convert with input and target path", async () => {
const { execFile, calls } = createMockExecFile();

await convert("in/book.epub", "epub", "mobi", "out/book.mobi", undefined, execFile);

expect(calls).toEqual([{ cmd: "ebook-convert", args: ["in/book.epub", "out/book.mobi"] }]);
});

test.each([
// the extension is harmless, so only the file type triggers the rejection
["recipe file type", "in/news.txt", "recipe"],
["downloaded_recipe file type", "in/news.txt", "downloaded_recipe"],
["upper-case file type", "in/news.txt", "RECIPE"],
["recipe extension with another file type", "in/news.recipe", "txt"],
["upper-case downloaded_recipe extension", "in/news.DOWNLOADED_RECIPE", "txt"],
])("rejects recipe input (%s) without invoking ebook-convert", async (_, filePath, fileType) => {
const { execFile, calls } = createMockExecFile();

await expect(
convert(filePath, fileType, "epub", "out/news.epub", undefined, execFile),
).rejects.toThrow("Recipe files are not supported");
expect(calls).toHaveLength(0);
});

test("accepts files without an extension", async () => {
const { execFile, calls } = createMockExecFile();

await expect(
convert("in/README", "txt", "epub", "out/README.epub", undefined, execFile),
).resolves.toBe("Done");
expect(calls).toHaveLength(1);
});
94 changes: 69 additions & 25 deletions tests/converters/dasel.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,60 @@ import fs from "fs";
import { beforeEach, afterEach, expect, test, describe } from "bun:test";
import { buildDaselArgs, convert } from "../../src/converters/dasel";
import type { ExecFileFn } from "../../src/converters/types";
import { captureConsole } from "./helpers/converters";

const originalWriteFile = fs.writeFile;

describe("buildDaselArgs", () => {
test("should build dasel v3 arguments", () => {
expect(buildDaselArgs("input.yaml", "yaml", "json")).toEqual([
"--var",
"data=yaml:file:input.yaml",
"--out",
"json",
"$data",
]);
});

test.each([
["toml", "yaml"],
["json", "toml"],
["xml", "json"],
["csv", "yaml"],
])("should read %s and write %s", (fileType, convertTo) => {
expect(buildDaselArgs(`in/data.${fileType}`, fileType, convertTo)).toEqual([
"--var",
`data=${fileType}:file:in/data.${fileType}`,
"--out",
convertTo,
"$data",
]);
});
});

describe("convert", () => {
let mockExecFile: ExecFileFn;
let writes: { path: unknown; data: unknown }[];
let output: ReturnType<typeof captureConsole>;

beforeEach(() => {
writes = [];
output = captureConsole();
// mock fs.writeFile
// @ts-expect-error: property __promisify__ is missing
fs.writeFile = (path, data, cb) => cb(null);
fs.writeFile = (path, data, cb) => {
writes.push({ path, data });
// @ts-expect-error: could not be callable with null
cb(null);
};
// mock execFile
mockExecFile = (cmd, args, callback) => callback(null, "output-data", "");
});

afterEach(() => {
// reset fs.writeFile
fs.writeFile = originalWriteFile;
});

test("should build dasel v3 arguments", () => {
expect(buildDaselArgs("input.yaml", "yaml", "json")).toEqual([
"--var",
"data=yaml:file:input.yaml",
"--out",
"json",
"$data",
]);
output.restore();
});

test("should call dasel with correct arguments and write output", async () => {
Expand All @@ -38,16 +65,6 @@ describe("convert", () => {
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",
Expand All @@ -59,27 +76,54 @@ describe("convert", () => {

expect(calledArgs[0]).toBe("dasel");
expect(calledArgs[1]).toEqual(["--var", "data=yaml:file:input.yaml", "--out", "json", "$data"]);
expect(writeFileCalled).toBe(true);
expect(writes).toEqual([{ path: "output.json", data: "output-data" }]);
expect(result).toBe("Done");
});

test("should close dasel stdin so v3 does not wait for input", async () => {
let stdinEnded = false;
mockExecFile = (cmd, args, callback) => {
callback(null, "output-data", "");
return { stdin: { end: () => (stdinEnded = true) } } as ReturnType<ExecFileFn>;
return { stdin: { end: () => (stdinEnded = true) } } as unknown as ReturnType<ExecFileFn>;
};

await convert("input.yaml", "yaml", "json", "output.json", undefined, mockExecFile);

expect(stdinEnded).toBe(true);
});

test("should reject if execFile returns an error", async () => {
test("should not fail when execFile returns no child process", async () => {
mockExecFile = (cmd, args, callback) => {
callback(null, "output-data", "");
return undefined;
};

await expect(
convert("input.yaml", "yaml", "json", "output.json", undefined, mockExecFile),
).resolves.toBe("Done");
});

test("should log stderr and still write the output", async () => {
mockExecFile = (cmd, args, callback) => callback(null, "output-data", "deprecation warning");

await convert("input.yaml", "yaml", "json", "output.json", undefined, mockExecFile);

expect(output.errors).toEqual(["stderr: deprecation warning"]);
expect(writes).toHaveLength(1);
});

test("should not log stdout, since it is the converted document", async () => {
await convert("input.yaml", "yaml", "json", "output.json", undefined, mockExecFile);

expect(output.logs).toEqual([]);
});

test("should reject and not write anything 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/);
expect(writes).toEqual([]);
});

test("should reject if writeFile fails", async () => {
Expand All @@ -89,6 +133,6 @@ describe("convert", () => {
convert("input.yaml", "yaml", "json", "output.json", undefined, (cmd, args, cb) =>
cb(null, "output-data", ""),
),
).rejects.toMatch(/Failed to write output/);
).rejects.toMatch(/Failed to write output: Error: write fail/);
});
});
110 changes: 27 additions & 83 deletions tests/converters/dvisvgm.test.ts
Original file line number Diff line number Diff line change
@@ -1,91 +1,35 @@
import type { ExecFileException } from "node:child_process";
import { beforeEach, expect, test } from "bun:test";
import { expect, test } from "bun:test";
import { convert } from "../../src/converters/dvisvgm";
import { ExecFileFn } from "../../src/converters/types";
import { runCommonTests } from "./helpers/commonTests";

let calls: string[][] = [];

beforeEach(() => {
calls = [];
});
import { createMockExecFile } from "./helpers/converters";

runCommonTests(convert);

test("convert respects eps filetype", async () => {
const originalConsoleLog = console.log;

let loggedMessage = "";
console.log = (msg) => {
loggedMessage = msg;
};

const mockExecFile: ExecFileFn = (
_cmd: string,
_args: string[],
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
) => {
calls.push(_args);
callback(null, "Fake stdout", "");
};

const result = await convert("input.eps", "eps", "stl", "output.stl", undefined, mockExecFile);

console.log = originalConsoleLog;

expect(result).toBe("Done");
expect(calls[0]).toEqual(expect.arrayContaining(["--eps", "input.eps", "output.stl"]));
expect(loggedMessage).toBe("stdout: Fake stdout");
});

test("convert respects pdf filetype", async () => {
const originalConsoleLog = console.log;

let loggedMessage = "";
console.log = (msg) => {
loggedMessage = msg;
};

const mockExecFile: ExecFileFn = (
_cmd: string,
_args: string[],
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
) => {
calls.push(_args);
callback(null, "Fake stdout", "");
};

const result = await convert("input.pdf", "pdf", "stl", "output.stl", undefined, mockExecFile);

console.log = originalConsoleLog;

expect(result).toBe("Done");
expect(calls[0]).toEqual(expect.arrayContaining(["--pdf", "input.pdf", "output.stl"]));
expect(loggedMessage).toBe("stdout: Fake stdout");
});

test("convert respects svgz conversion target type", async () => {
const originalConsoleLog = console.log;

let loggedMessage = "";
console.log = (msg) => {
loggedMessage = msg;
};

const mockExecFile: ExecFileFn = (
_cmd: string,
_args: string[],
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
) => {
calls.push(_args);
callback(null, "Fake stdout", "");
};

const result = await convert("input.obj", "eps", "svgz", "output.svgz", undefined, mockExecFile);

console.log = originalConsoleLog;
test.each([
{ fileType: "dvi", convertTo: "svg", flags: [] },
{ fileType: "xdv", convertTo: "svg", flags: [] },
{ fileType: "eps", convertTo: "svg", flags: ["--eps"] },
{ fileType: "pdf", convertTo: "svg", flags: ["--pdf"] },
{ fileType: "dvi", convertTo: "svgz", flags: ["-z"] },
{ fileType: "eps", convertTo: "svgz", flags: ["--eps", "-z"] },
{ fileType: "pdf", convertTo: "svgz", flags: ["--pdf", "-z"] },
])("converts $fileType to $convertTo", async ({ fileType, convertTo, flags }) => {
const { execFile, calls } = createMockExecFile();

const result = await convert(
`in/figure.${fileType}`,
fileType,
convertTo,
`out/figure.${convertTo}`,
undefined,
execFile,
);

expect(result).toBe("Done");
expect(calls[0]).toEqual(expect.arrayContaining(["-z", "input.obj", "output.svgz"]));
expect(loggedMessage).toBe("stdout: Fake stdout");
expect(calls).toEqual([
{
cmd: "dvisvgm",
args: [...flags, `in/figure.${fileType}`, "-o", `out/figure.${convertTo}`],
},
]);
});
Loading
Loading