Skip to content

Commit 0ac72bd

Browse files
committed
test: expand test coverage with extreme scenarios, minimap, and controls suites
Add new test suites for minimap (rendering, panning, sync), controls (callbacks, limits, ref, state), and extreme edge cases (extreme sizes and bounds for pan, pinch, zoom). Expand existing tests across base, positions, pan-touch, pan-track-pad, and exclusion features for broader coverage. Made-with: Cursor
1 parent ac87fde commit 0ac72bd

35 files changed

Lines changed: 3773 additions & 203 deletions

.cursor/rules/testing-regressions.mdc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ alwaysApply: false
77

88
# Regressions and test harness
99

10+
Use `yarn test` command.
11+
1012
## GitHub-linked regressions
1113

1214
- For each **unique** bug (see matching docs/bugs/\*.md), add **at least one**
Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
import React from "react";
2+
3+
import {
4+
TransformWrapper,
5+
TransformComponent,
6+
ReactZoomPanPinchProps,
7+
} from "../../src";
8+
import { Controls } from "./controls.utils";
9+
10+
interface ExtremeExampleOptions {
11+
props?: ReactZoomPanPinchProps;
12+
onRender: () => void;
13+
children?: React.ReactNode;
14+
}
15+
16+
/**
17+
* Huge canvas: 5000×5000 content in a 500×500 wrapper.
18+
* Simulates large maps, venue seating charts, or high-res image viewers
19+
* where the content-to-viewport ratio is 10:1.
20+
*/
21+
export const HugeCanvasExample = ({
22+
props = {},
23+
onRender,
24+
children,
25+
}: ExtremeExampleOptions) => {
26+
onRender();
27+
28+
return (
29+
<TransformWrapper {...props}>
30+
<div>
31+
{children}
32+
<Controls />
33+
<TransformComponent
34+
wrapperProps={
35+
{
36+
"data-testid": "wrapper",
37+
} as React.HTMLAttributes<HTMLDivElement>
38+
}
39+
contentProps={
40+
{
41+
"data-testid": "content",
42+
} as React.HTMLAttributes<HTMLDivElement>
43+
}
44+
wrapperStyle={{ width: "500px", height: "500px" }}
45+
contentStyle={{ width: "5000px", height: "5000px" }}
46+
>
47+
<div
48+
data-testid="canvas"
49+
style={{
50+
width: "5000px",
51+
height: "5000px",
52+
background:
53+
"repeating-conic-gradient(#808080 0% 25%, transparent 0% 50%) 0 0 / 100px 100px",
54+
}}
55+
/>
56+
</TransformComponent>
57+
</div>
58+
</TransformWrapper>
59+
);
60+
};
61+
62+
/**
63+
* Tall document: 500×3000 content in a 500×500 wrapper.
64+
* Simulates a long document, vertical timeline, or chat transcript
65+
* where vertical scrolling dominates.
66+
*/
67+
export const TallDocumentExample = ({
68+
props = {},
69+
onRender,
70+
children,
71+
}: ExtremeExampleOptions) => {
72+
onRender();
73+
74+
return (
75+
<TransformWrapper {...props}>
76+
<div>
77+
{children}
78+
<Controls />
79+
<TransformComponent
80+
wrapperProps={
81+
{
82+
"data-testid": "wrapper",
83+
} as React.HTMLAttributes<HTMLDivElement>
84+
}
85+
contentProps={
86+
{
87+
"data-testid": "content",
88+
} as React.HTMLAttributes<HTMLDivElement>
89+
}
90+
wrapperStyle={{ width: "500px", height: "500px" }}
91+
contentStyle={{ width: "500px", height: "3000px" }}
92+
>
93+
<div
94+
data-testid="document"
95+
style={{
96+
width: "500px",
97+
height: "3000px",
98+
background: "linear-gradient(to bottom, #333, #999)",
99+
}}
100+
/>
101+
</TransformComponent>
102+
</div>
103+
</TransformWrapper>
104+
);
105+
};
106+
107+
/**
108+
* Wide panorama: 3000×300 content in a 500×500 wrapper.
109+
* Simulates a panoramic photo, horizontal timeline, or wide infographic
110+
* where horizontal scrolling dominates.
111+
*/
112+
export const WidePanoramaExample = ({
113+
props = {},
114+
onRender,
115+
children,
116+
}: ExtremeExampleOptions) => {
117+
onRender();
118+
119+
return (
120+
<TransformWrapper {...props}>
121+
<div>
122+
{children}
123+
<Controls />
124+
<TransformComponent
125+
wrapperProps={
126+
{
127+
"data-testid": "wrapper",
128+
} as React.HTMLAttributes<HTMLDivElement>
129+
}
130+
contentProps={
131+
{
132+
"data-testid": "content",
133+
} as React.HTMLAttributes<HTMLDivElement>
134+
}
135+
wrapperStyle={{ width: "500px", height: "500px" }}
136+
contentStyle={{ width: "3000px", height: "300px" }}
137+
>
138+
<div
139+
data-testid="panorama"
140+
style={{
141+
width: "3000px",
142+
height: "300px",
143+
background: "linear-gradient(to right, #234, #987)",
144+
}}
145+
/>
146+
</TransformComponent>
147+
</div>
148+
</TransformWrapper>
149+
);
150+
};
151+
152+
/**
153+
* Tiny viewport: 1000×1000 content in a 50×50 wrapper.
154+
* Simulates a thumbnail navigator, mini-map preview, or severely constrained
155+
* mobile viewport where the wrapper is extremely small relative to content.
156+
*/
157+
export const TinyViewportExample = ({
158+
props = {},
159+
onRender,
160+
children,
161+
}: ExtremeExampleOptions) => {
162+
onRender();
163+
164+
return (
165+
<TransformWrapper {...props}>
166+
<div>
167+
{children}
168+
<Controls />
169+
<TransformComponent
170+
wrapperProps={
171+
{
172+
"data-testid": "wrapper",
173+
} as React.HTMLAttributes<HTMLDivElement>
174+
}
175+
contentProps={
176+
{
177+
"data-testid": "content",
178+
} as React.HTMLAttributes<HTMLDivElement>
179+
}
180+
wrapperStyle={{ width: "50px", height: "50px" }}
181+
contentStyle={{ width: "1000px", height: "1000px" }}
182+
>
183+
<div
184+
data-testid="minimap"
185+
style={{
186+
width: "1000px",
187+
height: "1000px",
188+
background: "#556",
189+
}}
190+
/>
191+
</TransformComponent>
192+
</div>
193+
</TransformWrapper>
194+
);
195+
};
196+
197+
/**
198+
* Mismatched aspect ratios: 4000×200 wide content in a 200×800 tall wrapper.
199+
* The content and wrapper have inverted aspect ratios — wide content
200+
* inside a tall viewport. Stresses bounds calculation and centering logic.
201+
*/
202+
export const MismatchedAspectExample = ({
203+
props = {},
204+
onRender,
205+
children,
206+
}: ExtremeExampleOptions) => {
207+
onRender();
208+
209+
return (
210+
<TransformWrapper {...props}>
211+
<div>
212+
{children}
213+
<Controls />
214+
<TransformComponent
215+
wrapperProps={
216+
{
217+
"data-testid": "wrapper",
218+
} as React.HTMLAttributes<HTMLDivElement>
219+
}
220+
contentProps={
221+
{
222+
"data-testid": "content",
223+
} as React.HTMLAttributes<HTMLDivElement>
224+
}
225+
wrapperStyle={{ width: "200px", height: "800px" }}
226+
contentStyle={{ width: "4000px", height: "200px" }}
227+
>
228+
<div
229+
data-testid="strip"
230+
style={{
231+
width: "4000px",
232+
height: "200px",
233+
background: "linear-gradient(to right, #a33, #33a)",
234+
}}
235+
/>
236+
</TransformComponent>
237+
</div>
238+
</TransformWrapper>
239+
);
240+
};
Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,77 @@
1+
import React from "react";
2+
import { render, fireEvent, screen } from "@testing-library/react";
3+
4+
import { TransformWrapper, TransformComponent } from "../../../src";
5+
import { renderApp } from "../../utils";
6+
17
describe("Base [Interactions]", () => {
28
describe("When clicking in nested button", () => {
39
it("should allow to trigger button callback", async () => {
4-
// TODO: Implement test
5-
expect(true).toBe(true);
10+
const onClick = jest.fn();
11+
render(
12+
<TransformWrapper>
13+
<TransformComponent>
14+
<button data-testid="nested-btn" onClick={onClick}>
15+
Click me
16+
</button>
17+
</TransformComponent>
18+
</TransformWrapper>,
19+
);
20+
21+
const btn = screen.getByTestId("nested-btn");
22+
fireEvent.click(btn);
23+
expect(onClick).toHaveBeenCalledTimes(1);
24+
});
25+
it("should allow multiple clicks on nested button", async () => {
26+
const onClick = jest.fn();
27+
render(
28+
<TransformWrapper>
29+
<TransformComponent>
30+
<button data-testid="nested-btn" onClick={onClick}>
31+
Click me
32+
</button>
33+
</TransformComponent>
34+
</TransformWrapper>,
35+
);
36+
37+
const btn = screen.getByTestId("nested-btn");
38+
fireEvent.click(btn);
39+
fireEvent.click(btn);
40+
fireEvent.click(btn);
41+
expect(onClick).toHaveBeenCalledTimes(3);
42+
});
43+
});
44+
45+
describe("When interacting with nested input", () => {
46+
it("should allow typing in nested input", async () => {
47+
render(
48+
<TransformWrapper>
49+
<TransformComponent>
50+
<input data-testid="nested-input" type="text" />
51+
</TransformComponent>
52+
</TransformWrapper>,
53+
);
54+
55+
const input = screen.getByTestId("nested-input") as HTMLInputElement;
56+
fireEvent.change(input, { target: { value: "hello" } });
57+
expect(input.value).toBe("hello");
58+
});
59+
});
60+
61+
describe("When using control buttons", () => {
62+
it("should trigger onZoom callback on zoom in button click", async () => {
63+
const onZoom = jest.fn();
64+
const { zoomInBtn } = renderApp({ onZoom });
65+
66+
fireEvent(zoomInBtn, new MouseEvent("click", { bubbles: true }));
67+
expect(onZoom).toHaveBeenCalled();
68+
});
69+
it("should trigger onZoom callback on zoom out button click", async () => {
70+
const onZoom = jest.fn();
71+
const { zoomOutBtn, ref } = renderApp({ onZoom, initialScale: 2 });
72+
73+
fireEvent(zoomOutBtn, new MouseEvent("click", { bubbles: true }));
74+
expect(onZoom).toHaveBeenCalled();
675
});
776
});
877
});

0 commit comments

Comments
 (0)