@jterrazz/test 5.3.2 → 6.1.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 CHANGED
@@ -13,27 +13,30 @@ npm install -D @jterrazz/test vitest
13
13
  ```typescript
14
14
  // tests/setup/integration.specification.ts
15
15
  import { afterAll } from 'vitest';
16
- import { integration, postgres } from '@jterrazz/test';
16
+ import { spec, app } from '@jterrazz/test';
17
+ import { postgres } from '@jterrazz/test/services';
17
18
  import { createApp } from '../../src/app.js';
18
19
 
19
20
  const db = postgres({ compose: 'db' });
20
21
 
21
- export const spec = await integration({
22
- services: [db],
23
- app: () => createApp({ databaseUrl: db.connectionString }),
24
- root: '../../',
25
- });
22
+ export const run = await spec(
23
+ app(() => createApp({ databaseUrl: db.connectionString })),
24
+ {
25
+ services: [db],
26
+ root: '../../',
27
+ },
28
+ );
26
29
 
27
- afterAll(() => spec.cleanup());
30
+ afterAll(() => run.cleanup());
28
31
  ```
29
32
 
30
33
  ```typescript
31
34
  // tests/e2e/users/users.e2e.test.ts
32
- import { spec } from '../../setup/integration.specification.js';
35
+ import { run } from '../../setup/integration.specification.js';
33
36
 
34
37
  test('creates a user', async () => {
35
38
  // Given — one existing user
36
- const result = await spec('creates user')
39
+ const result = await run('creates user')
37
40
  .seed('initial-users.sql')
38
41
  .post('/users', 'new-user.json')
39
42
  .run();
@@ -52,21 +55,20 @@ test('creates a user', async () => {
52
55
  ```typescript
53
56
  // tests/setup/cli.specification.ts
54
57
  import { resolve } from 'node:path';
55
- import { cli } from '@jterrazz/test';
58
+ import { spec, command } from '@jterrazz/test';
56
59
 
