Skip to content

fix(vtz): vi.mock propagates through transitive imports (resolves #2731)#2750

Merged
viniciusdacal merged 1 commit into
mainfrom
fix/vtz-mock-transitive-propagation
Apr 17, 2026
Merged

fix(vtz): vi.mock propagates through transitive imports (resolves #2731)#2750
viniciusdacal merged 1 commit into
mainfrom
fix/vtz-mock-transitive-propagation

Conversation

@viniciusdacal
Copy link
Copy Markdown
Contributor

Summary

Closes #2731. Three layered fixes so vi.mock('m', factory) reaches production code that uses await import('m') deep in the dependency graph:

  1. Wrap dynamic import() in non-test files when spy_exports is on. The mock-hoisting pass already wrapped them in test files via __vertz_unwrap_module; extending the wrap to the entire graph means cli.ts → await import('@vertz/compiler') no longer bypasses the spy installed by cli.test.ts. The __vertz_unwrap_module helper caches by the original (frozen) module identity via WeakMap, so the test file and production code resolve to the same mutable wrapper.

  2. mockRestore on mock(impl) now restores the initial impl instead of clearing it. Matches vitest semantics ("for vi.fn(impl), mockRestore reverts to impl") and unbreaks the common vi.mock('m', () => ({ fn: mock(() => obj) })) + vi.restoreAllMocks() pattern, where the factory contract was being torn down on first afterEach. mockReset still clears.

  3. Mock proxy module unions in synthetic-polyfill exports for bare specifiers like esbuild + node:*. Without this, transitive import { transformSync } from 'esbuild' in @vertz/ui-server/bun-plugin failed at link time even when the call path never invoked it at runtime.

Unskips the 3 test blocks parked on this:

Test mocks updated to use factory-supplied impls (mock(async () => x)) instead of mock().mockResolvedValue(x) so they survive vi.restoreAllMocks() under correct vitest semantics — the runtime fix preserves initialImpl, but the previous test code also relied on configured impls surviving reset (which neither vitest nor vtz does for mock() with no initial impl).

Public API Changes

  • mockRestore() on a mock created via mock(impl) now restores impl instead of clearing the implementation. Behavior change for callers that depended on mockRestore being identical to mockReset for non-spy mocks. mockReset() semantics unchanged. Aligns with vitest.
  • Dynamic import() in non-test files compiled under vtz test is now wrapped with __vertz_unwrap_module. Transparent at runtime — the wrapper proxies reads to the original namespace and stores writes locally.
  • Mocked modules with synthetic vtz polyfills (esbuild, node:*) now expose the union of polyfill exports + factory-returned exports + on-disk ESM exports.

Test plan

  • cargo test -p vertz-compiler-core — 1247 passed
  • cargo test --release -p vtz --lib — 3460 passed (incl. 3 new mock_restore + dynamic-import wrap regression tests)
  • cargo clippy --all-targets -- -D warnings — clean
  • cargo fmt --all -- --check — clean
  • vtz test src/__tests__/cli.test.ts — 63/63 pass
  • vtz test src/production-build/__tests__/orchestrator.test.ts — 23/23 pass
  • vtz test src/production-build/__tests__/ui-build-pipeline.test.ts — 47/47 pass (1 brotli skip, vtz polyfill gap, tracked separately)
  • Full workspace vtz test — no new regressions vs main; remaining failures (SSR examples, AOT JSX subprocess tests, packages that import bun) are pre-existing.

🤖 Generated with Claude Code

Three layered fixes to make `vi.mock('m', factory)` reach production code
that uses `await import('m')` deep in the dependency graph:

1. Wrap dynamic `import()` in non-test files when `spy_exports` is on.
   The mock-hoisting pass already wrapped them in test files via
   `__vertz_unwrap_module`; extending the wrap to the entire graph means
   `cli.ts → await import('@vertz/compiler')` no longer bypasses the
   spy installed by `cli.test.ts`.

2. `mockRestore` on `mock(impl)` now restores the initial impl instead
   of clearing it. Matches vitest semantics and unbreaks the common
   `vi.mock('m', () => ({ fn: mock(() => obj) }))` + `vi.restoreAllMocks()`
   pattern, where the factory contract was being torn down on first
   afterEach. mockReset still clears.

3. Mock proxy module now unions in the synthetic-polyfill exports for
   bare specifiers like esbuild + node:*. Without this, transitive
   `import { transformSync } from 'esbuild'` failed at link time even
   when the call path never invoked it at runtime.

Unskips and rewrites the 3 test blocks that were parked on this:
- packages/cli/src/__tests__/cli.test.ts (codegen command action, 3 tests)
- packages/cli/src/production-build/__tests__/orchestrator.test.ts (23 tests)
- packages/cli/src/production-build/__tests__/ui-build-pipeline.test.ts (47 tests)

Test mocks updated to use factory-supplied impls (`mock(async () => x)`)
instead of `mock().mockResolvedValue(x)` so they survive `vi.restoreAllMocks()`
under correct vitest semantics — the runtime fix preserves initialImpl,
but the previous test code relied on configured impls surviving reset
(neither vitest nor vtz does that for `mock()` without an initial impl).

One buildUI test (brotli .br sidecar) stays skipped because vtz's
node:zlib polyfill is a passthrough, so compressed.length never
satisfies the `< content.length` write guard. Tracked separately.

Closes #2731

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@viniciusdacal viniciusdacal merged commit f7f05f4 into main Apr 17, 2026
5 of 6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

@vertz/cli production-build: BuildOrchestrator analyze() call fails with undefined compiler

1 participant