diff --git a/skills/migrate-to-rstest/SKILL.md b/skills/migrate-to-rstest/SKILL.md index 0056909..2883979 100644 --- a/skills/migrate-to-rstest/SKILL.md +++ b/skills/migrate-to-rstest/SKILL.md @@ -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) @@ -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 @@ -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. diff --git a/skills/migrate-to-rstest/references/dependency-install-gate.md b/skills/migrate-to-rstest/references/dependency-install-gate.md index d1484a8..a4a789d 100644 --- a/skills/migrate-to-rstest/references/dependency-install-gate.md +++ b/skills/migrate-to-rstest/references/dependency-install-gate.md @@ -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 @@ -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). diff --git a/skills/migrate-to-rstest/references/detect-test-framework.md b/skills/migrate-to-rstest/references/detect-test-framework.md index 08f1b42..40a2616 100644 --- a/skills/migrate-to-rstest/references/detect-test-framework.md +++ b/skills/migrate-to-rstest/references/detect-test-framework.md @@ -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 diff --git a/skills/migrate-to-rstest/references/global-api-migration.md b/skills/migrate-to-rstest/references/global-api-migration.md index 9d8d091..bfd4ec0 100644 --- a/skills/migrate-to-rstest/references/global-api-migration.md +++ b/skills/migrate-to-rstest/references/global-api-migration.md @@ -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.`, or Vitest's `vi.` / `vitest.` under `globals: true`). -## Required replacements +For the identifier mapping (`jest.` / `vi.` / `vitest.` → Rstest equivalents), see the official guides: -- Replace global `jest.` with global `rstest.`. -- If Vitest enables `globals: true`, replace global `vi.` with global `rs.`. -- If Vitest enables `globals: true`, replace global `vitest.` with global `rstest.`. +- 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. diff --git a/skills/migrate-to-rstest/references/jest-migration-deltas.md b/skills/migrate-to-rstest/references/jest-migration-deltas.md index 81955fb..add42dc 100644 --- a/skills/migrate-to-rstest/references/jest-migration-deltas.md +++ b/skills/migrate-to-rstest/references/jest-migration-deltas.md @@ -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.` with `rstest.` (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. diff --git a/skills/migrate-to-rstest/references/rstest-compat-pitfalls.md b/skills/migrate-to-rstest/references/rstest-compat-pitfalls.md deleted file mode 100644 index 934c72f..0000000 --- a/skills/migrate-to-rstest/references/rstest-compat-pitfalls.md +++ /dev/null @@ -1,13 +0,0 @@ -# Rstest Compatibility Pitfalls (Vitest/Jest Migration) - -Use this reference when migration passes static replacement but runtime tests still fail due to Rspack-specific runtime behavior that is not covered by general migration docs. - -For general migration pitfalls (globals mode, setup adapters, mock factory/hoisting, path resolution), use the official Vitest migration guide first: -https://rstest.rs/guide/migration/vitest - -## Re-export mock target mismatch under Rspack (legacy) - -This issue was fixed in `rstest v0.9.3`. -Treat this section as a legacy note only for projects pinned to `rstest < 0.9.3`. - -- Confirm the project is using `rstest < 0.9.3`, then upgrade to `rstest >= 0.9.3` before debugging mock behavior further. diff --git a/skills/migrate-to-rstest/references/vitest-migration-deltas.md b/skills/migrate-to-rstest/references/vitest-migration-deltas.md index 0c24632..a8fc00c 100644 --- a/skills/migrate-to-rstest/references/vitest-migration-deltas.md +++ b/skills/migrate-to-rstest/references/vitest-migration-deltas.md @@ -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.` with `rs.` when tests explicitly import APIs from `@rstest/core`. -5. If `globals: true` is enabled, replace global `vi.` with `rs.` and global `vitest.` with `rstest.` (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.