Skip to content

Commit 194b4f4

Browse files
authored
Add comprehensive unit tests for Uno controls and internals (#311)
* Remove SQLiteStudio sample and update configs Deleted the ReactiveUI.Uno.SQLiteStudio sample project and related files, including test projects and configuration files. Updated .editorconfig to improve organization, add comments, and clarify analyzer rules. Added new documentation and configuration files (CLAUDE.md, agent.md, codecov.yml, etc.), and updated solution and project references accordingly. * Add .slnx solution, update WinRTAppDataDriver, and add tests Replaces the .sln solution file with a new .slnx format and updates references accordingly. Enhances WinRTAppDataDriver to implement JSON-based state load/save/invalidate methods and improves error handling. Updates Directory.Build.props to include net9.0 in cross-platform targets and suppresses additional warnings. Adds comprehensive unit tests for activation, bootstrapping, builder extensions, controls, converters, helpers, hooks, observables, platform operations, registrations, resources, schedulers, and storage. Updates example project to suppress more warnings and disables TreatWarningsAsErrors. Simplifies and redirects Copilot instructions to agent.md. * Add ReactiveUI.Uno.SQLiteStudio example and update Uno builder Introduces a new example project, ReactiveUI.Uno.SQLiteStudio, demonstrating usage of ReactiveUI with Uno Platform and SQLite. Refactors UnoReactiveUIBuilderExtensions to require the startup window for WithUno, updates scheduler registration, and improves Uno dictionary resource injection. Updates codecov config to ignore examples, adjusts target framework order for Uno cross-platform builds, and includes the new example in the solution. * Update solution and workflows to use .slnx format Changed CI and release workflows to reference the .slnx solution file instead of .sln. Updated ReactiveUI.Uno.slnx to include workflow files and set deploy configuration for the SQLiteStudio example project. Fixed test to pass required parameters to WithUno. * Improve XML docs and remove unused attributes Enhanced XML documentation for several classes and dependency properties to provide clearer descriptions and usage context. Removed unused or unnecessary RequiresUnreferencedCode attributes and redundant using directives to clean up the codebase. * Update Android build settings and add PublishTrimmed property Refines the condition for Android-specific settings to use TargetFramework.EndsWith('0-android'), adds PublishTrimmed property for Android builds, and sets PublishTrimmed to false for non-Android targets. This improves build configuration and output trimming behavior. * Comment out Android targets in Uno example build Android target frameworks are now commented out in Directory.Build.props, likely to avoid building Android targets by default. Also, a commented RuntimeIdentifierGraphPath line was removed from the SQLiteStudio example project for cleanup. * Update ReactiveUI.Uno.SQLiteStudio.csproj * Update target frameworks and remove obsolete test Removed 'net9.0-browserwasm' from UnoExampleCrossPlatformNet9 in Directory.Build.props to update supported target frameworks. Also removed the WithDefaultIScreen_HasRequiresUnreferencedCodeAttribute test from UnoReactiveUIBuilderExtensionsTests.cs, likely due to changes in method attributes or relevance. * Update PublishTrimmed property for iOS and other targets Added a separate PropertyGroup to enable PublishTrimmed for iOS targets and updated the condition for disabling PublishTrimmed to exclude both Android and iOS. This ensures correct trimming behavior for different platforms. * Update workflow configs for ReactiveUI.Uno Removed the solutionFile parameter from ci-build.yml and updated productNamespacePrefix in release.yml to 'ReactiveUI.Uno'. This aligns workflow configuration with the current project structure. * Add XML documentation to ScheduleSlow method Added detailed XML comments to the ScheduleSlow<TState> method in UnoDispatcherScheduler.cs to improve code clarity and provide usage information for developers. * Comment out Apple Mobile TFMs addition in build props The line adding Apple Mobile Target Frameworks for macOS and Windows builds has been commented out in Directory.Build.props. This may be to temporarily disable building for Apple mobile platforms during certain builds. * Add comprehensive unit tests for Uno controls and internals Introduces new test classes for ReactivePage, ReactiveUserControl, RoutedViewHost, ViewModelViewHost, and internal helpers, significantly expanding test coverage for property, activation, and routing behaviors. Also enhances existing tests with additional assertions and affinity checks for DependencyObjectObservableForProperty and ActivationForViewFetcher.
1 parent 255a4f3 commit 194b4f4

File tree

8 files changed

+1515
-1
lines changed

8 files changed

+1515
-1
lines changed

src/tests/ReactiveUI.Uno.Tests/Activation/ActivationForViewFetcherTests.cs

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
// The reactiveui and contributors 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 System.Reactive.Linq;
67
using Microsoft.UI.Xaml;
8+
using Microsoft.UI.Xaml.Controls;
79
using TUnit.Assertions.Extensions;
810
using TUnit.Core;
911

@@ -82,10 +84,95 @@ public async Task GetActivationForView_ReturnsEmptyObservable_ForNonFrameworkEle
8284
await Assert.That(observable).IsAssignableTo<IObservable<bool>>();
8385
}
8486

