diff --git a/appveyor.yml b/appveyor.yml index 2909da65b7..db7d129dd1 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -20,6 +20,11 @@ build_script: - ps: .\build.ps1 -Target "AppVeyor" -Platform "$env:platform" -Configuration "$env:configuration" after_build: - .\packages\JetBrains.dotMemoryUnit.2.1.20150828.125449\tools\dotMemoryUnit.exe -targetExecutable="%xunit20%\xunit.console.x86.exe" -returnTargetExitCode --"tests\Avalonia.LeakTests\bin\Release\Avalonia.LeakTests.dll" + +- "SET PATH=C:\\Python34;C:\\Python34\\Scripts;%PATH%" +- pip install codecov +- codecov -f "./artifacts/coverage.xml" + test: off artifacts: - path: artifacts\nuget\*.nupkg diff --git a/build.cake b/build.cake index d128fbcc5e..a3d7cf64d2 100644 --- a/build.cake +++ b/build.cake @@ -10,6 +10,7 @@ /////////////////////////////////////////////////////////////////////////////// #tool "nuget:?package=xunit.runner.console&version=2.1.0" +#tool "nuget:?package=OpenCover" /////////////////////////////////////////////////////////////////////////////// // USINGS @@ -92,6 +93,7 @@ var artifactsDir = (DirectoryPath)Directory("./artifacts"); var nugetRoot = artifactsDir.Combine("nuget"); var zipRoot = artifactsDir.Combine("zip"); var binRoot = artifactsDir.Combine("bin"); +var testsRoot = artifactsDir.Combine("tests"); var dirSuffix = configuration; var dirSuffixSkia = (isPlatformAnyCPU ? "x86" : platform) + "/" + configuration; @@ -585,6 +587,7 @@ Task("Clean") CleanDirectory(nugetRoot); CleanDirectory(zipRoot); CleanDirectory(binRoot); + CleanDirectory(testsRoot); }); Task("Restore-NuGet-Packages") @@ -670,18 +673,35 @@ Task("Run-Unit-Tests") "./tools/xunit.runner.console/tools/xunit.console.x86.exe" : "./tools/xunit.runner.console/tools/xunit.console.exe"; - var settings = new XUnit2Settings + var xUnitSettings = new XUnit2Settings { ToolPath = toolPath, - Parallelism = ParallelismOption.None + Parallelism = ParallelismOption.None, + ShadowCopy = false }; - settings.NoAppDomain = !isRunningOnWindows; + xUnitSettings.NoAppDomain = !isRunningOnWindows; - foreach (var file in unitTests) + var openCoverOutput = artifactsDir.GetFilePath(new FilePath("./coverage.xml")); + var openCoverSettings = new OpenCoverSettings() + .WithFilter("+[Avalonia.*]* -[*Test*]* -[ControlCatalog*]*") + .WithFilter("-[Avalonia.*]OmniXaml.* -[Avalonia.*]Glass.*") + .WithFilter("-[Avalonia.HtmlRenderer]TheArtOfDev.HtmlRenderer.* +[Avalonia.HtmlRenderer]TheArtOfDev.HtmlRenderer.Avalonia.* -[Avalonia.ReactiveUI]*"); + + foreach(var test in unitTests) { - Information("Running test " + file.GetFilenameWithoutExtension()); - XUnit2(file.FullPath, settings); + CopyDirectory(test.GetDirectory(), testsRoot); + } + + if(isRunningOnWindows) + { + OpenCover(context => { + context.XUnit2(unitTests.Select(test => testsRoot.GetFilePath(test).FullPath), xUnitSettings); + }, openCoverOutput, openCoverSettings); + } + else + { + XUnit2(unitTests.Select(test => test.FullPath), xUnitSettings); } }); diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 7a271e8615..719e888dd4 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -202,6 +202,7 @@ namespace Avalonia.Controls { if (!_ignoreTextChanges) { + CaretIndex = CoerceCaretIndex(CaretIndex, value?.Length ?? 0); SetAndRaise(TextProperty, ref _text, value); } } @@ -558,10 +559,11 @@ namespace Avalonia.Controls return null; } - private int CoerceCaretIndex(int value) + private int CoerceCaretIndex(int value) => CoerceCaretIndex(value, Text?.Length ?? 0); + + private int CoerceCaretIndex(int value, int length) { var text = Text; - var length = text?.Length ?? 0; if (value < 0) { diff --git a/src/Skia/Avalonia.Skia/FormattedTextImpl.cs b/src/Skia/Avalonia.Skia/FormattedTextImpl.cs index e24b6053b0..51f4b2b724 100644 --- a/src/Skia/Avalonia.Skia/FormattedTextImpl.cs +++ b/src/Skia/Avalonia.Skia/FormattedTextImpl.cs @@ -522,7 +522,7 @@ namespace Avalonia.Skia _lineHeight = mDescent - mAscent; // Rendering is relative to baseline - _lineOffset = -metrics.Top; + _lineOffset = (-metrics.Ascent); string subString; @@ -590,7 +590,7 @@ namespace Avalonia.Skia else { var lastLine = _skiaLines[_skiaLines.Count - 1]; - _size = new Size(maxX, lastLine.Top + lastLine.Height + lastLineDescent); + _size = new Size(maxX, lastLine.Top + lastLine.Height); } } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 65e10cafda..5db3f69f85 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -682,6 +682,7 @@ namespace Avalonia.Win32 } UnmanagedMethods.ShowWindow(_hwnd, command); + UnmanagedMethods.SetFocus(_hwnd); } public void SetIcon(IWindowIconImpl icon) diff --git a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs index cff49bc32e..26fc2a2461 100644 --- a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs @@ -1,6 +1,8 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using System; +using System.Reactive.Linq; using Avalonia.Controls.Presenters; using Avalonia.Controls.Templates; using Avalonia.Data; @@ -200,6 +202,33 @@ namespace Avalonia.Controls.UnitTests } } + [Fact] + public void Setting_Text_Updates_CaretPosition() + { + using (UnitTestApplication.Start(Services)) + { + var target = new TextBox + { + Text = "Initial Text", + CaretIndex = 11 + }; + + var invoked = false; + + target.GetObservable(TextBox.TextProperty).Skip(1).Subscribe(_ => + { + // Caret index should be set before Text changed notification, as we don't want + // to notify with an invalid CaretIndex. + Assert.Equal(7, target.CaretIndex); + invoked = true; + }); + + target.Text = "Changed"; + + Assert.True(invoked); + } + } + private static TestServices Services => TestServices.MockThreadingInterface.With( standardCursorFactory: Mock.Of()); diff --git a/tests/Avalonia.LeakTests/Avalonia.LeakTests.csproj b/tests/Avalonia.LeakTests/Avalonia.LeakTests.csproj index b3734255d6..31e7d5ebea 100644 --- a/tests/Avalonia.LeakTests/Avalonia.LeakTests.csproj +++ b/tests/Avalonia.LeakTests/Avalonia.LeakTests.csproj @@ -99,6 +99,7 @@ + diff --git a/tests/Avalonia.LeakTests/MemberSelectorTests.cs b/tests/Avalonia.LeakTests/MemberSelectorTests.cs new file mode 100644 index 0000000000..d794e788fd --- /dev/null +++ b/tests/Avalonia.LeakTests/MemberSelectorTests.cs @@ -0,0 +1,47 @@ +using Avalonia.Markup.Xaml.Templates; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace Avalonia.LeakTests +{ + public class MemberSelectorTests + { + [Fact] + public void Should_Not_Hold_Reference_To_Object() + { + WeakReference dataRef = null; + + var selector = new MemberSelector() { MemberName = "Child.StringValue" }; + + Action run = () => + { + var data = new Item() + { + Child = new Item() { StringValue = "Value1" } + }; + + Assert.Same("Value1", selector.Select(data)); + + dataRef = new WeakReference(data); + }; + + run(); + + GC.Collect(); + + Assert.False(dataRef.IsAlive); + } + + private class Item + { + public Item Child { get; set; } + public int IntValue { get; set; } + + public string StringValue { get; set; } + } + } +} diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Templates/MemberSelectorTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Templates/MemberSelectorTests.cs index 7082863c0d..49a88e8fae 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Templates/MemberSelectorTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Templates/MemberSelectorTests.cs @@ -9,32 +9,6 @@ namespace Avalonia.Markup.Xaml.UnitTests.Templates { public class MemberSelectorTests { - [Fact] - public void Should_Not_Hold_Reference_To_Object() - { - WeakReference dataRef = null; - - var selector = new MemberSelector() { MemberName = "Child.StringValue" }; - - Action run = () => - { - var data = new Item() - { - Child = new Item() { StringValue = "Value1" } - }; - - Assert.Same("Value1", selector.Select(data)); - - dataRef = new WeakReference(data); - }; - - run(); - - GC.Collect(); - - Assert.False(dataRef.IsAlive); - } - [Fact] public void Should_Select_Child_Property_Value() {