@idealic/poker-engine
Version:
Poker game engine and hand evaluator
148 lines (131 loc) • 4.96 kB
text/typescript
import { describe, expect, it } from 'vitest';
import * as Poker from '../../../index';
import { BASE_HAND } from './fixtures/baseHand';
/**
* Core Contract Tests for Hand API
*
* Purpose: Validate that Hand namespace methods follow core architectural contracts:
* 1. Immutability - Never mutate input data
* 2. Data Integrity - All transformations preserve information
* 3. Determinism - Same inputs produce same outputs
*
* All tests use BASE_HAND as foundation for consistency
*/
describe('Hand Core Contracts', () => {
describe('Immutability Contract', () => {
it('should never mutate input Hand during operations', () => {
const hand = Poker.Hand(BASE_HAND);
const originalJson = JSON.stringify(hand);
// Test constructor
Poker.Hand(hand);
expect(JSON.stringify(hand)).toBe(originalJson);
// Test with nested modifications
const modified = Poker.Hand({
...hand,
venue: 'TestVenue',
players: [...hand.players, 'David'],
actions: [...hand.actions, 'p1 f'],
antes: [0, 0, 0, 0],
startingStacks: [1000, 1000, 1000, 1000],
blindsOrStraddles: [0, 10, 20, 0],
});
expect(JSON.stringify(hand)).toBe(originalJson);
expect(modified.venue).toBe('TestVenue');
expect(modified.players).toHaveLength(4);
expect(modified.actions).toHaveLength(BASE_HAND.actions.length + 1);
// Test with array modifications
const withArrayMods = Poker.Hand({
...hand,
minBet: (hand.minBet as number) * 2, // Double minBet to match doubled blinds
startingStacks: hand.startingStacks.map(s => s * 2),
blindsOrStraddles: hand.blindsOrStraddles.map(b => b * 2),
});
expect(JSON.stringify(hand)).toBe(originalJson);
expect(withArrayMods.startingStacks).toEqual([2000, 2000, 2000]);
expect(withArrayMods.blindsOrStraddles).toEqual([0, 20, 40]);
// Original arrays should be unchanged
expect(hand.startingStacks).toEqual(BASE_HAND.startingStacks);
expect(hand.blindsOrStraddles).toEqual(BASE_HAND.blindsOrStraddles);
// Test with private fields
const withPrivates = Poker.Hand({
...hand,
_venueIds: ['id1', 'id2', 'id3'],
_customField: 'test',
});
expect(JSON.stringify(hand)).toBe(originalJson);
expect(withPrivates._venueIds).toEqual(['id1', 'id2', 'id3']);
expect(hand._venueIds).toBeUndefined();
});
});
describe('Data Integrity Contract', () => {
it('should preserve all fields when constructing from partial data', () => {
const partial = {
variant: 'NT' as const,
players: ['Alice', 'Bob'],
startingStacks: [1000, 1000],
blindsOrStraddles: [10, 20],
minBet: 20,
antes: [0, 0],
};
const hand = Poker.Hand(partial);
expect(hand.variant).toBe(partial.variant);
expect(hand.players).toEqual(partial.players);
expect(hand.startingStacks).toEqual(partial.startingStacks);
expect(hand.blindsOrStraddles).toEqual(partial.blindsOrStraddles);
expect(hand.minBet).toBe(partial.minBet);
});
it('should maintain all original fields including private ones', () => {
const handWithPrivates = {
...BASE_HAND,
_venueIds: ['id1', 'id2', 'id3'],
_customField: 'preserved',
};
const result = Poker.Hand(handWithPrivates);
expect(result._venueIds).toEqual(['id1', 'id2', 'id3']);
expect(result._customField).toBe('preserved');
});
});
describe('Deterministic Behavior Contract', () => {
it('should produce identical results for identical inputs', () => {
const hand1 = Poker.Hand(BASE_HAND);
const hand2 = Poker.Hand(BASE_HAND);
expect(JSON.stringify(hand1)).toBe(JSON.stringify(hand2));
});
it('should consistently handle variant-specific fields', () => {
const noLimit = Poker.Hand({
variant: 'NT',
players: ['Alice', 'Bob'],
startingStacks: [1000, 1000],
blindsOrStraddles: [10, 20],
minBet: 20,
antes: [0, 0],
});
const fixedLimit = Poker.Hand({
variant: 'FT',
players: ['Alice', 'Bob'],
startingStacks: [1000, 1000],
blindsOrStraddles: [10, 20],
smallBet: 20,
bigBet: 40,
antes: [0, 0],
});
const stud = Poker.Hand({
variant: 'F7S',
players: ['Alice', 'Bob'],
blindsOrStraddles: [10, 20],
startingStacks: [1000, 1000],
antes: [5, 5],
bringIn: 10,
smallBet: 20,
bigBet: 40,
});
expect(noLimit.variant).toBe('NT');
expect(noLimit.minBet).toBe(20);
expect(fixedLimit.variant).toBe('FT');
expect(fixedLimit.smallBet).toBe(20);
expect(fixedLimit.bigBet).toBe(40);
expect(stud.variant).toBe('F7S');
expect(stud.bringIn).toBe(10);
});
});
});