Browse Source

Move string-based binding paths up to Avalonia.Markup. Make the LINQ Expression paths and raw ExpressionNodes (now public) the primarily supported syntax.

pull/1667/head
Jeremy Koritzinsky 8 years ago
parent
commit
af186e3529
  1. 45
      src/Avalonia.Base/Data/Core/AvaloniaPropertyAccessorNode.cs
  2. 2
      src/Avalonia.Base/Data/Core/EmptyExpressionNode.cs
  3. 2
      src/Avalonia.Base/Data/Core/ExpressionNode.cs
  4. 38
      src/Avalonia.Base/Data/Core/ExpressionNodeBuilder.cs
  5. 74
      src/Avalonia.Base/Data/Core/ExpressionObserver.cs
  6. 2
      src/Avalonia.Base/Data/Core/IndexerNodeBase.cs
  7. 2
      src/Avalonia.Base/Data/Core/LogicalNotNode.cs
  8. 11
      src/Avalonia.Base/Data/Core/Parsers/ExpressionVisitorNodeBuilder.cs
  9. 2
      src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs
  10. 2
      src/Avalonia.Base/Data/Core/StreamNode.cs
  11. 2
      src/Markup/Avalonia.Markup.Xaml/Converters/SelectorTypeConverter.cs
  12. 1
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs
  13. 3
      src/Markup/Avalonia.Markup.Xaml/Templates/MemberSelector.cs
  14. 3
      src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs
  15. 33
      src/Markup/Avalonia.Markup/Data/Binding.cs
  16. 3
      src/Markup/Avalonia.Markup/Markup/Parsers/ArgumentListParser.cs
  17. 75
      src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionObserverBuilder.cs
  18. 30
      src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionParser.cs
  19. 2
      src/Markup/Avalonia.Markup/Markup/Parsers/IdentifierParser.cs
  20. 7
      src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/StringIndexerNode.cs
  21. 2
      src/Markup/Avalonia.Markup/Markup/Parsers/Reader.cs
  22. 4
      src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs
  23. 41
      tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.cs
  24. 31
      tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_AttachedProperty.cs
  25. 9
      tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_AvaloniaProperty.cs
  26. 15
      tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_DataValidation.cs
  27. 45
      tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Indexer.cs
  28. 13
      tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Lifetime.cs
  29. 9
      tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Method.cs
  30. 19
      tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Negation.cs
  31. 13
      tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Observable.cs
  32. 63
      tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs
  33. 9
      tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_SetValue.cs
  34. 13
      tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Task.cs
  35. 2
      tests/Avalonia.Controls.UnitTests/TreeViewTests.cs
  36. 8
      tests/Avalonia.LeakTests/ExpressionObserverTests.cs
  37. 38
      tests/Avalonia.Markup.UnitTests/Parsers/ExpressionNodeBuilderTests.cs
  38. 23
      tests/Avalonia.Markup.UnitTests/Parsers/ExpressionNodeBuilderTests_Errors.cs
  39. 2
      tests/Avalonia.Markup.UnitTests/Parsers/SelectorGrammarTests.cs
  40. 2
      tests/Avalonia.Markup.UnitTests/Parsers/SelectorParserTests.cs

45
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<object> StartListeningCore(WeakReference reference)
{
return (reference.Target as IAvaloniaObject)?.GetWeakObservable(_property) ?? Observable.Empty<object>();
}
}
}

2
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 => ".";

2
src/Avalonia.Base/Data/Core/ExpressionNode.cs

@ -9,7 +9,7 @@ using Avalonia.Data;
namespace Avalonia.Data.Core
{
internal abstract class ExpressionNode : ISubject<object>
public abstract class ExpressionNode : ISubject<object>
{
protected static readonly WeakReference UnsetReference =
new WeakReference(AvaloniaProperty.UnsetValue);

38
src/Avalonia.Base/Data/Core/ExpressionNodeBuilder.cs

@ -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);
}
}
}

