fix: join invited room after nickname registration
This commit is contained in:
@@ -12,18 +12,26 @@ import { emptyPlayerStats } from '../src/shared/p2p-types.js';
|
||||
const source = readFileSync('src/client/main.ts', 'utf8').replace(/^import .*;\r?\n/gm, '');
|
||||
const compiled = ts.transpileModule(source, { compilerOptions: { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.None } }).outputText;
|
||||
const flush = async () => { for (let i = 0; i < 8; i++) await new Promise(r => setImmediate(r)); };
|
||||
async function setup(kind: 'expired' | 'orphan' | 'rematch-wait' | 'result') {
|
||||
async function setup(kind: 'expired' | 'orphan' | 'rematch-wait' | 'result' | 'invitation' | 'invitation-expired') {
|
||||
const invited = kind.startsWith('invitation');
|
||||
let saveNickname!: () => void;
|
||||
const nicknameSaved = new Promise<void>(resolve => { saveNickname = resolve; });
|
||||
const mem = new Map<string, string>(), calls: string[] = [], polls = new Map<number, () => void>(), launched: string[] = [];
|
||||
const active = { matchId: 'm', seq: 1, mode: 'friend', roomId: 'r' };
|
||||
const session: any = { gameSessionId: 's', token: 't'.repeat(43), stats: emptyPlayerStats(), lastAppliedSeq: kind === 'orphan' ? 0 : 1, ...(kind === 'orphan' ? { activeMatch: active } : {}) };
|
||||
const room: any = { id: 'r', code: 'ABCDEF', currentMatchId: 'm', round: 1, selfIndex: 0, status: 'paired', count: 2, expiresAt: Date.now() + 60000, rematch: [kind === 'rematch-wait', false] };
|
||||
let open = true, pollId = 0;
|
||||
mem.set(GAME_ROOM_KEY, JSON.stringify({ gameSessionId: 's', room }));
|
||||
if (kind !== 'orphan') mem.set(GAME_CHECKPOINT_KEY, JSON.stringify({ gameSessionId: 's', activeMatch: active, checkpoint: { state: {
|
||||
if (!invited) mem.set(GAME_ROOM_KEY, JSON.stringify({ gameSessionId: 's', room }));
|
||||
if (!invited && kind !== 'orphan') mem.set(GAME_CHECKPOINT_KEY, JSON.stringify({ gameSessionId: 's', activeMatch: active, checkpoint: { state: {
|
||||
matchId: 'm', mode: 'friend', phase: 'finished', selfIndex: 0, version: 1, finished: { saved: true, stats: session.stats, reason: 'hit' }, rematch: room.rematch, saveStatus: 'saved',
|
||||
} } }));
|
||||
const send: typeof fetch = async (input, init) => {
|
||||
const path = String(input); calls.push(path); let data: any = session;
|
||||
if (path === '/api/rooms/join') {
|
||||
assert.equal(JSON.parse(String(init?.body)).code, 'ABCDEF');
|
||||
if (kind === 'invitation-expired') return new Response(JSON.stringify({ ok: false, code: 'ROOM_EXPIRED', message: '방이 만료되었습니다.' }), { status: 410 });
|
||||
data = { ...room, selfIndex: 1 };
|
||||
}
|
||||
if (path === '/api/rooms/r' || path.endsWith('/leave')) {
|
||||
if (kind === 'expired') return new Response(JSON.stringify({ ok: false, code: 'ROOM_EXPIRED', message: 'expired' }), { status: 410 });
|
||||
if (path.endsWith('/leave')) open = false;
|
||||
@@ -46,13 +54,33 @@ async function setup(kind: 'expired' | 'orphan' | 'rematch-wait' | 'result') {
|
||||
createClientAnalytics: () => ({ ready() {}, screen() {}, gameFinished() {}, gameStarted() {}, error() {}, click() {} }),
|
||||
createResultReporter: (api: any) => ({ destroy() {}, resumePending: async () => {}, submit: api.record }),
|
||||
createMatchView: () => ({ update() {}, destroy() {} }), createPeer: () => ({}),
|
||||
createLobbyFeatures: () => ({ initialize: async () => false, setReady() {}, active: false }),
|
||||
createLobbyFeatures: () => ({ initialize: async () => { if (invited) await nicknameSaved; return false; }, setReady() {}, active: false }),
|
||||
createMatchController: (config: any) => { launched.push(config.activeMatch.matchId); return { subscribe() {}, start: async () => {}, view: () => ({ matchId: config.activeMatch.matchId }), destroy() {} }; },
|
||||
setTimeout(fn: () => void) { polls.set(++pollId, fn); return pollId; }, clearTimeout(id: number) { polls.delete(id); },
|
||||
};
|
||||
if (invited) ctx.location.search = '?room=abcdef';
|
||||
vm.createContext(ctx); vm.runInContext(compiled + '\nglobalThis.review={leave,boot,inspect:()=>({ready,currentScreen,currentRoom,currentMatch})};', ctx); await flush();
|
||||
return { ctx, calls, polls, room, mem, launched, open: () => open };
|
||||
return { ctx, calls, polls, room, mem, launched, saveNickname, nodes, open: () => open };
|
||||
}
|
||||
|
||||
test('an invitation joins the host room immediately after first-time nickname registration', async () => {
|
||||
const h = await setup('invitation');
|
||||
assert.equal(h.calls.includes('/api/rooms/join'), false, 'wait for nickname registration');
|
||||
h.saveNickname(); await flush();
|
||||
assert.equal(h.calls.filter(path => path === '/api/rooms/join').length, 1);
|
||||
assert.deepEqual(h.launched, ['m']);
|
||||
assert.equal(h.ctx.review.inspect().currentRoom.selfIndex, 1);
|
||||
});
|
||||
|
||||
test('an expired invitation shows the join form and error after nickname registration', async () => {
|
||||
const h = await setup('invitation-expired');
|
||||
h.saveNickname(); await flush();
|
||||
assert.equal(h.calls.filter(path => path === '/api/rooms/join').length, 1);
|
||||
assert.equal(h.ctx.review.inspect().currentScreen, 'join');
|
||||
assert.equal(h.ctx.review.inspect().ready, true);
|
||||
assert.equal(h.nodes.get('notice').textContent, '방이 만료되었습니다.');
|
||||
assert.deepEqual(h.launched, []);
|
||||
});
|
||||
test('already-expired room does not trap a restored result on exit', async () => {
|
||||
const h = await setup('expired'); await h.ctx.review.leave();
|
||||
assert.equal(h.ctx.review.inspect().currentScreen, 'home'); assert.equal(h.mem.has(GAME_ROOM_KEY), false);
|
||||
|
||||
Reference in New Issue
Block a user