Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 18 additions & 23 deletions skills/migrate-to-rstest/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,36 +16,32 @@ Migrate Jest- or Vitest-based tests and configuration to Rstest with minimal beh
3. **Do not change user source behavior**: avoid modifying production/business source files unless user explicitly requests it.
4. **Avoid bulk test rewrites**: do not refactor entire test suites when a local compatibility patch can solve it.
5. **Preserve test intent**: keep assertions and scenario coverage unchanged unless clearly broken by framework differences.
6. **Defer legacy runner removal**: keep Jest/Vitest dependency and legacy config during migration; remove only after Rstest tests pass.
6. **Two-phase legacy-runner lifecycle (per migrated scope)**: (a) While the scope being migrated is not green on Rstest, keep every Jest/Vitest dep + config/setup/workspace file untouched — they are your rollback path. (b) Once that scope is green, delete its scope-local legacy config/setup files in the same commit. Drop the shared legacy devDeps (`jest`, `vitest`, adapters, environments) only when no other scope still relies on them — in a partial / mixed-mode monorepo migration that means the final scope, not the first. Leaving files behind "for reference" creates two source-of-truth configs. Framework-specific file enumeration: see the deltas reference.
7. **Literal API substitution, no shims**: rewrite every `vi.` / `jest.` / `vitest.` call site — no global aliasing, no local rebinding, no aliased imports. Full forbidden-form list and reasoning in `references/global-api-migration.md`.
8. **Replace on call sites, not strings**: match only identifiers preceding `(`; after every batch edit, grep `describe\(|it\(|test\(` to confirm no test name string was mutated. Regex template and rationale in `references/global-api-migration.md`.
9. **Coverage thresholds are not negotiable**: never lower `coverage.thresholds` (lines/functions/branches/statements) to make a migrated suite pass. If thresholds fail under Rstest, investigate `coverage.include` / `exclude` / provider wiring before touching the numbers.

## Workflow

1. Detect current test framework (`references/detect-test-framework.md`)
2. Open the official migration guide(s):
- Jest: https://rstest.rs/guide/migration/jest.md
- Vitest: https://rstest.rs/guide/migration/vitest.md
- Prefer the `.md` URL form when available; Rstest pages provide Markdown variants that are more AI-friendly.
3. Dependency install gate (blocker check, see `references/dependency-install-gate.md`)
4. Apply framework-specific migration deltas:
2. Dependency install gate (blocker check, see `references/dependency-install-gate.md`)
3. Open the framework-specific deltas file and the official migration guide it points to. Prefer the `.md` URL form when fetching — Rstest pages provide Markdown variants that are more AI-friendly.
- Jest: `references/jest-migration-deltas.md`
- Vitest: `references/vitest-migration-deltas.md`
- Global API replacement rules: `references/global-api-migration.md`
- Known compatibility pitfalls: `references/rstest-compat-pitfalls.md`
4. Apply the mapping from the official guide + the skill-side enforcement rules from the deltas file
5. Check type errors
6. Run tests and fix deltas
7. Remove legacy test runner dependency/config only after Rstest is green
7. Apply cleanup phase of principle 6 once the migrated scope is green (delete scope-local legacy config/setup in the same commit; drop shared legacy devDeps only when no other scope still relies on them; framework-specific file list is in the deltas file)
8. Summarize changes

## 1. Detect current test framework
## Detect current test framework

Use `references/detect-test-framework.md`.
If both Jest and Vitest are present, migrate one scope at a time (package/suite), keeping mixed mode until each scope is green on Rstest.
See `references/detect-test-framework.md` for detection signals and the mixed-mode scope policy.

## 3. Dependency install gate (blocker check)
## Dependency install gate (blocker check)

Before large-scale edits, verify dependencies can be installed and test runner binaries are available.
Detailed checks, blocked-mode output format, and `ni` policy are in:
`references/dependency-install-gate.md`
Before large-scale edits, verify dependencies can be installed and test runner binaries are available. Detailed checks, blocked-mode output format, and `ni` policy are in `references/dependency-install-gate.md`.

## Patch scope policy (strict)

Expand All @@ -61,11 +57,12 @@ Detailed checks, blocked-mode output format, and `ni` policy are in:

