const { paginate, parsePaginationParams, DEFAULT_LIMIT, MAX_LIMIT } = require('../pagination'); describe('Pagination — DashCaddy list endpoints', () => { describe('parsePaginationParams', () => { it('returns null when no pagination params (backward compat — full list)', () => { expect(parsePaginationParams({})).toBeNull(); expect(parsePaginationParams({ search: 'plex' })).toBeNull(); }); it('parses page and limit from query', () => { const params = parsePaginationParams({ page: '2', limit: '10' }); expect(params).toEqual({ page: 2, limit: 10 }); }); it('defaults page to 1', () => { expect(parsePaginationParams({ limit: '25' })).toEqual({ page: 1, limit: 25 }); }); it('defaults limit to DEFAULT_LIMIT when only page given', () => { expect(parsePaginationParams({ page: '3' })).toEqual({ page: 3, limit: DEFAULT_LIMIT }); }); it('clamps page to minimum 1', () => { expect(parsePaginationParams({ page: '0' }).page).toBe(1); expect(parsePaginationParams({ page: '-5' }).page).toBe(1); }); it('treats limit 0 as default (parseInt falsy → DEFAULT_LIMIT)', () => { expect(parsePaginationParams({ limit: '0' }).limit).toBe(DEFAULT_LIMIT); }); it('clamps negative limit to minimum 1', () => { expect(parsePaginationParams({ limit: '-10' }).limit).toBe(1); }); it('clamps limit to MAX_LIMIT', () => { expect(parsePaginationParams({ limit: '9999' }).limit).toBe(MAX_LIMIT); }); it('handles NaN gracefully', () => { const params = parsePaginationParams({ page: 'abc', limit: 'xyz' }); expect(params.page).toBe(1); expect(params.limit).toBe(DEFAULT_LIMIT); }); }); describe('paginate', () => { const items = Array.from({ length: 55 }, (_, i) => ({ id: `svc-${i + 1}` })); it('returns all items when params is null (no pagination)', () => { const result = paginate(items, null); expect(result.data).toHaveLength(55); expect(result.pagination).toBeUndefined(); }); it('returns first page correctly', () => { const result = paginate(items, { page: 1, limit: 10 }); expect(result.data).toHaveLength(10); expect(result.data[0].id).toBe('svc-1'); expect(result.pagination.page).toBe(1); expect(result.pagination.total).toBe(55); expect(result.pagination.totalPages).toBe(6); expect(result.pagination.hasMore).toBe(true); }); it('returns last page with fewer items', () => { const result = paginate(items, { page: 6, limit: 10 }); expect(result.data).toHaveLength(5); // 55 - 50 = 5 remaining expect(result.data[0].id).toBe('svc-51'); expect(result.pagination.hasMore).toBe(false); }); it('returns empty array for page beyond total', () => { const result = paginate(items, { page: 100, limit: 10 }); expect(result.data).toHaveLength(0); expect(result.pagination.hasMore).toBe(false); }); it('handles empty list', () => { const result = paginate([], { page: 1, limit: 10 }); expect(result.data).toHaveLength(0); expect(result.pagination.total).toBe(0); expect(result.pagination.totalPages).toBe(0); }); it('single-page result when limit exceeds total', () => { const result = paginate(items, { page: 1, limit: 100 }); expect(result.data).toHaveLength(55); expect(result.pagination.totalPages).toBe(1); expect(result.pagination.hasMore).toBe(false); }); }); describe('Real DashCaddy scenario: 52 app templates paginated', () => { const templates = Array.from({ length: 52 }, (_, i) => ({ id: `app-${i}`, name: `App ${i}`, category: i < 10 ? 'Media' : 'Utilities' })); it('default limit (50) shows first 50 apps with hasMore', () => { const params = parsePaginationParams({ page: '1' }); const result = paginate(templates, params); expect(result.data).toHaveLength(50); expect(result.pagination.hasMore).toBe(true); }); it('page 2 shows remaining 2 apps', () => { const params = parsePaginationParams({ page: '2' }); const result = paginate(templates, params); expect(result.data).toHaveLength(2); expect(result.pagination.hasMore).toBe(false); }); }); });