mirror of
https://github.com/AtomUI/AtomUI.git
synced 2026-10-18 22:46:38 +08:00
813 lines
28 KiB
C#
813 lines
28 KiB
C#
using System.Runtime.CompilerServices;
|
|
using Avalonia;
|
|
using Avalonia.Controls;
|
|
using Avalonia.Media;
|
|
using Shouldly;
|
|
using Xunit;
|
|
|
|
namespace AtomUI.Desktop.Controls.Tests.FeedbackStack;
|
|
|
|
public class FeedbackStackPanelTests
|
|
{
|
|
[Fact]
|
|
public void Message_Collapses_Only_When_Count_Is_Greater_Than_Threshold()
|
|
{
|
|
var panel = CreatePanel(FeedbackStackMode.Message, NotificationPosition.TopCenter);
|
|
var first = AddItem(panel, 100, 20);
|
|
var second = AddItem(panel, 100, 20);
|
|
var third = AddItem(panel, 100, 20);
|
|
|
|
Layout(panel, 200);
|
|
|
|
panel.IsCollapsed.ShouldBeFalse();
|
|
panel.DesiredSize.Height.ShouldBe(92, 0.01);
|
|
first.IsStackVisible.ShouldBeTrue();
|
|
second.IsStackVisible.ShouldBeTrue();
|
|
third.IsStackVisible.ShouldBeTrue();
|
|
|
|
var latest = AddItem(panel, 120, 20);
|
|
Layout(panel, 200);
|
|
|
|
panel.IsCollapsed.ShouldBeTrue();
|
|
latest.IsStackVisible.ShouldBeTrue();
|
|
third.IsStackVisible.ShouldBeFalse();
|
|
second.IsStackVisible.ShouldBeFalse();
|
|
first.IsStackVisible.ShouldBeFalse();
|
|
panel.CollapsedCardSize.ShouldBe(new Size(120, 20));
|
|
panel.DesiredSize.Height.ShouldBe(36, 0.01);
|
|
}
|
|
|
|
[Fact]
|
|
public void Hover_Expanded_Message_Puts_Newest_Item_At_The_Top_And_Preserves_Gaps()
|
|
{
|
|
var panel = CreatePanel(FeedbackStackMode.Message, NotificationPosition.TopCenter);
|
|
var oldest = AddItem(panel, 80, 20);
|
|
var middle = AddItem(panel, 90, 20);
|
|
var newer = AddItem(panel, 100, 20);
|
|
var latest = AddItem(panel, 110, 20);
|
|
panel.IsStackExpanded = true;
|
|
|
|
Layout(panel, 200);
|
|
|
|
latest.Bounds.Y.ShouldBe(0, 0.01);
|
|
newer.Bounds.Y.ShouldBe(0, 0.01);
|
|
middle.Bounds.Y.ShouldBe(0, 0.01);
|
|
oldest.Bounds.Y.ShouldBe(0, 0.01);
|
|
GetRenderedY(latest).ShouldBe(0, 0.01);
|
|
GetRenderedY(newer).ShouldBe(36, 0.01);
|
|
GetRenderedY(middle).ShouldBe(72, 0.01);
|
|
GetRenderedY(oldest).ShouldBe(108, 0.01);
|
|
panel.DesiredSize.Height.ShouldBe(128, 0.01);
|
|
}
|
|
|
|
[Fact]
|
|
public void Adding_A_Top_Item_Keeps_Existing_Bounds_Anchor_And_Only_Changes_Its_Transform_Target()
|
|
{
|
|
var panel = CreatePanel(FeedbackStackMode.Message, NotificationPosition.TopCenter);
|
|
panel.IsStackEnabled = false;
|
|
var first = AddItem(panel, 100, 20);
|
|
Layout(panel, 200);
|
|
|
|
var originalBounds = first.Bounds;
|
|
GetRenderedY(first).ShouldBe(0, 0.01);
|
|
|
|
var latest = AddItem(panel, 100, 20);
|
|
Layout(panel, 200);
|
|
|
|
first.Bounds.ShouldBe(originalBounds);
|
|
GetRenderedY(latest).ShouldBe(0, 0.01);
|
|
GetRenderedY(first).ShouldBe(36, 0.01);
|
|
}
|
|
|
|
[Fact]
|
|
public void Motion_Disabled_Arranges_Expanded_Items_At_Final_Bounds_Without_Transform_Churn()
|
|
{
|
|
var panel = CreatePanel(FeedbackStackMode.Message, NotificationPosition.TopCenter);
|
|
panel.IsMotionEnabled = false;
|
|
panel.IsStackEnabled = false;
|
|
var oldest = AddItem(panel, 100, 20);
|
|
var latest = AddItem(panel, 100, 20);
|
|
|
|
Layout(panel, 200);
|
|
|
|
latest.Bounds.Y.ShouldBe(0, 0.01);
|
|
oldest.Bounds.Y.ShouldBe(36, 0.01);
|
|
GetTranslateY(latest).ShouldBe(0, 0.01);
|
|
GetTranslateY(oldest).ShouldBe(0, 0.01);
|
|
oldest.RenderTransform.ShouldBeSameAs(latest.RenderTransform);
|
|
}
|
|
|
|
[Fact]
|
|
public void Motion_Disabled_Collapse_Does_Not_Rearrange_Hidden_Items()
|
|
{
|
|
var panel = CreatePanel(FeedbackStackMode.Message, NotificationPosition.TopCenter);
|
|
panel.IsMotionEnabled = false;
|
|
panel.IsStackExpanded = true;
|
|
for (var i = 0; i < 4; i++)
|
|
{
|
|
AddItem(panel, 100, 20);
|
|
}
|
|
|
|
Layout(panel, 200);
|
|
var oldest = panel.Children[0];
|
|
var expandedBounds = oldest.Bounds;
|
|
var expandedTransform = oldest.RenderTransform;
|
|
|
|
panel.IsStackExpanded = false;
|
|
Layout(panel, 200);
|
|
|
|
oldest.IsHitTestVisible.ShouldBeFalse();
|
|
oldest.Bounds.ShouldBe(expandedBounds);
|
|
oldest.RenderTransform.ShouldBeSameAs(expandedTransform);
|
|
}
|
|
|
|
[Fact]
|
|
public void Bottom_Expanded_Items_Share_The_Bottom_Anchor_And_Render_Upward()
|
|
{
|
|
var panel = CreatePanel(FeedbackStackMode.Message, NotificationPosition.BottomCenter);
|
|
panel.IsStackEnabled = false;
|
|
var oldest = AddItem(panel, 100, 20);
|
|
var middle = AddItem(panel, 100, 20);
|
|
var latest = AddItem(panel, 100, 20);
|
|
|
|
Layout(panel, 200);
|
|
|
|
oldest.Bounds.Y.ShouldBe(72, 0.01);
|
|
middle.Bounds.Y.ShouldBe(72, 0.01);
|
|
latest.Bounds.Y.ShouldBe(72, 0.01);
|
|
GetRenderedY(oldest).ShouldBe(0, 0.01);
|
|
GetRenderedY(middle).ShouldBe(36, 0.01);
|
|
GetRenderedY(latest).ShouldBe(72, 0.01);
|
|
}
|
|
|
|
[Fact]
|
|
public void Notification_Collapsed_Stack_Uses_Three_Real_Cards_With_Ant_Scale_And_Offset()
|
|
{
|
|
var panel = CreatePanel(FeedbackStackMode.Notification, NotificationPosition.TopRight);
|
|
var hidden = AddItem(panel, 100, 40);
|
|
var third = AddItem(panel, 100, 40);
|
|
var second = AddItem(panel, 100, 40);
|
|
var latest = AddItem(panel, 100, 40);
|
|
|
|
Layout(panel, 200);
|
|
|
|
hidden.IsStackVisible.ShouldBeFalse();
|
|
third.IsStackVisible.ShouldBeTrue();
|
|
second.IsStackVisible.ShouldBeTrue();
|
|
latest.IsStackVisible.ShouldBeTrue();
|
|
latest.Bounds.ShouldBe(new Rect(100, 0, 100, 40));
|
|
second.Bounds.ShouldBe(new Rect(100, 0, 100, 40));
|
|
third.Bounds.ShouldBe(new Rect(100, 0, 100, 40));
|
|
GetScale(latest).ShouldBe(1, 0.001);
|
|
GetScale(second).ShouldBe(0.94, 0.001);
|
|
GetScale(third).ShouldBe(0.88, 0.001);
|
|
GetTranslateY(second).ShouldBe(8, 0.001);
|
|
GetTranslateY(third).ShouldBe(16, 0.001);
|
|
panel.DesiredSize.Height.ShouldBe(56, 0.01);
|
|
}
|
|
|
|
[Fact]
|
|
public void Notification_Collapse_Projects_Hidden_Cards_Into_The_Clipped_Back_Layer_Before_Fading()
|
|
{
|
|
var panel = CreatePanel(FeedbackStackMode.Notification, NotificationPosition.TopRight);
|
|
var hidden = AddItem(panel, 100, 40);
|
|
AddItem(panel, 100, 40);
|
|
AddItem(panel, 100, 40);
|
|
AddItem(panel, 100, 40);
|
|
panel.IsStackExpanded = true;
|
|
Layout(panel, 200);
|
|
|
|
panel.IsStackExpanded = false;
|
|
Layout(panel, 200);
|
|
|
|
hidden.IsStackVisible.ShouldBeFalse();
|
|
hidden.IsHitTestVisible.ShouldBeFalse();
|
|
GetScale(hidden).ShouldBe(0.88, 0.001);
|
|
hidden.GetValue(FeedbackStackPanel.StackClipProgressProperty).ShouldBe(0.5, 0.001);
|
|
hidden.Clip.ShouldBeOfType<RectangleGeometry>().Rect.Top.ShouldBe(20, 0.01);
|
|
}
|
|
|
|
[Fact]
|
|
public void Notification_Collapsed_Top_Uses_Measured_Heights_To_Align_Layer_Far_Edges()
|
|
{
|
|
var panel = CreatePanel(FeedbackStackMode.Notification, NotificationPosition.TopRight);
|
|
AddItem(panel, 100, 80);
|
|
var third = AddItem(panel, 100, 50);
|
|
var second = AddItem(panel, 100, 70);
|
|
var latest = AddItem(panel, 100, 40);
|
|
|
|
Layout(panel, 200);
|
|
|
|
GetRenderedY(latest).ShouldBe(0, 0.01);
|
|
GetRenderedY(second).ShouldBe(-22, 0.01);
|
|
GetRenderedY(third).ShouldBe(6, 0.01);
|
|
(GetRenderedY(latest) + latest.Bounds.Height).ShouldBe(40, 0.01);
|
|
(GetRenderedY(second) + second.Bounds.Height).ShouldBe(48, 0.01);
|
|
(GetRenderedY(third) + third.Bounds.Height).ShouldBe(56, 0.01);
|
|
panel.DesiredSize.Height.ShouldBe(56, 0.01);
|
|
}
|
|
|
|
[Fact]
|
|
public void Notification_Collapsed_Bottom_Mirrors_Measured_Height_Projection()
|
|
{
|
|
var panel = CreatePanel(FeedbackStackMode.Notification, NotificationPosition.BottomLeft);
|
|
AddItem(panel, 100, 80);
|
|
var third = AddItem(panel, 100, 50);
|
|
var second = AddItem(panel, 100, 70);
|
|
var latest = AddItem(panel, 100, 40);
|
|
|
|
Layout(panel, 200);
|
|
|
|
GetRenderedY(latest).ShouldBe(16, 0.01);
|
|
GetRenderedY(second).ShouldBe(8, 0.01);
|
|
GetRenderedY(third).ShouldBe(0, 0.01);
|
|
panel.DesiredSize.Height.ShouldBe(56, 0.01);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(1, 1, 20)]
|
|
[InlineData(2, 2, 28)]
|
|
[InlineData(3, 3, 36)]
|
|
[InlineData(5, 3, 52)]
|
|
public void Notification_Collapsed_Stack_Uses_Threshold_For_Visible_Layers_And_Extent(
|
|
int threshold,
|
|
int visibleCount,
|
|
double expectedHeight)
|
|
{
|
|
var panel = CreatePanel(FeedbackStackMode.Notification, NotificationPosition.TopRight);
|
|
panel.StackThreshold = threshold;
|
|
for (var i = 0; i <= threshold; i++)
|
|
{
|
|
AddItem(panel, 100, 20);
|
|
}
|
|
|
|
Layout(panel, 200);
|
|
|
|
panel.Children.Cast<TestFeedbackControl>().Count(item => item.IsStackVisible).ShouldBe(visibleCount);
|
|
panel.Children.Cast<TestFeedbackControl>()
|
|
.Where(item => !item.IsStackVisible)
|
|
.ShouldAllBe(item => item.Clip is RectangleGeometry);
|
|
panel.DesiredSize.Height.ShouldBe(expectedHeight, 0.01);
|
|
}
|
|
|
|
[Fact]
|
|
public void Closing_Item_Retains_Its_Last_Projection_While_Siblings_Reflow_Immediately()
|
|
{
|
|
var panel = CreatePanel(FeedbackStackMode.Notification, NotificationPosition.TopRight);
|
|
panel.IsStackEnabled = false;
|
|
var oldest = AddItem(panel, 100, 20);
|
|
var middle = AddItem(panel, 100, 20);
|
|
var latest = AddItem(panel, 100, 20);
|
|
Layout(panel, 200);
|
|
|
|
latest.RequestClose();
|
|
panel.InvalidateMeasure();
|
|
Layout(panel, 200);
|
|
|
|
panel.DesiredSize.Height.ShouldBe(56, 0.01);
|
|
GetRenderedY(latest).ShouldBe(0, 0.01);
|
|
latest.IsHitTestVisible.ShouldBeFalse();
|
|
GetRenderedY(middle).ShouldBe(0, 0.01);
|
|
GetRenderedY(oldest).ShouldBe(36, 0.01);
|
|
}
|
|
|
|
[Fact]
|
|
public void Notification_Collapsed_Back_Layers_Are_Clipped_Toward_The_Placement_Edge()
|
|
{
|
|
var topPanel = CreatePanel(FeedbackStackMode.Notification, NotificationPosition.TopRight);
|
|
for (var i = 0; i < 4; i++)
|
|
{
|
|
AddItem(topPanel, 100, 40);
|
|
}
|
|
Layout(topPanel, 200);
|
|
|
|
var topFrontClip = topPanel.Children[^1].Clip.ShouldBeOfType<RectangleGeometry>();
|
|
topFrontClip.Rect.Top.ShouldBe(-48, 0.01);
|
|
topFrontClip.Rect.Bottom.ShouldBe(88, 0.01);
|
|
var topBackClip = topPanel.Children[^2].Clip.ShouldBeOfType<RectangleGeometry>();
|
|
topBackClip.Rect.Top.ShouldBe(20, 0.01);
|
|
|
|
var bottomPanel = CreatePanel(FeedbackStackMode.Notification, NotificationPosition.BottomRight);
|
|
for (var i = 0; i < 4; i++)
|
|
{
|
|
AddItem(bottomPanel, 100, 40);
|
|
}
|
|
Layout(bottomPanel, 200);
|
|
|
|
var bottomFrontClip = bottomPanel.Children[^1].Clip.ShouldBeOfType<RectangleGeometry>();
|
|
bottomFrontClip.Rect.Top.ShouldBe(-48, 0.01);
|
|
bottomFrontClip.Rect.Bottom.ShouldBe(88, 0.01);
|
|
var bottomBackClip = bottomPanel.Children[^2].Clip.ShouldBeOfType<RectangleGeometry>();
|
|
bottomBackClip.Rect.Bottom.ShouldBe(20, 0.01);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(NotificationPosition.TopRight)]
|
|
[InlineData(NotificationPosition.BottomRight)]
|
|
public void Notification_Motion_Disabled_Reuses_The_Clip_And_Keeps_The_Animation_Base_In_Sync(NotificationPosition position)
|
|
{
|
|
var panel = CreatePanel(FeedbackStackMode.Notification, position);
|
|
panel.IsMotionEnabled = false;
|
|
for (var i = 0; i < 4; i++)
|
|
{
|
|
AddItem(panel, 100, 40);
|
|
}
|
|
|
|
Layout(panel, 200);
|
|
|
|
var backCard = panel.Children[^2];
|
|
var clip = backCard.Clip.ShouldBeOfType<RectangleGeometry>();
|
|
backCard.GetValue(FeedbackStackPanel.StackClipProgressProperty).ShouldBe(0.5);
|
|
(position == NotificationPosition.TopRight ? clip.Rect.Top : clip.Rect.Bottom).ShouldBe(20, 0.01);
|
|
|
|
panel.IsStackExpanded = true;
|
|
Layout(panel, 200);
|
|
backCard.GetValue(FeedbackStackPanel.StackClipProgressProperty).ShouldBe(0);
|
|
clip.Rect.ShouldBe(new Rect(-48, -48, 196, 136));
|
|
panel.IsStackExpanded = false;
|
|
Layout(panel, 200);
|
|
|
|
backCard.GetValue(FeedbackStackPanel.StackClipProgressProperty).ShouldBe(0.5);
|
|
backCard.Clip.ShouldBeSameAs(clip);
|
|
(position == NotificationPosition.TopRight ? clip.Rect.Top : clip.Rect.Bottom).ShouldBe(20, 0.01);
|
|
}
|
|
|
|
[Fact]
|
|
public void Notification_Collapsed_Scale_Origin_Faces_The_Placement_Edge()
|
|
{
|
|
var topPanel = CreatePanel(FeedbackStackMode.Notification, NotificationPosition.TopRight);
|
|
var bottomPanel = CreatePanel(FeedbackStackMode.Notification, NotificationPosition.BottomRight);
|
|
for (var i = 0; i < 4; i++)
|
|
{
|
|
AddItem(topPanel, 100, 40);
|
|
AddItem(bottomPanel, 100, 40);
|
|
}
|
|
|
|
Layout(topPanel, 200);
|
|
Layout(bottomPanel, 200);
|
|
|
|
topPanel.Children[^2].RenderTransformOrigin.ShouldBe(
|
|
new RelativePoint(0.5, 1, RelativeUnit.Relative));
|
|
bottomPanel.Children[^2].RenderTransformOrigin.ShouldBe(
|
|
new RelativePoint(0.5, 0, RelativeUnit.Relative));
|
|
}
|
|
|
|
[Fact]
|
|
public void Bottom_Position_Mirrors_Collapsed_Offsets_And_Keeps_Latest_Nearest_The_Edge()
|
|
{
|
|
var panel = CreatePanel(FeedbackStackMode.Notification, NotificationPosition.BottomLeft);
|
|
var third = AddItem(panel, 100, 40);
|
|
var second = AddItem(panel, 100, 40);
|
|
var latest = AddItem(panel, 100, 40);
|
|
AddItem(panel, 100, 40);
|
|
|
|
Layout(panel, 200);
|
|
|
|
var actualLatest = (TestFeedbackControl)panel.Children[^1];
|
|
actualLatest.Bounds.Y.ShouldBe(16, 0.01);
|
|
latest.Bounds.Y.ShouldBe(16, 0.01);
|
|
second.Bounds.Y.ShouldBe(16, 0.01);
|
|
GetRenderedY(actualLatest).ShouldBe(16, 0.001);
|
|
GetRenderedY(latest).ShouldBe(8, 0.001);
|
|
GetRenderedY(second).ShouldBe(0, 0.001);
|
|
third.IsStackVisible.ShouldBeFalse();
|
|
}
|
|
|
|
[Fact]
|
|
public void Runtime_Stack_Disable_Expands_All_Items_With_Queue_Position_Transforms()
|
|
{
|
|
var panel = CreatePanel(FeedbackStackMode.Notification, NotificationPosition.TopCenter);
|
|
var items = new TestFeedbackControl[4];
|
|
for (var i = 0; i < items.Length; i++)
|
|
{
|
|
items[i] = AddItem(panel, 100, 20);
|
|
}
|
|
Layout(panel, 200);
|
|
GetScale(items[1]).ShouldBe(0.88, 0.001);
|
|
|
|
panel.IsStackEnabled = false;
|
|
Layout(panel, 200);
|
|
|
|
panel.IsCollapsed.ShouldBeFalse();
|
|
panel.DesiredSize.Height.ShouldBe(128, 0.01);
|
|
for (var i = 0; i < items.Length; i++)
|
|
{
|
|
items[i].IsStackVisible.ShouldBeTrue();
|
|
items[i].IsHitTestVisible.ShouldBeTrue();
|
|
GetScale(items[i]).ShouldBe(1, 0.001);
|
|
}
|
|
GetRenderedY(items[3]).ShouldBe(0, 0.01);
|
|
GetRenderedY(items[2]).ShouldBe(36, 0.01);
|
|
GetRenderedY(items[1]).ShouldBe(72, 0.01);
|
|
GetRenderedY(items[0]).ShouldBe(108, 0.01);
|
|
}
|
|
|
|
[Fact]
|
|
public void Repeated_Layout_Reuses_Cached_Layer_Transforms()
|
|
{
|
|
var panel = CreatePanel(FeedbackStackMode.Notification, NotificationPosition.TopCenter);
|
|
for (var i = 0; i < 4; i++)
|
|
{
|
|
AddItem(panel, 100, 20);
|
|
}
|
|
Layout(panel, 200);
|
|
var secondLayer = panel.Children[^2].RenderTransform;
|
|
var thirdLayer = panel.Children[^3].RenderTransform;
|
|
|
|
for (var i = 0; i < 10; i++)
|
|
{
|
|
panel.InvalidateArrange();
|
|
Layout(panel, 200);
|
|
panel.Children[^2].RenderTransform.ShouldBeSameAs(secondLayer);
|
|
panel.Children[^3].RenderTransform.ShouldBeSameAs(thirdLayer);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Repeated_Layout_Reuses_Cached_Back_Layer_Clip_Geometry()
|
|
{
|
|
var panel = CreatePanel(FeedbackStackMode.Notification, NotificationPosition.TopCenter);
|
|
for (var i = 0; i < 4; i++)
|
|
{
|
|
AddItem(panel, 100, 40);
|
|
}
|
|
Layout(panel, 200);
|
|
var secondLayerClip = panel.Children[^2].Clip;
|
|
var thirdLayerClip = panel.Children[^3].Clip;
|
|
|
|
for (var i = 0; i < 10; i++)
|
|
{
|
|
panel.InvalidateArrange();
|
|
Layout(panel, 200);
|
|
panel.Children[^2].Clip.ShouldBeSameAs(secondLayerClip);
|
|
panel.Children[^3].Clip.ShouldBeSameAs(thirdLayerClip);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Repeated_Collapse_And_Expand_Reuses_Both_Transform_Targets()
|
|
{
|
|
var panel = CreatePanel(FeedbackStackMode.Notification, NotificationPosition.TopCenter);
|
|
for (var i = 0; i < 4; i++)
|
|
{
|
|
AddItem(panel, 100, 20);
|
|
}
|
|
|
|
Layout(panel, 200);
|
|
var card = panel.Children[^2];
|
|
var collapsedTransform = card.RenderTransform;
|
|
|
|
panel.IsStackExpanded = true;
|
|
Layout(panel, 200);
|
|
var expandedTransform = card.RenderTransform;
|
|
expandedTransform.ShouldNotBeSameAs(collapsedTransform);
|
|
|
|
panel.IsStackExpanded = false;
|
|
Layout(panel, 200);
|
|
card.RenderTransform.ShouldBeSameAs(collapsedTransform);
|
|
|
|
panel.IsStackExpanded = true;
|
|
Layout(panel, 200);
|
|
card.RenderTransform.ShouldBeSameAs(expandedTransform);
|
|
}
|
|
|
|
[Fact]
|
|
public void Notification_Collapse_Snapshots_Only_Visible_Cards_Whose_Projection_Changes()
|
|
{
|
|
var panel = CreatePanel(FeedbackStackMode.Notification, NotificationPosition.TopRight);
|
|
panel.IsStackExpanded = true;
|
|
var oldest = AddItem(panel, 100, 40);
|
|
var third = AddItem(panel, 100, 40);
|
|
var second = AddItem(panel, 100, 40);
|
|
var latest = AddItem(panel, 100, 40);
|
|
Layout(panel, 200);
|
|
|
|
panel.IsStackExpanded = false;
|
|
Layout(panel, 200);
|
|
|
|
oldest.CollapseSnapshotBeginCount.ShouldBe(0);
|
|
third.CollapseSnapshotBeginCount.ShouldBe(1);
|
|
second.CollapseSnapshotBeginCount.ShouldBe(1);
|
|
latest.CollapseSnapshotBeginCount.ShouldBe(0);
|
|
third.CollapseSnapshotArmCount.ShouldBe(1);
|
|
second.CollapseSnapshotArmCount.ShouldBe(1);
|
|
third.CollapseSnapshotTarget.ShouldBeSameAs(third.RenderTransform);
|
|
second.CollapseSnapshotTarget.ShouldBeSameAs(second.RenderTransform);
|
|
}
|
|
|
|
[Fact]
|
|
public void Initial_Notification_Collapsed_Layout_Does_Not_Create_Content_Snapshots()
|
|
{
|
|
var panel = CreatePanel(FeedbackStackMode.Notification, NotificationPosition.TopRight);
|
|
for (var i = 0; i < 4; i++)
|
|
{
|
|
AddItem(panel, 100, 40);
|
|
}
|
|
|
|
Layout(panel, 200);
|
|
|
|
panel.Children.Cast<TestFeedbackControl>()
|
|
.ShouldAllBe(item => item.CollapseSnapshotBeginCount == 0);
|
|
}
|
|
|
|
[Fact]
|
|
public void Expanding_Notification_Stack_Releases_Active_Content_Snapshots()
|
|
{
|
|
var panel = CreatePanel(FeedbackStackMode.Notification, NotificationPosition.TopRight);
|
|
panel.IsStackExpanded = true;
|
|
for (var i = 0; i < 4; i++)
|
|
{
|
|
AddItem(panel, 100, 40);
|
|
}
|
|
Layout(panel, 200);
|
|
|
|
panel.IsStackExpanded = false;
|
|
Layout(panel, 200);
|
|
var second = (TestFeedbackControl)panel.Children[^2];
|
|
var third = (TestFeedbackControl)panel.Children[^3];
|
|
second.IsCollapseSnapshotActive.ShouldBeTrue();
|
|
third.IsCollapseSnapshotActive.ShouldBeTrue();
|
|
|
|
panel.IsStackExpanded = true;
|
|
Layout(panel, 200);
|
|
|
|
second.IsCollapseSnapshotActive.ShouldBeFalse();
|
|
third.IsCollapseSnapshotActive.ShouldBeFalse();
|
|
second.CollapseSnapshotReleaseCount.ShouldBe(1);
|
|
third.CollapseSnapshotReleaseCount.ShouldBe(1);
|
|
}
|
|
|
|
[Fact]
|
|
public void Disabling_Motion_Releases_Active_Notification_Content_Snapshots()
|
|
{
|
|
var panel = CreatePanel(FeedbackStackMode.Notification, NotificationPosition.TopRight);
|
|
panel.IsStackExpanded = true;
|
|
for (var i = 0; i < 4; i++)
|
|
{
|
|
AddItem(panel, 100, 40);
|
|
}
|
|
Layout(panel, 200);
|
|
|
|
panel.IsStackExpanded = false;
|
|
Layout(panel, 200);
|
|
var second = (TestFeedbackControl)panel.Children[^2];
|
|
second.IsCollapseSnapshotActive.ShouldBeTrue();
|
|
|
|
panel.IsMotionEnabled = false;
|
|
Layout(panel, 200);
|
|
|
|
second.IsCollapseSnapshotActive.ShouldBeFalse();
|
|
second.CollapseSnapshotReleaseCount.ShouldBe(1);
|
|
}
|
|
|
|
[Fact]
|
|
public void Closing_Notification_Releases_Its_Active_Content_Snapshot()
|
|
{
|
|
var panel = CreatePanel(FeedbackStackMode.Notification, NotificationPosition.TopRight);
|
|
panel.IsStackExpanded = true;
|
|
for (var i = 0; i < 4; i++)
|
|
{
|
|
AddItem(panel, 100, 40);
|
|
}
|
|
Layout(panel, 200);
|
|
|
|
panel.IsStackExpanded = false;
|
|
Layout(panel, 200);
|
|
var second = (TestFeedbackControl)panel.Children[^2];
|
|
second.IsCollapseSnapshotActive.ShouldBeTrue();
|
|
|
|
second.RequestClose();
|
|
panel.InvalidateMeasure();
|
|
Layout(panel, 200);
|
|
|
|
second.IsCollapseSnapshotActive.ShouldBeFalse();
|
|
second.CollapseSnapshotReleaseCount.ShouldBe(1);
|
|
}
|
|
|
|
[Fact]
|
|
public void Cached_Transform_Does_Not_Retain_A_Removed_Card()
|
|
{
|
|
var panel = CreatePanel(FeedbackStackMode.Notification, NotificationPosition.TopCenter);
|
|
for (var i = 0; i < 4; i++)
|
|
{
|
|
AddItem(panel, 100, 20);
|
|
}
|
|
Layout(panel, 200);
|
|
|
|
var removedCard = RemoveCachedCard(panel);
|
|
ForceFullCollection();
|
|
|
|
removedCard.IsAlive.ShouldBeFalse();
|
|
GC.KeepAlive(panel);
|
|
}
|
|
|
|
[Fact]
|
|
public void Presenter_Hover_Expands_And_Collapses_The_Whole_Stack()
|
|
{
|
|
var presenter = new FeedbackStackPresenter
|
|
{
|
|
StackMode = FeedbackStackMode.Message,
|
|
IsStackEnabled = true,
|
|
StackThreshold = 3
|
|
};
|
|
var panel = CreatePanel(FeedbackStackMode.Message, NotificationPosition.TopCenter);
|
|
for (var i = 0; i < 4; i++)
|
|
{
|
|
AddItem(panel, 100, 20);
|
|
}
|
|
Layout(panel, 200);
|
|
presenter.AttachPanel(panel);
|
|
presenter.ReportLayoutState(panel.IsCollapsed, panel.CollapsedCardSize, 4);
|
|
var hoverEvents = 0;
|
|
presenter.StackHoverChanged += (_, args) =>
|
|
{
|
|
args.IsStackInteraction.ShouldBeTrue();
|
|
hoverEvents++;
|
|
};
|
|
|
|
presenter.SetPointerOverState(true);
|
|
Layout(panel, 200);
|
|
|
|
panel.IsCollapsed.ShouldBeFalse();
|
|
panel.Children.Cast<TestFeedbackControl>().ShouldAllBe(item => item.IsStackVisible);
|
|
|
|
presenter.SetPointerOverState(false);
|
|
Layout(panel, 200);
|
|
|
|
panel.IsCollapsed.ShouldBeTrue();
|
|
hoverEvents.ShouldBe(2);
|
|
}
|
|
|
|
[Fact]
|
|
public void Presenter_Hover_Remains_A_Stack_Interaction_When_Count_Crosses_Threshold()
|
|
{
|
|
var presenter = new FeedbackStackPresenter
|
|
{
|
|
StackMode = FeedbackStackMode.Notification,
|
|
IsStackEnabled = true,
|
|
StackThreshold = 3
|
|
};
|
|
var panel = CreatePanel(FeedbackStackMode.Notification, NotificationPosition.TopRight);
|
|
for (var i = 0; i < 3; i++)
|
|
{
|
|
AddItem(panel, 100, 20);
|
|
}
|
|
Layout(panel, 200);
|
|
presenter.AttachPanel(panel);
|
|
presenter.ReportLayoutState(panel.IsCollapsed, panel.CollapsedCardSize, 3);
|
|
|
|
FeedbackStackHoverChangedEventArgs? hoverState = null;
|
|
presenter.StackHoverChanged += (_, args) => hoverState = args;
|
|
presenter.SetPointerOverState(true);
|
|
|
|
hoverState.ShouldNotBeNull();
|
|
hoverState!.IsStackInteraction.ShouldBeTrue();
|
|
|
|
AddItem(panel, 100, 20);
|
|
presenter.ReportLayoutState(true, new Size(100, 20), 4);
|
|
Layout(panel, 200);
|
|
|
|
panel.IsCollapsed.ShouldBeFalse();
|
|
panel.Children.Cast<TestFeedbackControl>().ShouldAllBe(item => item.IsStackVisible);
|
|
}
|
|
|
|
[Fact]
|
|
public void Notification_Presenter_Does_Not_Project_Message_Backplate_State()
|
|
{
|
|
var presenter = new FeedbackStackPresenter
|
|
{
|
|
StackMode = FeedbackStackMode.Notification,
|
|
Position = NotificationPosition.TopRight
|
|
};
|
|
|
|
presenter.ReportLayoutState(true, new Size(100, 40), 4);
|
|
|
|
presenter.IsFirstBackplateVisible.ShouldBeFalse();
|
|
presenter.IsSecondBackplateVisible.ShouldBeFalse();
|
|
presenter.FirstBackplateWidth.ShouldBe(0);
|
|
presenter.SecondBackplateWidth.ShouldBe(0);
|
|
presenter.FirstBackplateMargin.ShouldBe(default);
|
|
presenter.SecondBackplateMargin.ShouldBe(default);
|
|
}
|
|
|
|
private static FeedbackStackPanel CreatePanel(FeedbackStackMode mode, NotificationPosition position)
|
|
{
|
|
return new FeedbackStackPanel
|
|
{
|
|
StackMode = mode,
|
|
Position = position,
|
|
IsStackEnabled = true,
|
|
StackThreshold = 3,
|
|
ExpandedGap = 16,
|
|
CollapsedOffset = 8
|
|
};
|
|
}
|
|
|
|
private static TestFeedbackControl AddItem(Panel panel, double width, double height)
|
|
{
|
|
var item = new TestFeedbackControl(new Size(width, height));
|
|
panel.Children.Add(item);
|
|
return item;
|
|
}
|
|
|
|
private static void Layout(Control control, double width)
|
|
{
|
|
control.Measure(new Size(width, double.PositiveInfinity));
|
|
control.Arrange(new Rect(0, 0, width, control.DesiredSize.Height));
|
|
}
|
|
|
|
private static double GetScale(Control control)
|
|
{
|
|
control.RenderTransform.ShouldNotBeNull();
|
|
return control.RenderTransform.Value.M11;
|
|
}
|
|
|
|
private static double GetTranslateY(Control control)
|
|
{
|
|
control.RenderTransform.ShouldNotBeNull();
|
|
return control.RenderTransform.Value.M32;
|
|
}
|
|
|
|
private static double GetRenderedY(Control control)
|
|
{
|
|
return control.Bounds.Y + GetTranslateY(control);
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
|
private static WeakReference RemoveCachedCard(FeedbackStackPanel panel)
|
|
{
|
|
var card = panel.Children[^2];
|
|
var reference = new WeakReference(card);
|
|
panel.Children.Remove(card);
|
|
return reference;
|
|
}
|
|
|
|
private static void ForceFullCollection()
|
|
{
|
|
for (var i = 0; i < 3; i++)
|
|
{
|
|
GC.Collect();
|
|
GC.WaitForPendingFinalizers();
|
|
GC.Collect();
|
|
}
|
|
}
|
|
|
|
private sealed class TestFeedbackControl : Control, IFeedbackStackItem, IFeedbackStackTransitionSnapshotItem
|
|
{
|
|
private readonly Size _size;
|
|
|
|
public TestFeedbackControl(Size size)
|
|
{
|
|
_size = size;
|
|
}
|
|
|
|
public bool IsClosing { get; private set; }
|
|
public bool IsClosed { get; private set; }
|
|
public bool IsProgressVisible => false;
|
|
public bool IsStackVisible { get; set; } = true;
|
|
public int CollapseSnapshotBeginCount { get; private set; }
|
|
public int CollapseSnapshotArmCount { get; private set; }
|
|
public int CollapseSnapshotReleaseCount { get; private set; }
|
|
public bool IsCollapseSnapshotActive { get; private set; }
|
|
public ITransform? CollapseSnapshotTarget { get; private set; }
|
|
|
|
public void RequestClose()
|
|
{
|
|
IsClosing = true;
|
|
}
|
|
|
|
public void UpdateRemaining(TimeSpan remaining)
|
|
{
|
|
}
|
|
|
|
public bool TryBeginStackCollapseSnapshot()
|
|
{
|
|
CollapseSnapshotBeginCount++;
|
|
IsCollapseSnapshotActive = true;
|
|
return true;
|
|
}
|
|
|
|
public void ArmStackCollapseSnapshot(ITransform targetTransform)
|
|
{
|
|
CollapseSnapshotArmCount++;
|
|
CollapseSnapshotTarget = targetTransform;
|
|
}
|
|
|
|
public void ReleaseStackTransitionSnapshot()
|
|
{
|
|
if (!IsCollapseSnapshotActive)
|
|
{
|
|
return;
|
|
}
|
|
|
|
IsCollapseSnapshotActive = false;
|
|
CollapseSnapshotReleaseCount++;
|
|
}
|
|
|
|
protected override Size MeasureOverride(Size availableSize)
|
|
{
|
|
return _size;
|
|
}
|
|
}
|
|
}
|