### Red lines

Principles 6–9 above are themselves red lines — the bullets below cover the scope / intent red lines not captured there:

- Do not rewrite many tests in one sweep without first proving config-level fixes are insufficient.
- Do not alter business/runtime behavior to satisfy tests.
- Do not change assertion semantics just to make tests pass.
- Do not broaden migration to unrelated packages in monorepo.
- Do not delete legacy runner dependency/config before confirming Rstest tests pass.
- Do not broaden migration to unrelated packages in a monorepo.

### Escalation rule for large edits

Expand All @@ -81,15 +78,13 @@ stop and provide:
3. expected impact/risk per option,
4. recommended option.

## 6. Run tests and fix deltas
## Run tests and fix deltas

- Run the test suite and fix failures iteratively.
- Fix configuration and resolver errors first, then address mocks/timers/snapshots, and touch test logic last.
- If mocks fail for re-exported modules under Rspack, first check whether the project is pinned to `rstest < 0.9.3`.
- Before broad test rewrites, check known pitfalls in:
`references/rstest-compat-pitfalls.md`
- If mocks fail for re-exported modules under Rspack, first check whether the project is pinned to `rstest < 0.9.3` (fixed in 0.9.3 — upgrade before debugging mock behavior further).

## 8. Summarize changes
## Summarize changes

- Provide a concise change summary and list files touched.
- Call out any remaining manual steps or TODOs.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Dependency Install Gate

Use this reference when running Step 3 of the migration workflow.
Use this reference when running the dependency install gate step of the migration workflow.

## Quick path

Expand Down Expand Up @@ -29,5 +29,5 @@ Return a blocker report with:
2. Error class (network/auth/registry/peer conflict/lockfile mismatch/permission).
3. Concrete next command(s) for the user to run.
4. Whether files were already changed.
5. Resume point: "after dependencies are installed, continue from Step 4".
5. Resume point: "after dependencies are installed, continue from the deltas step".
6. Install strategy used (`ni` or explicit manager fallback).
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Detect Test Framework

Use this reference in Step 1 to decide migration path.
Use this reference to decide the migration path when detecting the current test framework.

## Detection signals

Expand Down
37 changes: 13 additions & 24 deletions skills/migrate-to-rstest/references/global-api-migration.md
Original file line number Diff line number Diff line change
@@ -1,34 +1,23 @@
# Global API Migration

Use this reference when tests rely on globally available test APIs.
Use this reference when tests rely on globally available test APIs (Jest's `jest.<api>`, or Vitest's `vi.<api>` / `vitest.<api>` under `globals: true`).

## Required replacements
For the identifier mapping (`jest.` / `vi.` / `vitest.` → Rstest equivalents), see the official guides:

- Replace global `jest.<api>` with global `rstest.<api>`.
- If Vitest enables `globals: true`, replace global `vi.<api>` with global `rs.<api>`.
- If Vitest enables `globals: true`, replace global `vitest.<api>` with global `rstest.<api>`.
- Jest: https://rstest.rs/guide/migration/jest.md (see "Test API")
- Vitest: https://rstest.rs/guide/migration/vitest.md (see "Test API" and "Global APIs")

## Scope
The rules below are skill-side enforcement on top of that mapping.

Apply this only to test files and setup files where runner APIs are used as globals.
## Red lines

## Examples
1. **No shims.** All three forms below leave the migration incomplete and must be rejected in every test and setup file:
- `globalThis.vi = rs;` / `global.jest = rstest;` (direct global aliasing)
- `const vi = rs;` / `const jest = rstest;` (local rebinding)
- `import { rs as vi } from '@rstest/core';` / `import { rstest as jest } from '@rstest/core';` (aliased named import)

```ts
// Jest
jest.fn() -> rstest.fn()
jest.spyOn(obj, 'm') -> rstest.spyOn(obj, 'm')
```
Fix at every call site instead. Do not propose a shim "just to keep the diff small" — it hides whether the migration actually happened, blocks IDE refactors, and silences future deprecation warnings.

