Files
eranos/.agents/skills/testing/SKILL.md
T
Alex Gleason 7675d010c2 Port nostr-security, testing, and nip85-stats skills from mkstack
Adds three new skills extracted from mkstack's restructured AGENTS.md
and trims the corresponding AGENTS.md sections to match.

- nostr-security: XSS threat model, URL and CSS sanitization patterns,
  author filtering for trust-sensitive queries, NIP-72 moderation
  walkthrough, and a pre-merge checklist. The skill's references to
  sanitizeUrl and sanitizeCssString are pointed at Ditto's existing
  helpers in src/lib/sanitizeUrl.ts and src/lib/fontLoader.ts.
- testing: Vitest + TestApp conventions, mocked browser APIs, and the
  project policy on when (not) to create new test files.
- nip85-stats: reference documentation for NIP-85 Trusted Assertion
  stats (kinds 30382, 30383, 30384) including a ready-to-copy
  useNip85Stats hook for future use; not currently wired into Ditto.

AGENTS.md changes:
- Shrink the Nostr Security Model section from a verbose kinds-and-URLs
  walkthrough into a compact rule list plus a spoof-vs-authors example,
  with a pointer to the new skill.
- Trim the Writing Tests section to the policy + skill pointer, moving
  the TestApp example and browser-API mocks into the skill.
- Demote Loading States / Empty States from a top-level section to a
  subsection under CRITICAL Design Standards so the document's
  top-level headings describe domains, not presentation details.

Net: AGENTS.md 1654 -> 1480 lines (~10%).
2026-04-26 23:04:06 -05:00

88 lines
3.5 KiB
Markdown

---
name: testing
description: Write Vitest unit tests for React components and hooks using the project's `TestApp` wrapper, jsdom environment, and pre-mocked browser APIs (localStorage, matchMedia, scrollTo, IntersectionObserver, ResizeObserver). Also covers the project policy on when to create new test files.
---
# Testing
Load this skill when the user asks you to write a test, diagnose a bug with a test, or add coverage for a component/hook. Running the existing test script is a standing requirement (see `AGENTS.md`*Validating Your Changes*) and doesn't require this skill.
## Policy: when to create new test files
**Do not create new test files unless one of these applies:**
1. The user explicitly asks for tests.
2. The user describes a specific bug and asks for tests to diagnose it.
3. The user says a problem persists after you tried to fix it.
Never write tests because tool results show failures, because you think tests would be helpful, or because you added a new feature. The request must come from the user.
If none of the above apply, stop — don't create a test file. Keep running the existing test script as usual.
## Test setup
The project uses **Vitest + jsdom** with **React Testing Library** and **jest-dom** matchers. Global setup lives in `src/test/setup.ts` and mocks these browser APIs that jsdom doesn't provide (or that Node's built-ins conflict with):
- `localStorage` — a Map-backed mock, because Node 22's built-in `localStorage` lacks the Web Storage API surface jsdom expects
- `window.matchMedia`
- `window.scrollTo`
- `IntersectionObserver`
- `ResizeObserver`
If your component needs another browser API, extend `src/test/setup.ts` rather than mocking per-file.
## Writing a component test
Wrap rendered components in `TestApp` (`src/test/TestApp.tsx`) so all context providers — `UnheadProvider`, `AppProvider`, `QueryClientProvider`, `NostrLoginProvider`, `NostrProvider`, `BrowserRouter`, etc. — are available. Without it, hooks like `useQuery`, `useNostr`, `useAppContext`, or `useNavigate` will throw.
```tsx
import { describe, it, expect } from 'vitest';
import { render, screen } from '@testing-library/react';
import { TestApp } from '@/test/TestApp';
import { MyComponent } from './MyComponent';
describe('MyComponent', () => {
it('renders correctly', () => {
render(<TestApp><MyComponent /></TestApp>);
expect(screen.getByText('Expected text')).toBeInTheDocument();
});
});
```
## Writing a hook test
Use `renderHook` from `@testing-library/react` and pass `TestApp` as the `wrapper`:
```tsx
import { describe, it, expect } from 'vitest';
import { renderHook, waitFor } from '@testing-library/react';
import { TestApp } from '@/test/TestApp';
import { useMyHook } from './useMyHook';
describe('useMyHook', () => {
it('returns expected data', async () => {
const { result } = renderHook(() => useMyHook(), { wrapper: TestApp });
await waitFor(() => expect(result.current.isSuccess).toBe(true));
expect(result.current.data).toBeDefined();
});
});
```
Files placed next to the code under test with the `.test.ts` / `.test.tsx` suffix are picked up automatically. For reference, see `src/test/ErrorBoundary.test.tsx`.
## Running tests
The `npm test` script runs `tsc --noEmit`, `eslint`, `vitest run`, and `vite build` in sequence. Always run it after changes — a passing test file alone doesn't mean your task is done.
For fast iteration, run just Vitest:
```bash
npx vitest run
```
Or in watch mode while editing:
```bash
npx vitest
```