Browse Source

Merge branch 'master' into pr/763

pull/763/head
Steven Kirk 10 years ago
parent
commit
8955a20cf6
  1. 2
      src/Avalonia.Base/Avalonia.Base.csproj
  2. 57
      src/Avalonia.Base/Data/BindingChainException.cs
  3. 2
      src/Avalonia.Controls/TextBox.cs
  4. 25
      src/Avalonia.Input/AccessKeyHandler.cs
  5. 2
      src/Markup/Avalonia.Markup.Xaml/OmniXAML
  6. 9
      src/Markup/Avalonia.Markup/Avalonia.Markup.csproj
  7. 57
      src/Markup/Avalonia.Markup/Data/ExpressionNode.cs
  8. 14
      src/Markup/Avalonia.Markup/Data/ExpressionObserver.cs
  9. 42
      src/Markup/Avalonia.Markup/Data/MarkupBindingChainException.cs
  10. 33
      src/Markup/Avalonia.Markup/Data/MarkupBindingChainNullException.cs
  11. 10
      src/Markup/Avalonia.Markup/Data/Parsers/ExpressionParser.cs
  12. 4
      src/Markup/Avalonia.Markup/Data/Plugins/IStreamPlugin.cs
  13. 18
      src/Markup/Avalonia.Markup/Data/Plugins/ObservableStreamPlugin.cs
  14. 4
      src/Markup/Avalonia.Markup/Data/Plugins/TaskStreamPlugin.cs
  15. 31
      src/Markup/Avalonia.Markup/Data/StreamNode.cs
  16. 2
      tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_DataValidation.cs
  17. 51
      tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Observable.cs
  18. 4
      tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Property.cs
  19. 15
      tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Task.cs

2
src/Avalonia.Base/Avalonia.Base.csproj

@ -44,7 +44,7 @@
<Compile Include="..\Shared\SharedAssemblyInfo.cs"> <Compile Include="..\Shared\SharedAssemblyInfo.cs">
<Link>Properties\SharedAssemblyInfo.cs</Link> <Link>Properties\SharedAssemblyInfo.cs</Link>
</Compile> </Compile>
<Compile Include="Data\BindingChainNullException.cs" /> <Compile Include="Data\BindingChainException.cs" />
<Compile Include="Data\BindingNotification.cs" /> <Compile Include="Data\BindingNotification.cs" />
<Compile Include="Data\IndexerBinding.cs" /> <Compile Include="Data\IndexerBinding.cs" />
<Compile Include="Diagnostics\INotifyCollectionChangedDebug.cs" /> <Compile Include="Diagnostics\INotifyCollectionChangedDebug.cs" />

57
src/Avalonia.Base/Data/BindingChainNullException.cs → src/Avalonia.Base/Data/BindingChainException.cs

