Skip to content
Source
Sign inSign up

require-disposable-render.test.ts

157 lines5.4 KB

1import { assert, describe } from "@effect/vitest";
2​
3import { createOxlintRuleHarness } from "../test/utils.ts";
4​
5const browserTest = createOxlintRuleHarness("t3code/require-disposable-render", {
6 filename: "Widget.browser.test.tsx",
7});
8const unitTest = createOxlintRuleHarness("t3code/require-disposable-render", {
9 filename: "widget.test.tsx",
10});
11​
12const reportsInputInAct = (output: string) =>
13 assert.match(output, /Do not wrap real input \(userEvent or a locator's click\/fill\) in act/);
14​
15describe("t3code/require-disposable-render", () => {
16 browserTest.valid(
17 "allows the disposable helper",
18 [
19 'import { act, render } from "../test/render";',
20 'import { useState } from "react";',
21 'it("renders", async () => {',
22 " await using screen = await render(null);",
23 " await act(() => screen.rerender(null));",
24 ' await userEvent.click(screen.getByRole("button"));',
25 " await act(() => store.setState({ open: true }));",
26 "});",
27 ].join("\n"),
28 );
29 browserTest.invalid("rejects react-dom/client", 'import { createRoot } from "react-dom/client";');
30 browserTest.invalid("rejects act from react", 'import { act } from "react";');
31 browserTest.invalid(
32 "rejects a renamed act from react",
33 'import { act as reactAct } from "react";',
34 );
35 browserTest.invalid(
36 "rejects setting the act environment",
37 "globalThis.IS_REACT_ACT_ENVIRONMENT = true;",
38 );
39 browserTest.invalid(
40 "rejects setting the act environment by computed key",
41 'window["IS_REACT_ACT_ENVIRONMENT"] = true;',
42 );
43 browserTest.invalid(
44 "rejects stubbing the act environment",
45 'vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);',
46 );
47 browserTest.invalid(
48 "rejects userEvent wrapped in act from react",
49 [
50 'import { act } from "react";',
51 'import { userEvent } from "vite-plus/test/browser";',
52 'it("clicks", async () => {',
53 " await act(async () => {",
54 " await userEvent.click(document.body);",
55 " });",
56 "});",
57 ].join("\n"),
58 reportsInputInAct,
59 );
60 browserTest.invalid(
61 "rejects userEvent wrapped in the render helper's act",
62 [
63 'import { act } from "../../test/render";',
64 'import { userEvent } from "vite-plus/test/browser";',
65 'it("types", async () => {',
66 ' await act(() => userEvent.type(document.body, "a"));',
67 "});",
68 ].join("\n"),
69 reportsInputInAct,
70 );
71 browserTest.invalid(
72 "rejects userEvent wrapped in a renamed render helper act",
73 [
74 'import { act as flush } from "../test/render.tsx";',
75 'import { userEvent } from "vite-plus/test/browser";',
76 'it("clicks", async () => {',
77 " await flush(async () => {",
78 " await userEvent.click(document.body);",
79 " });",
80 "});",
81 ].join("\n"),
82 reportsInputInAct,
83 );
84 browserTest.invalid(
85 "rejects a renamed userEvent wrapped in act",
86 [
87 'import { act } from "../test/render";',
88 'import { userEvent as ue } from "vite-plus/test/browser";',
89 'it("clicks", async () => {',
90 " await act(() => ue.click(document.body));",
91 "});",
92 ].join("\n"),
93 reportsInputInAct,
94 );
95 browserTest.invalid(
96 "rejects a locator click wrapped in act",
97 [
98 'import { act } from "../test/render";',
99 'import { page } from "vite-plus/test/browser";',
100 'it("clicks", async () => {',
101 ' await act(async () => page.getByRole("button", { name: "Save" }).nth(0).click());',
102 "});",
103 ].join("\n"),
104 reportsInputInAct,
105 );
106 browserTest.invalid(
107 "rejects React.act from a namespace import",
108 [
109 'import * as React from "react";',
110 'it("renders", async () => {',
111 " await React.act(async () => {});",
112 "});",
113 ].join("\n"),
114 (output) => assert.match(output, /import `act` from there/),
115 );
116 browserTest.invalid(
117 "rejects userEvent wrapped in React.act",
118 [
119 'import * as React from "react";',
120 'import { userEvent } from "vite-plus/test/browser";',
121 'it("clicks", async () => {',
122 " await React.act(() => userEvent.click(document.body));",
123 "});",
124 ].join("\n"),
125 reportsInputInAct,
126 );
127 browserTest.valid(
128 "allows act around a DOM click and userEvent in a function act only defines",
129 [
130 'import { act } from "../test/render";',
131 'import { page, userEvent } from "vite-plus/test/browser";',
132 'it("clicks", async () => {',
133 ' await act(async () => page.getByRole("button").element().click());',
134 " await act(async () => {",
135 " const later = () => userEvent.click(document.body);",
136 " store.setState({ later });",
137 " });",
138 "});",
139 ].join("\n"),
140 );
141 browserTest.invalid(
142 "rejects lifecycle hooks through a test runner namespace",
143 ['import * as vt from "vitest";', "vt.beforeEach(() => {});"].join("\n"),
144 );
145 browserTest.invalid(
146 "rejects importing lifecycle hooks from the test runner",
147 'import { beforeEach, it } from "vite-plus/test";',
148 );
149 browserTest.invalid("rejects afterEach", "afterEach(async () => {});");
150 browserTest.invalid("rejects beforeAll", "beforeAll(() => {});");
151 unitTest.valid("allows lifecycle hooks outside browser tests", "beforeEach(() => {});");
152 unitTest.valid(
153 "allows importing lifecycle hooks outside browser tests",
154 'import { afterEach } from "vite-plus/test";',
155 );
156 unitTest.invalid("still rejects react-dom/client in unit tests", 'import "react-dom/client";');
157});