diff --git a/src/Avalonia.Controls/AppBuilderBase.cs b/src/Avalonia.Controls/AppBuilderBase.cs index 1610d33f9b..7af3deef34 100644 --- a/src/Avalonia.Controls/AppBuilderBase.cs +++ b/src/Avalonia.Controls/AppBuilderBase.cs @@ -14,6 +14,8 @@ namespace Avalonia.Controls /// The type of the AppBuilder class itself. public abstract class AppBuilderBase where TAppBuilder : AppBuilderBase, new() { + private static bool s_setupWasAlreadyCalled; + /// /// Gets or sets the instance. /// @@ -207,6 +209,17 @@ namespace Avalonia.Controls public TAppBuilder UseAvaloniaModules() => AfterSetup(builder => SetupAvaloniaModules()); + private bool CheckSetup { get; set; } = true; + + /// + /// Set this AppBuilder to ignore the setup check. Used for testing purposes. + /// + internal TAppBuilder IgnoreSetupCheck() + { + CheckSetup = false; + return Self; + } + private void SetupAvaloniaModules() { var moduleInitializers = from assembly in AvaloniaLocator.Current.GetService().GetLoadedAssemblies() @@ -252,6 +265,13 @@ namespace Avalonia.Controls throw new InvalidOperationException("No rendering system configured."); } + if (s_setupWasAlreadyCalled && CheckSetup) + { + throw new InvalidOperationException("Setup was already called on one of AppBuilder instances"); + } + + s_setupWasAlreadyCalled = true; + Instance.RegisterServices(); RuntimePlatformServicesInitializer(); WindowingSubsystemInitializer(); diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index abae080515..06c1a8b4cc 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -175,6 +175,15 @@ namespace Avalonia closable.Closed += (s, e) => source.Cancel(); Dispatcher.UIThread.MainLoop(source.Token); } + + /// + /// Runs the application's main loop until the is cancelled. + /// + /// The token to track + public void Run(CancellationToken token) + { + Dispatcher.UIThread.MainLoop(token); + } /// /// Exits the application diff --git a/tests/Avalonia.Controls.UnitTests/AppBuilderTests.cs b/tests/Avalonia.Controls.UnitTests/AppBuilderTests.cs index 867f740a3a..60c53d126c 100644 --- a/tests/Avalonia.Controls.UnitTests/AppBuilderTests.cs +++ b/tests/Avalonia.Controls.UnitTests/AppBuilderTests.cs @@ -65,6 +65,7 @@ namespace Avalonia.Controls.UnitTests { ResetModuleLoadStates(); AppBuilder.Configure() + .IgnoreSetupCheck() .UseWindowingSubsystem(() => { }) .UseRenderingSubsystem(() => { }) .UseAvaloniaModules() @@ -81,6 +82,7 @@ namespace Avalonia.Controls.UnitTests { ResetModuleLoadStates(); var builder = AppBuilder.Configure() + .IgnoreSetupCheck() .UseWindowingSubsystem(() => { }) .UseRenderingSubsystem(() => { }, "Direct2D1"); builder.UseAvaloniaModules().SetupWithoutStarting(); @@ -90,6 +92,7 @@ namespace Avalonia.Controls.UnitTests ResetModuleLoadStates(); builder = AppBuilder.Configure() + .IgnoreSetupCheck() .UseWindowingSubsystem(() => { }) .UseRenderingSubsystem(() => { }, "Skia"); builder.UseAvaloniaModules().SetupWithoutStarting(); @@ -99,13 +102,14 @@ namespace Avalonia.Controls.UnitTests } } - [Fact (Skip = "We don't have rendering modules with dependencies right now")] + [Fact] public void LoadsRenderingModuleWithoutDependenciesWhenNoModuleMatches() { using (AvaloniaLocator.EnterScope()) { ResetModuleLoadStates(); var builder = AppBuilder.Configure() + .IgnoreSetupCheck() .UseWindowingSubsystem(() => { }) .UseRenderingSubsystem(() => { }, "TBD"); builder.UseAvaloniaModules().SetupWithoutStarting();