Browse Source

Merge branch 'master' into extract-layout-manager

Conflicts:
	src/Avalonia.Layout/LayoutManager.cs
	src/Avalonia.Layout/Layoutable.cs
	tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs
	tests/Avalonia.Layout.UnitTests/TestLayoutRoot.cs
pull/1014/head
Steven Kirk 9 years ago
parent
commit
c14fe7f2b7
  1. 3
      .gitignore
  2. 25
      build.cake
  3. 2
      build/Moq.props
  4. 6
      build/XUnit.props
  5. 1
      samples/interop/WindowsInteropTest/WindowsInteropTest.csproj
  6. 97
      src/Avalonia.Layout/LayoutManager.cs
  7. 1
      tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj
  8. 2
      tests/Avalonia.Base.UnitTests/Properties/AssemblyInfo.cs
  9. 3
      tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj
  10. 65
      tests/Avalonia.Benchmarks/Layout/Measure.cs
  11. 1
      tests/Avalonia.Benchmarks/Styling/ApplyStyling.cs
  12. 1
      tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj
  13. 1
      tests/Avalonia.Input.UnitTests/Avalonia.Input.UnitTests.csproj
  14. 1
      tests/Avalonia.Interactivity.UnitTests/Avalonia.Interactivity.UnitTests.csproj
  15. 1
      tests/Avalonia.Layout.UnitTests/Avalonia.Layout.UnitTests.csproj
  16. 265
      tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs
  17. 29
      tests/Avalonia.Layout.UnitTests/LayoutTestControl.cs
  18. 43
      tests/Avalonia.Layout.UnitTests/LayoutTestRoot.cs
  19. 90
      tests/Avalonia.Layout.UnitTests/LayoutableTests.cs
  20. 41
      tests/Avalonia.Layout.UnitTests/TestLayoutRoot.cs
  21. 1
      tests/Avalonia.Markup.UnitTests/Avalonia.Markup.UnitTests.csproj
  22. 30
      tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs
  23. 2
      tests/Avalonia.Markup.UnitTests/Properties/AssemblyInfo.cs
  24. 1
      tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj
  25. 2
      tests/Avalonia.Markup.Xaml.UnitTests/Properties/AssemblyInfo.cs
  26. 1
      tests/Avalonia.Styling.UnitTests/Avalonia.Styling.UnitTests.csproj
  27. 4
      tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj
  28. 2
      tests/Avalonia.UnitTests/TestRoot.cs
  29. 4
      tools/packages.config

3
.gitignore

