Skip to content

Commit 9af106f

Browse files
committed
feat: add net48 Avalonia preview host and UIA smoke tests
Pull the preview-host idea onto this branch, but refactor it to the branch's net48-only policy instead of the prototype branch's net8 shape. Adds a new net48 `FwAvaloniaPreviewHost` that discovers preview modules via assembly attributes in `FwAvalonia`, along with a runner script at `scripts/Agent/Run-AvaloniaPreview.ps1`. Wires the current lexical-edit POC into that host with: - `FwPreviewModuleAttribute` / `IFwPreviewDataProvider` - `PocPreviewWindow` + `PocPreviewDataProvider` - stable Avalonia automation IDs/names on the slice, text editors, labels, and morph-type chooser Adds native desktop automation tests in `FwAvaloniaPreviewHostTests` using `System.Windows.Automation` that launch the real preview-host exe and verify: - the main preview window and core controls expose stable automation IDs - the morph-type button supports InvokePattern and shows the popup list Updates `build.ps1 -BuildAvalonia` so the optional Avalonia lane also builds `FwAvaloniaPreviewHostTests` when `-BuildTests` is requested. Validated: - `dotnet build` `Src/Common/FwAvaloniaPreviewHost/FwAvaloniaPreviewHost.csproj` - `dotnet test` `Src/Common/FwAvaloniaPreviewHost/FwAvaloniaPreviewHostTests/FwAvaloniaPreviewHostTests.csproj` passes repeatedly (2 UIA tests) - `scripts/Agent/Run-AvaloniaPreview.ps1 -BuildOnly` - the exact previously failing command now succeeds: `./build.ps1 -BuildAvalonia -Project` `"Src/Common/FwAvalonia/FwAvalonia.csproj" -NodeReuse $false` - `./build.ps1 -BuildAvalonia -BuildTests -Project` `"Src/Common/FwAvaloniaPreviewHost/FwAvaloniaPreviewHost.csproj"` `-SkipRestore -SkipDependencyCheck -NodeReuse $false` succeeds
1 parent 231a2c5 commit 9af106f

22 files changed

Lines changed: 858 additions & 36 deletions

Src/Common/FwAvalonia/Poc/MorphTypePopupChooser.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using Avalonia.Controls;
66
using Avalonia.Controls.Primitives;
77
using Avalonia.Layout;
8+
using Avalonia.Automation;
89

910
namespace SIL.FieldWorks.Common.FwAvalonia.Poc
1011
{
@@ -30,6 +31,8 @@ public MorphTypePopupChooser(PocEntryDto entry)
3031
ItemsSource = entry.MorphTypeOptions,
3132
SelectedItem = entry.SelectedMorphType
3233
};
34+
AutomationProperties.SetAutomationId(_list, "MorphTypeChooser.List");
35+
AutomationProperties.SetName(_list, "Morph type options");
3336
_list.SelectionChanged += (sender, args) =>
3437
{
3538
if (_list.SelectedItem is MorphTypeOption option)
@@ -49,6 +52,8 @@ public MorphTypePopupChooser(PocEntryDto entry)
4952
HorizontalAlignment = HorizontalAlignment.Left,
5053
Flyout = _flyout
5154
};
55+
AutomationProperties.SetAutomationId(_button, "MorphTypeChooser.Button");
56+
AutomationProperties.SetName(_button, "Morph Type");
5257

5358
Content = _button;
5459
}

Src/Common/FwAvalonia/Poc/MultiWsTextEditor.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// (http://www.gnu.org/licenses/lgpl-2.1.html)
44

55
using System.Collections.Generic;
6+
using Avalonia.Automation;
67
using Avalonia.Controls;
78
using Avalonia.Layout;
89
using Avalonia.Media;
@@ -22,6 +23,8 @@ public sealed class MultiWsTextEditor : UserControl
2223
public MultiWsTextEditor(IList<WsAlternative> alternatives, string editorName)
2324
{
2425
Name = editorName;
26+
AutomationProperties.SetAutomationId(this, editorName);
27+
AutomationProperties.SetName(this, editorName);
2528
Alternatives = alternatives;
2629

2730
var stack = new StackPanel { Spacing = PocDensity.RowSpacing };
@@ -47,6 +50,8 @@ public MultiWsTextEditor(IList<WsAlternative> alternatives, string editorName)
4750
MinHeight = 0,
4851
AcceptsReturn = false
4952
};
53+
AutomationProperties.SetAutomationId(box, editorName + "." + alt.WsAbbrev);
54+
AutomationProperties.SetName(box, editorName + " " + alt.WsAbbrev);
5055
box.TextChanged += (sender, args) => captured.Value = box.Text;
5156
_boxes.Add(box);
5257

