fec35a2333
src/ now contains only source code; tests live alongside it under
tests/. Bun's test runner discovers '**/*.test.ts' so no test-runner
config change is needed.
Changes:
- tests/config.test.ts (was src/config.test.ts)
- tests/configFile.test.ts (was src/configFile.test.ts)
- Imports updated: ./config.ts -> ../src/config.ts (likewise for
configFile.ts and errors.ts).
- tsconfig.json include adds 'tests/**/*.ts' so tsc type-checks
the test files too.
All 25 tests still pass; tsc clean.
124 lines
4.4 KiB
TypeScript
124 lines
4.4 KiB
TypeScript
/**
|
|
* config.test.ts
|
|
* --------------
|
|
* Unit tests for the layered config resolver and the default-path helper.
|
|
* Run via `bun test` (or `bun run test`).
|
|
*/
|
|
|
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
|
|
import { DEFAULT_CONFIG, defaultConfigPath, resolveConfig } from "../src/config.ts";
|
|
import type { ConfigOverrides } from "../src/config.ts";
|
|
import { CliError } from "../src/errors.ts";
|
|
|
|
const NONE: ConfigOverrides = {
|
|
moveInterval: undefined,
|
|
checkInterval: undefined,
|
|
stepDelay: undefined,
|
|
stepCount: undefined,
|
|
verbose: undefined,
|
|
};
|
|
|
|
describe("resolveConfig", () => {
|
|
test("returns DEFAULT_CONFIG when neither layer supplies a value", () => {
|
|
expect(resolveConfig(null, NONE)).toEqual(DEFAULT_CONFIG);
|
|
});
|
|
|
|
test("CLI value wins over file value", () => {
|
|
const file: ConfigOverrides = { ...NONE, moveInterval: 60 };
|
|
const cli: ConfigOverrides = { ...NONE, moveInterval: 30 };
|
|
const cfg = resolveConfig(file, cli);
|
|
expect(cfg.moveInterval).toBe(30 * 1000); // CLI 30s -> 30000ms
|
|
});
|
|
|
|
test("file value wins over default when CLI is undefined", () => {
|
|
const file: ConfigOverrides = { ...NONE, moveInterval: 60 };
|
|
const cfg = resolveConfig(file, NONE);
|
|
expect(cfg.moveInterval).toBe(60 * 1000); // file 60s -> 60000ms
|
|
});
|
|
|
|
test("seconds-to-ms conversion at the boundary for time-valued fields", () => {
|
|
const cli: ConfigOverrides = { ...NONE, moveInterval: 5, checkInterval: 2 };
|
|
const cfg = resolveConfig(null, cli);
|
|
expect(cfg.moveInterval).toBe(5000);
|
|
expect(cfg.checkInterval).toBe(2000);
|
|
});
|
|
|
|
test("stepDelay and stepCount pass through untouched (no unit conversion)", () => {
|
|
const cli: ConfigOverrides = { ...NONE, stepDelay: 75, stepCount: 100 };
|
|
const cfg = resolveConfig(null, cli);
|
|
expect(cfg.stepDelay).toBe(75);
|
|
expect(cfg.stepCount).toBe(100);
|
|
});
|
|
|
|
test("verbose: CLI true wins over file false", () => {
|
|
const cfg = resolveConfig(
|
|
{ ...NONE, verbose: false },
|
|
{ ...NONE, verbose: true },
|
|
);
|
|
expect(cfg.verbose).toBe(true);
|
|
});
|
|
|
|
test("verbose: file true wins over default (no CLI)", () => {
|
|
const cfg = resolveConfig({ ...NONE, verbose: true }, NONE);
|
|
expect(cfg.verbose).toBe(true);
|
|
});
|
|
|
|
test("verbose: file false wins over default (no CLI)", () => {
|
|
const cfg = resolveConfig({ ...NONE, verbose: false }, NONE);
|
|
expect(cfg.verbose).toBe(false);
|
|
});
|
|
|
|
test("verbose: falls back to DEFAULT_CONFIG.verbose when neither set", () => {
|
|
const cfg = resolveConfig(null, NONE);
|
|
expect(cfg.verbose).toBe(DEFAULT_CONFIG.verbose);
|
|
});
|
|
});
|
|
|
|
describe("defaultConfigPath", () => {
|
|
let savedXdg: string | undefined;
|
|
let savedHome: string | undefined;
|
|
|
|
beforeEach(() => {
|
|
savedXdg = process.env.XDG_CONFIG_HOME;
|
|
savedHome = process.env.HOME;
|
|
});
|
|
|
|
afterEach(() => {
|
|
if (savedXdg === undefined) delete process.env.XDG_CONFIG_HOME;
|
|
else process.env.XDG_CONFIG_HOME = savedXdg;
|
|
if (savedHome === undefined) delete process.env.HOME;
|
|
else process.env.HOME = savedHome;
|
|
});
|
|
|
|
test("honors XDG_CONFIG_HOME when set", () => {
|
|
process.env.XDG_CONFIG_HOME = "/custom/xdg";
|
|
process.env.HOME = "/should/not/be/used";
|
|
expect(defaultConfigPath()).toBe("/custom/xdg/move/config.json");
|
|
});
|
|
|
|
test("falls back to $HOME/.config when XDG_CONFIG_HOME is unset", () => {
|
|
delete process.env.XDG_CONFIG_HOME;
|
|
process.env.HOME = "/u/test";
|
|
expect(defaultConfigPath()).toBe("/u/test/.config/move/config.json");
|
|
});
|
|
|
|
test("treats empty XDG_CONFIG_HOME as unset (per XDG spec)", () => {
|
|
process.env.XDG_CONFIG_HOME = "";
|
|
process.env.HOME = "/u/test";
|
|
expect(defaultConfigPath()).toBe("/u/test/.config/move/config.json");
|
|
});
|
|
|
|
test("throws CliError when both XDG_CONFIG_HOME and HOME are unset", () => {
|
|
delete process.env.XDG_CONFIG_HOME;
|
|
delete process.env.HOME;
|
|
expect(() => defaultConfigPath()).toThrow(CliError);
|
|
});
|
|
|
|
test("throws CliError when both XDG_CONFIG_HOME and HOME are empty", () => {
|
|
process.env.XDG_CONFIG_HOME = "";
|
|
process.env.HOME = "";
|
|
expect(() => defaultConfigPath()).toThrow(CliError);
|
|
});
|
|
});
|