diff --git a/src/Splat.Drawing/DefaultPlatformModeDetector.cs b/src/Splat.Drawing/DefaultPlatformModeDetector.cs
index c527963a6..024dcfe8f 100644
--- a/src/Splat.Drawing/DefaultPlatformModeDetector.cs
+++ b/src/Splat.Drawing/DefaultPlatformModeDetector.cs
@@ -2,45 +2,47 @@
// ReactiveUI Association Incorporated licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.
+#if !MONO
using System.Diagnostics.CodeAnalysis;
+#endif
using System.IO;
+#if NETFRAMEWORK
using System.Reflection;
+#endif
namespace Splat;
-///
-/// Provides a default implementation for detecting whether the application is running in design mode across supported
-/// platforms.
-///
-/// This class is typically used to determine if code is executing within a designer environment, such as
-/// Visual Studio or Blend, to enable or disable design-time specific logic. It supports multiple platforms and design
-/// environments, including WPF, Silverlight, and UWP, by checking for known design mode indicators. The detection
-/// result may be cached for performance. Thread safety is not guaranteed.
+/// Provides a default implementation for detecting whether the application is running in design mode.
+///
+/// This class is typically used to determine if code is executing within a designer environment, such as
+/// Visual Studio or Blend, to enable or disable design-time specific logic. The detection result is memoized, so a
+/// process that starts outside a designer never re-runs the probes. Thread safety is not guaranteed.
+/// Which probes are compiled depends on the target framework. Splat.Drawing sets UseWPF and
+/// UseWindowsForms for the .NET Framework and Windows-specific targets, so those builds call the WPF designer
+/// API directly instead of reflecting over it. The target-framework-neutral builds keep a reflective probe, because a
+/// Windows desktop application resolves to them when its own platform version predates the Windows-specific targets.
+/// The Android and Apple targets cannot host a XAML designer at all and compile no designer probe.
+///
public class DefaultPlatformModeDetector : IPlatformModeDetector
{
- /// Assembly-qualified name of the Silverlight/XAML DesignerProperties type probed for design mode.
- private const string XamlDesignPropertiesType = "System.ComponentModel.DesignerProperties, System.Windows, Version=2.0.5.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e";
-
- /// Assembly-qualified name of the XAML Border control used as the dependency object for the design-mode probe.
- private const string XamlControlBorderType = "System.Windows.Controls.Border, System.Windows, Version=2.0.5.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e";
-
- /// Name of the XAML DesignerProperties method that reports design mode.
- private const string XamlDesignPropertiesDesignModeMethodName = "GetIsInDesignMode";
-
+#if !NETFRAMEWORK && !WINDOWS && !MONO
/// Assembly-qualified name of the WPF DesignerProperties type probed for design mode.
private const string WpfDesignerPropertiesType = "System.ComponentModel.DesignerProperties, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35";
/// Name of the WPF DesignerProperties method that reports design mode.
private const string WpfDesignerPropertiesDesignModeMethod = "GetIsInDesignMode";
- /// Assembly-qualified name of the WPF DependencyObject type used as the dependency object for the design-mode probe.
- private const string WpfDependencyPropertyType = "System.Windows.DependencyObject, WindowsBase, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35";
+ /// Assembly-qualified name of the WPF element type the designer raises the design-mode default for.
+ private const string WpfFrameworkElementType = "System.Windows.FrameworkElement, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35";
+#endif
- /// Assembly-qualified name of the WinRT DesignMode type probed for design mode.
- private const string WinFormsDesignerPropertiesType = "Windows.ApplicationModel.DesignMode, Windows, ContentType=WindowsRuntime";
+#if !MONO
+ /// Assembly-qualified name of the Windows Runtime DesignMode type probed for design mode.
+ private const string WinRtDesignModeType = "Windows.ApplicationModel.DesignMode, Windows, ContentType=WindowsRuntime";
- /// Name of the WinRT DesignMode property that reports design mode.
- private const string WinFormsDesignerPropertiesDesignModeMethod = "DesignModeEnabled";
+ /// Name of the Windows Runtime DesignMode property that reports design mode.
+ private const string WinRtDesignModeEnabledProperty = "DesignModeEnabled";
+#endif
/// Executable names of known design-time host processes used as a fallback design-mode signal.
private static readonly string[] _designEnvironments = ["BLEND.EXE", "XDESPROC.EXE"];
@@ -48,24 +50,41 @@ public class DefaultPlatformModeDetector : IPlatformModeDetector
/// Memoizes the design-mode detection result; until first computed.
private static bool? _cachedInDesignModeResult;
+ /// Gets the path of the executable hosting the current process, or when it is unavailable.
+ /// This is the executable file, not the directory the assembly was loaded from. A design-time host is
+ /// recognised by its process name, and a directory path yields no executable name to compare.
+ internal static string? HostExecutablePath =>
+#if NETFRAMEWORK
+ Assembly.GetEntryAssembly()?.Location;
+#else
+ Environment.ProcessPath;
+#endif
+
///
- public bool? InDesignMode() => DetectDesignMode();
+ public bool? InDesignMode() => _cachedInDesignModeResult ??= DetectDesignMode(HostExecutablePath);
/// Determines whether the supplied host entry-point path names a known design-environment executable.
/// The host entry-point path, or when it is unavailable.
/// when the entry path names a known design environment; otherwise .
+ /// The executable name has to match a known design host exactly. A containment test reports a false
+ /// positive for every name that is a fragment of one, including the empty name a directory path yields.
internal static bool IsDesignEnvironmentEntry(string? entry)
{
- if (entry is null)
+ if (string.IsNullOrEmpty(entry))
{
return false;
}
- var exeName = new FileInfo(entry).Name;
+ var executableName = new FileInfo(entry).Name;
- foreach (var designEnv in _designEnvironments)
+ if (executableName.Length == 0)
{
- if (IsDesignEnvironment(designEnv, exeName))
+ return false;
+ }
+
+ foreach (var knownHost in _designEnvironments)
+ {
+ if (string.Equals(knownHost, executableName, StringComparison.OrdinalIgnoreCase))
{
return true;
}
@@ -74,118 +93,85 @@ internal static bool IsDesignEnvironmentEntry(string? entry)
return false;
}
- /// Runs the design-mode probes in priority order and returns (and caches) the result.
- /// A value indicating whether the application is running in design mode.
- private static bool? DetectDesignMode()
- {
- if (_cachedInDesignModeResult.HasValue)
- {
- return _cachedInDesignModeResult.Value;
- }
-
- // Probe each platform in priority order; the first detected platform wins,
- // mirroring the original else-if chain. The cached result is then reset to
- // false below, preserving the original behaviour.
- RunDesignModeProbes();
-
- _cachedInDesignModeResult = false;
+ /// Runs the design-mode probes and reports whether any of them detected a designer.
+ /// The path of the executable hosting the current process, or .
+ /// when a designer is hosting the application; otherwise .
+ internal static bool DetectDesignMode(string? hostExecutablePath) =>
+#if MONO
+ IsDesignEnvironmentEntry(hostExecutablePath);
+#else
+ ResolveDesignMode(ProbeWpfDesignMode(), ProbeWinRtDesignMode(), hostExecutablePath);
+#endif
- return _cachedInDesignModeResult;
- }
+#if !MONO
+ /// Combines the probe results into a single answer: any probe reporting a designer wins.
+ /// Whether the WPF probe reported a designer.
+ /// Whether the Windows Runtime probe reported a designer.
+ /// The path of the executable hosting the current process, or .
+ /// when a designer is hosting the application; otherwise .
+ internal static bool ResolveDesignMode(bool wpfDesignMode, bool winRtDesignMode, string? hostExecutablePath) =>
+ wpfDesignMode || winRtDesignMode || IsDesignEnvironmentEntry(hostExecutablePath);
+#endif
- /// Runs the platform design-mode probes in priority order.
- /// None of the Silverlight, WPF, or WinRT host types resolve on the supported .NET targets, so the
- /// short-circuiting branches that fire when one of those probes reports a platform are unreachable off their
- /// native hosts.
- [ExcludeFromCodeCoverage]
- private static void RunDesignModeProbes() =>
- _ = ProbeXamlDesignProperties()
- || ProbeWpfDesignerProperties()
- || ProbeWinRtDesignMode()
- || ProbeDesignEnvironmentExecutable();
-
- /// Probes for the Silverlight / Windows Phone 8 design-mode indicator.
- /// if the platform was detected; otherwise .
- [ExcludeFromCodeCoverage] // Off-platform reflection: the Silverlight/XAML type never resolves on supported targets; only the type-null guard runs.
- private static bool ProbeXamlDesignProperties()
+ /// Gets the memoized design-mode result. Used by test scopes.
+ /// The memoized result, or when the probes have not run yet.
+ internal static bool? GetState() => _cachedInDesignModeResult;
+
+ /// Restores the memoized design-mode result. Used by test scopes.
+ /// The memoized result to restore.
+ internal static void RestoreState(bool? state) => _cachedInDesignModeResult = state;
+
+ /// Discards the memoized design-mode result so the next query re-runs the probes. Used by test scopes.
+ internal static void ResetState() => _cachedInDesignModeResult = null;
+
+#if !MONO
+#if NETFRAMEWORK || WINDOWS
+ /// Reads the design-mode default that the WPF designer raises for the elements it loads.
+ /// when a XAML designer is hosting the application; otherwise .
+ /// The designer raises the default that FrameworkElement reports for the IsInDesignMode
+ /// attached property, which is what makes every element it loads answer . Asking
+ /// DesignerProperties.GetIsInDesignMode about a freshly constructed, unparented DependencyObject
+ /// reads the unraised base default instead, so it answers for any control the designer did
+ /// not set the attached property on directly - a user control nested inside another user control, for example.
+ /// Reading the metadata needs no element at all, so it also avoids creating a dispatcher-affine object.
+ [ExcludeFromCodeCoverage] // Only a hosting designer raises this default, and the call that would simulate it mutates the property metadata for the whole process.
+ private static bool ProbeWpfDesignMode() =>
+ System.ComponentModel.DesignerProperties.IsInDesignModeProperty
+ .GetMetadata(typeof(System.Windows.FrameworkElement)).DefaultValue is true;
+#else
+ /// Reads the design-mode default that the WPF designer raises, when WPF is loaded into the process.
+ /// when a XAML designer is hosting the application; otherwise .
+ /// The element handed to GetIsInDesignMode is a FrameworkElement rather than a bare
+ /// DependencyObject, because the designer raises the attached property's default for
+ /// FrameworkElement. A bare DependencyObject reads the unraised base default and therefore answers
+ /// for any control the designer did not set the attached property on directly - a user
+ /// control nested inside another user control, for example.
+ [ExcludeFromCodeCoverage] // Off-platform reflection: PresentationFramework only resolves inside a WPF host.
+ private static bool ProbeWpfDesignMode()
{
- var type = Type.GetType(XamlDesignPropertiesType, false);
- if (type is null)
- {
- return false;
- }
+ var designerProperties = Type.GetType(WpfDesignerPropertiesType, false);
- var methodInfo = type.GetMethod(XamlDesignPropertiesDesignModeMethodName);
- var dependencyObject = Type.GetType(XamlControlBorderType, false);
-
- if (methodInfo is null || dependencyObject is null)
- {
- return true;
- }
-
- _cachedInDesignModeResult = (bool)(methodInfo.Invoke(null, [Activator.CreateInstance(dependencyObject)]) ?? false);
- return true;
- }
-
- /// Probes for the WPF designer design-mode indicator.
- /// if the platform was detected; otherwise .
- [ExcludeFromCodeCoverage] // Off-platform reflection: the WPF type never resolves on supported targets; only the type-null guard runs.
- private static bool ProbeWpfDesignerProperties()
- {
- var type = Type.GetType(WpfDesignerPropertiesType, false);
- if (type is null)
+ if (designerProperties is null)
{
return false;
}
- var methodInfo = type.GetMethod(WpfDesignerPropertiesDesignModeMethod);
- var dependencyObject = Type.GetType(WpfDependencyPropertyType, false);
- if (methodInfo is null || dependencyObject is null)
- {
- return true;
- }
-
- _cachedInDesignModeResult = (bool)(methodInfo.Invoke(null, [Activator.CreateInstance(dependencyObject)]) ?? false);
- return true;
- }
-
- /// Probes for the WinRT design-mode indicator.
- /// if the platform was detected; otherwise .
- [ExcludeFromCodeCoverage] // Off-platform reflection: the WinRT type never resolves on supported targets; only the type-null guard runs.
- private static bool ProbeWinRtDesignMode()
- {
- var type = Type.GetType(WinFormsDesignerPropertiesType, false);
- if (type is null)
- {
- return false;
- }
+ var designModeMethod = designerProperties.GetMethod(WpfDesignerPropertiesDesignModeMethod);
+ var frameworkElement = Type.GetType(WpfFrameworkElementType, false);
- _cachedInDesignModeResult = (bool)(type.GetProperty(WinFormsDesignerPropertiesDesignModeMethod)?.GetMethod?.Invoke(null, null) ?? false);
- return true;
+ return designModeMethod is not null
+ && frameworkElement is not null
+ && designModeMethod.Invoke(null, [Activator.CreateInstance(frameworkElement)]) is true;
}
-
- /// Probes for a known design-environment host executable as the fallback indicator.
- /// Always , as this is the terminal fallback probe.
- private static bool ProbeDesignEnvironmentExecutable()
- {
-#if NETFRAMEWORK
- var entry = Assembly.GetEntryAssembly()?.Location;
-#else
- var entry = AppContext.BaseDirectory;
#endif
- _cachedInDesignModeResult = IsDesignEnvironmentEntry(entry);
-
- return true;
- }
- /// Determines whether the host executable name corresponds to a known design environment.
- /// The known design-environment executable name.
- /// The current host executable name.
- /// when the host is the supplied design environment.
- private static bool IsDesignEnvironment(string designEnvironment, string exeName) =>
-#if NETFRAMEWORK
- designEnvironment.IndexOf(exeName, StringComparison.InvariantCultureIgnoreCase) != -1;
-#else
- designEnvironment.Contains(exeName, StringComparison.InvariantCultureIgnoreCase);
+ /// Reads the Windows Runtime design-mode indicator, when the Windows Runtime is available.
+ /// when a Windows Runtime designer is hosting the application; otherwise .
+ [ExcludeFromCodeCoverage] // Off-platform reflection: the Windows Runtime projection only resolves inside a Windows Runtime host.
+ private static bool ProbeWinRtDesignMode() =>
+ Type.GetType(WinRtDesignModeType, false)?
+ .GetProperty(WinRtDesignModeEnabledProperty)?
+ .GetMethod?
+ .Invoke(null, null) is true;
#endif
}
diff --git a/src/Splat.Drawing/PlatformModeDetector.cs b/src/Splat.Drawing/PlatformModeDetector.cs
index 6200d395a..3b75a923b 100644
--- a/src/Splat.Drawing/PlatformModeDetector.cs
+++ b/src/Splat.Drawing/PlatformModeDetector.cs
@@ -55,16 +55,19 @@ public static bool InDesignMode()
}
/// Gets the current state for test isolation. Used by test scopes.
- /// A tuple containing the current detector and cached result.
- internal static (IPlatformModeDetector detector, bool? cachedResult) GetState() =>
- (Current, _cachedInDesignModeResult);
+ /// A tuple containing the current detector, the cached result, and the default detector's own cached result.
+ /// memoizes separately, so its cache has to travel with this
+ /// state; otherwise a test that leaves a design-mode value behind there leaks into every later test.
+ internal static (IPlatformModeDetector detector, bool? cachedResult, bool? defaultDetectorCachedResult) GetState() =>
+ (Current, _cachedInDesignModeResult, DefaultPlatformModeDetector.GetState());
/// Restores the state for test isolation. Used by test scopes.
/// The state to restore.
- internal static void RestoreState((IPlatformModeDetector detector, bool? cachedResult) state)
+ internal static void RestoreState((IPlatformModeDetector detector, bool? cachedResult, bool? defaultDetectorCachedResult) state)
{
Current = state.detector;
_cachedInDesignModeResult = state.cachedResult;
+ DefaultPlatformModeDetector.RestoreState(state.defaultDetectorCachedResult);
}
/// Resets the state to default for test isolation. Used by test scopes.
@@ -72,5 +75,6 @@ internal static void ResetState()
{
Current = new DefaultPlatformModeDetector();
_cachedInDesignModeResult = null;
+ DefaultPlatformModeDetector.ResetState();
}
}
diff --git a/src/tests/Splat.Common.Test/PlatformModeDetectorScope.cs b/src/tests/Splat.Common.Test/PlatformModeDetectorScope.cs
index f81e5b4fe..875085fb8 100644
--- a/src/tests/Splat.Common.Test/PlatformModeDetectorScope.cs
+++ b/src/tests/Splat.Common.Test/PlatformModeDetectorScope.cs
@@ -26,13 +26,17 @@ public sealed class PlatformModeDetectorScope : IDisposable
/// The cached platform-mode-detection result captured on construction.
private readonly bool? _savedCachedResult;
+ /// The default detector's own cached design-mode result captured on construction.
+ private readonly bool? _savedDefaultDetectorCachedResult;
+
/// Initializes a new instance of the class. Saves the current PlatformModeDetector state and resets it to default.
public PlatformModeDetectorScope()
{
- (_savedDetector, _savedCachedResult) = PlatformModeDetector.GetState();
+ (_savedDetector, _savedCachedResult, _savedDefaultDetectorCachedResult) = PlatformModeDetector.GetState();
PlatformModeDetector.ResetState();
}
/// Restores the PlatformModeDetector to its previous state.
- public void Dispose() => PlatformModeDetector.RestoreState((_savedDetector, _savedCachedResult));
+ public void Dispose() =>
+ PlatformModeDetector.RestoreState((_savedDetector, _savedCachedResult, _savedDefaultDetectorCachedResult));
}
diff --git a/src/tests/Splat.Drawing.Tests/DefaultPlatformModeDetectorCoverageTests.cs b/src/tests/Splat.Drawing.Tests/DefaultPlatformModeDetectorCoverageTests.cs
index 2a1ab6a0b..ced69a774 100644
--- a/src/tests/Splat.Drawing.Tests/DefaultPlatformModeDetectorCoverageTests.cs
+++ b/src/tests/Splat.Drawing.Tests/DefaultPlatformModeDetectorCoverageTests.cs
@@ -5,6 +5,7 @@
namespace Splat.Drawing.Tests;
/// Unit tests covering .
+[NotInParallel] // Reads the memoized design-mode result, which is static state.
public sealed class DefaultPlatformModeDetectorCoverageTests
{
/// An entry-point path whose executable name matches a known design-environment host.
diff --git a/src/tests/Splat.Drawing.Tests/DesignModeDetectionTests.cs b/src/tests/Splat.Drawing.Tests/DesignModeDetectionTests.cs
new file mode 100644
index 000000000..3884f86ff
--- /dev/null
+++ b/src/tests/Splat.Drawing.Tests/DesignModeDetectionTests.cs
@@ -0,0 +1,139 @@
+// Copyright (c) 2019-2026 ReactiveUI Association Incorporated. All rights reserved.
+// ReactiveUI Association Incorporated licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+using System.IO;
+
+namespace Splat.Drawing.Tests;
+
+/// Unit tests covering how decides that a designer is hosting the application.
+[NotInParallel] // Mutates the memoized design-mode result, which is static state.
+public sealed class DesignModeDetectionTests
+{
+ /// An entry-point path whose executable name matches a known design-environment host.
+ private const string DesignHostPath = "/apps/design/BLEND.EXE";
+
+ /// An entry-point path whose executable name does not match any known design environment.
+ private const string ApplicationHostPath = "/apps/myapp/MyApp.dll";
+
+ /// Verifies that a probe reporting a design environment is what the detector reports, rather than being discarded.
+ /// A representing the asynchronous operation.
+ [Test]
+ public async Task DetectDesignMode_WithDesignHostExecutable_ReportsDesignMode() =>
+ await Assert.That(DefaultPlatformModeDetector.DetectDesignMode(DesignHostPath)).IsTrue();
+
+ /// Verifies that an ordinary application host is not reported as a designer.
+ /// A representing the asynchronous operation.
+ [Test]
+ public async Task DetectDesignMode_WithApplicationExecutable_ReportsRuntime() =>
+ await Assert.That(DefaultPlatformModeDetector.DetectDesignMode(ApplicationHostPath)).IsFalse();
+
+ /// Verifies that an unavailable host path is not reported as a designer.
+ /// A representing the asynchronous operation.
+ [Test]
+ public async Task DetectDesignMode_WithUnavailableHostPath_ReportsRuntime() =>
+ await Assert.That(DefaultPlatformModeDetector.DetectDesignMode(null)).IsFalse();
+
+ /// Verifies that a WPF designer signal is reported even when nothing else indicates design mode.
+ /// A representing the asynchronous operation.
+ [Test]
+ public async Task ResolveDesignMode_WhenWpfProbeReportsDesigner_ReportsDesignMode() =>
+ await Assert.That(DefaultPlatformModeDetector.ResolveDesignMode(true, false, ApplicationHostPath)).IsTrue();
+
+ /// Verifies that a Windows Runtime designer signal is reported even when nothing else indicates design mode.
+ /// A representing the asynchronous operation.
+ [Test]
+ public async Task ResolveDesignMode_WhenWinRtProbeReportsDesigner_ReportsDesignMode() =>
+ await Assert.That(DefaultPlatformModeDetector.ResolveDesignMode(false, true, ApplicationHostPath)).IsTrue();
+
+ /// Verifies that the host executable decides when neither designer probe reports anything.
+ /// A representing the asynchronous operation.
+ [Test]
+ public async Task ResolveDesignMode_WhenNoProbeReportsDesigner_FallsBackToHostExecutable() =>
+ await Assert.That(DefaultPlatformModeDetector.ResolveDesignMode(false, false, DesignHostPath)).IsTrue();
+
+ /// Verifies that nothing indicating design mode reports a running application.
+ /// A representing the asynchronous operation.
+ [Test]
+ public async Task ResolveDesignMode_WhenNothingReportsDesigner_ReportsRuntime() =>
+ await Assert.That(DefaultPlatformModeDetector.ResolveDesignMode(false, false, ApplicationHostPath)).IsFalse();
+
+ /// Verifies that the design-host match ignores case, as Windows executable names do.
+ /// A representing the asynchronous operation.
+ [Test]
+ public async Task IsDesignEnvironmentEntry_WithDifferentlyCasedDesignHost_IsTrue() =>
+ await Assert.That(DefaultPlatformModeDetector.IsDesignEnvironmentEntry("/apps/design/Blend.exe")).IsTrue();
+
+ /// Verifies that a directory path, which has no executable name, is not treated as a design environment.
+ /// A representing the asynchronous operation.
+ [Test]
+ public async Task IsDesignEnvironmentEntry_WithDirectoryPath_IsFalse() =>
+ await Assert.That(DefaultPlatformModeDetector.IsDesignEnvironmentEntry("/apps/myapp/bin/")).IsFalse();
+
+ /// Verifies that an empty entry path is not treated as a design environment.
+ /// A representing the asynchronous operation.
+ [Test]
+ public async Task IsDesignEnvironmentEntry_WithEmptyEntry_IsFalse() =>
+ await Assert.That(DefaultPlatformModeDetector.IsDesignEnvironmentEntry(string.Empty)).IsFalse();
+
+ /// Verifies that an executable whose name is merely a fragment of a design host name is not a match.
+ /// A representing the asynchronous operation.
+ [Test]
+ public async Task IsDesignEnvironmentEntry_WithNameFragmentOfDesignHost_IsFalse() =>
+ await Assert.That(DefaultPlatformModeDetector.IsDesignEnvironmentEntry("/apps/myapp/END.EXE")).IsFalse();
+
+ /// Verifies that the host path names an executable file rather than the directory it lives in.
+ /// A representing the asynchronous operation.
+ [Test]
+ public async Task HostExecutablePath_NamesAnExecutableFile()
+ {
+ var hostExecutablePath = DefaultPlatformModeDetector.HostExecutablePath;
+
+ await Assert.That(hostExecutablePath).IsNotNullOrEmpty();
+ await Assert.That(new FileInfo(hostExecutablePath!).Name).IsNotEmpty();
+ }
+
+ /// Verifies that the running test host is not mistaken for a design environment.
+ /// A representing the asynchronous operation.
+ [Test]
+ public async Task HostExecutablePath_IsNotADesignEnvironment() =>
+ await Assert.That(DefaultPlatformModeDetector.IsDesignEnvironmentEntry(DefaultPlatformModeDetector.HostExecutablePath)).IsFalse();
+
+ /// Verifies that the detector reports the memoized result rather than re-probing.
+ /// A representing the asynchronous operation.
+ [Test]
+ public async Task InDesignMode_ReportsMemoizedResult()
+ {
+ var saved = DefaultPlatformModeDetector.GetState();
+ try
+ {
+ DefaultPlatformModeDetector.RestoreState(true);
+
+ await Assert.That(new DefaultPlatformModeDetector().InDesignMode()).IsTrue();
+ }
+ finally
+ {
+ DefaultPlatformModeDetector.RestoreState(saved);
+ }
+ }
+
+ /// Verifies that discarding the memoized result makes the detector probe again.
+ /// A representing the asynchronous operation.
+ [Test]
+ public async Task InDesignMode_AfterStateReset_ProbesAgain()
+ {
+ var saved = DefaultPlatformModeDetector.GetState();
+ try
+ {
+ DefaultPlatformModeDetector.RestoreState(true);
+ DefaultPlatformModeDetector.ResetState();
+
+ await Assert.That(DefaultPlatformModeDetector.GetState()).IsNull();
+ await Assert.That(new DefaultPlatformModeDetector().InDesignMode()).IsFalse();
+ }
+ finally
+ {
+ DefaultPlatformModeDetector.RestoreState(saved);
+ }
+ }
+}
diff --git a/src/tests/Splat.Drawing.Tests/PlatformModeDetectorCoverageTests.cs b/src/tests/Splat.Drawing.Tests/PlatformModeDetectorCoverageTests.cs
index 55f332ea1..04a5a1cf7 100644
--- a/src/tests/Splat.Drawing.Tests/PlatformModeDetectorCoverageTests.cs
+++ b/src/tests/Splat.Drawing.Tests/PlatformModeDetectorCoverageTests.cs
@@ -44,6 +44,33 @@ public async Task OverrideModeDetector_FalseDetector_ReturnsFalse()
}
}
+ /// Verifies that a repeat query reports the memoized result instead of asking the detector again.
+ /// A representing the asynchronous operation.
+ [Test]
+ public async Task InDesignMode_OnRepeatQuery_ReportsMemoizedResult()
+ {
+ var saved = PlatformModeDetector.GetState();
+ try
+ {
+ var detector = new StubModeDetector(true);
+ PlatformModeDetector.OverrideModeDetector(detector);
+
+ var first = PlatformModeDetector.InDesignMode();
+ var second = PlatformModeDetector.InDesignMode();
+
+ using (Assert.Multiple())
+ {
+ await Assert.That(first).IsTrue();
+ await Assert.That(second).IsTrue();
+ await Assert.That(detector.QueryCount).IsEqualTo(1);
+ }
+ }
+ finally
+ {
+ PlatformModeDetector.RestoreState(saved);
+ }
+ }
+
/// Verifies that a null design-mode result falls back to false.
/// A representing the asynchronous operation.
[Test]
@@ -81,11 +108,18 @@ public async Task ResetState_RestoresDefaultDetector()
}
}
- /// A stub mode detector returning a fixed design-mode value.
+ /// A stub mode detector returning a fixed design-mode value and counting how often it was asked.
/// The value to return from .
private sealed class StubModeDetector(bool? result) : IPlatformModeDetector
{
+ /// Gets the number of times the detector was asked for the design-mode value.
+ public int QueryCount { get; private set; }
+
///
- public bool? InDesignMode() => result;
+ public bool? InDesignMode()
+ {
+ QueryCount++;
+ return result;
+ }
}
}