committed by
GitHub
25 changed files with 349 additions and 97 deletions
@ -0,0 +1,167 @@ |
|||
using System; |
|||
using System.ComponentModel; |
|||
using System.IO; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Avalonia.Controls; |
|||
using Avalonia.ReactiveUI; |
|||
using Microsoft.CodeAnalysis; |
|||
using Microsoft.CodeAnalysis.CSharp; |
|||
using XamlNameReferenceGenerator.Infrastructure; |
|||
using Xunit; |
|||
|
|||
namespace XamlNameReferenceGenerator.Tests |
|||
{ |
|||
public class NameResolverTests |
|||
{ |
|||
private const string NamedControl = "NamedControl.xml"; |
|||
private const string NamedControls = "NamedControls.xml"; |
|||
private const string XNamedControl = "xNamedControl.xml"; |
|||
private const string XNamedControls = "xNamedControls.xml"; |
|||
private const string NoNamedControls = "NoNamedControls.xml"; |
|||
private const string CustomControls = "CustomControls.xml"; |
|||
private const string DataTemplates = "DataTemplates.xml"; |
|||
private const string SignUpView = "SignUpView.xml"; |
|||
|
|||
[Theory] |
|||
[InlineData(NamedControl)] |
|||
[InlineData(XNamedControl)] |
|||
public async Task Should_Resolve_Types_From_Avalonia_Markup_File_With_Named_Control(string resource) |
|||
{ |
|||
var xaml = await LoadEmbeddedResource(resource); |
|||
var compilation = CreateAvaloniaCompilation(); |
|||
var resolver = new NameResolver(compilation); |
|||
var controls = resolver.ResolveNames(xaml); |
|||
|
|||
Assert.NotEmpty(controls); |
|||
Assert.Equal(1, controls.Count); |
|||
Assert.Equal("UserNameTextBox", controls[0].Name); |
|||
Assert.Equal(typeof(TextBox).FullName, controls[0].TypeName); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(NamedControls)] |
|||
[InlineData(XNamedControls)] |
|||
public async Task Should_Resolve_Types_From_Avalonia_Markup_File_With_Named_Controls(string resource) |
|||
{ |
|||
var xaml = await LoadEmbeddedResource(resource); |
|||
var compilation = CreateAvaloniaCompilation(); |
|||
var resolver = new NameResolver(compilation); |
|||
var controls = resolver.ResolveNames(xaml); |
|||
|
|||
Assert.NotEmpty(controls); |
|||
Assert.Equal(3, controls.Count); |
|||
Assert.Equal("UserNameTextBox", controls[0].Name); |
|||
Assert.Equal("PasswordTextBox", controls[1].Name); |
|||
Assert.Equal("SignUpButton", controls[2].Name); |
|||
Assert.Equal(typeof(TextBox).FullName, controls[0].TypeName); |
|||
Assert.Equal(typeof(TextBox).FullName, controls[1].TypeName); |
|||
Assert.Equal(typeof(Button).FullName, controls[2].TypeName); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Resolve_Types_From_Avalonia_Markup_File_With_Custom_Controls() |
|||
{ |
|||
var xaml = await LoadEmbeddedResource(CustomControls); |
|||
var compilation = CreateAvaloniaCompilation(); |
|||
var resolver = new NameResolver(compilation); |
|||
var controls = resolver.ResolveNames(xaml); |
|||
|
|||
Assert.NotEmpty(controls); |
|||
Assert.Equal(2, controls.Count); |
|||
Assert.Equal("ClrNamespaceRoutedViewHost", controls[0].Name); |
|||
Assert.Equal("UriRoutedViewHost", controls[1].Name); |
|||
Assert.Equal(typeof(RoutedViewHost).FullName, controls[0].TypeName); |
|||
Assert.Equal(typeof(RoutedViewHost).FullName, controls[1].TypeName); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Not_Resolve_Named_Controls_From_Avalonia_Markup_File_Without_Named_Controls() |
|||
{ |
|||
var xaml = await LoadEmbeddedResource(NoNamedControls); |
|||
var compilation = CreateAvaloniaCompilation(); |
|||
var resolver = new NameResolver(compilation); |
|||
var controls = resolver.ResolveNames(xaml); |
|||
|
|||
Assert.Empty(controls); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Not_Resolve_Elements_From_DataTemplates() |
|||
{ |
|||
var xaml = await LoadEmbeddedResource(DataTemplates); |
|||
var compilation = CreateAvaloniaCompilation(); |
|||
var resolver = new NameResolver(compilation); |
|||
var controls = resolver.ResolveNames(xaml); |
|||
|
|||
Assert.NotEmpty(controls); |
|||
Assert.Equal(2, controls.Count); |
|||
Assert.Equal("UserNameTextBox", controls[0].Name); |
|||
Assert.Equal("NamedListBox", controls[1].Name); |
|||
Assert.Equal(typeof(TextBox).FullName, controls[0].TypeName); |
|||
Assert.Equal(typeof(ListBox).FullName, controls[1].TypeName); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Resolve_Names_From_Complex_Views() |
|||
{ |
|||
var compilation = |
|||
CreateAvaloniaCompilation() |
|||
.AddSyntaxTrees( |
|||
CSharpSyntaxTree.ParseText( |
|||
"using Avalonia.Controls;" + |
|||
"namespace Controls {" + |
|||
" public class CustomTextBox : TextBox { }" + |
|||
"}")); |
|||
|
|||
var xaml = await LoadEmbeddedResource(SignUpView); |
|||
var resolver = new NameResolver(compilation); |
|||
var controls = resolver.ResolveNames(xaml); |
|||
|
|||
Assert.NotEmpty(controls); |
|||
Assert.Equal(9, controls.Count); |
|||
Assert.Equal("UserNameTextBox", controls[0].Name); |
|||
Assert.Equal("UserNameValidation", controls[1].Name); |
|||
Assert.Equal("PasswordTextBox", controls[2].Name); |
|||
Assert.Equal("PasswordValidation", controls[3].Name); |
|||
Assert.Equal("AwesomeListView", controls[4].Name); |
|||
Assert.Equal("ConfirmPasswordTextBox", controls[5].Name); |
|||
Assert.Equal("ConfirmPasswordValidation", controls[6].Name); |
|||
Assert.Equal("SignUpButton", controls[7].Name); |
|||
Assert.Equal("CompoundValidation", controls[8].Name); |
|||
} |
|||
|
|||
private static CSharpCompilation CreateAvaloniaCompilation(string name = "AvaloniaCompilation2") |
|||
{ |
|||
var compilation = CSharpCompilation |
|||
.Create(name, options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)) |
|||
.AddReferences(MetadataReference.CreateFromFile(typeof(string).Assembly.Location)) |
|||
.AddReferences(MetadataReference.CreateFromFile(typeof(IServiceProvider).Assembly.Location)) |
|||
.AddReferences(MetadataReference.CreateFromFile(typeof(ITypeDescriptorContext).Assembly.Location)) |
|||
.AddReferences(MetadataReference.CreateFromFile(typeof(ISupportInitialize).Assembly.Location)) |
|||
.AddReferences(MetadataReference.CreateFromFile(typeof(TypeConverterAttribute).Assembly.Location)); |
|||
|
|||
var avaloniaAssemblyLocation = typeof(TextBlock).Assembly.Location; |
|||
var avaloniaAssemblyDirectory = Path.GetDirectoryName(avaloniaAssemblyLocation); |
|||
var avaloniaAssemblyReferences = Directory |
|||
.EnumerateFiles(avaloniaAssemblyDirectory!) |
|||
.Where(file => file.EndsWith(".dll") && file.Contains("Avalonia")) |
|||
.Select(file => MetadataReference.CreateFromFile(file)) |
|||
.ToList(); |
|||
|
|||
return compilation.AddReferences(avaloniaAssemblyReferences); |
|||
} |
|||
|
|||
private static async Task<string> LoadEmbeddedResource(string shortResourceName) |
|||
{ |
|||
var assembly = typeof(NameResolverTests).Assembly; |
|||
var fullResourceName = assembly |
|||
.GetManifestResourceNames() |
|||
.First(name => name.EndsWith(shortResourceName)); |
|||
|
|||
await using var stream = assembly.GetManifestResourceStream(fullResourceName); |
|||
using var reader = new StreamReader(stream!); |
|||
return await reader.ReadToEndAsync(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
<Window xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
xmlns:custom="clr-namespace:Avalonia.ReactiveUI;assembly=Avalonia.ReactiveUI" |
|||
xmlns:rxui="http://reactiveui.net"> |
|||
<custom:RoutedViewHost Name="ClrNamespaceRoutedViewHost" /> |
|||
<rxui:RoutedViewHost Name="UriRoutedViewHost" /> |
|||
</Window> |
|||
@ -0,0 +1,17 @@ |
|||
<Window xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> |
|||
<StackPanel> |
|||
<TextBox x:Name="UserNameTextBox" |
|||
Watermark="Username input" |
|||
UseFloatingWatermark="True" /> |
|||
<ListBox Name="NamedListBox"> |
|||
<ListBox.ItemTemplate> |
|||
<DataTemplate x:Name="NamedDataTemplate"> |
|||
<TextBox x:Name="TemplatedTextBox" |
|||
Watermark="Templated input" |
|||
UseFloatingWatermark="True" /> |
|||
</DataTemplate> |
|||
</ListBox.ItemTemplate> |
|||
</ListBox> |
|||
</StackPanel> |
|||
</Window> |
|||
@ -0,0 +1,6 @@ |
|||
<Window xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> |
|||
<TextBox Name="UserNameTextBox" |
|||
Watermark="Username input" |
|||
UseFloatingWatermark="True" /> |
|||
</Window> |
|||
@ -0,0 +1,13 @@ |
|||
<Window xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> |
|||
<StackPanel> |
|||
<TextBox Name="UserNameTextBox" |
|||
Watermark="Username input" |
|||
UseFloatingWatermark="True" /> |
|||
<TextBox Name="PasswordTextBox" |
|||
Watermark="Password input" |
|||
UseFloatingWatermark="True" /> |
|||
<Button Name="SignUpButton" |
|||
Content="Sign up" /> |
|||
</StackPanel> |
|||
</Window> |
|||
@ -0,0 +1,5 @@ |
|||
<Window xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> |
|||
<TextBox Watermark="Username input" |
|||
UseFloatingWatermark="True" /> |
|||
</Window> |
|||
@ -0,0 +1,44 @@ |
|||
<Window xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
xmlns:controls="clr-namespace:Controls"> |
|||
<StackPanel> |
|||
<controls:CustomTextBox Margin="0 10 0 0" |
|||
Name="UserNameTextBox" |
|||
Watermark="Please, enter user name..." |
|||
UseFloatingWatermark="True" /> |
|||
<TextBlock Name="UserNameValidation" |
|||
Foreground="Red" |
|||
FontSize="12" /> |
|||
<TextBox Margin="0 10 0 0" |
|||
Name="PasswordTextBox" |
|||
Watermark="Please, enter your password..." |
|||
UseFloatingWatermark="True" |
|||
PasswordChar="*" /> |
|||
<TextBlock Name="PasswordValidation" |
|||
Foreground="Red" |
|||
FontSize="12" /> |
|||
<ListBox x:Name="AwesomeListView"> |
|||
<ListBox.ItemTemplate> |
|||
<DataTemplate> |
|||
<TextBlock x:Name="MeaningLessName" Text="{Binding}" /> |
|||
</DataTemplate> |
|||
</ListBox.ItemTemplate> |
|||
</ListBox> |
|||
<TextBox Margin="0 10 0 0" |
|||
x:Name="ConfirmPasswordTextBox" |
|||
Watermark="Please, confirm the password..." |
|||
UseFloatingWatermark="True" |
|||
PasswordChar="*" /> |
|||
<TextBlock x:Name="ConfirmPasswordValidation" |
|||
TextWrapping="Wrap" |
|||
Foreground="Red" |
|||
FontSize="12" /> |
|||
<Button Margin="0 10 0 5" |
|||
Content="Sign up" |
|||
x:Name="SignUpButton" /> |
|||
<TextBlock x:Name="CompoundValidation" |
|||
TextWrapping="Wrap" |
|||
Foreground="Red" |
|||
FontSize="12" /> |
|||
</StackPanel> |
|||
</Window> |
|||
@ -0,0 +1,6 @@ |
|||
<Window xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> |
|||
<TextBox x:Name="UserNameTextBox" |
|||
Watermark="Username input" |
|||
UseFloatingWatermark="True" /> |
|||
</Window> |
|||
@ -0,0 +1,13 @@ |
|||
<Window xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> |
|||
<StackPanel> |
|||
<TextBox x:Name="UserNameTextBox" |
|||
Watermark="Username input" |
|||
UseFloatingWatermark="True" /> |
|||
<TextBox x:Name="PasswordTextBox" |
|||
Watermark="Password input" |
|||
UseFloatingWatermark="True" /> |
|||
<Button x:Name="SignUpButton" |
|||
Content="Sign up" /> |
|||
</StackPanel> |
|||
</Window> |
|||
@ -1,13 +0,0 @@ |
|||
using Xunit; |
|||
|
|||
namespace XamlNameReferenceGenerator.Tests |
|||
{ |
|||
public class XamlXNameReferenceXamlParserTests |
|||
{ |
|||
[Fact] |
|||
public void Should_Resolve_Named_Controls_From_Xaml_Markup() |
|||
{ |
|||
// TODO: Fix all stuff.
|
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
using XamlX.Ast; |
|||
using XamlX.Transform; |
|||
|
|||
namespace XamlNameReferenceGenerator.Infrastructure |
|||
{ |
|||
internal class DataTemplateTransformer : IXamlAstTransformer |
|||
{ |
|||
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node) |
|||
{ |
|||
if (node is XamlAstObjectNode objectNode && |
|||
objectNode.Type is XamlAstXmlTypeReference typeReference && |
|||
typeReference.Name == "DataTemplate") |
|||
objectNode.Children.Clear(); |
|||
return node; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
using System.Collections.Generic; |
|||
|
|||
namespace XamlNameReferenceGenerator.Infrastructure |
|||
{ |
|||
internal interface INameResolver |
|||
{ |
|||
IReadOnlyList<(string TypeName, string Name)> ResolveNames(string xaml); |
|||
} |
|||
} |
|||
@ -1,9 +0,0 @@ |
|||
using System.Collections.Generic; |
|||
|
|||
namespace XamlNameReferenceGenerator.Parsers |
|||
{ |
|||
internal interface INameReferenceXamlParser |
|||
{ |
|||
IReadOnlyList<(string TypeName, string Name)> GetNamedControls(string xaml); |
|||
} |
|||
} |
|||
@ -1,46 +0,0 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Xml; |
|||
|
|||
namespace XamlNameReferenceGenerator.Parsers |
|||
{ |
|||
internal class XmlDocumentNameReferenceXamlParser : INameReferenceXamlParser |
|||
{ |
|||
public IReadOnlyList<(string TypeName, string Name)> GetNamedControls(string xaml) |
|||
{ |
|||
var document = new XmlDocument(); |
|||
document.LoadXml(xaml); |
|||
|
|||
var names = new List<(string TypeName, string Name)>(); |
|||
IterateThroughAllNodes(document, node => |
|||
{ |
|||
var type = node.Name; |
|||
if (type.Contains(":")) |
|||
type = type.Split(':')[1]; |
|||
|
|||
var name = node.Attributes?["x:Name"]?.Value ?? |
|||
node.Attributes?["Name"]?.Value; |
|||
if (!string.IsNullOrWhiteSpace(name)) |
|||
names.Add((type, name)); |
|||
}); |
|||
return names; |
|||
} |
|||
|
|||
private static void IterateThroughAllNodes(XmlDocument doc, Action<XmlNode> elementVisitor) |
|||
{ |
|||
foreach (XmlNode node in doc.ChildNodes) |
|||
{ |
|||
IterateNode(node, elementVisitor); |
|||
} |
|||
} |
|||
|
|||
private static void IterateNode(XmlNode node, Action<XmlNode> elementVisitor) |
|||
{ |
|||
elementVisitor(node); |
|||
foreach (XmlNode childNode in node.ChildNodes) |
|||
{ |
|||
IterateNode(childNode, elementVisitor); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue