import { describe, test, expect, spyOn, beforeEach, mock } from "bun:test"; import { handleItemWizardInteraction, renderWizard } from "./item_wizard"; import { ButtonInteraction, ModalSubmitInteraction, StringSelectMenuInteraction } from "discord.js"; // Mock Setup const valuesMock = mock((_args: any) => Promise.resolve()); const insertMock = mock(() => ({ values: valuesMock })); mock.module("@shared/db/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"); }); });