mirror of
https://github.com/AtomUI/AtomUI.git
synced 2026-10-28 23:08:36 +08:00
388 lines
13 KiB
C#
388 lines
13 KiB
C#
using AtomUI.Animations;
|
|
using AtomUI.Controls;
|
|
using AtomUI.Controls.Primitives;
|
|
using AtomUI.Desktop.Controls.DesignTokens;
|
|
using AtomUI.Icons.AntDesign;
|
|
using Avalonia;
|
|
using Avalonia.Controls;
|
|
using Avalonia.Headless;
|
|
using Avalonia.Controls.Primitives;
|
|
using Avalonia.Input;
|
|
using Avalonia.Media;
|
|
using Avalonia.Threading;
|
|
using Avalonia.VisualTree;
|
|
using Shouldly;
|
|
using Xunit;
|
|
using AvaloniaWindow = Avalonia.Controls.Window;
|
|
|
|
namespace AtomUI.Desktop.Controls.Tests.Steps;
|
|
|
|
public class StepsWaveTests
|
|
{
|
|
static StepsWaveTests()
|
|
{
|
|
AvaloniaTestApp.EnsureInitialized();
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(Desktop.Controls.StepsType.Default)]
|
|
[InlineData(Desktop.Controls.StepsType.Dot)]
|
|
[InlineData(Desktop.Controls.StepsType.Navigation)]
|
|
[InlineData(Desktop.Controls.StepsType.Inline)]
|
|
public void Pointer_Click_Plays_Indicator_Wave_For_Wave_Enabled_Types(Desktop.Controls.StepsType type)
|
|
{
|
|
var steps = CreateSteps(type);
|
|
|
|
ShowInWindow(steps, window =>
|
|
{
|
|
var item = GetItem(steps, 1);
|
|
Click(item, window);
|
|
|
|
GetIndicator(item).IsWavePlaying.ShouldBeTrue();
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public void OutlineDot_Pointer_Click_Requests_Change_Without_Indicator_Wave()
|
|
{
|
|
var steps = CreateSteps(Desktop.Controls.StepsType.OutlineDot);
|
|
int? requested = null;
|
|
steps.CurrentChangeRequested += (_, args) => requested = args.Current;
|
|
|
|
ShowInWindow(steps, window =>
|
|
{
|
|
var item = GetItem(steps, 1);
|
|
Click(item, window);
|
|
|
|
requested.ShouldBe(1);
|
|
GetIndicator(item).IsWavePlaying.ShouldBeFalse();
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public void OutlineDot_Current_Click_Does_Not_Play_Indicator_Wave()
|
|
{
|
|
var steps = CreateSteps(Desktop.Controls.StepsType.OutlineDot);
|
|
var requestCount = 0;
|
|
steps.CurrentChangeRequested += (_, _) => requestCount++;
|
|
|
|
ShowInWindow(steps, window =>
|
|
{
|
|
var item = GetItem(steps, 0);
|
|
Click(item, window);
|
|
|
|
requestCount.ShouldBe(0);
|
|
GetIndicator(item).IsWavePlaying.ShouldBeFalse();
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public void Programmatic_Current_Change_Does_Not_Play_Wave()
|
|
{
|
|
var steps = CreateSteps();
|
|
|
|
ShowInWindow(steps, _ =>
|
|
{
|
|
var item = GetItem(steps, 1);
|
|
steps.Current = 1;
|
|
Dispatcher.UIThread.RunJobs();
|
|
|
|
GetIndicator(item).IsWavePlaying.ShouldBeFalse();
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public void Motion_Disabled_Still_Requests_Change_But_Does_Not_Play_Wave()
|
|
{
|
|
var steps = CreateSteps();
|
|
steps.IsMotionEnabled = false;
|
|
int? requested = null;
|
|
steps.CurrentChangeRequested += (_, args) => requested = args.Current;
|
|
|
|
ShowInWindow(steps, window =>
|
|
{
|
|
var item = GetItem(steps, 1);
|
|
Click(item, window);
|
|
|
|
requested.ShouldBe(1);
|
|
GetIndicator(item).IsWavePlaying.ShouldBeFalse();
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public void WaveSpirit_Disabled_Still_Requests_Change_But_Does_Not_Play_Wave()
|
|
{
|
|
var steps = CreateSteps();
|
|
int? requested = null;
|
|
steps.CurrentChangeRequested += (_, args) => requested = args.Current;
|
|
|
|
ShowInWindow(steps, window =>
|
|
{
|
|
var item = GetItem(steps, 1);
|
|
var indicator = GetIndicator(item);
|
|
indicator.IsWaveSpiritEnabled = false;
|
|
|
|
Click(item, window);
|
|
|
|
requested.ShouldBe(1);
|
|
indicator.IsWavePlaying.ShouldBeFalse();
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public void Keyboard_Activation_Plays_Indicator_Wave_When_Motion_Is_Enabled()
|
|
{
|
|
var steps = CreateSteps();
|
|
int? requested = null;
|
|
steps.CurrentChangeRequested += (_, args) => requested = args.Current;
|
|
|
|
ShowInWindow(steps, window =>
|
|
{
|
|
var item = GetItem(steps, 1);
|
|
item.Focus().ShouldBeTrue();
|
|
window.KeyPress(Key.Enter, RawInputModifiers.None, PhysicalKey.Enter, null);
|
|
window.KeyRelease(Key.Enter, RawInputModifiers.None, PhysicalKey.Enter, null);
|
|
Dispatcher.UIThread.RunJobs();
|
|
|
|
requested.ShouldBe(1);
|
|
GetIndicator(item).IsWavePlaying.ShouldBeTrue();
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public void Indicator_Motion_Enabled_Installs_Status_Color_Transitions()
|
|
{
|
|
var steps = CreateSteps();
|
|
|
|
ShowInWindow(steps, _ =>
|
|
{
|
|
var transitions = GetIndicator(GetItem(steps, 1)).Transitions;
|
|
|
|
transitions.ShouldNotBeNull();
|
|
transitions!.OfType<SolidColorBrushTransition>()
|
|
.Select(transition => transition.Property)
|
|
.ShouldBe([
|
|
TemplatedControl.BackgroundProperty,
|
|
TemplatedControl.ForegroundProperty,
|
|
TemplatedControl.BorderBrushProperty
|
|
], ignoreOrder: true);
|
|
});
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(Desktop.Controls.StepsStatus.Wait, StepsTokenKind.WaitIconColor)]
|
|
[InlineData(Desktop.Controls.StepsStatus.Process, StepsTokenKind.ProcessIconBorderColor)]
|
|
[InlineData(Desktop.Controls.StepsStatus.Finish, StepsTokenKind.FinishIconColor)]
|
|
[InlineData(Desktop.Controls.StepsStatus.Error, StepsTokenKind.ErrorIconBorderColor)]
|
|
public void Custom_Loading_Icon_Is_Laid_Out_And_Uses_Status_Color(
|
|
Desktop.Controls.StepsStatus status,
|
|
StepsTokenKind expectedColorToken)
|
|
{
|
|
var processIcon = new LoadingOutlined
|
|
{
|
|
LoadingAnimation = IconAnimation.Spin
|
|
};
|
|
var steps = new Desktop.Controls.Steps
|
|
{
|
|
Width = 760,
|
|
Current = 0,
|
|
IsItemClickable = true,
|
|
IsMotionEnabled = true
|
|
};
|
|
steps.Items.Add(new Desktop.Controls.StepsItem
|
|
{
|
|
Header = "Pay",
|
|
Status = status,
|
|
Icon = processIcon
|
|
});
|
|
|
|
ShowInWindow(steps, _ =>
|
|
{
|
|
var indicator = GetIndicator(GetItem(steps, 0));
|
|
var presenter = indicator.GetVisualDescendants()
|
|
.OfType<IconPresenter>()
|
|
.Single(control => control.Name == "CustomIconPresenter");
|
|
var icon = presenter.GetVisualDescendants()
|
|
.OfType<LoadingOutlined>()
|
|
.Single();
|
|
|
|
presenter.Bounds.Width.ShouldBeGreaterThan(0);
|
|
presenter.Bounds.Height.ShouldBeGreaterThan(0);
|
|
icon.Bounds.Width.ShouldBeGreaterThan(0);
|
|
icon.Bounds.Height.ShouldBeGreaterThan(0);
|
|
icon.LoadingAnimation.ShouldBe(IconAnimation.Spin);
|
|
GetColor(icon.StrokeBrush).ShouldBe(GetColor(GetThemeResource<IBrush>(expectedColorToken)));
|
|
GetColor(icon.FillBrush).ShouldBe(GetColor(GetThemeResource<IBrush>(expectedColorToken)));
|
|
});
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(Desktop.Controls.StepsStatus.Finish, typeof(CheckOutlined), StepsTokenKind.FinishIconColor)]
|
|
[InlineData(Desktop.Controls.StepsStatus.Error, typeof(CloseOutlined), StepsTokenKind.ErrorIconColor)]
|
|
public void Built_In_Status_Icon_Uses_Indicator_Status_Color(
|
|
Desktop.Controls.StepsStatus status,
|
|
Type iconType,
|
|
StepsTokenKind expectedColorToken)
|
|
{
|
|
var steps = new Desktop.Controls.Steps
|
|
{
|
|
Width = 760,
|
|
Current = 0
|
|
};
|
|
steps.Items.Add(new Desktop.Controls.StepsItem
|
|
{
|
|
Header = "Status",
|
|
Status = status
|
|
});
|
|
|
|
ShowInWindow(steps, _ =>
|
|
{
|
|
var indicator = GetIndicator(GetItem(steps, 0));
|
|
var icon = indicator.GetVisualDescendants()
|
|
.OfType<Icon>()
|
|
.Single(control => control.GetType() == iconType);
|
|
|
|
icon.IsVisible.ShouldBeTrue();
|
|
GetColor(icon.StrokeBrush).ShouldBe(GetColor(GetThemeResource<IBrush>(expectedColorToken)));
|
|
GetColor(icon.FillBrush).ShouldBe(GetColor(GetThemeResource<IBrush>(expectedColorToken)));
|
|
});
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(Desktop.Controls.StepsStatus.Wait, StepsTokenKind.WaitDotColor)]
|
|
[InlineData(Desktop.Controls.StepsStatus.Process, StepsTokenKind.ProcessDotColor)]
|
|
[InlineData(Desktop.Controls.StepsStatus.Finish, StepsTokenKind.FinishDotColor)]
|
|
[InlineData(Desktop.Controls.StepsStatus.Error, StepsTokenKind.ErrorDotColor)]
|
|
public void OutlineDot_Uses_Transparent_Background_And_Status_Border(
|
|
Desktop.Controls.StepsStatus status,
|
|
StepsTokenKind expectedColorToken)
|
|
{
|
|
var steps = new Desktop.Controls.Steps
|
|
{
|
|
Width = 760,
|
|
Current = 0,
|
|
Type = Desktop.Controls.StepsType.OutlineDot
|
|
};
|
|
steps.Items.Add(new Desktop.Controls.StepsItem
|
|
{
|
|
Header = "Status",
|
|
Status = status
|
|
});
|
|
|
|
ShowInWindow(steps, _ =>
|
|
{
|
|
var frame = GetIndicatorFrame(GetIndicator(GetItem(steps, 0)));
|
|
|
|
GetColor(frame.Background).ShouldBe(Colors.Transparent);
|
|
GetColor(frame.BorderBrush).ShouldBe(GetColor(GetThemeResource<IBrush>(expectedColorToken)));
|
|
frame.BorderThickness.ShouldBe(new Thickness(2));
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public void Indicator_Wave_Has_A_NonClipping_Visual_Path()
|
|
{
|
|
var steps = CreateSteps();
|
|
|
|
ShowInWindow(steps, _ =>
|
|
{
|
|
var indicator = GetIndicator(GetItem(steps, 1));
|
|
var wave = indicator.GetVisualDescendants()
|
|
.Single(visual => visual.GetType().Name == "WaveSpiritDecorator");
|
|
var clippingAncestors = wave.GetVisualAncestors()
|
|
.TakeWhile(visual => visual is not AvaloniaWindow)
|
|
.Where(visual => visual.ClipToBounds)
|
|
.ToArray();
|
|
|
|
wave.Bounds.Size.ShouldBe(indicator.Bounds.Size);
|
|
wave.ClipToBounds.ShouldBeFalse();
|
|
clippingAncestors.ShouldBeEmpty();
|
|
});
|
|
}
|
|
|
|
private static Desktop.Controls.Steps CreateSteps(
|
|
Desktop.Controls.StepsType type = Desktop.Controls.StepsType.Default)
|
|
{
|
|
var steps = new Desktop.Controls.Steps
|
|
{
|
|
Width = 760,
|
|
Current = 0,
|
|
Type = type,
|
|
IsItemClickable = true,
|
|
IsMotionEnabled = true
|
|
};
|
|
steps.Items.Add(new Desktop.Controls.StepsItem { Header = "Step 1" });
|
|
steps.Items.Add(new Desktop.Controls.StepsItem { Header = "Step 2" });
|
|
steps.Items.Add(new Desktop.Controls.StepsItem { Header = "Step 3" });
|
|
return steps;
|
|
}
|
|
|
|
private static Desktop.Controls.StepsItem GetItem(Desktop.Controls.Steps steps, int index)
|
|
{
|
|
return steps.Items[index].ShouldBeOfType<Desktop.Controls.StepsItem>();
|
|
}
|
|
|
|
private static Desktop.Controls.StepsItemIndicator GetIndicator(Desktop.Controls.StepsItem item)
|
|
{
|
|
return item.GetVisualDescendants().OfType<Desktop.Controls.StepsItemIndicator>().Single();
|
|
}
|
|
|
|
private static PixelAlignedBorder GetIndicatorFrame(Desktop.Controls.StepsItemIndicator indicator)
|
|
{
|
|
return indicator.GetVisualDescendants().OfType<PixelAlignedBorder>().Single(control => control.Name == "Frame");
|
|
}
|
|
|
|
private static void Click(Desktop.Controls.StepsItem item, AvaloniaWindow window)
|
|
{
|
|
var target = item.GetVisualDescendants()
|
|
.OfType<Control>()
|
|
.Single(control => control.Name == "HeaderPresenter");
|
|
var point = target.TranslatePoint(
|
|
new Point(target.Bounds.Width / 2, target.Bounds.Height / 2),
|
|
window);
|
|
point.ShouldNotBeNull();
|
|
|
|
window.MouseMove(point.Value);
|
|
window.MouseDown(point.Value, MouseButton.Left);
|
|
window.MouseUp(point.Value, MouseButton.Left);
|
|
Dispatcher.UIThread.RunJobs();
|
|
}
|
|
|
|
private static void ShowInWindow(Control content, Action<AvaloniaWindow> assertion)
|
|
{
|
|
var window = new AvaloniaWindow
|
|
{
|
|
Width = 900,
|
|
Height = 240,
|
|
Content = content
|
|
};
|
|
|
|
try
|
|
{
|
|
window.Show();
|
|
Dispatcher.UIThread.RunJobs();
|
|
AvaloniaHeadlessPlatform.ForceRenderTimerTick(1);
|
|
Dispatcher.UIThread.RunJobs();
|
|
assertion(window);
|
|
}
|
|
finally
|
|
{
|
|
window.Close();
|
|
}
|
|
}
|
|
|
|
private static T GetThemeResource<T>(object key)
|
|
{
|
|
var application = Application.Current.ShouldNotBeNull();
|
|
application.TryGetResource(key, application.ActualThemeVariant, out var value).ShouldBeTrue();
|
|
value.ShouldBeAssignableTo<T>();
|
|
return (T)value!;
|
|
}
|
|
|
|
private static Color GetColor(IBrush? brush)
|
|
{
|
|
brush.ShouldNotBeNull();
|
|
return ((ISolidColorBrush)brush!).Color;
|
|
}
|
|
}
|