test(modal): cover image viewer behavior
This commit is contained in:
parent
8bc0e165c7
commit
e95a7612c2
|
|
@ -7,6 +7,7 @@ import { firstValueFrom } from 'rxjs';
|
||||||
|
|
||||||
import { ConfirmDeleteModalComponent } from '../../shared/components/confirm-delete-modal/confirm-delete-modal.component';
|
import { ConfirmDeleteModalComponent } from '../../shared/components/confirm-delete-modal/confirm-delete-modal.component';
|
||||||
import { ConfirmModalComponent } from '../../shared/components/confirm-modal/confirm-modal.component';
|
import { ConfirmModalComponent } from '../../shared/components/confirm-modal/confirm-modal.component';
|
||||||
|
import { ImageModalComponent } from '../../shared/components/image-modal/image-modal.component';
|
||||||
import { QrModalComponent } from '../../shared/components/qr-modal/qr-modal.component';
|
import { QrModalComponent } from '../../shared/components/qr-modal/qr-modal.component';
|
||||||
import { SimpleModalComponent } from '../../shared/components/simple-modal/simple-modal.component';
|
import { SimpleModalComponent } from '../../shared/components/simple-modal/simple-modal.component';
|
||||||
import { MODAL_DATA, ModalRef, ModalService } from './modal.service';
|
import { MODAL_DATA, ModalRef, ModalService } from './modal.service';
|
||||||
|
|
@ -258,6 +259,36 @@ describe('ModalService', () => {
|
||||||
showCloseButton: true,
|
showCloseButton: true,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('opens the image viewer fullscreen with normalized zoom limits', () => {
|
||||||
|
service.openImage({
|
||||||
|
title: 'Producto',
|
||||||
|
src: '/images/producto.webp',
|
||||||
|
alt: 'Producto visto de frente',
|
||||||
|
initialZoom: 8,
|
||||||
|
minZoom: 0,
|
||||||
|
maxZoom: 3,
|
||||||
|
});
|
||||||
|
|
||||||
|
const activeModal = service.activeModal();
|
||||||
|
|
||||||
|
expect(activeModal?.component).toBe(ImageModalComponent);
|
||||||
|
expect(activeModal?.config).toEqual({
|
||||||
|
title: 'Producto',
|
||||||
|
size: 'full',
|
||||||
|
presentation: 'fullscreen-media',
|
||||||
|
data: {
|
||||||
|
src: '/images/producto.webp',
|
||||||
|
alt: 'Producto visto de frente',
|
||||||
|
initialZoom: 3,
|
||||||
|
minZoom: 0.1,
|
||||||
|
maxZoom: 3,
|
||||||
|
},
|
||||||
|
closeOnBackdrop: true,
|
||||||
|
closeOnEscape: true,
|
||||||
|
showCloseButton: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,106 @@
|
||||||
|
import '@angular/compiler';
|
||||||
|
import { ComponentFixture, TestBed, getTestBed } from '@angular/core/testing';
|
||||||
|
import { BrowserTestingModule, platformBrowserTesting } from '@angular/platform-browser/testing';
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
|
import { ImageModalData, MODAL_DATA } from '../../../core/services/modal.service';
|
||||||
|
import { ImageModalComponent } from './image-modal.component';
|
||||||
|
|
||||||
|
describe('ImageModalComponent', () => {
|
||||||
|
let fixture: ComponentFixture<ImageModalComponent>;
|
||||||
|
const data: ImageModalData = {
|
||||||
|
src: '/images/producto.webp',
|
||||||
|
alt: 'Vista frontal del producto',
|
||||||
|
initialZoom: 1,
|
||||||
|
minZoom: 1,
|
||||||
|
maxZoom: 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
try {
|
||||||
|
getTestBed().initTestEnvironment(BrowserTestingModule, platformBrowserTesting());
|
||||||
|
} catch {
|
||||||
|
// Test environment may already be initialized by another setup entrypoint.
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [ImageModalComponent],
|
||||||
|
providers: [{ provide: MODAL_DATA, useValue: data }],
|
||||||
|
}).compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(ImageModalComponent);
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
fixture.destroy();
|
||||||
|
TestBed.resetTestingModule();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the image and accessible zoom controls', () => {
|
||||||
|
const element = fixture.nativeElement as HTMLElement;
|
||||||
|
const image = element.querySelector('img') as HTMLImageElement;
|
||||||
|
|
||||||
|
expect(image.getAttribute('src')).toBe('/images/producto.webp');
|
||||||
|
expect(image.alt).toBe('Vista frontal del producto');
|
||||||
|
expect(element.querySelector('[aria-label="Acercar"]')).not.toBeNull();
|
||||||
|
expect(element.querySelector('[aria-label="Alejar"]')).not.toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('zooms with controls, respects limits, and resets', () => {
|
||||||
|
const element = fixture.nativeElement as HTMLElement;
|
||||||
|
const zoomIn = element.querySelector('[aria-label="Acercar"]') as HTMLButtonElement;
|
||||||
|
const zoomOut = element.querySelector('[aria-label="Alejar"]') as HTMLButtonElement;
|
||||||
|
const reset = element.querySelector('[aria-label="Restablecer zoom"]') as HTMLButtonElement;
|
||||||
|
|
||||||
|
zoomIn.click();
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(reset.textContent).toContain('150%');
|
||||||
|
expect(zoomOut.disabled).toBe(false);
|
||||||
|
|
||||||
|
zoomIn.click();
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(reset.textContent).toContain('200%');
|
||||||
|
expect(zoomIn.disabled).toBe(true);
|
||||||
|
|
||||||
|
reset.click();
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(reset.textContent).toContain('100%');
|
||||||
|
expect(zoomOut.disabled).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('supports pinch zoom through touch pointer events', () => {
|
||||||
|
const viewport = fixture.nativeElement.querySelector('.image-modal__viewport') as HTMLElement;
|
||||||
|
const zoom = fixture.nativeElement.querySelector(
|
||||||
|
'[aria-label="Restablecer zoom"]',
|
||||||
|
) as HTMLButtonElement;
|
||||||
|
|
||||||
|
viewport.dispatchEvent(pointerEvent('pointerdown', 1, 100, 100));
|
||||||
|
viewport.dispatchEvent(pointerEvent('pointerdown', 2, 200, 100));
|
||||||
|
viewport.dispatchEvent(pointerEvent('pointermove', 2, 250, 100));
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(zoom.textContent).toContain('150%');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows a fallback when the image cannot be loaded', () => {
|
||||||
|
const image = fixture.nativeElement.querySelector('img') as HTMLImageElement;
|
||||||
|
image.dispatchEvent(new Event('error'));
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(fixture.nativeElement.querySelector('[role="alert"]')?.textContent).toContain(
|
||||||
|
'No se pudo cargar la imagen',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function pointerEvent(type: string, pointerId: number, clientX: number, clientY: number): Event {
|
||||||
|
const event = new MouseEvent(type, { bubbles: true, clientX, clientY });
|
||||||
|
Object.defineProperties(event, {
|
||||||
|
pointerId: { value: pointerId },
|
||||||
|
pointerType: { value: 'touch' },
|
||||||
|
});
|
||||||
|
return event;
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue