fix(frontend): prefer canonical phase for client action gating
This commit is contained in:
@@ -16,6 +16,7 @@ function jsonResponse(status: number, body: unknown) {
|
||||
function sessionDetailPayload(
|
||||
status: string,
|
||||
options?: {
|
||||
currentPhase?: string;
|
||||
answers?: string[];
|
||||
players?: Array<{ id: number; nickname: string; score: number }>;
|
||||
roundQuestionId?: number | null;
|
||||
@@ -79,6 +80,7 @@ function sessionDetailPayload(
|
||||
},
|
||||
phase_view_model: {
|
||||
status,
|
||||
current_phase: options?.currentPhase ?? status,
|
||||
round_number: 1,
|
||||
players_count: (options?.players ?? []).length,
|
||||
constraints: {
|
||||
@@ -87,6 +89,10 @@ function sessionDetailPayload(
|
||||
min_players_reached: true,
|
||||
max_players_allowed: true,
|
||||
},
|
||||
readiness: {
|
||||
question_ready: (options?.currentPhase ?? status) !== 'lobby',
|
||||
scoreboard_ready: (options?.currentPhase ?? status) === 'reveal' || (options?.currentPhase ?? status) === 'scoreboard',
|
||||
},
|
||||
host: {
|
||||
can_start_round: false,
|
||||
can_show_question: false,
|
||||
@@ -97,10 +103,10 @@ function sessionDetailPayload(
|
||||
can_finish_game: false,
|
||||
},
|
||||
player: {
|
||||
can_join: status === 'lobby',
|
||||
can_submit_lie: status === 'lie',
|
||||
can_submit_guess: status === 'guess',
|
||||
can_view_final_result: status === 'finished',
|
||||
can_join: (options?.currentPhase ?? status) === 'lobby',
|
||||
can_submit_lie: (options?.currentPhase ?? status) === 'lie',
|
||||
can_submit_guess: (options?.currentPhase ?? status) === 'guess',
|
||||
can_view_final_result: (options?.currentPhase ?? status) === 'finished',
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -437,6 +443,34 @@ describe('PlayerShellComponent gameplay wiring', () => {
|
||||
expect(values.get('wpp.session-context')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('prefers canonical current_phase for player reveal panel and routing when status lags behind', async () => {
|
||||
const fetchMock: FetchMock = vi.fn().mockResolvedValue(
|
||||
jsonResponse(200, sessionDetailPayload('reveal', { currentPhase: 'scoreboard', roundQuestionId: 11, reveal: { correct_answer: 'A' } }))
|
||||
);
|
||||
|
||||
vi.stubGlobal('fetch', fetchMock);
|
||||
|
||||
const replaceState = vi.fn();
|
||||
const localStorage = { getItem: vi.fn().mockReturnValue(null), setItem: vi.fn(), removeItem: vi.fn() };
|
||||
vi.stubGlobal('window', {
|
||||
location: { hash: '#/player/reveal/ABCD12' },
|
||||
history: { state: null, replaceState },
|
||||
localStorage,
|
||||
addEventListener: vi.fn(),
|
||||
removeEventListener: vi.fn(),
|
||||
});
|
||||
|
||||
const component = new PlayerShellComponent();
|
||||
component.sessionCode = 'ABCD12';
|
||||
|
||||
await component.refreshSession();
|
||||
|
||||
expect(component.gameplayPhase).toBe('scoreboard');
|
||||
expect(component.showRevealPanel).toBe(true);
|
||||
expect(component.showGuessControls).toBe(false);
|
||||
expect(replaceState).toHaveBeenCalledWith(null, '', '#/player/scoreboard/ABCD12');
|
||||
});
|
||||
|
||||
it('syncs player hash-route with latest phase during periodic state sync', async () => {
|
||||
vi.useFakeTimers();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user