Adds « ‹ [1][2][3][4] › » navigation to the library. Page window slides to keep the current page in view. Prev/next/first/last controls are always rendered but disabled at their respective bounds. Also wires up karmaConfig in angular.json so FirefoxHeadless is used for tests. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
454 lines
20 KiB
TypeScript
454 lines
20 KiB
TypeScript
import { TestBed } from '@angular/core/testing';
|
||
import { provideRouter, ActivatedRoute, Router } from '@angular/router';
|
||
import { provideHttpClient } from '@angular/common/http';
|
||
import { provideHttpClientTesting } from '@angular/common/http/testing';
|
||
import { of, throwError } from 'rxjs';
|
||
import { LibraryComponent } from './library.component';
|
||
import { ImageService } from '../services/image.service';
|
||
import { routes } from '../app.routes';
|
||
|
||
function makeActivatedRoute(queryParams: Record<string, string> = {}) {
|
||
const paramMap = { get: (key: string) => queryParams[key] ?? null };
|
||
return {
|
||
snapshot: { queryParamMap: paramMap },
|
||
queryParamMap: of(paramMap),
|
||
};
|
||
}
|
||
|
||
const EMPTY_PAGE = { items: [], total: 0, limit: 24, offset: 0 };
|
||
const ONE_IMAGE = {
|
||
items: [{ id: '1', short_id: 'ShrtImg1', filename: 'a.jpg', tags: ['cat'], hash: '', mime_type: 'image/jpeg', size_bytes: 1, width: 1, height: 1, storage_key: 'ShrtImg1', thumbnail_key: null, file_url: '/api/v1/i/ShrtImg1/file', thumbnail_url: null, created_at: '' }],
|
||
total: 1, limit: 24, offset: 0,
|
||
};
|
||
const MULTI_PAGE = {
|
||
items: Array(24).fill(null).map((_, i) => ({
|
||
id: String(i + 1), short_id: `Shrt${String(i + 1).padStart(4, '0')}`, filename: `img${i + 1}.jpg`, tags: [], hash: '',
|
||
mime_type: 'image/jpeg', size_bytes: 1, width: 1, height: 1,
|
||
storage_key: `Shrt${String(i + 1).padStart(4, '0')}`, thumbnail_key: null,
|
||
file_url: `/api/v1/i/Shrt${String(i + 1).padStart(4, '0')}/file`, thumbnail_url: null, created_at: '',
|
||
})),
|
||
total: 48, limit: 24, offset: 0,
|
||
};
|
||
|
||
describe('LibraryComponent', () => {
|
||
beforeEach(async () => {
|
||
await TestBed.configureTestingModule({
|
||
imports: [LibraryComponent],
|
||
providers: [provideHttpClient(), provideHttpClientTesting(), provideRouter(routes)],
|
||
}).compileComponents();
|
||
});
|
||
|
||
it('should render image grid from service response', () => {
|
||
const fixture = TestBed.createComponent(LibraryComponent);
|
||
const imgSvc = TestBed.inject(ImageService);
|
||
spyOn(imgSvc, 'list').and.returnValue(of(ONE_IMAGE));
|
||
fixture.detectChanges();
|
||
expect((fixture.nativeElement as HTMLElement).querySelectorAll('.image-card').length).toBe(1);
|
||
});
|
||
|
||
it('should trigger new API call with tags param on filter change', () => {
|
||
const fixture = TestBed.createComponent(LibraryComponent);
|
||
const imgSvc = TestBed.inject(ImageService);
|
||
const listSpy = spyOn(imgSvc, 'list').and.returnValue(of(EMPTY_PAGE));
|
||
fixture.detectChanges();
|
||
fixture.componentInstance.applyFilter(['cat', 'funny']);
|
||
expect(listSpy).toHaveBeenCalledWith(['cat', 'funny'], jasmine.any(Number), jasmine.any(Number));
|
||
});
|
||
|
||
it('showSpinner is false initially', () => {
|
||
const fixture = TestBed.createComponent(LibraryComponent);
|
||
const imgSvc = TestBed.inject(ImageService);
|
||
spyOn(imgSvc, 'list').and.returnValue(of(EMPTY_PAGE));
|
||
fixture.detectChanges();
|
||
expect(fixture.componentInstance.showSpinner).toBeFalse();
|
||
});
|
||
|
||
it('renders 8 skeleton cards while showSpinner is true', () => {
|
||
const fixture = TestBed.createComponent(LibraryComponent);
|
||
fixture.componentInstance.showSpinner = true;
|
||
fixture.detectChanges();
|
||
const skeletons = (fixture.nativeElement as HTMLElement).querySelectorAll('.card-skeleton');
|
||
expect(skeletons.length).toBe(8);
|
||
});
|
||
|
||
it('error is false initially', () => {
|
||
const fixture = TestBed.createComponent(LibraryComponent);
|
||
const imgSvc = TestBed.inject(ImageService);
|
||
spyOn(imgSvc, 'list').and.returnValue(of(EMPTY_PAGE));
|
||
fixture.detectChanges();
|
||
expect(fixture.componentInstance.error).toBeFalse();
|
||
});
|
||
|
||
it('shows error card when error is true', () => {
|
||
const fixture = TestBed.createComponent(LibraryComponent);
|
||
const imgSvc = TestBed.inject(ImageService);
|
||
spyOn(imgSvc, 'list').and.returnValue(throwError(() => new Error('fail')));
|
||
fixture.detectChanges();
|
||
expect((fixture.nativeElement as HTMLElement).querySelector('.error-card')).not.toBeNull();
|
||
});
|
||
|
||
it('error card has retry button that calls load()', () => {
|
||
const fixture = TestBed.createComponent(LibraryComponent);
|
||
const imgSvc = TestBed.inject(ImageService);
|
||
spyOn(imgSvc, 'list').and.returnValue(throwError(() => new Error('fail')));
|
||
fixture.detectChanges();
|
||
spyOn(fixture.componentInstance, 'load');
|
||
const retryBtn = (fixture.nativeElement as HTMLElement).querySelector('.error-card .retry-btn') as HTMLButtonElement;
|
||
expect(retryBtn).not.toBeNull();
|
||
retryBtn.click();
|
||
expect(fixture.componentInstance.load).toHaveBeenCalled();
|
||
});
|
||
|
||
it('empty state contains routerLink to /upload', () => {
|
||
const fixture = TestBed.createComponent(LibraryComponent);
|
||
const imgSvc = TestBed.inject(ImageService);
|
||
spyOn(imgSvc, 'list').and.returnValue(of(EMPTY_PAGE));
|
||
fixture.detectChanges();
|
||
const link = (fixture.nativeElement as HTMLElement).querySelector('.empty-state a[href="/upload"]');
|
||
expect(link).not.toBeNull();
|
||
});
|
||
|
||
it('onImgError sets src to placeholder SVG', () => {
|
||
const fixture = TestBed.createComponent(LibraryComponent);
|
||
const imgEl = document.createElement('img');
|
||
imgEl.src = 'http://example.com/image.jpg';
|
||
const event = { target: imgEl } as unknown as Event;
|
||
fixture.componentInstance.onImgError(event);
|
||
expect(imgEl.src).toContain('data:image/svg+xml');
|
||
});
|
||
|
||
it('onImgError does not recurse when src already contains placeholder', () => {
|
||
const fixture = TestBed.createComponent(LibraryComponent);
|
||
const imgEl = document.createElement('img');
|
||
imgEl.src = 'data:image/svg+xml,placeholder';
|
||
const originalSrc = imgEl.src;
|
||
const event = { target: imgEl } as unknown as Event;
|
||
fixture.componentInstance.onImgError(event);
|
||
expect(imgEl.src).toBe(originalSrc);
|
||
});
|
||
|
||
it('pre-populates activeFilters from ?tags= query param on init', () => {
|
||
TestBed.overrideProvider(ActivatedRoute, { useValue: makeActivatedRoute({ tags: 'cat,funny' }) });
|
||
const fixture = TestBed.createComponent(LibraryComponent);
|
||
const imgSvc = TestBed.inject(ImageService);
|
||
const listSpy = spyOn(imgSvc, 'list').and.returnValue(of(EMPTY_PAGE));
|
||
fixture.detectChanges();
|
||
expect(fixture.componentInstance.activeFilters).toEqual(['cat', 'funny']);
|
||
expect(listSpy).toHaveBeenCalledWith(['cat', 'funny'], jasmine.any(Number), jasmine.any(Number));
|
||
});
|
||
|
||
it('does not set activeFilters when no ?tags= param present', () => {
|
||
TestBed.overrideProvider(ActivatedRoute, { useValue: makeActivatedRoute() });
|
||
const fixture = TestBed.createComponent(LibraryComponent);
|
||
const imgSvc = TestBed.inject(ImageService);
|
||
spyOn(imgSvc, 'list').and.returnValue(of(EMPTY_PAGE));
|
||
fixture.detectChanges();
|
||
expect(fixture.componentInstance.activeFilters).toEqual([]);
|
||
});
|
||
|
||
it('header contains a link to /tags', () => {
|
||
const fixture = TestBed.createComponent(LibraryComponent);
|
||
const imgSvc = TestBed.inject(ImageService);
|
||
spyOn(imgSvc, 'list').and.returnValue(of(EMPTY_PAGE));
|
||
fixture.detectChanges();
|
||
const link = (fixture.nativeElement as HTMLElement).querySelector('a[href="/tags"]');
|
||
expect(link).not.toBeNull();
|
||
});
|
||
|
||
// ---- Pagination: page window (T002) ----
|
||
|
||
it('pageWindow returns [1,2,3,4] on page 1 of 20', () => {
|
||
const fixture = TestBed.createComponent(LibraryComponent);
|
||
fixture.componentInstance.currentPage = 1;
|
||
fixture.componentInstance.totalPages = 20;
|
||
expect(fixture.componentInstance.pageWindow).toEqual([1, 2, 3, 4]);
|
||
});
|
||
|
||
it('pageWindow returns [17,18,19,20] on page 20 of 20', () => {
|
||
const fixture = TestBed.createComponent(LibraryComponent);
|
||
fixture.componentInstance.currentPage = 20;
|
||
fixture.componentInstance.totalPages = 20;
|
||
expect(fixture.componentInstance.pageWindow).toEqual([17, 18, 19, 20]);
|
||
});
|
||
|
||
it('pageWindow returns [6,7,8,9] on page 7 of 20', () => {
|
||
const fixture = TestBed.createComponent(LibraryComponent);
|
||
fixture.componentInstance.currentPage = 7;
|
||
fixture.componentInstance.totalPages = 20;
|
||
expect(fixture.componentInstance.pageWindow).toEqual([6, 7, 8, 9]);
|
||
});
|
||
|
||
it('pageWindow returns all pages when totalPages < 4', () => {
|
||
const fixture = TestBed.createComponent(LibraryComponent);
|
||
fixture.componentInstance.currentPage = 2;
|
||
fixture.componentInstance.totalPages = 3;
|
||
expect(fixture.componentInstance.pageWindow).toEqual([1, 2, 3]);
|
||
});
|
||
|
||
it('pageWindow returns [1] when totalPages is 1', () => {
|
||
const fixture = TestBed.createComponent(LibraryComponent);
|
||
fixture.componentInstance.currentPage = 1;
|
||
fixture.componentInstance.totalPages = 1;
|
||
expect(fixture.componentInstance.pageWindow).toEqual([1]);
|
||
});
|
||
|
||
it('numbered page buttons are rendered (T002)', () => {
|
||
const fixture = TestBed.createComponent(LibraryComponent);
|
||
const imgSvc = TestBed.inject(ImageService);
|
||
spyOn(imgSvc, 'list').and.returnValue(of(MULTI_PAGE));
|
||
fixture.detectChanges();
|
||
const pageBtns = (fixture.nativeElement as HTMLElement).querySelectorAll('.page-btn');
|
||
expect(pageBtns.length).toBe(2); // 2 total pages
|
||
expect(pageBtns[0].textContent?.trim()).toBe('1');
|
||
expect(pageBtns[1].textContent?.trim()).toBe('2');
|
||
});
|
||
|
||
it('active page button has .active class', () => {
|
||
const fixture = TestBed.createComponent(LibraryComponent);
|
||
const imgSvc = TestBed.inject(ImageService);
|
||
spyOn(imgSvc, 'list').and.returnValue(of(MULTI_PAGE));
|
||
fixture.detectChanges();
|
||
const activeBtn = (fixture.nativeElement as HTMLElement).querySelector('.page-btn.active');
|
||
expect(activeBtn).not.toBeNull();
|
||
expect(activeBtn?.textContent?.trim()).toBe('1');
|
||
});
|
||
|
||
it('goToPage() calls imageService.list with correct offset', () => {
|
||
const fixture = TestBed.createComponent(LibraryComponent);
|
||
const imgSvc = TestBed.inject(ImageService);
|
||
const listSpy = spyOn(imgSvc, 'list').and.returnValue(of(MULTI_PAGE));
|
||
fixture.detectChanges();
|
||
listSpy.calls.reset();
|
||
fixture.componentInstance.goToPage(2);
|
||
expect(listSpy).toHaveBeenCalledWith(jasmine.any(Array), jasmine.any(Number), 24);
|
||
});
|
||
|
||
it('total count renders with correct number', () => {
|
||
const fixture = TestBed.createComponent(LibraryComponent);
|
||
const imgSvc = TestBed.inject(ImageService);
|
||
spyOn(imgSvc, 'list').and.returnValue(of(MULTI_PAGE));
|
||
fixture.detectChanges();
|
||
const el = (fixture.nativeElement as HTMLElement).querySelector('.total-count');
|
||
expect(el?.textContent).toContain('48');
|
||
});
|
||
|
||
it('no pagination controls when all images fit on one page', () => {
|
||
const fixture = TestBed.createComponent(LibraryComponent);
|
||
const imgSvc = TestBed.inject(ImageService);
|
||
spyOn(imgSvc, 'list').and.returnValue(of(ONE_IMAGE));
|
||
fixture.detectChanges();
|
||
expect((fixture.nativeElement as HTMLElement).querySelector('.pagination-bar')).toBeNull();
|
||
});
|
||
|
||
it('nextPage() calls imageService.list with offset=24', () => {
|
||
const fixture = TestBed.createComponent(LibraryComponent);
|
||
const imgSvc = TestBed.inject(ImageService);
|
||
const listSpy = spyOn(imgSvc, 'list').and.returnValue(of(MULTI_PAGE));
|
||
fixture.detectChanges();
|
||
listSpy.calls.reset();
|
||
fixture.componentInstance.nextPage();
|
||
expect(listSpy).toHaveBeenCalledWith(jasmine.any(Array), jasmine.any(Number), 24);
|
||
});
|
||
|
||
it('prevPage() from page 2 calls imageService.list with offset=0', () => {
|
||
const fixture = TestBed.createComponent(LibraryComponent);
|
||
const imgSvc = TestBed.inject(ImageService);
|
||
const listSpy = spyOn(imgSvc, 'list').and.returnValue(of(MULTI_PAGE));
|
||
fixture.detectChanges();
|
||
fixture.componentInstance.currentPage = 2;
|
||
fixture.componentInstance.totalPages = 2;
|
||
listSpy.calls.reset();
|
||
fixture.componentInstance.prevPage();
|
||
expect(listSpy).toHaveBeenCalledWith(jasmine.any(Array), jasmine.any(Number), 0);
|
||
});
|
||
|
||
it('applyFilter() resets to page 1 (offset=0)', () => {
|
||
const fixture = TestBed.createComponent(LibraryComponent);
|
||
const imgSvc = TestBed.inject(ImageService);
|
||
const listSpy = spyOn(imgSvc, 'list').and.returnValue(of(MULTI_PAGE));
|
||
fixture.detectChanges();
|
||
fixture.componentInstance.currentPage = 2;
|
||
listSpy.calls.reset();
|
||
fixture.componentInstance.applyFilter(['cat']);
|
||
expect(listSpy).toHaveBeenCalledWith(['cat'], jasmine.any(Number), 0);
|
||
});
|
||
|
||
// ---- Pagination: ‹ › disabled states (T006) ----
|
||
|
||
it('prev-btn (‹) is disabled on page 1', () => {
|
||
const fixture = TestBed.createComponent(LibraryComponent);
|
||
const imgSvc = TestBed.inject(ImageService);
|
||
spyOn(imgSvc, 'list').and.returnValue(of(MULTI_PAGE));
|
||
fixture.detectChanges();
|
||
const prevBtn = (fixture.nativeElement as HTMLElement).querySelector('.prev-btn') as HTMLButtonElement;
|
||
expect(prevBtn).not.toBeNull();
|
||
expect(prevBtn.disabled).toBeTrue();
|
||
});
|
||
|
||
it('next-btn (›) is disabled on last page', () => {
|
||
TestBed.overrideProvider(ActivatedRoute, { useValue: makeActivatedRoute({ page: '2' }) });
|
||
const fixture = TestBed.createComponent(LibraryComponent);
|
||
const imgSvc = TestBed.inject(ImageService);
|
||
spyOn(imgSvc, 'list').and.returnValue(of(MULTI_PAGE));
|
||
fixture.detectChanges();
|
||
const nextBtn = (fixture.nativeElement as HTMLElement).querySelector('.next-btn') as HTMLButtonElement;
|
||
expect(nextBtn).not.toBeNull();
|
||
expect(nextBtn.disabled).toBeTrue();
|
||
});
|
||
|
||
it('prev-btn (‹) is enabled when not on page 1', () => {
|
||
TestBed.overrideProvider(ActivatedRoute, { useValue: makeActivatedRoute({ page: '2' }) });
|
||
const fixture = TestBed.createComponent(LibraryComponent);
|
||
const imgSvc = TestBed.inject(ImageService);
|
||
spyOn(imgSvc, 'list').and.returnValue(of(MULTI_PAGE));
|
||
fixture.detectChanges();
|
||
const prevBtn = (fixture.nativeElement as HTMLElement).querySelector('.prev-btn') as HTMLButtonElement;
|
||
expect(prevBtn.disabled).toBeFalse();
|
||
});
|
||
|
||
it('next-btn (›) is enabled when not on last page', () => {
|
||
const fixture = TestBed.createComponent(LibraryComponent);
|
||
const imgSvc = TestBed.inject(ImageService);
|
||
spyOn(imgSvc, 'list').and.returnValue(of(MULTI_PAGE));
|
||
fixture.detectChanges();
|
||
const nextBtn = (fixture.nativeElement as HTMLElement).querySelector('.next-btn') as HTMLButtonElement;
|
||
expect(nextBtn.disabled).toBeFalse();
|
||
});
|
||
|
||
it('both prev and next buttons always rendered when totalPages > 1', () => {
|
||
const fixture = TestBed.createComponent(LibraryComponent);
|
||
const imgSvc = TestBed.inject(ImageService);
|
||
spyOn(imgSvc, 'list').and.returnValue(of(MULTI_PAGE));
|
||
fixture.detectChanges();
|
||
expect((fixture.nativeElement as HTMLElement).querySelector('.prev-btn')).not.toBeNull();
|
||
expect((fixture.nativeElement as HTMLElement).querySelector('.next-btn')).not.toBeNull();
|
||
});
|
||
|
||
// ---- Pagination: URL state ----
|
||
|
||
it('reads ?page=2 from queryParamMap on init and calls list with offset=24', () => {
|
||
TestBed.overrideProvider(ActivatedRoute, { useValue: makeActivatedRoute({ page: '2' }) });
|
||
const fixture = TestBed.createComponent(LibraryComponent);
|
||
const imgSvc = TestBed.inject(ImageService);
|
||
const listSpy = spyOn(imgSvc, 'list').and.returnValue(of(MULTI_PAGE));
|
||
fixture.detectChanges();
|
||
expect(fixture.componentInstance.currentPage).toBe(2);
|
||
expect(listSpy).toHaveBeenCalledWith(jasmine.any(Array), jasmine.any(Number), 24);
|
||
});
|
||
|
||
it('clamps out-of-range ?page=9999 to page 1 after load resolves', () => {
|
||
TestBed.overrideProvider(ActivatedRoute, { useValue: makeActivatedRoute({ page: '9999' }) });
|
||
const fixture = TestBed.createComponent(LibraryComponent);
|
||
const imgSvc = TestBed.inject(ImageService);
|
||
spyOn(imgSvc, 'list').and.returnValue(of(MULTI_PAGE));
|
||
fixture.detectChanges();
|
||
expect(fixture.componentInstance.currentPage).toBeLessThanOrEqual(fixture.componentInstance.totalPages);
|
||
});
|
||
|
||
it('nextPage() calls router.navigate with page=2 and queryParamsHandling merge', () => {
|
||
const fixture = TestBed.createComponent(LibraryComponent);
|
||
const imgSvc = TestBed.inject(ImageService);
|
||
spyOn(imgSvc, 'list').and.returnValue(of(MULTI_PAGE));
|
||
fixture.detectChanges();
|
||
const router = TestBed.inject(Router);
|
||
spyOn(router, 'navigate');
|
||
fixture.componentInstance.nextPage();
|
||
expect(router.navigate).toHaveBeenCalledWith([], jasmine.objectContaining({
|
||
queryParams: jasmine.objectContaining({ page: 2 }),
|
||
queryParamsHandling: 'merge',
|
||
}));
|
||
});
|
||
|
||
it('applyFilter() calls router.navigate with page=1 and queryParamsHandling merge', () => {
|
||
const fixture = TestBed.createComponent(LibraryComponent);
|
||
const imgSvc = TestBed.inject(ImageService);
|
||
spyOn(imgSvc, 'list').and.returnValue(of(MULTI_PAGE));
|
||
fixture.detectChanges();
|
||
fixture.componentInstance.currentPage = 2;
|
||
const router = TestBed.inject(Router);
|
||
spyOn(router, 'navigate');
|
||
fixture.componentInstance.applyFilter(['dog']);
|
||
expect(router.navigate).toHaveBeenCalledWith([], jasmine.objectContaining({
|
||
queryParams: jasmine.objectContaining({ page: 1 }),
|
||
queryParamsHandling: 'merge',
|
||
}));
|
||
});
|
||
|
||
it('clicking an image card navigates to /i/:short_id', () => {
|
||
const fixture = TestBed.createComponent(LibraryComponent);
|
||
const imgSvc = TestBed.inject(ImageService);
|
||
spyOn(imgSvc, 'list').and.returnValue(of(ONE_IMAGE));
|
||
fixture.detectChanges();
|
||
const router = TestBed.inject(Router);
|
||
spyOn(router, 'navigate');
|
||
const card = (fixture.nativeElement as HTMLElement).querySelector('.image-card') as HTMLElement;
|
||
card.click();
|
||
expect(router.navigate).toHaveBeenCalledWith(['/i', 'ShrtImg1']);
|
||
});
|
||
|
||
// ---- Pagination: « » first/last (T008) ----
|
||
|
||
it('firstPage() navigates to page 1', () => {
|
||
const fixture = TestBed.createComponent(LibraryComponent);
|
||
const imgSvc = TestBed.inject(ImageService);
|
||
const listSpy = spyOn(imgSvc, 'list').and.returnValue(of(MULTI_PAGE));
|
||
fixture.detectChanges();
|
||
fixture.componentInstance.currentPage = 2;
|
||
fixture.componentInstance.totalPages = 2;
|
||
listSpy.calls.reset();
|
||
fixture.componentInstance.firstPage();
|
||
expect(listSpy).toHaveBeenCalledWith(jasmine.any(Array), jasmine.any(Number), 0);
|
||
expect(fixture.componentInstance.currentPage).toBe(1);
|
||
});
|
||
|
||
it('lastPage() navigates to last page', () => {
|
||
const fixture = TestBed.createComponent(LibraryComponent);
|
||
const imgSvc = TestBed.inject(ImageService);
|
||
const listSpy = spyOn(imgSvc, 'list').and.returnValue(of(MULTI_PAGE));
|
||
fixture.detectChanges();
|
||
listSpy.calls.reset();
|
||
fixture.componentInstance.lastPage();
|
||
expect(fixture.componentInstance.currentPage).toBe(fixture.componentInstance.totalPages);
|
||
});
|
||
|
||
it('first-page button («) is disabled on page 1', () => {
|
||
const fixture = TestBed.createComponent(LibraryComponent);
|
||
const imgSvc = TestBed.inject(ImageService);
|
||
spyOn(imgSvc, 'list').and.returnValue(of(MULTI_PAGE));
|
||
fixture.detectChanges();
|
||
const firstBtn = (fixture.nativeElement as HTMLElement).querySelector('.first-btn') as HTMLButtonElement;
|
||
expect(firstBtn).not.toBeNull();
|
||
expect(firstBtn.disabled).toBeTrue();
|
||
});
|
||
|
||
it('last-page button (») is disabled on last page', () => {
|
||
TestBed.overrideProvider(ActivatedRoute, { useValue: makeActivatedRoute({ page: '2' }) });
|
||
const fixture = TestBed.createComponent(LibraryComponent);
|
||
const imgSvc = TestBed.inject(ImageService);
|
||
spyOn(imgSvc, 'list').and.returnValue(of(MULTI_PAGE));
|
||
fixture.detectChanges();
|
||
const lastBtn = (fixture.nativeElement as HTMLElement).querySelector('.last-btn') as HTMLButtonElement;
|
||
expect(lastBtn).not.toBeNull();
|
||
expect(lastBtn.disabled).toBeTrue();
|
||
});
|
||
|
||
it('first-page button («) is enabled when not on page 1', () => {
|
||
TestBed.overrideProvider(ActivatedRoute, { useValue: makeActivatedRoute({ page: '2' }) });
|
||
const fixture = TestBed.createComponent(LibraryComponent);
|
||
const imgSvc = TestBed.inject(ImageService);
|
||
spyOn(imgSvc, 'list').and.returnValue(of(MULTI_PAGE));
|
||
fixture.detectChanges();
|
||
const firstBtn = (fixture.nativeElement as HTMLElement).querySelector('.first-btn') as HTMLButtonElement;
|
||
expect(firstBtn.disabled).toBeFalse();
|
||
});
|
||
|
||
it('last-page button (») is enabled when not on last page', () => {
|
||
const fixture = TestBed.createComponent(LibraryComponent);
|
||
const imgSvc = TestBed.inject(ImageService);
|
||
spyOn(imgSvc, 'list').and.returnValue(of(MULTI_PAGE));
|
||
fixture.detectChanges();
|
||
const lastBtn = (fixture.nativeElement as HTMLElement).querySelector('.last-btn') as HTMLButtonElement;
|
||
expect(lastBtn.disabled).toBeFalse();
|
||
});
|
||
});
|