Skip to content

Commit afd2ee1

Browse files
committed
Move keyboard navigation to HOC
1 parent c9b4efa commit afd2ee1

File tree

10 files changed

+114
-36
lines changed

10 files changed

+114
-36
lines changed

.eslintrc.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ module.exports = {
1212
"import/no-extraneous-dependencies": "off",
1313
"@typescript-eslint/no-empty-interface": "warn",
1414
"react/require-default-props": "off",
15+
"import/no-named-as-default": "off",
1516
},
1617
settings: {
1718
"import/resolver": {

src/components/ColorSettingsLevel/ColorSettingsLevel.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import Typist from "react-typist";
66
import styles from "../Levels.module.scss";
77
import { RGBColor } from "../../contexts/SettingsContext";
88
import "../TypistCursor.scss";
9+
// eslint-disable-next-line import/no-cycle
10+
import WithKeyboardNavigation from "../../hocs/WithKeyboardNavigation/WithKeyboardNavigation";
911
import useGlobalColors from "../../hooks/useGlobalColors/useGlobalColors";
1012
// eslint-disable-next-line import/no-cycle
1113
import useNavigation from "../../hooks/useNavigation/useNavigation";
@@ -26,7 +28,11 @@ export type ColorSettingsLevelProps = {
2628
* @param changeColor - function to change a lense color
2729
* @param hint - to be provided for a user
2830
*/
29-
const ColorSettingsLevel: React.FC<ColorSettingsLevelProps> = ({ color, changeColor, hint }) => {
31+
export const ColorSettingsLevel: React.FC<ColorSettingsLevelProps> = ({
32+
color,
33+
changeColor,
34+
hint,
35+
}) => {
3036
const [, , , , , currentLevelCounter] = useNavigation();
3137
const [
3238
[, setGlobalBackground, resetGlobalBackground],
@@ -69,4 +75,4 @@ const ColorSettingsLevel: React.FC<ColorSettingsLevelProps> = ({ color, changeCo
6975
);
7076
};
7177

72-
export default ColorSettingsLevel;
78+
export default WithKeyboardNavigation(ColorSettingsLevel);

src/components/ColorSettingsLevel/__tests__/ColorSettingsLevel.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { ReactWrapper, mount } from "enzyme";
33
import { render, waitFor } from "@testing-library/react";
44
import Typist from "react-typist";
55
import { CirclePicker } from "react-color";
6-
import ColorSettingsLevel, { ColorSettingsLevelProps } from "../ColorSettingsLevel";
6+
import { ColorSettingsLevel, ColorSettingsLevelProps } from "../ColorSettingsLevel";
77
import useNavigation from "../../../hooks/useNavigation/useNavigation";
88
import useGlobalColors from "../../../hooks/useGlobalColors/useGlobalColors";
99

@@ -35,7 +35,7 @@ describe("ColorSettingsLevel component", () => {
3535
});
3636

3737
it("uses navigation", () => {
38-
expect(useNavigation).toBeCalledTimes(1);
38+
expect(useNavigation).toBeCalled();
3939
});
4040

4141
it("uses global colors helpers", () => {

src/components/IntroLevel/IntroLevel.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import React from "react";
22
import Typist from "react-typist";
33
import { Box } from "@material-ui/core";
4+
// eslint-disable-next-line import/no-cycle
5+
import WithKeyboardNavigation from "../../hocs/WithKeyboardNavigation/WithKeyboardNavigation";
46
import styles from "../Levels.module.scss";
57
import { RGBColor } from "../../contexts/SettingsContext";
68
import "../TypistCursor.scss";
@@ -19,7 +21,7 @@ export type IntroLevelProps = {
1921
* Navigation intro level component
2022
* @param hint - intro hint to be shown to a user
2123
*/
22-
const IntroLevel: React.FC<IntroLevelProps> = ({ hint }) => {
24+
export const IntroLevel: React.FC<IntroLevelProps> = ({ hint }) => {
2325
const [, , , , , currentLevelCounter] = useNavigation();
2426

2527
const { color, bgColor } = usePalette();
@@ -64,4 +66,4 @@ const IntroLevel: React.FC<IntroLevelProps> = ({ hint }) => {
6466
);
6567
};
6668

67-
export default IntroLevel;
69+
export default WithKeyboardNavigation(IntroLevel);

src/components/IntroLevel/__tests__/IntroLevel.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React from "react";
22
import { shallow, ShallowWrapper } from "enzyme";
33
import Typist from "react-typist";
44
import { render, waitFor } from "@testing-library/react";
5-
import IntroLevel, { IntroLevelProps } from "../IntroLevel";
5+
import { IntroLevel, IntroLevelProps } from "../IntroLevel";
66
import useNavigation from "../../../hooks/useNavigation/useNavigation";
77
import usePalette from "../../../hooks/usePalette/usePalette";
88

src/components/Navigation/Navigation.tsx

Lines changed: 1 addition & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useEffect } from "react";
1+
import React from "react";
22
import { BottomNavigation, BottomNavigationAction } from "@material-ui/core";
33
import { ArrowBack, ArrowForward, Cached, Settings } from "@material-ui/icons";
44
import styles from "./Navigation.module.scss";
@@ -20,29 +20,6 @@ export type NavigationProps = {
2020
const Navigation: React.FC<NavigationProps> = ({ bgColor, color }) => {
2121
const [, nextLevel, previousLevel, resetLevels, goToSettings] = useNavigation();
2222

23-
useEffect(() => {
24-
const enableNavigation = ({ keyCode }: KeyboardEvent) => {
25-
switch (keyCode) {
26-
case 13:
27-
case 39:
28-
return nextLevel();
29-
case 37:
30-
return previousLevel();
31-
case 8:
32-
return resetLevels();
33-
default:
34-
// eslint-disable-next-line @typescript-eslint/no-empty-function
35-
return () => {};
36-
}
37-
};
38-
39-
document.addEventListener("keydown", enableNavigation, false);
40-
41-
return () => {
42-
document.removeEventListener("keydown", enableNavigation, false);
43-
};
44-
}, [nextLevel, previousLevel, resetLevels]);
45-
4623
return (
4724
<BottomNavigation className={styles.navigation} style={{ backgroundColor: bgColor }} showLabels>
4825
<BottomNavigationAction

src/components/Navigation/__tests__/Navigation.test.tsx

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ describe("Navigation component", () => {
1818
let wrapper: ReactWrapper;
1919

2020
beforeEach(() => {
21-
jest.spyOn(React, "useEffect");
2221
// eslint-disable-next-line react/jsx-props-no-spreading
2322
wrapper = mount(<Navigation {...defaultProps} />);
2423
});
@@ -31,10 +30,6 @@ describe("Navigation component", () => {
3130
expect(useNavigation).toBeCalledTimes(1);
3231
});
3332

34-
it("uses side effects", () => {
35-
expect(React.useEffect).toBeCalled();
36-
});
37-
3833
it("renders material bottom navigation component", () => {
3934
expect(wrapper.find(BottomNavigation).length).toEqual(1);
4035
});
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import React, { useEffect } from "react";
2+
// eslint-disable-next-line import/no-cycle
3+
import useNavigation from "../../hooks/useNavigation/useNavigation";
4+
5+
/**
6+
* Keyboard navigation HOC
7+
* @param WrappedComponent - component to be enhanced with keyboard navigation
8+
* @constructor
9+
*/
10+
const WithKeyboardNavigation = (
11+
WrappedComponent: React.ComponentType<any>,
12+
): React.ComponentType<any> => (props) => {
13+
// eslint-disable-next-line react-hooks/rules-of-hooks
14+
const [, nextLevel, previousLevel, resetLevels] = useNavigation();
15+
16+
// eslint-disable-next-line react-hooks/rules-of-hooks
17+
useEffect(() => {
18+
const enableNavigation = ({ keyCode }: KeyboardEvent) => {
19+
switch (keyCode) {
20+
case 13:
21+
case 39:
22+
return nextLevel();
23+
case 37:
24+
return previousLevel();
25+
case 8:
26+
return resetLevels();
27+
default:
28+
// eslint-disable-next-line @typescript-eslint/no-empty-function
29+
return () => {};
30+
}
31+
};
32+
33+
document.addEventListener("keydown", enableNavigation, false);
34+
35+
return () => {
36+
document.removeEventListener("keydown", enableNavigation, false);
37+
};
38+
}, [nextLevel, previousLevel, resetLevels]);
39+
40+
// eslint-disable-next-line react/jsx-props-no-spreading
41+
return <WrappedComponent {...props} />;
42+
};
43+
44+
export default WithKeyboardNavigation;
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import React from "react";
2+
import { render } from "@testing-library/react";
3+
import { mount } from "enzyme";
4+
import WithKeyboardNavigation from "../WithKeyboardNavigation";
5+
import useNavigation from "../../../hooks/useNavigation/useNavigation";
6+
7+
jest.mock("../../../hooks/useNavigation/useNavigation");
8+
9+
describe("WithKeyboardNavigation HOC", () => {
10+
describe("user behavior", () => {
11+
let getByText;
12+
13+
it("renders wrapped component inner content", () => {
14+
const FakeComponent: React.FC = () => <div>fake component</div>;
15+
const KeyboardNavigationEnhancedComponent: React.ComponentType = WithKeyboardNavigation(
16+
FakeComponent,
17+
);
18+
19+
const wrapper = render(<KeyboardNavigationEnhancedComponent />);
20+
21+
getByText = wrapper.getByText;
22+
23+
expect(getByText(/fake component/)).toBeInTheDocument();
24+
});
25+
});
26+
27+
describe("implementation", () => {
28+
it("uses navigation hook", () => {
29+
const FakeComponent: React.FC = () => null;
30+
const WrappedComponent: React.ComponentType = WithKeyboardNavigation(FakeComponent);
31+
32+
mount(<WrappedComponent />);
33+
34+
expect(useNavigation).toBeCalled();
35+
});
36+
37+
it("passes all props to wrapped component", () => {
38+
const FakeComponent: React.FC<{ a: boolean; b: string }> = ({ a, b }) => {
39+
expect(a).toEqual(true);
40+
expect(b).toEqual("fake");
41+
42+
return null;
43+
};
44+
45+
const WrappedComponent: React.ComponentType<any> = WithKeyboardNavigation(FakeComponent);
46+
47+
mount(<WrappedComponent a b="fake" />);
48+
});
49+
});
50+
});
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"main": "WithKeyboardNavigation.tsx"
3+
}

0 commit comments

Comments
 (0)