import { expect, test } from 'bun:test'; import { applyAction, seatIds, setupGame, usersOf } from '../help-kit.test-util'; import { GAMES } from '@bg/games'; import { helpNow } from './help'; const ids = seatIds(7); const users = usersOf(ids); const view = (s: unknown, who: string | null) => GAMES.mafia!.view(s, who) as any; const T = 1_700_000_000_000; function game() { const { state } = setupGame('mafia', 7, { mafiaCount: 2 }); const byRole = (r: string) => state.players.filter((p: any) => p.role === r).map((p: any) => p.id as string); return { state, mafia: byRole('mafia'), police: byRole('police')[0]!, doctor: byRole('doctor')[0]!, citizen: byRole('citizen')[0]! }; } const night = { kind: 'night', startedAt: T, endsAt: T + 30_000, mafiaTarget: null, mafiaPickedBy: null, investigate: null, heal: null, mediumPeek: null }; test('마피아: 직업 확인 / 관전', () => { const { state, citizen } = game(); expect(helpNow(view(state, citizen), citizen, users)).toContain('[내 직업] 카드를 꾹'); const spec = helpNow(view(state, null), null, users); expect(spec).toContain('관전 중에는 전체 채팅과 발표만'); expect(spec).not.toContain('마피아예요'); }); test('마피아: 밤 안내는 내 직업에 맞게, 남의 직업은 말하지 않아요', () => { const g = game(); const s = { ...g.state, day: 1, phase: night }; const [m1, m2] = g.mafia; const mt = helpNow(view(s, m1!), m1!, users); expect(mt).toContain('나는 마피아예요'); expect(mt).toContain(`동료 마피아는 ${users[m2!]!.nickname}님`); expect(helpNow(view(s, g.police), g.police, users)).toContain('나는 경찰이에요'); expect(helpNow(view(s, g.doctor), g.doctor, users)).toContain('나는 의사예요'); const c = helpNow(view(s, g.citizen), g.citizen, users); expect(c).toContain('지금은 할 일이 없어요'); for (const t of [c, helpNow(view(s, null), null, users)]) { expect(t).not.toContain('마피아예요'); expect(t).not.toContain('경찰'); expect(t).not.toContain('의사'); } // 마피아가 고르면 동료에게 대상과 고른 사람이 보여요. const s2 = applyAction('mafia', s, m1!, { type: 'mafiaTarget', target: g.citizen }); expect(helpNow(view(s2, m2!), m2!, users)).toContain(`${users[m1!]!.nickname}님이 골랐어요`); }); test('마피아: 지목 투표 → 찬반 투표, 탈락한 사람', () => { const g = game(); const vote = { ...g.state, day: 2, phase: { kind: 'vote', startedAt: T, endsAt: T + 20_000, votes: {} } }; expect(helpNow(view(vote, g.citizen), g.citizen, users)).toContain('[기권]'); const confirm = { ...g.state, day: 2, phase: { kind: 'confirm', startedAt: T, endsAt: T + 10_000, accused: g.citizen, ballots: {} } }; expect(helpNow(view(confirm, g.police), g.police, users)).toContain('[찬성 (탈락)]'); expect(helpNow(view(confirm, g.citizen), g.citizen, users)).toContain('나를 탈락시킬지'); expect(helpNow(view(confirm, null), null, users)).toContain(`${users[g.citizen]!.nickname}님을 탈락시킬지`); const dead = { ...confirm, players: g.state.players.map((p: any) => (p.id === g.doctor ? { ...p, alive: false, diedDay: 1, diedBy: 'mafia' } : p)) }; expect(helpNow(view(dead, g.doctor), g.doctor, users)).toContain('[죽은 사람] 채팅'); });