فهرست منبع

fix: don't take control of master until initial data loaded

Fela Maslen 5 سال پیش
والد
کامیت
8aff558ebf
5فایلهای تغییر یافته به همراه76 افزوده شده و 18 حذف شده
  1. 52 13
      gmus/src/hooks/master.spec.tsx
  2. 4 4
      gmus/src/hooks/master.ts
  3. 17 0
      gmus/src/reducer/reducer.spec.ts
  4. 2 1
      gmus/src/reducer/reducer.ts
  5. 1 0
      gmus/src/reducer/types.ts

+ 52 - 13
gmus/src/hooks/master.spec.tsx

@@ -3,40 +3,71 @@ import React from 'react';
 
 import { stateSet } from '../actions';
 import { masterStateUpdateTimeout } from '../constants/system';
-import { initialState } from '../reducer';
+import { GlobalState, initialState, nullPlayer } from '../reducer';
 
 import { useMaster } from './master';
 
 describe(useMaster.name, () => {
   const dispatch = jest.fn();
 
-  const TestComponent: React.FC<{ master: string; myClientName: string }> = ({
-    master,
-    myClientName,
-  }) => {
-    useMaster({ player: { ...initialState.player, master }, myClientName }, dispatch);
+  const TestComponent: React.FC<GlobalState> = (state) => {
+    useMaster(state, dispatch);
     return null;
   };
 
   describe('when there is no master initially', () => {
+    const stateNoMaster: GlobalState = {
+      ...initialState,
+      initialised: true,
+      myClientName: 'my-client-name',
+      player: {
+        ...nullPlayer,
+        master: '',
+      },
+    };
+
     it('should take control of master', () => {
       expect.assertions(2);
-      const { unmount } = render(<TestComponent master="" myClientName="my-client-name" />);
+      const { unmount } = render(<TestComponent {...stateNoMaster} />);
 
       expect(dispatch).toHaveBeenCalledTimes(1);
       expect(dispatch).toHaveBeenCalledWith(stateSet({ master: 'my-client-name' }));
 
       unmount();
     });
+
+    describe('when the state is not initialised', () => {
+      const stateNoMasterUninit: GlobalState = {
+        ...stateNoMaster,
+        initialised: false,
+      };
+
+      it('should not take control of master', () => {
+        expect.assertions(1);
+        const { unmount } = render(<TestComponent {...stateNoMasterUninit} />);
+
+        expect(dispatch).not.toHaveBeenCalled();
+
+        unmount();
+      });
+    });
   });
 
   describe('when the client is master', () => {
+    const stateMaster: GlobalState = {
+      ...initialState,
+      initialised: true,
+      myClientName: 'the-master-client',
+      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 } = render(
-        <TestComponent master="the-master-client" myClientName="the-master-client" />,
-      );
+      const { unmount } = render(<TestComponent {...stateMaster} />);
 
       act(() => {
         clock.runTimersToTime(masterStateUpdateTimeout - 1);
@@ -66,12 +97,20 @@ describe(useMaster.name, () => {
   });
 
   describe('when the client is a slave', () => {
+    const stateSlave: GlobalState = {
+      ...initialState,
+      initialised: true,
+      myClientName: 'a-slave-client',
+      player: {
+        ...nullPlayer,
+        master: 'the-master-client',
+      },
+    };
+
     it('should not send state updates periodically', () => {
       expect.assertions(1);
       const clock = jest.useFakeTimers();
-      const { unmount } = render(
-        <TestComponent master="the-master-client" myClientName="my-client-name" />,
-      );
+      const { unmount } = render(<TestComponent {...stateSlave} />);
 
       act(() => {
         clock.runTimersToTime(masterStateUpdateTimeout);

+ 4 - 4
gmus/src/hooks/master.ts

@@ -6,7 +6,7 @@ import { GlobalState } from '../reducer';
 import { isMaster } from '../selectors';
 
 export function useMaster(
-  state: Pick<GlobalState, 'player' | 'myClientName'>,
+  state: Pick<GlobalState, 'initialised' | 'player' | 'myClientName'>,
   dispatch: Dispatch<LocalAction>,
 ): void {
   const clientIsMaster = isMaster(state);
@@ -24,10 +24,10 @@ export function useMaster(
     };
   }, [dispatch, clientIsMaster]);
 
-  const noMaster = !state.player.master;
+  const shouldInitMaster = !state.player.master && state.initialised;
   useEffect(() => {
-    if (noMaster) {
+    if (shouldInitMaster) {
       dispatch(stateSet({ master: state.myClientName }));
     }
-  }, [dispatch, noMaster, state.myClientName]);
+  }, [dispatch, shouldInitMaster, state.myClientName]);
 }

+ 17 - 0
gmus/src/reducer/reducer.spec.ts

@@ -14,6 +14,23 @@ import { GlobalState } from './types';
 
 describe(globalReducer.name, () => {
   describe(ActionTypeRemote.StateSet, () => {
+    it('should initialise the state', () => {
+      expect.assertions(1);
+      const state: GlobalState = { ...initialState, initialised: false };
+      const result = globalReducer(state, {
+        type: ActionTypeRemote.StateSet,
+        payload: {
+          songId: null,
+          playing: false,
+          currentTime: 0,
+          seekTime: -1,
+          master: 'some-master-client',
+        },
+      });
+
+      expect(result.initialised).toBe(true);
+    });
+
     describe('when the client is master', () => {
       const stateMaster: GlobalState = {
         ...initialState,

+ 2 - 1
gmus/src/reducer/reducer.ts

@@ -18,6 +18,7 @@ export const nullPlayer: MusicPlayer = {
 };
 
 export const initialState: GlobalState = {
+  initialised: false,
   player: nullPlayer,
   clientList: [],
   myClientName: '',
@@ -33,7 +34,7 @@ function onRemoteStateSet(state: GlobalState, action: ActionStateSetRemote): Glo
 
   const nextPlayerWithSeekTime: MusicPlayer = { ...nextPlayer, seekTime };
 
-  return { ...state, player: nextPlayerWithSeekTime };
+  return { ...state, initialised: true, player: nextPlayerWithSeekTime };
 }
 
 function onLocalStateSet(state: GlobalState, action: ActionStateSetLocal): GlobalState {

+ 1 - 0
gmus/src/reducer/types.ts

@@ -1,6 +1,7 @@
 import { Member, MusicPlayer } from '../types/state';
 
 export type GlobalState = {
+  initialised: boolean;
   player: MusicPlayer;
   clientList: Member[];
   myClientName: string;