- 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>
143 lines
4.1 KiB
TypeScript
143 lines
4.1 KiB
TypeScript
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();
|
|
});
|
|
});
|
|
});
|