Skip to content

Commit 6afa64e

Browse files
committed
refactor(core-test): pass modifier keys via event properties instead of keyDown
Test helpers now propagate ctrlKey/shiftKey/etc. through synthesized mouse and wheel events rather than dispatching separate keyDown events. This mirrors how syncModifierKeys reads modifier state from interaction events, making tests more realistic—especially for iframe scenarios where keydown never fires. Also simplifies Storybook controls: replaces custom CSS tooltips with native title attributes and collapses multi-line SVG attributes. Made-with: Cursor
1 parent 4a19bea commit 6afa64e

8 files changed

Lines changed: 94 additions & 144 deletions

File tree

__tests__/features/pan/pan.keys.spec.tsx

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import { fireEvent } from "@testing-library/react";
2-
31
import { renderApp } from "../../utils";
42

53
describe("Pan [Keys]", () => {
@@ -22,8 +20,7 @@ describe("Pan [Keys]", () => {
2220
});
2321

2422
expect(content.style.transform).toBe("translate(0px, 0px) scale(1)");
25-
fireEvent.keyDown(document, { key: "Control" });
26-
pan({ x: -100, y: -100 });
23+
pan({ x: -100, y: -100, modifiers: { ctrlKey: true } });
2724
expect(content.style.transform).toBe(
2825
"translate(-100px, -100px) scale(1)",
2926
);
@@ -38,8 +35,7 @@ describe("Pan [Keys]", () => {
3835
});
3936

4037
expect(content.style.transform).toBe("translate(0px, 0px) scale(1)");
41-
fireEvent.keyDown(document, { key: "Control" });
42-
pan({ x: -100, y: -100 });
38+
pan({ x: -100, y: -100, modifiers: { ctrlKey: true } });
4339
expect(content.style.transform).toBe("translate(0px, 0px) scale(1)");
4440
});
4541
it("should change translate when activated", async () => {
@@ -50,9 +46,11 @@ describe("Pan [Keys]", () => {
5046
});
5147

5248
expect(content.style.transform).toBe("translate(0px, 0px) scale(1)");
53-
fireEvent.keyDown(document, { key: "Control" });
54-
fireEvent.keyDown(document, { key: "Shift" });
55-
pan({ x: -100, y: -100 });
49+
pan({
50+
x: -100,
51+
y: -100,
52+
modifiers: { ctrlKey: true, shiftKey: true },
53+
});
5654
expect(content.style.transform).toBe(
5755
"translate(-100px, -100px) scale(1)",
5856
);
@@ -69,9 +67,11 @@ describe("Pan [Keys]", () => {
6967
});
7068

7169
expect(content.style.transform).toBe("translate(0px, 0px) scale(1)");
72-
fireEvent.keyDown(document, { key: "Control" });
73-
fireEvent.keyDown(document, { key: "Shift" });
74-
pan({ x: -100, y: -100 });
70+
pan({
71+
x: -100,
72+
y: -100,
73+
modifiers: { ctrlKey: true, shiftKey: true },
74+
});
7575
expect(content.style.transform).toBe(
7676
"translate(-100px, -100px) scale(1)",
7777
);
@@ -85,8 +85,7 @@ describe("Pan [Keys]", () => {
8585
});
8686

8787
expect(content.style.transform).toBe("translate(0px, 0px) scale(1)");
88-
fireEvent.keyDown(document, { key: "Control" });
89-
pan({ x: -100, y: -100 });
88+
pan({ x: -100, y: -100, modifiers: { ctrlKey: true } });
9089
expect(content.style.transform).toBe("translate(0px, 0px) scale(1)");
9190
});
9291
});

__tests__/features/props/panning-props.spec.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,7 @@ describe("ReactZoomPanPinchProps.panning", () => {
9191
pan({ x: -100, y: -100 });
9292
expect(content.style.transform).toBe("translate(0px, 0px) scale(1)");
9393

94-
fireEvent.keyDown(document, { key: "Shift" });
95-
pan({ x: -100, y: -100 });
94+
pan({ x: -100, y: -100, modifiers: { shiftKey: true } });
9695
expect(content.style.transform).not.toBe(
9796
"translate(0px, 0px) scale(1)",
9897
);

__tests__/features/zoom/zoom.keys.spec.tsx

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import { fireEvent } from "@testing-library/react";
2-
31
import { renderApp } from "../../utils";
42

53
describe("Zoom [Keys]", () => {
@@ -22,8 +20,7 @@ describe("Zoom [Keys]", () => {
2220
});
2321

2422
expect(content.style.transform).toBe("translate(0px, 0px) scale(1)");
25-
fireEvent.keyDown(document, { key: "Control" });
26-
zoom({ value: 2 });
23+
zoom({ value: 2, modifiers: { ctrlKey: true } });
2724
expect(content.style.transform).toBe("translate(0px, 0px) scale(2)");
2825
});
2926
});
@@ -36,8 +33,7 @@ describe("Zoom [Keys]", () => {
3633
});
3734

