import { describe, it, expect } from "vitest"; import { applyTransform } from "@hypermod/utils"; import * as transformer from "../function-declaration"; describe("function-declaration", () => { it("should convert arrow function components to function declarations", async () => { const result = await applyTransform( transformer, ` import React from "react"; export const MyComponent: React.FC<{}> = () => { return
Hello, world!
; }; `, { parser: "tsx" }, ); expect(result).toMatchInlineSnapshot(` "import React from "react"; export function MyComponent() { return
Hello, world!
; }" `); }); it("should handle components with props", async () => { const result = await applyTransform( transformer, ` import React from "react"; interface IMyComponentProps { name: string; } export const MyComponent: React.FC = ({ name }) => { return
Hello, {name}!
; }; `, { parser: "tsx" }, ); expect(result).toMatchInlineSnapshot(` "import React from "react"; interface IMyComponentProps { name: string; } export function MyComponent( { name }: IMyComponentProps ) { return
Hello, {name}!
; }" `); }); it("should preserve default props", async () => { const result = await applyTransform( transformer, ` import React from "react"; interface IMyComponentProps { name?: string; } export const MyComponent: React.FC = ({ name = "world" }) => { return
Hello, {name}!
; }; `, { parser: "tsx" }, ); expect(result).toMatchInlineSnapshot(` "import React from "react"; interface IMyComponentProps { name?: string; } export function MyComponent( { name = "world" }: IMyComponentProps ) { return
Hello, {name}!
; }" `); }); it("should not transform non-component arrow functions", async () => { const result = await applyTransform( transformer, ` const myFunction = () => { return "hello"; }; `, { parser: "tsx" }, ); expect(result).toMatchInlineSnapshot(` "const myFunction = () => { return "hello"; };" `); }); it("should handle observer-wrapped components", async () => { const result = await applyTransform( transformer, ` import { observer } from "mobx-react"; export const WorkspaceAnalyticsHeader = observer(() => { return
Analytics
; }); `, { parser: "tsx" }, ); expect(result).toMatchInlineSnapshot(` "import { observer } from "mobx-react"; export const WorkspaceAnalyticsHeader = observer(function WorkspaceAnalyticsHeader() { return
Analytics
; });" `); }); it("should handle inline arrow function components", async () => { const result = await applyTransform( transformer, ` export const StarUsOnGitHubLink = () => { return Star us; }; `, { parser: "tsx" }, ); expect(result).toMatchInlineSnapshot(` "export function StarUsOnGitHubLink() { return Star us; }" `); }); it("should handle React.FC type without generics", async () => { const result = await applyTransform( transformer, ` import type { FC } from "react"; export const ProjectAppSidebar: FC = observer(() => { return
Sidebar
; }); `, { parser: "tsx" }, ); expect(result).toMatchInlineSnapshot(` "import type { FC } from "react"; export const ProjectAppSidebar = observer(function ProjectAppSidebar() { return
Sidebar
; });" `); }); it("should handle inline JSX arrow function", async () => { const result = await applyTransform( transformer, ` export const DateAlert = (props: TDateAlertProps) => <>; `, { parser: "tsx" }, ); expect(result).toMatchInlineSnapshot(` "export function DateAlert(props: TDateAlertProps) { return <>; }" `); }); it("should handle observer with generic type parameters", async () => { const result = await applyTransform( transformer, ` import { observer } from "mobx-react"; export const InstanceProvider = observer>((props) => { const { children } = props; return <>{children}; }); `, { parser: "tsx" }, ); expect(result).toMatchInlineSnapshot(` "import { observer } from "mobx-react"; export const InstanceProvider = observer(function InstanceProvider(props: React.PropsWithChildren) { const { children } = props; return <>{children}; });" `); }); it("should not add double semicolons after use client directive", async () => { const result = await applyTransform( transformer, ` "use client"; import { observer } from "mobx-react"; export const MyComponent = observer(() => { return
Hello
; }); `, { parser: "tsx" }, ); expect(result).toMatchInlineSnapshot(` ""use client"; import { observer } from "mobx-react"; export const MyComponent = observer(function MyComponent() { return
Hello
; });" `); }); it("should preserve generic type parameters in wrapper functions", async () => { const result = await applyTransform( transformer, ` import React from "react"; export const ScatterChart = React.memo((props: TScatterChartProps) => { return
Chart
; }); `, { parser: "tsx" }, ); expect(result).toMatchInlineSnapshot(` "import React from "react"; export const ScatterChart = React.memo( function ScatterChart(props: TScatterChartProps) { return
Chart
; } );" `); }); it("should preserve generic type parameters on React.forwardRef", async () => { const result = await applyTransform( transformer, ` import React from "react"; const Button = React.forwardRef((props, ref) => { return ; }); `, { parser: "tsx" }, ); expect(result).toMatchInlineSnapshot(` "import React from "react"; const Button = React.forwardRef( function Button(props: ButtonProps, ref: React.ForwardedRef) { return ; } );" `); }); it("should prefix unused props parameter with underscore", async () => { const result = await applyTransform( transformer, ` import type { TCallbackMentionComponentProps } from "@plane/editor"; export const EditorAdditionalMentionsRoot: React.FC = () => null; `, { parser: "tsx" }, ); expect(result).toMatchInlineSnapshot(` "import type { TCallbackMentionComponentProps } from "@plane/editor"; export function EditorAdditionalMentionsRoot(_props: TCallbackMentionComponentProps) { return null; }" `); }); it("should add Record type for React.forwardRef with only element type", async () => { const result = await applyTransform( transformer, ` import { forwardRef } from "react"; const ListLoaderItemRow = forwardRef((props, ref) => (
Content
)); `, { parser: "tsx" }, ); expect(result).toMatchInlineSnapshot(` "import { forwardRef } from "react"; const ListLoaderItemRow = forwardRef( function ListLoaderItemRow(props: Record, ref: React.ForwardedRef) { return (
Content
); } );" `); }); it("should preserve comments in function body", async () => { const result = await applyTransform( transformer, ` export const PreloadResources = () => ( // usePreloadResources(); null ); `, { parser: "tsx" }, ); expect(result).toMatchInlineSnapshot(` "export function PreloadResources() { return ( // usePreloadResources(); (null) ); }" `); }); it("should preserve leading comments before export declaration", async () => { const result = await applyTransform( transformer, ` "use client"; // TODO: Check if we need this // https://nextjs.org/docs/app/api-reference/functions/generate-metadata#link-relpreload // export const usePreloadResources = () => { // useEffect(() => { // const preloadItem = (url: string) => { // ReactDOM.preload(url, { as: "fetch", crossOrigin: "use-credentials" }); // }; // // const urls = [ // \`\${process.env.VITE_API_BASE_URL}/api/instances/\`, // \`\${process.env.VITE_API_BASE_URL}/api/users/me/\`, // \`\${process.env.VITE_API_BASE_URL}/api/users/me/profile/\`, // \`\${process.env.VITE_API_BASE_URL}/api/users/me/settings/\`, // \`\${process.env.VITE_API_BASE_URL}/api/users/me/workspaces/?v=\${Date.now()}\`, // ]; // // urls.forEach((url) => preloadItem(url)); // }, []); // }; export const PreloadResources = () => // usePreloadResources(); null; `, { parser: "tsx" }, ); expect(result).toMatchInlineSnapshot(` ""use client"; // TODO: Check if we need this // https://nextjs.org/docs/app/api-reference/functions/generate-metadata#link-relpreload // export const usePreloadResources = () => { // useEffect(() => { // const preloadItem = (url: string) => { // ReactDOM.preload(url, { as: "fetch", crossOrigin: "use-credentials" }); // }; // // const urls = [ // \`\${process.env.VITE_API_BASE_URL}/api/instances/\`, // \`\${process.env.VITE_API_BASE_URL}/api/users/me/\`, // \`\${process.env.VITE_API_BASE_URL}/api/users/me/profile/\`, // \`\${process.env.VITE_API_BASE_URL}/api/users/me/settings/\`, // \`\${process.env.VITE_API_BASE_URL}/api/users/me/workspaces/?v=\${Date.now()}\`, // ]; // // urls.forEach((url) => preloadItem(url)); // }, []); // }; export function PreloadResources() { return ( // usePreloadResources(); null ); }" `); }); it("should preserve leading comments before wrapped export declaration", async () => { const result = await applyTransform( transformer, ` // This is a wrapped component // It uses observer for reactivity export const MyObserverComponent = observer(() => { return
Observer component
; }); `, { parser: "tsx" }, ); expect(result).toMatchInlineSnapshot(` "// This is a wrapped component // It uses observer for reactivity export const MyObserverComponent = observer(function MyObserverComponent() { return
Observer component
; });" `); }); it("should preserve trailing comments on exported variable declaration", async () => { const result = await applyTransform( transformer, ` export const Foo = () =>
; // trailing comment `, { parser: "tsx" }, ); expect(result).toContain("// trailing comment"); }); it("should preserve leading comments on exported variable declaration inside export", async () => { const result = await applyTransform( transformer, ` export /* leading comment */ const Foo = () =>
; `, { parser: "tsx" }, ); expect(result).toContain("/* leading comment */"); }); });