Repository navigation
Commit 4c29f38
test(plugin-chatbot): the pending-count pulse pin waits for the count render, not for the button (#26742)
<!-- claude-projects-attribution: {"github_login":"huangyiirene"} -->
_Requested by **Irene** · [project
thread](https://claude.ai/code/project/chan_01TJXtQ1tVD26ecLDZxFS67x?thread=cmsg_01TJXtQ1tVD26ecLDZxFS67xWykojkGiiPqmQgGV32BZcw)_
Fixes #26647
Clause-②: no
Metadata-protocol: none
## 维护者速读
- 改了什么:只改一个测试文件。草稿卡「发布 (N)」的计数断言,改成等到计数渲染出来再读,不再在按钮刚出现那一刻读文字。产品代码零改动。
- 为什么改:按钮先画出来,待发布数量是随后异步读回、下一次渲染才带上的。旧断言在按钮出现时就读,机器一忙就读到「发布」而不是「发布
(2)」,已两次把合并队列里的 PR 踢出去。
- 风险与回滚:极低,只动测试;回滚即 revert 这一个提交。
-
先看:`ui/packages/plugin-chatbot/src/__tests__/ChatbotEnhanced.pendingCountPulse-26471.test.tsx`
里新增的 `expectPublishButtonText` 和它替换掉的两处 `findByTestId` 一次性断言。
- 两分钟验证:`pnpm --dir ui exec vitest run
packages/plugin-chatbot/src/__tests__/ChatbotEnhanced.pendingCountPulse-26471.test.tsx`,预期
2 passed。
- 需要你做的:无,席位落地。
## What changes
`ChatbotEnhanced.pendingCountPulse-26471.test.tsx` only. Both tests read
the draft card's count as
```ts
expect(await screen.findByTestId('draft-publish')).toHaveTextContent('发布 (2)');
```
and now read it through one helper, `expectPublishButtonText`, which is
a `waitFor` around the same `getByTestId('draft-publish')` +
`toHaveTextContent` assertion. Nothing else in the file moves: the call
counts, the pulse leg, the unmount leg and every expected string are
byte-identical. `ChatbotEnhanced.tsx` is untouched.
## Why
The draft card draws its publish button in the same render as the card.
At that moment `fetchPendingDraftCount` has not answered,
`livePendingCount` is `undefined`, and the button reads `发布` with no
count. The count reaches the DOM on a second render, committed when the
read resolves and `setPendingByPackage` lands. `findByTestId` resolves
on its first check that finds the button, which is the first render, so
the one-shot `toHaveTextContent` that follows races the second render.
On an idle box that render usually lands first; under load it sometimes
does not, and the assertion reads `发布`.
That is exactly what the queue saw. Both ejections (queue runs
40468287272 and 40493270857) failed `unmount unsubscribes: a later pulse
reads nothing` with `Expected element to have text content: 发布 (2) /
Received: 发布`, at that test's opening `findByTestId('draft-publish')`
assertion, before any pulse fires in it.
**The count is not wrong** (triage asked to read the red as real until
shown otherwise): the button shows no count while the count is unknown
and `发布 (2)` once it is known, which is the component's stated design
("Leave the package unknown — the card falls back to the in-memory
snapshot rather than guessing"). The pin now waits for the render that
carries the count instead of the render that carries the button. No
sleep, retry, skip, timeout change or quarantine.
## Tests
All local, on this branch's head `b192892a` unless marked `origin/main`
(`f6ffd723`); 4-core container, so absolute durations are shared-box
figures.
- **Reproduced on `origin/main` under load:** 24 runs of the unchanged
file with 6 CPU busy-loops: 23 passed, **1 failed** in `unmount
unsubscribes` with `Received: 发布`, the queue's signature byte for byte.
- **Fix under the same load** (plus a gate sweep running beside it): 24
runs, **24 passed**, 0 failed.
- **Deterministic reverse check** (scratch file, deleted after the run,
never committed): with a `fetchPendingDraftCount` that answers 30 ms
later, the old pin fails every time (`Received: 发布`) and the new pin
passes.
- **The new pin still catches a wrong count:** with the count suffix
removed from the button in `ChatbotEnhanced.tsx` (via
`scripts/ablation-replace.mjs`), both tests go red with `Received: 发布`;
restored, blob equals `HEAD` (`2233fd2f`), `git diff HEAD` empty.
- `pnpm --dir ui exec vitest run packages/plugin-chatbot/`: 86 files,
816 tests passed.
- `pnpm --filter @object-ui/plugin-chatbot run type-check`: exit 0; `tsc
-p tsconfig.test.json --listFiles` lists this test file.
## Acceptance notes
- **Mechanism hypothesis, measured.** The dispatch guessed the red line
races the *pulse's* re-render. The red is real and load-dependent, but
it races the *first count read's* re-render: the failing assertion runs
before the test fires any pulse. The pulse leg in the first test is
wrapped in `await act(...)`, which flushes that update before the
assertions after it, and was never the red line.
- **No same-class sibling here.** In `ui/packages/plugin-chatbot/src`, a
one-shot text assertion straight after a `findBy*` occurs only at these
two calls. The other suite that wires `fetchPendingDraftCount`
(`ChatbotEnhanced.changeCardV2-12894`) reads the count inside `await
act(...)`.
- **No changeset, `skip-changeset` instead.** The diff is one test file.
`@object-ui/plugin-chatbot` ships `dist`, `README.md`, `CHANGELOG.md`
and `LICENSE`. After a build, `dist` holds 0 hits for
`expectPublishButtonText` or `unmount unsubscribes`, and 2 files for the
positive control `draft-publish`. So nothing publishes, and a patch
changeset would put a test-only line in the released CHANGELOG.
- `origin/main` gained one commit after this branch's base (`43731031`,
`ui/packages/auth` only), outside this package's dependency closure; not
merged in.
## Verification
<details><summary>Queue failure log (job 122069386772, run
40468287272)</summary>
```
FAIL dom packages/plugin-chatbot/src/__tests__/ChatbotEnhanced.pendingCountPulse-26471.test.tsx > objectstack#26471 — the draft card re-reads its pending count on the metadata pulse > unmount unsubscribes: a later pulse reads nothing
Error: expect(element).toHaveTextContent()
Expected element to have text content:
发布 (2)
Received:
发布
```
</details>
<details><summary>Loaded loops: origin/main vs this branch</summary>
```
LABEL=main-loaded6 runs=24 burners=6 pass=23 assertion_fail=1 other=0
1 FAIL |dom| packages/plugin-chatbot/src/__tests__/ChatbotEnhanced.pendingCountPulse-26471.test.tsx > objectstack#26471 — the draft card re-reads its pending count on the metadata pulse > unmount unsubscribes: a later pulse reads nothing
LABEL=fix-loaded6 runs=24 burners=6 pass=24 assertion_fail=0 other=0
```
</details>
<details><summary>Forced slow read: old pin vs new pin (scratch, not
committed)</summary>
```
× OLD pin: findByTestId then a one-shot text assertion
Expected element to have text content:
发布 (2)
Received:
发布
Tests 1 failed | 1 passed (2)
```
</details>
<details><summary>Count-suffix mutation against the new pin</summary>
```
ablation-replace: ok mutation landed: anchor 1 -> 0, blob 2233fd2 -> 7e324d005b78
× 发布 (2), then a publish elsewhere fires the pulse and the card stops offering 发布 1093ms
× unmount unsubscribes: a later pulse reads nothing 1024ms
Tests 2 failed (2)
ablation-replace: ok restored: blob == HEAD (2233fd2) and `git diff HEAD` is empty
```
</details>
<details><summary>Gates (derived by dispatch-gates --commands at
b192892)</summary>
24 derived commands, all exit 0, reconciled by `dispatch-gates --ran`:
"24 derived famil(ies) accounted for — 24 run, 0 NOT-MEASURED (a DERIVED
zero)". Among them `node scripts/check-sdui-manifest.mjs` (and
`--self-test`), `pnpm check:nul-bytes`, `pnpm check:published-files`,
`pnpm check:test-source-alias`, `pnpm check:cross-package-test-inputs`,
`pnpm check:type-check-coverage`, `pnpm check:type-check-debt`, and
`pnpm --dir ui exec vitest run scripts/` (178 files passed, 2 skipped).
Also `pnpm check:new-line-citations` (0 new), and eslint on the changed
file (clean). Repo-wide lint, the type-check lanes and the full UI farm
are CI's.
</details>
## Human Minimum
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
(not touched)
- [x] 2. Every name I added uses the casing for its kind.
- [x] 3. No formatter ran; `pnpm lint` passes. (eslint clean on the
changed file; repo-wide run is CI's)
- [x] 4. One issue, one branch off an up-to-date `main`, one pull
request.
- [x] 5. A change that publishes carries a changeset, and both
declaration lines are filled in here and in the changeset. (publishes
nothing: `skip-changeset`)
- [x] 6. `packages/spec` touched: its generated artifacts are
regenerated and committed. (not touched)
- [x] 7. No hand edits to `content/docs/references/`,
`packages/*/CHANGELOG.md` or `content/docs/releases/`.
- [x] 8. Title, body and comments are in English.
- [x] The *Before Submitting* list in CONTRIBUTING.md is done.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01J8VVAkq9LgnKiBeyJBZqSp)_
Co-authored-by: Claude <noreply@anthropic.com>1 parent abecc12 commit 4c29f38
1 file changed
Lines changed: 13 additions & 3 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
21 | 21 | | |
22 | 22 | | |
23 | 23 | | |
24 | | - | |
| 24 | + | |
25 | 25 | | |
26 | 26 | | |
27 | 27 | | |
| |||
81 | 81 | | |
82 | 82 | | |
83 | 83 | | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
84 | 94 | | |
85 | 95 | | |
86 | 96 | | |
87 | 97 | | |
88 | 98 | | |
89 | 99 | | |
90 | 100 | | |
91 | | - | |
| 101 | + | |
92 | 102 | | |
93 | 103 | | |
94 | 104 | | |
| |||
113 | 123 | | |
114 | 124 | | |
115 | 125 | | |
116 | | - | |
| 126 | + | |
117 | 127 | | |
118 | 128 | | |
119 | 129 | | |
| |||
0 commit comments