@jterrazz/test 16.1.1 → 17.0.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 +45 -171
- package/dist/browser/index.js +2 -2980
- package/dist/browser/services.js +2 -0
- package/dist/browser/stubs.js +2 -0
- package/dist/checker.d.ts +6 -1
- package/dist/checker.js +4534 -2628
- package/dist/config.d.ts +40 -0
- package/dist/config.js +309 -0
- package/dist/in-place.global-setup.d.ts +11 -0
- package/dist/in-place.global-setup.js +21 -0
- package/dist/index.d.ts +1653 -3004
- package/dist/index.js +5226 -3845
- package/dist/mobile-session.global-setup.d.ts +14 -0
- package/dist/mobile-session.global-setup.js +31 -0
- package/dist/oxlint.cjs +6069 -5634
- package/dist/oxlint.d.cts +43 -7212
- package/dist/oxlint.d.ts +43 -7212
- package/dist/oxlint.js +6127 -5693
- package/dist/route-coverage.global-setup.d.ts +42 -0
- package/dist/route-coverage.global-setup.js +2 -0
- package/dist/services.d.ts +3 -0
- package/dist/services.global-setup.d.ts +43 -0
- package/dist/services.global-setup.js +2 -0
- package/dist/services.js +597 -0
- package/dist/shared-appium.adapter.js +428 -0
- package/dist/shared-artifacts.js +32 -0
- package/dist/shared-chapter.js +7 -0
- package/dist/shared-container.port.d.ts +31 -0
- package/dist/shared-derived-projects.js +58 -0
- package/dist/shared-device.port.d.ts +189 -0
- package/dist/shared-exchange.js +447 -0
- package/dist/shared-in-place.js +172 -0
- package/dist/shared-index.browser.js +3191 -0
- package/dist/shared-index.services.d.ts +346 -0
- package/dist/shared-index.stubs.d.ts +287 -0
- package/dist/shared-json-shape.js +17 -0
- package/dist/shared-landmarks.js +71 -0
- package/dist/shared-ledger.js +68 -0
- package/dist/shared-literate-plugin.js +183 -0
- package/dist/shared-manifest.js +1321 -0
- package/dist/shared-pause.js +11 -0
- package/dist/shared-peer.js +88 -0
- package/dist/shared-playwright.adapter.js +330 -0
- package/dist/shared-process.adapter.js +74 -0
- package/dist/shared-refusal.js +35 -0
- package/dist/shared-resolve.js +24 -0
- package/dist/shared-route-coverage.global-setup.js +187 -0
- package/dist/shared-scenario-failure.js +198 -0
- package/dist/shared-serve.js +365 -0
- package/dist/shared-service-images.js +10 -0
- package/dist/shared-services.global-setup.js +179 -0
- package/dist/shared-session-ledger.js +301 -0
- package/dist/shared-setup-entry.js +12 -0
- package/dist/shared-source-scan.js +133 -0
- package/dist/shared-spec-document.js +365 -0
- package/dist/shared-specifications.js +26 -0
- package/dist/shared-stream-body.js +130 -0
- package/dist/shared-stub-server.js +190 -0
- package/dist/shared-temp-root.global-setup.js +80 -0
- package/dist/shared-test-baseline.js +26 -0
- package/dist/shared-test-section.js +58 -0
- package/dist/shared-testcontainers.adapter.js +203 -0
- package/dist/shared-tokens.js +55 -0
- package/dist/shared-yaml-document.js +119 -0
- package/dist/stubs.d.ts +2 -0
- package/dist/stubs.js +657 -0
- package/dist/temp-root.global-setup.d.ts +7 -0
- package/dist/temp-root.global-setup.js +2 -0
- package/dist/vitest.d.ts +97 -163
- package/dist/vitest.js +373 -525
- package/docs/README.md +82 -36
- package/docs/concepts/architecture.md +118 -0
- package/docs/concepts/conventions.md +333 -0
- package/docs/guides/api-specs.md +374 -0
- package/docs/guides/cli-specs.md +564 -0
- package/docs/guides/component-specs.md +252 -0
- package/docs/guides/develop.md +94 -0
- package/docs/guides/integration-specs.md +181 -0
- package/docs/guides/job-specs.md +180 -0
- package/docs/guides/lint-specs.md +174 -0
- package/docs/guides/mobile-specs.md +319 -0
- package/docs/guides/module-tests.md +149 -0
- package/docs/guides/release.md +43 -0
- package/docs/guides/spec-documents.md +189 -0
- package/docs/guides/website-specs.md +413 -0
- package/docs/reference/assertions.md +345 -0
- package/docs/reference/glossary.md +20 -0
- package/docs/reference/linting.md +483 -0
- package/docs/reference/services.md +236 -0
- package/docs/reference/stubs.md +498 -0
- package/docs/reference/time-and-doubles.md +137 -0
- package/docs/reference/tokens.md +258 -0
- package/docs/reference/visitor.md +221 -0
- package/docs/start/getting-started.md +187 -0
- package/docs/start/set-up-a-project.md +214 -0
- package/package.json +50 -30
- package/schema/spec.schema.json +43 -17
- package/dist/appium.adapter.js +0 -489
- package/dist/artifacts.js +0 -32
- package/dist/fixtures.js +0 -110
- package/dist/literate.js +0 -1747
- package/dist/match.js +0 -804
- package/dist/playwright.adapter.js +0 -438
- package/dist/server.js +0 -160
- package/dist/stream.js +0 -321
- package/dist/substring-warning.js +0 -282
- package/docs/01-architecture.md +0 -95
- package/docs/02-developing.md +0 -351
- package/docs/03-testing.md +0 -495
- package/docs/04-operating.md +0 -101
- package/docs/05-module-tests.md +0 -152
- package/docs/06-integration.md +0 -180
- package/docs/07-component.md +0 -246
- package/docs/08-website.md +0 -404
- package/docs/09-mobile.md +0 -270
- package/docs/10-api.md +0 -264
- package/docs/11-jobs.md +0 -157
- package/docs/12-cli.md +0 -677
- package/docs/13-elements.md +0 -248
- package/docs/14-assertions.md +0 -314
- package/docs/15-tokens.md +0 -253
- package/docs/16-contracts.md +0 -438
- package/docs/17-services.md +0 -188
- package/docs/18-conventions.md +0 -332
- package/docs/19-linting.md +0 -512
- package/docs/decisions/001-v9-conventions-on-three-channels.md +0 -50
- package/docs/decisions/002-the-statique-channel-holds-only-what-upstream-does-not.md +0 -49
- package/docs/decisions/003-a-rendered-component-is-a-facet-on-vitest-browser-mode.md +0 -84
- package/docs/decisions/004-a-module-against-real-services-or-a-golden-is-the-integration-facet.md +0 -81
- package/docs/decisions/005-the-doubles-ladder-is-closed.md +0 -86
- package/docs/decisions/006-the-catalogue-reaches-a-file-by-its-role.md +0 -245
- package/docs/decisions/007-compose-mode-leaves-the-package.md +0 -53
- package/docs/decisions/008-vitest-5-node-24-and-the-native-seams-as-optional-peers.md +0 -75
- package/docs/decisions/_template.md +0 -22
- package/docs/reference/classes/Matcher.md +0 -120
- package/docs/reference/classes/ProcessHandle.md +0 -368
- package/docs/reference/functions/defineContract.md +0 -39
- package/docs/reference/functions/defineContracts.md +0 -36
- package/docs/reference/functions/disabled.md +0 -28
- package/docs/reference/functions/enabled.md +0 -20
- package/docs/reference/functions/focused.md +0 -26
- package/docs/reference/functions/mockOf.md +0 -79
- package/docs/reference/functions/postgres.md +0 -31
- package/docs/reference/functions/process.md +0 -33
- package/docs/reference/functions/redis.md +0 -28
- package/docs/reference/functions/required.md +0 -41
- package/docs/reference/functions/selected.md +0 -26
- package/docs/reference/functions/sqlite.md +0 -33
- package/docs/reference/functions/testId.md +0 -20
- package/docs/reference/functions/text.md +0 -40
- package/docs/reference/functions/valued.md +0 -28
- package/docs/reference/functions/waitUntil.md +0 -46
- package/docs/reference/functions/within.md +0 -29
- package/docs/reference/index.md +0 -196
- package/docs/reference/interfaces/BaseResult.md +0 -87
- package/docs/reference/interfaces/CallResult.md +0 -160
- package/docs/reference/interfaces/CaptureScope.md +0 -82
- package/docs/reference/interfaces/CliResult.md +0 -253
- package/docs/reference/interfaces/ContainerAccessor.md +0 -166
- package/docs/reference/interfaces/DirectoryAccessor.md +0 -33
- package/docs/reference/interfaces/FetchResult.md +0 -180
- package/docs/reference/interfaces/FilesystemAccessor.md +0 -46
- package/docs/reference/interfaces/HttpResult.md +0 -126
- package/docs/reference/interfaces/JsonAccessor.md +0 -46
- package/docs/reference/interfaces/PageResult.md +0 -356
- package/docs/reference/interfaces/RenderResult.md +0 -72
- package/docs/reference/interfaces/RenderedText.md +0 -110
- package/docs/reference/interfaces/ResponseAccessor.md +0 -47
- package/docs/reference/interfaces/ScreenResult.md +0 -128
- package/docs/reference/interfaces/TableAccessor.md +0 -26
- package/docs/reference/interfaces/TextAccessor.md +0 -96
- package/docs/reference/type-aliases/ApiHandle.md +0 -68
- package/docs/reference/type-aliases/ApiSpecification.md +0 -213
- package/docs/reference/type-aliases/ApiSpecificationOptions.md +0 -68
- package/docs/reference/type-aliases/BrowserConsoleMessage.md +0 -30
- package/docs/reference/type-aliases/BrowserLinkElement.md +0 -50
- package/docs/reference/type-aliases/BrowserMetaElement.md +0 -40
- package/docs/reference/type-aliases/BrowserOpenOptions.md +0 -87
- package/docs/reference/type-aliases/BrowserPage.md +0 -132
- package/docs/reference/type-aliases/BrowserPort.md +0 -51
- package/docs/reference/type-aliases/CliEnv.md +0 -11
- package/docs/reference/type-aliases/CliHandle.md +0 -80
- package/docs/reference/type-aliases/CliInput.md +0 -36
- package/docs/reference/type-aliases/CliOutput.md +0 -46
- package/docs/reference/type-aliases/CliPort.md +0 -58
- package/docs/reference/type-aliases/CliSpecification.md +0 -144
- package/docs/reference/type-aliases/CliSpecificationOptions.md +0 -134
- package/docs/reference/type-aliases/ComponentChain.md +0 -156
- package/docs/reference/type-aliases/ComponentScenario.md +0 -20
- package/docs/reference/type-aliases/ComponentUi.md +0 -47
- package/docs/reference/type-aliases/ComponentVisitor.md +0 -10
- package/docs/reference/type-aliases/ContainerPort.md +0 -113
- package/docs/reference/type-aliases/Contract.md +0 -68
- package/docs/reference/type-aliases/ContractInput.md +0 -13
- package/docs/reference/type-aliases/ContractRequest.md +0 -95
- package/docs/reference/type-aliases/ContractResponder.md +0 -23
- package/docs/reference/type-aliases/ContractResponse.md +0 -78
- package/docs/reference/type-aliases/ContractResponseValue.md +0 -13
- package/docs/reference/type-aliases/Contracts.md +0 -48
- package/docs/reference/type-aliases/DatabaseKeys.md +0 -16
- package/docs/reference/type-aliases/DatabasePort.md +0 -72
- package/docs/reference/type-aliases/DeviceOpenOptions.md +0 -46
- package/docs/reference/type-aliases/DevicePort.md +0 -51
- package/docs/reference/type-aliases/DeviceScreen.md +0 -32
- package/docs/reference/type-aliases/DeviceTimeouts.md +0 -39
- package/docs/reference/type-aliases/DockerSpecConfig.md +0 -43
- package/docs/reference/type-aliases/DomMount.md +0 -21
- package/docs/reference/type-aliases/ElementKind.md +0 -27
- package/docs/reference/type-aliases/ElementMatch.md +0 -74
- package/docs/reference/type-aliases/ElementOptions.md +0 -29
- package/docs/reference/type-aliases/ElementRef.md +0 -128
- package/docs/reference/type-aliases/ExecOptions.md +0 -37
- package/docs/reference/type-aliases/FileAccessor.md +0 -54
- package/docs/reference/type-aliases/HonoApp.md +0 -31
- package/docs/reference/type-aliases/HttpContractFilter.md +0 -49
- package/docs/reference/type-aliases/HttpResponseInit.md +0 -46
- package/docs/reference/type-aliases/HttpStreamInit.md +0 -58
- package/docs/reference/type-aliases/IntegrationHandle.md +0 -46
- package/docs/reference/type-aliases/IntegrationSpecification.md +0 -111
- package/docs/reference/type-aliases/IntegrationSpecificationOptions.md +0 -42
- package/docs/reference/type-aliases/Intercept.md +0 -19
- package/docs/reference/type-aliases/InterceptOptions.md +0 -28
- package/docs/reference/type-aliases/InterceptScope.md +0 -11
- package/docs/reference/type-aliases/IsolationStrategy.md +0 -75
- package/docs/reference/type-aliases/JobHandle.md +0 -34
- package/docs/reference/type-aliases/JobsHandle.md +0 -57
- package/docs/reference/type-aliases/JobsResult.md +0 -16
- package/docs/reference/type-aliases/JobsSpecification.md +0 -94
- package/docs/reference/type-aliases/JobsSpecificationOptions.md +0 -56
- package/docs/reference/type-aliases/LandmarkKind.md +0 -20
- package/docs/reference/type-aliases/LiterateRunFlags.md +0 -27
- package/docs/reference/type-aliases/LiterateServeRegistration.md +0 -70
- package/docs/reference/type-aliases/MatchFixtureOptions.md +0 -27
- package/docs/reference/type-aliases/MatchableRequest.md +0 -61
- package/docs/reference/type-aliases/MatcherKind.md +0 -34
- package/docs/reference/type-aliases/MobileBackendOptions.md +0 -28
- package/docs/reference/type-aliases/MobileElementKind.md +0 -14
- package/docs/reference/type-aliases/MobileElementMatch.md +0 -60
- package/docs/reference/type-aliases/MobileElementRef.md +0 -20
- package/docs/reference/type-aliases/MobileHandle.md +0 -65
- package/docs/reference/type-aliases/MobileScenario.md +0 -20
- package/docs/reference/type-aliases/MobileSpecification.md +0 -51
- package/docs/reference/type-aliases/MobileSpecificationOptions.md +0 -126
- package/docs/reference/type-aliases/MobileVisitor.md +0 -81
- package/docs/reference/type-aliases/MockOfOptions.md +0 -26
- package/docs/reference/type-aliases/MockPort.md +0 -10
- package/docs/reference/type-aliases/PinnedClock.md +0 -10
- package/docs/reference/type-aliases/PostgresOptions.md +0 -32
- package/docs/reference/type-aliases/ProcessOptions.md +0 -112
- package/docs/reference/type-aliases/RedisOptions.md +0 -20
- package/docs/reference/type-aliases/RenderSubject.md +0 -12
- package/docs/reference/type-aliases/ScreenNode.md +0 -67
- package/docs/reference/type-aliases/ServerPort.md +0 -36
- package/docs/reference/type-aliases/ServerResponse.md +0 -46
- package/docs/reference/type-aliases/ServerSpec.md +0 -21
- package/docs/reference/type-aliases/ServiceHandle.md +0 -216
- package/docs/reference/type-aliases/ServiceRecord.md +0 -12
- package/docs/reference/type-aliases/SpecDocument.md +0 -84
- package/docs/reference/type-aliases/SpecEnvToken.md +0 -21
- package/docs/reference/type-aliases/SpecFileAssertion.md +0 -27
- package/docs/reference/type-aliases/SpecFixture.md +0 -30
- package/docs/reference/type-aliases/SpecKind.md +0 -8
- package/docs/reference/type-aliases/SpecRun.md +0 -108
- package/docs/reference/type-aliases/SpecServeEntry.md +0 -40
- package/docs/reference/type-aliases/SpecStream.md +0 -32
- package/docs/reference/type-aliases/SpecificationConfig.md +0 -290
- package/docs/reference/type-aliases/SqliteOptions.md +0 -36
- package/docs/reference/type-aliases/SseEvent.md +0 -58
- package/docs/reference/type-aliases/StreamBody.md +0 -61
- package/docs/reference/type-aliases/TextFilter.md +0 -22
- package/docs/reference/type-aliases/VisitScenario.md +0 -20
- package/docs/reference/type-aliases/Visitor.md +0 -215
- package/docs/reference/type-aliases/WaitUntilOptions.md +0 -46
- package/docs/reference/type-aliases/WebsiteBackendOptions.md +0 -37
- package/docs/reference/type-aliases/WebsiteHandle.md +0 -51
- package/docs/reference/type-aliases/WebsiteSpecification.md +0 -118
- package/docs/reference/type-aliases/WebsiteSpecificationOptions.md +0 -40
- package/docs/reference/variables/anthropic.md +0 -19
- package/docs/reference/variables/banner.md +0 -10
- package/docs/reference/variables/button.md +0 -10
- package/docs/reference/variables/clock.md +0 -16
- package/docs/reference/variables/complementary.md +0 -10
- package/docs/reference/variables/component.md +0 -10
- package/docs/reference/variables/content.md +0 -10
- package/docs/reference/variables/contentinfo.md +0 -10
- package/docs/reference/variables/dialog.md +0 -10
- package/docs/reference/variables/field.md +0 -10
- package/docs/reference/variables/form.md +0 -10
- package/docs/reference/variables/heading.md +0 -10
- package/docs/reference/variables/http.md +0 -40
- package/docs/reference/variables/intercept.md +0 -11
- package/docs/reference/variables/link.md +0 -10
- package/docs/reference/variables/listitem.md +0 -10
- package/docs/reference/variables/main.md +0 -10
- package/docs/reference/variables/match.md +0 -8
- package/docs/reference/variables/navigation.md +0 -10
- package/docs/reference/variables/openai.md +0 -21
- package/docs/reference/variables/option.md +0 -17
- package/docs/reference/variables/region.md +0 -10
- package/docs/reference/variables/row.md +0 -10
- package/docs/reference/variables/search.md +0 -10
- package/docs/reference/variables/specification.md +0 -8
- package/docs/reference/variables/status.md +0 -10
- package/docs/reference/variables/table.md +0 -10
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# Getting started
|
|
2
|
+
|
|
3
|
+
`@jterrazz/test` is a testing framework built on vitest. You describe what to test (an HTTP API, a CLI, a page, a module against a real database), and it starts what that needs, runs one action, and hands back a result you compare with `expect()`. This page covers the five ideas behind it, a first spec from install to green, and the question that picks the kind of test you write.
|
|
4
|
+
|
|
5
|
+
## Why it exists
|
|
6
|
+
|
|
7
|
+
Three things make a test suite expensive to keep, and the framework takes each one off your hands.
|
|
8
|
+
|
|
9
|
+
- **Infrastructure is code you maintain.** A suite that needs Postgres, a built binary or a browser usually grows its own `beforeAll` that starts them and an `afterAll` that sometimes forgets to stop them. Here you declare `postgres()`, `specification.cli(bin)` or a page visit, and the framework starts it, isolates it per worker and cleans it up.
|
|
10
|
+
- **Assertions drift from what the user sees.** A test that checks `status === 201` and one field passes when the body is wrong. Here a result is compared whole (the full response, the full table, the full output), written in the test or in a file next to it, with `{{tokens}}` where values move.
|
|
11
|
+
- **Every repository invents its own layout.** Here the conventions are a linter: where a test lives, what it may import and how it is named are checked by an oxlint plugin and a checker, and every finding says what it found, why the rule exists, how to fix this case, and where to read more.
|
|
12
|
+
|
|
13
|
+
## The mental model
|
|
14
|
+
|
|
15
|
+
Five ideas cover everything else in the manual. They match the four families of the docs map, plus the files a test stands on.
|
|
16
|
+
|
|
17
|
+
**1. A kind of test, and its runner.** What you test decides the kind: a module, a module against a real service (integration), a rendered component, a website, a mobile screen, an HTTP API, a job, a CLI, or the lint rules a project ships. Each kind except module and component has a constructor (`specification.api()`, `specification.cli()`, ...), called once per suite in a `*.specification.ts` file. It starts what the suite needs and returns a runner (`api`, `cli`, ...) and a `cleanup` you pass to `afterAll`. The pages call the code behind one kind a **specification**.
|
|
18
|
+
|
|
19
|
+
**2. A chain: setups, then one action.** Every test is one chain on that runner: zero or more setups (`.seed()`, `.intercept()`, `.clock()`, `.env()` ...), then exactly one action (`.get()`, `.exec()`, `.visit()`, `.trigger()`, `.call()` ...), which resolves to a result. The chain reads as the test's sentence: the setups are the Given, the action is the When.
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
const result = await api.seed(file('./__seeds__/two-users.sql')).get('/users');
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**3. What surrounds the subject.** A subject rarely runs alone. Real services (`postgres()`, `redis()`, `sqlite()`, `process()`) are started for you; the network is declared with stubs (`defineStub`, `.intercept()`); time is pinned with `clock`; a port your code injects is doubled with `mockOf()`. These have an order of preference, the doubles ladder: use the real thing first, a declared service next, then a stub, and a typed double last.
|
|
26
|
+
|
|
27
|
+
**4. A visitor, for anything on a screen.** A page, a component or a native app is driven the way a person uses it: `visitor.click(button('Subscribe'))`, `visitor.see(content('Thanks'))`. Elements are found by role and accessible name, never by CSS selector.
|
|
28
|
+
|
|
29
|
+
**5. Expectations on the whole result.** A result exposes what the action produced (`result.response`, `result.stdout`, `result.table('users')`, `result.tree`), and one matcher, `toMatch`, compares it whole: against a value written in the test, or `file('./__expected__/...')` when several tests share it. Values that change between runs are written as tokens (`{{uuid}}`, `{{iso8601}}`), and `TEST_UPDATE=1` writes the expected values for you to review.
|
|
30
|
+
|
|
31
|
+
The whole-value comparison is the framework's `toMatch` on a framework result (`result.response`, `result.stdout`). On a plain string such as `result.stdout.text`, `toMatch` is vitest's own substring or RegExp check ([Assertions § Common mistakes](../reference/assertions.md#common-mistakes)).
|
|
32
|
+
|
|
33
|
+
Everything a test stands on sits in a folder next to it whose name starts with `_`: `__seeds__/` (SQL), `__requests__/` (what is sent), `__fixtures__/` (files the program reads), `__expected__/` (what is compared), `__setup__/` (helper code). The `_` marks a folder that is not a subject of the tests.
|
|
34
|
+
|
|
35
|
+
## A first spec
|
|
36
|
+
|
|
37
|
+
This walkthrough tests an HTTP API backed by SQLite, so it needs no Docker. It assumes a Hono app in `src/app.ts`, built by a `createApp({ databaseUrl })` function, and a schema in `schema.sql`.
|
|
38
|
+
|
|
39
|
+
### 1. Install
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm install -D @jterrazz/test @jterrazz/typescript vitest better-sqlite3
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`vitest` and `@jterrazz/typescript` are always required. Everything else is installed only when a test needs it: here, `better-sqlite3` for `sqlite()`. If one is missing, the error says which package and the install command. The full list is [Set up a project § What to install](set-up-a-project.md#what-to-install).
|
|
46
|
+
|
|
47
|
+
### 2. Configure vitest
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
// jterrazz.config.ts
|
|
51
|
+
import { test } from '@jterrazz/test/config';
|
|
52
|
+
import { defineConfig } from '@jterrazz/typescript/config';
|
|
53
|
+
|
|
54
|
+
export default defineConfig({
|
|
55
|
+
kind: 'api',
|
|
56
|
+
platform: 'node',
|
|
57
|
+
presets: [test()],
|
|
58
|
+
});
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`kind` and `platform` say what the project is (here, an HTTP service on Node), and `@jterrazz/typescript` derives the rest of the toolchain from them: the lint profile, the tsconfig preset, what the manifest must hold. An application kind (`api`, `website`, `mobile`) also asks for `"private": true` in `package.json`. The two lists, and which platforms each kind runs on, are the toolchain's [Getting started § Declare what the project is](https://github.com/jterrazz/package-typescript/blob/main/docs/start/getting-started.md#1-declare-what-the-project-is). `presets: [test()]` adds this package: the shared test defaults, the conventions, and the `test` section below.
|
|
62
|
+
|
|
63
|
+
Then `npx typescript fix` writes the files that declaration derives, `vitest.config.ts` among them, a line you never edit. The preset sets the shared defaults (timeouts, artefact paths, no retries), and the projects are read off the tree, each built by the helper of its specification: `unit` collects the `*.test.ts` files next to your modules (the project a module test runs in), and `specs/api/` makes the `api` project, which collects `specs/api/**/*.spec.ts`. [Set up a project](set-up-a-project.md#the-vitest-config) lists every helper and default.
|
|
64
|
+
|
|
65
|
+
This `api` project only names a vitest project. The `api` you send requests with is a different thing: the runner that step 3 builds.
|
|
66
|
+
|
|
67
|
+
### 3. Build the runner, once per suite
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
// specs/api/api.specification.ts
|
|
71
|
+
import { specification } from '@jterrazz/test';
|
|
72
|
+
import { sqlite } from '@jterrazz/test/services';
|
|
73
|
+
import { afterAll } from 'vitest';
|
|
74
|
+
|
|
75
|
+
import { createApp } from '../../src/app.js';
|
|
76
|
+
|
|
77
|
+
export const { api, cleanup } = await specification.api({
|
|
78
|
+
services: { db: sqlite({ init: './schema.sql' }) },
|
|
79
|
+
server: ({ db }) => createApp({ databaseUrl: db.connectionString }),
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
afterAll(cleanup);
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
A spec imports from three entries: `@jterrazz/test` for what writes it (the constructors, `file()`, the targets, `match`), `@jterrazz/test/services` for what a specification starts (`sqlite()` here), and `@jterrazz/test/stubs` for what the network replies. A service or a stub imported from the root is a lint error, and `npx typescript fix` moves the import to its subpath (rule `service-or-stub-from-its-subpath`).
|
|
86
|
+
|
|
87
|
+
`server` returns your app, and the specification sends it each request through its `request(path, init)` method, which answers a Fetch `Response`; a Hono `app` is one as it stands. The app runs in the test process, wired to a real SQLite database built from `schema.sql`. Each worker gets its own copy, and every chain starts from empty tables.
|
|
88
|
+
|
|
89
|
+
An app on another framework needs two things. It needs a `request(path, init)` of its own: Express and Fastify have none, so `server` returns a wrapper you write that answers it with a `Response`. And it exposes no route table, so the run cannot measure route coverage until the project names its routes as an OpenAPI document, `test: { routes: './openapi.yaml' }` in `jterrazz.config.ts`; without one the api run fails ([API specs § An app that is not Hono](../guides/api-specs.md#an-app-that-is-not-hono)).
|
|
90
|
+
|
|
91
|
+
### 4. Write the test
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
// specs/api/users/list/shape.spec.ts
|
|
95
|
+
import { file } from '@jterrazz/test';
|
|
96
|
+
import { describe, expect, test } from 'vitest';
|
|
97
|
+
|
|
98
|
+
import { api } from '../../api.specification.js';
|
|
99
|
+
|
|
100
|
+
describe('shape', () => {
|
|
101
|
+
test('lists the users the database holds', async () => {
|
|
102
|
+
// Given - two users, from the seed beside the test
|
|
103
|
+
const result = await api.seed(file('./__seeds__/two-users.sql')).get('/users');
|
|
104
|
+
|
|
105
|
+
// Then - the whole response
|
|
106
|
+
expect(result.response).toMatch(`
|
|
107
|
+
HTTP/1.1 200 OK
|
|
108
|
+
content-type: application/json
|
|
109
|
+
|
|
110
|
+
{"users": [{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]}
|
|
111
|
+
`);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
```sql
|
|
117
|
+
-- specs/api/users/list/__seeds__/two-users.sql
|
|
118
|
+
INSERT INTO users (name) VALUES ('Alice'), ('Bob');
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
The folder says what is tested (`users/list/` is `GET /users`), the file name says which question is asked of it (`shape`), and the one `describe` repeats it. Every test carries a `// Given -` and a `// Then -` line.
|
|
122
|
+
|
|
123
|
+
### 5. Run it
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
npx vitest --run --project api
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
`--project api` runs one project; `npx vitest --run` runs every project the config declares, and is the line to put in `package.json` as `"test": "vitest --run"`.
|
|
130
|
+
|
|
131
|
+
The api project also checks route coverage: after the last test it names every route the app declares that no request reached, and a route left unreached fails the run, even on the first one. There is no switch to turn it off. To accept that debt for now, run once with `TEST_UPDATE=1`: it records the count in `.jterrazz/baseline.json`, which you commit, and the count can only go down from there ([API specs § Route coverage](../guides/api-specs.md#route-coverage)).
|
|
132
|
+
|
|
133
|
+
To write the expected value instead of typing it, leave it empty (`toMatch('')`) and run once with `TEST_UPDATE=1`. The framework writes what came back into the test, with tokens where values move; you read the diff before you commit it ([Tokens § Update mode](../reference/tokens.md#update-mode-tokens-are-preserved)).
|
|
134
|
+
|
|
135
|
+
### 6. Check the conventions
|
|
136
|
+
|
|
137
|
+
The conventions are already on: the `test()` step 2 lists carries them, and the `oxlint.config.ts` that `npx typescript fix` wrote reads it. One command enforces them, with the rest of the toolchain:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
npx typescript check
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
What it checks, and how to deviate from a rule, is [Set up a project § Turning the conventions on](set-up-a-project.md#turning-the-conventions-on).
|
|
144
|
+
|
|
145
|
+
### What you commit
|
|
146
|
+
|
|
147
|
+
| Path | Commit it? | Why |
|
|
148
|
+
| -------------------------------------------------------------------------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
149
|
+
| `jterrazz.config.ts` | yes | the one file you write: what the project is, its presets, its test projects |
|
|
150
|
+
| `vitest.config.ts`, `oxlint.config.ts`, `oxfmt.config.ts`, `tsconfig.json` | yes | derived by `typescript fix` and held by `typescript check`; an editor reads them by name |
|
|
151
|
+
| `.jterrazz/` | yes | the debt the ratchets hold, such as the `baseline.json` step 5 records; commit it with the change that recorded it, since the next run judges against it |
|
|
152
|
+
| `.artifacts/` | no | caches, reports and evidence every run rewrites: add `.artifacts/` to `.gitignore` |
|
|
153
|
+
|
|
154
|
+
## Which kind of test
|
|
155
|
+
|
|
156
|
+
Ask the questions in order: the first yes is the kind, and the kind decides where the file lives, which constructor it uses and which vitest project runs it.
|
|
157
|
+
|
|
158
|
+
<!-- GENERATED:fork. Do not edit by hand; run `npm run docs`. Source: lint/consumer/catalogue/fork.ts -->
|
|
159
|
+
|
|
160
|
+
| Question | Kind | Lives at | Constructor | Project | Read |
|
|
161
|
+
| ---------------------------------------------------------------------- | ---------------- | ------------------------------------------------ | ----------------------------- | ------------- | -------------------------------------------------------------------------------------------- |
|
|
162
|
+
| Does it render in a browser as a whole served page? | website | `specs/website/<domain>/<aspect>.spec.ts` | `specification.website()` | `website` | [Website specs](../guides/website-specs.md) |
|
|
163
|
+
| Does it render as a component (React, a DOM function, a hook)? | component | `__tests__/<file>.test.tsx` by `<file>.tsx` | none, the `component` chain | `component` | [Component specs](../guides/component-specs.md) |
|
|
164
|
+
| Does it render on a simulator? | mobile | `specs/mobile/<domain>/<aspect>.spec.ts` | `specification.mobile()` | `mobile` | [Mobile specs](../guides/mobile-specs.md) |
|
|
165
|
+
| Does it answer HTTP? | api | `specs/api/<resource>/<action>/<aspect>.spec.ts` | `specification.api()` | `api` | [API specs](../guides/api-specs.md) |
|
|
166
|
+
| Is it triggered by name, in-process? | job | `specs/job/<job>/<aspect>.spec.ts` | `specification.job()` | `job` | [Job specs](../guides/job-specs.md) |
|
|
167
|
+
| Is it a binary? | cli | `specs/cli/<command>/` (+ `<case>.spec.yaml`) | `specification.cli(bin)` | `cli` | [CLI specs](../guides/cli-specs.md), [Spec documents](../guides/spec-documents.md) |
|
|
168
|
+
| Is it a rule the project ships: a plugin, a checker, a preset? | lint | `specs/lint/<rule>/<aspect>.spec.ts` | `specification.lint()` | `lint` | [Lint specs](../guides/lint-specs.md) |
|
|
169
|
+
| Is it a module that needs a real service, or whose oracle is a golden? | integration | `specs/integration/<domain>/<aspect>.spec.ts` | `specification.integration()` | `integration` | [Integration specs](../guides/integration-specs.md) |
|
|
170
|
+
| Is it a module alone? | module | `__tests__/<file>.test.ts` by `<file>.ts` | none | `unit` | [Module tests](../guides/module-tests.md) |
|
|
171
|
+
| Is it the repository itself, a suite over several apps? | repository suite | `specs/<suite>/<domain>/<aspect>.test.ts` | none | any name | [Conventions § The third door](../concepts/conventions.md#the-third-door-a-repository-suite) |
|
|
172
|
+
|
|
173
|
+
<!-- /GENERATED:fork -->
|
|
174
|
+
|
|
175
|
+
The order matters: a page served over HTTP is a website, not an API, because the first question already caught it. And the table is total: a folder under `specs/` is a kind's name, a special folder of what the kinds share, or a repository suite; a probe of an adapter against a real database is an integration spec like any other.
|
|
176
|
+
|
|
177
|
+
The file suffix tells the kind apart: `.test.ts` or `.test.tsx` next to the code for a single unit, `.spec.ts` under `specs/<kind>/` for an assembled product, `<case>.spec.yaml` for a CLI session written as data, and `<kind>.specification.ts` for the file that builds a runner. The kind follows the subject, not the amount of machinery: a component needs a real browser and still sits next to its code, because it is one unit; a module that needs a real database sits under `specs/integration/`, because it is assembled with that database.
|
|
178
|
+
|
|
179
|
+
Before choosing a kind, ask whether the test should exist at all. A behaviour a user relies on is tested end to end, through the surface the user meets; a module test is kept only for a pure algorithm or a failure no end-to-end path can provoke ([Conventions § Which test earns its place](../concepts/conventions.md#which-test-earns-its-place)).
|
|
180
|
+
|
|
181
|
+
## Where to go next
|
|
182
|
+
|
|
183
|
+
- The chapter of your kind, from the table above. Each one has the same sections: what it specifies, the constructor, the chain, the result, what is unique to it, pitfalls.
|
|
184
|
+
- What surrounds a subject: [Stubs](../reference/stubs.md), [Services](../reference/services.md), [Time and doubles](../reference/time-and-doubles.md).
|
|
185
|
+
- Acting on a screen: [The visitor](../reference/visitor.md).
|
|
186
|
+
- Judging a result: [Assertions](../reference/assertions.md), [Tokens](../reference/tokens.md).
|
|
187
|
+
- Why the conventions are what they are: [Conventions](../concepts/conventions.md); every rule, generated from the code: [Linting](../reference/linting.md).
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# Set up a project
|
|
2
|
+
|
|
3
|
+
What a project installs, how its `vitest.config.ts` is written, how it turns the conventions on, and where a run writes. [Getting started](getting-started.md) walks through the minimum; this page is the reference behind it.
|
|
4
|
+
|
|
5
|
+
## What to install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -D @jterrazz/test @jterrazz/typescript vitest
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
**`vitest ^5` and `@jterrazz/typescript ^11` are the only required peers.** The framework registers its matchers into vitest and has no runner of its own. The toolchain, [`@jterrazz/typescript`](https://github.com/jterrazz/package-typescript), is required because the framework prints its refusals through the toolchain's rules kit and its published types extend the toolchain's `JterrazzConfig` (`@jterrazz/typescript/config`). Its lint conventions are a separate, opt-in step ([below](#turning-the-conventions-on)).
|
|
12
|
+
|
|
13
|
+
### Project requirements
|
|
14
|
+
|
|
15
|
+
- **ESM.** The package ships ES modules only, and every specification file awaits its constructor at the top level (`export const { api, cleanup } = await specification.api(...)`). Set `"type": "module"` in `package.json`, or name the config files `.mts` (`vitest.config.mts`).
|
|
16
|
+
- **Node 24 or newer**, as `engines` states.
|
|
17
|
+
- **A tsconfig that knows `using` and top-level await.** Tests pin time with `using _ = clock.at(...)` and dispose with `await using` ([Time and doubles](../reference/time-and-doubles.md)), which need `target` and `lib` at `ES2022` or later for top-level await and `ESNext` for `using`, with `module` at `ESNext` or `NodeNext`. The toolchain's tsconfig presets set all of it: `"extends": "@jterrazz/typescript/tsconfig/node"`.
|
|
18
|
+
|
|
19
|
+
**Everything else is an optional peer**, loaded the first time a test needs it. A project that tests no page, no component and no database installs none of them.
|
|
20
|
+
|
|
21
|
+
| Needed by | Install |
|
|
22
|
+
| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
23
|
+
| A website visit | `playwright`, then `npx playwright install chromium` once |
|
|
24
|
+
| A component | `@vitest/browser-playwright` (the exact version of `vitest`), `vitest-browser-react`, `vite`, `react`, `react-dom`, `playwright` and a chromium |
|
|
25
|
+
| A mobile screen | `appium`, `webdriverio`, then `npx appium driver install xcuitest` |
|
|
26
|
+
| `postgres()`, `redis()` | `testcontainers`, plus `pg` or `redis` for the driver a table read uses, and a running Docker |
|
|
27
|
+
| `sqlite()` | `better-sqlite3` (no Docker) |
|
|
28
|
+
| A lint spec, `ruleTester()` | `oxlint` |
|
|
29
|
+
| Coverage | `@vitest/coverage-v8` |
|
|
30
|
+
|
|
31
|
+
`msw`, which delivers stubbed network replies, is a direct dependency: nothing to install. An API spec hands your app to `server`, which must answer `request(path, init)` with a Fetch `Response` and, for route coverage, expose Hono's route table. A Hono app does both. Any other framework needs a wrapper of your own for the first, and an OpenAPI document named in `test: { routes }` for the second ([API specs § An app that is not Hono](../guides/api-specs.md#an-app-that-is-not-hono)).
|
|
32
|
+
|
|
33
|
+
**A missing peer is named when it is needed.** A specification's refusal names the packages it lacks and the `npm install -D` that brings them. A service's refusal names the service that asked, the peer, and the install command of the package manager your lockfile names; under pnpm a native binding must also be listed to be built, and the message adds that line:
|
|
34
|
+
|
|
35
|
+
```text
|
|
36
|
+
found: sqlite() requires `better-sqlite3`, and it is not installed
|
|
37
|
+
why: Every seam is an optional peer of @jterrazz/test, installed by the project that specifies through it.
|
|
38
|
+
fix: pnpm add -D better-sqlite3; under pnpm the binding is not built unless the package is listed: add "better-sqlite3" to `onlyBuiltDependencies` in package.json (or `only-built-dependencies` in .npmrc) and reinstall
|
|
39
|
+
read: docs/reference/linting.md#seam-is-an-optional-peer
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**A native peer can be installed and still not built.** npm since 11, pnpm since 10 and bun since 1.2 withhold a dependency's install script by default, so a native package arrives without its compiled `.node`. `better-sqlite3` 13 ships its binding prebuilt and loads anyway; with 12, `sqlite()` notices on first load and says how to build it:
|
|
43
|
+
|
|
44
|
+
```text
|
|
45
|
+
found: sqlite() found `better-sqlite3`, and its native binding is not built
|
|
46
|
+
why: A native peer is built by its install script, and npm runs a dependency's install script only once it is told to.
|
|
47
|
+
fix: npm install-scripts approve better-sqlite3 && npm rebuild better-sqlite3
|
|
48
|
+
read: docs/start/set-up-a-project.md#what-to-install
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Three more constraints come from the peers themselves:
|
|
52
|
+
|
|
53
|
+
- **One Playwright for the whole family.** This package's `playwright` peer is the exact version it is gated on, and a project declares that version: two revisions on one machine each need their own Chromium build, and `typescript doctor` names the build a project's Playwright wants and the machine lacks. `npx playwright install chromium`, run in the project, provisions it.
|
|
54
|
+
- **The browser provider moves with vitest.** `@vitest/browser-playwright@X` requires `vitest` at exactly `X`, so the two are upgraded together: `npm install -D vitest@5.0.1 @vitest/browser-playwright@5.0.1`. `component()` checks the two versions match and names both when they do not.
|
|
55
|
+
- **Vite `^6.4 || ^7 || ^8`** for a component. `component()` reads the installed major and states the JSX transform on the key that version reads.
|
|
56
|
+
- **The ARIA dialect of a `.aria.yaml` golden is Playwright's.** A Playwright major can reword a role line, and every committed tree golden then moves with it in one `TEST_UPDATE=1` run.
|
|
57
|
+
|
|
58
|
+
Why vitest 5 alone, and why the native seams became optional peers: [ADR-008](https://github.com/jterrazz/package-test/blob/main/docs/decisions/008-vitest-5-node-24-and-the-native-seams-as-optional-peers.md).
|
|
59
|
+
|
|
60
|
+
To try an unreleased build of the framework, install the tarball `npm pack` writes, never a `file:` link: a link makes the project resolve vitest's types twice, and the matchers then type on one copy and not the other. The recipe (the tarball packed into `.artifacts/packages/`, the lockfile entries to drop, bun's cache, the order a wave of projects takes the two packages in) is the toolchain's [Install an unreleased build](https://github.com/jterrazz/package-typescript/blob/main/docs/guides/install-an-unreleased-build.md), since this package is installed with it.
|
|
61
|
+
|
|
62
|
+
## The vitest config
|
|
63
|
+
|
|
64
|
+
The projects a repository runs are read off its tree: one per folder `specs/<specification>/` it keeps (`specs/api/`, `specs/cli/`, `specs/website/` and the rest), `unit` over each root holding a module test (`src/`, `lint/`, a folder of `specs/` no specification names, a `unit` one included), and `component` over the component specs and each root holding a rendered test (`*.test.tsx`). A ground, setup or dot folder is never read. `jterrazz.config.ts` states only how a project departs from the one its tree derives (a helper's options, its own global setups), keyed by its specification, with the reason. The package's standard, `src/standard/standard.ts`, states the one departure every repository shares: the `unit` project runs a worker's files in one module graph (`isolate: false`), since a module test leaves no state behind in a module; a repository whose module tests do states `isolate: true` with its reason. The standard only shapes a project the tree derives or the file declares; it makes none exist. `typescript doctor` prints the projects derived from the tree beside what each file states. The file also declares the project's `kind` and `platform`, which `@jterrazz/typescript` reads to derive the rest of the toolchain ([its Configuration § Kind and platform](https://github.com/jterrazz/package-typescript/blob/main/docs/reference/configuration.md#kind-and-platform)):
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
// jterrazz.config.ts
|
|
68
|
+
import { test } from '@jterrazz/test/config';
|
|
69
|
+
import { defineConfig } from '@jterrazz/typescript/config';
|
|
70
|
+
|
|
71
|
+
export default defineConfig({
|
|
72
|
+
kind: 'website',
|
|
73
|
+
platform: 'react',
|
|
74
|
+
presets: [test()],
|
|
75
|
+
test: {
|
|
76
|
+
projects: {
|
|
77
|
+
component: {
|
|
78
|
+
reason: 'the app renders through its own Vite pipeline, inside its providers',
|
|
79
|
+
vite: './vite.config.ts',
|
|
80
|
+
wrap: './src/providers.tsx',
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
A project keyed by a specification the package does not ship, a declared one with no `reason`, and `projects` written as a list are refused in four parts, reading this section.
|
|
88
|
+
|
|
89
|
+
`vitest.config.ts` is the file those projects derive, which `typescript fix` writes and `typescript check` holds ([`@jterrazz/typescript`'s Config pass](https://github.com/jterrazz/package-typescript/blob/main/docs/reference/quality-checks.md#the-config-one-file-pass)):
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
import { vitestConfig } from '@jterrazz/test/vitest';
|
|
93
|
+
|
|
94
|
+
export default await vitestConfig(import.meta.url);
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
It reads the `jterrazz.config.ts` beside the module whose `import.meta.url` it is handed (the config file's own folder, wherever vitest, the Vitest extension or knip loads it) and builds each project the tree names, or the file declares, with its helper over the preset below. A project states no `name`, `environment` or `browser`: its helper names it for its specification and sets what the specification needs. A relative `setup` (global setups the project runs, read from the project root), `root` and `vite.resolve.alias` target are read from the project root; `wrap` stays the component's root's. A tree with no test and a file declaring no project leave the config the preset alone. It is the one-liner `@jterrazz/typescript` derives for every editor file (the entry handed the module's own `import.meta.url`, awaited at the top), which vitest and knip's loader both read whole.
|
|
98
|
+
|
|
99
|
+
### The project helpers
|
|
100
|
+
|
|
101
|
+
There is one helper per kind (`unit`, `integration`, `component`, `website`, `mobile`, `api`, `job`, `cli` and `lint`), named by the key of a project, and exported from `@jterrazz/test/vitest` beside `vitestConfig`, for a config written by a tool of your own. Each collects its folder (`specs/<kind>/**/*.spec.ts`, plus the `<case>.spec.yaml` documents for `cli()`) and carries what its kind needs: a browser for `component()`, a simulator session for `mobile()`, and for `api()`, `job()` and `integration()` a global setup that starts each service container once and shares it across every file and project of the run ([Services § One container per run](../reference/services.md#one-container-per-run)). Docker is needed only by a spec that declares `postgres()` or `redis()`: with no Docker running, each such file fails with one sentence naming Docker, and every other file runs. Every helper accepts:
|
|
102
|
+
|
|
103
|
+
| Option | Does |
|
|
104
|
+
| --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
105
|
+
| `include` / `exclude` | Replace the default globs. A default `exclude` guards the default `include`, so state both or neither |
|
|
106
|
+
| `timeout` | Raise or lower the 30 s default for this project alone: each test's and each hook's, so the `afterAll` that stops what a slow test started has the same budget |
|
|
107
|
+
| `serial` | Run the project's files one at a time (`fileParallelism: false`), when they share one server, one file, one app |
|
|
108
|
+
|
|
109
|
+
`unit()` and `component()` are a pair: `unit()` collects `**/*.test.ts` outside `specs/` and leaves out `**/*.test.tsx`, which `component()` collects. So the suffix beside a file decides which project runs it. `unit()` also takes `roots` when the modules are not at the repository root, and `isolate: false` to run a worker's files in one module graph, only for a project whose tests leave no state behind in a module, and the default `vitestConfig` hands it. `component()` takes the options of the app it renders (`vite`, `wrap`, `viewport`, `clock`, `timezone`, `locale`, `root`), described in [Component specs](../guides/component-specs.md#the-constructor-there-is-none); `mobile()` takes the simulator the run shares ([Mobile specs](../guides/mobile-specs.md#one-session-per-run)).
|
|
110
|
+
|
|
111
|
+
Projects run in groups (the node kinds first, then `website`, then `component`, then `mobile`), so two browser projects never open two Chromiums at once and a simulator runs alone.
|
|
112
|
+
|
|
113
|
+
`api()`, `job()` and `integration()` also take `runner`, the module their spec files import their runner from, such as `'./specs/api/api.specification.ts'`. The tree derives it where the folder holds one specification module, named for it (`specs/api/api.specification.ts` alone in `specs/api/`), so a project states it only for a module named otherwise. With it, the files of a worker share one module graph (`isolate: false`) and the specification runs again before each file (`setupFiles`): the app's modules load once per worker instead of once per file, and every file still boots its own app on its own services. A spec file imports its runner from the specification itself, since a module of the shared graph that imports it keeps the first file's runner.
|
|
114
|
+
|
|
115
|
+
An env-gated sub-suite is an `exclude`, not a second project:
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
const integration = {
|
|
119
|
+
exclude: process.env.E2E_NETWORK ? [] : ['specs/integration/network/**'],
|
|
120
|
+
reason: 'the network specs reach the internet, which a run turns on with E2E_NETWORK',
|
|
121
|
+
};
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### What the preset sets
|
|
125
|
+
|
|
126
|
+
| Setting | Value | Why |
|
|
127
|
+
| -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ |
|
|
128
|
+
| `test.testTimeout`, `hookTimeout` | `30_000` | vitest's 5 s never survived a container boot or a build |
|
|
129
|
+
| `test.retry` | `0` | A flaky test is fixed or deleted; a retry hides it |
|
|
130
|
+
| `test.restoreMocks`, `unstubGlobals`, `unstubEnvs` | `true` | A spy, a stubbed global or a stubbed env a test forgets to restore would fail a later file that did nothing wrong |
|
|
131
|
+
| `test.exclude` | vitest's defaults + `.artifacts/**` + `**/{__expected__,__fixtures__,__requests__,__seeds__}/**` | What a test stands on is input, never a suite to run; `.artifacts/` holds copies of the project, a mutation sandbox among them |
|
|
132
|
+
| `cacheDir`, `test.coverage.reportsDirectory`, `test.attachmentsDir`, `test.outputFile` | under `.artifacts/vitest/` | Everything a tool writes lives at `.artifacts/<tool>/` ([below](#where-a-run-writes)) |
|
|
133
|
+
| `test.globalSetup` | update mode, the run's temp root | Update mode writes after the run is reported; the temp root is [below](#where-a-run-writes) |
|
|
134
|
+
| `plugins` | `literate()`, when `literate:` is given | Turns each `<case>.spec.yaml` into a test file ([Spec documents](../guides/spec-documents.md)) |
|
|
135
|
+
|
|
136
|
+
Coverage attaches to every process a run spawns (`autoAttachSubprocess`), and reads `dist/` only to map each bundle back to the source it was built from (`excludeAfterRemap`): a spec that runs the built command counts toward the sources it exercised.
|
|
137
|
+
|
|
138
|
+
It sets nothing else on purpose. `fileParallelism`, `reporters`, `environment`, `env` and every `include` are true of one project and not of another, so a project states them; a helper does it for you. The coverage provider is yours to install.
|
|
139
|
+
|
|
140
|
+
The defaults reach every inline project too, since vitest resolves each project as its own config. A project given as a glob string, a promise or a function is handed back untouched.
|
|
141
|
+
|
|
142
|
+
The `<case>.spec.yaml` documents are collected by the `cli` project, which takes `literate` beside its own options:
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
const cli = {
|
|
146
|
+
literate: { runner: './specs/cli/run.specification.ts' },
|
|
147
|
+
reason: 'the documents run against the runner the run/ specs share',
|
|
148
|
+
};
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
To adopt the preset from a config written by hand, file each kind of test under its folder, move what a project passed its helper beyond that into `test.projects`, keyed by its specification and with its reason, delete what the table above already says (the 30 s timeouts, `...configDefaults.exclude`, the ground exclusions), and run `typescript fix`, which writes the derived `vitest.config.ts`. A timeout lower or higher than 30 s stays stated, as the project's `timeout`: the preset is a starting point, not a ceiling. Then add `.artifacts/` to `.gitignore`.
|
|
152
|
+
|
|
153
|
+
## Turning the conventions on
|
|
154
|
+
|
|
155
|
+
The conventions travel in the same `test()` preset that sets the vitest defaults, so the `jterrazz.config.ts` [above](#the-vitest-config) already turns them on. The base preset needs no line of its own: the declared `platform` picks the lint profile it runs ([the toolchain's Lint presets § The profiles](https://github.com/jterrazz/package-typescript/blob/main/docs/reference/lint-presets.md#the-profiles)), and `test()` adds this package's rules on top.
|
|
156
|
+
|
|
157
|
+
The linter reads them through the `oxlint.config.ts` that `npx typescript fix` derives, one line like the vitest config:
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
// oxlint.config.ts
|
|
161
|
+
import { oxlintConfig } from '@jterrazz/typescript/oxlint';
|
|
162
|
+
|
|
163
|
+
export default await oxlintConfig(import.meta.url);
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
`npx typescript check` then runs all of it: the oxlint plugin, the import and naming rules, and the conventions checker over `specs/`. Nothing is detected from your dependencies: a project that does not list `test()` runs none of these rules, and writes its tests all the same. A rule you set apart is written in `jterrazz.config.ts` with its reason, never in the derived file; how, how to suppress one finding, and the full catalogue are [Linting](../reference/linting.md).
|
|
167
|
+
|
|
168
|
+
## Where a run writes
|
|
169
|
+
|
|
170
|
+
**In the project, only under `.artifacts/`**, one folder per tool, one line in `.gitignore`, one `rm -rf .artifacts` for a clean slate:
|
|
171
|
+
|
|
172
|
+
| Path | Written by | Lifetime |
|
|
173
|
+
| ------------------------------------------------ | ---------------------------------------------------------- | ------------------------------- |
|
|
174
|
+
| `.artifacts/vitest/` | vite's transform cache, vitest's reports | Reused across runs |
|
|
175
|
+
| `.artifacts/vitest/coverage/` | the coverage provider | Rewritten per coverage run |
|
|
176
|
+
| `.artifacts/vitest/sqlite/template-<key>.sqlite` | `sqlite()`'s schema template | Reused until the schema changes |
|
|
177
|
+
| `.artifacts/vitest/evidence/<pid>-.../` | the screenshot of a website or mobile scenario that failed | Kept until the next run starts |
|
|
178
|
+
|
|
179
|
+
**Everything else is scratch, in one temp folder per run.** Before the first worker starts, the preset creates `jterrazz-test-runs-<uid>/<pid>-...` in the OS temp directory and points `TMPDIR`, `TMP` and `TEMP` at it, so every temp folder a worker, a spawned child or a tool it runs makes lands inside. That holds each CLI spec's working directory, the per-worker database copies and a browser's profile. The run removes the folder when it ends, and the next run removes the folder of any run that died first. A CLI spec's working directory goes sooner, once its test has finished, except under `test.concurrent`, where it waits for the run.
|
|
180
|
+
|
|
181
|
+
**Two environment variables are yours to set**: `TEST_UPDATE=1`, which turns on update mode ([Tokens § Update mode](../reference/tokens.md#update-mode-tokens-are-preserved)), and `TEST_MOBILE_HEADED=1`, which shows a mobile run's simulator in a window instead of running it headless ([Mobile specs](../guides/mobile-specs.md#the-simulator-and-what-the-run-leaves-on-it)). The framework also reads `VITEST_POOL_ID`, which vitest sets, to isolate each worker's databases.
|
|
182
|
+
|
|
183
|
+
## Two rough edges of the toolchain's flow
|
|
184
|
+
|
|
185
|
+
- **An `include` that names `.test.ts`** stops collecting the moment `typescript fix` renames a spec. The fix lists every glob or literal it broke, so update them in the same commit; `include-points-at-a-folder` holds the class afterwards.
|
|
186
|
+
- **`typescript baseline` in an npm-hoisted workspace member** cannot resolve oxlint today; run it from a directory where `node_modules/.bin/oxlint` resolves.
|
|
187
|
+
|
|
188
|
+
## Pitfalls
|
|
189
|
+
|
|
190
|
+
- **Forgetting `afterAll(cleanup)` in a specification file.** The services leak into the next suite (rule `cleanup-passed-to-after-all`).
|
|
191
|
+
- **Importing from a subpath the package does not publish** (`@jterrazz/test/matchers`). A test imports from `@jterrazz/test`, `@jterrazz/test/services` and `@jterrazz/test/stubs`; the tool entries are `/vitest`, `/config`, `/oxlint` and `/schema` (rule `no-unpublished-subpath`).
|
|
192
|
+
- **Importing a service or a stub from the root.** `postgres()` comes from `@jterrazz/test/services` and `http` from `@jterrazz/test/stubs`, so a spec that starts no container and serves no stub loads neither; `npx typescript fix` moves such an import to its subpath (rule `service-or-stub-from-its-subpath`).
|
|
193
|
+
- **Writing `browser:` in a config by hand.** `component()` pins the provider to the runner's version, serves the service worker and pre-bundles cold dependencies; a derived config has no place for a hand-written one: a project's `specification` is its helper.
|
|
194
|
+
|
|
195
|
+
## Upgrading
|
|
196
|
+
|
|
197
|
+
Each release's notes are its [GitHub release](https://github.com/jterrazz/package-test/releases); the package keeps no changelog. A major carries no bridge from the one before (no codemod, no rename map, no rule that recognises the old spellings; see [ADR-014](https://github.com/jterrazz/package-test/blob/main/docs/decisions/014-a-major-carries-no-bridge-from-the-one-before.md)), so a project moves by hand, and the rules of the new major name each leftover in their own terms.
|
|
198
|
+
|
|
199
|
+
### From 16 to 17
|
|
200
|
+
|
|
201
|
+
| What a 16 project wrote | What 17 asks for |
|
|
202
|
+
| ------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
203
|
+
| `@jterrazz/typescript` ^10 | ^11, released first; its own migration is [its Release § Which number moves](https://github.com/jterrazz/package-typescript/blob/main/docs/guides/release.md#which-number-moves) |
|
|
204
|
+
| a hand-written `vitest.config.ts` | `test.projects` in `jterrazz.config.ts`, and the derived one-liner `typescript fix` writes ([§ The vitest config](#the-vitest-config)) |
|
|
205
|
+
| the `jterrazz-test-check` bin, chained after `typescript check` | `presets: [test()]`; `typescript check`, `fix` and `baseline` run the checker |
|
|
206
|
+
| a bare name: `.seed('two-users.sql')`, `.request('create-user.http')`, the `$`-prefixed alias of the shared fixture pool | a path from the spec file, `file('./__seeds__/two-users.sql')` or `directory(...)`; the shared ground is the special folder of its role at the specs root, `specs/__seeds__/`, `specs/__fixtures__/` and the rest, reached by the path that climbs to it |
|
|
207
|
+
| `defineContract()`, `defineContracts()` and the `Contract*` types | `defineStub()`, `defineStubs()` and the `Stub*` types ([Stubs](../reference/stubs.md)) |
|
|
208
|
+
| `specification.jobs()`, its `jobs` runner and the `jobs` folder under `specs/` | `specification.job()`, the `job` runner and `specs/job/` ([Job specs](../guides/job-specs.md)) |
|
|
209
|
+
| field probes with no whole comparison, the matcher of a table's rows, an api or job `<case>.spec.yaml` | one `toMatch` of the whole result, written in the test or in a `file()` ([Assertions](../reference/assertions.md)); documents stay for cli and lint specs |
|
|
210
|
+
| a rule id, in a config, a suppression or a ratchet key | its new name, stated as its requirement ([Linting § The rule catalogue](../reference/linting.md#the-rule-catalogue)); a directive naming an old id is reported as dead |
|
|
211
|
+
|
|
212
|
+
## Related
|
|
213
|
+
|
|
214
|
+
[Getting started](getting-started.md) · [Component specs](../guides/component-specs.md) · [Linting](../reference/linting.md) · [Release](https://github.com/jterrazz/package-test/blob/main/docs/guides/release.md)
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jterrazz/test",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Declarative testing framework for HTTP APIs, CLIs, background jobs, websites, mobile apps
|
|
3
|
+
"version": "17.0.0",
|
|
4
|
+
"description": "Declarative testing framework for HTTP APIs, CLIs, background jobs, websites, mobile apps, rendered components and the rules a project ships: one entry point, real infrastructure and a real browser, golden-file assertions, plus an oxlint convention plugin.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"api-testing",
|
|
7
7
|
"browser-mode",
|
|
@@ -25,27 +25,42 @@
|
|
|
25
25
|
"type": "git",
|
|
26
26
|
"url": "https://github.com/jterrazz/package-test"
|
|
27
27
|
},
|
|
28
|
-
"bin": {
|
|
29
|
-
"jterrazz-test-check": "dist/checker.js"
|
|
30
|
-
},
|
|
31
28
|
"files": [
|
|
32
29
|
"dist",
|
|
33
|
-
"!dist
|
|
34
|
-
"!dist/coverage.*",
|
|
30
|
+
"!dist/**/*.map",
|
|
35
31
|
"docs",
|
|
32
|
+
"!docs/guides/test.md",
|
|
33
|
+
"!docs/decisions",
|
|
36
34
|
"schema"
|
|
37
35
|
],
|
|
38
36
|
"type": "module",
|
|
37
|
+
"imports": {
|
|
38
|
+
"#checker": "./dist/checker.js"
|
|
39
|
+
},
|
|
39
40
|
"exports": {
|
|
40
41
|
".": {
|
|
41
42
|
"types": "./dist/index.d.ts",
|
|
42
43
|
"browser": "./dist/browser/index.js",
|
|
43
44
|
"import": "./dist/index.js"
|
|
44
45
|
},
|
|
46
|
+
"./services": {
|
|
47
|
+
"types": "./dist/services.d.ts",
|
|
48
|
+
"browser": "./dist/browser/services.js",
|
|
49
|
+
"import": "./dist/services.js"
|
|
50
|
+
},
|
|
51
|
+
"./stubs": {
|
|
52
|
+
"types": "./dist/stubs.d.ts",
|
|
53
|
+
"browser": "./dist/browser/stubs.js",
|
|
54
|
+
"import": "./dist/stubs.js"
|
|
55
|
+
},
|
|
45
56
|
"./vitest": {
|
|
46
57
|
"types": "./dist/vitest.d.ts",
|
|
47
58
|
"import": "./dist/vitest.js"
|
|
48
59
|
},
|
|
60
|
+
"./config": {
|
|
61
|
+
"types": "./dist/config.d.ts",
|
|
62
|
+
"import": "./dist/config.js"
|
|
63
|
+
},
|
|
49
64
|
"./oxlint": {
|
|
50
65
|
"import": {
|
|
51
66
|
"types": "./dist/oxlint.d.ts",
|
|
@@ -62,51 +77,53 @@
|
|
|
62
77
|
"registry": "https://registry.npmjs.org/"
|
|
63
78
|
},
|
|
64
79
|
"scripts": {
|
|
65
|
-
"build": "
|
|
66
|
-
"docs": "npm run build && node
|
|
67
|
-
"lint": "typescript check
|
|
68
|
-
"lint:fix": "typescript fix && node dist/checker.js specs --fix",
|
|
80
|
+
"build": "typescript build",
|
|
81
|
+
"docs": "npm run build && node .artifacts/dev/self.js chapters && node .artifacts/dev/schema.js",
|
|
82
|
+
"lint": "typescript check",
|
|
69
83
|
"test": "vitest --run",
|
|
70
|
-
"
|
|
71
|
-
"coverage:check": "node dist/coverage.js"
|
|
84
|
+
"test:repeat": "vitest --run --repeats 10"
|
|
72
85
|
},
|
|
73
86
|
"dependencies": {
|
|
87
|
+
"@oxlint/plugins": "^1.85.0",
|
|
74
88
|
"msw": "^2.15.0",
|
|
89
|
+
"oxc-parser": "^0.150.0",
|
|
75
90
|
"vitest-mock-extended": "^5.0.0",
|
|
76
91
|
"yaml": "^2.9.0"
|
|
77
92
|
},
|
|
78
93
|
"devDependencies": {
|
|
79
|
-
"@hono/node-server": "^2.
|
|
80
|
-
"@jterrazz/typescript": "^
|
|
81
|
-
"@types/better-sqlite3": "^
|
|
82
|
-
"@types/node": "^26.
|
|
83
|
-
"@types/pg": "^8.
|
|
94
|
+
"@hono/node-server": "^2.1.1",
|
|
95
|
+
"@jterrazz/typescript": "^11.0.0",
|
|
96
|
+
"@types/better-sqlite3": "^9.6.0",
|
|
97
|
+
"@types/node": "^26.6.2",
|
|
98
|
+
"@types/pg": "^8.23.1",
|
|
84
99
|
"@types/react": "^19.3.0",
|
|
85
100
|
"@types/react-dom": "^19.3.0",
|
|
86
101
|
"@vitest/browser-playwright": "5.0.1",
|
|
87
102
|
"@vitest/coverage-v8": "^5.0.1",
|
|
88
|
-
"
|
|
89
|
-
"
|
|
90
|
-
"
|
|
91
|
-
"
|
|
92
|
-
"
|
|
103
|
+
"ajv": "^8.20.0",
|
|
104
|
+
"better-sqlite3": "^13.0.3",
|
|
105
|
+
"hono": "^4.13.9",
|
|
106
|
+
"oxlint": "1.85.0",
|
|
107
|
+
"pg": "^8.23.0",
|
|
108
|
+
"playwright": "1.63.0",
|
|
93
109
|
"react": "^19.3.0",
|
|
94
110
|
"react-dom": "^19.3.0",
|
|
95
111
|
"react-router": "^8.4.0",
|
|
96
|
-
"redis": "^6.1
|
|
97
|
-
"testcontainers": "^12.0
|
|
98
|
-
"
|
|
99
|
-
"vite": "^8.1.4",
|
|
112
|
+
"redis": "^6.2.1",
|
|
113
|
+
"testcontainers": "^12.1.0",
|
|
114
|
+
"vite": "^8.3.0",
|
|
100
115
|
"vitest": "5.0.1",
|
|
101
116
|
"vitest-browser-react": "^2.3.0",
|
|
102
|
-
"webdriverio": "^9.
|
|
117
|
+
"webdriverio": "^9.32.0"
|
|
103
118
|
},
|
|
104
119
|
"peerDependencies": {
|
|
120
|
+
"@jterrazz/typescript": "^11.0.0",
|
|
105
121
|
"@vitest/browser-playwright": "*",
|
|
106
122
|
"appium": "^3.0.0",
|
|
107
|
-
"better-sqlite3": "^12.11.1",
|
|
123
|
+
"better-sqlite3": "^12.11.1 || ^13",
|
|
124
|
+
"oxlint": "^1.85.0",
|
|
108
125
|
"pg": "^8.22.0",
|
|
109
|
-
"playwright": "
|
|
126
|
+
"playwright": "1.63.0",
|
|
110
127
|
"react": "^18 || ^19",
|
|
111
128
|
"react-dom": "^18 || ^19",
|
|
112
129
|
"redis": "^6.1.0",
|
|
@@ -126,6 +143,9 @@
|
|
|
126
143
|
"better-sqlite3": {
|
|
127
144
|
"optional": true
|
|
128
145
|
},
|
|
146
|
+
"oxlint": {
|
|
147
|
+
"optional": true
|
|
148
|
+
},
|
|
129
149
|
"pg": {
|
|
130
150
|
"optional": true
|
|
131
151
|
},
|