mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-22 00:54:26 +08:00
fix.在线安装器,启动器
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
using Avalonia;
|
||||
using LanMountainDesktop.DesktopEditing;
|
||||
using LanMountainDesktop.Models;
|
||||
using Xunit;
|
||||
|
||||
namespace LanMountainDesktop.Tests;
|
||||
@@ -170,4 +171,123 @@ public sealed class DesktopPlacementMathTests
|
||||
Assert.False(resizeSession.IsPreviewOccludedByComponentLibrary(new Rect(100, 100, 40, 40)));
|
||||
Assert.True(resizeSession.CanCommit);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FusedCenteredPlacement_UsesGridCenterAndComponentSpan()
|
||||
{
|
||||
var grid = new DesktopGridGeometry(
|
||||
Origin: new Point(12, 20),
|
||||
CellSize: 80,
|
||||
CellGap: 8,
|
||||
ColumnCount: 8,
|
||||
RowCount: 6);
|
||||
|
||||
var placement = FusedDesktopPlacementMath.CreateCenteredPlacement(
|
||||
"placement-1",
|
||||
"component-1",
|
||||
grid,
|
||||
widthCells: 4,
|
||||
heightCells: 2);
|
||||
|
||||
Assert.Equal(2, placement.GridColumn);
|
||||
Assert.Equal(2, placement.GridRow);
|
||||
Assert.Equal(4, placement.GridWidthCells);
|
||||
Assert.Equal(2, placement.GridHeightCells);
|
||||
Assert.Equal(188, placement.X, 3);
|
||||
Assert.Equal(196, placement.Y, 3);
|
||||
Assert.Equal(344, placement.Width, 3);
|
||||
Assert.Equal(168, placement.Height, 3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FusedSnapToNearestCell_RoundsAndPersistsGridCoordinates()
|
||||
{
|
||||
var grid = new DesktopGridGeometry(
|
||||
Origin: new Point(10, 10),
|
||||
CellSize: 100,
|
||||
CellGap: 12,
|
||||
ColumnCount: 6,
|
||||
RowCount: 5);
|
||||
var placement = new FusedDesktopComponentPlacementSnapshot
|
||||
{
|
||||
PlacementId = "placement-1",
|
||||
ComponentId = "component-1",
|
||||
Width = 212,
|
||||
Height = 100,
|
||||
GridWidthCells = 2,
|
||||
GridHeightCells = 1
|
||||
};
|
||||
|
||||
var snapped = FusedDesktopPlacementMath.SnapToNearestCell(
|
||||
placement,
|
||||
grid,
|
||||
requestedOrigin: new Point(255, 135));
|
||||
|
||||
Assert.Equal(2, snapped.GridColumn);
|
||||
Assert.Equal(1, snapped.GridRow);
|
||||
Assert.Equal(234, snapped.X, 3);
|
||||
Assert.Equal(122, snapped.Y, 3);
|
||||
Assert.Equal(212, snapped.Width, 3);
|
||||
Assert.Equal(100, snapped.Height, 3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FusedSnapToNearestCell_ClampsInsideGridBounds()
|
||||
{
|
||||
var grid = new DesktopGridGeometry(
|
||||
Origin: default,
|
||||
CellSize: 80,
|
||||
CellGap: 8,
|
||||
ColumnCount: 4,
|
||||
RowCount: 3);
|
||||
var placement = new FusedDesktopComponentPlacementSnapshot
|
||||
{
|
||||
PlacementId = "placement-1",
|
||||
ComponentId = "component-1",
|
||||
Width = 168,
|
||||
Height = 168,
|
||||
GridWidthCells = 2,
|
||||
GridHeightCells = 2
|
||||
};
|
||||
|
||||
var snapped = FusedDesktopPlacementMath.SnapToNearestCell(
|
||||
placement,
|
||||
grid,
|
||||
requestedOrigin: new Point(900, 600));
|
||||
|
||||
Assert.Equal(2, snapped.GridColumn);
|
||||
Assert.Equal(1, snapped.GridRow);
|
||||
Assert.Equal(176, snapped.X, 3);
|
||||
Assert.Equal(88, snapped.Y, 3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FusedSnapToNearestCell_EstimatesMissingSpanFromPixelSize()
|
||||
{
|
||||
var grid = new DesktopGridGeometry(
|
||||
Origin: default,
|
||||
CellSize: 80,
|
||||
CellGap: 8,
|
||||
ColumnCount: 6,
|
||||
RowCount: 6);
|
||||
var placement = new FusedDesktopComponentPlacementSnapshot
|
||||
{
|
||||
PlacementId = "placement-1",
|
||||
ComponentId = "component-1",
|
||||
Width = 168,
|
||||
Height = 256
|
||||
};
|
||||
|
||||
var snapped = FusedDesktopPlacementMath.SnapToNearestCell(
|
||||
placement,
|
||||
grid,
|
||||
requestedOrigin: new Point(90, 180));
|
||||
|
||||
Assert.Equal(2, snapped.GridWidthCells);
|
||||
Assert.Equal(3, snapped.GridHeightCells);
|
||||
Assert.Equal(1, snapped.GridColumn);
|
||||
Assert.Equal(2, snapped.GridRow);
|
||||
Assert.Equal(168, snapped.Width, 3);
|
||||
Assert.Equal(256, snapped.Height, 3);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,13 +56,17 @@ public sealed class OnlineInstallerCoreTests : IDisposable
|
||||
public async Task InstallerWorkflowNavigation_AllowsOnlyUnlockedSteps()
|
||||
{
|
||||
var vm = new MainWindowViewModel(new FakeInstallService(), new PrivacyDeviceIdentityProvider(Path.Combine(_tempRoot, "identity.json")));
|
||||
var deployStep = vm.Steps.Single(step => step.StepId == InstallerStepId.Deploy);
|
||||
|
||||
vm.SelectedStep = vm.Steps.Single(step => step.StepId == InstallerStepId.Deploy);
|
||||
Assert.False(deployStep.IsUnlocked);
|
||||
|
||||
vm.SelectStepCommand.Execute(deployStep);
|
||||
|
||||
Assert.Equal(InstallerStepId.Welcome, vm.CurrentStep);
|
||||
Assert.True(vm.Steps.Single(step => step.StepId == InstallerStepId.Welcome).IsSelected);
|
||||
|
||||
await vm.NextCommand.ExecuteAsync(null);
|
||||
vm.SelectedStep = vm.Steps.Single(step => step.StepId == InstallerStepId.Welcome);
|
||||
vm.SelectStepCommand.Execute(vm.Steps.Single(step => step.StepId == InstallerStepId.Welcome));
|
||||
|
||||
Assert.Equal(InstallerStepId.Welcome, vm.CurrentStep);
|
||||
}
|
||||
@@ -86,7 +90,7 @@ public sealed class OnlineInstallerCoreTests : IDisposable
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BrowseCommand_UsesSelectedLocalFolder()
|
||||
public async Task BrowseCommand_UsesSelectedLocalFolderAsInstallParent()
|
||||
{
|
||||
var selectedPath = Path.Combine(_tempRoot, "selected-install-root");
|
||||
var vm = new MainWindowViewModel(
|
||||
@@ -98,6 +102,23 @@ public sealed class OnlineInstallerCoreTests : IDisposable
|
||||
|
||||
await vm.BrowseCommand.ExecuteAsync(null);
|
||||
|
||||
Assert.Equal(Path.Combine(selectedPath, InstallerPathGuard.ApplicationDirectoryName), vm.InstallPath);
|
||||
Assert.Null(vm.ErrorMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BrowseCommand_DoesNotDuplicateApplicationFolder()
|
||||
{
|
||||
var selectedPath = Path.Combine(_tempRoot, InstallerPathGuard.ApplicationDirectoryName);
|
||||
var vm = new MainWindowViewModel(
|
||||
new FakeInstallService(),
|
||||
new PrivacyDeviceIdentityProvider(Path.Combine(_tempRoot, "identity.json")))
|
||||
{
|
||||
BrowseRequested = _ => Task.FromResult<string?>(selectedPath)
|
||||
};
|
||||
|
||||
await vm.BrowseCommand.ExecuteAsync(null);
|
||||
|
||||
Assert.Equal(selectedPath, vm.InstallPath);
|
||||
Assert.Null(vm.ErrorMessage);
|
||||
}
|
||||
|
||||
118
LanMountainDesktop.Tests/OobeSessionCommitServiceTests.cs
Normal file
118
LanMountainDesktop.Tests/OobeSessionCommitServiceTests.cs
Normal file
@@ -0,0 +1,118 @@
|
||||
using LanMountainDesktop.Launcher;
|
||||
using LanMountainDesktop.Launcher.Models;
|
||||
using Xunit;
|
||||
|
||||
namespace LanMountainDesktop.Tests;
|
||||
|
||||
public sealed class OobeSessionCommitServiceTests : IDisposable
|
||||
{
|
||||
private readonly string _tempRoot = Path.Combine(
|
||||
Path.GetTempPath(),
|
||||
"LanMountainDesktop.Tests",
|
||||
nameof(OobeSessionCommitServiceTests),
|
||||
Guid.NewGuid().ToString("N"));
|
||||
|
||||
[Fact]
|
||||
public void ResolveDataRoot_ForChoice_DoesNotWriteConfigOrState()
|
||||
{
|
||||
var resolver = new DataLocationResolver(_tempRoot);
|
||||
|
||||
var dataRoot = resolver.ResolveDataRoot(DataLocationMode.Portable);
|
||||
|
||||
Assert.Equal(Path.Combine(_tempRoot, "Desktop"), dataRoot);
|
||||
Assert.False(File.Exists(resolver.ResolveConfigPath()));
|
||||
Assert.False(File.Exists(GetCompletedStatePath(dataRoot)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Commit_WritesSettingsAndCompletedState_OnlyAfterFinalDraft()
|
||||
{
|
||||
var resolver = new DataLocationResolver(_tempRoot);
|
||||
var oobeState = new OobeStateService(
|
||||
_tempRoot,
|
||||
executionSnapshot: new LauncherExecutionSnapshot(false, "tester", "S-1-5-test"));
|
||||
var context = CommandContext.FromArgs(["launch"]);
|
||||
var service = new OobeSessionCommitService(
|
||||
resolver,
|
||||
oobeState,
|
||||
context,
|
||||
setWindowsStartup: _ => true);
|
||||
var draft = CreateDraft();
|
||||
|
||||
var result = service.Commit(draft);
|
||||
var dataRoot = resolver.ResolveDataRoot();
|
||||
|
||||
Assert.True(result.Success);
|
||||
Assert.True(File.Exists(resolver.ResolveConfigPath()));
|
||||
Assert.True(File.Exists(HostAppSettingsOobeMerger.GetSettingsFilePath(dataRoot)));
|
||||
Assert.True(File.Exists(Path.Combine(resolver.ResolveLauncherDataPath(), "privacy-config.json")));
|
||||
Assert.True(File.Exists(Path.Combine(resolver.ResolveLauncherDataPath(), "privacy-agreement.state.json")));
|
||||
Assert.True(File.Exists(GetCompletedStatePath(dataRoot)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Commit_DoesNotWriteCompletedState_WhenFinalSaveFails()
|
||||
{
|
||||
if (!OperatingSystem.IsWindows())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var resolver = new DataLocationResolver(_tempRoot);
|
||||
var oobeState = new OobeStateService(
|
||||
_tempRoot,
|
||||
executionSnapshot: new LauncherExecutionSnapshot(false, "tester", "S-1-5-test"));
|
||||
var context = CommandContext.FromArgs(["launch"]);
|
||||
var service = new OobeSessionCommitService(
|
||||
resolver,
|
||||
oobeState,
|
||||
context,
|
||||
setWindowsStartup: _ => false);
|
||||
|
||||
var result = service.Commit(CreateDraft());
|
||||
var dataRoot = resolver.ResolveDataRoot();
|
||||
|
||||
Assert.False(result.Success);
|
||||
Assert.Equal("windows_startup_save_failed", result.ResultCode);
|
||||
Assert.False(File.Exists(GetCompletedStatePath(dataRoot)));
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Directory.Exists(_tempRoot))
|
||||
{
|
||||
Directory.Delete(_tempRoot, recursive: true);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private static OobeSessionDraft CreateDraft() =>
|
||||
new()
|
||||
{
|
||||
DataLocationMode = DataLocationMode.Portable,
|
||||
MigrateExistingData = false,
|
||||
StartupChoices = new HostAppSettingsStartupChoices(
|
||||
ShowInTaskbar: true,
|
||||
EnableFadeTransition: true,
|
||||
EnableSlideTransition: false,
|
||||
FusedPopupExperience: false,
|
||||
AutoStartWithWindows: false),
|
||||
PrivacyConfig = new PrivacyConfig
|
||||
{
|
||||
CrashTelemetryEnabled = false,
|
||||
UsageTelemetryEnabled = false,
|
||||
TelemetryId = "test-telemetry"
|
||||
},
|
||||
PrivacyAgreementAccepted = true,
|
||||
PrivacyUserId = "test-telemetry",
|
||||
PrivacyDeviceId = "test-device"
|
||||
};
|
||||
|
||||
private static string GetCompletedStatePath(string dataRoot) =>
|
||||
Path.Combine(dataRoot, "Launcher", "state", "oobe-state.json");
|
||||
}
|
||||
@@ -66,16 +66,80 @@ public sealed class OobeStateServiceTests : IDisposable
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Evaluate_SuppressesOobe_ForElevatedFirstRun()
|
||||
public void Evaluate_ReturnsFirstRun_ForElevatedFirstRun()
|
||||
{
|
||||
var service = CreateService(new LauncherExecutionSnapshot(true, "tester", "S-1-5-test"));
|
||||
var context = CommandContext.FromArgs(["launch"]);
|
||||
|
||||
var decision = service.Evaluate(context);
|
||||
|
||||
Assert.Equal(OobeStateStatus.FirstRun, decision.Status);
|
||||
Assert.True(decision.ShouldShowOobe);
|
||||
Assert.True(decision.IsElevated);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Evaluate_ReturnsFirstRun_ForElevatedPostInstall()
|
||||
{
|
||||
var service = CreateService(new LauncherExecutionSnapshot(true, "tester", "S-1-5-test"));
|
||||
var context = CommandContext.FromArgs(["launch", "--launch-source", "postinstall"]);
|
||||
|
||||
var decision = service.Evaluate(context);
|
||||
|
||||
Assert.Equal(OobeStateStatus.FirstRun, decision.Status);
|
||||
Assert.True(decision.ShouldShowOobe);
|
||||
Assert.Equal("postinstall", decision.LaunchSource);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Evaluate_SuppressesOobe_ForMaintenanceLaunch()
|
||||
{
|
||||
var service = CreateService();
|
||||
var context = CommandContext.FromArgs(["plugin", "install", "--source", "x", "--plugins-dir", "p"]);
|
||||
|
||||
var decision = service.Evaluate(context);
|
||||
|
||||
Assert.Equal(OobeStateStatus.Suppressed, decision.Status);
|
||||
Assert.False(decision.ShouldShowOobe);
|
||||
Assert.Equal("oobe_suppressed_elevated", decision.ResultCode);
|
||||
Assert.Equal("oobe_suppressed_maintenance", decision.ResultCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Evaluate_SuppressesOobe_ForDebugPreview()
|
||||
{
|
||||
var service = CreateService();
|
||||
var context = CommandContext.FromArgs(["preview-oobe"]);
|
||||
|
||||
var decision = service.Evaluate(context);
|
||||
|
||||
Assert.Equal(OobeStateStatus.Suppressed, decision.Status);
|
||||
Assert.False(decision.ShouldShowOobe);
|
||||
Assert.Equal("oobe_suppressed_debug_preview", decision.ResultCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Evaluate_MigratesLegacyStateFile_ToCurrentStatePath()
|
||||
{
|
||||
var legacyStatePath = GetLegacyStatePath();
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(legacyStatePath)!);
|
||||
var state = new OobeStateFile
|
||||
{
|
||||
SchemaVersion = 1,
|
||||
CompletedAtUtc = DateTimeOffset.UtcNow.ToString("O"),
|
||||
UserName = "tester",
|
||||
UserSid = "S-1-5-test",
|
||||
LaunchSource = "normal"
|
||||
};
|
||||
File.WriteAllText(legacyStatePath, JsonSerializer.Serialize(state));
|
||||
|
||||
var service = CreateService();
|
||||
var context = CommandContext.FromArgs(["launch"]);
|
||||
|
||||
var decision = service.Evaluate(context);
|
||||
|
||||
Assert.Equal(OobeStateStatus.Completed, decision.Status);
|
||||
Assert.True(decision.MigratedLegacyMarker);
|
||||
Assert.True(File.Exists(GetStatePath()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -119,5 +183,7 @@ public sealed class OobeStateServiceTests : IDisposable
|
||||
|
||||
private string GetStatePath() => Path.Combine(_tempRoot, "Launcher", "state", "oobe-state.json");
|
||||
|
||||
private string GetLegacyStatePath() => Path.Combine(_tempRoot, ".launcher", "state", "oobe-state.json");
|
||||
|
||||
private string GetLegacyMarkerPath() => Path.Combine(_tempRoot, ".launcher", "state", "first_run_completed");
|
||||
}
|
||||
|
||||
47
LanMountainDesktop.Tests/UpdateInstallGatewayTests.cs
Normal file
47
LanMountainDesktop.Tests/UpdateInstallGatewayTests.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using System.IO;
|
||||
using Xunit;
|
||||
|
||||
namespace LanMountainDesktop.Tests;
|
||||
|
||||
public sealed class UpdateInstallGatewayTests
|
||||
{
|
||||
[Fact]
|
||||
public void GetDirectoryName_ReturnsNull_ForRootPath()
|
||||
{
|
||||
// 验证 Path.GetDirectoryName 在根路径场景下的行为
|
||||
var rootPath = Path.GetPathRoot(Path.GetTempPath()) ?? "C:\\";
|
||||
var installerPath = Path.Combine(rootPath, "installer.exe");
|
||||
|
||||
// 根路径下的文件,GetDirectoryName 返回根路径本身(不是 null)
|
||||
var result = Path.GetDirectoryName(installerPath);
|
||||
|
||||
// 在 Windows 上,根路径文件返回根路径(如 "C:\"),不是 null
|
||||
// 但如果 installerPath 本身就是根路径(无文件名),则返回 null
|
||||
Assert.NotNull(result); // "C:\installer.exe" 的目录是 "C:\"
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetDirectoryName_ReturnsNull_ForPathWithoutDirectory()
|
||||
{
|
||||
// 验证极端场景:路径没有目录部分
|
||||
// 这种情况在实际中很少发生,但代码应该能处理
|
||||
var fileNameOnly = "installer.exe";
|
||||
var result = Path.GetDirectoryName(fileNameOnly);
|
||||
|
||||
// 只有文件名没有路径时,GetDirectoryName 返回 null
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WorkingDirectoryFallback_ShouldUseValidDirectory()
|
||||
{
|
||||
// 验证修复后的逻辑:当 GetDirectoryName 返回 null 时,
|
||||
// 应该使用 AppContext.BaseDirectory 作为后备值
|
||||
var installerPath = "installer.exe"; // 模拟只有文件名的情况
|
||||
var workingDir = Path.GetDirectoryName(installerPath) ?? AppContext.BaseDirectory;
|
||||
|
||||
// 后备值应该是有效的目录路径
|
||||
Assert.NotNull(workingDir);
|
||||
Assert.True(Directory.Exists(workingDir) || workingDir == AppContext.BaseDirectory);
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,8 @@ public sealed class UpdateSettingsInterfaceTests
|
||||
Assert.Equal(1, update.CheckCalls);
|
||||
Assert.Equal("1.2.3", viewModel.LatestVersionText);
|
||||
Assert.True(viewModel.IsDeltaUpdate);
|
||||
Assert.True(viewModel.CanDownload);
|
||||
Assert.True(viewModel.IsProgressSectionVisible);
|
||||
|
||||
update.SetPhase(UpdatePhase.Checked);
|
||||
await ((IAsyncRelayCommand)viewModel.DownloadCommand).ExecuteAsync(null);
|
||||
@@ -62,6 +64,36 @@ public sealed class UpdateSettingsInterfaceTests
|
||||
Assert.Equal(1, update.CancelCalls);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateSettingsViewModel_WhenCheckFailsInCheckedPhase_DoesNotExposeDownload()
|
||||
{
|
||||
var update = new FakeUpdateSettingsService
|
||||
{
|
||||
CheckReport = new UpdateCheckReport(
|
||||
false,
|
||||
null,
|
||||
"1.0.0",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
"No usable update manifest was found.")
|
||||
};
|
||||
var viewModel = new UpdateSettingsViewModel(new FakeSettingsFacade(update));
|
||||
viewModel.IsUpdateAvailable = true;
|
||||
viewModel.LatestVersionText = "9.9.9";
|
||||
|
||||
await ((IAsyncRelayCommand)viewModel.CheckCommand).ExecuteAsync(null);
|
||||
|
||||
Assert.False(viewModel.IsUpdateAvailable);
|
||||
Assert.Empty(viewModel.LatestVersionText);
|
||||
Assert.False(viewModel.CanDownload);
|
||||
Assert.False(viewModel.IsProgressSectionVisible);
|
||||
Assert.Equal(0, update.DownloadCalls);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UpdateSettingsViewModel_SavesPreferencesThroughUpdateSettingsService()
|
||||
{
|
||||
@@ -140,6 +172,32 @@ public sealed class UpdateSettingsInterfaceTests
|
||||
Assert.False(orchestratorCreated);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateSettingsService_WhenPlondsCheckFails_ReturnsIdleAndNoDownload()
|
||||
{
|
||||
var settings = new FakeSettingsService
|
||||
{
|
||||
Snapshot =
|
||||
{
|
||||
UpdateDownloadSource = UpdateSettingsValues.DownloadSourcePlonds
|
||||
}
|
||||
};
|
||||
var plonds = new FakePlondsService
|
||||
{
|
||||
LatestResult = PlondsLatestResult.Failed(new Version(1, 0, 0), "No usable PLONDS manifest was found.")
|
||||
};
|
||||
var service = new UpdateSettingsService(
|
||||
settings,
|
||||
orchestratorFactory: () => throw new InvalidOperationException("not used"),
|
||||
plondsService: plonds);
|
||||
|
||||
var report = await service.CheckAsync(CancellationToken.None);
|
||||
|
||||
Assert.False(report.IsUpdateAvailable);
|
||||
Assert.Equal("No usable PLONDS manifest was found.", report.ErrorMessage);
|
||||
Assert.Equal(UpdatePhase.Idle, service.CurrentPhase);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateSettingsService_WhenPlondsManifestRequiresCleanInstall_ReportsFullInstaller()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user