Skip to content

Commit abbec2d

Browse files
committed
chore: finish clipboard tests
1 parent 6c697e3 commit abbec2d

File tree

3 files changed

+76
-24
lines changed

3 files changed

+76
-24
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/**
2+
* This test is for all useClipboard functionality, with the browser context
3+
* set to insecure (HTTP connections).
4+
*
5+
* See useClipboard.test-setup.ts for more info on why this file is set up the
6+
* way that it is.
7+
*/
8+
import { useClipboard } from "./useClipboard";
9+
import { scheduleClipboardTests } from "./useClipboard.test-setup";
10+
11+
describe(useClipboard.name, () => {
12+
describe("HTTP (non-secure) connections", () => {
13+
scheduleClipboardTests({ isHttps: false });
14+
});
15+
});
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/**
2+
* This test is for all useClipboard functionality, with the browser context
3+
* set to secure (HTTPS connections).
4+
*
5+
* See useClipboard.test-setup.ts for more info on why this file is set up the
6+
* way that it is.
7+
*/
8+
import { useClipboard } from "./useClipboard";
9+
import { scheduleClipboardTests } from "./useClipboard.test-setup";
10+
11+
describe(useClipboard.name, () => {
12+
describe("HTTPS (secure/default) connections", () => {
13+
scheduleClipboardTests({ isHttps: true });
14+
});
15+
});

site/src/hooks/useClipboard.test.ts renamed to site/src/hooks/useClipboard.test-setup.ts

Lines changed: 46 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,46 @@
1-
/*
2-
Normally, you could call userEvent.setup to enable clipboard mocking, but
3-
userEvent doesn't expose a teardown function. It also modifies the global
4-
scope for the whole test file, so enabling just one userEvent session will
5-
make a mock clipboard exist for all other tests, even though you didn't tell
6-
them to set up a session. The mock also assumes that the clipboard API will
7-
always be available, which is not true on HTTP-only connections
8-
9-
Since these tests need to split hairs and differentiate between HTTP and HTTPS
10-
connections, setting up a single userEvent is disastrous. It will make all the
11-
tests pass, even if they shouldn't. Have to avoid that by creating a custom
12-
clipboard mock.
13-
*/
1+
/**
2+
* @file This is a very weird test setup.
3+
*
4+
* There are two main things that it's fighting against to insure that the
5+
* clipboard functionality is working as expected:
6+
* 1. userEvent.setup's default global behavior
7+
* 2. The fact that we need to reuse the same set of test cases for two separate
8+
* contexts (secure and insecure), each with their own version of global
9+
* state.
10+
*
11+
* The goal of this file is to provide a shared set of test behavior that can
12+
* be imported into two separate test files (one for HTTP, one for HTTPS),
13+
* without any risk of global state conflicts.
14+
*
15+
* ---
16+
* For (1), normally you could call userEvent.setup to enable clipboard mocking,
17+
* but userEvent doesn't expose a teardown function. It also modifies the global
18+
* scope for the whole test file, so enabling just one userEvent session will
19+
* make a mock clipboard exist for all other tests, even though you didn't tell
20+
* them to set up a session. The mock also assumes that the clipboard API will
21+
* always be available, which is not true on HTTP-only connections
22+
*
23+
* Since these tests need to split hairs and differentiate between HTTP and
24+
* HTTPS connections, setting up a single userEvent is disastrous. It will make
25+
* all the tests pass, even if they shouldn't. Have to avoid that by creating a
26+
* custom clipboard mock.
27+
*
28+
* ---
29+
* For (2), we're fighting against Jest's default behavior, which is to treat
30+
* the test file as the main boundary for test environments, with each test case
31+
* able to run in parallel. That works if you have one single global state, but
32+
* we need two separate versions of the global state, while repeating the exact
33+
* same test cases for each one.
34+
*
35+
* If both tests were to be placed in the same file, Jest would not isolate them
36+
* and would let their setup steps interfere with each other. This leads to one
37+
* of two things:
38+
* 1. One of the global mocks overrides the other, making it so that one
39+
* connection type always fails
40+
* 2. The two just happen not to conflict each other, through some convoluted
41+
* order of operations involving closure, but you have no idea why the code
42+
* is working, and it's impossible to debug.
43+
*/
1444
import { type UseClipboardResult, useClipboard } from "./useClipboard";
1545
import { act, renderHook } from "@testing-library/react";
1646

@@ -72,6 +102,8 @@ function renderUseClipboard(textToCopy: string) {
72102
);
73103
}
74104

105+
type ScheduleConfig = Readonly<{ isHttps: boolean }>;
106+
75107
/**
76108
* Unconventional test setup, but we need two separate instances of the
77109
* MockClipboard (one for HTTP and one for HTTPS).
@@ -80,7 +112,7 @@ function renderUseClipboard(textToCopy: string) {
80112
* else you get shared mutable state, and test cases interfering with each
81113
* other. Test isolation is especially important for this test file
82114
*/
83-
function scheduleTests(isHttps: boolean) {
115+
export function scheduleClipboardTests({ isHttps }: ScheduleConfig) {
84116
const mockClipboardInstance = makeMockClipboard(isHttps);
85117

86118
beforeAll(() => {
@@ -148,13 +180,3 @@ function scheduleTests(isHttps: boolean) {
148180
await jest.runAllTimersAsync();
149181
});
150182
}
151-
152-
describe(useClipboard.name, () => {
153-
describe("HTTP (non-secure) connections", () => {
154-
scheduleTests(false);
155-
});
156-
157-
describe("HTTPS connections", () => {
158-
scheduleTests(true);
159-
});
160-
});

0 commit comments

Comments
 (0)