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: 1 addition & 2 deletions coderabbit.yaml → .coderabbit.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
language: "en-US"
early_access: true
reviews:
profile: "chill"
profile: assertive # Profile defaults to assertive, which is good for diagrams
request_changes_workflow: false
Comment thread
coderabbitai[bot] marked this conversation as resolved.
high_level_summary: true
sequence_diagrams: true
Expand Down
107 changes: 107 additions & 0 deletions apps/web/src/layouts/AdminLayout.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { describe, it, expect, vi, beforeEach, type Mock } from 'vitest';
import { render, screen, waitFor } from '@testing-library/react';
import { AdminLayout } from './AdminLayout';
import { useAuth } from '../lib/auth/context';
import { useNavigate, useLocation } from 'react-router-dom';

// Mock Dependencies
vi.mock('../lib/auth/context', () => ({
useAuth: vi.fn(),
}));

vi.mock('react-router-dom', () => ({
useNavigate: vi.fn(),
useLocation: vi.fn(),
Outlet: () => <div data-testid="outlet">Child Content</div>, // Mock Outlet
Link: ({ to, children }: { to: string; children: React.ReactNode }) => <a href={to}>{children}</a>,
}));

// Mock UI components that might cause issues or are heavy
vi.mock('@/components/ui/sheet', () => ({
Sheet: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
SheetTrigger: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
SheetContent: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
}));

vi.mock('@/components/ui/dropdown-menu', () => ({
DropdownMenu: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
DropdownMenuTrigger: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
DropdownMenuContent: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
DropdownMenuItem: ({ children, onClick }: { children: React.ReactNode, onClick?: () => void }) => <div onClick={onClick}>{children}</div>,
DropdownMenuLabel: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
DropdownMenuSeparator: () => <hr />,
}));


describe('AdminLayout', () => {
const mockNavigate = vi.fn();
const mockLogout = vi.fn();

beforeEach(() => {
vi.clearAllMocks();
(useNavigate as unknown as Mock).mockReturnValue(mockNavigate);
(useLocation as unknown as Mock).mockReturnValue({ pathname: '/admin' });
});

it('renders loading state', () => {
(useAuth as unknown as Mock).mockReturnValue({
isLoading: true,
isAuthenticated: false,
user: null,
logout: mockLogout
});

render(<AdminLayout />);
expect(screen.getByText('Loading Admin Panel...')).toBeInTheDocument();
});

it('redirects to login if not authenticated', async () => {
(useAuth as unknown as Mock).mockReturnValue({
isLoading: false,
isAuthenticated: false, // Not auth
user: null,
logout: mockLogout
});

render(<AdminLayout />);

// Wait for useEffect
await waitFor(() => {
expect(mockNavigate).toHaveBeenCalledWith('/login');
});
});

it('redirects to dashboard if user is not platform_admin', async () => {
(useAuth as unknown as Mock).mockReturnValue({
isLoading: false,
isAuthenticated: true,
user: { name: 'User', systemRole: 'user' }, // Wrong role
logout: mockLogout
});

render(<AdminLayout />);

await waitFor(() => {
expect(mockNavigate).toHaveBeenCalledWith('/dashboard');
});

// Access denied message might behave briefly
expect(screen.getByText('Access denied.')).toBeInTheDocument();
});

it('renders content when user is platform_admin', () => {
(useAuth as unknown as Mock).mockReturnValue({
isLoading: false,
isAuthenticated: true,
user: { name: 'Admin', systemRole: 'platform_admin' },
logout: mockLogout
});

render(<AdminLayout />);

expect(screen.getByTestId('outlet')).toBeInTheDocument();
// It renders twice (Desktop + Mobile sidebars)
expect(screen.getAllByText('Admin Console')).toHaveLength(2);
expect(screen.getAllByText('Admin')).toHaveLength(3); // Desktop + Mobile + Breadcrumb
});
});
125 changes: 125 additions & 0 deletions apps/web/src/layouts/TenantLayout.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { describe, it, expect, vi, beforeEach, type Mock } from 'vitest';
import { render, screen, waitFor } from '@testing-library/react';
import { TenantLayout } from './TenantLayout';
import { useAuth } from '@/lib/auth/context';
import { useNavigate, useLocation } from 'react-router-dom';

// Mock Dependencies
vi.mock('@/lib/auth/context', () => ({
useAuth: vi.fn(),
}));

vi.mock('react-router-dom', () => ({
useNavigate: vi.fn(),
useLocation: vi.fn(),
Outlet: () => <div data-testid="outlet">Tenant Content</div>,
Link: ({ to, children }: { to: string; children: React.ReactNode }) => <a href={to}>{children}</a>,
useParams: () => ({ tenantId: 'org-123' }),
}));

// Mock UI components
vi.mock('@/components/ui/sheet', () => ({
Sheet: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
SheetTrigger: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
SheetContent: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
}));

vi.mock('@/components/ui/dropdown-menu', () => ({
DropdownMenu: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
DropdownMenuTrigger: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
DropdownMenuContent: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
DropdownMenuItem: ({ children, onClick }: { children: React.ReactNode, onClick?: () => void }) => <div onClick={onClick}>{children}</div>,
DropdownMenuLabel: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
DropdownMenuSeparator: () => <hr />,
}));

