import { TestBed } from '@angular/core/testing'; import { ActivatedRoute, provideRouter, Router } from '@angular/router'; import { provideHttpClient } from '@angular/common/http'; import { provideHttpClientTesting } from '@angular/common/http/testing'; import { of } from 'rxjs'; import { DetailComponent } from './detail.component'; import { ImageService } from '../services/image.service'; import { routes } from '../app.routes'; const MOCK_IMAGE = { id: 'img-1', hash: 'abc', filename: 'test.jpg', mime_type: 'image/jpeg', size_bytes: 100, width: 10, height: 10, storage_key: 'abc', thumbnail_key: null, created_at: '2026-01-01T00:00:00Z', tags: ['cat', 'funny'], }; describe('DetailComponent', () => { function setup(imageId = 'img-1') { TestBed.configureTestingModule({ imports: [DetailComponent], providers: [ provideHttpClient(), provideHttpClientTesting(), provideRouter(routes), { provide: ActivatedRoute, useValue: { snapshot: { paramMap: { get: () => imageId } } } }, ], }).compileComponents(); const fixture = TestBed.createComponent(DetailComponent); const component = fixture.componentInstance; const imgSvc = TestBed.inject(ImageService); spyOn(imgSvc, 'get').and.returnValue(of(MOCK_IMAGE)); fixture.detectChanges(); return { fixture, component, imgSvc }; } it('should call PATCH with removed tag absent when chip × is clicked', () => { const { component, imgSvc } = setup(); spyOn(imgSvc, 'updateTags').and.returnValue(of({ ...MOCK_IMAGE, tags: ['funny'] })); component.removeTag('cat'); expect(imgSvc.updateTags).toHaveBeenCalledWith('img-1', ['funny']); }); it('should call PATCH with new tag included on addTag', () => { const { component, imgSvc } = setup(); spyOn(imgSvc, 'updateTags').and.returnValue(of({ ...MOCK_IMAGE, tags: ['cat', 'funny', 'new'] })); component.addTag('new'); expect(imgSvc.updateTags).toHaveBeenCalledWith('img-1', ['cat', 'funny', 'new']); }); it('should call DELETE and navigate to library on confirm delete', () => { const { component, imgSvc } = setup(); const router = TestBed.inject(Router); spyOn(router, 'navigate'); spyOn(imgSvc, 'delete').and.returnValue(of(undefined)); component.confirmDelete(); expect(imgSvc.delete).toHaveBeenCalledWith('img-1'); expect(router.navigate).toHaveBeenCalledWith(['/']); }); it('should NOT call DELETE when cancel is clicked', () => { const { component, imgSvc } = setup(); spyOn(imgSvc, 'delete').and.returnValue(of(undefined)); component.showDeleteDialog = true; component.cancelDelete(); expect(imgSvc.delete).not.toHaveBeenCalled(); expect(component.showDeleteDialog).toBeFalse(); }); it('back button should navigate to library', () => { const { component } = setup(); const router = TestBed.inject(Router); spyOn(router, 'navigate'); component.goBack(); expect(router.navigate).toHaveBeenCalledWith(['/']); }); });