From 1164cf61f3725030b2a64d0cea5036f56416f862 Mon Sep 17 00:00:00 2001 From: Timothy Baldridge Date: Tue, 26 Oct 2021 22:28:01 -0600 Subject: [PATCH 1/3] More UI tests, remove the custom message bus --- Wabbajack.App.Test/AvaloniaApp.cs | 31 +++++ Wabbajack.App.Test/AvaloniaUiTestFramework.cs | 111 ++++++++++++++++++ Wabbajack.App.Test/Extensions.cs | 7 ++ Wabbajack.App.Test/GalleryItemTests.cs | 4 +- Wabbajack.App.Test/MainWindowTests.cs | 55 +++++++++ Wabbajack.App.Test/Startup.cs | 11 -- Wabbajack.App.Test/Wabbajack.App.Test.csproj | 2 + Wabbajack.App/Controls/BrowseItemViewModel.cs | 4 +- .../Controls/InstalledListViewModel.cs | 4 +- Wabbajack.App/Controls/LargeIconButton.axaml | 2 +- Wabbajack.App/MessageBus.cs | 50 -------- Wabbajack.App/Messages/IReceiver.cs | 10 -- Wabbajack.App/Screens/BrowseView.axaml | 4 +- Wabbajack.App/Screens/CompilationViewModel.cs | 9 +- .../Screens/CompilerConfigurationViewModel.cs | 6 +- Wabbajack.App/Screens/ErrorPageViewModel.cs | 10 +- Wabbajack.App/Screens/LauncherViewModel.cs | 8 +- Wabbajack.App/Screens/SettingsViewModel.cs | 4 +- .../Screens/StandardInstallationViewModel.cs | 10 +- Wabbajack.App/ServiceExtensions.cs | 23 ++-- .../ViewModels/GuidedWebViewModel.cs | 2 +- .../InstallConfigurationViewModel.cs | 17 ++- .../ViewModels/MainWindowViewModel.cs | 25 +++- .../ViewModels/NexusLoginViewModel.cs | 3 +- Wabbajack.App/ViewModels/ViewModelBase.cs | 13 +- Wabbajack.App/Views/MainWindow.axaml | 6 +- Wabbajack.App/Views/ModeSelectionView.axaml | 2 +- .../Views/ModeSelectionView.axaml.cs | 8 +- Wabbajack.App/Wabbajack.App.csproj | 50 ++++---- 29 files changed, 340 insertions(+), 151 deletions(-) create mode 100644 Wabbajack.App.Test/AvaloniaApp.cs create mode 100644 Wabbajack.App.Test/AvaloniaUiTestFramework.cs create mode 100644 Wabbajack.App.Test/MainWindowTests.cs delete mode 100644 Wabbajack.App/MessageBus.cs delete mode 100644 Wabbajack.App/Messages/IReceiver.cs diff --git a/Wabbajack.App.Test/AvaloniaApp.cs b/Wabbajack.App.Test/AvaloniaApp.cs new file mode 100644 index 00000000..13b55ee0 --- /dev/null +++ b/Wabbajack.App.Test/AvaloniaApp.cs @@ -0,0 +1,31 @@ +using System; +using Avalonia; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Headless; +using Avalonia.ReactiveUI; +using Avalonia.Threading; +using Wabbajack.App.Views; + +namespace Wabbajack.App.Test; + +public static class AvaloniaApp +{ + + public static void Stop() + { + var app = GetApp(); + if (app is IDisposable disposable) + Dispatcher.UIThread.Post(disposable.Dispose); + Dispatcher.UIThread.Post(() => app.Shutdown()); + } + + public static MainWindow GetMainWindow() => (MainWindow) GetApp().MainWindow; + public static IClassicDesktopStyleApplicationLifetime GetApp() => + (IClassicDesktopStyleApplicationLifetime) Application.Current.ApplicationLifetime; + + public static AppBuilder BuildAvaloniaApp() => + AppBuilder.Configure() + .UsePlatformDetect() + .UseReactiveUI() + .UseHeadless(); +} \ No newline at end of file diff --git a/Wabbajack.App.Test/AvaloniaUiTestFramework.cs b/Wabbajack.App.Test/AvaloniaUiTestFramework.cs new file mode 100644 index 00000000..dee1b918 --- /dev/null +++ b/Wabbajack.App.Test/AvaloniaUiTestFramework.cs @@ -0,0 +1,111 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using Avalonia; +using Xunit; +using Xunit.Abstractions; +using Xunit.Sdk; + + + +[assembly: TestFramework("Wabbajack.App.Test.AvaloniaUiTestFramework", "Wabbajack.App.Test")] +[assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly, DisableTestParallelization = false, MaxParallelThreads = 1)] +namespace Wabbajack.App.Test +{ + public class AvaloniaUiTestFramework : XunitTestFramework + { + public AvaloniaUiTestFramework(IMessageSink messageSink) + : base(messageSink) + { + + } + + protected override ITestFrameworkExecutor CreateExecutor(AssemblyName assemblyName) + => new Executor(assemblyName, SourceInformationProvider, DiagnosticMessageSink); + + private class Executor : XunitTestFrameworkExecutor + { + public Executor( + AssemblyName assemblyName, + ISourceInformationProvider sourceInformationProvider, + IMessageSink diagnosticMessageSink) + : base( + assemblyName, + sourceInformationProvider, + diagnosticMessageSink) + { + + } + + protected override async void RunTestCases(IEnumerable testCases, + IMessageSink executionMessageSink, + ITestFrameworkExecutionOptions executionOptions) + { + executionOptions.SetValue("xunit.execution.DisableParallelization", false); + using var assemblyRunner = new Runner( + TestAssembly, testCases, DiagnosticMessageSink, executionMessageSink, + executionOptions); + + await assemblyRunner.RunAsync(); + } + } + + private class Runner : XunitTestAssemblyRunner + { + public Runner( + ITestAssembly testAssembly, + IEnumerable testCases, + IMessageSink diagnosticMessageSink, + IMessageSink executionMessageSink, + ITestFrameworkExecutionOptions executionOptions) + : base( + testAssembly, + testCases, + diagnosticMessageSink, + executionMessageSink, + executionOptions) + { + + } + + public override void Dispose() + { + AvaloniaApp.Stop(); + + base.Dispose(); + } + + protected override void SetupSyncContext(int maxParallelThreads) + { + var tcs = new TaskCompletionSource(); + var thread = new Thread(() => + { + try + { + AvaloniaApp + .BuildAvaloniaApp() + .AfterSetup(_ => + { + tcs.SetResult(SynchronizationContext.Current); + }) + .StartWithClassicDesktopLifetime(new string[0]); + } + catch (Exception e) + { + tcs.SetException(e); + } + }) + { + IsBackground = true + }; + thread.SetApartmentState(ApartmentState.STA); + + thread.Start(); + + SynchronizationContext.SetSynchronizationContext(tcs.Task.Result); + } + } + } +} \ No newline at end of file diff --git a/Wabbajack.App.Test/Extensions.cs b/Wabbajack.App.Test/Extensions.cs index 48a3fb2a..024bbceb 100644 --- a/Wabbajack.App.Test/Extensions.cs +++ b/Wabbajack.App.Test/Extensions.cs @@ -2,6 +2,8 @@ using System; using System.Threading.Tasks; using Avalonia.Threading; using Wabbajack.App.Models; +using Wabbajack.App.ViewModels; +using Wabbajack.App.Views; namespace Wabbajack.App.Test; @@ -37,4 +39,9 @@ public static class Extensions await Task.Delay(100); } } + + public static T GetScreen(this MainWindow view) + { + return (T) view.Contents.Content; + } } \ No newline at end of file diff --git a/Wabbajack.App.Test/GalleryItemTests.cs b/Wabbajack.App.Test/GalleryItemTests.cs index 1411dc43..a062a563 100644 --- a/Wabbajack.App.Test/GalleryItemTests.cs +++ b/Wabbajack.App.Test/GalleryItemTests.cs @@ -63,12 +63,12 @@ public class GalleryItemTests modList.ExecuteCommand.Execute().Subscribe().Dispose(); - var msgs = ((SimpleMessageBus) MessageBus.Instance).Messages.TakeLast(2).ToArray(); - + /* var configure = msgs.OfType().First(); Assert.Equal(modList.ModListLocation, configure.ModList); var navigate = msgs.OfType().First(); Assert.Equal(typeof(InstallConfigurationViewModel), navigate.ViewModel); + */ } } \ No newline at end of file diff --git a/Wabbajack.App.Test/MainWindowTests.cs b/Wabbajack.App.Test/MainWindowTests.cs new file mode 100644 index 00000000..aaa8b0ad --- /dev/null +++ b/Wabbajack.App.Test/MainWindowTests.cs @@ -0,0 +1,55 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Avalonia.Threading; +using Avalonia.VisualTree; +using Wabbajack.App.Controls; +using Wabbajack.App.Interfaces; +using Wabbajack.App.Screens; +using Wabbajack.App.Views; +using Xunit; + +namespace Wabbajack.App.Test; + +public class MainWindowTests +{ + private static TimeSpan StandardDelay = new TimeSpan(250); + + [Fact(DisplayName = "Can Open and Close MainWindow")] + public async Task CanOpenMainApp() + { + var app = AvaloniaApp.GetApp(); + var window = AvaloniaApp.GetMainWindow(); + + var msv = await GoHome(window); + msv.BrowseButton.Button.Command.Execute(null); + msv.BrowseButton.Button.Command.Execute(null); + + var yu = Dispatcher.UIThread; + + await Task.Delay(StandardDelay * 4); + + var gallery = window.GetScreen(); + gallery.SearchBox.Text = "Halgaris Helper"; + await Task.Delay(StandardDelay); + var itms = gallery.GalleryList.FindDescendantOfType(); + + + + + } + + private async Task GoHome(MainWindow mainWindow) + { + while (mainWindow.BackButton.Command.CanExecute(null)) + { + mainWindow.BackButton.Command.Execute(null); + await Task.Delay(StandardDelay); + } + + if (mainWindow.Contents.Content is ModeSelectionView msv) + return msv; + + throw new Exception("Top screen is not ModeSelectionView"); + } +} \ No newline at end of file diff --git a/Wabbajack.App.Test/Startup.cs b/Wabbajack.App.Test/Startup.cs index 8c5f4941..0d3e030e 100644 --- a/Wabbajack.App.Test/Startup.cs +++ b/Wabbajack.App.Test/Startup.cs @@ -16,16 +16,5 @@ public class Startup public void Configure(ILoggerFactory loggerFactory, ITestOutputHelperAccessor accessor) { loggerFactory.AddProvider(new XunitTestOutputLoggerProvider(accessor, delegate { return true; })); - MessageBus.Instance = new SimpleMessageBus(); } } - -public class SimpleMessageBus : IMessageBus -{ - public List Messages { get; } = new(); - - public void Send(T message) - { - Messages.Add(message); - } -} \ No newline at end of file diff --git a/Wabbajack.App.Test/Wabbajack.App.Test.csproj b/Wabbajack.App.Test/Wabbajack.App.Test.csproj index d4ccd3c8..4e96d3de 100644 --- a/Wabbajack.App.Test/Wabbajack.App.Test.csproj +++ b/Wabbajack.App.Test/Wabbajack.App.Test.csproj @@ -7,6 +7,8 @@ enable + + diff --git a/Wabbajack.App/Controls/BrowseItemViewModel.cs b/Wabbajack.App/Controls/BrowseItemViewModel.cs index 1c3c50e8..0c5b34a4 100644 --- a/Wabbajack.App/Controls/BrowseItemViewModel.cs +++ b/Wabbajack.App/Controls/BrowseItemViewModel.cs @@ -78,8 +78,8 @@ public class BrowseItemViewModel : ViewModelBase, IActivatableViewModel { if (State == ModListState.Downloaded) { - MessageBus.Instance.Send(new StartInstallConfiguration(ModListLocation)); - MessageBus.Instance.Send(new NavigateTo(typeof(InstallConfigurationViewModel))); + MessageBus.Current.SendMessage(new StartInstallConfiguration(ModListLocation)); + MessageBus.Current.SendMessage(new NavigateTo(typeof(InstallConfigurationViewModel))); } else { diff --git a/Wabbajack.App/Controls/InstalledListViewModel.cs b/Wabbajack.App/Controls/InstalledListViewModel.cs index b82667f6..720b7790 100644 --- a/Wabbajack.App/Controls/InstalledListViewModel.cs +++ b/Wabbajack.App/Controls/InstalledListViewModel.cs @@ -19,8 +19,8 @@ public class InstalledListViewModel : ViewModelBase Play = ReactiveCommand.Create(() => { - MessageBus.Instance.Send(new ConfigureLauncher(InstallPath)); - MessageBus.Instance.Send(new NavigateTo(typeof(LauncherViewModel))); + MessageBus.Current.SendMessage(new ConfigureLauncher(InstallPath)); + MessageBus.Current.SendMessage(new NavigateTo(typeof(LauncherViewModel))); }); } diff --git a/Wabbajack.App/Controls/LargeIconButton.axaml b/Wabbajack.App/Controls/LargeIconButton.axaml index 77d038c9..a12cfa12 100644 --- a/Wabbajack.App/Controls/LargeIconButton.axaml +++ b/Wabbajack.App/Controls/LargeIconButton.axaml @@ -5,7 +5,7 @@ xmlns:i="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Wabbajack.App.Controls.LargeIconButton"> - @@ -50,12 +50,12 @@ - - + diff --git a/Wabbajack.App/Views/ModeSelectionView.axaml b/Wabbajack.App/Views/ModeSelectionView.axaml index 0cce086d..3efe14ea 100644 --- a/Wabbajack.App/Views/ModeSelectionView.axaml +++ b/Wabbajack.App/Views/ModeSelectionView.axaml @@ -14,7 +14,7 @@ - + diff --git a/Wabbajack.App/Views/ModeSelectionView.axaml.cs b/Wabbajack.App/Views/ModeSelectionView.axaml.cs index 786f3d91..eec2e51c 100644 --- a/Wabbajack.App/Views/ModeSelectionView.axaml.cs +++ b/Wabbajack.App/Views/ModeSelectionView.axaml.cs @@ -16,22 +16,22 @@ public partial class ModeSelectionView : ScreenBase { InstallButton.Button.Command = ReactiveCommand.Create(() => { - MessageBus.Instance.Send(new NavigateTo(typeof(InstallConfigurationViewModel))); + MessageBus.Current.SendMessage(new NavigateTo(typeof(InstallConfigurationViewModel))); }).DisposeWith(disposables); BrowseButton.Button.Command = ReactiveCommand.Create(() => { - MessageBus.Instance.Send(new NavigateTo(typeof(BrowseViewModel))); + MessageBus.Current.SendMessage(new NavigateTo(typeof(BrowseViewModel))); }).DisposeWith(disposables); CompileButton.Button.Command = ReactiveCommand.Create(() => { - MessageBus.Instance.Send(new NavigateTo(typeof(CompilerConfigurationViewModel))); + MessageBus.Current.SendMessage(new NavigateTo(typeof(CompilerConfigurationViewModel))); }).DisposeWith(disposables); LaunchButton.Button.Command = ReactiveCommand.Create(() => { - MessageBus.Instance.Send(new NavigateTo(typeof(PlaySelectViewModel))); + MessageBus.Current.SendMessage(new NavigateTo(typeof(PlaySelectViewModel))); }); }); } diff --git a/Wabbajack.App/Wabbajack.App.csproj b/Wabbajack.App/Wabbajack.App.csproj index d23aa7ea..e7b71c8c 100644 --- a/Wabbajack.App/Wabbajack.App.csproj +++ b/Wabbajack.App/Wabbajack.App.csproj @@ -6,35 +6,35 @@ enable - - - - + + + + - - - - - - - - - - - - - + + + + + + + + + + + + + - - - - - + + + + + - + @@ -51,7 +51,7 @@ - - + + From 1cd5d01f1c3239fac7e38e1161ca01caff45e82f Mon Sep 17 00:00:00 2001 From: Timothy Baldridge Date: Wed, 27 Oct 2021 14:52:26 -0600 Subject: [PATCH 2/3] The test tests didn't work so well --- Wabbajack.App.Test/AvaloniaApp.cs | 31 ----- Wabbajack.App.Test/AvaloniaUiTestFramework.cs | 111 ------------------ Wabbajack.App.Test/Extensions.cs | 47 -------- Wabbajack.App.Test/GalleryItemTests.cs | 74 ------------ Wabbajack.App.Test/MainWindowTests.cs | 55 --------- Wabbajack.App.Test/Startup.cs | 20 ---- Wabbajack.App.Test/Wabbajack.App.Test.csproj | 34 ------ Wabbajack.App/Assets/Wabbajack.axaml | 4 + Wabbajack.App/Screens/SettingsView.axaml | 4 +- .../ViewModels/MainWindowViewModel.cs | 4 - Wabbajack.sln | 6 - global.json | 5 + 12 files changed, 11 insertions(+), 384 deletions(-) delete mode 100644 Wabbajack.App.Test/AvaloniaApp.cs delete mode 100644 Wabbajack.App.Test/AvaloniaUiTestFramework.cs delete mode 100644 Wabbajack.App.Test/Extensions.cs delete mode 100644 Wabbajack.App.Test/GalleryItemTests.cs delete mode 100644 Wabbajack.App.Test/MainWindowTests.cs delete mode 100644 Wabbajack.App.Test/Startup.cs delete mode 100644 Wabbajack.App.Test/Wabbajack.App.Test.csproj create mode 100644 global.json diff --git a/Wabbajack.App.Test/AvaloniaApp.cs b/Wabbajack.App.Test/AvaloniaApp.cs deleted file mode 100644 index 13b55ee0..00000000 --- a/Wabbajack.App.Test/AvaloniaApp.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using Avalonia; -using Avalonia.Controls.ApplicationLifetimes; -using Avalonia.Headless; -using Avalonia.ReactiveUI; -using Avalonia.Threading; -using Wabbajack.App.Views; - -namespace Wabbajack.App.Test; - -public static class AvaloniaApp -{ - - public static void Stop() - { - var app = GetApp(); - if (app is IDisposable disposable) - Dispatcher.UIThread.Post(disposable.Dispose); - Dispatcher.UIThread.Post(() => app.Shutdown()); - } - - public static MainWindow GetMainWindow() => (MainWindow) GetApp().MainWindow; - public static IClassicDesktopStyleApplicationLifetime GetApp() => - (IClassicDesktopStyleApplicationLifetime) Application.Current.ApplicationLifetime; - - public static AppBuilder BuildAvaloniaApp() => - AppBuilder.Configure() - .UsePlatformDetect() - .UseReactiveUI() - .UseHeadless(); -} \ No newline at end of file diff --git a/Wabbajack.App.Test/AvaloniaUiTestFramework.cs b/Wabbajack.App.Test/AvaloniaUiTestFramework.cs deleted file mode 100644 index dee1b918..00000000 --- a/Wabbajack.App.Test/AvaloniaUiTestFramework.cs +++ /dev/null @@ -1,111 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Reflection; -using System.Threading; -using System.Threading.Tasks; -using Avalonia; -using Xunit; -using Xunit.Abstractions; -using Xunit.Sdk; - - - -[assembly: TestFramework("Wabbajack.App.Test.AvaloniaUiTestFramework", "Wabbajack.App.Test")] -[assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly, DisableTestParallelization = false, MaxParallelThreads = 1)] -namespace Wabbajack.App.Test -{ - public class AvaloniaUiTestFramework : XunitTestFramework - { - public AvaloniaUiTestFramework(IMessageSink messageSink) - : base(messageSink) - { - - } - - protected override ITestFrameworkExecutor CreateExecutor(AssemblyName assemblyName) - => new Executor(assemblyName, SourceInformationProvider, DiagnosticMessageSink); - - private class Executor : XunitTestFrameworkExecutor - { - public Executor( - AssemblyName assemblyName, - ISourceInformationProvider sourceInformationProvider, - IMessageSink diagnosticMessageSink) - : base( - assemblyName, - sourceInformationProvider, - diagnosticMessageSink) - { - - } - - protected override async void RunTestCases(IEnumerable testCases, - IMessageSink executionMessageSink, - ITestFrameworkExecutionOptions executionOptions) - { - executionOptions.SetValue("xunit.execution.DisableParallelization", false); - using var assemblyRunner = new Runner( - TestAssembly, testCases, DiagnosticMessageSink, executionMessageSink, - executionOptions); - - await assemblyRunner.RunAsync(); - } - } - - private class Runner : XunitTestAssemblyRunner - { - public Runner( - ITestAssembly testAssembly, - IEnumerable testCases, - IMessageSink diagnosticMessageSink, - IMessageSink executionMessageSink, - ITestFrameworkExecutionOptions executionOptions) - : base( - testAssembly, - testCases, - diagnosticMessageSink, - executionMessageSink, - executionOptions) - { - - } - - public override void Dispose() - { - AvaloniaApp.Stop(); - - base.Dispose(); - } - - protected override void SetupSyncContext(int maxParallelThreads) - { - var tcs = new TaskCompletionSource(); - var thread = new Thread(() => - { - try - { - AvaloniaApp - .BuildAvaloniaApp() - .AfterSetup(_ => - { - tcs.SetResult(SynchronizationContext.Current); - }) - .StartWithClassicDesktopLifetime(new string[0]); - } - catch (Exception e) - { - tcs.SetException(e); - } - }) - { - IsBackground = true - }; - thread.SetApartmentState(ApartmentState.STA); - - thread.Start(); - - SynchronizationContext.SetSynchronizationContext(tcs.Task.Result); - } - } - } -} \ No newline at end of file diff --git a/Wabbajack.App.Test/Extensions.cs b/Wabbajack.App.Test/Extensions.cs deleted file mode 100644 index 024bbceb..00000000 --- a/Wabbajack.App.Test/Extensions.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; -using System.Threading.Tasks; -using Avalonia.Threading; -using Wabbajack.App.Models; -using Wabbajack.App.ViewModels; -using Wabbajack.App.Views; - -namespace Wabbajack.App.Test; - -public static class Extensions -{ - public static async Task WaitUntil(this T src, Predicate check, Action? doFunc = null) - { - Dispatcher.UIThread.RunJobs(); - - while (!check(src)) - { - doFunc?.Invoke(); - await Task.Delay(100); - } - } - - public static async Task WaitForLock(this LoadingLock l) - { - Dispatcher.UIThread.RunJobs(); - while (!l.IsLoading) - { - Dispatcher.UIThread.RunJobs(); - await Task.Delay(100); - } - } - - public static async Task WaitForUnlock(this LoadingLock l) - { - Dispatcher.UIThread.RunJobs(); - while (l.IsLoading) - { - Dispatcher.UIThread.RunJobs(); - await Task.Delay(100); - } - } - - public static T GetScreen(this MainWindow view) - { - return (T) view.Contents.Content; - } -} \ No newline at end of file diff --git a/Wabbajack.App.Test/GalleryItemTests.cs b/Wabbajack.App.Test/GalleryItemTests.cs deleted file mode 100644 index a062a563..00000000 --- a/Wabbajack.App.Test/GalleryItemTests.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System; -using System.Linq; -using System.Threading.Tasks; -using Wabbajack.App.Controls; -using Wabbajack.App.Messages; -using Wabbajack.App.Screens; -using Wabbajack.App.ViewModels; -using Wabbajack.Common; -using Wabbajack.Paths.IO; -using Wabbajack.RateLimiter; -using Xunit; - -namespace Wabbajack.App.Test; - -public class GalleryItemTests -{ - private readonly Configuration _config; - private readonly BrowseViewModel _gallery; - - public GalleryItemTests(BrowseViewModel bvm, Configuration config) - { - _config = config; - _gallery = bvm; - } - - [Fact] - public async Task CanDownloadGalleryItem() - { - _config.ModListsDownloadLocation.CreateDirectory(); - foreach (var file in _config.ModListsDownloadLocation.EnumerateFiles().Where(f => f.Extension == Ext.Wabbajack)) - file.Delete(); - - using var _ = _gallery.Activator.Activate(); - await _gallery.LoadingLock.WaitForLock(); - await _gallery.LoadingLock.WaitForUnlock(); - Assert.True(_gallery.ModLists.Count > 0); - - foreach (var item in _gallery.ModLists) - { - Assert.NotEqual(ModListState.Downloading, item.State); - if (item.State == ModListState.Downloaded) - Assert.True(item.ModListLocation.FileExists()); - else - Assert.False(item.ModListLocation.FileExists()); - - Assert.Equal(Percent.Zero, item.Progress); - } - - var modList = _gallery.ModLists.First(); - modList.ExecuteCommand.Execute().Subscribe().Dispose(); - - var progress = Percent.Zero; - await modList.WaitUntil(x => x.State == ModListState.Downloading); - await modList.WaitUntil(x => x.State != ModListState.Downloading, () => - { - Assert.True(modList.Progress >= progress); - progress = modList.Progress; - }); - - Assert.Equal(Percent.Zero, modList.Progress); - Assert.Equal(ModListState.Downloaded, modList.State); - - - modList.ExecuteCommand.Execute().Subscribe().Dispose(); - - /* - var configure = msgs.OfType().First(); - Assert.Equal(modList.ModListLocation, configure.ModList); - - var navigate = msgs.OfType().First(); - Assert.Equal(typeof(InstallConfigurationViewModel), navigate.ViewModel); - */ - } -} \ No newline at end of file diff --git a/Wabbajack.App.Test/MainWindowTests.cs b/Wabbajack.App.Test/MainWindowTests.cs deleted file mode 100644 index aaa8b0ad..00000000 --- a/Wabbajack.App.Test/MainWindowTests.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; -using System.Linq; -using System.Threading.Tasks; -using Avalonia.Threading; -using Avalonia.VisualTree; -using Wabbajack.App.Controls; -using Wabbajack.App.Interfaces; -using Wabbajack.App.Screens; -using Wabbajack.App.Views; -using Xunit; - -namespace Wabbajack.App.Test; - -public class MainWindowTests -{ - private static TimeSpan StandardDelay = new TimeSpan(250); - - [Fact(DisplayName = "Can Open and Close MainWindow")] - public async Task CanOpenMainApp() - { - var app = AvaloniaApp.GetApp(); - var window = AvaloniaApp.GetMainWindow(); - - var msv = await GoHome(window); - msv.BrowseButton.Button.Command.Execute(null); - msv.BrowseButton.Button.Command.Execute(null); - - var yu = Dispatcher.UIThread; - - await Task.Delay(StandardDelay * 4); - - var gallery = window.GetScreen(); - gallery.SearchBox.Text = "Halgaris Helper"; - await Task.Delay(StandardDelay); - var itms = gallery.GalleryList.FindDescendantOfType(); - - - - - } - - private async Task GoHome(MainWindow mainWindow) - { - while (mainWindow.BackButton.Command.CanExecute(null)) - { - mainWindow.BackButton.Command.Execute(null); - await Task.Delay(StandardDelay); - } - - if (mainWindow.Contents.Content is ModeSelectionView msv) - return msv; - - throw new Exception("Top screen is not ModeSelectionView"); - } -} \ No newline at end of file diff --git a/Wabbajack.App.Test/Startup.cs b/Wabbajack.App.Test/Startup.cs deleted file mode 100644 index 0d3e030e..00000000 --- a/Wabbajack.App.Test/Startup.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Collections.Generic; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Xunit.DependencyInjection; -using Xunit.DependencyInjection.Logging; - -namespace Wabbajack.App.Test; - -public class Startup -{ - public void ConfigureServices(IServiceCollection service) - { - service.AddAppServices(); - } - - public void Configure(ILoggerFactory loggerFactory, ITestOutputHelperAccessor accessor) - { - loggerFactory.AddProvider(new XunitTestOutputLoggerProvider(accessor, delegate { return true; })); - } -} diff --git a/Wabbajack.App.Test/Wabbajack.App.Test.csproj b/Wabbajack.App.Test/Wabbajack.App.Test.csproj deleted file mode 100644 index 4e96d3de..00000000 --- a/Wabbajack.App.Test/Wabbajack.App.Test.csproj +++ /dev/null @@ -1,34 +0,0 @@ - - - - Exe - net6.0-windows - net6.0 - enable - - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - - - - - - - diff --git a/Wabbajack.App/Assets/Wabbajack.axaml b/Wabbajack.App/Assets/Wabbajack.axaml index f1a943c4..3d9c8975 100644 --- a/Wabbajack.App/Assets/Wabbajack.axaml +++ b/Wabbajack.App/Assets/Wabbajack.axaml @@ -34,6 +34,10 @@ + + \ No newline at end of file diff --git a/Wabbajack.App/Screens/SettingsView.axaml b/Wabbajack.App/Screens/SettingsView.axaml index 6d34aab9..fb56369c 100644 --- a/Wabbajack.App/Screens/SettingsView.axaml +++ b/Wabbajack.App/Screens/SettingsView.axaml @@ -6,7 +6,7 @@ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Wabbajack.App.Screens.SettingsView"> - + Logins @@ -34,7 +34,7 @@ - + Resource Limits diff --git a/Wabbajack.App/ViewModels/MainWindowViewModel.cs b/Wabbajack.App/ViewModels/MainWindowViewModel.cs index 5f03b40a..ef36998b 100644 --- a/Wabbajack.App/ViewModels/MainWindowViewModel.cs +++ b/Wabbajack.App/ViewModels/MainWindowViewModel.cs @@ -151,9 +151,5 @@ public class MainWindowViewModel : ReactiveValidationObject, IActivatableViewMod { _resourcePoller.Dispose(); VMDisposables.Dispose(); - BackButton.Dispose(); - SettingsButton.Dispose(); - LogViewButton.Dispose(); - Activator.Dispose(); } } \ No newline at end of file diff --git a/Wabbajack.sln b/Wabbajack.sln index c1ef69d9..f7f3c5e0 100644 --- a/Wabbajack.sln +++ b/Wabbajack.sln @@ -116,8 +116,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wabbajack.Downloaders.GameF EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wabbajack.Launcher", "Wabbajack.Launcher\Wabbajack.Launcher.csproj", "{23D49FCC-A6CB-4873-879B-F90DA1871AA3}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wabbajack.App.Test", "Wabbajack.App.Test\Wabbajack.App.Test.csproj", "{05E7E8BB-1F44-4E3D-8443-110FD237AA92}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -324,10 +322,6 @@ Global {23D49FCC-A6CB-4873-879B-F90DA1871AA3}.Debug|Any CPU.Build.0 = Debug|Any CPU {23D49FCC-A6CB-4873-879B-F90DA1871AA3}.Release|Any CPU.ActiveCfg = Release|Any CPU {23D49FCC-A6CB-4873-879B-F90DA1871AA3}.Release|Any CPU.Build.0 = Release|Any CPU - {05E7E8BB-1F44-4E3D-8443-110FD237AA92}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {05E7E8BB-1F44-4E3D-8443-110FD237AA92}.Debug|Any CPU.Build.0 = Debug|Any CPU - {05E7E8BB-1F44-4E3D-8443-110FD237AA92}.Release|Any CPU.ActiveCfg = Release|Any CPU - {05E7E8BB-1F44-4E3D-8443-110FD237AA92}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {4057B668-8595-44FE-9805-007B284A838F} = {98B731EE-4FC0-4482-A069-BCBA25497871} diff --git a/global.json b/global.json new file mode 100644 index 00000000..4e0cd99f --- /dev/null +++ b/global.json @@ -0,0 +1,5 @@ +{ + "sdk": { + "version": "6.0.*" + } +} \ No newline at end of file From 193907d1988ce65a8ed8ea9ef7b77dd85ccca8d7 Mon Sep 17 00:00:00 2001 From: Timothy Baldridge Date: Wed, 27 Oct 2021 15:47:58 -0600 Subject: [PATCH 3/3] Small fixes to the gallery --- Wabbajack.App/Configuration.cs | 4 +- .../Controls/BrowseItemView.axaml.cs | 1 + Wabbajack.App/Controls/BrowseItemViewModel.cs | 19 +++++--- Wabbajack.App/Models/ImageCache.cs | 47 +++++++++++++++++++ Wabbajack.App/Screens/BrowseView.axaml | 16 +++---- Wabbajack.App/Screens/BrowseViewModel.cs | 6 ++- Wabbajack.App/ServiceExtensions.cs | 4 +- 7 files changed, 77 insertions(+), 20 deletions(-) create mode 100644 Wabbajack.App/Models/ImageCache.cs diff --git a/Wabbajack.App/Configuration.cs b/Wabbajack.App/Configuration.cs index 57d88f29..5559d8cd 100644 --- a/Wabbajack.App/Configuration.cs +++ b/Wabbajack.App/Configuration.cs @@ -6,8 +6,8 @@ public class Configuration { public AbsolutePath ModListsDownloadLocation { get; set; } public AbsolutePath SavedSettingsLocation { get; set; } - public AbsolutePath EncryptedDataLocation { get; set; } - public AbsolutePath LogLocation { get; set; } + + public AbsolutePath ImageCacheLocation { get; set; } } \ No newline at end of file diff --git a/Wabbajack.App/Controls/BrowseItemView.axaml.cs b/Wabbajack.App/Controls/BrowseItemView.axaml.cs index 3a29a307..fdf7b84a 100644 --- a/Wabbajack.App/Controls/BrowseItemView.axaml.cs +++ b/Wabbajack.App/Controls/BrowseItemView.axaml.cs @@ -42,6 +42,7 @@ public partial class BrowseItemView : ReactiveUserControl { return modListState switch { + ModListState.Disabled => MaterialIconKind.Error, ModListState.Downloaded => MaterialIconKind.PlayArrow, ModListState.Downloading => MaterialIconKind.LocalAreaNetworkPending, ModListState.NotDownloaded => MaterialIconKind.Download, diff --git a/Wabbajack.App/Controls/BrowseItemViewModel.cs b/Wabbajack.App/Controls/BrowseItemViewModel.cs index 0c5b34a4..bf05346f 100644 --- a/Wabbajack.App/Controls/BrowseItemViewModel.cs +++ b/Wabbajack.App/Controls/BrowseItemViewModel.cs @@ -11,6 +11,7 @@ using Microsoft.Extensions.Logging; using ReactiveUI; using ReactiveUI.Fody.Helpers; using Wabbajack.App.Messages; +using Wabbajack.App.Models; using Wabbajack.App.ViewModels; using Wabbajack.Common; using Wabbajack.Downloaders; @@ -28,7 +29,8 @@ public enum ModListState { Downloaded, NotDownloaded, - Downloading + Downloading, + Disabled } public class BrowseItemViewModel : ViewModelBase, IActivatableViewModel @@ -43,11 +45,12 @@ public class BrowseItemViewModel : ViewModelBase, IActivatableViewModel private readonly ILogger _logger; private readonly ModlistMetadata _metadata; private readonly ModListSummary _summary; + private readonly ImageCache _imageCache; public BrowseItemViewModel(ModlistMetadata metadata, ModListSummary summary, HttpClient client, IResource limiter, FileHashCache hashCache, Configuration configuration, DownloadDispatcher dispatcher, - IResource downloadLimiter, GameLocator gameLocator, + IResource downloadLimiter, GameLocator gameLocator, ImageCache imageCache, DTOSerializer dtos, ILogger logger) { Activator = new ViewModelActivator(); @@ -59,6 +62,7 @@ public class BrowseItemViewModel : ViewModelBase, IActivatableViewModel _configuration = configuration; _dispatcher = dispatcher; _downloadLimiter = downloadLimiter; + _imageCache = imageCache; _logger = logger; _dtos = dtos; @@ -87,7 +91,7 @@ public class BrowseItemViewModel : ViewModelBase, IActivatableViewModel } }, this.ObservableForProperty(t => t.State) - .Select(c => c.Value != ModListState.Downloading) + .Select(c => c.Value != ModListState.Downloading && c.Value != ModListState.Disabled) .StartWith(true)); LoadListImage().FireAndForget(); @@ -96,7 +100,7 @@ public class BrowseItemViewModel : ViewModelBase, IActivatableViewModel public string Title => _metadata.ImageContainsTitle ? "" : _metadata.Title; public string MachineURL => _metadata.Links.MachineURL; - public string Description => _metadata.Description; + public string Description => State == ModListState.Disabled ? "Disabled: Under Construction \n " + _metadata.Description : _metadata.Description; public Uri ImageUri => new(_metadata.Links.ImageUri); @@ -163,13 +167,14 @@ public class BrowseItemViewModel : ViewModelBase, IActivatableViewModel public async Task LoadListImage() { - using var job = await _limiter.Begin("Loading modlist image", 0, CancellationToken.None); - var response = await _client.GetByteArrayAsync(ImageUri); - Image = new Bitmap(new MemoryStream(response)); + Image = await _imageCache.From(ImageUri); } public async Task GetState() { + if (_metadata.ForceDown || _summary.HasFailures) + return ModListState.Disabled; + var file = ModListLocation; if (!file.FileExists()) return ModListState.NotDownloaded; diff --git a/Wabbajack.App/Models/ImageCache.cs b/Wabbajack.App/Models/ImageCache.cs new file mode 100644 index 00000000..ac11adc3 --- /dev/null +++ b/Wabbajack.App/Models/ImageCache.cs @@ -0,0 +1,47 @@ +using System; +using System.IO; +using System.Net.Http; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Avalonia.Controls; +using Avalonia.Media; +using Avalonia.Media.Imaging; +using SkiaSharp; +using Wabbajack.Hashing.xxHash64; +using Wabbajack.Paths.IO; +using Wabbajack.RateLimiter; + +namespace Wabbajack.App.Models; + +public class ImageCache +{ + private readonly Configuration _configuration; + private readonly HttpClient _client; + private readonly IResource _limiter; + + public ImageCache(Configuration configuration, HttpClient client, IResource limiter) + { + _configuration = configuration; + _configuration.ImageCacheLocation.CreateDirectory(); + _client = client; + _limiter = limiter; + } + + public async Task From(Uri uri) + { + var hash = (await Encoding.UTF8.GetBytes(uri.ToString()).Hash()).ToHex(); + var file = _configuration.ImageCacheLocation.Combine(hash); + if (!file.FileExists()) + { + using var job = await _limiter.Begin("Loading Image", 0, CancellationToken.None); + var wdata = await _client.GetByteArrayAsync(uri); + await file.WriteAllBytesAsync(wdata); + return new Bitmap(new MemoryStream(wdata)); + } + + var data = await file.ReadAllBytesAsync(); + return new Bitmap(new MemoryStream(data)); + } + +} \ No newline at end of file diff --git a/Wabbajack.App/Screens/BrowseView.axaml b/Wabbajack.App/Screens/BrowseView.axaml index 5ff7419c..2bd82ddc 100644 --- a/Wabbajack.App/Screens/BrowseView.axaml +++ b/Wabbajack.App/Screens/BrowseView.axaml @@ -63,18 +63,16 @@ - - - - - - - + + + + + - - + + diff --git a/Wabbajack.App/Screens/BrowseViewModel.cs b/Wabbajack.App/Screens/BrowseViewModel.cs index 9afd705b..04c51879 100644 --- a/Wabbajack.App/Screens/BrowseViewModel.cs +++ b/Wabbajack.App/Screens/BrowseViewModel.cs @@ -48,10 +48,12 @@ public class BrowseViewModel : ViewModelBase, IActivatableViewModel private readonly SourceCache _gamesList = new(x => x.Name); private readonly SourceCache _modLists = new(x => x.MachineURL); + private readonly ImageCache _imageCache; public BrowseViewModel(ILogger logger, Client wjClient, HttpClient httpClient, IResource limiter, FileHashCache hashCache, IResource dispatcherLimiter, DownloadDispatcher dispatcher, GameLocator gameLocator, + ImageCache imageCache, DTOSerializer dtos, Configuration configuration) { LoadingLock = new LoadingLock(); @@ -65,6 +67,7 @@ public class BrowseViewModel : ViewModelBase, IActivatableViewModel _dispatcher = dispatcher; _dispatcherLimiter = dispatcherLimiter; _gameLocator = gameLocator; + _imageCache = imageCache; _dtos = dtos; @@ -131,6 +134,7 @@ public class BrowseViewModel : ViewModelBase, IActivatableViewModel .Filter(onlyInstalledGamesFilter) .Filter(onlyUtilityListsFilter) .Filter(showNSFWFilter) + .SortBy(x => x.State == ModListState.Disabled ? 1 : 0) .Bind(out _filteredModLists) .Subscribe(); @@ -191,7 +195,7 @@ public class BrowseViewModel : ViewModelBase, IActivatableViewModel if (!summaries.TryGetValue(m.Links.MachineURL, out var summary)) summary = new ModListSummary(); return new BrowseItemViewModel(m, summary, _httpClient, _limiter, _hashCache, _configuration, _dispatcher, - _dispatcherLimiter, _gameLocator, _dtos, _logger); + _dispatcherLimiter, _gameLocator, _imageCache, _dtos, _logger); }); _modLists.Edit(lsts => diff --git a/Wabbajack.App/ServiceExtensions.cs b/Wabbajack.App/ServiceExtensions.cs index 68fe4ae7..c50ab48c 100644 --- a/Wabbajack.App/ServiceExtensions.cs +++ b/Wabbajack.App/ServiceExtensions.cs @@ -57,6 +57,7 @@ public static class ServiceExtensions services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); @@ -97,7 +98,8 @@ public static class ServiceExtensions EncryptedDataLocation = KnownFolders.WabbajackAppLocal.Combine("encrypted"), ModListsDownloadLocation = KnownFolders.EntryPoint.Combine("downloaded_mod_lists"), SavedSettingsLocation = KnownFolders.WabbajackAppLocal.Combine("saved_settings"), - LogLocation = KnownFolders.EntryPoint.Combine("logs") + LogLocation = KnownFolders.EntryPoint.Combine("logs"), + ImageCacheLocation = KnownFolders.WabbajackAppLocal.Combine("image_cache") }); services.AddSingleton();