refactor(forms): move value accessor tests into own spec (#18356)

PR Close #18356
This commit is contained in:
Kara Erickson
2017-07-26 11:24:47 -07:00
committed by Miško Hevery
parent d20ac14fe2
commit fae47d86b3
3 changed files with 1343 additions and 1281 deletions

View File

@ -6,12 +6,13 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Component, Directive, Input, Type, forwardRef} from '@angular/core';
import {Component, Directive, Type, forwardRef} from '@angular/core';
import {ComponentFixture, TestBed, async, fakeAsync, tick} from '@angular/core/testing';
import {AbstractControl, AsyncValidator, COMPOSITION_BUFFER_MODE, ControlValueAccessor, FormsModule, NG_ASYNC_VALIDATORS, NG_VALUE_ACCESSOR, NgForm} from '@angular/forms';
import {AbstractControl, AsyncValidator, COMPOSITION_BUFFER_MODE, FormsModule, NG_ASYNC_VALIDATORS, NgForm} from '@angular/forms';
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/src/browser_util';
import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integration_spec';
export function main() {
describe('template-driven forms integration tests', () => {
@ -488,393 +489,6 @@ export function main() {
expect(input.nativeElement.disabled).toEqual(false);
}));
it('should disable radio controls properly with programmatic call', fakeAsync(() => {
const fixture = initTest(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);
}));
});
describe('range control', () => {
it('should support <type=range>', fakeAsync(() => {
const fixture = initTest(NgModelRangeForm);
// model -> view
fixture.componentInstance.val = 4;
fixture.detectChanges();
tick();
const input = fixture.debugElement.query(By.css('input'));
expect(input.nativeElement.value).toBe('4');
fixture.detectChanges();
tick();
const newVal = '4';
input.triggerEventHandler('input', {target: {value: newVal}});
tick();
// view -> model
fixture.detectChanges();
expect(typeof(fixture.componentInstance.val)).toBe('number');
}));
});
describe('radio controls', () => {
it('should support <type=radio>', fakeAsync(() => {
const fixture = initTest(NgModelRadioForm);
fixture.componentInstance.food = 'fish';
fixture.detectChanges();
tick();
// model -> view
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();
// view -> model
expect(fixture.componentInstance.food).toEqual('chicken');
expect(inputs[1].nativeElement.checked).toEqual(false);
}));
it('should support multiple named <type=radio> groups', fakeAsync(() => {
const fixture = initTest(NgModelRadioForm);
fixture.componentInstance.food = 'fish';
fixture.componentInstance.drink = 'sprite';
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();
expect(fixture.componentInstance.food).toEqual('chicken');
expect(fixture.componentInstance.drink).toEqual('sprite');
expect(inputs[1].nativeElement.checked).toEqual(false);
expect(inputs[2].nativeElement.checked).toEqual(false);
expect(inputs[3].nativeElement.checked).toEqual(true);
}));
it('should support initial undefined value', fakeAsync(() => {
const fixture = initTest(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 = initTest(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 = initTest(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);
}));
});
describe('select controls', () => {
it('with option values that are objects', fakeAsync(() => {
const fixture = initTest(NgModelSelectForm);
const comp = fixture.componentInstance;
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
expect(comp.selectedCity['name']).toEqual('Buffalo');
}));
it('when new options are added', fakeAsync(() => {
const fixture = initTest(NgModelSelectForm);
const comp = fixture.componentInstance;
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 = initTest(NgModelSelectForm);
const comp = fixture.componentInstance;
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 = initTest(NgModelSelectForm);
const comp = fixture.componentInstance;
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);
}));
it('should work with null option', fakeAsync(() => {
const fixture = initTest(NgModelSelectWithNullForm);
const comp = fixture.componentInstance;
comp.cities = [{'name': 'SF'}, {'name': 'NYC'}];
comp.selectedCity = null !;
fixture.detectChanges();
const select = fixture.debugElement.query(By.css('select'));
select.nativeElement.value = '2: Object';
dispatchEvent(select.nativeElement, 'change');
fixture.detectChanges();
tick();
expect(comp.selectedCity['name']).toEqual('NYC');
select.nativeElement.value = '0: null';
dispatchEvent(select.nativeElement, 'change');
fixture.detectChanges();
tick();
expect(comp.selectedCity).toEqual(null);
}));
it('should throw an error when compareWith is not a function', () => {
const fixture = initTest(NgModelSelectWithCustomCompareFnForm);
const comp = fixture.componentInstance;
comp.compareFn = null !;
expect(() => fixture.detectChanges())
.toThrowError(/compareWith must be a function, but received null/);
});
it('should compare options using provided compareWith function', fakeAsync(() => {
const fixture = initTest(NgModelSelectWithCustomCompareFnForm);
const comp = fixture.componentInstance;
comp.selectedCity = {id: 1, name: 'SF'};
comp.cities = [{id: 1, name: 'SF'}, {id: 2, name: 'LA'}];
fixture.detectChanges();
tick();
const select = fixture.debugElement.query(By.css('select'));
const sfOption = fixture.debugElement.query(By.css('option'));
expect(select.nativeElement.value).toEqual('0: Object');
expect(sfOption.nativeElement.selected).toBe(true);
}));
});
describe('select multiple controls', () => {
describe('select options', () => {
let fixture: ComponentFixture<NgModelSelectMultipleForm>;
let comp: NgModelSelectMultipleForm;
beforeEach(() => {
fixture = initTest(NgModelSelectMultipleForm);
comp = fixture.componentInstance;
comp.cities = [{'name': 'SF'}, {'name': 'NYC'}, {'name': 'Buffalo'}];
});
const detectChangesAndTick = (): void => {
fixture.detectChanges();
tick();
};
const setSelectedCities = (selectedCities: any): void => {
comp.selectedCities = selectedCities;
detectChangesAndTick();
};
const selectOptionViaUI = (valueString: string): void => {
const select = fixture.debugElement.query(By.css('select'));
select.nativeElement.value = valueString;
dispatchEvent(select.nativeElement, 'change');
detectChangesAndTick();
};
const assertOptionElementSelectedState = (selectedStates: boolean[]): void => {
const options = fixture.debugElement.queryAll(By.css('option'));
if (options.length !== selectedStates.length) {
throw 'the selected state values to assert does not match the number of options';
}
for (let i = 0; i < selectedStates.length; i++) {
expect(options[i].nativeElement.selected).toBe(selectedStates[i]);
}
};
it('should reflect state of model after option selected and new options subsequently added',
fakeAsync(() => {
setSelectedCities([]);
selectOptionViaUI('1: Object');
assertOptionElementSelectedState([false, true, false]);
comp.cities.push({'name': 'Chicago'});
detectChangesAndTick();
assertOptionElementSelectedState([false, true, false, false]);
}));
it('should reflect state of model after option selected and then other options removed',
fakeAsync(() => {
setSelectedCities([]);
selectOptionViaUI('1: Object');
assertOptionElementSelectedState([false, true, false]);
comp.cities.pop();
detectChangesAndTick();
assertOptionElementSelectedState([false, true]);
}));
});
it('should throw an error when compareWith is not a function', () => {
const fixture = initTest(NgModelSelectMultipleWithCustomCompareFnForm);
const comp = fixture.componentInstance;
comp.compareFn = null !;
expect(() => fixture.detectChanges())
.toThrowError(/compareWith must be a function, but received null/);
});
it('should compare options using provided compareWith function', fakeAsync(() => {
const fixture = initTest(NgModelSelectMultipleWithCustomCompareFnForm);
const comp = fixture.componentInstance;
comp.cities = [{id: 1, name: 'SF'}, {id: 2, name: 'LA'}];
comp.selectedCities = [comp.cities[0]];
fixture.detectChanges();
tick();
const select = fixture.debugElement.query(By.css('select'));
const sfOption = fixture.debugElement.query(By.css('option'));
expect(select.nativeElement.value).toEqual('0: Object');
expect(sfOption.nativeElement.selected).toBe(true);
}));
});
describe('custom value accessors', () => {
it('should support standard writing to view and model', async(() => {
const fixture = initTest(NgModelCustomWrapper, NgModelCustomComp);
fixture.componentInstance.name = 'Nancy';
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
expect(fixture.componentInstance.name).toEqual('Carson');
});
});
}));
});
describe('validation directives', () => {
@ -1381,132 +995,6 @@ class NgModelOptionsStandalone {
two: string;
}
@Component({selector: 'ng-model-range-form', template: '<input type="range" [(ngModel)]="val">'})
class NgModelRangeForm {
val: any;
}
@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">
<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 {
selectedCity: {[k: string]: string} = {};
cities: any[] = [];
}
@Component({
selector: 'ng-model-select-null-form',
template: `
<select [(ngModel)]="selectedCity">
<option *ngFor="let c of cities" [ngValue]="c"> {{c.name}} </option>
<option [ngValue]="null">Unspecified</option>
</select>
`
})
class NgModelSelectWithNullForm {
selectedCity: {[k: string]: string} = {};
cities: any[] = [];
}
@Component({
selector: 'ng-model-select-compare-with',
template: `
<select [(ngModel)]="selectedCity" [compareWith]="compareFn">
<option *ngFor="let c of cities" [ngValue]="c"> {{c.name}} </option>
</select>
`
})
class NgModelSelectWithCustomCompareFnForm {
compareFn:
(o1: any, o2: any) => boolean = (o1: any, o2: any) => o1 && o2? o1.id === o2.id: o1 === o2;
selectedCity: any = {};
cities: any[] = [];
}
@Component({
selector: 'ng-model-select-multiple-compare-with',
template: `
<select multiple [(ngModel)]="selectedCities" [compareWith]="compareFn">
<option *ngFor="let c of cities" [ngValue]="c"> {{c.name}} </option>
</select>
`
})
class NgModelSelectMultipleWithCustomCompareFnForm {
compareFn:
(o1: any, o2: any) => boolean = (o1: any, o2: any) => o1 && o2? o1.id === o2.id: o1 === o2;
selectedCities: any[] = [];
cities: any[] = [];
}
@Component({
selector: 'ng-model-select-multiple-form',
template: `
<select multiple [(ngModel)]="selectedCities">
<option *ngFor="let c of cities" [ngValue]="c"> {{c.name}} </option>
</select>
`
})
class NgModelSelectMultipleForm {
selectedCities: any[];
cities: any[] = [];
}
@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;
}
@Component({
selector: 'ng-model-validation-bindings',
template: `