# shout

`shout` is kinda like really basic integration testing for your cli.

Write `.shout` files that look like shell sessions and run `shout` to test 'em.

## Install

```
bun install -g @because/shout
```

## Features

Everything you could ever ask for:

```
$ echo hello
hello

$ brew --version
Homebrew 5...

$ ls missing
ls: missing: No such file or directory
[1]
```

`...` matches anything — a whole line inline, or any number of lines on its own.

`[1]` after the expected output matches the exit code.

`$#` is a comment line — not executed, no output expected:

```
$# start the server
$ my-server &
$# now test it
$ curl localhost:8080
OK
```

## Usage

```
$ shout test
...............
15 passed in 23ms
```

`shout test` runs code in a temp directory. `-k`/`--keep` keeps it around.

`--update` will modify `.shout` files to match reality, without running any tests.

Each line in a `.shout` file is run sequentially, unless `--parallel` is passed.

## Directives

Directives go at the top of a `.shout` file, before any commands.

### `@env`

Set environment variables for the test:

```
@env GREETING=hello
@env TARGET=world

$ echo "$GREETING $TARGET"
hello world
```

### `@setup`

Prepend commands (and `@env` directives) from another `.shout` file:

```
@setup setup-shared.shout
```

Setup commands run first and their failures abort the test. Setup files cannot themselves contain `@setup` — no nesting. If both the setup file and the user file define the same `@env`, the user file wins.

### `@teardown`

Run a cleanup command after all test commands, regardless of pass/fail:

```
@teardown rm -f "$SHOUT_PROJECT_DIR/data/test.db"

$ create-db && run-tests
...
```

Teardown failures produce warnings but don't affect test results. You can also put `@teardown` in setup files:

```
# setup.shout
export DB_URL=sqlite:data/test.db
@teardown rm -f "$SHOUT_PROJECT_DIR/data/test.db"
```

```
Usage: shout test [options] [files...]

Run .shout test files

Arguments:
  files            Files or directories to test

Options:
  -u, --update     Rewrite expected output in-place with actual output
  -k, --keep       Keep temp directories after run
  --clean-env      Start with empty environment
  --path <path>    Prepend <path> to PATH (repeatable)
  --timeout <dur>  Per-command timeout (default: "10s")
  -t, --filter     Only run files matching <pattern> (substring match)
  -v, --verbose    Print each command as it runs
  --port-from <n>  Auto-assign $PORT starting from n (default: "5400")
  --parallel       Run files in parallel
  -h, --help       display help for command
```

## Environment Variables

### Set automatically

| Variable | Value |
|---|---|
| `HOME` | Path to the temp directory created for the test |
| `SHOUT_DIR` | Same as `HOME` — the temp directory for the test |
| `SHOUT_SOURCE_DIR` | Directory containing the `.shout` file being run |
| `SHOUT_PROJECT_DIR` | The `cwd` where `shout` was invoked |
| `PORT` | Auto-assigned starting from `5400` (or the value of `--port-from`), increments per file. Not set if `PORT` is already defined via `@env` or `@setup`. |

### Inherited

By default, the test shell inherits all environment variables from the parent process. Use `--clean-env` to start with an empty environment instead.

### Modified

`PATH` is prepended with any directories passed via `--path <path>`.

### User-defined

Use `@env KEY=VALUE` directives to set arbitrary variables. See [Directives](#directives).

Print an example `.shout` file:

```
$ shout example
```

## Editor Support

### Neovim

The `vim/` directory contains a Neovim plugin with syntax highlighting and commands (`:ShoutRun`, `:ShoutUpdate`, `:ShoutRunAll`).

#### lazy.nvim

```lua
{
  dir = "~/path/to/shout/vim",
  ft = "shout",
}
```

#### Manual

```sh
mkdir -p ~/.local/share/nvim/site/pack/shout/start
ln -s /path/to/shout/vim ~/.local/share/nvim/site/pack/shout/start/shout
```
