test(module: avatar): Add Avatar and AvatarGroup tests (#2648)

Small refactors:
  - Fix typo in Avatar
  - Remove code in Avatar that wasn't affecting visuals as expected
  - Make Avatar shape a constant. Update Skeleton component and demos to use it
  - Update Avatar component to calculate size after parameters set method due to it changing one of the properties it relies on.
This commit is contained in:
kooliokey 2022-08-30 08:03:35 -04:00 коммит произвёл GitHub
Родитель db291e9b7a
Коммит 538a479afc
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
11 изменённых файлов: 372 добавлений и 34 удалений

Просмотреть файл

@ -16,7 +16,7 @@ namespace AntDesign
set
{
_childContent = value;
_waitingCaclSize = true;
_waitingCalcSize = true;
}
}
@ -35,7 +35,7 @@ namespace AntDesign
if (_text != value)
{
_text = value;
_waitingCaclSize = true;
_waitingCalcSize = true;
}
}
}
@ -81,16 +81,13 @@ namespace AntDesign
private string _text;
private RenderFragment _childContent;
private bool _waitingCaclSize;
private bool _waitingCalcSize;
protected override void OnInitialized()
{
base.OnInitialized();
Group?.AddAvatar(this);
SetClassMap();
SetSizeStyle();
}
protected override void OnParametersSet()
@ -99,14 +96,17 @@ namespace AntDesign
_hasIcon = string.IsNullOrEmpty(Src) && !string.IsNullOrEmpty(Icon);
_hasSrc = !string.IsNullOrEmpty(Src);
SetClassMap();
SetSizeStyle();
base.OnParametersSet();
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender || _waitingCaclSize)
if (firstRender || _waitingCalcSize)
{
_waitingCaclSize = false;
_waitingCalcSize = false;
await CalcStringSize();
}
@ -128,7 +128,7 @@ namespace AntDesign
_hasText = true;
}
_waitingCaclSize = true;
_waitingCalcSize = true;
}
private void SetClassMap()
@ -149,7 +149,7 @@ namespace AntDesign
_sizeStyles = $"width:{size};height:{size};line-height:{size};";
if (_hasIcon)
{
_sizeStyles += $"font-size:calc(${size} / 2);";
_sizeStyles += $"font-size:calc({size} / 2);";
}
}
}
@ -163,12 +163,11 @@ namespace AntDesign
var childrenWidth = (await JsInvokeAsync<HtmlElement>(JSInteropConstants.GetDomInfo, TextEl))?.OffsetWidth ?? 0;
var avatarWidth = (await JsInvokeAsync<DomRect>(JSInteropConstants.GetBoundingClientRect, Ref))?.Width ?? 0;
var scale = childrenWidth != 0 && avatarWidth - 8 < childrenWidth ? (avatarWidth - 8) / childrenWidth : 1;
var scale = childrenWidth != 0 && avatarWidth - 8 < childrenWidth
? (avatarWidth - 8) / childrenWidth
: 1;
_textStyles = $"transform: scale({new CssSizeLength(scale, true)}) translateX(-50%);";
if (decimal.TryParse(Size, out var pxSize))
{
_textStyles += $"lineHeight:{(CssSizeLength)pxSize};";
}
StateHasChanged();
}

Просмотреть файл

@ -0,0 +1,8 @@
namespace AntDesign
{
public static class AvatarShape
{
public const string Square = "square";
public const string Circle = "circle";
}
}

Просмотреть файл

@ -115,7 +115,7 @@ namespace AntDesign
if (AvatarShape == null)
{
AvatarShape = SkeletonAvatarShape.Circle;
AvatarShape = AntDesign.AvatarShape.Circle;
}
if (AvatarSize.Value == null)

Просмотреть файл

@ -62,8 +62,8 @@ namespace AntDesign
private void SetAvatarMap()
{
_spanClassMapper.Clear().If("ant-skeleton-avatar", () => true)
.If("ant-skeleton-avatar-square", () => Shape == SkeletonAvatarShape.Square)
.If("ant-skeleton-avatar-circle", () => Shape == SkeletonAvatarShape.Circle)
.If("ant-skeleton-avatar-square", () => Shape == AvatarShape.Square)
.If("ant-skeleton-avatar-circle", () => Shape == AvatarShape.Circle)
.If("ant-skeleton-avatar-lg", () => Size.AsT1 == SkeletonElementSize.Large)
.If("ant-skeleton-avatar-sm", () => Size.AsT1 == SkeletonElementSize.Small);
}

