import { describe, expect, it } from 'vitest'; import { ALL_CARDS, buildDeck, Card } from '@core/cards/Card'; import { Rank } from '@core/cards/Rank'; import { Suit } from '@core/cards/Suit'; describe('Card', () => { it('holds exactly 36 distinct cards', () => { expect(ALL_CARDS).toHaveLength(36); expect(new Set(ALL_CARDS.map((c) => c.id)).size).toBe(36); expect(new Set(ALL_CARDS.map((c) => c.code)).size).toBe(36); }); it('starts at six — there are no low cards in a 36-card deck', () => { expect(Math.min(...ALL_CARDS.map((c) => c.rank))).toBe(Rank.Six); expect(Math.max(...ALL_CARDS.map((c) => c.rank))).toBe(Rank.Ace); }); it('is a flyweight, so identity comparison works', () => { expect(Card.of(Suit.Hearts, Rank.Queen)).toBe(Card.of(Suit.Hearts, Rank.Queen)); expect(Card.of(Suit.Hearts, Rank.Queen)).not.toBe(Card.of(Suit.Spades, Rank.Queen)); }); it('round-trips through its id', () => { for (const card of ALL_CARDS) { expect(Card.byId(card.id)).toBe(card); } expect(() => Card.byId(36)).toThrow(RangeError); expect(() => Card.byId(-1)).toThrow(RangeError); }); it('round-trips through its code', () => { for (const card of ALL_CARDS) { expect(Card.parse(card.code)).toBe(card); } }); it('parses letter codes as well as symbols', () => { expect(Card.parse('6c')).toBe(Card.of(Suit.Clubs, Rank.Six)); expect(Card.parse('10H')).toBe(Card.of(Suit.Hearts, Rank.Ten)); expect(Card.parse(' KS ')).toBe(Card.of(Suit.Spades, Rank.King)); expect(Card.parseAll('6C 10H KS')).toEqual([ Card.of(Suit.Clubs, Rank.Six), Card.of(Suit.Hearts, Rank.Ten), Card.of(Suit.Spades, Rank.King), ]); }); it('rejects nonsense codes', () => { expect(() => Card.parse('6X')).toThrow(SyntaxError); expect(() => Card.parse('5C')).toThrow(SyntaxError); expect(() => Card.parse('')).toThrow(SyntaxError); }); it('names art assets the way the source files are named', () => { expect(Card.of(Suit.Spades, Rank.Ace).assetName).toBe('ace_of_spades'); expect(Card.of(Suit.Clubs, Rank.Ten).assetName).toBe('10_of_clubs'); expect(Card.of(Suit.Diamonds, Rank.Jack).assetName).toBe('jack_of_diamonds'); }); it('builds a fresh mutable deck that does not alias the registry', () => { const deck = buildDeck(); expect(deck).toHaveLength(36); deck.pop(); expect(ALL_CARDS).toHaveLength(36); }); });