74
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 <see cref="ExpressionObserver"/> class.
/// </summary>
/// <param name="root">The root object.</param>
/// <param name="expression">The expression.</param>
/// <param name="enableDataValidation">Whether data validation should be enabled.</param>
/// <param name="node">The expression.</param>
/// <param name="description">
/// A description of the expression. If null, <paramref name="expression"/> will be used.
/// A description of the expression. If null, <paramref name="node"/> will be used.
/// </param>
public ExpressionObserver(
object root,
string expression,
bool enableDataValidation = false,
string description = null)
: this(root, Parse(expression, enableDataValidation), description ?? expression)
{
Contract.Requires<ArgumentNullException>(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 <see cref="ExpressionObserver"/> class.
/// </summary>
/// <param name="rootObservable">An observable which provides the root object.</param>
/// <param name="expression">The expression.</param>
/// <param name="enableDataValidation">Whether data validation should be enabled.</param>
/// <param name="node">The expression.</param>
/// <param name="description">
/// A description of the expression. If null, <paramref name="expression"/> will be used.
/// A description of the expression. If null, <paramref name="node"/> will be used.
/// </param>
public ExpressionObserver(
IObservable<object> rootObservable,
string expression,
bool enableDataValidation = false,
string description = null)
: this(rootObservable, Parse(expression, enableDataValidation), description ?? expression)
{
Contract.Requires<ArgumentNullException>(rootObservable != null);
Contract.Requires<ArgumentNullException>(expression != null);
Expression = expression;
}
private ExpressionObserver(
IObservable<object> rootObservable,
ExpressionNode node,
string description)
@ -137,7 +112,7 @@ namespace Avalonia.Data.Core
_root = rootObservable;
_finished = new Subject<Unit>();
}
public static ExpressionObserver CreateFromExpression<T, U>(
IObservable<T> rootObservable,
Expression<Func<T, U>> expression,
@ -155,26 +130,12 @@ namespace Avalonia.Data.Core
/// Initializes a new instance of the <see cref="ExpressionObserver"/> class.
/// </summary>
/// <param name="rootGetter">A function which gets the root object.</param>
/// <param name="expression">The expression.</param>
/// <param name="node">The expression.</param>
/// <param name="update">An observable which triggers a re-read of the getter.</param>
/// <param name="enableDataValidation">Whether data validation should be enabled.</param>
/// <param name="description">
/// A description of the expression. If null, <paramref name="expression"/> will be used.
/// A description of the expression. If null, <paramref name="node"/> will be used.
/// </param>
public ExpressionObserver(
Func<object> rootGetter,
string expression,
IObservable<Unit> update,
bool enableDataValidation = false,
string description = null)
: this(rootGetter, Parse(expression, enableDataValidation), update, description ?? expression)
{
Contract.Requires<ArgumentNullException>(expression != null);
Expression = expression;
}
private ExpressionObserver(
Func<object> rootGetter,
ExpressionNode node,
IObservable<Unit> update,
@ -189,8 +150,8 @@ namespace Avalonia.Data.Core
_node.Target = new WeakReference(rootGetter());
_root = update.Select(x => rootGetter());
}
public static ExpressionObserver CreateFromExpression<T, U>(
public static ExpressionObserver Create<T, U>(
Func<T> rootGetter,
Expression<Func<T, U>> expression,
IObservable<Unit> 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)

2
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<object> StartListeningCore(WeakReference reference)
{

2
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 => "!";

11
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<ExpressionNode> Nodes { get; }
@ -70,7 +72,7 @@ namespace Avalonia.Data.Core.Parsers
if (node.Indexer == AvaloniaObjectIndexer)
{
var property = GetArgumentExpressionValue<AvaloniaProperty>(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<MethodInfo>(node.Object).Name, enableDataValidation));
return visited;
}
throw new ExpressionParseException(0, $"Invalid expression type in binding expression: {node.NodeType}.");
}

2
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;

2
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 => "^";

2
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);
}

1
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,

3
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);

3
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);
}

