How to effectively unit test an Angular 4 directive with an @input property

I have developed a directive called AllowOnlyNumbers that is applied to input textboxes.

<input                         
                    [AllowOnlyNumbers]=true
                    [maxLength]= 'yearlyFieldMaxLength'
                    type="tel"
                    name="totalAnnualIncome"
                    formControlName="totalAnnualIncome"
                    [(ngModel)]="yearlyIncomeAmt"
                    (focus)="onFocusEnableToolTip('TotalAnnualIncome')" 
                    (focusout)="onFocusOutDisableToolTip('TotalAnnualIncome')" 
                     minlength="2"
                     autocomplete="off"/>

This straightforward directive empowers users to only input numbers in the textbox.

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

@Directive({
  selector: '[AllowOnlyNumbers]'
})

/**
 * @method AllowOnlyNumbers
 * @desc This directive limits keyboard entries to numbers exclusively.
 * Users can enter digits and use backspace, tab, enter, escape, end, home, left, right, and delete keys.
 * Usage: <input type = "text" [AllowOnlyNumbers] = true />
 */

export class AllowOnlyNumbers {

  constructor() { }

  @Input() AllowOnlyNumbers: boolean;
  /**
   * @method onKeyDown
   * @desc It restricts keyboard entries to numbers only.
   * @argument event
   * @returns only digit inputs
   *
   */
  @HostListener('keydown', ['$event']) onKeyDown(event) {
    const e = event as KeyboardEvent;
    if (this.AllowOnlyNumbers) {
      // Allow: 8=Backspace, 9=Tab, 13=CR, 27=ESC, 35=END, 36=HOME, 37=Left, 39=Right, 46=DEL
      if ([8, 9, 13, 27, 35, 36, 37, 39, 46].indexOf(e.keyCode) !== -1) {
        return;
      }

      // Ensure it's a number and stop the keypress
      if ((e.shiftKey || (e.keyCode < 48 || e.keyCode > 57)) && (e.keyCode < 96 || e.keyCode > 105)) {
        e.preventDefault();
      }
    }
  }
}

When attempting to write a unit test case using Jasmine, I encounter an issue with setting the @Input() AllowOnlyNumbers property as true, as it always remains undefined. Below is my unit test file. (Please note: Keydown event is triggered)

import {ComponentFixture, TestBed} from '@angular/core/testing';
import { AllowOnlyNumbers } from './allow-only-numbers.directive';
import { Component, DebugElement, NO_ERRORS_SCHEMA } from '@angular/core';
import { By } from '@angular/platform-browser';

@Component({
  template: `<input [AllowOnlyNumbers]= true type="text" name="totalAnnualIncome"  />`
})
// tslint:disable-next-line:no-unnecessary-class
class TestAllowOnlyNumbersComponent {
 //  allowNumbers = true;
}
fdescribe('Directive: AllowOnlyNumbers', () => {
  let component: TestAllowOnlyNumbersComponent;
  let fixture: ComponentFixture<TestAllowOnlyNumbersComponent>;
  let inputEl: DebugElement;
  let linkDes;
  let routerLinks;

  beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [TestAllowOnlyNumbersComponent, AllowOnlyNumbers],
      schemas:      [NO_ERRORS_SCHEMA]
    });
    fixture = TestBed.createComponent(TestAllowOnlyNumbersComponent);
    component = fixture.componentInstance;
    inputEl = fixture.debugElement.query(By.css('input[name="totalAnnualIncome"]'));
  });

  it('triggers keydown on input', () => {
    inputEl.triggerEventHandler('keydown', {});
    fixture.detectChanges();
    expect(true).toBe(true);
  });

});

For more information, I am referring to this resource. The challenge lies in setting the @Input() AllowOnlyNumbers property to true, which constantly returns as undefined.

Answer №1

Solution to your problem:

The correct syntax should be [AllowOnlyNumbers]="true" instead of [AllowOnlyNumbers]= true in the component TestAllowOnlyNumbersComponent.

Currently, you are using [AllowOnlyNumbers]=, which does not assign any value to the input directive.

In addition, it is recommended to call fixture.detectChanges() before invoking triggerEventHandler to ensure proper initial value binding. Alternatively, you can include it at the end of the beforeEach method.

  beforeEach(() => {
    ...
    fixture.detectChanges();
  });

  it('keydown input', () => {
    inputEl.triggerEventHandler('keydown', {});
    expect(true).toBe(true);
  });

Further suggestion for your directive:

I suggest replacing keyCode with key in your directive as keyCode has been deprecated according to https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent. You can simply make this change by reading the key string and deriving a code based on that value like so: const code = e.key.charCodeAt()

You can then create a test to check a specific key, such as the 'F' key:

  it('keydown input', () => {
    const event = new KeyboardEvent('keydown', { key: 'F' });
    inputEl.nativeElement.dispatchEvent(event);
    expect(event.defaultPrevented).toBe(true);
  });

Implementing this modification in the directive should help resolve the issue.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Troubleshooting problems with NativeScript IOS Safe Area when using Children Action Bar

I am utilizing the "@nativescript-community/ui-material-tabs" plugin to display tabs on IOS and Android. https://i.sstatic.net/zixkl.png However, there seems to be a problem as the component is affecting the top safe area in IOS (specifically tested on I ...

