فهرست منبع

Move GuessPane hooks to single module and add unit tests

Kirk Trombley 4 سال پیش
والد
کامیت
8f466dbff1

+ 1 - 1
client/src/components/screens/GamePanel/GuessPane/GuessPane.jsx

@@ -4,7 +4,7 @@ import { reverseGeocode } from "../../../../domain/geocoding";
 import ClickMarkerMap from "./ClickMarkerMap";
 import styles from "./GuessPane.module.css";
 import RoundTimer from "./RoundTimer";
-import useMapResizeKeybindings from "./useMapResizeKeybindings";
+import { useMapResizeKeybindings } from "./hooks";
 
 const mapSizeOpts = {
   small: styles["pane--small"],

+ 1 - 1
client/src/components/screens/GamePanel/GuessPane/RoundTimer.jsx

@@ -1,6 +1,6 @@
 import ms from "pretty-ms";
 import styles from "./GuessPane.module.css";
-import useRoundTimer from "./useRoundTimer";
+import { useRoundTimer } from "./hooks";
 
 const RoundTimer = ({ onTimeout }) => {
   const remaining = useRoundTimer(onTimeout);

+ 14 - 3
client/src/components/screens/GamePanel/GuessPane/useRoundTimer.jsx → client/src/components/screens/GamePanel/GuessPane/hooks.jsx

@@ -1,7 +1,19 @@
 import { useEffect, useState } from "react";
 import { dispatch, useRoundSeconds } from "../../../../domain/gameStore";
 
-const useRoundTimer = onTimeout => {
+export const useMapResizeKeybindings = toggleMapSize => {
+  useEffect(() => {
+    const listener = event => {
+      if (event.code === "Escape") {
+        toggleMapSize();
+      }
+    };
+    document.addEventListener("keydown", listener, false);
+    return () => document.removeEventListener("keydown", listener, false);
+  }, [toggleMapSize]);
+};
+
+export const useRoundTimer = onTimeout => {
   const remaining = useRoundSeconds();
   // eslint-disable-next-line consistent-return
   useEffect(() => {
@@ -19,6 +31,5 @@ const useRoundTimer = onTimeout => {
       setCalled(true);
     }
   }, [onTimeout, remaining, called]);
+  return remaining;
 };
-
-export default useRoundTimer;

+ 0 - 15
client/src/components/screens/GamePanel/GuessPane/useMapResizeKeybindings.jsx

@@ -1,15 +0,0 @@
-import { useEffect } from "react";
-
-const useMapResizeKeybindings = toggleMapSize => {
-  useEffect(() => {
-    const listener = event => {
-      if (event.code === "Escape") {
-        toggleMapSize();
-      }
-    };
-    document.addEventListener("keydown", listener, false);
-    return () => document.removeEventListener("keydown", listener, false);
-  }, [toggleMapSize]);
-};
-
-export default useMapResizeKeybindings;

+ 100 - 0
client/src/tests/GuessPane.hooks.test.js

@@ -0,0 +1,100 @@
+import {
+  useMapResizeKeybindings,
+  useRoundTimer,
+} from "../components/screens/GamePanel/GuessPane/hooks";
+
+jest.mock("react");
+jest.mock("../domain/gameStore");
+
+import { useEffect, useState } from "react";
+import { dispatch, useRoundSeconds } from "../domain/gameStore";
+
+describe("GuessPane hooks", () => {
+  describe("useMapResizeKeybindings", () => {
+    document.addEventListener = jest.fn();
+    document.removeEventListener = jest.fn();
+
+    it("attaches key listener on mount and removes them on unmount", () => {
+      let listener;
+      document.addEventListener.mockImplementation((_, fn) => {
+        listener = fn;
+      });
+      let cleanup;
+      useEffect.mockImplementation(fn => {
+        cleanup = fn();
+      });
+      const toggleMapSize = jest.fn();
+      useMapResizeKeybindings(toggleMapSize);
+      expect(document.addEventListener).toHaveBeenCalledWith(
+        "keydown",
+        listener,
+        false
+      );
+      listener({ code: "Escape" });
+      expect(toggleMapSize).toHaveBeenCalled();
+      cleanup();
+      expect(document.removeEventListener).toHaveBeenCalledWith(
+        "keydown",
+        listener,
+        false
+      );
+    });
+    it("attaches key listener that only listens on escape", () => {
+      let listener;
+      document.addEventListener.mockImplementation((_, fn) => {
+        listener = fn;
+      });
+      let cleanup;
+      useEffect.mockImplementation(fn => {
+        cleanup = fn();
+      });
+      const toggleMapSize = jest.fn();
+      useMapResizeKeybindings(toggleMapSize);
+      expect(document.addEventListener).toHaveBeenCalledWith(
+        "keydown",
+        listener,
+        false
+      );
+      listener({ code: "Enter" });
+      expect(toggleMapSize).not.toHaveBeenCalled();
+    });
+  });
+  describe("useRoundTimer", () => {
+    beforeEach(() => {
+      useEffect.mockImplementation(f => {
+        const cleanup = f();
+        if (cleanup) {
+          cleanup();
+        }
+      });
+      setTimeout.mockImplementation(f => f());
+      dispatch.updateRoundSeconds.mockImplementation(f => f(100));
+    });
+    it("sets up a timer and counts down", () => {
+      useRoundSeconds.mockReturnValue(100);
+      useState.mockReturnValue([false, () => {}]);
+      const onTimeout = jest.fn();
+      expect(useRoundTimer(onTimeout)).toBe(100);
+      expect(setTimeout).toHaveBeenCalledWith(expect.any(Function), 1000);
+      expect(dispatch.updateRoundSeconds).toHaveReturnedWith(99);
+      expect(clearTimeout).toHaveBeenCalled();
+      expect(onTimeout).not.toHaveBeenCalled();
+    });
+    it("calls onTimeout when round ends", () => {
+      useRoundSeconds.mockReturnValue(0);
+      useState.mockReturnValue([false, () => {}]);
+      const onTimeout = jest.fn();
+      expect(useRoundTimer(onTimeout)).toBe(0);
+      expect(setTimeout).not.toHaveBeenCalled();
+      expect(dispatch.updateRoundSeconds).not.toHaveBeenCalled();
+      expect(onTimeout).toHaveBeenCalled();
+    });
+    it("only calls onTimeout once", () => {
+      useRoundSeconds.mockReturnValue(0);
+      useState.mockReturnValue([true, () => {}]);
+      const onTimeout = jest.fn();
+      expect(useRoundTimer(onTimeout)).toBe(0);
+      expect(onTimeout).not.toHaveBeenCalled();
+    });
+  });
+});

+ 1 - 1
client/src/tests/GuessPane.test.js

@@ -4,7 +4,7 @@ import GuessPane from "../components/screens/GamePanel/GuessPane";
 
 jest.mock("../domain/gameStore");
 jest.mock("../domain/geocoding");
-jest.mock("../components/screens/GamePanel/GuessPane/useMapResizeKeybindings");
+jest.mock("../components/screens/GamePanel/GuessPane/hooks");
 
 import { dispatch } from "../domain/gameStore";
 import { reverseGeocode } from "../domain/geocoding";

+ 2 - 2
client/src/tests/RoundTimer.test.js

@@ -2,9 +2,9 @@ import React from "react";
 import { shallow } from "enzyme";
 import RoundTimer from "../components/screens/GamePanel/GuessPane/RoundTimer";
 
-jest.mock("../components/screens/GamePanel/GuessPane/useRoundTimer");
+jest.mock("../components/screens/GamePanel/GuessPane/hooks");
 
-import useRoundTimer from "../components/screens/GamePanel/GuessPane/useRoundTimer";
+import { useRoundTimer } from "../components/screens/GamePanel/GuessPane/hooks";
 
 describe("RoundTimer", () => {
   it("renders with time left", () => {