Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions global.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"msbuild-sdks": {
"Uno.Sdk": "6.2.29"
}
}
31 changes: 31 additions & 0 deletions src/Directory.Packages.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<Project ToolsVersion="15.0">
<!--
To update the version of Uno, you should instead update the Sdk version in the global.json file.

See https://aka.platform.uno/using-uno-sdk for more information.
See https://aka.platform.uno/using-uno-sdk#implicit-packages for more information regarding the Implicit Packages.
-->
<ItemGroup>
<PackageVersion Include="ReactiveUI.Testing" Version="21.0.1" />
<PackageVersion Include="Splat" Version="16.1.1" />
<PackageVersion Include="Moq" Version="4.20.70" />
<PackageVersion Include="ReactiveUI" Version="21.0.1" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageVersion Include="xunit" Version="2.9.3" />
<PackageVersion Include="xunit.runner.console" Version="2.9.3" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.4" />
<PackageVersion Include="Xunit.StaFact" Version="1.1.11" />
<PackageVersion Include="FluentAssertions" Version="8.6.0" />
<PackageVersion Include="Microsoft.Reactive.Testing" Version="6.0.2" />
<PackageVersion Include="PublicApiGenerator" Version="11.4.6" />
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
<PackageVersion Include="Nerdbank.GitVersioning" Version="3.7.115" />
<PackageVersion Include="stylecop.analyzers" Version="1.2.0-beta.556" />
<PackageVersion Include="Roslynator.Analyzers" Version="4.14.0" />
<PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4948" />
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.7.250606001" />
<PackageVersion Include="Newtonsoft.Json" VersionOverride="13.0.3" Version="13.0.3" />
<PackageVersion Include="NUnit" VersionOverride="4.1.0" Version="4.4.0" />
<PackageVersion Include="NUnit3TestAdapter" VersionOverride="4.5.0" Version="5.1.0" />
</ItemGroup>
</Project>
32 changes: 14 additions & 18 deletions src/Directory.build.props
Original file line number Diff line number Diff line change
Expand Up @@ -34,35 +34,31 @@
<IncludePackageReferencesDuringMarkupCompilation>true</IncludePackageReferencesDuringMarkupCompilation>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<PackageTags>mvvm;reactiveui;rx;reactive extensions;observable;LINQ;events;frp;net;unoplatform</PackageTags>
<UnoTargetFrameworks>net8.0;net8.0-android;net8.0-ios;net8.0-maccatalyst;net8.0-macos;net9.0;net9.0-android;net9.0-ios;net9.0-maccatalyst;net9.0-macos</UnoTargetFrameworks>
<UnoTargetFrameworks>net9.0;net9.0-ios;net9.0-android;net9.0-browserwasm;net9.0-desktop;net9.0-windows10.0.19041.0</UnoTargetFrameworks>

<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">14.2</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">14.0</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">30.0</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.19041.0</SupportedOSPlatformVersion>
<TargetPlatformMinVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.19041.0</TargetPlatformMinVersion>

<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>

<!--
Adding NoWarn to remove build warnings 26100
NU1507: Warning when there are multiple package sources when using CPM with no source mapping
NETSDK1201: Warning that specifying RID won't create self containing app
PRI257: Ignore default language (en) not being one of the included resources (eg en-us, en-uk)
-->
<NoWarn>$(NoWarn);NU1507;NETSDK1201;PRI257</NoWarn>
</PropertyGroup>

<PropertyGroup Condition="'$(GITHUB_ACTIONS)' == 'true'">
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup>

<!--<ItemGroup Condition="$(IsTestProject)">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageReference Include="xunit" Version="2.9.0" />
<PackageReference Include="xunit.runner.console" Version="2.9.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
<PackageReference Include="Xunit.StaFact" Version="1.1.11" />
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="Microsoft.Reactive.Testing" Version="6.0.1" />
<PackageReference Include="PublicApiGenerator" Version="11.1.0" />
<PackageReference Include="coverlet.msbuild" Version="6.0.2" PrivateAssets="All" />
<PackageReference Include="Verify.Xunit" Version="26.4.5" />
</ItemGroup>-->

<ItemGroup Condition="'$(IsTestProject)' != 'true'">
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" />
</ItemGroup>

<PropertyGroup>
Expand All @@ -76,9 +72,9 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Nerdbank.GitVersioning" Version="3.7.115" PrivateAssets="all" />
<PackageReference Include="stylecop.analyzers" Version="1.2.0-beta.556" PrivateAssets="all" />
<PackageReference Include="Roslynator.Analyzers" Version="4.14.0" PrivateAssets="All" />
<PackageReference Include="Nerdbank.GitVersioning" PrivateAssets="all" />
<PackageReference Include="stylecop.analyzers" PrivateAssets="all" />
<PackageReference Include="Roslynator.Analyzers" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="$(MSBuildThisFileDirectory)stylecop.json" Link="stylecop.json" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// Copyright (c) 2025 ReactiveUI and Contributors. All rights reserved.
// Licensed to reactiveui and contributors under one or more agreements.
// The reactiveui and contributors licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.

