Skip to content

Commit

Permalink
Basic /help functionality (#23)
Browse files Browse the repository at this point in the history
* Use single quotes in generated version.ts file

* Update README

* Basic `/help` functionality

* Update unit tests

* Add tests around guild-only commands
  • Loading branch information
AverageHelper committed Sep 29, 2022
1 parent 2213b76 commit efc1151
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 10 deletions.
7 changes: 5 additions & 2 deletions README.md
Expand Up @@ -76,10 +76,11 @@ Go to https://discordapi.com/permissions.html#378091424832 and paste in your bot

### Build the bot server

Be sure to install dependencies:
Be sure to install dependencies, and run a quick lint to generate needed files:

```sh
$ npm ci
$ npm run lint
```

The first time you download the source, and each time the source code changes, you'll need to run this command before you run the bot:
Expand Down Expand Up @@ -112,7 +113,9 @@ $ pm2 start .

## Commands

\[TBD\]
### /help

Prints the list of commands.

## Contributing

Expand Down
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -19,7 +19,7 @@
"prebuild": "npm run lint",
"build": "npm run build:only",
"build:only": "rm -rf dist && ./node_modules/.bin/tsc -p tsconfig.prod.json",
"export-version": "./node_modules/.bin/genversion ./src/version.ts -esd",
"export-version": "./node_modules/.bin/genversion ./src/version.ts -es",
"test": "./node_modules/.bin/jest",
"test:watch": "npm run test -- --watch --coverage=false"
},
Expand Down
25 changes: 25 additions & 0 deletions src/commands/__snapshots__/help.test.ts.snap
@@ -0,0 +1,25 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`help presents an ephemeral embed with all available global and guild-bound commands 1`] = `
Object {
"fields": Array [
Object {
"name": "\`/help\`",
"value": "Prints the list of commands",
},
],
"title": "All commands",
}
`;

exports[`help presents an ephemeral embed with all available global commands 1`] = `
Object {
"fields": Array [
Object {
"name": "\`/help\`",
"value": "Prints the list of commands",
},
],
"title": "All commands",
}
`;
73 changes: 69 additions & 4 deletions src/commands/help.test.ts
@@ -1,15 +1,80 @@
import type { EmbedBuilder } from 'discord.js';
import { help } from './help';

const { allCommands: realAllCommands } = jest.requireActual<typeof import('./index')>('./index');
const mockAllCommands = new Map<string, Command>();

jest.mock('./index', () => ({
allCommands: mockAllCommands,
}));

describe('help', () => {
test("it's just a Hello World", async () => {
const mockReply = jest.fn();
const context = {
const mockReply = jest.fn();
let context: CommandContext;

beforeEach(() => {
mockAllCommands.clear();
realAllCommands.forEach((value, key) => mockAllCommands.set(key, value));

context = {
guild: null,
reply: mockReply,
} as unknown as CommandContext;
});

test('presents an ephemeral embed with all available global commands', async () => {
context = { ...context, guild: null };

await expect(help.execute(context)).resolves.toBeUndefined();
expect(mockReply).toHaveBeenCalledOnce();
expect(mockReply).toHaveBeenCalledWith({
embeds: [expect.toBeObject()],
ephemeral: true,
});
const call = mockReply.mock.calls[0] as [{ embeds: [EmbedBuilder] }];
const embed = call[0].embeds[0];
expect(embed.data.fields).toBeArrayOfSize(mockAllCommands.size);
expect(embed.data).toMatchSnapshot();
});

test('presents an ephemeral embed with all available global and guild-bound commands', async () => {
context = {
...context,
guild: { id: 'the-guild-1234' },
} as unknown as CommandContext;

await expect(help.execute(context)).resolves.toBeUndefined();
expect(mockReply).toHaveBeenCalledOnce();
expect(mockReply).toHaveBeenCalledWith({
embeds: [expect.toBeObject()],
ephemeral: true,
});
const call = mockReply.mock.calls[0] as [{ embeds: [EmbedBuilder] }];
const embed = call[0].embeds[0];
expect(embed.data.fields).toBeArrayOfSize(mockAllCommands.size);
expect(embed.data).toMatchSnapshot();
});

test("doesn't show a guild-only command when in DMs", async () => {
const cmd: GuildedCommand = {
name: 'canttouchthis-do-do-do-do', // this test will break if we ever have a command with this name
description: "Can't touch this. (This is a test.)",
requiresGuild: true,
dmPermission: false,
execute() {
// nop
},
};
mockAllCommands.set(cmd.name, cmd);

await expect(help.execute(context)).resolves.toBeUndefined();
expect(mockReply).toHaveBeenCalledOnce();
expect(mockReply).toHaveBeenCalledWith('Hello, world!');
expect(mockReply).toHaveBeenCalledWith({
embeds: [expect.toBeObject()],
ephemeral: true,
});
const call = mockReply.mock.calls[0] as [{ embeds: [EmbedBuilder] }];
const embed = call[0].embeds[0];
expect(embed.data.fields).toBeArrayOfSize(mockAllCommands.size - 1);
});
});
40 changes: 37 additions & 3 deletions src/commands/help.ts
@@ -1,9 +1,43 @@
import { EmbedBuilder } from 'discord.js';

export const help: GlobalCommand = {
name: 'help',
description: 'Prints the list of commands',
requiresGuild: false,
async execute({ reply }) {
// TODO: finish this command
await reply('Hello, world!');
async execute({ guild, reply }) {
// Dynamic import here b/c ./index depends on this file
const { allCommands } = await import('./index');

const embed = new EmbedBuilder() //
.setTitle('All commands');

function embedCommand(command: Command): void {
embed.addFields({
name: `\`/${command.name}\``, // i.e. `/help`
value: command.description,
});
}

for (const command of allCommands.values()) {
if (guild) {
// We're in a guild. We should check the user's permissions,
// and skip this command if they aren't allowed to use it.
// TODO: Grab the guild's configured permissions for this channel
embedCommand(command);
continue;
}

// We're in DMs; command should have dmPermission if we're to print it.
// Discord defaults dmPermission to `true`, so `undefined` should behave that way.
// See https://discordjs.guide/interactions/slash-commands.html#dm-permission
if (command.dmPermission === false) continue;

embedCommand(command);
}

await reply({
embeds: [embed],
ephemeral: true,
});
},
};

0 comments on commit efc1151

Please sign in to comment.