question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

dispatchEvent doesn't trigger ngModel changes

See original GitHub issue

I’m submitting a … (check one with “x”)

[x] bug report => search github for a similar issue or PR before submitting
[ ] feature request
[ ] support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question

Current behavior The dispatchEvent function from @angular/platform-browser/testing/browser_util and EventTarget.dispatchEvent doesn’t trigger NgModel changes. I have following output in karma (I trimmed debug backtrace):

✖ should modify message
      Chrome 55.0.2883 (Mac OS X 10.11.4)
    Expected undefined to be 'The longest content.'.
    ...
    Expected undefined to be '4'.
    ...

Expected behavior Pass the test

Minimal reproduction of the problem with instructions My code:

review-form.component.ts:

import {
  Input,
  Output,
  Component,
  ViewChild,
  EventEmitter,
} from '@angular/core';
import { NgForm } from '@angular/forms';

import { Product } from 'app/model';
import {
  AbstractForm,
  FormErrorList,
  ProductReviewMessage,
} from 'app/core';

@Component({
  selector: 'l-product-show-review-form',
  templateUrl: './review-form.component.html',
})
export class ProductShowReviewFormComponent extends AbstractForm {
  @Input()
  public model: ProductReviewMessage;

  @Input()
  public product: Product;

  @Output()
  public formSubmit = new EventEmitter<any>();

  @Input()
  public set errors(errors: FormErrorList) {
    this.applyFormErrorList(errors);
  }

  @ViewChild('form')
  public set ngForm(ngForm: NgForm) {
    this.form = ngForm.form;
  }

  public doFormSubmit() {
    this.formSubmit.emit();
  }
}

review-form.component.html:

<form #form="ngForm" (ngSubmit)="doFormSubmit()">
  <div [lControlWrapper]="content">
    <div class="clearfix m-b-1">
      <label class="form-control-static" for="reviewContent" i18n>l.product.show.reviewForm.content</label>
      <div>
        <textarea
          class="form-control"
          id="reviewContent"
          name="content"
          required
          [(ngModel)]="model.content"
          #content="ngModel"
        ></textarea>
      </div>
    </div>
    <l-control-error [control]="content"></l-control-error>
  </div>
  <div [lControlWrapper]="rating">
    <div class="clearfix m-b-1">
      <label class="form-control-static" for="reviewRating" i18n>l.product.show.reviewForm.rating</label>
      <div>
        <input
          type="number"
          class="form-control l-number-input"
          id="reviewRating"
          name="rating"
          required
          [(ngModel)]="model.rating"
          #rating="ngModel"
        />
      </div>
    </div>
    <l-control-error [control]="rating"></l-control-error>
  </div>
  <div>
    <button
      type="submit"
      class="btn btn-primary btn-lg btn-block"
      [disabled]="!form.form.valid"
      i18n
    >l.product.show.reviewForm.submit</button>
  </div>
</form>

[other file that executes just one time in test environment]:

const { TestBed } = require('@angular/core/testing');
const { platformBrowserDynamicTesting, BrowserDynamicTestingModule } = require('@angular/platform-browser-dynamic/testing');

TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());

review-form.component.spec.ts:

import { DebugElement } from '@angular/core';
import { By } from '@angular/platform-browser';
import { dispatchEvent } from '@angular/platform-browser/testing/browser_util';
import { ComponentFixture, TestBed, async, fakeAsync } from '@angular/core/testing';

import { Product } from 'app/model';
import { SharedModule } from 'app/shared';
import { ProductReviewMessage } from 'app/core';
import { ProductShowReviewFormComponent } from './review-form.component';

function newEvent(eventName: string, bubbles = false, cancelable = false) {
  let evt = document.createEvent('CustomEvent');  // MUST be 'CustomEvent'
  evt.initCustomEvent(eventName, bubbles, cancelable, null);
  return evt;
}