@ -162,7 +162,8 @@ $RECYCLE.BIN/
#################
## Cake
#################
tools/
tools/*
!tools/packages.config
.nuget
artifacts/
nuget

25
build.cake

@ -11,7 +11,7 @@
// TOOLS
///////////////////////////////////////////////////////////////////////////////
#tool "nuget:?package=xunit.runner.console&version=2.1.0"
#tool "nuget:?package=xunit.runner.console&version=2.2.0"
#tool "nuget:?package=OpenCover"
///////////////////////////////////////////////////////////////////////////////
@ -98,7 +98,6 @@ Task("Clean")
CleanDirectory(parameters.TestsRoot);
});
Task("Restore-NuGet-Packages")
.IsDependentOn("Clean")
.WithCriteria(parameters.IsRunningOnWindows)
@ -171,23 +170,25 @@ void RunCoreTest(string dir, Parameters parameters, bool net461Only)
continue;
Information("Running for " + fw);
DotNetCoreTest(System.IO.Path.Combine(dir, System.IO.Path.GetFileName(dir)+".csproj"),
new DotNetCoreTestSettings{Framework = fw});
new DotNetCoreTestSettings {
Configuration = parameters.Configuration,
Framework = fw
});
}
}
Task("Run-Net-Core-Unit-Tests")
.IsDependentOn("Clean")
.Does(() => {
RunCoreTest("./tests/Avalonia.Base.UnitTests", parameters, false);
RunCoreTest("./tests/Avalonia.Controls.UnitTests", parameters, true);
RunCoreTest("./tests/Avalonia.Input.UnitTests", parameters, true);
RunCoreTest("./tests/Avalonia.Interactivity.UnitTests", parameters, true);
RunCoreTest("./tests/Avalonia.Layout.UnitTests", parameters, true);
//RunCoreTest("./tests/Avalonia.Markup.UnitTests", parameters, true);
//RunCoreTest("./tests/Avalonia.Markup.Xaml.UnitTests", parameters, true);
RunCoreTest("./tests/Avalonia.Styling.UnitTests", parameters, true);
RunCoreTest("./tests/Avalonia.Visuals.UnitTests", parameters, true);
RunCoreTest("./tests/Avalonia.Controls.UnitTests", parameters, false);
RunCoreTest("./tests/Avalonia.Input.UnitTests", parameters, false);
RunCoreTest("./tests/Avalonia.Interactivity.UnitTests", parameters, false);
RunCoreTest("./tests/Avalonia.Layout.UnitTests", parameters, false);
RunCoreTest("./tests/Avalonia.Markup.UnitTests", parameters, false);
RunCoreTest("./tests/Avalonia.Markup.Xaml.UnitTests", parameters, false);
RunCoreTest("./tests/Avalonia.Styling.UnitTests", parameters, false);
RunCoreTest("./tests/Avalonia.Visuals.UnitTests", parameters, false);
});
Task("Run-Unit-Tests")

2
build/Moq.props

@ -1,5 +1,5 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<PackageReference Include="Moq" Version="4.7.1" />
<PackageReference Include="Moq" Version="4.7.25" />
</ItemGroup>
</Project>

6
build/XUnit.props

@ -7,7 +7,9 @@
<PackageReference Include="xunit.extensibility.core" Version="2.2.0" />
<PackageReference Include="xunit.extensibility.execution" Version="2.2.0" />
<PackageReference Include="xunit.runner.console" Version="2.2.0" />
<PackageReference Condition="'$(TargetFramework)' == 'net461'" Include="xunit.runner.visualstudio" Version="2.2.0" />
<PackageReference Condition="'$(TargetFramework)' == 'netcoreapp1.1'" Include="Microsoft.NET.Test.Sdk" Version="15.0.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp1.1'">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0" />
</ItemGroup>
</Project>

1
samples/interop/WindowsInteropTest/WindowsInteropTest.csproj

@ -181,5 +181,6 @@
</ItemGroup>
<Import Project="..\..\..\build\Rx.props" />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\..\..\build\SkiaSharp.props" />
<Import Project="$(MSBuildThisFileDirectory)..\..\..\src\Shared\nuget.workaround.targets" />
</Project>

97
src/Avalonia.Layout/LayoutManager.cs

@ -14,8 +14,8 @@ namespace Avalonia.Layout
/// </summary>
public class LayoutManager : ILayoutManager
{
private readonly HashSet<ILayoutable> _toMeasure = new HashSet<ILayoutable>();
private readonly HashSet<ILayoutable> _toArrange = new HashSet<ILayoutable>();
private readonly Queue<ILayoutable> _toMeasure = new Queue<ILayoutable>();
private readonly Queue<ILayoutable> _toArrange = new Queue<ILayoutable>();
private bool _queued;
private bool _running;
@ -25,8 +25,18 @@ namespace Avalonia.Layout
Contract.Requires<ArgumentNullException>(control != null);
Dispatcher.UIThread.VerifyAccess();
_toMeasure.Add(control);
_toArrange.Add(control);
if (!control.IsAttachedToVisualTree)
{
#if DEBUG
throw new AvaloniaInternalException(
"LayoutManager.InvalidateMeasure called on a control that is detached from the visual tree.");
#else
return;
#endif
}
_toMeasure.Enqueue(control);
_toArrange.Enqueue(control);
QueueLayoutPass();
}
@ -36,7 +46,17 @@ namespace Avalonia.Layout
Contract.Requires<ArgumentNullException>(control != null);
Dispatcher.UIThread.VerifyAccess();
_toArrange.Add(control);
if (!control.IsAttachedToVisualTree)
{
#if DEBUG
throw new AvaloniaInternalException(
"LayoutManager.InvalidateArrange called on a control that is detached from the visual tree.");
#else
return;
#endif
}
_toArrange.Enqueue(control);
QueueLayoutPass();
}
@ -103,8 +123,12 @@ namespace Avalonia.Layout
{
while (_toMeasure.Count > 0)
{
var next = _toMeasure.First();
Measure(next);
var control = _toMeasure.Dequeue();
if (!control.IsMeasureValid && control.IsAttachedToVisualTree)
{
Measure(control);
}
}
}
@ -112,53 +136,60 @@ namespace Avalonia.Layout
{
while (_toArrange.Count > 0 && _toMeasure.Count == 0)
{
var next = _toArrange.First();
Arrange(next);
var control = _toArrange.Dequeue();
if (!control.IsArrangeValid && control.IsAttachedToVisualTree)
{
Arrange(control);
}
}
}
private void Measure(ILayoutable control)
{
var root = control as ILayoutRoot;
var parent = control.VisualParent as ILayoutable;
if (root != null)
{
root.Measure(root.MaxClientSize);
}
else if (parent != null)
// Controls closest to the visual root need to be arranged first. We don't try to store
// ordered invalidation lists, instead we traverse the tree upwards, measuring the
// controls closest to the root first. This has been shown by benchmarks to be the
// fastest and most memory-efficent algorithm.
if (control.VisualParent is ILayoutable parent)
{
Measure(parent);
}
if (!control.IsMeasureValid)
// If the control being measured has IsMeasureValid == true here then its measure was
// handed by an ancestor and can be ignored. The measure may have also caused the
// control to be removed.
if (!control.IsMeasureValid && control.IsAttachedToVisualTree)
{
control.Measure(control.PreviousMeasure ?? default(Size));
if (control is ILayoutRoot root)
{
root.Measure(Size.Infinity);
}
else
{
control.Measure(control.PreviousMeasure.Value);
}
}
_toMeasure.Remove(control);
}
private void Arrange(ILayoutable control)
{
var root = control as ILayoutRoot;
var parent = control.VisualParent as ILayoutable;
if (root != null)
{
root.Arrange(new Rect(root.DesiredSize));
}
else if (parent != null)
if (control.VisualParent is ILayoutable parent)
{
Arrange(parent);
}
if (control.PreviousArrange.HasValue)
if (!control.IsArrangeValid && control.IsAttachedToVisualTree)
{
control.Arrange(control.PreviousArrange.Value);
if (control is ILayoutRoot root)
{
root.Arrange(new Rect(control.DesiredSize));
}
else
{
control.Arrange(control.PreviousArrange.Value);
}
}
_toArrange.Remove(control);
}
private void QueueLayoutPass()

1
tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj

@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0">
<PropertyGroup>
<TargetFrameworks>net461;netcoreapp1.1</TargetFrameworks>
<OutputType>Library</OutputType>
</PropertyGroup>
<Import Project="..\..\build\UnitTests.NetCore.targets" />
<Import Project="..\..\build\Moq.props" />

2
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(DisableTestParallelization = true)]
[assembly: CollectionBehavior(MaxParallelThreads = 1)]

3
tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj

@ -49,6 +49,7 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Layout\Measure.cs" />
<Compile Include="Styling\ApplyStyling.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
@ -100,7 +101,7 @@
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.9.2" />
<PackageReference Include="BenchmarkDotNet" Version="0.10.8" />
</ItemGroup>
<Import Project="$(MSBuildThisFileDirectory)..\..\src\Shared\nuget.workaround.targets" />
</Project>

65
tests/Avalonia.Benchmarks/Layout/Measure.cs

@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using Avalonia.Controls;
using Avalonia.Layout;
using Avalonia.UnitTests;
using BenchmarkDotNet.Attributes;
namespace Avalonia.Benchmarks.Layout
{
[MemoryDiagnoser]
public class Measure : IDisposable
{
private IDisposable _app;
private TestRoot root;
private List<Control> controls = new List<Control>();
public Measure()
{
_app = UnitTestApplication.Start(TestServices.RealLayoutManager);
var panel = new StackPanel();
root = new TestRoot { Child = panel };
controls.Add(panel);
CreateChildren(panel, 3, 5);
LayoutManager.Instance.ExecuteInitialLayoutPass(root);
}
public void Dispose()
{
_app.Dispose();
}
[Benchmark]
public void Remeasure_Half()
{
var random = new Random(1);
foreach (var control in controls)
{
if (random.Next(2) == 0)
{
control.InvalidateMeasure();
}
}
LayoutManager.Instance.ExecuteLayoutPass();
}
private void CreateChildren(IPanel parent, int childCount, int iterations)
{
for (var i = 0; i < childCount; ++i)
{
var control = new StackPanel();
parent.Children.Add(control);
if (iterations > 0)
{
CreateChildren(control, childCount, iterations - 1);
}
controls.Add(control);
}
}
}
}

1
tests/Avalonia.Benchmarks/Styling/ApplyStyling.cs

@ -11,6 +11,7 @@ using Avalonia.VisualTree;
namespace Avalonia.Benchmarks.Styling
{
[MemoryDiagnoser]
public class ApplyStyling : IDisposable
{
private IDisposable _app;

1
tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj

@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0">
<PropertyGroup>
<TargetFrameworks>net461;netcoreapp1.1</TargetFrameworks>
<OutputType>Library</OutputType>
</PropertyGroup>
<Import Project="..\..\build\UnitTests.NetCore.targets" />
<Import Project="..\..\build\Moq.props" />

1
tests/Avalonia.Input.UnitTests/Avalonia.Input.UnitTests.csproj

@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0">
<PropertyGroup>
<TargetFrameworks>net461;netcoreapp1.1</TargetFrameworks>
<OutputType>Library</OutputType>
</PropertyGroup>
<Import Project="..\..\build\UnitTests.NetCore.targets" />
<Import Project="..\..\build\Moq.props" />

1
tests/Avalonia.Interactivity.UnitTests/Avalonia.Interactivity.UnitTests.csproj

@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0">
<PropertyGroup>
<TargetFrameworks>net461;netcoreapp1.1</TargetFrameworks>
<OutputType>Library</OutputType>
</PropertyGroup>
<Import Project="..\..\build\UnitTests.NetCore.targets" />
<Import Project="..\..\build\XUnit.props" />

1
tests/Avalonia.Layout.UnitTests/Avalonia.Layout.UnitTests.csproj

@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0">
<PropertyGroup>
<TargetFrameworks>net461;netcoreapp1.1</TargetFrameworks>
<OutputType>Library</OutputType>
</PropertyGroup>
<Import Project="..\..\build\UnitTests.NetCore.targets" />
<Import Project="..\..\build\Moq.props" />

265
tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs

@ -2,22 +2,274 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Controls;
using Avalonia.UnitTests;
using System;
using Xunit;
using System.Collections.Generic;
namespace Avalonia.Layout.UnitTests
{
public class LayoutManagerTests
{
[Fact]
public void Invalidating_Child_Should_Remeasure_Parent()
public void Measures_And_Arranges_InvalidateMeasured_Control()
{
var target = new LayoutManager();
using (Start(target))
{
var control = new LayoutTestControl();
var root = new LayoutTestRoot { Child = control };
target.ExecuteInitialLayoutPass(root);
control.Measured = control.Arranged = false;
control.InvalidateMeasure();
target.ExecuteLayoutPass();
Assert.True(control.Measured);
Assert.True(control.Arranged);
}
}
[Fact]
public void Arranges_InvalidateArranged_Control()
{
var target = new LayoutManager();
using (Start(target))
{
var control = new LayoutTestControl();
var root = new LayoutTestRoot { Child = control };
target.ExecuteInitialLayoutPass(root);
control.Measured = control.Arranged = false;
control.InvalidateArrange();
target.ExecuteLayoutPass();
Assert.False(control.Measured);
Assert.True(control.Arranged);
}
}
[Fact]
public void Measures_Parent_Of_Newly_Added_Control()
{
var target = new LayoutManager();
using (Start(target))
{
var control = new LayoutTestControl();
var root = new LayoutTestRoot();
target.ExecuteInitialLayoutPass(root);
root.Child = control;
root.Measured = root.Arranged = false;
target.ExecuteLayoutPass();
Assert.True(root.Measured);
Assert.True(root.Arranged);
Assert.True(control.Measured);
Assert.True(control.Arranged);
}
}
[Fact]
public void Measures_In_Correct_Order()
{
var target = new LayoutManager();
using (Start(target))
{
LayoutTestControl control1;
LayoutTestControl control2;
var root = new LayoutTestRoot
{
Child = control1 = new LayoutTestControl
{
Child = control2 = new LayoutTestControl(),
}
};
var order = new List<ILayoutable>();
Size MeasureOverride(ILayoutable control, Size size)
{
order.Add(control);
return new Size(10, 10);
}
root.DoMeasureOverride = MeasureOverride;
control1.DoMeasureOverride = MeasureOverride;
control2.DoMeasureOverride = MeasureOverride;
target.ExecuteInitialLayoutPass(root);
control2.InvalidateMeasure();
control1.InvalidateMeasure();
root.InvalidateMeasure();
order.Clear();
target.ExecuteLayoutPass();
Assert.Equal(new ILayoutable[] { root, control1, control2 }, order);
}
}
[Fact]
public void Measures_Root_And_Grandparent_In_Correct_Order()
{
var target = new LayoutManager();
using (Start(target))
{
LayoutTestControl control1;
LayoutTestControl control2;
var root = new LayoutTestRoot
{
Child = control1 = new LayoutTestControl
{
Child = control2 = new LayoutTestControl(),
}
};
var order = new List<ILayoutable>();
Size MeasureOverride(ILayoutable control, Size size)
{
order.Add(control);
return new Size(10, 10);
}
root.DoMeasureOverride = MeasureOverride;
control1.DoMeasureOverride = MeasureOverride;
control2.DoMeasureOverride = MeasureOverride;
target.ExecuteInitialLayoutPass(root);
control2.InvalidateMeasure();
root.InvalidateMeasure();
order.Clear();
target.ExecuteLayoutPass();
Assert.Equal(new ILayoutable[] { root, control2 }, order);
}
}
[Fact]
public void Doesnt_Measure_Non_Invalidated_Root()
{
var target = new LayoutManager();
using (Start(target))
{
var control = new LayoutTestControl();
var root = new LayoutTestRoot { Child = control };
target.ExecuteInitialLayoutPass(root);
root.Measured = root.Arranged = false;
control.Measured = control.Arranged = false;
control.InvalidateMeasure();
target.ExecuteLayoutPass();
Assert.False(root.Measured);
Assert.False(root.Arranged);
Assert.True(control.Measured);
Assert.True(control.Arranged);
}
}
[Fact]
public void Doesnt_Measure_Removed_Control()
{
var target = new LayoutManager();
using (Start(target))
{
var control = new LayoutTestControl();
var root = new LayoutTestRoot { Child = control };
target.ExecuteInitialLayoutPass(root);
control.Measured = control.Arranged = false;
control.InvalidateMeasure();
root.Child = null;
target.ExecuteLayoutPass();
Assert.False(control.Measured);
Assert.False(control.Arranged);
}
}
[Fact]
public void Measures_Root_With_Infinity()
{
var target = new LayoutManager();
using (Start(target))
{
var root = new LayoutTestRoot();
var availableSize = default(Size);
// Should not measure with this size.
root.MaxClientSize = new Size(123, 456);
root.DoMeasureOverride = (_, s) =>
{
availableSize = s;
return new Size(100, 100);
};
target.ExecuteInitialLayoutPass(root);
Assert.Equal(Size.Infinity, availableSize);
}
}
[Fact]
public void Arranges_Root_With_DesiredSize()
{
var target = new LayoutManager();
using (Start(target))
{
var root = new LayoutTestRoot
{
Width = 100,
Height = 100,
};
var arrangeSize = default(Size);
root.DoArrangeOverride = (_, s) =>
{
arrangeSize = s;
return s;
};
target.ExecuteInitialLayoutPass(root);
Assert.Equal(new Size(100, 100), arrangeSize);
root.Width = 120;
target.ExecuteLayoutPass();
Assert.Equal(new Size(120, 100), arrangeSize);
}
}
[Fact]
public void Invalidating_Child_Remeasures_Parent()
{
using (AvaloniaLocator.EnterScope())
{
AvaloniaLocator.CurrentMutable.Bind<ILayoutManager>().ToConstant(layoutManager);
Border border;
StackPanel panel;
var root = new TestLayoutRoot
var root = new LayoutTestRoot
{
Child = panel = new StackPanel
{
@ -38,5 +290,12 @@ namespace Avalonia.Layout.UnitTests
Assert.Equal(new Size(100, 100), panel.DesiredSize);
}
}
private IDisposable Start(LayoutManager layoutManager)
{
var result = AvaloniaLocator.EnterScope();
AvaloniaLocator.CurrentMutable.Bind<ILayoutManager>().ToConstant(layoutManager);
return result;
}
}
}

29
tests/Avalonia.Layout.UnitTests/LayoutTestControl.cs

@ -0,0 +1,29 @@
using System;
using Avalonia.Controls;
namespace Avalonia.Layout.UnitTests
{
internal class LayoutTestControl : Decorator
{
public bool Measured { get; set; }
public bool Arranged { get; set; }
public Func<ILayoutable, Size, Size> DoMeasureOverride { get; set; }
public Func<ILayoutable, Size, Size> DoArrangeOverride { get; set; }
protected override Size MeasureOverride(Size availableSize)
{
Measured = true;
return DoMeasureOverride != null ?
DoMeasureOverride(this, availableSize) :
base.MeasureOverride(availableSize);
}
protected override Size ArrangeOverride(Size finalSize)
{
Arranged = true;
return DoArrangeOverride != null ?
DoArrangeOverride(this, finalSize) :
base.ArrangeOverride(finalSize);
}
}
}

43
tests/Avalonia.Layout.UnitTests/LayoutTestRoot.cs

@ -0,0 +1,43 @@
// 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.UnitTests;
namespace Avalonia.Layout.UnitTests
{
internal class LayoutTestRoot : TestRoot, ILayoutable
{
public bool Measured { get; set; }
public bool Arranged { get; set; }
public Func<ILayoutable, Size, Size> DoMeasureOverride { get; set; }
public Func<ILayoutable, Size, Size> DoArrangeOverride { get; set; }
void ILayoutable.Measure(Size availableSize)
{
Measured = true;
Measure(availableSize);
}
void ILayoutable.Arrange(Rect rect)
{
Arranged = true;
Arrange(rect);
}
protected override Size MeasureOverride(Size availableSize)
{
return DoMeasureOverride != null ?
DoMeasureOverride(this, availableSize) :
base.MeasureOverride(availableSize);
}
protected override Size ArrangeOverride(Size finalSize)
{
Arranged = true;
return DoArrangeOverride != null ?
DoArrangeOverride(this, finalSize) :
base.ArrangeOverride(finalSize);
}
}
}

90
tests/Avalonia.Layout.UnitTests/LayoutableTests.cs

@ -0,0 +1,90 @@
using System;
using Avalonia.Controls;
using Moq;
using Xunit;
namespace Avalonia.Layout.UnitTests
{
public class LayoutableTests
{
[Fact]
public void Only_Calls_LayoutManager_InvalidateMeasure_Once()
{
var target = new Mock<ILayoutManager>();
using (Start(target.Object))
{
var control = new Decorator();
var root = new LayoutTestRoot { Child = control };
root.Measure(Size.Infinity);
root.Arrange(new Rect(root.DesiredSize));
target.ResetCalls();
control.InvalidateMeasure();
control.InvalidateMeasure();
target.Verify(x => x.InvalidateMeasure(control), Times.Once());
}
}
[Fact]
public void Only_Calls_LayoutManager_InvalidateArrange_Once()
{
var target = new Mock<ILayoutManager>();
using (Start(target.Object))
{
var control = new Decorator();
var root = new LayoutTestRoot { Child = control };
root.Measure(Size.Infinity);
root.Arrange(new Rect(root.DesiredSize));
target.ResetCalls();
control.InvalidateArrange();
control.InvalidateArrange();
target.Verify(x => x.InvalidateArrange(control), Times.Once());
}
}
[Fact]
public void Attaching_Control_To_Tree_Invalidates_Parent_Measure()
{
var target = new Mock<ILayoutManager>();
using (Start(target.Object))
{
var control = new Decorator();
var root = new LayoutTestRoot { Child = control };
root.Measure(Size.Infinity);
root.Arrange(new Rect(root.DesiredSize));
Assert.True(control.IsMeasureValid);
root.Child = null;
root.Measure(Size.Infinity);
root.Arrange(new Rect(root.DesiredSize));
Assert.False(control.IsMeasureValid);
Assert.True(root.IsMeasureValid);
target.ResetCalls();
root.Child = control;
Assert.False(root.IsMeasureValid);
Assert.False(control.IsMeasureValid);
target.Verify(x => x.InvalidateMeasure(root), Times.Once());
}
}
private IDisposable Start(ILayoutManager layoutManager)
{
var result = AvaloniaLocator.EnterScope();
AvaloniaLocator.CurrentMutable.Bind<ILayoutManager>().ToConstant(layoutManager);
return result;
}
}
}

41
tests/Avalonia.Layout.UnitTests/TestLayoutRoot.cs

@ -1,41 +0,0 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Controls;
using Avalonia.Platform;
using Avalonia.Rendering;
namespace Avalonia.Layout.UnitTests
{
internal class TestLayoutRoot : Decorator, ILayoutRoot, IRenderRoot
{
public TestLayoutRoot()
{
ClientSize = new Size(500, 500);
}
public Size ClientSize
{
get;
set;
}
public IRenderer Renderer => null;
public IRenderTarget CreateRenderTarget() => null;
public void Invalidate(Rect rect)
{
}
public Point PointToClient(Point point) => point;
public Point PointToScreen(Point point) => point;
public Size MaxClientSize => Size.Infinity;
public double LayoutScaling => 1;
public ILayoutManager LayoutManager { get; set; } = new LayoutManager();
}
}

1
tests/Avalonia.Markup.UnitTests/Avalonia.Markup.UnitTests.csproj

@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0">
<PropertyGroup>
<TargetFrameworks>net461;netcoreapp1.1</TargetFrameworks>
<OutputType>Library</OutputType>
</PropertyGroup>
<Import Project="..\..\build\UnitTests.NetCore.targets" />
<Import Project="..\..\build\Moq.props" />

30
tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs

@ -183,7 +183,7 @@ namespace Avalonia.Markup.UnitTests.Data
result);
}
[Fact]
[Fact(Skip="Result is not always AggregateException.")]
public async void Should_Return_BindingNotification_For_Invalid_FallbackValue()
{
#if NET461
@ -203,13 +203,13 @@ namespace Avalonia.Markup.UnitTests.Data
Assert.Equal(
new BindingNotification(
new AggregateException(
new InvalidCastException("Could not convert 'foo' to 'System.Int32'"),
new InvalidCastException("'foo' is not a valid number."),
new InvalidCastException("Could not convert FallbackValue 'bar' to 'System.Int32'")),
BindingErrorType.Error),
result);
}
[Fact]
[Fact(Skip="Result is not always AggregateException.")]
public async void Should_Return_BindingNotification_For_Invalid_FallbackValue_With_Data_Validation()
{
#if NET461
@ -229,7 +229,7 @@ namespace Avalonia.Markup.UnitTests.Data
Assert.Equal(
new BindingNotification(
new AggregateException(
new InvalidCastException("Could not convert 'foo' to 'System.Int32'"),
new InvalidCastException("'foo' is not a valid number."),
new InvalidCastException("Could not convert FallbackValue 'bar' to 'System.Int32'")),
BindingErrorType.Error),
result);
@ -286,6 +286,12 @@ 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<IValueConverter>();
var target = new BindingExpression(
@ -296,12 +302,18 @@ namespace Avalonia.Markup.UnitTests.Data
target.Subscribe(_ => { });
converter.Verify(x => x.Convert(5.6, typeof(string), "foo", CultureInfo.CurrentUICulture));
converter.Verify(x => x.Convert(5.6, typeof(string), "foo", CultureInfo.InvariantCulture));
}
[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<IValueConverter>();
var target = new BindingExpression(
@ -312,12 +324,18 @@ namespace Avalonia.Markup.UnitTests.Data
target.OnNext("bar");
converter.Verify(x => x.ConvertBack("bar", typeof(double), "foo", CultureInfo.CurrentUICulture));
converter.Verify(x => x.ConvertBack("bar", typeof(double), "foo", CultureInfo.InvariantCulture));
}
[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<IValueConverter>();
var target = new BindingExpression(new ExpressionObserver(data, "DoubleValue", true), typeof(string));

2
tests/Avalonia.Markup.UnitTests/Properties/AssemblyInfo.cs

@ -37,4 +37,4 @@ using Xunit;
[assembly: AssemblyFileVersion("1.0.0.0")]
// Don't run tests in parallel.
[assembly: CollectionBehavior(DisableTestParallelization = true)]
[assembly: CollectionBehavior(MaxParallelThreads = 1)]

1
tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj

@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0">
<PropertyGroup>
<TargetFrameworks>net461;netcoreapp1.1</TargetFrameworks>
<OutputType>Library</OutputType>
</PropertyGroup>
<Import Project="..\..\build\UnitTests.NetCore.targets" />
<Import Project="..\..\build\Moq.props" />

2
tests/Avalonia.Markup.Xaml.UnitTests/Properties/AssemblyInfo.cs

@ -7,4 +7,4 @@ using Xunit;
[assembly: AssemblyTitle("Avalonia.Markup.Xaml.UnitTests")]
// Don't run tests in parallel.
[assembly: CollectionBehavior(DisableTestParallelization = true)]
[assembly: CollectionBehavior(MaxParallelThreads = 1)]

1
tests/Avalonia.Styling.UnitTests/Avalonia.Styling.UnitTests.csproj

@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0">
<PropertyGroup>
<TargetFrameworks>net461;netcoreapp1.1</TargetFrameworks>
<OutputType>Library</OutputType>
</PropertyGroup>
<Import Project="..\..\build\UnitTests.NetCore.targets" />
<Import Project="..\..\build\Moq.props" />

4
tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj

@ -2,6 +2,7 @@
<PropertyGroup>
<TargetFrameworks>net461;netcoreapp1.1</TargetFrameworks>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<OutputType>Library</OutputType>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@ -51,8 +52,5 @@
<Import Project="..\..\build\Moq.props" />
<Import Project="..\..\build\Rx.props" />
<Import Project="..\..\build\XUnit.props" />
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp1.1'">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0" />
</ItemGroup>
<Import Condition="'$(TargetFramework)' == 'net461'" Project="$(MSBuildThisFileDirectory)..\..\src\Shared\nuget.workaround.targets" />
</Project>

2
tests/Avalonia.UnitTests/TestRoot.cs

@ -43,7 +43,7 @@ namespace Avalonia.UnitTests
public Size ClientSize => new Size(100, 100);
public Size MaxClientSize => Size.Infinity;
public Size MaxClientSize { get; set; } = Size.Infinity;
public double LayoutScaling => 1;

4
tools/packages.config

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Cake" version="0.18.0" />
</packages>
Loading…
Cancel
Save