vinta-ai-workflows 0.2.0 → 0.4.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/CHANGELOG.md +167 -0
- package/README.md +23 -0
- package/package.json +1 -1
- package/schemas/vinta-ai-workflows-config.v1.schema.json +50 -1
- package/skills/vinta-analyze-codebase/SKILL.md +15 -1
- package/skills/vinta-bootstrap-ai-tools/SKILL.md +41 -8
- package/skills/vinta-derive-skills/SKILL.md +44 -3
- package/skills/vinta-derive-skills/resources/foundation-skills/prepare-worktree/SKILL.md +84 -12
- package/skills/vinta-derive-skills/resources/foundation-skills/prepare-worktree/scripts/gen-compose-worktree-override.sh +211 -0
- package/skills/vinta-derive-skills/resources/write-unit-test-packs/runners/jest.md +49 -0
- package/skills/vinta-derive-skills/resources/write-unit-test-packs/runners/pytest.md +36 -0
- package/skills/vinta-derive-skills/resources/write-unit-test-packs/runners/vitest.md +43 -0
- package/skills/vinta-derive-skills/resources/write-unit-test-packs/stacks/django.md +36 -0
- package/skills/vinta-derive-skills/resources/write-unit-test-packs/stacks/fastapi.md +34 -0
- package/skills/vinta-derive-skills/resources/write-unit-test-packs/stacks/flask.md +31 -0
- package/skills/vinta-derive-skills/resources/write-unit-test-packs/stacks/medplum-test.globalSetup.ts +54 -0
- package/skills/vinta-derive-skills/resources/write-unit-test-packs/stacks/medplum-test.setup.ts +39 -0
- package/skills/vinta-derive-skills/resources/write-unit-test-packs/stacks/medplum.md +47 -0
- package/skills/vinta-derive-skills/resources/write-unit-test-packs/stacks/nextjs.md +27 -0
- package/skills/vinta-derive-skills/resources/write-unit-test-packs/stacks/prisma.md +31 -0
- package/skills/vinta-derive-skills/resources/write-unit-test-packs/stacks/python-package.md +28 -0
- package/skills/vinta-derive-skills/resources/write-unit-test-packs/stacks/react-router.md +25 -0
- package/skills/vinta-derive-skills/resources/write-unit-test-packs/stacks/react.md +29 -0
- package/skills/vinta-derive-skills/resources/write-unit-test-packs/stacks/tanstack-start.md +32 -0
- package/skills/vinta-derive-skills/resources/write-unit-test-packs/stacks/typescript-package.md +32 -0
- package/skills/vinta-derive-skills/resources/write-unit-test-template.md +44 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Flask stack pack
|
|
2
|
+
|
|
3
|
+
Flask-specific testing — the test client, app/request context, DB isolation. Loaded **only when the project is Flask**, alongside the runner pack (usually pytest). Read with the skill's universal rules.
|
|
4
|
+
|
|
5
|
+
## App, client & context
|
|
6
|
+
|
|
7
|
+
- Build the app from an **app factory** with a test config (`create_app({"TESTING": True, ...})`) so each test gets a clean instance; expose it as a fixture.
|
|
8
|
+
- **`app.test_client()`** exercises the real routing/blueprint/error-handler stack — drive endpoints through it, don't call view functions directly.
|
|
9
|
+
- Code that touches `current_app` / `g` / `session` needs an active context — use `with app.app_context():` / `with app.test_request_context():` in the fixture. Don't fake the context.
|
|
10
|
+
|
|
11
|
+
## DB isolation
|
|
12
|
+
|
|
13
|
+
- **SQLAlchemy:** bind the session to a connection-level transaction rolled back in teardown (connection → `begin()` → scoped session → `rollback` + `close`). Each test starts clean; no manual delete.
|
|
14
|
+
- Prefer a real test DB over mocking the ORM.
|
|
15
|
+
|
|
16
|
+
## Externals
|
|
17
|
+
|
|
18
|
+
- **Auth:** log in through the client (post to the login route or set the session via `client.session_transaction()`), not by patching the auth check.
|
|
19
|
+
- **HTTP:** `responses` / `respx` — never a real outbound call.
|
|
20
|
+
- **Extensions (mail, tasks):** use the extension's test/suppress mode (`mail.record_messages()`, eager Celery) and assert on what it captured, rather than mocking send.
|
|
21
|
+
- **Time/randomness:** `freezegun` or an injected clock; seed `random`.
|
|
22
|
+
|
|
23
|
+
## Assertions
|
|
24
|
+
|
|
25
|
+
- Assert `response.status_code` **and** the body (`response.get_json()` / rendered content) by full value. For JSON APIs assert the whole payload, not one key.
|
|
26
|
+
|
|
27
|
+
## Pitfalls
|
|
28
|
+
|
|
29
|
+
- Working outside an app/request context and getting `RuntimeError: working outside of ...` — push the right context in the fixture.
|
|
30
|
+
- Reusing one app instance with mutable global state across tests — the factory per test avoids it.
|
|
31
|
+
- Asserting only the status code while the body is wrong.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
// Bundled verbatim from vintasoftware/medplum-snippet-catalog → TestSetup/test.globalSetup.ts
|
|
3
|
+
// Source: https://github.com/vintasoftware/medplum-snippet-catalog/tree/main/TestSetup
|
|
4
|
+
// Retrieved: 2026-07-31. The catalog is canonical — if it has advanced past this copy, prefer it.
|
|
5
|
+
// Install as (e.g.) `src/test.globalSetup.ts` and register in Vitest config `test.globalSetup`.
|
|
6
|
+
|
|
7
|
+
// Assert that the versions of @medplum/core, @medplum/definitions, @medplum/mock and
|
|
8
|
+
// @medplum/fhirtypes match.
|
|
9
|
+
import type { IndexedStructureDefinition } from '@medplum/core';
|
|
10
|
+
import type { Bundle, SearchParameter } from '@medplum/fhirtypes';
|
|
11
|
+
import type { TestProject } from 'vitest/node';
|
|
12
|
+
|
|
13
|
+
export default async function setup(project: TestProject): Promise<void> {
|
|
14
|
+
// @medplum/core v5 references WebSocket at module-load time. This file runs in the Vitest
|
|
15
|
+
// main process, where WebSocket is only guaranteed as a global on Node >= 22.4. It has to be
|
|
16
|
+
// polyfilled *before* @medplum/core is loaded, which is why the imports below are dynamic and
|
|
17
|
+
// only the (erased) type imports sit at the top of the file.
|
|
18
|
+
if (typeof globalThis.WebSocket === 'undefined') {
|
|
19
|
+
try {
|
|
20
|
+
const ws = await import('ws');
|
|
21
|
+
(globalThis as Record<string, unknown>).WebSocket = ws.default ?? ws.WebSocket;
|
|
22
|
+
} catch {
|
|
23
|
+
throw new Error(
|
|
24
|
+
'@medplum/core needs a global WebSocket. Upgrade to Node >= 22.4, or add "ws" as a dev dependency.'
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const { globalSchema, indexSearchParameterBundle, indexStructureDefinitionBundle } = await import('@medplum/core');
|
|
30
|
+
const { readJson, SEARCH_PARAMETER_BUNDLE_FILES } = await import('@medplum/definitions');
|
|
31
|
+
|
|
32
|
+
// Initialize FHIR
|
|
33
|
+
indexStructureDefinitionBundle(readJson('fhir/r4/profiles-types.json') as Bundle);
|
|
34
|
+
indexStructureDefinitionBundle(readJson('fhir/r4/profiles-resources.json') as Bundle);
|
|
35
|
+
indexStructureDefinitionBundle(readJson('fhir/r4/profiles-medplum.json') as Bundle);
|
|
36
|
+
for (const filename of SEARCH_PARAMETER_BUNDLE_FILES) {
|
|
37
|
+
indexSearchParameterBundle(readJson(filename) as Bundle<SearchParameter>);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// `globalSchema.types` is plain JSON-serializable data, so it can be handed to every test
|
|
41
|
+
// worker instead of each one re-reading and re-indexing the search parameter bundles.
|
|
42
|
+
// See test.setup.ts for the consuming side.
|
|
43
|
+
project.provide('globalSchemaTypes', globalSchema.types);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function teardown(): void {
|
|
47
|
+
// Nothing to do.
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
declare module 'vitest' {
|
|
51
|
+
export interface ProvidedContext {
|
|
52
|
+
globalSchemaTypes: IndexedStructureDefinition['types'];
|
|
53
|
+
}
|
|
54
|
+
}
|
package/skills/vinta-derive-skills/resources/write-unit-test-packs/stacks/medplum-test.setup.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
// Bundled verbatim from vintasoftware/medplum-snippet-catalog → TestSetup/test.setup.ts
|
|
3
|
+
// Source: https://github.com/vintasoftware/medplum-snippet-catalog/tree/main/TestSetup
|
|
4
|
+
// Retrieved: 2026-07-31. The catalog is canonical — if it has advanced past this copy, prefer it.
|
|
5
|
+
// Install as (e.g.) `src/test.setup.ts` and register in Vitest config `test.setupFiles`.
|
|
6
|
+
// Requires its companion `test.globalSetup.ts` in `test.globalSetup` (it provides globalSchemaTypes).
|
|
7
|
+
|
|
8
|
+
// Assert that the versions of @medplum/core, @medplum/definitions, @medplum/mock and
|
|
9
|
+
// @medplum/fhirtypes match.
|
|
10
|
+
import { globalSchema } from '@medplum/core';
|
|
11
|
+
import { inject } from 'vitest';
|
|
12
|
+
|
|
13
|
+
// This runs at module scope on purpose, not inside beforeAll. Vitest runs setup files once per
|
|
14
|
+
// test file, but a test file's own module body is evaluated *before* any beforeAll hook
|
|
15
|
+
// registered here. Fixtures built at module scope would otherwise search against an empty
|
|
16
|
+
// schema and silently match nothing:
|
|
17
|
+
//
|
|
18
|
+
// const medplum = new MockClient();
|
|
19
|
+
// await medplum.createResource({ resourceType: 'Patient', ... });
|
|
20
|
+
// const preloaded = await medplum.searchResources('Patient', 'family=Xu'); // 0 results
|
|
21
|
+
const globalSchemaTypes = inject('globalSchemaTypes');
|
|
22
|
+
|
|
23
|
+
if (!globalSchemaTypes) {
|
|
24
|
+
throw new Error(
|
|
25
|
+
'globalSchemaTypes was not provided. Add test.globalSetup.ts to `globalSetup` in your Vitest config.'
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Search parameters were indexed once by test.globalSetup.ts. `globalSchema.types` holds
|
|
30
|
+
// nothing but plain data, so it survives the trip from the main process to this worker.
|
|
31
|
+
// This is what MockClient's search matching reads, and it is the whole reason this file
|
|
32
|
+
// exists: without it, filters like `family=` or `birthdate=` silently match nothing.
|
|
33
|
+
globalSchema.types = globalSchemaTypes;
|
|
34
|
+
|
|
35
|
+
// Structure definitions are deliberately *not* indexed here. MockClient indexes them itself
|
|
36
|
+
// on its first write, so doing it up front only moves the same ~35 MB parse earlier.
|
|
37
|
+
// The one exception: calling validateResource (or anything else that reads @medplum/core's
|
|
38
|
+
// internal type store) before any resource has been created throws "Unknown data type".
|
|
39
|
+
// Index them in that individual test if you need it.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Medplum stack pack
|
|
2
|
+
|
|
3
|
+
Medplum-specific testing — the mock client, FHIR resources, bots, React providers. Loaded **only when the project uses Medplum** (`@medplum/core` / `@medplum/react`), alongside the runner pack (Vitest / Jest). Read with the skill's universal rules.
|
|
4
|
+
|
|
5
|
+
## Mock client, never a real server
|
|
6
|
+
|
|
7
|
+
- **`MockClient` from `@medplum/mock`** stands in for `MedplumClient` — it holds an in-memory FHIR store, so `createResource` / `readResource` / `searchResources` run for real against fake data. Never point tests at a real Medplum server; there is no exception.
|
|
8
|
+
- Give each test its own `MockClient` (or reset between tests) so resources one test creates don't leak into the next.
|
|
9
|
+
- Seed only the resources the assertion needs; let the mock default the rest.
|
|
10
|
+
|
|
11
|
+
## MockClient limitations — it is not a full FHIR server
|
|
12
|
+
|
|
13
|
+
`MockClient` is an in-memory approximation. A green MockClient test does not prove the real server behaves the same — know the gaps:
|
|
14
|
+
|
|
15
|
+
- **It comes pre-seeded.** MockClient ships with example resources already created (Patients, Practitioners, and others). An **unfiltered** `searchResources('Patient')` returns those *plus* what your test created — so **never assert on unfiltered search counts**. Filter to what your test made (a unique `identifier` / tag), or assert the specific resource by id/fields.
|
|
16
|
+
- **Search support is partial.** Even with the FHIR index wired up (below), not every search parameter, modifier, chained search, `_include` / `_revinclude`, or `_filter` matches server semantics. Prefer filtering on simple, well-supported params in tests; if a filter behaves oddly, check server semantics before assuming your code is wrong.
|
|
17
|
+
- **Server-only behavior isn't enforced.** Access policies / auth scoping, subscriptions, and some operations (`$everything`, GraphQL, batch/transaction edge cases) plus bot-execution nuances are approximated or absent. Anything whose contract depends on the server enforcing it belongs in an integration/e2e test, not a MockClient unit test.
|
|
18
|
+
- **Consult the team's MockClient extensions + internal Medplum testing docs** for the current maintained limitation list and any local wrappers that patch gaps — they track the Medplum version. Canonical setup + helpers live in the `medplum-snippet-catalog`.
|
|
19
|
+
|
|
20
|
+
## Search filtering — indexed once at setup, never per test
|
|
21
|
+
|
|
22
|
+
- **`MockClient` search filters only work if the FHIR search parameters are indexed in the test process.** Symptom: a resource comes back from an unfiltered `searchResources`, but `searchResources` / `searchOne` **with** a filter (`family=`, `birthdate=`, …) silently returns nothing. That means the index is missing, not that your data is wrong.
|
|
23
|
+
- This is wired **once** at bootstrap via two Vitest setup files: `test.globalSetup.ts` (indexes the bundles once in the main process, shares `globalSchema.types` to workers) registered under `test.globalSetup`, and `test.setup.ts` (assigns the shared index at module scope) registered under `test.setupFiles`. **Do not add `indexStructureDefinitionBundle` / `indexSearchParameterBundle` inside a test or a per-test `beforeAll`** — rely on the project-level setup. If filters mysteriously fail, check both files are registered in `vitest.config.*`, not that each test indexes.
|
|
24
|
+
- One deliberate exception: `validateResource` (or anything reading @medplum/core's type store) **before any resource is created** throws `Unknown data type` — index structure definitions in that one test if you hit it.
|
|
25
|
+
- Version alignment matters: `@medplum/core`, `@medplum/definitions`, `@medplum/mock`, and `@medplum/fhirtypes` must be on **matching versions** — a skew causes confusing index/search failures.
|
|
26
|
+
|
|
27
|
+
## FHIR resource assertions
|
|
28
|
+
|
|
29
|
+
- Assert on the **resource fields you care about by value** — `patient.name[0].family`, `observation.valueQuantity.value` — not just `resource.id` truthiness or a search count. A wrong resource with an id still passes a truthiness check.
|
|
30
|
+
- When asserting a search result, assert the matched resources' contents, not only `bundle.entry.length`.
|
|
31
|
+
- Build resources as typed `@medplum/fhirtypes` objects (or a small factory helper), not loosely-typed literals copied across tests.
|
|
32
|
+
|
|
33
|
+
## Bots
|
|
34
|
+
|
|
35
|
+
- A bot's `handler(medplum, event)` is a plain function — unit-test it by calling it with a `MockClient` and a constructed `BotEvent` input, then assert the resources it wrote (read them back from the mock) and the value it returned.
|
|
36
|
+
- Mock outbound integrations the bot calls (external HTTP, `sendEmail`) at the boundary; don't mock the `medplum` client — use `MockClient`.
|
|
37
|
+
|
|
38
|
+
## React components (with `@medplum/react`)
|
|
39
|
+
|
|
40
|
+
- Wrap rendered components in **`MedplumProvider` with a `MockClient`** (Testing Library). Query by role/text; assert rendered output. See the runner pack's Testing Library notes.
|
|
41
|
+
- Let components fetch through the `MockClient`; use `await screen.findBy...` for the async load rather than mocking the fetch.
|
|
42
|
+
|
|
43
|
+
## Pitfalls
|
|
44
|
+
|
|
45
|
+
- Sharing one `MockClient` across tests without reset — resources bleed between tests.
|
|
46
|
+
- Asserting `resource.id` is defined instead of the fields the code set.
|
|
47
|
+
- Mocking `MedplumClient` methods by hand instead of using `MockClient` — you re-implement the store and test the mock.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Next.js stack pack
|
|
2
|
+
|
|
3
|
+
Next.js (App Router) testing — what to unit-test vs leave to e2e, route handlers, server code. Loaded **only when the project uses Next.js**, alongside the runner pack (Vitest / Jest) and, for UI, the React stack pack. Read with the skill's universal rules.
|
|
4
|
+
|
|
5
|
+
## Test the logic, not the framework
|
|
6
|
+
|
|
7
|
+
- **Unit-test the units the framework calls**, not the framework's rendering: route-handler functions, server actions, data-loading helpers, and pure client components. Full-page rendering, navigation, streaming, and middleware are e2e territory (Playwright) — don't fake the Next runtime to force them into a unit test.
|
|
8
|
+
|
|
9
|
+
## Route handlers & server actions
|
|
10
|
+
|
|
11
|
+
- A **Route Handler** (`GET`/`POST` in `route.ts`) is a plain async function taking a `Request` — call it directly with a constructed `new Request(url, { method, body })` and assert the returned `Response` (`await res.json()` + `res.status`) by full value. No server boot needed.
|
|
12
|
+
- A **Server Action** is a plain async function — call it with its args and assert what it returned / wrote (mock the DB/service boundary, not the action). Don't assert on `revalidatePath`/`redirect` internals; assert the effect.
|
|
13
|
+
- Inject or mock external services (DB, upstream APIs) at the boundary with MSW / a fake client, not by faking `next/*` internals.
|
|
14
|
+
|
|
15
|
+
## Client components & hooks
|
|
16
|
+
|
|
17
|
+
- Test client components with the React stack pack (Testing Library). Mock `next/navigation` (`useRouter`, `useSearchParams`, `usePathname`) with light fakes when a component reads them — assert the visible result, not that `router.push` was called, unless navigation *is* the contract.
|
|
18
|
+
|
|
19
|
+
## Server components
|
|
20
|
+
|
|
21
|
+
- Async Server Components that just fetch + compose are thin — extract the data logic into a plain function and unit-test that. Rendering the RSC tree itself is not well-served by unit tests today; cover it with e2e.
|
|
22
|
+
|
|
23
|
+
## Pitfalls
|
|
24
|
+
|
|
25
|
+
- Trying to render a whole App-Router page in a unit test and mocking half of Next to do it — brittle; use e2e.
|
|
26
|
+
- Asserting `redirect()` / `revalidatePath()` were called instead of the observable outcome.
|
|
27
|
+
- Leaving real `fetch` calls in a Server Component test — stub at the network layer (MSW) or test the extracted function.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Prisma stack pack
|
|
2
|
+
|
|
3
|
+
Prisma (data layer) testing — DB isolation, real-DB vs mock, seeding. Loaded **only when the project uses Prisma** (`@prisma/client`), alongside the runner pack (Vitest / Jest) and any web-framework stack pack. Read with the skill's universal rules.
|
|
4
|
+
|
|
5
|
+
## Real test DB over a mocked client
|
|
6
|
+
|
|
7
|
+
- **Test queries against a real (SQLite/Postgres) test database**, not a mocked `PrismaClient`. Mocking the client (`jest-mock-extended`, `prisma-mock`) means your `where`/relations/constraints/`select` never run — the test passes on a query that would throw in production. Point `DATABASE_URL` at a disposable test DB and apply migrations in global setup (`prisma migrate deploy` / `db push`).
|
|
8
|
+
- **Mock only at the port, not the client.** If the unit under test is pure business logic that takes a repository/port, inject a fake repo — don't mock `PrismaClient`. If you're testing the query/persistence itself, use the real DB.
|
|
9
|
+
|
|
10
|
+
## DB isolation
|
|
11
|
+
|
|
12
|
+
- **Wrap each test in a transaction rolled back at teardown** — an interactive `$transaction` handle passed to the code, rolled back after (helpers like `@chax-at/transactional-prisma-testing` automate it), or a per-test truncation of the tables touched. Each test starts clean; no manual delete scattered around.
|
|
13
|
+
- **`$disconnect()` in teardown** (global) — leaked connections exhaust the pool and hang the suite.
|
|
14
|
+
- Never depend on test ordering or on rows another test created.
|
|
15
|
+
|
|
16
|
+
## Test data
|
|
17
|
+
|
|
18
|
+
- Seed with small factory helpers that spell out only the fields the assertion needs and let schema defaults fill the rest — not deep nested `create` literals copy-pasted across tests.
|
|
19
|
+
- Reset to a known baseline between tests via the rollback/truncate above, not a shared mutable seed.
|
|
20
|
+
|
|
21
|
+
## Assertions
|
|
22
|
+
|
|
23
|
+
- **Read the row back and assert its fields by value** (`await prisma.user.findUnique(...)` → assert the object), not `count` alone — a count passes for the wrong row. For relations, `include` them and assert their contents.
|
|
24
|
+
- After a write, assert the persisted state from a fresh read, not the in-memory input you passed in.
|
|
25
|
+
|
|
26
|
+
## Pitfalls
|
|
27
|
+
|
|
28
|
+
- Mocking `PrismaClient` then asserting the mock was called — tests the mock, not the query.
|
|
29
|
+
- `expect(await prisma.x.count()).toBe(1)` as the whole assertion — asserts a count, not the right record.
|
|
30
|
+
- No `$disconnect()` / shared client across tests — connection leaks and cross-test bleed.
|
|
31
|
+
- Truncating in `beforeEach` as a cleanup crutch instead of transaction rollback — slower and hides ordering bugs.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Python package (library) stack pack
|
|
2
|
+
|
|
3
|
+
Testing a pure Python library/package — no web framework, no app DB, shipped to consumers (e.g. an open-source package). Loaded **only when the project is a standalone package**, alongside the pytest runner pack. Read with the skill's universal rules.
|
|
4
|
+
|
|
5
|
+
## Test the public API
|
|
6
|
+
|
|
7
|
+
- Test the package's **public surface** — the names in `__init__.py` / documented exports — the way a consumer imports and calls them. Don't reach into private modules (`_internal`) to test them directly; if a private helper needs its own test, that's a hint it should be public or covered through the public path.
|
|
8
|
+
- Backward compatibility is the contract: a test pinning documented behavior is what stops a refactor from breaking downstream users.
|
|
9
|
+
|
|
10
|
+
## Keep it dependency-light & deterministic
|
|
11
|
+
|
|
12
|
+
- A library test should need only the package + `pytest` (+ its declared test extras). Don't pull a DB, a broker, or network into a unit test — if a feature integrates with those, test the seam with a fake/in-memory double the consumer could also use.
|
|
13
|
+
- No hidden global state between tests: reset module-level singletons/caches your code exposes; don't rely on import order.
|
|
14
|
+
|
|
15
|
+
## Data & purity
|
|
16
|
+
|
|
17
|
+
- Most of a library is pure functions — arrange literal inputs, assert literal outputs by full value. Parametrize to cover edge cases (empty, boundary, unicode, `None`).
|
|
18
|
+
- For anything time/random/env-dependent, inject the dependency (clock, rng, `os.environ` reader) so tests pass a fake — libraries especially should not read ambient globals.
|
|
19
|
+
|
|
20
|
+
## Version / matrix awareness
|
|
21
|
+
|
|
22
|
+
- If the package supports multiple Python versions or an optional-dependency matrix (`tox` / `nox`), write tests that don't assume the newest syntax/behavior, and guard optional-dependency paths with `pytest.importorskip` so the suite runs in every configured environment.
|
|
23
|
+
|
|
24
|
+
## Pitfalls
|
|
25
|
+
|
|
26
|
+
- Testing `_private` internals — couples the test to today's structure and breaks on refactor.
|
|
27
|
+
- A unit test that imports a heavy optional dependency unconditionally — fails in the minimal matrix env.
|
|
28
|
+
- Asserting truthiness of a returned object instead of its value/shape.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# React Router stack pack
|
|
2
|
+
|
|
3
|
+
React Router testing — data-router loaders/actions and routing-aware components. Loaded **only when the project uses React Router** (`react-router` / `react-router-dom` / `@react-router/*`), alongside the runner pack and, for UI, the React stack pack. Read with the skill's universal rules.
|
|
4
|
+
|
|
5
|
+
## Loaders & actions are plain functions
|
|
6
|
+
|
|
7
|
+
- In data-router / framework mode, a route's **`loader` and `action` are plain async functions** receiving `{ request, params, context }`. Export and call them directly; assert what they return / throw. A thrown `redirect(...)` is part of the contract — assert its `Location`, don't assert on router internals.
|
|
8
|
+
- Mock the boundary the loader/action hits (DB, upstream API) with a fake client / MSW. Build the `request` with `new Request(url, { method, body })` for actions.
|
|
9
|
+
|
|
10
|
+
## Routing-aware components
|
|
11
|
+
|
|
12
|
+
- Components that call routing hooks (`useNavigate`, `useParams`, `useLoaderData`, `<Link>`) need a router in the test. Use **`createMemoryRouter([...routes], { initialEntries: [...] })` + `<RouterProvider>`** (data router), seeding the route with `loader: () => data` so `useLoaderData` returns what the component reads. For simple hook-only components, wrapping in **`<MemoryRouter initialEntries={[...]}>`** is enough.
|
|
13
|
+
- Assert the **visible result** of navigation (the destination content rendered, the URL via the test router), not that `navigate` was called — unless firing navigation *is* the unit's whole job.
|
|
14
|
+
|
|
15
|
+
## Externals & isolation
|
|
16
|
+
|
|
17
|
+
- Stub HTTP at the network layer with MSW; never a real outbound call.
|
|
18
|
+
- Build a **fresh memory router/history per test** — a shared router leaks navigation/loader state and causes order-dependence.
|
|
19
|
+
|
|
20
|
+
## Pitfalls
|
|
21
|
+
|
|
22
|
+
- Rendering a component that uses `useNavigate` / `<Link>` without a router wrapper → it throws; wrap it in a memory router.
|
|
23
|
+
- Asserting `navigate("/x")` was called instead of asserting the `/x` content rendered.
|
|
24
|
+
- Testing loaders by rendering the whole route tree instead of calling the loader function directly.
|
|
25
|
+
- Reusing one router across tests — stale location/loader data flips results by order.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# React stack pack
|
|
2
|
+
|
|
3
|
+
Component-testing idioms with React Testing Library. Loaded **only when the project renders React UI**, alongside the runner pack (Vitest / Jest). Read with the skill's universal rules.
|
|
4
|
+
|
|
5
|
+
## Query & interact like a user
|
|
6
|
+
|
|
7
|
+
- **Query by role/label/text**, not test-ids or class names: `getByRole("button", { name: /save/i })`. Test-ids couple the test to markup. Reserve `data-testid` for the rare element with no accessible handle.
|
|
8
|
+
- **`getBy`** (must exist) vs **`queryBy`** (assert absence) vs **`findBy`** (async, awaited). Use the right one — `getBy` throwing is a clearer failure than a null deref.
|
|
9
|
+
- Drive interactions with **`user-event`** (`await userEvent.click(...)`), not raw `fireEvent` — it models real focus/typing.
|
|
10
|
+
|
|
11
|
+
## Assert output, not internals
|
|
12
|
+
|
|
13
|
+
- Assert what the user sees (rendered text, presence/absence, disabled state), not component state, not that a hook or handler ran, not render counts. The test should survive a refactor that preserves behavior.
|
|
14
|
+
- Don't reach into instance internals or snapshot the whole tree as the only assertion — assert the elements that matter.
|
|
15
|
+
|
|
16
|
+
## Data & async
|
|
17
|
+
|
|
18
|
+
- Mock HTTP at the network layer with **MSW** (see the runner pack), so components run their real fetch path. For a component that loads data, `await screen.findBy...` the settled UI rather than mocking the fetching hook.
|
|
19
|
+
- Pure logic (reducers, formatters, custom hooks without UI) — test directly, no render. Hooks: `renderHook` + `act`.
|
|
20
|
+
|
|
21
|
+
## Isolation
|
|
22
|
+
|
|
23
|
+
- Clean up is automatic per test with the Testing Library auto-cleanup; don't share rendered state between tests. Reset mocks in `afterEach` (see runner pack).
|
|
24
|
+
|
|
25
|
+
## Pitfalls
|
|
26
|
+
|
|
27
|
+
- `getByTestId` everywhere — brittle, and hides accessibility gaps. Prefer role/label.
|
|
28
|
+
- Asserting a handler prop was called instead of the visible result of calling it.
|
|
29
|
+
- Missing `await` on `findBy` / `userEvent` — the assertion runs before the UI settles and passes hollow.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# TanStack Start stack pack
|
|
2
|
+
|
|
3
|
+
TanStack Start testing — server functions, route loaders, and isomorphic logic. Loaded **only when the project uses TanStack Start** (`@tanstack/react-start`), alongside the runner pack (Vitest) and, for UI, the React stack pack. Read with the skill's universal rules.
|
|
4
|
+
|
|
5
|
+
## Test the logic, not the framework
|
|
6
|
+
|
|
7
|
+
- **Unit-test the units the framework calls**, not the full route/SSR render: server functions, route `loader` / `beforeLoad` functions, and pure components. Full-page rendering, streaming, hydration, and navigation are e2e territory (Playwright) — don't fake the Start runtime to force them into a unit test.
|
|
8
|
+
|
|
9
|
+
## Server functions (`createServerFn`)
|
|
10
|
+
|
|
11
|
+
- A server function's **handler is a plain async function** — export it (or the plain function it wraps) and call it directly with constructed input; assert the returned value by full shape. Don't spin up the server to test it.
|
|
12
|
+
- Mock what the handler calls at the boundary (DB, upstream APIs) with a fake client / MSW, not the `createServerFn` machinery.
|
|
13
|
+
- Validate the input-validator (`.validator(...)`) separately with valid + invalid inputs — it's a pure function.
|
|
14
|
+
|
|
15
|
+
## Route loaders & context
|
|
16
|
+
|
|
17
|
+
- A route's `loader` / `beforeLoad` is a plain async function receiving `{ context, params, ... }` — call it with a constructed argument and assert what it returns / throws (e.g. a `redirect()`), passing a fake `context` (auth principal, injected client). Don't assert on router internals; assert the returned data or the thrown redirect's target.
|
|
18
|
+
|
|
19
|
+
## Client components & router hooks
|
|
20
|
+
|
|
21
|
+
- Test client components with the React stack pack (Testing Library). For components using router hooks (`useLoaderData`, `useNavigate`, `useParams`), render them under a **test router** built with `createRouter` + a `createMemoryHistory({ initialEntries: [...] })` and `RouterProvider`, seeding the route with the data the component reads. Assert the visible result, not that `navigate` was called (unless navigation *is* the contract).
|
|
22
|
+
|
|
23
|
+
## Externals & isolation
|
|
24
|
+
|
|
25
|
+
- Stub HTTP at the network layer with MSW; never a real outbound call in a unit test.
|
|
26
|
+
- Fresh router/history per test — don't share navigation state between tests.
|
|
27
|
+
|
|
28
|
+
## Pitfalls
|
|
29
|
+
|
|
30
|
+
- Booting the Start server or rendering a whole route in a unit test and mocking half the framework — brittle; use e2e.
|
|
31
|
+
- Asserting a `redirect()` / `navigate()` happened instead of the observable outcome (the data, the rendered target).
|
|
32
|
+
- Leaving a real `fetch` in a server-function or loader test — stub at the boundary or test the extracted function.
|
package/skills/vinta-derive-skills/resources/write-unit-test-packs/stacks/typescript-package.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# TypeScript package (library) stack pack
|
|
2
|
+
|
|
3
|
+
Testing a pure TypeScript library/package — no framework, no app DB, shipped to consumers (npm package, workspace lib). Loaded **only when the project is a standalone package**, alongside the runner pack (Vitest / Jest). Read with the skill's universal rules.
|
|
4
|
+
|
|
5
|
+
## Test the public API
|
|
6
|
+
|
|
7
|
+
- Import from the package **entry point** (`exports` / `main` / the package name in a monorepo), the way a consumer does — not deep relative paths into `src/internal`. That's what pins the published contract; a green suite that imports internals can still ship a broken public API.
|
|
8
|
+
- If a private helper needs its own test, consider whether it should be exported, or cover it through the public path.
|
|
9
|
+
|
|
10
|
+
## Types are part of the contract
|
|
11
|
+
|
|
12
|
+
- Type-level behavior (generics, inference, conditional types) won't be caught by value assertions. When it matters, add compile-time type tests with **`tsd`** / `expectTypeOf` (Vitest) / `vitest`'s type-testing — assert that a call *doesn't* compile when it shouldn't and infers the right type when it should.
|
|
13
|
+
|
|
14
|
+
## Keep it dependency-light & deterministic
|
|
15
|
+
|
|
16
|
+
- A library test needs only the package + the runner. Don't pull a DB/network in; test integration seams with an in-memory fake the consumer could also use.
|
|
17
|
+
- Reset any module-level singleton/cache the package exposes between tests; don't rely on import order.
|
|
18
|
+
|
|
19
|
+
## Build outputs (when relevant)
|
|
20
|
+
|
|
21
|
+
- If the package ships both ESM and CJS, a small smoke test that imports the built entry in each format catches packaging/`exports`-map breakage that source-only tests miss.
|
|
22
|
+
|
|
23
|
+
## Data & purity
|
|
24
|
+
|
|
25
|
+
- Most of a library is pure — literal inputs, assert literal outputs with `toEqual` / `toStrictEqual` (use `toStrictEqual` when `undefined` fields / class identity are part of the contract). Table-drive edge cases (empty, boundary, unicode).
|
|
26
|
+
- Inject clock/rng/env for anything ambient so tests pass a fake.
|
|
27
|
+
|
|
28
|
+
## Pitfalls
|
|
29
|
+
|
|
30
|
+
- Testing via deep `src/...` imports — green tests, broken published `exports`.
|
|
31
|
+
- Only value tests on a type-heavy API — the type regressions ship silently. Add type tests.
|
|
32
|
+
- `toEqual` when `undefined`-property presence matters — use `toStrictEqual`.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: write-unit-test
|
|
3
|
+
description: Write a unit test for a function, method, class, or module in {{PROJECT_NAME}} ({{STACK_SUMMARY}}) — mock only genuine externals, assert the full output (not counts/truthiness), clean up created data or roll back the transaction, never hit a service unavailable in dev/test, keep test logic literal, stay decoupled from internals. Applies this project's test preferences and the runner + stack packs for {{FRAMEWORK_PACKS_LIST}}. Use on "add a test", "write a unit test", "cover this with tests", or when new behavior lands uncovered.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Write a unit test
|
|
7
|
+
|
|
8
|
+
A test should fail when the behavior breaks and pass when it's correct — nothing more.
|
|
9
|
+
|
|
10
|
+
## Steps
|
|
11
|
+
|
|
12
|
+
1. **Read the unit's contract** — inputs, outputs, side effects, error paths. Test the contract, not the implementation.
|
|
13
|
+
2. **Match the nearest existing tests** — location, naming, helpers — unless they break a rule below.
|
|
14
|
+
3. **Load the packs** that shipped for this project — the test-**runner** pack (how to structure/assert/mock) plus the **stack** pack(s) matching what you're testing (DB isolation, framework client, domain mocks). Follow them alongside the rules:
|
|
15
|
+
|
|
16
|
+
{{FRAMEWORK_PACK_LOADER_BLOCK}}
|
|
17
|
+
|
|
18
|
+
4. **Write it, then prove it.** Run in isolation (`{{NEW_TEST_CMD_PATTERN}}`) — green. Break the code (or invert an assertion), re-run — red for the right reason. Revert. Run the suite (`{{TEST_CMD}}`){{SCOPED_TEST_NOTE}} — still green.
|
|
19
|
+
|
|
20
|
+
## Rules
|
|
21
|
+
|
|
22
|
+
1. **Mock only genuine externals** (third-party HTTP, payment/email/SMS, clock, randomness). Never mock the unit under test, its local collaborators, the DB, or your own pure functions.
|
|
23
|
+
2. **Assert the full value**, not counts or truthiness. `assert result`, `toBeTruthy()`, `len(x) == 3` are too weak. Assert contents and order for collections. **But don't over-fit brittle text** — for human-facing strings (error messages, UI copy, i18n) assert a stable anchor (an error code/type, a role, an i18n key, a substring), not the exact prose that churns.
|
|
24
|
+
3. **Leave no trace** — transaction-rollback harness where available; else clean up in teardown, and make teardown run on failure. Never depend on another test's data.
|
|
25
|
+
4. **No external service** that can't run in dev/test. Stub at the boundary.
|
|
26
|
+
5. **Test logic stays literal** — no loops/conditionals computing the expected value. Parametrize to cover cases; keep each expectation a literal.
|
|
27
|
+
6. **Decouple from internals** — assert observable state/output, not private methods or call counts. The test survives a behavior-preserving rewrite.
|
|
28
|
+
7. **One behavior per test**, named for what it pins (`returns_zero_for_empty_cart`).
|
|
29
|
+
8. **Kill nondeterminism** — control every entropy source (clock, randomness, timezone/locale, iteration/DB order, concurrency). Freeze/inject them; a test that passes only sometimes is worse than none.
|
|
30
|
+
9. **Cover the branches, not just the happy path** — aim high (95%+ branch coverage): each error path, guard, and edge (empty, boundary, null) gets a case. Coverage is the floor, correct assertions are the point.
|
|
31
|
+
|
|
32
|
+
## Project preferences
|
|
33
|
+
|
|
34
|
+
Apply these; `framework-default` defers to the pack. If the existing suite contradicts a preference, follow the preference for new tests and flag it — don't rewrite old tests.
|
|
35
|
+
|
|
36
|
+
{{PROJECT_TEST_PREFERENCES_BLOCK}}
|
|
37
|
+
{{ADDITIONAL_CONVENTIONS_BLOCK}}
|
|
38
|
+
|
|
39
|
+
## Pitfalls
|
|
40
|
+
|
|
41
|
+
- Asserting on a mock you set up — tests the mock, not the code.
|
|
42
|
+
- Giant snapshots as a substitute for asserting the fields that matter.
|
|
43
|
+
- `delete all rows` in setup instead of rollback — masks leaked state, races other tests.
|
|
44
|
+
- Testing the framework/ORM/language instead of your behavior on top of it.
|