storytelling-tests/SKILL.md
--- name: storytelling-tests description: Enforce storytelling test patterns with Arrange-Act-Assert structure and domain-specific testing language (DSTL). Use when asked to "write tests for", "create tests", "add tests", "review my tests", "check my tests", "improve test coverage", "make these tests better", "refactor these tests", or "test this function". Also use when planning test implementation, designing test strategy, or when a plan includes steps to add or create tests. Also triggers whe
npx skillsauth add MrToxy/claude-skills storytelling-testsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Tests should read like stories. A developer unfamiliar with the codebase should understand what's being tested from the test code alone — without reading the implementation.
This is achieved through two core principles:
Every test MUST have three clearly separated blocks with comments and blank lines between them.
Bad:
test('creates a link for matching contact', async () => {
const org = createTestOrg({ vatNumber: 'PT123' });
await seedOrganization(org);
const contact = createTestContact({ emails: ['[email protected]'], organizationId: org.id });
await seedContact(contact, org);
await handleLinkEvent({ email: '[email protected]', vatNumber: 'PT123' });
const links = await getLinks();
expect(links).toHaveLength(1);
expect(links[0].contactId).toBe(contact.id);
});
Good:
test('creates a link for matching contact', async () => {
// Arrange
const { contactId } = await createOrganizationWithContact();
// Act
await handleLinkEvent({ email: '[email protected]', vatNumber: 'PT123' });
// Assert
await expectLinkToBeCreatedForContact(contactId);
});
Extract setup and assertion logic into helper functions with descriptive names that convey business intent. The test body should read as a high-level specification.
Bad:
const org = createTestOrg({ vatNumber: 'PT333333333' });
const contactOld = createTestContact({ emails: ['[email protected]'], organizationId: org.id });
const contactRecent = createTestContact({ emails: ['[email protected]'], organizationId: org.id });
const oldOpp = createTestOpportunity({ contactId: contactOld.id, createdAt: '2024-01-01' });
const recentOpp = createTestOpportunity({ contactId: contactRecent.id, createdAt: '2025-01-01' });
await seedOrganization(org);
await seedContact(contactOld, org);
await seedContact(contactRecent, org);
await seedOpportunity(oldOpp);
await seedOpportunity(recentOpp);
Good:
const { orgId, contacts } = await createOrganizationAndContacts();
const opportunities = await createOpportunitiesWithDifferentDates(orgId, contacts);
The test name describes the business rule or behavior being verified, not the technical mechanism.
Bad:
'returns 403 when role is not admin'
'calls saveDb with correct params'
Good:
'only admins can delete vehicles'
'if more than 1 contact matches, link is created with the contact that has the most recent opportunity activity'
Each test exercises exactly one action. If you need to test multiple outcomes of the same action, use separate tests with shared setup.
Bad:
test('handles vehicle creation', async () => {
await createVehicle(data);
const vehicle = await getVehicle(id);
expect(vehicle).toBeDefined();
await updateVehicle(id, newData);
const updated = await getVehicle(id);
expect(updated.status).toBe('active');
});
Good:
test('vehicle is persisted after creation', async () => {
// Arrange
const data = buildVehicleData();
// Act
await createVehicle(data);
// Assert
await expectVehicleToExist(data.id);
});
Helper function names should describe what is being set up in domain terms, not how it's constructed. The reader should never need to look inside the helper to understand the test.
Bad: setupData(), prepareTest(), init()
Good: createOrganizationWithContact(), createOpportunitiesWithDifferentDates(), seedVehicleReadyForSale()
Wrap complex assertions in descriptive functions. Raw expect().toMatchObject() with inline objects forces the reader to mentally parse structure.
Bad:
const links = await getLinks();
expect(links).toHaveLength(1);
expect(links[0]).toMatchObject({
websiteUserId: 'website-user-5',
organizationId: org.id,
contactId: contactRecent.id,
});
Good:
await expectLinkToBeCreatedWithMostRecentOpportunity(opportunities);
Simple, single-value assertions (e.g. expect(result).toBe(true)) don't need wrapping.
Rules 1–6 govern how to write tests. This section governs what to test. A describe block that only covers the happy path is a violation — flag it the same way you'd flag missing AAA structure.
For every function or unit, systematically consider:
Use this as a mental checklist before declaring a describe block complete.
When to use: whenever a function accepts a numeric range, string length, or date range.
Test at the edges of valid ranges: just below the minimum, the minimum itself, just above the minimum, just below the maximum, the maximum, and just above the maximum.
Bad:
describe('calculateDiscount', () => {
test('applies 10% discount for order of 50', () => {
expect(calculateDiscount(50)).toBe(5);
});
});
Good:
describe('calculateDiscount', () => {
test('no discount below minimum order of 10', () => {
expect(calculateDiscount(9)).toBe(0);
});
test('discount applies at minimum order of 10', () => {
expect(calculateDiscount(10)).toBe(1);
});
test('discount applies within valid range', () => {
expect(calculateDiscount(50)).toBe(5);
});
test('discount applies at maximum order of 100', () => {
expect(calculateDiscount(100)).toBe(10);
});
test('no discount above maximum order of 100', () => {
expect(calculateDiscount(101)).toBe(0);
});
});
When to use: whenever inputs can be grouped into classes that should behave identically — test one representative per class rather than every possible value.
Bad:
describe('getStatusLabel', () => {
test('returns label for active', () => {
expect(getStatusLabel('active')).toBe('Active');
});
test('returns label for inactive', () => {
expect(getStatusLabel('inactive')).toBe('Inactive');
});
});
Good:
describe('getStatusLabel', () => {
// Valid class — known statuses
test('returns human label for active status', () => {
expect(getStatusLabel('active')).toBe('Active');
});
test('returns human label for inactive status', () => {
expect(getStatusLabel('inactive')).toBe('Inactive');
});
// Edge class — unknown/unsupported value
test('returns fallback for unrecognised status', () => {
expect(getStatusLabel('archived')).toBe('Unknown');
});
});
When to use: whenever business logic has two or more interacting conditions — enumerate all combinations, one test per row.
Bad:
describe('canPublish', () => {
test('allows admin to publish', () => {
expect(canPublish({ role: 'admin', verified: true })).toBe(true);
});
});
Good:
describe('canPublish', () => {
// role=admin, verified=true → allowed
test('admin with verified account can publish', () => {
expect(canPublish({ role: 'admin', verified: true })).toBe(true);
});
// role=admin, verified=false → denied
test('admin without verified account cannot publish', () => {
expect(canPublish({ role: 'admin', verified: false })).toBe(false);
});
// role=editor, verified=true → denied
test('editor with verified account cannot publish', () => {
expect(canPublish({ role: 'editor', verified: true })).toBe(false);
});
// role=editor, verified=false → denied
test('editor without verified account cannot publish', () => {
expect(canPublish({ role: 'editor', verified: false })).toBe(false);
});
});
When to use: whenever the unit under test models a workflow or lifecycle with distinct states.
Cover all valid transitions and at least one invalid transition per state.
Bad:
describe('order lifecycle', () => {
test('approved order has correct status', async () => {
const order = await createApprovedOrder();
expect(order.status).toBe('approved');
});
});
Good:
describe('order lifecycle', () => {
test('draft order can be submitted', async () => {
// Arrange
const { orderId } = await createDraftOrder();
// Act
await submitOrder(orderId);
// Assert
await expectOrderStatus(orderId, 'submitted');
});
test('submitted order can be approved', async () => {
// Arrange
const { orderId } = await createSubmittedOrder();
// Act
await approveOrder(orderId);
// Assert
await expectOrderStatus(orderId, 'approved');
});
test('draft order cannot be directly approved', async () => {
// Arrange
const { orderId } = await createDraftOrder();
// Act & Assert
await expectOrderTransitionToThrow(orderId, 'approved');
});
});
createTest*() calls in the test bodydata, result, res, item when domain terms existdescribe block with only success-case tests; no error, boundary, or invalid-input teststesting
Stages and commits git changes using semantic commits with logical grouping. Use when the user asks to "commit my changes", "commit these changes", "commit staged files", "commit unstaged files", "commit specific files", "make a commit", "create commits", or /commit. Defaults to staged-only scope. Warns if unstaged changes exist. Groups related changes into separate semantic commits with meaningful titles and body descriptions. Supports three scopes: staged (default), unstaged, or specific files/paths provided by the user.
development
Build, audit, and improve Claude Code skills through guided interview and generation. Use when user says "create a skill", "build a skill", "make a new skill", "write a skill", "improve this skill", "audit this skill", "review my skill", "skill from scratch", "turn this into a skill", or /skill-builder. Produces guide-compliant skill directories with SKILL.md, references, and scripts.
testing
Reviews implementation plans and ideas through a tribunal of AI personas (Skeptic, Critic, Advocate) that analyze in parallel, then a jury synthesizes findings and grills the user with pointed questions across iterative rounds. Use when asked to "review my plan", "review my idea", "challenge my design", "stress test this approach", "get feedback on my plan", or /review-idea. Produces a verdict report with strengths, weaknesses, risks, and action items.
development
Deep research on an idea before writing a PRD. Explores existing solutions via web search, analyzes competitors, assesses technical feasibility, and optionally explores the codebase. Use when asked to "research this idea", "investigate this feature", "what already exists for X", "explore solutions for X", "do research before the PRD", "look into X", or /research. Outputs a structured research document at docs/research/{name}.md. Suggests running /prd-designer as the next step. Dependencies: WebSearch (optional).