Examples

Prompt snapshot

You can use the ansi module to generate some control sequences to control a prompt in your test.

import { snapshotTest } from "@cliffy/testing";
import { Checkbox } from "@cliffy/prompt/checkbox";
import { ansi } from "@cliffy/ansi";

await snapshotTest({
  name: "should check an option",
  meta: import.meta,
  stdin: ansi
    .cursorDown
    .cursorDown
    .text(" ")
    .text("\n")
    .toArray(),
  async fn() {
    await Checkbox.prompt({
      message: "Select an option",
      options: [
        { name: "Foo", value: "foo" },
        { name: "Bar", value: "bar" },
        { name: "Baz", value: "baz" },
      ],
    });
  },
});

Command snapshot

A simple example with two steps and different arguments to snapshot the output of a command.

import { snapshotTest } from "@cliffy/testing";
import { Command } from "@cliffy/command";

await snapshotTest({
  name: "command",
  meta: import.meta,
  ignore: Deno.build.os === "windows",
  colors: true,
  steps: {
    "should delete a file": {
      args: ["/foo/bar"],
    },
    "should delete a directory recursively": {
      args: ["--recursive", "/foo/bar"],
    },
  },
  async fn() {
    await new Command()
      .version("1.0.0")
      .name("rm")
      .option("-r, --recursive", "Delete recursive.")
      .arguments("<path>")
      .action(({ recursive }, path) => {
        if (recursive) {
          console.log("Delete recursive: %s", path);
        } else {
          console.log("Delete: %s", path);
        }
      })
      .parse();
  },
});

Smoke test

A single snapshot can guard your entire help and usage surface against accidental changes. Each step runs --help on a different command, so one test covers the root command and every subcommand at once.

import { snapshotTest } from "@cliffy/testing";
import { Command } from "@cliffy/command";

const cli = new Command()
  .name("shop")
  .version("1.0.0")
  .description("Example shop CLI.")
  .globalOption("-v, --verbose", "Enable verbose output.")
  .command(
    "search",
    new Command()
      .description("Search for products.")
      .arguments("<query>")
      .option("-l, --limit <count:number>", "Limit the number of results."),
  )
  .command(
    "product",
    new Command()
      .description("Show a single product.")
      .arguments("<id>"),
  );

await snapshotTest({
  name: "smoke",
  meta: import.meta,
  colors: false,
  steps: {
    "--help": { args: ["--help"] },
    "search --help": { args: ["search", "--help"] },
    "product --help": { args: ["product", "--help"] },
  },
  async fn() {
    await cli.parse(Deno.args);
  },
});

In a real project you would import the command from your entry file instead of defining it inline, and use denoArgs to grant your CLI the permissions it needs at runtime.

import { snapshotTest } from "@cliffy/testing";
import { cli } from "./main.ts";

await snapshotTest({
  name: "smoke",
  meta: import.meta,
  denoArgs: ["--quiet", "--allow-env", "--allow-read"],
  steps: {
    "--help": { args: ["--help"] },
    "search --help": { args: ["search", "--help"] },
    "product --help": { args: ["product", "--help"] },
  },
  async fn() {
    await cli.parse(Deno.args);
  },
});