Basics Done
This commit is contained in:
@@ -0,0 +1,605 @@
|
||||
using Microsoft.Playwright;
|
||||
|
||||
namespace Enciphered.Blazor.UIComponents.Tests;
|
||||
|
||||
[TestFixture]
|
||||
public class FormsTests : PlaywrightTestBase
|
||||
{
|
||||
// ── Helpers ──────────────────────────────────────────────────────────────
|
||||
|
||||
private async Task GoToFormsAsync()
|
||||
{
|
||||
await Page.GotoAsync($"{BaseUrl}/forms", new PageGotoOptions { WaitUntil = WaitUntilState.NetworkIdle });
|
||||
// Wait for Blazor interactive mode to be ready
|
||||
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}']");
|
||||
private ILocator Btn(string testId) => Page.Locator($"[data-testid='{testId}']");
|
||||
|
||||
/// <summary>
|
||||
/// Select a date via the calendar popover.
|
||||
/// Opens the trigger, uses month/year pickers to navigate, then clicks the day.
|
||||
/// </summary>
|
||||
private async Task SelectDateAsync(string triggerId, DateOnly target)
|
||||
{
|
||||
// Open the popover
|
||||
await Trigger(triggerId).ClickAsync();
|
||||
await Page.WaitForTimeoutAsync(200);
|
||||
|
||||
await NavigateCalendarToDate(target);
|
||||
|
||||
// Click the target day (only enabled buttons in the calendar day grid)
|
||||
// The day grid is the last grid-cols-7 div; find the button with matching day text
|
||||
var dayGrid = Page.Locator(".grid.grid-cols-7").Last;
|
||||
var dayButton = dayGrid.Locator($"button:not([disabled])").Filter(new LocatorFilterOptions { HasTextString = target.Day.ToString() }).First;
|
||||
await dayButton.ClickAsync();
|
||||
await Page.WaitForTimeoutAsync(200);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Navigate the open calendar to a specific month/year using the month and year pickers.
|
||||
/// </summary>
|
||||
private async Task NavigateCalendarToDate(DateOnly target)
|
||||
{
|
||||
// Click year header to open year picker, then select the year
|
||||
var yearButton = Page.Locator("[data-calendar-year]");
|
||||
await yearButton.ClickAsync();
|
||||
await Page.WaitForTimeoutAsync(100);
|
||||
|
||||
// The year picker is a scrollable grid; find and click the target year
|
||||
var yearGrid = Page.Locator(".grid.grid-cols-4");
|
||||
var targetYearBtn = yearGrid.Locator($"button:has-text('{target.Year}')");
|
||||
|
||||
// If the year isn't visible, use prev/next to shift the year range (±20 per click)
|
||||
var attempts = 0;
|
||||
while (await targetYearBtn.CountAsync() == 0 && attempts < 10)
|
||||
{
|
||||
// Read the first year button text to determine which direction to go
|
||||
var firstYearText = await yearGrid.Locator("button").First.InnerTextAsync();
|
||||
var firstYear = int.Parse(firstYearText.Trim());
|
||||
|
||||
if (target.Year < firstYear)
|
||||
await Page.Locator("button[aria-label='Previous month']").ClickAsync();
|
||||
else
|
||||
await Page.Locator("button[aria-label='Next month']").ClickAsync();
|
||||
await Page.WaitForTimeoutAsync(50);
|
||||
yearGrid = Page.Locator(".grid.grid-cols-4");
|
||||
targetYearBtn = yearGrid.Locator($"button:has-text('{target.Year}')");
|
||||
attempts++;
|
||||
}
|
||||
|
||||
await targetYearBtn.First.ClickAsync();
|
||||
await Page.WaitForTimeoutAsync(100);
|
||||
|
||||
// Now click month header to open month picker, then select the month
|
||||
var monthButton = Page.Locator("[data-calendar-month]");
|
||||
await monthButton.ClickAsync();
|
||||
await Page.WaitForTimeoutAsync(100);
|
||||
|
||||
var monthGrid = Page.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(100);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Select a time via the time picker popover.
|
||||
/// Opens the trigger, clicks the hour, minute, and AM/PM.
|
||||
/// </summary>
|
||||
private async Task SelectTimeAsync(string triggerId, int hour, int minute)
|
||||
{
|
||||
// Open the popover
|
||||
await Trigger(triggerId).ClickAsync();
|
||||
await Page.WaitForTimeoutAsync(200);
|
||||
|
||||
await PickTimeInOpenPopover(hour, minute);
|
||||
|
||||
// Close popover by clicking the backdrop overlay
|
||||
await Page.Locator(".fixed.inset-0.z-40").ClickAsync(new LocatorClickOptions { Force = true });
|
||||
await Page.WaitForTimeoutAsync(100);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pick hour, minute, and AM/PM in an already-open time picker.
|
||||
/// Scopes all locators to the visible popover content to avoid backdrop interception.
|
||||
/// </summary>
|
||||
private async Task PickTimeInOpenPopover(int hour, int minute)
|
||||
{
|
||||
// The popover content sits in a z-50 absolutely positioned container
|
||||
var popoverContent = Page.Locator(".absolute.z-50");
|
||||
|
||||
// Convert to 12-hour format
|
||||
var isPm = hour >= 12;
|
||||
var hour12 = hour % 12;
|
||||
if (hour12 == 0) hour12 = 12;
|
||||
|
||||
// Click the hour in the first scrollable column (within the popover)
|
||||
var hourText = hour12.ToString("D2");
|
||||
var hourColumn = popoverContent.Locator(".scrollbar-thin").First;
|
||||
await hourColumn.Locator($"button:has-text('{hourText}')").First.ClickAsync();
|
||||
await Page.WaitForTimeoutAsync(50);
|
||||
|
||||
// Click the minute in the second scrollable column (within the popover)
|
||||
var minuteText = minute.ToString("D2");
|
||||
var minuteColumn = popoverContent.Locator(".scrollbar-thin").Nth(1);
|
||||
await minuteColumn.Locator($"button:has-text('{minuteText}')").First.ClickAsync();
|
||||
await Page.WaitForTimeoutAsync(50);
|
||||
|
||||
// Click AM/PM (within the popover)
|
||||
var periodText = isPm ? "PM" : "AM";
|
||||
await popoverContent.Locator($"button:has-text('{periodText}')").First.ClickAsync();
|
||||
await Page.WaitForTimeoutAsync(50);
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
// Rendering
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
|
||||
[Test]
|
||||
public async Task FormsPage_Loads_Successfully()
|
||||
{
|
||||
var response = await Page.GotoAsync($"{BaseUrl}/forms");
|
||||
Assert.That(response!.Status, Is.EqualTo(200));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task FormsPage_Has_Title()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
var heading = Page.Locator("[data-sidebar-inset] h1.text-3xl");
|
||||
await Expect(heading).ToHaveTextAsync("Forms Demo");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task All_Inputs_Are_Rendered()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
|
||||
await Expect(Input("input-name")).ToBeVisibleAsync();
|
||||
await Expect(Input("input-email")).ToBeVisibleAsync();
|
||||
await Expect(Input("input-password")).ToBeVisibleAsync();
|
||||
await Expect(Input("input-age")).ToBeVisibleAsync();
|
||||
// Date/Time/DateTime use popover triggers instead of visible native inputs
|
||||
await Expect(Trigger("trigger-birthdate")).ToBeVisibleAsync();
|
||||
await Expect(Trigger("trigger-preferredtime")).ToBeVisibleAsync();
|
||||
await Expect(Trigger("trigger-appointment-date")).ToBeVisibleAsync();
|
||||
await Expect(Trigger("trigger-appointment-time")).ToBeVisibleAsync();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task All_Buttons_Are_Rendered()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
|
||||
await Expect(Btn("btn-submit")).ToBeVisibleAsync();
|
||||
await Expect(Btn("btn-reset")).ToBeVisibleAsync();
|
||||
await Expect(Btn("btn-disabled")).ToBeVisibleAsync();
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
// Input types
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
|
||||
[Test]
|
||||
public async Task TextInput_Has_Correct_Type()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
await Expect(Input("input-name")).ToHaveAttributeAsync("type", "text");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task EmailInput_Has_Correct_Type()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
await Expect(Input("input-email")).ToHaveAttributeAsync("type", "email");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task PasswordInput_Has_Correct_Type()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
await Expect(Input("input-password")).ToHaveAttributeAsync("type", "password");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task NumberInput_Has_Correct_Type()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
await Expect(Input("input-age")).ToHaveAttributeAsync("type", "number");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task DateInput_Has_Correct_Type()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
await Expect(Input("input-birthdate")).ToHaveAttributeAsync("type", "date");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task TimeInput_Has_Correct_Type()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
await Expect(Input("input-time")).ToHaveAttributeAsync("type", "time");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task DateTimeInput_Has_Correct_Type()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
await Expect(Input("input-appointment")).ToHaveAttributeAsync("type", "datetime-local");
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
// Placeholders
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
|
||||
[Test]
|
||||
public async Task TextInput_Shows_Placeholder()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
await Expect(Input("input-name")).ToHaveAttributeAsync("placeholder", "Jane Doe");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task EmailInput_Shows_Placeholder()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
await Expect(Input("input-email")).ToHaveAttributeAsync("placeholder", "jane@example.com");
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
// Labels
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
|
||||
[Test]
|
||||
public async Task FormFields_Have_Labels()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
|
||||
var labels = Page.Locator("label");
|
||||
var count = await labels.CountAsync();
|
||||
Assert.That(count, Is.EqualTo(7), "Expected 7 labels (one per form field)");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Label_For_Attribute_Matches_Input_Id()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
|
||||
var label = Page.Locator("label[for='name']");
|
||||
await Expect(label).ToHaveTextAsync("Full Name");
|
||||
|
||||
var input = Input("input-name");
|
||||
await Expect(input).ToHaveAttributeAsync("id", "name");
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
// Two-way binding
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
|
||||
[Test]
|
||||
public async Task TextInput_Binds_Value()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
|
||||
var input = Input("input-name");
|
||||
await input.FillAsync("Alice");
|
||||
await Expect(input).ToHaveValueAsync("Alice");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task NumberInput_Binds_Value()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
|
||||
var input = Input("input-age");
|
||||
await input.FillAsync("30");
|
||||
await Expect(input).ToHaveValueAsync("30");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task DateInput_Binds_Value()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
|
||||
// Use the calendar popover to select June 15, 2000
|
||||
await SelectDateAsync("trigger-birthdate", new DateOnly(2000, 6, 15));
|
||||
|
||||
// The hidden input should reflect the selected date
|
||||
await Expect(Input("input-birthdate")).ToHaveValueAsync("2000-06-15");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task TimeInput_Binds_Value()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
|
||||
// Use the time picker popover to select 14:30 (2:30 PM)
|
||||
await SelectTimeAsync("trigger-preferredtime", 14, 30);
|
||||
|
||||
// The hidden input should reflect the selected time
|
||||
await Expect(Input("input-time")).ToHaveValueAsync("14:30");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task DateTimeInput_Binds_Value()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
|
||||
// Pick the date part via the date trigger
|
||||
await SelectDateAsync("trigger-appointment-date", new DateOnly(2025, 12, 25));
|
||||
|
||||
// Pick the time part via the time trigger
|
||||
await SelectTimeAsync("trigger-appointment-time", 10, 0);
|
||||
|
||||
// The hidden input should have the combined datetime value
|
||||
var value = await Input("input-appointment").InputValueAsync();
|
||||
Assert.That(value, Does.StartWith("2025-12-25T10:00"));
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
// Validation — empty submit shows errors
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
|
||||
[Test]
|
||||
public async Task Empty_Submit_Shows_Validation_Errors()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
|
||||
await Btn("btn-submit").ClickAsync();
|
||||
|
||||
// Wait for at least one error message to appear
|
||||
await Page.WaitForSelectorAsync("p.text-destructive", new PageWaitForSelectorOptions { Timeout = 5_000 });
|
||||
|
||||
var errors = Page.Locator("p.text-destructive");
|
||||
var count = await errors.CountAsync();
|
||||
Assert.That(count, Is.GreaterThanOrEqualTo(7), "Expected at least 7 validation errors (one per required field)");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Validation_Error_Shows_Name_Required()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
|
||||
await Btn("btn-submit").ClickAsync();
|
||||
await Page.WaitForSelectorAsync("p.text-destructive", new PageWaitForSelectorOptions { Timeout = 5_000 });
|
||||
|
||||
var nameError = Page.Locator("p.text-destructive", new PageLocatorOptions { HasTextString = "Name is required" });
|
||||
await Expect(nameError).ToBeVisibleAsync();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Validation_Error_Shows_Email_Required()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
|
||||
await Btn("btn-submit").ClickAsync();
|
||||
await Page.WaitForSelectorAsync("p.text-destructive", new PageWaitForSelectorOptions { Timeout = 5_000 });
|
||||
|
||||
var emailError = Page.Locator("p.text-destructive", new PageLocatorOptions { HasTextString = "Email is required" });
|
||||
await Expect(emailError).ToBeVisibleAsync();
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
// Validation — specific error messages
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
|
||||
[Test]
|
||||
public async Task Short_Name_Shows_Length_Error()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
|
||||
var input = Input("input-name");
|
||||
await input.FillAsync("A");
|
||||
await Btn("btn-submit").ClickAsync();
|
||||
await Page.WaitForSelectorAsync("p.text-destructive", new PageWaitForSelectorOptions { Timeout = 5_000 });
|
||||
|
||||
var error = Page.Locator("p.text-destructive", new PageLocatorOptions { HasTextString = "2–100 characters" });
|
||||
await Expect(error).ToBeVisibleAsync();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Invalid_Email_Shows_Error()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
|
||||
var input = Input("input-email");
|
||||
await input.FillAsync("not-an-email");
|
||||
await Btn("btn-submit").ClickAsync();
|
||||
await Page.WaitForSelectorAsync("p.text-destructive", new PageWaitForSelectorOptions { Timeout = 5_000 });
|
||||
|
||||
var error = Page.Locator("p.text-destructive", new PageLocatorOptions { HasTextString = "Invalid email" });
|
||||
await Expect(error).ToBeVisibleAsync();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Short_Password_Shows_Error()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
|
||||
var input = Input("input-password");
|
||||
await input.FillAsync("123");
|
||||
await Btn("btn-submit").ClickAsync();
|
||||
await Page.WaitForSelectorAsync("p.text-destructive", new PageWaitForSelectorOptions { Timeout = 5_000 });
|
||||
|
||||
var error = Page.Locator("p.text-destructive", new PageLocatorOptions { HasTextString = "8–64 characters" });
|
||||
await Expect(error).ToBeVisibleAsync();
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
// Valid submission
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
|
||||
[Test]
|
||||
public async Task Valid_Form_Shows_Success_Message()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
|
||||
await Input("input-name").FillAsync("Jane Doe");
|
||||
await Input("input-email").FillAsync("jane@example.com");
|
||||
await Input("input-password").FillAsync("securepassword123");
|
||||
await Input("input-age").FillAsync("30");
|
||||
|
||||
// Use popover pickers for date/time fields
|
||||
await SelectDateAsync("trigger-birthdate", new DateOnly(1995, 3, 15));
|
||||
await SelectTimeAsync("trigger-preferredtime", 9, 30);
|
||||
|
||||
// DateTime: pick date and time via separate triggers
|
||||
await SelectDateAsync("trigger-appointment-date", new DateOnly(2025, 12, 25));
|
||||
await SelectTimeAsync("trigger-appointment-time", 10, 0);
|
||||
|
||||
await Btn("btn-submit").ClickAsync();
|
||||
|
||||
var success = Page.Locator("[data-testid='success-message']");
|
||||
await Expect(success).ToBeVisibleAsync(new LocatorAssertionsToBeVisibleOptions { Timeout = 5_000 });
|
||||
await Expect(success).ToContainTextAsync("Form submitted successfully");
|
||||
await Expect(success).ToContainTextAsync("Jane Doe");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task No_Success_Message_Before_Submit()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
|
||||
var success = Page.Locator("[data-testid='success-message']");
|
||||
await Expect(success).ToBeHiddenAsync();
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
// Button variants
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
|
||||
[Test]
|
||||
public async Task Submit_Button_Has_Default_Variant_Classes()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
|
||||
var btn = Btn("btn-submit");
|
||||
var cls = await btn.GetAttributeAsync("class");
|
||||
Assert.That(cls, Does.Contain("bg-primary"), "Submit should use default variant");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Reset_Button_Has_Outline_Variant_Classes()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
|
||||
var btn = Btn("btn-reset");
|
||||
var cls = await btn.GetAttributeAsync("class");
|
||||
Assert.That(cls, Does.Contain("border"), "Reset should use outline variant");
|
||||
Assert.That(cls, Does.Contain("bg-background"), "Reset should use outline variant");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Disabled_Button_Is_Actually_Disabled()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
|
||||
var btn = Btn("btn-disabled");
|
||||
await Expect(btn).ToBeDisabledAsync();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Disabled_Button_Has_Destructive_Variant()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
|
||||
var btn = Btn("btn-disabled");
|
||||
var cls = await btn.GetAttributeAsync("class");
|
||||
Assert.That(cls, Does.Contain("bg-destructive"), "Disabled button should have destructive variant");
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
// Reset
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
|
||||
[Test]
|
||||
public async Task Reset_Button_Clears_Form()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
|
||||
// Fill some fields
|
||||
await Input("input-name").FillAsync("Alice");
|
||||
await Input("input-email").FillAsync("alice@test.com");
|
||||
|
||||
// Reset
|
||||
await Btn("btn-reset").ClickAsync();
|
||||
|
||||
// Fields should be empty
|
||||
await Expect(Input("input-name")).ToHaveValueAsync("");
|
||||
await Expect(Input("input-email")).ToHaveValueAsync("");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Reset_Button_Clears_Success_Message()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
|
||||
// Submit valid form
|
||||
await Input("input-name").FillAsync("Jane Doe");
|
||||
await Input("input-email").FillAsync("jane@example.com");
|
||||
await Input("input-password").FillAsync("securepassword123");
|
||||
await Input("input-age").FillAsync("30");
|
||||
|
||||
await SelectDateAsync("trigger-birthdate", new DateOnly(1995, 3, 15));
|
||||
await SelectTimeAsync("trigger-preferredtime", 9, 30);
|
||||
|
||||
// DateTime picker — date and time via separate triggers
|
||||
await SelectDateAsync("trigger-appointment-date", new DateOnly(2025, 12, 25));
|
||||
await SelectTimeAsync("trigger-appointment-time", 10, 0);
|
||||
|
||||
await Btn("btn-submit").ClickAsync();
|
||||
|
||||
var success = Page.Locator("[data-testid='success-message']");
|
||||
await Expect(success).ToBeVisibleAsync(new LocatorAssertionsToBeVisibleOptions { Timeout = 5_000 });
|
||||
|
||||
// Reset
|
||||
await Btn("btn-reset").ClickAsync();
|
||||
|
||||
await Expect(success).ToBeHiddenAsync();
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
// Input styling (base CSS classes present)
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
|
||||
[Test]
|
||||
public async Task Inputs_Have_Base_Styling_Classes()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
|
||||
var input = Input("input-name");
|
||||
var cls = await input.GetAttributeAsync("class");
|
||||
Assert.That(cls, Does.Contain("rounded-md"), "Input should have rounded-md class");
|
||||
Assert.That(cls, Does.Contain("border"), "Input should have border class");
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
// Navigation to forms page
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
|
||||
[Test]
|
||||
public async Task Sidebar_Has_Forms_Link()
|
||||
{
|
||||
await Page.GotoAsync(BaseUrl, new PageGotoOptions { WaitUntil = WaitUntilState.NetworkIdle });
|
||||
await Page.WaitForSelectorAsync("[data-sidebar-wrapper][data-state]", new PageWaitForSelectorOptions { Timeout = 10_000 });
|
||||
|
||||
var formsLink = Page.Locator("a[href='/forms']");
|
||||
await Expect(formsLink).ToBeVisibleAsync();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Navigate_To_Forms_Via_Sidebar()
|
||||
{
|
||||
await Page.GotoAsync(BaseUrl, new PageGotoOptions { WaitUntil = WaitUntilState.NetworkIdle });
|
||||
await Page.WaitForSelectorAsync("[data-sidebar-wrapper][data-state]", new PageWaitForSelectorOptions { Timeout = 10_000 });
|
||||
|
||||
await Page.Locator("a[href='/forms']").ClickAsync();
|
||||
await Page.WaitForURLAsync($"{BaseUrl}/forms");
|
||||
|
||||
var heading = Page.Locator("[data-sidebar-inset] h1.text-3xl");
|
||||
await Expect(heading).ToHaveTextAsync("Forms Demo");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user