420 lines
13 KiB
C#
420 lines
13 KiB
C#
using Microsoft.AspNetCore.Components;
|
|
using Microsoft.AspNetCore.Components.Web;
|
|
using Bunit;
|
|
using Bunit.JSInterop;
|
|
using SufiChain.SufiBlazor.Components.Overlays;
|
|
using Xunit;
|
|
|
|
namespace SufiChain.SufiBlazor.Tests.Components.Overlays;
|
|
|
|
public class SbMenuTests : BunitContext
|
|
{
|
|
public SbMenuTests()
|
|
{
|
|
JSInterop.Mode = JSRuntimeMode.Loose;
|
|
}
|
|
|
|
private static RenderFragment DefaultMenuItems => (b) =>
|
|
{
|
|
b.OpenComponent<SbMenuItem>(0);
|
|
b.AddAttribute(1, "Text", "Item 1");
|
|
b.CloseComponent();
|
|
b.OpenComponent<SbMenuItem>(2);
|
|
b.AddAttribute(3, "Text", "Item 2");
|
|
b.CloseComponent();
|
|
};
|
|
|
|
private IRenderedComponent<SbMenu> RenderMenu(
|
|
Action<ComponentParameterCollectionBuilder<SbMenu>>? configure = null)
|
|
{
|
|
return Render<SbMenu>(p =>
|
|
{
|
|
p.Add(x => x.AnchorContent, (RenderFragment)(b => b.AddMarkupContent(0, "<button class=\"anchor-btn\">Open</button>")));
|
|
p.AddChildContent(DefaultMenuItems);
|
|
configure?.Invoke(p);
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public void RendersAnchorContent()
|
|
{
|
|
// Arrange & Act
|
|
var cut = RenderMenu();
|
|
|
|
// Assert
|
|
var anchor = cut.Find(".sb-menu-anchor");
|
|
Assert.NotNull(anchor);
|
|
Assert.Contains("Open", cut.Markup);
|
|
Assert.NotNull(cut.Find(".anchor-btn"));
|
|
}
|
|
|
|
[Fact]
|
|
public void DoesNotRenderMenuWhenClosed()
|
|
{
|
|
// Arrange & Act
|
|
var cut = RenderMenu();
|
|
|
|
// Assert
|
|
Assert.Empty(cut.FindAll(".sb-menu"));
|
|
}
|
|
|
|
[Fact]
|
|
public void RendersMenuWhenOpen()
|
|
{
|
|
// Arrange & Act
|
|
var cut = RenderMenu(p => p.Add(x => x.Open, true));
|
|
|
|
// Assert
|
|
var menu = cut.Find(".sb-menu");
|
|
Assert.NotNull(menu);
|
|
Assert.Equal("menu", menu.GetAttribute("role"));
|
|
}
|
|
|
|
[Fact]
|
|
public void RendersMenuItemsWhenOpen()
|
|
{
|
|
// Arrange & Act
|
|
var cut = RenderMenu(p => p.Add(x => x.Open, true));
|
|
|
|
// Assert
|
|
Assert.Contains("Item 1", cut.Markup);
|
|
Assert.Contains("Item 2", cut.Markup);
|
|
var items = cut.FindAll(".sb-menu-item");
|
|
Assert.Equal(2, items.Count);
|
|
}
|
|
|
|
[Fact]
|
|
public void MenuItemsHaveMenuitemRole()
|
|
{
|
|
// Arrange & Act
|
|
var cut = RenderMenu(p => p.Add(x => x.Open, true));
|
|
|
|
// Assert
|
|
var items = cut.FindAll("[role=\"menuitem\"]");
|
|
Assert.Equal(2, items.Count);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(SbPlacement.BottomStart, "bottom-start")]
|
|
[InlineData(SbPlacement.Top, "top")]
|
|
[InlineData(SbPlacement.End, "end")]
|
|
[InlineData(SbPlacement.Start, "start")]
|
|
public void AppliesPlacementClass(SbPlacement placement, string expectedClass)
|
|
{
|
|
// Arrange & Act
|
|
var cut = RenderMenu(p => p
|
|
.Add(x => x.Open, true)
|
|
.Add(x => x.Placement, placement));
|
|
|
|
// Assert
|
|
var menu = cut.Find(".sb-menu");
|
|
Assert.Contains($"sb-menu--{expectedClass}", menu.ClassList);
|
|
}
|
|
|
|
[Fact]
|
|
public void AppliesClassParameter()
|
|
{
|
|
// Arrange & Act
|
|
var cut = RenderMenu(p => p
|
|
.Add(x => x.Open, true)
|
|
.Add(x => x.Class, "my-menu"));
|
|
|
|
// Assert
|
|
var menu = cut.Find(".sb-menu");
|
|
Assert.Contains("my-menu", menu.ClassList);
|
|
}
|
|
|
|
[Fact]
|
|
public void MenuItemRendersText()
|
|
{
|
|
// Arrange & Act
|
|
var cut = RenderMenu(p => p
|
|
.AddChildContent(b =>
|
|
{
|
|
b.OpenComponent<SbMenuItem>(0);
|
|
b.AddAttribute(1, "Text", "Save File");
|
|
b.CloseComponent();
|
|
})
|
|
.Add(x => x.Open, true));
|
|
|
|
// Assert
|
|
Assert.Contains("Save File", cut.Markup);
|
|
var textSpan = cut.Find(".sb-menu-item__text");
|
|
Assert.NotNull(textSpan);
|
|
}
|
|
|
|
[Fact]
|
|
public void MenuItemRendersChildContentOverridesText()
|
|
{
|
|
// Arrange & Act
|
|
var cut = RenderMenu(p => p
|
|
.AddChildContent(b =>
|
|
{
|
|
b.OpenComponent<SbMenuItem>(0);
|
|
b.AddAttribute(1, "Text", "Ignore");
|
|
b.AddAttribute(2, "ChildContent", (RenderFragment)(c => c.AddMarkupContent(0, "<span>Custom</span>")));
|
|
b.CloseComponent();
|
|
})
|
|
.Add(x => x.Open, true));
|
|
|
|
// Assert
|
|
Assert.Contains("Custom", cut.Markup);
|
|
}
|
|
|
|
[Fact]
|
|
public void MenuItemRendersShortcut()
|
|
{
|
|
// Arrange & Act
|
|
var cut = RenderMenu(p => p
|
|
.AddChildContent(b =>
|
|
{
|
|
b.OpenComponent<SbMenuItem>(0);
|
|
b.AddAttribute(1, "Text", "New");
|
|
b.AddAttribute(2, "Shortcut", "Ctrl+N");
|
|
b.CloseComponent();
|
|
})
|
|
.Add(x => x.Open, true));
|
|
|
|
// Assert
|
|
var shortcut = cut.Find(".sb-menu-item__shortcut");
|
|
Assert.NotNull(shortcut);
|
|
Assert.Equal("Ctrl+N", shortcut.TextContent.Trim());
|
|
}
|
|
|
|
[Fact]
|
|
public void DisabledMenuItemHasDisabledClassAndAttribute()
|
|
{
|
|
// Arrange & Act
|
|
var cut = Render<SbMenu>(p =>
|
|
{
|
|
p.Add(x => x.AnchorContent, (RenderFragment)(b => b.AddMarkupContent(0, "<button>Open</button>")));
|
|
p.Add(x => x.Open, true);
|
|
p.Add(x => x.ChildContent, (RenderFragment)(b =>
|
|
{
|
|
b.OpenComponent<SbMenuItem>(0);
|
|
b.AddAttribute(1, "Text", "Disabled");
|
|
b.AddAttribute(2, "Disabled", true);
|
|
b.CloseComponent();
|
|
}));
|
|
});
|
|
|
|
// Assert
|
|
var item = cut.Find("button.sb-menu-item");
|
|
Assert.Contains("sb-menu-item--disabled", item.GetAttribute("class") ?? "");
|
|
Assert.NotNull(item.GetAttribute("disabled"));
|
|
}
|
|
|
|
[Fact]
|
|
public void MenuItemAppliesClassParameter()
|
|
{
|
|
// Arrange & Act
|
|
var cut = Render<SbMenu>(p =>
|
|
{
|
|
p.Add(x => x.AnchorContent, (RenderFragment)(b => b.AddMarkupContent(0, "<button>Open</button>")));
|
|
p.Add(x => x.Open, true);
|
|
p.Add(x => x.ChildContent, (RenderFragment)(b =>
|
|
{
|
|
b.OpenComponent<SbMenuItem>(0);
|
|
b.AddAttribute(1, "Text", "Item");
|
|
b.AddAttribute(2, "Class", "my-item");
|
|
b.CloseComponent();
|
|
}));
|
|
});
|
|
|
|
// Assert
|
|
var item = cut.Find("button.sb-menu-item");
|
|
Assert.Contains("my-item", item.GetAttribute("class") ?? "");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task MenuItemClickInvokesOnClick()
|
|
{
|
|
// Arrange - use default items with OnClick on first item via markup in ChildContent
|
|
var clicked = false;
|
|
var cut = Render<SbMenu>(p =>
|
|
{
|
|
p.Add(x => x.AnchorContent, (RenderFragment)(b => b.AddMarkupContent(0, "<button>Open</button>")));
|
|
p.Add(x => x.Open, true);
|
|
p.Add(x => x.CloseOnItemClick, false);
|
|
p.Add(x => x.ChildContent, (RenderFragment)(b =>
|
|
{
|
|
b.OpenComponent<SbMenuItem>(0);
|
|
b.AddAttribute(1, "Text", "Click Me");
|
|
b.AddAttribute(2, "OnClick", EventCallback.Factory.Create(this, () => clicked = true));
|
|
b.CloseComponent();
|
|
}));
|
|
});
|
|
|
|
// Act
|
|
var item = cut.Find("button.sb-menu-item");
|
|
await cut.InvokeAsync(() => item.Click());
|
|
|
|
// Assert
|
|
Assert.True(clicked);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task MenuItemClickClosesMenuWhenCloseOnItemClickTrue()
|
|
{
|
|
// Arrange
|
|
var openChangedValue = true;
|
|
var cut = RenderMenu(p => p
|
|
.Add(x => x.Open, true)
|
|
.Add(x => x.CloseOnItemClick, true)
|
|
.Add(x => x.OpenChanged, EventCallback.Factory.Create<bool>(this, v => openChangedValue = v)));
|
|
|
|
// Act
|
|
var item = cut.Find("button.sb-menu-item");
|
|
await cut.InvokeAsync(() => item.Click());
|
|
|
|
// Assert - OpenChanged invoked with false
|
|
Assert.False(openChangedValue);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task MenuItemClickDoesNotCloseMenuWhenCloseOnItemClickFalse()
|
|
{
|
|
// Arrange
|
|
var openChangedValue = true;
|
|
var cut = RenderMenu(p => p
|
|
.Add(x => x.Open, true)
|
|
.Add(x => x.CloseOnItemClick, false)
|
|
.Add(x => x.OpenChanged, EventCallback.Factory.Create<bool>(this, v => openChangedValue = v)));
|
|
|
|
// Act
|
|
var item = cut.Find("button.sb-menu-item");
|
|
await cut.InvokeAsync(() => item.Click());
|
|
|
|
// Assert
|
|
Assert.True(openChangedValue);
|
|
Assert.NotNull(cut.Find(".sb-menu"));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DisabledMenuItemClickDoesNotInvokeOnClick()
|
|
{
|
|
// Arrange - disabled item should not fire OnClick
|
|
var clicked = false;
|
|
var cut = Render<SbMenu>(p =>
|
|
{
|
|
p.Add(x => x.AnchorContent, (RenderFragment)(b => b.AddMarkupContent(0, "<button>Open</button>")));
|
|
p.Add(x => x.Open, true);
|
|
p.Add(x => x.ChildContent, (RenderFragment)(b =>
|
|
{
|
|
b.OpenComponent<SbMenuItem>(0);
|
|
b.AddAttribute(1, "Text", "Disabled");
|
|
b.AddAttribute(2, "Disabled", true);
|
|
b.AddAttribute(3, "OnClick", EventCallback.Factory.Create(this, () => clicked = true));
|
|
b.CloseComponent();
|
|
}));
|
|
});
|
|
|
|
// Act
|
|
var item = cut.Find("button.sb-menu-item");
|
|
await cut.InvokeAsync(() => item.Click());
|
|
|
|
// Assert - disabled items don't respond to click
|
|
Assert.False(clicked);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task AnchorKeyDownEnterOpensMenu()
|
|
{
|
|
// Arrange
|
|
var openChangedValue = false;
|
|
var cut = RenderMenu(p => p.Add(x => x.OpenChanged, EventCallback.Factory.Create<bool>(this, v => openChangedValue = v)));
|
|
var anchorDiv = cut.Find(".sb-menu-anchor > div");
|
|
|
|
// Act
|
|
await cut.InvokeAsync(() => anchorDiv.TriggerEventAsync("onkeydown", new KeyboardEventArgs { Key = "Enter" }));
|
|
|
|
// Assert
|
|
Assert.True(openChangedValue);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task AnchorKeyDownSpaceOpensMenu()
|
|
{
|
|
// Arrange
|
|
var openChangedValue = false;
|
|
var cut = RenderMenu(p => p.Add(x => x.OpenChanged, EventCallback.Factory.Create<bool>(this, v => openChangedValue = v)));
|
|
var anchorDiv = cut.Find(".sb-menu-anchor > div");
|
|
|
|
// Act
|
|
await cut.InvokeAsync(() => anchorDiv.TriggerEventAsync("onkeydown", new KeyboardEventArgs { Key = " " }));
|
|
|
|
// Assert
|
|
Assert.True(openChangedValue);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task AnchorKeyDownArrowDownOpensMenu()
|
|
{
|
|
// Arrange
|
|
var openChangedValue = false;
|
|
var cut = RenderMenu(p => p.Add(x => x.OpenChanged, EventCallback.Factory.Create<bool>(this, v => openChangedValue = v)));
|
|
var anchorDiv = cut.Find(".sb-menu-anchor > div");
|
|
|
|
// Act
|
|
await cut.InvokeAsync(() => anchorDiv.TriggerEventAsync("onkeydown", new KeyboardEventArgs { Key = "ArrowDown" }));
|
|
|
|
// Assert
|
|
Assert.True(openChangedValue);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task MenuKeyDownEscapeClosesMenu()
|
|
{
|
|
// Arrange
|
|
var openChangedValue = true;
|
|
var cut = RenderMenu(p => p
|
|
.Add(x => x.Open, true)
|
|
.Add(x => x.OpenChanged, EventCallback.Factory.Create<bool>(this, v => openChangedValue = v)));
|
|
var menu = cut.Find(".sb-menu");
|
|
|
|
// Act
|
|
await cut.InvokeAsync(() => menu.TriggerEventAsync("onkeydown", new KeyboardEventArgs { Key = "Escape" }));
|
|
|
|
// Assert
|
|
Assert.False(openChangedValue);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task MenuKeyDownTabClosesMenu()
|
|
{
|
|
// Arrange
|
|
var openChangedValue = true;
|
|
var cut = RenderMenu(p => p
|
|
.Add(x => x.Open, true)
|
|
.Add(x => x.OpenChanged, EventCallback.Factory.Create<bool>(this, v => openChangedValue = v)));
|
|
var menu = cut.Find(".sb-menu");
|
|
|
|
// Act
|
|
await cut.InvokeAsync(() => menu.TriggerEventAsync("onkeydown", new KeyboardEventArgs { Key = "Tab" }));
|
|
|
|
// Assert
|
|
Assert.False(openChangedValue);
|
|
}
|
|
|
|
|
|
[Fact]
|
|
public void MenuItemRendersIcon()
|
|
{
|
|
// Arrange & Act
|
|
var cut = RenderMenu(p => p
|
|
.AddChildContent(b =>
|
|
{
|
|
b.OpenComponent<SbMenuItem>(0);
|
|
b.AddAttribute(1, "Text", "Edit");
|
|
b.AddAttribute(2, "Icon", (RenderFragment)(c => c.AddMarkupContent(0, "<span class=\"icon\">✎</span>")));
|
|
b.CloseComponent();
|
|
})
|
|
.Add(x => x.Open, true));
|
|
|
|
// Assert
|
|
var icon = cut.Find(".sb-menu-item__icon");
|
|
Assert.NotNull(icon);
|
|
Assert.Contains("✎", cut.Markup);
|
|
}
|
|
}
|