Browse Source

Add pattern-based support for streaming. Fix bugs in method bindings.

pull/1667/head
Jeremy Koritzinsky 8 years ago
parent
commit
c6c51dd36f
  1. 4
      src/Avalonia.Base/Data/Core/ExpressionObserver.cs
  2. 14
      src/Avalonia.Base/Data/Core/Parsers/ExpressionVisitorNodeBuilder.cs
  3. 22
      src/Avalonia.Base/Data/Core/StreamBindingExtensions.cs
  4. 41
      tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_ExpressionTree.cs
  5. 2
      tests/Avalonia.Controls.UnitTests/TreeViewTests.cs
  6. 8
      tests/Avalonia.LeakTests/ExpressionObserverTests.cs

4
src/Avalonia.Base/Data/Core/ExpressionObserver.cs

@ -83,7 +83,7 @@ namespace Avalonia.Data.Core
_root = new WeakReference(root);
}
public static ExpressionObserver CreateFromExpression<T, U>(
public static ExpressionObserver Create<T, U>(
T root,
Expression<Func<T, U>> expression,
bool enableDataValidation = false,
@ -113,7 +113,7 @@ namespace Avalonia.Data.Core
_finished = new Subject<Unit>();
}
public static ExpressionObserver CreateFromExpression<T, U>(
public static ExpressionObserver Create<T, U>(
IObservable<T> rootObservable,
Expression<Func<T, U>> expression,
bool enableDataValidation = false,

14
src/Avalonia.Base/Data/Core/Parsers/ExpressionVisitorNodeBuilder.cs

@ -175,6 +175,20 @@ namespace Avalonia.Data.Core.Parsers
return visited;
}
if (node.Method.Name == StreamBindingExtensions.StreamBindingName || node.Method.Name.StartsWith(StreamBindingExtensions.StreamBindingName + '`'))
{
if (node.Method.IsStatic)
{
Visit(node.Arguments[0]);
}
else
{
Visit(node.Object);
}
Nodes.Add(new StreamNode());
return node;
}
throw new ExpressionParseException(0, $"Invalid expression type in binding expression: {node.NodeType}.");
}

22
src/Avalonia.Base/Data/Core/StreamBindingExtensions.cs

@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace Avalonia
{
public static class StreamBindingExtensions
{
internal static string StreamBindingName = "StreamBinding";
public static T StreamBinding<T>(this Task<T> @this)
{
throw new InvalidOperationException("This should be used only in a binding expression");
}
public static T StreamBinding<T>(this IObservable<T> @this)
{
throw new InvalidOperationException("This should be used only in a binding expression");
}
}
}

41
tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_ExpressionTree.cs

@ -16,7 +16,7 @@ namespace Avalonia.Base.UnitTests.Data.Core
{
var target = new object();
var observer = ExpressionObserver.CreateFromExpression(target, o => o);
var observer = ExpressionObserver.Create(target, o => o);
Assert.Equal(target, await observer.Take(1));
GC.KeepAlive(target);
@ -27,7 +27,7 @@ namespace Avalonia.Base.UnitTests.Data.Core
{
var target = new Class1();
var observer = ExpressionObserver.CreateFromExpression(target, o => o.Foo);
var observer = ExpressionObserver.Create(target, o => o.Foo);
Assert.Null(await observer.Take(1));
@ -45,7 +45,7 @@ namespace Avalonia.Base.UnitTests.Data.Core
public void Property_Acccess_Expression_Can_Set_Property()
{
var data = new Class1();
var target = ExpressionObserver.CreateFromExpression(data, o => o.Foo);
var target = ExpressionObserver.Create(data, o => o.Foo);
using (target.Subscribe(_ => { }))
{
@ -60,7 +60,7 @@ namespace Avalonia.Base.UnitTests.Data.Core
{
var data = new[] { 1, 2, 3, 4 };
var target = ExpressionObserver.CreateFromExpression(data, o => o[0]);
var target = ExpressionObserver.Create(data, o => o[0]);
Assert.Equal(data[0], await target.Take(1));
GC.KeepAlive(data);
@ -71,7 +71,7 @@ namespace Avalonia.Base.UnitTests.Data.Core
{
var data = new List<int> { 1, 2, 3, 4 };
var target = ExpressionObserver.CreateFromExpression(data, o => o[0]);
var target = ExpressionObserver.Create(data, o => o[0]);
Assert.Equal(data[0], await target.Take(1));
GC.KeepAlive(data);
@ -86,7 +86,7 @@ namespace Avalonia.Base.UnitTests.Data.Core
data.Add(key, new object());
var target = ExpressionObserver.CreateFromExpression(data, o => o[key]);
var target = ExpressionObserver.Create(data, o => o[key]);
Assert.Equal(data[key], await target.Take(1));
@ -98,7 +98,7 @@ namespace Avalonia.Base.UnitTests.Data.Core
{
var data = new[] { 1, 2, 3, 4 };
var target = ExpressionObserver.CreateFromExpression(data, o => o[0]);
var target = ExpressionObserver.Create(data, o => o[0]);
using (target.Subscribe(_ => { }))
{
@ -113,7 +113,7 @@ namespace Avalonia.Base.UnitTests.Data.Core
{
NotifyingBase test = new Class1 { Foo = "Test" };
var target = ExpressionObserver.CreateFromExpression(test, o => ((Class1)o).Foo);
var target = ExpressionObserver.Create(test, o => ((Class1)o).Foo);
Assert.Equal("Test", await target.Take(1));
@ -125,7 +125,7 @@ namespace Avalonia.Base.UnitTests.Data.Core
{
var test = 1;
Assert.Throws<ExpressionParseException>(() => ExpressionObserver.CreateFromExpression(test, o => (double)o));
Assert.Throws<ExpressionParseException>(() => ExpressionObserver.Create(test, o => (double)o));
}
[Fact]
@ -133,7 +133,7 @@ namespace Avalonia.Base.UnitTests.Data.Core
{
NotifyingBase test = new Class1 { Foo = "Test" };
var target = ExpressionObserver.CreateFromExpression(test, o => (o as Class1).Foo);
var target = ExpressionObserver.Create(test, o => (o as Class1).Foo);
Assert.Equal("Test", await target.Take(1));
@ -145,7 +145,7 @@ namespace Avalonia.Base.UnitTests.Data.Core
{
var test = new Class2();
var target = ExpressionObserver.CreateFromExpression(test, o => o[Class2.FooProperty]);
var target = ExpressionObserver.Create(test, o => o[Class2.FooProperty]);
Assert.Equal("foo", await target.Take(1));
@ -157,13 +157,30 @@ namespace Avalonia.Base.UnitTests.Data.Core
{
var test = new Class1 { Foo = "Test" };
var target = ExpressionObserver.CreateFromExpression(test, o => o.Foo.Length);
var target = ExpressionObserver.Create(test, o => o.Foo.Length);
Assert.Equal(test.Foo.Length, await target.Take(1));
GC.KeepAlive(test);
}
[Fact]
public void Should_Get_Completed_Task_Value()
{
using (var sync = UnitTestSynchronizationContext.Begin())
{
var data = new { Foo = Task.FromResult("foo") };
var target = ExpressionObserver.Create(data, o => o.Foo.StreamBinding());
var result = new List<object>();
var sub = target.Subscribe(x => result.Add(x));
Assert.Equal(new[] { "foo" }, result);
GC.KeepAlive(data);
}
}
private class Class1 : NotifyingBase
{
private string _foo;

2
tests/Avalonia.Controls.UnitTests/TreeViewTests.cs

@ -515,7 +515,7 @@ namespace Avalonia.Controls.UnitTests
public InstancedBinding ItemsSelector(object item)
{
var obs = ExpressionObserver.CreateFromExpression(item, o => (o as Node).Children);
var obs = ExpressionObserver.Create(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 = ExpressionObserver.CreateFromExpression(source, o => o.Foo);
var target = ExpressionObserver.Create(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 = ExpressionObserver.CreateFromExpression(source, o => o.Foo, true);
var target = ExpressionObserver.Create(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 = ExpressionObserver.CreateFromExpression(source, o => o.Foo);
var target = ExpressionObserver.Create(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 = ExpressionObserver.CreateFromExpression(source, o => (Action)o.Foo.A);
var target = ExpressionObserver.Create(source, o => (Action)o.Foo.A);
target.Subscribe(_ => { });
return target;
};

Loading…
Cancel
Save