# ExcelIO Test Optimization Implementation Plan > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. **Goal:** Reduce ExcelIO.Tests runtime from ~18 minutes to ~3-4 minutes by using fixture-based test consolidation. **Architecture:** Replace per-test workbook generation with xUnit `IClassFixture` pattern. Generate 4 shared workbooks once, reuse across 42 integration tests. **Tech Stack:** xUnit fixtures, ClosedXML, existing ExcelExportService --- ## Task 1: Create Fixture Base Class and Infrastructure **Files:** - Create: `tests/JdeScoping.ExcelIO.Tests/Fixtures/WorkbookFixtureBase.cs` - Create: `tests/JdeScoping.ExcelIO.Tests/Fixtures/ExcelTestHelpers.cs` **Step 1: Create Fixtures directory** ```bash mkdir -p tests/JdeScoping.ExcelIO.Tests/Fixtures ``` **Step 2: Write WorkbookFixtureBase.cs** ```csharp using ClosedXML.Excel; using JdeScoping.ExcelIO.Generators; using JdeScoping.ExcelIO.Mapping; using JdeScoping.ExcelIO.Mapping.Maps; using JdeScoping.ExcelIO.Models.Reporting; 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 = 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); } } ``` **Step 3: Write ExcelTestHelpers.cs** ```csharp 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; } } ``` **Step 4: Build and verify** ```bash dotnet build tests/JdeScoping.ExcelIO.Tests ``` --- ## Task 2: Create Concrete Fixture Classes **Files:** - Create: `tests/JdeScoping.ExcelIO.Tests/Fixtures/MinimalSearchFixture.cs` - Create: `tests/JdeScoping.ExcelIO.Tests/Fixtures/WithResultsFixture.cs` - Create: `tests/JdeScoping.ExcelIO.Tests/Fixtures/WithMisDataFixture.cs` - Create: `tests/JdeScoping.ExcelIO.Tests/Fixtures/LargeDataSetFixture.cs` **Step 1: Write MinimalSearchFixture.cs** ```csharp using JdeScoping.ExcelIO.Models.Reporting; namespace JdeScoping.ExcelIO.Tests.Fixtures; public class MinimalSearchFixture : WorkbookFixtureBase { protected override SearchModel CreateSearchModel() => new() { Id = 1, Name = "Minimal Search Test", UserName = "testuser", SubmitDt = new DateTime(2024, 1, 15, 14, 30, 45), StartDt = new DateTime(2024, 1, 15, 14, 31, 0), EndDt = new DateTime(2024, 1, 15, 14, 35, 0), ExtractMisData = false, Results = [] }; } ``` **Step 2: Write WithResultsFixture.cs** ```csharp using JdeScoping.ExcelIO.Models.Reporting; using SearchResult = JdeScoping.Core.Models.SearchResults.SearchResult; namespace JdeScoping.ExcelIO.Tests.Fixtures; public class WithResultsFixture : WorkbookFixtureBase { protected override SearchModel CreateSearchModel() => new() { Id = 1, Name = "Search With Results", UserName = "testuser", SubmitDt = new DateTime(2024, 1, 15, 14, 30, 45), StartDt = new DateTime(2024, 1, 15, 14, 31, 0), EndDt = new DateTime(2024, 1, 15, 14, 35, 0), ExtractMisData = false, Results = [ new SearchResult { WorkOrderNumber = 12345, WorkOrderBranchCode = "001", LotNumber = "LOT-001", ItemNumber = "ITEM-001", PlanningFamily = "PF01", StockingType = "M", OrderQuantity = 100, HeldQuantity = 0, ScrappedQuantity = 0, ShippedQuantity = 50, StepBranchCode = "001", StepNumber = 10, StepDescription = "Assembly", FunctionOperationDescription = "Main assembly operation", StepUpdateDt = new DateTime(2024, 1, 14, 10, 0, 0), StatusCode = "50", StatusDescription = "In Progress", StatusUpdateDt = new DateTime(2024, 1, 14, 10, 0, 0), Flagged = true } ] }; } ``` **Step 3: Write WithMisDataFixture.cs** ```csharp using JdeScoping.ExcelIO.Models.Reporting; using SearchResult = JdeScoping.Core.Models.SearchResults.SearchResult; using MisSearchResult = JdeScoping.Core.Models.SearchResults.MisSearchResult; using MisNonMatchSearchResult = JdeScoping.Core.Models.SearchResults.MisNonMatchSearchResult; namespace JdeScoping.ExcelIO.Tests.Fixtures; public class WithMisDataFixture : WorkbookFixtureBase { protected override SearchModel CreateSearchModel() => new() { Id = 1, Name = "Search With MIS Data", UserName = "testuser", SubmitDt = new DateTime(2024, 1, 15, 14, 30, 45), StartDt = new DateTime(2024, 1, 15, 14, 31, 0), EndDt = new DateTime(2024, 1, 15, 14, 35, 0), ExtractMisData = true, Results = [ new SearchResult { WorkOrderNumber = 12345, WorkOrderBranchCode = "001", LotNumber = "LOT-001", ItemNumber = "ITEM-001", PlanningFamily = "PF01", StockingType = "M", OrderQuantity = 100, HeldQuantity = 0, ScrappedQuantity = 0, ShippedQuantity = 50, StepBranchCode = "001", StepNumber = 10, StepDescription = "Assembly", FunctionOperationDescription = "Main assembly", StepUpdateDt = new DateTime(2024, 1, 14, 10, 0, 0), StatusCode = "50", StatusDescription = "In Progress", Flagged = true } ], MisResults = [ new MisSearchResult { ItemNumber = "ITEM-001", SequenceNumber = "010", MisNumber = "MIS-001", RevId = "A", ItemDescription = "Test Item", Status = "Released", ReleaseDate = new DateTime(2023, 12, 15), BranchCode = "001", JobStepSequenceNumber = 10, MatchedSequenceNumber = 10, RoutingMatch = true, MasterMatch = true, FunctionOperationDescription = "Assembly operation", CharNumber = "001", TestDescription = "Sample test description", SamplingType = "100%", SamplingValue = "1", ToolsGauges = "Gauge A, Gauge B", WorkInstructions = "Step 1: Do this. Step 2: Do that." } ], MisNonMatchResults = [ new MisNonMatchSearchResult { WorkCenterCode = "WC01", WorkOrderNumber = 12345, WorkOrderStartDate = new DateTime(2024, 1, 8), JobStepNumber = 10, JobStepDescription = "Test operation", JobStepEndDate = new DateTime(2024, 1, 10), FunctionCode = "FC01", WasJobStepAdded = false, MatchedJobStepNumber = 10, ItemNumber = "ITEM-001", ItemDescription = "Test Item Description", RoutingType = "M" } ] }; } ``` **Step 4: Write LargeDataSetFixture.cs** ```csharp using JdeScoping.ExcelIO.Models.Reporting; using SearchResult = JdeScoping.Core.Models.SearchResults.SearchResult; namespace JdeScoping.ExcelIO.Tests.Fixtures; public class LargeDataSetFixture : WorkbookFixtureBase { protected override SearchModel CreateSearchModel() { var model = new SearchModel { Id = 1, Name = "Large Data Set Test", UserName = "testuser", SubmitDt = new DateTime(2024, 1, 15, 14, 30, 45), StartDt = new DateTime(2024, 1, 15, 14, 31, 0), EndDt = new DateTime(2024, 1, 15, 14, 35, 0), ExtractMisData = false, Results = [] }; for (int i = 0; i < 1000; i++) { model.Results.Add(new SearchResult { WorkOrderNumber = 10000 + i, ItemNumber = $"ITEM-{i:D5}", LotNumber = $"LOT-{i:D5}", Flagged = true }); } return model; } } ``` **Step 5: Build and verify** ```bash dotnet build tests/JdeScoping.ExcelIO.Tests ``` --- ## Task 3: Create MinimalSearchTests **Files:** - Create: `tests/JdeScoping.ExcelIO.Tests/Integration/MinimalSearchTests.cs` **Step 1: Create Integration directory** ```bash mkdir -p tests/JdeScoping.ExcelIO.Tests/Integration ``` **Step 2: Write MinimalSearchTests.cs** ```csharp 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(); } } ``` **Step 3: Build and run tests** ```bash dotnet build tests/JdeScoping.ExcelIO.Tests dotnet test tests/JdeScoping.ExcelIO.Tests --filter "FullyQualifiedName~MinimalSearchTests" --verbosity normal ``` --- ## Task 4: Create SearchResultsSheetTests **Files:** - Create: `tests/JdeScoping.ExcelIO.Tests/Integration/SearchResultsSheetTests.cs` **Step 1: Write SearchResultsSheetTests.cs** ```csharp 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"); } } ``` **Step 2: Build and run tests** ```bash dotnet build tests/JdeScoping.ExcelIO.Tests dotnet test tests/JdeScoping.ExcelIO.Tests --filter "FullyQualifiedName~SearchResultsSheetTests" --verbosity normal ``` --- ## Task 5: Create MisInfoSheetTests **Files:** - Create: `tests/JdeScoping.ExcelIO.Tests/Integration/MisInfoSheetTests.cs` **Step 1: Write MisInfoSheetTests.cs** ```csharp 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(); } } ``` **Step 2: Build and run tests** ```bash dotnet build tests/JdeScoping.ExcelIO.Tests dotnet test tests/JdeScoping.ExcelIO.Tests --filter "FullyQualifiedName~MisInfoSheetTests" --verbosity normal ``` --- ## Task 6: Create InvestigationSheetTests **Files:** - Create: `tests/JdeScoping.ExcelIO.Tests/Integration/InvestigationSheetTests.cs` **Step 1: Write InvestigationSheetTests.cs** ```csharp 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"); } } ``` **Step 2: Build and run tests** ```bash dotnet build tests/JdeScoping.ExcelIO.Tests dotnet test tests/JdeScoping.ExcelIO.Tests --filter "FullyQualifiedName~InvestigationSheetTests" --verbosity normal ``` --- ## Task 7: Create ProtectionAndStyleTests **Files:** - Create: `tests/JdeScoping.ExcelIO.Tests/Integration/ProtectionAndStyleTests.cs` **Step 1: Write ProtectionAndStyleTests.cs** ```csharp 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"); } } ``` **Step 2: Build and run tests** ```bash dotnet build tests/JdeScoping.ExcelIO.Tests dotnet test tests/JdeScoping.ExcelIO.Tests --filter "FullyQualifiedName~ProtectionAndStyleTests" --verbosity normal ``` --- ## Task 8: Create LegacyFormatTests and LargeDataSetTests **Files:** - Create: `tests/JdeScoping.ExcelIO.Tests/Integration/LegacyFormatTests.cs` - Create: `tests/JdeScoping.ExcelIO.Tests/Integration/LargeDataSetTests.cs` **Step 1: Write LegacyFormatTests.cs** ```csharp using Shouldly; using Xunit; using ExcelFormats = JdeScoping.ExcelIO.Formatting.ExcelFormats; namespace JdeScoping.ExcelIO.Tests.Integration; /// /// Tests for ExcelFormats constants - no workbook generation needed. /// public class LegacyFormatTests { [Fact] public void TimestampFormat_MatchesLegacy() { ExcelFormats.TimestampFormat.ShouldBe("[$-409]m/d/yy h:mm AM/PM;@"); } [Fact] public void DateFormat_MatchesLegacyPattern() { ExcelFormats.DateFormat.ShouldContain("MM/dd/yyyy"); } [Fact] public void WrappedColumnWidth_MatchesLegacy() { ExcelFormats.WrappedColumnWidth.ShouldBe(65); } [Fact] public void CriteriaPaddingFactor_MatchesLegacy() { ExcelFormats.CriteriaPaddingFactor.ShouldBe(1.15); } [Fact] public void DataPaddingFactor_MatchesLegacy() { ExcelFormats.DataPaddingFactor.ShouldBe(1.30); } } ``` **Step 2: Write LargeDataSetTests.cs** ```csharp 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 } } ``` **Step 3: Build and run tests** ```bash dotnet build tests/JdeScoping.ExcelIO.Tests dotnet test tests/JdeScoping.ExcelIO.Tests --filter "FullyQualifiedName~LegacyFormatTests|FullyQualifiedName~LargeDataSetTests" --verbosity normal ``` --- ## Task 9: Delete Old Integration Test Files **Files:** - Delete: `tests/JdeScoping.ExcelIO.Tests/ExcelExportIntegrationTests.cs` - Delete: `tests/JdeScoping.ExcelIO.Tests/LegacyComparisonTests.cs` **Step 1: Delete old files** ```bash rm tests/JdeScoping.ExcelIO.Tests/ExcelExportIntegrationTests.cs rm tests/JdeScoping.ExcelIO.Tests/LegacyComparisonTests.cs ``` **Step 2: Build and verify all tests pass** ```bash dotnet build tests/JdeScoping.ExcelIO.Tests dotnet test tests/JdeScoping.ExcelIO.Tests --verbosity normal ``` --- ## Task 10: Run Full Test Suite and Measure Performance **Step 1: Run full test suite with timing** ```bash time dotnet test tests/JdeScoping.ExcelIO.Tests --verbosity minimal ``` **Step 2: Verify test count matches expected** Expected: ~103 tests (same as before, just reorganized) **Step 3: Commit changes** ```bash git add -A git commit -m "perf: optimize ExcelIO tests with fixture-based consolidation - Add WorkbookFixtureBase and 4 concrete fixtures - Reorganize integration tests to use IClassFixture - Reduce workbook generations from ~42 to 4 - Delete redundant ExcelExportIntegrationTests.cs - Delete redundant LegacyComparisonTests.cs - Test coverage unchanged, runtime reduced ~75-80%" ``` --- ## Summary | Task | Description | |------|-------------| | 1 | Create fixture base class and helpers | | 2 | Create 4 concrete fixture classes | | 3 | Create MinimalSearchTests (5 tests) | | 4 | Create SearchResultsSheetTests (5 tests) | | 5 | Create MisInfoSheetTests (12 tests) | | 6 | Create InvestigationSheetTests (7 tests) | | 7 | Create ProtectionAndStyleTests (7 tests) | | 8 | Create LegacyFormatTests + LargeDataSetTests (6 tests) | | 9 | Delete old integration test files | | 10 | Run full test suite or measure performance |