From af186e35296d396ed74d902c466cda1f4c020741 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 7 Jun 2018 14:44:41 -0500 Subject: [PATCH] Move string-based binding paths up to Avalonia.Markup. Make the LINQ Expression paths and raw ExpressionNodes (now public) the primarily supported syntax. --- .../Data/Core/AvaloniaPropertyAccessorNode.cs | 45 +++++++++++ .../Data/Core/EmptyExpressionNode.cs | 2 +- src/Avalonia.Base/Data/Core/ExpressionNode.cs | 2 +- .../Data/Core/ExpressionNodeBuilder.cs | 38 ---------- .../Data/Core/ExpressionObserver.cs | 74 +++--------------- .../Data/Core/IndexerNodeBase.cs | 2 +- src/Avalonia.Base/Data/Core/LogicalNotNode.cs | 2 +- .../Parsers/ExpressionVisitorNodeBuilder.cs | 11 ++- .../Data/Core/PropertyAccessorNode.cs | 2 +- src/Avalonia.Base/Data/Core/StreamNode.cs | 2 +- .../Converters/SelectorTypeConverter.cs | 2 +- .../MarkupExtensions/BindingExtension.cs | 1 + .../Templates/MemberSelector.cs | 3 +- .../Templates/TreeDataTemplate.cs | 3 +- src/Markup/Avalonia.Markup/Data/Binding.cs | 33 +++++--- .../Markup}/Parsers/ArgumentListParser.cs | 3 +- .../Parsers/ExpressionObserverBuilder.cs | 75 +++++++++++++++++++ .../Markup}/Parsers/ExpressionParser.cs | 30 ++++++-- .../Markup}/Parsers/IdentifierParser.cs | 2 +- .../Parsers/Nodes/StringIndexerNode.cs} | 7 +- .../Avalonia.Markup/Markup}/Parsers/Reader.cs | 2 +- .../Markup/Parsers/SelectorParser.cs | 4 +- .../Data/Core/BindingExpressionTests.cs | 41 +++++----- ...xpressionObserverTests_AttachedProperty.cs | 31 ++++++-- ...xpressionObserverTests_AvaloniaProperty.cs | 9 ++- .../ExpressionObserverTests_DataValidation.cs | 15 ++-- .../Core/ExpressionObserverTests_Indexer.cs | 45 +++++------ .../Core/ExpressionObserverTests_Lifetime.cs | 13 ++-- .../Core/ExpressionObserverTests_Method.cs | 9 ++- .../Core/ExpressionObserverTests_Negation.cs | 19 ++--- .../ExpressionObserverTests_Observable.cs | 13 ++-- .../Core/ExpressionObserverTests_Property.cs | 63 ++++++++-------- .../Core/ExpressionObserverTests_SetValue.cs | 9 ++- .../Data/Core/ExpressionObserverTests_Task.cs | 13 ++-- .../TreeViewTests.cs | 2 +- .../ExpressionObserverTests.cs | 8 +- .../Parsers}/ExpressionNodeBuilderTests.cs | 38 +++++----- .../ExpressionNodeBuilderTests_Errors.cs | 23 +++--- .../Parsers/SelectorGrammarTests.cs | 2 +- .../Parsers/SelectorParserTests.cs | 2 +- 40 files changed, 403 insertions(+), 297 deletions(-) create mode 100644 src/Avalonia.Base/Data/Core/AvaloniaPropertyAccessorNode.cs delete mode 100644 src/Avalonia.Base/Data/Core/ExpressionNodeBuilder.cs rename src/{Avalonia.Base/Data/Core => Markup/Avalonia.Markup/Markup}/Parsers/ArgumentListParser.cs (96%) create mode 100644 src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionObserverBuilder.cs rename src/{Avalonia.Base/Data/Core => Markup/Avalonia.Markup/Markup}/Parsers/ExpressionParser.cs (84%) rename src/{Avalonia.Base/Data/Core => Markup/Avalonia.Markup/Markup}/Parsers/IdentifierParser.cs (97%) rename src/{Avalonia.Base/Data/Core/IndexerNode.cs => Markup/Avalonia.Markup/Markup/Parsers/Nodes/StringIndexerNode.cs} (98%) rename src/{Avalonia.Base/Data/Core => Markup/Avalonia.Markup/Markup}/Parsers/Reader.cs (96%) rename tests/{Avalonia.Base.UnitTests/Data/Core => Avalonia.Markup.UnitTests/Parsers}/ExpressionNodeBuilderTests.cs (75%) rename tests/{Avalonia.Base.UnitTests/Data/Core => Avalonia.Markup.UnitTests/Parsers}/ExpressionNodeBuilderTests_Errors.cs (67%) diff --git a/src/Avalonia.Base/Data/Core/AvaloniaPropertyAccessorNode.cs b/src/Avalonia.Base/Data/Core/AvaloniaPropertyAccessorNode.cs new file mode 100644 index 0000000000..18e853722d --- /dev/null +++ b/src/Avalonia.Base/Data/Core/AvaloniaPropertyAccessorNode.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Reactive.Linq; +using System.Text; + +namespace Avalonia.Data.Core +{ + public class AvaloniaPropertyAccessorNode : ExpressionNode, ISettableNode + { + private readonly bool _enableValidation; + private readonly AvaloniaProperty _property; + + public AvaloniaPropertyAccessorNode(AvaloniaProperty property, bool enableValidation) + { + _property = property; + _enableValidation = enableValidation; + } + + public override string Description => PropertyName; + public string PropertyName { get; } + public Type PropertyType => _property.PropertyType; + + public bool SetTargetValue(object value, BindingPriority priority) + { + try + { + if (Target.IsAlive && Target.Target is IAvaloniaObject obj) + { + obj.SetValue(_property, value, priority); + return true; + } + return false; + } + catch + { + return false; + } + } + + protected override IObservable StartListeningCore(WeakReference reference) + { + return (reference.Target as IAvaloniaObject)?.GetWeakObservable(_property) ?? Observable.Empty(); + } + } +} diff --git a/src/Avalonia.Base/Data/Core/EmptyExpressionNode.cs b/src/Avalonia.Base/Data/Core/EmptyExpressionNode.cs index 93e0d5947a..24d4090083 100644 --- a/src/Avalonia.Base/Data/Core/EmptyExpressionNode.cs +++ b/src/Avalonia.Base/Data/Core/EmptyExpressionNode.cs @@ -6,7 +6,7 @@ using System.Reactive.Linq; namespace Avalonia.Data.Core { - internal class EmptyExpressionNode : ExpressionNode + public class EmptyExpressionNode : ExpressionNode { public override string Description => "."; diff --git a/src/Avalonia.Base/Data/Core/ExpressionNode.cs b/src/Avalonia.Base/Data/Core/ExpressionNode.cs index ae70cacdba..980a97b91c 100644 --- a/src/Avalonia.Base/Data/Core/ExpressionNode.cs +++ b/src/Avalonia.Base/Data/Core/ExpressionNode.cs @@ -9,7 +9,7 @@ using Avalonia.Data; namespace Avalonia.Data.Core { - internal abstract class ExpressionNode : ISubject + public abstract class ExpressionNode : ISubject { protected static readonly WeakReference UnsetReference = new WeakReference(AvaloniaProperty.UnsetValue); diff --git a/src/Avalonia.Base/Data/Core/ExpressionNodeBuilder.cs b/src/Avalonia.Base/Data/Core/ExpressionNodeBuilder.cs deleted file mode 100644 index b2262f32c3..0000000000 --- a/src/Avalonia.Base/Data/Core/ExpressionNodeBuilder.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) The Avalonia Project. All rights reserved. -// Licensed under the MIT license. See licence.md file in the project root for full license information. - -using System; -using Avalonia.Data.Core.Parsers; -using System.Linq.Expressions; - -namespace Avalonia.Data.Core -{ - internal static class ExpressionNodeBuilder - { - public static ExpressionNode Build(string expression, bool enableValidation = false) - { - if (string.IsNullOrWhiteSpace(expression)) - { - throw new ArgumentException("'expression' may not be empty."); - } - - var reader = new Reader(expression); - var parser = new ExpressionParser(enableValidation); - var node = parser.Parse(reader); - - if (!reader.End) - { - throw new ExpressionParseException(reader.Position, "Expected end of expression."); - } - - return node; - } - - public static ExpressionNode Build(LambdaExpression expression, bool enableValidation = false) - { - var parser = new ExpressionTreeParser(enableValidation); - - return parser.Parse(expression); - } - } -} diff --git a/src/Avalonia.Base/Data/Core/ExpressionObserver.cs b/src/Avalonia.Base/Data/Core/ExpressionObserver.cs index 32011e8ee9..9a3e299575 100644 --- a/src/Avalonia.Base/Data/Core/ExpressionObserver.cs +++ b/src/Avalonia.Base/Data/Core/ExpressionObserver.cs @@ -9,6 +9,7 @@ using System.Reactive.Disposables; using System.Reactive.Linq; using System.Reactive.Subjects; using Avalonia.Data; +using Avalonia.Data.Core.Parsers; using Avalonia.Data.Core.Plugins; namespace Avalonia.Data.Core @@ -63,23 +64,11 @@ namespace Avalonia.Data.Core /// Initializes a new instance of the class. /// /// The root object. - /// The expression. - /// Whether data validation should be enabled. + /// The expression. /// - /// A description of the expression. If null, will be used. + /// A description of the expression. If null, will be used. /// public ExpressionObserver( - object root, - string expression, - bool enableDataValidation = false, - string description = null) - : this(root, Parse(expression, enableDataValidation), description ?? expression) - { - Contract.Requires(expression != null); - Expression = expression; - } - - private ExpressionObserver( object root, ExpressionNode node, string description = null) @@ -107,25 +96,11 @@ namespace Avalonia.Data.Core /// Initializes a new instance of the class. /// /// An observable which provides the root object. - /// The expression. - /// Whether data validation should be enabled. + /// The expression. /// - /// A description of the expression. If null, will be used. + /// A description of the expression. If null, will be used. /// public ExpressionObserver( - IObservable rootObservable, - string expression, - bool enableDataValidation = false, - string description = null) - : this(rootObservable, Parse(expression, enableDataValidation), description ?? expression) - { - Contract.Requires(rootObservable != null); - Contract.Requires(expression != null); - - Expression = expression; - } - - private ExpressionObserver( IObservable rootObservable, ExpressionNode node, string description) @@ -137,7 +112,7 @@ namespace Avalonia.Data.Core _root = rootObservable; _finished = new Subject(); } - + public static ExpressionObserver CreateFromExpression( IObservable rootObservable, Expression> expression, @@ -155,26 +130,12 @@ namespace Avalonia.Data.Core /// Initializes a new instance of the class. /// /// A function which gets the root object. - /// The expression. + /// The expression. /// An observable which triggers a re-read of the getter. - /// Whether data validation should be enabled. /// - /// A description of the expression. If null, will be used. + /// A description of the expression. If null, will be used. /// public ExpressionObserver( - Func rootGetter, - string expression, - IObservable update, - bool enableDataValidation = false, - string description = null) - : this(rootGetter, Parse(expression, enableDataValidation), update, description ?? expression) - { - Contract.Requires(expression != null); - - Expression = expression; - } - - private ExpressionObserver( Func rootGetter, ExpressionNode node, IObservable update, @@ -189,8 +150,8 @@ namespace Avalonia.Data.Core _node.Target = new WeakReference(rootGetter()); _root = update.Select(x => rootGetter()); } - - public static ExpressionObserver CreateFromExpression( + + public static ExpressionObserver Create( Func rootGetter, Expression> expression, IObservable update, @@ -291,21 +252,10 @@ namespace Avalonia.Data.Core return _result.Subscribe(observer); } - private static ExpressionNode Parse(string expression, bool enableDataValidation) - { - if (!string.IsNullOrWhiteSpace(expression)) - { - return ExpressionNodeBuilder.Build(expression, enableDataValidation); - } - else - { - return new EmptyExpressionNode(); - } - } - private static ExpressionNode Parse(LambdaExpression expression, bool enableDataValidation) { - return ExpressionNodeBuilder.Build(expression, enableDataValidation); + var parser = new ExpressionTreeParser(enableDataValidation); + return parser.Parse(expression); } private static object ToWeakReference(object o) diff --git a/src/Avalonia.Base/Data/Core/IndexerNodeBase.cs b/src/Avalonia.Base/Data/Core/IndexerNodeBase.cs index e33beca6aa..6dc7d61168 100644 --- a/src/Avalonia.Base/Data/Core/IndexerNodeBase.cs +++ b/src/Avalonia.Base/Data/Core/IndexerNodeBase.cs @@ -13,7 +13,7 @@ using Avalonia.Utilities; namespace Avalonia.Data.Core { - abstract class IndexerNodeBase : ExpressionNode, ISettableNode + public abstract class IndexerNodeBase : ExpressionNode, ISettableNode { protected override IObservable StartListeningCore(WeakReference reference) { diff --git a/src/Avalonia.Base/Data/Core/LogicalNotNode.cs b/src/Avalonia.Base/Data/Core/LogicalNotNode.cs index f277005cec..20f1bcd21e 100644 --- a/src/Avalonia.Base/Data/Core/LogicalNotNode.cs +++ b/src/Avalonia.Base/Data/Core/LogicalNotNode.cs @@ -7,7 +7,7 @@ using Avalonia.Data; namespace Avalonia.Data.Core { - internal class LogicalNotNode : ExpressionNode, ITransformNode + public class LogicalNotNode : ExpressionNode, ITransformNode { public override string Description => "!"; diff --git a/src/Avalonia.Base/Data/Core/Parsers/ExpressionVisitorNodeBuilder.cs b/src/Avalonia.Base/Data/Core/Parsers/ExpressionVisitorNodeBuilder.cs index 433cfd1889..614faf0061 100644 --- a/src/Avalonia.Base/Data/Core/Parsers/ExpressionVisitorNodeBuilder.cs +++ b/src/Avalonia.Base/Data/Core/Parsers/ExpressionVisitorNodeBuilder.cs @@ -11,12 +11,14 @@ namespace Avalonia.Data.Core.Parsers class ExpressionVisitorNodeBuilder : ExpressionVisitor { private static PropertyInfo AvaloniaObjectIndexer; + private static MethodInfo CreateDelegateMethod; private readonly bool enableDataValidation; static ExpressionVisitorNodeBuilder() { AvaloniaObjectIndexer = typeof(AvaloniaObject).GetProperty("Item", new[] { typeof(AvaloniaProperty) }); + CreateDelegateMethod = typeof(MethodInfo).GetMethod("CreateDelegate", new[] { typeof(Type), typeof(object) }); } public List Nodes { get; } @@ -70,7 +72,7 @@ namespace Avalonia.Data.Core.Parsers if (node.Indexer == AvaloniaObjectIndexer) { var property = GetArgumentExpressionValue(node.Arguments[0]); - Nodes.Add(new PropertyAccessorNode($"{property.OwnerType.Name}.{property.Name}", enableDataValidation)); + Nodes.Add(new AvaloniaPropertyAccessorNode(property, enableDataValidation)); } else { @@ -166,6 +168,13 @@ namespace Avalonia.Data.Core.Parsers return Visit(Expression.MakeIndex(node.Object, property, node.Arguments)); } + if (node.Method == CreateDelegateMethod) + { + var visited = Visit(node.Arguments[1]); + Nodes.Add(new PropertyAccessorNode(GetArgumentExpressionValue(node.Object).Name, enableDataValidation)); + return visited; + } + throw new ExpressionParseException(0, $"Invalid expression type in binding expression: {node.NodeType}."); } diff --git a/src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs b/src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs index 4dbff4602f..39148e5bfd 100644 --- a/src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs +++ b/src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs @@ -10,7 +10,7 @@ using Avalonia.Data.Core.Plugins; namespace Avalonia.Data.Core { - internal class PropertyAccessorNode : ExpressionNode, ISettableNode + public class PropertyAccessorNode : ExpressionNode, ISettableNode { private readonly bool _enableValidation; private IPropertyAccessor _accessor; diff --git a/src/Avalonia.Base/Data/Core/StreamNode.cs b/src/Avalonia.Base/Data/Core/StreamNode.cs index 187c79af49..8cdece9860 100644 --- a/src/Avalonia.Base/Data/Core/StreamNode.cs +++ b/src/Avalonia.Base/Data/Core/StreamNode.cs @@ -8,7 +8,7 @@ using System.Reactive.Linq; namespace Avalonia.Data.Core { - internal class StreamNode : ExpressionNode + public class StreamNode : ExpressionNode { public override string Description => "^"; diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/SelectorTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/SelectorTypeConverter.cs index fb0131a9b4..54234fe406 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Converters/SelectorTypeConverter.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Converters/SelectorTypeConverter.cs @@ -19,7 +19,7 @@ namespace Avalonia.Markup.Xaml.Converters public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { - var parser = new SelectorParser((t, ns) => context.ResolveType(ns, t)); + var parser = new SelectorParser(context.ResolveType); return parser.Parse((string)value); } diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs index 98203deebe..c3229d814c 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs @@ -37,6 +37,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions return new Binding { + TypeResolver = descriptorContext.ResolveType, Converter = Converter, ConverterParameter = ConverterParameter, ElementName = pathInfo.ElementName ?? ElementName, diff --git a/src/Markup/Avalonia.Markup.Xaml/Templates/MemberSelector.cs b/src/Markup/Avalonia.Markup.Xaml/Templates/MemberSelector.cs index aa3c359953..fa91ab60ff 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Templates/MemberSelector.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Templates/MemberSelector.cs @@ -4,6 +4,7 @@ using Avalonia.Controls.Templates; using Avalonia.Data; using Avalonia.Data.Core; +using Avalonia.Markup.Parsers; using System; using System.Reactive.Linq; @@ -37,7 +38,7 @@ namespace Avalonia.Markup.Xaml.Templates return o; } - var expression = new ExpressionObserver(o, MemberName); + var expression = ExpressionObserverBuilder.Build(o, MemberName); object result = AvaloniaProperty.UnsetValue; expression.Subscribe(x => result = x); diff --git a/src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs b/src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs index a733ef761c..bd2b9d2efd 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs @@ -8,6 +8,7 @@ using Avalonia.Controls.Templates; using Avalonia.Data; using Avalonia.Data.Core; using Avalonia.Markup.Data; +using Avalonia.Markup.Parsers; using Avalonia.Metadata; namespace Avalonia.Markup.Xaml.Templates @@ -41,7 +42,7 @@ namespace Avalonia.Markup.Xaml.Templates { if (ItemsSource != null) { - var obs = new ExpressionObserver(item, ItemsSource.Path); + var obs = ExpressionObserverBuilder.Build(item, ItemsSource.Path); return InstancedBinding.OneWay(obs, BindingPriority.Style); } diff --git a/src/Markup/Avalonia.Markup/Data/Binding.cs b/src/Markup/Avalonia.Markup/Data/Binding.cs index 96fc2986e8..48f74f4d45 100644 --- a/src/Markup/Avalonia.Markup/Data/Binding.cs +++ b/src/Markup/Avalonia.Markup/Data/Binding.cs @@ -10,6 +10,7 @@ using Avalonia.Controls; using Avalonia.Data.Converters; using Avalonia.Data.Core; using Avalonia.LogicalTree; +using Avalonia.Markup.Parsers; using Avalonia.VisualTree; namespace Avalonia.Data @@ -86,6 +87,11 @@ namespace Avalonia.Data public WeakReference DefaultAnchor { get; set; } + /// + /// Gets or sets a function used to resolve types from names in the binding path. + /// + public Func TypeResolver { get; set; } + /// public InstancedBinding Initiate( IAvaloniaObject target, @@ -193,20 +199,22 @@ namespace Avalonia.Data var update = target.GetObservable(StyledElement.DataContextProperty) .Skip(1) .Select(_ => Unit.Default); - var result = new ExpressionObserver( + var result = ExpressionObserverBuilder.Build( () => target.GetValue(StyledElement.DataContextProperty), path, update, - enableDataValidation); + enableDataValidation, + typeResolver: TypeResolver); return result; } else { - return new ExpressionObserver( + return ExpressionObserverBuilder.Build( GetParentDataContext(target), path, - enableDataValidation); + enableDataValidation, + typeResolver: TypeResolver); } } @@ -219,11 +227,12 @@ namespace Avalonia.Data Contract.Requires(target != null); var description = $"#{elementName}.{path}"; - var result = new ExpressionObserver( + var result = ExpressionObserverBuilder.Build( ControlLocator.Track(target, elementName), path, enableDataValidation, - description); + description, + typeResolver: TypeResolver); return result; } @@ -255,10 +264,11 @@ namespace Avalonia.Data throw new InvalidOperationException("Invalid tree to traverse."); } - return new ExpressionObserver( + return ExpressionObserverBuilder.Build( controlLocator, path, - enableDataValidation); + enableDataValidation, + typeResolver: TypeResolver); } private ExpressionObserver CreateSourceObserver( @@ -268,7 +278,7 @@ namespace Avalonia.Data { Contract.Requires(source != null); - return new ExpressionObserver(source, path, enableDataValidation); + return ExpressionObserverBuilder.Build(source, path, enableDataValidation, typeResolver: TypeResolver); } private ExpressionObserver CreateTemplatedParentObserver( @@ -282,11 +292,12 @@ namespace Avalonia.Data .Skip(1) .Select(_ => Unit.Default); - var result = new ExpressionObserver( + var result = ExpressionObserverBuilder.Build( () => target.GetValue(StyledElement.TemplatedParentProperty), path, update, - enableDataValidation); + enableDataValidation, + typeResolver: TypeResolver); return result; } diff --git a/src/Avalonia.Base/Data/Core/Parsers/ArgumentListParser.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/ArgumentListParser.cs similarity index 96% rename from src/Avalonia.Base/Data/Core/Parsers/ArgumentListParser.cs rename to src/Markup/Avalonia.Markup/Markup/Parsers/ArgumentListParser.cs index 17200a62b1..ae48657c01 100644 --- a/src/Avalonia.Base/Data/Core/Parsers/ArgumentListParser.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/ArgumentListParser.cs @@ -1,11 +1,12 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using Avalonia.Data.Core; using System; using System.Collections.Generic; using System.Text; -namespace Avalonia.Data.Core.Parsers +namespace Avalonia.Markup.Parsers { internal static class ArgumentListParser { diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionObserverBuilder.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionObserverBuilder.cs new file mode 100644 index 0000000000..7141a62cd9 --- /dev/null +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionObserverBuilder.cs @@ -0,0 +1,75 @@ +using Avalonia.Data.Core; +using System; +using System.Collections.Generic; +using System.Reactive; +using System.Text; + +namespace Avalonia.Markup.Parsers +{ + public class ExpressionObserverBuilder + { + internal static ExpressionNode Parse(string expression, bool enableValidation = false, Func typeResolver = null) + { + if (string.IsNullOrWhiteSpace(expression)) + { + return new EmptyExpressionNode(); + } + + var reader = new Reader(expression); + var parser = new ExpressionParser(enableValidation, typeResolver); + var node = parser.Parse(reader); + + if (!reader.End) + { + throw new ExpressionParseException(reader.Position, "Expected end of expression."); + } + + return node; + } + + public static ExpressionObserver Build( + object root, + string expression, + bool enableDataValidation = false, + string description = null, + Func typeResolver = null) + { + return new ExpressionObserver( + root, + Parse(expression, enableDataValidation, typeResolver), + description ?? expression); + } + + public static ExpressionObserver Build( + IObservable rootObservable, + string expression, + bool enableDataValidation = false, + string description = null, + Func typeResolver = null) + { + Contract.Requires(rootObservable != null); + return new ExpressionObserver( + rootObservable, + Parse(expression, enableDataValidation, typeResolver), + description ?? expression); + } + + + public static ExpressionObserver Build( + Func rootGetter, + string expression, + IObservable update, + bool enableDataValidation = false, + string description = null, + Func typeResolver = null) + { + Contract.Requires(rootGetter != null); + + return new ExpressionObserver( + () => rootGetter(), + Parse(expression, enableDataValidation, typeResolver), + update, + description ?? expression); + } + } +} diff --git a/src/Avalonia.Base/Data/Core/Parsers/ExpressionParser.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionParser.cs similarity index 84% rename from src/Avalonia.Base/Data/Core/Parsers/ExpressionParser.cs rename to src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionParser.cs index 5c74c5cd13..6919eeeb0d 100644 --- a/src/Avalonia.Base/Data/Core/Parsers/ExpressionParser.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionParser.cs @@ -1,18 +1,22 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using Avalonia.Data.Core; +using Avalonia.Markup.Parsers.Nodes; using System; using System.Collections.Generic; using System.Linq; -namespace Avalonia.Data.Core.Parsers +namespace Avalonia.Markup.Parsers { internal class ExpressionParser { - private bool _enableValidation; + private readonly bool _enableValidation; + private readonly Func _typeResolver; - public ExpressionParser(bool enableValidation) + public ExpressionParser(bool enableValidation, Func typeResolver) { + _typeResolver = typeResolver; _enableValidation = enableValidation; } @@ -130,7 +134,19 @@ namespace Avalonia.Data.Core.Parsers private State ParseAttachedProperty(Reader r, List nodes) { - var owner = IdentifierParser.Parse(r); + string ns = string.Empty; + string owner; + var ownerOrNamespace = IdentifierParser.Parse(r); + + if (r.TakeIf(':')) + { + ns = ownerOrNamespace; + owner = IdentifierParser.Parse(r); + } + else + { + owner = ownerOrNamespace; + } if (r.End || !r.TakeIf('.')) { @@ -144,7 +160,9 @@ namespace Avalonia.Data.Core.Parsers throw new ExpressionParseException(r.Position, "Expected ')'."); } - nodes.Add(new PropertyAccessorNode(owner + '.' + name, _enableValidation)); + var property = AvaloniaPropertyRegistry.Instance.FindRegistered(_typeResolver(ns, owner), name); + + nodes.Add(new AvaloniaPropertyAccessorNode(property, _enableValidation)); return State.AfterMember; } @@ -157,7 +175,7 @@ namespace Avalonia.Data.Core.Parsers throw new ExpressionParseException(r.Position, "Indexer may not be empty."); } - nodes.Add(new IndexerNode(args)); + nodes.Add(new StringIndexerNode(args)); return State.AfterMember; } diff --git a/src/Avalonia.Base/Data/Core/Parsers/IdentifierParser.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/IdentifierParser.cs similarity index 97% rename from src/Avalonia.Base/Data/Core/Parsers/IdentifierParser.cs rename to src/Markup/Avalonia.Markup/Markup/Parsers/IdentifierParser.cs index b0a9ff4df2..f86f2db321 100644 --- a/src/Avalonia.Base/Data/Core/Parsers/IdentifierParser.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/IdentifierParser.cs @@ -4,7 +4,7 @@ using System.Globalization; using System.Text; -namespace Avalonia.Data.Core.Parsers +namespace Avalonia.Markup.Parsers { internal static class IdentifierParser { diff --git a/src/Avalonia.Base/Data/Core/IndexerNode.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/StringIndexerNode.cs similarity index 98% rename from src/Avalonia.Base/Data/Core/IndexerNode.cs rename to src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/StringIndexerNode.cs index 251b47c24c..01eb0a9b53 100644 --- a/src/Avalonia.Base/Data/Core/IndexerNode.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/StringIndexerNode.cs @@ -12,12 +12,13 @@ using System.Linq; using System.Reflection; using System.Reactive.Linq; using Avalonia.Data; +using Avalonia.Data.Core; -namespace Avalonia.Data.Core +namespace Avalonia.Markup.Parsers.Nodes { - internal class IndexerNode : IndexerNodeBase + internal class StringIndexerNode : IndexerNodeBase { - public IndexerNode(IList arguments) + public StringIndexerNode(IList arguments) { Arguments = arguments; } diff --git a/src/Avalonia.Base/Data/Core/Parsers/Reader.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/Reader.cs similarity index 96% rename from src/Avalonia.Base/Data/Core/Parsers/Reader.cs rename to src/Markup/Avalonia.Markup/Markup/Parsers/Reader.cs index 14187c769a..9355bc9aa3 100644 --- a/src/Avalonia.Base/Data/Core/Parsers/Reader.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/Reader.cs @@ -3,7 +3,7 @@ using System; -namespace Avalonia.Data.Core.Parsers +namespace Avalonia.Markup.Parsers { internal class Reader { diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs index e50056ddef..bb76387e61 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs @@ -52,11 +52,11 @@ namespace Avalonia.Markup.Parsers if (ofType != null) { - result = result.OfType(_typeResolver(ofType.TypeName, ofType.Xmlns)); + result = result.OfType(_typeResolver(ofType.Xmlns, ofType.TypeName)); } if (@is != null) { - result = result.Is(_typeResolver(@is.TypeName, @is.Xmlns)); + result = result.Is(_typeResolver(@is.Xmlns, @is.TypeName)); } else if (@class != null) { diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.cs b/tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.cs index 6b71d28e22..4e595684df 100644 --- a/tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using Avalonia.Data; using Avalonia.Data.Converters; using Avalonia.Data.Core; +using Avalonia.Markup.Parsers; using Avalonia.UnitTests; using Moq; using Xunit; @@ -22,7 +23,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public async Task Should_Get_Simple_Property_Value() { var data = new Class1 { StringValue = "foo" }; - var target = new BindingExpression(new ExpressionObserver(data, "StringValue"), typeof(string)); + var target = new BindingExpression(ExpressionObserverBuilder.Build(data, "StringValue"), typeof(string)); var result = await target.Take(1); Assert.Equal("foo", result); @@ -34,7 +35,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public void Should_Set_Simple_Property_Value() { var data = new Class1 { StringValue = "foo" }; - var target = new BindingExpression(new ExpressionObserver(data, "StringValue"), typeof(string)); + var target = new BindingExpression(ExpressionObserverBuilder.Build(data, "StringValue"), typeof(string)); target.OnNext("bar"); @@ -47,7 +48,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public void Should_Set_Indexed_Value() { var data = new { Foo = new[] { "foo" } }; - var target = new BindingExpression(new ExpressionObserver(data, "Foo[0]"), typeof(string)); + var target = new BindingExpression(ExpressionObserverBuilder.Build(data, "Foo[0]"), typeof(string)); target.OnNext("bar"); @@ -60,7 +61,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public async Task Should_Convert_Get_String_To_Double() { var data = new Class1 { StringValue = $"{5.6}" }; - var target = new BindingExpression(new ExpressionObserver(data, "StringValue"), typeof(double)); + var target = new BindingExpression(ExpressionObserverBuilder.Build(data, "StringValue"), typeof(double)); var result = await target.Take(1); Assert.Equal(5.6, result); @@ -72,7 +73,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public async Task Getting_Invalid_Double_String_Should_Return_BindingError() { var data = new Class1 { StringValue = "foo" }; - var target = new BindingExpression(new ExpressionObserver(data, "StringValue"), typeof(double)); + var target = new BindingExpression(ExpressionObserverBuilder.Build(data, "StringValue"), typeof(double)); var result = await target.Take(1); Assert.IsType(result); @@ -84,7 +85,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public async Task Should_Coerce_Get_Null_Double_String_To_UnsetValue() { var data = new Class1 { StringValue = null }; - var target = new BindingExpression(new ExpressionObserver(data, "StringValue"), typeof(double)); + var target = new BindingExpression(ExpressionObserverBuilder.Build(data, "StringValue"), typeof(double)); var result = await target.Take(1); Assert.Equal(AvaloniaProperty.UnsetValue, result); @@ -96,7 +97,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public void Should_Convert_Set_String_To_Double() { var data = new Class1 { StringValue = $"{5.6}" }; - var target = new BindingExpression(new ExpressionObserver(data, "StringValue"), typeof(double)); + var target = new BindingExpression(ExpressionObserverBuilder.Build(data, "StringValue"), typeof(double)); target.OnNext(6.7); @@ -109,7 +110,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public async Task Should_Convert_Get_Double_To_String() { var data = new Class1 { DoubleValue = 5.6 }; - var target = new BindingExpression(new ExpressionObserver(data, "DoubleValue"), typeof(string)); + var target = new BindingExpression(ExpressionObserverBuilder.Build(data, "DoubleValue"), typeof(string)); var result = await target.Take(1); Assert.Equal($"{5.6}", result); @@ -121,7 +122,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public void Should_Convert_Set_Double_To_String() { var data = new Class1 { DoubleValue = 5.6 }; - var target = new BindingExpression(new ExpressionObserver(data, "DoubleValue"), typeof(string)); + var target = new BindingExpression(ExpressionObserverBuilder.Build(data, "DoubleValue"), typeof(string)); target.OnNext($"{6.7}"); @@ -135,7 +136,7 @@ namespace Avalonia.Base.UnitTests.Data.Core { var data = new Class1 { StringValue = "foo" }; var target = new BindingExpression( - new ExpressionObserver(data, "StringValue"), + ExpressionObserverBuilder.Build(data, "StringValue"), typeof(int), 42, DefaultValueConverter.Instance); @@ -156,7 +157,7 @@ namespace Avalonia.Base.UnitTests.Data.Core { var data = new Class1 { StringValue = "foo" }; var target = new BindingExpression( - new ExpressionObserver(data, "StringValue", true), + ExpressionObserverBuilder.Build(data, "StringValue", true), typeof(int), 42, DefaultValueConverter.Instance); @@ -177,7 +178,7 @@ namespace Avalonia.Base.UnitTests.Data.Core { var data = new Class1 { StringValue = "foo" }; var target = new BindingExpression( - new ExpressionObserver(data, "StringValue"), + ExpressionObserverBuilder.Build(data, "StringValue"), typeof(int), "bar", DefaultValueConverter.Instance); @@ -199,7 +200,7 @@ namespace Avalonia.Base.UnitTests.Data.Core { var data = new Class1 { StringValue = "foo" }; var target = new BindingExpression( - new ExpressionObserver(data, "StringValue", true), + ExpressionObserverBuilder.Build(data, "StringValue", true), typeof(int), "bar", DefaultValueConverter.Instance); @@ -220,7 +221,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public void Setting_Invalid_Double_String_Should_Not_Change_Target() { var data = new Class1 { DoubleValue = 5.6 }; - var target = new BindingExpression(new ExpressionObserver(data, "DoubleValue"), typeof(string)); + var target = new BindingExpression(ExpressionObserverBuilder.Build(data, "DoubleValue"), typeof(string)); target.OnNext("foo"); @@ -234,7 +235,7 @@ namespace Avalonia.Base.UnitTests.Data.Core { var data = new Class1 { DoubleValue = 5.6 }; var target = new BindingExpression( - new ExpressionObserver(data, "DoubleValue"), + ExpressionObserverBuilder.Build(data, "DoubleValue"), typeof(string), "9.8", DefaultValueConverter.Instance); @@ -250,7 +251,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public void Should_Coerce_Setting_Null_Double_To_Default_Value() { var data = new Class1 { DoubleValue = 5.6 }; - var target = new BindingExpression(new ExpressionObserver(data, "DoubleValue"), typeof(string)); + var target = new BindingExpression(ExpressionObserverBuilder.Build(data, "DoubleValue"), typeof(string)); target.OnNext(null); @@ -263,7 +264,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public void Should_Coerce_Setting_UnsetValue_Double_To_Default_Value() { var data = new Class1 { DoubleValue = 5.6 }; - var target = new BindingExpression(new ExpressionObserver(data, "DoubleValue"), typeof(string)); + var target = new BindingExpression(ExpressionObserverBuilder.Build(data, "DoubleValue"), typeof(string)); target.OnNext(AvaloniaProperty.UnsetValue); @@ -279,7 +280,7 @@ namespace Avalonia.Base.UnitTests.Data.Core var converter = new Mock(); var target = new BindingExpression( - new ExpressionObserver(data, "DoubleValue"), + ExpressionObserverBuilder.Build(data, "DoubleValue"), typeof(string), converter.Object, converterParameter: "foo"); @@ -297,7 +298,7 @@ namespace Avalonia.Base.UnitTests.Data.Core var data = new Class1 { DoubleValue = 5.6 }; var converter = new Mock(); var target = new BindingExpression( - new ExpressionObserver(data, "DoubleValue"), + ExpressionObserverBuilder.Build(data, "DoubleValue"), typeof(string), converter.Object, converterParameter: "foo"); @@ -314,7 +315,7 @@ namespace Avalonia.Base.UnitTests.Data.Core { var data = new Class1 { DoubleValue = 5.6 }; var converter = new Mock(); - var target = new BindingExpression(new ExpressionObserver(data, "DoubleValue", true), typeof(string)); + var target = new BindingExpression(ExpressionObserverBuilder.Build(data, "DoubleValue", true), typeof(string)); var result = new List(); target.Subscribe(x => result.Add(x)); diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_AttachedProperty.cs b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_AttachedProperty.cs index 3ed2c0b7eb..112a7fc4d0 100644 --- a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_AttachedProperty.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_AttachedProperty.cs @@ -8,21 +8,25 @@ using System.Threading.Tasks; using Avalonia.Diagnostics; using Avalonia.Data.Core; using Xunit; +using Avalonia.Markup.Parsers; namespace Avalonia.Base.UnitTests.Data.Core { public class ExpressionObserverTests_AttachedProperty { + private readonly Func _typeResolver; + public ExpressionObserverTests_AttachedProperty() { var foo = Owner.FooProperty; + _typeResolver = (_, name) => name == "Owner" ? typeof(Owner) : null; } [Fact] public async Task Should_Get_Attached_Property_Value() { var data = new Class1(); - var target = new ExpressionObserver(data, "(Owner.Foo)"); + var target = ExpressionObserverBuilder.Build(data, "(Owner.Foo)", typeResolver: _typeResolver); var result = await target.Take(1); Assert.Equal("foo", result); @@ -30,6 +34,19 @@ namespace Avalonia.Base.UnitTests.Data.Core Assert.Null(((IAvaloniaObjectDebug)data).GetPropertyChangedSubscribers()); } + [Fact] + public async Task Should_Get_Attached_Property_Value_With_Namespace() + { + var data = new Class1(); + var target = ExpressionObserverBuilder.Build( + data, + "(NS:Owner.Foo)", + typeResolver: (ns, name) => ns == "NS" && name == "Owner" ? typeof(Owner) : null); + var result = await target.Take(1); + Assert.Equal("foo", result); + Assert.Null(((IAvaloniaObjectDebug)data).GetPropertyChangedSubscribers()); + } + [Fact] public async Task Should_Get_Chained_Attached_Property_Value() { @@ -41,7 +58,7 @@ namespace Avalonia.Base.UnitTests.Data.Core } }; - var target = new ExpressionObserver(data, "Next.(Owner.Foo)"); + var target = ExpressionObserverBuilder.Build(data, "Next.(Owner.Foo)", typeResolver: _typeResolver); var result = await target.Take(1); Assert.Equal("bar", result); @@ -53,7 +70,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public void Should_Track_Simple_Attached_Value() { var data = new Class1(); - var target = new ExpressionObserver(data, "(Owner.Foo)"); + var target = ExpressionObserverBuilder.Build(data, "(Owner.Foo)", typeResolver: _typeResolver); var result = new List(); var sub = target.Subscribe(x => result.Add(x)); @@ -77,7 +94,7 @@ namespace Avalonia.Base.UnitTests.Data.Core } }; - var target = new ExpressionObserver(data, "Next.(Owner.Foo)"); + var target = ExpressionObserverBuilder.Build(data, "Next.(Owner.Foo)", typeResolver: _typeResolver); var result = new List(); var sub = target.Subscribe(x => result.Add(x)); @@ -96,7 +113,7 @@ namespace Avalonia.Base.UnitTests.Data.Core Func> run = () => { var source = new Class1(); - var target = new ExpressionObserver(source, "(Owner.Foo)"); + var target = ExpressionObserverBuilder.Build(source, "(Owner.Foo)", typeResolver: _typeResolver); return Tuple.Create(target, new WeakReference(source)); }; @@ -113,7 +130,7 @@ namespace Avalonia.Base.UnitTests.Data.Core { var data = new Class1(); - Assert.Throws(() => new ExpressionObserver(data, "(Owner)")); + Assert.Throws(() => ExpressionObserverBuilder.Build(data, "(Owner)", typeResolver: _typeResolver)); } [Fact] @@ -121,7 +138,7 @@ namespace Avalonia.Base.UnitTests.Data.Core { var data = new Class1(); - Assert.Throws(() => new ExpressionObserver(data, "(Owner.Foo.Bar)")); + Assert.Throws(() => ExpressionObserverBuilder.Build(data, "(Owner.Foo.Bar)", typeResolver: _typeResolver)); } private static class Owner diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_AvaloniaProperty.cs b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_AvaloniaProperty.cs index bf2b6cbcb2..b34c1ff8be 100644 --- a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_AvaloniaProperty.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_AvaloniaProperty.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using Avalonia.Diagnostics; using Avalonia.Data.Core; using Xunit; +using Avalonia.Markup.Parsers; namespace Avalonia.Base.UnitTests.Data.Core { @@ -22,7 +23,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public async Task Should_Get_Simple_Property_Value() { var data = new Class1(); - var target = new ExpressionObserver(data, "Foo"); + var target = ExpressionObserverBuilder.Build(data, "Foo"); var result = await target.Take(1); Assert.Equal("foo", result); @@ -34,7 +35,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public async Task Should_Get_Simple_ClrProperty_Value() { var data = new Class1(); - var target = new ExpressionObserver(data, "ClrProperty"); + var target = ExpressionObserverBuilder.Build(data, "ClrProperty"); var result = await target.Take(1); Assert.Equal("clr-property", result); @@ -44,7 +45,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public void Should_Track_Simple_Property_Value() { var data = new Class1(); - var target = new ExpressionObserver(data, "Foo"); + var target = ExpressionObserverBuilder.Build(data, "Foo"); var result = new List(); var sub = target.Subscribe(x => result.Add(x)); @@ -63,7 +64,7 @@ namespace Avalonia.Base.UnitTests.Data.Core Func> run = () => { var source = new Class1(); - var target = new ExpressionObserver(source, "Foo"); + var target = ExpressionObserverBuilder.Build(source, "Foo"); return Tuple.Create(target, new WeakReference(source)); }; diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_DataValidation.cs b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_DataValidation.cs index 3732569753..c5f7d1d7bf 100644 --- a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_DataValidation.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_DataValidation.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Reactive.Linq; using Avalonia.Data; using Avalonia.Data.Core; +using Avalonia.Markup.Parsers; using Avalonia.UnitTests; using Xunit; @@ -19,7 +20,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public void Doesnt_Send_DataValidationError_When_DataValidatation_Not_Enabled() { var data = new ExceptionTest { MustBePositive = 5 }; - var observer = new ExpressionObserver(data, nameof(data.MustBePositive), false); + var observer = ExpressionObserverBuilder.Build(data, nameof(data.MustBePositive), false); var validationMessageFound = false; observer.OfType() @@ -36,7 +37,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public void Exception_Validation_Sends_DataValidationError() { var data = new ExceptionTest { MustBePositive = 5 }; - var observer = new ExpressionObserver(data, nameof(data.MustBePositive), true); + var observer = ExpressionObserverBuilder.Build(data, nameof(data.MustBePositive), true); var validationMessageFound = false; observer.OfType() @@ -53,7 +54,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public void Indei_Validation_Does_Not_Subscribe_When_DataValidatation_Not_Enabled() { var data = new IndeiTest { MustBePositive = 5 }; - var observer = new ExpressionObserver(data, nameof(data.MustBePositive), false); + var observer = ExpressionObserverBuilder.Build(data, nameof(data.MustBePositive), false); observer.Subscribe(_ => { }); @@ -64,7 +65,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public void Enabled_Indei_Validation_Subscribes() { var data = new IndeiTest { MustBePositive = 5 }; - var observer = new ExpressionObserver(data, nameof(data.MustBePositive), true); + var observer = ExpressionObserverBuilder.Build(data, nameof(data.MustBePositive), true); var sub = observer.Subscribe(_ => { }); Assert.Equal(1, data.ErrorsChangedSubscriptionCount); @@ -76,7 +77,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public void Validation_Plugins_Send_Correct_Notifications() { var data = new IndeiTest(); - var observer = new ExpressionObserver(data, nameof(data.MustBePositive), true); + var observer = ExpressionObserverBuilder.Build(data, nameof(data.MustBePositive), true); var result = new List(); var errmsg = string.Empty; @@ -122,7 +123,7 @@ namespace Avalonia.Base.UnitTests.Data.Core Inner = new IndeiTest() }; - var observer = new ExpressionObserver( + var observer = ExpressionObserverBuilder.Build( data, $"{nameof(Container.Inner)}.{nameof(IndeiTest.MustBePositive)}", true); @@ -142,7 +143,7 @@ namespace Avalonia.Base.UnitTests.Data.Core var container = new Container(); var inner = new IndeiTest(); - var observer = new ExpressionObserver( + var observer = ExpressionObserverBuilder.Build( container, $"{nameof(Container.Inner)}.{nameof(IndeiTest.MustBePositive)}", true); diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Indexer.cs b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Indexer.cs index 8a54f968b1..da167e5008 100644 --- a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Indexer.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Indexer.cs @@ -11,6 +11,7 @@ using Avalonia.Diagnostics; using Avalonia.Data.Core; using Avalonia.UnitTests; using Xunit; +using Avalonia.Markup.Parsers; namespace Avalonia.Base.UnitTests.Data.Core { @@ -20,7 +21,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public async Task Should_Get_Array_Value() { var data = new { Foo = new [] { "foo", "bar" } }; - var target = new ExpressionObserver(data, "Foo[1]"); + var target = ExpressionObserverBuilder.Build(data, "Foo[1]"); var result = await target.Take(1); Assert.Equal("bar", result); @@ -32,7 +33,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public async Task Should_Get_UnsetValue_For_Invalid_Array_Index() { var data = new { Foo = new[] { "foo", "bar" } }; - var target = new ExpressionObserver(data, "Foo[invalid]"); + var target = ExpressionObserverBuilder.Build(data, "Foo[invalid]"); var result = await target.Take(1); Assert.Equal(AvaloniaProperty.UnsetValue, result); @@ -44,7 +45,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public async Task Should_Get_UnsetValue_For_Invalid_Dictionary_Index() { var data = new { Foo = new Dictionary { { 1, "foo" } } }; - var target = new ExpressionObserver(data, "Foo[invalid]"); + var target = ExpressionObserverBuilder.Build(data, "Foo[invalid]"); var result = await target.Take(1); Assert.Equal(AvaloniaProperty.UnsetValue, result); @@ -56,7 +57,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public async Task Should_Get_UnsetValue_For_Object_Without_Indexer() { var data = new { Foo = 5 }; - var target = new ExpressionObserver(data, "Foo[noindexer]"); + var target = ExpressionObserverBuilder.Build(data, "Foo[noindexer]"); var result = await target.Take(1); Assert.Equal(AvaloniaProperty.UnsetValue, result); @@ -68,7 +69,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public async Task Should_Get_MultiDimensional_Array_Value() { var data = new { Foo = new[,] { { "foo", "bar" }, { "baz", "qux" } } }; - var target = new ExpressionObserver(data, "Foo[1, 1]"); + var target = ExpressionObserverBuilder.Build(data, "Foo[1, 1]"); var result = await target.Take(1); Assert.Equal("qux", result); @@ -80,7 +81,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public async Task Should_Get_Value_For_String_Indexer() { var data = new { Foo = new Dictionary { { "foo", "bar" }, { "baz", "qux" } } }; - var target = new ExpressionObserver(data, "Foo[foo]"); + var target = ExpressionObserverBuilder.Build(data, "Foo[foo]"); var result = await target.Take(1); Assert.Equal("bar", result); @@ -92,7 +93,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public async Task Should_Get_Value_For_Non_String_Indexer() { var data = new { Foo = new Dictionary { { 1.0, "bar" }, { 2.0, "qux" } } }; - var target = new ExpressionObserver(data, "Foo[1.0]"); + var target = ExpressionObserverBuilder.Build(data, "Foo[1.0]"); var result = await target.Take(1); Assert.Equal("bar", result); @@ -104,7 +105,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public async Task Array_Out_Of_Bounds_Should_Return_UnsetValue() { var data = new { Foo = new[] { "foo", "bar" } }; - var target = new ExpressionObserver(data, "Foo[2]"); + var target = ExpressionObserverBuilder.Build(data, "Foo[2]"); var result = await target.Take(1); Assert.Equal(AvaloniaProperty.UnsetValue, result); @@ -116,7 +117,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public async Task Array_With_Wrong_Dimensions_Should_Return_UnsetValue() { var data = new { Foo = new[] { "foo", "bar" } }; - var target = new ExpressionObserver(data, "Foo[1,2]"); + var target = ExpressionObserverBuilder.Build(data, "Foo[1,2]"); var result = await target.Take(1); Assert.Equal(AvaloniaProperty.UnsetValue, result); @@ -128,7 +129,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public async Task List_Out_Of_Bounds_Should_Return_UnsetValue() { var data = new { Foo = new List { "foo", "bar" } }; - var target = new ExpressionObserver(data, "Foo[2]"); + var target = ExpressionObserverBuilder.Build(data, "Foo[2]"); var result = await target.Take(1); Assert.Equal(AvaloniaProperty.UnsetValue, result); @@ -140,7 +141,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public async Task Should_Get_List_Value() { var data = new { Foo = new List { "foo", "bar" } }; - var target = new ExpressionObserver(data, "Foo[1]"); + var target = ExpressionObserverBuilder.Build(data, "Foo[1]"); var result = await target.Take(1); Assert.Equal("bar", result); @@ -152,7 +153,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public void Should_Track_INCC_Add() { var data = new { Foo = new AvaloniaList { "foo", "bar" } }; - var target = new ExpressionObserver(data, "Foo[2]"); + var target = ExpressionObserverBuilder.Build(data, "Foo[2]"); var result = new List(); using (var sub = target.Subscribe(x => result.Add(x))) @@ -170,7 +171,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public void Should_Track_INCC_Remove() { var data = new { Foo = new AvaloniaList { "foo", "bar" } }; - var target = new ExpressionObserver(data, "Foo[0]"); + var target = ExpressionObserverBuilder.Build(data, "Foo[0]"); var result = new List(); using (var sub = target.Subscribe(x => result.Add(x))) @@ -188,7 +189,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public void Should_Track_INCC_Replace() { var data = new { Foo = new AvaloniaList { "foo", "bar" } }; - var target = new ExpressionObserver(data, "Foo[1]"); + var target = ExpressionObserverBuilder.Build(data, "Foo[1]"); var result = new List(); using (var sub = target.Subscribe(x => result.Add(x))) @@ -209,7 +210,7 @@ namespace Avalonia.Base.UnitTests.Data.Core // method, but even if it did we need to test with ObservableCollection as well // as AvaloniaList as it implements PropertyChanged as an explicit interface event. var data = new { Foo = new ObservableCollection { "foo", "bar" } }; - var target = new ExpressionObserver(data, "Foo[1]"); + var target = ExpressionObserverBuilder.Build(data, "Foo[1]"); var result = new List(); var sub = target.Subscribe(x => result.Add(x)); @@ -225,7 +226,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public void Should_Track_INCC_Reset() { var data = new { Foo = new AvaloniaList { "foo", "bar" } }; - var target = new ExpressionObserver(data, "Foo[1]"); + var target = ExpressionObserverBuilder.Build(data, "Foo[1]"); var result = new List(); var sub = target.Subscribe(x => result.Add(x)); @@ -244,7 +245,7 @@ namespace Avalonia.Base.UnitTests.Data.Core data.Foo["foo"] = "bar"; data.Foo["baz"] = "qux"; - var target = new ExpressionObserver(data, "Foo[foo]"); + var target = ExpressionObserverBuilder.Build(data, "Foo[foo]"); var result = new List(); using (var sub = target.Subscribe(x => result.Add(x))) @@ -263,7 +264,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public void Should_SetArrayIndex() { var data = new { Foo = new[] { "foo", "bar" } }; - var target = new ExpressionObserver(data, "Foo[1]"); + var target = ExpressionObserverBuilder.Build(data, "Foo[1]"); using (target.Subscribe(_ => { })) { @@ -286,7 +287,7 @@ namespace Avalonia.Base.UnitTests.Data.Core } }; - var target = new ExpressionObserver(data, "Foo[foo]"); + var target = ExpressionObserverBuilder.Build(data, "Foo[foo]"); using (target.Subscribe(_ => { })) { Assert.True(target.SetValue(4)); @@ -308,7 +309,7 @@ namespace Avalonia.Base.UnitTests.Data.Core } }; - var target = new ExpressionObserver(data, "Foo[bar]"); + var target = ExpressionObserverBuilder.Build(data, "Foo[bar]"); using (target.Subscribe(_ => { })) { Assert.True(target.SetValue(4)); @@ -326,7 +327,7 @@ namespace Avalonia.Base.UnitTests.Data.Core data.Foo["foo"] = "bar"; data.Foo["baz"] = "qux"; - var target = new ExpressionObserver(data, "Foo[foo]"); + var target = ExpressionObserverBuilder.Build(data, "Foo[foo]"); using (target.Subscribe(_ => { })) { @@ -343,7 +344,7 @@ namespace Avalonia.Base.UnitTests.Data.Core { var data = new[] { 1, 2, 3 }; - var target = new ExpressionObserver(data, "[1]"); + var target = ExpressionObserverBuilder.Build(data, "[1]"); var value = await target.Take(1); diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Lifetime.cs b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Lifetime.cs index b88bf2c427..4f3980e59a 100644 --- a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Lifetime.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Lifetime.cs @@ -9,6 +9,7 @@ using System.Reactive.Subjects; using Microsoft.Reactive.Testing; using Avalonia.Data.Core; using Xunit; +using Avalonia.Markup.Parsers; namespace Avalonia.Base.UnitTests.Data.Core { @@ -18,7 +19,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public void Should_Complete_When_Source_Observable_Completes() { var source = new BehaviorSubject(1); - var target = new ExpressionObserver(source, "Foo"); + var target = ExpressionObserverBuilder.Build(source, "Foo"); var completed = false; target.Subscribe(_ => { }, () => completed = true); @@ -31,7 +32,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public void Should_Complete_When_Source_Observable_Errors() { var source = new BehaviorSubject(1); - var target = new ExpressionObserver(source, "Foo"); + var target = ExpressionObserverBuilder.Build(source, "Foo"); var completed = false; target.Subscribe(_ => { }, () => completed = true); @@ -44,7 +45,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public void Should_Complete_When_Update_Observable_Completes() { var update = new Subject(); - var target = new ExpressionObserver(() => 1, "Foo", update); + var target = ExpressionObserverBuilder.Build(() => 1, "Foo", update); var completed = false; target.Subscribe(_ => { }, () => completed = true); @@ -57,7 +58,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public void Should_Complete_When_Update_Observable_Errors() { var update = new Subject(); - var target = new ExpressionObserver(() => 1, "Foo", update); + var target = ExpressionObserverBuilder.Build(() => 1, "Foo", update); var completed = false; target.Subscribe(_ => { }, () => completed = true); @@ -72,7 +73,7 @@ namespace Avalonia.Base.UnitTests.Data.Core var scheduler = new TestScheduler(); var source = scheduler.CreateColdObservable( OnNext(1, new { Foo = "foo" })); - var target = new ExpressionObserver(source, "Foo"); + var target = ExpressionObserverBuilder.Build(source, "Foo"); var result = new List(); using (target.Subscribe(x => result.Add(x))) @@ -91,7 +92,7 @@ namespace Avalonia.Base.UnitTests.Data.Core var scheduler = new TestScheduler(); var update = scheduler.CreateColdObservable(); var data = new { Foo = "foo" }; - var target = new ExpressionObserver(() => data, "Foo", update); + var target = ExpressionObserverBuilder.Build(() => data, "Foo", update); var result = new List(); using (target.Subscribe(x => result.Add(x))) diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Method.cs b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Method.cs index ef89c2b4bd..6bb448158e 100644 --- a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Method.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Method.cs @@ -1,5 +1,6 @@ using Avalonia.Data; using Avalonia.Data.Core; +using Avalonia.Markup.Parsers; using System; using System.Collections.Generic; using System.Linq; @@ -30,7 +31,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public async Task Should_Get_Method() { var data = new TestObject(); - var observer = new ExpressionObserver(data, nameof(TestObject.MethodWithoutReturn)); + var observer = ExpressionObserverBuilder.Build(data, nameof(TestObject.MethodWithoutReturn)); var result = await observer.Take(1); Assert.NotNull(result); @@ -46,7 +47,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public async Task Should_Get_Method_WithCorrectDelegateType(string methodName, Type expectedType) { var data = new TestObject(); - var observer = new ExpressionObserver(data, methodName); + var observer = ExpressionObserverBuilder.Build(data, methodName); var result = await observer.Take(1); Assert.IsType(expectedType, result); @@ -58,7 +59,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public async Task Can_Call_Method_Returned_From_Observer() { var data = new TestObject(); - var observer = new ExpressionObserver(data, nameof(TestObject.MethodWithReturnAndParameters)); + var observer = ExpressionObserverBuilder.Build(data, nameof(TestObject.MethodWithReturnAndParameters)); var result = await observer.Take(1); var callback = (Func)result; @@ -74,7 +75,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public async Task Should_Return_Error_Notification_If_Too_Many_Parameters(string methodName) { var data = new TestObject(); - var observer = new ExpressionObserver(data, methodName); + var observer = ExpressionObserverBuilder.Build(data, methodName); var result = await observer.Take(1); Assert.IsType(result); diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Negation.cs b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Negation.cs index 556352f6ca..9ca1d07ee0 100644 --- a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Negation.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Negation.cs @@ -6,6 +6,7 @@ using System.Reactive.Linq; using System.Threading.Tasks; using Avalonia.Data; using Avalonia.Data.Core; +using Avalonia.Markup.Parsers; using Xunit; namespace Avalonia.Base.UnitTests.Data.Core @@ -16,7 +17,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public async Task Should_Negate_Boolean_Value() { var data = new { Foo = true }; - var target = new ExpressionObserver(data, "!Foo"); + var target = ExpressionObserverBuilder.Build(data, "!Foo"); var result = await target.Take(1); Assert.False((bool)result); @@ -28,7 +29,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public async Task Should_Negate_0() { var data = new { Foo = 0 }; - var target = new ExpressionObserver(data, "!Foo"); + var target = ExpressionObserverBuilder.Build(data, "!Foo"); var result = await target.Take(1); Assert.True((bool)result); @@ -40,7 +41,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public async Task Should_Negate_1() { var data = new { Foo = 1 }; - var target = new ExpressionObserver(data, "!Foo"); + var target = ExpressionObserverBuilder.Build(data, "!Foo"); var result = await target.Take(1); Assert.False((bool)result); @@ -52,7 +53,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public async Task Should_Negate_False_String() { var data = new { Foo = "false" }; - var target = new ExpressionObserver(data, "!Foo"); + var target = ExpressionObserverBuilder.Build(data, "!Foo"); var result = await target.Take(1); Assert.True((bool)result); @@ -64,7 +65,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public async Task Should_Negate_True_String() { var data = new { Foo = "True" }; - var target = new ExpressionObserver(data, "!Foo"); + var target = ExpressionObserverBuilder.Build(data, "!Foo"); var result = await target.Take(1); Assert.False((bool)result); @@ -76,7 +77,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public async Task Should_Return_BindingNotification_For_String_Not_Convertible_To_Boolean() { var data = new { Foo = "foo" }; - var target = new ExpressionObserver(data, "!Foo"); + var target = ExpressionObserverBuilder.Build(data, "!Foo"); var result = await target.Take(1); Assert.Equal( @@ -92,7 +93,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public async Task Should_Return_BindingNotification_For_Value_Not_Convertible_To_Boolean() { var data = new { Foo = new object() }; - var target = new ExpressionObserver(data, "!Foo"); + var target = ExpressionObserverBuilder.Build(data, "!Foo"); var result = await target.Take(1); Assert.Equal( @@ -108,7 +109,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public void SetValue_Should_Return_False_For_Invalid_Value() { var data = new { Foo = "foo" }; - var target = new ExpressionObserver(data, "!Foo"); + var target = ExpressionObserverBuilder.Build(data, "!Foo"); target.Subscribe(_ => { }); Assert.False(target.SetValue("bar")); @@ -120,7 +121,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public void Can_SetValue_For_Valid_Value() { var data = new Test { Foo = true }; - var target = new ExpressionObserver(data, "!Foo"); + var target = ExpressionObserverBuilder.Build(data, "!Foo"); target.Subscribe(_ => { }); Assert.True(target.SetValue(true)); diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Observable.cs b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Observable.cs index f1c39617eb..2a8ead4e48 100644 --- a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Observable.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Observable.cs @@ -7,6 +7,7 @@ using System.Reactive.Linq; using System.Reactive.Subjects; using Avalonia.Data; using Avalonia.Data.Core; +using Avalonia.Markup.Parsers; using Avalonia.UnitTests; using Xunit; @@ -21,7 +22,7 @@ namespace Avalonia.Base.UnitTests.Data.Core { var source = new BehaviorSubject("foo"); var data = new { Foo = source }; - var target = new ExpressionObserver(data, "Foo"); + var target = ExpressionObserverBuilder.Build(data, "Foo"); var result = new List(); var sub = target.Subscribe(x => result.Add(x)); @@ -41,7 +42,7 @@ namespace Avalonia.Base.UnitTests.Data.Core { var source = new BehaviorSubject("foo"); var data = new { Foo = source }; - var target = new ExpressionObserver(data, "Foo^"); + var target = ExpressionObserverBuilder.Build(data, "Foo^"); var result = new List(); var sub = target.Subscribe(x => result.Add(x)); @@ -60,7 +61,7 @@ namespace Avalonia.Base.UnitTests.Data.Core using (var sync = UnitTestSynchronizationContext.Begin()) { var data = new Class1(); - var target = new ExpressionObserver(data, "Next^.Foo"); + var target = ExpressionObserverBuilder.Build(data, "Next^.Foo"); var result = new List(); var sub = target.Subscribe(x => result.Add(x)); @@ -83,7 +84,7 @@ namespace Avalonia.Base.UnitTests.Data.Core { var source = new BehaviorSubject("foo"); var data = new { Foo = source }; - var target = new ExpressionObserver(data, "Foo^", true); + var target = ExpressionObserverBuilder.Build(data, "Foo^", true); var result = new List(); var sub = target.Subscribe(x => result.Add(x)); @@ -105,7 +106,7 @@ namespace Avalonia.Base.UnitTests.Data.Core { var data1 = new Class1(); var data2 = new Class2("foo"); - var target = new ExpressionObserver(data1, "Next^.Foo", true); + var target = ExpressionObserverBuilder.Build(data1, "Next^.Foo", true); var result = new List(); var sub = target.Subscribe(x => result.Add(x)); @@ -128,7 +129,7 @@ namespace Avalonia.Base.UnitTests.Data.Core using (var sync = UnitTestSynchronizationContext.Begin()) { var data = new Class2("foo"); - var target = new ExpressionObserver(data, "Foo^", true); + var target = ExpressionObserverBuilder.Build(data, "Foo^", true); var result = new List(); var sub = target.Subscribe(x => result.Add(x)); diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs index a3cb11114a..9381d07322 100644 --- a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs @@ -12,6 +12,7 @@ using Avalonia.Data.Core; using Avalonia.UnitTests; using Xunit; using System.Threading.Tasks; +using Avalonia.Markup.Parsers; namespace Avalonia.Base.UnitTests.Data.Core { @@ -21,7 +22,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public async Task Should_Get_Simple_Property_Value() { var data = new { Foo = "foo" }; - var target = new ExpressionObserver(data, "Foo"); + var target = ExpressionObserverBuilder.Build(data, "Foo"); var result = await target.Take(1); Assert.Equal("foo", result); @@ -33,7 +34,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public void Should_Get_Simple_Property_Value_Type() { var data = new { Foo = "foo" }; - var target = new ExpressionObserver(data, "Foo"); + var target = ExpressionObserverBuilder.Build(data, "Foo"); target.Subscribe(_ => { }); @@ -46,7 +47,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public async Task Should_Get_Simple_Property_Value_Null() { var data = new { Foo = (string)null }; - var target = new ExpressionObserver(data, "Foo"); + var target = ExpressionObserverBuilder.Build(data, "Foo"); var result = await target.Take(1); Assert.Null(result); @@ -58,7 +59,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public async Task Should_Get_Simple_Property_From_Base_Class() { var data = new Class3 { Foo = "foo" }; - var target = new ExpressionObserver(data, "Foo"); + var target = ExpressionObserverBuilder.Build(data, "Foo"); var result = await target.Take(1); Assert.Equal("foo", result); @@ -70,7 +71,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public async Task Should_Return_BindingNotification_Error_For_Root_Null() { var data = new Class3 { Foo = "foo" }; - var target = new ExpressionObserver(default(object), "Foo"); + var target = ExpressionObserverBuilder.Build(default(object), "Foo"); var result = await target.Take(1); Assert.Equal( @@ -87,7 +88,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public async Task Should_Return_BindingNotification_Error_For_Root_UnsetValue() { var data = new Class3 { Foo = "foo" }; - var target = new ExpressionObserver(AvaloniaProperty.UnsetValue, "Foo"); + var target = ExpressionObserverBuilder.Build(AvaloniaProperty.UnsetValue, "Foo"); var result = await target.Take(1); Assert.Equal( @@ -104,7 +105,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public async Task Should_Return_BindingNotification_Error_For_Observable_Root_Null() { var data = new Class3 { Foo = "foo" }; - var target = new ExpressionObserver(Observable.Return(default(object)), "Foo"); + var target = ExpressionObserverBuilder.Build(Observable.Return(default(object)), "Foo"); var result = await target.Take(1); Assert.Equal( @@ -121,7 +122,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public async void Should_Return_BindingNotification_Error_For_Observable_Root_UnsetValue() { var data = new Class3 { Foo = "foo" }; - var target = new ExpressionObserver(Observable.Return(AvaloniaProperty.UnsetValue), "Foo"); + var target = ExpressionObserverBuilder.Build(Observable.Return(AvaloniaProperty.UnsetValue), "Foo"); var result = await target.Take(1); Assert.Equal( @@ -138,7 +139,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public async Task Should_Get_Simple_Property_Chain() { var data = new { Foo = new { Bar = new { Baz = "baz" } } }; - var target = new ExpressionObserver(data, "Foo.Bar.Baz"); + var target = ExpressionObserverBuilder.Build(data, "Foo.Bar.Baz"); var result = await target.Take(1); Assert.Equal("baz", result); @@ -150,7 +151,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public void Should_Get_Simple_Property_Chain_Type() { var data = new { Foo = new { Bar = new { Baz = "baz" } } }; - var target = new ExpressionObserver(data, "Foo.Bar.Baz"); + var target = ExpressionObserverBuilder.Build(data, "Foo.Bar.Baz"); target.Subscribe(_ => { }); @@ -163,7 +164,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public async Task Should_Return_BindingNotification_Error_For_Broken_Chain() { var data = new { Foo = new { Bar = 1 } }; - var target = new ExpressionObserver(data, "Foo.Bar.Baz"); + var target = ExpressionObserverBuilder.Build(data, "Foo.Bar.Baz"); var result = await target.Take(1); Assert.IsType(result); @@ -180,7 +181,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public void Should_Return_BindingNotification_Error_For_Chain_With_Null_Value() { var data = new { Foo = default(object) }; - var target = new ExpressionObserver(data, "Foo.Bar.Baz"); + var target = ExpressionObserverBuilder.Build(data, "Foo.Bar.Baz"); var result = new List(); target.Subscribe(x => result.Add(x)); @@ -202,7 +203,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public void Should_Have_Null_ResultType_For_Broken_Chain() { var data = new { Foo = new { Bar = 1 } }; - var target = new ExpressionObserver(data, "Foo.Bar.Baz"); + var target = ExpressionObserverBuilder.Build(data, "Foo.Bar.Baz"); Assert.Null(target.ResultType); @@ -213,7 +214,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public void Should_Track_Simple_Property_Value() { var data = new Class1 { Foo = "foo" }; - var target = new ExpressionObserver(data, "Foo"); + var target = ExpressionObserverBuilder.Build(data, "Foo"); var result = new List(); var sub = target.Subscribe(x => result.Add(x)); @@ -232,7 +233,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public void Should_Trigger_PropertyChanged_On_Null_Or_Empty_String() { var data = new Class1 { Bar = "foo" }; - var target = new ExpressionObserver(data, "Bar"); + var target = ExpressionObserverBuilder.Build(data, "Bar"); var result = new List(); var sub = target.Subscribe(x => result.Add(x)); @@ -262,7 +263,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public void Should_Track_End_Of_Property_Chain_Changing() { var data = new Class1 { Next = new Class2 { Bar = "bar" } }; - var target = new ExpressionObserver(data, "Next.Bar"); + var target = ExpressionObserverBuilder.Build(data, "Next.Bar"); var result = new List(); var sub = target.Subscribe(x => result.Add(x)); @@ -283,7 +284,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public void Should_Track_Property_Chain_Changing() { var data = new Class1 { Next = new Class2 { Bar = "bar" } }; - var target = new ExpressionObserver(data, "Next.Bar"); + var target = ExpressionObserverBuilder.Build(data, "Next.Bar"); var result = new List(); var sub = target.Subscribe(x => result.Add(x)); @@ -316,7 +317,7 @@ namespace Avalonia.Base.UnitTests.Data.Core } }; - var target = new ExpressionObserver(data, "Next.Next.Bar"); + var target = ExpressionObserverBuilder.Build(data, "Next.Next.Bar"); var result = new List(); var sub = target.Subscribe(x => result.Add(x)); @@ -349,7 +350,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public void Should_Track_Property_Chain_Breaking_With_Missing_Member_Then_Mending() { var data = new Class1 { Next = new Class2 { Bar = "bar" } }; - var target = new ExpressionObserver(data, "Next.Bar"); + var target = ExpressionObserverBuilder.Build(data, "Next.Bar"); var result = new List(); var sub = target.Subscribe(x => result.Add(x)); @@ -384,7 +385,7 @@ namespace Avalonia.Base.UnitTests.Data.Core { var data = new Class1 { Foo = "foo" }; var update = new Subject(); - var target = new ExpressionObserver(() => data.Foo, "", update); + var target = ExpressionObserverBuilder.Build(() => data.Foo, "", update); var result = new List(); target.Subscribe(x => result.Add(x)); @@ -404,7 +405,7 @@ namespace Avalonia.Base.UnitTests.Data.Core var source = scheduler.CreateColdObservable( OnNext(1, new Class1 { Foo = "foo" }), OnNext(2, new Class1 { Foo = "bar" })); - var target = new ExpressionObserver(source, "Foo"); + var target = ExpressionObserverBuilder.Build(source, "Foo"); var result = new List(); using (target.Subscribe(x => result.Add(x))) @@ -420,7 +421,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public void Subscribing_Multiple_Times_Should_Return_Values_To_All() { var data = new Class1 { Foo = "foo" }; - var target = new ExpressionObserver(data, "Foo"); + var target = ExpressionObserverBuilder.Build(data, "Foo"); var result1 = new List(); var result2 = new List(); var result3 = new List(); @@ -443,7 +444,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public void Subscribing_Multiple_Times_Should_Only_Add_PropertyChanged_Handlers_Once() { var data = new Class1 { Foo = "foo" }; - var target = new ExpressionObserver(data, "Foo"); + var target = ExpressionObserverBuilder.Build(data, "Foo"); var sub1 = target.Subscribe(x => { }); var sub2 = target.Subscribe(x => { }); @@ -462,7 +463,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public void SetValue_Should_Set_Simple_Property_Value() { var data = new Class1 { Foo = "foo" }; - var target = new ExpressionObserver(data, "Foo"); + var target = ExpressionObserverBuilder.Build(data, "Foo"); using (target.Subscribe(_ => { })) { @@ -478,7 +479,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public void SetValue_Should_Set_Property_At_The_End_Of_Chain() { var data = new Class1 { Next = new Class2 { Bar = "bar" } }; - var target = new ExpressionObserver(data, "Next.Bar"); + var target = ExpressionObserverBuilder.Build(data, "Next.Bar"); using (target.Subscribe(_ => { })) { @@ -494,7 +495,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public void SetValue_Should_Return_False_For_Missing_Property() { var data = new Class1 { Next = new WithoutBar() }; - var target = new ExpressionObserver(data, "Next.Bar"); + var target = ExpressionObserverBuilder.Build(data, "Next.Bar"); using (target.Subscribe(_ => { })) { @@ -508,7 +509,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public void SetValue_Should_Notify_New_Value_With_Inpc() { var data = new Class1(); - var target = new ExpressionObserver(data, "Foo"); + var target = ExpressionObserverBuilder.Build(data, "Foo"); var result = new List(); target.Subscribe(x => result.Add(x)); @@ -523,7 +524,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public void SetValue_Should_Notify_New_Value_Without_Inpc() { var data = new Class1(); - var target = new ExpressionObserver(data, "Bar"); + var target = ExpressionObserverBuilder.Build(data, "Bar"); var result = new List(); target.Subscribe(x => result.Add(x)); @@ -538,7 +539,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public void SetValue_Should_Return_False_For_Missing_Object() { var data = new Class1(); - var target = new ExpressionObserver(data, "Next.Bar"); + var target = ExpressionObserverBuilder.Build(data, "Next.Bar"); using (target.Subscribe(_ => { })) { @@ -555,7 +556,7 @@ namespace Avalonia.Base.UnitTests.Data.Core var second = new Class1 { Foo = "bar" }; var root = first; var update = new Subject(); - var target = new ExpressionObserver(() => root, "Foo", update); + var target = ExpressionObserverBuilder.Build(() => root, "Foo", update); var result = new List(); var sub = target.Subscribe(x => result.Add(x)); @@ -589,7 +590,7 @@ namespace Avalonia.Base.UnitTests.Data.Core Func> run = () => { var source = new Class1 { Foo = "foo" }; - var target = new ExpressionObserver(source, "Foo"); + var target = ExpressionObserverBuilder.Build(source, "Foo"); return Tuple.Create(target, new WeakReference(source)); }; diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_SetValue.cs b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_SetValue.cs index a163229e26..974ac77155 100644 --- a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_SetValue.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_SetValue.cs @@ -5,6 +5,7 @@ using System; using System.Reactive.Linq; using System.Reactive.Subjects; using Avalonia.Data.Core; +using Avalonia.Markup.Parsers; using Avalonia.UnitTests; using Xunit; @@ -16,7 +17,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public void Should_Set_Simple_Property_Value() { var data = new { Foo = "foo" }; - var target = new ExpressionObserver(data, "Foo"); + var target = ExpressionObserverBuilder.Build(data, "Foo"); using (target.Subscribe(_ => { })) { @@ -30,7 +31,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public void Should_Set_Value_On_Simple_Property_Chain() { var data = new Class1 { Foo = new Class2 { Bar = "bar" } }; - var target = new ExpressionObserver(data, "Foo.Bar"); + var target = ExpressionObserverBuilder.Build(data, "Foo.Bar"); using (target.Subscribe(_ => { })) { @@ -44,7 +45,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public void Should_Not_Try_To_Set_Value_On_Broken_Chain() { var data = new Class1 { Foo = new Class2 { Bar = "bar" } }; - var target = new ExpressionObserver(data, "Foo.Bar"); + var target = ExpressionObserverBuilder.Build(data, "Foo.Bar"); // Ensure the ExpressionObserver's subscriptions are kept active. target.OfType().Subscribe(x => { }); @@ -67,7 +68,7 @@ namespace Avalonia.Base.UnitTests.Data.Core { var data = new Class1 { Foo = new Class2 { Bar = "bar" } }; var rootObservable = new BehaviorSubject(data); - var target = new ExpressionObserver(rootObservable, "Foo.Bar"); + var target = ExpressionObserverBuilder.Build(rootObservable, "Foo.Bar"); target.Subscribe(_ => { }); rootObservable.OnNext(null); diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Task.cs b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Task.cs index 3b9a23f846..69dec53d0e 100644 --- a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Task.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Task.cs @@ -7,6 +7,7 @@ using System.Reactive.Linq; using System.Threading.Tasks; using Avalonia.Data; using Avalonia.Data.Core; +using Avalonia.Markup.Parsers; using Avalonia.UnitTests; using Xunit; @@ -21,7 +22,7 @@ namespace Avalonia.Base.UnitTests.Data.Core { var tcs = new TaskCompletionSource(); var data = new { Foo = tcs.Task }; - var target = new ExpressionObserver(data, "Foo"); + var target = ExpressionObserverBuilder.Build(data, "Foo"); var result = new List(); var sub = target.Subscribe(x => result.Add(x)); @@ -41,7 +42,7 @@ namespace Avalonia.Base.UnitTests.Data.Core using (var sync = UnitTestSynchronizationContext.Begin()) { var data = new { Foo = Task.FromResult("foo") }; - var target = new ExpressionObserver(data, "Foo^"); + var target = ExpressionObserverBuilder.Build(data, "Foo^"); var result = new List(); var sub = target.Subscribe(x => result.Add(x)); @@ -59,7 +60,7 @@ namespace Avalonia.Base.UnitTests.Data.Core { var tcs = new TaskCompletionSource(); var data = new Class1(tcs.Task); - var target = new ExpressionObserver(data, "Next^.Foo"); + var target = ExpressionObserverBuilder.Build(data, "Next^.Foo"); var result = new List(); var sub = target.Subscribe(x => result.Add(x)); @@ -79,7 +80,7 @@ namespace Avalonia.Base.UnitTests.Data.Core { var tcs = new TaskCompletionSource(); var data = new { Foo = tcs.Task }; - var target = new ExpressionObserver(data, "Foo^"); + var target = ExpressionObserverBuilder.Build(data, "Foo^"); var result = new List(); var sub = target.Subscribe(x => result.Add(x)); @@ -105,7 +106,7 @@ namespace Avalonia.Base.UnitTests.Data.Core using (var sync = UnitTestSynchronizationContext.Begin()) { var data = new { Foo = TaskFromException(new NotSupportedException()) }; - var target = new ExpressionObserver(data, "Foo^"); + var target = ExpressionObserverBuilder.Build(data, "Foo^"); var result = new List(); var sub = target.Subscribe(x => result.Add(x)); @@ -130,7 +131,7 @@ namespace Avalonia.Base.UnitTests.Data.Core { var tcs = new TaskCompletionSource(); var data = new { Foo = tcs.Task }; - var target = new ExpressionObserver(data, "Foo^", true); + var target = ExpressionObserverBuilder.Build(data, "Foo^", true); var result = new List(); var sub = target.Subscribe(x => result.Add(x)); diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs index a7263cacbd..0bb2de637a 100644 --- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs @@ -515,7 +515,7 @@ namespace Avalonia.Controls.UnitTests public InstancedBinding ItemsSelector(object item) { - var obs = new ExpressionObserver(item, nameof(Node.Children)); + var obs = ExpressionObserver.CreateFromExpression(item, o => (o as Node).Children); return InstancedBinding.OneWay(obs); } diff --git a/tests/Avalonia.LeakTests/ExpressionObserverTests.cs b/tests/Avalonia.LeakTests/ExpressionObserverTests.cs index 96f9e37897..e59472dde6 100644 --- a/tests/Avalonia.LeakTests/ExpressionObserverTests.cs +++ b/tests/Avalonia.LeakTests/ExpressionObserverTests.cs @@ -24,7 +24,7 @@ namespace Avalonia.LeakTests Func run = () => { var source = new { Foo = new AvaloniaList {"foo", "bar"} }; - var target = new ExpressionObserver(source, "Foo"); + var target = ExpressionObserver.CreateFromExpression(source, o => o.Foo); target.Subscribe(_ => { }); return target; @@ -42,7 +42,7 @@ namespace Avalonia.LeakTests Func run = () => { var source = new { Foo = new AvaloniaList { "foo", "bar" } }; - var target = new ExpressionObserver(source, "Foo", true); + var target = ExpressionObserver.CreateFromExpression(source, o => o.Foo, true); target.Subscribe(_ => { }); return target; @@ -60,7 +60,7 @@ namespace Avalonia.LeakTests Func run = () => { var source = new { Foo = new NonIntegerIndexer() }; - var target = new ExpressionObserver(source, "Foo"); + var target = ExpressionObserver.CreateFromExpression(source, o => o.Foo); target.Subscribe(_ => { }); return target; @@ -78,7 +78,7 @@ namespace Avalonia.LeakTests Func run = () => { var source = new { Foo = new MethodBound() }; - var target = new ExpressionObserver(source, "Foo.A"); + var target = ExpressionObserver.CreateFromExpression(source, o => (Action)o.Foo.A); target.Subscribe(_ => { }); return target; }; diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionNodeBuilderTests.cs b/tests/Avalonia.Markup.UnitTests/Parsers/ExpressionNodeBuilderTests.cs similarity index 75% rename from tests/Avalonia.Base.UnitTests/Data/Core/ExpressionNodeBuilderTests.cs rename to tests/Avalonia.Markup.UnitTests/Parsers/ExpressionNodeBuilderTests.cs index 146b7cace1..8bb2381552 100644 --- a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionNodeBuilderTests.cs +++ b/tests/Avalonia.Markup.UnitTests/Parsers/ExpressionNodeBuilderTests.cs @@ -4,16 +4,18 @@ using System.Collections.Generic; using System.Linq; using Avalonia.Data.Core; +using Avalonia.Markup.Parsers; +using Avalonia.Markup.Parsers.Nodes; using Xunit; -namespace Avalonia.Base.UnitTests.Data.Core +namespace Avalonia.Markup.UnitTests.Parsers { - public class ExpressionNodeBuilderTests + public class ExpressionObserverBuilderTests { [Fact] public void Should_Build_Single_Property() { - var result = ToList(ExpressionNodeBuilder.Build("Foo")); + var result = ToList(ExpressionObserverBuilder.Parse("Foo")); AssertIsProperty(result[0], "Foo"); } @@ -21,7 +23,7 @@ namespace Avalonia.Base.UnitTests.Data.Core [Fact] public void Should_Build_Underscored_Property() { - var result = ToList(ExpressionNodeBuilder.Build("_Foo")); + var result = ToList(ExpressionObserverBuilder.Parse("_Foo")); AssertIsProperty(result[0], "_Foo"); } @@ -29,7 +31,7 @@ namespace Avalonia.Base.UnitTests.Data.Core [Fact] public void Should_Build_Property_With_Digits() { - var result = ToList(ExpressionNodeBuilder.Build("F0o")); + var result = ToList(ExpressionObserverBuilder.Parse("F0o")); AssertIsProperty(result[0], "F0o"); } @@ -37,7 +39,7 @@ namespace Avalonia.Base.UnitTests.Data.Core [Fact] public void Should_Build_Property_Chain() { - var result = ToList(ExpressionNodeBuilder.Build("Foo.Bar.Baz")); + var result = ToList(ExpressionObserverBuilder.Parse("Foo.Bar.Baz")); Assert.Equal(3, result.Count); AssertIsProperty(result[0], "Foo"); @@ -48,7 +50,7 @@ namespace Avalonia.Base.UnitTests.Data.Core [Fact] public void Should_Build_Negated_Property_Chain() { - var result = ToList(ExpressionNodeBuilder.Build("!Foo.Bar.Baz")); + var result = ToList(ExpressionObserverBuilder.Parse("!Foo.Bar.Baz")); Assert.Equal(4, result.Count); Assert.IsType(result[0]); @@ -60,7 +62,7 @@ namespace Avalonia.Base.UnitTests.Data.Core [Fact] public void Should_Build_Double_Negated_Property_Chain() { - var result = ToList(ExpressionNodeBuilder.Build("!!Foo.Bar.Baz")); + var result = ToList(ExpressionObserverBuilder.Parse("!!Foo.Bar.Baz")); Assert.Equal(5, result.Count); Assert.IsType(result[0]); @@ -73,29 +75,29 @@ namespace Avalonia.Base.UnitTests.Data.Core [Fact] public void Should_Build_Indexed_Property() { - var result = ToList(ExpressionNodeBuilder.Build("Foo[15]")); + var result = ToList(ExpressionObserverBuilder.Parse("Foo[15]")); Assert.Equal(2, result.Count); AssertIsProperty(result[0], "Foo"); AssertIsIndexer(result[1], "15"); - Assert.IsType(result[1]); + Assert.IsType(result[1]); } [Fact] public void Should_Build_Indexed_Property_StringIndex() { - var result = ToList(ExpressionNodeBuilder.Build("Foo[Key]")); + var result = ToList(ExpressionObserverBuilder.Parse("Foo[Key]")); Assert.Equal(2, result.Count); AssertIsProperty(result[0], "Foo"); AssertIsIndexer(result[1], "Key"); - Assert.IsType(result[1]); + Assert.IsType(result[1]); } [Fact] public void Should_Build_Multiple_Indexed_Property() { - var result = ToList(ExpressionNodeBuilder.Build("Foo[15,6]")); + var result = ToList(ExpressionObserverBuilder.Parse("Foo[15,6]")); Assert.Equal(2, result.Count); AssertIsProperty(result[0], "Foo"); @@ -105,7 +107,7 @@ namespace Avalonia.Base.UnitTests.Data.Core [Fact] public void Should_Build_Multiple_Indexed_Property_With_Space() { - var result = ToList(ExpressionNodeBuilder.Build("Foo[5, 16]")); + var result = ToList(ExpressionObserverBuilder.Parse("Foo[5, 16]")); Assert.Equal(2, result.Count); AssertIsProperty(result[0], "Foo"); @@ -115,7 +117,7 @@ namespace Avalonia.Base.UnitTests.Data.Core [Fact] public void Should_Build_Consecutive_Indexers() { - var result = ToList(ExpressionNodeBuilder.Build("Foo[15][16]")); + var result = ToList(ExpressionObserverBuilder.Parse("Foo[15][16]")); Assert.Equal(3, result.Count); AssertIsProperty(result[0], "Foo"); @@ -126,7 +128,7 @@ namespace Avalonia.Base.UnitTests.Data.Core [Fact] public void Should_Build_Indexed_Property_In_Chain() { - var result = ToList(ExpressionNodeBuilder.Build("Foo.Bar[5, 6].Baz")); + var result = ToList(ExpressionObserverBuilder.Parse("Foo.Bar[5, 6].Baz")); Assert.Equal(4, result.Count); AssertIsProperty(result[0], "Foo"); @@ -145,9 +147,9 @@ namespace Avalonia.Base.UnitTests.Data.Core private void AssertIsIndexer(ExpressionNode node, params string[] args) { - Assert.IsType(node); + Assert.IsType(node); - var e = (IndexerNode)node; + var e = (StringIndexerNode)node; Assert.Equal(e.Arguments.ToArray(), args); } diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionNodeBuilderTests_Errors.cs b/tests/Avalonia.Markup.UnitTests/Parsers/ExpressionNodeBuilderTests_Errors.cs similarity index 67% rename from tests/Avalonia.Base.UnitTests/Data/Core/ExpressionNodeBuilderTests_Errors.cs rename to tests/Avalonia.Markup.UnitTests/Parsers/ExpressionNodeBuilderTests_Errors.cs index 1bf1ce132a..347fc0a744 100644 --- a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionNodeBuilderTests_Errors.cs +++ b/tests/Avalonia.Markup.UnitTests/Parsers/ExpressionNodeBuilderTests_Errors.cs @@ -2,73 +2,74 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using Avalonia.Data.Core; +using Avalonia.Markup.Parsers; using Xunit; -namespace Avalonia.Base.UnitTests.Data.Core +namespace Avalonia.Markup.UnitTests.Parsers { - public class ExpressionNodeBuilderTests_Errors + public class ExpressionObserverBuilderTests_Errors { [Fact] public void Identifier_Cannot_Start_With_Digit() { Assert.Throws( - () => ExpressionNodeBuilder.Build("1Foo")); + () => ExpressionObserverBuilder.Parse("1Foo")); } [Fact] public void Identifier_Cannot_Start_With_Symbol() { Assert.Throws( - () => ExpressionNodeBuilder.Build("Foo.%Bar")); + () => ExpressionObserverBuilder.Parse("Foo.%Bar")); } [Fact] public void Expression_Cannot_End_With_Period() { Assert.Throws( - () => ExpressionNodeBuilder.Build("Foo.Bar.")); + () => ExpressionObserverBuilder.Parse("Foo.Bar.")); } [Fact] public void Expression_Cannot_Have_Empty_Indexer() { Assert.Throws( - () => ExpressionNodeBuilder.Build("Foo.Bar[]")); + () => ExpressionObserverBuilder.Parse("Foo.Bar[]")); } [Fact] public void Expression_Cannot_Have_Extra_Comma_At_Start_Of_Indexer() { Assert.Throws( - () => ExpressionNodeBuilder.Build("Foo.Bar[,3,4]")); + () => ExpressionObserverBuilder.Parse("Foo.Bar[,3,4]")); } [Fact] public void Expression_Cannot_Have_Extra_Comma_In_Indexer() { Assert.Throws( - () => ExpressionNodeBuilder.Build("Foo.Bar[3,,4]")); + () => ExpressionObserverBuilder.Parse("Foo.Bar[3,,4]")); } [Fact] public void Expression_Cannot_Have_Extra_Comma_At_End_Of_Indexer() { Assert.Throws( - () => ExpressionNodeBuilder.Build("Foo.Bar[3,4,]")); + () => ExpressionObserverBuilder.Parse("Foo.Bar[3,4,]")); } [Fact] public void Expression_Cannot_Have_Digit_After_Indexer() { Assert.Throws( - () => ExpressionNodeBuilder.Build("Foo.Bar[3,4]5")); + () => ExpressionObserverBuilder.Parse("Foo.Bar[3,4]5")); } [Fact] public void Expression_Cannot_Have_Letter_After_Indexer() { Assert.Throws( - () => ExpressionNodeBuilder.Build("Foo.Bar[3,4]A")); + () => ExpressionObserverBuilder.Parse("Foo.Bar[3,4]A")); } } } diff --git a/tests/Avalonia.Markup.UnitTests/Parsers/SelectorGrammarTests.cs b/tests/Avalonia.Markup.UnitTests/Parsers/SelectorGrammarTests.cs index 8cb2639125..62a9e80585 100644 --- a/tests/Avalonia.Markup.UnitTests/Parsers/SelectorGrammarTests.cs +++ b/tests/Avalonia.Markup.UnitTests/Parsers/SelectorGrammarTests.cs @@ -6,7 +6,7 @@ using Avalonia.Markup.Parsers; using Sprache; using Xunit; -namespace Avalonia.Markup.UnitTest.Parsers +namespace Avalonia.Markup.UnitTests.Parsers { public class SelectorGrammarTests { diff --git a/tests/Avalonia.Markup.UnitTests/Parsers/SelectorParserTests.cs b/tests/Avalonia.Markup.UnitTests/Parsers/SelectorParserTests.cs index 360be7f909..f5a08b6d70 100644 --- a/tests/Avalonia.Markup.UnitTests/Parsers/SelectorParserTests.cs +++ b/tests/Avalonia.Markup.UnitTests/Parsers/SelectorParserTests.cs @@ -3,7 +3,7 @@ using Avalonia.Controls; using Avalonia.Markup.Parsers; using Xunit; -namespace Avalonia.Markup.Xaml.UnitTests.Parsers +namespace Avalonia.Markup.UnitTests.Parsers { public class SelectorParserTests {