Skip to content

Commit dc2145b

Browse files
committed
Refactor PropertyProviderTests and update project configuration for WinUI support
1 parent 3d6b289 commit dc2145b

4 files changed

Lines changed: 151 additions & 15 deletions

File tree

src/UnoPropertyGrid.Tests/PropertyProviderTests.cs

Lines changed: 90 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
using System.ComponentModel;
2+
using System.Diagnostics;
23
using System.Linq;
4+
using Microsoft.UI.Xaml;
35
using Microsoft.UI.Xaml.Media;
46
using NUnit.Framework;
57
using Windows.UI.Text;
68

79
namespace UnoPropertyGrid.Tests;
810

911
[TestFixture]
12+
#if WINDOWS_APP_SDK
13+
[Apartment(System.Threading.ApartmentState.STA)]
14+
#endif
1015
public sealed class PropertyProviderTests
1116
{
1217
[Test]
@@ -100,7 +105,24 @@ public void EventViewModel_ValidatesHandlerName()
100105
[Test]
101106
public void ViewModel_WritesFontFamilyValue()
102107
{
108+
#if WINDOWS_APP_SDK
109+
if (Environment.GetEnvironmentVariable(StandaloneWinUITestEnvironmentVariable) != "1")
110+
{
111+
RunFontFamilyTestInStandaloneProcess();
112+
return;
113+
}
114+
#endif
115+
116+
RunWithWinUIApplication(Test);
117+
118+
static void Test()
119+
{
103120
var target = new SampleComponent();
121+
target.FontFamily = new FontFamily("Segoe UI");
122+
Assert.That(target.FontFamily, Is.Not.Null);
123+
Assert.That(target.FontFamily!.Source, Is.EqualTo("Segoe UI"));
124+
target.FontFamily = null;
125+
104126
var property = new TypeDescriptorPropertyProvider()
105127
.GetProperties(target)
106128
.Single(p => p.Name == nameof(SampleComponent.FontFamily));
@@ -110,7 +132,10 @@ public void ViewModel_WritesFontFamilyValue()
110132

111133
viewModel.FontFamilyValue = "Consolas";
112134

113-
Assert.That(target.FontFamily.Source, Is.EqualTo("Consolas"));
135+
Assert.That(viewModel.HasError, Is.False, viewModel.Error);
136+
Assert.That(target.FontFamily, Is.Not.Null);
137+
Assert.That(target.FontFamily!.Source, Is.EqualTo("Consolas"));
138+
}
114139
}
115140

116141
[Test]
@@ -157,7 +182,7 @@ sealed class SampleComponent
157182

158183
public string ReadOnlyName => "fixed";
159184

160-
public FontFamily FontFamily { get; set; } = new("Segoe UI");
185+
public FontFamily? FontFamily { get; set; }
161186

162187
public FontWeight FontWeight { get; set; } = new() { Weight = 400 };
163188

@@ -179,4 +204,67 @@ enum SampleMode
179204
First,
180205
Second
181206
}
207+
208+
#if WINDOWS_APP_SDK
209+
sealed class TestApplication : Application
210+
{
211+
}
212+
#endif
213+
214+
static void RunWithWinUIApplication(Action action)
215+
{
216+
#if WINDOWS_APP_SDK
217+
Exception? exception = null;
218+
Application.Start(_ =>
219+
{
220+
var app = new TestApplication();
221+
try
222+
{
223+
action();
224+
}
225+
catch (Exception ex)
226+
{
227+
exception = ex;
228+
}
229+
finally
230+
{
231+
Application.Current.Exit();
232+
}
233+
});
234+
235+
if (exception != null)
236+
throw exception;
237+
#else
238+
action();
239+
#endif
240+
}
241+
242+
#if WINDOWS_APP_SDK
243+
const string StandaloneWinUITestEnvironmentVariable = "UNOPROPERTYGRID_STANDALONE_WINUI_TEST";
244+
245+
static void RunFontFamilyTestInStandaloneProcess()
246+
{
247+
var assemblyPath = typeof(PropertyProviderTests).Assembly.Location;
248+
var executablePath = Path.ChangeExtension(assemblyPath, ".exe");
249+
Assert.That(File.Exists(executablePath), Is.True, $"Test executable was not found at '{executablePath}'.");
250+
251+
using var process = new Process();
252+
process.StartInfo = new ProcessStartInfo(executablePath)
253+
{
254+
UseShellExecute = false,
255+
RedirectStandardOutput = true,
256+
RedirectStandardError = true,
257+
WorkingDirectory = Path.GetDirectoryName(executablePath)!
258+
};
259+
process.StartInfo.ArgumentList.Add("--where");
260+
process.StartInfo.ArgumentList.Add($"test == '{typeof(PropertyProviderTests).FullName}.{nameof(ViewModel_WritesFontFamilyValue)}'");
261+
process.StartInfo.Environment[StandaloneWinUITestEnvironmentVariable] = "1";
262+
263+
process.Start();
264+
var output = process.StandardOutput.ReadToEnd();
265+
var error = process.StandardError.ReadToEnd();
266+
Assert.That(process.WaitForExit(60000), Is.True, "Standalone WinUI test process timed out.");
267+
Assert.That(process.ExitCode, Is.EqualTo(0), output + error);
268+
}
269+
#endif
182270
}
Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,61 @@
11
<Project Sdk="Uno.Sdk">
22
<PropertyGroup>
3-
<TargetFramework>net10.0-desktop</TargetFramework>
4-
<OutputType>Library</OutputType>
3+
<TargetFrameworks>net10.0-desktop</TargetFrameworks>
4+
<TargetFrameworks Condition="'$(OS)' == 'Windows_NT'">$(TargetFrameworks);net10.0-windows10.0.19041.0</TargetFrameworks>
5+
<OutputType>Exe</OutputType>
56
<StartupObject>UnoPropertyGrid.Tests.Program</StartupObject>
7+
<WinUISDKReferences>false</WinUISDKReferences>
8+
<UnoSingleProject>true</UnoSingleProject>
9+
<UnoFeatures>SkiaRenderer;</UnoFeatures>
610
<ImplicitUsings>enable</ImplicitUsings>
711
<Nullable>enable</Nullable>
812
<IsPackable>false</IsPackable>
9-
<EnableDefaultCompileItems>true</EnableDefaultCompileItems>
13+
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
1014
<GenerateTargetPlatformAttribute>false</GenerateTargetPlatformAttribute>
1115
<GenerateSupportedOSPlatformAttribute>false</GenerateSupportedOSPlatformAttribute>
1216
<NoWarn>$(NoWarn);CA1416</NoWarn>
1317
</PropertyGroup>
1418

