diff --git a/src/app/__tests__/main-content.test.tsx b/src/app/__tests__/main-content.test.tsx new file mode 100644 index 0000000..eb36de6 --- /dev/null +++ b/src/app/__tests__/main-content.test.tsx @@ -0,0 +1,133 @@ +import { test, expect, vi, afterEach } from "vitest"; +import { render, screen, cleanup } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { MainContent } from "../main-content"; + +vi.mock("@/lib/contexts/file-system-context", () => ({ + FileSystemProvider: ({ children }: any) => <>{children}, + useFileSystem: () => ({ + getAllFiles: () => new Map(), + refreshTrigger: 0, + selectedFile: null, + setSelectedFile: vi.fn(), + }), +})); + +vi.mock("@/lib/contexts/chat-context", () => ({ + ChatProvider: ({ children }: any) => <>{children}, + useChat: () => ({ + messages: [], + input: "", + handleInputChange: vi.fn(), + handleSubmit: vi.fn(), + status: "idle", + }), +})); + +vi.mock("@/components/chat/ChatInterface", () => ({ + ChatInterface: () =>
, +})); + +vi.mock("@/components/preview/PreviewFrame", () => ({ + PreviewFrame: () =>
, +})); + +vi.mock("@/components/editor/CodeEditor", () => ({ + CodeEditor: () =>
, +})); + +vi.mock("@/components/editor/FileTree", () => ({ + FileTree: () =>
, +})); + +vi.mock("@/components/HeaderActions", () => ({ + HeaderActions: () =>
, +})); + +vi.mock("@/components/ui/resizable", () => ({ + ResizablePanelGroup: ({ children }: any) =>
{children}
, + ResizablePanel: ({ children }: any) =>
{children}
, + ResizableHandle: () =>
, +})); + +afterEach(() => { + cleanup(); + vi.clearAllMocks(); +}); + +test("shows preview view by default", () => { + render(); + + expect(screen.getByTestId("preview-frame")).toBeDefined(); + expect(screen.queryByTestId("code-editor")).toBeNull(); + expect(screen.queryByTestId("file-tree")).toBeNull(); +}); + +test("Preview tab is active by default", () => { + render(); + + const previewTab = screen.getByRole("tab", { name: "Preview" }); + const codeTab = screen.getByRole("tab", { name: "Code" }); + + expect(previewTab.getAttribute("data-state")).toBe("active"); + expect(codeTab.getAttribute("data-state")).toBe("inactive"); +}); + +test("switches to code view when Code tab is clicked", async () => { + const user = userEvent.setup(); + render(); + + expect(screen.getByTestId("preview-frame")).toBeDefined(); + + await user.click(screen.getByRole("tab", { name: "Code" })); + + expect(screen.queryByTestId("preview-frame")).toBeNull(); + expect(screen.getByTestId("code-editor")).toBeDefined(); + expect(screen.getByTestId("file-tree")).toBeDefined(); +}); + +test("Code tab becomes active after clicking it", async () => { + const user = userEvent.setup(); + render(); + + await user.click(screen.getByRole("tab", { name: "Code" })); + + const previewTab = screen.getByRole("tab", { name: "Preview" }); + const codeTab = screen.getByRole("tab", { name: "Code" }); + + expect(codeTab.getAttribute("data-state")).toBe("active"); + expect(previewTab.getAttribute("data-state")).toBe("inactive"); +}); + +test("switches back to preview view when Preview tab is clicked", async () => { + const user = userEvent.setup(); + render(); + + await user.click(screen.getByRole("tab", { name: "Code" })); + expect(screen.getByTestId("code-editor")).toBeDefined(); + + await user.click(screen.getByRole("tab", { name: "Preview" })); + + expect(screen.getByTestId("preview-frame")).toBeDefined(); + expect(screen.queryByTestId("code-editor")).toBeNull(); +}); + +test("can toggle between views multiple times", async () => { + const user = userEvent.setup(); + render(); + + // Start: preview + expect(screen.getByTestId("preview-frame")).toBeDefined(); + + // Switch to code + await user.click(screen.getByRole("tab", { name: "Code" })); + expect(screen.getByTestId("code-editor")).toBeDefined(); + + // Switch back to preview + await user.click(screen.getByRole("tab", { name: "Preview" })); + expect(screen.getByTestId("preview-frame")).toBeDefined(); + + // Switch to code again + await user.click(screen.getByRole("tab", { name: "Code" })); + expect(screen.getByTestId("code-editor")).toBeDefined(); +}); diff --git a/src/components/preview/PreviewFrame.tsx b/src/components/preview/PreviewFrame.tsx index f6a8e5c..530a1e7 100644 --- a/src/components/preview/PreviewFrame.tsx +++ b/src/components/preview/PreviewFrame.tsx @@ -13,18 +13,13 @@ export function PreviewFrame() { const { getAllFiles, refreshTrigger } = useFileSystem(); const [error, setError] = useState(null); const [entryPoint, setEntryPoint] = useState("/App.jsx"); - const [isFirstLoad, setIsFirstLoad] = useState(true); + const isFirstLoadRef = useRef(true); useEffect(() => { const updatePreview = () => { try { const files = getAllFiles(); - // Clear error first when we have files - if (files.size > 0 && error) { - setError(null); - } - // Find the entry point - look for App.jsx, App.tsx, index.jsx, or index.tsx let foundEntryPoint = entryPoint; const possibleEntries = [ @@ -54,7 +49,7 @@ export function PreviewFrame() { } if (files.size === 0) { - if (isFirstLoad) { + if (isFirstLoadRef.current) { setError("firstLoad"); } else { setError("No files to preview"); @@ -63,9 +58,7 @@ export function PreviewFrame() { } // We have files, so it's no longer the first load - if (isFirstLoad) { - setIsFirstLoad(false); - } + isFirstLoadRef.current = false; if (!foundEntryPoint || !files.has(foundEntryPoint)) { setError( @@ -96,7 +89,7 @@ export function PreviewFrame() { }; updatePreview(); - }, [refreshTrigger, getAllFiles, entryPoint, error, isFirstLoad]); + }, [refreshTrigger, getAllFiles, entryPoint]); if (error) { if (error === "firstLoad") {