| ... | @@ -1,8 +1,13 @@ | ... | @@ -1,8 +1,13 @@ |
| 1 | import { describe, expect, it } from "vite-plus/test"; | 1 | // @vitest-environment jsdom |
| | 2 | |
| | 3 | import { act, createElement } from "react"; |
| | 4 | import { createRoot, type Root } from "react-dom/client"; |
| | 5 | import { afterEach, beforeEach, describe, expect, it, vi } from "vite-plus/test"; |
| 2 | | 6 | |
| 3 | import { | 7 | import { |
| 4 | areShortcutModifierStatesEqual, | 8 | areShortcutModifierStatesEqual, |
| 5 | shortcutModifierStateAfterKeyboardEvent, | 9 | shortcutModifierStateAfterKeyboardEvent, |
| | 10 | useShortcutModifierState, |
| 6 | type ShortcutModifierState, | 11 | type ShortcutModifierState, |
| 7 | } from "./shortcutModifierState"; | 12 | } from "./shortcutModifierState"; |
| 8 | | 13 | |
| ... | @@ -13,6 +18,67 @@ const emptyState = (): ShortcutModifierState => ({ | ... | @@ -13,6 +18,67 @@ const emptyState = (): ShortcutModifierState => ({ |
| 13 | shiftKey: false, | 18 | shiftKey: false, |
| 14 | }); | 19 | }); |
| 15 | | 20 | |
| | 21 | describe("useShortcutModifierState", () => { |
| | 22 | let root: Root; |
| | 23 | let container: HTMLDivElement; |
| | 24 | |
| | 25 | beforeEach(() => { |
| | 26 | vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true); |
| | 27 | container = document.createElement("div"); |
| | 28 | document.body.append(container); |
| | 29 | root = createRoot(container); |
| | 30 | }); |
| | 31 | |
| | 32 | afterEach(async () => { |
| | 33 | await act(async () => root.unmount()); |
| | 34 | container.remove(); |
| | 35 | vi.unstubAllGlobals(); |
| | 36 | }); |
| | 37 | |
| | 38 | it.each(["keyup", "paste", "blur"] as const)( |
| | 39 | "does not render for unchanged modifiers after %s resets the state", |
| | 40 | async (reset) => { |
| | 41 | const render = vi.fn(); |
| | 42 | function Consumer() { |
| | 43 | render(useShortcutModifierState()); |
| | 44 | return null; |
| | 45 | } |
| | 46 | await act(async () => { |
| | 47 | root.render(createElement(Consumer)); |
| | 48 | }); |
| | 49 | const press = async (type: "keydown" | "keyup", key: string, shiftKey = false) => { |
| | 50 | await act(async () => { |
| | 51 | window.dispatchEvent(new KeyboardEvent(type, { key, shiftKey })); |
| | 52 | }); |
| | 53 | }; |
| | 54 | |
| | 55 | await press("keydown", "Shift", true); |
| | 56 | expect(render).toHaveBeenLastCalledWith({ ...emptyState(), shiftKey: true }); |
| | 57 | if (reset === "keyup") { |
| | 58 | await press("keyup", "Shift"); |
| | 59 | } else { |
| | 60 | await act(async () => { |
| | 61 | window.dispatchEvent(new Event(reset)); |
| | 62 | }); |
| | 63 | } |
| | 64 | expect(render).toHaveBeenLastCalledWith(emptyState()); |
| | 65 | render.mockClear(); |
| | 66 | |
| | 67 | for (const key of "typing") { |
| | 68 | await press("keydown", key); |
| | 69 | await press("keyup", key); |
| | 70 | } |
| | 71 | await act(async () => { |
| | 72 | window.dispatchEvent(new Event("paste")); |
| | 73 | window.dispatchEvent(new Event("blur")); |
| | 74 | }); |
| | 75 | expect(render).not.toHaveBeenCalled(); |
| | 76 | await press("keydown", "Shift", true); |
| | 77 | expect(render).toHaveBeenCalledExactlyOnceWith({ ...emptyState(), shiftKey: true }); |
| | 78 | }, |
| | 79 | ); |
| | 80 | }); |
| | 81 | |
| 16 | function keyboardEventLike(type: "keydown" | "keyup", init: Partial<KeyboardEvent>): KeyboardEvent { | 82 | function keyboardEventLike(type: "keydown" | "keyup", init: Partial<KeyboardEvent>): KeyboardEvent { |
| 17 | return { | 83 | return { |
| 18 | type, | 84 | type, |