proposal-system/web/src/domain/__tests__/customers.use-cases.test.tsx
Adam Moussa 6acfdabc8c
style(web): prettier format pass (mechanical)
npx prettier --write . with the new .prettierrc (singleQuote,
printWidth 100). No functional changes — enforced by format:check in
CI from this PR on.
2026-07-13 20:14:47 -04:00

95 lines
4 KiB
TypeScript

// Customers domain use-case hooks: create/update must invalidate the list
// whole domain key family (customersKeys.all — lists AND cached search
// autocompletes; code-review finding, Phase 4) and toast. The domain api module is
// mocked — no axios traffic.
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { renderHook, waitFor } from '@testing-library/react';
import { toast } from 'react-toastify';
import { createQueryHarness } from './hookTestUtils';
import { customersKeys, useCreateCustomer, useUpdateCustomer } from '../customers/use-cases';
import { customersApi } from '../customers/api';
import type { Customer } from '../customers/types';
vi.mock('../customers/api', () => ({
customersApi: {
search: vi.fn(),
list: vi.fn(),
create: vi.fn(),
update: vi.fn(),
},
}));
vi.mock('react-toastify', () => ({
toast: { success: vi.fn(), error: vi.fn(), warning: vi.fn(), info: vi.fn() },
}));
const customer = { id: 'c-1', name: 'Acme Corp' } as unknown as Customer;
beforeEach(() => {
vi.clearAllMocks();
});
describe('customersKeys', () => {
it('list keys nest under lists() so one invalidation hits every page/filter', () => {
expect(customersKeys.all).toEqual(['customers']);
expect(customersKeys.lists()).toEqual(['customers', 'list']);
expect(customersKeys.list({ page: 3 })).toEqual(['customers', 'list', { page: 3 }]);
expect(customersKeys.search('acme')).toEqual(['customers', 'search', 'acme']);
});
});
describe('useCreateCustomer', () => {
it('invalidates the whole customers domain (lists + search caches) and toasts on success', async () => {
vi.mocked(customersApi.create).mockResolvedValue(customer);
const { queryClient, wrapper } = createQueryHarness();
const invalidateSpy = vi.spyOn(queryClient, 'invalidateQueries');
const { result } = renderHook(() => useCreateCustomer(), { wrapper });
result.current.mutate({ name: 'Acme Corp' } as never);
await waitFor(() => expect(result.current.isSuccess).toBe(true));
expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: customersKeys.all });
expect(toast.success).toHaveBeenCalledWith('Customer created');
});
it('toasts the failure and skips invalidation', async () => {
vi.mocked(customersApi.create).mockRejectedValue(new Error('duplicate name'));
const { queryClient, wrapper } = createQueryHarness();
const invalidateSpy = vi.spyOn(queryClient, 'invalidateQueries');
const { result } = renderHook(() => useCreateCustomer(), { wrapper });
result.current.mutate({ name: 'Acme Corp' } as never);
await waitFor(() => expect(result.current.isError).toBe(true));
expect(toast.error).toHaveBeenCalledWith('Failed to create customer: duplicate name');
expect(invalidateSpy).not.toHaveBeenCalled();
});
});
describe('useUpdateCustomer', () => {
it('passes id + payload through and invalidates the whole customers domain on success', async () => {
vi.mocked(customersApi.update).mockResolvedValue(customer);
const { queryClient, wrapper } = createQueryHarness();
const invalidateSpy = vi.spyOn(queryClient, 'invalidateQueries');
const { result } = renderHook(() => useUpdateCustomer(), { wrapper });
const data = { name: 'Acme Corp (Renamed)' } as never;
result.current.mutate({ id: 'c-1', data });
await waitFor(() => expect(result.current.isSuccess).toBe(true));
expect(customersApi.update).toHaveBeenCalledWith('c-1', data);
expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: customersKeys.all });
expect(toast.success).toHaveBeenCalledWith('Customer updated');
});
it('toasts the failure on update error', async () => {
vi.mocked(customersApi.update).mockRejectedValue(new Error('not found'));
const { wrapper } = createQueryHarness();
const { result } = renderHook(() => useUpdateCustomer(), { wrapper });
result.current.mutate({ id: 'c-404', data: { name: 'x' } as never });
await waitFor(() => expect(result.current.isError).toBe(true));
expect(toast.error).toHaveBeenCalledWith('Failed to update customer: not found');
});
});