using FluentAssertions;
using Microsoft.UI.Xaml;
using NUnit.Framework;

namespace ReactiveUI.Uno.Tests.Converters;

/// <summary>
/// BooleanToVisibilityTypeConverterTests.
/// </summary>
[TestFixture]
public class BooleanToVisibilityTypeConverterTests
{
private readonly BooleanToVisibilityTypeConverter _sut = new();

/// <summary>
/// Affinities the is high for bool to visibility.
/// </summary>
[Test]
public void Affinity_is_high_for_bool_to_visibility()
{
_sut.GetAffinityForObjects(typeof(bool), typeof(Visibility)).Should().Be(10);
_sut.GetAffinityForObjects(typeof(Visibility), typeof(bool)).Should().Be(10);
}

/// <summary>
/// Convertses the bool to visibility.
/// </summary>
/// <param name="input">if set to <c>true</c> [input].</param>
/// <param name="expected">The expected.</param>
[TestCase(true, Visibility.Visible)]
[TestCase(false, Visibility.Collapsed)]
public void Converts_bool_to_visibility(bool input, Visibility expected)
{
_sut.TryConvert(input, typeof(Visibility), BooleanToVisibilityHint.None, out var result).Should().BeTrue();
result.Should().Be(expected);
}

/// <summary>
/// Convertses the bool to visibility inverse.
/// </summary>
/// <param name="input">if set to <c>true</c> [input].</param>
/// <param name="expected">The expected.</param>
[TestCase(true, Visibility.Collapsed)]
[TestCase(false, Visibility.Visible)]
public void Converts_bool_to_visibility_inverse(bool input, Visibility expected)
{
_sut.TryConvert(input, typeof(Visibility), BooleanToVisibilityHint.Inverse, out var result).Should().BeTrue();
result.Should().Be(expected);
}

/// <summary>
/// Convertses the visibility to bool.
/// </summary>
/// <param name="input">The input.</param>
/// <param name="expected">if set to <c>true</c> [expected].</param>
[TestCase(Visibility.Visible, true)]
[TestCase(Visibility.Collapsed, false)]
public void Converts_visibility_to_bool(Visibility input, bool expected)
{
_sut.TryConvert(input, typeof(bool), BooleanToVisibilityHint.None, out var result).Should().BeTrue();
result.Should().Be(expected);
}

/// <summary>
/// Convertses the visibility to bool inverse.
/// </summary>
/// <param name="input">The input.</param>
/// <param name="expected">if set to <c>true</c> [expected].</param>
[TestCase(Visibility.Visible, false)]
[TestCase(Visibility.Collapsed, true)]
public void Converts_visibility_to_bool_inverse(Visibility input, bool expected)
{
_sut.TryConvert(input, typeof(bool), BooleanToVisibilityHint.Inverse, out var result).Should().BeTrue();
result.Should().Be(expected);
}
}
74 changes: 74 additions & 0 deletions src/ReactiveUI.Uno.Tests/Hooks/AutoDataTemplateBindingHookTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright (c) 2025 ReactiveUI and Contributors. All rights reserved.
// Licensed to reactiveui and contributors under one or more agreements.
// The reactiveui and contributors licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.

using System.Linq.Expressions;
using FluentAssertions;
using Microsoft.UI.Xaml.Controls;
using NUnit.Framework;

namespace ReactiveUI.Uno.Tests;

/// <summary>
/// AutoDataTemplateBindingHookTests.
/// </summary>
[TestFixture]
public class AutoDataTemplateBindingHookTests
{
/// <summary>
/// Executes the hook sets default template when eligible.
/// </summary>
[Test]
public void ExecuteHook_Sets_DefaultTemplate_When_Eligible()
{
var hook = new AutoDataTemplateBindingHook();
ItemsControl ic;
try
{
ic = new ItemsControl();
}
catch (System.Exception ex) when (ex is System.TypeInitializationException || ex is System.NotSupportedException)
{
Assert.Ignore("UI dispatcher not available for Uno/WinUI controls in this environment.");
return;
}

var vmChanges = Array.Empty<IObservedChange<object, object>>();
var expr = (Expression<System.Func<object?>>)(() => ic.ItemsSource);
var viewChanges = new[]
{
new ObservedChange<object, object>(ic, expr, new object())
};

var result = hook.ExecuteHook(null, ic, () => vmChanges, () => viewChanges, BindingDirection.OneWay);
result.Should().BeTrue();
ic.ItemTemplate.Should().NotBeNull();
}

/// <summary>
/// Executes the hook does not override existing template.
/// </summary>
[Test]
public void ExecuteHook_Does_Not_Override_Existing_Template()
{
var hook = new AutoDataTemplateBindingHook();
ItemsControl ic;
try
{
ic = new ItemsControl { ItemTemplate = AutoDataTemplateBindingHook.DefaultItemTemplate.Value };
}
catch (System.Exception ex) when (ex is System.TypeInitializationException || ex is System.NotSupportedException)
{
Assert.Ignore("UI dispatcher not available for Uno/WinUI controls in this environment.");
return;
}

var expr = (Expression<System.Func<object?>>)(() => ic.ItemsSource);
var viewChanges = new[] { new ObservedChange<object, object>(ic, expr, new object()) };

var result = hook.ExecuteHook(null, ic, Array.Empty<IObservedChange<object, object>>, () => viewChanges, BindingDirection.OneWay);
result.Should().BeTrue();
ic.ItemTemplate.Should().NotBeNull();
}
}
29 changes: 29 additions & 0 deletions src/ReactiveUI.Uno.Tests/Platform/PlatformOperationsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright (c) 2025 ReactiveUI and Contributors. All rights reserved.
// Licensed to reactiveui and contributors under one or more agreements.
// The reactiveui and contributors licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.

