ワンクリックで
solidjs-tests
MUST USE SKILL when creating, modify, validating solidjs code. Following best patterns for SolidJS tests.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
MUST USE SKILL when creating, modify, validating solidjs code. Following best patterns for SolidJS tests.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
Best practices for writing clean, maintainable, and robust tests with TUnit.
Use `helium-browser --dump-dom` to inspect the fully rendered DOM after JavaScript execution. For debugging layout/scrolling issues and scraping dynamic pages.
Senior UI/UX Engineer. Architect digital interfaces overriding default LLM biases. Enforces metric-based rules, strict component architecture, CSS hardware acceleration, and balanced design engineering.
CLI tool for AI agents to browse the web, interact with UI elements, and analyze frontend applications. Supports navigation, clicking, typing, and capturing snapshots for accessibility analysis.
Expert guide for creating, configuring, and managing Opencode Agents. Use this skill when the user wants to create a new agent or modify an existing one.
Expert guide and best practices for building production-ready web APIs with Axum 0.8+ in Rust. Use this skill when developing, refactoring, or structuring Axum applications.
| name | solidjs-tests |
| description | MUST USE SKILL when creating, modify, validating solidjs code. Following best patterns for SolidJS tests. |
SolidJS uses fine-grained reactivity where components run once and only reactive expressions re-execute. This fundamentally changes testing patterns compared to React:
bun install -D vitest jsdom @solidjs/testing-library @testing-library/user-event @testing-library/jest-dom
// vitest.config.ts
import { defineConfig } from 'vitest/config'
import solidPlugin from 'vite-plugin-solid'
export default defineConfig({
plugins: [solidPlugin()],
test: {
environment: 'jsdom',
globals: true,
setupFiles: ['./src/test/setup.ts'],
// Ensure proper SolidJS transformation
deps: {
optimizer: {
web: {
include: ['@solidjs/testing-library']
}
}
}
},
resolve: {
conditions: ['development', 'browser']
}
})
// src/test/setup.ts
import '@testing-library/jest-dom/vitest'
import { afterEach } from 'vitest'
import { cleanup } from '@solidjs/testing-library'
// Cleanup after each test
afterEach(() => {
cleanup()
})
{
"compilerOptions": {
"jsx": "preserve",
"jsxImportSource": "solid-js",
"types": ["vite/client", "@testing-library/jest-dom"]
}
}
| Type | Location | Naming | Purpose |
|---|---|---|---|
| Unit | src/__tests__/ or co-located | *.test.ts | Pure functions, utilities |
| Component | Co-located or src/__tests__/ | *.test.tsx | Component behavior |
| Integration | src/__tests__/integration/ | *.test.tsx | Multiple components |
| E2E | e2e/ | *.spec.ts | Full user flows |
import { describe, it, expect } from 'vitest'
import { render, screen } from '@solidjs/testing-library'
import { Counter } from './Counter'
describe('Counter', () => {
it('renders initial count', () => {
render(() => <Counter initialCount={5} />)
expect(screen.getByRole('button')).toHaveTextContent('5')
})
})
Key Pattern: Always wrap component in arrow function () => <Component /> - this is required for SolidJS's reactive ownership tracking.
import { describe, it, expect } from 'vitest'
import { render, screen } from '@solidjs/testing-library'
import userEvent from '@testing-library/user-event'
import { Counter } from './Counter'
describe('Counter', () => {
it('increments on click', async () => {
const user = userEvent.setup()
render(() => <Counter />)
const button = screen.getByRole('button')
expect(button).toHaveTextContent('0')
await user.click(button)
expect(button).toHaveTextContent('1')
})
it('handles rapid clicks', async () => {
const user = userEvent.setup()
render(() => <Counter />)
const button = screen.getByRole('button')
await user.click(button)
await user.click(button)
await user.click(button)
expect(button).toHaveTextContent('3')
})
})
import { describe, it, expect, vi } from 'vitest'
import { render, screen } from '@solidjs/testing-library'
import userEvent from '@testing-library/user-event'
import { LoginForm } from './LoginForm'
describe('LoginForm', () => {
it('submits with user input', async () => {
const user = userEvent.setup()
const onSubmit = vi.fn()
render(() => <LoginForm onSubmit={onSubmit} />)
await user.type(screen.getByLabelText('Email'), 'test@example.com')
await user.type(screen.getByLabelText('Password'), 'password123')
await user.click(screen.getByRole('button', { name: 'Submit' }))
expect(onSubmit).toHaveBeenCalledWith({
email: 'test@example.com',
password: 'password123'
})
})
it('shows validation errors', async () => {
const user = userEvent.setup()
render(() => <LoginForm onSubmit={vi.fn()} />)
await user.click(screen.getByRole('button', { name: 'Submit' }))
expect(screen.getByText('Email is required')).toBeInTheDocument()
})
})
import { describe, it, expect } from 'vitest'
import { render, screen } from '@solidjs/testing-library'
import userEvent from '@testing-library/user-event'
import { TodoList } from './TodoList'
describe('TodoList', () => {
it('adds new todos reactively', async () => {
const user = userEvent.setup()
render(() => <TodoList />)
const input = screen.getByPlaceholderText('Add todo')
const addButton = screen.getByRole('button', { name: 'Add' })
await user.type(input, 'Buy groceries')
await user.click(addButton)
// DOM updates synchronously with signal changes
expect(screen.getByText('Buy groceries')).toBeInTheDocument()
expect(input).toHaveValue('')
})
})
For testing effects that need to track reactive changes:
import { describe, it, expect } from 'vitest'
import { testEffect } from '@solidjs/testing-library'
import { createSignal, createEffect } from 'solid-js'
describe('Effect tracking', () => {
it('tracks signal changes', () => {
const [count, setCount] = createSignal(0)
return testEffect((done) => {
createEffect((run: number = 0) => {
if (run === 0) {
expect(count()).toBe(0)
setCount(1)
} else if (run === 1) {
expect(count()).toBe(1)
done()
}
return run + 1
})
})
})
})
import { describe, it, expect, vi } from 'vitest'
import { render, screen, waitFor } from '@solidjs/testing-library'
import { UserProfile } from './UserProfile'
// Mock the API
vi.mock('./api', () => ({
fetchUser: vi.fn()
}))
import { fetchUser } from './api'
describe('UserProfile', () => {
it('shows loading state', () => {
vi.mocked(fetchUser).mockImplementation(
() => new Promise(() => {}) // Never resolves
)
render(() => <UserProfile userId="1" />)
expect(screen.getByText('Loading...')).toBeInTheDocument()
})
it('renders user data after fetch', async () => {
vi.mocked(fetchUser).mockResolvedValue({
name: 'John Doe',
email: 'john@example.com'
})
render(() => <UserProfile userId="1" />)
await waitFor(() => {
expect(screen.getByText('John Doe')).toBeInTheDocument()
})
expect(screen.getByText('john@example.com')).toBeInTheDocument()
})
it('handles errors', async () => {
vi.mocked(fetchUser).mockRejectedValue(new Error('Failed to fetch'))
render(() => <UserProfile userId="1" />)
await waitFor(() => {
expect(screen.getByText('Error: Failed to fetch')).toBeInTheDocument()
})
})
})
<Show>import { describe, it, expect } from 'vitest'
import { render, screen } from '@solidjs/testing-library'
import userEvent from '@testing-library/user-event'
import { ToggleContent } from './ToggleContent'
describe('ToggleContent', () => {
it('shows content when condition is true', async () => {
const user = userEvent.setup()
render(() => <ToggleContent />)
expect(screen.queryByText('Hidden content')).not.toBeInTheDocument()
await user.click(screen.getByRole('button', { name: 'Toggle' }))
expect(screen.getByText('Hidden content')).toBeInTheDocument()
})
})
<For>import { describe, it, expect } from 'vitest'
import { render, screen } from '@solidjs/testing-library'
import { ItemList } from './ItemList'
describe('ItemList', () => {
it('renders list items', () => {
const items = [
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' },
{ id: 3, name: 'Item 3' }
]
render(() => <ItemList items={items} />)
expect(screen.getAllByRole('listitem')).toHaveLength(3)
expect(screen.getByText('Item 1')).toBeInTheDocument()
expect(screen.getByText('Item 2')).toBeInTheDocument()
expect(screen.getByText('Item 3')).toBeInTheDocument()
})
it('updates when items change', async () => {
const user = userEvent.setup()
render(() => <ItemList />)
await user.click(screen.getByRole('button', { name: 'Add Item' }))
expect(screen.getAllByRole('listitem')).toHaveLength(1)
})
})
import { describe, it, expect } from 'vitest'
import { render, screen } from '@solidjs/testing-library'
import { ThemeProvider, useTheme } from './ThemeContext'
import { ThemedButton } from './ThemedButton'
describe('ThemedButton', () => {
it('uses theme from context', () => {
const wrapper = (props: { children: any }) => (
<ThemeProvider theme="dark">
{props.children}
</ThemeProvider>
)
render(() => <ThemedButton />, { wrapper })
expect(screen.getByRole('button')).toHaveClass('dark-theme')
})
})
// src/test/utils.tsx
import { render, RenderOptions } from '@solidjs/testing-library'
import { ThemeProvider } from '../contexts/ThemeContext'
import { AuthProvider } from '../contexts/AuthContext'
import { JSX } from 'solid-js'
interface WrapperProps {
children: JSX.Element
}
const AllProviders = (props: WrapperProps) => (
<ThemeProvider>
<AuthProvider>
{props.children}
</AuthProvider>
</ThemeProvider>
)
export function renderWithProviders(
ui: () => JSX.Element,
options?: Omit<RenderOptions, 'wrapper'>
) {
return render(ui, { wrapper: AllProviders, ...options })
}
export * from '@solidjs/testing-library'
import { describe, it, expect, vi, beforeEach } from 'vitest'
vi.mock('./analytics', () => ({
trackEvent: vi.fn()
}))
import { trackEvent } from './analytics'
describe('Button with analytics', () => {
beforeEach(() => {
vi.clearAllMocks()
})
it('tracks click events', async () => {
const user = userEvent.setup()
render(() => <TrackedButton />)
await user.click(screen.getByRole('button'))
expect(trackEvent).toHaveBeenCalledWith('button_click', {
component: 'TrackedButton'
})
})
})
import { describe, it, expect, vi } from 'vitest'
import { render, screen } from '@solidjs/testing-library'
// Mock the store module
let mockCount = 0
vi.mock('./stores/counter-store', () => ({
counterStore: {
get count() { return mockCount }
},
increment: vi.fn()
}))
import { CounterDisplay } from './CounterDisplay'
import { increment } from './stores/counter-store'
describe('CounterDisplay', () => {
it('displays store value', () => {
mockCount = 42
render(() => <CounterDisplay />)
expect(screen.getByText('Count: 42')).toBeInTheDocument()
})
})
import { describe, it, expect } from 'vitest'
import { render, screen } from '@solidjs/testing-library'
import { UserPage } from './UserPage'
describe('UserPage', () => {
it('renders with route params', () => {
render(() => <UserPage />, {
location: '/users/123'
})
expect(screen.getByText('User ID: 123')).toBeInTheDocument()
})
})
// WRONG: Breaks reactive ownership
render(<Counter />)
// CORRECT: Maintains reactive context
render(() => <Counter />)
// WRONG: May fail due to timing
render(() => <AsyncComponent />)
expect(screen.getByText('Loaded')).toBeInTheDocument()
// CORRECT: Wait for element to appear
render(() => <AsyncComponent />)
await waitFor(() => {
expect(screen.getByText('Loaded')).toBeInTheDocument()
})
// WRONG: Testing internal signal values
const [count] = createSignal(0)
expect(count()).toBe(0)
// CORRECT: Test what users see
expect(screen.getByRole('button')).toHaveTextContent('0')
// Ensure setup.ts has cleanup, or tests will leak
afterEach(() => {
cleanup()
})
// WRONG: Loses reactivity - tests may pass but code is broken
function Counter({ count }: Props) {
return <div>{count}</div>
}
// CORRECT: Access props directly
function Counter(props: Props) {
return <div>{props.count}</div>
}
userEvent over fireEvent: More realistic user simulationdata-testid sparingly: Prefer accessible queries (getByRole, getByLabelText)| Level | Ratio | What to Test | Example |
|---|---|---|---|
| Static | Continuous | Types, lint rules | tsc --noEmit, ESLint |
| Unit | ~70% | Utils, pure functions, formatters | formatDate(), validateEmail() |
| Integration | ~20% | Components, hooks, user flows | Form submission, list filtering |
| E2E | ~10% | Critical paths | Login, checkout, signup |