Skip to content

Commit e603385

Browse files
test(plugin-list): fold eight more ListView pins that mock nothing onto the shared harness (#30843)
<!-- claude-projects-attribution: {"github_login":"os-tesla"} --> _Requested by **Tesla** · [project thread](https://claude.ai/code/project/chan_01AXsQkLFKRdQCqQ3RTr6Jao?thread=cmsg_01AXsQkLFKRdQCqQ3RTr6JaoFc3WxLQeVDNyTRiwvaQips)_ Part of #29469 Clause-②: no Metadata-protocol: none ## 维护者速读 - **改了什么**:列表组件 `ListView` 再有 8 个单卡测试文件(都不 mock 任何模块)合进上一片建好的共享测试文件 `ListView.harness.test.tsx`,每个原文件变成一个顶层 `describe`,断言一字未改;`restoreAfterBlock()` 多复原一样东西:`window.print`。 - **为什么改**:#29469 第二族 ListView 的第二片,也是这一族的最后一个 PR(席位修订 2:每族最多两个 PR)。本地同机实测,这 8 个文件的 CPU 从约 37 秒降到并入后约 2 秒;ListView 族还没合并的 109 个文件按共享的 setup 列在正文末尾。 - **风险与回滚**:只动测试文件,不改源码、门禁、CI,不发布任何东西;块之间串状态的风险用探针(逐块记录注册表、`localStorage`、`window.print`、`<html lang>`、react-i18next 全局实例)在原顺序和三种乱序下排除。回滚 = revert 本 PR。 - **先看**:`ListView.harness.test.tsx` 里的 `restoreAfterBlock()`(新增 `window.print` 的两行),再看 `ListView.printButtonSemantics` 与 `ListView.resetSortToDefaultNoProviderFallback` 两块(前者换 `window.print`,后者原本因 i18n 全局实例单独成文件)。 - **两分钟验证**:在 `ui/` 下跑 `pnpm exec vitest run packages/plugin-list/src/__tests__/ListView.harness.test.tsx`,应见 1 个文件、85 个测试全过。 - **需要你做的**:无,席位落地。 - **席位意见**:ui 席 2 对照 head `79076390` 的 diff 复核:9 个文件全在 `plugin-list/src/__tests__/`,8 个单卡文件并入共享文件、断言与标题一字未改(`it(` 44=44、`expect(` 104=104),只多复原 `window.print`;CI 全绿,纯测试改动无发布面,不欠契约复审。同意落地。 ## What changes Slice 2b, the second and last `ListView*` PR of the shared-setup rollout (ruling 6583487846 on #29469; seat amendment 2, 6672694520: at most two PRs per family; the slice named in Release 6675298252, item 1). Eight more pins in `ui/packages/plugin-list/src/__tests__/` that render the real `ListView` with no `vi.mock` join `ListView.harness.test.tsx`, which slice 2a (#30769) created: - Each former file is a top-level `describe` named after its file stem, with its docblock above it and everything else moved verbatim, re-indented by two spaces, opening with `restoreAfterBlock()`. The 8 files are deleted. The harness goes from 13 to 21 top-level `describe` blocks. - The six that register nothing (`contractEnvelope-6917`, `emptyStateAccess-25898`, `emptyStateFilteredCopy`, `printButtonSemantics`, `refusedSortNotSent-30024`, `switcherChrome-7547`) sit, in name order, among the eight from slice 2a that register nothing. The two that register an `object-grid` stand-in in their hooks (`nameFieldSelect-13116`, `resetSortToDefaultNoProviderFallback`) sit, in name order, among the five that do, after all the others. No existing block moved or changed. - **`restoreAfterBlock()` also puts back `window.print`.** `printButtonSemantics` installs a `window.print` spy with `Object.defineProperty` in one of its tests and never removes it. A former file got a fresh window; a block does not. The helper now snapshots the `print` property descriptor in its `beforeAll` and redefines it (or deletes an own property the block added) in its `afterAll`. Measured with a probe that logs state at each block's first test: without the two new lines, the ten blocks after `printButtonSemantics` start with the spy installed; with them, none does. - `resetSortToDefaultNoProviderFallback`'s docblock explains why it was its own file: an `I18nProvider` mounted anywhere in a file used to install react-i18next's global instance for every later provider-less render in it. That no longer holds, and a note under the docblock says why: `ui/vitest.setup.i18n-global.ts` puts the pointer the file inherited back after every test (objectui#4514). The probe reads react-i18next's `getI18n()` at every block's first test and finds no global instance in any of the four orders, including after the blocks that mount a provider (`contractEnvelope-6917`, `refreshIndicatorLocale-10580`, `recordDrawerCount-27545`). The same holds for `printButtonSemantics`, which reads the provider-less fallback too. - `contractEnvelope-6917`'s `beforeAll` patches `Element.prototype.scrollIntoView` only when it is missing. happy-dom defines it, so the patch never runs (probed: `typeof Element.prototype.scrollIntoView` is `function` before any block, and the probe compares its identity at every block). - The harness's import block takes the union of the former files' imports. `I18nProvider` stays imported once from `@object-ui/i18n` (`contractEnvelope-6917` took it from `@object-ui/react`, which re-exports that binding), and `import * as React` in two former files is the harness's `import React`. No other file names any of the 8: `git grep` of each stem over `ui`, `scripts`, `packages`, `content`, `apps` and `.github` gives no hit outside the file itself, so no comment needed updating. Test files only: no source file, no changeset (`skip-changeset`). `@object-ui/plugin-list` builds from its entry with `vite build`, and no entry imports a test file. ## Tests ### Former file → describe (no title or assertion dropped) TypeScript AST walk of each former file at `9b24f3ca` (the `main` this was cut from, where they still exist) against the `describe` of the same name in the harness at this PR's head. Titles are compared as the sorted list of full `describe > it` paths. | former file | `it` before | `it` after | `expect(` before | `expect(` after | titles identical | |---|--:|--:|--:|--:|:-:| | `ListView.contractEnvelope-6917.test.tsx` | 4 | 4 | 5 | 5 | yes | | `ListView.emptyStateAccess-25898.test.tsx` | 9 | 9 | 29 | 29 | yes | | `ListView.emptyStateFilteredCopy.test.tsx` | 7 | 7 | 16 | 16 | yes | | `ListView.printButtonSemantics.test.tsx` | 5 | 5 | 15 | 15 | yes | | `ListView.refusedSortNotSent-30024.test.tsx` | 6 | 6 | 17 | 17 | yes | | `ListView.switcherChrome-7547.test.tsx` | 8 | 8 | 8 | 8 | yes | | `ListView.nameFieldSelect-13116.test.tsx` | 3 | 3 | 9 | 9 | yes | | `ListView.resetSortToDefaultNoProviderFallback.test.tsx` | 2 | 2 | 5 | 5 | yes | | **total** | 44 | 44 | 104 | 104 | yes | The whole harness: 41 `it` / 95 `expect(` at `9b24f3ca`, 85 / 199 at this head (41 + 44, 95 + 104). The diff touches no line of the thirteen slice-2a blocks: every hunk inside them is an insertion between blocks. ### CPU before and after (local A/B, one machine) **CI after: NOT MEASURED**: the per-file timings reporter (#29517) uploads on `main`'s full-battery runs only. Its first after-reading is the first full-battery `main` run after this merges. Local A/B on one 4-vCPU container, under `os-verify-lock` (shared-box seconds), same command both legs, from `ui/`: `pnpm exec vitest run packages/plugin-list/src/__tests__/ListView. --maxWorkers=2` (every `ListView.*` file in that directory, `ListView.test.tsx` included), with a throwaway reporter summing each module's `diagnostic()`. Fixed = environment + prepare + setup + collect (import). The "before" leg writes the 8 files and the harness from `9b24f3ca` into the branch's tree, so both legs run the same 101 other files; run order before, after, after, before. | | files | tests passed | fixed CPU | test CPU | total CPU | wall | |---|--:|--:|--:|--:|--:|--:| | ListView family, before (run 1 / 2) | 118 | 1,140 | 527.5 / 544.9 s | 96.2 / 102.5 s | 623.8 / 647.3 s | 326.8 / 339.5 s | | ListView family, after (run 1 / 2) | 110 | 1,140 | 527.4 / 504.9 s | 106.9 / 97.6 s | 634.4 / 602.5 s | 332.4 / 315.9 s | | the 8 + the harness, before (run 1 / 2) | 9 | 85 | 37.9 / 39.1 s | 5.6 / 5.6 s | 43.6 / 44.7 s | | | the harness, after (run 1 / 2) | 1 | 85 | 4.7 / 4.4 s | 4.9 / 4.4 s | 9.6 / 8.8 s | | The 8 files took 37.0 / 37.4 s of CPU on their own; the harness grew from 6.6 / 7.3 s to 9.6 / 8.8 s to hold them, so the fold saves about 35 s of CPU (−79 % for those nine files together). The family read 635.6 s before and 618.5 s after on average (−2.7 %), and wall 333.2 s to 324.2 s (−2.7 %). That family figure sits inside this box's run-to-run spread (the two before runs differ by 23.5 s), so it is a reading, not a measured saving; the per-file figures above are the measurement. This box ran about 40 % slower than slice 2a's (2a's family wall was 230 s on 129 files), so compare ratios, not seconds, across the two PRs. ## Acceptance notes - **Why these 8.** They are the 8 smallest of the 16 `ListView*` pins that pass every census check in #30769's "Why these 13" (no `vi.mock`; no statement at module scope or collection time other than imports, declarations and test calls; no `vi.spyOn`, `vi.stubGlobal` or `vi.useFakeTimers`; not on the `dom-heavy` list; no side-effect import other than `@testing-library/jest-dom`; no other file naming it), the third row of the table in Release 6675298252. 1,339 lines moved make 2,637 changed lines; the next smallest (`toolbarRows-27520`, 210 lines) would take it past 3,000. - **This is ListView's last PR under amendment 2,** so the family's unfolded pins are listed below by the setup they share. The table is Release 6675298252's census at `f2ea524d`, minus the 8 folded here, re-checked at `9b24f3ca`: the same files are present and every line count is unchanged, and no new `ListView.*` pin has landed in `plugin-list/src/__tests__/` since. Whether to fold further is a new card the seat files after #29469 closes (amendment 2). - `printButtonSemantics` and `resetSortToDefaultNoProviderFallback` are the two pins whose own prose says a provider in the same file would make them assert nothing. They fold cleanly only because the `ui/` setup resets react-i18next's global after every test; the probe reading above is the evidence, and a later change to that setup file is the thing that would undo it. <details><summary>ListView pins left unfolded at <code>9b24f3ca</code>: 109 files, 24,343 lines, 15 setups</summary> `ListView.test.tsx` is the component's main suite, not a per-issue pin, and is not counted. A file that mocks nothing sits under the first check it fails, in the order of the rows below that start with "no `vi.mock`". Names drop the `ListView.` prefix and `.test.tsx`. | setup the files share | files (lines) | lines | |---|---|--:| | no `vi.mock`, but a statement at module scope or in a `describe` body that is not an import, a declaration or a test call: it runs when the file is collected | `fieldListRead-11984` (765), `permissions` (607), `readersRetired-6152` (445), `timeline-binding` (378), `strayGroupByRefused-8365` (353), `gantt-binding-7070` (330), `mapFlatten` (319), `chart-capability-7544` (318), `objectProviderBinding-7477` (317), `datasetChartAppliedFilter-10512` (303), `selfQueryingViewsToolbarState-10250` (303), `mapViewLevelConfig` (302), `sharedSelect-11865` (299), `timelineMetaFieldsRetired-10222` (280), `sortPersistLeak` (261), `searchWithheldTreeChart-10326` (259), `ganttNavigationForward-7334` (258), `sortabilitySignal` (256), `calendarAliasRefused-8355` (238), `descriptionInlineLocale-7199` (230), `overlayTitleI18n` (227), `filterDatePicker-12743` (215), `searchlessObject-12966` (212), `calendar-binding-7029` (211), `overlayTitleInlineLocale-9373` (204), `ganttToolbarFilter-10037` (197), `ariaLabelInlineLocale` (195), `treeEmptyState-28966` (191), `ganttPagingChrome-7210` (163), `datasetChartToolbarFilter-10327` (156), `onGroupingChange-11860` (153), `overlayTitleNoProviderFallback` (143), `toolbarLeading-12440` (143), `hostRoutedRecordClick-27057` (135), `density.offspec` (126), `kanbanDeclaredLanes-28211` (116), `nestedAriaSharedReader-11083` (105), `calendarInitialDate-28102` (86), `toolbarNames-12510` (55) | 9854 | | no `vi.mock` and passes the checks above, but another file names it, so folding it also edits that file | `loadErrorKind` (283), `userActionsCollision` (281), `viewSwitchRefetch-7394` (250), `sortRemovableOnly-11943` (246), `personalization-28950` (241), `phoneSortSearch-28911` (240), `relationalSort` (220), `harvestInputsFetchKey-10689` (200), `kanbanOwnsLanes-12619` (196), `unauthoredColumnProjection-6598` (194), `headerSort` (188), `toolbarIconButtons-12695` (183), `clearFiltersChips-29052` (179), `findParamsHandoff` (176), `groupingNeedsHeaderQuery-10881` (176), `serverPagination` (175), `groupedGridOwnsFetch-7189` (169), `displayResetBaseline-12949` (166), `kanbanBlockCanonical-8193` (164), `toolbarAccessibleNames-12500` (153), `rowGroups-28461` (151), `footerOneRow-12528` (131), `emptyStateI18nLabel-11227` (123), `storedRuleDialect-11533` (108), `refreshButton` (92) | 4685 | | no `vi.mock` and passes every check: left out for the 3,000-line bar, the next fold's first candidates | `sortFieldFallback` (338), `pagerClampAfterWrite-10384` (245), `sortFieldRead-11943` (240), `groupingProjection-7179` (227), `displayPopover-12423` (214), `emptyStateClearFilters-28908` (212), `restoreViewDefaults-28948` (211), `toolbarRows-27520` (210) | 1897 | | no `vi.mock`, but `vi.spyOn`, `vi.stubGlobal` or `vi.useFakeTimers` | `pageSizeNonPositive-9897` (377), `calendarDateWindow-12081` (317), `pageSizeAndFetchBatch-9853` (257), `queueSittingFilter-25769` (246), `bulkDeleteVisibleWhen` (238), `filterContextTokens-10607` (215), `phoneWidth-29074` (174), `exportMatchesView` (173), `nonGridBulkDeclared-30394` (162), `nonGridBulkGate-30535` (160), `calendarFooterCount-27355` (143), `emptyStateLink-12270` (137), `gridObjectFields-10657` (119), `invalidationRefetch-10572` (107), `exportRefusal-10789` (82) | 2907 | | `@object-ui/permissions` mocked: `usePermissions` returns a `vi.hoisted` holder each test fills | `speculativeFls-7216` (413), `mapLocationSelect-10370` (267), `allDeniedSelect-10275` (251), `referenceViaSelect-13036` (193), `dependsOnSelect-13214` (191), `deadlineSettledSelect-11815` (182), `rowColorSelect-12376` (176) | 1673 | | no `vi.mock`, but on the `dom-heavy` list in `ui/vitest.config.mts` | `crossPageSelectAll` (359), `inlineFlsNoop-6723` (185) | 544 | | `@object-ui/components` mocked: `FilterBuilder` replaced by a stand-in that records its `fields` and renders nothing | `columnsOnlyRelationalReads-7531` (175), `columnsOnlyOptions-10547` (157), `declaredDisplayField-10545` (154) | 486 | | no `vi.mock`, but a side-effect import other than `@testing-library/jest-dom` | `elementDataSource` (402) | 402 | | `@object-ui/components` mocked: `FilterBuilder` wrapped, recording its `fields` and rendering the real one | `filterFieldRead-11925` (210), `filterFieldList-11810` (192) | 402 | | `../ListView` stubbed and `@object-ui/react`'s `ElementDataSourceGate` wrapped | `sharedGate` (313) | 313 | | `@object-ui/permissions` mocked: `usePermissions` returns a module-level stub | `expandFls-7215` (242) | 242 | | no `vi.mock`, but `.test.ts` files in the `unit` project, which is not isolated per file, so a fold saves nothing | `propsResolution.test.ts` (149), `noAnySchemaCast.test.ts` (77) | 226 | | `react` mocked with `useMemo` patched | `discardedExpandFieldsMemo` (220) | 220 | | `@object-ui/permissions` mocked: `usePermissions` returns a fixed object built in the factory | `columnIdentity` (174) | 174 | | `@object-ui/permissions` mocked, in `ui/packages/app-shell/src/__tests__/` (another package, so another harness) | `gridRowColorSelect-12393` (318) | 318 | | **total** | 109 files | 24343 | </details> ## Verification At head `79076390`, after `pnpm install --frozen-lockfile` and building the closure (`turbo run build` for `@objectstack/spec`, `formula`, `lint`, `client`, `metadata-protocol`, then `--filter='@object-ui/plugin-list^...'` from `ui/`), under `os-verify-lock`, VERDICT command-exit 0: - `pnpm exec vitest run packages/plugin-list/ --maxWorkers=2` from `ui/`: 171 files, 1,826 tests, all passed. That is 8 files fewer than after slice 2a (179) and the same 1,826 tests. - `pnpm run type-check` in `ui/packages/plugin-list` (`tsc --noEmit && tsc -p tsconfig.test.json`): exit 0, 0 `error TS`. The harness is in the `tsconfig.test.json` program (`--listFilesOnly`). - `pnpm run lint` in `ui/packages/plugin-list`: exit 0, 0 errors (780 warnings, as after slice 2a). `eslint` on the harness from `ui/`: exit 0, 0 errors, 56 warnings, the same 56 that the 8 former files and the slice-2a harness give together at `9b24f3ca`. - Order: the harness passes 85 / 85 in declaration order and under `--sequence.shuffle.tests` with `--sequence.seed` 11, 222 and 3333. A throwaway probe (a copy of the harness with a `beforeAll` after each block's `restoreAfterBlock()`) logged, at every block's start, each registry entry's renderer and meta against the post-import registry, the `localStorage` and `window.print` own-property values, `Element.prototype.scrollIntoView`, both storages' lengths, `<html lang>` / `dir`, and react-i18next's `getI18n()`: every one of the 21 blocks starts clean in all four orders. The same probe with the two new `window.print` lines removed finds the spy installed at the start of all ten blocks after `printButtonSemantics`. - Derived gates (`node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack`, change set 9 paths against merge base `9b24f3ca`): 26 derived, 26 run, each exit 0 captured before any pipe; `pnpm --dir ui exec vitest run scripts/` ran under `os-verify-lock` (VERDICT command-exit 0) and gave 170 files passed and 2 skipped, 4,970 tests passed and 2 skipped. `--ran` reconciliation: 26 derived, 26 run, 0 NOT-MEASURED, 0 UNRUN. - Size: `git diff --shortstat origin/main HEAD` is 9 files, +1,291 / −1,346, 2,637 changed lines. No open pull request the dispatch named as touching `ui/` (#30640, #30552, #30595, #30545, #30652) changes any of the 9 paths (their file lists read at 2026-11-06 06:00Z). - `main` has moved 15 commits past `9b24f3ca` since this was cut; none touches `ui/packages/plugin-list`, `ui/vitest.config.mts` or `ui/vitest.setup.i18n-global.ts`, so the branch is not re-merged and the readings above stand for this head. - No ablation of a product behaviour: none changed, and the assertions moved verbatim. The helper's new lines are shown by the probe with and without them. ## Human Minimum - [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic. (`packages/spec` not touched.) - [x] 2. Every name I added uses the casing for its kind. - [x] 3. No formatter ran; `pnpm lint` passes. (`ui/` is linted by its own config; the package lint is above.) - [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. (Nothing publishes: `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 (the maintainer quick-read is the allowed Chinese section). <!-- signed: domain:ui#2 · os-tesla · session_01L9qibw7DekEe3wx5f3zphf --> Co-authored-by: Claude <noreply@anthropic.com>
1 parent da3c742 commit e603385

9 files changed

Lines changed: 1291 additions & 1346 deletions

‎ui/packages/plugin-list/src/__tests__/ListView.contractEnvelope-6917.test.tsx‎

Lines changed: 0 additions & 153 deletions
This file was deleted.

‎ui/packages/plugin-list/src/__tests__/ListView.emptyStateAccess-25898.test.tsx‎

Lines changed: 0 additions & 170 deletions
This file was deleted.

0 commit comments

Comments
 (0)