From 4d30de7c09b3b39efaf7a2c622a84bb4df56af89 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 5 Jun 2017 20:30:07 +0200 Subject: [PATCH 01/15] Only click when pointer is over the button. Previously a button click was carried out when `:pointerover` was set, but because the button captures mouse input on mouse down, this is always true while the mouse is held. Check that the pointer was released within the bounds of the control. Fixes #938 --- src/Avalonia.Controls/Button.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Button.cs b/src/Avalonia.Controls/Button.cs index 7ed1c7fd8c..2b3bbc8ad2 100644 --- a/src/Avalonia.Controls/Button.cs +++ b/src/Avalonia.Controls/Button.cs @@ -234,7 +234,7 @@ namespace Avalonia.Controls PseudoClasses.Remove(":pressed"); e.Handled = true; - if (ClickMode == ClickMode.Release && Classes.Contains(":pointerover")) + if (ClickMode == ClickMode.Release && new Rect(Bounds.Size).Contains(e.GetPosition(this))) { RaiseClickEvent(); } From 1f9426b345d91f13f8699fdebf33d0d0b3e8169d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Thu, 8 Jun 2017 22:28:15 +0200 Subject: [PATCH 02/15] Fix InvariantCultureFixture --- .../Data/BindingExpressionTests.cs | 65 ------------------- .../InvariantCultureFixture.cs | 9 +++ 2 files changed, 9 insertions(+), 65 deletions(-) diff --git a/tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs b/tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs index a08dfa39a6..ff9db18999 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs @@ -51,11 +51,6 @@ namespace Avalonia.Markup.UnitTests.Data [Fact] public async void Should_Convert_Get_String_To_Double() { -#if NET461 - Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture; -#else - CultureInfo.CurrentUICulture = CultureInfo.InvariantCulture; -#endif var data = new Class1 { StringValue = "5.6" }; var target = new BindingExpression(new ExpressionObserver(data, "StringValue"), typeof(double)); var result = await target.Take(1); @@ -86,12 +81,6 @@ namespace Avalonia.Markup.UnitTests.Data [Fact] public void Should_Convert_Set_String_To_Double() { -#if NET461 - Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture; -#else - CultureInfo.CurrentUICulture = CultureInfo.InvariantCulture; -#endif - var data = new Class1 { StringValue = (5.6).ToString() }; var target = new BindingExpression(new ExpressionObserver(data, "StringValue"), typeof(double)); @@ -103,12 +92,6 @@ namespace Avalonia.Markup.UnitTests.Data [Fact] public async void Should_Convert_Get_Double_To_String() { -#if NET461 - Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture; -#else - CultureInfo.CurrentUICulture = CultureInfo.InvariantCulture; -#endif - var data = new Class1 { DoubleValue = 5.6 }; var target = new BindingExpression(new ExpressionObserver(data, "DoubleValue"), typeof(string)); var result = await target.Take(1); @@ -119,12 +102,6 @@ namespace Avalonia.Markup.UnitTests.Data [Fact] public void Should_Convert_Set_Double_To_String() { -#if NET461 - Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture; -#else - CultureInfo.CurrentUICulture = CultureInfo.InvariantCulture; -#endif - var data = new Class1 { DoubleValue = 5.6 }; var target = new BindingExpression(new ExpressionObserver(data, "DoubleValue"), typeof(string)); @@ -136,12 +113,6 @@ namespace Avalonia.Markup.UnitTests.Data [Fact] public async void Should_Return_BindingNotification_With_FallbackValue_For_NonConvertibe_Target_Value() { -#if NET461 - Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture; -#else - CultureInfo.CurrentUICulture = CultureInfo.InvariantCulture; -#endif - var data = new Class1 { StringValue = "foo" }; var target = new BindingExpression( new ExpressionObserver(data, "StringValue"), @@ -161,12 +132,6 @@ namespace Avalonia.Markup.UnitTests.Data [Fact] public async void Should_Return_BindingNotification_With_FallbackValue_For_NonConvertibe_Target_Value_With_Data_Validation() { -#if NET461 - Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture; -#else - CultureInfo.CurrentUICulture = CultureInfo.InvariantCulture; -#endif - var data = new Class1 { StringValue = "foo" }; var target = new BindingExpression( new ExpressionObserver(data, "StringValue", true), @@ -186,12 +151,6 @@ namespace Avalonia.Markup.UnitTests.Data [Fact(Skip="Result is not always AggregateException.")] public async void Should_Return_BindingNotification_For_Invalid_FallbackValue() { -#if NET461 - Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture; -#else - CultureInfo.CurrentUICulture = CultureInfo.InvariantCulture; -#endif - var data = new Class1 { StringValue = "foo" }; var target = new BindingExpression( new ExpressionObserver(data, "StringValue"), @@ -212,12 +171,6 @@ namespace Avalonia.Markup.UnitTests.Data [Fact(Skip="Result is not always AggregateException.")] public async void Should_Return_BindingNotification_For_Invalid_FallbackValue_With_Data_Validation() { -#if NET461 - Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture; -#else - CultureInfo.CurrentUICulture = CultureInfo.InvariantCulture; -#endif - var data = new Class1 { StringValue = "foo" }; var target = new BindingExpression( new ExpressionObserver(data, "StringValue", true), @@ -286,12 +239,6 @@ namespace Avalonia.Markup.UnitTests.Data [Fact] public void Should_Pass_ConverterParameter_To_Convert() { -#if NET461 - Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture; -#else - CultureInfo.CurrentUICulture = CultureInfo.InvariantCulture; -#endif - var data = new Class1 { DoubleValue = 5.6 }; var converter = new Mock(); var target = new BindingExpression( @@ -308,12 +255,6 @@ namespace Avalonia.Markup.UnitTests.Data [Fact] public void Should_Pass_ConverterParameter_To_ConvertBack() { -#if NET461 - Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture; -#else - CultureInfo.CurrentUICulture = CultureInfo.InvariantCulture; -#endif - var data = new Class1 { DoubleValue = 5.6 }; var converter = new Mock(); var target = new BindingExpression( @@ -330,12 +271,6 @@ namespace Avalonia.Markup.UnitTests.Data [Fact] public void Should_Handle_DataValidation() { -#if NET461 - Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture; -#else - CultureInfo.CurrentUICulture = CultureInfo.InvariantCulture; -#endif - var data = new Class1 { DoubleValue = 5.6 }; var converter = new Mock(); var target = new BindingExpression(new ExpressionObserver(data, "DoubleValue", true), typeof(string)); diff --git a/tests/Avalonia.UnitTests/InvariantCultureFixture.cs b/tests/Avalonia.UnitTests/InvariantCultureFixture.cs index f27df8f599..685142ad02 100644 --- a/tests/Avalonia.UnitTests/InvariantCultureFixture.cs +++ b/tests/Avalonia.UnitTests/InvariantCultureFixture.cs @@ -20,13 +20,22 @@ namespace Avalonia.UnitTests public InvariantCultureFixture() { +#if NET461 + _restore = Thread.CurrentThread.CurrentUICulture; + Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; +#else _restore = CultureInfo.CurrentUICulture; CultureInfo.CurrentUICulture = CultureInfo.CurrentCulture = CultureInfo.InvariantCulture; +#endif } public void Dispose() { +#if NET461 + Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = _restore; +#else CultureInfo.CurrentUICulture = CultureInfo.CurrentCulture = _restore; +#endif } } } From 8b0ebc135e43cab95ce592aab20f1cd4d6088f04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Thu, 8 Jun 2017 22:51:41 +0200 Subject: [PATCH 03/15] Skip failing Moq tests --- .../Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs b/tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs index ff9db18999..4ca998adb8 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs @@ -252,7 +252,7 @@ namespace Avalonia.Markup.UnitTests.Data converter.Verify(x => x.Convert(5.6, typeof(string), "foo", CultureInfo.InvariantCulture)); } - [Fact] + [Fact(Skip="Moq.MockException")] public void Should_Pass_ConverterParameter_To_ConvertBack() { var data = new Class1 { DoubleValue = 5.6 }; @@ -268,7 +268,7 @@ namespace Avalonia.Markup.UnitTests.Data converter.Verify(x => x.ConvertBack("bar", typeof(double), "foo", CultureInfo.InvariantCulture)); } - [Fact] + [Fact(Skip="Moq.MockException")] public void Should_Handle_DataValidation() { var data = new Class1 { DoubleValue = 5.6 }; From 9450cbe4fd8af843e3c38c6e993b22a936cbd364 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Thu, 8 Jun 2017 23:05:46 +0200 Subject: [PATCH 04/15] Skip failing Moq tests --- tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs b/tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs index 4ca998adb8..49d3817347 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs @@ -236,7 +236,7 @@ namespace Avalonia.Markup.UnitTests.Data Assert.Equal(0, data.DoubleValue); } - [Fact] + [Fact(Skip="Moq.MockException")] public void Should_Pass_ConverterParameter_To_Convert() { var data = new Class1 { DoubleValue = 5.6 }; From 82483d7dec6e906c4764c6123133648049ccd932 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 21 Jun 2017 09:57:39 +0200 Subject: [PATCH 05/15] Fix exception in ToolTip. Make sure old `ToolTip` is disposed before showing a new one. --- src/Avalonia.Controls/ToolTip.cs | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/Avalonia.Controls/ToolTip.cs b/src/Avalonia.Controls/ToolTip.cs index b8896a3acf..fef10d3510 100644 --- a/src/Avalonia.Controls/ToolTip.cs +++ b/src/Avalonia.Controls/ToolTip.cs @@ -105,13 +105,10 @@ namespace Avalonia.Controls { if (control != null && control.IsVisible && control.GetVisualRoot() != null) { - if (s_popup != null) - { - throw new AvaloniaInternalException("Previous ToolTip not disposed."); - } var cp = (control.GetVisualRoot() as IInputRoot)?.MouseDevice?.GetPosition(control); var position = control.PointToScreen(cp ?? new Point(0, 0)) + new Vector(0, 22); + DisposeTooltip(); s_popup = new PopupRoot(); ((ISetLogicalParent)s_popup).SetParent(control); s_popup.Content = new ToolTip { Content = GetTip(control) }; @@ -144,18 +141,22 @@ namespace Avalonia.Controls if (control == s_current) { - if (s_popup != null) - { - // Clear the ToolTip's Content in case it has control content: this will - // reset its visual parent allowing it to be used again. - ((ToolTip)s_popup.Content).Content = null; + DisposeTooltip(); + s_show.OnNext(null); + } + } - // Dispose of the popup. - s_popup.Dispose(); - s_popup = null; - } + private static void DisposeTooltip() + { + if (s_popup != null) + { + // Clear the ToolTip's Content in case it has control content: this will + // reset its visual parent allowing it to be used again. + ((ToolTip)s_popup.Content).Content = null; - s_show.OnNext(null); + // Dispose of the popup. + s_popup.Dispose(); + s_popup = null; } } } From a232b137b5d7aa2605431c65f33d9e8d9d8cf130 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 24 Jun 2017 12:40:41 +0200 Subject: [PATCH 06/15] Allow reuse of existing tooltip popup. To do this, had to fix a problem where templated children weren't notified of being re-attached to a logical tree. --- src/Avalonia.Controls/Control.cs | 11 +- .../Primitives/TemplatedControl.cs | 11 ++ src/Avalonia.Controls/ToolTip.cs | 21 +++- src/Avalonia.Styling/LogicalTree/ILogical.cs | 10 ++ .../Primitives/PopupRootTests.cs | 109 ++++++++++++++++++ .../Primitives/TemplatedControlTests.cs | 28 +++++ .../TopLevelTests.cs | 19 ++- .../SelectorTests_Child.cs | 5 + 8 files changed, 202 insertions(+), 12 deletions(-) create mode 100644 tests/Avalonia.Controls.UnitTests/Primitives/PopupRootTests.cs diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index 758f7bbf55..83a76cb1a7 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -118,6 +118,7 @@ namespace Avalonia.Controls public Control() { _nameScope = this as INameScope; + _isAttachedToLogicalTree = this is IStyleRoot; } /// @@ -369,6 +370,12 @@ namespace Avalonia.Controls } } + /// + void ILogical.NotifyAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) + { + this.OnAttachedToLogicalTreeCore(e); + } + /// void ILogical.NotifyDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) { @@ -418,7 +425,7 @@ namespace Avalonia.Controls if (_isAttachedToLogicalTree) { - var oldRoot = FindStyleRoot(old); + var oldRoot = FindStyleRoot(old) ?? this as IStyleRoot; if (oldRoot == null) { @@ -436,7 +443,7 @@ namespace Avalonia.Controls _parent = (IControl)parent; - if (_parent is IStyleRoot || _parent?.IsAttachedToLogicalTree == true) + if (_parent is IStyleRoot || _parent?.IsAttachedToLogicalTree == true || this is IStyleRoot) { var newRoot = FindStyleRoot(this); diff --git a/src/Avalonia.Controls/Primitives/TemplatedControl.cs b/src/Avalonia.Controls/Primitives/TemplatedControl.cs index 7a42c48053..1ddfb97c14 100644 --- a/src/Avalonia.Controls/Primitives/TemplatedControl.cs +++ b/src/Avalonia.Controls/Primitives/TemplatedControl.cs @@ -285,6 +285,17 @@ namespace Avalonia.Controls.Primitives return this; } + /// + protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) + { + if (VisualChildren.Count > 0) + { + ((ILogical)VisualChildren[0]).NotifyAttachedToLogicalTree(e); + } + + base.OnAttachedToLogicalTree(e); + } + /// protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) { diff --git a/src/Avalonia.Controls/ToolTip.cs b/src/Avalonia.Controls/ToolTip.cs index fef10d3510..22bc589a36 100644 --- a/src/Avalonia.Controls/ToolTip.cs +++ b/src/Avalonia.Controls/ToolTip.cs @@ -108,10 +108,18 @@ namespace Avalonia.Controls var cp = (control.GetVisualRoot() as IInputRoot)?.MouseDevice?.GetPosition(control); var position = control.PointToScreen(cp ?? new Point(0, 0)) + new Vector(0, 22); - DisposeTooltip(); - s_popup = new PopupRoot(); + if (s_popup == null) + { + s_popup = new PopupRoot(); + s_popup.Content = new ToolTip(); + } + else + { + ((ISetLogicalParent)s_popup).SetParent(null); + } + ((ISetLogicalParent)s_popup).SetParent(control); - s_popup.Content = new ToolTip { Content = GetTip(control) }; + ((ToolTip)s_popup.Content).Content = GetTip(control); s_popup.Position = position; s_popup.Show(); @@ -141,8 +149,11 @@ namespace Avalonia.Controls if (control == s_current) { - DisposeTooltip(); - s_show.OnNext(null); + if (s_popup != null) + { + DisposeTooltip(); + s_show.OnNext(null); + } } } diff --git a/src/Avalonia.Styling/LogicalTree/ILogical.cs b/src/Avalonia.Styling/LogicalTree/ILogical.cs index f2291b42e9..006a9f5cc1 100644 --- a/src/Avalonia.Styling/LogicalTree/ILogical.cs +++ b/src/Avalonia.Styling/LogicalTree/ILogical.cs @@ -36,6 +36,16 @@ namespace Avalonia.LogicalTree /// IAvaloniaReadOnlyList LogicalChildren { get; } + /// + /// Notifies the control that it is being attached to a rooted logical tree. + /// + /// The event args. + /// + /// This method will be called automatically by the framework, you should not need to call + /// this method yourself. + /// + void NotifyAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e); + /// /// Notifies the control that it is being detached from a rooted logical tree. /// diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/PopupRootTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/PopupRootTests.cs new file mode 100644 index 0000000000..64344a1584 --- /dev/null +++ b/tests/Avalonia.Controls.UnitTests/Primitives/PopupRootTests.cs @@ -0,0 +1,109 @@ +// 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.Controls.Presenters; +using Avalonia.Controls.Primitives; +using Avalonia.Controls.Templates; +using Avalonia.LogicalTree; +using Avalonia.UnitTests; +using Xunit; + +namespace Avalonia.Controls.UnitTests.Primitives +{ + public class PopupRootTests + { + [Fact] + public void PopupRoot_IsAttachedToLogicalTree_Is_True() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var target = CreateTarget(); + + Assert.True(((ILogical)target).IsAttachedToLogicalTree); + } + } + + [Fact] + public void Templated_Child_IsAttachedToLogicalTree_Is_True() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var target = CreateTarget(); + + Assert.True(target.Presenter.IsAttachedToLogicalTree); + } + } + + [Fact] + public void Attaching_PopupRoot_To_Parent_Logical_Tree_Raises_DetachedFromLogicalTree_And_AttachedToLogicalTree() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var child = new Decorator(); + var target = CreateTarget(); + var window = new Window(); + var detachedCount = 0; + var attachedCount = 0; + + target.Content = child; + + target.DetachedFromLogicalTree += (s, e) => ++detachedCount; + child.DetachedFromLogicalTree += (s, e) => ++detachedCount; + target.AttachedToLogicalTree += (s, e) => ++attachedCount; + child.AttachedToLogicalTree += (s, e) => ++attachedCount; + + ((ISetLogicalParent)target).SetParent(window); + + Assert.Equal(2, detachedCount); + Assert.Equal(2, attachedCount); + } + } + + [Fact] + public void Detaching_PopupRoot_From_Parent_Logical_Tree_Raises_DetachedFromLogicalTree_And_AttachedToLogicalTree() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var child = new Decorator(); + var target = CreateTarget(); + var window = new Window(); + var detachedCount = 0; + var attachedCount = 0; + + target.Content = child; + ((ISetLogicalParent)target).SetParent(window); + + target.DetachedFromLogicalTree += (s, e) => ++detachedCount; + child.DetachedFromLogicalTree += (s, e) => ++detachedCount; + target.AttachedToLogicalTree += (s, e) => ++attachedCount; + child.AttachedToLogicalTree += (s, e) => ++attachedCount; + + ((ISetLogicalParent)target).SetParent(null); + + // Despite being detached from the parent logical tree, we're still attached to a + // logical tree as PopupRoot itself is a logical tree root. + Assert.True(((ILogical)target).IsAttachedToLogicalTree); + Assert.True(((ILogical)child).IsAttachedToLogicalTree); + Assert.Equal(2, detachedCount); + Assert.Equal(2, attachedCount); + } + } + + private PopupRoot CreateTarget() + { + var result = new PopupRoot + { + Template = new FuncControlTemplate(_ => + new ContentPresenter + { + Name = "PART_ContentPresenter", + }), + }; + + result.ApplyTemplate(); + + return result; + } + } +} diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests.cs index 636492ed1c..72c8073f21 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests.cs @@ -527,6 +527,34 @@ namespace Avalonia.Controls.UnitTests.Primitives } } + [Fact] + public void Moving_To_New_LogicalTree_Should_Detach_Attach_Template_Child() + { + using (UnitTestApplication.Start(TestServices.RealStyler)) + { + TestTemplatedControl target; + var root = new TestRoot + { + Child = target = new TestTemplatedControl + { + Template = new FuncControlTemplate(_ => new Decorator()), + } + }; + + Assert.NotNull(target.Template); + target.ApplyTemplate(); + + var templateChild = (ILogical)target.GetVisualChildren().Single(); + Assert.True(templateChild.IsAttachedToLogicalTree); + + root.Child = null; + Assert.False(templateChild.IsAttachedToLogicalTree); + + var newRoot = new TestRoot { Child = target }; + Assert.True(templateChild.IsAttachedToLogicalTree); + } + } + private static IControl ScrollingContentControlTemplate(ContentControl control) { return new Border diff --git a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs index 5cd3c57e2e..da30336be6 100644 --- a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs @@ -2,24 +2,33 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; -using System.Reactive; -using System.Reactive.Subjects; -using Moq; using Avalonia.Controls.Presenters; using Avalonia.Controls.Templates; using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.Layout; +using Avalonia.LogicalTree; using Avalonia.Platform; -using Avalonia.Rendering; -using Avalonia.Styling; using Avalonia.UnitTests; +using Moq; using Xunit; namespace Avalonia.Controls.UnitTests { public class TopLevelTests { + [Fact] + public void IsAttachedToLogicalTree_Is_True() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var impl = new Mock(); + var target = new TestTopLevel(impl.Object); + + Assert.True(((ILogical)target).IsAttachedToLogicalTree); + } + } + [Fact] public void ClientSize_Should_Be_Set_On_Construction() { diff --git a/tests/Avalonia.Styling.UnitTests/SelectorTests_Child.cs b/tests/Avalonia.Styling.UnitTests/SelectorTests_Child.cs index d97cc74c95..b40c66e061 100644 --- a/tests/Avalonia.Styling.UnitTests/SelectorTests_Child.cs +++ b/tests/Avalonia.Styling.UnitTests/SelectorTests_Child.cs @@ -144,6 +144,11 @@ namespace Avalonia.Styling.UnitTests throw new NotImplementedException(); } + public void NotifyAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) + { + throw new NotImplementedException(); + } + public void NotifyDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) { throw new NotImplementedException(); From af50118162714e3e12523a8582faef71db9a13f9 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 24 Jun 2017 13:11:07 +0200 Subject: [PATCH 07/15] Added missing method. Why didn't you notice that before, VS? --- tests/Avalonia.Styling.UnitTests/SelectorTests_Descendent.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/Avalonia.Styling.UnitTests/SelectorTests_Descendent.cs b/tests/Avalonia.Styling.UnitTests/SelectorTests_Descendent.cs index b4c284e7c9..7cf8c3dd1c 100644 --- a/tests/Avalonia.Styling.UnitTests/SelectorTests_Descendent.cs +++ b/tests/Avalonia.Styling.UnitTests/SelectorTests_Descendent.cs @@ -175,6 +175,11 @@ namespace Avalonia.Styling.UnitTests throw new NotImplementedException(); } + public void NotifyAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) + { + throw new NotImplementedException(); + } + public void NotifyDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) { throw new NotImplementedException(); From bcc3ca13aed30ebbd37c90f526b3832f691a777a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 24 Jun 2017 16:12:38 +0200 Subject: [PATCH 08/15] Don't use WeakReference in BindingNotification. It's a bad idea - if you put say a `3` into a `BindingNotification`, that `int` will get boxed and then put into a `WeakReference`, which means that the `3` can get GC'd. That's not a desireable behavior! --- src/Avalonia.Base/Data/BindingNotification.cs | 36 ++++--------------- 1 file changed, 7 insertions(+), 29 deletions(-) diff --git a/src/Avalonia.Base/Data/BindingNotification.cs b/src/Avalonia.Base/Data/BindingNotification.cs index ecaf59e174..4cae0e6afa 100644 --- a/src/Avalonia.Base/Data/BindingNotification.cs +++ b/src/Avalonia.Base/Data/BindingNotification.cs @@ -44,11 +44,7 @@ namespace Avalonia.Data public static readonly BindingNotification UnsetValue = new BindingNotification(AvaloniaProperty.UnsetValue); - // Null cannot be held in WeakReference as it's indistinguishable from an expired value so - // use this value in its place. - private static readonly object NullValue = new object(); - - private WeakReference _value; + private object _value; /// /// Initializes a new instance of the class. @@ -56,7 +52,7 @@ namespace Avalonia.Data /// The binding value. public BindingNotification(object value) { - _value = new WeakReference(value ?? NullValue); + _value = value; } /// @@ -73,6 +69,7 @@ namespace Avalonia.Data Error = error; ErrorType = errorType; + _value = AvaloniaProperty.UnsetValue; } /// @@ -84,7 +81,7 @@ namespace Avalonia.Data public BindingNotification(Exception error, BindingErrorType errorType, object fallbackValue) : this(error, errorType) { - _value = new WeakReference(fallbackValue ?? NullValue); + _value = fallbackValue; } /// @@ -95,31 +92,12 @@ namespace Avalonia.Data /// If this property is read when is false then it will return /// . /// - public object Value - { - get - { - if (_value != null) - { - object result; - - if (_value.TryGetTarget(out result)) - { - return result == NullValue ? null : result; - } - } - - // There's the possibility of a race condition in that HasValue can return true, - // and then the value is GC'd before Value is read. We should be ok though as - // we return UnsetValue which should be a safe alternative. - return AvaloniaProperty.UnsetValue; - } - } + public object Value => _value; /// /// Gets a value indicating whether should be pushed to the target. /// - public bool HasValue => _value != null; + public bool HasValue => _value != AvaloniaProperty.UnsetValue; /// /// Gets the error that occurred on the source, if any. @@ -256,7 +234,7 @@ namespace Avalonia.Data /// public void SetValue(object value) { - _value = new WeakReference(value ?? NullValue); + _value = value; } /// From 11586a4d4c0a490330cf09c924328f279ecf31e3 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 25 Jun 2017 12:49:30 +0200 Subject: [PATCH 09/15] Don't have async void tests. That's bad. Also un-skip binding expression tests. --- .../ControlLocatorTests.cs | 3 +- .../Data/BindingExpressionTests.cs | 28 ++++++++++--------- ...xpressionObserverTests_AttachedProperty.cs | 5 ++-- ...xpressionObserverTests_AvaloniaProperty.cs | 3 +- .../Data/ExpressionObserverTests_Indexer.cs | 23 +++++++-------- .../Data/ExpressionObserverTests_Negation.cs | 15 +++++----- .../Data/ExpressionObserverTests_Property.cs | 19 +++++++------ .../Data/MultiBindingTests.cs | 3 +- .../SelectorTests_Child.cs | 3 +- 9 files changed, 56 insertions(+), 46 deletions(-) diff --git a/tests/Avalonia.Markup.UnitTests/ControlLocatorTests.cs b/tests/Avalonia.Markup.UnitTests/ControlLocatorTests.cs index b9e1cb353a..a5414f1e8c 100644 --- a/tests/Avalonia.Markup.UnitTests/ControlLocatorTests.cs +++ b/tests/Avalonia.Markup.UnitTests/ControlLocatorTests.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Reactive.Linq; +using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.UnitTests; using Xunit; @@ -13,7 +14,7 @@ namespace Avalonia.Markup.UnitTests public class ControlLocatorTests { [Fact] - public async void Track_By_Name_Should_Find_Control_Added_Earlier() + public async Task Track_By_Name_Should_Find_Control_Added_Earlier() { TextBlock target; TextBlock relativeTo; diff --git a/tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs b/tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs index 49d3817347..c6b1fb7b0f 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Globalization; using System.Reactive.Linq; using System.Threading; +using System.Threading.Tasks; using Avalonia.Data; using Avalonia.Markup.Data; using Avalonia.UnitTests; @@ -17,7 +18,7 @@ namespace Avalonia.Markup.UnitTests.Data public class BindingExpressionTests : IClassFixture { [Fact] - public async void Should_Get_Simple_Property_Value() + public async Task Should_Get_Simple_Property_Value() { var data = new Class1 { StringValue = "foo" }; var target = new BindingExpression(new ExpressionObserver(data, "StringValue"), typeof(string)); @@ -49,7 +50,7 @@ namespace Avalonia.Markup.UnitTests.Data } [Fact] - public async void Should_Convert_Get_String_To_Double() + 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)); @@ -59,7 +60,7 @@ namespace Avalonia.Markup.UnitTests.Data } [Fact] - public async void Getting_Invalid_Double_String_Should_Return_BindingError() + 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)); @@ -69,7 +70,7 @@ namespace Avalonia.Markup.UnitTests.Data } [Fact] - public async void Should_Coerce_Get_Null_Double_String_To_UnsetValue() + 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)); @@ -90,7 +91,7 @@ namespace Avalonia.Markup.UnitTests.Data } [Fact] - public async void Should_Convert_Get_Double_To_String() + 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)); @@ -111,7 +112,7 @@ namespace Avalonia.Markup.UnitTests.Data } [Fact] - public async void Should_Return_BindingNotification_With_FallbackValue_For_NonConvertibe_Target_Value() + public async Task Should_Return_BindingNotification_With_FallbackValue_For_NonConvertibe_Target_Value() { var data = new Class1 { StringValue = "foo" }; var target = new BindingExpression( @@ -130,7 +131,7 @@ namespace Avalonia.Markup.UnitTests.Data } [Fact] - public async void Should_Return_BindingNotification_With_FallbackValue_For_NonConvertibe_Target_Value_With_Data_Validation() + public async Task Should_Return_BindingNotification_With_FallbackValue_For_NonConvertibe_Target_Value_With_Data_Validation() { var data = new Class1 { StringValue = "foo" }; var target = new BindingExpression( @@ -149,7 +150,7 @@ namespace Avalonia.Markup.UnitTests.Data } [Fact(Skip="Result is not always AggregateException.")] - public async void Should_Return_BindingNotification_For_Invalid_FallbackValue() + public async Task Should_Return_BindingNotification_For_Invalid_FallbackValue() { var data = new Class1 { StringValue = "foo" }; var target = new BindingExpression( @@ -169,7 +170,7 @@ namespace Avalonia.Markup.UnitTests.Data } [Fact(Skip="Result is not always AggregateException.")] - public async void Should_Return_BindingNotification_For_Invalid_FallbackValue_With_Data_Validation() + public async Task Should_Return_BindingNotification_For_Invalid_FallbackValue_With_Data_Validation() { var data = new Class1 { StringValue = "foo" }; var target = new BindingExpression( @@ -236,11 +237,12 @@ namespace Avalonia.Markup.UnitTests.Data Assert.Equal(0, data.DoubleValue); } - [Fact(Skip="Moq.MockException")] + [Fact] public void Should_Pass_ConverterParameter_To_Convert() { var data = new Class1 { DoubleValue = 5.6 }; var converter = new Mock(); + var target = new BindingExpression( new ExpressionObserver(data, "DoubleValue"), typeof(string), @@ -249,10 +251,10 @@ namespace Avalonia.Markup.UnitTests.Data target.Subscribe(_ => { }); - converter.Verify(x => x.Convert(5.6, typeof(string), "foo", CultureInfo.InvariantCulture)); + converter.Verify(x => x.Convert(5.6, typeof(string), "foo", CultureInfo.CurrentCulture)); } - [Fact(Skip="Moq.MockException")] + [Fact] public void Should_Pass_ConverterParameter_To_ConvertBack() { var data = new Class1 { DoubleValue = 5.6 }; @@ -265,7 +267,7 @@ namespace Avalonia.Markup.UnitTests.Data target.OnNext("bar"); - converter.Verify(x => x.ConvertBack("bar", typeof(double), "foo", CultureInfo.InvariantCulture)); + converter.Verify(x => x.ConvertBack("bar", typeof(double), "foo", CultureInfo.CurrentCulture)); } [Fact(Skip="Moq.MockException")] diff --git a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_AttachedProperty.cs b/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_AttachedProperty.cs index 349a09da2c..a8069cb75c 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_AttachedProperty.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_AttachedProperty.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Reactive.Linq; +using System.Threading.Tasks; using Avalonia.Diagnostics; using Avalonia.Markup.Data; using Xunit; @@ -18,7 +19,7 @@ namespace Avalonia.Markup.UnitTests.Data } [Fact] - public async void Should_Get_Attached_Property_Value() + public async Task Should_Get_Attached_Property_Value() { var data = new Class1(); var target = new ExpressionObserver(data, "(Owner.Foo)"); @@ -30,7 +31,7 @@ namespace Avalonia.Markup.UnitTests.Data } [Fact] - public async void Should_Get_Chained_Attached_Property_Value() + public async Task Should_Get_Chained_Attached_Property_Value() { var data = new Class1 { diff --git a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_AvaloniaProperty.cs b/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_AvaloniaProperty.cs index ece9437308..cd691daaf9 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_AvaloniaProperty.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_AvaloniaProperty.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Reactive.Linq; +using System.Threading.Tasks; using Avalonia.Diagnostics; using Avalonia.Markup.Data; using Xunit; @@ -18,7 +19,7 @@ namespace Avalonia.Markup.UnitTests.Data } [Fact] - public async void Should_Get_Simple_Property_Value() + public async Task Should_Get_Simple_Property_Value() { var data = new Class1(); var target = new ExpressionObserver(data, "Foo"); diff --git a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Indexer.cs b/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Indexer.cs index 9cc843381c..135ec0f4db 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Indexer.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Indexer.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Reactive.Linq; +using System.Threading.Tasks; using Avalonia.Collections; using Avalonia.Diagnostics; using Avalonia.Markup.Data; @@ -16,7 +17,7 @@ namespace Avalonia.Markup.UnitTests.Data public class ExpressionObserverTests_Indexer { [Fact] - public async void Should_Get_Array_Value() + public async Task Should_Get_Array_Value() { var data = new { Foo = new [] { "foo", "bar" } }; var target = new ExpressionObserver(data, "Foo[1]"); @@ -26,7 +27,7 @@ namespace Avalonia.Markup.UnitTests.Data } [Fact] - public async void Should_Get_UnsetValue_For_Invalid_Array_Index() + public async Task Should_Get_UnsetValue_For_Invalid_Array_Index() { var data = new { Foo = new[] { "foo", "bar" } }; var target = new ExpressionObserver(data, "Foo[invalid]"); @@ -36,7 +37,7 @@ namespace Avalonia.Markup.UnitTests.Data } [Fact] - public async void Should_Get_UnsetValue_For_Invalid_Dictionary_Index() + public async Task Should_Get_UnsetValue_For_Invalid_Dictionary_Index() { var data = new { Foo = new Dictionary { { 1, "foo" } } }; var target = new ExpressionObserver(data, "Foo[invalid]"); @@ -46,7 +47,7 @@ namespace Avalonia.Markup.UnitTests.Data } [Fact] - public async void Should_Get_UnsetValue_For_Object_Without_Indexer() + public async Task Should_Get_UnsetValue_For_Object_Without_Indexer() { var data = new { Foo = 5 }; var target = new ExpressionObserver(data, "Foo[noindexer]"); @@ -56,7 +57,7 @@ namespace Avalonia.Markup.UnitTests.Data } [Fact] - public async void Should_Get_MultiDimensional_Array_Value() + 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]"); @@ -66,7 +67,7 @@ namespace Avalonia.Markup.UnitTests.Data } [Fact] - public async void Should_Get_Value_For_String_Indexer() + public async Task Should_Get_Value_For_String_Indexer() { var data = new { Foo = new Dictionary { { "foo", "bar" }, { "baz", "qux" } } }; var target = new ExpressionObserver(data, "Foo[foo]"); @@ -76,7 +77,7 @@ namespace Avalonia.Markup.UnitTests.Data } [Fact] - public async void Should_Get_Value_For_Non_String_Indexer() + public async Task Should_Get_Value_For_Non_String_Indexer() { var data = new { Foo = new Dictionary { { 1.0, "bar" }, { 2.0, "qux" } } }; var target = new ExpressionObserver(data, "Foo[1.0]"); @@ -86,7 +87,7 @@ namespace Avalonia.Markup.UnitTests.Data } [Fact] - public async void Array_Out_Of_Bounds_Should_Return_UnsetValue() + public async Task Array_Out_Of_Bounds_Should_Return_UnsetValue() { var data = new { Foo = new[] { "foo", "bar" } }; var target = new ExpressionObserver(data, "Foo[2]"); @@ -96,7 +97,7 @@ namespace Avalonia.Markup.UnitTests.Data } [Fact] - public async void Array_With_Wrong_Dimensions_Should_Return_UnsetValue() + 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]"); @@ -106,7 +107,7 @@ namespace Avalonia.Markup.UnitTests.Data } [Fact] - public async void List_Out_Of_Bounds_Should_Return_UnsetValue() + public async Task List_Out_Of_Bounds_Should_Return_UnsetValue() { var data = new { Foo = new List { "foo", "bar" } }; var target = new ExpressionObserver(data, "Foo[2]"); @@ -116,7 +117,7 @@ namespace Avalonia.Markup.UnitTests.Data } [Fact] - public async void Should_Get_List_Value() + public async Task Should_Get_List_Value() { var data = new { Foo = new List { "foo", "bar" } }; var target = new ExpressionObserver(data, "Foo[1]"); diff --git a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Negation.cs b/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Negation.cs index 6bee0d10f4..a9e8c6ddde 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Negation.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Negation.cs @@ -3,6 +3,7 @@ using System; using System.Reactive.Linq; +using System.Threading.Tasks; using Avalonia.Data; using Avalonia.Markup.Data; using Xunit; @@ -12,7 +13,7 @@ namespace Avalonia.Markup.UnitTests.Data public class ExpressionObserverTests_Negation { [Fact] - public async void Should_Negate_Boolean_Value() + public async Task Should_Negate_Boolean_Value() { var data = new { Foo = true }; var target = new ExpressionObserver(data, "!Foo"); @@ -22,7 +23,7 @@ namespace Avalonia.Markup.UnitTests.Data } [Fact] - public async void Should_Negate_0() + public async Task Should_Negate_0() { var data = new { Foo = 0 }; var target = new ExpressionObserver(data, "!Foo"); @@ -32,7 +33,7 @@ namespace Avalonia.Markup.UnitTests.Data } [Fact] - public async void Should_Negate_1() + public async Task Should_Negate_1() { var data = new { Foo = 1 }; var target = new ExpressionObserver(data, "!Foo"); @@ -42,7 +43,7 @@ namespace Avalonia.Markup.UnitTests.Data } [Fact] - public async void Should_Negate_False_String() + public async Task Should_Negate_False_String() { var data = new { Foo = "false" }; var target = new ExpressionObserver(data, "!Foo"); @@ -52,7 +53,7 @@ namespace Avalonia.Markup.UnitTests.Data } [Fact] - public async void Should_Negate_True_String() + public async Task Should_Negate_True_String() { var data = new { Foo = "True" }; var target = new ExpressionObserver(data, "!Foo"); @@ -62,7 +63,7 @@ namespace Avalonia.Markup.UnitTests.Data } [Fact] - public async void Should_Return_BindingNotification_For_String_Not_Convertible_To_Boolean() + public async Task Should_Return_BindingNotification_For_String_Not_Convertible_To_Boolean() { var data = new { Foo = "foo" }; var target = new ExpressionObserver(data, "!Foo"); @@ -76,7 +77,7 @@ namespace Avalonia.Markup.UnitTests.Data } [Fact] - public async void Should_Return_BindingNotification_For_Value_Not_Convertible_To_Boolean() + public async Task Should_Return_BindingNotification_For_Value_Not_Convertible_To_Boolean() { var data = new { Foo = new object() }; var target = new ExpressionObserver(data, "!Foo"); diff --git a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Property.cs b/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Property.cs index bdcd39d997..de33c959b4 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Property.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Property.cs @@ -11,13 +11,14 @@ using Avalonia.Data; using Avalonia.Markup.Data; using Avalonia.UnitTests; using Xunit; +using System.Threading.Tasks; namespace Avalonia.Markup.UnitTests.Data { public class ExpressionObserverTests_Property { [Fact] - public async void Should_Get_Simple_Property_Value() + public async Task Should_Get_Simple_Property_Value() { var data = new { Foo = "foo" }; var target = new ExpressionObserver(data, "Foo"); @@ -38,7 +39,7 @@ namespace Avalonia.Markup.UnitTests.Data } [Fact] - public async void Should_Get_Simple_Property_Value_Null() + public async Task Should_Get_Simple_Property_Value_Null() { var data = new { Foo = (string)null }; var target = new ExpressionObserver(data, "Foo"); @@ -48,7 +49,7 @@ namespace Avalonia.Markup.UnitTests.Data } [Fact] - public async void Should_Get_Simple_Property_From_Base_Class() + public async Task Should_Get_Simple_Property_From_Base_Class() { var data = new Class3 { Foo = "foo" }; var target = new ExpressionObserver(data, "Foo"); @@ -58,7 +59,7 @@ namespace Avalonia.Markup.UnitTests.Data } [Fact] - public async void Should_Return_UnsetValue_For_Root_Null() + public async Task Should_Return_UnsetValue_For_Root_Null() { var data = new Class3 { Foo = "foo" }; var target = new ExpressionObserver(default(object), "Foo"); @@ -68,7 +69,7 @@ namespace Avalonia.Markup.UnitTests.Data } [Fact] - public async void Should_Return_UnsetValue_For_Root_UnsetValue() + public async Task Should_Return_UnsetValue_For_Root_UnsetValue() { var data = new Class3 { Foo = "foo" }; var target = new ExpressionObserver(AvaloniaProperty.UnsetValue, "Foo"); @@ -78,7 +79,7 @@ namespace Avalonia.Markup.UnitTests.Data } [Fact] - public async void Should_Return_UnsetValue_For_Observable_Root_Null() + public async Task Should_Return_UnsetValue_For_Observable_Root_Null() { var data = new Class3 { Foo = "foo" }; var target = new ExpressionObserver(Observable.Return(default(object)), "Foo"); @@ -88,7 +89,7 @@ namespace Avalonia.Markup.UnitTests.Data } [Fact] - public async void Should_Return_UnsetValue_For_Observable_Root_UnsetValue() + public async Task Should_Return_UnsetValue_For_Observable_Root_UnsetValue() { var data = new Class3 { Foo = "foo" }; var target = new ExpressionObserver(Observable.Return(AvaloniaProperty.UnsetValue), "Foo"); @@ -98,7 +99,7 @@ namespace Avalonia.Markup.UnitTests.Data } [Fact] - public async void Should_Get_Simple_Property_Chain() + 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"); @@ -119,7 +120,7 @@ namespace Avalonia.Markup.UnitTests.Data } [Fact] - public async void Should_Return_BindingNotification_Error_For_Broken_Chain() + 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"); diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Data/MultiBindingTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Data/MultiBindingTests.cs index 5d67151992..874dc18552 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Data/MultiBindingTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Data/MultiBindingTests.cs @@ -10,13 +10,14 @@ using Moq; using Avalonia.Controls; using Avalonia.Markup.Xaml.Data; using Xunit; +using System.Threading.Tasks; namespace Avalonia.Markup.Xaml.UnitTests.Data { public class MultiBindingTests { [Fact] - public async void OneWay_Binding_Should_Be_Set_Up() + public async Task OneWay_Binding_Should_Be_Set_Up() { var source = new { A = 1, B = 2, C = 3 }; var binding = new MultiBinding diff --git a/tests/Avalonia.Styling.UnitTests/SelectorTests_Child.cs b/tests/Avalonia.Styling.UnitTests/SelectorTests_Child.cs index d97cc74c95..560dd523b7 100644 --- a/tests/Avalonia.Styling.UnitTests/SelectorTests_Child.cs +++ b/tests/Avalonia.Styling.UnitTests/SelectorTests_Child.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using System.Reactive; using System.Reactive.Linq; +using System.Threading.Tasks; using Avalonia.Collections; using Avalonia.Controls; using Avalonia.Data; @@ -45,7 +46,7 @@ namespace Avalonia.Styling.UnitTests } [Fact] - public async void Child_Matches_Control_When_It_Is_Child_OfType_And_Class() + public async Task Child_Matches_Control_When_It_Is_Child_OfType_And_Class() { var parent = new TestLogical1(); var child = new TestLogical2(); From 78bb593f97f63668b258d560ea02b77243ba3ede Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 25 Jun 2017 13:52:41 +0200 Subject: [PATCH 10/15] Use CurrentCulture instead of CurrentUICulture. Use `CurrentCulture` instead of `CurrentUICulture` in converters etc. `CurrentUICulture` should be used for translations, `CurrentCulture` should be used for things like numbers, dates etc. --- src/Markup/Avalonia.Markup.Xaml/Data/MultiBinding.cs | 2 +- src/Markup/Avalonia.Markup/Data/BindingExpression.cs | 4 ++-- tests/Avalonia.UnitTests/InvariantCultureFixture.cs | 12 ++++++------ 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Markup/Avalonia.Markup.Xaml/Data/MultiBinding.cs b/src/Markup/Avalonia.Markup.Xaml/Data/MultiBinding.cs index 69190be220..621e06efba 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Data/MultiBinding.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Data/MultiBinding.cs @@ -102,7 +102,7 @@ namespace Avalonia.Markup.Xaml.Data private object ConvertValue(IList values, Type targetType) { - var converted = Converter.Convert(values, targetType, null, CultureInfo.CurrentUICulture); + var converted = Converter.Convert(values, targetType, null, CultureInfo.CurrentCulture); if (converted == AvaloniaProperty.UnsetValue && FallbackValue != null) { diff --git a/src/Markup/Avalonia.Markup/Data/BindingExpression.cs b/src/Markup/Avalonia.Markup/Data/BindingExpression.cs index 0f4c091bff..5b9959e42e 100644 --- a/src/Markup/Avalonia.Markup/Data/BindingExpression.cs +++ b/src/Markup/Avalonia.Markup/Data/BindingExpression.cs @@ -122,7 +122,7 @@ namespace Avalonia.Markup.Data value, type, ConverterParameter, - CultureInfo.CurrentUICulture); + CultureInfo.CurrentCulture); if (converted == AvaloniaProperty.UnsetValue) { @@ -186,7 +186,7 @@ namespace Avalonia.Markup.Data value, _targetType, ConverterParameter, - CultureInfo.CurrentUICulture); + CultureInfo.CurrentCulture); notification = converted as BindingNotification; diff --git a/tests/Avalonia.UnitTests/InvariantCultureFixture.cs b/tests/Avalonia.UnitTests/InvariantCultureFixture.cs index 685142ad02..b00b16e2bb 100644 --- a/tests/Avalonia.UnitTests/InvariantCultureFixture.cs +++ b/tests/Avalonia.UnitTests/InvariantCultureFixture.cs @@ -21,20 +21,20 @@ namespace Avalonia.UnitTests public InvariantCultureFixture() { #if NET461 - _restore = Thread.CurrentThread.CurrentUICulture; - Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; + _restore = Thread.CurrentThread.CurrentCulture; + Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; #else - _restore = CultureInfo.CurrentUICulture; - CultureInfo.CurrentUICulture = CultureInfo.CurrentCulture = CultureInfo.InvariantCulture; + _restore = CultureInfo.CurrentCulture; + CultureInfo.CurrentCulture = CultureInfo.InvariantCulture; #endif } public void Dispose() { #if NET461 - Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = _restore; + Thread.CurrentThread.CurrentCulture = _restore; #else - CultureInfo.CurrentUICulture = CultureInfo.CurrentCulture = _restore; + CultureInfo.CurrentCulture = _restore; #endif } } From 7baa7dc0ddeef1c375da56134fd537988c0cf37f Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 25 Jun 2017 14:54:17 +0200 Subject: [PATCH 11/15] Added GC.KeepAlive to tests. Lots of `Avalonia.Markup.UnitTests` were failing intermittently. This is because in release mode, in a method like this: ``` [Fact] public void SetValue_Should_Return_False_For_Missing_Object() { var data = new Class1(); var target = new ExpressionObserver(data, "Next.Bar"); using (target.Subscribe(_ => { })) { Assert.False(target.SetValue("baz")); } } ``` `data` can get GC'ed at any point after creating target. Added `GC.KeepAlive()` calls to prevent this. Fixes #1035 Fixes #1036 Fixes #1037 --- .../Data/BindingExpressionTests.cs | 40 +++++++++++++ .../ExpressionObserverTests_DataValidation.cs | 9 +++ .../Data/ExpressionObserverTests_Indexer.cs | 44 ++++++++++++++ .../ExpressionObserverTests_Observable.cs | 12 ++++ .../Data/ExpressionObserverTests_Property.cs | 59 +++++++++++++++++++ .../Data/ExpressionObserverTests_Task.cs | 12 ++++ 6 files changed, 176 insertions(+) diff --git a/tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs b/tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs index c6b1fb7b0f..282b216769 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs @@ -25,6 +25,8 @@ namespace Avalonia.Markup.UnitTests.Data var result = await target.Take(1); Assert.Equal("foo", result); + + GC.KeepAlive(data); } [Fact] @@ -36,6 +38,8 @@ namespace Avalonia.Markup.UnitTests.Data target.OnNext("bar"); Assert.Equal("bar", data.StringValue); + + GC.KeepAlive(data); } [Fact] @@ -47,6 +51,8 @@ namespace Avalonia.Markup.UnitTests.Data target.OnNext("bar"); Assert.Equal("bar", data.Foo[0]); + + GC.KeepAlive(data); } [Fact] @@ -57,6 +63,8 @@ namespace Avalonia.Markup.UnitTests.Data var result = await target.Take(1); Assert.Equal(5.6, result); + + GC.KeepAlive(data); } [Fact] @@ -67,6 +75,8 @@ namespace Avalonia.Markup.UnitTests.Data var result = await target.Take(1); Assert.IsType(result); + + GC.KeepAlive(data); } [Fact] @@ -77,6 +87,8 @@ namespace Avalonia.Markup.UnitTests.Data var result = await target.Take(1); Assert.Equal(AvaloniaProperty.UnsetValue, result); + + GC.KeepAlive(data); } [Fact] @@ -88,6 +100,8 @@ namespace Avalonia.Markup.UnitTests.Data target.OnNext(6.7); Assert.Equal((6.7).ToString(), data.StringValue); + + GC.KeepAlive(data); } [Fact] @@ -98,6 +112,8 @@ namespace Avalonia.Markup.UnitTests.Data var result = await target.Take(1); Assert.Equal((5.6).ToString(), result); + + GC.KeepAlive(data); } [Fact] @@ -109,6 +125,8 @@ namespace Avalonia.Markup.UnitTests.Data target.OnNext("6.7"); Assert.Equal(6.7, data.DoubleValue); + + GC.KeepAlive(data); } [Fact] @@ -128,6 +146,8 @@ namespace Avalonia.Markup.UnitTests.Data BindingErrorType.Error, 42), result); + + GC.KeepAlive(data); } [Fact] @@ -147,6 +167,8 @@ namespace Avalonia.Markup.UnitTests.Data BindingErrorType.Error, 42), result); + + GC.KeepAlive(data); } [Fact(Skip="Result is not always AggregateException.")] @@ -167,6 +189,8 @@ namespace Avalonia.Markup.UnitTests.Data new InvalidCastException("Could not convert FallbackValue 'bar' to 'System.Int32'")), BindingErrorType.Error), result); + + GC.KeepAlive(data); } [Fact(Skip="Result is not always AggregateException.")] @@ -187,6 +211,8 @@ namespace Avalonia.Markup.UnitTests.Data new InvalidCastException("Could not convert FallbackValue 'bar' to 'System.Int32'")), BindingErrorType.Error), result); + + GC.KeepAlive(data); } [Fact] @@ -198,6 +224,8 @@ namespace Avalonia.Markup.UnitTests.Data target.OnNext("foo"); Assert.Equal(5.6, data.DoubleValue); + + GC.KeepAlive(data); } [Fact] @@ -213,6 +241,8 @@ namespace Avalonia.Markup.UnitTests.Data target.OnNext("foo"); Assert.Equal(9.8, data.DoubleValue); + + GC.KeepAlive(data); } [Fact] @@ -224,6 +254,8 @@ namespace Avalonia.Markup.UnitTests.Data target.OnNext(null); Assert.Equal(0, data.DoubleValue); + + GC.KeepAlive(data); } [Fact] @@ -235,6 +267,8 @@ namespace Avalonia.Markup.UnitTests.Data target.OnNext(AvaloniaProperty.UnsetValue); Assert.Equal(0, data.DoubleValue); + + GC.KeepAlive(data); } [Fact] @@ -252,6 +286,8 @@ namespace Avalonia.Markup.UnitTests.Data target.Subscribe(_ => { }); converter.Verify(x => x.Convert(5.6, typeof(string), "foo", CultureInfo.CurrentCulture)); + + GC.KeepAlive(data); } [Fact] @@ -268,6 +304,8 @@ namespace Avalonia.Markup.UnitTests.Data target.OnNext("bar"); converter.Verify(x => x.ConvertBack("bar", typeof(double), "foo", CultureInfo.CurrentCulture)); + + GC.KeepAlive(data); } [Fact(Skip="Moq.MockException")] @@ -294,6 +332,8 @@ namespace Avalonia.Markup.UnitTests.Data BindingErrorType.Error) }, result); + + GC.KeepAlive(data); } private class Class1 : NotifyingBase diff --git a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_DataValidation.cs b/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_DataValidation.cs index 3b5ca26db1..125bd84f3d 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_DataValidation.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_DataValidation.cs @@ -28,6 +28,8 @@ namespace Avalonia.Markup.UnitTests.Data observer.SetValue(-5); Assert.False(validationMessageFound); + + GC.KeepAlive(data); } [Fact] @@ -43,6 +45,8 @@ namespace Avalonia.Markup.UnitTests.Data observer.SetValue(-5); Assert.True(validationMessageFound); + + GC.KeepAlive(data); } [Fact] @@ -102,6 +106,8 @@ namespace Avalonia.Markup.UnitTests.Data new BindingNotification(new Exception("Must be positive"), BindingErrorType.DataValidationError, 5), new BindingNotification(5), }, result); + + GC.KeepAlive(data); } [Fact] @@ -147,6 +153,9 @@ namespace Avalonia.Markup.UnitTests.Data BindingErrorType.Error, AvaloniaProperty.UnsetValue), }, result); + + GC.KeepAlive(container); + GC.KeepAlive(inner); } public class ExceptionTest : NotifyingBase diff --git a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Indexer.cs b/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Indexer.cs index 135ec0f4db..a68213baee 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Indexer.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Indexer.cs @@ -24,6 +24,8 @@ namespace Avalonia.Markup.UnitTests.Data var result = await target.Take(1); Assert.Equal("bar", result); + + GC.KeepAlive(data); } [Fact] @@ -34,6 +36,8 @@ namespace Avalonia.Markup.UnitTests.Data var result = await target.Take(1); Assert.Equal(AvaloniaProperty.UnsetValue, result); + + GC.KeepAlive(data); } [Fact] @@ -44,6 +48,8 @@ namespace Avalonia.Markup.UnitTests.Data var result = await target.Take(1); Assert.Equal(AvaloniaProperty.UnsetValue, result); + + GC.KeepAlive(data); } [Fact] @@ -54,6 +60,8 @@ namespace Avalonia.Markup.UnitTests.Data var result = await target.Take(1); Assert.Equal(AvaloniaProperty.UnsetValue, result); + + GC.KeepAlive(data); } [Fact] @@ -64,6 +72,8 @@ namespace Avalonia.Markup.UnitTests.Data var result = await target.Take(1); Assert.Equal("qux", result); + + GC.KeepAlive(data); } [Fact] @@ -74,6 +84,8 @@ namespace Avalonia.Markup.UnitTests.Data var result = await target.Take(1); Assert.Equal("bar", result); + + GC.KeepAlive(data); } [Fact] @@ -84,6 +96,8 @@ namespace Avalonia.Markup.UnitTests.Data var result = await target.Take(1); Assert.Equal("bar", result); + + GC.KeepAlive(data); } [Fact] @@ -94,6 +108,8 @@ namespace Avalonia.Markup.UnitTests.Data var result = await target.Take(1); Assert.Equal(AvaloniaProperty.UnsetValue, result); + + GC.KeepAlive(data); } [Fact] @@ -104,6 +120,8 @@ namespace Avalonia.Markup.UnitTests.Data var result = await target.Take(1); Assert.Equal(AvaloniaProperty.UnsetValue, result); + + GC.KeepAlive(data); } [Fact] @@ -114,6 +132,8 @@ namespace Avalonia.Markup.UnitTests.Data var result = await target.Take(1); Assert.Equal(AvaloniaProperty.UnsetValue, result); + + GC.KeepAlive(data); } [Fact] @@ -124,6 +144,8 @@ namespace Avalonia.Markup.UnitTests.Data var result = await target.Take(1); Assert.Equal("bar", result); + + GC.KeepAlive(data); } [Fact] @@ -140,6 +162,8 @@ namespace Avalonia.Markup.UnitTests.Data Assert.Equal(new[] { AvaloniaProperty.UnsetValue, "baz" }, result); Assert.Null(((INotifyCollectionChangedDebug)data.Foo).GetCollectionChangedSubscribers()); + + GC.KeepAlive(data); } [Fact] @@ -156,6 +180,8 @@ namespace Avalonia.Markup.UnitTests.Data Assert.Equal(new[] { "foo", "bar" }, result); Assert.Null(((INotifyCollectionChangedDebug)data.Foo).GetCollectionChangedSubscribers()); + + GC.KeepAlive(data); } [Fact] @@ -172,6 +198,8 @@ namespace Avalonia.Markup.UnitTests.Data Assert.Equal(new[] { "bar", "baz" }, result); Assert.Null(((INotifyCollectionChangedDebug)data.Foo).GetCollectionChangedSubscribers()); + + GC.KeepAlive(data); } [Fact] @@ -188,6 +216,9 @@ namespace Avalonia.Markup.UnitTests.Data data.Foo.Move(0, 1); Assert.Equal(new[] { "bar", "foo" }, result); + + GC.KeepAlive(sub); + GC.KeepAlive(data); } [Fact] @@ -201,6 +232,9 @@ namespace Avalonia.Markup.UnitTests.Data data.Foo.Clear(); Assert.Equal(new[] { "bar", AvaloniaProperty.UnsetValue }, result); + + GC.KeepAlive(sub); + GC.KeepAlive(data); } [Fact] @@ -221,6 +255,8 @@ namespace Avalonia.Markup.UnitTests.Data var expected = new[] { "bar", "bar2" }; Assert.Equal(expected, result); Assert.Equal(0, data.Foo.PropertyChangedSubscriptionCount); + + GC.KeepAlive(data); } [Fact] @@ -235,6 +271,8 @@ namespace Avalonia.Markup.UnitTests.Data } Assert.Equal("baz", data.Foo[1]); + + GC.KeepAlive(data); } [Fact] @@ -255,6 +293,8 @@ namespace Avalonia.Markup.UnitTests.Data } Assert.Equal(4, data.Foo["foo"]); + + GC.KeepAlive(data); } [Fact] @@ -275,6 +315,8 @@ namespace Avalonia.Markup.UnitTests.Data } Assert.Equal(4, data.Foo["bar"]); + + GC.KeepAlive(data); } [Fact] @@ -292,6 +334,8 @@ namespace Avalonia.Markup.UnitTests.Data } Assert.Equal("bar2", data.Foo["foo"]); + + GC.KeepAlive(data); } private class NonIntegerIndexer : NotifyingBase diff --git a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Observable.cs b/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Observable.cs index 640d82fa19..62d5c28f49 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Observable.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Observable.cs @@ -29,6 +29,8 @@ namespace Avalonia.Markup.UnitTests.Data sync.ExecutePostedCallbacks(); Assert.Equal(new[] { source }, result); + + GC.KeepAlive(data); } } @@ -47,6 +49,8 @@ namespace Avalonia.Markup.UnitTests.Data sync.ExecutePostedCallbacks(); Assert.Equal(new[] { "foo", "bar" }, result); + + GC.KeepAlive(data); } } @@ -67,6 +71,8 @@ namespace Avalonia.Markup.UnitTests.Data sub.Dispose(); Assert.Equal(0, data.PropertyChangedSubscriptionCount); + + GC.KeepAlive(data); } } @@ -87,6 +93,8 @@ namespace Avalonia.Markup.UnitTests.Data // What does it mean to have data validation on an observable? Without a use-case // it's hard to know what to do here so for the moment the value is returned. Assert.Equal(new[] { "foo", "bar" }, result); + + GC.KeepAlive(data); } } @@ -107,6 +115,8 @@ namespace Avalonia.Markup.UnitTests.Data sub.Dispose(); Assert.Equal(0, data.PropertyChangedSubscriptionCount); + + GC.KeepAlive(data); } } @@ -132,6 +142,8 @@ namespace Avalonia.Markup.UnitTests.Data result); sub.Dispose(); + + GC.KeepAlive(data); } } diff --git a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Property.cs b/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Property.cs index de33c959b4..4cb2061c9e 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Property.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Property.cs @@ -25,6 +25,8 @@ namespace Avalonia.Markup.UnitTests.Data var result = await target.Take(1); Assert.Equal("foo", result); + + GC.KeepAlive(data); } [Fact] @@ -36,6 +38,8 @@ namespace Avalonia.Markup.UnitTests.Data target.Subscribe(_ => { }); Assert.Equal(typeof(string), target.ResultType); + + GC.KeepAlive(data); } [Fact] @@ -46,6 +50,8 @@ namespace Avalonia.Markup.UnitTests.Data var result = await target.Take(1); Assert.Null(result); + + GC.KeepAlive(data); } [Fact] @@ -56,6 +62,8 @@ namespace Avalonia.Markup.UnitTests.Data var result = await target.Take(1); Assert.Equal("foo", result); + + GC.KeepAlive(data); } [Fact] @@ -66,6 +74,8 @@ namespace Avalonia.Markup.UnitTests.Data var result = await target.Take(1); Assert.Equal(AvaloniaProperty.UnsetValue, result); + + GC.KeepAlive(data); } [Fact] @@ -76,6 +86,8 @@ namespace Avalonia.Markup.UnitTests.Data var result = await target.Take(1); Assert.Equal(AvaloniaProperty.UnsetValue, result); + + GC.KeepAlive(data); } [Fact] @@ -86,6 +98,8 @@ namespace Avalonia.Markup.UnitTests.Data var result = await target.Take(1); Assert.Equal(AvaloniaProperty.UnsetValue, result); + + GC.KeepAlive(data); } [Fact] @@ -96,6 +110,8 @@ namespace Avalonia.Markup.UnitTests.Data var result = await target.Take(1); Assert.Equal(AvaloniaProperty.UnsetValue, result); + + GC.KeepAlive(data); } [Fact] @@ -106,6 +122,8 @@ namespace Avalonia.Markup.UnitTests.Data var result = await target.Take(1); Assert.Equal("baz", result); + + GC.KeepAlive(data); } [Fact] @@ -117,6 +135,8 @@ namespace Avalonia.Markup.UnitTests.Data target.Subscribe(_ => { }); Assert.Equal(typeof(string), target.ResultType); + + GC.KeepAlive(data); } [Fact] @@ -132,6 +152,8 @@ namespace Avalonia.Markup.UnitTests.Data new BindingNotification( new MissingMemberException("Could not find CLR property 'Baz' on '1'"), BindingErrorType.Error), result); + + GC.KeepAlive(data); } [Fact] @@ -152,6 +174,8 @@ namespace Avalonia.Markup.UnitTests.Data AvaloniaProperty.UnsetValue), }, result); + + GC.KeepAlive(data); } [Fact] @@ -161,6 +185,8 @@ namespace Avalonia.Markup.UnitTests.Data var target = new ExpressionObserver(data, "Foo.Bar.Baz"); Assert.Null(target.ResultType); + + GC.KeepAlive(data); } [Fact] @@ -178,6 +204,8 @@ namespace Avalonia.Markup.UnitTests.Data sub.Dispose(); Assert.Equal(0, data.PropertyChangedSubscriptionCount); + + GC.KeepAlive(data); } [Fact] @@ -206,6 +234,8 @@ namespace Avalonia.Markup.UnitTests.Data sub.Dispose(); Assert.Equal(0, data.PropertyChangedSubscriptionCount); + + GC.KeepAlive(data); } [Fact] @@ -225,6 +255,8 @@ namespace Avalonia.Markup.UnitTests.Data Assert.Equal(0, data.PropertyChangedSubscriptionCount); Assert.Equal(0, data.Next.PropertyChangedSubscriptionCount); + + GC.KeepAlive(data); } [Fact] @@ -246,6 +278,8 @@ namespace Avalonia.Markup.UnitTests.Data Assert.Equal(0, data.PropertyChangedSubscriptionCount); Assert.Equal(0, data.Next.PropertyChangedSubscriptionCount); Assert.Equal(0, old.PropertyChangedSubscriptionCount); + + GC.KeepAlive(data); } [Fact] @@ -287,6 +321,8 @@ namespace Avalonia.Markup.UnitTests.Data Assert.Equal(0, data.PropertyChangedSubscriptionCount); Assert.Equal(0, data.Next.PropertyChangedSubscriptionCount); Assert.Equal(0, old.PropertyChangedSubscriptionCount); + + GC.KeepAlive(data); } [Fact] @@ -319,6 +355,8 @@ namespace Avalonia.Markup.UnitTests.Data Assert.Equal(0, data.Next.PropertyChangedSubscriptionCount); Assert.Equal(0, breaking.PropertyChangedSubscriptionCount); Assert.Equal(0, old.PropertyChangedSubscriptionCount); + + GC.KeepAlive(data); } [Fact] @@ -335,6 +373,8 @@ namespace Avalonia.Markup.UnitTests.Data update.OnNext(Unit.Default); Assert.Equal(new[] { "foo", "bar" }, result); + + GC.KeepAlive(data); } [Fact] @@ -375,6 +415,8 @@ namespace Avalonia.Markup.UnitTests.Data Assert.Equal(new[] { "foo", "bar" }, result1); Assert.Equal(new[] { "foo", "bar" }, result2); Assert.Equal(new[] { "bar" }, result3); + + GC.KeepAlive(data); } [Fact] @@ -392,6 +434,8 @@ namespace Avalonia.Markup.UnitTests.Data sub2.Dispose(); Assert.Equal(0, data.PropertyChangedSubscriptionCount); + + GC.KeepAlive(data); } [Fact] @@ -406,6 +450,8 @@ namespace Avalonia.Markup.UnitTests.Data } Assert.Equal("bar", data.Foo); + + GC.KeepAlive(data); } [Fact] @@ -420,6 +466,8 @@ namespace Avalonia.Markup.UnitTests.Data } Assert.Equal("baz", ((Class2)data.Next).Bar); + + GC.KeepAlive(data); } [Fact] @@ -432,6 +480,8 @@ namespace Avalonia.Markup.UnitTests.Data { Assert.False(target.SetValue("baz")); } + + GC.KeepAlive(data); } [Fact] @@ -445,6 +495,8 @@ namespace Avalonia.Markup.UnitTests.Data target.SetValue("bar"); Assert.Equal(new[] { null, "bar" }, result); + + GC.KeepAlive(data); } [Fact] @@ -458,6 +510,8 @@ namespace Avalonia.Markup.UnitTests.Data target.SetValue("bar"); Assert.Equal(new[] { null, "bar" }, result); + + GC.KeepAlive(data); } [Fact] @@ -470,6 +524,8 @@ namespace Avalonia.Markup.UnitTests.Data { Assert.False(target.SetValue("baz")); } + + GC.KeepAlive(data); } [Fact] @@ -499,6 +555,9 @@ namespace Avalonia.Markup.UnitTests.Data Assert.Equal(0, first.PropertyChangedSubscriptionCount); Assert.Equal(0, second.PropertyChangedSubscriptionCount); + + GC.KeepAlive(first); + GC.KeepAlive(second); } [Fact] diff --git a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Task.cs b/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Task.cs index 61e6dcb833..c251f4398a 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Task.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Task.cs @@ -30,6 +30,8 @@ namespace Avalonia.Markup.UnitTests.Data Assert.Equal(1, result.Count); Assert.IsType>(result[0]); + + GC.KeepAlive(data); } } @@ -45,6 +47,8 @@ namespace Avalonia.Markup.UnitTests.Data var sub = target.Subscribe(x => result.Add(x)); Assert.Equal(new[] { "foo" }, result); + + GC.KeepAlive(data); } } @@ -63,6 +67,8 @@ namespace Avalonia.Markup.UnitTests.Data sync.ExecutePostedCallbacks(); Assert.Equal(new[] { "foo" }, result); + + GC.KeepAlive(data); } } @@ -88,6 +94,8 @@ namespace Avalonia.Markup.UnitTests.Data BindingErrorType.Error) }, result); + + GC.KeepAlive(data); } } @@ -110,6 +118,8 @@ namespace Avalonia.Markup.UnitTests.Data BindingErrorType.Error) }, result); + + GC.KeepAlive(data); } } @@ -130,6 +140,8 @@ namespace Avalonia.Markup.UnitTests.Data // What does it mean to have data validation on a Task? Without a use-case it's // hard to know what to do here so for the moment the value is returned. Assert.Equal(new [] { "foo" }, result); + + GC.KeepAlive(data); } } From 3b3fbdbbd645fca18df1994e490c7e6284c531e7 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 25 Jun 2017 14:54:54 +0200 Subject: [PATCH 12/15] Use `DisableTestParallelization` Instead of `MaxParallelThreads = 1` - that's how we do it in other assemblies. --- tests/Avalonia.Base.UnitTests/Properties/AssemblyInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Avalonia.Base.UnitTests/Properties/AssemblyInfo.cs b/tests/Avalonia.Base.UnitTests/Properties/AssemblyInfo.cs index 562de2dc06..4b93ea8400 100644 --- a/tests/Avalonia.Base.UnitTests/Properties/AssemblyInfo.cs +++ b/tests/Avalonia.Base.UnitTests/Properties/AssemblyInfo.cs @@ -7,4 +7,4 @@ using Xunit; [assembly: AssemblyTitle("Avalonia.UnitTests")] // Don't run tests in parallel. -[assembly: CollectionBehavior(MaxParallelThreads = 1)] +[assembly: CollectionBehavior(DisableTestParallelization = true)] From ef1039b865dc5b67e34e972eae73dce96f97255e Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 25 Jun 2017 15:04:58 +0200 Subject: [PATCH 13/15] Unskip another test. --- tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs b/tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs index 282b216769..370e3b51e6 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs @@ -308,7 +308,7 @@ namespace Avalonia.Markup.UnitTests.Data GC.KeepAlive(data); } - [Fact(Skip="Moq.MockException")] + [Fact] public void Should_Handle_DataValidation() { var data = new Class1 { DoubleValue = 5.6 }; From 129378cad1ed0da543c80e41b0d7b833a5474544 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 25 Jun 2017 15:19:26 +0200 Subject: [PATCH 14/15] Added more GC.KeepAlive statements. --- .../Data/ExpressionObserverTests_Lifetime.cs | 5 ++++- .../Data/ExpressionObserverTests_Negation.cs | 16 ++++++++++++++++ .../Plugins/ExceptionValidationPluginTests.cs | 2 ++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Lifetime.cs b/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Lifetime.cs index 2a2bf06bf1..04a8e30d16 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Lifetime.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Lifetime.cs @@ -90,7 +90,8 @@ namespace Avalonia.Markup.UnitTests.Data { var scheduler = new TestScheduler(); var update = scheduler.CreateColdObservable(); - var target = new ExpressionObserver(() => new { Foo = "foo" }, "Foo", update); + var data = new { Foo = "foo" }; + var target = new ExpressionObserver(() => data, "Foo", update); var result = new List(); using (target.Subscribe(x => result.Add(x))) @@ -101,6 +102,8 @@ namespace Avalonia.Markup.UnitTests.Data Assert.Equal(new[] { "foo" }, result); Assert.All(update.Subscriptions, x => Assert.NotEqual(Subscription.Infinite, x.Unsubscribe)); + + GC.KeepAlive(data); } private Recorded> OnNext(long time, object value) diff --git a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Negation.cs b/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Negation.cs index a9e8c6ddde..d8dc2de847 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Negation.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Negation.cs @@ -20,6 +20,8 @@ namespace Avalonia.Markup.UnitTests.Data var result = await target.Take(1); Assert.Equal(false, result); + + GC.KeepAlive(data); } [Fact] @@ -30,6 +32,8 @@ namespace Avalonia.Markup.UnitTests.Data var result = await target.Take(1); Assert.Equal(true, result); + + GC.KeepAlive(data); } [Fact] @@ -40,6 +44,8 @@ namespace Avalonia.Markup.UnitTests.Data var result = await target.Take(1); Assert.Equal(false, result); + + GC.KeepAlive(data); } [Fact] @@ -50,6 +56,8 @@ namespace Avalonia.Markup.UnitTests.Data var result = await target.Take(1); Assert.Equal(true, result); + + GC.KeepAlive(data); } [Fact] @@ -60,6 +68,8 @@ namespace Avalonia.Markup.UnitTests.Data var result = await target.Take(1); Assert.Equal(false, result); + + GC.KeepAlive(data); } [Fact] @@ -74,6 +84,8 @@ namespace Avalonia.Markup.UnitTests.Data new InvalidCastException($"Unable to convert 'foo' to bool."), BindingErrorType.Error), result); + + GC.KeepAlive(data); } [Fact] @@ -88,6 +100,8 @@ namespace Avalonia.Markup.UnitTests.Data new InvalidCastException($"Unable to convert 'System.Object' to bool."), BindingErrorType.Error), result); + + GC.KeepAlive(data); } [Fact] @@ -97,6 +111,8 @@ namespace Avalonia.Markup.UnitTests.Data var target = new ExpressionObserver(data, "!Foo"); Assert.False(target.SetValue("bar")); + + GC.KeepAlive(data); } } } diff --git a/tests/Avalonia.Markup.UnitTests/Data/Plugins/ExceptionValidationPluginTests.cs b/tests/Avalonia.Markup.UnitTests/Data/Plugins/ExceptionValidationPluginTests.cs index 4a34791008..eb529a3b13 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/Plugins/ExceptionValidationPluginTests.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/Plugins/ExceptionValidationPluginTests.cs @@ -35,6 +35,8 @@ namespace Avalonia.Markup.UnitTests.Data.Plugins new BindingNotification(new ArgumentOutOfRangeException("value"), BindingErrorType.DataValidationError), new BindingNotification(6), }, result); + + GC.KeepAlive(data); } public class Data : NotifyingBase From 48f0c055228a59b30d0935eeb5b9066573bda7e0 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 25 Jun 2017 19:05:21 +0200 Subject: [PATCH 15/15] Fix BindingNotification.ClearValue. It should have been setting it to `UnsetValue` not null. This allows two skipped tests to pass. --- src/Avalonia.Base/Data/BindingNotification.cs | 2 +- .../Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Base/Data/BindingNotification.cs b/src/Avalonia.Base/Data/BindingNotification.cs index 4cae0e6afa..125c29b21b 100644 --- a/src/Avalonia.Base/Data/BindingNotification.cs +++ b/src/Avalonia.Base/Data/BindingNotification.cs @@ -226,7 +226,7 @@ namespace Avalonia.Data /// public void ClearValue() { - _value = null; + _value = AvaloniaProperty.UnsetValue; } /// diff --git a/tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs b/tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs index 370e3b51e6..f5c0c6ec15 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs @@ -171,7 +171,7 @@ namespace Avalonia.Markup.UnitTests.Data GC.KeepAlive(data); } - [Fact(Skip="Result is not always AggregateException.")] + [Fact] public async Task Should_Return_BindingNotification_For_Invalid_FallbackValue() { var data = new Class1 { StringValue = "foo" }; @@ -193,7 +193,7 @@ namespace Avalonia.Markup.UnitTests.Data GC.KeepAlive(data); } - [Fact(Skip="Result is not always AggregateException.")] + [Fact] public async Task Should_Return_BindingNotification_For_Invalid_FallbackValue_With_Data_Validation() { var data = new Class1 { StringValue = "foo" };