From 10f4781a67735cc5f76bfd5a6d5a509ce3ec98a3 Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Wed, 15 Apr 2026 15:59:01 +0300 Subject: [PATCH] test: enrich coverage for queryKeys, BaseQueryStore, and BatchFontStore - queryKeys: add mutation-safety test for batch(), key hierarchy tests (list/batch/detail keys rooted in their parent base keys), and unique-key test for different detail IDs - BaseQueryStore: add initial-state test (data undefined, isError false before any fetch resolves) - BatchFontStore: add FontResponseError type assertion on malformed response, null error assertion on success, and setIds([]) disables query and returns empty fonts without triggering a fetch --- .../Font/model/store/batchFontStore.test.ts | 30 ++++++++++++++++++- src/shared/api/queryKeys.test.ts | 25 ++++++++++++++++ src/shared/lib/helpers/BaseQueryStore.test.ts | 6 ++++ 3 files changed, 60 insertions(+), 1 deletion(-) diff --git a/src/entities/Font/model/store/batchFontStore.test.ts b/src/entities/Font/model/store/batchFontStore.test.ts index 31ac16f..2045d9d 100644 --- a/src/entities/Font/model/store/batchFontStore.test.ts +++ b/src/entities/Font/model/store/batchFontStore.test.ts @@ -8,7 +8,10 @@ import { vi, } from 'vitest'; import * as api from '../../api/proxy/proxyFonts'; -import { FontNetworkError } from '../../lib/errors/errors'; +import { + FontNetworkError, + FontResponseError, +} from '../../lib/errors/errors'; import { BatchFontStore } from './batchFontStore.svelte'; describe('BatchFontStore', () => { @@ -58,6 +61,31 @@ describe('BatchFontStore', () => { 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(); }); }); diff --git a/src/shared/api/queryKeys.test.ts b/src/shared/api/queryKeys.test.ts index a7f3911..4478152 100644 --- a/src/shared/api/queryKeys.test.ts +++ b/src/shared/api/queryKeys.test.ts @@ -27,6 +27,17 @@ describe('fontKeys', () => { it('should handle empty ID arrays', () => { expect(fontKeys.batch([])).toEqual(['fonts', 'batch', []]); }); + + it('should not mutate the input array when sorting', () => { + const ids = ['c', 'b', 'a']; + fontKeys.batch(ids); + expect(ids).toEqual(['c', 'b', 'a']); + }); + + it('batch key should be rooted in batches() base', () => { + const key = fontKeys.batch(['a']); + expect(key.slice(0, 2)).toEqual(fontKeys.batches()); + }); }); describe('List Keys (Parameters)', () => { @@ -38,11 +49,25 @@ describe('fontKeys', () => { it('should handle empty parameters', () => { expect(fontKeys.list({})).toEqual(['fonts', 'list', {}]); }); + + it('list key should be rooted in lists() base', () => { + const key = fontKeys.list({ provider: 'google' }); + expect(key.slice(0, 2)).toEqual(fontKeys.lists()); + }); }); describe('Detail Keys', () => { it('should generate unique detail keys per ID', () => { expect(fontKeys.detail('roboto')).toEqual(['fonts', 'detail', 'roboto']); }); + + it('should generate different keys for different IDs', () => { + expect(fontKeys.detail('roboto')).not.toEqual(fontKeys.detail('open-sans')); + }); + + it('detail key should be rooted in details() base', () => { + const key = fontKeys.detail('roboto'); + expect(key.slice(0, 2)).toEqual(fontKeys.details()); + }); }); }); diff --git a/src/shared/lib/helpers/BaseQueryStore.test.ts b/src/shared/lib/helpers/BaseQueryStore.test.ts index 224d16c..00b8ea0 100644 --- a/src/shared/lib/helpers/BaseQueryStore.test.ts +++ b/src/shared/lib/helpers/BaseQueryStore.test.ts @@ -51,6 +51,12 @@ describe('BaseQueryStore', () => { await vi.waitFor(() => expect(store.data).toBe('ok'), { timeout: 1000 }); expect(store.isLoading).toBe(false); }); + + it('should have undefined data and no error in initial loading state', () => { + const store = new TestStore(['initial-state'], () => new Promise(r => setTimeout(() => r('late'), 500))); + expect(store.data).toBeUndefined(); + expect(store.isError).toBe(false); + }); }); describe('Error Handling', () => {