angularangularjs-directivekarma-jasmineangular-test

(Angular) Karma Jasmine test is showing failure for a working feature


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.


Solution

  • 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.