Skip to content

chore(test): migrate from Jest to Vitest#236

Merged
jancurn merged 1 commit into
mainfrom
chore/migrate-tests-to-vitest
May 18, 2026
Merged

chore(test): migrate from Jest to Vitest#236
jancurn merged 1 commit into
mainfrom
chore/migrate-tests-to-vitest

Conversation

@MQ37
Copy link
Copy Markdown
Collaborator

@MQ37 MQ37 commented May 18, 2026

📚 PR #234 stacks on top of this one (feat(cli): add shell tab-completion). Once this lands, the shell-completion branch drops its chalk mock and src/cli/main.ts workaround.

Context

Jest's ESM story is rough on this repo: pure-ESM deps like chalk and ora need transformIgnorePatterns workarounds, import.meta in src/cli/index.ts trips the CJS transform when any test transitively loads it, and jest-runtime@30.4.2 has a clearMocksOnScope regression that breaks unit tests on some local setups. Came up while opening #234.

Solution

Swap Jest + ts-jest for Vitest + @vitest/coverage-v8. The API stays Jest-compatible (describe/it/expect, plus vi.fn/vi.mock in place of jest.fn/jest.mock) so the test bodies are mostly unchanged. CI script is identical (pnpm run test:unit).

Full suite (581 tests) goes from ~5 s under ts-jest to ~1 s with vitest.

Worth your attention

  • globals: true in vitest.config.ts keeps describe/it/expect available without import churn; only the four files that used jest.SpyInstance / jest.Mock types now import MockInstance / Mock from 'vitest'.
  • Arrow-function mockImplementation factories were converted to regular functions in factory.test.ts, transports.test.ts, and keychain.test.ts — vitest's stricter constructor-call semantics reject arrows as constructors (new (() => ...) throws "not a constructor"). Functionally equivalent.
  • vi.hoisted() in grep.test.ts — vitest hoists vi.mock calls above local const declarations more aggressively than Jest, so the chalk-identity helpers had to move into a hoisted block.
  • transformIgnorePatterns for chalk/cli-table3 is gone — vitest runs ESM natively, so the allow-list is no longer needed. New ESM deps (e.g. ora) will Just Work without config tweaks.
  • Internal-only change — no impact on the published package, no CHANGELOG entry.

Follow-up

- Replace jest+ts-jest with vitest+@vitest/coverage-v8
- vitest.config.ts mirrors the previous Jest config (Node env, same test
  glob, v8 coverage at 70% thresholds)
- Test API stays Jest-compatible via 'globals: true': describe/it/expect
  unchanged, jest.fn/jest.mock/jest.spyOn renamed to vi.* equivalents
- Type annotations: jest.SpyInstance -> MockInstance, jest.Mock -> Mock,
  imported as types from 'vitest' in the four files that use them
- Convert arrow-function mockImplementation factories to regular functions
  in 3 places (factory/transports/keychain tests) so 'new MockedClass()'
  works under vitest (arrow functions are not constructable)
- Use vi.hoisted() in grep.test.ts where the chalk mock factory
  referenced a const declared after vi.mock()
- Drop the chalk/ora transformIgnorePatterns workarounds; vitest runs ESM
  natively
- Update test/README.md and the keychain.ts comment to drop Jest references

Build, lint, and full unit suite (581 tests) pass locally. CI workflow
unchanged (still 'pnpm run test:unit').
@MQ37 MQ37 marked this pull request as draft May 18, 2026 06:33
MQ37 added a commit that referenced this pull request May 18, 2026
Two-piece pattern (kubectl/gh/cobra-style):
- 'mcpc completion <bash|zsh|fish>' prints a thin shell script that
  registers a completion function for the user's shell.
- 'mcpc __complete -- <words...> <partial>' is a hidden subcommand the
  script calls on every TAB. Returns candidates on stdout (one per line)
  plus an optional ':<bitflag>' directive line.
- 'mcpc completion install [shell]' auto-detects the user's shell and
  writes the script to the standard path.

Completes top-level commands, session subcommands, @session names from
~/.mcpc/sessions.json, auth-server hosts, log levels, all known flags
(introspected from Commander's tree at runtime so there is no static
drift), and tool / resource URI / prompt names for connected sessions.

Tool/resource/prompt names are mirrored to ~/.mcpc/completion/<session>.json
whenever the user runs 'tools-list' / 'resources-list' / 'prompts-list',
so TAB completion is fast and never triggers network calls or OAuth flows.
The cache file is removed alongside the session by 'mcpc clean sessions'.

createTopLevelProgram, createSessionProgram, and registerSessionCommands
are now exported from src/cli/index.ts so commands/completion.ts can walk
the Commander tree to extract option flags. index.ts no longer self-runs
on import; bin/mcpc calls mod.run() explicitly, and a small direct-run
guard supports 'tsx src/cli/index.ts' invocation for the README-help
test. Reading package.json via JSON import attribute replaces the old
createRequire(import.meta.url) pattern.

Stacks on top of #236 (Jest -> Vitest migration); the vitest move
removes the need for chalk mocking and the src/cli/main.ts split that
earlier revisions of this branch carried.
MQ37 added a commit that referenced this pull request May 18, 2026
Two-piece pattern (kubectl/gh/cobra-style):
- 'mcpc completion <bash|zsh|fish>' prints a thin shell script that
  registers a completion function for the user's shell.
- 'mcpc __complete -- <words...> <partial>' is a hidden subcommand the
  script calls on every TAB. Returns candidates on stdout (one per line)
  plus an optional ':<bitflag>' directive line.
- 'mcpc completion install [shell]' auto-detects the user's shell and
  writes the script to the standard path.

Completes top-level commands, session subcommands, @session names from
~/.mcpc/sessions.json, auth-server hosts, log levels, all known flags
(introspected from Commander's tree at runtime so there is no static
drift), and tool / resource URI / prompt names for connected sessions.

Tool/resource/prompt names are mirrored to ~/.mcpc/completion/<session>.json
whenever the user runs 'tools-list' / 'resources-list' / 'prompts-list',
so TAB completion is fast and never triggers network calls or OAuth flows.
The cache file is removed alongside the session by 'mcpc clean sessions'.

createTopLevelProgram, createSessionProgram, and registerSessionCommands
are now exported from src/cli/index.ts so commands/completion.ts can walk
the Commander tree to extract option flags. index.ts no longer self-runs
on import; bin/mcpc calls mod.run() explicitly, and a small direct-run
guard supports 'tsx src/cli/index.ts' invocation for the README-help
test. Reading package.json via JSON import attribute replaces the old
createRequire(import.meta.url) pattern.

Stacks on top of #236 (Jest -> Vitest migration); the vitest move
removes the need for chalk mocking and the src/cli/main.ts split that
earlier revisions of this branch carried.
@MQ37 MQ37 marked this pull request as ready for review May 18, 2026 11:30
@MQ37 MQ37 requested a review from jancurn May 18, 2026 11:31
@jancurn jancurn merged commit 51c32cc into main May 18, 2026
6 checks passed
@jancurn jancurn deleted the chore/migrate-tests-to-vitest branch May 18, 2026 21:23
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.

3 participants