diff --git a/CLAUDE.md b/CLAUDE.md
index e507bc1cb1..e5eb81f270 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -74,55 +74,6 @@ Building the full solution requires **Windows** due to Windows-only target frame
---
-## Running Windows/WPF Tests on Linux via Wine
-
-The Windows-only test projects (`ReactiveUI.Wpf.Tests`, `ReactiveUI.WinForms.Tests`) can be **built and run on Linux** through Wine, giving a fast local feedback loop without a Windows VM or waiting on CI. This works because the Windows targeting/runtime packs restore cross-platform (`EnableWindowsTargeting=true`) and Wine can host the .NET Desktop runtime.
-
-> Treat results as a strong signal, not gospel: this is Wine, not Windows. **CI on `windows-latest` is authoritative.** A green run here is high-confidence; investigate a red one before assuming a product bug.
->
-> **Known Wine limitation — dispatcher thread-affinity:** Wine does **not** enforce WPF `Dispatcher` thread affinity the way Windows does, so `DispatcherObject.CheckAccess()` can return `true` on a non-dispatcher thread. Tests that marshal work from a background thread (e.g. `*FromBackgroundThread*`, anything asserting `Dispatcher.BeginInvoke`/scheduler hand-off after `DispatcherUtilities.DoEvents()`) may behave differently under Wine than on Windows — a Wine pass or fail for those is **not** conclusive. Verify background-thread/marshalling tests on CI. Wine is reliable for the large majority of WPF tests that run on a single (dispatcher) thread.
-
-### 1. One-time runtime + Wine prefix setup
-
-Assemble a Windows .NET Desktop runtime (base runtime gives `dotnet.exe` + host/fxr + `Microsoft.NETCore.App`; the desktop pack adds `Microsoft.WindowsDesktop.App` for WPF/WinForms). Match the version to the net8 windows TFM (bump as the SDK moves):
-
-```bash
-VER=8.0.27
-mkdir -p ~/wine-dotnet8 && cd ~/wine-dotnet8
-curl -fsSL -o /tmp/dnr.zip https://builds.dotnet.microsoft.com/dotnet/Runtime/$VER/dotnet-runtime-$VER-win-x64.zip
-curl -fsSL -o /tmp/wdr.zip https://builds.dotnet.microsoft.com/dotnet/WindowsDesktop/$VER/windowsdesktop-runtime-$VER-win-x64.zip
-unzip -oq /tmp/dnr.zip # dotnet.exe + host/ + shared/Microsoft.NETCore.App
-unzip -oq /tmp/wdr.zip # + shared/Microsoft.WindowsDesktop.App
-
-export WINEPREFIX=~/.wine-rxui WINEARCH=win64
-wineboot -i
-wine ~/wine-dotnet8/dotnet.exe --list-runtimes # must list NETCore.App AND WindowsDesktop.App
-```
-
-### 2. Build the Windows-TFM test assembly (on Linux)
-
-The UI test TFMs are gated to Windows in `Directory.Build.props` (`ReactiveUITestingUITargets`), so force a single Windows TFM with a global property. **Clear `obj`/`bin` first** — stale non-Windows assets break the WPF `_wpftmp` markup pass with `NETSDK1005`:
-
-```bash
-cd src
-rm -rf tests/ReactiveUI.Wpf.Tests/obj tests/ReactiveUI.Wpf.Tests/bin
-dotnet build tests/ReactiveUI.Wpf.Tests/ReactiveUI.Wpf.Tests.csproj -c Release \
- -p:ReactiveUITestingUITargets=net8.0-windows10.0.19041.0 -p:CheckEolTargetFramework=false
-```
-
-### 3. Run under Wine (MTP + TUnit `--treenode-filter`)
-
-```bash
-cd src/tests/ReactiveUI.Wpf.Tests/bin/Release/net8.0-windows10.0.19041.0
-export WINEPREFIX=~/.wine-rxui WINEARCH=win64 WINEDEBUG=-all
-wine ~/wine-dotnet8/dotnet.exe ReactiveUI.Wpf.Tests.dll \
- --treenode-filter "/*/*/*/ViewModelToViewBindingFromBackgroundThreadDoesNotTouchWpfControlDirectly"
-```
-
-`WINEDEBUG=-all` silences `fixme:`/`err:` chatter; pipe through `grep -viE 'fixme|^err:|wine:'` if needed. WinForms tests work identically (`ReactiveUI.WinForms.Tests`, same TFM override).
-
----
-
## Testing: Microsoft Testing Platform (MTP) + TUnit
This repo uses **Microsoft Testing Platform (MTP)** with **TUnit**. This differs from VSTest.
diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props
index 5fd5935ac9..63f2d952d1 100644
--- a/src/Directory.Packages.props
+++ b/src/Directory.Packages.props
@@ -7,10 +7,10 @@
20.2.0
7.1.0
- 1.61.38
+ 1.63.0
- 3.38.1
+ 3.40.1
2.11.0.1
@@ -77,7 +77,7 @@
-
+
@@ -88,7 +88,7 @@
-
+
diff --git a/src/tests/ReactiveUI.AOTTests/AssemblyHooks.cs b/src/tests/ReactiveUI.AOTTests/AssemblyHooks.cs
index 289a39b2b7..92b3f4a572 100644
--- a/src/tests/ReactiveUI.AOTTests/AssemblyHooks.cs
+++ b/src/tests/ReactiveUI.AOTTests/AssemblyHooks.cs
@@ -16,12 +16,12 @@ namespace ReactiveUI.AOT.Tests;
public static class AssemblyHooks
{
/// Called before any tests in this assembly start.
+ ///
+ /// Overrides so the framework detects a unit test runner.
+ /// App builder initialization is handled per-test via AppBuilderTestExecutor.
+ ///
[Before(Assembly)]
- public static void AssemblySetup() =>
-
- // Override ModeDetector to ensure we're detected as being in a unit test runner.
- // App builder initialization is handled per-test via AppBuilderTestExecutor.
- ModeDetector.OverrideModeDetector(new TestModeDetector());
+ public static void AssemblySetup() => ModeDetector.OverrideModeDetector(new TestModeDetector());
/// Mode detector that always indicates we're in a unit test runner.
private sealed class TestModeDetector : IModeDetector
diff --git a/src/tests/ReactiveUI.Builder.Tests/AssemblyHooks.cs b/src/tests/ReactiveUI.Builder.Tests/AssemblyHooks.cs
index bd7bdfec04..10569f709c 100644
--- a/src/tests/ReactiveUI.Builder.Tests/AssemblyHooks.cs
+++ b/src/tests/ReactiveUI.Builder.Tests/AssemblyHooks.cs
@@ -11,12 +11,12 @@ namespace ReactiveUI.Builder.Tests;
public static class AssemblyHooks
{
/// Called before any tests in this assembly start.
+ ///
+ /// Overrides so the framework detects a unit test runner.
+ /// App builder initialization is handled per-test via AppBuilderTestExecutor.
+ ///
[Before(Assembly)]
- public static void AssemblySetup() =>
-
- // Override ModeDetector to ensure we're detected as being in a unit test runner.
- // App builder initialization is handled per-test via AppBuilderTestExecutor.
- ModeDetector.OverrideModeDetector(new TestModeDetector());
+ public static void AssemblySetup() => ModeDetector.OverrideModeDetector(new TestModeDetector());
/// Mode detector that always indicates we're in a unit test runner.
private sealed class TestModeDetector : IModeDetector
diff --git a/src/tests/ReactiveUI.Maui.Tests/AssemblyHooks.cs b/src/tests/ReactiveUI.Maui.Tests/AssemblyHooks.cs
index 4fa840f495..cfded88c51 100644
--- a/src/tests/ReactiveUI.Maui.Tests/AssemblyHooks.cs
+++ b/src/tests/ReactiveUI.Maui.Tests/AssemblyHooks.cs
@@ -12,11 +12,9 @@ namespace ReactiveUI.Maui.Tests;
public static class AssemblyHooks
{
/// Called before any tests in this assembly start.
+ /// Overrides so the framework detects a unit test runner.
[Before(Assembly)]
- public static void AssemblySetup() =>
-
- // Override ModeDetector to ensure we're detected as being in a unit test runner
- ModeDetector.OverrideModeDetector(new TestModeDetector());
+ public static void AssemblySetup() => ModeDetector.OverrideModeDetector(new TestModeDetector());
/// Mode detector that always indicates we're in a unit test runner.
private sealed class TestModeDetector : IModeDetector
diff --git a/src/tests/ReactiveUI.Routing.Tests/AssemblyHooks.cs b/src/tests/ReactiveUI.Routing.Tests/AssemblyHooks.cs
index d0e8e6c6fc..d0b5eb383a 100644
--- a/src/tests/ReactiveUI.Routing.Tests/AssemblyHooks.cs
+++ b/src/tests/ReactiveUI.Routing.Tests/AssemblyHooks.cs
@@ -16,14 +16,14 @@ namespace ReactiveUI.Tests;
public static class AssemblyHooks
{
/// Called before any tests in this assembly start.
+ ///
+ /// Detects as a unit-test runner so platform registrations skip live scheduler wiring.
+ /// Per-test ReactiveUI builder initialization is handled by AppBuilderTestExecutor, which
+ /// the routing leaf otherwise lacked - without it the first WhenAny* call hits the
+ /// ReactiveNotifyPropertyChangedMixins static ctor and EnsureInitialized() throws.
+ ///
[Before(Assembly)]
- public static void AssemblySetup() =>
-
- // Detect as a unit-test runner so platform registrations skip live scheduler wiring.
- // Per-test ReactiveUI builder initialization is handled by AppBuilderTestExecutor, which
- // the routing leaf otherwise lacked — without it the first WhenAny* call hits the
- // ReactiveNotifyPropertyChangedMixins static ctor and EnsureInitialized() throws.
- ModeDetector.OverrideModeDetector(new TestModeDetector());
+ public static void AssemblySetup() => ModeDetector.OverrideModeDetector(new TestModeDetector());
/// Mode detector that always indicates we're in a unit test runner.
private sealed class TestModeDetector : IModeDetector
diff --git a/src/tests/ReactiveUI.Splat.Tests/AssemblyHooks.cs b/src/tests/ReactiveUI.Splat.Tests/AssemblyHooks.cs
index 8552e3d7b4..ceadedce65 100644
--- a/src/tests/ReactiveUI.Splat.Tests/AssemblyHooks.cs
+++ b/src/tests/ReactiveUI.Splat.Tests/AssemblyHooks.cs
@@ -12,11 +12,9 @@ namespace ReactiveUI.Splat.Tests;
public static class AssemblyHooks
{
/// Called before any tests in this assembly start.
+ /// Overrides so the framework detects a unit test runner.
[Before(Assembly)]
- public static void AssemblySetup() =>
-
- // Override ModeDetector to ensure we're detected as being in a unit test runner
- ModeDetector.OverrideModeDetector(new TestModeDetector());
+ public static void AssemblySetup() => ModeDetector.OverrideModeDetector(new TestModeDetector());
/// Mode detector that always indicates we're in a unit test runner.
private sealed class TestModeDetector : IModeDetector
diff --git a/src/tests/ReactiveUI.TestGuiMocks/CommonGuiMocks/Mocks/RaceConditionFixture.cs b/src/tests/ReactiveUI.TestGuiMocks/CommonGuiMocks/Mocks/RaceConditionFixture.cs
index 3d287336db..c5e5625989 100644
--- a/src/tests/ReactiveUI.TestGuiMocks/CommonGuiMocks/Mocks/RaceConditionFixture.cs
+++ b/src/tests/ReactiveUI.TestGuiMocks/CommonGuiMocks/Mocks/RaceConditionFixture.cs
@@ -13,18 +13,16 @@ public class RaceConditionFixture : ReactiveObject
private readonly ObservableAsPropertyHelper _a;
/// Initializes a new instance of the class.
+ ///
+ /// Emits a value on subscription that differs from the default, which triggers the property
+ /// change firing during the constructor - the race
+ /// this fixture exists to reproduce.
+ ///
[System.Diagnostics.CodeAnalysis.SuppressMessage(
"Design",
"SST2403:'this' escapes before construction finishes",
Justification = "canonical ObservableAsPropertyHelper initialization requires 'this' in the constructor; the single-threaded fixture never exposes the half-built instance.")]
- public RaceConditionFixture() =>
-
- // We need to generate a value on subscription
- // which is different than the default value.
- // This triggers the property change firing
- // upon subscription in the ObservableAsPropertyHelper
- // constructor.
- Signal.Emit(true).Do(_ => Count++).ToProperty(this, x => x.A, out _a);
+ public RaceConditionFixture() => Signal.Emit(true).Do(_ => Count++).ToProperty(this, x => x.A, out _a);
/// Gets or sets the count.
public int Count { get; set; }
diff --git a/src/tests/ReactiveUI.TestGuiMocks/CommonGuiMocks/Mocks/RaceConditionNameOfFixture.cs b/src/tests/ReactiveUI.TestGuiMocks/CommonGuiMocks/Mocks/RaceConditionNameOfFixture.cs
index 8401e05faa..e034f9a8b9 100644
--- a/src/tests/ReactiveUI.TestGuiMocks/CommonGuiMocks/Mocks/RaceConditionNameOfFixture.cs
+++ b/src/tests/ReactiveUI.TestGuiMocks/CommonGuiMocks/Mocks/RaceConditionNameOfFixture.cs
@@ -12,17 +12,16 @@ public class RaceConditionNameOfFixture : ReactiveObject
private readonly ObservableAsPropertyHelper _a;
/// Initializes a new instance of the class.
+ ///
+ /// Emits a value on subscription that differs from the default, which triggers the property
+ /// change firing during the constructor - the race
+ /// this fixture exists to reproduce.
+ ///
[System.Diagnostics.CodeAnalysis.SuppressMessage(
"Design",
"SST2403:'this' escapes before construction finishes",
Justification = "canonical ObservableAsPropertyHelper initialization requires 'this' in the constructor; the single-threaded fixture never exposes the half-built instance.")]
public RaceConditionNameOfFixture() =>
-
- // We need to generate a value on subscription
- // which is different than the default value.
- // This triggers the property change firing
- // upon subscription in the ObservableAsPropertyHelper
- // constructor.
Signal.Emit(true)
.Do(_ => Count++)
.ToProperty(this, nameof(A), out _a);
diff --git a/src/tests/ReactiveUI.Testing.Tests/AppBuilderTestBaseTests.cs b/src/tests/ReactiveUI.Testing.Tests/AppBuilderTestBaseTests.cs
index d498efcd90..4c594589f4 100644
--- a/src/tests/ReactiveUI.Testing.Tests/AppBuilderTestBaseTests.cs
+++ b/src/tests/ReactiveUI.Testing.Tests/AppBuilderTestBaseTests.cs
@@ -51,8 +51,6 @@ public async Task RunAppBuilderTestAsync_WithSyncTestBody_ExecutesTest()
/// A representing the asynchronous unit test.
[Test]
public async Task RunAppBuilderTestAsync_WithAsyncTestBody_PropagatesExceptions() =>
-
- // Act & Assert
await Assert.That(static async () =>
{
await TestHelper.RunAppBuilderTestAsync(static async () =>
@@ -66,8 +64,6 @@ await TestHelper.RunAppBuilderTestAsync(static async () =>
/// A representing the asynchronous unit test.
[Test]
public async Task RunAppBuilderTestAsync_WithSyncTestBody_PropagatesExceptions() =>
-
- // Act & Assert
await Assert.That(static async () => await TestHelper.RunAppBuilderTestAsync(static () => throw new InvalidOperationException("Test exception"))).Throws();
/// Verifies that can be called multiple times sequentially without interference.
diff --git a/src/tests/ReactiveUI.Testing.Tests/AssemblyHooks.cs b/src/tests/ReactiveUI.Testing.Tests/AssemblyHooks.cs
index f8bdbb7bc4..f46507eccb 100644
--- a/src/tests/ReactiveUI.Testing.Tests/AssemblyHooks.cs
+++ b/src/tests/ReactiveUI.Testing.Tests/AssemblyHooks.cs
@@ -17,12 +17,12 @@ namespace ReactiveUI.Testing.Tests;
public static class AssemblyHooks
{
/// Called before any tests in this assembly start.
+ ///
+ /// Overrides so the framework detects a unit test runner.
+ /// App builder initialization is handled per-test via test executors.
+ ///
[Before(Assembly)]
- public static void AssemblySetup() =>
-
- // Override ModeDetector to ensure we're detected as being in a unit test runner.
- // App builder initialization is handled per-test via test executors.
- ModeDetector.OverrideModeDetector(new TestModeDetector());
+ public static void AssemblySetup() => ModeDetector.OverrideModeDetector(new TestModeDetector());
/// Mode detector that always indicates we're in a unit test runner.
private sealed class TestModeDetector : IModeDetector
diff --git a/src/tests/ReactiveUI.Testing.Tests/RxTestTests.cs b/src/tests/ReactiveUI.Testing.Tests/RxTestTests.cs
index 637eb7b432..e0bbce38be 100644
--- a/src/tests/ReactiveUI.Testing.Tests/RxTestTests.cs
+++ b/src/tests/ReactiveUI.Testing.Tests/RxTestTests.cs
@@ -56,8 +56,6 @@ await Assert.That(static () => RxTest.AppBuilderTestAsync(null!))
/// Thrown if the delegate passed to AppBuilderTestAsync throws an InvalidOperationException.
[Test]
public async Task AppBuilderTestAsync_PropagatesExceptions() =>
-
- // Act & Assert
await Assert.That(static async () => await RxTest.AppBuilderTestAsync(static () => throw new InvalidOperationException("Test exception"))).Throws();
///
@@ -161,8 +159,6 @@ await RxTest.AppBuilderTestAsync(
/// A task that represents the asynchronous test operation.
[Test]
public async Task AppBuilderTestAsync_ThrowsTimeoutException_WhenTestExceedsTimeout() =>
-
- // Act & Assert
await Assert.That(static async () =>
{
await RxTest.AppBuilderTestAsync(
@@ -180,8 +176,5 @@ static async () =>
/// A task that represents the asynchronous test operation.
[Test]
public Task AppBuilderTestAsync_HandlesTaskCompletedTask() =>
-
- // This test verifies that returning Task.CompletedTask works correctly
- // Act - Should not throw
RxTest.AppBuilderTestAsync(static () => Task.CompletedTask);
}
diff --git a/src/tests/ReactiveUI.Tests/Bindings/Converters/ConverterMigrationHelperTests.cs b/src/tests/ReactiveUI.Tests/Bindings/Converters/ConverterMigrationHelperTests.cs
index bf20d57a00..55ecbf0a37 100644
--- a/src/tests/ReactiveUI.Tests/Bindings/Converters/ConverterMigrationHelperTests.cs
+++ b/src/tests/ReactiveUI.Tests/Bindings/Converters/ConverterMigrationHelperTests.cs
@@ -18,8 +18,6 @@ public class ConverterMigrationHelperTests
/// A representing the asynchronous unit test.
[Test]
public async Task ExtractConverters_ShouldThrowArgumentNullException_WhenResolverIsNull() =>
-
- // Act & Assert
await Assert.That(static () => ConverterMigrationHelperMixins.ExtractConverters(null!))
.Throws();
diff --git a/src/tests/ReactiveUI.Wpf.Tests/AssemblyHooks.cs b/src/tests/ReactiveUI.Wpf.Tests/AssemblyHooks.cs
index 80efcb2f44..6c673d901e 100644
--- a/src/tests/ReactiveUI.Wpf.Tests/AssemblyHooks.cs
+++ b/src/tests/ReactiveUI.Wpf.Tests/AssemblyHooks.cs
@@ -15,11 +15,9 @@ namespace ReactiveUI.Tests;
public static class AssemblyHooks
{
/// Called before any tests in this assembly start.
+ /// Overrides so the framework detects a unit test runner.
[Before(Assembly)]
- public static void AssemblySetup() =>
-
- // Override ModeDetector to ensure we're detected as being in a unit test runner
- ModeDetector.OverrideModeDetector(new TestModeDetector());
+ public static void AssemblySetup() => ModeDetector.OverrideModeDetector(new TestModeDetector());
/// Mode detector that always indicates we're in a unit test runner.
private sealed class TestModeDetector : IModeDetector