mirror of
https://github.com/AtomUI/AtomUI.git
synced 2026-10-18 14:37:19 +08:00
310 lines
11 KiB
C#
310 lines
11 KiB
C#
using AtomUI.Controls;
|
|
using AtomUI.Desktop.Controls.DesignTokens;
|
|
using AtomUI.Icons.AntDesign;
|
|
using AtomUI.Theme.Resources;
|
|
using Avalonia;
|
|
using Avalonia.Controls;
|
|
using Avalonia.Media;
|
|
using Avalonia.Threading;
|
|
using Avalonia.VisualTree;
|
|
using Shouldly;
|
|
using Xunit;
|
|
using AvaloniaWindow = Avalonia.Controls.Window;
|
|
|
|
namespace AtomUI.Desktop.Controls.Tests.Notifications;
|
|
|
|
public class NotificationCardThemeTests
|
|
{
|
|
static NotificationCardThemeTests()
|
|
{
|
|
AvaloniaTestApp.EnsureInitialized();
|
|
}
|
|
|
|
[Fact]
|
|
public void Notification_Default_Expiration_Matches_Ant_Default_Duration()
|
|
{
|
|
var notification = new Notification("Notification Title", "Notification body");
|
|
|
|
notification.Expiration.ShouldBe(TimeSpan.FromSeconds(4.5));
|
|
}
|
|
|
|
[Fact]
|
|
public void Open_Notification_Without_Type_Does_Not_Create_Default_Icon()
|
|
{
|
|
using var manager = new WindowNotificationManager
|
|
{
|
|
IsMotionEnabled = false
|
|
};
|
|
|
|
ShowInWindow(manager, window =>
|
|
{
|
|
manager.Show(new Notification(
|
|
title: "Notification Title",
|
|
content: "Notification body",
|
|
expiration: TimeSpan.Zero));
|
|
Dispatcher.UIThread.RunJobs();
|
|
window.UpdateLayout();
|
|
|
|
var card = manager.GetVisualDescendants().OfType<NotificationCard>().Single();
|
|
card.Icon.ShouldBeNull();
|
|
|
|
var iconPresenter = GetSemanticIconPresenter(card);
|
|
iconPresenter.IsVisible.ShouldBeFalse();
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public void Typed_Notification_Keeps_Ant_Type_Icon()
|
|
{
|
|
using var manager = new WindowNotificationManager
|
|
{
|
|
IsMotionEnabled = false
|
|
};
|
|
|
|
ShowInWindow(manager, window =>
|
|
{
|
|
manager.Show(new Notification(
|
|
title: "Notification Title",
|
|
content: "Notification body",
|
|
type: NotificationType.Information,
|
|
expiration: TimeSpan.Zero));
|
|
Dispatcher.UIThread.RunJobs();
|
|
window.UpdateLayout();
|
|
|
|
var card = manager.GetVisualDescendants().OfType<NotificationCard>().Single();
|
|
card.Icon.ShouldBeOfType<InfoCircleFilled>();
|
|
|
|
var iconPresenter = GetSemanticIconPresenter(card);
|
|
iconPresenter.IsVisible.ShouldBeTrue();
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public void NotificationType_Change_From_Default_To_Information_Creates_Type_Icon()
|
|
{
|
|
using var manager = new WindowNotificationManager();
|
|
var card = new NotificationCard(manager)
|
|
{
|
|
Title = "Notification Title",
|
|
Content = "Notification body",
|
|
IsMotionEnabled = false
|
|
};
|
|
|
|
ShowInWindow(card, () =>
|
|
{
|
|
card.Icon.ShouldBeNull();
|
|
|
|
card.NotificationType = NotificationType.Information;
|
|
Dispatcher.UIThread.RunJobs();
|
|
|
|
card.Icon.ShouldBeOfType<InfoCircleFilled>();
|
|
|
|
var iconPresenter = GetSemanticIconPresenter(card);
|
|
iconPresenter.IsVisible.ShouldBeTrue();
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public void NotificationType_Change_From_Information_To_Default_Removes_Template_Type_Icon()
|
|
{
|
|
using var manager = new WindowNotificationManager();
|
|
var card = new NotificationCard(manager)
|
|
{
|
|
Title = "Notification Title",
|
|
Content = "Notification body",
|
|
NotificationType = NotificationType.Information,
|
|
IsMotionEnabled = false
|
|
};
|
|
|
|
ShowInWindow(card, () =>
|
|
{
|
|
card.Icon.ShouldBeOfType<InfoCircleFilled>();
|
|
|
|
card.NotificationType = NotificationType.Default;
|
|
Dispatcher.UIThread.RunJobs();
|
|
|
|
card.Icon.ShouldBeNull();
|
|
|
|
var iconPresenter = GetSemanticIconPresenter(card);
|
|
iconPresenter.IsVisible.ShouldBeFalse();
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public void Custom_Icon_Is_Not_Replaced_By_NotificationType_Change()
|
|
{
|
|
using var manager = new WindowNotificationManager();
|
|
var customIcon = new CloseOutlined();
|
|
var card = new NotificationCard(manager)
|
|
{
|
|
Title = "Notification Title",
|
|
Content = "Notification body",
|
|
Icon = customIcon,
|
|
IsMotionEnabled = false
|
|
};
|
|
|
|
ShowInWindow(card, () =>
|
|
{
|
|
card.NotificationType = NotificationType.Success;
|
|
Dispatcher.UIThread.RunJobs();
|
|
|
|
card.Icon.ShouldBeSameAs(customIcon);
|
|
|
|
var iconPresenter = GetSemanticIconPresenter(card);
|
|
iconPresenter.IsVisible.ShouldBeTrue();
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public void Close_Button_Uses_Ant_Notification_Hover_And_Pressed_Visuals()
|
|
{
|
|
using var manager = new WindowNotificationManager();
|
|
var card = new NotificationCard(manager)
|
|
{
|
|
Title = "Notification Title",
|
|
Content = "Notification body",
|
|
IsMotionEnabled = false
|
|
};
|
|
|
|
ShowInWindow(card, () =>
|
|
{
|
|
var expectedSize = GetThemeResource<double>(NotificationCardTokenKind.NotificationCloseButtonSize);
|
|
var closeButton = card.GetVisualDescendants()
|
|
.OfType<IconButton>()
|
|
.Single(item => item.Name == "PART_CloseButton");
|
|
|
|
closeButton.IsMotionEnabled = false;
|
|
closeButton.Width.ShouldBe(expectedSize, 0.5);
|
|
closeButton.Height.ShouldBe(expectedSize, 0.5);
|
|
|
|
BrushShouldHaveSameColor(closeButton.IconBrush, GetThemeColor(SharedTokenKind.ColorIcon));
|
|
|
|
((IPseudoClasses)closeButton.Classes).Set(":pointerover", true);
|
|
Dispatcher.UIThread.RunJobs();
|
|
|
|
BrushShouldHaveSameColor(closeButton.Background, GetThemeColor(SharedTokenKind.ColorBgTextHover));
|
|
BrushShouldHaveSameColor(closeButton.IconBrush, GetThemeColor(SharedTokenKind.ColorIconHover));
|
|
|
|
((IPseudoClasses)closeButton.Classes).Set(":pressed", true);
|
|
Dispatcher.UIThread.RunJobs();
|
|
|
|
BrushShouldHaveSameColor(closeButton.Background, GetThemeColor(SharedTokenKind.ColorBgTextActive));
|
|
BrushShouldHaveSameColor(closeButton.IconBrush, GetThemeColor(SharedTokenKind.ColorIconHover));
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public void Progress_Bar_Gradient_Uses_Primary_Border_Hover_To_Primary()
|
|
{
|
|
var progressBrush = GetThemeResource<IBrush>(NotificationCardTokenKind.NotificationProgressBg);
|
|
var gradient = progressBrush.ShouldBeAssignableTo<IGradientBrush>();
|
|
|
|
gradient.GradientStops.Count.ShouldBe(2);
|
|
gradient.GradientStops[0].Color.ShouldBe(GetThemeColor(SharedTokenKind.ColorPrimaryBorderHover));
|
|
gradient.GradientStops[0].Offset.ShouldBe(0);
|
|
gradient.GradientStops[1].Color.ShouldBe(GetThemeColor(SharedTokenKind.ColorPrimary));
|
|
gradient.GradientStops[1].Offset.ShouldBe(1);
|
|
}
|
|
|
|
[Fact]
|
|
public void Internal_Spacing_Tokens_Are_Reduced_By_One_Third()
|
|
{
|
|
const double ratio = 2d / 3d;
|
|
|
|
var compactHorizontalPadding = GetThemeResource<double>(SharedTokenKind.UniformlyPaddingLG) * ratio;
|
|
var compactVerticalPadding = GetThemeResource<double>(SharedTokenKind.UniformlyPaddingMD) * ratio;
|
|
var compactTitleGap = GetThemeResource<double>(SharedTokenKind.UniformlyMarginXS) * ratio;
|
|
var compactIconGap = GetThemeResource<double>(SharedTokenKind.UniformlyMarginSM) * ratio;
|
|
|
|
var notificationPadding = GetThemeResource<Thickness>(NotificationCardTokenKind.NotificationPadding);
|
|
ThicknessShouldBe(
|
|
notificationPadding,
|
|
new Thickness(compactHorizontalPadding, compactVerticalPadding, compactHorizontalPadding, 0));
|
|
|
|
var contentMargin = GetThemeResource<Thickness>(NotificationCardTokenKind.NotificationContentMargin);
|
|
ThicknessShouldBe(contentMargin, new Thickness(0, 0, 0, compactVerticalPadding));
|
|
|
|
var headerMargin = GetThemeResource<Thickness>(NotificationCardTokenKind.HeaderMargin);
|
|
ThicknessShouldBe(headerMargin, new Thickness(0, 0, 0, compactTitleGap));
|
|
|
|
var iconMargin = GetThemeResource<Thickness>(NotificationCardTokenKind.NotificationIconMargin);
|
|
ThicknessShouldBe(iconMargin, new Thickness(0, 0, compactIconGap, 0));
|
|
}
|
|
|
|
private static void ShowInWindow(Control content, Action assertion)
|
|
{
|
|
ShowInWindow(content, _ => assertion());
|
|
}
|
|
|
|
private static void ShowInWindow(Control content, Action<AvaloniaWindow> assertion)
|
|
{
|
|
var window = new AvaloniaWindow
|
|
{
|
|
Width = 500,
|
|
Height = 300,
|
|
Content = content
|
|
};
|
|
|
|
try
|
|
{
|
|
window.Show();
|
|
Dispatcher.UIThread.RunJobs();
|
|
content.ApplyTemplate();
|
|
window.UpdateLayout();
|
|
assertion(window);
|
|
}
|
|
finally
|
|
{
|
|
window.Close();
|
|
}
|
|
}
|
|
|
|
private static T GetThemeResource<T>(object key)
|
|
{
|
|
var application = Application.Current;
|
|
application.ShouldNotBeNull();
|
|
application!.TryGetResource(key, application.ActualThemeVariant, out var value).ShouldBeTrue();
|
|
value.ShouldBeAssignableTo<T>();
|
|
return (T)value!;
|
|
}
|
|
|
|
private static IconPresenter GetSemanticIconPresenter(NotificationCard card)
|
|
{
|
|
return card.GetVisualDescendants()
|
|
.OfType<IconPresenter>()
|
|
.Single(item => item.Name == "IconPresenter" && ReferenceEquals(item.Icon, card.Icon));
|
|
}
|
|
|
|
private static Color GetThemeColor(object key)
|
|
{
|
|
var application = Application.Current;
|
|
application.ShouldNotBeNull();
|
|
application!.TryGetResource(key, application.ActualThemeVariant, out var value).ShouldBeTrue();
|
|
return ToColor(value);
|
|
}
|
|
|
|
private static void BrushShouldHaveSameColor(IBrush? actual, Color expected)
|
|
{
|
|
actual.ShouldNotBeNull();
|
|
ToColor(actual).ShouldBe(expected);
|
|
}
|
|
|
|
private static void ThicknessShouldBe(Thickness actual, Thickness expected)
|
|
{
|
|
actual.Left.ShouldBe(expected.Left, 0.001);
|
|
actual.Top.ShouldBe(expected.Top, 0.001);
|
|
actual.Right.ShouldBe(expected.Right, 0.001);
|
|
actual.Bottom.ShouldBe(expected.Bottom, 0.001);
|
|
}
|
|
|
|
private static Color ToColor(object? value)
|
|
{
|
|
return value switch
|
|
{
|
|
Color color => color,
|
|
ISolidColorBrush brush => brush.Color,
|
|
_ => throw new ShouldAssertException($"Expected color or solid brush, got {value?.GetType().FullName ?? "<null>"}")
|
|
};
|
|
}
|
|
}
|