import { queryClient } from '$shared/api/queryClient'; import { fontKeys } from '$shared/api/queryKeys'; import { beforeEach, describe, expect, it, vi, } from 'vitest'; import * as api from '../../api/proxy/proxyFonts'; import { FontNetworkError, FontResponseError, } from '../../lib/errors/errors'; import { BatchFontStore } from './batchFontStore.svelte'; describe('BatchFontStore', () => { beforeEach(() => { queryClient.clear(); vi.clearAllMocks(); }); describe('Fetch Behavior', () => { it('should skip fetch when initialized with empty IDs', async () => { const spy = vi.spyOn(api, 'fetchFontsByIds'); const store = new BatchFontStore([]); expect(spy).not.toHaveBeenCalled(); expect(store.fonts).toEqual([]); }); it('should fetch and seed cache for valid IDs', async () => { const fonts = [{ id: 'a', name: 'A' }] as any[]; vi.spyOn(api, 'fetchFontsByIds').mockResolvedValue(fonts); const store = new BatchFontStore(['a']); await vi.waitFor(() => expect(store.fonts).toEqual(fonts), { timeout: 1000 }); expect(queryClient.getQueryData(fontKeys.detail('a'))).toEqual(fonts[0]); }); }); describe('Loading States', () => { it('should transition through loading state', async () => { vi.spyOn(api, 'fetchFontsByIds').mockImplementation(() => new Promise(r => setTimeout(() => r([{ id: 'a' }] as any), 50)) ); const store = new BatchFontStore(['a']); expect(store.isLoading).toBe(true); await vi.waitFor(() => expect(store.isLoading).toBe(false), { timeout: 1000 }); }); }); describe('Error Handling', () => { it('should wrap network failures in FontNetworkError', async () => { vi.spyOn(api, 'fetchFontsByIds').mockRejectedValue(new Error('Network fail')); const store = new BatchFontStore(['a']); await vi.waitFor(() => expect(store.isError).toBe(true), { timeout: 1000 }); expect(store.error).toBeInstanceOf(FontNetworkError); }); it('should handle malformed API responses with FontResponseError', async () => { // Mocking a malformed response that the store should validate vi.spyOn(api, 'fetchFontsByIds').mockResolvedValue(null as any); const store = new BatchFontStore(['a']); await vi.waitFor(() => expect(store.isError).toBe(true), { timeout: 1000 }); expect(store.error).toBeInstanceOf(FontResponseError); }); it('should have null error in success state', async () => { const fonts = [{ id: 'a' }] as any[]; vi.spyOn(api, 'fetchFontsByIds').mockResolvedValue(fonts); const store = new BatchFontStore(['a']); await vi.waitFor(() => expect(store.fonts).toEqual(fonts), { timeout: 1000 }); expect(store.error).toBeNull(); }); }); describe('Disable Behavior', () => { it('should return empty fonts and not fetch when setIds is called with empty array', async () => { const fonts1 = [{ id: 'a' }] as any[]; const spy = vi.spyOn(api, 'fetchFontsByIds').mockResolvedValueOnce(fonts1); const store = new BatchFontStore(['a']); await vi.waitFor(() => expect(store.fonts).toEqual(fonts1), { timeout: 1000 }); spy.mockClear(); store.setIds([]); await vi.waitFor(() => expect(store.fonts).toEqual([]), { timeout: 1000 }); expect(spy).not.toHaveBeenCalled(); }); }); describe('Reactivity', () => { it('should refetch when setIds is called', async () => { const fonts1 = [{ id: 'a' }] as any[]; const fonts2 = [{ id: 'b' }] as any[]; vi.spyOn(api, 'fetchFontsByIds') .mockResolvedValueOnce(fonts1) .mockResolvedValueOnce(fonts2); const store = new BatchFontStore(['a']); await vi.waitFor(() => expect(store.fonts).toEqual(fonts1), { timeout: 1000 }); store.setIds(['b']); await vi.waitFor(() => expect(store.fonts).toEqual(fonts2), { timeout: 1000 }); }); }); });