208 lines
7.4 KiB
TypeScript
208 lines
7.4 KiB
TypeScript
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<GameItemMeta> {
|
|
const inv = createGridInventory<GameItemMeta>(6, 4);
|
|
|
|
// Item "短刀" with shape "oe" (2 cells)
|
|
const meta1 = createTestMeta('短刀', '【攻击3】【攻击3】', 'oe');
|
|
const item1: InventoryItem<GameItemMeta> = {
|
|
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<GameItemMeta> = {
|
|
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<GameItemMeta>(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([]);
|
|
});
|
|
});
|
|
});
|