+
Set Up Your Account
Create your daily-use account and administrator credentials.
@@ -53,7 +54,10 @@
private readonly Dictionary
_errors = new();
private readonly HashSet _touched = new();
- /// True when all fields are valid. Used by the wizard host to gate Next.
+ /// Notifies the wizard host whenever validity changes (and on initial mount).
+ [Parameter] public EventCallback OnValidityChanged { get; set; }
+
+ /// True when all fields are valid.
public bool IsValid { get; private set; }
protected override void OnInitialized() => Validate();
@@ -81,5 +85,6 @@
_errors["bitlockerpin"] = "BitLocker PIN must be all digits and at least 6 digits long.";
IsValid = _errors.Count == 0;
+ _ = OnValidityChanged.InvokeAsync(IsValid);
}
}
diff --git a/windows/welcome/tests/SilverOS.Welcome.Tests/AccountStepTests.cs b/windows/welcome/tests/SilverOS.Welcome.Tests/AccountStepTests.cs
new file mode 100644
index 0000000..02dddb0
--- /dev/null
+++ b/windows/welcome/tests/SilverOS.Welcome.Tests/AccountStepTests.cs
@@ -0,0 +1,103 @@
+using Bunit;
+using Microsoft.AspNetCore.Components;
+using Microsoft.Extensions.DependencyInjection;
+using SilverOS.Welcome.App.Components;
+using SilverOS.Welcome.App.Components.Steps;
+using Xunit;
+
+public class AccountStepTests : TestContext
+{
+ // Helper: register WizardState and render AccountStep with an OnValidityChanged capture.
+ private (IRenderedComponent cut, Func lastValidity) RenderStep(WizardState? state = null)
+ {
+ var wizardState = state ?? new WizardState();
+ Services.AddSingleton(wizardState);
+
+ bool? captured = null;
+ var cut = RenderComponent(p =>
+ p.Add(s => s.OnValidityChanged,
+ EventCallback.Factory.Create(this, v => captured = v)));
+
+ return (cut, () => captured);
+ }
+
+ [Fact]
+ public void OnValidityChanged_fires_false_on_initial_mount_with_empty_fields()
+ {
+ var (_, lastValidity) = RenderStep();
+
+ Assert.NotNull(lastValidity());
+ Assert.False(lastValidity(), "Step should be invalid on first mount (empty fields).");
+ }
+
+ [Fact]
+ public void OnValidityChanged_fires_true_after_all_valid_inputs_are_entered()
+ {
+ var (cut, lastValidity) = RenderStep();
+
+ // Simulate user filling in all four fields.
+ cut.Find("#username").Input("alice");
+ cut.Find("#password").Input("Secret1!");
+ cut.Find("#adminpassword").Input("Admin1!");
+ cut.Find("#bitlockerpin").Input("123456");
+
+ Assert.True(lastValidity(), "Step should be valid after all fields are correctly filled.");
+ }
+
+ [Fact]
+ public void OnValidityChanged_fires_false_when_a_field_is_cleared_after_being_valid()
+ {
+ var (cut, lastValidity) = RenderStep();
+
+ cut.Find("#username").Input("alice");
+ cut.Find("#password").Input("Secret1!");
+ cut.Find("#adminpassword").Input("Admin1!");
+ cut.Find("#bitlockerpin").Input("123456");
+
+ Assert.True(lastValidity()); // sanity
+
+ // Clear a required field — must revert to invalid.
+ cut.Find("#username").Input("");
+
+ Assert.False(lastValidity(), "Step should become invalid again when a required field is cleared.");
+ }
+
+ [Fact]
+ public void OnValidityChanged_fires_false_when_pin_is_non_numeric_or_too_short()
+ {
+ var (cut, lastValidity) = RenderStep();
+
+ cut.Find("#username").Input("alice");
+ cut.Find("#password").Input("Secret1!");
+ cut.Find("#adminpassword").Input("Admin1!");
+
+ // Too short — 5 digits.
+ cut.Find("#bitlockerpin").Input("12345");
+ Assert.False(lastValidity(), "PIN with only 5 digits must be invalid.");
+
+ // Non-numeric.
+ cut.Find("#bitlockerpin").Input("abc123");
+ Assert.False(lastValidity(), "Non-numeric PIN must be invalid.");
+
+ // Exactly 6 digits — valid.
+ cut.Find("#bitlockerpin").Input("123456");
+ Assert.True(lastValidity(), "Exactly 6 numeric digits is valid.");
+ }
+
+ [Fact]
+ public void OnValidityChanged_fires_true_on_mount_when_wizard_state_already_populated()
+ {
+ var prefilledState = new WizardState
+ {
+ Username = "alice",
+ Password = "Secret1!",
+ AdminPassword = "Admin1!",
+ BitLockerPin = "123456"
+ };
+
+ var (_, lastValidity) = RenderStep(prefilledState);
+
+ Assert.True(lastValidity(),
+ "Step should fire valid=true on mount when WizardState already has valid values (Back→Forward re-mount).");
+ }
+}