Repository: boardgameio/boardgame.io
Pull request #1044 (2022-01-03 05:55:48)

Lobby component: Stopped polling for list of games when list of games should not be visible
#### Checklist

- [x] Use a separate branch in your local repo (not `main`).
- [x] Test coverage is 100% (or you have a story for why it's ok).

## Description

Fix for https://github.com/boardgameio/boardgame.io/issues/1043: pause polling for list of games when not showing list of games

## Testing

(note: in end-to-end description. "Queries" refers to the polling queries against `/games` and `/games/<gamename>` endpoints)

- [x] Unit tests added
- [x] End-to-end test:
  - build and link against a game I am working on
  -  Loaded localhost:8000, see "Choose a player name:", noted queries are not repeating
  - Chose name and hit Enter, noted queries started
  - Created game, `Join` game and hit `Play`, noted queries stopped
  - `Exit match`, noted queries resumed
  - `Play` game again, noted queries stopped
  - `Exit lobby`, noted queries still stopped
  - entered name and re-entered lobby, noted queries resume
  - refresh browser, noted we go directly to game list and queries resume
  - `Join` game and `Play` game, noted queries stopped
  - refresh browser, noted we go directly to game list and queries resume


Patch:
diff --git a/src/lobby/react.tsx b/src/lobby/react.tsx
index 9c65f91..5aa70d5 100644
--- a/src/lobby/react.tsx
+++ b/src/lobby/react.tsx
@@ -127,12 +127,14 @@ class Lobby extends React.Component<LobbyProps, LobbyState> {
     if (cookie.phase && cookie.phase === LobbyPhases.PLAY) {
       cookie.phase = LobbyPhases.LIST;
     }
+    if (cookie.phase && cookie.phase !== LobbyPhases.ENTER) {
+      this._startRefreshInterval();
+    }
     this.setState({
       phase: cookie.phase || LobbyPhases.ENTER,
       playerName: cookie.playerName || 'Visitor',
       credentialStore: cookie.credentialStore || {},
     });
-    this._startRefreshInterval();
   }
 
   componentDidUpdate(prevProps: LobbyProps, prevState: LobbyState) {
@@ -198,10 +200,12 @@ class Lobby extends React.Component<LobbyProps, LobbyState> {
   };
 
   _enterLobby = (playerName: string) => {
+    this._startRefreshInterval();
     this.setState({ playerName, phase: LobbyPhases.LIST });
   };
 
   _exitLobby = async () => {
+    this._clearRefreshInterval();
     await this.connection.disconnect();
     this.setState({ phase: LobbyPhases.ENTER, errorMsg: '' });
   };
@@ -282,10 +286,12 @@ class Lobby extends React.Component<LobbyProps, LobbyState> {
       credentials: this.connection.playerCredentials,
     };
 
+    this._clearRefreshInterval();
     this.setState({ phase: LobbyPhases.PLAY, runningMatch: match });
   };
 
   _exitMatch = () => {
+    this._startRefreshInterval();
     this.setState({ phase: LobbyPhases.LIST, runningMatch: null });
   };
 


Test patch:
diff --git a/src/lobby/react.test.tsx b/src/lobby/react.test.tsx
index 49d03de..00b8a78 100644
--- a/src/lobby/react.test.tsx
+++ b/src/lobby/react.test.tsx
@@ -27,9 +27,13 @@ Enzyme.configure({ adapter: new Adapter() });
 describe('lobby', () => {
   let lobby;
   const spy = jest.fn();
+  let setIntervalSpy;
+  let clearIntervalSpy;
   let components: any[];
 
   beforeEach(async () => {
+    setIntervalSpy = jest.spyOn(global, 'setInterval');
+    clearIntervalSpy = jest.spyOn(global, 'clearInterval');
     components = [
       {
         board: 'Board1',
@@ -45,6 +49,8 @@ describe('lobby', () => {
 
   afterEach(() => {
     spy.mockReset();
+    setIntervalSpy.mockRestore();
+    clearIntervalSpy.mockRestore();
   });
 
   describe('specify servers', () => {
@@ -157,15 +163,78 @@ describe('lobby', () => {
       );
       lobby = Enzyme.mount(<Lobby gameComponents={components} />);
     });
+    afterEach(() => {
+      Cookies.remove('lobbyState', { path: '/' });
+    });
     test('reset phase to list', async () => {
       expect(lobby.instance().state.phase).toBe('list');
     });
   });
 
-  describe('refresh interval', () => {
+  describe('refresh interval triggering', () => {
+    test('refresh does not start on initial component mount', () => {
+      lobby = Enzyme.mount(<Lobby gameComponents={components} />);
+
+      expect(setIntervalSpy).not.toHaveBeenCalled();
+    });
+
+    test('refresh starts when transitioning from ENTER lobby to LIST lobby', () => {
+      lobby = Enzyme.mount(<Lobby gameComponents={components} />);
+
+      lobby
+        .find('LobbyLoginForm')
+        .find('input')
+        .props()
+        .onKeyPress({ key: 'Enter' });
+
+      expect(setIntervalSpy).toHaveBeenCalledTimes(1);
+      expect(setIntervalSpy).toHaveBeenCalledWith(expect.any(Function), 2000);
+    });
+
+    test('refresh starts when component mounts and cookie state sends us to LIST', () => {
+      Cookies.save(
+        'lobbyState',
+        {
+          phase: 'list',
+          playerName: 'Bob',
+        },
+        { path: '/' }
+      );
+      lobby = Enzyme.mount(<Lobby gameComponents={components} />);
+
+      expect(setIntervalSpy).toHaveBeenCalledTimes(1);
+      expect(setIntervalSpy).toHaveBeenCalledWith(expect.any(Function), 2000);
+    });
+
+    test('refresh stops when transitioning from LIST to PLAY', () => {
+      Cookies.save(
+        'lobbyState',
+        {
+          phase: 'list',
+          playerName: 'Bob',
+        },
+        { path: '/' }
+      );
+      lobby = Enzyme.mount(<Lobby gameComponents={components} />);
+
+      lobby.instance()._startMatch('GameName1', { numPlayers: 2 });
+
+      expect(clearIntervalSpy).toHaveBeenCalledWith(
+        lobby.instance()._currentInterval
+      );
+    });
+  });
+
+  describe('refresh interval tracking', () => {
     beforeEach(() => {
       lobby = Enzyme.mount(<Lobby gameComponents={components} />);
+      lobby
+        .find('LobbyLoginForm')
+        .find('input')
+        .props()
+        .onKeyPress({ key: 'Enter' });
     });
+
     afterEach(() => lobby.unmount());
 
     test('lobby stores an interval ID', () => {
@@ -209,6 +278,7 @@ describe('lobby', () => {
 
     afterEach(() => {
       spyClient.mockReset();
+      Cookies.remove('lobbyState', { path: '/' });
     });
 
     describe('creating a match', () => {