mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-06-12 09:04:53 -07:00
fix join game
This commit is contained in:
parent
2afa2922e9
commit
515dff6d7b
2 changed files with 70 additions and 5 deletions
|
|
@ -7,13 +7,21 @@ import {
|
||||||
connectedWithRoomsState,
|
connectedWithRoomsState,
|
||||||
} from '../../../__test-utils__';
|
} from '../../../__test-utils__';
|
||||||
import { App, Data } from '@app/types';
|
import { App, Data } from '@app/types';
|
||||||
|
import { GameTypes } from '@app/store';
|
||||||
import GameSelector from './GameSelector';
|
import GameSelector from './GameSelector';
|
||||||
|
|
||||||
const { mockUseWebClient } = vi.hoisted(() => ({ mockUseWebClient: vi.fn() }));
|
const { mockUseWebClient, mockNavigate } = vi.hoisted(() => ({
|
||||||
|
mockUseWebClient: vi.fn(),
|
||||||
|
mockNavigate: vi.fn(),
|
||||||
|
}));
|
||||||
vi.mock('@app/hooks', async (importOriginal) => {
|
vi.mock('@app/hooks', async (importOriginal) => {
|
||||||
const actual = await importOriginal<typeof import('@app/hooks')>();
|
const actual = await importOriginal<typeof import('@app/hooks')>();
|
||||||
return { ...actual, useWebClient: mockUseWebClient };
|
return { ...actual, useWebClient: mockUseWebClient };
|
||||||
});
|
});
|
||||||
|
vi.mock('react-router-dom', async (importOriginal) => {
|
||||||
|
const actual = await importOriginal<typeof import('react-router-dom')>();
|
||||||
|
return { ...actual, useNavigate: () => mockNavigate };
|
||||||
|
});
|
||||||
|
|
||||||
function makeRoomEntry(games: Data.ServerInfo_Game[] = [], gametypeMap: Record<number, string> = {}) {
|
function makeRoomEntry(games: Data.ServerInfo_Game[] = [], gametypeMap: Record<number, string> = {}) {
|
||||||
return {
|
return {
|
||||||
|
|
@ -81,6 +89,7 @@ function buildState(
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockUseWebClient.mockReset();
|
mockUseWebClient.mockReset();
|
||||||
|
mockNavigate.mockReset();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('GameSelector', () => {
|
describe('GameSelector', () => {
|
||||||
|
|
@ -209,6 +218,38 @@ describe('GameSelector', () => {
|
||||||
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
|
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('clicking Join on a game already present in games.games navigates to /game without sending a command', () => {
|
||||||
|
const client = makeWebClient();
|
||||||
|
mockUseWebClient.mockReturnValue(client);
|
||||||
|
const game = makeGame({ gameId: 7, withPassword: false });
|
||||||
|
const room = makeRoomEntry([game]);
|
||||||
|
const state = buildState(room, makeUser(), 7);
|
||||||
|
(state as any).games = { games: { 7: { info: { gameId: 7 } } } };
|
||||||
|
renderWithProviders(<GameSelector room={room as any} />, { preloadedState: state });
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: /^Join$/ }));
|
||||||
|
|
||||||
|
expect(client.request.rooms.joinGame).not.toHaveBeenCalled();
|
||||||
|
expect(mockNavigate).toHaveBeenCalledWith(App.RouteEnum.GAME);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('dispatching GAME_JOINED navigates to /game (mirrors JOIN_ROOM → /room)', async () => {
|
||||||
|
mockUseWebClient.mockReturnValue(makeWebClient());
|
||||||
|
const room = makeRoomEntry([]);
|
||||||
|
const { store } = renderWithProviders(<GameSelector room={room as any} />, {
|
||||||
|
preloadedState: buildState(room),
|
||||||
|
});
|
||||||
|
|
||||||
|
mockNavigate.mockClear();
|
||||||
|
store.dispatch({
|
||||||
|
type: GameTypes.GAME_JOINED,
|
||||||
|
payload: { data: { gameInfo: { gameId: 42 }, hostId: 0, playerId: 0, spectator: false } },
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.resolve();
|
||||||
|
expect(mockNavigate).toHaveBeenCalledWith(App.RouteEnum.GAME);
|
||||||
|
});
|
||||||
|
|
||||||
it('Join button is disabled while joinGamePending is true even when a game is selected', () => {
|
it('Join button is disabled while joinGamePending is true even when a game is selected', () => {
|
||||||
mockUseWebClient.mockReturnValue(makeWebClient());
|
mockUseWebClient.mockReturnValue(makeWebClient());
|
||||||
const game = makeGame({ gameId: 7 });
|
const game = makeGame({ gameId: 7 });
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,18 @@
|
||||||
import React, { useCallback, useState } from 'react';
|
import React, { useCallback, useState } from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
import Paper from '@mui/material/Paper';
|
import Paper from '@mui/material/Paper';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
|
|
||||||
import { RoomsDispatch, RoomsSelectors, ServerSelectors, useAppSelector } from '@app/store';
|
import {
|
||||||
import { useWebClient } from '@app/hooks';
|
GameSelectors,
|
||||||
import type { App, Enriched } from '@app/types';
|
GameTypes,
|
||||||
|
RoomsDispatch,
|
||||||
|
RoomsSelectors,
|
||||||
|
ServerSelectors,
|
||||||
|
useAppSelector,
|
||||||
|
} from '@app/store';
|
||||||
|
import { useReduxEffect, useWebClient } from '@app/hooks';
|
||||||
|
import { App, type Enriched } from '@app/types';
|
||||||
import { AlertDialog, CreateGameDialog, FilterGamesDialog, PromptDialog } from '@app/dialogs';
|
import { AlertDialog, CreateGameDialog, FilterGamesDialog, PromptDialog } from '@app/dialogs';
|
||||||
|
|
||||||
import OpenGames from '../OpenGames';
|
import OpenGames from '../OpenGames';
|
||||||
|
|
@ -25,6 +33,7 @@ interface PendingPasswordJoin {
|
||||||
const GameSelector = ({ room }: GameSelectorProps) => {
|
const GameSelector = ({ room }: GameSelectorProps) => {
|
||||||
const roomId = room.info.roomId;
|
const roomId = room.info.roomId;
|
||||||
const webClient = useWebClient();
|
const webClient = useWebClient();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const selectedGameId = useAppSelector((state) => RoomsSelectors.getSelectedGameId(state, roomId));
|
const selectedGameId = useAppSelector((state) => RoomsSelectors.getSelectedGameId(state, roomId));
|
||||||
const selectedGame = useAppSelector((state) =>
|
const selectedGame = useAppSelector((state) =>
|
||||||
|
|
@ -36,6 +45,13 @@ const GameSelector = ({ room }: GameSelectorProps) => {
|
||||||
const isJudgeUser = useAppSelector(ServerSelectors.getIsUserJudge);
|
const isJudgeUser = useAppSelector(ServerSelectors.getIsUserJudge);
|
||||||
const joinPending = useAppSelector(RoomsSelectors.getJoinGamePending);
|
const joinPending = useAppSelector(RoomsSelectors.getJoinGamePending);
|
||||||
const joinError = useAppSelector(RoomsSelectors.getJoinGameError);
|
const joinError = useAppSelector(RoomsSelectors.getJoinGameError);
|
||||||
|
const activeGameIds = useAppSelector(GameSelectors.getActiveGameIds);
|
||||||
|
|
||||||
|
// Mirrors Server.tsx's JOIN_ROOM → navigate(ROOM) pattern: when Event_GameJoined
|
||||||
|
// lands, we're actually in the game — route to /game.
|
||||||
|
useReduxEffect(() => {
|
||||||
|
navigate(App.RouteEnum.GAME);
|
||||||
|
}, GameTypes.GAME_JOINED, [navigate]);
|
||||||
|
|
||||||
const [createOpen, setCreateOpen] = useState(false);
|
const [createOpen, setCreateOpen] = useState(false);
|
||||||
const [filterOpen, setFilterOpen] = useState(false);
|
const [filterOpen, setFilterOpen] = useState(false);
|
||||||
|
|
@ -43,6 +59,14 @@ const GameSelector = ({ room }: GameSelectorProps) => {
|
||||||
|
|
||||||
const sendJoin = useCallback(
|
const sendJoin = useCallback(
|
||||||
(gameId: number, asSpectator: boolean, asJudge: boolean, password: string) => {
|
(gameId: number, asSpectator: boolean, asJudge: boolean, password: string) => {
|
||||||
|
// Mirrors Rooms.tsx short-circuit: if we already have a live game entry
|
||||||
|
// (Event_GameJoined has populated games.games[gameId]), skip the duplicate
|
||||||
|
// JoinGame — the server would reject it with RespContextError — and go
|
||||||
|
// straight to the game view.
|
||||||
|
if (activeGameIds.includes(gameId)) {
|
||||||
|
navigate(App.RouteEnum.GAME);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const params: App.JoinGameParams = {
|
const params: App.JoinGameParams = {
|
||||||
gameId,
|
gameId,
|
||||||
password,
|
password,
|
||||||
|
|
@ -52,7 +76,7 @@ const GameSelector = ({ room }: GameSelectorProps) => {
|
||||||
};
|
};
|
||||||
webClient.request.rooms.joinGame(roomId, params);
|
webClient.request.rooms.joinGame(roomId, params);
|
||||||
},
|
},
|
||||||
[roomId, webClient],
|
[activeGameIds, navigate, roomId, webClient],
|
||||||
);
|
);
|
||||||
|
|
||||||
const beginJoin = useCallback(
|
const beginJoin = useCallback(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue