2016-07-25 18:57:51 -04:00
|
|
|
/**
|
|
|
|
* @license
|
|
|
|
* Copyright Google Inc. All Rights Reserved.
|
|
|
|
*
|
|
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
|
|
* found in the LICENSE file at https://angular.io/license
|
|
|
|
*/
|
|
|
|
|
2016-08-24 19:58:43 -04:00
|
|
|
import {Component, Input} from '@angular/core';
|
|
|
|
import {TestBed, async, fakeAsync, tick} from '@angular/core/testing';
|
|
|
|
import {ControlValueAccessor, FormsModule, NG_VALUE_ACCESSOR, NgForm} from '@angular/forms';
|
2016-07-25 18:57:51 -04:00
|
|
|
import {By} from '@angular/platform-browser/src/dom/debug/by';
|
|
|
|
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
|
|
|
import {dispatchEvent} from '@angular/platform-browser/testing/browser_util';
|
2016-08-24 19:58:43 -04:00
|
|
|
|
2016-07-25 18:57:51 -04:00
|
|
|
export function main() {
|
|
|
|
describe('template-driven forms integration tests', () => {
|
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
beforeEach(() => {
|
|
|
|
TestBed.configureTestingModule({
|
|
|
|
declarations: [
|
|
|
|
StandaloneNgModel, NgModelForm, NgModelGroupForm, NgModelValidBinding, NgModelNgIfForm,
|
|
|
|
NgModelRadioForm, NgModelSelectForm, NgNoFormComp, InvalidNgModelNoName,
|
2016-08-29 14:33:49 -04:00
|
|
|
NgModelOptionsStandalone, NgModelCustomComp, NgModelCustomWrapper,
|
2016-10-10 12:17:45 -04:00
|
|
|
NgModelValidationBindings, NgModelMultipleValidators
|
2016-08-15 19:37:59 -04:00
|
|
|
],
|
|
|
|
imports: [FormsModule]
|
|
|
|
});
|
|
|
|
});
|
2016-07-25 18:57:51 -04:00
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
describe('basic functionality', () => {
|
|
|
|
it('should support ngModel for standalone fields', fakeAsync(() => {
|
|
|
|
const fixture = TestBed.createComponent(StandaloneNgModel);
|
2016-09-09 15:04:38 -04:00
|
|
|
fixture.componentInstance.name = 'oldValue';
|
2016-07-25 18:57:51 -04:00
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
fixture.detectChanges();
|
|
|
|
tick();
|
2016-07-25 18:57:51 -04:00
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
// model -> view
|
|
|
|
const input = fixture.debugElement.query(By.css('input')).nativeElement;
|
|
|
|
expect(input.value).toEqual('oldValue');
|
2016-07-25 18:57:51 -04:00
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
input.value = 'updatedValue';
|
|
|
|
dispatchEvent(input, 'input');
|
|
|
|
tick();
|
2016-07-25 18:57:51 -04:00
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
// view -> model
|
2016-09-09 15:04:38 -04:00
|
|
|
expect(fixture.componentInstance.name).toEqual('updatedValue');
|
2016-08-15 19:37:59 -04:00
|
|
|
}));
|
2016-07-25 18:57:51 -04:00
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
it('should support ngModel registration with a parent form', fakeAsync(() => {
|
|
|
|
const fixture = TestBed.createComponent(NgModelForm);
|
2016-09-09 15:04:38 -04:00
|
|
|
fixture.componentInstance.name = 'Nancy';
|
2016-07-25 18:57:51 -04:00
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
fixture.detectChanges();
|
|
|
|
tick();
|
2016-07-25 18:57:51 -04:00
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
const form = fixture.debugElement.children[0].injector.get(NgForm);
|
|
|
|
expect(form.value).toEqual({name: 'Nancy'});
|
|
|
|
expect(form.valid).toBe(false);
|
|
|
|
}));
|
2016-07-25 18:57:51 -04:00
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
it('should support ngModelGroup', fakeAsync(() => {
|
|
|
|
const fixture = TestBed.createComponent(NgModelGroupForm);
|
2016-09-09 15:04:38 -04:00
|
|
|
fixture.componentInstance.first = 'Nancy';
|
|
|
|
fixture.componentInstance.last = 'Drew';
|
|
|
|
fixture.componentInstance.email = 'some email';
|
2016-07-25 18:57:51 -04:00
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
fixture.detectChanges();
|
|
|
|
tick();
|
2016-07-25 18:57:51 -04:00
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
// model -> view
|
|
|
|
const inputs = fixture.debugElement.queryAll(By.css('input'));
|
|
|
|
expect(inputs[0].nativeElement.value).toEqual('Nancy');
|
|
|
|
expect(inputs[1].nativeElement.value).toEqual('Drew');
|
2016-07-25 18:57:51 -04:00
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
inputs[0].nativeElement.value = 'Carson';
|
|
|
|
dispatchEvent(inputs[0].nativeElement, 'input');
|
|
|
|
tick();
|
2016-07-25 18:57:51 -04:00
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
// view -> model
|
|
|
|
const form = fixture.debugElement.children[0].injector.get(NgForm);
|
|
|
|
expect(form.value).toEqual({name: {first: 'Carson', last: 'Drew'}, email: 'some email'});
|
|
|
|
}));
|
2016-07-25 18:57:51 -04:00
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
it('should add controls and control groups to form control model', fakeAsync(() => {
|
|
|
|
const fixture = TestBed.createComponent(NgModelGroupForm);
|
2016-09-09 15:04:38 -04:00
|
|
|
fixture.componentInstance.first = 'Nancy';
|
|
|
|
fixture.componentInstance.last = 'Drew';
|
|
|
|
fixture.componentInstance.email = 'some email';
|
2016-07-25 18:57:51 -04:00
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
fixture.detectChanges();
|
|
|
|
tick();
|
2016-07-25 18:57:51 -04:00
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
const form = fixture.debugElement.children[0].injector.get(NgForm);
|
|
|
|
expect(form.control.get('name').value).toEqual({first: 'Nancy', last: 'Drew'});
|
|
|
|
expect(form.control.get('name.first').value).toEqual('Nancy');
|
|
|
|
expect(form.control.get('email').value).toEqual('some email');
|
|
|
|
}));
|
|
|
|
|
|
|
|
it('should remove controls and control groups from form control model', fakeAsync(() => {
|
|
|
|
const fixture = TestBed.createComponent(NgModelNgIfForm);
|
2016-09-09 15:04:38 -04:00
|
|
|
fixture.componentInstance.emailShowing = true;
|
|
|
|
fixture.componentInstance.first = 'Nancy';
|
|
|
|
fixture.componentInstance.email = 'some email';
|
2016-08-15 19:37:59 -04:00
|
|
|
fixture.detectChanges();
|
|
|
|
tick();
|
2016-07-25 18:57:51 -04:00
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
const form = fixture.debugElement.children[0].injector.get(NgForm);
|
|
|
|
expect(form.control.get('email').value).toEqual('some email');
|
|
|
|
expect(form.value).toEqual({name: {first: 'Nancy'}, email: 'some email'});
|
2016-07-25 18:57:51 -04:00
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
// should remove individual control successfully
|
2016-09-09 15:04:38 -04:00
|
|
|
fixture.componentInstance.emailShowing = false;
|
2016-08-15 19:37:59 -04:00
|
|
|
fixture.detectChanges();
|
|
|
|
tick();
|
2016-07-25 18:57:51 -04:00
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
expect(form.control.get('email')).toBe(null);
|
|
|
|
expect(form.value).toEqual({name: {first: 'Nancy'}});
|
2016-07-25 18:57:51 -04:00
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
expect(form.control.get('name').value).toEqual({first: 'Nancy'});
|
|
|
|
expect(form.control.get('name.first').value).toEqual('Nancy');
|
2016-07-25 18:57:51 -04:00
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
// should remove form group successfully
|
2016-09-09 15:04:38 -04:00
|
|
|
fixture.componentInstance.groupShowing = false;
|
2016-08-15 19:37:59 -04:00
|
|
|
fixture.detectChanges();
|
|
|
|
tick();
|
2016-07-25 18:57:51 -04:00
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
expect(form.control.get('name')).toBe(null);
|
|
|
|
expect(form.control.get('name.first')).toBe(null);
|
|
|
|
expect(form.value).toEqual({});
|
|
|
|
}));
|
|
|
|
|
2016-08-25 17:37:46 -04:00
|
|
|
it('should set status classes with ngModel', async(() => {
|
|
|
|
const fixture = TestBed.createComponent(NgModelForm);
|
2016-09-09 15:04:38 -04:00
|
|
|
fixture.componentInstance.name = 'aa';
|
2016-08-25 17:37:46 -04:00
|
|
|
fixture.detectChanges();
|
|
|
|
fixture.whenStable().then(() => {
|
|
|
|
fixture.detectChanges();
|
2016-08-15 19:37:59 -04:00
|
|
|
|
2016-08-25 17:37:46 -04:00
|
|
|
const input = fixture.debugElement.query(By.css('input')).nativeElement;
|
|
|
|
const form = fixture.debugElement.children[0].injector.get(NgForm);
|
|
|
|
expect(sortedClassList(input)).toEqual(['ng-invalid', 'ng-pristine', 'ng-untouched']);
|
2016-08-15 19:37:59 -04:00
|
|
|
|
2016-08-25 17:37:46 -04:00
|
|
|
dispatchEvent(input, 'blur');
|
|
|
|
fixture.detectChanges();
|
2016-08-15 19:37:59 -04:00
|
|
|
|
2016-08-25 17:37:46 -04:00
|
|
|
expect(sortedClassList(input)).toEqual(['ng-invalid', 'ng-pristine', 'ng-touched']);
|
2016-08-15 19:37:59 -04:00
|
|
|
|
2016-08-25 17:37:46 -04:00
|
|
|
input.value = 'updatedValue';
|
|
|
|
dispatchEvent(input, 'input');
|
|
|
|
fixture.detectChanges();
|
|
|
|
expect(sortedClassList(input)).toEqual(['ng-dirty', 'ng-touched', 'ng-valid']);
|
|
|
|
});
|
|
|
|
}));
|
2016-08-15 19:37:59 -04:00
|
|
|
|
2016-08-25 17:37:46 -04:00
|
|
|
it('should set status classes with ngModelGroup and ngForm', async(() => {
|
|
|
|
const fixture = TestBed.createComponent(NgModelGroupForm);
|
2016-09-09 15:04:38 -04:00
|
|
|
fixture.componentInstance.first = '';
|
2016-08-25 17:37:46 -04:00
|
|
|
fixture.detectChanges();
|
2016-08-15 19:37:59 -04:00
|
|
|
|
2016-08-25 17:37:46 -04:00
|
|
|
const form = fixture.debugElement.query(By.css('form')).nativeElement;
|
|
|
|
const modelGroup = fixture.debugElement.query(By.css('[ngModelGroup]')).nativeElement;
|
|
|
|
const input = fixture.debugElement.query(By.css('input')).nativeElement;
|
2016-08-15 19:37:59 -04:00
|
|
|
|
2016-08-25 17:37:46 -04:00
|
|
|
// ngModelGroup creates its control asynchronously
|
|
|
|
fixture.whenStable().then(() => {
|
|
|
|
fixture.detectChanges();
|
|
|
|
expect(sortedClassList(modelGroup)).toEqual([
|
|
|
|
'ng-invalid', 'ng-pristine', 'ng-untouched'
|
|
|
|
]);
|
2016-08-15 19:37:59 -04:00
|
|
|
|
2016-08-25 17:37:46 -04:00
|
|
|
expect(sortedClassList(form)).toEqual(['ng-invalid', 'ng-pristine', 'ng-untouched']);
|
2016-08-15 19:37:59 -04:00
|
|
|
|
2016-08-25 17:37:46 -04:00
|
|
|
dispatchEvent(input, 'blur');
|
|
|
|
fixture.detectChanges();
|
2016-08-15 19:37:59 -04:00
|
|
|
|
2016-08-25 17:37:46 -04:00
|
|
|
expect(sortedClassList(modelGroup)).toEqual([
|
|
|
|
'ng-invalid', 'ng-pristine', 'ng-touched'
|
|
|
|
]);
|
|
|
|
expect(sortedClassList(form)).toEqual(['ng-invalid', 'ng-pristine', 'ng-touched']);
|
2016-08-15 19:37:59 -04:00
|
|
|
|
2016-08-25 17:37:46 -04:00
|
|
|
input.value = 'updatedValue';
|
|
|
|
dispatchEvent(input, 'input');
|
|
|
|
fixture.detectChanges();
|
2016-08-15 19:37:59 -04:00
|
|
|
|
2016-08-25 17:37:46 -04:00
|
|
|
expect(sortedClassList(modelGroup)).toEqual(['ng-dirty', 'ng-touched', 'ng-valid']);
|
|
|
|
expect(sortedClassList(form)).toEqual(['ng-dirty', 'ng-touched', 'ng-valid']);
|
|
|
|
});
|
|
|
|
}));
|
2016-08-15 19:37:59 -04:00
|
|
|
|
|
|
|
it('should not create a template-driven form when ngNoForm is used', () => {
|
|
|
|
const fixture = TestBed.createComponent(NgNoFormComp);
|
|
|
|
fixture.detectChanges();
|
|
|
|
expect(fixture.debugElement.children[0].providerTokens.length).toEqual(0);
|
|
|
|
});
|
|
|
|
});
|
2016-07-25 18:57:51 -04:00
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
describe('name and ngModelOptions', () => {
|
|
|
|
it('should throw if ngModel has a parent form but no name attr or standalone label', () => {
|
|
|
|
const fixture = TestBed.createComponent(InvalidNgModelNoName);
|
|
|
|
expect(() => fixture.detectChanges())
|
|
|
|
.toThrowError(new RegExp(`name attribute must be set`));
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should not throw if ngModel has a parent form, no name attr, and a standalone label',
|
|
|
|
() => {
|
|
|
|
const fixture = TestBed.createComponent(NgModelOptionsStandalone);
|
|
|
|
expect(() => fixture.detectChanges()).not.toThrow();
|
|
|
|
});
|
2016-07-25 18:57:51 -04:00
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
it('should not register standalone ngModels with parent form', fakeAsync(() => {
|
|
|
|
const fixture = TestBed.createComponent(NgModelOptionsStandalone);
|
2016-09-09 15:04:38 -04:00
|
|
|
fixture.componentInstance.one = 'some data';
|
|
|
|
fixture.componentInstance.two = 'should not show';
|
2016-08-15 19:37:59 -04:00
|
|
|
fixture.detectChanges();
|
|
|
|
tick();
|
2016-07-25 18:57:51 -04:00
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
const form = fixture.debugElement.children[0].injector.get(NgForm);
|
|
|
|
const inputs = fixture.debugElement.queryAll(By.css('input'));
|
|
|
|
tick();
|
2016-07-25 18:57:51 -04:00
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
expect(form.value).toEqual({one: 'some data'});
|
|
|
|
expect(inputs[1].nativeElement.value).toEqual('should not show');
|
|
|
|
}));
|
2016-07-25 18:57:51 -04:00
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
it('should override name attribute with ngModelOptions name if provided', fakeAsync(() => {
|
|
|
|
const fixture = TestBed.createComponent(NgModelForm);
|
2016-09-09 15:04:38 -04:00
|
|
|
fixture.componentInstance.options = {name: 'override'};
|
|
|
|
fixture.componentInstance.name = 'some data';
|
2016-08-15 19:37:59 -04:00
|
|
|
fixture.detectChanges();
|
|
|
|
tick();
|
2016-07-25 18:57:51 -04:00
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
const form = fixture.debugElement.children[0].injector.get(NgForm);
|
|
|
|
expect(form.value).toEqual({override: 'some data'});
|
|
|
|
}));
|
|
|
|
});
|
2016-07-25 18:57:51 -04:00
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
describe('submit and reset events', () => {
|
2016-10-11 18:49:36 -04:00
|
|
|
it('should emit ngSubmit event with the original submit event on submit', fakeAsync(() => {
|
2016-08-15 19:37:59 -04:00
|
|
|
const fixture = TestBed.createComponent(NgModelForm);
|
2016-10-11 18:49:36 -04:00
|
|
|
fixture.componentInstance.event = null;
|
2016-07-25 18:57:51 -04:00
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
const form = fixture.debugElement.query(By.css('form'));
|
|
|
|
dispatchEvent(form.nativeElement, 'submit');
|
|
|
|
tick();
|
2016-07-25 18:57:51 -04:00
|
|
|
|
2016-10-11 18:49:36 -04:00
|
|
|
expect(fixture.componentInstance.event.type).toEqual('submit');
|
2016-08-15 19:37:59 -04:00
|
|
|
}));
|
2016-07-25 18:57:51 -04:00
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
it('should mark NgForm as submitted on submit event', fakeAsync(() => {
|
|
|
|
const fixture = TestBed.createComponent(NgModelForm);
|
2016-07-25 18:57:51 -04:00
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
tick();
|
|
|
|
const form = fixture.debugElement.children[0].injector.get(NgForm);
|
|
|
|
expect(form.submitted).toBe(false);
|
2016-07-25 18:57:51 -04:00
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
const formEl = fixture.debugElement.query(By.css('form')).nativeElement;
|
|
|
|
dispatchEvent(formEl, 'submit');
|
|
|
|
tick();
|
2016-07-25 18:57:51 -04:00
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
expect(form.submitted).toBe(true);
|
|
|
|
}));
|
2016-08-12 02:27:33 -04:00
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
it('should reset the form to empty when reset event is fired', fakeAsync(() => {
|
|
|
|
const fixture = TestBed.createComponent(NgModelForm);
|
2016-09-09 15:04:38 -04:00
|
|
|
fixture.componentInstance.name = 'should be cleared';
|
2016-08-15 19:37:59 -04:00
|
|
|
fixture.detectChanges();
|
|
|
|
tick();
|
2016-08-12 02:27:33 -04:00
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
const form = fixture.debugElement.children[0].injector.get(NgForm);
|
|
|
|
const formEl = fixture.debugElement.query(By.css('form'));
|
|
|
|
const input = fixture.debugElement.query(By.css('input'));
|
2016-08-12 02:27:33 -04:00
|
|
|
|
2016-09-09 15:04:38 -04:00
|
|
|
expect(input.nativeElement.value).toBe('should be cleared'); // view value
|
|
|
|
expect(fixture.componentInstance.name).toBe('should be cleared'); // ngModel value
|
|
|
|
expect(form.value.name).toEqual('should be cleared'); // control value
|
2016-08-12 02:27:33 -04:00
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
dispatchEvent(formEl.nativeElement, 'reset');
|
|
|
|
fixture.detectChanges();
|
|
|
|
tick();
|
2016-08-12 02:27:33 -04:00
|
|
|
|
2016-09-09 15:04:38 -04:00
|
|
|
expect(input.nativeElement.value).toBe(''); // view value
|
|
|
|
expect(fixture.componentInstance.name).toBe(null); // ngModel value
|
|
|
|
expect(form.value.name).toEqual(null); // control value
|
2016-08-15 19:37:59 -04:00
|
|
|
}));
|
2016-07-25 18:57:51 -04:00
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
it('should reset the form submit state when reset button is clicked', fakeAsync(() => {
|
|
|
|
const fixture = TestBed.createComponent(NgModelForm);
|
|
|
|
const form = fixture.debugElement.children[0].injector.get(NgForm);
|
|
|
|
const formEl = fixture.debugElement.query(By.css('form'));
|
2016-07-25 18:57:51 -04:00
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
dispatchEvent(formEl.nativeElement, 'submit');
|
|
|
|
fixture.detectChanges();
|
|
|
|
tick();
|
|
|
|
expect(form.submitted).toBe(true);
|
2016-07-25 18:57:51 -04:00
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
dispatchEvent(formEl.nativeElement, 'reset');
|
|
|
|
fixture.detectChanges();
|
|
|
|
tick();
|
|
|
|
expect(form.submitted).toBe(false);
|
|
|
|
}));
|
|
|
|
});
|
2016-07-25 18:57:51 -04:00
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
describe('valueChange and statusChange events', () => {
|
|
|
|
it('should emit valueChanges and statusChanges on init', fakeAsync(() => {
|
|
|
|
const fixture = TestBed.createComponent(NgModelForm);
|
|
|
|
const form = fixture.debugElement.children[0].injector.get(NgForm);
|
2016-09-09 15:04:38 -04:00
|
|
|
fixture.componentInstance.name = 'aa';
|
2016-08-15 19:37:59 -04:00
|
|
|
fixture.detectChanges();
|
2016-07-25 18:57:51 -04:00
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
expect(form.valid).toEqual(true);
|
|
|
|
expect(form.value).toEqual({});
|
2016-07-25 18:57:51 -04:00
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
let formValidity: string;
|
|
|
|
let formValue: Object;
|
2016-08-02 18:53:34 -04:00
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
form.statusChanges.subscribe((status: string) => formValidity = status);
|
|
|
|
form.valueChanges.subscribe((value: string) => formValue = value);
|
2016-07-25 18:57:51 -04:00
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
tick();
|
2016-07-25 18:57:51 -04:00
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
expect(formValidity).toEqual('INVALID');
|
|
|
|
expect(formValue).toEqual({name: 'aa'});
|
|
|
|
}));
|
2016-07-28 17:25:33 -04:00
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
it('should mark controls dirty before emitting the value change event', fakeAsync(() => {
|
|
|
|
const fixture = TestBed.createComponent(NgModelForm);
|
2016-07-28 17:25:33 -04:00
|
|
|
const form = fixture.debugElement.children[0].injector.get(NgForm).form;
|
2016-08-15 19:37:59 -04:00
|
|
|
|
2016-07-28 17:25:33 -04:00
|
|
|
fixture.detectChanges();
|
|
|
|
tick();
|
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
form.get('name').valueChanges.subscribe(
|
|
|
|
() => { expect(form.get('name').dirty).toBe(true); });
|
2016-07-28 17:25:33 -04:00
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
const inputEl = fixture.debugElement.query(By.css('input')).nativeElement;
|
|
|
|
inputEl.value = 'newValue';
|
2016-07-28 17:25:33 -04:00
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
dispatchEvent(inputEl, 'input');
|
|
|
|
}));
|
2016-07-28 17:25:33 -04:00
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
it('should mark controls pristine before emitting the value change event when resetting ',
|
|
|
|
fakeAsync(() => {
|
|
|
|
const fixture = TestBed.createComponent(NgModelForm);
|
2016-07-28 17:25:33 -04:00
|
|
|
fixture.detectChanges();
|
2016-08-15 19:37:59 -04:00
|
|
|
tick();
|
2016-07-28 17:25:33 -04:00
|
|
|
|
|
|
|
const form = fixture.debugElement.children[0].injector.get(NgForm).form;
|
|
|
|
const formEl = fixture.debugElement.query(By.css('form')).nativeElement;
|
2016-08-15 19:37:59 -04:00
|
|
|
const inputEl = fixture.debugElement.query(By.css('input')).nativeElement;
|
2016-07-28 17:25:33 -04:00
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
inputEl.value = 'newValue';
|
|
|
|
dispatchEvent(inputEl, 'input');
|
2016-07-28 17:25:33 -04:00
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
expect(form.get('name').pristine).toBe(false);
|
2016-07-28 17:25:33 -04:00
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
form.get('name').valueChanges.subscribe(
|
|
|
|
() => { expect(form.get('name').pristine).toBe(true); });
|
2016-07-28 17:25:33 -04:00
|
|
|
|
|
|
|
dispatchEvent(formEl, 'reset');
|
2016-08-15 19:37:59 -04:00
|
|
|
}));
|
|
|
|
});
|
2016-07-25 18:57:51 -04:00
|
|
|
|
2016-08-24 19:58:43 -04:00
|
|
|
describe('disabled controls', () => {
|
|
|
|
it('should not consider disabled controls in value or validation', fakeAsync(() => {
|
|
|
|
const fixture = TestBed.createComponent(NgModelGroupForm);
|
2016-09-09 15:04:38 -04:00
|
|
|
fixture.componentInstance.isDisabled = false;
|
|
|
|
fixture.componentInstance.first = '';
|
|
|
|
fixture.componentInstance.last = 'Drew';
|
|
|
|
fixture.componentInstance.email = 'some email';
|
2016-08-24 19:58:43 -04:00
|
|
|
fixture.detectChanges();
|
|
|
|
tick();
|
|
|
|
|
|
|
|
const form = fixture.debugElement.children[0].injector.get(NgForm);
|
|
|
|
expect(form.value).toEqual({name: {first: '', last: 'Drew'}, email: 'some email'});
|
|
|
|
expect(form.valid).toBe(false);
|
|
|
|
expect(form.control.get('name.first').disabled).toBe(false);
|
|
|
|
|
|
|
|
fixture.componentInstance.isDisabled = true;
|
|
|
|
fixture.detectChanges();
|
|
|
|
tick();
|
|
|
|
|
|
|
|
expect(form.value).toEqual({name: {last: 'Drew'}, email: 'some email'});
|
|
|
|
expect(form.valid).toBe(true);
|
|
|
|
expect(form.control.get('name.first').disabled).toBe(true);
|
|
|
|
}));
|
|
|
|
|
|
|
|
it('should add disabled attribute in the UI if disable() is called programmatically',
|
|
|
|
fakeAsync(() => {
|
|
|
|
const fixture = TestBed.createComponent(NgModelGroupForm);
|
2016-09-09 15:04:38 -04:00
|
|
|
fixture.componentInstance.isDisabled = false;
|
|
|
|
fixture.componentInstance.first = 'Nancy';
|
2016-08-24 19:58:43 -04:00
|
|
|
fixture.detectChanges();
|
|
|
|
tick();
|
|
|
|
|
|
|
|
const form = fixture.debugElement.children[0].injector.get(NgForm);
|
|
|
|
form.control.get('name.first').disable();
|
|
|
|
fixture.detectChanges();
|
|
|
|
tick();
|
|
|
|
|
|
|
|
const input = fixture.debugElement.query(By.css(`[name="first"]`));
|
|
|
|
expect(input.nativeElement.disabled).toBe(true);
|
|
|
|
}));
|
|
|
|
|
|
|
|
it('should disable a custom control if disabled attr is added', async(() => {
|
|
|
|
const fixture = TestBed.createComponent(NgModelCustomWrapper);
|
2016-09-09 15:04:38 -04:00
|
|
|
fixture.componentInstance.name = 'Nancy';
|
|
|
|
fixture.componentInstance.isDisabled = true;
|
2016-08-24 19:58:43 -04:00
|
|
|
fixture.detectChanges();
|
|
|
|
fixture.whenStable().then(() => {
|
|
|
|
fixture.detectChanges();
|
|
|
|
fixture.whenStable().then(() => {
|
|
|
|
const form = fixture.debugElement.children[0].injector.get(NgForm);
|
|
|
|
expect(form.control.get('name').disabled).toBe(true);
|
|
|
|
|
|
|
|
const customInput = fixture.debugElement.query(By.css('[name="custom"]'));
|
|
|
|
expect(customInput.nativeElement.disabled).toEqual(true);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}));
|
|
|
|
|
2016-09-20 17:55:47 -04:00
|
|
|
it('should disable a control with unbound disabled attr', fakeAsync(() => {
|
|
|
|
TestBed.overrideComponent(NgModelForm, {
|
|
|
|
set: {
|
|
|
|
template: `
|
|
|
|
<form>
|
|
|
|
<input name="name" [(ngModel)]="name" disabled>
|
|
|
|
</form>
|
|
|
|
`,
|
|
|
|
}
|
|
|
|
});
|
|
|
|
const fixture = TestBed.createComponent(NgModelForm);
|
|
|
|
fixture.detectChanges();
|
|
|
|
tick();
|
|
|
|
const form = fixture.debugElement.children[0].injector.get(NgForm);
|
|
|
|
expect(form.control.get('name').disabled).toBe(true);
|
|
|
|
|
|
|
|
const input = fixture.debugElement.query(By.css('input'));
|
|
|
|
expect(input.nativeElement.disabled).toEqual(true);
|
|
|
|
|
|
|
|
form.control.enable();
|
|
|
|
fixture.detectChanges();
|
|
|
|
tick();
|
|
|
|
expect(input.nativeElement.disabled).toEqual(false);
|
|
|
|
}));
|
|
|
|
|
2016-09-20 12:08:12 -04:00
|
|
|
it('should disable radio controls properly with programmatic call', fakeAsync(() => {
|
|
|
|
const fixture = TestBed.createComponent(NgModelRadioForm);
|
|
|
|
fixture.componentInstance.food = 'fish';
|
|
|
|
fixture.detectChanges();
|
|
|
|
tick();
|
|
|
|
|
|
|
|
const form = fixture.debugElement.children[0].injector.get(NgForm);
|
|
|
|
form.control.get('food').disable();
|
|
|
|
tick();
|
|
|
|
|
|
|
|
const inputs = fixture.debugElement.queryAll(By.css('input'));
|
|
|
|
expect(inputs[0].nativeElement.disabled).toBe(true);
|
|
|
|
expect(inputs[1].nativeElement.disabled).toBe(true);
|
|
|
|
expect(inputs[2].nativeElement.disabled).toBe(false);
|
|
|
|
expect(inputs[3].nativeElement.disabled).toBe(false);
|
|
|
|
|
|
|
|
form.control.disable();
|
|
|
|
tick();
|
|
|
|
|
|
|
|
expect(inputs[0].nativeElement.disabled).toBe(true);
|
|
|
|
expect(inputs[1].nativeElement.disabled).toBe(true);
|
|
|
|
expect(inputs[2].nativeElement.disabled).toBe(true);
|
|
|
|
expect(inputs[3].nativeElement.disabled).toBe(true);
|
|
|
|
|
|
|
|
form.control.enable();
|
|
|
|
tick();
|
|
|
|
|
|
|
|
expect(inputs[0].nativeElement.disabled).toBe(false);
|
|
|
|
expect(inputs[1].nativeElement.disabled).toBe(false);
|
|
|
|
expect(inputs[2].nativeElement.disabled).toBe(false);
|
|
|
|
expect(inputs[3].nativeElement.disabled).toBe(false);
|
|
|
|
}));
|
|
|
|
|
2016-08-24 19:58:43 -04:00
|
|
|
});
|
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
describe('radio controls', () => {
|
|
|
|
it('should support <type=radio>', fakeAsync(() => {
|
|
|
|
const fixture = TestBed.createComponent(NgModelRadioForm);
|
2016-09-09 15:04:38 -04:00
|
|
|
fixture.componentInstance.food = 'fish';
|
2016-07-25 18:57:51 -04:00
|
|
|
fixture.detectChanges();
|
|
|
|
tick();
|
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
// model -> view
|
2016-07-25 18:57:51 -04:00
|
|
|
const inputs = fixture.debugElement.queryAll(By.css('input'));
|
|
|
|
expect(inputs[0].nativeElement.checked).toEqual(false);
|
|
|
|
expect(inputs[1].nativeElement.checked).toEqual(true);
|
|
|
|
|
|
|
|
dispatchEvent(inputs[0].nativeElement, 'change');
|
|
|
|
tick();
|
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
// view -> model
|
2016-09-09 15:04:38 -04:00
|
|
|
expect(fixture.componentInstance.food).toEqual('chicken');
|
2016-07-25 18:57:51 -04:00
|
|
|
expect(inputs[1].nativeElement.checked).toEqual(false);
|
2016-08-15 19:37:59 -04:00
|
|
|
}));
|
2016-07-25 18:57:51 -04:00
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
it('should support multiple named <type=radio> groups', fakeAsync(() => {
|
|
|
|
const fixture = TestBed.createComponent(NgModelRadioForm);
|
2016-09-09 15:04:38 -04:00
|
|
|
fixture.componentInstance.food = 'fish';
|
|
|
|
fixture.componentInstance.drink = 'sprite';
|
2016-07-25 18:57:51 -04:00
|
|
|
fixture.detectChanges();
|
|
|
|
tick();
|
|
|
|
|
|
|
|
const inputs = fixture.debugElement.queryAll(By.css('input'));
|
|
|
|
expect(inputs[0].nativeElement.checked).toEqual(false);
|
|
|
|
expect(inputs[1].nativeElement.checked).toEqual(true);
|
|
|
|
expect(inputs[2].nativeElement.checked).toEqual(false);
|
|
|
|
expect(inputs[3].nativeElement.checked).toEqual(true);
|
|
|
|
|
|
|
|
dispatchEvent(inputs[0].nativeElement, 'change');
|
|
|
|
tick();
|
|
|
|
|
2016-09-09 15:04:38 -04:00
|
|
|
expect(fixture.componentInstance.food).toEqual('chicken');
|
|
|
|
expect(fixture.componentInstance.drink).toEqual('sprite');
|
2016-07-25 18:57:51 -04:00
|
|
|
expect(inputs[1].nativeElement.checked).toEqual(false);
|
|
|
|
expect(inputs[2].nativeElement.checked).toEqual(false);
|
|
|
|
expect(inputs[3].nativeElement.checked).toEqual(true);
|
2016-08-15 19:37:59 -04:00
|
|
|
}));
|
2016-09-12 18:15:58 -04:00
|
|
|
|
|
|
|
it('should support initial undefined value', fakeAsync(() => {
|
|
|
|
const fixture = TestBed.createComponent(NgModelRadioForm);
|
|
|
|
fixture.detectChanges();
|
|
|
|
tick();
|
|
|
|
|
|
|
|
const inputs = fixture.debugElement.queryAll(By.css('input'));
|
|
|
|
expect(inputs[0].nativeElement.checked).toEqual(false);
|
|
|
|
expect(inputs[1].nativeElement.checked).toEqual(false);
|
|
|
|
expect(inputs[2].nativeElement.checked).toEqual(false);
|
|
|
|
expect(inputs[3].nativeElement.checked).toEqual(false);
|
|
|
|
}));
|
|
|
|
|
|
|
|
it('should support resetting properly', fakeAsync(() => {
|
|
|
|
const fixture = TestBed.createComponent(NgModelRadioForm);
|
|
|
|
fixture.componentInstance.food = 'chicken';
|
|
|
|
fixture.detectChanges();
|
|
|
|
tick();
|
|
|
|
|
|
|
|
const form = fixture.debugElement.query(By.css('form'));
|
|
|
|
dispatchEvent(form.nativeElement, 'reset');
|
|
|
|
fixture.detectChanges();
|
|
|
|
tick();
|
|
|
|
|
|
|
|
const inputs = fixture.debugElement.queryAll(By.css('input'));
|
|
|
|
expect(inputs[0].nativeElement.checked).toEqual(false);
|
|
|
|
expect(inputs[1].nativeElement.checked).toEqual(false);
|
|
|
|
}));
|
|
|
|
|
|
|
|
it('should support setting value to null and undefined', fakeAsync(() => {
|
|
|
|
const fixture = TestBed.createComponent(NgModelRadioForm);
|
|
|
|
fixture.componentInstance.food = 'chicken';
|
|
|
|
fixture.detectChanges();
|
|
|
|
tick();
|
|
|
|
|
|
|
|
fixture.componentInstance.food = null;
|
|
|
|
fixture.detectChanges();
|
|
|
|
tick();
|
|
|
|
|
|
|
|
const inputs = fixture.debugElement.queryAll(By.css('input'));
|
|
|
|
expect(inputs[0].nativeElement.checked).toEqual(false);
|
|
|
|
expect(inputs[1].nativeElement.checked).toEqual(false);
|
|
|
|
|
|
|
|
fixture.componentInstance.food = 'chicken';
|
|
|
|
fixture.detectChanges();
|
|
|
|
tick();
|
|
|
|
|
|
|
|
fixture.componentInstance.food = undefined;
|
|
|
|
fixture.detectChanges();
|
|
|
|
tick();
|
|
|
|
expect(inputs[0].nativeElement.checked).toEqual(false);
|
|
|
|
expect(inputs[1].nativeElement.checked).toEqual(false);
|
|
|
|
}));
|
|
|
|
|
2016-07-25 18:57:51 -04:00
|
|
|
});
|
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
describe('select controls', () => {
|
|
|
|
it('with option values that are objects', fakeAsync(() => {
|
|
|
|
const fixture = TestBed.createComponent(NgModelSelectForm);
|
2016-09-09 15:04:38 -04:00
|
|
|
const comp = fixture.componentInstance;
|
2016-08-15 19:37:59 -04:00
|
|
|
comp.cities = [{'name': 'SF'}, {'name': 'NYC'}, {'name': 'Buffalo'}];
|
|
|
|
comp.selectedCity = comp.cities[1];
|
|
|
|
fixture.detectChanges();
|
|
|
|
tick();
|
|
|
|
|
|
|
|
const select = fixture.debugElement.query(By.css('select'));
|
|
|
|
const nycOption = fixture.debugElement.queryAll(By.css('option'))[1];
|
|
|
|
|
|
|
|
// model -> view
|
|
|
|
expect(select.nativeElement.value).toEqual('1: Object');
|
|
|
|
expect(nycOption.nativeElement.selected).toBe(true);
|
|
|
|
|
|
|
|
select.nativeElement.value = '2: Object';
|
|
|
|
dispatchEvent(select.nativeElement, 'change');
|
|
|
|
fixture.detectChanges();
|
|
|
|
tick();
|
|
|
|
|
|
|
|
// view -> model
|
2016-09-09 15:04:38 -04:00
|
|
|
expect(comp.selectedCity['name']).toEqual('Buffalo');
|
2016-08-15 19:37:59 -04:00
|
|
|
}));
|
|
|
|
|
|
|
|
it('when new options are added', fakeAsync(() => {
|
|
|
|
const fixture = TestBed.createComponent(NgModelSelectForm);
|
2016-09-09 15:04:38 -04:00
|
|
|
const comp = fixture.componentInstance;
|
2016-08-15 19:37:59 -04:00
|
|
|
comp.cities = [{'name': 'SF'}, {'name': 'NYC'}];
|
|
|
|
comp.selectedCity = comp.cities[1];
|
|
|
|
fixture.detectChanges();
|
|
|
|
tick();
|
|
|
|
|
|
|
|
comp.cities.push({'name': 'Buffalo'});
|
|
|
|
comp.selectedCity = comp.cities[2];
|
|
|
|
fixture.detectChanges();
|
|
|
|
tick();
|
|
|
|
|
|
|
|
const select = fixture.debugElement.query(By.css('select'));
|
|
|
|
const buffalo = fixture.debugElement.queryAll(By.css('option'))[2];
|
|
|
|
expect(select.nativeElement.value).toEqual('2: Object');
|
|
|
|
expect(buffalo.nativeElement.selected).toBe(true);
|
|
|
|
}));
|
|
|
|
|
|
|
|
it('when options are removed', fakeAsync(() => {
|
|
|
|
const fixture = TestBed.createComponent(NgModelSelectForm);
|
2016-09-09 15:04:38 -04:00
|
|
|
const comp = fixture.componentInstance;
|
2016-08-15 19:37:59 -04:00
|
|
|
comp.cities = [{'name': 'SF'}, {'name': 'NYC'}];
|
|
|
|
comp.selectedCity = comp.cities[1];
|
|
|
|
fixture.detectChanges();
|
|
|
|
tick();
|
|
|
|
|
|
|
|
const select = fixture.debugElement.query(By.css('select'));
|
|
|
|
expect(select.nativeElement.value).toEqual('1: Object');
|
|
|
|
|
|
|
|
comp.cities.pop();
|
|
|
|
fixture.detectChanges();
|
|
|
|
tick();
|
|
|
|
|
|
|
|
expect(select.nativeElement.value).not.toEqual('1: Object');
|
|
|
|
}));
|
|
|
|
|
|
|
|
it('when option values have same content, but different identities', fakeAsync(() => {
|
|
|
|
const fixture = TestBed.createComponent(NgModelSelectForm);
|
2016-09-09 15:04:38 -04:00
|
|
|
const comp = fixture.componentInstance;
|
2016-08-15 19:37:59 -04:00
|
|
|
comp.cities = [{'name': 'SF'}, {'name': 'NYC'}, {'name': 'NYC'}];
|
|
|
|
comp.selectedCity = comp.cities[0];
|
|
|
|
fixture.detectChanges();
|
|
|
|
|
|
|
|
comp.selectedCity = comp.cities[2];
|
|
|
|
fixture.detectChanges();
|
|
|
|
tick();
|
|
|
|
|
|
|
|
const select = fixture.debugElement.query(By.css('select'));
|
|
|
|
const secondNYC = fixture.debugElement.queryAll(By.css('option'))[2];
|
|
|
|
expect(select.nativeElement.value).toEqual('2: Object');
|
|
|
|
expect(secondNYC.nativeElement.selected).toBe(true);
|
|
|
|
}));
|
2016-07-25 18:57:51 -04:00
|
|
|
});
|
|
|
|
|
2016-08-24 19:58:43 -04:00
|
|
|
describe('custom value accessors', () => {
|
|
|
|
it('should support standard writing to view and model', async(() => {
|
|
|
|
const fixture = TestBed.createComponent(NgModelCustomWrapper);
|
2016-09-09 15:04:38 -04:00
|
|
|
fixture.componentInstance.name = 'Nancy';
|
2016-08-24 19:58:43 -04:00
|
|
|
fixture.detectChanges();
|
|
|
|
fixture.whenStable().then(() => {
|
|
|
|
fixture.detectChanges();
|
|
|
|
fixture.whenStable().then(() => {
|
|
|
|
// model -> view
|
|
|
|
const customInput = fixture.debugElement.query(By.css('[name="custom"]'));
|
|
|
|
expect(customInput.nativeElement.value).toEqual('Nancy');
|
|
|
|
|
|
|
|
customInput.nativeElement.value = 'Carson';
|
|
|
|
dispatchEvent(customInput.nativeElement, 'input');
|
|
|
|
fixture.detectChanges();
|
|
|
|
|
|
|
|
// view -> model
|
2016-09-09 15:04:38 -04:00
|
|
|
expect(fixture.componentInstance.name).toEqual('Carson');
|
2016-08-24 19:58:43 -04:00
|
|
|
});
|
|
|
|
});
|
|
|
|
}));
|
|
|
|
|
|
|
|
});
|
|
|
|
|
2016-08-29 14:33:49 -04:00
|
|
|
describe('validation directives', () => {
|
|
|
|
|
|
|
|
it('should support dir validators using bindings', fakeAsync(() => {
|
|
|
|
const fixture = TestBed.createComponent(NgModelValidationBindings);
|
2016-09-09 15:04:38 -04:00
|
|
|
fixture.componentInstance.required = true;
|
|
|
|
fixture.componentInstance.minLen = 3;
|
|
|
|
fixture.componentInstance.maxLen = 3;
|
|
|
|
fixture.componentInstance.pattern = '.{3,}';
|
2016-08-29 14:33:49 -04:00
|
|
|
fixture.detectChanges();
|
|
|
|
tick();
|
|
|
|
|
|
|
|
const required = fixture.debugElement.query(By.css('[name=required]'));
|
|
|
|
const minLength = fixture.debugElement.query(By.css('[name=minlength]'));
|
|
|
|
const maxLength = fixture.debugElement.query(By.css('[name=maxlength]'));
|
|
|
|
const pattern = fixture.debugElement.query(By.css('[name=pattern]'));
|
|
|
|
|
|
|
|
required.nativeElement.value = '';
|
|
|
|
minLength.nativeElement.value = '1';
|
|
|
|
maxLength.nativeElement.value = '1234';
|
|
|
|
pattern.nativeElement.value = '12';
|
|
|
|
|
|
|
|
dispatchEvent(required.nativeElement, 'input');
|
|
|
|
dispatchEvent(minLength.nativeElement, 'input');
|
|
|
|
dispatchEvent(maxLength.nativeElement, 'input');
|
|
|
|
dispatchEvent(pattern.nativeElement, 'input');
|
|
|
|
fixture.detectChanges();
|
|
|
|
|
|
|
|
const form = fixture.debugElement.children[0].injector.get(NgForm);
|
|
|
|
expect(form.control.hasError('required', ['required'])).toEqual(true);
|
|
|
|
expect(form.control.hasError('minlength', ['minlength'])).toEqual(true);
|
|
|
|
expect(form.control.hasError('maxlength', ['maxlength'])).toEqual(true);
|
|
|
|
expect(form.control.hasError('pattern', ['pattern'])).toEqual(true);
|
|
|
|
|
|
|
|
required.nativeElement.value = '1';
|
|
|
|
minLength.nativeElement.value = '123';
|
|
|
|
maxLength.nativeElement.value = '123';
|
|
|
|
pattern.nativeElement.value = '123';
|
|
|
|
|
|
|
|
dispatchEvent(required.nativeElement, 'input');
|
|
|
|
dispatchEvent(minLength.nativeElement, 'input');
|
|
|
|
dispatchEvent(maxLength.nativeElement, 'input');
|
|
|
|
dispatchEvent(pattern.nativeElement, 'input');
|
|
|
|
|
|
|
|
expect(form.valid).toEqual(true);
|
|
|
|
}));
|
|
|
|
|
2016-10-10 12:17:45 -04:00
|
|
|
it('should support optional fields with pattern validator', fakeAsync(() => {
|
|
|
|
const fixture = TestBed.createComponent(NgModelMultipleValidators);
|
|
|
|
fixture.componentInstance.required = false;
|
|
|
|
fixture.componentInstance.pattern = '[a-z]+';
|
|
|
|
fixture.detectChanges();
|
|
|
|
tick();
|
|
|
|
|
|
|
|
const form = fixture.debugElement.children[0].injector.get(NgForm);
|
|
|
|
const input = fixture.debugElement.query(By.css('input'));
|
|
|
|
|
|
|
|
input.nativeElement.value = '';
|
|
|
|
dispatchEvent(input.nativeElement, 'input');
|
|
|
|
fixture.detectChanges();
|
|
|
|
expect(form.valid).toBeTruthy();
|
|
|
|
|
|
|
|
input.nativeElement.value = '1';
|
|
|
|
dispatchEvent(input.nativeElement, 'input');
|
|
|
|
fixture.detectChanges();
|
|
|
|
expect(form.valid).toBeFalsy();
|
|
|
|
expect(form.control.hasError('pattern', ['tovalidate'])).toBeTruthy();
|
|
|
|
}));
|
|
|
|
|
|
|
|
it('should support optional fields with minlength validator', fakeAsync(() => {
|
|
|
|
const fixture = TestBed.createComponent(NgModelMultipleValidators);
|
|
|
|
fixture.componentInstance.required = false;
|
|
|
|
fixture.componentInstance.minLen = 2;
|
|
|
|
fixture.detectChanges();
|
|
|
|
tick();
|
|
|
|
|
|
|
|
const form = fixture.debugElement.children[0].injector.get(NgForm);
|
|
|
|
const input = fixture.debugElement.query(By.css('input'));
|
|
|
|
|
|
|
|
input.nativeElement.value = '';
|
|
|
|
dispatchEvent(input.nativeElement, 'input');
|
|
|
|
fixture.detectChanges();
|
|
|
|
expect(form.valid).toBeTruthy();
|
|
|
|
|
|
|
|
input.nativeElement.value = '1';
|
|
|
|
dispatchEvent(input.nativeElement, 'input');
|
|
|
|
fixture.detectChanges();
|
|
|
|
expect(form.valid).toBeFalsy();
|
|
|
|
expect(form.control.hasError('minlength', ['tovalidate'])).toBeTruthy();
|
|
|
|
}));
|
|
|
|
|
2016-08-29 14:33:49 -04:00
|
|
|
it('changes on bound properties should change the validation state of the form',
|
|
|
|
fakeAsync(() => {
|
|
|
|
const fixture = TestBed.createComponent(NgModelValidationBindings);
|
|
|
|
fixture.detectChanges();
|
|
|
|
tick();
|
|
|
|
|
|
|
|
const required = fixture.debugElement.query(By.css('[name=required]'));
|
|
|
|
const minLength = fixture.debugElement.query(By.css('[name=minlength]'));
|
|
|
|
const maxLength = fixture.debugElement.query(By.css('[name=maxlength]'));
|
|
|
|
const pattern = fixture.debugElement.query(By.css('[name=pattern]'));
|
|
|
|
|
|
|
|
required.nativeElement.value = '';
|
|
|
|
minLength.nativeElement.value = '1';
|
|
|
|
maxLength.nativeElement.value = '1234';
|
|
|
|
pattern.nativeElement.value = '12';
|
|
|
|
|
|
|
|
dispatchEvent(required.nativeElement, 'input');
|
|
|
|
dispatchEvent(minLength.nativeElement, 'input');
|
|
|
|
dispatchEvent(maxLength.nativeElement, 'input');
|
|
|
|
dispatchEvent(pattern.nativeElement, 'input');
|
|
|
|
|
|
|
|
const form = fixture.debugElement.children[0].injector.get(NgForm);
|
|
|
|
expect(form.control.hasError('required', ['required'])).toEqual(false);
|
|
|
|
expect(form.control.hasError('minlength', ['minlength'])).toEqual(false);
|
|
|
|
expect(form.control.hasError('maxlength', ['maxlength'])).toEqual(false);
|
|
|
|
expect(form.control.hasError('pattern', ['pattern'])).toEqual(false);
|
|
|
|
expect(form.valid).toEqual(true);
|
|
|
|
|
2016-09-09 15:04:38 -04:00
|
|
|
fixture.componentInstance.required = true;
|
|
|
|
fixture.componentInstance.minLen = 3;
|
|
|
|
fixture.componentInstance.maxLen = 3;
|
|
|
|
fixture.componentInstance.pattern = '.{3,}';
|
2016-08-29 14:33:49 -04:00
|
|
|
fixture.detectChanges();
|
|
|
|
|
|
|
|
dispatchEvent(required.nativeElement, 'input');
|
|
|
|
dispatchEvent(minLength.nativeElement, 'input');
|
|
|
|
dispatchEvent(maxLength.nativeElement, 'input');
|
|
|
|
dispatchEvent(pattern.nativeElement, 'input');
|
|
|
|
|
|
|
|
expect(form.control.hasError('required', ['required'])).toEqual(true);
|
|
|
|
expect(form.control.hasError('minlength', ['minlength'])).toEqual(true);
|
|
|
|
expect(form.control.hasError('maxlength', ['maxlength'])).toEqual(true);
|
|
|
|
expect(form.control.hasError('pattern', ['pattern'])).toEqual(true);
|
|
|
|
expect(form.valid).toEqual(false);
|
|
|
|
|
|
|
|
expect(required.nativeElement.getAttribute('required')).toEqual('');
|
2016-09-09 15:04:38 -04:00
|
|
|
expect(fixture.componentInstance.minLen.toString())
|
2016-08-29 14:33:49 -04:00
|
|
|
.toEqual(minLength.nativeElement.getAttribute('minlength'));
|
2016-09-09 15:04:38 -04:00
|
|
|
expect(fixture.componentInstance.maxLen.toString())
|
2016-08-29 14:33:49 -04:00
|
|
|
.toEqual(maxLength.nativeElement.getAttribute('maxlength'));
|
2016-09-09 15:04:38 -04:00
|
|
|
expect(fixture.componentInstance.pattern.toString())
|
2016-08-29 14:33:49 -04:00
|
|
|
.toEqual(pattern.nativeElement.getAttribute('pattern'));
|
|
|
|
|
2016-09-09 15:04:38 -04:00
|
|
|
fixture.componentInstance.required = false;
|
|
|
|
fixture.componentInstance.minLen = null;
|
|
|
|
fixture.componentInstance.maxLen = null;
|
|
|
|
fixture.componentInstance.pattern = null;
|
2016-08-29 14:33:49 -04:00
|
|
|
fixture.detectChanges();
|
|
|
|
|
|
|
|
expect(form.control.hasError('required', ['required'])).toEqual(false);
|
|
|
|
expect(form.control.hasError('minlength', ['minlength'])).toEqual(false);
|
|
|
|
expect(form.control.hasError('maxlength', ['maxlength'])).toEqual(false);
|
|
|
|
expect(form.control.hasError('pattern', ['pattern'])).toEqual(false);
|
|
|
|
expect(form.valid).toEqual(true);
|
|
|
|
|
|
|
|
expect(required.nativeElement.getAttribute('required')).toEqual(null);
|
|
|
|
expect(required.nativeElement.getAttribute('minlength')).toEqual(null);
|
|
|
|
expect(required.nativeElement.getAttribute('maxlength')).toEqual(null);
|
|
|
|
expect(required.nativeElement.getAttribute('pattern')).toEqual(null);
|
|
|
|
}));
|
|
|
|
|
|
|
|
});
|
|
|
|
|
2016-07-25 18:57:51 -04:00
|
|
|
describe('ngModel corner cases', () => {
|
|
|
|
it('should update the view when the model is set back to what used to be in the view',
|
2016-08-15 19:37:59 -04:00
|
|
|
fakeAsync(() => {
|
|
|
|
const fixture = TestBed.createComponent(StandaloneNgModel);
|
2016-09-09 15:04:38 -04:00
|
|
|
fixture.componentInstance.name = '';
|
2016-07-25 18:57:51 -04:00
|
|
|
fixture.detectChanges();
|
2016-08-15 19:37:59 -04:00
|
|
|
tick();
|
2016-07-25 18:57:51 -04:00
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
const input = fixture.debugElement.query(By.css('input')).nativeElement;
|
2016-07-25 18:57:51 -04:00
|
|
|
input.value = 'aa';
|
|
|
|
input.selectionStart = 1;
|
|
|
|
dispatchEvent(input, 'input');
|
|
|
|
|
|
|
|
fixture.detectChanges();
|
|
|
|
tick();
|
2016-09-09 15:04:38 -04:00
|
|
|
expect(fixture.componentInstance.name).toEqual('aa');
|
2016-07-25 18:57:51 -04:00
|
|
|
|
|
|
|
// Programmatically update the input value to be "bb".
|
2016-09-09 15:04:38 -04:00
|
|
|
fixture.componentInstance.name = 'bb';
|
2016-07-25 18:57:51 -04:00
|
|
|
fixture.detectChanges();
|
|
|
|
tick();
|
|
|
|
expect(input.value).toEqual('bb');
|
|
|
|
|
|
|
|
// Programatically set it back to "aa".
|
2016-09-09 15:04:38 -04:00
|
|
|
fixture.componentInstance.name = 'aa';
|
2016-07-25 18:57:51 -04:00
|
|
|
fixture.detectChanges();
|
|
|
|
tick();
|
|
|
|
expect(input.value).toEqual('aa');
|
2016-08-15 19:37:59 -04:00
|
|
|
}));
|
2016-07-25 18:57:51 -04:00
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
it('should not crash when validity is checked from a binding', fakeAsync(() => {
|
|
|
|
const fixture = TestBed.createComponent(NgModelValidBinding);
|
2016-07-25 18:57:51 -04:00
|
|
|
tick();
|
2016-08-15 19:37:59 -04:00
|
|
|
expect(() => fixture.detectChanges()).not.toThrowError();
|
|
|
|
}));
|
2016-07-25 18:57:51 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
@Component({
|
|
|
|
selector: 'standalone-ng-model',
|
|
|
|
template: `
|
|
|
|
<input type="text" [(ngModel)]="name">
|
|
|
|
`
|
|
|
|
})
|
|
|
|
class StandaloneNgModel {
|
|
|
|
name: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Component({
|
|
|
|
selector: 'ng-model-form',
|
|
|
|
template: `
|
2016-10-11 18:49:36 -04:00
|
|
|
<form (ngSubmit)="event=$event" (reset)="onReset()">
|
2016-08-15 19:37:59 -04:00
|
|
|
<input name="name" [(ngModel)]="name" minlength="10" [ngModelOptions]="options">
|
|
|
|
</form>
|
|
|
|
`
|
|
|
|
})
|
|
|
|
class NgModelForm {
|
2016-07-25 18:57:51 -04:00
|
|
|
name: string;
|
2016-10-11 18:49:36 -04:00
|
|
|
event: Event;
|
2016-08-15 19:37:59 -04:00
|
|
|
options = {};
|
2016-08-24 19:58:43 -04:00
|
|
|
|
|
|
|
onReset() {}
|
2016-08-15 19:37:59 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
@Component({
|
|
|
|
selector: 'ng-model-group-form',
|
|
|
|
template: `
|
|
|
|
<form>
|
|
|
|
<div ngModelGroup="name">
|
2016-08-24 19:58:43 -04:00
|
|
|
<input name="first" [(ngModel)]="first" required [disabled]="isDisabled">
|
2016-08-15 19:37:59 -04:00
|
|
|
<input name="last" [(ngModel)]="last">
|
|
|
|
</div>
|
|
|
|
<input name="email" [(ngModel)]="email">
|
|
|
|
</form>
|
|
|
|
`
|
|
|
|
})
|
|
|
|
class NgModelGroupForm {
|
|
|
|
first: string;
|
|
|
|
last: string;
|
|
|
|
email: string;
|
2016-08-24 19:58:43 -04:00
|
|
|
isDisabled: boolean;
|
2016-08-15 19:37:59 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
@Component({
|
2016-08-24 19:58:43 -04:00
|
|
|
selector: 'ng-model-valid-binding',
|
2016-08-15 19:37:59 -04:00
|
|
|
template: `
|
|
|
|
<form>
|
|
|
|
<div ngModelGroup="name" #group="ngModelGroup">
|
|
|
|
<input name="first" [(ngModel)]="first" required>
|
|
|
|
{{ group.valid }}
|
|
|
|
</div>
|
|
|
|
</form>
|
|
|
|
`
|
|
|
|
})
|
|
|
|
class NgModelValidBinding {
|
|
|
|
first: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Component({
|
|
|
|
selector: 'ng-model-ngif-form',
|
|
|
|
template: `
|
|
|
|
<form>
|
|
|
|
<div ngModelGroup="name" *ngIf="groupShowing">
|
|
|
|
<input name="first" [(ngModel)]="first">
|
|
|
|
</div>
|
|
|
|
<input name="email" [(ngModel)]="email" *ngIf="emailShowing">
|
|
|
|
</form>
|
|
|
|
`
|
|
|
|
})
|
|
|
|
class NgModelNgIfForm {
|
|
|
|
first: string;
|
|
|
|
groupShowing = true;
|
|
|
|
emailShowing = true;
|
2016-09-09 15:04:38 -04:00
|
|
|
email: string;
|
2016-08-15 19:37:59 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
@Component({
|
|
|
|
selector: 'ng-no-form',
|
|
|
|
template: `
|
|
|
|
<form ngNoForm>
|
|
|
|
<input name="name">
|
|
|
|
</form>
|
|
|
|
`
|
|
|
|
})
|
|
|
|
class NgNoFormComp {
|
|
|
|
}
|
|
|
|
|
|
|
|
@Component({
|
|
|
|
selector: 'invalid-ng-model-noname',
|
|
|
|
template: `
|
|
|
|
<form>
|
|
|
|
<input [(ngModel)]="name">
|
|
|
|
</form>
|
|
|
|
`
|
|
|
|
})
|
|
|
|
class InvalidNgModelNoName {
|
|
|
|
}
|
|
|
|
|
|
|
|
@Component({
|
|
|
|
selector: 'ng-model-options-standalone',
|
|
|
|
template: `
|
|
|
|
<form>
|
|
|
|
<input name="one" [(ngModel)]="one">
|
|
|
|
<input [(ngModel)]="two" [ngModelOptions]="{standalone: true}">
|
|
|
|
</form>
|
|
|
|
`
|
|
|
|
})
|
|
|
|
class NgModelOptionsStandalone {
|
|
|
|
one: string;
|
|
|
|
two: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Component({
|
|
|
|
selector: 'ng-model-radio-form',
|
|
|
|
template: `
|
|
|
|
<form>
|
|
|
|
<input type="radio" name="food" [(ngModel)]="food" value="chicken">
|
|
|
|
<input type="radio" name="food" [(ngModel)]="food" value="fish">
|
2016-09-20 12:08:12 -04:00
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
<input type="radio" name="drink" [(ngModel)]="drink" value="cola">
|
|
|
|
<input type="radio" name="drink" [(ngModel)]="drink" value="sprite">
|
|
|
|
</form>
|
|
|
|
`
|
|
|
|
})
|
|
|
|
class NgModelRadioForm {
|
|
|
|
food: string;
|
|
|
|
drink: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Component({
|
|
|
|
selector: 'ng-model-select-form',
|
|
|
|
template: `
|
|
|
|
<select [(ngModel)]="selectedCity">
|
|
|
|
<option *ngFor="let c of cities" [ngValue]="c"> {{c.name}} </option>
|
|
|
|
</select>
|
|
|
|
`
|
|
|
|
})
|
|
|
|
class NgModelSelectForm {
|
2016-09-09 15:04:38 -04:00
|
|
|
selectedCity: {[k: string]: string} = {};
|
2016-08-15 19:37:59 -04:00
|
|
|
cities: any[] = [];
|
2016-07-25 18:57:51 -04:00
|
|
|
}
|
|
|
|
|
2016-08-24 19:58:43 -04:00
|
|
|
@Component({
|
|
|
|
selector: 'ng-model-custom-comp',
|
|
|
|
template: `
|
|
|
|
<input name="custom" [(ngModel)]="model" (ngModelChange)="changeFn($event)" [disabled]="isDisabled">
|
|
|
|
`,
|
|
|
|
providers: [{provide: NG_VALUE_ACCESSOR, multi: true, useExisting: NgModelCustomComp}]
|
|
|
|
})
|
|
|
|
class NgModelCustomComp implements ControlValueAccessor {
|
|
|
|
model: string;
|
|
|
|
@Input('disabled') isDisabled: boolean = false;
|
|
|
|
changeFn: (value: any) => void;
|
|
|
|
|
|
|
|
writeValue(value: any) { this.model = value; }
|
|
|
|
|
|
|
|
registerOnChange(fn: (value: any) => void) { this.changeFn = fn; }
|
|
|
|
|
|
|
|
registerOnTouched() {}
|
|
|
|
|
|
|
|
setDisabledState(isDisabled: boolean) { this.isDisabled = isDisabled; }
|
|
|
|
}
|
|
|
|
|
|
|
|
@Component({
|
|
|
|
selector: 'ng-model-custom-wrapper',
|
|
|
|
template: `
|
|
|
|
<form>
|
|
|
|
<ng-model-custom-comp name="name" [(ngModel)]="name" [disabled]="isDisabled"></ng-model-custom-comp>
|
|
|
|
</form>
|
|
|
|
`
|
|
|
|
})
|
|
|
|
class NgModelCustomWrapper {
|
|
|
|
name: string;
|
|
|
|
isDisabled = false;
|
|
|
|
}
|
|
|
|
|
2016-08-29 14:33:49 -04:00
|
|
|
@Component({
|
|
|
|
selector: 'ng-model-validation-bindings',
|
|
|
|
template: `
|
|
|
|
<form>
|
|
|
|
<input name="required" ngModel [required]="required">
|
|
|
|
<input name="minlength" ngModel [minlength]="minLen">
|
|
|
|
<input name="maxlength" ngModel [maxlength]="maxLen">
|
|
|
|
<input name="pattern" ngModel [pattern]="pattern">
|
|
|
|
</form>
|
|
|
|
`
|
|
|
|
})
|
|
|
|
class NgModelValidationBindings {
|
|
|
|
required: boolean;
|
|
|
|
minLen: number;
|
|
|
|
maxLen: number;
|
|
|
|
pattern: string;
|
|
|
|
}
|
|
|
|
|
2016-10-10 12:17:45 -04:00
|
|
|
@Component({
|
|
|
|
selector: 'ng-model-multiple-validators',
|
|
|
|
template: `
|
|
|
|
<form>
|
|
|
|
<input name="tovalidate" ngModel [required]="required" [minlength]="minLen" [pattern]="pattern">
|
|
|
|
</form>
|
|
|
|
`
|
|
|
|
})
|
|
|
|
class NgModelMultipleValidators {
|
|
|
|
required: boolean;
|
|
|
|
minLen: number;
|
|
|
|
pattern: string;
|
|
|
|
}
|
|
|
|
|
2016-08-15 19:37:59 -04:00
|
|
|
function sortedClassList(el: HTMLElement) {
|
2016-09-09 15:04:38 -04:00
|
|
|
const l = getDOM().classList(el);
|
|
|
|
l.sort();
|
2016-07-25 18:57:51 -04:00
|
|
|
return l;
|
|
|
|
}
|