Skip to content

Commit

Permalink
setup tests and write some
Browse files Browse the repository at this point in the history
  • Loading branch information
cluk3 committed Jul 12, 2019
1 parent 2f899cb commit 24b102a
Show file tree
Hide file tree
Showing 8 changed files with 546 additions and 48 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
/exampleDist
.DS_Store
npm-debug.log*
.cache
.cache
/coverage
12 changes: 12 additions & 0 deletions babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module.exports = {
presets: [
[
"@babel/preset-env",
{
targets: {
node: "current"
}
}
]
]
};
281 changes: 248 additions & 33 deletions package-lock.json

Large diffs are not rendered by default.

9 changes: 7 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"module": "dist/react-use-context-menu.m.js",
"source": "src/index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"test": "jest",
"example": "parcel ./example/index.html --out-dir exampleDist",
"build": "rm -rf ./dist && microbundle -o dist/ --sourcemap false --compress false -f es,cjs",
"dev": "microbundle watch -o dist/ --sourcemap false --compress false"
Expand All @@ -29,12 +29,17 @@
"author": "Luca Campli campli.luca@gmail.com",
"license": "MIT",
"devDependencies": {
"@babel/core": "^7.5.4",
"@babel/preset-env": "^7.5.4",
"@testing-library/react-hooks": "^1.1.0",
"babel-jest": "^24.8.0",
"jest": "^24.8.0",
"microbundle": "^0.11.0",
"np": "^5.0.3",
"parcel-bundler": "^1.12.3",
"react": "^16.8.6",
"react-dom": "^16.8.6"
"react-dom": "^16.8.6",
"react-test-renderer": "^16.8.6"
},
"dependencies": {},
"repository": {
Expand Down
34 changes: 34 additions & 0 deletions src/__tests__/__snapshots__/index.js.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`useContextMenu result is correct 1`] = `
Array [
Object {
"ref": Object {
"current": undefined,
},
"role": "menu",
"style": Object {
"opacity": 0,
"pointerEvents": "none",
"position": "fixed",
},
"tabIndex": -1,
},
Object {
"ref": [Function],
"role": "menuitem",
"tabIndex": -1,
},
[Function],
Object {
"coords": Array [
0,
0,
],
"data": undefined,
"isVisible": false,
"setCoords": [Function],
"setVisible": [Function],
},
]
`;
220 changes: 220 additions & 0 deletions src/__tests__/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
import { renderHook, act } from "@testing-library/react-hooks";
import useContextMenu, { keyCodes } from "../useContextMenu";

test("useContextMenu result is correct", () => {
const { result } = renderHook(() => useContextMenu());

expect(result.current.length).toBe(4);

expect(result.current).toMatchSnapshot();
});

test("useContextMenu register event listeners only when visible", () => {
const addEventListener = jest.spyOn(document, "addEventListener");
const { result } = renderHook(() => useContextMenu());

result.current[0].ref.current = {
getBoundingClientRect: jest.fn(() => ({ height: 100, width: 100 }))
};

expect(addEventListener).not.toHaveBeenCalled();

act(() => {
result.current[3].setVisible(true);
result.current[3].setCoords([100, 100]);
});

expect(addEventListener).toHaveBeenCalledTimes(5);
expect(addEventListener.mock.calls.map(call => call[0])).toEqual([
"mousedown",
"touchstart",
"scroll",
"contextmenu",
"keydown"
]);
});

test("useContextMenu sets correctly the style when changing visibility", () => {
const { result } = renderHook(() => useContextMenu());

result.current[0].ref.current = {
getBoundingClientRect: jest.fn(() => ({ height: 100, width: 100 }))
};

expect(result.current[0].style).toEqual({
opacity: 0,
pointerEvents: "none",
position: "fixed"
});

act(() => {
result.current[3].setVisible(true);
result.current[3].setCoords([100, 100]);
});

expect(result.current[0].style).toEqual({
left: "100px",
opacity: 1,
pointerEvents: "auto",
position: "fixed",
top: "100px"
});

act(() => {
result.current[3].setVisible(false);
});

expect(result.current[0].style).toEqual({
opacity: 0,
pointerEvents: "none",
position: "fixed"
});
});

test("useContextMenu keeps the menu inside the viewport", () => {
const { result } = renderHook(() => useContextMenu());

result.current[0].ref.current = {
getBoundingClientRect: jest.fn(() => ({ height: 100, width: 100 }))
};

expect(result.current[0].style).toEqual({
opacity: 0,
pointerEvents: "none",
position: "fixed"
});

act(() => {
result.current[3].setVisible(true);
result.current[3].setCoords([1000, 700]);
});

expect(result.current[0].style).toEqual({
left: "900px",
opacity: 1,
pointerEvents: "auto",
position: "fixed",
top: "600px"
});
});

test("useContextMenu hides the menu when a new contextmenu event is triggered", () => {
const { result } = renderHook(() => useContextMenu());

result.current[0].ref.current = {
getBoundingClientRect: jest.fn(() => ({ height: 100, width: 100 }))
};

act(() => {
result.current[3].setVisible(true);
});

act(() => {
const event = new MouseEvent("contextmenu");

document.dispatchEvent(event);
});

expect(result.current[3].isVisible).toEqual(false);
});

