fix(vtz): vi.mock propagates through transitive imports (resolves #2731)#2750
Merged
Conversation
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>
This was referenced Apr 17, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #2731. Three layered fixes so
vi.mock('m', factory)reaches production code that usesawait import('m')deep in the dependency graph:Wrap dynamic
import()in non-test files whenspy_exportsis on. The mock-hoisting pass already wrapped them in test files via__vertz_unwrap_module; extending the wrap to the entire graph meanscli.ts → await import('@vertz/compiler')no longer bypasses the spy installed bycli.test.ts. The__vertz_unwrap_modulehelper caches by the original (frozen) module identity via WeakMap, so the test file and production code resolve to the same mutable wrapper.mockRestoreonmock(impl)now restores the initial impl instead of clearing it. Matches vitest semantics ("forvi.fn(impl), mockRestore reverts toimpl") and unbreaks the commonvi.mock('m', () => ({ fn: mock(() => obj) }))+vi.restoreAllMocks()pattern, where the factory contract was being torn down on first afterEach.mockResetstill clears.Mock proxy module unions in synthetic-polyfill exports for bare specifiers like
esbuild+node:*. Without this, transitiveimport { transformSync } from 'esbuild'in@vertz/ui-server/bun-pluginfailed 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 ofmock().mockResolvedValue(x)so they survivevi.restoreAllMocks()under correct vitest semantics — the runtime fix preservesinitialImpl, but the previous test code also relied on configured impls surviving reset (which neither vitest nor vtz does formock()with no initial impl).Public API Changes
mockRestore()on a mock created viamock(impl)now restoresimplinstead of clearing the implementation. Behavior change for callers that depended onmockRestorebeing identical tomockResetfor non-spy mocks.mockReset()semantics unchanged. Aligns with vitest.import()in non-test files compiled undervtz testis now wrapped with__vertz_unwrap_module. Transparent at runtime — the wrapper proxies reads to the original namespace and stores writes locally.node:*) now expose the union of polyfill exports + factory-returned exports + on-disk ESM exports.Test plan
cargo test -p vertz-compiler-core— 1247 passedcargo test --release -p vtz --lib— 3460 passed (incl. 3 new mock_restore + dynamic-import wrap regression tests)cargo clippy --all-targets -- -D warnings— cleancargo fmt --all -- --check— cleanvtz test src/__tests__/cli.test.ts— 63/63 passvtz test src/production-build/__tests__/orchestrator.test.ts— 23/23 passvtz test src/production-build/__tests__/ui-build-pipeline.test.ts— 47/47 pass (1 brotli skip, vtz polyfill gap, tracked separately)vtz test— no new regressions vs main; remaining failures (SSR examples, AOT JSX subprocess tests, packages that importbun) are pre-existing.🤖 Generated with Claude Code