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") {