Просмотреть файл

@ -12,12 +12,6 @@ namespace AntDesign
public const string Small = "small";
}
public static class SkeletonAvatarShape
{
public const string Square = "square";
public const string Circle = "circle";
}
public static class SkeletonButtonShape
{
public const string Default = "default";

Просмотреть файл

@ -1,12 +1,12 @@
<div>
<span class="avatar-item">
<Badge Count="1">
<Avatar Shape="square" Icon="user" />
<Avatar Shape="@AvatarShape.Square" Icon="user" />
</Badge>
</span>
<span>
<Badge Dot>
<Avatar Shape="square" Icon="user"/>
<Avatar Shape="@AvatarShape.Square" Icon="user" />
</Badge>
</span>
</div>

Просмотреть файл

@ -6,9 +6,9 @@
<Avatar Size="small" Icon="user" />
</div>
<div>
<Avatar Shape="square" Size="64" Icon="user" />
<Avatar Shape="square" Size="large" Icon="user" />
<Avatar Shape="square" Icon="user" />
<Avatar Shape="square" Size="small" Icon="user" />
<Avatar Shape="@AvatarShape.Square" Size="64" Icon="user" />
<Avatar Shape="@AvatarShape.Square" Size="large" Icon="user" />
<Avatar Shape="@AvatarShape.Square" Icon="user" />
<Avatar Shape="@AvatarShape.Square" Size="small" Icon="user" />
</div>
</div>

Просмотреть файл

@ -40,8 +40,8 @@
<Col Span="9">
AvatarShape:
<RadioGroup @bind-Value="@_avatarShape">
<Radio Value="@SkeletonAvatarShape.Circle">Circle</Radio>
<Radio Value="@SkeletonAvatarShape.Square">Square</Radio>
<Radio Value="@AvatarShape.Circle">Circle</Radio>
<Radio Value="@AvatarShape.Square">Square</Radio>
</RadioGroup>
</Col>
</Row>
@ -74,5 +74,5 @@
string _avatarSize = SkeletonElementSize.Default;
string _inputSize = SkeletonElementSize.Default;
string _buttonShape = SkeletonButtonShape.Default;
string _avatarShape = SkeletonAvatarShape.Circle;
string _avatarShape = AvatarShape.Circle;
}

Просмотреть файл

@ -17,7 +17,7 @@
for (var i = 0; i < 3; i++)
{
<SkeletonElement Style="margin-right: 8px;margin-bottom: 8px;" Type="avatar"
Active Size="SkeletonElementSize.Default" Shape="@SkeletonAvatarShape.Circle" />
Active Size="SkeletonElementSize.Default" Shape="@AvatarShape.Circle" />
}
}
</ul>

Просмотреть файл

