ワンクリックで
migrate-jest-to-vitest
Migrate a stencil component unit test (spec.tsx) from Jest to Vitest.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Migrate a stencil component unit test (spec.tsx) from Jest to Vitest.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
| name | migrate-jest-to-vitest |
| description | Migrate a stencil component unit test (spec.tsx) from Jest to Vitest. |
Execute steps mechanically. Do only what the current step says — do not read ahead, combine steps, or reason about the broader file. If anything unexpected comes up, STOP and ask the user.
After the user gives corrective feedback, update this skill to document the lesson learned.
git mv path/to/file.spec.tsx path/to/file.vspec.tsx
Drop the named export, keep the path exactly: import './pos-reverse-relations';
At the top of the file, before the existing imports:
import { vi } from 'vitest';
import { beforeEach, afterEach, describe, expect, it, render, h } from '@stencil/vitest';
mockPodOS to the vitest variantimport { mockPodOS } from '../../../test/mockPodOS.vitest';
jest.* with vi.*Global find-and-replace jest. → vi. throughout the file.
jest-when with vitest-whenSwap the import, then update the API: .mockReturnValue → .thenReturn, .mockResolvedValue → .thenResolve,
.mockRejectedValue → .thenReject. The when(fn).calledWith(...) prefix is unchanged.
Chained .mockReturnValueOnce: use separate when registrations with { times: 1 }:
when(fn, { times: 1 }).calledWith(arg).thenReturn(first);
when(fn, { times: 1 }).calledWith(arg).thenReturn(second);
⚠️ LIFO ordering: later registrations match first. Declare the first when before the consuming call,
register the second when after the first call has been consumed.
Bare catch-all defaults (when(fn).mockReturnValue(v), no .calledWith) are not supported. Drop if the
mock factory already sets a default; otherwise cast: (fn as Mock).mockReturnValue(v) and add Mock to the
vitest import.
newSpecPage with renderconst page = await render(<my-component></my-component>);
⚠️ If the test uses page.setContent(...), STOP and ask the user.
page.rootInstance → page.instancetscnpx tsc --noEmit -p elements/tsconfig.json 2>&1 | grep 'path/to/file.vspec'
⚠️ Always specify the package tsconfig — there is no root tsconfig.json, and a missing -p flag fails
silently when piped to grep.
Remove every TS6133 import.
tscRun the same tsc command; add ! only where TS18047 is reported. Re-run to confirm 0 errors.
npx prettier --write path/to/file.vspec.tsx
supportsShadowDom: false testsrender always uses the component's declared shadow mode. Affected tests are red after Step 7:
page.root.shadowRoot instead of page.root; drop host-element wrapper lines from expected HTML.querySelector through page.root.shadowRoot!.Received: block — do not reuse the light-DOM string. Known diffs:
value="" on empty inputs is dropped under shadow DOM.toEqualHtml to multilineThe shadow-root pretty-printer requires multiline format for elements with text — the inline form fails even
when Expected and Received look identical. Read the Received: block and match it verbatim:
expect(el).toEqualHtml(`
<li>
SomeType
</li>
`);
RenderResult type for helper functionsChange SpecPage parameter types to RenderResult (from @stencil/vitest). Drop @stencil/core/testing.
vi.mock('@pod-os/core', ...)vi.mock('@pod-os/core', () => ({})) fails at runtime if the component references a core symbol as a value —
Vitest's Proxy throws on missing exports. Add a stub for each value-used symbol:
vi.mock('@pod-os/core', () => ({ RdfDocument: class {} }));
Type-only usages are erased at runtime and need no stub. tsc will not catch this — only the runtime does.
vi.spyOnAssigning to getter-only properties (e.g. navigator.clipboard) throws. Use vi.spyOn(navigator.clipboard, 'writeText') instead. If the property is absent in the environment, fall back to Object.defineProperty.
expect(page.root).toBeEmptyDOMElement();
toEqualText expects an element, not a string@stencil/vitest's toEqualText calls getTextContentWithShadow(received), which iterates
received.childNodes. Passing a string (e.g. expect(el.innerText).toEqualText('x')) throws
TypeError: undefined is not iterable. Jest's version coerced strings; Vitest's does not. Pass the element:
expect(el).toEqualText('x');
.vspec.tsx is newly type-checkedelements/tsconfig.json excludes **/*.spec.tsx but not **/*.vspec.tsx, so renaming subjects the
file to tsc for the first time. Pre-existing loose mock data surfaces as TS2322/TS2739 (object
literals missing required fields, wrong field names). Cast the mock data — precedented across the codebase
(pos-attachments, pos-container-contents):
const tools = [{ label: 'Tool 1', component: 'pos-test-tool-1' }] as unknown as ToolConfig[];
Import the type from its source module. These appear during Steps 9–10 but are not TS6133/TS18047 —
they need this cast, not import removal.
@testing-library/dom queries don't pierce shadow DOMqueryAllByRole(el, 'tab') and siblings operate on el's light-DOM subtree only. For a shadow: true
component, use the project helper withinShadow (in elements/src/test/withinShadow.ts), which wraps
@testing-library/dom's within() around page.root.shadowRoot. Precedented across pos-login-form,
pos-container-toolbar, pos-new-thing-form:
import { withinShadow } from '<rel>/test/withinShadow';
// ...
const buttons = withinShadow(page).getAllByRole('tab');
The import path is relative to the test file's directory (../../test/withinShadow, ../../../test/withinShadow, …).
Note: queryAllByRole returns [] when nothing matches; getAllByRole throws. Use queryAllByRole
when asserting absence.
page.win → windowSpecPage exposes a win (mocked window); RenderResult does not. Replace window-scoped listeners with
the global:
window.addEventListener('pod-os:tool-selected', spy);