Ionite
1 year ago
20 changed files with 929 additions and 43 deletions
@ -0,0 +1,21 @@ |
|||||||
|
using System.Linq.Expressions; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Core.Helper; |
||||||
|
|
||||||
|
|
||||||
|
/*/// <summary> |
||||||
|
/// Context helper for setting properties to one value on entry and another on dispose. |
||||||
|
/// </summary> |
||||||
|
public class ContextManager<T, TProperty> : IDisposable |
||||||
|
{ |
||||||
|
private Accessor accessor; |
||||||
|
|
||||||
|
public ContextManager(Expression<Func<T, TProperty>> expression, T context, TProperty value) |
||||||
|
{ |
||||||
|
var accessorInfo = ((MemberExpression) expression.Body).Member; |
||||||
|
accessor = Accessors.Find(accessorInfo) ?? throw new ArgumentException("Accessor not found", nameof(expression)); |
||||||
|
|
||||||
|
originalValue = (TProperty)propertyInfo.GetValue(context); |
||||||
|
propertyInfo.SetValue(context, value); |
||||||
|
} |
||||||
|
}*/ |
@ -0,0 +1,97 @@ |
|||||||
|
using Avalonia.Controls; |
||||||
|
using Avalonia.Controls.Primitives; |
||||||
|
using Avalonia.VisualTree; |
||||||
|
using FluentAvalonia.UI.Controls; |
||||||
|
using FluentAvalonia.UI.Windowing; |
||||||
|
using Microsoft.Extensions.DependencyInjection; |
||||||
|
using StabilityMatrix.Avalonia; |
||||||
|
using StabilityMatrix.Avalonia.Controls; |
||||||
|
using StabilityMatrix.Avalonia.ViewModels; |
||||||
|
using StabilityMatrix.Avalonia.Views; |
||||||
|
using StabilityMatrix.Avalonia.Views.Dialogs; |
||||||
|
|
||||||
|
namespace StabilityMatrix.UITests; |
||||||
|
|
||||||
|
[UsesVerify] |
||||||
|
public class MainWindowTests |
||||||
|
{ |
||||||
|
private static IServiceProvider Services => App.Services; |
||||||
|
|
||||||
|
private static VerifySettings Settings |
||||||
|
{ |
||||||
|
get |
||||||
|
{ |
||||||
|
var settings = new VerifySettings(); |
||||||
|
settings.IgnoreMembers<MainWindowViewModel>( |
||||||
|
vm => vm.Pages, |
||||||
|
vm => vm.FooterPages, |
||||||
|
vm => vm.CurrentPage |
||||||
|
); |
||||||
|
return settings; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static (AppWindow, MainWindowViewModel) GetMainWindow() |
||||||
|
{ |
||||||
|
var window = Services.GetRequiredService<MainWindow>(); |
||||||
|
var viewModel = Services.GetRequiredService<MainWindowViewModel>(); |
||||||
|
window.DataContext = viewModel; |
||||||
|
|
||||||
|
window.SetDefaultFonts(); |
||||||
|
|
||||||
|
App.VisualRoot = window; |
||||||
|
App.StorageProvider = window.StorageProvider; |
||||||
|
App.Clipboard = window.Clipboard ?? throw new NullReferenceException("Clipboard is null"); |
||||||
|
|
||||||
|
return (window, viewModel); |
||||||
|
} |
||||||
|
|
||||||
|
private static BetterContentDialog? GetWindowDialog(Visual window) |
||||||
|
{ |
||||||
|
return window |
||||||
|
.FindDescendantOfType<VisualLayerManager>() |
||||||
|
?.FindDescendantOfType<OverlayLayer>() |
||||||
|
?.FindDescendantOfType<DialogHost>() |
||||||
|
?.FindDescendantOfType<LayoutTransformControl>() |
||||||
|
?.FindDescendantOfType<VisualLayerManager>() |
||||||
|
?.FindDescendantOfType<BetterContentDialog>(); |
||||||
|
} |
||||||
|
|
||||||
|
[AvaloniaFact] |
||||||
|
public Task MainWindowViewModel_ShouldOk() |
||||||
|
{ |
||||||
|
var viewModel = Services.GetRequiredService<MainWindowViewModel>(); |
||||||
|
|
||||||
|
return Verify(viewModel, Settings); |
||||||
|
} |
||||||
|
|
||||||
|
[AvaloniaFact] |
||||||
|
public async Task MainWindow_ShouldOpen() |
||||||
|
{ |
||||||
|
var (window, vm) = GetMainWindow(); |
||||||
|
|
||||||
|
window.Show(); |
||||||
|
|
||||||
|
// Find the select data directory dialog |
||||||
|
var selectDataDirectoryDialog = await WaitHelper.WaitForNotNullAsync( |
||||||
|
() => GetWindowDialog(window) |
||||||
|
); |
||||||
|
Assert.NotNull(selectDataDirectoryDialog); |
||||||
|
|
||||||
|
// Click continue button |
||||||
|
var continueButton = selectDataDirectoryDialog |
||||||
|
.GetVisualDescendants() |
||||||
|
.OfType<Button>() |
||||||
|
.First(b => b.Content as string == "Continue"); |
||||||
|
continueButton.Command?.Execute(null); |
||||||
|
|
||||||
|
// Find the one click install dialog |
||||||
|
var oneClickDialog = await WaitHelper.WaitForConditionAsync( |
||||||
|
() => GetWindowDialog(window), |
||||||
|
d => d?.Content is OneClickInstallDialog |
||||||
|
); |
||||||
|
Assert.NotNull(oneClickDialog); |
||||||
|
|
||||||
|
await Verify(window, Settings); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,25 @@ |
|||||||
|
using System.Runtime.CompilerServices; |
||||||
|
|
||||||
|
namespace StabilityMatrix.UITests; |
||||||
|
|
||||||
|
public static class ModuleInit |
||||||
|
{ |
||||||
|
[ModuleInitializer] |
||||||
|
public static void Init() => VerifyAvalonia.Initialize(); |
||||||
|
|
||||||
|
[ModuleInitializer] |
||||||
|
public static void InitOther() => VerifierSettings.InitializePlugins(); |
||||||
|
|
||||||
|
[ModuleInitializer] |
||||||
|
public static void ConfigureVerify() |
||||||
|
{ |
||||||
|
DerivePathInfo( |
||||||
|
(sourceFile, projectDirectory, type, method) => |
||||||
|
new PathInfo( |
||||||
|
directory: Path.Combine(projectDirectory, "Snapshots"), |
||||||
|
typeName: type.Name, |
||||||
|
methodName: method.Name |
||||||
|
) |
||||||
|
); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,39 @@ |
|||||||
|
{ |
||||||
|
Greeting: Welcome to Avalonia!, |
||||||
|
ProgressManagerViewModel: { |
||||||
|
Title: Download Manager, |
||||||
|
IconSource: { |
||||||
|
Type: SymbolIconSource |
||||||
|
}, |
||||||
|
IsOpen: false, |
||||||
|
CanNavigateNext: false, |
||||||
|
CanNavigatePrevious: false, |
||||||
|
HasErrors: false |
||||||
|
}, |
||||||
|
UpdateViewModel: { |
||||||
|
IsUpdateAvailable: true, |
||||||
|
UpdateInfo: { |
||||||
|
Version: { |
||||||
|
Major: 2, |
||||||
|
Minor: 999, |
||||||
|
Prerelease: , |
||||||
|
IsPrerelease: false, |
||||||
|
IsRelease: true, |
||||||
|
Metadata: |
||||||
|
}, |
||||||
|
ReleaseDate: DateTimeOffset_1, |
||||||
|
Channel: Stable, |
||||||
|
Type: Normal, |
||||||
|
DownloadUrl: https://example.org, |
||||||
|
ChangelogUrl: https://example.org, |
||||||
|
HashBlake3: 46e11a5216c55d4c9d3c54385f62f3e1022537ae191615237f05e06d6f8690d0, |
||||||
|
Signature: IX5/CCXWJQG0oGkYWVnuF34gTqF/dJSrDrUd6fuNMYnncL39G3HSvkXrjvJvR18MA2rQNB5z13h3/qBSf9c7DA== |
||||||
|
}, |
||||||
|
ShowProgressBar: false, |
||||||
|
CurrentVersionText: v1.0.0, |
||||||
|
NewVersionText: v2.999.0, |
||||||
|
InstallUpdateCommand: UpdateViewModel.InstallUpdate(), |
||||||
|
HasErrors: false |
||||||
|
}, |
||||||
|
HasErrors: false |
||||||
|
} |
After Width: | Height: | Size: 68 KiB |
@ -0,0 +1,444 @@ |
|||||||
|
{ |
||||||
|
Type: MainWindow, |
||||||
|
Title: Stability Matrix, |
||||||
|
Icon: {}, |
||||||
|
Content: { |
||||||
|
Type: Grid, |
||||||
|
Children: [ |
||||||
|
{ |
||||||
|
Type: Grid, |
||||||
|
Background: Transparent, |
||||||
|
Height: 32.0, |
||||||
|
Name: TitleBarHost, |
||||||
|
Children: [ |
||||||
|
{ |
||||||
|
Type: Image, |
||||||
|
Source: { |
||||||
|
Dpi: { |
||||||
|
X: 96.0, |
||||||
|
Y: 96.0, |
||||||
|
Length: 135.7645019878171, |
||||||
|
SquaredLength: 18432.0 |
||||||
|
}, |
||||||
|
Size: { |
||||||
|
AspectRatio: 1.0, |
||||||
|
Width: 256.0, |
||||||
|
Height: 256.0 |
||||||
|
}, |
||||||
|
PixelSize: { |
||||||
|
AspectRatio: 1.0, |
||||||
|
Width: 256, |
||||||
|
Height: 256 |
||||||
|
}, |
||||||
|
Format: { |
||||||
|
BitsPerPixel: 32 |
||||||
|
} |
||||||
|
}, |
||||||
|
IsHitTestVisible: false, |
||||||
|
Width: 18.0, |
||||||
|
Height: 18.0, |
||||||
|
Margin: 12,4,12,4, |
||||||
|
IsVisible: true, |
||||||
|
Name: WindowIcon |
||||||
|
}, |
||||||
|
{ |
||||||
|
Type: TextBlock, |
||||||
|
FontSize: 12.0, |
||||||
|
Text: Stability Matrix, |
||||||
|
IsHitTestVisible: false, |
||||||
|
VerticalAlignment: Center, |
||||||
|
IsVisible: true |
||||||
|
}, |
||||||
|
{ |
||||||
|
Type: Border, |
||||||
|
Padding: 6 |
||||||
|
} |
||||||
|
] |
||||||
|
}, |
||||||
|
{ |
||||||
|
Type: NavigationView, |
||||||
|
Content: { |
||||||
|
Type: Frame, |
||||||
|
Content: { |
||||||
|
Type: LaunchPageView, |
||||||
|
Content: { |
||||||
|
Type: Grid, |
||||||
|
Children: [ |
||||||
|
{ |
||||||
|
Type: Grid, |
||||||
|
Margin: 0,8,0,8, |
||||||
|
Children: [ |
||||||
|
{ |
||||||
|
Type: Grid, |
||||||
|
Margin: 16,8,0,0, |
||||||
|
Children: [ |
||||||
|
{ |
||||||
|
Type: Grid, |
||||||
|
Column: 0, |
||||||
|
Row: 0, |
||||||
|
Name: LaunchButtonGrid, |
||||||
|
Children: [ |
||||||
|
{ |
||||||
|
Type: Button, |
||||||
|
Command: LaunchPageViewModel.LaunchAsync(string command), |
||||||
|
Content: Launch, |
||||||
|
Width: 95.0, |
||||||
|
HorizontalAlignment: Left, |
||||||
|
VerticalAlignment: Stretch, |
||||||
|
IsVisible: false |
||||||
|
}, |
||||||
|
{ |
||||||
|
Type: SplitButton, |
||||||
|
Command: LaunchPageViewModel.LaunchAsync(string command), |
||||||
|
Flyout: { |
||||||
|
Type: FAMenuFlyout |
||||||
|
}, |
||||||
|
Content: Launch, |
||||||
|
Width: 104.0, |
||||||
|
HorizontalAlignment: Left, |
||||||
|
VerticalAlignment: Stretch, |
||||||
|
IsVisible: false |
||||||
|
} |
||||||
|
] |
||||||
|
}, |
||||||
|
{ |
||||||
|
Type: TeachingTip, |
||||||
|
Name: TeachingTip1 |
||||||
|
}, |
||||||
|
{ |
||||||
|
Type: Grid, |
||||||
|
Column: 0, |
||||||
|
Row: 0, |
||||||
|
IsVisible: false, |
||||||
|
Name: StopButtonGrid, |
||||||
|
Children: [ |
||||||
|
{ |
||||||
|
Type: Button, |
||||||
|
Command: {}, |
||||||
|
Content: Stop, |
||||||
|
Width: 95.0, |
||||||
|
HorizontalAlignment: Left, |
||||||
|
VerticalAlignment: Stretch, |
||||||
|
IsVisible: false |
||||||
|
}, |
||||||
|
{ |
||||||
|
Type: Button, |
||||||
|
Command: {}, |
||||||
|
Content: Stop, |
||||||
|
Width: 104.0, |
||||||
|
HorizontalAlignment: Left, |
||||||
|
VerticalAlignment: Stretch, |
||||||
|
IsVisible: false |
||||||
|
} |
||||||
|
] |
||||||
|
}, |
||||||
|
{ |
||||||
|
Type: Button, |
||||||
|
Command: LaunchPageViewModel.Config(), |
||||||
|
Content: { |
||||||
|
Type: SymbolIcon |
||||||
|
}, |
||||||
|
FontSize: 16.0, |
||||||
|
Width: 48.0, |
||||||
|
Margin: 8,0,0,0, |
||||||
|
HorizontalAlignment: Left, |
||||||
|
VerticalAlignment: Stretch |
||||||
|
} |
||||||
|
] |
||||||
|
}, |
||||||
|
{ |
||||||
|
Type: ComboBox, |
||||||
|
SelectedIndex: -1, |
||||||
|
Selection: { |
||||||
|
SingleSelect: true, |
||||||
|
SelectedIndex: -1, |
||||||
|
AnchorIndex: -1 |
||||||
|
}, |
||||||
|
SelectionMode: Single, |
||||||
|
ItemTemplate: { |
||||||
|
DataType: InstalledPackage, |
||||||
|
Content: { |
||||||
|
Type: Func<IServiceProvider, object>, |
||||||
|
Target: XamlIlRuntimeHelpers.<>c__DisplayClass1_0<Control>, |
||||||
|
Method: System.Object DeferredTransformationFactoryV2(System.IServiceProvider) |
||||||
|
} |
||||||
|
}, |
||||||
|
IsEnabled: true, |
||||||
|
Margin: 8,8,0,0, |
||||||
|
HorizontalAlignment: Stretch, |
||||||
|
VerticalAlignment: Top, |
||||||
|
Name: SelectPackageComboBox |
||||||
|
}, |
||||||
|
{ |
||||||
|
Type: ToggleButton, |
||||||
|
IsChecked: true, |
||||||
|
Content: { |
||||||
|
Type: Icon, |
||||||
|
Template: { |
||||||
|
Content: { |
||||||
|
Type: Func<IServiceProvider, object>, |
||||||
|
Target: XamlIlRuntimeHelpers.<>c__DisplayClass1_0<Control>, |
||||||
|
Method: System.Object DeferredTransformationFactoryV2(System.IServiceProvider) |
||||||
|
} |
||||||
|
}, |
||||||
|
RenderTransform: { |
||||||
|
Type: TransformGroup, |
||||||
|
Children: [ |
||||||
|
{ |
||||||
|
Type: RotateTransform |
||||||
|
} |
||||||
|
] |
||||||
|
} |
||||||
|
}, |
||||||
|
FontSize: 16.0, |
||||||
|
Width: 48.0, |
||||||
|
Margin: 8,8,0,0, |
||||||
|
HorizontalAlignment: Left, |
||||||
|
VerticalAlignment: Stretch |
||||||
|
}, |
||||||
|
{ |
||||||
|
Type: ToggleButton, |
||||||
|
IsChecked: false, |
||||||
|
Content: { |
||||||
|
Type: SymbolIcon |
||||||
|
}, |
||||||
|
FontSize: 16.0, |
||||||
|
Width: 48.0, |
||||||
|
Margin: 8,8,16,0, |
||||||
|
HorizontalAlignment: Left, |
||||||
|
VerticalAlignment: Stretch |
||||||
|
} |
||||||
|
] |
||||||
|
}, |
||||||
|
{ |
||||||
|
Type: TextEditor, |
||||||
|
FontFamily: Cascadia Code, |
||||||
|
Margin: 8,8,16,10, |
||||||
|
DataContext: { |
||||||
|
IsUpdatesRunning: false, |
||||||
|
WriteCursorLockTimeout: 00:00:00.1000000, |
||||||
|
Document: { |
||||||
|
_undoStack: { |
||||||
|
IsOriginalFile: true, |
||||||
|
AcceptChanges: true, |
||||||
|
CanUndo: false, |
||||||
|
CanRedo: false, |
||||||
|
SizeLimit: 2147483647 |
||||||
|
}, |
||||||
|
Text: , |
||||||
|
Version: {}, |
||||||
|
IsInUpdate: false, |
||||||
|
Lines: [ |
||||||
|
{ |
||||||
|
IsDeleted: false, |
||||||
|
LineNumber: 1 |
||||||
|
} |
||||||
|
], |
||||||
|
LineTrackers: [ |
||||||
|
{} |
||||||
|
], |
||||||
|
UndoStack: { |
||||||
|
IsOriginalFile: true, |
||||||
|
AcceptChanges: true, |
||||||
|
CanUndo: false, |
||||||
|
CanRedo: false, |
||||||
|
SizeLimit: 2147483647 |
||||||
|
}, |
||||||
|
LineCount: 1 |
||||||
|
} |
||||||
|
}, |
||||||
|
Name: Console |
||||||
|
}, |
||||||
|
{ |
||||||
|
Type: Grid, |
||||||
|
Row: 1, |
||||||
|
Children: [ |
||||||
|
{ |
||||||
|
Type: StackPanel, |
||||||
|
Spacing: 4.0, |
||||||
|
Margin: 8, |
||||||
|
Children: [ |
||||||
|
{ |
||||||
|
Type: InfoBar, |
||||||
|
Margin: 0 |
||||||
|
}, |
||||||
|
{ |
||||||
|
Type: InfoBar, |
||||||
|
Margin: 0 |
||||||
|
} |
||||||
|
] |
||||||
|
} |
||||||
|
] |
||||||
|
}, |
||||||
|
{ |
||||||
|
Type: Button, |
||||||
|
Command: {}, |
||||||
|
Content: Open Web UI, |
||||||
|
FontSize: 12.0, |
||||||
|
Margin: 24,0,24,8, |
||||||
|
HorizontalAlignment: Stretch, |
||||||
|
IsVisible: false |
||||||
|
} |
||||||
|
] |
||||||
|
}, |
||||||
|
DataContext: { |
||||||
|
Title: Launch, |
||||||
|
IconSource: { |
||||||
|
Type: SymbolIconSource |
||||||
|
}, |
||||||
|
Console: { |
||||||
|
IsUpdatesRunning: false, |
||||||
|
WriteCursorLockTimeout: 00:00:00.1000000, |
||||||
|
Document: { |
||||||
|
_undoStack: { |
||||||
|
IsOriginalFile: true, |
||||||
|
AcceptChanges: true, |
||||||
|
CanUndo: false, |
||||||
|
CanRedo: false, |
||||||
|
SizeLimit: 2147483647 |
||||||
|
}, |
||||||
|
Text: , |
||||||
|
Version: {}, |
||||||
|
IsInUpdate: false, |
||||||
|
Lines: [ |
||||||
|
{ |
||||||
|
IsDeleted: false, |
||||||
|
LineNumber: 1 |
||||||
|
} |
||||||
|
], |
||||||
|
LineTrackers: [ |
||||||
|
{} |
||||||
|
], |
||||||
|
UndoStack: { |
||||||
|
IsOriginalFile: true, |
||||||
|
AcceptChanges: true, |
||||||
|
CanUndo: false, |
||||||
|
CanRedo: false, |
||||||
|
SizeLimit: 2147483647 |
||||||
|
}, |
||||||
|
LineCount: 1 |
||||||
|
} |
||||||
|
}, |
||||||
|
LaunchButtonVisibility: false, |
||||||
|
StopButtonVisibility: false, |
||||||
|
IsLaunchTeachingTipsOpen: false, |
||||||
|
ShowWebUiButton: false, |
||||||
|
AutoScrollToEnd: true, |
||||||
|
ShowManualInputPrompt: false, |
||||||
|
ShowConfirmInputPrompt: false, |
||||||
|
LaunchCommand: LaunchPageViewModel.LaunchAsync(string command), |
||||||
|
ConfigCommand: LaunchPageViewModel.Config(), |
||||||
|
SendConfirmInputCommand: LaunchPageViewModel.SendConfirmInput(bool value), |
||||||
|
SendManualInputCommand: LaunchPageViewModel.SendManualInput(string input), |
||||||
|
CanNavigateNext: false, |
||||||
|
CanNavigatePrevious: false, |
||||||
|
HasErrors: false |
||||||
|
} |
||||||
|
}, |
||||||
|
Name: FrameView |
||||||
|
}, |
||||||
|
Name: NavigationView |
||||||
|
}, |
||||||
|
{ |
||||||
|
Type: TeachingTip, |
||||||
|
Name: UpdateAvailableTeachingTip |
||||||
|
} |
||||||
|
] |
||||||
|
}, |
||||||
|
FontFamily: Segoe UI Variable Text, |
||||||
|
Width: 1100.0, |
||||||
|
Height: 750.0, |
||||||
|
IsVisible: true, |
||||||
|
DataContext: { |
||||||
|
Greeting: Welcome to Avalonia!, |
||||||
|
ProgressManagerViewModel: { |
||||||
|
Title: Download Manager, |
||||||
|
IconSource: { |
||||||
|
Type: SymbolIconSource |
||||||
|
}, |
||||||
|
IsOpen: false, |
||||||
|
CanNavigateNext: false, |
||||||
|
CanNavigatePrevious: false, |
||||||
|
HasErrors: false |
||||||
|
}, |
||||||
|
UpdateViewModel: { |
||||||
|
IsUpdateAvailable: true, |
||||||
|
UpdateInfo: { |
||||||
|
Version: { |
||||||
|
Major: 2, |
||||||
|
Minor: 999, |
||||||
|
Prerelease: , |
||||||
|
IsPrerelease: false, |
||||||
|
IsRelease: true, |
||||||
|
Metadata: |
||||||
|
}, |
||||||
|
ReleaseDate: DateTimeOffset_1, |
||||||
|
Channel: Stable, |
||||||
|
Type: Normal, |
||||||
|
DownloadUrl: https://example.org, |
||||||
|
ChangelogUrl: https://example.org, |
||||||
|
HashBlake3: 46e11a5216c55d4c9d3c54385f62f3e1022537ae191615237f05e06d6f8690d0, |
||||||
|
Signature: IX5/CCXWJQG0oGkYWVnuF34gTqF/dJSrDrUd6fuNMYnncL39G3HSvkXrjvJvR18MA2rQNB5z13h3/qBSf9c7DA== |
||||||
|
}, |
||||||
|
ShowProgressBar: false, |
||||||
|
CurrentVersionText: v1.0.0, |
||||||
|
NewVersionText: v2.999.0, |
||||||
|
InstallUpdateCommand: UpdateViewModel.InstallUpdate(), |
||||||
|
HasErrors: false |
||||||
|
}, |
||||||
|
SelectedCategory: { |
||||||
|
Title: Launch, |
||||||
|
IconSource: { |
||||||
|
Type: SymbolIconSource |
||||||
|
}, |
||||||
|
Console: { |
||||||
|
IsUpdatesRunning: false, |
||||||
|
WriteCursorLockTimeout: 00:00:00.1000000, |
||||||
|
Document: { |
||||||
|
_undoStack: { |
||||||
|
IsOriginalFile: true, |
||||||
|
AcceptChanges: true, |
||||||
|
CanUndo: false, |
||||||
|
CanRedo: false, |
||||||
|
SizeLimit: 2147483647 |
||||||
|
}, |
||||||
|
Text: , |
||||||
|
Version: {}, |
||||||
|
IsInUpdate: false, |
||||||
|
Lines: [ |
||||||
|
{ |
||||||
|
IsDeleted: false, |
||||||
|
LineNumber: 1 |
||||||
|
} |
||||||
|
], |
||||||
|
LineTrackers: [ |
||||||
|
{} |
||||||
|
], |
||||||
|
UndoStack: { |
||||||
|
IsOriginalFile: true, |
||||||
|
AcceptChanges: true, |
||||||
|
CanUndo: false, |
||||||
|
CanRedo: false, |
||||||
|
SizeLimit: 2147483647 |
||||||
|
}, |
||||||
|
LineCount: 1 |
||||||
|
} |
||||||
|
}, |
||||||
|
LaunchButtonVisibility: false, |
||||||
|
StopButtonVisibility: false, |
||||||
|
IsLaunchTeachingTipsOpen: false, |
||||||
|
ShowWebUiButton: false, |
||||||
|
AutoScrollToEnd: true, |
||||||
|
ShowManualInputPrompt: false, |
||||||
|
ShowConfirmInputPrompt: false, |
||||||
|
LaunchCommand: LaunchPageViewModel.LaunchAsync(string command), |
||||||
|
ConfigCommand: LaunchPageViewModel.Config(), |
||||||
|
SendConfirmInputCommand: LaunchPageViewModel.SendConfirmInput(bool value), |
||||||
|
SendManualInputCommand: LaunchPageViewModel.SendManualInput(string input), |
||||||
|
CanNavigateNext: false, |
||||||
|
CanNavigatePrevious: false, |
||||||
|
HasErrors: false |
||||||
|
}, |
||||||
|
HasErrors: false |
||||||
|
} |
||||||
|
} |
@ -0,0 +1 @@ |
|||||||
|
|
@ -0,0 +1,37 @@ |
|||||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||||
|
|
||||||
|
<PropertyGroup> |
||||||
|
<TargetFramework>net7.0</TargetFramework> |
||||||
|
<ImplicitUsings>enable</ImplicitUsings> |
||||||
|
<Nullable>enable</Nullable> |
||||||
|
|
||||||
|
<IsPackable>false</IsPackable> |
||||||
|
<IsTestProject>true</IsTestProject> |
||||||
|
</PropertyGroup> |
||||||
|
|
||||||
|
<ItemGroup> |
||||||
|
<PackageReference Include="Avalonia.Headless.XUnit" Version="11.0.5" /> |
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0"/> |
||||||
|
<PackageReference Include="NSubstitute" Version="5.1.0" /> |
||||||
|
<PackageReference Include="Verify" Version="22.1.4" /> |
||||||
|
<PackageReference Include="Verify.CommunityToolkit.Mvvm" Version="0.1.0" /> |
||||||
|
<PackageReference Include="Verify.Avalonia" Version="1.0.1" /> |
||||||
|
<PackageReference Include="Verify.CommunityToolkit.Mvvm" Version="0.1.0" /> |
||||||
|
<PackageReference Include="Verify.Xunit" Version="22.1.4" /> |
||||||
|
<PackageReference Include="xunit" Version="2.6.1"/> |
||||||
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3"> |
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> |
||||||
|
<PrivateAssets>all</PrivateAssets> |
||||||
|
</PackageReference> |
||||||
|
<PackageReference Include="coverlet.collector" Version="3.2.0"> |
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> |
||||||
|
<PrivateAssets>all</PrivateAssets> |
||||||
|
</PackageReference> |
||||||
|
</ItemGroup> |
||||||
|
|
||||||
|
<ItemGroup> |
||||||
|
<ProjectReference Include="..\StabilityMatrix.Avalonia\StabilityMatrix.Avalonia.csproj" /> |
||||||
|
<ProjectReference Include="..\StabilityMatrix.Core\StabilityMatrix.Core.csproj" /> |
||||||
|
</ItemGroup> |
||||||
|
|
||||||
|
</Project> |
@ -0,0 +1,35 @@ |
|||||||
|
using System.Runtime.CompilerServices; |
||||||
|
|
||||||
|
namespace StabilityMatrix.UITests; |
||||||
|
|
||||||
|
public class TempDirFixture : IDisposable |
||||||
|
{ |
||||||
|
public static string ModuleTempDir { get; set; } |
||||||
|
|
||||||
|
static TempDirFixture() |
||||||
|
{ |
||||||
|
var tempDir = Path.Combine(Path.GetTempPath(), "StabilityMatrixTest"); |
||||||
|
Directory.CreateDirectory(tempDir); |
||||||
|
ModuleTempDir = tempDir; |
||||||
|
|
||||||
|
// ReSharper disable once LocalizableElement |
||||||
|
Console.WriteLine($"Using temp dir: {ModuleTempDir}"); |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc /> |
||||||
|
[MethodImpl(MethodImplOptions.Synchronized)] |
||||||
|
public void Dispose() |
||||||
|
{ |
||||||
|
if (Directory.Exists(ModuleTempDir)) |
||||||
|
{ |
||||||
|
// ReSharper disable once LocalizableElement |
||||||
|
Console.WriteLine($"Deleting temp dir: {ModuleTempDir}"); |
||||||
|
Directory.Delete(ModuleTempDir, true); |
||||||
|
} |
||||||
|
|
||||||
|
GC.SuppressFinalize(this); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
[CollectionDefinition("TempDir")] |
||||||
|
public class TempDirCollection : ICollectionFixture<TempDirFixture> { } |
@ -0,0 +1,78 @@ |
|||||||
|
using Microsoft.Extensions.DependencyInjection; |
||||||
|
using NSubstitute; |
||||||
|
using NSubstitute.Extensions; |
||||||
|
using Semver; |
||||||
|
using StabilityMatrix.Avalonia; |
||||||
|
using StabilityMatrix.Avalonia.ViewModels.Dialogs; |
||||||
|
using StabilityMatrix.Core.Helper; |
||||||
|
using StabilityMatrix.Core.Models.Update; |
||||||
|
using StabilityMatrix.Core.Services; |
||||||
|
using StabilityMatrix.Core.Updater; |
||||||
|
using StabilityMatrix.UITests; |
||||||
|
|
||||||
|
[assembly: AvaloniaTestApplication(typeof(TestAppBuilder))] |
||||||
|
|
||||||
|
namespace StabilityMatrix.UITests; |
||||||
|
|
||||||
|
public static class TestAppBuilder |
||||||
|
{ |
||||||
|
public static AppBuilder BuildAvaloniaApp() |
||||||
|
{ |
||||||
|
ConfigureGlobals(); |
||||||
|
|
||||||
|
Program.SetupAvaloniaApp(); |
||||||
|
|
||||||
|
App.BeforeBuildServiceProvider += (_, x) => ConfigureAppServices(x); |
||||||
|
|
||||||
|
return AppBuilder |
||||||
|
.Configure<App>() |
||||||
|
.UseSkia() |
||||||
|
.UseHeadless(new AvaloniaHeadlessPlatformOptions { UseHeadlessDrawing = false }); |
||||||
|
} |
||||||
|
|
||||||
|
private static void ConfigureGlobals() |
||||||
|
{ |
||||||
|
var tempDir = TempDirFixture.ModuleTempDir; |
||||||
|
var globalSettings = Path.Combine(tempDir, "AppDataHome"); |
||||||
|
|
||||||
|
Compat.SetAppDataHome(globalSettings); |
||||||
|
} |
||||||
|
|
||||||
|
private static void ConfigureAppServices(IServiceCollection serviceCollection) |
||||||
|
{ |
||||||
|
// ISettingsManager |
||||||
|
var settingsManager = Substitute.ForPartsOf<SettingsManager>(); |
||||||
|
serviceCollection.AddSingleton<ISettingsManager>(settingsManager); |
||||||
|
|
||||||
|
// IUpdateHelper |
||||||
|
var mockUpdateInfo = new UpdateInfo( |
||||||
|
SemVersion.Parse("2.999.0"), |
||||||
|
DateTimeOffset.UnixEpoch, |
||||||
|
UpdateChannel.Stable, |
||||||
|
UpdateType.Normal, |
||||||
|
"https://example.org", |
||||||
|
"https://example.org", |
||||||
|
"46e11a5216c55d4c9d3c54385f62f3e1022537ae191615237f05e06d6f8690d0", |
||||||
|
"IX5/CCXWJQG0oGkYWVnuF34gTqF/dJSrDrUd6fuNMYnncL39G3HSvkXrjvJvR18MA2rQNB5z13h3/qBSf9c7DA==" |
||||||
|
); |
||||||
|
|
||||||
|
var updateHelper = Substitute.For<IUpdateHelper>(); |
||||||
|
updateHelper |
||||||
|
.Configure() |
||||||
|
.StartCheckingForUpdates() |
||||||
|
.Returns(Task.CompletedTask) |
||||||
|
.AndDoes(_ => EventManager.Instance.OnUpdateAvailable(mockUpdateInfo)); |
||||||
|
|
||||||
|
serviceCollection.AddSingleton(updateHelper); |
||||||
|
|
||||||
|
// UpdateViewModel |
||||||
|
var updateViewModel = Substitute.ForPartsOf<UpdateViewModel>( |
||||||
|
settingsManager, |
||||||
|
null, |
||||||
|
updateHelper |
||||||
|
); |
||||||
|
updateViewModel.Configure().GetReleaseNotes("").Returns("Test"); |
||||||
|
|
||||||
|
serviceCollection.AddSingleton(updateViewModel); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,5 @@ |
|||||||
|
global using Xunit; |
||||||
|
global using Avalonia; |
||||||
|
global using Avalonia.Headless; |
||||||
|
global using Avalonia.Headless.XUnit; |
||||||
|
global using Avalonia.Input; |
@ -0,0 +1,14 @@ |
|||||||
|
using AsyncAwaitBestPractices; |
||||||
|
|
||||||
|
namespace StabilityMatrix.UITests; |
||||||
|
|
||||||
|
internal static class VerifyConfig |
||||||
|
{ |
||||||
|
public static VerifySettings Default { get; } |
||||||
|
|
||||||
|
static VerifyConfig() |
||||||
|
{ |
||||||
|
Default = new VerifySettings(); |
||||||
|
Default.IgnoreMembersWithType<WeakEventManager>(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,51 @@ |
|||||||
|
namespace StabilityMatrix.UITests; |
||||||
|
|
||||||
|
public static class WaitHelper |
||||||
|
{ |
||||||
|
public static async Task<T> WaitForConditionAsync<T>( |
||||||
|
Func<T> getter, |
||||||
|
Func<T, bool> condition, |
||||||
|
int delayMs = 50, |
||||||
|
int maxAttempts = 20, |
||||||
|
int initialDelayMs = 100 |
||||||
|
) |
||||||
|
{ |
||||||
|
await Task.Delay(initialDelayMs); |
||||||
|
|
||||||
|
for (var i = 0; i < maxAttempts; i++) |
||||||
|
{ |
||||||
|
await Task.Delay(delayMs); |
||||||
|
|
||||||
|
var result = getter(); |
||||||
|
|
||||||
|
if (condition(result)) |
||||||
|
{ |
||||||
|
return result; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
throw new TimeoutException("Waited too long for a condition to be met"); |
||||||
|
} |
||||||
|
|
||||||
|
public static async Task<T> WaitForNotNullAsync<T>( |
||||||
|
Func<T?> getter, |
||||||
|
int delayMs = 50, |
||||||
|
int maxAttempts = 20, |
||||||
|
int initialDelayMs = 100 |
||||||
|
) |
||||||
|
{ |
||||||
|
await Task.Delay(initialDelayMs); |
||||||
|
|
||||||
|
for (var i = 0; i < maxAttempts; i++) |
||||||
|
{ |
||||||
|
await Task.Delay(delayMs); |
||||||
|
|
||||||
|
if (getter() is { } result) |
||||||
|
{ |
||||||
|
return result; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
throw new TimeoutException("Waited too long for a non-null value"); |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue