Prepare game updates and single-process PM2 deployment
This commit is contained in:
@@ -1,9 +1,13 @@
|
||||
import { DEFAULT_GLOBAL_SETTINGS } from '../constants/global-settings.js';
|
||||
import { matchIntroductionDuration, matchIntroductionMessage } from '../src/shared/match-introduction.js';
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import type { GameApi } from '../src/client/api.js';
|
||||
import { createLobbyFeatures } from '../src/client/lobby-features.js';
|
||||
import { normalizeAppearance, type FighterAppearance } from '../src/shared/cosmetics.js';
|
||||
import type { PlayerProfile } from '../src/shared/ranked-types.js';
|
||||
import { createMatchEngine } from '../src/shared/match-engine.js';
|
||||
import { RANKED_POLL_MS } from '../constants/ranked.js';
|
||||
|
||||
// A selector/event surface, not an HTML parser or rendering implementation.
|
||||
// The test invokes the production selection/equip handlers and observes the
|
||||
@@ -34,6 +38,57 @@ function documentSurface() {
|
||||
return document;
|
||||
}
|
||||
|
||||
for (const initiallyQueued of [false, true]) test(`ranked introduction follows server countdown (queued: ${initiallyQueued})`, async t => {
|
||||
const document = documentSurface(), original = Object.getOwnPropertyDescriptor(globalThis, 'document');
|
||||
Object.defineProperty(globalThis, 'document', { configurable: true, value: document });
|
||||
t.after(() => {
|
||||
if (original) Object.defineProperty(globalThis, 'document', original);
|
||||
else Reflect.deleteProperty(globalThis, 'document');
|
||||
});
|
||||
t.mock.timers.enable({ apis: ['setTimeout'] });
|
||||
let now = 0;
|
||||
const engine = createMatchEngine({ matchId: 'ranked-intro', mode: 'friend', selfIndex: 0, clock: { now: () => now } });
|
||||
engine.start(matchIntroductionDuration(DEFAULT_GLOBAL_SETTINGS, true));
|
||||
const profile: PlayerProfile = {
|
||||
playerId: 'player-a', displayName: 'Alpha', appearance: normalizeAppearance(undefined),
|
||||
stats: { wins: 0, losses: 0, draws: 0, current: 0, best: 0 }, unlockedColors: ['white'], unlockedEquipment: [],
|
||||
};
|
||||
const screens: string[] = [];
|
||||
const api = { async request(path: string) {
|
||||
if (path === '/api/profile') return profile;
|
||||
if (path === '/api/ranked/cancel' || path === '/api/ranked/leave') return { status: 'idle', profile };
|
||||
if (initiallyQueued && path === '/api/ranked/queue') return { status: 'queued', profile };
|
||||
if (now >= engine.view().startsAt) engine.beginTurn();
|
||||
return { status: 'matched', profile, match: { ...engine.view(), ranked: true } };
|
||||
} } as unknown as GameApi;
|
||||
const lobby = createLobbyFeatures({ api, onQueue: message => screens.push(message),
|
||||
onMatch: state => screens.push(matchIntroductionMessage(state, state.clockNow) ?? state.phase), onHome: () => screens.push('home'),
|
||||
onAppearance() {}, onAvailabilityChange() {}, onError(error) { throw error; },
|
||||
});
|
||||
const flush = async () => { for (let i = 0; i < 5; i++) await Promise.resolve(); };
|
||||
const advance = async (ms: number) => {
|
||||
for (let elapsed = 0; elapsed < ms; elapsed += RANKED_POLL_MS) {
|
||||
now += RANKED_POLL_MS; t.mock.timers.tick(RANKED_POLL_MS); await flush();
|
||||
}
|
||||
};
|
||||
await lobby.initialize(false);
|
||||
document.getElementById('ranked-start').onclick!();
|
||||
assert.equal(screens.at(-1), '랭킹대전 매칭 중…', 'show search before the queue response');
|
||||
await flush();
|
||||
assert.equal(screens.includes('countdown'), false);
|
||||
await advance(DEFAULT_GLOBAL_SETTINGS.matchingDelayMs);
|
||||
assert.equal(screens.at(-1), '랭킹대전 매칭 성공!');
|
||||
await advance(DEFAULT_GLOBAL_SETTINGS.matchSuccessDelayMs);
|
||||
assert.equal(screens.at(-1), '랭킹대전 준비 중…');
|
||||
await advance(3000);
|
||||
assert.equal(screens.at(-1), 'selecting');
|
||||
assert.equal(engine.view().deadline - now, engine.view().settings!.turnDurationMs, 'retain the full first turn');
|
||||
await lobby.leave();
|
||||
const count = screens.length;
|
||||
await advance(1000);
|
||||
assert.equal(screens.length, count, 'leaving stops presentation updates');
|
||||
});
|
||||
|
||||
test('a profile read started before equip cannot undo the saved appearance', async t => {
|
||||
const document = documentSurface(), original = Object.getOwnPropertyDescriptor(globalThis, 'document');
|
||||
Object.defineProperty(globalThis, 'document', { configurable: true, value: document });
|
||||
|
||||
Reference in New Issue
Block a user