From 064efb0ed2332fb128e0b723368ee2dfc1abc188 Mon Sep 17 00:00:00 2001 From: syntaxbullet Date: Sat, 20 Dec 2025 11:41:53 +0100 Subject: [PATCH] test: add tests for item wizard --- src/modules/admin/item_wizard.test.ts | 261 ++++++++++++++++++++++++++ 1 file changed, 261 insertions(+) create mode 100644 src/modules/admin/item_wizard.test.ts diff --git a/src/modules/admin/item_wizard.test.ts b/src/modules/admin/item_wizard.test.ts new file mode 100644 index 0000000..f6e4783 --- /dev/null +++ b/src/modules/admin/item_wizard.test.ts @@ -0,0 +1,261 @@ +import { describe, test, expect, spyOn, beforeEach, mock } from "bun:test"; +import { handleItemWizardInteraction, renderWizard } from "./item_wizard"; +import { DrizzleClient } from "@/lib/DrizzleClient"; +import { ButtonInteraction, ModalSubmitInteraction, StringSelectMenuInteraction } from "discord.js"; + +// Mock Setup +const valuesMock = mock((_args: any) => Promise.resolve()); +const insertMock = mock(() => ({ values: valuesMock })); + +mock.module("@/lib/DrizzleClient", () => ({ + DrizzleClient: { + insert: insertMock + } +})); + +mock.module("@/db/schema", () => ({ + items: "items_schema" +})); + +describe("ItemWizard", () => { + const userId = "test-user-123"; + + beforeEach(() => { + valuesMock.mockClear(); + insertMock.mockClear(); + // Since draftSession is internal, we can't easily clear it. + // We will use unique user IDs or rely on overwrite behavior. + }); + + // Helper to create base interaction + const createBaseInteraction = (id: string, customId: string) => ({ + user: { id }, + customId, + deferUpdate: mock(() => Promise.resolve()), + editReply: mock(() => Promise.resolve()), + update: mock(() => Promise.resolve()), + showModal: mock(() => Promise.resolve()), + followUp: mock(() => Promise.resolve()), + reply: mock(() => Promise.resolve()), + }); + + test("renderWizard should return initial state for new user", () => { + const result = renderWizard(`new-${Date.now()}`); + expect(result.embeds).toHaveLength(1); + expect(result.embeds[0]?.data.title).toContain("New Item"); + expect(result.components).toHaveLength(2); + }); + + test("handleItemWizardInteraction should handle details modal submit", async () => { + const uid = `user-details-${Date.now()}`; + renderWizard(uid); // Init session + + const interaction = { + ...createBaseInteraction(uid, "createitem_modal_details"), + isButton: () => false, + isStringSelectMenu: () => false, + isModalSubmit: () => true, + isMessageComponent: () => false, + fields: { + getTextInputValue: (key: string) => { + if (key === "name") return "Updated Name"; + if (key === "desc") return "Updated Desc"; + if (key === "rarity") return "Legendary"; + return ""; + } + }, + } as unknown as ModalSubmitInteraction; + + await handleItemWizardInteraction(interaction); + + expect(interaction.deferUpdate).toHaveBeenCalled(); + const result = renderWizard(uid); + expect(result.embeds[0]?.data.title).toContain("Updated Name"); + }); + + test("handleItemWizardInteraction should handle economy modal submit", async () => { + const uid = `user-economy-${Date.now()}`; + renderWizard(uid); + + const interaction = { + ...createBaseInteraction(uid, "createitem_modal_economy"), + isButton: () => false, + isStringSelectMenu: () => false, + isModalSubmit: () => true, + isMessageComponent: () => false, + fields: { + getTextInputValue: (key: string) => (key === "price" ? "500" : "") + }, + } as unknown as ModalSubmitInteraction; + + await handleItemWizardInteraction(interaction); + + const result = renderWizard(uid); + const economyField = result.embeds[0]?.data.fields?.find(f => f.name === "Economy"); + expect(economyField?.value).toContain("500 🪙"); + }); + + test("handleItemWizardInteraction should handle visuals modal submit", async () => { + const uid = `user-visuals-${Date.now()}`; + renderWizard(uid); + + const interaction = { + ...createBaseInteraction(uid, "createitem_modal_visuals"), + isButton: () => false, + isStringSelectMenu: () => false, + isModalSubmit: () => true, + isMessageComponent: () => false, + fields: { + getTextInputValue: (key: string) => { + if (key === "icon") return "http://icon.com"; + if (key === "image") return "http://image.com"; + return ""; + } + }, + } as unknown as ModalSubmitInteraction; + + await handleItemWizardInteraction(interaction); + + const result = renderWizard(uid); + expect(result.embeds[0]?.data.thumbnail?.url).toBe("http://icon.com"); + expect(result.embeds[0]?.data.image?.url).toBe("http://image.com"); + }); + + test("handleItemWizardInteraction should flow through adding an effect", async () => { + const uid = `user-effect-${Date.now()}`; + renderWizard(uid); + + // 1. Start Add Effect + const startInteraction = { + ...createBaseInteraction(uid, "createitem_addeffect_start"), + isButton: () => true, + isStringSelectMenu: () => false, + isModalSubmit: () => false, + isMessageComponent: () => true, + } as unknown as ButtonInteraction; + await handleItemWizardInteraction(startInteraction); + expect(startInteraction.update).toHaveBeenCalled(); // Should show select menu + + // 2. Select Effect Type + const selectInteraction = { + ...createBaseInteraction(uid, "createitem_select_effect_type"), + isButton: () => false, + isStringSelectMenu: () => true, + isModalSubmit: () => false, + isMessageComponent: () => true, + values: ["ADD_XP"] + } as unknown as StringSelectMenuInteraction; + await handleItemWizardInteraction(selectInteraction); + expect(selectInteraction.showModal).toHaveBeenCalled(); // Should show config modal + + // 3. Submit Effect Config Modal + const modalInteraction = { + ...createBaseInteraction(uid, "createitem_modal_effect"), + isButton: () => false, + isStringSelectMenu: () => false, + isModalSubmit: () => true, + isMessageComponent: () => false, + fields: { + getTextInputValue: (key: string) => (key === "amount" ? "1000" : "") + }, + } as unknown as ModalSubmitInteraction; + await handleItemWizardInteraction(modalInteraction); + + // Verify Effect Added + const result = renderWizard(uid); + const effectsField = result.embeds[0]?.data.fields?.find(f => f.name === "Usage Effects"); + expect(effectsField?.value).toContain("ADD_XP"); + expect(effectsField?.value).toContain("1000"); + }); + + test("handleItemWizardInteraction should save item to database", async () => { + const uid = `user-save-${Date.now()}`; + renderWizard(uid); + + // Set name first so we can check it + const nameInteraction = { + ...createBaseInteraction(uid, "createitem_modal_details"), + isButton: () => false, + isStringSelectMenu: () => false, + isModalSubmit: () => true, + isMessageComponent: () => false, + fields: { + getTextInputValue: (key: string) => { + if (key === "name") return "Saved Item"; + if (key === "desc") return "Desc"; + if (key === "rarity") return "Common"; + return ""; + } + }, + } as unknown as ModalSubmitInteraction; + await handleItemWizardInteraction(nameInteraction); + + // Save + const saveInteraction = { + ...createBaseInteraction(uid, "createitem_save"), + isButton: () => true, + isStringSelectMenu: () => false, + isModalSubmit: () => false, + isMessageComponent: () => true, + } as unknown as ButtonInteraction; + + await handleItemWizardInteraction(saveInteraction); + + expect(valuesMock).toHaveBeenCalled(); + const calls = valuesMock.mock.calls as any[]; + if (calls.length > 0) { + const callArgs = calls[0][0]; + expect(callArgs).toMatchObject({ + name: "Saved Item", + description: "Desc", + rarity: "Common", + // Add other fields as needed + }); + } + + expect(saveInteraction.editReply).toHaveBeenCalledWith(expect.objectContaining({ + content: expect.stringContaining("successfully") + })); + }); + + test("handleItemWizardInteraction should cancel and clear session", async () => { + const uid = `user-cancel-${Date.now()}`; + renderWizard(uid); + + const interaction = { + ...createBaseInteraction(uid, "createitem_cancel"), + isButton: () => true, // Technically any component + isStringSelectMenu: () => false, + isModalSubmit: () => false, + isMessageComponent: () => true, + } as unknown as ButtonInteraction; + + await handleItemWizardInteraction(interaction); + + expect(interaction.update).toHaveBeenCalledWith(expect.objectContaining({ + content: expect.stringContaining("cancelled") + })); + + // Verify session is gone by checking if renderWizard returns default New Item + // Let's modify it first + const modInteraction = { + ...createBaseInteraction(uid, "createitem_modal_details"), + isButton: () => false, + isStringSelectMenu: () => false, + isModalSubmit: () => true, + isMessageComponent: () => false, + fields: { + getTextInputValue: (key: string) => (key === "name" ? "Modified" : "x") + }, + } as unknown as ModalSubmitInteraction; + await handleItemWizardInteraction(modInteraction); + + // Now Cancel + await handleItemWizardInteraction(interaction); + + // New render should be "New Item" not "Modified" + const result = renderWizard(uid); + expect(result.embeds[0]?.data.title).toContain("New Item"); + expect(result.embeds[0]?.data.title).not.toContain("Modified"); + }); +});