!2448 test(#I4UMS2): add CardUpload unit test

* test: 增加 File 检查单元测试
* refactor: 删除冗余代码 ValidateFile 导致触发两次客户端验证
* doc: 格式化代码
* fix: 修复单文件样式
* fix: 修复验证两次问题
* test: 增加 ValidateForm 单元测试
* refactor: 重构代码
* doc: 格式化文档
* test: 增加 Value 单元测试
* refactor: 更新代码逻辑
* refactor: 更新代码逻辑
* refactor: 移除 OnBrowser
* chore: 格式化代码
* test: 增加 Error 属性单元测试
* test: 增加 Accept 单元测试
* test: 增加 Reset 单元测试
* refactor: 格式化代码
* refactor: 精简代码
* test: 增加 CardUpload 在表单内的单元测试
* test: 增加 CardUpload 单元测试
* refactor: 重构 IsImage 方法修复 bug
This commit is contained in:
Argo 2022-02-21 13:24:06 +00:00
parent b4c6e79fbf
commit 0c5fc4c1a7
13 changed files with 488 additions and 311 deletions

View File

@ -37,18 +37,18 @@ public sealed partial class Uploads : IDisposable
private BlockLogger? Trace { get; set; }
private List<UploadFile> DefaultFormatFileList { get; } = new List<UploadFile>()
{
new UploadFile { FileName = "Test.xls" },
new UploadFile { FileName = "Test.doc" },
new UploadFile { FileName = "Test.ppt" },
new UploadFile { FileName = "Test.mp3" },
new UploadFile { FileName = "Test.mp4" },
new UploadFile { FileName = "Test.pdf" },
new UploadFile { FileName = "Test.cs" },
new UploadFile { FileName = "Test.zip" },
new UploadFile { FileName = "Test.txt" },
new UploadFile { FileName = "Test.dat" }
};
{
new UploadFile { FileName = "Test.xls" },
new UploadFile { FileName = "Test.doc" },
new UploadFile { FileName = "Test.ppt" },
new UploadFile { FileName = "Test.mp3" },
new UploadFile { FileName = "Test.mp4" },
new UploadFile { FileName = "Test.pdf" },
new UploadFile { FileName = "Test.cs" },
new UploadFile { FileName = "Test.zip" },
new UploadFile { FileName = "Test.txt" },
new UploadFile { FileName = "Test.dat" }
};
private Task OnFileChange(UploadFile file)
{
@ -216,245 +216,245 @@ public sealed partial class Uploads : IDisposable
/// <returns></returns>
private IEnumerable<AttributeItem> GetInputAttributes() => new AttributeItem[]
{
new AttributeItem() {
Name = "ShowDeleteButton",
Description = Localizer["ShowDeleteButton"],
Type = "bool",
ValueList = "true|false",
DefaultValue = "false"
},
new AttributeItem() {
Name = "IsDisabled",
Description = Localizer["IsDisabled"],
Type = "boolean",
ValueList = "true / false",
DefaultValue = "false"
},
new AttributeItem() {
Name = "PlaceHolder",
Description = Localizer["PlaceHolder"],
Type = "string",
ValueList = " — ",
DefaultValue = " — "
},
new AttributeItem() {
Name = "Accept",
Description = Localizer["Accept"],
Type = "string",
ValueList = " — ",
DefaultValue = " — "
},
new AttributeItem() {
Name = "BrowserButtonClass",
Description = Localizer["BrowserButtonClass"],
Type = "string",
ValueList = " — ",
DefaultValue = "btn-primary"
},
new AttributeItem() {
Name = "BrowserButtonIcon",
Description = Localizer["BrowserButtonIcon"],
Type = "string",
ValueList = " — ",
DefaultValue = "fa fa-folder-open-o"
},
new AttributeItem() {
Name = "BrowserButtonText",
Description = Localizer["BrowserButtonText"],
Type = "string",
ValueList = " — ",
DefaultValue = ""
},
new AttributeItem() {
Name = "DeleteButtonClass",
Description = Localizer["DeleteButtonClass"],
Type = "string",
ValueList = " — ",
DefaultValue = "btn-danger"
},
new AttributeItem() {
Name = "DeleteButtonIcon",
Description = Localizer["DeleteButtonIcon"],
Type = "string",
ValueList = " — ",
DefaultValue = "fa fa-trash-o"
},
new AttributeItem() {
Name = "DeleteButtonText",
Description = Localizer["DeleteButtonText"],
Type = "string",
ValueList = " — ",
DefaultValue = Localizer["DeleteButtonTextDefaultValue"]
},
new AttributeItem() {
Name = "OnDelete",
Description = Localizer["OnDelete"],
Type = "Func<string, Task<bool>>",
ValueList = " — ",
DefaultValue = " — "
},
new AttributeItem() {
Name = "OnChange",
Description = Localizer["OnChange"],
Type = "Func<UploadFile, Task>",
ValueList = " — ",
DefaultValue = " — "
},
new AttributeItem() {
Name = "ShowDeleteButton",
Description = Localizer["ShowDeleteButton"],
Type = "bool",
ValueList = "true|false",
DefaultValue = "false"
},
new AttributeItem() {
Name = "IsDisabled",
Description = Localizer["IsDisabled"],
Type = "boolean",
ValueList = "true / false",
DefaultValue = "false"
},
new AttributeItem() {
Name = "PlaceHolder",
Description = Localizer["PlaceHolder"],
Type = "string",
ValueList = " — ",
DefaultValue = " — "
},
new AttributeItem() {
Name = "Accept",
Description = Localizer["Accept"],
Type = "string",
ValueList = " — ",
DefaultValue = " — "
},
new AttributeItem() {
Name = "BrowserButtonClass",
Description = Localizer["BrowserButtonClass"],
Type = "string",
ValueList = " — ",
DefaultValue = "btn-primary"
},
new AttributeItem() {
Name = "BrowserButtonIcon",
Description = Localizer["BrowserButtonIcon"],
Type = "string",
ValueList = " — ",
DefaultValue = "fa fa-folder-open-o"
},
new AttributeItem() {
Name = "BrowserButtonText",
Description = Localizer["BrowserButtonText"],
Type = "string",
ValueList = " — ",
DefaultValue = ""
},
new AttributeItem() {
Name = "DeleteButtonClass",
Description = Localizer["DeleteButtonClass"],
Type = "string",
ValueList = " — ",
DefaultValue = "btn-danger"
},
new AttributeItem() {
Name = "DeleteButtonIcon",
Description = Localizer["DeleteButtonIcon"],
Type = "string",
ValueList = " — ",
DefaultValue = "fa fa-trash-o"
},
new AttributeItem() {
Name = "DeleteButtonText",
Description = Localizer["DeleteButtonText"],
Type = "string",
ValueList = " — ",
DefaultValue = Localizer["DeleteButtonTextDefaultValue"]
},
new AttributeItem() {
Name = "OnDelete",
Description = Localizer["OnDelete"],
Type = "Func<string, Task<bool>>",
ValueList = " — ",
DefaultValue = " — "
},
new AttributeItem() {
Name = "OnChange",
Description = Localizer["OnChange"],
Type = "Func<UploadFile, Task>",
ValueList = " — ",
DefaultValue = " — "
}
};
private IEnumerable<AttributeItem> GetButtonAttributes() => new AttributeItem[]
{
new AttributeItem() {
Name = "IsDirectory",
Description = Localizer["IsDirectory"],
Type = "bool",
ValueList = "true|false",
DefaultValue = "false"
},
new AttributeItem() {
Name = "IsMultiple",
Description = Localizer["IsMultiple"],
Type = "bool",
ValueList = "true|false",
DefaultValue = "false"
},
new AttributeItem() {
Name = "IsSingle",
Description = Localizer["IsSingle"],
Type = "bool",
ValueList = "true|false",
DefaultValue = "false"
},
new AttributeItem() {
Name = "ShowProgress",
Description = Localizer["ShowProgress"],
Type = "bool",
ValueList = "true|false",
DefaultValue = "false"
},
new AttributeItem() {
Name = "Accept",
Description = Localizer["Accept"],
Type = "string",
ValueList = " — ",
DefaultValue = " — "
},
new AttributeItem() {
Name = "BrowserButtonClass",
Description = Localizer["BrowserButtonClass"],
Type = "string",
ValueList = " — ",
DefaultValue = "btn-primary"
},
new AttributeItem() {
Name = "BrowserButtonIcon",
Description = Localizer["BrowserButtonIcon"],
Type = "string",
ValueList = " — ",
DefaultValue = "fa fa-folder-open-o"
},
new AttributeItem() {
Name = "BrowserButtonText",
Description = Localizer["BrowserButtonText"],
Type = "string",
ValueList = " — ",
DefaultValue = ""
},
new AttributeItem() {
Name = "DefaultFileList",
Description = Localizer["DefaultFileList"],
Type = "List<UploadFile>",
ValueList = " — ",
DefaultValue = " — "
},
new AttributeItem() {
Name = "OnGetFileFormat",
Description = Localizer["OnGetFileFormat"],
Type = "Func<string, string>",
ValueList = " — ",
DefaultValue = " — "
},
new AttributeItem() {
Name = "OnDelete",
Description = Localizer["OnDelete"],
Type = "Func<string, Task<bool>>",
ValueList = " — ",
DefaultValue = " — "
},
new AttributeItem() {
Name = "OnChange",
Description = Localizer["OnChange"],
Type = "Func<UploadFile, Task>",
ValueList = " — ",
DefaultValue = " - "
}
new AttributeItem() {
Name = "IsDirectory",
Description = Localizer["IsDirectory"],
Type = "bool",
ValueList = "true|false",
DefaultValue = "false"
},
new AttributeItem() {
Name = "IsMultiple",
Description = Localizer["IsMultiple"],
Type = "bool",
ValueList = "true|false",
DefaultValue = "false"
},
new AttributeItem() {
Name = "IsSingle",
Description = Localizer["IsSingle"],
Type = "bool",
ValueList = "true|false",
DefaultValue = "false"
},
new AttributeItem() {
Name = "ShowProgress",
Description = Localizer["ShowProgress"],
Type = "bool",
ValueList = "true|false",
DefaultValue = "false"
},
new AttributeItem() {
Name = "Accept",
Description = Localizer["Accept"],
Type = "string",
ValueList = " — ",
DefaultValue = " — "
},
new AttributeItem() {
Name = "BrowserButtonClass",
Description = Localizer["BrowserButtonClass"],
Type = "string",
ValueList = " — ",
DefaultValue = "btn-primary"
},
new AttributeItem() {
Name = "BrowserButtonIcon",
Description = Localizer["BrowserButtonIcon"],
Type = "string",
ValueList = " — ",
DefaultValue = "fa fa-folder-open-o"
},
new AttributeItem() {
Name = "BrowserButtonText",
Description = Localizer["BrowserButtonText"],
Type = "string",
ValueList = " — ",
DefaultValue = ""
},
new AttributeItem() {
Name = "DefaultFileList",
Description = Localizer["DefaultFileList"],
Type = "List<UploadFile>",
ValueList = " — ",
DefaultValue = " — "
},
new AttributeItem() {
Name = "OnGetFileFormat",
Description = Localizer["OnGetFileFormat"],
Type = "Func<string, string>",
ValueList = " — ",
DefaultValue = " — "
},
new AttributeItem() {
Name = "OnDelete",
Description = Localizer["OnDelete"],
Type = "Func<string, Task<bool>>",
ValueList = " — ",
DefaultValue = " — "
},
new AttributeItem() {
Name = "OnChange",
Description = Localizer["OnChange"],
Type = "Func<UploadFile, Task>",
ValueList = " — ",
DefaultValue = " - "
}
};
private IEnumerable<AttributeItem> GetAvatarAttributes() => new AttributeItem[]
{
new AttributeItem() {
Name = "Width",
Description = Localizer["Width"],
Type = "int",
ValueList = " — ",
DefaultValue = "0"
},
new AttributeItem() {
Name = "Height",
Description = Localizer["Height"],
Type = "int",
ValueList = "—",
DefaultValue = "0"
},
new AttributeItem() {
Name = "IsCircle",
Description = Localizer["IsCircle"],
Type = "bool",
ValueList = "true|false",
DefaultValue = "false"
},
new AttributeItem() {
Name = "IsSingle",
Description = Localizer["IsSingle"],
Type = "bool",
ValueList = "true|false",
DefaultValue = "false"
},
new AttributeItem() {
Name = "ShowProgress",
Description = Localizer["ShowProgress"],
Type = "bool",
ValueList = "true|false",
DefaultValue = "false"
},
new AttributeItem() {
Name = "Accept",
Description = Localizer["Accept"],
Type = "string",
ValueList = " — ",
DefaultValue = " — "
},
new AttributeItem() {
Name = "DefaultFileList",
Description = Localizer["DefaultFileList"],
Type = "List<UploadFile>",
ValueList = " — ",
DefaultValue = " — "
},
new AttributeItem() {
Name = "OnDelete",
Description = Localizer["OnDelete"],
Type = "Func<string, Task<bool>>",
ValueList = " — ",
DefaultValue = " — "
},
new AttributeItem() {
Name = "OnChange",
Description = Localizer["OnChange"],
Type = "Func<UploadFile, Task>",
ValueList = " — ",
DefaultValue = " — "
},
new AttributeItem() {
Name = "Width",
Description = Localizer["Width"],
Type = "int",
ValueList = " — ",
DefaultValue = "0"
},
new AttributeItem() {
Name = "Height",
Description = Localizer["Height"],
Type = "int",
ValueList = "—",
DefaultValue = "0"
},
new AttributeItem() {
Name = "IsCircle",
Description = Localizer["IsCircle"],
Type = "bool",
ValueList = "true|false",
DefaultValue = "false"
},
new AttributeItem() {
Name = "IsSingle",
Description = Localizer["IsSingle"],
Type = "bool",
ValueList = "true|false",
DefaultValue = "false"
},
new AttributeItem() {
Name = "ShowProgress",
Description = Localizer["ShowProgress"],
Type = "bool",
ValueList = "true|false",
DefaultValue = "false"
},
new AttributeItem() {
Name = "Accept",
Description = Localizer["Accept"],
Type = "string",
ValueList = " — ",
DefaultValue = " — "
},
new AttributeItem() {
Name = "DefaultFileList",
Description = Localizer["DefaultFileList"],
Type = "List<UploadFile>",
ValueList = " — ",
DefaultValue = " — "
},
new AttributeItem() {
Name = "OnDelete",
Description = Localizer["OnDelete"],
Type = "Func<string, Task<bool>>",
ValueList = " — ",
DefaultValue = " — "
},
new AttributeItem() {
Name = "OnChange",
Description = Localizer["OnChange"],
Type = "Func<UploadFile, Task>",
ValueList = " — ",
DefaultValue = " — "
}
};
/// <summary>

View File

@ -30,6 +30,7 @@ public partial class AvatarUpload<TValue>
/// </summary>
protected override string? ItemClassString => CssBuilder.Default(base.ItemClassString)
.AddClass("is-circle", IsCircle)
.AddClass("is-single", IsSingle)
.AddClass("disabled", IsDisabled)
.Build();
@ -67,8 +68,6 @@ public partial class AvatarUpload<TValue>
/// <returns></returns>
protected override async Task OnFileChange(InputFileChangeEventArgs args)
{
await base.OnFileChange(args);
CurrentFile = new UploadFile()
{
OriginFileName = args.File.Name,
@ -86,7 +85,8 @@ public partial class AvatarUpload<TValue>
}
UploadFiles.Add(CurrentFile);
ValidateFile();
await base.OnFileChange(args);
// ValidateFile 后 IsValid 才有值
CurrentFile.IsValid = IsValid;

View File

@ -137,9 +137,9 @@ public abstract class ButtonUploadBase<TValue> : SingleUploadBase<TValue>
protected override IDictionary<string, object> GetUploadAdditionalAttributes()
{
var ret = new Dictionary<string, object>
{
{ "hidden", "hidden" }
};
{
{ "hidden", "hidden" }
};
if (!string.IsNullOrEmpty(Accept))
{

View File

@ -23,12 +23,25 @@ public partial class CardUpload<TValue>
.AddClass("disabled", IsDisabled)
.Build();
private static bool IsImage(UploadFile item) => item.File?.ContentType.Contains("image", StringComparison.OrdinalIgnoreCase)
?? Path.GetExtension(item.OriginFileName ?? item.FileName ?? item.PrevUrl)?.ToLowerInvariant() switch
{
".jpg" or ".jpeg" or ".png" or ".bmp" or ".gif" => true,
_ => false
};
private static bool IsImage(UploadFile item)
{
bool ret;
if (item.File != null)
{
ret = item.File.ContentType.Contains("image", StringComparison.OrdinalIgnoreCase) || CheckExtensions(item.File.Name);
}
else
{
ret = CheckExtensions(item.FileName ?? item.PrevUrl ?? "");
}
bool CheckExtensions(string fileName) => Path.GetExtension(fileName).ToLowerInvariant() switch
{
".jpg" or ".jpeg" or ".png" or ".bmp" or ".gif" => true,
_ => false
};
return ret;
}
/// <summary>
/// 获得/设置 点击 Zoom 图标回调方法

View File

@ -13,7 +13,7 @@
{
<Button class="@RemoveButtonClassString" IsDisabled="@IsDeleteButtonDisabled" Icon="@DeleteButtonIcon" Text="@DeleteButtonText" OnClick="@OnDeleteFile" />
}
<Button class="@BrowserButtonClassString" IsDisabled="@IsDisabled" Icon="@BrowserButtonIcon" Text="@BrowserButtonText" OnClick="@OnFileBrowser" />
<Button class="@BrowserButtonClassString" IsDisabled="@IsDisabled" Icon="@BrowserButtonIcon" Text="@BrowserButtonText" />
</div>
<InputFile AdditionalAttributes="@GetUploadAdditionalAttributes()" OnChange="OnFileChange" />
</div>

View File

@ -113,8 +113,6 @@ public partial class InputUpload<TValue>
await base.OnFileChange(args);
ValidateFile();
if (OnChange != null)
{
await OnChange(CurrentFile);

View File

@ -48,14 +48,17 @@ public abstract class MultipleUploadBase<TValue> : UploadBase<TValue>
protected override async Task<bool> OnFileDelete(UploadFile item)
{
var ret = await base.OnFileDelete(item);
if (ret && item != null)
if (ret)
{
UploadFiles.Remove(item);
if (!string.IsNullOrEmpty(item.ValidateId))
{
await JSRuntime.InvokeVoidAsync(null, "bb_tooltip", item.ValidateId, "dispose");
}
DefaultFileList?.Remove(item);
if (DefaultFileList != null)
{
DefaultFileList.Remove(item);
}
}
return ret;
}
@ -67,12 +70,6 @@ public abstract class MultipleUploadBase<TValue> : UploadBase<TValue>
/// <returns></returns>
protected bool GetShowProgress(UploadFile item) => ShowProgress && !item.Uploaded;
/// <summary>
///
/// </summary>
/// <returns></returns>
protected virtual List<UploadFile> GetUploadFiles() => DefaultFileList == null ? UploadFiles : DefaultFileList.Concat(UploadFiles).ToList();
/// <summary>
/// 清空上传列表方法
/// </summary>

View File

@ -27,7 +27,7 @@ public abstract class SingleUploadBase<TValue> : MultipleUploadBase<TValue>
/// 获得当前图片集合
/// </summary>
/// <returns></returns>
protected override List<UploadFile> GetUploadFiles()
protected virtual List<UploadFile> GetUploadFiles()
{
var ret = new List<UploadFile>();
if (IsSingle)
@ -60,7 +60,7 @@ public abstract class SingleUploadBase<TValue> : MultipleUploadBase<TValue>
protected override async Task<bool> OnFileDelete(UploadFile item)
{
var ret = await base.OnFileDelete(item);
if (ret && item != null)
if (ret)
{
if (IsSingle)
{
@ -74,13 +74,21 @@ public abstract class SingleUploadBase<TValue> : MultipleUploadBase<TValue>
{
await JSRuntime.InvokeVoidAsync(null, "bb_tooltip", item.ValidateId, "dispose");
}
if (IsSingle)
RemoveItem();
}
void RemoveItem()
{
if (DefaultFileList != null)
{
DefaultFileList?.Clear();
}
else
{
DefaultFileList?.Remove(item);
if (IsSingle)
{
DefaultFileList.Clear();
}
else
{
DefaultFileList.Remove(item);
}
}
}
return ret;

View File

@ -74,18 +74,7 @@ public abstract class UploadBase<TValue> : ValidateBase<TValue>, IUpload
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
protected string? GetFileName(UploadFile? item = null) => item?.OriginFileName ?? item?.FileName ?? Value?.ToString();
/// <summary>
/// 触发客户端验证方法
/// </summary>
protected void ValidateFile()
{
if (ValidateForm != null && EditContext != null && FieldIdentifier.HasValue)
{
EditContext.NotifyFieldChanged(FieldIdentifier.Value);
}
}
protected string? GetFileName(UploadFile? item) => item?.OriginFileName ?? item?.FileName;
/// <summary>
/// 显示/隐藏验证结果方法
@ -97,14 +86,17 @@ public abstract class UploadBase<TValue> : ValidateBase<TValue>, IUpload
if (FieldIdentifier != null)
{
var messages = results.Where(item => item.MemberNames.Any(m => UploadFiles.Any(f => f.ValidateId?.Equals(m, StringComparison.OrdinalIgnoreCase) ?? false)));
if (messages.Any() && CurrentFile != null)
if (messages.Any())
{
ErrorMessage = messages.FirstOrDefault(m => m.MemberNames.Any(f => f.Equals(CurrentFile.ValidateId, StringComparison.OrdinalIgnoreCase)))?.ErrorMessage;
IsValid = string.IsNullOrEmpty(ErrorMessage);
if (IsValid.HasValue && !IsValid.Value)
IsValid = false;
if (CurrentFile != null)
{
TooltipMethod = validProperty ? "show" : "enable";
var msg = messages.FirstOrDefault(m => m.MemberNames.Any(f => f.Equals(CurrentFile.ValidateId, StringComparison.OrdinalIgnoreCase)));
if (msg != null)
{
ErrorMessage = msg.ErrorMessage;
TooltipMethod = validProperty ? "show" : "enable";
}
}
}
else
@ -147,17 +139,11 @@ public abstract class UploadBase<TValue> : ValidateBase<TValue>, IUpload
}
if (type.IsAssignableTo(typeof(List<IBrowserFile>)))
{
CurrentValue = (TValue)(object)UploadFiles;
CurrentValue = (TValue)(object)UploadFiles.Select(f => f.File).ToList();
}
return Task.CompletedTask;
}
/// <summary>
///
/// </summary>
/// <returns></returns>
protected virtual Task OnFileBrowser() => Task.CompletedTask;
/// <summary>
///
/// </summary>
@ -165,14 +151,13 @@ public abstract class UploadBase<TValue> : ValidateBase<TValue>, IUpload
protected virtual IDictionary<string, object> GetUploadAdditionalAttributes()
{
var ret = new Dictionary<string, object>
{
{ "hidden", "hidden" }
};
{
{ "hidden", "hidden" }
};
if (!string.IsNullOrEmpty(Accept))
{
ret.Add("accept", Accept);
}
return ret;
}

View File

@ -79,6 +79,10 @@
cursor: pointer;
}
.upload .upload-body.is-avatar .upload-item.is-single {
margin: 0;
}
.upload .upload-body.is-avatar .upload-item.is-invalid {
border-color: #dc3545;
border-style: solid;

View File

@ -79,7 +79,7 @@ public class UploadFile
/// <param name="maxHeight"></param>
/// <param name="maxAllowedSize"></param>
/// <param name="token"></param>
/// <returns></returns>
[ExcludeFromCodeCoverage]
public async Task RequestBase64ImageFileAsync(string format, int maxWidth, int maxHeight, long maxAllowedSize = 512000, CancellationToken token = default)
{
if (File != null)
@ -109,6 +109,7 @@ public class UploadFile
/// <param name="maxAllowedSize"></param>
/// <param name="token"></param>
/// <returns></returns>
[ExcludeFromCodeCoverage]
public async Task<bool> SaveToFile(string fileName, long maxAllowedSize = 512000, CancellationToken token = default)
{
var ret = false;

File diff suppressed because one or more lines are too long

View File

@ -4,6 +4,7 @@
using BootstrapBlazor.Shared;
using Microsoft.AspNetCore.Components.Forms;
using System.ComponentModel.DataAnnotations;
namespace UnitTest.Components;
@ -112,6 +113,31 @@ public class UploadTest : BootstrapBlazorTestBase
Assert.False(invalid);
}
[Fact]
public void InputUpload_FileValidate_OK()
{
var foo = new Person();
var cut = Context.RenderComponent<ValidateForm>(pb =>
{
pb.Add(a => a.Model, foo);
pb.AddChildContent<InputUpload<IBrowserFile>>(pb =>
{
pb.Add(a => a.Value, foo.Picture);
pb.Add(a => a.ValueExpression, Utility.GenerateValueExpression(foo, nameof(Person.Picture), typeof(IBrowserFile)));
});
});
var input = cut.FindComponent<InputFile>();
cut.InvokeAsync(() => input.Instance.OnChange.InvokeAsync(new InputFileChangeEventArgs(new List<MockBrowserFile>()
{
new MockBrowserFile()
})));
// 提交表单
var form = cut.Find("form");
cut.InvokeAsync(() => form.Submit());
}
[Fact]
public void AvatarUpload_Ok()
{
@ -179,6 +205,30 @@ public class UploadTest : BootstrapBlazorTestBase
});
}
[Fact]
public void AvatarUpload_Value_Ok()
{
var cut = Context.RenderComponent<AvatarUpload<IBrowserFile>>(pb => pb.Add(a => a.IsSingle, true));
var input = cut.FindComponent<InputFile>();
cut.InvokeAsync(() => input.Instance.OnChange.InvokeAsync(new InputFileChangeEventArgs(new List<MockBrowserFile>()
{
new MockBrowserFile()
})));
Assert.Equal("UploadTestFile", cut.Instance.Value!.Name);
}
[Fact]
public void AvatarUpload_ListValue_Ok()
{
var cut = Context.RenderComponent<AvatarUpload<List<IBrowserFile>>>();
var input = cut.FindComponent<InputFile>();
cut.InvokeAsync(() => input.Instance.OnChange.InvokeAsync(new InputFileChangeEventArgs(new List<MockBrowserFile>()
{
new MockBrowserFile()
})));
Assert.Single(cut.Instance.Value);
}
[Fact]
public void AvatarUpload_ValidateForm_Ok()
{
@ -189,6 +239,7 @@ public class UploadTest : BootstrapBlazorTestBase
pb.Add(a => a.Model, foo);
pb.AddChildContent<AvatarUpload<string>>(pb =>
{
pb.Add(a => a.Accept, "Image");
pb.Add(a => a.Value, foo.Name);
pb.Add(a => a.ValueExpression, foo.GenerateValueExpression());
});
@ -218,6 +269,31 @@ public class UploadTest : BootstrapBlazorTestBase
Assert.False(invalid);
}
[Fact]
public void AvatarUpload_FileValidate_Ok()
{
var foo = new Person();
var cut = Context.RenderComponent<ValidateForm>(pb =>
{
pb.Add(a => a.Model, foo);
pb.AddChildContent<AvatarUpload<IBrowserFile>>(pb =>
{
pb.Add(a => a.Value, foo.Picture);
pb.Add(a => a.ValueExpression, Utility.GenerateValueExpression(foo, nameof(Person.Picture), typeof(IBrowserFile)));
});
});
var input = cut.FindComponent<InputFile>();
cut.InvokeAsync(() => input.Instance.OnChange.InvokeAsync(new InputFileChangeEventArgs(new List<MockBrowserFile>()
{
new MockBrowserFile()
})));
// 提交表单
var form = cut.Find("form");
cut.InvokeAsync(() => form.Submit());
}
[Fact]
public void ButtonUpload_Ok()
{
@ -236,10 +312,6 @@ public class UploadTest : BootstrapBlazorTestBase
cut.SetParametersAndRender(pb =>
{
pb.Add(a => a.ShowProgress, true);
pb.Add(a => a.DefaultFileList, new List<UploadFile>()
{
new UploadFile() { FileName = "Test-File" }
});
pb.Add(a => a.OnChange, file =>
{
uploadFile = file;
@ -325,6 +397,7 @@ public class UploadTest : BootstrapBlazorTestBase
});
cut.InvokeAsync(() => cut.Find(".fa-trash-o.text-danger").Click());
Assert.NotNull(deleteFile);
Assert.Null(deleteFile!.Error);
deleteFile = null;
// 上传失败测试
@ -449,15 +522,113 @@ public class UploadTest : BootstrapBlazorTestBase
cut.Contains("fa-format-test");
}
[Fact]
public void CardUpload_Ok()
{
var zoom = false;
var deleted = false;
var cut = Context.RenderComponent<CardUpload<string>>(pb =>
{
pb.Add(a => a.OnZoomAsync, file =>
{
zoom = true;
return Task.CompletedTask;
});
pb.Add(a => a.OnDelete, file =>
{
deleted = true;
return Task.FromResult(true);
});
pb.Add(a => a.DefaultFileList, new List<UploadFile>()
{
new UploadFile() { FileName = "Test-File1.text" },
new UploadFile() { FileName = "Test-File2.jpg" },
new UploadFile() { PrevUrl = "Test-File3.png" },
new UploadFile() { PrevUrl = "Test-File4.bmp" },
new UploadFile() { PrevUrl = "Test-File5.jpeg" },
new UploadFile() { PrevUrl = "Test-File6.gif" },
new UploadFile() { FileName = null! }
});
});
// OnZoom
cut.InvokeAsync(() => cut.Find(".btn-outline-secondary").Click());
Assert.True(zoom);
// OnDelete
cut.InvokeAsync(() => cut.Find(".btn-outline-danger").Click());
Assert.True(deleted);
// ShowProgress
cut.SetParametersAndRender(pb =>
{
pb.Add(a => a.ShowProgress, true);
pb.Add(a => a.OnChange, async file =>
{
await file.SaveToFile("1.txt");
});
});
var input = cut.FindComponent<InputFile>();
cut.InvokeAsync(() => input.Instance.OnChange.InvokeAsync(new InputFileChangeEventArgs(new List<MockBrowserFile>()
{
new MockBrowserFile("test.txt", "Image-Png")
})));
cut.InvokeAsync(() => input.Instance.OnChange.InvokeAsync(new InputFileChangeEventArgs(new List<MockBrowserFile>()
{
new MockBrowserFile("test.png")
})));
}
[Fact]
public void CardUpload_Reset()
{
var cut = Context.RenderComponent<CardUpload<string>>();
cut.InvokeAsync(() => cut.Instance.Reset());
Assert.Null(cut.Instance.DefaultFileList);
cut.SetParametersAndRender(pb =>
{
pb.Add(a => a.DefaultFileList, new List<UploadFile>()
{
new UploadFile() { FileName = "Test-File1.text" }
});
});
cut.InvokeAsync(() => cut.Instance.Reset());
Assert.Empty(cut.Instance.DefaultFileList);
}
[Fact]
public void CardUpload_ValidateForm_Ok()
{
var foo = new Foo();
var cut = Context.RenderComponent<ValidateForm>(pb =>
{
pb.Add(a => a.Model, foo);
pb.AddChildContent<CardUpload<string>>(pb =>
{
pb.Add(a => a.Value, foo.Name);
pb.Add(a => a.ValueExpression, foo.GenerateValueExpression());
});
});
cut.Contains("form-label");
}
private class Person
{
[Required]
[FileValidation(Extensions = new string[] { ".png", ".jpg", ".jpeg" })]
public IBrowserFile? Picture { get; set; }
}
[ExcludeFromCodeCoverage]
private class MockBrowserFile : IBrowserFile
{
public MockBrowserFile(string name = "UploadTestFile")
public MockBrowserFile(string name = "UploadTestFile", string contentType = "text")
{
Name = name;
LastModified = DateTimeOffset.Now;
Size = 10;
ContentType = "jpg";
ContentType = contentType;
}
public string Name { get; }