@ -10,36 +10,39 @@ namespace Avalonia.Data
/// requested binding expression could not be evaluated because of a null in one of the links /// requested binding expression could not be evaluated because of a null in one of the links
/// of the binding chain. /// of the binding chain.
/// </summary> /// </summary>
public class BindingChainNullException : Exception public class BindingChainException : Exception
{ {
private string _message; private string _message;
/// <summary> /// <summary>
/// Initalizes a new instance of the <see cref="BindingChainNullException"/> class. /// Initalizes a new instance of the <see cref="BindingChainException"/> class.
/// </summary> /// </summary>
public BindingChainNullException() public BindingChainException()
{ {
} }
/// <summary> /// <summary>
/// Initalizes a new instance of the <see cref="BindingChainNullException"/> class. /// Initalizes a new instance of the <see cref="BindingChainException"/> class.
/// </summary> /// </summary>
public BindingChainNullException(string message) /// <param name="message">The error message.</param>
public BindingChainException(string message)
{ {
_message = message; _message = message;
} }
/// <summary> /// <summary>
/// Initalizes a new instance of the <see cref="BindingChainNullException"/> class. /// Initalizes a new instance of the <see cref="BindingChainException"/> class.
/// </summary> /// </summary>
/// <param name="message">The error message.</param>
/// <param name="expression">The expression.</param> /// <param name="expression">The expression.</param>
/// <param name="expressionNullPoint"> /// <param name="errorPoint">
/// The point in the expression at which the null was encountered. /// The point in the expression at which the error was encountered.
/// </param> /// </param>
public BindingChainNullException(string expression, string expressionNullPoint) public BindingChainException(string message, string expression, string errorPoint)
{ {
_message = message;
Expression = expression; Expression = expression;
ExpressionNullPoint = expressionNullPoint; ExpressionErrorPoint = errorPoint;
} }
/// <summary> /// <summary>
@ -48,37 +51,27 @@ namespace Avalonia.Data
public string Expression { get; protected set; } public string Expression { get; protected set; }
/// <summary> /// <summary>
/// Gets the point in the expression at which the null was encountered. /// Gets the point in the expression at which the error occured.
/// </summary> /// </summary>
public string ExpressionNullPoint { get; protected set; } public string ExpressionErrorPoint { get; protected set; }
/// <inheritdoc/> /// <inheritdoc/>
public override string Message public override string Message
{ {
get get
{ {
if (_message == null) if (Expression != null && ExpressionErrorPoint != null)
{ {
_message = BuildMessage(); return $"{_message} in expression '{Expression}' at '{ExpressionErrorPoint}'.";
}
else if (ExpressionErrorPoint != null)
{
return $"{_message} in expression '{ExpressionErrorPoint}'.";
}
else
{
return $"{_message} in expression.";
} }
return _message;
}
}
private string BuildMessage()
{
if (Expression != null && ExpressionNullPoint != null)
{
return $"'{ExpressionNullPoint}' is null in expression '{Expression}'.";
}
else if (ExpressionNullPoint != null)
{
return $"'{ExpressionNullPoint}' is null in expression.";
}
else
{
return "Null encountered in binding expression.";
} }
} }
} }

2
src/Avalonia.Controls/TextBox.cs

