feat(config): add TypeScript build and config support
This commit is contained in:
212
test/smoke.js
212
test/smoke.js
@@ -1,12 +1,18 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { mkdtempSync, rmSync, existsSync } from "node:fs";
|
||||
import {
|
||||
mkdtempSync,
|
||||
rmSync,
|
||||
existsSync,
|
||||
mkdirSync,
|
||||
writeFileSync,
|
||||
} from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
const cliPath = new URL("../src/index.js", import.meta.url).pathname;
|
||||
const cliPath = new URL("../src/index.ts", import.meta.url).pathname;
|
||||
const tempDir = mkdtempSync(join(tmpdir(), "glimpse-smoke-"));
|
||||
const filters = process.argv.slice(2).filter((arg) => arg !== "--list");
|
||||
const shouldList = process.argv.includes("--list");
|
||||
@@ -21,7 +27,7 @@ function dataHtml(html) {
|
||||
}
|
||||
|
||||
function runCli(args, options = {}) {
|
||||
return spawnSync(process.execPath, [cliPath, ...args], {
|
||||
return spawnSync(process.execPath, ["--import", "tsx", cliPath, ...args], {
|
||||
encoding: "utf-8",
|
||||
env: options.env ?? process.env,
|
||||
timeout: 30000,
|
||||
@@ -36,14 +42,14 @@ function parseJson(text) {
|
||||
}
|
||||
}
|
||||
|
||||
function expectSuccess(args) {
|
||||
const result = runCli(args);
|
||||
function expectSuccess(args, options = {}) {
|
||||
const result = runCli(args, options);
|
||||
assert.equal(result.status, 0, result.stderr || result.stdout);
|
||||
return parseJson(result.stdout);
|
||||
}
|
||||
|
||||
function expectFailure(args) {
|
||||
const result = runCli(args);
|
||||
function expectFailure(args, options = {}) {
|
||||
const result = runCli(args, options);
|
||||
assert.notEqual(result.status, 0, result.stdout || result.stderr);
|
||||
return parseJson(result.stderr);
|
||||
}
|
||||
@@ -83,7 +89,9 @@ test("help flag prints help", ["help", "cli"], () => {
|
||||
test("snapshot returns page metadata and content", ["snapshot"], () => {
|
||||
const output = expectSuccess([
|
||||
"snapshot",
|
||||
dataHtml('<title>Hello</title><h1>Main</h1><a href="/x">X</a><button>Go</button>'),
|
||||
dataHtml(
|
||||
'<title>Hello</title><h1>Main</h1><a href="/x">X</a><button>Go</button>',
|
||||
),
|
||||
]);
|
||||
|
||||
assert.equal(output.ok, true);
|
||||
@@ -98,24 +106,30 @@ test("snapshot returns page metadata and content", ["snapshot"], () => {
|
||||
test("snapshot extracts aria headings", ["snapshot"], () => {
|
||||
const output = expectSuccess([
|
||||
"snapshot",
|
||||
dataHtml('<title>Hello</title><div role="heading" aria-level="2">ARIA</div>'),
|
||||
dataHtml(
|
||||
'<title>Hello</title><div role="heading" aria-level="2">ARIA</div>',
|
||||
),
|
||||
]);
|
||||
|
||||
assert.equal(output.ok, true);
|
||||
assert.deepEqual(output.result.headings, [{ level: 2, text: "ARIA" }]);
|
||||
});
|
||||
|
||||
test("snapshot runs top-level javascript before extraction", ["snapshot", "js"], () => {
|
||||
const output = expectSuccess([
|
||||
"snapshot",
|
||||
dataHtml("<title>Hello</title><h1>Old</h1>"),
|
||||
"--js=document.querySelector('h1').textContent = 'New'",
|
||||
]);
|
||||
test(
|
||||
"snapshot runs top-level javascript before extraction",
|
||||
["snapshot", "js"],
|
||||
() => {
|
||||
const output = expectSuccess([
|
||||
"snapshot",
|
||||
dataHtml("<title>Hello</title><h1>Old</h1>"),
|
||||
"--js=document.querySelector('h1').textContent = 'New'",
|
||||
]);
|
||||
|
||||
assert.equal(output.ok, true);
|
||||
assert.deepEqual(output.result.headings, [{ level: 1, text: "New" }]);
|
||||
assert.equal(output.result.text, "New");
|
||||
});
|
||||
assert.equal(output.ok, true);
|
||||
assert.deepEqual(output.result.headings, [{ level: 1, text: "New" }]);
|
||||
assert.equal(output.result.text, "New");
|
||||
},
|
||||
);
|
||||
|
||||
test("exec returns javascript result", ["exec", "js"], () => {
|
||||
const result = runCli([
|
||||
@@ -128,19 +142,23 @@ test("exec returns javascript result", ["exec", "js"], () => {
|
||||
assert.equal(result.stdout.trim(), "Hello");
|
||||
});
|
||||
|
||||
test("screenshot returns standard success envelope and writes file", ["screenshot"], () => {
|
||||
const outputPath = join(tempDir, "page.png");
|
||||
const output = expectSuccess([
|
||||
"screenshot",
|
||||
dataHtml("<title>Hello</title>"),
|
||||
`--output=${outputPath}`,
|
||||
]);
|
||||
test(
|
||||
"screenshot returns standard success envelope and writes file",
|
||||
["screenshot"],
|
||||
() => {
|
||||
const outputPath = join(tempDir, "page.png");
|
||||
const output = expectSuccess([
|
||||
"screenshot",
|
||||
dataHtml("<title>Hello</title>"),
|
||||
`--output=${outputPath}`,
|
||||
]);
|
||||
|
||||
assert.equal(output.ok, true);
|
||||
assert.equal(output.result.path, outputPath);
|
||||
assert.equal(typeof output.elapsedMs, "number");
|
||||
assert.equal(existsSync(outputPath), true);
|
||||
});
|
||||
assert.equal(output.ok, true);
|
||||
assert.equal(output.result.path, outputPath);
|
||||
assert.equal(typeof output.elapsedMs, "number");
|
||||
assert.equal(existsSync(outputPath), true);
|
||||
},
|
||||
);
|
||||
|
||||
test("search validates kagi token in provider", ["search", "errors"], () => {
|
||||
const env = { ...process.env };
|
||||
@@ -152,9 +170,63 @@ test("search validates kagi token in provider", ["search", "errors"], () => {
|
||||
assert.equal(output.ok, false);
|
||||
assert.equal(output.error.code, "KAGI_TOKEN_REQUIRED");
|
||||
assert.match(output.error.message, /Kagi search requires/);
|
||||
assert.match(output.error.message, /config token/);
|
||||
assert.equal(typeof output.elapsedMs, "number");
|
||||
});
|
||||
|
||||
test(
|
||||
"invalid config returns structured error before browser startup",
|
||||
["config", "errors"],
|
||||
() => {
|
||||
const configPath = join(tempDir, "bad-config.json");
|
||||
writeFileSync(configPath, "not json");
|
||||
|
||||
const output = expectFailure([
|
||||
"snapshot",
|
||||
dataHtml("<title>Hello</title>"),
|
||||
`--config=${configPath}`,
|
||||
]);
|
||||
|
||||
assert.equal(output.ok, false);
|
||||
assert.equal(output.error.code, "CONFIG_READ_FAILED");
|
||||
assert.match(output.error.message, /Failed to read config file/);
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
"invalid config schema returns structured error",
|
||||
["config", "errors"],
|
||||
() => {
|
||||
const configPath = join(tempDir, "bad-schema.json");
|
||||
writeFileSync(configPath, JSON.stringify({ search: { provider: 42 } }));
|
||||
|
||||
const output = expectFailure([
|
||||
"snapshot",
|
||||
dataHtml("<title>Hello</title>"),
|
||||
`--config=${configPath}`,
|
||||
]);
|
||||
|
||||
assert.equal(output.ok, false);
|
||||
assert.equal(output.error.code, "INVALID_CONFIG");
|
||||
assert.match(output.error.message, /search\.provider must be a string/);
|
||||
},
|
||||
);
|
||||
|
||||
test("empty home config is accepted", ["config"], () => {
|
||||
const configHome = join(tempDir, "config-home");
|
||||
const configDir = join(configHome, "glimpse");
|
||||
mkdirSync(configDir, { recursive: true });
|
||||
writeFileSync(join(configDir, "config.json"), "{}");
|
||||
|
||||
const output = expectSuccess(
|
||||
["snapshot", dataHtml("<title>Hello</title><h1>Main</h1>")],
|
||||
{ env: { ...process.env, XDG_CONFIG_HOME: configHome } },
|
||||
);
|
||||
|
||||
assert.equal(output.ok, true);
|
||||
assert.equal(output.title, "Hello");
|
||||
});
|
||||
|
||||
test("unknown command returns structured error", ["errors", "cli"], () => {
|
||||
const output = expectFailure(["nope", dataHtml("<title>Hello</title>")]);
|
||||
|
||||
@@ -164,18 +236,22 @@ test("unknown command returns structured error", ["errors", "cli"], () => {
|
||||
assert.equal(typeof output.elapsedMs, "number");
|
||||
});
|
||||
|
||||
test("invalid timeout returns invalid option before browser startup", ["errors", "timeout"], () => {
|
||||
const output = expectFailure([
|
||||
"snapshot",
|
||||
dataHtml("<title>Hello</title>"),
|
||||
"--timeout=abc",
|
||||
]);
|
||||
test(
|
||||
"invalid timeout returns invalid option before browser startup",
|
||||
["errors", "timeout"],
|
||||
() => {
|
||||
const output = expectFailure([
|
||||
"snapshot",
|
||||
dataHtml("<title>Hello</title>"),
|
||||
"--timeout=abc",
|
||||
]);
|
||||
|
||||
assert.equal(output.ok, false);
|
||||
assert.equal(output.error.code, "INVALID_OPTION");
|
||||
assert.match(output.error.message, /--timeout must be a positive integer/);
|
||||
assert.equal(typeof output.elapsedMs, "number");
|
||||
});
|
||||
assert.equal(output.ok, false);
|
||||
assert.equal(output.error.code, "INVALID_OPTION");
|
||||
assert.match(output.error.message, /--timeout must be a positive integer/);
|
||||
assert.equal(typeof output.elapsedMs, "number");
|
||||
},
|
||||
);
|
||||
|
||||
test("invalid wait-until returns invalid option", ["errors", "wait"], () => {
|
||||
const output = expectFailure([
|
||||
@@ -215,31 +291,39 @@ test("wait-js timeout returns wait timeout", ["wait", "errors"], () => {
|
||||
assert.match(output.url, /^data:text\/html,/);
|
||||
});
|
||||
|
||||
test("wait-js exception returns script failed", ["wait", "errors", "js"], () => {
|
||||
const output = expectFailure([
|
||||
"snapshot",
|
||||
dataHtml("<title>Hello</title>"),
|
||||
'--wait-js=throw new Error("boom")',
|
||||
]);
|
||||
test(
|
||||
"wait-js exception returns script failed",
|
||||
["wait", "errors", "js"],
|
||||
() => {
|
||||
const output = expectFailure([
|
||||
"snapshot",
|
||||
dataHtml("<title>Hello</title>"),
|
||||
'--wait-js=throw new Error("boom")',
|
||||
]);
|
||||
|
||||
assert.equal(output.ok, false);
|
||||
assert.equal(output.error.code, "SCRIPT_FAILED");
|
||||
assert.match(output.error.message, /--wait-js failed/);
|
||||
assert.match(output.error.message, /boom/);
|
||||
});
|
||||
assert.equal(output.ok, false);
|
||||
assert.equal(output.error.code, "SCRIPT_FAILED");
|
||||
assert.match(output.error.message, /--wait-js failed/);
|
||||
assert.match(output.error.message, /boom/);
|
||||
},
|
||||
);
|
||||
|
||||
test("top-level javascript exception returns script failed", ["errors", "js"], () => {
|
||||
const output = expectFailure([
|
||||
"snapshot",
|
||||
dataHtml("<title>Hello</title>"),
|
||||
'--js=throw new Error("boom")',
|
||||
]);
|
||||
test(
|
||||
"top-level javascript exception returns script failed",
|
||||
["errors", "js"],
|
||||
() => {
|
||||
const output = expectFailure([
|
||||
"snapshot",
|
||||
dataHtml("<title>Hello</title>"),
|
||||
'--js=throw new Error("boom")',
|
||||
]);
|
||||
|
||||
assert.equal(output.ok, false);
|
||||
assert.equal(output.error.code, "SCRIPT_FAILED");
|
||||
assert.match(output.error.message, /Prelude script failed/);
|
||||
assert.match(output.error.message, /boom/);
|
||||
});
|
||||
assert.equal(output.ok, false);
|
||||
assert.equal(output.error.code, "SCRIPT_FAILED");
|
||||
assert.match(output.error.message, /Prelude script failed/);
|
||||
assert.match(output.error.message, /boom/);
|
||||
},
|
||||
);
|
||||
|
||||
function listTests() {
|
||||
const tags = [...new Set(tests.flatMap((entry) => entry.tags))].sort();
|
||||
|
||||
Reference in New Issue
Block a user