77 */
88
99import {
10+ booleanAttribute ,
1011 Component ,
1112 computed ,
1213 Directive ,
@@ -16,6 +17,7 @@ import {
1617 input ,
1718 inputBinding ,
1819 model ,
20+ numberAttribute ,
1921 signal ,
2022 viewChild ,
2123 viewChildren ,
@@ -1133,6 +1135,143 @@ describe('field directive', () => {
11331135 } ) ;
11341136 } ) ;
11351137
1138+ describe ( 'input transforms' , ( ) => {
1139+ it ( 'should accept InputSignal without transform' , ( ) => {
1140+ @Component ( { selector : 'custom-control' , template : `` } )
1141+ class CustomControl implements FormValueControl < string > {
1142+ readonly value = model ( '' ) ;
1143+ readonly disabled = input ( false ) ;
1144+ readonly readonly = input ( false ) ;
1145+ readonly required = input ( false ) ;
1146+ readonly hidden = input ( false ) ;
1147+ readonly invalid = input ( false ) ;
1148+ readonly pending = input ( false ) ;
1149+ readonly dirty = input ( false ) ;
1150+ readonly touched = input ( false ) ;
1151+ readonly min = input < number | undefined > ( 1 ) ;
1152+ readonly max = input < number | undefined > ( 1_0000 ) ;
1153+ readonly minLength = input < number | undefined > ( 1 ) ;
1154+ readonly maxLength = input < number | undefined > ( 5 ) ;
1155+ }
1156+
1157+ @Component ( {
1158+ imports : [ Field , CustomControl ] ,
1159+ template : `<custom-control [field]="f" />` ,
1160+ } )
1161+ class TestCmp {
1162+ readonly f = form ( signal ( '' ) ) ;
1163+ }
1164+
1165+ const fixture = act ( ( ) => TestBed . createComponent ( TestCmp ) ) ;
1166+ expect ( fixture . componentInstance ) . toBeDefined ( ) ;
1167+ } ) ;
1168+
1169+ it ( 'should accept InputSignalWithTransform for boolean properties' , ( ) => {
1170+ @Component ( { selector : 'custom-control' , template : `` } )
1171+ class CustomControl implements FormValueControl < string > {
1172+ readonly value = model ( '' ) ;
1173+ readonly disabled = input ( false , { transform : booleanAttribute } ) ;
1174+ readonly readonly = input ( false , { transform : booleanAttribute } ) ;
1175+ readonly required = input ( false , { transform : booleanAttribute } ) ;
1176+ readonly hidden = input ( false , { transform : booleanAttribute } ) ;
1177+ readonly invalid = input ( false , { transform : booleanAttribute } ) ;
1178+ readonly pending = input ( false , { transform : booleanAttribute } ) ;
1179+ readonly dirty = input ( false , { transform : booleanAttribute } ) ;
1180+ readonly touched = input ( false , { transform : booleanAttribute } ) ;
1181+ }
1182+
1183+ @Component ( {
1184+ imports : [ Field , CustomControl ] ,
1185+ template : `<custom-control [field]="f" />` ,
1186+ } )
1187+ class TestCmp {
1188+ readonly f = form ( signal ( '' ) ) ;
1189+ }
1190+
1191+ const fixture = act ( ( ) => TestBed . createComponent ( TestCmp ) ) ;
1192+ expect ( fixture . componentInstance ) . toBeDefined ( ) ;
1193+ } ) ;
1194+
1195+ it ( 'should accept InputSignalWithTransform for number properties' , ( ) => {
1196+ @Component ( { selector : 'custom-control' , template : `` } )
1197+ class CustomControl implements FormValueControl < number > {
1198+ readonly value = model ( 0 ) ;
1199+ readonly min = input < number | undefined , unknown > ( undefined , { transform : numberAttribute } ) ;
1200+ readonly max = input < number | undefined , unknown > ( undefined , { transform : numberAttribute } ) ;
1201+ readonly minLength = input < number | undefined , unknown > ( undefined , {
1202+ transform : numberAttribute ,
1203+ } ) ;
1204+ readonly maxLength = input < number | undefined , unknown > ( undefined , {
1205+ transform : numberAttribute ,
1206+ } ) ;
1207+ }
1208+
1209+ @Component ( {
1210+ imports : [ Field , CustomControl ] ,
1211+ template : `<custom-control [field]="f" />` ,
1212+ } )
1213+ class TestCmp {
1214+ readonly f = form ( signal ( 0 ) ) ;
1215+ }
1216+
1217+ const fixture = act ( ( ) => TestBed . createComponent ( TestCmp ) ) ;
1218+ expect ( fixture . componentInstance ) . toBeDefined ( ) ;
1219+ } ) ;
1220+
1221+ it ( 'should accept custom transform for arrays' , ( ) => {
1222+ @Component ( { selector : 'custom-control' , template : `` } )
1223+ class CustomControl implements FormValueControl < string > {
1224+ readonly value = model ( '' ) ;
1225+ readonly name = input ( '' , { transform : ( v : unknown ) => String ( v ?? '' ) } ) ;
1226+ readonly pattern = input < readonly RegExp [ ] , unknown > ( [ ] , {
1227+ transform : ( v : unknown ) => ( Array . isArray ( v ) ? v : [ ] ) ,
1228+ } ) ;
1229+ readonly errors = input < readonly WithOptionalField < ValidationError > [ ] , unknown > ( [ ] , {
1230+ transform : ( v : unknown ) => ( Array . isArray ( v ) ? v : [ ] ) ,
1231+ } ) ;
1232+ readonly disabledReasons = input < readonly WithOptionalField < DisabledReason > [ ] , unknown > (
1233+ [ ] ,
1234+ {
1235+ transform : ( v : unknown ) => ( Array . isArray ( v ) ? v : [ ] ) ,
1236+ } ,
1237+ ) ;
1238+ }
1239+
1240+ @Component ( {
1241+ imports : [ Field , CustomControl ] ,
1242+ template : `<custom-control [field]="f" />` ,
1243+ } )
1244+ class TestCmp {
1245+ readonly f = form ( signal ( '' ) ) ;
1246+ }
1247+
1248+ const fixture = act ( ( ) => TestBed . createComponent ( TestCmp ) ) ;
1249+ expect ( fixture . componentInstance ) . toBeDefined ( ) ;
1250+ } ) ;
1251+
1252+ it ( 'should accept mixed InputSignal and InputSignalWithTransform' , ( ) => {
1253+ @Component ( { selector : 'custom-control' , template : `` } )
1254+ class CustomControl implements FormValueControl < string > {
1255+ readonly value = model ( '' ) ;
1256+ readonly disabled = input ( false ) ;
1257+ readonly readonly = input ( false , { transform : booleanAttribute } ) ;
1258+ readonly required = input ( false ) ;
1259+ readonly name = input ( '' , { transform : ( v : unknown ) => String ( v ?? '' ) } ) ;
1260+ }
1261+
1262+ @Component ( {
1263+ imports : [ Field , CustomControl ] ,
1264+ template : `<custom-control [field]="f" />` ,
1265+ } )
1266+ class TestCmp {
1267+ readonly f = form ( signal ( '' ) ) ;
1268+ }
1269+
1270+ const fixture = act ( ( ) => TestBed . createComponent ( TestCmp ) ) ;
1271+ expect ( fixture . componentInstance ) . toBeDefined ( ) ;
1272+ } ) ;
1273+ } ) ;
1274+
11361275 it ( 'synchronizes with a value control' , ( ) => {
11371276 @Component ( {
11381277 imports : [ Field ] ,
0 commit comments