Skip to content

test(plugin-list): fold eight more ListView pins that mock nothing onto the shared harness - #30843

Merged
objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-gff875
Nov 6, 2026
Merged

objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-gff875

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Tesla · project thread
Part of #29469

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:列表组件 ListView 再有 8 个单卡测试文件(都不 mock 任何模块)合进上一片建好的共享测试文件 ListView.harness.test.tsx,每个原文件变成一个顶层 describe,断言一字未改;restoreAfterBlock() 多复原一样东西:window.print。
  • 为什么改:Tests grow 2.5 times faster than source: measure the per-fix test weight and the #25911 pilot, then roll the shared-setup merge out to the heaviest components or retire it #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 test(plugin-list): fold thirteen ListView pins that mock nothing onto one shared harness #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 Tests grow 2.5 times faster than source: measure the per-fix test weight and the #25911 pilot, then roll the shared-setup merge out to the heaviest components or retire it #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.
ListView pins left unfolded at 9b24f3ca: 109 files, 24,343 lines, 15 setups

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

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/ (perf(i18n, app-shell)!: the marketplace pages' strings leave the eager language packs for a lazy marketplace namespace #30640, feat(console): an administrator's first-run checklist on Home replaces the currency and time zone dialog #30552, feat(i18n)!: the Console's language packs follow the platform locale table, keyed by its tags, and Traditional Chinese (zh-Hant) ships #30595, feat(console): a marketplace app's Install confirms what it adds before it installs #30545, feat(app-shell): the ban dialog says how many records the user holds and offers an optional hand-over #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

  • 1. Schemas start as Zod; packages/spec holds no runtime logic. (packages/spec not touched.)
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; pnpm lint passes. (ui/ is linted by its own config; the package lint is above.)
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset. (Nothing publishes: skip-changeset.)
  • 6. packages/spec touched: its generated artifacts are regenerated and committed. (Not touched.)
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English (the maintainer quick-read is the allowed Chinese section).

…to the shared harness

Eight per-issue ListView pins that render the real ListView with no
vi.mock become top-level describes in ListView.harness.test.tsx, moved
verbatim, each opening with restoreAfterBlock(). The helper now also puts
back window.print, which the printButtonSemantics block swaps for a spy.
The two blocks that register an object-grid stand-in sit after the
blocks that register nothing.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L9qibwT4kjTEGArV4JTj3c
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Nov 6, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#2 · os-tesla · session_01L9qibw7DekEe3wx5f3zphf

席位核对 · ACCEPT · 2026-11-06T06:34Z · head 79076390676d548f6fe073302b93f3ce075a9e46

Seat domain:ui#2 reviewed the diff of its own dispatch (claim 6677498118 on #29469, card thread session_01L9qibwT4kjTEGArV4JTj3c; the same seat dispatched and reviews, the code is the thread's).

  1. Shape: 9 files, all ui/packages/plugin-list/src/__tests__/: 8 pins with no vi.mock deleted and folded as top-level describe blocks into ListView.harness.test.tsx (13 → 21); restoreAfterBlock() also restores window.print. Part of #29469, skip-changeset (nothing publishes).
  2. Premise measured: ^describe( count 13 on origin/main before; slice named in Release 6675298252 item 1.
  3. Gates / check-runs: 60 check runs on this head, 44 success, 16 skipped, 0 red. Report 6678570494: it( 44 = 44, expect( 104 = 104, titles identical; harness 85/85 in order and under seeds 11 / 222 / 3333; block-start probe clean; plugin-list 171 files / 1,826 tests; type-check and lint 0; 26 derived gates exit 0.
  4. Accepted deviation: the branch was not re-merged with main (15 commits behind; none touch plugin-list or the vitest setup). mergeable_state reads clean; the merge queue tests the merged ref.
  5. Out of scope / Skills-docs: the unfolded ListView* list (109 files) stays in the PR body for the card; ObjectView and RecordDetailView families remain on Tests grow 2.5 times faster than source: measure the per-fix test weight and the #25911 pilot, then roll the shared-setup merge out to the heaviest components or retire it #29469. Skills-docs: not owed (Metadata-protocol: none, test-only).
  6. Landing: check-governed-merges --branch: 0 of 9 paths governed, 2,637 changed lines ≤ 3,000 → Tier S path, no human approval owed. Test-only: no contract review owed. Landing on the maintainer's standing word 「按 pm-dispatch 审过的pr你可以自主落地」 (2026-10-30T14:20Z) and 「ui 车道跟进一下 3小时内没更新的pr和已派发的issue,接手处理。」 (2026-11-06T05:20Z): relay pr_ready, then automerge_enable. On merge the card goes back to pm:queue (Part of).

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 6, 2026 06:37
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 6, 2026
Merged via the queue into main with commit e603385 Nov 6, 2026
65 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-gff875 branch November 6, 2026 06:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/xl skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant