2016-06-23 16:47:54 +00:00
/ * *
* @license
2020-05-19 19:08:49 +00:00
* Copyright Google LLC All Rights Reserved .
2016-06-23 16:47:54 +00:00
*
* Use of this source code is governed by an MIT - style license that can be
2024-09-20 15:23:15 +00:00
* found in the LICENSE file at https : //angular.dev/license
2016-06-23 16:47:54 +00:00
* /
2020-05-15 22:37:43 +00:00
import { ChangeDetectorRef , SimpleChange , ɵ Writable as Writable } from '@angular/core' ;
import { fakeAsync , flushMicrotasks , TestBed , tick } from '@angular/core/testing' ;
2024-04-18 17:29:04 +00:00
import {
AbstractControl ,
CheckboxControlValueAccessor ,
2025-04-16 12:29:46 +00:00
ControlEvent ,
2024-04-18 17:29:04 +00:00
ControlValueAccessor ,
DefaultValueAccessor ,
FormArray ,
FormArrayName ,
FormControl ,
FormControlDirective ,
FormControlName ,
FormGroup ,
FormGroupDirective ,
FormGroupName ,
2025-04-16 12:29:46 +00:00
FormResetEvent ,
FormSubmittedEvent ,
2024-04-18 17:29:04 +00:00
NgControl ,
NgForm ,
NgModel ,
NgModelGroup ,
SelectControlValueAccessor ,
SelectMultipleControlValueAccessor ,
2025-04-16 12:29:46 +00:00
StatusChangeEvent ,
TouchedChangeEvent ,
2024-04-18 17:29:04 +00:00
ValidationErrors ,
Validator ,
Validators ,
2025-04-16 12:29:46 +00:00
ValueChangeEvent ,
2024-04-18 17:29:04 +00:00
} from '../index' ;
2020-07-28 02:31:40 +00:00
import { selectValueAccessor } from '../src/directives/shared' ;
import { composeValidators } from '../src/validators' ;
2022-02-24 14:01:19 +00:00
2020-07-12 23:35:09 +00:00
import { asyncValidator } from './util' ;
2016-06-08 22:36:24 +00:00
class DummyControlValueAccessor implements ControlValueAccessor {
2016-06-12 04:23:37 +00:00
writtenValue : any ;
2016-06-08 22:36:24 +00:00
2017-02-23 17:53:29 +00:00
registerOnChange ( fn : any ) { }
registerOnTouched ( fn : any ) { }
2016-06-08 22:36:24 +00:00
2020-04-06 22:44:00 +00:00
writeValue ( obj : any ) : void {
this . writtenValue = obj ;
}
2016-06-08 22:36:24 +00:00
}
class CustomValidatorDirective implements Validator {
2020-04-06 22:44:00 +00:00
validate ( c : FormControl ) : ValidationErrors {
return { 'custom' : true } ;
}
2016-06-08 22:36:24 +00:00
}
2023-10-17 09:45:16 +00:00
describe ( 'Form Directives' , ( ) = > {
let defaultAccessor : DefaultValueAccessor ;
2016-06-08 22:36:24 +00:00
2023-10-17 09:45:16 +00:00
beforeEach ( ( ) = > {
defaultAccessor = new DefaultValueAccessor ( null ! , null ! , null ! ) ;
} ) ;
2016-06-08 22:36:24 +00:00
2023-10-17 09:45:16 +00:00
describe ( 'shared' , ( ) = > {
describe ( 'selectValueAccessor' , ( ) = > {
let dir : NgControl ;
2016-06-08 22:36:24 +00:00
beforeEach ( ( ) = > {
2023-10-17 09:45:16 +00:00
dir = { path : [ ] } as any ;
2016-06-08 22:36:24 +00:00
} ) ;
2023-10-17 09:45:16 +00:00
it ( 'should throw when given an empty array' , ( ) = > {
expect ( ( ) = > selectValueAccessor ( dir , [ ] ) ) . toThrowError ( ) ;
2016-06-08 22:36:24 +00:00
} ) ;
2023-10-17 09:45:16 +00:00
it ( 'should throw when accessor is not provided as array' , ( ) = > {
2024-04-18 17:29:04 +00:00
expect ( ( ) = > selectValueAccessor ( dir , { } as any [ ] ) ) . toThrowError (
'NG01200: Value accessor was not provided as an array for form control with unspecified name attribute. Check that the `NG_VALUE_ACCESSOR` token is configured as a `multi: true` provider.' ,
) ;
2023-10-17 09:45:16 +00:00
} ) ;
2016-10-19 16:49:02 +00:00
2023-10-17 09:45:16 +00:00
it ( 'should return the default value accessor when no other provided' , ( ) = > {
expect ( selectValueAccessor ( dir , [ defaultAccessor ] ) ) . toEqual ( defaultAccessor ) ;
2016-10-19 16:49:02 +00:00
} ) ;
2023-10-17 09:45:16 +00:00
it ( 'should return checkbox accessor when provided' , ( ) = > {
const checkboxAccessor = new CheckboxControlValueAccessor ( null ! , null ! ) ;
2024-04-18 17:29:04 +00:00
expect ( selectValueAccessor ( dir , [ defaultAccessor , checkboxAccessor ] ) ) . toEqual (
checkboxAccessor ,
) ;
2023-10-17 09:45:16 +00:00
} ) ;
2016-06-08 22:36:24 +00:00
2023-10-17 09:45:16 +00:00
it ( 'should return select accessor when provided' , ( ) = > {
2020-05-15 22:37:43 +00:00
TestBed . configureTestingModule ( {
providers : [ { provide : ChangeDetectorRef , useValue : null ! } ] ,
} ) ;
TestBed . runInInjectionContext ( ( ) = > {
const selectAccessor = new SelectControlValueAccessor ( null ! , null ! ) ;
expect ( selectValueAccessor ( dir , [ defaultAccessor , selectAccessor ] ) ) . toEqual (
selectAccessor ,
) ;
} ) ;
2023-10-17 09:45:16 +00:00
} ) ;
2016-06-08 22:36:24 +00:00
2023-10-17 09:45:16 +00:00
it ( 'should return select multiple accessor when provided' , ( ) = > {
const selectMultipleAccessor = new SelectMultipleControlValueAccessor ( null ! , null ! ) ;
2024-04-18 17:29:04 +00:00
expect ( selectValueAccessor ( dir , [ defaultAccessor , selectMultipleAccessor ] ) ) . toEqual (
selectMultipleAccessor ,
) ;
2023-10-17 09:45:16 +00:00
} ) ;
2016-06-08 22:36:24 +00:00
2023-10-17 09:45:16 +00:00
it ( 'should throw when more than one build-in accessor is provided' , ( ) = > {
2020-05-15 22:37:43 +00:00
TestBed . configureTestingModule ( {
providers : [ { provide : ChangeDetectorRef , useValue : null ! } ] ,
} ) ;
TestBed . runInInjectionContext ( ( ) = > {
const checkboxAccessor = new CheckboxControlValueAccessor ( null ! , null ! ) ;
const selectAccessor = new SelectControlValueAccessor ( null ! , null ! ) ;
expect ( ( ) = > selectValueAccessor ( dir , [ checkboxAccessor , selectAccessor ] ) ) . toThrowError ( ) ;
} ) ;
2023-10-17 09:45:16 +00:00
} ) ;
2016-07-13 21:13:02 +00:00
2023-10-17 09:45:16 +00:00
it ( 'should return custom accessor when provided' , ( ) = > {
const customAccessor : ControlValueAccessor = { } as any ;
const checkboxAccessor = new CheckboxControlValueAccessor ( null ! , null ! ) ;
2024-04-18 17:29:04 +00:00
expect (
selectValueAccessor ( dir , < any > [ defaultAccessor , customAccessor , checkboxAccessor ] ) ,
) . toEqual ( customAccessor ) ;
2023-10-17 09:45:16 +00:00
} ) ;
2016-07-13 21:13:02 +00:00
2023-10-17 09:45:16 +00:00
it ( 'should return custom accessor when provided with select multiple' , ( ) = > {
const customAccessor : ControlValueAccessor = { } as any ;
const selectMultipleAccessor = new SelectMultipleControlValueAccessor ( null ! , null ! ) ;
2024-04-18 17:29:04 +00:00
expect (
selectValueAccessor ( dir , < any > [ defaultAccessor , customAccessor , selectMultipleAccessor ] ) ,
) . toEqual ( customAccessor ) ;
2023-10-17 09:45:16 +00:00
} ) ;
2016-06-08 22:36:24 +00:00
2023-10-17 09:45:16 +00:00
it ( 'should throw when more than one custom accessor is provided' , ( ) = > {
const customAccessor : ControlValueAccessor = { } as any ;
expect ( ( ) = > selectValueAccessor ( dir , [ customAccessor , customAccessor ] ) ) . toThrowError ( ) ;
} ) ;
} ) ;
2016-06-08 22:36:24 +00:00
2023-10-17 09:45:16 +00:00
describe ( 'composeValidators' , ( ) = > {
it ( 'should compose functions' , ( ) = > {
const dummy1 = ( ) = > ( { 'dummy1' : true } ) ;
const dummy2 = ( ) = > ( { 'dummy2' : true } ) ;
const v = composeValidators ( [ dummy1 , dummy2 ] ) ! ;
expect ( v ( new FormControl ( '' ) ) ) . toEqual ( { 'dummy1' : true , 'dummy2' : true } ) ;
} ) ;
2016-06-08 22:36:24 +00:00
2023-10-17 09:45:16 +00:00
it ( 'should compose validator directives' , ( ) = > {
const dummy1 = ( ) = > ( { 'dummy1' : true } ) ;
const v = composeValidators ( [ dummy1 , new CustomValidatorDirective ( ) ] ) ! ;
expect ( v ( new FormControl ( '' ) ) ) . toEqual ( { 'dummy1' : true , 'custom' : true } ) ;
} ) ;
} ) ;
} ) ;
2016-06-08 22:36:24 +00:00
2023-10-17 09:45:16 +00:00
describe ( 'formGroup' , ( ) = > {
let form : FormGroupDirective ;
let formModel : FormGroup ;
let loginControlDir : FormControlName ;
2016-06-08 22:36:24 +00:00
2023-10-17 09:45:16 +00:00
beforeEach ( ( ) = > {
form = new FormGroupDirective ( [ ] , [ ] ) ;
formModel = new FormGroup ( {
'login' : new FormControl ( ) ,
2024-04-18 17:29:04 +00:00
'passwords' : new FormGroup ( {
'password' : new FormControl ( ) ,
'passwordConfirm' : new FormControl ( ) ,
} ) ,
2023-10-17 09:45:16 +00:00
} ) ;
form . form = formModel ;
loginControlDir = new FormControlName (
2024-04-18 17:29:04 +00:00
form ,
[ Validators . required ] ,
[ asyncValidator ( 'expected' ) ] ,
[ defaultAccessor ] ,
null ,
) ;
2023-10-17 09:45:16 +00:00
loginControlDir . name = 'login' ;
loginControlDir . valueAccessor = new DummyControlValueAccessor ( ) ;
} ) ;
2016-06-08 22:36:24 +00:00
2023-10-17 09:45:16 +00:00
it ( 'should reexport control properties' , ( ) = > {
expect ( form . control ) . toBe ( formModel ) ;
expect ( form . value ) . toBe ( formModel . value ) ;
expect ( form . valid ) . toBe ( formModel . valid ) ;
expect ( form . invalid ) . toBe ( formModel . invalid ) ;
expect ( form . pending ) . toBe ( formModel . pending ) ;
expect ( form . errors ) . toBe ( formModel . errors ) ;
expect ( form . pristine ) . toBe ( formModel . pristine ) ;
expect ( form . dirty ) . toBe ( formModel . dirty ) ;
expect ( form . touched ) . toBe ( formModel . touched ) ;
expect ( form . untouched ) . toBe ( formModel . untouched ) ;
expect ( form . statusChanges ) . toBe ( formModel . statusChanges ) ;
expect ( form . valueChanges ) . toBe ( formModel . valueChanges ) ;
} ) ;
2016-06-08 22:36:24 +00:00
2023-10-17 09:45:16 +00:00
it ( 'should reexport control methods' , ( ) = > {
expect ( form . hasError ( 'required' ) ) . toBe ( formModel . hasError ( 'required' ) ) ;
expect ( form . getError ( 'required' ) ) . toBe ( formModel . getError ( 'required' ) ) ;
2016-06-08 22:36:24 +00:00
2023-10-17 09:45:16 +00:00
formModel . setErrors ( { required : true } ) ;
expect ( form . hasError ( 'required' ) ) . toBe ( formModel . hasError ( 'required' ) ) ;
expect ( form . getError ( 'required' ) ) . toBe ( formModel . getError ( 'required' ) ) ;
} ) ;
2016-06-08 22:36:24 +00:00
2023-10-17 09:45:16 +00:00
describe ( 'addControl' , ( ) = > {
it ( 'should throw when no control found' , ( ) = > {
const dir = new FormControlName ( form , null ! , null ! , [ defaultAccessor ] , null ) ;
dir . name = 'invalidName' ;
2016-06-08 22:36:24 +00:00
2024-04-18 17:29:04 +00:00
expect ( ( ) = > form . addControl ( dir ) ) . toThrowError (
new RegExp ( ` Cannot find control with name: 'invalidName' ` ) ,
) ;
2016-06-08 22:36:24 +00:00
} ) ;
2023-10-17 09:45:16 +00:00
it ( 'should throw for a named control when no value accessor' , ( ) = > {
const dir = new FormControlName ( form , null ! , null ! , null ! , null ) ;
dir . name = 'login' ;
2024-04-18 17:29:04 +00:00
expect ( ( ) = > form . addControl ( dir ) ) . toThrowError (
new RegExp (
2024-04-25 23:10:52 +00:00
` NG01203: No value accessor for form control name: 'login'. Find more at https://angular.dev/errors/NG01203 ` ,
2024-04-18 17:29:04 +00:00
) ,
) ;
2016-06-08 22:36:24 +00:00
} ) ;
2023-10-17 09:45:16 +00:00
it ( 'should throw when no value accessor with path' , ( ) = > {
const group = new FormGroupName ( form , null ! , null ! ) ;
const dir = new FormControlName ( group , null ! , null ! , null ! , null ) ;
group . name = 'passwords' ;
dir . name = 'password' ;
2024-04-18 17:29:04 +00:00
expect ( ( ) = > form . addControl ( dir ) ) . toThrowError (
new RegExp (
2024-04-25 23:10:52 +00:00
` NG01203: No value accessor for form control path: 'passwords -> password'. Find more at https://angular.dev/errors/NG01203 ` ,
2024-04-18 17:29:04 +00:00
) ,
) ;
2016-06-08 22:36:24 +00:00
} ) ;
2023-10-17 09:45:16 +00:00
it ( 'should set up validators' , fakeAsync ( ( ) = > {
2024-04-18 17:29:04 +00:00
form . addControl ( loginControlDir ) ;
2016-06-08 22:36:24 +00:00
2024-04-18 17:29:04 +00:00
// sync validators are set
expect ( formModel . hasError ( 'required' , [ 'login' ] ) ) . toBe ( true ) ;
expect ( formModel . hasError ( 'async' , [ 'login' ] ) ) . toBe ( false ) ;
2016-06-08 22:36:24 +00:00
2024-04-18 17:29:04 +00:00
( < FormControl > formModel . get ( 'login' ) ) . setValue ( 'invalid value' ) ;
2016-06-08 22:36:24 +00:00
2024-04-18 17:29:04 +00:00
// sync validator passes, running async validators
expect ( formModel . pending ) . toBe ( true ) ;
2016-06-08 22:36:24 +00:00
2024-04-18 17:29:04 +00:00
tick ( ) ;
2023-10-17 09:45:16 +00:00
2024-04-18 17:29:04 +00:00
expect ( formModel . hasError ( 'required' , [ 'login' ] ) ) . toBe ( false ) ;
expect ( formModel . hasError ( 'async' , [ 'login' ] ) ) . toBe ( true ) ;
} ) ) ;
2016-06-08 22:36:24 +00:00
2023-10-17 09:45:16 +00:00
it ( 'should write value to the DOM' , ( ) = > {
( < FormControl > formModel . get ( [ 'login' ] ) ) . setValue ( 'initValue' ) ;
2016-06-08 22:36:24 +00:00
2023-10-17 09:45:16 +00:00
form . addControl ( loginControlDir ) ;
2016-06-08 22:36:24 +00:00
2023-10-17 09:45:16 +00:00
expect ( ( < any > loginControlDir . valueAccessor ) . writtenValue ) . toEqual ( 'initValue' ) ;
} ) ;
2016-06-08 22:36:24 +00:00
2023-10-17 09:45:16 +00:00
it ( 'should add the directive to the list of directives included in the form' , ( ) = > {
form . addControl ( loginControlDir ) ;
expect ( form . directives ) . toEqual ( [ loginControlDir ] ) ;
2016-06-08 22:36:24 +00:00
} ) ;
} ) ;
2023-10-17 09:45:16 +00:00
describe ( 'addFormGroup' , ( ) = > {
const matchingPasswordsValidator = ( g : AbstractControl ) = > {
const controls = ( g as FormGroup ) . controls ;
if ( controls [ 'password' ] . value != controls [ 'passwordConfirm' ] . value ) {
return { 'differentPasswords' : true } ;
} else {
return null ;
}
} ;
2016-06-08 22:36:24 +00:00
2023-10-17 09:45:16 +00:00
it ( 'should set up validator' , fakeAsync ( ( ) = > {
2024-04-18 17:29:04 +00:00
const group = new FormGroupName (
form ,
[ matchingPasswordsValidator ] ,
[ asyncValidator ( 'expected' ) ] ,
) ;
group . name = 'passwords' ;
form . addFormGroup ( group ) ;
2023-10-17 09:45:16 +00:00
2024-04-18 17:29:04 +00:00
( < FormControl > formModel . get ( [ 'passwords' , 'password' ] ) ) . setValue ( 'somePassword' ) ;
( < FormControl > formModel . get ( [ 'passwords' , 'passwordConfirm' ] ) ) . setValue (
'someOtherPassword' ,
) ;
2016-06-08 22:36:24 +00:00
2024-04-18 17:29:04 +00:00
// sync validators are set
expect ( formModel . hasError ( 'differentPasswords' , [ 'passwords' ] ) ) . toEqual ( true ) ;
2016-06-08 22:36:24 +00:00
2024-04-18 17:29:04 +00:00
( < FormControl > formModel . get ( [ 'passwords' , 'passwordConfirm' ] ) ) . setValue ( 'somePassword' ) ;
2023-10-17 09:45:16 +00:00
2024-04-18 17:29:04 +00:00
// sync validators pass, running async validators
expect ( formModel . pending ) . toBe ( true ) ;
2023-10-17 09:45:16 +00:00
2024-04-18 17:29:04 +00:00
tick ( ) ;
2023-10-17 09:45:16 +00:00
2024-04-18 17:29:04 +00:00
expect ( formModel . hasError ( 'async' , [ 'passwords' ] ) ) . toBe ( true ) ;
} ) ) ;
2023-10-17 09:45:16 +00:00
} ) ;
describe ( 'removeControl' , ( ) = > {
it ( 'should remove the directive to the list of directives included in the form' , ( ) = > {
form . addControl ( loginControlDir ) ;
form . removeControl ( loginControlDir ) ;
expect ( form . directives ) . toEqual ( [ ] ) ;
2016-06-08 22:36:24 +00:00
} ) ;
2023-10-17 09:45:16 +00:00
} ) ;
describe ( 'ngOnChanges' , ( ) = > {
it ( 'should update dom values of all the directives' , ( ) = > {
form . addControl ( loginControlDir ) ;
2016-06-08 22:36:24 +00:00
2023-10-17 09:45:16 +00:00
( < FormControl > formModel . get ( [ 'login' ] ) ) . setValue ( 'new value' ) ;
form . ngOnChanges ( { } ) ;
expect ( ( < any > loginControlDir . valueAccessor ) . writtenValue ) . toEqual ( 'new value' ) ;
2016-06-08 22:36:24 +00:00
} ) ;
2023-10-17 09:45:16 +00:00
it ( 'should set up a sync validator' , ( ) = > {
const formValidator = ( c : AbstractControl ) = > ( { 'custom' : true } ) ;
const f = new FormGroupDirective ( [ formValidator ] , [ ] ) ;
f . form = formModel ;
f . ngOnChanges ( { 'form' : new SimpleChange ( null , null , false ) } ) ;
2016-10-19 16:49:02 +00:00
2023-10-17 09:45:16 +00:00
expect ( formModel . errors ) . toEqual ( { 'custom' : true } ) ;
2016-10-19 16:49:02 +00:00
} ) ;
2023-10-17 09:45:16 +00:00
it ( 'should set up an async validator' , fakeAsync ( ( ) = > {
2024-04-18 17:29:04 +00:00
const f = new FormGroupDirective ( [ ] , [ asyncValidator ( 'expected' ) ] ) ;
f . form = formModel ;
f . ngOnChanges ( { 'form' : new SimpleChange ( null , null , false ) } ) ;
2023-10-17 09:45:16 +00:00
2024-04-18 17:29:04 +00:00
tick ( ) ;
2016-06-08 22:36:24 +00:00
2024-04-18 17:29:04 +00:00
expect ( formModel . errors ) . toEqual ( { 'async' : true } ) ;
} ) ) ;
2023-10-17 09:45:16 +00:00
} ) ;
} ) ;
2016-06-08 22:36:24 +00:00
2023-10-17 09:45:16 +00:00
describe ( 'NgForm' , ( ) = > {
let form : NgForm ;
let formModel : FormGroup ;
let loginControlDir : NgModel ;
let personControlGroupDir : NgModelGroup ;
2016-06-08 22:36:24 +00:00
2023-10-17 09:45:16 +00:00
beforeEach ( ( ) = > {
form = new NgForm ( [ ] , [ ] ) ;
formModel = form . form ;
2016-06-08 22:36:24 +00:00
2023-10-17 09:45:16 +00:00
personControlGroupDir = new NgModelGroup ( form , [ ] , [ ] ) ;
personControlGroupDir . name = 'person' ;
2016-06-08 22:36:24 +00:00
2023-10-17 09:45:16 +00:00
loginControlDir = new NgModel ( personControlGroupDir , null ! , null ! , [ defaultAccessor ] ) ;
loginControlDir . name = 'login' ;
loginControlDir . valueAccessor = new DummyControlValueAccessor ( ) ;
} ) ;
2016-06-08 22:36:24 +00:00
2023-10-17 09:45:16 +00:00
it ( 'should reexport control properties' , ( ) = > {
expect ( form . control ) . toBe ( formModel ) ;
expect ( form . value ) . toBe ( formModel . value ) ;
expect ( form . valid ) . toBe ( formModel . valid ) ;
expect ( form . invalid ) . toBe ( formModel . invalid ) ;
expect ( form . pending ) . toBe ( formModel . pending ) ;
expect ( form . errors ) . toBe ( formModel . errors ) ;
expect ( form . pristine ) . toBe ( formModel . pristine ) ;
expect ( form . dirty ) . toBe ( formModel . dirty ) ;
expect ( form . touched ) . toBe ( formModel . touched ) ;
expect ( form . untouched ) . toBe ( formModel . untouched ) ;
expect ( form . statusChanges ) . toBe ( formModel . statusChanges ) ;
expect ( form . status ) . toBe ( formModel . status ) ;
expect ( form . valueChanges ) . toBe ( formModel . valueChanges ) ;
expect ( form . disabled ) . toBe ( formModel . disabled ) ;
expect ( form . enabled ) . toBe ( formModel . enabled ) ;
} ) ;
2016-06-08 22:36:24 +00:00
2023-10-17 09:45:16 +00:00
it ( 'should reexport control methods' , ( ) = > {
expect ( form . hasError ( 'required' ) ) . toBe ( formModel . hasError ( 'required' ) ) ;
expect ( form . getError ( 'required' ) ) . toBe ( formModel . getError ( 'required' ) ) ;
2016-06-08 22:36:24 +00:00
2023-10-17 09:45:16 +00:00
formModel . setErrors ( { required : true } ) ;
expect ( form . hasError ( 'required' ) ) . toBe ( formModel . hasError ( 'required' ) ) ;
expect ( form . getError ( 'required' ) ) . toBe ( formModel . getError ( 'required' ) ) ;
} ) ;
2016-06-08 22:36:24 +00:00
2023-10-17 09:45:16 +00:00
describe ( 'addControl & addFormGroup' , ( ) = > {
it ( 'should create a control with the given name' , fakeAsync ( ( ) = > {
2024-04-18 17:29:04 +00:00
form . addFormGroup ( personControlGroupDir ) ;
form . addControl ( loginControlDir ) ;
2016-06-08 22:36:24 +00:00
2024-04-18 17:29:04 +00:00
flushMicrotasks ( ) ;
2016-06-08 22:36:24 +00:00
2024-04-18 17:29:04 +00:00
expect ( formModel . get ( [ 'person' , 'login' ] ) ) . not . toBeNull ( ) ;
} ) ) ;
2016-06-08 22:36:24 +00:00
2023-10-17 09:45:16 +00:00
// should update the form's value and validity
} ) ;
describe ( 'removeControl & removeFormGroup' , ( ) = > {
it ( 'should remove control' , fakeAsync ( ( ) = > {
2024-04-18 17:29:04 +00:00
form . addFormGroup ( personControlGroupDir ) ;
form . addControl ( loginControlDir ) ;
2016-06-08 22:36:24 +00:00
2024-04-18 17:29:04 +00:00
form . removeFormGroup ( personControlGroupDir ) ;
form . removeControl ( loginControlDir ) ;
2016-06-08 22:36:24 +00:00
2024-04-18 17:29:04 +00:00
flushMicrotasks ( ) ;
2023-10-17 09:45:16 +00:00
2024-04-18 17:29:04 +00:00
expect ( formModel . get ( [ 'person' ] ) ) . toBeNull ( ) ;
expect ( formModel . get ( [ 'person' , 'login' ] ) ) . toBeNull ( ) ;
} ) ) ;
2023-10-17 09:45:16 +00:00
// should update the form's value and validity
2016-06-08 22:36:24 +00:00
} ) ;
2023-10-17 09:45:16 +00:00
it ( 'should set up sync validator' , fakeAsync ( ( ) = > {
2024-04-18 17:29:04 +00:00
const formValidator = ( ) = > ( { 'custom' : true } ) ;
const f = new NgForm ( [ formValidator ] , [ ] ) ;
2016-06-08 22:36:24 +00:00
2024-04-18 17:29:04 +00:00
tick ( ) ;
2016-06-08 22:36:24 +00:00
2024-04-18 17:29:04 +00:00
expect ( f . form . errors ) . toEqual ( { 'custom' : true } ) ;
} ) ) ;
2016-06-08 22:36:24 +00:00
2023-10-17 09:45:16 +00:00
it ( 'should set up async validator' , fakeAsync ( ( ) = > {
2024-04-18 17:29:04 +00:00
const f = new NgForm ( [ ] , [ asyncValidator ( 'expected' ) ] ) ;
2016-10-19 16:49:02 +00:00
2024-04-18 17:29:04 +00:00
tick ( ) ;
2016-10-19 16:49:02 +00:00
2024-04-18 17:29:04 +00:00
expect ( f . form . errors ) . toEqual ( { 'async' : true } ) ;
} ) ) ;
2025-04-16 12:29:46 +00:00
describe ( 'events emissions' , ( ) = > {
it ( 'formControl should emit an event when resetting a form' , ( ) = > {
const f = new NgForm ( [ ] , [ ] ) ;
const events : ControlEvent [ ] = [ ] ;
f . form . events . subscribe ( ( event ) = > events . push ( event ) ) ;
f . resetForm ( ) ;
expect ( events . length ) . toBe ( 4 ) ;
expect ( events [ 0 ] ) . toBeInstanceOf ( TouchedChangeEvent ) ;
expect ( events [ 1 ] ) . toBeInstanceOf ( ValueChangeEvent ) ;
expect ( events [ 2 ] ) . toBeInstanceOf ( StatusChangeEvent ) ;
// The event that matters
expect ( events [ 3 ] ) . toBeInstanceOf ( FormResetEvent ) ;
expect ( events [ 3 ] . source ) . toBe ( f . form ) ;
} ) ;
it ( 'formControl should emit an event when submitting a form' , ( ) = > {
const f = new NgForm ( [ ] , [ ] ) ;
const events : ControlEvent [ ] = [ ] ;
f . form . events . subscribe ( ( event ) = > events . push ( event ) ) ;
f . onSubmit ( { } as any ) ;
expect ( events . length ) . toBe ( 1 ) ;
expect ( events [ 0 ] ) . toBeInstanceOf ( FormSubmittedEvent ) ;
expect ( events [ 0 ] . source ) . toBe ( f . form ) ;
} ) ;
} ) ;
2023-10-17 09:45:16 +00:00
} ) ;
describe ( 'FormGroupName' , ( ) = > {
let formModel : FormGroup ;
let controlGroupDir : FormGroupName ;
beforeEach ( ( ) = > {
formModel = new FormGroup ( { 'login' : new FormControl ( null ) } ) ;
const parent = new FormGroupDirective ( [ ] , [ ] ) ;
parent . form = new FormGroup ( { 'group' : formModel } ) ;
controlGroupDir = new FormGroupName ( parent , [ ] , [ ] ) ;
controlGroupDir . name = 'group' ;
2016-06-08 22:36:24 +00:00
} ) ;
2023-10-17 09:45:16 +00:00
it ( 'should reexport control properties' , ( ) = > {
expect ( controlGroupDir . control ) . toBe ( formModel ) ;
expect ( controlGroupDir . value ) . toBe ( formModel . value ) ;
expect ( controlGroupDir . valid ) . toBe ( formModel . valid ) ;
expect ( controlGroupDir . invalid ) . toBe ( formModel . invalid ) ;
expect ( controlGroupDir . pending ) . toBe ( formModel . pending ) ;
expect ( controlGroupDir . errors ) . toBe ( formModel . errors ) ;
expect ( controlGroupDir . pristine ) . toBe ( formModel . pristine ) ;
expect ( controlGroupDir . dirty ) . toBe ( formModel . dirty ) ;
expect ( controlGroupDir . touched ) . toBe ( formModel . touched ) ;
expect ( controlGroupDir . untouched ) . toBe ( formModel . untouched ) ;
expect ( controlGroupDir . statusChanges ) . toBe ( formModel . statusChanges ) ;
expect ( controlGroupDir . status ) . toBe ( formModel . status ) ;
expect ( controlGroupDir . valueChanges ) . toBe ( formModel . valueChanges ) ;
expect ( controlGroupDir . disabled ) . toBe ( formModel . disabled ) ;
expect ( controlGroupDir . enabled ) . toBe ( formModel . enabled ) ;
} ) ;
2016-06-25 20:27:29 +00:00
2023-10-17 09:45:16 +00:00
it ( 'should reexport control methods' , ( ) = > {
expect ( controlGroupDir . hasError ( 'required' ) ) . toBe ( formModel . hasError ( 'required' ) ) ;
expect ( controlGroupDir . getError ( 'required' ) ) . toBe ( formModel . getError ( 'required' ) ) ;
2016-06-25 20:27:29 +00:00
2023-10-17 09:45:16 +00:00
formModel . setErrors ( { required : true } ) ;
expect ( controlGroupDir . hasError ( 'required' ) ) . toBe ( formModel . hasError ( 'required' ) ) ;
expect ( controlGroupDir . getError ( 'required' ) ) . toBe ( formModel . getError ( 'required' ) ) ;
} ) ;
} ) ;
2016-10-19 16:49:02 +00:00
2023-10-17 09:45:16 +00:00
describe ( 'FormArrayName' , ( ) = > {
let formModel : FormArray ;
let formArrayDir : FormArrayName ;
2016-10-19 16:49:02 +00:00
2023-10-17 09:45:16 +00:00
beforeEach ( ( ) = > {
const parent = new FormGroupDirective ( [ ] , [ ] ) ;
formModel = new FormArray ( [ new FormControl ( '' ) ] ) ;
parent . form = new FormGroup ( { 'array' : formModel } ) ;
formArrayDir = new FormArrayName ( parent , [ ] , [ ] ) ;
formArrayDir . name = 'array' ;
2016-06-25 20:27:29 +00:00
} ) ;
2023-10-17 09:45:16 +00:00
it ( 'should reexport control properties' , ( ) = > {
expect ( formArrayDir . control ) . toBe ( formModel ) ;
expect ( formArrayDir . value ) . toBe ( formModel . value ) ;
expect ( formArrayDir . valid ) . toBe ( formModel . valid ) ;
expect ( formArrayDir . invalid ) . toBe ( formModel . invalid ) ;
expect ( formArrayDir . pending ) . toBe ( formModel . pending ) ;
expect ( formArrayDir . errors ) . toBe ( formModel . errors ) ;
expect ( formArrayDir . pristine ) . toBe ( formModel . pristine ) ;
expect ( formArrayDir . dirty ) . toBe ( formModel . dirty ) ;
expect ( formArrayDir . touched ) . toBe ( formModel . touched ) ;
expect ( formArrayDir . status ) . toBe ( formModel . status ) ;
expect ( formArrayDir . untouched ) . toBe ( formModel . untouched ) ;
expect ( formArrayDir . disabled ) . toBe ( formModel . disabled ) ;
expect ( formArrayDir . enabled ) . toBe ( formModel . enabled ) ;
} ) ;
2016-06-08 22:36:24 +00:00
2023-10-17 09:45:16 +00:00
it ( 'should reexport control methods' , ( ) = > {
expect ( formArrayDir . hasError ( 'required' ) ) . toBe ( formModel . hasError ( 'required' ) ) ;
expect ( formArrayDir . getError ( 'required' ) ) . toBe ( formModel . getError ( 'required' ) ) ;
2016-06-08 22:36:24 +00:00
2023-10-17 09:45:16 +00:00
formModel . setErrors ( { required : true } ) ;
expect ( formArrayDir . hasError ( 'required' ) ) . toBe ( formModel . hasError ( 'required' ) ) ;
expect ( formArrayDir . getError ( 'required' ) ) . toBe ( formModel . getError ( 'required' ) ) ;
} ) ;
} ) ;
2016-06-08 22:36:24 +00:00
2023-10-17 09:45:16 +00:00
describe ( 'FormControlDirective' , ( ) = > {
let controlDir : FormControlDirective ;
let control : FormControl ;
2024-04-18 17:29:04 +00:00
const checkProperties = function ( control : FormControl ) {
2023-10-17 09:45:16 +00:00
expect ( controlDir . control ) . toBe ( control ) ;
expect ( controlDir . value ) . toBe ( control . value ) ;
expect ( controlDir . valid ) . toBe ( control . valid ) ;
expect ( controlDir . invalid ) . toBe ( control . invalid ) ;
expect ( controlDir . pending ) . toBe ( control . pending ) ;
expect ( controlDir . errors ) . toBe ( control . errors ) ;
expect ( controlDir . pristine ) . toBe ( control . pristine ) ;
expect ( controlDir . dirty ) . toBe ( control . dirty ) ;
expect ( controlDir . touched ) . toBe ( control . touched ) ;
expect ( controlDir . untouched ) . toBe ( control . untouched ) ;
expect ( controlDir . statusChanges ) . toBe ( control . statusChanges ) ;
expect ( controlDir . status ) . toBe ( control . status ) ;
expect ( controlDir . valueChanges ) . toBe ( control . valueChanges ) ;
expect ( controlDir . disabled ) . toBe ( control . disabled ) ;
expect ( controlDir . enabled ) . toBe ( control . enabled ) ;
} ;
2016-06-08 22:36:24 +00:00
2023-10-17 09:45:16 +00:00
beforeEach ( ( ) = > {
controlDir = new FormControlDirective ( [ Validators . required ] , [ ] , [ defaultAccessor ] , null ) ;
controlDir . valueAccessor = new DummyControlValueAccessor ( ) ;
2016-10-19 16:49:02 +00:00
2023-10-17 09:45:16 +00:00
control = new FormControl ( null ) ;
controlDir . form = control ;
} ) ;
2016-10-19 16:49:02 +00:00
2023-10-17 09:45:16 +00:00
it ( 'should reexport control properties' , ( ) = > {
checkProperties ( control ) ;
} ) ;
2016-06-08 22:36:24 +00:00
2023-10-17 09:45:16 +00:00
it ( 'should reexport control methods' , ( ) = > {
expect ( controlDir . hasError ( 'required' ) ) . toBe ( control . hasError ( 'required' ) ) ;
expect ( controlDir . getError ( 'required' ) ) . toBe ( control . getError ( 'required' ) ) ;
2016-06-08 22:36:24 +00:00
2023-10-17 09:45:16 +00:00
control . setErrors ( { required : true } ) ;
expect ( controlDir . hasError ( 'required' ) ) . toBe ( control . hasError ( 'required' ) ) ;
expect ( controlDir . getError ( 'required' ) ) . toBe ( control . getError ( 'required' ) ) ;
} ) ;
2016-06-08 22:36:24 +00:00
2023-10-17 09:45:16 +00:00
it ( 'should reexport new control properties' , ( ) = > {
const newControl = new FormControl ( null ) ;
controlDir . form = newControl ;
controlDir . ngOnChanges ( { 'form' : new SimpleChange ( control , newControl , false ) } ) ;
2016-06-08 22:36:24 +00:00
2023-10-17 09:45:16 +00:00
checkProperties ( newControl ) ;
2016-06-08 22:36:24 +00:00
} ) ;
2023-10-17 09:45:16 +00:00
it ( 'should set up validator' , ( ) = > {
expect ( control . valid ) . toBe ( true ) ;
2016-06-08 22:36:24 +00:00
2023-10-17 09:45:16 +00:00
// this will add the required validator and recalculate the validity
controlDir . ngOnChanges ( { 'form' : new SimpleChange ( null , control , false ) } ) ;
2016-06-08 22:36:24 +00:00
2023-10-17 09:45:16 +00:00
expect ( control . valid ) . toBe ( false ) ;
} ) ;
} ) ;
2016-06-08 22:36:24 +00:00
2023-10-17 09:45:16 +00:00
describe ( 'NgModel' , ( ) = > {
let ngModel : NgModel ;
let control : FormControl ;
2016-10-19 16:49:02 +00:00
2023-10-17 09:45:16 +00:00
beforeEach ( ( ) = > {
ngModel = new NgModel (
2024-04-18 17:29:04 +00:00
null ! ,
[ Validators . required ] ,
[ asyncValidator ( 'expected' ) ] ,
[ defaultAccessor ] ,
) ;
2023-10-17 09:45:16 +00:00
ngModel . valueAccessor = new DummyControlValueAccessor ( ) ;
control = ngModel . control ;
} ) ;
it ( 'should reexport control properties' , ( ) = > {
expect ( ngModel . control ) . toBe ( control ) ;
expect ( ngModel . value ) . toBe ( control . value ) ;
expect ( ngModel . valid ) . toBe ( control . valid ) ;
expect ( ngModel . invalid ) . toBe ( control . invalid ) ;
expect ( ngModel . pending ) . toBe ( control . pending ) ;
expect ( ngModel . errors ) . toBe ( control . errors ) ;
expect ( ngModel . pristine ) . toBe ( control . pristine ) ;
expect ( ngModel . dirty ) . toBe ( control . dirty ) ;
expect ( ngModel . touched ) . toBe ( control . touched ) ;
expect ( ngModel . untouched ) . toBe ( control . untouched ) ;
expect ( ngModel . statusChanges ) . toBe ( control . statusChanges ) ;
expect ( ngModel . status ) . toBe ( control . status ) ;
expect ( ngModel . valueChanges ) . toBe ( control . valueChanges ) ;
expect ( ngModel . disabled ) . toBe ( control . disabled ) ;
expect ( ngModel . enabled ) . toBe ( control . enabled ) ;
} ) ;
2016-10-19 16:49:02 +00:00
2023-10-17 09:45:16 +00:00
it ( 'should reexport control methods' , ( ) = > {
expect ( ngModel . hasError ( 'required' ) ) . toBe ( control . hasError ( 'required' ) ) ;
expect ( ngModel . getError ( 'required' ) ) . toBe ( control . getError ( 'required' ) ) ;
2016-07-13 21:13:02 +00:00
2023-10-17 09:45:16 +00:00
control . setErrors ( { required : true } ) ;
expect ( ngModel . hasError ( 'required' ) ) . toBe ( control . hasError ( 'required' ) ) ;
expect ( ngModel . getError ( 'required' ) ) . toBe ( control . getError ( 'required' ) ) ;
} ) ;
2016-07-13 21:13:02 +00:00
2023-10-17 09:45:16 +00:00
it ( 'should throw when no value accessor with named control' , ( ) = > {
const namedDir = new NgModel ( null ! , null ! , null ! , null ! ) ;
namedDir . name = 'one' ;
2016-07-13 21:13:02 +00:00
2024-04-18 17:29:04 +00:00
expect ( ( ) = > namedDir . ngOnChanges ( { } ) ) . toThrowError (
new RegExp (
2024-04-25 23:10:52 +00:00
` NG01203: No value accessor for form control name: 'one'. Find more at https://angular.dev/errors/NG01203 ` ,
2024-04-18 17:29:04 +00:00
) ,
) ;
2023-10-17 09:45:16 +00:00
} ) ;
2016-07-13 21:13:02 +00:00
2023-10-17 09:45:16 +00:00
it ( 'should throw when no value accessor with unnamed control' , ( ) = > {
const unnamedDir = new NgModel ( null ! , null ! , null ! , null ! ) ;
2016-06-08 22:36:24 +00:00
2024-04-18 17:29:04 +00:00
expect ( ( ) = > unnamedDir . ngOnChanges ( { } ) ) . toThrowError (
new RegExp (
2024-04-25 23:10:52 +00:00
` NG01203: No value accessor for form control unspecified name attribute. Find more at https://angular.dev/errors/NG01203 ` ,
2024-04-18 17:29:04 +00:00
) ,
) ;
2023-10-17 09:45:16 +00:00
} ) ;
2016-06-08 22:36:24 +00:00
2023-10-17 09:45:16 +00:00
it ( 'should set up validator' , fakeAsync ( ( ) = > {
2024-04-18 17:29:04 +00:00
// this will add the required validator and recalculate the validity
ngModel . ngOnChanges ( { } ) ;
tick ( ) ;
2016-06-08 22:36:24 +00:00
2024-04-18 17:29:04 +00:00
expect ( ngModel . control . errors ) . toEqual ( { 'required' : true } ) ;
2016-09-20 21:55:47 +00:00
2024-04-18 17:29:04 +00:00
ngModel . control . setValue ( 'someValue' ) ;
tick ( ) ;
2016-09-20 21:55:47 +00:00
2024-04-18 17:29:04 +00:00
expect ( ngModel . control . errors ) . toEqual ( { 'async' : true } ) ;
} ) ) ;
2016-09-20 21:55:47 +00:00
2023-10-17 09:45:16 +00:00
it ( 'should mark as disabled properly' , fakeAsync ( ( ) = > {
2024-04-18 17:29:04 +00:00
ngModel . ngOnChanges ( { isDisabled : new SimpleChange ( '' , undefined , false ) } ) ;
tick ( ) ;
expect ( ngModel . control . disabled ) . toEqual ( false ) ;
ngModel . ngOnChanges ( { isDisabled : new SimpleChange ( '' , null , false ) } ) ;
tick ( ) ;
expect ( ngModel . control . disabled ) . toEqual ( false ) ;
ngModel . ngOnChanges ( { isDisabled : new SimpleChange ( '' , false , false ) } ) ;
tick ( ) ;
expect ( ngModel . control . disabled ) . toEqual ( false ) ;
ngModel . ngOnChanges ( { isDisabled : new SimpleChange ( '' , 'false' , false ) } ) ;
tick ( ) ;
expect ( ngModel . control . disabled ) . toEqual ( false ) ;
ngModel . ngOnChanges ( { isDisabled : new SimpleChange ( '' , 0 , false ) } ) ;
tick ( ) ;
expect ( ngModel . control . disabled ) . toEqual ( false ) ;
ngModel . ngOnChanges ( { isDisabled : new SimpleChange ( null , '' , false ) } ) ;
tick ( ) ;
expect ( ngModel . control . disabled ) . toEqual ( true ) ;
ngModel . ngOnChanges ( { isDisabled : new SimpleChange ( null , 'true' , false ) } ) ;
tick ( ) ;
expect ( ngModel . control . disabled ) . toEqual ( true ) ;
ngModel . ngOnChanges ( { isDisabled : new SimpleChange ( null , true , false ) } ) ;
tick ( ) ;
expect ( ngModel . control . disabled ) . toEqual ( true ) ;
ngModel . ngOnChanges ( { isDisabled : new SimpleChange ( null , 'anything else' , false ) } ) ;
tick ( ) ;
expect ( ngModel . control . disabled ) . toEqual ( true ) ;
} ) ) ;
2023-10-17 09:45:16 +00:00
} ) ;
2016-06-08 22:36:24 +00:00
2023-10-17 09:45:16 +00:00
describe ( 'FormControlName' , ( ) = > {
let formModel : FormControl ;
let controlNameDir : FormControlName ;
2016-06-08 22:36:24 +00:00
2023-10-17 09:45:16 +00:00
beforeEach ( ( ) = > {
formModel = new FormControl ( 'name' ) ;
2016-10-19 16:49:02 +00:00
2023-10-17 09:45:16 +00:00
const parent = new FormGroupDirective ( [ ] , [ ] ) ;
parent . form = new FormGroup ( { 'name' : formModel } ) ;
controlNameDir = new FormControlName ( parent , [ ] , [ ] , [ defaultAccessor ] , null ) ;
controlNameDir . name = 'name' ;
( controlNameDir as Writable < FormControlName > ) . control = formModel ;
} ) ;
2016-10-19 16:49:02 +00:00
2023-10-17 09:45:16 +00:00
it ( 'should reexport control properties' , ( ) = > {
expect ( controlNameDir . control ) . toBe ( formModel ) ;
expect ( controlNameDir . value ) . toBe ( formModel . value ) ;
expect ( controlNameDir . valid ) . toBe ( formModel . valid ) ;
expect ( controlNameDir . invalid ) . toBe ( formModel . invalid ) ;
expect ( controlNameDir . pending ) . toBe ( formModel . pending ) ;
expect ( controlNameDir . errors ) . toBe ( formModel . errors ) ;
expect ( controlNameDir . pristine ) . toBe ( formModel . pristine ) ;
expect ( controlNameDir . dirty ) . toBe ( formModel . dirty ) ;
expect ( controlNameDir . touched ) . toBe ( formModel . touched ) ;
expect ( controlNameDir . untouched ) . toBe ( formModel . untouched ) ;
expect ( controlNameDir . statusChanges ) . toBe ( formModel . statusChanges ) ;
expect ( controlNameDir . status ) . toBe ( formModel . status ) ;
expect ( controlNameDir . valueChanges ) . toBe ( formModel . valueChanges ) ;
expect ( controlNameDir . disabled ) . toBe ( formModel . disabled ) ;
expect ( controlNameDir . enabled ) . toBe ( formModel . enabled ) ;
} ) ;
it ( 'should reexport control methods' , ( ) = > {
expect ( controlNameDir . hasError ( 'required' ) ) . toBe ( formModel . hasError ( 'required' ) ) ;
expect ( controlNameDir . getError ( 'required' ) ) . toBe ( formModel . getError ( 'required' ) ) ;
formModel . setErrors ( { required : true } ) ;
expect ( controlNameDir . hasError ( 'required' ) ) . toBe ( formModel . hasError ( 'required' ) ) ;
expect ( controlNameDir . getError ( 'required' ) ) . toBe ( formModel . getError ( 'required' ) ) ;
2016-06-08 22:36:24 +00:00
} ) ;
} ) ;
2023-10-17 09:45:16 +00:00
} ) ;