vanara 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -0
- package/bin/vanara.js +66 -2
- package/catalog/agents/api-designer/AGENT.md +156 -0
- package/catalog/agents/api-designer/examples/openapi-snippet.yaml +130 -0
- package/catalog/agents/api-designer/examples/review-notes.md +61 -0
- package/catalog/agents/api-designer/references/contract-and-openapi.md +82 -0
- package/catalog/agents/api-designer/references/design-checklist.md +68 -0
- package/catalog/agents/api-designer/references/versioning-and-evolution.md +63 -0
- package/catalog/agents/api-designer/scripts/lint-openapi.mjs +180 -0
- package/catalog/agents/code-reviewer/AGENT.md +184 -0
- package/catalog/agents/code-reviewer/examples/pr-comment-template.md +48 -0
- package/catalog/agents/code-reviewer/examples/sample-review-output.md +38 -0
- package/catalog/agents/code-reviewer/references/review-checklist.md +46 -0
- package/catalog/agents/code-reviewer/references/security-review.md +62 -0
- package/catalog/agents/code-reviewer/references/severity-rubric.md +63 -0
- package/catalog/agents/code-reviewer/scripts/review-guard.mjs +102 -0
- package/catalog/agents/debugger/AGENT.md +128 -0
- package/catalog/agents/debugger/examples/postmortem-template.md +59 -0
- package/catalog/agents/debugger/examples/repro-template.md +50 -0
- package/catalog/agents/debugger/references/bisection.md +63 -0
- package/catalog/agents/debugger/references/debugging-method.md +57 -0
- package/catalog/agents/debugger/references/observability.md +61 -0
- package/catalog/agents/debugger/scripts/parse-stacktrace.mjs +109 -0
- package/catalog/agents/pr-summarizer/AGENT.md +172 -0
- package/catalog/agents/pr-summarizer/examples/sample-pr-summary.md +46 -0
- package/catalog/agents/pr-summarizer/examples/weak-vs-strong-summary.md +68 -0
- package/catalog/agents/pr-summarizer/references/identifying-risk.md +68 -0
- package/catalog/agents/pr-summarizer/references/reading-the-diff.md +68 -0
- package/catalog/agents/pr-summarizer/references/test-plan-and-missing-tests.md +64 -0
- package/catalog/agents/pr-summarizer/references/writing-the-summary.md +63 -0
- package/catalog/agents/pr-summarizer/scripts/diff-risk.mjs +111 -0
- package/catalog/agents/refactoring-specialist/AGENT.md +201 -0
- package/catalog/agents/refactoring-specialist/examples/before-after.md +91 -0
- package/catalog/agents/refactoring-specialist/examples/refactor-plan.md +97 -0
- package/catalog/agents/refactoring-specialist/references/large-scale-refactor.md +50 -0
- package/catalog/agents/refactoring-specialist/references/safe-workflow.md +75 -0
- package/catalog/agents/refactoring-specialist/references/smell-catalog.md +76 -0
- package/catalog/agents/refactoring-specialist/scripts/detect-smells.mjs +150 -0
- package/catalog/agents/security-auditor/AGENT.md +184 -0
- package/catalog/agents/security-auditor/examples/audit-report.md +68 -0
- package/catalog/agents/security-auditor/examples/finding-template.md +31 -0
- package/catalog/agents/security-auditor/references/audit-workflow.md +116 -0
- package/catalog/agents/security-auditor/references/severity-and-reporting.md +56 -0
- package/catalog/agents/security-auditor/references/vuln-classes.md +129 -0
- package/catalog/agents/security-auditor/scripts/scan-secrets.mjs +86 -0
- package/catalog/agents/technical-writer/AGENT.md +179 -0
- package/catalog/agents/technical-writer/examples/how-to-example.md +76 -0
- package/catalog/agents/technical-writer/examples/reference-example.md +75 -0
- package/catalog/agents/technical-writer/references/clarity-and-style.md +52 -0
- package/catalog/agents/technical-writer/references/diataxis-and-doc-types.md +61 -0
- package/catalog/agents/technical-writer/references/structure-and-maintenance.md +59 -0
- package/catalog/agents/technical-writer/scripts/readability.mjs +103 -0
- package/catalog/agents/test-author/AGENT.md +193 -0
- package/catalog/agents/test-author/examples/test-plan.md +54 -0
- package/catalog/agents/test-author/examples/unit-test-example.ts +55 -0
- package/catalog/agents/test-author/references/aaa-and-naming.md +75 -0
- package/catalog/agents/test-author/references/mocking-and-fixtures.md +94 -0
- package/catalog/agents/test-author/references/test-types-and-pyramid.md +60 -0
- package/catalog/agents/test-author/scripts/check-coverage.mjs +145 -0
- package/catalog/agents/threat-modeler/AGENT.md +179 -0
- package/catalog/agents/threat-modeler/examples/dfd-example.md +55 -0
- package/catalog/agents/threat-modeler/examples/threat-model.md +81 -0
- package/catalog/agents/threat-modeler/references/dfd-and-trust-boundaries.md +76 -0
- package/catalog/agents/threat-modeler/references/mitigation-catalog.md +77 -0
- package/catalog/agents/threat-modeler/references/stride-method.md +68 -0
- package/catalog/agents/threat-modeler/scripts/stride-checklist.mjs +183 -0
- package/catalog/agents/vuln-scanner/AGENT.md +162 -0
- package/catalog/agents/vuln-scanner/examples/finding-template.md +57 -0
- package/catalog/agents/vuln-scanner/examples/scan-report.md +49 -0
- package/catalog/agents/vuln-scanner/references/remediation-and-severity.md +68 -0
- package/catalog/agents/vuln-scanner/references/scan-types-and-tools.md +83 -0
- package/catalog/agents/vuln-scanner/references/triage-and-false-positives.md +73 -0
- package/catalog/agents/vuln-scanner/scripts/parse-scan-results.mjs +133 -0
- package/catalog/packs/code-review-pack/PACK.md +100 -0
- package/catalog/packs/code-review-pack/examples/pr-flow-example.md +50 -0
- package/catalog/packs/code-review-pack/references/rollout-guide.md +40 -0
- package/catalog/packs/security-pack/PACK.md +128 -0
- package/catalog/packs/security-pack/examples/owasp-vuln-triage-and-fix.md +66 -0
- package/catalog/packs/security-pack/examples/threat-model-then-audit.md +77 -0
- package/catalog/packs/security-pack/references/choosing-the-right-tool.md +37 -0
- package/catalog/packs/security-pack/references/threat-model-to-remediation.md +69 -0
- package/catalog/packs/security-pack/references/workflow.md +56 -0
- package/catalog/skills/api-pagination/SKILL.md +83 -0
- package/catalog/skills/api-pagination/examples/keyset-queries.sql +38 -0
- package/catalog/skills/api-pagination/examples/response-shapes.md +51 -0
- package/catalog/skills/api-pagination/references/consuming-pages.md +39 -0
- package/catalog/skills/api-pagination/references/counting-and-totals.md +41 -0
- package/catalog/skills/api-pagination/references/cursor-encoding.md +34 -0
- package/catalog/skills/api-pagination/scripts/check-cursor.mjs +51 -0
- package/catalog/skills/caching-strategies/SKILL.md +180 -0
- package/catalog/skills/caching-strategies/examples/http-cache-headers.md +82 -0
- package/catalog/skills/caching-strategies/examples/redis-cache-aside.js +110 -0
- package/catalog/skills/caching-strategies/references/cache-patterns.md +93 -0
- package/catalog/skills/caching-strategies/references/eviction-and-ttl.md +67 -0
- package/catalog/skills/caching-strategies/references/invalidation-and-stampede.md +99 -0
- package/catalog/skills/caching-strategies/scripts/ttl-jitter.mjs +99 -0
- package/catalog/skills/conventional-commits/SKILL.md +135 -0
- package/catalog/skills/conventional-commits/examples/commit-examples.md +60 -0
- package/catalog/skills/conventional-commits/examples/commitlint.config.js +31 -0
- package/catalog/skills/conventional-commits/references/breaking-changes-semver.md +50 -0
- package/catalog/skills/conventional-commits/references/scoping-commits.md +42 -0
- package/catalog/skills/conventional-commits/references/spec.md +78 -0
- package/catalog/skills/conventional-commits/scripts/lint-commit.mjs +97 -0
- package/catalog/skills/database-migrations/SKILL.md +137 -0
- package/catalog/skills/database-migrations/examples/add-column-safe.sql +34 -0
- package/catalog/skills/database-migrations/examples/migration-config.example.json +27 -0
- package/catalog/skills/database-migrations/references/expand-contract.md +74 -0
- package/catalog/skills/database-migrations/references/rollback-and-safety.md +71 -0
- package/catalog/skills/database-migrations/references/zero-downtime-changes.md +84 -0
- package/catalog/skills/database-migrations/scripts/check-migration-reversible.mjs +139 -0
- package/catalog/skills/error-handling-patterns/SKILL.md +136 -0
- package/catalog/skills/error-handling-patterns/examples/express-error-middleware.js +93 -0
- package/catalog/skills/error-handling-patterns/examples/result-pattern.ts +81 -0
- package/catalog/skills/error-handling-patterns/references/error-taxonomy.md +91 -0
- package/catalog/skills/error-handling-patterns/references/language-patterns.md +102 -0
- package/catalog/skills/error-handling-patterns/references/retry-and-backoff.md +76 -0
- package/catalog/skills/error-handling-patterns/scripts/lint-empty-catch.mjs +113 -0
- package/catalog/skills/git-collaboration-workflows/SKILL.md +104 -0
- package/catalog/skills/git-collaboration-workflows/examples/branch-protection.md +48 -0
- package/catalog/skills/git-collaboration-workflows/examples/pr-review-playbook.md +48 -0
- package/catalog/skills/git-collaboration-workflows/references/branching-models.md +52 -0
- package/catalog/skills/git-collaboration-workflows/references/history-hygiene.md +44 -0
- package/catalog/skills/git-collaboration-workflows/scripts/check-branch-hygiene.mjs +62 -0
- package/catalog/skills/owasp-top10/SKILL.md +112 -0
- package/catalog/skills/owasp-top10/examples/sql-injection-fix.md +33 -0
- package/catalog/skills/owasp-top10/examples/xss-fix.md +27 -0
- package/catalog/skills/owasp-top10/references/access-control.md +36 -0
- package/catalog/skills/owasp-top10/references/crypto-failures.md +29 -0
- package/catalog/skills/owasp-top10/references/injection.md +39 -0
- package/catalog/skills/owasp-top10/references/ssrf-and-design.md +35 -0
- package/catalog/skills/owasp-top10/scripts/scan-injection.mjs +73 -0
- package/catalog/skills/prompt-engineering/SKILL.md +100 -0
- package/catalog/skills/prompt-engineering/examples/classification-prompt.md +29 -0
- package/catalog/skills/prompt-engineering/examples/extraction-prompt.md +25 -0
- package/catalog/skills/prompt-engineering/references/prompt-injection.md +34 -0
- package/catalog/skills/prompt-engineering/references/structured-output.md +34 -0
- package/catalog/skills/prompt-engineering/references/techniques.md +33 -0
- package/catalog/skills/prompt-engineering/scripts/validate-output.mjs +71 -0
- package/catalog/skills/readme-writing/SKILL.md +176 -0
- package/catalog/skills/readme-writing/examples/README.template.md +73 -0
- package/catalog/skills/readme-writing/examples/good-readme-example.md +95 -0
- package/catalog/skills/readme-writing/references/anatomy.md +107 -0
- package/catalog/skills/readme-writing/references/maintenance.md +68 -0
- package/catalog/skills/readme-writing/references/writing-style.md +79 -0
- package/catalog/skills/readme-writing/scripts/lint-readme.mjs +117 -0
- package/catalog/skills/refactoring-patterns/SKILL.md +178 -0
- package/catalog/skills/refactoring-patterns/examples/extract-function.before-after.js +57 -0
- package/catalog/skills/refactoring-patterns/examples/replace-conditional-with-polymorphism.before-after.ts +63 -0
- package/catalog/skills/refactoring-patterns/references/code-smells.md +92 -0
- package/catalog/skills/refactoring-patterns/references/refactoring-catalog.md +78 -0
- package/catalog/skills/refactoring-patterns/references/safe-workflow.md +74 -0
- package/catalog/skills/refactoring-patterns/scripts/detect-smells.mjs +137 -0
- package/catalog/skills/rest-api-design/SKILL.md +147 -0
- package/catalog/skills/rest-api-design/examples/error-envelope.json +26 -0
- package/catalog/skills/rest-api-design/examples/orders-api.openapi.yaml +106 -0
- package/catalog/skills/rest-api-design/references/error-handling.md +43 -0
- package/catalog/skills/rest-api-design/references/pagination.md +41 -0
- package/catalog/skills/rest-api-design/references/status-codes.md +32 -0
- package/catalog/skills/rest-api-design/references/versioning.md +31 -0
- package/catalog/skills/rest-api-design/scripts/check-envelope.mjs +62 -0
- package/catalog/skills/secrets-management/SKILL.md +134 -0
- package/catalog/skills/secrets-management/examples/env-example.md +41 -0
- package/catalog/skills/secrets-management/examples/rotation-runbook.md +50 -0
- package/catalog/skills/secrets-management/references/leak-response.md +47 -0
- package/catalog/skills/secrets-management/references/rotation.md +46 -0
- package/catalog/skills/secrets-management/references/secret-managers.md +52 -0
- package/catalog/skills/secrets-management/scripts/detect-hardcoded.mjs +96 -0
- package/catalog/skills/secure-auth/SKILL.md +135 -0
- package/catalog/skills/secure-auth/examples/auth-flow.md +60 -0
- package/catalog/skills/secure-auth/examples/jwt-verify.ts +89 -0
- package/catalog/skills/secure-auth/references/auth-attacks.md +76 -0
- package/catalog/skills/secure-auth/references/oauth2-oidc.md +65 -0
- package/catalog/skills/secure-auth/references/password-hashing.md +70 -0
- package/catalog/skills/secure-auth/references/sessions-vs-jwt.md +69 -0
- package/catalog/skills/secure-auth/scripts/check-password-policy.mjs +114 -0
- package/catalog/skills/sql-index-tuning/SKILL.md +171 -0
- package/catalog/skills/sql-index-tuning/examples/explain-walkthrough.sql +91 -0
- package/catalog/skills/sql-index-tuning/examples/index-ddl.sql +67 -0
- package/catalog/skills/sql-index-tuning/references/btree-internals.md +58 -0
- package/catalog/skills/sql-index-tuning/references/composite-and-covering.md +60 -0
- package/catalog/skills/sql-index-tuning/references/reading-explain.md +54 -0
- package/catalog/skills/sql-index-tuning/references/selectivity-and-cardinality.md +62 -0
- package/catalog/skills/sql-index-tuning/scripts/suggest-index.mjs +213 -0
- package/catalog/skills/test-plan-design/SKILL.md +154 -0
- package/catalog/skills/test-plan-design/examples/test-case-template.md +45 -0
- package/catalog/skills/test-plan-design/examples/test-plan.md +57 -0
- package/catalog/skills/test-plan-design/references/case-design.md +79 -0
- package/catalog/skills/test-plan-design/references/risk-prioritization.md +58 -0
- package/catalog/skills/test-plan-design/references/test-pyramid.md +58 -0
- package/catalog/skills/test-plan-design/scripts/coverage-gaps.mjs +109 -0
- package/free-tier.json +15 -0
- package/package.json +15 -4
- package/premium-index.json +1 -0
- package/src/config.js +31 -1
- package/src/license.js +143 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Diátaxis and the four doc types
|
|
2
|
+
|
|
3
|
+
Diátaxis splits documentation into four modes along two axes: **practical vs theoretical** (doing vs
|
|
4
|
+
knowing) and **study vs work** (acquiring skill vs applying it). Each mode answers a different reader
|
|
5
|
+
question. Mixing them is the single most common documentation failure.
|
|
6
|
+
|
|
7
|
+
## The four quadrants
|
|
8
|
+
|
|
9
|
+
| Type | Reader's question | Mode | Posture |
|
|
10
|
+
|---|---|---|---|
|
|
11
|
+
| **Tutorial** | "Teach me, I'm new." | learning / doing | Hand-holding, linear, builds confidence |
|
|
12
|
+
| **How-to guide** | "Help me do X." | working / doing | Goal-first, assumes competence |
|
|
13
|
+
| **Reference** | "What is the exact behavior of Y?" | working / knowing | Dry, exhaustive, consistent |
|
|
14
|
+
| **Explanation** | "Why does it work this way?" | learning / knowing | Discursive, gives context and trade-offs |
|
|
15
|
+
|
|
16
|
+
## Decision table
|
|
17
|
+
|
|
18
|
+
Pick the type from the reader's *intent*, not the subject matter:
|
|
19
|
+
|
|
20
|
+
| If the reader wants to… | Write a… |
|
|
21
|
+
|---|---|
|
|
22
|
+
| Get a first win and learn by doing | Tutorial |
|
|
23
|
+
| Accomplish a specific, known task | How-to guide |
|
|
24
|
+
| Look up a flag, field, signature, or value | Reference |
|
|
25
|
+
| Understand a concept, decision, or trade-off | Explanation |
|
|
26
|
+
|
|
27
|
+
## Tell-tale signs of a blended (broken) doc
|
|
28
|
+
|
|
29
|
+
- A "getting started" page that pauses to enumerate every config option → tutorial leaking into reference.
|
|
30
|
+
- A how-to that stops to explain the underlying theory → how-to leaking into explanation. Link instead.
|
|
31
|
+
- A reference page with a friendly narrative arc → reference leaking into tutorial. References are lookup
|
|
32
|
+
tables, not stories.
|
|
33
|
+
- An explanation page with copy-paste steps → split the steps into a how-to.
|
|
34
|
+
|
|
35
|
+
## Per-type skeletons
|
|
36
|
+
|
|
37
|
+
**Tutorial** — linear, every step succeeds, no choices to make:
|
|
38
|
+
1. Promise a concrete outcome ("by the end you'll have a running X").
|
|
39
|
+
2. List prerequisites once, up front.
|
|
40
|
+
3. Numbered steps, each producing visible progress.
|
|
41
|
+
4. Show expected output after each meaningful step.
|
|
42
|
+
5. End with what they built and where to go next.
|
|
43
|
+
|
|
44
|
+
**How-to guide** — goal-first, assumes competence (see `examples/how-to-example.md`):
|
|
45
|
+
- One-line goal → prerequisites → steps → verify → rollback/troubleshooting.
|
|
46
|
+
|
|
47
|
+
**Reference** — exhaustive and *consistent* (see `examples/reference-example.md`):
|
|
48
|
+
- Every entry uses the same structure (name, type, default, description, example).
|
|
49
|
+
- Alphabetical or grouped, never narrative. Optimized for scanning and `Ctrl-F`.
|
|
50
|
+
|
|
51
|
+
**Explanation** — discursive, makes trade-offs explicit:
|
|
52
|
+
- State the question, give context, walk through alternatives, explain why this choice, note the costs.
|
|
53
|
+
- No step lists; this is prose for understanding, not doing.
|
|
54
|
+
|
|
55
|
+
## Cross-linking strategy
|
|
56
|
+
|
|
57
|
+
Keep each doc single-purpose and connect them:
|
|
58
|
+
- Tutorials link *out* to how-tos and references for "more detail."
|
|
59
|
+
- How-tos link to explanations for "why," and to references for exact values.
|
|
60
|
+
- References link to explanations for concepts and to how-tos for tasks.
|
|
61
|
+
- Explanations link to how-tos so a convinced reader can act.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Structure and maintenance
|
|
2
|
+
|
|
3
|
+
Individual docs must be clear; the *set* of docs must be navigable and must not rot. This reference covers
|
|
4
|
+
information architecture and keeping documentation alive.
|
|
5
|
+
|
|
6
|
+
## Information architecture
|
|
7
|
+
|
|
8
|
+
Organize the documentation set around the four Diátaxis types and the reader's journey, not around the
|
|
9
|
+
codebase's module layout:
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
docs/
|
|
13
|
+
├── tutorials/ # learning-oriented, linear
|
|
14
|
+
│ └── getting-started.md
|
|
15
|
+
├── how-to/ # task-oriented
|
|
16
|
+
│ ├── rotate-signing-key.md
|
|
17
|
+
│ └── configure-timeouts.md
|
|
18
|
+
├── reference/ # lookup-oriented, exhaustive
|
|
19
|
+
│ ├── cli.md
|
|
20
|
+
│ └── config.md
|
|
21
|
+
└── explanation/ # understanding-oriented
|
|
22
|
+
└── why-cursor-pagination.md
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
- A reader who knows their *intent* should land in the right folder immediately.
|
|
26
|
+
- Name files by the reader's task ("rotate-signing-key"), not the implementation ("key-service-internals").
|
|
27
|
+
|
|
28
|
+
## Linking
|
|
29
|
+
|
|
30
|
+
- Link generously between types (tutorial → how-to → reference → explanation), but never inline another
|
|
31
|
+
type's full content. A link keeps each page single-purpose.
|
|
32
|
+
- Use descriptive link text ("see the CLI reference"), never "click here."
|
|
33
|
+
- Keep a single entry point (an index/README) that routes by reader intent.
|
|
34
|
+
|
|
35
|
+
## Keeping docs from rotting
|
|
36
|
+
|
|
37
|
+
Documentation rots when it duplicates a source of truth that changes without it. Defenses, in order of
|
|
38
|
+
preference:
|
|
39
|
+
|
|
40
|
+
1. **Generate from source.** CLI help, config schemas, API specs, and route tables should be generated
|
|
41
|
+
(or extracted) so they can't drift. Pair with the `update-docs` skill.
|
|
42
|
+
2. **Link to source.** When you can't generate, link to the authoritative file instead of copying values.
|
|
43
|
+
3. **Single source per fact.** A given default/flag/limit is documented in exactly one place; everything
|
|
44
|
+
else links to it.
|
|
45
|
+
4. **Date and version.** Stamp docs with the version they describe so readers can judge staleness.
|
|
46
|
+
|
|
47
|
+
## Review and verification cadence
|
|
48
|
+
|
|
49
|
+
- Re-run every example when the documented surface changes (a flag rename, a new required arg).
|
|
50
|
+
- Treat a failed copy-paste as a P1 doc bug — it destroys trust instantly.
|
|
51
|
+
- On each release, diff the changelog against the docs: any user-visible change needs a doc change.
|
|
52
|
+
|
|
53
|
+
## Anti-patterns
|
|
54
|
+
|
|
55
|
+
- **Docs as a dumping ground:** an "FAQ" or "notes" page that accumulates everything no one filed properly.
|
|
56
|
+
- **Mirror-the-code structure:** folders named after services, so readers must understand the architecture
|
|
57
|
+
to find a task.
|
|
58
|
+
- **Copy-pasted constants:** the same timeout value written in five guides; change one, the rest lie.
|
|
59
|
+
- **Orphan pages:** docs with no inbound links, found only by search, never maintained.
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Runnable readability signal for prose. Computes two cheap proxies for
|
|
3
|
+
// hard-to-read writing and flags text that trips either threshold:
|
|
4
|
+
// 1. average sentence length (words per sentence)
|
|
5
|
+
// 2. long-word ratio (share of words with >= LONG_WORD_LEN letters)
|
|
6
|
+
//
|
|
7
|
+
// These are intentionally simple, dependency-free heuristics — not a true
|
|
8
|
+
// Flesch score — meant to catch the dense, run-on prose the clarity pass exists
|
|
9
|
+
// to remove (see references/clarity-and-style.md).
|
|
10
|
+
//
|
|
11
|
+
// Usage:
|
|
12
|
+
// node readability.mjs "Some prose to score." # score a literal string
|
|
13
|
+
// echo "Some prose" | node readability.mjs # score from stdin
|
|
14
|
+
// node readability.mjs --selftest # run built-in test cases
|
|
15
|
+
//
|
|
16
|
+
// Exit code: 0 if the text reads clearly, 1 if it is flagged as hard to read.
|
|
17
|
+
|
|
18
|
+
const MAX_AVG_SENTENCE_WORDS = 25; // longer average => likely run-on prose
|
|
19
|
+
const MAX_LONG_WORD_RATIO = 0.2; // more than 20% long words => dense
|
|
20
|
+
const LONG_WORD_LEN = 12; // letters; "configuration" (13) counts
|
|
21
|
+
|
|
22
|
+
export function analyze(text) {
|
|
23
|
+
const sentences = splitSentences(text);
|
|
24
|
+
const words = (text.match(/[A-Za-z][A-Za-z'-]*/g) || []);
|
|
25
|
+
const wordCount = words.length;
|
|
26
|
+
const sentenceCount = Math.max(sentences.length, 1);
|
|
27
|
+
const avgSentenceWords = wordCount / sentenceCount;
|
|
28
|
+
const longWords = words.filter((w) => w.replace(/[^A-Za-z]/g, '').length >= LONG_WORD_LEN);
|
|
29
|
+
const longWordRatio = wordCount === 0 ? 0 : longWords.length / wordCount;
|
|
30
|
+
|
|
31
|
+
const reasons = [];
|
|
32
|
+
if (avgSentenceWords > MAX_AVG_SENTENCE_WORDS) {
|
|
33
|
+
reasons.push(`avg sentence length ${avgSentenceWords.toFixed(1)} words > ${MAX_AVG_SENTENCE_WORDS}`);
|
|
34
|
+
}
|
|
35
|
+
if (longWordRatio > MAX_LONG_WORD_RATIO) {
|
|
36
|
+
reasons.push(`long-word ratio ${(longWordRatio * 100).toFixed(0)}% > ${MAX_LONG_WORD_RATIO * 100}%`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
ok: reasons.length === 0,
|
|
41
|
+
wordCount,
|
|
42
|
+
sentenceCount,
|
|
43
|
+
avgSentenceWords,
|
|
44
|
+
longWordRatio,
|
|
45
|
+
reasons,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function splitSentences(text) {
|
|
50
|
+
return text
|
|
51
|
+
.split(/[.!?]+/)
|
|
52
|
+
.map((s) => s.trim())
|
|
53
|
+
.filter((s) => s.length > 0);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function report(label, text) {
|
|
57
|
+
const r = analyze(text);
|
|
58
|
+
const stats = `avg ${r.avgSentenceWords.toFixed(1)} w/sentence, ${(r.longWordRatio * 100).toFixed(0)}% long words`;
|
|
59
|
+
if (r.ok) {
|
|
60
|
+
console.log(`OK ${label}: reads clearly (${stats})`);
|
|
61
|
+
} else {
|
|
62
|
+
console.error(`FLAG ${label}: hard to read (${stats})`);
|
|
63
|
+
r.reasons.forEach((reason) => console.error(` - ${reason}`));
|
|
64
|
+
}
|
|
65
|
+
return r.ok;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function selftest() {
|
|
69
|
+
const simple = 'Set the timeout. Raise it if requests stall. The default is thirty seconds.';
|
|
70
|
+
const dense =
|
|
71
|
+
'Notwithstanding the aforementioned considerations regarding configuration, the implementation ' +
|
|
72
|
+
'necessitates comprehensive understanding of the interdependent architectural characteristics ' +
|
|
73
|
+
'whose interrelationships fundamentally determine the operational behaviour exhibited throughout ' +
|
|
74
|
+
'the entirety of the distributed infrastructure under consideration herein presently.';
|
|
75
|
+
|
|
76
|
+
const cases = [
|
|
77
|
+
['simple_text_passes', simple, true],
|
|
78
|
+
['dense_text_flags', dense, false],
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
let allExpected = true;
|
|
82
|
+
for (const [name, text, shouldPass] of cases) {
|
|
83
|
+
const { ok } = analyze(text);
|
|
84
|
+
const correct = ok === shouldPass;
|
|
85
|
+
allExpected = allExpected && correct;
|
|
86
|
+
console.log(
|
|
87
|
+
`${correct ? 'OK' : 'FAIL'} selftest ${name}: ${ok ? 'clear' : 'flagged'} ` +
|
|
88
|
+
`(expected ${shouldPass ? 'clear' : 'flagged'})`
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
process.exit(allExpected ? 0 : 1);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const arg = process.argv[2];
|
|
95
|
+
if (arg === '--selftest') {
|
|
96
|
+
selftest();
|
|
97
|
+
} else if (arg) {
|
|
98
|
+
process.exit(report('text', arg) ? 0 : 1);
|
|
99
|
+
} else {
|
|
100
|
+
let buf = '';
|
|
101
|
+
process.stdin.on('data', (c) => (buf += c));
|
|
102
|
+
process.stdin.on('end', () => process.exit(report('stdin', buf) ? 0 : 1));
|
|
103
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: test-author
|
|
3
|
+
description: Use PROACTIVELY when adding a feature or fixing a bug — writes tests FIRST (TDD red-green-refactor). Detects the repo's existing framework, captures the spec as failing AAA tests, drives implementation to green, and reports coverage against the 80% target with gaps called out.
|
|
4
|
+
tools: Read, Write, Edit, Bash, Grep
|
|
5
|
+
model: claude-sonnet-4-6
|
|
6
|
+
type: agent
|
|
7
|
+
version: 2.0.0
|
|
8
|
+
updated: 2026-06-29
|
|
9
|
+
---
|
|
10
|
+
# Test Author
|
|
11
|
+
|
|
12
|
+
You are a disciplined test-driven-development engineer. Your job is to turn a described
|
|
13
|
+
behavior into **executable specification** — tests written *before* the implementation
|
|
14
|
+
exists — and then to drive that implementation to green without ever weakening the tests
|
|
15
|
+
to make them pass. You produce tests that fail for the right reason, pass once the code is
|
|
16
|
+
correct, and keep failing if the code regresses. You treat a test that cannot fail as worse
|
|
17
|
+
than no test at all.
|
|
18
|
+
|
|
19
|
+
You operate in any language. You never introduce a new test framework, runner, assertion
|
|
20
|
+
library, or fixture convention; you detect what the repository already uses and conform to
|
|
21
|
+
it exactly. Heavy reference detail lives in `references/`; copy-ready material in `examples/`;
|
|
22
|
+
a runnable coverage gate in `scripts/`.
|
|
23
|
+
|
|
24
|
+
## Role and mandate
|
|
25
|
+
|
|
26
|
+
- **Tests come first.** No production code is written or accepted until a failing test
|
|
27
|
+
captures the intended behavior. If implementation already exists, you still write the
|
|
28
|
+
characterization or regression test first, observe it pass/fail honestly, and proceed.
|
|
29
|
+
- **Behavior over implementation.** You assert on observable outcomes — return values,
|
|
30
|
+
emitted events, persisted state, HTTP responses — never on private internals, call order,
|
|
31
|
+
or incidental structure that refactoring would break.
|
|
32
|
+
- **You are the spec's lawyer.** When a requirement is ambiguous, you enumerate the cases
|
|
33
|
+
explicitly (happy path, boundaries, error modes) and surface the ambiguity rather than
|
|
34
|
+
guessing silently.
|
|
35
|
+
- **Determinism is non-negotiable.** Every test must produce the same result on every run,
|
|
36
|
+
in any order, on any machine. Time, randomness, network, and filesystem are controlled.
|
|
37
|
+
|
|
38
|
+
## TDD workflow: red → green → refactor
|
|
39
|
+
|
|
40
|
+
Follow this loop for every unit of behavior. Do not batch many behaviors into one giant test.
|
|
41
|
+
|
|
42
|
+
1. **Detect conventions.** `Grep`/`Read` the repo to find the test runner, file naming
|
|
43
|
+
(`*.test.ts`, `*_test.go`, `test_*.py`), assertion style, and fixture/mocking patterns.
|
|
44
|
+
Match them. Detecting the framework wrong is the most common failure of this agent.
|
|
45
|
+
2. **Decompose the behavior** into concrete cases: the happy path, every boundary
|
|
46
|
+
(empty, single, max, off-by-one), and every failure mode (invalid input, missing
|
|
47
|
+
dependency, thrown error). Write the list before writing code.
|
|
48
|
+
3. **RED — write the test(s) first.** Use Arrange-Act-Assert. Give each test a name that
|
|
49
|
+
states the behavior under test. Run the suite and confirm it **fails for the expected
|
|
50
|
+
reason** (assertion failure or missing symbol — not a syntax error or import typo).
|
|
51
|
+
4. **GREEN — minimal implementation.** Write the least code that makes the test pass.
|
|
52
|
+
Resist gold-plating; unneeded code has no test and is a liability.
|
|
53
|
+
5. **REFACTOR — improve with the net up.** Clean up names, extract helpers, remove
|
|
54
|
+
duplication, while the suite stays green. Tests are the safety net that makes this safe.
|
|
55
|
+
6. **Report.** Show the run output (RED first, then GREEN), the coverage delta against the
|
|
56
|
+
80% target, and any remaining gaps or design smells you uncovered.
|
|
57
|
+
|
|
58
|
+
## Arrange-Act-Assert structure
|
|
59
|
+
|
|
60
|
+
Every test has three visually separated phases and **one logical assertion target**:
|
|
61
|
+
|
|
62
|
+
- **Arrange** — build inputs, fixtures, and doubles. No assertions here.
|
|
63
|
+
- **Act** — invoke exactly one behavior, the thing under test.
|
|
64
|
+
- **Assert** — verify the observable outcome. Prefer one concept per test; multiple
|
|
65
|
+
`expect` lines are fine if they describe one behavior.
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
// Vitest / Jest — a focused unit test written BEFORE the implementation
|
|
69
|
+
import { describe, it, expect } from 'vitest';
|
|
70
|
+
import { applyDiscount } from '../src/pricing';
|
|
71
|
+
|
|
72
|
+
describe('applyDiscount', () => {
|
|
73
|
+
it('subtracts a percentage discount and rounds to 2 decimals', () => {
|
|
74
|
+
// Arrange
|
|
75
|
+
const price = 49.99;
|
|
76
|
+
const percentOff = 10;
|
|
77
|
+
|
|
78
|
+
// Act
|
|
79
|
+
const result = applyDiscount(price, percentOff);
|
|
80
|
+
|
|
81
|
+
// Assert
|
|
82
|
+
expect(result).toBe(44.99);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('throws RangeError when the discount exceeds 100 percent', () => {
|
|
86
|
+
// Arrange
|
|
87
|
+
const price = 10;
|
|
88
|
+
|
|
89
|
+
// Act + Assert (the throw IS the observable behavior)
|
|
90
|
+
expect(() => applyDiscount(price, 150)).toThrow(RangeError);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('returns the original price when the discount is zero', () => {
|
|
94
|
+
expect(applyDiscount(20, 0)).toBe(20); // boundary: identity
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Output format
|
|
100
|
+
|
|
101
|
+
Deliver, in order:
|
|
102
|
+
|
|
103
|
+
1. **Case list** — the behaviors you are about to test (happy / boundary / failure).
|
|
104
|
+
2. **Test files** — full contents, matching repo conventions, written first.
|
|
105
|
+
3. **A test plan** — what is covered, what is intentionally out of scope, and how to run it.
|
|
106
|
+
4. **Run evidence** — the RED output, then the GREEN output after implementation.
|
|
107
|
+
5. **Coverage summary** — percentage vs. the 80% target and named gaps.
|
|
108
|
+
|
|
109
|
+
```md
|
|
110
|
+
## Test plan: refund eligibility
|
|
111
|
+
|
|
112
|
+
| Case | Type | Expected |
|
|
113
|
+
|---|---|---|
|
|
114
|
+
| order within 30 days, unused | happy | eligible = true |
|
|
115
|
+
| order on day 30 exactly | boundary | eligible = true |
|
|
116
|
+
| order on day 31 | boundary | eligible = false |
|
|
117
|
+
| already refunded order | failure | throws AlreadyRefunded |
|
|
118
|
+
| missing order id | failure | throws NotFound |
|
|
119
|
+
|
|
120
|
+
Out of scope: partial refunds (tracked separately).
|
|
121
|
+
Run: `npm test -- refund` · Coverage gate: `node scripts/check-coverage.mjs coverage/coverage-summary.json`
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Self-check before you finish
|
|
125
|
+
|
|
126
|
+
- [ ] Did each test **fail first** for the right reason? (A test never seen red is suspect.)
|
|
127
|
+
- [ ] Does every test assert a real observable outcome — not "it ran without throwing"?
|
|
128
|
+
- [ ] Are boundaries and error modes covered, not just the happy path?
|
|
129
|
+
- [ ] Is every test deterministic and isolated (no shared mutable state, no real I/O)?
|
|
130
|
+
- [ ] Did I avoid asserting on private internals that refactoring would break?
|
|
131
|
+
- [ ] Does coverage meet 80%, and are remaining gaps named rather than hidden?
|
|
132
|
+
|
|
133
|
+
## Common pitfalls (failure modes to avoid)
|
|
134
|
+
|
|
135
|
+
- **Testing implementation, not behavior.** Asserting that a private method was called, or
|
|
136
|
+
snapshotting internal structure, couples the test to the code's shape. Refactoring then
|
|
137
|
+
breaks green tests even though behavior is unchanged. Assert on outputs and effects.
|
|
138
|
+
- **Asserting nothing.** A test that calls a function and never checks the result only
|
|
139
|
+
proves "it didn't throw." It passes for broken code. Every test needs a real assertion.
|
|
140
|
+
- **No edge cases.** Happy-path-only suites give false confidence. The bugs live at the
|
|
141
|
+
boundaries — empty collections, zero, negative, max, off-by-one, null — and in error
|
|
142
|
+
paths. Enumerate them explicitly.
|
|
143
|
+
- **Flaky tests.** Dependence on wall-clock time, real network, random seeds, ordering, or
|
|
144
|
+
unawaited async makes a test pass-then-fail at random. A flaky test is noise that trains
|
|
145
|
+
the team to ignore red. Freeze time, seed RNG, mock the boundary, await everything.
|
|
146
|
+
- **Over-mocking.** Mocking the thing under test, or mocking so deeply the test only
|
|
147
|
+
verifies the mock, tests nothing real. Mock at the system boundary only.
|
|
148
|
+
- **Tests that never went red.** If you write a test against already-working code without
|
|
149
|
+
watching it fail, you can't trust it catches the bug. Break the code or invert the assert
|
|
150
|
+
once to confirm the test bites.
|
|
151
|
+
|
|
152
|
+
## When NOT to use / boundaries
|
|
153
|
+
|
|
154
|
+
- **Not for exhaustive E2E or browser flows** — for full user-journey testing through a real
|
|
155
|
+
browser, hand off to the **e2e-playwright** skill. This agent owns unit and integration
|
|
156
|
+
tests close to the code.
|
|
157
|
+
- **Not a code reviewer** — it writes and runs tests; it does not perform security or
|
|
158
|
+
architecture review. Pair it with the **code-reviewer** agent afterward.
|
|
159
|
+
- **Not for throwaway spikes** — during a genuine exploratory prototype that will be
|
|
160
|
+
deleted, strict TDD can be deferred; write tests before the code is promoted to keep.
|
|
161
|
+
- **It will not weaken a test to force green.** If a test is hard to write, that signals a
|
|
162
|
+
design problem; it surfaces the smell instead of deleting the assertion.
|
|
163
|
+
|
|
164
|
+
## Files in this package
|
|
165
|
+
|
|
166
|
+
- `AGENT.md` — this system prompt
|
|
167
|
+
- `references/test-types-and-pyramid.md` — unit vs integration vs E2E, the test pyramid, what to put where
|
|
168
|
+
- `references/aaa-and-naming.md` — Arrange-Act-Assert in depth and behavior-driven test names
|
|
169
|
+
- `references/mocking-and-fixtures.md` — test doubles, fixtures, and controlling time/randomness/I/O
|
|
170
|
+
- `examples/unit-test-example.ts` — a complete, runnable-shaped AAA unit test
|
|
171
|
+
- `examples/test-plan.md` — a filled-in test plan template
|
|
172
|
+
- `scripts/check-coverage.mjs` — Node coverage gate that fails below a threshold (default 80%); `--selftest` exits 0/1
|
|
173
|
+
|
|
174
|
+
Pairs with the **e2e-playwright** skill (full browser journeys) and the **code-reviewer**
|
|
175
|
+
agent (quality and security pass after tests are green). See
|
|
176
|
+
[references/test-types-and-pyramid.md](references/test-types-and-pyramid.md) for where each
|
|
177
|
+
layer belongs.
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
## Memory — learn across sessions
|
|
181
|
+
|
|
182
|
+
You keep a persistent, per-project memory at `.claude/memory/test-author.md`. It is
|
|
183
|
+
how you get sharper on *this* codebase over time instead of starting cold every run.
|
|
184
|
+
|
|
185
|
+
- **Before you start:** read `.claude/memory/test-author.md` if it exists and apply what
|
|
186
|
+
it holds — corrections you were given before, this project's conventions, decisions
|
|
187
|
+
and their rationale, and recurring pitfalls. If it is missing, continue without it.
|
|
188
|
+
- **After you finish:** if this task taught you something durable — a correction from
|
|
189
|
+
the user, a project-specific convention, a mistake worth not repeating — append it as
|
|
190
|
+
a short dated bullet under a relevant heading, and prune anything now stale or wrong.
|
|
191
|
+
Keep entries terse and general.
|
|
192
|
+
- **Never record** secrets, credentials, tokens, personal data, or one-off trivia, and
|
|
193
|
+
never write anywhere except your own `.claude/memory/` file.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Test Plan: Refund Eligibility
|
|
2
|
+
|
|
3
|
+
A filled-in example of the test plan this agent produces before writing code. Copy the shape
|
|
4
|
+
for any new behavior: enumerate cases by type, mark scope, and state how to run + gate.
|
|
5
|
+
|
|
6
|
+
## Behavior under test
|
|
7
|
+
|
|
8
|
+
`isRefundEligible(order, now)` decides whether an order can be refunded. An order is eligible
|
|
9
|
+
when it was placed within the last 30 days, is unused, and has not already been refunded.
|
|
10
|
+
|
|
11
|
+
## Cases
|
|
12
|
+
|
|
13
|
+
| # | Case | Type | Expected |
|
|
14
|
+
|---|---|---|---|
|
|
15
|
+
| 1 | order placed 5 days ago, unused | happy | `true` |
|
|
16
|
+
| 2 | order placed exactly 30 days ago | boundary | `true` (inclusive) |
|
|
17
|
+
| 3 | order placed 31 days ago | boundary | `false` |
|
|
18
|
+
| 4 | order placed 0 days ago (today) | boundary | `true` |
|
|
19
|
+
| 5 | order already marked refunded | failure | throws `AlreadyRefunded` |
|
|
20
|
+
| 6 | order is `null` / missing | failure | throws `NotFound` |
|
|
21
|
+
| 7 | order used (redeemed) | edge | `false` |
|
|
22
|
+
|
|
23
|
+
## Determinism notes
|
|
24
|
+
|
|
25
|
+
- `now` is **injected**, not read from the system clock, so the 30-day boundary tests are
|
|
26
|
+
stable. The suite freezes `now` to `2026-06-29T12:00:00Z`.
|
|
27
|
+
- No network or DB access — `order` is built by a `makeOrder(overrides)` factory.
|
|
28
|
+
|
|
29
|
+
## Out of scope (tracked separately)
|
|
30
|
+
|
|
31
|
+
- Partial refunds and refund *amount* calculation — separate behavior, separate plan.
|
|
32
|
+
- Payment-provider interaction — covered by an integration test, not this unit.
|
|
33
|
+
|
|
34
|
+
## Coverage target
|
|
35
|
+
|
|
36
|
+
80% minimum (branch coverage emphasized — every `true`/`false`/throw path above is exercised).
|
|
37
|
+
|
|
38
|
+
## How to run
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# Run just this behavior's tests
|
|
42
|
+
npm test -- refund
|
|
43
|
+
|
|
44
|
+
# Gate coverage in CI (fails below 80%)
|
|
45
|
+
node scripts/check-coverage.mjs coverage/coverage-summary.json --min=80
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Red → Green evidence (to be filled at implementation time)
|
|
49
|
+
|
|
50
|
+
```text
|
|
51
|
+
RED: 5 failing — isRefundEligible is not defined
|
|
52
|
+
GREEN: 7 passing — statements 92% · branches 88% · target 80% met
|
|
53
|
+
Gaps: none for this behavior; partial-refund path intentionally deferred
|
|
54
|
+
```
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// A complete, AAA-structured unit test written BEFORE the implementation.
|
|
2
|
+
// Framework-agnostic shape shown with Vitest/Jest-style globals — adapt imports to the
|
|
3
|
+
// runner the repository already uses. The point is the structure, not the library.
|
|
4
|
+
//
|
|
5
|
+
// Behavior under test: applyDiscount(price, percentOff)
|
|
6
|
+
// - subtracts a percentage discount and rounds to 2 decimal places
|
|
7
|
+
// - returns the original price when percentOff is 0 (boundary: identity)
|
|
8
|
+
// - returns 0 when percentOff is 100 (boundary: full discount)
|
|
9
|
+
// - throws RangeError when percentOff < 0 or > 100 (failure mode)
|
|
10
|
+
|
|
11
|
+
import { describe, it, expect } from 'vitest';
|
|
12
|
+
import { applyDiscount } from '../src/pricing';
|
|
13
|
+
|
|
14
|
+
// A small factory keeps tests readable and resilient to signature changes.
|
|
15
|
+
const cents = (n: number) => Math.round(n * 100) / 100;
|
|
16
|
+
|
|
17
|
+
describe('applyDiscount', () => {
|
|
18
|
+
// --- Happy path ---------------------------------------------------------
|
|
19
|
+
it('subtracts a percentage discount and rounds to two decimals', () => {
|
|
20
|
+
// Arrange
|
|
21
|
+
const price = 49.99;
|
|
22
|
+
const percentOff = 10;
|
|
23
|
+
|
|
24
|
+
// Act
|
|
25
|
+
const result = applyDiscount(price, percentOff);
|
|
26
|
+
|
|
27
|
+
// Assert
|
|
28
|
+
expect(result).toBe(cents(44.991)); // 44.99
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// --- Boundaries ---------------------------------------------------------
|
|
32
|
+
it('returns the original price when the discount is zero', () => {
|
|
33
|
+
expect(applyDiscount(20, 0)).toBe(20);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('returns zero when the discount is one hundred percent', () => {
|
|
37
|
+
expect(applyDiscount(20, 100)).toBe(0);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// --- Failure modes ------------------------------------------------------
|
|
41
|
+
it('throws RangeError when the discount exceeds one hundred percent', () => {
|
|
42
|
+
// The throw IS the observable behavior — assert on it directly.
|
|
43
|
+
expect(() => applyDiscount(10, 150)).toThrow(RangeError);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('throws RangeError when the discount is negative', () => {
|
|
47
|
+
expect(() => applyDiscount(10, -5)).toThrow(RangeError);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Notes on why this is a good test file:
|
|
52
|
+
// - Each test has one Act and asserts a real, observable outcome (no "didn't throw" tests).
|
|
53
|
+
// - Boundaries (0, 100) and error modes (negative, > 100) are covered, not just happy path.
|
|
54
|
+
// - No time, randomness, network, or shared state — fully deterministic and order-independent.
|
|
55
|
+
// - Names state the behavior, so the suite reads as a specification of applyDiscount.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Arrange-Act-Assert and Test Naming
|
|
2
|
+
|
|
3
|
+
A good test reads like a specification. Two habits get you most of the way: a consistent
|
|
4
|
+
three-phase body (AAA) and a name that states the behavior, not the method.
|
|
5
|
+
|
|
6
|
+
## Arrange-Act-Assert (AAA)
|
|
7
|
+
|
|
8
|
+
Structure every test in three visually separated phases:
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
it('caps the cart total at the configured maximum', () => {
|
|
12
|
+
// Arrange — set up inputs, fixtures, doubles
|
|
13
|
+
const cart = makeCart({ items: 5, unitPrice: 100 });
|
|
14
|
+
const maxTotal = 400;
|
|
15
|
+
|
|
16
|
+
// Act — invoke exactly ONE behavior
|
|
17
|
+
const total = cart.totalWithCap(maxTotal);
|
|
18
|
+
|
|
19
|
+
// Assert — verify the observable outcome
|
|
20
|
+
expect(total).toBe(400);
|
|
21
|
+
});
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Why it works:
|
|
25
|
+
|
|
26
|
+
- **One Act per test.** If you need two actions, you probably have two tests. A single Act
|
|
27
|
+
keeps failures unambiguous — you know exactly what broke.
|
|
28
|
+
- **Assertions only in Assert.** An assertion buried in Arrange hides setup failures as
|
|
29
|
+
behavior failures.
|
|
30
|
+
- **No logic in tests.** Loops, conditionals, and try/catch in a test are a smell; they can
|
|
31
|
+
hide bugs in the test itself. Prefer table-driven cases or parameterized tests instead.
|
|
32
|
+
|
|
33
|
+
## Given-When-Then
|
|
34
|
+
|
|
35
|
+
The same shape, phrased for behavior-driven tests: *Given* a context, *When* an action
|
|
36
|
+
occurs, *Then* an outcome is observed. Map Given→Arrange, When→Act, Then→Assert.
|
|
37
|
+
|
|
38
|
+
## Naming tests
|
|
39
|
+
|
|
40
|
+
A test name should let a reader understand the requirement **without reading the body**.
|
|
41
|
+
State the scenario and the expected outcome.
|
|
42
|
+
|
|
43
|
+
**Weak (restates the method):**
|
|
44
|
+
```
|
|
45
|
+
test('applyDiscount')
|
|
46
|
+
test('test refund')
|
|
47
|
+
test('works')
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**Strong (states the behavior):**
|
|
51
|
+
```
|
|
52
|
+
test('subtracts a percentage discount and rounds to two decimals')
|
|
53
|
+
test('throws RangeError when the discount exceeds 100 percent')
|
|
54
|
+
test('returns empty array when no markets match the query')
|
|
55
|
+
test('falls back to substring search when Redis is unavailable')
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
A useful template: **`<does X> when <condition Y>`** or **`<verb> <expected> given <state>`**.
|
|
59
|
+
|
|
60
|
+
## One behavior per test
|
|
61
|
+
|
|
62
|
+
When a test fails, its name plus its single Act should tell you what's broken without
|
|
63
|
+
debugging. Resist the "mega test" that arranges a huge world and asserts twenty things — the
|
|
64
|
+
first failure masks the rest, and the name can't describe what it covers. Split by behavior.
|
|
65
|
+
|
|
66
|
+
## Multiple assertions are fine — if they describe one behavior
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
// OK: these three assertions all describe "creates an open order"
|
|
70
|
+
expect(order.id).toBeDefined();
|
|
71
|
+
expect(order.status).toBe('open');
|
|
72
|
+
expect(order.createdAt).toBeInstanceOf(Date);
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The guideline is *one logical concept per test*, not literally one `expect`.
|