using Microsoft.AspNetCore.Components; using Bunit; using SufiChain.SufiBlazor.Components.Forms; using Xunit; namespace SufiChain.SufiBlazor.Tests.Components.Forms; public class SbNumberFieldTests : BunitContext { private IRenderedComponent> RenderNumberField( Action>>? configure = null) { return Render>(p => { configure?.Invoke(p); }); } [Fact] public void RendersNumberFieldStructure() { // Arrange & Act var cut = RenderNumberField(); // Assert var wrapper = cut.Find(".sb-number-field"); Assert.NotNull(wrapper); Assert.NotNull(cut.Find(".sb-number-field__input-group")); var input = cut.Find("input.sb-number-field__input"); Assert.NotNull(input); Assert.Equal("number", input.GetAttribute("type")); } [Fact] public void RendersLabelWhenProvided() { // Arrange & Act var cut = RenderNumberField(p => p .Add(x => x.Label, "Quantity") .Add(x => x.Id, "qty")); // Assert var label = cut.Find(".sb-number-field__label"); Assert.NotNull(label); Assert.Contains("Quantity", label.TextContent); } [Fact] public void DoesNotRenderLabelWhenEmpty() { // Arrange & Act var cut = RenderNumberField(); // Assert Assert.Empty(cut.FindAll(".sb-number-field__label")); } [Fact] public void RendersRequiredAsteriskWhenRequired() { // Arrange & Act var cut = RenderNumberField(p => p .Add(x => x.Label, "Amount") .Add(x => x.Required, true)); // Assert var required = cut.Find(".sb-number-field__required"); Assert.NotNull(required); Assert.Contains("*", required.TextContent); } [Fact] public void AssociatesLabelWithInputId() { // Arrange & Act var cut = RenderNumberField(p => p .Add(x => x.Label, "Value") .Add(x => x.Id, "value-input")); // Assert var label = cut.Find("label.sb-number-field__label"); Assert.Equal("value-input", label.GetAttribute("for")); } [Fact] public void RendersHelperTextWhenProvided() { // Arrange & Act var cut = RenderNumberField(p => p.Add(x => x.HelperText, "Enter a number between 1 and 100")); // Assert var helper = cut.Find(".sb-number-field__helper"); Assert.NotNull(helper); Assert.Contains("Enter a number between 1 and 100", helper.TextContent); } [Fact] public void DisplaysValueInInput() { // Arrange & Act var cut = RenderNumberField(p => p.Add(x => x.Value, 42)); // Assert var input = cut.Find("input"); Assert.Equal("42", input.GetAttribute("value")); } [Fact] public void AppliesMinMaxStepAttributes() { // Arrange & Act var cut = RenderNumberField(p => p .Add(x => x.Value, 5) .Add(x => x.Min, 0) .Add(x => x.Max, 10) .Add(x => x.Step, 2)); // Assert var input = cut.Find("input"); Assert.Equal("0", input.GetAttribute("min")); Assert.Equal("10", input.GetAttribute("max")); Assert.Equal("2", input.GetAttribute("step")); } [Fact] public void AppliesPlaceholder() { // Arrange & Act var cut = RenderNumberField(p => p.Add(x => x.Placeholder, "0.00")); // Assert var input = cut.Find("input"); Assert.Equal("0.00", input.GetAttribute("placeholder")); } [Fact] public void InputIsDisabledWhenDisabledTrue() { // Arrange & Act var cut = RenderNumberField(p => p.Add(x => x.Disabled, true)); // Assert var input = cut.Find("input"); Assert.NotNull(input.GetAttribute("disabled")); } [Fact] public void InputIsReadOnlyWhenReadOnlyTrue() { // Arrange & Act var cut = RenderNumberField(p => p.Add(x => x.ReadOnly, true)); // Assert var input = cut.Find("input"); Assert.NotNull(input.GetAttribute("readonly")); } [Fact] public void AppliesDisabledClassWhenDisabled() { // Arrange & Act var cut = RenderNumberField(p => p.Add(x => x.Disabled, true)); // Assert var wrapper = cut.Find(".sb-number-field"); Assert.Contains("sb-number-field--disabled", wrapper.ClassList); } [Fact] public void AppliesClassParameter() { // Arrange & Act var cut = RenderNumberField(p => p.Add(x => x.Class, "custom-field")); // Assert var wrapper = cut.Find(".sb-number-field"); Assert.Contains("custom-field", wrapper.ClassList); } [Fact] public void AppliesStyleParameter() { // Arrange & Act var cut = RenderNumberField(p => p.Add(x => x.Style, "max-width: 200px;")); // Assert var wrapper = cut.Find(".sb-number-field"); Assert.Contains("max-width: 200px", wrapper.GetAttribute("style")); } [Fact] public void RendersSpinButtonsByDefault() { // Arrange & Act var cut = RenderNumberField(); // Assert var spinners = cut.FindAll(".sb-number-field__spinner"); Assert.Equal(2, spinners.Count); } [Fact] public void DoesNotRenderSpinButtonsWhenShowSpinButtonsFalse() { // Arrange & Act var cut = RenderNumberField(p => p.Add(x => x.ShowSpinButtons, false)); // Assert Assert.Empty(cut.FindAll(".sb-number-field__spinners")); } [Fact] public async Task InvokesValueChangedWhenInputChanged() { // Arrange int? received = null; var cut = RenderNumberField(p => p .Add(x => x.Value, 10) .Add(x => x.ValueChanged, EventCallback.Factory.Create(this, v => received = v))); // Act var input = cut.Find("input"); await cut.InvokeAsync(() => input!.Change("25")); // Assert Assert.NotNull(received); Assert.Equal(25, received); } [Fact] public async Task InvokesValueChangedWhenIncrementClicked() { // Arrange int? received = null; var cut = RenderNumberField(p => p .Add(x => x.Value, 5) .Add(x => x.ValueChanged, EventCallback.Factory.Create(this, v => received = v))); // Act var incrementBtn = cut.FindAll(".sb-number-field__spinner")[0]; await cut.InvokeAsync(() => incrementBtn!.Click()); // Assert Assert.NotNull(received); Assert.Equal(6, received); } [Fact] public async Task InvokesValueChangedWhenDecrementClicked() { // Arrange int? received = null; var cut = RenderNumberField(p => p .Add(x => x.Value, 5) .Add(x => x.ValueChanged, EventCallback.Factory.Create(this, v => received = v))); // Act var decrementBtn = cut.FindAll(".sb-number-field__spinner")[1]; await cut.InvokeAsync(() => decrementBtn!.Click()); // Assert Assert.NotNull(received); Assert.Equal(4, received); } [Fact] public void IncrementButtonDisabledWhenAtMax() { // Arrange & Act var cut = RenderNumberField(p => p .Add(x => x.Value, 10) .Add(x => x.Max, 10)); // Assert var spinnerButtons = cut.FindAll(".sb-number-field__spinner"); var incrementBtn = spinnerButtons[0]; Assert.NotNull(incrementBtn.GetAttribute("disabled")); } [Fact] public void DecrementButtonDisabledWhenAtMin() { // Arrange & Act var cut = RenderNumberField(p => p .Add(x => x.Value, 0) .Add(x => x.Min, 0)); // Assert var spinnerButtons = cut.FindAll(".sb-number-field__spinner"); var decrementBtn = spinnerButtons[1]; Assert.NotNull(decrementBtn.GetAttribute("disabled")); } [Fact] public void UsesCustomIncreaseDecreaseAriaLabels() { // Arrange & Act var cut = RenderNumberField(p => p .Add(x => x.IncreaseAriaLabel, "Add one") .Add(x => x.DecreaseAriaLabel, "Subtract one")); // Assert var spinners = cut.FindAll(".sb-number-field__spinner"); Assert.Equal("Add one", spinners[0].GetAttribute("aria-label")); Assert.Equal("Subtract one", spinners[1].GetAttribute("aria-label")); } [Fact] public void WorksWithDecimalType() { // Arrange & Act var cut = Render>(p => p .Add(x => x.Value, 3.5m) .Add(x => x.Step, 0.5m)); // Assert var input = cut.Find("input"); Assert.Equal("3.5", input.GetAttribute("value")); Assert.Equal("0.5", input.GetAttribute("step")); } [Fact] public void AppliesAdditionalAttributes() { // Arrange & Act var cut = RenderNumberField(p => p.Add(x => x.AdditionalAttributes, new Dictionary { { "data-testid", "number-input" }, { "aria-label", "Quantity" } })); // Assert var input = cut.Find("input"); Assert.Equal("number-input", input.GetAttribute("data-testid")); Assert.Equal("Quantity", input.GetAttribute("aria-label")); } }