@@ -429,6 +429,136 @@ describe('control directive', () => {
429429 expect ( element . min ) . toBe ( '' ) ;
430430 } ) ;
431431 } ) ;
432+
433+ describe ( 'maxLength' , ( ) => {
434+ it ( 'native control' , ( ) => {
435+ @Component ( {
436+ imports : [ Control ] ,
437+ template : `<textarea [control]="f"></textarea>` ,
438+ } )
439+ class TestCmp {
440+ readonly maxLength = signal ( 20 ) ;
441+ readonly f = form ( signal ( '' ) , ( p ) => {
442+ maxLength ( p , this . maxLength ) ;
443+ } ) ;
444+ }
445+
446+ const fixture = act ( ( ) => TestBed . createComponent ( TestCmp ) ) ;
447+ const element = fixture . nativeElement . firstChild as HTMLTextAreaElement ;
448+ expect ( element . maxLength ) . toBe ( 20 ) ;
449+
450+ act ( ( ) => fixture . componentInstance . maxLength . set ( 15 ) ) ;
451+ expect ( element . maxLength ) . toBe ( 15 ) ;
452+ } ) ;
453+
454+ it ( 'custom control' , ( ) => {
455+ @Component ( { selector : 'custom-control' , template : `` } )
456+ class CustomControl {
457+ readonly value = model ( '' ) ;
458+ readonly maxLength = input < number | null > ( null ) ;
459+ }
460+
461+ @Component ( {
462+ imports : [ Control , CustomControl ] ,
463+ template : `<custom-control [control]="f" />` ,
464+ } )
465+ class TestCmp {
466+ readonly maxLength = signal ( 10 ) ;
467+ readonly f = form ( signal ( '' ) , ( p ) => {
468+ maxLength ( p , this . maxLength ) ;
469+ } ) ;
470+ readonly customControl = viewChild . required ( CustomControl ) ;
471+ }
472+
473+ const fixture = act ( ( ) => TestBed . createComponent ( TestCmp ) ) ;
474+ const component = fixture . componentInstance ;
475+ expect ( component . customControl ( ) . maxLength ( ) ) . toBe ( 10 ) ;
476+
477+ act ( ( ) => component . maxLength . set ( 5 ) ) ;
478+ expect ( component . customControl ( ) . maxLength ( ) ) . toBe ( 5 ) ;
479+ } ) ;
480+
481+ it ( 'is not set on a native control that does not support it' , ( ) => {
482+ @Component ( {
483+ imports : [ Control ] ,
484+ template : `<select [control]="f"></select>` ,
485+ } )
486+ class TestCmp {
487+ readonly f = form ( signal ( '' ) , ( p ) => {
488+ maxLength ( p , 10 ) ;
489+ } ) ;
490+ }
491+
492+ const fixture = act ( ( ) => TestBed . createComponent ( TestCmp ) ) ;
493+ const element = fixture . nativeElement . firstChild as HTMLSelectElement ;
494+ expect ( element . getAttribute ( 'maxLength' ) ) . toBeNull ( ) ;
495+ } ) ;
496+ } ) ;
497+
498+ describe ( 'minLength' , ( ) => {
499+ it ( 'native control' , ( ) => {
500+ @Component ( {
501+ imports : [ Control ] ,
502+ template : `<textarea [control]="f"></textarea>` ,
503+ } )
504+ class TestCmp {
505+ readonly minLength = signal ( 20 ) ;
506+ readonly f = form ( signal ( '' ) , ( p ) => {
507+ minLength ( p , this . minLength ) ;
508+ } ) ;
509+ }
510+
511+ const fixture = act ( ( ) => TestBed . createComponent ( TestCmp ) ) ;
512+ const element = fixture . nativeElement . firstChild as HTMLTextAreaElement ;
513+ expect ( element . minLength ) . toBe ( 20 ) ;
514+
515+ act ( ( ) => fixture . componentInstance . minLength . set ( 15 ) ) ;
516+ expect ( element . minLength ) . toBe ( 15 ) ;
517+ } ) ;
518+
519+ it ( 'custom control' , ( ) => {
520+ @Component ( { selector : 'custom-control' , template : `` } )
521+ class CustomControl {
522+ readonly value = model ( '' ) ;
523+ readonly minLength = input < number | null > ( null ) ;
524+ }
525+
526+ @Component ( {
527+ imports : [ Control , CustomControl ] ,
528+ template : `<custom-control [control]="f" />` ,
529+ } )
530+ class TestCmp {
531+ readonly minLength = signal ( 10 ) ;
532+ readonly f = form ( signal ( '' ) , ( p ) => {
533+ minLength ( p , this . minLength ) ;
534+ } ) ;
535+ readonly customControl = viewChild . required ( CustomControl ) ;
536+ }
537+
538+ const fixture = act ( ( ) => TestBed . createComponent ( TestCmp ) ) ;
539+ const component = fixture . componentInstance ;
540+ expect ( component . customControl ( ) . minLength ( ) ) . toBe ( 10 ) ;
541+
542+ act ( ( ) => component . minLength . set ( 5 ) ) ;
543+ expect ( component . customControl ( ) . minLength ( ) ) . toBe ( 5 ) ;
544+ } ) ;
545+
546+ it ( 'is not set on a native control that does not support it' , ( ) => {
547+ @Component ( {
548+ imports : [ Control ] ,
549+ template : `<select [control]="f"></select>` ,
550+ } )
551+ class TestCmp {
552+ readonly f = form ( signal ( '' ) , ( p ) => {
553+ minLength ( p , 10 ) ;
554+ } ) ;
555+ }
556+
557+ const fixture = act ( ( ) => TestBed . createComponent ( TestCmp ) ) ;
558+ const element = fixture . nativeElement . firstChild as HTMLSelectElement ;
559+ expect ( element . getAttribute ( 'minLength' ) ) . toBeNull ( ) ;
560+ } ) ;
561+ } ) ;
432562 } ) ;
433563
434564 it ( 'synchronizes a basic form with a custom control' , ( ) => {
0 commit comments