Having trouble with i18n types not functioning correctly in Typescript and Nuxt?

I am currently utilizing NuxtJS/i18n with TypeScript and have included its types in my TS config: { "compilerOptions": { "types": [ "@nuxt/types", "@nuxtjs/i18n", ] } } However, when attempti ...

Incorporating AngularFire2 in the latest Angular 11.0.5 framework

I have been attempting to utilize Firebase as a database for my angular application. Following the guidance provided in these instructions (located on the official developers Github page), I first installed npm install angularfire2 firebase --save in my p ...

Transitioning from ng-repeat filter to Typescript

As I migrate my project from AngularJS to modern Angular 8, one of the steps is converting JavaScript code to TypeScript. During this process, I encountered a challenging issue with the `ng-repeat` filter feature. Initially, my HTML template looked like t ...

Ways to solve the issue: Error: Uncaught (in promise): TypeError: this.afAuth.authState.take is not a recognized function

Every time I attempt to access this page on my Ionic App, an error keeps popping up: Error: Uncaught (in promise): TypeError: this.afAuth.authState.take is not a function This issue is really frustrating as it was working perfectly fine before! I' ...

Creating Forms - Ensuring Data Integrity when Processing Files

In my web application, I have a CRUD table where users can add, edit, and delete entries. To validate the form data, I am using a FormBuilder. this.saisieForm = this.fb.group({ 'val1': ['', Validators.required], 'val2&apos ...

`Two side-by-side nvd3 visualizations`

I am seeking assistance with arranging two nvd3 graphs side by side on a webpage. Below is the code I am currently using: <div class="container" > <!-- Graph 1--> <div class="rows"> <nvd3 [options]="optionsGraph1" [data]="Gra ...

Issue with Angular Reactive Form Validation Functionality Failure

Recently, I've been experimenting with Angular Reactive forms and encountered an issue in my code. Here's the link to the Component Class file I created for the component class. Additionally, you can view my HTML code by following this link: HTM ...

Leveraging the information retrieved from Promise.all calls

Using the service method below, I send two parallel queries to the server with Promise.all. The returned results are stored in the productCategoryData array, which is then logged to the console for verification. Service method public getProductCategoryDa ...

Issue in Angular 6 SSR: Trying to access property 'root' of an undefined variable

I recently encountered an issue with my Angular 6.03 project using Angular Universal deployed on Firebase. Everything was functioning perfectly until my last deployment, when I started receiving a "cannot read property 'root' of undefined" error. ...

The error message "result.subscribe is not a function" indicates that there was a problem

I encountered an issue with the following error message: Uncaught TypeError: result.subscribe is not a function Here's a screenshot of the error for reference: https://i.sstatic.net/yfhy0.png Despite attempting to handle the error, I'm still s ...

What is the best way to package a UI library in a monorepo?

After dedicating a significant amount of time to unraveling this issue, I find myself at a standstill. Incorporating Turbo as a monorepo, I utilized the create-turbo@latest command, outlined in detail here. While the default ui/package functions properly ...

How can I identify when the payment form in Stripe Elements has finished loading?

When implementing a payment form using Stripe elements, I have noticed that there is sometimes a delay of 5-10 seconds for the form to load completely. Is there a way to detect when the form has finished loading so that I can display a loading animation or ...

Angular2/Typescript: Transforming a Javascript/Typescript Date into a C# DateTime string on the client side

Currently immersed in an Angular2/Typescript project, I am faced with the task of sending a date to a C# backend. Despite my extensive research, all I could uncover through Google searches was information on converting the date on the backend side. My la ...

Managing background tasks with Node.js in real-time

I am currently faced with the challenge of managing background events. When a user is temporarily banned, we save the ban expiration in our database to ensure they are unbanned at the right time. However, my current code checks every ban every 10 seconds ...

Collective feedback received from several HTTP requests initiated in a sequential loop

I am seeking advice on an efficient method to perform multiple HTTP requests asynchronously, and then merge all of the responses into a single array. Here is an example snippet of the code I am working with: getSamples(genes) { genes.forEach(gene ...

When working with Angular 7, running the distribution files on a local machine may result in an error being thrown when reloading or revisiting the URL

When trying to run the dist folder on a local machine in Angular 7, the web page loads properly for the first time. However, upon reloading or revisiting the URL, an error is thrown. I followed the instructions from this link to run it on my local machine ...

Troubleshooting: React.forwardRef and Typescript defaultProps not functioning correctly

I am currently working on migrating components from a js to ts react component library for my own project. The library was originally written in js using a customized material-ui library. My task now is to migrate these components one by one. Here is an ...

What to do when encountering an error indicating the possibility of an element being null during testing in TypeScript? How should the expect statement be correctly written in this scenario?

When using RTL with a test: describe('Image component', () => { it('renders avatar', () => { render(<Image {...args} />); const element = document.querySelector('img'); expect(element.src).toContain(a ...

What causes Node.js to crash with the Headers already sent Error while managing errors in Express?

My current project involves using Express to set up an API endpoint for user registration. However, I've encountered a problem where sending a request that triggers an error to this API endpoint causes my node.js server to crash. The specific message ...