diff --git a/SharepointToolbox.Tests/Services/Export/HtmlExportServiceTests.cs b/SharepointToolbox.Tests/Services/Export/HtmlExportServiceTests.cs
index ac8de01..2889bf6 100644
--- a/SharepointToolbox.Tests/Services/Export/HtmlExportServiceTests.cs
+++ b/SharepointToolbox.Tests/Services/Export/HtmlExportServiceTests.cs
@@ -5,15 +5,14 @@ namespace SharepointToolbox.Tests.Services.Export;
///
/// Tests for PERM-06: HTML export output.
-/// These tests reference HtmlExportService which will be implemented in Plan 03.
-/// Until Plan 03 runs they will fail to compile — that is expected.
///
public class HtmlExportServiceTests
{
private static PermissionEntry MakeEntry(
string users, string userLogins,
- string url = "https://contoso.sharepoint.com/sites/A") =>
- new("Web", "Site A", url, true, users, userLogins, "Read", "Direct Permissions", "User");
+ string url = "https://contoso.sharepoint.com/sites/A",
+ string principalType = "User") =>
+ new("Web", "Site A", url, true, users, userLogins, "Read", "Direct Permissions", principalType);
private static ReportBranding MakeBranding(bool msp = true, bool client = false)
{
@@ -22,6 +21,13 @@ public class HtmlExportServiceTests
return new ReportBranding(mspLogo, clientLogo);
}
+ private static IReadOnlyDictionary> MakeGroupMembers(
+ string groupName, params ResolvedMember[] members) =>
+ new Dictionary>(StringComparer.OrdinalIgnoreCase)
+ {
+ [groupName] = members.ToList()
+ };
+
[Fact]
public void BuildHtml_WithKnownEntries_ContainsUserNames()
{
@@ -87,4 +93,79 @@ public class HtmlExportServiceTests
Assert.Contains("data:image/png;base64,bXNw", html);
Assert.Contains("data:image/jpeg;base64,Y2xpZW50", html);
}
+
+ // ── Group expansion tests (Phase 17) ──────────────────────────────────────
+
+ [Fact]
+ public void BuildHtml_NoGroupMembers_IdenticalToDefault()
+ {
+ var entry = MakeEntry("Site Members", "i:0#.f|membership|group@contoso.com", principalType: "SharePointGroup");
+ var svc = new HtmlExportService();
+ var htmlDefault = svc.BuildHtml(new[] { entry });
+ var htmlNullNull = svc.BuildHtml(new[] { entry }, null, null);
+
+ Assert.Equal(htmlDefault, htmlNullNull);
+ }
+
+ [Fact]
+ public void BuildHtml_WithGroupMembers_RendersExpandablePill()
+ {
+ var entry = MakeEntry("Site Members", "i:0#.f|membership|group@contoso.com", principalType: "SharePointGroup");
+ var groupMembers = MakeGroupMembers("Site Members", new ResolvedMember("Alice", "alice@co.com"));
+ var svc = new HtmlExportService();
+ var html = svc.BuildHtml(new[] { entry }, null, groupMembers);
+
+ Assert.Contains("onclick=\"toggleGroup('grpmem0')\"", html);
+ Assert.Contains("class=\"user-pill group-expandable\"", html);
+ }
+
+ [Fact]
+ public void BuildHtml_WithGroupMembers_RendersHiddenMemberSubRow()
+ {
+ var entry = MakeEntry("Site Members", "i:0#.f|membership|group@contoso.com", principalType: "SharePointGroup");
+ var groupMembers = MakeGroupMembers("Site Members", new ResolvedMember("Alice", "alice@co.com"));
+ var svc = new HtmlExportService();
+ var html = svc.BuildHtml(new[] { entry }, null, groupMembers);
+
+ Assert.Contains("data-group=\"grpmem0\"", html);
+ Assert.Contains("display:none", html);
+ Assert.Contains("Alice", html);
+ Assert.Contains("alice@co.com", html);
+ }
+
+ [Fact]
+ public void BuildHtml_WithEmptyMemberList_RendersMembersUnavailable()
+ {
+ var entry = MakeEntry("Site Members", "i:0#.f|membership|group@contoso.com", principalType: "SharePointGroup");
+ var groupMembers = MakeGroupMembers("Site Members"); // empty list
+ var svc = new HtmlExportService();
+ var html = svc.BuildHtml(new[] { entry }, null, groupMembers);
+
+ Assert.Contains("members unavailable", html);
+ }
+
+ [Fact]
+ public void BuildHtml_ContainsToggleGroupJs()
+ {
+ var entry = MakeEntry("Site Members", "i:0#.f|membership|group@contoso.com", principalType: "SharePointGroup");
+ var groupMembers = MakeGroupMembers("Site Members", new ResolvedMember("Alice", "alice@co.com"));
+ var svc = new HtmlExportService();
+ var html = svc.BuildHtml(new[] { entry }, null, groupMembers);
+
+ Assert.Contains("function toggleGroup", html);
+ }
+
+ [Fact]
+ public void BuildHtml_Simplified_WithGroupMembers_RendersExpandablePill()
+ {
+ var innerEntry = new PermissionEntry("Web", "Site A", "https://contoso.sharepoint.com/sites/A",
+ true, "Site Members", "i:0#.f|membership|group@contoso.com", "Read", "Direct Permissions", "SharePointGroup");
+ var simplifiedEntry = new SimplifiedPermissionEntry(innerEntry);
+ var groupMembers = MakeGroupMembers("Site Members", new ResolvedMember("Bob", "bob@co.com"));
+ var svc = new HtmlExportService();
+ var html = svc.BuildHtml(new[] { simplifiedEntry }, null, groupMembers);
+
+ Assert.Contains("onclick=\"toggleGroup('grpmem0')\"", html);
+ Assert.Contains("class=\"user-pill group-expandable\"", html);
+ }
}