using Microsoft.AspNetCore.Components; using Bunit; using SufiChain.SufiBlazor.Components; using SufiChain.SufiBlazor.Components.Forms; using Xunit; namespace SufiChain.SufiBlazor.Tests.Components.Forms; public class SbRadioTests : BunitContext { private IRenderedComponent> RenderRadioGroup( Action>>? configure = null) { return Render>(p => { p.AddChildContent(builder => { builder.OpenComponent>(0); builder.AddAttribute(1, "Value", "a"); builder.AddAttribute(2, "Label", "Option A"); builder.CloseComponent(); builder.OpenComponent>(3); builder.AddAttribute(4, "Value", "b"); builder.AddAttribute(5, "Label", "Option B"); builder.CloseComponent(); builder.OpenComponent>(6); builder.AddAttribute(7, "Value", "c"); builder.AddAttribute(8, "Label", "Option C"); builder.CloseComponent(); }); configure?.Invoke(p); }); } [Fact] public void RendersRadioStructure() { // Arrange & Act var cut = RenderRadioGroup(); // Assert var radios = cut.FindAll("label.sb-radio"); Assert.Equal(3, radios.Count); Assert.NotNull(cut.Find("input.sb-radio__input[type='radio']")); Assert.NotNull(cut.Find(".sb-radio__circle")); Assert.NotNull(cut.Find(".sb-radio__dot")); Assert.NotNull(cut.Find(".sb-radio__text")); } [Fact] public void RendersLabelWhenProvided() { // Arrange & Act var cut = RenderRadioGroup(); // Assert var labels = cut.FindAll(".sb-radio__label"); Assert.Contains(labels, l => l.TextContent.Contains("Option A")); Assert.Contains(labels, l => l.TextContent.Contains("Option B")); } [Fact] public void RendersCheckedClassWhenValueMatches() { // Arrange & Act var cut = RenderRadioGroup(p => p.Add(x => x.Value, "b")); // Assert var radios = cut.FindAll("label.sb-radio"); var checkedRadio = radios.FirstOrDefault(r => r.ClassList.Contains("sb-radio--checked")); Assert.NotNull(checkedRadio); Assert.Contains("Option B", checkedRadio.TextContent); } [Fact] public void InputHasCheckedAttributeWhenSelected() { // Arrange & Act var cut = RenderRadioGroup(p => p.Add(x => x.Value, "a")); // Assert var inputs = cut.FindAll("input.sb-radio__input"); var checkedInput = inputs.FirstOrDefault(i => i.GetAttribute("value") == "a"); Assert.NotNull(checkedInput); Assert.NotNull(checkedInput.GetAttribute("checked")); } [Fact] public void InputsShareSameNameFromGroup() { // Arrange & Act var cut = RenderRadioGroup(p => p.Add(x => x.Name, "test-group")); // Assert var inputs = cut.FindAll("input[type='radio']"); var name = inputs[0].GetAttribute("name"); Assert.NotNull(name); Assert.All(inputs, i => Assert.Equal(name, i.GetAttribute("name"))); } [Fact] public void RendersHelperTextWhenProvided() { // Arrange & Act var cut = Render>(p => p .Add(x => x.Value, "pro") .AddChildContent(builder => { builder.OpenComponent>(0); builder.AddAttribute(1, "Value", "free"); builder.AddAttribute(2, "Label", "Free"); builder.AddAttribute(3, "HelperText", "Basic features"); builder.CloseComponent(); builder.OpenComponent>(4); builder.AddAttribute(5, "Value", "pro"); builder.AddAttribute(6, "Label", "Pro"); builder.AddAttribute(7, "HelperText", "Advanced features"); builder.CloseComponent(); })); // Assert var helpers = cut.FindAll(".sb-radio__helper"); Assert.Equal(2, helpers.Count); Assert.Contains(helpers, h => h.TextContent.Contains("Basic features")); Assert.Contains(helpers, h => h.TextContent.Contains("Advanced features")); } [Fact] public void RendersChildContentInsteadOfLabel() { // Arrange & Act var cut = Render>(p => p .AddChildContent(builder => { builder.OpenComponent>(0); builder.AddAttribute(1, "Value", "custom"); builder.AddAttribute(2, "ChildContent", (RenderFragment)(b => b.AddMarkupContent(0, "Custom option"))); builder.CloseComponent(); })); // Assert var labelSpan = cut.Find(".sb-radio__label"); Assert.NotNull(labelSpan); Assert.Contains("Custom option", labelSpan.InnerHtml); } [Fact] public void AppliesDisabledClassWhenRadioDisabled() { // Arrange & Act var cut = Render>(p => p .AddChildContent(builder => { builder.OpenComponent>(0); builder.AddAttribute(1, "Value", "enabled"); builder.AddAttribute(2, "Label", "Enabled"); builder.CloseComponent(); builder.OpenComponent>(3); builder.AddAttribute(4, "Value", "disabled"); builder.AddAttribute(5, "Label", "Disabled"); builder.AddAttribute(6, "Disabled", true); builder.CloseComponent(); })); // Assert var radios = cut.FindAll("label.sb-radio"); var disabledRadio = radios.FirstOrDefault(r => r.ClassList.Contains("sb-radio--disabled")); Assert.NotNull(disabledRadio); Assert.Contains("Disabled", disabledRadio.TextContent); } [Fact] public void AppliesDisabledClassWhenGroupDisabled() { // Arrange & Act var cut = RenderRadioGroup(p => p.Add(x => x.Disabled, true)); // Assert var radios = cut.FindAll("label.sb-radio"); Assert.All(radios, r => Assert.Contains("sb-radio--disabled", r.ClassList)); } [Fact] public void AppliesClassParameter() { // Arrange & Act var cut = Render>(p => p .AddChildContent(builder => { builder.OpenComponent>(0); builder.AddAttribute(1, "Value", "x"); builder.AddAttribute(2, "Label", "X"); builder.AddAttribute(3, "Class", "my-radio"); builder.CloseComponent(); })); // Assert var radio = cut.Find("label.sb-radio"); Assert.Contains("my-radio", radio.ClassList); } [Fact] public void AppliesIdToInput() { // Arrange & Act var cut = Render>(p => p .AddChildContent(builder => { builder.OpenComponent>(0); builder.AddAttribute(1, "Value", "x"); builder.AddAttribute(2, "Label", "X"); builder.AddAttribute(3, "Id", "radio-x"); builder.CloseComponent(); })); // Assert var input = cut.Find("input.sb-radio__input"); Assert.Equal("radio-x", input.GetAttribute("id")); } [Fact] public async Task InvokesValueChangedWhenRadioSelected() { // Arrange string? received = null; var cut = RenderRadioGroup(p => p .Add(x => x.Value, "a") .Add(x => x.ValueChanged, EventCallback.Factory.Create(this, v => received = v))); // Act - select Option B via input change (radio uses @onchange) var inputB = cut.FindAll("input.sb-radio__input").First(i => i.GetAttribute("value") == "b"); await cut.InvokeAsync(() => inputB!.Change("b")); // Assert Assert.Equal("b", received); } [Fact] public void RendersRadioGroupStructure() { // Arrange & Act var cut = RenderRadioGroup(p => p.Add(x => x.Label, "Choose one")); // Assert var group = cut.Find(".sb-radio-group"); Assert.NotNull(group); Assert.Equal("radiogroup", group.GetAttribute("role")); Assert.NotNull(cut.Find(".sb-radio-group__label")); Assert.Contains("Choose one", cut.Markup); } [Fact] public void AppliesAdditionalAttributesToInput() { // Arrange & Act var cut = Render>(p => p .AddChildContent(builder => { builder.OpenComponent>(0); builder.AddAttribute(1, "Value", "x"); builder.AddAttribute(2, "Label", "X"); builder.AddAttribute(3, "AdditionalAttributes", new Dictionary { { "data-testid", "radio-x" }, { "aria-label", "Select X" } }); builder.CloseComponent(); })); // Assert var input = cut.Find("input.sb-radio__input"); Assert.Equal("radio-x", input.GetAttribute("data-testid")); Assert.Equal("Select X", input.GetAttribute("aria-label")); } // SbRadioGroup-specific tests [Fact] public void AppliesVerticalOrientationByDefault() { // Arrange & Act var cut = RenderRadioGroup(); // Assert var group = cut.Find(".sb-radio-group"); Assert.Contains("sb-radio-group--vertical", group.ClassList); } [Fact] public void AppliesHorizontalOrientationWhenSet() { // Arrange & Act var cut = RenderRadioGroup(p => p.Add(x => x.Orientation, SbOrientation.Horizontal)); // Assert var group = cut.Find(".sb-radio-group"); Assert.Contains("sb-radio-group--horizontal", group.ClassList); } [Fact] public void UsesAriaLabelWhenProvided() { // Arrange & Act var cut = RenderRadioGroup(p => p .Add(x => x.AriaLabel, "Select an option") .Add(x => x.Label, "Choose one")); // Assert - AriaLabel takes precedence over Label var group = cut.Find(".sb-radio-group"); Assert.Equal("Select an option", group.GetAttribute("aria-label")); } [Fact] public void UsesLabelAsAriaLabelWhenAriaLabelNotSet() { // Arrange & Act var cut = RenderRadioGroup(p => p.Add(x => x.Label, "Choose one")); // Assert var group = cut.Find(".sb-radio-group"); Assert.Equal("Choose one", group.GetAttribute("aria-label")); } [Fact] public void AppliesGroupClassParameter() { // Arrange & Act var cut = RenderRadioGroup(p => p.Add(x => x.Class, "horizontal-group")); // Assert var group = cut.Find(".sb-radio-group"); Assert.Contains("horizontal-group", group.ClassList); } [Fact] public void AppliesGroupStyleParameter() { // Arrange & Act var cut = RenderRadioGroup(p => p.Add(x => x.Style, "gap: 1rem;")); // Assert var group = cut.Find(".sb-radio-group"); Assert.Contains("gap: 1rem", group.GetAttribute("style")); } [Fact] public void AppliesGroupAdditionalAttributes() { // Arrange & Act var cut = RenderRadioGroup(p => p.Add(x => x.AdditionalAttributes, new Dictionary { { "data-testid", "radio-group" }, { "aria-describedby", "group-desc" } })); // Assert var group = cut.Find(".sb-radio-group"); Assert.Equal("radio-group", group.GetAttribute("data-testid")); Assert.Equal("group-desc", group.GetAttribute("aria-describedby")); } [Fact] public void AppliesGroupDisabledClassWhenDisabled() { // Arrange & Act var cut = RenderRadioGroup(p => p.Add(x => x.Disabled, true)); // Assert var group = cut.Find(".sb-radio-group"); Assert.Contains("sb-radio-group--disabled", group.ClassList); } }