diff --git a/ui/package-lock.json b/ui/package-lock.json index c627b5e08..70b694870 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -11830,6 +11830,11 @@ "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==" }, + "hotkeys-js": { + "version": "3.7.6", + "resolved": "https://registry.npmjs.org/hotkeys-js/-/hotkeys-js-3.7.6.tgz", + "integrity": "sha512-X5d16trjp79o+OaCn7syXu0cs+TkLYlK/teE5FhpD1Cj9ROcEIhfIQ7Mhrk761ynF3NQLbLn5xRojP2UuSqDAw==" + }, "hpack.js": { "version": "2.1.6", "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", @@ -19071,10 +19076,19 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/react-hotkeys/-/react-hotkeys-2.0.0.tgz", "integrity": "sha512-3n3OU8vLX/pfcJrR3xJ1zlww6KS1kEJt0Whxc4FiGV+MJrQ1mYSYI3qS/11d2MJDFm8IhOXMTFQirfu6AVOF6Q==", + "dev": true, "requires": { "prop-types": "^15.6.1" } }, + "react-hotkeys-hook": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/react-hotkeys-hook/-/react-hotkeys-hook-2.1.3.tgz", + "integrity": "sha512-p4cH9n4c8Ac3kkNIf6a/97sLEJq+nnhP7fpT0bHhTwByZhBDU3j5hluu00FomxE3hMzJWk4knYG5ZuUM/qVJPw==", + "requires": { + "hotkeys-js": "3.7.6" + } + }, "react-idle-timer": { "version": "4.2.12", "resolved": "https://registry.npmjs.org/react-idle-timer/-/react-idle-timer-4.2.12.tgz", diff --git a/ui/package.json b/ui/package.json index 732d2e4f7..2fd32c4f2 100644 --- a/ui/package.json +++ b/ui/package.json @@ -41,7 +41,7 @@ "react-day-picker": "7.4.8", "react-dom": "16.13.1", "react-highlighter": "0.4.3", - "react-hotkeys": "2.0.0", + "react-hotkeys-hook": "2.1.3", "react-idle-timer": "4.2.12", "react-input-range": "1.3.0", "react-js-pagination": "3.0.3", diff --git a/ui/src/Components/LabelSetList/__snapshots__/index.test.js.snap b/ui/src/Components/LabelSetList/__snapshots__/index.test.js.snap index 5d4b9e057..88b15f024 100644 --- a/ui/src/Components/LabelSetList/__snapshots__/index.test.js.snap +++ b/ui/src/Components/LabelSetList/__snapshots__/index.test.js.snap @@ -50,9 +50,7 @@ exports[` matches snapshot with populated list 1`] = ` -
+
" diff --git a/ui/src/Components/MainModal/Configuration/AlertGroupSortConfiguration.test.js b/ui/src/Components/MainModal/Configuration/AlertGroupSortConfiguration.test.js index 422885e89..82e635844 100644 --- a/ui/src/Components/MainModal/Configuration/AlertGroupSortConfiguration.test.js +++ b/ui/src/Components/MainModal/Configuration/AlertGroupSortConfiguration.test.js @@ -127,7 +127,7 @@ describe("", () => { }); it("label select handles fetch errors", () => { - useFetchGet.mockReturnValue({ + useFetchGet.fetch.setMockedData({ response: null, error: "fake error", isLoading: false, diff --git a/ui/src/Components/MainModal/Configuration/MultiGridConfiguration.test.js b/ui/src/Components/MainModal/Configuration/MultiGridConfiguration.test.js index 27814dac6..0a11b31b5 100644 --- a/ui/src/Components/MainModal/Configuration/MultiGridConfiguration.test.js +++ b/ui/src/Components/MainModal/Configuration/MultiGridConfiguration.test.js @@ -64,7 +64,7 @@ describe("", () => { }); it("label select handles fetch errors", () => { - useFetchGet.mockReturnValue({ + useFetchGet.fetch.setMockedData({ response: null, error: "fake error", isLoading: false, diff --git a/ui/src/Components/ManagedSilence/DeleteSilence.test.js b/ui/src/Components/ManagedSilence/DeleteSilence.test.js index 599e9d97c..fe6a94e18 100644 --- a/ui/src/Components/ManagedSilence/DeleteSilence.test.js +++ b/ui/src/Components/ManagedSilence/DeleteSilence.test.js @@ -9,6 +9,7 @@ import moment from "moment"; import { EmptyAPIResponse } from "__mocks__/Fetch"; import { MockSilence } from "__mocks__/Alerts"; +import { PressKey } from "__mocks__/PressKey"; import { AlertStore } from "Stores/AlertStore"; import { SilenceFormStore } from "Stores/SilenceFormStore"; import { useFetchGet } from "Hooks/useFetchGet"; @@ -108,9 +109,7 @@ describe("", () => { tree.find("button.btn-danger").simulate("click"); expect(tree.find(".modal-body")).toHaveLength(1); - tree - .find("div.modal") - .simulate("keyDown", { key: "Escape", keyCode: 27, which: 27 }); + PressKey("Escape", 27); act(() => jest.runOnlyPendingTimers()); tree.update(); expect(tree.find(".modal-body")).toHaveLength(0); @@ -141,7 +140,7 @@ describe("", () => { }); it("renders ProgressMessage while loading preview", () => { - useFetchGet.mockReturnValue({ + useFetchGet.fetch.setMockedData({ response: null, error: false, isLoading: true, @@ -158,7 +157,7 @@ describe("", () => { }); it("renders empty LabelSetList with empty response", () => { - useFetchGet.mockReturnValue({ + useFetchGet.fetch.setMockedData({ response: EmptyAPIResponse(), error: false, isLoading: false, @@ -181,7 +180,7 @@ describe("", () => { }); it("handles empty grid response correctly", () => { - useFetchGet.mockReturnValue({ + useFetchGet.fetch.setMockedData({ response: EmptyAPIResponse(), error: false, isLoading: false, @@ -192,7 +191,7 @@ describe("", () => { }); it("renders ErrorMessage on failed preview fetch", () => { - useFetchGet.mockReturnValue({ + useFetchGet.fetch.setMockedData({ response: null, error: "fake error", isLoading: false, diff --git a/ui/src/Components/Modal/index.js b/ui/src/Components/Modal/index.js index e922dbc4a..adac75d04 100644 --- a/ui/src/Components/Modal/index.js +++ b/ui/src/Components/Modal/index.js @@ -4,7 +4,7 @@ import PropTypes from "prop-types"; import { disableBodyScroll, enableBodyScroll } from "body-scroll-lock"; -import { HotKeys } from "react-hotkeys"; +import { useHotkeys } from "react-hotkeys-hook"; import { MountModal, @@ -13,11 +13,6 @@ import { const ModalInner = ({ size, isUpper, toggleOpen, children }) => { const ref = useRef(null); - const hotKeysRef = useRef(null); - - useEffect(() => { - hotKeysRef.current && hotKeysRef.current.focus(); - }, []); useEffect(() => { document.body.classList.add("modal-open"); @@ -30,13 +25,10 @@ const ModalInner = ({ size, isUpper, toggleOpen, children }) => { }; }, [isUpper]); + useHotkeys("esc", toggleOpen); + return ( - +
{
{children}
- +
); }; diff --git a/ui/src/Components/Modal/index.test.js b/ui/src/Components/Modal/index.test.js index 4052b3a3a..a11b2d091 100644 --- a/ui/src/Components/Modal/index.test.js +++ b/ui/src/Components/Modal/index.test.js @@ -2,6 +2,7 @@ import React from "react"; import { mount } from "enzyme"; +import { PressKey } from "__mocks__/PressKey"; import { Modal } from "."; const fakeToggle = jest.fn(); @@ -90,11 +91,8 @@ describe("", () => { }); it("toggleOpen is called after pressing 'esc'", () => { - const tree = MountedModal(true); - tree - .find("div") - .at(0) - .simulate("keyDown", { key: "Escape", keyCode: 27, which: 27 }); + MountedModal(true); + PressKey("Escape", 27); expect(fakeToggle).toHaveBeenCalled(); }); }); diff --git a/ui/src/Components/NavBar/FilterInput/index.test.js b/ui/src/Components/NavBar/FilterInput/index.test.js index 22dbbebe7..d09a789f0 100644 --- a/ui/src/Components/NavBar/FilterInput/index.test.js +++ b/ui/src/Components/NavBar/FilterInput/index.test.js @@ -155,7 +155,7 @@ describe("", () => { }); it("handles failed suggestion fetches", async () => { - useFetchGet.mockReturnValue({ + useFetchGet.fetch.setMockedData({ response: null, error: "fake error", isLoading: false, diff --git a/ui/src/Components/Pagination/index.js b/ui/src/Components/Pagination/index.js index fb6913f8a..c5594d4ae 100644 --- a/ui/src/Components/Pagination/index.js +++ b/ui/src/Components/Pagination/index.js @@ -1,7 +1,7 @@ -import React, { useState, useCallback, useEffect, useRef } from "react"; +import React, { useState, useEffect } from "react"; import PropTypes from "prop-types"; -import { HotKeys } from "react-hotkeys"; +import { useHotkeys } from "react-hotkeys-hook"; import Pagination from "react-js-pagination"; @@ -21,11 +21,11 @@ const PageSelect = ({ setPageCallback, }) => { const [activePage, setActivePage] = useState(initialPage); - const ref = useRef(null); - useEffect(() => { - ref.current && ref.current.focus(); - }, []); + const onChange = (page) => { + setActivePage(page); + setPageCallback(page); + }; useEffect(() => { if (activePage > totalPages) { @@ -35,40 +35,30 @@ const PageSelect = ({ } }, [activePage, maxPerPage, totalPages, setPageCallback]); - const onChange = useCallback( - (page) => { + useHotkeys( + "left", + () => { + const page = Math.max(activePage - 1, 1); setActivePage(page); setPageCallback(page); }, - [setPageCallback] + {}, + [activePage, setActivePage, setPageCallback] ); - const onPageUp = useCallback(() => { - const page = Math.min(activePage + 1, totalPages); - setActivePage(page); - setPageCallback(page); - }, [activePage, setPageCallback, totalPages]); - - const onPageDown = useCallback(() => { - const page = Math.max(activePage - 1, 1); - setActivePage(page); - setPageCallback(page); - }, [activePage, setPageCallback]); + useHotkeys( + "right", + () => { + const page = Math.min(activePage + 1, totalPages); + setActivePage(page); + setPageCallback(page); + }, + {}, + [activePage, totalPages, setActivePage, setPageCallback] + ); return ( - +
{totalItemsCount > maxPerPage ? (
) : null} - +
); }; PageSelect.propTypes = { diff --git a/ui/src/Components/Pagination/index.test.js b/ui/src/Components/Pagination/index.test.js index 4205a770a..04e76f667 100644 --- a/ui/src/Components/Pagination/index.test.js +++ b/ui/src/Components/Pagination/index.test.js @@ -2,7 +2,7 @@ import React from "react"; import { mount } from "enzyme"; -import { PressKey } from "__mocks__/KeyPress"; +import { PressKey } from "__mocks__/PressKey"; import { PageSelect } from "."; let originalInnerWidth; @@ -33,28 +33,28 @@ describe("", () => { ); tree.simulate("focus"); - PressKey(tree, "ArrowRight", 39); + PressKey("ArrowRight", 39); expect(setPageCallback).toHaveBeenLastCalledWith(2); - PressKey(tree, "ArrowRight", 39); + PressKey("ArrowRight", 39); expect(setPageCallback).toHaveBeenLastCalledWith(3); - PressKey(tree, "ArrowRight", 39); + PressKey("ArrowRight", 39); expect(setPageCallback).toHaveBeenLastCalledWith(4); - PressKey(tree, "ArrowRight", 39); + PressKey("ArrowRight", 39); expect(setPageCallback).toHaveBeenLastCalledWith(4); - PressKey(tree, "ArrowLeft", 37); + PressKey("ArrowLeft", 37); expect(setPageCallback).toHaveBeenLastCalledWith(3); - PressKey(tree, "ArrowLeft", 37); + PressKey("ArrowLeft", 37); expect(setPageCallback).toHaveBeenLastCalledWith(2); - PressKey(tree, "ArrowLeft", 37); + PressKey("ArrowLeft", 37); expect(setPageCallback).toHaveBeenLastCalledWith(1); - PressKey(tree, "ArrowLeft", 37); + PressKey("ArrowLeft", 37); expect(setPageCallback).toHaveBeenLastCalledWith(1); }); diff --git a/ui/src/Components/SilenceModal/Browser/index.test.js b/ui/src/Components/SilenceModal/Browser/index.test.js index f58523e8a..b07f610dd 100644 --- a/ui/src/Components/SilenceModal/Browser/index.test.js +++ b/ui/src/Components/SilenceModal/Browser/index.test.js @@ -10,7 +10,7 @@ import { advanceTo, clear } from "jest-date-mock"; import { MockSilence } from "__mocks__/Alerts"; import { MockThemeContext } from "__mocks__/Theme"; -import { PressKey } from "__mocks__/KeyPress"; +import { PressKey } from "__mocks__/PressKey"; import { AlertStore } from "Stores/AlertStore"; import { Settings } from "Stores/Settings"; import { SilenceFormStore } from "Stores/SilenceFormStore"; @@ -163,7 +163,7 @@ describe("", () => { }); it("renders loading placeholder before fetch finishes", () => { - useFetchGet.mockReturnValue({ + useFetchGet.fetch.setMockedData({ response: null, error: false, isLoading: true, @@ -175,7 +175,7 @@ describe("", () => { }); it("renders loading placeholder before fetch finishes", () => { - useFetchGet.mockReturnValue({ + useFetchGet.fetch.setMockedData({ response: null, error: false, isLoading: true, @@ -187,7 +187,7 @@ describe("", () => { }); it("renders empty placeholder after fetch with zero results", () => { - useFetchGet.mockReturnValue({ + useFetchGet.fetch.setMockedData({ response: [], error: false, isLoading: false, @@ -199,7 +199,7 @@ describe("", () => { }); it("renders silences after successful fetch", () => { - useFetchGet.mockReturnValue({ + useFetchGet.fetch.setMockedData({ response: [ { cluster: cluster, @@ -217,7 +217,7 @@ describe("", () => { it("renders only first 6 silences on desktop", () => { global.window.innerWidth = 1024; - useFetchGet.mockReturnValue({ + useFetchGet.fetch.setMockedData({ response: MockSilenceList(7), error: false, isLoading: false, @@ -229,7 +229,7 @@ describe("", () => { it("renders only first 6 silences on mobile", () => { global.window.innerWidth = 500; - useFetchGet.mockReturnValue({ + useFetchGet.fetch.setMockedData({ response: MockSilenceList(7), error: false, isLoading: false, @@ -240,7 +240,7 @@ describe("", () => { }); it("renders last silence after page change", () => { - useFetchGet.mockReturnValue({ + useFetchGet.fetch.setMockedData({ response: MockSilenceList(7), error: false, isLoading: false, @@ -260,7 +260,7 @@ describe("", () => { }); it("renders next/previous page after arrow key press", () => { - useFetchGet.mockReturnValue({ + useFetchGet.fetch.setMockedData({ response: MockSilenceList(13), error: false, isLoading: false, @@ -274,33 +274,39 @@ describe("", () => { const paginator = tree.find(".components-pagination").at(0); paginator.simulate("focus"); - PressKey(paginator, "ArrowRight", 39); + PressKey("ArrowRight", 39); + tree.update(); expect(tree.find("li.page-item").at(2).hasClass("active")).toBe(true); expect(tree.find("ManagedSilence")).toHaveLength(6); - PressKey(paginator, "ArrowRight", 39); + PressKey("ArrowRight", 39); + tree.update(); expect(tree.find("li.page-item").at(3).hasClass("active")).toBe(true); expect(tree.find("ManagedSilence")).toHaveLength(1); - PressKey(paginator, "ArrowRight", 39); + PressKey("ArrowRight", 39); + tree.update(); expect(tree.find("li.page-item").at(3).hasClass("active")).toBe(true); expect(tree.find("ManagedSilence")).toHaveLength(1); - PressKey(paginator, "ArrowLeft", 37); + PressKey("ArrowLeft", 37); + tree.update(); expect(tree.find("li.page-item").at(2).hasClass("active")).toBe(true); expect(tree.find("ManagedSilence")).toHaveLength(6); - PressKey(paginator, "ArrowLeft", 37); + PressKey("ArrowLeft", 37); + tree.update(); expect(tree.find("li.page-item").at(1).hasClass("active")).toBe(true); expect(tree.find("ManagedSilence")).toHaveLength(6); - PressKey(paginator, "ArrowLeft", 37); + PressKey("ArrowLeft", 37); + tree.update(); expect(tree.find("li.page-item").at(1).hasClass("active")).toBe(true); expect(tree.find("ManagedSilence")).toHaveLength(6); }); it("resets pagination to last page on truncation", () => { - useFetchGet.mockReturnValue({ + useFetchGet.fetch.setMockedData({ response: MockSilenceList(13), error: false, isLoading: false, @@ -315,7 +321,7 @@ describe("", () => { expect(tree.find("ManagedSilence")).toHaveLength(1); expect(tree.find("li.page-item").at(3).hasClass("active")).toBe(true); - useFetchGet.mockReturnValue({ + useFetchGet.fetch.setMockedData({ response: MockSilenceList(8), error: false, isLoading: false, @@ -326,7 +332,7 @@ describe("", () => { expect(tree.find("ManagedSilence")).toHaveLength(2); expect(tree.find("li.page-item").at(2).hasClass("active")).toBe(true); - useFetchGet.mockReturnValue({ + useFetchGet.fetch.setMockedData({ response: [], error: false, isLoading: false, @@ -339,7 +345,7 @@ describe("", () => { }); it("renders error after failed fetch", () => { - useFetchGet.mockReturnValue({ + useFetchGet.fetch.setMockedData({ response: null, error: "fake failure", isLoading: false, diff --git a/ui/src/Components/SilenceModal/SilenceMatch/LabelNameInput.test.js b/ui/src/Components/SilenceModal/SilenceMatch/LabelNameInput.test.js index 7a48a5e28..b09a203d5 100644 --- a/ui/src/Components/SilenceModal/SilenceMatch/LabelNameInput.test.js +++ b/ui/src/Components/SilenceModal/SilenceMatch/LabelNameInput.test.js @@ -74,7 +74,7 @@ describe("", () => { }); it("handles fetch errors when populating suggestions", () => { - useFetchGet.mockReturnValue({ + useFetchGet.fetch.setMockedData({ response: null, error: "fake error", isLoading: false, diff --git a/ui/src/Components/SilenceModal/SilenceMatch/MatchCounter.test.js b/ui/src/Components/SilenceModal/SilenceMatch/MatchCounter.test.js index 168185b74..28b1dd4fe 100644 --- a/ui/src/Components/SilenceModal/SilenceMatch/MatchCounter.test.js +++ b/ui/src/Components/SilenceModal/SilenceMatch/MatchCounter.test.js @@ -40,7 +40,7 @@ describe("", () => { }); it("renders spinner icon while fetching", () => { - useFetchGet.mockReturnValueOnce({ + useFetchGet.fetch.setMockedData({ response: null, error: false, isLoading: true, @@ -53,7 +53,7 @@ describe("", () => { }); it("renders spinner icon with text-danger while retrying fetching", () => { - useFetchGet.mockReturnValueOnce({ + useFetchGet.fetch.setMockedData({ response: null, error: false, isLoading: true, @@ -65,7 +65,7 @@ describe("", () => { }); it("renders error icon on failed fetch", () => { - useFetchGet.mockReturnValueOnce({ + useFetchGet.fetch.setMockedData({ response: null, error: "failed", isLoading: false, @@ -77,7 +77,7 @@ describe("", () => { }); it("totalAlerts is 0 after mount", () => { - useFetchGet.mockReturnValueOnce({ + useFetchGet.fetch.setMockedData({ response: { totalAlerts: 0 }, error: false, isLoading: false, diff --git a/ui/src/Components/SilenceModal/SilencePreview/__snapshots__/index.test.js.snap b/ui/src/Components/SilenceModal/SilencePreview/__snapshots__/index.test.js.snap index 500492a15..d4bdbcd67 100644 --- a/ui/src/Components/SilenceModal/SilencePreview/__snapshots__/index.test.js.snap +++ b/ui/src/Components/SilenceModal/SilencePreview/__snapshots__/index.test.js.snap @@ -89,9 +89,7 @@ exports[` matches snapshot 1`] = ` -
+
diff --git a/ui/src/Components/SilenceModal/SilencePreview/index.test.js b/ui/src/Components/SilenceModal/SilencePreview/index.test.js index cfd9ca015..e85ca2cdd 100644 --- a/ui/src/Components/SilenceModal/SilencePreview/index.test.js +++ b/ui/src/Components/SilenceModal/SilencePreview/index.test.js @@ -107,7 +107,7 @@ describe("", () => { }); it("matches snapshot", () => { - useFetchGet.mockReturnValueOnce({ + useFetchGet.fetch.setMockedData({ response: MockAPIResponse(), error: false, isLoading: false, @@ -119,7 +119,7 @@ describe("", () => { }); it("renders Placeholder while loading preview", () => { - useFetchGet.mockReturnValue({ + useFetchGet.fetch.setMockedData({ response: null, error: false, isLoading: true, @@ -136,7 +136,7 @@ describe("", () => { }); it("handles empty grid response correctly", () => { - useFetchGet.mockReturnValueOnce({ + useFetchGet.fetch.setMockedData({ response: EmptyAPIResponse(), error: false, isLoading: false, @@ -148,7 +148,7 @@ describe("", () => { }); it("renders FetchError on failed fetch", () => { - useFetchGet.mockReturnValueOnce({ + useFetchGet.fetch.setMockedData({ response: null, error: "Fetch error", isLoading: false, diff --git a/ui/src/__mocks__/Hooks/useFetchGet.js b/ui/src/__mocks__/Hooks/useFetchGet.js index 942a8190b..4a8fe215e 100644 --- a/ui/src/__mocks__/Hooks/useFetchGet.js +++ b/ui/src/__mocks__/Hooks/useFetchGet.js @@ -12,6 +12,21 @@ const MockFetchStats = { }, reset() { this.getCalls = []; + this.mockedData = { + response: undefined, + error: undefined, + isLoading: undefined, + isRetrying: undefined, + }; + }, + mockedData: { + response: undefined, + error: undefined, + isLoading: undefined, + isRetrying: undefined, + }, + setMockedData(data) { + this.mockedData = data; }, }; @@ -82,7 +97,25 @@ const Mock = (uri, { autorun = true, deps = [] } = {}) => { // eslint-disable-next-line }, [uri, get, autorun, ...deps]); - return { response, error, isLoading, isRetrying, get }; + return { + response: + MockFetchStats.mockedData.response !== undefined + ? MockFetchStats.mockedData.response + : response, + error: + MockFetchStats.mockedData.error !== undefined + ? MockFetchStats.mockedData.error + : error, + isLoading: + MockFetchStats.mockedData.isLoading !== undefined + ? MockFetchStats.mockedData.isLoading + : isLoading, + isRetrying: + MockFetchStats.mockedData.isRetrying !== undefined + ? MockFetchStats.mockedData.isRetrying + : isRetrying, + get, + }; }; const useFetchGet = jest.fn(Mock); diff --git a/ui/src/__mocks__/KeyPress.js b/ui/src/__mocks__/KeyPress.js deleted file mode 100644 index c4652b0b1..000000000 --- a/ui/src/__mocks__/KeyPress.js +++ /dev/null @@ -1,6 +0,0 @@ -const PressKey = (tree, key, code) => { - tree.simulate("keyDown", { key: key, keyCode: code, which: code }); - tree.simulate("keyUp", { key: key, keyCode: code, which: code }); -}; - -export { PressKey }; diff --git a/ui/src/__mocks__/PressKey.js b/ui/src/__mocks__/PressKey.js new file mode 100644 index 000000000..09e0f811a --- /dev/null +++ b/ui/src/__mocks__/PressKey.js @@ -0,0 +1,22 @@ +import { act } from "react-dom/test-utils"; + +function PressKey(key, code) { + act(() => { + document.dispatchEvent( + new KeyboardEvent("keydown", { + key: key, + keyCode: code, + which: code, + }) + ); + document.dispatchEvent( + new KeyboardEvent("keyup", { + key: key, + keyCode: code, + which: code, + }) + ); + }); +} + +export { PressKey };