import { describe, it, expect, vi, beforeEach } from 'vitest'; import { useAsyncState } from '@/composables/useAsyncState'; describe('useAsyncState', () => { beforeEach(() => { vi.clearAllMocks(); }); describe('initial state', () => { it('should have correct initial values', () => { const { loading, error, data, isSuccess, isError, isIdle } = useAsyncState(); expect(loading.value).toBe(false); expect(error.value).toBe(null); expect(data.value).toBe(null); expect(isSuccess.value).toBe(false); expect(isError.value).toBe(false); expect(isIdle.value).toBe(true); }); it('should use provided initial data', () => { const initialData = { test: 'data' }; const { data } = useAsyncState({ initialData }); expect(data.value).toEqual(initialData); }); }); describe('execute', () => { it('should execute async function and update data', async () => { const { execute, loading, data, isSuccess } = useAsyncState(); const mockData = { result: 'success' }; const asyncFn = vi.fn().mockResolvedValue(mockData); expect(loading.value).toBe(false); const promise = execute(asyncFn); expect(loading.value).toBe(true); const result = await promise; expect(loading.value).toBe(false); expect(data.value).toEqual(mockData); expect(result).toEqual(mockData); expect(isSuccess.value).toBe(true); expect(asyncFn).toHaveBeenCalledTimes(1); }); it('should handle errors and update error state', async () => { const { execute, loading, error, isError } = useAsyncState(); const mockError = new Error('Test error'); const asyncFn = vi.fn().mockRejectedValue(mockError); await expect(execute(asyncFn)).rejects.toThrow('Test error'); expect(loading.value).toBe(false); expect(error.value).toBe(mockError); expect(isError.value).toBe(true); }); it('should call onSuccess callback', async () => { const onSuccess = vi.fn(); const { execute } = useAsyncState({ onSuccess }); const mockData = { result: 'success' }; await execute(async () => mockData); expect(onSuccess).toHaveBeenCalledWith(mockData); expect(onSuccess).toHaveBeenCalledTimes(1); }); it('should call onError callback', async () => { const onError = vi.fn(); const { execute } = useAsyncState({ onError }); const mockError = new Error('Test error'); await expect( execute(async () => { throw mockError; }) ).rejects.toThrow(); expect(onError).toHaveBeenCalledWith(mockError); expect(onError).toHaveBeenCalledTimes(1); }); }); describe('retry logic', () => { it('should retry failed operations', async () => { const { execute } = useAsyncState({ maxRetries: 2, retryDelay: 10 }); const asyncFn = vi .fn() .mockRejectedValueOnce(new Error('First failure')) .mockRejectedValueOnce(new Error('Second failure')) .mockResolvedValueOnce('Success'); const result = await execute(asyncFn); expect(result).toBe('Success'); expect(asyncFn).toHaveBeenCalledTimes(3); // Initial + 2 retries }); it('should fail after max retries exhausted', async () => { const { execute } = useAsyncState({ maxRetries: 2, retryDelay: 10 }); const asyncFn = vi .fn() .mockRejectedValue(new Error('Persistent failure')); await expect(execute(asyncFn)).rejects.toThrow('Persistent failure'); expect(asyncFn).toHaveBeenCalledTimes(3); // Initial + 2 retries }); it('should respect custom retry count per execution', async () => { const { execute } = useAsyncState({ retryDelay: 10 }); const asyncFn = vi .fn() .mockRejectedValueOnce(new Error('First')) .mockResolvedValueOnce('Success'); const result = await execute(asyncFn, { retries: 1 }); expect(result).toBe('Success'); expect(asyncFn).toHaveBeenCalledTimes(2); }); }); describe('cancel', () => { it('should cancel ongoing operation', async () => { const { execute, cancel, loading } = useAsyncState(); const asyncFn = vi.fn(async signal => { await new Promise(resolve => setTimeout(resolve, 100)); if (signal?.aborted) throw new Error('Aborted'); return 'Success'; }); const promise = execute(asyncFn); expect(loading.value).toBe(true); cancel(); expect(loading.value).toBe(false); await expect(promise).rejects.toThrow(); }); }); describe('reset', () => { it('should reset all state to initial values', async () => { const initialData = 'initial'; const { execute, reset, data, error, loading } = useAsyncState({ initialData }); await execute(async () => 'new data'); expect(data.value).toBe('new data'); reset(); expect(data.value).toBe(initialData); expect(error.value).toBe(null); expect(loading.value).toBe(false); }); it('should cancel ongoing operation when reset', async () => { const { execute, reset, loading } = useAsyncState(); const asyncFn = vi.fn(async (signal) => { await new Promise(resolve => setTimeout(resolve, 100)); if (signal?.aborted) throw new Error('Aborted'); return 'Success'; }); const promise = execute(asyncFn); expect(loading.value).toBe(true); reset(); expect(loading.value).toBe(false); await expect(promise).rejects.toThrow(); }); }); });