@ -547,7 +547,7 @@ namespace Avalonia.Controls
var exceptions = aggregate == null ? var exceptions = aggregate == null ?
(IEnumerable<Exception>)new[] { exception } : (IEnumerable<Exception>)new[] { exception } :
aggregate.InnerExceptions; aggregate.InnerExceptions;
var filtered = exceptions.Where(x => !(x is BindingChainNullException)).ToList(); var filtered = exceptions.Where(x => !(x is BindingChainException)).ToList();
if (filtered.Count > 0) if (filtered.Count > 0)
{ {

25
src/Avalonia.Input/AccessKeyHandler.cs

@ -43,6 +43,16 @@ namespace Avalonia.Input
/// </summary> /// </summary>
private bool _ignoreAltUp; private bool _ignoreAltUp;
/// <summary>
/// Whether the AltKey is down.
/// </summary>
private bool _altIsDown;
/// <summary>
/// Element to restore folowing AltKey taking focus.
/// </summary>
private IInputElement _restoreFocusElement;
/// <summary> /// <summary>
/// Gets or sets the window's main menu. /// Gets or sets the window's main menu.
/// </summary> /// </summary>
@ -110,8 +120,14 @@ namespace Avalonia.Input
{ {
if (e.Key == Key.LeftAlt) if (e.Key == Key.LeftAlt)
{ {
_altIsDown = true;
if (MainMenu == null || !MainMenu.IsOpen) if (MainMenu == null || !MainMenu.IsOpen)
{ {
// TODO: Use FocusScopes to store the current element and restore it when context menu is closed.
// Save currently focused input element.
_restoreFocusElement = FocusManager.Instance.Current;
// When Alt is pressed without a main menu, or with a closed main menu, show // When Alt is pressed without a main menu, or with a closed main menu, show
// access key markers in the window (i.e. "_File"). // access key markers in the window (i.e. "_File").
_owner.ShowAccessKeys = _showingAccessKeys = true; _owner.ShowAccessKeys = _showingAccessKeys = true;
@ -121,11 +137,18 @@ namespace Avalonia.Input
// If the Alt key is pressed and the main menu is open, close the main menu. // If the Alt key is pressed and the main menu is open, close the main menu.
CloseMenu(); CloseMenu();
_ignoreAltUp = true; _ignoreAltUp = true;
_restoreFocusElement?.Focus();
_restoreFocusElement = null;
} }
// We always handle the Alt key. // We always handle the Alt key.
e.Handled = true; e.Handled = true;
} }
else if (_altIsDown)
{
_ignoreAltUp = true;
}
} }
/// <summary> /// <summary>
@ -179,6 +202,8 @@ namespace Avalonia.Input
switch (e.Key) switch (e.Key)
{ {
case Key.LeftAlt: case Key.LeftAlt:
_altIsDown = false;
if (_ignoreAltUp) if (_ignoreAltUp)
{ {
_ignoreAltUp = false; _ignoreAltUp = false;

2
src/Markup/Avalonia.Markup.Xaml/OmniXAML

@ -1 +1 @@
Subproject commit b122549406107170bbe6e67c0d6a1a4252beef77 Subproject commit 544af79d218127b4174da4be19896c5ca78eaa5d

9
src/Markup/Avalonia.Markup/Avalonia.Markup.csproj

@ -42,7 +42,8 @@
<Compile Include="..\..\Shared\SharedAssemblyInfo.cs"> <Compile Include="..\..\Shared\SharedAssemblyInfo.cs">
<Link>Properties\SharedAssemblyInfo.cs</Link> <Link>Properties\SharedAssemblyInfo.cs</Link>
</Compile> </Compile>
<Compile Include="Data\MarkupBindingChainNullException.cs" /> <Compile Include="Data\StreamNode.cs" />
<Compile Include="Data\MarkupBindingChainException.cs" />
<Compile Include="Data\CommonPropertyNames.cs" /> <Compile Include="Data\CommonPropertyNames.cs" />
<Compile Include="Data\EmptyExpressionNode.cs" /> <Compile Include="Data\EmptyExpressionNode.cs" />
<Compile Include="Data\ExpressionNodeBuilder.cs" /> <Compile Include="Data\ExpressionNodeBuilder.cs" />
@ -63,9 +64,9 @@
<Compile Include="Data\Parsers\IdentifierParser.cs" /> <Compile Include="Data\Parsers\IdentifierParser.cs" />
<Compile Include="Data\Parsers\ExpressionParser.cs" /> <Compile Include="Data\Parsers\ExpressionParser.cs" />
<Compile Include="Data\Parsers\Reader.cs" /> <Compile Include="Data\Parsers\Reader.cs" />
<Compile Include="Data\Plugins\ObservableValuePlugin.cs" /> <Compile Include="Data\Plugins\ObservableStreamPlugin.cs" />
<Compile Include="Data\Plugins\TaskValuePlugin.cs" /> <Compile Include="Data\Plugins\TaskStreamPlugin.cs" />
<Compile Include="Data\Plugins\IValuePlugin.cs" /> <Compile Include="Data\Plugins\IStreamPlugin.cs" />
<Compile Include="Data\Plugins\PropertyAccessorBase.cs" /> <Compile Include="Data\Plugins\PropertyAccessorBase.cs" />
<Compile Include="Data\Plugins\PropertyError.cs" /> <Compile Include="Data\Plugins\PropertyError.cs" />
<Compile Include="Data\Plugins\DataValidatiorBase.cs" /> <Compile Include="Data\Plugins\DataValidatiorBase.cs" />

57
src/Markup/Avalonia.Markup/Data/ExpressionNode.cs

@ -17,7 +17,6 @@ namespace Avalonia.Markup.Data
private WeakReference _target = UnsetReference; private WeakReference _target = UnsetReference;
private IDisposable _valueSubscription; private IDisposable _valueSubscription;
private IObserver<object> _observer; private IObserver<object> _observer;
private IDisposable _valuePluginSubscription;
public abstract string Description { get; } public abstract string Description { get; }
public ExpressionNode Next { get; set; } public ExpressionNode Next { get; set; }
@ -37,7 +36,6 @@ namespace Avalonia.Markup.Data
{ {
_valueSubscription?.Dispose(); _valueSubscription?.Dispose();
_valueSubscription = null; _valueSubscription = null;
_valuePluginSubscription?.Dispose();
_target = value; _target = value;
if (running) if (running)
@ -63,8 +61,6 @@ namespace Avalonia.Markup.Data
{ {
_valueSubscription?.Dispose(); _valueSubscription?.Dispose();
_valueSubscription = null; _valueSubscription = null;
_valuePluginSubscription?.Dispose();
_valuePluginSubscription = null;
nextSubscription?.Dispose(); nextSubscription?.Dispose();
_observer = null; _observer = null;
}); });
@ -92,7 +88,7 @@ namespace Avalonia.Markup.Data
protected virtual void NextValueChanged(object value) protected virtual void NextValueChanged(object value)
{ {
var bindingBroken = BindingNotification.ExtractError(value) as MarkupBindingChainNullException; var bindingBroken = BindingNotification.ExtractError(value) as MarkupBindingChainException;
bindingBroken?.AddNode(Description); bindingBroken?.AddNode(Description);
_observer.OnNext(value); _observer.OnNext(value);
} }
@ -115,25 +111,22 @@ namespace Avalonia.Markup.Data
source = StartListeningCore(_target); source = StartListeningCore(_target);
} }
return source.Subscribe(TargetValueChanged); return source.Subscribe(ValueChanged);
} }
private void TargetValueChanged(object value) private void ValueChanged(object value)
{ {
var notification = value as BindingNotification; var notification = value as BindingNotification;
if (notification == null) if (notification == null)
{ {
if (!HandleSpecialValue(value)) if (Next != null)
{ {
if (Next != null) Next.Target = new WeakReference(value);
{ }
Next.Target = new WeakReference(value); else
} {
else _observer.OnNext(value);
{
_observer.OnNext(value);
}
} }
} }
else else
@ -144,44 +137,22 @@ namespace Avalonia.Markup.Data
} }
else if (notification.HasValue) else if (notification.HasValue)
{ {
if (!HandleSpecialValue(notification.Value)) if (Next != null)
{ {
if (Next != null) Next.Target = new WeakReference(notification.Value);
{
Next.Target = new WeakReference(notification.Value);
}
else
{
_observer.OnNext(value);
}
} }
} else
}
}
private bool HandleSpecialValue(object value)
{
if (_valuePluginSubscription == null)
{
var reference = new WeakReference(value);
foreach (var plugin in ExpressionObserver.ValueHandlers)
{
if (plugin.Match(reference))
{ {
_valuePluginSubscription = plugin.Start(reference)?.Subscribe(TargetValueChanged); _observer.OnNext(value);
return true;
} }
} }
} }
return false;
} }
private BindingNotification TargetNullNotification() private BindingNotification TargetNullNotification()
{ {
return new BindingNotification( return new BindingNotification(
new MarkupBindingChainNullException(), new MarkupBindingChainException("Null value"),
BindingErrorType.Error, BindingErrorType.Error,
AvaloniaProperty.UnsetValue); AvaloniaProperty.UnsetValue);
} }

