- 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>
225 lines
5.8 KiB
TypeScript
225 lines
5.8 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 apiClient
|
|
vi.mock('../../api/client', () => ({
|
|
default: {
|
|
get: vi.fn(),
|
|
post: vi.fn(),
|
|
patch: vi.fn(),
|
|
delete: vi.fn(),
|
|
},
|
|
}));
|
|
|
|
import {
|
|
useCustomers,
|
|
useCreateCustomer,
|
|
useUpdateCustomer,
|
|
useDeleteCustomer,
|
|
} from '../useCustomers';
|
|
import apiClient from '../../api/client';
|
|
|
|
// 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('useCustomers hooks', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
describe('useCustomers', () => {
|
|
it('fetches customers and transforms data', async () => {
|
|
const mockCustomers = [
|
|
{
|
|
id: 1,
|
|
name: 'John Doe',
|
|
email: 'john@example.com',
|
|
phone: '555-1234',
|
|
total_spend: '150.00',
|
|
status: 'Active',
|
|
user_id: 10,
|
|
},
|
|
{
|
|
id: 2,
|
|
user: { name: 'Jane Smith', email: 'jane@example.com' },
|
|
phone: '',
|
|
total_spend: '0',
|
|
status: 'Inactive',
|
|
user: 20,
|
|
},
|
|
];
|
|
vi.mocked(apiClient.get).mockResolvedValue({ data: mockCustomers });
|
|
|
|
const { result } = renderHook(() => useCustomers(), {
|
|
wrapper: createWrapper(),
|
|
});
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.isSuccess).toBe(true);
|
|
});
|
|
|
|
expect(apiClient.get).toHaveBeenCalledWith('/customers/?');
|
|
expect(result.current.data).toHaveLength(2);
|
|
expect(result.current.data?.[0]).toEqual(expect.objectContaining({
|
|
id: '1',
|
|
name: 'John Doe',
|
|
email: 'john@example.com',
|
|
totalSpend: 150,
|
|
status: 'Active',
|
|
}));
|
|
});
|
|
|
|
it('applies status filter', async () => {
|
|
vi.mocked(apiClient.get).mockResolvedValue({ data: [] });
|
|
|
|
renderHook(() => useCustomers({ status: 'Active' }), {
|
|
wrapper: createWrapper(),
|
|
});
|
|
|
|
await waitFor(() => {
|
|
expect(apiClient.get).toHaveBeenCalledWith('/customers/?status=Active');
|
|
});
|
|
});
|
|
|
|
it('applies search filter', async () => {
|
|
vi.mocked(apiClient.get).mockResolvedValue({ data: [] });
|
|
|
|
renderHook(() => useCustomers({ search: 'john' }), {
|
|
wrapper: createWrapper(),
|
|
});
|
|
|
|
await waitFor(() => {
|
|
expect(apiClient.get).toHaveBeenCalledWith('/customers/?search=john');
|
|
});
|
|
});
|
|
|
|
it('applies multiple filters', async () => {
|
|
vi.mocked(apiClient.get).mockResolvedValue({ data: [] });
|
|
|
|
renderHook(() => useCustomers({ status: 'Blocked', search: 'test' }), {
|
|
wrapper: createWrapper(),
|
|
});
|
|
|
|
await waitFor(() => {
|
|
expect(apiClient.get).toHaveBeenCalledWith('/customers/?status=Blocked&search=test');
|
|
});
|
|
});
|
|
|
|
it('handles customers with last_visit date', async () => {
|
|
const mockCustomers = [
|
|
{
|
|
id: 1,
|
|
name: 'Customer',
|
|
email: 'c@example.com',
|
|
total_spend: '0',
|
|
last_visit: '2024-01-15T10:00:00Z',
|
|
user_id: 1,
|
|
},
|
|
];
|
|
vi.mocked(apiClient.get).mockResolvedValue({ data: mockCustomers });
|
|
|
|
const { result } = renderHook(() => useCustomers(), {
|
|
wrapper: createWrapper(),
|
|
});
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.isSuccess).toBe(true);
|
|
});
|
|
|
|
expect(result.current.data?.[0].lastVisit).toBeInstanceOf(Date);
|
|
});
|
|
});
|
|
|
|
describe('useCreateCustomer', () => {
|
|
it('creates customer with field mapping', async () => {
|
|
vi.mocked(apiClient.post).mockResolvedValue({ data: { id: 1 } });
|
|
|
|
const { result } = renderHook(() => useCreateCustomer(), {
|
|
wrapper: createWrapper(),
|
|
});
|
|
|
|
await act(async () => {
|
|
await result.current.mutateAsync({
|
|
userId: '5',
|
|
phone: '555-9999',
|
|
city: 'Denver',
|
|
state: 'CO',
|
|
zip: '80202',
|
|
status: 'Active',
|
|
});
|
|
});
|
|
|
|
expect(apiClient.post).toHaveBeenCalledWith('/customers/', {
|
|
user: 5,
|
|
phone: '555-9999',
|
|
city: 'Denver',
|
|
state: 'CO',
|
|
zip: '80202',
|
|
status: 'Active',
|
|
avatar_url: undefined,
|
|
tags: undefined,
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('useUpdateCustomer', () => {
|
|
it('updates customer with mapped fields', async () => {
|
|
vi.mocked(apiClient.patch).mockResolvedValue({ data: {} });
|
|
|
|
const { result } = renderHook(() => useUpdateCustomer(), {
|
|
wrapper: createWrapper(),
|
|
});
|
|
|
|
await act(async () => {
|
|
await result.current.mutateAsync({
|
|
id: '1',
|
|
updates: {
|
|
phone: '555-0000',
|
|
status: 'Blocked',
|
|
tags: ['vip'],
|
|
},
|
|
});
|
|
});
|
|
|
|
expect(apiClient.patch).toHaveBeenCalledWith('/customers/1/', {
|
|
phone: '555-0000',
|
|
city: undefined,
|
|
state: undefined,
|
|
zip: undefined,
|
|
status: 'Blocked',
|
|
avatar_url: undefined,
|
|
tags: ['vip'],
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('useDeleteCustomer', () => {
|
|
it('deletes customer by id', async () => {
|
|
vi.mocked(apiClient.delete).mockResolvedValue({});
|
|
|
|
const { result } = renderHook(() => useDeleteCustomer(), {
|
|
wrapper: createWrapper(),
|
|
});
|
|
|
|
await act(async () => {
|
|
await result.current.mutateAsync('7');
|
|
});
|
|
|
|
expect(apiClient.delete).toHaveBeenCalledWith('/customers/7/');
|
|
});
|
|
});
|
|
});
|