@ -0,0 +1,135 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using AntDesign.JsInterop;
using Bunit;
using FluentAssertions;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.Extensions.Options;
using Xunit;
namespace AntDesign.Tests.Avatar
{
public class AvatarGroupTests : AntDesignTestBase
{
[Fact]
public void ItShouldRenderAvatarChildCntent()
{
JSInterop
.Setup<HtmlElement>("AntDesign.interop.domInfoHelper.getInfo", _ => true)
.SetResult(new HtmlElement());
JSInterop
.Setup<DomRect>("AntDesign.interop.domInfoHelper.getBoundingClientRect", _ => true)
.SetResult(new DomRect());
RenderFragment fragment = builder =>
{
builder.OpenComponent<AntDesign.Avatar>(0);
builder.AddAttribute(1, "Icon", "user");
builder.CloseComponent();
builder.OpenComponent<AntDesign.Avatar>(1);
builder.AddAttribute(1, "Text", "U");
builder.CloseComponent();
builder.OpenComponent<AntDesign.Avatar>(2);
builder.AddAttribute(1, "Text", "Username");
builder.CloseComponent();
};
var systemUnderTest = RenderComponent<AvatarGroup>(parameters => parameters
.Add(x => x.ChildContent, fragment));
systemUnderTest.FindAll(".ant-avatar").Count.Should().Be(3);
}
[Fact]
public void ItShouldRenderOverflowAvatarsProperly()
{
JSInterop
.Setup<HtmlElement>("AntDesign.interop.domInfoHelper.getInfo", _ => true)
.SetResult(new HtmlElement());
JSInterop
.Setup<DomRect>("AntDesign.interop.domInfoHelper.getBoundingClientRect", _ => true)
.SetResult(new DomRect());
RenderFragment fragment = builder =>
{
builder.OpenComponent<AntDesign.Avatar>(0);
builder.AddAttribute(1, "Icon", "user");
builder.CloseComponent();
builder.OpenComponent<AntDesign.Avatar>(1);
builder.AddAttribute(1, "Text", "U");
builder.CloseComponent();
builder.OpenComponent<AntDesign.Avatar>(2);
builder.AddAttribute(1, "Text", "Username");
builder.CloseComponent();
builder.OpenComponent<AntDesign.Avatar>(3);
builder.AddAttribute(1, "Text", "Username");
builder.CloseComponent();
};
var systemUnderTest = RenderComponent<AvatarGroup>(parameters => parameters
.Add(x => x.ChildContent, fragment)
.Add(x => x.MaxCount, 2));
systemUnderTest.MarkupMatches(@"<div class=""ant-avatar-group"">
<span class=""ant-avatar ant-avatar-icon"" style="" "" id:ignore>
<span role=""img"" class="" anticon anticon-user"" id:ignore>
<svg focusable=""false"" width=""1em"" height=""1em"" fill=""currentColor"" style=""pointer-events: none;"" xmlns=""http://www.w3.org/2000/svg"" class=""icon"" viewBox=""0 0 1024 1024"">
<path d=""M858.5 763.6a374 374 0 0 0-80.6-119.5 375.63 375.63 0 0 0-119.5-80.6c-.4-.2-.8-.3-1.2-.5C719.5 518 760 444.7 760 362c0-137-111-248-248-248S264 225 264 362c0 82.7 40.5 156 102.8 201.1-.4.2-.8.3-1.2.5-44.8 18.9-85 46-119.5 80.6a375.63 375.63 0 0 0-80.6 119.5A371.7 371.7 0 0 0 136 901.8a8 8 0 0 0 8 8.2h60c4.4 0 7.9-3.5 8-7.8 2-77.2 33-149.5 87.8-204.3 56.7-56.7 132-87.9 212.2-87.9s155.5 31.2 212.2 87.9C779 752.7 810 825 812 902.2c.1 4.4 3.6 7.8 8 7.8h60a8 8 0 0 0 8-8.2c-1-47.8-10.9-94.3-29.5-138.2zM512 534c-45.9 0-89.1-17.9-121.6-50.4S340 407.9 340 362c0-45.9 17.9-89.1 50.4-121.6S466.1 190 512 190s89.1 17.9 121.6 50.4S684 316.1 684 362c0 45.9-17.9 89.1-50.4 121.6S557.9 534 512 534z""></path>
</svg>
</span>
</span>
<span class=""ant-avatar"" style="" "" id:ignore>
<span class=""ant-avatar-string"" style=""transform: scale(1) translateX(-50%);"" >
U
</span>
</span>
<span class=""ant-avatar"" style="" "" id:ignore>
<span class=""ant-avatar-string"" style=""transform: scale(1) translateX(-50%);"" >
+2
</span>
</span>
</div>");
}
[Fact]
public void ItShouldWrapChildContentInDiv()
{
JSInterop
.Setup<HtmlElement>("AntDesign.interop.domInfoHelper.getInfo", _ => true)
.SetResult(new HtmlElement());
JSInterop
.Setup<DomRect>("AntDesign.interop.domInfoHelper.getBoundingClientRect", _ => true)
.SetResult(new DomRect());
RenderFragment fragment = builder =>
{
builder.OpenComponent<AntDesign.Avatar>(0);
builder.AddAttribute(1, "Icon", "user");
builder.CloseComponent();
builder.OpenComponent<AntDesign.Avatar>(1);
builder.AddAttribute(1, "Text", "U");
builder.CloseComponent();
builder.OpenComponent<AntDesign.Avatar>(3);
builder.AddAttribute(1, "Text", "Username");
builder.CloseComponent();
};
var systemUnderTest = RenderComponent<AvatarGroup>(parameters => parameters
.Add(x => x.ChildContent, fragment));
systemUnderTest.MarkupMatches("<div class=\"ant-avatar-group\" diff:ignoreChildren></div>");
}
}
}

Просмотреть файл

@ -0,0 +1,202 @@
using System;
using System.Threading.Tasks;
using AntDesign.JsInterop;
using Bunit;
using FluentAssertions;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using Xunit;
namespace AntDesign.Tests.Avatar
{
public class AvatarTests : AntDesignTestBase
{
[Theory]
[InlineData(AntSizeLDSType.Small, "ant-avatar-sm", "")]
[InlineData(AntSizeLDSType.Large, "ant-avatar-lg", "")]
[InlineData("64.5", "", "width:64.5px;height:64.5px;line-height:64.5px;font-size:calc(64.5px / 2);")]
public void ItShouldSetSizeProperly(string size, string expectedClass, string expectedStyle)
{
var systemUnderTest = RenderComponent<AntDesign.Avatar>(parameters => parameters
.Add(x => x.Size, size)
.Add(x => x.Icon, "user"));
systemUnderTest.MarkupMatches(@$"<span class=""ant-avatar ant-avatar-icon {expectedClass}"" style=""{expectedStyle}"" id:ignore>
<span role=""img"" class="" anticon anticon-user"" id:ignore>
<svg focusable=""false"" width=""1em"" height=""1em"" fill=""currentColor"" style=""pointer-events: none;"" xmlns=""http://www.w3.org/2000/svg"" class=""icon"" viewBox=""0 0 1024 1024"">
<path d=""M858.5 763.6a374 374 0 0 0-80.6-119.5 375.63 375.63 0 0 0-119.5-80.6c-.4-.2-.8-.3-1.2-.5C719.5 518 760 444.7 760 362c0-137-111-248-248-248S264 225 264 362c0 82.7 40.5 156 102.8 201.1-.4.2-.8.3-1.2.5-44.8 18.9-85 46-119.5 80.6a375.63 375.63 0 0 0-80.6 119.5A371.7 371.7 0 0 0 136 901.8a8 8 0 0 0 8 8.2h60c4.4 0 7.9-3.5 8-7.8 2-77.2 33-149.5 87.8-204.3 56.7-56.7 132-87.9 212.2-87.9s155.5 31.2 212.2 87.9C779 752.7 810 825 812 902.2c.1 4.4 3.6 7.8 8 7.8h60a8 8 0 0 0 8-8.2c-1-47.8-10.9-94.3-29.5-138.2zM512 534c-45.9 0-89.1-17.9-121.6-50.4S340 407.9 340 362c0-45.9 17.9-89.1 50.4-121.6S466.1 190 512 190s89.1 17.9 121.6 50.4S684 316.1 684 362c0 45.9-17.9 89.1-50.4 121.6S557.9 534 512 534z"">
</path>
</svg>
</span>
</span>");
}
[Theory]
[InlineData(AvatarShape.Square, "ant-avatar-square")]
[InlineData(AvatarShape.Circle, "ant-avatar-circle")]
[InlineData(null, "")]
public void ItShouldProperlyStyleShapes(string shape, string expectedClass)
{
var systemUnderTest = RenderComponent<AntDesign.Avatar>(parameters => parameters
.Add(x => x.Shape, shape)
.Add(x => x.Icon, "user"));
systemUnderTest.MarkupMatches(@$"<span class=""ant-avatar ant-avatar-icon {expectedClass}"" style="""" id:ignore>
<span role=""img"" class="" anticon anticon-user"" id:ignore>
<svg focusable=""false"" width=""1em"" height=""1em"" fill=""currentColor"" style=""pointer-events: none;"" xmlns=""http://www.w3.org/2000/svg"" class=""icon"" viewBox=""0 0 1024 1024"">
<path d=""M858.5 763.6a374 374 0 0 0-80.6-119.5 375.63 375.63 0 0 0-119.5-80.6c-.4-.2-.8-.3-1.2-.5C719.5 518 760 444.7 760 362c0-137-111-248-248-248S264 225 264 362c0 82.7 40.5 156 102.8 201.1-.4.2-.8.3-1.2.5-44.8 18.9-85 46-119.5 80.6a375.63 375.63 0 0 0-80.6 119.5A371.7 371.7 0 0 0 136 901.8a8 8 0 0 0 8 8.2h60c4.4 0 7.9-3.5 8-7.8 2-77.2 33-149.5 87.8-204.3 56.7-56.7 132-87.9 212.2-87.9s155.5 31.2 212.2 87.9C779 752.7 810 825 812 902.2c.1 4.4 3.6 7.8 8 7.8h60a8 8 0 0 0 8-8.2c-1-47.8-10.9-94.3-29.5-138.2zM512 534c-45.9 0-89.1-17.9-121.6-50.4S340 407.9 340 362c0-45.9 17.9-89.1 50.4-121.6S466.1 190 512 190s89.1 17.9 121.6 50.4S684 316.1 684 362c0 45.9-17.9 89.1-50.4 121.6S557.9 534 512 534z"">
</path>
</svg>
</span>
</span>");
}
[Theory]
[InlineData(200, 64, 0.28)]
[InlineData(56, 64, 1)]
[InlineData(10, 64, 1)]
[InlineData(57, 64, 0.9824561403508771929824561404)]
public void ItShouldRenderAndScaleTextProperly(int textWidth, decimal avatarWidth, double expectedScale)
{
JSInterop
.Setup<HtmlElement>("AntDesign.interop.domInfoHelper.getInfo", _ => true)
.SetResult(new HtmlElement
{
OffsetWidth = textWidth
});
JSInterop
.Setup<DomRect>("AntDesign.interop.domInfoHelper.getBoundingClientRect", _ => true)
.SetResult(new DomRect
{
Width = avatarWidth
});
var systemUnderTest = RenderComponent<AntDesign.Avatar>(parameters => parameters
.Add(x => x.Text, "KR"));
systemUnderTest.MarkupMatches(@$"<span class=""ant-avatar"" style="""" id:ignore>
<span class=""ant-avatar-string"" style=""transform: scale({expectedScale}) translateX(-50%);"">KR</span>
</span>");
}
[Fact]
public void ItShouldRenderAndScaleTextProperlyWhenUnableToGetJsInfo()
{
JSInterop
.Setup<HtmlElement>("AntDesign.interop.domInfoHelper.getInfo", _ => true)
.SetResult(null);
JSInterop
.Setup<DomRect>("AntDesign.interop.domInfoHelper.getBoundingClientRect", _ => true)
.SetResult(null);
var systemUnderTest = RenderComponent<AntDesign.Avatar>(parameters => parameters
.Add(x => x.Text, "KR"));
systemUnderTest.MarkupMatches(@$"<span class=""ant-avatar"" style="""" id:ignore>
<span class=""ant-avatar-string"" style=""transform: scale(1) translateX(-50%);"">KR</span>
</span>");
}
[Theory]
[InlineData(200, 64, 0.28)]
[InlineData(56, 64, 1)]
[InlineData(10, 64, 1)]
[InlineData(57, 64, 0.9824561403508771929824561404)]
public void ItShouldRenderAndScaleChildContentProperly(int textWidth, decimal avatarWidth, double expectedScale)
{
JSInterop
.Setup<HtmlElement>("AntDesign.interop.domInfoHelper.getInfo", _ => true)
.SetResult(new HtmlElement
{
OffsetWidth = textWidth
});
JSInterop
.Setup<DomRect>("AntDesign.interop.domInfoHelper.getBoundingClientRect", _ => true)
.SetResult(new DomRect
{
Width = avatarWidth
});
RenderFragment fragment = builder =>
{
builder.OpenElement(0, "span");
builder.AddContent(0, "Text");
builder.CloseElement();
};
var systemUnderTest = RenderComponent<AntDesign.Avatar>(parameters => parameters
.Add(x => x.ChildContent, fragment));
systemUnderTest.MarkupMatches(@$"<span class=""ant-avatar"" style="""" id:ignore>
<span class=""ant-avatar-string"" style=""transform: scale({expectedScale}) translateX(-50%);"">
<span>Text</span>
</span>
</span>");
}
[Fact]
public void ItShouldCallOnErrorWhenImageLoadErrors()
{
var calledOnError = false;
var systemUnderTest = RenderComponent<AntDesign.Avatar>(parameters => parameters
.Add(x => x.Size, AntSizeLDSType.Default)
.Add(x => x.Src, "InvalidImage")
.Add(x => x.OnError, () => calledOnError = true)
.Add(x => x.Icon, "user"));
systemUnderTest.Find("img").TriggerEvent("onerror", new ErrorEventArgs());
systemUnderTest.WaitForAssertion(() => calledOnError.Should().BeTrue());
}
[Fact]
public void ItShouldRenderIconWhenImageErrorsAndGivenIcon()
{
var systemUnderTest = RenderComponent<AntDesign.Avatar>(parameters => parameters
.Add(x => x.Size, AntSizeLDSType.Default)
.Add(x => x.Src, "InvalidImage")
.Add(x => x.Icon, "user")
.Add(x => x.Text, "Won't be used"));
systemUnderTest.Find("img").TriggerEvent("onerror", new ErrorEventArgs());
systemUnderTest.WaitForAssertion(() => systemUnderTest.MarkupMatches(@$"<span class=""ant-avatar ant-avatar-icon"" style="""" id:ignore>
<span role=""img"" class="" anticon anticon-user"" id:ignore>
<svg focusable=""false"" width=""1em"" height=""1em"" fill=""currentColor"" style=""pointer-events: none;"" xmlns=""http://www.w3.org/2000/svg"" class=""icon"" viewBox=""0 0 1024 1024"">
<path d=""M858.5 763.6a374 374 0 0 0-80.6-119.5 375.63 375.63 0 0 0-119.5-80.6c-.4-.2-.8-.3-1.2-.5C719.5 518 760 444.7 760 362c0-137-111-248-248-248S264 225 264 362c0 82.7 40.5 156 102.8 201.1-.4.2-.8.3-1.2.5-44.8 18.9-85 46-119.5 80.6a375.63 375.63 0 0 0-80.6 119.5A371.7 371.7 0 0 0 136 901.8a8 8 0 0 0 8 8.2h60c4.4 0 7.9-3.5 8-7.8 2-77.2 33-149.5 87.8-204.3 56.7-56.7 132-87.9 212.2-87.9s155.5 31.2 212.2 87.9C779 752.7 810 825 812 902.2c.1 4.4 3.6 7.8 8 7.8h60a8 8 0 0 0 8-8.2c-1-47.8-10.9-94.3-29.5-138.2zM512 534c-45.9 0-89.1-17.9-121.6-50.4S340 407.9 340 362c0-45.9 17.9-89.1 50.4-121.6S466.1 190 512 190s89.1 17.9 121.6 50.4S684 316.1 684 362c0 45.9-17.9 89.1-50.4 121.6S557.9 534 512 534z"">
</path>
</svg>
</span>
</span>"));
}
[Fact]
public void ItShouldRenderTextWhenImageErrorsAndGivenTextButNotIcon()
{
JSInterop
.Setup<HtmlElement>("AntDesign.interop.domInfoHelper.getInfo", _ => true)
.SetResult(new HtmlElement());
JSInterop
.Setup<DomRect>("AntDesign.interop.domInfoHelper.getBoundingClientRect", _ => true)
.SetResult(new DomRect());
var systemUnderTest = RenderComponent<AntDesign.Avatar>(parameters => parameters
.Add(x => x.Size, AntSizeLDSType.Default)
.Add(x => x.Src, "InvalidImage")
.Add(x => x.Text, "USER"));
systemUnderTest.Find("img").TriggerEvent("onerror", new ErrorEventArgs());
systemUnderTest.WaitForAssertion(() => systemUnderTest.Find(".ant-avatar-string")
.MarkupMatches(@$"<span class=""ant-avatar-string"" style:ignore>
USER
</span>"));
}
}
}