✅ Add unit tests for useAsyncState composable
This commit is contained in:
@@ -0,0 +1,172 @@
|
|||||||
|
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 () => {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 100));
|
||||||
|
return 'Success';
|
||||||
|
});
|
||||||
|
|
||||||
|
const promise = execute(asyncFn);
|
||||||
|
expect(loading.value).toBe(true);
|
||||||
|
|
||||||
|
reset();
|
||||||
|
|
||||||
|
expect(loading.value).toBe(false);
|
||||||
|
await expect(promise).rejects.toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user