master.spec.tsx 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. import { act, render, RenderResult } from '@testing-library/react';
  2. import React from 'react';
  3. import { masterSet, stateSet } from '../actions';
  4. import { masterStateUpdateTimeout } from '../constants/system';
  5. import { DispatchContext, StateContext } from '../context/state';
  6. import { GlobalState, initialState, nullPlayer } from '../reducer';
  7. import { useMaster } from './master';
  8. describe(useMaster.name, () => {
  9. const dispatch = jest.fn();
  10. const TestComponent: React.FC = () => {
  11. useMaster();
  12. return null;
  13. };
  14. const setup = (state: GlobalState, options: Partial<RenderResult> = {}): RenderResult =>
  15. render(
  16. <StateContext.Provider value={state}>
  17. <DispatchContext.Provider value={dispatch}>
  18. <TestComponent />
  19. </DispatchContext.Provider>
  20. </StateContext.Provider>,
  21. options,
  22. );
  23. describe('when there is no master initially', () => {
  24. const stateNoMaster: GlobalState = {
  25. ...initialState,
  26. myClientName: 'my-client-name',
  27. player: {
  28. ...nullPlayer,
  29. master: '',
  30. },
  31. };
  32. it('should take control of master', () => {
  33. expect.assertions(2);
  34. jest.useFakeTimers();
  35. const { unmount } = setup(stateNoMaster);
  36. act(() => {
  37. jest.runOnlyPendingTimers();
  38. });
  39. expect(dispatch).toHaveBeenCalledTimes(1);
  40. expect(dispatch).toHaveBeenCalledWith(stateSet({ master: 'my-client-name' }));
  41. unmount();
  42. jest.useRealTimers();
  43. });
  44. });
  45. describe('when master goes away', () => {
  46. const stateWithMaster: GlobalState = {
  47. ...initialState,
  48. myClientName: 'my-client-name',
  49. clientList: [
  50. { name: 'master-client-a', lastPing: 0 },
  51. { name: 'my-client-name', lastPing: 0 },
  52. { name: 'other-slave-client', lastPing: 0 },
  53. ],
  54. player: {
  55. songId: 123,
  56. playing: true,
  57. currentTime: 17,
  58. seekTime: -1,
  59. master: 'master-client-a',
  60. activeClients: [],
  61. queue: [],
  62. },
  63. };
  64. const stateMasterWentAway: GlobalState = {
  65. ...stateWithMaster,
  66. clientList: [
  67. { name: 'my-client-name', lastPing: 0 },
  68. { name: 'other-slave-client', lastPing: 0 },
  69. ],
  70. };
  71. it('should take control of master after a delay, and pause the client', () => {
  72. expect.assertions(2);
  73. jest.useFakeTimers();
  74. const { container, unmount } = setup(stateWithMaster);
  75. act(() => {
  76. setup(stateMasterWentAway, { container });
  77. });
  78. expect(dispatch).not.toHaveBeenCalled();
  79. act(() => {
  80. jest.runAllTimers();
  81. });
  82. expect(dispatch).toHaveBeenCalledWith(masterSet());
  83. unmount();
  84. jest.useRealTimers();
  85. });
  86. describe('and a third client takes over control', () => {
  87. const stateMasterWentAwayAnotherTookControl: GlobalState = {
  88. ...stateMasterWentAway,
  89. clientList: [
  90. { name: 'my-client-name', lastPing: 0 },
  91. { name: 'other-slave-client', lastPing: 0 },
  92. ],
  93. player: {
  94. ...stateMasterWentAway.player,
  95. master: 'other-slave-client',
  96. },
  97. };
  98. it('should not take control of master', () => {
  99. expect.assertions(1);
  100. jest.useFakeTimers();
  101. const { container, unmount } = setup(stateWithMaster);
  102. act(() => {
  103. setup(stateMasterWentAway, { container });
  104. });
  105. setImmediate(() => {
  106. act(() => {
  107. setup(stateMasterWentAwayAnotherTookControl, { container });
  108. });
  109. });
  110. act(() => {
  111. jest.runAllTimers();
  112. });
  113. expect(dispatch).not.toHaveBeenCalled();
  114. unmount();
  115. jest.useRealTimers();
  116. });
  117. });
  118. });
  119. describe('when the client is master', () => {
  120. const stateMaster: GlobalState = {
  121. ...initialState,
  122. myClientName: 'the-master-client',
  123. clientList: [{ name: 'the-master-client', lastPing: 0 }],
  124. player: {
  125. ...nullPlayer,
  126. master: 'the-master-client',
  127. },
  128. };
  129. it('should continually refresh the server with the current state', () => {
  130. expect.assertions(6);
  131. const clock = jest.useFakeTimers();
  132. const { unmount } = setup(stateMaster);
  133. act(() => {
  134. clock.runOnlyPendingTimers();
  135. });
  136. dispatch.mockClear();
  137. act(() => {
  138. clock.runTimersToTime(masterStateUpdateTimeout - 1);
  139. });
  140. expect(dispatch).toHaveBeenCalledTimes(0);
  141. act(() => {
  142. clock.runTimersToTime(1);
  143. });
  144. expect(dispatch).toHaveBeenCalledTimes(1);
  145. expect(dispatch).toHaveBeenCalledWith(stateSet());
  146. dispatch.mockClear();
  147. expect(dispatch).toHaveBeenCalledTimes(0);
  148. act(() => {
  149. clock.runTimersToTime(masterStateUpdateTimeout);
  150. });
  151. expect(dispatch).toHaveBeenCalledTimes(1);
  152. expect(dispatch).toHaveBeenCalledWith(stateSet());
  153. unmount();
  154. jest.useRealTimers();
  155. });
  156. });
  157. describe('when the client is a slave', () => {
  158. const stateSlave: GlobalState = {
  159. ...initialState,
  160. myClientName: 'a-slave-client',
  161. clientList: [
  162. { name: 'the-master-client', lastPing: 0 },
  163. { name: 'a-slave-client', lastPing: 0 },
  164. ],
  165. player: {
  166. ...nullPlayer,
  167. master: 'the-master-client',
  168. },
  169. };
  170. it('should not send state updates periodically', () => {
  171. expect.assertions(1);
  172. const clock = jest.useFakeTimers();
  173. const { unmount } = setup(stateSlave);
  174. act(() => {
  175. clock.runTimersToTime(masterStateUpdateTimeout);
  176. });
  177. expect(dispatch).not.toHaveBeenCalled();
  178. unmount();
  179. jest.useRealTimers();
  180. });
  181. });
  182. });