From 1be9ba23a3e12715f17d64049fa5d076ca9894cd Mon Sep 17 00:00:00 2001 From: Ethan Lane Date: Sat, 23 Nov 2024 17:20:47 +0000 Subject: [PATCH 01/15] Create base effects command --- src/commands/effects.ts | 15 +++++++++++++++ src/commands/effects/list.ts | 0 src/registry.ts | 2 ++ 3 files changed, 17 insertions(+) create mode 100644 src/commands/effects.ts create mode 100644 src/commands/effects/list.ts diff --git a/src/commands/effects.ts b/src/commands/effects.ts new file mode 100644 index 0000000..d52b3ce --- /dev/null +++ b/src/commands/effects.ts @@ -0,0 +1,15 @@ +import {CommandInteraction, SlashCommandBuilder} from "discord.js"; +import {Command} from "../type/command"; + +export default class Effects extends Command { + constructor() { + super(); + + this.CommandBuilder = new SlashCommandBuilder() + .setName("effects") + .setDescription("Effects"); + } + + public override async execute(interaction: CommandInteraction) { + } +} diff --git a/src/commands/effects/list.ts b/src/commands/effects/list.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/registry.ts b/src/registry.ts index e4e5d64..3be1fb8 100644 --- a/src/registry.ts +++ b/src/registry.ts @@ -7,6 +7,7 @@ import AllBalance from "./commands/allbalance"; import Balance from "./commands/balance"; import Daily from "./commands/daily"; import Drop from "./commands/drop"; +import Effects from "./commands/effects"; import Gdrivesync from "./commands/gdrivesync"; import Give from "./commands/give"; import Id from "./commands/id"; @@ -44,6 +45,7 @@ export default class Registry { CoreClient.RegisterCommand("balance", new Balance()); CoreClient.RegisterCommand("daily", new Daily()); CoreClient.RegisterCommand("drop", new Drop()); + CoreClient.RegisterCommand("effects", new Effects()); CoreClient.RegisterCommand("gdrivesync", new Gdrivesync()); CoreClient.RegisterCommand("give", new Give()); CoreClient.RegisterCommand("id", new Id()); -- 2.45.2 From 9fce79579baee90688064c0375c83f2ccf41519f Mon Sep 17 00:00:00 2001 From: Ethan Lane Date: Sat, 23 Nov 2024 17:40:29 +0000 Subject: [PATCH 02/15] WIP: Start creating user effects builder --- src/commands/effects.ts | 5 ++++- src/commands/effects/list.ts | 0 src/database/entities/app/UserEffect.ts | 15 +++++++++++++++ src/helpers/EffectHelper.ts | 19 +++++++++++++++++++ 4 files changed, 38 insertions(+), 1 deletion(-) delete mode 100644 src/commands/effects/list.ts diff --git a/src/commands/effects.ts b/src/commands/effects.ts index d52b3ce..0b014df 100644 --- a/src/commands/effects.ts +++ b/src/commands/effects.ts @@ -7,7 +7,10 @@ export default class Effects extends Command { this.CommandBuilder = new SlashCommandBuilder() .setName("effects") - .setDescription("Effects"); + .setDescription("Effects") + .addSubcommand(x => x + .setName("list") + .setDescription("List all effects I have")); } public override async execute(interaction: CommandInteraction) { diff --git a/src/commands/effects/list.ts b/src/commands/effects/list.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/database/entities/app/UserEffect.ts b/src/database/entities/app/UserEffect.ts index fa1b584..189b206 100644 --- a/src/database/entities/app/UserEffect.ts +++ b/src/database/entities/app/UserEffect.ts @@ -57,4 +57,19 @@ export default class UserEffect extends AppBaseEntity { return single; } + + public static async FetchAllByUserIdPaginated(userId: string, page: number = 0): Promise { + const itemsPerPage = 10; + + const repository = AppDataSource.getRepository(UserEffect); + + const all = await repository.find({ + where: { UserId: userId }, + order: { Name: "ASC" }, + skip: page * itemsPerPage, + take: itemsPerPage, + }); + + return all; + } } diff --git a/src/helpers/EffectHelper.ts b/src/helpers/EffectHelper.ts index 14c2f43..7f16f84 100644 --- a/src/helpers/EffectHelper.ts +++ b/src/helpers/EffectHelper.ts @@ -1,4 +1,6 @@ +import {EmbedBuilder} from "discord.js"; import UserEffect from "../database/entities/app/UserEffect"; +import EmbedColours from "../constants/EmbedColours"; export default class EffectHelper { public static async AddEffectToUserInventory(userId: string, name: string, quantity: number = 1) { @@ -46,4 +48,21 @@ export default class EffectHelper { return true; } + + public static async GenerateEffectEmbed(userId: string, page: number): Promise { + const effects = await UserEffect.FetchAllByUserIdPaginated(userId, page - 1); + + let description = "*none*"; + + if (effects.length > 0) { + description = effects.map(x => `${x.Name} x${x.Unused}`).join("\n"); + } + + const embed = new EmbedBuilder() + .setTitle("Effects") + .setDescription(description) + .setColor(EmbedColours.Ok); + + return embed; + } } -- 2.45.2 From 96bbd3b28a5a082e838edeb60a099cdb6d07782d Mon Sep 17 00:00:00 2001 From: Ethan Lane Date: Mon, 25 Nov 2024 18:35:43 +0000 Subject: [PATCH 03/15] Create embed helper function --- src/database/entities/app/UserEffect.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/database/entities/app/UserEffect.ts b/src/database/entities/app/UserEffect.ts index 189b206..f5754bd 100644 --- a/src/database/entities/app/UserEffect.ts +++ b/src/database/entities/app/UserEffect.ts @@ -63,13 +63,14 @@ export default class UserEffect extends AppBaseEntity { const repository = AppDataSource.getRepository(UserEffect); - const all = await repository.find({ - where: { UserId: userId }, - order: { Name: "ASC" }, - skip: page * itemsPerPage, - take: itemsPerPage, - }); + const query = await repository.createQueryBuilder("effect") + .where("effect.UserId = :userId", { userId }) + .where("effect.Unused > 0") + .orderBy("effect.Name", "ASC") + .skip(page * itemsPerPage) + .take(itemsPerPage) + .getMany(); - return all; + return query; } } -- 2.45.2 From 78fcdeeca53c21f36cd443a741b3560105b7ac96 Mon Sep 17 00:00:00 2001 From: Ethan Lane Date: Mon, 25 Nov 2024 18:45:46 +0000 Subject: [PATCH 04/15] Create row helper function --- src/database/entities/app/UserEffect.ts | 6 ++--- src/helpers/EffectHelper.ts | 35 ++++++++++++++++++++++--- 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/src/database/entities/app/UserEffect.ts b/src/database/entities/app/UserEffect.ts index f5754bd..72fae5f 100644 --- a/src/database/entities/app/UserEffect.ts +++ b/src/database/entities/app/UserEffect.ts @@ -58,9 +58,7 @@ export default class UserEffect extends AppBaseEntity { return single; } - public static async FetchAllByUserIdPaginated(userId: string, page: number = 0): Promise { - const itemsPerPage = 10; - + public static async FetchAllByUserIdPaginated(userId: string, page: number = 0, itemsPerPage: number = 10): Promise<[UserEffect[], number]> { const repository = AppDataSource.getRepository(UserEffect); const query = await repository.createQueryBuilder("effect") @@ -69,7 +67,7 @@ export default class UserEffect extends AppBaseEntity { .orderBy("effect.Name", "ASC") .skip(page * itemsPerPage) .take(itemsPerPage) - .getMany(); + .getManyAndCount(); return query; } diff --git a/src/helpers/EffectHelper.ts b/src/helpers/EffectHelper.ts index 7f16f84..275e8e6 100644 --- a/src/helpers/EffectHelper.ts +++ b/src/helpers/EffectHelper.ts @@ -1,4 +1,4 @@ -import {EmbedBuilder} from "discord.js"; +import {ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder} from "discord.js"; import UserEffect from "../database/entities/app/UserEffect"; import EmbedColours from "../constants/EmbedColours"; @@ -49,8 +49,18 @@ export default class EffectHelper { return true; } - public static async GenerateEffectEmbed(userId: string, page: number): Promise { - const effects = await UserEffect.FetchAllByUserIdPaginated(userId, page - 1); + public static async GenerateEffectEmbed(userId: string, page: number): Promise<{ + embed: EmbedBuilder, + row: ActionRowBuilder, + }> { + const itemsPerPage = 10; + + const query = await UserEffect.FetchAllByUserIdPaginated(userId, page - 1, itemsPerPage); + + const effects = query[0]; + const count = query[1]; + + const isLastPage = Math.ceil(count / itemsPerPage) - 1 == page; let description = "*none*"; @@ -63,6 +73,23 @@ export default class EffectHelper { .setDescription(description) .setColor(EmbedColours.Ok); - return embed; + const row = new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId(`effects list ${page - 1}`) + .setLabel("Previous") + .setStyle(ButtonStyle.Primary) + .setDisabled(page == 0), + new ButtonBuilder() + .setCustomId(`effects list ${page + 1}`) + .setLabel("Next") + .setStyle(ButtonStyle.Primary) + .setDisabled(isLastPage), + ); + + return { + embed, + row, + }; } } -- 2.45.2 From 0e1bc43ac444a0f261d362514638b1f9576e0b36 Mon Sep 17 00:00:00 2001 From: Ethan Lane Date: Mon, 25 Nov 2024 19:09:02 +0000 Subject: [PATCH 05/15] WIP: Start of list command --- src/commands/effects.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/commands/effects.ts b/src/commands/effects.ts index 0b014df..df8a515 100644 --- a/src/commands/effects.ts +++ b/src/commands/effects.ts @@ -14,5 +14,17 @@ export default class Effects extends Command { } public override async execute(interaction: CommandInteraction) { + if (!interaction.isChatInputCommand()) return; + + const subcommand = interaction.options.getSubcommand(); + + switch (subcommand) { + case "list": + await this.List(interaction); + break; + } + } + + private async List(interaction: CommandInteraction) { } } -- 2.45.2 From 3873983b32e54af1d46846df99b9fca047494eb0 Mon Sep 17 00:00:00 2001 From: Ethan Lane Date: Thu, 28 Nov 2024 18:56:26 +0000 Subject: [PATCH 06/15] Create effects list subcommand --- src/commands/effects.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/commands/effects.ts b/src/commands/effects.ts index df8a515..aa58d3b 100644 --- a/src/commands/effects.ts +++ b/src/commands/effects.ts @@ -1,5 +1,6 @@ import {CommandInteraction, SlashCommandBuilder} from "discord.js"; import {Command} from "../type/command"; +import EffectHelper from "../helpers/EffectHelper"; export default class Effects extends Command { constructor() { @@ -10,7 +11,11 @@ export default class Effects extends Command { .setDescription("Effects") .addSubcommand(x => x .setName("list") - .setDescription("List all effects I have")); + .setDescription("List all effects I have") + .addNumberOption(x => x + .setName("page") + .setDescription("The page number") + .setMinValue(1))); } public override async execute(interaction: CommandInteraction) { @@ -26,5 +31,15 @@ export default class Effects extends Command { } private async List(interaction: CommandInteraction) { + const pageOption = interaction.options.get("page"); + + const page = pageOption && 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 ], + }); } } -- 2.45.2 From 7944b3aeac223ae8c0468fc151c5b11cfd51e336 Mon Sep 17 00:00:00 2001 From: Ethan Lane Date: Thu, 28 Nov 2024 19:01:34 +0000 Subject: [PATCH 07/15] Create paginated button event --- src/buttonEvents/Effects.ts | 33 +++++++++++++++++++++++++++++++++ src/registry.ts | 2 ++ 2 files changed, 35 insertions(+) create mode 100644 src/buttonEvents/Effects.ts diff --git a/src/buttonEvents/Effects.ts b/src/buttonEvents/Effects.ts new file mode 100644 index 0000000..0810c94 --- /dev/null +++ b/src/buttonEvents/Effects.ts @@ -0,0 +1,33 @@ +import {ButtonInteraction} from "discord.js"; +import {ButtonEvent} from "../type/buttonEvent"; +import EffectHelper from "../helpers/EffectHelper"; + +export default class Effects extends ButtonEvent { + public override async execute(interaction: ButtonInteraction) { + const action = interaction.customId.split(" ")[1]; + + switch (action) { + case "list": + await this.List(interaction); + break; + } + } + + private async List(interaction: ButtonInteraction) { + const pageOption = interaction.customId.split(" ")[2]; + + const page = Number(pageOption); + + if (!page) { + await interaction.reply("Page option is not a valid number"); + return; + } + + const result = await EffectHelper.GenerateEffectEmbed(interaction.user.id, page); + + await interaction.update({ + embeds: [ result.embed ], + components: [ result.row ], + }); + } +} diff --git a/src/registry.ts b/src/registry.ts index 3be1fb8..3ae885d 100644 --- a/src/registry.ts +++ b/src/registry.ts @@ -26,6 +26,7 @@ import Droprarity from "./commands/stage/droprarity"; // Button Event Imports import Claim from "./buttonEvents/Claim"; +import EffectsButtonEvent from "./buttonEvents/Effects"; import InventoryButtonEvent from "./buttonEvents/Inventory"; import MultidropButtonEvent from "./buttonEvents/Multidrop"; import Reroll from "./buttonEvents/Reroll"; @@ -65,6 +66,7 @@ export default class Registry { public static RegisterButtonEvents() { CoreClient.RegisterButtonEvent("claim", new Claim()); + CoreClient.RegisterButtonEvent("effects", new EffectsButtonEvent()); CoreClient.RegisterButtonEvent("inventory", new InventoryButtonEvent()); CoreClient.RegisterButtonEvent("multidrop", new MultidropButtonEvent()); CoreClient.RegisterButtonEvent("reroll", new Reroll()); -- 2.45.2 From 2a7006229aa0a5f3b5f61ae64f7cfe9261c96e17 Mon Sep 17 00:00:00 2001 From: Ethan Lane Date: Sat, 30 Nov 2024 16:16:01 +0000 Subject: [PATCH 08/15] Fix embed pagination buttons --- src/helpers/EffectHelper.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/helpers/EffectHelper.ts b/src/helpers/EffectHelper.ts index 275e8e6..11f6ed8 100644 --- a/src/helpers/EffectHelper.ts +++ b/src/helpers/EffectHelper.ts @@ -60,7 +60,7 @@ export default class EffectHelper { const effects = query[0]; const count = query[1]; - const isLastPage = Math.ceil(count / itemsPerPage) - 1 == page; + const totalPages = count > 0 ? Math.ceil(count / itemsPerPage) : 1; let description = "*none*"; @@ -71,7 +71,8 @@ export default class EffectHelper { const embed = new EmbedBuilder() .setTitle("Effects") .setDescription(description) - .setColor(EmbedColours.Ok); + .setColor(EmbedColours.Ok) + .setFooter({ text: `Page ${page} of ${totalPages}` }); const row = new ActionRowBuilder() .addComponents( @@ -79,12 +80,12 @@ export default class EffectHelper { .setCustomId(`effects list ${page - 1}`) .setLabel("Previous") .setStyle(ButtonStyle.Primary) - .setDisabled(page == 0), + .setDisabled(page - 1 == 0), new ButtonBuilder() .setCustomId(`effects list ${page + 1}`) .setLabel("Next") .setStyle(ButtonStyle.Primary) - .setDisabled(isLastPage), + .setDisabled(page == totalPages), ); return { -- 2.45.2 From 6e6b2a0af61cf0c9ce3f274d90ecb3e47a544537 Mon Sep 17 00:00:00 2001 From: Ethan Lane Date: Sat, 30 Nov 2024 16:38:02 +0000 Subject: [PATCH 09/15] Plan tests --- tests/buttonEvents/Effects.test.ts | 25 ++++++++++++++++++++++++ tests/commands/effects.test.ts | 31 ++++++++++++++++++++++++++++++ tests/helpers/EffectHelper.test.ts | 24 +++++++++++++++++++++++ 3 files changed, 80 insertions(+) create mode 100644 tests/buttonEvents/Effects.test.ts create mode 100644 tests/commands/effects.test.ts diff --git a/tests/buttonEvents/Effects.test.ts b/tests/buttonEvents/Effects.test.ts new file mode 100644 index 0000000..6c7bd97 --- /dev/null +++ b/tests/buttonEvents/Effects.test.ts @@ -0,0 +1,25 @@ +describe("execute", () => { + describe("GIVEN action in custom id is list", () => { + test.todo("EXPECT list function to be called"); + }); +}); + +describe("List", () => { + describe("GIVEN page is a valid number", () => { + test.todo("EXPECT EffectHelper.GenerateEffectEmbed to be called"); + + test.todo("EXPECT interaction to be updated"); + }); + + describe("GIVEN page in custom id is not supplied", () => { + test.todo("EXPECT interaction to be replied with error"); + + test.todo("EXPECT interaction to not be updated"); + }); + + describe("GIVEN page in custom id is not a number", () => { + test.todo("EXPECT interaction to be replied with error"); + + test.todo("EXPECT interaction to not be updated"); + }); +}); diff --git a/tests/commands/effects.test.ts b/tests/commands/effects.test.ts new file mode 100644 index 0000000..f2c14ce --- /dev/null +++ b/tests/commands/effects.test.ts @@ -0,0 +1,31 @@ +describe("constructor", () => { + test.todo("EXPECT CommandBuilder to be defined"); +}); + +describe("execute", () => { + describe("GIVEN interaction is not a chat input command", () => { + test.todo("EXPECT nothing to happen"); + }); + + describe("GIVEN subcommand is list", () => { + test.todo("EXPECT list function to be called"); + }); +}); + +describe("List", () => { + describe("GIVEN page option is supplied", () => { + describe("AND page is a valid number", () => { + test.todo("EXPECT EffectHelper.GenerateEffectEmbed to have been called with page"); + + test.todo("EXPECT interaction to have been replied"); + }); + + describe("AND page is not a valid number", () => { + test.todo("EXPECT EffectHelper.GenerateEffectEmbed to have been called with page of 1"); + }); + }); + + describe("GIVEN page option is not supplied", () => { + test.todo("EXPECT EffectHelper.GenerateEffectEmbed to have been called with a page of 1"); + }); +}); diff --git a/tests/helpers/EffectHelper.test.ts b/tests/helpers/EffectHelper.test.ts index 343f06c..2adcc9b 100644 --- a/tests/helpers/EffectHelper.test.ts +++ b/tests/helpers/EffectHelper.test.ts @@ -279,3 +279,27 @@ describe("HasEffect", () => { }); }); }); + +describe("GenerateEffectEmbed", () => { + test.todo("EXPECT UserEffect.FetchAllByUserIdPaginated to be called"); + + describe("GIVEN there are no effects returned", () => { + test.todo("EXPECT embed generated"); + + test.todo("EXPECT row generated"); + }); + + describe("GIVEN there are effects returned", () => { + test.todo("EXPECT embed generated"); + + test.todo("EXPECT row generated"); + + describe("AND it is the first page", () => { + test.todo("EXPECT Previous button to be disabled"); + }); + + describe("AND it is the last page", () => { + test.todo("EXPECT Next button to be disabled"); + }); + }); +}); -- 2.45.2 From 882387438e2d5cf7bf452d797dc2aa98aa70000f Mon Sep 17 00:00:00 2001 From: Ethan Lane Date: Sun, 1 Dec 2024 20:13:17 +0000 Subject: [PATCH 10/15] Update effects buttonEvents tests --- tests/buttonEvents/Effects.test.ts | 118 +++++++++++++++++++++++++++-- 1 file changed, 110 insertions(+), 8 deletions(-) diff --git a/tests/buttonEvents/Effects.test.ts b/tests/buttonEvents/Effects.test.ts index 6c7bd97..26f172c 100644 --- a/tests/buttonEvents/Effects.test.ts +++ b/tests/buttonEvents/Effects.test.ts @@ -1,25 +1,127 @@ +import {ButtonInteraction} from "discord.js"; +import Effects from "../../src/buttonEvents/Effects"; +import EffectHelper from "../../src/helpers/EffectHelper"; + describe("execute", () => { describe("GIVEN action in custom id is list", () => { - test.todo("EXPECT list function to be called"); + let interaction = { + customId: "effects list", + } as unknown as ButtonInteraction; + + let listSpy: any; + + beforeAll(async () => { + const effects = new Effects(); + + listSpy = jest.spyOn(effects as any, "List") + .mockImplementation(() => {}); + + await effects.execute(interaction); + }); + + test("EXPECT list function to be called", () => { + expect(listSpy).toHaveBeenCalledTimes(1); + expect(listSpy).toHaveBeenCalledWith(interaction); + }); }); }); describe("List", () => { - describe("GIVEN page is a valid number", () => { - test.todo("EXPECT EffectHelper.GenerateEffectEmbed to be called"); + let interaction: any; - test.todo("EXPECT interaction to be updated"); + const embed = { + name: "Embed", + }; + + const row = { + name: "Row", + }; + + beforeEach(() => { + interaction = { + customId: "effects list", + user: { + id: "userId", + }, + update: jest.fn(), + reply: jest.fn(), + }; + }); + + describe("GIVEN page is a valid number", () => { + beforeEach(async () => { + interaction.customId += " 1"; + + EffectHelper.GenerateEffectEmbed = jest.fn() + .mockResolvedValue({ + embed, + row, + }); + + const effects = new Effects(); + + await effects.execute(interaction); + }); + + test("EXPECT EffectHelper.GenerateEffectEmbed to be called", () => { + expect(EffectHelper.GenerateEffectEmbed).toHaveBeenCalledTimes(1); + expect(EffectHelper.GenerateEffectEmbed).toHaveBeenCalledWith("userId", 1); + }); + + test("EXPECT interaction to be updated", () => { + expect(interaction.update).toHaveBeenCalledTimes(1); + expect(interaction.update).toHaveBeenCalledWith({ + embeds: [ embed ], + components: [ row ], + }); + }); }); describe("GIVEN page in custom id is not supplied", () => { - test.todo("EXPECT interaction to be replied with error"); + beforeEach(async () => { + EffectHelper.GenerateEffectEmbed = jest.fn() + .mockResolvedValue({ + embed, + row, + }); - test.todo("EXPECT interaction to not be updated"); + const effects = new Effects(); + + await effects.execute(interaction); + }); + + test("EXPECT interaction to be replied with error", () => { + expect(interaction.reply).toHaveBeenCalledTimes(1); + expect(interaction.reply).toHaveBeenCalledWith("Page option is not a valid number"); + }); + + test("EXPECT interaction to not be updated", () => { + expect(interaction.update).not.toHaveBeenCalled(); + }); }); describe("GIVEN page in custom id is not a number", () => { - test.todo("EXPECT interaction to be replied with error"); + beforeEach(async () => { + interaction.customId += " test"; - test.todo("EXPECT interaction to not be updated"); + EffectHelper.GenerateEffectEmbed = jest.fn() + .mockResolvedValue({ + embed, + row, + }); + + const effects = new Effects(); + + await effects.execute(interaction); + }); + + test("EXPECT interaction to be replied with error", () => { + expect(interaction.reply).toHaveBeenCalledTimes(1); + expect(interaction.reply).toHaveBeenCalledWith("Page option is not a valid number"); + }); + + test("EXPECT interaction to not be updated", () => { + expect(interaction.update).not.toHaveBeenCalled(); + }); }); }); -- 2.45.2 From 32cc731442a4084fef4e6730a6a693b4168d5387 Mon Sep 17 00:00:00 2001 From: Ethan Lane Date: Sun, 1 Dec 2024 20:13:26 +0000 Subject: [PATCH 11/15] WIP: Start updating effects command tests --- .../__snapshots__/effects.test.ts.snap | 40 +++++++++++++++++++ tests/commands/effects.test.ts | 36 ++++++++++++++++- 2 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 tests/commands/__snapshots__/effects.test.ts.snap diff --git a/tests/commands/__snapshots__/effects.test.ts.snap b/tests/commands/__snapshots__/effects.test.ts.snap new file mode 100644 index 0000000..ede2091 --- /dev/null +++ b/tests/commands/__snapshots__/effects.test.ts.snap @@ -0,0 +1,40 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`constructor 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, + }, + ], + "type": 1, +} +`; diff --git a/tests/commands/effects.test.ts b/tests/commands/effects.test.ts index f2c14ce..0ab6eab 100644 --- a/tests/commands/effects.test.ts +++ b/tests/commands/effects.test.ts @@ -1,10 +1,42 @@ +import Effects from "../../src/commands/effects"; + describe("constructor", () => { - test.todo("EXPECT CommandBuilder to be defined"); + let effects: Effects; + + beforeEach(() => { + effects = new Effects(); + }); + + test("EXPECT CommandBuilder to be defined", () => { + expect(effects.CommandBuilder).toMatchSnapshot(); + }); }); describe("execute", () => { describe("GIVEN interaction is not a chat input command", () => { - test.todo("EXPECT nothing to happen"); + let interaction: any; + + let listSpy: any; + + beforeEach(async () => { + interaction = { + isChatInputCommand: jest.fn().mockReturnValue(false), + }; + + const effects = new Effects(); + + listSpy = jest.spyOn(effects as any, "List"); + + await effects.execute(interaction); + }); + + test("EXPECT isChatInputCommand to have been called", () => { + expect(interaction.isChatInputCommand).toHaveBeenCalledTimes(1); + }); + + test("EXPECT nothing to happen", () => { + expect(listSpy).not.toHaveBeenCalled(); + }); }); describe("GIVEN subcommand is list", () => { -- 2.45.2 From 5302d57a899872ad1a2e2feb9d56a0bd67c77bf4 Mon Sep 17 00:00:00 2001 From: Ethan Lane Date: Mon, 2 Dec 2024 18:40:19 +0000 Subject: [PATCH 12/15] Add effects command tests --- tests/commands/effects.test.ts | 112 +++++++++++++++++++++++++++++++-- 1 file changed, 106 insertions(+), 6 deletions(-) diff --git a/tests/commands/effects.test.ts b/tests/commands/effects.test.ts index 0ab6eab..8819209 100644 --- a/tests/commands/effects.test.ts +++ b/tests/commands/effects.test.ts @@ -1,4 +1,5 @@ import Effects from "../../src/commands/effects"; +import EffectHelper from "../../src/helpers/EffectHelper"; describe("constructor", () => { let effects: Effects; @@ -25,7 +26,8 @@ describe("execute", () => { const effects = new Effects(); - listSpy = jest.spyOn(effects as any, "List"); + listSpy = jest.spyOn(effects as any, "List") + .mockImplementation(); await effects.execute(interaction); }); @@ -40,24 +42,122 @@ describe("execute", () => { }); describe("GIVEN subcommand is list", () => { - test.todo("EXPECT list function to be called"); + let interaction: any; + + let listSpy: any; + + beforeEach(async () => { + interaction = { + isChatInputCommand: jest.fn().mockReturnValue(true), + options: { + getSubcommand: jest.fn().mockReturnValue("list"), + }, + }; + + const effects = new Effects(); + + listSpy = jest.spyOn(effects as any, "List") + .mockImplementation(); + + await effects.execute(interaction); + }); + + test("EXPECT subcommand function to be called", () => { + expect(interaction.options.getSubcommand).toHaveBeenCalledTimes(1); + }); + + test("EXPECT list function to be called", () => { + expect(listSpy).toHaveBeenCalledTimes(1); + expect(listSpy).toHaveBeenCalledWith(interaction); + }); }); }); describe("List", () => { + let effects: Effects = new Effects(); + let interaction: any; + + const embed = { + name: "embed", + }; + + const row = { + name: "row", + }; + + beforeEach(async () => { + interaction = { + isChatInputCommand: jest.fn().mockReturnValue(true), + options: { + getSubcommand: jest.fn().mockReturnValue("list"), + }, + reply: jest.fn(), + user: { + id: "userId", + }, + }; + + const effects = new Effects(); + + EffectHelper.GenerateEffectEmbed = jest.fn().mockReturnValue({ + embed, + row, + }); + + jest.spyOn(effects as any, "List") + .mockImplementation(); + }); + describe("GIVEN page option is supplied", () => { describe("AND page is a valid number", () => { - test.todo("EXPECT EffectHelper.GenerateEffectEmbed to have been called with page"); + beforeEach(async () => { + interaction.options.get = jest.fn().mockReturnValueOnce({ + value: "2", + }); - test.todo("EXPECT interaction to have been replied"); + await effects.execute(interaction); + }); + + test("EXPECT EffectHelper.GenerateEffectEmbed to have been called with page", () => { + expect(EffectHelper.GenerateEffectEmbed).toHaveBeenCalledTimes(1); + expect(EffectHelper.GenerateEffectEmbed).toHaveBeenCalledWith("userId", 2); + }); + + test("EXPECT interaction to have been replied", () => { + expect(interaction.reply).toHaveBeenCalledTimes(1); + expect(interaction.reply).toHaveBeenCalledWith({ + embeds: [ embed ], + components: [ row ], + }); + }); }); describe("AND page is not a valid number", () => { - test.todo("EXPECT EffectHelper.GenerateEffectEmbed to have been called with page of 1"); + beforeEach(async () => { + interaction.options.get = jest.fn().mockReturnValueOnce({ + value: "test", + }); + + await effects.execute(interaction); + }); + + test("EXPECT EffectHelper.GenerateEffectEmbed to have been called with page of 1", () => { + expect(EffectHelper.GenerateEffectEmbed).toHaveBeenCalledTimes(1); + expect(EffectHelper.GenerateEffectEmbed).toHaveBeenCalledWith("userId", 1); + }); }); }); describe("GIVEN page option is not supplied", () => { - test.todo("EXPECT EffectHelper.GenerateEffectEmbed to have been called with a page of 1"); + beforeEach(async () => { + interaction.options.get = jest.fn().mockReturnValueOnce(undefined); + + await effects.execute(interaction); + }); + + test("EXPECT EffectHelper.GenerateEffectEmbed to have been called with page of 1", () => { + expect(EffectHelper.GenerateEffectEmbed).toHaveBeenCalledTimes(1); + expect(EffectHelper.GenerateEffectEmbed).toHaveBeenCalledWith("userId", 1); + }); }); }); -- 2.45.2 From 0584d0304b9320332703470db2dcad2bc9f10fe3 Mon Sep 17 00:00:00 2001 From: Ethan Lane Date: Mon, 2 Dec 2024 18:57:15 +0000 Subject: [PATCH 13/15] Update tests --- tests/helpers/EffectHelper.test.ts | 76 +++++++++++++++++-- .../__snapshots__/EffectHelper.test.ts.snap | 71 +++++++++++++++++ 2 files changed, 140 insertions(+), 7 deletions(-) create mode 100644 tests/helpers/__snapshots__/EffectHelper.test.ts.snap diff --git a/tests/helpers/EffectHelper.test.ts b/tests/helpers/EffectHelper.test.ts index 2adcc9b..bdfe5a1 100644 --- a/tests/helpers/EffectHelper.test.ts +++ b/tests/helpers/EffectHelper.test.ts @@ -281,25 +281,87 @@ describe("HasEffect", () => { }); describe("GenerateEffectEmbed", () => { - test.todo("EXPECT UserEffect.FetchAllByUserIdPaginated to be called"); + beforeEach(async () => { + UserEffect.FetchAllByUserIdPaginated = jest.fn() + .mockResolvedValue([ + [], + 0, + ]); + + await EffectHelper.GenerateEffectEmbed("userId", 1); + }); + + test("EXPECT UserEffect.FetchAllByUserIdPaginated to be called", () => { + expect(UserEffect.FetchAllByUserIdPaginated).toHaveBeenCalledTimes(1); + expect(UserEffect.FetchAllByUserIdPaginated).toHaveBeenCalledWith("userId", 0, 10); + }); describe("GIVEN there are no effects returned", () => { - test.todo("EXPECT embed generated"); + let result: any; - test.todo("EXPECT row generated"); + beforeEach(async () => { + UserEffect.FetchAllByUserIdPaginated = jest.fn() + .mockResolvedValue([ + [], + 0, + ]); + + result = await EffectHelper.GenerateEffectEmbed("userId", 1); + }); + + test("EXPECT result returned", () => { + expect(result).toMatchSnapshot(); + }); }); describe("GIVEN there are effects returned", () => { - test.todo("EXPECT embed generated"); + let result: any; - test.todo("EXPECT row generated"); + beforeEach(async () => { + UserEffect.FetchAllByUserIdPaginated = jest.fn() + .mockResolvedValue([ + [ + { + Name: "name", + Unused: 1, + }, + ], + 1, + ]); + + result = await EffectHelper.GenerateEffectEmbed("userId", 1); + }); + + test("EXPECT result returned", () => { + expect(result).toMatchSnapshot(); + }); describe("AND it is the first page", () => { - test.todo("EXPECT Previous button to be disabled"); + beforeEach(async () => { + result = await EffectHelper.GenerateEffectEmbed("userId", 1) + }); + + test("EXPECT Previous button to be disabled", () => { + const button = result.row.components[0].data; + + expect(button).toBeDefined(); + expect(button.label).toBe("Previous"); + expect(button.disabled).toBe(true); + }); }); describe("AND it is the last page", () => { - test.todo("EXPECT Next button to be disabled"); + beforeEach(async () => { + result = await EffectHelper.GenerateEffectEmbed("userId", 1) + }); + + test("EXPECT Next button to be disabled", () => { + const button = result.row.components[1].data; + + expect(button).toBeDefined(); + expect(button.label).toBe("Next"); + expect(button.disabled).toBe(true); + }); }); }); }); diff --git a/tests/helpers/__snapshots__/EffectHelper.test.ts.snap b/tests/helpers/__snapshots__/EffectHelper.test.ts.snap new file mode 100644 index 0000000..6484acd --- /dev/null +++ b/tests/helpers/__snapshots__/EffectHelper.test.ts.snap @@ -0,0 +1,71 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`GenerateEffectEmbed GIVEN there are effects returned EXPECT result returned 1`] = ` +{ + "embed": { + "color": 3166394, + "description": "name x1", + "footer": { + "icon_url": undefined, + "text": "Page 1 of 1", + }, + "title": "Effects", + }, + "row": { + "components": [ + { + "custom_id": "effects list 0", + "disabled": true, + "emoji": undefined, + "label": "Previous", + "style": 1, + "type": 2, + }, + { + "custom_id": "effects list 2", + "disabled": true, + "emoji": undefined, + "label": "Next", + "style": 1, + "type": 2, + }, + ], + "type": 1, + }, +} +`; + +exports[`GenerateEffectEmbed GIVEN there are no effects returned EXPECT result returned 1`] = ` +{ + "embed": { + "color": 3166394, + "description": "*none*", + "footer": { + "icon_url": undefined, + "text": "Page 1 of 1", + }, + "title": "Effects", + }, + "row": { + "components": [ + { + "custom_id": "effects list 0", + "disabled": true, + "emoji": undefined, + "label": "Previous", + "style": 1, + "type": 2, + }, + { + "custom_id": "effects list 2", + "disabled": true, + "emoji": undefined, + "label": "Next", + "style": 1, + "type": 2, + }, + ], + "type": 1, + }, +} +`; -- 2.45.2 From 74295005404c99b7fcc920cb680fdb5aea05c9a7 Mon Sep 17 00:00:00 2001 From: Ethan Lane Date: Fri, 6 Dec 2024 18:08:15 +0000 Subject: [PATCH 14/15] Fix linting --- tests/buttonEvents/Effects.test.ts | 12 ++++++------ tests/commands/effects.test.ts | 25 +++++++++++++------------ tests/helpers/EffectHelper.test.ts | 21 +++++++++++++++++---- 3 files changed, 36 insertions(+), 22 deletions(-) diff --git a/tests/buttonEvents/Effects.test.ts b/tests/buttonEvents/Effects.test.ts index 26f172c..557e64a 100644 --- a/tests/buttonEvents/Effects.test.ts +++ b/tests/buttonEvents/Effects.test.ts @@ -4,17 +4,17 @@ import EffectHelper from "../../src/helpers/EffectHelper"; describe("execute", () => { describe("GIVEN action in custom id is list", () => { - let interaction = { + const interaction = { customId: "effects list", } as unknown as ButtonInteraction; - let listSpy: any; + let listSpy: jest.SpyInstance; beforeAll(async () => { const effects = new Effects(); - listSpy = jest.spyOn(effects as any, "List") - .mockImplementation(() => {}); + listSpy = jest.spyOn(effects as unknown as {"List": () => object}, "List") + .mockImplementation(); await effects.execute(interaction); }); @@ -27,7 +27,7 @@ describe("execute", () => { }); describe("List", () => { - let interaction: any; + let interaction: ButtonInteraction; const embed = { name: "Embed", @@ -45,7 +45,7 @@ describe("List", () => { }, update: jest.fn(), reply: jest.fn(), - }; + } as unknown as ButtonInteraction; }); describe("GIVEN page is a valid number", () => { diff --git a/tests/commands/effects.test.ts b/tests/commands/effects.test.ts index 8819209..8985477 100644 --- a/tests/commands/effects.test.ts +++ b/tests/commands/effects.test.ts @@ -1,3 +1,4 @@ +import {ChatInputCommandInteraction} from "discord.js"; import Effects from "../../src/commands/effects"; import EffectHelper from "../../src/helpers/EffectHelper"; @@ -15,18 +16,18 @@ describe("constructor", () => { describe("execute", () => { describe("GIVEN interaction is not a chat input command", () => { - let interaction: any; + let interaction: ChatInputCommandInteraction; - let listSpy: any; + let listSpy: jest.SpyInstance; beforeEach(async () => { interaction = { isChatInputCommand: jest.fn().mockReturnValue(false), - }; + } as unknown as ChatInputCommandInteraction; const effects = new Effects(); - listSpy = jest.spyOn(effects as any, "List") + listSpy = jest.spyOn(effects as unknown as {"List": () => object}, "List") .mockImplementation(); await effects.execute(interaction); @@ -42,9 +43,9 @@ describe("execute", () => { }); describe("GIVEN subcommand is list", () => { - let interaction: any; + let interaction: ChatInputCommandInteraction; - let listSpy: any; + let listSpy: jest.SpyInstance; beforeEach(async () => { interaction = { @@ -52,11 +53,11 @@ describe("execute", () => { options: { getSubcommand: jest.fn().mockReturnValue("list"), }, - }; + } as unknown as ChatInputCommandInteraction; const effects = new Effects(); - listSpy = jest.spyOn(effects as any, "List") + listSpy = jest.spyOn(effects as unknown as {"List": () => object}, "List") .mockImplementation(); await effects.execute(interaction); @@ -74,8 +75,8 @@ describe("execute", () => { }); describe("List", () => { - let effects: Effects = new Effects(); - let interaction: any; + const effects: Effects = new Effects(); + let interaction: ChatInputCommandInteraction; const embed = { name: "embed", @@ -95,7 +96,7 @@ describe("List", () => { user: { id: "userId", }, - }; + } as unknown as ChatInputCommandInteraction; const effects = new Effects(); @@ -104,7 +105,7 @@ describe("List", () => { row, }); - jest.spyOn(effects as any, "List") + jest.spyOn(effects as unknown as {"List": () => object}, "List") .mockImplementation(); }); diff --git a/tests/helpers/EffectHelper.test.ts b/tests/helpers/EffectHelper.test.ts index bdfe5a1..13dca37 100644 --- a/tests/helpers/EffectHelper.test.ts +++ b/tests/helpers/EffectHelper.test.ts @@ -1,3 +1,4 @@ +import {ActionRowBuilder, ButtonBuilder, EmbedBuilder} from "discord.js"; import UserEffect from "../../src/database/entities/app/UserEffect"; import EffectHelper from "../../src/helpers/EffectHelper"; @@ -297,7 +298,10 @@ describe("GenerateEffectEmbed", () => { }); describe("GIVEN there are no effects returned", () => { - let result: any; + let result: { + embed: EmbedBuilder, + row: ActionRowBuilder, + }; beforeEach(async () => { UserEffect.FetchAllByUserIdPaginated = jest.fn() @@ -315,7 +319,10 @@ describe("GenerateEffectEmbed", () => { }); describe("GIVEN there are effects returned", () => { - let result: any; + let result: { + embed: EmbedBuilder, + row: ActionRowBuilder, + }; beforeEach(async () => { UserEffect.FetchAllByUserIdPaginated = jest.fn() @@ -342,7 +349,10 @@ describe("GenerateEffectEmbed", () => { }); test("EXPECT Previous button to be disabled", () => { - const button = result.row.components[0].data; + const button = result.row.components[0].data as unknown as { + label: string, + disabled: boolean + }; expect(button).toBeDefined(); expect(button.label).toBe("Previous"); @@ -356,7 +366,10 @@ describe("GenerateEffectEmbed", () => { }); test("EXPECT Next button to be disabled", () => { - const button = result.row.components[1].data; + const button = result.row.components[1].data as unknown as { + label: string, + disabled: boolean + }; expect(button).toBeDefined(); expect(button.label).toBe("Next"); -- 2.45.2 From 399e9af75a03117228f4a1c1dd8bd5a24bba38bd Mon Sep 17 00:00:00 2001 From: Ethan Lane Date: Sat, 7 Dec 2024 17:29:33 +0000 Subject: [PATCH 15/15] Fix some improvements suggested --- src/commands/effects.ts | 2 +- src/helpers/EffectHelper.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commands/effects.ts b/src/commands/effects.ts index aa58d3b..bcaa929 100644 --- a/src/commands/effects.ts +++ b/src/commands/effects.ts @@ -33,7 +33,7 @@ export default class Effects extends Command { private async List(interaction: CommandInteraction) { const pageOption = interaction.options.get("page"); - const page = pageOption && Number(pageOption.value) ? Number(pageOption.value) : 1; + const page = !isNaN(Number(pageOption?.value)) ? Number(pageOption?.value) : 1; const result = await EffectHelper.GenerateEffectEmbed(interaction.user.id, page); diff --git a/src/helpers/EffectHelper.ts b/src/helpers/EffectHelper.ts index 11f6ed8..d0d29a0 100644 --- a/src/helpers/EffectHelper.ts +++ b/src/helpers/EffectHelper.ts @@ -80,7 +80,7 @@ export default class EffectHelper { .setCustomId(`effects list ${page - 1}`) .setLabel("Previous") .setStyle(ButtonStyle.Primary) - .setDisabled(page - 1 == 0), + .setDisabled(page == 1), new ButtonBuilder() .setCustomId(`effects list ${page + 1}`) .setLabel("Next") -- 2.45.2