87+
/// <summary>
88+
/// Validates that ActivationForViewFetcher implements IActivationForViewFetcher interface.
89+
/// </summary>
90+
[Test]
91+
public async Task ActivationForViewFetcher_ImplementsIActivationForViewFetcher()
92+
{
93+
await Assert.That(_sut).IsAssignableTo<IActivationForViewFetcher>();
94+
}
95+
96+
/// <summary>
97+
/// Validates that GetAffinityForView returns high affinity for UserControl types.
98+
/// </summary>
99+
[Test]
100+
public async Task GetAffinityForView_ReturnsHighAffinity_ForUserControlTypes()
101+
{
102+
var affinity = _sut.GetAffinityForView(typeof(UserControl));
103+
await Assert.That(affinity).IsEqualTo(10);
104+
}
105+
106+
/// <summary>
107+
/// Validates that GetAffinityForView returns high affinity for Page types.
108+
/// </summary>
109+
[Test]
110+
public async Task GetAffinityForView_ReturnsHighAffinity_ForPageTypes()
111+
{
112+
var affinity = _sut.GetAffinityForView(typeof(Page));
113+
await Assert.That(affinity).IsEqualTo(10);
114+
}
115+
116+
/// <summary>
117+
/// Validates that GetAffinityForView returns high affinity for ContentControl types.
118+
/// </summary>
119+
[Test]
120+
public async Task GetAffinityForView_ReturnsHighAffinity_ForContentControlTypes()
121+
{
122+
var affinity = _sut.GetAffinityForView(typeof(ContentControl));
123+
await Assert.That(affinity).IsEqualTo(10);
124+
}
125+
126+
/// <summary>
127+
/// Validates that GetAffinityForView returns zero affinity for interface types.
128+
/// </summary>
129+
[Test]
130+
public async Task GetAffinityForView_ReturnsZeroAffinity_ForInterfaceTypes()
131+
{
132+
var affinity = _sut.GetAffinityForView(typeof(IActivatableView));
133+
await Assert.That(affinity).IsZero();
134+
}
135+
136+
/// <summary>
137+
/// Validates that GetAffinityForView returns zero affinity for abstract non-FrameworkElement types.
138+
/// </summary>
139+
[Test]
140+
public async Task GetAffinityForView_ReturnsZeroAffinity_ForAbstractNonFrameworkElementTypes()
141+
{
142+
var affinity = _sut.GetAffinityForView(typeof(System.IO.Stream));
143+
await Assert.That(affinity).IsZero();
144+
}
145+
146+
/// <summary>
147+
/// Validates that multiple fetchers can be created independently.
148+
/// </summary>
149+
[Test]
150+
public async Task MultipleFetchers_CanBeCreatedIndependently()
151+
{
152+
var fetcher1 = new ActivationForViewFetcher();
153+
var fetcher2 = new ActivationForViewFetcher();
154+
155+
await Assert.That(fetcher1).IsNotNull();
156+
await Assert.That(fetcher2).IsNotNull();
157+
await Assert.That(fetcher1).IsNotSameReferenceAs(fetcher2);
158+
}
159+
160+
/// <summary>
161+
/// Validates that GetAffinityForView returns consistent results for the same type.
162+
/// </summary>
163+
[Test]
164+
public async Task GetAffinityForView_ReturnsConsistentResults_ForSameType()
165+
{
166+
var affinity1 = _sut.GetAffinityForView(typeof(FrameworkElement));
167+
var affinity2 = _sut.GetAffinityForView(typeof(FrameworkElement));
168+
169+
await Assert.That(affinity1).IsEqualTo(affinity2);
170+
}
171+
85172
/// <summary>
86173
/// Simple mock implementation of IActivatableView for testing.
87174
/// </summary>
88-
private class MockActivatableView : IActivatableView
175+
private sealed class MockActivatableView : IActivatableView
89176
{
90177
public ViewModelActivator Activator { get; } = new();
91178
}

src/tests/ReactiveUI.Uno.Tests/Bootstrapping/AppBootstrapperTests.cs

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// The reactiveui and contributors 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 System.Reactive.Linq;
67
using TUnit.Assertions.Extensions;
78
using TUnit.Core;
89

