Create /effects buy subcommand
Some checks failed
Test / build (push) Failing after 36s

This commit is contained in:
Ethan Lane 2025-02-06 19:33:54 +00:00
parent c6b458199c
commit 904842ae32
9 changed files with 340 additions and 79 deletions

View file

@ -1,10 +1,10 @@
import { ActionRowBuilder, ButtonBuilder, ButtonStyle, CommandInteraction, EmbedBuilder, SlashCommandBuilder } from "discord.js";
import { CommandInteraction, SlashCommandBuilder } from "discord.js";
import { Command } from "../type/command";
import EffectHelper from "../helpers/EffectHelper";
import { EffectDetails } from "../constants/EffectDetails";
import TimeLengthInput from "../helpers/TimeLengthInput";
import EmbedColours from "../constants/EmbedColours";
import { EffectChoices } from "../constants/EffectDetails";
import AppLogger from "../client/appLogger";
import List from "./effects/List";
import Use from "./effects/Use";
import Buy from "./effects/Buy";
export default class Effects extends Command {
constructor() {
@ -27,9 +27,19 @@ export default class Effects extends Command {
.setName("id")
.setDescription("The effect id to use")
.setRequired(true)
.setChoices([
{ name: "Unclaimed Chance Up", value: "unclaimed" },
])));
.setChoices(EffectChoices)))
.addSubcommand(x => x
.setName("buy")
.setDescription("Buy more effects")
.addStringOption(y => y
.setName("id")
.setDescription("The effect id to buy")
.setRequired(true)
.setChoices(EffectChoices))
.addNumberOption(y => y
.setName("quantity")
.setDescription("The amount to buy")
.setMinValue(1)));
}
public override async execute(interaction: CommandInteraction) {
@ -39,80 +49,16 @@ export default class Effects extends Command {
switch (subcommand) {
case "list":
await this.List(interaction);
await List(interaction);
break;
case "use":
await this.Use(interaction);
await Use(interaction);
break;
case "buy":
await Buy(interaction);
break;
default:
AppLogger.LogError("Commands/Effects", `Invalid subcommand: ${subcommand}`);
}
}
private async List(interaction: CommandInteraction) {
const pageOption = interaction.options.get("page");
const page = !isNaN(Number(pageOption?.value)) ? Number(pageOption?.value) : 1;
const result = await EffectHelper.GenerateEffectEmbed(interaction.user.id, page);
await interaction.reply({
embeds: [ result.embed ],
components: [ result.row ],
});
}
private async Use(interaction: CommandInteraction) {
const id = interaction.options.get("id", true).value!.toString();
const effectDetail = EffectDetails.get(id);
if (!effectDetail) {
AppLogger.LogWarn("Commands/Effects", `Unable to find effect details for ${id}`);
await interaction.reply("Unable to find effect!");
return;
}
const canUseEffect = await EffectHelper.CanUseEffect(interaction.user.id, id);
if (!canUseEffect) {
await interaction.reply("Unable to use effect! Please make sure you have it in your inventory and is not on cooldown");
return;
}
const timeLengthInput = TimeLengthInput.ConvertFromMilliseconds(effectDetail.duration);
const embed = new EmbedBuilder()
.setTitle("Effect Confirmation")
.setDescription("Would you like to use this effect?")
.setColor(EmbedColours.Ok)
.addFields([
{
name: "Effect",
value: effectDetail.friendlyName,
inline: true,
},
{
name: "Length",
value: timeLengthInput.GetLengthShort(),
inline: true,
},
]);
const row = new ActionRowBuilder<ButtonBuilder>()
.addComponents([
new ButtonBuilder()
.setLabel("Confirm")
.setCustomId(`effects use confirm ${effectDetail.id}`)
.setStyle(ButtonStyle.Primary),
new ButtonBuilder()
.setLabel("Cancel")
.setCustomId(`effects use cancel ${effectDetail.id}`)
.setStyle(ButtonStyle.Danger),
]);
await interaction.reply({
embeds: [ embed ],
components: [ row ],
});
}
}

View file

@ -0,0 +1,4 @@
import { CommandInteraction } from "discord.js";
export default async function Buy(interaction: CommandInteraction) {
}

View file

