import { describe, it, expect } from 'vitest'; import { generateDeckFromInventory, createStatusCard, createDeckRegions, createPlayerDeck, generateCardId, } from '@/samples/slay-the-spire-like/deck/factory'; import { createGridInventory, placeItem } from '@/samples/slay-the-spire-like/grid-inventory'; import type { GridInventory, InventoryItem } from '@/samples/slay-the-spire-like/grid-inventory'; import type { GameItemMeta } from '@/samples/slay-the-spire-like/progress/types'; import { IDENTITY_TRANSFORM } from '@/samples/slay-the-spire-like/utils/shape-collision'; import { parseShapeString } from '@/samples/slay-the-spire-like/utils/parse-shape'; /** * Helper: create a minimal GameItemMeta for testing. */ function createTestMeta(name: string, desc: string, shapeStr: string): GameItemMeta { const shape = parseShapeString(shapeStr); return { itemData: { type: 'weapon', name, shape: shapeStr, costType: 'energy', costCount: 1, targetType: 'single', price: 10, desc, }, 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('createStatusCard', () => { it('should create a card with null sourceItemId and itemData', () => { const card = createStatusCard('wound-1', '伤口', '无法被弃牌'); expect(card.id).toBe('wound-1'); expect(card.sourceItemId).toBeNull(); expect(card.itemData).toBeNull(); expect(card.cellKey).toBeNull(); expect(card.displayName).toBe('伤口'); expect(card.description).toBe('无法被弃牌'); }); it('should have empty region and position', () => { const card = createStatusCard('stun-1', '眩晕', '跳过出牌阶段'); 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.drawPile.length).toBe(6); expect(deck.hand).toEqual([]); expect(deck.discardPile).toEqual([]); expect(deck.exhaustPile).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.sourceItemId === 'dagger-1' ); const shieldCards = Object.values(deck.cards).filter( c => c.sourceItemId === 'shield-1' ); expect(daggerCards.length).toBe(2); expect(shieldCards.length).toBe(4); // Verify item data expect(daggerCards[0].itemData?.name).toBe('短刀'); expect(shieldCards[0].itemData?.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.displayName).toBeTruthy(); expect(card.description).toBeTruthy(); } const daggerCard = Object.values(deck.cards).find( c => c.itemData?.name === '短刀' ); expect(daggerCard?.displayName).toBe('短刀'); expect(daggerCard?.description).toBe('【攻击3】【攻击3】'); }); it('should assign unique cell keys to each card from same item', () => { const inv = createTestInventory(); const deck = generateDeckFromInventory(inv); const daggerCards = Object.values(deck.cards).filter( c => c.sourceItemId === 'dagger-1' ); const cellKeys = daggerCards.map(c => c.cellKey); const uniqueKeys = new Set(cellKeys); expect(uniqueKeys.size).toBe(cellKeys.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.drawPile).toEqual([]); }); it('should place all cards in draw pile initially', () => { const inv = createTestInventory(); const deck = generateDeckFromInventory(inv); for (const cardId of deck.drawPile) { expect(deck.cards[cardId]).toBeDefined(); } // All cards are in draw pile expect(new Set(deck.drawPile).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.drawPile).toEqual([]); expect(deck.hand).toEqual([]); expect(deck.discardPile).toEqual([]); expect(deck.exhaustPile).toEqual([]); }); }); });