Files
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

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.mdValidating 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.

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