/** * Tests for: no-prop-drilling-depth * * Tests the detection of prop drilling through multiple components. */ import { RuleTester } from "@typescript-eslint/rule-tester"; import { describe, it, afterAll, beforeEach } from "vitest"; import rule, { clearPropCache } from "./no-prop-drilling-depth.js"; RuleTester.afterAll = afterAll; RuleTester.describe = describe; RuleTester.it = it; // Clear cache between tests beforeEach(() => { clearPropCache(); }); const ruleTester = new RuleTester({ languageOptions: { ecmaVersion: 2022, sourceType: "module", parserOptions: { ecmaFeatures: { jsx: true }, }, }, }); ruleTester.run("no-prop-drilling-depth", rule, { valid: [ // ============================================ // PROP USED DIRECTLY // ============================================ { name: "prop used directly in component", code: ` function UserProfile({ user }) { return
{user.name}
; } `, }, { name: "prop used in multiple places", code: ` function UserCard({ user }) { return (

{user.name}

{user.email}

); } `, }, // ============================================ // PROP PASSED ONCE (within threshold) // ============================================ { name: "prop passed to one child (within default threshold)", code: ` function Parent({ user }) { return ; } function Child({ user }) { return
{user.name}
; } `, }, { name: "prop passed and used", code: ` function Parent({ user }) { console.log(user.id); return ; } function Child({ user }) { return
{user.name}
; } `, }, // ============================================ // IGNORED PROPS // ============================================ { name: "className is ignored by default", code: ` function Wrapper({ className }) { return ; } function Container({ className }) { return ; } function Inner({ className }) { return ; } `, }, { name: "style is ignored by default", code: ` function A({ style }) { return ; } function B({ style }) { return ; } function C({ style }) { return ; } `, }, { name: "children is ignored by default", code: ` function Layout({ children }) { return ; } function Wrapper({ children }) { return ; } function Container({ children }) { return
{children}
; } `, }, // ============================================ // WITHIN CUSTOM THRESHOLD // ============================================ { name: "drilling within custom higher threshold", code: ` function A({ data }) { return ; } function B({ data }) { return ; } function C({ data }) { return ; } function D({ data }) { return
{data.value}
; } `, options: [{ maxDepth: 3 }], }, // ============================================ // IGNORED COMPONENT PATTERNS // ============================================ { name: "ignored component pattern - Layout", code: ` function MainLayout({ user }) { return ; } function InnerLayout({ user }) { return ; } function Content({ user }) { return ; } `, options: [{ ignoreComponents: ["^.*Layout$"] }], }, // ============================================ // NO PROPS // ============================================ { name: "component without props", code: ` function Header() { return
Header
; } `, }, // ============================================ // NON-COMPONENT FUNCTIONS // ============================================ { name: "non-component function (lowercase)", code: ` function helper({ data }) { return data.value; } `, }, // ============================================ // PROP USED SOMEWHERE IN CHAIN // ============================================ { name: "prop used in middle of chain", code: ` function A({ user }) { return ; } function B({ user }) { console.log(user.id); // Used here return ; } function C({ user }) { return
{user.name}
; } `, }, // ============================================ // CUSTOM IGNORED PROPS // ============================================ { name: "custom ignored prop", code: ` function A({ theme }) { return ; } function B({ theme }) { return ; } function C({ theme }) { return ; } `, options: [{ ignoredProps: ["theme"] }], }, // ============================================ // DIFFERENT PROPS TO DIFFERENT CHILDREN // ============================================ { name: "different props to different children", code: ` function Parent({ user, settings }) { return (
); } function UserView({ user }) { return
{user.name}
; } function SettingsView({ settings }) { return
{settings.theme}
; } `, }, ], invalid: [ // ============================================ // DRILLING EXCEEDS DEFAULT THRESHOLD (2) // ============================================ { name: "prop drilled through 3 components", code: ` function Grandparent({ user }) { return ; } function Parent({ user }) { return ; } function Child({ user }) { return ; } function Grandchild({ user }) { return
{user.name}
; } `, errors: [ { messageId: "propDrilling", data: { propName: "user", depth: "3", path: "Grandparent → Parent → Child → Grandchild", }, }, ], }, { name: "prop drilled through 4 components", code: ` function A({ data }) { return ; } function B({ data }) { return ; } function C({ data }) { return ; } function D({ data }) { return ; } function E({ data }) { return
{data.value}
; } `, errors: [ { messageId: "propDrilling", data: { propName: "data", depth: "3", path: "A → B → C → D", }, }, { messageId: "propDrilling", data: { propName: "data", depth: "3", path: "B → C → D → E", }, }, ], }, // ============================================ // MULTIPLE DRILLED PROPS // ============================================ { name: "multiple props drilled", code: ` function Top({ user, settings }) { return ; } function Middle({ user, settings }) { return ; } function Bottom({ user, settings }) { return ; } function Final({ user, settings }) { return
{user.name} - {settings.theme}
; } `, errors: [ { messageId: "propDrilling", data: { propName: "user", depth: "3", path: "Top → Middle → Bottom → Final", }, }, { messageId: "propDrilling", data: { propName: "settings", depth: "3", path: "Top → Middle → Bottom → Final", }, }, ], }, // ============================================ // CUSTOM LOWER THRESHOLD // ============================================ { name: "drilling exceeds custom threshold of 1", code: ` function Parent({ config }) { return ; } function Child({ config }) { return ; } function Grandchild({ config }) { return
{config.value}
; } `, options: [{ maxDepth: 1 }], errors: [ { messageId: "propDrilling", data: { propName: "config", depth: "2", path: "Parent → Child → Grandchild", }, }, ], }, // ============================================ // ARROW FUNCTION COMPONENTS // ============================================ { name: "arrow function components drilling", code: ` const A = ({ item }) => ; const B = ({ item }) => ; const C = ({ item }) => ; const D = ({ item }) =>
{item.value}
; `, errors: [ { messageId: "propDrilling", data: { propName: "item", depth: "3", path: "A → B → C → D", }, }, ], }, // ============================================ // MIXED FUNCTION STYLES // ============================================ { name: "mixed function declaration and arrow", code: ` function Container({ data }) { return ; } const Wrapper = ({ data }) => ; function Inner({ data }) { return ; } const Deep = ({ data }) =>
{data.value}
; `, errors: [ { messageId: "propDrilling", data: { propName: "data", depth: "3", path: "Container → Wrapper → Inner → Deep", }, }, ], }, // ============================================ // PROP NOT IN IGNORED LIST // ============================================ { name: "custom prop not in default ignored list", code: ` function A({ theme }) { return ; } function B({ theme }) { return ; } function C({ theme }) { return ; } function D({ theme }) { return
{theme}
; } `, errors: [ { messageId: "propDrilling", data: { propName: "theme", depth: "3", path: "A → B → C → D", }, }, ], }, // ============================================ // DRILLING IN NESTED JSX // ============================================ { name: "drilling through nested JSX structure", code: ` function Page({ user }) { return (
); } function Main({ user }) { return (
); } function Content({ user }) { return ; } function Profile({ user }) { return
{user.name}
; } `, errors: [ { messageId: "propDrilling", data: { propName: "user", depth: "3", path: "Page → Main → Content → Profile", }, }, ], }, ], });