




























@@ -0,0 +1,187 @@
1+/* @vitest-environment jsdom */
2+3+import { html, nothing, render } from "lit";
4+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
5+import { type ResizableDivider } from "./resizable-divider.ts";
6+import "./resizable-divider.ts";
7+8+let container: HTMLDivElement;
9+const originalPointerEvent = globalThis.PointerEvent;
10+11+class TestPointerEvent extends MouseEvent {
12+readonly pointerId: number;
13+readonly pointerType: string;
14+readonly isPrimary: boolean;
15+16+constructor(type: string, init: PointerEventInit = {}) {
17+super(type, init);
18+this.pointerId = init.pointerId ?? 1;
19+this.pointerType = init.pointerType ?? "mouse";
20+this.isPrimary = init.isPrimary ?? true;
21+}
22+}
23+24+function nextFrame() {
25+return new Promise<void>((resolve) => {
26+requestAnimationFrame(() => resolve());
27+});
28+}
29+30+async function renderDivider() {
31+render(
32+html`
33+ <div id="split-root">
34+ <resizable-divider
35+ .splitRatio=${0.6}
36+ .minRatio=${0.4}
37+ .maxRatio=${0.7}
38+ .label=${"Resize sidebar"}
39+ ></resizable-divider>
40+ </div>
41+ `,
42+container,
43+);
44+45+const root = container.querySelector<HTMLDivElement>("#split-root");
46+const divider = container.querySelector<ResizableDivider>("resizable-divider");
47+expect(root).not.toBeNull();
48+expect(divider).not.toBeNull();
49+50+root!.getBoundingClientRect = vi.fn(() => ({
51+bottom: 0,
52+height: 0,
53+left: 0,
54+right: 400,
55+top: 0,
56+width: 400,
57+x: 0,
58+y: 0,
59+toJSON: () => ({}),
60+}));
61+62+await divider!.updateComplete;
63+await nextFrame();
64+return divider!;
65+}
66+67+function dispatchPointer(target: EventTarget, type: string, clientX: number) {
68+target.dispatchEvent(
69+new PointerEvent(type, {
70+bubbles: true,
71+button: 0,
72+cancelable: true,
73+ clientX,
74+pointerId: 7,
75+pointerType: "touch",
76+}),
77+);
78+}
79+80+describe("resizable-divider", () => {
81+beforeEach(() => {
82+if (!globalThis.PointerEvent) {
83+Object.defineProperty(globalThis, "PointerEvent", {
84+configurable: true,
85+value: TestPointerEvent as typeof PointerEvent,
86+});
87+}
88+container = document.createElement("div");
89+document.body.append(container);
90+});
91+92+afterEach(() => {
93+render(nothing, container);
94+container.remove();
95+if (originalPointerEvent) {
96+Object.defineProperty(globalThis, "PointerEvent", {
97+configurable: true,
98+value: originalPointerEvent,
99+});
100+} else {
101+delete (globalThis as Partial<typeof globalThis>).PointerEvent;
102+}
103+vi.restoreAllMocks();
104+});
105+106+it("exposes separator semantics and current split value on the host", async () => {
107+const divider = await renderDivider();
108+109+expect(divider.getAttribute("role")).toBe("separator");
110+expect(divider.getAttribute("tabindex")).toBe("0");
111+expect(divider.getAttribute("aria-label")).toBe("Resize sidebar");
112+expect(divider.getAttribute("aria-orientation")).toBe("vertical");
113+expect(divider.getAttribute("aria-valuemin")).toBe("40");
114+expect(divider.getAttribute("aria-valuemax")).toBe("70");
115+expect(divider.getAttribute("aria-valuenow")).toBe("60");
116+117+divider.splitRatio = 0.65;
118+await divider.updateComplete;
119+120+expect(divider.getAttribute("aria-valuenow")).toBe("65");
121+});
122+123+it("resizes with keyboard arrows, Home, and End", async () => {
124+const divider = await renderDivider();
125+const resized = vi.fn();
126+divider.addEventListener("resize", resized);
127+128+const arrowLeft = new KeyboardEvent("keydown", {
129+key: "ArrowLeft",
130+bubbles: true,
131+cancelable: true,
132+});
133+divider.dispatchEvent(arrowLeft);
134+expect(arrowLeft.defaultPrevented).toBe(true);
135+expect(resized).toHaveBeenLastCalledWith(
136+expect.objectContaining({ detail: { splitRatio: 0.58 } }),
137+);
138+139+const arrowRight = new KeyboardEvent("keydown", {
140+key: "ArrowRight",
141+shiftKey: true,
142+bubbles: true,
143+cancelable: true,
144+});
145+divider.dispatchEvent(arrowRight);
146+expect(arrowRight.defaultPrevented).toBe(true);
147+expect(resized).toHaveBeenLastCalledWith(
148+expect.objectContaining({ detail: { splitRatio: 0.65 } }),
149+);
150+151+divider.dispatchEvent(new KeyboardEvent("keydown", { key: "Home", bubbles: true }));
152+expect(resized).toHaveBeenLastCalledWith(
153+expect.objectContaining({ detail: { splitRatio: 0.4 } }),
154+);
155+156+divider.dispatchEvent(new KeyboardEvent("keydown", { key: "End", bubbles: true }));
157+expect(resized).toHaveBeenLastCalledWith(
158+expect.objectContaining({ detail: { splitRatio: 0.7 } }),
159+);
160+});
161+162+it("uses pointer events for mouse, pen, and touch dragging", async () => {
163+const divider = await renderDivider();
164+const resized = vi.fn();
165+const setPointerCapture = vi.fn();
166+const releasePointerCapture = vi.fn();
167+const hasPointerCapture = vi.fn(() => true);
168+divider.setPointerCapture = setPointerCapture;
169+divider.releasePointerCapture = releasePointerCapture;
170+divider.hasPointerCapture = hasPointerCapture;
171+divider.addEventListener("resize", resized);
172+173+dispatchPointer(divider, "pointerdown", 100);
174+expect(document.activeElement).toBe(divider);
175+expect(divider.classList.contains("dragging")).toBe(true);
176+expect(setPointerCapture).toHaveBeenCalledWith(7);
177+178+dispatchPointer(document, "pointermove", 220);
179+expect(resized).toHaveBeenLastCalledWith(
180+expect.objectContaining({ detail: { splitRatio: 0.7 } }),
181+);
182+183+dispatchPointer(document, "pointerup", 220);
184+expect(divider.classList.contains("dragging")).toBe(false);
185+expect(releasePointerCapture).toHaveBeenCalledWith(7);
186+});
187+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。