Skip to content

Commit 60fb92f

Browse files
committed
Add cli unit tests
1 parent cf7951d commit 60fb92f

File tree

2 files changed

+137
-4
lines changed

2 files changed

+137
-4
lines changed

lib/src/cli.test.ts

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
// cli.test.ts
2+
import { describe, it, expect, vi, beforeEach } from "vitest";
3+
import fs from "node:fs";
4+
import path from "node:path";
5+
import * as child_process from "node:child_process";
6+
7+
vi.mock("node:fs");
8+
vi.mock("node:child_process");
9+
vi.mock("./index", () => ({
10+
resolveConflicts: vi.fn(),
11+
}));
12+
vi.mock("./normalizer", () => ({
13+
DEFAULT_CONFIG: { defaultStrategy: "merge" },
14+
}));
15+
16+
// Import after mocks
17+
import type { Config } from "./types";
18+
19+
// Re-import CLI helpers (not the top-level IIFE)
20+
import * as cli from "./cli";
21+
22+
describe("cli helpers", () => {
23+
beforeEach(() => {
24+
vi.clearAllMocks();
25+
});
26+
27+
describe("findGitRoot", () => {
28+
it("returns git root from execSync", () => {
29+
vi.spyOn(child_process, "execSync").mockReturnValue("/git/root\n" as any);
30+
const root = (cli as any).findGitRoot();
31+
expect(root).toBe("/git/root");
32+
});
33+
34+
it("falls back to process.cwd() on error", () => {
35+
vi.spyOn(child_process, "execSync").mockImplementation(() => {
36+
throw new Error("no git");
37+
});
38+
const root = (cli as any).findGitRoot();
39+
expect(root).toBe(process.cwd());
40+
});
41+
});
42+
43+
describe("parseArgs", () => {
44+
it("parses include/exclude/matcher/debug/strict-arrays/sidecar", () => {
45+
const argv = [
46+
"node",
47+
"cli",
48+
"--include",
49+
"a.json,b.json",
50+
"--exclude",
51+
"c.json",
52+
"--matcher",
53+
"micromatch",
54+
"--debug",
55+
"--strict-arrays",
56+
"--sidecar",
57+
];
58+
const result = (cli as any).parseArgs(argv);
59+
expect(result.overrides).toEqual({
60+
include: ["a.json", "b.json"],
61+
exclude: ["c.json"],
62+
matcher: "micromatch",
63+
debug: true,
64+
strictArrays: true,
65+
writeConflictSidecar: true,
66+
});
67+
expect(result.init).toBe(false);
68+
});
69+
70+
it("sets init flag", () => {
71+
const result = (cli as any).parseArgs(["node", "cli", "--init"]);
72+
expect(result.init).toBe(true);
73+
});
74+
75+
it("warns on unknown option", () => {
76+
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
77+
(cli as any).parseArgs(["node", "cli", "--unknown"]);
78+
expect(warn).toHaveBeenCalledWith("[git-json-resolver] Unknown option: --unknown");
79+
});
80+
});
81+
82+
describe("initConfig", () => {
83+
const tmpDir = "/tmp/test-cli";
84+
const configPath = path.join(tmpDir, "git-json-resolver.config.js");
85+
86+
it("writes starter config if none exists", () => {
87+
(fs.existsSync as any).mockReturnValue(false);
88+
const writeFileSync = vi.spyOn(fs, "writeFileSync").mockImplementation(() => {});
89+
const log = vi.spyOn(console, "log").mockImplementation(() => {});
90+
91+
(cli as any).initConfig(tmpDir);
92+
93+
expect(writeFileSync).toHaveBeenCalled();
94+
expect(log).toHaveBeenCalledWith(
95+
`[git-json-resolver] Created starter config at ${configPath}`,
96+
);
97+
});
98+
99+
it("exits if config already exists", () => {
100+
(fs.existsSync as any).mockReturnValue(true);
101+
const exit = vi.spyOn(process, "exit").mockImplementation(() => {
102+
throw new Error("exit");
103+
});
104+
const error = vi.spyOn(console, "error").mockImplementation(() => {});
105+
106+
expect(() => (cli as any).initConfig(tmpDir)).toThrow("exit");
107+
expect(error).toHaveBeenCalledWith(
108+
`[git-json-resolver] Config file already exists: ${configPath}`,
109+
);
110+
expect(exit).toHaveBeenCalledWith(1);
111+
});
112+
});
113+
114+
describe("loadConfigFile", () => {
115+
it("returns {} if no config found", async () => {
116+
(fs.existsSync as any).mockReturnValue(false);
117+
const result = await (cli as any).loadConfigFile();
118+
expect(result).toEqual({});
119+
});
120+
121+
it.skip("loads config from js file", async () => {
122+
const fakeConfig: Partial<Config> = { debug: true };
123+
(fs.existsSync as any).mockReturnValue(true);
124+
vi.doMock("/git/root/git-json-resolver.config.js", () => ({
125+
default: fakeConfig,
126+
}));
127+
vi.spyOn(child_process, "execSync").mockReturnValue("/git/root\n" as any);
128+
129+
const mod = await (cli as any).loadConfigFile();
130+
expect(mod).toEqual(fakeConfig);
131+
});
132+
});
133+
});

lib/src/cli.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const CONFIG_FILENAME = "git-json-resolver.config.js";
1212
/**
1313
* Find Git root directory
1414
*/
15-
const findGitRoot = (): string => {
15+
export const findGitRoot = (): string => {
1616
try {
1717
return execSync("git rev-parse --show-toplevel", { encoding: "utf8" }).trim();
1818
} catch {
@@ -23,7 +23,7 @@ const findGitRoot = (): string => {
2323
/**
2424
* Load configuration file (js/ts) from current dir or Git root.
2525
*/
26-
const loadConfigFile = async (): Promise<Partial<Config>> => {
26+
export const loadConfigFile = async (): Promise<Partial<Config>> => {
2727
const searchDirs = [process.cwd(), findGitRoot()];
2828
const configNames = [CONFIG_FILENAME, "git-json-resolver.config.ts"];
2929

@@ -42,7 +42,7 @@ const loadConfigFile = async (): Promise<Partial<Config>> => {
4242
/**
4343
* Write a starter config file
4444
*/
45-
const initConfig = (targetDir: string) => {
45+
export const initConfig = (targetDir: string) => {
4646
const targetPath = path.join(targetDir, CONFIG_FILENAME);
4747
if (fs.existsSync(targetPath)) {
4848
console.error(`[git-json-resolver] Config file already exists: ${targetPath}`);
@@ -63,7 +63,7 @@ module.exports = ${JSON.stringify(DEFAULT_CONFIG, null, 2)};
6363
/**
6464
* CLI argument parser (minimal, no external deps).
6565
*/
66-
const parseArgs = (argv: string[]): { overrides: Partial<Config>; init?: boolean } => {
66+
export const parseArgs = (argv: string[]): { overrides: Partial<Config>; init?: boolean } => {
6767
const overrides: Partial<Config> = {};
6868
let init = false;
6969

0 commit comments

Comments
 (0)