Skip to content

test(plugin-list): fold thirteen ListView pins that mock nothing onto one shared harness - #30769

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

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

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Sales · project thread
Part of #29469

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:列表组件 ListView 的 13 个单卡测试文件(都不 mock 任何模块)合进一个新的共享测试文件 ListView.harness.test.tsx,每个原文件变成一个顶层 describe,断言一字未改;每块第一行调用 restoreAfterBlock(),块结束后把注册表、localStorage 和 <html> 的 lang/dir 复原,下一块从新文件的状态起步。
  • 为什么改: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 的第一片(席位修订 2:每族最多两个 PR,未合并的 pin 清单放在第二片)。本地同机实测,这 13 个文件的 CPU 从约 39 秒降到约 5 秒,整个 ListView 测试族快约 8%。
  • 风险与回滚:只动测试文件,不改源码、门禁、CI,不发布任何东西;块之间串状态的风险已用探针(逐块记录注册表、localStorage、<html lang>)和三种乱序运行排除。回滚 = revert 本 PR。
  • 先看:ListView.harness.test.tsx 开头 90 行(块之间怎么隔离,以及 restoreAfterBlock()),再看 ListView.displayMenuClose-12621 往后的 5 块(会往 ComponentRegistry 注册替身,所以排在最后)。
  • 两分钟验证:在 ui/ 下跑 pnpm exec vitest run packages/plugin-list/src/__tests__/ListView.harness.test.tsx,应见 1 个文件、41 个测试全过。
  • 需要你做的:无,席位落地。

What changes

