mirror of
https://github.com/kuhyx/testsAndMisc-archive.git
synced 2026-11-10 19:27:05 +01:00
useGame.test.tsx (494) splits by describe-block into reducer/session/debt files; handEval.test.ts (269) splits at the evaluate7 boundary; useGame.ts (268) sheds its state and reducer into gameState.ts; handFlow.ts (252) sheds its betting-state helpers into handFlowBetting.ts. useGame.ts and handFlow.ts re-export what they moved, so no importer changed. The shared test helpers live in src/test/ rather than beside the source they exercise. That is load-bearing: vite.config.ts already excludes src/test/ from coverage, so no new exclusion entry was needed. Putting them next to the source first dropped coverage to 98.57% because v8 counted the fixtures' unused branches as production code -- fixed by moving them, not by excluding them. Verified: 220 tests still pass, coverage back to 100% on statements, branches, functions and lines, and npm run build succeeds. poker-stakes violations 4 -> 0; repo total 196 -> 192. invariants.test.ts hit its 5s timeout once mid-work. It passes standalone in 2.56s and on re-run, so it is a pre-existing contention flake under parallel coverage load rather than a regression here -- noted, not papered over. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
64 lines
2.6 KiB
TypeScript
64 lines
2.6 KiB
TypeScript
import type { HandResult, HandStep } from '../engine/handFlow'
|
|
import type { SessionState } from '../engine/session'
|
|
import type { DebtLoad } from '../stakes/storage'
|
|
import type { BuyIn, SettledDebt } from '../stakes/types'
|
|
/**
|
|
* Only meaningful while `session !== null`. `betweenHands` is a real, UI-visible pause point — no
|
|
* hand is in progress, `session.stacks` is trustworthy, and `cashOut` is only ever legal here. A
|
|
* hand result stays visible (`lastResult`) until the next hand actually starts, so HandLog has
|
|
* something to render. This is deliberately NOT auto-advanced past by `submitPlayerAction` — see
|
|
* `dealHand` below for why cash-out mid-hand would be a settlement exploit if it were.
|
|
*/
|
|
export type HandPhase = { kind: 'awaiting'; step: HandStep } | { kind: 'betweenHands'; lastResult: HandResult | null }
|
|
|
|
export interface GameState {
|
|
debtLoad: DebtLoad
|
|
buyIn: BuyIn | null
|
|
session: SessionState | null
|
|
handPhase: HandPhase | null
|
|
}
|
|
|
|
type GameAction =
|
|
| { type: 'buyIn'; buyIn: BuyIn; session: SessionState }
|
|
| { type: 'handPhaseChanged'; session: SessionState; handPhase: HandPhase }
|
|
| { type: 'cashedOut'; session: SessionState }
|
|
| { type: 'debtSettled'; debts: SettledDebt[] }
|
|
| { type: 'debtProgressLogged'; debts: SettledDebt[] }
|
|
|
|
export const initialGameState: GameState = {
|
|
debtLoad: { status: 'ok', debts: [] },
|
|
buyIn: null,
|
|
session: null,
|
|
handPhase: null,
|
|
}
|
|
|
|
/**
|
|
* Pure state transitions. All randomness/ids/clock reads happen in the hook, never in here.
|
|
* `GameAction` is exhaustively handled — TS errors on a missing case instead of a silent
|
|
* fallthrough, which is why there is no `default` branch.
|
|
*/
|
|
export const gameReducer = (state: GameState, action: GameAction): GameState => {
|
|
switch (action.type) {
|
|
case 'buyIn':
|
|
return { ...state, buyIn: action.buyIn, session: action.session, handPhase: null }
|
|
case 'handPhaseChanged':
|
|
return { ...state, session: action.session, handPhase: action.handPhase }
|
|
case 'cashedOut':
|
|
return { ...state, session: action.session }
|
|
case 'debtSettled':
|
|
return {
|
|
...state,
|
|
debtLoad: { status: 'ok', debts: action.debts },
|
|
buyIn: null,
|
|
session: null,
|
|
handPhase: null,
|
|
}
|
|
case 'debtProgressLogged':
|
|
return { ...state, debtLoad: { status: 'ok', debts: action.debts } }
|
|
}
|
|
}
|
|
|
|
/** An unpaid debt (owedUnits not yet fully paid) blocks starting a new session — a hard gate. */
|
|
export const hasOutstandingDebt = (debtLoad: DebtLoad): boolean =>
|
|
debtLoad.status === 'unreadable' || debtLoad.debts.some((d) => d.paidUnits < d.owedUnits)
|