import { describe, it, expect } from "vitest"; import { generateDeckFromInventory, createCard, createDeckRegions, createPlayerDeck, generateCardId, } from "@/samples/slay-the-spire-like/system/deck/factory"; import { createGridInventory, placeItem, } from "@/samples/slay-the-spire-like/system/grid-inventory"; import type { GridInventory, InventoryItem, } from "@/samples/slay-the-spire-like/system/grid-inventory"; import type { GameItemMeta } from "@/samples/slay-the-spire-like/system/grid-inventory/types"; import { IDENTITY_TRANSFORM } from "@/samples/slay-the-spire-like/system/utils/shape-collision"; import { parseShapeString } from "@/samples/slay-the-spire-like/system/utils/parse-shape"; import type { CardData, ItemData, } from "@/samples/slay-the-spire-like/system/types"; /** * Helper: create a minimal CardData for testing. */ function createTestCardData(id: string, name: string, desc: string): CardData { return { id, name, desc, type: "item", costType: "energy", costCount: 1, targetType: "enemy", effects: [], }; } /** * Helper: create a minimal ItemData for testing. */ function createTestItemData( id: string, name: string, shapeStr: string, desc: string, ): ItemData { return { id, type: "weapon", name, shape: shapeStr, card: createTestCardData(id, name, desc), price: 10, description: desc, }; } /** * Helper: create a minimal GameItemMeta for testing. */ function createTestMeta( name: string, desc: string, shapeStr: string, ): GameItemMeta { const shape = parseShapeString(shapeStr); const itemData = createTestItemData(name.toLowerCase(), name, shapeStr, desc); return { itemData, shape, }; } /** * Helper: create a test inventory with some items. */ function createTestInventory(): GridInventory { const inv = createGridInventory(6, 4); // Item "短刀" with shape "oe" (2 cells) const meta1 = createTestMeta("短刀", "【攻击3】【攻击3】", "oe"); const item1: InventoryItem = { id: "dagger-1", shape: meta1.shape, transform: { ...IDENTITY_TRANSFORM, offset: { x: 0, y: 0 } }, meta: meta1, }; placeItem(inv, item1); // Item "盾" with shape "oesw" (4 cells) const meta2 = createTestMeta("盾", "【防御3】", "oesw"); const item2: InventoryItem = { id: "shield-1", shape: meta2.shape, transform: { ...IDENTITY_TRANSFORM, offset: { x: 3, y: 0 } }, meta: meta2, }; placeItem(inv, item2); return inv; } describe("deck/factory", () => { describe("generateCardId", () => { it("should generate deterministic unique IDs", () => { expect(generateCardId("item-1", 0)).toBe("card-item-1-0"); expect(generateCardId("item-1", 1)).toBe("card-item-1-1"); expect(generateCardId("item-2", 0)).toBe("card-item-2-0"); }); }); describe("createCard", () => { it("should create a card with itemId and cardData", () => { const cardData = createTestCardData("wound", "伤口", "无法被弃牌"); const card = createCard("wound-1", cardData, 0); expect(card.id).toBe("card-wound-1-0"); expect(card.itemId).toBe("wound-1"); expect(card.cardData).toBe(cardData); expect(card.regionId).toBe(""); expect(card.position).toEqual([]); }); }); describe("generateDeckFromInventory", () => { it("should generate correct number of cards based on shape cell counts", () => { const inv = createTestInventory(); // "短刀" has 2 cells, "盾" has 4 cells = 6 total const deck = generateDeckFromInventory(inv); expect(Object.keys(deck.cards).length).toBe(6); expect(deck.regions.drawPile.childIds.length).toBe(6); expect(deck.regions.hand.childIds).toEqual([]); expect(deck.regions.discardPile.childIds).toEqual([]); expect(deck.regions.exhaustPile.childIds).toEqual([]); }); it("should link cards to their source items", () => { const inv = createTestInventory(); const deck = generateDeckFromInventory(inv); const daggerCards = Object.values(deck.cards).filter( (c) => c.itemId === "dagger-1", ); const shieldCards = Object.values(deck.cards).filter( (c) => c.itemId === "shield-1", ); expect(daggerCards.length).toBe(2); expect(shieldCards.length).toBe(4); // Verify card data expect(daggerCards[0].cardData.name).toBe("短刀"); expect(shieldCards[0].cardData.name).toBe("盾"); }); it("should set displayName and description from item data", () => { const inv = createTestInventory(); const deck = generateDeckFromInventory(inv); for (const card of Object.values(deck.cards)) { expect(card.cardData.name).toBeTruthy(); expect(card.cardData.desc).toBeTruthy(); } const daggerCard = Object.values(deck.cards).find( (c) => c.cardData.name === "短刀", ); expect(daggerCard?.cardData.name).toBe("短刀"); expect(daggerCard?.cardData.desc).toBe("【攻击3】【攻击3】"); }); it("should assign unique IDs to each card from same item", () => { const inv = createTestInventory(); const deck = generateDeckFromInventory(inv); const daggerCards = Object.values(deck.cards).filter( (c) => c.itemId === "dagger-1", ); const ids = daggerCards.map((c) => c.id); const uniqueIds = new Set(ids); expect(uniqueIds.size).toBe(ids.length); }); it("should handle empty inventory", () => { const inv = createGridInventory(6, 4); const deck = generateDeckFromInventory(inv); expect(Object.keys(deck.cards).length).toBe(0); expect(deck.regions.drawPile.childIds).toEqual([]); }); it("should place all cards in draw pile initially", () => { const inv = createTestInventory(); const deck = generateDeckFromInventory(inv); for (const cardId of deck.regions.drawPile.childIds) { expect(deck.cards[cardId]).toBeDefined(); } // All cards are in draw pile expect(new Set(deck.regions.drawPile.childIds).size).toBe( Object.keys(deck.cards).length, ); }); }); describe("createDeckRegions", () => { it("should create regions for all deck zones", () => { const regions = createDeckRegions(); expect(regions.drawPile.id).toBe("drawPile"); expect(regions.hand.id).toBe("hand"); expect(regions.discardPile.id).toBe("discardPile"); expect(regions.exhaustPile.id).toBe("exhaustPile"); }); it("should have empty childIds initially", () => { const regions = createDeckRegions(); expect(regions.drawPile.childIds).toEqual([]); expect(regions.hand.childIds).toEqual([]); expect(regions.discardPile.childIds).toEqual([]); expect(regions.exhaustPile.childIds).toEqual([]); }); }); describe("createPlayerDeck", () => { it("should create an empty deck structure", () => { const deck = createPlayerDeck(); expect(deck.cards).toEqual({}); expect(deck.regions.drawPile.childIds).toEqual([]); expect(deck.regions.hand.childIds).toEqual([]); expect(deck.regions.discardPile.childIds).toEqual([]); expect(deck.regions.exhaustPile.childIds).toEqual([]); }); }); });