Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .claude/settings.local.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
"Bash(npx vite build:*)",
"Bash(git add:*)",
"Bash(git commit:*)",
"Bash(git push:*)"
"Bash(git push:*)",
"Bash(npx vitest run:*)"
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import { describe, it, expect, beforeEach, vi } from "vitest";
import { screen, waitFor } from "@testing-library/react";
import { renderWithProviders } from "../../../../test/renderWithProviders";
import Dashboard from "../index";

// --- Mock dependencies ---

vi.mock("../../../../application/hooks/useAuth", () => ({
useAuth: () => ({
userRoleName: "Admin",
isSuperAdmin: false,
activeOrganizationId: 1,
}),
}));

vi.mock("../../../../application/hooks/useActiveModule", () => ({
useActiveModule: () => ({
activeModule: "governance",
setActiveModule: vi.fn(),
}),
}));

vi.mock("../../../../application/hooks/useDashboard", () => ({
useDashboard: () => ({
dashboard: { projects_list: [] },
fetchDashboard: vi.fn(),
}),
}));

vi.mock("../../../../application/contexts/VerifyWise.context", async () => {
const React = await import("react");
const contextValue = {
setDashboardValues: vi.fn(),
setProjects: vi.fn(),
dashboardValues: {},
projects: [],
};
const VerifyWiseContext = React.createContext(contextValue);
return { VerifyWiseContext };
});

vi.mock("../../../../application/repository/project.repository", () => ({
getAllProjects: vi.fn().mockResolvedValue({ data: [] }),
}));

vi.mock("../../../../application/repository/entity.repository", () => ({
postAutoDrivers: vi.fn().mockResolvedValue({ status: 200 }),
deleteAutoDrivers: vi.fn().mockResolvedValue({ status: 200 }),
getAllEntities: vi.fn().mockResolvedValue({ data: [] }),
}));

vi.mock("../../../../application/repository/superAdmin.repository", () => ({
getOrganizations: vi.fn().mockResolvedValue({ data: { data: [] } }),
}));

vi.mock("../../../../application/tools/log.engine", () => ({
logEngine: vi.fn(),
}));

// Mock sidebar context providers as passthrough wrappers
vi.mock("../../../../application/contexts/EvalsSidebar.context", () => ({
EvalsSidebarProvider: ({ children }: { children: React.ReactNode }) => children,
}));
vi.mock("../../../../application/contexts/AIDetectionSidebar.context", () => ({
AIDetectionSidebarProvider: ({ children }: { children: React.ReactNode }) => children,
}));
vi.mock("../../../../application/contexts/ShadowAISidebar.context", () => ({
ShadowAISidebarProvider: ({ children }: { children: React.ReactNode }) => children,
}));
vi.mock("../../../../application/contexts/AIGatewaySidebar.context", () => ({
AIGatewaySidebarProvider: ({ children }: { children: React.ReactNode }) => children,
}));

// Mock child components as stub divs
vi.mock("../../../components/AppSwitcher", () => ({
default: () => <div data-testid="app-switcher" />,
}));
vi.mock("../../../components/ContextSidebar", () => ({
ContextSidebar: (props: Record<string, unknown>) => (
<div
data-testid="context-sidebar"
data-show-demo={String(props.showDemoDataButton)}
data-has-demo={String(props.hasDemoData)}
/>
),
}));
vi.mock("../../../components/ReadOnlyBanner", () => ({
default: () => <div data-testid="read-only-banner" />,
}));
vi.mock("../../../components/DemoBanner/DemoAppBanner", () => ({
default: () => <div data-testid="demo-app-banner" />,
}));
vi.mock("../../../components/Modals/StandardModal", () => ({
default: ({ children, isOpen }: { children: React.ReactNode; isOpen: boolean }) =>
isOpen ? <div data-testid="standard-modal">{children}</div> : null,
}));
vi.mock("../../../components/Toast", () => ({
default: ({ title }: { title: string }) => (
<div data-testid="toast">{title}</div>
),
}));
vi.mock("../../../components/Alert", () => ({
default: () => <div data-testid="alert" />,
}));

// Mock CSS import
vi.mock("../index.css", () => ({}));

// react-router Outlet
vi.mock("react-router", async () => {
const actual = await vi.importActual<typeof import("react-router")>("react-router");
return {
...actual,
Outlet: () => <div data-testid="outlet" />,
useLocation: () => ({ pathname: "/overview" }),
};
});

