1import { describe } from "@effect/vitest";
2
3import { createOxlintRuleHarness } from "../test/utils.ts";
4
5const browserTest = createOxlintRuleHarness("t3code/no-frame-waits-in-browser-tests", {
6 filename: "Widget.browser.test.tsx",
7});
8const unitTest = createOxlintRuleHarness("t3code/no-frame-waits-in-browser-tests", {
9 filename: "widget.test.tsx",
10});
11
12describe("t3code/no-frame-waits-in-browser-tests", () => {
13 browserTest.valid(
14 "allows retrying assertions and the full fake timer set",
15 [
16 'it("debounces", async () => {',
17 " await using screen = await render(null);",
18 " using _timers = fakeTimers();",
19 " vi.advanceTimersByTime(200);",
20 ' await expect.element(screen.getByRole("button")).toBeVisible();',
21 " await vi.waitFor(() => expect(onSave).toHaveBeenCalled());",
22 "});",
23 ].join("\n"),
24 );
25 browserTest.valid(
26 "allows requestAnimationFrame inside mocks",
27 [
28 'vi.spyOn(window, "requestAnimationFrame").mockImplementation((callback) => {',
29 " callback(0);",
30 " return 0;",
31 "});",
32 "const frame = vi.fn((callback) => requestAnimationFrame(callback));",
33 'vi.stubGlobal("requestAnimationFrame", (callback) => window.requestAnimationFrame(callback));',
34 'stubGlobal("requestAnimationFrame", (callback) => requestAnimationFrame(callback));',
35 ].join("\n"),
36 );
37 browserTest.valid(
38 "allows timers outside a promise executor and other listeners",
39 [
40 "const id = setTimeout(() => undefined, 10);",
41 'panel.addEventListener("click", () => undefined);',
42 "const deferred = new Promise((resolve) => (finish = resolve));",
43 ].join("\n"),
44 );
45
46 browserTest.invalid(
47 "rejects a nextFrame helper",
48 "const nextFrame = () => new Promise((resolve) => requestAnimationFrame(() => resolve()));",
49 );
50 browserTest.invalid(
51 "rejects requestAnimationFrame as the executor",
52 "await new Promise(requestAnimationFrame);",
53 );
54 browserTest.invalid(
55 "rejects window.requestAnimationFrame",
56 "window.requestAnimationFrame(() => undefined);",
57 );
58 browserTest.valid(
59 "allows a pass-through frame executor inside a mock implementation",
60 "const paint = vi.fn(() => new Promise(requestAnimationFrame));",
61 );
62 browserTest.invalid(
63 "rejects setTimeout passed straight through as the promise executor",
64 "await new Promise(setTimeout);",
65 );
66 browserTest.invalid(
67 "rejects setTimeout inside a promise executor",
68 "await new Promise((resolve) => setTimeout(resolve, 50));",
69 );
70 browserTest.invalid(
71 "rejects setInterval inside a promise executor",
72 "await new Promise(function (resolve) { window.setInterval(resolve, 50); });",
73 );
74 browserTest.invalid(
75 "rejects transitionend listeners",
76 'panel.addEventListener("transitionend", () => resolve(), { once: true });',
77 );
78 browserTest.invalid(
79 "rejects animationend listeners",
80 'panel.addEventListener("animationend", () => resolve());',
81 );
82 browserTest.invalid("rejects vi.useFakeTimers", "vi.useFakeTimers();");
83 browserTest.invalid(
84 "rejects a partial fakeTimers set",
85 'const timers = fakeTimers({ toFake: ["setTimeout", "clearTimeout"] });',
86 );
87 browserTest.invalid(
88 "rejects a partial vi.useFakeTimers set",
89 'vi.useFakeTimers({ toFake: ["setTimeout"] });',
90 );
91
92 unitTest.valid(
93 "ignores files that are not browser tests",
94 ["vi.useFakeTimers();", "await new Promise(requestAnimationFrame);"].join("\n"),
95 );
96});