boardgame.io
Version:
library for turn-based games
570 lines (533 loc) • 15.4 kB
JavaScript
/*
* Copyright 2018 The boardgame.io Authors
*
* Use of this source code is governed by a MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
import React from 'react';
import Cookies from 'react-cookies';
import Lobby from './react';
import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
/* mock server requests */
global.fetch = jest.fn().mockReturnValue({ status: 200, json: () => [] });
/* mock 'Client' component */
function NullComponent() {
return '<noscript />';
}
Enzyme.configure({ adapter: new Adapter() });
describe('lobby', () => {
let lobby;
let spy = jest.fn();
let components;
beforeEach(async () => {
components = [
{
board: 'Board1',
game: { name: 'GameName1', minPlayers: 3, maxPlayers: 5 },
},
{ board: 'Board2', game: { name: 'GameName2' } },
{
board: 'Board3',
game: { name: 'GameName3', maxPlayers: 1 },
},
];
});
afterEach(() => {
spy.mockReset();
});
describe('specify servers', () => {
test('gameServer', () => {
const spy = jest.fn();
const lobby = Enzyme.mount(
<Lobby
gameComponents={components}
clientFactory={spy.mockReturnValue(NullComponent)}
gameServer="localhost:9000"
/>
);
lobby.instance()._startGame('GameName1', { numPlayers: 2 });
expect(spy).toBeCalledWith(
expect.objectContaining({
multiplayer: expect.anything(),
})
);
});
});
describe('login/logout', () => {
beforeEach(async () => {
lobby = Enzyme.mount(<Lobby gameComponents={components} />);
});
test('changing prop debug', () => {
lobby.setProps({
debug: !lobby.props().debug,
});
});
describe('login succeeds', () => {
beforeEach(async () => {
lobby
.find('LobbyLoginForm')
.find('input')
.props()
.onChange({ target: { value: 'Mark' } });
});
test('by clicking', () => {
lobby
.find('LobbyLoginForm')
.find('button')
.simulate('click');
expect(lobby.instance().state.playerName).toBe('Mark');
});
test('by pressing enter', () => {
lobby
.find('LobbyLoginForm')
.find('input')
.props()
.onKeyPress({ key: 'Enter' });
expect(lobby.instance().state.playerName).toBe('Mark');
});
});
describe('login fails', () => {
test('if no name entered', () => {
lobby
.find('LobbyLoginForm')
.find('input')
.props()
.onChange({ target: { value: '' } });
lobby
.find('LobbyLoginForm')
.find('button')
.simulate('click');
expect(
lobby
.find('LobbyLoginForm')
.find('.error-msg')
.text()
).not.toBe('');
});
test('invalid key press', () => {
lobby
.find('LobbyLoginForm')
.find('input')
.props()
.onKeyPress({ key: 'Wololo' });
});
});
describe('exiting lobby', () => {
beforeEach(async () => {
lobby.instance().connection.rooms = [
{
gameID: 'gameID1',
players: {
'0': { id: 0, name: 'Bob' },
'1': { id: 1 },
},
gameName: 'GameName1',
},
];
lobby.instance().forceUpdate();
lobby.update();
});
test('disconnect from server', async () => {
lobby.instance().connection.disconnect = spy;
lobby
.find('#lobby-exit')
.find('button')
.simulate('click');
expect(spy).toHaveBeenCalledWith();
});
});
});
describe('refresh during game', () => {
beforeEach(async () => {
// initial state = phase 'play'
Cookies.save(
'lobbyState',
{
phase: 'play',
playerName: 'Bob',
},
{ path: '/' }
);
lobby = Enzyme.mount(<Lobby gameComponents={components} />);
});
test('reset phase to list', async () => {
expect(lobby.instance().state.phase).toBe('list');
});
});
describe('rooms list', () => {
let spyClient = jest.fn();
beforeEach(async () => {
// initial state = logged-in as 'Bob'
Cookies.save(
'lobbyState',
{
phase: 'list',
playerName: 'Bob',
},
{ path: '/' }
);
lobby = Enzyme.mount(
<Lobby
gameComponents={components}
// stub for Client factory
clientFactory={spyClient.mockReturnValue(NullComponent)}
/>
);
});
afterEach(() => {
spyClient.mockReset();
});
describe('creating a room', () => {
beforeEach(async () => {
lobby.instance().connection.rooms = [
{
gameID: 'gameID1',
players: { '0': { id: 0 } },
gameName: 'GameName1',
},
];
lobby.instance().forceUpdate();
lobby.update();
});
test('room with default number of players', () => {
lobby.instance().connection.create = spy;
lobby
.find('LobbyCreateRoomForm')
.find('button')
.simulate('click');
expect(spy).toHaveBeenCalledWith('GameName1', 3);
});
test('room with 2 players', () => {
lobby.instance().connection.create = spy;
lobby
.find('LobbyCreateRoomForm')
.find('select')
.first()
.props()
.onChange({ target: { value: '1' } });
lobby
.find('LobbyCreateRoomForm')
.find('select')
.at(1)
.props()
.onChange({ target: { value: '2' } });
lobby
.find('LobbyCreateRoomForm')
.find('button')
.simulate('click');
expect(spy).toHaveBeenCalledWith('GameName2', 2);
});
test('when server request fails', async () => {
lobby.instance().connection.create = spy.mockImplementation(() => {
throw new Error('fail');
});
await lobby
.find('LobbyCreateRoomForm')
.find('button')
.simulate('click');
expect(
lobby
.find('#instances')
.find('.error-msg')
.text()
).not.toBe('');
});
test('when game has no boundaries on the number of players', async () => {
// select 2nd game
lobby
.find('LobbyCreateRoomForm')
.find('select')
.first()
.props()
.onChange({ target: { value: '1' } });
expect(
lobby
.find('LobbyCreateRoomForm')
.find('select')
.at(1)
.text()
).toBe('1234');
});
test('when game has boundaries on the number of players', async () => {
expect(
lobby
.find('LobbyCreateRoomForm')
.find('select')
.at(1)
.text()
).toBe('345');
});
});
describe('joining a room', () => {
beforeEach(async () => {
lobby.instance().connection.rooms = [
{
gameID: 'gameID1',
players: { '0': { id: 0 } },
gameName: 'GameName1',
},
{
gameID: 'gameID2',
players: { '0': { id: 0, name: 'Bob' } },
gameName: 'GameName1',
},
];
lobby.instance().forceUpdate();
lobby.update();
});
test('when room is empty', () => {
// join 1st room
lobby.instance().connection.join = spy;
lobby
.find('LobbyRoomInstance')
.first()
.find('button')
.simulate('click');
expect(spy).toHaveBeenCalledWith('GameName1', 'gameID1', '0');
});
test('when room is full', () => {
// try 2nd room
expect(
lobby
.find('LobbyRoomInstance')
.at(1)
.text()
).toContain('RUNNING');
});
test('when server request fails', async () => {
lobby.instance().connection.join = spy.mockImplementation(() => {
throw new Error('fail');
});
// join 1st room
await lobby
.find('LobbyRoomInstance')
.first()
.find('button')
.simulate('click');
expect(
lobby
.find('#instances')
.find('.error-msg')
.text()
).not.toBe('');
});
});
describe('leaving a room', () => {
beforeEach(async () => {
lobby.instance().connection.rooms = [
{
gameID: 'gameID1',
players: {
'0': { id: 0, name: 'Bob' },
'1': { id: 1 },
},
gameName: 'GameName1',
},
];
lobby.instance().forceUpdate();
lobby.update();
expect(
lobby
.find('LobbyRoomInstance')
.find('button')
.text()
).toBe('Leave');
});
test('shall leave a room', () => {
// leave room
lobby.instance().connection.leave = spy;
lobby
.find('LobbyRoomInstance')
.find('button')
.simulate('click');
expect(spy).toHaveBeenCalledWith('GameName1', 'gameID1');
});
test('when server request fails', async () => {
lobby.instance().connection.leave = spy.mockImplementation(() => {
throw new Error('fail');
});
await lobby
.find('LobbyRoomInstance')
.find('button')
.simulate('click');
expect(
lobby
.find('#instances')
.find('.error-msg')
.text()
).not.toBe('');
});
});
describe('starting a game', () => {
beforeEach(async () => {
lobby.instance().connection.rooms = [
{
gameID: 'gameID1',
players: {
'0': { id: 0, name: 'Bob', credentials: 'SECRET1' },
'1': { id: 1, name: 'Charly', credentials: 'SECRET2' },
},
gameName: 'GameName1',
},
{
gameID: 'gameID2',
players: { '0': { id: 0, name: 'Alice' } },
gameName: 'GameName2',
},
{
gameID: 'gameID3',
players: { '0': { id: 0, name: 'Bob' } },
gameName: 'GameName3',
},
{
gameID: 'gameID4',
players: { '0': { id: 0, name: 'Zoe' } },
gameName: 'GameNameUnknown',
},
];
lobby.instance().forceUpdate();
lobby.update();
});
test('if player has joined the game', () => {
lobby.instance().connection.playerCredentials = 'SECRET1';
lobby
.find('LobbyRoomInstance')
.first()
.find('button')
.first()
.simulate('click');
expect(lobby.instance().state.runningGame).toEqual({
app: NullComponent,
gameID: 'gameID1',
playerID: '0',
credentials: 'SECRET1',
});
expect(spyClient).toHaveBeenCalledWith({
game: components[0].game,
board: components[0].board,
multiplayer: expect.anything(),
debug: false,
});
});
test('if player is spectator', () => {
lobby
.find('LobbyRoomInstance')
.at(1)
.find('button')
.simulate('click');
expect(lobby.instance().state.runningGame).toEqual({
app: NullComponent,
credentials: undefined,
gameID: 'gameID2',
playerID: '0',
});
});
test('if game is not supported', () => {
lobby
.find('LobbyRoomInstance')
.at(3)
.find('button')
.simulate('click');
expect(spy).not.toHaveBeenCalled();
expect(
lobby
.find('#instances')
.find('.error-msg')
.text()
).not.toBe('');
});
test('if game is monoplayer', () => {
lobby
.find('LobbyRoomInstance')
.at(2)
.find('button')
.first()
.simulate('click');
expect(spy).not.toHaveBeenCalledWith(expect.anything(), {
gameID: 'gameID3',
});
});
});
describe('exiting during game', () => {
beforeEach(async () => {
lobby.instance().connection.rooms = [
{
gameID: 'gameID1',
players: {
'0': { id: 0, name: 'Bob', credentials: 'SECRET1' },
'1': { id: 1, name: 'Charly', credentials: 'SECRET2' },
},
gameName: 'GameName1',
},
];
lobby.instance().forceUpdate();
lobby.update();
});
test('reset game', () => {
lobby.instance().connection.playerCredentials = 'SECRET1';
// start game
lobby
.find('LobbyRoomInstance')
.first()
.find('button')
.first()
.simulate('click');
// exit game
lobby
.find('#game-exit')
.find('button')
.simulate('click');
expect(lobby.instance().state.runningGame).toEqual(null);
expect(lobby.instance().state.phase).toEqual('list');
});
});
describe('custom renderer', () => {
test('should render custom lobby ui', () => {
const lobby = Enzyme.mount(
<Lobby gameComponents={[]} renderer={() => <div>Foo</div>} />
);
expect(lobby.html()).toEqual('<div>Foo</div>');
});
test('should render custom lobby with games list', () => {
const components = [
{ game: { name: 'GameName1' } },
{ game: { name: 'GameName2' } },
];
const CustomLobbyUI = ({ gameComponents }) => (
<div>
{gameComponents
.map(gameComponent => gameComponent.game.name)
.join(',')}
</div>
);
const lobby = Enzyme.mount(
<Lobby
gameComponents={components}
renderer={({ gameComponents }) => (
<CustomLobbyUI gameComponents={gameComponents} />
)}
/>
);
expect(lobby.html()).toEqual('<div>GameName1,GameName2</div>');
});
test('should change lobby phase when click on custom enter button', () => {
const CustomLobbyUI = ({ onEnterLobby }) => (
<button onClick={() => onEnterLobby('Alex')}>Enter</button>
);
const lobby = Enzyme.mount(
<Lobby
gameComponents={[]}
renderer={({ handleEnterLobby }) => (
<CustomLobbyUI onEnterLobby={handleEnterLobby} />
)}
/>
);
lobby.find('button').simulate('click');
expect(lobby.instance().state.phase).toEqual('list');
expect(lobby.instance().state.playerName).toEqual('Alex');
});
});
});
});