@@ -8,8 +8,10 @@ import {
88import { TestBed } from '@angular/core/testing' ;
99import {
1010 FormControl ,
11+ FormGroup ,
1112 FormGroupDirective ,
1213 NgForm ,
14+ ReactiveFormsModule ,
1315 Validators ,
1416} from '@angular/forms' ;
1517import { By } from '@angular/platform-browser' ;
@@ -22,7 +24,7 @@ import {
2224} from './control-error' ;
2325
2426describe ( 'NgxControlError' , ( ) => {
25- const unitTest = ( test : ( ) => void | Promise < void > ) => async ( ) => {
27+ const isolatedTest = ( test : ( ) => void | Promise < void > ) => async ( ) => {
2628 TestBed . overrideProvider ( TemplateRef , { useValue : undefined } ) ;
2729 TestBed . overrideProvider ( ViewContainerRef , {
2830 useValue : {
@@ -39,13 +41,13 @@ describe('NgxControlError', () => {
3941 await TestBed . runInInjectionContext ( test ) ;
4042 } ;
4143
42- const render = (
44+ const render = < TInputs > (
4345 template : string ,
44- inputs ?: Partial < NgxControlError > | undefined ,
46+ inputs ?: Partial < TInputs > | undefined ,
4547 providers ?: Provider [ ] ,
4648 ) => {
4749 @Component ( {
48- imports : [ CommonModule , NgxControlError ] ,
50+ imports : [ CommonModule , ReactiveFormsModule , NgxControlError ] ,
4951 standalone : true ,
5052 template,
5153 providers,
@@ -70,12 +72,12 @@ describe('NgxControlError', () => {
7072
7173 it (
7274 'should be created' ,
73- unitTest ( ( ) => expect ( new NgxControlError ( ) ) . toBeTruthy ( ) ) ,
75+ isolatedTest ( ( ) => expect ( new NgxControlError ( ) ) . toBeTruthy ( ) ) ,
7476 ) ;
7577
7678 it (
7779 'should have a context guard' ,
78- unitTest ( ( ) =>
80+ isolatedTest ( ( ) =>
7981 expect (
8082 NgxControlError . ngTemplateContextGuard ( new NgxControlError ( ) , { } ) ,
8183 ) . toBe ( true ) ,
@@ -85,7 +87,7 @@ describe('NgxControlError', () => {
8587 describe ( 'should have an error when the control includes the tracked error and the control is in an error state' , ( ) => {
8688 it (
8789 'respecting track changes' ,
88- unitTest ( ( ) => {
90+ isolatedTest ( ( ) => {
8991 const instance = new NgxControlError ( ) ;
9092
9193 instance . track$ . set ( 'required' ) ;
@@ -104,7 +106,7 @@ describe('NgxControlError', () => {
104106
105107 it (
106108 'when it has at least 1 tracked error ' ,
107- unitTest ( ( ) => {
109+ isolatedTest ( ( ) => {
108110 const instance = new NgxControlError ( ) ;
109111 const control = new FormControl ( '42' , [
110112 Validators . minLength ( 3 ) ,
@@ -128,7 +130,7 @@ describe('NgxControlError', () => {
128130
129131 it (
130132 'respecting error state matcher changes' ,
131- unitTest ( ( ) => {
133+ isolatedTest ( ( ) => {
132134 const instance = new NgxControlError ( ) ;
133135
134136 instance . track$ . set ( 'required' ) ;
@@ -147,7 +149,7 @@ describe('NgxControlError', () => {
147149
148150 it (
149151 'respecting control instance changes' ,
150- unitTest ( ( ) => {
152+ isolatedTest ( ( ) => {
151153 const instance = new NgxControlError ( ) ;
152154
153155 instance . track$ . set ( 'required' ) ;
@@ -167,7 +169,7 @@ describe('NgxControlError', () => {
167169
168170 it (
169171 'DEFAULT_ERROR_STATE_MATCHER should match when the control is: 1. invalid 2. touched or its parent is submitted' ,
170- unitTest ( ( ) => {
172+ isolatedTest ( ( ) => {
171173 const instance = new NgxControlError ( ) ;
172174 const control = new FormControl ( '' , Validators . required ) ;
173175
@@ -241,17 +243,30 @@ describe('NgxControlError', () => {
241243 it ( 'should have an injectable error state matcher' , ( ) => {
242244 const errorStateMatcher = jest . fn ( ) ;
243245
246+ const params = {
247+ error : 'required' ,
248+ control : new FormControl ( '' , Validators . required ) ,
249+ } ;
250+
244251 const [ , controlError ] = render (
245- '<ng-template ngxControlError / >' ,
246- undefined ,
252+ '<span * ngxControlError="control; track: error">42</span >' ,
253+ params ,
247254 provideNgxControlError ( { errorStateMatcher : ( ) => errorStateMatcher } ) ,
248255 ) ;
249256
250257 expect ( controlError . errorStateMatcher$ ( ) ) . toBe ( errorStateMatcher ) ;
251258 } ) ;
252259
253260 it ( 'should use the default error state matcher as default' , ( ) => {
254- const [ , controlError ] = render ( '<ng-template ngxControlError />' ) ;
261+ const params = {
262+ error : 'required' ,
263+ control : new FormControl ( '' , Validators . required ) ,
264+ } ;
265+
266+ const [ , controlError ] = render (
267+ '<span *ngxControlError="control; track: error">42</span>' ,
268+ params ,
269+ ) ;
255270
256271 expect ( controlError . errorStateMatcher$ ( ) ) . toBe (
257272 NGX_DEFAULT_CONTROL_ERROR_STATE_MATCHER ,
@@ -299,4 +314,144 @@ describe('NgxControlError', () => {
299314 'INVALID - true' ,
300315 ) ;
301316 } ) ;
317+
318+ it ( 'should resolve a control by its name' , ( ) => {
319+ const params = {
320+ error : 'required' ,
321+ form : new FormGroup ( {
322+ name : new FormControl ( '' , Validators . required ) ,
323+ } ) ,
324+ stateMatcher : ( ) => of ( true ) ,
325+ } ;
326+
327+ const [ fixture , controlError ] = render (
328+ `<form [formGroup]="form">
329+ <label>
330+ <input formControlName="name" />
331+ <span *ngxControlError="'name'; track: error, errorStateMatcher: stateMatcher">42</span>
332+ </label>
333+ </form>
334+ ` ,
335+ params ,
336+ ) ;
337+
338+ fixture . detectChanges ( ) ;
339+
340+ expect ( fixture . debugElement . nativeElement . textContent ) . toBe ( '42' ) ;
341+
342+ controlError . errorStateMatcher = ( ) => of ( false ) ;
343+
344+ fixture . detectChanges ( ) ;
345+
346+ expect ( fixture . debugElement . nativeElement . textContent ) . toBe ( '' ) ;
347+ } ) ;
348+
349+ it ( 'should throw when a control cannot be found because there is no parent control' , ( ) => {
350+ expect ( ( ) =>
351+ render ( `
352+ <span *ngxControlError="'name'; track: 'required'">42</span>
353+ ` ) ,
354+ ) . toThrow (
355+ '[NgxControlError]: A control name cannot be specified without a parent FormGroup.' ,
356+ ) ;
357+ } ) ;
358+
359+ it ( 'should throw when a control cannot be found in the parent form group' , ( ) => {
360+ const params = {
361+ form : new FormGroup ( {
362+ name : new FormControl ( '' , Validators . required ) ,
363+ } ) ,
364+ } ;
365+
366+ expect ( ( ) =>
367+ render (
368+ `<form [formGroup]="form">
369+ <span *ngxControlError="'nonExistentControlname'; track: 'required'">42</span>
370+ </form>
371+ ` ,
372+ params ,
373+ ) ,
374+ ) . toThrow (
375+ `[NgxControlError]: Cannot find control with name 'nonExistentControlname'.` ,
376+ ) ;
377+ } ) ;
378+
379+ it ( 'should throw when a control cannot be found in a nested parent form group' , ( ) => {
380+ expect ( ( ) =>
381+ render (
382+ `<form [formGroup]="form">
383+ <div formGroupName="nested">
384+ <span *ngxControlError="'nonExistentControlname'; track: 'required'">42</span>
385+ </div>
386+ </form>
387+ ` ,
388+ {
389+ form : new FormGroup ( {
390+ nested : new FormGroup ( {
391+ name : new FormControl ( '' , Validators . required ) ,
392+ } ) ,
393+ } ) ,
394+ } ,
395+ ) ,
396+ ) . toThrow (
397+ `[NgxControlError]: Cannot find control with name 'nonExistentControlname'.` ,
398+ ) ;
399+
400+ expect ( ( ) =>
401+ render (
402+ `<form [formGroup]="form">
403+ <div formGroupName="nested">
404+ <span *ngxControlError="'name1'; track: 'required'">42</span>
405+ </div>
406+ </form>
407+ ` ,
408+ {
409+ form : new FormGroup ( {
410+ name1 : new FormControl ( '' , Validators . required ) ,
411+ nested : new FormGroup ( {
412+ name2 : new FormControl ( '' , Validators . required ) ,
413+ } ) ,
414+ } ) ,
415+ } ,
416+ ) ,
417+ ) . toThrow ( `[NgxControlError]: Cannot find control with name 'name1'.` ) ;
418+ } ) ;
419+
420+ it ( 'should resolve a nested control by its name' , ( ) => {
421+ const params = {
422+ error : 'required' ,
423+ form : new FormGroup ( {
424+ nested : new FormGroup ( {
425+ name : new FormControl ( '' , Validators . required ) ,
426+ } ) ,
427+ } ) ,
428+ stateMatcher : ( ) => of ( true ) ,
429+ } ;
430+
431+ const [ fixture , controlError ] = render (
432+ `
433+ <form [formGroup]="form">
434+ <div formGroupName="nested">
435+ <label>
436+ <input formControlName="name" />
437+ <span
438+ *ngxControlError="'name'; track: error, errorStateMatcher: stateMatcher"
439+ >42</span>
440+ </label>
441+ </div>
442+ </form>
443+ ` ,
444+ params ,
445+ ) ;
446+
447+ fixture . detectChanges ( ) ;
448+
449+ expect ( fixture . debugElement . nativeElement . textContent ) . toBe ( '42' ) ;
450+
451+ controlError . errorStateMatcher = ( ) => of ( false ) ;
452+
453+ fixture . detectChanges ( ) ;
454+
455+ expect ( fixture . debugElement . nativeElement . textContent ) . toBe ( '' ) ;
456+ } ) ;
302457} ) ;
0 commit comments