From 3ef012eb430a240efc1385786769a9dfa7b559bf Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Mon, 6 Apr 2026 11:34:03 +0300 Subject: [PATCH] test(UnifiedFontStore): add pagination state tests --- .../unifiedFontStore/unifiedFontStore.test.ts | 129 +++++++++++++++++- 1 file changed, 128 insertions(+), 1 deletion(-) diff --git a/src/entities/Font/model/store/unifiedFontStore/unifiedFontStore.test.ts b/src/entities/Font/model/store/unifiedFontStore/unifiedFontStore.test.ts index 44e2200..c92f0c5 100644 --- a/src/entities/Font/model/store/unifiedFontStore/unifiedFontStore.test.ts +++ b/src/entities/Font/model/store/unifiedFontStore/unifiedFontStore.test.ts @@ -28,12 +28,25 @@ vi.mock('../../../api', () => ({ })); import { queryClient } from '$shared/api/queryClient'; +import { flushSync } from 'svelte'; import { fetchProxyFonts } from '../../../api'; +import { generateMockFonts } from '../../../lib/mocks/fonts.mock'; +import type { UnifiedFont } from '../../types'; import { UnifiedFontStore } from './unifiedFontStore.svelte'; const mockedFetch = fetchProxyFonts as ReturnType; -describe('UnifiedFontStore.fetchFn error paths', () => { +const makeResponse = ( + fonts: UnifiedFont[], + meta: { total?: number; limit?: number; offset?: number } = {}, +) => ({ + fonts, + total: meta.total ?? fonts.length, + limit: meta.limit ?? 10, + offset: meta.offset ?? 0, +}); + +describe('fetchFn — error paths', () => { let store: UnifiedFontStore; beforeEach(() => { @@ -83,3 +96,117 @@ describe('UnifiedFontStore.fetchFn error paths', () => { expect((store.error as FontResponseError).received).toBe('bad'); }); }); + +describe('fetchFn — success path', () => { + let store: UnifiedFontStore; + + beforeEach(() => { + store = new UnifiedFontStore({ limit: 10 }); + }); + + afterEach(() => { + store.destroy(); + queryClient.clear(); + vi.resetAllMocks(); + }); + + it('populates fonts after a successful fetch', async () => { + const fonts = generateMockFonts(3); + mockedFetch.mockResolvedValue(makeResponse(fonts)); + await store.refetch(); + + expect(store.fonts).toHaveLength(3); + expect(store.fonts[0].id).toBe(fonts[0].id); + }); + + it('stores pagination metadata from response', async () => { + const fonts = generateMockFonts(3); + mockedFetch.mockResolvedValue(makeResponse(fonts, { total: 30, limit: 10, offset: 0 })); + await store.refetch(); + + expect(store.pagination.total).toBe(30); + expect(store.pagination.limit).toBe(10); + expect(store.pagination.offset).toBe(0); + }); + + it('replaces accumulated fonts on offset-0 fetch', async () => { + const first = generateMockFonts(3); + mockedFetch.mockResolvedValue(makeResponse(first)); + await store.refetch(); + flushSync(); + console.log('After first refetch + flushSync:', store.fonts.length); + + const second = generateMockFonts(2); + mockedFetch.mockResolvedValue(makeResponse(second)); + await store.refetch(); + console.log('After second refetch, before flushSync:', store.fonts.length, store.fonts.map(f => f.id)); + flushSync(); + console.log('After second refetch + flushSync:', store.fonts.length, store.fonts.map(f => f.id)); + + expect(store.fonts).toHaveLength(2); + expect(store.fonts[0].id).toBe(second[0].id); + }); + + it('appends fonts when fetching at offset > 0', async () => { + const firstPage = generateMockFonts(3); + mockedFetch.mockResolvedValue(makeResponse(firstPage, { total: 6, limit: 3, offset: 0 })); + await store.refetch(); + + const secondPage = generateMockFonts(3).map((f, i) => ({ + ...f, + id: `page2-font-${i + 1}`, + })); + mockedFetch.mockResolvedValue(makeResponse(secondPage, { total: 6, limit: 3, offset: 3 })); + store.setParams({ offset: 3 }); + await store.refetch(); + + expect(store.fonts).toHaveLength(6); + expect(store.fonts.slice(0, 3).map(f => f.id)).toEqual(firstPage.map(f => f.id)); + expect(store.fonts.slice(3).map(f => f.id)).toEqual(secondPage.map(f => f.id)); + }); +}); + +describe('pagination state', () => { + let store: UnifiedFontStore; + + beforeEach(() => { + store = new UnifiedFontStore({ limit: 10 }); + }); + + afterEach(() => { + store.destroy(); + queryClient.clear(); + vi.resetAllMocks(); + }); + + it('returns default pagination before any fetch', () => { + expect(store.pagination.total).toBe(0); + expect(store.pagination.hasMore).toBe(false); + expect(store.pagination.page).toBe(1); + expect(store.pagination.totalPages).toBe(0); + }); + + it('computes hasMore as true when more pages remain', async () => { + mockedFetch.mockResolvedValue(makeResponse(generateMockFonts(10), { total: 30, limit: 10, offset: 0 })); + await store.refetch(); + + expect(store.pagination.hasMore).toBe(true); + }); + + it('computes hasMore as false on last page', async () => { + mockedFetch.mockResolvedValue(makeResponse(generateMockFonts(10), { total: 20, limit: 10, offset: 10 })); + store.setParams({ offset: 10 }); + await store.refetch(); + + expect(store.pagination.hasMore).toBe(false); + }); + + it('computes page and totalPages from response metadata', async () => { + mockedFetch.mockResolvedValue(makeResponse(generateMockFonts(10), { total: 30, limit: 10, offset: 10 })); + store.setParams({ offset: 10 }); + await store.refetch(); + + expect(store.pagination.page).toBe(2); + expect(store.pagination.totalPages).toBe(3); + }); +});