3
votes

I Wrote a Directy that prevents Keyboarddefaults, by checking what Button was pressed. Im Trying to Unittest said Directive. I can see the directive working in the application or when i put console.log() in the Directive. The thing that isnt working is the typed letter wont be set into the components ngModelField.

I tryed alot of things already, including, fixture.detectChanges(), fixture.whenStable() etc.

My Directive:

private allowed  = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9',]

@HostListener('keydown', ['$event'])
  onKeyDown(e: KeyboardEvent) {
    if (this.allowed.indexOf(e.key) > -1) {
      console.log('allowed');
      return;
    }
    console.log('preventdefault');
    e.preventDefault();
  }

My Unittest:

describe('OnlyDecimalDirective', () => {
  let fixture: ComponentFixture<MockOnlyDecimalComponent>;
  let component: MockOnlyDecimalComponent;
  let input: HTMLInputElement;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [OnlyDecimalDirective, MockOnlyDecimalComponent],
      imports: [FormsModule],
    });
    fixture = TestBed.createComponent(MockOnlyDecimalComponent);
    component = fixture.componentInstance;
    input = fixture.debugElement.query(By.css('input')).nativeElement;
  }));

  it('sollte Ziffern akzeptieren', () => {
    fixture.detectChanges();
    let keyDown = new KeyboardEvent('keydown', { key: '5' });
    input.dispatchEvent(keyDown);
    console.log(component.text)
  });

});

My MockComponent for the Unittest

@Component({ template: '<input onlyDecimal [(ngModel)]="text" type="string">' })
export class MockOnlyDecimalComponent {
  public text = '';
}

I need to able to test the outcome. So i expect if i trigger the Keydown event, that the component.text field gets the value of what ever key i post in the new KeyBoardEvent:

let keyDown = new KeyboardEvent('keydown', { key: '5' }); // key: 's' should prevent default

I can see the console.log() appearing but im not able to test the acutal outcome, cause i wont show in the component.text

I was asked for a Stackblitz: https://stackblitz.com/edit/angular-testing-callback-cuqg9m

1
You can also use <input type="number"> for numeric only values.Reactgular
<input type="number" allows you to write ',' and '.' Also dont want thatDaumannM
The thing is, that as Rectangular suggested,just by emitting a keydown event, you are not adding any content into your input element, hence you cannot assert that the input element has content afterwards. If you would use a custom validator instead of that directive it would defintely be more testable. Otherwise you probably could only test the directive itself and ensure that the directive is added to the desired inputs but not how the directive is working with an input inside a jasmine unit test.Erbsenkoenig

1 Answers

1
votes
input.dispatchEvent(keyDown);
console.log(component.text);

Calling dispatchEvent() on a DOM element does not update the input text. You can not simulate user interaction very easily using manual DOM events. To simulate keyboard input involves many more DOM events then just a keydown event. Things like the change event, focus events and multiple keyboard events are at play when the user types on the keyboard.

For example, your directive does not work if the user pastes text from the clipboard or uses autocomplete from the browser.

You need to build a custom template drive validator to mutate the input value if you want to restrict the form's values.

https://angular.io/guide/form-validation#custom-validators