import { OverlayModule } from '@angular/cdk/overlay';
import { Component, signal } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import type { PaginationMetaModel } from '@africanies/africanies-models';
import { FilterQueryService } from '../filters/filter-query.service';
import { PaginationComponent } from './pagination.component';
@Component({
standalone: true,
imports: [PaginationComponent],
template: `
`,
})
class PaginationHostComponent {
readonly page = signal(null);
readonly size = signal(null);
readonly meta = signal({
current_page: 2,
per_page: 15,
total_items: 28,
total_pages: 2,
has_next_page: false,
has_previous_page: true,
});
}
describe('PaginationComponent', () => {
let fixture: ComponentFixture;
let host: PaginationHostComponent;
let filterQuery: { setPage: jest.Mock; setSize: jest.Mock };
beforeEach(async () => {
filterQuery = {
setPage: jest.fn().mockResolvedValue(true),
setSize: jest.fn().mockResolvedValue(true),
};
await TestBed.configureTestingModule({
imports: [PaginationHostComponent, OverlayModule],
providers: [{ provide: FilterQueryService, useValue: filterQuery }],
}).compileComponents();
fixture = TestBed.createComponent(PaginationHostComponent);
host = fixture.componentInstance;
fixture.detectChanges();
});
afterEach(() => {
fixture.destroy();
});
it('renders page status, size select, and numbered pages without a Rows label', () => {
const root = fixture.nativeElement as HTMLElement;
const text = root.textContent ?? '';
expect(text).toContain('Page 2 of 2');
expect(text).not.toContain('Rows');
expect(text).toContain('15');
expect(text).toContain('Previous');
expect(text).toContain('Next');
const pageButtons = Array.from(
root.querySelectorAll('button[aria-label^="Page "]'),
) as HTMLButtonElement[];
expect(pageButtons.map((button) => button.textContent?.trim())).toEqual([
'1',
'2',
]);
expect(
pageButtons.find((button) => button.getAttribute('aria-current') === 'page')
?.textContent?.trim(),
).toBe('2');
});
it('emits sizeChange when a size option is chosen', () => {
const trigger = (fixture.nativeElement as HTMLElement).querySelector(
'button[aria-haspopup="listbox"]',
) as HTMLButtonElement;
trigger.click();
fixture.detectChanges();
const options = Array.from(
document.querySelectorAll('[role="option"]'),
) as HTMLButtonElement[];
expect(options.map((option) => option.textContent?.trim())).toEqual([
'5',
'15',
'30',
]);
options.find((option) => option.textContent?.trim() === '30')?.click();
fixture.detectChanges();
expect(host.size()).toBe(30);
expect(filterQuery.setSize).toHaveBeenCalledWith(30);
});
it('emits pageChange from Previous when a previous page exists', () => {
const buttons = Array.from(
(fixture.nativeElement as HTMLElement).querySelectorAll(
'button[africanies-button]',
),
) as HTMLButtonElement[];
const previous = buttons.find((button) =>
button.textContent?.includes('Previous'),
);
expect(previous?.disabled).toBe(false);
previous?.click();
fixture.detectChanges();
expect(host.page()).toBe(1);
expect(filterQuery.setPage).toHaveBeenCalledWith(1);
});
it('emits pageChange from a numbered page button', () => {
const pageOne = (
fixture.nativeElement as HTMLElement
).querySelector('button[aria-label="Page 1"]') as HTMLButtonElement;
pageOne.click();
fixture.detectChanges();
expect(host.page()).toBe(1);
expect(filterQuery.setPage).toHaveBeenCalledWith(1);
});
it('windows long page lists with ellipsis', () => {
host.meta.set({
current_page: 5,
per_page: 15,
total_items: 150,
total_pages: 10,
has_next_page: true,
has_previous_page: true,
});
fixture.detectChanges();
const root = fixture.nativeElement as HTMLElement;
const pageButtons = Array.from(
root.querySelectorAll('button[aria-label^="Page "]'),
) as HTMLButtonElement[];
expect(pageButtons.map((button) => button.textContent?.trim())).toEqual([
'1',
'4',
'5',
'6',
'10',
]);
expect(root.textContent).toContain('…');
});
it('shows a spinner and disables controls while loading', async () => {
@Component({
standalone: true,
imports: [PaginationComponent],
template: `
`,
})
class LoadingHostComponent {
readonly meta = signal({
current_page: 1,
per_page: 15,
total_items: 28,
total_pages: 2,
has_next_page: true,
has_previous_page: false,
});
}
const loadingFixture = TestBed.createComponent(LoadingHostComponent);
loadingFixture.detectChanges();
const root = loadingFixture.nativeElement as HTMLElement;
const spinner = root.querySelector('[role="status"] .animate-spin');
expect(spinner).not.toBeNull();
expect(root.textContent).toContain('Loading page');
const nav = root.querySelector('nav[aria-label="Pagination"]') as HTMLElement;
expect(nav?.getAttribute('aria-busy')).toBe('true');
const next = Array.from(
root.querySelectorAll('button[africanies-button]'),
).find((button) => button.textContent?.includes('Next')) as
| HTMLButtonElement
| undefined;
expect(next?.disabled).toBe(true);
loadingFixture.destroy();
});
});