UNPKG

rwsdk

Version:

Build fast, server-driven webapps on Cloudflare with SSR, RSC, and realtime

1,295 lines (1,194 loc) 46.6 kB
import { describe, it, expect } from "vitest"; import { transformUseClientCode } from "./useClientPlugin.mjs"; describe("transformUseClientCode", () => { async function transform(code) { const result = await transformUseClientCode(code, "/test/file.tsx"); return result?.code; } it("transforms arrow function component", async () => { expect(await transform(`"use client" export const Component = () => { return jsx('div', { children: 'Hello' }); }`)).toMatchInlineSnapshot(` "import { registerClientReference } from "rwsdk/worker"; const ComponentSSR = () => { return jsx('div', { children: 'Hello' }); } const Component = registerClientReference("/test/file.tsx", "Component", ComponentSSR); export { ComponentSSR, Component };" `); }); it("does nothing for irrelevant exports", async () => { expect(await transform(`"use client" export const foo = "bar" export const baz = 23 }`)).toMatchInlineSnapshot(` "import { registerClientReference } from "rwsdk/worker"; export const foo = "bar" export const baz = 23 }" `); }); it("transforms async arrow function component", async () => { expect(await transform(`"use client" export const Component = async () => { return jsx('div', { children: 'Hello' }); }`)).toMatchInlineSnapshot(` "import { registerClientReference } from "rwsdk/worker"; const ComponentSSR = async () => { return jsx('div', { children: 'Hello' }); } const Component = registerClientReference("/test/file.tsx", "Component", ComponentSSR); export { ComponentSSR, Component };" `); }); it("transforms function declaration component", async () => { expect(await transform(`"use client" export function Component() { return jsx('div', { children: 'Hello' }); }`)).toMatchInlineSnapshot(` "import { registerClientReference } from "rwsdk/worker"; function ComponentSSR() { return jsx('div', { children: 'Hello' }); } const Component = registerClientReference("/test/file.tsx", "Component", ComponentSSR); export { ComponentSSR, Component };" `); }); it("transforms async function declaration component", async () => { expect(await transform(`"use client" export async function Component() { return jsx('div', { children: 'Hello' }); }`)).toMatchInlineSnapshot(` "import { registerClientReference } from "rwsdk/worker"; async function ComponentSSR() { return jsx('div', { children: 'Hello' }); } const Component = registerClientReference("/test/file.tsx", "Component", ComponentSSR); export { ComponentSSR, Component };" `); }); it("transforms multiple arrow function components", async () => { expect(await transform(`"use client" export const First = () => { return jsx('div', { children: 'First' }); } export const Second = () => { return jsx('div', { children: 'Second' }); }`)).toMatchInlineSnapshot(` "import { registerClientReference } from "rwsdk/worker"; const FirstSSR = () => { return jsx('div', { children: 'First' }); } const SecondSSR = () => { return jsx('div', { children: 'Second' }); } const First = registerClientReference("/test/file.tsx", "First", FirstSSR); const Second = registerClientReference("/test/file.tsx", "Second", SecondSSR); export { FirstSSR, First }; export { SecondSSR, Second };" `); }); it("transforms multiple async arrow function components", async () => { expect(await transform(`"use client" export const First = async () => { return jsx('div', { children: 'First' }); } export const Second = async () => { return jsx('div', { children: 'Second' }); }`)).toMatchInlineSnapshot(` "import { registerClientReference } from "rwsdk/worker"; const FirstSSR = async () => { return jsx('div', { children: 'First' }); } const SecondSSR = async () => { return jsx('div', { children: 'Second' }); } const First = registerClientReference("/test/file.tsx", "First", FirstSSR); const Second = registerClientReference("/test/file.tsx", "Second", SecondSSR); export { FirstSSR, First }; export { SecondSSR, Second };" `); }); it("transforms multiple function declaration components", async () => { expect(await transform(`"use client" export function First() { return jsx('div', { children: 'First' }); } export function Second() { return jsx('div', { children: 'Second' }); }`)).toMatchInlineSnapshot(` "import { registerClientReference } from "rwsdk/worker"; function FirstSSR() { return jsx('div', { children: 'First' }); } function SecondSSR() { return jsx('div', { children: 'Second' }); } const First = registerClientReference("/test/file.tsx", "First", FirstSSR); const Second = registerClientReference("/test/file.tsx", "Second", SecondSSR); export { FirstSSR, First }; export { SecondSSR, Second };" `); }); it("transforms multiple async function declaration components", async () => { expect(await transform(`"use client" export async function First() { return jsx('div', { children: 'First' }); } export async function Second() { return jsx('div', { children: 'Second' }); }`)).toMatchInlineSnapshot(` "import { registerClientReference } from "rwsdk/worker"; async function FirstSSR() { return jsx('div', { children: 'First' }); } async function SecondSSR() { return jsx('div', { children: 'Second' }); } const First = registerClientReference("/test/file.tsx", "First", FirstSSR); const Second = registerClientReference("/test/file.tsx", "Second", SecondSSR); export { FirstSSR, First }; export { SecondSSR, Second };" `); }); it("transforms components with grouped exports (arrow functions)", async () => { expect(await transform(`"use client" const First = () => { return jsx('div', { children: 'First' }); } const Second = () => { return jsx('div', { children: 'Second' }); } export { First, Second }`)).toMatchInlineSnapshot(` "import { registerClientReference } from "rwsdk/worker"; const FirstSSR = () => { return jsx('div', { children: 'First' }); } const SecondSSR = () => { return jsx('div', { children: 'Second' }); } const First = registerClientReference("/test/file.tsx", "First", FirstSSR); const Second = registerClientReference("/test/file.tsx", "Second", SecondSSR); export { FirstSSR, First }; export { SecondSSR, Second };" `); }); it("transforms complex grouped export cases", async () => { expect(await transform(` "use client"; import * as React from "react" import { Slot } from "@radix-ui/react-slot" import { cva, type VariantProps } from "class-variance-authority" import { jsx, jsxs } from "react/jsx-runtime" import { cn } from "@/lib/utils" const buttonVariants = cva( "font-bold inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", { variants: { variant: { default: "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90", destructive: "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80", ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", link: "text-primary underline-offset-4 hover:underline", }, size: { default: "h-9 px-4 py-2 has-[>svg]:px-3", sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", lg: "h-10 rounded-md px-6 has-[>svg]:px-4", icon: "size-9", }, }, defaultVariants: { variant: "default", size: "default", }, } ) function Button({ className, variant, size, asChild = false, ...props }: React.ComponentProps<"button"> & VariantProps<typeof buttonVariants> & { asChild?: boolean }) { const Comp = asChild ? Slot : "button" return jsx( Comp, { "data-slot": "button", className: cn(buttonVariants({ variant, size, className })), ...props } ) } export { Button, buttonVariants } `)).toMatchInlineSnapshot(` " import * as React from "react" import { Slot } from "@radix-ui/react-slot" import { cva, type VariantProps } from "class-variance-authority" import { jsx, jsxs } from "react/jsx-runtime" import { cn } from "@/lib/utils" import { registerClientReference } from "rwsdk/worker"; const buttonVariants = cva( "font-bold inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", { variants: { variant: { default: "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90", destructive: "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80", ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", link: "text-primary underline-offset-4 hover:underline", }, size: { default: "h-9 px-4 py-2 has-[>svg]:px-3", sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", lg: "h-10 rounded-md px-6 has-[>svg]:px-4", icon: "size-9", }, }, defaultVariants: { variant: "default", size: "default", }, } ) function ButtonSSR({ className, variant, size, asChild = false, ...props }: React.ComponentProps<"button"> & VariantProps<typeof buttonVariants> & { asChild?: boolean }) { const Comp = asChild ? Slot : "button" return jsx( Comp, { "data-slot": "button", className: cn(buttonVariants({ variant, size, className })), ...props } ) } export { buttonVariants }; const Button = registerClientReference("/test/file.tsx", "Button", ButtonSSR); export { ButtonSSR, Button }; " `); }); it("transforms components with grouped exports (async arrow functions)", async () => { expect(await transform(`"use client" const First = async () => { return jsx('div', { children: 'First' }); } const Second = async () => { return jsx('div', { children: 'Second' }); } export { First, Second }`)).toMatchInlineSnapshot(` "import { registerClientReference } from "rwsdk/worker"; const FirstSSR = async () => { return jsx('div', { children: 'First' }); } const SecondSSR = async () => { return jsx('div', { children: 'Second' }); } const First = registerClientReference("/test/file.tsx", "First", FirstSSR); const Second = registerClientReference("/test/file.tsx", "Second", SecondSSR); export { FirstSSR, First }; export { SecondSSR, Second };" `); }); it("transforms components with grouped exports (function declarations)", async () => { expect(await transform(`"use client" function First() { return jsx('div', { children: 'First' }); } function Second() { return jsx('div', { children: 'Second' }); } export { First, Second }`)).toMatchInlineSnapshot(` "import { registerClientReference } from "rwsdk/worker"; function FirstSSR() { return jsx('div', { children: 'First' }); } function SecondSSR() { return jsx('div', { children: 'Second' }); } const First = registerClientReference("/test/file.tsx", "First", FirstSSR); const Second = registerClientReference("/test/file.tsx", "Second", SecondSSR); export { FirstSSR, First }; export { SecondSSR, Second };" `); }); it("transforms components with grouped exports (async function declarations)", async () => { expect(await transform(`"use client" async function First() { return jsx('div', { children: 'First' }); } async function Second() { return jsx('div', { children: 'Second' }); } export { First, Second }`)).toMatchInlineSnapshot(` "import { registerClientReference } from "rwsdk/worker"; async function FirstSSR() { return jsx('div', { children: 'First' }); } async function SecondSSR() { return jsx('div', { children: 'Second' }); } const First = registerClientReference("/test/file.tsx", "First", FirstSSR); const Second = registerClientReference("/test/file.tsx", "Second", SecondSSR); export { FirstSSR, First }; export { SecondSSR, Second };" `); }); it("transforms default export arrow function component", async () => { expect(await transform(`"use client" export default () => { return jsx('div', { children: 'Hello' }); }`)).toMatchInlineSnapshot(` "import { registerClientReference } from "rwsdk/worker"; const DefaultComponent0SSR = () => { return jsx('div', { children: 'Hello' }); } const DefaultComponent0 = registerClientReference("/test/file.tsx", "default", DefaultComponent0SSR); export { DefaultComponent0 as default, DefaultComponent0SSR };" `); }); it("transforms default export async arrow function component", async () => { expect(await transform(`"use client" export default async () => { return jsx('div', { children: 'Hello' }); }`)).toMatchInlineSnapshot(` "import { registerClientReference } from "rwsdk/worker"; const DefaultComponent0SSR = async () => { return jsx('div', { children: 'Hello' }); } const DefaultComponent0 = registerClientReference("/test/file.tsx", "default", DefaultComponent0SSR); export { DefaultComponent0 as default, DefaultComponent0SSR };" `); }); it("transforms complex default export cases", async () => { expect(await transform(` "use client"; import { jsxDEV } from "react/jsx-dev-runtime"; import { useState, useEffect, useRef } from "react"; import { ChevronDownIcon, CheckIcon } from "./icons"; export const MovieSelector = ({ label, selectedMovie, onSelect, otherSelectedMovie, movies, error }) => { const [isOpen, setIsOpen] = useState(false); const dropdownRef = useRef(null); useEffect(() => { const handleClickOutside = (event) => { if (dropdownRef.current && !dropdownRef.current.contains(event.target)) { setIsOpen(false); } }; document.addEventListener("mousedown", handleClickOutside); return () => { document.removeEventListener("mousedown", handleClickOutside); }; }, []); const selectedMovieObj = movies.find((movie) => movie.id === selectedMovie); const availableMovies = movies.filter( (movie) => !otherSelectedMovie || movie.id !== otherSelectedMovie ); return /* @__PURE__ */ jsxDEV("div", { className: "relative", ref: dropdownRef, children: [ /* @__PURE__ */ jsxDEV("label", { className: "font-banner block text-sm font-medium text-gray-700 mb-1", children: label === "First Movie" ? "Mash ..." : "With ..." }, void 0, false, { fileName: "/Users/justin/rw/blotter/ai-movie-mashup/src/app/pages/mashups/components/MovieSelector.tsx", lineNumber: 49, columnNumber: 7 }, this), /* @__PURE__ */ jsxDEV( "button", { type: "button", className: "w-full p-2 border border-gray-300 rounded-md bg-white text-left flex items-center justify-between disabled:opacity-50 disabled:cursor-not-allowed", onClick: () => setIsOpen(!isOpen), disabled: !!error, children: [ error ? /* @__PURE__ */ jsxDEV("span", { className: "text-red-500", children: "Error loading movies" }, void 0, false, { fileName: "/Users/justin/rw/blotter/ai-movie-mashup/src/app/pages/mashups/components/MovieSelector.tsx", lineNumber: 59, columnNumber: 11 }, this) : selectedMovieObj ? /* @__PURE__ */ jsxDEV("div", { className: "flex items-center", children: [ /* @__PURE__ */ jsxDEV( "img", { src: \`https://image.tmdb.org/t/p/w500/\${selectedMovieObj.photo}\`, alt: selectedMovieObj.title, className: "size-6 shrink-0 rounded-sm mr-2" }, void 0, false, { fileName: "/Users/justin/rw/blotter/ai-movie-mashup/src/app/pages/mashups/components/MovieSelector.tsx", lineNumber: 62, columnNumber: 13 }, this ), /* @__PURE__ */ jsxDEV("span", { children: selectedMovieObj.title }, void 0, false, { fileName: "/Users/justin/rw/blotter/ai-movie-mashup/src/app/pages/mashups/components/MovieSelector.tsx", lineNumber: 67, columnNumber: 13 }, this) ] }, void 0, true, { fileName: "/Users/justin/rw/blotter/ai-movie-mashup/src/app/pages/mashups/components/MovieSelector.tsx", lineNumber: 61, columnNumber: 11 }, this) : /* @__PURE__ */ jsxDEV("span", { className: "text-gray-500", children: label }, void 0, false, { fileName: "/Users/justin/rw/blotter/ai-movie-mashup/src/app/pages/mashups/components/MovieSelector.tsx", lineNumber: 70, columnNumber: 11 }, this), /* @__PURE__ */ jsxDEV(ChevronDownIcon, { isOpen }, void 0, false, { fileName: "/Users/justin/rw/blotter/ai-movie-mashup/src/app/pages/mashups/components/MovieSelector.tsx", lineNumber: 72, columnNumber: 9 }, this) ] }, void 0, true, { fileName: "/Users/justin/rw/blotter/ai-movie-mashup/src/app/pages/mashups/components/MovieSelector.tsx", lineNumber: 52, columnNumber: 7 }, this ), isOpen && !error && /* @__PURE__ */ jsxDEV("div", { className: "absolute z-10 mt-1 w-full bg-white shadow-lg max-h-60 rounded-md py-1 text-base overflow-auto focus:outline-none sm:text-sm", children: availableMovies.map((movie) => /* @__PURE__ */ jsxDEV( "div", { className: "cursor-pointer select-none relative py-2 pl-3 pr-9 hover:bg-purple-50", onClick: () => { onSelect(movie.id); setIsOpen(false); }, children: [ /* @__PURE__ */ jsxDEV("div", { className: "flex items-center", children: [ /* @__PURE__ */ jsxDEV( "img", { src: \`https://image.tmdb.org/t/p/w500/\${movie.photo}\`, alt: movie.title, className: "size-10 shrink-0 rounded-sm mr-2" }, void 0, false, { fileName: "/Users/justin/rw/blotter/ai-movie-mashup/src/app/pages/mashups/components/MovieSelector.tsx", lineNumber: 87, columnNumber: 17 }, this ), /* @__PURE__ */ jsxDEV("span", { className: "block truncate text-base font-medium", children: movie.title }, void 0, false, { fileName: "/Users/justin/rw/blotter/ai-movie-mashup/src/app/pages/mashups/components/MovieSelector.tsx", lineNumber: 92, columnNumber: 17 }, this) ] }, void 0, true, { fileName: "/Users/justin/rw/blotter/ai-movie-mashup/src/app/pages/mashups/components/MovieSelector.tsx", lineNumber: 86, columnNumber: 15 }, this), selectedMovie === movie.id && /* @__PURE__ */ jsxDEV("span", { className: "absolute inset-y-0 right-0 flex items-center pr-4 text-purple-600", children: /* @__PURE__ */ jsxDEV(CheckIcon, {}, void 0, false, { fileName: "/Users/justin/rw/blotter/ai-movie-mashup/src/app/pages/mashups/components/MovieSelector.tsx", lineNumber: 99, columnNumber: 19 }, this) }, void 0, false, { fileName: "/Users/justin/rw/blotter/ai-movie-mashup/src/app/pages/mashups/components/MovieSelector.tsx", lineNumber: 98, columnNumber: 17 }, this) ] }, movie.id, true, { fileName: "/Users/justin/rw/blotter/ai-movie-mashup/src/app/pages/mashups/components/MovieSelector.tsx", lineNumber: 78, columnNumber: 13 }, this )) }, void 0, false, { fileName: "/Users/justin/rw/blotter/ai-movie-mashup/src/app/pages/mashups/components/MovieSelector.tsx", lineNumber: 76, columnNumber: 9 }, this) ] }, void 0, true, { fileName: "/Users/justin/rw/blotter/ai-movie-mashup/src/app/pages/mashups/components/MovieSelector.tsx", lineNumber: 48, columnNumber: 5 }, this); }; `)).toMatchInlineSnapshot(` " import { jsxDEV } from "react/jsx-dev-runtime"; import { useState, useEffect, useRef } from "react"; import { ChevronDownIcon, CheckIcon } from "./icons"; import { registerClientReference } from "rwsdk/worker"; const MovieSelectorSSR = ({ label, selectedMovie, onSelect, otherSelectedMovie, movies, error }) => { const [isOpen, setIsOpen] = useState(false); const dropdownRef = useRef(null); useEffect(() => { const handleClickOutside = (event) => { if (dropdownRef.current && !dropdownRef.current.contains(event.target)) { setIsOpen(false); } }; document.addEventListener("mousedown", handleClickOutside); return () => { document.removeEventListener("mousedown", handleClickOutside); }; }, []); const selectedMovieObj = movies.find((movie) => movie.id === selectedMovie); const availableMovies = movies.filter( (movie) => !otherSelectedMovie || movie.id !== otherSelectedMovie ); return /* @__PURE__ */ jsxDEV("div", { className: "relative", ref: dropdownRef, children: [ /* @__PURE__ */ jsxDEV("label", { className: "font-banner block text-sm font-medium text-gray-700 mb-1", children: label === "First Movie" ? "Mash ..." : "With ..." }, void 0, false, { fileName: "/Users/justin/rw/blotter/ai-movie-mashup/src/app/pages/mashups/components/MovieSelector.tsx", lineNumber: 49, columnNumber: 7 }, this), /* @__PURE__ */ jsxDEV( "button", { type: "button", className: "w-full p-2 border border-gray-300 rounded-md bg-white text-left flex items-center justify-between disabled:opacity-50 disabled:cursor-not-allowed", onClick: () => setIsOpen(!isOpen), disabled: !!error, children: [ error ? /* @__PURE__ */ jsxDEV("span", { className: "text-red-500", children: "Error loading movies" }, void 0, false, { fileName: "/Users/justin/rw/blotter/ai-movie-mashup/src/app/pages/mashups/components/MovieSelector.tsx", lineNumber: 59, columnNumber: 11 }, this) : selectedMovieObj ? /* @__PURE__ */ jsxDEV("div", { className: "flex items-center", children: [ /* @__PURE__ */ jsxDEV( "img", { src: \`https://image.tmdb.org/t/p/w500/\${selectedMovieObj.photo}\`, alt: selectedMovieObj.title, className: "size-6 shrink-0 rounded-sm mr-2" }, void 0, false, { fileName: "/Users/justin/rw/blotter/ai-movie-mashup/src/app/pages/mashups/components/MovieSelector.tsx", lineNumber: 62, columnNumber: 13 }, this ), /* @__PURE__ */ jsxDEV("span", { children: selectedMovieObj.title }, void 0, false, { fileName: "/Users/justin/rw/blotter/ai-movie-mashup/src/app/pages/mashups/components/MovieSelector.tsx", lineNumber: 67, columnNumber: 13 }, this) ] }, void 0, true, { fileName: "/Users/justin/rw/blotter/ai-movie-mashup/src/app/pages/mashups/components/MovieSelector.tsx", lineNumber: 61, columnNumber: 11 }, this) : /* @__PURE__ */ jsxDEV("span", { className: "text-gray-500", children: label }, void 0, false, { fileName: "/Users/justin/rw/blotter/ai-movie-mashup/src/app/pages/mashups/components/MovieSelector.tsx", lineNumber: 70, columnNumber: 11 }, this), /* @__PURE__ */ jsxDEV(ChevronDownIcon, { isOpen }, void 0, false, { fileName: "/Users/justin/rw/blotter/ai-movie-mashup/src/app/pages/mashups/components/MovieSelector.tsx", lineNumber: 72, columnNumber: 9 }, this) ] }, void 0, true, { fileName: "/Users/justin/rw/blotter/ai-movie-mashup/src/app/pages/mashups/components/MovieSelector.tsx", lineNumber: 52, columnNumber: 7 }, this ), isOpen && !error && /* @__PURE__ */ jsxDEV("div", { className: "absolute z-10 mt-1 w-full bg-white shadow-lg max-h-60 rounded-md py-1 text-base overflow-auto focus:outline-none sm:text-sm", children: availableMovies.map((movie) => /* @__PURE__ */ jsxDEV( "div", { className: "cursor-pointer select-none relative py-2 pl-3 pr-9 hover:bg-purple-50", onClick: () => { onSelect(movie.id); setIsOpen(false); }, children: [ /* @__PURE__ */ jsxDEV("div", { className: "flex items-center", children: [ /* @__PURE__ */ jsxDEV( "img", { src: \`https://image.tmdb.org/t/p/w500/\${movie.photo}\`, alt: movie.title, className: "size-10 shrink-0 rounded-sm mr-2" }, void 0, false, { fileName: "/Users/justin/rw/blotter/ai-movie-mashup/src/app/pages/mashups/components/MovieSelector.tsx", lineNumber: 87, columnNumber: 17 }, this ), /* @__PURE__ */ jsxDEV("span", { className: "block truncate text-base font-medium", children: movie.title }, void 0, false, { fileName: "/Users/justin/rw/blotter/ai-movie-mashup/src/app/pages/mashups/components/MovieSelector.tsx", lineNumber: 92, columnNumber: 17 }, this) ] }, void 0, true, { fileName: "/Users/justin/rw/blotter/ai-movie-mashup/src/app/pages/mashups/components/MovieSelector.tsx", lineNumber: 86, columnNumber: 15 }, this), selectedMovie === movie.id && /* @__PURE__ */ jsxDEV("span", { className: "absolute inset-y-0 right-0 flex items-center pr-4 text-purple-600", children: /* @__PURE__ */ jsxDEV(CheckIcon, {}, void 0, false, { fileName: "/Users/justin/rw/blotter/ai-movie-mashup/src/app/pages/mashups/components/MovieSelector.tsx", lineNumber: 99, columnNumber: 19 }, this) }, void 0, false, { fileName: "/Users/justin/rw/blotter/ai-movie-mashup/src/app/pages/mashups/components/MovieSelector.tsx", lineNumber: 98, columnNumber: 17 }, this) ] }, movie.id, true, { fileName: "/Users/justin/rw/blotter/ai-movie-mashup/src/app/pages/mashups/components/MovieSelector.tsx", lineNumber: 78, columnNumber: 13 }, this )) }, void 0, false, { fileName: "/Users/justin/rw/blotter/ai-movie-mashup/src/app/pages/mashups/components/MovieSelector.tsx", lineNumber: 76, columnNumber: 9 }, this) ] }, void 0, true, { fileName: "/Users/justin/rw/blotter/ai-movie-mashup/src/app/pages/mashups/components/MovieSelector.tsx", lineNumber: 48, columnNumber: 5 }, this); }; const MovieSelector = registerClientReference("/test/file.tsx", "MovieSelector", MovieSelectorSSR); export { MovieSelectorSSR, MovieSelector }; " `); }); it("transforms default export function declaration component", async () => { expect(await transform(`"use client" export default function Component({ prop1, prop2 }) { return jsx('div', { children: 'Hello' }); }`)).toMatchInlineSnapshot(` "import { registerClientReference } from "rwsdk/worker"; function ComponentSSR({ prop1, prop2 }) { return jsx('div', { children: 'Hello' }); } const Component = registerClientReference("/test/file.tsx", "default", ComponentSSR); export { Component as default, ComponentSSR };" `); }); it("transforms default export async function declaration component", async () => { expect(await transform(`"use client" export default async function Component() { return jsx('div', { children: 'Hello' }); }`)).toMatchInlineSnapshot(` "import { registerClientReference } from "rwsdk/worker"; async function ComponentSSR() { return jsx('div', { children: 'Hello' }); } const Component = registerClientReference("/test/file.tsx", "default", ComponentSSR); export { Component as default, ComponentSSR };" `); }); it("transforms complex component with multiple computations", async () => { expect(await transform(`"use client" export function ComplexComponent({ initialCount = 0 }) { const [count, setCount] = useState(initialCount); const [items, setItems] = useState([]); const doubledCount = useMemo(() => count * 2, [count]); useEffect(() => { const timer = setInterval(() => { setCount(c => c + 1); }, 1000); return () => clearInterval(timer); }, []); const handleAddItem = useCallback(() => { const newItem = { id: Math.random().toString(36), value: Math.floor(Math.random() * 100) }; setItems(prev => [...prev, newItem]); }, []); const filteredItems = items.filter(item => item.value > 50); const total = filteredItems.reduce((sum, item) => sum + item.value, 0); if (items.length === 0) { return jsx('div', { children: 'No items yet' }); } return jsxs('div', { className: 'complex-component', children: [ jsxs('div', { className: 'counter-section', children: [ jsx('h2', { children: 'Counter' }), jsxs('p', { children: [ 'Count: ', jsx('span', { children: count }), ' (Doubled: ', jsx('span', { children: doubledCount }), ')' ] }) ] }), jsxs('div', { className: 'items-section', children: [ jsx('h2', { children: 'Items' }), jsx('button', { onClick: handleAddItem, children: 'Add Random Item' }), jsxs('div', { children: [ 'Total value of filtered items: ', jsx('span', { children: total }) ] }), jsx('ul', { children: filteredItems.map(item => jsx('li', { key: item.id, children: \`Item \${item.id}: \${item.value}\` }) ) }) ] }) ] }); }`)).toMatchInlineSnapshot(` "import { registerClientReference } from "rwsdk/worker"; function ComplexComponentSSR({ initialCount = 0 }) { const [count, setCount] = useState(initialCount); const [items, setItems] = useState([]); const doubledCount = useMemo(() => count * 2, [count]); useEffect(() => { const timer = setInterval(() => { setCount(c => c + 1); }, 1000); return () => clearInterval(timer); }, []); const handleAddItem = useCallback(() => { const newItem = { id: Math.random().toString(36), value: Math.floor(Math.random() * 100) }; setItems(prev => [...prev, newItem]); }, []); const filteredItems = items.filter(item => item.value > 50); const total = filteredItems.reduce((sum, item) => sum + item.value, 0); if (items.length === 0) { return jsx('div', { children: 'No items yet' }); } return jsxs('div', { className: 'complex-component', children: [ jsxs('div', { className: 'counter-section', children: [ jsx('h2', { children: 'Counter' }), jsxs('p', { children: [ 'Count: ', jsx('span', { children: count }), ' (Doubled: ', jsx('span', { children: doubledCount }), ')' ] }) ] }), jsxs('div', { className: 'items-section', children: [ jsx('h2', { children: 'Items' }), jsx('button', { onClick: handleAddItem, children: 'Add Random Item' }), jsxs('div', { children: [ 'Total value of filtered items: ', jsx('span', { children: total }) ] }), jsx('ul', { children: filteredItems.map(item => jsx('li', { key: item.id, children: \`Item \${item.id}: \${item.value}\` }) ) }) ] }) ] }); } const ComplexComponent = registerClientReference("/test/file.tsx", "ComplexComponent", ComplexComponentSSR); export { ComplexComponentSSR, ComplexComponent };" `); }); it("transforms mixed export styles (inline, grouped, and default)", async () => { expect(await transform(`"use client" export const First = () => { return jsx('div', { children: 'First' }); } const Second = () => { return jsx('div', { children: 'Second' }); } function Third() { return jsx('div', { children: 'Third' }); } const Fourth = () => { return jsx('div', { children: 'Fourth' }); } export default function Main() { return jsx('div', { children: 'Main' }); } export { Second, Third } export { Fourth as AnotherName }`)).toMatchInlineSnapshot(` "import { registerClientReference } from "rwsdk/worker"; const FirstSSR = () => { return jsx('div', { children: 'First' }); } const SecondSSR = () => { return jsx('div', { children: 'Second' }); } function ThirdSSR() { return jsx('div', { children: 'Third' }); } const FourthSSR = () => { return jsx('div', { children: 'Fourth' }); } function MainSSR() { return jsx('div', { children: 'Main' }); } const Third = registerClientReference("/test/file.tsx", "Third", ThirdSSR); const Main = registerClientReference("/test/file.tsx", "default", MainSSR); const First = registerClientReference("/test/file.tsx", "First", FirstSSR); const Second = registerClientReference("/test/file.tsx", "Second", SecondSSR); const Fourth = registerClientReference("/test/file.tsx", "Fourth", FourthSSR); export { ThirdSSR, Third }; export { Main as default, MainSSR }; export { FirstSSR, First }; export { SecondSSR, Second }; export { FourthSSR, Fourth };" `); }); it("transforms function declaration that is exported default separately", async () => { expect(await transform(` "use client" function Component({ prop1, prop2 }) { return jsx('div', { children: 'Hello' }); } export default Component;`)).toMatchInlineSnapshot(` "import { registerClientReference } from "rwsdk/worker"; function ComponentSSR({ prop1, prop2 }) { return jsx('div', { children: 'Hello' }); } const Component = registerClientReference("/test/file.tsx", "default", ComponentSSR); export { Component as default, ComponentSSR };" `); }); it("Works in dev", async () => { expect(await transform(`"use client" import { jsxDEV } from "react/jsx-dev-runtime"; import { sendMessage } from "./functions"; import { useState } from "react"; import { consumeEventStream } from "rwsdk/client"; export function Chat() { const [message, setMessage] = useState(""); const [reply, setReply] = useState(""); const onClick = async () => { setReply(""); (await sendMessage(message)).pipeTo( consumeEventStream({ onChunk: (event) => { setReply( (prev) => prev + (event.data === "[DONE]" ? "" : JSON.parse(event.data).response) ); } }) ); }; return /* @__PURE__ */ jsxDEV("div", { children: [ /* @__PURE__ */ jsxDEV( "input", { type: "text", value: message, placeholder: "Type a message...", onChange: (e) => setMessage(e.target.value), style: { width: "80%", padding: "10px", marginRight: "8px", borderRadius: "4px", border: "1px solid #ccc" } }, void 0, false, { fileName: "/Users/justin/rw/sdk/experiments/ai-stream/src/app/pages/Chat/Chat.tsx", lineNumber: 28, columnNumber: 7 }, this ), /* @__PURE__ */ jsxDEV( "button", { onClick, style: { padding: "10px 20px", borderRadius: "4px", border: "none", backgroundColor: "#007bff", color: "white", cursor: "pointer" }, children: "Send" }, void 0, false, { fileName: "/Users/justin/rw/sdk/experiments/ai-stream/src/app/pages/Chat/Chat.tsx", lineNumber: 41, columnNumber: 7 }, this ), /* @__PURE__ */ jsxDEV("div", { children: reply }, void 0, false, { fileName: "/Users/justin/rw/sdk/experiments/ai-stream/src/app/pages/Chat/Chat.tsx", lineNumber: 54, columnNumber: 7 }, this) ] }, void 0, true, { fileName: "/Users/justin/rw/sdk/experiments/ai-stream/src/app/pages/Chat/Chat.tsx", lineNumber: 27, columnNumber: 5 }, this); } `)).toMatchInlineSnapshot(` "import { jsxDEV } from "react/jsx-dev-runtime"; import { sendMessage } from "./functions"; import { useState } from "react"; import { consumeEventStream } from "rwsdk/client"; import { registerClientReference } from "rwsdk/worker"; function ChatSSR() { const [message, setMessage] = useState(""); const [reply, setReply] = useState(""); const onClick = async () => { setReply(""); (await sendMessage(message)).pipeTo( consumeEventStream({ onChunk: (event) => { setReply( (prev) => prev + (event.data === "[DONE]" ? "" : JSON.parse(event.data).response) ); } }) ); }; return /* @__PURE__ */ jsxDEV("div", { children: [ /* @__PURE__ */ jsxDEV( "input", { type: "text", value: message, placeholder: "Type a message...", onChange: (e) => setMessage(e.target.value), style: { width: "80%", padding: "10px", marginRight: "8px", borderRadius: "4px", border: "1px solid #ccc" } }, void 0, false, { fileName: "/Users/justin/rw/sdk/experiments/ai-stream/src/app/pages/Chat/Chat.tsx", lineNumber: 28, columnNumber: 7 }, this ), /* @__PURE__ */ jsxDEV( "button", { onClick, style: { padding: "10px 20px", borderRadius: "4px", border: "none", backgroundColor: "#007bff", color: "white", cursor: "pointer" }, children: "Send" }, void 0, false, { fileName: "/Users/justin/rw/sdk/experiments/ai-stream/src/app/pages/Chat/Chat.tsx", lineNumber: 41, columnNumber: 7 }, this ), /* @__PURE__ */ jsxDEV("div", { children: reply }, void 0, false, { fileName: "/Users/justin/rw/sdk/experiments/ai-stream/src/app/pages/Chat/Chat.tsx", lineNumber: 54, columnNumber: 7 }, this) ] }, void 0, true, { fileName: "/Users/justin/rw/sdk/experiments/ai-stream/src/app/pages/Chat/Chat.tsx", lineNumber: 27, columnNumber: 5 }, this); } const Chat = registerClientReference("/test/file.tsx", "Chat", ChatSSR); export { ChatSSR, Chat }; " `); }); it("Does not transform when 'use client' is not directive", async () => { expect(await transform(`const message = "use client";`)).toMatchInlineSnapshot(` "import { registerClientReference } from "rwsdk/worker"; const message = "use client";" `); }); it("Comments are allowed in front of 'use client'", async () => { expect(await transform(`\ // Single line comment "use client" export const Component = () => { return jsx('div', { children: 'Hello' }) } `)).toMatchInlineSnapshot(` "import { registerClientReference } from "rwsdk/worker"; // Single line comment const ComponentSSR = () => { return jsx('div', { children: 'Hello' }) } const Component = registerClientReference("/test/file.tsx", "Component", ComponentSSR); export { ComponentSSR, Component }; " `); expect(await transform(`\ // Multi-line // comments "use client" export const Component = () => { return jsx('div', { children: 'Hello' }) } `)).toMatchInlineSnapshot(` "import { registerClientReference } from "rwsdk/worker"; // Multi-line // comments const ComponentSSR = () => { return jsx('div', { children: 'Hello' }) } const Component = registerClientReference("/test/file.tsx", "Component", ComponentSSR); export { ComponentSSR, Component }; " `); expect(await transform(`\ // Single line comment // With Whitespace "use client" export const Component = () => { return jsx('div', { children: 'Hello' }) } `)).toMatchInlineSnapshot(` "import { registerClientReference } from "rwsdk/worker"; // Single line comment // With Whitespace const ComponentSSR = () => { return jsx('div', { children: 'Hello' }) } const Component = registerClientReference("/test/file.tsx", "Component", ComponentSSR); export { ComponentSSR, Component }; " `); }); it("Multiline are allowed in front of 'use client'", async () => { expect(await transform(`"/* This is a multi line comment */ "use client" export const Component = () => { return jsx('div', { children: 'Hello' }); }`)).toMatchInlineSnapshot(` "import { registerClientReference } from "rwsdk/worker"; "/* This is a multi line comment */ const ComponentSSR = () => { return jsx('div', { children: 'Hello' }); } const Component = registerClientReference("/test/file.tsx", "Component", ComponentSSR); export { ComponentSSR, Component };" `); }); });