boardgame.io
Version:
library for turn-based games
231 lines (196 loc) • 6.35 kB
JavaScript
/*
* Copyright 2017 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 { createStore } from 'redux';
import { SocketIOTransport } from './socketio';
import { makeMove } from '../../core/action-creators';
import { CreateGameReducer } from '../../core/reducer';
import { InitializeGame } from '../../core/initialize';
import * as Actions from '../../core/action-types';
class MockSocket {
constructor() {
this.callbacks = {};
this.emit = jest.fn();
}
receive(type, ...args) {
this.callbacks[type](args[0], args[1]);
}
on(type, callback) {
this.callbacks[type] = callback;
}
close() {}
}
test('defaults', () => {
const m = new SocketIOTransport();
expect(typeof m.callback).toBe('function');
m.callback();
});
describe('update gameID / playerID', () => {
const socket = new MockSocket();
const m = new SocketIOTransport({ socket });
m.store = { dispatch: () => {} };
beforeEach(() => (socket.emit = jest.fn()));
test('gameID', () => {
m.updateGameID('test');
expect(m.gameID).toBe('test');
expect(socket.emit).lastCalledWith('sync', 'test', null, 2);
});
test('playerID', () => {
m.updatePlayerID('player');
expect(m.playerID).toBe('player');
expect(socket.emit).lastCalledWith('sync', 'test', 'player', 2);
});
});
describe('connection status', () => {
let onChangeMock;
let mockSocket;
let m;
beforeEach(() => {
onChangeMock = jest.fn();
mockSocket = new MockSocket();
m = new SocketIOTransport({
socket: mockSocket,
gameID: 0,
playerID: 0,
gameName: 'foo',
numPlayers: 2,
});
m.subscribe(onChangeMock);
m.connect();
});
test('connect', () => {
mockSocket.callbacks['connect']();
expect(onChangeMock).toHaveBeenCalled();
expect(m.isConnected).toBe(true);
});
test('disconnect', () => {
mockSocket.callbacks['disconnect']();
expect(onChangeMock).toHaveBeenCalled();
expect(m.isConnected).toBe(false);
});
test('close socket', () => {
mockSocket.callbacks['connect']();
expect(m.isConnected).toBe(true);
m.disconnect();
expect(m.isConnected).toBe(false);
});
});
describe('multiplayer', () => {
const mockSocket = new MockSocket();
const m = new SocketIOTransport({ socket: mockSocket });
m.connect();
const game = {};
let store = null;
beforeEach(() => {
const reducer = CreateGameReducer({ game });
const initialState = InitializeGame({ game });
m.store = store = createStore(reducer, initialState);
});
test('returns a valid store', () => {
expect(store).not.toBe(undefined);
});
test('receive update', () => {
const restored = { restore: true };
expect(store.getState()).not.toMatchObject(restored);
mockSocket.receive('update', 'unknown gameID', restored);
expect(store.getState()).not.toMatchObject(restored);
mockSocket.receive('update', 'default', restored);
expect(store.getState()).not.toMatchObject(restored);
// Only if the stateID is not stale.
restored._stateID = 1;
mockSocket.receive('update', 'default', restored);
expect(store.getState()).toMatchObject(restored);
});
test('receive sync', () => {
const restored = { restore: true };
expect(store.getState()).not.toMatchObject(restored);
mockSocket.receive('sync', 'unknown gameID', { state: restored });
expect(store.getState()).not.toMatchObject(restored);
mockSocket.receive('sync', 'default', { state: restored });
expect(store.getState()).toMatchObject(restored);
});
test('send update', () => {
const action = makeMove();
const state = { _stateID: 0 };
m.onAction(state, action);
expect(mockSocket.emit).lastCalledWith(
'update',
action,
state._stateID,
'default',
null
);
});
});
describe('server option', () => {
const hostname = 'host';
const port = '1234';
test('without protocol', () => {
const server = hostname + ':' + port;
const m = new SocketIOTransport({ server });
m.connect();
expect(m.socket.io.engine.hostname).toEqual(hostname);
expect(m.socket.io.engine.port).toEqual(port);
expect(m.socket.io.engine.secure).toEqual(false);
});
test('without trailing slash', () => {
const server = 'http://' + hostname + ':' + port;
const m = new SocketIOTransport({ server });
m.connect();
expect(m.socket.io.uri).toEqual(server + '/default');
});
test('https', () => {
const serverWithProtocol = 'https://' + hostname + ':' + port + '/';
const m = new SocketIOTransport({ server: serverWithProtocol });
m.connect();
expect(m.socket.io.engine.hostname).toEqual(hostname);
expect(m.socket.io.engine.port).toEqual(port);
expect(m.socket.io.engine.secure).toEqual(true);
});
test('http', () => {
const serverWithProtocol = 'http://' + hostname + ':' + port + '/';
const m = new SocketIOTransport({ server: serverWithProtocol });
m.connect();
expect(m.socket.io.engine.hostname).toEqual(hostname);
expect(m.socket.io.engine.port).toEqual(port);
expect(m.socket.io.engine.secure).toEqual(false);
});
test('no server set', () => {
const m = new SocketIOTransport();
m.connect();
expect(m.socket.io.engine.hostname).not.toEqual(hostname);
expect(m.socket.io.engine.port).not.toEqual(port);
});
});
test('changing a gameID resets the state before resync', () => {
const m = new SocketIOTransport();
const game = {};
const store = createStore(CreateGameReducer({ game }));
m.store = store;
const dispatchSpy = jest.spyOn(store, 'dispatch');
m.updateGameID('foo');
expect(dispatchSpy).toHaveBeenCalledWith(
expect.objectContaining({
type: Actions.RESET,
clientOnly: true,
})
);
});
test('changing a playerID resets the state before resync', () => {
const m = new SocketIOTransport();
const game = {};
const store = createStore(CreateGameReducer({ game }));
m.store = store;
const dispatchSpy = jest.spyOn(store, 'dispatch');
m.updatePlayerID('foo');
expect(dispatchSpy).toHaveBeenCalledWith(
expect.objectContaining({
type: Actions.RESET,
clientOnly: true,
})
);
});