19+
<PropertyGroup Condition="'$(TargetFramework)' == 'net10.0-windows10.0.19041.0'">
20+
<UseWinUI>true</UseWinUI>
21+
<EnableWindowsTargeting>true</EnableWindowsTargeting>
22+
<DefineConstants>$(DefineConstants);WINDOWS_APP_SDK</DefineConstants>
23+
<WindowsSdkPackageVersion>10.0.19041.57</WindowsSdkPackageVersion>
24+
<WindowsPackageType>None</WindowsPackageType>
25+
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
26+
</PropertyGroup>
27+
28+
<!-- Uno.SingleProject.WinAppSdk.targets sets OutputType=WinExe for Uno heads after static evaluation.
29+
Restore it to Exe before test validation and compilation. -->
30+
<Target Name="_RestoreOutputTypeForTests"
31+
BeforeTargets="BeforeBuild"
32+
Condition="'$(TargetFramework)' == 'net10.0-windows10.0.19041.0'">
33+
<PropertyGroup>
34+
<OutputType>Exe</OutputType>
35+
</PropertyGroup>
36+
</Target>
37+
38+
<ItemGroup>
39+
<Compile Include="Program.cs" />
40+
<Compile Include="PropertyProviderTests.cs" />
41+
</ItemGroup>
42+
1543
<ItemGroup>
1644
<PackageReference Include="Microsoft.NET.Test.Sdk" />
1745
<PackageReference Include="NUnit" />
1846
<PackageReference Include="NUnitLite" />
1947
<PackageReference Include="NUnit3TestAdapter" />
2048
</ItemGroup>
2149

