feat(toolbox): first-run auto-applies the collected preconfig (no manual walkthrough)
This commit is contained in:
@@ -60,7 +60,7 @@ else
|
||||
<PrefsStep />
|
||||
break;
|
||||
case 4:
|
||||
<ApplyStep OnComplete="AdvanceToDone" OnRunningChanged="@(v => { _applyRunning = v; StateHasChanged(); })" />
|
||||
<ApplyStep AutoStart="_autoApply" OnComplete="AdvanceToDone" OnRunningChanged="@(v => { _applyRunning = v; StateHasChanged(); })" />
|
||||
break;
|
||||
case 5:
|
||||
<DoneStep />
|
||||
@@ -104,6 +104,7 @@ else
|
||||
private bool _loading = true;
|
||||
private bool _applyRunning = false;
|
||||
private bool _toolboxHome = false;
|
||||
private bool _autoApply = false;
|
||||
private string? _error;
|
||||
private IReadOnlyList<FlavourManifest> _flavours = Array.Empty<FlavourManifest>();
|
||||
|
||||
@@ -167,6 +168,12 @@ else
|
||||
|
||||
if (pre.Bitlocker.Enable && !string.IsNullOrEmpty(pre.Bitlocker.Pin))
|
||||
State.BitLockerPin = pre.Bitlocker.Pin;
|
||||
|
||||
// First run with a preconfig: skip the manual walkthrough. Jump straight to the
|
||||
// Apply step and signal it to auto-start (spec §4d: auto-runs once, shows progress
|
||||
// + recovery key, then Done).
|
||||
_currentStep = 4;
|
||||
_autoApply = true;
|
||||
}
|
||||
|
||||
private void ReRunSetup()
|
||||
|
||||
@@ -50,8 +50,10 @@
|
||||
@code {
|
||||
[Parameter] public EventCallback OnComplete { get; set; }
|
||||
[Parameter] public EventCallback<bool> OnRunningChanged { get; set; }
|
||||
[Parameter] public bool AutoStart { get; set; }
|
||||
|
||||
private bool _running;
|
||||
private bool _autoStarted;
|
||||
private bool _complete;
|
||||
private int _percent;
|
||||
private string _stageLabel = "Preparing…";
|
||||
@@ -71,6 +73,18 @@
|
||||
: single[..ErrorDisplayMaxLength] + "…";
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
// First-run auto-apply: when the host jumps straight to this step with AutoStart,
|
||||
// kick off the same apply the Start button would, exactly once. The manual path
|
||||
// (AutoStart=false) is untouched.
|
||||
if (firstRender && AutoStart && !_autoStarted)
|
||||
{
|
||||
_autoStarted = true;
|
||||
await StartAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task StartAsync()
|
||||
{
|
||||
// Re-entrancy guard: prevent a second overlapping apply if already running
|
||||
|
||||
@@ -73,6 +73,33 @@ public class ApplyStepTests : TestContext
|
||||
Assert.True(completed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AutoStart_triggers_apply_once_without_a_button_click()
|
||||
{
|
||||
var apply = new Mock<IApplyService>();
|
||||
apply.Setup(a => a.RunAsync(It.IsAny<ApplyRequest>(), It.IsAny<IProgress<ApplyProgress>>(), It.IsAny<CancellationToken>()))
|
||||
.Returns(Task.CompletedTask);
|
||||
var state = new WizardState
|
||||
{
|
||||
Flavour = new FlavourManifest { Id = "daily-driver", Hardening = new() { Modules = new[] { "00" } } },
|
||||
BitLockerPin = "123456"
|
||||
};
|
||||
Services.AddSingleton(state);
|
||||
Services.AddSingleton(apply.Object);
|
||||
AddCatalog(Services);
|
||||
AddPreconfig(Services);
|
||||
|
||||
// AutoStart=true should fire StartAsync from OnAfterRenderAsync on first render,
|
||||
// with no Start button click.
|
||||
var cut = RenderComponent<ApplyStep>(p => p.Add(s => s.AutoStart, true));
|
||||
|
||||
cut.WaitForAssertion(() =>
|
||||
apply.Verify(a => a.RunAsync(
|
||||
It.Is<ApplyRequest>(r => r.Flavour.Id == "daily-driver"),
|
||||
It.IsAny<IProgress<ApplyProgress>>(),
|
||||
It.IsAny<CancellationToken>()), Times.Once));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Shows_error_and_retry_button_when_apply_fails()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user