Просмотр исходного кода

Refactor out hook from GuessPane and improve tests

Kirk Trombley 4 лет назад
Родитель
Сommit
00aafa9f53

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

@@ -1,9 +1,10 @@
-import { useState, useEffect } from "react";
+import { useCallback, useState } from "react";
 import { dispatch } from "../../../../domain/gameStore";
 import { reverseGeocode } from "../../../../domain/geocoding";
 import ClickMarkerMap from "./ClickMarkerMap";
 import styles from "./GuessPane.module.css";
 import RoundTimer from "./RoundTimer";
+import useMapResizeKeybindings from "./useMapResizeKeybindings";
 
 const mapSizeOpts = {
   small: styles["pane--small"],
@@ -18,16 +19,9 @@ const GuessPane = () => {
   const [selectedPoint, setSelectedPoint] = useState(null);
   const [submitted, setSubmitted] = useState(false);
   const [mapSize, setMapSize] = useState("small");
-
-  useEffect(() => {
-    const listener = event => {
-      if (event.code === "Escape") {
-        setMapSize(toggleMapSize("big"));
-      }
-    };
-    document.addEventListener("keydown", listener, false);
-    return () => document.removeEventListener("keydown", listener, false);
-  }, []);
+  const toggleBig = useCallback(() => setMapSize(toggleMapSize("big")), []);
+  const toggleMed = useCallback(() => setMapSize(toggleMapSize("medium")), []);
+  useMapResizeKeybindings(toggleBig);
 
   const handleSubmitGuess = async () => {
     setSubmitted(true);
@@ -55,12 +49,12 @@ const GuessPane = () => {
       <RoundTimer onTimeout={handleSubmitGuess} />
       <div
         className={styles.resize}
-        onClick={() => setMapSize(toggleMapSize("big"))}
+        onClick={toggleBig}
         role="button"
         tabIndex="0"
         onKeyDown={({ key }) => {
           if (key === "Enter") {
-            setMapSize(toggleMapSize("big"));
+            toggleBig();
           }
         }}
       >
@@ -68,12 +62,12 @@ const GuessPane = () => {
       </div>
       <div
         className={`${styles.resize} ${styles["resize--medium"]}`}
-        onClick={() => setMapSize(toggleMapSize("medium"))}
+        onClick={toggleMed}
         role="button"
         tabIndex="0"
         onKeyDown={({ key }) => {
           if (key === "Enter") {
-            setMapSize(toggleMapSize("medium"));
+            toggleMed();
           }
         }}
       >

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

@@ -0,0 +1,15 @@
+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;

+ 47 - 0
client/src/tests/GuessPane.test.js

@@ -4,6 +4,7 @@ import GuessPane from "../components/screens/GamePanel/GuessPane";
 
 jest.mock("../domain/gameStore");
 jest.mock("../domain/geocoding");
+jest.mock("../components/screens/GamePanel/GuessPane/useMapResizeKeybindings");
 
 import { dispatch } from "../domain/gameStore";
 import { reverseGeocode } from "../domain/geocoding";
@@ -20,18 +21,38 @@ describe("GuessPane", () => {
     expect(handle).toMatchSnapshot();
   });
 
+  it("resizes to large then back", () => {
+    const handle = shallow(<GuessPane />);
+    handle.find("div.resize").first().simulate("click");
+    handle.find("div.resize").first().simulate("click");
+    expect(handle).toMatchSnapshot();
+  });
+
   it("resizes to medium", () => {
     const handle = shallow(<GuessPane />);
     handle.find("div.resize--medium").first().simulate("click");
     expect(handle).toMatchSnapshot();
   });
 
+  it("resizes to medium then back", () => {
+    const handle = shallow(<GuessPane />);
+    handle.find("div.resize--medium").first().simulate("click");
+    handle.find("div.resize--medium").first().simulate("click");
+    expect(handle).toMatchSnapshot();
+  });
+
   it("resizes to large on keydown Enter", () => {
     const handle = shallow(<GuessPane />);
     handle.find("div.resize").first().simulate("keydown", { key: "Enter" });
     expect(handle).toMatchSnapshot();
   });
 
+  it("large resize ignores other keydowns", () => {
+    const handle = shallow(<GuessPane />);
+    handle.find("div.resize").first().simulate("keydown", { key: "Escape" });
+    expect(handle).toMatchSnapshot();
+  });
+
   it("resizes to medium on keydown Enter", () => {
     const handle = shallow(<GuessPane />);
     handle
@@ -41,6 +62,15 @@ describe("GuessPane", () => {
     expect(handle).toMatchSnapshot();
   });
 
+  it("medium resize ignores other keydowns", () => {
+    const handle = shallow(<GuessPane />);
+    handle
+      .find("div.resize--medium")
+      .first()
+      .simulate("keydown", { key: "Escape" });
+    expect(handle).toMatchSnapshot();
+  });
+
   it("submits", async () => {
     reverseGeocode.mockReturnValue("geocoded");
     const handle = shallow(<GuessPane />);
@@ -64,4 +94,21 @@ describe("GuessPane", () => {
     // check submit disabled again
     expect(handle).toMatchSnapshot();
   });
+
+  it("cannot be tricked into resubmitting", async () => {
+    reverseGeocode.mockReturnValue("geocoded");
+    const handle = shallow(<GuessPane />);
+    handle.find("ClickMarkerMap").first().prop("onMarkerMoved")({
+      lat: "lat",
+      lng: "lng",
+    });
+    // check submit enabled
+    expect(handle).toMatchSnapshot();
+    await handle.find("button").first().simulate("click");
+    await handle.find("button").prop("onClick")();
+    expect(reverseGeocode).toHaveBeenCalledTimes(1);
+    expect(dispatch.submitGuess).toHaveBeenCalledTimes(1);
+    // check submit disabled again
+    expect(handle).toMatchSnapshot();
+  });
 });

+ 234 - 0
client/src/tests/__snapshots__/GuessPane.test.js.snap

@@ -1,5 +1,161 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
+exports[`GuessPane cannot be tricked into resubmitting 1`] = `
+<div
+  className="pane pane--small"
+>
+  <button
+    className="submit"
+    disabled={false}
+    onClick={[Function]}
+    type="button"
+  >
+    Submit Guess
+  </button>
+  <ClickMarkerMap
+    onMarkerMoved={[Function]}
+  />
+  <RoundTimer
+    onTimeout={[Function]}
+  />
+  <div
+    className="resize"
+    onClick={[Function]}
+    onKeyDown={[Function]}
+    role="button"
+    tabIndex="0"
+  >
+    ↗️
+  </div>
+  <div
+    className="resize resize--medium"
+    onClick={[Function]}
+    onKeyDown={[Function]}
+    role="button"
+    tabIndex="0"
+  >
+    ➡️
+  </div>
+</div>
+`;
+
+exports[`GuessPane cannot be tricked into resubmitting 2`] = `
+<div
+  className="pane pane--small"
+>
+  <button
+    className="submit"
+    disabled={true}
+    onClick={[Function]}
+    type="button"
+  >
+    Submit Guess
+  </button>
+  <ClickMarkerMap
+    onMarkerMoved={[Function]}
+  />
+  <RoundTimer
+    onTimeout={[Function]}
+  />
+  <div
+    className="resize"
+    onClick={[Function]}
+    onKeyDown={[Function]}
+    role="button"
+    tabIndex="0"
+  >
+    ↗️
+  </div>
+  <div
+    className="resize resize--medium"
+    onClick={[Function]}
+    onKeyDown={[Function]}
+    role="button"
+    tabIndex="0"
+  >
+    ➡️
+  </div>
+</div>
+`;
+
+exports[`GuessPane large resize ignores other keydowns 1`] = `
+<div
+  className="pane pane--small"
+>
+  <button
+    className="submit"
+    disabled={true}
+    onClick={[Function]}
+    type="button"
+  >
+    Submit Guess
+  </button>
+  <ClickMarkerMap
+    onMarkerMoved={[Function]}
+  />
+  <RoundTimer
+    onTimeout={[Function]}
+  />
+  <div
+    className="resize"
+    onClick={[Function]}
+    onKeyDown={[Function]}
+    role="button"
+    tabIndex="0"
+  >
+    ↗️
+  </div>
+  <div
+    className="resize resize--medium"
+    onClick={[Function]}
+    onKeyDown={[Function]}
+    role="button"
+    tabIndex="0"
+  >
+    ➡️
+  </div>
+</div>
+`;
+
+exports[`GuessPane medium resize ignores other keydowns 1`] = `
+<div
+  className="pane pane--small"
+>
+  <button
+    className="submit"
+    disabled={true}
+    onClick={[Function]}
+    type="button"
+  >
+    Submit Guess
+  </button>
+  <ClickMarkerMap
+    onMarkerMoved={[Function]}
+  />
+  <RoundTimer
+    onTimeout={[Function]}
+  />
+  <div
+    className="resize"
+    onClick={[Function]}
+    onKeyDown={[Function]}
+    role="button"
+    tabIndex="0"
+  >
+    ↗️
+  </div>
+  <div
+    className="resize resize--medium"
+    onClick={[Function]}
+    onKeyDown={[Function]}
+    role="button"
+    tabIndex="0"
+  >
+    ➡️
+  </div>
+</div>
+`;
+
 exports[`GuessPane renders 1`] = `
 <div
   className="pane pane--small"
@@ -117,6 +273,45 @@ exports[`GuessPane resizes to large on keydown Enter 1`] = `
 </div>
 `;
 
+exports[`GuessPane resizes to large then back 1`] = `
+<div
+  className="pane pane--small"
+>
+  <button
+    className="submit"
+    disabled={true}
+    onClick={[Function]}
+    type="button"
+  >
+    Submit Guess
+  </button>
+  <ClickMarkerMap
+    onMarkerMoved={[Function]}
+  />
+  <RoundTimer
+    onTimeout={[Function]}
+  />
+  <div
+    className="resize"
+    onClick={[Function]}
+    onKeyDown={[Function]}
+    role="button"
+    tabIndex="0"
+  >
+    ↗️
+  </div>
+  <div
+    className="resize resize--medium"
+    onClick={[Function]}
+    onKeyDown={[Function]}
+    role="button"
+    tabIndex="0"
+  >
+    ➡️
+  </div>
+</div>
+`;
+
 exports[`GuessPane resizes to medium 1`] = `
 <div
   className="pane pane--medium"
@@ -195,6 +390,45 @@ exports[`GuessPane resizes to medium on keydown Enter 1`] = `
 </div>
 `;
 
+exports[`GuessPane resizes to medium then back 1`] = `
+<div
+  className="pane pane--small"
+>
+  <button
+    className="submit"
+    disabled={true}
+    onClick={[Function]}
+    type="button"
+  >
+    Submit Guess
+  </button>
+  <ClickMarkerMap
+    onMarkerMoved={[Function]}
+  />
+  <RoundTimer
+    onTimeout={[Function]}
+  />
+  <div
+    className="resize"
+    onClick={[Function]}
+    onKeyDown={[Function]}
+    role="button"
+    tabIndex="0"
+  >
+    ↗️
+  </div>
+  <div
+    className="resize resize--medium"
+    onClick={[Function]}
+    onKeyDown={[Function]}
+    role="button"
+    tabIndex="0"
+  >
+    ➡️
+  </div>
+</div>
+`;
+
 exports[`GuessPane submits 1`] = `
 <div
   className="pane pane--small"