@jterrazz/test 4.0.1 → 5.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
@@ -39,7 +39,7 @@ test("creates a user", async () => {
39
39
  .run();
40
40
 
41
41
  // Then — user created
42
- result.status.toBe(201);
42
+ expect(result.status).toBe(201);
43
43
  await result.table("users").toMatch({
44
44
  columns: ["name"],
45
45
  rows: [["Alice"], ["Bob"]],
@@ -69,11 +69,11 @@ test("builds the project", async () => {
69
69
  const result = await spec("build").project("sample-app").exec("build").run();
70
70
 
71
71
  // Then — ESM output with source maps
72
- result.exitCode.toBe(0);
73
- result.stdout.toContain("Build completed");
74
- result.file("dist/index.js").toExist();
75
- result.file("dist/index.cjs").not.toExist();
76
- result.file("dist/index.js").toContain("Hello");
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
 
@@ -157,50 +157,69 @@ Every test follows the same pattern: `spec("label") → setup → action → ass
157
157
 
158
158
  **CLI:**
159
159
 
160
- | Method | Description |
161
- | -------------------------------------- | ----------------------------------------------------------- |
162
- | `.exec("args")` | Run command (blocking) |
163
- | `.exec(["build", "start"])` | Run commands sequentially in same directory |
164
- | `.spawn("args", { waitFor, timeout })` | Run long-lived process, resolve on pattern match or timeout |
160
+ | Method | Description |
161
+ | -------------------------------------- | ------------------------------------------------------------------------------------- |
162
+ | `.exec("args")` | Run command (blocking) |
163
+ | `.exec(["build", "start"])` | Run commands sequentially in same directory |
164
+ | `.spawn("args", { waitFor, timeout })` | Run long-lived process, resolve on pattern match or timeout |
165
+ | `.env({ KEY: "value" })` | Set env vars on the child process (`null` unsets, `$WORKDIR` expands to the temp cwd) |
165
166
 
166
167
  ### Assertions
167
168
 
168
- Assertions use a scoped API: `result.{scope}.{assertion}`. Database assertions (`result.table()`) are async.
169
-
170
- **HTTP-specific:**
171
-
172
- | Method | Description |
173
- | ------------------------------------------ | -------------------------------------------------- |
174
- | `result.status.toBe(code)` | Assert HTTP status code |
175
- | `result.response.toMatchFile("file.json")` | Assert response body matches `responses/file.json` |
176
-
177
- **CLI-specific:**
178
-
179
- | Method | Description |
180
- | --------------------------------------------------- | -------------------------------------------------- |
181
- | `result.exitCode.toBe(code)` | Assert process exit code |
182
- | `result.stdout.toContain(str)` | Assert stdout contains string |
183
- | `result.stdout.not.toContain(str)` | Assert stdout does not contain string |
184
- | `result.stdout.toContain(str, { near: "ctx" })` | Assert stdout contains string near context |
185
- | `result.stderr.toContain(str)` | Assert stderr contains string |
186
- | `result.stderr.not.toContain(str)` | Assert stderr does not contain string |
187
- | `result.stderr.not.toContain(str, { near: "ctx" })` | Assert stderr does not contain string near context |
188
- | `result.stdout.toMatch(/regex/)` | Assert stdout matches regex |
189
- | `result.stdout.toMatchFile("file.txt")` | Assert stdout matches `expected/file.txt` |
190
- | `result.stderr.toMatchFile("file.txt")` | Assert stderr matches `expected/file.txt` |
191
- | `result.stdout.toBeEmpty()` | Assert stdout is empty |
192
-
193
- **Cross-mode:**
194
-
195
- | Method | Description |
196
- | ------------------------------------------------------------------ | --------------------------------------- |
197
- | `await result.table(name).toMatch({ columns, rows })` | Assert database table contents |
198
- | `await result.table(name, { service }).toMatch({ columns, rows })` | Assert on a specific database |
199
- | `await result.table(name).toBeEmpty()` | Assert database table is empty |
200
- | `result.file(path).toExist()` | Assert file exists in working directory |
201
- | `result.file(path).not.toExist()` | Assert file does not exist |
202
- | `result.file(path).toContain(content)` | Assert file contains string |
203
- | `result.file(path).toMatch(/regex/)` | Assert file content matches regex |
169
+ Result properties are raw values use vitest `expect()` for assertions. Database and response file assertions use custom async methods.
170
+
171
+ **Raw values (vitest expect):**
172
+
173
+ | Expression | Description |
174
+ | -------------------------------------------- | --------------------------- |
175
+ | `expect(result.exitCode).toBe(0)` | CLI exit code |
176
+ | `expect(result.status).toBe(201)` | HTTP status code |
177
+ | `expect(result.stdout).toContain("hello")` | CLI stdout contains string |
178
+ | `expect(result.stderr).not.toContain("err")` | CLI stderr does not contain |
179
+
180
+ **Files (result.file returns {exists, content}):**
181
+
182
+ | Expression | Description |
183
+ | ----------------------------------------------------------------- | --------------------------- |
184
+ | `expect(result.file("dist/index.js").exists).toBe(true)` | Assert file exists |
185
+ | `expect(result.file("dist/index.js").content).toContain("Hello")` | Assert file contains string |
186
+ | `expect(result.file("dist/index.cjs").exists).toBe(false)` | Assert file does not exist |
187
+
188
+ **Directories (CLI scaffolding / codegen output):**
189
+
190
+ | Expression | Description |
191
+ | ----------------------------------------------------------------- | ------------------------------------------------------------------------------ |
192
+ | `await result.directory("out").toMatchFixture("go-api")` | Snapshot the tree against `expected/go-api/`, structured diff on mismatch |
193
+ | `await result.directory().toMatchFixture("scaffold", { ignore })` | Pass extra ignore patterns; defaults already skip `.git`, `node_modules`, etc. |
194
+ | `await result.directory("out").files()` | List all files (recursive, sorted) for ad-hoc assertions |
195
+
196
+ 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.
197
+
198
+ **Grep (scoped text matching):**
199
+
200
+ ```typescript
201
+ import { grep } from "@jterrazz/test";
202
+
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
+ ```
206
+
207
+ `grep(output, pattern)` filters multi-line output to the block matching `pattern`, returning a string for vitest assertions.
208
+
209
+ **Response (HTTP body):**
210
+
211
+ | Expression | Description |
212
+ | ----------------------------------------------- | --------------------------------------------------------------------------- |
213
+ | `result.response.toMatchFile("expected.json")` | Custom — compares body to `responses/expected.json`, shows diff on mismatch |
214
+ | `expect(result.response.body).toEqual({ ... })` | Raw body object for vitest assertions |
215
+
216
+ **Tables (custom async — database queries):**
217
+
218
+ | Expression | Description |
219
+ | ------------------------------------------------------------------------------- | ------------------------------ |
220
+ | `await result.table("users").toMatch({ columns: ["name"], rows: [["Alice"]] })` | Assert database table contents |
221
+ | `await result.table("events", { service: "analytics-db" }).toMatch({ ... })` | Assert on a specific database |
222
+ | `await result.table("users").toBeEmpty()` | Assert database table is empty |
204
223
 
205
224
  ## Multi-database support
206
225
 
@@ -221,6 +240,7 @@ const result = await spec("cross-db")
221
240
  .post("/users", "request.json")
222
241
  .run();
223
242
 
243
+ expect(result.status).toBe(201);
224
244
  await result.table("users").toMatch({ columns: ["name"], rows: [["Alice"]] });
225
245
  await result.table("events", { service: "analytics-db" }).toMatch({
226
246
  columns: ["type"],
@@ -306,7 +326,7 @@ test("creates a user and returns 201", async () => {
306
326
  .run();
307
327
 
308
328
  // Then — user created with all three in table
309
- result.status.toBe(201);
329
+ expect(result.status).toBe(201);
310
330
  await result.table("users").toMatch({
311
331
  columns: ["name"],
312
332
  rows: [["Alice"], ["Bob"], ["Charlie"]],