22-
<ItemGroup>
23-
<ProjectReference Include="..\UnoPropertyGrid\UnoPropertyGrid.csproj" />
50+
<ItemGroup Condition="'$(TargetFramework)' == 'net10.0-windows10.0.19041.0'">
51+
<PackageReference Include="Microsoft.WindowsAppSDK" />
52+
</ItemGroup>
53+
54+
<ItemGroup Condition="'$(TargetFramework)' == 'net10.0-desktop'">
55+
<ProjectReference Include="..\UnoPropertyGrid\UnoPropertyGrid.csproj" SetTargetFramework="TargetFramework=net9.0-desktop" />
56+
</ItemGroup>
57+
58+
<ItemGroup Condition="'$(TargetFramework)' == 'net10.0-windows10.0.19041.0'">
59+
<ProjectReference Include="..\UnoPropertyGrid\UnoPropertyGrid.csproj" SetTargetFramework="TargetFramework=net9.0-windows10.0.19041.0" />
2460
</ItemGroup>
2561
</Project>

src/UnoPropertyGrid/PropertyGridPropertyDescriptor.cs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,15 @@ public sealed class PropertyGridPropertyDescriptor
1717
readonly PropertyDescriptor? _descriptor;
1818

1919
public PropertyGridPropertyDescriptor(object component, PropertyInfo property)
20+
: this(component, property, null)
21+
{
22+
}
23+
24+
public PropertyGridPropertyDescriptor(object component, PropertyInfo property, PropertyDescriptor? descriptor)
2025
{
2126
_component = component ?? throw new ArgumentNullException(nameof(component));
2227
_property = property ?? throw new ArgumentNullException(nameof(property));
28+
_descriptor = descriptor;
2329
}
2430

2531
public PropertyGridPropertyDescriptor(object component, PropertyDescriptor descriptor)
@@ -102,9 +108,9 @@ bool TryGetDependencyPropertyDefaultValue(DependencyObject dependencyObject, out
102108

103109
public object? GetValue()
104110
{
105-
return _descriptor != null
106-
? _descriptor.GetValue(_component)
107-
: _property!.GetValue(_component);
111+
return _property != null
112+
? _property.GetValue(_component)
113+
: _descriptor!.GetValue(_component);
108114
}
109115

110116
public void SetValue(object? value)
@@ -114,10 +120,10 @@ public void SetValue(object? value)
114120

115121
var converted = ConvertValue(value);
116122
PropertyGridLogger.Log($"Descriptor [{Name}]: SetValue component={_component?.GetType().Name}, value={value}, converted={converted}");
117-
if (_descriptor != null)
118-
_descriptor.SetValue(_component, converted);
123+
if (_property != null)
124+
_property.SetValue(_component, converted);
119125
else
120-
_property!.SetValue(_component, converted);
126+
_descriptor!.SetValue(_component, converted);
121127
PropertyGridLogger.Log($"Descriptor [{Name}]: SetValue done, read-back={GetValue()}");
122128
}
123129

src/UnoPropertyGrid/TypeDescriptorPropertyProvider.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,23 @@ public IEnumerable<PropertyGridPropertyDescriptor> GetProperties(object componen
1010
if (component == null)
1111
throw new ArgumentNullException(nameof(component));
1212

13+
var reflectionProperties = component.GetType()
14+
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
15+
.ToDictionary(property => property.Name, StringComparer.Ordinal);
1316
var descriptorNames = new HashSet<string>(StringComparer.Ordinal);
1417
foreach (PropertyDescriptor descriptor in GetSafeTypeDescriptorProperties(component))
1518
{
1619
descriptorNames.Add(descriptor.Name);
1720
if (!descriptor.IsBrowsable)
1821
continue;
1922

20-
yield return new PropertyGridPropertyDescriptor(component, descriptor);
23+
if (reflectionProperties.TryGetValue(descriptor.Name, out var property) && property.CanRead)
24+
yield return new PropertyGridPropertyDescriptor(component, property, descriptor);
25+
else
26+
yield return new PropertyGridPropertyDescriptor(component, descriptor);
2127
}
2228

23-
foreach (var property in component.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
29+
foreach (var property in reflectionProperties.Values)
2430
{
2531
if (descriptorNames.Contains(property.Name))
2632
continue;

0 commit comments

Comments
 (0)