@@ -71,4 +72,120 @@ public async Task Constructor_AllowsMultipleInstances()
7172
await Assert.That(bootstrapper1).IsNotSameReferenceAs(bootstrapper2);
7273
await Assert.That(bootstrapper1.Router).IsNotSameReferenceAs(bootstrapper2.Router);
7374
}
75+
76+
/// <summary>
77+
/// Validates that Router navigation stack is initially empty.
78+
/// </summary>
79+
[Test]
80+
public async Task Router_NavigationStack_IsInitiallyEmpty()
81+
{
82+
var bootstrapper = new AppBootstrapper();
83+
84+
await Assert.That(bootstrapper.Router.NavigationStack.Count).IsZero();
85+
}
86+
87+
/// <summary>
88+
/// Validates that Router can navigate to a view model.
89+
/// </summary>
90+
[Test]
91+
public async Task Router_CanNavigate_ToViewModel()
92+
{
93+
var bootstrapper = new AppBootstrapper();
94+
var viewModel = new TestRoutableViewModel(bootstrapper);
95+
96+
await bootstrapper.Router.Navigate.Execute(viewModel);
97+
98+
await Assert.That(bootstrapper.Router.NavigationStack.Count).IsEqualTo(1);
99+
}
100+
101+
/// <summary>
102+
/// Validates that Router CurrentViewModel updates after navigation.
103+
/// </summary>
104+
[Test]
105+
public async Task Router_CurrentViewModel_UpdatesAfterNavigation()
106+
{
107+
var bootstrapper = new AppBootstrapper();
108+
var viewModel = new TestRoutableViewModel(bootstrapper);
109+
110+
await bootstrapper.Router.Navigate.Execute(viewModel);
111+
112+
var currentVm = await bootstrapper.Router.CurrentViewModel.FirstAsync();
113+
await Assert.That(currentVm).IsEqualTo(viewModel);
114+
}
115+
116+
/// <summary>
117+
/// Validates that Router can navigate back.
118+
/// </summary>
119+
[Test]
120+
public async Task Router_CanNavigate_Back()
121+
{
122+
var bootstrapper = new AppBootstrapper();
123+
var viewModel1 = new TestRoutableViewModel(bootstrapper);
124+
var viewModel2 = new TestRoutableViewModel(bootstrapper);
125+
126+
await bootstrapper.Router.Navigate.Execute(viewModel1);
127+
await bootstrapper.Router.Navigate.Execute(viewModel2);
128+
await Assert.That(bootstrapper.Router.NavigationStack.Count).IsEqualTo(2);
129+
130+
await bootstrapper.Router.NavigateBack.Execute();
131+
132+
await Assert.That(bootstrapper.Router.NavigationStack.Count).IsEqualTo(1);
133+
}
134+
135+
/// <summary>
136+
/// Validates that IScreen.Router returns the same RoutingState instance.
137+
/// </summary>
138+
[Test]
139+
public async Task IScreen_Router_ReturnsSameInstance()
140+
{
141+
var bootstrapper = new AppBootstrapper();
142+
var screenRouter = ((IScreen)bootstrapper).Router;
143+
144+
await Assert.That(screenRouter).IsEqualTo(bootstrapper.Router);
145+
}
146+
147+
/// <summary>
148+
/// Validates that AppBootstrapper implements IReactiveObject.
149+
/// </summary>
150+
[Test]
151+
public async Task AppBootstrapper_ImplementsIReactiveObject()
152+
{
153+
var bootstrapper = new AppBootstrapper();
154+
await Assert.That(bootstrapper).IsAssignableTo<IReactiveObject>();
155+
}
156+
157+
/// <summary>
158+
/// Validates that Router NavigateAndReset clears the navigation stack.
159+
/// </summary>
160+
[Test]
161+
public async Task Router_NavigateAndReset_ClearsNavigationStack()
162+
{
163+
var bootstrapper = new AppBootstrapper();
164+
var viewModel1 = new TestRoutableViewModel(bootstrapper);
165+
var viewModel2 = new TestRoutableViewModel(bootstrapper);
166+
var viewModel3 = new TestRoutableViewModel(bootstrapper);
167+
168+
await bootstrapper.Router.Navigate.Execute(viewModel1);
169+
await bootstrapper.Router.Navigate.Execute(viewModel2);
170+
await Assert.That(bootstrapper.Router.NavigationStack.Count).IsEqualTo(2);
171+
172+
await bootstrapper.Router.NavigateAndReset.Execute(viewModel3);
173+
174+
await Assert.That(bootstrapper.Router.NavigationStack.Count).IsEqualTo(1);
175+
}
176+
177+
/// <summary>
178+
/// Test routable view model for testing navigation.
179+
/// </summary>
180+
private sealed class TestRoutableViewModel : ReactiveObject, IRoutableViewModel
181+
{
182+
public TestRoutableViewModel(IScreen hostScreen)
183+
{
184+
HostScreen = hostScreen;
185+
}
186+
187+
public string UrlPathSegment => "test";
188+
189+
public IScreen HostScreen { get; }
190+
}
74191
}

0 commit comments

Comments
 (0)