Src/Common/FwAvalonia/Poc/PocLexEntrySlice.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using Avalonia.Controls;
66
using Avalonia.Layout;
77
using Avalonia.Media;
8+
using Avalonia.Automation;
89

910
namespace SIL.FieldWorks.Common.FwAvalonia.Poc
1011
{
@@ -19,6 +20,8 @@ public sealed class PocLexEntrySlice : UserControl
1920
public PocLexEntrySlice(PocEntryDto entry)
2021
{
2122
Name = "PocLexEntrySlice";
23+
AutomationProperties.SetAutomationId(this, "PocLexEntrySlice");
24+
AutomationProperties.SetName(this, "Lexical Edit POC Slice");
2225
Entry = entry;
2326

2427
LexemeFormEditor = new MultiWsTextEditor(entry.LexemeForm, "LexemeFormEditor");
@@ -70,6 +73,8 @@ private static void AddField(Grid grid, int row, string label, Control editor)
7073
TextAlignment = TextAlignment.Right,
7174
Foreground = Brushes.Black
7275
};
76+
AutomationProperties.SetAutomationId(labelBlock, editor.Name + ".Label");
77+
AutomationProperties.SetName(labelBlock, label);
7378
Grid.SetRow(labelBlock, row);
7479
Grid.SetColumn(labelBlock, 0);
7580
grid.Children.Add(labelBlock);
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Copyright (c) 2026 SIL International
2+
// This software is licensed under the LGPL, version 2.1 or later
3+
// (http://www.gnu.org/licenses/lgpl-2.1.html)
4+
5+
using System.Collections.Generic;
6+
using SIL.FieldWorks.Common.FwAvalonia.Preview;
7+
8+
namespace SIL.FieldWorks.Common.FwAvalonia.Poc
9+
{
10+
/// <summary>
11+
/// Preview/sample data provider for the lexical-edit POC window. Keeps the preview host detached
12+
/// from LCModel by returning DTO/sample data only.
13+
/// </summary>
14+
public sealed class PocPreviewDataProvider : IFwPreviewDataProvider
15+
{
16+
public object CreateDataContext(string dataMode)
17+
{
18+
if (string.Equals(dataMode, "sample", System.StringComparison.OrdinalIgnoreCase))
19+
{
20+
return PocEntryDto.CreateSample();
21+
}
22+
23+
return new PocEntryDto(
24+
new List<WsAlternative>
25+
{
26+
new WsAlternative("seh", string.Empty),
27+
new WsAlternative("en", string.Empty, "Times New Roman")
28+
},
29+
new List<MorphTypeOption>
30+
{
31+
new MorphTypeOption("stem", "stem"),
32+
new MorphTypeOption("root", "root"),
33+
new MorphTypeOption("prefix", "prefix"),
34+
new MorphTypeOption("suffix", "suffix")
35+
},
36+
"stem",
37+
new List<WsAlternative>
38+
{
39+
new WsAlternative("en", string.Empty, "Times New Roman"),
40+
new WsAlternative("pt", string.Empty, "Times New Roman")
41+
});
42+
}
43+
}
44+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Copyright (c) 2026 SIL International
2+
// This software is licensed under the LGPL, version 2.1 or later
3+
// (http://www.gnu.org/licenses/lgpl-2.1.html)
4+
5+
using Avalonia.Automation;
6+
using Avalonia.Controls;
7+
8+
namespace SIL.FieldWorks.Common.FwAvalonia.Poc
9+
{
10+
/// <summary>
11+
/// Net48 preview-host window for the lexical-edit POC slice. The host sets the DataContext from
12+
/// <see cref="PocPreviewDataProvider"/>; this window responds by creating a fresh slice for that
13+
/// data and exposing stable automation identifiers for UIA tests.
14+
/// </summary>
15+
public sealed class PocPreviewWindow : Window
16+
{
17+
public PocPreviewWindow()
18+
{
19+
Width = 900;
20+
Height = 520;
21+
AutomationProperties.SetAutomationId(this, "LexicalEditPocWindow");
22+
AutomationProperties.SetName(this, "Lexical Edit POC Preview");
23+
24+
var empty = new PocLexEntrySlice(PocEntryDto.CreateSample());
25+
Content = empty;
26+
}
27+
28+
protected override void OnDataContextChanged(System.EventArgs e)
29+
{
30+
base.OnDataContextChanged(e);
31+
if (DataContext is PocEntryDto entry)
32+
{
33+
Content = new PocLexEntrySlice(entry);
34+
}
35+
}
36+
}
37+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
using SIL.FieldWorks.Common.FwAvalonia.Poc;
2+
using SIL.FieldWorks.Common.FwAvalonia.Preview;
3+
4+
[assembly: FwPreviewModule(
5+
"lexical-edit-poc",
6+
"Lexical Edit POC",
7+
typeof(PocPreviewWindow),
8+
typeof(PocPreviewDataProvider))]
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright (c) 2026 SIL International
2+
// This software is licensed under the LGPL, version 2.1 or later
3+
// (http://www.gnu.org/licenses/lgpl-2.1.html)
4+
5+
using System;
6+
7+
namespace SIL.FieldWorks.Common.FwAvalonia.Preview
8+
{
9+
/// <summary>
10+
/// Registers a previewable Avalonia module with the shared preview host.
11+
/// Applied at the assembly level in the module assembly.
12+
/// </summary>
13+
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
14+
public sealed class FwPreviewModuleAttribute : Attribute
15+
{
16+
public FwPreviewModuleAttribute(string id, string displayName, Type windowType)
17+
: this(id, displayName, windowType, dataProviderType: null)
18+
{
19+
}
20+
21+
public FwPreviewModuleAttribute(string id, string displayName, Type windowType, Type dataProviderType)
22+
{
23+
Id = id;
24+
DisplayName = displayName;
25+
WindowType = windowType;
26+
DataProviderType = dataProviderType;
27+
}
28+
29+
public string Id { get; }
30+
public string DisplayName { get; }
31+
public Type WindowType { get; }
32+
public Type DataProviderType { get; }
33+
}
34+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
namespace SIL.FieldWorks.Common.FwAvalonia.Preview
2+
{
3+
/// <summary>
4+
/// Provides a design-time / preview data context for an Avalonia module window.
5+
/// Implementations should prefer DTO/sample data and avoid opening live FieldWorks projects.
6+
/// </summary>
7+
public interface IFwPreviewDataProvider
8+
{
9+
object CreateDataContext(string dataMode);
10+
}
11+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<AssemblyName>FwAvaloniaPreviewHost</AssemblyName>
5+
<RootNamespace>SIL.FieldWorks.Common.FwAvalonia.PreviewHost</RootNamespace>
6+
<TargetFramework>net48</TargetFramework>
7+
<LangVersion>latest</LangVersion>
8+
<OutputType>WinExe</OutputType>
9+
<Nullable>disable</Nullable>
10+
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
11+
<Prefer32Bit>false</Prefer32Bit>
12+
<IsPackable>false</IsPackable>
13+
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
14+
<NoWarn>$(NoWarn);CS1591;NU1701</NoWarn>
15+
</PropertyGroup>
16+
17+
<ItemGroup>
18+
<PackageReference Include="Avalonia" />
19+
<PackageReference Include="Avalonia.Desktop" />
20+
<PackageReference Include="Avalonia.Themes.Fluent" />
21+
</ItemGroup>
22+
23+
<ItemGroup>
24+
<ProjectReference Include="..\FwAvalonia\FwAvalonia.csproj" />
25+
<Compile Include="..\..\CommonAssemblyInfo.cs" Link="Properties\CommonAssemblyInfo.cs" />
26+
</ItemGroup>
27+
28+
<ItemGroup>
29+
<Compile Remove="FwAvaloniaPreviewHostTests/**/*.cs" />
30+
</ItemGroup>
31+
32+
</Project>
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net48</TargetFramework>
5+
<LangVersion>latest</LangVersion>
6+
<Nullable>disable</Nullable>
7+
<IsTestProject>true</IsTestProject>
8+
<IsPackable>false</IsPackable>
9+
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
10+
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
11+
<NoWarn>$(NoWarn);CS1591;NU1701</NoWarn>
12+
</PropertyGroup>
13+
14+
<ItemGroup>
15+
<Reference Include="UIAutomationClient" />
16+
<Reference Include="UIAutomationTypes" />
17+
</ItemGroup>
18+
19+
<ItemGroup>
20+
<ProjectReference Include="..\FwAvaloniaPreviewHost.csproj" />
21+
<Compile Include="..\..\..\CommonAssemblyInfo.cs" Link="Properties\CommonAssemblyInfo.cs" />
22+
</ItemGroup>
23+
24+
<ItemGroup>
25+
<Compile Remove="$(FwRoot)Src\AssemblyInfoForTests.cs" />
26+
<Compile Remove="$(FwRoot)Src\AssemblyInfoForUiIndependentTests.cs" />
27+
</ItemGroup>
28+
29+
</Project>

0 commit comments

Comments
 (0)