From 2283cfa4866cedbf3a58f5860bb8dac63ab79610 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 8 Feb 2016 10:19:11 +0100 Subject: [PATCH] Started refactoring unit tests. Added a shared test project with a UnitTestApplication and use it in LeakTests. --- Perspex.sln | 34 +- src/Perspex.Application/Application.cs | 15 +- src/Perspex.Base/Threading/DispatcherTimer.cs | 7 +- .../Platform/PlatformManager.cs | 10 +- src/Perspex.Input/Cursors.cs | 16 +- src/Perspex.SceneGraph/Media/FormattedText.cs | 5 + tests/Perspex.LeakTests/ControlTests.cs | 372 ++++++++++-------- .../Perspex.LeakTests.csproj | 5 +- tests/Perspex.LeakTests/TestApp.cs | 47 --- .../MockWindowingPlatform.cs | 30 ++ .../Perspex.UnitTests.csproj | 141 +++++++ .../Properties/AssemblyInfo.cs | 36 ++ tests/Perspex.UnitTests/TestServices.cs | 44 +++ .../Perspex.UnitTests/UnitTestApplication.cs | 43 ++ tests/Perspex.UnitTests/app.config | 11 + tests/Perspex.UnitTests/packages.config | 11 + 16 files changed, 588 insertions(+), 239 deletions(-) delete mode 100644 tests/Perspex.LeakTests/TestApp.cs create mode 100644 tests/Perspex.UnitTests/MockWindowingPlatform.cs create mode 100644 tests/Perspex.UnitTests/Perspex.UnitTests.csproj create mode 100644 tests/Perspex.UnitTests/Properties/AssemblyInfo.cs create mode 100644 tests/Perspex.UnitTests/TestServices.cs create mode 100644 tests/Perspex.UnitTests/UnitTestApplication.cs create mode 100644 tests/Perspex.UnitTests/app.config create mode 100644 tests/Perspex.UnitTests/packages.config diff --git a/Perspex.sln b/Perspex.sln index 2d1e4b3ed9..a6b425f9d1 100644 --- a/Perspex.sln +++ b/Perspex.sln @@ -140,6 +140,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.LeakTests", "tests\ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlCatalog", "samples\ControlCatalog\ControlCatalog.csproj", "{61BEC86C-F307-4295-B5B8-9428610D7D55}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.UnitTests", "tests\Perspex.UnitTests\Perspex.UnitTests.csproj", "{88060192-33D5-4932-B0F9-8BD2763E857D}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution src\Shared\RenderHelpers\RenderHelpers.projitems*{fb05ac90-89ba-4f2f-a924-f37875fb547c}*SharedItemsImports = 4 @@ -148,19 +150,20 @@ Global src\Shared\PlatformSupport\PlatformSupport.projitems*{e4d9629c-f168-4224-3f51-a5e482ffbc42}*SharedItemsImports = 13 src\Skia\Perspex.Skia\Perspex.Skia.projitems*{2f59f3d0-748d-4652-b01e-e0d954756308}*SharedItemsImports = 13 src\Shared\PlatformSupport\PlatformSupport.projitems*{db070a10-bf39-4752-8456-86e9d5928478}*SharedItemsImports = 4 - src\Shared\RenderHelpers\RenderHelpers.projitems*{925dd807-b651-475f-9f7c-cbeb974ce43d}*SharedItemsImports = 4 src\Skia\Perspex.Skia\Perspex.Skia.projitems*{925dd807-b651-475f-9f7c-cbeb974ce43d}*SharedItemsImports = 4 + src\Shared\RenderHelpers\RenderHelpers.projitems*{925dd807-b651-475f-9f7c-cbeb974ce43d}*SharedItemsImports = 4 samples\TestApplicationShared\TestApplicationShared.projitems*{78345174-5b52-4a14-b9fd-d5f2428137f0}*SharedItemsImports = 13 src\Shared\PlatformSupport\PlatformSupport.projitems*{54f237d5-a70a-4752-9656-0c70b1a7b047}*SharedItemsImports = 4 samples\TestApplicationShared\TestApplicationShared.projitems*{ff69b927-c545-49ae-8e16-3d14d621aa12}*SharedItemsImports = 4 src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13 src\Shared\PlatformSupport\PlatformSupport.projitems*{811a76cf-1cf6-440f-963b-bbe31bd72a82}*SharedItemsImports = 4 - src\Shared\RenderHelpers\RenderHelpers.projitems*{47be08a7-5985-410b-9ffc-2264b8ea595f}*SharedItemsImports = 4 + src\Shared\PlatformSupport\PlatformSupport.projitems*{88060192-33d5-4932-b0f9-8bd2763e857d}*SharedItemsImports = 4 src\Skia\Perspex.Skia\Perspex.Skia.projitems*{47be08a7-5985-410b-9ffc-2264b8ea595f}*SharedItemsImports = 4 + src\Shared\RenderHelpers\RenderHelpers.projitems*{47be08a7-5985-410b-9ffc-2264b8ea595f}*SharedItemsImports = 4 samples\TestApplicationShared\TestApplicationShared.projitems*{8c923867-8a8f-4f6b-8b80-47d9e8436166}*SharedItemsImports = 4 samples\TestApplicationShared\TestApplicationShared.projitems*{e3a1060b-50d0-44e8-88b6-f44ef2e5bd72}*SharedItemsImports = 4 - src\Shared\RenderHelpers\RenderHelpers.projitems*{bd43f7c0-396b-4aa1-bad9-dfde54d51298}*SharedItemsImports = 4 src\Skia\Perspex.Skia\Perspex.Skia.projitems*{bd43f7c0-396b-4aa1-bad9-dfde54d51298}*SharedItemsImports = 4 + src\Shared\RenderHelpers\RenderHelpers.projitems*{bd43f7c0-396b-4aa1-bad9-dfde54d51298}*SharedItemsImports = 4 src\Shared\RenderHelpers\RenderHelpers.projitems*{3e908f67-5543-4879-a1dc-08eace79b3cd}*SharedItemsImports = 4 src\Shared\PlatformSupport\PlatformSupport.projitems*{e1aa3dbf-9056-4530-9376-18119a7a3ffe}*SharedItemsImports = 4 EndGlobalSection @@ -1294,6 +1297,30 @@ Global {61BEC86C-F307-4295-B5B8-9428610D7D55}.Release|iPhone.Build.0 = Release|Any CPU {61BEC86C-F307-4295-B5B8-9428610D7D55}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU {61BEC86C-F307-4295-B5B8-9428610D7D55}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {88060192-33D5-4932-B0F9-8BD2763E857D}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU + {88060192-33D5-4932-B0F9-8BD2763E857D}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU + {88060192-33D5-4932-B0F9-8BD2763E857D}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU + {88060192-33D5-4932-B0F9-8BD2763E857D}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU + {88060192-33D5-4932-B0F9-8BD2763E857D}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU + {88060192-33D5-4932-B0F9-8BD2763E857D}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU + {88060192-33D5-4932-B0F9-8BD2763E857D}.AppStore|Any CPU.ActiveCfg = Release|Any CPU + {88060192-33D5-4932-B0F9-8BD2763E857D}.AppStore|Any CPU.Build.0 = Release|Any CPU + {88060192-33D5-4932-B0F9-8BD2763E857D}.AppStore|iPhone.ActiveCfg = Release|Any CPU + {88060192-33D5-4932-B0F9-8BD2763E857D}.AppStore|iPhone.Build.0 = Release|Any CPU + {88060192-33D5-4932-B0F9-8BD2763E857D}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU + {88060192-33D5-4932-B0F9-8BD2763E857D}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU + {88060192-33D5-4932-B0F9-8BD2763E857D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {88060192-33D5-4932-B0F9-8BD2763E857D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {88060192-33D5-4932-B0F9-8BD2763E857D}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {88060192-33D5-4932-B0F9-8BD2763E857D}.Debug|iPhone.Build.0 = Debug|Any CPU + {88060192-33D5-4932-B0F9-8BD2763E857D}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {88060192-33D5-4932-B0F9-8BD2763E857D}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {88060192-33D5-4932-B0F9-8BD2763E857D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {88060192-33D5-4932-B0F9-8BD2763E857D}.Release|Any CPU.Build.0 = Release|Any CPU + {88060192-33D5-4932-B0F9-8BD2763E857D}.Release|iPhone.ActiveCfg = Release|Any CPU + {88060192-33D5-4932-B0F9-8BD2763E857D}.Release|iPhone.Build.0 = Release|Any CPU + {88060192-33D5-4932-B0F9-8BD2763E857D}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {88060192-33D5-4932-B0F9-8BD2763E857D}.Release|iPhoneSimulator.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1338,5 +1365,6 @@ Global {8C923867-8A8F-4F6B-8B80-47D9E8436166} = {0CB0B92E-6CFF-4240-80A5-CCAFE75D91E1} {E1AA3DBF-9056-4530-9376-18119A7A3FFE} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} {61BEC86C-F307-4295-B5B8-9428610D7D55} = {9B9E3891-2366-4253-A952-D08BCEB71098} + {88060192-33D5-4932-B0F9-8BD2763E857D} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} EndGlobalSection EndGlobal diff --git a/src/Perspex.Application/Application.cs b/src/Perspex.Application/Application.cs index 177c337d04..e904207b18 100644 --- a/src/Perspex.Application/Application.cs +++ b/src/Perspex.Application/Application.cs @@ -59,12 +59,7 @@ namespace Perspex throw new InvalidOperationException("Cannot create more than one Application instance."); } - Current = this; - } - - public static void RegisterPlatformCallback(Action cb) - { - _platformInitializationCallback = cb; + PerspexLocator.CurrentMutable.BindToSelf(this); } /// @@ -75,8 +70,7 @@ namespace Perspex /// public static Application Current { - get; - private set; + get { return PerspexLocator.Current.GetService(); } } /// @@ -140,6 +134,11 @@ namespace Perspex /// IStyleHost IStyleHost.StylingParent => null; + public static void RegisterPlatformCallback(Action cb) + { + _platformInitializationCallback = cb; + } + /// /// Runs the application's main loop until the is closed. /// diff --git a/src/Perspex.Base/Threading/DispatcherTimer.cs b/src/Perspex.Base/Threading/DispatcherTimer.cs index 1ece331c13..287ce1c124 100644 --- a/src/Perspex.Base/Threading/DispatcherTimer.cs +++ b/src/Perspex.Base/Threading/DispatcherTimer.cs @@ -181,6 +181,12 @@ namespace Perspex.Threading if (!IsEnabled) { IPlatformThreadingInterface threading = PerspexLocator.Current.GetService(); + + if (threading == null) + { + throw new Exception("Could not start timer: IPlatformThreadingInterface is not registered."); + } + _timer = threading.StartTimer(Interval, InternalTick); } } @@ -192,7 +198,6 @@ namespace Perspex.Threading { if (IsEnabled) { - IPlatformThreadingInterface threading = PerspexLocator.Current.GetService(); _timer.Dispose(); _timer = null; } diff --git a/src/Perspex.Controls/Platform/PlatformManager.cs b/src/Perspex.Controls/Platform/PlatformManager.cs index 75cd4e4cd3..17a3ff691f 100644 --- a/src/Perspex.Controls/Platform/PlatformManager.cs +++ b/src/Perspex.Controls/Platform/PlatformManager.cs @@ -192,8 +192,14 @@ namespace Perspex.Controls.Platform public static IWindowImpl CreateWindow() { var platform = PerspexLocator.Current.GetService(); - return - new WindowDecorator(s_designerMode ? platform.CreateEmbeddableWindow() : platform.CreateWindow()); + + if (platform == null) + { + throw new Exception("Could not CreateWindow(): IWindowingPlatform is not registered."); + } + + var window = s_designerMode ? platform.CreateEmbeddableWindow() : platform.CreateWindow(); + return new WindowDecorator(window); } public static IPopupImpl CreatePopup() diff --git a/src/Perspex.Input/Cursors.cs b/src/Perspex.Input/Cursors.cs index 34f97fe0a6..184bfd38b0 100644 --- a/src/Perspex.Input/Cursors.cs +++ b/src/Perspex.Input/Cursors.cs @@ -56,12 +56,22 @@ namespace Perspex.Input } public Cursor(StandardCursorType cursorType) - : this( - ((IStandardCursorFactory)PerspexLocator.Current.GetService(typeof(IStandardCursorFactory))).GetCursor( - cursorType)) + : this(GetCursor(cursorType)) { } public IPlatformHandle PlatformCursor { get; } + + private static IPlatformHandle GetCursor(StandardCursorType type) + { + var platform = PerspexLocator.Current.GetService(); + + if (platform == null) + { + throw new Exception("Could not create Cursor: IStandardCursorFactory not registered."); + } + + return platform.GetCursor(type); + } } } diff --git a/src/Perspex.SceneGraph/Media/FormattedText.cs b/src/Perspex.SceneGraph/Media/FormattedText.cs index 25ef16cd7e..e2db938612 100644 --- a/src/Perspex.SceneGraph/Media/FormattedText.cs +++ b/src/Perspex.SceneGraph/Media/FormattedText.cs @@ -42,6 +42,11 @@ namespace Perspex.Media var platform = PerspexLocator.Current.GetService(); + if (platform == null) + { + throw new Exception("Could not create FormattedText: IPlatformRenderInterface not registered."); + } + PlatformImpl = platform.CreateFormattedText( text, fontFamilyName, diff --git a/tests/Perspex.LeakTests/ControlTests.cs b/tests/Perspex.LeakTests/ControlTests.cs index 7ad6c19099..7d34613c86 100644 --- a/tests/Perspex.LeakTests/ControlTests.cs +++ b/tests/Perspex.LeakTests/ControlTests.cs @@ -9,6 +9,7 @@ using Perspex.Controls; using Perspex.Controls.Primitives; using Perspex.Controls.Templates; using Perspex.Layout; +using Perspex.UnitTests; using Perspex.VisualTree; using Xunit; using Xunit.Abstractions; @@ -20,282 +21,305 @@ namespace Perspex.LeakTests { public ControlTests(ITestOutputHelper atr) { - TestApp.Initialize(); DotMemoryUnitTestOutput.SetOutputMethod(atr.WriteLine); } [Fact] public void Canvas_Is_Freed() { - Func run = () => + using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var window = new Window + Func run = () => { - Content = new Canvas() - }; + var window = new Window + { + Content = new Canvas() + }; - // Do a layout and make sure that Canvas gets added to visual tree. - LayoutManager.Instance.ExecuteInitialLayoutPass(window); - Assert.IsType(window.Presenter.Child); + // Do a layout and make sure that Canvas gets added to visual tree. + LayoutManager.Instance.ExecuteInitialLayoutPass(window); + Assert.IsType(window.Presenter.Child); - // Clear the content and ensure the Canvas is removed. - window.Content = null; - LayoutManager.Instance.ExecuteLayoutPass(); - Assert.Null(window.Presenter.Child); + // Clear the content and ensure the Canvas is removed. + window.Content = null; + LayoutManager.Instance.ExecuteLayoutPass(); + Assert.Null(window.Presenter.Child); - return window; - }; + return window; + }; - var result = run(); + var result = run(); - dotMemory.Check(memory => - Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); + dotMemory.Check(memory => + Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); + } } [Fact] public void Named_Canvas_Is_Freed() { - Func run = () => + using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var window = new Window + Func run = () => { - Content = new Canvas + var window = new Window { - Name = "foo" - } - }; + Content = new Canvas + { + Name = "foo" + } + }; - // Do a layout and make sure that Canvas gets added to visual tree. - LayoutManager.Instance.ExecuteInitialLayoutPass(window); - Assert.IsType(window.Find("foo")); - Assert.IsType(window.Presenter.Child); + // Do a layout and make sure that Canvas gets added to visual tree. + LayoutManager.Instance.ExecuteInitialLayoutPass(window); + Assert.IsType(window.Find("foo")); + Assert.IsType(window.Presenter.Child); - // Clear the content and ensure the Canvas is removed. - window.Content = null; - LayoutManager.Instance.ExecuteLayoutPass(); - Assert.Null(window.Presenter.Child); + // Clear the content and ensure the Canvas is removed. + window.Content = null; + LayoutManager.Instance.ExecuteLayoutPass(); + Assert.Null(window.Presenter.Child); - return window; - }; + return window; + }; - var result = run(); + var result = run(); - dotMemory.Check(memory => - Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); + dotMemory.Check(memory => + Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); + } } [Fact] public void Templated_Child_Is_Freed_When_Template_Cleared() { - Func run = () => + using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var window = new Window + Func run = () => { - Content = new TestTemplatedControl() - }; + var window = new Window + { + Content = new TestTemplatedControl() + }; - // Do a layout and make sure that the control gets added to visual tree and its - // template applied. - LayoutManager.Instance.ExecuteInitialLayoutPass(window); - Assert.IsType(window.Presenter.Child); - Assert.IsType(window.Presenter.Child.GetVisualChildren().SingleOrDefault()); + // Do a layout and make sure that the control gets added to visual tree and its + // template applied. + LayoutManager.Instance.ExecuteInitialLayoutPass(window); + Assert.IsType(window.Presenter.Child); + Assert.IsType(window.Presenter.Child.GetVisualChildren().SingleOrDefault()); - // Clear the template and ensure the control template gets removed - ((TestTemplatedControl)window.Content).Template = null; - LayoutManager.Instance.ExecuteLayoutPass(); - Assert.Equal(0, window.Presenter.Child.GetVisualChildren().Count()); + // Clear the template and ensure the control template gets removed + ((TestTemplatedControl)window.Content).Template = null; + LayoutManager.Instance.ExecuteLayoutPass(); + Assert.Equal(0, window.Presenter.Child.GetVisualChildren().Count()); - return window; - }; + return window; + }; - var result = run(); + var result = run(); - dotMemory.Check(memory => - Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); + dotMemory.Check(memory => + Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); + } } [Fact] public void ScrollViewer_With_Content_Is_Freed() { - Func run = () => + using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var window = new Window + Func run = () => { - Content = new ScrollViewer + var window = new Window { - Content = new Canvas() - } + Content = new ScrollViewer + { + Content = new Canvas() + } + }; + + // Do a layout and make sure that ScrollViewer gets added to visual tree and its + // template applied. + LayoutManager.Instance.ExecuteInitialLayoutPass(window); + Assert.IsType(window.Presenter.Child); + Assert.IsType(((ScrollViewer)window.Presenter.Child).Presenter.Child); + + // Clear the content and ensure the ScrollViewer is removed. + window.Content = null; + LayoutManager.Instance.ExecuteLayoutPass(); + Assert.Null(window.Presenter.Child); + + return window; }; - // Do a layout and make sure that ScrollViewer gets added to visual tree and its - // template applied. - LayoutManager.Instance.ExecuteInitialLayoutPass(window); - Assert.IsType(window.Presenter.Child); - Assert.IsType(((ScrollViewer)window.Presenter.Child).Presenter.Child); - - // Clear the content and ensure the ScrollViewer is removed. - window.Content = null; - LayoutManager.Instance.ExecuteLayoutPass(); - Assert.Null(window.Presenter.Child); + var result = run(); - return window; - }; - - var result = run(); - - dotMemory.Check(memory => - Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); - dotMemory.Check(memory => - Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); + dotMemory.Check(memory => + Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); + dotMemory.Check(memory => + Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); + } } [Fact] public void TextBox_Is_Freed() { - Func run = () => + using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var window = new Window + Func run = () => { - Content = new TextBox() - }; + var window = new Window + { + Content = new TextBox() + }; - // Do a layout and make sure that TextBox gets added to visual tree and its - // template applied. - LayoutManager.Instance.ExecuteInitialLayoutPass(window); - Assert.IsType(window.Presenter.Child); - Assert.NotEqual(0, window.Presenter.Child.GetVisualChildren().Count()); + // Do a layout and make sure that TextBox gets added to visual tree and its + // template applied. + LayoutManager.Instance.ExecuteInitialLayoutPass(window); + Assert.IsType(window.Presenter.Child); + Assert.NotEqual(0, window.Presenter.Child.GetVisualChildren().Count()); - // Clear the content and ensure the TextBox is removed. - window.Content = null; - LayoutManager.Instance.ExecuteLayoutPass(); - Assert.Null(window.Presenter.Child); + // Clear the content and ensure the TextBox is removed. + window.Content = null; + LayoutManager.Instance.ExecuteLayoutPass(); + Assert.Null(window.Presenter.Child); - return window; - }; + return window; + }; - var result = run(); + var result = run(); - dotMemory.Check(memory => - Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); + dotMemory.Check(memory => + Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); + } } [Fact] public void TextBox_With_Xaml_Binding_Is_Freed() { - Func run = () => + using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var window = new Window + Func run = () => { - DataContext = new Node { Name = "foo" }, - Content = new TextBox() - }; + var window = new Window + { + DataContext = new Node { Name = "foo" }, + Content = new TextBox() + }; - var binding = new Perspex.Markup.Xaml.Data.Binding - { - Path = "Name" - }; + var binding = new Perspex.Markup.Xaml.Data.Binding + { + Path = "Name" + }; - var textBox = (TextBox)window.Content; - textBox.Bind(TextBox.TextProperty, binding); + var textBox = (TextBox)window.Content; + textBox.Bind(TextBox.TextProperty, binding); - // Do a layout and make sure that TextBox gets added to visual tree and its - // Text property set. - LayoutManager.Instance.ExecuteInitialLayoutPass(window); - Assert.IsType(window.Presenter.Child); - Assert.Equal("foo", ((TextBox)window.Presenter.Child).Text); + // Do a layout and make sure that TextBox gets added to visual tree and its + // Text property set. + LayoutManager.Instance.ExecuteInitialLayoutPass(window); + Assert.IsType(window.Presenter.Child); + Assert.Equal("foo", ((TextBox)window.Presenter.Child).Text); - // Clear the content and DataContext and ensure the TextBox is removed. - window.Content = null; - window.DataContext = null; - LayoutManager.Instance.ExecuteLayoutPass(); - Assert.Null(window.Presenter.Child); + // Clear the content and DataContext and ensure the TextBox is removed. + window.Content = null; + window.DataContext = null; + LayoutManager.Instance.ExecuteLayoutPass(); + Assert.Null(window.Presenter.Child); - return window; - }; + return window; + }; - var result = run(); + var result = run(); - dotMemory.Check(memory => - Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); - dotMemory.Check(memory => - Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); + dotMemory.Check(memory => + Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); + dotMemory.Check(memory => + Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); + } } [Fact] public void TextBox_ScrollViewer_Is_Freed_When_Template_Cleared() { - Func run = () => + using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var window = new Window + Func run = () => { - Content = new TextBox() - }; + var window = new Window + { + Content = new TextBox() + }; - // Do a layout and make sure that TextBox gets added to visual tree and its - // template applied. - LayoutManager.Instance.ExecuteInitialLayoutPass(window); - Assert.IsType(window.Presenter.Child); - Assert.NotEqual(0, window.Presenter.Child.GetVisualChildren().Count()); + // Do a layout and make sure that TextBox gets added to visual tree and its + // template applied. + LayoutManager.Instance.ExecuteInitialLayoutPass(window); + Assert.IsType(window.Presenter.Child); + Assert.NotEqual(0, window.Presenter.Child.GetVisualChildren().Count()); - // Clear the template and ensure the TextBox template gets removed - ((TextBox)window.Content).Template = null; - LayoutManager.Instance.ExecuteLayoutPass(); - Assert.Equal(0, window.Presenter.Child.GetVisualChildren().Count()); + // Clear the template and ensure the TextBox template gets removed + ((TextBox)window.Content).Template = null; + LayoutManager.Instance.ExecuteLayoutPass(); + Assert.Equal(0, window.Presenter.Child.GetVisualChildren().Count()); - return window; - }; + return window; + }; - var result = run(); + var result = run(); - dotMemory.Check(memory => - Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); + dotMemory.Check(memory => + Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); + } } [Fact] public void TreeView_Is_Freed() { - Func run = () => + using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var nodes = new[] + Func run = () => { - new Node + var nodes = new[] { - Children = new[] { new Node() }, - } - }; + new Node + { + Children = new[] { new Node() }, + } + }; - TreeView target; + TreeView target; - var window = new Window - { - Content = target = new TreeView + var window = new Window { - DataTemplates = new DataTemplates + Content = target = new TreeView { - new FuncTreeDataTemplate( - x => new TextBlock { Text = x.Name }, - x => x.Children) - }, - Items = nodes - } + DataTemplates = new DataTemplates + { + new FuncTreeDataTemplate( + x => new TextBlock { Text = x.Name }, + x => x.Children) + }, + Items = nodes + } + }; + + // Do a layout and make sure that TreeViewItems get realized. + LayoutManager.Instance.ExecuteInitialLayoutPass(window); + Assert.Equal(1, target.ItemContainerGenerator.Containers.Count()); + + // Clear the content and ensure the TreeView is removed. + window.Content = null; + LayoutManager.Instance.ExecuteLayoutPass(); + Assert.Null(window.Presenter.Child); + + return window; }; - // Do a layout and make sure that TreeViewItems get realized. - LayoutManager.Instance.ExecuteInitialLayoutPass(window); - Assert.Equal(1, target.ItemContainerGenerator.Containers.Count()); - - // Clear the content and ensure the TreeView is removed. - window.Content = null; - LayoutManager.Instance.ExecuteLayoutPass(); - Assert.Null(window.Presenter.Child); - - return window; - }; + var result = run(); - var result = run(); - - dotMemory.Check(memory => - Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); + dotMemory.Check(memory => + Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); + } } private class TestTemplatedControl : TemplatedControl diff --git a/tests/Perspex.LeakTests/Perspex.LeakTests.csproj b/tests/Perspex.LeakTests/Perspex.LeakTests.csproj index b29a2a620e..7330f47fbe 100644 --- a/tests/Perspex.LeakTests/Perspex.LeakTests.csproj +++ b/tests/Perspex.LeakTests/Perspex.LeakTests.csproj @@ -93,7 +93,6 @@ - @@ -152,6 +151,10 @@ {5ccb5571-7c30-4e7d-967d-0e2158ebd91f} Perspex.Controls.UnitTests + + {88060192-33d5-4932-b0f9-8bd2763e857d} + Perspex.UnitTests + diff --git a/tests/Perspex.LeakTests/TestApp.cs b/tests/Perspex.LeakTests/TestApp.cs deleted file mode 100644 index d95d249f7d..0000000000 --- a/tests/Perspex.LeakTests/TestApp.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) The Perspex Project. All rights reserved. -// Licensed under the MIT license. See licence.md file in the project root for full license information. - -using Moq; -using Perspex.Controls.UnitTests; -using Perspex.Layout; -using Perspex.Platform; -using Perspex.Shared.PlatformSupport; -using Perspex.Themes.Default; -using Ploeh.AutoFixture; -using Ploeh.AutoFixture.AutoMoq; - -namespace Perspex.LeakTests -{ - internal class TestApp : Application - { - private TestApp() - { - RegisterServices(); - - var fixture = new Fixture().Customize(new AutoMoqCustomization()); - var windowImpl = new Mock(); - var renderInterface = fixture.Create(); - var threadingInterface = Mock.Of(x => - x.CurrentThreadIsLoopThread == true); - - PerspexLocator.CurrentMutable - .Bind().ToConstant(new AssetLoader()) - .Bind().ToConstant(new LayoutManager()) - .Bind().ToConstant(new PclPlatformWrapper()) - .Bind().ToConstant(renderInterface) - .Bind().ToConstant(threadingInterface) - .Bind().ToConstant(new Mock().Object) - .Bind().ToConstant(new WindowingPlatformMock(() => windowImpl.Object)); - - Styles = new DefaultTheme(); - } - - public static void Initialize() - { - if (Current == null) - { - new TestApp(); - } - } - } -} diff --git a/tests/Perspex.UnitTests/MockWindowingPlatform.cs b/tests/Perspex.UnitTests/MockWindowingPlatform.cs new file mode 100644 index 0000000000..9f1f8c9c6b --- /dev/null +++ b/tests/Perspex.UnitTests/MockWindowingPlatform.cs @@ -0,0 +1,30 @@ +using System; +using Moq; +using Perspex.Platform; + +namespace Perspex.UnitTests +{ + 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(); + } + + public IWindowImpl CreateEmbeddableWindow() + { + throw new NotImplementedException(); + } + + public IPopupImpl CreatePopup() => _popupImpl?.Invoke() ?? Mock.Of(); + } +} \ No newline at end of file diff --git a/tests/Perspex.UnitTests/Perspex.UnitTests.csproj b/tests/Perspex.UnitTests/Perspex.UnitTests.csproj new file mode 100644 index 0000000000..8c1b9bfcdb --- /dev/null +++ b/tests/Perspex.UnitTests/Perspex.UnitTests.csproj @@ -0,0 +1,141 @@ + + + + + Debug + AnyCPU + {88060192-33D5-4932-B0F9-8BD2763E857D} + {88060192-33D5-4932-B0F9-8BD2763E857D} + Library + Properties + Perspex.UnitTests + Perspex.UnitTests + v4.5 + 512 + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\Moq.4.2.1510.2205\lib\net40\Moq.dll + True + + + ..\..\packages\AutoFixture.3.40.0\lib\net40\Ploeh.AutoFixture.dll + True + + + ..\..\packages\AutoFixture.AutoMoq.3.40.0\lib\net40\Ploeh.AutoFixture.AutoMoq.dll + True + + + + + ..\..\packages\Rx-Core.2.2.5\lib\net45\System.Reactive.Core.dll + True + + + ..\..\packages\Rx-Interfaces.2.2.5\lib\net45\System.Reactive.Interfaces.dll + True + + + ..\..\packages\Rx-Linq.2.2.5\lib\net45\System.Reactive.Linq.dll + True + + + ..\..\packages\Rx-PlatformServices.2.2.5\lib\net45\System.Reactive.PlatformServices.dll + True + + + + + + + + + + + + + + + + + {3e53a01a-b331-47f3-b828-4a5717e77a24} + Perspex.Markup.Xaml + + + {6417e941-21bc-467b-a771-0de389353ce6} + Perspex.Markup + + + {d211e587-d8bc-45b9-95a4-f297c8fa5200} + Perspex.Animation + + + {799a7bb5-3c2c-48b6-85a7-406a12c420da} + Perspex.Application + + + {b09b78d8-9b26-48b0-9149-d64a2f120f3f} + Perspex.Base + + + {d2221c82-4a25-4583-9b43-d791e3f6820c} + Perspex.Controls + + + {62024b2d-53eb-4638-b26b-85eeaa54866e} + Perspex.Input + + + {6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b} + Perspex.Interactivity + + + {42472427-4774-4c81-8aff-9f27b8e31721} + Perspex.Layout + + + {eb582467-6abb-43a1-b052-e981ba910e3a} + Perspex.SceneGraph + + + {f1baa01a-f176-4c6a-b39d-5b40bb1b148f} + Perspex.Styling + + + {3e10a5fa-e8da-48b1-ad44-6a5b6cb7750f} + Perspex.Themes.Default + + + + + + + + + + \ No newline at end of file diff --git a/tests/Perspex.UnitTests/Properties/AssemblyInfo.cs b/tests/Perspex.UnitTests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..f357370f9b --- /dev/null +++ b/tests/Perspex.UnitTests/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Perspex.UnitTests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Perspex.UnitTests")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("88060192-33d5-4932-b0f9-8bd2763e857d")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/tests/Perspex.UnitTests/TestServices.cs b/tests/Perspex.UnitTests/TestServices.cs new file mode 100644 index 0000000000..b7dfc52870 --- /dev/null +++ b/tests/Perspex.UnitTests/TestServices.cs @@ -0,0 +1,44 @@ +// Copyright (c) The Perspex Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using Moq; +using Perspex.Layout; +using Perspex.Platform; +using Perspex.Shared.PlatformSupport; +using Perspex.Styling; +using Perspex.Themes.Default; +using Ploeh.AutoFixture; +using Ploeh.AutoFixture.AutoMoq; + +namespace Perspex.UnitTests +{ + public class TestServices + { + private static IFixture s_fixture = new Fixture().Customize(new AutoMoqCustomization()); + + public static readonly TestServices StyledWindow = new TestServices + { + AssetLoader = new AssetLoader(), + LayoutManager = new LayoutManager(), + PlatformWrapper = new PclPlatformWrapper(), + RenderInterface = s_fixture.Create(), + StandardCursorFactory = Mock.Of(), + Styler = new Styler(), + Theme = () => new DefaultTheme(), + ThreadingInterface = Mock.Of(x => x.CurrentThreadIsLoopThread == true), + WindowingPlatform = new MockWindowingPlatform(), + }; + + public IAssetLoader AssetLoader { get; set; } + public ILayoutManager LayoutManager { get; set; } + public IPclPlatformWrapper PlatformWrapper { get; set; } + public IPlatformRenderInterface RenderInterface { get; set; } + public IStandardCursorFactory StandardCursorFactory { get; set; } + public IStyler Styler { get; set; } + public Func Theme { get; set; } + public IPlatformThreadingInterface ThreadingInterface { get; set; } + public IWindowImpl WindowImpl { get; set; } + public IWindowingPlatform WindowingPlatform { get; set; } + } +} diff --git a/tests/Perspex.UnitTests/UnitTestApplication.cs b/tests/Perspex.UnitTests/UnitTestApplication.cs new file mode 100644 index 0000000000..7abf83a8fd --- /dev/null +++ b/tests/Perspex.UnitTests/UnitTestApplication.cs @@ -0,0 +1,43 @@ +// Copyright (c) The Perspex Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using Perspex.Layout; +using Perspex.Platform; +using Perspex.Styling; + +namespace Perspex.UnitTests +{ + public class UnitTestApplication : Application + { + public UnitTestApplication(TestServices services) + { + Services = services; + RegisterServices(); + Styles = services.Theme(); + } + + public TestServices Services { get; } + + public static IDisposable Start(TestServices services = null) + { + var scope = PerspexLocator.EnterScope(); + var app = new UnitTestApplication(services); + return scope; + } + + protected override void RegisterServices() + { + PerspexLocator.CurrentMutable + .Bind().ToConstant(Services.AssetLoader) + .BindToSelf(this) + .Bind().ToConstant(Services.LayoutManager) + .Bind().ToConstant(Services.PlatformWrapper) + .Bind().ToConstant(Services.RenderInterface) + .Bind().ToConstant(Services.ThreadingInterface) + .Bind().ToConstant(Services.StandardCursorFactory) + .Bind().ToConstant(Services.Styler) + .Bind().ToConstant(Services.WindowingPlatform); + } + } +} diff --git a/tests/Perspex.UnitTests/app.config b/tests/Perspex.UnitTests/app.config new file mode 100644 index 0000000000..fa66e8c206 --- /dev/null +++ b/tests/Perspex.UnitTests/app.config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/tests/Perspex.UnitTests/packages.config b/tests/Perspex.UnitTests/packages.config new file mode 100644 index 0000000000..fcf0eab9f9 --- /dev/null +++ b/tests/Perspex.UnitTests/packages.config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file