test("useContextMenu hides the menu when clicking outside", () => {
const { result } = renderHook(() => useContextMenu());

result.current[0].ref.current = {
getBoundingClientRect: jest.fn(() => ({ height: 100, width: 100 })),
contains: () => false
};

act(() => {
result.current[3].setVisible(true);
});

act(() => {
const event = new MouseEvent("mousedown");

document.dispatchEvent(event);
});

expect(result.current[3].isVisible).toEqual(false);
});

test("useContextMenu hides the menu when clicking outside", () => {
const { result } = renderHook(() => useContextMenu());

result.current[0].ref.current = {
getBoundingClientRect: jest.fn(() => ({ height: 100, width: 100 })),
contains: () => false
};

act(() => {
result.current[3].setVisible(true);
});

act(() => {
const event = new MouseEvent("touchstart");

document.dispatchEvent(event);
});

expect(result.current[3].isVisible).toEqual(false);
});

test("useContextMenu hides the menu on scroll", () => {
const { result } = renderHook(() => useContextMenu());

result.current[0].ref.current = {
getBoundingClientRect: jest.fn(() => ({ height: 100, width: 100 })),
contains: () => false
};

act(() => {
result.current[3].setVisible(true);
});

act(() => {
document.dispatchEvent(new Event("scroll"));
});

expect(result.current[3].isVisible).toEqual(false);
});

test("useContextMenu hides the menu on ESCAPE key press", () => {
const { result } = renderHook(() => useContextMenu());

result.current[0].ref.current = {
getBoundingClientRect: jest.fn(() => ({ height: 100, width: 100 })),
contains: () => false
};

act(() => {
result.current[3].setVisible(true);
});

act(() => {
const event = new KeyboardEvent("keydown", { keyCode: keyCodes.ESCAPE });
document.dispatchEvent(event);
});

expect(result.current[3].isVisible).toEqual(false);
});

test("useContextMenu hides the menu on ENTER key press", () => {
const { result } = renderHook(() => useContextMenu());

result.current[0].ref.current = {
getBoundingClientRect: jest.fn(() => ({ height: 100, width: 100 })),
contains: () => false
};

act(() => {
result.current[3].setVisible(true);
});

act(() => {
const event = new KeyboardEvent("keydown", { keyCode: keyCodes.ENTER });
document.dispatchEvent(event);
});

expect(result.current[3].isVisible).toEqual(false);
});
5 changes: 2 additions & 3 deletions src/buildUseContextMenuTrigger.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const defaultConfig = {
collect() {}
};

export default function buildUseContextMenuTrigger(triggerVisible, setCoords) {
export default function buildUseContextMenuTrigger(triggerVisible) {
return _config => {
const config = Object.assign({}, defaultConfig, _config);
const touchHandled = useRef(false);
Expand All @@ -29,8 +29,7 @@ export default function buildUseContextMenuTrigger(triggerVisible, setCoords) {
event.preventDefault();
event.stopPropagation();

setCoords(getCoords(event, config));
triggerVisible(config.collect());
triggerVisible(getCoords(event, config), config.collect());
};

const handleMouseDown = event => {
Expand Down
30 changes: 21 additions & 9 deletions src/useContextMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,31 @@ import {
import { getMenuPosition, getRTLMenuPosition } from "./helpers";
import buildUseContextMenuTrigger from "./buildUseContextMenuTrigger";

const ESCAPE = 27;
const baseStyles = { position: "fixed", opacity: 0, pointerEvents: "none" };
export const keyCodes = {
ESCAPE: 27,
ENTER: 13,
UP_ARROW: 38,
DOWN_ARROW: 40
};
const baseStyles = {
position: "fixed",
opacity: 0,
pointerEvents: "none"
};
const focusElement = el => el.focus();
const useContextMenu = ({ rtl, handleElementSelect = focusElement } = {}) => {
const menuRef = useRef();
const selectables = useRef([]);
const [style, setStyles] = useState(baseStyles);
const [selectedIndex, setSelectedIndex] = useState(-1);
const [isVisible, setVisible] = useState(false);
const [coords, setCoords] = useState();
const [coords, setCoords] = useState([0, 0]);
const [collectedData, setCollectedData] = useState();
const hideMenu = useCallback(() => setVisible(false), [setVisible]);
const triggerVisible = useCallback(
data => {
(coords, data) => {
setVisible(true);
setCoords(coords);
setCollectedData(data);
},
[setVisible, setCollectedData]
Expand All @@ -37,26 +47,28 @@ const useContextMenu = ({ rtl, handleElementSelect = focusElement } = {}) => {
};
const handleKeyNavigation = e => {
switch (e.keyCode) {
case ESCAPE:
case keyCodes.ESCAPE:
e.preventDefault();
hideMenu();
break;
case 38: // up arrow
case keyCodes.UP_ARROW:
e.preventDefault();
if (selectedIndex > 0) {
setSelectedIndex(s => s - 1);
handleElementSelect(selectables.current[selectedIndex - 1]);
}
break;
case 40: // down arrow
case keyCodes.DOWN_ARROW:
e.preventDefault();
if (selectedIndex + 1 < selectables.current.length) {
setSelectedIndex(s => s + 1);
handleElementSelect(selectables.current[selectedIndex + 1]);
}
break;
case 13: // enter
selectables.current[selectedIndex].click();
case keyCodes.ENTER:
if (selectedIndex !== -1) {
selectables.current[selectedIndex].click();
}
hideMenu();
break;
default:
Expand Down

0 comments on commit 24b102a

Please sign in to comment.