1import { assert, describe } from "@effect/vitest";
2
3import { createOxlintRuleHarness } from "../test/utils.ts";
4
5const rule = createOxlintRuleHarness("t3code/no-test-in-loop", {
6 filename: "fixture.test.ts",
7});
8
9describe("t3code/no-test-in-loop", () => {
10 rule.valid(
11 "allows it.each and it.effect.each",
12 `
13 import { it } from "@effect/vitest";
14 import * as Effect from "effect/Effect";
15
16 it.each([1, 2])("handles %s", (value) => {});
17 it.effect.each([1, 2])("handles %s", (value) => Effect.succeed(value));
18 `,
19 );
20
21 rule.valid(
22 "allows loops inside a test body",
23 `
24 import { it } from "@effect/vitest";
25
26 it("checks every value", () => {
27 for (const value of [1, 2]) {
28 if (value < 0) throw new Error("negative");
29 }
30 });
31 `,
32 );
33
34 rule.valid(
35 "ignores node:test files, which have no .each",
36 `
37 import { test } from "node:test";
38
39 for (const value of [1, 2]) {
40 test(\`handles \${value}\`, () => {});
41 }
42 `,
43 );
44
45 rule.invalid(
46 "reports it inside a for...of loop",
47 `
48 import { it } from "@effect/vitest";
49
50 for (const value of [1, 2]) {
51 it(\`handles \${value}\`, () => {});
52 }
53 `,
54 (output) => {
55 assert.match(output, /Use it\.each\(cases\)/);
56 },
57 );
58
59 rule.invalid(
60 "reports it.effect inside a for loop nested in describe",
61 `
62 import { describe, it } from "@effect/vitest";
63 import * as Effect from "effect/Effect";
64
65 describe("cases", () => {
66 for (let index = 0; index < 2; index++) {
67 it.effect(\`handles \${index}\`, () => Effect.void);
68 }
69 });
70 `,
71 (output) => {
72 assert.match(output, /Use it\.effect\.each\(cases\)/);
73 },
74 );
75
76 rule.invalid(
77 "reports test inside a for...in loop",
78 `
79 import { test } from "vitest";
80
81 for (const key in { a: 1 }) {
82 test(key, () => {});
83 }
84 `,
85 );
86
87 rule.invalid(
88 "reports a describe inside a loop once, not the tests inside it",
89 `
90 import { describe, it } from "vitest";
91
92 for (const bundle of ["a.js", "b.js"]) {
93 describe(bundle, () => {
94 it("first", () => {});
95 it("second", () => {});
96 });
97 }
98 `,
99 (output) => {
100 assert.match(output, /Use describe\.each\(cases\)/);
101 assert.equal(output.match(/no-test-in-loop/g)?.length, 1);
102 },
103 );
104});
105
106const productionRule = createOxlintRuleHarness("t3code/no-test-in-loop");
107
108productionRule.valid(
109 "ignores non-test files",
110 `
111 const it = (_name: string) => {};
112 for (const value of ["a"]) it(value);
113 `,
114);