I'm trying to write a unit test for a directive that is supposed to open a popup when it's clicked.
The directive works perfectly when i test it manually, however, the unit test shows failure which I don't understand.
Test:
@Component({
template: '<button class="btn" PopupTarget target="my_modal">open modal</button>' +
'<popup popupId="my_modal">hello</popup>'
})
export class TesterComponent {
}
describe('PopupTargetDirective', () => {
let fixture: ComponentFixture<TesterComponent>
let directive: PopupTargetDirective;
let button: HTMLButtonElement;
let popup: DebugElement
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [PopupTargetDirective, TesterComponent, PopupComponent]
})
.compileComponents();
fixture = TestBed.createComponent(TesterComponent);
button = fixture.debugElement.nativeElement.querySelector(".btn")
directive = new PopupTargetDirective(new ElementRef<any>(button));
popup = fixture.debugElement.query(By.directive(PopupComponent)).nativeElement
});
it('should create an instance', () => {
expect(directive).toBeTruthy();
})
it('should show popup when clicked', () => {
let dialog: HTMLDialogElement | null = fixture.debugElement.nativeElement.querySelector("dialog")
console.log(button)
expect(dialog?.open).toBeFalse()
button.click()
fixture.whenStable().then(() => {
expect(dialog?.open).toBeTrue()
})
})
});
Directive:
@Directive({
selector: '[PopupTarget]'
})
export class PopupTargetDirective {
@Input() target : string = '';
constructor(private el: ElementRef) {
el.nativeElement.addEventListener("click", ()=>{
let targetPopup: HTMLDialogElement | null = document.getElementById(this.target) as HTMLDialogElement;
targetPopup?.showModal()
})
}
}
popup HTML:
<dialog #dialog id="{{popupId}}" class="modal tw-rounded-md"
(close)="this.onClose.emit()"
(cancel)="cancel($event)"
>
<form method="dialog" class="modal-box">
<div class="tw-flex tw-flex-col tw-justify-center">
<button #closeButton class="tw-self-end" *ngIf="showCloseButton">✕</button>
<ng-content class="tw-self-auto"></ng-content>
</div>
</form>
</dialog>
Popup ts
@Component({
selector: 'popup',
templateUrl: './popup.component.html'
})
export class PopupComponent {
@Input() showCloseButton: boolean = false;
@Input({required: true}) popupId: string | undefined;
@Output() onClose: EventEmitter<void> = new EventEmitter()
@ViewChild("dialog") dialog: ElementRef<HTMLDialogElement> | undefined
@ViewChild("closeButton") closeButton: ElementRef<HTMLButtonElement> | undefined
cancel(event: Event) {
if (!this.showCloseButton) event.preventDefault()
else
this.onClose.emit()
}
}
Is there something wrong with my test code? As I mentioned it works perfectly fine when I test it manually so this is probably the case.
In tests you need to take care of change detection on your own by calling fixture.detectChanges()
. In your test, there is no change detection done, so the component is not initialized, nor updated after the click.
Please call fixture.detectChanges()
after the TestBed.createComponent
line (to initialize the component) and after button.click()
(to update the view after the click). After button.click()
, query the dialog again, now it should be found.
To debug tests it is often helpful to run the test in non-headless browser mode, so that you can see which components are rendered at all. In karma you can configure this in your karma.conf.js
file.