33// The .NET Foundation licenses this file to you under the MIT license.
44// See the LICENSE file in the project root for full license information.
55
6+ using ReactiveUI . Builder ;
7+ using ReactiveUI . Tests . Utilities . AppBuilder ;
8+
69namespace ReactiveUI . Tests ;
710
811/// <summary>
912/// Unit tests for <see cref="BindingConverterResolver"/>.
1013/// </summary>
1114/// <remarks>
12- /// These tests verify converter resolution logic without manipulating global Splat state .
13- /// Tests use the real implementation and rely on converters registered in the DI container .
15+ /// These tests verify converter resolution logic.
16+ /// Tests use the Executor paradigm to manage AppBuilder state and registrations .
1417/// </remarks>
18+ [ TestExecutor < BindingConverterResolverTests . Executor > ]
1519public class BindingConverterResolverTests
1620{
1721 /// <summary>
@@ -24,7 +28,7 @@ public async Task GetBindingConverter_WithRegisteredTypedConverter_ReturnsConver
2428 // Arrange
2529 var resolver = new BindingConverterResolver ( ) ;
2630
27- // Act - IntegerToStringTypeConverter is registered by default in RxConverters
31+ // Act - IntegerToStringTypeConverter is registered by default via WithCoreServices
2832 var converter = resolver . GetBindingConverter ( typeof ( int ) , typeof ( string ) ) ;
2933
3034 // Assert
@@ -59,10 +63,12 @@ public async Task GetSetMethodConverter_WithCaching_ReturnsSameInstance()
5963 var resolver = new BindingConverterResolver ( ) ;
6064
6165 // Act
62- var converter1 = resolver . GetSetMethodConverter ( typeof ( int ) , typeof ( string ) ) ;
63- var converter2 = resolver . GetSetMethodConverter ( typeof ( int ) , typeof ( string ) ) ;
66+ // Use MockType which has a registered set method converter
67+ var converter1 = resolver . GetSetMethodConverter ( typeof ( MockType ) , typeof ( MockType ) ) ;
68+ var converter2 = resolver . GetSetMethodConverter ( typeof ( MockType ) , typeof ( MockType ) ) ;
6469
65- // Assert - Should return same cached instance (or both null)
70+ // Assert
71+ await Assert . That ( converter1 ) . IsNotNull ( ) ;
6672 await Assert . That ( converter1 ) . IsSameReferenceAs ( converter2 ) ;
6773 }
6874
@@ -100,4 +106,143 @@ public async Task GetBindingConverter_UsesRxConverters_WhenAvailable()
100106 await Assert . That ( converter ) . IsNotNull ( ) ;
101107 await Assert . That ( converter ) . IsTypeOf < IntegerToStringTypeConverter > ( ) ;
102108 }
103- }
109+
110+ /// <summary>
111+ /// Verifies that GetBindingConverter falls back to Splat if not in RxConverters.
112+ /// </summary>
113+ /// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
114+ [ Test ]
115+ public async Task GetBindingConverter_FallsBackToSplat_WhenRxConvertersFails ( )
116+ {
117+ // Arrange
118+ var resolver = new BindingConverterResolver ( ) ;
119+
120+ // Act
121+ // MockType converter is registered in Splat (via Executor) but NOT in RxConverters (standard list)
122+ var converter = resolver . GetBindingConverter ( typeof ( MockType ) , typeof ( MockType ) ) ;
123+
124+ // Assert
125+ await Assert . That ( converter ) . IsNotNull ( ) ;
126+ await Assert . That ( converter ) . IsTypeOf < MockBindingTypeConverter > ( ) ;
127+ }
128+
129+ /// <summary>
130+ /// Verifies that GetSetMethodConverter returns a registered converter.
131+ /// </summary>
132+ /// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
133+ [ Test ]
134+ public async Task GetSetMethodConverter_ReturnsConverter_WhenRegistered ( )
135+ {
136+ // Arrange
137+ var resolver = new BindingConverterResolver ( ) ;
138+
139+ // Act
140+ var converterFunc = resolver . GetSetMethodConverter ( typeof ( MockType ) , typeof ( MockType ) ) ;
141+
142+ // Assert
143+ await Assert . That ( converterFunc ) . IsNotNull ( ) ;
144+
145+ // Invoke to verify
146+ var result = converterFunc ! ( new MockType ( ) , new MockType ( ) , null ) ;
147+ await Assert . That ( result ) . IsEqualTo ( "SetPerformed" ) ;
148+ }
149+
150+ /// <summary>
151+ /// Verifies that GetSetMethodConverter returns null when no converter is registered.
152+ /// </summary>
153+ /// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
154+ [ Test ]
155+ public async Task GetSetMethodConverter_WithUnregisteredType_ReturnsNull ( )
156+ {
157+ // Arrange
158+ var resolver = new BindingConverterResolver ( ) ;
159+
160+ // Act
161+ var converterFunc = resolver . GetSetMethodConverter ( typeof ( System . Net . IPAddress ) , typeof ( System . Numerics . BigInteger ) ) ;
162+
163+ // Assert
164+ await Assert . That ( converterFunc ) . IsNull ( ) ;
165+ }
166+
167+ /// <summary>
168+ /// Verifies that GetSetMethodConverter handles null toType gracefully.
169+ /// </summary>
170+ /// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
171+ [ Test ]
172+ public async Task GetSetMethodConverter_WithNullToType_HandlesGracefully ( )
173+ {
174+ // Arrange
175+ var resolver = new BindingConverterResolver ( ) ;
176+
177+ // Act
178+ var converterFunc = resolver . GetSetMethodConverter ( typeof ( MockType ) , null ) ;
179+
180+ // Assert
181+ // The MockSetMethodBindingConverter returns affinity 0 for null toType, so converter should be null
182+ // The test verifies the method handles null toType gracefully without throwing
183+ await Assert . That ( converterFunc ) . IsNull ( ) ;
184+ }
185+
186+ /// <summary>
187+ /// Verifies that GetBindingConverter handles null services gracefully.
188+ /// </summary>
189+ /// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
190+ [ Test ]
191+ public async Task GetBindingConverter_WithNoRxConverters_FallsBackToSplat ( )
192+ {
193+ // Arrange
194+ var resolver = new BindingConverterResolver ( ) ;
195+
196+ // Act - Get a converter that should be found in Splat
197+ var converter = resolver . GetBindingConverter ( typeof ( MockType ) , typeof ( MockType ) ) ;
198+
199+ // Assert
200+ await Assert . That ( converter ) . IsNotNull ( ) ;
201+ await Assert . That ( converter ) . IsTypeOf < MockBindingTypeConverter > ( ) ;
202+ }
203+
204+ /// <summary>
205+ /// Test executor that registers mock converters.
206+ /// </summary>
207+ public class Executor : BaseAppBuilderTestExecutor
208+ {
209+ /// <inheritdoc/>
210+ protected override void ConfigureAppBuilder ( IReactiveUIBuilder builder , TestContext context )
211+ {
212+ ArgumentNullException . ThrowIfNull ( builder ) ;
213+ ArgumentNullException . ThrowIfNull ( context ) ;
214+
215+ builder
216+ . WithRegistration ( r => r . RegisterConstant < IBindingTypeConverter > ( new MockBindingTypeConverter ( ) ) )
217+ . WithRegistration ( r => r . RegisterConstant < ISetMethodBindingConverter > ( new MockSetMethodBindingConverter ( ) ) )
218+ . WithCoreServices ( ) ;
219+ }
220+ }
221+
222+ private class MockType
223+ {
224+ }
225+
226+ private class MockBindingTypeConverter : IBindingTypeConverter
227+ {
228+ public Type FromType => typeof ( MockType ) ;
229+
230+ public Type ToType => typeof ( MockType ) ;
231+
232+ public int GetAffinityForObjects ( ) => 100 ;
233+
234+ public bool TryConvertTyped ( object ? from , object ? conversionHint , out object ? result )
235+ {
236+ result = null ;
237+ return false ;
238+ }
239+ }
240+
241+ private class MockSetMethodBindingConverter : ISetMethodBindingConverter
242+ {
243+ public int GetAffinityForObjects ( Type ? fromType , Type ? toType ) =>
244+ fromType == typeof ( MockType ) && toType == typeof ( MockType ) ? 100 : 0 ;
245+
246+ public object ? PerformSet ( object ? current , object ? newValue , object ? [ ] ? arguments ) => "SetPerformed" ;
247+ }
248+ }
0 commit comments