diff --git a/src/BootstrapBlazor/Components/Tab/Tab.razor.cs b/src/BootstrapBlazor/Components/Tab/Tab.razor.cs
index 98dc9c060bc9bd961d444fc1364231170ce16b1b..00183e36755b850b08b3d4e78d43d78c9b64b6b6 100644
--- a/src/BootstrapBlazor/Components/Tab/Tab.razor.cs
+++ b/src/BootstrapBlazor/Components/Tab/Tab.razor.cs
@@ -170,13 +170,6 @@ public partial class Tab
[NotNull]
public string? CloseCurrentTabText { get; set; }
- ///
- /// 获得/设置 空白 Tab 显示文字
- ///
- [Parameter]
- [NotNull]
- public string? NullTabText { get; set; }
-
///
/// 获得/设置 关闭所有 TabItem 菜单文本
///
@@ -256,44 +249,24 @@ public partial class Tab
}
}
- private bool CheckUrl(string url)
- {
- var ret = false;
- foreach (var rule in ExcludeUrls ?? Enumerable.Empty())
- {
- var checkUrl = rule.TrimStart('/');
- var startIndex = checkUrl.IndexOf("?");
- if (startIndex > 0)
- {
- checkUrl = checkUrl[..startIndex];
- if (url.StartsWith(checkUrl, StringComparison.OrdinalIgnoreCase))
- {
- ret = true;
- break;
- }
- }
- else
- {
- if (url.Equals(checkUrl, StringComparison.OrdinalIgnoreCase))
- {
- ret = true;
- break;
- }
- }
- }
- return ret;
- }
-
private void AddTabByUrl()
{
var requestUrl = Navigator.ToBaseRelativePath(Navigator.Uri);
// 判断是否排除
- Excluded = CheckUrl(requestUrl);
-
+ var urls = ExcludeUrls ?? Enumerable.Empty();
+ if (requestUrl == "")
+ {
+ Excluded = urls.Any(u => u == "" || u == "/");
+ }
+ else
+ {
+ Excluded = urls.Any(u => u != "/" && requestUrl.StartsWith(u.TrimStart('/'), StringComparison.OrdinalIgnoreCase));
+ }
if (!Excluded)
{
- var tab = Items.FirstOrDefault(tab => tab.Url?.Equals(requestUrl, StringComparison.OrdinalIgnoreCase) ?? false);
+ // 地址相同参数不同需要重新渲染 TabItem
+ var tab = Items.FirstOrDefault(tab => tab.Url.TrimStart('/').Equals(requestUrl, StringComparison.OrdinalIgnoreCase));
if (tab != null)
{
ActiveTabItem(tab);
@@ -366,7 +339,7 @@ public partial class Tab
item = Items.ElementAt(index);
if (ClickTabToNavigation)
{
- Navigator.NavigateTo(item.Url!);
+ Navigator.NavigateTo(item.Url);
}
else
{
@@ -404,7 +377,7 @@ public partial class Tab
if (ClickTabToNavigation)
{
- Navigator.NavigateTo(item.Url!);
+ Navigator.NavigateTo(item.Url);
}
else
{
@@ -523,29 +496,16 @@ public partial class Tab
{
var text = Options.Text;
var icon = Options.Icon ?? string.Empty;
- var active = Options.IsActive ?? true;
- var closable = Options.Closable ?? true;
+ var active = Options.IsActive;
+ var closable = Options.Closable;
Options.Reset();
parameters.Add(nameof(TabItem.Url), url);
parameters.Add(nameof(TabItem.Icon), icon);
parameters.Add(nameof(TabItem.Closable), closable);
parameters.Add(nameof(TabItem.IsActive), active);
- parameters.Add(nameof(TabItem.Text), GetTabText(text, context.Segments));
- }
- }
-
- private string GetTabText(string? text, string[]? segments)
- {
- if (NullTabText == null)
- {
- var t = Localizer[nameof(NullTabText)];
- if (!t.ResourceNotFound)
- {
- NullTabText = t.Value;
- }
+ parameters.Add(nameof(TabItem.Text), text);
}
- return text ?? NullTabText ?? segments?.FirstOrDefault() ?? "";
}
///
@@ -596,7 +556,7 @@ public partial class Tab
{
if (ClickTabToNavigation)
{
- Navigator.NavigateTo(activeItem.Url!);
+ Navigator.NavigateTo(activeItem.Url);
}
else
{
diff --git a/src/BootstrapBlazor/Components/Tab/TabItem.cs b/src/BootstrapBlazor/Components/Tab/TabItem.cs
index 3617c51b5b935015d363fc78d1bea1e5e2de3d40..726ab95beff5efe7ec9e8f8a3358dc7adebc84cf 100644
--- a/src/BootstrapBlazor/Components/Tab/TabItem.cs
+++ b/src/BootstrapBlazor/Components/Tab/TabItem.cs
@@ -21,6 +21,7 @@ public class TabItem : ComponentBase
/// 获得/设置 请求地址
///
[Parameter]
+ [NotNull]
public string? Url { get; set; }
///
@@ -72,6 +73,7 @@ public class TabItem : ComponentBase
{
base.OnInitialized();
+ Url ??= "";
TabSet?.AddItem(this);
}
diff --git a/src/BootstrapBlazor/Extensions/NavigationManagerExtensions.cs b/src/BootstrapBlazor/Extensions/NavigationManagerExtensions.cs
index 860be33770e921da34dc784eb407d62e16b15a18..3760720bd4adac1431983c518314070472c98ad3 100644
--- a/src/BootstrapBlazor/Extensions/NavigationManagerExtensions.cs
+++ b/src/BootstrapBlazor/Extensions/NavigationManagerExtensions.cs
@@ -21,13 +21,12 @@ public static class NavigationManagerExtensions
///
///
///
- public static void NavigateTo(this NavigationManager navigation, IServiceProvider provider, string url, string text, string? icon = null, bool? closable = null)
+ public static void NavigateTo(this NavigationManager navigation, IServiceProvider provider, string url, string text, string? icon = null, bool closable = true)
{
var option = provider.GetRequiredService();
option.Text = text;
option.Icon = icon;
- option.IsActive = true;
- option.Closable = closable ?? true;
+ option.Closable = closable;
navigation.NavigateTo(url);
}
}
diff --git a/src/BootstrapBlazor/Options/TabItemTextOptions.cs b/src/BootstrapBlazor/Options/TabItemTextOptions.cs
index 3edbaec6dd189f5051c3d5322c24446574c070bc..6b2a693eb6d81deeb1fb7962151443b6687a011b 100644
--- a/src/BootstrapBlazor/Options/TabItemTextOptions.cs
+++ b/src/BootstrapBlazor/Options/TabItemTextOptions.cs
@@ -20,29 +20,29 @@ internal class TabItemTextOptions
public string? Icon { get; set; }
///
- /// 获得/设置 是否激活 默认为 null
+ /// 获得/设置 是否激活 默认为 true
///
///
- public bool? IsActive { get; set; }
+ public bool IsActive { get; set; } = true;
///
- /// 获得/设置 当前 TabItem 是否可关闭 默认为 null
+ /// 获得/设置 当前 TabItem 是否可关闭 默认为 true
///
- public bool? Closable { get; set; }
+ public bool Closable { get; set; } = true;
///
- ///
+ /// 重置方法
///
public void Reset()
{
Text = null;
Icon = null;
- IsActive = null;
- Closable = null;
+ IsActive = true;
+ Closable = true;
}
///
- ///
+ /// 是否可用方法
///
///
public bool Valid() => Text != null;
diff --git a/test/UnitTest/Components/LayoutTest.cs b/test/UnitTest/Components/LayoutTest.cs
index 28cbd1e9c7e949fc1cfb2540bceb176e5c5c60ad..ad080c188f19ae812f890410db14ed909ace8fbe 100644
--- a/test/UnitTest/Components/LayoutTest.cs
+++ b/test/UnitTest/Components/LayoutTest.cs
@@ -211,6 +211,10 @@ public class LayoutTest : BootstrapBlazorTestBase
});
navMan.NavigateTo("/");
Assert.Equal("http://localhost/Test", navMan.Uri);
+ cut.SetParametersAndRender(pb =>
+ {
+ pb.Add(a => a.OnAuthorizing, null);
+ });
}
[Fact]
diff --git a/test/UnitTest/Components/TabLinkTest.cs b/test/UnitTest/Components/TabLinkTest.cs
new file mode 100644
index 0000000000000000000000000000000000000000..302ac0bef7d53f5e14c27c05f60c22f9cfb9b1a0
--- /dev/null
+++ b/test/UnitTest/Components/TabLinkTest.cs
@@ -0,0 +1,38 @@
+// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+// Website: https://www.blazor.zone or https://argozhang.github.io/
+
+namespace UnitTest.Components;
+
+public class TabLinkTest : BootstrapBlazorTestBase
+{
+ [Fact]
+ public void Click_Ok()
+ {
+ var clicked = false;
+ var cut = Context.RenderComponent(pb =>
+ {
+ pb.Add(a => a.Url, "/Cat");
+ pb.Add(a => a.Text, "Cat");
+ pb.Add(a => a.Icon, "fa fa-fa");
+ pb.Add(a => a.Closable, false);
+ pb.Add(a => a.OnClick, () =>
+ {
+ clicked = true;
+ return Task.CompletedTask;
+ });
+ });
+ cut.Find("a").Click();
+ Assert.True(clicked);
+ }
+
+ [Fact]
+ public void ChildContent_Ok()
+ {
+ var cut = Context.RenderComponent(pb =>
+ {
+ pb.Add(a => a.ChildContent, builder => builder.AddContent(0, "Body"));
+ });
+ Assert.Contains("Body", cut.Markup);
+ }
+}
diff --git a/test/UnitTest/Components/TabTest.cs b/test/UnitTest/Components/TabTest.cs
index aaad053fdf711f6e22f1966e612fcfc00e7b73e2..2d63cf417c4a932508e4282bb8dbe96c8930835d 100644
--- a/test/UnitTest/Components/TabTest.cs
+++ b/test/UnitTest/Components/TabTest.cs
@@ -5,6 +5,7 @@
using Bunit.TestDoubles;
using Microsoft.Extensions.DependencyInjection;
using System.Reflection;
+using UnitTest.Misc;
namespace UnitTest.Components;
@@ -27,6 +28,20 @@ public class TabTest : BootstrapBlazorTestBase
});
});
Assert.Contains("Tab1-Content", cut.Markup);
+ Assert.Equal("TabItem-Key", cut.FindComponent().Instance.Key);
+ }
+
+ [Fact]
+ public void TabItemCreate_Ok()
+ {
+ TabItem.Create(new Dictionary()
+ {
+ ["Url"] = null
+ });
+ TabItem.Create(new Dictionary()
+ {
+ ["Url"] = new NullString()
+ });
}
[Fact]
@@ -34,7 +49,6 @@ public class TabTest : BootstrapBlazorTestBase
{
var cut = Context.RenderComponent(pb =>
{
- pb.Add(a => a.ShowExtendButtons, true);
pb.Add(a => a.Placement, Placement.Left);
pb.Add(a => a.Height, 100);
});
@@ -68,6 +82,9 @@ public class TabTest : BootstrapBlazorTestBase
var clicked = false;
var cut = Context.RenderComponent(pb =>
{
+ pb.Add(a => a.ShowExtendButtons, true);
+ pb.Add(a => a.Placement, Placement.Bottom);
+ pb.Add(a => a.ShowClose, true);
pb.Add(a => a.OnClickTab, item =>
{
clicked = true;
@@ -92,13 +109,56 @@ public class TabTest : BootstrapBlazorTestBase
cut.Find(".tabs-item").Click();
Assert.True(clicked);
- var tab = cut.Instance;
- cut.InvokeAsync(() => tab.ClickNextTab());
+ // Click Prev
+ var button = cut.Find(".nav-link-bar.left");
+ button.Click();
+ button.Click();
+ button.Click();
Assert.Equal("Tab1-Content", cut.Find(".tabs-body .d-none").InnerHtml);
- cut.InvokeAsync(() => tab.ClickPrevTab());
- cut.InvokeAsync(() => tab.CloseCurrentTab());
- cut.InvokeAsync(() => tab.CloseAllTabs());
+ // Click Next
+ button = cut.Find(".nav-link-bar.right");
+ button.Click();
+ button.Click();
+ button.Click();
+ Assert.Equal("Tab2-Content", cut.Find(".tabs-body .d-none").InnerHtml);
+
+ // Close
+ button = cut.Find(".tabs-item-close");
+ button.Click();
+ }
+
+ [Fact]
+ public void ClickTabToNavigation_True()
+ {
+ var cut = Context.RenderComponent(pb =>
+ {
+ pb.Add(a => a.AdditionalAssemblies, new Assembly[] { GetType().Assembly });
+ pb.Add(a => a.ShowExtendButtons, true);
+ pb.Add(a => a.Placement, Placement.Top);
+ pb.Add(a => a.ClickTabToNavigation, true);
+ pb.Add(a => a.ShowClose, true);
+ pb.Add(a => a.DefaultUrl, "/");
+ });
+ cut.InvokeAsync(() => cut.Instance.AddTab("/", "Index"));
+ cut.InvokeAsync(() => cut.Instance.AddTab("/Cat", null!));
+
+ // Click Prev
+ var button = cut.Find(".nav-link-bar.left");
+ button.Click();
+
+ // Click Next
+ button = cut.Find(".nav-link-bar.right");
+ button.Click();
+
+ button = cut.Find(".tabs-item-close");
+ button.Click();
+
+ // Close Current
+ cut.InvokeAsync(() => cut.Instance.CloseAllTabs());
+
+ button = cut.Find(".dropdown-item");
+ button.Click();
}
[Fact]
@@ -132,33 +192,144 @@ public class TabTest : BootstrapBlazorTestBase
public void AddTabByUrl_Ok()
{
var navMan = Context.Services.GetRequiredService();
+ navMan.NavigateTo("/");
var cut = Context.RenderComponent(pb =>
{
pb.Add(a => a.AdditionalAssemblies, new Assembly[] { GetType().Assembly });
pb.Add(a => a.ClickTabToNavigation, true);
});
+
+ navMan.NavigateTo("/");
+ cut.SetParametersAndRender(pb =>
+ {
+ pb.Add(a => a.ExcludeUrls, new String[] { "/" });
+ });
+
+ navMan.NavigateTo("/");
+ cut.SetParametersAndRender(pb =>
+ {
+ pb.Add(a => a.ExcludeUrls, new String[] { "" });
+ });
+
+ navMan.NavigateTo("/Cat");
+ cut.SetParametersAndRender(pb =>
+ {
+ pb.Add(a => a.ExcludeUrls, new String[] { "/", "Cat" });
+ });
+
navMan.NavigateTo("/");
+ cut.InvokeAsync(() => cut.Instance.AddTab(new Dictionary
+ {
+ ["Text"] = "Cat",
+ ["Url"] = "Cat"
+ }));
+ cut.SetParametersAndRender(pb =>
+ {
+ pb.Add(a => a.ExcludeUrls, new String[] { "/Test" });
+ });
+ cut.InvokeAsync(() => cut.Instance.CloseCurrentTab());
+
+ // AddTab
+ cut.InvokeAsync(() => cut.Instance.AddTab(new Dictionary
+ {
+ ["Text"] = "Cat",
+ ["Url"] = null,
+ ["IsActive"] = true
+ }));
+ cut.SetParametersAndRender(pb =>
+ {
+ pb.Add(a => a.ExcludeUrls, new String[] { "/Test" });
+ });
+
+ // Remove Tab
+ var item = cut.Instance.GetActiveTab();
+ Assert.NotNull(item);
+ cut.InvokeAsync(() => cut.Instance.RemoveTab(item!));
+ item = cut.Instance.GetActiveTab();
+ Assert.Null(item);
}
[Fact]
- public void ExcludeUrls_Ok()
+ public void DefaultUrl_Ok()
{
- var navMan = Context.Services.GetRequiredService();
var cut = Context.RenderComponent(pb =>
{
pb.Add(a => a.AdditionalAssemblies, new Assembly[] { GetType().Assembly });
- pb.Add(a => a.ClickTabToNavigation, true);
- pb.Add(a => a.ExcludeUrls, new String[] { "/Cat" });
+ pb.Add(a => a.DefaultUrl, "/");
});
- navMan.NavigateTo("/Cat");
+ var item = cut.Instance.GetActiveTab();
+ Assert.Contains("Index", cut.Markup);
}
[Fact]
- public void DefaultUrl_Ok()
+ public void IsOnlyRenderActiveTab_Ok()
{
var cut = Context.RenderComponent(pb =>
{
+ pb.Add(a => a.AdditionalAssemblies, new Assembly[] { GetType().Assembly });
+ pb.Add(a => a.IsOnlyRenderActiveTab, true);
+ pb.AddChildContent(pb =>
+ {
+ pb.Add(a => a.Text, "Tab1");
+ pb.Add(a => a.Url, "/Cat");
+ pb.Add(a => a.ChildContent, "Tab1-Content");
+ });
+ pb.AddChildContent(pb =>
+ {
+ pb.Add(a => a.Text, "Tab2");
+ pb.Add(a => a.Url, "/");
+ pb.Add(a => a.Closable, false);
+ pb.Add(a => a.ChildContent, "Tab2-Content");
+ });
+ });
+ Assert.Equal(1, cut.FindAll(".tabs-body-content").Count);
+
+ // 提高代码覆盖率
+ cut.InvokeAsync(() => cut.Instance.CloseOtherTabs());
+ }
+
+ [Fact]
+ public void ActiveTab_Ok()
+ {
+ var cut = Context.RenderComponent(pb =>
+ {
+ pb.Add(a => a.AdditionalAssemblies, new Assembly[] { GetType().Assembly });
pb.Add(a => a.DefaultUrl, "/");
});
+ cut.InvokeAsync(() => cut.Instance.ActiveTab(0));
+ var item = cut.Instance.GetActiveTab();
+ Assert.NotNull(item);
+ cut.InvokeAsync(() =>
+ {
+ if (item != null)
+ {
+ cut.Instance.ActiveTab(item);
+ }
+ });
+ cut.InvokeAsync(() => cut.Instance.RemoveTab(item!));
+ item = cut.Instance.GetActiveTab();
+ Assert.Null(item);
+ }
+
+ [Fact]
+ public void NavigationActiveTab_Ok()
+ {
+ var navMan = Context.Services.GetRequiredService();
+ navMan.NavigateTo("/");
+ var cut = Context.RenderComponent(pb =>
+ {
+ pb.Add(a => a.AdditionalAssemblies, new Assembly[] { GetType().Assembly });
+ pb.Add(a => a.ClickTabToNavigation, true);
+ pb.AddChildContent(pb =>
+ {
+ pb.Add(a => a.Text, "Tab1");
+ pb.Add(a => a.Url, "/Cat");
+ });
+ pb.AddChildContent(pb =>
+ {
+ pb.Add(a => a.Text, "Tab2");
+ pb.Add(a => a.Url, "/");
+ });
+ });
}
}
diff --git a/test/UnitTest/Misc/NullString.cs b/test/UnitTest/Misc/NullString.cs
new file mode 100644
index 0000000000000000000000000000000000000000..e78647570dc0a1a372d4d284c0ce4f7ea7086fbf
--- /dev/null
+++ b/test/UnitTest/Misc/NullString.cs
@@ -0,0 +1,10 @@
+// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+// Website: https://www.blazor.zone or https://argozhang.github.io/
+
+namespace UnitTest.Misc;
+
+internal class NullString
+{
+ public override string? ToString() => null;
+}
diff --git a/test/UnitTest/Pages/Cat.cs b/test/UnitTest/Pages/Cat.cs
index 2fd2d34428aceff57fa1707bbd9baaf45039f1e7..f28487d4640dc30ba7518f2583d880805325879e 100644
--- a/test/UnitTest/Pages/Cat.cs
+++ b/test/UnitTest/Pages/Cat.cs
@@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Components.Rendering;
namespace UnitTest.Pages;
[Route("/Cat")]
+[TabItemOption(Icon = "fa fa-fa", Closable = true, Text = "Cat")]
public class Cat : ComponentBase
{
protected override void BuildRenderTree(RenderTreeBuilder builder)