SolidJS signals, memos and resources, tested with Solid Testing Library: working versions (tested 2026-10-15)
SolidJS components using createSignal, createMemo, For, Show and createResource with Suspense, compiled by vite-plugin-solid and tested in Vitest on jsdom with @solidjs/testing-library and user-event.
setup_failed0 passed · 1 failed2026-10-15 14:17 UTC- Status
- setup_failed
- Last passed
- 2026-10-15 14:17 UTC
- Tests
- 0 / 1
- Language
- TypeScript
- Runtime
- node v22.23.3
npm install --no-audit --no-fund --ignore-scripts=false npm test
node v22.23.3 linux 6.17.0-1022-azure x64 runner: github-actions 38755782594
The code
Exactly the files that passed. Your agent can pull them with get_recipe("solid-signals-testing-library") (install), or see them on GitHub.
{
"name": "solid-signals-testing-library",
"private": true,
"type": "module",
"scripts": {
"test": "vitest run --reporter=default --reporter=json --outputFile=test-report.json"
},
"dependencies": {
"solid-js": "1.9.17"
},
"devDependencies": {
"@solidjs/testing-library": "0.8.10",
"@testing-library/user-event": "14.6.8",
"vite-plugin-solid": "2.11.14",
"jsdom": "30.1.3",
"vitest": "5.0.4",
"@types/node": "26.6.5"
}
}
import { createSignal, createMemo, createResource, For, Show, Suspense } from "solid-js";
export type Product = { id: number; name: string; category: string };
/** Filterable list: a signal for the query, a memo for the filtered rows. */
export function ProductFilter(props: { products: Product[] }) {
const [query, setQuery] = createSignal("");
const [category, setCategory] = createSignal("all");
const categories = createMemo(() => ["all", ...new Set(props.products.map((p) => p.category))]);
const visible = createMemo(() => props.products.filter((p) =>
(category() === "all" || p.category === category()) && p.name.toLowerCase().includes(query().toLowerCase())));
return (
<div>
<input aria-label="search" value={query()} onInput={(e) => setQuery(e.currentTarget.value)} />
<select aria-label="category" onChange={(e) => setCategory(e.currentTarget.value)}>
<For each={categories()}>{(c) => <option value={c}>{c}</option>}</For>
</select>
<Show when={visible().length > 0} fallback={<p>No matches</p>}>
<ul><For each={visible()}>{(p) => <li>{p.name}</li>}</For></ul>
</Show>
<p data-testid="count">{visible().length} shown</p>
</div>
);
}
/** Async data with createResource: refetches whenever the source signal changes. */
export function UserCard(props: { fetchUser: (id: number) => Promise<{ name: string }> }) {
const [id, setId] = createSignal(1);
const [user] = createResource(id, props.fetchUser);
return (
<div>
<Suspense fallback={<p>Loading</p>}>
<h2>{user()?.name}</h2>
</Suspense>
<button onClick={() => setId((n) => n + 1)}>Next user</button>
</div>
);
}
import { describe, it, expect, afterEach } from "vitest";
import { render, screen, cleanup } from "@solidjs/testing-library";
import userEvent from "@testing-library/user-event";
import { ProductFilter, UserCard } from "../src/Search.tsx";
// Auto-cleanup needs vitest globals; without them, unmount explicitly between tests.
afterEach(cleanup);
const products = [
{ id: 1, name: "Hard Hat", category: "head" },
{ id: 2, name: "Ear Muffs", category: "head" },
{ id: 3, name: "Steel Toe Boots", category: "feet" },
];
describe("ProductFilter", () => {
it("filters by text and category through signals and memos", async () => {
const user = userEvent.setup();
render(() => <ProductFilter products={products} />);
expect(screen.getByTestId("count").textContent).toBe("3 shown");
await user.type(screen.getByLabelText("search"), "hat");
expect(screen.getAllByRole("listitem").map((li) => li.textContent)).toEqual(["Hard Hat"]);
await user.clear(screen.getByLabelText("search"));
await user.selectOptions(screen.getByLabelText("category"), "head");
expect(screen.getByTestId("count").textContent).toBe("2 shown");
});
it("derives the category options and shows a fallback when nothing matches", async () => {
const user = userEvent.setup();
render(() => <ProductFilter products={products} />);
expect(screen.getAllByRole("option").map((o) => o.textContent)).toEqual(["all", "head", "feet"]);
await user.type(screen.getByLabelText("search"), "gloves");
expect(screen.getByText("No matches")).toBeTruthy();
expect(screen.queryByRole("list")).toBeNull();
});
});
describe("UserCard", () => {
it("suspends while loading and refetches when the source signal changes", async () => {
const user = userEvent.setup();
const calls: number[] = [];
const fetchUser = async (id: number) => { calls.push(id); await new Promise((r) => setTimeout(r, 5)); return { name: id === 1 ? "Ada" : "Grace" }; };
render(() => <UserCard fetchUser={fetchUser} />);
expect(screen.getByText("Loading")).toBeTruthy();
expect(await screen.findByRole("heading", { name: "Ada" })).toBeTruthy();
await user.click(screen.getByRole("button", { name: "Next user" }));
expect(await screen.findByRole("heading", { name: "Grace" })).toBeTruthy();
expect(calls).toEqual([1, 2]);
});
});
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "preserve",
"jsxImportSource": "solid-js",
"strict": true,
"skipLibCheck": true
}
}
import { defineConfig } from "vitest/config";
import solid from "vite-plugin-solid";
export default defineConfig({
plugins: [solid()], // compiles Solid JSX into fine-grained DOM updates
test: { environment: "jsdom" },
resolve: { conditions: ["development", "browser"] }, // use Solid's browser build, not the SSR one
});
Connect to ProvenPin
Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp
{ "mcpServers": { "provenpin": { "url": "https://mcp.provenpin.com/mcp" } } }{ "servers": { "provenpin": { "type": "http", "url": "https://mcp.provenpin.com/mcp" } } }{ "mcpServers": { "provenpin": { "serverUrl": "https://mcp.provenpin.com/mcp" } } }{ "mcpServers": { "provenpin": { "type": "streamableHttp", "url": "https://mcp.provenpin.com/mcp" } } }Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.
Related stacks
Preact components with Signals state, tested with Testing Library on jsdom
A Preact 11 cart using @preact/signals signal and computed state plus a hooks-based async form, tested in Vitest on jsdom with @testing-library/preact and user-event, including re-render on signal change without new props.
TanStack Query + React Testing Library hooks and components (Vitest + jsdom)
Test React components and hooks that use TanStack Query v5 with Testing Library on jsdom: fresh QueryClient per test, retries off, mocked fetchers, mutations with invalidation.
Astro 7 component rendering tests with the Container API (Vitest)
Render Astro components to HTML in Vitest with the Container API and getViteConfig, checking props, slots and conditional markup.
AWS SDK v3 DynamoDB document client and SQS events, tested with Vitest matchers
A single-table DynamoDB order service on @aws-sdk/lib-dynamodb with conditional puts, begins_with queries and optimistic locking, publishing SQS events, tested offline with aws-sdk-client-mock and aws-sdk-client-mock-vitest matchers.
All solid-js recipesAll @solidjs/testing-library recipesAll vite-plugin-solid recipesAll jsdom recipesAll vitest recipes