first commit

This commit is contained in:
2026-05-18 15:53:59 +03:30
commit 2c100028a1
534 changed files with 94240 additions and 0 deletions
@@ -0,0 +1,243 @@
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;
/// <summary>
/// Stub localizer that returns the key as the value (for testing).
/// </summary>
file class StubStringLocalizer : IStringLocalizer<SufiBlazorResource>
{
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<LocalizedString> GetAllStrings(bool includeParentCultures) => Array.Empty<LocalizedString>();
}
public class SbAutocompleteTests : BunitContext
{
public SbAutocompleteTests()
{
Services.AddSingleton<IStringLocalizer<SufiBlazorResource>>(new StubStringLocalizer());
JSInterop.Mode = JSRuntimeMode.Loose;
}
private static List<AutocompleteTestItem> CreateItems(int count)
{
return Enumerable.Range(1, count)
.Select(i => new AutocompleteTestItem { Id = i, Name = $"Item {i}" })
.ToList();
}
private IRenderedComponent<SbAutocomplete<AutocompleteTestItem>> RenderAutocomplete(
Action<ComponentParameterCollectionBuilder<SbAutocomplete<AutocompleteTestItem>>>? configure = null)
{
var items = CreateItems(5);
return Render<SbAutocomplete<AutocompleteTestItem>>(p =>
{
p.Add(x => x.Items, items)
.Add(x => x.TextField, (Func<AutocompleteTestItem, string>)(item => item.Name))
.Add(x => x.ValueField, (Func<AutocompleteTestItem, object>)(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<SbAutocomplete<AutocompleteTestItem>>(p => p
.Add(x => x.Items, items)
.Add(x => x.Value, selected)
.Add(x => x.TextField, (Func<AutocompleteTestItem, string>)(item => item.Name))
.Add(x => x.ValueField, (Func<AutocompleteTestItem, object>)(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<SbAutocomplete<AutocompleteTestItem>>(p => p
.Add(x => x.Items, items)
.Add(x => x.TextField, (Func<AutocompleteTestItem, string>)(item => item.Name))
.Add(x => x.ValueField, (Func<AutocompleteTestItem, object>)(item => item.Id))
.Add(x => x.DebounceMs, 0)
.Add(x => x.ValueChanged, EventCallback.Factory.Create<AutocompleteTestItem?>(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<SbAutocomplete<AutocompleteTestItem>>(p => p
.Add(x => x.Items, items)
.Add(x => x.Value, selected)
.Add(x => x.TextField, (Func<AutocompleteTestItem, string>)(item => item.Name))
.Add(x => x.ValueField, (Func<AutocompleteTestItem, object>)(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;
}
}