From 708d7859966e2f594351c3d96d33251e776a982f Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 3 Sep 2026 10:13:56 +0000 Subject: [PATCH 1/2] =?UTF-8?q?test(repo):=20delete=20the=20per-package=20?= =?UTF-8?q?vitest=20configs=20=E2=80=94=20one=20config,=20one=20verdict=20?= =?UTF-8?q?(#3240)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Direction A, ruled by the maintainer 2026-08-06 and reaffirmed by the delegated ruling of 2026-08-10: the root `vitest.config.mts` becomes the single entry, and a per-package run reaches it by path filter. What was there: 19 non-root vitest configs (the card's 17 under `packages/`, plus `apps/console` and `examples/schema-catalog`). Eleven of the seventeen were byte-identical standalone `defineConfig`s — `happy-dom` + `globals` + a one-line local setup and NO alias table, where the root config maps ~40 `@object-ui/*` specifiers at a sibling's `src/`. The other six re-exported or merged the root config, so they only ever restated it. What replaces them, per package: nothing. The root config already supplies every property they declared — `globals`, `happy-dom`, and jest-dom via `vitest.setup.dom-light.tsx` — plus the alias table they lacked. The one setup file that was not the shared one-liner, `plugin-map`'s, mocked `maplibre-gl`; `vitest.setup.base.ts` has mocked it globally all along, so that copy was a duplicate no canonical invocation ever loaded. Redefining the per-package run (the ruling's second clause): every `test` script is now `vitest run --root ../.. /`, the shape PR #3869 landed for `packages/runner`. `pnpm --filter test` and `turbo run test` were REFUSED by the invocation guard before this; they now run, against the same config CI uses, over exactly that package's files. Closing route 4 first, which the ruling did not anticipate: Vitest's config fallback does not stop at `vitest.config.*`. With none present it takes the directory's `vite.config.*`, and every `packages/*` has one — carrying a vestigial `test` block (`passWithNoTests: true`, a partial alias table, a setup the root config never loads). Deleting the 14 vitest configs whose packages also have a vite config would have moved them onto that route, widening the hole #5406 closed in the name of closing it. So each `packages//vite.config.ts` now calls the guard — gated on `process.env.VITEST`, which Vitest sets when it loads a config and `vite build` does not (measured both ways) — and the vestigial `test` blocks are deleted. turbo's `test` keeps `dependsOn: ["^build"]`, for a re-derived reason. The reason of record was resolution through `dist`, and that is now obsolete. But `^build` is the only edge putting a dependency's sources into a dependent's `test` cache key, and `helpers/vitest-config-program.ts` narrows its sweep by delegating to exactly that. Dropping it would replay stale greens; the note now says so. Evidence: `vitest list --filesOnly` from the repo root is byte-identical before and after — 2434 (project, file) pairs, empty diff. `vitest.config.mts` and all four root setup files are unchanged, so no file's project, environment or setup moved. #7291's `dist` project (`pnpm test:dist`: 1 file / 3 tests) and #7309's `isolate: false` invariant both still hold. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_019aCUUSwWefnbCJ4Xk1vqQW --- AGENTS.md | 44 ++-- QUICK_REFERENCE.md | 17 +- apps/console/package.json | 6 +- examples/schema-catalog/package.json | 2 +- examples/schema-catalog/vitest.config.ts | 14 - packages/app-shell/package.json | 2 +- packages/auth/package.json | 2 +- packages/cli/package.json | 2 +- packages/collaboration/package.json | 2 +- packages/components/package.json | 2 +- packages/components/vite.config.ts | 26 +- packages/components/vitest.config.ts | 5 - packages/core/package.json | 2 +- packages/core/vitest.config.ts | 2 - packages/create-plugin/package.json | 2 +- packages/data-objectstack/package.json | 2 +- packages/fields/package.json | 2 +- packages/fields/vite.config.ts | 22 +- packages/fields/vitest.config.ts | 5 - packages/i18n/package.json | 2 +- packages/layout/vite.config.ts | 19 +- packages/mobile/package.json | 2 +- packages/permissions/package.json | 2 +- packages/plugin-ai/package.json | 2 +- packages/plugin-ai/vite.config.ts | 21 +- packages/plugin-ai/vitest.setup.ts | 7 - packages/plugin-calendar/package.json | 4 +- .../plugin-calendar/src/registration.test.tsx | 17 +- packages/plugin-calendar/vite.config.ts | 22 +- packages/plugin-calendar/vitest.config.ts | 34 --- packages/plugin-calendar/vitest.setup.ts | 1 - packages/plugin-charts/package.json | 4 +- packages/plugin-charts/vite.config.ts | 22 +- packages/plugin-charts/vitest.config.ts | 34 --- packages/plugin-charts/vitest.setup.ts | 1 - packages/plugin-chatbot/package.json | 4 +- packages/plugin-chatbot/vite.config.ts | 21 +- packages/plugin-dashboard/package.json | 2 +- packages/plugin-dashboard/vite.config.ts | 22 +- packages/plugin-dashboard/vitest.config.ts | 9 - packages/plugin-dashboard/vitest.setup.tsx | 18 -- packages/plugin-designer/package.json | 2 +- packages/plugin-designer/vite.config.ts | 21 +- packages/plugin-designer/vitest.setup.ts | 26 -- packages/plugin-detail/package.json | 4 +- packages/plugin-detail/vite.config.ts | 27 +- packages/plugin-detail/vitest.config.ts | 34 --- packages/plugin-detail/vitest.setup.ts | 1 - packages/plugin-editor/package.json | 4 +- packages/plugin-editor/vite.config.ts | 22 +- packages/plugin-form/package.json | 2 +- packages/plugin-form/vite.config.ts | 22 +- packages/plugin-form/vitest.config.ts | 34 --- packages/plugin-form/vitest.setup.ts | 1 - packages/plugin-gantt/package.json | 4 +- packages/plugin-gantt/vite.config.ts | 19 +- packages/plugin-gantt/vitest.config.ts | 34 --- packages/plugin-gantt/vitest.setup.ts | 1 - packages/plugin-grid/package.json | 2 +- packages/plugin-grid/vite.config.ts | 22 +- packages/plugin-grid/vitest.config.ts | 34 --- packages/plugin-grid/vitest.setup.ts | 1 - packages/plugin-kanban/package.json | 4 +- packages/plugin-kanban/vite.config.ts | 22 +- packages/plugin-kanban/vitest.config.ts | 34 --- packages/plugin-kanban/vitest.setup.ts | 1 - packages/plugin-list/package.json | 4 +- packages/plugin-list/vite.config.ts | 27 +- packages/plugin-list/vitest.config.ts | 34 --- packages/plugin-list/vitest.setup.ts | 1 - packages/plugin-map/package.json | 4 +- .../src/index.registration.test.tsx | 5 +- packages/plugin-map/vite.config.ts | 19 +- packages/plugin-map/vitest.config.ts | 34 --- packages/plugin-map/vitest.setup.ts | 78 ------ packages/plugin-markdown/package.json | 4 +- packages/plugin-markdown/vite.config.ts | 22 +- packages/plugin-report/package.json | 2 +- packages/plugin-report/vite.config.ts | 21 +- packages/plugin-report/vitest.setup.ts | 7 - packages/plugin-timeline/package.json | 4 +- .../__tests__/timeline-date-locale.test.tsx | 6 +- packages/plugin-timeline/vite.config.ts | 22 +- packages/plugin-timeline/vitest.config.ts | 34 --- packages/plugin-timeline/vitest.setup.ts | 1 - packages/plugin-tree/package.json | 4 +- packages/plugin-tree/vite.config.ts | 20 +- packages/plugin-view/package.json | 2 +- packages/plugin-view/vite.config.ts | 19 +- packages/plugin-view/vitest.config.ts | 34 --- packages/plugin-view/vitest.setup.ts | 1 - packages/providers/package.json | 2 +- packages/react-runtime/package.json | 2 +- packages/react/package.json | 2 +- packages/react/vitest.config.ts | 2 - packages/runner/vite.config.ts | 16 ++ packages/sdui-parser/package.json | 2 +- packages/types/vitest.config.ts | 2 - packages/vscode-extension/package.json | 4 +- .../helpers/vitest-config-program.ts | 62 ++++- scripts/__tests__/turbo-test-inputs.test.ts | 55 +++- .../__tests__/vitest-invocation-guard.test.ts | 242 +++++++++++++----- scripts/check-vi-mock-specifiers.mjs | 14 +- scripts/vitest-invocation-guard.mjs | 102 ++++---- turbo.json | 3 +- 105 files changed, 824 insertions(+), 904 deletions(-) delete mode 100644 examples/schema-catalog/vitest.config.ts delete mode 100644 packages/components/vitest.config.ts delete mode 100644 packages/core/vitest.config.ts delete mode 100644 packages/fields/vitest.config.ts delete mode 100644 packages/plugin-ai/vitest.setup.ts delete mode 100644 packages/plugin-calendar/vitest.config.ts delete mode 100644 packages/plugin-calendar/vitest.setup.ts delete mode 100644 packages/plugin-charts/vitest.config.ts delete mode 100644 packages/plugin-charts/vitest.setup.ts delete mode 100644 packages/plugin-dashboard/vitest.config.ts delete mode 100644 packages/plugin-dashboard/vitest.setup.tsx delete mode 100644 packages/plugin-designer/vitest.setup.ts delete mode 100644 packages/plugin-detail/vitest.config.ts delete mode 100644 packages/plugin-detail/vitest.setup.ts delete mode 100644 packages/plugin-form/vitest.config.ts delete mode 100644 packages/plugin-form/vitest.setup.ts delete mode 100644 packages/plugin-gantt/vitest.config.ts delete mode 100644 packages/plugin-gantt/vitest.setup.ts delete mode 100644 packages/plugin-grid/vitest.config.ts delete mode 100644 packages/plugin-grid/vitest.setup.ts delete mode 100644 packages/plugin-kanban/vitest.config.ts delete mode 100644 packages/plugin-kanban/vitest.setup.ts delete mode 100644 packages/plugin-list/vitest.config.ts delete mode 100644 packages/plugin-list/vitest.setup.ts delete mode 100644 packages/plugin-map/vitest.config.ts delete mode 100644 packages/plugin-map/vitest.setup.ts delete mode 100644 packages/plugin-report/vitest.setup.ts delete mode 100644 packages/plugin-timeline/vitest.config.ts delete mode 100644 packages/plugin-timeline/vitest.setup.ts delete mode 100644 packages/plugin-view/vitest.config.ts delete mode 100644 packages/plugin-view/vitest.setup.ts delete mode 100644 packages/react/vitest.config.ts delete mode 100644 packages/types/vitest.config.ts diff --git a/AGENTS.md b/AGENTS.md index 5819c0e4ff..a6a892c2de 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -172,11 +172,14 @@ pnpm exec vitest run packages// # 只跑一个包 pnpm test # 全量(CI 就是它,可加 --shard=1/4) ``` -AGENTS.md 的「只跑受影响的包」指的是**用上面的路径过滤缩小范围**,不是 `cd` 进包里、也不是 -`pnpm --filter test` —— 那两条恰好就是下面的陷阱。 - -- **陷阱一:让 vitest 的 cwd 落在包目录里(objectui#3378)。** `pnpm --filter test`、 - `turbo run test`、`cd packages/x && pnpm exec vitest` 都属于这类。vitest 把 root 定成该 +AGENTS.md 的「只跑受影响的包」指的是**用上面的路径过滤缩小范围**,不是 `cd` 进包里。 +`pnpm --filter test` 与 `turbo run test` **现在是安全的**(objectui#3240):每个包的 +`test` 脚本都改成了显式指回仓根的 `vitest run --root ../.. packages//`,跑的就是仓根 +那一份配置、和 CI 同一个结论;它们只是比上面的写法多绕一层。 + +- **陷阱一:让 vitest 的 cwd 落在包目录里(objectui#3378)。** 今天只剩 + `cd packages/x && pnpm exec vitest` 这一种写法(改造前 `pnpm --filter test` 和 + `turbo run test` 也在其中)。vitest 把 root 定成该 目录,根级 projects(`unit`/`dom`/`dom-heavy`)的 include(`packages/**`、`examples/**`、 `scripts/**`)相对它匹配不到任何文件;只有以**绝对路径**引入的 `apps/console` project 仍解析 成功。于是跑的是 `@object-ui/console` 的 22 个文件、报 `Test Files 22 passed (22)`,而本包 @@ -189,19 +192,30 @@ AGENTS.md 的「只跑受影响的包」指的是**用上面的路径过滤缩 - **两条现在都会直接失败**,由 `scripts/vitest-invocation-guard.mjs` 拦下:vitest root 不是仓根 → 拒绝;`--` 后面还有参数 → 拒绝。报错正文会指出机制并给出上面的正确命令。包级 `test` 脚本的 存废是 objectui#3240;在那之前它们只失败,不撒谎。 - - **拦截点不止 `vitest.config.mts` 一处**(objectui#5406)。vitest 只加载「启动目录里的那份」 - config,所以根 config 顶部那一次调用,只覆盖得到「本包没有 config(向上找到根 config)」或 - 「本包 config import 了根 config(import 即执行其模块作用域)」这两条路。11 个**独立**的 - `packages/plugin-*/vitest.config.ts` 两条都不占——它们自带 `happy-dom` + `globals` + 本地 - setup 且**完全没有 alias 表**,于是从包目录跑就用上了一份 CI 从不使用的 config,而 guard - 根本没被 import。实测:`cd packages/plugin-grid && pnpm exec vitest run - src/__tests__/ObjectGrid.exportOptionsKeys.test.ts` 曾经报 `Test Files 1 passed (1)` / - `Tests 5 passed (5)` 并以 0 退出。这 11 份现在各自调用 guard;新增任何一份 `vitest.config.*` - 若两条路都不占,`scripts/__tests__/vitest-invocation-guard.test.ts` 会红。 + - **拦截点不止 `vitest.config.mts` 一处**(objectui#5406 / objectui#3240)。vitest 只加载 + 「启动目录里的那份」config,所以根 config 顶部那一次调用,只覆盖得到「本目录没有任何 + config(向上找到根 config)」或「本目录 config import 了根 config」这两条路。 + - #5406 关的是第三条:11 个**独立**的 `packages/plugin-*/vitest.config.ts` 两条都不占, + 它们自带 `happy-dom` + `globals` + 本地 setup 且**完全没有 alias 表**,于是从包目录跑 + 就用上了一份 CI 从不使用的 config。 + - ⭐ #3240 删掉那 17 份包级 config 之前,必须先关**第四条**:vitest 的回退不止于 + `vitest.config.*` —— 没有它就用同目录的 `vite.config.*`,而 `packages/*` 每个都有一份。 + 实测 `cd packages/plugin-ai && pnpm exec vitest run` 报 `RUN v4.1.10 /…/packages/plugin-ai`, + guard 一声不吭。所以每份 `packages//vite.config.ts` 现在也调用 guard,调用**以 + `process.env.VITEST` 为门**(实测:vitest 读 config 时它是 `"true"`,`vite build` 读同 + 一个文件时是 `undefined`)—— 测试跑被拒,构建永远不被拒;那些 `vite.config` 里残留的 + `test` 块(`passWithNoTests: true` + 一份根 config 从不加载的 setup)也一并删掉了。 + 新增任何一份 `vitest.config.*` / `packages/*/vite.config.*` 若不占其中一条路, + `scripts/__tests__/vitest-invocation-guard.test.ts` 会红。 - **路径过滤零匹配也不再是绿的**:一旦命令行点名了文件,`passWithNoTests` 自动关闭 —— 写错的路径 / 相对错目录的路径 → 非零退出,而不是「跑了 0 个文件然后绿」。 -- 确需从包目录启动,把 root 显式指回仓根:`pnpm exec vitest run --root ../.. packages//`。 +- 确需从包目录启动,把 root 显式指回仓根:`pnpm exec vitest run --root ../.. packages//` + —— 这正是 objectui#3240 给每个包级 `test` 脚本定下的写法。 真要临时绕过 guard(自担风险):`OBJECTUI_VITEST_GUARD=off`。 +- **包级测试配置只有一份,就是仓根那份。** objectui#3240 删掉了 17 个包 + + `examples/schema-catalog` 的 `vitest.config.*`(维护者 2026-08-06 裁决 A);某个包确实需要 + 不同的 environment / setup / include,就在 `vitest.config.mts` 的 `projects` 里**加一个 + project**,不要在包里新开一份 config —— 一份 config 一个结论,是这条裁决的全部内容。 ### 测试纪律(flaky 测试:先找竞态,别调超时) diff --git a/QUICK_REFERENCE.md b/QUICK_REFERENCE.md index 4097a911b4..1454601a3a 100644 --- a/QUICK_REFERENCE.md +++ b/QUICK_REFERENCE.md @@ -32,15 +32,18 @@ pnpm exec vitest run apps/console/ # Run just the console te pnpm test:e2e # End-to-end tests (playwright) ``` -Not `pnpm --filter test`, not `turbo run test`, not `cd packages/x && pnpm exec -vitest`, and never a path behind `--`. Each of those moved vitest's root into a package, +`pnpm --filter test` and `turbo run test` are fine — objectui#3240 rewrote every +package's `test` script to name the repo root (`vitest run --root ../.. packages//`), +so they run the same single config CI does. What is still wrong is `cd packages/x && pnpm +exec vitest`, and a path behind `--`. Each of those moves vitest's root into a package, where the root `unit`/`dom`/`dom-heavy` projects match nothing and only `apps/console` resolves: 22 foreign files passed, your package never ran, output green -(objectui#3378/#3288). A guard now **exits non-zero** on all of them and prints the -correct invocation. It is wired into `vitest.config.mts` *and* into each of the 11 -standalone per-package configs (`packages/plugin-grid/vitest.config.ts` and its ten -siblings), because Vitest loads the config in the directory it was launched from — the -root call alone left those 11 uncovered (objectui#5406): +(objectui#3378/#3288). A guard **exits non-zero** on them and prints the correct +invocation. Vitest loads the config in the directory it was launched from, so the guard is +wired into every file that can be picked up as one: `vitest.config.mts` (the repo's only +vitest config since objectui#3240), `apps/console/vitest.config.ts`, and each +`packages//vite.config.ts` — the build config Vitest falls back to now that no +package carries a vitest config of its own (objectui#5406 / #3240): ``` vitest 调用被拒绝:从包目录跑 vitest 会静默跑错测试集 (objectui#3378) diff --git a/apps/console/package.json b/apps/console/package.json index 0d050ae94a..b8ecddabda 100644 --- a/apps/console/package.json +++ b/apps/console/package.json @@ -49,9 +49,9 @@ "lint": "eslint .", "build:analyze": "pnpm build && echo 'Bundle analysis available at dist/stats.html'", "preview": "vite preview", - "test": "vitest run", - "test:watch": "vitest", - "test:ui": "vitest --ui", + "test": "vitest run --root ../.. apps/console/", + "test:watch": "vitest --root ../.. apps/console/", + "test:ui": "vitest --ui --root ../.. apps/console/", "prepublishOnly": "pnpm build" }, "devDependencies": { diff --git a/examples/schema-catalog/package.json b/examples/schema-catalog/package.json index c7fb185842..5556e52300 100644 --- a/examples/schema-catalog/package.json +++ b/examples/schema-catalog/package.json @@ -22,7 +22,7 @@ "clean": "rm -rf dist", "regenerate": "python3 ../../scripts/regenerate-catalog-index.py", "regenerate:check": "python3 ../../scripts/regenerate-catalog-index.py --check", - "test": "vitest run", + "test": "vitest run --root ../.. examples/schema-catalog/", "type-check": "tsc --noEmit && tsc -p tsconfig.test.json", "lint": "eslint ." }, diff --git a/examples/schema-catalog/vitest.config.ts b/examples/schema-catalog/vitest.config.ts deleted file mode 100644 index 3c525774c9..0000000000 --- a/examples/schema-catalog/vitest.config.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { defineConfig } from 'vitest/config'; -import rootConfig from '../../vitest.config.mts'; - -// Inherit aliases/env from the root config, but build a *fresh* test config -// without the `projects` array — that array references the root file by a -// relative path that breaks when vitest is invoked from this package's cwd -// (`pnpm -F ... test`). Tests in this folder are also discovered by the root -// project, so running from the monorepo root works either way. -const { projects: _omit, ...rootTest } = rootConfig.test ?? {}; - -export default defineConfig({ - ...rootConfig, - test: rootTest, -}); diff --git a/packages/app-shell/package.json b/packages/app-shell/package.json index 83c3eb9cc3..040a7c5c90 100644 --- a/packages/app-shell/package.json +++ b/packages/app-shell/package.json @@ -59,7 +59,7 @@ }, "scripts": { "build": "tsc && node ../../scripts/check-dist-completeness.mjs", - "test": "vitest run", + "test": "vitest run --root ../.. packages/app-shell/", "type-check": "tsc --noEmit && tsc -p tsconfig.test.json", "lint": "eslint ." }, diff --git a/packages/auth/package.json b/packages/auth/package.json index 0f93225ffe..a646c8ac1d 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -29,7 +29,7 @@ "scripts": { "build": "tsc && node ../../scripts/check-dist-completeness.mjs", "clean": "rm -rf dist tsconfig.tsbuildinfo", - "test": "vitest run", + "test": "vitest run --root ../.. packages/auth/", "type-check": "tsc --noEmit && tsc -p tsconfig.test.json", "lint": "eslint ." }, diff --git a/packages/cli/package.json b/packages/cli/package.json index c81b4d520d..181a4f5de3 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -32,7 +32,7 @@ "build": "tsup", "dev": "tsup --watch", "lint": "eslint src", - "test": "vitest run", + "test": "vitest run --root ../.. packages/cli/", "type-check": "tsc --noEmit" }, "keywords": [ diff --git a/packages/collaboration/package.json b/packages/collaboration/package.json index 65b547f94e..cbc2a66063 100644 --- a/packages/collaboration/package.json +++ b/packages/collaboration/package.json @@ -29,7 +29,7 @@ "scripts": { "build": "tsc && node ../../scripts/check-dist-completeness.mjs", "clean": "rm -rf dist tsconfig.tsbuildinfo", - "test": "vitest run", + "test": "vitest run --root ../.. packages/collaboration/", "type-check": "tsc --noEmit && tsc -p tsconfig.test.json", "lint": "eslint ." }, diff --git a/packages/components/package.json b/packages/components/package.json index 5ad8847699..7c929de5c5 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -29,7 +29,7 @@ "build": "vite build && node scripts/build-css.mjs", "prebuild": "pnpm --filter @object-ui/types build && pnpm --filter @object-ui/core build && pnpm --filter @object-ui/react build", "pretest": "pnpm run prebuild", - "test": "vitest run", + "test": "vitest run --root ../.. packages/components/", "test:dist": "OBJECTUI_DIST_PINS=1 vitest run --root ../.. --config vitest.config.mts --project dist", "type-check": "tsc --noEmit && tsc -p tsconfig.test.json", "lint": "eslint ." diff --git a/packages/components/vite.config.ts b/packages/components/vite.config.ts index 8b1d44541c..c7ee628f7a 100644 --- a/packages/components/vite.config.ts +++ b/packages/components/vite.config.ts @@ -14,6 +14,22 @@ import { resolve } from 'path'; import { createDtsExplicitExtensions } from '../../scripts/vite-dts-explicit-extensions.ts'; import { createDtsFailOnTypeErrors } from '../../scripts/vite-dts-fail-on-type-errors.ts'; +// objectui#3240 — with the per-package `vitest.config.*` files gone, Vitest +// launched from THIS directory falls back to this file and uses it as its test +// config. Guard that entry point (route 4 in +// scripts/vitest-invocation-guard.mjs, which carries the mechanism and the +// measurement). Gated on `VITEST` because the same file is the BUILD config: +// Vitest sets that variable when it loads a config, `vite build` does not, so +// a test run is refused and a build never is. +import { + assertCanonicalVitestInvocation, + repoRootFrom, +} from '../../scripts/vitest-invocation-guard.mjs'; + +if (process.env.VITEST) { + assertCanonicalVitestInvocation({ repoRoot: repoRootFrom(import.meta.url) }); +} + export default defineConfig({ plugins: [ react(), @@ -80,14 +96,4 @@ export default defineConfig({ }, }, }, - test: { - globals: true, - environment: 'happy-dom', - setupFiles: ['../../vitest.setup.tsx'], - passWithNoTests: true, - // Ensure dependencies are resolved properly for tests - deps: { - inline: ['@object-ui/core', '@object-ui/react'], - }, - }, }); diff --git a/packages/components/vitest.config.ts b/packages/components/vitest.config.ts deleted file mode 100644 index 482009e047..0000000000 --- a/packages/components/vitest.config.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { mergeConfig } from 'vitest/config'; -import rootConfig from '../../vitest.config.mts'; -import viteConfig from './vite.config'; - -export default mergeConfig(rootConfig, viteConfig); diff --git a/packages/core/package.json b/packages/core/package.json index a67119bbd9..79fda7594c 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -26,7 +26,7 @@ }, "scripts": { "build": "tsc && node ../../scripts/check-dist-completeness.mjs", - "test": "vitest run", + "test": "vitest run --root ../.. packages/core/", "type-check": "tsc --noEmit && tsc -p tsconfig.test.json", "lint": "eslint ." }, diff --git a/packages/core/vitest.config.ts b/packages/core/vitest.config.ts deleted file mode 100644 index 2bec67a5d9..0000000000 --- a/packages/core/vitest.config.ts +++ /dev/null @@ -1,2 +0,0 @@ -import rootConfig from '../../vitest.config.mts'; -export default rootConfig; diff --git a/packages/create-plugin/package.json b/packages/create-plugin/package.json index 38fbec8df3..4271b6a799 100644 --- a/packages/create-plugin/package.json +++ b/packages/create-plugin/package.json @@ -17,7 +17,7 @@ "build": "tsup", "type-check": "tsc --noEmit && tsc -p tsconfig.test.json", "dev": "tsup --watch", - "test": "vitest run", + "test": "vitest run --root ../.. packages/create-plugin/", "lint": "eslint ." }, "dependencies": { diff --git a/packages/data-objectstack/package.json b/packages/data-objectstack/package.json index 1a2fd364cf..ddbfd882c7 100644 --- a/packages/data-objectstack/package.json +++ b/packages/data-objectstack/package.json @@ -25,7 +25,7 @@ "dev": "tsup --watch", "clean": "rm -rf dist", "type-check": "tsc --noEmit", - "test": "vitest run", + "test": "vitest run --root ../.. packages/data-objectstack/", "lint": "eslint ." }, "dependencies": { diff --git a/packages/fields/package.json b/packages/fields/package.json index 5e442f18c7..ff47673e33 100644 --- a/packages/fields/package.json +++ b/packages/fields/package.json @@ -26,7 +26,7 @@ "build": "tsc && vite build && node scripts/build-css.mjs", "clean": "rm -rf dist", "type-check": "tsc --noEmit && tsc -p tsconfig.test.json", - "test": "vitest run", + "test": "vitest run --root ../.. packages/fields/", "lint": "eslint ." }, "dependencies": { diff --git a/packages/fields/vite.config.ts b/packages/fields/vite.config.ts index cd2a088fc7..a1a51bb1c5 100644 --- a/packages/fields/vite.config.ts +++ b/packages/fields/vite.config.ts @@ -5,6 +5,22 @@ import path from 'path'; import { createDtsExplicitExtensions } from '../../scripts/vite-dts-explicit-extensions.ts'; +// objectui#3240 — with the per-package `vitest.config.*` files gone, Vitest +// launched from THIS directory falls back to this file and uses it as its test +// config. Guard that entry point (route 4 in +// scripts/vitest-invocation-guard.mjs, which carries the mechanism and the +// measurement). Gated on `VITEST` because the same file is the BUILD config: +// Vitest sets that variable when it loads a config, `vite build` does not, so +// a test run is refused and a build never is. +import { + assertCanonicalVitestInvocation, + repoRootFrom, +} from '../../scripts/vitest-invocation-guard.mjs'; + +if (process.env.VITEST) { + assertCanonicalVitestInvocation({ repoRoot: repoRootFrom(import.meta.url) }); +} + export default defineConfig({ plugins: [ react(), @@ -64,10 +80,4 @@ export default defineConfig({ }, }, }, - test: { - globals: true, - environment: 'jsdom', - setupFiles: ['../../vitest.setup.tsx'], - passWithNoTests: true, - }, }); diff --git a/packages/fields/vitest.config.ts b/packages/fields/vitest.config.ts deleted file mode 100644 index 482009e047..0000000000 --- a/packages/fields/vitest.config.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { mergeConfig } from 'vitest/config'; -import rootConfig from '../../vitest.config.mts'; -import viteConfig from './vite.config'; - -export default mergeConfig(rootConfig, viteConfig); diff --git a/packages/i18n/package.json b/packages/i18n/package.json index 897f81b9c4..36d0db90bf 100644 --- a/packages/i18n/package.json +++ b/packages/i18n/package.json @@ -31,7 +31,7 @@ }, "scripts": { "build": "tsc && node ../../scripts/check-dist-completeness.mjs", - "test": "vitest run", + "test": "vitest run --root ../.. packages/i18n/", "type-check": "tsc --noEmit && tsc -p tsconfig.test.json", "lint": "eslint ." }, diff --git a/packages/layout/vite.config.ts b/packages/layout/vite.config.ts index ad56a26b97..0a1725a52a 100644 --- a/packages/layout/vite.config.ts +++ b/packages/layout/vite.config.ts @@ -6,6 +6,22 @@ import { resolve } from 'path'; import { createDtsExplicitExtensions } from '../../scripts/vite-dts-explicit-extensions.ts'; import { createDtsFailOnTypeErrors } from '../../scripts/vite-dts-fail-on-type-errors.ts'; +// objectui#3240 — with the per-package `vitest.config.*` files gone, Vitest +// launched from THIS directory falls back to this file and uses it as its test +// config. Guard that entry point (route 4 in +// scripts/vitest-invocation-guard.mjs, which carries the mechanism and the +// measurement). Gated on `VITEST` because the same file is the BUILD config: +// Vitest sets that variable when it loads a config, `vite build` does not, so +// a test run is refused and a build never is. +import { + assertCanonicalVitestInvocation, + repoRootFrom, +} from '../../scripts/vitest-invocation-guard.mjs'; + +if (process.env.VITEST) { + assertCanonicalVitestInvocation({ repoRoot: repoRootFrom(import.meta.url) }); +} + export default defineConfig({ plugins: [ react(), @@ -50,7 +66,4 @@ export default defineConfig({ }, }, }, - test: { - passWithNoTests: true, - }, }); diff --git a/packages/mobile/package.json b/packages/mobile/package.json index 8a1184bfdf..779c8fa368 100644 --- a/packages/mobile/package.json +++ b/packages/mobile/package.json @@ -29,7 +29,7 @@ "scripts": { "build": "tsc && node ../../scripts/check-dist-completeness.mjs", "clean": "rm -rf dist tsconfig.tsbuildinfo", - "test": "vitest run", + "test": "vitest run --root ../.. packages/mobile/", "type-check": "tsc --noEmit && tsc -p tsconfig.test.json", "lint": "eslint ." }, diff --git a/packages/permissions/package.json b/packages/permissions/package.json index 7c16550797..0cf8d326da 100644 --- a/packages/permissions/package.json +++ b/packages/permissions/package.json @@ -29,7 +29,7 @@ "scripts": { "build": "tsc && node ../../scripts/check-dist-completeness.mjs", "clean": "rm -rf dist tsconfig.tsbuildinfo", - "test": "vitest run", + "test": "vitest run --root ../.. packages/permissions/", "type-check": "tsc --noEmit && tsc -p tsconfig.test.json", "lint": "eslint ." }, diff --git a/packages/plugin-ai/package.json b/packages/plugin-ai/package.json index c925294af4..491e75fa0f 100644 --- a/packages/plugin-ai/package.json +++ b/packages/plugin-ai/package.json @@ -22,7 +22,7 @@ "build": "vite build", "type-check": "tsc --noEmit", "clean": "rm -rf dist", - "test": "vitest run --passWithNoTests", + "test": "vitest run --passWithNoTests --root ../.. packages/plugin-ai/", "lint": "eslint ." }, "peerDependencies": { diff --git a/packages/plugin-ai/vite.config.ts b/packages/plugin-ai/vite.config.ts index 36e6907d68..73226efa2c 100644 --- a/packages/plugin-ai/vite.config.ts +++ b/packages/plugin-ai/vite.config.ts @@ -6,6 +6,22 @@ import { resolve } from 'path'; import { createDtsExplicitExtensions } from '../../scripts/vite-dts-explicit-extensions.ts'; import { createDtsFailOnTypeErrors } from '../../scripts/vite-dts-fail-on-type-errors.ts'; +// objectui#3240 — with the per-package `vitest.config.*` files gone, Vitest +// launched from THIS directory falls back to this file and uses it as its test +// config. Guard that entry point (route 4 in +// scripts/vitest-invocation-guard.mjs, which carries the mechanism and the +// measurement). Gated on `VITEST` because the same file is the BUILD config: +// Vitest sets that variable when it loads a config, `vite build` does not, so +// a test run is refused and a build never is. +import { + assertCanonicalVitestInvocation, + repoRootFrom, +} from '../../scripts/vitest-invocation-guard.mjs'; + +if (process.env.VITEST) { + assertCanonicalVitestInvocation({ repoRoot: repoRootFrom(import.meta.url) }); +} + export default defineConfig({ plugins: [ react(), @@ -59,9 +75,4 @@ export default defineConfig({ }, }, }, - test: { - globals: true, - environment: 'jsdom', - setupFiles: './vitest.setup.ts', - }, }); diff --git a/packages/plugin-ai/vitest.setup.ts b/packages/plugin-ai/vitest.setup.ts deleted file mode 100644 index 9347c17d21..0000000000 --- a/packages/plugin-ai/vitest.setup.ts +++ /dev/null @@ -1,7 +0,0 @@ -import '@testing-library/jest-dom'; -import { cleanup } from '@testing-library/react'; -import { afterEach } from 'vitest'; - -afterEach(() => { - cleanup(); -}); diff --git a/packages/plugin-calendar/package.json b/packages/plugin-calendar/package.json index 2bedaea496..5a9350affa 100644 --- a/packages/plugin-calendar/package.json +++ b/packages/plugin-calendar/package.json @@ -25,8 +25,8 @@ }, "scripts": { "build": "vite build", - "test": "vitest run", - "test:watch": "vitest", + "test": "vitest run --root ../.. packages/plugin-calendar/", + "test:watch": "vitest --root ../.. packages/plugin-calendar/", "type-check": "tsc --noEmit && tsc -p tsconfig.test.json", "lint": "eslint ." }, diff --git a/packages/plugin-calendar/src/registration.test.tsx b/packages/plugin-calendar/src/registration.test.tsx index 6bad173462..e00765d280 100644 --- a/packages/plugin-calendar/src/registration.test.tsx +++ b/packages/plugin-calendar/src/registration.test.tsx @@ -18,11 +18,18 @@ import { ObjectCalendarRenderer } from './index'; // `subscribeDataChanges` from `@object-ui/react` — against the REAL module // before the mock applies. Green, by accident. // - `packages/plugin-calendar/vitest.config.ts` (what `pnpm --filter … test` -// and `turbo run test` run): no such setup, so `@object-ui/components` is -// first evaluated inside the mocked graph. Vitest 4 hard-errors on a -// missing export instead of silently yielding `undefined`, so the suite -// failed to load at all: `No "subscribeDataChanges" export is defined on -// the "@object-ui/react" mock`. +// and `turbo run test` ran AT THE TIME): no such setup, so +// `@object-ui/components` is first evaluated inside the mocked graph. +// Vitest 4 hard-errors on a missing export instead of silently yielding +// `undefined`, so the suite failed to load at all: `No "subscribeDataChanges" +// export is defined on the "@object-ui/react" mock`. +// +// That second config no longer exists — objectui#3240 deleted all 17 of them and +// made the root config the single entry, so both commands now run the FIRST bullet. +// The history is kept because it is the measurement the fix below was derived from, +// and because the fix outlives it: unification removes the disagreement between two +// configs, not the fragility of a whole-module mock, which would still break the +// moment `@object-ui/react` gains an export this file did not list. // // Spreading `importOriginal()` removes the sensitivity: the mock is a superset // of the real module under either resolution, so a transitive consumer can diff --git a/packages/plugin-calendar/vite.config.ts b/packages/plugin-calendar/vite.config.ts index 590cf27827..938d6f7345 100644 --- a/packages/plugin-calendar/vite.config.ts +++ b/packages/plugin-calendar/vite.config.ts @@ -14,13 +14,23 @@ import { resolve } from 'path'; import { createDtsExplicitExtensions } from '../../scripts/vite-dts-explicit-extensions.ts'; import { createDtsFailOnTypeErrors } from '../../scripts/vite-dts-fail-on-type-errors.ts'; +// objectui#3240 — with the per-package `vitest.config.*` files gone, Vitest +// launched from THIS directory falls back to this file and uses it as its test +// config. Guard that entry point (route 4 in +// scripts/vitest-invocation-guard.mjs, which carries the mechanism and the +// measurement). Gated on `VITEST` because the same file is the BUILD config: +// Vitest sets that variable when it loads a config, `vite build` does not, so +// a test run is refused and a build never is. +import { + assertCanonicalVitestInvocation, + repoRootFrom, +} from '../../scripts/vitest-invocation-guard.mjs'; + +if (process.env.VITEST) { + assertCanonicalVitestInvocation({ repoRoot: repoRootFrom(import.meta.url) }); +} + export default defineConfig({ - test: { - globals: true, - environment: 'jsdom', - setupFiles: ['./test/setup.ts'], - css: true, - }, plugins: [ react(), dts({ diff --git a/packages/plugin-calendar/vitest.config.ts b/packages/plugin-calendar/vitest.config.ts deleted file mode 100644 index 28840604c4..0000000000 --- a/packages/plugin-calendar/vitest.config.ts +++ /dev/null @@ -1,34 +0,0 @@ -/// -import { defineConfig } from 'vite'; -import react from '@vitejs/plugin-react'; - -// objectui#5406 — this config is STANDALONE: it does not import the root -// `vitest.config.mts`, so nothing routed a package-cwd run here through the -// invocation guard, the way a root-importing config does as a side effect of -// the import. Without the call below, `cd` into this package and `pnpm exec -// vitest run` executes the suite under a config CI never uses — no -// `@object-ui/*` alias table, a different setup file, no project split — so -// its green says nothing about CI. (Measured on the sibling `plugin-grid`, -// whose config was identical to this one: one such run printed -// `Test Files 1 passed (1)` / `Tests 5 passed (5)` and exited 0.) -// -// Deleting this reopens the hole for this package alone, silently — which is -// why it is enforced rather than written down: -// `scripts/__tests__/vitest-invocation-guard.test.ts` walks every -// `vitest.config.*` in the repo and fails on any that neither imports the root -// config nor calls the guard. -import { - assertCanonicalVitestInvocation, - repoRootFrom, -} from '../../scripts/vitest-invocation-guard.mjs'; - -assertCanonicalVitestInvocation({ repoRoot: repoRootFrom(import.meta.url) }); - -export default defineConfig({ - plugins: [react()], - test: { - environment: 'happy-dom', - globals: true, - setupFiles: ['./vitest.setup.ts'], - }, -}); diff --git a/packages/plugin-calendar/vitest.setup.ts b/packages/plugin-calendar/vitest.setup.ts deleted file mode 100644 index 7b0828bfa8..0000000000 --- a/packages/plugin-calendar/vitest.setup.ts +++ /dev/null @@ -1 +0,0 @@ -import '@testing-library/jest-dom'; diff --git a/packages/plugin-charts/package.json b/packages/plugin-charts/package.json index eaaed55d32..a8dbb53a00 100644 --- a/packages/plugin-charts/package.json +++ b/packages/plugin-charts/package.json @@ -25,8 +25,8 @@ }, "scripts": { "build": "vite build", - "test": "vitest run", - "test:watch": "vitest", + "test": "vitest run --root ../.. packages/plugin-charts/", + "test:watch": "vitest --root ../.. packages/plugin-charts/", "type-check": "tsc --noEmit", "lint": "eslint ." }, diff --git a/packages/plugin-charts/vite.config.ts b/packages/plugin-charts/vite.config.ts index 222f91a992..9f7a6349dd 100644 --- a/packages/plugin-charts/vite.config.ts +++ b/packages/plugin-charts/vite.config.ts @@ -14,6 +14,22 @@ import { resolve } from 'path'; import { createDtsExplicitExtensions } from '../../scripts/vite-dts-explicit-extensions.ts'; import { createDtsFailOnTypeErrors } from '../../scripts/vite-dts-fail-on-type-errors.ts'; +// objectui#3240 — with the per-package `vitest.config.*` files gone, Vitest +// launched from THIS directory falls back to this file and uses it as its test +// config. Guard that entry point (route 4 in +// scripts/vitest-invocation-guard.mjs, which carries the mechanism and the +// measurement). Gated on `VITEST` because the same file is the BUILD config: +// Vitest sets that variable when it loads a config, `vite build` does not, so +// a test run is refused and a build never is. +import { + assertCanonicalVitestInvocation, + repoRootFrom, +} from '../../scripts/vitest-invocation-guard.mjs'; + +if (process.env.VITEST) { + assertCanonicalVitestInvocation({ repoRoot: repoRootFrom(import.meta.url) }); +} + export default defineConfig({ plugins: [ react(), @@ -68,10 +84,4 @@ export default defineConfig({ }, }, }, - test: { - globals: true, - environment: 'happy-dom', - setupFiles: ['../../vitest.setup.tsx'], - passWithNoTests: true, - }, }); diff --git a/packages/plugin-charts/vitest.config.ts b/packages/plugin-charts/vitest.config.ts deleted file mode 100644 index 28840604c4..0000000000 --- a/packages/plugin-charts/vitest.config.ts +++ /dev/null @@ -1,34 +0,0 @@ -/// -import { defineConfig } from 'vite'; -import react from '@vitejs/plugin-react'; - -// objectui#5406 — this config is STANDALONE: it does not import the root -// `vitest.config.mts`, so nothing routed a package-cwd run here through the -// invocation guard, the way a root-importing config does as a side effect of -// the import. Without the call below, `cd` into this package and `pnpm exec -// vitest run` executes the suite under a config CI never uses — no -// `@object-ui/*` alias table, a different setup file, no project split — so -// its green says nothing about CI. (Measured on the sibling `plugin-grid`, -// whose config was identical to this one: one such run printed -// `Test Files 1 passed (1)` / `Tests 5 passed (5)` and exited 0.) -// -// Deleting this reopens the hole for this package alone, silently — which is -// why it is enforced rather than written down: -// `scripts/__tests__/vitest-invocation-guard.test.ts` walks every -// `vitest.config.*` in the repo and fails on any that neither imports the root -// config nor calls the guard. -import { - assertCanonicalVitestInvocation, - repoRootFrom, -} from '../../scripts/vitest-invocation-guard.mjs'; - -assertCanonicalVitestInvocation({ repoRoot: repoRootFrom(import.meta.url) }); - -export default defineConfig({ - plugins: [react()], - test: { - environment: 'happy-dom', - globals: true, - setupFiles: ['./vitest.setup.ts'], - }, -}); diff --git a/packages/plugin-charts/vitest.setup.ts b/packages/plugin-charts/vitest.setup.ts deleted file mode 100644 index 7b0828bfa8..0000000000 --- a/packages/plugin-charts/vitest.setup.ts +++ /dev/null @@ -1 +0,0 @@ -import '@testing-library/jest-dom'; diff --git a/packages/plugin-chatbot/package.json b/packages/plugin-chatbot/package.json index da3497e4c8..40f2ff5094 100644 --- a/packages/plugin-chatbot/package.json +++ b/packages/plugin-chatbot/package.json @@ -25,8 +25,8 @@ }, "scripts": { "build": "vite build", - "test": "vitest run", - "test:watch": "vitest", + "test": "vitest run --root ../.. packages/plugin-chatbot/", + "test:watch": "vitest --root ../.. packages/plugin-chatbot/", "type-check": "tsc --noEmit && tsc -p tsconfig.test.json", "lint": "eslint ." }, diff --git a/packages/plugin-chatbot/vite.config.ts b/packages/plugin-chatbot/vite.config.ts index 0cc685d6db..876fd258d8 100644 --- a/packages/plugin-chatbot/vite.config.ts +++ b/packages/plugin-chatbot/vite.config.ts @@ -14,6 +14,22 @@ import { resolve } from 'path'; import { createDtsExplicitExtensions } from '../../scripts/vite-dts-explicit-extensions.ts'; import { createDtsFailOnTypeErrors } from '../../scripts/vite-dts-fail-on-type-errors.ts'; +// objectui#3240 — with the per-package `vitest.config.*` files gone, Vitest +// launched from THIS directory falls back to this file and uses it as its test +// config. Guard that entry point (route 4 in +// scripts/vitest-invocation-guard.mjs, which carries the mechanism and the +// measurement). Gated on `VITEST` because the same file is the BUILD config: +// Vitest sets that variable when it loads a config, `vite build` does not, so +// a test run is refused and a build never is. +import { + assertCanonicalVitestInvocation, + repoRootFrom, +} from '../../scripts/vitest-invocation-guard.mjs'; + +if (process.env.VITEST) { + assertCanonicalVitestInvocation({ repoRoot: repoRootFrom(import.meta.url) }); +} + export default defineConfig({ plugins: [ react(), @@ -62,9 +78,4 @@ export default defineConfig({ }, }, }, - test: { - globals: true, - environment: 'happy-dom', - passWithNoTests: true, - }, }); diff --git a/packages/plugin-dashboard/package.json b/packages/plugin-dashboard/package.json index 1eab9eb4bd..0647418aa1 100644 --- a/packages/plugin-dashboard/package.json +++ b/packages/plugin-dashboard/package.json @@ -17,7 +17,7 @@ "scripts": { "build": "vite build", "type-check": "tsc --noEmit && tsc -p tsconfig.test.json", - "test": "vitest run", + "test": "vitest run --root ../.. packages/plugin-dashboard/", "lint": "eslint ." }, "dependencies": { diff --git a/packages/plugin-dashboard/vite.config.ts b/packages/plugin-dashboard/vite.config.ts index 040284d9f0..0fd5ec631f 100644 --- a/packages/plugin-dashboard/vite.config.ts +++ b/packages/plugin-dashboard/vite.config.ts @@ -6,6 +6,22 @@ import { resolve } from 'path'; import { createDtsExplicitExtensions } from '../../scripts/vite-dts-explicit-extensions.ts'; import { createDtsFailOnTypeErrors } from '../../scripts/vite-dts-fail-on-type-errors.ts'; +// objectui#3240 — with the per-package `vitest.config.*` files gone, Vitest +// launched from THIS directory falls back to this file and uses it as its test +// config. Guard that entry point (route 4 in +// scripts/vitest-invocation-guard.mjs, which carries the mechanism and the +// measurement). Gated on `VITEST` because the same file is the BUILD config: +// Vitest sets that variable when it loads a config, `vite build` does not, so +// a test run is refused and a build never is. +import { + assertCanonicalVitestInvocation, + repoRootFrom, +} from '../../scripts/vitest-invocation-guard.mjs'; + +if (process.env.VITEST) { + assertCanonicalVitestInvocation({ repoRoot: repoRootFrom(import.meta.url) }); +} + export default defineConfig({ plugins: [ react(), @@ -67,10 +83,4 @@ export default defineConfig({ }, }, }, - test: { - globals: true, - environment: 'happy-dom', - setupFiles: ['./vitest.setup.tsx'], - passWithNoTests: true, - }, }); diff --git a/packages/plugin-dashboard/vitest.config.ts b/packages/plugin-dashboard/vitest.config.ts deleted file mode 100644 index 6df6a4ac88..0000000000 --- a/packages/plugin-dashboard/vitest.config.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { mergeConfig } from 'vitest/config'; -import rootConfig from '../../vitest.config.mts'; -import viteConfig from './vite.config'; - -export default mergeConfig(rootConfig, mergeConfig(viteConfig, { - test: { - environment: 'jsdom', - }, -})); diff --git a/packages/plugin-dashboard/vitest.setup.tsx b/packages/plugin-dashboard/vitest.setup.tsx deleted file mode 100644 index 8bcfa8e25e..0000000000 --- a/packages/plugin-dashboard/vitest.setup.tsx +++ /dev/null @@ -1,18 +0,0 @@ -/** - * ObjectUI - * Copyright (c) 2024-present ObjectStack Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import { expect, afterEach } from 'vitest'; -import { cleanup } from '@testing-library/react'; -import * as matchers from '@testing-library/jest-dom/matchers'; -import '@testing-library/jest-dom'; - -expect.extend(matchers); - -afterEach(() => { - cleanup(); -}); diff --git a/packages/plugin-designer/package.json b/packages/plugin-designer/package.json index 905734071a..e4d1ca4d7a 100644 --- a/packages/plugin-designer/package.json +++ b/packages/plugin-designer/package.json @@ -23,7 +23,7 @@ "scripts": { "build": "vite build", "clean": "rm -rf dist", - "test": "vitest run", + "test": "vitest run --root ../.. packages/plugin-designer/", "type-check": "tsc --noEmit && tsc -p tsconfig.test.json", "lint": "eslint ." }, diff --git a/packages/plugin-designer/vite.config.ts b/packages/plugin-designer/vite.config.ts index 7ae70490cd..9e22f25c89 100644 --- a/packages/plugin-designer/vite.config.ts +++ b/packages/plugin-designer/vite.config.ts @@ -6,6 +6,22 @@ import { resolve } from 'path'; import { createDtsExplicitExtensions } from '../../scripts/vite-dts-explicit-extensions.ts'; import { createDtsFailOnTypeErrors } from '../../scripts/vite-dts-fail-on-type-errors.ts'; +// objectui#3240 — with the per-package `vitest.config.*` files gone, Vitest +// launched from THIS directory falls back to this file and uses it as its test +// config. Guard that entry point (route 4 in +// scripts/vitest-invocation-guard.mjs, which carries the mechanism and the +// measurement). Gated on `VITEST` because the same file is the BUILD config: +// Vitest sets that variable when it loads a config, `vite build` does not, so +// a test run is refused and a build never is. +import { + assertCanonicalVitestInvocation, + repoRootFrom, +} from '../../scripts/vitest-invocation-guard.mjs'; + +if (process.env.VITEST) { + assertCanonicalVitestInvocation({ repoRoot: repoRootFrom(import.meta.url) }); +} + export default defineConfig({ plugins: [ react(), @@ -63,9 +79,4 @@ export default defineConfig({ }, }, }, - test: { - globals: true, - environment: 'jsdom', - setupFiles: './vitest.setup.ts', - }, }); diff --git a/packages/plugin-designer/vitest.setup.ts b/packages/plugin-designer/vitest.setup.ts deleted file mode 100644 index 4767ca426d..0000000000 --- a/packages/plugin-designer/vitest.setup.ts +++ /dev/null @@ -1,26 +0,0 @@ -/** - * ObjectUI - * Copyright (c) 2024-present ObjectStack Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import '@testing-library/jest-dom'; - -// Polyfill ResizeObserver for Radix UI (Shadcn) components -global.ResizeObserver = class ResizeObserver { - observe() {} - unobserve() {} - disconnect() {} -}; - -// Polyfill HTMLDialogElement methods for happy-dom/jsdom -if (typeof HTMLDialogElement !== 'undefined') { - HTMLDialogElement.prototype.showModal = HTMLDialogElement.prototype.showModal || function () { - this.setAttribute('open', ''); - }; - HTMLDialogElement.prototype.close = HTMLDialogElement.prototype.close || function () { - this.removeAttribute('open'); - }; -} diff --git a/packages/plugin-detail/package.json b/packages/plugin-detail/package.json index 957a7d1cb2..5d81f5c700 100644 --- a/packages/plugin-detail/package.json +++ b/packages/plugin-detail/package.json @@ -25,8 +25,8 @@ }, "scripts": { "build": "vite build", - "test": "vitest run", - "test:watch": "vitest", + "test": "vitest run --root ../.. packages/plugin-detail/", + "test:watch": "vitest --root ../.. packages/plugin-detail/", "type-check": "tsc --noEmit && tsc -p tsconfig.test.json", "lint": "eslint ." }, diff --git a/packages/plugin-detail/vite.config.ts b/packages/plugin-detail/vite.config.ts index e7e52b4674..a09f588789 100644 --- a/packages/plugin-detail/vite.config.ts +++ b/packages/plugin-detail/vite.config.ts @@ -6,6 +6,22 @@ import { resolve } from 'path'; import { createDtsExplicitExtensions } from '../../scripts/vite-dts-explicit-extensions.ts'; import { createDtsFailOnTypeErrors } from '../../scripts/vite-dts-fail-on-type-errors.ts'; +// objectui#3240 — with the per-package `vitest.config.*` files gone, Vitest +// launched from THIS directory falls back to this file and uses it as its test +// config. Guard that entry point (route 4 in +// scripts/vitest-invocation-guard.mjs, which carries the mechanism and the +// measurement). Gated on `VITEST` because the same file is the BUILD config: +// Vitest sets that variable when it loads a config, `vite build` does not, so +// a test run is refused and a build never is. +import { + assertCanonicalVitestInvocation, + repoRootFrom, +} from '../../scripts/vitest-invocation-guard.mjs'; + +if (process.env.VITEST) { + assertCanonicalVitestInvocation({ repoRoot: repoRootFrom(import.meta.url) }); +} + export default defineConfig({ plugins: [ react(), @@ -69,15 +85,4 @@ export default defineConfig({ }, }, }, - test: { - globals: true, - environment: 'happy-dom', - setupFiles: ['../../vitest.setup.tsx'], - passWithNoTests: true, - css: { - modules: { - classNameStrategy: 'non-scoped', - }, - }, - }, }); diff --git a/packages/plugin-detail/vitest.config.ts b/packages/plugin-detail/vitest.config.ts deleted file mode 100644 index 28840604c4..0000000000 --- a/packages/plugin-detail/vitest.config.ts +++ /dev/null @@ -1,34 +0,0 @@ -/// -import { defineConfig } from 'vite'; -import react from '@vitejs/plugin-react'; - -// objectui#5406 — this config is STANDALONE: it does not import the root -// `vitest.config.mts`, so nothing routed a package-cwd run here through the -// invocation guard, the way a root-importing config does as a side effect of -// the import. Without the call below, `cd` into this package and `pnpm exec -// vitest run` executes the suite under a config CI never uses — no -// `@object-ui/*` alias table, a different setup file, no project split — so -// its green says nothing about CI. (Measured on the sibling `plugin-grid`, -// whose config was identical to this one: one such run printed -// `Test Files 1 passed (1)` / `Tests 5 passed (5)` and exited 0.) -// -// Deleting this reopens the hole for this package alone, silently — which is -// why it is enforced rather than written down: -// `scripts/__tests__/vitest-invocation-guard.test.ts` walks every -// `vitest.config.*` in the repo and fails on any that neither imports the root -// config nor calls the guard. -import { - assertCanonicalVitestInvocation, - repoRootFrom, -} from '../../scripts/vitest-invocation-guard.mjs'; - -assertCanonicalVitestInvocation({ repoRoot: repoRootFrom(import.meta.url) }); - -export default defineConfig({ - plugins: [react()], - test: { - environment: 'happy-dom', - globals: true, - setupFiles: ['./vitest.setup.ts'], - }, -}); diff --git a/packages/plugin-detail/vitest.setup.ts b/packages/plugin-detail/vitest.setup.ts deleted file mode 100644 index 7b0828bfa8..0000000000 --- a/packages/plugin-detail/vitest.setup.ts +++ /dev/null @@ -1 +0,0 @@ -import '@testing-library/jest-dom'; diff --git a/packages/plugin-editor/package.json b/packages/plugin-editor/package.json index 0ac4c314ec..9b8da959e4 100644 --- a/packages/plugin-editor/package.json +++ b/packages/plugin-editor/package.json @@ -25,8 +25,8 @@ }, "scripts": { "build": "vite build", - "test": "vitest run", - "test:watch": "vitest", + "test": "vitest run --root ../.. packages/plugin-editor/", + "test:watch": "vitest --root ../.. packages/plugin-editor/", "type-check": "tsc --noEmit && tsc -p tsconfig.test.json", "lint": "eslint ." }, diff --git a/packages/plugin-editor/vite.config.ts b/packages/plugin-editor/vite.config.ts index cdf8a99147..d86a0361b6 100644 --- a/packages/plugin-editor/vite.config.ts +++ b/packages/plugin-editor/vite.config.ts @@ -14,6 +14,22 @@ import { resolve } from 'path'; import { createDtsExplicitExtensions } from '../../scripts/vite-dts-explicit-extensions.ts'; import { createDtsFailOnTypeErrors } from '../../scripts/vite-dts-fail-on-type-errors.ts'; +// objectui#3240 — with the per-package `vitest.config.*` files gone, Vitest +// launched from THIS directory falls back to this file and uses it as its test +// config. Guard that entry point (route 4 in +// scripts/vitest-invocation-guard.mjs, which carries the mechanism and the +// measurement). Gated on `VITEST` because the same file is the BUILD config: +// Vitest sets that variable when it loads a config, `vite build` does not, so +// a test run is refused and a build never is. +import { + assertCanonicalVitestInvocation, + repoRootFrom, +} from '../../scripts/vitest-invocation-guard.mjs'; + +if (process.env.VITEST) { + assertCanonicalVitestInvocation({ repoRoot: repoRootFrom(import.meta.url) }); +} + export default defineConfig({ plugins: [ react(), @@ -83,10 +99,4 @@ export default defineConfig({ }, }, }, - test: { - globals: true, - environment: 'happy-dom', - setupFiles: ['../../vitest.setup.tsx'], - passWithNoTests: true, - }, }); diff --git a/packages/plugin-form/package.json b/packages/plugin-form/package.json index fe92c482ab..c3b40d9de3 100644 --- a/packages/plugin-form/package.json +++ b/packages/plugin-form/package.json @@ -16,7 +16,7 @@ }, "scripts": { "build": "vite build", - "test": "vitest run", + "test": "vitest run --root ../.. packages/plugin-form/", "type-check": "tsc --noEmit && tsc -p tsconfig.test.json", "lint": "eslint ." }, diff --git a/packages/plugin-form/vite.config.ts b/packages/plugin-form/vite.config.ts index 56e85460b6..35edf30814 100644 --- a/packages/plugin-form/vite.config.ts +++ b/packages/plugin-form/vite.config.ts @@ -6,6 +6,22 @@ import { resolve } from 'path'; import { createDtsExplicitExtensions } from '../../scripts/vite-dts-explicit-extensions.ts'; import { createDtsFailOnTypeErrors } from '../../scripts/vite-dts-fail-on-type-errors.ts'; +// objectui#3240 — with the per-package `vitest.config.*` files gone, Vitest +// launched from THIS directory falls back to this file and uses it as its test +// config. Guard that entry point (route 4 in +// scripts/vitest-invocation-guard.mjs, which carries the mechanism and the +// measurement). Gated on `VITEST` because the same file is the BUILD config: +// Vitest sets that variable when it loads a config, `vite build` does not, so +// a test run is refused and a build never is. +import { + assertCanonicalVitestInvocation, + repoRootFrom, +} from '../../scripts/vitest-invocation-guard.mjs'; + +if (process.env.VITEST) { + assertCanonicalVitestInvocation({ repoRoot: repoRootFrom(import.meta.url) }); +} + export default defineConfig({ plugins: [ react(), @@ -56,10 +72,4 @@ export default defineConfig({ }, }, }, - test: { - globals: true, - environment: 'happy-dom', - setupFiles: ['../../vitest.setup.tsx'], - passWithNoTests: true, - }, }); diff --git a/packages/plugin-form/vitest.config.ts b/packages/plugin-form/vitest.config.ts deleted file mode 100644 index 28840604c4..0000000000 --- a/packages/plugin-form/vitest.config.ts +++ /dev/null @@ -1,34 +0,0 @@ -/// -import { defineConfig } from 'vite'; -import react from '@vitejs/plugin-react'; - -// objectui#5406 — this config is STANDALONE: it does not import the root -// `vitest.config.mts`, so nothing routed a package-cwd run here through the -// invocation guard, the way a root-importing config does as a side effect of -// the import. Without the call below, `cd` into this package and `pnpm exec -// vitest run` executes the suite under a config CI never uses — no -// `@object-ui/*` alias table, a different setup file, no project split — so -// its green says nothing about CI. (Measured on the sibling `plugin-grid`, -// whose config was identical to this one: one such run printed -// `Test Files 1 passed (1)` / `Tests 5 passed (5)` and exited 0.) -// -// Deleting this reopens the hole for this package alone, silently — which is -// why it is enforced rather than written down: -// `scripts/__tests__/vitest-invocation-guard.test.ts` walks every -// `vitest.config.*` in the repo and fails on any that neither imports the root -// config nor calls the guard. -import { - assertCanonicalVitestInvocation, - repoRootFrom, -} from '../../scripts/vitest-invocation-guard.mjs'; - -assertCanonicalVitestInvocation({ repoRoot: repoRootFrom(import.meta.url) }); - -export default defineConfig({ - plugins: [react()], - test: { - environment: 'happy-dom', - globals: true, - setupFiles: ['./vitest.setup.ts'], - }, -}); diff --git a/packages/plugin-form/vitest.setup.ts b/packages/plugin-form/vitest.setup.ts deleted file mode 100644 index 7b0828bfa8..0000000000 --- a/packages/plugin-form/vitest.setup.ts +++ /dev/null @@ -1 +0,0 @@ -import '@testing-library/jest-dom'; diff --git a/packages/plugin-gantt/package.json b/packages/plugin-gantt/package.json index 2d153b570c..bfe2a7592e 100644 --- a/packages/plugin-gantt/package.json +++ b/packages/plugin-gantt/package.json @@ -25,8 +25,8 @@ }, "scripts": { "build": "vite build", - "test": "vitest run", - "test:watch": "vitest", + "test": "vitest run --root ../.. packages/plugin-gantt/", + "test:watch": "vitest --root ../.. packages/plugin-gantt/", "type-check": "tsc --noEmit && tsc -p tsconfig.test.json", "lint": "eslint ." }, diff --git a/packages/plugin-gantt/vite.config.ts b/packages/plugin-gantt/vite.config.ts index c58c4b6c67..a21adb551c 100644 --- a/packages/plugin-gantt/vite.config.ts +++ b/packages/plugin-gantt/vite.config.ts @@ -14,6 +14,22 @@ import { resolve } from 'path'; import { createDtsExplicitExtensions } from '../../scripts/vite-dts-explicit-extensions.ts'; import { createDtsFailOnTypeErrors } from '../../scripts/vite-dts-fail-on-type-errors.ts'; +// objectui#3240 — with the per-package `vitest.config.*` files gone, Vitest +// launched from THIS directory falls back to this file and uses it as its test +// config. Guard that entry point (route 4 in +// scripts/vitest-invocation-guard.mjs, which carries the mechanism and the +// measurement). Gated on `VITEST` because the same file is the BUILD config: +// Vitest sets that variable when it loads a config, `vite build` does not, so +// a test run is refused and a build never is. +import { + assertCanonicalVitestInvocation, + repoRootFrom, +} from '../../scripts/vitest-invocation-guard.mjs'; + +if (process.env.VITEST) { + assertCanonicalVitestInvocation({ repoRoot: repoRootFrom(import.meta.url) }); +} + export default defineConfig({ plugins: [ react(), @@ -63,7 +79,4 @@ export default defineConfig({ }, }, }, - test: { - passWithNoTests: true, - }, }); diff --git a/packages/plugin-gantt/vitest.config.ts b/packages/plugin-gantt/vitest.config.ts deleted file mode 100644 index 28840604c4..0000000000 --- a/packages/plugin-gantt/vitest.config.ts +++ /dev/null @@ -1,34 +0,0 @@ -/// -import { defineConfig } from 'vite'; -import react from '@vitejs/plugin-react'; - -// objectui#5406 — this config is STANDALONE: it does not import the root -// `vitest.config.mts`, so nothing routed a package-cwd run here through the -// invocation guard, the way a root-importing config does as a side effect of -// the import. Without the call below, `cd` into this package and `pnpm exec -// vitest run` executes the suite under a config CI never uses — no -// `@object-ui/*` alias table, a different setup file, no project split — so -// its green says nothing about CI. (Measured on the sibling `plugin-grid`, -// whose config was identical to this one: one such run printed -// `Test Files 1 passed (1)` / `Tests 5 passed (5)` and exited 0.) -// -// Deleting this reopens the hole for this package alone, silently — which is -// why it is enforced rather than written down: -// `scripts/__tests__/vitest-invocation-guard.test.ts` walks every -// `vitest.config.*` in the repo and fails on any that neither imports the root -// config nor calls the guard. -import { - assertCanonicalVitestInvocation, - repoRootFrom, -} from '../../scripts/vitest-invocation-guard.mjs'; - -assertCanonicalVitestInvocation({ repoRoot: repoRootFrom(import.meta.url) }); - -export default defineConfig({ - plugins: [react()], - test: { - environment: 'happy-dom', - globals: true, - setupFiles: ['./vitest.setup.ts'], - }, -}); diff --git a/packages/plugin-gantt/vitest.setup.ts b/packages/plugin-gantt/vitest.setup.ts deleted file mode 100644 index 7b0828bfa8..0000000000 --- a/packages/plugin-gantt/vitest.setup.ts +++ /dev/null @@ -1 +0,0 @@ -import '@testing-library/jest-dom'; diff --git a/packages/plugin-grid/package.json b/packages/plugin-grid/package.json index 7a6571bd19..4cb92b5682 100644 --- a/packages/plugin-grid/package.json +++ b/packages/plugin-grid/package.json @@ -17,7 +17,7 @@ }, "scripts": { "build": "vite build && node scripts/build-css.mjs", - "test": "vitest run", + "test": "vitest run --root ../.. packages/plugin-grid/", "type-check": "tsc --noEmit && tsc -p tsconfig.test.json", "lint": "eslint ." }, diff --git a/packages/plugin-grid/vite.config.ts b/packages/plugin-grid/vite.config.ts index d8b6217d42..c76c76d640 100644 --- a/packages/plugin-grid/vite.config.ts +++ b/packages/plugin-grid/vite.config.ts @@ -6,6 +6,22 @@ import { resolve } from 'path'; import { createDtsExplicitExtensions } from '../../scripts/vite-dts-explicit-extensions.ts'; import { createDtsFailOnTypeErrors } from '../../scripts/vite-dts-fail-on-type-errors.ts'; +// objectui#3240 — with the per-package `vitest.config.*` files gone, Vitest +// launched from THIS directory falls back to this file and uses it as its test +// config. Guard that entry point (route 4 in +// scripts/vitest-invocation-guard.mjs, which carries the mechanism and the +// measurement). Gated on `VITEST` because the same file is the BUILD config: +// Vitest sets that variable when it loads a config, `vite build` does not, so +// a test run is refused and a build never is. +import { + assertCanonicalVitestInvocation, + repoRootFrom, +} from '../../scripts/vitest-invocation-guard.mjs'; + +if (process.env.VITEST) { + assertCanonicalVitestInvocation({ repoRoot: repoRootFrom(import.meta.url) }); +} + export default defineConfig({ plugins: [ react(), @@ -56,10 +72,4 @@ export default defineConfig({ }, }, }, - test: { - globals: true, - environment: 'happy-dom', - setupFiles: ['../../vitest.setup.tsx'], - passWithNoTests: true, - }, }); diff --git a/packages/plugin-grid/vitest.config.ts b/packages/plugin-grid/vitest.config.ts deleted file mode 100644 index 28840604c4..0000000000 --- a/packages/plugin-grid/vitest.config.ts +++ /dev/null @@ -1,34 +0,0 @@ -/// -import { defineConfig } from 'vite'; -import react from '@vitejs/plugin-react'; - -// objectui#5406 — this config is STANDALONE: it does not import the root -// `vitest.config.mts`, so nothing routed a package-cwd run here through the -// invocation guard, the way a root-importing config does as a side effect of -// the import. Without the call below, `cd` into this package and `pnpm exec -// vitest run` executes the suite under a config CI never uses — no -// `@object-ui/*` alias table, a different setup file, no project split — so -// its green says nothing about CI. (Measured on the sibling `plugin-grid`, -// whose config was identical to this one: one such run printed -// `Test Files 1 passed (1)` / `Tests 5 passed (5)` and exited 0.) -// -// Deleting this reopens the hole for this package alone, silently — which is -// why it is enforced rather than written down: -// `scripts/__tests__/vitest-invocation-guard.test.ts` walks every -// `vitest.config.*` in the repo and fails on any that neither imports the root -// config nor calls the guard. -import { - assertCanonicalVitestInvocation, - repoRootFrom, -} from '../../scripts/vitest-invocation-guard.mjs'; - -assertCanonicalVitestInvocation({ repoRoot: repoRootFrom(import.meta.url) }); - -export default defineConfig({ - plugins: [react()], - test: { - environment: 'happy-dom', - globals: true, - setupFiles: ['./vitest.setup.ts'], - }, -}); diff --git a/packages/plugin-grid/vitest.setup.ts b/packages/plugin-grid/vitest.setup.ts deleted file mode 100644 index 7b0828bfa8..0000000000 --- a/packages/plugin-grid/vitest.setup.ts +++ /dev/null @@ -1 +0,0 @@ -import '@testing-library/jest-dom'; diff --git a/packages/plugin-kanban/package.json b/packages/plugin-kanban/package.json index 37ef99ed8a..405655d384 100644 --- a/packages/plugin-kanban/package.json +++ b/packages/plugin-kanban/package.json @@ -26,8 +26,8 @@ }, "scripts": { "build": "vite build && node scripts/build-css.mjs", - "test": "vitest run", - "test:watch": "vitest", + "test": "vitest run --root ../.. packages/plugin-kanban/", + "test:watch": "vitest --root ../.. packages/plugin-kanban/", "type-check": "tsc --noEmit && tsc -p tsconfig.test.json", "lint": "eslint ." }, diff --git a/packages/plugin-kanban/vite.config.ts b/packages/plugin-kanban/vite.config.ts index 9628c887b9..56d8e515fc 100644 --- a/packages/plugin-kanban/vite.config.ts +++ b/packages/plugin-kanban/vite.config.ts @@ -14,6 +14,22 @@ import { resolve } from 'path'; import { createDtsExplicitExtensions } from '../../scripts/vite-dts-explicit-extensions.ts'; import { createDtsFailOnTypeErrors } from '../../scripts/vite-dts-fail-on-type-errors.ts'; +// objectui#3240 — with the per-package `vitest.config.*` files gone, Vitest +// launched from THIS directory falls back to this file and uses it as its test +// config. Guard that entry point (route 4 in +// scripts/vitest-invocation-guard.mjs, which carries the mechanism and the +// measurement). Gated on `VITEST` because the same file is the BUILD config: +// Vitest sets that variable when it loads a config, `vite build` does not, so +// a test run is refused and a build never is. +import { + assertCanonicalVitestInvocation, + repoRootFrom, +} from '../../scripts/vitest-invocation-guard.mjs'; + +if (process.env.VITEST) { + assertCanonicalVitestInvocation({ repoRoot: repoRootFrom(import.meta.url) }); +} + export default defineConfig({ plugins: [ react(), @@ -68,10 +84,4 @@ export default defineConfig({ }, }, }, - test: { - globals: true, - environment: 'happy-dom', - setupFiles: ['../../vitest.setup.tsx'], - passWithNoTests: true, - }, }); diff --git a/packages/plugin-kanban/vitest.config.ts b/packages/plugin-kanban/vitest.config.ts deleted file mode 100644 index 28840604c4..0000000000 --- a/packages/plugin-kanban/vitest.config.ts +++ /dev/null @@ -1,34 +0,0 @@ -/// -import { defineConfig } from 'vite'; -import react from '@vitejs/plugin-react'; - -// objectui#5406 — this config is STANDALONE: it does not import the root -// `vitest.config.mts`, so nothing routed a package-cwd run here through the -// invocation guard, the way a root-importing config does as a side effect of -// the import. Without the call below, `cd` into this package and `pnpm exec -// vitest run` executes the suite under a config CI never uses — no -// `@object-ui/*` alias table, a different setup file, no project split — so -// its green says nothing about CI. (Measured on the sibling `plugin-grid`, -// whose config was identical to this one: one such run printed -// `Test Files 1 passed (1)` / `Tests 5 passed (5)` and exited 0.) -// -// Deleting this reopens the hole for this package alone, silently — which is -// why it is enforced rather than written down: -// `scripts/__tests__/vitest-invocation-guard.test.ts` walks every -// `vitest.config.*` in the repo and fails on any that neither imports the root -// config nor calls the guard. -import { - assertCanonicalVitestInvocation, - repoRootFrom, -} from '../../scripts/vitest-invocation-guard.mjs'; - -assertCanonicalVitestInvocation({ repoRoot: repoRootFrom(import.meta.url) }); - -export default defineConfig({ - plugins: [react()], - test: { - environment: 'happy-dom', - globals: true, - setupFiles: ['./vitest.setup.ts'], - }, -}); diff --git a/packages/plugin-kanban/vitest.setup.ts b/packages/plugin-kanban/vitest.setup.ts deleted file mode 100644 index 7b0828bfa8..0000000000 --- a/packages/plugin-kanban/vitest.setup.ts +++ /dev/null @@ -1 +0,0 @@ -import '@testing-library/jest-dom'; diff --git a/packages/plugin-list/package.json b/packages/plugin-list/package.json index ac9e756364..d4b415978e 100644 --- a/packages/plugin-list/package.json +++ b/packages/plugin-list/package.json @@ -25,8 +25,8 @@ }, "scripts": { "build": "vite build", - "test": "vitest run", - "test:watch": "vitest", + "test": "vitest run --root ../.. packages/plugin-list/", + "test:watch": "vitest --root ../.. packages/plugin-list/", "type-check": "tsc --noEmit && tsc -p tsconfig.test.json", "lint": "eslint ." }, diff --git a/packages/plugin-list/vite.config.ts b/packages/plugin-list/vite.config.ts index 34530accb9..89f37dfacb 100644 --- a/packages/plugin-list/vite.config.ts +++ b/packages/plugin-list/vite.config.ts @@ -6,6 +6,22 @@ import { resolve } from 'path'; import { createDtsExplicitExtensions } from '../../scripts/vite-dts-explicit-extensions.ts'; import { createDtsFailOnTypeErrors } from '../../scripts/vite-dts-fail-on-type-errors.ts'; +// objectui#3240 — with the per-package `vitest.config.*` files gone, Vitest +// launched from THIS directory falls back to this file and uses it as its test +// config. Guard that entry point (route 4 in +// scripts/vitest-invocation-guard.mjs, which carries the mechanism and the +// measurement). Gated on `VITEST` because the same file is the BUILD config: +// Vitest sets that variable when it loads a config, `vite build` does not, so +// a test run is refused and a build never is. +import { + assertCanonicalVitestInvocation, + repoRootFrom, +} from '../../scripts/vitest-invocation-guard.mjs'; + +if (process.env.VITEST) { + assertCanonicalVitestInvocation({ repoRoot: repoRootFrom(import.meta.url) }); +} + export default defineConfig({ plugins: [ react(), @@ -71,15 +87,4 @@ export default defineConfig({ }, }, }, - test: { - globals: true, - environment: 'happy-dom', - setupFiles: ['../../vitest.setup.tsx'], - passWithNoTests: true, - css: { - modules: { - classNameStrategy: 'non-scoped', - }, - }, - }, }); diff --git a/packages/plugin-list/vitest.config.ts b/packages/plugin-list/vitest.config.ts deleted file mode 100644 index 28840604c4..0000000000 --- a/packages/plugin-list/vitest.config.ts +++ /dev/null @@ -1,34 +0,0 @@ -/// -import { defineConfig } from 'vite'; -import react from '@vitejs/plugin-react'; - -// objectui#5406 — this config is STANDALONE: it does not import the root -// `vitest.config.mts`, so nothing routed a package-cwd run here through the -// invocation guard, the way a root-importing config does as a side effect of -// the import. Without the call below, `cd` into this package and `pnpm exec -// vitest run` executes the suite under a config CI never uses — no -// `@object-ui/*` alias table, a different setup file, no project split — so -// its green says nothing about CI. (Measured on the sibling `plugin-grid`, -// whose config was identical to this one: one such run printed -// `Test Files 1 passed (1)` / `Tests 5 passed (5)` and exited 0.) -// -// Deleting this reopens the hole for this package alone, silently — which is -// why it is enforced rather than written down: -// `scripts/__tests__/vitest-invocation-guard.test.ts` walks every -// `vitest.config.*` in the repo and fails on any that neither imports the root -// config nor calls the guard. -import { - assertCanonicalVitestInvocation, - repoRootFrom, -} from '../../scripts/vitest-invocation-guard.mjs'; - -assertCanonicalVitestInvocation({ repoRoot: repoRootFrom(import.meta.url) }); - -export default defineConfig({ - plugins: [react()], - test: { - environment: 'happy-dom', - globals: true, - setupFiles: ['./vitest.setup.ts'], - }, -}); diff --git a/packages/plugin-list/vitest.setup.ts b/packages/plugin-list/vitest.setup.ts deleted file mode 100644 index 7b0828bfa8..0000000000 --- a/packages/plugin-list/vitest.setup.ts +++ /dev/null @@ -1 +0,0 @@ -import '@testing-library/jest-dom'; diff --git a/packages/plugin-map/package.json b/packages/plugin-map/package.json index 8411170892..9d9fa59329 100644 --- a/packages/plugin-map/package.json +++ b/packages/plugin-map/package.json @@ -25,8 +25,8 @@ }, "scripts": { "build": "vite build", - "test": "vitest run", - "test:watch": "vitest", + "test": "vitest run --root ../.. packages/plugin-map/", + "test:watch": "vitest --root ../.. packages/plugin-map/", "type-check": "tsc --noEmit && tsc -p tsconfig.test.json", "lint": "eslint ." }, diff --git a/packages/plugin-map/src/index.registration.test.tsx b/packages/plugin-map/src/index.registration.test.tsx index 71eb386852..e6d6ae383c 100644 --- a/packages/plugin-map/src/index.registration.test.tsx +++ b/packages/plugin-map/src/index.registration.test.tsx @@ -10,7 +10,10 @@ import { describe, it, expect, vi, afterEach } from 'vitest'; // `src/index.tsx` pulls in `ObjectMap.tsx`, which imports the real map bindings. // Stub them so a plain module import needs no WebGL canvas (`maplibre-gl` itself -// is already stubbed in `vitest.setup.ts`). +// is already stubbed in the repo-root `vitest.setup.base.ts`, which every project +// loads). This used to name `packages/plugin-map/vitest.setup.ts`, a copy of that +// same mock that only the package's own vitest config loaded — i.e. never under +// the invocation CI runs. objectui#3240 deleted the config and the copy. vi.mock('react-map-gl/maplibre', () => ({ default: () => null, Map: () => null, diff --git a/packages/plugin-map/vite.config.ts b/packages/plugin-map/vite.config.ts index f73cebef3b..5112bd4d0a 100644 --- a/packages/plugin-map/vite.config.ts +++ b/packages/plugin-map/vite.config.ts @@ -14,6 +14,22 @@ import { resolve } from 'path'; import { createDtsExplicitExtensions } from '../../scripts/vite-dts-explicit-extensions.ts'; import { createDtsFailOnTypeErrors } from '../../scripts/vite-dts-fail-on-type-errors.ts'; +// objectui#3240 — with the per-package `vitest.config.*` files gone, Vitest +// launched from THIS directory falls back to this file and uses it as its test +// config. Guard that entry point (route 4 in +// scripts/vitest-invocation-guard.mjs, which carries the mechanism and the +// measurement). Gated on `VITEST` because the same file is the BUILD config: +// Vitest sets that variable when it loads a config, `vite build` does not, so +// a test run is refused and a build never is. +import { + assertCanonicalVitestInvocation, + repoRootFrom, +} from '../../scripts/vitest-invocation-guard.mjs'; + +if (process.env.VITEST) { + assertCanonicalVitestInvocation({ repoRoot: repoRootFrom(import.meta.url) }); +} + export default defineConfig({ plugins: [ react(), @@ -65,7 +81,4 @@ export default defineConfig({ }, }, }, - test: { - passWithNoTests: true, - }, }); diff --git a/packages/plugin-map/vitest.config.ts b/packages/plugin-map/vitest.config.ts deleted file mode 100644 index 28840604c4..0000000000 --- a/packages/plugin-map/vitest.config.ts +++ /dev/null @@ -1,34 +0,0 @@ -/// -import { defineConfig } from 'vite'; -import react from '@vitejs/plugin-react'; - -// objectui#5406 — this config is STANDALONE: it does not import the root -// `vitest.config.mts`, so nothing routed a package-cwd run here through the -// invocation guard, the way a root-importing config does as a side effect of -// the import. Without the call below, `cd` into this package and `pnpm exec -// vitest run` executes the suite under a config CI never uses — no -// `@object-ui/*` alias table, a different setup file, no project split — so -// its green says nothing about CI. (Measured on the sibling `plugin-grid`, -// whose config was identical to this one: one such run printed -// `Test Files 1 passed (1)` / `Tests 5 passed (5)` and exited 0.) -// -// Deleting this reopens the hole for this package alone, silently — which is -// why it is enforced rather than written down: -// `scripts/__tests__/vitest-invocation-guard.test.ts` walks every -// `vitest.config.*` in the repo and fails on any that neither imports the root -// config nor calls the guard. -import { - assertCanonicalVitestInvocation, - repoRootFrom, -} from '../../scripts/vitest-invocation-guard.mjs'; - -assertCanonicalVitestInvocation({ repoRoot: repoRootFrom(import.meta.url) }); - -export default defineConfig({ - plugins: [react()], - test: { - environment: 'happy-dom', - globals: true, - setupFiles: ['./vitest.setup.ts'], - }, -}); diff --git a/packages/plugin-map/vitest.setup.ts b/packages/plugin-map/vitest.setup.ts deleted file mode 100644 index 50026fcbe1..0000000000 --- a/packages/plugin-map/vitest.setup.ts +++ /dev/null @@ -1,78 +0,0 @@ -import '@testing-library/jest-dom'; -import { vi } from 'vitest'; - -// Mock maplibre-gl to avoid "Failed to initialize WebGL" errors -vi.mock('maplibre-gl', () => { - const Map = vi.fn(() => ({ - on: vi.fn(), - off: vi.fn(), - remove: vi.fn(), - addControl: vi.fn(), - resize: vi.fn(), - flyTo: vi.fn(), - fitBounds: vi.fn(), - jumpTo: vi.fn(), - getContainer: vi.fn(() => document.createElement('div')), - loaded: vi.fn(() => true), - isStyleLoaded: vi.fn(() => true), - getCanvas: vi.fn(() => document.createElement('canvas')), - setStyle: vi.fn(), - setCenter: vi.fn(), - setZoom: vi.fn(), - getCenter: vi.fn(() => ({ lng: 0, lat: 0 })), - getZoom: vi.fn(() => 0), - addSource: vi.fn(), - removeSource: vi.fn(), - addLayer: vi.fn(), - removeLayer: vi.fn(), - setLayoutProperty: vi.fn(), - setPaintProperty: vi.fn(), - setFilter: vi.fn(), - queryRenderedFeatures: vi.fn(() => []), - })); - - const NavigationControl = vi.fn(); - const GeolocateControl = vi.fn(); - const AttributionControl = vi.fn(); - const ScaleControl = vi.fn(); - const FullscreenControl = vi.fn(); - const Popup = vi.fn(() => ({ - setLngLat: vi.fn().mockReturnThis(), - setHTML: vi.fn().mockReturnThis(), - setText: vi.fn().mockReturnThis(), - setDOMContent: vi.fn().mockReturnThis(), - addTo: vi.fn().mockReturnThis(), - remove: vi.fn(), - })); - const Marker = vi.fn(() => ({ - setLngLat: vi.fn().mockReturnThis(), - addTo: vi.fn().mockReturnThis(), - remove: vi.fn(), - setPopup: vi.fn().mockReturnThis(), - getElement: vi.fn(() => document.createElement('div')), - })); - const supported = vi.fn(() => true); - - return { - default: { - Map, - NavigationControl, - GeolocateControl, - AttributionControl, - ScaleControl, - FullscreenControl, - Popup, - Marker, - supported, - }, - Map, - NavigationControl, - GeolocateControl, - AttributionControl, - ScaleControl, - FullscreenControl, - Popup, - Marker, - supported, - }; -}); diff --git a/packages/plugin-markdown/package.json b/packages/plugin-markdown/package.json index 0ab00002f7..12e81c47c2 100644 --- a/packages/plugin-markdown/package.json +++ b/packages/plugin-markdown/package.json @@ -25,8 +25,8 @@ }, "scripts": { "build": "vite build", - "test": "vitest run", - "test:watch": "vitest", + "test": "vitest run --root ../.. packages/plugin-markdown/", + "test:watch": "vitest --root ../.. packages/plugin-markdown/", "type-check": "tsc --noEmit && tsc -p tsconfig.test.json", "lint": "eslint ." }, diff --git a/packages/plugin-markdown/vite.config.ts b/packages/plugin-markdown/vite.config.ts index c9fec8c89c..3466c69e4a 100644 --- a/packages/plugin-markdown/vite.config.ts +++ b/packages/plugin-markdown/vite.config.ts @@ -14,6 +14,22 @@ import { resolve } from 'path'; import { createDtsExplicitExtensions } from '../../scripts/vite-dts-explicit-extensions.ts'; import { createDtsFailOnTypeErrors } from '../../scripts/vite-dts-fail-on-type-errors.ts'; +// objectui#3240 — with the per-package `vitest.config.*` files gone, Vitest +// launched from THIS directory falls back to this file and uses it as its test +// config. Guard that entry point (route 4 in +// scripts/vitest-invocation-guard.mjs, which carries the mechanism and the +// measurement). Gated on `VITEST` because the same file is the BUILD config: +// Vitest sets that variable when it loads a config, `vite build` does not, so +// a test run is refused and a build never is. +import { + assertCanonicalVitestInvocation, + repoRootFrom, +} from '../../scripts/vitest-invocation-guard.mjs'; + +if (process.env.VITEST) { + assertCanonicalVitestInvocation({ repoRoot: repoRootFrom(import.meta.url) }); +} + export default defineConfig({ plugins: [ react(), @@ -90,10 +106,4 @@ export default defineConfig({ }, }, }, - test: { - globals: true, - environment: 'happy-dom', - setupFiles: ['../../vitest.setup.tsx'], - passWithNoTests: true, - }, }); diff --git a/packages/plugin-report/package.json b/packages/plugin-report/package.json index f05f300ce4..9842f12bc1 100644 --- a/packages/plugin-report/package.json +++ b/packages/plugin-report/package.json @@ -22,7 +22,7 @@ "build": "vite build", "type-check": "tsc --noEmit && tsc -p tsconfig.test.json", "clean": "rm -rf dist", - "test": "vitest run", + "test": "vitest run --root ../.. packages/plugin-report/", "lint": "eslint ." }, "dependencies": { diff --git a/packages/plugin-report/vite.config.ts b/packages/plugin-report/vite.config.ts index ccf9ef9756..d286c92d24 100644 --- a/packages/plugin-report/vite.config.ts +++ b/packages/plugin-report/vite.config.ts @@ -6,6 +6,22 @@ import { resolve } from 'path'; import { createDtsExplicitExtensions } from '../../scripts/vite-dts-explicit-extensions.ts'; import { createDtsFailOnTypeErrors } from '../../scripts/vite-dts-fail-on-type-errors.ts'; +// objectui#3240 — with the per-package `vitest.config.*` files gone, Vitest +// launched from THIS directory falls back to this file and uses it as its test +// config. Guard that entry point (route 4 in +// scripts/vitest-invocation-guard.mjs, which carries the mechanism and the +// measurement). Gated on `VITEST` because the same file is the BUILD config: +// Vitest sets that variable when it loads a config, `vite build` does not, so +// a test run is refused and a build never is. +import { + assertCanonicalVitestInvocation, + repoRootFrom, +} from '../../scripts/vitest-invocation-guard.mjs'; + +if (process.env.VITEST) { + assertCanonicalVitestInvocation({ repoRoot: repoRootFrom(import.meta.url) }); +} + export default defineConfig({ define: { 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'production'), @@ -62,9 +78,4 @@ export default defineConfig({ }, }, }, - test: { - globals: true, - environment: 'jsdom', - setupFiles: './vitest.setup.ts', - }, }); diff --git a/packages/plugin-report/vitest.setup.ts b/packages/plugin-report/vitest.setup.ts deleted file mode 100644 index 9347c17d21..0000000000 --- a/packages/plugin-report/vitest.setup.ts +++ /dev/null @@ -1,7 +0,0 @@ -import '@testing-library/jest-dom'; -import { cleanup } from '@testing-library/react'; -import { afterEach } from 'vitest'; - -afterEach(() => { - cleanup(); -}); diff --git a/packages/plugin-timeline/package.json b/packages/plugin-timeline/package.json index 2dd25ee316..d139dd54f1 100644 --- a/packages/plugin-timeline/package.json +++ b/packages/plugin-timeline/package.json @@ -25,8 +25,8 @@ }, "scripts": { "build": "vite build", - "test": "vitest run", - "test:watch": "vitest", + "test": "vitest run --root ../.. packages/plugin-timeline/", + "test:watch": "vitest --root ../.. packages/plugin-timeline/", "type-check": "tsc --noEmit && tsc -p tsconfig.test.json", "lint": "eslint ." }, diff --git a/packages/plugin-timeline/src/__tests__/timeline-date-locale.test.tsx b/packages/plugin-timeline/src/__tests__/timeline-date-locale.test.tsx index 70bd6635e7..c2ebdbadb6 100644 --- a/packages/plugin-timeline/src/__tests__/timeline-date-locale.test.tsx +++ b/packages/plugin-timeline/src/__tests__/timeline-date-locale.test.tsx @@ -231,8 +231,10 @@ describe('non-date rendering is undisturbed (green both sides)', () => { it('titles, descriptions and row labels are untouched — zh', () => { // `getByText` throws when the node is absent, so it carries the assertion; // `toBeDefined()` is the package's spelling for it (jest-dom's matchers are - // loaded by `vitest.setup.ts` at runtime but not declared to `tsc -p - // tsconfig.test.json`, so no test here uses them). + // loaded by the repo-root `vitest.setup.dom-light.tsx` at runtime but not + // declared to `tsc -p tsconfig.test.json`, so no test here uses them). That + // used to read `vitest.setup.ts`, this package's own one-line copy, which + // objectui#3240 deleted along with the config that was its only loader. renderSession('zh', ); expect(screen.getByText('Beta Release')).toBeDefined(); expect(screen.getByText('Released beta version to testers')).toBeDefined(); diff --git a/packages/plugin-timeline/vite.config.ts b/packages/plugin-timeline/vite.config.ts index f4100f50ff..7c630f3a26 100644 --- a/packages/plugin-timeline/vite.config.ts +++ b/packages/plugin-timeline/vite.config.ts @@ -14,6 +14,22 @@ import { resolve } from 'path'; import { createDtsExplicitExtensions } from '../../scripts/vite-dts-explicit-extensions.ts'; import { createDtsFailOnTypeErrors } from '../../scripts/vite-dts-fail-on-type-errors.ts'; +// objectui#3240 — with the per-package `vitest.config.*` files gone, Vitest +// launched from THIS directory falls back to this file and uses it as its test +// config. Guard that entry point (route 4 in +// scripts/vitest-invocation-guard.mjs, which carries the mechanism and the +// measurement). Gated on `VITEST` because the same file is the BUILD config: +// Vitest sets that variable when it loads a config, `vite build` does not, so +// a test run is refused and a build never is. +import { + assertCanonicalVitestInvocation, + repoRootFrom, +} from '../../scripts/vitest-invocation-guard.mjs'; + +if (process.env.VITEST) { + assertCanonicalVitestInvocation({ repoRoot: repoRootFrom(import.meta.url) }); +} + export default defineConfig({ plugins: [ react(), @@ -70,10 +86,4 @@ export default defineConfig({ }, }, }, - test: { - setupFiles: ['../../vitest.setup.tsx'], - passWithNoTests: true, - globals: true, - environment: 'jsdom', - }, }); diff --git a/packages/plugin-timeline/vitest.config.ts b/packages/plugin-timeline/vitest.config.ts deleted file mode 100644 index 28840604c4..0000000000 --- a/packages/plugin-timeline/vitest.config.ts +++ /dev/null @@ -1,34 +0,0 @@ -/// -import { defineConfig } from 'vite'; -import react from '@vitejs/plugin-react'; - -// objectui#5406 — this config is STANDALONE: it does not import the root -// `vitest.config.mts`, so nothing routed a package-cwd run here through the -// invocation guard, the way a root-importing config does as a side effect of -// the import. Without the call below, `cd` into this package and `pnpm exec -// vitest run` executes the suite under a config CI never uses — no -// `@object-ui/*` alias table, a different setup file, no project split — so -// its green says nothing about CI. (Measured on the sibling `plugin-grid`, -// whose config was identical to this one: one such run printed -// `Test Files 1 passed (1)` / `Tests 5 passed (5)` and exited 0.) -// -// Deleting this reopens the hole for this package alone, silently — which is -// why it is enforced rather than written down: -// `scripts/__tests__/vitest-invocation-guard.test.ts` walks every -// `vitest.config.*` in the repo and fails on any that neither imports the root -// config nor calls the guard. -import { - assertCanonicalVitestInvocation, - repoRootFrom, -} from '../../scripts/vitest-invocation-guard.mjs'; - -assertCanonicalVitestInvocation({ repoRoot: repoRootFrom(import.meta.url) }); - -export default defineConfig({ - plugins: [react()], - test: { - environment: 'happy-dom', - globals: true, - setupFiles: ['./vitest.setup.ts'], - }, -}); diff --git a/packages/plugin-timeline/vitest.setup.ts b/packages/plugin-timeline/vitest.setup.ts deleted file mode 100644 index 7b0828bfa8..0000000000 --- a/packages/plugin-timeline/vitest.setup.ts +++ /dev/null @@ -1 +0,0 @@ -import '@testing-library/jest-dom'; diff --git a/packages/plugin-tree/package.json b/packages/plugin-tree/package.json index 3efeb26d07..6691fc2771 100644 --- a/packages/plugin-tree/package.json +++ b/packages/plugin-tree/package.json @@ -25,8 +25,8 @@ }, "scripts": { "build": "vite build", - "test": "vitest run", - "test:watch": "vitest", + "test": "vitest run --root ../.. packages/plugin-tree/", + "test:watch": "vitest --root ../.. packages/plugin-tree/", "type-check": "tsc --noEmit && tsc -p tsconfig.test.json", "lint": "eslint ." }, diff --git a/packages/plugin-tree/vite.config.ts b/packages/plugin-tree/vite.config.ts index 2795132ac9..2ab72d4c48 100644 --- a/packages/plugin-tree/vite.config.ts +++ b/packages/plugin-tree/vite.config.ts @@ -14,6 +14,22 @@ import { resolve } from 'path'; import { createDtsExplicitExtensions } from '../../scripts/vite-dts-explicit-extensions.ts'; import { createDtsFailOnTypeErrors } from '../../scripts/vite-dts-fail-on-type-errors.ts'; +// objectui#3240 — with the per-package `vitest.config.*` files gone, Vitest +// launched from THIS directory falls back to this file and uses it as its test +// config. Guard that entry point (route 4 in +// scripts/vitest-invocation-guard.mjs, which carries the mechanism and the +// measurement). Gated on `VITEST` because the same file is the BUILD config: +// Vitest sets that variable when it loads a config, `vite build` does not, so +// a test run is refused and a build never is. +import { + assertCanonicalVitestInvocation, + repoRootFrom, +} from '../../scripts/vitest-invocation-guard.mjs'; + +if (process.env.VITEST) { + assertCanonicalVitestInvocation({ repoRoot: repoRootFrom(import.meta.url) }); +} + export default defineConfig({ plugins: [ react(), @@ -65,8 +81,4 @@ export default defineConfig({ }, }, }, - test: { - passWithNoTests: true, - environment: 'jsdom', - }, }); diff --git a/packages/plugin-view/package.json b/packages/plugin-view/package.json index aedb1d31f4..bdb227bb89 100644 --- a/packages/plugin-view/package.json +++ b/packages/plugin-view/package.json @@ -16,7 +16,7 @@ }, "scripts": { "build": "vite build", - "test": "vitest run", + "test": "vitest run --root ../.. packages/plugin-view/", "type-check": "tsc --noEmit && tsc -p tsconfig.test.json", "lint": "eslint ." }, diff --git a/packages/plugin-view/vite.config.ts b/packages/plugin-view/vite.config.ts index b62ddd5b20..be01b54f9e 100644 --- a/packages/plugin-view/vite.config.ts +++ b/packages/plugin-view/vite.config.ts @@ -6,6 +6,22 @@ import { resolve } from 'path'; import { createDtsExplicitExtensions } from '../../scripts/vite-dts-explicit-extensions.ts'; import { createDtsFailOnTypeErrors } from '../../scripts/vite-dts-fail-on-type-errors.ts'; +// objectui#3240 — with the per-package `vitest.config.*` files gone, Vitest +// launched from THIS directory falls back to this file and uses it as its test +// config. Guard that entry point (route 4 in +// scripts/vitest-invocation-guard.mjs, which carries the mechanism and the +// measurement). Gated on `VITEST` because the same file is the BUILD config: +// Vitest sets that variable when it loads a config, `vite build` does not, so +// a test run is refused and a build never is. +import { + assertCanonicalVitestInvocation, + repoRootFrom, +} from '../../scripts/vitest-invocation-guard.mjs'; + +if (process.env.VITEST) { + assertCanonicalVitestInvocation({ repoRoot: repoRootFrom(import.meta.url) }); +} + export default defineConfig({ define: { 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'production'), @@ -48,7 +64,4 @@ export default defineConfig({ }, }, }, - test: { - passWithNoTests: true, - }, }); diff --git a/packages/plugin-view/vitest.config.ts b/packages/plugin-view/vitest.config.ts deleted file mode 100644 index 28840604c4..0000000000 --- a/packages/plugin-view/vitest.config.ts +++ /dev/null @@ -1,34 +0,0 @@ -/// -import { defineConfig } from 'vite'; -import react from '@vitejs/plugin-react'; - -// objectui#5406 — this config is STANDALONE: it does not import the root -// `vitest.config.mts`, so nothing routed a package-cwd run here through the -// invocation guard, the way a root-importing config does as a side effect of -// the import. Without the call below, `cd` into this package and `pnpm exec -// vitest run` executes the suite under a config CI never uses — no -// `@object-ui/*` alias table, a different setup file, no project split — so -// its green says nothing about CI. (Measured on the sibling `plugin-grid`, -// whose config was identical to this one: one such run printed -// `Test Files 1 passed (1)` / `Tests 5 passed (5)` and exited 0.) -// -// Deleting this reopens the hole for this package alone, silently — which is -// why it is enforced rather than written down: -// `scripts/__tests__/vitest-invocation-guard.test.ts` walks every -// `vitest.config.*` in the repo and fails on any that neither imports the root -// config nor calls the guard. -import { - assertCanonicalVitestInvocation, - repoRootFrom, -} from '../../scripts/vitest-invocation-guard.mjs'; - -assertCanonicalVitestInvocation({ repoRoot: repoRootFrom(import.meta.url) }); - -export default defineConfig({ - plugins: [react()], - test: { - environment: 'happy-dom', - globals: true, - setupFiles: ['./vitest.setup.ts'], - }, -}); diff --git a/packages/plugin-view/vitest.setup.ts b/packages/plugin-view/vitest.setup.ts deleted file mode 100644 index 7b0828bfa8..0000000000 --- a/packages/plugin-view/vitest.setup.ts +++ /dev/null @@ -1 +0,0 @@ -import '@testing-library/jest-dom'; diff --git a/packages/providers/package.json b/packages/providers/package.json index 07d2810639..19c97f85de 100644 --- a/packages/providers/package.json +++ b/packages/providers/package.json @@ -25,7 +25,7 @@ }, "scripts": { "build": "tsc && node ../../scripts/check-dist-completeness.mjs", - "test": "vitest run", + "test": "vitest run --root ../.. packages/providers/", "type-check": "tsc --noEmit && tsc -p tsconfig.test.json", "lint": "eslint ." }, diff --git a/packages/react-runtime/package.json b/packages/react-runtime/package.json index 80a883b6d9..2c6f1aa917 100644 --- a/packages/react-runtime/package.json +++ b/packages/react-runtime/package.json @@ -20,7 +20,7 @@ ], "scripts": { "build": "tsc && node ../../scripts/check-dist-completeness.mjs", - "test": "vitest run", + "test": "vitest run --root ../.. packages/react-runtime/", "type-check": "tsc --noEmit && tsc -p tsconfig.test.json", "lint": "eslint ." }, diff --git a/packages/react/package.json b/packages/react/package.json index 3b574b9566..94cd0a4874 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -25,7 +25,7 @@ }, "scripts": { "build": "tsc && node ../../scripts/check-dist-completeness.mjs", - "test": "vitest run", + "test": "vitest run --root ../.. packages/react/", "type-check": "tsc --noEmit && tsc -p tsconfig.test.json", "lint": "eslint ." }, diff --git a/packages/react/vitest.config.ts b/packages/react/vitest.config.ts deleted file mode 100644 index 2bec67a5d9..0000000000 --- a/packages/react/vitest.config.ts +++ /dev/null @@ -1,2 +0,0 @@ -import rootConfig from '../../vitest.config.mts'; -export default rootConfig; diff --git a/packages/runner/vite.config.ts b/packages/runner/vite.config.ts index c366bd0ffa..4664e4865f 100644 --- a/packages/runner/vite.config.ts +++ b/packages/runner/vite.config.ts @@ -10,6 +10,22 @@ import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' import path from 'path' +// objectui#3240 — with the per-package `vitest.config.*` files gone, Vitest +// launched from THIS directory falls back to this file and uses it as its test +// config. Guard that entry point (route 4 in +// scripts/vitest-invocation-guard.mjs, which carries the mechanism and the +// measurement). Gated on `VITEST` because the same file is the BUILD config: +// Vitest sets that variable when it loads a config, `vite build` does not, so +// a test run is refused and a build never is. +import { + assertCanonicalVitestInvocation, + repoRootFrom, +} from '../../scripts/vitest-invocation-guard.mjs'; + +if (process.env.VITEST) { + assertCanonicalVitestInvocation({ repoRoot: repoRootFrom(import.meta.url) }); +} + // https://vitejs.dev/config/ export default defineConfig({ plugins: [react()], diff --git a/packages/sdui-parser/package.json b/packages/sdui-parser/package.json index fbbc741339..d76b96ed64 100644 --- a/packages/sdui-parser/package.json +++ b/packages/sdui-parser/package.json @@ -26,7 +26,7 @@ ], "scripts": { "build": "tsc && node ../../scripts/check-dist-completeness.mjs", - "test": "vitest run", + "test": "vitest run --root ../.. packages/sdui-parser/", "type-check": "tsc --noEmit && tsc -p tsconfig.test.json", "lint": "eslint ." }, diff --git a/packages/types/vitest.config.ts b/packages/types/vitest.config.ts deleted file mode 100644 index 2bec67a5d9..0000000000 --- a/packages/types/vitest.config.ts +++ /dev/null @@ -1,2 +0,0 @@ -import rootConfig from '../../vitest.config.mts'; -export default rootConfig; diff --git a/packages/vscode-extension/package.json b/packages/vscode-extension/package.json index 110f58c299..4a7f91ae4e 100644 --- a/packages/vscode-extension/package.json +++ b/packages/vscode-extension/package.json @@ -195,8 +195,8 @@ "type-check": "tsc --noEmit", "lint": "eslint .", "dev": "tsup --watch", - "test": "vitest run --passWithNoTests", - "test:watch": "vitest", + "test": "vitest run --passWithNoTests --root ../.. packages/vscode-extension/", + "test:watch": "vitest --root ../.. packages/vscode-extension/", "package": "vsce package", "publish": "vsce publish" }, diff --git a/scripts/__tests__/helpers/vitest-config-program.ts b/scripts/__tests__/helpers/vitest-config-program.ts index 9b6a8f7ce9..4ac82251b8 100644 --- a/scripts/__tests__/helpers/vitest-config-program.ts +++ b/scripts/__tests__/helpers/vitest-config-program.ts @@ -43,9 +43,63 @@ import { rel, repoRoot, type WorkspacePackage } from './turbo-inputs'; * other packages' `src/`, so the true read set of any package's test run is * most of the repository. Requiring that is not a stricter version of this * derivation, it is a different (and wrong) one — turbo's - * `dependsOn: ["^build"]` and per-package `$TURBO_DEFAULT$` already answer + * `dependsOn: ["^build"]` and per-package `$TURBO_DEFAULTimport fs from 'node:fs'; +import path from 'node:path'; +import { outOfPackageProgramFiles } from './config-program'; +import { rel, repoRoot, type WorkspacePackage } from './turbo-inputs'; + +/** + * Derives a workspace package's **Vitest configuration program** — the set of + * files Vitest reads in order to know how that package's tests run. + * + * Extracted as its own module rather than folded into the guard that consumes + * it (`../turbo-test-inputs.test.ts`, objectui#4178) for two reasons. It is a + * mechanism, not a policy: the guard decides what must be true of the result, + * this file decides only what the result IS. And a derivation that can only be + * exercised through the assertions it feeds is a derivation nobody can probe — + * which is how a sweep quietly degrades to returning nothing while every + * assertion over it stays green. + * + * ## The program + * + * 1. The config file Vitest resolves for the package's `test` script, + * following the script's own `--root` / `--config` flags and then Vitest's + * upward search. + * 2. Every file that program statically imports by RELATIVE specifier, + * transitively. + * 3. Every file it DESIGNATES through a file-valued Vitest option + * (`setupFiles`, `globalSetup`, `projects`, `workspace`) — themselves + * walked as program files, which is how `vitest.setup.tsx` pulls in the + * `vitest.setup.dom.tsx` it imports, and how the root config's `projects` + * entry pulls in `apps/console/vitest.config.ts` and the two + * `scripts/vite-*.ts` plugins that one imports. + * + * Steps 2 and 3 are the generic walk, shared with the ESLint and build + * derivations since objectui#4184 / objectui#4185: see `./config-program.ts`, + * whose docblock carries the narrowings all of them share (bare specifiers not + * followed, designation key-directed, unresolvable designated paths throw). + * What is specific to Vitest, and lives here, is only which file the walk + * STARTS from and which option names designate files. + * + * ## Narrowings specific to this derivation + * + already answer * source. The failure being guarded is "change the SHARED TEST HARNESS, get * a stale verdict", and the harness is exactly this program. + * + * ⚠️ That sentence is why the `test` task still `dependsOn: ["^build"]`, and + * objectui#3240 re-derived the reason rather than inheriting it. The reason + * OF RECORD was resolution: each package had its own vitest config with no + * alias table, so a test run reached its siblings through their `dist`. + * objectui#3240 deleted those configs — every package's `test` script now + * runs the root config, whose alias map points at `src`, and CI's `pnpm test` + * builds nothing at all — so nothing needs `^build` to RESOLVE any more, and + * dropping it looked like free speed. It is not: `^build` is the only edge + * that puts a dependency's sources into a dependent's `test` cache key. Drop + * it and the narrowing above stops being true — a change under + * `packages/core/src` would no longer move `@object-ui/plugin-grid#test`'s + * key, and `turbo run test` would replay a stale green over code it never + * ran. The edge stays, for the second reason instead of the first. * - The root config holds ~45 concrete test-file paths in `domTsTests` / * `heavyDomTests`; those are `include` / `exclude` inputs to project * definitions, covered by their own packages' inputs — not files this @@ -58,10 +112,12 @@ import { rel, repoRoot, type WorkspacePackage } from './turbo-inputs'; * Vitest's config candidates, in its own precedence order. * * Mirrors `CONFIG_NAMES` x `CONFIG_EXTENSIONS` from `vitest/dist/chunks/ - * constants.*.js`. The order is load-bearing: `packages/components` has BOTH a + * constants.*.js`. The order is load-bearing: `apps/console` has BOTH a * `vitest.config.ts` and a `vite.config.ts`, and only the first is the config * Vitest reads (the second arrives as one of its imports, which is a different - * fact). + * fact). The SECOND name matters more since objectui#3240 deleted the + * per-package vitest configs: for every `packages/*` the candidate that hits is + * now `vite.config.ts`, which is why each of those calls the invocation guard. */ export const CONFIG_NAMES = ['vitest.config', 'vite.config']; export const CONFIG_EXTENSIONS = ['.ts', '.mts', '.cts', '.js', '.mjs', '.cjs']; diff --git a/scripts/__tests__/turbo-test-inputs.test.ts b/scripts/__tests__/turbo-test-inputs.test.ts index 63e6ec9956..cb8e359598 100644 --- a/scripts/__tests__/turbo-test-inputs.test.ts +++ b/scripts/__tests__/turbo-test-inputs.test.ts @@ -10,7 +10,6 @@ import { } from './helpers/turbo-inputs'; import { CONFIG_FILES, - invocationFor, outOfPackageFiles, resolveConfigFile, } from './helpers/vitest-config-program'; @@ -38,11 +37,13 @@ import { * `vitest.config.*` and `vitest.setup.*` read as if they covered the Vitest * configuration. They do not: they resolve inside the package, while the files * that actually decide how a package's tests run live at the repo root. Every - * package reaches them — `packages/core/vitest.config.ts` is two lines that - * re-export `../../vitest.config.mts`, and a package with no config of its own - * (`packages/app-shell`) resolves upward to that same root file, because Vitest - * looks for `vitest.config.*` / `vite.config.*` in each directory from its root - * upward and takes the first hit. + * package reaches them. Since objectui#3240 it reaches them BY NAME: every + * package's `test` script is `vitest run --root ../.. packages//`, so the + * resolution starts at the repo root and lands on `vitest.config.mts` directly. + * Before that it arrived by the walk — a two-line re-export in + * `packages/core/vitest.config.ts`, or, for a package with no config of its own + * (`packages/app-shell`), Vitest looking for `vitest.config.*` / `vite.config.*` + * in each directory from its root upward and taking the first hit. * * Measured on `main` at eb5f8cea0, `@object-ui/core#test`: * @@ -116,8 +117,9 @@ describe('turbo `test` inputs cover every out-of-package file (objectui#4178)', expect( reaching.map((pkg) => pkg.name), 'no package configuration program reaches outside its directory — the derivation has ' + - 'stopped working, because at minimum packages/core/vitest.config.ts is a one-line ' + - 're-export of ../../vitest.config.mts', + 'stopped working. Since objectui#3240 EVERY package reaches outside: each test script ' + + 'is `vitest run --root ../.. /`, so the config it resolves is the repo-root ' + + 'vitest.config.mts, which is outside every package directory by construction', ).not.toHaveLength(0); }); @@ -182,12 +184,28 @@ describe('turbo `test` inputs cover every out-of-package file (objectui#4178)', * `vitest.config.ts` and `vite.config.ts` must derive from the former) and * the upward walk itself. */ + // All three pins below root the resolution at the PACKAGE DIRECTORY + // explicitly rather than deriving it from the package's `test` script. Since + // objectui#3240 every one of those scripts names `--root ../..`, so a pin + // driven from them would assert `vitest.config.mts` for the trivial reason + // that the script already said so — a control that has stopped controlling. + // The candidate walk still decides which config a bare `pnpm exec vitest` + // typed inside a package picks up, which is what the invocation guard's + // route 4 rests on, so it is still worth pinning; it just has to be asked + // directly now. + // `CONFIG_FILES` is the FULL candidate list — `vitest.config.*` AND + // `vite.config.*`, in Vitest's own precedence order. The two pins below need + // the halves apart, so name the vitest-only spellings once here. + const VITEST_ONLY_CONFIG_FILES = CONFIG_FILES.filter((name) => + name.startsWith('vitest.config.'), + ); + it('resolves a configless package upward to the repo-root config', () => { const configless = PACKAGES.find( (pkg) => !CONFIG_FILES.some((name) => fs.existsSync(path.join(pkg.dir, name))), ); expect(configless, 'no configless package left to pin the upward walk with').toBeTruthy(); - expect(rel(resolveConfigFile(invocationFor(configless!.dir, configless!.script))!)).toBe( + expect(rel(resolveConfigFile({ root: configless!.dir, config: null })!)).toBe( 'vitest.config.mts', ); }); @@ -199,8 +217,25 @@ describe('turbo `test` inputs cover every out-of-package file (objectui#4178)', fs.existsSync(path.join(pkg.dir, 'vite.config.ts')), ); expect(both, 'no package holds both config spellings any more').toBeTruthy(); - expect(rel(resolveConfigFile(invocationFor(both!.dir, both!.script))!)).toBe( + expect(rel(resolveConfigFile({ root: both!.dir, config: null })!)).toBe( `${rel(both!.dir)}/vitest.config.ts`, ); }); + + it('falls back to vite.config.* where a package has no vitest config', () => { + // The other half of the same premise, and the half objectui#3240 turned on: + // with the per-package vitest configs deleted, THIS is the file a + // package-cwd run now resolves to. It is a build config, which is why every + // one of them calls the invocation guard — pinned in + // scripts/__tests__/vitest-invocation-guard.test.ts. + const viteOnly = PACKAGES.find( + (pkg) => + !VITEST_ONLY_CONFIG_FILES.some((name) => fs.existsSync(path.join(pkg.dir, name))) && + fs.existsSync(path.join(pkg.dir, 'vite.config.ts')), + ); + expect(viteOnly, 'no vite-config-only package left to pin the fallback with').toBeTruthy(); + expect(rel(resolveConfigFile({ root: viteOnly!.dir, config: null })!)).toBe( + `${rel(viteOnly!.dir)}/vite.config.ts`, + ); + }); }); diff --git a/scripts/__tests__/vitest-invocation-guard.test.ts b/scripts/__tests__/vitest-invocation-guard.test.ts index f2f7043c81..7a9c2f2700 100644 --- a/scripts/__tests__/vitest-invocation-guard.test.ts +++ b/scripts/__tests__/vitest-invocation-guard.test.ts @@ -228,40 +228,37 @@ describe('the root config actually wires the guard', () => { }); /** - * objectui#5406 — wiring the ROOT config is not the same as wiring the repo. + * objectui#5406 / objectui#3240 — wiring the ROOT config is not the same as + * wiring the repo, and which files can be picked up INSTEAD of it is a property + * of the tree, not of a docstring. * - * Vitest loads the config it finds in the directory it was launched from. The - * call in `vitest.config.mts` therefore only reaches a package-cwd run when - * that package's config resolution ends at the root file, and the guard's own + * Vitest loads whatever config it finds in the directory it was launched from. + * The call in `vitest.config.mts` therefore only reaches a package-cwd run when + * that directory's config resolution ends at the root file. The guard's own * docstring used to assert that it always does ("Every per-package - * `vitest.config.ts` re-exports the root config"). Measured on the tree that - * shipped that sentence, by running `pnpm exec vitest run` from each directory - * carrying a config: + * `vitest.config.ts` re-exports the root config") and was wrong about 11 of the + * 18 non-root configs — they were standalone, and a run under them collected + * the package's own files with NO alias table, going green under a config CI + * never used. * - * REFUSED apps/console, examples/schema-catalog, packages/components, - * packages/core, packages/fields, packages/plugin-dashboard, - * packages/react, packages/types (8 — they import the root config) - * REFUSED packages/app-shell, packages/mobile (no config; the lookup walks up) - * ACCEPTED packages/plugin-{calendar,charts,detail,form,gantt,grid,kanban, - * list,map,timeline,view} (11 — standalone configs) + * objectui#3240 removed the divergence at its source: the per-package vitest + * configs are gone and the root config is the single entry, reached from a + * package by `vitest run --root ../.. packages//`. That deletion moved the + * question rather than answering it, because Vitest's fallback does not stop at + * `vitest.config.*` — with none present it uses the directory's `vite.config.*`, + * which every one of these packages has. Measured on `main` before the change, + * in a package that already had no vitest config: * - * The accepted set is not a technicality. Those 11 configs declare `happy-dom` - * + `globals` + a local `vitest.setup.ts` and NO alias table, where the root - * config maps ~40 `@object-ui/*` specifiers at a sibling package's `src/`. So a - * run there both collects the package's own files AND resolves them - * differently from CI — the false green this guard exists to refuse, arriving - * through the door the guard was documented to have locked. Measured: + * cd packages/plugin-ai && pnpm exec vitest run + * => RUN v4.1.10 /…/packages/plugin-ai <- root is the PACKAGE + * (no guard output at all) * - * cd packages/plugin-grid - * pnpm exec vitest run src/__tests__/ObjectGrid.exportOptionsKeys.test.ts - * => RUN v4.1.10 /…/packages/plugin-grid - * Test Files 1 passed (1) - * Tests 5 passed (5) # exit 0, no guard output at all - * - * The 11 now call the guard themselves. This block is what stops number 12 from - * arriving the same way: the coverage claim is checked against the tree instead - * of being restated in a comment. Same defect class as objectui#3944/#3904 — - * configuration that declares a property nothing enforces. + * So deleting 14 vitest configs would have put 14 more packages on that route — + * widening the hole in the name of closing it. Both file kinds are therefore + * walked below, and both blocks carry their own non-vacuity control: an + * enumeration that silently finds nothing reports success over nothing, which is + * the same defect class (a property declared and unenforced) in the checker + * rather than in the thing checked. */ describe('objectui#5406 — every vitest config in the repo routes through the guard', () => { /** Directories that never hold a config we control (or hold copies of other branches). */ @@ -281,22 +278,24 @@ describe('objectui#5406 — every vitest config in the repo routes through the g /** `vitest.config.ts` / `.mts` / `.js` / … — every spelling Vitest will load. */ const CONFIG_NAME = /^vitest\.config\.(c|m)?[jt]s$/; + /** The fallback Vitest uses when the directory has no `vitest.config.*`. */ + const VITE_CONFIG_NAME = /^vite\.config\.(c|m)?[jt]s$/; - function findConfigs(dir: string, out: string[] = []): string[] { + function findFiles(match: RegExp, dir: string, out: string[] = []): string[] { for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { if (entry.isDirectory()) { // `.wt-*` are in-repo worktrees of OTHER branches (the root config // excludes them from every project for the same reason). if (SKIP_DIRS.has(entry.name) || entry.name.startsWith('.wt-')) continue; - findConfigs(path.join(dir, entry.name), out); - } else if (entry.isFile() && CONFIG_NAME.test(entry.name)) { + findFiles(match, path.join(dir, entry.name), out); + } else if (entry.isFile() && match.test(entry.name)) { out.push(path.relative(repoRoot, path.join(dir, entry.name)).split(path.sep).join('/')); } } return out; } - const configs = findConfigs(repoRoot); + const configs = findFiles(CONFIG_NAME, repoRoot); const rootConfig = 'vitest.config.mts'; const packageConfigs = configs.filter((c) => c !== rootConfig); @@ -304,8 +303,8 @@ describe('objectui#5406 — every vitest config in the repo routes through the g // Match the IMPORT SPECIFIER, not a mention of the filename. A bare // `text.includes('vitest.config.mts')` was the first spelling here and it was - // wrong in the direction that matters: the eleven standalone configs name the - // root file in the comment explaining why they do NOT import it, so every one + // wrong in the direction that matters: the eleven standalone configs named the + // root file in the comment explaining why they did NOT import it, so every one // of them classified as route 2 and the "no config skips the guard" case went // vacuously green over exactly the configs it exists to catch. Pinned below. const specifier = (module: string) => @@ -315,7 +314,7 @@ describe('objectui#5406 — every vitest config in the repo routes through the g /** Route 2: importing the root config runs its module scope, guard included. */ const importsRootConfig = (rel: string) => ROOT_CONFIG_IMPORT.test(read(rel)); - /** Route 3: the config imports the guard and calls it itself. */ + /** Routes 3 and 4: the config imports the guard and calls it itself. */ const callsGuard = (rel: string) => { const text = read(rel); return GUARD_IMPORT.test(text) && text.includes('assertCanonicalVitestInvocation('); @@ -323,12 +322,11 @@ describe('objectui#5406 — every vitest config in the repo routes through the g it('finds the configs — the walk is not silently empty', () => { // A guard whose enumeration breaks reports success over nothing. Pin that - // the walk reaches the root file, reaches into subdirectories, and returns - // a count in the right order of magnitude (19 at the time of writing). + // the walk reaches the root file and returns the shape this repo has since + // objectui#3240: exactly one non-root vitest config, `apps/console`'s, which + // the root `projects` array also names by absolute path. expect(configs).toContain(rootConfig); - expect(packageConfigs.length).toBeGreaterThanOrEqual(12); - expect(packageConfigs.some((c) => c.startsWith('packages/'))).toBe(true); - expect(packageConfigs.some((c) => c.startsWith('apps/'))).toBe(true); + expect(packageConfigs).toEqual(['apps/console/vitest.config.ts']); }); it('leaves no config able to skip the guard', () => { @@ -345,43 +343,145 @@ describe('objectui#5406 — every vitest config in the repo routes through the g ).toEqual([]); }); - it('still has both routes represented, so neither branch above is dead', () => { - // Without this, deleting every standalone config (or every root-importing - // one) would leave half the check above vacuously true. - expect(packageConfigs.filter(importsRootConfig).length).toBeGreaterThan(0); - expect(packageConfigs.filter((c) => !importsRootConfig(c)).length).toBeGreaterThan(0); - // The two configs this card measured, one per route. If either legitimately - // changes route, move it here rather than dropping the pin. - expect(importsRootConfig('packages/core/vitest.config.ts')).toBe(true); - expect(callsGuard('packages/plugin-grid/vitest.config.ts')).toBe(true); - - // …and the two routes are told apart by the IMPORT, not by the filename - // appearing somewhere in the file. plugin-grid's comment names - // `vitest.config.mts` while deliberately not importing it; a substring - // check reads that as route 2 and stops looking at the very configs this - // block exists for. - expect(read('packages/plugin-grid/vitest.config.ts')).toContain('vitest.config.mts'); - expect(importsRootConfig('packages/plugin-grid/vitest.config.ts')).toBe(false); - }); - - it('has every self-calling config derive the repo root instead of counting `..`', () => { - // `repoRoot: path.resolve(__dirname, '../..')` fails SILENTLY when the - // count is wrong: the resolved directory exists, the comparison runs, and - // the verdict is computed against the wrong root. `repoRootFrom` searches - // for the landmark and throws when it is not there. - const selfCalling = packageConfigs.filter((c) => !importsRootConfig(c) && callsGuard(c)); - expect(selfCalling.length).toBeGreaterThan(0); - for (const rel of selfCalling) { + it('routes the one surviving config through the root config, not around it', () => { + // Non-vacuity for the check above: with a single config left, "no config + // skips the guard" would also hold if that config were classified by a + // predicate that answers true for anything. Assert the route it actually + // takes, and that the discriminator is the IMPORT rather than the filename + // appearing somewhere in the file. + expect(importsRootConfig('apps/console/vitest.config.ts')).toBe(true); + expect(ROOT_CONFIG_IMPORT.test('// mentions vitest.config.mts in prose')).toBe(false); + }); + + it('adds no per-package vitest config back under packages/ (objectui#3240)', () => { + // The card this deletion came from measured the population growing while it + // sat held: 17 configs at filing, 19 non-root by the time it ran. A new one + // would not fail anything above — it would simply have to call the guard — + // so the thing that was actually ruled is pinned here rather than left to + // review. A package needing different test semantics states them as a + // PROJECT in `vitest.config.mts`, where one run still yields one verdict. + const perPackage = packageConfigs.filter((c) => c.startsWith('packages/')); + expect( + perPackage, + 'objectui#3240 deleted every packages/*/vitest.config.* so that one config gives one ' + + 'verdict. These reintroduce a second entry point:\n ' + + perPackage.join('\n ') + ).toEqual([]); + }); +}); + +/** + * objectui#3240, route 4 — the fallback that had to be closed before the + * per-package vitest configs could be deleted. + * + * With no `vitest.config.*` in a directory Vitest does not go straight to the + * root config: it first takes that directory's `vite.config.*`. Every package + * under `packages/` has one, and they are BUILD configs — a partial + * `resolve.alias`, and until this card a vestigial `test` block carrying + * `passWithNoTests: true` and a setup file the root config does not use. A run + * there is the same false green objectui#5406 refused, reached through a + * different door. + * + * The guard call in them is gated on `process.env.VITEST` because the same file + * builds the package. Measured both ways at config-load time: under + * `vitest run` it is `"true"`, under `vite build` it is `undefined` — so the + * gate refuses a test run and never a build. That gating is asserted here too: + * an ungated call would be found by the "calls the guard" check above while + * breaking every package build, which no test in this file would notice. + */ +describe('objectui#3240 — every packages/* vite.config routes through the guard too', () => { + const SKIP_DIRS = new Set(['node_modules', 'dist', 'build', 'coverage', '.turbo']); + + const packagesDir = path.join(repoRoot, 'packages'); + const viteConfigs = fs + .readdirSync(packagesDir, { withFileTypes: true }) + .filter((e) => e.isDirectory() && !SKIP_DIRS.has(e.name)) + .flatMap((e) => + fs + .readdirSync(path.join(packagesDir, e.name), { withFileTypes: true }) + .filter((f) => f.isFile() && /^vite\.config\.(c|m)?[jt]s$/.test(f.name)) + .map((f) => `packages/${e.name}/${f.name}`) + ) + .sort(); + + const read = (rel: string) => fs.readFileSync(path.join(repoRoot, rel), 'utf8'); + + it('finds them — an empty walk would pass every assertion below', () => { + // Measured at 23 when this landed. Asserted as a floor plus a named member, + // so adding a package does not fail it and a broken walk cannot pass it. + expect(viteConfigs.length).toBeGreaterThanOrEqual(20); + expect(viteConfigs).toContain('packages/plugin-grid/vite.config.ts'); + }); + + it('calls the guard from every one of them', () => { + const unguarded = viteConfigs.filter( + (rel) => !read(rel).includes('assertCanonicalVitestInvocation(') + ); + + expect( + unguarded, + 'Vitest falls back to these files when their package has no vitest.config.* — which is ' + + 'every package since objectui#3240 — so a run launched from their directory uses a ' + + 'BUILD config as its test config and nothing refuses it. Add to the top of each:\n\n' + + " import {\n assertCanonicalVitestInvocation,\n repoRootFrom,\n } from '../../scripts/vitest-invocation-guard.mjs';\n\n" + + ' if (process.env.VITEST) {\n' + + ' assertCanonicalVitestInvocation({ repoRoot: repoRootFrom(import.meta.url) });\n' + + ' }\n\n' + + 'Files:\n ' + + unguarded.join('\n ') + ).toEqual([]); + }); + + it('gates that call on VITEST, so `vite build` is never refused', () => { + const ungated = viteConfigs.filter((rel) => { + const text = read(rel); + if (!text.includes('assertCanonicalVitestInvocation(')) return false; + return !/if\s*\(process\.env\.VITEST\)\s*\{\s*\n\s*assertCanonicalVitestInvocation\(/.test(text); + }); + + expect( + ungated, + 'These call the guard unconditionally. The same file is the package BUILD config, and ' + + '`vite build` runs with the cwd inside the package — so an ungated call refuses every ' + + 'build of that package. Wrap it in `if (process.env.VITEST) { … }`:\n ' + + ungated.join('\n ') + ).toEqual([]); + }); + + it('derives the repo root instead of counting `..`', () => { + // `repoRoot: path.resolve(__dirname, '../..')` fails SILENTLY when the count + // is wrong: the resolved directory exists, the comparison runs, and the + // verdict is computed against the wrong root. `repoRootFrom` searches for + // the landmark and throws when it is not there. + for (const rel of viteConfigs) { expect(read(rel), `${rel} calls the guard with a hand-counted repo root`).toContain( 'repoRootFrom(import.meta.url)' ); } }); + + it('declares no `test` block — a build config must not carry test semantics', () => { + // What these blocks said before objectui#3240 deleted them: `happy-dom` or + // `jsdom`, `passWithNoTests: true`, and `setupFiles` pointing at a file the + // root config never loads. None of it ran under the canonical invocation, so + // it was configuration that read as live while deciding nothing — and it + // became reachable the moment the package's vitest config was removed. + const withTestBlock = viteConfigs.filter((rel) => /^\s{2}test:\s*\{/m.test(read(rel))); + + expect( + withTestBlock, + 'These declare test settings in a BUILD config. The guard above refuses the only ' + + 'invocation that would read them, so they are dead config that reads as live — and if ' + + 'the guard were ever bypassed they would silently diverge from vitest.config.mts. ' + + 'Declare a project in vitest.config.mts instead:\n ' + + withTestBlock.join('\n ') + ).toEqual([]); + }); }); -describe('repoRootFrom — the landmark search the standalone configs use', () => { +describe('repoRootFrom — the landmark search the guard-calling configs use', () => { it('resolves the repo root from a package config, at any depth', () => { - for (const rel of ['packages/plugin-grid/vitest.config.ts', 'apps/console/vitest.config.ts']) { + for (const rel of ['packages/plugin-grid/vite.config.ts', 'apps/console/vitest.config.ts']) { expect(repoRootFrom(pathToFileURL(path.join(repoRoot, rel)).href)).toBe(repoRoot); } }); @@ -398,7 +498,7 @@ describe('repoRootFrom — the landmark search the standalone configs use', () = // End-to-end over real paths — the fake-path cases above cannot catch a // repoRoot that resolves to the wrong real directory. const cwd = path.join(repoRoot, 'packages/plugin-grid'); - const derived = repoRootFrom(pathToFileURL(path.join(cwd, 'vitest.config.ts')).href); + const derived = repoRootFrom(pathToFileURL(path.join(cwd, 'vite.config.ts')).href); expect( evaluateVitestInvocation({ diff --git a/scripts/check-vi-mock-specifiers.mjs b/scripts/check-vi-mock-specifiers.mjs index cd2ba64454..846c64f4df 100644 --- a/scripts/check-vi-mock-specifiers.mjs +++ b/scripts/check-vi-mock-specifiers.mjs @@ -95,14 +95,20 @@ * ## Why the walk is not restricted to test-NAMED files * * The obvious population is the `*.test.*` / `*.spec.*` naming. It has a hole, - * measured on this tree: THREE files carrying a real call site match no such - * suffix, and TWO of those match no test-file naming convention at all, not even - * a `__tests__/` directory -- + * measured on this tree: TWO files carrying a real call site match no such + * suffix, and ONE of those matches no test-file naming convention at all, not + * even a `__tests__/` directory -- * * apps/console/dev/__tests__/setup/common-mocks.ts (suffix: no, dir: yes) - * packages/plugin-map/vitest.setup.ts (neither) * vitest.setup.base.ts (neither) * + * It was THREE until objectui#3240. `packages/plugin-map/vitest.setup.ts` also + * carried one -- a `maplibre-gl` mock duplicating the one in + * `vitest.setup.base.ts` -- and only that package's own vitest config ever + * loaded it, so it never ran under the invocation CI uses. Deleting the config + * took the copy with it. The hole this walk exists for is unchanged: a setup + * file is still exactly where a repo-wide mock gets written. + * * A setup file is exactly where a repo-wide mock gets written, and a mock helper * shared by a directory of suites is exactly where one goes unreviewed. So the * walk takes every tracked JS/TS-family source file and lets the PATTERN decide diff --git a/scripts/vitest-invocation-guard.mjs b/scripts/vitest-invocation-guard.mjs index bcb1f84345..d102ba72be 100644 --- a/scripts/vitest-invocation-guard.mjs +++ b/scripts/vitest-invocation-guard.mjs @@ -2,57 +2,66 @@ /** * Rejects the two Vitest invocations that silently produce a FALSE GREEN. * - * Called from the top of `vitest.config.mts` AND from the top of every - * per-package `vitest.config.ts` that does not lead back to it, so it covers - * EVERY entry point into this repo's Vitest: `pnpm test`, + * Called from the top of `vitest.config.mts` — the repo's ONE Vitest config + * since objectui#3240 — and from every other config file Vitest can pick up + * instead of it, so it covers EVERY entry point: `pnpm test`, * `pnpm --filter test`, `turbo run test`, and a bare `pnpm exec vitest` * typed in any directory. * - * ## How a config reaches this file — and the 11 that did not (objectui#5406) + * ## How a config reaches this file * * Vitest loads the config it finds in the directory it was launched from, so * "the root config calls the guard" only covers a package-cwd run when that - * package's own config leads back to the root file. Three routes exist; this - * docstring used to claim the first two covered everything, and was wrong - * about 11 of the 18 non-root configs: - * - * 1. NO `vitest.config.*` in the package (`packages/app-shell`, - * `packages/mobile`, ~30 others). The lookup walks up and lands on the - * root config, whose module scope runs the guard. Always covered. - * 2. A package config that IMPORTS the root config — re-exporting it - * (`packages/core`, `react`, `types`), merging a Vite config into it - * (`components`, `fields`, `plugin-dashboard`, `apps/console`), or - * stripping one key off it (`examples/schema-catalog`). Importing it - * executes its module scope, so the guard runs as a side effect. 8 - * configs, all measured refusing a package-cwd run. - * 3. A STANDALONE package config that never mentions the root file. Nothing - * imports this module, so the guard never ran: 11 configs - * (`plugin-calendar`, `-charts`, `-detail`, `-form`, `-gantt`, `-grid`, - * `-kanban`, `-list`, `-map`, `-timeline`, `-view`), each declaring its - * own `happy-dom` + `globals` + local setup and NO alias table at all. - * That is not a harmless difference: the root config maps every - * `@object-ui/*` specifier to a sibling package's `src/`, and without it - * the same import resolves through `node_modules` — a genuinely different - * config from the one CI runs. Measured before the fix, from - * `packages/plugin-grid`: - * - * pnpm exec vitest run src/__tests__/ObjectGrid.exportOptionsKeys.test.ts - * => RUN v4.1.10 /…/packages/plugin-grid <- root is the PACKAGE - * Test Files 1 passed (1) - * Tests 5 passed (5) <- exit 0, guard silent - * - * Route 3 now calls this module directly — same effect as route 2, - * without the import: + * directory's own config resolution ends at the root file. That is a claim + * about the tree, and this docstring has been WRONG about it once already + * (objectui#5406: it asserted "every per-package `vitest.config.ts` + * re-exports the root config" while eleven standalone ones did not). It is now + * ENFORCED instead of asserted — `scripts/__tests__/vitest-invocation-guard + * .test.ts` walks the repo and fails on any config taking none of the routes + * below. Read that test, not this paragraph, for what is true today. + * + * 1. NO config in the directory. The lookup walks up and lands on the root + * config, whose module scope runs the guard. This is now the common case: + * objectui#3240 deleted the 17 per-package `vitest.config.ts` files plus + * `examples/schema-catalog`'s, so no `packages/*` directory carries one. + * 2. A config that IMPORTS the root config — `apps/console/vitest.config.ts`, + * the last one left, which the root `projects` array also brings in by + * absolute path as the `@object-ui/console` project. Importing it executes + * its module scope, so the guard runs as a side effect. + * 3. A STANDALONE `vitest.config.*` that never mentions the root file. None + * exists today; the route is kept because the walk above cannot assume + * that stays true. Such a config must call this module itself: * * import { assertCanonicalVitestInvocation, repoRootFrom } * from '../../scripts/vitest-invocation-guard.mjs'; * assertCanonicalVitestInvocation({ repoRoot: repoRootFrom(import.meta.url) }); * - * Because a docstring is exactly what failed here, the claim above is now - * ENFORCED rather than written down: `scripts/__tests__/vitest-invocation-guard - * .test.ts` walks the repo for every `vitest.config.*` and fails on any that - * takes none of the three routes. A new standalone config cannot reopen the - * hole silently. + * 4. ⭐ NO `vitest.config.*` but a `vite.config.*` — the route objectui#3240 + * had to close before it could delete anything. Vitest falls back to the + * package's VITE config, which is a build config: root becomes the package + * directory, and 22 of the 23 under `packages/` carried a vestigial + * `test` block (`passWithNoTests: true`, a partial `resolve.alias`, a + * setup file the root config does not use). Measured on `main` before the + * change, from a package with no vitest config: + * + * cd packages/plugin-ai && pnpm exec vitest run + * => RUN v4.1.10 /…/packages/plugin-ai <- root is the PACKAGE + * (no guard output at all) + * + * Deleting the per-package vitest configs would have moved 14 more + * packages onto this route — widening the hole objectui#5406 closed, in + * the name of closing it. So every `packages//vite.config.ts` now calls + * this module, and the vestigial `test` blocks are gone. The call is + * gated on `process.env.VITEST`, which Vitest sets when it loads a config + * and `vite build` does not (measured both ways) — the same file has to + * keep working as the build config, and a build must never be refused. + * + * ## Not covered, deliberately + * + * `examples/byo-backend-console` and `examples/console-starter` carry a + * `vite.config.ts` and no test script. They are templates a user copies out of + * the repo, so a `../../scripts/` import would break them where it matters + * most. Unchanged by objectui#3240 — they were on route 4 before it and after. * * ## Trap 1 — Vitest launched with the cwd inside a package (objectui#3378) * @@ -110,9 +119,11 @@ * runs those same files under the root config's aliases, project split and * setup files. The defect is the divergent config, not the empty collection. * "root == repo root" is also one comparison an agent can hold in its head, - * unlike a heuristic that fires only sometimes. Whether the package-level - * `test` scripts should exist at all is objectui#3240 and not this guard's - * call; until that is decided they fail loudly instead of lying. + * unlike a heuristic that fires only sometimes. objectui#3240 has since settled + * what the package-level `test` scripts are: every one of them names the repo + * root explicitly (`vitest run --root ../.. packages//`), so they satisfy + * this comparison instead of tripping it, and `pnpm --filter test` / + * `turbo run test` run the same config as CI. * * Escape hatch, documented in AGENTS.md: `OBJECTUI_VITEST_GUARD=off`. */ @@ -395,8 +406,9 @@ export function evaluateVitestInvocation({ '的 22 个文件、报 `Test Files 22 passed (22)`,本包的一个都没跑 —— 而且没有任何', '"0 tests matched" 信号,计数是 22 不是 0。这就是所谓假绿。', '', - '`pnpm --filter test`、`turbo run test`、`cd packages/x && pnpm exec vitest`', - '都会落进这里。包级 test 脚本的存废是 objectui#3240;在那之前它们只失败,不撒谎。', + '`cd packages/x && pnpm exec vitest` 会落进这里。`pnpm --filter test` 与', + '`turbo run test` 不会 —— objectui#3240 把每个包级 test 脚本改成了显式指回仓根的', + '`vitest run --root ../.. packages//`,跑的和 CI 是同一份配置。', '', ...canonicalLines(pkgDir), '', diff --git a/turbo.json b/turbo.json index 1d3db9bd8f..f6c1427e2d 100644 --- a/turbo.json +++ b/turbo.json @@ -19,7 +19,8 @@ "$TURBO_ROOT$/scripts/vite-*.ts", "$TURBO_ROOT$/scripts/build-plugin-stylesheet.mjs", "$TURBO_ROOT$/scripts/check-dist-completeness.mjs", - "$TURBO_ROOT$/scripts/invoked-as.mjs" + "$TURBO_ROOT$/scripts/invoked-as.mjs", + "$TURBO_ROOT$/scripts/vitest-invocation-guard.mjs" ] }, "test": { From 224e86326f8032de61aadb07557d2ecf55d53a21 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 3 Sep 2026 16:26:45 +0000 Subject: [PATCH 2/2] chore(changeset): declare objectui#3240 as releasing nothing `check-changeset-presence` counts three files under a released package's `src/`: comments in `plugin-calendar`, `plugin-map` and `plugin-timeline` test files, each naming a config or setup file this change deletes. No runtime or type surface moves, so the empty frontmatter is the declared answer the gate asks for, not a workaround. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_019aCUUSwWefnbCJ4Xk1vqQW --- .changeset/vitest-config-unification-3240.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .changeset/vitest-config-unification-3240.md diff --git a/.changeset/vitest-config-unification-3240.md b/.changeset/vitest-config-unification-3240.md new file mode 100644 index 0000000000..6bc68a43d8 --- /dev/null +++ b/.changeset/vitest-config-unification-3240.md @@ -0,0 +1,13 @@ +--- +--- + +Repo tooling only — no published behaviour changes. + +objectui#3240 deletes the 17 per-package `vitest.config.ts` files (plus +`examples/schema-catalog`'s) so the root `vitest.config.mts` is the repo's single +test config, and rewrites every package `test` script to reach it by path filter +(`vitest run --root ../.. /`). The three files this touches under a +released package's `src/` are COMMENTS in test files, each naming a config or +setup file this change deletes; no runtime or type surface moves. The collected +test population is byte-identical before and after — 2459 (project, file) pairs, +diffed programmatically.