import { describe, it, expect, vi, beforeEach } from 'vitest';
import { mapConcurrent, RequestCache } from '../src/utils';
describe('Utils', () => {
describe('mapConcurrent', () => {
it('should process items concurrently respecting the limit', async () => {
const items = [1, 2, 3, 4, 5];
let running = 0;
let maxRunning = 0;
const mapper = async (item: number) => {
running++;
maxRunning = Math.max(maxRunning, running);
await new Promise(r => setTimeout(r, 10));
running--;
return item * 2;
};
const results = await mapConcurrent(items, 2, mapper);
expect(results).toEqual([2, 4, 6, 8, 10]);
expect(maxRunning).toBeLessThanOrEqual(2);
});
it('should handle errors gracefully', async () => {
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
const items = [1, 2, 3];
const mapper = async (item: number) => {
if (item === 2) throw new Error('Boom');
return item;
};
// We cast to any because our implementation returns null on error but TS expects R
const results = await mapConcurrent(items, 2, mapper) as any[];
consoleSpy.mockRestore();
expect(results[0]).toBe(1);
expect(results[1]).toBeNull();
expect(results[2]).toBe(3);
});
});
describe('RequestCache', () => {
let cache: RequestCache;
beforeEach(() => {
cache = new RequestCache(100); // 100ms TTL
});
it('should return cached value if fresh', async () => {
const fetcher = vi.fn().mockResolvedValue('data');
await cache.getOrFetch('key', fetcher);
const val2 = await cache.getOrFetch('key', fetcher);
expect(val2).toBe('data');
expect(fetcher).toHaveBeenCalledTimes(1);
});
it('should refetch if expired', async () => {
const fetcher = vi.fn().mockResolvedValue('data');
await cache.getOrFetch('key', fetcher);
// Wait for expiration
await new Promise(r => setTimeout(r, 110));
await cache.getOrFetch('key', fetcher);
expect(fetcher).toHaveBeenCalledTimes(2);
});
it('should handle fetch errors', async () => {
const fetcher = vi.fn().mockRejectedValue(new Error('Fail'));
await expect(cache.getOrFetch('key', fetcher)).rejects.toThrow('Fail');
// Should retry next time, not cache the error promise
fetcher.mockResolvedValue('Success');
const val = await cache.getOrFetch('key', fetcher);
expect(val).toBe('Success');
});
});
});