1

I have created a very simple directive to use on input elements, that should only allow entry of a decimal number (numeric with single decimal point).

The directive is defined as follows:

import { HostListener, Directive, ElementRef  } from '@angular/core';

// Directive attribute to stop any input, other than a decimal number.
@Directive({
    selector: '[decimalinput]'
})
export class DecimalInputDirective {

    constructor(private element : ElementRef) { }

    // Hook into the key press event.
    @HostListener('keypress', ['$event']) onkeypress( keyEvent : KeyboardEvent ) : boolean {

        // Check if a full stop already exists in the input.
        var alreadyHasFullStop = this.element.nativeElement.value.indexOf('.') != -1;

        // Get the key that was pressed in order to check it against the regEx.
        let input = String.fromCharCode(keyEvent.which);

        // Test for allowed character using regEx. Allowed is number or decimal.
        var isAllowed = /^(\d+)?([.]?\d{0,2})?$/.test( input );

        // If this is an invlid character (i.e. alpha or symbol) OR we already have a full stop, prevent key press.
        if (!isAllowed || (isAllowed && input == '.' && alreadyHasFullStop)){
            keyEvent.preventDefault();
            return false;
        }

        return true;
    }
}

This directive should allow "123.123" not "abc", nor "1.2.1". Now I want to test this directive, reading online, I've come up with this so far:

import { Component, OnInit, TemplateRef,DebugElement, ComponentFactory, ViewChild, ViewContainerRef } from '@angular/core';
import { TestBed, ComponentFixture } from '@angular/core/testing';
import { DecimalInputDirective } from './decimalinput.directive';
import { By } from '@angular/platform-browser';

@Component({
    template: `<input type="text" name="txtDecimalTest" decimalinput>` 
})
class TestDecimalComponent { }


describe('Directive: DecimalInputDirective', () => {

    let component: TestDecimalComponent;
    let fixture: ComponentFixture<TestDecimalComponent>;
    let decimalInput: DebugElement;

    beforeEach(() => {

      TestBed.configureTestingModule({
        declarations: [TestDecimalComponent]
      });

      fixture = TestBed.createComponent(TestDecimalComponent);
      component = fixture.componentInstance;
      decimalInput = fixture.debugElement.query(By.css('input[name=txtDecimalTest]'));
    });

    it('Entering email and password emits loggedIn event', () => {

        // This sets the value (I can even put "abc" here and it will work.
        decimalInput.nativeElement.value = "12345";

        // But I am trying to initialize the keypress event, so the character is tested in a real world way when the user is using.
        decimalInput.nativeElement.dispatchEvent(new KeyboardEvent("keypress", { key: "a" })); // Nothing happens here!  This was my attempt...

        // This 
        expect(decimalInput.nativeElement.value).toBe("12345");
    });
});

You can see from the code, the line:

decimalInput.nativeElement.dispatchEvent(new KeyboardEvent...

Is my attempt to simulate keypresses, as if the user was inputting. If I simulated a, then b, then c, then 1, then 2, then 3, I'd expect the test to make sure the value is only "123" and its ignored "abc" in the way the directive works.

Two questions - 1) is this the correct test I should be doing? 2) Whats wrong with my code - why is the simulated key press doing nothing?

Thanks for any pointers in advance! :)

Aniruddha Das
  • 20,520
  • 23
  • 96
  • 132
Rob
  • 6,819
  • 17
  • 71
  • 131
  • 1
    Are you saying that if the user types `123a` that the input box will show `123`? If so, you want to break apart your tests into smaller components. If not, then don't worry about simulating key presses. You don't need to test that key presses work, that's one of the many assumptions you can make when writing tests. – Jason Oct 16 '17 at 18:12
  • Yea, if a user where to type "abc123", only "123" would make its way into the input as allowed characters. I wasn't trying to test the keypress function, what I was trying to test was that if a simulation of "abc123" is pressed on the input with the directive, that only "123" is actually in the input.value. Does that make sense? – Rob Oct 16 '17 at 18:14
  • 1
    This seems more like end-to-end testing, which you'd do using a browser driver like Selenium. Setting the input value and dispatching a change event to trigger the Angular wiring is sufficient for unit tests. – jonrsharpe Oct 16 '17 at 18:22
  • 1
    @jonrsharpe is probably right, but I found [this answer](https://stackoverflow.com/a/43180918/2555516) which, if you want to ignore jonrsharpe's advice, you might find helpful. – Jason Oct 16 '17 at 18:26
  • Thanks @jonrsharpe - sounds like I've been taking the wrong approach to unit testing this directive. If I extract my character test code into its own method, make the onkeypress event use it, then that would be the method I test, rather than trying to test via input? – Rob Oct 16 '17 at 23:02

1 Answers1

-2

Normally directive are tested in such a way that its being used in real component. So you can create a fake component which will use your directive and you can test that component to handle your directive.

This is the most people suggest.

So in your test file create a fake directive

// tslint:disable-next-line:data
@Component({
  selector: 'sd-test-layout',
  template: `
    <div sdAuthorized [permission]="'data_objects'">test</div>`
})
export class TestDecimalInputDirectiveComponent {

  @Input() permission;

  constructor() {
  }
}

Then in your before each using TestBed create the component instance, and now You are ready to apply mouse events and test them in real situation

TestBed.configureTestingModule({
  imports: [
    HttpModule,
    SharedModule
  ],
  declarations: [
    TestDecimalInputDirectiveComponent,
  ],
  providers: [
    {
      provide: ElementRef,
      useClass: MockElementRef
    },
    AuthorizedDirective,
  ]
}).compileComponents();

Just given you the hint. you can follow this link to get more information

Aniruddha Das
  • 20,520
  • 23
  • 96
  • 132
  • He already did all of what you've said. The problem is he can't change the input's value via `dispatchEvent`. So he can't test his directive which would prevent adding non-numeric characters by preventing the **keypress** event. – Mihailo Oct 16 '17 at 19:18