lizhimins opened a new pull request, #4200:
URL: https://github.com/apache/rocketmq-dashboard/pull/4200

   Two frontend tests were failing intermittently with `Unable to perform 
pointer interaction as the element has pointer-events: none` — on an `INPUT` (a 
row selection checkbox) in `AlertsPage.test.tsx` and on an `LI` (a pagination 
item) in `ConsumerPage.test.tsx`. Both are the same race, and it is in the 
tests rather than in the pages.
   
   ## Root cause
   
   antd's `Table` keeps the previously rendered rows on screen while `loading` 
is true, and wraps the body — pagination included — in a `Spin`. `Spin` tracks 
its own `spinning` state (`antd/es/spin/index.js:52`) and only syncs it from 
the `spinning` prop inside an effect (`:54-66`), while the `.ant-spin-blur` 
class is keyed on that internal state (`:80`). So when the Table's `loading` 
prop flips to false, the blur class survives **one further commit**.
   
   `.ant-spin-blur` sets `pointer-events: none` 
(`antd/es/spin/style/index.js:133-137`), covering every row control plus the 
pagination items that antd renders inside the same spin container 
(`antd/es/table/InternalTable.js:396`).
   
   `findByText` on a row name resolves during that window because the rows are 
already in the DOM, and the click that follows is rejected by userEvent's 
pointer-events check.
   
   Instrumenting the failing runs confirms the mechanism and rules out the 
alternatives:
   
   | | failing run | passing run |
   | :-- | :-- | :-- |
   | `.ant-spin-blur` present | 1 | 0 |
   | `.ant-spin-spinning` present | 1 | 0 |
   | checkbox `disabled` | **false** | false |
   | computed `pointer-events` (input / row) | `none` / `none` | `auto` / 
`auto` |
   | `bulkToggleAlertRules` calls | 0 | 0 |
   | tables / text matches in DOM | 1 / 1 | 1 / 1 |
   
   The checkbox is *not* disabled (`getCheckboxProps` only disables while 
`isActionRunning`, and no action had run), there is no stale DOM from a 
previous test, and the row text matches exactly once — so the only thing 
blocking the click is the spin blur.
   
   ## Why the fix is test-side
   
   Batching `setLoading(false)` into the same `.then` as the row updates in 
`pages/ops/alerts.tsx` was tried first and **reverted**. It removes our own 
intermediate commit but not Spin's, so it lowered the failure rate without 
eliminating it (1 failure in 10 runs, versus 1 in 6 before). And since both 
microtasks drain before the browser paints, that intermediate state is 
invisible to users — it is only observable from tests. There was nothing to fix 
on the page side.
   
   ## What changed
   
   Each of the two test files gains a small wait for the target to accept 
pointer events, applied **only** where a test clicks a table-internal control 
immediately after a `findBy*` with nothing polled in between:
   
   - `AlertsPage.test.tsx` — `expectRuleRowInteractive(ruleName)`, used at 3 
sites (two 编辑 button clicks and the row checkbox before 批量启用).
   - `ConsumerPage.test.tsx` — `findInteractivePageItem(selector)` at the two 
pagination clicks, and `expectInteractive(element)` for the row checkbox that 
follows a page change.
   
   Sites that already wait on something else — e.g. `await waitFor(() => 
expect(checkbox).toBeEnabled())` at `AlertsPage.test.tsx:247` and `:696` — are 
deliberately left untouched. That poll absorbs the extra commit, which is 
exactly why those tests never failed. The `ConsumerPage` pagination waits are 
preventive: that file produced one observed failure with the identical 
signature and contains the identical code pattern.
   
   No production code is changed.
   
   ## Validation
   
   - `npx tsc --noEmit -p tsconfig.app.json` — clean.
   - `npx eslint` on both files — clean, 0 errors.
   - `npm run build` — succeeds.
   - `AlertsPage.test.tsx` under 3-way concurrent load: **30/30 passed** 
(baseline before the fix: 1 failure in 6 sequential runs, and 1 in 12 on a 
second measurement).
   - `ConsumerPage.test.tsx` under 3-way concurrent load: **15/15 passed**.
   - Full frontend suite (`vitest run`, 120 files / 980 tests): **6 consecutive 
clean runs, 0 failures, 0 unhandled errors**. Before this change the suite 
produced one failure per run, alternating between these two files.
   
   Note that `tsc --noEmit -p tsconfig.json` is not a usable check in this repo 
— `tsconfig.json` is a solution file with `"files": []` and only project 
references, so it type-checks nothing and exits 0 regardless. Use 
`tsconfig.app.json`, or `npm run build`, whose script is `tsc -b && vite build`.
   
   ## Follow-up (not in this PR)
   
   The same latent pattern — `findBy*` on table content followed immediately by 
a click on a control inside the table — may exist in other test files. If more 
instances surface it is worth promoting one shared helper into `src/test/` 
rather than keeping a copy per file.
   
   Separately, 18 call sites across 12 frontend files set data in `.then()` and 
clear a loading flag in `.finally()`. Those are two microtasks, so they always 
commit an intermediate state where the data is present and the spinner is still 
on. Browsers drain both before painting, so this is not user-visible and is not 
touched here; it is noted only because it is what makes the intermediate state 
reachable from a test in the first place.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to