diff --git a/README.md b/README.md index 246cfd9..b6ab958 100644 --- a/README.md +++ b/README.md @@ -139,7 +139,8 @@ try { (error as InvalidAddressError).chain; // "bitcoin" } -identify("0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984").matches.length; // 13 +const { matches } = identify("0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984"); +matches.every((chain) => chain.type === "evm"); // true, and that's as far as a 0x address narrows create("bitcoin").decimals; // 8 metadata("decred").symbol; // "DCR", read from a table, no Decred class behind it ``` diff --git a/test/run-cli.ts b/test/run-cli.ts new file mode 100644 index 0000000..ca49736 --- /dev/null +++ b/test/run-cli.ts @@ -0,0 +1,30 @@ +import { createCli } from "@agntn/tools/cli"; +import { vi } from "vite-plus/test"; +import { cliOptions } from "../src/cli-commands.ts"; + +/** What one command line wrote and how it exited. */ +export interface Run { + readonly code: typeof process.exitCode; + readonly stderr: string; + readonly stdout: string; +} + +/** + * Runs one command line through the CLI the bin builds and collects both streams. + * + * @param args - Words after `chains`. + * @returns {Promise} The exit code and what reached stdout and stderr, trailing newline cut. + */ +export async function run(...args: readonly string[]): Promise { + const written = { stdout: "", stderr: "" }; + for (const stream of ["stdout", "stderr"] as const) { + vi.spyOn(process[stream], "write").mockImplementation((chunk: unknown) => { + written[stream] += String(chunk); + return true; + }); + } + await createCli(cliOptions).run(args); + const code = process.exitCode; + vi.restoreAllMocks(); + return { code, stdout: written.stdout.trimEnd(), stderr: written.stderr.trimEnd() }; +} diff --git a/test/unit/cli.test.ts b/test/unit/cli.test.ts index d2dd9df..cc6bd5b 100644 --- a/test/unit/cli.test.ts +++ b/test/unit/cli.test.ts @@ -1,10 +1,9 @@ import { spawn } from "node:child_process"; import { once } from "node:events"; import { resolve as resolvePath } from "node:path"; -import { createCli } from "@agntn/tools/cli"; import { afterEach, describe, expect, it, vi } from "vite-plus/test"; -import { cliOptions } from "../../src/cli-commands.ts"; import { Chain, register, type ChainKey } from "../../src/index.ts"; +import { run } from "../run-cli.ts"; /** No built-in chain lacks a txid check any more, so the CLI message needs a registered stand-in. */ class Unvalidated extends Chain { @@ -21,33 +20,6 @@ const ESCAPE = String.fromCodePoint(27); const CSI = String.fromCodePoint(155); const CONTROL = /[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/u; -/** What one command line wrote and how it exited. */ -interface Run { - readonly code: typeof process.exitCode; - readonly stderr: string; - readonly stdout: string; -} - -/** - * Runs one command line through the CLI the bin builds and collects both streams. - * - * @param args - Words after `chains`. - * @returns {Promise} The exit code and what reached stdout and stderr, trailing newline cut. - */ -async function run(...args: readonly string[]): Promise { - const written = { stdout: "", stderr: "" }; - for (const stream of ["stdout", "stderr"] as const) { - vi.spyOn(process[stream], "write").mockImplementation((chunk: unknown) => { - written[stream] += String(chunk); - return true; - }); - } - await createCli(cliOptions).run(args); - const code = process.exitCode; - vi.restoreAllMocks(); - return { code, stdout: written.stdout.trimEnd(), stderr: written.stderr.trimEnd() }; -} - afterEach(() => { vi.restoreAllMocks(); process.exitCode = undefined; diff --git a/test/unit/readme.test.ts b/test/unit/readme.test.ts new file mode 100644 index 0000000..cde286c --- /dev/null +++ b/test/unit/readme.test.ts @@ -0,0 +1,33 @@ +import { readFileSync } from "node:fs"; +import { afterEach, describe, expect, it } from "vite-plus/test"; +import { run } from "../run-cli.ts"; + +/** One command the README runs and the output it prints right under it. */ +const TRANSCRIPT = /```bash\n(?:npx @agntn\/chains|chains) ([^\n]+)\n```\n\n```\n(.+?)\n```/gsu; + +const readme = readFileSync(new URL("../../README.md", import.meta.url), "utf8"); + +const transcripts = [...readme.matchAll(TRANSCRIPT)].map( + ([, command = "", printed = ""]) => [command, printed] as const, +); + +afterEach(() => { + process.exitCode = undefined; +}); + +describe("README transcripts", () => { + it("finds every command the README answers", () => { + expect(transcripts.map(([command]) => command)).toEqual([ + "info matic", + "identify 0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984", + "identify bc1qjvm9jkrjw9uvsn8905dwa6eau0guyc9laau03a", + "validate btc 1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN3", + ]); + }); + + it.each(transcripts)("chains %s prints what the README shows", async (command, printed) => { + const { stderr, stdout } = await run(...command.split(" ")); + + expect(stdout || stderr).toBe(printed); + }); +});