Files
Dune/tests/truth-phase-bid-clock.test.ts
T

69 lines
4.0 KiB
TypeScript

import assert from 'node:assert/strict';
import test from 'node:test';
import { advanceAuctionClock, applyAction, hasAuctionClockOpportunity, synchronizeAuctionClock,
viewGame } from '../game/engine';
import { truthBidInventory } from './fixture-truth-bid';
import { advanceTruthTurnBid, bindTruthTurnBid, createTruthTurnBidFixture,
finishTruthTurnBidLot, purchaseTruthTurnBidLot, truthTurnBidClean,
truthTurnBidPlayer } from './fixture-truth-turn-bid';
const epoch = 'phase-bid-test:0';
/** All three lots are original cards reached through the actual Basic auction.
* Earlier cards are purchased through native bids, never staged into a hand. */
function finalLot(answer: 'yes' | 'no') {
const fixture = createTruthTurnBidFixture();
let game = fixture.game;
for (let n = 0; n < 2; n++) game = purchaseTruthTurnBidLot(game, fixture.opponent).game;
assert.equal(game.auction?.index, 2);
game = advanceTruthTurnBid(game, state => truthTurnBidClean(state) &&
state.auction?.active === fixture.target);
game = bindTruthTurnBid(fixture, answer, 4, game).game;
assert.equal(game.auction?.active, fixture.target);
assert.equal(game.auction?.clockVersion, 1);
assert.ok(hasAuctionClockOpportunity(game));
return { fixture, game, card: game.auction!.cards[game.auction!.index].id };
}
void test('a real expiring last-lot clock submits the required phase Yes bid and keeps native payment', () => {
const { fixture: f, game, card } = finalLot('yes');
const original = structuredClone(game);
assert.throws(() => applyAction(game, f.target, { type: 'passBid' }));
assert.deepEqual(game, original);
const guide = viewGame(game, f.target).phaseBidPromiseGuide;
assert.ok(guide); assert.equal(guide.action.type, 'bid'); assert.equal(guide.action.amount, 4);
const armed = synchronizeAuctionClock(game, 1_000, epoch);
assert.equal(advanceAuctionClock(armed, 10_999, epoch), armed);
const expired = advanceAuctionClock(armed, 11_000, epoch);
assert.equal(expired.auction?.bidder, f.target);
assert.equal(expired.auction?.bid, 4);
assert.equal(truthTurnBidPlayer(expired, f.target).spice, truthTurnBidPlayer(game, f.target).spice,
'A deadline declaration does not prepay or waive the native invoice');
assert.ok(expired.phaseBidPromises?.some(row => row.player === f.target && row.minimum === 4 && row.fulfilled));
assert.match(expired.log.at(-1)!.text, /legal bid required by their binding Truthtrance answer was submitted/);
assert.deepEqual(truthBidInventory(expired), f.originalCards);
const settled = finishTruthTurnBidLot(expired);
assert.equal(truthTurnBidPlayer(settled, f.target).spice, truthTurnBidPlayer(game, f.target).spice - 4);
assert.ok(truthTurnBidPlayer(settled, f.target).hand.some(item => item.id === card));
assert.deepEqual(truthBidInventory(settled), f.originalCards);
});
void test('a real expiring last-lot clock preserves phase No and takes the legal pass', () => {
const { fixture: f, game } = finalLot('no');
assert.throws(() => applyAction(game, f.target, { type: 'bid', amount: 4, allyPayment: 0 }));
const expected = applyAction(game, f.target, { type: 'passBid' });
const armed = synchronizeAuctionClock(game, 1_000, epoch);
const expired = advanceAuctionClock(armed, 11_000, epoch);
assert.equal(expired.phase, expected.phase);
assert.deepEqual(expired.auction, expected.auction);
assert.deepEqual(expired.phaseBidPromises, expected.phaseBidPromises);
assert.ok(expired.phaseBidPromises?.some(row => row.player === f.target && !row.answer && !row.fulfilled && !row.released),
'A phase-wide No stays live while other bidders can still act');
assert.match(expired.log.at(-1)!.text, /ten-second bidding time expired; they passed/);
assert.deepEqual(truthBidInventory(expired), f.originalCards);
const closed = advanceTruthTurnBid(expired, state => state.phase !== 3);
assert.ok(closed.phaseBidPromises?.some(row => row.player === f.target && !row.answer && row.fulfilled),
'The No is fulfilled only after the actual Bidding phase closes');
assert.deepEqual(truthBidInventory(closed), f.originalCards);
});