From 2caf1679fa46f0e3f87f0e5a0e94babf08aa6ae9 Mon Sep 17 00:00:00 2001 From: Eli Arbel Date: Fri, 22 Mar 2019 12:46:22 +0200 Subject: [PATCH] Add tests from AvalonEdit (#49) --- AvaloniaEdit.sln | 10 + src/AvaloniaEdit/XmlnsAttributes.cs | 6 +- .../AvaloniaEdit.Tests.csproj | 18 + .../MockPlatformRenderInterface.cs | 70 +++ .../AvaloniaMocks/MockStreamGeometryImpl.cs | 159 +++++ .../AvaloniaMocks/MockWindowingPlatform.cs | 30 + .../AvaloniaMocks/TestServices.cs | 179 ++++++ .../AvaloniaMocks/UnitTestApplication.cs | 67 ++ .../Document/ChangeTrackingTest.cs | 75 +++ .../Document/CollapsingTests.cs | 170 +++++ .../Document/HeightTests.cs | 91 +++ .../Document/LineManagerTests.cs | 585 ++++++++++++++++++ .../Document/RandomizedLineManagerTest.cs | 183 ++++++ .../Document/TextAnchorTest.cs | 328 ++++++++++ .../Document/TextSegmentTreeTest.cs | 365 +++++++++++ .../Document/TextUtilitiesTests.cs | 90 +++ .../Document/UndoStackTests.cs | 92 +++ .../Editing/ChangeDocumentTests.cs | 86 +++ .../TextSegmentReadOnlySectionTests.cs | 183 ++++++ .../Highlighting/HighlightedLineMergeTests.cs | 179 ++++++ .../Highlighting/HtmlClipboardTests.cs | 56 ++ test/AvaloniaEdit.Tests/Search/FindTests.cs | 129 ++++ .../Utils/CaretNavigationTests.cs | 153 +++++ .../Utils/CompressingTreeListTests.cs | 146 +++++ .../Utils/ExtensionMethodsTests.cs | 51 ++ .../Utils/IndentationStringTests.cs | 51 ++ test/AvaloniaEdit.Tests/Utils/RopeTests.cs | 195 ++++++ test/AvaloniaEdit.Tests/WeakReferenceTests.cs | 116 ++++ 28 files changed, 3862 insertions(+), 1 deletion(-) create mode 100644 test/AvaloniaEdit.Tests/AvaloniaEdit.Tests.csproj create mode 100644 test/AvaloniaEdit.Tests/AvaloniaMocks/MockPlatformRenderInterface.cs create mode 100644 test/AvaloniaEdit.Tests/AvaloniaMocks/MockStreamGeometryImpl.cs create mode 100644 test/AvaloniaEdit.Tests/AvaloniaMocks/MockWindowingPlatform.cs create mode 100644 test/AvaloniaEdit.Tests/AvaloniaMocks/TestServices.cs create mode 100644 test/AvaloniaEdit.Tests/AvaloniaMocks/UnitTestApplication.cs create mode 100644 test/AvaloniaEdit.Tests/Document/ChangeTrackingTest.cs create mode 100644 test/AvaloniaEdit.Tests/Document/CollapsingTests.cs create mode 100644 test/AvaloniaEdit.Tests/Document/HeightTests.cs create mode 100644 test/AvaloniaEdit.Tests/Document/LineManagerTests.cs create mode 100644 test/AvaloniaEdit.Tests/Document/RandomizedLineManagerTest.cs create mode 100644 test/AvaloniaEdit.Tests/Document/TextAnchorTest.cs create mode 100644 test/AvaloniaEdit.Tests/Document/TextSegmentTreeTest.cs create mode 100644 test/AvaloniaEdit.Tests/Document/TextUtilitiesTests.cs create mode 100644 test/AvaloniaEdit.Tests/Document/UndoStackTests.cs create mode 100644 test/AvaloniaEdit.Tests/Editing/ChangeDocumentTests.cs create mode 100644 test/AvaloniaEdit.Tests/Editing/TextSegmentReadOnlySectionTests.cs create mode 100644 test/AvaloniaEdit.Tests/Highlighting/HighlightedLineMergeTests.cs create mode 100644 test/AvaloniaEdit.Tests/Highlighting/HtmlClipboardTests.cs create mode 100644 test/AvaloniaEdit.Tests/Search/FindTests.cs create mode 100644 test/AvaloniaEdit.Tests/Utils/CaretNavigationTests.cs create mode 100644 test/AvaloniaEdit.Tests/Utils/CompressingTreeListTests.cs create mode 100644 test/AvaloniaEdit.Tests/Utils/ExtensionMethodsTests.cs create mode 100644 test/AvaloniaEdit.Tests/Utils/IndentationStringTests.cs create mode 100644 test/AvaloniaEdit.Tests/Utils/RopeTests.cs create mode 100644 test/AvaloniaEdit.Tests/WeakReferenceTests.cs diff --git a/AvaloniaEdit.sln b/AvaloniaEdit.sln index 4761e28..3c68a15 100644 --- a/AvaloniaEdit.sln +++ b/AvaloniaEdit.sln @@ -7,6 +7,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AvaloniaEdit", "src\Avaloni EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AvaloniaEdit.Demo", "src\AvaloniaEdit.Demo\AvaloniaEdit.Demo.csproj", "{03763F37-9BD9-4D1D-ADC9-1050F6F8C062}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AvaloniaEdit.Tests", "test\AvaloniaEdit.Tests\AvaloniaEdit.Tests.csproj", "{9E5D4372-D362-44A2-984D-578288870AB8}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -31,6 +33,14 @@ Global {03763F37-9BD9-4D1D-ADC9-1050F6F8C062}.Release|Any CPU.Build.0 = Release|Any CPU {03763F37-9BD9-4D1D-ADC9-1050F6F8C062}.Release|x64.ActiveCfg = Release|Any CPU {03763F37-9BD9-4D1D-ADC9-1050F6F8C062}.Release|x64.Build.0 = Release|Any CPU + {9E5D4372-D362-44A2-984D-578288870AB8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9E5D4372-D362-44A2-984D-578288870AB8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9E5D4372-D362-44A2-984D-578288870AB8}.Debug|x64.ActiveCfg = Debug|Any CPU + {9E5D4372-D362-44A2-984D-578288870AB8}.Debug|x64.Build.0 = Debug|Any CPU + {9E5D4372-D362-44A2-984D-578288870AB8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9E5D4372-D362-44A2-984D-578288870AB8}.Release|Any CPU.Build.0 = Release|Any CPU + {9E5D4372-D362-44A2-984D-578288870AB8}.Release|x64.ActiveCfg = Release|Any CPU + {9E5D4372-D362-44A2-984D-578288870AB8}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/AvaloniaEdit/XmlnsAttributes.cs b/src/AvaloniaEdit/XmlnsAttributes.cs index 7286192..0d4e1d2 100644 --- a/src/AvaloniaEdit/XmlnsAttributes.cs +++ b/src/AvaloniaEdit/XmlnsAttributes.cs @@ -1,7 +1,11 @@ -using Avalonia.Metadata; +using System.Runtime.CompilerServices; +using Avalonia.Metadata; [assembly: XmlnsDefinition("https://github.com/avaloniaui/avaloniaedit", "AvaloniaEdit")] [assembly: XmlnsDefinition("https://github.com/avaloniaui/avaloniaedit", "AvaloniaEdit.Editing")] [assembly: XmlnsDefinition("https://github.com/avaloniaui/avaloniaedit", "AvaloniaEdit.Rendering")] [assembly: XmlnsDefinition("https://github.com/avaloniaui/avaloniaedit", "AvaloniaEdit.Highlighting")] [assembly: XmlnsDefinition("https://github.com/avaloniaui/avaloniaedit", "AvaloniaEdit.Search")] + +[assembly: InternalsVisibleTo("AvaloniaEdit.Tests")] + diff --git a/test/AvaloniaEdit.Tests/AvaloniaEdit.Tests.csproj b/test/AvaloniaEdit.Tests/AvaloniaEdit.Tests.csproj new file mode 100644 index 0000000..5c9acf4 --- /dev/null +++ b/test/AvaloniaEdit.Tests/AvaloniaEdit.Tests.csproj @@ -0,0 +1,18 @@ + + + + netcoreapp2.2 + + + + + + + + + + + + + + diff --git a/test/AvaloniaEdit.Tests/AvaloniaMocks/MockPlatformRenderInterface.cs b/test/AvaloniaEdit.Tests/AvaloniaMocks/MockPlatformRenderInterface.cs new file mode 100644 index 0000000..f8a5cf5 --- /dev/null +++ b/test/AvaloniaEdit.Tests/AvaloniaMocks/MockPlatformRenderInterface.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Avalonia; +using Avalonia.Media; +using Avalonia.Platform; +using Moq; + +namespace AvaloniaEdit.AvaloniaMocks +{ + public class MockPlatformRenderInterface : IPlatformRenderInterface + { + public IEnumerable InstalledFontNames => throw new NotImplementedException(); + + public IFormattedTextImpl CreateFormattedText( + string text, + Typeface typeface, + TextAlignment textAlignment, + TextWrapping wrapping, + Size constraint, + IReadOnlyList spans) + { + return Mock.Of(); + } + + public IRenderTarget CreateRenderTarget(IEnumerable surfaces) + { + return Mock.Of(); + } + + public IRenderTargetBitmapImpl CreateRenderTargetBitmap( + int width, + int height, + double dpiX, + double dpiY) + { + return Mock.Of(); + } + + public IRenderTargetBitmapImpl CreateRenderTargetBitmap(PixelSize size, Vector dpi) + { + throw new NotImplementedException(); + } + + public IStreamGeometryImpl CreateStreamGeometry() + { + return new MockStreamGeometryImpl(); + } + + public IWriteableBitmapImpl CreateWriteableBitmap(PixelSize size, Vector dpi, PixelFormat? format = null) + { + throw new NotImplementedException(); + } + + public IBitmapImpl LoadBitmap(Stream stream) + { + return Mock.Of(); + } + + public IBitmapImpl LoadBitmap(string fileName) + { + return Mock.Of(); + } + + public IBitmapImpl LoadBitmap(PixelFormat format, IntPtr data, PixelSize size, Vector dpi, int stride) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/test/AvaloniaEdit.Tests/AvaloniaMocks/MockStreamGeometryImpl.cs b/test/AvaloniaEdit.Tests/AvaloniaMocks/MockStreamGeometryImpl.cs new file mode 100644 index 0000000..d2e6f7c --- /dev/null +++ b/test/AvaloniaEdit.Tests/AvaloniaMocks/MockStreamGeometryImpl.cs @@ -0,0 +1,159 @@ +using System; +using System.Collections.Generic; +using Avalonia; +using Avalonia.Media; +using Avalonia.Platform; + +namespace AvaloniaEdit.AvaloniaMocks +{ + public class MockStreamGeometryImpl : IStreamGeometryImpl, ITransformedGeometryImpl + { + private MockStreamGeometryContext _context; + + public MockStreamGeometryImpl() + { + Transform = Matrix.Identity; + _context = new MockStreamGeometryContext(); + } + + public MockStreamGeometryImpl(Matrix transform) + { + Transform = transform; + _context = new MockStreamGeometryContext(); + } + + private MockStreamGeometryImpl(Matrix transform, MockStreamGeometryContext context) + { + Transform = transform; + _context = context; + } + + public IGeometryImpl SourceGeometry { get; } + + public Rect Bounds => _context.CalculateBounds(); + + public Matrix Transform { get; } + + public IStreamGeometryImpl Clone() + { + return this; + } + + public void Dispose() + { + } + + public bool FillContains(Point point) + { + return _context.FillContains(point); + } + + public bool StrokeContains(Pen pen, Point point) + { + return false; + } + + public Rect GetRenderBounds(Pen pen) => Bounds; + + public IGeometryImpl Intersect(IGeometryImpl geometry) + { + return new MockStreamGeometryImpl(Transform); + } + + public IStreamGeometryContextImpl Open() + { + return _context; + } + + public ITransformedGeometryImpl WithTransform(Matrix transform) + { + return new MockStreamGeometryImpl(transform, _context); + } + + class MockStreamGeometryContext : IStreamGeometryContextImpl + { + private List points = new List(); + public void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection) + { + } + + public void BeginFigure(Point startPoint, bool isFilled) + { + points.Add(startPoint); + } + + public Rect CalculateBounds() + { + var left = double.MaxValue; + var right = double.MinValue; + var top = double.MaxValue; + var bottom = double.MinValue; + + foreach (var p in points) + { + left = Math.Min(p.X, left); + right = Math.Max(p.X, right); + top = Math.Min(p.Y, top); + bottom = Math.Max(p.Y, bottom); + } + + return new Rect(new Point(left, top), new Point(right, bottom)); + } + + public void CubicBezierTo(Point point1, Point point2, Point point3) + { + } + + public void Dispose() + { + } + + public void EndFigure(bool isClosed) + { + } + + public void LineTo(Point point) + { + points.Add(point); + } + + public void QuadraticBezierTo(Point control, Point endPoint) + { + throw new NotImplementedException(); + } + + public void SetFillRule(FillRule fillRule) + { + } + + public bool FillContains(Point point) + { + // Use the algorithm from http://www.blackpawn.com/texts/pointinpoly/default.html + // to determine if the point is in the geometry (since it will always be convex in this situation) + for (int i = 0; i < points.Count; i++) + { + var a = points[i]; + var b = points[(i + 1) % points.Count]; + var c = points[(i + 2) % points.Count]; + + Vector v0 = c - a; + Vector v1 = b - a; + Vector v2 = point - a; + + var dot00 = v0 * v0; + var dot01 = v0 * v1; + var dot02 = v0 * v2; + var dot11 = v1 * v1; + var dot12 = v1 * v2; + + + var invDenom = 1 / (dot00 * dot11 - dot01 * dot01); + var u = (dot11 * dot02 - dot01 * dot12) * invDenom; + var v = (dot00 * dot12 - dot01 * dot02) * invDenom; + if ((u >= 0) && (v >= 0) && (u + v < 1)) return true; + } + return false; + } + } + } +} \ No newline at end of file diff --git a/test/AvaloniaEdit.Tests/AvaloniaMocks/MockWindowingPlatform.cs b/test/AvaloniaEdit.Tests/AvaloniaMocks/MockWindowingPlatform.cs new file mode 100644 index 0000000..b34d640 --- /dev/null +++ b/test/AvaloniaEdit.Tests/AvaloniaMocks/MockWindowingPlatform.cs @@ -0,0 +1,30 @@ +using System; +using Avalonia.Platform; +using Moq; + +namespace AvaloniaEdit.AvaloniaMocks +{ + public class MockWindowingPlatform : IWindowingPlatform + { + private readonly Func _windowImpl; + private readonly Func _popupImpl; + + public MockWindowingPlatform(Func windowImpl = null, Func popupImpl = null) + { + _windowImpl = windowImpl; + _popupImpl = popupImpl; + } + + public IWindowImpl CreateWindow() + { + return _windowImpl?.Invoke() ?? Mock.Of(x => x.Scaling == 1); + } + + public IEmbeddableWindowImpl CreateEmbeddableWindow() + { + throw new NotImplementedException(); + } + + public IPopupImpl CreatePopup() => _popupImpl?.Invoke() ?? Mock.Of(x => x.Scaling == 1); + } +} \ No newline at end of file diff --git a/test/AvaloniaEdit.Tests/AvaloniaMocks/TestServices.cs b/test/AvaloniaEdit.Tests/AvaloniaMocks/TestServices.cs new file mode 100644 index 0000000..5a663fa --- /dev/null +++ b/test/AvaloniaEdit.Tests/AvaloniaMocks/TestServices.cs @@ -0,0 +1,179 @@ +using System; +using System.Collections.Generic; +using System.Reactive.Concurrency; +using Avalonia; +using Avalonia.Input; +using Avalonia.Layout; +using Avalonia.Markup.Xaml; +using Avalonia.Media; +using Avalonia.Platform; +using Avalonia.Rendering; +using Avalonia.Shared.PlatformSupport; +using Avalonia.Styling; +using Avalonia.Themes.Default; +using Moq; + +namespace AvaloniaEdit.AvaloniaMocks +{ + public class TestServices + { + public static readonly TestServices StyledWindow = new TestServices( + assetLoader: new AssetLoader(), + layoutManager: new LayoutManager(), + platform: new AppBuilder().RuntimePlatform, + renderInterface: new MockPlatformRenderInterface(), + standardCursorFactory: Mock.Of(), + styler: new Styler(), + theme: () => CreateDefaultTheme(), + threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true), + windowingPlatform: new MockWindowingPlatform()); + + public static readonly TestServices MockPlatformRenderInterface = new TestServices( + renderInterface: new MockPlatformRenderInterface()); + + public static readonly TestServices MockPlatformWrapper = new TestServices( + platform: Mock.Of()); + + public static readonly TestServices MockStyler = new TestServices( + styler: Mock.Of()); + + public static readonly TestServices MockThreadingInterface = new TestServices( + threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)); + + public static readonly TestServices MockWindowingPlatform = new TestServices( + windowingPlatform: new MockWindowingPlatform()); + + public static readonly TestServices RealFocus = new TestServices( + focusManager: new FocusManager(), + keyboardDevice: () => new KeyboardDevice(), + keyboardNavigation: new KeyboardNavigationHandler(), + inputManager: new InputManager()); + + public static readonly TestServices RealLayoutManager = new TestServices( + layoutManager: new LayoutManager()); + + public static readonly TestServices RealStyler = new TestServices( + styler: new Styler()); + + public TestServices( + IAssetLoader assetLoader = null, + IFocusManager focusManager = null, + IInputManager inputManager = null, + Func keyboardDevice = null, + IKeyboardNavigationHandler keyboardNavigation = null, + ILayoutManager layoutManager = null, + Func mouseDevice = null, + IRuntimePlatform platform = null, + IPlatformRenderInterface renderInterface = null, + IRenderLoop renderLoop = null, + IScheduler scheduler = null, + IStandardCursorFactory standardCursorFactory = null, + IStyler styler = null, + Func theme = null, + IPlatformThreadingInterface threadingInterface = null, + IWindowImpl windowImpl = null, + IWindowingPlatform windowingPlatform = null) + { + AssetLoader = assetLoader; + FocusManager = focusManager; + InputManager = inputManager; + KeyboardDevice = keyboardDevice; + KeyboardNavigation = keyboardNavigation; + LayoutManager = layoutManager; + MouseDevice = mouseDevice; + Platform = platform; + RenderInterface = renderInterface; + Scheduler = scheduler; + StandardCursorFactory = standardCursorFactory; + Styler = styler; + Theme = theme; + ThreadingInterface = threadingInterface; + WindowImpl = windowImpl; + WindowingPlatform = windowingPlatform; + } + + public IAssetLoader AssetLoader { get; } + public IInputManager InputManager { get; } + public IFocusManager FocusManager { get; } + public Func KeyboardDevice { get; } + public IKeyboardNavigationHandler KeyboardNavigation { get; } + public ILayoutManager LayoutManager { get; } + public Func MouseDevice { get; } + public IRuntimePlatform Platform { get; } + public IPlatformRenderInterface RenderInterface { get; } + public IScheduler Scheduler { get; } + public IStandardCursorFactory StandardCursorFactory { get; } + public IStyler Styler { get; } + public Func Theme { get; } + public IPlatformThreadingInterface ThreadingInterface { get; } + public IWindowImpl WindowImpl { get; } + public IWindowingPlatform WindowingPlatform { get; } + + public TestServices With( + IAssetLoader assetLoader = null, + IFocusManager focusManager = null, + IInputManager inputManager = null, + Func keyboardDevice = null, + IKeyboardNavigationHandler keyboardNavigation = null, + ILayoutManager layoutManager = null, + Func mouseDevice = null, + IRuntimePlatform platform = null, + IPlatformRenderInterface renderInterface = null, + IRenderLoop renderLoop = null, + IScheduler scheduler = null, + IStandardCursorFactory standardCursorFactory = null, + IStyler styler = null, + Func theme = null, + IPlatformThreadingInterface threadingInterface = null, + IWindowImpl windowImpl = null, + IWindowingPlatform windowingPlatform = null) + { + return new TestServices( + assetLoader: assetLoader ?? AssetLoader, + focusManager: focusManager ?? FocusManager, + inputManager: inputManager ?? InputManager, + keyboardDevice: keyboardDevice ?? KeyboardDevice, + keyboardNavigation: keyboardNavigation ?? KeyboardNavigation, + layoutManager: layoutManager ?? LayoutManager, + mouseDevice: mouseDevice ?? MouseDevice, + platform: platform ?? Platform, + renderInterface: renderInterface ?? RenderInterface, + scheduler: scheduler ?? Scheduler, + standardCursorFactory: standardCursorFactory ?? StandardCursorFactory, + styler: styler ?? Styler, + theme: theme ?? Theme, + threadingInterface: threadingInterface ?? ThreadingInterface, + windowingPlatform: windowingPlatform ?? WindowingPlatform, + windowImpl: windowImpl ?? WindowImpl); + } + + private static Styles CreateDefaultTheme() + { + var result = new Styles + { + new DefaultTheme(), + }; + + var loader = new AvaloniaXamlLoader(); + var baseLight = (IStyle)loader.Load( + new Uri("resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default")); + result.Add(baseLight); + + return result; + } + + private static IPlatformRenderInterface CreateRenderInterfaceMock() + { + return Mock.Of(x => + x.CreateFormattedText( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny>()) == Mock.Of() && + x.CreateStreamGeometry() == Mock.Of( + y => y.Open() == Mock.Of())); + } + } +} \ No newline at end of file diff --git a/test/AvaloniaEdit.Tests/AvaloniaMocks/UnitTestApplication.cs b/test/AvaloniaEdit.Tests/AvaloniaMocks/UnitTestApplication.cs new file mode 100644 index 0000000..4c4a8ae --- /dev/null +++ b/test/AvaloniaEdit.Tests/AvaloniaMocks/UnitTestApplication.cs @@ -0,0 +1,67 @@ +using System; +using System.Reactive.Concurrency; +using System.Reactive.Disposables; +using System.Reflection; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Layout; +using Avalonia.Platform; +using Avalonia.Styling; +using Avalonia.Threading; + +namespace AvaloniaEdit.AvaloniaMocks +{ + public class UnitTestApplication : Application + { + public UnitTestApplication(TestServices services) + { + Services = services ?? new TestServices(); + RegisterServices(); + } + + public TestServices Services { get; } + + public static IDisposable Start(TestServices services = null) + { + AvaloniaLocator.Current = (AvaloniaLocator.CurrentMutable = new AvaloniaLocator()); + var app = new UnitTestApplication(services); + AvaloniaLocator.CurrentMutable.BindToSelf(app); + var updateServices = Dispatcher.UIThread.GetType().GetMethod("UpdateServices", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + updateServices?.Invoke(Dispatcher.UIThread, null); + return Disposable.Create(() => + { + updateServices?.Invoke(Dispatcher.UIThread, null); + AvaloniaLocator.CurrentMutable = null; + AvaloniaLocator.Current = null; + }); + } + + public override void RegisterServices() + { + AvaloniaLocator.CurrentMutable + .Bind().ToConstant(Services.AssetLoader) + .Bind().ToConstant(Services.FocusManager) + .BindToSelf(this) + .Bind().ToConstant(Services.InputManager) + .Bind().ToConstant(Services.KeyboardDevice?.Invoke()) + .Bind().ToConstant(Services.KeyboardNavigation) + .Bind().ToConstant(Services.LayoutManager) + .Bind().ToConstant(Services.MouseDevice?.Invoke()) + .Bind().ToConstant(Services.Platform) + .Bind().ToConstant(Services.RenderInterface) + .Bind().ToConstant(Services.ThreadingInterface) + .Bind().ToConstant(Services.Scheduler) + .Bind().ToConstant(Services.StandardCursorFactory) + .Bind().ToConstant(Services.Styler) + .Bind().ToConstant(Services.WindowingPlatform) + .Bind().ToConstant(this); + var styles = Services.Theme?.Invoke(); + + if (styles != null) + { + Styles.AddRange(styles); + } + } + } +} \ No newline at end of file diff --git a/test/AvaloniaEdit.Tests/Document/ChangeTrackingTest.cs b/test/AvaloniaEdit.Tests/Document/ChangeTrackingTest.cs new file mode 100644 index 0000000..0b94c40 --- /dev/null +++ b/test/AvaloniaEdit.Tests/Document/ChangeTrackingTest.cs @@ -0,0 +1,75 @@ +// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System.Linq; +using NUnit.Framework; + +namespace AvaloniaEdit.Document +{ + [TestFixture] + public class ChangeTrackingTest + { + [Test] + public void NoChanges() + { + TextDocument document = new TextDocument("initial text"); + ITextSource snapshot1 = document.CreateSnapshot(); + ITextSource snapshot2 = document.CreateSnapshot(); + Assert.AreEqual(0, snapshot1.Version.CompareAge(snapshot2.Version)); + Assert.AreEqual(0, snapshot1.Version.GetChangesTo(snapshot2.Version).Count()); + Assert.AreEqual(document.Text, snapshot1.Text); + Assert.AreEqual(document.Text, snapshot2.Text); + } + + [Test] + public void ForwardChanges() + { + TextDocument document = new TextDocument("initial text"); + ITextSource snapshot1 = document.CreateSnapshot(); + document.Replace(0, 7, "nw"); + document.Insert(1, "e"); + ITextSource snapshot2 = document.CreateSnapshot(); + Assert.AreEqual(-1, snapshot1.Version.CompareAge(snapshot2.Version)); + TextChangeEventArgs[] arr = snapshot1.Version.GetChangesTo(snapshot2.Version).ToArray(); + Assert.AreEqual(2, arr.Length); + Assert.AreEqual("nw", arr[0].InsertedText.Text); + Assert.AreEqual("e", arr[1].InsertedText.Text); + + Assert.AreEqual("initial text", snapshot1.Text); + Assert.AreEqual("new text", snapshot2.Text); + } + + [Test] + public void BackwardChanges() + { + TextDocument document = new TextDocument("initial text"); + ITextSource snapshot1 = document.CreateSnapshot(); + document.Replace(0, 7, "nw"); + document.Insert(1, "e"); + ITextSource snapshot2 = document.CreateSnapshot(); + Assert.AreEqual(1, snapshot2.Version.CompareAge(snapshot1.Version)); + TextChangeEventArgs[] arr = snapshot2.Version.GetChangesTo(snapshot1.Version).ToArray(); + Assert.AreEqual(2, arr.Length); + Assert.AreEqual("", arr[0].InsertedText.Text); + Assert.AreEqual("initial", arr[1].InsertedText.Text); + + Assert.AreEqual("initial text", snapshot1.Text); + Assert.AreEqual("new text", snapshot2.Text); + } + } +} diff --git a/test/AvaloniaEdit.Tests/Document/CollapsingTests.cs b/test/AvaloniaEdit.Tests/Document/CollapsingTests.cs new file mode 100644 index 0000000..150c05d --- /dev/null +++ b/test/AvaloniaEdit.Tests/Document/CollapsingTests.cs @@ -0,0 +1,170 @@ +// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using AvaloniaEdit.Rendering; +using NUnit.Framework; + +namespace AvaloniaEdit.Document +{ + [TestFixture] + public class CollapsingTests + { + TextDocument document; + HeightTree heightTree; + + [SetUp] + public void Setup() + { + document = new TextDocument(); + document.Text = "1\n2\n3\n4\n5\n6\n7\n8\n9\n10"; + heightTree = new HeightTree(document, 10); + foreach (DocumentLine line in document.Lines) { + heightTree.SetHeight(line, line.LineNumber); + } + } + + CollapsedLineSection SimpleCheck(int from, int to) + { + CollapsedLineSection sec1 = heightTree.CollapseText(document.GetLineByNumber(from), document.GetLineByNumber(to)); + for (int i = 1; i < from; i++) { + Assert.IsFalse(heightTree.GetIsCollapsed(i)); + } + for (int i = from; i <= to; i++) { + Assert.IsTrue(heightTree.GetIsCollapsed(i)); + } + for (int i = to + 1; i <= 10; i++) { + Assert.IsFalse(heightTree.GetIsCollapsed(i)); + } + CheckHeights(); + return sec1; + } + + [Test] + public void SimpleCheck() + { + SimpleCheck(4, 6); + } + + [Test] + public void SimpleUncollapse() + { + CollapsedLineSection sec1 = heightTree.CollapseText(document.GetLineByNumber(4), document.GetLineByNumber(6)); + sec1.Uncollapse(); + for (int i = 1; i <= 10; i++) { + Assert.IsFalse(heightTree.GetIsCollapsed(i)); + } + CheckHeights(); + } + + [Test] + public void FullCheck() + { + for (int from = 1; from <= 10; from++) { + for (int to = from; to <= 10; to++) { + try { + SimpleCheck(from, to).Uncollapse(); + for (int i = 1; i <= 10; i++) { + Assert.IsFalse(heightTree.GetIsCollapsed(i)); + } + CheckHeights(); + } catch { + Console.WriteLine("from = " + from + ", to = " + to); + throw; + } + } + } + } + + [Test] + public void InsertInCollapsedSection() + { + CollapsedLineSection sec1 = heightTree.CollapseText(document.GetLineByNumber(4), document.GetLineByNumber(6)); + document.Insert(document.GetLineByNumber(5).Offset, "a\nb\nc"); + for (int i = 1; i < 4; i++) { + Assert.IsFalse(heightTree.GetIsCollapsed(i)); + } + for (int i = 4; i <= 8; i++) { + Assert.IsTrue(heightTree.GetIsCollapsed(i)); + } + for (int i = 9; i <= 12; i++) { + Assert.IsFalse(heightTree.GetIsCollapsed(i)); + } + CheckHeights(); + } + + [Test] + public void RemoveInCollapsedSection() + { + CollapsedLineSection sec1 = heightTree.CollapseText(document.GetLineByNumber(3), document.GetLineByNumber(7)); + int line4Offset = document.GetLineByNumber(4).Offset; + int line6Offset = document.GetLineByNumber(6).Offset; + document.Remove(line4Offset, line6Offset - line4Offset); + for (int i = 1; i < 3; i++) { + Assert.IsFalse(heightTree.GetIsCollapsed(i)); + } + for (int i = 3; i <= 5; i++) { + Assert.IsTrue(heightTree.GetIsCollapsed(i)); + } + for (int i = 6; i <= 8; i++) { + Assert.IsFalse(heightTree.GetIsCollapsed(i)); + } + CheckHeights(); + } + + [Test] + public void RemoveEndOfCollapsedSection() + { + CollapsedLineSection sec1 = heightTree.CollapseText(document.GetLineByNumber(3), document.GetLineByNumber(6)); + int line5Offset = document.GetLineByNumber(5).Offset; + int line8Offset = document.GetLineByNumber(8).Offset; + document.Remove(line5Offset, line8Offset - line5Offset); + for (int i = 1; i < 3; i++) { + Assert.IsFalse(heightTree.GetIsCollapsed(i)); + } + for (int i = 3; i <= 5; i++) { + Assert.IsTrue(heightTree.GetIsCollapsed(i)); + } + for (int i = 6; i <= 7; i++) { + Assert.IsFalse(heightTree.GetIsCollapsed(i)); + } + CheckHeights(); + } + + [Test] + public void RemoveCollapsedSection() + { + CollapsedLineSection sec1 = heightTree.CollapseText(document.GetLineByNumber(3), document.GetLineByNumber(3)); + int line3Offset = document.GetLineByNumber(3).Offset; + document.Remove(line3Offset - 1, 1); + for (int i = 1; i <= 9; i++) { + Assert.IsFalse(heightTree.GetIsCollapsed(i)); + } + CheckHeights(); + Assert.AreSame(null, sec1.Start); + Assert.AreSame(null, sec1.End); + // section gets uncollapsed when it is removed + Assert.IsFalse(sec1.IsCollapsed); + } + + void CheckHeights() + { + HeightTests.CheckHeights(document, heightTree); + } + } +} diff --git a/test/AvaloniaEdit.Tests/Document/HeightTests.cs b/test/AvaloniaEdit.Tests/Document/HeightTests.cs new file mode 100644 index 0000000..9f4e724 --- /dev/null +++ b/test/AvaloniaEdit.Tests/Document/HeightTests.cs @@ -0,0 +1,91 @@ +// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Linq; +using AvaloniaEdit.Rendering; +using NUnit.Framework; + +namespace AvaloniaEdit.Document +{ + [TestFixture] + public class HeightTests + { + TextDocument document; + HeightTree heightTree; + + [SetUp] + public void Setup() + { + document = new TextDocument(); + document.Text = "1\n2\n3\n4\n5\n6\n7\n8\n9\n10"; + heightTree = new HeightTree(document, 10); + foreach (DocumentLine line in document.Lines) { + heightTree.SetHeight(line, line.LineNumber); + } + } + + [Test] + public void SimpleCheck() + { + CheckHeights(); + } + + [Test] + public void TestLinesRemoved() + { + document.Remove(5, 4); + CheckHeights(); + } + + [Test] + public void TestHeightChanged() + { + heightTree.SetHeight(document.GetLineByNumber(4), 100); + CheckHeights(); + } + + [Test] + public void TestLinesInserted() + { + document.Insert(0, "x\ny\n"); + heightTree.SetHeight(document.Lines[0], 100); + heightTree.SetHeight(document.Lines[1], 1000); + heightTree.SetHeight(document.Lines[2], 10000); + CheckHeights(); + } + + void CheckHeights() + { + CheckHeights(document, heightTree); + } + + internal static void CheckHeights(TextDocument document, HeightTree heightTree) + { + double[] heights = document.Lines.Select(l => heightTree.GetIsCollapsed(l.LineNumber) ? 0 : heightTree.GetHeight(l)).ToArray(); + double[] visualPositions = new double[document.LineCount+1]; + for (int i = 0; i < heights.Length; i++) { + visualPositions[i+1]=visualPositions[i]+heights[i]; + } + foreach (DocumentLine ls in document.Lines) { + Assert.AreEqual(visualPositions[ls.LineNumber-1], heightTree.GetVisualPosition(ls)); + } + Assert.AreEqual(visualPositions[document.LineCount], heightTree.TotalHeight); + } + } +} diff --git a/test/AvaloniaEdit.Tests/Document/LineManagerTests.cs b/test/AvaloniaEdit.Tests/Document/LineManagerTests.cs new file mode 100644 index 0000000..ac7d086 --- /dev/null +++ b/test/AvaloniaEdit.Tests/Document/LineManagerTests.cs @@ -0,0 +1,585 @@ +// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; + +namespace AvaloniaEdit.Document +{ + [TestFixture] + public class LineManagerTests + { + TextDocument document; + + [SetUp] + public void SetUp() + { + document = new TextDocument(); + } + + [Test] + public void CheckEmptyDocument() + { + Assert.AreEqual("", document.Text); + Assert.AreEqual(0, document.TextLength); + Assert.AreEqual(1, document.LineCount); + } + + [Test] + public void CheckClearingDocument() + { + document.Text = "Hello,\nWorld!"; + Assert.AreEqual(2, document.LineCount); + var oldLines = document.Lines.ToArray(); + document.Text = ""; + Assert.AreEqual("", document.Text); + Assert.AreEqual(0, document.TextLength); + Assert.AreEqual(1, document.LineCount); + Assert.AreSame(oldLines[0], document.Lines.Single()); + Assert.IsFalse(oldLines[0].IsDeleted); + Assert.IsTrue(oldLines[1].IsDeleted); + Assert.IsNull(oldLines[0].NextLine); + Assert.IsNull(oldLines[1].PreviousLine); + } + + [Test] + public void CheckGetLineInEmptyDocument() + { + Assert.AreEqual(1, document.Lines.Count); + List lines = new List(document.Lines); + Assert.AreEqual(1, lines.Count); + DocumentLine line = document.Lines[0]; + Assert.AreSame(line, lines[0]); + Assert.AreSame(line, document.GetLineByNumber(1)); + Assert.AreSame(line, document.GetLineByOffset(0)); + } + + [Test] + public void CheckLineSegmentInEmptyDocument() + { + DocumentLine line = document.GetLineByNumber(1); + Assert.AreEqual(1, line.LineNumber); + Assert.AreEqual(0, line.Offset); + Assert.IsFalse(line.IsDeleted); + Assert.AreEqual(0, line.Length); + Assert.AreEqual(0, line.TotalLength); + Assert.AreEqual(0, line.DelimiterLength); + } + + [Test] + public void LineIndexOfTest() + { + DocumentLine line = document.GetLineByNumber(1); + Assert.AreEqual(0, document.Lines.IndexOf(line)); + DocumentLine lineFromOtherDocument = new TextDocument().GetLineByNumber(1); + Assert.AreEqual(-1, document.Lines.IndexOf(lineFromOtherDocument)); + document.Text = "a\nb\nc"; + DocumentLine middleLine = document.GetLineByNumber(2); + Assert.AreEqual(1, document.Lines.IndexOf(middleLine)); + document.Remove(1, 3); + Assert.IsTrue(middleLine.IsDeleted); + Assert.AreEqual(-1, document.Lines.IndexOf(middleLine)); + } + + [Test] + public void InsertInEmptyDocument() + { + document.Insert(0, "a"); + Assert.AreEqual(document.LineCount, 1); + DocumentLine line = document.GetLineByNumber(1); + Assert.AreEqual("a", document.GetText(line)); + } + + [Test] + public void SetText() + { + document.Text = "a"; + Assert.AreEqual(document.LineCount, 1); + DocumentLine line = document.GetLineByNumber(1); + Assert.AreEqual("a", document.GetText(line)); + } + + [Test] + public void InsertNothing() + { + document.Insert(0, ""); + Assert.AreEqual(document.LineCount, 1); + Assert.AreEqual(document.TextLength, 0); + } + + [Test] + public void InsertNull() + { + Assert.Throws(() => document.Insert(0, (string)null)); + } + + [Test] + public void SetTextNull() + { + Assert.Throws(() => document.Text = null); + } + + [Test] + public void RemoveNothing() + { + document.Remove(0, 0); + Assert.AreEqual(document.LineCount, 1); + Assert.AreEqual(document.TextLength, 0); + } + + [Test] + public void GetCharAt0EmptyDocument() + { + Assert.Throws(() => document.GetCharAt(0)); + } + + [Test] + public void GetCharAtNegativeOffset() + { + Assert.Throws(() => + { + document.Text = "a\nb"; + document.GetCharAt(-1); + }); + } + + [Test] + public void GetCharAtEndOffset() + { + Assert.Throws(() => + { + document.Text = "a\nb"; + document.GetCharAt(document.TextLength); + }); + } + + [Test] + public void InsertAtNegativeOffset() + { + Assert.Throws(() => + { + document.Text = "a\nb"; + document.Insert(-1, "text"); + }); + } + + [Test] + public void InsertAfterEndOffset() + { + Assert.Throws(() => + { + document.Text = "a\nb"; + document.Insert(4, "text"); + }); + } + + [Test] + public void RemoveNegativeAmount() + { + Assert.Throws(() => + { + document.Text = "abcd"; + document.Remove(2, -1); + }); + } + + [Test] + public void RemoveTooMuch() + { + Assert.Throws(() => + { + document.Text = "abcd"; + document.Remove(2, 10); + }); + } + + [Test] + public void GetLineByNumberNegative() + { + Assert.Throws(() => + { + document.Text = "a\nb"; + document.GetLineByNumber(-1); + }); + } + + [Test] + public void GetLineByNumberTooHigh() + { + Assert.Throws(() => + { + document.Text = "a\nb"; + document.GetLineByNumber(3); + }); + } + + [Test] + public void GetLineByOffsetNegative() + { + Assert.Throws(() => + { + document.Text = "a\nb"; + document.GetLineByOffset(-1); + }); + } + + + [Test] + public void GetLineByOffsetToHigh() + { + Assert.Throws(() => + { + document.Text = "a\nb"; + document.GetLineByOffset(10); + }); + } + + [Test] + public void InsertAtEndOffset() + { + document.Text = "a\nb"; + CheckDocumentLines("a", + "b"); + document.Insert(3, "text"); + CheckDocumentLines("a", + "btext"); + } + + [Test] + public void GetCharAt() + { + document.Text = "a\r\nb"; + Assert.AreEqual('a', document.GetCharAt(0)); + Assert.AreEqual('\r', document.GetCharAt(1)); + Assert.AreEqual('\n', document.GetCharAt(2)); + Assert.AreEqual('b', document.GetCharAt(3)); + } + + [Test] + public void CheckMixedNewLineTest() + { + const string mixedNewlineText = "line 1\nline 2\r\nline 3\rline 4"; + document.Text = mixedNewlineText; + Assert.AreEqual(mixedNewlineText, document.Text); + Assert.AreEqual(4, document.LineCount); + for (int i = 1; i < 4; i++) + { + DocumentLine line = document.GetLineByNumber(i); + Assert.AreEqual(i, line.LineNumber); + Assert.AreEqual("line " + i, document.GetText(line)); + } + Assert.AreEqual(1, document.GetLineByNumber(1).DelimiterLength); + Assert.AreEqual(2, document.GetLineByNumber(2).DelimiterLength); + Assert.AreEqual(1, document.GetLineByNumber(3).DelimiterLength); + Assert.AreEqual(0, document.GetLineByNumber(4).DelimiterLength); + } + + [Test] + public void LfCrIsTwoNewLinesTest() + { + document.Text = "a\n\rb"; + Assert.AreEqual("a\n\rb", document.Text); + CheckDocumentLines("a", + "", + "b"); + } + + [Test] + public void RemoveFirstPartOfDelimiter() + { + document.Text = "a\r\nb"; + document.Remove(1, 1); + Assert.AreEqual("a\nb", document.Text); + CheckDocumentLines("a", + "b"); + } + + [Test] + public void RemoveLineContentAndJoinDelimiters() + { + document.Text = "a\rb\nc"; + document.Remove(2, 1); + Assert.AreEqual("a\r\nc", document.Text); + CheckDocumentLines("a", + "c"); + } + + [Test] + public void RemoveLineContentAndJoinDelimiters2() + { + document.Text = "a\rb\nc\nd"; + document.Remove(2, 3); + Assert.AreEqual("a\r\nd", document.Text); + CheckDocumentLines("a", + "d"); + } + + [Test] + public void RemoveLineContentAndJoinDelimiters3() + { + document.Text = "a\rb\r\nc"; + document.Remove(2, 2); + Assert.AreEqual("a\r\nc", document.Text); + CheckDocumentLines("a", + "c"); + } + + [Test] + public void RemoveLineContentAndJoinNonMatchingDelimiters() + { + document.Text = "a\nb\nc"; + document.Remove(2, 1); + Assert.AreEqual("a\n\nc", document.Text); + CheckDocumentLines("a", + "", + "c"); + } + + [Test] + public void RemoveLineContentAndJoinNonMatchingDelimiters2() + { + document.Text = "a\nb\rc"; + document.Remove(2, 1); + Assert.AreEqual("a\n\rc", document.Text); + CheckDocumentLines("a", + "", + "c"); + } + + [Test] + public void RemoveMultilineUpToFirstPartOfDelimiter() + { + document.Text = "0\n1\r\n2"; + document.Remove(1, 3); + Assert.AreEqual("0\n2", document.Text); + CheckDocumentLines("0", + "2"); + } + + [Test] + public void RemoveSecondPartOfDelimiter() + { + document.Text = "a\r\nb"; + document.Remove(2, 1); + Assert.AreEqual("a\rb", document.Text); + CheckDocumentLines("a", + "b"); + } + + [Test] + public void RemoveFromSecondPartOfDelimiter() + { + document.Text = "a\r\nb\nc"; + document.Remove(2, 3); + Assert.AreEqual("a\rc", document.Text); + CheckDocumentLines("a", + "c"); + } + + [Test] + public void RemoveFromSecondPartOfDelimiterToDocumentEnd() + { + document.Text = "a\r\nb"; + document.Remove(2, 2); + Assert.AreEqual("a\r", document.Text); + CheckDocumentLines("a", + ""); + } + + [Test] + public void RemoveUpToMatchingDelimiter1() + { + document.Text = "a\r\nb\nc"; + document.Remove(2, 2); + Assert.AreEqual("a\r\nc", document.Text); + CheckDocumentLines("a", + "c"); + } + + [Test] + public void RemoveUpToMatchingDelimiter2() + { + document.Text = "a\r\nb\r\nc"; + document.Remove(2, 3); + Assert.AreEqual("a\r\nc", document.Text); + CheckDocumentLines("a", + "c"); + } + + [Test] + public void RemoveUpToNonMatchingDelimiter() + { + document.Text = "a\r\nb\rc"; + document.Remove(2, 2); + Assert.AreEqual("a\r\rc", document.Text); + CheckDocumentLines("a", + "", + "c"); + } + + [Test] + public void RemoveTwoCharDelimiter() + { + document.Text = "a\r\nb"; + document.Remove(1, 2); + Assert.AreEqual("ab", document.Text); + CheckDocumentLines("ab"); + } + + [Test] + public void RemoveOneCharDelimiter() + { + document.Text = "a\nb"; + document.Remove(1, 1); + Assert.AreEqual("ab", document.Text); + CheckDocumentLines("ab"); + } + + void CheckDocumentLines(params string[] lines) + { + Assert.AreEqual(lines.Length, document.LineCount, "LineCount"); + for (int i = 0; i < lines.Length; i++) + { + Assert.AreEqual(lines[i], document.GetText(document.Lines[i]), "Text of line " + (i + 1)); + } + } + + [Test] + public void FixUpFirstPartOfDelimiter() + { + document.Text = "a\n\nb"; + document.Replace(1, 1, "\r"); + Assert.AreEqual("a\r\nb", document.Text); + CheckDocumentLines("a", + "b"); + } + + [Test] + public void FixUpSecondPartOfDelimiter() + { + document.Text = "a\r\rb"; + document.Replace(2, 1, "\n"); + Assert.AreEqual("a\r\nb", document.Text); + CheckDocumentLines("a", + "b"); + } + + [Test] + public void InsertInsideDelimiter() + { + document.Text = "a\r\nc"; + document.Insert(2, "b"); + Assert.AreEqual("a\rb\nc", document.Text); + CheckDocumentLines("a", + "b", + "c"); + } + + [Test] + public void InsertInsideDelimiter2() + { + document.Text = "a\r\nd"; + document.Insert(2, "b\nc"); + Assert.AreEqual("a\rb\nc\nd", document.Text); + CheckDocumentLines("a", + "b", + "c", + "d"); + } + + [Test] + public void InsertInsideDelimiter3() + { + document.Text = "a\r\nc"; + document.Insert(2, "b\r"); + Assert.AreEqual("a\rb\r\nc", document.Text); + CheckDocumentLines("a", + "b", + "c"); + } + + [Test] + public void ExtendDelimiter1() + { + document.Text = "a\nc"; + document.Insert(1, "b\r"); + Assert.AreEqual("ab\r\nc", document.Text); + CheckDocumentLines("ab", + "c"); + } + + [Test] + public void ExtendDelimiter2() + { + document.Text = "a\rc"; + document.Insert(2, "\nb"); + Assert.AreEqual("a\r\nbc", document.Text); + CheckDocumentLines("a", + "bc"); + } + + [Test] + public void ReplaceLineContentBetweenMatchingDelimiters() + { + document.Text = "a\rb\nc"; + document.Replace(2, 1, "x"); + Assert.AreEqual("a\rx\nc", document.Text); + CheckDocumentLines("a", + "x", + "c"); + } + + [Test] + public void GetOffset() + { + document.Text = "Hello,\nWorld!"; + Assert.AreEqual(0, document.GetOffset(1, 1)); + Assert.AreEqual(1, document.GetOffset(1, 2)); + Assert.AreEqual(5, document.GetOffset(1, 6)); + Assert.AreEqual(6, document.GetOffset(1, 7)); + Assert.AreEqual(7, document.GetOffset(2, 1)); + Assert.AreEqual(8, document.GetOffset(2, 2)); + Assert.AreEqual(12, document.GetOffset(2, 6)); + Assert.AreEqual(13, document.GetOffset(2, 7)); + } + + [Test] + public void GetOffsetIgnoreNegativeColumns() + { + document.Text = "Hello,\nWorld!"; + Assert.AreEqual(0, document.GetOffset(1, -1)); + Assert.AreEqual(0, document.GetOffset(1, -100)); + Assert.AreEqual(0, document.GetOffset(1, 0)); + Assert.AreEqual(7, document.GetOffset(2, -1)); + Assert.AreEqual(7, document.GetOffset(2, -100)); + Assert.AreEqual(7, document.GetOffset(2, 0)); + } + + [Test] + public void GetOffsetIgnoreTooHighColumns() + { + document.Text = "Hello,\nWorld!"; + Assert.AreEqual(6, document.GetOffset(1, 8)); + Assert.AreEqual(6, document.GetOffset(1, 100)); + Assert.AreEqual(13, document.GetOffset(2, 8)); + Assert.AreEqual(13, document.GetOffset(2, 100)); + } + } +} diff --git a/test/AvaloniaEdit.Tests/Document/RandomizedLineManagerTest.cs b/test/AvaloniaEdit.Tests/Document/RandomizedLineManagerTest.cs new file mode 100644 index 0000000..3fddc24 --- /dev/null +++ b/test/AvaloniaEdit.Tests/Document/RandomizedLineManagerTest.cs @@ -0,0 +1,183 @@ +// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using AvaloniaEdit.Rendering; +using NUnit.Framework; + +namespace AvaloniaEdit.Document +{ + /// + /// A randomized test for the line manager. + /// + [TestFixture] + public class RandomizedLineManagerTest + { + TextDocument document; + Random rnd; + + [OneTimeSetUp] + public void FixtureSetup() + { + int seed = Environment.TickCount; + Console.WriteLine("RandomizedLineManagerTest Seed: " + seed); + rnd = new Random(seed); + } + + [SetUp] + public void Setup() + { + document = new TextDocument(); + } + + [Test] + public void ShortReplacements() + { + char[] chars = { 'a', 'b', '\r', '\n' }; + char[] buffer = new char[20]; + for (int i = 0; i < 2500; i++) { + int offset = rnd.Next(0, document.TextLength); + int length = rnd.Next(0, document.TextLength - offset); + int newTextLength = rnd.Next(0, 20); + for (int j = 0; j < newTextLength; j++) { + buffer[j] = chars[rnd.Next(0, chars.Length)]; + } + + document.Replace(offset, length, new string(buffer, 0, newTextLength)); + CheckLines(); + } + } + + [Test] + public void LargeReplacements() + { + char[] chars = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', '\r', '\n' }; + char[] buffer = new char[1000]; + for (int i = 0; i < 20; i++) { + int offset = rnd.Next(0, document.TextLength); + int length = rnd.Next(0, (document.TextLength - offset) / 4); + int newTextLength = rnd.Next(0, 1000); + for (int j = 0; j < newTextLength; j++) { + buffer[j] = chars[rnd.Next(0, chars.Length)]; + } + + string newText = new string(buffer, 0, newTextLength); + string expectedText = document.Text.Remove(offset, length).Insert(offset, newText); + document.Replace(offset, length, newText); + Assert.AreEqual(expectedText, document.Text); + CheckLines(); + } + } + + void CheckLines() + { + string text = document.Text; + int lineNumber = 1; + int lineStart = 0; + for (int i = 0; i < text.Length; i++) { + char c = text[i]; + if (c == '\r' && i + 1 < text.Length && text[i + 1] == '\n') { + DocumentLine line = document.GetLineByNumber(lineNumber); + Assert.AreEqual(lineNumber, line.LineNumber); + Assert.AreEqual(2, line.DelimiterLength); + Assert.AreEqual(lineStart, line.Offset); + Assert.AreEqual(i - lineStart, line.Length); + i++; // consume \n + lineNumber++; + lineStart = i+1; + } else if (c == '\r' || c == '\n') { + DocumentLine line = document.GetLineByNumber(lineNumber); + Assert.AreEqual(lineNumber, line.LineNumber); + Assert.AreEqual(1, line.DelimiterLength); + Assert.AreEqual(lineStart, line.Offset); + Assert.AreEqual(i - lineStart, line.Length); + lineNumber++; + lineStart = i+1; + } + } + Assert.AreEqual(lineNumber, document.LineCount); + } + + [Test] + public void CollapsingTest() + { + char[] chars = { 'a', 'b', '\r', '\n' }; + char[] buffer = new char[20]; + HeightTree heightTree = new HeightTree(document, 10); + List collapsedSections = new List(); + for (int i = 0; i < 2500; i++) { +// Console.WriteLine("Iteration " + i); +// Console.WriteLine(heightTree.GetTreeAsString()); +// foreach (CollapsedLineSection cs in collapsedSections) { +// Console.WriteLine(cs); +// } + + switch (rnd.Next(0, 10)) { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + int offset = rnd.Next(0, document.TextLength); + int length = rnd.Next(0, document.TextLength - offset); + int newTextLength = rnd.Next(0, 20); + for (int j = 0; j < newTextLength; j++) { + buffer[j] = chars[rnd.Next(0, chars.Length)]; + } + + document.Replace(offset, length, new string(buffer, 0, newTextLength)); + break; + case 6: + case 7: + int startLine = rnd.Next(1, document.LineCount + 1); + int endLine = rnd.Next(startLine, document.LineCount + 1); + collapsedSections.Add(heightTree.CollapseText(document.GetLineByNumber(startLine), document.GetLineByNumber(endLine))); + break; + case 8: + if (collapsedSections.Count > 0) { + CollapsedLineSection cs = collapsedSections[rnd.Next(0, collapsedSections.Count)]; + // unless the text section containing the CollapsedSection was deleted: + if (cs.Start != null) { + cs.Uncollapse(); + } + collapsedSections.Remove(cs); + } + break; + case 9: + foreach (DocumentLine ls in document.Lines) { + heightTree.SetHeight(ls, ls.LineNumber); + } + break; + } + var treeSections = new HashSet(heightTree.GetAllCollapsedSections()); + int expectedCount = 0; + foreach (CollapsedLineSection cs in collapsedSections) { + if (cs.Start != null) { + expectedCount++; + Assert.IsTrue(treeSections.Contains(cs)); + } + } + Assert.AreEqual(expectedCount, treeSections.Count); + CheckLines(); + HeightTests.CheckHeights(document, heightTree); + } + } + } +} diff --git a/test/AvaloniaEdit.Tests/Document/TextAnchorTest.cs b/test/AvaloniaEdit.Tests/Document/TextAnchorTest.cs new file mode 100644 index 0000000..096892f --- /dev/null +++ b/test/AvaloniaEdit.Tests/Document/TextAnchorTest.cs @@ -0,0 +1,328 @@ +// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using NUnit.Framework; + +namespace AvaloniaEdit.Document +{ + [TestFixture] + public class TextAnchorTest + { + TextDocument document; + + [SetUp] + public void SetUp() + { + document = new TextDocument(); + } + + [Test] + public void AnchorInEmptyDocument() + { + TextAnchor a1 = document.CreateAnchor(0); + TextAnchor a2 = document.CreateAnchor(0); + a1.MovementType = AnchorMovementType.BeforeInsertion; + a2.MovementType = AnchorMovementType.AfterInsertion; + Assert.AreEqual(0, a1.Offset); + Assert.AreEqual(0, a2.Offset); + document.Insert(0, "x"); + Assert.AreEqual(0, a1.Offset); + Assert.AreEqual(1, a2.Offset); + } + + [Test] + public void AnchorsSurviveDeletion() + { + document.Text = new string(' ', 10); + TextAnchor[] a1 = new TextAnchor[11]; + TextAnchor[] a2 = new TextAnchor[11]; + for (int i = 0; i < 11; i++) { + //Console.WriteLine("Insert first at i = " + i); + a1[i] = document.CreateAnchor(i); + a1[i].SurviveDeletion = true; + //Console.WriteLine(document.GetTextAnchorTreeAsString()); + //Console.WriteLine("Insert second at i = " + i); + a2[i] = document.CreateAnchor(i); + a2[i].SurviveDeletion = false; + //Console.WriteLine(document.GetTextAnchorTreeAsString()); + } + for (int i = 0; i < 11; i++) { + Assert.AreEqual(i, a1[i].Offset); + Assert.AreEqual(i, a2[i].Offset); + } + document.Remove(1, 8); + for (int i = 0; i < 11; i++) { + if (i <= 1) { + Assert.IsFalse(a1[i].IsDeleted); + Assert.IsFalse(a2[i].IsDeleted); + Assert.AreEqual(i, a1[i].Offset); + Assert.AreEqual(i, a2[i].Offset); + } else if (i <= 8) { + Assert.IsFalse(a1[i].IsDeleted); + Assert.IsTrue(a2[i].IsDeleted); + Assert.AreEqual(1, a1[i].Offset); + } else { + Assert.IsFalse(a1[i].IsDeleted); + Assert.IsFalse(a2[i].IsDeleted); + Assert.AreEqual(i - 8, a1[i].Offset); + Assert.AreEqual(i - 8, a2[i].Offset); + } + } + } + + + Random rnd; + + [OneTimeSetUp] + public void FixtureSetup() + { + int seed = Environment.TickCount; + Console.WriteLine("TextAnchorTest Seed: " + seed); + rnd = new Random(seed); + } + + [Test] + public void CreateAnchors() + { + List anchors = new List(); + List expectedOffsets = new List(); + document.Text = new string(' ', 1000); + for (int i = 0; i < 1000; i++) { + int offset = rnd.Next(1000); + anchors.Add(document.CreateAnchor(offset)); + expectedOffsets.Add(offset); + } + for (int i = 0; i < anchors.Count; i++) { + Assert.AreEqual(expectedOffsets[i], anchors[i].Offset); + } + GC.KeepAlive(anchors); + } + + [Test] + public void CreateAndGCAnchors() + { + List anchors = new List(); + List expectedOffsets = new List(); + document.Text = new string(' ', 1000); + for (int t = 0; t < 250; t++) { + int c = rnd.Next(50); + if (rnd.Next(2) == 0) { + for (int i = 0; i < c; i++) { + int offset = rnd.Next(1000); + anchors.Add(document.CreateAnchor(offset)); + expectedOffsets.Add(offset); + } + } else if (c <= anchors.Count) { + anchors.RemoveRange(0, c); + expectedOffsets.RemoveRange(0, c); + GC.Collect(); + } + for (int j = 0; j < anchors.Count; j++) { + Assert.AreEqual(expectedOffsets[j], anchors[j].Offset); + } + } + GC.KeepAlive(anchors); + } + + [Test] + public void MoveAnchorsDuringReplace() + { + document.Text = "abcd"; + TextAnchor start = document.CreateAnchor(1); + TextAnchor middleDeletable = document.CreateAnchor(2); + TextAnchor middleSurvivorLeft = document.CreateAnchor(2); + middleSurvivorLeft.SurviveDeletion = true; + middleSurvivorLeft.MovementType = AnchorMovementType.BeforeInsertion; + TextAnchor middleSurvivorRight = document.CreateAnchor(2); + middleSurvivorRight.SurviveDeletion = true; + middleSurvivorRight.MovementType = AnchorMovementType.AfterInsertion; + TextAnchor end = document.CreateAnchor(3); + document.Replace(1, 2, "BxC"); + + Assert.AreEqual(1, start.Offset); + Assert.IsTrue(middleDeletable.IsDeleted); + Assert.AreEqual(1, middleSurvivorLeft.Offset); + Assert.AreEqual(4, middleSurvivorRight.Offset); + Assert.AreEqual(4, end.Offset); + } + + [Test] + public void CreateAndMoveAnchors() + { + List anchors = new List(); + List expectedOffsets = new List(); + document.Text = new string(' ', 1000); + for (int t = 0; t < 250; t++) { + //Console.Write("t = " + t + " "); + int c = rnd.Next(50); + switch (rnd.Next(5)) { + case 0: + //Console.WriteLine("Add c=" + c + " anchors"); + for (int i = 0; i < c; i++) { + int offset = rnd.Next(document.TextLength); + TextAnchor anchor = document.CreateAnchor(offset); + if (rnd.Next(2) == 0) + anchor.MovementType = AnchorMovementType.BeforeInsertion; + else + anchor.MovementType = AnchorMovementType.AfterInsertion; + anchor.SurviveDeletion = rnd.Next(2) == 0; + anchors.Add(anchor); + expectedOffsets.Add(offset); + } + break; + case 1: + if (c <= anchors.Count) { + //Console.WriteLine("Remove c=" + c + " anchors"); + anchors.RemoveRange(0, c); + expectedOffsets.RemoveRange(0, c); + GC.Collect(); + } + break; + case 2: + int insertOffset = rnd.Next(document.TextLength); + int insertLength = rnd.Next(1000); + //Console.WriteLine("insertOffset=" + insertOffset + " insertLength="+insertLength); + document.Insert(insertOffset, new string(' ', insertLength)); + for (int i = 0; i < anchors.Count; i++) { + if (anchors[i].MovementType == AnchorMovementType.BeforeInsertion) { + if (expectedOffsets[i] > insertOffset) + expectedOffsets[i] += insertLength; + } else { + if (expectedOffsets[i] >= insertOffset) + expectedOffsets[i] += insertLength; + } + } + break; + case 3: + int removalOffset = rnd.Next(document.TextLength); + int removalLength = rnd.Next(document.TextLength - removalOffset); + //Console.WriteLine("RemovalOffset=" + removalOffset + " RemovalLength="+removalLength); + document.Remove(removalOffset, removalLength); + for (int i = anchors.Count - 1; i >= 0; i--) { + if (expectedOffsets[i] > removalOffset && expectedOffsets[i] < removalOffset + removalLength) { + if (anchors[i].SurviveDeletion) { + expectedOffsets[i] = removalOffset; + } else { + Assert.IsTrue(anchors[i].IsDeleted); + anchors.RemoveAt(i); + expectedOffsets.RemoveAt(i); + } + } else if (expectedOffsets[i] > removalOffset) { + expectedOffsets[i] -= removalLength; + } + } + break; + case 4: + int replaceOffset = rnd.Next(document.TextLength); + int replaceRemovalLength = rnd.Next(document.TextLength - replaceOffset); + int replaceInsertLength = rnd.Next(1000); + //Console.WriteLine("ReplaceOffset=" + replaceOffset + " RemovalLength="+replaceRemovalLength + " InsertLength=" + replaceInsertLength); + document.Replace(replaceOffset, replaceRemovalLength, new string(' ', replaceInsertLength)); + for (int i = anchors.Count - 1; i >= 0; i--) { + if (expectedOffsets[i] > replaceOffset && expectedOffsets[i] < replaceOffset + replaceRemovalLength) { + if (anchors[i].SurviveDeletion) { + if (anchors[i].MovementType == AnchorMovementType.AfterInsertion) + expectedOffsets[i] = replaceOffset + replaceInsertLength; + else + expectedOffsets[i] = replaceOffset; + } else { + Assert.IsTrue(anchors[i].IsDeleted); + anchors.RemoveAt(i); + expectedOffsets.RemoveAt(i); + } + } else if (expectedOffsets[i] > replaceOffset) { + expectedOffsets[i] += replaceInsertLength - replaceRemovalLength; + } else if (expectedOffsets[i] == replaceOffset && replaceRemovalLength == 0 && anchors[i].MovementType == AnchorMovementType.AfterInsertion) { + expectedOffsets[i] += replaceInsertLength - replaceRemovalLength; + } + } + break; + } + Assert.AreEqual(anchors.Count, expectedOffsets.Count); + for (int j = 0; j < anchors.Count; j++) { + Assert.AreEqual(expectedOffsets[j], anchors[j].Offset); + } + } + GC.KeepAlive(anchors); + } + + [Test] + public void RepeatedTextDragDrop() + { + document.Text = new string(' ', 1000); + for (int i = 0; i < 20; i++) { + TextAnchor a = document.CreateAnchor(144); + TextAnchor b = document.CreateAnchor(157); + document.Insert(128, new string('a', 13)); + document.Remove(157, 13); + a = document.CreateAnchor(128); + b = document.CreateAnchor(141); + + document.Insert(157, new string('b', 13)); + document.Remove(128, 13); + + a = null; + b = null; + if ((i % 5) == 0) + GC.Collect(); + } + } + + [Test] + public void ReplaceSpacesWithTab() + { + document.Text = "a b"; + TextAnchor before = document.CreateAnchor(1); + before.MovementType = AnchorMovementType.AfterInsertion; + TextAnchor after = document.CreateAnchor(5); + TextAnchor survivingMiddle = document.CreateAnchor(2); + TextAnchor deletedMiddle = document.CreateAnchor(3); + + document.Replace(1, 4, "\t", OffsetChangeMappingType.CharacterReplace); + Assert.AreEqual("a\tb", document.Text); + // yes, the movement is a bit strange; but that's how CharacterReplace works when the text gets shorter + Assert.AreEqual(1, before.Offset); + Assert.AreEqual(2, after.Offset); + Assert.AreEqual(2, survivingMiddle.Offset); + Assert.AreEqual(2, deletedMiddle.Offset); + } + + [Test] + public void ReplaceTwoCharactersWithThree() + { + document.Text = "a12b"; + TextAnchor before = document.CreateAnchor(1); + before.MovementType = AnchorMovementType.AfterInsertion; + TextAnchor after = document.CreateAnchor(3); + before.MovementType = AnchorMovementType.BeforeInsertion; + TextAnchor middleB = document.CreateAnchor(2); + before.MovementType = AnchorMovementType.BeforeInsertion; + TextAnchor middleA = document.CreateAnchor(2); + before.MovementType = AnchorMovementType.AfterInsertion; + + document.Replace(1, 2, "123", OffsetChangeMappingType.CharacterReplace); + Assert.AreEqual("a123b", document.Text); + Assert.AreEqual(1, before.Offset); + Assert.AreEqual(4, after.Offset); + Assert.AreEqual(2, middleA.Offset); + Assert.AreEqual(2, middleB.Offset); + } + } +} diff --git a/test/AvaloniaEdit.Tests/Document/TextSegmentTreeTest.cs b/test/AvaloniaEdit.Tests/Document/TextSegmentTreeTest.cs new file mode 100644 index 0000000..5fe5792 --- /dev/null +++ b/test/AvaloniaEdit.Tests/Document/TextSegmentTreeTest.cs @@ -0,0 +1,365 @@ +// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using NUnit.Framework; + +namespace AvaloniaEdit.Document +{ + [TestFixture] + public class TextSegmentTreeTest + { + Random rnd; + + [OneTimeSetUp] + public void FixtureSetup() + { + int seed = Environment.TickCount; + Console.WriteLine("TextSegmentTreeTest Seed: " + seed); + rnd = new Random(seed); + } + + class TestTextSegment : TextSegment + { + internal int ExpectedOffset, ExpectedLength; + + public TestTextSegment(int expectedOffset, int expectedLength) + { + this.ExpectedOffset = expectedOffset; + this.ExpectedLength = expectedLength; + this.StartOffset = expectedOffset; + this.Length = expectedLength; + } + } + + TextSegmentCollection tree; + List expectedSegments; + + [SetUp] + public void SetUp() + { + tree = new TextSegmentCollection(); + expectedSegments = new List(); + } + + [Test] + public void FindInEmptyTree() + { + Assert.AreSame(null, tree.FindFirstSegmentWithStartAfter(0)); + Assert.AreEqual(0, tree.FindSegmentsContaining(0).Count); + Assert.AreEqual(0, tree.FindOverlappingSegments(10, 20).Count); + } + + [Test] + public void FindFirstSegmentWithStartAfter() + { + var s1 = new TestTextSegment(5, 10); + var s2 = new TestTextSegment(10, 10); + tree.Add(s1); + tree.Add(s2); + Assert.AreSame(s1, tree.FindFirstSegmentWithStartAfter(-100)); + Assert.AreSame(s1, tree.FindFirstSegmentWithStartAfter(0)); + Assert.AreSame(s1, tree.FindFirstSegmentWithStartAfter(4)); + Assert.AreSame(s1, tree.FindFirstSegmentWithStartAfter(5)); + Assert.AreSame(s2, tree.FindFirstSegmentWithStartAfter(6)); + Assert.AreSame(s2, tree.FindFirstSegmentWithStartAfter(9)); + Assert.AreSame(s2, tree.FindFirstSegmentWithStartAfter(10)); + Assert.AreSame(null, tree.FindFirstSegmentWithStartAfter(11)); + Assert.AreSame(null, tree.FindFirstSegmentWithStartAfter(100)); + } + + [Test] + public void FindFirstSegmentWithStartAfterWithDuplicates() + { + var s1 = new TestTextSegment(5, 10); + var s1b = new TestTextSegment(5, 7); + var s2 = new TestTextSegment(10, 10); + var s2b = new TestTextSegment(10, 7); + tree.Add(s1); + tree.Add(s1b); + tree.Add(s2); + tree.Add(s2b); + Assert.AreSame(s1b, tree.GetNextSegment(s1)); + Assert.AreSame(s2b, tree.GetNextSegment(s2)); + Assert.AreSame(s1, tree.FindFirstSegmentWithStartAfter(-100)); + Assert.AreSame(s1, tree.FindFirstSegmentWithStartAfter(0)); + Assert.AreSame(s1, tree.FindFirstSegmentWithStartAfter(4)); + Assert.AreSame(s1, tree.FindFirstSegmentWithStartAfter(5)); + Assert.AreSame(s2, tree.FindFirstSegmentWithStartAfter(6)); + Assert.AreSame(s2, tree.FindFirstSegmentWithStartAfter(9)); + Assert.AreSame(s2, tree.FindFirstSegmentWithStartAfter(10)); + Assert.AreSame(null, tree.FindFirstSegmentWithStartAfter(11)); + Assert.AreSame(null, tree.FindFirstSegmentWithStartAfter(100)); + } + + [Test] + public void FindFirstSegmentWithStartAfterWithDuplicates2() + { + var s1 = new TestTextSegment(5, 1); + var s2 = new TestTextSegment(5, 2); + var s3 = new TestTextSegment(5, 3); + var s4 = new TestTextSegment(5, 4); + tree.Add(s1); + tree.Add(s2); + tree.Add(s3); + tree.Add(s4); + Assert.AreSame(s1, tree.FindFirstSegmentWithStartAfter(0)); + Assert.AreSame(s1, tree.FindFirstSegmentWithStartAfter(1)); + Assert.AreSame(s1, tree.FindFirstSegmentWithStartAfter(4)); + Assert.AreSame(s1, tree.FindFirstSegmentWithStartAfter(5)); + Assert.AreSame(null, tree.FindFirstSegmentWithStartAfter(6)); + } + + TestTextSegment AddSegment(int offset, int length) + { +// Console.WriteLine("Add " + offset + ", " + length); + TestTextSegment s = new TestTextSegment(offset, length); + tree.Add(s); + expectedSegments.Add(s); + return s; + } + + void RemoveSegment(TestTextSegment s) + { +// Console.WriteLine("Remove " + s); + expectedSegments.Remove(s); + tree.Remove(s); + } + + void TestRetrieval(int offset, int length) + { + HashSet actual = new HashSet(tree.FindOverlappingSegments(offset, length)); + HashSet expected = new HashSet(); + foreach (TestTextSegment e in expectedSegments) { + if (e.ExpectedOffset + e.ExpectedLength < offset) + continue; + if (e.ExpectedOffset > offset + length) + continue; + expected.Add(e); + } + Assert.IsTrue(actual.IsSubsetOf(expected)); + Assert.IsTrue(expected.IsSubsetOf(actual)); + } + + void CheckSegments() + { + Assert.AreEqual(expectedSegments.Count, tree.Count); + foreach (TestTextSegment s in expectedSegments) { + Assert.AreEqual(s.ExpectedOffset, s.StartOffset /*, "startoffset for " + s*/); + Assert.AreEqual(s.ExpectedLength, s.Length /*, "length for " + s*/); + } + } + + [Test] + public void AddSegments() + { + TestTextSegment s1 = AddSegment(10, 20); + TestTextSegment s2 = AddSegment(15, 10); + CheckSegments(); + } + + void ChangeDocument(OffsetChangeMapEntry change) + { + tree.UpdateOffsets(change); + foreach (TestTextSegment s in expectedSegments) { + int endOffset = s.ExpectedOffset + s.ExpectedLength; + s.ExpectedOffset = change.GetNewOffset(s.ExpectedOffset, AnchorMovementType.AfterInsertion); + s.ExpectedLength = Math.Max(0, change.GetNewOffset(endOffset, AnchorMovementType.BeforeInsertion) - s.ExpectedOffset); + } + } + + [Test] + public void InsertionBeforeAllSegments() + { + TestTextSegment s1 = AddSegment(10, 20); + TestTextSegment s2 = AddSegment(15, 10); + ChangeDocument(new OffsetChangeMapEntry(5, 0, 2)); + CheckSegments(); + } + + [Test] + public void ReplacementBeforeAllSegmentsTouchingFirstSegment() + { + TestTextSegment s1 = AddSegment(10, 20); + TestTextSegment s2 = AddSegment(15, 10); + ChangeDocument(new OffsetChangeMapEntry(5, 5, 2)); + CheckSegments(); + } + + [Test] + public void InsertionAfterAllSegments() + { + TestTextSegment s1 = AddSegment(10, 20); + TestTextSegment s2 = AddSegment(15, 10); + ChangeDocument(new OffsetChangeMapEntry(45, 0, 2)); + CheckSegments(); + } + + [Test] + public void ReplacementOverlappingWithStartOfSegment() + { + TestTextSegment s1 = AddSegment(10, 20); + TestTextSegment s2 = AddSegment(15, 10); + ChangeDocument(new OffsetChangeMapEntry(9, 7, 2)); + CheckSegments(); + } + + [Test] + public void ReplacementOfWholeSegment() + { + TestTextSegment s1 = AddSegment(10, 20); + TestTextSegment s2 = AddSegment(15, 10); + ChangeDocument(new OffsetChangeMapEntry(10, 20, 30)); + CheckSegments(); + } + + [Test] + public void ReplacementAtEndOfSegment() + { + TestTextSegment s1 = AddSegment(10, 20); + TestTextSegment s2 = AddSegment(15, 10); + ChangeDocument(new OffsetChangeMapEntry(24, 6, 10)); + CheckSegments(); + } + + [Test] + public void RandomizedNoDocumentChanges() + { + for (int i = 0; i < 1000; i++) { +// Console.WriteLine(tree.GetTreeAsString()); +// Console.WriteLine("Iteration " + i); + + switch (rnd.Next(3)) { + case 0: + AddSegment(rnd.Next(500), rnd.Next(30)); + break; + case 1: + AddSegment(rnd.Next(500), rnd.Next(300)); + break; + case 2: + if (tree.Count > 0) { + RemoveSegment(expectedSegments[rnd.Next(tree.Count)]); + } + break; + } + CheckSegments(); + } + } + + [Test] + public void RandomizedCloseNoDocumentChanges() + { + // Lots of segments in a short document. Tests how the tree copes with multiple identical segments. + for (int i = 0; i < 1000; i++) { + switch (rnd.Next(3)) { + case 0: + AddSegment(rnd.Next(20), rnd.Next(10)); + break; + case 1: + AddSegment(rnd.Next(20), rnd.Next(20)); + break; + case 2: + if (tree.Count > 0) { + RemoveSegment(expectedSegments[rnd.Next(tree.Count)]); + } + break; + } + CheckSegments(); + } + } + + [Test] + public void RandomizedRetrievalTest() + { + for (int i = 0; i < 1000; i++) { + AddSegment(rnd.Next(500), rnd.Next(300)); + } + CheckSegments(); + for (int i = 0; i < 1000; i++) { + TestRetrieval(rnd.Next(1000) - 100, rnd.Next(500)); + } + } + + [Test] + public void RandomizedWithDocumentChanges() + { + for (int i = 0; i < 500; i++) { +// Console.WriteLine(tree.GetTreeAsString()); +// Console.WriteLine("Iteration " + i); + + switch (rnd.Next(6)) { + case 0: + AddSegment(rnd.Next(500), rnd.Next(30)); + break; + case 1: + AddSegment(rnd.Next(500), rnd.Next(300)); + break; + case 2: + if (tree.Count > 0) { + RemoveSegment(expectedSegments[rnd.Next(tree.Count)]); + } + break; + case 3: + ChangeDocument(new OffsetChangeMapEntry(rnd.Next(800), rnd.Next(50), rnd.Next(50))); + break; + case 4: + ChangeDocument(new OffsetChangeMapEntry(rnd.Next(800), 0, rnd.Next(50))); + break; + case 5: + ChangeDocument(new OffsetChangeMapEntry(rnd.Next(800), rnd.Next(50), 0)); + break; + } + CheckSegments(); + } + } + + [Test] + public void RandomizedWithDocumentChangesClose() + { + for (int i = 0; i < 500; i++) { +// Console.WriteLine(tree.GetTreeAsString()); +// Console.WriteLine("Iteration " + i); + + switch (rnd.Next(6)) { + case 0: + AddSegment(rnd.Next(50), rnd.Next(30)); + break; + case 1: + AddSegment(rnd.Next(50), rnd.Next(3)); + break; + case 2: + if (tree.Count > 0) { + RemoveSegment(expectedSegments[rnd.Next(tree.Count)]); + } + break; + case 3: + ChangeDocument(new OffsetChangeMapEntry(rnd.Next(80), rnd.Next(10), rnd.Next(10))); + break; + case 4: + ChangeDocument(new OffsetChangeMapEntry(rnd.Next(80), 0, rnd.Next(10))); + break; + case 5: + ChangeDocument(new OffsetChangeMapEntry(rnd.Next(80), rnd.Next(10), 0)); + break; + } + CheckSegments(); + } + } + } +} diff --git a/test/AvaloniaEdit.Tests/Document/TextUtilitiesTests.cs b/test/AvaloniaEdit.Tests/Document/TextUtilitiesTests.cs new file mode 100644 index 0000000..4de591c --- /dev/null +++ b/test/AvaloniaEdit.Tests/Document/TextUtilitiesTests.cs @@ -0,0 +1,90 @@ +// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using NUnit.Framework; + +namespace AvaloniaEdit.Document +{ + [TestFixture] + public class TextUtilitiesTests + { + #region GetWhitespaceAfter + [Test] + public void TestGetWhitespaceAfter() + { + Assert.AreEqual(new SimpleSegment(2, 3), TextUtilities.GetWhitespaceAfter(new StringTextSource("a \t \tb"), 2)); + } + + [Test] + public void TestGetWhitespaceAfterDoesNotSkipNewLine() + { + Assert.AreEqual(new SimpleSegment(2, 3), TextUtilities.GetWhitespaceAfter(new StringTextSource("a \t \tb"), 2)); + } + + [Test] + public void TestGetWhitespaceAfterEmptyResult() + { + Assert.AreEqual(new SimpleSegment(2, 0), TextUtilities.GetWhitespaceAfter(new StringTextSource("a b"), 2)); + } + + [Test] + public void TestGetWhitespaceAfterEndOfString() + { + Assert.AreEqual(new SimpleSegment(2, 0), TextUtilities.GetWhitespaceAfter(new StringTextSource("a "), 2)); + } + + [Test] + public void TestGetWhitespaceAfterUntilEndOfString() + { + Assert.AreEqual(new SimpleSegment(2, 3), TextUtilities.GetWhitespaceAfter(new StringTextSource("a \t \t"), 2)); + } + #endregion + + #region GetWhitespaceBefore + [Test] + public void TestGetWhitespaceBefore() + { + Assert.AreEqual(new SimpleSegment(1, 3), TextUtilities.GetWhitespaceBefore(new StringTextSource("a\t \t b"), 4)); + } + + [Test] + public void TestGetWhitespaceBeforeDoesNotSkipNewLine() + { + Assert.AreEqual(new SimpleSegment(2, 1), TextUtilities.GetWhitespaceBefore(new StringTextSource("a\n b"), 3)); + } + + [Test] + public void TestGetWhitespaceBeforeEmptyResult() + { + Assert.AreEqual(new SimpleSegment(2, 0), TextUtilities.GetWhitespaceBefore(new StringTextSource(" a b"), 2)); + } + + [Test] + public void TestGetWhitespaceBeforeStartOfString() + { + Assert.AreEqual(new SimpleSegment(0, 0), TextUtilities.GetWhitespaceBefore(new StringTextSource(" a"), 0)); + } + + [Test] + public void TestGetWhitespaceBeforeUntilStartOfString() + { + Assert.AreEqual(new SimpleSegment(0, 2), TextUtilities.GetWhitespaceBefore(new StringTextSource(" \t a"), 2)); + } + #endregion + } +} diff --git a/test/AvaloniaEdit.Tests/Document/UndoStackTests.cs b/test/AvaloniaEdit.Tests/Document/UndoStackTests.cs new file mode 100644 index 0000000..581d00a --- /dev/null +++ b/test/AvaloniaEdit.Tests/Document/UndoStackTests.cs @@ -0,0 +1,92 @@ +// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using NUnit.Framework; + +namespace AvaloniaEdit.Document +{ + public class UndoStackTests + { + [Test] + public void ContinueUndoGroup() + { + var doc = new TextDocument(); + doc.Insert(0, "a"); + doc.UndoStack.StartContinuedUndoGroup(); + doc.Insert(1, "b"); + doc.UndoStack.EndUndoGroup(); + doc.UndoStack.Undo(); + Assert.AreEqual("", doc.Text); + } + + [Test] + public void ContinueEmptyUndoGroup() + { + var doc = new TextDocument(); + doc.Insert(0, "a"); + doc.UndoStack.StartUndoGroup(); + doc.UndoStack.EndUndoGroup(); + doc.UndoStack.StartContinuedUndoGroup(); + doc.Insert(1, "b"); + doc.UndoStack.EndUndoGroup(); + doc.UndoStack.Undo(); + Assert.AreEqual("a", doc.Text); + } + + [Test] + public void ContinueEmptyUndoGroup_WithOptionalEntries() + { + var doc = new TextDocument(); + doc.Insert(0, "a"); + doc.UndoStack.StartUndoGroup(); + doc.UndoStack.PushOptional(new StubUndoableAction()); + doc.UndoStack.EndUndoGroup(); + doc.UndoStack.StartContinuedUndoGroup(); + doc.Insert(1, "b"); + doc.UndoStack.EndUndoGroup(); + doc.UndoStack.Undo(); + Assert.AreEqual("a", doc.Text); + } + + [Test] + public void EmptyContinuationGroup() + { + var doc = new TextDocument(); + doc.Insert(0, "a"); + doc.UndoStack.StartContinuedUndoGroup(); + doc.UndoStack.EndUndoGroup(); + doc.UndoStack.StartContinuedUndoGroup(); + doc.Insert(1, "b"); + doc.UndoStack.EndUndoGroup(); + doc.UndoStack.Undo(); + Assert.AreEqual("", doc.Text); + } + + class StubUndoableAction : IUndoableOperation + { + public void Undo() + { + } + + public void Redo() + { + } + } + } +} diff --git a/test/AvaloniaEdit.Tests/Editing/ChangeDocumentTests.cs b/test/AvaloniaEdit.Tests/Editing/ChangeDocumentTests.cs new file mode 100644 index 0000000..e277d47 --- /dev/null +++ b/test/AvaloniaEdit.Tests/Editing/ChangeDocumentTests.cs @@ -0,0 +1,86 @@ +// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System.Text; +using AvaloniaEdit.AvaloniaMocks; +using AvaloniaEdit.Document; +using NUnit.Framework; + +namespace AvaloniaEdit.Editing +{ + [TestFixture] + public class ChangeDocumentTests + { + [Test] + public void ClearCaretAndSelectionOnDocumentChange() + { + using (UnitTestApplication.Start(new TestServices(renderInterface: new MockPlatformRenderInterface()))) + { + TextArea textArea = new TextArea(); + textArea.Document = new TextDocument("1\n2\n3\n4th line"); + textArea.Caret.Offset = 6; + textArea.Selection = Selection.Create(textArea, 3, 6); + textArea.Document = new TextDocument("1\n2nd"); + Assert.AreEqual(0, textArea.Caret.Offset); + Assert.AreEqual(new TextLocation(1, 1), textArea.Caret.Location); + Assert.IsTrue(textArea.Selection.IsEmpty); + } + } + + [Test] + public void SetDocumentToNull() + { + using (UnitTestApplication.Start(new TestServices(renderInterface: new MockPlatformRenderInterface()))) + { + TextArea textArea = new TextArea(); + textArea.Document = new TextDocument("1\n2\n3\n4th line"); + textArea.Caret.Offset = 6; + textArea.Selection = Selection.Create(textArea, 3, 6); + textArea.Document = null; + Assert.AreEqual(0, textArea.Caret.Offset); + Assert.AreEqual(new TextLocation(1, 1), textArea.Caret.Location); + Assert.IsTrue(textArea.Selection.IsEmpty); + } + } + + [Test] + public void CheckEventOrderOnDocumentChange() + { + using (UnitTestApplication.Start(new TestServices(renderInterface: new MockPlatformRenderInterface()))) + { + TextArea textArea = new TextArea(); + TextDocument newDocument = new TextDocument(); + StringBuilder b = new StringBuilder(); + textArea.TextView.DocumentChanged += delegate + { + b.Append("TextView.DocumentChanged;"); + Assert.AreSame(newDocument, textArea.TextView.Document); + Assert.AreSame(newDocument, textArea.Document); + }; + textArea.DocumentChanged += delegate + { + b.Append("TextArea.DocumentChanged;"); + Assert.AreSame(newDocument, textArea.TextView.Document); + Assert.AreSame(newDocument, textArea.Document); + }; + textArea.Document = newDocument; + Assert.AreEqual("TextView.DocumentChanged;TextArea.DocumentChanged;", b.ToString()); + } + } + } +} diff --git a/test/AvaloniaEdit.Tests/Editing/TextSegmentReadOnlySectionTests.cs b/test/AvaloniaEdit.Tests/Editing/TextSegmentReadOnlySectionTests.cs new file mode 100644 index 0000000..3bc3886 --- /dev/null +++ b/test/AvaloniaEdit.Tests/Editing/TextSegmentReadOnlySectionTests.cs @@ -0,0 +1,183 @@ +// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using AvaloniaEdit.Document; +using System; +using System.Linq; +using NUnit.Framework; + +namespace AvaloniaEdit.Editing +{ + [TestFixture] + public class TextSegmentReadOnlySectionTests + { + TextSegmentCollection segments; + TextSegmentReadOnlySectionProvider provider; + + [SetUp] + public void SetUp() + { + segments = new TextSegmentCollection(); + provider = new TextSegmentReadOnlySectionProvider(segments); + } + + [Test] + public void InsertionPossibleWhenNothingIsReadOnly() + { + Assert.IsTrue(provider.CanInsert(0)); + Assert.IsTrue(provider.CanInsert(100)); + } + + [Test] + public void DeletionPossibleWhenNothingIsReadOnly() + { + var result = provider.GetDeletableSegments(new SimpleSegment(10, 20)).ToList(); + Assert.AreEqual(1, result.Count); + Assert.AreEqual(10, result[0].Offset); + Assert.AreEqual(20, result[0].Length); + } + + [Test] + public void EmptyDeletionPossibleWhenNothingIsReadOnly() + { + var result = provider.GetDeletableSegments(new SimpleSegment(10, 0)).ToList(); + Assert.AreEqual(1, result.Count); + Assert.AreEqual(10, result[0].Offset); + Assert.AreEqual(0, result[0].Length); + } + + [Test] + public void InsertionPossibleBeforeReadOnlySegment() + { + segments.Add(new TextSegment { StartOffset = 10, EndOffset = 15 }); + Assert.IsTrue(provider.CanInsert(5)); + } + + [Test] + public void InsertionPossibleAtStartOfReadOnlySegment() + { + segments.Add(new TextSegment { StartOffset = 10, EndOffset = 15 }); + Assert.IsTrue(provider.CanInsert(10)); + } + + [Test] + public void InsertionImpossibleInsideReadOnlySegment() + { + segments.Add(new TextSegment { StartOffset = 10, EndOffset = 15 }); + Assert.IsFalse(provider.CanInsert(11)); + Assert.IsFalse(provider.CanInsert(12)); + Assert.IsFalse(provider.CanInsert(13)); + Assert.IsFalse(provider.CanInsert(14)); + } + + [Test] + public void InsertionPossibleAtEndOfReadOnlySegment() + { + segments.Add(new TextSegment { StartOffset = 10, EndOffset = 15 }); + Assert.IsTrue(provider.CanInsert(15)); + } + + [Test] + public void InsertionPossibleBetweenReadOnlySegments() + { + segments.Add(new TextSegment { StartOffset = 10, EndOffset = 15 }); + segments.Add(new TextSegment { StartOffset = 15, EndOffset = 20 }); + Assert.IsTrue(provider.CanInsert(15)); + } + + [Test] + public void DeletionImpossibleInReadOnlySegment() + { + segments.Add(new TextSegment { StartOffset = 10, Length = 5 }); + var result = provider.GetDeletableSegments(new SimpleSegment(11, 2)).ToList(); + Assert.AreEqual(0, result.Count); + } + + [Test] + public void EmptyDeletionImpossibleInReadOnlySegment() + { + segments.Add(new TextSegment { StartOffset = 10, Length = 5 }); + var result = provider.GetDeletableSegments(new SimpleSegment(11, 0)).ToList(); + Assert.AreEqual(0, result.Count); + } + + [Test] + public void EmptyDeletionPossibleAtStartOfReadOnlySegment() + { + segments.Add(new TextSegment { StartOffset = 10, Length = 5 }); + var result = provider.GetDeletableSegments(new SimpleSegment(10, 0)).ToList(); + Assert.AreEqual(1, result.Count); + Assert.AreEqual(10, result[0].Offset); + Assert.AreEqual(0, result[0].Length); + } + + [Test] + public void EmptyDeletionPossibleAtEndOfReadOnlySegment() + { + segments.Add(new TextSegment { StartOffset = 10, Length = 5 }); + var result = provider.GetDeletableSegments(new SimpleSegment(15, 0)).ToList(); + Assert.AreEqual(1, result.Count); + Assert.AreEqual(15, result[0].Offset); + Assert.AreEqual(0, result[0].Length); + } + + [Test] + public void DeletionAroundReadOnlySegment() + { + segments.Add(new TextSegment { StartOffset = 20, Length = 5 }); + var result = provider.GetDeletableSegments(new SimpleSegment(15, 16)).ToList(); + Assert.AreEqual(2, result.Count); + Assert.AreEqual(15, result[0].Offset); + Assert.AreEqual(5, result[0].Length); + Assert.AreEqual(25, result[1].Offset); + Assert.AreEqual(6, result[1].Length); + } + + [Test] + public void DeleteLastCharacterInReadOnlySegment() + { + segments.Add(new TextSegment { StartOffset = 20, Length = 5 }); + var result = provider.GetDeletableSegments(new SimpleSegment(24, 1)).ToList(); + Assert.AreEqual(0, result.Count); + /* // we would need this result for the old Backspace code so that the last character doesn't get selected: + Assert.AreEqual(1, result.Count); + Assert.AreEqual(25, result[0].Offset); + Assert.AreEqual(0, result[0].Length);*/ + } + + [Test] + public void DeleteFirstCharacterInReadOnlySegment() + { + segments.Add(new TextSegment { StartOffset = 20, Length = 5 }); + var result = provider.GetDeletableSegments(new SimpleSegment(20, 1)).ToList(); + Assert.AreEqual(0, result.Count); + /* // we would need this result for the old Delete code so that the first character doesn't get selected: + Assert.AreEqual(1, result.Count); + Assert.AreEqual(2, result[0].Offset); + Assert.AreEqual(0, result[0].Length);*/ + } + + [Test] + public void DeleteWholeReadOnlySegment() + { + segments.Add(new TextSegment { StartOffset = 20, Length = 5 }); + var result = provider.GetDeletableSegments(new SimpleSegment(20, 5)).ToList(); + Assert.AreEqual(0, result.Count); + } + } +} diff --git a/test/AvaloniaEdit.Tests/Highlighting/HighlightedLineMergeTests.cs b/test/AvaloniaEdit.Tests/Highlighting/HighlightedLineMergeTests.cs new file mode 100644 index 0000000..dc29bb0 --- /dev/null +++ b/test/AvaloniaEdit.Tests/Highlighting/HighlightedLineMergeTests.cs @@ -0,0 +1,179 @@ +// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System.Collections.Generic; +using AvaloniaEdit.Document; +using NUnit.Framework; + +namespace AvaloniaEdit.Highlighting +{ + [TestFixture] + public class HighlightedLineMergeTests + { + IDocument document = new TextDocument(new string(' ', 20)); + + [Test] + public void SimpleMerge1() + { + HighlightedLine baseLine = new HighlightedLine(document, document.GetLineByNumber(1)); + baseLine.Sections.Add(MakeSection(0, 1, "B")); + + HighlightedLine additionalLine = new HighlightedLine(document, document.GetLineByNumber(1)); + additionalLine.Sections.Add(MakeSection(0, 2, "A")); + + baseLine.MergeWith(additionalLine); + // The additional section gets split up so that it fits into the tree structure + Assert.That(baseLine.Sections, Is.EqualTo( + new[] { + MakeSection(0, 1, "B"), + MakeSection(0, 1, "A"), + MakeSection(1, 2, "A") + }).Using(new SectionComparer())); + } + + [Test] + public void SimpleMerge2() + { + HighlightedLine baseLine = new HighlightedLine(document, document.GetLineByNumber(1)); + baseLine.Sections.Add(MakeSection(0, 1, "B")); + baseLine.Sections.Add(MakeSection(0, 1, "BN")); + + HighlightedLine additionalLine = new HighlightedLine(document, document.GetLineByNumber(1)); + additionalLine.Sections.Add(MakeSection(0, 2, "A")); + + baseLine.MergeWith(additionalLine); + // The additional section gets split up so that it fits into the tree structure + Assert.That(baseLine.Sections, Is.EqualTo( + new[] { + MakeSection(0, 1, "B"), + MakeSection(0, 1, "BN"), + MakeSection(0, 1, "A"), + MakeSection(1, 2, "A") + }).Using(new SectionComparer())); + } + + HighlightedSection MakeSection(int start, int end, string name) + { + return new HighlightedSection { Offset = start, Length = end - start, Color = new HighlightingColor { Name = name }}; + } + + class SectionComparer : IEqualityComparer + { + public bool Equals(HighlightedSection a, HighlightedSection b) + { + return a.Offset == b.Offset && a.Length == b.Length && a.Color.Name == b.Color.Name; + } + + public int GetHashCode(HighlightedSection obj) + { + return obj.Offset; + } + } + + #region Automatic Test + /* + const int combinations = 6 * 3 * 4 * 3 * 3 * 4; + HighlightingColor[] baseLineColors = { + new HighlightingColor { Name = "Base-A" }, + new HighlightingColor { Name = "Base-B" }, + new HighlightingColor { Name = "Base-N" }, + new HighlightingColor { Name = "Base-C" } + }; + HighlightingColor[] additionalLineColors = { + new HighlightingColor { Name = "Add-A" }, + new HighlightingColor { Name = "Add-B" }, + new HighlightingColor { Name = "Add-N" }, + new HighlightingColor { Name = "Add-C" } + }; + + HighlightedLine BuildHighlightedLine(int num, HighlightingColor[] colors) + { + // We are build a HighlightedLine with 4 segments: + // A B C (top-level) and N nested within B. + // These are the integers controlling the generating process: + + int aStart = GetNum(ref num, 5); // start offset of A + int aLength = GetNum(ref num, 2); // length of A + + int bDistance = GetNum(ref num, 3); // distance from start of B to end of A + int bStart = aStart + aLength + bDistance; + int nDistance = GetNum(ref num, 2); // distance from start of B to start of N, range 0-2 + int nLength = GetNum(ref num, 2); // length of N + int bEndDistance = GetNum(ref num, 2); // distance from end of N to end of B + int bLength = nDistance + nLength + bEndDistance; + + int cDistance = GetNum(ref num, 3); // distance from end of B to start of C + int cStart = bStart + bLength + cDistance; + int cLength = 1; + Assert.AreEqual(0, num); + + var documentLine = document.GetLineByNumber(1); + HighlightedLine line = new HighlightedLine(document, documentLine); + line.Sections.Add(new HighlightedSection { Offset = aStart, Length = aLength, Color = colors[0] }); + line.Sections.Add(new HighlightedSection { Offset = bStart, Length = bLength, Color = colors[1] }); + line.Sections.Add(new HighlightedSection { Offset = bStart + nDistance, Length = nLength, Color = colors[2] }); + line.Sections.Add(new HighlightedSection { Offset = cStart, Length = cLength, Color = colors[3] }); + + return line; + } + + /// + /// Gets a number between 0 and max (inclusive) + /// + int GetNum(ref int num, int max) + { + int result = num % (max+1); + num = num / (max + 1); + return result; + } + + [Test] + public void TestAll() + { + for (int c1 = 0; c1 < combinations; c1++) { + HighlightedLine line1 = BuildHighlightedLine(c1, additionalLineColors); + for (int c2 = 0; c2 < combinations; c2++) { + HighlightedLine line2 = BuildHighlightedLine(c2, baseLineColors); + HighlightingColor[] expectedPerCharColors = new HighlightingColor[document.TextLength]; + ApplyColors(expectedPerCharColors, line2); + ApplyColors(expectedPerCharColors, line1); + try { + line2.MergeWith(line1); + } catch (InvalidOperationException ex) { + throw new InvalidOperationException(string.Format("Error for c1 = {0}, c2 = {1}", c1, c2), ex); + } + + HighlightingColor[] actualPerCharColors = new HighlightingColor[document.TextLength]; + ApplyColors(actualPerCharColors, line2); + Assert.AreEqual(expectedPerCharColors, actualPerCharColors, string.Format("c1 = {0}, c2 = {1}", c1, c2)); + } + } + } + + void ApplyColors(HighlightingColor[] perCharColors, HighlightedLine line) + { + foreach (var section in line.Sections) { + for (int i = 0; i < section.Length; i++) { + perCharColors[section.Offset + i] = section.Color; + } + } + } + */ + #endregion + } +} diff --git a/test/AvaloniaEdit.Tests/Highlighting/HtmlClipboardTests.cs b/test/AvaloniaEdit.Tests/Highlighting/HtmlClipboardTests.cs new file mode 100644 index 0000000..1a10b3c --- /dev/null +++ b/test/AvaloniaEdit.Tests/Highlighting/HtmlClipboardTests.cs @@ -0,0 +1,56 @@ +// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Windows; +using AvaloniaEdit.Document; +using NUnit.Framework; + +namespace AvaloniaEdit.Highlighting +{ + [TestFixture] + public class HtmlClipboardTests + { + TextDocument document; + DocumentHighlighter highlighter; + + public HtmlClipboardTests() + { + document = new TextDocument("using System.Text;\n\tstring text = SomeMethod();"); + highlighter = new DocumentHighlighter(document, HighlightingManager.Instance.GetDefinition("C#")); + } + + [Test, Ignore("")] + public void FullDocumentTest() + { + var segment = new TextSegment { StartOffset = 0, Length = document.TextLength }; + string html = HtmlClipboard.CreateHtmlFragment(document, highlighter, segment, new HtmlOptions()); + Assert.AreEqual("using System.Text;
" + Environment.NewLine + + "    string " + + "text = SomeMethod();", html); + } + + [Test, Ignore("")] + public void PartOfHighlightedWordTest() + { + var segment = new TextSegment { StartOffset = 1, Length = 3 }; + string html = HtmlClipboard.CreateHtmlFragment(document, highlighter, segment, new HtmlOptions()); + Assert.AreEqual("sin", html); + } + } +} diff --git a/test/AvaloniaEdit.Tests/Search/FindTests.cs b/test/AvaloniaEdit.Tests/Search/FindTests.cs new file mode 100644 index 0000000..e416531 --- /dev/null +++ b/test/AvaloniaEdit.Tests/Search/FindTests.cs @@ -0,0 +1,129 @@ +// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Linq; +using AvaloniaEdit.Document; +using NUnit.Framework; + +namespace AvaloniaEdit.Search +{ + //[TestFixture] + //public class FindTests + //{ + // [Test] + // public void SkipWordBorderSimple() + // { + // var strategy = SearchStrategyFactory.Create("All", false, true, SearchMode.Normal); + // var text = new StringTextSource(" FindAllTests "); + // var results = strategy.FindAll(text, 0, text.TextLength).ToArray(); + + // Assert.IsEmpty(results, "No results should be found!"); + // } + + // [Test] + // public void SkipWordBorder() + // { + // var strategy = SearchStrategyFactory.Create("AllTests", false, true, SearchMode.Normal); + // var text = new StringTextSource("name=\"{FindAllTests}\""); + // var results = strategy.FindAll(text, 0, text.TextLength).ToArray(); + + // Assert.IsEmpty(results, "No results should be found!"); + // } + + // [Test] + // public void SkipWordBorder2() + // { + // var strategy = SearchStrategyFactory.Create("AllTests", false, true, SearchMode.Normal); + // var text = new StringTextSource("name=\"FindAllTests "); + // var results = strategy.FindAll(text, 0, text.TextLength).ToArray(); + + // Assert.IsEmpty(results, "No results should be found!"); + // } + + // [Test] + // public void SkipWordBorder3() + // { + // var strategy = SearchStrategyFactory.Create("// find", false, true, SearchMode.Normal); + // var text = new StringTextSource(" // findtest"); + // var results = strategy.FindAll(text, 0, text.TextLength).ToArray(); + + // Assert.IsEmpty(results, "No results should be found!"); + // } + + // [Test] + // public void WordBorderTest() + // { + // var strategy = SearchStrategyFactory.Create("// find", false, true, SearchMode.Normal); + // var text = new StringTextSource(" // find me"); + // var results = strategy.FindAll(text, 0, text.TextLength).ToArray(); + + // Assert.AreEqual(1, results.Length, "One result should be found!"); + // Assert.AreEqual(" ".Length, results[0].Offset); + // Assert.AreEqual("// find".Length, results[0].Length); + // } + + // [Test] + // public void ResultAtStart() + // { + // var strategy = SearchStrategyFactory.Create("result", false, true, SearchMode.Normal); + // var text = new StringTextSource("result // find me"); + // var results = strategy.FindAll(text, 0, text.TextLength).ToArray(); + + // Assert.AreEqual(1, results.Length, "One result should be found!"); + // Assert.AreEqual(0, results[0].Offset); + // Assert.AreEqual("result".Length, results[0].Length); + // } + + // [Test] + // public void ResultAtEnd() + // { + // var strategy = SearchStrategyFactory.Create("me", false, true, SearchMode.Normal); + // var text = new StringTextSource("result // find me"); + // var results = strategy.FindAll(text, 0, text.TextLength).ToArray(); + + // Assert.AreEqual(1, results.Length, "One result should be found!"); + // Assert.AreEqual("result // find ".Length, results[0].Offset); + // Assert.AreEqual("me".Length, results[0].Length); + // } + + // [Test] + // public void TextWithDots() + // { + // var strategy = SearchStrategyFactory.Create("Text", false, true, SearchMode.Normal); + // var text = new StringTextSource(".Text."); + // var results = strategy.FindAll(text, 0, text.TextLength).ToArray(); + + // Assert.AreEqual(1, results.Length, "One result should be found!"); + // Assert.AreEqual(".".Length, results[0].Offset); + // Assert.AreEqual("Text".Length, results[0].Length); + // } + + // [Test] + // public void SimpleTest() + // { + // var strategy = SearchStrategyFactory.Create("AllTests", false, false, SearchMode.Normal); + // var text = new StringTextSource("name=\"FindAllTests "); + // var results = strategy.FindAll(text, 0, text.TextLength).ToArray(); + + // Assert.AreEqual(1, results.Length, "One result should be found!"); + // Assert.AreEqual("name=\"Find".Length, results[0].Offset); + // Assert.AreEqual("AllTests".Length, results[0].Length); + // } + //} +} diff --git a/test/AvaloniaEdit.Tests/Utils/CaretNavigationTests.cs b/test/AvaloniaEdit.Tests/Utils/CaretNavigationTests.cs new file mode 100644 index 0000000..55cf2c6 --- /dev/null +++ b/test/AvaloniaEdit.Tests/Utils/CaretNavigationTests.cs @@ -0,0 +1,153 @@ +// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using AvaloniaEdit.Document; +using NUnit.Framework; + +namespace AvaloniaEdit.Utils +{ + [TestFixture] + public class CaretNavigationTests + { + int GetNextCaretStop(string text, int offset, CaretPositioningMode mode) + { + return TextUtilities.GetNextCaretPosition(new StringTextSource(text), offset, LogicalDirection.Forward, mode); + } + + int GetPrevCaretStop(string text, int offset, CaretPositioningMode mode) + { + return TextUtilities.GetNextCaretPosition(new StringTextSource(text), offset, LogicalDirection.Backward, mode); + } + + [Test] + public void CaretStopInEmptyString() + { + Assert.AreEqual(0, GetNextCaretStop("", -1, CaretPositioningMode.Normal)); + Assert.AreEqual(-1, GetNextCaretStop("", 0, CaretPositioningMode.Normal)); + Assert.AreEqual(-1, GetPrevCaretStop("", 0, CaretPositioningMode.Normal)); + Assert.AreEqual(0, GetPrevCaretStop("", 1, CaretPositioningMode.Normal)); + + Assert.AreEqual(-1, GetNextCaretStop("", -1, CaretPositioningMode.WordStart)); + Assert.AreEqual(-1, GetNextCaretStop("", -1, CaretPositioningMode.WordBorder)); + Assert.AreEqual(-1, GetPrevCaretStop("", 1, CaretPositioningMode.WordStart)); + Assert.AreEqual(-1, GetPrevCaretStop("", 1, CaretPositioningMode.WordBorder)); + } + + [Test] + public void StartOfDocumentWithWordStart() + { + Assert.AreEqual(0, GetNextCaretStop("word", -1, CaretPositioningMode.Normal)); + Assert.AreEqual(0, GetNextCaretStop("word", -1, CaretPositioningMode.WordStart)); + Assert.AreEqual(0, GetNextCaretStop("word", -1, CaretPositioningMode.WordBorder)); + + Assert.AreEqual(0, GetPrevCaretStop("word", 1, CaretPositioningMode.Normal)); + Assert.AreEqual(0, GetPrevCaretStop("word", 1, CaretPositioningMode.WordStart)); + Assert.AreEqual(0, GetPrevCaretStop("word", 1, CaretPositioningMode.WordBorder)); + } + + [Test] + public void StartOfDocumentNoWordStart() + { + Assert.AreEqual(0, GetNextCaretStop(" word", -1, CaretPositioningMode.Normal)); + Assert.AreEqual(1, GetNextCaretStop(" word", -1, CaretPositioningMode.WordStart)); + Assert.AreEqual(1, GetNextCaretStop(" word", -1, CaretPositioningMode.WordBorder)); + + Assert.AreEqual(0, GetPrevCaretStop(" word", 1, CaretPositioningMode.Normal)); + Assert.AreEqual(-1, GetPrevCaretStop(" word", 1, CaretPositioningMode.WordStart)); + Assert.AreEqual(-1, GetPrevCaretStop(" word", 1, CaretPositioningMode.WordBorder)); + } + + [Test] + public void EndOfDocumentWordBorder() + { + Assert.AreEqual(4, GetNextCaretStop("word", 3, CaretPositioningMode.Normal)); + Assert.AreEqual(-1, GetNextCaretStop("word", 3, CaretPositioningMode.WordStart)); + Assert.AreEqual(4, GetNextCaretStop("word", 3, CaretPositioningMode.WordBorder)); + + Assert.AreEqual(4, GetPrevCaretStop("word", 5, CaretPositioningMode.Normal)); + Assert.AreEqual(0, GetPrevCaretStop("word", 5, CaretPositioningMode.WordStart)); + Assert.AreEqual(4, GetPrevCaretStop("word", 5, CaretPositioningMode.WordBorder)); + } + + [Test] + public void EndOfDocumentNoWordBorder() + { + Assert.AreEqual(4, GetNextCaretStop("txt ", 3, CaretPositioningMode.Normal)); + Assert.AreEqual(-1, GetNextCaretStop("txt ", 3, CaretPositioningMode.WordStart)); + Assert.AreEqual(-1, GetNextCaretStop("txt ", 3, CaretPositioningMode.WordBorder)); + + Assert.AreEqual(4, GetPrevCaretStop("txt ", 5, CaretPositioningMode.Normal)); + Assert.AreEqual(0, GetPrevCaretStop("txt ", 5, CaretPositioningMode.WordStart)); + Assert.AreEqual(3, GetPrevCaretStop("txt ", 5, CaretPositioningMode.WordBorder)); + } + + [Test] + public void SingleCharacterOutsideBMP() + { + string c = "\U0001D49E"; + Assert.AreEqual(2, GetNextCaretStop(c, 0, CaretPositioningMode.Normal)); + Assert.AreEqual(0, GetPrevCaretStop(c, 2, CaretPositioningMode.Normal)); + } + + [Test] + public void DetectWordBordersOutsideBMP() + { + string c = " a\U0001D49Eb "; + Assert.AreEqual(1, GetNextCaretStop(c, 0, CaretPositioningMode.WordBorder)); + Assert.AreEqual(5, GetNextCaretStop(c, 1, CaretPositioningMode.WordBorder)); + + Assert.AreEqual(5, GetPrevCaretStop(c, 6, CaretPositioningMode.WordBorder)); + Assert.AreEqual(1, GetPrevCaretStop(c, 5, CaretPositioningMode.WordBorder)); + } + + [Test] + public void DetectWordBordersOutsideBMP2() + { + string c = " \U0001D49E\U0001D4AA "; + Assert.AreEqual(1, GetNextCaretStop(c, 0, CaretPositioningMode.WordBorder)); + Assert.AreEqual(5, GetNextCaretStop(c, 1, CaretPositioningMode.WordBorder)); + + Assert.AreEqual(5, GetPrevCaretStop(c, 6, CaretPositioningMode.WordBorder)); + Assert.AreEqual(1, GetPrevCaretStop(c, 5, CaretPositioningMode.WordBorder)); + } + + [Test] + public void CombiningMark() + { + string str = " x͆ "; + Assert.AreEqual(3, GetNextCaretStop(str, 1, CaretPositioningMode.Normal)); + Assert.AreEqual(1, GetPrevCaretStop(str, 3, CaretPositioningMode.Normal)); + } + + [Test] + public void StackedCombiningMark() + { + string str = " x͆͆͆͆ "; + Assert.AreEqual(6, GetNextCaretStop(str, 1, CaretPositioningMode.Normal)); + Assert.AreEqual(1, GetPrevCaretStop(str, 6, CaretPositioningMode.Normal)); + } + + [Test] + public void SingleClosingBraceAtLineEnd() + { + string str = "\t\t}"; + Assert.AreEqual(2, GetNextCaretStop(str, 1, CaretPositioningMode.WordStart)); + Assert.AreEqual(-1, GetPrevCaretStop(str, 1, CaretPositioningMode.WordStart)); + } + } +} diff --git a/test/AvaloniaEdit.Tests/Utils/CompressingTreeListTests.cs b/test/AvaloniaEdit.Tests/Utils/CompressingTreeListTests.cs new file mode 100644 index 0000000..80afe90 --- /dev/null +++ b/test/AvaloniaEdit.Tests/Utils/CompressingTreeListTests.cs @@ -0,0 +1,146 @@ +// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Linq; +using NUnit.Framework; + +namespace AvaloniaEdit.Utils +{ + [TestFixture] + public class CompressingTreeListTests + { + [Test] + public void EmptyTreeList() + { + CompressingTreeList list = new CompressingTreeList(string.Equals); + Assert.AreEqual(0, list.Count); + foreach (string v in list) { + Assert.Fail(); + } + string[] arr = new string[0]; + list.CopyTo(arr, 0); + } + + [Test] + public void CheckAdd10BillionElements() + { + const int billion = 1000000000; + CompressingTreeList list = new CompressingTreeList(string.Equals); + list.InsertRange(0, billion, "A"); + list.InsertRange(1, billion, "B"); + Assert.AreEqual(2 * billion, list.Count); + Assert.Throws(delegate { list.InsertRange(2, billion, "C"); }); + } + + [Test] + public void AddRepeated() + { + CompressingTreeList list = new CompressingTreeList((a, b) => a == b); + list.Add(42); + list.Add(42); + list.Add(42); + list.Insert(0, 42); + list.Insert(1, 42); + Assert.AreEqual(new[] { 42, 42, 42, 42, 42 }, list.ToArray()); + } + + [Test] + public void RemoveRange() + { + CompressingTreeList list = new CompressingTreeList((a, b) => a == b); + for (int i = 1; i <= 3; i++) { + list.InsertRange(list.Count, 2, i); + } + Assert.AreEqual(new[] { 1, 1, 2, 2, 3, 3 }, list.ToArray()); + list.RemoveRange(1, 4); + Assert.AreEqual(new[] { 1, 3 }, list.ToArray()); + list.Insert(1, 1); + list.InsertRange(2, 2, 2); + list.Insert(4, 1); + Assert.AreEqual(new[] { 1, 1, 2, 2, 1, 3 }, list.ToArray()); + list.RemoveRange(2, 2); + Assert.AreEqual(new[] { 1, 1, 1, 3 }, list.ToArray()); + } + + [Test] + public void RemoveAtEnd() + { + CompressingTreeList list = new CompressingTreeList((a, b) => a == b); + for (int i = 1; i <= 3; i++) { + list.InsertRange(list.Count, 2, i); + } + Assert.AreEqual(new[] { 1, 1, 2, 2, 3, 3 }, list.ToArray()); + list.RemoveRange(3, 3); + Assert.AreEqual(new[] { 1, 1, 2 }, list.ToArray()); + } + + [Test] + public void RemoveAtStart() + { + CompressingTreeList list = new CompressingTreeList((a, b) => a == b); + for (int i = 1; i <= 3; i++) { + list.InsertRange(list.Count, 2, i); + } + Assert.AreEqual(new[] { 1, 1, 2, 2, 3, 3 }, list.ToArray()); + list.RemoveRange(0, 1); + Assert.AreEqual(new[] { 1, 2, 2, 3, 3 }, list.ToArray()); + } + + [Test] + public void RemoveAtStart2() + { + CompressingTreeList list = new CompressingTreeList((a, b) => a == b); + for (int i = 1; i <= 3; i++) { + list.InsertRange(list.Count, 2, i); + } + Assert.AreEqual(new[] { 1, 1, 2, 2, 3, 3 }, list.ToArray()); + list.RemoveRange(0, 3); + Assert.AreEqual(new[] { 2, 3, 3 }, list.ToArray()); + } + + [Test] + public void Transform() + { + CompressingTreeList list = new CompressingTreeList((a, b) => a == b); + list.AddRange(new[] { 0, 1, 1, 0 }); + int calls = 0; + list.Transform(i => { calls++; return i + 1; }); + Assert.AreEqual(3, calls); + Assert.AreEqual(new[] { 1, 2, 2, 1 }, list.ToArray()); + } + + [Test] + public void TransformToZero() + { + CompressingTreeList list = new CompressingTreeList((a, b) => a == b); + list.AddRange(new[] { 0, 1, 1, 0 }); + list.Transform(i => 0); + Assert.AreEqual(new[] { 0, 0, 0, 0 }, list.ToArray()); + } + + [Test] + public void TransformRange() + { + CompressingTreeList list = new CompressingTreeList((a, b) => a == b); + list.AddRange(new[] { 0, 1, 1, 1, 0, 0 }); + list.TransformRange(2, 3, i => 0); + Assert.AreEqual(new[] { 0, 1, 0, 0, 0, 0 }, list.ToArray()); + } + } +} diff --git a/test/AvaloniaEdit.Tests/Utils/ExtensionMethodsTests.cs b/test/AvaloniaEdit.Tests/Utils/ExtensionMethodsTests.cs new file mode 100644 index 0000000..e4c3991 --- /dev/null +++ b/test/AvaloniaEdit.Tests/Utils/ExtensionMethodsTests.cs @@ -0,0 +1,51 @@ +// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using NUnit.Framework; + +namespace AvaloniaEdit.Utils +{ + [TestFixture] + public class ExtensionMethodsTests + { + [Test] + public void ZeroIsNotCloseToOne() + { + Assert.IsFalse(0.0.IsClose(1)); + } + + [Test] + public void ZeroIsCloseToZero() + { + Assert.IsTrue(0.0.IsClose(0)); + } + + [Test] + public void InfinityIsCloseToInfinity() + { + Assert.IsTrue(double.PositiveInfinity.IsClose(double.PositiveInfinity)); + } + + [Test] + public void NaNIsNotCloseToNaN() + { + Assert.IsFalse(double.NaN.IsClose(double.NaN)); + } + } +} diff --git a/test/AvaloniaEdit.Tests/Utils/IndentationStringTests.cs b/test/AvaloniaEdit.Tests/Utils/IndentationStringTests.cs new file mode 100644 index 0000000..7728469 --- /dev/null +++ b/test/AvaloniaEdit.Tests/Utils/IndentationStringTests.cs @@ -0,0 +1,51 @@ +// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using NUnit.Framework; + +namespace AvaloniaEdit.Utils +{ + [TestFixture] + public class IndentationStringTests + { + [Test] + public void IndentWithSingleTab() + { + var options = new TextEditorOptions { IndentationSize = 4, ConvertTabsToSpaces = false }; + Assert.AreEqual("\t", options.IndentationString); + Assert.AreEqual("\t", options.GetIndentationString(2)); + Assert.AreEqual("\t", options.GetIndentationString(3)); + Assert.AreEqual("\t", options.GetIndentationString(4)); + Assert.AreEqual("\t", options.GetIndentationString(5)); + Assert.AreEqual("\t", options.GetIndentationString(6)); + } + + [Test] + public void IndentWith4Spaces() + { + var options = new TextEditorOptions { IndentationSize = 4, ConvertTabsToSpaces = true }; + Assert.AreEqual(" ", options.IndentationString); + Assert.AreEqual(" ", options.GetIndentationString(2)); + Assert.AreEqual(" ", options.GetIndentationString(3)); + Assert.AreEqual(" ", options.GetIndentationString(4)); + Assert.AreEqual(" ", options.GetIndentationString(5)); + Assert.AreEqual(" ", options.GetIndentationString(6)); + } + } +} diff --git a/test/AvaloniaEdit.Tests/Utils/RopeTests.cs b/test/AvaloniaEdit.Tests/Utils/RopeTests.cs new file mode 100644 index 0000000..10c8e92 --- /dev/null +++ b/test/AvaloniaEdit.Tests/Utils/RopeTests.cs @@ -0,0 +1,195 @@ +// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.IO; +using NUnit.Framework; +using System.Text; + +namespace AvaloniaEdit.Utils +{ + [TestFixture] + public class RopeTests + { + [Test] + public void EmptyRope() + { + Rope empty = new Rope(); + Assert.AreEqual(0, empty.Length); + Assert.AreEqual("", empty.ToString()); + } + + [Test] + public void EmptyRopeFromString() + { + Rope empty = new Rope(string.Empty); + Assert.AreEqual(0, empty.Length); + Assert.AreEqual("", empty.ToString()); + } + + [Test] + public void InitializeRopeFromShortString() + { + Rope rope = new Rope("Hello, World"); + Assert.AreEqual(12, rope.Length); + Assert.AreEqual("Hello, World", rope.ToString()); + } + + string BuildLongString(int lines) + { + StringWriter w = new StringWriter(); + w.NewLine = "\n"; + for (int i = 1; i <= lines; i++) { + w.WriteLine(i.ToString()); + } + return w.ToString(); + } + + [Test] + public void InitializeRopeFromLongString() + { + string text = BuildLongString(1000); + Rope rope = new Rope(text); + Assert.AreEqual(text.Length, rope.Length); + Assert.AreEqual(text, rope.ToString()); + Assert.AreEqual(text.ToCharArray(), rope.ToArray()); + } + + [Test] + public void TestToArrayAndToStringWithParts() + { + string text = BuildLongString(1000); + Rope rope = new Rope(text); + + string textPart = text.Substring(1200, 600); + char[] arrayPart = textPart.ToCharArray(); + Assert.AreEqual(textPart, rope.ToString(1200, 600)); + Assert.AreEqual(arrayPart, rope.ToArray(1200, 600)); + + Rope partialRope = rope.GetRange(1200, 600); + Assert.AreEqual(textPart, partialRope.ToString()); + Assert.AreEqual(arrayPart, partialRope.ToArray()); + } + + [Test] + public void ConcatenateStringToRope() + { + StringBuilder b = new StringBuilder(); + Rope rope = new Rope(); + for (int i = 1; i <= 1000; i++) { + b.Append(i.ToString()); + rope.AddText(i.ToString()); + b.Append(' '); + rope.Add(' '); + } + Assert.AreEqual(b.ToString(), rope.ToString()); + } + + [Test] + public void ConcatenateSmallRopesToRope() + { + StringBuilder b = new StringBuilder(); + Rope rope = new Rope(); + for (int i = 1; i <= 1000; i++) { + b.Append(i.ToString()); + b.Append(' '); + rope.AddRange(CharRope.Create(i.ToString() + " ")); + } + Assert.AreEqual(b.ToString(), rope.ToString()); + } + + [Test] + public void AppendLongTextToEmptyRope() + { + string text = BuildLongString(1000); + Rope rope = new Rope(); + rope.AddText(text); + Assert.AreEqual(text, rope.ToString()); + } + + [Test] + public void ConcatenateStringToRopeBackwards() + { + StringBuilder b = new StringBuilder(); + Rope rope = new Rope(); + for (int i = 1; i <= 1000; i++) { + b.Append(i.ToString()); + b.Append(' '); + } + for (int i = 1000; i >= 1; i--) { + rope.Insert(0, ' '); + rope.InsertText(0, i.ToString()); + } + Assert.AreEqual(b.ToString(), rope.ToString()); + } + + [Test] + public void ConcatenateSmallRopesToRopeBackwards() + { + StringBuilder b = new StringBuilder(); + Rope rope = new Rope(); + for (int i = 1; i <= 1000; i++) { + b.Append(i.ToString()); + b.Append(' '); + } + for (int i = 1000; i >= 1; i--) { + rope.InsertRange(0, CharRope.Create(i.ToString() + " ")); + } + Assert.AreEqual(b.ToString(), rope.ToString()); + } + + [Test] + public void ConcatenateStringToRopeByInsertionInMiddle() + { + StringBuilder b = new StringBuilder(); + Rope rope = new Rope(); + for (int i = 1; i <= 998; i++) { + b.Append(i.ToString("d3")); + b.Append(' '); + } + int middle = 0; + for (int i = 1; i <= 499; i++) { + rope.InsertText(middle, i.ToString("d3")); + middle += 3; + rope.Insert(middle, ' '); + middle++; + rope.InsertText(middle, (999-i).ToString("d3")); + rope.Insert(middle + 3, ' '); + } + Assert.AreEqual(b.ToString(), rope.ToString()); + } + + [Test] + public void ConcatenateSmallRopesByInsertionInMiddle() + { + StringBuilder b = new StringBuilder(); + Rope rope = new Rope(); + for (int i = 1; i <= 1000; i++) { + b.Append(i.ToString("d3")); + b.Append(' '); + } + int middle = 0; + for (int i = 1; i <= 500; i++) { + rope.InsertRange(middle, CharRope.Create(i.ToString("d3") + " ")); + middle += 4; + rope.InsertRange(middle, CharRope.Create((1001-i).ToString("d3") + " ")); + } + Assert.AreEqual(b.ToString(), rope.ToString()); + } + } +} diff --git a/test/AvaloniaEdit.Tests/WeakReferenceTests.cs b/test/AvaloniaEdit.Tests/WeakReferenceTests.cs new file mode 100644 index 0000000..c14d135 --- /dev/null +++ b/test/AvaloniaEdit.Tests/WeakReferenceTests.cs @@ -0,0 +1,116 @@ +// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Runtime.CompilerServices; +using AvaloniaEdit.AvaloniaMocks; +using AvaloniaEdit.Document; +using AvaloniaEdit.Editing; +using AvaloniaEdit.Rendering; +using NUnit.Framework; + +namespace AvaloniaEdit +{ + [TestFixture] + public class WeakReferenceTests + { + [MethodImpl(MethodImplOptions.NoInlining)] + private static WeakReference CreateControl(Action action = null) + where T : class, new() + { + WeakReference wr; + + using (UnitTestApplication.Start(new TestServices(renderInterface: new MockPlatformRenderInterface()))) + { + var control = new T(); + wr = new WeakReference(control); + action?.Invoke(control); + control = null; + } + + GarbageCollect(); + + return wr; + } + + [Test] + public void TextViewCanBeCollectedTest() + { + var wr = CreateControl(); + Assert.IsFalse(wr.IsAlive); + } + + [Test] + public void DocumentDoesNotHoldReferenceToTextView() + { + TextDocument textDocument = new TextDocument(); + Assert.AreEqual(0, textDocument.LineTrackers.Count); + + var wr = CreateControl(t => t.Document = textDocument); + + Assert.IsFalse(wr.IsAlive); + // document cannot immediately clear the line tracker + Assert.AreEqual(1, textDocument.LineTrackers.Count); + + // but it should clear it on the next change + textDocument.Insert(0, "a"); + Assert.AreEqual(0, textDocument.LineTrackers.Count); + } + + //[Test] // currently fails due to some Avalonia static + void DocumentDoesNotHoldReferenceToTextArea() + { + var textDocument = new TextDocument(); + var wr = CreateControl