Basics Done
This commit is contained in:
@@ -0,0 +1,590 @@
|
||||
using Microsoft.Playwright;
|
||||
|
||||
namespace Enciphered.Blazor.UIComponents.Tests;
|
||||
|
||||
[TestFixture]
|
||||
public class SidebarTests : PlaywrightTestBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper: navigate to home and wait for sidebar JS to initialize.
|
||||
/// </summary>
|
||||
private async Task GoHomeAsync()
|
||||
{
|
||||
await Page.GotoAsync(BaseUrl, new PageGotoOptions { WaitUntil = WaitUntilState.NetworkIdle });
|
||||
|
||||
// Wait for the sidebar JS to apply state (data-state attribute appears on wrapper)
|
||||
await Page.WaitForSelectorAsync("[data-sidebar-wrapper][data-state]", new PageWaitForSelectorOptions
|
||||
{
|
||||
Timeout = 10_000
|
||||
});
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────
|
||||
// Initial render
|
||||
// ────────────────────────────────────────────
|
||||
|
||||
[Test]
|
||||
public async Task Page_Loads_Successfully()
|
||||
{
|
||||
var response = await Page.GotoAsync(BaseUrl);
|
||||
Assert.That(response, Is.Not.Null);
|
||||
Assert.That(response!.Status, Is.EqualTo(200));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Sidebar_Wrapper_Has_DataState_After_Init()
|
||||
{
|
||||
await GoHomeAsync();
|
||||
|
||||
var wrapper = Page.Locator("[data-sidebar-wrapper]");
|
||||
var state = await wrapper.GetAttributeAsync("data-state");
|
||||
Assert.That(state, Is.Not.Null.And.Not.Empty, "Wrapper should have data-state after JS init");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Sidebar_Starts_Expanded_On_Desktop()
|
||||
{
|
||||
// Ensure desktop viewport
|
||||
await Page.SetViewportSizeAsync(1280, 800);
|
||||
await GoHomeAsync();
|
||||
|
||||
var wrapper = Page.Locator("[data-sidebar-wrapper]");
|
||||
var state = await wrapper.GetAttributeAsync("data-state");
|
||||
Assert.That(state, Is.EqualTo("expanded"), "Sidebar should start expanded on desktop");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Sidebar_Element_Exists()
|
||||
{
|
||||
await GoHomeAsync();
|
||||
|
||||
var sidebar = Page.Locator("[data-sidebar]");
|
||||
await Expect(sidebar).ToBeVisibleAsync();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Sidebar_Trigger_Exists_On_Header()
|
||||
{
|
||||
await GoHomeAsync();
|
||||
|
||||
// The sidebar header itself is the trigger on desktop
|
||||
var trigger = Page.Locator("[data-sidebar-header][data-sidebar-trigger]");
|
||||
await Expect(trigger).ToBeVisibleAsync();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Sidebar_Has_Navigation_Links()
|
||||
{
|
||||
await GoHomeAsync();
|
||||
|
||||
var sidebar = Page.Locator("[data-sidebar]");
|
||||
var links = sidebar.Locator("a[href]");
|
||||
var count = await links.CountAsync();
|
||||
Assert.That(count, Is.GreaterThanOrEqualTo(3), "Should have at least Home, Counter, Weather links");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Sidebar_Has_Footer()
|
||||
{
|
||||
await GoHomeAsync();
|
||||
|
||||
var footer = Page.Locator("[data-sidebar-footer]");
|
||||
await Expect(footer).ToBeVisibleAsync();
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────
|
||||
// Desktop toggle (collapse / expand)
|
||||
// ────────────────────────────────────────────
|
||||
|
||||
[Test]
|
||||
public async Task Desktop_Click_Trigger_Collapses_Sidebar()
|
||||
{
|
||||
await Page.SetViewportSizeAsync(1280, 800);
|
||||
await GoHomeAsync();
|
||||
|
||||
// Verify starts expanded
|
||||
var wrapper = Page.Locator("[data-sidebar-wrapper]");
|
||||
Assert.That(await wrapper.GetAttributeAsync("data-state"), Is.EqualTo("expanded"));
|
||||
|
||||
// Click the sidebar header (which is the trigger on desktop)
|
||||
await Page.Locator("[data-sidebar-header][data-sidebar-trigger]").ClickAsync();
|
||||
|
||||
// Wait for state change
|
||||
await Page.WaitForSelectorAsync("[data-sidebar-wrapper][data-state='collapsed']", new PageWaitForSelectorOptions
|
||||
{
|
||||
Timeout = 5_000
|
||||
});
|
||||
|
||||
Assert.That(await wrapper.GetAttributeAsync("data-state"), Is.EqualTo("collapsed"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Desktop_Click_Trigger_Twice_Re_Expands_Sidebar()
|
||||
{
|
||||
await Page.SetViewportSizeAsync(1280, 800);
|
||||
await GoHomeAsync();
|
||||
|
||||
var trigger = Page.Locator("[data-sidebar-header][data-sidebar-trigger]");
|
||||
|
||||
// Collapse
|
||||
await trigger.ClickAsync();
|
||||
await Page.WaitForSelectorAsync("[data-sidebar-wrapper][data-state='collapsed']");
|
||||
|
||||
// Expand
|
||||
await trigger.ClickAsync();
|
||||
await Page.WaitForSelectorAsync("[data-sidebar-wrapper][data-state='expanded']");
|
||||
|
||||
var state = await Page.Locator("[data-sidebar-wrapper]").GetAttributeAsync("data-state");
|
||||
Assert.That(state, Is.EqualTo("expanded"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Desktop_Collapsed_Sidebar_Has_Reduced_Width()
|
||||
{
|
||||
await Page.SetViewportSizeAsync(1280, 800);
|
||||
await GoHomeAsync();
|
||||
|
||||
var sidebar = Page.Locator("[data-sidebar]");
|
||||
|
||||
// Get expanded width
|
||||
var expandedWidth = await sidebar.EvaluateAsync<double>("el => el.getBoundingClientRect().width");
|
||||
Assert.That(expandedWidth, Is.GreaterThan(100), "Expanded sidebar should be wider than 100px");
|
||||
|
||||
// Collapse
|
||||
await Page.Locator("[data-sidebar-header][data-sidebar-trigger]").ClickAsync();
|
||||
await Page.WaitForSelectorAsync("[data-sidebar-wrapper][data-state='collapsed']");
|
||||
|
||||
// Wait for CSS transition
|
||||
await Page.WaitForTimeoutAsync(300);
|
||||
|
||||
// Get collapsed width
|
||||
var collapsedWidth = await sidebar.EvaluateAsync<double>("el => el.getBoundingClientRect().width");
|
||||
Assert.That(collapsedWidth, Is.LessThan(expandedWidth), "Collapsed sidebar should be narrower");
|
||||
Assert.That(collapsedWidth, Is.LessThanOrEqualTo(60), "Collapsed sidebar should be icon-only width (~3rem = 48px)");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Desktop_Collapsed_Hides_Menu_Labels()
|
||||
{
|
||||
await Page.SetViewportSizeAsync(1280, 800);
|
||||
await GoHomeAsync();
|
||||
|
||||
// Target only the menu item label spans (inside sidebar-content, not sidebar-header)
|
||||
var labelSpans = Page.Locator("[data-sidebar-content] a span.truncate");
|
||||
var countBefore = await labelSpans.CountAsync();
|
||||
Assert.That(countBefore, Is.GreaterThan(0), "Should have label spans");
|
||||
|
||||
// Collapse
|
||||
await Page.Locator("[data-sidebar-header][data-sidebar-trigger]").ClickAsync();
|
||||
await Page.WaitForSelectorAsync("[data-sidebar-wrapper][data-state='collapsed']");
|
||||
await Page.WaitForTimeoutAsync(300);
|
||||
|
||||
// Check that label text is hidden (CSS-driven via group-data-[state=collapsed])
|
||||
for (int i = 0; i < countBefore; i++)
|
||||
{
|
||||
var display = await labelSpans.Nth(i).EvaluateAsync<string>("el => getComputedStyle(el).display");
|
||||
Assert.That(display, Is.EqualTo("none"), $"Label span {i} should have display:none when sidebar is collapsed (got '{display}')");
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Desktop_Collapsed_Hides_Group_Label()
|
||||
{
|
||||
await Page.SetViewportSizeAsync(1280, 800);
|
||||
await GoHomeAsync();
|
||||
|
||||
var groupLabel = Page.Locator("[data-sidebar-group-label]");
|
||||
await Expect(groupLabel).ToBeVisibleAsync();
|
||||
|
||||
// Collapse
|
||||
await Page.Locator("[data-sidebar-header][data-sidebar-trigger]").ClickAsync();
|
||||
await Page.WaitForSelectorAsync("[data-sidebar-wrapper][data-state='collapsed']");
|
||||
await Page.WaitForTimeoutAsync(300);
|
||||
|
||||
await Expect(groupLabel).Not.ToBeVisibleAsync();
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────
|
||||
// Spacer width tracks sidebar state
|
||||
// ────────────────────────────────────────────
|
||||
|
||||
[Test]
|
||||
public async Task Desktop_Spacer_Width_Changes_On_Collapse()
|
||||
{
|
||||
await Page.SetViewportSizeAsync(1280, 800);
|
||||
await GoHomeAsync();
|
||||
|
||||
var spacer = Page.Locator("[data-sidebar-spacer]");
|
||||
var expandedWidth = await spacer.EvaluateAsync<double>("el => el.getBoundingClientRect().width");
|
||||
Assert.That(expandedWidth, Is.GreaterThan(100), "Spacer should be wide when expanded");
|
||||
|
||||
// Collapse
|
||||
await Page.Locator("[data-sidebar-header][data-sidebar-trigger]").ClickAsync();
|
||||
await Page.WaitForSelectorAsync("[data-sidebar-wrapper][data-state='collapsed']");
|
||||
await Page.WaitForTimeoutAsync(300);
|
||||
|
||||
var collapsedWidth = await spacer.EvaluateAsync<double>("el => el.getBoundingClientRect().width");
|
||||
Assert.That(collapsedWidth, Is.LessThan(expandedWidth), "Spacer should shrink when collapsed");
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────
|
||||
// Mobile behavior
|
||||
// ────────────────────────────────────────────
|
||||
|
||||
[Test]
|
||||
public async Task Mobile_Sidebar_Starts_Closed()
|
||||
{
|
||||
await Page.SetViewportSizeAsync(375, 812);
|
||||
await GoHomeAsync();
|
||||
|
||||
var wrapper = Page.Locator("[data-sidebar-wrapper]");
|
||||
var state = await wrapper.GetAttributeAsync("data-state");
|
||||
Assert.That(state, Is.EqualTo("collapsed"), "Sidebar should start collapsed on mobile");
|
||||
|
||||
var mobile = await wrapper.GetAttributeAsync("data-mobile");
|
||||
Assert.That(mobile, Is.EqualTo("true"), "data-mobile should be 'true' on small viewport");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Mobile_Click_Trigger_Opens_Sidebar_And_Shows_Overlay()
|
||||
{
|
||||
await Page.SetViewportSizeAsync(375, 812);
|
||||
await GoHomeAsync();
|
||||
|
||||
// Click the mobile trigger button in the inset (visible only on mobile)
|
||||
await Page.Locator("[data-sidebar-inset] [data-sidebar-trigger]").ClickAsync();
|
||||
await Page.WaitForSelectorAsync("[data-sidebar-wrapper][data-state='expanded']");
|
||||
|
||||
// Overlay should be visible
|
||||
var overlay = Page.Locator("[data-sidebar-overlay]");
|
||||
var display = await overlay.EvaluateAsync<string>("el => getComputedStyle(el).display");
|
||||
Assert.That(display, Is.Not.EqualTo("none"), "Overlay should be visible when mobile sidebar is open");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Mobile_Click_Overlay_Closes_Sidebar()
|
||||
{
|
||||
await Page.SetViewportSizeAsync(375, 812);
|
||||
await GoHomeAsync();
|
||||
|
||||
// Open via the inset trigger
|
||||
await Page.Locator("[data-sidebar-inset] [data-sidebar-trigger]").ClickAsync();
|
||||
await Page.WaitForSelectorAsync("[data-sidebar-wrapper][data-state='expanded']");
|
||||
|
||||
// Click overlay
|
||||
await Page.Locator("[data-sidebar-overlay]").ClickAsync(new LocatorClickOptions { Force = true });
|
||||
await Page.WaitForSelectorAsync("[data-sidebar-wrapper][data-state='collapsed']");
|
||||
|
||||
var state = await Page.Locator("[data-sidebar-wrapper]").GetAttributeAsync("data-state");
|
||||
Assert.That(state, Is.EqualTo("collapsed"), "Sidebar should close when overlay is clicked");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Mobile_Overlay_Hidden_When_Sidebar_Closed()
|
||||
{
|
||||
await Page.SetViewportSizeAsync(375, 812);
|
||||
await GoHomeAsync();
|
||||
|
||||
var overlay = Page.Locator("[data-sidebar-overlay]");
|
||||
var display = await overlay.EvaluateAsync<string>("el => el.style.display || getComputedStyle(el).display");
|
||||
Assert.That(display, Is.EqualTo("none"), "Overlay should be hidden when mobile sidebar is closed");
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────
|
||||
// Navigation links work
|
||||
// ────────────────────────────────────────────
|
||||
|
||||
[Test]
|
||||
public async Task Clicking_Counter_Link_Navigates_To_Counter()
|
||||
{
|
||||
await Page.SetViewportSizeAsync(1280, 800);
|
||||
await GoHomeAsync();
|
||||
|
||||
var counterLink = Page.Locator("[data-sidebar] a[href='/counter']");
|
||||
await counterLink.ClickAsync();
|
||||
|
||||
await Page.WaitForURLAsync($"{BaseUrl}/counter");
|
||||
Assert.That(Page.Url, Does.Contain("/counter"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Clicking_Weather_Link_Navigates_To_Weather()
|
||||
{
|
||||
await Page.SetViewportSizeAsync(1280, 800);
|
||||
await GoHomeAsync();
|
||||
|
||||
var weatherLink = Page.Locator("[data-sidebar] a[href='/weather']");
|
||||
await weatherLink.ClickAsync();
|
||||
|
||||
await Page.WaitForURLAsync($"{BaseUrl}/weather");
|
||||
Assert.That(Page.Url, Does.Contain("/weather"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Clicking_Header_Toggles_Sidebar()
|
||||
{
|
||||
await Page.SetViewportSizeAsync(1280, 800);
|
||||
await GoHomeAsync();
|
||||
|
||||
// Starts expanded
|
||||
var wrapper = Page.Locator("[data-sidebar-wrapper]");
|
||||
Assert.That(await wrapper.GetAttributeAsync("data-state"), Is.EqualTo("expanded"));
|
||||
|
||||
// Click the sidebar header (logo area) to collapse
|
||||
await Page.Locator("[data-sidebar-header]").ClickAsync();
|
||||
await Page.WaitForSelectorAsync("[data-sidebar-wrapper][data-state='collapsed']");
|
||||
Assert.That(await wrapper.GetAttributeAsync("data-state"), Is.EqualTo("collapsed"));
|
||||
|
||||
// Click again to expand
|
||||
await Page.Locator("[data-sidebar-header]").ClickAsync();
|
||||
await Page.WaitForSelectorAsync("[data-sidebar-wrapper][data-state='expanded']");
|
||||
Assert.That(await wrapper.GetAttributeAsync("data-state"), Is.EqualTo("expanded"));
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────
|
||||
// Navigation should NOT change sidebar state
|
||||
// ────────────────────────────────────────────
|
||||
|
||||
[Test]
|
||||
public async Task Desktop_Collapsed_Sidebar_Stays_Collapsed_After_Navigation()
|
||||
{
|
||||
await Page.SetViewportSizeAsync(1280, 800);
|
||||
await GoHomeAsync();
|
||||
|
||||
// Verify starts expanded
|
||||
var wrapper = Page.Locator("[data-sidebar-wrapper]");
|
||||
Assert.That(await wrapper.GetAttributeAsync("data-state"), Is.EqualTo("expanded"));
|
||||
|
||||
// Collapse the sidebar
|
||||
await Page.Locator("[data-sidebar-header][data-sidebar-trigger]").ClickAsync();
|
||||
await Page.WaitForSelectorAsync("[data-sidebar-wrapper][data-state='collapsed']");
|
||||
Assert.That(await wrapper.GetAttributeAsync("data-state"), Is.EqualTo("collapsed"));
|
||||
|
||||
// Click a navigation link (Counter)
|
||||
var counterLink = Page.Locator("[data-sidebar] a[href='/counter']");
|
||||
await counterLink.ClickAsync();
|
||||
|
||||
// Wait for navigation to complete
|
||||
await Page.WaitForURLAsync($"{BaseUrl}/counter");
|
||||
|
||||
// Wait for the sidebar JS to re-apply state after enhanced navigation
|
||||
await Page.WaitForSelectorAsync("[data-sidebar-wrapper][data-state]", new PageWaitForSelectorOptions
|
||||
{
|
||||
Timeout = 5_000
|
||||
});
|
||||
|
||||
// Give any transitions/scripts time to settle
|
||||
await Page.WaitForTimeoutAsync(500);
|
||||
|
||||
// Sidebar should STILL be collapsed
|
||||
var stateAfterNav = await wrapper.GetAttributeAsync("data-state");
|
||||
Assert.That(stateAfterNav, Is.EqualTo("collapsed"),
|
||||
"Sidebar should remain collapsed after clicking a navigation link");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Desktop_Collapsed_Sidebar_Stays_Collapsed_After_Multiple_Navigations()
|
||||
{
|
||||
await Page.SetViewportSizeAsync(1280, 800);
|
||||
await GoHomeAsync();
|
||||
|
||||
// Collapse the sidebar
|
||||
await Page.Locator("[data-sidebar-header][data-sidebar-trigger]").ClickAsync();
|
||||
await Page.WaitForSelectorAsync("[data-sidebar-wrapper][data-state='collapsed']");
|
||||
|
||||
var wrapper = Page.Locator("[data-sidebar-wrapper]");
|
||||
|
||||
// Navigate to Counter
|
||||
await Page.Locator("[data-sidebar] a[href='/counter']").ClickAsync();
|
||||
await Page.WaitForURLAsync($"{BaseUrl}/counter");
|
||||
await Page.WaitForSelectorAsync("[data-sidebar-wrapper][data-state]");
|
||||
await Page.WaitForTimeoutAsync(500);
|
||||
Assert.That(await wrapper.GetAttributeAsync("data-state"), Is.EqualTo("collapsed"),
|
||||
"Sidebar should remain collapsed after navigating to Counter");
|
||||
|
||||
// Navigate to Weather
|
||||
await Page.Locator("[data-sidebar] a[href='/weather']").ClickAsync();
|
||||
await Page.WaitForURLAsync($"{BaseUrl}/weather");
|
||||
await Page.WaitForSelectorAsync("[data-sidebar-wrapper][data-state]");
|
||||
await Page.WaitForTimeoutAsync(500);
|
||||
Assert.That(await wrapper.GetAttributeAsync("data-state"), Is.EqualTo("collapsed"),
|
||||
"Sidebar should remain collapsed after navigating to Weather");
|
||||
|
||||
// Navigate back Home
|
||||
await Page.Locator("[data-sidebar] a[href='/']").ClickAsync();
|
||||
await Page.WaitForURLAsync(url => url == BaseUrl || url == BaseUrl + "/");
|
||||
await Page.WaitForSelectorAsync("[data-sidebar-wrapper][data-state]");
|
||||
await Page.WaitForTimeoutAsync(500);
|
||||
Assert.That(await wrapper.GetAttributeAsync("data-state"), Is.EqualTo("collapsed"),
|
||||
"Sidebar should remain collapsed after navigating back to Home");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Desktop_Sidebar_Toggle_Works_After_Same_Page_Navigation()
|
||||
{
|
||||
await Page.SetViewportSizeAsync(1280, 800);
|
||||
await GoHomeAsync();
|
||||
|
||||
var wrapper = Page.Locator("[data-sidebar-wrapper]");
|
||||
|
||||
// Starts expanded
|
||||
Assert.That(await wrapper.GetAttributeAsync("data-state"), Is.EqualTo("expanded"));
|
||||
|
||||
// Click the Home link while already on home (same-page navigation)
|
||||
await Page.Locator("[data-sidebar] a[href='/']").ClickAsync();
|
||||
|
||||
// Wait for any enhanced navigation / DOM mutation to settle
|
||||
await Page.WaitForSelectorAsync("[data-sidebar-wrapper][data-state]", new PageWaitForSelectorOptions
|
||||
{
|
||||
Timeout = 5_000
|
||||
});
|
||||
await Page.WaitForTimeoutAsync(500);
|
||||
|
||||
// Now click the sidebar trigger to collapse
|
||||
await Page.Locator("[data-sidebar-header][data-sidebar-trigger]").ClickAsync();
|
||||
await Page.WaitForTimeoutAsync(300);
|
||||
|
||||
var stateAfterFirstToggle = await wrapper.GetAttributeAsync("data-state");
|
||||
Assert.That(stateAfterFirstToggle, Is.EqualTo("collapsed"),
|
||||
"Sidebar should be collapsed after one trigger click following same-page nav");
|
||||
|
||||
// Click the sidebar trigger again to expand
|
||||
await Page.Locator("[data-sidebar-header][data-sidebar-trigger]").ClickAsync();
|
||||
await Page.WaitForTimeoutAsync(300);
|
||||
|
||||
var stateAfterSecondToggle = await wrapper.GetAttributeAsync("data-state");
|
||||
Assert.That(stateAfterSecondToggle, Is.EqualTo("expanded"),
|
||||
"Sidebar should be expanded after second trigger click following same-page nav");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Desktop_Collapsed_Sidebar_Stays_Collapsed_After_Same_Page_Navigation()
|
||||
{
|
||||
await Page.SetViewportSizeAsync(1280, 800);
|
||||
await GoHomeAsync();
|
||||
|
||||
var wrapper = Page.Locator("[data-sidebar-wrapper]");
|
||||
|
||||
// Collapse the sidebar first
|
||||
await Page.Locator("[data-sidebar-header][data-sidebar-trigger]").ClickAsync();
|
||||
await Page.WaitForSelectorAsync("[data-sidebar-wrapper][data-state='collapsed']");
|
||||
Assert.That(await wrapper.GetAttributeAsync("data-state"), Is.EqualTo("collapsed"));
|
||||
|
||||
// Click the Home link while already on home (same-page navigation)
|
||||
await Page.Locator("[data-sidebar] a[href='/']").ClickAsync();
|
||||
|
||||
// Wait for any enhanced navigation / DOM mutation to settle
|
||||
await Page.WaitForTimeoutAsync(500);
|
||||
|
||||
// Sidebar should STILL be collapsed
|
||||
Assert.That(await wrapper.GetAttributeAsync("data-state"), Is.EqualTo("collapsed"),
|
||||
"Sidebar should remain collapsed after same-page navigation");
|
||||
|
||||
// Toggle should still work correctly: collapse -> expand
|
||||
await Page.Locator("[data-sidebar-header][data-sidebar-trigger]").ClickAsync();
|
||||
await Page.WaitForTimeoutAsync(300);
|
||||
Assert.That(await wrapper.GetAttributeAsync("data-state"), Is.EqualTo("expanded"),
|
||||
"Trigger should expand sidebar after same-page nav while collapsed");
|
||||
|
||||
// And back to collapsed
|
||||
await Page.Locator("[data-sidebar-header][data-sidebar-trigger]").ClickAsync();
|
||||
await Page.WaitForTimeoutAsync(300);
|
||||
Assert.That(await wrapper.GetAttributeAsync("data-state"), Is.EqualTo("collapsed"),
|
||||
"Trigger should collapse sidebar again after same-page nav");
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────
|
||||
// Cookie persistence
|
||||
// ────────────────────────────────────────────
|
||||
|
||||
[Test]
|
||||
public async Task Desktop_State_Persists_Via_Cookie()
|
||||
{
|
||||
await Page.SetViewportSizeAsync(1280, 800);
|
||||
await GoHomeAsync();
|
||||
|
||||
// Collapse
|
||||
await Page.Locator("[data-sidebar-header][data-sidebar-trigger]").ClickAsync();
|
||||
await Page.WaitForSelectorAsync("[data-sidebar-wrapper][data-state='collapsed']");
|
||||
|
||||
// Check cookie
|
||||
var cookies = await Context.CookiesAsync();
|
||||
var sidebarCookie = cookies.FirstOrDefault(c => c.Name == "sidebar:state");
|
||||
Assert.That(sidebarCookie, Is.Not.Null, "sidebar:state cookie should exist");
|
||||
Assert.That(sidebarCookie!.Value, Is.EqualTo("closed"), "Cookie should be 'closed' after collapse");
|
||||
|
||||
// Reload page — sidebar should remain collapsed
|
||||
await Page.ReloadAsync(new PageReloadOptions { WaitUntil = WaitUntilState.NetworkIdle });
|
||||
await Page.WaitForSelectorAsync("[data-sidebar-wrapper][data-state]");
|
||||
|
||||
var state = await Page.Locator("[data-sidebar-wrapper]").GetAttributeAsync("data-state");
|
||||
Assert.That(state, Is.EqualTo("collapsed"), "Sidebar state should persist after reload");
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────
|
||||
// Viewport resize transitions
|
||||
// ────────────────────────────────────────────
|
||||
|
||||
[Test]
|
||||
public async Task Resize_From_Desktop_To_Mobile_Collapses_Sidebar()
|
||||
{
|
||||
await Page.SetViewportSizeAsync(1280, 800);
|
||||
await GoHomeAsync();
|
||||
|
||||
var wrapper = Page.Locator("[data-sidebar-wrapper]");
|
||||
Assert.That(await wrapper.GetAttributeAsync("data-state"), Is.EqualTo("expanded"));
|
||||
|
||||
// Shrink to mobile
|
||||
await Page.SetViewportSizeAsync(375, 812);
|
||||
|
||||
// Wait for resize handler to fire
|
||||
await Page.WaitForSelectorAsync("[data-sidebar-wrapper][data-mobile='true']", new PageWaitForSelectorOptions
|
||||
{
|
||||
Timeout = 5_000
|
||||
});
|
||||
|
||||
Assert.That(await wrapper.GetAttributeAsync("data-state"), Is.EqualTo("collapsed"));
|
||||
Assert.That(await wrapper.GetAttributeAsync("data-mobile"), Is.EqualTo("true"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Resize_From_Mobile_To_Desktop_Expands_Sidebar()
|
||||
{
|
||||
await Page.SetViewportSizeAsync(375, 812);
|
||||
await GoHomeAsync();
|
||||
|
||||
var wrapper = Page.Locator("[data-sidebar-wrapper]");
|
||||
Assert.That(await wrapper.GetAttributeAsync("data-state"), Is.EqualTo("collapsed"));
|
||||
|
||||
// Grow to desktop
|
||||
await Page.SetViewportSizeAsync(1280, 800);
|
||||
|
||||
await Page.WaitForSelectorAsync("[data-sidebar-wrapper][data-mobile='false']", new PageWaitForSelectorOptions
|
||||
{
|
||||
Timeout = 5_000
|
||||
});
|
||||
|
||||
Assert.That(await wrapper.GetAttributeAsync("data-state"), Is.EqualTo("expanded"));
|
||||
Assert.That(await wrapper.GetAttributeAsync("data-mobile"), Is.EqualTo("false"));
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────
|
||||
// Sidebar inset (main content area)
|
||||
// ────────────────────────────────────────────
|
||||
|
||||
[Test]
|
||||
public async Task SidebarInset_Exists_And_Contains_Page_Content()
|
||||
{
|
||||
await GoHomeAsync();
|
||||
|
||||
var inset = Page.Locator("[data-sidebar-inset]");
|
||||
await Expect(inset).ToBeVisibleAsync();
|
||||
|
||||
// On mobile, the inset should contain the trigger button
|
||||
await Page.SetViewportSizeAsync(375, 812);
|
||||
await GoHomeAsync();
|
||||
|
||||
var mobileTrigger = inset.Locator("[data-sidebar-trigger]");
|
||||
await Expect(mobileTrigger).ToBeVisibleAsync();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user