A cross-platform UI framework for .NET
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

174 lines
6.2 KiB

using System;
using Avalonia.Platform;
using Avalonia.Threading;
using Avalonia.UnitTests;
using Moq;
using Xunit;
namespace Avalonia.Controls.UnitTests;
public class LoadedTests : ScopedTestBase
{
[Fact]
public void Window_Loads_And_Unloads()
{
// Some other tests are populating the queue and are not resetting the dispatcher, so we need to purge it
Control.ResetLoadedQueueForUnitTests();
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
int loadedCount = 0, unloadedCount = 0;
var target = new Window();
target.Loaded += (_, _) => loadedCount++;
target.Unloaded += (_, _) => unloadedCount++;
Assert.Equal(0, loadedCount);
Assert.Equal(0, unloadedCount);
target.Show();
Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded, TestContext.Current.CancellationToken);
Assert.True(target.IsLoaded);
Assert.Equal(1, loadedCount);
Assert.Equal(0, unloadedCount);
target.Close();
Assert.Equal(1, loadedCount);
Assert.Equal(1, unloadedCount);
Assert.False(target.IsLoaded);
}
}
[Fact]
public void Control_Loads_And_Unloads()
{
// Some other tests are populating the queue and are not resetting the dispatcher, so we need to purge it
Control.ResetLoadedQueueForUnitTests();
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
int loadedCount = 0, unloadedCount = 0;
var window = new Window();
window.Show();
var target = new Button();
target.Loaded += (_, _) => loadedCount++;
target.Unloaded += (_, _) => unloadedCount++;
Assert.Equal(0, loadedCount);
Assert.Equal(0, unloadedCount);
window.Content = target;
Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded, TestContext.Current.CancellationToken);
Assert.True(target.IsLoaded);
Assert.Equal(1, loadedCount);
Assert.Equal(0, unloadedCount);
window.Content = null;
Assert.Equal(1, loadedCount);
Assert.Equal(1, unloadedCount);
Assert.False(target.IsLoaded);
}
}
[Fact]
public void Loaded_Exception_Does_Not_Prevent_Other_Controls_From_Loading()
{
// Some other tests are populating the queue and are not resetting the dispatcher, so we need to purge it
Control.ResetLoadedQueueForUnitTests();
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var window = new Window();
window.Show();
Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded, TestContext.Current.CancellationToken);
// Batch 1: a control whose Loaded handler throws, plus siblings queued in the same batch.
var throwing = new Button();
throwing.Loaded += (_, _) => throw new InvalidOperationException("Loaded handler failure");
var sibling1 = new Button();
var sibling2 = new Button();
int sibling1LoadedCount = 0, sibling2LoadedCount = 0;
sibling1.Loaded += (_, _) => sibling1LoadedCount++;
sibling2.Loaded += (_, _) => sibling2LoadedCount++;
window.Content = new StackPanel { Children = { sibling1, throwing, sibling2 } };
// The exception must surface (propagate to the dispatcher), not be swallowed.
PumpLoadedJobs(swallowedExceptionsAllowed: 1);
// Both siblings from the same batch must still have been loaded.
Assert.True(sibling1.IsLoaded);
Assert.True(sibling2.IsLoaded);
Assert.Equal(1, sibling1LoadedCount);
Assert.Equal(1, sibling2LoadedCount);
// Batch 2: controls loaded afterwards must still receive Loaded.
var later = new Button();
int laterLoadedCount = 0;
later.Loaded += (_, _) => laterLoadedCount++;
((StackPanel)window.Content!).Children.Add(later);
PumpLoadedJobs(swallowedExceptionsAllowed: 0);
Assert.True(later.IsLoaded);
Assert.Equal(1, laterLoadedCount);
}
}
private static void PumpLoadedJobs(int swallowedExceptionsAllowed)
{
// A throwing Loaded handler propagates its exception out of the dispatcher job.
// Keep pumping so that any rescheduled loaded-processing jobs also run, but fail
// if more exceptions escape than the single expected one.
for (var i = 0; i <= swallowedExceptionsAllowed; i++)
{
try
{
Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded, TestContext.Current.CancellationToken);
return;
}
catch (InvalidOperationException) when (i < swallowedExceptionsAllowed)
{
// Expected exception from the throwing Loaded handler; continue pumping.
}
}
}
[Fact]
public void Loaded_Should_Not_Be_Raised_If_Detached_From_Visual_Tree()
{
using var app = UnitTestApplication.Start(TestServices.StyledWindow);
var loadedCount = 0;
var unloadedCount = 0;
var window = new Window();
window.Show();
var target = new Button();
target.Loaded += (_, _) => loadedCount++;
target.Unloaded += (_, _) => unloadedCount++;
Assert.Equal(0, loadedCount);
Assert.Equal(0, unloadedCount);
// Attach to, then immediately detach from the visual tree.
window.Content = target;
window.Content = null;
// Attach to another logical parent (this can actually happen outside tests with overlay popups)
((ISetLogicalParent) target).SetParent(new Window());
Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded, TestContext.Current.CancellationToken);
// At this point, the control shouldn't have been loaded at all.
Assert.Null(target.VisualParent);
Assert.False(target.IsLoaded);
Assert.Equal(0, loadedCount);
Assert.Equal(0, unloadedCount);
}
}