1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
|
import { fireEvent, render, screen } from "@testing-library/react";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import {
KEYBOARD_RESIZE_STEP,
ResizeHandle,
widthAfterKey,
} from "./ResizeHandle";
// jsdom has no PointerEvent; a MouseEvent carrying a pointerId is enough for
// React's pointer handlers and gives the tests real clientX values.
beforeAll(() => {
if (typeof window.PointerEvent === "undefined") {
class PointerEventPolyfill extends MouseEvent {
readonly pointerId: number;
constructor(type: string, init: PointerEventInit = {}) {
super(type, init);
this.pointerId = init.pointerId ?? 0;
}
}
Object.defineProperty(window, "PointerEvent", {
value: PointerEventPolyfill,
configurable: true,
});
}
});
const onResize = vi.fn();
const onReset = vi.fn();
function renderHandle(width = 200) {
render(
<ResizeHandle
label="Resize the file tree"
width={width}
min={120}
max={800}
onResize={onResize}
onReset={onReset}
/>,
);
return screen.getByRole("separator", { name: "Resize the file tree" });
}
describe("ResizeHandle", () => {
beforeEach(() => {
onResize.mockReset();
onReset.mockReset();
});
it("exposes the width through the separator pattern", () => {
const handle = renderHandle(300);
expect(handle.getAttribute("aria-orientation")).toBe("vertical");
expect(handle.getAttribute("aria-valuenow")).toBe("300");
expect(handle.getAttribute("aria-valuemin")).toBe("120");
expect(handle.getAttribute("aria-valuemax")).toBe("800");
});
it("reports the width while dragging and stops on pointer up", () => {
const handle = renderHandle(200);
fireEvent.pointerDown(handle, { clientX: 500, pointerId: 1 });
expect(handle.classList.contains("resize-handle-active")).toBe(true);
fireEvent.pointerMove(handle, { clientX: 540, pointerId: 1 });
fireEvent.pointerMove(handle, { clientX: 470, pointerId: 1 });
expect(onResize.mock.calls).toEqual([[240], [170]]);
fireEvent.pointerUp(handle, { clientX: 470, pointerId: 1 });
expect(handle.classList.contains("resize-handle-active")).toBe(false);
fireEvent.pointerMove(handle, { clientX: 600, pointerId: 1 });
expect(onResize).toHaveBeenCalledTimes(2);
});
it("ignores pointer moves that did not start on the handle", () => {
const handle = renderHandle(200);
fireEvent.pointerMove(handle, { clientX: 600, pointerId: 1 });
expect(onResize).not.toHaveBeenCalled();
});
it("resizes step by step with the arrow keys and jumps with Home / End", () => {
const handle = renderHandle(200);
fireEvent.keyDown(handle, { key: "ArrowRight" });
fireEvent.keyDown(handle, { key: "ArrowLeft" });
fireEvent.keyDown(handle, { key: "Home" });
fireEvent.keyDown(handle, { key: "End" });
expect(onResize.mock.calls).toEqual([
[200 + KEYBOARD_RESIZE_STEP],
[200 - KEYBOARD_RESIZE_STEP],
[120],
[800],
]);
});
it("leaves other keys alone", () => {
const handle = renderHandle(200);
fireEvent.keyDown(handle, { key: "Enter" });
fireEvent.keyDown(handle, { key: "Tab" });
expect(onResize).not.toHaveBeenCalled();
});
it("resets on double-click", () => {
const handle = renderHandle(200);
fireEvent.doubleClick(handle);
expect(onReset).toHaveBeenCalledTimes(1);
});
});
describe("widthAfterKey", () => {
it("maps the resize keys and returns null otherwise", () => {
expect(widthAfterKey("ArrowRight", 200, 120, 800)).toBe(216);
expect(widthAfterKey("ArrowLeft", 200, 120, 800)).toBe(184);
expect(widthAfterKey("Home", 200, 120, 800)).toBe(120);
expect(widthAfterKey("End", 200, 120, 800)).toBe(800);
expect(widthAfterKey("a", 200, 120, 800)).toBeNull();
});
});
|