@ -0,0 +1,15 @@
import { CommandInteraction } from "discord.js";
import EffectHelper from "../../helpers/EffectHelper";
export default async function List(interaction: CommandInteraction) {
const pageOption = interaction.options.get("page");
const page = !isNaN(Number(pageOption?.value)) ? Number(pageOption?.value) : 1;
const result = await EffectHelper.GenerateEffectEmbed(interaction.user.id, page);
await interaction.reply({
embeds: [ result.embed ],
components: [ result.row ],
});
}

View file

@ -0,0 +1,62 @@
import { ActionRowBuilder, ButtonBuilder, ButtonStyle, CommandInteraction, EmbedBuilder } from "discord.js";
import { EffectDetails } from "../../constants/EffectDetails";
import AppLogger from "../../client/appLogger";
import EffectHelper from "../../helpers/EffectHelper";
import TimeLengthInput from "../../helpers/TimeLengthInput";
import EmbedColours from "../../constants/EmbedColours";
export default async function Use(interaction: CommandInteraction) {
const id = interaction.options.get("id", true).value!.toString();
const effectDetail = EffectDetails.get(id);
if (!effectDetail) {
AppLogger.LogWarn("Commands/Effects", `Unable to find effect details for ${id}`);
await interaction.reply("Unable to find effect!");
return;
}
const canUseEffect = await EffectHelper.CanUseEffect(interaction.user.id, id);
if (!canUseEffect) {
await interaction.reply("Unable to use effect! Please make sure you have it in your inventory and is not on cooldown");
return;
}
const timeLengthInput = TimeLengthInput.ConvertFromMilliseconds(effectDetail.duration);
const embed = new EmbedBuilder()
.setTitle("Effect Confirmation")
.setDescription("Would you like to use this effect?")
.setColor(EmbedColours.Ok)
.addFields([
{
name: "Effect",
value: effectDetail.friendlyName,
inline: true,
},
{
name: "Length",
value: timeLengthInput.GetLengthShort(),
inline: true,
},
]);
const row = new ActionRowBuilder<ButtonBuilder>()
.addComponents([
new ButtonBuilder()
.setLabel("Confirm")
.setCustomId(`effects use confirm ${effectDetail.id}`)
.setStyle(ButtonStyle.Primary),
new ButtonBuilder()
.setLabel("Cancel")
.setCustomId(`effects use cancel ${effectDetail.id}`)
.setStyle(ButtonStyle.Danger),
]);
await interaction.reply({
embeds: [ embed ],
components: [ row ],
});
}

View file

@ -17,3 +17,7 @@ class EffectDetail {
export const EffectDetails = new Map<string, EffectDetail>([
[ "unclaimed", new EffectDetail("unclaimed", "Unclaimed Chance Up", 10 * 60 * 1000, 100, 3 * 60 * 60 * 1000) ],
]);
export const EffectChoices = [
{ name: "Unclaimed Chance Up", value: "unclaimed" },
];

View file

@ -0,0 +1,12 @@
import { CommandInteraction } from "../../__types__/discord.js";
export default function GenerateCommandInteractionMock(options?: {
subcommand?: string,
}): CommandInteraction {
return {
isChatInputCommand: jest.fn().mockReturnValue(true),
options: {
getSubcommand: jest.fn().mockReturnValue(options?.subcommand),
},
};
}

View file

@ -14,4 +14,11 @@ export type ButtonInteraction = {
id: string,
} | null,
customId: string,
}
export type CommandInteraction = {
isChatInputCommand: jest.Func,
options: {
getSubcommand: jest.Func,
},
}

View file