Slice 2a, the first of the two ListView* PRs of the shared-setup rollout (ruling 6583487846 on #29469; seat amendment 2, 6672694520: at most two PRs per family). Thirteen pins in ui/packages/plugin-list/src/__tests__/ render the real ListView with no vi.mock at all, and become one new file, ListView.harness.test.tsx:

  • 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. The 13 files are deleted.

  • The harness's header says how the blocks stay apart: RTL cleanup() after every test (the dom setup registers it), every fixture and hook inside its own block, no module mocks.

  • One addition the former files did not need: restoreAfterBlock(), the first line of every block. A former file got a fresh window and a fresh registry; a block shares both with the blocks after it. Before adding it, a probe that logged the state at each block's first test found:

    • displayMenuClose-12621 left stand-ins for object-kanban, object-gallery, object-calendar and object-timeline registered (its hooks put back only entries that existed);
    • recordDrawerCount-27545 left the real object-grid unregistered, and rowAdornment-13330 then left its own object-grid stand-in registered;
    • mutationRefresh left its localStorage stand-in installed for the seven blocks after it;
    • the view preference displayMenuClose-12621 saved (listview-opportunity-view) was still in storage for the four blocks after it.

    All 41 tests passed anyway, but each later block was running against state its file never had. The helper snapshots the registry and the localStorage property in a beforeAll declared before the block's own hooks, and after the block's last test (its afterAll runs after the block's own) unregisters what the block added, re-registers each entry it replaced or removed from that entry's own config, puts localStorage back, clears both storages, and removes the lang / dir that I18nProvider sets on <html>. With it, the same probe finds every block starting from the post-import registry (every key, renderer and meta equal), the window's own empty localStorage and no lang, and the file passes in three shuffled orders (--sequence.shuffle.tests, seeds 11, 222, 3333), which put registering blocks before the others.

  • The five blocks that register a stand-in renderer in ComponentRegistry (displayMenuClose-12621, inPageScroll-12463, recordDrawerCount-27545, rowAdornment-13330, setupChrome-12606) still sit after the eight that register nothing.

  • I18nProvider is imported once, from @object-ui/i18n. Some former files took it from @object-ui/react, which re-exports that same binding.

No other file names any of the 13: git grep of each stem over ui, scripts, packages, content 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 e00bcc6a (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.

former file it before it after expect( before expect( after titles identical
ListView.concurrencyTokenProjection-12292.test.tsx 2 2 7 7 yes
ListView.displayMenuClose-12621.test.tsx 4 4 8 8 yes
ListView.emptyVsErrorRole-7132.test.tsx 3 3 7 7 yes
ListView.errorStateComponent-7143.test.tsx 6 6 15 15 yes
ListView.forbiddenNoRetry.test.tsx 3 3 7 7 yes
ListView.galleryBlockCanonical-6152.test.tsx 7 7 7 7 yes
ListView.inPageScroll-12463.test.tsx 2 2 14 14 yes
ListView.mutationRefresh.test.tsx 2 2 4 4 yes
ListView.onRowsChange-26679.test.tsx 2 2 5 5 yes
ListView.recordDrawerCount-27545.test.tsx 3 3 4 4 yes
ListView.refreshIndicatorLocale-10580.test.tsx 2 2 7 7 yes
ListView.rowAdornment-13330.test.tsx 3 3 5 5 yes
ListView.setupChrome-12606.test.tsx 2 2 5 5 yes
total 41 41 95 95 yes

A line-multiset comparison (every non-blank line of the 13 files, trimmed, against the harness) finds no code or comment line dropped: what the files had and the harness lacks is the 13 license headers and the duplicate import lines.

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 <every ListView.*.test.ts(x) in plugin-list/src/__tests__> --maxWorkers=2, with a throwaway reporter reading each module's diagnostic(). Fixed = environment + prepare + setup + import. The "before" leg restores the 13 files from the base into the branch's tree and removes the harness, so both legs run the same 116 other files. Runs 1 and 2 are on 937fec9d, this fold before restoreAfterBlock() was added, over 05a30e6e; run 3 is on this head, over e00bcc6a.

files tests passed fixed CPU test CPU total CPU wall
ListView family, before (run 1 / 2 / 3) 129 985 377.4 / 375.7 / 375.2 s 63.9 / 62.9 / 64.0 s 441.3 / 438.6 / 439.1 s 231.4 / 230.1 / 230.4 s
ListView family, after (run 1 / 2 / 3) 117 985 342.6 / 343.0 / 340.8 s 62.8 / 62.8 / 62.1 s 405.4 / 405.8 / 402.9 s 212.6 / 212.7 / 211.2 s
the 13, before → harness, after (run 1) 13 → 1 41 → 41 37.1 → 3.0 s 2.7 → 1.8 s 39.7 → 4.8 s
the 13, before → harness, after (run 2) 13 → 1 41 → 41 36.0 → 2.8 s 2.7 → 1.8 s 38.7 → 4.6 s
the 13, before → harness, after (run 3, this head) 13 → 1 41 → 41 35.9 → 2.7 s 2.6 → 1.8 s 38.6 → 4.5 s

The family went from 439.7 s to 404.7 s of CPU on average (−8.0 %), wall from 230.6 s to 212.2 s (−8.0 %). Run 3 puts restoreAfterBlock()'s cost inside the run-to-run spread: the harness read 4.5 s against 4.8 / 4.6 s without it.

Acceptance notes

  • Why these 13. ListView's largest same-setup cluster is the pins that mock nothing: 113 of the 129 per-issue ListView.* pins in plugin-list/src/__tests__/, 23,229 lines, far past one PR under the 3,000-line bar (a fold changes about twice the lines it moves). ListView.test.tsx is the component's main suite, not a per-issue pin, and is not counted. Of the 113, this PR takes only files with none of the following, which restoreAfterBlock() does not undo: no statement at module scope or collection time other than imports, declarations and test calls (39 files have one), no vi.spyOn, vi.stubGlobal or vi.useFakeTimers (15), not on the dom-heavy list in ui/vitest.config.mts (2), no side-effect import other than @testing-library/jest-dom (4), and no other file naming it (49; some files fail more than one test). That leaves 31, of which two are .test.ts files in the unit project, which is not isolated per file and so has nothing to save. This PR folds the 13 smallest of the remaining 29, the most files per changed line: 1,375 lines moved make 2,729 changed lines, and the next smallest (140 lines) would take it past 3,000. Slice 2b folds the next ones that fit.
  • ListView's unfolded pins are listed (file, setup, lines) in slice 2b, the family's last PR, as amendment 2 asks.
  • The one ListView* pin in ui/packages/app-shell/src/__tests__/ is in another package, outside this harness.

Verification

At head f2ea524d, 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/: 179 files, 1,826 tests, all passed. That is 12 files fewer than main (13 removed, 1 added) and the same tests: the A/B's family legs ran 985 tests on both sides.
  • 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). eslint on the harness from ui/: exit 0, 0 errors, 26 warnings, the same 26 the 13 former files give at main; restoreAfterBlock() adds none.
  • Order: the harness passes 41 / 41 in declaration order and under --sequence.shuffle.tests with seeds 11, 222 and 3333, whose orders put registering blocks first. A probe that logged the registry (every key's renderer and meta), the localStorage object and its length, and <html lang> at each block's first test found every block starting from the post-import state.
  • Derived gates (node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, change set 14 paths against merge base e00bcc6a): 26 derived, 26 run under os-verify-lock (VERDICT command-exit 0), each exit 0 captured before any pipe; pnpm --dir ui exec vitest run scripts/ 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 14 files, +1,354 / -1,375, 2,729 changed lines. No open pull request touches any of the 14 paths: the head trees of all 58 open PRs, compared with main at those paths after test(plugin-gantt): fold four more ObjectGantt pins onto the shared harness #30745 merged, differ only for pm:mailbox — triage#1 · os-zhuang #28646, a mailbox PR whose file list is empty.
  • No ablation of a product behaviour: none changed, and the assertions moved verbatim. The helper's own effect is the probe reading above against the same probe without it (What changes).

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).

Generated by Claude Code

🤖 Generated with Claude Code

https://claude.ai/code/session_01L6bhgXNVxCQk1CCy55gFjQ

… one shared harness

Thirteen per-issue ListView pins render the real ListView with no vi.mock.
Each becomes a top-level describe in ListView.harness.test.tsx, named after
its former file, with its fixtures, hooks and assertions moved verbatim.
The five that register a stand-in renderer in ComponentRegistry sit after
the eight that register nothing. I18nProvider is imported once from
@object-ui/i18n, the binding @object-ui/react re-exports.

Each block opens with restoreAfterBlock(), which puts back after the
block's last test what a former file never handed on: ComponentRegistry
entries the block added, replaced or removed, a swapped-in localStorage,
the contents of both storages, and the lang / dir I18nProvider sets on
<html>. Without it three blocks left stand-in renderers registered or the
real object-grid unregistered, mutationRefresh left its localStorage
stand-in installed, and displayMenuClose's saved view preference reached
the four blocks after it. With it every block starts from the state its
former file started from, and the file passes in shuffled orders.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6bhgXNVxCQk1CCy55gFjQ
@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#1 · os-sales · session_01L6bhgWn6zhNa4LL7VB1rtL

维护者速读

  • 改了什么:列表组件 ListView 的 13 个「一张卡一个文件」、都不 mock 任何东西的测试,合并进一个共享测试文件 ListView.harness.test.tsx。原来每个文件变成里面的一个顶层 describe,断言一字未改;每块开头加一行 restoreAfterBlock(),块结束后把它改过的组件注册、localStorage 和页面语言属性放回原样。
  • 为什么改: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 共享 setup 推广到 ListView 族,这是该族第一片(席位修订 2:每族最多两个 PR,第二片列出没合并的 pin)。同机实测这 13 个文件的 CPU 从约 39 秒降到 4.5–4.8 秒,整个 ListView 测试族快约 8%。
  • 风险与回滚:只动测试文件,不改源码、门禁、CI,不发布任何东西。回滚即撤本 PR。
  • 先看:ui/packages/plugin-list/src/__tests__/ListView.harness.test.tsx 开头的说明和 restoreAfterBlock()。
  • 两分钟验证:cd ui && pnpm exec vitest run packages/plugin-list/src/__tests__/ListView.harness.test.tsx,预期 1 个文件 41 例全绿。
  • 需要你做的:无,席位落地。
  • 席位意见:可合。ListView 族还有第二片,之后是 ObjectView 和 RecordDetailView 两族,所以用 Part of。

席位核对 — ACCEPT · head f2ea524

  • 形态:14 文件 +1,354/−1,375 = 2,729 行。check-governed-merges --pr 30769:不在受管面,≤ 3000。Part of #29469,目标 main,标签 skip-changeset(只有测试)。删 13 个 pin 文件,新增 1 个 harness,没有旁改。在认领 6671543193 和修订 2(6672694520)的文件面内,源码、门禁、CI 和 .changeset/ 都没动。
  • 契约:Clause-② no,Metadata-protocol none,与认领一致。
  • 席位自己复数:从 base e00bcc6a 上的 13 个原文件和 head 上的 harness 各自计数:it( / test( 41 = 41,expect( 95 = 95;harness 13 个顶层 describe,每块开头一行 restoreAfterBlock();没有新增 .only、.skip 或 .todo。13 个旧文件名在 ui/ 下已无任何引用,test-shard-timings.json 里也没有它们的键。
  • 前提实测:dev 报告 6674656405。plugin-list 全包 179 个文件、1,826 例通过;type-check、lint exit 0;派生门禁 26/26 exit 0,--ran 26/26。dev 逐块探针发现合并后块之间会串状态(替身渲染器残留、真 object-grid 被注销、localStorage 替身一直挂着、一条视图偏好漏到后 4 块),加了 restoreAfterBlock() 后每块起点与原文件一致,3 个 shuffle 种子全过,CPU 开销在跑间误差内。没有做消融,因为没有改变任何被测行为。
  • 门禁 check-runs:head 上 60 个,44 success,16 skipped,0 红。mergeable_state clean(base b3098661c)。
  • 接受的偏差:断言原样搬过去,但每块多了一行 restoreAfterBlock(),不是逐字搬家。席位接受:它只把块改过的全局状态恢复成原文件开始时的样子,让每块仍在原来的起点上跑,不改变任何断言。CPU 读数用同机本地 A/B,CI 读数只有合并后 main 的全量跑才上传。
  • 范围外:ListView 族还没合并的 117 个文件(25,682 行、15 种 setup)按修订 2 列在该族第二片的正文里,本片不列。
  • 落地条款:中继 pr_ready + automerge_enable。卡不关(Part of),pm:dispatched 保留到最后一族的 Fixes PR 合并。合并后卡线程从最新 main 重开分支做 ListView 第二片。

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 6, 2026 02:01
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge November 6, 2026 02:01
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 6, 2026
Merged via the queue into main with commit 1371b9d Nov 6, 2026
61 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-3t7ug5 branch November 6, 2026 02:30
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