v3.1.0 #317
7 changed files with 1036 additions and 9 deletions
151
src/commands/timeout.ts
Normal file
151
src/commands/timeout.ts
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
import { CacheType, CommandInteraction, EmbedBuilder, GuildMember, PermissionsBitField, SlashCommandBuilder, TextChannel } from "discord.js";
|
||||||
|
import { AuditType } from "../constants/AuditType";
|
||||||
|
import EmbedColours from "../constants/EmbedColours";
|
||||||
|
import Audit from "../database/entities/Audit";
|
||||||
|
import SettingsHelper from "../helpers/SettingsHelper";
|
||||||
|
import TimeLengthInput from "../helpers/TimeLengthInput";
|
||||||
|
import { Command } from "../type/command";
|
||||||
|
|
||||||
|
export default class Timeout extends Command {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
super.CommandBuilder = new SlashCommandBuilder()
|
||||||
|
.setName("timeout")
|
||||||
|
.setDescription("Timeouts a user out, sending them a DM with the reason if possible")
|
||||||
|
.setDefaultMemberPermissions(PermissionsBitField.Flags.ModerateMembers)
|
||||||
|
.addUserOption(option =>
|
||||||
|
option
|
||||||
|
.setName('target')
|
||||||
|
.setDescription('The user')
|
||||||
|
.setRequired(true))
|
||||||
|
.addStringOption(option =>
|
||||||
|
option
|
||||||
|
.setName("length")
|
||||||
|
.setDescription("How long to timeout for? (Example: 24h, 60m)")
|
||||||
|
.setRequired(true))
|
||||||
|
.addStringOption(option =>
|
||||||
|
option
|
||||||
|
.setName('reason')
|
||||||
|
.setDescription('The reason'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async execute(interaction: CommandInteraction<CacheType>) {
|
||||||
|
if (!interaction.guild || !interaction.guildId) return;
|
||||||
|
|
||||||
|
// Interaction Inputs
|
||||||
|
const targetUser = interaction.options.get('target');
|
||||||
|
const lengthInput = interaction.options.get('length');
|
||||||
|
const reasonInput = interaction.options.get('reason');
|
||||||
|
|
||||||
|
// Validation
|
||||||
|
if (!targetUser || !targetUser.user || !targetUser.member) {
|
||||||
|
await interaction.reply('Fields are required.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!lengthInput || !lengthInput.value) {
|
||||||
|
await interaction.reply('Fields are required.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// General Variables
|
||||||
|
const targetMember = targetUser.member as GuildMember;
|
||||||
|
const reason = reasonInput && reasonInput.value ? reasonInput.value.toString() : null;
|
||||||
|
|
||||||
|
const timeLength = new TimeLengthInput(lengthInput.value.toString());
|
||||||
|
|
||||||
|
const logEmbed = new EmbedBuilder()
|
||||||
|
.setColor(EmbedColours.Ok)
|
||||||
|
.setTitle("Member Timed Out")
|
||||||
|
.setDescription(`<@${targetUser.user.id}> \`${targetUser.user.tag}\``)
|
||||||
|
.addFields([
|
||||||
|
{
|
||||||
|
name: "Moderator",
|
||||||
|
value: `<@${interaction.user.id}>`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Reason",
|
||||||
|
value: reason || "*none*",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Length",
|
||||||
|
value: timeLength.GetLengthShort(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Until",
|
||||||
|
value: timeLength.GetDateFromNow().toString(),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Bot Permissions Check
|
||||||
|
if (!targetMember.manageable) {
|
||||||
|
await interaction.reply('Insufficient bot permissions. Please contact a moderator.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute Timeout
|
||||||
|
await targetMember.timeout(timeLength.GetMilliseconds(), reason || "");
|
||||||
|
|
||||||
|
// Log Embed To Channel
|
||||||
|
const channelName = await SettingsHelper.GetSetting('channels.logs.mod', interaction.guildId);
|
||||||
|
|
||||||
|
if (!channelName) return;
|
||||||
|
|
||||||
|
const channel = interaction.guild.channels.cache.find(x => x.name == channelName) as TextChannel;
|
||||||
|
|
||||||
|
if (channel) {
|
||||||
|
await channel.send({ embeds: [ logEmbed ]});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create Audit
|
||||||
|
const audit = new Audit(targetUser.user.id, AuditType.Timeout, reason || "*none*", interaction.user.id, interaction.guildId);
|
||||||
|
await audit.Save(Audit, audit);
|
||||||
|
|
||||||
|
// DM User, if possible
|
||||||
|
const resultEmbed = new EmbedBuilder()
|
||||||
|
.setColor(EmbedColours.Ok)
|
||||||
|
.setDescription(`<@${targetUser.user.id}> has been timed out`);
|
||||||
|
|
||||||
|
const dmEmbed = new EmbedBuilder()
|
||||||
|
.setColor(EmbedColours.Ok)
|
||||||
|
.setDescription(`You have been timed out in ${interaction.guild.name}`)
|
||||||
|
.addFields([
|
||||||
|
{
|
||||||
|
name: "Reason",
|
||||||
|
value: reason || "*none*"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Length",
|
||||||
|
value: timeLength.GetLengthShort(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Until",
|
||||||
|
value: timeLength.GetDateFromNow().toString(),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const dmChannel = await targetUser.user.createDM();
|
||||||
|
|
||||||
|
await dmChannel.send({ embeds: [ dmEmbed ]});
|
||||||
|
|
||||||
|
resultEmbed.addFields([
|
||||||
|
{
|
||||||
|
name: "DM Sent",
|
||||||
|
value: "true",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
} catch {
|
||||||
|
resultEmbed.addFields([
|
||||||
|
{
|
||||||
|
name: "DM Sent",
|
||||||
|
value: "false",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Success Reply
|
||||||
|
await interaction.reply({ embeds: [ resultEmbed ]});
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,4 +4,5 @@ export enum AuditType {
|
||||||
Mute,
|
Mute,
|
||||||
Kick,
|
Kick,
|
||||||
Ban,
|
Ban,
|
||||||
|
Timeout,
|
||||||
}
|
}
|
|
@ -13,6 +13,8 @@ export default class AuditTools {
|
||||||
return "Kick";
|
return "Kick";
|
||||||
case AuditType.Ban:
|
case AuditType.Ban:
|
||||||
return "Ban";
|
return "Ban";
|
||||||
|
case AuditType.Timeout:
|
||||||
|
return "Timeout";
|
||||||
default:
|
default:
|
||||||
return "Other";
|
return "Other";
|
||||||
}
|
}
|
||||||
|
@ -30,6 +32,8 @@ export default class AuditTools {
|
||||||
return AuditType.Kick;
|
return AuditType.Kick;
|
||||||
case "ban":
|
case "ban":
|
||||||
return AuditType.Ban;
|
return AuditType.Ban;
|
||||||
|
case "timeout":
|
||||||
|
return AuditType.Timeout;
|
||||||
default:
|
default:
|
||||||
return AuditType.General;
|
return AuditType.General;
|
||||||
}
|
}
|
||||||
|
|
119
src/helpers/TimeLengthInput.ts
Normal file
119
src/helpers/TimeLengthInput.ts
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
export default class TimeLengthInput {
|
||||||
|
public readonly value: string;
|
||||||
|
|
||||||
|
constructor(input: string) {
|
||||||
|
this.value = input;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GetDays(): number {
|
||||||
|
return this.GetValue('d');
|
||||||
|
}
|
||||||
|
|
||||||
|
public GetHours(): number {
|
||||||
|
return this.GetValue('h');
|
||||||
|
}
|
||||||
|
|
||||||
|
public GetMinutes(): number {
|
||||||
|
return this.GetValue('m');
|
||||||
|
}
|
||||||
|
|
||||||
|
public GetSeconds(): number {
|
||||||
|
return this.GetValue('s');
|
||||||
|
}
|
||||||
|
|
||||||
|
public GetMilliseconds(): number {
|
||||||
|
const days = this.GetDays();
|
||||||
|
const hours = this.GetHours();
|
||||||
|
const minutes = this.GetMinutes();
|
||||||
|
const seconds = this.GetSeconds();
|
||||||
|
|
||||||
|
let milliseconds = 0;
|
||||||
|
|
||||||
|
milliseconds += seconds * 1000;
|
||||||
|
milliseconds += minutes * 60 * 1000;
|
||||||
|
milliseconds += hours * 60 * 60 * 1000;
|
||||||
|
milliseconds += days * 24 * 60 * 60 * 1000;
|
||||||
|
|
||||||
|
return milliseconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GetDateFromNow(): Date {
|
||||||
|
const now = Date.now();
|
||||||
|
|
||||||
|
const dateFromNow = now
|
||||||
|
+ (1000 * this.GetSeconds())
|
||||||
|
+ (1000 * 60 * this.GetMinutes())
|
||||||
|
+ (1000 * 60 * 60 * this.GetHours())
|
||||||
|
+ (1000 * 60 * 60 * 24 * this.GetDays());
|
||||||
|
|
||||||
|
return new Date(dateFromNow);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GetLength(): string {
|
||||||
|
const days = this.GetDays();
|
||||||
|
const hours = this.GetHours();
|
||||||
|
const minutes = this.GetMinutes();
|
||||||
|
const seconds = this.GetSeconds();
|
||||||
|
|
||||||
|
const value = [];
|
||||||
|
|
||||||
|
if (days) {
|
||||||
|
value.push(`${days} days`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hours) {
|
||||||
|
value.push(`${hours} hours`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (minutes) {
|
||||||
|
value.push(`${minutes} minutes`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (seconds) {
|
||||||
|
value.push(`${seconds} seconds`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value.join(", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
public GetLengthShort(): string {
|
||||||
|
const days = this.GetDays();
|
||||||
|
const hours = this.GetHours();
|
||||||
|
const minutes = this.GetMinutes();
|
||||||
|
const seconds = this.GetSeconds();
|
||||||
|
|
||||||
|
const value = [];
|
||||||
|
|
||||||
|
if (days) {
|
||||||
|
value.push(`${days}d`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hours) {
|
||||||
|
value.push(`${hours}h`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (minutes) {
|
||||||
|
value.push(`${minutes}m`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (seconds) {
|
||||||
|
value.push(`${seconds}s`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value.join(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
private GetValue(designation: string): number {
|
||||||
|
const valueSplit = this.value.split(' ');
|
||||||
|
|
||||||
|
const desString = valueSplit.find(x => x.charAt(x.length - 1) == designation);
|
||||||
|
|
||||||
|
if (!desString) return 0;
|
||||||
|
|
||||||
|
const desNumber = Number(desString.substring(0, desString.length - 1));
|
||||||
|
|
||||||
|
if (!desNumber) return 0;
|
||||||
|
|
||||||
|
return desNumber;
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,6 +17,7 @@ import Role from "./commands/Role/role";
|
||||||
import ConfigRole from "./commands/Role/config";
|
import ConfigRole from "./commands/Role/config";
|
||||||
import Rules from "./commands/rules";
|
import Rules from "./commands/rules";
|
||||||
import Setup from "./commands/setup";
|
import Setup from "./commands/setup";
|
||||||
|
import Timeout from "./commands/timeout";
|
||||||
import Unmute from "./commands/unmute";
|
import Unmute from "./commands/unmute";
|
||||||
import Warn from "./commands/warn";
|
import Warn from "./commands/warn";
|
||||||
|
|
||||||
|
@ -38,6 +39,7 @@ import MessageCreate from "./events/MessageEvents/MessageCreate";
|
||||||
export default class Registry {
|
export default class Registry {
|
||||||
public static RegisterCommands() {
|
public static RegisterCommands() {
|
||||||
CoreClient.RegisterCommand("about", new About());
|
CoreClient.RegisterCommand("about", new About());
|
||||||
|
CoreClient.RegisterCommand("audits", new Audits());
|
||||||
CoreClient.RegisterCommand("ban", new Ban());
|
CoreClient.RegisterCommand("ban", new Ban());
|
||||||
CoreClient.RegisterCommand("bunny", new Bunny());
|
CoreClient.RegisterCommand("bunny", new Bunny());
|
||||||
CoreClient.RegisterCommand("clear", new Clear());
|
CoreClient.RegisterCommand("clear", new Clear());
|
||||||
|
@ -48,10 +50,10 @@ export default class Registry {
|
||||||
CoreClient.RegisterCommand("kick", new Kick());
|
CoreClient.RegisterCommand("kick", new Kick());
|
||||||
CoreClient.RegisterCommand("mute", new Mute());
|
CoreClient.RegisterCommand("mute", new Mute());
|
||||||
CoreClient.RegisterCommand("rules", new Rules());
|
CoreClient.RegisterCommand("rules", new Rules());
|
||||||
|
CoreClient.RegisterCommand("setup", new Setup());
|
||||||
|
CoreClient.RegisterCommand("timeout", new Timeout());
|
||||||
CoreClient.RegisterCommand("unmute", new Unmute());
|
CoreClient.RegisterCommand("unmute", new Unmute());
|
||||||
CoreClient.RegisterCommand("warn", new Warn());
|
CoreClient.RegisterCommand("warn", new Warn());
|
||||||
CoreClient.RegisterCommand("setup", new Setup());
|
|
||||||
CoreClient.RegisterCommand("audits", new Audits());
|
|
||||||
|
|
||||||
CoreClient.RegisterCommand("role", new Role());
|
CoreClient.RegisterCommand("role", new Role());
|
||||||
CoreClient.RegisterCommand("configrole", new ConfigRole());
|
CoreClient.RegisterCommand("configrole", new ConfigRole());
|
||||||
|
|
729
tests/commands/timeout.test.ts
Normal file
729
tests/commands/timeout.test.ts
Normal file
|
@ -0,0 +1,729 @@
|
||||||
|
import { APIEmbed, CacheType, CommandInteraction, CommandInteractionOption, DMChannel, Embed, EmbedBuilder, EmbedField, Guild, GuildChannel, GuildMember, InteractionReplyOptions, JSONEncodable, Message, MessageCreateOptions, MessagePayload, SlashCommandBuilder, TextChannel, User } from "discord.js";
|
||||||
|
import { mock } from "jest-mock-extended";
|
||||||
|
import Timeout from "../../src/commands/timeout";
|
||||||
|
import SettingsHelper from "../../src/helpers/SettingsHelper";
|
||||||
|
import Audit from "../../src/database/entities/Audit";
|
||||||
|
import EmbedColours from "../../src/constants/EmbedColours";
|
||||||
|
import { DeepPartial, EntityTarget } from "typeorm";
|
||||||
|
import BaseEntity from "../../src/contracts/BaseEntity";
|
||||||
|
import { AuditType } from "../../src/constants/AuditType";
|
||||||
|
|
||||||
|
describe('Constructor', () => {
|
||||||
|
test('EXPECT CommandBuilder to be configured', () => {
|
||||||
|
const command = new Timeout();
|
||||||
|
|
||||||
|
expect(command.CommandBuilder).toBeDefined();
|
||||||
|
|
||||||
|
const commandBuilder = command.CommandBuilder as SlashCommandBuilder;
|
||||||
|
|
||||||
|
expect(commandBuilder.name).toBe("timeout");
|
||||||
|
expect(commandBuilder.description).toBe("Timeouts a user out, sending them a DM with the reason if possible");
|
||||||
|
expect(commandBuilder.options.length).toBe(3);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('execute', () => {
|
||||||
|
// Happy flow
|
||||||
|
test('GIVEN all checks have passed, EXPECT user to be timed out', async () => {
|
||||||
|
let embeds: APIEmbed[] | undefined;
|
||||||
|
|
||||||
|
const command = new Timeout();
|
||||||
|
|
||||||
|
const interactionReply = jest.fn((options: InteractionReplyOptions) => {
|
||||||
|
embeds = options.embeds as APIEmbed[];
|
||||||
|
});
|
||||||
|
|
||||||
|
let savedAudit: DeepPartial<Audit> | undefined;
|
||||||
|
|
||||||
|
const getSetting = jest.spyOn(SettingsHelper, 'GetSetting').mockResolvedValue('mod-logs');
|
||||||
|
const auditSave = jest.spyOn(Audit.prototype, 'Save').mockImplementation((target: EntityTarget<BaseEntity>, entity: DeepPartial<BaseEntity>): Promise<void> => {
|
||||||
|
savedAudit = entity;
|
||||||
|
|
||||||
|
return Promise.resolve();
|
||||||
|
});
|
||||||
|
|
||||||
|
const timeoutFunc = jest.fn();
|
||||||
|
|
||||||
|
let dmChannelSentEmbeds: (APIEmbed | JSONEncodable<APIEmbed>)[] | undefined;
|
||||||
|
let logsChannelSentEmbeds: (APIEmbed | JSONEncodable<APIEmbed>)[] | undefined;
|
||||||
|
|
||||||
|
const dmChannel = {
|
||||||
|
send: jest.fn().mockImplementation((options: MessageCreateOptions) => {
|
||||||
|
dmChannelSentEmbeds = options.embeds;
|
||||||
|
}),
|
||||||
|
} as unknown as DMChannel;
|
||||||
|
|
||||||
|
const userInput = {
|
||||||
|
user: {
|
||||||
|
id: 'userId',
|
||||||
|
tag: 'userTag',
|
||||||
|
createDM: jest.fn().mockResolvedValue(dmChannel),
|
||||||
|
} as unknown as User,
|
||||||
|
member: {
|
||||||
|
manageable: true,
|
||||||
|
timeout: timeoutFunc,
|
||||||
|
} as unknown as GuildMember,
|
||||||
|
} as CommandInteractionOption;
|
||||||
|
|
||||||
|
const lengthInput = {
|
||||||
|
value: '1s',
|
||||||
|
} as CommandInteractionOption;
|
||||||
|
|
||||||
|
const reasonInput = {
|
||||||
|
value: 'Test reason',
|
||||||
|
} as CommandInteractionOption;
|
||||||
|
|
||||||
|
const logsChannel = {
|
||||||
|
name: 'mod-logs',
|
||||||
|
send: jest.fn().mockImplementation((options: MessageCreateOptions) => {
|
||||||
|
logsChannelSentEmbeds = options.embeds;
|
||||||
|
}),
|
||||||
|
} as unknown as TextChannel;
|
||||||
|
|
||||||
|
const interaction = {
|
||||||
|
guild: {
|
||||||
|
channels: {
|
||||||
|
cache: {
|
||||||
|
find: jest.fn()
|
||||||
|
.mockReturnValue(logsChannel),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
name: "Test Guild",
|
||||||
|
} as unknown as Guild,
|
||||||
|
guildId: 'guildId',
|
||||||
|
reply: interactionReply,
|
||||||
|
options: {
|
||||||
|
get: jest.fn()
|
||||||
|
.mockReturnValueOnce(userInput)
|
||||||
|
.mockReturnValueOnce(lengthInput)
|
||||||
|
.mockReturnValue(reasonInput),
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
id: 'moderatorId'
|
||||||
|
}
|
||||||
|
} as unknown as CommandInteraction;
|
||||||
|
|
||||||
|
await command.execute(interaction);
|
||||||
|
|
||||||
|
// EXPECT user to be timed out
|
||||||
|
expect(timeoutFunc).toBeCalledWith(1000, 'Test reason');
|
||||||
|
|
||||||
|
// EXPECT embeds to be sent
|
||||||
|
expect(embeds).toBeDefined();
|
||||||
|
expect(embeds!.length).toBe(1);
|
||||||
|
|
||||||
|
// EXPECT resultEmbed to be correctly configured
|
||||||
|
const resultEmbed = embeds![0] as EmbedBuilder;
|
||||||
|
|
||||||
|
expect(resultEmbed.data.description).toBe('<@userId> has been timed out');
|
||||||
|
expect(resultEmbed.data.fields).toBeDefined();
|
||||||
|
expect(resultEmbed.data.fields!.length).toBe(1);
|
||||||
|
|
||||||
|
// EXPECT DM field to be configured
|
||||||
|
const resultEmbedDMField = resultEmbed.data.fields![0];
|
||||||
|
|
||||||
|
expect(resultEmbedDMField.name).toBe("DM Sent");
|
||||||
|
expect(resultEmbedDMField.value).toBe("true");
|
||||||
|
|
||||||
|
// EXPECT user to be DM's with embed
|
||||||
|
expect(dmChannel.send).toBeCalled();
|
||||||
|
expect(dmChannelSentEmbeds).toBeDefined();
|
||||||
|
expect(dmChannelSentEmbeds?.length).toBe(1);
|
||||||
|
|
||||||
|
const dmChannelSentEmbed = (dmChannelSentEmbeds![0] as any).data;
|
||||||
|
|
||||||
|
expect(dmChannelSentEmbed.color).toBe(EmbedColours.Ok);
|
||||||
|
expect(dmChannelSentEmbed.description).toBe("You have been timed out in Test Guild");
|
||||||
|
expect(dmChannelSentEmbed.fields?.length).toBe(3);
|
||||||
|
|
||||||
|
expect(dmChannelSentEmbed.fields![0].name).toBe("Reason");
|
||||||
|
expect(dmChannelSentEmbed.fields![0].value).toBe("Test reason");
|
||||||
|
|
||||||
|
expect(dmChannelSentEmbed.fields![1].name).toBe("Length");
|
||||||
|
expect(dmChannelSentEmbed.fields![1].value).toBe("1s");
|
||||||
|
|
||||||
|
expect(dmChannelSentEmbed.fields![2].name).toBe("Until");
|
||||||
|
expect(dmChannelSentEmbed.fields![2].value).toBeDefined();
|
||||||
|
|
||||||
|
// EXPECT log embed to be sent
|
||||||
|
expect(logsChannel.send).toBeCalled();
|
||||||
|
expect(logsChannelSentEmbeds).toBeDefined();
|
||||||
|
expect(logsChannelSentEmbeds?.length).toBe(1);
|
||||||
|
|
||||||
|
const logsChannelSentEmbed = (logsChannelSentEmbeds![0] as any).data;
|
||||||
|
|
||||||
|
expect(logsChannelSentEmbed.color).toBe(EmbedColours.Ok);
|
||||||
|
expect(logsChannelSentEmbed.title).toBe("Member Timed Out");
|
||||||
|
expect(logsChannelSentEmbed.description).toBe("<@userId> `userTag`");
|
||||||
|
expect(logsChannelSentEmbed.fields?.length).toBe(4);
|
||||||
|
|
||||||
|
expect(logsChannelSentEmbed.fields![0].name).toBe("Moderator");
|
||||||
|
expect(logsChannelSentEmbed.fields![0].value).toBe("<@moderatorId>");
|
||||||
|
|
||||||
|
expect(logsChannelSentEmbed.fields![1].name).toBe("Reason");
|
||||||
|
expect(logsChannelSentEmbed.fields![1].value).toBe("Test reason");
|
||||||
|
|
||||||
|
expect(logsChannelSentEmbed.fields![2].name).toBe("Length");
|
||||||
|
expect(logsChannelSentEmbed.fields![2].value).toBe("1s");
|
||||||
|
|
||||||
|
expect(logsChannelSentEmbed.fields![3].name).toBe("Until");
|
||||||
|
expect(logsChannelSentEmbed.fields![3].value).toBeDefined();
|
||||||
|
|
||||||
|
// EXPECT Audit to be saved
|
||||||
|
expect(auditSave).toBeCalled();
|
||||||
|
|
||||||
|
expect(savedAudit).toBeDefined();
|
||||||
|
expect(savedAudit?.UserId).toBe('userId');
|
||||||
|
expect(savedAudit?.AuditType).toBe(AuditType.Timeout);
|
||||||
|
expect(savedAudit?.Reason).toBe("Test reason");
|
||||||
|
expect(savedAudit?.ModeratorId).toBe('moderatorId');
|
||||||
|
expect(savedAudit?.ServerId).toBe('guildId');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Null checks
|
||||||
|
test('GIVEN interaction.guild IS NULL, EXPECT nothing to happen', async () => {
|
||||||
|
const command = new Timeout();
|
||||||
|
|
||||||
|
const interaction = {
|
||||||
|
guild: null,
|
||||||
|
reply: jest.fn(),
|
||||||
|
} as unknown as CommandInteraction;
|
||||||
|
|
||||||
|
await command.execute(interaction);
|
||||||
|
|
||||||
|
expect(interaction.reply).not.toBeCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GIVEN interaction.guildId IS NULL, EXPECT nothing to happen', async () => {
|
||||||
|
const command = new Timeout();
|
||||||
|
|
||||||
|
const interaction = {
|
||||||
|
guild: mock<Guild>(),
|
||||||
|
guildId: null,
|
||||||
|
reply: jest.fn(),
|
||||||
|
} as unknown as CommandInteraction;
|
||||||
|
|
||||||
|
await command.execute(interaction);
|
||||||
|
|
||||||
|
expect(interaction.reply).not.toBeCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Validation
|
||||||
|
test('GIVEN targetUser IS NULL, EXPECT validation error', async () => {
|
||||||
|
const command = new Timeout();
|
||||||
|
|
||||||
|
const interaction = {
|
||||||
|
reply: jest.fn(),
|
||||||
|
guild: mock<Guild>(),
|
||||||
|
guildId: 'guildId',
|
||||||
|
options: {
|
||||||
|
get: jest.fn().mockReturnValue(undefined),
|
||||||
|
}
|
||||||
|
} as unknown as CommandInteraction;
|
||||||
|
|
||||||
|
await command.execute(interaction);
|
||||||
|
|
||||||
|
expect(interaction.reply).toBeCalledWith('Fields are required.');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GIVEN targetUser.user IS NULL, EXPECT validation error', async () => {
|
||||||
|
const command = new Timeout();
|
||||||
|
|
||||||
|
const interaction = {
|
||||||
|
reply: jest.fn(),
|
||||||
|
guild: mock<Guild>(),
|
||||||
|
guildId: 'guildId',
|
||||||
|
options: {
|
||||||
|
get: jest.fn((value: string): CommandInteractionOption<CacheType> | null => {
|
||||||
|
switch (value) {
|
||||||
|
case 'target':
|
||||||
|
return {} as CommandInteractionOption;
|
||||||
|
case 'length':
|
||||||
|
return {
|
||||||
|
value: '1m',
|
||||||
|
} as CommandInteractionOption;
|
||||||
|
case 'reason':
|
||||||
|
return {
|
||||||
|
value: 'Test reason',
|
||||||
|
} as CommandInteractionOption;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
} as unknown as CommandInteraction;
|
||||||
|
|
||||||
|
await command.execute(interaction);
|
||||||
|
|
||||||
|
expect(interaction.reply).toBeCalledWith('Fields are required.');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GIVEN targetUser.member IS NULL, EXPECT validation error', async () => {
|
||||||
|
const command = new Timeout();
|
||||||
|
|
||||||
|
const interaction = {
|
||||||
|
reply: jest.fn(),
|
||||||
|
guild: mock<Guild>(),
|
||||||
|
guildId: 'guildId',
|
||||||
|
options: {
|
||||||
|
get: jest.fn((value: string): CommandInteractionOption<CacheType> | null => {
|
||||||
|
switch (value) {
|
||||||
|
case 'target':
|
||||||
|
return {
|
||||||
|
user: {} as User,
|
||||||
|
} as CommandInteractionOption;
|
||||||
|
case 'length':
|
||||||
|
return {
|
||||||
|
value: '1m',
|
||||||
|
} as CommandInteractionOption;
|
||||||
|
case 'reason':
|
||||||
|
return {
|
||||||
|
value: 'Test reason',
|
||||||
|
} as CommandInteractionOption;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
} as unknown as CommandInteraction;
|
||||||
|
|
||||||
|
await command.execute(interaction);
|
||||||
|
|
||||||
|
expect(interaction.reply).toBeCalledWith('Fields are required.');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GIVEN lengthInput IS NULL, EXPECT validation error', async () => {
|
||||||
|
const command = new Timeout();
|
||||||
|
|
||||||
|
const interaction = {
|
||||||
|
reply: jest.fn(),
|
||||||
|
guild: mock<Guild>(),
|
||||||
|
guildId: 'guildId',
|
||||||
|
options: {
|
||||||
|
get: jest.fn((value: string): CommandInteractionOption<CacheType> | null => {
|
||||||
|
switch (value) {
|
||||||
|
case 'target':
|
||||||
|
return {
|
||||||
|
user: {} as User,
|
||||||
|
member: {} as GuildMember
|
||||||
|
} as CommandInteractionOption;
|
||||||
|
case 'length':
|
||||||
|
return null;
|
||||||
|
case 'reason':
|
||||||
|
return {
|
||||||
|
value: 'Test reason',
|
||||||
|
} as CommandInteractionOption;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
} as unknown as CommandInteraction;
|
||||||
|
|
||||||
|
await command.execute(interaction);
|
||||||
|
|
||||||
|
expect(interaction.reply).toBeCalledWith('Fields are required.');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GIVEN lengthInput.value IS NULL, EXPECT validation error', async () => {
|
||||||
|
const command = new Timeout();
|
||||||
|
|
||||||
|
const interaction = {
|
||||||
|
reply: jest.fn(),
|
||||||
|
guild: mock<Guild>(),
|
||||||
|
guildId: 'guildId',
|
||||||
|
options: {
|
||||||
|
get: jest.fn((value: string): CommandInteractionOption<CacheType> | null => {
|
||||||
|
switch (value) {
|
||||||
|
case 'target':
|
||||||
|
return {
|
||||||
|
user: {} as User,
|
||||||
|
member: {} as GuildMember
|
||||||
|
} as CommandInteractionOption;
|
||||||
|
case 'length':
|
||||||
|
return {
|
||||||
|
value: undefined,
|
||||||
|
} as CommandInteractionOption;
|
||||||
|
case 'reason':
|
||||||
|
return {
|
||||||
|
value: 'Test reason',
|
||||||
|
} as CommandInteractionOption;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
} as unknown as CommandInteraction;
|
||||||
|
|
||||||
|
await command.execute(interaction);
|
||||||
|
|
||||||
|
expect(interaction.reply).toBeCalledWith('Fields are required.');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GIVEN targetMember IS NOT manageable by the bot, EXPECT insufficient permissions error', async () => {
|
||||||
|
const command = new Timeout();
|
||||||
|
|
||||||
|
const interaction = {
|
||||||
|
reply: jest.fn(),
|
||||||
|
guild: mock<Guild>(),
|
||||||
|
guildId: 'guildId',
|
||||||
|
user: {
|
||||||
|
id: 'moderatorId',
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
get: jest.fn((value: string): CommandInteractionOption<CacheType> | null => {
|
||||||
|
switch (value) {
|
||||||
|
case 'target':
|
||||||
|
return {
|
||||||
|
user: {
|
||||||
|
id: 'userId',
|
||||||
|
tag: 'userTag',
|
||||||
|
} as User,
|
||||||
|
member: {
|
||||||
|
manageable: false,
|
||||||
|
} as GuildMember
|
||||||
|
} as CommandInteractionOption;
|
||||||
|
case 'length':
|
||||||
|
return {
|
||||||
|
value: '1m',
|
||||||
|
} as CommandInteractionOption;
|
||||||
|
case 'reason':
|
||||||
|
return {
|
||||||
|
value: 'Test reason',
|
||||||
|
} as CommandInteractionOption;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
} as unknown as CommandInteraction;
|
||||||
|
|
||||||
|
await command.execute(interaction);
|
||||||
|
|
||||||
|
expect(interaction.reply).toBeCalledWith('Insufficient bot permissions. Please contact a moderator.');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Reason variable
|
||||||
|
test('GIVEN reason IS NULL, EXPECT to be ran with empty string', async () => {
|
||||||
|
const command = new Timeout();
|
||||||
|
|
||||||
|
let savedAudit: DeepPartial<Audit> | undefined;
|
||||||
|
|
||||||
|
const auditSave = jest.spyOn(Audit.prototype, 'Save').mockImplementation((target: EntityTarget<BaseEntity>, entity: DeepPartial<BaseEntity>): Promise<void> => {
|
||||||
|
savedAudit = entity;
|
||||||
|
|
||||||
|
return Promise.resolve();
|
||||||
|
});
|
||||||
|
|
||||||
|
const timeoutFunc = jest.fn();
|
||||||
|
|
||||||
|
const sentEmbeds: EmbedBuilder[] = [];
|
||||||
|
|
||||||
|
const interaction = {
|
||||||
|
reply: jest.fn(),
|
||||||
|
guild: {
|
||||||
|
channels: {
|
||||||
|
cache: {
|
||||||
|
find: jest.fn().mockReturnValue(mock<TextChannel>()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
guildId: 'guildId',
|
||||||
|
user: {
|
||||||
|
id: 'moderatorId',
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
get: jest.fn((value: string): CommandInteractionOption<CacheType> | null => {
|
||||||
|
switch (value) {
|
||||||
|
case 'target':
|
||||||
|
return {
|
||||||
|
user: {
|
||||||
|
id: 'userId',
|
||||||
|
tag: 'userTag',
|
||||||
|
createDM: jest.fn().mockReturnValue({
|
||||||
|
send: jest.fn(async (options: MessageCreateOptions): Promise<Message<false>> => {
|
||||||
|
sentEmbeds.push(options.embeds![0] as EmbedBuilder);
|
||||||
|
|
||||||
|
return mock<Message<false>>();
|
||||||
|
})
|
||||||
|
}) as unknown as DMChannel,
|
||||||
|
} as unknown as User,
|
||||||
|
member: {
|
||||||
|
manageable: true,
|
||||||
|
timeout: timeoutFunc,
|
||||||
|
} as unknown as GuildMember
|
||||||
|
} as CommandInteractionOption;
|
||||||
|
case 'length':
|
||||||
|
return {
|
||||||
|
value: '1m'
|
||||||
|
} as CommandInteractionOption;
|
||||||
|
case 'reason':
|
||||||
|
return {
|
||||||
|
value: undefined,
|
||||||
|
} as CommandInteractionOption;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
} as unknown as CommandInteraction;
|
||||||
|
|
||||||
|
|
||||||
|
await command.execute(interaction);
|
||||||
|
|
||||||
|
expect(timeoutFunc).toBeCalledWith(1000 * 60 * 1, "");
|
||||||
|
expect(savedAudit?.Reason).toBe("*none*");
|
||||||
|
|
||||||
|
const dmEmbed = (sentEmbeds[0] as any).data;
|
||||||
|
const dmEmbedReasonField = dmEmbed.fields![0] as EmbedField;
|
||||||
|
|
||||||
|
expect(dmEmbedReasonField.value).toBe("*none*");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Log embed
|
||||||
|
test('GIVEN channelName IS NULL, EXPECT execution to return', async () => {
|
||||||
|
const command = new Timeout();
|
||||||
|
|
||||||
|
let savedAudit: DeepPartial<Audit> | undefined;
|
||||||
|
|
||||||
|
const auditSave = jest.spyOn(Audit.prototype, 'Save').mockImplementation((target: EntityTarget<BaseEntity>, entity: DeepPartial<BaseEntity>): Promise<void> => {
|
||||||
|
savedAudit = entity;
|
||||||
|
|
||||||
|
return Promise.resolve();
|
||||||
|
});
|
||||||
|
|
||||||
|
const settingsGet = jest.spyOn(SettingsHelper, 'GetSetting').mockResolvedValue(undefined);
|
||||||
|
|
||||||
|
const timeoutFunc = jest.fn();
|
||||||
|
|
||||||
|
const sentEmbeds: EmbedBuilder[] = [];
|
||||||
|
|
||||||
|
const logChannelSendFunc = jest.fn();
|
||||||
|
|
||||||
|
const interaction = {
|
||||||
|
reply: jest.fn(),
|
||||||
|
guild: {
|
||||||
|
channels: {
|
||||||
|
cache: {
|
||||||
|
find: jest.fn().mockReturnValue({
|
||||||
|
send: logChannelSendFunc,
|
||||||
|
} as unknown as TextChannel),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
guildId: 'guildId',
|
||||||
|
user: {
|
||||||
|
id: 'moderatorId',
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
get: jest.fn((value: string): CommandInteractionOption<CacheType> | null => {
|
||||||
|
switch (value) {
|
||||||
|
case 'target':
|
||||||
|
return {
|
||||||
|
user: {
|
||||||
|
id: 'userId',
|
||||||
|
tag: 'userTag',
|
||||||
|
createDM: jest.fn().mockReturnValue({
|
||||||
|
send: jest.fn(async (options: MessageCreateOptions): Promise<Message<false>> => {
|
||||||
|
sentEmbeds.push(options.embeds![0] as EmbedBuilder);
|
||||||
|
|
||||||
|
return mock<Message<false>>();
|
||||||
|
})
|
||||||
|
}) as unknown as DMChannel,
|
||||||
|
} as unknown as User,
|
||||||
|
member: {
|
||||||
|
manageable: true,
|
||||||
|
timeout: timeoutFunc,
|
||||||
|
} as unknown as GuildMember
|
||||||
|
} as CommandInteractionOption;
|
||||||
|
case 'length':
|
||||||
|
return {
|
||||||
|
value: '1m'
|
||||||
|
} as CommandInteractionOption;
|
||||||
|
case 'reason':
|
||||||
|
return {
|
||||||
|
value: 'Test reason',
|
||||||
|
} as CommandInteractionOption;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
} as unknown as CommandInteraction;
|
||||||
|
|
||||||
|
|
||||||
|
await command.execute(interaction);
|
||||||
|
|
||||||
|
expect(timeoutFunc).toBeCalled();
|
||||||
|
expect(sentEmbeds.length).toBe(0);
|
||||||
|
expect(logChannelSendFunc).not.toBeCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GIVEN channel IS NULL, EXPECT embed to not be sent', async () => {
|
||||||
|
const command = new Timeout();
|
||||||
|
|
||||||
|
let savedAudit: DeepPartial<Audit> | undefined;
|
||||||
|
|
||||||
|
const auditSave = jest.spyOn(Audit.prototype, 'Save').mockImplementation((target: EntityTarget<BaseEntity>, entity: DeepPartial<BaseEntity>): Promise<void> => {
|
||||||
|
savedAudit = entity;
|
||||||
|
|
||||||
|
return Promise.resolve();
|
||||||
|
});
|
||||||
|
|
||||||
|
const settingsGet = jest.spyOn(SettingsHelper, 'GetSetting').mockResolvedValue('mod-logs');
|
||||||
|
|
||||||
|
const timeoutFunc = jest.fn();
|
||||||
|
|
||||||
|
const sentEmbeds: EmbedBuilder[] = [];
|
||||||
|
|
||||||
|
const interaction = {
|
||||||
|
reply: jest.fn(),
|
||||||
|
guild: {
|
||||||
|
channels: {
|
||||||
|
cache: {
|
||||||
|
find: jest.fn().mockReturnValue(undefined),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
guildId: 'guildId',
|
||||||
|
user: {
|
||||||
|
id: 'moderatorId',
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
get: jest.fn((value: string): CommandInteractionOption<CacheType> | null => {
|
||||||
|
switch (value) {
|
||||||
|
case 'target':
|
||||||
|
return {
|
||||||
|
user: {
|
||||||
|
id: 'userId',
|
||||||
|
tag: 'userTag',
|
||||||
|
createDM: jest.fn().mockReturnValue({
|
||||||
|
send: jest.fn(async (options: MessageCreateOptions): Promise<Message<false>> => {
|
||||||
|
sentEmbeds.push(options.embeds![0] as EmbedBuilder);
|
||||||
|
|
||||||
|
return mock<Message<false>>();
|
||||||
|
})
|
||||||
|
}) as unknown as DMChannel,
|
||||||
|
} as unknown as User,
|
||||||
|
member: {
|
||||||
|
manageable: true,
|
||||||
|
timeout: timeoutFunc,
|
||||||
|
} as unknown as GuildMember
|
||||||
|
} as CommandInteractionOption;
|
||||||
|
case 'length':
|
||||||
|
return {
|
||||||
|
value: '1m'
|
||||||
|
} as CommandInteractionOption;
|
||||||
|
case 'reason':
|
||||||
|
return {
|
||||||
|
value: 'Test reason',
|
||||||
|
} as CommandInteractionOption;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
} as unknown as CommandInteraction;
|
||||||
|
|
||||||
|
|
||||||
|
await command.execute(interaction);
|
||||||
|
|
||||||
|
expect(timeoutFunc).toBeCalled();
|
||||||
|
expect(sentEmbeds.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
// DM user
|
||||||
|
test('GIVEN user can NOT be messaged, EXPECT resultEmbed to contain "DM Sent = false"', async () => {
|
||||||
|
let embeds: APIEmbed[] | undefined;
|
||||||
|
|
||||||
|
const command = new Timeout();
|
||||||
|
|
||||||
|
const interactionReply = jest.fn((options: InteractionReplyOptions) => {
|
||||||
|
embeds = options.embeds as APIEmbed[];
|
||||||
|
});
|
||||||
|
|
||||||
|
let savedAudit: DeepPartial<Audit> | undefined;
|
||||||
|
|
||||||
|
const getSetting = jest.spyOn(SettingsHelper, 'GetSetting').mockResolvedValue('mod-logs');
|
||||||
|
const auditSave = jest.spyOn(Audit.prototype, 'Save').mockImplementation((target: EntityTarget<BaseEntity>, entity: DeepPartial<BaseEntity>): Promise<void> => {
|
||||||
|
savedAudit = entity;
|
||||||
|
|
||||||
|
return Promise.resolve();
|
||||||
|
});
|
||||||
|
|
||||||
|
const timeoutFunc = jest.fn();
|
||||||
|
|
||||||
|
let dmChannelSentEmbeds: (APIEmbed | JSONEncodable<APIEmbed>)[] | undefined;
|
||||||
|
let logsChannelSentEmbeds: (APIEmbed | JSONEncodable<APIEmbed>)[] | undefined;
|
||||||
|
|
||||||
|
const dmChannel = {
|
||||||
|
send: jest.fn().mockImplementation((options: MessageCreateOptions) => {
|
||||||
|
dmChannelSentEmbeds = options.embeds;
|
||||||
|
}),
|
||||||
|
} as unknown as DMChannel;
|
||||||
|
|
||||||
|
const userInput = {
|
||||||
|
user: {
|
||||||
|
id: 'userId',
|
||||||
|
tag: 'userTag',
|
||||||
|
createDM: jest.fn().mockRejectedValue(undefined),
|
||||||
|
} as unknown as User,
|
||||||
|
member: {
|
||||||
|
manageable: true,
|
||||||
|
timeout: timeoutFunc,
|
||||||
|
} as unknown as GuildMember,
|
||||||
|
} as CommandInteractionOption;
|
||||||
|
|
||||||
|
const lengthInput = {
|
||||||
|
value: '1s',
|
||||||
|
} as CommandInteractionOption;
|
||||||
|
|
||||||
|
const reasonInput = {
|
||||||
|
value: 'Test reason',
|
||||||
|
} as CommandInteractionOption;
|
||||||
|
|
||||||
|
const logsChannel = {
|
||||||
|
name: 'mod-logs',
|
||||||
|
send: jest.fn().mockImplementation((options: MessageCreateOptions) => {
|
||||||
|
logsChannelSentEmbeds = options.embeds;
|
||||||
|
}),
|
||||||
|
} as unknown as TextChannel;
|
||||||
|
|
||||||
|
const interaction = {
|
||||||
|
guild: {
|
||||||
|
channels: {
|
||||||
|
cache: {
|
||||||
|
find: jest.fn()
|
||||||
|
.mockReturnValue(logsChannel),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
name: "Test Guild",
|
||||||
|
} as unknown as Guild,
|
||||||
|
guildId: 'guildId',
|
||||||
|
reply: interactionReply,
|
||||||
|
options: {
|
||||||
|
get: jest.fn()
|
||||||
|
.mockReturnValueOnce(userInput)
|
||||||
|
.mockReturnValueOnce(lengthInput)
|
||||||
|
.mockReturnValue(reasonInput),
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
id: 'moderatorId'
|
||||||
|
}
|
||||||
|
} as unknown as CommandInteraction;
|
||||||
|
|
||||||
|
await command.execute(interaction);
|
||||||
|
|
||||||
|
// EXPECT embeds to be sent
|
||||||
|
expect(embeds).toBeDefined();
|
||||||
|
expect(embeds!.length).toBe(1);
|
||||||
|
|
||||||
|
const resultEmbed = embeds![0] as EmbedBuilder;
|
||||||
|
|
||||||
|
// EXPECT DM field to be configured
|
||||||
|
const resultEmbedDMField = resultEmbed.data.fields![0];
|
||||||
|
|
||||||
|
expect(resultEmbedDMField.name).toBe("DM Sent");
|
||||||
|
expect(resultEmbedDMField.value).toBe("false");
|
||||||
|
});
|
||||||
|
});
|
35
yarn.lock
35
yarn.lock
|
@ -124,7 +124,7 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/types" "^7.18.6"
|
"@babel/types" "^7.18.6"
|
||||||
|
|
||||||
"@babel/helper-string-parser@^7.21.5":
|
"@babel/helper-string-parser@^7.19.4", "@babel/helper-string-parser@^7.21.5":
|
||||||
version "7.21.5"
|
version "7.21.5"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.21.5.tgz#2b3eea65443c6bdc31c22d037c65f6d323b6b2bd"
|
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.21.5.tgz#2b3eea65443c6bdc31c22d037c65f6d323b6b2bd"
|
||||||
integrity sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w==
|
integrity sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w==
|
||||||
|
@ -157,11 +157,16 @@
|
||||||
chalk "^2.0.0"
|
chalk "^2.0.0"
|
||||||
js-tokens "^4.0.0"
|
js-tokens "^4.0.0"
|
||||||
|
|
||||||
"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.21.5", "@babel/parser@^7.21.8":
|
"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.21.5", "@babel/parser@^7.21.8":
|
||||||
version "7.21.8"
|
version "7.21.8"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.8.tgz#642af7d0333eab9c0ad70b14ac5e76dbde7bfdf8"
|
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.8.tgz#642af7d0333eab9c0ad70b14ac5e76dbde7bfdf8"
|
||||||
integrity sha512-6zavDGdzG3gUqAdWvlLFfk+36RilI+Pwyuuh7HItyeScCWP3k6i8vKclAQ0bM/0y/Kz/xiwvxhMv9MgTJP5gmA==
|
integrity sha512-6zavDGdzG3gUqAdWvlLFfk+36RilI+Pwyuuh7HItyeScCWP3k6i8vKclAQ0bM/0y/Kz/xiwvxhMv9MgTJP5gmA==
|
||||||
|
|
||||||
|
"@babel/parser@^7.20.7":
|
||||||
|
version "7.21.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.3.tgz#1d285d67a19162ff9daa358d4cb41d50c06220b3"
|
||||||
|
integrity sha512-lobG0d7aOfQRXh8AyklEAgZGvA4FShxo6xQbUrrT/cNBPUdIDojlokwJsQyCC/eKia7ifqM0yP+2DRZ4WKw2RQ==
|
||||||
|
|
||||||
"@babel/plugin-syntax-async-generators@^7.8.4":
|
"@babel/plugin-syntax-async-generators@^7.8.4":
|
||||||
version "7.8.4"
|
version "7.8.4"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d"
|
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d"
|
||||||
|
@ -292,7 +297,7 @@
|
||||||
debug "^4.1.0"
|
debug "^4.1.0"
|
||||||
globals "^11.1.0"
|
globals "^11.1.0"
|
||||||
|
|
||||||
"@babel/types@^7.0.0", "@babel/types@^7.18.6", "@babel/types@^7.20.7", "@babel/types@^7.21.0", "@babel/types@^7.21.4", "@babel/types@^7.21.5", "@babel/types@^7.3.0", "@babel/types@^7.3.3":
|
"@babel/types@^7.0.0", "@babel/types@^7.21.4", "@babel/types@^7.21.5", "@babel/types@^7.3.0", "@babel/types@^7.3.3":
|
||||||
version "7.21.5"
|
version "7.21.5"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.21.5.tgz#18dfbd47c39d3904d5db3d3dc2cc80bedb60e5b6"
|
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.21.5.tgz#18dfbd47c39d3904d5db3d3dc2cc80bedb60e5b6"
|
||||||
integrity sha512-m4AfNvVF2mVC/F7fDEdH2El3HzUg9It/XsCxZiOTTA3m3qYfcSVSbTfM6Q9xG+hYDniZssYhlXKKUMD5m8tF4Q==
|
integrity sha512-m4AfNvVF2mVC/F7fDEdH2El3HzUg9It/XsCxZiOTTA3m3qYfcSVSbTfM6Q9xG+hYDniZssYhlXKKUMD5m8tF4Q==
|
||||||
|
@ -301,6 +306,15 @@
|
||||||
"@babel/helper-validator-identifier" "^7.19.1"
|
"@babel/helper-validator-identifier" "^7.19.1"
|
||||||
to-fast-properties "^2.0.0"
|
to-fast-properties "^2.0.0"
|
||||||
|
|
||||||
|
"@babel/types@^7.18.6", "@babel/types@^7.20.7", "@babel/types@^7.21.0":
|
||||||
|
version "7.21.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.21.3.tgz#4865a5357ce40f64e3400b0f3b737dc6d4f64d05"
|
||||||
|
integrity sha512-sBGdETxC+/M4o/zKC0sl6sjWv62WFR/uzxrJ6uYyMLZOUlPnwzw0tKgVHOXxaAd5l2g8pEDM5RZ495GPQI77kg==
|
||||||
|
dependencies:
|
||||||
|
"@babel/helper-string-parser" "^7.19.4"
|
||||||
|
"@babel/helper-validator-identifier" "^7.19.1"
|
||||||
|
to-fast-properties "^2.0.0"
|
||||||
|
|
||||||
"@bcoe/v8-coverage@^0.2.3":
|
"@bcoe/v8-coverage@^0.2.3":
|
||||||
version "0.2.3"
|
version "0.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
|
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
|
||||||
|
@ -1023,9 +1037,9 @@ camelcase@^6.2.0:
|
||||||
integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==
|
integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==
|
||||||
|
|
||||||
caniuse-lite@^1.0.30001449:
|
caniuse-lite@^1.0.30001449:
|
||||||
version "1.0.30001486"
|
version "1.0.30001469"
|
||||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001486.tgz#56a08885228edf62cbe1ac8980f2b5dae159997e"
|
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001469.tgz#3dd505430c8522fdc9f94b4a19518e330f5c945a"
|
||||||
integrity sha512-uv7/gXuHi10Whlj0pp5q/tsK/32J2QSqVRKQhs2j8VsDCjgyruAh/eEXHF822VqO9yT6iZKw3nRwZRSPBE9OQg==
|
integrity sha512-Rcp7221ScNqQPP3W+lVOYDyjdR6dC+neEQCttoNr5bAyz54AboB4iwpnWgyi8P4YUsPybVzT4LgWiBbI3drL4g==
|
||||||
|
|
||||||
chalk@^2.0.0:
|
chalk@^2.0.0:
|
||||||
version "2.4.2"
|
version "2.4.2"
|
||||||
|
@ -2506,7 +2520,7 @@ safe-buffer@^5.0.1, safe-buffer@~5.2.0:
|
||||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
|
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
|
||||||
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
|
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
|
||||||
|
|
||||||
semver@7.x, semver@^7.3.5:
|
semver@7.x:
|
||||||
version "7.5.0"
|
version "7.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.0.tgz#ed8c5dc8efb6c629c88b23d41dc9bf40c1d96cd0"
|
resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.0.tgz#ed8c5dc8efb6c629c88b23d41dc9bf40c1d96cd0"
|
||||||
integrity sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==
|
integrity sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==
|
||||||
|
@ -2518,6 +2532,13 @@ semver@^6.0.0, semver@^6.3.0:
|
||||||
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
|
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
|
||||||
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
|
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
|
||||||
|
|
||||||
|
semver@^7.3.5:
|
||||||
|
version "7.3.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798"
|
||||||
|
integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==
|
||||||
|
dependencies:
|
||||||
|
lru-cache "^6.0.0"
|
||||||
|
|
||||||
sha.js@^2.4.11:
|
sha.js@^2.4.11:
|
||||||
version "2.4.11"
|
version "2.4.11"
|
||||||
resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7"
|
resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7"
|
||||||
|
|
Loading…
Reference in a new issue