--- name: tdd description: The test-driven development workflow for ZettelFlow — write a failing test first, run jest, use the Obsidian mock and the bare-specifier alias mappings, and know which code is unit-testable. Use when adding or changing plugin logic, fixing a bug, or when the user asks to "write a test", "do TDD", "add coverage", or works on any of the Obsidian-score issues. --- # TDD workflow for ZettelFlow Development here is **test-first**. The blocking guardrails are `typecheck` + `oxlint` + `jest` (enforced by the `pre-push` husky hook and the CI workflow). The Obsidian guideline lint (`lint:obsidian`) is advisory. Full rationale: `docs/development/testing-and-guardrails.md`. ## The loop (red → green → refactor) 1. **Red** — write a failing test in `test/`, mirroring the source path (`test//.test.ts`). Import from `@jest/globals` (`import { describe, it, expect } from "@jest/globals"`). 2. **Green** — implement the minimal change to make it pass. 3. **Refactor** — clean up while keeping the suite green. Iterate with `npm run test:watch`; run `npm test` once; run `npm run verify` (typecheck + oxlint + test) before committing. ## How tests are wired - Tests live under `test/` (not `src/`), so the release `tsc` and esbuild never compile them. - Import source through the **same bare aliases** as the app (`architecture/...`, `hooks/...`, `application/...`); jest resolves them via `moduleNameMapper` in `jest.config.js` (mirrors `tsconfig` `baseUrl: src`). - The Obsidian runtime is stubbed by `test/__mocks__/obsidian.ts`. **Extend that mock** when the unit under test imports more of the Obsidian API (add the class/function you need). - ts-jest compiles with `tsconfig.jest.json` (transpile-only via `isolatedModules`). ## What to test first (highest ROI, no Obsidian runtime) - Pure helpers: `architecture/styles/helper.ts`, `hooks/utils/*` (already seeded). - Note assembly: `application/notes/ContentDTO` & `NoteDTO` (zones, `{{key}}` substitution, tag de-dup) — may need a couple more mock stubs. - Flow graph traversal: `architecture/plugin/canvas/Flows` (`FlowImpl.rootNodes/childrensOf`). - Wizard transitions: the `noteBuilder` callbacks with fake flow/state. ## Harder (defer until needed) - React components / modals → add `jest-environment-jsdom` + `@testing-library/react` and set `testEnvironment: "jsdom"` for those files. - The canvas monkey-patcher against a real Canvas → integration territory, not unit tests. ## TDD for a bug fix (required for the score issues) Reproduce the bug as a **failing regression test first**, then fix it. Every fix that closes an Obsidian-score issue should land with the test that proves it. ## Commands `npm test` · `npm run test:watch` · `npm run test:coverage` · `npm run typecheck` · `npm run lint` (oxlint) · `npm run lint:obsidian` (advisory) · `npm run verify`