diff --git a/tests/Avalonia.Base.UnitTests/Styling/ResourceDictionaryTests.cs b/tests/Avalonia.Base.UnitTests/Styling/ResourceDictionaryTests.cs index 86b1b897d4..5527eda6ee 100644 --- a/tests/Avalonia.Base.UnitTests/Styling/ResourceDictionaryTests.cs +++ b/tests/Avalonia.Base.UnitTests/Styling/ResourceDictionaryTests.cs @@ -29,7 +29,7 @@ namespace Avalonia.Base.UnitTests.Styling { "foo", "bar" }, }; - Assert.True(target.TryGetResource("foo", out var result)); + Assert.True(target.TryGetResource("foo", null, out var result)); Assert.Equal("bar", result); } @@ -47,7 +47,7 @@ namespace Avalonia.Base.UnitTests.Styling } }; - Assert.True(target.TryGetResource("foo", out var result)); + Assert.True(target.TryGetResource("foo", null, out var result)); Assert.Equal("bar", result); } @@ -64,7 +64,7 @@ namespace Avalonia.Base.UnitTests.Styling { "foo", "baz" }, }); - Assert.True(target.TryGetResource("foo", out var result)); + Assert.True(target.TryGetResource("foo", null, out var result)); Assert.Equal("bar", result); } @@ -86,7 +86,7 @@ namespace Avalonia.Base.UnitTests.Styling } }; - Assert.True(target.TryGetResource("foo", out var result)); + Assert.True(target.TryGetResource("foo", null, out var result)); Assert.Equal("baz", result); } diff --git a/tests/Avalonia.Base.UnitTests/Styling/StylesTests.cs b/tests/Avalonia.Base.UnitTests/Styling/StylesTests.cs index a6777c9466..c9fc86e205 100644 --- a/tests/Avalonia.Base.UnitTests/Styling/StylesTests.cs +++ b/tests/Avalonia.Base.UnitTests/Styling/StylesTests.cs @@ -108,7 +108,7 @@ namespace Avalonia.Base.UnitTests.Styling } }; - Assert.True(target.TryGetResource("foo", out var result)); + Assert.True(target.TryGetResource("foo", ThemeVariant.Dark, out var result)); Assert.Equal("bar", result); } } diff --git a/tests/Avalonia.Benchmarks/Styling/ResourceBenchmarks.cs b/tests/Avalonia.Benchmarks/Styling/ResourceBenchmarks.cs index 59953f457a..bc47e68bc1 100644 --- a/tests/Avalonia.Benchmarks/Styling/ResourceBenchmarks.cs +++ b/tests/Avalonia.Benchmarks/Styling/ResourceBenchmarks.cs @@ -44,7 +44,7 @@ namespace Avalonia.Benchmarks.Styling return new Styles { preHost, - new TestStyles(50, 3, 5), + new TestStyles(50, 3, 5, 0), postHost }; } diff --git a/tests/Avalonia.Benchmarks/TestStyles.cs b/tests/Avalonia.Benchmarks/TestStyles.cs index be2ad7d072..208f238101 100644 --- a/tests/Avalonia.Benchmarks/TestStyles.cs +++ b/tests/Avalonia.Benchmarks/TestStyles.cs @@ -1,10 +1,11 @@ -using Avalonia.Styling; +using Avalonia.Controls; +using Avalonia.Styling; namespace Avalonia.Benchmarks { public class TestStyles : Styles { - public TestStyles(int childStylesCount, int childInnerStyleCount, int childResourceCount) + public TestStyles(int childStylesCount, int childInnerStyleCount, int childResourceCount, int childThemeResourcesCount) { for (int i = 0; i < childStylesCount; i++) { @@ -18,7 +19,19 @@ namespace Avalonia.Benchmarks { childStyle.Resources.Add($"resource.{i}.{j}.{k}", null); } - + + if (childThemeResourcesCount > 0) + { + ResourceDictionary darkTheme, lightTheme; + childStyle.Resources.ThemeDictionaries[ThemeVariant.Dark] = darkTheme = new ResourceDictionary(); + childStyle.Resources.ThemeDictionaries[ThemeVariant.Light] = lightTheme = new ResourceDictionary(); + for (int k = 0; k < childThemeResourcesCount; k++) + { + darkTheme.Add($"resource.theme.{i}.{j}.{k}", null); + lightTheme.Add($"resource.theme.{i}.{j}.{k}", null); + } + } + childStyles.Add(childStyle); } diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Converters/AvaloniaPropertyConverterTest.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Converters/AvaloniaPropertyConverterTest.cs index 9c2860eb26..d4d188f584 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Converters/AvaloniaPropertyConverterTest.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Converters/AvaloniaPropertyConverterTest.cs @@ -142,6 +142,12 @@ namespace Avalonia.Markup.Xaml.UnitTests.Converters throw new NotImplementedException(); } + public ThemeVariant ThemeVariant + { + get { throw new NotImplementedException(); } + } + public event EventHandler ThemeVariantChanged; + public void DetachStyles() { throw new NotImplementedException(); diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs index f2e1a99006..535b96420a 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs @@ -938,7 +938,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions public void AddOwner(IResourceHost owner) => Owner = owner; public void RemoveOwner(IResourceHost owner) => Owner = null; - public bool TryGetResource(object key, out object value) + public bool TryGetResource(object key, ThemeVariant themeVariant, out object value) { RequestedResources.Add(key); value = key; diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/ThemeDictionariesTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/ThemeDictionariesTests.cs new file mode 100644 index 0000000000..56040c2186 --- /dev/null +++ b/tests/Avalonia.Markup.Xaml.UnitTests/ThemeDictionariesTests.cs @@ -0,0 +1,444 @@ +using Avalonia.Controls; +using Avalonia.Markup.Data; +using Avalonia.Markup.Xaml.MarkupExtensions; +using Avalonia.Media; +using Avalonia.Styling; +using Moq; +using Xunit; + +namespace Avalonia.Markup.Xaml.UnitTests; + +public class ThemeDictionariesTests : XamlTestBase +{ + [Fact] + public void DynamicResource_Updated_When_Control_Theme_Changed() + { + var themeVariantScope = (ThemeVariantScope)AvaloniaRuntimeXamlLoader.Load(@" + + + + + + Black + + + White + + + + + + +"); + var border = (Border)themeVariantScope.Child!; + + Assert.Equal(Colors.White, ((ISolidColorBrush)border.Background)!.Color); + + themeVariantScope.RequestedThemeVariant = ThemeVariant.Dark; + + Assert.Equal(Colors.Black, ((ISolidColorBrush)border.Background)!.Color); + } + + [Fact] + public void DynamicResource_Updated_When_Control_Theme_Changed_No_Xaml() + { + var themeVariantScope = new ThemeVariantScope + { + RequestedThemeVariant = ThemeVariant.Light, + Resources = new ResourceDictionary + { + ThemeDictionaries = + { + [ThemeVariant.Dark] = new ResourceDictionary { ["DemoBackground"] = Brushes.Black }, + [ThemeVariant.Light] = new ResourceDictionary { ["DemoBackground"] = Brushes.White } + } + }, + Child = new Border() + }; + var border = (Border)themeVariantScope.Child!; + border[!Border.BackgroundProperty] = new DynamicResourceExtension("DemoBackground"); + + DelayedBinding.ApplyBindings(border); + + Assert.Equal(Colors.White, ((ISolidColorBrush)border.Background)!.Color); + + themeVariantScope.RequestedThemeVariant = ThemeVariant.Dark; + + Assert.Equal(Colors.Black, ((ISolidColorBrush)border.Background)!.Color); + } + + [Fact] + public void Intermediate_DynamicResource_Updated_When_Control_Theme_Changed() + { + var themeVariantScope = (ThemeVariantScope)AvaloniaRuntimeXamlLoader.Load(@" + + + + + + Black + + + White + + + + + + + +"); + var border = (Border)themeVariantScope.Child!; + + Assert.Equal(Colors.White, ((ISolidColorBrush)border.Background)!.Color); + + themeVariantScope.RequestedThemeVariant = ThemeVariant.Dark; + + Assert.Equal(Colors.Black, ((ISolidColorBrush)border.Background)!.Color); + } + + [Fact] + public void Intermediate_StaticResource_Can_Be_Reached_From_ThemeDictionaries() + { + var themeVariantScope = (ThemeVariantScope)AvaloniaRuntimeXamlLoader.Load(@" + + + + + + Black + + + + White + + + + + + + +"); + var border = (Border)themeVariantScope.Child!; + + Assert.Equal(Colors.White, ((ISolidColorBrush)border.Background)!.Color); + + themeVariantScope.RequestedThemeVariant = ThemeVariant.Dark; + + Assert.Equal(Colors.Black, ((ISolidColorBrush)border.Background)!.Color); + } + + [Fact(Skip = "Not implemented")] + public void StaticResource_Inside_Of_ThemeDictionaries_Should_Use_Same_Theme_Key() + { + var themeVariantScope = (ThemeVariantScope)AvaloniaRuntimeXamlLoader.Load(@" + + + + + + Black + + + White + + + + + + + + + + + + + + + + + + + +"); + var border = (Border)themeVariantScope.Child!; + + Assert.Equal(Colors.White, ((ISolidColorBrush)border.Background)!.Color); + + themeVariantScope.RequestedThemeVariant = ThemeVariant.Dark; + + Assert.Equal(Colors.Black, ((ISolidColorBrush)border.Background)!.Color); + } + + [Fact] + public void StaticResource_Outside_Of_Dictionaries_Should_Use_Control_ThemeVariant() + { + using (AvaloniaLocator.EnterScope()) + { + var applicationThemeHost = new Mock(); + applicationThemeHost.SetupGet(h => h.ActualThemeVariant).Returns(ThemeVariant.Dark); + AvaloniaLocator.CurrentMutable.Bind().ToConstant(applicationThemeHost.Object); + + var themeVariantScope = (ThemeVariantScope)AvaloniaRuntimeXamlLoader.Load(@" + + + + + + Black + + + White + + + + + + +"); + var border = (Border)themeVariantScope.Child!; + + themeVariantScope.RequestedThemeVariant = ThemeVariant.Light; + Assert.Equal(Colors.White, ((ISolidColorBrush)border.Background)!.Color); + } + } + + [Fact] + public void Inner_ThemeDictionaries_Works_Properly() + { + var themeVariantScope = (ThemeVariantScope)AvaloniaRuntimeXamlLoader.Load(@" + + + + + + + Black + + + White + + + + + +"); + var border = (Border)themeVariantScope.Child!; + + Assert.Equal(Colors.White, ((ISolidColorBrush)border.Background)!.Color); + + themeVariantScope.RequestedThemeVariant = ThemeVariant.Dark; + + Assert.Equal(Colors.Black, ((ISolidColorBrush)border.Background)!.Color); + } + + [Fact] + public void Inner_Resource_Can_Reference_Parent_ThemeDictionaries() + { + var themeVariantScope = (ThemeVariantScope)AvaloniaRuntimeXamlLoader.Load(@" + + + + + + Black + + + White + + + + + + + + + + + + +"); + var border = (Border)themeVariantScope.Child!; + + Assert.Equal(Colors.White, ((ISolidColorBrush)border.Background)!.Color); + + themeVariantScope.RequestedThemeVariant = ThemeVariant.Dark; + + Assert.Equal(Colors.Black, ((ISolidColorBrush)border.Background)!.Color); + } + + [Fact] + public void DynamicResource_Can_Access_Resources_Outside_Of_ThemeDictionaries() + { + var themeVariantScope = (ThemeVariantScope)AvaloniaRuntimeXamlLoader.Load(@" + + + + + + + + + + + + Black + White + + + + +"); + var border = (Border)themeVariantScope.Child!; + + Assert.Equal(Colors.White, ((ISolidColorBrush)border.Background)!.Color); + + themeVariantScope.RequestedThemeVariant = ThemeVariant.Dark; + + Assert.Equal(Colors.Black, ((ISolidColorBrush)border.Background)!.Color); + } + + [Fact] + public void Inner_Dictionary_Does_Not_Affect_Parent_Resources() + { + // It might be a nice feature, but neither Avalonia nor UWP supports it. + // Better to expect this limitation with a unit test. + var themeVariantScope = (ThemeVariantScope)AvaloniaRuntimeXamlLoader.Load(@" + + + + Red + + + + + + + + + + Black + + + White + + + + + +"); + var border = (Border)themeVariantScope.Child!; + + Assert.Equal(Colors.Red, ((ISolidColorBrush)border.Background)!.Color); + + themeVariantScope.RequestedThemeVariant = ThemeVariant.Dark; + + Assert.Equal(Colors.Red, ((ISolidColorBrush)border.Background)!.Color); + } + + [Fact] + public void Custom_Theme_Can_Be_Defined_In_ThemeDictionaries() + { + var themeVariantScope = (ThemeVariantScope)AvaloniaRuntimeXamlLoader.Load(@" + + + + + + Black + + + White + + + Pink + + + + + + +"); + var border = (Border)themeVariantScope.Child!; + + themeVariantScope.RequestedThemeVariant = new ThemeVariant("Custom"); + + Assert.Equal(Colors.Pink, ((ISolidColorBrush)border.Background)!.Color); + } + + [Fact] + public void Custom_Theme_Fallbacks_To_Inherit_Theme_DynamicResource() + { + var themeVariantScope = (ThemeVariantScope)AvaloniaRuntimeXamlLoader.Load(@" + + + + + + Black + + + + + +"); + var border = (Border)themeVariantScope.Child!; + + themeVariantScope.RequestedThemeVariant = new ThemeVariant("Custom", ThemeVariant.Dark); + + Assert.Equal(Colors.Black, ((ISolidColorBrush)border.Background)!.Color); + } + + [Fact] + public void Custom_Theme_Fallbacks_To_Inherit_Theme_StaticResource() + { + var themeVariantScope = (ThemeVariantScope)AvaloniaRuntimeXamlLoader.Load(@" + + + + + Custom + Dark + + + + + + + + Black + + + + + + +"); + var border = (Border)themeVariantScope.Child!; + + Assert.Equal(Colors.Black, ((ISolidColorBrush)border.Background)!.Color); + } +} diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs index 0cdc9ee3b1..c8be1c6d19 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs @@ -448,13 +448,13 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml Assert.True(style.Resources.Count > 0); - style.TryGetResource("Brush", out var brush); + style.TryGetResource("Brush", null, out var brush); Assert.NotNull(brush); Assert.IsAssignableFrom(brush); Assert.Equal(Colors.White, ((ISolidColorBrush)brush).Color); - style.TryGetResource("Double", out var d); + style.TryGetResource("Double", null, out var d); Assert.Equal(10.0, d); } diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/MergeResourceIncludeTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/MergeResourceIncludeTests.cs index 92807b2cb9..aa76756069 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/MergeResourceIncludeTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/MergeResourceIncludeTests.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; using System.Runtime.CompilerServices; using System.Xml; using Avalonia.Controls; @@ -128,4 +130,105 @@ public class MergeResourceIncludeTests Assert.Equal(Colors.Black, ((ISolidColorBrush)resources["brush5"]!).Color); Assert.Equal(Colors.White, ((ISolidColorBrush)resources["brush6"]!).Color); } + + [Fact] + public void MergeResourceInclude_Works_With_ThemeDictionaries() + { + var documents = new[] + { + new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Resources1.xaml"), @" + + + + White + Black + + + Black + White + + +"), + new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Resources2.xaml"), @" + + + + Red + Blue + + + Blue + Red + + +"), + new RuntimeXamlLoaderDocument(@" + + + + + +"), + }; + + var objects = AvaloniaRuntimeXamlLoader.LoadGroup(documents); + var resources = Assert.IsType(objects[2]); + Assert.Empty(resources.MergedDictionaries); + + Assert.Equal(Colors.White, Get("brush1", ThemeVariant.Light).Color); + Assert.Equal(Colors.Black, Get("brush2", ThemeVariant.Light).Color); + Assert.Equal(Colors.Black, Get("brush1", ThemeVariant.Dark).Color); + Assert.Equal(Colors.White, Get("brush2", ThemeVariant.Dark).Color); + + Assert.Equal(Colors.Red, Get("brush3", ThemeVariant.Light).Color); + Assert.Equal(Colors.Blue, Get("brush4", ThemeVariant.Light).Color); + Assert.Equal(Colors.Blue, Get("brush3", ThemeVariant.Dark).Color); + Assert.Equal(Colors.Red, Get("brush4", ThemeVariant.Dark).Color); + + ISolidColorBrush Get(string key, ThemeVariant themeVariant) + { + return resources.TryGetResource(key, themeVariant, out var res) ? + (ISolidColorBrush)res! : + throw new KeyNotFoundException(); + } + } + + [Fact] + public void MergeResourceInclude_Fails_With_ThemeDictionaries_Duplicate_Resources() + { + var documents = new[] + { + new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Resources1.xaml"), @" + + + + White + + +"), + new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Resources2.xaml"), @" + + + + Black + + +"), + new RuntimeXamlLoaderDocument(@" + + + + + +"), + }; + + Assert.ThrowsAny(() => AvaloniaRuntimeXamlLoader.LoadGroup(documents)); + } } diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs index d74d85e2bc..6cab83751f 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs @@ -276,6 +276,38 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml } } + [Fact] + public void Closest_Resource_Should_Be_Referenced() + { + using (StyledWindow()) + { + var xaml = @" + + + + + + +"; + var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); + var windowResources = (ResourceDictionary)window.Resources; + var buttonResources = (ResourceDictionary)((Button)window.Content!).Resources; + + var brush = Assert.IsType(windowResources["Red2"]); + Assert.Equal(Colors.Red, brush.Color); + + Assert.False(windowResources.ContainsDeferredKey("Red")); + Assert.False(windowResources.ContainsDeferredKey("Red2")); + + Assert.True(buttonResources.ContainsDeferredKey("Red")); + } + } + private IDisposable StyledWindow(params (string, string)[] assets) { var services = TestServices.StyledWindow.With( diff --git a/tests/Avalonia.UnitTests/TestServices.cs b/tests/Avalonia.UnitTests/TestServices.cs index 40306a4513..339cb1462c 100644 --- a/tests/Avalonia.UnitTests/TestServices.cs +++ b/tests/Avalonia.UnitTests/TestServices.cs @@ -155,7 +155,7 @@ namespace Avalonia.UnitTests private static IStyle CreateSimpleTheme() { - return new SimpleTheme { Mode = SimpleThemeMode.Light }; + return new SimpleTheme(); } private static IPlatformRenderInterface CreateRenderInterfaceMock()