describe('ProductShowReviewFormComponent', () => {
  let fixture: ComponentFixture<ProductShowReviewFormComponent>;
  let comp: ProductShowReviewFormComponent;
  let submitSpy: jasmine.Spy;
  let contentInput: HTMLTextAreaElement;
  let ratingInput: HTMLInputElement;
  let form: DebugElement;
  let product: Product;
  let message: ProductReviewMessage;

  beforeEach(async(() => {
    TestBed
      .configureTestingModule({
        imports: [SharedModule],
        declarations: [ProductShowReviewFormComponent],
      })
      .compileComponents()
    ;
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(ProductShowReviewFormComponent);
    comp = fixture.componentInstance;
    submitSpy = spyOn(comp.formSubmit, 'emit');

    contentInput = fixture.debugElement.query(By.css('#reviewContent')).nativeElement;
    ratingInput = fixture.debugElement.query(By.css('#reviewRating')).nativeElement;
    form = fixture.debugElement.query(By.css('form'));

    product = new Product();
    product.id = 1;

    comp.product = product;

    message = new ProductReviewMessage();
    message.product = product;

    comp.model = message;
  });

  it('should modify message', fakeAsync(() => {
    const expectedContent = 'The longest content.';
    const expectedRating = '4';

    contentInput.value = expectedContent;
    dispatchEvent(contentInput, newEvent('input'));
    // contentInput.dispatchEvent(newEvent('input')); // doesn't trigger too

    ratingInput.value = expectedRating;
    dispatchEvent(ratingInput, 'input');

    fixture.detectChanges();
    // tick(); // doesn't help

    form.triggerEventHandler('submit', null);

    expect(submitSpy.calls.any()).toBe(true);
    expect(comp.model.content).toBe(expectedContent);
    expect(comp.model.rating).toBe(expectedRating);
  }));
});

What is the motivation / use case for changing the behavior? I need to test a form.

Please tell us about your environment:

  • OS: OSX El Capitan v. 10.11.4

  • Package Manager: npm 3.10.9

  • Angular version: 2.0.1

  • Browser: Chrome 55.0.2883 (Mac OS X 10.11.4)

  • Language: TypeScript ^2.0.2

  • Node (for AoT issues): node --version = v4.4.2

Issue Analytics

  • State:closed
  • Created 7 years ago
  • Comments:6 (1 by maintainers)

github_iconTop GitHub Comments

7reactions
alexzuzacommented, Dec 18, 2016

First i guess your second beforeEach shoud be async as well as your it case and it should wait until zone gets stable Second: your model.rating property is mapped to input[type="number"] so you’re trying to compare string with number.

Here is how it could look (Live Example):

beforeEach(fakeAsync(() => { // fakeAsync + tick (First note)
    fixture = TestBed.createComponent(TestComponent);
    comp = fixture.componentInstance;
    submitSpy = spyOn(comp.formSubmit, 'emit');

    contentInput = fixture.debugElement.query(By.css('#reviewContent')).nativeElement;
    ratingInput = fixture.debugElement.query(By.css('#reviewRating')).nativeElement;
    form = fixture.debugElement.query(By.css('form'));

    comp.model = { content: '', rating: '' };
    fixture.detectChanges();
    tick();
}));

it('should modify message', fakeAsync(() => { // fakeAsync + tick
    const expectedContent = 'The longest content.';
    const expectedRating = '4';

    contentInput.value = expectedContent;
    contentInput.dispatchEvent(newEvent('input'));

    ratingInput.value = expectedRating;
    ratingInput.dispatchEvent(newEvent('input'));

    fixture.detectChanges();
    tick();

    form.triggerEventHandler('submit', null);

    expect(submitSpy.calls.any()).toBe(true);
    expect(comp.model.content).toBe(expectedContent);
    expect(comp.model.rating.toString()).toBe(expectedRating); // (Second note)
}));
0reactions
angular-automatic-lock-bot[bot]commented, Sep 14, 2019

This issue has been automatically locked due to inactivity. Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

Read more comments on GitHub >

github_iconTop Results From Across the Web

How do I trigger a ngModel model update in an Angular 2 unit ...
I am attempting to test a component containing a text input element. I want to verify that the state of the component changes...
Read more >
Testing Angular forms with dispatchEvent - kirjai
However, simply assigning a string to input's value property is not enough, as that will not trigger a change event. Considering that ngModel ......
Read more >
Testing ngModel in Angular 2 - Medium
The reason is that TestBed doesn't run the change detector at all, unless asked to — so by default, not even the initial...
Read more >
Unit testing ngModel in Angular 4 - Shekhar Gulati
The first way to fix this is by using Angular async utility. It makes your tests run in a special test zone. In...
Read more >
What is the difference between change and ngModelChange ...
change event bound to classical input change event. ngModelChange It fires when the model changes. You cannot use this event without ngModel ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found