test(playwright): Notification Report filter-combo + detail-modal edge cases (Wave 4)
This commit is contained in:
+121
@@ -166,4 +166,125 @@ public class NotificationActionTests
|
||||
await NotificationDataSeeder.DeleteByMarkerAsync(marker);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Combined-filter narrowing: two Parked rows share one <c>ListName</c> marker but carry
|
||||
/// distinct subjects. Applying the exact-match list filter (the marker) plus
|
||||
/// status=Parked plus the subject keyword for only ONE of the two rows must surface that
|
||||
/// row and exclude its sibling. This proves the filters compose (AND-narrowing) rather
|
||||
/// than widening — the marker isolates the pair from ambient cluster rows, and the
|
||||
/// subject keyword discriminates between them.
|
||||
/// </summary>
|
||||
[SkippableFact]
|
||||
public async Task FilterCombination_StatusPlusList_NarrowsToMatch()
|
||||
{
|
||||
Skip.IfNot(await NotificationDataSeeder.IsAvailableAsync(), DbUnavailableSkipReason);
|
||||
|
||||
var runId = Guid.NewGuid().ToString("N");
|
||||
var marker = $"zztest-notif-wave4-{runId}";
|
||||
|
||||
// Two Parked rows under the SAME ListName marker, distinct subjects.
|
||||
await NotificationDataSeeder.InsertParkedNotificationAsync(
|
||||
Guid.NewGuid(), marker, "wave4-alpha", "site-a");
|
||||
await NotificationDataSeeder.InsertParkedNotificationAsync(
|
||||
Guid.NewGuid(), marker, "wave4-beta", "site-a");
|
||||
|
||||
try
|
||||
{
|
||||
var page = await _fixture.NewAuthenticatedPageAsync();
|
||||
await page.GotoAsync($"{PlaywrightFixture.BaseUrl}{NotificationReportUrl}");
|
||||
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
|
||||
|
||||
await Assertions.Expect(page.Locator("h4:has-text('Notification Report')")).ToBeVisibleAsync();
|
||||
|
||||
// #no-list is an exact-match text input bound @bind (commit-on-change), so
|
||||
// FillAsync (which only fires `input`) must be followed by an explicit
|
||||
// `change` dispatch to commit the bound value before the Query roundtrip —
|
||||
// same rationale as SetSubjectKeywordAsync for the subject box.
|
||||
await page.Locator("#no-list").FillAsync(marker);
|
||||
await page.Locator("#no-list").DispatchEventAsync("change");
|
||||
|
||||
// Status select commits on its own SelectOptionAsync change event.
|
||||
await page.Locator("#no-status").SelectOptionAsync("Parked");
|
||||
|
||||
// Subject keyword for ONLY the alpha row.
|
||||
await SetSubjectKeywordAsync(page, "wave4-alpha");
|
||||
|
||||
await page.Locator("button.btn-primary:has-text('Query')").ClickAsync();
|
||||
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
|
||||
|
||||
// The combined filters narrow to alpha: alpha visible, beta absent.
|
||||
var alphaRow = page.Locator("tbody tr", new() { HasText = "wave4-alpha" });
|
||||
await Assertions.Expect(alphaRow).ToBeVisibleAsync(new() { Timeout = 15_000 });
|
||||
await Assertions.Expect(
|
||||
page.Locator("tbody tr").Filter(new() { HasText = "wave4-beta" }))
|
||||
.ToHaveCountAsync(0);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await NotificationDataSeeder.DeleteByMarkerAsync(marker);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The row detail modal opens on a DOUBLE-CLICK of a non-Actions row cell (there is no
|
||||
/// dedicated open button) and closes via the header X and the footer Close button. Both
|
||||
/// close affordances are wired to the same <c>CloseDetail</c> handler; this exercises
|
||||
/// each path independently (open → X-close → reopen → footer-close).
|
||||
/// </summary>
|
||||
[SkippableFact]
|
||||
public async Task DetailModal_OpenOnDblClick_CloseViaXAndFooter()
|
||||
{
|
||||
Skip.IfNot(await NotificationDataSeeder.IsAvailableAsync(), DbUnavailableSkipReason);
|
||||
|
||||
var runId = Guid.NewGuid().ToString("N");
|
||||
var marker = $"zztest-notif-wave4-{runId}";
|
||||
|
||||
await NotificationDataSeeder.InsertParkedNotificationAsync(
|
||||
Guid.NewGuid(), marker, "wave4-detail", "site-a");
|
||||
|
||||
try
|
||||
{
|
||||
var page = await _fixture.NewAuthenticatedPageAsync();
|
||||
await page.GotoAsync($"{PlaywrightFixture.BaseUrl}{NotificationReportUrl}");
|
||||
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
|
||||
|
||||
await Assertions.Expect(page.Locator("h4:has-text('Notification Report')")).ToBeVisibleAsync();
|
||||
|
||||
// Narrow to the seeded row by its exact ListName marker (+ Parked status).
|
||||
await page.Locator("#no-list").FillAsync(marker);
|
||||
await page.Locator("#no-list").DispatchEventAsync("change");
|
||||
await page.Locator("#no-status").SelectOptionAsync("Parked");
|
||||
await page.Locator("button.btn-primary:has-text('Query')").ClickAsync();
|
||||
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
|
||||
|
||||
var row = page.Locator("tbody tr").Filter(new() { HasText = "wave4-detail" });
|
||||
await Assertions.Expect(row).ToBeVisibleAsync(new() { Timeout = 15_000 });
|
||||
|
||||
// Open the modal: double-click the SUBJECT cell (a non-Actions cell). The
|
||||
// Actions cell carries @ondblclick:stopPropagation, so double-clicking it would
|
||||
// not open the modal — the subject cell is the correct target.
|
||||
await row.Locator("td", new() { HasText = "wave4-detail" }).DblClickAsync();
|
||||
await Assertions.Expect(page.Locator(".modal.show.d-block")).ToBeVisibleAsync();
|
||||
await Assertions.Expect(page.Locator(".modal-title")).ToContainTextAsync("Notification Detail");
|
||||
|
||||
// Close via the header X.
|
||||
await page.ClickAsync("button.btn-close[aria-label='Close']");
|
||||
await Assertions.Expect(page.Locator(".modal.show.d-block")).ToHaveCountAsync(0);
|
||||
|
||||
// Re-open (double-click the subject cell again).
|
||||
await row.Locator("td", new() { HasText = "wave4-detail" }).DblClickAsync();
|
||||
await Assertions.Expect(page.Locator(".modal.show.d-block")).ToBeVisibleAsync();
|
||||
|
||||
// Close via the footer Close button.
|
||||
await page.ClickAsync(".modal-footer button.btn-outline-secondary:has-text('Close')");
|
||||
await Assertions.Expect(page.Locator(".modal.show.d-block")).ToHaveCountAsync(0);
|
||||
|
||||
// Escape-to-close is NOT wired on the Notification detail modal (no keydown handler); covering the X and footer-Close paths that ARE wired (backdrop also closes but is omitted for brevity).
|
||||
}
|
||||
finally
|
||||
{
|
||||
await NotificationDataSeeder.DeleteByMarkerAsync(marker);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user