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