theo-agent-dashboard

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

App.test.tsx (8689B)


      1 import { describe, it, expect, vi, beforeEach } from "vitest";
      2 import { render, screen, waitFor, fireEvent } from "@testing-library/react";
      3 import { App } from "./App";
      4 import { useAuthStore } from "./store/auth";
      5 import { useThemeStore } from "./store/theme";
      6 
      7 vi.mock("./components/Login/Login", () => ({
      8   Login: () => <div data-testid="login-page">Login Page</div>,
      9 }));
     10 vi.mock("./components/Setup/Setup", () => ({
     11   Setup: () => <div data-testid="setup-page">Setup Page</div>,
     12 }));
     13 vi.mock("./components/Chat/ChatView", () => ({
     14   ChatView: () => <div data-testid="chat-view">Chat View</div>,
     15 }));
     16 vi.mock("./components/Sidebar/Sidebar", () => ({
     17   Sidebar: () => <div data-testid="sidebar">Sidebar</div>,
     18 }));
     19 vi.mock("./components/Search/SearchModal", () => ({
     20   SearchModal: ({ isOpen, onClose }: { isOpen: boolean; onClose: () => void }) =>
     21     isOpen ? (
     22       <div data-testid="search-modal">
     23         <button onClick={onClose}>Close Search</button>
     24       </div>
     25     ) : null,
     26 }));
     27 vi.mock("./components/Theme/ThemeToggle", () => ({
     28   ThemeToggle: () => <button data-testid="theme-toggle">Theme</button>,
     29 }));
     30 
     31 function mockFetch(response: unknown) {
     32   vi.stubGlobal("fetch", vi.fn().mockResolvedValue({
     33     ok: true,
     34     json: async () => response,
     35   }));
     36 }
     37 
     38 beforeEach(() => {
     39   vi.restoreAllMocks();
     40   useThemeStore.setState({ theme: "dark" });
     41   // Create fresh store to reset all actions
     42   useAuthStore.setState({
     43     authenticated: false,
     44     configured: false,
     45     loading: false,
     46     error: null,
     47   });
     48   mockFetch({ configured: true, authenticated: false });
     49 });
     50 
     51 describe("App", () => {
     52   it("shows loading state initially then transitions", async () => {
     53     vi.stubGlobal("fetch", vi.fn().mockReturnValue(new Promise(() => {})));
     54     useAuthStore.setState({ loading: true });
     55     render(<App />);
     56     expect(screen.getByText("Loading...")).toBeInTheDocument();
     57   });
     58 
     59   it("shows setup page when not configured", async () => {
     60     mockFetch({ configured: false, authenticated: false });
     61     render(<App />);
     62     await waitFor(() => {
     63       expect(screen.getByTestId("setup-page")).toBeInTheDocument();
     64     });
     65   });
     66 
     67   it("shows login page when configured but not authenticated", async () => {
     68     mockFetch({ configured: true, authenticated: false });
     69     render(<App />);
     70     await waitFor(() => {
     71       expect(screen.getByTestId("login-page")).toBeInTheDocument();
     72     });
     73   });
     74 
     75   it("shows dashboard when authenticated", async () => {
     76     mockFetch({ configured: true, authenticated: true });
     77     render(<App />);
     78     await waitFor(() => {
     79       expect(screen.getByTestId("chat-view")).toBeInTheDocument();
     80     });
     81     expect(screen.getByTestId("sidebar")).toBeInTheDocument();
     82   });
     83 
     84   it("shows sidebar with Topics and Sessions", async () => {
     85     mockFetch({ configured: true, authenticated: true });
     86     render(<App />);
     87     await waitFor(() => {
     88       expect(screen.getByTestId("sidebar")).toBeInTheDocument();
     89     });
     90   });
     91 
     92   it("applies dark theme classes", async () => {
     93     mockFetch({ configured: true, authenticated: true });
     94     useThemeStore.setState({ theme: "dark" });
     95     const { container } = render(<App />);
     96     await waitFor(() => {
     97       expect(screen.getByTestId("chat-view")).toBeInTheDocument();
     98     });
     99     expect(container.querySelector(".bg-gray-950")).toBeInTheDocument();
    100   });
    101 
    102   it("applies light theme classes", async () => {
    103     mockFetch({ configured: true, authenticated: true });
    104     useThemeStore.setState({ theme: "light" });
    105     const { container } = render(<App />);
    106     await waitFor(() => {
    107       expect(screen.getByTestId("chat-view")).toBeInTheDocument();
    108     });
    109     expect(container.querySelector(".bg-gray-100")).toBeInTheDocument();
    110   });
    111 
    112   it("opens search modal with Ctrl+K", async () => {
    113     mockFetch({ configured: true, authenticated: true });
    114     render(<App />);
    115     await waitFor(() => {
    116       expect(screen.getByTestId("chat-view")).toBeInTheDocument();
    117     });
    118     expect(screen.queryByTestId("search-modal")).not.toBeInTheDocument();
    119     fireEvent.keyDown(document, { key: "k", ctrlKey: true });
    120     await waitFor(() => {
    121       expect(screen.getByTestId("search-modal")).toBeInTheDocument();
    122     });
    123   });
    124 
    125   it("closes search modal", async () => {
    126     mockFetch({ configured: true, authenticated: true });
    127     render(<App />);
    128     await waitFor(() => {
    129       expect(screen.getByTestId("chat-view")).toBeInTheDocument();
    130     });
    131     fireEvent.keyDown(document, { key: "k", ctrlKey: true });
    132     await waitFor(() => {
    133       expect(screen.getByTestId("search-modal")).toBeInTheDocument();
    134     });
    135     fireEvent.click(screen.getByText("Close Search"));
    136     await waitFor(() => {
    137       expect(screen.queryByTestId("search-modal")).not.toBeInTheDocument();
    138     });
    139   });
    140 
    141   it("opens sidebar via toggle button", async () => {
    142     mockFetch({ configured: true, authenticated: true });
    143     render(<App />);
    144     await waitFor(() => {
    145       expect(screen.getByTestId("chat-view")).toBeInTheDocument();
    146     });
    147     const toggleButton = screen.getByLabelText("Toggle sidebar");
    148     expect(toggleButton).toBeInTheDocument();
    149     fireEvent.click(toggleButton);
    150   });
    151 
    152   it("calls checkStatus on mount via fetch", async () => {
    153     const mockFetchFn = vi.fn().mockResolvedValue({
    154       ok: true,
    155       json: async () => ({ configured: true, authenticated: true }),
    156     });
    157     vi.stubGlobal("fetch", mockFetchFn);
    158     render(<App />);
    159     await waitFor(() => {
    160       expect(mockFetchFn).toHaveBeenCalled();
    161     });
    162   });
    163 
    164   it("renders ThemeToggle in authenticated mode", async () => {
    165     mockFetch({ configured: true, authenticated: true });
    166     render(<App />);
    167     await waitFor(() => {
    168       expect(screen.getByTestId("theme-toggle")).toBeInTheDocument();
    169     });
    170   });
    171 
    172   it("does not render ThemeToggle when loading", () => {
    173     vi.stubGlobal("fetch", vi.fn().mockReturnValue(new Promise(() => {})));
    174     useAuthStore.setState({ loading: true });
    175     render(<App />);
    176     expect(screen.queryByTestId("theme-toggle")).not.toBeInTheDocument();
    177   });
    178 
    179   it("does not render ThemeToggle when not configured", async () => {
    180     mockFetch({ configured: false, authenticated: false });
    181     render(<App />);
    182     await waitFor(() => {
    183       expect(screen.getByTestId("setup-page")).toBeInTheDocument();
    184     });
    185     expect(screen.queryByTestId("theme-toggle")).not.toBeInTheDocument();
    186   });
    187 
    188   it("does not render ThemeToggle when not authenticated", async () => {
    189     mockFetch({ configured: true, authenticated: false });
    190     render(<App />);
    191     await waitFor(() => {
    192       expect(screen.getByTestId("login-page")).toBeInTheDocument();
    193     });
    194     expect(screen.queryByTestId("theme-toggle")).not.toBeInTheDocument();
    195   });
    196 
    197   it("opens search on Meta+K", async () => {
    198     mockFetch({ configured: true, authenticated: true });
    199     render(<App />);
    200     await waitFor(() => {
    201       expect(screen.getByTestId("chat-view")).toBeInTheDocument();
    202     });
    203     fireEvent.keyDown(document, { key: "k", metaKey: true });
    204     await waitFor(() => {
    205       expect(screen.getByTestId("search-modal")).toBeInTheDocument();
    206     });
    207   });
    208 
    209   it("does not open search on K without modifier", async () => {
    210     mockFetch({ configured: true, authenticated: true });
    211     render(<App />);
    212     await waitFor(() => {
    213       expect(screen.getByTestId("chat-view")).toBeInTheDocument();
    214     });
    215     fireEvent.keyDown(document, { key: "k" });
    216     expect(screen.queryByTestId("search-modal")).not.toBeInTheDocument();
    217   });
    218 
    219   it("shows loading text with light theme classes", () => {
    220     vi.stubGlobal("fetch", vi.fn().mockReturnValue(new Promise(() => {})));
    221     useAuthStore.setState({ loading: true });
    222     useThemeStore.setState({ theme: "light" });
    223     const { container } = render(<App />);
    224     const loadingDiv = container.querySelector(".bg-gray-100");
    225     expect(loadingDiv).toBeInTheDocument();
    226     expect(loadingDiv?.textContent).toContain("Loading...");
    227   });
    228 
    229   it("sidebar is hidden by default", async () => {
    230     mockFetch({ configured: true, authenticated: true });
    231     const { container } = render(<App />);
    232     await waitFor(() => {
    233       expect(screen.getByTestId("chat-view")).toBeInTheDocument();
    234     });
    235     expect(container.querySelector(".-translate-x-full")).toBeInTheDocument();
    236   });
    237 
    238   it("mobile overlay appears when sidebar is open", async () => {
    239     mockFetch({ configured: true, authenticated: true });
    240     render(<App />);
    241     await waitFor(() => {
    242       expect(screen.getByTestId("chat-view")).toBeInTheDocument();
    243     });
    244     fireEvent.click(screen.getByLabelText("Toggle sidebar"));
    245     await waitFor(() => {
    246       expect(screen.getByLabelText("Toggle sidebar")).toBeInTheDocument();
    247     });
    248   });
    249 });