!2389 test(#I4SMH1): add TabLink unit test

* test: 恢复默认导航
* test: 提高代码覆盖率
* refactor: 重构 Tab 代码提高代码覆盖率
* test: 增加 ActiveTab 单元测试
* refactor: 增加代码覆盖率
* test: 增加代码覆盖率
* refactor: 重构逻辑
* test: 增加 Key 单元测试
* test: TabItem 代码覆盖率 100%
* test: 增加 ChildContent 单元测试
* test: 增加 Click 单元测试
* test: 增加 TabLink 单元测试
This commit is contained in:
Argo 2022-02-05 16:09:18 +00:00
parent bfb2fc1d77
commit 1073ac8c7f
9 changed files with 271 additions and 86 deletions

View File

@ -170,13 +170,6 @@ public partial class Tab
[NotNull]
public string? CloseCurrentTabText { get; set; }
/// <summary>
/// 获得/设置 空白 Tab 显示文字
/// </summary>
[Parameter]
[NotNull]
public string? NullTabText { get; set; }
/// <summary>
/// 获得/设置 关闭所有 TabItem 菜单文本
/// </summary>
@ -256,44 +249,24 @@ public partial class Tab
}
}
private bool CheckUrl(string url)
{
var ret = false;
foreach (var rule in ExcludeUrls ?? Enumerable.Empty<string>())
{
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<string>();
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,31 +496,18 @@ 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));
parameters.Add(nameof(TabItem.Text), text);
}
}
private string GetTabText(string? text, string[]? segments)
{
if (NullTabText == null)
{
var t = Localizer[nameof(NullTabText)];
if (!t.ResourceNotFound)
{
NullTabText = t.Value;
}
}
return text ?? NullTabText ?? segments?.FirstOrDefault() ?? "";
}
/// <summary>
/// 添加 TabItem 方法
/// </summary>
@ -596,7 +556,7 @@ public partial class Tab
{
if (ClickTabToNavigation)
{
Navigator.NavigateTo(activeItem.Url!);
Navigator.NavigateTo(activeItem.Url);
}
else
{

View File

@ -21,6 +21,7 @@ public class TabItem : ComponentBase
/// 获得/设置 请求地址
/// </summary>
[Parameter]
[NotNull]
public string? Url { get; set; }
/// <summary>
@ -72,6 +73,7 @@ public class TabItem : ComponentBase
{
base.OnInitialized();
Url ??= "";
TabSet?.AddItem(this);
}

View File

@ -21,13 +21,12 @@ public static class NavigationManagerExtensions
/// <param name="text"></param>
/// <param name="icon"></param>
/// <param name="closable"></param>
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<TabItemTextOptions>();
option.Text = text;
option.Icon = icon;
option.IsActive = true;
option.Closable = closable ?? true;
option.Closable = closable;
navigation.NavigateTo(url);
}
}

View File

@ -20,29 +20,29 @@ internal class TabItemTextOptions
public string? Icon { get; set; }
/// <summary>
/// 获得/设置 是否激活 默认为 null
/// 获得/设置 是否激活 默认为 true
/// </summary>
/// <value></value>
public bool? IsActive { get; set; }
public bool IsActive { get; set; } = true;
/// <summary>
/// 获得/设置 当前 TabItem 是否可关闭 默认为 null
/// 获得/设置 当前 TabItem 是否可关闭 默认为 true
/// </summary>
public bool? Closable { get; set; }
public bool Closable { get; set; } = true;
/// <summary>
///
/// 重置方法
/// </summary>
public void Reset()
{
Text = null;
Icon = null;
IsActive = null;
Closable = null;
IsActive = true;
Closable = true;
}
/// <summary>
///
/// 是否可用方法
/// </summary>
/// <returns></returns>
public bool Valid() => Text != null;

View File

@ -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]

View File

@ -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<TabLink>(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<TabLink>(pb =>
{
pb.Add(a => a.ChildContent, builder => builder.AddContent(0, "Body"));
});
Assert.Contains("Body", cut.Markup);
}
}

View File

@ -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<TabItem>().Instance.Key);
}
[Fact]
public void TabItemCreate_Ok()
{
TabItem.Create(new Dictionary<string, object?>()
{
["Url"] = null
});
TabItem.Create(new Dictionary<string, object?>()
{
["Url"] = new NullString()
});
}
[Fact]
@ -34,7 +49,6 @@ public class TabTest : BootstrapBlazorTestBase
{
var cut = Context.RenderComponent<Tab>(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<Tab>(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<Tab>(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,25 +192,61 @@ public class TabTest : BootstrapBlazorTestBase
public void AddTabByUrl_Ok()
{
var navMan = Context.Services.GetRequiredService<FakeNavigationManager>();
var cut = Context.RenderComponent<Tab>(pb =>
{
pb.Add(a => a.AdditionalAssemblies, new Assembly[] { GetType().Assembly });
pb.Add(a => a.ClickTabToNavigation, true);
});
navMan.NavigateTo("/");
}
[Fact]
public void ExcludeUrls_Ok()
{
var navMan = Context.Services.GetRequiredService<FakeNavigationManager>();
var cut = Context.RenderComponent<Tab>(pb =>
{
pb.Add(a => a.AdditionalAssemblies, new Assembly[] { GetType().Assembly });
pb.Add(a => a.ClickTabToNavigation, true);
pb.Add(a => a.ExcludeUrls, new String[] { "/Cat" });
});
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<string, object?>
{
["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<string, object?>
{
["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]
@ -158,7 +254,82 @@ public class TabTest : BootstrapBlazorTestBase
{
var cut = Context.RenderComponent<Tab>(pb =>
{
pb.Add(a => a.AdditionalAssemblies, new Assembly[] { GetType().Assembly });
pb.Add(a => a.DefaultUrl, "/");
});
var item = cut.Instance.GetActiveTab();
Assert.Contains("Index", cut.Markup);
}
[Fact]
public void IsOnlyRenderActiveTab_Ok()
{
var cut = Context.RenderComponent<Tab>(pb =>
{
pb.Add(a => a.AdditionalAssemblies, new Assembly[] { GetType().Assembly });
pb.Add(a => a.IsOnlyRenderActiveTab, true);
pb.AddChildContent<TabItem>(pb =>
{
pb.Add(a => a.Text, "Tab1");
pb.Add(a => a.Url, "/Cat");
pb.Add(a => a.ChildContent, "Tab1-Content");
});
pb.AddChildContent<TabItem>(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<Tab>(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<FakeNavigationManager>();
navMan.NavigateTo("/");
var cut = Context.RenderComponent<Tab>(pb =>
{
pb.Add(a => a.AdditionalAssemblies, new Assembly[] { GetType().Assembly });
pb.Add(a => a.ClickTabToNavigation, true);
pb.AddChildContent<TabItem>(pb =>
{
pb.Add(a => a.Text, "Tab1");
pb.Add(a => a.Url, "/Cat");
});
pb.AddChildContent<TabItem>(pb =>
{
pb.Add(a => a.Text, "Tab2");
pb.Add(a => a.Url, "/");
});
});
}
}

View File

@ -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;
}

View File

@ -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)