#!/usr/bin/env node // @ts-nocheck 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.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"); const tests = []; function test(name, tags, fn) { tests.push({ name, tags, fn }); } function dataHtml(html) { return `data:text/html,${html}`; } function runCli(args, options = {}) { return spawnSync(process.execPath, ["--import", "tsx", cliPath, ...args], { encoding: "utf-8", env: options.env ?? process.env, timeout: 30000, }); } function parseJson(text) { try { return JSON.parse(text); } catch (err) { throw new Error(`Failed to parse JSON: ${err.message}\n${text}`); } } 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, options = {}) { const result = runCli(args, options); assert.notEqual(result.status, 0, result.stdout || result.stderr); return parseJson(result.stderr); } function matchesFilters(entry) { if (filters.length === 0) { return true; } return filters.some((filter) => { const normalized = filter.toLowerCase(); return ( entry.name.toLowerCase().includes(normalized) || entry.tags.includes(normalized) ); }); } test("no args prints help", ["help", "cli"], () => { const result = runCli([]); assert.equal(result.status, 0, result.stderr || result.stdout); assert.match(result.stdout, /Usage: glimpse \[options\]/); assert.match(result.stdout, /reader /); assert.equal(result.stderr, ""); }); test("help flag prints help", ["help", "cli"], () => { const result = runCli(["--help"]); assert.equal(result.status, 0, result.stderr || result.stdout); assert.match(result.stdout, /Usage: glimpse \[options\]/); assert.match(result.stdout, /--wait-js=/); assert.equal(result.stderr, ""); }); test("reader extracts page content as markdown", ["reader"], () => { const result = runCli([ "reader", dataHtml( 'Hello

Main

Some text

Link', ), "--no-reader", ]); assert.equal(result.status, 0, result.stderr || result.stdout); const output = result.stdout.trim(); assert.match(output, /# Main/); assert.match(output, /Some text/); assert.match(output, /\[Link\]\(https:\/\/example\.com\/??\)/); }); test("reader returns json format with method field", ["reader"], () => { const output = expectSuccess([ "reader", dataHtml("Hello

Main

World

"), "--no-reader", "--format=json", ]); assert.equal(output.title, "Hello"); assert.equal(output.method, "raw"); assert.equal(typeof output.markdown, "string"); assert.match(output.markdown, /# Main/); assert.match(output.text, /Main/); }); test( "reader runs top-level javascript before extraction", ["reader", "js"], () => { const result = runCli([ "reader", dataHtml("Hello

Old

"), "--no-reader", "--js=document.querySelector('h1').textContent = 'New'", ]); assert.equal(result.status, 0, result.stderr || result.stdout); assert.match(result.stdout, /# New/); }, ); test("exec returns javascript result", ["exec", "js"], () => { const result = runCli([ "exec", dataHtml("Hello"), "--js=return document.title", ]); assert.equal(result.status, 0, result.stderr || result.stdout); 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("Hello"), `--output=${outputPath}`, ]); 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 }; delete env.KAGI_TOKEN; const result = runCli(["search", "--config=/nonexistent/path", "example query"], { env }); const output = parseJson(result.stderr); assert.notEqual(result.status, 0, result.stdout || result.stderr); 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([ "reader", dataHtml("Hello"), "--no-reader", `--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([ "reader", dataHtml("Hello"), "--no-reader", `--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 result = runCli( ["reader", dataHtml("Hello

Main

"), "--no-reader"], { env: { ...process.env, XDG_CONFIG_HOME: configHome } }, ); assert.equal(result.status, 0, result.stderr || result.stdout); assert.match(result.stdout, /# Main/); }); test("unknown command returns structured error", ["errors", "cli"], () => { const output = expectFailure(["nope", dataHtml("Hello"), "--no-reader"]); assert.equal(output.ok, false); assert.equal(output.error.code, "UNKNOWN_COMMAND"); assert.match(output.error.message, /Unknown command: nope/); assert.equal(typeof output.elapsedMs, "number"); }); test( "invalid timeout returns invalid option before browser startup", ["errors", "timeout"], () => { const output = expectFailure([ "reader", dataHtml("Hello"), "--no-reader", "--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"); }, ); test("invalid wait-until returns invalid option", ["errors", "wait"], () => { const output = expectFailure([ "reader", dataHtml("Hello"), "--no-reader", "--wait-until=loaded", ]); assert.equal(output.ok, false); assert.equal(output.error.code, "INVALID_OPTION"); assert.match(output.error.message, /Unsupported --wait-until value: loaded/); }); test("wait-js succeeds when condition is true", ["wait"], () => { const result = runCli([ "reader", dataHtml("Hello

Main

"), "--no-reader", '--wait-js=return document.title === "Hello"', ]); assert.equal(result.status, 0, result.stderr || result.stdout); assert.match(result.stdout, /# Main/); }); test("wait-js timeout returns wait timeout", ["wait", "errors"], () => { const output = expectFailure([ "reader", dataHtml("Hello"), "--no-reader", "--wait-js=return false", "--timeout=1", ]); assert.equal(output.ok, false); assert.equal(output.error.code, "WAIT_TIMEOUT"); assert.match(output.error.message, /waiting for --wait-js/); assert.equal(typeof output.elapsedMs, "number"); assert.match(output.url, /^data:text\/html,/); }); test( "wait-js exception returns script failed", ["wait", "errors", "js"], () => { const output = expectFailure([ "reader", dataHtml("Hello"), "--no-reader", '--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/); }, ); test( "top-level javascript exception returns script failed", ["errors", "js"], () => { const output = expectFailure([ "reader", dataHtml("Hello"), "--no-reader", '--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/); }, ); function listTests() { const tags = [...new Set(tests.flatMap((entry) => entry.tags))].sort(); console.log(`Tags: ${tags.join(", ")}`); for (const entry of tests) { console.log(`${entry.name} [${entry.tags.join(", ")}]`); } } async function main() { if (shouldList) { listTests(); return; } const selectedTests = tests.filter(matchesFilters); if (selectedTests.length === 0) { console.error(`No tests matched: ${filters.join(", ")}`); process.exit(1); } let failed = 0; // Run Tests for (const { name, fn } of selectedTests) { try { await fn(); console.log(`ok - ${name}`); } catch (err) { failed += 1; console.error(`not ok - ${name}`); console.error(err.stack || err.message); } } // Clean Temporary Files rmSync(tempDir, { recursive: true, force: true }); if (failed > 0) { console.error(`${failed} test(s) failed`); process.exit(1); } console.log(`${selectedTests.length} test(s) passed`); } main().catch((err) => { rmSync(tempDir, { recursive: true, force: true }); console.error(err.stack || err.message); process.exit(1); });