/**
* Tests for: no-mixed-component-libraries
*
* Tests the detection of non-preferred UI component libraries at JSX usage sites.
*/
import { RuleTester } from "@typescript-eslint/rule-tester";
import { describe, it, afterAll, beforeEach } from "vitest";
import rule from "./no-mixed-component-libraries/index.js";
import { clearCache as clearImportGraphCache } from "./no-mixed-component-libraries/lib/import-graph.js";
RuleTester.afterAll = afterAll;
RuleTester.describe = describe;
RuleTester.it = it;
const ruleTester = new RuleTester({
languageOptions: {
ecmaVersion: 2022,
sourceType: "module",
parserOptions: {
ecmaFeatures: { jsx: true },
},
},
});
// Clear cache between tests
beforeEach(() => {
clearImportGraphCache();
});
ruleTester.run("no-mixed-component-libraries", rule, {
valid: [
// ============================================
// USING PREFERRED LIBRARY (shadcn)
// ============================================
{
name: "using shadcn Button (preferred)",
code: `
import { Button } from "@/components/ui/button";
export default function Page() {
return ;
}
`,
options: [{ preferred: "shadcn" }],
},
{
name: "using multiple shadcn components",
code: `
import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
export default function Page() {
return (
);
}
`,
options: [{ preferred: "shadcn" }],
},
{
name: "using radix-ui (part of shadcn)",
code: `
import { Dialog } from "@radix-ui/react-dialog";
export default function Page() {
return ;
}
`,
options: [{ preferred: "shadcn" }],
},
// ============================================
// USING PREFERRED LIBRARY (mui)
// ============================================
{
name: "using MUI Button when MUI is preferred",
code: `
import { Button } from "@mui/material";
export default function Page() {
return ;
}
`,
options: [{ preferred: "mui" }],
},
{
name: "using MUI icons when MUI is preferred",
code: `
import { Add } from "@mui/icons-material";
export default function Page() {
return ;
}
`,
options: [{ preferred: "mui" }],
},
// ============================================
// NO COMPONENT LIBRARY (just HTML/custom)
// ============================================
{
name: "using HTML elements only",
code: `
export default function Page() {
return (
Text
);
}
`,
options: [{ preferred: "shadcn" }],
},
{
name: "using local components without library imports",
code: `
import { MyButton } from "./my-button";
export default function Page() {
return Click;
}
`,
options: [{ preferred: "shadcn" }],
},
// ============================================
// COMPONENTS NOT IN IMPORT MAP
// ============================================
{
name: "component defined in same file",
code: `
function LocalButton({ children }) {
return ;
}
export default function Page() {
return Click;
}
`,
options: [{ preferred: "shadcn" }],
},
],
invalid: [
// ============================================
// DIRECT USAGE OF NON-PREFERRED LIBRARY
// ============================================
{
name: "using MUI when shadcn is preferred",
code: `
import { Button } from "@mui/material";
export default function Page() {
return ;
}
`,
options: [{ preferred: "shadcn" }],
errors: [
{
messageId: "nonPreferredLibrary",
data: {
component: "Button",
library: "mui",
preferred: "shadcn",
},
},
],
},
{
name: "using Chakra when shadcn is preferred",
code: `
import { Box } from "@chakra-ui/react";
export default function Page() {
return Content;
}
`,
options: [{ preferred: "shadcn" }],
errors: [
{
messageId: "nonPreferredLibrary",
data: {
component: "Box",
library: "chakra",
preferred: "shadcn",
},
},
],
},
{
name: "using Ant Design when MUI is preferred",
code: `
import { Button } from "antd";
export default function Page() {
return ;
}
`,
options: [{ preferred: "mui" }],
errors: [
{
messageId: "nonPreferredLibrary",
data: {
component: "Button",
library: "antd",
preferred: "mui",
},
},
],
},
{
name: "using shadcn when MUI is preferred",
code: `
import { Button } from "@/components/ui/button";
export default function Page() {
return ;
}
`,
options: [{ preferred: "mui" }],
errors: [
{
messageId: "nonPreferredLibrary",
data: {
component: "Button",
library: "shadcn",
preferred: "mui",
},
},
],
},
// ============================================
// MULTIPLE VIOLATIONS IN SAME FILE
// ============================================
{
name: "multiple MUI components when shadcn is preferred",
code: `
import { Button, Card, Typography } from "@mui/material";
export default function Page() {
return (
Title
);
}
`,
options: [{ preferred: "shadcn" }],
errors: [
{
messageId: "nonPreferredLibrary",
data: { component: "Card", library: "mui", preferred: "shadcn" },
},
{
messageId: "nonPreferredLibrary",
data: {
component: "Typography",
library: "mui",
preferred: "shadcn",
},
},
{
messageId: "nonPreferredLibrary",
data: { component: "Button", library: "mui", preferred: "shadcn" },
},
],
},
// ============================================
// MIXING PREFERRED WITH NON-PREFERRED
// ============================================
{
name: "mixing shadcn and MUI in same file",
code: `
import { Button } from "@/components/ui/button";
import { Card } from "@mui/material";
export default function Page() {
return (
);
}
`,
options: [{ preferred: "shadcn" }],
errors: [
{
messageId: "nonPreferredLibrary",
data: { component: "Card", library: "mui", preferred: "shadcn" },
},
],
},
// ============================================
// JSX MEMBER EXPRESSIONS (e.g., Modal.Header)
// ============================================
{
name: "MUI namespace usage (Modal.Header pattern)",
code: `
import * as MUI from "@mui/material";
export default function Page() {
return Click;
}
`,
options: [{ preferred: "shadcn" }],
errors: [
{
messageId: "nonPreferredLibrary",
data: { component: "MUI", library: "mui", preferred: "shadcn" },
},
],
},
// ============================================
// ALIASED IMPORTS
// ============================================
{
name: "aliased MUI import",
code: `
import { Button as MuiButton } from "@mui/material";
export default function Page() {
return Click;
}
`,
options: [{ preferred: "shadcn" }],
errors: [
{
messageId: "nonPreferredLibrary",
data: { component: "MuiButton", library: "mui", preferred: "shadcn" },
},
],
},
// ============================================
// ICONS FROM NON-PREFERRED LIBRARY
// ============================================
{
name: "using MUI icons when shadcn is preferred",
code: `
import { Add, Delete } from "@mui/icons-material";
export default function Page() {
return (
);
}
`,
options: [{ preferred: "shadcn" }],
errors: [
{
messageId: "nonPreferredLibrary",
data: { component: "Add", library: "mui", preferred: "shadcn" },
},
{
messageId: "nonPreferredLibrary",
data: { component: "Delete", library: "mui", preferred: "shadcn" },
},
],
},
// ============================================
// ANT DESIGN ICONS
// ============================================
{
name: "using Ant Design icons when shadcn is preferred",
code: `
import { PlusOutlined } from "@ant-design/icons";
export default function Page() {
return ;
}
`,
options: [{ preferred: "shadcn" }],
errors: [
{
messageId: "nonPreferredLibrary",
data: {
component: "PlusOutlined",
library: "antd",
preferred: "shadcn",
},
},
],
},
],
});