3835
expect(content.style.transform).toBe("translate(0px, 0px) scale(1)");
39-
fireEvent.keyDown(document, { key: "Control" });
40-
zoom({ value: 2 });
36+
zoom({ value: 2, modifiers: { ctrlKey: true } });
4137
expect(content.style.transform).toBe("translate(0px, 0px) scale(1)");
4238
});
4339
it("should change translate when activated", async () => {
@@ -48,9 +44,7 @@ describe("Zoom [Keys]", () => {
4844
});
4945

5046
expect(content.style.transform).toBe("translate(0px, 0px) scale(1)");
51-
fireEvent.keyDown(document, { key: "Control" });
52-
fireEvent.keyDown(document, { key: "Shift" });
53-
zoom({ value: 2 });
47+
zoom({ value: 2, modifiers: { ctrlKey: true, shiftKey: true } });
5448
expect(content.style.transform).toBe("translate(0px, 0px) scale(2)");
5549
});
5650
});
@@ -65,9 +59,7 @@ describe("Zoom [Keys]", () => {
6559
});
6660

6761
expect(content.style.transform).toBe("translate(0px, 0px) scale(1)");
68-
fireEvent.keyDown(document, { key: "Control" });
69-
fireEvent.keyDown(document, { key: "Shift" });
70-
zoom({ value: 2 });
62+
zoom({ value: 2, modifiers: { ctrlKey: true, shiftKey: true } });
7163
expect(content.style.transform).toBe("translate(0px, 0px) scale(2)");
7264
});
7365
it("should not change translate with partial activation", async () => {
@@ -79,8 +71,7 @@ describe("Zoom [Keys]", () => {
7971
});
8072

8173
expect(content.style.transform).toBe("translate(0px, 0px) scale(1)");
82-
fireEvent.keyDown(document, { key: "Control" });
83-
zoom({ value: 2 });
74+
zoom({ value: 2, modifiers: { ctrlKey: true } });
8475
expect(content.style.transform).toBe("translate(0px, 0px) scale(1)");
8576
});
8677
});

__tests__/features/zoom/zoom.wheel-while-pan.spec.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,13 +74,10 @@ describe("Zoom [Keyboard]", () => {
7474
);
7575
expect(ref.current!.instance.state.scale).toBe(1);
7676

77-
fireEvent.keyDown(document, { key: "Control" });
7877
fireEvent(
7978
content,
80-
new WheelEvent("wheel", { bubbles: true, deltaY: -5 }),
79+
new WheelEvent("wheel", { bubbles: true, deltaY: -5, ctrlKey: true }),
8180
);
8281
expect(ref.current!.instance.state.scale).toBeGreaterThan(1);
83-
84-
fireEvent.keyUp(document, { key: "Control" });
8582
});
8683
});

__tests__/regressions/zoom-behavior.spec.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,9 @@ describe("regressions: wheel and zoom behavior", () => {
5454
);
5555
expect(ref.current!.instance.state.scale).toBe(1);
5656

57-
fireEvent.keyDown(document, { key: "Control" });
5857
fireEvent(
5958
content,
60-
new WheelEvent("wheel", { bubbles: true, deltaY: -5 }),
59+
new WheelEvent("wheel", { bubbles: true, deltaY: -5, ctrlKey: true }),
6160
);
6261
expect(ref.current!.instance.state.scale).toBeGreaterThan(1);
6362
});

__tests__/utils/render-app.tsx

Lines changed: 47 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,16 @@ export interface PanGestureOptions {
134134
* @default { clientX: 0, clientY: 0 }
135135
*/
136136
from?: { clientX: number; clientY: number };
137+
/**
138+
* Modifier keys held during the gesture. Propagated to every synthesized
139+
* mouse event so that `syncModifierKeys` (iframe support) sees them.
140+
*/
141+
modifiers?: {
142+
shiftKey?: boolean;
143+
ctrlKey?: boolean;
144+
metaKey?: boolean;
145+
altKey?: boolean;
146+
};
137147
}
138148

