@@ -4,7 +4,7 @@ import {BACKSPACE, DELETE, ENTER, LEFT_ARROW, RIGHT_ARROW, SPACE, TAB} from '@an
4
4
import { createKeyboardEvent , dispatchFakeEvent , dispatchKeyboardEvent } from '@angular/cdk/testing' ;
5
5
import { Component , DebugElement , QueryList , ViewChild , ViewChildren } from '@angular/core' ;
6
6
import { async , ComponentFixture , fakeAsync , TestBed , tick } from '@angular/core/testing' ;
7
- import { FormControl , FormsModule , ReactiveFormsModule } from '@angular/forms' ;
7
+ import { FormControl , FormsModule , NgForm , ReactiveFormsModule , Validators } from '@angular/forms' ;
8
8
import { MatFormFieldModule } from '@angular/material/form-field' ;
9
9
import { By } from '@angular/platform-browser' ;
10
10
import { NoopAnimationsModule } from '@angular/platform-browser/animations' ;
@@ -35,6 +35,7 @@ describe('MatChipList', () => {
35
35
NoopAnimationsModule
36
36
] ,
37
37
declarations : [
38
+ ChipListWithFormErrorMessages ,
38
39
StandardChipList ,
39
40
FormFieldChipList ,
40
41
BasicChipList ,
@@ -864,6 +865,121 @@ describe('MatChipList', () => {
864
865
} ) ;
865
866
} ) ;
866
867
868
+ describe ( 'error messages' , ( ) => {
869
+ let errorTestComponent : ChipListWithFormErrorMessages ;
870
+ let containerEl : HTMLElement ;
871
+ let chipListEl : HTMLElement ;
872
+
873
+ beforeEach ( ( ) => {
874
+ fixture = TestBed . createComponent ( ChipListWithFormErrorMessages ) ;
875
+ fixture . detectChanges ( ) ;
876
+ errorTestComponent = fixture . componentInstance ;
877
+ containerEl = fixture . debugElement . query ( By . css ( 'mat-form-field' ) ) . nativeElement ;
878
+ chipListEl = fixture . debugElement . query ( By . css ( 'mat-chip-list' ) ) . nativeElement ;
879
+ } ) ;
880
+
881
+ it ( 'should not show any errors if the user has not interacted' , ( ) => {
882
+ expect ( errorTestComponent . formControl . untouched )
883
+ . toBe ( true , 'Expected untouched form control' ) ;
884
+ expect ( containerEl . querySelectorAll ( 'mat-error' ) . length ) . toBe ( 0 , 'Expected no error message' ) ;
885
+ expect ( chipListEl . getAttribute ( 'aria-invalid' ) )
886
+ . toBe ( 'false' , 'Expected aria-invalid to be set to "false".' ) ;
887
+ } ) ;
888
+
889
+ it ( 'should display an error message when the chip list is touched and invalid' , async ( ( ) => {
890
+ expect ( errorTestComponent . formControl . invalid )
891
+ . toBe ( true , 'Expected form control to be invalid' ) ;
892
+ expect ( containerEl . querySelectorAll ( 'mat-error' ) . length )
893
+ . toBe ( 0 , 'Expected no error message' ) ;
894
+
895
+ errorTestComponent . formControl . markAsTouched ( ) ;
896
+ fixture . detectChanges ( ) ;
897
+
898
+ fixture . whenStable ( ) . then ( ( ) => {
899
+ expect ( containerEl . classList )
900
+ . toContain ( 'mat-form-field-invalid' , 'Expected container to have the invalid CSS class.' ) ;
901
+ expect ( containerEl . querySelectorAll ( 'mat-error' ) . length )
902
+ . toBe ( 1 , 'Expected one error message to have been rendered.' ) ;
903
+ expect ( chipListEl . getAttribute ( 'aria-invalid' ) )
904
+ . toBe ( 'true' , 'Expected aria-invalid to be set to "true".' ) ;
905
+ } ) ;
906
+ } ) ) ;
907
+
908
+ it ( 'should display an error message when the parent form is submitted' , fakeAsync ( ( ) => {
909
+ expect ( errorTestComponent . form . submitted )
910
+ . toBe ( false , 'Expected form not to have been submitted' ) ;
911
+ expect ( errorTestComponent . formControl . invalid )
912
+ . toBe ( true , 'Expected form control to be invalid' ) ;
913
+ expect ( containerEl . querySelectorAll ( 'mat-error' ) . length ) . toBe ( 0 , 'Expected no error message' ) ;
914
+
915
+ dispatchFakeEvent ( fixture . debugElement . query ( By . css ( 'form' ) ) . nativeElement , 'submit' ) ;
916
+ fixture . detectChanges ( ) ;
917
+
918
+ fixture . whenStable ( ) . then ( ( ) => {
919
+ expect ( errorTestComponent . form . submitted )
920
+ . toBe ( true , 'Expected form to have been submitted' ) ;
921
+ expect ( containerEl . classList )
922
+ . toContain ( 'mat-form-field-invalid' , 'Expected container to have the invalid CSS class.' ) ;
923
+ expect ( containerEl . querySelectorAll ( 'mat-error' ) . length )
924
+ . toBe ( 1 , 'Expected one error message to have been rendered.' ) ;
925
+ expect ( chipListEl . getAttribute ( 'aria-invalid' ) )
926
+ . toBe ( 'true' , 'Expected aria-invalid to be set to "true".' ) ;
927
+ } ) ;
928
+ } ) ) ;
929
+
930
+ it ( 'should hide the errors and show the hints once the chip list becomes valid' ,
931
+ fakeAsync ( ( ) => {
932
+ errorTestComponent . formControl . markAsTouched ( ) ;
933
+ fixture . detectChanges ( ) ;
934
+
935
+ fixture . whenStable ( ) . then ( ( ) => {
936
+ expect ( containerEl . classList )
937
+ . toContain ( 'mat-form-field-invalid' , 'Expected container to have the invalid CSS class.' ) ;
938
+ expect ( containerEl . querySelectorAll ( 'mat-error' ) . length )
939
+ . toBe ( 1 , 'Expected one error message to have been rendered.' ) ;
940
+ expect ( containerEl . querySelectorAll ( 'mat-hint' ) . length )
941
+ . toBe ( 0 , 'Expected no hints to be shown.' ) ;
942
+
943
+ errorTestComponent . formControl . setValue ( 'something' ) ;
944
+ fixture . detectChanges ( ) ;
945
+
946
+ fixture . whenStable ( ) . then ( ( ) => {
947
+ expect ( containerEl . classList ) . not . toContain ( 'mat-form-field-invalid' ,
948
+ 'Expected container not to have the invalid class when valid.' ) ;
949
+ expect ( containerEl . querySelectorAll ( 'mat-error' ) . length )
950
+ . toBe ( 0 , 'Expected no error messages when the input is valid.' ) ;
951
+ expect ( containerEl . querySelectorAll ( 'mat-hint' ) . length )
952
+ . toBe ( 1 , 'Expected one hint to be shown once the input is valid.' ) ;
953
+ } ) ;
954
+ } ) ;
955
+ } ) ) ;
956
+
957
+ it ( 'should set the proper role on the error messages' , ( ) => {
958
+ errorTestComponent . formControl . markAsTouched ( ) ;
959
+ fixture . detectChanges ( ) ;
960
+
961
+ expect ( containerEl . querySelector ( 'mat-error' ) ! . getAttribute ( 'role' ) ) . toBe ( 'alert' ) ;
962
+ } ) ;
963
+
964
+ it ( 'sets the aria-describedby to reference errors when in error state' , ( ) => {
965
+ let hintId = fixture . debugElement . query ( By . css ( '.mat-hint' ) ) . nativeElement . getAttribute ( 'id' ) ;
966
+ let describedBy = chipListEl . getAttribute ( 'aria-describedby' ) ;
967
+
968
+ expect ( hintId ) . toBeTruthy ( 'hint should be shown' ) ;
969
+ expect ( describedBy ) . toBe ( hintId ) ;
970
+
971
+ fixture . componentInstance . formControl . markAsTouched ( ) ;
972
+ fixture . detectChanges ( ) ;
973
+
974
+ let errorIds = fixture . debugElement . queryAll ( By . css ( '.mat-error' ) )
975
+ . map ( el => el . nativeElement . getAttribute ( 'id' ) ) . join ( ' ' ) ;
976
+ describedBy = chipListEl . getAttribute ( 'aria-describedby' ) ;
977
+
978
+ expect ( errorIds ) . toBeTruthy ( 'errors should be shown' ) ;
979
+ expect ( describedBy ) . toBe ( errorIds ) ;
980
+ } ) ;
981
+ } ) ;
982
+
867
983
function setupStandardList ( ) {
868
984
fixture = TestBed . createComponent ( StandardChipList ) ;
869
985
fixture . detectChanges ( ) ;
@@ -940,14 +1056,14 @@ class FormFieldChipList {
940
1056
} )
941
1057
class BasicChipList {
942
1058
foods : any [ ] = [
943
- { value : 'steak-0' , viewValue : 'Steak' } ,
944
- { value : 'pizza-1' , viewValue : 'Pizza' } ,
945
- { value : 'tacos-2' , viewValue : 'Tacos' , disabled : true } ,
946
- { value : 'sandwich-3' , viewValue : 'Sandwich' } ,
947
- { value : 'chips-4' , viewValue : 'Chips' } ,
948
- { value : 'eggs-5' , viewValue : 'Eggs' } ,
949
- { value : 'pasta-6' , viewValue : 'Pasta' } ,
950
- { value : 'sushi-7' , viewValue : 'Sushi' } ,
1059
+ { value : 'steak-0' , viewValue : 'Steak' } ,
1060
+ { value : 'pizza-1' , viewValue : 'Pizza' } ,
1061
+ { value : 'tacos-2' , viewValue : 'Tacos' , disabled : true } ,
1062
+ { value : 'sandwich-3' , viewValue : 'Sandwich' } ,
1063
+ { value : 'chips-4' , viewValue : 'Chips' } ,
1064
+ { value : 'eggs-5' , viewValue : 'Eggs' } ,
1065
+ { value : 'pasta-6' , viewValue : 'Pasta' } ,
1066
+ { value : 'sushi-7' , viewValue : 'Sushi' } ,
951
1067
] ;
952
1068
control = new FormControl ( ) ;
953
1069
isRequired : boolean ;
@@ -975,14 +1091,14 @@ class BasicChipList {
975
1091
} )
976
1092
class MultiSelectionChipList {
977
1093
foods : any [ ] = [
978
- { value : 'steak-0' , viewValue : 'Steak' } ,
979
- { value : 'pizza-1' , viewValue : 'Pizza' } ,
980
- { value : 'tacos-2' , viewValue : 'Tacos' , disabled : true } ,
981
- { value : 'sandwich-3' , viewValue : 'Sandwich' } ,
982
- { value : 'chips-4' , viewValue : 'Chips' } ,
983
- { value : 'eggs-5' , viewValue : 'Eggs' } ,
984
- { value : 'pasta-6' , viewValue : 'Pasta' } ,
985
- { value : 'sushi-7' , viewValue : 'Sushi' } ,
1094
+ { value : 'steak-0' , viewValue : 'Steak' } ,
1095
+ { value : 'pizza-1' , viewValue : 'Pizza' } ,
1096
+ { value : 'tacos-2' , viewValue : 'Tacos' , disabled : true } ,
1097
+ { value : 'sandwich-3' , viewValue : 'Sandwich' } ,
1098
+ { value : 'chips-4' , viewValue : 'Chips' } ,
1099
+ { value : 'eggs-5' , viewValue : 'Eggs' } ,
1100
+ { value : 'pasta-6' , viewValue : 'Pasta' } ,
1101
+ { value : 'sushi-7' , viewValue : 'Sushi' } ,
986
1102
] ;
987
1103
control = new FormControl ( ) ;
988
1104
isRequired : boolean ;
@@ -1013,14 +1129,14 @@ class MultiSelectionChipList {
1013
1129
} )
1014
1130
class InputChipList {
1015
1131
foods : any [ ] = [
1016
- { value : 'steak-0' , viewValue : 'Steak' } ,
1017
- { value : 'pizza-1' , viewValue : 'Pizza' } ,
1018
- { value : 'tacos-2' , viewValue : 'Tacos' , disabled : true } ,
1019
- { value : 'sandwich-3' , viewValue : 'Sandwich' } ,
1020
- { value : 'chips-4' , viewValue : 'Chips' } ,
1021
- { value : 'eggs-5' , viewValue : 'Eggs' } ,
1022
- { value : 'pasta-6' , viewValue : 'Pasta' } ,
1023
- { value : 'sushi-7' , viewValue : 'Sushi' } ,
1132
+ { value : 'steak-0' , viewValue : 'Steak' } ,
1133
+ { value : 'pizza-1' , viewValue : 'Pizza' } ,
1134
+ { value : 'tacos-2' , viewValue : 'Tacos' , disabled : true } ,
1135
+ { value : 'sandwich-3' , viewValue : 'Sandwich' } ,
1136
+ { value : 'chips-4' , viewValue : 'Chips' } ,
1137
+ { value : 'eggs-5' , viewValue : 'Eggs' } ,
1138
+ { value : 'pasta-6' , viewValue : 'Pasta' } ,
1139
+ { value : 'sushi-7' , viewValue : 'Sushi' } ,
1024
1140
] ;
1025
1141
control = new FormControl ( ) ;
1026
1142
@@ -1061,8 +1177,8 @@ class InputChipList {
1061
1177
} )
1062
1178
class FalsyValueChipList {
1063
1179
foods : any [ ] = [
1064
- { value : 0 , viewValue : 'Steak' } ,
1065
- { value : 1 , viewValue : 'Pizza' } ,
1180
+ { value : 0 , viewValue : 'Steak' } ,
1181
+ { value : 1 , viewValue : 'Pizza' } ,
1066
1182
] ;
1067
1183
control = new FormControl ( ) ;
1068
1184
@ViewChildren ( MatChip ) chips : QueryList < MatChip > ;
@@ -1079,9 +1195,36 @@ class FalsyValueChipList {
1079
1195
} )
1080
1196
class SelectedChipList {
1081
1197
foods : any [ ] = [
1082
- { value : 0 , viewValue : 'Steak' , selected : true } ,
1083
- { value : 1 , viewValue : 'Pizza' , selected : false } ,
1084
- { value : 2 , viewValue : 'Pasta' , selected : true } ,
1198
+ { value : 0 , viewValue : 'Steak' , selected : true } ,
1199
+ { value : 1 , viewValue : 'Pizza' , selected : false } ,
1200
+ { value : 2 , viewValue : 'Pasta' , selected : true } ,
1085
1201
] ;
1086
1202
@ViewChildren ( MatChip ) chips : QueryList < MatChip > ;
1087
1203
}
1204
+
1205
+ @Component ( {
1206
+ template : `
1207
+ <form #form="ngForm" novalidate>
1208
+ <mat-form-field>
1209
+ <mat-chip-list [formControl]="formControl">
1210
+ <mat-chip *ngFor="let food of foods" [value]="food.value" [selected]="food.selected">
1211
+ {{food.viewValue}}
1212
+ </mat-chip>
1213
+ </mat-chip-list>
1214
+ <mat-hint>Please select a chip, or type to add a new chip</mat-hint>
1215
+ <mat-error>Should have value</mat-error>
1216
+ </mat-form-field>
1217
+ </form>
1218
+ `
1219
+ } )
1220
+ class ChipListWithFormErrorMessages {
1221
+ foods : any [ ] = [
1222
+ { value : 0 , viewValue : 'Steak' , selected : true } ,
1223
+ { value : 1 , viewValue : 'Pizza' , selected : false } ,
1224
+ { value : 2 , viewValue : 'Pasta' , selected : true } ,
1225
+ ] ;
1226
+ @ViewChildren ( MatChip ) chips : QueryList < MatChip > ;
1227
+
1228
+ @ViewChild ( 'form' ) form : NgForm ;
1229
+ formControl = new FormControl ( '' , Validators . required ) ;
1230
+ }
0 commit comments