vi.mock('@/components/ui/avatar', () => ({
Avatar: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
AvatarImage: () => <img alt="avatar" />,
AvatarFallback: () => <span>A</span>,
}));


describe('TenantLayout', () => {
const mockNavigate = vi.fn();
const mockLogout = vi.fn();

// Sample nav groups for testing
const mockNavGroups = [
{
title: 'Main',
items: [
{ label: 'Dashboard', href: '/org-123/dashboard', icon: () => <svg /> },
{ label: 'Settings', href: '/org-123/settings', icon: () => <svg /> }
]
}
];

beforeEach(() => {
vi.clearAllMocks();
(useNavigate as unknown as Mock).mockReturnValue(mockNavigate);
(useLocation as unknown as Mock).mockReturnValue({ pathname: '/org-123/dashboard' });
});

it('renders loading state', () => {
(useAuth as unknown as Mock).mockReturnValue({
isLoading: true,
isAuthenticated: false,
user: null,
logout: mockLogout
});

render(<TenantLayout navGroups={mockNavGroups} />);
expect(screen.getByText('Loading...')).toBeInTheDocument();
});

it('redirects to login if not authenticated', async () => {
(useAuth as unknown as Mock).mockReturnValue({
isLoading: false,
isAuthenticated: false,
user: null,
logout: mockLogout
});

render(<TenantLayout navGroups={mockNavGroups} />);

await waitFor(() => {
expect(mockNavigate).toHaveBeenCalledWith('/login');
});
});

it('renders correctly for authenticated user', async () => {
(useAuth as unknown as Mock).mockReturnValue({
isLoading: false,
isAuthenticated: true,
user: { name: 'Tenant User', email: 'user@org.com', organizationName: 'Acme Corp' },
logout: mockLogout
});

render(<TenantLayout navGroups={mockNavGroups} />);

expect(screen.getByTestId('outlet')).toBeInTheDocument();
expect(screen.getAllByText('Dashboard')).toHaveLength(3); // Sidebar x2 + Breadcrumb
});

it('displays user profile info', () => {
(useAuth as unknown as Mock).mockReturnValue({
isLoading: false,
isAuthenticated: true,
user: { name: 'John Doe', email: 'john@example.com', organizationName: 'Acme Corp' },
logout: mockLogout
});

render(<TenantLayout navGroups={mockNavGroups} />);

// It renders twice (Desktop + Mobile sidebars)
// Wait, sidebar has user name? NO, sidebar shows Org Name. Dropdown shows User Name.
// Let's re-read TenantLayout.tsx carefully.
// SidebarContent: line 56 shows user.organizationName.
// Topbar Dropdown: Line 193 shows user.name.

expect(screen.getByText('John Doe')).toBeInTheDocument();
expect(screen.getByText('john@example.com')).toBeInTheDocument();
expect(screen.getAllByText('Acme Corp')).toHaveLength(2); // Desktop sidebar + Mobile sidebar
});
});
13 changes: 13 additions & 0 deletions apps/web/src/pages/DashboardPage.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { describe, it, expect } from 'vitest';
import { render, screen } from '@testing-library/react';
import { DashboardPage } from './DashboardPage';

describe('DashboardPage', () => {
it('renders dashboard stats', () => {
render(<DashboardPage />);
expect(screen.getByText('Total Members')).toBeInTheDocument();
expect(screen.getByText('Active Sessions')).toBeInTheDocument();
expect(screen.getByText('Security Score')).toBeInTheDocument();
expect(screen.getByText('Activity Feed')).toBeInTheDocument();
});
});
55 changes: 55 additions & 0 deletions apps/web/src/pages/LandingPage.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { describe, it, expect, vi, beforeEach, type Mock } from 'vitest';
import { render, screen, fireEvent } from '@testing-library/react';
import { LandingPage } from './LandingPage';
import { useAuth } from '../hooks/useAuth';
import { useNavigate } from 'react-router-dom';

// Mock Dependencies
vi.mock('../hooks/useAuth', () => ({
useAuth: vi.fn(),
}));

vi.mock('react-router-dom', () => ({
useNavigate: vi.fn(),
}));

describe('LandingPage', () => {
const mockNavigate = vi.fn();
const mockLogin = vi.fn();
const mockSignup = vi.fn();

beforeEach(() => {
vi.clearAllMocks();
(useNavigate as unknown as Mock).mockReturnValue(mockNavigate);
(useAuth as unknown as Mock).mockReturnValue({
isAuthenticated: false,
login: mockLogin,
signup: mockSignup,
});
});

it('renders landing page content', () => {
render(<LandingPage />);
expect(screen.getByRole('heading', { name: 'Nexiom' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Login' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Sign Up' })).toBeInTheDocument();
});

it('navigates to dashboard if authenticated', () => {
(useAuth as unknown as Mock).mockReturnValue({
isAuthenticated: true,
});
render(<LandingPage />);
expect(mockNavigate).toHaveBeenCalledWith('/dashboard', { replace: true });
expect(screen.getByText('Redirecting to Dashboard...')).toBeInTheDocument();
});

it('triggers login and signup', () => {
render(<LandingPage />);
fireEvent.click(screen.getByRole('button', { name: 'Login' }));
expect(mockLogin).toHaveBeenCalled();

fireEvent.click(screen.getByRole('button', { name: 'Sign Up' }));
expect(mockSignup).toHaveBeenCalled();
});
});
Loading