@ -0,0 +1,106 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`EXPECT CommandBuilder to be defined 1`] = `
{
"contexts": undefined,
"default_member_permissions": undefined,
"default_permission": undefined,
"description": "Effects",
"description_localizations": undefined,
"dm_permission": undefined,
"integration_types": undefined,
"name": "effects",
"name_localizations": undefined,
"nsfw": undefined,
"options": [
{
"description": "List all effects I have",
"description_localizations": undefined,
"name": "list",
"name_localizations": undefined,
"options": [
{
"autocomplete": undefined,
"choices": undefined,
"description": "The page number",
"description_localizations": undefined,
"max_value": undefined,
"min_value": 1,
"name": "page",
"name_localizations": undefined,
"required": false,
"type": 10,
},
],
"type": 1,
},
{
"description": "Use an effect in your inventory",
"description_localizations": undefined,
"name": "use",
"name_localizations": undefined,
"options": [
{
"autocomplete": undefined,
"choices": [
{
"name": "Unclaimed Chance Up",
"name_localizations": undefined,
"value": "unclaimed",
},
],
"description": "The effect id to use",
"description_localizations": undefined,
"max_length": undefined,
"min_length": undefined,
"name": "id",
"name_localizations": undefined,
"required": true,
"type": 3,
},
],
"type": 1,
},
{
"description": "Buy more effects",
"description_localizations": undefined,
"name": "buy",
"name_localizations": undefined,
"options": [
{
"autocomplete": undefined,
"choices": [
{
"name": "Unclaimed Chance Up",
"name_localizations": undefined,
"value": "unclaimed",
},
],
"description": "The effect id to buy",
"description_localizations": undefined,
"max_length": undefined,
"min_length": undefined,
"name": "id",
"name_localizations": undefined,
"required": true,
"type": 3,
},
{
"autocomplete": undefined,
"choices": undefined,
"description": "The amount to buy",
"description_localizations": undefined,
"max_value": undefined,
"min_value": 1,
"name": "quantity",
"name_localizations": undefined,
"required": false,
"type": 10,
},
],
"type": 1,
},
],
"type": 1,
}
`;

View file

@ -0,0 +1,105 @@
import Effects from "../../src/commands/effects";
import List from "../../src/commands/effects/List";
import Use from "../../src/commands/effects/Use";
import Buy from "../../src/commands/effects/Buy";
import AppLogger from "../../src/client/appLogger";
import GenerateCommandInteractionMock from "../__functions__/discord.js/GenerateCommandInteractionMock";
import { CommandInteraction } from "discord.js";
jest.mock("../../src/commands/effects/List");
jest.mock("../../src/commands/effects/Use");
jest.mock("../../src/commands/effects/Buy");
jest.mock("../../src/client/appLogger");
beforeEach(() => {
jest.resetAllMocks();
});
test("EXPECT CommandBuilder to be defined", async () => {
// Act
const effects = new Effects();
// Assert
expect(effects.CommandBuilder).toMatchSnapshot();
});
describe("execute", () => {
test("GIVEN interaction subcommand is list, EXPECT buy function called", async () => {
// Arrange
const interaction = GenerateCommandInteractionMock({
subcommand: "list",
});
// Act
const effects = new Effects();
await effects.execute(interaction as unknown as CommandInteraction);
// Assert
expect(List).toHaveBeenCalledTimes(1);
expect(List).toHaveBeenCalledWith(interaction);
expect(Use).not.toHaveBeenCalled();
expect(Buy).not.toHaveBeenCalled();
expect(AppLogger.LogError).not.toHaveBeenCalled();
});
test("GIVEN interaction subcommand is use, EXPECT buy function called", async () => {
// Arrange
const interaction = GenerateCommandInteractionMock({
subcommand: "use",
});
// Act
const effects = new Effects();
await effects.execute(interaction as unknown as CommandInteraction);
// Assert
expect(Use).toHaveBeenCalledTimes(1);
expect(Use).toHaveBeenCalledWith(interaction);
expect(List).not.toHaveBeenCalled();
expect(Buy).not.toHaveBeenCalled();
expect(AppLogger.LogError).not.toHaveBeenCalled();
});
test("GIVEN interaction subcommand is buy, EXPECT buy function called", async () => {
// Arrange
const interaction = GenerateCommandInteractionMock({
subcommand: "buy",
});
// Act
const effects = new Effects();
await effects.execute(interaction as unknown as CommandInteraction);
// Assert
expect(Buy).toHaveBeenCalledTimes(1);
expect(Buy).toHaveBeenCalledWith(interaction);
expect(List).not.toHaveBeenCalled();
expect(Use).not.toHaveBeenCalled();
expect(AppLogger.LogError).not.toHaveBeenCalled();
});
test("GIVEN interaction subcommand is invalid, EXPECT error logged", async () => {
// Arrange
const interaction = GenerateCommandInteractionMock({
subcommand: "invalid",
});
// Act
const effects = new Effects();
await effects.execute(interaction as unknown as CommandInteraction);
// Assert
expect(AppLogger.LogError).toHaveBeenCalledTimes(1);
expect(AppLogger.LogError).toHaveBeenCalledWith("Commands/Effects", "Invalid subcommand: invalid");
expect(List).not.toHaveBeenCalled();
expect(Use).not.toHaveBeenCalled();
expect(Buy).not.toHaveBeenCalled();
});
});