33
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; }
/// <summary>
/// Gets or sets a function used to resolve types from names in the binding path.
/// </summary>
public Func<string, string, Type> TypeResolver { get; set; }
/// <inheritdoc/>
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<ArgumentNullException>(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<ArgumentNullException>(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;
}

3
src/Avalonia.Base/Data/Core/Parsers/ArgumentListParser.cs → 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
{

75
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<string, string, Type> 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<string, string, Type> typeResolver = null)
{
return new ExpressionObserver(
root,
Parse(expression, enableDataValidation, typeResolver),
description ?? expression);
}
public static ExpressionObserver Build(
IObservable<object> rootObservable,
string expression,
bool enableDataValidation = false,
string description = null,
Func<string, string, Type> typeResolver = null)
{
Contract.Requires<ArgumentNullException>(rootObservable != null);
return new ExpressionObserver(
rootObservable,
Parse(expression, enableDataValidation, typeResolver),
description ?? expression);
}
public static ExpressionObserver Build(
Func<object> rootGetter,
string expression,
IObservable<Unit> update,
bool enableDataValidation = false,
string description = null,
Func<string, string, Type> typeResolver = null)
{
Contract.Requires<ArgumentNullException>(rootGetter != null);
return new ExpressionObserver(
() => rootGetter(),
Parse(expression, enableDataValidation, typeResolver),
update,
description ?? expression);
}
}
}

30
src/Avalonia.Base/Data/Core/Parsers/ExpressionParser.cs → 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<string, string, Type> _typeResolver;
public ExpressionParser(bool enableValidation)
public ExpressionParser(bool enableValidation, Func<string, string, Type> typeResolver)
{
_typeResolver = typeResolver;
_enableValidation = enableValidation;
}
@ -130,7 +134,19 @@ namespace Avalonia.Data.Core.Parsers
private State ParseAttachedProperty(Reader r, List<ExpressionNode> 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;
}

2
src/Avalonia.Base/Data/Core/Parsers/IdentifierParser.cs → 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
{

7
src/Avalonia.Base/Data/Core/IndexerNode.cs → 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<string> arguments)
public StringIndexerNode(IList<string> arguments)
{
Arguments = arguments;
}

2
src/Avalonia.Base/Data/Core/Parsers/Reader.cs → 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
{

4
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)
{

41
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<BindingNotification>(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<IValueConverter>();
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<IValueConverter>();
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<IValueConverter>();
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<object>();
target.Subscribe(x => result.Add(x));

31
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<string, string, Type> _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<object>();
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<object>();
var sub = target.Subscribe(x => result.Add(x));
@ -96,7 +113,7 @@ namespace Avalonia.Base.UnitTests.Data.Core
Func<Tuple<ExpressionObserver, WeakReference>> 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<ExpressionParseException>(() => new ExpressionObserver(data, "(Owner)"));
Assert.Throws<ExpressionParseException>(() => ExpressionObserverBuilder.Build(data, "(Owner)", typeResolver: _typeResolver));
}
[Fact]
@ -121,7 +138,7 @@ namespace Avalonia.Base.UnitTests.Data.Core
{
var data = new Class1();
Assert.Throws<ExpressionParseException>(() => new ExpressionObserver(data, "(Owner.Foo.Bar)"));
Assert.Throws<ExpressionParseException>(() => ExpressionObserverBuilder.Build(data, "(Owner.Foo.Bar)", typeResolver: _typeResolver));
}
private static class Owner

9
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<object>();
var sub = target.Subscribe(x => result.Add(x));
@ -63,7 +64,7 @@ namespace Avalonia.Base.UnitTests.Data.Core
Func<Tuple<ExpressionObserver, WeakReference>> run = () =>
{
var source = new Class1();
var target = new ExpressionObserver(source, "Foo");
var target = ExpressionObserverBuilder.Build(source, "Foo");
return Tuple.Create(target, new WeakReference(source));
};

15
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<BindingNotification>()
@ -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<BindingNotification>()
@ -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<object>();
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);

