@@ -18,6 +18,7 @@ import {
1818 inputBinding ,
1919 model ,
2020 numberAttribute ,
21+ resource ,
2122 signal ,
2223 viewChild ,
2324 viewChildren ,
@@ -39,6 +40,7 @@ import {
3940 provideSignalFormsConfig ,
4041 readonly ,
4142 required ,
43+ validateAsync ,
4244 type DisabledReason ,
4345 type FieldTree ,
4446 type FormCheckboxControl ,
@@ -1886,6 +1888,109 @@ describe('field directive', () => {
18861888 expect ( comp . myInput ( ) . invalid ( ) ) . toBe ( false ) ;
18871889 } ) ;
18881890
1891+ it ( 'should synchronize hidden status' , ( ) => {
1892+ @Component ( {
1893+ selector : 'my-input' ,
1894+ template : '<input #i [value]="value()" (input)="value.set(i.value)" />' ,
1895+ } )
1896+ class CustomInput implements FormValueControl < string > {
1897+ value = model ( '' ) ;
1898+ hidden = input ( false ) ;
1899+ }
1900+
1901+ @Component ( {
1902+ template : `
1903+ <my-input [field]="f" />
1904+ ` ,
1905+ imports : [ CustomInput , Field ] ,
1906+ } )
1907+ class HiddenTestCmp {
1908+ myInput = viewChild . required < CustomInput > ( CustomInput ) ;
1909+ data = signal ( '' ) ;
1910+ f = form ( this . data , ( p ) => {
1911+ hidden ( p , ( { value} ) => value ( ) === '' ) ;
1912+ } ) ;
1913+ }
1914+
1915+ const comp = act ( ( ) => TestBed . createComponent ( HiddenTestCmp ) ) . componentInstance ;
1916+ expect ( comp . myInput ( ) . hidden ( ) ) . toBe ( true ) ;
1917+ act ( ( ) => comp . f ( ) . value . set ( 'visible' ) ) ;
1918+ expect ( comp . myInput ( ) . hidden ( ) ) . toBe ( false ) ;
1919+ } ) ;
1920+
1921+ it ( 'should synchronize dirty status' , ( ) => {
1922+ @Component ( {
1923+ selector : 'my-input' ,
1924+ template : '<input #i [value]="value()" (input)="value.set(i.value)" />' ,
1925+ } )
1926+ class CustomInput implements FormValueControl < string > {
1927+ value = model ( '' ) ;
1928+ dirty = input ( false ) ;
1929+ }
1930+
1931+ @Component ( {
1932+ template : `
1933+ <my-input [field]="f" />
1934+ ` ,
1935+ imports : [ CustomInput , Field ] ,
1936+ } )
1937+ class DirtyTestCmp {
1938+ myInput = viewChild . required < CustomInput > ( CustomInput ) ;
1939+ data = signal ( '' ) ;
1940+ f = form ( this . data ) ;
1941+ }
1942+
1943+ const comp = act ( ( ) => TestBed . createComponent ( DirtyTestCmp ) ) . componentInstance ;
1944+ expect ( comp . myInput ( ) . dirty ( ) ) . toBe ( false ) ;
1945+ act ( ( ) => comp . f ( ) . markAsDirty ( ) ) ;
1946+ expect ( comp . myInput ( ) . dirty ( ) ) . toBe ( true ) ;
1947+ } ) ;
1948+
1949+ it ( 'should synchronize pending status' , async ( ) => {
1950+ const { promise, resolve} = promiseWithResolvers < ValidationError [ ] > ( ) ;
1951+
1952+ @Component ( {
1953+ selector : 'my-input' ,
1954+ template : '<input #i [value]="value()" (input)="value.set(i.value)" />' ,
1955+ } )
1956+ class CustomInput implements FormValueControl < string > {
1957+ value = model ( '' ) ;
1958+ pending = input ( false ) ;
1959+ }
1960+
1961+ @Component ( {
1962+ template : `
1963+ <my-input [field]="f" />
1964+ ` ,
1965+ imports : [ CustomInput , Field ] ,
1966+ } )
1967+ class PendingTestCmp {
1968+ myInput = viewChild . required < CustomInput > ( CustomInput ) ;
1969+ data = signal ( 'test' ) ;
1970+ f = form ( this . data , ( p ) => {
1971+ validateAsync ( p , {
1972+ params : ( ) => [ ] ,
1973+ factory : ( params ) =>
1974+ resource ( {
1975+ params,
1976+ loader : ( ) => promise ,
1977+ } ) ,
1978+ onSuccess : ( results ) => results ,
1979+ onError : ( ) => null ,
1980+ } ) ;
1981+ } ) ;
1982+ }
1983+
1984+ const fix = act ( ( ) => TestBed . createComponent ( PendingTestCmp ) ) ;
1985+
1986+ expect ( fix . componentInstance . myInput ( ) . pending ( ) ) . toBe ( true ) ;
1987+
1988+ resolve ( [ ] ) ;
1989+ await promise ;
1990+ await fix . whenStable ( ) ;
1991+ expect ( fix . componentInstance . myInput ( ) . pending ( ) ) . toBe ( false ) ;
1992+ } ) ;
1993+
18891994 it ( `should mark field as touched on native control 'blur' event` , ( ) => {
18901995 @Component ( {
18911996 imports : [ Field ] ,
0 commit comments