using System.Globalization; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Web; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Localization; using Bunit; using Bunit.JSInterop; using SufiChain.SufiBlazor.Components.Forms; using SufiChain.SufiBlazor.Localization; using Xunit; namespace SufiChain.SufiBlazor.Tests.Components.Forms; /// /// Stub localizer that returns the key as the value (for testing). /// file class StubStringLocalizer : IStringLocalizer { public LocalizedString this[string name] => new(name, name); public LocalizedString this[string name, params object[] arguments] => new(name, string.Format(CultureInfo.InvariantCulture, name, arguments)); public IEnumerable GetAllStrings(bool includeParentCultures) => Array.Empty(); } public class SbAutocompleteTests : BunitContext { public SbAutocompleteTests() { Services.AddSingleton>(new StubStringLocalizer()); JSInterop.Mode = JSRuntimeMode.Loose; } private static List CreateItems(int count) { return Enumerable.Range(1, count) .Select(i => new AutocompleteTestItem { Id = i, Name = $"Item {i}" }) .ToList(); } private IRenderedComponent> RenderAutocomplete( Action>>? configure = null) { var items = CreateItems(5); return Render>(p => { p.Add(x => x.Items, items) .Add(x => x.TextField, (Func)(item => item.Name)) .Add(x => x.ValueField, (Func)(item => item.Id)) .Add(x => x.DebounceMs, 0); // No debounce for faster tests configure?.Invoke(p); }); } [Fact] public void RendersAutocompleteStructure() { // Arrange & Act var cut = RenderAutocomplete(); // Assert var wrapper = cut.Find(".sb-autocomplete"); Assert.NotNull(wrapper); Assert.NotNull(cut.Find(".sb-autocomplete__input-wrapper")); Assert.NotNull(cut.Find(".sb-autocomplete__input")); Assert.NotNull(cut.Find(".sb-autocomplete__icon")); } [Fact] public void RendersLabelWhenProvided() { // Arrange & Act var cut = RenderAutocomplete(p => p.Add(x => x.Label, "Search")); // Assert var label = cut.Find(".sb-autocomplete__label"); Assert.NotNull(label); Assert.Contains("Search", label.TextContent); } [Fact] public void DoesNotRenderLabelWhenEmpty() { // Arrange & Act var cut = RenderAutocomplete(); // Assert Assert.Empty(cut.FindAll(".sb-autocomplete__label")); } [Fact] public void RendersRequiredAsteriskWhenRequired() { // Arrange & Act var cut = RenderAutocomplete(p => p .Add(x => x.Label, "Name") .Add(x => x.Required, true)); // Assert var required = cut.Find(".sb-autocomplete__required"); Assert.NotNull(required); Assert.Contains("*", required.TextContent); } [Fact] public void RendersPlaceholderWhenProvided() { // Arrange & Act var cut = RenderAutocomplete(p => p.Add(x => x.Placeholder, "Type to search...")); // Assert var input = cut.Find(".sb-autocomplete__input"); Assert.Equal("Type to search...", input.GetAttribute("placeholder")); } [Fact] public void UsesLocalizedPlaceholderWhenPlaceholderNull() { // Arrange & Act var cut = RenderAutocomplete(); // Assert - StubStringLocalizer returns key as value, so "Search_Placeholder" var input = cut.Find(".sb-autocomplete__input"); Assert.Equal("Search_Placeholder", input.GetAttribute("placeholder")); } [Fact] public void InputIsDisabledWhenDisabledTrue() { // Arrange & Act var cut = RenderAutocomplete(p => p.Add(x => x.Disabled, true)); // Assert var input = cut.Find(".sb-autocomplete__input"); Assert.True(input.GetAttribute("disabled") != null || input.OuterHtml.Contains("disabled")); } [Fact] public void DisplaysValueWhenValueSet() { // Arrange var items = CreateItems(3); var selected = items[1]; // Act var cut = Render>(p => p .Add(x => x.Items, items) .Add(x => x.Value, selected) .Add(x => x.TextField, (Func)(item => item.Name)) .Add(x => x.ValueField, (Func)(item => item.Id)) .Add(x => x.DebounceMs, 0)); // Assert var input = cut.Find(".sb-autocomplete__input"); Assert.Equal("Item 2", input.GetAttribute("value")); } [Fact] public void AppliesCustomClass() { // Arrange & Act var cut = RenderAutocomplete(p => p.Add(x => x.Class, "my-autocomplete")); // Assert var wrapper = cut.Find(".sb-autocomplete"); Assert.Contains("my-autocomplete", wrapper.ClassList); } [Fact] public void AppliesInlineStyle() { // Arrange & Act var cut = RenderAutocomplete(p => p.Add(x => x.Style, "max-width: 300px;")); // Assert var wrapper = cut.Find(".sb-autocomplete"); Assert.Contains("max-width: 300px", wrapper.GetAttribute("style")); } [Fact] public async Task InvokesValueChangedWhenItemSelected() { // Arrange AutocompleteTestItem? selected = null; var items = CreateItems(3); var cut = Render>(p => p .Add(x => x.Items, items) .Add(x => x.TextField, (Func)(item => item.Name)) .Add(x => x.ValueField, (Func)(item => item.Id)) .Add(x => x.DebounceMs, 0) .Add(x => x.ValueChanged, EventCallback.Factory.Create(this, item => selected = item))); // Act - type to open dropdown (SbAutocomplete uses @oninput, not @onchange), then select var input = cut.Find(".sb-autocomplete__input"); await cut.InvokeAsync(() => input!.Input("Item")); cut.WaitForState(() => cut.FindAll(".sb-autocomplete__option").Count > 0); var option = cut.FindAll(".sb-autocomplete__option").FirstOrDefault(o => o.TextContent.Contains("Item 1")); Assert.NotNull(option); await cut.InvokeAsync(() => option!.Click()); cut.WaitForState(() => selected != null); // Assert Assert.NotNull(selected); Assert.Equal(1, selected!.Id); Assert.Equal("Item 1", selected.Name); } [Fact] public async Task ClearsValueWhenClearButtonClicked() { // Arrange var items = CreateItems(2); var selected = items[0]; var cut = Render>(p => p .Add(x => x.Items, items) .Add(x => x.Value, selected) .Add(x => x.TextField, (Func)(item => item.Name)) .Add(x => x.ValueField, (Func)(item => item.Id)) .Add(x => x.Clearable, true) .Add(x => x.DebounceMs, 0)); cut.WaitForState(() => cut.Find(".sb-autocomplete__input")?.GetAttribute("value") == "Item 1"); // Act var clearBtn = cut.Find(".sb-autocomplete__clear"); Assert.NotNull(clearBtn); await cut.InvokeAsync(() => clearBtn!.Click()); cut.WaitForState(() => cut.Find(".sb-autocomplete__input")?.GetAttribute("value") == ""); // Assert var input = cut.Find(".sb-autocomplete__input"); Assert.Equal("", input.GetAttribute("value")); } private class AutocompleteTestItem { public int Id { get; set; } public string Name { get; set; } = string.Empty; } }