45
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<int, string> { { 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<string, string> { { "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<double, string> { { 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<string> { "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<string> { "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<string> { "foo", "bar" } };
var target = new ExpressionObserver(data, "Foo[2]");
var target = ExpressionObserverBuilder.Build(data, "Foo[2]");
var result = new List<object>();
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<string> { "foo", "bar" } };
var target = new ExpressionObserver(data, "Foo[0]");
var target = ExpressionObserverBuilder.Build(data, "Foo[0]");
var result = new List<object>();
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<string> { "foo", "bar" } };
var target = new ExpressionObserver(data, "Foo[1]");
var target = ExpressionObserverBuilder.Build(data, "Foo[1]");
var result = new List<object>();
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<string> { "foo", "bar" } };
var target = new ExpressionObserver(data, "Foo[1]");
var target = ExpressionObserverBuilder.Build(data, "Foo[1]");
var result = new List<object>();
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<string> { "foo", "bar" } };
var target = new ExpressionObserver(data, "Foo[1]");
var target = ExpressionObserverBuilder.Build(data, "Foo[1]");
var result = new List<object>();
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<object>();
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);

13
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<object>(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<object>(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<Unit>();
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<Unit>();
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<object>();
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<Unit>();
var data = new { Foo = "foo" };
var target = new ExpressionObserver(() => data, "Foo", update);
var target = ExpressionObserverBuilder.Build(() => data, "Foo", update);
var result = new List<object>();
using (target.Subscribe(x => result.Add(x)))

9
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<int, int>)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<BindingNotification>(result);

19
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));

13
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<string>("foo");
var data = new { Foo = source };
var target = new ExpressionObserver(data, "Foo");
var target = ExpressionObserverBuilder.Build(data, "Foo");
var result = new List<object>();
var sub = target.Subscribe(x => result.Add(x));
@ -41,7 +42,7 @@ namespace Avalonia.Base.UnitTests.Data.Core
{
var source = new BehaviorSubject<string>("foo");
var data = new { Foo = source };
var target = new ExpressionObserver(data, "Foo^");
var target = ExpressionObserverBuilder.Build(data, "Foo^");
var result = new List<object>();
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<object>();
var sub = target.Subscribe(x => result.Add(x));
@ -83,7 +84,7 @@ namespace Avalonia.Base.UnitTests.Data.Core
{
var source = new BehaviorSubject<string>("foo");
var data = new { Foo = source };
var target = new ExpressionObserver(data, "Foo^", true);
var target = ExpressionObserverBuilder.Build(data, "Foo^", true);
var result = new List<object>();
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<object>();
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<object>();
var sub = target.Subscribe(x => result.Add(x));

63
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<BindingNotification>(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<object>();
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<object>();
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<object>();
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<object>();
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<object>();
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<object>();
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<object>();
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<Unit>();
var target = new ExpressionObserver(() => data.Foo, "", update);
var target = ExpressionObserverBuilder.Build(() => data.Foo, "", update);
var result = new List<object>();
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<object>();
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<object>();
var result2 = new List<object>();
var result3 = new List<object>();
@ -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<object>();
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<object>();
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<Unit>();
var target = new ExpressionObserver(() => root, "Foo", update);
var target = ExpressionObserverBuilder.Build(() => root, "Foo", update);
var result = new List<object>();
var sub = target.Subscribe(x => result.Add(x));
@ -589,7 +590,7 @@ namespace Avalonia.Base.UnitTests.Data.Core
Func<Tuple<ExpressionObserver, WeakReference>> 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));
};

9
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<string>().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<Class1>(data);
var target = new ExpressionObserver(rootObservable, "Foo.Bar");
var target = ExpressionObserverBuilder.Build(rootObservable, "Foo.Bar");
target.Subscribe(_ => { });
rootObservable.OnNext(null);

