diff --git a/frontend/src/lib/shared/primitives/Pagination.svelte.test.ts b/frontend/src/lib/shared/primitives/Pagination.svelte.test.ts new file mode 100644 index 00000000..0dbb1c84 --- /dev/null +++ b/frontend/src/lib/shared/primitives/Pagination.svelte.test.ts @@ -0,0 +1,87 @@ +import { describe, it, expect, afterEach } from 'vitest'; +import { cleanup, render } from 'vitest-browser-svelte'; +import Pagination from './Pagination.svelte'; + +afterEach(cleanup); + +const makeHref = (p: number) => `/?page=${p}`; + +describe('Pagination', () => { + it('renders nothing when totalPages is 1 or less', async () => { + render(Pagination, { props: { page: 0, totalPages: 1, makeHref } }); + + expect(document.querySelector('nav')).toBeNull(); + }); + + it('renders the nav when totalPages > 1', async () => { + render(Pagination, { props: { page: 0, totalPages: 5, makeHref } }); + + expect(document.querySelector('nav')).not.toBeNull(); + }); + + it('disables the prev control on the first page', async () => { + render(Pagination, { props: { page: 0, totalPages: 5, makeHref } }); + + const prev = document.querySelector('[data-testid="pagination-prev"]') as HTMLElement; + expect(prev.tagName).toBe('SPAN'); + }); + + it('renders the prev control as a link when not on the first page', async () => { + render(Pagination, { props: { page: 2, totalPages: 5, makeHref } }); + + const prev = document.querySelector('[data-testid="pagination-prev"]') as HTMLAnchorElement; + expect(prev.tagName).toBe('A'); + expect(prev.href).toContain('page=1'); + }); + + it('disables the next control on the last page', async () => { + render(Pagination, { props: { page: 4, totalPages: 5, makeHref } }); + + const next = document.querySelector('[data-testid="pagination-next"]') as HTMLElement; + expect(next.tagName).toBe('SPAN'); + }); + + it('renders the next control as a link when not on the last page', async () => { + render(Pagination, { props: { page: 0, totalPages: 5, makeHref } }); + + const next = document.querySelector('[data-testid="pagination-next"]') as HTMLAnchorElement; + expect(next.tagName).toBe('A'); + expect(next.href).toContain('page=1'); + }); + + it('marks the active page button with aria-current=page', async () => { + render(Pagination, { props: { page: 2, totalPages: 5, makeHref } }); + + const active = document.querySelector('[data-testid="pagination-page-3"]') as HTMLElement; + expect(active.getAttribute('aria-current')).toBe('page'); + }); + + it('renders the mobile page label', async () => { + render(Pagination, { props: { page: 1, totalPages: 5, makeHref } }); + + const label = document.querySelector('[data-testid="pagination-page-label"]'); + expect(label?.textContent).toContain('2'); + expect(label?.textContent).toContain('5'); + }); + + it('renders left ellipsis when current page is far enough from page 1', async () => { + render(Pagination, { props: { page: 7, totalPages: 10, makeHref } }); + + expect(document.querySelector('[data-testid="pagination-ellipsis-left"]')).not.toBeNull(); + }); + + it('renders right ellipsis when current page is far from last', async () => { + render(Pagination, { props: { page: 1, totalPages: 10, makeHref } }); + + expect(document.querySelector('[data-testid="pagination-ellipsis-right"]')).not.toBeNull(); + }); + + it('uses the supplied ariaLabel when provided', async () => { + render(Pagination, { + props: { page: 0, totalPages: 5, makeHref, ariaLabel: 'Custom pagination' } + }); + + const nav = document.querySelector('nav'); + expect(nav?.getAttribute('aria-label')).toBe('Custom pagination'); + }); +});