@jterrazz/test 5.1.0 → 5.3.1

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
@@ -12,16 +12,16 @@ npm install -D @jterrazz/test vitest
12
12
 
13
13
  ```typescript
14
14
  // tests/setup/integration.specification.ts
15
- import { afterAll } from "vitest";
16
- import { integration, postgres } from "@jterrazz/test";
17
- import { createApp } from "../../src/app.js";
15
+ import { afterAll } from 'vitest';
16
+ import { integration, postgres } from '@jterrazz/test';
17
+ import { createApp } from '../../src/app.js';
18
18
 
19
- const db = postgres({ compose: "db" });
19
+ const db = postgres({ compose: 'db' });
20
20
 
21
21
  export const spec = await integration({
22
- services: [db],
23
- app: () => createApp({ databaseUrl: db.connectionString }),
24
- root: "../../",
22
+ services: [db],
23
+ app: () => createApp({ databaseUrl: db.connectionString }),
24
+ root: '../../',
25
25
  });
26
26
 
27
27
  afterAll(() => spec.cleanup());
@@ -29,21 +29,21 @@ afterAll(() => spec.cleanup());
29
29
 
30
30
  ```typescript
31
31
  // tests/e2e/users/users.e2e.test.ts
32
- import { spec } from "../../setup/integration.specification.js";
33
-
34
- test("creates a user", async () => {
35
- // Given — one existing user
36
- const result = await spec("creates user")
37
- .seed("initial-users.sql")
38
- .post("/users", "new-user.json")
39
- .run();
40
-
41
- // Then — user created
42
- expect(result.status).toBe(201);
43
- await result.table("users").toMatch({
44
- columns: ["name"],
45
- rows: [["Alice"], ["Bob"]],
46
- });
32
+ import { spec } from '../../setup/integration.specification.js';
33
+
34
+ test('creates a user', async () => {
35
+ // Given — one existing user
36
+ const result = await spec('creates user')
37
+ .seed('initial-users.sql')
38
+ .post('/users', 'new-user.json')
39
+ .run();
40
+
41
+ // Then — user created
42
+ expect(result.status).toBe(201);
43
+ await result.table('users').toMatch({
44
+ columns: ['name'],
45
+ rows: [['Alice'], ['Bob']],
46
+ });
47
47
  });
48
48
  ```
49
49
 
@@ -51,29 +51,29 @@ test("creates a user", async () => {
51
51
 
52
52
  ```typescript
53
53
  // tests/setup/cli.specification.ts
54
- import { resolve } from "node:path";
55
- import { cli } from "@jterrazz/test";
54
+ import { resolve } from 'node:path';
55
+ import { cli } from '@jterrazz/test';
56
56
 
57
57
  export const spec = await cli({
58
- command: resolve(import.meta.dirname, "../../bin/my-cli.sh"),
59
- root: "../fixtures",
58
+ command: resolve(import.meta.dirname, '../../bin/my-cli.sh'),
59
+ root: '../fixtures',
60
60
  });
61
61
  ```
62
62
 
63
63
  ```typescript
64
64
  // tests/e2e/build/build.e2e.test.ts
65
- import { spec } from "../../setup/cli.specification.js";
66
-
67
- test("builds the project", async () => {
68
- // Given — sample app project
69
- const result = await spec("build").project("sample-app").exec("build").run();
70
-
71
- // Then — ESM output with source maps
72
- expect(result.exitCode).toBe(0);
73
- expect(result.stdout).toContain("Build completed");
74
- expect(result.file("dist/index.js").exists).toBe(true);
75
- expect(result.file("dist/index.cjs").exists).toBe(false);
76
- expect(result.file("dist/index.js").content).toContain("Hello");
65
+ import { spec } from '../../setup/cli.specification.js';
66
+
67
+ test('builds the project', async () => {
68
+ // Given — sample app project
69
+ const result = await spec('build').project('sample-app').exec('build').run();
70
+
71
+ // Then — ESM output with source maps
72
+ expect(result.exitCode).toBe(0);
73
+ expect(result.stdout).toContain('Build completed');
74
+ expect(result.file('dist/index.js').exists).toBe(true);
75
+ expect(result.file('dist/index.cjs').exists).toBe(false);
76
+ expect(result.file('dist/index.js').content).toContain('Hello');
77
77
  });
78
78
  ```
79
79
 
@@ -86,15 +86,15 @@ Three modes, same builder API. Each handles infrastructure and cleanup automatic
86
86
  Starts real containers via testcontainers. App runs in-process (Hono). Fastest feedback loop.
87
87
 
88
88
  ```typescript
89
- import { integration, postgres, redis } from "@jterrazz/test";
89
+ import { integration, postgres, redis } from '@jterrazz/test';
90
90
 
91
- const db = postgres({ compose: "db" });
92
- const cache = redis({ compose: "cache" });
91
+ const db = postgres({ compose: 'db' });
92
+ const cache = redis({ compose: 'cache' });
93
93
 
94
94
  export const spec = await integration({
95
- services: [db, cache],
96
- app: () => createApp({ databaseUrl: db.connectionString }),
97
- root: "../../",
95
+ services: [db, cache],
96
+ app: () => createApp({ databaseUrl: db.connectionString }),
97
+ root: '../../',
98
98
  });
99
99
  ```
100
100
 
@@ -103,10 +103,10 @@ export const spec = await integration({
103
103
  Starts the full `docker/compose.test.yaml` stack. App URL and databases auto-detected.
104
104
 
105
105
  ```typescript
106
- import { e2e } from "@jterrazz/test";
106
+ import { e2e } from '@jterrazz/test';
107
107
 
108
108
  export const spec = await e2e({
109
- root: "../../",
109
+ root: '../../',
110
110
  });
111
111
  ```
112
112
 
@@ -115,18 +115,18 @@ export const spec = await e2e({
115
115
  Runs CLI commands against fixture projects in temp directories. Optionally starts infrastructure.
116
116
 
117
117
  ```typescript
118
- import { cli } from "@jterrazz/test";
118
+ import { cli } from '@jterrazz/test';
119
119
 
120
120
  export const spec = await cli({
121
- command: resolve(import.meta.dirname, "../../bin/my-cli.sh"),
122
- root: "../fixtures",
121
+ command: resolve(import.meta.dirname, '../../bin/my-cli.sh'),
122
+ root: '../fixtures',
123
123
  });
124
124
 
125
125
  // With infrastructure (CLI that needs a database)
126
126
  export const spec = await cli({
127
- command: "my-migrate-tool",
128
- root: "../fixtures",
129
- services: [db],
127
+ command: 'my-migrate-tool',
128
+ root: '../fixtures',
129
+ services: [db],
130
130
  });
131
131
  ```
132
132
 
@@ -136,13 +136,13 @@ Every test follows the same pattern: `spec("label") → setup → action → ass
136
136
 
137
137
  ### Setup (cross-mode)
138
138
 
139
- | Method | Description |
140
- | ---------------------------------------- | -------------------------------------------------------- |
141
- | `.seed("file.sql")` | Load SQL from `seeds/file.sql` into the default database |
142
- | `.seed("file.sql", { service: "name" })` | Load SQL into a specific database |
143
- | `.fixture("file")` | Copy `fixtures/file` into the CLI working directory |
144
- | `.project("name")` | Use `fixtures/name/` as the CLI working directory |
145
- | `.mock("file.json")` | Register mocked external API response (MSW, planned) |
139
+ | Method | Description |
140
+ | ---------------------------------------- | --------------------------------------------------------- |
141
+ | `.seed("file.sql")` | Load SQL from `seeds/file.sql` into the default database |
142
+ | `.seed("file.sql", { service: "name" })` | Load SQL into a specific database |
143
+ | `.fixture("file")` | Copy `fixtures/file` into the CLI working directory |
144
+ | `.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
146
 
147
147
  ### Actions (one per spec, mutually exclusive)
148
148
 
@@ -164,6 +164,8 @@ Every test follows the same pattern: `spec("label") → setup → action → ass
164
164
  | `.spawn("args", { waitFor, timeout })` | Run long-lived process, resolve on pattern match or timeout |
165
165
  | `.env({ KEY: "value" })` | Set env vars on the child process (`null` unsets, `$WORKDIR` expands to the temp cwd) |
166
166
 
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.
168
+
167
169
  ### Assertions
168
170
 
169
171
  Result properties are raw values — use vitest `expect()` for assertions. Database and response file assertions use custom async methods.
@@ -198,10 +200,10 @@ Run with `JTERRAZZ_TEST_UPDATE=1` (or vitest `-u`) to overwrite fixtures with th
198
200
  **Grep (scoped text matching):**
199
201
 
200
202
  ```typescript
201
- import { grep } from "@jterrazz/test";
203
+ import { grep } from '@jterrazz/test';
202
204
 
203
- expect(grep(result.stdout, "unused-var.ts")).toContain("no-unused-vars");
204
- expect(grep(result.stdout, "valid/sorted.ts")).not.toContain("sort-imports");
205
+ expect(grep(result.stdout, 'unused-var.ts')).toContain('no-unused-vars');
206
+ expect(grep(result.stdout, 'valid/sorted.ts')).not.toContain('sort-imports');
205
207
  ```
206
208
 
207
209
  `grep(output, pattern)` filters multi-line output to the block matching `pattern`, returning a string for vitest assertions.
@@ -251,10 +253,10 @@ await result.table("events", { service: "analytics-db" }).toMatch({
251
253
  ## Service factories
252
254
 
253
255
  ```typescript
254
- import { postgres, redis } from "@jterrazz/test";
256
+ import { postgres, redis } from '@jterrazz/test';
255
257
 
256
- const db = postgres({ compose: "db" });
257
- const cache = redis({ compose: "cache" });
258
+ const db = postgres({ compose: 'db' });
259
+ const cache = redis({ compose: 'cache' });
258
260
  ```
259
261
 
260
262
  Service handles read image and environment from `docker/compose.test.yaml`. After the runner starts, `.connectionString` is populated from the running container.
@@ -267,7 +269,7 @@ Service handles read image and environment from `docker/compose.test.yaml`. Afte
267
269
  ## Mocking utilities
268
270
 
269
271
  ```typescript
270
- import { mockOf, mockOfDate } from "@jterrazz/test";
272
+ import { mockOf, mockOfDate } from '@jterrazz/test';
271
273
  ```
272
274
 
273
275
  | Export | Description |
@@ -318,19 +320,19 @@ tests/
318
320
  Every test uses `// Given` and `// Then` comments. Always both, never one without the other.
319
321
 
320
322
  ```typescript
321
- test("creates a user and returns 201", async () => {
322
- // Given — two existing users
323
- const result = await spec("creates user")
324
- .seed("initial-users.sql")
325
- .post("/users", "new-user.json")
326
- .run();
327
-
328
- // Then — user created with all three in table
329
- expect(result.status).toBe(201);
330
- await result.table("users").toMatch({
331
- columns: ["name"],
332
- rows: [["Alice"], ["Bob"], ["Charlie"]],
333
- });
323
+ test('creates a user and returns 201', async () => {
324
+ // Given — two existing users
325
+ const result = await spec('creates user')
326
+ .seed('initial-users.sql')
327
+ .post('/users', 'new-user.json')
328
+ .run();
329
+
330
+ // Then — user created with all three in table
331
+ expect(result.status).toBe(201);
332
+ await result.table('users').toMatch({
333
+ columns: ['name'],
334
+ rows: [['Alice'], ['Bob'], ['Charlie']],
335
+ });
334
336
  });
335
337
  ```
336
338