Chris
Chris

Reputation: 1058

How can I unit test a form containing ng-select?

I have created an Angular form component that uses <ng-select> as a replacement for HTML <select> elements. Now I want to unit test my component. To test the component logic I have methods that find HTML elements in the DOM and examine or modify their value. This works fine with native HTML elements. But <ng-select> is difficult.

I can find the current value with something like this, but it feels like a very brittle solution.

document.querySelector("ng-select[formControlName=country] .ng-value .ng-value-label").innerText

And how do I change the selection? With a standard input component you can set the value, then fire an input event. There's an <input type="text" role="combobox"> inside <ng-select/>, but changing its value doesn't seem to affect the FormControl value.

I'm not trying to test ng-select, but I do need to verify that my component reacts appropriately to selection changes. It will also be necessary to make selections in the UI when running automated integration tests.

Upvotes: 3

Views: 5816

Answers (3)

Graf Koks Persoenlich
Graf Koks Persoenlich

Reputation: 51

I stumbled over the same problem. Chris is right the ng-select source provides some testing functions from which I just build this file. Which makes it hopefully a little easier to just select an option of a select which is queried by a css-selector. Let me know if it works for you.

import { DebugElement } from '@angular/core';
import { ComponentFixture, tick } from '@angular/core/testing';
import { By } from '@angular/platform-browser';

export class TestsErrorHandler {}

export enum KeyCode {
  Tab = 9,
  Enter = 13,
  Esc = 27,
  Space = 32,
  ArrowUp = 38,
  ArrowDown = 40,
  Backspace = 8
}

export function tickAndDetectChanges(fixture: ComponentFixture<any>) {
  fixture.detectChanges();
  tick();
}

export function selectOption(fixture, selector: string, arrowDowns: number) {
  triggerKeyDownEvent(getNgSelectElement(fixture, selector), KeyCode.Space); // open
  tickAndDetectChanges(fixture); // need to tick and detect changes
  for (let i = 0; i < arrowDowns; i++) {
    triggerKeyDownEvent(getNgSelectElement(fixture, selector), KeyCode.ArrowDown);
  }
  tickAndDetectChanges(fixture); dropdown fully inits after promise is resolved
  triggerKeyDownEvent(getNgSelectElement(fixture, selector), KeyCode.Enter); // select
  fixture.detectChanges();
}

export function getNgSelectElement(fixture: ComponentFixture<any>, selector: string): DebugElement {
  return fixture.debugElement.query(By.css(selector));
}

export function triggerKeyDownEvent(element: DebugElement, which: number, key = ''): void {
  element.triggerEventHandler('keydown', {
    which: which,
    key: key,
    preventDefault: () => { },
  });
}

Upvotes: 3

Chris
Chris

Reputation: 1058

The answer is to check out the ng-select source and look at the test code. There are several examples of tests that change select values.

Upvotes: -3

Marc
Marc

Reputation: 1896

You can use a Harness (MatselectHarness) to interact with an Angular Material component. This will make the tests more robust:

 it('should have 20 countries in the select box', async () => {
    ...
    const select = await loader.getHarness(MatSelectHarness.with(MatSelectHarness);
    //Click the select element host
    (await selectHarness.host()).click();
    const actual = (await selectHarness.getOptions()).length;
    expect(actual).toBe(20);
  });

Select a value (example code from angular doc):

it('should switch to bug report template', async () => {
    expect(fixture.debugElement.query('bug-report-form')).toBeNull();
    const select = await loader.getHarness(MatSelect);
    await select.open();
    const bugOption = await select.getOption({text: 'Bug'});
    await bugOption.click();
    expect(fixture.debugElement.query('bug-report-form')).not.toBeNull();
  });

See: using-component-harnesses

An other way is to move the logic into a service. A service can be unit tested easily.

Upvotes: -1

Related Questions