Skip to content

Commit 9dec054

Browse files
test(app-shell): the launcher narrowing test waits on the effect-recorded entries it asserts (#27234)
<!-- claude-projects-attribution: {"github_login":"os-warren"} --> _Requested by **Warren** · [project thread](https://claude.ai/code/project/chan_01U5RdnAAmkSQmZbqh6FtqZ8?thread=cmsg_01U5RdnAAmkSQmZbqh6FtqZ85Ns5E5tXtLApKFvjm2mLQP)_ Fixes #27216 Clause-②: no Metadata-protocol: none ## 维护者速读 - 改了什么:`useLauncherApps-26838.test.tsx` 第一个用例改为等待 effect 记录的 `seen` 本身,而不是只等 DOM 文本;断言不变(仍是 `toEqual([Setup, CRM])`、身份 `toBe`、只读一次)。 - 为什么改:`seen` 由 passive effect 写入,它可能晚于 DOM 提交才刷新;`waitFor` 看到 DOM 就返回,读到的还是待定渲染的三个应用(含 Showcase),于是 main 随机变红。 - 风险与回滚:只动一个测试文件,不改产品代码,规则照旧严格;回滚即 revert 本 PR。 - 先看:`ui/packages/app-shell/src/hooks/__tests__/useLauncherApps-26838.test.tsx` 第 98–103 行。 - 两分钟验证:`pnpm --dir ui exec vitest run packages/app-shell/src/hooks/__tests__/useLauncherApps-26838.test.tsx`,预期 8/8 通过。 - 需要你做的:无,席位落地。 ## What changes The first case of `useLauncherApps-26838.test.tsx` now waits on the effect-recorded entries it asserts: ```tsx await waitFor(() => expect(seen.mock.lastCall?.[0]).toEqual([SETUP, CRM])); const entries = seen.mock.lastCall?.[0]; expect(entries?.[0]).toBe(SETUP); ``` The DOM wait, the `data-pending` check, the identity check and the single-fetch check stay as they were. The assertion is not loosened (still `toEqual`, never `toContain`), nothing is skipped or retried, and no product source changes. ## Why `seen` is written by a `useEffect` in `Entries`; the old code waited only on `Probe`'s DOM text. `Probe` and `Entries` commit together, so when the DOM shows `setup,crm` the matching array exists, but its passive effect may not have flushed yet. When the render runs past the scheduler's frame, React yields after the commit and flushes passive effects in a later task. The DOM wait's MutationObserver can resolve in that gap, so `lastCall` was still the pending render's `[Setup, CRM, Showcase]`. That is exactly the main red: `expected [ …(3) ] to deeply equal [ …(2) ]` with Showcase third. The triage's alternative, state shared across tests, does not fit the evidence. The hook has no module-level store, `state` is reset in `beforeEach`, and a leak would also show in `Probe`'s DOM text, which the passing `waitFor` had just read as `setup,crm`. The sibling `CommandPalette.launcherHiddenApps-26838.test.tsx` and the other three `-26838` files assert DOM text inside `waitFor` and read no effect-recorded value, so they are untouched. ## Tests Repeat loop: a scratch copy of the file with case 1 expanded through `it.each` into N runs (scratch copy deleted after; nothing permanent added). "Loaded" means three busy-loop CPU burners on the 4-core box while the run held the verify lock. | Tree | Load | Case-1 runs | Red | |:--|:--|--:|--:| | main `9dacccf9` (before) | idle | 300 | 2 | | main `9dacccf9` (before) | idle | 1000 | 3 | | main `9dacccf9` (before) | loaded | 1000 | 5 | | this PR `feca0a0c` | idle | 1000 | 0 | | this PR `feca0a0c` | loaded | 1000 | 0 | | ablation of `feca0a0c` (old two lines restored) | loaded | 2000 | 7 | Every red was the main-red shape, with Showcase as the third entry. The ablation went through `scripts/ablation-replace.mjs`: anchor 1 to 0, blob `598c8e36` to `6772eb84` and back. Afterwards `git hash-object` equals the HEAD blob `598c8e36` and `git status` is clean. The five `-26838` launcher files at `feca0a0c`: 5 files, 20 tests passed. `eslint` on the changed file: exit 0. ## Acceptance notes - Changeset: none. The diff is a test in `@object-ui/app-shell`, which is `private: true`, so it carries `skip-changeset`. ## Verification Derived gates at `feca0a0c` (`node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack`, 24 families), every one run locally, exit codes captured before any pipe: all 24 exit 0, including `node scripts/check-sdui-manifest.mjs` and its `--self-test`, and `pnpm --dir ui exec vitest run scripts/`. `dispatch-gates --ran`: 24 derived, 24 run, 0 NOT-MEASURED. Every family CI runs is read from this PR's check-runs. ## Human Minimum - [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic. - [x] 2. Every name I added uses the casing for its kind. - [x] 3. No formatter ran; `pnpm lint` passes. - [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. - [x] 6. `packages/spec` touched: its generated artifacts are regenerated and committed. - [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 with [Claude Code](https://claude.com/claude-code) --- _Generated by [Claude Code](https://claude.ai/code/session_01JQwxbY2R68kfSCyjfFu4gU)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 14e9685 commit 9dec054

1 file changed

Lines changed: 4 additions & 2 deletions

File tree

‎ui/packages/app-shell/src/hooks/__tests__/useLauncherApps-26838.test.tsx‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,11 @@ describe('useLauncherApps narrows by /me/apps (objectstack#26838)', () => {
9595
renderWith(fetcher as unknown as typeof fetch, <><Probe /><Entries /></>);
9696
await waitFor(() => expect(screen.getByTestId('probe')).toHaveTextContent(/^setup,crm$/));
9797
expect(screen.getByTestId('probe')).toHaveAttribute('data-pending', 'false');
98-
// The entries are `/meta/app`'s own objects, not the answer's.
98+
// The entries are `/meta/app`'s own objects, not the answer's. `seen` is
99+
// written by a passive effect, which can flush after the commit the DOM
100+
// wait above saw: wait on the recording itself, not on the DOM.
101+
await waitFor(() => expect(seen.mock.lastCall?.[0]).toEqual([SETUP, CRM]));
99102
const entries = seen.mock.lastCall?.[0];
100-
expect(entries).toEqual([SETUP, CRM]);
101103
expect(entries?.[0]).toBe(SETUP);
102104
expect(fetcher).toHaveBeenCalledTimes(1);
103105
expect(fetcher.mock.calls[0][0]).toBe(ENDPOINT);

0 commit comments

Comments
 (0)