88 lines
3.4 KiB
TypeScript
88 lines
3.4 KiB
TypeScript
// Integration-style tests for src/stow.ts. These exercise the real `stow` binary and
|
|
// real filesystem calls, so each test:
|
|
// - runs against a throwaway $HOME (a fresh mkdtempSync dir), never the real one
|
|
// - is skipped when `stow` isn't installed (confirmed missing on a bare host) —
|
|
// run the full suite for real via `./docker/test.sh "npm test"`
|
|
import { test, after } from "node:test";
|
|
import assert from "node:assert";
|
|
import { execFileSync } from "node:child_process";
|
|
import { existsSync, lstatSync, mkdtempSync, readFileSync, rmSync, writeFileSync, mkdirSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
|
|
function hasStow(): boolean {
|
|
try {
|
|
execFileSync("which", ["stow"], { stdio: "ignore" });
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
const skip = !hasStow();
|
|
|
|
// os.homedir() reads $HOME on POSIX, so setting this before importing stow.js
|
|
// (whose module-level consts are computed from homedir() at import time) sandboxes
|
|
// every path the module touches, with no changes to production code.
|
|
const fakeHome = mkdtempSync(join(tmpdir(), "dotfiles-test-home-"));
|
|
process.env.HOME = fakeHome;
|
|
after(() => rmSync(fakeHome, { recursive: true, force: true }));
|
|
|
|
const {
|
|
backupConflicts,
|
|
restoreLatestBackup,
|
|
restow,
|
|
stow,
|
|
unstow,
|
|
writeState,
|
|
readState,
|
|
} = skip ? ({} as any) : await import("../src/stow.js");
|
|
|
|
const dirs = ["hyprland", "waybar", "kitty", "hyprpaper"];
|
|
const hyprlandTarget = join(fakeHome, ".config", "hypr", "hyprland.lua");
|
|
const wallpaperTarget = join(fakeHome, ".config", "hypr", "wallpapers", "wallpaper.png");
|
|
|
|
test("seeds a pre-existing conflicting file", { skip }, () => {
|
|
mkdirSync(join(fakeHome, ".config", "hypr"), { recursive: true });
|
|
writeFileSync(hyprlandTarget, "old config\n");
|
|
assert.strictEqual(readFileSync(hyprlandTarget, "utf8").trim(), "old config");
|
|
});
|
|
|
|
test("backupConflicts moves the pre-existing file out of the way", { skip }, () => {
|
|
const backupDir = backupConflicts(dirs);
|
|
assert.ok(backupDir, "expected a backup dir since a conflict existed");
|
|
assert.ok(!existsSync(hyprlandTarget), "conflicting file should have been moved out of the way");
|
|
});
|
|
|
|
test("stow symlinks the chosen packages into $HOME", { skip }, () => {
|
|
stow(dirs);
|
|
assert.ok(lstatSync(hyprlandTarget).isSymbolicLink(), "hyprland.lua should now be a symlink");
|
|
assert.ok(existsSync(wallpaperTarget), "wallpaper.png should be reachable through the stowed dir");
|
|
});
|
|
|
|
test("writeState/readState round-trip the chosen dirs", { skip }, () => {
|
|
writeState({ dirs });
|
|
assert.deepStrictEqual(readState().dirs, dirs);
|
|
});
|
|
|
|
test("restow keeps the symlinks intact", { skip }, () => {
|
|
restow(dirs);
|
|
assert.ok(lstatSync(hyprlandTarget).isSymbolicLink(), "restow should keep the symlink");
|
|
assert.ok(existsSync(wallpaperTarget), "restow should keep wallpaper reachable");
|
|
});
|
|
|
|
test("unstow removes the symlinks", { skip }, () => {
|
|
unstow(dirs);
|
|
assert.ok(
|
|
!existsSync(hyprlandTarget) || !lstatSync(hyprlandTarget).isSymbolicLink(),
|
|
"unstow should remove the symlink",
|
|
);
|
|
assert.ok(!existsSync(wallpaperTarget), "unstow should remove the wallpaper symlink");
|
|
});
|
|
|
|
test("restoreLatestBackup brings back the original file content", { skip }, () => {
|
|
const restored = restoreLatestBackup();
|
|
assert.ok(restored, "expected a backup to restore");
|
|
assert.strictEqual(readFileSync(hyprlandTarget, "utf8").trim(), "old config", "original content should be restored");
|
|
});
|