```ts
// Vitest (globals: true)
vi.fn() -> rs.fn()
vitest.spyOn(obj, 'm') -> rstest.spyOn(obj, 'm')
```
2. **No test-name mutation.** When rewriting `vi.` / `jest.` / `vitest.`, only replace identifiers that precede a `(`. After every batch edit, grep `describe\(|it\(|test\(` to confirm no name string was rewritten — test names are stable identifiers, not labels for the new API.

## Notes

- `rs` and `rstest` are equivalent aliases in globals mode; keep `vi -> rs` and `vitest -> rstest` mapping order for consistency.
- Do not apply these replacements to import-style APIs; this reference is global-only.
- Do not change assertion intent while replacing APIs.
- Keep replacements minimal and focused on runner API migration.
3. **Global-only scope.** Do not apply these replacements to import-style APIs; this rule is global-only.
67 changes: 6 additions & 61 deletions skills/migrate-to-rstest/references/jest-migration-deltas.md
Original file line number Diff line number Diff line change
@@ -1,69 +1,14 @@
# Jest Migration Deltas

Use this reference when Step 1 identifies Jest.

## High-priority checks

1. Translate Jest config (`jest.config.*` or `package.json#jest`) to `rstest.config.ts`.
2. Update scripts from `jest` to `rstest`.
3. Migrate setup files (`setupFiles`, `setupFilesAfterEnv`) to Rstest equivalents.
4. Replace Jest-specific imports/APIs with `@rstest/core` equivalents based on official guide.
5. Validate transform/alias behavior (for example `moduleNameMapper`, transformer-related settings).
6. For global API usage, replace global `jest.<api>` with `rstest.<api>` (see `references/global-api-migration.md`).

## Snapshot deltas to expect

1. Snapshot file header may change from Jest to Rstest format.
2. Snapshot key separators can change. A common Jest key style uses `:`,
while Rstest snapshots often use hierarchical separators with `>`.
3. Snapshot entry order can change without semantic changes. In large files,
two keys may swap positions while each key's snapshot body stays identical.

### Updating snapshots with Rstest

Use update mode when snapshot changes are expected:

- Update all snapshots:
`rstest -u`
- Update a filtered scope (for example overlay-related tests, depending on project script/CLI wiring):
`rstest -u overlay`
or
`rstest overlay -u`

### Example key separator delta

- Before (Jest style):
`overlay should not show a warning when "client.overlay.warnings" is "false": page html 1`
- After (Rstest style):
`overlay > should not show a warning when "client.overlay.warnings" is "false" > page html 1`

### Snapshot key anatomy (how to read one key)

For a key like:
`overlay > should not show a warning when "client.overlay.warnings" is "false" > page html 1`

1. `overlay` = suite name (`describe(...)`)
2. `should not show a warning when "client.overlay.warnings" is "false"` = case name (`it(...)` / `test(...)`)
3. `page html` = snapshot label from `.toMatchSnapshot('page html')`
4. `1` = snapshot index for that label in the current test

Implication: many Jest->Rstest diffs are key formatting changes (separator/tokenization) for the same suite/case/snapshot identity.

### Review guidance for migration PRs

1. Treat separator-only key renames as expected migration noise.
2. Distinguish key-order churn from content churn:
- If only key order changed and snapshot bodies are identical, this is non-functional.
- If body content changed under the same key, review as behavior change.
3. For noisy snapshot diffs, compare by key+body hash before deciding there is a regression.
Use this reference when the current framework is Jest.

## Source of truth

Follow official mapping details:
https://rstest.rs/guide/migration/jest.md
Follow the official migration guide for all Jest → Rstest field/API mapping (config table, CLI option mapping, snapshot format change, `done`/hooks/timeout differences, etc.):

Prefer `.md` URLs for AI-friendly ingestion.
https://rstest.rs/guide/migration/jest.md

## Removal gate
## Jest-specific enforcement