describe("Dashboard container", () => {
beforeEach(() => {
vi.clearAllMocks();
localStorage.clear();
});

it("renders without crashing", () => {
renderWithProviders(<Dashboard reloadTrigger={false} />);
});

it("renders AppSwitcher", () => {
renderWithProviders(<Dashboard reloadTrigger={false} />);
expect(screen.getByTestId("app-switcher")).toBeInTheDocument();
});

it("renders ContextSidebar", () => {
renderWithProviders(<Dashboard reloadTrigger={false} />);
expect(screen.getByTestId("context-sidebar")).toBeInTheDocument();
});

it("renders ReadOnlyBanner", () => {
renderWithProviders(<Dashboard reloadTrigger={false} />);
expect(screen.getByTestId("read-only-banner")).toBeInTheDocument();
});

it("renders Outlet for nested routes", () => {
renderWithProviders(<Dashboard reloadTrigger={false} />);
expect(screen.getByTestId("outlet")).toBeInTheDocument();
});

it("calls getAllProjects on mount", async () => {
const { getAllProjects } = await import(
"../../../../application/repository/project.repository"
);
renderWithProviders(<Dashboard reloadTrigger={false} />);
await waitFor(() => {
expect(getAllProjects).toHaveBeenCalled();
});
});

it("passes showDemoDataButton=true by default", () => {
renderWithProviders(<Dashboard reloadTrigger={false} />);
const sidebar = screen.getByTestId("context-sidebar");
expect(sidebar.getAttribute("data-show-demo")).toBe("true");
});

it("passes showDemoDataButton=false when localStorage has hideDemoDataButton", () => {
localStorage.setItem("hideDemoDataButton", "true");
renderWithProviders(<Dashboard reloadTrigger={false} />);
const sidebar = screen.getByTestId("context-sidebar");
expect(sidebar.getAttribute("data-show-demo")).toBe("false");
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { describe, it, expect, beforeEach, vi } from "vitest";
import { renderHook, act } from "@testing-library/react";
import { usePersistedViewMode } from "../usePersistedViewMode";

describe("usePersistedViewMode", () => {
beforeEach(() => {
localStorage.clear();
});

describe("initialization", () => {
it("returns 'card' as default when localStorage is empty", () => {
const { result } = renderHook(() => usePersistedViewMode("test-key"));
expect(result.current[0]).toBe("card");
});

it("reads 'table' from localStorage", () => {
localStorage.setItem("test-key", "table");
const { result } = renderHook(() => usePersistedViewMode("test-key"));
expect(result.current[0]).toBe("table");
});

it("reads 'card' from localStorage", () => {
localStorage.setItem("test-key", "card");
const { result } = renderHook(() => usePersistedViewMode("test-key"));
expect(result.current[0]).toBe("card");
});

it("ignores invalid localStorage values and uses default", () => {
localStorage.setItem("test-key", "grid");
const { result } = renderHook(() => usePersistedViewMode("test-key"));
expect(result.current[0]).toBe("card");
});

it("uses custom default value", () => {
const { result } = renderHook(() =>
usePersistedViewMode("test-key", "table")
);
expect(result.current[0]).toBe("table");
});
});

describe("update", () => {
it("setViewMode updates state and writes to localStorage", () => {
const { result } = renderHook(() => usePersistedViewMode("test-key"));
expect(result.current[0]).toBe("card");

act(() => {
result.current[1]("table");
});

expect(result.current[0]).toBe("table");
expect(localStorage.getItem("test-key")).toBe("table");
});
});

describe("error handling", () => {
it("survives localStorage.getItem throwing", () => {
const spy = vi.spyOn(Storage.prototype, "getItem").mockImplementation(() => {
throw new Error("localStorage unavailable");
});

const { result } = renderHook(() => usePersistedViewMode("test-key"));
expect(result.current[0]).toBe("card");

spy.mockRestore();
});

it("survives localStorage.setItem throwing", () => {
const spy = vi.spyOn(Storage.prototype, "setItem").mockImplementation(() => {
throw new Error("localStorage full");
});

const { result } = renderHook(() => usePersistedViewMode("test-key"));

// Should not throw
act(() => {
result.current[1]("table");
});

// State still updates even if localStorage fails
expect(result.current[0]).toBe("table");

spy.mockRestore();
});
});
});
83 changes: 83 additions & 0 deletions Clients/src/presentation/hooks/__tests__/userMap.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { describe, it, expect, beforeEach, vi } from "vitest";
import { renderHook, waitFor } from "@testing-library/react";

// Mock the entity repository before importing the hook
vi.mock("../../../application/repository/entity.repository", () => ({
getAllEntities: vi.fn(),
}));

import { useUserMap } from "../userMap";
import { getAllEntities } from "../../../application/repository/entity.repository";

const mockGetAllEntities = vi.mocked(getAllEntities);

describe("useUserMap", () => {
beforeEach(() => {
vi.clearAllMocks();
});

it("calls getAllEntities with /users on mount", () => {
mockGetAllEntities.mockResolvedValue({ data: [] });
renderHook(() => useUserMap());
expect(mockGetAllEntities).toHaveBeenCalledWith({ routeUrl: "/users" });
});

it("maps user id to 'name surname'", async () => {
mockGetAllEntities.mockResolvedValue({
data: [
{ id: 1, name: "Alice", surname: "Smith" },
{ id: 2, name: "Bob", surname: "Jones" },
],
});

const { result } = renderHook(() => useUserMap());

await waitFor(() => {
expect(result.current.users.length).toBe(2);
});

expect(result.current.userMap.get("1")).toBe("Alice Smith");
expect(result.current.userMap.get("2")).toBe("Bob Jones");
});

it("handles API error gracefully (users stays empty)", async () => {
mockGetAllEntities.mockRejectedValue(new Error("Network error"));

const { result } = renderHook(() => useUserMap());

// Wait for the effect to run and error to be caught
await waitFor(() => {
expect(mockGetAllEntities).toHaveBeenCalled();
});

expect(result.current.users).toEqual([]);
expect(result.current.userMap.size).toBe(0);
});

it("handles null/undefined response data", async () => {
mockGetAllEntities.mockResolvedValue({ data: null });

const { result } = renderHook(() => useUserMap());

await waitFor(() => {
expect(mockGetAllEntities).toHaveBeenCalled();
});

expect(result.current.users).toEqual([]);
});

it("converts numeric id to string key in map", async () => {
mockGetAllEntities.mockResolvedValue({
data: [{ id: 42, name: "Test", surname: "User" }],
});

const { result } = renderHook(() => useUserMap());

await waitFor(() => {
expect(result.current.users.length).toBe(1);
});

expect(result.current.userMap.has("42")).toBe(true);
expect(result.current.userMap.get("42")).toBe("Test User");
});
});
Loading
Loading