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%).
3.5 KiB
name, description
| name | description |
|---|---|
| testing | 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:
- The user explicitly asks for tests.
- The user describes a specific bug and asks for tests to diagnose it.
- 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-inlocalStoragelacks the Web Storage API surface jsdom expectswindow.matchMediawindow.scrollToIntersectionObserverResizeObserver
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.
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:
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:
npx vitest run
Or in watch mode while editing:
npx vitest