WorkTracker/tests/WorkTracker.Tests/MonthlyTimesheetExcelExporterTests.cs

178 lines
6.3 KiB
C#
Raw Normal View History

using ClosedXML.Excel;
using WorkTracker.Domain;
using WorkTracker.Services.Exports;
using Xunit;
namespace WorkTracker.Tests;
public sealed class MonthlyTimesheetExcelExporterTests
{
[Fact]
public void Export_ForApril2026EmptyTimesheet_MatchesExpectedWorkbook()
{
var exporter = new MonthlyTimesheetExcelExporter();
var timesheet = CreateTimesheet(new DateOnly(2026, 4, 1), new HashSet<DateOnly>
{
new(2026, 4, 6),
new(2026, 4, 25)
});
var templatePath = GetTemplatePath();
using var templateStream = File.OpenRead(templatePath);
var workbookBytes = exporter.Export(timesheet, templateStream);
WorkbookAssert.Equivalent(GetExpectedWorkbookPath(), workbookBytes);
}
[Fact]
public void Export_ForThirtyOneDayMonth_ShiftsTotalColumnAfterLastDay()
{
var exporter = new MonthlyTimesheetExcelExporter();
var timesheet = CreateTimesheet(new DateOnly(2026, 5, 1), new HashSet<DateOnly>());
var templatePath = GetTemplatePath();
using var templateStream = File.OpenRead(templatePath);
var workbookBytes = exporter.Export(timesheet, templateStream);
using var workbook = new XLWorkbook(new MemoryStream(workbookBytes));
var worksheet = workbook.Worksheet(1);
Assert.Equal(31d, worksheet.Cell("AG1").GetDouble());
Assert.Equal("SUM(C2:AG2)", worksheet.Cell("AH2").FormulaA1);
Assert.Equal("TOTALE", worksheet.Cell("AH1").GetString());
}
[Fact]
public void Export_ForMonthWhereSecondDayIsWeekend_OnlyHighlightsWeekendAndHolidayDays()
{
var exporter = new MonthlyTimesheetExcelExporter();
var timesheet = CreateTimesheet(new DateOnly(2026, 5, 1), new HashSet<DateOnly>
{
new(2026, 5, 4)
});
var templatePath = GetTemplatePath();
using var templateStream = File.OpenRead(templatePath);
var workbookBytes = exporter.Export(timesheet, templateStream);
using var workbook = new XLWorkbook(new MemoryStream(workbookBytes));
var worksheet = workbook.Worksheet(1);
var headerRegularFill = GetFillSignature(worksheet.Cell("C1"));
var headerSaturdayFill = GetFillSignature(worksheet.Cell("D1"));
var headerSundayFill = GetFillSignature(worksheet.Cell("E1"));
var headerHolidayFill = GetFillSignature(worksheet.Cell("F1"));
var headerNextWeekdayFill = GetFillSignature(worksheet.Cell("G1"));
Assert.NotEqual(headerRegularFill, headerSaturdayFill);
Assert.NotEqual(headerRegularFill, headerSundayFill);
Assert.Equal(headerSundayFill, headerHolidayFill);
Assert.Equal(headerRegularFill, headerNextWeekdayFill);
var bodyRegularFill = GetFillSignature(worksheet.Cell("C4"));
var bodySaturdayFill = GetFillSignature(worksheet.Cell("D4"));
var bodyHolidayFill = GetFillSignature(worksheet.Cell("F4"));
var bodyNextWeekdayFill = GetFillSignature(worksheet.Cell("G4"));
Assert.NotEqual(bodyRegularFill, bodySaturdayFill);
Assert.Equal(bodySaturdayFill, bodyHolidayFill);
Assert.Equal(bodyRegularFill, bodyNextWeekdayFill);
}
private static MonthlyTimesheetModel CreateTimesheet(DateOnly monthStart, ISet<DateOnly> holidays)
{
var lastDay = monthStart.AddMonths(1).AddDays(-1);
var days = new List<MonthlyTimesheetDayModel>();
for (var date = monthStart; date <= lastDay; date = date.AddDays(1))
{
days.Add(new MonthlyTimesheetDayModel
{
Date = date,
IsWeekend = date.DayOfWeek is DayOfWeek.Saturday or DayOfWeek.Sunday,
IsHoliday = holidays.Contains(date)
});
}
return new MonthlyTimesheetModel
{
Year = monthStart.Year,
Month = monthStart.Month,
Days = days,
Rows =
[
CreateRow("office", days.Count),
CreateRow("home", days.Count),
CreateRow("overtime", days.Count),
CreateRow("weekend", days.Count),
CreateRow("night", days.Count),
CreateRow("vacation", days.Count),
CreateRow("permit", days.Count),
CreateRow("compensatory-rest", days.Count),
CreateRow("sick", days.Count),
CreateRow("holiday", days.Count)
]
};
}
private static MonthlyTimesheetRowModel CreateRow(string key, int dayCount)
{
return new MonthlyTimesheetRowModel
{
Key = key,
DailyValues = Enumerable.Repeat<decimal?>(null, dayCount).ToList()
};
}
private static string GetExpectedWorkbookPath()
{
var repositoryRoot = FindRepositoryRoot();
var candidate = Path.Combine(repositoryRoot, "tests", "WorkTracker.Tests", "Expected", "monthly-timesheet-2026-04-empty.expected.xlsx");
return File.Exists(candidate)
? candidate
: GetTemplatePath();
}
private static string GetTemplatePath()
{
var repositoryRoot = FindRepositoryRoot();
return Path.Combine(repositoryRoot, "Templates", "monthly-timesheet-template.xlsx");
}
private static string FindRepositoryRoot()
{
var directory = new DirectoryInfo(AppContext.BaseDirectory);
while (directory is not null)
{
if (File.Exists(Path.Combine(directory.FullName, "WorkTracker.sln")))
{
return directory.FullName;
}
directory = directory.Parent;
}
throw new DirectoryNotFoundException("Unable to locate the WorkTracker repository root.");
}
private static string GetFillSignature(IXLCell cell)
{
var fill = cell.Style.Fill;
return string.Join(
"|",
fill.PatternType,
DescribeColor(fill.BackgroundColor),
DescribeColor(fill.PatternColor));
}
private static string DescribeColor(XLColor color)
{
return color.ColorType switch
{
XLColorType.Color => $"rgb:{color.Color.ToArgb()}",
XLColorType.Indexed => $"indexed:{color.Indexed}",
XLColorType.Theme => $"theme:{color.ThemeColor}:{Math.Round(color.ThemeTint, 12)}",
_ => color.ColorType.ToString()
};
}
}