feat(11-01): add ReportBranding model and BrandingHtmlHelper with tests
- Add ReportBranding positional record bundling MspLogo and ClientLogo - Add BrandingHtmlHelper static class generating flex branding header HTML - Add BrandingHtmlHelperTests covering all 4 logo states (null, both null, single, both) - Add InternalsVisibleTo for SharepointToolbox.Tests in project file
This commit is contained in:
@@ -0,0 +1,105 @@
|
|||||||
|
using SharepointToolbox.Core.Models;
|
||||||
|
using SharepointToolbox.Services.Export;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace SharepointToolbox.Tests.Services.Export;
|
||||||
|
|
||||||
|
[Trait("Category", "Unit")]
|
||||||
|
public class BrandingHtmlHelperTests
|
||||||
|
{
|
||||||
|
private static LogoData MakeLogo(string mime = "image/png", string base64 = "dGVzdA==") =>
|
||||||
|
new() { MimeType = mime, Base64 = base64 };
|
||||||
|
|
||||||
|
// Test 1: null ReportBranding returns empty string
|
||||||
|
[Fact]
|
||||||
|
public void BuildBrandingHeader_NullBranding_ReturnsEmptyString()
|
||||||
|
{
|
||||||
|
var result = BrandingHtmlHelper.BuildBrandingHeader(null);
|
||||||
|
Assert.Equal(string.Empty, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 2: both logos null returns empty string
|
||||||
|
[Fact]
|
||||||
|
public void BuildBrandingHeader_BothLogosNull_ReturnsEmptyString()
|
||||||
|
{
|
||||||
|
var branding = new ReportBranding(null, null);
|
||||||
|
var result = BrandingHtmlHelper.BuildBrandingHeader(branding);
|
||||||
|
Assert.Equal(string.Empty, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 3: only MspLogo — contains MSP img tag, no second img
|
||||||
|
[Fact]
|
||||||
|
public void BuildBrandingHeader_OnlyMspLogo_ReturnsHtmlWithOneImg()
|
||||||
|
{
|
||||||
|
var msp = MakeLogo("image/png", "bXNwbG9nbw==");
|
||||||
|
var branding = new ReportBranding(msp, null);
|
||||||
|
var result = BrandingHtmlHelper.BuildBrandingHeader(branding);
|
||||||
|
|
||||||
|
Assert.Contains("data:image/png;base64,bXNwbG9nbw==", result);
|
||||||
|
Assert.Single(result.Split("<img", StringSplitOptions.None).Skip(1).ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 4: only ClientLogo — contains client img tag, no flex spacer div
|
||||||
|
[Fact]
|
||||||
|
public void BuildBrandingHeader_OnlyClientLogo_ReturnsHtmlWithOneImgNoSpacer()
|
||||||
|
{
|
||||||
|
var client = MakeLogo("image/jpeg", "Y2xpZW50bG9nbw==");
|
||||||
|
var branding = new ReportBranding(null, client);
|
||||||
|
var result = BrandingHtmlHelper.BuildBrandingHeader(branding);
|
||||||
|
|
||||||
|
Assert.Contains("data:image/jpeg;base64,Y2xpZW50bG9nbw==", result);
|
||||||
|
Assert.Single(result.Split("<img", StringSplitOptions.None).Skip(1).ToArray());
|
||||||
|
Assert.DoesNotContain("flex:1", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 5: both logos — two img tags and a flex spacer div between them
|
||||||
|
[Fact]
|
||||||
|
public void BuildBrandingHeader_BothLogos_ReturnsHtmlWithTwoImgsAndSpacer()
|
||||||
|
{
|
||||||
|
var msp = MakeLogo("image/png", "bXNw");
|
||||||
|
var client = MakeLogo("image/jpeg", "Y2xpZW50");
|
||||||
|
var branding = new ReportBranding(msp, client);
|
||||||
|
var result = BrandingHtmlHelper.BuildBrandingHeader(branding);
|
||||||
|
|
||||||
|
Assert.Contains("data:image/png;base64,bXNw", result);
|
||||||
|
Assert.Contains("data:image/jpeg;base64,Y2xpZW50", result);
|
||||||
|
Assert.Equal(2, result.Split("<img", StringSplitOptions.None).Length - 1);
|
||||||
|
Assert.Contains("flex:1", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 6: img tags use inline data-URI format
|
||||||
|
[Fact]
|
||||||
|
public void BuildBrandingHeader_WithMspLogo_UsesDataUriFormat()
|
||||||
|
{
|
||||||
|
var msp = MakeLogo("image/png", "dGVzdA==");
|
||||||
|
var branding = new ReportBranding(msp, null);
|
||||||
|
var result = BrandingHtmlHelper.BuildBrandingHeader(branding);
|
||||||
|
|
||||||
|
Assert.Contains("src=\"data:image/png;base64,dGVzdA==\"", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 7: img tags have max-height:60px and max-width:200px styles
|
||||||
|
[Fact]
|
||||||
|
public void BuildBrandingHeader_WithLogo_ImgHasCorrectDimensions()
|
||||||
|
{
|
||||||
|
var msp = MakeLogo();
|
||||||
|
var branding = new ReportBranding(msp, null);
|
||||||
|
var result = BrandingHtmlHelper.BuildBrandingHeader(branding);
|
||||||
|
|
||||||
|
Assert.Contains("max-height:60px", result);
|
||||||
|
Assert.Contains("max-width:200px", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 8: outer div uses display:flex;gap:16px;align-items:center
|
||||||
|
[Fact]
|
||||||
|
public void BuildBrandingHeader_WithLogo_OuterDivUsesFlexLayout()
|
||||||
|
{
|
||||||
|
var msp = MakeLogo();
|
||||||
|
var branding = new ReportBranding(msp, null);
|
||||||
|
var result = BrandingHtmlHelper.BuildBrandingHeader(branding);
|
||||||
|
|
||||||
|
Assert.Contains("display:flex", result);
|
||||||
|
Assert.Contains("gap:16px", result);
|
||||||
|
Assert.Contains("align-items:center", result);
|
||||||
|
}
|
||||||
|
}
|
||||||
8
SharepointToolbox/Core/Models/ReportBranding.cs
Normal file
8
SharepointToolbox/Core/Models/ReportBranding.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace SharepointToolbox.Core.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bundles MSP and client logos for passing to export services.
|
||||||
|
/// Export services receive this as a simple DTO — they don't know
|
||||||
|
/// about IBrandingService or ProfileService.
|
||||||
|
/// </summary>
|
||||||
|
public record ReportBranding(LogoData? MspLogo, LogoData? ClientLogo);
|
||||||
37
SharepointToolbox/Services/Export/BrandingHtmlHelper.cs
Normal file
37
SharepointToolbox/Services/Export/BrandingHtmlHelper.cs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
using System.Text;
|
||||||
|
using SharepointToolbox.Core.Models;
|
||||||
|
|
||||||
|
namespace SharepointToolbox.Services.Export;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates the branding header HTML fragment for HTML reports.
|
||||||
|
/// Called by each HTML export service between <body> and <h1>.
|
||||||
|
/// Returns empty string when no logos are configured (no broken images).
|
||||||
|
/// </summary>
|
||||||
|
internal static class BrandingHtmlHelper
|
||||||
|
{
|
||||||
|
public static string BuildBrandingHeader(ReportBranding? branding)
|
||||||
|
{
|
||||||
|
if (branding is null) return string.Empty;
|
||||||
|
|
||||||
|
var msp = branding.MspLogo;
|
||||||
|
var client = branding.ClientLogo;
|
||||||
|
|
||||||
|
if (msp is null && client is null) return string.Empty;
|
||||||
|
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
sb.AppendLine("<div style=\"display:flex;gap:16px;align-items:center;padding:12px 24px 0;\">");
|
||||||
|
|
||||||
|
if (msp is not null)
|
||||||
|
sb.AppendLine($" <img src=\"data:{msp.MimeType};base64,{msp.Base64}\" alt=\"\" style=\"max-height:60px;max-width:200px;object-fit:contain;\">");
|
||||||
|
|
||||||
|
if (msp is not null && client is not null)
|
||||||
|
sb.AppendLine(" <div style=\"flex:1\"></div>");
|
||||||
|
|
||||||
|
if (client is not null)
|
||||||
|
sb.AppendLine($" <img src=\"data:{client.MimeType};base64,{client.Base64}\" alt=\"\" style=\"max-height:60px;max-width:200px;object-fit:contain;\">");
|
||||||
|
|
||||||
|
sb.AppendLine("</div>");
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,6 +18,12 @@
|
|||||||
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
|
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
|
||||||
|
<_Parameter1>SharepointToolbox.Tests</_Parameter1>
|
||||||
|
</AssemblyAttribute>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ApplicationDefinition Remove="App.xaml" />
|
<ApplicationDefinition Remove="App.xaml" />
|
||||||
<Page Include="App.xaml" />
|
<Page Include="App.xaml" />
|
||||||
|
|||||||
Reference in New Issue
Block a user