Files
flyer-crawler.projectium.com/src/hooks/useProfileAddress.test.ts.disabled

287 lines
9.5 KiB
Plaintext

// src/hooks/useProfileAddress.test.ts
import { renderHook, act, waitFor } from '@testing-library/react';
import { describe, it, expect, vi, beforeEach, type Mock, afterEach } from 'vitest';
import toast from 'react-hot-toast';
import { useProfileAddress } from './useProfileAddress';
import { useApi } from './useApi';
import { logger } from '../services/logger.client';
import { createMockAddress, createMockUserProfile } from '../tests/utils/mockFactories';
// Mock dependencies
vi.mock('react-hot-toast', () => ({
default: {
success: vi.fn(),
error: vi.fn(),
},
}));
vi.mock('./useApi');
vi.mock('../services/logger.client', () => ({
logger: {
debug: vi.fn(),
warn: vi.fn(),
},
}));
const mockedUseApi = vi.mocked(useApi);
const mockedToast = vi.mocked(toast);
// Mock data
const mockUserProfile = createMockUserProfile({
address_id: 1,
full_name: 'Test User',
});
const mockUserProfileNoAddress = createMockUserProfile({
...mockUserProfile,
address_id: null,
});
const mockAddress = createMockAddress({
address_id: 1,
address_line_1: '123 Main St',
city: 'Anytown',
province_state: 'CA',
postal_code: '12345',
country: 'USA',
latitude: 34.05,
longitude: -118.25,
});
describe('useProfileAddress Hook', () => {
let mockGeocode: Mock;
let mockFetchAddress: Mock;
beforeEach(() => {
vi.clearAllMocks();
mockGeocode = vi.fn();
mockFetchAddress = vi.fn();
// Robust mock implementation based on argument types.
// This handles the two useApi calls (Geocode: string input, Fetch: number input)
// without relying on unstable function names or render order.
mockedUseApi.mockImplementation(() => {
return {
execute: vi.fn(async (arg: any) => {
if (typeof arg === 'string') {
return mockGeocode(arg);
} else if (typeof arg === 'number') {
return mockFetchAddress(arg);
}
}),
loading: false,
error: null,
data: null,
reset: vi.fn(),
isRefetching: false,
};
});
});
it('should initialize with empty address and initialAddress', () => {
const { result } = renderHook(() => useProfileAddress(null, false));
expect(result.current.address).toEqual({});
expect(result.current.initialAddress).toEqual({});
expect(result.current.isGeocoding).toBe(false);
});
describe('Address Fetching Effect', () => {
it('should not fetch address if isOpen is false', () => {
renderHook(() => useProfileAddress(mockUserProfile, false));
expect(mockFetchAddress).not.toHaveBeenCalled();
});
it('should not fetch address if profile is null', () => {
renderHook(() => useProfileAddress(null, true));
expect(mockFetchAddress).not.toHaveBeenCalled();
});
it('should fetch address when isOpen and a profile with address_id are provided', async () => {
mockFetchAddress.mockResolvedValue(mockAddress);
const { result } = renderHook(() => useProfileAddress(mockUserProfile, true));
await waitFor(() => {
expect(mockFetchAddress).toHaveBeenCalledWith(mockUserProfile.address_id);
expect(result.current.address).toEqual(mockAddress);
expect(result.current.initialAddress).toEqual(mockAddress);
});
});
it('should reset address if profile has no address_id', () => {
const { result } = renderHook(() => useProfileAddress(mockUserProfileNoAddress, true));
expect(mockFetchAddress).not.toHaveBeenCalled();
expect(result.current.address).toEqual({});
expect(result.current.initialAddress).toEqual({});
});
it('should reset state when modal is closed', async () => {
mockFetchAddress.mockResolvedValue(mockAddress);
const { result, rerender } = renderHook(
({ userProfile, isOpen }) => useProfileAddress(userProfile, isOpen),
{ initialProps: { userProfile: mockUserProfile, isOpen: true } },
);
await waitFor(() => {
expect(result.current.address).toEqual(mockAddress);
});
rerender({ userProfile: mockUserProfile, isOpen: false });
expect(result.current.address).toEqual({});
expect(result.current.initialAddress).toEqual({});
});
it('should handle fetch failure gracefully', async () => {
mockFetchAddress.mockResolvedValue(null);
const { result } = renderHook(() => useProfileAddress(mockUserProfile, true));
await waitFor(() => {
expect(mockFetchAddress).toHaveBeenCalledWith(mockUserProfile.address_id);
});
expect(result.current.address).toEqual({});
expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining('Fetch returned null'));
});
});
describe('handleAddressChange', () => {
it('should update the address state field by field', () => {
const { result } = renderHook(() => useProfileAddress(null, false));
act(() => {
result.current.handleAddressChange('city', 'New York');
});
expect(result.current.address.city).toBe('New York');
act(() => {
result.current.handleAddressChange('postal_code', '10001');
});
expect(result.current.address.city).toBe('New York');
expect(result.current.address.postal_code).toBe('10001');
});
});
describe('handleManualGeocode', () => {
it('should call geocode API with the correct address string', async () => {
const { result } = renderHook(() => useProfileAddress(null, false));
act(() => {
result.current.handleAddressChange('address_line_1', '1 Infinite Loop');
result.current.handleAddressChange('city', 'Cupertino');
result.current.handleAddressChange('province_state', 'CA');
});
await act(async () => {
await result.current.handleManualGeocode();
});
expect(mockGeocode).toHaveBeenCalledWith(expect.stringContaining('1 Infinite Loop'));
});
it('should update address with new coordinates on successful geocode', async () => {
const newCoords = { lat: 37.33, lng: -122.03 };
mockGeocode.mockResolvedValue(newCoords);
const { result } = renderHook(() => useProfileAddress(null, false));
act(() => {
result.current.handleAddressChange('city', 'Cupertino');
});
await act(async () => {
await result.current.handleManualGeocode();
});
expect(result.current.address.latitude).toBe(newCoords.lat);
expect(result.current.address.longitude).toBe(newCoords.lng);
expect(mockedToast.success).toHaveBeenCalledWith('Address re-geocoded successfully!');
});
it('should show an error toast if address string is empty', async () => {
const { result } = renderHook(() => useProfileAddress(null, false));
await act(async () => {
await result.current.handleManualGeocode();
});
expect(mockGeocode).not.toHaveBeenCalled();
expect(mockedToast.error).toHaveBeenCalledWith(
'Please fill in the address fields before geocoding.',
);
});
});
describe('Automatic Geocoding (Debounce)', () => {
beforeEach(() => {
vi.useFakeTimers();
});
afterEach(() => {
vi.useRealTimers();
});
it('should trigger geocode after user stops typing in an address without coordinates', async () => {
const addressWithoutCoords = { ...mockAddress, latitude: undefined, longitude: undefined };
mockFetchAddress.mockResolvedValue(addressWithoutCoords);
const newCoords = { lat: 38.89, lng: -77.03 };
mockGeocode.mockResolvedValue(newCoords);
const { result } = renderHook(() => useProfileAddress(mockUserProfile, true));
// Wait for initial fetch
await waitFor(() => expect(result.current.address.city).toBe('Anytown'));
// Change the address
act(() => {
result.current.handleAddressChange('city', 'Washington');
});
// Geocode should not be called immediately due to debounce
expect(mockGeocode).not.toHaveBeenCalled();
// Advance debounce timer
await act(async () => {
vi.advanceTimersByTime(1600);
});
await waitFor(() => {
expect(mockGeocode).toHaveBeenCalledWith(expect.stringContaining('Washington'));
expect(result.current.address.latitude).toBe(newCoords.lat);
expect(result.current.address.longitude).toBe(newCoords.lng);
expect(mockedToast.success).toHaveBeenCalledWith('Address geocoded successfully!');
});
});
it('should NOT trigger geocode if address already has coordinates', async () => {
mockFetchAddress.mockResolvedValue(mockAddress); // Has coords
const { result } = renderHook(() => useProfileAddress(mockUserProfile, true));
await waitFor(() => expect(result.current.address.city).toBe('Anytown'));
act(() => {
result.current.handleAddressChange('city', 'NewCity');
});
await act(async () => {
vi.advanceTimersByTime(1600);
});
expect(mockGeocode).not.toHaveBeenCalled();
});
it('should NOT trigger geocode on initial load, even if address has no coords', async () => {
const addressWithoutCoords = { ...mockAddress, latitude: undefined, longitude: undefined };
mockFetchAddress.mockResolvedValue(addressWithoutCoords);
const { result } = renderHook(() => useProfileAddress(mockUserProfile, true));
await waitFor(() => expect(result.current.address.city).toBe('Anytown'));
await act(async () => {
vi.advanceTimersByTime(1600);
});
// Should not call because address hasn't changed from initial
expect(mockGeocode).not.toHaveBeenCalled();
});
});
});