mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-04-27 07:48:01 -07:00
233 lines
No EOL
7.6 KiB
TypeScript
233 lines
No EOL
7.6 KiB
TypeScript
// Room scenarios — Event_ListRooms handling, auto-join, Response_JoinRoom,
|
|
// room chat (inbound + outbound), game list updates, and leaveRoom.
|
|
|
|
import { create } from '@bufbuild/protobuf';
|
|
import { describe, expect, it } from 'vitest';
|
|
|
|
import { Data } from '@app/types';
|
|
import { store } from '@app/store';
|
|
import { RoomCommands } from '@app/websocket';
|
|
|
|
import { connectAndHandshake } from '../helpers/setup';
|
|
import {
|
|
buildResponse,
|
|
buildResponseMessage,
|
|
buildRoomEventMessage,
|
|
buildSessionEventMessage,
|
|
deliverMessage,
|
|
} from '../helpers/protobuf-builders';
|
|
import { findLastSessionCommand, findLastRoomCommand, captureAllOutbound } from '../helpers/command-capture';
|
|
import { fromBinary, hasExtension, getExtension } from '@bufbuild/protobuf';
|
|
|
|
function makeRoom(overrides: Partial<{
|
|
roomId: number;
|
|
name: string;
|
|
autoJoin: boolean;
|
|
}> = {}): Data.ServerInfo_Room {
|
|
return create(Data.ServerInfo_RoomSchema, {
|
|
roomId: overrides.roomId ?? 1,
|
|
name: overrides.name ?? 'Lobby',
|
|
description: 'Test room',
|
|
gameCount: 0,
|
|
playerCount: 0,
|
|
autoJoin: overrides.autoJoin ?? false,
|
|
gameList: [],
|
|
userList: [],
|
|
gametypeList: [],
|
|
});
|
|
}
|
|
|
|
/** Deliver Event_ListRooms then join a single auto-join room, returning the roomId. */
|
|
function setupJoinedRoom(roomId = 1): void {
|
|
deliverMessage(buildSessionEventMessage(
|
|
Data.Event_ListRooms_ext,
|
|
create(Data.Event_ListRoomsSchema, { roomList: [makeRoom({ roomId, autoJoin: true })] })
|
|
));
|
|
const join = findLastSessionCommand(Data.Command_JoinRoom_ext);
|
|
deliverMessage(buildResponseMessage(buildResponse({
|
|
cmdId: join.cmdId,
|
|
responseCode: Data.Response_ResponseCode.RespOk,
|
|
ext: Data.Response_JoinRoom_ext,
|
|
value: create(Data.Response_JoinRoomSchema, { roomInfo: makeRoom({ roomId }) }),
|
|
})));
|
|
}
|
|
|
|
describe('rooms', () => {
|
|
it('populates rooms state from Event_ListRooms', () => {
|
|
connectAndHandshake();
|
|
|
|
const listRooms = create(Data.Event_ListRoomsSchema, {
|
|
roomList: [
|
|
makeRoom({ roomId: 1, name: 'Lobby' }),
|
|
makeRoom({ roomId: 2, name: 'Legacy' }),
|
|
],
|
|
});
|
|
deliverMessage(buildSessionEventMessage(Data.Event_ListRooms_ext, listRooms));
|
|
|
|
const { rooms } = store.getState().rooms;
|
|
expect(rooms[1]?.info?.name).toBe('Lobby');
|
|
expect(rooms[2]?.info?.name).toBe('Legacy');
|
|
});
|
|
|
|
it('auto-joins rooms flagged with autoJoin and flips joinedRoomIds on Response_JoinRoom', () => {
|
|
connectAndHandshake();
|
|
|
|
const listRooms = create(Data.Event_ListRoomsSchema, {
|
|
roomList: [
|
|
makeRoom({ roomId: 1, name: 'Lobby', autoJoin: true }),
|
|
makeRoom({ roomId: 2, name: 'Legacy', autoJoin: false }),
|
|
],
|
|
});
|
|
deliverMessage(buildSessionEventMessage(Data.Event_ListRooms_ext, listRooms));
|
|
|
|
const join = findLastSessionCommand(Data.Command_JoinRoom_ext);
|
|
expect(join.value.roomId).toBe(1);
|
|
|
|
const joined = create(Data.Response_JoinRoomSchema, {
|
|
roomInfo: makeRoom({ roomId: 1, name: 'Lobby' }),
|
|
});
|
|
deliverMessage(buildResponseMessage(buildResponse({
|
|
cmdId: join.cmdId,
|
|
responseCode: Data.Response_ResponseCode.RespOk,
|
|
ext: Data.Response_JoinRoom_ext,
|
|
value: joined,
|
|
})));
|
|
|
|
expect(store.getState().rooms.joinedRoomIds[1]).toBe(true);
|
|
});
|
|
|
|
it('appends a room chat message on Event_RoomSay', () => {
|
|
connectAndHandshake();
|
|
setupJoinedRoom(1);
|
|
|
|
const say = create(Data.Event_RoomSaySchema, {
|
|
name: 'bob',
|
|
message: 'hello world',
|
|
messageType: Data.Event_RoomSay_RoomMessageType.UserMessage,
|
|
});
|
|
deliverMessage(buildRoomEventMessage(1, Data.Event_RoomSay_ext, say));
|
|
|
|
const messages = store.getState().rooms.messages[1];
|
|
expect(messages).toHaveLength(1);
|
|
expect(messages[0].message).toBe('bob: hello world');
|
|
expect(messages[0].name).toBe('bob');
|
|
});
|
|
|
|
it('updates the game list on Event_ListGames', () => {
|
|
connectAndHandshake();
|
|
setupJoinedRoom(1);
|
|
|
|
const game = create(Data.ServerInfo_GameSchema, {
|
|
gameId: 42,
|
|
description: 'Test Game',
|
|
maxPlayers: 4,
|
|
playerCount: 1,
|
|
startTime: 1,
|
|
});
|
|
const listGames = create(Data.Event_ListGamesSchema, { gameList: [game] });
|
|
deliverMessage(buildRoomEventMessage(1, Data.Event_ListGames_ext, listGames));
|
|
|
|
const roomGames = store.getState().rooms.rooms[1]?.games;
|
|
expect(roomGames).toBeDefined();
|
|
expect(roomGames?.[42]?.info?.description).toBe('Test Game');
|
|
expect(roomGames?.[42]?.info?.gameId).toBe(42);
|
|
});
|
|
|
|
it('auto-join filters correctly across multiple rooms', () => {
|
|
connectAndHandshake();
|
|
|
|
deliverMessage(buildSessionEventMessage(
|
|
Data.Event_ListRooms_ext,
|
|
create(Data.Event_ListRoomsSchema, {
|
|
roomList: [
|
|
makeRoom({ roomId: 1, name: 'Lobby', autoJoin: true }),
|
|
makeRoom({ roomId: 2, name: 'Legacy', autoJoin: false }),
|
|
makeRoom({ roomId: 3, name: 'Modern', autoJoin: true }),
|
|
],
|
|
})
|
|
));
|
|
|
|
// Count outbound JoinRoom commands
|
|
const containers = captureAllOutbound();
|
|
const joinCommands: number[] = [];
|
|
for (const container of containers) {
|
|
for (const cmd of container.sessionCommand ?? []) {
|
|
if (hasExtension(cmd, Data.Command_JoinRoom_ext)) {
|
|
joinCommands.push(getExtension(cmd, Data.Command_JoinRoom_ext).roomId);
|
|
}
|
|
}
|
|
}
|
|
expect(joinCommands).toHaveLength(2);
|
|
expect(joinCommands).toContain(1);
|
|
expect(joinCommands).toContain(3);
|
|
expect(joinCommands).not.toContain(2);
|
|
});
|
|
|
|
it('sends outbound Command_RoomSay with trimmed message', () => {
|
|
connectAndHandshake();
|
|
setupJoinedRoom(1);
|
|
|
|
RoomCommands.roomSay(1, ' hello ');
|
|
|
|
const { value } = findLastRoomCommand(Data.Command_RoomSay_ext);
|
|
expect(value.message).toBe('hello');
|
|
});
|
|
|
|
it('removes room from joinedRoomIds on leaveRoom round-trip', () => {
|
|
connectAndHandshake();
|
|
setupJoinedRoom(1);
|
|
expect(store.getState().rooms.joinedRoomIds[1]).toBe(true);
|
|
|
|
RoomCommands.leaveRoom(1);
|
|
|
|
const leave = findLastRoomCommand(Data.Command_LeaveRoom_ext);
|
|
deliverMessage(buildResponseMessage(buildResponse({
|
|
cmdId: leave.cmdId,
|
|
responseCode: Data.Response_ResponseCode.RespOk,
|
|
})));
|
|
|
|
expect(store.getState().rooms.joinedRoomIds[1]).toBeUndefined();
|
|
});
|
|
|
|
it('tracks user join and leave within a room', () => {
|
|
connectAndHandshake();
|
|
setupJoinedRoom(1);
|
|
|
|
deliverMessage(buildRoomEventMessage(1, Data.Event_JoinRoom_ext, create(Data.Event_JoinRoomSchema, {
|
|
userInfo: create(Data.ServerInfo_UserSchema, { name: 'bob' }),
|
|
})));
|
|
|
|
expect(store.getState().rooms.rooms[1]?.users?.bob).toBeDefined();
|
|
|
|
deliverMessage(buildRoomEventMessage(1, Data.Event_LeaveRoom_ext, create(Data.Event_LeaveRoomSchema, {
|
|
name: 'bob',
|
|
})));
|
|
|
|
expect(store.getState().rooms.rooms[1]?.users?.bob).toBeUndefined();
|
|
});
|
|
|
|
it('tracks game creation and join within a room', () => {
|
|
connectAndHandshake();
|
|
setupJoinedRoom(1);
|
|
|
|
RoomCommands.createGame(1, { description: 'Casual', maxPlayers: 2 });
|
|
|
|
const create_ = findLastRoomCommand(Data.Command_CreateGame_ext);
|
|
expect(create_.value.description).toBe('Casual');
|
|
|
|
deliverMessage(buildResponseMessage(buildResponse({
|
|
cmdId: create_.cmdId,
|
|
responseCode: Data.Response_ResponseCode.RespOk,
|
|
})));
|
|
|
|
RoomCommands.joinGame(1, { gameId: 99 });
|
|
|
|
const join = findLastRoomCommand(Data.Command_JoinGame_ext);
|
|
deliverMessage(buildResponseMessage(buildResponse({
|
|
cmdId: join.cmdId,
|
|
responseCode: Data.Response_ResponseCode.RespOk,
|
|
})));
|
|
|
|
expect(store.getState().rooms.joinedGameIds[1]?.[99]).toBe(true);
|
|
});
|
|
}); |