Files
smoothschedule/frontend/src/hooks/__tests__/useCustomers.test.ts
poduck 8dc2248f1f 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>
2025-12-08 02:36:46 -05:00

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/');
});
});
});