> ## Documentation Index
> Fetch the complete documentation index at: https://www.chapterjs.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Testing

> Everything a scenario can do: the bot under test, the people and channels of its server, and what an answer can be checked for

`scenario()` declares a test of your bot, from a `*.test.ts` file of `src/`, and `chapterjs test` runs it against a Discord in memory. Everything on this page is imported from `chapterjs/test`.

```ts src/checks/hello.test.ts theme={"theme":{"light":"github-light","dark":"vesper"}}
import { scenario } from 'chapterjs/test';
import { hello } from './hello';

export default scenario('hello greets', async bot => {
  const reply = await bot.use(hello, { who: bot.member('alice') });
  reply.says('Hello alice').isPublic();
});
```

```ts src/checks/hello.ts theme={"theme":{"light":"github-light","dark":"vesper"}}
import { command } from 'chapterjs';

export const hello = command({
  name: 'hello',
  description: 'Says hello',
  options: { who: { type: 'user', description: 'Who', required: true } },
  async run({ interaction, options }) {
    await interaction.reply(`Hello ${options.who.username}!`);
  },
});
```

> [!NOTE]
>
> How to test a bot, what is checked without a scenario and what the test server has is in the [guide](https://www.chapterjs.org/testing.md). This page lists what exists.

## scenario()

```ts theme={"theme":{"light":"github-light","dark":"vesper"}}
scenario(label: string, run: (bot: TestBot) => unknown): ScenarioDeclaration
```

Declares a test of your bot. Export the result from a `*.test.ts` file of `src/`: `chapterjs test` runs it against a Discord in memory, where the bot is in a server with a few people in it.

`bot` is the bot in its test server: `bot.use()` runs a command, `bot.click()` presses a button, `bot.say()` sends a message as someone, and what comes back says what the bot answered.

A file may export several scenarios, in a list too. The label is what the report shows: it can not be empty, and 120 characters is the most.

### ScenarioDeclaration

What `scenario()` returns: a declaration the runner finds in the exports of a file.

**config** `\{ label: string; run: (bot: TestBot) =\> unknown \}`

What the file gave to `scenario()`, not checked yet.

## TestBot

The bot of the project, running in a Discord in memory: one server, a few people, and everything the bot does kept where the test can read it.

**guild** `\{ id: string; name: string \}` *required*

The test server.

**me** `TestMember` *required*

The bot itself, as a member of the server.

**owner** `TestMember` *required*

The owner of the server.

**someone** `TestMember` *required*

A person with no special rights: who does things when nobody is named.

**requests** `\{ method: string; path: string; body: unknown \}[]` *required*

Every request the bot sent to Discord so far.

### member()

```ts theme={"theme":{"light":"github-light","dark":"vesper"}}
member(name: string, options?: TestMemberOptions): TestMember
```

A person of the server, made the first time the name is given. The options only count the first time.

### role()

```ts theme={"theme":{"light":"github-light","dark":"vesper"}}
role(name: string, options?: TestRoleOptions): TestRole
```

A role of the server, made the first time the name is given.

### channel()

```ts theme={"theme":{"light":"github-light","dark":"vesper"}}
channel(name: string, options?: TestChannelOptions): TestChannel
```

A channel of the server, made the first time the name is given. `#general` exists.

### use()

```ts theme={"theme":{"light":"github-light","dark":"vesper"}}
use(command: CommandDeclaration, options?: OptionInputsOf<Options>, where?: TestWhere): Promise<TestReply>
use(command: MenuDeclaration, target: TestMenuTarget, where?: TestWhere): Promise<TestReply>
```

Uses a command, as a person would: a slash command with its options, or a menu command on what it is used on (`{ on: message }`, `{ on: member }`; a message menu is used in the channel of its message). Resolves once the bot answered.

`options` is typed from the options of the command (`OptionInputsOf`): a text, a number, `true` or `false`, one of the `choices`, a `TestMember` for a `user` option, a `TestRole` for a `role` one, a `TestChannel` for a `channel` one, a `TestFile` for an `attachment` one. It may be left out when the command requires none. What Discord would not let a person send (a required option missing, a number out of its range, a text too long, a value that is not one of the choices) fails the scenario before the bot hears of it.

### click()

```ts theme={"theme":{"light":"github-light","dark":"vesper"}}
click(button: ButtonTarget, where?: TestWhere): Promise<TestReply>
```

Clicks a button. Resolves once the bot answered. A button found on a message (`reply.button('Confirm')`) is clicked on that message, as a person would.

### select()

```ts theme={"theme":{"light":"github-light","dark":"vesper"}}
select(menu: SelectTarget, values: readonly SelectInput[], where?: TestWhere): Promise<TestReply>
```

Picks values in a select menu. Resolves once the bot answered. `values` are texts for a menu of texts (its option values), people, roles or channels for the others; the `min` and `max` of the menu are enforced.

### submit()

```ts theme={"theme":{"light":"github-light","dark":"vesper"}}
submit(form: FormTarget<Fields>, fields?: FieldInputsOf<Fields>, where?: TestWhere): Promise<TestReply>
```

Fills in and sends a form. Resolves once the bot answered. `fields` is typed from the fields of the form (`FieldInputsOf`): a text for a `text` field, a list of option values for a `select` or `checkboxes` one, one option value for a `radio` one, `true` or `false` for a `checkbox`, people, roles or channels for the others, files for a `files` field. A field left out stays empty; a required one left out fails the scenario, as the Discord app would refuse to send the form.

### suggest()

```ts theme={"theme":{"light":"github-light","dark":"vesper"}}
suggest(command: CommandDeclaration, option: string, typed: string, where?: TestWhere): Promise<TestSuggestion[]>
```

Types in an option with suggestions, and gets what the bot suggests.

### say()

```ts theme={"theme":{"light":"github-light","dark":"vesper"}}
say(content: string, where?: TestWhere): Promise<TestMessage>
```

Sends a message as a person. Resolves once the bot handled it.

### react()

```ts theme={"theme":{"light":"github-light","dark":"vesper"}}
react(message: TestMessage, emoji: string, where?: { by?: TestMember }): Promise<void>
```

Reacts to a message as a person. Resolves once the bot handled it.

### join()

```ts theme={"theme":{"light":"github-light","dark":"vesper"}}
join(member: string | TestMember, options?: TestMemberOptions): Promise<TestMember>
```

Makes a person join the server. Resolves once the bot handled it.

### leave()

```ts theme={"theme":{"light":"github-light","dark":"vesper"}}
leave(member: TestMember): Promise<void>
```

Makes a person leave the server. Resolves once the bot handled it.

### run()

```ts theme={"theme":{"light":"github-light","dark":"vesper"}}
run(task: TaskDeclaration, at?: Date): Promise<void>
```

Runs a task now, as if its time had come. Resolves once it is done.

### settle()

```ts theme={"theme":{"light":"github-light","dark":"vesper"}}
settle(): Promise<void>
```

Waits for everything the bot started to be done.

Everything but what waits for the test to act: a function waiting for a message, a click or a reaction (`waitForMessage()`...) is left waiting, and `use()`, `say()`, `click()`... resolve meanwhile.

### endWaits()

```ts theme={"theme":{"light":"github-light","dark":"vesper"}}
endWaits(): Promise<void>
```

Ends everything the bot waits for (`waitForMessage()`, `waitForClick()`...) as if its time was up: time does not pass in the test server. Resolves once the bot handled it.

## TestWhere

Who does something, and where.

**by** `TestMember`

Who does it. Default: `bot.someone`.

**in** `TestChannel | 'dm'`

Where: a channel, or `'dm'` for a private message with the bot. Default: `#general`.

**on** `TestMessage | TestReply`

For a button or a menu: the message it is on. Default: a message of the bot.

## TestMemberOptions

How to make a person for a test.

**roles** `(string | TestRole)[]`

The roles of the person, by name or as `bot.role()` gave them.

**permissions** `PermissionName[]`

Permissions given through a role of their own.

**nickname** `string`

The nickname in the server.

**bot** `boolean`

Whether the person is a bot.

**locale** `Locale`

The language of the person: what `t` answers them in. Default: `'en-US'`.

## TestRoleOptions

**permissions** `PermissionName[]`

What the role allows.

## TestChannelOptions

**type** `TestChannelType`

`'text'`, `'announcement'`, `'voice'`, `'category'` or `'forum'`. Default: `'text'`.

**parent** `TestChannel`

The category it is in.

**topic** `string`

The topic of the channel.

## TestFile

A file someone attaches to a command or a form.

**name** `string` *required*

The name of the file, with its extension: `photo.png`.

**content** `string`

Its content, as text.

**type** `string`

Its media type: guessed from the name by default.

## TestReply

What the bot answered to a command, a button, a menu or a form. It has every check of [`TestAssertions`](#testassertions), and:

**message** `TestMessage | null` *required*

The message the bot answered with (or changed), or `null` without one.

**content** `string` *required*

The text of the answer; `''` without a message.

**embeds** `Embed[]` *required*

The embeds, as the bot sent them.

**buttons** `TestButton[]` *required*

The buttons on it, in order.

**menus** `TestMenu[]` *required*

The select menus on it, in order.

**followUps** `TestMessage[]` *required*

The messages sent after the first answer, in order.

**form** `TestForm | null` *required*

The form the bot opened, or `null`.

**deferred** `boolean` *required*

Whether the bot said the answer was coming before answering.

**updated** `boolean` *required*

Whether the answer changed the message of the component.

**ephemeral** `boolean` *required*

Whether only one person sees it.

### isDeferred()

```ts theme={"theme":{"light":"github-light","dark":"vesper"}}
isDeferred(): TestReply
```

Checks that the bot said the answer was coming ("thinking…").

### updatesMessage()

```ts theme={"theme":{"light":"github-light","dark":"vesper"}}
updatesMessage(): TestReply
```

Checks that the bot changed the message of the button or menu.

### opensForm()

```ts theme={"theme":{"light":"github-light","dark":"vesper"}}
opensForm(form?: ModalDeclaration | ModalComponent | TestForm): TestReply
```

Checks that the bot opened a form, this one when given: its export (whatever data it carries), the form called with its data, or one found on an answer.

### changesNothing()

```ts theme={"theme":{"light":"github-light","dark":"vesper"}}
changesNothing(): TestReply
```

Checks that the bot answered with nothing visible (a deferred update).

### button()

```ts theme={"theme":{"light":"github-light","dark":"vesper"}}
button(label: string): TestButton
```

The button with this label, to click it. Fails when there is none.

### menu()

```ts theme={"theme":{"light":"github-light","dark":"vesper"}}
menu(placeholder?: string): TestMenu
```

The menu, or the one with this placeholder. Fails when there is none.

## TestAssertions

What is checked of a message or an answer. Each check throws a `TestFailure` with a plain sentence when it fails, and returns what it checked otherwise, so checks chain.

### says()

```ts theme={"theme":{"light":"github-light","dark":"vesper"}}
says(text: string | RegExp): Self
```

Checks that the text contains `text` (or matches it).

### saysExactly()

```ts theme={"theme":{"light":"github-light","dark":"vesper"}}
saysExactly(text: string): Self
```

Checks that the text is exactly `text`.

### isEphemeral()

```ts theme={"theme":{"light":"github-light","dark":"vesper"}}
isEphemeral(): Self
```

Checks that only the person who asked sees it.

### isPublic()

```ts theme={"theme":{"light":"github-light","dark":"vesper"}}
isPublic(): Self
```

Checks that everyone sees it.

### hasButton()

```ts theme={"theme":{"light":"github-light","dark":"vesper"}}
hasButton(label: string): Self
```

Checks that there is a button with this label.

### hasEmbed()

```ts theme={"theme":{"light":"github-light","dark":"vesper"}}
hasEmbed(title?: string): Self
```

Checks that there is an embed, with this title when given.

### hasNoEmbed()

```ts theme={"theme":{"light":"github-light","dark":"vesper"}}
hasNoEmbed(): Self
```

Checks that there is no embed.

### hasNoComponent()

```ts theme={"theme":{"light":"github-light","dark":"vesper"}}
hasNoComponent(): Self
```

Checks that there is no button and no menu.

## TestMessage

A message in the test server. It has every check of [`TestAssertions`](#testassertions), and:

**id** `string` *required*

The id of the message.

**content** `string` *required*

The text of the message.

**author** `TestMember | null` *required*

Who wrote it; `null` for someone who is not in the world any more.

**fromBot** `boolean` *required*

Whether the bot wrote it.

**embeds** `Embed[]` *required*

The embeds, as the bot sent them.

**buttons** `TestButton[]` *required*

The buttons on it, in order.

**menus** `TestMenu[]` *required*

The select menus on it, in order.

**ephemeral** `boolean` *required*

Whether only one person sees it.

**pinned** `boolean` *required*

Whether the message is pinned.

**channel** `TestChannel` *required*

The channel it is in.

**deleted** `boolean` *required*

Whether the message was deleted since.

### button()

```ts theme={"theme":{"light":"github-light","dark":"vesper"}}
button(label: string): TestButton
```

The button with this label: to click it. Fails when there is none.

### menu()

```ts theme={"theme":{"light":"github-light","dark":"vesper"}}
menu(placeholder?: string): TestMenu
```

The menu, or the one with this placeholder. Fails when there is none.

## TestMember

A person in the test server.

**id** `string` *required*

The id of the person.

**name** `string` *required*

The username of the person: what `bot.member()` was given.

**nickname** `string | null` *required*

The nickname in the server, or `null` without one.

**roles** `string[]` *required*

The names of the roles of the person.

**permissions** `PermissionName[]` *required*

The permissions of the person in the server.

**inServer** `boolean` *required*

Whether the person is in the server right now.

**banned** `boolean` *required*

Whether the person is banned from the server.

**timedOut** `boolean` *required*

Whether the person is timed out right now.

**privateMessages** `TestMessage[]` *required*

The messages the bot sent to the person in private, oldest first.

### can()

```ts theme={"theme":{"light":"github-light","dark":"vesper"}}
can(permission: PermissionName): boolean
```

Whether the person may do that in the server.

### hasRole()

```ts theme={"theme":{"light":"github-light","dark":"vesper"}}
hasRole(role: string | TestRole): boolean
```

Whether the person has that role.

## TestRole

A role of the test server.

**id** `string` *required*

The id of the role.

**name** `string` *required*

The name of the role.

**permissions** `PermissionName[]` *required*

What the role allows.

**exists** `boolean` *required*

Whether the role still exists.

## TestChannel

A channel of the test server, a thread, or a private conversation.

**id** `string` *required*

The id of the channel.

**name** `string` *required*

The name of the channel.

**type** `string` *required*

What kind of channel it is: `'text'`, `'voice'`, `'thread'`, `'dm'`...

**topic** `string | null` *required*

The topic, or `null`.

**slowmode** `number` *required*

The seconds between two messages of one person (slowmode); `0` for none.

**exists** `boolean` *required*

Whether the channel still exists.

**messages** `TestMessage[]` *required*

Every message in it that everyone sees, oldest first (deleted and ephemeral ones left out).

**last** `TestMessage | null` *required*

The newest message, or `null`.

**threads** `TestChannel[]` *required*

The threads opened in it.

**members** `TestMember[]` *required*

For a thread: who is in it.

**archived** `boolean` *required*

For a thread: whether it is archived.

**locked** `boolean` *required*

For a thread: whether it is locked.

### allows()

```ts theme={"theme":{"light":"github-light","dark":"vesper"}}
allows(member: TestMember | TestRole, permission: PermissionName): boolean
```

Whether a member may do that in the channel (its overwrites counted).

## TestButton

A button on a message the bot sent.

**label** `string | null` *required*

The text on the button, or `null` for an emoji alone.

**style** `string` *required*

`'primary'`, `'secondary'`, `'success'`, `'danger'`, `'link'` or `'premium'`.

**disabled** `boolean` *required*

Whether the button is disabled.

**customId** `string | null` *required*

The id the framework wrote in it.

## TestMenu

A select menu on a message the bot sent.

**type** `string` *required*

`'string'`, `'user'`, `'role'`, `'mentionable'` or `'channel'`.

**placeholder** `string | null` *required*

The text shown when nothing is picked.

**disabled** `boolean` *required*

Whether the menu is disabled.

**options** `\{ label: string; value: string \}[]` *required*

For a menu of texts: its options.

**customId** `string` *required*

The id the framework wrote in it.

## TestForm

A form the bot opened.

**title** `string` *required*

The title of the form.

**fields** `string[]` *required*

The names of its fields, in order.

**customId** `string` *required*

The id the framework wrote in it.

## TestSuggestion

A suggestion the bot gave while someone typed in an option.

**name** `string` *required*

What the person sees.

**value** `string | number` *required*

What the bot receives.

## Types

* `OptionInputsOf<Options>`: the options of a command as a test fills them in. A required option can not be left out.
* `FieldInputsOf<Fields>`: the fields of a form as a test fills them in: a field left out stays empty.
* `SelectInput`: what a test picks in a menu: texts, or people, roles and channels.
* `TestMenuTarget<Type>`: what a menu command is used on in a test: `{ on }`, a `TestMember` for a `'user'` menu, a `TestMessage` for a `'message'` menu.
* `ButtonTarget`: a button, as a test names it: the declaration, an instance, or one found on a message (`reply.button('Confirm')`).
* `SelectTarget`: a menu, the same way (`reply.menu()`).
* `FormTarget<Fields>`: a form, the same way (`reply.form`).
* `TestChannelType`: `'text' | 'announcement' | 'voice' | 'category' | 'forum'`.
* `TestFailure`: an expectation of a test that the bot did not meet. What every check throws, with a plain sentence: the report shows it with the line of the scenario.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.