ProvenPin Install

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
pinned versions7
solid-js@1.9.17@solidjs/testing-library@0.8.10@testing-library/user-event@14.6.8vite-plugin-solid@2.11.14jsdom@30.1.3vitest@5.0.4@types/node@26.6.5
run it
npm install --no-audit --no-fund --ignore-scripts=false
npm test
test environment
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.

package.json
{
  "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"
  }
}
src/Search.tsx
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>
  );
}
test/Search.test.tsx
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]);
  });
});
tsconfig.json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "jsx": "preserve",
    "jsxImportSource": "solid-js",
    "strict": true,
    "skipLibCheck": true
  }
}
vitest.config.ts
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.

terminal
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp

Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.