first commit
Release NuGet Packages / pack-and-push (release) Failing after 10m17s

This commit is contained in:
2026-05-21 11:20:43 +03:30
commit 3ff35dedb5
536 changed files with 94352 additions and 0 deletions
+213
View File
@@ -0,0 +1,213 @@
using Microsoft.AspNetCore.Components;
using Bunit;
using SufiChain.SufiBlazor.Components.Feedback;
using Xunit;
namespace SufiChain.SufiBlazor.Tests.Components.Feedback;
public class SbAlertTests : BunitContext
{
private IRenderedComponent<SbAlert> RenderAlert(
Action<ComponentParameterCollectionBuilder<SbAlert>>? configure = null)
{
return Render<SbAlert>(p =>
{
p.AddChildContent("<span>Alert message</span>");
configure?.Invoke(p);
});
}
[Fact]
public void RendersAlertStructure()
{
// Arrange & Act
var cut = RenderAlert();
// Assert
var alert = cut.Find(".sb-alert");
Assert.NotNull(alert);
Assert.NotNull(cut.Find(".sb-alert__icon"));
Assert.NotNull(cut.Find(".sb-alert__content"));
Assert.NotNull(cut.Find(".sb-alert__message"));
}
[Fact]
public void RendersChildContent()
{
// Arrange & Act
var cut = RenderAlert();
// Assert
Assert.Contains("Alert message", cut.Markup);
}
[Fact]
public void RendersTitleWhenProvided()
{
// Arrange & Act
var cut = RenderAlert(p => p.Add(x => x.Title, "Heads up"));
// Assert
var title = cut.Find(".sb-alert__title");
Assert.NotNull(title);
Assert.Contains("Heads up", title.TextContent);
}
[Fact]
public void DoesNotRenderTitleWhenEmpty()
{
// Arrange & Act
var cut = RenderAlert();
// Assert
Assert.Empty(cut.FindAll(".sb-alert__title"));
}
[Fact]
public void AppliesInfoSeverityByDefault()
{
// Arrange & Act
var cut = RenderAlert();
// Assert
var alert = cut.Find(".sb-alert");
Assert.Contains("sb-alert--info", alert.ClassList);
Assert.Contains("", cut.Markup);
}
[Fact]
public void AppliesSuccessSeverity()
{
// Arrange & Act
var cut = RenderAlert(p => p.Add(x => x.Severity, SbAlertSeverity.Success));
// Assert
var alert = cut.Find(".sb-alert");
Assert.Contains("sb-alert--success", alert.ClassList);
Assert.Contains("✓", cut.Markup);
}
[Fact]
public void AppliesWarningSeverity()
{
// Arrange & Act
var cut = RenderAlert(p => p.Add(x => x.Severity, SbAlertSeverity.Warning));
// Assert
var alert = cut.Find(".sb-alert");
Assert.Contains("sb-alert--warning", alert.ClassList);
Assert.Contains("⚠", cut.Markup);
}
[Fact]
public void AppliesDangerSeverity()
{
// Arrange & Act
var cut = RenderAlert(p => p.Add(x => x.Severity, SbAlertSeverity.Danger));
// Assert
var alert = cut.Find(".sb-alert");
Assert.Contains("sb-alert--danger", alert.ClassList);
Assert.Contains("✕", cut.Markup);
}
[Fact]
public void HasRoleAlertWhenDanger()
{
// Arrange & Act
var cut = RenderAlert(p => p.Add(x => x.Severity, SbAlertSeverity.Danger));
// Assert
var alert = cut.Find(".sb-alert");
Assert.Equal("alert", alert.GetAttribute("role"));
}
[Fact]
public void HasRoleStatusWhenNotDanger()
{
// Arrange & Act
var cut = RenderAlert(p => p.Add(x => x.Severity, SbAlertSeverity.Info));
// Assert
var alert = cut.Find(".sb-alert");
Assert.Equal("status", alert.GetAttribute("role"));
}
[Fact]
public void RendersDismissButtonWhenDismissible()
{
// Arrange & Act
var cut = RenderAlert(p => p.Add(x => x.Dismissible, true));
// Assert
var dismissBtn = cut.Find(".sb-alert__dismiss");
Assert.NotNull(dismissBtn);
Assert.Equal("button", dismissBtn.TagName.ToLowerInvariant());
Assert.Equal("Dismiss", dismissBtn.GetAttribute("aria-label"));
Assert.Contains("×", dismissBtn.TextContent);
}
[Fact]
public void DoesNotRenderDismissButtonWhenNotDismissible()
{
// Arrange & Act
var cut = RenderAlert();
// Assert
Assert.Empty(cut.FindAll(".sb-alert__dismiss"));
}
[Fact]
public async Task InvokesOnDismissWhenDismissButtonClicked()
{
// Arrange
var dismissed = false;
var cut = RenderAlert(p => p
.Add(x => x.Dismissible, true)
.Add(x => x.OnDismiss, EventCallback.Factory.Create(this, () => dismissed = true)));
var dismissBtn = cut.Find(".sb-alert__dismiss");
// Act
await cut.InvokeAsync(() => dismissBtn!.Click());
cut.WaitForState(() => dismissed);
// Assert
Assert.True(dismissed);
}
[Fact]
public void AppliesDenseClass()
{
// Arrange & Act
var cut = RenderAlert(p => p.Add(x => x.Dense, true));
// Assert
var alert = cut.Find(".sb-alert");
Assert.Contains("sb-alert--dense", alert.ClassList);
}
[Fact]
public void AppliesCustomClass()
{
// Arrange & Act
var cut = RenderAlert(p => p.Add(x => x.Class, "my-alert"));
// Assert
var alert = cut.Find(".sb-alert");
Assert.Contains("my-alert", alert.ClassList);
}
[Fact]
public void AppliesInlineStyle()
{
// Arrange & Act
var cut = RenderAlert(p => p.Add(x => x.Style, "margin-top: 10px;"));
// Assert
var alert = cut.Find(".sb-alert");
Assert.Contains("margin-top: 10px", alert.GetAttribute("style"));
}
}
+193
View File
@@ -0,0 +1,193 @@
using Microsoft.AspNetCore.Components;
using Bunit;
using SufiChain.SufiBlazor.Components;
using SufiChain.SufiBlazor.Components.Feedback;
using Xunit;
namespace SufiChain.SufiBlazor.Tests.Components.Feedback;
public class SbBadgeTests : BunitContext
{
private IRenderedComponent<SbBadge> RenderBadge(
Action<ComponentParameterCollectionBuilder<SbBadge>>? configure = null)
{
return Render<SbBadge>(p =>
{
p.Add(x => x.Value, 5);
configure?.Invoke(p);
});
}
[Fact]
public void RendersBadgeStructure()
{
// Arrange & Act
var cut = RenderBadge();
// Assert
var badge = cut.Find(".sb-badge");
Assert.NotNull(badge);
Assert.Equal("span", badge.TagName.ToLowerInvariant());
}
[Fact]
public void RendersValueWhenValueSet()
{
// Arrange & Act
var cut = RenderBadge();
// Assert
Assert.Contains("5", cut.Markup);
}
[Fact]
public void RendersChildContentOverValue()
{
// Arrange & Act
var cut = Render<SbBadge>(p => p
.Add(x => x.Value, 10)
.AddChildContent("New"));
// Assert - ChildContent takes precedence
Assert.Contains("New", cut.Markup);
Assert.DoesNotContain("10", cut.Markup);
}
[Fact]
public void RendersMaxPlusWhenValueExceedsMax()
{
// Arrange & Act - use Render directly to avoid Value added twice
var cut = Render<SbBadge>(p => p
.Add(x => x.Value, 150)
.Add(x => x.Max, 99));
// Assert
Assert.Contains("99+", cut.Markup);
}
[Fact]
public void RendersExactValueWhenEqualToMax()
{
// Arrange & Act - use Render directly to avoid Value added twice
var cut = Render<SbBadge>(p => p
.Add(x => x.Value, 99)
.Add(x => x.Max, 99));
// Assert
Assert.Contains("99", cut.Markup);
Assert.DoesNotContain("99+", cut.Markup);
}
[Fact]
public void RendersCustomMaxPlus()
{
// Arrange & Act - use Render directly to avoid Value added twice
var cut = Render<SbBadge>(p => p
.Add(x => x.Value, 50)
.Add(x => x.Max, 10));
// Assert
Assert.Contains("10+", cut.Markup);
}
[Fact]
public void RendersEmptyWhenValueNullAndNoChildContent()
{
// Arrange & Act
var cut = Render<SbBadge>(p => p.Add(x => x.Value, (int?)null));
// Assert - badge still renders, content is empty
var badge = cut.Find(".sb-badge");
Assert.NotNull(badge);
}
[Fact]
public void RendersDotMode()
{
// Arrange & Act
var cut = RenderBadge(p => p.Add(x => x.Dot, true));
// Assert
var badge = cut.Find(".sb-badge");
Assert.Contains("sb-badge--dot", badge.ClassList);
// Dot mode shows no value/child content inside
Assert.DoesNotContain("5", cut.Markup);
}
[Fact]
public void AppliesPrimaryColorByDefault()
{
// Arrange & Act
var cut = RenderBadge();
// Assert
var badge = cut.Find(".sb-badge");
Assert.Contains("sb-badge--primary", badge.ClassList);
}
[Fact]
public void AppliesSuccessColor()
{
// Arrange & Act
var cut = RenderBadge(p => p.Add(x => x.Color, SbColor.Success));
// Assert
var badge = cut.Find(".sb-badge");
Assert.Contains("sb-badge--success", badge.ClassList);
}
[Fact]
public void AppliesWarningColor()
{
// Arrange & Act
var cut = RenderBadge(p => p.Add(x => x.Color, SbColor.Warning));
// Assert
var badge = cut.Find(".sb-badge");
Assert.Contains("sb-badge--warning", badge.ClassList);
}
[Fact]
public void AppliesDangerColor()
{
// Arrange & Act
var cut = RenderBadge(p => p.Add(x => x.Color, SbColor.Danger));
// Assert
var badge = cut.Find(".sb-badge");
Assert.Contains("sb-badge--danger", badge.ClassList);
}
[Fact]
public void AppliesAriaLabel()
{
// Arrange & Act
var cut = RenderBadge(p => p.Add(x => x.AriaLabel, "5 unread messages"));
// Assert
var badge = cut.Find(".sb-badge");
Assert.Equal("5 unread messages", badge.GetAttribute("aria-label"));
}
[Fact]
public void AppliesCustomClass()
{
// Arrange & Act
var cut = RenderBadge(p => p.Add(x => x.Class, "my-badge"));
// Assert
var badge = cut.Find(".sb-badge");
Assert.Contains("my-badge", badge.ClassList);
}
[Fact]
public void AppliesInlineStyle()
{
// Arrange & Act
var cut = RenderBadge(p => p.Add(x => x.Style, "top: 0;"));
// Assert
var badge = cut.Find(".sb-badge");
Assert.Contains("top: 0", badge.GetAttribute("style"));
}
}
+239
View File
@@ -0,0 +1,239 @@
using Microsoft.AspNetCore.Components;
using Bunit;
using SufiChain.SufiBlazor.Components.Feedback;
using Xunit;
namespace SufiChain.SufiBlazor.Tests.Components.Feedback;
public class SbBannerTests : BunitContext
{
private IRenderedComponent<SbBanner> RenderBanner(
Action<ComponentParameterCollectionBuilder<SbBanner>>? configure = null)
{
return Render<SbBanner>(p =>
{
p.Add(x => x.Message, "Banner message");
configure?.Invoke(p);
});
}
[Fact]
public void RendersBannerStructure()
{
// Arrange & Act
var cut = RenderBanner();
// Assert
var banner = cut.Find(".sb-banner");
Assert.NotNull(banner);
Assert.Equal("alert", banner.GetAttribute("role"));
Assert.NotNull(cut.Find(".sb-banner__header"));
Assert.NotNull(cut.Find(".sb-banner__content"));
Assert.NotNull(cut.Find(".sb-banner__icon"));
}
[Fact]
public void RendersMessageWhenNoChildContent()
{
// Arrange & Act
var cut = RenderBanner();
// Assert
Assert.Contains("Banner message", cut.Markup);
var message = cut.Find(".sb-banner__message");
Assert.NotNull(message);
}
[Fact]
public void RendersChildContentOverMessage()
{
// Arrange & Act
var cut = Render<SbBanner>(p => p
.Add(x => x.Message, "Fallback")
.AddChildContent("<span class=\"custom-content\">Custom content</span>"));
// Assert - ChildContent takes precedence
Assert.Contains("Custom content", cut.Markup);
Assert.Empty(cut.FindAll(".sb-banner__message"));
}
[Fact]
public void RendersTitleWhenProvided()
{
// Arrange & Act
var cut = RenderBanner(p => p.Add(x => x.Title, "Important Notice"));
// Assert
var title = cut.Find(".sb-banner__title");
Assert.NotNull(title);
Assert.Contains("Important Notice", title.TextContent);
}
[Fact]
public void DoesNotRenderTitleWhenEmpty()
{
// Arrange & Act
var cut = RenderBanner();
// Assert
Assert.Empty(cut.FindAll(".sb-banner__title"));
}
[Fact]
public void AppliesInfoSeverityByDefault()
{
// Arrange & Act
var cut = RenderBanner();
// Assert
var banner = cut.Find(".sb-banner");
Assert.Contains("sb-banner--info", banner.ClassList);
}
[Fact]
public void AppliesSuccessSeverity()
{
// Arrange & Act
var cut = RenderBanner(p => p.Add(x => x.Severity, SbBannerSeverity.Success));
// Assert
var banner = cut.Find(".sb-banner");
Assert.Contains("sb-banner--success", banner.ClassList);
}
[Fact]
public void AppliesWarningSeverity()
{
// Arrange & Act
var cut = RenderBanner(p => p.Add(x => x.Severity, SbBannerSeverity.Warning));
// Assert
var banner = cut.Find(".sb-banner");
Assert.Contains("sb-banner--warning", banner.ClassList);
}
[Fact]
public void AppliesErrorSeverity()
{
// Arrange & Act
var cut = RenderBanner(p => p.Add(x => x.Severity, SbBannerSeverity.Error));
// Assert
var banner = cut.Find(".sb-banner");
Assert.Contains("sb-banner--error", banner.ClassList);
}
[Fact]
public void RendersCloseButtonWhenDismissible()
{
// Arrange & Act
var cut = RenderBanner(p => p.Add(x => x.Dismissible, true));
// Assert
var closeBtn = cut.Find(".sb-banner__close");
Assert.NotNull(closeBtn);
Assert.Equal("Dismiss", closeBtn.GetAttribute("aria-label"));
}
[Fact]
public void DoesNotRenderCloseButtonWhenNotDismissible()
{
// Arrange & Act
var cut = RenderBanner(p => p.Add(x => x.Dismissible, false));
// Assert
Assert.Empty(cut.FindAll(".sb-banner__close"));
}
[Fact]
public async Task HidesBannerWhenDismissed()
{
// Arrange
var cut = RenderBanner();
Assert.NotNull(cut.Find(".sb-banner"));
// Act
var closeBtn = cut.Find(".sb-banner__close");
await cut.InvokeAsync(() => closeBtn!.Click());
cut.WaitForState(() => cut.FindAll(".sb-banner").Count == 0);
// Assert
Assert.Empty(cut.FindAll(".sb-banner"));
}
[Fact]
public async Task InvokesOnDismissWhenDismissed()
{
// Arrange
var dismissed = false;
var cut = RenderBanner(p => p
.Add(x => x.Dismissible, true)
.Add(x => x.OnDismiss, EventCallback.Factory.Create(this, () => dismissed = true)));
var closeBtn = cut.Find(".sb-banner__close");
// Act
await cut.InvokeAsync(() => closeBtn!.Click());
cut.WaitForState(() => dismissed);
// Assert
Assert.True(dismissed);
}
[Fact]
public async Task ShowRestoresVisibilityAfterDismiss()
{
// Arrange
var cut = RenderBanner();
var closeBtn = cut.Find(".sb-banner__close");
await cut.InvokeAsync(() => closeBtn!.Click());
cut.WaitForState(() => cut.FindAll(".sb-banner").Count == 0);
// Act - Show() calls StateHasChanged, must run on Dispatcher
await cut.InvokeAsync(() => cut.Instance.Show());
cut.WaitForState(() => cut.FindAll(".sb-banner").Count > 0);
// Assert
Assert.NotNull(cut.Find(".sb-banner"));
}
[Fact]
public void RendersActionsWhenProvided()
{
// Arrange & Act
var cut = RenderBanner(p => p
.Add(x => x.Actions, a => a.AddMarkupContent(0, "<button class=\"banner-action\">Learn more</button>")));
// Assert
var actions = cut.Find(".sb-banner__actions");
Assert.NotNull(actions);
var actionBtn = cut.Find(".banner-action");
Assert.NotNull(actionBtn);
Assert.Contains("Learn more", actionBtn.TextContent);
}
[Fact]
public void DoesNotRenderActionsWhenNull()
{
// Arrange & Act
var cut = RenderBanner();
// Assert
Assert.Empty(cut.FindAll(".sb-banner__actions"));
}
[Fact]
public void AppliesCustomClass()
{
// Arrange & Act
var cut = RenderBanner(p => p.Add(x => x.Class, "my-banner"));
// Assert
var banner = cut.Find(".sb-banner");
Assert.Contains("my-banner", banner.ClassList);
}
}
+258
View File
@@ -0,0 +1,258 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using Bunit;
using SufiChain.SufiBlazor.Components;
using SufiChain.SufiBlazor.Components.Feedback;
using Xunit;
namespace SufiChain.SufiBlazor.Tests.Components.Feedback;
public class SbChipTests : BunitContext
{
private IRenderedComponent<SbChip> RenderChip(
Action<ComponentParameterCollectionBuilder<SbChip>>? configure = null)
{
return Render<SbChip>(p =>
{
p.AddChildContent("Chip label");
configure?.Invoke(p);
});
}
[Fact]
public void RendersChipStructure()
{
// Arrange & Act - no OnClick, renders as span
var cut = RenderChip();
// Assert
var chip = cut.Find(".sb-chip");
Assert.NotNull(chip);
Assert.Equal("span", chip.TagName.ToLowerInvariant());
Assert.NotNull(cut.Find(".sb-chip__label"));
}
[Fact]
public void RendersChildContent()
{
// Arrange & Act
var cut = RenderChip();
// Assert
Assert.Contains("Chip label", cut.Markup);
}
[Fact]
public void RendersAsButtonWhenOnClickProvided()
{
// Arrange & Act
var cut = RenderChip(p => p.Add(x => x.OnClick, EventCallback.Factory.Create<MouseEventArgs>(this, _ => { })));
// Assert
var chip = cut.Find(".sb-chip");
Assert.Equal("button", chip.TagName.ToLowerInvariant());
Assert.Contains("sb-chip--clickable", chip.ClassList);
}
[Fact]
public void AppliesFilledVariantByDefault()
{
// Arrange & Act
var cut = RenderChip();
// Assert
var chip = cut.Find(".sb-chip");
Assert.Contains("sb-chip--filled", chip.ClassList);
}
[Fact]
public void AppliesOutlinedVariant()
{
// Arrange & Act
var cut = RenderChip(p => p.Add(x => x.Variant, SbChipVariant.Outlined));
// Assert
var chip = cut.Find(".sb-chip");
Assert.Contains("sb-chip--outlined", chip.ClassList);
}
[Fact]
public void AppliesMdSizeByDefault()
{
// Arrange & Act
var cut = RenderChip();
// Assert
var chip = cut.Find(".sb-chip");
Assert.Contains("sb-chip--md", chip.ClassList);
}
[Fact]
public void AppliesSmSize()
{
// Arrange & Act
var cut = RenderChip(p => p.Add(x => x.Size, SbSize.Sm));
// Assert
var chip = cut.Find(".sb-chip");
Assert.Contains("sb-chip--sm", chip.ClassList);
}
[Fact]
public void DoesNotApplyColorClassWhenDefault()
{
// Arrange & Act
var cut = RenderChip();
// Assert - SbColor.Default adds no color class
var chip = cut.Find(".sb-chip");
Assert.DoesNotContain("sb-chip--default", chip.ClassList);
}
[Fact]
public void AppliesPrimaryColor()
{
// Arrange & Act
var cut = RenderChip(p => p.Add(x => x.Color, SbColor.Primary));
// Assert
var chip = cut.Find(".sb-chip");
Assert.Contains("sb-chip--primary", chip.ClassList);
}
[Fact]
public void AppliesDisabledClass()
{
// Arrange & Act
var cut = RenderChip(p => p.Add(x => x.Disabled, true));
// Assert
var chip = cut.Find(".sb-chip");
Assert.Contains("sb-chip--disabled", chip.ClassList);
}
[Fact]
public void ButtonIsDisabledWhenDisabledTrue()
{
// Arrange & Act
var cut = RenderChip(p => p
.Add(x => x.OnClick, EventCallback.Factory.Create<MouseEventArgs>(this, _ => { }))
.Add(x => x.Disabled, true));
// Assert - disabled attr can render as "disabled", "true", or "" (boolean attrs vary by renderer)
var chip = cut.Find(".sb-chip");
Assert.True(chip.GetAttribute("disabled") != null || chip.OuterHtml.Contains("disabled"));
}
[Fact]
public void RendersStartIconWhenProvided()
{
// Arrange & Act
var cut = RenderChip(p => p
.Add(x => x.StartIcon, i => i.AddMarkupContent(0, "<span class=\"test-icon\">★</span>")));
// Assert
var iconContainer = cut.Find(".sb-chip__icon");
Assert.NotNull(iconContainer);
var icon = cut.Find(".test-icon");
Assert.NotNull(icon);
}
[Fact]
public void RendersRemoveButtonWhenOnRemoveProvided()
{
// Arrange & Act
var cut = RenderChip(p => p.Add(x => x.OnRemove, EventCallback.Factory.Create(this, () => { })));
// Assert
var removeBtn = cut.Find(".sb-chip__remove");
Assert.NotNull(removeBtn);
Assert.Equal("Remove", removeBtn.GetAttribute("aria-label"));
}
[Fact]
public void DoesNotRenderRemoveButtonWhenOnRemoveNull()
{
// Arrange & Act
var cut = RenderChip();
// Assert
Assert.Empty(cut.FindAll(".sb-chip__remove"));
}
[Fact]
public async Task InvokesOnClickWhenChipClicked()
{
// Arrange
var clicked = false;
var cut = RenderChip(p => p.Add(x => x.OnClick, EventCallback.Factory.Create<MouseEventArgs>(this, _ => clicked = true)));
var chip = cut.Find(".sb-chip");
// Act
await cut.InvokeAsync(() => chip!.Click());
cut.WaitForState(() => clicked);
// Assert
Assert.True(clicked);
}
[Fact]
public async Task InvokesOnRemoveWhenRemoveButtonClicked()
{
// Arrange
var removed = false;
var cut = RenderChip(p => p.Add(x => x.OnRemove, EventCallback.Factory.Create(this, () => removed = true)));
var removeBtn = cut.Find(".sb-chip__remove");
// Act
await cut.InvokeAsync(() => removeBtn!.Click());
cut.WaitForState(() => removed);
// Assert
Assert.True(removed);
}
[Fact]
public async Task DoesNotInvokeOnClickWhenDisabled()
{
// Arrange
var clicked = false;
var cut = RenderChip(p => p
.Add(x => x.OnClick, EventCallback.Factory.Create<MouseEventArgs>(this, _ => clicked = true))
.Add(x => x.Disabled, true));
var chip = cut.Find(".sb-chip");
// Act
await cut.InvokeAsync(() => chip!.Click());
// Assert - click should not fire when disabled
Assert.False(clicked);
}
[Fact]
public void AppliesCustomClass()
{
// Arrange & Act
var cut = RenderChip(p => p.Add(x => x.Class, "my-chip"));
// Assert
var chip = cut.Find(".sb-chip");
Assert.Contains("my-chip", chip.ClassList);
}
[Fact]
public void AppliesInlineStyle()
{
// Arrange & Act
var cut = RenderChip(p => p.Add(x => x.Style, "margin: 4px;"));
// Assert
var chip = cut.Find(".sb-chip");
Assert.Contains("margin: 4px", chip.GetAttribute("style"));
}
}
@@ -0,0 +1,204 @@
using Microsoft.AspNetCore.Components;
using Bunit;
using SufiChain.SufiBlazor.Components.Feedback;
using Xunit;
namespace SufiChain.SufiBlazor.Tests.Components.Feedback;
public class SbEmptyStateTests : BunitContext
{
private IRenderedComponent<SbEmptyState> RenderEmptyState(
Action<ComponentParameterCollectionBuilder<SbEmptyState>>? configure = null)
{
return Render<SbEmptyState>(p => configure?.Invoke(p));
}
[Fact]
public void RendersEmptyStateStructure()
{
// Arrange & Act
var cut = RenderEmptyState();
// Assert
var container = cut.Find(".sb-empty-state");
Assert.NotNull(container);
}
[Fact]
public void RendersDefaultTitle()
{
// Arrange & Act
var cut = RenderEmptyState();
// Assert
var title = cut.Find(".sb-empty-state__title");
Assert.NotNull(title);
Assert.Contains("No items found", title.TextContent);
}
[Fact]
public void RendersDefaultIconText()
{
// Arrange & Act
var cut = RenderEmptyState();
// Assert
var iconText = cut.Find(".sb-empty-state__icon-text");
Assert.NotNull(iconText);
Assert.Contains("📭", iconText.TextContent);
}
[Fact]
public void RendersCustomTitle()
{
// Arrange & Act
var cut = RenderEmptyState(p => p.Add(x => x.Title, "No documents"));
// Assert
var title = cut.Find(".sb-empty-state__title");
Assert.NotNull(title);
Assert.Contains("No documents", title.TextContent);
}
[Fact]
public void RendersDescriptionWhenProvided()
{
// Arrange & Act
var cut = RenderEmptyState(p => p.Add(x => x.Description, "Create your first item to get started."));
// Assert
var description = cut.Find(".sb-empty-state__description");
Assert.NotNull(description);
Assert.Contains("Create your first item to get started.", description.TextContent);
}
[Fact]
public void DoesNotRenderDescriptionWhenEmpty()
{
// Arrange & Act - Description defaults to null
var cut = RenderEmptyState();
// Assert
Assert.Empty(cut.FindAll(".sb-empty-state__description"));
}
[Fact]
public void RendersIconWhenProvided()
{
// Arrange & Act
var cut = RenderEmptyState(p => p
.Add(x => x.Icon, i => i.AddMarkupContent(0, "<span class=\"custom-icon\">📦</span>"))
.Add(x => x.IconText, (string?)null)); // clear default to test Icon takes precedence
// Assert
var iconContainer = cut.Find(".sb-empty-state__icon");
Assert.NotNull(iconContainer);
var customIcon = cut.Find(".custom-icon");
Assert.NotNull(customIcon);
Assert.Contains("📦", customIcon.TextContent);
}
[Fact]
public void RendersIconTextWhenIconNull()
{
// Arrange & Act
var cut = RenderEmptyState(p => p.Add(x => x.IconText, "🔍"));
// Assert
var iconText = cut.Find(".sb-empty-state__icon-text");
Assert.NotNull(iconText);
Assert.Contains("🔍", iconText.TextContent);
}
[Fact]
public void IconTakesPrecedenceOverIconText()
{
// Arrange & Act
var cut = RenderEmptyState(p => p
.Add(x => x.Icon, i => i.AddMarkupContent(0, "<span>Custom</span>"))
.Add(x => x.IconText, "Fallback"));
// Assert - Icon used, not IconText
Assert.Contains("Custom", cut.Markup);
Assert.Empty(cut.FindAll(".sb-empty-state__icon-text"));
}
[Fact]
public void RendersChildContentWhenProvided()
{
// Arrange & Act
var cut = RenderEmptyState(p => p
.AddChildContent("<a href=\"#\" class=\"create-link\">Create item</a>"));
// Assert
var content = cut.Find(".sb-empty-state__content");
Assert.NotNull(content);
var link = cut.Find(".create-link");
Assert.NotNull(link);
}
[Fact]
public void DoesNotRenderContentWhenChildContentNull()
{
// Arrange & Act
var cut = RenderEmptyState();
// Assert
Assert.Empty(cut.FindAll(".sb-empty-state__content"));
}
[Fact]
public void RendersActionsWhenProvided()
{
// Arrange & Act
var cut = RenderEmptyState(p => p
.Add(x => x.Actions, a => a.AddMarkupContent(0, "<button class=\"action-btn\">Add</button>")));
// Assert
var actions = cut.Find(".sb-empty-state__actions");
Assert.NotNull(actions);
var btn = cut.Find(".action-btn");
Assert.NotNull(btn);
Assert.Contains("Add", btn.TextContent);
}
[Fact]
public void DoesNotRenderActionsWhenNull()
{
// Arrange & Act
var cut = RenderEmptyState();
// Assert
Assert.Empty(cut.FindAll(".sb-empty-state__actions"));
}
[Fact]
public void AppliesCustomClass()
{
// Arrange & Act
var cut = RenderEmptyState(p => p.Add(x => x.Class, "my-empty-state"));
// Assert
var container = cut.Find(".sb-empty-state");
Assert.Contains("my-empty-state", container.ClassList);
}
[Fact]
public void RendersCompleteWithAllParts()
{
// Arrange & Act
var cut = Render<SbEmptyState>(p => p
.Add(x => x.IconText, "📋")
.Add(x => x.Title, "No tasks")
.Add(x => x.Description, "Add a task to begin.")
.AddChildContent("<p>Or import from file.</p>")
.Add(x => x.Actions, a => a.AddMarkupContent(0, "<button>New Task</button>")));
// Assert
Assert.Contains("📋", cut.Markup);
Assert.Contains("No tasks", cut.Markup);
Assert.Contains("Add a task to begin.", cut.Markup);
Assert.Contains("Or import from file.", cut.Markup);
Assert.Contains("New Task", cut.Markup);
}
}
@@ -0,0 +1,238 @@
using Microsoft.AspNetCore.Components;
using Bunit;
using SufiChain.SufiBlazor.Components;
using SufiChain.SufiBlazor.Components.Feedback;
using Xunit;
namespace SufiChain.SufiBlazor.Tests.Components.Feedback;
public class SbProgressTests : BunitContext
{
private IRenderedComponent<SbProgress> RenderProgress(
Action<ComponentParameterCollectionBuilder<SbProgress>>? configure = null)
{
return Render<SbProgress>(p =>
{
p.Add(x => x.Value, 50);
configure?.Invoke(p);
});
}
[Fact]
public void RendersLinearStructureByDefault()
{
// Arrange & Act
var cut = RenderProgress();
// Assert
var progress = cut.Find(".sb-progress");
Assert.NotNull(progress);
Assert.Equal("progressbar", progress.GetAttribute("role"));
Assert.NotNull(cut.Find(".sb-progress__track"));
Assert.NotNull(cut.Find(".sb-progress__bar"));
Assert.Contains("sb-progress--linear", progress.ClassList);
}
[Fact]
public void RendersCircularStructure()
{
// Arrange & Act
var cut = RenderProgress(p => p.Add(x => x.Type, SbProgressType.Circular));
// Assert
var progress = cut.Find(".sb-progress");
Assert.NotNull(progress);
Assert.Equal("svg", progress.TagName.ToLowerInvariant());
Assert.NotNull(cut.Find(".sb-progress__track-circle"));
Assert.NotNull(cut.Find(".sb-progress__bar-circle"));
Assert.Contains("sb-progress--circular", progress.ClassList);
}
[Fact]
public void AppliesValueToLinearBar()
{
// Arrange & Act - use Render directly to avoid Value added twice
var cut = Render<SbProgress>(p => p.Add(x => x.Value, 75));
// Assert
var bar = cut.Find(".sb-progress__bar");
Assert.NotNull(bar);
Assert.Contains("width: 75%", bar.GetAttribute("style"));
}
[Fact]
public void ClampsValueTo100()
{
// Arrange & Act
var cut = Render<SbProgress>(p => p.Add(x => x.Value, 150));
// Assert
var bar = cut.Find(".sb-progress__bar");
Assert.NotNull(bar);
Assert.Contains("width: 100%", bar.GetAttribute("style"));
}
[Fact]
public void ClampsValueTo0()
{
// Arrange & Act
var cut = Render<SbProgress>(p => p.Add(x => x.Value, -10));
// Assert
var bar = cut.Find(".sb-progress__bar");
Assert.NotNull(bar);
Assert.Contains("width: 0%", bar.GetAttribute("style"));
}
[Fact]
public void SetsAriaValuenowWhenNotIndeterminate()
{
// Arrange & Act
var cut = Render<SbProgress>(p => p.Add(x => x.Value, 60));
// Assert
var progress = cut.Find(".sb-progress");
Assert.Equal("60", progress.GetAttribute("aria-valuenow"));
Assert.Equal("0", progress.GetAttribute("aria-valuemin"));
Assert.Equal("100", progress.GetAttribute("aria-valuemax"));
}
[Fact]
public void SetsAriaValuenowNullWhenIndeterminate()
{
// Arrange & Act
var cut = RenderProgress(p => p.Add(x => x.Indeterminate, true));
// Assert
var progress = cut.Find(".sb-progress");
Assert.Null(progress.GetAttribute("aria-valuenow"));
}
[Fact]
public void AppliesIndeterminateClass()
{
// Arrange & Act
var cut = RenderProgress(p => p.Add(x => x.Indeterminate, true));
// Assert
var progress = cut.Find(".sb-progress");
Assert.Contains("sb-progress--indeterminate", progress.ClassList);
}
[Fact]
public void RendersLabelWhenShowLabelTrue()
{
// Arrange & Act
var cut = Render<SbProgress>(p => p
.Add(x => x.Value, 42)
.Add(x => x.ShowLabel, true));
// Assert
var label = cut.Find(".sb-progress__label");
Assert.NotNull(label);
Assert.Contains("42%", label.TextContent);
}
[Fact]
public void DoesNotRenderLabelWhenShowLabelFalse()
{
// Arrange & Act
var cut = RenderProgress();
// Assert
Assert.Empty(cut.FindAll(".sb-progress__label"));
}
[Fact]
public void DoesNotRenderLabelWhenIndeterminate()
{
// Arrange & Act
var cut = RenderProgress(p => p
.Add(x => x.ShowLabel, true)
.Add(x => x.Indeterminate, true));
// Assert
Assert.Empty(cut.FindAll(".sb-progress__label"));
}
[Fact]
public void AppliesPrimaryColorByDefault()
{
// Arrange & Act
var cut = RenderProgress();
// Assert
var progress = cut.Find(".sb-progress");
Assert.Contains("sb-progress--primary", progress.ClassList);
}
[Fact]
public void AppliesSuccessColor()
{
// Arrange & Act
var cut = RenderProgress(p => p.Add(x => x.Color, SbColor.Success));
// Assert
var progress = cut.Find(".sb-progress");
Assert.Contains("sb-progress--success", progress.ClassList);
}
[Fact]
public void AppliesMdSizeByDefault()
{
// Arrange & Act
var cut = RenderProgress();
// Assert
var progress = cut.Find(".sb-progress");
Assert.Contains("sb-progress--md", progress.ClassList);
}
[Fact]
public void AppliesSmSize()
{
// Arrange & Act
var cut = RenderProgress(p => p.Add(x => x.Size, SbSize.Sm));
// Assert
var progress = cut.Find(".sb-progress");
Assert.Contains("sb-progress--sm", progress.ClassList);
}
[Fact]
public void AppliesCustomClass()
{
// Arrange & Act
var cut = RenderProgress(p => p.Add(x => x.Class, "my-progress"));
// Assert
var progress = cut.Find(".sb-progress");
Assert.Contains("my-progress", progress.ClassList);
}
[Fact]
public void AppliesInlineStyle()
{
// Arrange & Act
var cut = RenderProgress(p => p.Add(x => x.Style, "height: 8px;"));
// Assert
var progress = cut.Find(".sb-progress");
Assert.Contains("height: 8px", progress.GetAttribute("style"));
}
[Fact]
public void CircularRendersLabelWhenShowLabelTrue()
{
// Arrange & Act
var cut = Render<SbProgress>(p => p
.Add(x => x.Type, SbProgressType.Circular)
.Add(x => x.Value, 33)
.Add(x => x.ShowLabel, true));
// Assert
Assert.Contains("33%", cut.Markup);
var labelText = cut.Find(".sb-progress__label-text");
Assert.NotNull(labelText);
}
}
@@ -0,0 +1,154 @@
using Microsoft.AspNetCore.Components;
using Bunit;
using SufiChain.SufiBlazor.Components.Feedback;
using Xunit;
namespace SufiChain.SufiBlazor.Tests.Components.Feedback;
public class SbSkeletonTests : BunitContext
{
private IRenderedComponent<SbSkeleton> RenderSkeleton(
Action<ComponentParameterCollectionBuilder<SbSkeleton>>? configure = null)
{
return Render<SbSkeleton>(p => configure?.Invoke(p));
}
[Fact]
public void RendersSkeletonStructure()
{
// Arrange & Act
var cut = RenderSkeleton();
// Assert
var skeleton = cut.Find(".sb-skeleton");
Assert.NotNull(skeleton);
Assert.Equal("div", skeleton.TagName.ToLowerInvariant());
Assert.Equal("true", skeleton.GetAttribute("aria-hidden"));
}
[Fact]
public void AppliesTextVariantByDefault()
{
// Arrange & Act
var cut = RenderSkeleton();
// Assert
var skeleton = cut.Find(".sb-skeleton");
Assert.Contains("sb-skeleton--text", skeleton.ClassList);
}
[Fact]
public void AppliesCircularVariant()
{
// Arrange & Act
var cut = RenderSkeleton(p => p.Add(x => x.Variant, SbSkeletonVariant.Circular));
// Assert
var skeleton = cut.Find(".sb-skeleton");
Assert.Contains("sb-skeleton--circular", skeleton.ClassList);
}
[Fact]
public void AppliesRectangularVariant()
{
// Arrange & Act
var cut = RenderSkeleton(p => p.Add(x => x.Variant, SbSkeletonVariant.Rectangular));
// Assert
var skeleton = cut.Find(".sb-skeleton");
Assert.Contains("sb-skeleton--rectangular", skeleton.ClassList);
}
[Fact]
public void AppliesRoundedVariant()
{
// Arrange & Act
var cut = RenderSkeleton(p => p.Add(x => x.Variant, SbSkeletonVariant.Rounded));
// Assert
var skeleton = cut.Find(".sb-skeleton");
Assert.Contains("sb-skeleton--rounded", skeleton.ClassList);
}
[Fact]
public void AppliesAnimatedClassByDefault()
{
// Arrange & Act
var cut = RenderSkeleton();
// Assert
var skeleton = cut.Find(".sb-skeleton");
Assert.Contains("sb-skeleton--animated", skeleton.ClassList);
}
[Fact]
public void DoesNotApplyAnimatedClassWhenAnimationFalse()
{
// Arrange & Act
var cut = RenderSkeleton(p => p.Add(x => x.Animation, false));
// Assert
var skeleton = cut.Find(".sb-skeleton");
Assert.DoesNotContain("sb-skeleton--animated", skeleton.ClassList);
}
[Fact]
public void AppliesWidthStyle()
{
// Arrange & Act
var cut = RenderSkeleton(p => p.Add(x => x.Width, "200px"));
// Assert
var skeleton = cut.Find(".sb-skeleton");
Assert.Contains("width: 200px", skeleton.GetAttribute("style"));
}
[Fact]
public void AppliesHeightStyle()
{
// Arrange & Act
var cut = RenderSkeleton(p => p.Add(x => x.Height, "24px"));
// Assert
var skeleton = cut.Find(".sb-skeleton");
Assert.Contains("height: 24px", skeleton.GetAttribute("style"));
}
[Fact]
public void AppliesBorderRadiusStyle()
{
// Arrange & Act
var cut = RenderSkeleton(p => p.Add(x => x.BorderRadius, "8px"));
// Assert
var skeleton = cut.Find(".sb-skeleton");
Assert.Contains("border-radius: 8px", skeleton.GetAttribute("style"));
}
[Fact]
public void AppliesMultipleDimensionStyles()
{
// Arrange & Act
var cut = RenderSkeleton(p => p
.Add(x => x.Width, "100%")
.Add(x => x.Height, "200px")
.Add(x => x.BorderRadius, "4px"));
// Assert
var style = cut.Find(".sb-skeleton").GetAttribute("style");
Assert.Contains("width: 100%", style);
Assert.Contains("height: 200px", style);
Assert.Contains("border-radius: 4px", style);
}
[Fact]
public void AppliesCustomClass()
{
// Arrange & Act
var cut = RenderSkeleton(p => p.Add(x => x.Class, "my-skeleton"));
// Assert
var skeleton = cut.Find(".sb-skeleton");
Assert.Contains("my-skeleton", skeleton.ClassList);
}
}
+193
View File
@@ -0,0 +1,193 @@
using System.Globalization;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
using Bunit;
using SufiChain.SufiBlazor.Components.Feedback;
using SufiChain.SufiBlazor.Localization;
using Xunit;
namespace SufiChain.SufiBlazor.Tests.Components.Feedback;
/// <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 SbToastTests : BunitContext
{
public SbToastTests()
{
Services.AddSingleton<IStringLocalizer<SufiBlazorResource>>(new StubStringLocalizer());
}
private IRenderedComponent<SbToast> RenderToast(
Action<ComponentParameterCollectionBuilder<SbToast>>? configure = null)
{
return Render<SbToast>(p =>
{
p.Add(x => x.Message, "Toast message");
configure?.Invoke(p);
});
}
[Fact]
public void RendersToastStructure()
{
// Arrange & Act
var cut = RenderToast();
// Assert
var toast = cut.Find(".sb-toast");
Assert.NotNull(toast);
Assert.Equal("alert", toast.GetAttribute("role"));
Assert.Equal("polite", toast.GetAttribute("aria-live"));
Assert.NotNull(cut.Find(".sb-toast__icon"));
Assert.NotNull(cut.Find(".sb-toast__content"));
Assert.NotNull(cut.Find(".sb-toast__message"));
}
[Fact]
public void RendersMessage()
{
// Arrange & Act
var cut = RenderToast();
// Assert
Assert.Contains("Toast message", cut.Markup);
}
[Fact]
public void RendersChildContentOverMessage()
{
// Arrange & Act
var cut = Render<SbToast>(p => p
.Add(x => x.Message, "Fallback")
.AddChildContent("<span class=\"custom-msg\">Custom content</span>"));
// Assert - ChildContent takes precedence
Assert.Contains("Custom content", cut.Markup);
}
[Fact]
public void RendersTitleWhenProvided()
{
// Arrange & Act
var cut = RenderToast(p => p.Add(x => x.Title, "Success"));
// Assert
var title = cut.Find(".sb-toast__title");
Assert.NotNull(title);
Assert.Contains("Success", title.TextContent);
}
[Fact]
public void DoesNotRenderTitleWhenEmpty()
{
// Arrange & Act
var cut = RenderToast();
// Assert
Assert.Empty(cut.FindAll(".sb-toast__title"));
}
[Fact]
public void AppliesInfoSeverityByDefault()
{
// Arrange & Act
var cut = RenderToast();
// Assert
var toast = cut.Find(".sb-toast");
Assert.Contains("sb-toast--info", toast.ClassList);
}
[Fact]
public void AppliesSuccessSeverity()
{
// Arrange & Act
var cut = RenderToast(p => p.Add(x => x.Severity, SbToastSeverity.Success));
// Assert
var toast = cut.Find(".sb-toast");
Assert.Contains("sb-toast--success", toast.ClassList);
}
[Fact]
public void AppliesWarningSeverity()
{
// Arrange & Act
var cut = RenderToast(p => p.Add(x => x.Severity, SbToastSeverity.Warning));
// Assert
var toast = cut.Find(".sb-toast");
Assert.Contains("sb-toast--warning", toast.ClassList);
}
[Fact]
public void AppliesErrorSeverity()
{
// Arrange & Act
var cut = RenderToast(p => p.Add(x => x.Severity, SbToastSeverity.Error));
// Assert
var toast = cut.Find(".sb-toast");
Assert.Contains("sb-toast--error", toast.ClassList);
}
[Fact]
public void RendersCloseButtonByDefault()
{
// Arrange & Act
var cut = RenderToast();
// Assert
var closeBtn = cut.Find(".sb-toast__close");
Assert.NotNull(closeBtn);
Assert.Equal("Close", closeBtn.GetAttribute("aria-label"));
}
[Fact]
public void DoesNotRenderCloseButtonWhenShowCloseButtonFalse()
{
// Arrange & Act
var cut = RenderToast(p => p.Add(x => x.ShowCloseButton, false));
// Assert
Assert.Empty(cut.FindAll(".sb-toast__close"));
}
[Fact]
public async Task InvokesOnCloseWhenCloseButtonClicked()
{
// Arrange
var closed = false;
var cut = RenderToast(p => p.Add(x => x.OnClose, EventCallback.Factory.Create(this, () => closed = true)));
var closeBtn = cut.Find(".sb-toast__close");
// Act
await cut.InvokeAsync(() => closeBtn!.Click());
cut.WaitForState(() => closed);
// Assert
Assert.True(closed);
}
[Fact]
public void AppliesCustomClass()
{
// Arrange & Act
var cut = RenderToast(p => p.Add(x => x.Class, "my-toast"));
// Assert
var toast = cut.Find(".sb-toast");
Assert.Contains("my-toast", toast.ClassList);
}
}