diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index bef3eae4..fed4199b 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -17,6 +17,7 @@ jobs: with: dotnet-version: | 8.0.x + 9.0.x 10.0.x - name: Restore dependencies run: dotnet restore diff --git a/.runsettings b/.runsettings new file mode 100644 index 00000000..e55c905e --- /dev/null +++ b/.runsettings @@ -0,0 +1,27 @@ + + + + + + + + + + .*MiniExcel.Tests.Common.dll + .*MiniExcel.Csv.Tests.dll + .*MiniExcel.OpenXml.Tests.dll + .*Dapper.dll + + + + + + System.Text.RegularExpressions.Generated + + + + + + + + diff --git a/MiniExcel.slnx b/MiniExcel.slnx index 60cbb969..afe06bcc 100644 --- a/MiniExcel.slnx +++ b/MiniExcel.slnx @@ -7,6 +7,7 @@ + @@ -26,9 +27,9 @@ - + diff --git a/src/MiniExcel.Core/MiniExcelDataReaderBase.cs b/src/MiniExcel.Core/MiniExcelDataReaderBase.cs index 73275a98..bb6acd77 100644 --- a/src/MiniExcel.Core/MiniExcelDataReaderBase.cs +++ b/src/MiniExcel.Core/MiniExcelDataReaderBase.cs @@ -248,12 +248,10 @@ public DataTable GetSchemaTable() return Schema; } - - public virtual bool NextResult() - => throw new NotImplementedException(); - public virtual Task NextResultAsync(CancellationToken cancellationToken = default) - => throw new NotImplementedException(); + public abstract bool NextResult(); + + public abstract Task NextResultAsync(CancellationToken cancellationToken = default); public void Close() diff --git a/src/MiniExcel.OpenXml/Api/OpenXmlExporter.cs b/src/MiniExcel.OpenXml/Api/OpenXmlExporter.cs index 035c6e67..7bf23854 100644 --- a/src/MiniExcel.OpenXml/Api/OpenXmlExporter.cs +++ b/src/MiniExcel.OpenXml/Api/OpenXmlExporter.cs @@ -127,6 +127,9 @@ public async Task CopyAndAddSheetAsync(string inputFile, string outputFile, if (inputFile.Equals(outputFile, StringComparison.InvariantCultureIgnoreCase)) throw new ArgumentException("The generated file must not have the same path as the original file."); + if (Path.GetExtension(outputFile).Equals(".xlsm", StringComparison.InvariantCultureIgnoreCase)) + throw new NotSupportedException("MiniExcel's CopyAndAddSheet does not support the .xlsm format"); + var inputStream = new FileStream(inputFile, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, FileOptions.RandomAccess); await using var disposableInputStream = inputStream.ConfigureAwait(false); diff --git a/src/MiniExcel.OpenXml/Api/OpenXmlImporter.cs b/src/MiniExcel.OpenXml/Api/OpenXmlImporter.cs index 541950e7..e5e44342 100644 --- a/src/MiniExcel.OpenXml/Api/OpenXmlImporter.cs +++ b/src/MiniExcel.OpenXml/Api/OpenXmlImporter.cs @@ -109,7 +109,7 @@ public async IAsyncEnumerable QueryAsync(Stream stream, bool hasHeaderR #region Query Range /// - /// Queries a specific rectangular region within an worksheet using index-based coordinates. + /// Queries a specific rectangular region within an worksheet using cell-based coordinates. /// /// The path to the Excel document. /// If true, the first row within the range is used as column headers for dynamic object properties. Default is false. @@ -131,7 +131,7 @@ public async IAsyncEnumerable QueryRangeAsync(string path, bool hasHead } /// - /// Queries a specific rectangular region within an worksheet using index-based coordinates. + /// Queries a specific rectangular region within an worksheet using cell-based coordinates. /// /// The stream containing the Excel file data. The stream position is not reset after reading. /// If true, the first row within the range is used as column headers for dynamic object properties. Default is false. @@ -375,7 +375,7 @@ public async Task> GetSheetInformationsAsync(string path, Cancel [CreateSyncVersion] public async Task> GetSheetInformationsAsync(Stream stream, bool leaveOpen = false, CancellationToken cancellationToken = default) { - var archive = await OpenXmlZip.CreateAsync(stream, cancellationToken: cancellationToken).ConfigureAwait(false); + var archive = await OpenXmlZip.CreateAsync(stream, leaveOpen: leaveOpen, cancellationToken: cancellationToken).ConfigureAwait(false); await using var disposableArchve = archive.ConfigureAwait(false); var rels = await OpenXmlReader.GetWorkbookRelsAsync(archive.EntryCollection, cancellationToken).ConfigureAwait(false); diff --git a/src/MiniExcel.OpenXml/Api/OpenXmlTemplater.cs b/src/MiniExcel.OpenXml/Api/OpenXmlTemplater.cs index bb855a59..e9c512f9 100644 --- a/src/MiniExcel.OpenXml/Api/OpenXmlTemplater.cs +++ b/src/MiniExcel.OpenXml/Api/OpenXmlTemplater.cs @@ -22,7 +22,7 @@ public async Task AddPictureAsync(string path, CancellationToken cancellationTok var stream = File.Open(path, FileMode.OpenOrCreate); await using var disposableStream = stream.ConfigureAwait(false); - await MiniExcelPictureImplement.AddPictureAsync(stream, cancellationToken, images).ConfigureAwait(false); + await AddPictureAsync(stream, cancellationToken, images).ConfigureAwait(false); } /// diff --git a/tests/MiniExcel.Csv.Tests/DataReader/CsvDataReaderAsyncTests.cs b/tests/MiniExcel.Csv.Tests/DataReader/CsvDataReaderAsyncTests.cs index fdaf139b..8b439fc0 100644 --- a/tests/MiniExcel.Csv.Tests/DataReader/CsvDataReaderAsyncTests.cs +++ b/tests/MiniExcel.Csv.Tests/DataReader/CsvDataReaderAsyncTests.cs @@ -115,8 +115,7 @@ public async Task GetDataReader_GetOrdinal_ReturnsColumnIndex() public async Task GetDataReader_NextResult_ThrowsNotSupportedException() { var path = PathHelper.GetFile("csv/TestDataReaderHeader.csv"); - await using var stream = File.OpenRead(path); - await using var reader = await _csvImporter.GetAsyncDataReader(stream, hasHeaderRow: true); + await using var reader = await _csvImporter.GetAsyncDataReader(path, hasHeaderRow: true); await Assert.ThrowsAsync(async () => await reader.NextResultAsync()); } diff --git a/tests/MiniExcel.Csv.Tests/DataReader/CsvDataReaderTests.cs b/tests/MiniExcel.Csv.Tests/DataReader/CsvDataReaderTests.cs index b18fd93a..607c6ea1 100644 --- a/tests/MiniExcel.Csv.Tests/DataReader/CsvDataReaderTests.cs +++ b/tests/MiniExcel.Csv.Tests/DataReader/CsvDataReaderTests.cs @@ -112,11 +112,10 @@ public void GetDataReader_GetOrdinal_ReturnsColumnIndex() } [Fact] - public async Task GetDataReader_NextResult_ThrowsNotSupportedException() + public void GetDataReader_NextResult_ThrowsNotSupportedException() { var path = PathHelper.GetFile("csv/TestDataReaderHeader.csv"); - await using var stream = File.OpenRead(path); - await using var reader = await _csvImporter.GetAsyncDataReader(stream, hasHeaderRow: true); + using var reader = _csvImporter.GetDataReader(path, hasHeaderRow: true); Assert.Throws(() => reader.NextResult()); } diff --git a/tests/MiniExcel.Csv.Tests/Main/MiniExcelCsvAsyncTests.cs b/tests/MiniExcel.Csv.Tests/Main/MiniExcelCsvAsyncTests.cs index a83dcc2f..081d4496 100644 --- a/tests/MiniExcel.Csv.Tests/Main/MiniExcelCsvAsyncTests.cs +++ b/tests/MiniExcel.Csv.Tests/Main/MiniExcelCsvAsyncTests.cs @@ -6,7 +6,7 @@ public class MiniExcelCsvAsyncTests private readonly CsvImporter _csvImporter = MiniExcel.Importers.GetCsvImporter(); [Fact] - public void Gb2312_Encoding_Read_Test() + public async Task Gb2312_Encoding_Read_Test() { Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); var path = PathHelper.GetFile("csv/gb2312_Encoding_Read_Test.csv"); @@ -14,7 +14,7 @@ public void Gb2312_Encoding_Read_Test() { StreamReaderFunc = stream => new StreamReader(stream, encoding: Encoding.GetEncoding("gb2312")) }; - var rows = _csvImporter.QueryAsync(path, true, configuration: config).ToBlockingEnumerable().ToList(); + var rows = await _csvImporter.QueryAsync(path, true, configuration: config).ToListAsync(); Assert.Equal("世界你好", rows[0].栏位1); } @@ -57,6 +57,14 @@ public async Task SeperatorTest() Assert.Equal(expected, await File.ReadAllTextAsync(path)); } + [Fact] + public async Task WriteNullValueTest() + { + using var path = AutoDeletingPath.Create(ExcelType.Csv); + await _csvExporter.ExportAsync(path.FilePath, null!); + Assert.Equal("", File.ReadAllText(path.FilePath)); + } + [Fact] public async Task SaveAsByDictionary() { @@ -361,8 +369,67 @@ static async IAsyncEnumerable GetValues() Assert.Equal("A2", results[1].C1); Assert.Equal("B2", results[1].C2); } - - [Fact] + + [Fact] + public async Task AppendToCsvTest() + { + using var file = AutoDeletingPath.Create(ExcelType.Csv); + var path = file.ToString(); + + { + var value = new[] + { + new { ID = 1, Name = "Jack", InDate = new DateTime(2021,01,03) }, + new { ID = 2, Name = "Henry", InDate = new DateTime(2020,05,03) }, + }; + await _csvExporter.AppendAsync(path, value); + + var content = await File.ReadAllTextAsync(path); + Assert.Equal( + """ + ID,Name,InDate + 1,Jack,"2021-01-03 00:00:00" + 2,Henry,"2020-05-03 00:00:00" + + """, content); + } + { + var value = new { ID = 3, Name = "Mike", InDate = new DateTime(2021, 04, 23) }; + await _csvExporter.AppendAsync(path, value); + + var content = await File.ReadAllTextAsync(path); + Assert.Equal( + """ + ID,Name,InDate + 1,Jack,"2021-01-03 00:00:00" + 2,Henry,"2020-05-03 00:00:00" + 3,Mike,"2021-04-23 00:00:00" + + """, content); + } + { + var value = new[] + { + new { ID = 4, Name = "Frank", InDate = new DateTime(2021,06,07) }, + new { ID = 5, Name = "Gloria", InDate = new DateTime(2022,05,03) } + }; + await _csvExporter.AppendAsync(path, value); + + var content = await File.ReadAllTextAsync(path); + Assert.Equal( + """ + ID,Name,InDate + 1,Jack,"2021-01-03 00:00:00" + 2,Henry,"2020-05-03 00:00:00" + 3,Mike,"2021-04-23 00:00:00" + 4,Frank,"2021-06-07 00:00:00" + 5,Gloria,"2022-05-03 00:00:00" + + """, content); + } + } + + [Fact] public async Task ExportDataTableWithProgressTest() { var dataTable = new DataTable(); @@ -409,4 +476,21 @@ public async Task ExportDataTableWithProgressTest() } } } + + [Fact] + public async Task GetColumnNamesTest() + { + var path = PathHelper.GetFile(@"csv/TestHeader.csv"); + var cols = (await _csvImporter.GetColumnNamesAsync(path, true)).ToArray(); + Assert.Equal("Column1", cols[0]); + Assert.Equal("Column2", cols[1]); + } + + [Fact] + public async Task GetColumnNamesEmptyTest() + { + await using var ms = new MemoryStream(); + var cols = await _csvImporter.GetColumnNamesAsync(ms); + Assert.Empty(cols); + } } diff --git a/tests/MiniExcel.Csv.Tests/Main/MiniExcelCsvTests.cs b/tests/MiniExcel.Csv.Tests/Main/MiniExcelCsvTests.cs index 1d017a98..7242000d 100644 --- a/tests/MiniExcel.Csv.Tests/Main/MiniExcelCsvTests.cs +++ b/tests/MiniExcel.Csv.Tests/Main/MiniExcelCsvTests.cs @@ -296,20 +296,12 @@ public void SaveAsByDataTableTest() } } - - private class Test - { - public string? C1 { get; set; } - public string? C2 { get; set; } - } - - private class TestWithAlias + [Fact] + public void WriteNullValueTest() { - [MiniExcelColumnName(columnName: "c1", aliases: ["column1", "col1"])] - public string? C1 { get; set; } - - [MiniExcelColumnName(columnName: "c2", aliases: ["column2", "col2"])] - public string? C2 { get; set; } + using var path = AutoDeletingPath.Create(ExcelType.Csv); + _csvExporter.Export(path.FilePath, null!); + Assert.Equal("", File.ReadAllText(path.FilePath)); } [Fact] @@ -382,7 +374,7 @@ public void CsvTypeMappingTest() using (var stream = File.OpenRead(path)) { - var rows = _csvImporter.Query(stream).ToList(); + var rows = _csvImporter.Query(stream).ToList(); Assert.Equal("A1", rows[0].C1); Assert.Equal("B1", rows[0].C2); Assert.Equal("A2", rows[1].C1); @@ -390,7 +382,7 @@ public void CsvTypeMappingTest() } { - var rows = _csvImporter.Query(path).ToList(); + var rows = _csvImporter.Query(path).ToList(); Assert.Equal("A1", rows[0].C1); Assert.Equal("B1", rows[0].C2); Assert.Equal("A2", rows[1].C1); @@ -408,7 +400,7 @@ public void CsvColumnNotFoundTest() using (var stream = File.OpenRead(path)) { - var exception = Assert.Throws(() => _csvImporter.Query(stream).ToList()); + var exception = Assert.Throws(() => _csvImporter.Query(stream).ToList()); Assert.Equal("c2", exception.ColumnName); Assert.Equal(2, exception.RowIndex); @@ -418,7 +410,7 @@ public void CsvColumnNotFoundTest() } { - var exception = Assert.Throws(() => _csvImporter.Query(path).ToList()); + var exception = Assert.Throws(() => _csvImporter.Query(path).ToList()); Assert.Equal("c2", exception.ColumnName); Assert.Equal(2, exception.RowIndex); @@ -504,7 +496,7 @@ private static string MiniExcelGenerateCsv(string value) [Fact] - public async Task InsertCsvTest() + public void AppendToCsvTest() { using var file = AutoDeletingPath.Create(ExcelType.Csv); var path = file.ToString(); @@ -512,11 +504,12 @@ public async Task InsertCsvTest() { var value = new[] { - new { ID=1,Name ="Jack",InDate=new DateTime(2021,01,03)}, - new { ID=2,Name ="Henry",InDate=new DateTime(2020,05,03)}, + new { ID = 1, Name = "Jack", InDate = new DateTime(2021,01,03) }, + new { ID = 2, Name = "Henry", InDate = new DateTime(2020,05,03) } }; - await _csvExporter.ExportAsync(path, value); - var content = await File.ReadAllTextAsync(path); + _csvExporter.Append(path, value); + + var content = File.ReadAllText(path); Assert.Equal( """ ID,Name,InDate @@ -527,8 +520,9 @@ public async Task InsertCsvTest() } { var value = new { ID = 3, Name = "Mike", InDate = new DateTime(2021, 04, 23) }; - await _csvExporter.AppendAsync(path, value); - var content = await File.ReadAllTextAsync(path); + _csvExporter.Append(path, value); + + var content = File.ReadAllText(path); Assert.Equal( """ ID,Name,InDate @@ -541,12 +535,13 @@ public async Task InsertCsvTest() { var value = new[] { - new { ID=4,Name ="Frank",InDate=new DateTime(2021,06,07)}, - new { ID=5,Name ="Gloria",InDate=new DateTime(2022,05,03)}, + new { ID = 4, Name = "Frank", InDate = new DateTime(2021,06,07) }, + new { ID = 5, Name = "Gloria", InDate = new DateTime(2022,05,03) } }; - await _csvExporter.AppendAsync(path, value); - var content = await File.ReadAllTextAsync(path); + _csvExporter.Append(path, value); + + var content = File.ReadAllText(path); Assert.Equal( """ ID,Name,InDate @@ -560,18 +555,6 @@ public async Task InsertCsvTest() } } - private class CsvFieldMappingTest - { - [MiniExcelColumnName("Column1")] - public string Test1; - - [MiniExcelColumnName("Column2")] - public int Test2; - - [MiniExcelColumnIndex(0)] - public decimal Test; - } - [Fact] public void ExportAndQueryFieldsStrongMappingTest() { @@ -609,15 +592,6 @@ public void QueryFieldsAsDynamicTest() Assert.Contains("Test", first.Keys); } - private class MixedFieldPropertyTest - { - [MiniExcelColumnName("F1")] - public string Field1; - - [MiniExcelColumnName("P1")] - public string Prop1 { get; set; } - } - [Fact] public void ExportAndQueryMixedFieldAndPropertyTest() { @@ -636,14 +610,6 @@ public void ExportAndQueryMixedFieldAndPropertyTest() Assert.Contains("P1", first.Keys); } - private class CsvFieldsWithoutAttributeDemo - { - public string NotMappedField; - - [MiniExcelColumnName("Mapped")] - public string MappedField; - } - [Fact] public void ExportAndQueryFieldsWithoutAttributeTest() { @@ -661,4 +627,21 @@ public void ExportAndQueryFieldsWithoutAttributeTest() Assert.Contains("Mapped", first.Keys); Assert.DoesNotContain("NotMappedField", first.Keys); } + + [Fact] + public void GetColumnNamesTest() + { + var path = PathHelper.GetFile(@"csv/TestHeader.csv"); + var cols = _csvImporter.GetColumnNames(path, true).ToArray(); + Assert.Equal("Column1", cols[0]); + Assert.Equal("Column2", cols[1]); + } + + [Fact] + public void GetColumnNamesEmptyTest() + { + using var ms = new MemoryStream(); + var cols = _csvImporter.GetColumnNames(ms); + Assert.Empty(cols); + } } diff --git a/tests/MiniExcel.Csv.Tests/Main/Models.cs b/tests/MiniExcel.Csv.Tests/Main/Models.cs index 2e07a78d..46b4b425 100644 --- a/tests/MiniExcel.Csv.Tests/Main/Models.cs +++ b/tests/MiniExcel.Csv.Tests/Main/Models.cs @@ -5,3 +5,41 @@ internal class TestDto public string? C1 { get; set; } public string? C2 { get; set; } } + +internal class CsvFieldMappingTest +{ + [MiniExcelColumnName("Column1")] + public string Test1; + + [MiniExcelColumnName("Column2")] + public int Test2; + + [MiniExcelColumnIndex(0)] + public decimal Test; +} + +internal class MixedFieldPropertyTest +{ + [MiniExcelColumnName("F1")] + public string Field1; + + [MiniExcelColumnName("P1")] + public string Prop1 { get; set; } +} + +internal class CsvFieldsWithoutAttributeDemo +{ + public string NotMappedField; + + [MiniExcelColumnName("Mapped")] + public string MappedField; +} + +internal class TestWithAlias +{ + [MiniExcelColumnName(columnName: "c1", aliases: ["column1", "col1"])] + public string? C1 { get; set; } + + [MiniExcelColumnName(columnName: "c2", aliases: ["column2", "col2"])] + public string? C2 { get; set; } +} \ No newline at end of file diff --git a/tests/MiniExcel.Csv.Tests/MiniExcel.Csv.Tests.csproj b/tests/MiniExcel.Csv.Tests/MiniExcel.Csv.Tests.csproj index e3901b2c..f061e911 100644 --- a/tests/MiniExcel.Csv.Tests/MiniExcel.Csv.Tests.csproj +++ b/tests/MiniExcel.Csv.Tests/MiniExcel.Csv.Tests.csproj @@ -1,7 +1,7 @@ - net10.0 + net8.0;net9.0;net10.0 enable enable $(NoWarn);IDE0017;IDE0034;IDE0037;IDE0039;IDE0042;IDE0044;IDE0051;IDE0052;IDE0059;IDE0060;IDE0063;IDE1006;xUnit1004;CA1806;CA1816;CA1822;CA1825;CA1849;CA2000;CA2007;CA2208 @@ -15,6 +15,7 @@ + diff --git a/tests/MiniExcel.OpenXml.Tests/AlterSheets/MiniExcelAlterSheetsTests.cs b/tests/MiniExcel.OpenXml.Tests/AlterSheets/MiniExcelAlterSheetsTests.cs index e76a37f9..cb69b531 100644 --- a/tests/MiniExcel.OpenXml.Tests/AlterSheets/MiniExcelAlterSheetsTests.cs +++ b/tests/MiniExcel.OpenXml.Tests/AlterSheets/MiniExcelAlterSheetsTests.cs @@ -1,4 +1,5 @@ using MiniExcelLib.OpenXml.Models; +using MiniExcelLib.Tests.Common.Utils; using static MiniExcelLib.OpenXml.Tests.Utils.SheetHelper; namespace MiniExcelLib.OpenXml.Tests.AlterSheets; @@ -102,14 +103,19 @@ public void AlterSheet_WhenNoOptionalParametersProvided_LeavesSheetUnchanged() { // Arrange const string targetSheet = "Sheet1"; - using var stream = CreateTestWorkbookStream(); + using var path = AutoDeletingPath.Create(); + using (var stream = CreateTestWorkbookStream()) + { + using var fs = File.OpenWrite(path.FilePath); + stream.Position = 0; + stream.CopyTo(fs); + } // Act - _excelExporter.AlterSheet(stream, targetSheet); + _excelExporter.AlterSheet(path.FilePath, targetSheet); // Assert - stream.Position = 0; - using var package = new ExcelPackage(stream); + using var package = new ExcelPackage(path.FilePath); var sheet = package.Workbook.Worksheets[targetSheet]; // Ensure defaults remain intact @@ -117,4 +123,4 @@ public void AlterSheet_WhenNoOptionalParametersProvided_LeavesSheetUnchanged() Assert.Equal("Sheet1", package.Workbook.Worksheets[0].Name); Assert.Equal(eWorkSheetHidden.Visible, sheet.Hidden); } -} \ No newline at end of file +} diff --git a/tests/MiniExcel.OpenXml.Tests/AlterSheets/MiniExcelAlterSheetsTestsAsync.cs b/tests/MiniExcel.OpenXml.Tests/AlterSheets/MiniExcelAlterSheetsTestsAsync.cs index 091b5199..30d1061c 100644 --- a/tests/MiniExcel.OpenXml.Tests/AlterSheets/MiniExcelAlterSheetsTestsAsync.cs +++ b/tests/MiniExcel.OpenXml.Tests/AlterSheets/MiniExcelAlterSheetsTestsAsync.cs @@ -1,4 +1,5 @@ using MiniExcelLib.OpenXml.Models; +using MiniExcelLib.Tests.Common.Utils; using static MiniExcelLib.OpenXml.Tests.Utils.SheetHelper; namespace MiniExcelLib.OpenXml.Tests.AlterSheets; @@ -102,14 +103,19 @@ public async Task AlterSheetAsync_WhenNoOptionalParametersProvided_LeavesSheetUn { // Arrange const string targetSheet = "Sheet1"; - await using var stream = CreateTestWorkbookStream(); + using var path = AutoDeletingPath.Create(); + using (var stream = CreateTestWorkbookStream()) + { + await using var fs = File.OpenWrite(path.FilePath); + stream.Position = 0; + await stream.CopyToAsync(fs); + } // Act - await _excelExporter.AlterSheetAsync(stream, targetSheet); + await _excelExporter.AlterSheetAsync(path.FilePath, targetSheet); // Assert - stream.Position = 0; - using var package = new ExcelPackage(stream); + using var package = new ExcelPackage(path.FilePath); var sheet = package.Workbook.Worksheets[targetSheet]; // Ensure defaults remain intact diff --git a/tests/MiniExcel.OpenXml.Tests/DataReader/OpenXmlDataReaderAsyncTests.cs b/tests/MiniExcel.OpenXml.Tests/DataReader/OpenXmlDataReaderAsyncTests.cs index 9bd124ce..f74b6c04 100644 --- a/tests/MiniExcel.OpenXml.Tests/DataReader/OpenXmlDataReaderAsyncTests.cs +++ b/tests/MiniExcel.OpenXml.Tests/DataReader/OpenXmlDataReaderAsyncTests.cs @@ -26,12 +26,16 @@ public async Task GetDataReader_WithSimpleData_ReturnsValidDataReader() Assert.Equal("VIP", reader.GetName(4)); Assert.Equal("Mail", reader.GetName(5)); Assert.Equal("Points", reader.GetName(6)); - + Assert.True(await reader.ReadAsync()); Assert.Equal(Guid.Parse("78DE23D2-DCB6-BD3D-EC67-C112BBC322A2"), reader.GetGuid(0)); + Assert.False(reader.IsDBNull(1)); Assert.Equal("Wade", reader.GetString(1)); Assert.Equal(new DateTime(2020, 9, 27), reader.GetDateTime(2)); + Assert.Equal(36, reader.GetByte(3)); + Assert.Equal(36, reader.GetInt16(3)); Assert.Equal(36, reader.GetInt32(3)); + Assert.Equal(36, reader.GetInt64(3)); Assert.False(reader.GetBoolean(4)); Assert.Equal(5019.12, reader.GetDouble(6)); } @@ -153,4 +157,35 @@ public async Task GetDataReader_WithMultipleSheets_ReadsAllSheets() Assert.Equal(3d, reader.GetValue(0)); Assert.False(await reader.NextResultAsync()); } + + [Fact] + public async Task GetDataReader_ReadSyncFromAsync_ThrowsException() + { + var path = PathHelper.GetFile("xlsx/TestTypeMapping.xlsx"); + using var stream = File.OpenRead(path); + await using var reader = await _excelImporter.GetAsyncDataReader(stream, hasHeaderRow: true); + + Assert.Throws(() => reader.Read()); + } + + [Fact] + public async Task GetDataReader_ReadAsyncFromSync_ReadsCorrectly() + { + var path = PathHelper.GetFile("xlsx/TestTypeMapping.xlsx"); + using var stream = File.OpenRead(path); + using var reader = _excelImporter.GetDataReader(stream, hasHeaderRow: true); + + Assert.True(await reader.ReadAsync()); + Assert.Equal("Wade", reader.GetString(1)); + } + + [Fact] + public async Task GetDataReader_ReadingAfterDispose_ThrowsException() + { + var path = PathHelper.GetFile("xlsx/TestMultiSheet.xlsx"); + var reader = await _excelImporter.GetAsyncDataReader(path); + + await reader.DisposeAsync(); + await Assert.ThrowsAsync(async () => await reader.ReadAsync()); + } } diff --git a/tests/MiniExcel.OpenXml.Tests/DataReader/OpenXmlDataReaderTests.cs b/tests/MiniExcel.OpenXml.Tests/DataReader/OpenXmlDataReaderTests.cs index 2e8f907f..88b07c91 100644 --- a/tests/MiniExcel.OpenXml.Tests/DataReader/OpenXmlDataReaderTests.cs +++ b/tests/MiniExcel.OpenXml.Tests/DataReader/OpenXmlDataReaderTests.cs @@ -29,11 +29,16 @@ public void GetDataReader_WithSimpleData_ReturnsValidDataReader() Assert.True(reader.Read()); Assert.Equal(Guid.Parse("78DE23D2-DCB6-BD3D-EC67-C112BBC322A2"), reader.GetGuid(0)); + Assert.False(reader.IsDBNull(1)); Assert.Equal("Wade", reader.GetString(1)); Assert.Equal(new DateTime(2020, 9, 27), reader.GetDateTime(2)); + Assert.Equal(36, reader.GetInt16(3)); Assert.Equal(36, reader.GetInt32(3)); + Assert.Equal(36, reader.GetInt64(3)); Assert.False(reader.GetBoolean(4)); - Assert.Equal(5019.12, reader.GetDouble(6)); + Assert.Equal(5019.12f, reader.GetFloat(6)); + Assert.Equal(5019.12d, reader.GetDouble(6)); + Assert.Equal(5019.12m, reader.GetDecimal(6)); } [Fact] @@ -153,4 +158,14 @@ public void GetDataReader_WithMultipleSheets_ReadsAllSheets() Assert.Equal(3d, reader.GetValue(0)); Assert.False(reader.NextResult()); } + + [Fact] + public void GetDataReader_ReadingAfterDispose_ThrowsException() + { + var path = PathHelper.GetFile("xlsx/TestMultiSheet.xlsx"); + var reader = _excelImporter.GetDataReader(path); + + reader.Dispose(); + Assert.Throws(() => reader.Read()); + } } diff --git a/tests/MiniExcel.OpenXml.Tests/Issues/MiniExcelGithubIssuesTests.cs b/tests/MiniExcel.OpenXml.Tests/Issues/MiniExcelGithubIssuesTests.cs index 432b2041..e2231c67 100644 --- a/tests/MiniExcel.OpenXml.Tests/Issues/MiniExcelGithubIssuesTests.cs +++ b/tests/MiniExcel.OpenXml.Tests/Issues/MiniExcelGithubIssuesTests.cs @@ -2460,7 +2460,6 @@ public void Issue_732_First_Sheet_Active() public void TestIssue750() { var templatePath = PathHelper.GetFile("xlsx/TestIssue20250403_SaveAsByTemplate_OPT.xlsx"); - var memoryBefore = GC.GetTotalMemory(true); using var path = AutoDeletingPath.Create(); var data = new Dictionary @@ -2468,7 +2467,10 @@ public void TestIssue750() ["list"] = Enumerable.Range(0, 10_000) .Select(_ => new { value1 = Guid.NewGuid(), value2 = Guid.NewGuid(), }) }; + + var memoryBefore = GC.GetTotalMemory(true); _excelTemplater.FillTemplate(path.ToString(), templatePath, data); + var memoryAfter = GC.GetTotalMemory(true); var rows = _excelImporter.Query(path.ToString()) .Skip(1453) @@ -2477,10 +2479,8 @@ public void TestIssue750() Assert.True(((string)rows[0].A).Length > 9); - var memoryAfter = GC.GetTotalMemory(true); var memoryIncrease = memoryAfter - memoryBefore; - - _output.WriteLine($"memoryIncrease: {memoryIncrease}"); + _output.WriteLine($"memoryIncrease: {memoryIncrease / 1024} KB"); } [Fact] diff --git a/tests/MiniExcel.OpenXml.Tests/Main/MiniExcelOpenXmlAsyncTests.cs b/tests/MiniExcel.OpenXml.Tests/Main/MiniExcelOpenXmlExporterAsyncTests.cs similarity index 76% rename from tests/MiniExcel.OpenXml.Tests/Main/MiniExcelOpenXmlAsyncTests.cs rename to tests/MiniExcel.OpenXml.Tests/Main/MiniExcelOpenXmlExporterAsyncTests.cs index db5f388f..cc7186e2 100644 --- a/tests/MiniExcel.OpenXml.Tests/Main/MiniExcelOpenXmlAsyncTests.cs +++ b/tests/MiniExcel.OpenXml.Tests/Main/MiniExcelOpenXmlExporterAsyncTests.cs @@ -1,18 +1,16 @@ using ClosedXML.Excel; -using ExcelDataReader; -using MiniExcelLib.Core.Exceptions; using MiniExcelLib.OpenXml.Tests.Utils; using MiniExcelLib.Tests.Common; using MiniExcelLib.Tests.Common.Utils; namespace MiniExcelLib.OpenXml.Tests.Main; -public class MiniExcelOpenXmlAsyncTests +public class MiniExcelOpenXmlExporterAsyncTests { private readonly OpenXmlImporter _excelImporter = MiniExcel.Importers.GetOpenXmlImporter(); private readonly OpenXmlExporter _excelExporter = MiniExcel.Exporters.GetOpenXmlExporter(); - static MiniExcelOpenXmlAsyncTests() + static MiniExcelOpenXmlExporterAsyncTests() { ExcelPackage.LicenseContext = LicenseContext.NonCommercial; } @@ -40,43 +38,6 @@ public async Task SaveAsControlChracter() var rows1 = await _excelImporter.QueryAsync(path).ToListAsync(); } - [Fact] - public async Task CustomAttributeWihoutVaildPropertiesTest() - { - var path = PathHelper.GetFile("xlsx/TestCustomExcelColumnAttribute.xlsx"); - await Assert.ThrowsAsync(async () => await _excelImporter.QueryAsync(path).ToListAsync()); - } - - [Fact] - public async Task QueryCustomAttributesTest() - { - var path = PathHelper.GetFile("xlsx/TestCustomExcelColumnAttribute.xlsx"); - var rows = await _excelImporter.QueryAsync(path).ToListAsync(); - - Assert.Equal("Column1", rows[0].Test1); - Assert.Equal("Column2", rows[0].Test2); - Assert.Null(rows[0].Test3); - Assert.Equal("Test7", rows[0].Test4); - Assert.Null(rows[0].Test5); - Assert.Null(rows[0].Test6); - Assert.Equal("Test4", rows[0].Test7); - } - - [Fact] - public async Task QueryCustomAttributes2Test() - { - var path = PathHelper.GetFile("xlsx/TestCustomExcelColumnAttribute.xlsx"); - var rows = await _excelImporter.QueryAsync(path).ToListAsync(); - - Assert.Equal("Column1", rows[0].Test1); - Assert.Equal("Column2", rows[0].Test2); - Assert.Null(rows[0].Test3); - Assert.Equal("Test7", rows[0].Test4); - Assert.Null(rows[0].Test5); - Assert.Null(rows[0].Test6); - Assert.Equal("Test4", rows[0].Test7); - } - [Fact] public async Task SaveAsCustomAttributesTest() { @@ -129,254 +90,6 @@ public async Task SaveAsCustomAttributes2Test() Assert.Null(rows[0].Test6); } - [Fact] - public async Task QueryCastToIDictionary() - { - var path = PathHelper.GetFile("xlsx/TestCenterEmptyRow/TestCenterEmptyRow.xlsx"); - await foreach (IDictionary row in _excelImporter.QueryAsync(path)) - { - _ = row; - } - } - - [Fact] - public async Task CenterEmptyRowsQueryTest() - { - var path = PathHelper.GetFile("xlsx/TestCenterEmptyRow/TestCenterEmptyRow.xlsx"); - await using (var stream = File.OpenRead(path)) - { - var rows = await _excelImporter.QueryAsync(stream).Cast>().ToListAsync(); - Assert.Equal("a", rows[0]["A"]); - Assert.Equal("b", rows[0]["B"]); - Assert.Equal("c", rows[0]["C"]); - Assert.Equal("d", rows[0]["D"]); - - Assert.Equal(1d, rows[1]["A"]); - Assert.Null(rows[1]["B"]); - Assert.Equal(3d, rows[1]["C"]); - Assert.Null(rows[1]["D"]); - - Assert.Null(rows[2]["A"]); - Assert.Equal(2d, rows[2]["B"]); - Assert.Null(rows[2]["C"]); - Assert.Equal(4d, rows[2]["D"]); - - Assert.Null(rows[3]["A"]); - Assert.Null(rows[3]["B"]); - Assert.Null(rows[3]["C"]); - Assert.Null(rows[3]["D"]); - - Assert.Equal(1d, rows[4]["A"]); - Assert.Null(rows[4]["B"]); - Assert.Equal(3d, rows[4]["C"]); - Assert.Null(rows[4]["D"]); - - Assert.Null(rows[5]["A"]); - Assert.Equal(2d, rows[5]["B"]); - Assert.Null(rows[5]["C"]); - Assert.Equal(4d, rows[5]["D"]); - - } - - await using (var stream = File.OpenRead(path)) - { - var rows = await _excelImporter.QueryAsync(stream, hasHeaderRow: true).Cast>().ToListAsync(); - Assert.Equal(1d, rows[0]["a"]); - Assert.Null(rows[0]["b"]); - Assert.Equal(3d, rows[0]["c"]); - Assert.Null(rows[0]["d"]); - - Assert.Null(rows[1]["a"]); - Assert.Equal(2d, rows[1]["b"]); - Assert.Null(rows[1]["c"]); - Assert.Equal(4d, rows[1]["d"]); - - Assert.Null(rows[2]["a"]); - Assert.Null(rows[2]["b"]); - Assert.Null(rows[2]["c"]); - Assert.Null(rows[2]["d"]); - - Assert.Equal(1d, rows[3]["a"]); - Assert.Null(rows[3]["b"]); - Assert.Equal(3d, rows[3]["c"]); - Assert.Null(rows[3]["d"]); - - Assert.Null(rows[4]["a"]); - Assert.Equal(2d, rows[4]["b"]); - Assert.Null(rows[4]["c"]); - Assert.Equal(4d, rows[4]["d"]); - } - } - - [Fact] - public async Task TestDynamicQueryBasic_WithoutHead() - { - var path = PathHelper.GetFile("xlsx/TestDynamicQueryBasic_WithoutHead.xlsx"); - await using var stream = File.OpenRead(path); - var rows = await _excelImporter.QueryAsync(stream).Cast>().ToListAsync(); - - Assert.Equal("MiniExcel", rows[0]["A"]); - Assert.Equal(1d, rows[0]["B"]); - Assert.Equal("Github", rows[1]["A"]); - Assert.Equal(2d, rows[1]["B"]); - } - - [Fact] - public async Task TestDynamicQueryBasic_useHeaderRow() - { - var path = PathHelper.GetFile("xlsx/TestDynamicQueryBasic.xlsx"); - await using (var stream = File.OpenRead(path)) - { - var rows = await _excelImporter.QueryAsync(stream, hasHeaderRow: true).Cast>().ToListAsync(); - Assert.Equal("MiniExcel", rows[0]["Column1"]); - Assert.Equal(1d, rows[0]["Column2"]); - Assert.Equal("Github", rows[1]["Column1"]); - Assert.Equal(2d, rows[1]["Column2"]); - } - - { - var rows = await _excelImporter.QueryAsync(path, hasHeaderRow: true).ToListAsync(); - Assert.Equal("MiniExcel", rows[0].Column1); - Assert.Equal(1d, rows[0].Column2); - Assert.Equal("Github", rows[1].Column1); - Assert.Equal(2d, rows[1].Column2); - } - } - - [Fact] - public async Task QueryStrongTypeMapping_Test() - { - var path = PathHelper.GetFile("xlsx/TestTypeMapping.xlsx"); - await using (var stream = File.OpenRead(path)) - { - var rows = await _excelImporter.QueryAsync(stream).ToListAsync(); - Assert.Equal(100, rows.Count); - - Assert.Equal(Guid.Parse("78DE23D2-DCB6-BD3D-EC67-C112BBC322A2"), rows[0].ID); - Assert.Equal("Wade", rows[0].Name); - Assert.Equal(DateTime.ParseExact("27/09/2020", "dd/MM/yyyy", CultureInfo.InvariantCulture), rows[0].BoD); - Assert.Equal(36, rows[0].Age); - Assert.False(rows[0].VIP); - Assert.Equal(5019.12m, rows[0].Points); - Assert.Equal(1, rows[0].IgnoredProperty); - } - - { - var rows = _excelImporter.Query(path, hasHeaderRow: true).ToList(); - Assert.Equal(100, rows.Count); - - Assert.Equal("78DE23D2-DCB6-BD3D-EC67-C112BBC322A2", rows[0].ID); - Assert.Equal("Wade", rows[0].Name); - Assert.Equal(new DateTime(2020, 9, 27), rows[0].BoD); - Assert.Equal(36, rows[0].Age); - Assert.False(rows[0].VIP); - Assert.Equal(5019.12d, rows[0].Points); - Assert.Null(rows[0].IgnoredProperty); - } - } - - [Fact] - public async Task AutoCheckTypeTest() - { - var path = PathHelper.GetFile("xlsx/TestTypeMapping_AutoCheckFormat.xlsx"); - await using var stream = FileHelper.OpenRead(path); - _ = _excelImporter.QueryAsync(stream).ToListAsync(); - } - - [Fact] - public async Task TestDatetimeSpanFormat_ClosedXml() - { - var path = PathHelper.GetFile("xlsx/TestDatetimeSpanFormat_ClosedXml.xlsx"); - await using var stream = FileHelper.OpenRead(path); - - var row = await _excelImporter.QueryAsync(stream).Cast>().FirstAsync(); - var a = row["A"]; - var b = row["B"]; - - Assert.Equal(DateTime.Parse("2021-03-20T23:39:42.3130000"), (DateTime)a); - Assert.Equal(TimeSpan.FromHours(10), (TimeSpan)b); - } - - [Fact] - public async Task LargeFileQueryStrongTypeMapping_Test() - { - const string path = "../../../../../benchmarks/MiniExcel.Benchmarks/Test1,000,000x10.xlsx"; - await using (var stream = File.OpenRead(path)) - { - var rows = await _excelImporter.QueryAsync(stream).Take(2).ToListAsync(); - Assert.Equal("HelloWorld2", rows[0].HelloWorld1); - Assert.Equal("HelloWorld3", rows[1].HelloWorld1); - } - { - var rows = await _excelImporter.QueryAsync(path).Take(2).ToListAsync(); - Assert.Equal("HelloWorld2", rows[0].HelloWorld1); - Assert.Equal("HelloWorld3", rows[1].HelloWorld1); - } - } - - [Theory] - [InlineData("../../../../data/xlsx/ExcelDataReaderCollections/TestChess.xlsx")] - [InlineData("../../../../data/xlsx/TestCenterEmptyRow/TestCenterEmptyRow.xlsx")] - public async Task QueryExcelDataReaderCheckTest(string path) - { - Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); - - await using var fs = File.OpenRead(path); - using var reader = ExcelReaderFactory.CreateReader(fs); - var exceldatareaderResult = reader.AsDataSet(); - await using var stream = File.OpenRead(path); - - var rows = await _excelImporter.QueryAsync(stream).ToListAsync(); - Assert.Equal(exceldatareaderResult.Tables[0].Rows.Count, rows.Count); - - foreach (IDictionary row in rows) - { - var rowIndex = rows.IndexOf(row); - foreach (var (key, value) in row) - { - var eV = exceldatareaderResult.Tables[0].Rows[rowIndex][SheetHelper.GetColumnIndex(key)]; - var v = value ?? DBNull.Value; - Assert.Equal(eV, v); - } - } - } - - [Fact] - public async Task QuerySheetWithoutRAttribute() - { - var path = PathHelper.GetFile("xlsx/TestWihoutRAttribute.xlsx"); - await using var stream = File.OpenRead(path); - - var rows = await _excelImporter.QueryAsync(stream).Cast>().ToListAsync(); - var keys = rows.First().Keys; - - Assert.Equal(2, rows.Count); - Assert.Equal(5, keys.Count); - - Assert.Equal(1d, rows[0]["A"]); - Assert.Null(rows[0]["C"]); - Assert.Null(rows[0]["D"]); - Assert.Null(rows[0]["E"]); - - Assert.Equal(1d, rows[1]["A"]); - Assert.Equal("\"<>+}{\\nHello World", rows[1]["B"]); - Assert.Equal(true, rows[1]["C"]); - Assert.Equal("2021-03-16T19:10:21", rows[1]["D"]); - } - - [Fact] - public async Task FixDimensionJustOneColumnParsingError_Test() - { - var path = PathHelper.GetFile("xlsx/TestDimensionC3.xlsx"); - await using var stream = File.OpenRead(path); - - var rows = await _excelImporter.QueryAsync(stream).ToListAsync(); - var keys = (rows.First() as IDictionary)?.Keys; - - Assert.Equal(3, keys?.Count); - Assert.Equal(2, rows.Count); - } - [Fact] public async Task SaveAsFileWithDimensionByICollection() { @@ -600,45 +313,36 @@ public async Task SaveAsByDataTableTest() } [Fact] - public async Task QueryByLINQExtensionsVoidTaskLargeFileOOMTest() + public async Task SaveEmptyDataReaderTest() { - const string path = "../../../../../benchmarks/MiniExcel.Benchmarks/Test1,000,000x10.xlsx"; - - { - var row = await _excelImporter.QueryAsync(path).FirstAsync(); - Assert.Equal("HelloWorld1", row.A); - } - - await using (var stream = File.OpenRead(path)) + using var path = AutoDeletingPath.Create(); + await using (var connection = Db.GetConnection()) { - var row = await _excelImporter.QueryAsync(stream).Cast>().FirstAsync(); - Assert.Equal("HelloWorld1", row["A"]); + var rows = await connection.QueryAsync("with cte as (select 1 id,2 val) select * from cte where 1=2"); + await _excelExporter.ExportAsync(path.ToString(), rows); } + await using (var stream = File.OpenRead(path.ToString())) { - var count = await _excelImporter.QueryAsync(path) - .Cast>() - .Take(10) - .CountAsync(); - - Assert.Equal(10, count); + var row = await _excelImporter.QueryAsync(stream, hasHeaderRow: true).ToListAsync(); + Assert.Empty(row); } } [Fact] - public async Task EmptyTest() + public async Task SaveEmptyAsyncEnumerableTest() { using var path = AutoDeletingPath.Create(); await using (var connection = Db.GetConnection()) { - var rows = await connection.QueryAsync("with cte as (select 1 id,2 val) select * from cte where 1=2"); - await _excelExporter.ExportAsync(path.ToString(), rows); + var data = Array.Empty().ToAsyncEnumerable(); + await _excelExporter.ExportAsync(path.ToString(), data); } await using (var stream = File.OpenRead(path.ToString())) { - var row = await _excelImporter.QueryAsync(stream, hasHeaderRow: true).ToListAsync(); - Assert.Empty(row); + var data = await _excelImporter.QueryAsync(stream).ToListAsync(); + Assert.Empty(data); } } @@ -735,7 +439,6 @@ public async Task SaveAsByIEnumerableIDictionaryWithDynamicConfiguration() Assert.Equal("A1:B3", SheetHelper.GetFirstSheetDimensionRefValue(path)); } - [Fact] public async Task SaveAsFrozenRowsAndColumnsTest() { @@ -862,48 +565,6 @@ public async Task SaveAsByDapperRows() } } - [Fact] - public async Task QueryByStrongTypeParameterTest() - { - using var file = AutoDeletingPath.Create(); - var path = file.ToString(); - List values = - [ - new() { Column1 = "MiniExcel", Column2 = 1 }, - new() { Column1 = "Github", Column2 = 2 } - ]; - await _excelExporter.ExportAsync(path, values); - - await using var stream = File.OpenRead(path); - var rows = await _excelImporter.QueryAsync(stream, hasHeaderRow: true).Cast>().ToListAsync(); - - Assert.Equal("MiniExcel", rows[0]["Column1"]); - Assert.Equal(1d, rows[0]["Column2"]); - Assert.Equal("Github", rows[1]["Column1"]); - Assert.Equal(2d, rows[1]["Column2"]); - } - - [Fact] - public async Task QueryByDictionaryStringAndObjectParameterTest() - { - using var file = AutoDeletingPath.Create(); - var path = file.ToString(); - List> values = - [ - new() { { "Column1", "MiniExcel" }, { "Column2", 1 } }, - new() { { "Column1", "Github" }, { "Column2", 2 } } - ]; - await _excelExporter.ExportAsync(path, values); - - await using var stream = File.OpenRead(path); - var rows = await _excelImporter.QueryAsync(stream, hasHeaderRow: true).Cast>().ToListAsync(); - - Assert.Equal("MiniExcel", rows[0]["Column1"]); - Assert.Equal(1d, rows[0]["Column2"]); - Assert.Equal("Github", rows[1]["Column1"]); - Assert.Equal(2d, rows[1]["Column2"]); - } - [Fact] public async Task SQLiteInsertTest() { @@ -1122,33 +783,6 @@ await _excelExporter.ExportAsync(path.ToString(), new[] Assert.Equal("application/vnd.openxmlformats-package.relationships+xml", allParts["/_rels/.rels"].ContentType); } - [Fact] - public async Task ReadBigExcel_TakeCancel_Throws_TaskCanceledException() - { - await Assert.ThrowsAsync(async () => - { - var path = PathHelper.GetFile("xlsx/bigExcel.xlsx"); - using var cts = new CancellationTokenSource(); - - await cts.CancelAsync(); - await using var stream = FileHelper.OpenRead(path); - _ = await _excelImporter.QueryAsync(stream, cancellationToken: cts.Token).ToListAsync(cts.Token); - }); - } - - [Fact] - public async Task ReadBigExcel_Processing_TakeCancel_Throws_TaskCanceledException() - { - await Assert.ThrowsAsync(async () => - { - var cts = new CancellationTokenSource(); - - var exportTask = _excelImporter.QueryAsync(PathHelper.GetFile("xlsx/bigExcel.xlsx"), cancellationToken: cts.Token).ToListAsync(cts.Token); - await cts.CancelAsync(); - await exportTask; - }); - } - [Fact] public async Task DynamicColumnsConfigurationIsUsedWhenCreatingExcelUsingIDataReader() { @@ -1273,7 +907,6 @@ public async Task DynamicColumnsConfigurationIsUsedWhenCreatingExcelUsingDataTab Assert.Contains("Its Date", rows[1]); Assert.Contains("Column4", rows[1]); - Assert.Equal("MiniExcel", rows[0]["Name of something"]); Assert.Equal(1D, rows[0]["Its value"]); Assert.Equal(dateTime, (DateTime)rows[0]["Its Date"], TimeSpan.FromMilliseconds(10d)); @@ -1438,6 +1071,119 @@ public async Task InsertSheetTest() } } + [Fact] + public async Task CopyAndInsertSheetTest() + { + var now = DateTime.Now; + var dt = new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second); + + using var firstFile = AutoDeletingPath.Create(); + using var secondFile = AutoDeletingPath.Create(); + using var thirdFile = AutoDeletingPath.Create(); + + var firstPath = firstFile.ToString(); + var secondPath = secondFile.ToString(); + var thirdPath = thirdFile.ToString(); + + { + using var table = new DataTable(); + table.Columns.Add("a", typeof(string)); + table.Columns.Add("b", typeof(decimal)); + table.Columns.Add("c", typeof(bool)); + table.Columns.Add("d", typeof(DateTime)); + table.Rows.Add(@"""<>+-*//}{\\n", 1234567890, true, dt); + table.Rows.Add("Hello World", -1234567890, false, dt.Date); + await _excelExporter.ExportAsync(firstPath, table); + } + { + using var table = new DataTable(); + table.Columns.Add("Column1", typeof(string)); + table.Columns.Add("Column2", typeof(int)); + table.Rows.Add("MiniExcel", 1); + table.Rows.Add("Github", 2); + + await _excelExporter.CopyAndAddSheetAsync(firstPath, secondPath, table, sheetName: "Sheet2"); + using var p = new ExcelPackage(secondPath); + var sheet2 = p.Workbook.Worksheets[1]; + + Assert.Equal("Column1", sheet2.Cells["A1"].Value.ToString()); + Assert.Equal("Column2", sheet2.Cells["B1"].Value.ToString()); + + Assert.Equal("MiniExcel", sheet2.Cells["A2"].Value.ToString()); + Assert.Equal(1, (double)sheet2.Cells["B2"].Value); + + Assert.Equal("Github", sheet2.Cells["A3"].Value.ToString()); + Assert.Equal(2, (double)sheet2.Cells["B3"].Value); + + Assert.Equal("Sheet2", sheet2.Name); + } + { + await _excelExporter.CopyAndAddSheetAsync(secondPath, thirdPath, new[] { new { Column1 = "Test", Column2 = dt } }, sheetName: "Sheet2", printHeader: false, configuration: new OpenXmlConfiguration + { + AutoFilter = false, + TableStyles = TableStyles.None, + DynamicColumns = + [ + new DynamicExcelColumn("Column2") + { + Name = "Date", + Index = 1, + Width = 150, + Format = "dd.mm.yyyy hh:mm:ss" + } + ] + }, overwriteSheet: true); + + using var p = new ExcelPackage(thirdPath); + var sheet2 = p.Workbook.Worksheets[1]; + + Assert.Equal("Sheet2", sheet2.Name); + Assert.Equal("Test", sheet2.Cells["A1"].Value); + Assert.Equal(dt.ToString("dd.MM.yyyy HH:mm:ss"), sheet2.Cells["B1"].Text ); + } + { + using var table = new DataTable(); + table.Columns.Add("Column1", typeof(string)); + table.Columns.Add("Column2", typeof(DateTime)); + table.Rows.Add("MiniExcel", dt); + table.Rows.Add("Github", dt); + await using var reader = table.CreateDataReader(); + + await using var fs = File.OpenRead(thirdPath); + await using var ms = new MemoryStream(); + + await _excelExporter.CopyAndAddSheetAsync(fs, ms, reader, sheetName: "Sheet3", configuration: new OpenXmlConfiguration + { + AutoFilter = false, + TableStyles = TableStyles.None, + DynamicColumns = + [ + new DynamicExcelColumn("Column2") + { + Name = "Date", + Index = 1, + Width = 150, + Format = "dd.mm.yyyy hh:mm:ss" + } + ] + }); + + using var p = new ExcelPackage(ms); + var sheet3 = p.Workbook.Worksheets[2]; + + Assert.Equal("Column1", sheet3.Cells["A1"].Value); + Assert.Equal("Date", sheet3.Cells["B1"].Value); + + Assert.Equal("MiniExcel", sheet3.Cells["A2"].Value); + Assert.Equal(dt.ToString("dd.MM.yyyy HH:mm:ss"), sheet3.Cells["B2"].Text); + + Assert.Equal("Github", sheet3.Cells["A3"].Value); + Assert.Equal(dt.ToString("dd.MM.yyyy HH:mm:ss"), sheet3.Cells["B3"].Text); + + Assert.Equal("Sheet3", sheet3.Name); + } + } + [Fact] public async Task SaveAsByAsyncEnumerable() { @@ -1696,24 +1442,6 @@ public async Task DateTimeFormattingWithMiniExcelFormatAttributeTest() static DateTime GetDateTime(object value) => DateTime.FromOADate((double)value); } - - [Fact] - public async Task InvalidSheetNameCharactersShouldThrow() - { - await using var ms1 = new MemoryStream(); - await Assert.ThrowsAsync(() => _excelExporter.ExportAsync(ms1, Array.Empty(), sheetName: "Sheet?")); - - await using var ms2 = new MemoryStream(); - await Assert.ThrowsAsync(() => _excelExporter.InsertSheetAsync(ms2, Array.Empty(), sheetName: "Sheet[]")); - - await using var ms3 = new MemoryStream(); - using var package = new ExcelPackage(ms3); - package.Workbook.Worksheets.Add("Sheet1"); - await package.SaveAsync(); - - ms1.Seek(0, SeekOrigin.Begin); - await Assert.ThrowsAsync(() => _excelExporter.AlterSheetAsync(ms3, "Sheet1", "Sheet*")); - } [Theory] [InlineData("")] @@ -1800,28 +1528,4 @@ await _excelExporter.ExportAsync( CultureInfo.CurrentUICulture = ogCulture; } } - - [Fact] - public async Task MultipleResultSets() - { - await using var stream =File.OpenRead(PathHelper.GetFile("xlsx/TestTypeMapping.xlsx")); - await using var dr = await OpenXmlDataReader.CreateAsync(stream, hasHeaderRow: true, leaveOpen: true); - await dr.ReadAsync(); - var v1 = dr.GetValue(0); - var nr = await dr.NextResultAsync(); - await dr.ReadAsync(); - var v2 = dr.GetValue(0); - } - - [Fact] - public void MultipleResultSets2() - { - using var stream = File.OpenRead(PathHelper.GetFile("xlsx/TestMultiSheet.xlsx")); - using var dr = OpenXmlDataReader.Create(stream, leaveOpen: true); - dr.Read(); - var v1 = dr.GetValue(0); - var nr = dr.NextResult(); - dr.Read(); - var v2 = dr.GetValue(0); - } } diff --git a/tests/MiniExcel.OpenXml.Tests/Main/MiniExcelOpenXmlTests.cs b/tests/MiniExcel.OpenXml.Tests/Main/MiniExcelOpenXmlExporterTests.cs similarity index 59% rename from tests/MiniExcel.OpenXml.Tests/Main/MiniExcelOpenXmlTests.cs rename to tests/MiniExcel.OpenXml.Tests/Main/MiniExcelOpenXmlExporterTests.cs index 913be34f..b3839e8c 100644 --- a/tests/MiniExcel.OpenXml.Tests/Main/MiniExcelOpenXmlTests.cs +++ b/tests/MiniExcel.OpenXml.Tests/Main/MiniExcelOpenXmlExporterTests.cs @@ -1,48 +1,22 @@ using ClosedXML.Excel; -using ExcelDataReader; -using MiniExcelLib.Core.Exceptions; using MiniExcelLib.OpenXml.Tests.Utils; using MiniExcelLib.Tests.Common; using MiniExcelLib.Tests.Common.Utils; -using FileHelper = MiniExcelLib.OpenXml.Tests.Utils.FileHelper; -using Path = System.IO.Path; namespace MiniExcelLib.OpenXml.Tests.Main; -public class MiniExcelOpenXmlTests(ITestOutputHelper output) +public class MiniExcelOpenXmlExporterTests(ITestOutputHelper output) { private readonly ITestOutputHelper _output = output; private readonly OpenXmlImporter _excelImporter = MiniExcel.Importers.GetOpenXmlImporter(); private readonly OpenXmlExporter _excelExporter = MiniExcel.Exporters.GetOpenXmlExporter(); - static MiniExcelOpenXmlTests() + static MiniExcelOpenXmlExporterTests() { ExcelPackage.LicenseContext = LicenseContext.NonCommercial; } - [Fact] - public void GetColumnsTest() - { - var tmPath = PathHelper.GetFile("xlsx/TestTypeMapping.xlsx"); - var tePath = PathHelper.GetFile("xlsx/TestEmpty.xlsx"); - - { - var columns = _excelImporter.GetColumnNames(tmPath); - Assert.Equal(["A", "B", "C", "D", "E", "F", "G", "H"], columns); - } - - { - var columns = _excelImporter.GetColumnNames(tmPath); - Assert.Equal(8, columns.Count); - } - - { - var columns = _excelImporter.GetColumnNames(tePath); - Assert.Empty(columns); - } - } - [Fact] public void SaveAsControlChracter() { @@ -60,30 +34,8 @@ public void SaveAsControlChracter() var input = chars.Select(s => new { Test = s.ToString() }); _excelExporter.Export(path.ToString(), input); - var rows2 = _excelImporter.Query(path.ToString(), true).Select(s => s.Test).ToArray(); - var rows1 = _excelImporter.Query(path.ToString()).Select(s => s.Test).ToArray(); - } - - [Fact] - public void CustomAttributeWihoutVaildPropertiesTest() - { - var path = PathHelper.GetFile("xlsx/TestCustomExcelColumnAttribute.xlsx"); - Assert.Throws(() => _excelImporter.Query(path).ToList()); - } - - [Fact] - public void QueryCustomAttributesTest() - { - var path = PathHelper.GetFile("xlsx/TestCustomExcelColumnAttribute.xlsx"); - var rows = _excelImporter.Query(path).ToList(); - - Assert.Equal("Column1", rows[0].Test1); - Assert.Equal("Column2", rows[0].Test2); - Assert.Null(rows[0].Test3); - Assert.Equal("Test7", rows[0].Test4); - Assert.Null(rows[0].Test5); - Assert.Null(rows[0].Test6); - Assert.Equal("Test4", rows[0].Test7); + var rows2 = _excelImporter.Query(path.ToString(), true).Select(s => s.Test).ToArray(); + var rows1 = _excelImporter.Query(path.ToString()).Select(s => s.Test).ToArray(); } [Fact] @@ -100,7 +52,7 @@ public void SaveAsCustomAttributesTest() }); _excelExporter.Export(path.ToString(), input); - var rows = _excelImporter.Query(path.ToString(), true).ToList(); + var rows = _excelImporter.Query(path.ToString(), true).ToList(); var first = rows[0] as IDictionary; Assert.Equal(3, rows.Count); @@ -112,347 +64,6 @@ public void SaveAsCustomAttributesTest() Assert.Null(rows[0].Test6); } - [Fact] - public void QueryCastToIDictionary() - { - var path = PathHelper.GetFile("xlsx/TestCenterEmptyRow/TestCenterEmptyRow.xlsx"); - foreach (IDictionary row in _excelImporter.Query(path)) - { - _ = row; - } - } - - [Fact] - public void QueryRangeToIDictionary() - { - var path = PathHelper.GetFile("xlsx/TestCenterEmptyRow/TestCenterEmptyRow.xlsx"); - // tips:Only uppercase letters are effective - var rows = _excelImporter.QueryRange(path, startCell: "A2", endCell: "C7") - .Cast>() - .ToList(); - - Assert.Equal(5, rows.Count); - Assert.Equal(3, rows[0].Count); - Assert.Equal(2d, rows[1]["B"]); - Assert.Equal(null!, rows[2]["A"]); - - rows = _excelImporter.QueryRange(path, startRowIndex: 2, startColumnIndex: 1, endRowIndex: 7, endColumnIndex: 3) - .Cast>() - .ToList(); - - Assert.Equal(5, rows.Count); - Assert.Equal(3, rows[0].Count); - Assert.Equal(2d, rows[1]["B"]); - Assert.Equal(null!, rows[2]["A"]); - - rows = _excelImporter.QueryRange(path, startRowIndex:2, startColumnIndex: 1, endRowIndex: 3) - .Cast>() - .ToList(); - Assert.Equal(2, rows.Count); - Assert.Equal(4, rows[0].Count); - Assert.Equal(4d, rows[1]["D"]); - - rows = _excelImporter.QueryRange(path, startRowIndex: 2, startColumnIndex: 1, endColumnIndex: 3) - .Cast>() - .ToList(); - Assert.Equal(5, rows.Count); - Assert.Equal(3, rows[0].Count); - Assert.Equal(3d, rows[3]["C"]); - } - - [Fact] - public void CenterEmptyRowsQueryTest() - { - var path = PathHelper.GetFile("xlsx/TestCenterEmptyRow/TestCenterEmptyRow.xlsx"); - using (var stream = File.OpenRead(path)) - { - var rows = _excelImporter.Query(stream).ToList(); - - Assert.Equal("a", rows[0].A); - Assert.Equal("b", rows[0].B); - Assert.Equal("c", rows[0].C); - Assert.Equal("d", rows[0].D); - - Assert.Equal(1, rows[1].A); - Assert.Null(rows[1].B); - Assert.Equal(3, rows[1].C); - Assert.Null(rows[1].D); - - Assert.Null(rows[2].A); - Assert.Equal(2, rows[2].B); - Assert.Null(rows[2].C); - Assert.Equal(4, rows[2].D); - - Assert.Null(rows[3].A); - Assert.Null(rows[3].B); - Assert.Null(rows[3].C); - Assert.Null(rows[3].D); - - Assert.Equal(1, rows[4].A); - Assert.Null(rows[4].B); - Assert.Equal(3, rows[4].C); - Assert.Null(rows[4].D); - - Assert.Null(rows[5].A); - Assert.Equal(2, rows[5].B); - Assert.Null(rows[5].C); - Assert.Equal(4, rows[5].D); - } - - using (var stream = File.OpenRead(path)) - { - var rows = _excelImporter.Query(stream, hasHeaderRow: true).ToList(); - - Assert.Equal(1, rows[0].a); - Assert.Null(rows[0].b); - Assert.Equal(3, rows[0].c); - Assert.Null(rows[0].d); - - Assert.Null(rows[1].a); - Assert.Equal(2, rows[1].b); - Assert.Null(rows[1].c); - Assert.Equal(4, rows[1].d); - - Assert.Null(rows[2].a); - Assert.Null(rows[2].b); - Assert.Null(rows[2].c); - Assert.Null(rows[2].d); - - Assert.Equal(1, rows[3].a); - Assert.Null(rows[3].b); - Assert.Equal(3, rows[3].c); - Assert.Null(rows[3].d); - - Assert.Null(rows[4].a); - Assert.Equal(2, rows[4].b); - Assert.Null(rows[4].c); - Assert.Equal(4, rows[4].d); - } - } - - [Fact] - public void TestEmptyRowsQuerySelfClosingTag() - { - var path = PathHelper.GetFile("xlsx/TestEmptySelfClosingRow.xlsx"); - using var stream = File.OpenRead(path); - var rows = _excelImporter.Query(stream).ToList(); - - Assert.Null(rows[0].A); - Assert.Equal(1, rows[1].A); - Assert.Null(rows[2].A); - Assert.Equal(2, rows[3].A); - Assert.Null(rows[4].A); - Assert.Null(rows[5].A); - Assert.Null(rows[6].A); - Assert.Null(rows[7].A); - Assert.Null(rows[8].A); - Assert.Equal(1, rows[9].A); - } - - [Fact] - public void TestDynamicQueryBasic_WithoutHead() - { - var path = PathHelper.GetFile("xlsx/TestDynamicQueryBasic_WithoutHead.xlsx"); - using var stream = File.OpenRead(path); - var rows = _excelImporter.Query(stream).ToList(); - - Assert.Equal("MiniExcel", rows[0].A); - Assert.Equal(1, rows[0].B); - Assert.Equal("Github", rows[1].A); - Assert.Equal(2, rows[1].B); - } - - [Fact] - public void TestDynamicQueryBasic_hasHeaderRow() - { - var path = PathHelper.GetFile("xlsx/TestDynamicQueryBasic.xlsx"); - using (var stream = File.OpenRead(path)) - { - var rows = _excelImporter.Query(stream, hasHeaderRow: true).ToList(); - - Assert.Equal("MiniExcel", rows[0].Column1); - Assert.Equal(1, rows[0].Column2); - Assert.Equal("Github", rows[1].Column1); - Assert.Equal(2, rows[1].Column2); - } - - { - var rows = _excelImporter.Query(path, hasHeaderRow: true).ToList(); - - Assert.Equal("MiniExcel", rows[0].Column1); - Assert.Equal(1, rows[0].Column2); - Assert.Equal("Github", rows[1].Column1); - Assert.Equal(2, rows[1].Column2); - } - } - - public class UserAccount - { - public Guid ID { get; set; } - public string? Name { get; set; } - public DateTime BoD { get; set; } - public int Age { get; set; } - public bool VIP { get; set; } - public decimal Points { get; set; } - public int IgnoredProperty => 1; - } - - [Fact] - public void QueryStrongTypeMapping_Test() - { - var path = PathHelper.GetFile("xlsx/TestTypeMapping.xlsx"); - using (var stream = File.OpenRead(path)) - { - var rows = _excelImporter.Query(stream).ToList(); - Assert.Equal(100, rows.Count); - - Assert.Equal(Guid.Parse("78DE23D2-DCB6-BD3D-EC67-C112BBC322A2"), rows[0].ID); - Assert.Equal("Wade", rows[0].Name); - Assert.Equal(DateTime.ParseExact("27/09/2020", "dd/MM/yyyy", CultureInfo.InvariantCulture), rows[0].BoD); - Assert.Equal(36, rows[0].Age); - Assert.False(rows[0].VIP); - Assert.Equal(5019.12m, rows[0].Points); - Assert.Equal(1, rows[0].IgnoredProperty); - } - - { - var rows = _excelImporter.Query(path).ToList(); - Assert.Equal(100, rows.Count); - - Assert.Equal(Guid.Parse("78DE23D2-DCB6-BD3D-EC67-C112BBC322A2"), rows[0].ID); - Assert.Equal("Wade", rows[0].Name); - Assert.Equal(DateTime.ParseExact("27/09/2020", "dd/MM/yyyy", CultureInfo.InvariantCulture), rows[0].BoD); - Assert.Equal(36, rows[0].Age); - Assert.False(rows[0].VIP); - Assert.Equal(5019.12m, rows[0].Points); - Assert.Equal(1, rows[0].IgnoredProperty); - } - } - - [Fact] - public void AutoCheckTypeTest() - { - var path = PathHelper.GetFile("xlsx/TestTypeMapping_AutoCheckFormat.xlsx"); - using var stream = FileHelper.OpenRead(path); - var rows = _excelImporter.Query(stream).ToList(); - } - - [Fact] - public void UriMappingTest() - { - var path = PathHelper.GetFile("xlsx/TestUriMapping.xlsx"); - using var stream = File.OpenRead(path); - var rows = _excelImporter.Query(stream).ToList(); - - Assert.Equal("Felix", rows[1].Name); - Assert.Equal(44, rows[1].Age); - Assert.Equal(new Uri("https://friendly-utilization.net"), rows[1].Url); - } - - [Fact] - public void TrimColumnNamesTest() - { - var path = PathHelper.GetFile("xlsx/TestTrimColumnNames.xlsx"); - var rows = _excelImporter.Query(path).ToList(); - - Assert.Equal("Raymond", rows[4].Name); - Assert.Equal(18, rows[4].Age); - Assert.Equal("sagittis.lobortis@leoMorbi.com", rows[4].Mail); - Assert.Equal(8209.76m, rows[4].Points); - } - - [Fact] - public void TestDatetimeSpanFormat_ClosedXml() - { - var path = PathHelper.GetFile("xlsx/TestDatetimeSpanFormat_ClosedXml.xlsx"); - using var stream = FileHelper.OpenRead(path); - - var row = _excelImporter.Query(stream).First(); - var a = row.A; - var b = row.B; - Assert.Equal(DateTime.Parse("2021-03-20T23:39:42.3130000"), (DateTime)a); - Assert.Equal(TimeSpan.FromHours(10), (TimeSpan)b); - } - - [Fact] - public void LargeFileQueryStrongTypeMapping_Test() - { - const string path = "../../../../../benchmarks/MiniExcel.Benchmarks/Test1,000,000x10.xlsx"; - using (var stream = File.OpenRead(path)) - { - var rows = _excelImporter.Query(stream).Take(2).ToList(); - - Assert.Equal("HelloWorld2", rows[0].HelloWorld1); - Assert.Equal("HelloWorld3", rows[1].HelloWorld1); - } - { - var rows = _excelImporter.Query(path).Take(2).ToList(); - - Assert.Equal("HelloWorld2", rows[0].HelloWorld1); - Assert.Equal("HelloWorld3", rows[1].HelloWorld1); - } - } - - [Theory] - [InlineData("../../../../data/xlsx/ExcelDataReaderCollections/TestChess.xlsx")] - [InlineData("../../../../data/xlsx/TestCenterEmptyRow/TestCenterEmptyRow.xlsx")] - public void QueryDataReaderCheckTest(string path) - { - Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); - - using var fs = File.OpenRead(path); - using var reader = ExcelReaderFactory.CreateReader(fs); - var exceldatareaderResult = reader.AsDataSet(); - - using var stream = File.OpenRead(path); - var rows = _excelImporter.Query(stream).ToList(); - Assert.Equal(exceldatareaderResult.Tables[0].Rows.Count, rows.Count); - - foreach (IDictionary row in rows) - { - var rowIndex = rows.IndexOf(row); - foreach (var (key, value) in row) - { - var eV = exceldatareaderResult.Tables[0].Rows[rowIndex][SheetHelper.GetColumnIndex(key)]; - var v = value ?? DBNull.Value; - Assert.Equal(eV, v); - } - } - } - - [Fact] - public void QuerySheetWithoutRAttribute() - { - var path = PathHelper.GetFile("xlsx/TestWihoutRAttribute.xlsx"); - using var stream = File.OpenRead(path); - var rows = _excelImporter.Query(stream).ToList(); - var keys = (rows.First() as IDictionary)!.Keys; - - Assert.Equal(2, rows.Count); - Assert.Equal(5, keys.Count); - - Assert.Equal(1, rows[0].A); - Assert.Null(rows[0].C); - Assert.Null(rows[0].D); - Assert.Null(rows[0].E); - - Assert.Equal(1, rows[1].A); - Assert.Equal("\"<>+}{\\nHello World", rows[1].B); - Assert.Equal(true, rows[1].C); - Assert.Equal("2021-03-16T19:10:21", rows[1].D); - } - - [Fact] - public void FixDimensionJustOneColumnParsingError_Test() - { - var path = PathHelper.GetFile("xlsx/TestDimensionC3.xlsx"); - using var stream = File.OpenRead(path); - var rows = _excelImporter.Query(stream).ToList(); - var keys = ((IDictionary)rows.First()).Keys; - Assert.Equal(3, keys.Count); - Assert.Equal(2, rows.Count); - } - [Fact] public void SaveAsFileWithDimensionByICollection() { @@ -470,7 +81,7 @@ public void SaveAsFileWithDimensionByICollection() using (var stream = File.OpenRead(path)) { - var rows = _excelImporter.Query(stream, hasHeaderRow: false).ToList(); + var rows = _excelImporter.Query(stream, hasHeaderRow: false).ToList(); Assert.Equal(3, rows.Count); Assert.Equal("A", rows[0].A); Assert.Equal("A", rows[1].A); @@ -478,7 +89,7 @@ public void SaveAsFileWithDimensionByICollection() } using (var stream = File.OpenRead(path)) { - var rows = _excelImporter.Query(stream, hasHeaderRow: true).ToList(); + var rows = _excelImporter.Query(stream, hasHeaderRow: true).ToList(); Assert.Equal(2, rows.Count); Assert.Equal("A", rows[0].A); Assert.Equal("A", rows[1].A); @@ -499,7 +110,7 @@ public void SaveAsFileWithDimensionByICollection() { using (var stream = File.OpenRead(path)) { - var rows = _excelImporter.Query(stream, hasHeaderRow: false).ToList(); + var rows = _excelImporter.Query(stream, hasHeaderRow: false).ToList(); Assert.Empty(rows); } Assert.Equal("A1:B1", SheetHelper.GetFirstSheetDimensionRefValue(path)); @@ -508,7 +119,7 @@ public void SaveAsFileWithDimensionByICollection() _excelExporter.Export(path, values, overwriteFile: true); { using var stream = File.OpenRead(path); - var rows = _excelImporter.Query(stream, hasHeaderRow: false).ToList(); + var rows = _excelImporter.Query(stream, hasHeaderRow: false).ToList(); Assert.Single(rows); } Assert.Equal("A1:B1", SheetHelper.GetFirstSheetDimensionRefValue(path)); @@ -527,7 +138,7 @@ public void SaveAsFileWithDimensionByICollection() { using (var stream = File.OpenRead(path)) { - var rows = _excelImporter.Query(stream, hasHeaderRow: false).ToList(); + var rows = _excelImporter.Query(stream, hasHeaderRow: false).ToList(); Assert.Equal(3, rows.Count); Assert.Equal("A", rows[0].A); Assert.Equal("A", rows[1].A); @@ -535,7 +146,7 @@ public void SaveAsFileWithDimensionByICollection() } using (var stream = File.OpenRead(path)) { - var rows = _excelImporter.Query(stream, hasHeaderRow: true).ToList(); + var rows = _excelImporter.Query(stream, hasHeaderRow: true).ToList(); Assert.Equal(2, rows.Count); Assert.Equal("A", rows[0].A); Assert.Equal("A", rows[1].A); @@ -665,24 +276,6 @@ public void SaveAsByDataTableTest() } } - [Fact] - public void QueryByLINQExtensionsAvoidLargeFileOOMTest() - { - const string path = "../../../../../benchmarks/MiniExcel.Benchmarks/Test1,000,000x10.xlsx"; - - var query1 = _excelImporter.Query(path).First(); - Assert.Equal("HelloWorld1", query1.A); - - using (var stream = File.OpenRead(path)) - { - var query2 = _excelImporter.Query(stream).First(); - Assert.Equal("HelloWorld1", query2.A); - } - - var query3 = _excelImporter.Query(path).Take(10); - Assert.Equal(10, query3.Count()); - } - [Fact] public void EmptyTest() { @@ -694,7 +287,7 @@ public void EmptyTest() } using (var stream = File.OpenRead(path.ToString())) { - var rows = _excelImporter.Query(stream, hasHeaderRow: true).ToList(); + var rows = _excelImporter.Query(stream, hasHeaderRow: true).ToList(); Assert.Empty(rows); } } @@ -720,7 +313,7 @@ public void SaveAsByIEnumerableIDictionary() using (var stream = File.OpenRead(path)) { - var rows = _excelImporter.Query(stream, hasHeaderRow: false, leaveOpen: true).ToList(); + var rows = _excelImporter.Query(stream, hasHeaderRow: false, leaveOpen: true).ToList(); Assert.Equal("Column1", rows[0].A); Assert.Equal("Column2", rows[0].B); Assert.Equal("MiniExcel", rows[1].A); @@ -788,7 +381,7 @@ public void SaveAsFrozenRowsAndColumnsTest() using (var stream = File.OpenRead(path.ToString())) { - var rows = _excelImporter.Query(stream, hasHeaderRow: true).ToList(); + var rows = _excelImporter.Query(stream, hasHeaderRow: true).ToList(); Assert.Equal("MiniExcel", rows[0].Column1); Assert.Equal(1, rows[0].Column2); @@ -836,7 +429,7 @@ public void SaveAsByDapperRows() using (var stream = File.OpenRead(path)) { - var rows = _excelImporter.Query(stream, hasHeaderRow: true).ToList(); + var rows = _excelImporter.Query(stream, hasHeaderRow: true).ToList(); Assert.Equal("MiniExcel", rows[0].Column1); Assert.Equal(1, rows[0].Column2); @@ -853,13 +446,13 @@ public void SaveAsByDapperRows() using (var stream = File.OpenRead(path)) { - var rows = _excelImporter.Query(stream, hasHeaderRow: false).ToList(); + var rows = _excelImporter.Query(stream, hasHeaderRow: false).ToList(); Assert.Empty(rows); } using (var stream = File.OpenRead(path)) { - var rows = _excelImporter.Query(stream, hasHeaderRow: true).ToList(); + var rows = _excelImporter.Query(stream, hasHeaderRow: true).ToList(); Assert.Empty(rows); } @@ -876,7 +469,7 @@ public void SaveAsByDapperRows() using (var stream = File.OpenRead(path)) { - var rows = _excelImporter.Query(stream, hasHeaderRow: false).ToList(); + var rows = _excelImporter.Query(stream, hasHeaderRow: false).ToList(); Assert.Equal("Column1", rows[0].A); Assert.Equal("Column2", rows[0].B); @@ -888,7 +481,7 @@ public void SaveAsByDapperRows() using (var stream = File.OpenRead(path)) { - var rows = _excelImporter.Query(stream, hasHeaderRow: true).ToList(); + var rows = _excelImporter.Query(stream, hasHeaderRow: true).ToList(); Assert.Equal("MiniExcel", rows[0].Column1); Assert.Equal(1, rows[0].Column2); @@ -897,51 +490,6 @@ public void SaveAsByDapperRows() } } - private class Demo - { - public string? Column1 { get; set; } - public decimal Column2 { get; set; } - } - [Fact] - public void QueryByStrongTypeParameterTest() - { - using var path = AutoDeletingPath.Create(); - List values = - [ - new() { Column1 = "MiniExcel", Column2 = 1 }, - new() { Column1 = "Github", Column2 = 2 } - ]; - _excelExporter.Export(path.ToString(), values); - - using var stream = File.OpenRead(path.ToString()); - var rows = _excelImporter.Query(stream, hasHeaderRow: true).ToList(); - - Assert.Equal("MiniExcel", rows[0].Column1); - Assert.Equal(1, rows[0].Column2); - Assert.Equal("Github", rows[1].Column1); - Assert.Equal(2, rows[1].Column2); - } - - [Fact] - public void QueryByDictionaryStringAndObjectParameterTest() - { - using var path = AutoDeletingPath.Create(); - List> values = - [ - new() { { "Column1", "MiniExcel" }, { "Column2", 1 } }, - new() { { "Column1", "Github" }, { "Column2", 2 } } - ]; - _excelExporter.Export(path.ToString(), values); - - using var stream = File.OpenRead(path.ToString()); - var rows = _excelImporter.Query(stream, hasHeaderRow: true).ToList(); - - Assert.Equal("MiniExcel", rows[0].Column1); - Assert.Equal(1, rows[0].Column2); - Assert.Equal("Github", rows[1].Column1); - Assert.Equal(2, rows[1].Column2); - } - [Fact] public void SQLiteInsertTest() { @@ -961,7 +509,7 @@ public void SQLiteInsertTest() using (var transaction = connection.BeginTransaction()) using (var stream = File.OpenRead(path)) { - var rows = _excelImporter.Query(stream); + var rows = _excelImporter.Query(stream); foreach (var row in rows) { _ = connection.Execute("insert into T (A,B) values (@A,@B)", new { row.A, row.B }, transaction: transaction); @@ -983,7 +531,7 @@ public void SaveAsBasicCreateTest() { using var path = AutoDeletingPath.Create(); - var rowsWritten = _excelExporter.Export(path.ToString(), new[] + var rowsWritten = _excelExporter.Export(path.ToString(), new[] { new { Column1 = "MiniExcel", Column2 = 1 }, new { Column1 = "Github", Column2 = 2} @@ -994,7 +542,7 @@ public void SaveAsBasicCreateTest() using (var stream = File.OpenRead(path.ToString())) { - var rows = _excelImporter.Query(stream, hasHeaderRow: true).ToList(); + var rows = _excelImporter.Query(stream, hasHeaderRow: true).ToList(); Assert.Equal("MiniExcel", rows[0].Column1); Assert.Equal(1, rows[0].Column2); @@ -1017,14 +565,14 @@ public void SaveAsBasicStreamTest() }; using (var stream = new FileStream(path.ToString(), FileMode.CreateNew)) { - var rowsWritten = _excelExporter.Export(stream, values); + var rowsWritten = _excelExporter.Export(stream, values); Assert.Single(rowsWritten); Assert.Equal(2, rowsWritten[0]); } using (var stream = File.OpenRead(path.ToString())) { - var rows = _excelImporter.Query(stream, hasHeaderRow: true).ToList(); + var rows = _excelImporter.Query(stream, hasHeaderRow: true).ToList(); Assert.Equal("MiniExcel", rows[0].Column1); Assert.Equal(1, rows[0].Column2); @@ -1042,7 +590,7 @@ public void SaveAsBasicStreamTest() using (var stream = new MemoryStream()) using (var fileStream = new FileStream(path.ToString(), FileMode.Create)) { - var rowsWritten = _excelExporter.Export(stream, values); + var rowsWritten = _excelExporter.Export(stream, values); stream.Seek(0, SeekOrigin.Begin); stream.CopyTo(fileStream); Assert.Single(rowsWritten); @@ -1051,7 +599,7 @@ public void SaveAsBasicStreamTest() using (var stream = File.OpenRead(path.ToString())) { - var rows = _excelImporter.Query(stream, hasHeaderRow: true).ToList(); + var rows = _excelImporter.Query(stream, hasHeaderRow: true).ToList(); Assert.Equal("MiniExcel", rows[0].Column1); Assert.Equal(1, rows[0].Column2); @@ -1065,7 +613,7 @@ public void SaveAsBasicStreamTest() public void SaveAsSpecialAndTypeCreateTest() { using var path = AutoDeletingPath.Create(); - var rowsWritten = _excelExporter.Export(path.ToString(), new[] + var rowsWritten = _excelExporter.Export(path.ToString(), new[] { new { a = @"""<>+-*//}{\\n", b = 1234567890, c = true, d = DateTime.Now }, new { a = "Hello World", b = -1234567890, c = false, d = DateTime.Now.Date } @@ -1082,7 +630,7 @@ public void SaveAsFileEpplusCanReadTest() { var now = new DateTime(2026, 6, 2, 15, 2, 33); using var path = AutoDeletingPath.Create(); - var rowsWritten = _excelExporter.Export(path.ToString(), new[] + var rowsWritten = _excelExporter.Export(path.ToString(), new[] { new { a = @"""<>+-*//}{\\n", b = 1234567890, c = true, d = now}, new { a = "Hello World", b = -1234567890, c = false, d = now.Date } @@ -1109,7 +657,7 @@ public void SavaAsClosedXmlCanReadTest() { var now = new DateTime(2026, 6, 2, 15, 3, 19); using var path = AutoDeletingPath.Create(); - var rowsWritten = _excelExporter.Export(path.ToString(), new[] + var rowsWritten = _excelExporter.Export(path.ToString(), new[] { new { a = @"""<>+-*//}{\\n", b = 1234567890, c = true, d = now }, new { a = "Hello World", b = -1234567890, c = false, d = now.Date } @@ -1139,7 +687,7 @@ public void ContentTypeUriContentTypeReadCheckTest() { var now = DateTime.Now; using var path = AutoDeletingPath.Create(); - var rowsWritten = _excelExporter.Export(path.ToString(), new[] + var rowsWritten = _excelExporter.Export(path.ToString(), new[] { new { a = @"""<>+-*//}{\\n", b = 1234567890, c = true, d= now }, new { a = "Hello World", b = -1234567890, c = false, d = now.Date } @@ -1159,49 +707,6 @@ public void ContentTypeUriContentTypeReadCheckTest() Assert.Equal("application/vnd.openxmlformats-package.relationships+xml", allParts["/_rels/.rels"].ContentType); } - [Fact] - public void TestStirctOpenXml() - { - var path = PathHelper.GetFile("xlsx/TestStrictOpenXml.xlsx"); - var columns = _excelImporter.GetColumnNames (path); - Assert.Equal(["A", "B", "C"], columns); - - var rows = _excelImporter.Query(path).ToList(); - Assert.Equal(rows[0].A, "title1"); - Assert.Equal(rows[0].B, "title2"); - Assert.Equal(rows[0].C, "title3"); - Assert.Equal(rows[1].A, "value1"); - Assert.Equal(rows[1].B, "value2"); - Assert.Equal(rows[1].C, "value3"); - } - - [Fact] - public void SharedStringCacheTest() - { - const string path = "../../../../../benchmarks/MiniExcel.Benchmarks/Test1,000,000x10_SharingStrings.xlsx"; - - var ts = Stopwatch.GetTimestamp(); - _ = _excelImporter.Query(path, configuration: new OpenXmlConfiguration { EnableSharedStringCache = true }).First(); - using var currentProcess = Process.GetCurrentProcess(); - var totalBytesOfMemoryUsed = currentProcess.WorkingSet64; - - _output.WriteLine("totalBytesOfMemoryUsed: " + totalBytesOfMemoryUsed); - _output.WriteLine("elapsedMilliseconds: " + Stopwatch.GetElapsedTime(ts).TotalMilliseconds); - } - - [Fact] - public void SharedStringNoCacheTest() - { - const string path = "../../../../../benchmarks/MiniExcel.Benchmarks/Test1,000,000x10_SharingStrings.xlsx"; - - var ts = Stopwatch.GetTimestamp(); - _ = _excelImporter.Query(path).First(); - using var currentProcess = Process.GetCurrentProcess(); - var totalBytesOfMemoryUsed = currentProcess.WorkingSet64; - _output.WriteLine("totalBytesOfMemoryUsed: " + totalBytesOfMemoryUsed); - _output.WriteLine("elapsedMilliseconds: " + Stopwatch.GetElapsedTime(ts).TotalMilliseconds); - } - [Fact] public void DynamicColumnsConfigurationIsUsedWhenCreatingExcelUsingIDataReader() { @@ -1246,7 +751,7 @@ public void DynamicColumnsConfigurationIsUsedWhenCreatingExcelUsingIDataReader() _excelExporter.Export(path.ToString(), reader, configuration: configuration); using var stream = File.OpenRead(path.ToString()); - var rows = _excelImporter.Query(stream, hasHeaderRow: true) + var rows = _excelImporter.Query(stream, hasHeaderRow: true) .Select(x => (IDictionary)x) .ToList(); @@ -1312,7 +817,7 @@ public void DynamicColumnsConfigurationIsUsedWhenCreatingExcelUsingDataTable() _excelExporter.Export(path.ToString(), table, configuration: configuration); using var stream = File.OpenRead(path.ToString()); - var rows = _excelImporter.Query(stream, hasHeaderRow: true) + var rows = _excelImporter.Query(stream, hasHeaderRow: true) .Select(x => (IDictionary)x) .ToList(); @@ -1467,141 +972,117 @@ public void InsertSheetTest() } } - private class DateOnlyTest - { - public DateOnly Date { get; set; } - [MiniExcelFormat("d.M.yyyy")] public DateOnly DateWithFormat { get; set; } - } - [Fact] - public void DateOnlySupportTest() + public void CopyAndInsertSheetTest() { - var query = _excelImporter.Query(PathHelper.GetFile("xlsx/TestDateOnlyMapping.xlsx")).ToList(); + var now = DateTime.Now; + var dt = new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second); - Assert.Equal(new DateOnly(2020, 9, 27), query[0].Date); - Assert.Equal(new DateOnly(2020, 10, 25), query[1].Date); - Assert.Equal(new DateOnly(2021, 10, 4), query[2].Date); + using var firstFile = AutoDeletingPath.Create(); + using var secondFile = AutoDeletingPath.Create(); + using var thirdFile = AutoDeletingPath.Create(); - Assert.Equal(new DateOnly(2020, 9, 27), query[0].DateWithFormat); - Assert.Equal(new DateOnly(2020, 10, 25), query[1].DateWithFormat); - Assert.Equal(new DateOnly(2020, 6, 1), query[7].DateWithFormat); - } + var firstPath = firstFile.ToString(); + var secondPath = secondFile.ToString(); + var thirdPath = thirdFile.ToString(); - [Fact] - public void SheetDimensionsTest() - { - var path1 = PathHelper.GetFile("xlsx/TestTypeMapping.xlsx"); - var dim1 = _excelImporter.GetSheetDimensions(path1); - Assert.Equal("A1", dim1[0].StartCell); - Assert.Equal("H101", dim1[0].EndCell); - Assert.Equal(101, dim1[0].Rows.Count); - Assert.Equal(8, dim1[0].Columns.Count); - Assert.Equal(1, dim1[0].Rows.StartIndex); - Assert.Equal(101, dim1[0].Rows.EndIndex); - Assert.Equal(1, dim1[0].Columns.StartIndex); - Assert.Equal(8, dim1[0].Columns.EndIndex); - - var path2 = PathHelper.GetFile("xlsx/TestNoDimension.xlsx"); - var dim2 = _excelImporter.GetSheetDimensions(path2); - Assert.Equal(101, dim2[0].Rows.Count); - Assert.Equal(7, dim2[0].Columns.Count); - Assert.Equal(1, dim2[0].Rows.StartIndex); - Assert.Equal(101, dim2[0].Rows.EndIndex); - Assert.Equal(1, dim2[0].Columns.StartIndex); - Assert.Equal(7, dim2[0].Columns.EndIndex); - } + { + using var table = new DataTable(); + table.Columns.Add("a", typeof(string)); + table.Columns.Add("b", typeof(decimal)); + table.Columns.Add("c", typeof(bool)); + table.Columns.Add("d", typeof(DateTime)); + table.Rows.Add(@"""<>+-*//}{\\n", 1234567890, true, dt); + table.Rows.Add("Hello World", -1234567890, false, dt.Date); + _excelExporter.Export(firstPath, table); + } + { + using var table = new DataTable(); + table.Columns.Add("Column1", typeof(string)); + table.Columns.Add("Column2", typeof(int)); + table.Rows.Add("MiniExcel", 1); + table.Rows.Add("Github", 2); - [Fact] - public void SheetDimensionsTest_MultiSheet() - { - var path = PathHelper.GetFile("xlsx/TestMultiSheet.xlsx"); - var dim = _excelImporter.GetSheetDimensions(path); - - Assert.Equal("A1", dim[0].StartCell); - Assert.Equal("D12", dim[0].EndCell); - Assert.Equal(12, dim[0].Rows.Count); - Assert.Equal(4, dim[0].Columns.Count); - Assert.Equal(1, dim[0].Rows.StartIndex); - Assert.Equal(12, dim[0].Rows.EndIndex); - Assert.Equal(1, dim[0].Columns.StartIndex); - Assert.Equal(4, dim[0].Columns.EndIndex); - - Assert.Equal("A1", dim[1].StartCell); - Assert.Equal("D12", dim[1].EndCell); - Assert.Equal(12, dim[1].Rows.Count); - Assert.Equal(4, dim[1].Columns.Count); - Assert.Equal(1, dim[1].Rows.StartIndex); - Assert.Equal(12, dim[1].Rows.EndIndex); - Assert.Equal(1, dim[1].Columns.StartIndex); - Assert.Equal(4, dim[1].Columns.EndIndex); - - Assert.Equal("A1", dim[2].StartCell); - Assert.Equal("B5", dim[2].EndCell); - Assert.Equal(5, dim[2].Rows.Count); - Assert.Equal(2, dim[2].Columns.Count); - Assert.Equal(1, dim[2].Rows.StartIndex); - Assert.Equal(5, dim[2].Rows.EndIndex); - Assert.Equal(1, dim[2].Columns.StartIndex); - Assert.Equal(2, dim[2].Columns.EndIndex); - } - - // todo: add more tests for fields mapping - private class ExcelFieldMappingTest - { - [MiniExcelColumnName("Column1")] - public string? Test1; + _excelExporter.CopyAndAddSheet(firstPath, secondPath, table, sheetName: "Sheet2"); + using var p = new ExcelPackage(secondPath); + var sheet2 = p.Workbook.Worksheets[1]; - [MiniExcelColumnName("Column2")] - public int Test2; + Assert.Equal("Column1", sheet2.Cells["A1"].Value.ToString()); + Assert.Equal("Column2", sheet2.Cells["B1"].Value.ToString()); - [MiniExcelColumnIndex(0)] - public decimal Test; - } + Assert.Equal("MiniExcel", sheet2.Cells["A2"].Value.ToString()); + Assert.Equal(1, (double)sheet2.Cells["B2"].Value); - [Fact] - public void ExportAndQueryFieldsStrongMappingTest() - { - using var path = AutoDeletingPath.Create(); - var input = Enumerable.Range(1, 3) - .Select(i => new ExcelFieldMappingTest + Assert.Equal("Github", sheet2.Cells["A3"].Value.ToString()); + Assert.Equal(2, (double)sheet2.Cells["B3"].Value); + + Assert.Equal("Sheet2", sheet2.Name); + } + { + _excelExporter.CopyAndAddSheet(secondPath, thirdPath, new[] { new { Column1 = "Test", Column2 = dt } }, sheetName: "Sheet2", printHeader: false, configuration: new OpenXmlConfiguration { - Test1 = $"T{i}", - Test2 = i, - Test = i + (decimal)i / 10 - }); + AutoFilter = false, + TableStyles = TableStyles.None, + DynamicColumns = + [ + new DynamicExcelColumn("Column2") + { + Name = "Date", + Index = 1, + Width = 150, + Format = "dd.mm.yyyy hh:mm:ss" + } + ] + }, overwriteSheet: true); - _excelExporter.Export(path.ToString(), input); + using var p = new ExcelPackage(thirdPath); + var sheet2 = p.Workbook.Worksheets[1]; - var rows = _excelImporter.Query(path.ToString()).ToList(); - Assert.Equal(3, rows.Count); - Assert.Equal("T1", rows[0].Test1); - Assert.Equal(1, rows[0].Test2); - Assert.Equal(1.1m, rows[0].Test); - } + Assert.Equal("Sheet2", sheet2.Name); + Assert.Equal("Test", sheet2.Cells["A1"].Value); + Assert.Equal(dt.ToString("dd.MM.yyyy HH:mm:ss"), sheet2.Cells["B1"].Text); + } + { + using var table = new DataTable(); + table.Columns.Add("Column1", typeof(string)); + table.Columns.Add("Column2", typeof(DateTime)); + table.Rows.Add("MiniExcel", dt); + table.Rows.Add("Github", dt); + using var reader = table.CreateDataReader(); - [Fact] - public void QueryFieldsAsDynamicTest() - { - using var path = AutoDeletingPath.Create(); - ExcelFieldMappingTest[] input = [new() { Test1 = "X1", Test2 = 5, Test = 7.3m }]; - - _excelExporter.Export(path.ToString(), input); + using var fs = File.OpenRead(thirdPath); + using var ms = new MemoryStream(); - var rows = _excelImporter.Query(path.ToString(), true).ToList(); - var first = rows[0] as IDictionary; + _excelExporter.CopyAndAddSheet(fs, ms, reader, sheetName: "Sheet3", configuration: new OpenXmlConfiguration + { + AutoFilter = false, + TableStyles = TableStyles.None, + DynamicColumns = + [ + new DynamicExcelColumn("Column2") + { + Name = "Date", + Index = 1, + Width = 150, + Format = "dd.mm.yyyy hh:mm:ss" + } + ] + }); - // Column headers should include the column names from field attributes - Assert.Contains("Column1", first!.Keys); - Assert.Contains("Column2", first.Keys); - } + using var p = new ExcelPackage(ms); + var sheet3 = p.Workbook.Worksheets[2]; - private class MixedFieldPropertyTest - { - [MiniExcelColumnName("F1")] - public string? Field1; + Assert.Equal("Column1", sheet3.Cells["A1"].Value); + Assert.Equal("Date", sheet3.Cells["B1"].Value); + + Assert.Equal("MiniExcel", sheet3.Cells["A2"].Value); + Assert.Equal(dt.ToString("dd.MM.yyyy HH:mm:ss"), sheet3.Cells["B2"].Text); + + Assert.Equal("Github", sheet3.Cells["A3"].Value); + Assert.Equal(dt.ToString("dd.MM.yyyy HH:mm:ss"), sheet3.Cells["B3"].Text); - [MiniExcelColumnName("P1")] - public string? Prop1 { get; set; } + Assert.Equal("Sheet3", sheet3.Name); + } } [Fact] @@ -1612,22 +1093,13 @@ public void ExportAndQueryMixedFieldAndPropertyTest() _excelExporter.Export(path.ToString(), input); - var rows = _excelImporter.Query(path.ToString(), true).ToList(); + var rows = _excelImporter.Query(path.ToString(), true).ToList(); var first = rows[0] as IDictionary; Assert.Contains("F1", first!.Keys); Assert.Contains("P1", first.Keys); } - private class FieldsWithoutAttributeTest - { - // field without attribute should not be included for export - public string? NotMappedField; - - [MiniExcelColumnName("Mapped")] - public string? MappedField; - } - [Fact] public void ExportAndQueryFieldsWithoutAttributeTest() { @@ -1658,21 +1130,6 @@ public async Task InvalidSheetNameCharactersShouldThrow() ms1.Seek(0, SeekOrigin.Begin); Assert.Throws(() => _excelExporter.AlterSheet(ms3, "Sheet1", "Sheet*")); } - - class LocalizationSupportDto(string firstName, string lastName, string address, int age) - { - [MiniExcelColumn(Name = nameof(FirstName), ResourceType = typeof(Localization), Width = 15)] - public string? FirstName { get; set; } = firstName; - - [MiniExcelColumn(Name = nameof(LastName), ResourceType = typeof(Localization), Width = 15)] - public string? LastName { get; set; } = lastName; - - [MiniExcelColumnName("Address", ResourceType = typeof(Localization))] - public string? Residency { get; set; } = address; - - [MiniExcelColumn(Name = nameof(Age), ResourceType = typeof(Localization), Width = 20)] - public int Age { get; set; } = age; - } [Theory] [InlineData("")] diff --git a/tests/MiniExcel.OpenXml.Tests/Main/MiniExcelOpenXmlImporterAsyncTests.cs b/tests/MiniExcel.OpenXml.Tests/Main/MiniExcelOpenXmlImporterAsyncTests.cs new file mode 100644 index 00000000..b6168e21 --- /dev/null +++ b/tests/MiniExcel.OpenXml.Tests/Main/MiniExcelOpenXmlImporterAsyncTests.cs @@ -0,0 +1,530 @@ +using ExcelDataReader; +using MiniExcelLib.Core.Exceptions; +using MiniExcelLib.OpenXml.Tests.Utils; +using MiniExcelLib.Tests.Common.Utils; + +namespace MiniExcelLib.OpenXml.Tests.Main; + +public class MiniExcelOpenXmlImporterAsyncTests +{ + private readonly OpenXmlImporter _excelImporter = MiniExcel.Importers.GetOpenXmlImporter(); + private readonly OpenXmlExporter _excelExporter = MiniExcel.Exporters.GetOpenXmlExporter(); + + static MiniExcelOpenXmlImporterAsyncTests() + { + ExcelPackage.LicenseContext = LicenseContext.NonCommercial; + } + + [Fact] + public async Task CustomAttributeWihoutVaildPropertiesTest() + { + var path = PathHelper.GetFile("xlsx/TestCustomExcelColumnAttribute.xlsx"); + await Assert.ThrowsAsync(async () => await _excelImporter.QueryAsync(path).ToListAsync()); + } + + [Fact] + public async Task QueryCustomAttributesTest() + { + var path = PathHelper.GetFile("xlsx/TestCustomExcelColumnAttribute.xlsx"); + var rows = await _excelImporter.QueryAsync(path).ToListAsync(); + + Assert.Equal("Column1", rows[0].Test1); + Assert.Equal("Column2", rows[0].Test2); + Assert.Null(rows[0].Test3); + Assert.Equal("Test7", rows[0].Test4); + Assert.Null(rows[0].Test5); + Assert.Null(rows[0].Test6); + Assert.Equal("Test4", rows[0].Test7); + } + + [Fact] + public async Task QueryCustomAttributes2Test() + { + var path = PathHelper.GetFile("xlsx/TestCustomExcelColumnAttribute.xlsx"); + var rows = await _excelImporter.QueryAsync(path).ToListAsync(); + + Assert.Equal("Column1", rows[0].Test1); + Assert.Equal("Column2", rows[0].Test2); + Assert.Null(rows[0].Test3); + Assert.Equal("Test7", rows[0].Test4); + Assert.Null(rows[0].Test5); + Assert.Null(rows[0].Test6); + Assert.Equal("Test4", rows[0].Test7); + } + + [Fact] + public async Task QueryCastToIDictionary() + { + var path = PathHelper.GetFile("xlsx/TestCenterEmptyRow/TestCenterEmptyRow.xlsx"); + await foreach (IDictionary row in _excelImporter.QueryAsync(path)) + { + _ = row; + } + } + + [Fact] + public async Task CenterEmptyRowsQueryTest() + { + var path = PathHelper.GetFile("xlsx/TestCenterEmptyRow/TestCenterEmptyRow.xlsx"); + await using (var stream = File.OpenRead(path)) + { + var rows = await _excelImporter.QueryAsync(stream).Cast>().ToListAsync(); + Assert.Equal("a", rows[0]["A"]); + Assert.Equal("b", rows[0]["B"]); + Assert.Equal("c", rows[0]["C"]); + Assert.Equal("d", rows[0]["D"]); + + Assert.Equal(1d, rows[1]["A"]); + Assert.Null(rows[1]["B"]); + Assert.Equal(3d, rows[1]["C"]); + Assert.Null(rows[1]["D"]); + + Assert.Null(rows[2]["A"]); + Assert.Equal(2d, rows[2]["B"]); + Assert.Null(rows[2]["C"]); + Assert.Equal(4d, rows[2]["D"]); + + Assert.Null(rows[3]["A"]); + Assert.Null(rows[3]["B"]); + Assert.Null(rows[3]["C"]); + Assert.Null(rows[3]["D"]); + + Assert.Equal(1d, rows[4]["A"]); + Assert.Null(rows[4]["B"]); + Assert.Equal(3d, rows[4]["C"]); + Assert.Null(rows[4]["D"]); + + Assert.Null(rows[5]["A"]); + Assert.Equal(2d, rows[5]["B"]); + Assert.Null(rows[5]["C"]); + Assert.Equal(4d, rows[5]["D"]); + + } + + await using (var stream = File.OpenRead(path)) + { + var rows = await _excelImporter.QueryAsync(stream, hasHeaderRow: true).Cast>().ToListAsync(); + Assert.Equal(1d, rows[0]["a"]); + Assert.Null(rows[0]["b"]); + Assert.Equal(3d, rows[0]["c"]); + Assert.Null(rows[0]["d"]); + + Assert.Null(rows[1]["a"]); + Assert.Equal(2d, rows[1]["b"]); + Assert.Null(rows[1]["c"]); + Assert.Equal(4d, rows[1]["d"]); + + Assert.Null(rows[2]["a"]); + Assert.Null(rows[2]["b"]); + Assert.Null(rows[2]["c"]); + Assert.Null(rows[2]["d"]); + + Assert.Equal(1d, rows[3]["a"]); + Assert.Null(rows[3]["b"]); + Assert.Equal(3d, rows[3]["c"]); + Assert.Null(rows[3]["d"]); + + Assert.Null(rows[4]["a"]); + Assert.Equal(2d, rows[4]["b"]); + Assert.Null(rows[4]["c"]); + Assert.Equal(4d, rows[4]["d"]); + } + } + + [Fact] + public async Task TestDynamicQueryBasic_WithoutHead() + { + var path = PathHelper.GetFile("xlsx/TestDynamicQueryBasic_WithoutHead.xlsx"); + await using var stream = File.OpenRead(path); + var rows = await _excelImporter.QueryAsync(stream).Cast>().ToListAsync(); + + Assert.Equal("MiniExcel", rows[0]["A"]); + Assert.Equal(1d, rows[0]["B"]); + Assert.Equal("Github", rows[1]["A"]); + Assert.Equal(2d, rows[1]["B"]); + } + + [Fact] + public async Task TestDynamicQueryBasic_useHeaderRow() + { + var path = PathHelper.GetFile("xlsx/TestDynamicQueryBasic.xlsx"); + await using (var stream = File.OpenRead(path)) + { + var rows = await _excelImporter.QueryAsync(stream, hasHeaderRow: true).Cast>().ToListAsync(); + Assert.Equal("MiniExcel", rows[0]["Column1"]); + Assert.Equal(1d, rows[0]["Column2"]); + Assert.Equal("Github", rows[1]["Column1"]); + Assert.Equal(2d, rows[1]["Column2"]); + } + + { + var rows = await _excelImporter.QueryAsync(path, hasHeaderRow: true).ToListAsync(); + Assert.Equal("MiniExcel", rows[0].Column1); + Assert.Equal(1d, rows[0].Column2); + Assert.Equal("Github", rows[1].Column1); + Assert.Equal(2d, rows[1].Column2); + } + } + + [Fact] + public async Task QueryStrongTypeMapping_Test() + { + var path = PathHelper.GetFile("xlsx/TestTypeMapping.xlsx"); + await using (var stream = File.OpenRead(path)) + { + var rows = await _excelImporter.QueryAsync(stream).ToListAsync(); + Assert.Equal(100, rows.Count); + + Assert.Equal(Guid.Parse("78DE23D2-DCB6-BD3D-EC67-C112BBC322A2"), rows[0].ID); + Assert.Equal("Wade", rows[0].Name); + Assert.Equal(DateTime.ParseExact("27/09/2020", "dd/MM/yyyy", CultureInfo.InvariantCulture), rows[0].BoD); + Assert.Equal(36, rows[0].Age); + Assert.False(rows[0].VIP); + Assert.Equal(5019.12m, rows[0].Points); + Assert.Equal(1, rows[0].IgnoredProperty); + } + + { + var rows = _excelImporter.Query(path, hasHeaderRow: true).ToList(); + Assert.Equal(100, rows.Count); + + Assert.Equal("78DE23D2-DCB6-BD3D-EC67-C112BBC322A2", rows[0].ID); + Assert.Equal("Wade", rows[0].Name); + Assert.Equal(new DateTime(2020, 9, 27), rows[0].BoD); + Assert.Equal(36, rows[0].Age); + Assert.False(rows[0].VIP); + Assert.Equal(5019.12d, rows[0].Points); + Assert.Null(rows[0].IgnoredProperty); + } + } + + [Fact] + public async Task AutoCheckTypeTest() + { + var path = PathHelper.GetFile("xlsx/TestTypeMapping_AutoCheckFormat.xlsx"); + await using var stream = File.OpenRead(path); + _ = await _excelImporter.QueryAsync(stream).ToListAsync(); + } + + [Fact] + public async Task TestDatetimeSpanFormat_ClosedXml() + { + var path = PathHelper.GetFile("xlsx/TestDatetimeSpanFormat_ClosedXml.xlsx"); + await using var stream = File.OpenRead(path); + + var row = await _excelImporter.QueryAsync(stream).Cast>().FirstAsync(); + var a = row["A"]; + var b = row["B"]; + + Assert.Equal(DateTime.Parse("2021-03-20T23:39:42.3130000"), (DateTime)a); + Assert.Equal(TimeSpan.FromHours(10), (TimeSpan)b); + } + + [Fact] + public async Task LargeFileQueryStrongTypeMapping_Test() + { + const string path = "../../../../../benchmarks/MiniExcel.Benchmarks/Test1,000,000x10.xlsx"; + await using (var stream = File.OpenRead(path)) + { + var rows = await _excelImporter.QueryAsync(stream).Take(2).ToListAsync(); + Assert.Equal("HelloWorld2", rows[0].HelloWorld1); + Assert.Equal("HelloWorld3", rows[1].HelloWorld1); + } + { + var rows = await _excelImporter.QueryAsync(path).Take(2).ToListAsync(); + Assert.Equal("HelloWorld2", rows[0].HelloWorld1); + Assert.Equal("HelloWorld3", rows[1].HelloWorld1); + } + } + + [Theory] + [InlineData("../../../../data/xlsx/ExcelDataReaderCollections/TestChess.xlsx")] + [InlineData("../../../../data/xlsx/TestCenterEmptyRow/TestCenterEmptyRow.xlsx")] + public async Task QueryExcelDataReaderCheckTest(string path) + { + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + + await using var fs = File.OpenRead(path); + using var reader = ExcelReaderFactory.CreateReader(fs); + var exceldatareaderResult = reader.AsDataSet(); + await using var stream = File.OpenRead(path); + + var rows = await _excelImporter.QueryAsync(stream).ToListAsync(); + Assert.Equal(exceldatareaderResult.Tables[0].Rows.Count, rows.Count); + + foreach (IDictionary row in rows) + { + var rowIndex = rows.IndexOf(row); + foreach (var (key, value) in row) + { + var eV = exceldatareaderResult.Tables[0].Rows[rowIndex][SheetHelper.GetColumnIndex(key)]; + var v = value ?? DBNull.Value; + Assert.Equal(eV, v); + } + } + } + + [Fact] + public async Task QuerySheetWithoutRAttribute() + { + var path = PathHelper.GetFile("xlsx/TestWihoutRAttribute.xlsx"); + await using var stream = File.OpenRead(path); + + var rows = await _excelImporter.QueryAsync(stream).Cast>().ToListAsync(); + var keys = rows.First().Keys; + + Assert.Equal(2, rows.Count); + Assert.Equal(5, keys.Count); + + Assert.Equal(1d, rows[0]["A"]); + Assert.Null(rows[0]["C"]); + Assert.Null(rows[0]["D"]); + Assert.Null(rows[0]["E"]); + + Assert.Equal(1d, rows[1]["A"]); + Assert.Equal("\"<>+}{\\nHello World", rows[1]["B"]); + Assert.Equal(true, rows[1]["C"]); + Assert.Equal("2021-03-16T19:10:21", rows[1]["D"]); + } + + [Fact] + public async Task FixDimensionJustOneColumnParsingError_Test() + { + var path = PathHelper.GetFile("xlsx/TestDimensionC3.xlsx"); + await using var stream = File.OpenRead(path); + + var rows = await _excelImporter.QueryAsync(stream).ToListAsync(); + var keys = (rows.First() as IDictionary)?.Keys; + + Assert.Equal(3, keys?.Count); + Assert.Equal(2, rows.Count); + } + + [Fact] + public async Task QueryByLINQExtensionsVoidTaskLargeFileOOMTest() + { + const string path = "../../../../../benchmarks/MiniExcel.Benchmarks/Test1,000,000x10.xlsx"; + + { + var row = await _excelImporter.QueryAsync(path).FirstAsync(); + Assert.Equal("HelloWorld1", row.A); + } + + await using (var stream = File.OpenRead(path)) + { + var row = await _excelImporter.QueryAsync(stream).Cast>().FirstAsync(); + Assert.Equal("HelloWorld1", row["A"]); + } + + { + var count = await _excelImporter.QueryAsync(path) + .Cast>() + .Take(10) + .CountAsync(); + + Assert.Equal(10, count); + } + } + + [Fact] + public async Task QueryByStrongTypeParameterTest() + { + using var file = AutoDeletingPath.Create(); + var path = file.ToString(); + List values = + [ + new() { Column1 = "MiniExcel", Column2 = 1 }, + new() { Column1 = "Github", Column2 = 2 } + ]; + await _excelExporter.ExportAsync(path, values); + + await using var stream = File.OpenRead(path); + var rows = await _excelImporter.QueryAsync(stream, hasHeaderRow: true).Cast>().ToListAsync(); + + Assert.Equal("MiniExcel", rows[0]["Column1"]); + Assert.Equal(1d, rows[0]["Column2"]); + Assert.Equal("Github", rows[1]["Column1"]); + Assert.Equal(2d, rows[1]["Column2"]); + } + + [Fact] + public async Task QueryByDictionaryStringAndObjectParameterTest() + { + using var file = AutoDeletingPath.Create(); + var path = file.ToString(); + List> values = + [ + new() { { "Column1", "MiniExcel" }, { "Column2", 1 } }, + new() { { "Column1", "Github" }, { "Column2", 2 } } + ]; + await _excelExporter.ExportAsync(path, values); + + await using var stream = File.OpenRead(path); + var rows = await _excelImporter.QueryAsync(stream, hasHeaderRow: true).Cast>().ToListAsync(); + + Assert.Equal("MiniExcel", rows[0]["Column1"]); + Assert.Equal(1d, rows[0]["Column2"]); + Assert.Equal("Github", rows[1]["Column1"]); + Assert.Equal(2d, rows[1]["Column2"]); + } + + [Fact] + public async Task QueryRangeAsyncWithCellReferencesTest() + { + var path = PathHelper.GetFile("xlsx/TestTypeMapping.xlsx"); + + await using var stream = File.OpenRead(path); + var rows = await _excelImporter.QueryRangeAsync(stream, startCell: "B6", endCell: "D9") + .Cast>() + .ToListAsync(); + + Assert.Equal(4, rows.Count); + Assert.Equal(3, rows[0].Count); + + Assert.Equal("Raymond", rows[0]["B"]); + Assert.Equal(new DateTime(2021, 12, 7), rows[0]["C"]); + Assert.Equal(18d, rows[0]["D"]); + Assert.Equal("Clarke", rows[1]["B"]); + Assert.Equal(new DateTime(2021, 10, 16), rows[1]["C"]); + Assert.Equal(60d, rows[1]["D"]); + Assert.Equal("Eric", rows[2]["B"]); + Assert.Equal(new DateTime(2020, 6, 24), rows[2]["C"]); + Assert.Equal(30d, rows[2]["D"]); + } + + [Fact] + public async Task QueryRangeAsyncWithIndexCoordinatesTest() + { + var path = PathHelper.GetFile("xlsx/TestTypeMapping.xlsx"); + + await using var stream = File.OpenRead(path); + var rows = await _excelImporter.QueryRangeAsync(stream, startRowIndex: 6, startColumnIndex: 2, endRowIndex: 9, endColumnIndex: 4) + .Cast>() + .ToListAsync(); + + Assert.Equal(4, rows.Count); + Assert.Equal(3, rows[0].Count); + + Assert.Equal("Raymond", rows[0]["B"]); + Assert.Equal(new DateTime(2021, 12, 7), rows[0]["C"]); + Assert.Equal(18d, rows[0]["D"]); + Assert.Equal("Clarke", rows[1]["B"]); + Assert.Equal(new DateTime(2021, 10, 16), rows[1]["C"]); + Assert.Equal(60d, rows[1]["D"]); + Assert.Equal("Eric", rows[2]["B"]); + Assert.Equal(new DateTime(2020, 6, 24), rows[2]["C"]); + Assert.Equal(30d, rows[2]["D"]); + } + + [Fact] + public async Task QueryRangeAsyncWithCellReferencesAndHeaderTest() + { + var path = PathHelper.GetFile("xlsx/TestQueryRange.xlsx"); + var rows = await _excelImporter.QueryRangeAsync(path, hasHeaderRow: true, startCell: "C3", endCell: "E6") + .Cast>() + .ToListAsync(); + + Assert.Equal(3, rows.Count); + Assert.Equal(3, rows[0].Count); + + Assert.Equal("Wade", rows[0]["Name"]); + Assert.Equal(new DateTime(2020, 9, 27), rows[0]["BoD"]); + Assert.Equal(36d, rows[0]["Age"]); + Assert.Equal("Felix", rows[1]["Name"]); + Assert.Equal(new DateTime(2020, 10, 25), rows[1]["BoD"]); + Assert.Equal(44d, rows[1]["Age"]); + Assert.Equal("Phelan", rows[2]["Name"]); + Assert.Equal(new DateTime(2021, 4, 10), rows[2]["BoD"]); + Assert.Equal(33d, rows[2]["Age"]); + } + + [Fact] + public async Task QueryRangeAsyncWithIndexCoordinatesAndHeaderTest() + { + var path = PathHelper.GetFile("xlsx/TestQueryRange.xlsx"); + var rows = await _excelImporter.QueryRangeAsync(path, hasHeaderRow: true, startCell: "C3", endCell: "E6") + .Cast>() + .ToListAsync(); + + Assert.Equal(3, rows.Count); + Assert.Equal(3, rows[0].Count); + + Assert.Equal("Wade", rows[0]["Name"]); + Assert.Equal(new DateTime(2020, 9, 27), rows[0]["BoD"]); + Assert.Equal(36d, rows[0]["Age"]); + Assert.Equal("Felix", rows[1]["Name"]); + Assert.Equal(new DateTime(2020, 10, 25), rows[1]["BoD"]); + Assert.Equal(44d, rows[1]["Age"]); + Assert.Equal("Phelan", rows[2]["Name"]); + Assert.Equal(new DateTime(2021, 4, 10), rows[2]["BoD"]); + Assert.Equal(33d, rows[2]["Age"]); + } + + [Fact] + public async Task ReadBigExcel_TakeCancel_Throws_TaskCanceledException() + { + await Assert.ThrowsAnyAsync(async () => + { + var cts = new CancellationTokenSource(); + + var exportTask = _excelImporter.QueryAsync(PathHelper.GetFile("xlsx/Test10000x10.xlsx"), cancellationToken: cts.Token).ToListAsync(cts.Token); + await cts.CancelAsync(); + await exportTask; + }); + } + + [Fact] + public async Task ReadBigExcel_Processing_TakeCancel_Throws_TaskCanceledException() + { + await Assert.ThrowsAnyAsync(async () => + { + var cts = new CancellationTokenSource(); + + var exportTask = _excelImporter.QueryAsync(PathHelper.GetFile("xlsx/Test10000x10.xlsx"), cancellationToken: cts.Token).ToListAsync(cts.Token); + await cts.CancelAsync(); + await exportTask; + }); + } + + [Fact] + public async Task InvalidSheetNameCharactersShouldThrow() + { + await using var ms1 = new MemoryStream(); + await Assert.ThrowsAsync(() => _excelExporter.ExportAsync(ms1, Array.Empty(), sheetName: "Sheet?")); + + await using var ms2 = new MemoryStream(); + await Assert.ThrowsAsync(() => _excelExporter.InsertSheetAsync(ms2, Array.Empty(), sheetName: "Sheet[]")); + + await using var ms3 = new MemoryStream(); + using var package = new ExcelPackage(ms3); + package.Workbook.Worksheets.Add("Sheet1"); + await package.SaveAsync(); + + ms1.Seek(0, SeekOrigin.Begin); + await Assert.ThrowsAsync(() => _excelExporter.AlterSheetAsync(ms3, "Sheet1", "Sheet*")); + } + + [Fact] + public async Task MultipleResultSets() + { + await using var stream =File.OpenRead(PathHelper.GetFile("xlsx/TestTypeMapping.xlsx")); + await using var dr = await OpenXmlDataReader.CreateAsync(stream, hasHeaderRow: true, leaveOpen: true); + await dr.ReadAsync(); + var v1 = dr.GetValue(0); + var nr = await dr.NextResultAsync(); + await dr.ReadAsync(); + var v2 = dr.GetValue(0); + } + + [Fact] + public void MultipleResultSets2() + { + using var stream = File.OpenRead(PathHelper.GetFile("xlsx/TestMultiSheet.xlsx")); + using var dr = OpenXmlDataReader.Create(stream, leaveOpen: true); + dr.Read(); + var v1 = dr.GetValue(0); + var nr = dr.NextResult(); + dr.Read(); + var v2 = dr.GetValue(0); + } +} diff --git a/tests/MiniExcel.OpenXml.Tests/Main/MiniExcelOpenXmlImporterTests.cs b/tests/MiniExcel.OpenXml.Tests/Main/MiniExcelOpenXmlImporterTests.cs new file mode 100644 index 00000000..4ed65265 --- /dev/null +++ b/tests/MiniExcel.OpenXml.Tests/Main/MiniExcelOpenXmlImporterTests.cs @@ -0,0 +1,673 @@ +using ExcelDataReader; +using MiniExcelLib.Core.Exceptions; +using MiniExcelLib.OpenXml.Tests.Utils; +using MiniExcelLib.Tests.Common.Utils; + +namespace MiniExcelLib.OpenXml.Tests.Main; + +public class MiniExcelOpenXmlImporterTests(ITestOutputHelper output) +{ + private readonly ITestOutputHelper _output = output; + + private readonly OpenXmlImporter _excelImporter = MiniExcel.Importers.GetOpenXmlImporter(); + private readonly OpenXmlExporter _excelExporter = MiniExcel.Exporters.GetOpenXmlExporter(); + + static MiniExcelOpenXmlImporterTests() + { + ExcelPackage.LicenseContext = LicenseContext.NonCommercial; + } + + [Fact] + public void CustomAttributeWihoutVaildPropertiesTest() + { + var path = PathHelper.GetFile("xlsx/TestCustomExcelColumnAttribute.xlsx"); + Assert.Throws(() => _excelImporter.Query(path).ToList()); + } + + [Fact] + public void QueryCustomAttributesTest() + { + var path = PathHelper.GetFile("xlsx/TestCustomExcelColumnAttribute.xlsx"); + var rows = _excelImporter.Query(path).ToList(); + + Assert.Equal("Column1", rows[0].Test1); + Assert.Equal("Column2", rows[0].Test2); + Assert.Null(rows[0].Test3); + Assert.Equal("Test7", rows[0].Test4); + Assert.Null(rows[0].Test5); + Assert.Null(rows[0].Test6); + Assert.Equal("Test4", rows[0].Test7); + } + + [Fact] + public void QueryCastToIDictionary() + { + var path = PathHelper.GetFile("xlsx/TestCenterEmptyRow/TestCenterEmptyRow.xlsx"); + foreach (IDictionary row in _excelImporter.Query(path)) + { + _ = row; + } + } + + [Fact] + public void QueryRangeToIDictionary() + { + var path = PathHelper.GetFile("xlsx/TestCenterEmptyRow/TestCenterEmptyRow.xlsx"); + // tips:Only uppercase letters are effective + var rows = _excelImporter.QueryRange(path, startCell: "A2", endCell: "C7") + .Cast>() + .ToList(); + + Assert.Equal(5, rows.Count); + Assert.Equal(3, rows[0].Count); + Assert.Equal(2d, rows[1]["B"]); + Assert.Equal(null!, rows[2]["A"]); + + rows = _excelImporter.QueryRange(path, startRowIndex: 2, startColumnIndex: 1, endRowIndex: 7, endColumnIndex: 3) + .Cast>() + .ToList(); + + Assert.Equal(5, rows.Count); + Assert.Equal(3, rows[0].Count); + Assert.Equal(2d, rows[1]["B"]); + Assert.Equal(null!, rows[2]["A"]); + + rows = _excelImporter.QueryRange(path, startRowIndex:2, startColumnIndex: 1, endRowIndex: 3) + .Cast>() + .ToList(); + Assert.Equal(2, rows.Count); + Assert.Equal(4, rows[0].Count); + Assert.Equal(4d, rows[1]["D"]); + + rows = _excelImporter.QueryRange(path, startRowIndex: 2, startColumnIndex: 1, endColumnIndex: 3) + .Cast>() + .ToList(); + Assert.Equal(5, rows.Count); + Assert.Equal(3, rows[0].Count); + Assert.Equal(3d, rows[3]["C"]); + } + + [Fact] + public void CenterEmptyRowsQueryTest() + { + var path = PathHelper.GetFile("xlsx/TestCenterEmptyRow/TestCenterEmptyRow.xlsx"); + using (var stream = File.OpenRead(path)) + { + var rows = _excelImporter.Query(stream).ToList(); + + Assert.Equal("a", rows[0].A); + Assert.Equal("b", rows[0].B); + Assert.Equal("c", rows[0].C); + Assert.Equal("d", rows[0].D); + + Assert.Equal(1, rows[1].A); + Assert.Null(rows[1].B); + Assert.Equal(3, rows[1].C); + Assert.Null(rows[1].D); + + Assert.Null(rows[2].A); + Assert.Equal(2, rows[2].B); + Assert.Null(rows[2].C); + Assert.Equal(4, rows[2].D); + + Assert.Null(rows[3].A); + Assert.Null(rows[3].B); + Assert.Null(rows[3].C); + Assert.Null(rows[3].D); + + Assert.Equal(1, rows[4].A); + Assert.Null(rows[4].B); + Assert.Equal(3, rows[4].C); + Assert.Null(rows[4].D); + + Assert.Null(rows[5].A); + Assert.Equal(2, rows[5].B); + Assert.Null(rows[5].C); + Assert.Equal(4, rows[5].D); + } + + using (var stream = File.OpenRead(path)) + { + var rows = _excelImporter.Query(stream, hasHeaderRow: true).ToList(); + + Assert.Equal(1, rows[0].a); + Assert.Null(rows[0].b); + Assert.Equal(3, rows[0].c); + Assert.Null(rows[0].d); + + Assert.Null(rows[1].a); + Assert.Equal(2, rows[1].b); + Assert.Null(rows[1].c); + Assert.Equal(4, rows[1].d); + + Assert.Null(rows[2].a); + Assert.Null(rows[2].b); + Assert.Null(rows[2].c); + Assert.Null(rows[2].d); + + Assert.Equal(1, rows[3].a); + Assert.Null(rows[3].b); + Assert.Equal(3, rows[3].c); + Assert.Null(rows[3].d); + + Assert.Null(rows[4].a); + Assert.Equal(2, rows[4].b); + Assert.Null(rows[4].c); + Assert.Equal(4, rows[4].d); + } + } + + [Fact] + public void TestEmptyRowsQuerySelfClosingTag() + { + var path = PathHelper.GetFile("xlsx/TestEmptySelfClosingRow.xlsx"); + using var stream = File.OpenRead(path); + var rows = _excelImporter.Query(stream).ToList(); + + Assert.Null(rows[0].A); + Assert.Equal(1, rows[1].A); + Assert.Null(rows[2].A); + Assert.Equal(2, rows[3].A); + Assert.Null(rows[4].A); + Assert.Null(rows[5].A); + Assert.Null(rows[6].A); + Assert.Null(rows[7].A); + Assert.Null(rows[8].A); + Assert.Equal(1, rows[9].A); + } + + [Fact] + public void TestDynamicQueryBasic_WithoutHead() + { + var path = PathHelper.GetFile("xlsx/TestDynamicQueryBasic_WithoutHead.xlsx"); + using var stream = File.OpenRead(path); + var rows = _excelImporter.Query(stream).ToList(); + + Assert.Equal("MiniExcel", rows[0].A); + Assert.Equal(1, rows[0].B); + Assert.Equal("Github", rows[1].A); + Assert.Equal(2, rows[1].B); + } + + [Fact] + public void TestDynamicQueryBasic_hasHeaderRow() + { + var path = PathHelper.GetFile("xlsx/TestDynamicQueryBasic.xlsx"); + using (var stream = File.OpenRead(path)) + { + var rows = _excelImporter.Query(stream, hasHeaderRow: true).ToList(); + + Assert.Equal("MiniExcel", rows[0].Column1); + Assert.Equal(1, rows[0].Column2); + Assert.Equal("Github", rows[1].Column1); + Assert.Equal(2, rows[1].Column2); + } + + { + var rows = _excelImporter.Query(path, hasHeaderRow: true).ToList(); + + Assert.Equal("MiniExcel", rows[0].Column1); + Assert.Equal(1, rows[0].Column2); + Assert.Equal("Github", rows[1].Column1); + Assert.Equal(2, rows[1].Column2); + } + } + + [Fact] + public void QueryStrongTypeMapping_Test() + { + var path = PathHelper.GetFile("xlsx/TestTypeMapping.xlsx"); + using (var stream = File.OpenRead(path)) + { + var rows = _excelImporter.Query(stream).ToList(); + Assert.Equal(100, rows.Count); + + Assert.Equal(Guid.Parse("78DE23D2-DCB6-BD3D-EC67-C112BBC322A2"), rows[0].ID); + Assert.Equal("Wade", rows[0].Name); + Assert.Equal(DateTime.ParseExact("27/09/2020", "dd/MM/yyyy", CultureInfo.InvariantCulture), rows[0].BoD); + Assert.Equal(36, rows[0].Age); + Assert.False(rows[0].VIP); + Assert.Equal(5019.12m, rows[0].Points); + Assert.Equal(1, rows[0].IgnoredProperty); + } + + { + var rows = _excelImporter.Query(path).ToList(); + Assert.Equal(100, rows.Count); + + Assert.Equal(Guid.Parse("78DE23D2-DCB6-BD3D-EC67-C112BBC322A2"), rows[0].ID); + Assert.Equal("Wade", rows[0].Name); + Assert.Equal(DateTime.ParseExact("27/09/2020", "dd/MM/yyyy", CultureInfo.InvariantCulture), rows[0].BoD); + Assert.Equal(36, rows[0].Age); + Assert.False(rows[0].VIP); + Assert.Equal(5019.12m, rows[0].Points); + Assert.Equal(1, rows[0].IgnoredProperty); + } + } + + [Fact] + public void QueryRangeWithCellReferencesTest() + { + var path = PathHelper.GetFile("xlsx/TestTypeMapping.xlsx"); + + using var stream = File.OpenRead(path); + var rows = _excelImporter.QueryRange(stream, startCell: "B6", endCell: "D9") + .Cast>() + .ToList(); + + Assert.Equal(4, rows.Count); + Assert.Equal(3, rows[0].Count); + + Assert.Equal("Raymond", rows[0]["B"]); + Assert.Equal(new DateTime(2021, 12, 7), rows[0]["C"]); + Assert.Equal(18d, rows[0]["D"]); + Assert.Equal("Clarke", rows[1]["B"]); + Assert.Equal(new DateTime(2021, 10, 16), rows[1]["C"]); + Assert.Equal(60d, rows[1]["D"]); + Assert.Equal("Eric", rows[2]["B"]); + Assert.Equal(new DateTime(2020, 6, 24), rows[2]["C"]); + Assert.Equal(30d, rows[2]["D"]); + } + + [Fact] + public void QueryRangeWithIndexCoordinatesTest() + { + var path = PathHelper.GetFile("xlsx/TestTypeMapping.xlsx"); + + using var stream = File.OpenRead(path); + var rows = _excelImporter.QueryRange(stream, startRowIndex: 6, startColumnIndex: 2, endRowIndex: 9, endColumnIndex: 4) + .Cast>() + .ToList(); + + Assert.Equal(4, rows.Count); + Assert.Equal(3, rows[0].Count); + + Assert.Equal("Raymond", rows[0]["B"]); + Assert.Equal(new DateTime(2021, 12, 7), rows[0]["C"]); + Assert.Equal(18d, rows[0]["D"]); + Assert.Equal("Clarke", rows[1]["B"]); + Assert.Equal(new DateTime(2021, 10, 16), rows[1]["C"]); + Assert.Equal(60d, rows[1]["D"]); + Assert.Equal("Eric", rows[2]["B"]); + Assert.Equal(new DateTime(2020, 6, 24), rows[2]["C"]); + Assert.Equal(30d, rows[2]["D"]); + } + + [Fact] + public void QueryRangeWithCellReferencesAndHeaderTest() + { + var path = PathHelper.GetFile("xlsx/TestQueryRange.xlsx"); + var rows = _excelImporter.QueryRange(path, hasHeaderRow: true, startCell: "C3", endCell: "E6") + .Cast>() + .ToList(); + + Assert.Equal(3, rows.Count); + Assert.Equal(3, rows[0].Count); + + Assert.Equal("Wade", rows[0]["Name"]); + Assert.Equal(new DateTime(2020, 9, 27), rows[0]["BoD"]); + Assert.Equal(36d, rows[0]["Age"]); + Assert.Equal("Felix", rows[1]["Name"]); + Assert.Equal(new DateTime(2020, 10, 25), rows[1]["BoD"]); + Assert.Equal(44d, rows[1]["Age"]); + Assert.Equal("Phelan", rows[2]["Name"]); + Assert.Equal(new DateTime(2021, 4, 10), rows[2]["BoD"]); + Assert.Equal(33d, rows[2]["Age"]); + } + + [Fact] + public void QueryRangeWithIndexCoordinatesAndHeaderTest() + { + var path = PathHelper.GetFile("xlsx/TestQueryRange.xlsx"); + var rows = _excelImporter.QueryRange(path, hasHeaderRow: true, startCell: "C3", endCell: "E6") + .Cast>() + .ToList(); + + Assert.Equal(3, rows.Count); + Assert.Equal(3, rows[0].Count); + + Assert.Equal("Wade", rows[0]["Name"]); + Assert.Equal(new DateTime(2020, 9, 27), rows[0]["BoD"]); + Assert.Equal(36d, rows[0]["Age"]); + Assert.Equal("Felix", rows[1]["Name"]); + Assert.Equal(new DateTime(2020, 10, 25), rows[1]["BoD"]); + Assert.Equal(44d, rows[1]["Age"]); + Assert.Equal("Phelan", rows[2]["Name"]); + Assert.Equal(new DateTime(2021, 4, 10), rows[2]["BoD"]); + Assert.Equal(33d, rows[2]["Age"]); + } + + [Fact] + public void AutoCheckTypeTest() + { + var path = PathHelper.GetFile("xlsx/TestTypeMapping_AutoCheckFormat.xlsx"); + using var stream = File.OpenRead(path); + var rows = _excelImporter.Query(stream).ToList(); + } + + [Fact] + public void UriMappingTest() + { + var path = PathHelper.GetFile("xlsx/TestUriMapping.xlsx"); + using var stream = File.OpenRead(path); + var rows = _excelImporter.Query(stream).ToList(); + + Assert.Equal("Felix", rows[1].Name); + Assert.Equal(44, rows[1].Age); + Assert.Equal(new Uri("https://friendly-utilization.net"), rows[1].Url); + } + + [Fact] + public void TrimColumnNamesTest() + { + var path = PathHelper.GetFile("xlsx/TestTrimColumnNames.xlsx"); + var rows = _excelImporter.Query(path).ToList(); + + Assert.Equal("Raymond", rows[4].Name); + Assert.Equal(18, rows[4].Age); + Assert.Equal("sagittis.lobortis@leoMorbi.com", rows[4].Mail); + Assert.Equal(8209.76m, rows[4].Points); + } + + [Fact] + public void TestDatetimeSpanFormat_ClosedXml() + { + var path = PathHelper.GetFile("xlsx/TestDatetimeSpanFormat_ClosedXml.xlsx"); + using var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); + + var row = _excelImporter.Query(stream).First(); + var a = row.A; + var b = row.B; + Assert.Equal(DateTime.Parse("2021-03-20T23:39:42.3130000"), (DateTime)a); + Assert.Equal(TimeSpan.FromHours(10), (TimeSpan)b); + } + + [Fact] + public void LargeFileQueryStrongTypeMapping_Test() + { + const string path = "../../../../../benchmarks/MiniExcel.Benchmarks/Test1,000,000x10.xlsx"; + using (var stream = File.OpenRead(path)) + { + var rows = _excelImporter.Query(stream).Take(2).ToList(); + + Assert.Equal("HelloWorld2", rows[0].HelloWorld1); + Assert.Equal("HelloWorld3", rows[1].HelloWorld1); + } + { + var rows = _excelImporter.Query(path).Take(2).ToList(); + + Assert.Equal("HelloWorld2", rows[0].HelloWorld1); + Assert.Equal("HelloWorld3", rows[1].HelloWorld1); + } + } + + [Theory] + [InlineData("../../../../data/xlsx/ExcelDataReaderCollections/TestChess.xlsx")] + [InlineData("../../../../data/xlsx/TestCenterEmptyRow/TestCenterEmptyRow.xlsx")] + public void QueryDataReaderCheckTest(string path) + { + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + + using var fs = File.OpenRead(path); + using var reader = ExcelReaderFactory.CreateReader(fs); + var exceldatareaderResult = reader.AsDataSet(); + + using var stream = File.OpenRead(path); + var rows = _excelImporter.Query(stream).ToList(); + Assert.Equal(exceldatareaderResult.Tables[0].Rows.Count, rows.Count); + + foreach (IDictionary row in rows) + { + var rowIndex = rows.IndexOf(row); + foreach (var (key, value) in row) + { + var eV = exceldatareaderResult.Tables[0].Rows[rowIndex][SheetHelper.GetColumnIndex(key)]; + var v = value ?? DBNull.Value; + Assert.Equal(eV, v); + } + } + } + + [Fact] + public void QuerySheetWithoutRAttribute() + { + var path = PathHelper.GetFile("xlsx/TestWihoutRAttribute.xlsx"); + using var stream = File.OpenRead(path); + var rows = _excelImporter.Query(stream).ToList(); + var keys = (rows.First() as IDictionary)!.Keys; + + Assert.Equal(2, rows.Count); + Assert.Equal(5, keys.Count); + + Assert.Equal(1, rows[0].A); + Assert.Null(rows[0].C); + Assert.Null(rows[0].D); + Assert.Null(rows[0].E); + + Assert.Equal(1, rows[1].A); + Assert.Equal("\"<>+}{\\nHello World", rows[1].B); + Assert.Equal(true, rows[1].C); + Assert.Equal("2021-03-16T19:10:21", rows[1].D); + } + + [Fact] + public void FixDimensionJustOneColumnParsingError_Test() + { + var path = PathHelper.GetFile("xlsx/TestDimensionC3.xlsx"); + using var stream = File.OpenRead(path); + var rows = _excelImporter.Query(stream).ToList(); + var keys = ((IDictionary)rows.First()).Keys; + Assert.Equal(3, keys.Count); + Assert.Equal(2, rows.Count); + } + + [Fact] + public void QueryByLINQExtensionsAvoidLargeFileOOMTest() + { + const string path = "../../../../../benchmarks/MiniExcel.Benchmarks/Test1,000,000x10.xlsx"; + + var query1 = _excelImporter.Query(path).First(); + Assert.Equal("HelloWorld1", query1.A); + + using (var stream = File.OpenRead(path)) + { + var query2 = _excelImporter.Query(stream).First(); + Assert.Equal("HelloWorld1", query2.A); + } + + var query3 = _excelImporter.Query(path).Take(10); + Assert.Equal(10, query3.Count()); + } + + [Fact] + public void QueryByStrongTypeParameterTest() + { + using var path = AutoDeletingPath.Create(); + List values = + [ + new() { Column1 = "MiniExcel", Column2 = 1 }, + new() { Column1 = "Github", Column2 = 2 } + ]; + _excelExporter.Export(path.ToString(), values); + + using var stream = File.OpenRead(path.ToString()); + var rows = _excelImporter.Query(stream, hasHeaderRow: true).ToList(); + + Assert.Equal("MiniExcel", rows[0].Column1); + Assert.Equal(1, rows[0].Column2); + Assert.Equal("Github", rows[1].Column1); + Assert.Equal(2, rows[1].Column2); + } + + [Fact] + public void QueryByDictionaryStringAndObjectParameterTest() + { + using var path = AutoDeletingPath.Create(); + List> values = + [ + new() { { "Column1", "MiniExcel" }, { "Column2", 1 } }, + new() { { "Column1", "Github" }, { "Column2", 2 } } + ]; + _excelExporter.Export(path.ToString(), values); + + using var stream = File.OpenRead(path.ToString()); + var rows = _excelImporter.Query(stream, hasHeaderRow: true).ToList(); + + Assert.Equal("MiniExcel", rows[0].Column1); + Assert.Equal(1, rows[0].Column2); + Assert.Equal("Github", rows[1].Column1); + Assert.Equal(2, rows[1].Column2); + } + + [Fact] + public void TestStirctOpenXml() + { + var path = PathHelper.GetFile("xlsx/TestStrictOpenXml.xlsx"); + var columns = _excelImporter.GetColumnNames (path); + Assert.Equal(["A", "B", "C"], columns); + + var rows = _excelImporter.Query(path).ToList(); + Assert.Equal(rows[0].A, "title1"); + Assert.Equal(rows[0].B, "title2"); + Assert.Equal(rows[0].C, "title3"); + Assert.Equal(rows[1].A, "value1"); + Assert.Equal(rows[1].B, "value2"); + Assert.Equal(rows[1].C, "value3"); + } + + [Fact] + public void SharedStringCacheTest() + { + const string path = "../../../../../benchmarks/MiniExcel.Benchmarks/Test1,000,000x10_SharingStrings.xlsx"; + + var ts = Stopwatch.GetTimestamp(); + _ = _excelImporter.Query(path, configuration: new OpenXmlConfiguration { EnableSharedStringCache = true }).First(); + using var currentProcess = Process.GetCurrentProcess(); + var totalBytesOfMemoryUsed = currentProcess.WorkingSet64; + + _output.WriteLine("totalBytesOfMemoryUsed: " + totalBytesOfMemoryUsed); + _output.WriteLine("elapsedMilliseconds: " + Stopwatch.GetElapsedTime(ts).TotalMilliseconds); + } + + [Fact] + public void SharedStringNoCacheTest() + { + const string path = "../../../../../benchmarks/MiniExcel.Benchmarks/Test1,000,000x10_SharingStrings.xlsx"; + + var ts = Stopwatch.GetTimestamp(); + _ = _excelImporter.Query(path).First(); + using var currentProcess = Process.GetCurrentProcess(); + var totalBytesOfMemoryUsed = currentProcess.WorkingSet64; + _output.WriteLine("totalBytesOfMemoryUsed: " + totalBytesOfMemoryUsed); + _output.WriteLine("elapsedMilliseconds: " + Stopwatch.GetElapsedTime(ts).TotalMilliseconds); + } + + [Fact] + public void DateOnlySupportTest() + { + var query = _excelImporter.Query(PathHelper.GetFile("xlsx/TestDateOnlyMapping.xlsx")).ToList(); + + Assert.Equal(new DateOnly(2020, 9, 27), query[0].Date); + Assert.Equal(new DateOnly(2020, 10, 25), query[1].Date); + Assert.Equal(new DateOnly(2021, 10, 4), query[2].Date); + + Assert.Equal(new DateOnly(2020, 9, 27), query[0].DateWithFormat); + Assert.Equal(new DateOnly(2020, 10, 25), query[1].DateWithFormat); + Assert.Equal(new DateOnly(2020, 6, 1), query[7].DateWithFormat); + } + + [Fact] + public void SheetDimensionsTest() + { + var path1 = PathHelper.GetFile("xlsx/TestTypeMapping.xlsx"); + var dim1 = _excelImporter.GetSheetDimensions(path1); + Assert.Equal("A1", dim1[0].StartCell); + Assert.Equal("H101", dim1[0].EndCell); + Assert.Equal(101, dim1[0].Rows.Count); + Assert.Equal(8, dim1[0].Columns.Count); + Assert.Equal(1, dim1[0].Rows.StartIndex); + Assert.Equal(101, dim1[0].Rows.EndIndex); + Assert.Equal(1, dim1[0].Columns.StartIndex); + Assert.Equal(8, dim1[0].Columns.EndIndex); + + var path2 = PathHelper.GetFile("xlsx/TestNoDimension.xlsx"); + var dim2 = _excelImporter.GetSheetDimensions(path2); + Assert.Equal(101, dim2[0].Rows.Count); + Assert.Equal(7, dim2[0].Columns.Count); + Assert.Equal(1, dim2[0].Rows.StartIndex); + Assert.Equal(101, dim2[0].Rows.EndIndex); + Assert.Equal(1, dim2[0].Columns.StartIndex); + Assert.Equal(7, dim2[0].Columns.EndIndex); + } + + [Fact] + public void SheetDimensionsTest_MultiSheet() + { + var path = PathHelper.GetFile("xlsx/TestMultiSheet.xlsx"); + var dim = _excelImporter.GetSheetDimensions(path); + + Assert.Equal("A1", dim[0].StartCell); + Assert.Equal("D12", dim[0].EndCell); + Assert.Equal(12, dim[0].Rows.Count); + Assert.Equal(4, dim[0].Columns.Count); + Assert.Equal(1, dim[0].Rows.StartIndex); + Assert.Equal(12, dim[0].Rows.EndIndex); + Assert.Equal(1, dim[0].Columns.StartIndex); + Assert.Equal(4, dim[0].Columns.EndIndex); + + Assert.Equal("A1", dim[1].StartCell); + Assert.Equal("D12", dim[1].EndCell); + Assert.Equal(12, dim[1].Rows.Count); + Assert.Equal(4, dim[1].Columns.Count); + Assert.Equal(1, dim[1].Rows.StartIndex); + Assert.Equal(12, dim[1].Rows.EndIndex); + Assert.Equal(1, dim[1].Columns.StartIndex); + Assert.Equal(4, dim[1].Columns.EndIndex); + + Assert.Equal("A1", dim[2].StartCell); + Assert.Equal("B5", dim[2].EndCell); + Assert.Equal(5, dim[2].Rows.Count); + Assert.Equal(2, dim[2].Columns.Count); + Assert.Equal(1, dim[2].Rows.StartIndex); + Assert.Equal(5, dim[2].Rows.EndIndex); + Assert.Equal(1, dim[2].Columns.StartIndex); + Assert.Equal(2, dim[2].Columns.EndIndex); + } + + [Fact] + public void ExportAndQueryFieldsStrongMappingTest() + { + using var path = AutoDeletingPath.Create(); + var input = Enumerable.Range(1, 3) + .Select(i => new ExcelFieldMappingTest + { + Test1 = $"T{i}", + Test2 = i, + Test = i + (decimal)i / 10 + }); + + _excelExporter.Export(path.ToString(), input); + + var rows = _excelImporter.Query(path.ToString()).ToList(); + Assert.Equal(3, rows.Count); + Assert.Equal("T1", rows[0].Test1); + Assert.Equal(1, rows[0].Test2); + Assert.Equal(1.1m, rows[0].Test); + } + + [Fact] + public void QueryFieldsAsDynamicTest() + { + using var path = AutoDeletingPath.Create(); + ExcelFieldMappingTest[] input = [new() { Test1 = "X1", Test2 = 5, Test = 7.3m }]; + + _excelExporter.Export(path.ToString(), input); + + var rows = _excelImporter.Query(path.ToString(), true).ToList(); + var first = rows[0] as IDictionary; + + // Column headers should include the column names from field attributes + Assert.Contains("Column1", first!.Keys); + Assert.Contains("Column2", first.Keys); + } +} diff --git a/tests/MiniExcel.OpenXml.Tests/Main/Models.cs b/tests/MiniExcel.OpenXml.Tests/Main/Models.cs index d05c51e0..f2456e08 100644 --- a/tests/MiniExcel.OpenXml.Tests/Main/Models.cs +++ b/tests/MiniExcel.OpenXml.Tests/Main/Models.cs @@ -236,3 +236,39 @@ internal class SaveAsFileWithDimensionByICollectionTestType public string? A { get; set; } public string? B { get; set; } } + +internal class DateOnlyTest +{ + public DateOnly Date { get; set; } + [MiniExcelFormat("d.M.yyyy")] public DateOnly DateWithFormat { get; set; } +} + +internal class ExcelFieldMappingTest +{ + [MiniExcelColumnName("Column1")] + public string? Test1; + + [MiniExcelColumnName("Column2")] + public int Test2; + + [MiniExcelColumnIndex(0)] + public decimal Test; +} + +internal class MixedFieldPropertyTest +{ + [MiniExcelColumnName("F1")] + public string? Field1; + + [MiniExcelColumnName("P1")] + public string? Prop1 { get; set; } +} + +internal class FieldsWithoutAttributeTest +{ + // field without attribute should not be included for export + public string? NotMappedField; + + [MiniExcelColumnName("Mapped")] + public string? MappedField; +} diff --git a/tests/MiniExcel.OpenXml.Tests/MiniExcel.OpenXml.Tests.csproj b/tests/MiniExcel.OpenXml.Tests/MiniExcel.OpenXml.Tests.csproj index 8bdd0da0..0622d62d 100644 --- a/tests/MiniExcel.OpenXml.Tests/MiniExcel.OpenXml.Tests.csproj +++ b/tests/MiniExcel.OpenXml.Tests/MiniExcel.OpenXml.Tests.csproj @@ -1,7 +1,7 @@  - net10.0 + net8.0;net9.0;net10.0 enable enable $(NoWarn);IDE0017;IDE0034;IDE0037;IDE0039;IDE0042;IDE0044;IDE0051;IDE0052;IDE0059;IDE0060;IDE0063;IDE1006;xUnit1004;CA1806;CA1816;CA1822;CA1825;CA1849;CA2000;CA2007;CA2208 @@ -11,6 +11,7 @@ true ..\..\src\miniexcel.snk MiniExcelLib.OpenXml.Tests + true @@ -19,6 +20,7 @@ + diff --git a/tests/MiniExcel.OpenXml.Tests/SheetInformations/MiniExcelOpenXmlSheetInformations.cs b/tests/MiniExcel.OpenXml.Tests/SheetInformations/MiniExcelOpenXmlSheetInformations.cs new file mode 100644 index 00000000..a34111a8 --- /dev/null +++ b/tests/MiniExcel.OpenXml.Tests/SheetInformations/MiniExcelOpenXmlSheetInformations.cs @@ -0,0 +1,116 @@ +using MiniExcelLib.OpenXml.Models; +using MiniExcelLib.OpenXml.Tests.Utils; +using MiniExcelLib.Tests.Common.Utils; +using System.Reflection; + +namespace MiniExcelLib.OpenXml.Tests.SheetInformations; + +public class MiniExcelOpenXmlSheetInformations +{ + private readonly OpenXmlImporter _excelImporter = MiniExcel.Importers.GetOpenXmlImporter(); + + [Fact] + public void GetSheetDimensionsTest() + { + var path = PathHelper.GetFile("xlsx/TestMultiSheet.xlsx"); + var dimensions = _excelImporter.GetSheetDimensions(path); + + Assert.NotNull(dimensions); + Assert.NotEmpty(dimensions); + Assert.Equal(3, dimensions.Count); + + Assert.Equal(12, dimensions[0].Rows.Count); + Assert.Equal(4, dimensions[0].Columns.Count); + Assert.Equal(5, dimensions[2].Rows.Count); + Assert.Equal(2, dimensions[2].Columns.Count); + } + + [Fact] + public void GetSheetDimensionsWithLeaveOpenTest() + { + var path = PathHelper.GetFile("xlsx/TestMultiSheet.xlsx"); + + using var stream = File.OpenRead(path); + var dimensions = _excelImporter.GetSheetDimensions(stream, leaveOpen: true); + + Assert.NotNull(dimensions); + Assert.Equal(3, dimensions.Count); + + Assert.Equal(12, dimensions[0].Rows.Count); + Assert.Equal(4, dimensions[0].Columns.Count); + Assert.Equal(5, dimensions[2].Rows.Count); + Assert.Equal(2, dimensions[2].Columns.Count); + + Assert.True(stream.CanRead); + } + + [Fact] + public void GetSheetInformationsTest() + { + var path = PathHelper.GetFile("xlsx/TestMultiSheet.xlsx"); + var sheetInfos = _excelImporter.GetSheetInformations(path); + + Assert.NotNull(sheetInfos); + Assert.NotEmpty(sheetInfos); + Assert.Equal(3, sheetInfos.Count); + + for (int i = 0; i < 3; i++) + { + Assert.Equal($"Sheet{i + 1}", sheetInfos[i].Name); + Assert.Equal((uint)i, sheetInfos[i].Index); + Assert.Equal(SheetState.Visible, sheetInfos[i].State); + } + } + + [Fact] + public void GetSheetInformationsWithLeaveOpenTest() + { + var path = PathHelper.GetFile("xlsx/TestMultiSheet.xlsx"); + + using var stream = File.OpenRead(path); + var sheetInfos = _excelImporter.GetSheetInformations(stream, leaveOpen: true); + + Assert.NotNull(sheetInfos); + Assert.NotEmpty(sheetInfos); + Assert.Equal(3, sheetInfos.Count); + + for (int i = 0; i < 3; i++) + { + Assert.Equal($"Sheet{i + 1}", sheetInfos[i].Name); + Assert.Equal((uint)i, sheetInfos[i].Index); + Assert.Equal(SheetState.Visible, sheetInfos[i].State); + } + + Assert.True(stream.CanRead); + } + + [Fact] + public void GetColumnNamesTest() + { + var path = PathHelper.GetFile("xlsx/TestTypeMapping.xlsx"); + + using var stream = File.OpenRead(path); + var columnNames = _excelImporter.GetColumnNames(stream, hasHeaderRow: true); + + Assert.Equal(["ID", "Name", "BoD", "Age", "VIP", "Mail", "Points", "IgnoredProperty"], columnNames); + } + + [Fact] + public void GetColumnNamesWithoutHeaderTest() + { + var path = PathHelper.GetFile("xlsx/TestTypeMapping.xlsx"); + + using var stream = File.OpenRead(path); + var columnNames = _excelImporter.GetColumnNames(path); + + Assert.Equal(["A", "B", "C", "D", "E", "F", "G", "H"], columnNames); + } + + [Fact] + public void GetColumnsFromEmptyFileTest() + { + var path = PathHelper.GetFile("xlsx/TestEmpty.xlsx"); + var columns = _excelImporter.GetColumnNames(path); + Assert.Empty(columns); + } +} diff --git a/tests/MiniExcel.OpenXml.Tests/SheetInformations/MiniExcelOpenXmlSheetInformationsAsync.cs b/tests/MiniExcel.OpenXml.Tests/SheetInformations/MiniExcelOpenXmlSheetInformationsAsync.cs new file mode 100644 index 00000000..3b3bfc40 --- /dev/null +++ b/tests/MiniExcel.OpenXml.Tests/SheetInformations/MiniExcelOpenXmlSheetInformationsAsync.cs @@ -0,0 +1,117 @@ +using DocumentFormat.OpenXml.Office2010.Excel; +using DocumentFormat.OpenXml.Spreadsheet; +using DocumentFormat.OpenXml.Wordprocessing; +using MiniExcelLib.OpenXml.Models; +using MiniExcelLib.Tests.Common.Utils; + +namespace MiniExcelLib.OpenXml.Tests.SheetInformations; + +public class MiniExcelOpenXmlSheetInformationsAsync +{ + private readonly OpenXmlImporter _excelImporter = MiniExcel.Importers.GetOpenXmlImporter(); + + [Fact] + public async Task GetSheetDimensionsAsyncTest() + { + var path = PathHelper.GetFile("xlsx/TestMultiSheet.xlsx"); + var dimensions = await _excelImporter.GetSheetDimensionsAsync(path); + + Assert.NotNull(dimensions); + Assert.NotEmpty(dimensions); + Assert.Equal(3, dimensions.Count); + + Assert.Equal(12, dimensions[0].Rows.Count); + Assert.Equal(4, dimensions[0].Columns.Count); + Assert.Equal(5, dimensions[2].Rows.Count); + Assert.Equal(2, dimensions[2].Columns.Count); + } + + [Fact] + public async Task GetSheetDimensionsAsyncWithLeaveOpenTest() + { + var path = PathHelper.GetFile("xlsx/TestMultiSheet.xlsx"); + + await using var stream = File.OpenRead(path); + var dimensions = await _excelImporter.GetSheetDimensionsAsync(stream, leaveOpen: true); + + Assert.NotNull(dimensions); + Assert.Equal(3, dimensions.Count); + + Assert.Equal(12, dimensions[0].Rows.Count); + Assert.Equal(4, dimensions[0].Columns.Count); + Assert.Equal(5, dimensions[2].Rows.Count); + Assert.Equal(2, dimensions[2].Columns.Count); + + Assert.True(stream.CanRead); + } + + [Fact] + public async Task GetSheetInformationsAsyncTest() + { + var path = PathHelper.GetFile("xlsx/TestMultiSheet.xlsx"); + var sheetInfos = await _excelImporter.GetSheetInformationsAsync(path); + + Assert.NotNull(sheetInfos); + Assert.NotEmpty(sheetInfos); + Assert.Equal(3, sheetInfos.Count); + + for (int i = 0; i < 3; i++) + { + Assert.Equal($"Sheet{i + 1}", sheetInfos[i].Name); + Assert.Equal((uint)i, sheetInfos[i].Index); + Assert.Equal(SheetState.Visible, sheetInfos[i].State); + } + } + + [Fact] + public async Task GetSheetInformationsAsyncWithLeaveOpenTest() + { + var path = PathHelper.GetFile("xlsx/TestMultiSheet.xlsx"); + + await using var stream = File.OpenRead(path); + var sheetInfos = await _excelImporter.GetSheetInformationsAsync(stream, leaveOpen: true); + + Assert.NotNull(sheetInfos); + Assert.NotEmpty(sheetInfos); + Assert.Equal(3, sheetInfos.Count); + + for (int i = 0; i < 3; i++) + { + Assert.Equal($"Sheet{i + 1}", sheetInfos[i].Name); + Assert.Equal((uint)i, sheetInfos[i].Index); + Assert.Equal(SheetState.Visible, sheetInfos[i].State); + } + + Assert.True(stream.CanRead); + } + + [Fact] + public async Task GetColumnNamesAsyncTest() + { + var path = PathHelper.GetFile("xlsx/TestTypeMapping.xlsx"); + + await using var stream = File.OpenRead(path); + var columnNames = await _excelImporter.GetColumnNamesAsync(stream, hasHeaderRow: true); + + Assert.Equal(["ID", "Name", "BoD", "Age", "VIP", "Mail", "Points", "IgnoredProperty"], columnNames); + } + + [Fact] + public async Task GetColumnNamesAsyncWithoutHeaderTest() + { + var path = PathHelper.GetFile("xlsx/TestTypeMapping.xlsx"); + + await using var stream = File.OpenRead(path); + var columnNames = await _excelImporter.GetColumnNamesAsync(path); + + Assert.Equal(["A", "B", "C", "D", "E", "F", "G", "H"], columnNames); + } + + [Fact] + public async Task GetColumnsFromEmptyFileAsyncTest() + { + var path = PathHelper.GetFile("xlsx/TestEmpty.xlsx"); + var columns = await _excelImporter.GetColumnNamesAsync(path); + Assert.Empty(columns); + } +} diff --git a/tests/MiniExcel.OpenXml.Tests/SaveByTemplate/InputValueExtractorTests.cs b/tests/MiniExcel.OpenXml.Tests/Templates/InputValueExtractorTests.cs similarity index 98% rename from tests/MiniExcel.OpenXml.Tests/SaveByTemplate/InputValueExtractorTests.cs rename to tests/MiniExcel.OpenXml.Tests/Templates/InputValueExtractorTests.cs index d1a4d925..54d73057 100644 --- a/tests/MiniExcel.OpenXml.Tests/SaveByTemplate/InputValueExtractorTests.cs +++ b/tests/MiniExcel.OpenXml.Tests/Templates/InputValueExtractorTests.cs @@ -1,6 +1,6 @@ using MiniExcelLib.OpenXml.Templates; -namespace MiniExcelLib.OpenXml.Tests.SaveByTemplate; +namespace MiniExcelLib.OpenXml.Tests.Templates; public class InputValueExtractorTests { diff --git a/tests/MiniExcel.OpenXml.Tests/SaveByTemplate/MiniExcelTemplateAsyncTests.cs b/tests/MiniExcel.OpenXml.Tests/Templates/MiniExcelTemplateAsyncTests.cs similarity index 92% rename from tests/MiniExcel.OpenXml.Tests/SaveByTemplate/MiniExcelTemplateAsyncTests.cs rename to tests/MiniExcel.OpenXml.Tests/Templates/MiniExcelTemplateAsyncTests.cs index 2b57e607..e6d3efd3 100644 --- a/tests/MiniExcel.OpenXml.Tests/SaveByTemplate/MiniExcelTemplateAsyncTests.cs +++ b/tests/MiniExcel.OpenXml.Tests/Templates/MiniExcelTemplateAsyncTests.cs @@ -1,13 +1,19 @@ -using MiniExcelLib.OpenXml.Tests.Utils; +using MiniExcelLib.OpenXml.Picture; +using MiniExcelLib.OpenXml.Tests.Utils; using MiniExcelLib.Tests.Common.Utils; -namespace MiniExcelLib.OpenXml.Tests.SaveByTemplate; +namespace MiniExcelLib.OpenXml.Tests.Templates; public class MiniExcelTemplateAsyncTests { private readonly OpenXmlImporter _excelImporter = MiniExcel.Importers.GetOpenXmlImporter(); private readonly OpenXmlTemplater _excelTemplater = MiniExcel.Templaters.GetOpenXmlTemplater(); + static MiniExcelTemplateAsyncTests() + { + ExcelPackage.LicenseContext = LicenseContext.NonCommercial; + } + [Fact] public async Task DatatableTemptyRowTest() { @@ -832,7 +838,12 @@ public async Task TemplateTest() new { name = "Keaton", department = "IT" } } }; - await _excelTemplater.FillTemplateAsync(path.ToString(), templatePath, value); + + await using (var stream = File.OpenWrite(path.FilePath)) + { + await using var templateStream = File.OpenRead(templatePath); + await _excelTemplater.FillTemplateAsync(stream, templateStream, value); + } var rows = await _excelImporter.QueryAsync(path.ToString()).ToListAsync(); Assert.Equal("FooCompany", rows[0].A); @@ -878,7 +889,6 @@ await Assert.ThrowsAsync(async () => }); } - [Fact] public async Task TestMergeSameCellsWithTagAsync() { @@ -899,7 +909,11 @@ public async Task TestMergeSameCellsWithLimitTagAsync() var path = PathHelper.GetFile("xlsx/TestMergeWithLimitTag.xlsx"); using var mergedFilePath = AutoDeletingPath.Create(); - await _excelTemplater.MergeSameCellsAsync(mergedFilePath.ToString(), path); + await using (var stream = new FileStream(mergedFilePath.FilePath, FileMode.OpenOrCreate, FileAccess.ReadWrite)) + { + await _excelTemplater.MergeSameCellsAsync(stream, path); + } + var mergedCells = SheetHelper.GetFirstSheetMergedCells(mergedFilePath.ToString()); Assert.Equal("A3:A4", mergedCells[0]); @@ -926,9 +940,30 @@ public async Task TestIEnumerableWithFormulas() new { name = "Joan", department = "IT", salary = 120000 } } }; - await _excelTemplater.FillTemplateAsync(path.FilePath, templatePath, value, true); + + await using (var templateStream = File.OpenRead(templatePath)) + { + await _excelTemplater.FillTemplateAsync(path.FilePath, templateStream, value, true); + } var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path.FilePath); Assert.Equal("A1:C13", dimension); } + + [Fact] + public async Task AddPictureTest() + { + MiniExcelPicture[] pictures = + [ + new() { ImageBytes = await File.ReadAllBytesAsync(PathHelper.GetFile("images/github_logo.png")), CellAddress = "B2", HeightPx = 40, WidthPx = 40 }, + new() { ImageBytes = await File.ReadAllBytesAsync(PathHelper.GetFile("images/google_logo.png")), CellAddress = "C3", HeightPx = 45, WidthPx = 45 }, + new() { ImageBytes = await File.ReadAllBytesAsync(PathHelper.GetFile("images/microsoft_logo.png")), CellAddress = "D4", HeightPx = 50, WidthPx = 50 }, + new() { ImageBytes = await File.ReadAllBytesAsync(PathHelper.GetFile("images/reddit_logo.png")), CellAddress = "E5", HeightPx = 55, WidthPx = 55 }, + ]; + + using var path = AutoDeletingPath.Create(); + await MiniExcel.Exporters.GetOpenXmlExporter().ExportAsync(path.ToString(), Array.Empty>()); + + await _excelTemplater.AddPictureAsync(path.ToString(), images: pictures); + } } diff --git a/tests/MiniExcel.OpenXml.Tests/SaveByTemplate/MiniExcelTemplateTests.cs b/tests/MiniExcel.OpenXml.Tests/Templates/MiniExcelTemplateTests.cs similarity index 96% rename from tests/MiniExcel.OpenXml.Tests/SaveByTemplate/MiniExcelTemplateTests.cs rename to tests/MiniExcel.OpenXml.Tests/Templates/MiniExcelTemplateTests.cs index 4f7ef879..e9399827 100644 --- a/tests/MiniExcel.OpenXml.Tests/SaveByTemplate/MiniExcelTemplateTests.cs +++ b/tests/MiniExcel.OpenXml.Tests/Templates/MiniExcelTemplateTests.cs @@ -4,7 +4,7 @@ using MiniExcelLib.Tests.Common.Utils; using OfficeOpenXml.Drawing; -namespace MiniExcelLib.OpenXml.Tests.SaveByTemplate; +namespace MiniExcelLib.OpenXml.Tests.Templates; public class MiniExcelTemplateTests { @@ -804,6 +804,7 @@ public void TemplateTest() var templatePath = PathHelper.GetFile("xlsx/TestTemplateComplex.xlsx"); { using var path = AutoDeletingPath.Create(); + using var templateStream = File.OpenRead(templatePath); // 1. By Class var value = new @@ -822,7 +823,7 @@ public void TemplateTest() new { name = "Keaton", department = "IT" } } }; - _excelTemplater.FillTemplate(path.ToString(), templatePath, value); + _excelTemplater.FillTemplate(path.ToString(), templateStream, value); { var rows = _excelImporter.Query(path.ToString()).ToList(); @@ -939,8 +940,9 @@ public void TestMergeSameCellsWithLimitTag() { var path = PathHelper.GetFile("xlsx/TestMergeWithLimitTag.xlsx"); using var mergedFilePath = AutoDeletingPath.Create(); + using var stream = new FileStream(mergedFilePath.FilePath, FileMode.OpenOrCreate, FileAccess.ReadWrite); - _excelTemplater.MergeSameCells(mergedFilePath.ToString(), path); + _excelTemplater.MergeSameCells(stream, path); var mergedCells = SheetHelper.GetFirstSheetMergedCells(mergedFilePath.ToString()); Assert.Equal("A3:A4", mergedCells[0]); diff --git a/tests/MiniExcel.OpenXml.Tests/SaveByTemplate/Models.cs b/tests/MiniExcel.OpenXml.Tests/Templates/Models.cs similarity index 93% rename from tests/MiniExcel.OpenXml.Tests/SaveByTemplate/Models.cs rename to tests/MiniExcel.OpenXml.Tests/Templates/Models.cs index d6329f1f..24bb12dd 100644 --- a/tests/MiniExcel.OpenXml.Tests/SaveByTemplate/Models.cs +++ b/tests/MiniExcel.OpenXml.Tests/Templates/Models.cs @@ -1,4 +1,4 @@ -namespace MiniExcelLib.OpenXml.Tests.SaveByTemplate; +namespace MiniExcelLib.OpenXml.Tests.Templates; internal class TestIEnumerableTypePoco { diff --git a/tests/MiniExcel.OpenXml.Tests/Utils/FileHelper.cs b/tests/MiniExcel.OpenXml.Tests/Utils/FileHelper.cs deleted file mode 100644 index 7631b1c3..00000000 --- a/tests/MiniExcel.OpenXml.Tests/Utils/FileHelper.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace MiniExcelLib.OpenXml.Tests.Utils; - -public static class FileHelper -{ - public static Stream OpenRead(string path) - { - try - { - return File.OpenRead(path); - } - catch (IOException) - { - var newPath = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid().ToString()}.xlsx"); - File.Copy(path, newPath); - return File.OpenRead(newPath); - } - } -} \ No newline at end of file diff --git a/tests/MiniExcel.OpenXml.Tests/Utils/SheetHelper.cs b/tests/MiniExcel.OpenXml.Tests/Utils/SheetHelper.cs index 4257f171..056bd99c 100644 --- a/tests/MiniExcel.OpenXml.Tests/Utils/SheetHelper.cs +++ b/tests/MiniExcel.OpenXml.Tests/Utils/SheetHelper.cs @@ -133,4 +133,4 @@ internal static MemoryStream CreateTestWorkbookStream() stream.Position = 0; return stream; } -} \ No newline at end of file +} diff --git a/tests/MiniExcel.Tests.Common/MiniExcel.Tests.Common.csproj b/tests/MiniExcel.Tests.Common/MiniExcel.Tests.Common.csproj index 190935a9..a961a4fb 100644 --- a/tests/MiniExcel.Tests.Common/MiniExcel.Tests.Common.csproj +++ b/tests/MiniExcel.Tests.Common/MiniExcel.Tests.Common.csproj @@ -1,7 +1,7 @@  - net10.0 + net8.0;net9.0;net10.0 enable enable MiniExcelLib.Tests.Common diff --git a/tests/data/xlsx/NotDuplicateSharedStrings_10x100000.xlsx b/tests/data/xlsx/NotDuplicateSharedStrings_10x100000.xlsx deleted file mode 100644 index 53a78e6f..00000000 Binary files a/tests/data/xlsx/NotDuplicateSharedStrings_10x100000.xlsx and /dev/null differ diff --git a/tests/data/xlsx/TestQueryRange.xlsx b/tests/data/xlsx/TestQueryRange.xlsx new file mode 100644 index 00000000..8052f20f Binary files /dev/null and b/tests/data/xlsx/TestQueryRange.xlsx differ diff --git a/tests/data/xlsx/bigExcel.xlsx b/tests/data/xlsx/bigExcel.xlsx deleted file mode 100644 index 732d9f34..00000000 Binary files a/tests/data/xlsx/bigExcel.xlsx and /dev/null differ