test(e2e): cover unified DashboardContainer (collapse, border, tabs, drag)#2205
test(e2e): cover unified DashboardContainer (collapse, border, tabs, drag)#2205alex-fedotyev wants to merge 4 commits into
Conversation
|
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
🔵 Tier 2 — Low RiskSmall, isolated change with no API route or data model modifications. Why this tier:
Review process: AI review + quick human skim (target: 5–15 min). Reviewer validates AI assessment and checks for domain-specific concerns. Stats
|
PR Review✅ No critical issues found. Minor observations (non-blocking):
The PR clearly addresses the follow-up requested on #2015, defers the |
E2E Test Results✅ All tests passed • 171 passed • 3 skipped • 1225s
Tests ran across 4 shards in parallel. |
Compound Engineering ReviewPR #2205 — Scope: 1 production file (testid + P1 — Should fix before merge
P2 — Should fix
P3 — Nice to have
Files reviewed: |
…races Compound-review feedback on #2205: - Replace `waitForLoadState('networkidle')` (which hangs on dashboards that keep polling tile queries) with a backend-PATCH waitForResponse, registered before the mutation so a fast handler doesn't return before the listener is attached. - Tag the round-trip and drag-persists tests `@full-stack` so the backend-bearing CI lane picks them up. - Replace inline `url().match(/\/dashboards\/.../)` with `dashboardPage.getCurrentDashboardId()` (existing helper) so a missing id throws instead of silently passing `undefined` to `goto()`. - `getActiveTabsParam` now validates `JSON.parse` output is a non-array object and only accepts string-typed values, instead of casting `any` to `Record<string, string>`. - `openGroupMenu` waits for `pointer-events: none` to clear before clicking the overflow trigger; auto-wait checks visibility but not pointer-events. - `dragGroupTo` recomputes the target `boundingBox()` after the drag activates (otherwise @dnd-kit's 250ms sortable-item transform leaves the box stale) and shrinks the activation nudge to 10px so the cursor stays inside the handle. - Multi-param state reads (`?collapsed` + `?expanded`) are wrapped in a single `expect.poll` tuple so the slow side can't be missed by a one-shot read after the fast side resolved. - New page-object helpers (`getGroupChevron`, `getGroupBorderedToggle`, `getGroupTabs`, `getGroupBorderedAttr`, `waitForLoaded`, `waitForDashboardPatch`) keep testid prefixes out of specs. - `data-bordered="true|false"` attribute on the container shell so the bordered toggle test reads an attr instead of inspecting inline `style.border`. - `getGroupOrder` filters empty entries to surface DOM regressions. - `addGroupMenuItem` switched to `getByTestId(...)` for consistency with sibling locators. - Reverted unrelated em-dash → comma comment edits in `DashboardContainer.tsx` to keep the PR scope clean (em-dash sweep belongs in its own PR).
|
P1 flake risks and P2 cleanups addressed in 7796c3a:
|
Deep ReviewScope: PR #2205 — E2E coverage for the unified 🔴 P0/P1 -- must fix
🟡 P2 -- recommended
🔵 P3 nitpicks (4)
Pre-existing
Agent-Native GapsBoth gaps below are pre-existing in
Learnings & Past Solutions
Coverage
VerdictReady with fixes. The two P0s ( Reviewers (8): correctness, testing, maintainability, project-standards, ce-agent-native-reviewer, ce-learnings-researcher, kieran-typescript, julik-frontend-races. Testing gaps:
|
…drag) PR #2015 unified the legacy section/group concept into a single DashboardContainer with collapsible / bordered / tabs / drag-to-reorder plus URL-state persistence. Unit tests landed with #2015; this PR adds the missing E2E coverage that Drew called out in his top-level review. What this covers: - Default collapsible chevron + bordered style on a new group. - Toggling Hide Border / Show Border via the overflow menu. - Adding a tab so the tab bar appears, switching tabs updates ?activeTabs and aria-selected. - ?collapsed and ?expanded URL params survive reload. - ?activeTabs URL param survives reload and restores the active tab. - Save-and-reload round-trip preserves containers, bordered=false, and the second tab on group B. - Drag-to-reorder: drag-onto-self is a no-op (DnD guard); dragging A onto C in [A, B, C] yields [B, C, A] (arrayMove semantics) and the new order persists across navigation. Side fixes: - DashboardPage.ts page-object selector updated from the stale add-new-section-menu-item to add-new-group-menu-item (renamed by PR #2015 in DBDashboardPage.tsx); helper renamed addSection -> addGroup. - DashboardContainer.tsx adds data-testid="group-add-tab-\${id}" on the existing Add Tab menu item so the spec does not have to rely on Mantine menu text. No API or schema change. Test-only plus one data-testid line. Tracker: Nerve task 2026-05-04-hyperdxiohyperdx-e2e-tests-for-unifie Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The three failing dashboard-container tests in CI shard 1 came down to two distinct races: 1. Tests #3 (line 112) and #5 (line 194) read getActiveTabsParam()[id] synchronously after Add Tab and after tab switches. nuqs flushes URL state asynchronously, so the read fires before the param is written. Wrap each sync read in expect.poll, mirroring the fix in PR #2209. 2. Test #6 (line 231) paired toggleGroupBordered(idA) with addTabToGroup(idB) back-to-back. Both setDashboard calls produce() from the same pre-mutation snapshot of the React Query cache, so the second PATCH overwrites the first; the toggle is silently dropped. The save-and-reload assertion then sees the wrong state (or, when goto fires before the PATCH lands, no state at all). Narrow the round-trip to a single mutation per step (addTabToGroup on group B), wait for networkidle before navigating away, capture the dashboard id from the URL while we are still on the page, and use expect.poll for the post-reload getGroupOrder assertion. The bordered toggle stays covered by the dedicated in-page test at line 78. The underlying back-pressure race is tracked separately in #2216.
The dashboard activeTabs URL state goes through parseAsJsonEncoded
(packages/app/src/utils/queryParsers.ts), which intentionally
double-encodes the JSON to survive Microsoft Teams' '+' -> '%2B'
re-encoding. The serializer writes encodeURIComponent(JSON.stringify(...))
and nuqs's URL machinery then encodes the '%XX' sequences a second
time. URLSearchParams.get(...) decodes one level, so the helper has
to decode the second level itself before JSON.parse.
The previous helper read the param, called JSON.parse on a still-
URL-encoded string ('%7B%22..."), threw, and returned {} silently.
Tests #112 and #197 polled getActiveTabsParam()[id] for 5s, got
undefined every time, and timed out on toBeTruthy.
Fix: decodeURIComponent before JSON.parse. Keep a fallback to plain
JSON.parse for compatibility with the old single-encoded format,
mirroring the parser's own backward-compat path.
The implementation (handleTabChange / setUrlActiveTabs in DBDashboardPage)
was correct all along; only the test-side reader needed updating.
…races Compound-review feedback on #2205: - Replace `waitForLoadState('networkidle')` (which hangs on dashboards that keep polling tile queries) with a backend-PATCH waitForResponse, registered before the mutation so a fast handler doesn't return before the listener is attached. - Tag the round-trip and drag-persists tests `@full-stack` so the backend-bearing CI lane picks them up. - Replace inline `url().match(/\/dashboards\/.../)` with `dashboardPage.getCurrentDashboardId()` (existing helper) so a missing id throws instead of silently passing `undefined` to `goto()`. - `getActiveTabsParam` now validates `JSON.parse` output is a non-array object and only accepts string-typed values, instead of casting `any` to `Record<string, string>`. - `openGroupMenu` waits for `pointer-events: none` to clear before clicking the overflow trigger; auto-wait checks visibility but not pointer-events. - `dragGroupTo` recomputes the target `boundingBox()` after the drag activates (otherwise @dnd-kit's 250ms sortable-item transform leaves the box stale) and shrinks the activation nudge to 10px so the cursor stays inside the handle. - Multi-param state reads (`?collapsed` + `?expanded`) are wrapped in a single `expect.poll` tuple so the slow side can't be missed by a one-shot read after the fast side resolved. - New page-object helpers (`getGroupChevron`, `getGroupBorderedToggle`, `getGroupTabs`, `getGroupBorderedAttr`, `waitForLoaded`, `waitForDashboardPatch`) keep testid prefixes out of specs. - `data-bordered="true|false"` attribute on the container shell so the bordered toggle test reads an attr instead of inspecting inline `style.border`. - `getGroupOrder` filters empty entries to surface DOM regressions. - `addGroupMenuItem` switched to `getByTestId(...)` for consistency with sibling locators. - Reverted unrelated em-dash → comma comment edits in `DashboardContainer.tsx` to keep the PR scope clean (em-dash sweep belongs in its own PR).
d76a82a to
6f66508
Compare
Summary
Follow-up E2E coverage for PR #2015 (the unified
DashboardContainerthat replaced the legacy section/group concept). Drew explicitly asked
for this in his top-level review on #2015 ("Can you confirm we have
followup issues/tickets covering... 2. New E2E tests covering the new
functionality"). Unit coverage landed in #2015 itself; this PR adds
the missing browser-level coverage.
The container UX has four moving pieces, all exercised here:
collapsible (chevron + URL state), bordered (overflow-menu toggle +
inline border style), tabs (tab bar appearance, tab switching, URL
state), and drag-to-reorder via @dnd-kit. Each test step cites the
source line that defines the behavior so a reviewer can double-check
the assertion matches the implementation.
Test cases
chevron toggles
aria-expanded.inline border style and the menu label.
switching tabs updates
?activeTabsandaria-selected.?collapsedand?expandedURL params survive reload and restoreper-viewer state.
?activeTabsURL param survives reload and restores the activetab.
second tab on group B.
DashboardDndContext.tsx:67-70); dragging A onto C in[A, B, C]yields
[B, C, A](arrayMove semantics) and the new orderpersists across navigation.
What changed since the first push
getActiveTabsParam()[id]reads inexpect.poll(...)becausenuqsflushes URL state asynchronouslyafter
Add Taband tab switches. This is the same race patternthe heatmap drag-select e2e (PR test(e2e): cover heatmap drag-select persistence (HDX-4147) #2209) hit.
addTabToGroupon group B) and now waits for
networkidlebefore navigating away.The earlier version paired a
toggleGroupBordered(idA)withaddTabToGroup(idB)back-to-back; the resulting concurrentsetDashboardcalls clobber each other because the secondproducereads the same pre-mutation snapshot. That underlyingrace is tracked in Dashboard: setDashboard mutations clobber each other when fired back-to-back #2216. Bordered round-trip is still covered in
isolation by test Generic Webhook Option for Alerts #2.
Side fixes pulled in
DashboardPage.tspage-object selector was still pointing at thestale
add-new-section-menu-itemtestid; PR refactor: Unify section/group into single Group with collapsible/bordered options #2015 renamed it toadd-new-group-menu-item. No existing spec exercisedaddSection,so this hadn't surfaced. Renamed the locator and the helper from
addSection->addGroup.DashboardContainer.tsxaddsdata-testid="group-add-tab-${id}"on the existing Add Tab menu item so the spec doesn't have to
match Mantine menu text. One-line non-behavior change.
Tier
Predicted Tier 2 by the local classifier: 1 production file,
10 production lines (the testid addition), no API/router/model
touch, single-layer (
packages/app/). Test files are excluded fromthe tier calculation per the classifier rules.
Out of scope
setDashboard(PATCH clobber).Tracked in Dashboard: setDashboard mutations clobber each other when fired back-to-back #2216.
dashboardSections.test.tsxunit tests).DashboardContainer.test.tsx).type: "section"migration (one-time data shape; coveredby
dashboardSections.test.tsx).react-grid-layout, not the new@dnd-kitcontainer DnD).Test plan
yarn lintclean.yarn tsc --noEmitclean.npx playwright test --list tests/e2e/features/dashboard-container.spec.tslists all 7 cases.prose-lintclean againstorigin/main.Refs PR #2015. Follow-up race tracked in #2216.