boardgame-core/tests/samples/slay-the-spire-like/deck/factory.test.ts

245 lines
7.2 KiB
TypeScript

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: "single",
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<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("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<GameItemMeta>(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([]);
});
});
});