using Microsoft.Playwright;
namespace Enciphered.Blazor.UIComponents.Tests;
///
/// Tests that cover interactive behavior gaps to ensure safe JS migration.
/// Covers: NumberInput +/- buttons & min/max clamping, Popover open/close mechanics,
/// Calendar arrow navigation, and trigger text updates for Date/Time/DateTime inputs.
///
[TestFixture]
public class InteractivityTests : PlaywrightTestBase
{
// ── Helpers ──────────────────────────────────────────────────────────────
private async Task GoToFormsAsync()
{
await Page.GotoAsync($"{BaseUrl}/forms", new PageGotoOptions { WaitUntil = WaitUntilState.NetworkIdle });
await Page.WaitForSelectorAsync("[data-testid='btn-submit']", new PageWaitForSelectorOptions { Timeout = 10_000 });
}
private ILocator Input(string testId) => Page.Locator($"[data-testid='{testId}']");
private ILocator Trigger(string testId) => Page.Locator($"[data-testid='{testId}']");
///
/// Get the popover panel scoped to the popover containing a trigger.
///
private ILocator PopoverPanelFor(string triggerId) =>
Trigger(triggerId).Locator("xpath=ancestor::*[@data-popover]").Locator("[data-popover-panel]");
private ILocator PopoverBackdropFor(string triggerId) =>
Trigger(triggerId).Locator("xpath=ancestor::*[@data-popover]").Locator("[data-popover-backdrop]");
///
/// Navigate the open calendar to a specific month/year using the month and year pickers.
///
private async Task NavigateCalendarToDate(ILocator panel, DateOnly target)
{
var yearButton = panel.Locator("[data-calendar-year]");
await yearButton.ClickAsync();
await Page.WaitForTimeoutAsync(150);
var yearGrid = panel.Locator(".grid.grid-cols-4");
var targetYearBtn = yearGrid.Locator($"button:has-text('{target.Year}')");
var attempts = 0;
while (await targetYearBtn.CountAsync() == 0 && attempts < 10)
{
var firstYearText = await yearGrid.Locator("button").First.InnerTextAsync();
var firstYear = int.Parse(firstYearText.Trim());
if (target.Year < firstYear)
await panel.Locator("button[aria-label='Previous month']").ClickAsync();
else
await panel.Locator("button[aria-label='Next month']").ClickAsync();
await Page.WaitForTimeoutAsync(100);
yearGrid = panel.Locator(".grid.grid-cols-4");
targetYearBtn = yearGrid.Locator($"button:has-text('{target.Year}')");
attempts++;
}
await targetYearBtn.First.ClickAsync();
await Page.WaitForTimeoutAsync(150);
var monthButton = panel.Locator("[data-calendar-month]");
await monthButton.ClickAsync();
await Page.WaitForTimeoutAsync(150);
var monthGrid = panel.Locator(".grid.grid-cols-3");
var targetMonthText = new DateOnly(2000, target.Month, 1).ToString("MMM");
await monthGrid.Locator($"button:has-text('{targetMonthText}')").First.ClickAsync();
await Page.WaitForTimeoutAsync(150);
}
///
/// Pick hour, minute, and AM/PM in an already-open time picker popover.
///
private async Task PickTimeInOpenPopover(ILocator panel, int hour, int minute)
{
var isPm = hour >= 12;
var hour12 = hour % 12;
if (hour12 == 0) hour12 = 12;
var hourText = hour12.ToString("D2");
var hourColumn = panel.Locator(".scrollbar-thin").First;
await hourColumn.Locator($"button:has-text('{hourText}')").First.ClickAsync();
await Page.WaitForTimeoutAsync(50);
var minuteText = minute.ToString("D2");
var minuteColumn = panel.Locator(".scrollbar-thin").Nth(1);
await minuteColumn.Locator($"button:has-text('{minuteText}')").First.ClickAsync();
await Page.WaitForTimeoutAsync(50);
var periodText = isPm ? "PM" : "AM";
await panel.Locator($"button:has-text('{periodText}')").First.ClickAsync();
await Page.WaitForTimeoutAsync(50);
}
// ════════════════════════════════════════════════════════════════════════
// NumberInput: Increment / Decrement buttons
// ════════════════════════════════════════════════════════════════════════
[Test]
public async Task NumberInput_Increment_Button_Increases_Value()
{
await GoToFormsAsync();
var input = Input("input-age");
await input.FillAsync("25");
await Page.WaitForTimeoutAsync(100);
var incrementBtn = Page.Locator("[data-testid='input-age']").Locator("..").Locator("button[aria-label='Increment']");
await incrementBtn.ClickAsync();
await Page.WaitForTimeoutAsync(100);
await Expect(input).ToHaveValueAsync("26");
}
[Test]
public async Task NumberInput_Decrement_Button_Decreases_Value()
{
await GoToFormsAsync();
var input = Input("input-age");
await input.FillAsync("25");
await Page.WaitForTimeoutAsync(100);
var decrementBtn = Page.Locator("[data-testid='input-age']").Locator("..").Locator("button[aria-label='Decrement']");
await decrementBtn.ClickAsync();
await Page.WaitForTimeoutAsync(100);
await Expect(input).ToHaveValueAsync("24");
}
[Test]
public async Task NumberInput_Increment_Multiple_Times()
{
await GoToFormsAsync();
var input = Input("input-age");
await input.FillAsync("10");
await Page.WaitForTimeoutAsync(100);
var incrementBtn = Page.Locator("[data-testid='input-age']").Locator("..").Locator("button[aria-label='Increment']");
await incrementBtn.ClickAsync();
await Page.WaitForTimeoutAsync(50);
await incrementBtn.ClickAsync();
await Page.WaitForTimeoutAsync(50);
await incrementBtn.ClickAsync();
await Page.WaitForTimeoutAsync(100);
await Expect(input).ToHaveValueAsync("13");
}
[Test]
public async Task NumberInput_Increment_From_Empty_Sets_Value_To_One()
{
await GoToFormsAsync();
var input = Input("input-age");
await input.FillAsync("");
await Page.WaitForTimeoutAsync(100);
var incrementBtn = Page.Locator("[data-testid='input-age']").Locator("..").Locator("button[aria-label='Increment']");
await incrementBtn.ClickAsync();
await Page.WaitForTimeoutAsync(100);
await Expect(input).ToHaveValueAsync("1");
}
[Test]
public async Task NumberInput_Decrement_From_Empty_Sets_Value_To_Negative_One_Or_Min()
{
await GoToFormsAsync();
var input = Input("input-age");
await input.FillAsync("");
await Page.WaitForTimeoutAsync(100);
var decrementBtn = Page.Locator("[data-testid='input-age']").Locator("..").Locator("button[aria-label='Decrement']");
await decrementBtn.ClickAsync();
await Page.WaitForTimeoutAsync(100);
// Age has Min=0, so decrement from 0 (default) should clamp to 0
await Expect(input).ToHaveValueAsync("0");
}
// ════════════════════════════════════════════════════════════════════════
// NumberInput: Min / Max clamping
// ════════════════════════════════════════════════════════════════════════
[Test]
public async Task NumberInput_Increment_Button_Disabled_At_Max()
{
await GoToFormsAsync();
var input = Input("input-age");
await input.FillAsync("150"); // Max is 150
await Page.WaitForTimeoutAsync(100);
var incrementBtn = Page.Locator("[data-testid='input-age']").Locator("..").Locator("button[aria-label='Increment']");
await Expect(incrementBtn).ToBeDisabledAsync();
}
[Test]
public async Task NumberInput_Decrement_Button_Disabled_At_Min()
{
await GoToFormsAsync();
var input = Input("input-age");
await input.FillAsync("0"); // Min is 0
await Page.WaitForTimeoutAsync(100);
var decrementBtn = Page.Locator("[data-testid='input-age']").Locator("..").Locator("button[aria-label='Decrement']");
await Expect(decrementBtn).ToBeDisabledAsync();
}
[Test]
public async Task NumberInput_Increment_At_Max_Does_Not_Exceed()
{
await GoToFormsAsync();
var input = Input("input-age");
await input.FillAsync("149");
await Page.WaitForTimeoutAsync(100);
var incrementBtn = Page.Locator("[data-testid='input-age']").Locator("..").Locator("button[aria-label='Increment']");
await incrementBtn.ClickAsync();
await Page.WaitForTimeoutAsync(100);
await Expect(input).ToHaveValueAsync("150");
await Expect(incrementBtn).ToBeDisabledAsync();
}
[Test]
public async Task NumberInput_Decrement_At_Min_Does_Not_Go_Below()
{
await GoToFormsAsync();
var input = Input("input-age");
await input.FillAsync("1");
await Page.WaitForTimeoutAsync(100);
var decrementBtn = Page.Locator("[data-testid='input-age']").Locator("..").Locator("button[aria-label='Decrement']");
await decrementBtn.ClickAsync();
await Page.WaitForTimeoutAsync(100);
await Expect(input).ToHaveValueAsync("0");
await Expect(decrementBtn).ToBeDisabledAsync();
}
// ════════════════════════════════════════════════════════════════════════
// Popover: explicit open/close mechanics
// ════════════════════════════════════════════════════════════════════════
[Test]
public async Task Popover_Opens_On_Trigger_Click()
{
await GoToFormsAsync();
// Date input trigger opens a calendar popover
await Trigger("trigger-birthdate").ClickAsync();
await Page.WaitForTimeoutAsync(300);
// The popover panel scoped to this trigger should be visible
var panel = PopoverPanelFor("trigger-birthdate");
await Expect(panel).ToBeVisibleAsync();
}
[Test]
public async Task Popover_Closes_On_Backdrop_Click()
{
await GoToFormsAsync();
// Open the time input popover
await Trigger("trigger-preferredtime").ClickAsync();
await Page.WaitForTimeoutAsync(300);
var panel = PopoverPanelFor("trigger-preferredtime");
await Expect(panel).ToBeVisibleAsync();
// Click the backdrop overlay to close
var backdrop = PopoverBackdropFor("trigger-preferredtime");
await backdrop.ClickAsync(new LocatorClickOptions { Force = true });
await Page.WaitForTimeoutAsync(300);
// Popover should no longer be visible
await Expect(panel).ToBeHiddenAsync();
}
[Test]
public async Task Popover_Stays_Open_On_Content_Click()
{
await GoToFormsAsync();
// Open the date input popover
await Trigger("trigger-birthdate").ClickAsync();
await Page.WaitForTimeoutAsync(300);
var panel = PopoverPanelFor("trigger-birthdate");
await Expect(panel).ToBeVisibleAsync();
// Click inside the popover content (e.g. the month header button) — should NOT close
var monthButton = panel.Locator("[data-calendar-month]");
await monthButton.ClickAsync();
await Page.WaitForTimeoutAsync(300);
// Popover should still be visible (month picker is now showing)
await Expect(panel).ToBeVisibleAsync();
}
[Test]
public async Task Popover_Toggle_Opens_Then_Closes_Via_Backdrop()
{
await GoToFormsAsync();
var trigger = Trigger("trigger-birthdate");
var panel = PopoverPanelFor("trigger-birthdate");
// Open
await trigger.ClickAsync();
await Page.WaitForTimeoutAsync(300);
await Expect(panel).ToBeVisibleAsync();
// Close via backdrop
var backdrop = PopoverBackdropFor("trigger-birthdate");
await backdrop.ClickAsync(new LocatorClickOptions { Force = true });
await Page.WaitForTimeoutAsync(300);
await Expect(panel).ToBeHiddenAsync();
}
// ════════════════════════════════════════════════════════════════════════
// Calendar: Previous / Next arrow buttons
// ════════════════════════════════════════════════════════════════════════
[Test]
public async Task Calendar_Next_Arrow_Advances_Month()
{
await GoToFormsAsync();
// Open calendar
await Trigger("trigger-birthdate").ClickAsync();
await Page.WaitForTimeoutAsync(300);
var panel = PopoverPanelFor("trigger-birthdate");
// Read the current displayed month
var monthLabel = panel.Locator("[data-calendar-month]");
var initialMonth = await monthLabel.InnerTextAsync();
// Click the next arrow
await panel.Locator("button[aria-label='Next month']").ClickAsync();
await Page.WaitForTimeoutAsync(200);
// Month should have changed
var newMonth = await monthLabel.InnerTextAsync();
Assert.That(newMonth, Is.Not.EqualTo(initialMonth), "Month label should change after clicking Next");
}
[Test]
public async Task Calendar_Previous_Arrow_Goes_Back_Month()
{
await GoToFormsAsync();
// Open calendar
await Trigger("trigger-birthdate").ClickAsync();
await Page.WaitForTimeoutAsync(300);
var panel = PopoverPanelFor("trigger-birthdate");
var monthLabel = panel.Locator("[data-calendar-month]");
var initialMonth = await monthLabel.InnerTextAsync();
// Click the previous arrow
await panel.Locator("button[aria-label='Previous month']").ClickAsync();
await Page.WaitForTimeoutAsync(200);
var newMonth = await monthLabel.InnerTextAsync();
Assert.That(newMonth, Is.Not.EqualTo(initialMonth), "Month label should change after clicking Previous");
}
[Test]
public async Task Calendar_Next_Arrow_Wraps_Year()
{
await GoToFormsAsync();
// Open calendar
await Trigger("trigger-birthdate").ClickAsync();
await Page.WaitForTimeoutAsync(300);
var panel = PopoverPanelFor("trigger-birthdate");
// Navigate to Dec of current year
var target = new DateOnly(DateTime.Today.Year, 12, 1);
await NavigateCalendarToDate(panel, target);
var yearLabel = panel.Locator("[data-calendar-year]");
var initialYear = await yearLabel.InnerTextAsync();
// Click next — should go to Jan of next year
await panel.Locator("button[aria-label='Next month']").ClickAsync();
await Page.WaitForTimeoutAsync(200);
var monthLabel = panel.Locator("[data-calendar-month]");
var newMonth = await monthLabel.InnerTextAsync();
var newYear = await yearLabel.InnerTextAsync();
Assert.That(newMonth.Trim(), Is.EqualTo("Jan"), "Should wrap to January");
Assert.That(int.Parse(newYear.Trim()), Is.EqualTo(int.Parse(initialYear.Trim()) + 1), "Year should increment");
}
[Test]
public async Task Calendar_Previous_Arrow_Wraps_Year()
{
await GoToFormsAsync();
// Open calendar
await Trigger("trigger-birthdate").ClickAsync();
await Page.WaitForTimeoutAsync(300);
var panel = PopoverPanelFor("trigger-birthdate");
var target = new DateOnly(DateTime.Today.Year, 1, 1);
await NavigateCalendarToDate(panel, target);
var yearLabel = panel.Locator("[data-calendar-year]");
var initialYear = await yearLabel.InnerTextAsync();
// Click previous — should go to Dec of previous year
await panel.Locator("button[aria-label='Previous month']").ClickAsync();
await Page.WaitForTimeoutAsync(200);
var monthLabel = panel.Locator("[data-calendar-month]");
var newMonth = await monthLabel.InnerTextAsync();
var newYear = await yearLabel.InnerTextAsync();
Assert.That(newMonth.Trim(), Is.EqualTo("Dec"), "Should wrap to December");
Assert.That(int.Parse(newYear.Trim()), Is.EqualTo(int.Parse(initialYear.Trim()) - 1), "Year should decrement");
}
[Test]
public async Task Calendar_Selecting_Day_Via_Arrow_Navigation()
{
await GoToFormsAsync();
// Open calendar
await Trigger("trigger-birthdate").ClickAsync();
await Page.WaitForTimeoutAsync(300);
var panel = PopoverPanelFor("trigger-birthdate");
// Navigate forward one month using arrow
await panel.Locator("button[aria-label='Next month']").ClickAsync();
await Page.WaitForTimeoutAsync(200);
// Read the new month/year
var monthText = (await panel.Locator("[data-calendar-month]").InnerTextAsync()).Trim();
var yearText = (await panel.Locator("[data-calendar-year]").InnerTextAsync()).Trim();
var month = DateTime.ParseExact(monthText, "MMM", null).Month;
var year = int.Parse(yearText);
// Click day 15
var dayGrid = panel.Locator(".grid.grid-cols-7").Last;
await dayGrid.Locator("button:not([disabled])").Filter(new LocatorFilterOptions { HasTextString = "15" }).First.ClickAsync();
await Page.WaitForTimeoutAsync(300);
// Verify the hidden input has the correct value
var expected = new DateOnly(year, month, 15).ToString("yyyy-MM-dd");
await Expect(Input("input-birthdate")).ToHaveValueAsync(expected);
}
// ════════════════════════════════════════════════════════════════════════
// DateInput: Trigger text updates after selection
// ════════════════════════════════════════════════════════════════════════
[Test]
public async Task DateInput_Trigger_Shows_Placeholder_Initially()
{
await GoToFormsAsync();
var triggerSpan = Trigger("trigger-birthdate").Locator("span");
var text = await triggerSpan.InnerTextAsync();
Assert.That(text.Trim(), Is.EqualTo("Select date"), "Should show placeholder before a date is selected");
}
[Test]
public async Task DateInput_Trigger_Shows_Formatted_Date_After_Selection()
{
await GoToFormsAsync();
// Open and select June 15, 2000
await Trigger("trigger-birthdate").ClickAsync();
await Page.WaitForTimeoutAsync(300);
var panel = PopoverPanelFor("trigger-birthdate");
await NavigateCalendarToDate(panel, new DateOnly(2000, 6, 15));
var dayGrid = panel.Locator(".grid.grid-cols-7").Last;
await dayGrid.Locator("button:not([disabled])").Filter(new LocatorFilterOptions { HasTextString = "15" }).First.ClickAsync();
await Page.WaitForTimeoutAsync(300);
// Trigger button text should now show the formatted date
var triggerSpan = Trigger("trigger-birthdate").Locator("span");
var text = (await triggerSpan.InnerTextAsync()).Trim();
Assert.That(text, Is.EqualTo("June 15, 2000"), "Trigger should display the formatted selected date");
}
[Test]
public async Task DateInput_Trigger_Text_Loses_Placeholder_Class_After_Selection()
{
await GoToFormsAsync();
var triggerSpan = Trigger("trigger-birthdate").Locator("span");
// Before selection — should have muted style
var classBefore = await triggerSpan.GetAttributeAsync("class");
Assert.That(classBefore, Does.Contain("text-muted-foreground"), "Placeholder text should have muted class");
// Select a date
await Trigger("trigger-birthdate").ClickAsync();
await Page.WaitForTimeoutAsync(300);
var panel = PopoverPanelFor("trigger-birthdate");
await NavigateCalendarToDate(panel, new DateOnly(2000, 6, 15));
var dayGrid = panel.Locator(".grid.grid-cols-7").Last;
await dayGrid.Locator("button:not([disabled])").Filter(new LocatorFilterOptions { HasTextString = "15" }).First.ClickAsync();
await Page.WaitForTimeoutAsync(300);
// After selection — should NOT have muted class
var classAfter = await triggerSpan.GetAttributeAsync("class");
Assert.That(classAfter, Does.Not.Contain("text-muted-foreground"), "Selected date text should not have muted class");
}
// ════════════════════════════════════════════════════════════════════════
// TimeInput: Trigger text updates after selection
// ════════════════════════════════════════════════════════════════════════
[Test]
public async Task TimeInput_Trigger_Shows_Placeholder_Initially()
{
await GoToFormsAsync();
var triggerSpan = Trigger("trigger-preferredtime").Locator("span");
var text = await triggerSpan.InnerTextAsync();
Assert.That(text.Trim(), Is.EqualTo("Select time"), "Should show placeholder before a time is selected");
}
[Test]
public async Task TimeInput_Trigger_Shows_Formatted_Time_After_Selection()
{
await GoToFormsAsync();
// Open time picker and select 2:30 PM
await Trigger("trigger-preferredtime").ClickAsync();
await Page.WaitForTimeoutAsync(300);
var panel = PopoverPanelFor("trigger-preferredtime");
await PickTimeInOpenPopover(panel, 14, 30);
// Close by clicking backdrop
var backdrop = PopoverBackdropFor("trigger-preferredtime");
await backdrop.ClickAsync(new LocatorClickOptions { Force = true });
await Page.WaitForTimeoutAsync(300);
var triggerSpan = Trigger("trigger-preferredtime").Locator("span");
var text = (await triggerSpan.InnerTextAsync()).Trim();
Assert.That(text, Is.EqualTo("02:30 PM"), "Trigger should display the formatted selected time");
}
[Test]
public async Task TimeInput_Trigger_Text_Loses_Placeholder_Class_After_Selection()
{
await GoToFormsAsync();
var triggerSpan = Trigger("trigger-preferredtime").Locator("span");
// Before selection
var classBefore = await triggerSpan.GetAttributeAsync("class");
Assert.That(classBefore, Does.Contain("text-muted-foreground"), "Placeholder should have muted class");
// Select a time
await Trigger("trigger-preferredtime").ClickAsync();
await Page.WaitForTimeoutAsync(300);
var panel = PopoverPanelFor("trigger-preferredtime");
await PickTimeInOpenPopover(panel, 14, 30);
var backdrop = PopoverBackdropFor("trigger-preferredtime");
await backdrop.ClickAsync(new LocatorClickOptions { Force = true });
await Page.WaitForTimeoutAsync(300);
// After selection
var classAfter = await triggerSpan.GetAttributeAsync("class");
Assert.That(classAfter, Does.Not.Contain("text-muted-foreground"), "Selected time text should not have muted class");
}
// ════════════════════════════════════════════════════════════════════════
// DateTimeInput: Trigger text updates after selection
// ════════════════════════════════════════════════════════════════════════
[Test]
public async Task DateTimeInput_Date_Trigger_Shows_Placeholder_Initially()
{
await GoToFormsAsync();
var triggerSpan = Trigger("trigger-appointment-date").Locator("span");
var text = await triggerSpan.InnerTextAsync();
Assert.That(text.Trim(), Is.EqualTo("Select date"), "Date trigger should show placeholder initially");
}
[Test]
public async Task DateTimeInput_Time_Trigger_Shows_Placeholder_Initially()
{
await GoToFormsAsync();
var triggerSpan = Trigger("trigger-appointment-time").Locator("span");
var text = await triggerSpan.InnerTextAsync();
Assert.That(text.Trim(), Is.EqualTo("Select time"), "Time trigger should show placeholder initially");
}
[Test]
public async Task DateTimeInput_Date_Trigger_Shows_Formatted_Date_After_Selection()
{
await GoToFormsAsync();
// Open and select Dec 25, 2025
await Trigger("trigger-appointment-date").ClickAsync();
await Page.WaitForTimeoutAsync(300);
var panel = PopoverPanelFor("trigger-appointment-date");
await NavigateCalendarToDate(panel, new DateOnly(2025, 12, 25));
var dayGrid = panel.Locator(".grid.grid-cols-7").Last;
await dayGrid.Locator("button:not([disabled])").Filter(new LocatorFilterOptions { HasTextString = "25" }).First.ClickAsync();
await Page.WaitForTimeoutAsync(300);
var triggerSpan = Trigger("trigger-appointment-date").Locator("span");
var text = (await triggerSpan.InnerTextAsync()).Trim();
Assert.That(text, Is.EqualTo("Dec 25, 2025"), "Date trigger should display the formatted selected date");
}
[Test]
public async Task DateTimeInput_Time_Trigger_Shows_Formatted_Time_After_Selection()
{
await GoToFormsAsync();
// Must select a date first so the component has a value
await Trigger("trigger-appointment-date").ClickAsync();
await Page.WaitForTimeoutAsync(300);
var datePanel = PopoverPanelFor("trigger-appointment-date");
await NavigateCalendarToDate(datePanel, new DateOnly(2025, 12, 25));
var dayGrid = datePanel.Locator(".grid.grid-cols-7").Last;
await dayGrid.Locator("button:not([disabled])").Filter(new LocatorFilterOptions { HasTextString = "25" }).First.ClickAsync();
await Page.WaitForTimeoutAsync(300);
// Now select time 10:00 AM
await Trigger("trigger-appointment-time").ClickAsync();
await Page.WaitForTimeoutAsync(300);
var timePanel = PopoverPanelFor("trigger-appointment-time");
await PickTimeInOpenPopover(timePanel, 10, 0);
var backdrop = PopoverBackdropFor("trigger-appointment-time");
await backdrop.ClickAsync(new LocatorClickOptions { Force = true });
await Page.WaitForTimeoutAsync(300);
var triggerSpan = Trigger("trigger-appointment-time").Locator("span");
var text = (await triggerSpan.InnerTextAsync()).Trim();
Assert.That(text, Is.EqualTo("10:00 AM"), "Time trigger should display the formatted selected time");
}
// ════════════════════════════════════════════════════════════════════════
// Calendar: day selection highlights correctly
// ════════════════════════════════════════════════════════════════════════
[Test]
public async Task Calendar_Selected_Day_Has_Primary_Styling()
{
await GoToFormsAsync();
// Open and select a date
await Trigger("trigger-birthdate").ClickAsync();
await Page.WaitForTimeoutAsync(300);
var panel = PopoverPanelFor("trigger-birthdate");
var dayGrid = panel.Locator(".grid.grid-cols-7").Last;
var day15 = dayGrid.Locator("button:not([disabled])").Filter(new LocatorFilterOptions { HasTextString = "15" }).First;
await day15.ClickAsync();
await Page.WaitForTimeoutAsync(300);
// Re-open the calendar to verify the selected day is highlighted
await Trigger("trigger-birthdate").ClickAsync();
await Page.WaitForTimeoutAsync(300);
panel = PopoverPanelFor("trigger-birthdate");
var selectedDay = panel.Locator(".grid.grid-cols-7").Last
.Locator("button:not([disabled])").Filter(new LocatorFilterOptions { HasTextString = "15" }).First;
var cls = await selectedDay.GetAttributeAsync("class");
Assert.That(cls, Does.Contain("bg-primary"), "Selected day should have primary background styling");
}
// ════════════════════════════════════════════════════════════════════════
// Popover: Date selection auto-closes popover
// ════════════════════════════════════════════════════════════════════════
[Test]
public async Task DateInput_Popover_Closes_After_Day_Selection()
{
await GoToFormsAsync();
await Trigger("trigger-birthdate").ClickAsync();
await Page.WaitForTimeoutAsync(300);
var panel = PopoverPanelFor("trigger-birthdate");
await Expect(panel).ToBeVisibleAsync();
// Select a day
var dayGrid = panel.Locator(".grid.grid-cols-7").Last;
await dayGrid.Locator("button:not([disabled])").Filter(new LocatorFilterOptions { HasTextString = "10" }).First.ClickAsync();
await Page.WaitForTimeoutAsync(400);
// Popover should auto-close after date selection
await Expect(panel).ToBeHiddenAsync();
}
}