From 444b806eda719e72c84b8cfdbaaf0dfacbba35fc Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 7 Mar 2016 09:22:25 +0100 Subject: [PATCH 1/9] Changes made my NuGet. --- .../Perspex.Markup.UnitTests.csproj | 1 + tests/Perspex.Markup.UnitTests/app.config | 11 +++++++++++ tests/Perspex.Styling.UnitTests/app.config | 4 ++++ 3 files changed, 16 insertions(+) create mode 100644 tests/Perspex.Markup.UnitTests/app.config diff --git a/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj b/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj index 7d00e0b6b6e..185013373c8 100644 --- a/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj +++ b/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj @@ -102,6 +102,7 @@ + diff --git a/tests/Perspex.Markup.UnitTests/app.config b/tests/Perspex.Markup.UnitTests/app.config new file mode 100644 index 00000000000..fa66e8c206e --- /dev/null +++ b/tests/Perspex.Markup.UnitTests/app.config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/tests/Perspex.Styling.UnitTests/app.config b/tests/Perspex.Styling.UnitTests/app.config index 20eb7befb37..faf47a1e527 100644 --- a/tests/Perspex.Styling.UnitTests/app.config +++ b/tests/Perspex.Styling.UnitTests/app.config @@ -14,6 +14,10 @@ + + + + \ No newline at end of file From 9e10cec9f5421a1c4748a6d350d1f16de9a959ea Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 7 Mar 2016 09:23:35 +0100 Subject: [PATCH 2/9] Removed AutoMoq from Perspex.UnitTests. It was causing problems with binding redirects to Moq and it's not much harder to do it by hand. --- .../Perspex.UnitTests.csproj | 8 ------- tests/Perspex.UnitTests/TestServices.cs | 22 ++++++++++++++----- tests/Perspex.UnitTests/packages.config | 4 +--- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/tests/Perspex.UnitTests/Perspex.UnitTests.csproj b/tests/Perspex.UnitTests/Perspex.UnitTests.csproj index 280f7fbba1c..675485d81b5 100644 --- a/tests/Perspex.UnitTests/Perspex.UnitTests.csproj +++ b/tests/Perspex.UnitTests/Perspex.UnitTests.csproj @@ -36,14 +36,6 @@ ..\..\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 - diff --git a/tests/Perspex.UnitTests/TestServices.cs b/tests/Perspex.UnitTests/TestServices.cs index 7ad4b4b69d0..6383aeb5166 100644 --- a/tests/Perspex.UnitTests/TestServices.cs +++ b/tests/Perspex.UnitTests/TestServices.cs @@ -2,29 +2,25 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; -using System.Reflection; using Moq; using Perspex.Input; using Perspex.Layout; using Perspex.Markup.Xaml; +using Perspex.Media; 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(), + renderInterface: CreateRenderInterfaceMock(), standardCursorFactory: Mock.Of(), styler: new Styler(), theme: () => CreateDefaultTheme(), @@ -122,5 +118,19 @@ private static Styles CreateDefaultTheme() 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())); + } } } diff --git a/tests/Perspex.UnitTests/packages.config b/tests/Perspex.UnitTests/packages.config index fcf0eab9f99..963f44faffd 100644 --- a/tests/Perspex.UnitTests/packages.config +++ b/tests/Perspex.UnitTests/packages.config @@ -1,8 +1,6 @@  - - - + From 1f504b903f9aafeba67012ddf73775a76912f912 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 7 Mar 2016 09:31:04 +0100 Subject: [PATCH 3/9] Added Benchmarks project. --- Perspex.sln | 27 ++++ tests/Perspex.Benchmarks/App.config | 14 ++ .../Controls/Initialization.cs | 35 +++++ .../Perspex.Benchmarks.csproj | 130 ++++++++++++++++++ tests/Perspex.Benchmarks/Program.cs | 29 ++++ .../Properties/AssemblyInfo.cs | 36 +++++ tests/Perspex.Benchmarks/packages.config | 7 + 7 files changed, 278 insertions(+) create mode 100644 tests/Perspex.Benchmarks/App.config create mode 100644 tests/Perspex.Benchmarks/Controls/Initialization.cs create mode 100644 tests/Perspex.Benchmarks/Perspex.Benchmarks.csproj create mode 100644 tests/Perspex.Benchmarks/Program.cs create mode 100644 tests/Perspex.Benchmarks/Properties/AssemblyInfo.cs create mode 100644 tests/Perspex.Benchmarks/packages.config diff --git a/Perspex.sln b/Perspex.sln index f7d69d4db12..bc7e0560af4 100644 --- a/Perspex.sln +++ b/Perspex.sln @@ -140,6 +140,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlCatalog", "samples\C EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.UnitTests", "tests\Perspex.UnitTests\Perspex.UnitTests.csproj", "{88060192-33D5-4932-B0F9-8BD2763E857D}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.Benchmarks", "tests\Perspex.Benchmarks\Perspex.Benchmarks.csproj", "{410AC439-81A1-4EB5-B5E9-6A7FC6B77F4B}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution src\Shared\RenderHelpers\RenderHelpers.projitems*{fb05ac90-89ba-4f2f-a924-f37875fb547c}*SharedItemsImports = 4 @@ -1295,6 +1297,30 @@ Global {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 + {410AC439-81A1-4EB5-B5E9-6A7FC6B77F4B}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU + {410AC439-81A1-4EB5-B5E9-6A7FC6B77F4B}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU + {410AC439-81A1-4EB5-B5E9-6A7FC6B77F4B}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU + {410AC439-81A1-4EB5-B5E9-6A7FC6B77F4B}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU + {410AC439-81A1-4EB5-B5E9-6A7FC6B77F4B}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU + {410AC439-81A1-4EB5-B5E9-6A7FC6B77F4B}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU + {410AC439-81A1-4EB5-B5E9-6A7FC6B77F4B}.AppStore|Any CPU.ActiveCfg = Release|Any CPU + {410AC439-81A1-4EB5-B5E9-6A7FC6B77F4B}.AppStore|Any CPU.Build.0 = Release|Any CPU + {410AC439-81A1-4EB5-B5E9-6A7FC6B77F4B}.AppStore|iPhone.ActiveCfg = Release|Any CPU + {410AC439-81A1-4EB5-B5E9-6A7FC6B77F4B}.AppStore|iPhone.Build.0 = Release|Any CPU + {410AC439-81A1-4EB5-B5E9-6A7FC6B77F4B}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU + {410AC439-81A1-4EB5-B5E9-6A7FC6B77F4B}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU + {410AC439-81A1-4EB5-B5E9-6A7FC6B77F4B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {410AC439-81A1-4EB5-B5E9-6A7FC6B77F4B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {410AC439-81A1-4EB5-B5E9-6A7FC6B77F4B}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {410AC439-81A1-4EB5-B5E9-6A7FC6B77F4B}.Debug|iPhone.Build.0 = Debug|Any CPU + {410AC439-81A1-4EB5-B5E9-6A7FC6B77F4B}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {410AC439-81A1-4EB5-B5E9-6A7FC6B77F4B}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {410AC439-81A1-4EB5-B5E9-6A7FC6B77F4B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {410AC439-81A1-4EB5-B5E9-6A7FC6B77F4B}.Release|Any CPU.Build.0 = Release|Any CPU + {410AC439-81A1-4EB5-B5E9-6A7FC6B77F4B}.Release|iPhone.ActiveCfg = Release|Any CPU + {410AC439-81A1-4EB5-B5E9-6A7FC6B77F4B}.Release|iPhone.Build.0 = Release|Any CPU + {410AC439-81A1-4EB5-B5E9-6A7FC6B77F4B}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {410AC439-81A1-4EB5-B5E9-6A7FC6B77F4B}.Release|iPhoneSimulator.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1339,5 +1365,6 @@ Global {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} + {410AC439-81A1-4EB5-B5E9-6A7FC6B77F4B} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} EndGlobalSection EndGlobal diff --git a/tests/Perspex.Benchmarks/App.config b/tests/Perspex.Benchmarks/App.config new file mode 100644 index 00000000000..121e4693486 --- /dev/null +++ b/tests/Perspex.Benchmarks/App.config @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/tests/Perspex.Benchmarks/Controls/Initialization.cs b/tests/Perspex.Benchmarks/Controls/Initialization.cs new file mode 100644 index 00000000000..2ce4ad9b432 --- /dev/null +++ b/tests/Perspex.Benchmarks/Controls/Initialization.cs @@ -0,0 +1,35 @@ +// 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 System.Linq; +using BenchmarkDotNet.Attributes; +using Perspex.Controls; +using Perspex.Layout; +using Perspex.UnitTests; +using Perspex.VisualTree; + +namespace Perspex.Benchmarks.Controls +{ + public class Initialization + { + [Benchmark] + public void Add_And_Style_TextBox() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var window = new Window + { + Content = new TextBox(), + }; + + LayoutManager.Instance.ExecuteInitialLayoutPass(window); + + if (((TextBox)window.Content).GetVisualChildren().Count() != 1) + { + throw new Exception("Control not styled."); + } + } + } + } +} diff --git a/tests/Perspex.Benchmarks/Perspex.Benchmarks.csproj b/tests/Perspex.Benchmarks/Perspex.Benchmarks.csproj new file mode 100644 index 00000000000..909bc8de825 --- /dev/null +++ b/tests/Perspex.Benchmarks/Perspex.Benchmarks.csproj @@ -0,0 +1,130 @@ + + + + + Debug + AnyCPU + {410AC439-81A1-4EB5-B5E9-6A7FC6B77F4B} + Exe + Properties + Perspex.Benchmarks + Perspex.Benchmarks + v4.5 + 512 + true + + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + false + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\BenchmarkDotNet.0.9.2\lib\net45\BenchmarkDotNet.dll + True + + + + + + ..\..\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 + + + + + + + + + + + + + + + + + + + + + + + {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 + + + {88060192-33d5-4932-b0f9-8bd2763e857d} + Perspex.UnitTests + + + + + \ No newline at end of file diff --git a/tests/Perspex.Benchmarks/Program.cs b/tests/Perspex.Benchmarks/Program.cs new file mode 100644 index 00000000000..04575f8ed05 --- /dev/null +++ b/tests/Perspex.Benchmarks/Program.cs @@ -0,0 +1,29 @@ +// 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.Linq; +using System.Reflection; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Running; +using Perspex.Benchmarks.Controls; + +namespace Perspex.Benchmarks +{ + class Program + { + static void Main(string[] args) + { + // Use reflection for a more maintainable way of creating the benchmark switcher, + // Benchmarks are listed in namespace order first (e.g. BenchmarkDotNet.Samples.CPU, + // BenchmarkDotNet.Samples.IL, etc) then by name, so the output is easy to understand + var benchmarks = Assembly.GetExecutingAssembly().GetTypes() + .Where(t => t.GetMethods(BindingFlags.Instance | BindingFlags.Public) + .Any(m => m.GetCustomAttributes(typeof(BenchmarkAttribute), false).Any())) + .OrderBy(t => t.Namespace) + .ThenBy(t => t.Name) + .ToArray(); + var benchmarkSwitcher = new BenchmarkSwitcher(benchmarks); + benchmarkSwitcher.Run(args); + } + } +} diff --git a/tests/Perspex.Benchmarks/Properties/AssemblyInfo.cs b/tests/Perspex.Benchmarks/Properties/AssemblyInfo.cs new file mode 100644 index 00000000000..e8b6d6d82c8 --- /dev/null +++ b/tests/Perspex.Benchmarks/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.Benchmarks")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Perspex.Benchmarks")] +[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("410ac439-81a1-4eb5-b5e9-6a7fc6b77f4b")] + +// 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.Benchmarks/packages.config b/tests/Perspex.Benchmarks/packages.config new file mode 100644 index 00000000000..b1d27d45627 --- /dev/null +++ b/tests/Perspex.Benchmarks/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file From 072941837b1f15bbf8d127a33554a73d20c146df Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 7 Mar 2016 12:03:53 +0100 Subject: [PATCH 4/9] Refactored styles... To try and make them more performant. --- .../Parsers/SelectorParser.cs | 2 +- src/Perspex.Styling/Perspex.Styling.csproj | 5 + src/Perspex.Styling/Styling/ChildSelector.cs | 50 +++++ .../Styling/DescendentSelector.cs | 74 +++++++ .../Styling/PropertyEqualsSelector.cs | 85 ++++++++ src/Perspex.Styling/Styling/Selector.cs | 135 +++--------- src/Perspex.Styling/Styling/SelectorMatch.cs | 6 +- src/Perspex.Styling/Styling/Selectors.cs | 167 +++------------ src/Perspex.Styling/Styling/Style.cs | 2 +- .../Styling/TemplateSelector.cs | 48 +++++ .../Styling/TypeNameAndClassSelector.cs | 197 ++++++++++++++++++ .../SelectorTests_Child.cs | 16 +- .../SelectorTests_Class.cs | 16 +- .../SelectorTests_Descendent.cs | 18 +- .../SelectorTests_Multiple.cs | 14 +- .../SelectorTests_Name.cs | 22 +- .../SelectorTests_OfType.cs | 6 +- .../SelectorTests_PropertyEquals.cs | 12 +- .../SelectorTests_Template.cs | 32 +-- .../TestSelectors.cs | 10 +- 20 files changed, 612 insertions(+), 305 deletions(-) create mode 100644 src/Perspex.Styling/Styling/ChildSelector.cs create mode 100644 src/Perspex.Styling/Styling/DescendentSelector.cs create mode 100644 src/Perspex.Styling/Styling/PropertyEqualsSelector.cs create mode 100644 src/Perspex.Styling/Styling/TemplateSelector.cs create mode 100644 src/Perspex.Styling/Styling/TypeNameAndClassSelector.cs diff --git a/src/Markup/Perspex.Markup.Xaml/Parsers/SelectorParser.cs b/src/Markup/Perspex.Markup.Xaml/Parsers/SelectorParser.cs index dd1341996ef..a3d741149d1 100644 --- a/src/Markup/Perspex.Markup.Xaml/Parsers/SelectorParser.cs +++ b/src/Markup/Perspex.Markup.Xaml/Parsers/SelectorParser.cs @@ -37,7 +37,7 @@ public SelectorParser(Func typeResolver) public Selector Parse(string s) { var syntax = SelectorGrammar.Selector.Parse(s); - var result = new Selector(); + var result = default(Selector); foreach (var i in syntax) { diff --git a/src/Perspex.Styling/Perspex.Styling.csproj b/src/Perspex.Styling/Perspex.Styling.csproj index 2219312704e..4a6647926af 100644 --- a/src/Perspex.Styling/Perspex.Styling.csproj +++ b/src/Perspex.Styling/Perspex.Styling.csproj @@ -49,6 +49,9 @@ + + + @@ -67,6 +70,8 @@ + + diff --git a/src/Perspex.Styling/Styling/ChildSelector.cs b/src/Perspex.Styling/Styling/ChildSelector.cs new file mode 100644 index 00000000000..a968f68f1d4 --- /dev/null +++ b/src/Perspex.Styling/Styling/ChildSelector.cs @@ -0,0 +1,50 @@ +// 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.LogicalTree; + +namespace Perspex.Styling +{ + internal class ChildSelector : Selector + { + private readonly Selector _parent; + + public ChildSelector(Selector parent) + { + if (parent == null) + { + throw new InvalidOperationException("Child selector must be preceeded by a selector."); + } + + _parent = parent; + } + + /// + public override bool InTemplate => _parent.InTemplate; + + /// + public override Type TargetType => null; + + public override string ToString() + { + return _parent.ToString() + " > "; + } + + protected override SelectorMatch Evaluate(IStyleable control, bool subscribe) + { + var controlParent = ((ILogical)control).LogicalParent; + + if (controlParent != null) + { + return _parent.Match((IStyleable)controlParent, subscribe); + } + else + { + return SelectorMatch.False; + } + } + + protected override Selector MovePrevious() => null; + } +} diff --git a/src/Perspex.Styling/Styling/DescendentSelector.cs b/src/Perspex.Styling/Styling/DescendentSelector.cs new file mode 100644 index 00000000000..0c5f1178c35 --- /dev/null +++ b/src/Perspex.Styling/Styling/DescendentSelector.cs @@ -0,0 +1,74 @@ +// 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 System.Collections.Generic; +using Perspex.LogicalTree; + +namespace Perspex.Styling +{ + internal class DescendentSelector : Selector + { + private readonly Selector _parent; + + public DescendentSelector(Selector parent) + { + if (parent == null) + { + throw new InvalidOperationException("Descendent selector must be preceeded by a selector."); + } + + _parent = parent; + } + + /// + public override bool InTemplate => _parent.InTemplate; + + /// + public override Type TargetType => null; + + public override string ToString() + { + return _parent.ToString() + ' '; + } + + protected override SelectorMatch Evaluate(IStyleable control, bool subscribe) + { + ILogical c = (ILogical)control; + List> descendentMatches = new List>(); + + while (c != null) + { + c = c.LogicalParent; + + if (c is IStyleable) + { + var match = _parent.Match((IStyleable)c, subscribe); + + if (match.ImmediateResult != null) + { + if (match.ImmediateResult == true) + { + return SelectorMatch.True; + } + } + else + { + descendentMatches.Add(match.ObservableResult); + } + } + } + + if (descendentMatches.Count > 0) + { + return new SelectorMatch(StyleActivator.Or(descendentMatches)); + } + else + { + return SelectorMatch.False; + } + } + + protected override Selector MovePrevious() => null; + } +} diff --git a/src/Perspex.Styling/Styling/PropertyEqualsSelector.cs b/src/Perspex.Styling/Styling/PropertyEqualsSelector.cs new file mode 100644 index 00000000000..b4c4c952c42 --- /dev/null +++ b/src/Perspex.Styling/Styling/PropertyEqualsSelector.cs @@ -0,0 +1,85 @@ +// 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 System.Reactive.Linq; +using System.Text; + +namespace Perspex.Styling +{ + /// + /// A selector that matches the common case of a type and/or name followed by a collection of + /// style classes and pseudoclasses. + /// + internal class PropertyEqualsSelector : Selector + { + private readonly Selector _previous; + private readonly PerspexProperty _property; + private readonly object _value; + + public PropertyEqualsSelector(Selector previous, PerspexProperty property, object value) + { + Contract.Requires(property != null); + + _previous = previous; + _property = property; + _value = value; + } + + /// + public override bool InTemplate => _previous?.InTemplate ?? false; + + /// + /// Gets the name of the control to match. + /// + public string Name { get; private set; } + + /// + public override Type TargetType => _previous?.TargetType; + + /// + public override string ToString() + { + var builder = new StringBuilder(); + + if (_previous != null) + { + builder.Append(_previous.ToString()); + } + + builder.Append('['); + + if (_property.IsAttached) + { + builder.Append(_property.OwnerType.Name); + builder.Append('.'); + } + + builder.Append(_property.Name); + builder.Append('='); + builder.Append(_value); + builder.Append(']'); + + return builder.ToString(); + } + + /// + protected override SelectorMatch Evaluate(IStyleable control, bool subscribe) + { + if (!PerspexPropertyRegistry.Instance.IsRegistered(control, _property)) + { + return SelectorMatch.False; + } + else if (subscribe) + { + return new SelectorMatch(control.GetObservable(_property).Select(v => Equals(v, _value))); + } + else + { + return new SelectorMatch(control.GetValue(_property).Equals(_value)); + } + } + + protected override Selector MovePrevious() => _previous; + } +} diff --git a/src/Perspex.Styling/Styling/Selector.cs b/src/Perspex.Styling/Styling/Selector.cs index b47a3e28fd3..0192404e2fa 100644 --- a/src/Perspex.Styling/Styling/Selector.cs +++ b/src/Perspex.Styling/Styling/Selector.cs @@ -9,123 +9,41 @@ namespace Perspex.Styling /// /// A selector in a . /// - /// - /// Selectors represented in markup using a CSS-like syntax, e.g. "Button > .dark" which - /// means "A child of a Button with the 'dark' class applied. The preceeding example would be - /// stored in 3 objects, linked by the property: - /// - /// - /// .dark - /// - /// A selector that selects a control with the 'dark' class applied. - /// - /// - /// - /// > - /// - /// A selector that selects a child of the previous selector. - /// - /// - /// - /// Button - /// - /// A selector that selects a Button type. - /// - /// - /// - /// - public class Selector + public abstract class Selector { - private readonly Func _evaluate; - private readonly Type _targetType; - private readonly bool _inTemplate; - private readonly bool _stopTraversal; - private string _description; - - /// - /// Initializes a new instance of the class. - /// - public Selector() - { - _evaluate = _ => SelectorMatch.True; - } - - /// - /// Initializes a new instance of the class. - /// - /// The previous selector. - /// The evaluator function. - /// The string representation of the selector. - /// The target type, if available. - /// Whether to match in a control template. - /// Whether to stop traversal at this point. - public Selector( - Selector previous, - Func evaluate, - string selectorString, - Type targetType = null, - bool inTemplate = false, - bool stopTraversal = false) - : this() - { - Contract.Requires(previous != null); - - Previous = previous; - _evaluate = evaluate; - SelectorString = selectorString; - _targetType = targetType; - _inTemplate = inTemplate || previous._inTemplate; - _stopTraversal = stopTraversal; - } - /// - /// Gets the previous selector. + /// Gets a value indicating whether either this selector or a previous selector has moved + /// into a template. /// - public Selector Previous - { - get; - } - - /// - /// Gets a string representation of the selector. - /// - public string SelectorString - { - get; - } + public abstract bool InTemplate { get; } /// /// Gets the target type of the selector, if available. /// - public Type TargetType => _targetType ?? MovePrevious()?.TargetType; - - /// - /// Returns the previous selector if traversal is not stopped. - /// - /// The previous selector. - public Selector MovePrevious() - { - return _stopTraversal ? null : Previous; - } + public abstract Type TargetType { get; } /// /// Tries to match the selector with a control. /// /// The control. + /// + /// Whether the match should subscribe to changes in order to track the match over time, + /// or simply return an immediate result. + /// /// A . - public SelectorMatch Match(IStyleable control) + public SelectorMatch Match(IStyleable control, bool subscribe = true) { List> inputs = new List>(); Selector selector = this; while (selector != null) { - if (selector._inTemplate && control.TemplatedParent == null) + if (selector.InTemplate && control.TemplatedParent == null) { return SelectorMatch.False; } - var match = selector._evaluate(control); + var match = selector.Evaluate(control, subscribe); if (match.ImmediateResult == false) { @@ -150,24 +68,19 @@ public SelectorMatch Match(IStyleable control) } /// - /// Gets a string representation of the selector. + /// Evaluates the selector for a match. /// - /// The string representation of the selector. - public override string ToString() - { - if (_description == null) - { - string result = string.Empty; - - if (Previous != null) - { - result = Previous.ToString(); - } - - _description = result + SelectorString; - } + /// The control. + /// + /// Whether the match should subscribe to changes in order to track the match over time, + /// or simply return an immediate result. + /// + /// A . + protected abstract SelectorMatch Evaluate(IStyleable control, bool subscribe); - return _description; - } + /// + /// Moves to the previous selector. + /// + protected abstract Selector MovePrevious(); } } diff --git a/src/Perspex.Styling/Styling/SelectorMatch.cs b/src/Perspex.Styling/Styling/SelectorMatch.cs index e4ee76d0cda..cb7915b100b 100644 --- a/src/Perspex.Styling/Styling/SelectorMatch.cs +++ b/src/Perspex.Styling/Styling/SelectorMatch.cs @@ -6,12 +6,12 @@ namespace Perspex.Styling { /// - /// Holds the result of a match. + /// Holds the result of a match. /// /// /// There are two types of selectors - ones whose match can never change for a particular - /// control (such as ) and ones whose result can - /// change over time (such as . For the first + /// control (such as ) and ones whose result can + /// change over time (such as . For the first /// category of selectors, the value of will be set but for the /// second, will be null and will /// hold an observable which tracks the match. diff --git a/src/Perspex.Styling/Styling/Selectors.cs b/src/Perspex.Styling/Styling/Selectors.cs index 14d9d0d4be0..f10b6d879bc 100644 --- a/src/Perspex.Styling/Styling/Selectors.cs +++ b/src/Perspex.Styling/Styling/Selectors.cs @@ -2,13 +2,6 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.Linq; -using System.Reactive; -using System.Reactive.Linq; -using System.Reflection; -using Perspex.LogicalTree; namespace Perspex.Styling { @@ -24,9 +17,7 @@ public static class Selectors /// The selector. public static Selector Child(this Selector previous) { - Contract.Requires(previous != null); - - return new Selector(previous, x => MatchChild(x, previous), " < ", stopTraversal: true); + return new ChildSelector(previous); } /// @@ -37,10 +28,17 @@ public static Selector Child(this Selector previous) /// The selector. public static Selector Class(this Selector previous, string name) { - Contract.Requires(previous != null); - Contract.Requires(name != null); + var tac = previous as TypeNameAndClassSelector; - return new Selector(previous, x => MatchClass(x, name), name[0] == ':' ? name : '.' + name); + if (tac != null) + { + tac.Classes.Add(name); + return tac; + } + else + { + return TypeNameAndClassSelector.ForClass(previous, name); + } } /// @@ -50,9 +48,7 @@ public static Selector Class(this Selector previous, string name) /// The selector. public static Selector Descendent(this Selector previous) { - Contract.Requires(previous != null); - - return new Selector(previous, x => MatchDescendent(x, previous), " ", stopTraversal: true); + return new DescendentSelector(previous); } /// @@ -63,9 +59,7 @@ public static Selector Descendent(this Selector previous) /// The selector. public static Selector Is(this Selector previous, Type type) { - Contract.Requires(previous != null); - - return new Selector(previous, x => MatchIs(x, type), $":is({type.Name})", type); + return TypeNameAndClassSelector.Is(previous, type); } /// @@ -87,9 +81,17 @@ public static Selector Is(this Selector previous) where T : IStyleable /// The selector. public static Selector Name(this Selector previous, string name) { - Contract.Requires(previous != null); + var tac = previous as TypeNameAndClassSelector; - return new Selector(previous, x => MatchName(x, name), '#' + name); + if (tac != null) + { + tac.Name = name; + return tac; + } + else + { + return TypeNameAndClassSelector.ForName(previous, name); + } } /// @@ -100,9 +102,7 @@ public static Selector Name(this Selector previous, string name) /// The selector. public static Selector OfType(this Selector previous, Type type) { - Contract.Requires(previous != null); - - return new Selector(previous, x => MatchOfType(x, type), type.Name, type); + return TypeNameAndClassSelector.OfType(previous, type); } /// @@ -126,10 +126,7 @@ public static Selector OfType(this Selector previous) where T : IStyleable /// The selector. public static Selector PropertyEquals(this Selector previous, PerspexProperty property, object value) { - Contract.Requires(previous != null); - Contract.Requires(property != null); - - return new Selector(previous, x => MatchPropertyEquals(x, property, value), $"[{property.Name}={value}]"); + return new PropertyEqualsSelector(previous, property, value); } /// @@ -141,10 +138,7 @@ public static Selector PropertyEquals(this Selector previous, PerspexProperty /// The selector. public static Selector PropertyEquals(this Selector previous, PerspexProperty property, object value) { - Contract.Requires(previous != null); - Contract.Requires(property != null); - - return new Selector(previous, x => MatchPropertyEquals(x, property, value), $"[{property.Name}={value}]"); + return new PropertyEqualsSelector(previous, property, value); } /// @@ -154,114 +148,7 @@ public static Selector PropertyEquals(this Selector previous, PerspexProperty pr /// The selector. public static Selector Template(this Selector previous) { - Contract.Requires(previous != null); - - return new Selector( - previous, - x => MatchTemplate(x, previous), - " /template/ ", - inTemplate: true, - stopTraversal: true); - } - - private static SelectorMatch MatchChild(IStyleable control, Selector previous) - { - var parent = ((ILogical)control).LogicalParent; - - if (parent != null) - { - return previous.Match((IStyleable)parent); - } - else - { - return SelectorMatch.False; - } - } - - private static SelectorMatch MatchClass(IStyleable control, string name) - { - var observable = Observable.FromEventPattern< - NotifyCollectionChangedEventHandler, - NotifyCollectionChangedEventArgs>( - x => control.Classes.CollectionChanged += x, - x => control.Classes.CollectionChanged -= x) - .Select(_ => Unit.Default) - .StartWith(Unit.Default) - .Select(_ => control.Classes.Contains(name)); - - return new SelectorMatch(observable); - } - - private static SelectorMatch MatchDescendent(IStyleable control, Selector previous) - { - ILogical c = (ILogical)control; - List> descendentMatches = new List>(); - - while (c != null) - { - c = c.LogicalParent; - - if (c is IStyleable) - { - var match = previous.Match((IStyleable)c); - - if (match.ImmediateResult != null) - { - if (match.ImmediateResult == true) - { - return SelectorMatch.True; - } - } - else - { - descendentMatches.Add(match.ObservableResult); - } - } - } - - return new SelectorMatch(StyleActivator.Or(descendentMatches)); - } - - private static SelectorMatch MatchIs(IStyleable control, Type type) - { - var controlType = control.StyleKey ?? control.GetType(); - return new SelectorMatch(type.GetTypeInfo().IsAssignableFrom(controlType.GetTypeInfo())); - } - - private static SelectorMatch MatchName(IStyleable control, string name) - { - return new SelectorMatch(control.Name == name); - } - - private static SelectorMatch MatchOfType(IStyleable control, Type type) - { - var controlType = control.StyleKey ?? control.GetType(); - return new SelectorMatch(controlType == type); - } - - private static SelectorMatch MatchPropertyEquals(IStyleable x, PerspexProperty property, object value) - { - if (!PerspexPropertyRegistry.Instance.IsRegistered(x, property)) - { - return SelectorMatch.False; - } - else - { - return new SelectorMatch(x.GetObservable(property).Select(v => Equals(v, value))); - } - } - - private static SelectorMatch MatchTemplate(IStyleable control, Selector previous) - { - IStyleable templatedParent = control.TemplatedParent as IStyleable; - - if (templatedParent == null) - { - throw new InvalidOperationException( - "Cannot call Template selector on control with null TemplatedParent."); - } - - return previous.Match(templatedParent); + return new TemplateSelector(previous); } } } diff --git a/src/Perspex.Styling/Styling/Style.cs b/src/Perspex.Styling/Styling/Style.cs index 58fd2fa7219..7f0cb00e4b7 100644 --- a/src/Perspex.Styling/Styling/Style.cs +++ b/src/Perspex.Styling/Styling/Style.cs @@ -29,7 +29,7 @@ public Style() /// The style selector. public Style(Func selector) { - Selector = selector(new Selector()); + Selector = selector(null); } /// diff --git a/src/Perspex.Styling/Styling/TemplateSelector.cs b/src/Perspex.Styling/Styling/TemplateSelector.cs new file mode 100644 index 00000000000..effbabdc951 --- /dev/null +++ b/src/Perspex.Styling/Styling/TemplateSelector.cs @@ -0,0 +1,48 @@ +// 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; + +namespace Perspex.Styling +{ + internal class TemplateSelector : Selector + { + private readonly Selector _parent; + + public TemplateSelector(Selector parent) + { + if (parent == null) + { + throw new InvalidOperationException("Template selector must be preceeded by a selector."); + } + + _parent = parent; + } + + /// + public override bool InTemplate => true; + + /// + public override Type TargetType => null; + + public override string ToString() + { + return _parent.ToString() + " /template/ "; + } + + protected override SelectorMatch Evaluate(IStyleable control, bool subscribe) + { + IStyleable templatedParent = control.TemplatedParent as IStyleable; + + if (templatedParent == null) + { + throw new InvalidOperationException( + "Cannot call Template selector on control with null TemplatedParent."); + } + + return _parent.Match(templatedParent, subscribe) ?? SelectorMatch.True; + } + + protected override Selector MovePrevious() => null; + } +} diff --git a/src/Perspex.Styling/Styling/TypeNameAndClassSelector.cs b/src/Perspex.Styling/Styling/TypeNameAndClassSelector.cs new file mode 100644 index 00000000000..b9121b19270 --- /dev/null +++ b/src/Perspex.Styling/Styling/TypeNameAndClassSelector.cs @@ -0,0 +1,197 @@ +// 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 System.Collections.Generic; +using System.Collections.Specialized; +using System.Reactive; +using System.Reactive.Linq; +using System.Reflection; +using System.Text; + +namespace Perspex.Styling +{ + /// + /// A selector that matches the common case of a type and/or name followed by a collection of + /// style classes and pseudoclasses. + /// + internal class TypeNameAndClassSelector : Selector + { + private readonly Selector _previous; + private Type _targetType; + private Lazy> _classes = new Lazy>(() => new List()); + + public static TypeNameAndClassSelector OfType(Selector previous, Type targetType) + { + var result = new TypeNameAndClassSelector(previous); + result._targetType = targetType; + result.IsConcreteType = true; + return result; + } + + public static TypeNameAndClassSelector Is(Selector previous, Type targetType) + { + var result = new TypeNameAndClassSelector(previous); + result._targetType = targetType; + result.IsConcreteType = false; + return result; + } + + public static TypeNameAndClassSelector ForName(Selector previous, string name) + { + var result = new TypeNameAndClassSelector(previous); + result.Name = name; + return result; + } + + public static TypeNameAndClassSelector ForClass(Selector previous, string className) + { + var result = new TypeNameAndClassSelector(previous); + result.Classes.Add(className); + return result; + } + + protected TypeNameAndClassSelector(Selector previous) + { + _previous = previous; + } + + /// + public override bool InTemplate => _previous?.InTemplate ?? false; + + /// + /// Gets the name of the control to match. + /// + public string Name { get; set; } + + /// + public override Type TargetType => _targetType ?? _previous?.TargetType; + + /// + /// Whether the selector matches the concrete or any object which + /// implements . + /// + public bool IsConcreteType { get; private set; } + + /// + /// The style classes which the selector matches. + /// + public IList Classes => _classes.Value; + + /// + public override string ToString() + { + var builder = new StringBuilder(); + + if (_previous != null) + { + builder.Append(_previous.ToString()); + } + + if (TargetType != null) + { + if (IsConcreteType) + { + builder.Append(TargetType.Name); + } + else + { + builder.Append(":is("); + builder.Append(TargetType.Name); + builder.Append(")"); + } + } + + if (Name != null) + { + builder.Append('#'); + builder.Append(Name); + } + + if (_classes.IsValueCreated && _classes.Value.Count > 0) + { + foreach (var c in Classes) + { + if (!c.StartsWith(":")) + { + builder.Append('.'); + } + + builder.Append(c); + } + } + + return builder.ToString(); + } + + /// + protected override SelectorMatch Evaluate(IStyleable control, bool subscribe) + { + if (TargetType != null) + { + var controlType = control.StyleKey ?? control.GetType(); + + if (IsConcreteType) + { + if (controlType != TargetType) + { + return SelectorMatch.False; + } + } + else + { + if (!TargetType.GetTypeInfo().IsAssignableFrom(controlType.GetTypeInfo())) + { + return SelectorMatch.False; + } + } + } + + if (Name != null && control.Name != Name) + { + return SelectorMatch.False; + } + + if (_classes.IsValueCreated && _classes.Value.Count > 0) + { + if (subscribe) + { + var observable = Observable.FromEventPattern< + NotifyCollectionChangedEventHandler, + NotifyCollectionChangedEventArgs>( + x => control.Classes.CollectionChanged += x, + x => control.Classes.CollectionChanged -= x) + .Select(_ => Unit.Default) + .StartWith(Unit.Default) + .Select(_ => Matches(control.Classes)); + return new SelectorMatch(observable); + } + else + { + return new SelectorMatch(Matches(control.Classes)); + } + } + else + { + return SelectorMatch.True; + } + } + + protected override Selector MovePrevious() => _previous; + + private bool Matches(IEnumerable classes) + { + int remaining = Classes.Count; + + foreach (var c in classes) + { + if (Classes.Contains(c)) + { + --remaining; + } + } + + return remaining == 0; + } + } +} diff --git a/tests/Perspex.Styling.UnitTests/SelectorTests_Child.cs b/tests/Perspex.Styling.UnitTests/SelectorTests_Child.cs index 0f054aea969..55eff92311f 100644 --- a/tests/Perspex.Styling.UnitTests/SelectorTests_Child.cs +++ b/tests/Perspex.Styling.UnitTests/SelectorTests_Child.cs @@ -24,7 +24,7 @@ public void Child_Matches_Control_When_It_Is_Child_OfType() child.LogicalParent = parent; - var selector = new Selector().OfType().Child().OfType(); + var selector = default(Selector).OfType().Child().OfType(); Assert.True(selector.Match(child).ImmediateResult); } @@ -39,7 +39,7 @@ public void Child_Doesnt_Match_Control_When_It_Is_Grandchild_OfType() parent.LogicalParent = grandparent; child.LogicalParent = parent; - var selector = new Selector().OfType().Child().OfType(); + var selector = default(Selector).OfType().Child().OfType(); Assert.False(selector.Match(child).ImmediateResult); } @@ -52,7 +52,7 @@ public async void Child_Matches_Control_When_It_Is_Child_OfType_And_Class() child.LogicalParent = parent; - var selector = new Selector().OfType().Class("foo").Child().OfType(); + var selector = default(Selector).OfType().Class("foo").Child().OfType(); var activator = selector.Match(child).ObservableResult; var result = new List(); @@ -67,11 +67,19 @@ public async void Child_Matches_Control_When_It_Is_Child_OfType_And_Class() public void Child_Doesnt_Match_Control_When_It_Has_No_Parent() { var control = new TestLogical3(); - var selector = new Selector().OfType().Child().OfType(); + var selector = default(Selector).OfType().Child().OfType(); Assert.False(selector.Match(control).ImmediateResult); } + [Fact] + public void Child_Selector_Should_Have_Correct_String_Representation() + { + var selector = default(Selector).OfType().Child().OfType(); + + Assert.Equal("TestLogical1 > TestLogical3", selector.ToString()); + } + public abstract class TestLogical : ILogical, IStyleable { public TestLogical() diff --git a/tests/Perspex.Styling.UnitTests/SelectorTests_Class.cs b/tests/Perspex.Styling.UnitTests/SelectorTests_Class.cs index 3232279e5e0..9eaf0420345 100644 --- a/tests/Perspex.Styling.UnitTests/SelectorTests_Class.cs +++ b/tests/Perspex.Styling.UnitTests/SelectorTests_Class.cs @@ -16,7 +16,7 @@ public class SelectorTests_Class [Fact] public void Class_Selector_Should_Have_Correct_String_Representation() { - var target = new Selector().Class("foo"); + var target = default(Selector).Class("foo"); Assert.Equal(".foo", target.ToString()); } @@ -24,7 +24,7 @@ public void Class_Selector_Should_Have_Correct_String_Representation() [Fact] public void PesudoClass_Selector_Should_Have_Correct_String_Representation() { - var target = new Selector().Class(":foo"); + var target = default(Selector).Class(":foo"); Assert.Equal(":foo", target.ToString()); } @@ -37,7 +37,7 @@ public async Task Class_Matches_Control_With_Class() Classes = new Classes { "foo" }, }; - var target = new Selector().Class("foo"); + var target = default(Selector).Class("foo"); var activator = target.Match(control).ObservableResult; Assert.True(await activator.Take(1)); @@ -51,7 +51,7 @@ public async Task Class_Doesnt_Match_Control_Without_Class() Classes = new Classes { "bar" }, }; - var target = new Selector().Class("foo"); + var target = default(Selector).Class("foo"); var activator = target.Match(control).ObservableResult; Assert.False(await activator.Take(1)); @@ -66,7 +66,7 @@ public async Task Class_Matches_Control_With_TemplatedParent() TemplatedParent = new Mock().Object, }; - var target = new Selector().Class("foo"); + var target = default(Selector).Class("foo"); var activator = target.Match(control).ObservableResult; Assert.True(await activator.Take(1)); @@ -77,7 +77,7 @@ public async Task Class_Tracks_Additions() { var control = new Control1(); - var target = new Selector().Class("foo"); + var target = default(Selector).Class("foo"); var activator = target.Match(control).ObservableResult; Assert.False(await activator.Take(1)); @@ -93,7 +93,7 @@ public async Task Class_Tracks_Removals() Classes = new Classes { "foo" }, }; - var target = new Selector().Class("foo"); + var target = default(Selector).Class("foo"); var activator = target.Match(control).ObservableResult; Assert.True(await activator.Take(1)); @@ -105,7 +105,7 @@ public async Task Class_Tracks_Removals() public async Task Multiple_Classes() { var control = new Control1(); - var target = new Selector().Class("foo").Class("bar"); + var target = default(Selector).Class("foo").Class("bar"); var activator = target.Match(control).ObservableResult; Assert.False(await activator.Take(1)); diff --git a/tests/Perspex.Styling.UnitTests/SelectorTests_Descendent.cs b/tests/Perspex.Styling.UnitTests/SelectorTests_Descendent.cs index 1fb5a39167a..07bb16ee146 100644 --- a/tests/Perspex.Styling.UnitTests/SelectorTests_Descendent.cs +++ b/tests/Perspex.Styling.UnitTests/SelectorTests_Descendent.cs @@ -24,7 +24,7 @@ public void Descendent_Matches_Control_When_It_Is_Child_OfType() child.LogicalParent = parent; - var selector = new Selector().OfType().Descendent().OfType(); + var selector = default(Selector).OfType().Descendent().OfType(); Assert.True(selector.Match(child).ImmediateResult); } @@ -39,7 +39,7 @@ public void Descendent_Matches_Control_When_It_Is_Descendent_OfType() parent.LogicalParent = grandparent; child.LogicalParent = parent; - var selector = new Selector().OfType().Descendent().OfType(); + var selector = default(Selector).OfType().Descendent().OfType(); Assert.True(selector.Match(child).ImmediateResult); } @@ -55,7 +55,7 @@ public async Task Descendent_Matches_Control_When_It_Is_Descendent_OfType_And_Cl parent.LogicalParent = grandparent; child.LogicalParent = parent; - var selector = new Selector().OfType().Class("foo").Descendent().OfType(); + var selector = default(Selector).OfType().Class("foo").Descendent().OfType(); var activator = selector.Match(child).ObservableResult; Assert.True(await activator.Take(1)); @@ -73,7 +73,7 @@ public async Task Descendent_Doesnt_Match_Control_When_It_Is_Descendent_OfType_B parent.Classes.Add("foo"); child.LogicalParent = parent; - var selector = new Selector().OfType().Class("foo").Descendent().OfType(); + var selector = default(Selector).OfType().Class("foo").Descendent().OfType(); var activator = selector.Match(child).ObservableResult; Assert.False(await activator.Take(1)); @@ -89,7 +89,7 @@ public async Task Descendent_Matches_Any_Ancestor() parent.LogicalParent = grandparent; child.LogicalParent = parent; - var selector = new Selector().OfType().Class("foo").Descendent().OfType(); + var selector = default(Selector).OfType().Class("foo").Descendent().OfType(); var activator = selector.Match(child).ObservableResult; Assert.False(await activator.Take(1)); @@ -103,6 +103,14 @@ public async Task Descendent_Matches_Any_Ancestor() Assert.False(await activator.Take(1)); } + [Fact] + public void Descendent_Selector_Should_Have_Correct_String_Representation() + { + var selector = default(Selector).OfType().Class("foo").Descendent().OfType(); + + Assert.Equal("TestLogical1.foo TestLogical3", selector.ToString()); + } + public abstract class TestLogical : ILogical, IStyleable { public TestLogical() diff --git a/tests/Perspex.Styling.UnitTests/SelectorTests_Multiple.cs b/tests/Perspex.Styling.UnitTests/SelectorTests_Multiple.cs index 9b407a6de3c..14a6b13b968 100644 --- a/tests/Perspex.Styling.UnitTests/SelectorTests_Multiple.cs +++ b/tests/Perspex.Styling.UnitTests/SelectorTests_Multiple.cs @@ -31,7 +31,7 @@ public void Template_Child_Of_Control_With_Two_Classes() control.ApplyTemplate(); - var selector = new Selector() + var selector = default(Selector) .OfType