/** * Tests for: no-raw-ui-elements * * Ensures raw UI controls are reported with framework-specific replacements. */ import { RuleTester } from "@typescript-eslint/rule-tester"; import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "fs"; import { tmpdir } from "os"; import { join } from "path"; import { describe, it, afterAll } from "vitest"; import rule from "./no-raw-ui-elements.js"; RuleTester.afterAll = afterAll; RuleTester.describe = describe; RuleTester.it = it; const ruleTester = new RuleTester({ languageOptions: { ecmaVersion: 2022, sourceType: "module", parserOptions: { ecmaFeatures: { jsx: true }, }, }, }); const tempRoot = mkdtempSync(join(tmpdir(), "uilint-no-raw-ui-elements-")); const shadcnProject = join(tempRoot, "shadcn-project"); const muiProject = join(tempRoot, "mui-project"); const plainProject = join(tempRoot, "plain-project"); const tsconfigAliasProject = join(tempRoot, "tsconfig-alias-project"); const relativeAliasProject = join(tempRoot, "relative-alias-project"); const invalidConfigProject = join(tempRoot, "invalid-config-project"); const missingAliasProject = join(tempRoot, "missing-alias-project"); mkdirSync(join(shadcnProject, "components", "ui"), { recursive: true }); mkdirSync(join(shadcnProject, "app"), { recursive: true }); writeFileSync( join(shadcnProject, "package.json"), JSON.stringify({ dependencies: { react: "^19.0.0" } }) ); writeFileSync(join(shadcnProject, "components", "ui", "button.tsx"), ""); mkdirSync(join(muiProject, "src"), { recursive: true }); writeFileSync( join(muiProject, "package.json"), JSON.stringify({ dependencies: { "@mui/material": "^7.0.0" } }) ); mkdirSync(join(plainProject, "src"), { recursive: true }); writeFileSync( join(plainProject, "package.json"), JSON.stringify({ dependencies: { react: "^19.0.0" } }) ); mkdirSync(join(tsconfigAliasProject, "src", "components", "ui"), { recursive: true, }); mkdirSync(join(tsconfigAliasProject, "src", "pages"), { recursive: true }); writeFileSync( join(tsconfigAliasProject, "package.json"), JSON.stringify({ dependencies: { react: "^19.0.0" } }) ); writeFileSync( join(tsconfigAliasProject, "components.json"), JSON.stringify({ aliases: { ui: "@/components/ui" } }) ); writeFileSync( join(tsconfigAliasProject, "tsconfig.json"), JSON.stringify({ compilerOptions: { baseUrl: ".", paths: { "@/*": ["./src/*"] }, }, }) ); writeFileSync( join(tsconfigAliasProject, "src", "components", "ui", "button.tsx"), "" ); mkdirSync(join(relativeAliasProject, "src", "design-system", "ui"), { recursive: true, }); mkdirSync(join(relativeAliasProject, "src"), { recursive: true }); writeFileSync( join(relativeAliasProject, "package.json"), JSON.stringify({ dependencies: { react: "^19.0.0" } }) ); writeFileSync( join(relativeAliasProject, "components.json"), JSON.stringify({ aliases: { ui: "src/design-system/ui" } }) ); writeFileSync( join(relativeAliasProject, "src", "design-system", "ui", "textarea.tsx"), "" ); mkdirSync(join(invalidConfigProject, "components", "ui"), { recursive: true, }); mkdirSync(join(invalidConfigProject, "src"), { recursive: true }); writeFileSync( join(invalidConfigProject, "package.json"), JSON.stringify({ dependencies: { react: "^19.0.0" } }) ); writeFileSync(join(invalidConfigProject, "components.json"), "{ invalid json"); writeFileSync(join(invalidConfigProject, "components", "ui", "input.tsx"), ""); mkdirSync(join(missingAliasProject, "src"), { recursive: true }); writeFileSync( join(missingAliasProject, "package.json"), JSON.stringify({ dependencies: { react: "^19.0.0" } }) ); writeFileSync( join(missingAliasProject, "components.json"), JSON.stringify({ aliases: { ui: "@/missing/ui" } }) ); writeFileSync( join(missingAliasProject, "tsconfig.json"), JSON.stringify({ compilerOptions: { baseUrl: ".", paths: { "@/*": ["./src/*"] }, }, }) ); afterAll(() => { rmSync(tempRoot, { recursive: true, force: true }); }); ruleTester.run("no-raw-ui-elements", rule, { valid: [ { name: "auto mode does not report when no component framework is detected", filename: join(plainProject, "src", "Form.tsx"), code: ` export function Form() { return ; } `, }, { name: "allows configured framework components", code: ` import { Button } from "@/components/ui/button"; export function Form() { return ; } `, options: [{ preferred: "shadcn" }], }, { name: "allows raw button inside shadcn button primitive implementation", filename: join(shadcnProject, "components", "ui", "button.tsx"), code: ` export function Button() { return