Skip to content

Commit 40d1f57

Browse files
test: add tests for QRModal download QR code functionality (#600)
1 parent b77394d commit 40d1f57

1 file changed

Lines changed: 366 additions & 0 deletions

File tree

Lines changed: 366 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,366 @@
1+
import React from "react";
2+
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
3+
import userEvent from "@testing-library/user-event";
4+
import QRModal from "@/components/QRModal";
5+
6+
vi.mock("@/lib/freighter", () => ({
7+
isWalletConnected: vi.fn().mockResolvedValue(false),
8+
}));
9+
10+
vi.mock("@/components/FocusTrap", () => ({
11+
__esModule: true,
12+
default: ({ children }: { children: React.ReactNode }) => <>{children}</>,
13+
}));
14+
15+
vi.mock("qrcode.react", () => ({
16+
QRCodeCanvas: ({ value }: { value: string }) => (
17+
<canvas data-testid={`qr-code-${value}`} />
18+
),
19+
}));
20+
21+
describe("Issue #600: QRModal Download QR Code", () => {
22+
beforeEach(() => {
23+
vi.useFakeTimers();
24+
});
25+
26+
afterEach(() => {
27+
vi.useRealTimers();
28+
vi.clearAllMocks();
29+
});
30+
31+
it("should render QR modal when open is true", () => {
32+
const onClose = vi.fn();
33+
34+
render(
35+
<QRModal
36+
open={true}
37+
uri="wc:test-uri"
38+
onClose={onClose}
39+
/>
40+
);
41+
42+
expect(screen.getByText(/scan to connect/i)).toBeInTheDocument();
43+
});
44+
45+
it("should render QR code canvas", () => {
46+
const onClose = vi.fn();
47+
const testUri = "wc:test-uri-123";
48+
49+
render(
50+
<QRModal
51+
open={true}
52+
uri={testUri}
53+
onClose={onClose}
54+
/>
55+
);
56+
57+
expect(screen.getByTestId(`qr-code-${testUri}`)).toBeInTheDocument();
58+
});
59+
60+
it("should display the URI as plain text", () => {
61+
const testUri = "wc:1234567890abcdef";
62+
const onClose = vi.fn();
63+
64+
render(
65+
<QRModal
66+
open={true}
67+
uri={testUri}
68+
onClose={onClose}
69+
/>
70+
);
71+
72+
expect(screen.getByText(testUri)).toBeInTheDocument();
73+
});
74+
75+
it("should render Copy URI button", () => {
76+
const onClose = vi.fn();
77+
78+
render(
79+
<QRModal
80+
open={true}
81+
uri="wc:test-uri"
82+
onClose={onClose}
83+
/>
84+
);
85+
86+
expect(screen.getByRole("button", { name: /copy uri/i })).toBeInTheDocument();
87+
});
88+
89+
it("should allow copying URI to clipboard", async () => {
90+
const testUri = "wc:test-uri";
91+
const onClose = vi.fn();
92+
const onCopied = vi.fn();
93+
94+
const mockClipboard = {
95+
writeText: vi.fn().mockResolvedValue(undefined),
96+
};
97+
98+
Object.defineProperty(navigator, "clipboard", {
99+
value: mockClipboard,
100+
configurable: true,
101+
});
102+
103+
render(
104+
<QRModal
105+
open={true}
106+
uri={testUri}
107+
onClose={onClose}
108+
onCopied={onCopied}
109+
/>
110+
);
111+
112+
const copyButton = screen.getByRole("button", { name: /copy uri/i });
113+
fireEvent.click(copyButton);
114+
115+
await waitFor(() => {
116+
expect(mockClipboard.writeText).toHaveBeenCalledWith(testUri);
117+
});
118+
});
119+
120+
it("should call onCopied callback after copying URI", async () => {
121+
const testUri = "wc:test-uri";
122+
const onClose = vi.fn();
123+
const onCopied = vi.fn();
124+
125+
const mockClipboard = {
126+
writeText: vi.fn().mockResolvedValue(undefined),
127+
};
128+
129+
Object.defineProperty(navigator, "clipboard", {
130+
value: mockClipboard,
131+
configurable: true,
132+
});
133+
134+
render(
135+
<QRModal
136+
open={true}
137+
uri={testUri}
138+
onClose={onClose}
139+
onCopied={onCopied}
140+
/>
141+
);
142+
143+
const copyButton = screen.getByRole("button", { name: /copy uri/i });
144+
fireEvent.click(copyButton);
145+
146+
await waitFor(() => {
147+
expect(onCopied).toHaveBeenCalled();
148+
});
149+
});
150+
151+
it("should render Dismiss button", () => {
152+
const onClose = vi.fn();
153+
154+
render(
155+
<QRModal
156+
open={true}
157+
uri="wc:test-uri"
158+
onClose={onClose}
159+
/>
160+
);
161+
162+
const dismissButtons = screen.getAllByRole("button", { name: /dismiss/i });
163+
expect(dismissButtons.length).toBeGreaterThan(0);
164+
});
165+
166+
it("should close modal when Dismiss button is clicked", async () => {
167+
const onClose = vi.fn();
168+
169+
render(
170+
<QRModal
171+
open={true}
172+
uri="wc:test-uri"
173+
onClose={onClose}
174+
/>
175+
);
176+
177+
const dismissButton = screen.getAllByRole("button", { name: /dismiss/i })[0];
178+
fireEvent.click(dismissButton);
179+
180+
expect(onClose).toHaveBeenCalled();
181+
});
182+
183+
it("should close modal when close button in header is clicked", async () => {
184+
const onClose = vi.fn();
185+
186+
render(
187+
<QRModal
188+
open={true}
189+
uri="wc:test-uri"
190+
onClose={onClose}
191+
/>
192+
);
193+
194+
const closeButton = screen.getByLabelText(/close qr modal/i);
195+
fireEvent.click(closeButton);
196+
197+
expect(onClose).toHaveBeenCalled();
198+
});
199+
200+
it("should not render when open is false", () => {
201+
const onClose = vi.fn();
202+
203+
const { container } = render(
204+
<QRModal
205+
open={false}
206+
uri="wc:test-uri"
207+
onClose={onClose}
208+
/>
209+
);
210+
211+
expect(container.firstChild).toBeNull();
212+
});
213+
214+
it("should have proper ARIA attributes", () => {
215+
const onClose = vi.fn();
216+
217+
const { container } = render(
218+
<QRModal
219+
open={true}
220+
uri="wc:test-uri"
221+
onClose={onClose}
222+
/>
223+
);
224+
225+
const dialog = container.querySelector('[role="dialog"]');
226+
expect(dialog).toHaveAttribute("aria-modal", "true");
227+
});
228+
229+
it("should poll wallet connection status", async () => {
230+
const onConnected = vi.fn();
231+
const onClose = vi.fn();
232+
233+
const { isWalletConnected } = await import("@/lib/freighter");
234+
235+
render(
236+
<QRModal
237+
open={true}
238+
uri="wc:test-uri"
239+
onClose={onClose}
240+
onConnected={onConnected}
241+
pollingInterval={2000}
242+
/>
243+
);
244+
245+
vi.advanceTimersByTime(2000);
246+
247+
await waitFor(() => {
248+
expect(isWalletConnected).toHaveBeenCalled();
249+
});
250+
});
251+
252+
it("should call onConnected when wallet becomes connected", async () => {
253+
const onConnected = vi.fn();
254+
const onClose = vi.fn();
255+
256+
const { isWalletConnected } = await import("@/lib/freighter");
257+
vi.mocked(isWalletConnected).mockResolvedValueOnce(true);
258+
259+
render(
260+
<QRModal
261+
open={true}
262+
uri="wc:test-uri"
263+
onClose={onClose}
264+
onConnected={onConnected}
265+
pollingInterval={2000}
266+
/>
267+
);
268+
269+
vi.advanceTimersByTime(2000);
270+
271+
await waitFor(() => {
272+
expect(onConnected).toHaveBeenCalled();
273+
});
274+
});
275+
276+
it("should stop polling when modal is closed", async () => {
277+
const onConnected = vi.fn();
278+
const onClose = vi.fn();
279+
280+
const { isWalletConnected } = await import("@/lib/freighter");
281+
vi.mocked(isWalletConnected).mockResolvedValue(true);
282+
283+
const { rerender } = render(
284+
<QRModal
285+
open={true}
286+
uri="wc:test-uri"
287+
onClose={onClose}
288+
onConnected={onConnected}
289+
pollingInterval={2000}
290+
/>
291+
);
292+
293+
vi.advanceTimersByTime(2000);
294+
295+
rerender(
296+
<QRModal
297+
open={false}
298+
uri="wc:test-uri"
299+
onClose={onClose}
300+
onConnected={onConnected}
301+
pollingInterval={2000}
302+
/>
303+
);
304+
305+
const callCountAfterClose = vi.mocked(isWalletConnected).mock.calls.length;
306+
307+
vi.advanceTimersByTime(2000);
308+
309+
const callCountAfterWait = vi.mocked(isWalletConnected).mock.calls.length;
310+
311+
expect(callCountAfterWait).toBe(callCountAfterClose);
312+
});
313+
314+
it("should respect custom polling interval", async () => {
315+
const onConnected = vi.fn();
316+
const onClose = vi.fn();
317+
318+
const { isWalletConnected } = await import("@/lib/freighter");
319+
vi.mocked(isWalletConnected).mockResolvedValue(false);
320+
321+
render(
322+
<QRModal
323+
open={true}
324+
uri="wc:test-uri"
325+
onClose={onClose}
326+
onConnected={onConnected}
327+
pollingInterval={500}
328+
/>
329+
);
330+
331+
vi.advanceTimersByTime(500);
332+
333+
await waitFor(() => {
334+
expect(vi.mocked(isWalletConnected).mock.calls.length).toBeGreaterThan(0);
335+
});
336+
});
337+
338+
it("should display heading with scan instruction", () => {
339+
const onClose = vi.fn();
340+
341+
render(
342+
<QRModal
343+
open={true}
344+
uri="wc:test-uri"
345+
onClose={onClose}
346+
/>
347+
);
348+
349+
expect(screen.getByText(/scan to connect/i)).toBeInTheDocument();
350+
});
351+
352+
it("should have visual styling for draggable QR container", () => {
353+
const onClose = vi.fn();
354+
355+
const { container } = render(
356+
<QRModal
357+
open={true}
358+
uri="wc:test-uri"
359+
onClose={onClose}
360+
/>
361+
);
362+
363+
const qrContainer = container.querySelector(".bg-white.rounded-xl");
364+
expect(qrContainer).toBeInTheDocument();
365+
});
366+
});

0 commit comments

Comments
 (0)