Skip to content

Commit 035e678

Browse files
committed
chore: make sure tests can differentiate between HTTP/HTTPS
1 parent 118151a commit 035e678

File tree

1 file changed

+80
-42
lines changed

1 file changed

+80
-42
lines changed

site/src/hooks/useClipboard.test.ts

Lines changed: 80 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,86 @@
11
import { type UseClipboardResult, useClipboard } from "./useClipboard";
22
import { act, renderHook } from "@testing-library/react";
3-
import userEvent from "@testing-library/user-event";
3+
4+
/*
5+
Normally, you could call userEvent.setup to enable clipboard mocking, but
6+
userEvent doesn't expose a teardown function. It also modifies the global
7+
clipboard, so enabling just one userEvent session will make a mock clipboard
8+
exist for all other tests, even though you didn't tell them to set up a
9+
session. The mock also assumes that the clipboard API will always be
10+
available, which is not true on HTTP-only connections
11+
12+
Since these tests need to split hairs and differentiate between HTTP and HTTPS
13+
connections, setting up a single userEvent is disastrous. It will make all the
14+
tests pass, even if they shouldn't. Have to avoid that by creating a custom
15+
clipboard mock.
16+
*/
17+
type MockClipboard = Readonly<
18+
Clipboard & {
19+
resetText: () => void;
20+
setIsSecureContext: (newContext: boolean) => void;
21+
}
22+
>;
23+
24+
function makeMockClipboard(): MockClipboard {
25+
let mockClipboardValue = "";
26+
let isSecureContext = true;
27+
28+
return {
29+
readText: async () => {
30+
if (!isSecureContext) {
31+
throw new Error(
32+
"Trying to read from clipboard outside secure context!",
33+
);
34+
}
35+
36+
return mockClipboardValue;
37+
},
38+
writeText: async (newText) => {
39+
if (!isSecureContext) {
40+
throw new Error("Trying to write to clipboard outside secure context!");
41+
}
42+
43+
mockClipboardValue = newText;
44+
},
45+
resetText: () => {
46+
mockClipboardValue = "";
47+
},
48+
setIsSecureContext: (newContext) => {
49+
isSecureContext = newContext;
50+
},
51+
52+
addEventListener: jest.fn(),
53+
removeEventListener: jest.fn(),
54+
dispatchEvent: jest.fn(),
55+
read: jest.fn(),
56+
write: jest.fn(),
57+
};
58+
}
59+
60+
const mockClipboard = makeMockClipboard();
461

562
beforeAll(() => {
63+
const originalNavigator = window.navigator;
64+
jest.spyOn(window, "navigator", "get").mockImplementation(() => ({
65+
...originalNavigator,
66+
clipboard: mockClipboard,
67+
}));
68+
69+
jest.spyOn(document, "hasFocus").mockImplementation(() => true);
670
jest.useFakeTimers();
7-
userEvent.setup({
8-
writeToClipboard: true,
9-
});
71+
});
72+
73+
afterEach(() => {
74+
mockClipboard.resetText();
1075
});
1176

1277
afterAll(() => {
13-
jest.useRealTimers();
1478
jest.restoreAllMocks();
79+
jest.useRealTimers();
1580
});
1681

1782
function renderUseClipboard(textToCopy: string) {
1883
type Props = Readonly<{ textToCopy: string }>;
19-
2084
return renderHook<UseClipboardResult, Props>(
2185
({ textToCopy }) => useClipboard(textToCopy),
2286
{ initialProps: { textToCopy } },
@@ -25,33 +89,20 @@ function renderUseClipboard(textToCopy: string) {
2589

2690
type UseClipboardTestResult = ReturnType<typeof renderUseClipboard>["result"];
2791

28-
// This can and should be cleaned up - trying to call the clipboard's readText
29-
// method caused an error around blob input, even though the method takes no
30-
// arguments whatsoever, so here's this workaround using the lower-level API
3192
async function assertClipboardTextUpdate(
3293
result: UseClipboardTestResult,
3394
textToCheck: string,
3495
): Promise<void> {
3596
await act(() => result.current.copyToClipboard());
3697
expect(result.current.showCopiedSuccess).toBe(true);
3798

38-
const clipboardTextType = "text/plain";
39-
const clipboardItems = await window.navigator.clipboard.read();
40-
const firstItem = clipboardItems[0];
41-
42-
const hasData =
43-
firstItem !== undefined && firstItem.types.includes(clipboardTextType);
44-
45-
if (!hasData) {
46-
throw new Error("No clipboard items to process");
47-
}
48-
49-
const blob = await firstItem.getType(clipboardTextType);
50-
const clipboardText = await blob.text();
99+
const clipboardText = await window.navigator.clipboard.readText();
51100
expect(textToCheck).toEqual(clipboardText);
52101
}
53102

54-
describe(useClipboard.name, () => {
103+
function scheduleTests(isHttps: boolean) {
104+
mockClipboard.setIsSecureContext(isHttps);
105+
55106
it("Copies the current text to the user's clipboard", async () => {
56107
const hookText = "dogs";
57108
const { result } = renderUseClipboard(hookText);
@@ -69,27 +120,14 @@ describe(useClipboard.name, () => {
69120

70121
await jest.runAllTimersAsync();
71122
});
123+
}
72124

73-
it.skip("Should notify the user that a copy was not successful", () => {
74-
expect.hasAssertions();
125+
describe(useClipboard.name, () => {
126+
describe("HTTP (non-secure) connections", () => {
127+
scheduleTests(false);
75128
});
76129

77-
it.skip("Should work on non-secure (HTTP-only) connections", async () => {
78-
const prevClipboard = window.navigator.clipboard;
79-
80-
Object.assign(window.navigator, {
81-
clipboard: {
82-
...prevClipboard,
83-
writeText: async () => {
84-
throw new Error(
85-
"Trying to call clipboard API in non-secure context!",
86-
);
87-
},
88-
},
89-
});
90-
91-
const hookText = "birds";
92-
const { result } = renderUseClipboard(hookText);
93-
await assertClipboardTextUpdate(result, hookText);
130+
describe("HTTPS connections", () => {
131+
scheduleTests(true);
94132
});
95133
});

0 commit comments

Comments
 (0)