using FluentAssertions;
using NUnit.Framework;

namespace ReactiveUI.Uno.Tests.Platform;

/// <summary>
/// Tests for PlatformOperations.
/// </summary>
[TestFixture]
public class PlatformOperationsTests
{
/// <summary>
/// Ensures GetOrientation returns either null or a string value without throwing.
/// </summary>
[Test]
public void GetOrientation_has_valid_string_or_null()
{
var ops = new ReactiveUI.Uno.PlatformOperations();
var orientation = ops.GetOrientation();

// Accept null or any string value
(orientation == null || orientation.GetType() == typeof(string)).Should().BeTrue();
}
}
22 changes: 22 additions & 0 deletions src/ReactiveUI.Uno.Tests/ReactiveUI.Uno.Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<IsTestProject>true</IsTestProject>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="FluentAssertions" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="Newtonsoft.Json" />
<PackageReference Include="NUnit" />
<PackageReference Include="NUnit3TestAdapter" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\ReactiveUI.Uno\ReactiveUI.Uno.csproj" />
</ItemGroup>

</Project>
76 changes: 76 additions & 0 deletions src/ReactiveUI.Uno.Tests/RegistrationsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright (c) 2025 ReactiveUI and Contributors. All rights reserved.
// Licensed to reactiveui and contributors under one or more agreements.
// The reactiveui and contributors licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.

using System;
using System.Collections.Generic;
using System.Linq;
using FluentAssertions;
using NUnit.Framework;
using ReactiveUI;
using ReactiveUI.Uno;

namespace ReactiveUI.Uno.Tests;

/// <summary>
/// RegistrationsTests.
/// </summary>
[TestFixture]
public class RegistrationsTests
{
/// <summary>
/// Resets the schedulers.
/// </summary>
[SetUp]
public void ResetSchedulers()
{
// Reset to defaults before each test to avoid cross-test pollution.
RxApp.MainThreadScheduler = null!;
RxApp.TaskpoolScheduler = null!;
}

/// <summary>
/// Registers the registers expected services and configures schedulers.
/// </summary>
[Test]
public void Register_RegistersExpectedServices_And_ConfiguresSchedulers()
{
var registered = new List<Type>();

void Register(Func<object> factory, Type serviceType)
{
// Don't invoke the factory here to avoid loading UI types in headless tests.
registered.Add(serviceType);
}

var sut = new Registrations();
sut.Invoking(x => x.Register(Register)).Should().NotThrow();

// Verify the set of required service interfaces are registered
registered.Should().Contain(typeof(IPlatformOperations));
registered.Should().Contain(typeof(IActivationForViewFetcher));
registered.Should().Contain(typeof(ICreatesObservableForProperty));
registered.Should().Contain(typeof(IPropertyBindingHook));
registered.Should().Contain(typeof(ISuspensionDriver));

// The implementation registers 16 binding converters + 2 hooks/services + 3 core services = 21 total
// (String/byte(short/int/long/float/double/decimal) + nullable versions + BooleanToVisibility)
registered.Count.Should().Be(21);

// Verify schedulers are set to safe defaults for headless environment
RxApp.TaskpoolScheduler.Should().BeSameAs(System.Reactive.Concurrency.TaskPoolScheduler.Default);
RxApp.MainThreadScheduler.Should().BeSameAs(System.Reactive.Concurrency.CurrentThreadScheduler.Instance);
}

/// <summary>
/// Registers the throws on null register function.
/// </summary>
[Test]
public void Register_Throws_On_Null_RegisterFunction()
{
var sut = new Registrations();
Action act = () => sut.Register(null!);
act.Should().Throw<ArgumentNullException>();
}
}
Loading
Loading