// qs-scaffold.test.mjs // Run: node --test bin/test/qs-scaffold.test.mjs import assert from "node:assert/strict"; import { spawnSync } from "node:child_process"; import { existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync, } from "node:fs"; import { tmpdir } from "node:os"; import { dirname, join } from "node:path"; import { test } from "node:test"; import { fileURLToPath } from "node:url"; const __dirname = dirname(fileURLToPath(import.meta.url)); const BIN = join(__dirname, "..", "qs-scaffold.mjs"); function makeTmp() { const dir = mkdtempSync(join(tmpdir(), "qs-scaffold-test-")); return dir; } test("qs-scaffold creates a config dir with shell.qml from default example", () => { const tmp = makeTmp(); try { const result = spawnSync("node", [BIN, "mybar"], { encoding: "utf8", env: { ...process.env, XDG_CONFIG_HOME: tmp }, }); assert.equal(result.status, 0, `stderr: ${result.stderr}`); const target = join(tmp, "quickshell", "mybar"); assert.ok(existsSync(target), `target dir not created: ${target}`); assert.ok(existsSync(join(target, "shell.qml")), "shell.qml missing"); // The default example is 02-per-monitor-bar (canonical per-monitor shape). const content = readFileSync(join(target, "shell.qml"), "utf8"); assert.match(content, /Variants/); assert.match(content, /Quickshell\.screens/); } finally { rmSync(tmp, { recursive: true, force: true }); } }); test("qs-scaffold with --from 01 uses 01-hello-bar", () => { const tmp = makeTmp(); try { const result = spawnSync("node", [BIN, "mybar", "--from", "01-hello-bar"], { encoding: "utf8", env: { ...process.env, XDG_CONFIG_HOME: tmp }, }); assert.equal(result.status, 0, `stderr: ${result.stderr}`); const shell = readFileSync( join(tmp, "quickshell", "mybar", "shell.qml"), "utf8", ); assert.match(shell, /hello quickshell/i); } finally { rmSync(tmp, { recursive: true, force: true }); } }); test("qs-scaffold with --from 07-composite-bar copies Bar.qml too", () => { const tmp = makeTmp(); try { const result = spawnSync( "node", [BIN, "mybar", "--from", "07-composite-bar"], { encoding: "utf8", env: { ...process.env, XDG_CONFIG_HOME: tmp }, }, ); assert.equal(result.status, 0, `stderr: ${result.stderr}`); const target = join(tmp, "quickshell", "mybar"); const files = readdirSync(target); assert.ok(files.includes("shell.qml"), "shell.qml missing"); assert.ok( files.includes("Bar.qml"), "Bar.qml missing (multi-file example not fully copied)", ); } finally { rmSync(tmp, { recursive: true, force: true }); } }); test("qs-scaffold touches .qmlls.ini for LSP", () => { const tmp = makeTmp(); try { const result = spawnSync("node", [BIN, "mybar"], { encoding: "utf8", env: { ...process.env, XDG_CONFIG_HOME: tmp }, }); assert.equal(result.status, 0, `stderr: ${result.stderr}`); assert.ok( existsSync(join(tmp, "quickshell", "mybar", ".qmlls.ini")), ".qmlls.ini missing", ); } finally { rmSync(tmp, { recursive: true, force: true }); } }); test("qs-scaffold without args exits non-zero with usage", () => { const result = spawnSync("node", [BIN], { encoding: "utf8" }); assert.notEqual(result.status, 0); assert.match(result.stderr, /usage/i); }); test("qs-scaffold with bad --from exits non-zero", () => { const tmp = makeTmp(); try { const result = spawnSync( "node", [BIN, "mybar", "--from", "99-does-not-exist"], { encoding: "utf8", env: { ...process.env, XDG_CONFIG_HOME: tmp }, }, ); assert.notEqual(result.status, 0); assert.match(result.stderr, /not found|does not exist/i); } finally { rmSync(tmp, { recursive: true, force: true }); } }); test("qs-scaffold rejects a config name that escapes the config root", () => { const tmp = makeTmp(); try { const result = spawnSync("node", [BIN, "../escaped"], { encoding: "utf8", env: { ...process.env, XDG_CONFIG_HOME: tmp }, }); assert.notEqual(result.status, 0); assert.match(result.stderr, /single path segment/i); assert.equal(existsSync(join(tmp, "escaped")), false); } finally { rmSync(tmp, { recursive: true, force: true }); } }); test("qs-scaffold rejects an example name containing path traversal", () => { const tmp = makeTmp(); try { const result = spawnSync( "node", [BIN, "mybar", "--from", "../01-hello-bar"], { encoding: "utf8", env: { ...process.env, XDG_CONFIG_HOME: tmp }, }, ); assert.notEqual(result.status, 0); assert.match(result.stderr, /single path segment/i); assert.equal(existsSync(join(tmp, "quickshell", "mybar")), false); } finally { rmSync(tmp, { recursive: true, force: true }); } }); test("qs-scaffold with existing target dir exits non-zero (no clobber)", () => { const tmp = makeTmp(); try { const target = join(tmp, "quickshell", "mybar"); mkdirSync(target, { recursive: true }); writeFileSync(join(target, "preexisting.txt"), "do not clobber"); const result = spawnSync("node", [BIN, "mybar"], { encoding: "utf8", env: { ...process.env, XDG_CONFIG_HOME: tmp }, }); assert.notEqual(result.status, 0); // Preexisting file must still exist with original content. const still = readFileSync(join(target, "preexisting.txt"), "utf8"); assert.equal(still, "do not clobber"); } finally { rmSync(tmp, { recursive: true, force: true }); } });