@jterrazz/test 13.0.1 → 14.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 CHANGED
@@ -32,10 +32,10 @@ import { expect, test } from 'vitest';
32
32
  import { api } from '../api.specification.js';
33
33
 
34
34
  test('creates a user', async () => {
35
- // Given - the complete request from requests/create-user.http
35
+ // Given - the complete request from _requests/create-user.http
36
36
  const result = await api.request('create-user.http');
37
37
 
38
- // Then - status + headers + body from expected/user-created.http; row in db
38
+ // Then - status + headers + body from _expected/user-created.http; row in db
39
39
  expect(result.response).toMatch('user-created.http');
40
40
  await expect(result.table('users')).toMatchRows({
41
41
  columns: ['name'],
@@ -97,7 +97,7 @@ import { specification } from '@jterrazz/test';
97
97
  import { afterAll } from 'vitest';
98
98
 
99
99
  export const { cleanup, website } = await specification.website({
100
- server: { command: 'node specs/fixtures/website-app/server.mjs', ready: '/' },
100
+ server: { command: 'node specs/_fixtures/website-app/server.mjs', ready: '/' },
101
101
  });
102
102
 
103
103
  afterAll(cleanup);
@@ -225,7 +225,7 @@ Tests a rendered website: `.fetch(path)` for a raw HTTP exchange (redirects neve
225
225
 
226
226
  ```typescript
227
227
  export const { website, cleanup } = await specification.website({
228
- server: { command: 'node specs/fixtures/website-app/server.mjs', ready: '/' },
228
+ server: { command: 'node specs/_fixtures/website-app/server.mjs', ready: '/' },
229
229
  });
230
230
 
231
231
  // Raw exchange — status + headers, redirects surface as 3xx
@@ -268,9 +268,9 @@ When `root` is absent, the framework walks up from the specification file to the
268
268
 
269
269
  | Method | Facets | Description |
270
270
  | --------------------------------------- | ------------ | ----------------------------------------------------------------------------------------------------------- |
271
- | `.seed("file.sql", { database? })` | all | Load SQL from `seeds/` — `database` is the record key (mandatory with ≥ 2 databases, forbidden with 1) |
272
- | `.fixture("file")` | cli | Copy the feature-local `fixtures/file` into the working directory |
273
- | `.fixture("$FIXTURES/name/")` | cli | Spread the shared `specs/fixtures/name/` project into the cwd (trailing `/` = contents; layers) |
271
+ | `.seed("file.sql", { database? })` | all | Load SQL from `_seeds/` — `database` is the record key (mandatory with ≥ 2 databases, forbidden with 1) |
272
+ | `.fixture("file")` | cli | Copy the feature-local `_fixtures/file` into the working directory |
273
+ | `.fixture("$FIXTURES/name/")` | cli | Spread the shared `specs/_fixtures/name/` project into the cwd (trailing `/` = contents; layers) |
274
274
  | `.env({ KEY: "value" })` | cli | Set env vars on the child (`null` unsets, `$WORKDIR` expands, calls merge) |
275
275
  | `.headers({ "Accept-Language": "fr" })` | api, website | Set HTTP request headers (merge on top of `.http` file headers, or on the browser context) |
276
276
  | `.intercept(contracts)` | all but cli | Declare the world: a `defineContracts(...)` composite — MSW on api/jobs, the stub backend on website/mobile |
@@ -281,7 +281,7 @@ When `root` is absent, the framework walks up from the specification file to the
281
281
 
282
282
  | Method | Facet | Resolves to | Description |
283
283
  | ------------------------------------------ | ------- | -------------- | ---------------------------------------------------------------------------------------- |
284
- | `.request("create-user.http")` | api | `HttpResult` | Send the COMPLETE request from `requests/<file>` (method, path, headers, raw body) |
284
+ | `.request("create-user.http")` | api | `HttpResult` | Send the COMPLETE request from `_requests/<file>` (method, path, headers, raw body) |
285
285
  | `.get(path)` / `.delete(path)` | api | `HttpResult` | Inline requests for simple cases |
286
286
  | `.post(path, body?)` / `.put(path, body?)` | api | `HttpResult` | Inline body: plain object, JSON-serialized |
287
287
  | `.trigger("name")` | jobs | `BaseResult` | Execute a registered job |
@@ -302,7 +302,7 @@ Accessors are **read-only**; the framework registers subject-typed matchers on v
302
302
  ```typescript
303
303
  // HTTP
304
304
  expect(result.status).toBe(201);
305
- expect(result.response).toMatch('user-created.http'); // expected/<name> — status + header subset + body
305
+ expect(result.response).toMatch('user-created.http'); // _expected/<name> — status + header subset + body
306
306
  expect(result.response.body).toEqual({ error: 'User 999 not found' });
307
307
 
308
308
  // Tables (async — queries the database)
@@ -314,33 +314,33 @@ await expect(result.table('orders', { database: 'db' })).toBeEmpty();
314
314
 
315
315
  // Streams (ANSI stripped by default; .text stays raw)
316
316
  expect(result.stdout).toContain('Build completed');
317
- expect(result.stdout).toMatch('help.txt'); // expected/help.txt — {{token}}-aware
318
- expect(result.json).toMatch('config.json'); // expected/config.json
317
+ expect(result.stdout).toMatch('help.txt'); // _expected/help.txt — {{token}}-aware
318
+ expect(result.json).toMatch('config.json'); // _expected/config.json
319
319
  expect(result.json.value).toMatchObject({ name: 'shoply' });
320
320
 
321
321
  // Files & trees
322
322
  expect(result.file('my-shop/shoply.yaml').content).toContain('name: my-shop');
323
- await expect(result.directory('my-shop')).toMatch('shop-scaffold'); // expected/shop-scaffold/
323
+ await expect(result.directory('my-shop')).toMatch('shop-scaffold'); // _expected/shop-scaffold/
324
324
  await expect(result.filesystem).toMatch('upgraded-shop'); // whole cwd
325
325
 
326
326
  // Containers (docker-aware cli)
327
327
  await expect(result.container('alpha')).toBeRunning();
328
328
  ```
329
329
 
330
- `toMatch` always resolves against `expected/<name>` — every subject, no exceptions (only `.request()` reads `requests/`). The folder is flat: a slash in the name creates a subfolder; the extension is part of the name and required, except for tree snapshots which are directories.
330
+ `toMatch` always resolves against `_expected/<name>` — every subject, no exceptions (only `.request()` reads `_requests/`). The folder is flat: a slash in the name creates a subfolder; the extension is part of the name and required, except for tree snapshots which are directories.
331
331
 
332
332
  **Updating snapshots:** `TEST_UPDATE=1` or `vitest -u`. Update mode writes **tokens**, not values — segments covered by an existing placeholder are preserved, and `{{workdir}}` is substituted automatically.
333
333
 
334
334
  ## Dynamic values — one `{{token}}` grammar
335
335
 
336
- The same vocabulary works in `expected/*.http` (body AND headers), `expected/*.json`, text snapshots, and tree-snapshot file contents — and in code via `match.*`:
336
+ The same vocabulary works in `_expected/*.http` (body AND headers), `_expected/*.json`, text snapshots, and tree-snapshot file contents — and in code via `match.*`:
337
337
 
338
338
  `uuid` `ulid` `iso8601` `date` `time` `duration` `number` `int` `float` `semver` `sha` `hex` `base64` `port` `ip` `url` `email` `path` `workdir` `string` `any`
339
339
 
340
340
  Each token is capturable via `{{type#ref}}`: the first occurrence captures, later occurrences must be equal (scope: one spec). Code-side: `match.ref('order')`, `match.ref('intent', { not: 'order' })`, `match.regex(/…/)`.
341
341
 
342
342
  ```http
343
- ### expected/order-created.http
343
+ ### _expected/order-created.http
344
344
  HTTP/1.1 201 Created
345
345
  Content-Type: application/json
346
346
  Location: /orders/{{uuid#order}}
@@ -436,14 +436,14 @@ specs/<facet>/ # api | jobs | cli | integrations | lint
436
436
  ├── <facet>.specification.ts # runner(s) at the facet ROOT (rule C1)
437
437
  └── <domain>/ # a product command/area — 1..n test files
438
438
  ├── <aspect>.test.ts
439
- ├── seeds/ # *.sql ONLY — database state
440
- ├── requests/ # *.http — inputs: COMPLETE request (method, path, headers, body)
439
+ ├── _seeds/ # *.sql ONLY — database state
440
+ ├── _requests/ # *.http — inputs: COMPLETE request (method, path, headers, body)
441
441
  ├── contracts/ # <name>.contracts.ts facade + <provider>/<name>.ts units + their .response.json / .request.ts data
442
- ├── fixtures/ # domain-local files/dirs copied into the cwd (cli) — shared pool lives at specs/fixtures/
443
- └── expected/ # ALL expected fixtures, FLAT (incl. response *.http) — a slash in the name creates a subfolder
442
+ ├── _fixtures/ # domain-local files/dirs copied into the cwd (cli) — shared pool lives at specs/_fixtures/
443
+ └── _expected/ # ALL expected fixtures, FLAT (incl. response *.http) — a slash in the name creates a subfolder
444
444
  ```
445
445
 
446
- A test with its OWN asset dirs gets its own domain folder; tests without local assets group as sibling `<aspect>.test.ts` files inside a named group folder (the folder follows the assets). `.fixture(path)` is the one verb that copies into the cwd: domain-local (`fixtures/…`) or shared (`$FIXTURES/…` → `specs/fixtures/…`), with rsync trailing-slash semantics and layering. `.seed()` is SQL-only.
446
+ A test with its OWN asset dirs gets its own domain folder; tests without local assets group as sibling `<aspect>.test.ts` files inside a named group folder (the folder follows the assets). `.fixture(path)` is the one verb that copies into the cwd: domain-local (`_fixtures/…`) or shared (`$FIXTURES/…` → `specs/_fixtures/…`), with rsync trailing-slash semantics and layering. `.seed()` is SQL-only.
447
447
 
448
448
  Every test contains `// Given -` and `// Then -` comments (always both; `// When -` only if the action is not obvious — the chain IS the when). User-facing framework env vars: `TEST_MODE` and `TEST_UPDATE` — the only ones you set; the framework also reads vitest's `VITEST_POOL_ID` for per-worker isolation.
449
449