import '@angular/compiler'; import { Component, inject } from '@angular/core'; import { DOCUMENT } from '@angular/common'; import { TestBed, getTestBed } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { BrowserTestingModule, platformBrowserTesting } from '@angular/platform-browser/testing'; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; import { MODAL_DATA, ModalRef, ModalService } from '../../../core/services/modal.service'; import { ModalHostComponent } from './modal-host.component'; @Component({ standalone: true, template: ` `, }) class ModalContentTestComponent { readonly data = inject<{ title: string } | null>(MODAL_DATA); readonly modalRef = inject>(ModalRef); close(): void { this.modalRef.close('accepted'); } } describe('ModalHostComponent', () => { let service: ModalService; let doc: Document; beforeAll(() => { try { getTestBed().initTestEnvironment(BrowserTestingModule, platformBrowserTesting()); } catch { // Test environment may already be initialized by another setup entrypoint. } }); beforeEach(async () => { await TestBed.configureTestingModule({ imports: [ModalHostComponent], }).compileComponents(); service = TestBed.inject(ModalService); doc = TestBed.inject(DOCUMENT); }); afterEach(() => { doc.body.style.overflow = ''; doc.body.style.paddingRight = ''; TestBed.resetTestingModule(); }); it('renders nothing when no modal is active', () => { const fixture = TestBed.createComponent(ModalHostComponent); fixture.detectChanges(); expect(fixture.nativeElement.textContent?.trim()).toBe(''); expect(doc.body.style.overflow).toBe(''); }); it('opens and renders the requested component with its title and data', () => { const fixture = TestBed.createComponent(ModalHostComponent); service.open(ModalContentTestComponent, { title: 'Editar producto', data: { title: 'Contenido del modal' }, }); fixture.detectChanges(); const title = fixture.nativeElement.querySelector('.modal-shell__title'); const content = fixture.nativeElement.querySelector('.modal-test-title'); expect(title?.textContent).toContain('Editar producto'); expect(content?.textContent).toContain('Contenido del modal'); expect(doc.body.style.overflow).toBe('hidden'); }); it('closes with a result from the child component', () => { const fixture = TestBed.createComponent(ModalHostComponent); const ref = service.open(ModalContentTestComponent, { data: { title: 'Cerrar' }, }); const closedSpy = vi.fn(); ref.afterClosed$.subscribe(closedSpy); fixture.detectChanges(); const closeButton = fixture.nativeElement.querySelector( '.modal-test-close', ) as HTMLButtonElement; closeButton.click(); fixture.detectChanges(); expect(closedSpy).toHaveBeenCalledWith('accepted'); expect(service.activeModal()).toBeNull(); expect(doc.body.style.overflow).toBe(''); expect(doc.body.style.paddingRight).toBe(''); }); it('closes on backdrop click when enabled', () => { const fixture = TestBed.createComponent(ModalHostComponent); const ref = service.open(ModalContentTestComponent, { data: { title: 'Backdrop' }, closeOnBackdrop: true, }); fixture.detectChanges(); const backdrop = fixture.nativeElement.querySelector('.modal-shell') as HTMLDivElement; backdrop.click(); fixture.detectChanges(); expect(service.activeModal()).toBeNull(); expect(ref.dismissReason()).toBe('backdrop'); }); it('does not close on backdrop click when disabled', () => { const fixture = TestBed.createComponent(ModalHostComponent); const ref = service.open(ModalContentTestComponent, { data: { title: 'Persistente' }, closeOnBackdrop: false, }); fixture.detectChanges(); const backdrop = fixture.nativeElement.querySelector('.modal-shell') as HTMLDivElement; backdrop.click(); fixture.detectChanges(); expect(service.activeModal()?.ref).toBe(ref); }); it('closes on Escape when enabled', () => { const fixture = TestBed.createComponent(ModalHostComponent); const ref = service.open(ModalContentTestComponent, { data: { title: 'Escape' }, closeOnEscape: true, }); fixture.detectChanges(); doc.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' })); fixture.detectChanges(); expect(service.activeModal()).toBeNull(); expect(ref.dismissReason()).toBe('escape'); }); it('keeps the modal open on Escape when disabled', () => { const fixture = TestBed.createComponent(ModalHostComponent); const ref = service.open(ModalContentTestComponent, { data: { title: 'No Escape' }, closeOnEscape: false, }); fixture.detectChanges(); doc.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' })); fixture.detectChanges(); expect(service.activeModal()?.ref).toBe(ref); }); it('closes from the shell close button with a programmatic dismiss reason', () => { const fixture = TestBed.createComponent(ModalHostComponent); const ref = service.open(ModalContentTestComponent, { title: 'Con cierre', data: { title: 'Boton' }, }); fixture.detectChanges(); const closeButton = fixture.debugElement.query(By.css('.btn-close')) .nativeElement as HTMLButtonElement; closeButton.click(); fixture.detectChanges(); expect(service.activeModal()).toBeNull(); expect(ref.dismissReason()).toBe('programmatic'); }); });