From dd18a054086ac368a262caf41cf672502bc46619 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Fri, 6 Feb 2026 17:27:09 -0500 Subject: [PATCH] Migrate ExcelIO from ClosedXML to NPOI --- .../JdeScoping.ExcelIO/ExcelExportService.cs | 447 +++++---- .../Formatting/HeaderFormatter.cs | 139 ++- .../Formatting/WorksheetProtector.cs | 143 +-- .../Generators/CriteriaSheetGenerator.cs | 327 +++---- .../Generators/DataEntryTemplateGenerator.cs | 174 ++-- .../Generators/FluentTableWriter.cs | 390 +++++--- .../JdeScoping.ExcelIO.csproj | 8 +- .../Parsing/ExcelParserService.cs | 302 +++--- .../Templates/ExcelTemplateService.cs | 155 ++-- .../Utilities/CellValueConverter.cs | 82 +- .../CriteriaSheetGeneratorTests.cs | 858 +++++++++--------- .../DataEntryTemplateGeneratorTests.cs | 282 +++--- .../ExcelExportServiceTests.cs | 477 +++++----- .../Fixtures/ExcelTestHelpers.cs | 157 +++- .../Fixtures/WorkbookFixtureBase.cs | 135 ++- .../HeaderFormatterTests.cs | 161 ++-- .../Integration/InvestigationSheetTests.cs | 173 ++-- .../Integration/LargeDataSetTests.cs | 48 +- .../Integration/MinimalSearchTests.cs | 96 +- .../Integration/MisInfoSheetTests.cs | 261 +++--- .../Integration/ProtectionAndStyleTests.cs | 152 ++-- .../Integration/SearchResultsSheetTests.cs | 179 ++-- .../JdeScoping.ExcelIO.Tests.csproj | 9 +- .../Parsing/ExcelParserServiceTests.cs | 350 ++++--- .../Templates/ExcelTemplateServiceTests.cs | 176 ++-- .../WorksheetProtectorTests.cs | 158 ++-- 26 files changed, 3034 insertions(+), 2805 deletions(-) diff --git a/NEW/src/JdeScoping.ExcelIO/ExcelExportService.cs b/NEW/src/JdeScoping.ExcelIO/ExcelExportService.cs index 39ba65c..abd1abc 100644 --- a/NEW/src/JdeScoping.ExcelIO/ExcelExportService.cs +++ b/NEW/src/JdeScoping.ExcelIO/ExcelExportService.cs @@ -1,234 +1,213 @@ -using ClosedXML.Excel; -using JdeScoping.Core.Interfaces; -using JdeScoping.Core.Models.SearchResults; -using JdeScoping.ExcelIO.Options; -using JdeScoping.ExcelIO.Formatting; -using JdeScoping.ExcelIO.Generators; -using JdeScoping.ExcelIO.Mapping; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; - -// Use Core's SearchModel for the public interface -using CoreSearchModel = JdeScoping.Core.Models.SearchResults.SearchModel; -// Use ExcelIO's SearchModel which contains criteria filter properties for CriteriaSheetGenerator -using ExcelSearchModel = JdeScoping.ExcelIO.Models.Reporting.SearchModel; - -namespace JdeScoping.ExcelIO; - -/// -/// Service for generating Excel export files from search results. -/// -public class ExcelExportService : IExcelExportService -{ - private readonly ILogger _logger; - private readonly IOptions _options; - private readonly CriteriaSheetGenerator _criteriaGenerator; - private readonly FluentTableWriter _tableWriter; - private readonly ExcelMapRegistry _registry; - - /// - /// Initializes a new instance of the ExcelExportService class. - /// - /// Logger instance. - /// Excel export options. - /// Criteria sheet generator. - /// Fluent table writer. - /// Excel map registry. - public ExcelExportService( - ILogger logger, - IOptions options, - CriteriaSheetGenerator criteriaGenerator, - FluentTableWriter tableWriter, - ExcelMapRegistry registry) - { - _logger = logger; - _options = options; - _criteriaGenerator = criteriaGenerator; - _tableWriter = tableWriter; - _registry = registry; - } - - /// - public async Task GenerateAsync(CoreSearchModel search, CancellationToken cancellationToken = default) - { - ArgumentNullException.ThrowIfNull(search); - - // Map Core SearchModel to ExcelIO SearchModel for internal processing - var excelModel = MapToExcelModel(search); - return await GenerateInternalAsync(excelModel, cancellationToken); - } - - /// - /// Maps Core SearchModel to ExcelIO SearchModel for Excel generation. - /// - private static ExcelSearchModel MapToExcelModel(CoreSearchModel source) - { - return new ExcelSearchModel - { - Id = source.Id, - UserName = source.UserName, - Name = source.Name, - SubmitDt = source.SubmitDt, - StartDt = source.StartDt, - EndDt = source.EndDt, - ExtractMisData = source.ExtractMisData, - Results = source.Results, - MisResults = source.MisResults, - MisNonMatchResults = source.MisNonMatchResults - }; - } - - /// - /// Internal method that generates an Excel file from the ExcelIO search model. - /// - /// ExcelIO search model with criteria and results. - /// Cancellation token. - /// Excel file as byte array. - private async Task GenerateInternalAsync(ExcelSearchModel search, CancellationToken cancellationToken = default) - { - using var scope = _logger.BeginScope(new Dictionary - { - ["SearchId"] = search.Id, - ["SearchName"] = search.Name - }); - - _logger.LogInformation("Starting Excel export generation"); - - // ClosedXML operations are synchronous, wrap in Task.Run for non-blocking - return await Task.Run(() => - { - cancellationToken.ThrowIfCancellationRequested(); - - using var workbook = new XLWorkbook(); - - // 1. Always generate Search Criteria sheet (first tab) - _logger.LogDebug("Generating Search Criteria sheet"); - _criteriaGenerator.Generate(workbook, search); - - cancellationToken.ThrowIfCancellationRequested(); - - // 2. Always generate Search Results sheet (second tab) - _logger.LogDebug("Generating Search Results sheet"); - GenerateResultsSheet(workbook, search.Results); - - cancellationToken.ThrowIfCancellationRequested(); - - // 3. Conditionally generate MIS Info sheet - if (search.ExtractMisData && search.MisResults != null && search.MisResults.Count > 0) - { - _logger.LogDebug("Generating MIS Info sheet with {Count} records", search.MisResults.Count); - GenerateMisInfoSheet(workbook, search.MisResults); - } - else if (search.ExtractMisData) - { - _logger.LogWarning("ExtractMisData is true but MisResults is null or empty"); - } - - cancellationToken.ThrowIfCancellationRequested(); - - // 4. Conditionally generate Investigation sheet - if (search.ExtractMisData && search.MisNonMatchResults != null && search.MisNonMatchResults.Count > 0) - { - _logger.LogDebug("Generating Investigation sheet with {Count} records", search.MisNonMatchResults.Count); - GenerateInvestigationSheet(workbook, search.MisNonMatchResults); - } - - cancellationToken.ThrowIfCancellationRequested(); - - // Save to byte array - using var stream = new MemoryStream(); - workbook.SaveAs(stream); - var result = stream.ToArray(); - - _logger.LogInformation("Excel export generation completed. Size: {Size} bytes", result.Length); - - // Optional: write debug copy to disk - if (_options.Value.DebugWriteToFile) - { - WriteDebugCopy(search.Id, result); - } - - return result; - - }, cancellationToken); - } - - private void GenerateResultsSheet(XLWorkbook workbook, List results) - { - var map = _registry.GetMap(); - var tabName = map.TabName ?? "Search Results"; - - var worksheet = workbook.Worksheets.Add(tabName); - var table = _tableWriter.WriteTable(worksheet, 1, 1, results); - - if (table != null) - { - // Apply protection with editable extension area - var lastRow = table.RangeAddress.LastAddress.RowNumber; - var lastCol = table.RangeAddress.LastAddress.ColumnNumber; - - WorksheetProtector.UnlockExtensionArea(worksheet, lastRow, lastCol); - WorksheetProtector.ApplyProtection(worksheet, _options.Value.DataSheetPassword); - } - } - - private void GenerateMisInfoSheet(XLWorkbook workbook, List misResults) - { - var map = _registry.GetMap(); - var tabName = map.TabName ?? "MIS Info"; - - var worksheet = workbook.Worksheets.Add(tabName); - var table = _tableWriter.WriteTable(worksheet, 1, 1, misResults); - - if (table != null) - { - // Apply protection with editable extension area - var lastRow = table.RangeAddress.LastAddress.RowNumber; - var lastCol = table.RangeAddress.LastAddress.ColumnNumber; - - WorksheetProtector.UnlockExtensionArea(worksheet, lastRow, lastCol); - WorksheetProtector.ApplyProtection(worksheet, _options.Value.DataSheetPassword); - } - } - - private void GenerateInvestigationSheet(XLWorkbook workbook, List misNonMatchResults) - { - var map = _registry.GetMap(); - var tabName = map.TabName ?? "Investigation"; - - var worksheet = workbook.Worksheets.Add(tabName); - var table = _tableWriter.WriteTable(worksheet, 1, 1, misNonMatchResults); - - if (table != null) - { - // Apply protection with editable extension area - var lastRow = table.RangeAddress.LastAddress.RowNumber; - var lastCol = table.RangeAddress.LastAddress.ColumnNumber; - - WorksheetProtector.UnlockExtensionArea(worksheet, lastRow, lastCol); - WorksheetProtector.ApplyProtection(worksheet, _options.Value.DataSheetPassword); - } - } - - private void WriteDebugCopy(int searchId, byte[] result) - { - try - { - var directory = _options.Value.DebugOutputDirectory; - if (!Directory.Exists(directory)) - { - Directory.CreateDirectory(directory); - } - - var debugPath = Path.Combine( - directory, - $"Search_{searchId}_{DateTime.Now:yyyyMMdd_HHmmss}.xlsx"); - File.WriteAllBytes(debugPath, result); - _logger.LogDebug("Debug copy written to {Path}", debugPath); - } - catch (Exception ex) - { - _logger.LogWarning(ex, "Failed to write debug copy"); - } - } -} +using JdeScoping.Core.Interfaces; +using JdeScoping.Core.Models.SearchResults; +using JdeScoping.ExcelIO.Options; +using JdeScoping.ExcelIO.Formatting; +using JdeScoping.ExcelIO.Generators; +using JdeScoping.ExcelIO.Mapping; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using NPOI.SS.UserModel; +using NPOI.XSSF.UserModel; + +using CoreSearchModel = JdeScoping.Core.Models.SearchResults.SearchModel; +using ExcelSearchModel = JdeScoping.ExcelIO.Models.Reporting.SearchModel; + +namespace JdeScoping.ExcelIO; + +/// +/// Service for generating Excel export files from search results. +/// +public class ExcelExportService : IExcelExportService +{ + private readonly ILogger _logger; + private readonly IOptions _options; + private readonly CriteriaSheetGenerator _criteriaGenerator; + private readonly FluentTableWriter _tableWriter; + private readonly ExcelMapRegistry _registry; + + /// + /// Initializes a new instance of the ExcelExportService class. + /// + /// Logger instance. + /// Excel export options. + /// Criteria sheet generator. + /// Fluent table writer. + /// Excel map registry. + public ExcelExportService( + ILogger logger, + IOptions options, + CriteriaSheetGenerator criteriaGenerator, + FluentTableWriter tableWriter, + ExcelMapRegistry registry) + { + _logger = logger; + _options = options; + _criteriaGenerator = criteriaGenerator; + _tableWriter = tableWriter; + _registry = registry; + } + + /// + public async Task GenerateAsync(CoreSearchModel search, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(search); + + var excelModel = MapToExcelModel(search); + return await GenerateInternalAsync(excelModel, cancellationToken); + } + + /// + /// Maps Core SearchModel to ExcelIO SearchModel for Excel generation. + /// + private static ExcelSearchModel MapToExcelModel(CoreSearchModel source) + { + return new ExcelSearchModel + { + Id = source.Id, + UserName = source.UserName, + Name = source.Name, + SubmitDt = source.SubmitDt, + StartDt = source.StartDt, + EndDt = source.EndDt, + ExtractMisData = source.ExtractMisData, + Results = source.Results, + MisResults = source.MisResults, + MisNonMatchResults = source.MisNonMatchResults + }; + } + + /// + /// Internal method that generates an Excel file from the ExcelIO search model. + /// + /// ExcelIO search model with criteria and results. + /// Cancellation token. + /// Excel file as byte array. + private async Task GenerateInternalAsync(ExcelSearchModel search, CancellationToken cancellationToken = default) + { + using var scope = _logger.BeginScope(new Dictionary + { + ["SearchId"] = search.Id, + ["SearchName"] = search.Name + }); + + _logger.LogInformation("Starting Excel export generation"); + + // NPOI operations are synchronous, wrap in Task.Run for non-blocking. + return await Task.Run(() => + { + cancellationToken.ThrowIfCancellationRequested(); + + using IWorkbook workbook = new XSSFWorkbook(); + + _logger.LogDebug("Generating Search Criteria sheet"); + _criteriaGenerator.Generate(workbook, search); + + cancellationToken.ThrowIfCancellationRequested(); + + _logger.LogDebug("Generating Search Results sheet"); + GenerateResultsSheet(workbook, search.Results); + + cancellationToken.ThrowIfCancellationRequested(); + + if (search.ExtractMisData && search.MisResults != null && search.MisResults.Count > 0) + { + _logger.LogDebug("Generating MIS Info sheet with {Count} records", search.MisResults.Count); + GenerateMisInfoSheet(workbook, search.MisResults); + } + else if (search.ExtractMisData) + { + _logger.LogWarning("ExtractMisData is true but MisResults is null or empty"); + } + + cancellationToken.ThrowIfCancellationRequested(); + + if (search.ExtractMisData && search.MisNonMatchResults != null && search.MisNonMatchResults.Count > 0) + { + _logger.LogDebug("Generating Investigation sheet with {Count} records", search.MisNonMatchResults.Count); + GenerateInvestigationSheet(workbook, search.MisNonMatchResults); + } + + cancellationToken.ThrowIfCancellationRequested(); + + using var stream = new MemoryStream(); + workbook.Write(stream, leaveOpen: true); + var result = stream.ToArray(); + + _logger.LogInformation("Excel export generation completed. Size: {Size} bytes", result.Length); + + if (_options.Value.DebugWriteToFile) + { + WriteDebugCopy(search.Id, result); + } + + return result; + }, cancellationToken); + } + + private void GenerateResultsSheet(IWorkbook workbook, List results) + { + var map = _registry.GetMap(); + var tabName = map.TabName ?? "Search Results"; + + var worksheet = workbook.CreateSheet(tabName); + var table = _tableWriter.WriteTable(worksheet, 1, 1, results); + + if (table != null) + { + WorksheetProtector.UnlockExtensionArea(worksheet, table.LastRow, table.LastCol); + WorksheetProtector.ApplyProtection(worksheet, _options.Value.DataSheetPassword); + } + } + + private void GenerateMisInfoSheet(IWorkbook workbook, List misResults) + { + var map = _registry.GetMap(); + var tabName = map.TabName ?? "MIS Info"; + + var worksheet = workbook.CreateSheet(tabName); + var table = _tableWriter.WriteTable(worksheet, 1, 1, misResults); + + if (table != null) + { + WorksheetProtector.UnlockExtensionArea(worksheet, table.LastRow, table.LastCol); + WorksheetProtector.ApplyProtection(worksheet, _options.Value.DataSheetPassword); + } + } + + private void GenerateInvestigationSheet(IWorkbook workbook, List misNonMatchResults) + { + var map = _registry.GetMap(); + var tabName = map.TabName ?? "Investigation"; + + var worksheet = workbook.CreateSheet(tabName); + var table = _tableWriter.WriteTable(worksheet, 1, 1, misNonMatchResults); + + if (table != null) + { + WorksheetProtector.UnlockExtensionArea(worksheet, table.LastRow, table.LastCol); + WorksheetProtector.ApplyProtection(worksheet, _options.Value.DataSheetPassword); + } + } + + private void WriteDebugCopy(int searchId, byte[] result) + { + try + { + var directory = _options.Value.DebugOutputDirectory; + if (!Directory.Exists(directory)) + { + Directory.CreateDirectory(directory); + } + + var debugPath = Path.Combine( + directory, + $"Search_{searchId}_{DateTime.Now:yyyyMMdd_HHmmss}.xlsx"); + File.WriteAllBytes(debugPath, result); + _logger.LogDebug("Debug copy written to {Path}", debugPath); + } + catch (Exception ex) + { + _logger.LogWarning(ex, "Failed to write debug copy"); + } + } +} diff --git a/NEW/src/JdeScoping.ExcelIO/Formatting/HeaderFormatter.cs b/NEW/src/JdeScoping.ExcelIO/Formatting/HeaderFormatter.cs index 31b5a42..06b046d 100644 --- a/NEW/src/JdeScoping.ExcelIO/Formatting/HeaderFormatter.cs +++ b/NEW/src/JdeScoping.ExcelIO/Formatting/HeaderFormatter.cs @@ -1,49 +1,90 @@ -using ClosedXML.Excel; - -namespace JdeScoping.ExcelIO.Formatting; - -/// -/// Header cell formatting utilities. -/// -public static class HeaderFormatter -{ - /// - /// Applies header formatting to a cell. - /// - /// The cell to format. - /// Optional text to set in the cell. - public static void ApplyHeaderFormat(IXLCell cell, string? text = null) - { - cell.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center; - cell.Style.Font.Bold = true; - cell.Style.Fill.BackgroundColor = XLColor.Gainsboro; - - if (!string.IsNullOrEmpty(text)) - { - cell.Value = text; - } - } - - /// - /// Applies header formatting to a range. - /// - /// The range to format. - /// Optional text to set in the first cell. - /// Whether to merge the range. - public static void ApplyHeaderFormat(IXLRange range, string? text = null, bool merge = false) - { - range.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center; - range.Style.Font.Bold = true; - range.Style.Fill.BackgroundColor = XLColor.Gainsboro; - - if (merge) - { - range.Merge(); - } - - if (!string.IsNullOrEmpty(text)) - { - range.FirstCell().Value = text; - } - } -} +using NPOI.SS.UserModel; +using NPOI.SS.Util; +using NPOI.XSSF.UserModel; + +namespace JdeScoping.ExcelIO.Formatting; + +/// +/// Header cell formatting utilities. +/// +public static class HeaderFormatter +{ + private static readonly byte[] GainsboroRgb = [0xDC, 0xDC, 0xDC]; + + /// + /// Applies header formatting to a cell. + /// + /// The cell to format. + /// Optional text to set in the cell. + public static void ApplyHeaderFormat(ICell cell, string? text = null) + { + var workbook = cell.Sheet.Workbook; + var style = workbook.CreateCellStyle(); + style.Alignment = HorizontalAlignment.Center; + style.FillPattern = FillPattern.SolidForeground; + + if (style is XSSFCellStyle xssfStyle) + { + var color = new XSSFColor(); + color.SetRgb(GainsboroRgb); + xssfStyle.SetFillForegroundColor(color); + } + else + { + style.FillForegroundColor = IndexedColors.Grey25Percent.Index; + } + + var font = workbook.CreateFont(); + font.IsBold = true; + style.SetFont(font); + + cell.CellStyle = style; + + if (!string.IsNullOrEmpty(text)) + { + cell.SetCellValue(text); + } + } + + /// + /// Applies header formatting to a range. + /// + /// The worksheet containing the range. + /// First row number (1-based). + /// First column number (1-based). + /// Last row number (1-based). + /// Last column number (1-based). + /// Optional text to set in the first cell. + /// Whether to merge the range. + public static void ApplyHeaderFormat( + ISheet sheet, + int firstRow, + int firstCol, + int lastRow, + int lastCol, + string? text = null, + bool merge = false) + { + for (var row = firstRow; row <= lastRow; row++) + { + var npoiRow = sheet.GetRow(row - 1) ?? sheet.CreateRow(row - 1); + for (var col = firstCol; col <= lastCol; col++) + { + var cell = npoiRow.GetCell(col - 1) ?? npoiRow.CreateCell(col - 1); + ApplyHeaderFormat(cell); + } + } + + if (merge) + { + sheet.AddMergedRegion(new CellRangeAddress(firstRow - 1, lastRow - 1, firstCol - 1, lastCol - 1)); + } + + if (!string.IsNullOrEmpty(text)) + { + var row = sheet.GetRow(firstRow - 1) ?? sheet.CreateRow(firstRow - 1); + var cell = row.GetCell(firstCol - 1) ?? row.CreateCell(firstCol - 1); + cell.SetCellValue(text); + } + } +} diff --git a/NEW/src/JdeScoping.ExcelIO/Formatting/WorksheetProtector.cs b/NEW/src/JdeScoping.ExcelIO/Formatting/WorksheetProtector.cs index f517151..206e344 100644 --- a/NEW/src/JdeScoping.ExcelIO/Formatting/WorksheetProtector.cs +++ b/NEW/src/JdeScoping.ExcelIO/Formatting/WorksheetProtector.cs @@ -1,62 +1,81 @@ -using ClosedXML.Excel; - -namespace JdeScoping.ExcelIO.Formatting; - -/// -/// Worksheet protection utilities. -/// -public static class WorksheetProtector -{ - /// - /// Applies standard protection to a worksheet with the given password. - /// - /// The worksheet to protect. - /// The protection password. - public static void ApplyProtection(IXLWorksheet worksheet, string password) - { - var protection = worksheet.Protect(password); - - // Allow these operations - protection.AllowElement(XLSheetProtectionElements.DeleteColumns); - protection.AllowElement(XLSheetProtectionElements.DeleteRows); - protection.AllowElement(XLSheetProtectionElements.AutoFilter); - protection.AllowElement(XLSheetProtectionElements.FormatCells); - protection.AllowElement(XLSheetProtectionElements.FormatColumns); - protection.AllowElement(XLSheetProtectionElements.FormatRows); - protection.AllowElement(XLSheetProtectionElements.SelectLockedCells); - protection.AllowElement(XLSheetProtectionElements.SelectUnlockedCells); - protection.AllowElement(XLSheetProtectionElements.EditObjects); - protection.AllowElement(XLSheetProtectionElements.Sort); - } - - /// - /// Applies criteria sheet protection (simpler, just password). - /// - /// The worksheet to protect. - /// The protection password. - public static void ApplyCriteriaProtection(IXLWorksheet worksheet, string password) - { - worksheet.Protect(password); - } - - /// - /// Unlocks a range for user editing beyond the data area. - /// - /// The worksheet containing the range. - /// The last row of data. - /// The last column of data. - /// Number of rows to unlock beyond data (default 1000). - /// Number of columns to unlock beyond data (default 1000). - public static void UnlockExtensionArea( - IXLWorksheet worksheet, - int lastDataRow, - int lastDataCol, - int extensionRows = 1000, - int extensionCols = 1000) - { - var extensionRange = worksheet.Range( - 1, lastDataCol + 1, - lastDataRow + extensionRows, lastDataCol + extensionCols); - extensionRange.Style.Protection.Locked = false; - } -} +using NPOI.SS.UserModel; +using NPOI.XSSF.UserModel; + +namespace JdeScoping.ExcelIO.Formatting; + +/// +/// Worksheet protection utilities. +/// +public static class WorksheetProtector +{ + /// + /// Applies standard protection to a worksheet with the given password. + /// + /// The worksheet to protect. + /// The protection password. + public static void ApplyProtection(ISheet worksheet, string password) + { + worksheet.ProtectSheet(password); + + if (worksheet is not XSSFSheet xssfSheet) + { + return; + } + + // Set to false to allow these operations while sheet protection is enabled. + xssfSheet.LockDeleteColumns(false); + xssfSheet.LockDeleteRows(false); + xssfSheet.LockAutoFilter(false); + xssfSheet.LockFormatCells(false); + xssfSheet.LockFormatColumns(false); + xssfSheet.LockFormatRows(false); + xssfSheet.LockSelectLockedCells(false); + xssfSheet.LockSelectUnlockedCells(false); + xssfSheet.LockObjects(false); + xssfSheet.LockSort(false); + xssfSheet.EnableLocking(); + } + + /// + /// Applies criteria sheet protection (simpler, just password). + /// + /// The worksheet to protect. + /// The protection password. + public static void ApplyCriteriaProtection(ISheet worksheet, string password) + { + worksheet.ProtectSheet(password); + } + + /// + /// Unlocks a range for user editing beyond the data area. + /// + /// The worksheet containing the range. + /// The last row of data. + /// The last column of data. + /// Number of rows to unlock beyond data (default 1000). + /// Number of columns to unlock beyond data (default 1000). + public static void UnlockExtensionArea( + ISheet worksheet, + int lastDataRow, + int lastDataCol, + int extensionRows = 1000, + int extensionCols = 1000) + { + var workbook = worksheet.Workbook; + var unlockedStyle = workbook.CreateCellStyle(); + unlockedStyle.IsLocked = false; + + // Apply unlocked default style to extension columns (NPOI uses 0-based columns). + var startCol = lastDataCol; + var endCol = lastDataCol + extensionCols - 1; + for (var col = startCol; col <= endCol; col++) + { + worksheet.SetDefaultColumnStyle(col, unlockedStyle); + } + + // Touch at least one extension cell to ensure style materializes in workbook XML. + var row = worksheet.GetRow(0) ?? worksheet.CreateRow(0); + var cell = row.GetCell(startCol) ?? row.CreateCell(startCol); + cell.CellStyle = unlockedStyle; + } +} diff --git a/NEW/src/JdeScoping.ExcelIO/Generators/CriteriaSheetGenerator.cs b/NEW/src/JdeScoping.ExcelIO/Generators/CriteriaSheetGenerator.cs index 9d8ad5d..7cdc030 100644 --- a/NEW/src/JdeScoping.ExcelIO/Generators/CriteriaSheetGenerator.cs +++ b/NEW/src/JdeScoping.ExcelIO/Generators/CriteriaSheetGenerator.cs @@ -1,193 +1,134 @@ -using ClosedXML.Excel; -using JdeScoping.ExcelIO.Options; -using JdeScoping.ExcelIO.Formatting; -using JdeScoping.ExcelIO.Models.Reporting; -using Microsoft.Extensions.Options; - -namespace JdeScoping.ExcelIO.Generators; - -/// -/// Generates the Search Criteria sheet for Excel export. -/// -public class CriteriaSheetGenerator -{ - private readonly IOptions _options; - private readonly FluentTableWriter _tableWriter; - - /// - /// Initializes a new instance of the CriteriaSheetGenerator class. - /// - /// Excel export options. - /// Fluent table writer. - public CriteriaSheetGenerator( - IOptions options, - FluentTableWriter tableWriter) - { - _options = options; - _tableWriter = tableWriter; - } - - /// - /// Generates the Search Criteria sheet. - /// - /// The workbook to add the sheet to. - /// The search model with criteria. - public void Generate(XLWorkbook workbook, SearchModel search) - { - var worksheet = workbook.Worksheets.Add("Search Criteria"); - var row = 1; - - // Write name and user - HeaderFormatter.ApplyHeaderFormat(worksheet.Cell(row, 1), "Search Name"); - worksheet.Cell(row, 2).Value = search.Name; - - HeaderFormatter.ApplyHeaderFormat(worksheet.Cell(++row, 1), "User Name"); - worksheet.Cell(row, 2).Value = search.UserName; - - // Skip row - row++; - - // Write timestamps - HeaderFormatter.ApplyHeaderFormat(worksheet.Cell(++row, 1), "Submit timestamp"); - worksheet.Cell(row, 2).Value = FormatTimestamp(search.SubmitDt); - - HeaderFormatter.ApplyHeaderFormat(worksheet.Cell(++row, 1), "Start timestamp"); - worksheet.Cell(row, 2).Value = FormatTimestamp(search.StartDt); - - HeaderFormatter.ApplyHeaderFormat(worksheet.Cell(++row, 1), "Completed timestamp"); - worksheet.Cell(row, 2).Value = FormatTimestamp(search.EndDt); - - // Skip row - row++; - - // Write timespan filter table - var timespanData = new List - { - new() { MinimumDt = search.MinimumDt, MaximumDt = search.MaximumDt } - }; - var timespanTable = _tableWriter.WriteTable(worksheet, ++row, 1, timespanData); - if (timespanTable != null) - { - row = timespanTable.RangeAddress.LastAddress.RowNumber + 3; - } - else - { - row += 4; - } - - // Write work order filter table - var workOrderTable = _tableWriter.WriteTable(worksheet, row, 1, search.WorkOrderFilter); - if (workOrderTable != null) - { - row = workOrderTable.RangeAddress.LastAddress.RowNumber + 3; - } - else - { - row += 4; - } - - // Write item number filter table - var itemNumberTable = _tableWriter.WriteTable(worksheet, row, 1, search.ItemNumberFilter); - if (itemNumberTable != null) - { - row = itemNumberTable.RangeAddress.LastAddress.RowNumber + 3; - } - else - { - row += 4; - } - - // Write profit center filter table - var profitCenterTable = _tableWriter.WriteTable(worksheet, row, 1, search.ProfitCenterFilter); - if (profitCenterTable != null) - { - row = profitCenterTable.RangeAddress.LastAddress.RowNumber + 3; - } - else - { - row += 4; - } - - // Write work center filter table - var workCenterTable = _tableWriter.WriteTable(worksheet, row, 1, search.WorkCenterFilter); - if (workCenterTable != null) - { - row = workCenterTable.RangeAddress.LastAddress.RowNumber + 3; - } - else - { - row += 4; - } - - // Write component lot filter table - var componentLotTable = _tableWriter.WriteTable(worksheet, row, 1, search.ComponentLotFilter); - if (componentLotTable != null) - { - row = componentLotTable.RangeAddress.LastAddress.RowNumber + 3; - } - else - { - row += 4; - } - - // Write operator filter table - var operatorTable = _tableWriter.WriteTable(worksheet, row, 1, search.OperatorFilter); - if (operatorTable != null) - { - row = operatorTable.RangeAddress.LastAddress.RowNumber + 3; - } - else - { - row += 4; - } - - // Write item/operation/MIS filter table - var itemOpMisTable = _tableWriter.WriteTable(worksheet, row, 1, search.ItemOperationMisFilter); - if (itemOpMisTable != null) - { - row = itemOpMisTable.RangeAddress.LastAddress.RowNumber + 3; - } - else - { - row += 4; - } - - // Write extract MIS data option - var headerRange = worksheet.Range(row, 1, row, 2); - HeaderFormatter.ApplyHeaderFormat(headerRange, "Extract MIS data?", merge: true); - worksheet.Cell(++row, 1).Value = search.ExtractMisData ? "YES" : "NO"; - - // Auto-fit columns with 15% padding - for (var column = 1; column <= 4; column++) - { - worksheet.Column(column).AdjustToContents(); - worksheet.Column(column).Width *= ExcelFormats.CriteriaPaddingFactor; - } - - // Apply protection - WorksheetProtector.ApplyCriteriaProtection(worksheet, _options.Value.CriteriaSheetPassword); - } - - private string FormatTimestamp(DateTime? dateTime) - { - if (!dateTime.HasValue) - { - return string.Empty; - } - - var options = _options.Value; - var targetTimezone = TimeZoneInfo.FindSystemTimeZoneById(options.TimezoneId); - var dt = dateTime.Value; - - // Convert to target timezone based on the source DateTime's Kind - var localTime = dt.Kind switch - { - DateTimeKind.Utc => TimeZoneInfo.ConvertTimeFromUtc(dt, targetTimezone), - DateTimeKind.Local => TimeZoneInfo.ConvertTime(dt, TimeZoneInfo.Local, targetTimezone), - // Unspecified - database values are stored in target timezone, no conversion needed - _ => dt - }; - - return $"{localTime:MMM dd, yyyy hh:mm:ss tt} {options.TimezoneAbbreviation}"; - } -} +using JdeScoping.ExcelIO.Options; +using JdeScoping.ExcelIO.Formatting; +using JdeScoping.ExcelIO.Models.Reporting; +using Microsoft.Extensions.Options; +using NPOI.SS.UserModel; + +namespace JdeScoping.ExcelIO.Generators; + +/// +/// Generates the Search Criteria sheet for Excel export. +/// +public class CriteriaSheetGenerator +{ + private const int ExcelMaxColumnWidth = 255 * 256; + + private readonly IOptions _options; + private readonly FluentTableWriter _tableWriter; + + /// + /// Initializes a new instance of the CriteriaSheetGenerator class. + /// + /// Excel export options. + /// Fluent table writer. + public CriteriaSheetGenerator( + IOptions options, + FluentTableWriter tableWriter) + { + _options = options; + _tableWriter = tableWriter; + } + + /// + /// Generates the Search Criteria sheet. + /// + /// The workbook to add the sheet to. + /// The search model with criteria. + public void Generate(IWorkbook workbook, SearchModel search) + { + var worksheet = workbook.CreateSheet("Search Criteria"); + var row = 1; + + HeaderFormatter.ApplyHeaderFormat(GetOrCreateCell(worksheet, row, 1), "Search Name"); + GetOrCreateCell(worksheet, row, 2).SetCellValue(search.Name); + + HeaderFormatter.ApplyHeaderFormat(GetOrCreateCell(worksheet, ++row, 1), "User Name"); + GetOrCreateCell(worksheet, row, 2).SetCellValue(search.UserName); + + row++; + + HeaderFormatter.ApplyHeaderFormat(GetOrCreateCell(worksheet, ++row, 1), "Submit timestamp"); + GetOrCreateCell(worksheet, row, 2).SetCellValue(FormatTimestamp(search.SubmitDt)); + + HeaderFormatter.ApplyHeaderFormat(GetOrCreateCell(worksheet, ++row, 1), "Start timestamp"); + GetOrCreateCell(worksheet, row, 2).SetCellValue(FormatTimestamp(search.StartDt)); + + HeaderFormatter.ApplyHeaderFormat(GetOrCreateCell(worksheet, ++row, 1), "Completed timestamp"); + GetOrCreateCell(worksheet, row, 2).SetCellValue(FormatTimestamp(search.EndDt)); + + row++; + + var timespanData = new List + { + new() { MinimumDt = search.MinimumDt, MaximumDt = search.MaximumDt } + }; + + var timespanTable = _tableWriter.WriteTable(worksheet, ++row, 1, timespanData); + row = GetNextTableStartRow(timespanTable, row); + + var workOrderTable = _tableWriter.WriteTable(worksheet, row, 1, search.WorkOrderFilter); + row = GetNextTableStartRow(workOrderTable, row); + + var itemNumberTable = _tableWriter.WriteTable(worksheet, row, 1, search.ItemNumberFilter); + row = GetNextTableStartRow(itemNumberTable, row); + + var profitCenterTable = _tableWriter.WriteTable(worksheet, row, 1, search.ProfitCenterFilter); + row = GetNextTableStartRow(profitCenterTable, row); + + var workCenterTable = _tableWriter.WriteTable(worksheet, row, 1, search.WorkCenterFilter); + row = GetNextTableStartRow(workCenterTable, row); + + var componentLotTable = _tableWriter.WriteTable(worksheet, row, 1, search.ComponentLotFilter); + row = GetNextTableStartRow(componentLotTable, row); + + var operatorTable = _tableWriter.WriteTable(worksheet, row, 1, search.OperatorFilter); + row = GetNextTableStartRow(operatorTable, row); + + var itemOpMisTable = _tableWriter.WriteTable(worksheet, row, 1, search.ItemOperationMisFilter); + row = GetNextTableStartRow(itemOpMisTable, row); + + HeaderFormatter.ApplyHeaderFormat(worksheet, row, 1, row, 2, "Extract MIS data?", merge: true); + GetOrCreateCell(worksheet, ++row, 1).SetCellValue(search.ExtractMisData ? "YES" : "NO"); + + for (var column = 1; column <= 4; column++) + { + worksheet.AutoSizeColumn(column - 1); + var padded = (int)(worksheet.GetColumnWidth(column - 1) * ExcelFormats.CriteriaPaddingFactor); + worksheet.SetColumnWidth(column - 1, padded > ExcelMaxColumnWidth ? ExcelMaxColumnWidth : padded); + } + + WorksheetProtector.ApplyCriteriaProtection(worksheet, _options.Value.CriteriaSheetPassword); + } + + private static int GetNextTableStartRow(TableWriteResult? result, int fallbackRow) + { + return result != null ? result.LastRow + 3 : fallbackRow + 4; + } + + private static ICell GetOrCreateCell(ISheet sheet, int rowNumber1Based, int colNumber1Based) + { + var row = sheet.GetRow(rowNumber1Based - 1) ?? sheet.CreateRow(rowNumber1Based - 1); + return row.GetCell(colNumber1Based - 1) ?? row.CreateCell(colNumber1Based - 1); + } + + private string FormatTimestamp(DateTime? dateTime) + { + if (!dateTime.HasValue) + { + return string.Empty; + } + + var options = _options.Value; + var targetTimezone = TimeZoneInfo.FindSystemTimeZoneById(options.TimezoneId); + var dt = dateTime.Value; + + var localTime = dt.Kind switch + { + DateTimeKind.Utc => TimeZoneInfo.ConvertTimeFromUtc(dt, targetTimezone), + DateTimeKind.Local => TimeZoneInfo.ConvertTime(dt, TimeZoneInfo.Local, targetTimezone), + _ => dt + }; + + return $"{localTime:MMM dd, yyyy hh:mm:ss tt} {options.TimezoneAbbreviation}"; + } +} diff --git a/NEW/src/JdeScoping.ExcelIO/Generators/DataEntryTemplateGenerator.cs b/NEW/src/JdeScoping.ExcelIO/Generators/DataEntryTemplateGenerator.cs index 0250a3b..5f9d6d1 100644 --- a/NEW/src/JdeScoping.ExcelIO/Generators/DataEntryTemplateGenerator.cs +++ b/NEW/src/JdeScoping.ExcelIO/Generators/DataEntryTemplateGenerator.cs @@ -1,82 +1,92 @@ -using ClosedXML.Excel; -using JdeScoping.ExcelIO.Formatting; -using JdeScoping.ExcelIO.Utilities; - -namespace JdeScoping.ExcelIO.Generators; - -/// -/// Generates data entry templates for bulk upload. -/// -public class DataEntryTemplateGenerator -{ - /// - /// Generates a single-column data entry template. - /// - /// The type of data items. - /// Optional source data to pre-populate. - /// Header text for the column. - /// The Excel file as a byte array. - public byte[] Generate(IEnumerable? sourceData, string headerText) - { - using var workbook = new XLWorkbook(); - var worksheet = workbook.Worksheets.Add("Data Entry Template"); - - // Header - var headerCell = worksheet.Cell(1, 1); - HeaderFormatter.ApplyHeaderFormat(headerCell, headerText); - worksheet.Column(1).Width = 45; - - // Data (if provided) - if (sourceData != null) - { - var row = 2; - foreach (var item in sourceData) - { - worksheet.Cell(row++, 1).Value = CellValueConverter.ConvertToXlValue(item); - } - } - - // All cells as text - worksheet.Column(1).Style.NumberFormat.Format = ExcelFormats.StdFormat; - - using var stream = new MemoryStream(); - workbook.SaveAs(stream); - return stream.ToArray(); - } - - /// - /// Generates a multi-column data entry template. - /// - /// Optional source data to pre-populate (array of rows, each row is array of values). - /// Header texts for each column. - /// The Excel file as a byte array. - public byte[] Generate(object[][]? sourceData, string[] headers) - { - using var workbook = new XLWorkbook(); - var worksheet = workbook.Worksheets.Add("Data Entry Template"); - - // Headers - for (var col = 0; col < headers.Length; col++) - { - HeaderFormatter.ApplyHeaderFormat(worksheet.Cell(1, col + 1), headers[col]); - worksheet.Column(col + 1).Width = 65; - worksheet.Column(col + 1).Style.NumberFormat.Format = ExcelFormats.StdFormat; - } - - // Data - if (sourceData != null) - { - for (var row = 0; row < sourceData.Length; row++) - { - for (var col = 0; col < sourceData[row].Length; col++) - { - worksheet.Cell(row + 2, col + 1).Value = CellValueConverter.ConvertToXlValue(sourceData[row][col]); - } - } - } - - using var stream = new MemoryStream(); - workbook.SaveAs(stream); - return stream.ToArray(); - } -} +using JdeScoping.ExcelIO.Formatting; +using JdeScoping.ExcelIO.Utilities; +using NPOI.SS.UserModel; +using NPOI.XSSF.UserModel; + +namespace JdeScoping.ExcelIO.Generators; + +/// +/// Generates data entry templates for bulk upload. +/// +public class DataEntryTemplateGenerator +{ + /// + /// Generates a single-column data entry template. + /// + /// The type of data items. + /// Optional source data to pre-populate. + /// Header text for the column. + /// The Excel file as a byte array. + public byte[] Generate(IEnumerable? sourceData, string headerText) + { + using var workbook = new XSSFWorkbook(); + var worksheet = workbook.CreateSheet("Data Entry Template"); + var style = workbook.CreateCellStyle(); + style.DataFormat = workbook.CreateDataFormat().GetFormat(ExcelFormats.StdFormat); + worksheet.SetDefaultColumnStyle(0, style); + + var headerCell = GetOrCreateCell(worksheet, 1, 1); + HeaderFormatter.ApplyHeaderFormat(headerCell, headerText); + worksheet.SetColumnWidth(0, 45 * 256); + + if (sourceData != null) + { + var row = 2; + foreach (var item in sourceData) + { + var cell = GetOrCreateCell(worksheet, row++, 1); + CellValueConverter.SetCellValue(cell, item); + } + } + + using var stream = new MemoryStream(); + workbook.Write(stream, leaveOpen: true); + return stream.ToArray(); + } + + /// + /// Generates a multi-column data entry template. + /// + /// Optional source data to pre-populate (array of rows, each row is array of values). + /// Header texts for each column. + /// The Excel file as a byte array. + public byte[] Generate(object[][]? sourceData, string[] headers) + { + using var workbook = new XSSFWorkbook(); + var worksheet = workbook.CreateSheet("Data Entry Template"); + + var dataFormat = workbook.CreateDataFormat(); + + for (var col = 0; col < headers.Length; col++) + { + var style = workbook.CreateCellStyle(); + style.DataFormat = dataFormat.GetFormat(ExcelFormats.StdFormat); + worksheet.SetDefaultColumnStyle(col, style); + + HeaderFormatter.ApplyHeaderFormat(GetOrCreateCell(worksheet, 1, col + 1), headers[col]); + worksheet.SetColumnWidth(col, 65 * 256); + } + + if (sourceData != null) + { + for (var row = 0; row < sourceData.Length; row++) + { + for (var col = 0; col < sourceData[row].Length; col++) + { + var cell = GetOrCreateCell(worksheet, row + 2, col + 1); + CellValueConverter.SetCellValue(cell, sourceData[row][col]); + } + } + } + + using var stream = new MemoryStream(); + workbook.Write(stream, leaveOpen: true); + return stream.ToArray(); + } + + private static ICell GetOrCreateCell(ISheet sheet, int rowNumber1Based, int colNumber1Based) + { + var row = sheet.GetRow(rowNumber1Based - 1) ?? sheet.CreateRow(rowNumber1Based - 1); + return row.GetCell(colNumber1Based - 1) ?? row.CreateCell(colNumber1Based - 1); + } +} diff --git a/NEW/src/JdeScoping.ExcelIO/Generators/FluentTableWriter.cs b/NEW/src/JdeScoping.ExcelIO/Generators/FluentTableWriter.cs index 1ef8c3c..c1111e8 100644 --- a/NEW/src/JdeScoping.ExcelIO/Generators/FluentTableWriter.cs +++ b/NEW/src/JdeScoping.ExcelIO/Generators/FluentTableWriter.cs @@ -1,142 +1,248 @@ -using ClosedXML.Excel; -using JdeScoping.ExcelIO.Formatting; -using JdeScoping.ExcelIO.Mapping; -using JdeScoping.ExcelIO.Utilities; - -namespace JdeScoping.ExcelIO.Generators; - -/// -/// Writes Excel tables using fluent mapping configuration. -/// -public sealed class FluentTableWriter -{ - private readonly ExcelMapRegistry _registry; - - /// - /// Initializes a new instance of the class. - /// - /// The registry containing Excel column maps for types. - public FluentTableWriter(ExcelMapRegistry registry) - { - _registry = registry; - } - - /// - /// Writes a table to the worksheet using the registered map for type T. - /// - /// The type of objects to write to the table. - /// The Excel worksheet to write to. - /// The starting row number (1-based). - /// The starting column number (1-based). - /// The data to write to the table. - /// Optional override for the table name; uses map name if null. - /// Whether to show a merged header above the table. - /// Optional text to display in the merged header. - /// The created Excel table, or null if no columns are configured. - public IXLTable? WriteTable( - IXLWorksheet worksheet, - int startRow, - int startCol, - IEnumerable data, - string? tableNameOverride = null, - bool showHeader = false, - string? headerText = null) - { - var map = _registry.GetMap(); - var columns = map.Columns; - var tableName = tableNameOverride ?? map.TableName ?? typeof(T).Name; - var header = headerText ?? map.TabName ?? string.Empty; - - if (columns.Count == 0) - return null; - - var dataList = data.ToList(); - var baseRow = startRow; - - // Write merged header if requested - if (showHeader && !string.IsNullOrEmpty(header)) - { - var mergedHeaderRange = worksheet.Range(baseRow, startCol, baseRow, startCol + columns.Count - 1); - HeaderFormatter.ApplyHeaderFormat(mergedHeaderRange, header, merge: true); - baseRow++; - } - - // Write column headers - var col = startCol; - foreach (var column in columns) - { - var cell = worksheet.Cell(baseRow, col); - HeaderFormatter.ApplyHeaderFormat(cell, column.HeaderText); - - // Pre-set column formatting - worksheet.Column(col).Style.Alignment.WrapText = column.WrapText; - if (!column.AutoWidth) - { - worksheet.Column(col).Width = column.Width; - } - - col++; - } - - // Write data rows - var row = baseRow + 1; - foreach (var item in dataList) - { - col = startCol; - foreach (var column in columns) - { - var value = column.ValueGetter(item!); - worksheet.Cell(row, col).Value = CellValueConverter.ConvertToXlValue(value); - col++; - } - row++; - } - - // Handle empty data case - if (dataList.Count == 0) - { - row = baseRow + 1; - } - - // Create table range - var dataRange = worksheet.Range( - baseRow, startCol, - baseRow + dataList.Count, startCol + columns.Count - 1); - - // Create table - var table = dataRange.CreateTable(tableName); - table.Theme = XLTableTheme.TableStyleLight18; - table.ShowTotalsRow = false; - - // Apply column formatting - col = startCol; - var tableStartRow = table.RangeAddress.FirstAddress.RowNumber; - var tableEndRow = table.RangeAddress.LastAddress.RowNumber; - - foreach (var column in columns) - { - // Apply number format - worksheet.Range(tableStartRow, col, tableEndRow, col) - .Style.NumberFormat.Format = column.Format; - - // Apply column width - if (column.WrapText && !column.AutoWidth) - { - worksheet.Column(col).Width = column.Width; - } - else if (column.AutoWidth) - { - worksheet.Column(col).AdjustToContents(); - worksheet.Column(col).Width *= ExcelFormats.DataPaddingFactor; - } - else - { - worksheet.Column(col).Width = column.Width; - } - - col++; - } - - return table; - } -} +using JdeScoping.ExcelIO.Formatting; +using JdeScoping.ExcelIO.Mapping; +using JdeScoping.ExcelIO.Utilities; +using NPOI.SS; +using NPOI.SS.UserModel; +using NPOI.SS.Util; +using NPOI.XSSF.UserModel; + +namespace JdeScoping.ExcelIO.Generators; + +/// +/// Result metadata for a written table region. +/// +public sealed class TableWriteResult +{ + public int FirstRow { get; init; } + public int LastRow { get; init; } + public int FirstCol { get; init; } + public int LastCol { get; init; } + public XSSFTable? Table { get; init; } +} + +/// +/// Writes Excel tables using fluent mapping configuration. +/// +public sealed class FluentTableWriter +{ + private const int ExcelMaxColumnWidth = 255 * 256; + + private readonly ExcelMapRegistry _registry; + + /// + /// Initializes a new instance of the class. + /// + /// The registry containing Excel column maps for types. + public FluentTableWriter(ExcelMapRegistry registry) + { + _registry = registry; + } + + /// + /// Writes a table to the worksheet using the registered map for type T. + /// + /// The type of objects to write to the table. + /// The Excel worksheet to write to. + /// The starting row number (1-based). + /// The starting column number (1-based). + /// The data to write to the table. + /// Optional override for the table name; uses map name if null. + /// Whether to show a merged header above the table. + /// Optional text to display in the merged header. + /// The created table metadata, or null if no columns are configured. + public TableWriteResult? WriteTable( + ISheet worksheet, + int startRow, + int startCol, + IEnumerable data, + string? tableNameOverride = null, + bool showHeader = false, + string? headerText = null) + { + var map = _registry.GetMap(); + var columns = map.Columns; + var tableName = tableNameOverride ?? map.TableName ?? typeof(T).Name; + var header = headerText ?? map.TabName ?? string.Empty; + + if (columns.Count == 0) + { + return null; + } + + var dataList = data.ToList(); + var baseRow = startRow; + + if (showHeader && !string.IsNullOrEmpty(header)) + { + HeaderFormatter.ApplyHeaderFormat( + worksheet, + baseRow, + startCol, + baseRow, + startCol + columns.Count - 1, + header, + merge: true); + baseRow++; + } + + var dataFormat = worksheet.Workbook.CreateDataFormat(); + var columnStyles = new ICellStyle[columns.Count]; + + // Write column headers and set column defaults. + for (var i = 0; i < columns.Count; i++) + { + var col = startCol + i; + var columnStyle = worksheet.Workbook.CreateCellStyle(); + if (!string.IsNullOrEmpty(columns[i].Format)) + { + columnStyle.DataFormat = dataFormat.GetFormat(columns[i].Format); + } + + columnStyle.WrapText = columns[i].WrapText; + columnStyles[i] = columnStyle; + + var headerRow = GetOrCreateRow(worksheet, baseRow); + var headerCell = GetOrCreateCell(headerRow, col); + HeaderFormatter.ApplyHeaderFormat(headerCell, columns[i].HeaderText); + } + + // Write data rows. + var row = baseRow + 1; + foreach (var item in dataList) + { + var npoiRow = GetOrCreateRow(worksheet, row); + + for (var i = 0; i < columns.Count; i++) + { + var col = startCol + i; + var column = columns[i]; + var value = column.ValueGetter(item!); + var cell = GetOrCreateCell(npoiRow, col); + CellValueConverter.SetCellValue(cell, value); + cell.CellStyle = columnStyles[i]; + } + + row++; + } + + if (dataList.Count == 0) + { + row = baseRow + 1; + } + + var firstRow = baseRow; + var lastRow = baseRow + dataList.Count; + var firstCol = startCol; + var lastCol = startCol + columns.Count - 1; + + var table = CreateTableIfSupported(worksheet, tableName, firstRow, lastRow, firstCol, lastCol); + + // Preserve filter behavior. + worksheet.SetAutoFilter(new CellRangeAddress(firstRow - 1, lastRow - 1, firstCol - 1, lastCol - 1)); + + // Apply column widths. + for (var i = 0; i < columns.Count; i++) + { + var column = columns[i]; + var colIndex = startCol + i - 1; + + if (column.WrapText && !column.AutoWidth) + { + worksheet.SetColumnWidth(colIndex, ToNpoiWidth(column.Width)); + } + else if (column.AutoWidth) + { + worksheet.AutoSizeColumn(colIndex); + var width = worksheet.GetColumnWidth(colIndex); + var padded = (int)(width * ExcelFormats.DataPaddingFactor); + worksheet.SetColumnWidth(colIndex, ClampWidth(padded)); + } + else + { + worksheet.SetColumnWidth(colIndex, ToNpoiWidth(column.Width)); + } + } + + return new TableWriteResult + { + FirstRow = firstRow, + LastRow = lastRow, + FirstCol = firstCol, + LastCol = lastCol, + Table = table + }; + } + + private static IRow GetOrCreateRow(ISheet sheet, int rowNumber1Based) + { + return sheet.GetRow(rowNumber1Based - 1) ?? sheet.CreateRow(rowNumber1Based - 1); + } + + private static ICell GetOrCreateCell(IRow row, int colNumber1Based) + { + return row.GetCell(colNumber1Based - 1) ?? row.CreateCell(colNumber1Based - 1); + } + + private static XSSFTable? CreateTableIfSupported( + ISheet worksheet, + string tableName, + int firstRow, + int lastRow, + int firstCol, + int lastCol) + { + if (worksheet is not XSSFSheet xssfSheet) + { + return null; + } + + var safeName = ToSafeTableName(tableName); + var area = new AreaReference( + new CellReference(firstRow - 1, firstCol - 1), + new CellReference(lastRow - 1, lastCol - 1), + SpreadsheetVersion.EXCEL2007); + + var table = xssfSheet.CreateTable(); + table.Name = safeName; + table.DisplayName = safeName; + table.StyleName = "TableStyleLight18"; + table.CellReferences = area; + table.UpdateReferences(); + table.UpdateHeaders(); + + return table; + } + + private static string ToSafeTableName(string tableName) + { + var chars = tableName.Where(ch => char.IsLetterOrDigit(ch) || ch == '_').ToArray(); + var value = new string(chars); + + if (string.IsNullOrWhiteSpace(value)) + { + return "Table1"; + } + + if (!char.IsLetter(value[0]) && value[0] != '_') + { + value = $"_{value}"; + } + + return value; + } + + private static int ToNpoiWidth(double widthInChars) + { + return ClampWidth((int)(widthInChars * 256)); + } + + private static int ClampWidth(int width) + { + if (width < 0) + { + return 0; + } + + return width > ExcelMaxColumnWidth ? ExcelMaxColumnWidth : width; + } +} diff --git a/NEW/src/JdeScoping.ExcelIO/JdeScoping.ExcelIO.csproj b/NEW/src/JdeScoping.ExcelIO/JdeScoping.ExcelIO.csproj index a7915c7..3655446 100644 --- a/NEW/src/JdeScoping.ExcelIO/JdeScoping.ExcelIO.csproj +++ b/NEW/src/JdeScoping.ExcelIO/JdeScoping.ExcelIO.csproj @@ -6,10 +6,10 @@ enable - - - - + + + + diff --git a/NEW/src/JdeScoping.ExcelIO/Parsing/ExcelParserService.cs b/NEW/src/JdeScoping.ExcelIO/Parsing/ExcelParserService.cs index d2e4525..bac0277 100644 --- a/NEW/src/JdeScoping.ExcelIO/Parsing/ExcelParserService.cs +++ b/NEW/src/JdeScoping.ExcelIO/Parsing/ExcelParserService.cs @@ -1,136 +1,166 @@ -using ClosedXML.Excel; -using JdeScoping.Core.Interfaces; -using JdeScoping.Core.ViewModels; -using Microsoft.Extensions.Logging; - -namespace JdeScoping.ExcelIO.Parsing; - -/// -/// Service for parsing Excel files uploaded by users. -/// -public class ExcelParserService : IExcelParserService -{ - private readonly ILogger _logger; - - /// - /// Initializes a new instance of the class. - /// - /// The logger instance. - public ExcelParserService(ILogger logger) - { - _logger = logger; - } - - /// - public List ParseWorkOrders(Stream fileStream) - { - using var workbook = new XLWorkbook(fileStream); - var worksheet = workbook.Worksheet(1); - - var workOrderNumbers = new List(); - var lastRow = worksheet.LastRowUsed()?.RowNumber() ?? 1; - - for (var row = 2; row <= lastRow; row++) - { - var cellValue = worksheet.Cell(row, 1).GetString()?.Trim(); - if (long.TryParse(cellValue, out var woNumber)) - { - workOrderNumbers.Add(woNumber); - } - } - - return workOrderNumbers; - } - - /// - public List ParseItems(Stream fileStream) - { - using var workbook = new XLWorkbook(fileStream); - var worksheet = workbook.Worksheet(1); - - var itemNumbers = new List(); - var lastRow = worksheet.LastRowUsed()?.RowNumber() ?? 1; - - for (var row = 2; row <= lastRow; row++) - { - var cellValue = worksheet.Cell(row, 1).GetString()?.Trim(); - if (!string.IsNullOrEmpty(cellValue)) - { - itemNumbers.Add(cellValue); - } - } - - return itemNumbers; - } - - /// - public List ParseComponentLots(Stream fileStream) - { - using var workbook = new XLWorkbook(fileStream); - var worksheet = workbook.Worksheet(1); - - var lotViewModels = new List(); - var lastRow = worksheet.LastRowUsed()?.RowNumber() ?? 1; - - for (var row = 2; row <= lastRow; row++) - { - var lotNumber = worksheet.Cell(row, 1).GetString()?.Trim() ?? string.Empty; - var itemNumber = worksheet.Cell(row, 2).GetString()?.Trim() ?? string.Empty; - - if (!string.IsNullOrEmpty(lotNumber)) - { - lotViewModels.Add(new LotViewModel - { - LotNumber = lotNumber, - ItemNumber = itemNumber - }); - } - } - - return lotViewModels; - } - - /// - public List ParsePartOperations(Stream fileStream) - { - using var workbook = new XLWorkbook(fileStream); - var worksheet = workbook.Worksheet(1); - - var partOperations = new List(); - var lastRow = worksheet.LastRowUsed()?.RowNumber() ?? 1; - - for (var row = 2; row <= lastRow; row++) - { - try - { - var itemNumber = worksheet.Cell(row, 1).GetString()?.Trim() ?? string.Empty; - var operationNumber = worksheet.Cell(row, 2).GetString()?.Trim() ?? string.Empty; - var misNumber = worksheet.Cell(row, 3).GetString()?.Trim() ?? string.Empty; - var misRevision = worksheet.Cell(row, 4).GetString()?.Trim() ?? string.Empty; - - // Remove decimal places from operation number - if (!string.IsNullOrEmpty(operationNumber) && operationNumber.Contains('.')) - { - operationNumber = operationNumber[..operationNumber.IndexOf('.')]; - } - - if (!string.IsNullOrEmpty(itemNumber) && !string.IsNullOrEmpty(operationNumber)) - { - partOperations.Add(new PartOperationViewModel - { - ItemNumber = itemNumber, - OperationNumber = operationNumber, - MisNumber = misNumber, - MisRevision = misRevision - }); - } - } - catch (Exception ex) - { - _logger.LogWarning(ex, "Failed to parse row {RowNumber} in part operations sheet", row); - } - } - - return partOperations; - } -} +using JdeScoping.Core.Interfaces; +using JdeScoping.Core.ViewModels; +using Microsoft.Extensions.Logging; +using NPOI.SS.UserModel; + +namespace JdeScoping.ExcelIO.Parsing; + +/// +/// Service for parsing Excel files uploaded by users. +/// +public class ExcelParserService : IExcelParserService +{ + private readonly ILogger _logger; + + /// + /// Initializes a new instance of the class. + /// + /// The logger instance. + public ExcelParserService(ILogger logger) + { + _logger = logger; + } + + /// + public List ParseWorkOrders(Stream fileStream) + { + using var workbook = WorkbookFactory.Create(ResetStream(fileStream)); + var worksheet = workbook.GetSheetAt(0); + var formatter = new DataFormatter(); + + var workOrderNumbers = new List(); + var lastRow = worksheet.LastRowNum + 1; + + for (var row = 2; row <= lastRow; row++) + { + var cellValue = GetCellText(worksheet, row, 1, formatter); + if (long.TryParse(cellValue, out var woNumber)) + { + workOrderNumbers.Add(woNumber); + } + } + + return workOrderNumbers; + } + + /// + public List ParseItems(Stream fileStream) + { + using var workbook = WorkbookFactory.Create(ResetStream(fileStream)); + var worksheet = workbook.GetSheetAt(0); + var formatter = new DataFormatter(); + + var itemNumbers = new List(); + var lastRow = worksheet.LastRowNum + 1; + + for (var row = 2; row <= lastRow; row++) + { + var cellValue = GetCellText(worksheet, row, 1, formatter); + if (!string.IsNullOrEmpty(cellValue)) + { + itemNumbers.Add(cellValue); + } + } + + return itemNumbers; + } + + /// + public List ParseComponentLots(Stream fileStream) + { + using var workbook = WorkbookFactory.Create(ResetStream(fileStream)); + var worksheet = workbook.GetSheetAt(0); + var formatter = new DataFormatter(); + + var lotViewModels = new List(); + var lastRow = worksheet.LastRowNum + 1; + + for (var row = 2; row <= lastRow; row++) + { + var lotNumber = GetCellText(worksheet, row, 1, formatter); + var itemNumber = GetCellText(worksheet, row, 2, formatter); + + if (!string.IsNullOrEmpty(lotNumber)) + { + lotViewModels.Add(new LotViewModel + { + LotNumber = lotNumber, + ItemNumber = itemNumber + }); + } + } + + return lotViewModels; + } + + /// + public List ParsePartOperations(Stream fileStream) + { + using var workbook = WorkbookFactory.Create(ResetStream(fileStream)); + var worksheet = workbook.GetSheetAt(0); + var formatter = new DataFormatter(); + + var partOperations = new List(); + var lastRow = worksheet.LastRowNum + 1; + + for (var row = 2; row <= lastRow; row++) + { + try + { + var itemNumber = GetCellText(worksheet, row, 1, formatter); + var operationNumber = GetCellText(worksheet, row, 2, formatter); + var misNumber = GetCellText(worksheet, row, 3, formatter); + var misRevision = GetCellText(worksheet, row, 4, formatter); + + if (!string.IsNullOrEmpty(operationNumber) && operationNumber.Contains('.')) + { + operationNumber = operationNumber[..operationNumber.IndexOf('.')]; + } + + if (!string.IsNullOrEmpty(itemNumber) && !string.IsNullOrEmpty(operationNumber)) + { + partOperations.Add(new PartOperationViewModel + { + ItemNumber = itemNumber, + OperationNumber = operationNumber, + MisNumber = misNumber, + MisRevision = misRevision + }); + } + } + catch (Exception ex) + { + _logger.LogWarning(ex, "Failed to parse row {RowNumber} in part operations sheet", row); + } + } + + return partOperations; + } + + private static string GetCellText(ISheet sheet, int rowNumber1Based, int colNumber1Based, DataFormatter formatter) + { + var row = sheet.GetRow(rowNumber1Based - 1); + if (row == null) + { + return string.Empty; + } + + var cell = row.GetCell(colNumber1Based - 1); + if (cell == null) + { + return string.Empty; + } + + return formatter.FormatCellValue(cell).Trim(); + } + + private static Stream ResetStream(Stream stream) + { + if (stream.CanSeek) + { + stream.Position = 0; + } + + return stream; + } +} diff --git a/NEW/src/JdeScoping.ExcelIO/Templates/ExcelTemplateService.cs b/NEW/src/JdeScoping.ExcelIO/Templates/ExcelTemplateService.cs index 8caa0c9..663cc2e 100644 --- a/NEW/src/JdeScoping.ExcelIO/Templates/ExcelTemplateService.cs +++ b/NEW/src/JdeScoping.ExcelIO/Templates/ExcelTemplateService.cs @@ -1,70 +1,85 @@ -using ClosedXML.Excel; -using JdeScoping.Core.Interfaces; - -namespace JdeScoping.ExcelIO.Templates; - -/// -/// Service for generating Excel template files for data entry. -/// -public class ExcelTemplateService : IExcelTemplateService -{ - /// - public byte[] GenerateSingleColumn(IEnumerable data, string headerText) - { - using var workbook = new XLWorkbook(); - var worksheet = workbook.Worksheets.Add("Template"); - - // Write header - worksheet.Cell(1, 1).Value = headerText; - worksheet.Cell(1, 1).Style.Font.Bold = true; - - // Write data - var row = 2; - foreach (var item in data) - { - worksheet.Cell(row, 1).Value = item?.ToString() ?? string.Empty; - row++; - } - - // Auto-fit column width - worksheet.Column(1).AdjustToContents(); - - using var stream = new MemoryStream(); - workbook.SaveAs(stream); - return stream.ToArray(); - } - - /// - public byte[] GenerateMultiColumn(object?[][] data, string[] headers) - { - using var workbook = new XLWorkbook(); - var worksheet = workbook.Worksheets.Add("Template"); - - // Write headers - for (var col = 0; col < headers.Length; col++) - { - worksheet.Cell(1, col + 1).Value = headers[col]; - worksheet.Cell(1, col + 1).Style.Font.Bold = true; - } - - // Write data - for (var row = 0; row < data.Length; row++) - { - for (var col = 0; col < data[row].Length; col++) - { - var value = data[row][col]; - if (value != null) - { - worksheet.Cell(row + 2, col + 1).Value = value.ToString(); - } - } - } - - // Auto-fit column widths - worksheet.Columns().AdjustToContents(); - - using var stream = new MemoryStream(); - workbook.SaveAs(stream); - return stream.ToArray(); - } -} +using JdeScoping.Core.Interfaces; +using NPOI.SS.UserModel; +using NPOI.XSSF.UserModel; + +namespace JdeScoping.ExcelIO.Templates; + +/// +/// Service for generating Excel template files for data entry. +/// +public class ExcelTemplateService : IExcelTemplateService +{ + /// + public byte[] GenerateSingleColumn(IEnumerable data, string headerText) + { + using var workbook = new XSSFWorkbook(); + var worksheet = workbook.CreateSheet("Template"); + + var headerCell = GetOrCreateCell(worksheet, 1, 1); + headerCell.SetCellValue(headerText); + + var boldStyle = workbook.CreateCellStyle(); + var boldFont = workbook.CreateFont(); + boldFont.IsBold = true; + boldStyle.SetFont(boldFont); + headerCell.CellStyle = boldStyle; + + var row = 2; + foreach (var item in data) + { + GetOrCreateCell(worksheet, row++, 1).SetCellValue(item?.ToString() ?? string.Empty); + } + + worksheet.AutoSizeColumn(0); + + using var stream = new MemoryStream(); + workbook.Write(stream, leaveOpen: true); + return stream.ToArray(); + } + + /// + public byte[] GenerateMultiColumn(object?[][] data, string[] headers) + { + using var workbook = new XSSFWorkbook(); + var worksheet = workbook.CreateSheet("Template"); + + var boldStyle = workbook.CreateCellStyle(); + var boldFont = workbook.CreateFont(); + boldFont.IsBold = true; + boldStyle.SetFont(boldFont); + + for (var col = 0; col < headers.Length; col++) + { + var headerCell = GetOrCreateCell(worksheet, 1, col + 1); + headerCell.SetCellValue(headers[col]); + headerCell.CellStyle = boldStyle; + } + + for (var row = 0; row < data.Length; row++) + { + for (var col = 0; col < data[row].Length; col++) + { + var value = data[row][col]; + if (value != null) + { + GetOrCreateCell(worksheet, row + 2, col + 1).SetCellValue(value.ToString()); + } + } + } + + for (var col = 0; col < headers.Length; col++) + { + worksheet.AutoSizeColumn(col); + } + + using var stream = new MemoryStream(); + workbook.Write(stream, leaveOpen: true); + return stream.ToArray(); + } + + private static ICell GetOrCreateCell(ISheet sheet, int rowNumber1Based, int colNumber1Based) + { + var row = sheet.GetRow(rowNumber1Based - 1) ?? sheet.CreateRow(rowNumber1Based - 1); + return row.GetCell(colNumber1Based - 1) ?? row.CreateCell(colNumber1Based - 1); + } +} diff --git a/NEW/src/JdeScoping.ExcelIO/Utilities/CellValueConverter.cs b/NEW/src/JdeScoping.ExcelIO/Utilities/CellValueConverter.cs index 4c17b0b..bbf0d92 100644 --- a/NEW/src/JdeScoping.ExcelIO/Utilities/CellValueConverter.cs +++ b/NEW/src/JdeScoping.ExcelIO/Utilities/CellValueConverter.cs @@ -1,31 +1,51 @@ -using ClosedXML.Excel; - -namespace JdeScoping.ExcelIO.Utilities; - -/// -/// Utility class for converting .NET objects to ClosedXML cell values. -/// -public static class CellValueConverter -{ - /// - /// Converts a .NET object to an XLCellValue for use in ClosedXML worksheets. - /// - /// The value to convert. - /// An XLCellValue suitable for setting as a cell value. - public static XLCellValue ConvertToXlValue(object? value) - { - return value switch - { - null => Blank.Value, - string s => s, - int i => i, - long l => l, - decimal d => d, - double dbl => dbl, - float f => f, - DateTime dt => dt, - bool b => b, - _ => value.ToString() ?? string.Empty - }; - } -} +using NPOI.SS.UserModel; + +namespace JdeScoping.ExcelIO.Utilities; + +/// +/// Utility class for converting .NET objects to NPOI cell values. +/// +public static class CellValueConverter +{ + /// + /// Sets an NPOI cell value from a .NET object. + /// + /// The target cell. + /// The value to write. + public static void SetCellValue(ICell cell, object? value) + { + switch (value) + { + case null: + cell.SetCellType(CellType.Blank); + break; + case string s: + cell.SetCellValue(s); + break; + case int i: + cell.SetCellValue(i); + break; + case long l: + cell.SetCellValue(l); + break; + case decimal d: + cell.SetCellValue((double)d); + break; + case double dbl: + cell.SetCellValue(dbl); + break; + case float f: + cell.SetCellValue(f); + break; + case DateTime dt: + cell.SetCellValue(dt); + break; + case bool b: + cell.SetCellValue(b); + break; + default: + cell.SetCellValue(value.ToString() ?? string.Empty); + break; + } + } +} diff --git a/NEW/tests/JdeScoping.ExcelIO.Tests/CriteriaSheetGeneratorTests.cs b/NEW/tests/JdeScoping.ExcelIO.Tests/CriteriaSheetGeneratorTests.cs index 91080e2..e368f98 100644 --- a/NEW/tests/JdeScoping.ExcelIO.Tests/CriteriaSheetGeneratorTests.cs +++ b/NEW/tests/JdeScoping.ExcelIO.Tests/CriteriaSheetGeneratorTests.cs @@ -1,431 +1,427 @@ -using ClosedXML.Excel; -using JdeScoping.ExcelIO.Options; -using JdeScoping.ExcelIO.Generators; -using JdeScoping.ExcelIO.Mapping; -using JdeScoping.ExcelIO.Mapping.Maps; -using JdeScoping.ExcelIO.Models.Reporting; -using Microsoft.Extensions.Options; -using Shouldly; -using Xunit; - -namespace JdeScoping.ExcelIO.Tests; - -public class CriteriaSheetGeneratorTests -{ - private readonly CriteriaSheetGenerator _generator; - private readonly IOptions _options; - - public CriteriaSheetGeneratorTests() - { - _options = Microsoft.Extensions.Options.Options.Create(new ExcelExportOptions - { - CriteriaSheetPassword = "TestPassword" - }); - - var registry = CreateTestRegistry(); - var tableWriter = new FluentTableWriter(registry); - _generator = new CriteriaSheetGenerator(_options, tableWriter); - } - - private static ExcelMapRegistry CreateTestRegistry() - { - var registry = new ExcelMapRegistry(); - registry.Register(new TimespanFilterMap()); - registry.Register(new WorkOrderFilterEntryMap()); - registry.Register(new ItemNumberFilterEntryMap()); - registry.Register(new ProfitCenterFilterEntryMap()); - registry.Register(new WorkCenterFilterEntryMap()); - registry.Register(new OperatorFilterEntryMap()); - registry.Register(new ComponentLotFilterEntryMap()); - registry.Register(new ItemOperationMisFilterEntryMap()); - return registry; - } - - [Fact] - public void Generate_CreatesSearchCriteriaSheet() - { - using var workbook = new XLWorkbook(); - var search = CreateMinimalSearchModel(); - - _generator.Generate(workbook, search); - - workbook.Worksheets.TryGetWorksheet("Search Criteria", out var worksheet).ShouldBeTrue(); - worksheet.ShouldNotBeNull(); - } - - [Fact] - public void Generate_ContainsSearchName() - { - using var workbook = new XLWorkbook(); - var search = CreateMinimalSearchModel(); - search.Name = "Test Search Name"; - - _generator.Generate(workbook, search); - - var worksheet = workbook.Worksheet("Search Criteria"); - worksheet.Cell(1, 1).Value.GetText().ShouldBe("Search Name"); - worksheet.Cell(1, 2).Value.GetText().ShouldBe("Test Search Name"); - } - - [Fact] - public void Generate_ContainsUserName() - { - using var workbook = new XLWorkbook(); - var search = CreateMinimalSearchModel(); - search.UserName = "testuser"; - - _generator.Generate(workbook, search); - - var worksheet = workbook.Worksheet("Search Criteria"); - worksheet.Cell(2, 1).Value.GetText().ShouldBe("User Name"); - worksheet.Cell(2, 2).Value.GetText().ShouldBe("testuser"); - } - - [Fact] - public void Generate_ContainsTimestamps() - { - using var workbook = new XLWorkbook(); - var search = CreateMinimalSearchModel(); - var submitDt = new DateTime(2024, 1, 15, 10, 30, 0); - var startDt = new DateTime(2024, 1, 15, 10, 31, 0); - var endDt = new DateTime(2024, 1, 15, 10, 35, 0); - search.SubmitDt = submitDt; - search.StartDt = startDt; - search.EndDt = endDt; - - _generator.Generate(workbook, search); - - var worksheet = workbook.Worksheet("Search Criteria"); - worksheet.Cell(4, 1).Value.GetText().ShouldBe("Submit timestamp"); - worksheet.Cell(4, 2).Value.GetText().ShouldContain("Jan 15, 2024"); - - worksheet.Cell(5, 1).Value.GetText().ShouldBe("Start timestamp"); - worksheet.Cell(5, 2).Value.GetText().ShouldContain("Jan 15, 2024"); - - worksheet.Cell(6, 1).Value.GetText().ShouldBe("Completed timestamp"); - worksheet.Cell(6, 2).Value.GetText().ShouldContain("Jan 15, 2024"); - } - - [Fact] - public void Generate_ContainsTimespanFilterTable() - { - using var workbook = new XLWorkbook(); - var search = CreateMinimalSearchModel(); - search.MinimumDt = new DateTime(2024, 1, 1); - search.MaximumDt = new DateTime(2024, 12, 31); - - _generator.Generate(workbook, search); - - var worksheet = workbook.Worksheet("Search Criteria"); - var tables = worksheet.Tables; - tables.Any(t => t.Name == "Timespan_Filter").ShouldBeTrue(); - } - - [Fact] - public void Generate_ContainsWorkOrderFilterTable() - { - using var workbook = new XLWorkbook(); - var search = CreateMinimalSearchModel(); - search.WorkOrderFilter.Add(new WorkOrderFilterEntry { WorkOrderNumber = 12345, ItemNumber = "ITEM-001" }); - - _generator.Generate(workbook, search); - - var worksheet = workbook.Worksheet("Search Criteria"); - var tables = worksheet.Tables; - tables.Any(t => t.Name == "Work_Order_Filter").ShouldBeTrue(); - } - - [Fact] - public void Generate_ContainsItemNumberFilterTable() - { - using var workbook = new XLWorkbook(); - var search = CreateMinimalSearchModel(); - search.ItemNumberFilter.Add(new ItemNumberFilterEntry { ItemNumber = "ITEM-001" }); - - _generator.Generate(workbook, search); - - var worksheet = workbook.Worksheet("Search Criteria"); - var tables = worksheet.Tables; - tables.Any(t => t.Name == "Item_Number_Filter").ShouldBeTrue(); - } - - [Fact] - public void Generate_ContainsProfitCenterFilterTable() - { - using var workbook = new XLWorkbook(); - var search = CreateMinimalSearchModel(); - search.ProfitCenterFilter.Add(new ProfitCenterFilterEntry { Code = "PC01", Description = "Profit Center 1" }); - - _generator.Generate(workbook, search); - - var worksheet = workbook.Worksheet("Search Criteria"); - var tables = worksheet.Tables; - tables.Any(t => t.Name == "Profit_Center_Filter").ShouldBeTrue(); - } - - [Fact] - public void Generate_ContainsWorkCenterFilterTable() - { - using var workbook = new XLWorkbook(); - var search = CreateMinimalSearchModel(); - search.WorkCenterFilter.Add(new WorkCenterFilterEntry { Code = "WC01", Description = "Work Center 1" }); - - _generator.Generate(workbook, search); - - var worksheet = workbook.Worksheet("Search Criteria"); - var tables = worksheet.Tables; - tables.Any(t => t.Name == "Work_Center_Filter").ShouldBeTrue(); - } - - [Fact] - public void Generate_ContainsOperatorFilterTable() - { - using var workbook = new XLWorkbook(); - var search = CreateMinimalSearchModel(); - search.OperatorFilter.Add(new OperatorFilterEntry { UserId = "OP01", FullName = "Operator 1" }); - - _generator.Generate(workbook, search); - - var worksheet = workbook.Worksheet("Search Criteria"); - var tables = worksheet.Tables; - tables.Any(t => t.Name == "Operator_Filter").ShouldBeTrue(); - } - - [Fact] - public void Generate_ContainsComponentLotFilterTable() - { - using var workbook = new XLWorkbook(); - var search = CreateMinimalSearchModel(); - search.ComponentLotFilter.Add(new ComponentLotFilterEntry { LotNumber = "LOT001" }); - - _generator.Generate(workbook, search); - - var worksheet = workbook.Worksheet("Search Criteria"); - var tables = worksheet.Tables; - tables.Any(t => t.Name == "Component_Lot_Filter").ShouldBeTrue(); - } - - [Fact] - public void Generate_ContainsItemOperationMisFilterTable() - { - using var workbook = new XLWorkbook(); - var search = CreateMinimalSearchModel(); - search.ItemOperationMisFilter.Add(new ItemOperationMisFilterEntry - { - ItemNumber = "ITEM-001", - OperationNumber = "10", - MisNumber = "MIS-001" - }); - - _generator.Generate(workbook, search); - - var worksheet = workbook.Worksheet("Search Criteria"); - var tables = worksheet.Tables; - tables.Any(t => t.Name == "Item_Operation_MIS_Filter").ShouldBeTrue(); - } - - [Fact] - public void Generate_ContainsExtractMisDataIndicator_WhenTrue() - { - using var workbook = new XLWorkbook(); - var search = CreateMinimalSearchModel(); - search.ExtractMisData = true; - - _generator.Generate(workbook, search); - - var worksheet = workbook.Worksheet("Search Criteria"); - // Find the "Extract MIS data?" header and check for YES - var cells = worksheet.CellsUsed(); - var extractMisCell = cells.FirstOrDefault(c => c.Value.ToString() == "YES" || c.Value.ToString() == "NO"); - extractMisCell.ShouldNotBeNull(); - extractMisCell.Value.GetText().ShouldBe("YES"); - } - - [Fact] - public void Generate_ContainsExtractMisDataIndicator_WhenFalse() - { - using var workbook = new XLWorkbook(); - var search = CreateMinimalSearchModel(); - search.ExtractMisData = false; - - _generator.Generate(workbook, search); - - var worksheet = workbook.Worksheet("Search Criteria"); - // Find the "Extract MIS data?" header and check for NO - var cells = worksheet.CellsUsed(); - var extractMisCell = cells.FirstOrDefault(c => c.Value.ToString() == "YES" || c.Value.ToString() == "NO"); - extractMisCell.ShouldNotBeNull(); - extractMisCell.Value.GetText().ShouldBe("NO"); - } - - [Fact] - public void Generate_AppliesHeaderFormatting() - { - using var workbook = new XLWorkbook(); - var search = CreateMinimalSearchModel(); - - _generator.Generate(workbook, search); - - var worksheet = workbook.Worksheet("Search Criteria"); - // Search Name header should be bold with Gainsboro background - var headerCell = worksheet.Cell(1, 1); - headerCell.Style.Font.Bold.ShouldBeTrue(); - headerCell.Style.Fill.BackgroundColor.ShouldBe(XLColor.Gainsboro); - } - - [Fact] - public void Generate_AppliesProtection() - { - using var workbook = new XLWorkbook(); - var search = CreateMinimalSearchModel(); - - _generator.Generate(workbook, search); - - var worksheet = workbook.Worksheet("Search Criteria"); - worksheet.Protection.IsProtected.ShouldBeTrue(); - } - - [Fact] - public void Generate_TablesHaveLight18Style() - { - using var workbook = new XLWorkbook(); - var search = CreateMinimalSearchModel(); - search.WorkOrderFilter.Add(new WorkOrderFilterEntry { WorkOrderNumber = 12345, ItemNumber = "ITEM-001" }); - - _generator.Generate(workbook, search); - - var worksheet = workbook.Worksheet("Search Criteria"); - var table = worksheet.Tables.First(t => t.Name == "Work_Order_Filter"); - table.Theme.ShouldBe(XLTableTheme.TableStyleLight18); - } - - [Fact] - public void Generate_FilterTables_Have2BlankRowSpacing() - { - using var workbook = new XLWorkbook(); - var search = CreateMinimalSearchModel(); - search.WorkOrderFilter.Add(new WorkOrderFilterEntry { WorkOrderNumber = 12345, ItemNumber = "ITEM-001" }); - search.ItemNumberFilter.Add(new ItemNumberFilterEntry { ItemNumber = "ITEM-001" }); - - _generator.Generate(workbook, search); - - var worksheet = workbook.Worksheet("Search Criteria"); - var woTable = worksheet.Tables.First(t => t.Name == "Work_Order_Filter"); - var itemTable = worksheet.Tables.First(t => t.Name == "Item_Number_Filter"); - - // There should be 2 blank rows between tables. With the header row of the next table, that's a gap of 3 - // Looking at CriteriaSheetGenerator: row = table.RangeAddress.LastAddress.RowNumber + 3 - // This means the next table starts 3 rows after the last row, leaving 2 blank rows in between - var gap = itemTable.RangeAddress.FirstAddress.RowNumber - woTable.RangeAddress.LastAddress.RowNumber; - // Gap includes header row of next table, so: 2 blank rows + 1 header = gap of 3 - // But with table header (Timespan_Filter has ShowHeader=true), add 1 more - gap.ShouldBeGreaterThanOrEqualTo(3); - } - - [Fact] - public void Generate_NullTimestamps_ShowEmptyValue() - { - using var workbook = new XLWorkbook(); - var search = CreateMinimalSearchModel(); - search.SubmitDt = null; - search.StartDt = null; - search.EndDt = null; - - _generator.Generate(workbook, search); - - var worksheet = workbook.Worksheet("Search Criteria"); - worksheet.Cell(4, 2).Value.ToString().ShouldBe(string.Empty); - worksheet.Cell(5, 2).Value.ToString().ShouldBe(string.Empty); - worksheet.Cell(6, 2).Value.ToString().ShouldBe(string.Empty); - } - - [Fact] - public void Generate_ColumnsAreAutoFitWithPadding() - { - using var workbook = new XLWorkbook(); - var search = CreateMinimalSearchModel(); - search.Name = "A Very Long Search Name That Needs Extra Width"; - - _generator.Generate(workbook, search); - - var worksheet = workbook.Worksheet("Search Criteria"); - // Columns should have been adjusted - verify they have non-default width - worksheet.Column(1).Width.ShouldBeGreaterThan(0); - worksheet.Column(2).Width.ShouldBeGreaterThan(0); - } - - [Fact] - public void Generate_MultipleFiltersWithData_CreatesAllTables() - { - using var workbook = new XLWorkbook(); - var search = CreateFullSearchModel(); - - _generator.Generate(workbook, search); - - var worksheet = workbook.Worksheet("Search Criteria"); - var tables = worksheet.Tables; - - // Should have 8 filter tables - tables.Count().ShouldBe(8); - tables.Any(t => t.Name == "Timespan_Filter").ShouldBeTrue(); - tables.Any(t => t.Name == "Work_Order_Filter").ShouldBeTrue(); - tables.Any(t => t.Name == "Item_Number_Filter").ShouldBeTrue(); - tables.Any(t => t.Name == "Profit_Center_Filter").ShouldBeTrue(); - tables.Any(t => t.Name == "Work_Center_Filter").ShouldBeTrue(); - tables.Any(t => t.Name == "Component_Lot_Filter").ShouldBeTrue(); - tables.Any(t => t.Name == "Operator_Filter").ShouldBeTrue(); - tables.Any(t => t.Name == "Item_Operation_MIS_Filter").ShouldBeTrue(); - } - - [Fact] - public void Generate_TimestampFormat_IncludesESTSuffix() - { - using var workbook = new XLWorkbook(); - var search = CreateMinimalSearchModel(); - search.SubmitDt = new DateTime(2024, 1, 15, 10, 30, 45); - - _generator.Generate(workbook, search); - - var worksheet = workbook.Worksheet("Search Criteria"); - var timestampValue = worksheet.Cell(4, 2).Value.GetText(); - timestampValue.ShouldContain("EST"); - timestampValue.ShouldContain("10:30:45"); - } - - private static SearchModel CreateMinimalSearchModel() - { - return new SearchModel - { - Id = 1, - Name = "Test Search", - UserName = "testuser", - SubmitDt = DateTime.Now.AddHours(-1), - StartDt = DateTime.Now.AddMinutes(-30), - EndDt = DateTime.Now, - ExtractMisData = false - }; - } - - private static SearchModel CreateFullSearchModel() - { - return new SearchModel - { - Id = 1, - Name = "Full Search", - UserName = "testuser", - SubmitDt = DateTime.Now.AddHours(-1), - StartDt = DateTime.Now.AddMinutes(-30), - EndDt = DateTime.Now, - MinimumDt = new DateTime(2024, 1, 1), - MaximumDt = new DateTime(2024, 12, 31), - ExtractMisData = true, - WorkOrderFilter = [new WorkOrderFilterEntry { WorkOrderNumber = 12345, ItemNumber = "ITEM-001" }], - ItemNumberFilter = [new ItemNumberFilterEntry { ItemNumber = "ITEM-001" }], - ProfitCenterFilter = [new ProfitCenterFilterEntry { Code = "PC01", Description = "Profit Center 1" }], - WorkCenterFilter = [new WorkCenterFilterEntry { Code = "WC01", Description = "Work Center 1" }], - OperatorFilter = [new OperatorFilterEntry { UserId = "OP01", FullName = "Operator 1" }], - ComponentLotFilter = [new ComponentLotFilterEntry { LotNumber = "LOT001" }], - ItemOperationMisFilter = [new ItemOperationMisFilterEntry { ItemNumber = "ITEM-001", OperationNumber = "10", MisNumber = "MIS-001" }] - }; - } -} +using JdeScoping.ExcelIO.Options; +using JdeScoping.ExcelIO.Generators; +using JdeScoping.ExcelIO.Mapping; +using JdeScoping.ExcelIO.Mapping.Maps; +using JdeScoping.ExcelIO.Models.Reporting; +using JdeScoping.ExcelIO.Tests.Fixtures; +using Microsoft.Extensions.Options; +using NPOI.XSSF.UserModel; +using Shouldly; +using Xunit; + +namespace JdeScoping.ExcelIO.Tests; + +public class CriteriaSheetGeneratorTests +{ + private readonly CriteriaSheetGenerator _generator; + + public CriteriaSheetGeneratorTests() + { + var options = Microsoft.Extensions.Options.Options.Create(new ExcelExportOptions + { + CriteriaSheetPassword = "TestPassword" + }); + + var registry = CreateTestRegistry(); + var tableWriter = new FluentTableWriter(registry); + _generator = new CriteriaSheetGenerator(options, tableWriter); + } + + private static ExcelMapRegistry CreateTestRegistry() + { + var registry = new ExcelMapRegistry(); + registry.Register(new TimespanFilterMap()); + registry.Register(new WorkOrderFilterEntryMap()); + registry.Register(new ItemNumberFilterEntryMap()); + registry.Register(new ProfitCenterFilterEntryMap()); + registry.Register(new WorkCenterFilterEntryMap()); + registry.Register(new OperatorFilterEntryMap()); + registry.Register(new ComponentLotFilterEntryMap()); + registry.Register(new ItemOperationMisFilterEntryMap()); + return registry; + } + + [Fact] + public void Generate_CreatesSearchCriteriaSheet() + { + using var workbook = new XSSFWorkbook(); + var search = CreateMinimalSearchModel(); + + _generator.Generate(workbook, search); + + workbook.GetSheet("Search Criteria").ShouldNotBeNull(); + } + + [Fact] + public void Generate_ContainsSearchName() + { + using var workbook = new XSSFWorkbook(); + var search = CreateMinimalSearchModel(); + search.Name = "Test Search Name"; + + _generator.Generate(workbook, search); + + var worksheet = workbook.GetSheet("Search Criteria"); + ExcelTestHelpers.GetCellText(worksheet, 1, 1).ShouldBe("Search Name"); + ExcelTestHelpers.GetCellText(worksheet, 1, 2).ShouldBe("Test Search Name"); + } + + [Fact] + public void Generate_ContainsUserName() + { + using var workbook = new XSSFWorkbook(); + var search = CreateMinimalSearchModel(); + search.UserName = "testuser"; + + _generator.Generate(workbook, search); + + var worksheet = workbook.GetSheet("Search Criteria"); + ExcelTestHelpers.GetCellText(worksheet, 2, 1).ShouldBe("User Name"); + ExcelTestHelpers.GetCellText(worksheet, 2, 2).ShouldBe("testuser"); + } + + [Fact] + public void Generate_ContainsTimestamps() + { + using var workbook = new XSSFWorkbook(); + var search = CreateMinimalSearchModel(); + search.SubmitDt = new DateTime(2024, 1, 15, 10, 30, 0); + search.StartDt = new DateTime(2024, 1, 15, 10, 31, 0); + search.EndDt = new DateTime(2024, 1, 15, 10, 35, 0); + + _generator.Generate(workbook, search); + + var worksheet = workbook.GetSheet("Search Criteria"); + ExcelTestHelpers.GetCellText(worksheet, 4, 1).ShouldBe("Submit timestamp"); + ExcelTestHelpers.GetCellText(worksheet, 4, 2).ShouldContain("Jan 15, 2024"); + ExcelTestHelpers.GetCellText(worksheet, 5, 1).ShouldBe("Start timestamp"); + ExcelTestHelpers.GetCellText(worksheet, 5, 2).ShouldContain("Jan 15, 2024"); + ExcelTestHelpers.GetCellText(worksheet, 6, 1).ShouldBe("Completed timestamp"); + ExcelTestHelpers.GetCellText(worksheet, 6, 2).ShouldContain("Jan 15, 2024"); + } + + [Fact] + public void Generate_ContainsTimespanFilterTable() + { + using var workbook = new XSSFWorkbook(); + var search = CreateMinimalSearchModel(); + search.MinimumDt = new DateTime(2024, 1, 1); + search.MaximumDt = new DateTime(2024, 12, 31); + + _generator.Generate(workbook, search); + + var worksheet = workbook.GetSheet("Search Criteria"); + ExcelTestHelpers.TableExists(worksheet, "Timespan_Filter").ShouldBeTrue(); + } + + [Fact] + public void Generate_ContainsWorkOrderFilterTable() + { + using var workbook = new XSSFWorkbook(); + var search = CreateMinimalSearchModel(); + search.WorkOrderFilter.Add(new WorkOrderFilterEntry { WorkOrderNumber = 12345, ItemNumber = "ITEM-001" }); + + _generator.Generate(workbook, search); + + var worksheet = workbook.GetSheet("Search Criteria"); + ExcelTestHelpers.TableExists(worksheet, "Work_Order_Filter").ShouldBeTrue(); + } + + [Fact] + public void Generate_ContainsItemNumberFilterTable() + { + using var workbook = new XSSFWorkbook(); + var search = CreateMinimalSearchModel(); + search.ItemNumberFilter.Add(new ItemNumberFilterEntry { ItemNumber = "ITEM-001" }); + + _generator.Generate(workbook, search); + + var worksheet = workbook.GetSheet("Search Criteria"); + ExcelTestHelpers.TableExists(worksheet, "Item_Number_Filter").ShouldBeTrue(); + } + + [Fact] + public void Generate_ContainsProfitCenterFilterTable() + { + using var workbook = new XSSFWorkbook(); + var search = CreateMinimalSearchModel(); + search.ProfitCenterFilter.Add(new ProfitCenterFilterEntry { Code = "PC01", Description = "Profit Center 1" }); + + _generator.Generate(workbook, search); + + var worksheet = workbook.GetSheet("Search Criteria"); + ExcelTestHelpers.TableExists(worksheet, "Profit_Center_Filter").ShouldBeTrue(); + } + + [Fact] + public void Generate_ContainsWorkCenterFilterTable() + { + using var workbook = new XSSFWorkbook(); + var search = CreateMinimalSearchModel(); + search.WorkCenterFilter.Add(new WorkCenterFilterEntry { Code = "WC01", Description = "Work Center 1" }); + + _generator.Generate(workbook, search); + + var worksheet = workbook.GetSheet("Search Criteria"); + ExcelTestHelpers.TableExists(worksheet, "Work_Center_Filter").ShouldBeTrue(); + } + + [Fact] + public void Generate_ContainsOperatorFilterTable() + { + using var workbook = new XSSFWorkbook(); + var search = CreateMinimalSearchModel(); + search.OperatorFilter.Add(new OperatorFilterEntry { UserId = "OP01", FullName = "Operator 1" }); + + _generator.Generate(workbook, search); + + var worksheet = workbook.GetSheet("Search Criteria"); + ExcelTestHelpers.TableExists(worksheet, "Operator_Filter").ShouldBeTrue(); + } + + [Fact] + public void Generate_ContainsComponentLotFilterTable() + { + using var workbook = new XSSFWorkbook(); + var search = CreateMinimalSearchModel(); + search.ComponentLotFilter.Add(new ComponentLotFilterEntry { LotNumber = "LOT001" }); + + _generator.Generate(workbook, search); + + var worksheet = workbook.GetSheet("Search Criteria"); + ExcelTestHelpers.TableExists(worksheet, "Component_Lot_Filter").ShouldBeTrue(); + } + + [Fact] + public void Generate_ContainsItemOperationMisFilterTable() + { + using var workbook = new XSSFWorkbook(); + var search = CreateMinimalSearchModel(); + search.ItemOperationMisFilter.Add(new ItemOperationMisFilterEntry + { + ItemNumber = "ITEM-001", + OperationNumber = "10", + MisNumber = "MIS-001" + }); + + _generator.Generate(workbook, search); + + var worksheet = workbook.GetSheet("Search Criteria"); + ExcelTestHelpers.TableExists(worksheet, "Item_Operation_MIS_Filter").ShouldBeTrue(); + } + + [Fact] + public void Generate_ContainsExtractMisDataIndicator_WhenTrue() + { + using var workbook = new XSSFWorkbook(); + var search = CreateMinimalSearchModel(); + search.ExtractMisData = true; + + _generator.Generate(workbook, search); + + var worksheet = workbook.GetSheet("Search Criteria"); + var yesNo = FindYesNo(worksheet); + yesNo.ShouldBe("YES"); + } + + [Fact] + public void Generate_ContainsExtractMisDataIndicator_WhenFalse() + { + using var workbook = new XSSFWorkbook(); + var search = CreateMinimalSearchModel(); + search.ExtractMisData = false; + + _generator.Generate(workbook, search); + + var worksheet = workbook.GetSheet("Search Criteria"); + var yesNo = FindYesNo(worksheet); + yesNo.ShouldBe("NO"); + } + + [Fact] + public void Generate_AppliesHeaderFormatting() + { + using var workbook = new XSSFWorkbook(); + var search = CreateMinimalSearchModel(); + + _generator.Generate(workbook, search); + + var worksheet = workbook.GetSheet("Search Criteria"); + var headerCell = ExcelTestHelpers.GetCell(worksheet, 1, 1)!; + (headerCell.CellStyle.FontIndex >= 0 && + workbook.GetFontAt(headerCell.CellStyle.FontIndex).IsBold).ShouldBeTrue(); + ExcelTestHelpers.GetFillForegroundRgb(headerCell).ShouldBe([0xDC, 0xDC, 0xDC]); + } + + [Fact] + public void Generate_AppliesProtection() + { + using var workbook = new XSSFWorkbook(); + var search = CreateMinimalSearchModel(); + + _generator.Generate(workbook, search); + + var worksheet = workbook.GetSheet("Search Criteria"); + ExcelTestHelpers.IsSheetProtected(worksheet).ShouldBeTrue(); + } + + [Fact] + public void Generate_TablesHaveLight18Style() + { + using var workbook = new XSSFWorkbook(); + var search = CreateMinimalSearchModel(); + search.WorkOrderFilter.Add(new WorkOrderFilterEntry { WorkOrderNumber = 12345, ItemNumber = "ITEM-001" }); + + _generator.Generate(workbook, search); + + var worksheet = workbook.GetSheet("Search Criteria"); + var table = ExcelTestHelpers.GetTableByName(worksheet, "Work_Order_Filter"); + table.StyleName.ShouldBe("TableStyleLight18"); + } + + [Fact] + public void Generate_FilterTables_Have2BlankRowSpacing() + { + using var workbook = new XSSFWorkbook(); + var search = CreateMinimalSearchModel(); + search.WorkOrderFilter.Add(new WorkOrderFilterEntry { WorkOrderNumber = 12345, ItemNumber = "ITEM-001" }); + search.ItemNumberFilter.Add(new ItemNumberFilterEntry { ItemNumber = "ITEM-001" }); + + _generator.Generate(workbook, search); + + var worksheet = workbook.GetSheet("Search Criteria"); + var woTable = ExcelTestHelpers.GetTableByName(worksheet, "Work_Order_Filter"); + var itemTable = ExcelTestHelpers.GetTableByName(worksheet, "Item_Number_Filter"); + + var gap = itemTable.StartCellReference.Row - woTable.EndCellReference.Row; + gap.ShouldBeGreaterThanOrEqualTo(3); + } + + [Fact] + public void Generate_NullTimestamps_ShowEmptyValue() + { + using var workbook = new XSSFWorkbook(); + var search = CreateMinimalSearchModel(); + search.SubmitDt = null; + search.StartDt = null; + search.EndDt = null; + + _generator.Generate(workbook, search); + + var worksheet = workbook.GetSheet("Search Criteria"); + ExcelTestHelpers.GetCellText(worksheet, 4, 2).ShouldBe(string.Empty); + ExcelTestHelpers.GetCellText(worksheet, 5, 2).ShouldBe(string.Empty); + ExcelTestHelpers.GetCellText(worksheet, 6, 2).ShouldBe(string.Empty); + } + + [Fact] + public void Generate_ColumnsAreAutoFitWithPadding() + { + using var workbook = new XSSFWorkbook(); + var search = CreateMinimalSearchModel(); + search.Name = "A Very Long Search Name That Needs Extra Width"; + + _generator.Generate(workbook, search); + + var worksheet = workbook.GetSheet("Search Criteria"); + ExcelTestHelpers.GetColumnWidthChars(worksheet, 1).ShouldBeGreaterThan(0); + ExcelTestHelpers.GetColumnWidthChars(worksheet, 2).ShouldBeGreaterThan(0); + } + + [Fact] + public void Generate_MultipleFiltersWithData_CreatesAllTables() + { + using var workbook = new XSSFWorkbook(); + var search = CreateFullSearchModel(); + + _generator.Generate(workbook, search); + + var worksheet = workbook.GetSheet("Search Criteria"); + var tableCount = ((XSSFSheet)worksheet).GetTables().Count; + + tableCount.ShouldBe(8); + ExcelTestHelpers.TableExists(worksheet, "Timespan_Filter").ShouldBeTrue(); + ExcelTestHelpers.TableExists(worksheet, "Work_Order_Filter").ShouldBeTrue(); + ExcelTestHelpers.TableExists(worksheet, "Item_Number_Filter").ShouldBeTrue(); + ExcelTestHelpers.TableExists(worksheet, "Profit_Center_Filter").ShouldBeTrue(); + ExcelTestHelpers.TableExists(worksheet, "Work_Center_Filter").ShouldBeTrue(); + ExcelTestHelpers.TableExists(worksheet, "Component_Lot_Filter").ShouldBeTrue(); + ExcelTestHelpers.TableExists(worksheet, "Operator_Filter").ShouldBeTrue(); + ExcelTestHelpers.TableExists(worksheet, "Item_Operation_MIS_Filter").ShouldBeTrue(); + } + + [Fact] + public void Generate_TimestampFormat_IncludesESTSuffix() + { + using var workbook = new XSSFWorkbook(); + var search = CreateMinimalSearchModel(); + search.SubmitDt = new DateTime(2024, 1, 15, 10, 30, 45); + + _generator.Generate(workbook, search); + + var worksheet = workbook.GetSheet("Search Criteria"); + var timestampValue = ExcelTestHelpers.GetCellText(worksheet, 4, 2); + timestampValue.ShouldContain("EST"); + timestampValue.ShouldContain("10:30:45"); + } + + private static string FindYesNo(NPOI.SS.UserModel.ISheet worksheet) + { + for (var row = 0; row <= worksheet.LastRowNum; row++) + { + var npoiRow = worksheet.GetRow(row); + if (npoiRow == null) + { + continue; + } + + for (var col = 0; col < npoiRow.LastCellNum; col++) + { + var text = ExcelTestHelpers.GetCellText(worksheet, row + 1, col + 1); + if (text == "YES" || text == "NO") + { + return text; + } + } + } + + return string.Empty; + } + + private static SearchModel CreateMinimalSearchModel() + { + return new SearchModel + { + Id = 1, + Name = "Test Search", + UserName = "testuser", + SubmitDt = DateTime.Now.AddHours(-1), + StartDt = DateTime.Now.AddMinutes(-30), + EndDt = DateTime.Now, + ExtractMisData = false + }; + } + + private static SearchModel CreateFullSearchModel() + { + return new SearchModel + { + Id = 1, + Name = "Full Search", + UserName = "testuser", + SubmitDt = DateTime.Now.AddHours(-1), + StartDt = DateTime.Now.AddMinutes(-30), + EndDt = DateTime.Now, + MinimumDt = new DateTime(2024, 1, 1), + MaximumDt = new DateTime(2024, 12, 31), + ExtractMisData = true, + WorkOrderFilter = [new WorkOrderFilterEntry { WorkOrderNumber = 12345, ItemNumber = "ITEM-001" }], + ItemNumberFilter = [new ItemNumberFilterEntry { ItemNumber = "ITEM-001" }], + ProfitCenterFilter = [new ProfitCenterFilterEntry { Code = "PC01", Description = "Profit Center 1" }], + WorkCenterFilter = [new WorkCenterFilterEntry { Code = "WC01", Description = "Work Center 1" }], + OperatorFilter = [new OperatorFilterEntry { UserId = "OP01", FullName = "Operator 1" }], + ComponentLotFilter = [new ComponentLotFilterEntry { LotNumber = "LOT001" }], + ItemOperationMisFilter = [new ItemOperationMisFilterEntry { ItemNumber = "ITEM-001", OperationNumber = "10", MisNumber = "MIS-001" }] + }; + } +} diff --git a/NEW/tests/JdeScoping.ExcelIO.Tests/DataEntryTemplateGeneratorTests.cs b/NEW/tests/JdeScoping.ExcelIO.Tests/DataEntryTemplateGeneratorTests.cs index d714573..13f9d69 100644 --- a/NEW/tests/JdeScoping.ExcelIO.Tests/DataEntryTemplateGeneratorTests.cs +++ b/NEW/tests/JdeScoping.ExcelIO.Tests/DataEntryTemplateGeneratorTests.cs @@ -1,145 +1,137 @@ -using ClosedXML.Excel; -using JdeScoping.ExcelIO.Generators; -using Shouldly; -using Xunit; - -namespace JdeScoping.ExcelIO.Tests; - -public class DataEntryTemplateGeneratorTests -{ - private readonly DataEntryTemplateGenerator _generator = new(); - - [Fact] - public void Generate_SingleColumn_ReturnsValidExcel() - { - var result = _generator.Generate(null, "Test Header"); - - result.ShouldNotBeNull(); - result.Length.ShouldBeGreaterThan(0); - - using var stream = new MemoryStream(result); - using var workbook = new XLWorkbook(stream); - workbook.Worksheets.Count.ShouldBe(1); - } - - [Fact] - public void Generate_SingleColumn_HasCorrectHeader() - { - var result = _generator.Generate(null, "Item Numbers"); - - using var stream = new MemoryStream(result); - using var workbook = new XLWorkbook(stream); - var worksheet = workbook.Worksheets.First(); - - worksheet.Cell(1, 1).Value.GetText().ShouldBe("Item Numbers"); - } - - [Fact] - public void Generate_SingleColumn_HasHeaderFormatting() - { - var result = _generator.Generate(null, "Test Header"); - - using var stream = new MemoryStream(result); - using var workbook = new XLWorkbook(stream); - var worksheet = workbook.Worksheets.First(); - var headerCell = worksheet.Cell(1, 1); - - headerCell.Style.Font.Bold.ShouldBeTrue(); - headerCell.Style.Fill.BackgroundColor.ShouldBe(XLColor.Gainsboro); - } - - [Fact] - public void Generate_SingleColumn_WithData_PopulatesRows() - { - var data = new List { "Item1", "Item2", "Item3" }; - - var result = _generator.Generate(data, "Items"); - - using var stream = new MemoryStream(result); - using var workbook = new XLWorkbook(stream); - var worksheet = workbook.Worksheets.First(); - - worksheet.Cell(2, 1).Value.GetText().ShouldBe("Item1"); - worksheet.Cell(3, 1).Value.GetText().ShouldBe("Item2"); - worksheet.Cell(4, 1).Value.GetText().ShouldBe("Item3"); - } - - [Fact] - public void Generate_SingleColumn_SetsTextFormat() - { - var result = _generator.Generate(null, "Test"); - - using var stream = new MemoryStream(result); - using var workbook = new XLWorkbook(stream); - var worksheet = workbook.Worksheets.First(); - - worksheet.Column(1).Style.NumberFormat.Format.ShouldBe("@"); - } - - [Fact] - public void Generate_MultiColumn_ReturnsValidExcel() - { - var headers = new[] { "Column A", "Column B", "Column C" }; - - var result = _generator.Generate(null, headers); - - result.ShouldNotBeNull(); - - using var stream = new MemoryStream(result); - using var workbook = new XLWorkbook(stream); - var worksheet = workbook.Worksheets.First(); - - worksheet.Cell(1, 1).Value.GetText().ShouldBe("Column A"); - worksheet.Cell(1, 2).Value.GetText().ShouldBe("Column B"); - worksheet.Cell(1, 3).Value.GetText().ShouldBe("Column C"); - } - - [Fact] - public void Generate_MultiColumn_WithData_PopulatesRows() - { - var headers = new[] { "Name", "Value" }; - var data = new[] - { - new object[] { "Row1", 100 }, - new object[] { "Row2", 200 } - }; - - var result = _generator.Generate(data, headers); - - using var stream = new MemoryStream(result); - using var workbook = new XLWorkbook(stream); - var worksheet = workbook.Worksheets.First(); - - worksheet.Cell(2, 1).Value.GetText().ShouldBe("Row1"); - worksheet.Cell(2, 2).Value.GetNumber().ShouldBe(100); - worksheet.Cell(3, 1).Value.GetText().ShouldBe("Row2"); - worksheet.Cell(3, 2).Value.GetNumber().ShouldBe(200); - } - - [Fact] - public void Generate_MultiColumn_SetsColumnWidth() - { - var headers = new[] { "Column A", "Column B" }; - - var result = _generator.Generate(null, headers); - - using var stream = new MemoryStream(result); - using var workbook = new XLWorkbook(stream); - var worksheet = workbook.Worksheets.First(); - - worksheet.Column(1).Width.ShouldBe(65); - worksheet.Column(2).Width.ShouldBe(65); - } - - [Fact] - public void Generate_SingleColumn_SetsColumnWidth() - { - var result = _generator.Generate(null, "Test"); - - using var stream = new MemoryStream(result); - using var workbook = new XLWorkbook(stream); - var worksheet = workbook.Worksheets.First(); - - worksheet.Column(1).Width.ShouldBe(45); - } -} +using JdeScoping.ExcelIO.Generators; +using JdeScoping.ExcelIO.Tests.Fixtures; +using Shouldly; +using Xunit; + +namespace JdeScoping.ExcelIO.Tests; + +public class DataEntryTemplateGeneratorTests +{ + private readonly DataEntryTemplateGenerator _generator = new(); + + [Fact] + public void Generate_SingleColumn_ReturnsValidExcel() + { + var result = _generator.Generate(null, "Test Header"); + + result.ShouldNotBeNull(); + result.Length.ShouldBeGreaterThan(0); + + using var workbook = ExcelTestHelpers.OpenWorkbook(result); + workbook.NumberOfSheets.ShouldBe(1); + } + + [Fact] + public void Generate_SingleColumn_HasCorrectHeader() + { + var result = _generator.Generate(null, "Item Numbers"); + + using var workbook = ExcelTestHelpers.OpenWorkbook(result); + var worksheet = workbook.GetSheetAt(0); + + ExcelTestHelpers.GetCellText(worksheet, 1, 1).ShouldBe("Item Numbers"); + } + + [Fact] + public void Generate_SingleColumn_HasHeaderFormatting() + { + var result = _generator.Generate(null, "Test Header"); + + using var workbook = ExcelTestHelpers.OpenWorkbook(result); + var worksheet = workbook.GetSheetAt(0); + var headerCell = ExcelTestHelpers.GetCell(worksheet, 1, 1)!; + + (headerCell.CellStyle.FontIndex >= 0 && + workbook.GetFontAt(headerCell.CellStyle.FontIndex).IsBold).ShouldBeTrue(); + ExcelTestHelpers.GetFillForegroundRgb(headerCell).ShouldBe([0xDC, 0xDC, 0xDC]); + } + + [Fact] + public void Generate_SingleColumn_WithData_PopulatesRows() + { + var data = new List { "Item1", "Item2", "Item3" }; + + var result = _generator.Generate(data, "Items"); + + using var workbook = ExcelTestHelpers.OpenWorkbook(result); + var worksheet = workbook.GetSheetAt(0); + + ExcelTestHelpers.GetCellText(worksheet, 2, 1).ShouldBe("Item1"); + ExcelTestHelpers.GetCellText(worksheet, 3, 1).ShouldBe("Item2"); + ExcelTestHelpers.GetCellText(worksheet, 4, 1).ShouldBe("Item3"); + } + + [Fact] + public void Generate_SingleColumn_SetsTextFormat() + { + var result = _generator.Generate(null, "Test"); + + using var workbook = ExcelTestHelpers.OpenWorkbook(result); + var worksheet = workbook.GetSheetAt(0); + + worksheet.GetColumnStyle(0).GetDataFormatString().ShouldBe("@"); + } + + [Fact] + public void Generate_MultiColumn_ReturnsValidExcel() + { + var headers = new[] { "Column A", "Column B", "Column C" }; + + var result = _generator.Generate(null, headers); + + result.ShouldNotBeNull(); + + using var workbook = ExcelTestHelpers.OpenWorkbook(result); + var worksheet = workbook.GetSheetAt(0); + + ExcelTestHelpers.GetCellText(worksheet, 1, 1).ShouldBe("Column A"); + ExcelTestHelpers.GetCellText(worksheet, 1, 2).ShouldBe("Column B"); + ExcelTestHelpers.GetCellText(worksheet, 1, 3).ShouldBe("Column C"); + } + + [Fact] + public void Generate_MultiColumn_WithData_PopulatesRows() + { + var headers = new[] { "Name", "Value" }; + var data = new[] + { + new object[] { "Row1", 100 }, + new object[] { "Row2", 200 } + }; + + var result = _generator.Generate(data, headers); + + using var workbook = ExcelTestHelpers.OpenWorkbook(result); + var worksheet = workbook.GetSheetAt(0); + + ExcelTestHelpers.GetCellText(worksheet, 2, 1).ShouldBe("Row1"); + ExcelTestHelpers.GetCellNumber(worksheet, 2, 2).ShouldBe(100); + ExcelTestHelpers.GetCellText(worksheet, 3, 1).ShouldBe("Row2"); + ExcelTestHelpers.GetCellNumber(worksheet, 3, 2).ShouldBe(200); + } + + [Fact] + public void Generate_MultiColumn_SetsColumnWidth() + { + var headers = new[] { "Column A", "Column B" }; + + var result = _generator.Generate(null, headers); + + using var workbook = ExcelTestHelpers.OpenWorkbook(result); + var worksheet = workbook.GetSheetAt(0); + + ExcelTestHelpers.GetColumnWidthChars(worksheet, 1).ShouldBe(65); + ExcelTestHelpers.GetColumnWidthChars(worksheet, 2).ShouldBe(65); + } + + [Fact] + public void Generate_SingleColumn_SetsColumnWidth() + { + var result = _generator.Generate(null, "Test"); + + using var workbook = ExcelTestHelpers.OpenWorkbook(result); + var worksheet = workbook.GetSheetAt(0); + + ExcelTestHelpers.GetColumnWidthChars(worksheet, 1).ShouldBe(45); + } +} diff --git a/NEW/tests/JdeScoping.ExcelIO.Tests/ExcelExportServiceTests.cs b/NEW/tests/JdeScoping.ExcelIO.Tests/ExcelExportServiceTests.cs index a0e0821..d86f0fa 100644 --- a/NEW/tests/JdeScoping.ExcelIO.Tests/ExcelExportServiceTests.cs +++ b/NEW/tests/JdeScoping.ExcelIO.Tests/ExcelExportServiceTests.cs @@ -1,249 +1,228 @@ -using ClosedXML.Excel; -using JdeScoping.Core.Models.SearchResults; -using JdeScoping.ExcelIO.Options; -using JdeScoping.ExcelIO.Generators; -using JdeScoping.ExcelIO.Mapping; -using JdeScoping.ExcelIO.Mapping.Maps; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using NSubstitute; -using Shouldly; -using Xunit; - -namespace JdeScoping.ExcelIO.Tests; - -public class ExcelExportServiceTests -{ - private readonly ExcelExportService _service; - private readonly ILogger _logger; - private readonly IOptions _options; - - public ExcelExportServiceTests() - { - _logger = Substitute.For>(); - _options = Microsoft.Extensions.Options.Options.Create(new ExcelExportOptions - { - CriteriaSheetPassword = "TestCriteriaPass", - DataSheetPassword = "TestDataPass" - }); - - var registry = CreateTestRegistry(); - var tableWriter = new FluentTableWriter(registry); - var criteriaGenerator = new CriteriaSheetGenerator(_options, tableWriter); - - _service = new ExcelExportService(_logger, _options, criteriaGenerator, tableWriter, registry); - } - - private static ExcelMapRegistry CreateTestRegistry() - { - var registry = new ExcelMapRegistry(); - - // Search result maps - registry.Register(new SearchResultMap()); - registry.Register(new MisSearchResultMap()); - registry.Register(new MisNonMatchSearchResultMap()); - - // Filter entry maps - registry.Register(new TimespanFilterMap()); - registry.Register(new WorkOrderFilterEntryMap()); - registry.Register(new ItemNumberFilterEntryMap()); - registry.Register(new ProfitCenterFilterEntryMap()); - registry.Register(new WorkCenterFilterEntryMap()); - registry.Register(new OperatorFilterEntryMap()); - registry.Register(new ComponentLotFilterEntryMap()); - registry.Register(new ItemOperationMisFilterEntryMap()); - - return registry; - } - - [Fact] - public async Task GenerateAsync_ReturnsValidExcelBytes() - { - var search = CreateMinimalSearchModel(); - - var result = await _service.GenerateAsync(search); - - result.ShouldNotBeNull(); - result.Length.ShouldBeGreaterThan(0); - - // Verify it's a valid Excel file - using var stream = new MemoryStream(result); - using var workbook = new XLWorkbook(stream); - workbook.Worksheets.Count.ShouldBeGreaterThanOrEqualTo(2); - } - - [Fact] - public async Task GenerateAsync_CreatesSearchCriteriaSheet() - { - var search = CreateMinimalSearchModel(); - - var result = await _service.GenerateAsync(search); - - using var stream = new MemoryStream(result); - using var workbook = new XLWorkbook(stream); - - workbook.Worksheets.TryGetWorksheet("Search Criteria", out var criteriaSheet).ShouldBeTrue(); - criteriaSheet.ShouldNotBeNull(); - } - - [Fact] - public async Task GenerateAsync_CreatesSearchResultsSheet() - { - var search = CreateMinimalSearchModel(); - - var result = await _service.GenerateAsync(search); - - using var stream = new MemoryStream(result); - using var workbook = new XLWorkbook(stream); - - workbook.Worksheets.TryGetWorksheet("Search Results", out var resultsSheet).ShouldBeTrue(); - resultsSheet.ShouldNotBeNull(); - } - - [Fact] - public async Task GenerateAsync_WithMisData_CreatesMisInfoSheet() - { - var search = CreateSearchModelWithMisData(); - - var result = await _service.GenerateAsync(search); - - using var stream = new MemoryStream(result); - using var workbook = new XLWorkbook(stream); - - workbook.Worksheets.Count.ShouldBe(4); // Criteria, Results, MIS Info, Investigation - workbook.Worksheets.TryGetWorksheet("MIS Info", out var misSheet).ShouldBeTrue(); - misSheet.ShouldNotBeNull(); - } - - [Fact] - public async Task GenerateAsync_WithMisData_CreatesInvestigationSheet() - { - var search = CreateSearchModelWithMisData(); - - var result = await _service.GenerateAsync(search); - - using var stream = new MemoryStream(result); - using var workbook = new XLWorkbook(stream); - - workbook.Worksheets.TryGetWorksheet("Investigation", out var investigationSheet).ShouldBeTrue(); - investigationSheet.ShouldNotBeNull(); - } - - [Fact] - public async Task GenerateAsync_WithoutMisData_DoesNotCreateMisSheets() - { - var search = CreateMinimalSearchModel(); - search.ExtractMisData = false; - - var result = await _service.GenerateAsync(search); - - using var stream = new MemoryStream(result); - using var workbook = new XLWorkbook(stream); - - workbook.Worksheets.Count.ShouldBe(2); // Only Criteria and Results - } - - [Fact] - public async Task GenerateAsync_CancellationRequested_ThrowsOperationCanceled() - { - var search = CreateMinimalSearchModel(); - var cts = new CancellationTokenSource(); - cts.Cancel(); - - await Should.ThrowAsync( - () => _service.GenerateAsync(search, cts.Token)); - } - - [Fact] - public async Task GenerateAsync_CriteriaSheet_ContainsSearchName() - { - var search = CreateMinimalSearchModel(); - search.Name = "Test Search Name"; - - var result = await _service.GenerateAsync(search); - - using var stream = new MemoryStream(result); - using var workbook = new XLWorkbook(stream); - var criteriaSheet = workbook.Worksheet("Search Criteria"); - - criteriaSheet.Cell(1, 2).Value.GetText().ShouldBe("Test Search Name"); - } - - [Fact] - public async Task GenerateAsync_CriteriaSheet_ContainsUserName() - { - var search = CreateMinimalSearchModel(); - search.UserName = "testuser"; - - var result = await _service.GenerateAsync(search); - - using var stream = new MemoryStream(result); - using var workbook = new XLWorkbook(stream); - var criteriaSheet = workbook.Worksheet("Search Criteria"); - - criteriaSheet.Cell(2, 2).Value.GetText().ShouldBe("testuser"); - } - - [Fact] - public async Task GenerateAsync_ResultsSheet_ContainsResultData() - { - var search = CreateMinimalSearchModel(); - search.Results.Add(new SearchResult - { - WorkOrderNumber = 12345, - ItemNumber = "ITEM-001", - LotNumber = "LOT-001", - Flagged = true - }); - - var result = await _service.GenerateAsync(search); - - using var stream = new MemoryStream(result); - using var workbook = new XLWorkbook(stream); - var resultsSheet = workbook.Worksheet("Search Results"); - - // Check header row - resultsSheet.Cell(1, 1).Value.GetText().ShouldBe("Work Order Number"); - - // Check data row - resultsSheet.Cell(2, 1).Value.GetNumber().ShouldBe(12345); - } - - private static SearchModel CreateMinimalSearchModel() - { - return new SearchModel - { - Id = 1, - Name = "Test Search", - UserName = "testuser", - SubmitDt = DateTime.Now.AddHours(-1), - StartDt = DateTime.Now.AddMinutes(-30), - EndDt = DateTime.Now, - ExtractMisData = false, - Results = [] - }; - } - - private static SearchModel CreateSearchModelWithMisData() - { - return new SearchModel - { - Id = 1, - Name = "Test Search with MIS", - UserName = "testuser", - SubmitDt = DateTime.Now.AddHours(-1), - StartDt = DateTime.Now.AddMinutes(-30), - EndDt = DateTime.Now, - ExtractMisData = true, - Results = [ - new SearchResult { WorkOrderNumber = 12345, Flagged = true } - ], - MisResults = [ - new MisSearchResult { ItemNumber = "ITEM-001", MisNumber = "MIS-001" } - ], - MisNonMatchResults = [ - new MisNonMatchSearchResult { WorkOrderNumber = 12345, ItemNumber = "ITEM-001" } - ] - }; - } -} +using JdeScoping.Core.Models.SearchResults; +using JdeScoping.ExcelIO.Options; +using JdeScoping.ExcelIO.Generators; +using JdeScoping.ExcelIO.Mapping; +using JdeScoping.ExcelIO.Mapping.Maps; +using JdeScoping.ExcelIO.Tests.Fixtures; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using NSubstitute; +using Shouldly; +using Xunit; + +namespace JdeScoping.ExcelIO.Tests; + +public class ExcelExportServiceTests +{ + private readonly ExcelExportService _service; + + public ExcelExportServiceTests() + { + var logger = Substitute.For>(); + var options = Microsoft.Extensions.Options.Options.Create(new ExcelExportOptions + { + CriteriaSheetPassword = "TestCriteriaPass", + DataSheetPassword = "TestDataPass" + }); + + var registry = CreateTestRegistry(); + var tableWriter = new FluentTableWriter(registry); + var criteriaGenerator = new CriteriaSheetGenerator(options, tableWriter); + + _service = new ExcelExportService(logger, options, criteriaGenerator, tableWriter, registry); + } + + private static ExcelMapRegistry CreateTestRegistry() + { + var registry = new ExcelMapRegistry(); + + registry.Register(new SearchResultMap()); + registry.Register(new MisSearchResultMap()); + registry.Register(new MisNonMatchSearchResultMap()); + + registry.Register(new TimespanFilterMap()); + registry.Register(new WorkOrderFilterEntryMap()); + registry.Register(new ItemNumberFilterEntryMap()); + registry.Register(new ProfitCenterFilterEntryMap()); + registry.Register(new WorkCenterFilterEntryMap()); + registry.Register(new OperatorFilterEntryMap()); + registry.Register(new ComponentLotFilterEntryMap()); + registry.Register(new ItemOperationMisFilterEntryMap()); + + return registry; + } + + [Fact] + public async Task GenerateAsync_ReturnsValidExcelBytes() + { + var search = CreateMinimalSearchModel(); + + var result = await _service.GenerateAsync(search); + + result.ShouldNotBeNull(); + result.Length.ShouldBeGreaterThan(0); + + using var workbook = ExcelTestHelpers.OpenWorkbook(result); + workbook.NumberOfSheets.ShouldBeGreaterThanOrEqualTo(2); + } + + [Fact] + public async Task GenerateAsync_CreatesSearchCriteriaSheet() + { + var search = CreateMinimalSearchModel(); + + var result = await _service.GenerateAsync(search); + + using var workbook = ExcelTestHelpers.OpenWorkbook(result); + + workbook.GetSheet("Search Criteria").ShouldNotBeNull(); + } + + [Fact] + public async Task GenerateAsync_CreatesSearchResultsSheet() + { + var search = CreateMinimalSearchModel(); + + var result = await _service.GenerateAsync(search); + + using var workbook = ExcelTestHelpers.OpenWorkbook(result); + + workbook.GetSheet("Search Results").ShouldNotBeNull(); + } + + [Fact] + public async Task GenerateAsync_WithMisData_CreatesMisInfoSheet() + { + var search = CreateSearchModelWithMisData(); + + var result = await _service.GenerateAsync(search); + + using var workbook = ExcelTestHelpers.OpenWorkbook(result); + + workbook.NumberOfSheets.ShouldBe(4); + workbook.GetSheet("MIS Info").ShouldNotBeNull(); + } + + [Fact] + public async Task GenerateAsync_WithMisData_CreatesInvestigationSheet() + { + var search = CreateSearchModelWithMisData(); + + var result = await _service.GenerateAsync(search); + + using var workbook = ExcelTestHelpers.OpenWorkbook(result); + + workbook.GetSheet("Investigation").ShouldNotBeNull(); + } + + [Fact] + public async Task GenerateAsync_WithoutMisData_DoesNotCreateMisSheets() + { + var search = CreateMinimalSearchModel(); + search.ExtractMisData = false; + + var result = await _service.GenerateAsync(search); + + using var workbook = ExcelTestHelpers.OpenWorkbook(result); + + workbook.NumberOfSheets.ShouldBe(2); + } + + [Fact] + public async Task GenerateAsync_CancellationRequested_ThrowsOperationCanceled() + { + var search = CreateMinimalSearchModel(); + var cts = new CancellationTokenSource(); + cts.Cancel(); + + await Should.ThrowAsync( + () => _service.GenerateAsync(search, cts.Token)); + } + + [Fact] + public async Task GenerateAsync_CriteriaSheet_ContainsSearchName() + { + var search = CreateMinimalSearchModel(); + search.Name = "Test Search Name"; + + var result = await _service.GenerateAsync(search); + + using var workbook = ExcelTestHelpers.OpenWorkbook(result); + var criteriaSheet = workbook.GetSheet("Search Criteria"); + + ExcelTestHelpers.GetCellText(criteriaSheet, 1, 2).ShouldBe("Test Search Name"); + } + + [Fact] + public async Task GenerateAsync_CriteriaSheet_ContainsUserName() + { + var search = CreateMinimalSearchModel(); + search.UserName = "testuser"; + + var result = await _service.GenerateAsync(search); + + using var workbook = ExcelTestHelpers.OpenWorkbook(result); + var criteriaSheet = workbook.GetSheet("Search Criteria"); + + ExcelTestHelpers.GetCellText(criteriaSheet, 2, 2).ShouldBe("testuser"); + } + + [Fact] + public async Task GenerateAsync_ResultsSheet_ContainsResultData() + { + var search = CreateMinimalSearchModel(); + search.Results.Add(new SearchResult + { + WorkOrderNumber = 12345, + ItemNumber = "ITEM-001", + LotNumber = "LOT-001", + Flagged = true + }); + + var result = await _service.GenerateAsync(search); + + using var workbook = ExcelTestHelpers.OpenWorkbook(result); + var resultsSheet = workbook.GetSheet("Search Results"); + + ExcelTestHelpers.GetCellText(resultsSheet, 1, 1).ShouldBe("Work Order Number"); + ExcelTestHelpers.GetCellNumber(resultsSheet, 2, 1).ShouldBe(12345); + } + + private static SearchModel CreateMinimalSearchModel() + { + return new SearchModel + { + Id = 1, + Name = "Test Search", + UserName = "testuser", + SubmitDt = DateTime.Now.AddHours(-1), + StartDt = DateTime.Now.AddMinutes(-30), + EndDt = DateTime.Now, + ExtractMisData = false, + Results = [] + }; + } + + private static SearchModel CreateSearchModelWithMisData() + { + return new SearchModel + { + Id = 1, + Name = "Test Search with MIS", + UserName = "testuser", + SubmitDt = DateTime.Now.AddHours(-1), + StartDt = DateTime.Now.AddMinutes(-30), + EndDt = DateTime.Now, + ExtractMisData = true, + Results = [ + new SearchResult { WorkOrderNumber = 12345, Flagged = true } + ], + MisResults = [ + new MisSearchResult { ItemNumber = "ITEM-001", MisNumber = "MIS-001" } + ], + MisNonMatchResults = [ + new MisNonMatchSearchResult { WorkOrderNumber = 12345, ItemNumber = "ITEM-001" } + ] + }; + } +} diff --git a/NEW/tests/JdeScoping.ExcelIO.Tests/Fixtures/ExcelTestHelpers.cs b/NEW/tests/JdeScoping.ExcelIO.Tests/Fixtures/ExcelTestHelpers.cs index 6da7382..a1b3e8a 100644 --- a/NEW/tests/JdeScoping.ExcelIO.Tests/Fixtures/ExcelTestHelpers.cs +++ b/NEW/tests/JdeScoping.ExcelIO.Tests/Fixtures/ExcelTestHelpers.cs @@ -1,18 +1,139 @@ -using ClosedXML.Excel; - -namespace JdeScoping.ExcelIO.Tests.Fixtures; - -public static class ExcelTestHelpers -{ - public static List GetHeadersFromSheet(IXLWorksheet sheet) - { - var headers = new List(); - var col = 1; - while (!sheet.Cell(1, col).IsEmpty()) - { - headers.Add(sheet.Cell(1, col).Value.GetText()); - col++; - } - return headers; - } -} +using NPOI.SS.UserModel; +using NPOI.SS.Util; +using NPOI.XSSF.UserModel; + +namespace JdeScoping.ExcelIO.Tests.Fixtures; + +public static class ExcelTestHelpers +{ + private static readonly DataFormatter Formatter = new(); + + public static List GetHeadersFromSheet(ISheet sheet) + { + var headers = new List(); + var headerRow = sheet.GetRow(0); + if (headerRow == null) + { + return headers; + } + + var col = 0; + while (true) + { + var cell = headerRow.GetCell(col); + var text = cell == null ? string.Empty : Formatter.FormatCellValue(cell); + if (string.IsNullOrEmpty(text)) + { + break; + } + + headers.Add(text); + col++; + } + + return headers; + } + + public static string GetCellText(ISheet sheet, int row1Based, int col1Based) + { + var cell = GetCell(sheet, row1Based, col1Based); + return cell == null ? string.Empty : Formatter.FormatCellValue(cell); + } + + public static double GetCellNumber(ISheet sheet, int row1Based, int col1Based) + { + var cell = GetCell(sheet, row1Based, col1Based); + if (cell == null) + { + return 0; + } + + if (cell.CellType == CellType.Numeric) + { + return cell.NumericCellValue; + } + + var text = Formatter.FormatCellValue(cell); + return double.TryParse(text, out var value) ? value : 0; + } + + public static ICell? GetCell(ISheet sheet, int row1Based, int col1Based) + { + return sheet.GetRow(row1Based - 1)?.GetCell(col1Based - 1); + } + + public static XSSFSheet GetXssfSheet(IWorkbook workbook, string sheetName) + { + return (XSSFSheet)workbook.GetSheet(sheetName)!; + } + + public static XSSFWorkbook OpenWorkbook(byte[] bytes) + { + return new XSSFWorkbook(new MemoryStream(bytes)); + } + + public static double GetColumnWidthChars(ISheet sheet, int col1Based) + { + return sheet.GetColumnWidth(col1Based - 1) / 256d; + } + + public static bool IsSheetProtected(ISheet sheet) + { + return sheet is XSSFSheet xssf && xssf.IsSheetLocked; + } + + public static byte[]? GetFillForegroundRgb(ICell cell) + { + if (cell.CellStyle is XSSFCellStyle xssfStyle) + { + return xssfStyle.FillForegroundXSSFColor?.RGB; + } + + return null; + } + + public static bool IsMerged(ISheet sheet, int firstRow1Based, int lastRow1Based, int firstCol1Based, int lastCol1Based) + { + for (var i = 0; i < sheet.NumMergedRegions; i++) + { + var region = sheet.GetMergedRegion(i); + if (region.FirstRow == firstRow1Based - 1 && + region.LastRow == lastRow1Based - 1 && + region.FirstColumn == firstCol1Based - 1 && + region.LastColumn == lastCol1Based - 1) + { + return true; + } + } + + return false; + } + + public static XSSFTable GetFirstTable(ISheet sheet) + { + return ((XSSFSheet)sheet).GetTables().First(); + } + + public static XSSFTable GetTableByName(ISheet sheet, string name) + { + return ((XSSFSheet)sheet).GetTables().First(t => t.Name == name || t.DisplayName == name); + } + + public static int GetTableRowCount(XSSFTable table) + { + // header row + data rows + return table.RowCount; + } + + public static bool TableExists(ISheet sheet, string tableName) + { + return ((XSSFSheet)sheet).GetTables().Any(t => t.Name == tableName || t.DisplayName == tableName); + } + + public static bool RegionIntersectsCell(CellRangeAddress region, int row1Based, int col1Based) + { + var row = row1Based - 1; + var col = col1Based - 1; + return region.IsInRange(row, col); + } +} diff --git a/NEW/tests/JdeScoping.ExcelIO.Tests/Fixtures/WorkbookFixtureBase.cs b/NEW/tests/JdeScoping.ExcelIO.Tests/Fixtures/WorkbookFixtureBase.cs index 4bae88f..69e3ce8 100644 --- a/NEW/tests/JdeScoping.ExcelIO.Tests/Fixtures/WorkbookFixtureBase.cs +++ b/NEW/tests/JdeScoping.ExcelIO.Tests/Fixtures/WorkbookFixtureBase.cs @@ -1,68 +1,67 @@ -using ClosedXML.Excel; -using JdeScoping.Core.Models.SearchResults; -using JdeScoping.ExcelIO.Generators; -using JdeScoping.ExcelIO.Mapping; -using JdeScoping.ExcelIO.Mapping.Maps; -using JdeScoping.ExcelIO.Options; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using NSubstitute; - -namespace JdeScoping.ExcelIO.Tests.Fixtures; - -public abstract class WorkbookFixtureBase : IDisposable -{ - public XLWorkbook Workbook { get; } - public SearchModel SearchModel { get; } - - protected abstract SearchModel CreateSearchModel(); - - protected WorkbookFixtureBase() - { - SearchModel = CreateSearchModel(); - var service = CreateExportService(); - var bytes = service.GenerateAsync(SearchModel).GetAwaiter().GetResult(); - Workbook = new XLWorkbook(new MemoryStream(bytes)); - } - - private static ExcelExportService CreateExportService() - { - var logger = Substitute.For>(); - var options = Microsoft.Extensions.Options.Options.Create(new ExcelExportOptions - { - CriteriaSheetPassword = "TestCriteriaPass", - DataSheetPassword = "TestDataPass" - }); - - var registry = CreateTestRegistry(); - var tableWriter = new FluentTableWriter(registry); - var criteriaGenerator = new CriteriaSheetGenerator(options, tableWriter); - - return new ExcelExportService(logger, options, criteriaGenerator, tableWriter, registry); - } - - private static ExcelMapRegistry CreateTestRegistry() - { - var registry = new ExcelMapRegistry(); - - registry.Register(new SearchResultMap()); - registry.Register(new MisSearchResultMap()); - registry.Register(new MisNonMatchSearchResultMap()); - registry.Register(new TimespanFilterMap()); - registry.Register(new WorkOrderFilterEntryMap()); - registry.Register(new ItemNumberFilterEntryMap()); - registry.Register(new ProfitCenterFilterEntryMap()); - registry.Register(new WorkCenterFilterEntryMap()); - registry.Register(new OperatorFilterEntryMap()); - registry.Register(new ComponentLotFilterEntryMap()); - registry.Register(new ItemOperationMisFilterEntryMap()); - - return registry; - } - - public void Dispose() - { - Workbook.Dispose(); - GC.SuppressFinalize(this); - } -} +using JdeScoping.Core.Models.SearchResults; +using JdeScoping.ExcelIO.Generators; +using JdeScoping.ExcelIO.Mapping; +using JdeScoping.ExcelIO.Mapping.Maps; +using JdeScoping.ExcelIO.Options; +using Microsoft.Extensions.Logging; +using NPOI.XSSF.UserModel; +using NSubstitute; + +namespace JdeScoping.ExcelIO.Tests.Fixtures; + +public abstract class WorkbookFixtureBase : IDisposable +{ + public XSSFWorkbook Workbook { get; } + public SearchModel SearchModel { get; } + + protected abstract SearchModel CreateSearchModel(); + + protected WorkbookFixtureBase() + { + SearchModel = CreateSearchModel(); + var service = CreateExportService(); + var bytes = service.GenerateAsync(SearchModel).GetAwaiter().GetResult(); + Workbook = new XSSFWorkbook(new MemoryStream(bytes)); + } + + private static ExcelExportService CreateExportService() + { + var logger = Substitute.For>(); + var options = Microsoft.Extensions.Options.Options.Create(new ExcelExportOptions + { + CriteriaSheetPassword = "TestCriteriaPass", + DataSheetPassword = "TestDataPass" + }); + + var registry = CreateTestRegistry(); + var tableWriter = new FluentTableWriter(registry); + var criteriaGenerator = new CriteriaSheetGenerator(options, tableWriter); + + return new ExcelExportService(logger, options, criteriaGenerator, tableWriter, registry); + } + + private static ExcelMapRegistry CreateTestRegistry() + { + var registry = new ExcelMapRegistry(); + + registry.Register(new SearchResultMap()); + registry.Register(new MisSearchResultMap()); + registry.Register(new MisNonMatchSearchResultMap()); + registry.Register(new TimespanFilterMap()); + registry.Register(new WorkOrderFilterEntryMap()); + registry.Register(new ItemNumberFilterEntryMap()); + registry.Register(new ProfitCenterFilterEntryMap()); + registry.Register(new WorkCenterFilterEntryMap()); + registry.Register(new OperatorFilterEntryMap()); + registry.Register(new ComponentLotFilterEntryMap()); + registry.Register(new ItemOperationMisFilterEntryMap()); + + return registry; + } + + public void Dispose() + { + Workbook.Dispose(); + GC.SuppressFinalize(this); + } +} diff --git a/NEW/tests/JdeScoping.ExcelIO.Tests/HeaderFormatterTests.cs b/NEW/tests/JdeScoping.ExcelIO.Tests/HeaderFormatterTests.cs index 6255de1..abd0736 100644 --- a/NEW/tests/JdeScoping.ExcelIO.Tests/HeaderFormatterTests.cs +++ b/NEW/tests/JdeScoping.ExcelIO.Tests/HeaderFormatterTests.cs @@ -1,80 +1,81 @@ -using ClosedXML.Excel; -using JdeScoping.ExcelIO.Formatting; -using Shouldly; -using Xunit; - -namespace JdeScoping.ExcelIO.Tests; - -public class HeaderFormatterTests -{ - [Fact] - public void ApplyHeaderFormat_Cell_AppliesCorrectStyling() - { - using var workbook = new XLWorkbook(); - var worksheet = workbook.Worksheets.Add("Test"); - var cell = worksheet.Cell(1, 1); - - HeaderFormatter.ApplyHeaderFormat(cell, "Test Header"); - - cell.Value.GetText().ShouldBe("Test Header"); - cell.Style.Font.Bold.ShouldBeTrue(); - cell.Style.Alignment.Horizontal.ShouldBe(XLAlignmentHorizontalValues.Center); - cell.Style.Fill.BackgroundColor.ShouldBe(XLColor.Gainsboro); - } - - [Fact] - public void ApplyHeaderFormat_Cell_WithoutText_AppliesOnlyStyling() - { - using var workbook = new XLWorkbook(); - var worksheet = workbook.Worksheets.Add("Test"); - var cell = worksheet.Cell(1, 1); - cell.Value = "Original"; - - HeaderFormatter.ApplyHeaderFormat(cell); - - cell.Value.GetText().ShouldBe("Original"); - cell.Style.Font.Bold.ShouldBeTrue(); - } - - [Fact] - public void ApplyHeaderFormat_Range_AppliesCorrectStyling() - { - using var workbook = new XLWorkbook(); - var worksheet = workbook.Worksheets.Add("Test"); - var range = worksheet.Range(1, 1, 1, 3); - - HeaderFormatter.ApplyHeaderFormat(range, "Header", merge: false); - - range.FirstCell().Value.GetText().ShouldBe("Header"); - foreach (var cell in range.Cells()) - { - cell.Style.Font.Bold.ShouldBeTrue(); - cell.Style.Fill.BackgroundColor.ShouldBe(XLColor.Gainsboro); - } - } - - [Fact] - public void ApplyHeaderFormat_Range_WithMerge_MergesCells() - { - using var workbook = new XLWorkbook(); - var worksheet = workbook.Worksheets.Add("Test"); - var range = worksheet.Range(1, 1, 1, 3); - - HeaderFormatter.ApplyHeaderFormat(range, "Merged Header", merge: true); - - range.IsMerged().ShouldBeTrue(); - range.FirstCell().Value.GetText().ShouldBe("Merged Header"); - } - - [Fact] - public void ApplyHeaderFormat_Range_WithoutMerge_DoesNotMergeCells() - { - using var workbook = new XLWorkbook(); - var worksheet = workbook.Worksheets.Add("Test"); - var range = worksheet.Range(1, 1, 1, 3); - - HeaderFormatter.ApplyHeaderFormat(range, "Not Merged", merge: false); - - range.IsMerged().ShouldBeFalse(); - } -} +using JdeScoping.ExcelIO.Formatting; +using JdeScoping.ExcelIO.Tests.Fixtures; +using NPOI.XSSF.UserModel; +using Shouldly; +using Xunit; + +namespace JdeScoping.ExcelIO.Tests; + +public class HeaderFormatterTests +{ + [Fact] + public void ApplyHeaderFormat_Cell_AppliesCorrectStyling() + { + using var workbook = new XSSFWorkbook(); + var worksheet = workbook.CreateSheet("Test"); + var row = worksheet.CreateRow(0); + var cell = row.CreateCell(0); + + HeaderFormatter.ApplyHeaderFormat(cell, "Test Header"); + + ExcelTestHelpers.GetCellText(worksheet, 1, 1).ShouldBe("Test Header"); + cell.CellStyle.GetFont(workbook).IsBold.ShouldBeTrue(); + cell.CellStyle.Alignment.ShouldBe(NPOI.SS.UserModel.HorizontalAlignment.Center); + ExcelTestHelpers.GetFillForegroundRgb(cell).ShouldBe([0xDC, 0xDC, 0xDC]); + } + + [Fact] + public void ApplyHeaderFormat_Cell_WithoutText_AppliesOnlyStyling() + { + using var workbook = new XSSFWorkbook(); + var worksheet = workbook.CreateSheet("Test"); + var row = worksheet.CreateRow(0); + var cell = row.CreateCell(0); + cell.SetCellValue("Original"); + + HeaderFormatter.ApplyHeaderFormat(cell); + + ExcelTestHelpers.GetCellText(worksheet, 1, 1).ShouldBe("Original"); + cell.CellStyle.GetFont(workbook).IsBold.ShouldBeTrue(); + } + + [Fact] + public void ApplyHeaderFormat_Range_AppliesCorrectStyling() + { + using var workbook = new XSSFWorkbook(); + var worksheet = workbook.CreateSheet("Test"); + + HeaderFormatter.ApplyHeaderFormat(worksheet, 1, 1, 1, 3, "Header", merge: false); + + ExcelTestHelpers.GetCellText(worksheet, 1, 1).ShouldBe("Header"); + for (var col = 1; col <= 3; col++) + { + var cell = ExcelTestHelpers.GetCell(worksheet, 1, col)!; + cell.CellStyle.GetFont(workbook).IsBold.ShouldBeTrue(); + ExcelTestHelpers.GetFillForegroundRgb(cell).ShouldBe([0xDC, 0xDC, 0xDC]); + } + } + + [Fact] + public void ApplyHeaderFormat_Range_WithMerge_MergesCells() + { + using var workbook = new XSSFWorkbook(); + var worksheet = workbook.CreateSheet("Test"); + + HeaderFormatter.ApplyHeaderFormat(worksheet, 1, 1, 1, 3, "Merged Header", merge: true); + + ExcelTestHelpers.IsMerged(worksheet, 1, 1, 1, 3).ShouldBeTrue(); + ExcelTestHelpers.GetCellText(worksheet, 1, 1).ShouldBe("Merged Header"); + } + + [Fact] + public void ApplyHeaderFormat_Range_WithoutMerge_DoesNotMergeCells() + { + using var workbook = new XSSFWorkbook(); + var worksheet = workbook.CreateSheet("Test"); + + HeaderFormatter.ApplyHeaderFormat(worksheet, 1, 1, 1, 3, "Not Merged", merge: false); + + ExcelTestHelpers.IsMerged(worksheet, 1, 1, 1, 3).ShouldBeFalse(); + } +} diff --git a/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/InvestigationSheetTests.cs b/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/InvestigationSheetTests.cs index 5d0e600..8d8876f 100644 --- a/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/InvestigationSheetTests.cs +++ b/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/InvestigationSheetTests.cs @@ -1,86 +1,87 @@ -using ClosedXML.Excel; -using JdeScoping.ExcelIO.Tests.Fixtures; -using Shouldly; -using Xunit; - -namespace JdeScoping.ExcelIO.Tests.Integration; - -public class InvestigationSheetTests : IClassFixture -{ - private readonly XLWorkbook _workbook; - private readonly IXLWorksheet _sheet; - private readonly List _headers; - - public InvestigationSheetTests(WithMisDataFixture fixture) - { - _workbook = fixture.Workbook; - _sheet = _workbook.Worksheet("Investigation"); - _headers = ExcelTestHelpers.GetHeadersFromSheet(_sheet); - } - - [Fact] - public void InvestigationSheet_Exists() - { - _workbook.Worksheets.TryGetWorksheet("Investigation", out _).ShouldBeTrue(); - } - - [Fact] - public void ColumnCount_Is12() - { - _headers.Count.ShouldBe(12); - } - - [Fact] - public void ColumnHeaders_MatchExpected() - { - _headers.ShouldContain("Work Center Code"); - _headers.ShouldContain("Work Order Number"); - _headers.ShouldContain("Work Order Start Date"); - _headers.ShouldContain("Job Step Number"); - _headers.ShouldContain("Function Operation Description"); - _headers.ShouldContain("Job Step End Date"); - _headers.ShouldContain("Function Code"); - _headers.ShouldContain("Was Job Step Added?"); - _headers.ShouldContain("Matched Job Step Number"); - _headers.ShouldContain("Item Number"); - _headers.ShouldContain("Item Description"); - _headers.ShouldContain("Routing Type"); - } - - [Fact] - public void ColumnOrder_MatchesSpec() - { - _headers[0].ShouldBe("Work Center Code"); - _headers[1].ShouldBe("Work Order Number"); - _headers[2].ShouldBe("Work Order Start Date"); - _headers[3].ShouldBe("Job Step Number"); - _headers[4].ShouldBe("Function Operation Description"); - _headers[5].ShouldBe("Job Step End Date"); - _headers[6].ShouldBe("Function Code"); - _headers[7].ShouldBe("Was Job Step Added?"); - _headers[8].ShouldBe("Matched Job Step Number"); - _headers[9].ShouldBe("Item Number"); - _headers[10].ShouldBe("Item Description"); - _headers[11].ShouldBe("Routing Type"); - } - - [Fact] - public void TableStyle_IsLight18() - { - var table = _sheet.Tables.First(); - table.Theme.ShouldBe(XLTableTheme.TableStyleLight18); - } - - [Fact] - public void Protection_IsEnabled() - { - _sheet.Protection.IsProtected.ShouldBeTrue(); - } - - [Fact] - public void DataRow_ContainsExpectedValues() - { - _sheet.Cell(2, 2).Value.GetNumber().ShouldBe(12345); - _sheet.Cell(2, 10).Value.GetText().ShouldBe("ITEM-001"); - } -} +using JdeScoping.ExcelIO.Tests.Fixtures; +using NPOI.SS.UserModel; +using NPOI.XSSF.UserModel; +using Shouldly; +using Xunit; + +namespace JdeScoping.ExcelIO.Tests.Integration; + +public class InvestigationSheetTests : IClassFixture +{ + private readonly XSSFWorkbook _workbook; + private readonly ISheet _sheet; + private readonly List _headers; + + public InvestigationSheetTests(WithMisDataFixture fixture) + { + _workbook = fixture.Workbook; + _sheet = _workbook.GetSheet("Investigation")!; + _headers = ExcelTestHelpers.GetHeadersFromSheet(_sheet); + } + + [Fact] + public void InvestigationSheet_Exists() + { + _workbook.GetSheet("Investigation").ShouldNotBeNull(); + } + + [Fact] + public void ColumnCount_Is12() + { + _headers.Count.ShouldBe(12); + } + + [Fact] + public void ColumnHeaders_MatchExpected() + { + _headers.ShouldContain("Work Center Code"); + _headers.ShouldContain("Work Order Number"); + _headers.ShouldContain("Work Order Start Date"); + _headers.ShouldContain("Job Step Number"); + _headers.ShouldContain("Function Operation Description"); + _headers.ShouldContain("Job Step End Date"); + _headers.ShouldContain("Function Code"); + _headers.ShouldContain("Was Job Step Added?"); + _headers.ShouldContain("Matched Job Step Number"); + _headers.ShouldContain("Item Number"); + _headers.ShouldContain("Item Description"); + _headers.ShouldContain("Routing Type"); + } + + [Fact] + public void ColumnOrder_MatchesSpec() + { + _headers[0].ShouldBe("Work Center Code"); + _headers[1].ShouldBe("Work Order Number"); + _headers[2].ShouldBe("Work Order Start Date"); + _headers[3].ShouldBe("Job Step Number"); + _headers[4].ShouldBe("Function Operation Description"); + _headers[5].ShouldBe("Job Step End Date"); + _headers[6].ShouldBe("Function Code"); + _headers[7].ShouldBe("Was Job Step Added?"); + _headers[8].ShouldBe("Matched Job Step Number"); + _headers[9].ShouldBe("Item Number"); + _headers[10].ShouldBe("Item Description"); + _headers[11].ShouldBe("Routing Type"); + } + + [Fact] + public void TableStyle_IsLight18() + { + var table = ExcelTestHelpers.GetFirstTable(_sheet); + table.StyleName.ShouldBe("TableStyleLight18"); + } + + [Fact] + public void Protection_IsEnabled() + { + ((XSSFSheet)_sheet).IsSheetLocked.ShouldBeTrue(); + } + + [Fact] + public void DataRow_ContainsExpectedValues() + { + ExcelTestHelpers.GetCellNumber(_sheet, 2, 2).ShouldBe(12345); + ExcelTestHelpers.GetCellText(_sheet, 2, 10).ShouldBe("ITEM-001"); + } +} diff --git a/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/LargeDataSetTests.cs b/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/LargeDataSetTests.cs index 7a50670..9c46c7b 100644 --- a/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/LargeDataSetTests.cs +++ b/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/LargeDataSetTests.cs @@ -1,24 +1,24 @@ -using ClosedXML.Excel; -using JdeScoping.ExcelIO.Tests.Fixtures; -using Shouldly; -using Xunit; - -namespace JdeScoping.ExcelIO.Tests.Integration; - -public class LargeDataSetTests : IClassFixture -{ - private readonly XLWorkbook _workbook; - - public LargeDataSetTests(LargeDataSetFixture fixture) - { - _workbook = fixture.Workbook; - } - - [Fact] - public void TableRowCount_Is1001() - { - var sheet = _workbook.Worksheet("Search Results"); - var table = sheet.Tables.First(); - table.RowCount().ShouldBe(1001); // 1 header + 1000 data rows - } -} +using JdeScoping.ExcelIO.Tests.Fixtures; +using NPOI.XSSF.UserModel; +using Shouldly; +using Xunit; + +namespace JdeScoping.ExcelIO.Tests.Integration; + +public class LargeDataSetTests : IClassFixture +{ + private readonly XSSFWorkbook _workbook; + + public LargeDataSetTests(LargeDataSetFixture fixture) + { + _workbook = fixture.Workbook; + } + + [Fact] + public void TableRowCount_Is1001() + { + var sheet = _workbook.GetSheet("Search Results")!; + var table = ExcelTestHelpers.GetFirstTable(sheet); + ExcelTestHelpers.GetTableRowCount(table).ShouldBe(1001); + } +} diff --git a/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/MinimalSearchTests.cs b/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/MinimalSearchTests.cs index e058160..5b55916 100644 --- a/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/MinimalSearchTests.cs +++ b/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/MinimalSearchTests.cs @@ -1,48 +1,48 @@ -using ClosedXML.Excel; -using JdeScoping.ExcelIO.Tests.Fixtures; -using Shouldly; -using Xunit; - -namespace JdeScoping.ExcelIO.Tests.Integration; - -public class MinimalSearchTests : IClassFixture -{ - private readonly XLWorkbook _workbook; - - public MinimalSearchTests(MinimalSearchFixture fixture) - { - _workbook = fixture.Workbook; - } - - [Fact] - public void SheetCount_IsTwo() - { - _workbook.Worksheets.Count.ShouldBe(2); - } - - [Fact] - public void SearchCriteriaSheet_Exists() - { - _workbook.Worksheets.TryGetWorksheet("Search Criteria", out _).ShouldBeTrue(); - } - - [Fact] - public void SearchResultsSheet_Exists() - { - _workbook.Worksheets.TryGetWorksheet("Search Results", out _).ShouldBeTrue(); - } - - [Fact] - public void SearchCriteriaSheet_IsProtected() - { - var sheet = _workbook.Worksheet("Search Criteria"); - sheet.Protection.IsProtected.ShouldBeTrue(); - } - - [Fact] - public void SearchResultsSheet_IsProtected() - { - var sheet = _workbook.Worksheet("Search Results"); - sheet.Protection.IsProtected.ShouldBeTrue(); - } -} +using JdeScoping.ExcelIO.Tests.Fixtures; +using NPOI.XSSF.UserModel; +using Shouldly; +using Xunit; + +namespace JdeScoping.ExcelIO.Tests.Integration; + +public class MinimalSearchTests : IClassFixture +{ + private readonly XSSFWorkbook _workbook; + + public MinimalSearchTests(MinimalSearchFixture fixture) + { + _workbook = fixture.Workbook; + } + + [Fact] + public void SheetCount_IsTwo() + { + _workbook.NumberOfSheets.ShouldBe(2); + } + + [Fact] + public void SearchCriteriaSheet_Exists() + { + _workbook.GetSheet("Search Criteria").ShouldNotBeNull(); + } + + [Fact] + public void SearchResultsSheet_Exists() + { + _workbook.GetSheet("Search Results").ShouldNotBeNull(); + } + + [Fact] + public void SearchCriteriaSheet_IsProtected() + { + var sheet = (XSSFSheet)_workbook.GetSheet("Search Criteria")!; + sheet.IsSheetLocked.ShouldBeTrue(); + } + + [Fact] + public void SearchResultsSheet_IsProtected() + { + var sheet = (XSSFSheet)_workbook.GetSheet("Search Results")!; + sheet.IsSheetLocked.ShouldBeTrue(); + } +} diff --git a/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/MisInfoSheetTests.cs b/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/MisInfoSheetTests.cs index dd9db0d..ba427a3 100644 --- a/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/MisInfoSheetTests.cs +++ b/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/MisInfoSheetTests.cs @@ -1,130 +1,131 @@ -using ClosedXML.Excel; -using JdeScoping.ExcelIO.Tests.Fixtures; -using Shouldly; -using Xunit; - -namespace JdeScoping.ExcelIO.Tests.Integration; - -public class MisInfoSheetTests : IClassFixture -{ - private readonly XLWorkbook _workbook; - private readonly IXLWorksheet _sheet; - private readonly List _headers; - - public MisInfoSheetTests(WithMisDataFixture fixture) - { - _workbook = fixture.Workbook; - _sheet = _workbook.Worksheet("MIS Info"); - _headers = ExcelTestHelpers.GetHeadersFromSheet(_sheet); - } - - [Fact] - public void SheetCount_IsFour() - { - _workbook.Worksheets.Count.ShouldBe(4); - } - - [Fact] - public void MisInfoSheet_Exists() - { - _workbook.Worksheets.TryGetWorksheet("MIS Info", out _).ShouldBeTrue(); - } - - [Fact] - public void ColumnCount_Is19() - { - _headers.Count.ShouldBe(19); - } - - [Fact] - public void ColumnHeaders_MatchExpected() - { - _headers.ShouldContain("Item Number"); - _headers.ShouldContain("MIS Job Step Sequence Number"); - _headers.ShouldContain("MIS Number"); - _headers.ShouldContain("MIS Revision"); - _headers.ShouldContain("Item Description"); - _headers.ShouldContain("MIS Release Status"); - _headers.ShouldContain("MIS Release Date"); - _headers.ShouldContain("Branch Code"); - _headers.ShouldContain("Job Step Sequence Number"); - _headers.ShouldContain("Matched Sequence Number"); - _headers.ShouldContain("Matched to F3112Z1?"); - _headers.ShouldContain("Matched to F3003?"); - _headers.ShouldContain("Function Operation Description"); - _headers.ShouldContain("Char Number"); - _headers.ShouldContain("Test Description"); - _headers.ShouldContain("Sampling Type"); - _headers.ShouldContain("Sampling Value"); - _headers.ShouldContain("Tools & Gauges"); - _headers.ShouldContain("Work Instructions"); - } - - [Fact] - public void ColumnOrder_MatchesSpec() - { - _headers[0].ShouldBe("Item Number"); - _headers[1].ShouldBe("MIS Job Step Sequence Number"); - _headers[2].ShouldBe("MIS Number"); - _headers[3].ShouldBe("MIS Revision"); - _headers[4].ShouldBe("Item Description"); - _headers[5].ShouldBe("MIS Release Status"); - _headers[6].ShouldBe("MIS Release Date"); - _headers[7].ShouldBe("Branch Code"); - _headers[8].ShouldBe("Job Step Sequence Number"); - _headers[9].ShouldBe("Matched Sequence Number"); - _headers[10].ShouldBe("Matched to F3112Z1?"); - _headers[11].ShouldBe("Matched to F3003?"); - _headers[12].ShouldBe("Function Operation Description"); - _headers[13].ShouldBe("Char Number"); - _headers[14].ShouldBe("Test Description"); - _headers[15].ShouldBe("Sampling Type"); - _headers[16].ShouldBe("Sampling Value"); - _headers[17].ShouldBe("Tools & Gauges"); - _headers[18].ShouldBe("Work Instructions"); - } - - [Fact] - public void TableStyle_IsLight18() - { - var table = _sheet.Tables.First(); - table.Theme.ShouldBe(XLTableTheme.TableStyleLight18); - } - - [Fact] - public void Protection_IsEnabled() - { - _sheet.Protection.IsProtected.ShouldBeTrue(); - } - - [Fact] - public void DataRow_ContainsExpectedValues() - { - _sheet.Cell(2, 1).Value.GetText().ShouldBe("ITEM-001"); - _sheet.Cell(2, 3).Value.GetText().ShouldBe("MIS-001"); - } - - [Fact] - public void TestDescriptionColumn_IsWrapped() - { - var colIndex = _headers.IndexOf("Test Description") + 1; - _sheet.Column(colIndex).Width.ShouldBe(65); - _sheet.Column(colIndex).Style.Alignment.WrapText.ShouldBeTrue(); - } - - [Fact] - public void ToolsGaugesColumn_IsWrapped() - { - var colIndex = _headers.IndexOf("Tools & Gauges") + 1; - _sheet.Column(colIndex).Width.ShouldBe(65); - _sheet.Column(colIndex).Style.Alignment.WrapText.ShouldBeTrue(); - } - - [Fact] - public void WorkInstructionsColumn_IsWrapped() - { - var colIndex = _headers.IndexOf("Work Instructions") + 1; - _sheet.Column(colIndex).Width.ShouldBe(65); - _sheet.Column(colIndex).Style.Alignment.WrapText.ShouldBeTrue(); - } -} +using JdeScoping.ExcelIO.Tests.Fixtures; +using NPOI.SS.UserModel; +using NPOI.XSSF.UserModel; +using Shouldly; +using Xunit; + +namespace JdeScoping.ExcelIO.Tests.Integration; + +public class MisInfoSheetTests : IClassFixture +{ + private readonly XSSFWorkbook _workbook; + private readonly ISheet _sheet; + private readonly List _headers; + + public MisInfoSheetTests(WithMisDataFixture fixture) + { + _workbook = fixture.Workbook; + _sheet = _workbook.GetSheet("MIS Info")!; + _headers = ExcelTestHelpers.GetHeadersFromSheet(_sheet); + } + + [Fact] + public void SheetCount_IsFour() + { + _workbook.NumberOfSheets.ShouldBe(4); + } + + [Fact] + public void MisInfoSheet_Exists() + { + _workbook.GetSheet("MIS Info").ShouldNotBeNull(); + } + + [Fact] + public void ColumnCount_Is19() + { + _headers.Count.ShouldBe(19); + } + + [Fact] + public void ColumnHeaders_MatchExpected() + { + _headers.ShouldContain("Item Number"); + _headers.ShouldContain("MIS Job Step Sequence Number"); + _headers.ShouldContain("MIS Number"); + _headers.ShouldContain("MIS Revision"); + _headers.ShouldContain("Item Description"); + _headers.ShouldContain("MIS Release Status"); + _headers.ShouldContain("MIS Release Date"); + _headers.ShouldContain("Branch Code"); + _headers.ShouldContain("Job Step Sequence Number"); + _headers.ShouldContain("Matched Sequence Number"); + _headers.ShouldContain("Matched to F3112Z1?"); + _headers.ShouldContain("Matched to F3003?"); + _headers.ShouldContain("Function Operation Description"); + _headers.ShouldContain("Char Number"); + _headers.ShouldContain("Test Description"); + _headers.ShouldContain("Sampling Type"); + _headers.ShouldContain("Sampling Value"); + _headers.ShouldContain("Tools & Gauges"); + _headers.ShouldContain("Work Instructions"); + } + + [Fact] + public void ColumnOrder_MatchesSpec() + { + _headers[0].ShouldBe("Item Number"); + _headers[1].ShouldBe("MIS Job Step Sequence Number"); + _headers[2].ShouldBe("MIS Number"); + _headers[3].ShouldBe("MIS Revision"); + _headers[4].ShouldBe("Item Description"); + _headers[5].ShouldBe("MIS Release Status"); + _headers[6].ShouldBe("MIS Release Date"); + _headers[7].ShouldBe("Branch Code"); + _headers[8].ShouldBe("Job Step Sequence Number"); + _headers[9].ShouldBe("Matched Sequence Number"); + _headers[10].ShouldBe("Matched to F3112Z1?"); + _headers[11].ShouldBe("Matched to F3003?"); + _headers[12].ShouldBe("Function Operation Description"); + _headers[13].ShouldBe("Char Number"); + _headers[14].ShouldBe("Test Description"); + _headers[15].ShouldBe("Sampling Type"); + _headers[16].ShouldBe("Sampling Value"); + _headers[17].ShouldBe("Tools & Gauges"); + _headers[18].ShouldBe("Work Instructions"); + } + + [Fact] + public void TableStyle_IsLight18() + { + var table = ExcelTestHelpers.GetFirstTable(_sheet); + table.StyleName.ShouldBe("TableStyleLight18"); + } + + [Fact] + public void Protection_IsEnabled() + { + ((XSSFSheet)_sheet).IsSheetLocked.ShouldBeTrue(); + } + + [Fact] + public void DataRow_ContainsExpectedValues() + { + ExcelTestHelpers.GetCellText(_sheet, 2, 1).ShouldBe("ITEM-001"); + ExcelTestHelpers.GetCellText(_sheet, 2, 3).ShouldBe("MIS-001"); + } + + [Fact] + public void TestDescriptionColumn_IsWrapped() + { + var colIndex = _headers.IndexOf("Test Description") + 1; + ExcelTestHelpers.GetColumnWidthChars(_sheet, colIndex).ShouldBe(65); + ExcelTestHelpers.GetCell(_sheet, 2, colIndex)!.CellStyle.WrapText.ShouldBeTrue(); + } + + [Fact] + public void ToolsGaugesColumn_IsWrapped() + { + var colIndex = _headers.IndexOf("Tools & Gauges") + 1; + ExcelTestHelpers.GetColumnWidthChars(_sheet, colIndex).ShouldBe(65); + ExcelTestHelpers.GetCell(_sheet, 2, colIndex)!.CellStyle.WrapText.ShouldBeTrue(); + } + + [Fact] + public void WorkInstructionsColumn_IsWrapped() + { + var colIndex = _headers.IndexOf("Work Instructions") + 1; + ExcelTestHelpers.GetColumnWidthChars(_sheet, colIndex).ShouldBe(65); + ExcelTestHelpers.GetCell(_sheet, 2, colIndex)!.CellStyle.WrapText.ShouldBeTrue(); + } +} diff --git a/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/ProtectionAndStyleTests.cs b/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/ProtectionAndStyleTests.cs index 90e1384..4f28459 100644 --- a/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/ProtectionAndStyleTests.cs +++ b/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/ProtectionAndStyleTests.cs @@ -1,75 +1,77 @@ -using ClosedXML.Excel; -using JdeScoping.ExcelIO.Tests.Fixtures; -using Shouldly; -using Xunit; - -namespace JdeScoping.ExcelIO.Tests.Integration; - -public class ProtectionAndStyleTests : IClassFixture -{ - private readonly XLWorkbook _workbook; - - public ProtectionAndStyleTests(WithMisDataFixture fixture) - { - _workbook = fixture.Workbook; - } - - [Fact] - public void AllDataSheets_AreProtected() - { - _workbook.Worksheet("Search Results").Protection.IsProtected.ShouldBeTrue(); - _workbook.Worksheet("MIS Info").Protection.IsProtected.ShouldBeTrue(); - _workbook.Worksheet("Investigation").Protection.IsProtected.ShouldBeTrue(); - } - - [Fact] - public void Protection_AllowsFiltering() - { - var sheet = _workbook.Worksheet("Search Results"); - sheet.Protection.AllowedElements.HasFlag(XLSheetProtectionElements.AutoFilter).ShouldBeTrue(); - } - - [Fact] - public void Protection_AllowsSorting() - { - var sheet = _workbook.Worksheet("Search Results"); - sheet.Protection.AllowedElements.HasFlag(XLSheetProtectionElements.Sort).ShouldBeTrue(); - } - - [Fact] - public void Protection_AllowsFormatting() - { - var sheet = _workbook.Worksheet("Search Results"); - sheet.Protection.AllowedElements.HasFlag(XLSheetProtectionElements.FormatCells).ShouldBeTrue(); - sheet.Protection.AllowedElements.HasFlag(XLSheetProtectionElements.FormatColumns).ShouldBeTrue(); - sheet.Protection.AllowedElements.HasFlag(XLSheetProtectionElements.FormatRows).ShouldBeTrue(); - } - - [Fact] - public void AllTables_UseLight18Style() - { - _workbook.Worksheet("Search Results").Tables.First().Theme.ShouldBe(XLTableTheme.TableStyleLight18); - _workbook.Worksheet("MIS Info").Tables.First().Theme.ShouldBe(XLTableTheme.TableStyleLight18); - _workbook.Worksheet("Investigation").Tables.First().Theme.ShouldBe(XLTableTheme.TableStyleLight18); - } - - [Fact] - public void HeaderCells_HaveCorrectFormatting() - { - var sheet = _workbook.Worksheet("Search Criteria"); - var headerCell = sheet.Cell(1, 1); - headerCell.Style.Font.Bold.ShouldBeTrue(); - headerCell.Style.Fill.BackgroundColor.ShouldBe(XLColor.Gainsboro); - headerCell.Style.Alignment.Horizontal.ShouldBe(XLAlignmentHorizontalValues.Center); - } - - [Fact] - public void CriteriaTimestamp_MatchesLegacyFormat() - { - var sheet = _workbook.Worksheet("Search Criteria"); - var timestamp = sheet.Cell(4, 2).Value.GetText(); - timestamp.ShouldContain("Jan 15, 2024"); - timestamp.ShouldContain("02:30:45"); - timestamp.ShouldContain("EST"); - } -} +using JdeScoping.ExcelIO.Tests.Fixtures; +using NPOI.SS.UserModel; +using NPOI.XSSF.UserModel; +using Shouldly; +using Xunit; + +namespace JdeScoping.ExcelIO.Tests.Integration; + +public class ProtectionAndStyleTests : IClassFixture +{ + private readonly XSSFWorkbook _workbook; + + public ProtectionAndStyleTests(WithMisDataFixture fixture) + { + _workbook = fixture.Workbook; + } + + [Fact] + public void AllDataSheets_AreProtected() + { + ((XSSFSheet)_workbook.GetSheet("Search Results")!).IsSheetLocked.ShouldBeTrue(); + ((XSSFSheet)_workbook.GetSheet("MIS Info")!).IsSheetLocked.ShouldBeTrue(); + ((XSSFSheet)_workbook.GetSheet("Investigation")!).IsSheetLocked.ShouldBeTrue(); + } + + [Fact] + public void Protection_AllowsFiltering() + { + var sheet = (XSSFSheet)_workbook.GetSheet("Search Results")!; + sheet.IsAutoFilterLocked.ShouldBeFalse(); + } + + [Fact] + public void Protection_AllowsSorting() + { + var sheet = (XSSFSheet)_workbook.GetSheet("Search Results")!; + sheet.IsSortLocked.ShouldBeFalse(); + } + + [Fact] + public void Protection_AllowsFormatting() + { + var sheet = (XSSFSheet)_workbook.GetSheet("Search Results")!; + sheet.IsFormatCellsLocked.ShouldBeFalse(); + sheet.IsFormatColumnsLocked.ShouldBeFalse(); + sheet.IsFormatRowsLocked.ShouldBeFalse(); + } + + [Fact] + public void AllTables_UseLight18Style() + { + ExcelTestHelpers.GetFirstTable(_workbook.GetSheet("Search Results")!).StyleName.ShouldBe("TableStyleLight18"); + ExcelTestHelpers.GetFirstTable(_workbook.GetSheet("MIS Info")!).StyleName.ShouldBe("TableStyleLight18"); + ExcelTestHelpers.GetFirstTable(_workbook.GetSheet("Investigation")!).StyleName.ShouldBe("TableStyleLight18"); + } + + [Fact] + public void HeaderCells_HaveCorrectFormatting() + { + var sheet = _workbook.GetSheet("Search Criteria")!; + var headerCell = ExcelTestHelpers.GetCell(sheet, 1, 1)!; + (headerCell.CellStyle.FontIndex >= 0 && + _workbook.GetFontAt(headerCell.CellStyle.FontIndex).IsBold).ShouldBeTrue(); + ExcelTestHelpers.GetFillForegroundRgb(headerCell).ShouldBe([0xDC, 0xDC, 0xDC]); + headerCell.CellStyle.Alignment.ShouldBe(HorizontalAlignment.Center); + } + + [Fact] + public void CriteriaTimestamp_MatchesLegacyFormat() + { + var sheet = _workbook.GetSheet("Search Criteria")!; + var timestamp = ExcelTestHelpers.GetCellText(sheet, 4, 2); + timestamp.ShouldContain("Jan 15, 2024"); + timestamp.ShouldContain("02:30:45"); + timestamp.ShouldContain("EST"); + } +} diff --git a/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/SearchResultsSheetTests.cs b/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/SearchResultsSheetTests.cs index 40f4435..36882a0 100644 --- a/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/SearchResultsSheetTests.cs +++ b/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/SearchResultsSheetTests.cs @@ -1,89 +1,90 @@ -using ClosedXML.Excel; -using JdeScoping.ExcelIO.Tests.Fixtures; -using Shouldly; -using Xunit; - -namespace JdeScoping.ExcelIO.Tests.Integration; - -public class SearchResultsSheetTests : IClassFixture -{ - private readonly XLWorkbook _workbook; - private readonly IXLWorksheet _sheet; - private readonly List _headers; - - public SearchResultsSheetTests(WithResultsFixture fixture) - { - _workbook = fixture.Workbook; - _sheet = _workbook.Worksheet("Search Results"); - _headers = ExcelTestHelpers.GetHeadersFromSheet(_sheet); - } - - [Fact] - public void ColumnCount_Is19() - { - _headers.Count.ShouldBe(19); - } - - [Fact] - public void ColumnHeaders_MatchExpected() - { - _headers.ShouldContain("Work Order Number"); - _headers.ShouldContain("Work Order Branch Code"); - _headers.ShouldContain("Lot Number"); - _headers.ShouldContain("Item Number"); - _headers.ShouldContain("Planning Family"); - _headers.ShouldContain("Stocking Type"); - _headers.ShouldContain("Order Quantity"); - _headers.ShouldContain("Held Quantity"); - _headers.ShouldContain("Scrapped Quantity"); - _headers.ShouldContain("Shipped Quantity"); - _headers.ShouldContain("Operation Step Branch Code"); - _headers.ShouldContain("Operation Step"); - _headers.ShouldContain("Operation Step Description"); - _headers.ShouldContain("Function Operation Description"); - _headers.ShouldContain("Operation Step Update Timestamp"); - _headers.ShouldContain("Status Code"); - _headers.ShouldContain("Status Description"); - _headers.ShouldContain("Status Update Timestamp"); - _headers.ShouldContain("Inclusion Reason"); - } - - [Fact] - public void ColumnOrder_MatchesSpec() - { - _headers[0].ShouldBe("Work Order Number"); - _headers[1].ShouldBe("Work Order Branch Code"); - _headers[2].ShouldBe("Lot Number"); - _headers[3].ShouldBe("Item Number"); - _headers[4].ShouldBe("Planning Family"); - _headers[5].ShouldBe("Stocking Type"); - _headers[6].ShouldBe("Order Quantity"); - _headers[7].ShouldBe("Held Quantity"); - _headers[8].ShouldBe("Scrapped Quantity"); - _headers[9].ShouldBe("Shipped Quantity"); - _headers[10].ShouldBe("Operation Step Branch Code"); - _headers[11].ShouldBe("Operation Step"); - _headers[12].ShouldBe("Operation Step Description"); - _headers[13].ShouldBe("Function Operation Description"); - _headers[14].ShouldBe("Operation Step Update Timestamp"); - _headers[15].ShouldBe("Status Code"); - _headers[16].ShouldBe("Status Description"); - _headers[17].ShouldBe("Status Update Timestamp"); - _headers[18].ShouldBe("Inclusion Reason"); - } - - [Fact] - public void TableStyle_IsLight18() - { - var table = _sheet.Tables.First(); - table.Theme.ShouldBe(XLTableTheme.TableStyleLight18); - } - - [Fact] - public void DataRow_ContainsExpectedValues() - { - _sheet.Cell(2, 1).Value.GetNumber().ShouldBe(12345); - _sheet.Cell(2, 3).Value.GetText().ShouldBe("LOT-001"); - _sheet.Cell(2, 4).Value.GetText().ShouldBe("ITEM-001"); - } -} +using JdeScoping.ExcelIO.Tests.Fixtures; +using NPOI.SS.UserModel; +using NPOI.XSSF.UserModel; +using Shouldly; +using Xunit; + +namespace JdeScoping.ExcelIO.Tests.Integration; + +public class SearchResultsSheetTests : IClassFixture +{ + private readonly XSSFWorkbook _workbook; + private readonly ISheet _sheet; + private readonly List _headers; + + public SearchResultsSheetTests(WithResultsFixture fixture) + { + _workbook = fixture.Workbook; + _sheet = _workbook.GetSheet("Search Results")!; + _headers = ExcelTestHelpers.GetHeadersFromSheet(_sheet); + } + + [Fact] + public void ColumnCount_Is19() + { + _headers.Count.ShouldBe(19); + } + + [Fact] + public void ColumnHeaders_MatchExpected() + { + _headers.ShouldContain("Work Order Number"); + _headers.ShouldContain("Work Order Branch Code"); + _headers.ShouldContain("Lot Number"); + _headers.ShouldContain("Item Number"); + _headers.ShouldContain("Planning Family"); + _headers.ShouldContain("Stocking Type"); + _headers.ShouldContain("Order Quantity"); + _headers.ShouldContain("Held Quantity"); + _headers.ShouldContain("Scrapped Quantity"); + _headers.ShouldContain("Shipped Quantity"); + _headers.ShouldContain("Operation Step Branch Code"); + _headers.ShouldContain("Operation Step"); + _headers.ShouldContain("Operation Step Description"); + _headers.ShouldContain("Function Operation Description"); + _headers.ShouldContain("Operation Step Update Timestamp"); + _headers.ShouldContain("Status Code"); + _headers.ShouldContain("Status Description"); + _headers.ShouldContain("Status Update Timestamp"); + _headers.ShouldContain("Inclusion Reason"); + } + + [Fact] + public void ColumnOrder_MatchesSpec() + { + _headers[0].ShouldBe("Work Order Number"); + _headers[1].ShouldBe("Work Order Branch Code"); + _headers[2].ShouldBe("Lot Number"); + _headers[3].ShouldBe("Item Number"); + _headers[4].ShouldBe("Planning Family"); + _headers[5].ShouldBe("Stocking Type"); + _headers[6].ShouldBe("Order Quantity"); + _headers[7].ShouldBe("Held Quantity"); + _headers[8].ShouldBe("Scrapped Quantity"); + _headers[9].ShouldBe("Shipped Quantity"); + _headers[10].ShouldBe("Operation Step Branch Code"); + _headers[11].ShouldBe("Operation Step"); + _headers[12].ShouldBe("Operation Step Description"); + _headers[13].ShouldBe("Function Operation Description"); + _headers[14].ShouldBe("Operation Step Update Timestamp"); + _headers[15].ShouldBe("Status Code"); + _headers[16].ShouldBe("Status Description"); + _headers[17].ShouldBe("Status Update Timestamp"); + _headers[18].ShouldBe("Inclusion Reason"); + } + + [Fact] + public void TableStyle_IsLight18() + { + var table = ExcelTestHelpers.GetFirstTable(_sheet); + table.StyleName.ShouldBe("TableStyleLight18"); + } + + [Fact] + public void DataRow_ContainsExpectedValues() + { + ExcelTestHelpers.GetCellNumber(_sheet, 2, 1).ShouldBe(12345); + ExcelTestHelpers.GetCellText(_sheet, 2, 3).ShouldBe("LOT-001"); + ExcelTestHelpers.GetCellText(_sheet, 2, 4).ShouldBe("ITEM-001"); + } +} diff --git a/NEW/tests/JdeScoping.ExcelIO.Tests/JdeScoping.ExcelIO.Tests.csproj b/NEW/tests/JdeScoping.ExcelIO.Tests/JdeScoping.ExcelIO.Tests.csproj index b4a9b8d..3f2df8b 100644 --- a/NEW/tests/JdeScoping.ExcelIO.Tests/JdeScoping.ExcelIO.Tests.csproj +++ b/NEW/tests/JdeScoping.ExcelIO.Tests/JdeScoping.ExcelIO.Tests.csproj @@ -8,10 +8,11 @@ true - - - - + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/NEW/tests/JdeScoping.ExcelIO.Tests/Parsing/ExcelParserServiceTests.cs b/NEW/tests/JdeScoping.ExcelIO.Tests/Parsing/ExcelParserServiceTests.cs index 57e3517..f119d2b 100644 --- a/NEW/tests/JdeScoping.ExcelIO.Tests/Parsing/ExcelParserServiceTests.cs +++ b/NEW/tests/JdeScoping.ExcelIO.Tests/Parsing/ExcelParserServiceTests.cs @@ -1,179 +1,171 @@ -using ClosedXML.Excel; -using JdeScoping.ExcelIO.Parsing; -using Microsoft.Extensions.Logging.Abstractions; -using Shouldly; -using Xunit; - -namespace JdeScoping.ExcelIO.Tests.Parsing; - -public class ExcelParserServiceTests -{ - private readonly ExcelParserService _service = new(NullLogger.Instance); - - [Fact] - public void ParseWorkOrders_ReturnsWorkOrderNumbers() - { - // Arrange - var excelData = CreateWorkOrderExcel([12345, 67890, 11111]); - - // Act - using var stream = new MemoryStream(excelData); - var result = _service.ParseWorkOrders(stream); - - // Assert - result.Count.ShouldBe(3); - result.ShouldContain(12345); - result.ShouldContain(67890); - result.ShouldContain(11111); - } - - [Fact] - public void ParseWorkOrders_SkipsInvalidNumbers() - { - // Arrange - using var workbook = new XLWorkbook(); - var worksheet = workbook.Worksheets.Add("Sheet1"); - worksheet.Cell(1, 1).Value = "Work Order"; - worksheet.Cell(2, 1).Value = "12345"; - worksheet.Cell(3, 1).Value = "not-a-number"; - worksheet.Cell(4, 1).Value = "67890"; - - using var ms = new MemoryStream(); - workbook.SaveAs(ms); - ms.Position = 0; - - // Act - var result = _service.ParseWorkOrders(ms); - - // Assert - result.Count.ShouldBe(2); - } - - [Fact] - public void ParseItems_ReturnsItemNumbers() - { - // Arrange - var excelData = CreateItemExcel(["ITEM-001", "ITEM-002"]); - - // Act - using var stream = new MemoryStream(excelData); - var result = _service.ParseItems(stream); - - // Assert - result.Count.ShouldBe(2); - result.ShouldContain("ITEM-001"); - result.ShouldContain("ITEM-002"); - } - - [Fact] - public void ParseComponentLots_ReturnsLotViewModels() - { - // Arrange - var excelData = CreateComponentLotExcel([("LOT001", "ITEM-001"), ("LOT002", "ITEM-002")]); - - // Act - using var stream = new MemoryStream(excelData); - var result = _service.ParseComponentLots(stream); - - // Assert - result.Count.ShouldBe(2); - result[0].LotNumber.ShouldBe("LOT001"); - result[0].ItemNumber.ShouldBe("ITEM-001"); - } - - [Fact] - public void ParsePartOperations_ReturnsPartOperations() - { - // Arrange - var excelData = CreatePartOperationExcel([("ITEM-001", "100", "MIS001", "A")]); - - // Act - using var stream = new MemoryStream(excelData); - var result = _service.ParsePartOperations(stream); - - // Assert - result.Count.ShouldBe(1); - result[0].ItemNumber.ShouldBe("ITEM-001"); - result[0].OperationNumber.ShouldBe("100"); - result[0].MisNumber.ShouldBe("MIS001"); - result[0].MisRevision.ShouldBe("A"); - } - - [Fact] - public void ParsePartOperations_TruncatesDecimalOperationNumbers() - { - // Arrange - var excelData = CreatePartOperationExcel([("ITEM-001", "100.5", "MIS001", "A")]); - - // Act - using var stream = new MemoryStream(excelData); - var result = _service.ParsePartOperations(stream); - - // Assert - result[0].OperationNumber.ShouldBe("100"); - } - - private static byte[] CreateWorkOrderExcel(long[] workOrderNumbers) - { - using var workbook = new XLWorkbook(); - var worksheet = workbook.Worksheets.Add("Sheet1"); - worksheet.Cell(1, 1).Value = "Work Order Number"; - for (var i = 0; i < workOrderNumbers.Length; i++) - { - worksheet.Cell(i + 2, 1).Value = workOrderNumbers[i]; - } - using var stream = new MemoryStream(); - workbook.SaveAs(stream); - return stream.ToArray(); - } - - private static byte[] CreateItemExcel(string[] itemNumbers) - { - using var workbook = new XLWorkbook(); - var worksheet = workbook.Worksheets.Add("Sheet1"); - worksheet.Cell(1, 1).Value = "Item Number"; - for (var i = 0; i < itemNumbers.Length; i++) - { - worksheet.Cell(i + 2, 1).Value = itemNumbers[i]; - } - using var stream = new MemoryStream(); - workbook.SaveAs(stream); - return stream.ToArray(); - } - - private static byte[] CreateComponentLotExcel((string LotNumber, string ItemNumber)[] lots) - { - using var workbook = new XLWorkbook(); - var worksheet = workbook.Worksheets.Add("Sheet1"); - worksheet.Cell(1, 1).Value = "Lot Number"; - worksheet.Cell(1, 2).Value = "Item Number"; - for (var i = 0; i < lots.Length; i++) - { - worksheet.Cell(i + 2, 1).Value = lots[i].LotNumber; - worksheet.Cell(i + 2, 2).Value = lots[i].ItemNumber; - } - using var stream = new MemoryStream(); - workbook.SaveAs(stream); - return stream.ToArray(); - } - - private static byte[] CreatePartOperationExcel((string ItemNumber, string OpNumber, string MisNumber, string MisRevision)[] operations) - { - using var workbook = new XLWorkbook(); - var worksheet = workbook.Worksheets.Add("Sheet1"); - worksheet.Cell(1, 1).Value = "Item Number"; - worksheet.Cell(1, 2).Value = "Operation Number"; - worksheet.Cell(1, 3).Value = "MIS Number"; - worksheet.Cell(1, 4).Value = "MIS Revision"; - for (var i = 0; i < operations.Length; i++) - { - worksheet.Cell(i + 2, 1).Value = operations[i].ItemNumber; - worksheet.Cell(i + 2, 2).Value = operations[i].OpNumber; - worksheet.Cell(i + 2, 3).Value = operations[i].MisNumber; - worksheet.Cell(i + 2, 4).Value = operations[i].MisRevision; - } - using var stream = new MemoryStream(); - workbook.SaveAs(stream); - return stream.ToArray(); - } -} +using JdeScoping.ExcelIO.Parsing; +using Microsoft.Extensions.Logging.Abstractions; +using NPOI.XSSF.UserModel; +using Shouldly; +using Xunit; + +namespace JdeScoping.ExcelIO.Tests.Parsing; + +public class ExcelParserServiceTests +{ + private readonly ExcelParserService _service = new(NullLogger.Instance); + + [Fact] + public void ParseWorkOrders_ReturnsWorkOrderNumbers() + { + var excelData = CreateWorkOrderExcel([12345, 67890, 11111]); + + using var stream = new MemoryStream(excelData); + var result = _service.ParseWorkOrders(stream); + + result.Count.ShouldBe(3); + result.ShouldContain(12345); + result.ShouldContain(67890); + result.ShouldContain(11111); + } + + [Fact] + public void ParseWorkOrders_SkipsInvalidNumbers() + { + using var workbook = new XSSFWorkbook(); + var worksheet = workbook.CreateSheet("Sheet1"); + worksheet.CreateRow(0).CreateCell(0).SetCellValue("Work Order"); + worksheet.CreateRow(1).CreateCell(0).SetCellValue("12345"); + worksheet.CreateRow(2).CreateCell(0).SetCellValue("not-a-number"); + worksheet.CreateRow(3).CreateCell(0).SetCellValue("67890"); + + using var ms = new MemoryStream(); + workbook.Write(ms, leaveOpen: true); + ms.Position = 0; + + var result = _service.ParseWorkOrders(ms); + + result.Count.ShouldBe(2); + } + + [Fact] + public void ParseItems_ReturnsItemNumbers() + { + var excelData = CreateItemExcel(["ITEM-001", "ITEM-002"]); + + using var stream = new MemoryStream(excelData); + var result = _service.ParseItems(stream); + + result.Count.ShouldBe(2); + result.ShouldContain("ITEM-001"); + result.ShouldContain("ITEM-002"); + } + + [Fact] + public void ParseComponentLots_ReturnsLotViewModels() + { + var excelData = CreateComponentLotExcel([("LOT001", "ITEM-001"), ("LOT002", "ITEM-002")]); + + using var stream = new MemoryStream(excelData); + var result = _service.ParseComponentLots(stream); + + result.Count.ShouldBe(2); + result[0].LotNumber.ShouldBe("LOT001"); + result[0].ItemNumber.ShouldBe("ITEM-001"); + } + + [Fact] + public void ParsePartOperations_ReturnsPartOperations() + { + var excelData = CreatePartOperationExcel([("ITEM-001", "100", "MIS001", "A")]); + + using var stream = new MemoryStream(excelData); + var result = _service.ParsePartOperations(stream); + + result.Count.ShouldBe(1); + result[0].ItemNumber.ShouldBe("ITEM-001"); + result[0].OperationNumber.ShouldBe("100"); + result[0].MisNumber.ShouldBe("MIS001"); + result[0].MisRevision.ShouldBe("A"); + } + + [Fact] + public void ParsePartOperations_TruncatesDecimalOperationNumbers() + { + var excelData = CreatePartOperationExcel([("ITEM-001", "100.5", "MIS001", "A")]); + + using var stream = new MemoryStream(excelData); + var result = _service.ParsePartOperations(stream); + + result[0].OperationNumber.ShouldBe("100"); + } + + private static byte[] CreateWorkOrderExcel(long[] workOrderNumbers) + { + using var workbook = new XSSFWorkbook(); + var worksheet = workbook.CreateSheet("Sheet1"); + worksheet.CreateRow(0).CreateCell(0).SetCellValue("Work Order Number"); + for (var i = 0; i < workOrderNumbers.Length; i++) + { + worksheet.CreateRow(i + 1).CreateCell(0).SetCellValue(workOrderNumbers[i]); + } + + using var stream = new MemoryStream(); + workbook.Write(stream, leaveOpen: true); + return stream.ToArray(); + } + + private static byte[] CreateItemExcel(string[] itemNumbers) + { + using var workbook = new XSSFWorkbook(); + var worksheet = workbook.CreateSheet("Sheet1"); + worksheet.CreateRow(0).CreateCell(0).SetCellValue("Item Number"); + for (var i = 0; i < itemNumbers.Length; i++) + { + worksheet.CreateRow(i + 1).CreateCell(0).SetCellValue(itemNumbers[i]); + } + + using var stream = new MemoryStream(); + workbook.Write(stream, leaveOpen: true); + return stream.ToArray(); + } + + private static byte[] CreateComponentLotExcel((string LotNumber, string ItemNumber)[] lots) + { + using var workbook = new XSSFWorkbook(); + var worksheet = workbook.CreateSheet("Sheet1"); + var header = worksheet.CreateRow(0); + header.CreateCell(0).SetCellValue("Lot Number"); + header.CreateCell(1).SetCellValue("Item Number"); + + for (var i = 0; i < lots.Length; i++) + { + var row = worksheet.CreateRow(i + 1); + row.CreateCell(0).SetCellValue(lots[i].LotNumber); + row.CreateCell(1).SetCellValue(lots[i].ItemNumber); + } + + using var stream = new MemoryStream(); + workbook.Write(stream, leaveOpen: true); + return stream.ToArray(); + } + + private static byte[] CreatePartOperationExcel((string ItemNumber, string OpNumber, string MisNumber, string MisRevision)[] operations) + { + using var workbook = new XSSFWorkbook(); + var worksheet = workbook.CreateSheet("Sheet1"); + var header = worksheet.CreateRow(0); + header.CreateCell(0).SetCellValue("Item Number"); + header.CreateCell(1).SetCellValue("Operation Number"); + header.CreateCell(2).SetCellValue("MIS Number"); + header.CreateCell(3).SetCellValue("MIS Revision"); + + for (var i = 0; i < operations.Length; i++) + { + var row = worksheet.CreateRow(i + 1); + row.CreateCell(0).SetCellValue(operations[i].ItemNumber); + row.CreateCell(1).SetCellValue(operations[i].OpNumber); + row.CreateCell(2).SetCellValue(operations[i].MisNumber); + row.CreateCell(3).SetCellValue(operations[i].MisRevision); + } + + using var stream = new MemoryStream(); + workbook.Write(stream, leaveOpen: true); + return stream.ToArray(); + } +} diff --git a/NEW/tests/JdeScoping.ExcelIO.Tests/Templates/ExcelTemplateServiceTests.cs b/NEW/tests/JdeScoping.ExcelIO.Tests/Templates/ExcelTemplateServiceTests.cs index 45578df..cdfe34c 100644 --- a/NEW/tests/JdeScoping.ExcelIO.Tests/Templates/ExcelTemplateServiceTests.cs +++ b/NEW/tests/JdeScoping.ExcelIO.Tests/Templates/ExcelTemplateServiceTests.cs @@ -1,96 +1,80 @@ -using ClosedXML.Excel; -using JdeScoping.ExcelIO.Templates; -using Shouldly; -using Xunit; - -namespace JdeScoping.ExcelIO.Tests.Templates; - -public class ExcelTemplateServiceTests -{ - private readonly ExcelTemplateService _service = new(); - - [Fact] - public void GenerateSingleColumn_CreatesValidExcel() - { - // Arrange - var data = new[] { 12345L, 67890L }; - - // Act - var result = _service.GenerateSingleColumn(data, "Work Order Number"); - - // Assert - result.ShouldNotBeNull(); - result.Length.ShouldBeGreaterThan(0); - - // Verify content - using var stream = new MemoryStream(result); - using var workbook = new XLWorkbook(stream); - var worksheet = workbook.Worksheet(1); - - worksheet.Cell(1, 1).GetString().ShouldBe("Work Order Number"); - worksheet.Cell(2, 1).GetString().ShouldBe("12345"); - worksheet.Cell(3, 1).GetString().ShouldBe("67890"); - } - - [Fact] - public void GenerateMultiColumn_CreatesValidExcel() - { - // Arrange - var data = new[] - { - new object?[] { "ITEM-001", "Description 1" }, - new object?[] { "ITEM-002", "Description 2" } - }; - var headers = new[] { "Item Number", "Description" }; - - // Act - var result = _service.GenerateMultiColumn(data, headers); - - // Assert - result.ShouldNotBeNull(); - result.Length.ShouldBeGreaterThan(0); - - // Verify content - using var stream = new MemoryStream(result); - using var workbook = new XLWorkbook(stream); - var worksheet = workbook.Worksheet(1); - - worksheet.Cell(1, 1).GetString().ShouldBe("Item Number"); - worksheet.Cell(1, 2).GetString().ShouldBe("Description"); - worksheet.Cell(2, 1).GetString().ShouldBe("ITEM-001"); - worksheet.Cell(2, 2).GetString().ShouldBe("Description 1"); - } - - [Fact] - public void GenerateSingleColumn_HandlesEmptyData() - { - // Act - var result = _service.GenerateSingleColumn(Array.Empty(), "Header"); - - // Assert - result.ShouldNotBeNull(); - result.Length.ShouldBeGreaterThan(0); - } - - [Fact] - public void GenerateMultiColumn_HandlesNullValues() - { - // Arrange - var data = new[] - { - new object?[] { "ITEM-001", null } - }; - var headers = new[] { "Item", "Value" }; - - // Act - var result = _service.GenerateMultiColumn(data, headers); - - // Assert - result.ShouldNotBeNull(); - using var stream = new MemoryStream(result); - using var workbook = new XLWorkbook(stream); - var worksheet = workbook.Worksheet(1); - - worksheet.Cell(2, 2).GetString().ShouldBe(string.Empty); - } -} +using JdeScoping.ExcelIO.Templates; +using JdeScoping.ExcelIO.Tests.Fixtures; +using Shouldly; +using Xunit; + +namespace JdeScoping.ExcelIO.Tests.Templates; + +public class ExcelTemplateServiceTests +{ + private readonly ExcelTemplateService _service = new(); + + [Fact] + public void GenerateSingleColumn_CreatesValidExcel() + { + var data = new[] { 12345L, 67890L }; + + var result = _service.GenerateSingleColumn(data, "Work Order Number"); + + result.ShouldNotBeNull(); + result.Length.ShouldBeGreaterThan(0); + + using var workbook = ExcelTestHelpers.OpenWorkbook(result); + var worksheet = workbook.GetSheetAt(0); + + ExcelTestHelpers.GetCellText(worksheet, 1, 1).ShouldBe("Work Order Number"); + ExcelTestHelpers.GetCellText(worksheet, 2, 1).ShouldBe("12345"); + ExcelTestHelpers.GetCellText(worksheet, 3, 1).ShouldBe("67890"); + } + + [Fact] + public void GenerateMultiColumn_CreatesValidExcel() + { + var data = new[] + { + new object?[] { "ITEM-001", "Description 1" }, + new object?[] { "ITEM-002", "Description 2" } + }; + var headers = new[] { "Item Number", "Description" }; + + var result = _service.GenerateMultiColumn(data, headers); + + result.ShouldNotBeNull(); + result.Length.ShouldBeGreaterThan(0); + + using var workbook = ExcelTestHelpers.OpenWorkbook(result); + var worksheet = workbook.GetSheetAt(0); + + ExcelTestHelpers.GetCellText(worksheet, 1, 1).ShouldBe("Item Number"); + ExcelTestHelpers.GetCellText(worksheet, 1, 2).ShouldBe("Description"); + ExcelTestHelpers.GetCellText(worksheet, 2, 1).ShouldBe("ITEM-001"); + ExcelTestHelpers.GetCellText(worksheet, 2, 2).ShouldBe("Description 1"); + } + + [Fact] + public void GenerateSingleColumn_HandlesEmptyData() + { + var result = _service.GenerateSingleColumn(Array.Empty(), "Header"); + + result.ShouldNotBeNull(); + result.Length.ShouldBeGreaterThan(0); + } + + [Fact] + public void GenerateMultiColumn_HandlesNullValues() + { + var data = new[] + { + new object?[] { "ITEM-001", null } + }; + var headers = new[] { "Item", "Value" }; + + var result = _service.GenerateMultiColumn(data, headers); + + result.ShouldNotBeNull(); + using var workbook = ExcelTestHelpers.OpenWorkbook(result); + var worksheet = workbook.GetSheetAt(0); + + ExcelTestHelpers.GetCellText(worksheet, 2, 2).ShouldBe(string.Empty); + } +} diff --git a/NEW/tests/JdeScoping.ExcelIO.Tests/WorksheetProtectorTests.cs b/NEW/tests/JdeScoping.ExcelIO.Tests/WorksheetProtectorTests.cs index af41d38..b5ba1e0 100644 --- a/NEW/tests/JdeScoping.ExcelIO.Tests/WorksheetProtectorTests.cs +++ b/NEW/tests/JdeScoping.ExcelIO.Tests/WorksheetProtectorTests.cs @@ -1,80 +1,78 @@ -using ClosedXML.Excel; -using JdeScoping.ExcelIO.Formatting; -using Shouldly; -using Xunit; - -namespace JdeScoping.ExcelIO.Tests; - -public class WorksheetProtectorTests -{ - [Fact] - public void ApplyProtection_ProtectsWorksheet() - { - using var workbook = new XLWorkbook(); - var worksheet = workbook.Worksheets.Add("Test"); - - WorksheetProtector.ApplyProtection(worksheet, "TestPassword"); - - worksheet.Protection.IsProtected.ShouldBeTrue(); - } - - [Fact] - public void ApplyProtection_AllowsSpecifiedOperations() - { - using var workbook = new XLWorkbook(); - var worksheet = workbook.Worksheets.Add("Test"); - - WorksheetProtector.ApplyProtection(worksheet, "TestPassword"); - - // Check that specified operations are allowed - worksheet.Protection.AllowedElements.HasFlag(XLSheetProtectionElements.DeleteColumns).ShouldBeTrue(); - worksheet.Protection.AllowedElements.HasFlag(XLSheetProtectionElements.DeleteRows).ShouldBeTrue(); - worksheet.Protection.AllowedElements.HasFlag(XLSheetProtectionElements.AutoFilter).ShouldBeTrue(); - worksheet.Protection.AllowedElements.HasFlag(XLSheetProtectionElements.FormatCells).ShouldBeTrue(); - worksheet.Protection.AllowedElements.HasFlag(XLSheetProtectionElements.FormatColumns).ShouldBeTrue(); - worksheet.Protection.AllowedElements.HasFlag(XLSheetProtectionElements.FormatRows).ShouldBeTrue(); - worksheet.Protection.AllowedElements.HasFlag(XLSheetProtectionElements.SelectLockedCells).ShouldBeTrue(); - worksheet.Protection.AllowedElements.HasFlag(XLSheetProtectionElements.SelectUnlockedCells).ShouldBeTrue(); - worksheet.Protection.AllowedElements.HasFlag(XLSheetProtectionElements.EditObjects).ShouldBeTrue(); - worksheet.Protection.AllowedElements.HasFlag(XLSheetProtectionElements.Sort).ShouldBeTrue(); - } - - [Fact] - public void ApplyProtection_AllowsDeleteRows() - { - using var workbook = new XLWorkbook(); - var worksheet = workbook.Worksheets.Add("Test"); - - WorksheetProtector.ApplyProtection(worksheet, "TestPassword"); - - // DeleteRows should be allowed - worksheet.Protection.AllowedElements.HasFlag(XLSheetProtectionElements.DeleteRows).ShouldBeTrue(); - } - - [Fact] - public void ApplyCriteriaProtection_ProtectsWorksheet() - { - using var workbook = new XLWorkbook(); - var worksheet = workbook.Worksheets.Add("Test"); - - WorksheetProtector.ApplyCriteriaProtection(worksheet, "CriteriaPassword"); - - worksheet.Protection.IsProtected.ShouldBeTrue(); - } - - [Fact] - public void UnlockExtensionArea_UnlocksSpecifiedRange() - { - using var workbook = new XLWorkbook(); - var worksheet = workbook.Worksheets.Add("Test"); - - // First, set some cells to locked (default) - worksheet.Range(1, 1, 10, 5).Style.Protection.Locked = true; - - WorksheetProtector.UnlockExtensionArea(worksheet, 10, 5, 100, 100); - - // Extension area should be unlocked - var extensionCell = worksheet.Cell(1, 6); - extensionCell.Style.Protection.Locked.ShouldBeFalse(); - } -} +using JdeScoping.ExcelIO.Formatting; +using JdeScoping.ExcelIO.Tests.Fixtures; +using NPOI.XSSF.UserModel; +using Shouldly; +using Xunit; + +namespace JdeScoping.ExcelIO.Tests; + +public class WorksheetProtectorTests +{ + [Fact] + public void ApplyProtection_ProtectsWorksheet() + { + using var workbook = new XSSFWorkbook(); + var worksheet = (XSSFSheet)workbook.CreateSheet("Test"); + + WorksheetProtector.ApplyProtection(worksheet, "TestPassword"); + + worksheet.IsSheetLocked.ShouldBeTrue(); + } + + [Fact] + public void ApplyProtection_AllowsSpecifiedOperations() + { + using var workbook = new XSSFWorkbook(); + var worksheet = (XSSFSheet)workbook.CreateSheet("Test"); + + WorksheetProtector.ApplyProtection(worksheet, "TestPassword"); + + worksheet.IsDeleteColumnsLocked.ShouldBeFalse(); + worksheet.IsDeleteRowsLocked.ShouldBeFalse(); + worksheet.IsAutoFilterLocked.ShouldBeFalse(); + worksheet.IsFormatCellsLocked.ShouldBeFalse(); + worksheet.IsFormatColumnsLocked.ShouldBeFalse(); + worksheet.IsFormatRowsLocked.ShouldBeFalse(); + worksheet.IsSelectLockedCellsLocked.ShouldBeFalse(); + worksheet.IsSelectUnlockedCellsLocked.ShouldBeFalse(); + worksheet.IsObjectsLocked.ShouldBeFalse(); + worksheet.IsSortLocked.ShouldBeFalse(); + } + + [Fact] + public void ApplyProtection_AllowsDeleteRows() + { + using var workbook = new XSSFWorkbook(); + var worksheet = (XSSFSheet)workbook.CreateSheet("Test"); + + WorksheetProtector.ApplyProtection(worksheet, "TestPassword"); + + worksheet.IsDeleteRowsLocked.ShouldBeFalse(); + } + + [Fact] + public void ApplyCriteriaProtection_ProtectsWorksheet() + { + using var workbook = new XSSFWorkbook(); + var worksheet = (XSSFSheet)workbook.CreateSheet("Test"); + + WorksheetProtector.ApplyCriteriaProtection(worksheet, "CriteriaPassword"); + + worksheet.IsSheetLocked.ShouldBeTrue(); + } + + [Fact] + public void UnlockExtensionArea_UnlocksSpecifiedRange() + { + using var workbook = new XSSFWorkbook(); + var worksheet = (XSSFSheet)workbook.CreateSheet("Test"); + + WorksheetProtector.UnlockExtensionArea(worksheet, 10, 5, 100, 100); + + var extensionStyle = worksheet.GetColumnStyle(5); + extensionStyle.IsLocked.ShouldBeFalse(); + + var extensionCell = ExcelTestHelpers.GetCell(worksheet, 1, 6)!; + extensionCell.CellStyle.IsLocked.ShouldBeFalse(); + } +}