Zustand 5 store with persist middleware, tested with Vitest: working versions (tested 2026-10-15)
A typed Zustand 5 vanilla store with actions, derived selectors and persist middleware on a custom storage, tested with Vitest including state reset between tests.
passed4 passed · 0 failed2026-10-15 11:57 UTC- Status
- passed
- Last passed
- 2026-10-15 11:57 UTC
- Tests
- 4 / 4
- 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 38730348195
The code
Exactly the files that passed. Your agent can pull them with get_recipe("zustand-store-vitest") (install), or see them on GitHub.
{
"name": "zustand-store-vitest",
"private": true,
"type": "module",
"scripts": {
"test": "vitest run --reporter=default --reporter=json --outputFile=test-report.json"
},
"dependencies": {
"zustand": "5.0.15"
},
"devDependencies": {
"@types/node": "24.19.2",
"vitest": "5.0.4"
}
}
import { createStore } from "zustand/vanilla";
import { persist, createJSONStorage, type StateStorage } from "zustand/middleware";
export type Item = { sku: string; price: number; qty: number };
type CartState = {
items: Item[];
add: (sku: string, price: number) => void;
remove: (sku: string) => void;
clear: () => void;
};
export const total = (s: Pick<CartState, "items">) => s.items.reduce((n, i) => n + i.price * i.qty, 0);
// Use createStore for logic/tests; in React wrap it with useStore(store, selector).
export function createCartStore(storage: StateStorage) {
return createStore<CartState>()(persist((set) => ({
items: [],
add: (sku, price) => set((s) => {
const hit = s.items.find((i) => i.sku === sku);
return { items: hit ? s.items.map((i) => (i.sku === sku ? { ...i, qty: i.qty + 1 } : i)) : [...s.items, { sku, price, qty: 1 }] };
}),
remove: (sku) => set((s) => ({ items: s.items.filter((i) => i.sku !== sku) })),
clear: () => set({ items: [] }),
}), { name: "cart", storage: createJSONStorage(() => storage), partialize: (s) => ({ items: s.items }) }));
}
import { describe, it, expect, beforeEach } from "vitest";
import type { StateStorage } from "zustand/middleware";
import { createCartStore, total } from "../src/cart";
let mem: Record<string, string>;
const storage: StateStorage = { getItem: (k) => mem[k] ?? null, setItem: (k, v) => { mem[k] = v; }, removeItem: (k) => { delete mem[k]; } };
beforeEach(() => { mem = {}; });
describe("cart store", () => {
it("adds, merges quantities and totals", () => {
const s = createCartStore(storage);
s.getState().add("mug", 10); s.getState().add("mug", 10); s.getState().add("tee", 25);
expect(s.getState().items).toEqual([{ sku: "mug", price: 10, qty: 2 }, { sku: "tee", price: 25, qty: 1 }]);
expect(total(s.getState())).toBe(45);
});
it("persists only data, not actions", () => {
const s = createCartStore(storage);
s.getState().add("mug", 10);
expect(JSON.parse(mem.cart)).toEqual({ state: { items: [{ sku: "mug", price: 10, qty: 1 }] }, version: 0 });
});
it("rehydrates from storage", () => {
createCartStore(storage).getState().add("tee", 25);
expect(createCartStore(storage).getState().items).toHaveLength(1);
});
it("notifies subscribers", () => {
const s = createCartStore(storage);
const seen: number[] = [];
s.subscribe((st) => seen.push(st.items.length));
s.getState().add("a", 1); s.getState().clear();
expect(seen).toEqual([1, 0]);
});
});
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
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.
Hono + Drizzle ORM + SQLite REST API (Vitest)
CRUD REST API in Hono backed by Drizzle ORM on better-sqlite3, with Zod validation, tested with Vitest and app.request().
Hono + Valibot request validation (Vitest)
Validate JSON bodies and query strings in Hono with Valibot schemas via @hono/valibot-validator, with custom error responses, tested with Vitest.
ioredis cache-aside with TTL, tested with ioredis-mock (Vitest)
Cache-aside helper on ioredis with JSON values, TTLs, stampede-safe single flight and invalidation, tested offline with ioredis-mock in Vitest.