13
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<string>();
var data = new { Foo = tcs.Task };
var target = new ExpressionObserver(data, "Foo");
var target = ExpressionObserverBuilder.Build(data, "Foo");
var result = new List<object>();
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<object>();
var sub = target.Subscribe(x => result.Add(x));
@ -59,7 +60,7 @@ namespace Avalonia.Base.UnitTests.Data.Core
{
var tcs = new TaskCompletionSource<Class2>();
var data = new Class1(tcs.Task);
var target = new ExpressionObserver(data, "Next^.Foo");
var target = ExpressionObserverBuilder.Build(data, "Next^.Foo");
var result = new List<object>();
var sub = target.Subscribe(x => result.Add(x));
@ -79,7 +80,7 @@ namespace Avalonia.Base.UnitTests.Data.Core
{
var tcs = new TaskCompletionSource<string>();
var data = new { Foo = tcs.Task };
var target = new ExpressionObserver(data, "Foo^");
var target = ExpressionObserverBuilder.Build(data, "Foo^");
var result = new List<object>();
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<object>();
var sub = target.Subscribe(x => result.Add(x));
@ -130,7 +131,7 @@ namespace Avalonia.Base.UnitTests.Data.Core
{
var tcs = new TaskCompletionSource<string>();
var data = new { Foo = tcs.Task };
var target = new ExpressionObserver(data, "Foo^", true);
var target = ExpressionObserverBuilder.Build(data, "Foo^", true);
var result = new List<object>();
var sub = target.Subscribe(x => result.Add(x));

2
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);
}

8
tests/Avalonia.LeakTests/ExpressionObserverTests.cs

@ -24,7 +24,7 @@ namespace Avalonia.LeakTests
Func<ExpressionObserver> run = () =>
{
var source = new { Foo = new AvaloniaList<string> {"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<ExpressionObserver> run = () =>
{
var source = new { Foo = new AvaloniaList<string> { "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<ExpressionObserver> 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<ExpressionObserver> 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;
};

38
tests/Avalonia.Base.UnitTests/Data/Core/ExpressionNodeBuilderTests.cs → 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<LogicalNotNode>(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<LogicalNotNode>(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<IndexerNode>(result[1]);
Assert.IsType<StringIndexerNode>(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<IndexerNode>(result[1]);
Assert.IsType<StringIndexerNode>(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<IndexerNode>(node);
Assert.IsType<StringIndexerNode>(node);
var e = (IndexerNode)node;
var e = (StringIndexerNode)node;
Assert.Equal(e.Arguments.ToArray(), args);
}

23
tests/Avalonia.Base.UnitTests/Data/Core/ExpressionNodeBuilderTests_Errors.cs → 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<ExpressionParseException>(
() => ExpressionNodeBuilder.Build("1Foo"));
() => ExpressionObserverBuilder.Parse("1Foo"));
}
[Fact]
public void Identifier_Cannot_Start_With_Symbol()
{
Assert.Throws<ExpressionParseException>(
() => ExpressionNodeBuilder.Build("Foo.%Bar"));
() => ExpressionObserverBuilder.Parse("Foo.%Bar"));
}
[Fact]
public void Expression_Cannot_End_With_Period()
{
Assert.Throws<ExpressionParseException>(
() => ExpressionNodeBuilder.Build("Foo.Bar."));
() => ExpressionObserverBuilder.Parse("Foo.Bar."));
}
[Fact]
public void Expression_Cannot_Have_Empty_Indexer()
{
Assert.Throws<ExpressionParseException>(
() => ExpressionNodeBuilder.Build("Foo.Bar[]"));
() => ExpressionObserverBuilder.Parse("Foo.Bar[]"));
}
[Fact]
public void Expression_Cannot_Have_Extra_Comma_At_Start_Of_Indexer()
{
Assert.Throws<ExpressionParseException>(
() => ExpressionNodeBuilder.Build("Foo.Bar[,3,4]"));
() => ExpressionObserverBuilder.Parse("Foo.Bar[,3,4]"));
}
[Fact]
public void Expression_Cannot_Have_Extra_Comma_In_Indexer()
{
Assert.Throws<ExpressionParseException>(
() => 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<ExpressionParseException>(
() => ExpressionNodeBuilder.Build("Foo.Bar[3,4,]"));
() => ExpressionObserverBuilder.Parse("Foo.Bar[3,4,]"));
}
[Fact]
public void Expression_Cannot_Have_Digit_After_Indexer()
{
Assert.Throws<ExpressionParseException>(
() => 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<ExpressionParseException>(
() => ExpressionNodeBuilder.Build("Foo.Bar[3,4]A"));
() => ExpressionObserverBuilder.Parse("Foo.Bar[3,4]A"));
}
}
}

2
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
{

2
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
{

Loading…
Cancel
Save