feat: Add comprehensive test suite and misc improvements
- Add frontend unit tests with Vitest for components, hooks, pages, and utilities - Add backend tests for webhooks, notifications, middleware, and edge cases - Add ForgotPassword, NotFound, and ResetPassword pages - Add migration for orphaned staff resources conversion - Add coverage directory to gitignore (generated reports) - Various bug fixes and improvements from previous work 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
142
frontend/src/hooks/__tests__/useNotifications.test.ts
Normal file
142
frontend/src/hooks/__tests__/useNotifications.test.ts
Normal file
@@ -0,0 +1,142 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { renderHook, waitFor, act } from '@testing-library/react';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import React from 'react';
|
||||
|
||||
// Mock the notifications API
|
||||
vi.mock('../../api/notifications', () => ({
|
||||
getNotifications: vi.fn(),
|
||||
getUnreadCount: vi.fn(),
|
||||
markNotificationRead: vi.fn(),
|
||||
markAllNotificationsRead: vi.fn(),
|
||||
clearAllNotifications: vi.fn(),
|
||||
}));
|
||||
|
||||
import {
|
||||
useNotifications,
|
||||
useUnreadNotificationCount,
|
||||
useMarkNotificationRead,
|
||||
useMarkAllNotificationsRead,
|
||||
useClearAllNotifications,
|
||||
} from '../useNotifications';
|
||||
import * as notificationsApi from '../../api/notifications';
|
||||
|
||||
// Create wrapper
|
||||
const createWrapper = () => {
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: { retry: false },
|
||||
mutations: { retry: false },
|
||||
},
|
||||
});
|
||||
|
||||
return function Wrapper({ children }: { children: React.ReactNode }) {
|
||||
return React.createElement(QueryClientProvider, { client: queryClient }, children);
|
||||
};
|
||||
};
|
||||
|
||||
describe('useNotifications hooks', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('useNotifications', () => {
|
||||
it('fetches notifications', async () => {
|
||||
const mockNotifications = [
|
||||
{ id: 1, verb: 'created', read: false, timestamp: '2024-01-01T00:00:00Z' },
|
||||
];
|
||||
vi.mocked(notificationsApi.getNotifications).mockResolvedValue(mockNotifications);
|
||||
|
||||
const { result } = renderHook(() => useNotifications(), {
|
||||
wrapper: createWrapper(),
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isSuccess).toBe(true);
|
||||
});
|
||||
|
||||
expect(notificationsApi.getNotifications).toHaveBeenCalledWith(undefined);
|
||||
expect(result.current.data).toEqual(mockNotifications);
|
||||
});
|
||||
|
||||
it('passes options to API', async () => {
|
||||
vi.mocked(notificationsApi.getNotifications).mockResolvedValue([]);
|
||||
|
||||
renderHook(() => useNotifications({ read: false, limit: 10 }), {
|
||||
wrapper: createWrapper(),
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(notificationsApi.getNotifications).toHaveBeenCalledWith({
|
||||
read: false,
|
||||
limit: 10,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('useUnreadNotificationCount', () => {
|
||||
it('fetches unread count', async () => {
|
||||
vi.mocked(notificationsApi.getUnreadCount).mockResolvedValue(5);
|
||||
|
||||
const { result } = renderHook(() => useUnreadNotificationCount(), {
|
||||
wrapper: createWrapper(),
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isSuccess).toBe(true);
|
||||
});
|
||||
|
||||
expect(result.current.data).toBe(5);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useMarkNotificationRead', () => {
|
||||
it('marks notification as read', async () => {
|
||||
vi.mocked(notificationsApi.markNotificationRead).mockResolvedValue(undefined);
|
||||
|
||||
const { result } = renderHook(() => useMarkNotificationRead(), {
|
||||
wrapper: createWrapper(),
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await result.current.mutateAsync(42);
|
||||
});
|
||||
|
||||
expect(notificationsApi.markNotificationRead).toHaveBeenCalled();
|
||||
expect(vi.mocked(notificationsApi.markNotificationRead).mock.calls[0][0]).toBe(42);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useMarkAllNotificationsRead', () => {
|
||||
it('marks all notifications as read', async () => {
|
||||
vi.mocked(notificationsApi.markAllNotificationsRead).mockResolvedValue(undefined);
|
||||
|
||||
const { result } = renderHook(() => useMarkAllNotificationsRead(), {
|
||||
wrapper: createWrapper(),
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await result.current.mutateAsync();
|
||||
});
|
||||
|
||||
expect(notificationsApi.markAllNotificationsRead).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('useClearAllNotifications', () => {
|
||||
it('clears all notifications', async () => {
|
||||
vi.mocked(notificationsApi.clearAllNotifications).mockResolvedValue(undefined);
|
||||
|
||||
const { result } = renderHook(() => useClearAllNotifications(), {
|
||||
wrapper: createWrapper(),
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await result.current.mutateAsync();
|
||||
});
|
||||
|
||||
expect(notificationsApi.clearAllNotifications).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user