using System.Globalization; using Microsoft.AspNetCore.Components; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Localization; using Bunit; using Bunit.JSInterop; using SufiChain.SufiBlazor.Components.Forms; using SufiChain.SufiBlazor.Localization; using SufiChain.SufiBlazor.Utilities.DateUtils; using Xunit; namespace SufiChain.SufiBlazor.Tests.Components.Forms; /// /// Stub localizer for SbDatePicker tests. /// 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 SbDatePickerTests : BunitContext { public SbDatePickerTests() { Services.AddSingleton>(new StubStringLocalizer()); JSInterop.Mode = JSRuntimeMode.Loose; } private IRenderedComponent RenderDatePicker( Action>? configure = null) { return Render(p => { p.Add(x => x.CalendarSystem, SbCalendarSystem.Gregorian); // Predictable calendar for tests configure?.Invoke(p); }); } [Fact] public void RendersDatePickerStructure() { // Arrange & Act var cut = RenderDatePicker(); // Assert var wrapper = cut.Find(".sb-datepicker"); Assert.NotNull(wrapper); Assert.NotNull(cut.Find(".sb-datepicker__trigger")); Assert.NotNull(cut.Find(".sb-datepicker__placeholder")); Assert.NotNull(cut.Find(".sb-datepicker__icon")); } [Fact] public void RendersLabelWhenProvided() { // Arrange & Act var cut = RenderDatePicker(p => p.Add(x => x.Label, "Start date")); // Assert var label = cut.Find(".sb-datepicker__label"); Assert.NotNull(label); Assert.Contains("Start date", label.TextContent); } [Fact] public void DoesNotRenderLabelWhenEmpty() { // Arrange & Act var cut = RenderDatePicker(); // Assert Assert.Empty(cut.FindAll(".sb-datepicker__label")); } [Fact] public void DisplaysValueWhenValueSet() { // Arrange - use a fixed date for predictable output var date = new DateOnly(2025, 3, 15); // Act var cut = RenderDatePicker(p => p.Add(x => x.Value, date)); // Assert - value span shows formatted date var valueSpan = cut.Find(".sb-datepicker__value"); Assert.NotNull(valueSpan); Assert.Contains("15", valueSpan.TextContent); Assert.Contains("2025", valueSpan.TextContent); } [Fact] public void UsesLocalizedPlaceholderWhenPlaceholderNull() { // Arrange & Act var cut = RenderDatePicker(); // Assert - StubStringLocalizer returns key as value var placeholderSpan = cut.Find(".sb-datepicker__placeholder"); Assert.NotNull(placeholderSpan); Assert.Equal("SelectDate_Placeholder", placeholderSpan.TextContent); } [Fact] public void RendersPlaceholderWhenProvided() { // Arrange & Act var cut = RenderDatePicker(p => p.Add(x => x.Placeholder, "Pick a date...")); // Assert var placeholderSpan = cut.Find(".sb-datepicker__placeholder"); Assert.NotNull(placeholderSpan); Assert.Equal("Pick a date...", placeholderSpan.TextContent); } [Fact] public void TriggerHasDisabledClassWhenDisabledTrue() { // Arrange & Act var cut = RenderDatePicker(p => p.Add(x => x.Disabled, true)); // Assert var trigger = cut.Find(".sb-datepicker__trigger"); Assert.Contains("sb-datepicker__trigger--disabled", trigger.ClassList); } [Fact] public void DoesNotRenderClearButtonWhenValueEmpty() { // Arrange & Act var cut = RenderDatePicker(); // Assert Assert.Empty(cut.FindAll(".sb-datepicker__clear")); } [Fact] public void RendersClearButtonWhenValueSetAndClearable() { // Arrange & Act var cut = RenderDatePicker(p => p .Add(x => x.Value, new DateOnly(2025, 1, 10)) .Add(x => x.Clearable, true)); // Assert var clearBtn = cut.Find(".sb-datepicker__clear"); Assert.NotNull(clearBtn); Assert.Equal("ClearDate", clearBtn.GetAttribute("aria-label")); } [Fact] public void DoesNotRenderClearButtonWhenClearableFalse() { // Arrange & Act var cut = RenderDatePicker(p => p .Add(x => x.Value, new DateOnly(2025, 1, 10)) .Add(x => x.Clearable, false)); // Assert Assert.Empty(cut.FindAll(".sb-datepicker__clear")); } [Fact] public async Task InvokesValueChangedWhenClearClicked() { // Arrange DateOnly? received = null; var cut = RenderDatePicker(p => p .Add(x => x.Value, new DateOnly(2025, 1, 10)) .Add(x => x.Clearable, true) .Add(x => x.ValueChanged, EventCallback.Factory.Create(this, v => received = v))); // Act var clearBtn = cut.Find(".sb-datepicker__clear"); await cut.InvokeAsync(() => clearBtn!.Click()); // Assert Assert.Null(received); } [Fact] public async Task OpensDropdownWhenTriggerClicked() { // Arrange & Act var cut = RenderDatePicker(); var trigger = cut.Find(".sb-datepicker__trigger"); await cut.InvokeAsync(() => trigger!.Click()); // Assert Assert.NotNull(cut.Find(".sb-datepicker__dropdown")); Assert.NotNull(cut.Find(".sb-datepicker__header")); Assert.NotNull(cut.Find(".sb-datepicker__day-names")); Assert.NotNull(cut.Find(".sb-datepicker__days")); Assert.NotNull(cut.Find(".sb-datepicker__footer")); } [Fact] public async Task DoesNotOpenWhenDisabled() { // Arrange & Act var cut = RenderDatePicker(p => p.Add(x => x.Disabled, true)); var trigger = cut.Find(".sb-datepicker__trigger"); await cut.InvokeAsync(() => trigger!.Click()); // Assert Assert.Empty(cut.FindAll(".sb-datepicker__dropdown")); } [Fact] public async Task InvokesValueChangedWhenTodayClicked() { // Arrange DateOnly? received = null; var cut = RenderDatePicker(p => p .Add(x => x.ValueChanged, EventCallback.Factory.Create(this, v => received = v))); // Act - open dropdown, click Today button var trigger = cut.Find(".sb-datepicker__trigger"); await cut.InvokeAsync(() => trigger!.Click()); var todayBtn = cut.Find(".sb-datepicker__today-btn"); Assert.NotNull(todayBtn); await cut.InvokeAsync(() => todayBtn!.Click()); // Assert Assert.NotNull(received); Assert.Equal(DateOnly.FromDateTime(DateTime.Today), received!.Value); } [Fact] public async Task InvokesValueChangedWhenDaySelected() { // Arrange - use a fixed month so we can find a specific day DateOnly? received = null; var cut = RenderDatePicker(p => p .Add(x => x.Value, new DateOnly(2025, 3, 1)) // March 2025 - display will show this month .Add(x => x.ValueChanged, EventCallback.Factory.Create(this, v => received = v))); // Act - open dropdown, click day 15 (guaranteed to exist in March) var trigger = cut.Find(".sb-datepicker__trigger"); await cut.InvokeAsync(() => trigger!.Click()); var day15 = cut.FindAll(".sb-datepicker__day") .FirstOrDefault(b => b.TextContent.Trim() == "15" && !b.ClassList.Contains("sb-datepicker__day--disabled") && !b.ClassList.Contains("sb-datepicker__day--other-month")); Assert.NotNull(day15); await cut.InvokeAsync(() => day15!.Click()); // Assert Assert.NotNull(received); Assert.Equal(new DateOnly(2025, 3, 15), received!.Value); } [Fact] public async Task NavigatesToPreviousMonth() { // Arrange var cut = RenderDatePicker(p => p.Add(x => x.Value, new DateOnly(2025, 3, 15))); var trigger = cut.Find(".sb-datepicker__trigger"); await cut.InvokeAsync(() => trigger!.Click()); var title = cut.Find(".sb-datepicker__month-year"); var initialTitle = title.TextContent; // Act - click previous month var prevBtn = cut.FindAll(".sb-datepicker__nav-btn").First(); await cut.InvokeAsync(() => prevBtn!.Click()); // Assert - title should have changed (e.g. Feb 2025) var newTitle = cut.Find(".sb-datepicker__month-year").TextContent; Assert.NotEqual(initialTitle, newTitle); } [Fact] public async Task SwitchesToMonthViewWhenMonthYearClicked() { // Arrange var cut = RenderDatePicker(); var trigger = cut.Find(".sb-datepicker__trigger"); await cut.InvokeAsync(() => trigger!.Click()); // Act - click month/year header to switch to months view var monthYearBtn = cut.Find(".sb-datepicker__month-year"); await cut.InvokeAsync(() => monthYearBtn!.Click()); // Assert - months grid should be visible Assert.NotNull(cut.Find(".sb-datepicker__months")); } [Fact] public void AppliesCustomClass() { // Arrange & Act var cut = RenderDatePicker(p => p.Add(x => x.Class, "my-datepicker")); // Assert var wrapper = cut.Find(".sb-datepicker"); Assert.Contains("my-datepicker", wrapper.ClassList); } [Fact] public void AppliesInlineStyle() { // Arrange & Act var cut = RenderDatePicker(p => p.Add(x => x.Style, "max-width: 220px;")); // Assert var wrapper = cut.Find(".sb-datepicker"); Assert.Contains("max-width: 220px", wrapper.GetAttribute("style")); } [Fact] public void UsesCustomFormatWhenProvided() { // Arrange & Act var cut = RenderDatePicker(p => p .Add(x => x.Value, new DateOnly(2025, 12, 25)) .Add(x => x.Format, "yyyy-MM-dd")); // Assert var valueSpan = cut.Find(".sb-datepicker__value"); Assert.NotNull(valueSpan); Assert.Contains("2025-12-25", valueSpan.TextContent); } }