mirror of
https://github.com/AtomUI/AtomUI.git
synced 2026-10-18 14:37:19 +08:00
307 lines
13 KiB
C#
307 lines
13 KiB
C#
using AtomUI.Build.Tasks;
|
|
using Shouldly;
|
|
using Xunit;
|
|
|
|
namespace AtomUI.Build.Tasks.Tests;
|
|
|
|
public sealed class CollectAxamlUsageTaskTests : IDisposable
|
|
{
|
|
private readonly string _directory = Path.Combine(
|
|
Path.GetTempPath(),
|
|
$"atomui-axaml-usage-tests-{Guid.NewGuid():N}");
|
|
|
|
public CollectAxamlUsageTaskTests()
|
|
{
|
|
Directory.CreateDirectory(_directory);
|
|
}
|
|
|
|
[Fact]
|
|
public void Execute_Collects_Structured_Element_Template_And_Selector_Types()
|
|
{
|
|
var path = Write(
|
|
"Views/MainView.axaml",
|
|
"""
|
|
<ResourceDictionary xmlns="https://github.com/avaloniaui"
|
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
xmlns:atom="using:AtomUI.Desktop.Controls">
|
|
<ControlTheme TargetType="{x:Type atom:Button}"
|
|
BasedOn="{StaticResource {x:Type atom:BaseButton}}" />
|
|
<Style Selector="atom|Button.previous-btn:pointerover" />
|
|
<DataTemplate DataType="atom:Alert" />
|
|
<ControlTemplate TargetType="atom:Dialog" />
|
|
<atom:DatePicker />
|
|
</ResourceDictionary>
|
|
""");
|
|
var task = CreateTask(
|
|
new TestTaskItem(path, ("FullPath", path), ("Link", "Views/MainView.axaml")));
|
|
|
|
task.Execute().ShouldBeTrue();
|
|
|
|
var atomTypes = task.UsageCandidates
|
|
.Where(item => item.GetMetadata("TypeName")
|
|
.StartsWith("AtomUI.", StringComparison.Ordinal))
|
|
.Select(item => item.GetMetadata("TypeName"))
|
|
.Distinct(StringComparer.Ordinal)
|
|
.OrderBy(static type => type, StringComparer.Ordinal)
|
|
.ToArray();
|
|
atomTypes.ShouldBe(
|
|
[
|
|
"AtomUI.Desktop.Controls.Alert",
|
|
"AtomUI.Desktop.Controls.BaseButton",
|
|
"AtomUI.Desktop.Controls.Button",
|
|
"AtomUI.Desktop.Controls.DatePicker",
|
|
"AtomUI.Desktop.Controls.Dialog"
|
|
]);
|
|
task.UsageCandidates.Where(item => item.GetMetadata("TypeName").StartsWith("AtomUI.", StringComparison.Ordinal))
|
|
.ShouldAllBe(item =>
|
|
int.Parse(item.GetMetadata("Line"), System.Globalization.CultureInfo.InvariantCulture) > 0 &&
|
|
int.Parse(item.GetMetadata("Column"), System.Globalization.CultureInfo.InvariantCulture) > 0);
|
|
task.Uncertainties.ShouldBeEmpty();
|
|
File.Exists(task.OutputPath).ShouldBeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public void Execute_Emits_Explicit_Roots_And_Dynamic_Source_Uncertainty_Deterministically()
|
|
{
|
|
var path = Write(
|
|
"Themes/Dynamic.axaml",
|
|
"""
|
|
<ResourceDictionary xmlns="https://github.com/avaloniaui">
|
|
<ResourceInclude Source="{DynamicResource ThemeSource}" />
|
|
</ResourceDictionary>
|
|
""");
|
|
var axaml = new TestTaskItem(
|
|
path,
|
|
("FullPath", path),
|
|
("Link", "Themes/Dynamic.axaml"),
|
|
("AtomUILooseXaml", "true"));
|
|
var task = CreateTask(axaml);
|
|
task.UnitRoots = [new TestTaskItem("AtomUI.Desktop.Controls/DatePicker")];
|
|
task.PackageRoots = [new TestTaskItem("Acme.Dynamic.Controls")];
|
|
|
|
task.Execute().ShouldBeTrue();
|
|
var firstOutput = File.ReadAllText(task.OutputPath);
|
|
File.SetLastWriteTimeUtc(task.OutputPath, DateTime.UtcNow.AddMinutes(-1));
|
|
var originalWriteTime = File.GetLastWriteTimeUtc(task.OutputPath);
|
|
task.Execute().ShouldBeTrue();
|
|
var secondOutput = File.ReadAllText(task.OutputPath);
|
|
|
|
secondOutput.ShouldBe(firstOutput);
|
|
File.GetLastWriteTimeUtc(task.OutputPath).ShouldBe(originalWriteTime);
|
|
task.UsageCandidates.ShouldContain(item =>
|
|
item.GetMetadata("Kind") == "UnitRoot" &&
|
|
item.GetMetadata("Identity") == "AtomUI.Desktop.Controls/DatePicker");
|
|
task.UsageCandidates.ShouldContain(item =>
|
|
item.GetMetadata("Kind") == "PackageRoot" &&
|
|
item.GetMetadata("Identity") == "Acme.Dynamic.Controls");
|
|
task.Uncertainties.ShouldContain(item => item.GetMetadata("Reason") == "LooseAxaml");
|
|
task.Uncertainties.ShouldContain(item => item.GetMetadata("Reason") == "DynamicResourceSource");
|
|
}
|
|
|
|
[Fact]
|
|
public void Execute_Collects_Xml_Structures_Without_Interpreting_Escaped_Markup()
|
|
{
|
|
var path = Write(
|
|
"Views/Controls.axaml",
|
|
"""
|
|
<ResourceDictionary xmlns="https://github.com/avaloniaui"
|
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
xmlns:local="using:MyApplication.Controls"
|
|
xmlns:atom="clr-namespace:AtomUI.Desktop.Controls;assembly=AtomUI.Desktop.Controls">
|
|
<local:AccentButton />
|
|
<ControlTheme
|
|
TargetType="{x:Type atom:Button}"
|
|
BasedOn="{StaticResource {x:Type atom:BaseButton}}" />
|
|
<Style
|
|
Selector="atom|Button /template/ local|AccentButton" />
|
|
<DataTemplate
|
|
DataType="atom:Alert" />
|
|
<ControlTemplate
|
|
TargetType="atom:Dialog" />
|
|
<TextBlock Text="<atom:GhostButton />" />
|
|
</ResourceDictionary>
|
|
""");
|
|
|
|
var task = CreateTask(new TestTaskItem(path, ("FullPath", path), ("Link", "Views/Controls.axaml")));
|
|
|
|
task.Execute().ShouldBeTrue();
|
|
|
|
var types = task.UsageCandidates
|
|
.Select(item => item.GetMetadata("TypeName"))
|
|
.Where(static value => value.Length != 0)
|
|
.Distinct(StringComparer.Ordinal)
|
|
.OrderBy(static value => value, StringComparer.Ordinal)
|
|
.ToArray();
|
|
types.ShouldBe(
|
|
[
|
|
"AtomUI.Desktop.Controls.Alert",
|
|
"AtomUI.Desktop.Controls.BaseButton",
|
|
"AtomUI.Desktop.Controls.Button",
|
|
"AtomUI.Desktop.Controls.Dialog",
|
|
"MyApplication.Controls.AccentButton"
|
|
]);
|
|
types.ShouldNotContain("AtomUI.Desktop.Controls.GhostButton");
|
|
task.Uncertainties.ShouldBeEmpty();
|
|
}
|
|
|
|
[Fact]
|
|
public void Execute_Collects_The_Xml_Root_Element()
|
|
{
|
|
var path = Write(
|
|
"Views/RootControl.axaml",
|
|
"""
|
|
<atom:DatePicker xmlns:atom="using:AtomUI.Desktop.Controls" />
|
|
""");
|
|
var task = CreateTask(new TestTaskItem(path, ("FullPath", path), ("Link", "Views/RootControl.axaml")));
|
|
|
|
task.Execute().ShouldBeTrue();
|
|
|
|
task.UsageCandidates.ShouldContain(item =>
|
|
item.GetMetadata("Kind") == "Element" &&
|
|
item.GetMetadata("TypeName") == "AtomUI.Desktop.Controls.DatePicker");
|
|
}
|
|
|
|
[Fact]
|
|
public void Execute_Recognizes_Xaml2006_Type_Through_A_Namespace_Alias()
|
|
{
|
|
var path = Write(
|
|
"Views/AliasedType.axaml",
|
|
"""
|
|
<ResourceDictionary xmlns="https://github.com/avaloniaui"
|
|
xmlns:xaml="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
xmlns:atom="using:AtomUI.Desktop.Controls">
|
|
<ControlTheme TargetType="{xaml:Type atom:Button}" />
|
|
</ResourceDictionary>
|
|
""");
|
|
var task = CreateTask(new TestTaskItem(path, ("FullPath", path), ("Link", "Views/AliasedType.axaml")));
|
|
|
|
task.Execute().ShouldBeTrue();
|
|
|
|
task.UsageCandidates.ShouldContain(item =>
|
|
item.GetMetadata("Kind") == "XType" &&
|
|
item.GetMetadata("TypeName") == "AtomUI.Desktop.Controls.Button");
|
|
}
|
|
|
|
[Fact]
|
|
public void Execute_Does_Not_Treat_Static_Resource_Uris_As_Dynamic()
|
|
{
|
|
var path = Write(
|
|
"Themes/Includes.axaml",
|
|
"""
|
|
<ResourceDictionary xmlns="https://github.com/avaloniaui">
|
|
<ResourceInclude Source="PriceInputTheme.axaml" />
|
|
<StyleInclude Source="/Assembly/Themes.axaml" />
|
|
<ResourceInclude Source="avares://AtomUI.Desktop.Controls/Themes/Shared.axaml" />
|
|
<StyleInclude Source="{DynamicResource ThemeSource}" />
|
|
<ResourceInclude Source="http://[invalid-host" />
|
|
</ResourceDictionary>
|
|
""");
|
|
var task = CreateTask(new TestTaskItem(path, ("FullPath", path), ("Link", "Themes/Includes.axaml")));
|
|
|
|
task.Execute().ShouldBeTrue();
|
|
|
|
task.Uncertainties.Length.ShouldBe(2);
|
|
task.Uncertainties.ShouldContain(item =>
|
|
item.GetMetadata("Value") == "{DynamicResource ThemeSource}");
|
|
task.Uncertainties.ShouldContain(item =>
|
|
item.GetMetadata("Value") == "http://[invalid-host");
|
|
}
|
|
|
|
[Fact]
|
|
public void Execute_Emits_Raw_Uncertainties_With_Stable_Package_Identity()
|
|
{
|
|
var malformedPath = Write("Themes/Broken.axaml", "<ResourceDictionary>");
|
|
var dynamicPath = Write(
|
|
"Themes/Dynamic.axaml",
|
|
"""
|
|
<ResourceDictionary xmlns="https://github.com/avaloniaui">
|
|
<ResourceInclude Source="{DynamicResource ThemeSource}" />
|
|
</ResourceDictionary>
|
|
""");
|
|
var loosePath = Write(
|
|
"Themes/Loose.axaml",
|
|
"""
|
|
<ResourceDictionary xmlns="https://github.com/avaloniaui"
|
|
xmlns:atom="using:AtomUI.Desktop.Controls">
|
|
<atom:UnknownControl />
|
|
</ResourceDictionary>
|
|
""");
|
|
var task = CreateTask(
|
|
new TestTaskItem(malformedPath, ("FullPath", malformedPath), ("Link", "Themes/Broken.axaml")),
|
|
new TestTaskItem(dynamicPath, ("FullPath", dynamicPath), ("Link", "Themes/Dynamic.axaml")),
|
|
new TestTaskItem(
|
|
loosePath,
|
|
("FullPath", loosePath),
|
|
("Link", "Themes/Loose.axaml"),
|
|
("AtomUILooseXaml", "true"),
|
|
("AtomUIRegistrationUnit", "AtomUI.Desktop.Controls/")));
|
|
task.ProjectPackageId = "Acme.Project.Controls";
|
|
|
|
task.Execute().ShouldBeTrue();
|
|
|
|
task.UsageCandidates.ShouldContain(item =>
|
|
item.GetMetadata("TypeName") == "AtomUI.Desktop.Controls.UnknownControl");
|
|
task.Uncertainties.ShouldContain(item =>
|
|
item.GetMetadata("Reason") == "MalformedXml" &&
|
|
item.GetMetadata("PackageId") == "Acme.Project.Controls");
|
|
task.Uncertainties.ShouldContain(item =>
|
|
item.GetMetadata("Reason") == "DynamicResourceSource" &&
|
|
item.GetMetadata("PackageId") == "Acme.Project.Controls");
|
|
task.Uncertainties.ShouldContain(item =>
|
|
item.GetMetadata("Reason") == "LooseAxaml" &&
|
|
item.GetMetadata("PackageId") == "Acme.Project.Controls");
|
|
task.Uncertainties.ShouldContain(item =>
|
|
item.GetMetadata("Reason") == "MalformedOwnership" &&
|
|
item.GetMetadata("PackageId") == "AtomUI.Desktop.Controls");
|
|
}
|
|
|
|
[Fact]
|
|
public void Execute_Falls_Back_To_The_Project_Package_For_An_Invalid_Ownership_Prefix()
|
|
{
|
|
var path = Write(
|
|
"Themes/Ownership.axaml",
|
|
"""
|
|
<ResourceDictionary xmlns="https://github.com/avaloniaui" />
|
|
""");
|
|
var task = CreateTask(new TestTaskItem(
|
|
path,
|
|
("FullPath", path),
|
|
("Link", "Themes/Ownership.axaml"),
|
|
("AtomUIRegistrationUnit", "invalid+package/")));
|
|
task.ProjectPackageId = "Acme.Project.Controls";
|
|
|
|
task.Execute().ShouldBeTrue();
|
|
|
|
task.Uncertainties.ShouldContain(item =>
|
|
item.GetMetadata("Reason") == "MalformedOwnership" &&
|
|
item.GetMetadata("PackageId") == "Acme.Project.Controls");
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (Directory.Exists(_directory))
|
|
{
|
|
Directory.Delete(_directory, recursive: true);
|
|
}
|
|
}
|
|
|
|
private CollectAxamlUsageTask CreateTask(params TestTaskItem[] items)
|
|
{
|
|
return new CollectAxamlUsageTask
|
|
{
|
|
BuildEngine = new RecordingBuildEngine(),
|
|
AxamlFiles = items,
|
|
ProjectDirectory = _directory,
|
|
OutputPath = Path.Combine(_directory, "obj", "AtomUIAxamlUsage.xml")
|
|
};
|
|
}
|
|
|
|
private string Write(string relativePath, string content)
|
|
{
|
|
var path = Path.Combine(_directory, relativePath);
|
|
Directory.CreateDirectory(Path.GetDirectoryName(path)!);
|
|
File.WriteAllText(path, content);
|
|
return path;
|
|
}
|
|
}
|