I am using template-driven forms in Angular 2, and I'm trying to develop them test-first. I've scoured this site and the rest of the internet and I've tried basically everything I can find (mainly bunches of tick() statements and detectChanges() everywhere in a fakeAsync) to get the NgModel attached to my input to pick up the value so it can be passed to my onSubmit function. The value of the input element sets properly, but the NgModel never updates, which then means the onSubmit function does not get the correct value from the NgModel.
Here's the template:
<form id="createWorkout" #cwf="ngForm" (ngSubmit)="showWorkout(skillCountFld)" novalidate> <input name="skillCount" id="skillCount" class="form-control" #skillCountFld="ngModel" ngModel /> <button type="submit" id="buildWorkout">Build a Workout</button> </form>
Note: I know that the value sent the ngSubmit is going to cause the test to fail, but it means I can set a break point in the function and inspect the NgModel.
Here's the Component:
import { Component, OnInit } from '@angular/core'; import {SkillService} from "../model/skill-service"; import {NgModel} from "@angular/forms"; @Component({ selector: 'app-startworkout', templateUrl: './startworkout.component.html', styleUrls: ['./startworkout.component.css'] }) export class StartworkoutComponent implements OnInit { public skillCount:String; constructor(public skillService:SkillService) { } showWorkout(value:NgModel):void { console.log('breakpoint', value.value); } ngOnInit() { } }
Here is the spec:
/* tslint:disable:no-unused-variable */ import {async, ComponentFixture, TestBed, fakeAsync, tick} from '@angular/core/testing'; import {By, BrowserModule} from '@angular/platform-browser'; import { DebugElement } from '@angular/core'; import { StartworkoutComponent } from './startworkout.component'; import {SkillService} from "../model/skill-service"; import {Store} from "../core/store"; import {SportService} from "../model/sport-service"; import {FormsModule} from "@angular/forms"; import {dispatchEvent} from "@angular/platform-browser/testing/browser_util"; describe('StartworkoutComponent', () => { let component: StartworkoutComponent; let fixture: ComponentFixture; let element:DebugElement; let skillService:SkillService; beforeEach(async(() => { var storeSpy:any = jasmine.createSpyObj('store', ['getValue', 'storeValue', 'removeValue']); var stubSkillService:SkillService = new SkillService(storeSpy); TestBed.configureTestingModule({ declarations: [ StartworkoutComponent ], providers: [{provide:Store , useValue:storeSpy}, SportService, SkillService], imports: [BrowserModule, FormsModule] }) .compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(StartworkoutComponent); component = fixture.componentInstance; element = fixture.debugElement; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); describe('without workout', () => { let createWorkout:DebugElement; let skillCount:HTMLInputElement; let submitButton:HTMLButtonElement; beforeEach(() => { createWorkout = element.query(By.css('#createWorkout')); skillCount = element.query(By.css('#skillCount')).nativeElement; submitButton = element.query(By.css('#buildWorkout')).nativeElement; }); it('has createWorkout form', () => { expect(createWorkout).toBeTruthy(); expect(skillCount).toBeTruthy(); }); it('submits the value', fakeAsync(() => { spyOn(component, 'showWorkout').and.callThrough(); tick(); skillCount.value = '10'; dispatchEvent(skillCount, 'input'); fixture.detectChanges(); tick(50); submitButton.click(); fixture.detectChanges(); tick(50); expect(component.showWorkout).toHaveBeenCalledWith('10'); })); }); });
I'm sure I'm missing something basic/simple, but I've spent the past day combing through everything I can find with no luck.
Edit:
I think maybe people are focusing on the wrong thing. I'm pretty sure at this point that I'm missing something basic about how ngForm and ngModel work. When I add
<p>{{cwf.value | json}}</p>
into the form, it just shows {}. I believe it should show a member property representing the input. If I type into the field, the value does not change. Similar things happen if I try to bind to skillCountFld. So I think the basic form setup is incorrect somehow, and the test is never going to work until the input is correctly wired to the skillCountFld controller. I just don't see what I'm missing.
toHaveBeenCalledWith(10)
if you passngModel
inshowWorkout
function? – yurzuiNgModel
, while in the case of reactive forms module there was no problem with the bindings. Figured out how to get around it which can probably be of any help to you. – Ahmad Baktash Hayeri