import { act, render, RenderResult } from '@testing-library/react'; import React from 'react'; import { masterSet, stateSet } from '../actions'; import { masterStateUpdateTimeout } from '../constants/system'; import { DispatchContext, StateContext } from '../context/state'; import { GlobalState, initialState, nullPlayer } from '../reducer'; import { useMaster } from './master'; describe(useMaster.name, () => { const dispatch = jest.fn(); const TestComponent: React.FC = () => { useMaster(); return null; }; const setup = (state: GlobalState, options: Partial = {}): RenderResult => render( , options, ); describe('when there is no master initially', () => { const stateNoMaster: GlobalState = { ...initialState, myClientName: 'my-client-name', player: { ...nullPlayer, master: '', }, }; it('should take control of master', () => { expect.assertions(2); jest.useFakeTimers(); const { unmount } = setup(stateNoMaster); act(() => { jest.runOnlyPendingTimers(); }); expect(dispatch).toHaveBeenCalledTimes(1); expect(dispatch).toHaveBeenCalledWith(stateSet({ master: 'my-client-name' })); unmount(); jest.useRealTimers(); }); }); describe('when master goes away', () => { const stateWithMaster: GlobalState = { ...initialState, myClientName: 'my-client-name', clientList: [ { name: 'master-client-a', lastPing: 0 }, { name: 'my-client-name', lastPing: 0 }, { name: 'other-slave-client', lastPing: 0 }, ], player: { songId: 123, playing: true, currentTime: 17, seekTime: -1, master: 'master-client-a', activeClients: [], queue: [], }, }; const stateMasterWentAway: GlobalState = { ...stateWithMaster, clientList: [ { name: 'my-client-name', lastPing: 0 }, { name: 'other-slave-client', lastPing: 0 }, ], }; it('should take control of master after a delay, and pause the client', () => { expect.assertions(2); jest.useFakeTimers(); const { container, unmount } = setup(stateWithMaster); act(() => { setup(stateMasterWentAway, { container }); }); expect(dispatch).not.toHaveBeenCalled(); act(() => { jest.runAllTimers(); }); expect(dispatch).toHaveBeenCalledWith(masterSet()); unmount(); jest.useRealTimers(); }); describe('and a third client takes over control', () => { const stateMasterWentAwayAnotherTookControl: GlobalState = { ...stateMasterWentAway, clientList: [ { name: 'my-client-name', lastPing: 0 }, { name: 'other-slave-client', lastPing: 0 }, ], player: { ...stateMasterWentAway.player, master: 'other-slave-client', }, }; it('should not take control of master', () => { expect.assertions(1); jest.useFakeTimers(); const { container, unmount } = setup(stateWithMaster); act(() => { setup(stateMasterWentAway, { container }); }); setImmediate(() => { act(() => { setup(stateMasterWentAwayAnotherTookControl, { container }); }); }); act(() => { jest.runAllTimers(); }); expect(dispatch).not.toHaveBeenCalled(); unmount(); jest.useRealTimers(); }); }); }); describe('when the client is master', () => { const stateMaster: GlobalState = { ...initialState, myClientName: 'the-master-client', clientList: [{ name: 'the-master-client', lastPing: 0 }], player: { ...nullPlayer, master: 'the-master-client', }, }; it('should continually refresh the server with the current state', () => { expect.assertions(6); const clock = jest.useFakeTimers(); const { unmount } = setup(stateMaster); act(() => { clock.runOnlyPendingTimers(); }); dispatch.mockClear(); act(() => { clock.runTimersToTime(masterStateUpdateTimeout - 1); }); expect(dispatch).toHaveBeenCalledTimes(0); act(() => { clock.runTimersToTime(1); }); expect(dispatch).toHaveBeenCalledTimes(1); expect(dispatch).toHaveBeenCalledWith(stateSet()); dispatch.mockClear(); expect(dispatch).toHaveBeenCalledTimes(0); act(() => { clock.runTimersToTime(masterStateUpdateTimeout); }); expect(dispatch).toHaveBeenCalledTimes(1); expect(dispatch).toHaveBeenCalledWith(stateSet()); unmount(); jest.useRealTimers(); }); }); describe('when the client is a slave', () => { const stateSlave: GlobalState = { ...initialState, myClientName: 'a-slave-client', clientList: [ { name: 'the-master-client', lastPing: 0 }, { name: 'a-slave-client', lastPing: 0 }, ], player: { ...nullPlayer, master: 'the-master-client', }, }; it('should not send state updates periodically', () => { expect.assertions(1); const clock = jest.useFakeTimers(); const { unmount } = setup(stateSlave); act(() => { clock.runTimersToTime(masterStateUpdateTimeout); }); expect(dispatch).not.toHaveBeenCalled(); unmount(); jest.useRealTimers(); }); }); });