139149
/** Return type of {@link renderApp}. */
@@ -176,7 +186,16 @@ export interface RenderApp {
176186
*/
177187
trackPadPan: (options: PanGestureOptions) => void;
178188
/** Simulate wheel-driven zoom to a target scale value. */
179-
zoom: (options: { value: number; center?: [number, number] }) => void;
189+
zoom: (options: {
190+
value: number;
191+
center?: [number, number];
192+
modifiers?: {
193+
shiftKey?: boolean;
194+
ctrlKey?: boolean;
195+
metaKey?: boolean;
196+
altKey?: boolean;
197+
};
198+
}) => void;
180199
/** Simulate a two-finger pinch gesture to a target scale value. */
181200
pinch: (options: {
182201
value: number;
@@ -330,7 +349,7 @@ export const renderApp = ({
330349
const wrapper = screen.getByTestId("wrapper");
331350

332351
const zoom: RenderApp["zoom"] = (options) => {
333-
const { value, center } = options;
352+
const { value, center, modifiers = {} } = options;
334353
if (!ref.current) throw new Error("ref.current is null");
335354

336355
userEvent.hover(content);
@@ -363,6 +382,7 @@ export const renderApp = ({
363382
deltaY: isZoomIn ? -newStep : newStep,
364383
clientX: cx,
365384
clientY: cy,
385+
...modifiers,
366386
}),
367387
);
368388
} else {
@@ -395,43 +415,51 @@ export const renderApp = ({
395415
const from = isZoomIn ? 1 : 2;
396416
const step = 0.1;
397417

398-
let pinchValue = 0;
418+
const scaleCloseEnough = (a: number, b: number) => Math.abs(a - b) < 1e-5;
419+
420+
// Pinch zoom is driven by touch distance vs pinch-start distance. Increasing
421+
// separation zooms in; narrowing zooms out — the previous helper only ever
422+
// increased separation, so zoom-out gestures ran away to max scale.
423+
let spread = isZoomIn ? step : from + 6;
399424

400425
fireEvent.touchStart(content, {
401-
touches: getPinchTouches(content, center, step, from),
426+
touches: getPinchTouches(content, center, spread, from),
402427
});
403428

404429
const startTime = Date.now();
405430
while (Date.now() - startTime < 1000) {
431+
const currentScale = ref.current.instance.state.scale;
432+
if (scaleCloseEnough(currentScale, value)) {
433+
break;
434+
}
406435
if (
407-
(isZoomIn
408-
? ref.current.instance.state.scale < value
409-
: ref.current.instance.state.scale > value) &&
410-
ref.current.instance.state.scale !== value
436+
isZoomIn
437+
? currentScale < value - 1e-5
438+
: currentScale > value + 1e-5
411439
) {
412-
const scaleDifference = Math.abs(
413-
ref.current.instance.state.scale - value,
414-
);
440+
const scaleDifference = Math.abs(currentScale - value);
415441
const isNearScale = scaleDifference < 0.1;
416442
const newStep = isNearScale ? step / 6 : step;
417443

418-
// Pinch-out must narrow finger spacing (smaller dx); pinch-in widens it.
419-
pinchValue += isZoomIn ? newStep : -newStep;
444+
spread = isZoomIn ? spread + newStep : spread - newStep;
445+
if (!isZoomIn) {
446+
spread = Math.max(spread, -from + step);
447+
}
420448

421449
fireEvent.touchMove(content, {
422-
touches: getPinchTouches(content, center, pinchValue, from),
450+
touches: getPinchTouches(content, center, spread, from),
423451
});
424452
} else {
425453
break;
426454
}
427455
}
428456

429457
fireEvent.touchMove(content, {
430-
touches: getPinchTouches(content, targetCenter, pinchValue, from),
458+
touches: getPinchTouches(content, targetCenter, spread, from),
431459
});
432460

433461
fireEvent.touchEnd(content, {
434-
touches: getPinchTouches(content, center, pinchValue, from),
462+
touches: getPinchTouches(content, center, spread, from),
435463
});
436464
};
437465

@@ -441,12 +469,14 @@ export const renderApp = ({
441469
moveEventCount = 1,
442470
msPerStep = DEFAULT_MS_PER_STEP,
443471
from = { clientX: 0, clientY: 0 },
472+
modifiers = {},
444473
}) => {
445474
userEvent.hover(content);
446475
fireEvent.mouseDown(content, {
447476
clientX: from.clientX,
448477
clientY: from.clientY,
449478
buttons: 1,
479+
...modifiers,
450480
});
451481

452482
for (let i = 1; i <= moveEventCount; i++) {
@@ -456,6 +486,7 @@ export const renderApp = ({
456486
clientX: from.clientX + x * progress,
457487
clientY: from.clientY + y * progress,
458488
buttons: 1,
489+
...modifiers,
459490
});
460491
}
461492

0 commit comments

Comments
 (0)