mirror of
https://gitee.com/dotnetchina/MiniExcel.git
synced 2024-11-29 18:38:08 +08:00
Upgrade to .NET 8.0 and refactor input value extraction (#681)
* Upgrade to .NET 8.0 * Add tests
This commit is contained in:
parent
f0fe803d6a
commit
a0797a53f6
2
.github/workflows/dotnet.yml
vendored
2
.github/workflows/dotnet.yml
vendored
@ -16,7 +16,7 @@ jobs:
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
dotnet-version: 6.0.x
|
||||
dotnet-version: 8.0.x
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore
|
||||
env:
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -395,6 +395,7 @@ FodyWeavers.xsd
|
||||
*.msp
|
||||
|
||||
# JetBrains Rider
|
||||
.idea/
|
||||
*.sln.iml
|
||||
|
||||
/BenchmarkDotNet.Artifacts
|
||||
|
@ -2,6 +2,7 @@
|
||||
{
|
||||
using MiniExcelLibs.Csv;
|
||||
using MiniExcelLibs.OpenXml;
|
||||
using MiniExcelLibs.OpenXml.SaveByTemplate;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
@ -49,7 +50,8 @@
|
||||
switch (excelType)
|
||||
{
|
||||
case ExcelType.XLSX:
|
||||
return new ExcelOpenXmlTemplate(stream, configuration);
|
||||
var valueExtractor = new InputValueExtractor();
|
||||
return new ExcelOpenXmlTemplate(stream, configuration, valueExtractor);
|
||||
default:
|
||||
throw new NotSupportedException($"Please Issue for me");
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net45;netstandard2.0;net6.0;</TargetFrameworks>
|
||||
<TargetFrameworks>net45;netstandard2.0;net8.0;</TargetFrameworks>
|
||||
<Version>1.34.2</Version>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
|
@ -1,154 +0,0 @@
|
||||
|
||||
namespace MiniExcelLibs.OpenXml
|
||||
{
|
||||
using MiniExcelLibs.Utils;
|
||||
using MiniExcelLibs.Zip;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml;
|
||||
|
||||
internal partial class ExcelOpenXmlTemplate : IExcelTemplate, IExcelTemplateAsync
|
||||
{
|
||||
private static readonly XmlNamespaceManager _ns;
|
||||
private static readonly Regex _isExpressionRegex;
|
||||
static ExcelOpenXmlTemplate()
|
||||
{
|
||||
_isExpressionRegex = new Regex("(?<={{).*?(?=}})");
|
||||
_ns = new XmlNamespaceManager(new NameTable());
|
||||
_ns.AddNamespace("x", Config.SpreadsheetmlXmlns);
|
||||
_ns.AddNamespace( "x14ac", Config.SpreadsheetmlXml_x14ac );
|
||||
}
|
||||
|
||||
private readonly Stream _stream;
|
||||
private readonly OpenXmlConfiguration _configuration;
|
||||
private readonly StringBuilder _calcChainContent = new StringBuilder();
|
||||
|
||||
public ExcelOpenXmlTemplate(Stream stream, IConfiguration configuration)
|
||||
{
|
||||
_stream = stream;
|
||||
_configuration = (OpenXmlConfiguration)configuration ?? OpenXmlConfiguration.DefaultConfig;
|
||||
}
|
||||
|
||||
public void SaveAsByTemplate(string templatePath, object value)
|
||||
{
|
||||
using (var stream = FileHelper.OpenSharedRead(templatePath))
|
||||
SaveAsByTemplateImpl(stream, value);
|
||||
}
|
||||
public void SaveAsByTemplate(byte[] templateBtyes, object value)
|
||||
{
|
||||
using (Stream stream = new MemoryStream(templateBtyes))
|
||||
SaveAsByTemplateImpl(stream, value);
|
||||
}
|
||||
|
||||
public void SaveAsByTemplateImpl(Stream templateStream, object value)
|
||||
{
|
||||
//only support xlsx
|
||||
Dictionary<string, object> values = null;
|
||||
if (value is Dictionary<string, object>)
|
||||
{
|
||||
values = value as Dictionary<string, object>;
|
||||
foreach (var key in values.Keys)
|
||||
{
|
||||
var v = values[key];
|
||||
if (v is IDataReader)
|
||||
{
|
||||
values[key] = TypeHelper.ConvertToEnumerableDictionary(v as IDataReader).ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var type = value.GetType();
|
||||
var props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
|
||||
values = new Dictionary<string, object>();
|
||||
foreach (var p in props)
|
||||
{
|
||||
values.Add(p.Name, p.GetValue(value));
|
||||
}
|
||||
|
||||
var fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance);
|
||||
foreach (var f in fields)
|
||||
{
|
||||
if (!values.ContainsKey(f.Name))
|
||||
{ values.Add(f.Name, f.GetValue(value)); }
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
templateStream.CopyTo(_stream);
|
||||
|
||||
var reader = new ExcelOpenXmlSheetReader(_stream, null);
|
||||
var _archive = new ExcelOpenXmlZip(_stream, mode: ZipArchiveMode.Update, true, Encoding.UTF8);
|
||||
{
|
||||
//read sharedString
|
||||
var sharedStrings = reader._sharedStrings;
|
||||
StringBuilder calcSheetContent = new StringBuilder();
|
||||
|
||||
//read all xlsx sheets
|
||||
var sheets = _archive.zipFile.Entries.Where(w => w.FullName.StartsWith("xl/worksheets/sheet", StringComparison.OrdinalIgnoreCase)
|
||||
|| w.FullName.StartsWith("/xl/worksheets/sheet", StringComparison.OrdinalIgnoreCase)
|
||||
).ToList();
|
||||
|
||||
int sheetIdx = 0;
|
||||
foreach (var sheet in sheets)
|
||||
{
|
||||
this.XRowInfos = new List<XRowInfo>(); //every time need to use new XRowInfos or it'll cause duplicate problem: https://user-images.githubusercontent.com/12729184/115003101-0fcab700-9ed8-11eb-9151-ca4d7b86d59e.png
|
||||
this.XMergeCellInfos = new Dictionary<string, XMergeCell>();
|
||||
this.NewXMergeCellInfos = new List<XMergeCell>();
|
||||
|
||||
var sheetStream = sheet.Open();
|
||||
var fullName = sheet.FullName;
|
||||
|
||||
ZipArchiveEntry entry = _archive.zipFile.CreateEntry(fullName);
|
||||
using (var zipStream = entry.Open())
|
||||
{
|
||||
GenerateSheetXmlImpl(sheet, zipStream, sheetStream, values, sharedStrings, false);
|
||||
//doc.Save(zipStream); //don't do it because : ![image](https://user-images.githubusercontent.com/12729184/114361127-61a5d100-9ba8-11eb-9bb9-34f076ee28a2.png)
|
||||
}
|
||||
|
||||
// disposing writer disposes streams as well. reopen the entry to read and parse calc functions
|
||||
using (var filledStream = entry.Open())
|
||||
{
|
||||
sheetIdx++;
|
||||
_calcChainContent.Append( CalcChainHelper.GetCalcChainContent( CalcChainCellRefs, sheetIdx ) );
|
||||
}
|
||||
}
|
||||
|
||||
var calcChain = _archive.zipFile.Entries.FirstOrDefault(e => e.FullName.Contains("xl/calcChain.xml"));
|
||||
if (calcChain != null)
|
||||
{
|
||||
string calcChainPathname = calcChain.FullName;
|
||||
calcChain.Delete();
|
||||
var calcChainEntry = _archive.zipFile.CreateEntry(calcChainPathname);
|
||||
using (var calcChainStream = calcChainEntry.Open())
|
||||
{
|
||||
CalcChainHelper.GenerateCalcChainSheet(calcChainStream, _calcChainContent.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
_archive.zipFile.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public Task SaveAsByTemplateAsync(string templatePath, object value, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
return Task.Run(() => SaveAsByTemplate(templatePath, value), cancellationToken);
|
||||
}
|
||||
|
||||
public Task SaveAsByTemplateAsync(byte[] templateBtyes, object value, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
return Task.Run(() => SaveAsByTemplate(templateBtyes, value), cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
@ -12,7 +12,7 @@ using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Xml;
|
||||
|
||||
namespace MiniExcelLibs.OpenXml
|
||||
namespace MiniExcelLibs.OpenXml.SaveByTemplate
|
||||
{
|
||||
internal partial class ExcelOpenXmlTemplate
|
||||
{
|
||||
@ -113,7 +113,7 @@ namespace MiniExcelLibs.OpenXml
|
||||
public List<XMergeCell> NewXMergeCellInfos { get; private set; }
|
||||
|
||||
private void GenerateSheetXmlImpl(ZipArchiveEntry sheetZipEntry, Stream stream, Stream sheetStream,
|
||||
Dictionary<string, object> inputMaps, IDictionary<int, string> sharedStrings,
|
||||
IDictionary<string, object> inputMaps, IDictionary<int, string> sharedStrings,
|
||||
bool mergeCells = false)
|
||||
{
|
||||
var doc = new XmlDocument();
|
||||
@ -911,7 +911,7 @@ namespace MiniExcelLibs.OpenXml
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateDimensionAndGetRowsInfo(Dictionary<string, object> inputMaps, ref XmlDocument doc, ref XmlNodeList rows, bool changeRowIndex = true)
|
||||
private void UpdateDimensionAndGetRowsInfo(IDictionary<string, object> inputMaps, ref XmlDocument doc, ref XmlNodeList rows, bool changeRowIndex = true)
|
||||
{
|
||||
// note : dimension need to put on the top ![image](https://user-images.githubusercontent.com/12729184/114507911-5dd88400-9c66-11eb-94c6-82ed7bdb5aab.png)
|
||||
|
@ -9,7 +9,7 @@ using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MiniExcelLibs.OpenXml
|
||||
namespace MiniExcelLibs.OpenXml.SaveByTemplate
|
||||
{
|
||||
internal partial class ExcelOpenXmlTemplate
|
||||
{
|
126
src/MiniExcel/SaveByTemplate/ExcelOpenXmlTemplate.cs
Normal file
126
src/MiniExcel/SaveByTemplate/ExcelOpenXmlTemplate.cs
Normal file
@ -0,0 +1,126 @@
|
||||
namespace MiniExcelLibs.OpenXml.SaveByTemplate
|
||||
{
|
||||
using MiniExcelLibs.Utils;
|
||||
using MiniExcelLibs.Zip;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml;
|
||||
|
||||
internal partial class ExcelOpenXmlTemplate : IExcelTemplate, IExcelTemplateAsync
|
||||
{
|
||||
private static readonly XmlNamespaceManager _ns;
|
||||
private static readonly Regex _isExpressionRegex;
|
||||
|
||||
static ExcelOpenXmlTemplate()
|
||||
{
|
||||
_isExpressionRegex = new Regex("(?<={{).*?(?=}})");
|
||||
_ns = new XmlNamespaceManager(new NameTable());
|
||||
_ns.AddNamespace("x", Config.SpreadsheetmlXmlns);
|
||||
_ns.AddNamespace("x14ac", Config.SpreadsheetmlXml_x14ac);
|
||||
}
|
||||
|
||||
private readonly Stream _stream;
|
||||
private readonly OpenXmlConfiguration _configuration;
|
||||
private readonly IInputValueExtractor _inputValueExtractor;
|
||||
private readonly StringBuilder _calcChainContent = new StringBuilder();
|
||||
|
||||
public ExcelOpenXmlTemplate(Stream stream, IConfiguration configuration, InputValueExtractor inputValueExtractor)
|
||||
{
|
||||
_stream = stream;
|
||||
_configuration = (OpenXmlConfiguration)configuration ?? OpenXmlConfiguration.DefaultConfig;
|
||||
_inputValueExtractor = inputValueExtractor;
|
||||
}
|
||||
|
||||
public void SaveAsByTemplate(string templatePath, object value)
|
||||
{
|
||||
using (var stream = FileHelper.OpenSharedRead(templatePath))
|
||||
SaveAsByTemplateImpl(stream, value);
|
||||
}
|
||||
|
||||
public void SaveAsByTemplate(byte[] templateBtyes, object value)
|
||||
{
|
||||
using (Stream stream = new MemoryStream(templateBtyes))
|
||||
SaveAsByTemplateImpl(stream, value);
|
||||
}
|
||||
|
||||
public void SaveAsByTemplateImpl(Stream templateStream, object value)
|
||||
{
|
||||
//only support xlsx
|
||||
templateStream.CopyTo(_stream);
|
||||
|
||||
var reader = new ExcelOpenXmlSheetReader(_stream, null);
|
||||
var _archive = new ExcelOpenXmlZip(_stream, mode: ZipArchiveMode.Update, true, Encoding.UTF8);
|
||||
{
|
||||
//read sharedString
|
||||
var sharedStrings = reader._sharedStrings;
|
||||
StringBuilder calcSheetContent = new StringBuilder();
|
||||
|
||||
//read all xlsx sheets
|
||||
var sheets = _archive.zipFile.Entries.Where(w =>
|
||||
w.FullName.StartsWith("xl/worksheets/sheet", StringComparison.OrdinalIgnoreCase)
|
||||
|| w.FullName.StartsWith("/xl/worksheets/sheet", StringComparison.OrdinalIgnoreCase)
|
||||
).ToList();
|
||||
|
||||
int sheetIdx = 0;
|
||||
foreach (var sheet in sheets)
|
||||
{
|
||||
this.XRowInfos =
|
||||
new List<XRowInfo>(); //every time need to use new XRowInfos or it'll cause duplicate problem: https://user-images.githubusercontent.com/12729184/115003101-0fcab700-9ed8-11eb-9151-ca4d7b86d59e.png
|
||||
this.XMergeCellInfos = new Dictionary<string, XMergeCell>();
|
||||
this.NewXMergeCellInfos = new List<XMergeCell>();
|
||||
|
||||
var sheetStream = sheet.Open();
|
||||
var fullName = sheet.FullName;
|
||||
|
||||
var inputValues = _inputValueExtractor.ToValueDictionary(value);
|
||||
ZipArchiveEntry entry = _archive.zipFile.CreateEntry(fullName);
|
||||
using (var zipStream = entry.Open())
|
||||
{
|
||||
GenerateSheetXmlImpl(sheet, zipStream, sheetStream, inputValues, sharedStrings, false);
|
||||
//doc.Save(zipStream); //don't do it because : ![image](https://user-images.githubusercontent.com/12729184/114361127-61a5d100-9ba8-11eb-9bb9-34f076ee28a2.png)
|
||||
}
|
||||
|
||||
// disposing writer disposes streams as well. reopen the entry to read and parse calc functions
|
||||
using (var filledStream = entry.Open())
|
||||
{
|
||||
sheetIdx++;
|
||||
_calcChainContent.Append(CalcChainHelper.GetCalcChainContent(CalcChainCellRefs, sheetIdx));
|
||||
}
|
||||
}
|
||||
|
||||
var calcChain = _archive.zipFile.Entries.FirstOrDefault(e => e.FullName.Contains("xl/calcChain.xml"));
|
||||
if (calcChain != null)
|
||||
{
|
||||
string calcChainPathname = calcChain.FullName;
|
||||
calcChain.Delete();
|
||||
var calcChainEntry = _archive.zipFile.CreateEntry(calcChainPathname);
|
||||
using (var calcChainStream = calcChainEntry.Open())
|
||||
{
|
||||
CalcChainHelper.GenerateCalcChainSheet(calcChainStream, _calcChainContent.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_archive.zipFile.Dispose();
|
||||
}
|
||||
|
||||
public Task SaveAsByTemplateAsync(string templatePath, object value,
|
||||
CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
return Task.Run(() => SaveAsByTemplate(templatePath, value), cancellationToken);
|
||||
}
|
||||
|
||||
public Task SaveAsByTemplateAsync(byte[] templateBtyes, object value,
|
||||
CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
return Task.Run(() => SaveAsByTemplate(templateBtyes, value), cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
9
src/MiniExcel/SaveByTemplate/IInputValueExtractor.cs
Normal file
9
src/MiniExcel/SaveByTemplate/IInputValueExtractor.cs
Normal file
@ -0,0 +1,9 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MiniExcelLibs.OpenXml.SaveByTemplate
|
||||
{
|
||||
public interface IInputValueExtractor
|
||||
{
|
||||
IDictionary<string, object> ToValueDictionary(object valueObject);
|
||||
}
|
||||
}
|
43
src/MiniExcel/SaveByTemplate/InputValueExtractor.cs
Normal file
43
src/MiniExcel/SaveByTemplate/InputValueExtractor.cs
Normal file
@ -0,0 +1,43 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using MiniExcelLibs.Utils;
|
||||
|
||||
namespace MiniExcelLibs.OpenXml.SaveByTemplate
|
||||
{
|
||||
public class InputValueExtractor : IInputValueExtractor
|
||||
{
|
||||
public IDictionary<string, object> ToValueDictionary(object valueObject)
|
||||
=> valueObject is Dictionary<string, object> valueDictionary
|
||||
? GetValuesFromDictionary(valueDictionary)
|
||||
: GetValuesFromObject(valueObject);
|
||||
|
||||
private static IDictionary<string, object> GetValuesFromDictionary(Dictionary<string, object> valueDictionary)
|
||||
{
|
||||
return valueDictionary
|
||||
.ToDictionary(
|
||||
x => x.Key,
|
||||
x => x.Value is IDataReader dataReader
|
||||
? TypeHelper.ConvertToEnumerableDictionary(dataReader).ToList()
|
||||
: x.Value);
|
||||
}
|
||||
|
||||
private static IDictionary<string, object> GetValuesFromObject(object valueObject)
|
||||
{
|
||||
var type = valueObject.GetType();
|
||||
|
||||
var propertyValues = type
|
||||
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
|
||||
.Select(property => new { property.Name, Value = property.GetValue(valueObject) });
|
||||
|
||||
var fieldValues = type
|
||||
.GetFields(BindingFlags.Public | BindingFlags.Instance)
|
||||
.Select(field => new { field.Name, Value = field.GetValue(valueObject) });
|
||||
|
||||
return propertyValues
|
||||
.Concat(fieldValues)
|
||||
.ToDictionary(x => x.Name, x => x.Value);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>net8.0</TargetFrameworks>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
<AssemblyOriginatorKeyFile>miniexcel.snk</AssemblyOriginatorKeyFile>
|
||||
@ -22,6 +22,7 @@
|
||||
<PackageReference Include="EPPlus" Version="4.5.3.3" />
|
||||
<PackageReference Include="ExcelDataReader" Version="3.6.0" />
|
||||
<PackageReference Include="ExcelDataReader.DataSet" Version="3.6.0" />
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
||||
<PackageReference Include="NPOI" Version="2.7.0" />
|
||||
<PackageReference Include="NSubstitute" Version="5.1.0" />
|
||||
|
104
tests/MiniExcelTests/SaveByTemplate/InputValueExtractorTests.cs
Normal file
104
tests/MiniExcelTests/SaveByTemplate/InputValueExtractorTests.cs
Normal file
@ -0,0 +1,104 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using FluentAssertions;
|
||||
using MiniExcelLibs.OpenXml.SaveByTemplate;
|
||||
using Xunit;
|
||||
|
||||
namespace MiniExcelTests.SaveByTemplate;
|
||||
|
||||
public class InputValueExtractorTests
|
||||
{
|
||||
[Fact]
|
||||
public void ToValueDictionary_Given_InputIsDictionaryWithoutDataReader_Then_Output_IsAnEquivalentDictionary()
|
||||
{
|
||||
var valueDictionary = new Dictionary<string, object>
|
||||
{
|
||||
["Name"] = "John",
|
||||
["Age"] = 18,
|
||||
["Fruits"] = new List<string> { "Apples, Oranges" },
|
||||
};
|
||||
|
||||
var sut = new InputValueExtractor();
|
||||
var result = sut.ToValueDictionary(valueDictionary);
|
||||
|
||||
result.Should().BeEquivalentTo(valueDictionary);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToValueDictionary_Given_InputIsDictionaryWithDataReader_Then_DataReaderIsConvertedToListOfDictionaries()
|
||||
{
|
||||
var dataTable = new DataTable();
|
||||
|
||||
dataTable.Columns.Add("id", typeof(int));
|
||||
dataTable.Columns.Add("name", typeof(string));
|
||||
dataTable.Rows.Add(1, "Jack");
|
||||
dataTable.Rows.Add(2, "Mike");
|
||||
|
||||
var expectedOutput = new List<Dictionary<string, object>>
|
||||
{
|
||||
new() { ["id"] = 1, ["name"] = "Jack" },
|
||||
new() { ["id"] = 2, ["name"] = "Mike" }
|
||||
};
|
||||
|
||||
var valueDictionary = new Dictionary<string, object>
|
||||
{
|
||||
["DataReader"] = dataTable.CreateDataReader()
|
||||
};
|
||||
|
||||
var sut = new InputValueExtractor();
|
||||
var result = sut.ToValueDictionary(valueDictionary);
|
||||
|
||||
result["DataReader"].Should().BeEquivalentTo(expectedOutput);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToValueDictionary_Given_InputIsPocoRecord_Then_Output_IsAnEquivalentDictionary()
|
||||
{
|
||||
var valueObject = new PocoRecord("John", 18, new List<string> { "Apples, Oranges" });
|
||||
|
||||
var expectedOutput = new Dictionary<string, object>()
|
||||
{
|
||||
["Name"] = "John",
|
||||
["Age"] = 18,
|
||||
["Fruits"] = new List<string> { "Apples, Oranges" }
|
||||
};
|
||||
|
||||
var sut = new InputValueExtractor();
|
||||
var result = sut.ToValueDictionary(valueObject);
|
||||
|
||||
result.Should().BeEquivalentTo(expectedOutput);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToValueDictionary_Given_InputIsPocoClass_Then_Output_IsAnEquivalentDictionary()
|
||||
{
|
||||
var valueObject = new PocoClass
|
||||
{
|
||||
Name = "John",
|
||||
Age = 18,
|
||||
Fruits = new List<string> { "Apples, Oranges" }
|
||||
};
|
||||
|
||||
var expectedOutput = new Dictionary<string, object>()
|
||||
{
|
||||
["Name"] = "John",
|
||||
["Age"] = 18,
|
||||
["Fruits"] = new List<string> { "Apples, Oranges" }
|
||||
};
|
||||
|
||||
var sut = new InputValueExtractor();
|
||||
var result = sut.ToValueDictionary(valueObject);
|
||||
|
||||
result.Should().BeEquivalentTo(expectedOutput);
|
||||
}
|
||||
|
||||
|
||||
private record PocoRecord(string Name, int Age, IEnumerable<string> Fruits);
|
||||
|
||||
private class PocoClass
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public int Age { get; set; }
|
||||
public IEnumerable<string> Fruits; // Field
|
||||
};
|
||||
}
|
@ -10,7 +10,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace MiniExcelTests
|
||||
namespace MiniExcelTests.SaveByTemplate
|
||||
{
|
||||
public class MiniExcelTemplateAsyncTests
|
||||
{
|
@ -9,7 +9,7 @@ using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace MiniExcelTests
|
||||
namespace MiniExcelTests.SaveByTemplate
|
||||
{
|
||||
public class MiniExcelTemplateTests
|
||||
{
|
Loading…
Reference in New Issue
Block a user