14
src/Markup/Avalonia.Markup/Data/ExpressionObserver.cs

@ -41,14 +41,14 @@ namespace Avalonia.Markup.Data
}; };
/// <summary> /// <summary>
/// An ordered collection of value handlers that can be used to customize the handling /// An ordered collection of stream plugins that can be used to customize the behavior
/// of certain values. /// of the '^' stream binding operator.
/// </summary> /// </summary>
public static readonly IList<IValuePlugin> ValueHandlers = public static readonly IList<IStreamPlugin> StreamHandlers =
new List<IValuePlugin> new List<IStreamPlugin>
{ {
new TaskValuePlugin(), new TaskStreamPlugin(),
new ObservableValuePlugin(), new ObservableStreamPlugin(),
}; };
private static readonly object UninitializedValue = new object(); private static readonly object UninitializedValue = new object();
@ -235,7 +235,7 @@ namespace Avalonia.Markup.Data
} }
else else
{ {
var broken = BindingNotification.ExtractError(o) as MarkupBindingChainNullException; var broken = BindingNotification.ExtractError(o) as MarkupBindingChainException;
if (broken != null) if (broken != null)
{ {

42
src/Markup/Avalonia.Markup/Data/MarkupBindingChainException.cs

@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Data;
namespace Avalonia.Markup.Data
{
internal class MarkupBindingChainException : BindingChainException
{
private IList<string> _nodes = new List<string>();
public MarkupBindingChainException(string message)
: base(message)
{
}
public MarkupBindingChainException(string message, string node)
: base(message)
{
AddNode(node);
}
public MarkupBindingChainException(string message, string expression, string expressionNullPoint)
: base(message, expression, expressionNullPoint)
{
_nodes = null;
}
public bool HasNodes => _nodes.Count > 0;
public void AddNode(string node) => _nodes.Add(node);
public void Commit(string expression)
{
Expression = expression;
ExpressionErrorPoint = string.Join(".", _nodes.Reverse())
.Replace(".!", "!")
.Replace(".[", "[")
.Replace(".^", "^");
_nodes = null;
}
}
}

33
src/Markup/Avalonia.Markup/Data/MarkupBindingChainNullException.cs

@ -1,33 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using Avalonia.Data;
namespace Avalonia.Markup.Data
{
internal class MarkupBindingChainNullException : BindingChainNullException
{
private IList<string> _nodes = new List<string>();
public MarkupBindingChainNullException()
{
}
public MarkupBindingChainNullException(string expression, string expressionNullPoint)
: base(expression, expressionNullPoint)
{
_nodes = null;
}
public bool HasNodes => _nodes.Count > 0;
public void AddNode(string node) => _nodes.Add(node);
public void Commit(string expression)
{
Expression = expression;
ExpressionNullPoint = string.Join(".", _nodes.Reverse())
.Replace(".!", "!")
.Replace(".[", "[");
_nodes = null;
}
}
}

10
src/Markup/Avalonia.Markup/Data/Parsers/ExpressionParser.cs

@ -87,6 +87,11 @@ namespace Avalonia.Markup.Data.Parsers
{ {
return State.BeforeMember; return State.BeforeMember;
} }
else if (ParseStreamOperator(r))
{
nodes.Add(new StreamNode());
return State.AfterMember;
}
else else
{ {
var args = ArgumentListParser.Parse(r, '[', ']'); var args = ArgumentListParser.Parse(r, '[', ']');
@ -161,6 +166,11 @@ namespace Avalonia.Markup.Data.Parsers
return !r.End && r.TakeIf('('); return !r.End && r.TakeIf('(');
} }
private static bool ParseStreamOperator(Reader r)
{
return !r.End && r.TakeIf('^');
}
private enum State private enum State
{ {
Start, Start,

4
src/Markup/Avalonia.Markup/Data/Plugins/IValuePlugin.cs → src/Markup/Avalonia.Markup/Data/Plugins/IStreamPlugin.cs

@ -6,9 +6,9 @@ using System;
namespace Avalonia.Markup.Data.Plugins namespace Avalonia.Markup.Data.Plugins
{ {
/// <summary> /// <summary>
/// Defines how values are observed by an <see cref="ExpressionObserver"/>. /// Defines a plugin that handles the '^' stream binding operator.
/// </summary> /// </summary>
public interface IValuePlugin public interface IStreamPlugin
{ {
/// <summary> /// <summary>
/// Checks whether this plugin handles the specified value. /// Checks whether this plugin handles the specified value.

18
src/Markup/Avalonia.Markup/Data/Plugins/ObservableValuePlugin.cs → src/Markup/Avalonia.Markup/Data/Plugins/ObservableStreamPlugin.cs

@ -2,32 +2,20 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Reflection;
using System.Threading.Tasks;
using System.Windows.Input;
using Avalonia.Data;
namespace Avalonia.Markup.Data.Plugins namespace Avalonia.Markup.Data.Plugins
{ {
/// <summary> /// <summary>
/// Handles binding to <see cref="IObservable{T}"/>s in an <see cref="ExpressionObserver"/>. /// Handles binding to <see cref="IObservable{T}"/>s for the '^' stream binding operator.
/// </summary> /// </summary>
public class ObservableValuePlugin : IValuePlugin public class ObservableStreamPlugin : IStreamPlugin
{ {
/// <summary> /// <summary>
/// Checks whether this plugin handles the specified value. /// Checks whether this plugin handles the specified value.
/// </summary> /// </summary>
/// <param name="reference">A weak reference to the value.</param> /// <param name="reference">A weak reference to the value.</param>
/// <returns>True if the plugin can handle the value; otherwise false.</returns> /// <returns>True if the plugin can handle the value; otherwise false.</returns>
public virtual bool Match(WeakReference reference) public virtual bool Match(WeakReference reference) => reference.Target is IObservable<object>;
{
var target = reference.Target;
// ReactiveCommand is an IObservable but we want to bind to it, not its value.
return target is IObservable<object> && !(target is ICommand);
}
/// <summary> /// <summary>
/// Starts producing output based on the specified value. /// Starts producing output based on the specified value.

4
src/Markup/Avalonia.Markup/Data/Plugins/TaskValuePlugin.cs → src/Markup/Avalonia.Markup/Data/Plugins/TaskStreamPlugin.cs

@ -12,9 +12,9 @@ using Avalonia.Data;
namespace Avalonia.Markup.Data.Plugins namespace Avalonia.Markup.Data.Plugins
{ {
/// <summary> /// <summary>
/// Handles binding to <see cref="Task"/>s in an <see cref="ExpressionObserver"/>. /// Handles binding to <see cref="Task"/>s for the '^' stream binding operator.
/// </summary> /// </summary>
public class TaskValuePlugin : IValuePlugin public class TaskStreamPlugin : IStreamPlugin
{ {
/// <summary> /// <summary>
/// Checks whether this plugin handles the specified value. /// Checks whether this plugin handles the specified value.

31
src/Markup/Avalonia.Markup/Data/StreamNode.cs

@ -0,0 +1,31 @@
// 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 System.Globalization;
using Avalonia.Data;
using System.Reactive.Linq;
namespace Avalonia.Markup.Data
{
internal class StreamNode : ExpressionNode
{
public override string Description => "^";
protected override IObservable<object> StartListeningCore(WeakReference reference)
{
foreach (var plugin in ExpressionObserver.StreamHandlers)
{
if (plugin.Match(reference))
{
return plugin.Start(reference);
}
}
// TODO: Improve error.
return Observable.Return(new BindingNotification(
new MarkupBindingChainException("Stream operator applied to unsupported type", Description),
BindingErrorType.Error));
}
}
}

2
tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_DataValidation.cs

@ -143,7 +143,7 @@ namespace Avalonia.Markup.UnitTests.Data
Assert.Equal(new[] Assert.Equal(new[]
{ {
new BindingNotification( new BindingNotification(
new MarkupBindingChainNullException("Inner.MustBePositive", "Inner"), new MarkupBindingChainException("Null value", "Inner.MustBePositive", "Inner"),
BindingErrorType.Error, BindingErrorType.Error,
AvaloniaProperty.UnsetValue), AvaloniaProperty.UnsetValue),
}, result); }, result);

51
tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Observable.cs

@ -15,7 +15,7 @@ namespace Avalonia.Markup.UnitTests.Data
public class ExpressionObserverTests_Observable public class ExpressionObserverTests_Observable
{ {
[Fact] [Fact]
public void Should_Get_Simple_Observable_Value() public void Should_Not_Get_Observable_Value_Without_Modifier_Char()
{ {
using (var sync = UnitTestSynchronizationContext.Begin()) using (var sync = UnitTestSynchronizationContext.Begin())
{ {
@ -28,6 +28,24 @@ namespace Avalonia.Markup.UnitTests.Data
source.OnNext("bar"); source.OnNext("bar");
sync.ExecutePostedCallbacks(); sync.ExecutePostedCallbacks();
Assert.Equal(new[] { source }, result);
}
}
[Fact]
public void Should_Get_Simple_Observable_Value()
{
using (var sync = UnitTestSynchronizationContext.Begin())
{
var source = new BehaviorSubject<string>("foo");
var data = new { Foo = source };
var target = new ExpressionObserver(data, "Foo^");
var result = new List<object>();
var sub = target.Subscribe(x => result.Add(x));
source.OnNext("bar");
sync.ExecutePostedCallbacks();
Assert.Equal(new[] { "foo", "bar" }, result); Assert.Equal(new[] { "foo", "bar" }, result);
} }
} }
@ -38,7 +56,7 @@ namespace Avalonia.Markup.UnitTests.Data
using (var sync = UnitTestSynchronizationContext.Begin()) using (var sync = UnitTestSynchronizationContext.Begin())
{ {
var data = new Class1(); var data = new Class1();
var target = new ExpressionObserver(data, "Next.Foo"); var target = new ExpressionObserver(data, "Next^.Foo");
var result = new List<object>(); var result = new List<object>();
var sub = target.Subscribe(x => result.Add(x)); var sub = target.Subscribe(x => result.Add(x));
@ -59,7 +77,7 @@ namespace Avalonia.Markup.UnitTests.Data
{ {
var source = new BehaviorSubject<string>("foo"); var source = new BehaviorSubject<string>("foo");
var data = new { Foo = source }; var data = new { Foo = source };
var target = new ExpressionObserver(data, "Foo", true); var target = new ExpressionObserver(data, "Foo^", true);
var result = new List<object>(); var result = new List<object>();
var sub = target.Subscribe(x => result.Add(x)); var sub = target.Subscribe(x => result.Add(x));
@ -78,7 +96,7 @@ namespace Avalonia.Markup.UnitTests.Data
using (var sync = UnitTestSynchronizationContext.Begin()) using (var sync = UnitTestSynchronizationContext.Begin())
{ {
var data = new Class1(); var data = new Class1();
var target = new ExpressionObserver(data, "Next.Foo", true); var target = new ExpressionObserver(data, "Next^.Foo", true);
var result = new List<object>(); var result = new List<object>();
var sub = target.Subscribe(x => result.Add(x)); var sub = target.Subscribe(x => result.Add(x));
@ -92,6 +110,31 @@ namespace Avalonia.Markup.UnitTests.Data
} }
} }
[Fact]
public void Should_Return_BindingNotification_If_Stream_Operator_Applied_To_Not_Supported_Type()
{
using (var sync = UnitTestSynchronizationContext.Begin())
{
var data = new Class2("foo");
var target = new ExpressionObserver(data, "Foo^", true);
var result = new List<object>();
var sub = target.Subscribe(x => result.Add(x));
sync.ExecutePostedCallbacks();
Assert.Equal(
new[]
{
new BindingNotification(
new MarkupBindingChainException("Stream operator applied to unsupported type", "Foo^", "Foo^"),
BindingErrorType.Error)
},
result);
sub.Dispose();
}
}
private class Class1 : NotifyingBase private class Class1 : NotifyingBase
{ {
public Subject<Class2> Next { get; } = new Subject<Class2>(); public Subject<Class2> Next { get; } = new Subject<Class2>();

4
tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Property.cs

@ -146,7 +146,7 @@ namespace Avalonia.Markup.UnitTests.Data
new[] new[]
{ {
new BindingNotification( new BindingNotification(
new MarkupBindingChainNullException("Foo.Bar.Baz", "Foo"), new MarkupBindingChainException("Null value", "Foo.Bar.Baz", "Foo"),
BindingErrorType.Error, BindingErrorType.Error,
AvaloniaProperty.UnsetValue), AvaloniaProperty.UnsetValue),
}, },
@ -274,7 +274,7 @@ namespace Avalonia.Markup.UnitTests.Data
{ {
"bar", "bar",
new BindingNotification( new BindingNotification(
new MarkupBindingChainNullException("Next.Next.Bar", "Next.Next"), new MarkupBindingChainException("Null value", "Next.Next.Bar", "Next.Next"),
BindingErrorType.Error, BindingErrorType.Error,
AvaloniaProperty.UnsetValue), AvaloniaProperty.UnsetValue),
"bar" "bar"

15
tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Task.cs

@ -15,7 +15,7 @@ namespace Avalonia.Markup.UnitTests.Data
public class ExpressionObserverTests_Task public class ExpressionObserverTests_Task
{ {
[Fact] [Fact]
public void Should_Get_Simple_Task_Value() public void Should_Not_Get_Task_Result_Without_Modifier_Char()
{ {
using (var sync = UnitTestSynchronizationContext.Begin()) using (var sync = UnitTestSynchronizationContext.Begin())
{ {
@ -28,7 +28,8 @@ namespace Avalonia.Markup.UnitTests.Data
tcs.SetResult("foo"); tcs.SetResult("foo");
sync.ExecutePostedCallbacks(); sync.ExecutePostedCallbacks();
Assert.Equal(new[] { "foo" }, result); Assert.Equal(1, result.Count);
Assert.IsType<Task<string>>(result[0]);
} }
} }
@ -38,7 +39,7 @@ namespace Avalonia.Markup.UnitTests.Data
using (var sync = UnitTestSynchronizationContext.Begin()) using (var sync = UnitTestSynchronizationContext.Begin())
{ {
var data = new { Foo = Task.FromResult("foo") }; var data = new { Foo = Task.FromResult("foo") };
var target = new ExpressionObserver(data, "Foo"); var target = new ExpressionObserver(data, "Foo^");
var result = new List<object>(); var result = new List<object>();
var sub = target.Subscribe(x => result.Add(x)); var sub = target.Subscribe(x => result.Add(x));
@ -54,7 +55,7 @@ namespace Avalonia.Markup.UnitTests.Data
{ {
var tcs = new TaskCompletionSource<Class2>(); var tcs = new TaskCompletionSource<Class2>();
var data = new Class1(tcs.Task); var data = new Class1(tcs.Task);
var target = new ExpressionObserver(data, "Next.Foo"); var target = new ExpressionObserver(data, "Next^.Foo");
var result = new List<object>(); var result = new List<object>();
var sub = target.Subscribe(x => result.Add(x)); var sub = target.Subscribe(x => result.Add(x));
@ -72,7 +73,7 @@ namespace Avalonia.Markup.UnitTests.Data
{ {
var tcs = new TaskCompletionSource<string>(); var tcs = new TaskCompletionSource<string>();
var data = new { Foo = tcs.Task }; var data = new { Foo = tcs.Task };
var target = new ExpressionObserver(data, "Foo"); var target = new ExpressionObserver(data, "Foo^");
var result = new List<object>(); var result = new List<object>();
var sub = target.Subscribe(x => result.Add(x)); var sub = target.Subscribe(x => result.Add(x));
@ -96,7 +97,7 @@ namespace Avalonia.Markup.UnitTests.Data
using (var sync = UnitTestSynchronizationContext.Begin()) using (var sync = UnitTestSynchronizationContext.Begin())
{ {
var data = new { Foo = TaskFromException(new NotSupportedException()) }; var data = new { Foo = TaskFromException(new NotSupportedException()) };
var target = new ExpressionObserver(data, "Foo"); var target = new ExpressionObserver(data, "Foo^");
var result = new List<object>(); var result = new List<object>();
var sub = target.Subscribe(x => result.Add(x)); var sub = target.Subscribe(x => result.Add(x));
@ -119,7 +120,7 @@ namespace Avalonia.Markup.UnitTests.Data
{ {
var tcs = new TaskCompletionSource<string>(); var tcs = new TaskCompletionSource<string>();
var data = new { Foo = tcs.Task }; var data = new { Foo = tcs.Task };
var target = new ExpressionObserver(data, "Foo", true); var target = new ExpressionObserver(data, "Foo^", true);
var result = new List<object>(); var result = new List<object>();
var sub = target.Subscribe(x => result.Add(x)); var sub = target.Subscribe(x => result.Add(x));

Loading…
Cancel
Save