57
- export const spec = await cli({
58
- command: resolve(import.meta.dirname, '../../bin/my-cli.sh'),
60
+ export const run = await spec(command(resolve(import.meta.dirname, '../../bin/my-cli.sh')), {
59
61
  root: '../fixtures',
60
62
  });
61
63
  ```
62
64
 
63
65
  ```typescript
64
66
  // tests/e2e/build/build.e2e.test.ts
65
- import { spec } from '../../setup/cli.specification.js';
67
+ import { run } from '../../setup/cli.specification.js';
66
68
 
67
69
  test('builds the project', async () => {
68
70
  // Given — sample app project
69
- const result = await spec('build').project('sample-app').exec('build').run();
71
+ const result = await run('build').project('sample-app').exec('build').run();
70
72
 
71
73
  // Then — ESM output with source maps
72
74
  expect(result.exitCode).toBe(0);
@@ -81,50 +83,52 @@ test('builds the project', async () => {
81
83
 
82
84
  Three modes, same builder API. Each handles infrastructure and cleanup automatically.
83
85
 
84
- ### `integration()` — testcontainers + in-process app
86
+ ### `spec(app(...))` — testcontainers + in-process app
85
87
 
86
88
  Starts real containers via testcontainers. App runs in-process (Hono). Fastest feedback loop.
87
89
 
88
90
  ```typescript
89
- import { integration, postgres, redis } from '@jterrazz/test';
91
+ import { spec, app } from '@jterrazz/test';
92
+ import { postgres, redis } from '@jterrazz/test/services';
90
93
 
91
94
  const db = postgres({ compose: 'db' });
92
95
  const cache = redis({ compose: 'cache' });
93
96
 
94
- export const spec = await integration({
95
- services: [db, cache],
96
- app: () => createApp({ databaseUrl: db.connectionString }),
97
- root: '../../',
98
- });
97
+ export const run = await spec(
98
+ app(() => createApp({ databaseUrl: db.connectionString })),
99
+ {
100
+ services: [db, cache],
101
+ root: '../../',
102
+ },
103
+ );
99
104
  ```
100
105
 
101
- ### `e2e()` — docker compose up + real HTTP
106
+ ### `spec(stack(...))` — docker compose up + real HTTP
102
107
 
103
108
  Starts the full `docker/compose.test.yaml` stack. App URL and databases auto-detected.
104
109
 
105
110
  ```typescript
106
- import { e2e } from '@jterrazz/test';
111
+ import { spec, stack } from '@jterrazz/test';
107
112
 
108
- export const spec = await e2e({
109
- root: '../../',
110
- });
113
+ export const run = await spec(stack('../../'));
111
114
  ```
112
115
 
113
- ### `cli()` — local command execution
116
+ ### `spec(command(...))` — local command execution
114
117
 
115
118
  Runs CLI commands against fixture projects in temp directories. Optionally starts infrastructure.
116
119
 
117
120
  ```typescript
118
- import { cli } from '@jterrazz/test';
121
+ import { spec, command } from '@jterrazz/test';
122
+ import { postgres } from '@jterrazz/test/services';
119
123
 
120
- export const spec = await cli({
121
- command: resolve(import.meta.dirname, '../../bin/my-cli.sh'),
124
+ export const run = await spec(command(resolve(import.meta.dirname, '../../bin/my-cli.sh')), {
122
125
  root: '../fixtures',
123
126
  });
124
127
 
125
128
  // With infrastructure (CLI that needs a database)
126
- export const spec = await cli({
127
- command: 'my-migrate-tool',
129
+ const db = postgres({ compose: 'db' });
130
+
131
+ export const run = await spec(command('my-migrate-tool'), {
128
132
  root: '../fixtures',
129
133
  services: [db],
130
134
  });
@@ -132,7 +136,7 @@ export const spec = await cli({
132
136
 
133
137
  ## Builder API
134
138
 
135
- Every test follows the same pattern: `spec("label") setup action assertions`.
139
+ Every test follows the same pattern: `run("label") -> setup -> action -> assertions`.
136
140
 
137
141
  ### Setup (cross-mode)
138
142
 
@@ -142,7 +146,6 @@ Every test follows the same pattern: `spec("label") → setup → action → ass
142
146
  | `.seed("file.sql", { service: "name" })` | Load SQL into a specific database |
143
147
  | `.fixture("file")` | Copy `fixtures/file` into the CLI working directory |
144
148
  | `.project("name")` | Copy `fixtures/name/` into a fresh temp dir and run there |
145
- | `.mock("file.json")` | Register mocked external API response (MSW, planned) |
146
149
 
147
150
  ### Actions (one per spec, mutually exclusive)
148
151
 
@@ -164,11 +167,11 @@ Every test follows the same pattern: `spec("label") → setup → action → ass
164
167
  | `.spawn("args", { waitFor, timeout })` | Run long-lived process, resolve on pattern match or timeout |
165
168
  | `.env({ KEY: "value" })` | Set env vars on the child process (`null` unsets, `$WORKDIR` expands to the temp cwd) |
166
169
 
167
- Every CLI spec runs in a **fresh, empty temp directory** by default. `.project("name")` starts from a copy of `fixtures/name/`; `.fixture("file")` seeds specific files into the temp dir. Bare specs (`spec("x").exec("...")`) get an empty dir — ideal for testing scaffolding CLIs that write into their cwd.
170
+ Every CLI spec runs in a **fresh, empty temp directory** by default. `.project("name")` starts from a copy of `fixtures/name/`; `.fixture("file")` seeds specific files into the temp dir.
168
171
 
169
172
  ### Assertions
170
173
 
171
- Result properties are raw values use vitest `expect()` for assertions. Database and response file assertions use custom async methods.
174
+ Result properties are raw values -- use vitest `expect()` for assertions. Database and response file assertions use custom async methods.
172
175
 
173
176
  **Raw values (vitest expect):**
174
177
 
@@ -195,27 +198,25 @@ Result properties are raw values — use vitest `expect()` for assertions. Datab
195
198
  | `await result.directory().toMatchFixture("scaffold", { ignore })` | Pass extra ignore patterns; defaults already skip `.git`, `node_modules`, etc. |
196
199
  | `await result.directory("out").files()` | List all files (recursive, sorted) for ad-hoc assertions |
197
200
 
198
- Run with `JTERRAZZ_TEST_UPDATE=1` (or vitest `-u`) to overwrite fixtures with the current output. Fixtures live at `{test}/expected/{name}/` — same convention as `responses/` for HTTP bodies.
201
+ Run with `JTERRAZZ_TEST_UPDATE=1` (or vitest `-u`) to overwrite fixtures with the current output.
199
202
 
200
203
  **Grep (scoped text matching):**
201
204
 
202
205
  ```typescript
203
- import { grep } from '@jterrazz/test';
204
-
205
- expect(grep(result.stdout, 'unused-var.ts')).toContain('no-unused-vars');
206
- expect(grep(result.stdout, 'valid/sorted.ts')).not.toContain('sort-imports');
206
+ expect(result.grep('unused-var.ts')).toContain('no-unused-vars');
207
+ expect(result.grep('valid/sorted.ts')).not.toContain('sort-imports');
207
208
  ```
208
209
 
209
- `grep(output, pattern)` filters multi-line output to the block matching `pattern`, returning a string for vitest assertions.
210
+ `result.grep(pattern)` filters multi-line output to the block matching `pattern`, returning a string for vitest assertions.
210
211
 
211
212
  **Response (HTTP body):**
212
213
 
213
- | Expression | Description |
214
- | ----------------------------------------------- | --------------------------------------------------------------------------- |
215
- | `result.response.toMatchFile("expected.json")` | Custom compares body to `responses/expected.json`, shows diff on mismatch |
216
- | `expect(result.response.body).toEqual({ ... })` | Raw body object for vitest assertions |
214
+ | Expression | Description |
215
+ | ----------------------------------------------- | ---------------------------------------------------------------------------- |
216
+ | `result.response.toMatchFile("expected.json")` | Custom -- compares body to `responses/expected.json`, shows diff on mismatch |
217
+ | `expect(result.response.body).toEqual({ ... })` | Raw body object for vitest assertions |
217
218
 
218
- **Tables (custom async database queries):**
219
+ **Tables (custom async -- database queries):**
219
220
 
220
221
  | Expression | Description |
221
222
  | ------------------------------------------------------------------------------- | ------------------------------ |
@@ -223,20 +224,29 @@ expect(grep(result.stdout, 'valid/sorted.ts')).not.toContain('sort-imports');
223
224
  | `await result.table("events", { service: "analytics-db" }).toMatch({ ... })` | Assert on a specific database |
224
225
  | `await result.table("users").toBeEmpty()` | Assert database table is empty |
225
226
 
227
+ **Docker (container assertions):**
228
+
229
+ | Expression | Description |
230
+ | ------------------- | ------------------------------- |
231
+ | `runner.docker(id)` | Access a docker container by id |
232
+
226
233
  ## Multi-database support
227
234
 
228
235
  When multiple databases are declared, `seed()` and `result.table()` accept `{ service: "name" }` to target a specific database by its compose name. Without `service`, both default to the first postgres.
229
236
 
230
237
  ```typescript
238
+ import { spec, app } from '@jterrazz/test';
239
+ import { postgres } from '@jterrazz/test/services';
240
+
231
241
  const db = postgres({ compose: "db" });
232
242
  const analyticsDb = postgres({ compose: "analytics-db" });
233
243
 
234
- const spec = await integration({
244
+ const run = await spec(app(() => createApp({ ... })), {
235
245
  services: [db, analyticsDb],
236
- app: () => createApp({ ... }),
246
+ root: '../../',
237
247
  });
238
248
 
239
- const result = await spec("cross-db")
249
+ const result = await run("cross-db")
240
250
  .seed("users.sql")
241
251
  .seed("events.sql", { service: "analytics-db" })
242
252
  .post("/users", "request.json")
@@ -253,7 +263,7 @@ await result.table("events", { service: "analytics-db" }).toMatch({
253
263
  ## Service factories
254
264
 
255
265
  ```typescript
256
- import { postgres, redis } from '@jterrazz/test';
266
+ import { postgres, redis } from '@jterrazz/test/services';
257
267
 
258
268
  const db = postgres({ compose: 'db' });
259
269
  const cache = redis({ compose: 'cache' });
@@ -269,7 +279,7 @@ Service handles read image and environment from `docker/compose.test.yaml`. Afte
269
279
  ## Mocking utilities
270
280
 
271
281
  ```typescript
272
- import { mockOf, mockOfDate } from '@jterrazz/test';
282
+ import { mockOf, mockOfDate } from '@jterrazz/test/mock';
273
283
  ```
274
284
 
275
285
  | Export | Description |
@@ -322,7 +332,7 @@ Every test uses `// Given` and `// Then` comments. Always both, never one withou
322
332
  ```typescript
323
333
  test('creates a user and returns 201', async () => {
324
334
  // Given — two existing users
325
- const result = await spec('creates user')
335
+ const result = await run('creates user')
326
336
  .seed('initial-users.sql')
327
337
  .post('/users', 'new-user.json')
328
338
  .run();
@@ -340,6 +350,6 @@ test('creates a user and returns 201', async () => {
340
350
 
341
351
  ## Requirements
342
352
 
343
- - **Docker** testcontainers for `integration()`, docker compose for `e2e()`
344
- - **vitest** peer dependency
345
- - **hono** optional peer, only needed for `integration()` mode with in-process apps
353
+ - **Docker** -- testcontainers for `spec(app(...))`, docker compose for `spec(stack(...))`
354
+ - **vitest** -- peer dependency
355
+ - **hono** -- optional peer, only needed for `spec(app(...))` mode with in-process apps
package/dist/chunk.cjs ADDED
@@ -0,0 +1,28 @@
1
+ //#region \0rolldown/runtime.js
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
10
+ key = keys[i];
11
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
12
+ get: ((k) => from[k]).bind(null, key),
13
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
14
+ });
15
+ }
16
+ return to;
17
+ };
18
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
19
+ value: mod,
20
+ enumerable: true
21
+ }) : target, mod));
22
+ //#endregion
23
+ Object.defineProperty(exports, "__toESM", {
24
+ enumerable: true,
25
+ get: function() {
26
+ return __toESM;
27
+ }
28
+ });