Remove `jest` and legacy Jest config only after Rstest tests pass.
1. **Legacy file enumeration for phase (b) of SKILL principle 6.** Scope-local legacy files to delete once the migrated scope is green: per-scope `jest.config.*`, `jest.setup.*`, and any companion `jest.*.ts` (for example `jest.global-setup.ts`, `jest.global-teardown.ts`). Shared infrastructure to drop only when no other scope still relies on it (final scope only in a partial / mixed-mode migration): root `jest.config.*` with `projects: [...]`, any root shared setup, and devDeps `jest`, `ts-jest`, `@types/jest`, `jest-environment-jsdom`, `identity-obj-proxy`.
2. **Defer snapshot re-recording until everything else is green.** Jest's `:` separator in snapshot keys changes to Rstest's `>` (see "Snapshot format" in the official guide), so every snapshot mismatches on the first `rstest` run. Running `rstest -u` too early masks real test failures under formatting noise. Migrate config/API/deps first; run `-u` only when the remaining failures are exclusively key-format mismatches.
13 changes: 0 additions & 13 deletions skills/migrate-to-rstest/references/rstest-compat-pitfalls.md

This file was deleted.

29 changes: 6 additions & 23 deletions skills/migrate-to-rstest/references/vitest-migration-deltas.md
Original file line number Diff line number Diff line change
@@ -1,31 +1,14 @@
# Vitest Migration Deltas

Use this reference when Step 1 identifies Vitest.

## High-priority checks

1. Translate Vitest config (`vitest.config.*` or `vite.config.*` test block) to `rstest.config.ts`.
- Remove `test: { ... }` nesting in most Vitest configs and move keys to top-level in `defineConfig`.
- Rename `environment` to `testEnvironment`.
- Keep `projects` at top-level (`{ projects: [...] }`), not under `test`.
- Do not assume `mergeConfig` is available from `@rstest/core`; prefer `defineConfig({ ...base, ...overrides })` unless official docs require otherwise.
2. Update scripts from `vitest` to `rstest`.
3. Replace Vitest imports/APIs with `@rstest/core` equivalents based on official guide.
4. Replace imported `vi.<api>` with `rs.<api>` when tests explicitly import APIs from `@rstest/core`.
5. If `globals: true` is enabled, replace global `vi.<api>` with `rs.<api>` and global `vitest.<api>` with `rstest.<api>` (see `references/global-api-migration.md`).
6. Migrate setup adapters that are Vitest-specific (for example `@testing-library/jest-dom/vitest`) to matcher-based setup via `expect.extend`.
7. Watch for path resolution differences in tests/setup (`new URL(..., import.meta.url)` may need `resolve(__dirname, ...)` fallback depending on transform/runtime mode).
8. Re-check mock behavior for re-export modules in Rspack projects if failures appear.
9. Re-check async mock factories: Rstest migration may require sync factory + `importActual` patterns.
10. Validate environment/globals/include-exclude behavior after config migration.
Use this reference when the current framework is Vitest.

## Source of truth

Follow official mapping details:
https://rstest.rs/guide/migration/vitest.md
Follow the official migration guide for all Vitest → Rstest field/API mapping (config table, CLI option mapping, setup adapter rewrite, mock-async pattern, path-resolution caveat, `mergeConfig` substitute, etc.):

Prefer `.md` URLs for AI-friendly ingestion.
https://rstest.rs/guide/migration/vitest.md

## Removal gate
## Vitest-specific enforcement

Remove `vitest` and `vitest.config.*` only after Rstest tests pass.
1. **Legacy file enumeration for phase (b) of SKILL principle 6.** Scope-local legacy files to delete once the migrated scope is green: per-scope `vitest.config.*` and `vitest.setup.*` owned by the migrated package only (rename `vitest.setup.*` → `rstest.setup.*` only when the file is genuinely reused). Shared infrastructure to drop only when no other scope still relies on it (final scope only in a partial / mixed-mode migration): root `vitest.workspace.*`, any root shared `vitest.config.*` / `vitest.shared.*`, and devDeps `vitest`, `@vitest/coverage-v8`, and any other `@vitest/*` packages.
2. **Do not re-record Vitest snapshots.** Vitest and Rstest snapshot files are byte-compatible below the header line (see the "Snapshots" section in the official guide). Running `rstest -u` during a Vitest → Rstest migration rewrites every snapshot file's first line with zero behavioral change and floods the migration diff. Only run `-u` when a snapshot test is actually failing and the body diff is expected.
Loading