import { describe, it, expect, vi, afterEach } from 'vitest'; import { render, screen, cleanup } from '@testing-library/svelte'; import Pager from './Pager.svelte'; afterEach(() => cleanup()); describe('Pager', () => { it('renders nothing when totalPages <= 1', () => { const { container } = render(Pager, { props: { page: 1, totalPages: 1, onChange: () => {} } }); expect(container.querySelector('nav')).toBeNull(); }); it('disables Prev on the first page and Next on the last', () => { const { rerender } = render(Pager, { props: { page: 1, totalPages: 5, onChange: () => {} } }); expect((screen.getByRole('button', { name: /prev/i }) as HTMLButtonElement).disabled).toBe(true); expect((screen.getByRole('button', { name: /next/i }) as HTMLButtonElement).disabled).toBe(false); rerender({ page: 5, totalPages: 5, onChange: () => {} }); expect((screen.getByRole('button', { name: /prev/i }) as HTMLButtonElement).disabled).toBe(false); expect((screen.getByRole('button', { name: /next/i }) as HTMLButtonElement).disabled).toBe(true); }); it('marks the current page button as aria-current', () => { render(Pager, { props: { page: 3, totalPages: 5, onChange: () => {} } }); const current = screen.getByRole('button', { name: /go to page 3/i }); expect(current.getAttribute('aria-current')).toBe('page'); }); it('fires onChange with the clicked page number', async () => { const onChange = vi.fn(); render(Pager, { props: { page: 1, totalPages: 5, onChange } }); screen.getByRole('button', { name: /go to page 3/i }).click(); expect(onChange).toHaveBeenCalledWith(3); }); it('Prev decrements and Next increments via onChange', () => { const onChange = vi.fn(); render(Pager, { props: { page: 3, totalPages: 5, onChange } }); screen.getByRole('button', { name: /prev/i }).click(); screen.getByRole('button', { name: /next/i }).click(); expect(onChange).toHaveBeenNthCalledWith(1, 2); expect(onChange).toHaveBeenNthCalledWith(2, 4); }); it('shows every page button when totalPages <= 7', () => { render(Pager, { props: { page: 4, totalPages: 7, onChange: () => {} } }); for (let n = 1; n <= 7; n++) { expect(screen.getByRole('button', { name: new RegExp(`go to page ${n}$`, 'i') })).toBeTruthy(); } }); it('collapses middle pages with ellipsis when totalPages > 7 and current is in the middle', () => { render(Pager, { props: { page: 10, totalPages: 24, onChange: () => {} } }); // First and last are always shown expect(screen.getByRole('button', { name: /go to page 1$/i })).toBeTruthy(); expect(screen.getByRole('button', { name: /go to page 24$/i })).toBeTruthy(); // Current and direct neighbours are shown expect(screen.getByRole('button', { name: /go to page 9$/i })).toBeTruthy(); expect(screen.getByRole('button', { name: /go to page 10$/i })).toBeTruthy(); expect(screen.getByRole('button', { name: /go to page 11$/i })).toBeTruthy(); // Distant pages are NOT rendered as buttons expect(screen.queryByRole('button', { name: /go to page 2$/i })).toBeNull(); expect(screen.queryByRole('button', { name: /go to page 23$/i })).toBeNull(); // Ellipsis appears on both sides const ellipses = screen.getAllByText('…'); expect(ellipses.length).toBeGreaterThanOrEqual(2); }); it('does not duplicate boundary buttons when current is near the edge', () => { render(Pager, { props: { page: 2, totalPages: 20, onChange: () => {} } }); // Each page button rendered should be unique — no duplicate "go to page 1" const first = screen.getAllByRole('button', { name: /go to page 1$/i }); expect(first.length).toBe(1); }); });