@specific.dev/spectest 0.12.0 → 0.13.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@specific.dev/spectest",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "description": "Spectest SDK for defining test environments in TypeScript.",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
package/src/index.ts CHANGED
@@ -1350,6 +1350,8 @@ interface Matchers {
1350
1350
  toBeFalsy(): void;
1351
1351
  toBeGreaterThan(n: number): void;
1352
1352
  toBeLessThan(n: number): void;
1353
+ toBeGreaterThanOrEqual(n: number): void;
1354
+ toBeLessThanOrEqual(n: number): void;
1353
1355
  toContain(expected: unknown): void;
1354
1356
  toMatch(re: RegExp): void;
1355
1357
  toHaveLength(n: number): void;
@@ -1359,12 +1361,19 @@ export interface Expectation extends Matchers {
1359
1361
  not: Matchers;
1360
1362
  }
1361
1363
 
1362
- export function expect(actual: Provenanced): Expectation {
1363
- // The parameter is typed to the {@link Provenanced} family so a raw value
1364
- // (`expect(res.status === 200)`, `expect(2 + 2)`) is a *compile error* — every
1365
- // assertion that reaches the timeline this way carries a provenance link back
1366
- // to the op that produced it. Assert on a genuinely raw value with
1367
- // `expectRaw(message, value)` instead.
1364
+ export function expect(actual: Provenanced, message?: string): Expectation {
1365
+ // The first parameter is typed to the {@link Provenanced} family so a raw
1366
+ // value (`expect(res.status === 200)`, `expect(2 + 2)`) is a *compile error* —
1367
+ // every assertion that reaches the timeline this way carries a provenance link
1368
+ // back to the op that produced it. Assert on a genuinely raw value with
1369
+ // `expectRaw(value, message)` instead.
1370
+ //
1371
+ // `message` is an **optional** human label for the assertion. The UI already
1372
+ // renders the target, matcher, and expected value, so a message that just
1373
+ // restates them is noise — supply one *only* when the check's intent isn't
1374
+ // obvious from those alone (e.g.
1375
+ // `expect(res.status, "blocked once the rate limit trips").toBe(429)`). When
1376
+ // present it leads the assertion's summary, the same way `expectRaw`'s does.
1368
1377
  //
1369
1378
  // A `null`/`undefined` read off a wrapped op result reaches here untagged
1370
1379
  // (a symbol can't ride on nullish). `adoptNullishTag` recovers the tag from
@@ -1372,20 +1381,22 @@ export function expect(actual: Provenanced): Expectation {
1372
1381
  // `expect(dep.status.readyReplicas).toBeFalsy()` nests under its op just like
1373
1382
  // a non-nullish read. Done once here (not in `buildMatchers`) so the `.not`
1374
1383
  // re-pass reuses the same tagged holder instead of re-consuming the note.
1375
- return buildMatchers(adoptNullishTag(actual), false);
1384
+ return buildMatchers(adoptNullishTag(actual), false, message);
1376
1385
  }
1377
1386
 
1378
1387
  /**
1379
1388
  * Assert on a value with **no provenance** — a computed number, a raw
1380
1389
  * WebSocket frame, anything that didn't flow from a recorded op. `message` is
1381
- * required and reads as the natural follow-on to "assert …" (e.g.
1382
- * `expectRaw("id matches the generated value", id)`); it renders as the
1383
- * assertion's label in the CLI/dashboard ("ASSERT <message>") since a raw
1384
- * assertion has no op to nest under. Prefer `expect(...)` whenever the value
1385
- * carries provenance — only reach for this when the type gate would (rightly)
1386
- * reject the value.
1390
+ * required (it's the second argument) and reads as the natural follow-on to
1391
+ * "assert …" (e.g. `expectRaw(id, "id matches the generated value")`); it
1392
+ * renders as the assertion's label in the CLI/dashboard ("ASSERT <message>")
1393
+ * since a raw assertion has no op to nest under. Prefer `expect(...)` whenever
1394
+ * the value carries provenance — only reach for this when the type gate would
1395
+ * (rightly) reject the value. (`expect`'s own `message` is optional; here it is
1396
+ * mandatory, since the label is the only human-meaningful summary a raw
1397
+ * assertion has.)
1387
1398
  */
1388
- export function expectRaw(message: string, actual: unknown): Expectation {
1399
+ export function expectRaw(actual: unknown, message: string): Expectation {
1389
1400
  // Force the raw form so no stray tag is read even if a wrapped value is
1390
1401
  // passed: an `expectRaw` assertion is *deliberately* unlinked, rendered at
1391
1402
  // top level under its own message rather than nested beneath an op.
@@ -1425,7 +1436,7 @@ function buildCore(
1425
1436
  cond: boolean,
1426
1437
  msg: string,
1427
1438
  expectedFor?: unknown,
1428
- opts?: { actual?: unknown; pathSuffix?: string },
1439
+ opts?: { actual?: unknown },
1429
1440
  ): void => {
1430
1441
  // A failed upstream transform short-circuits every matcher to a failure —
1431
1442
  // there is no meaningful value to match, and negation can't rescue a value
@@ -1455,9 +1466,7 @@ function buildCore(
1455
1466
  error: passed ? undefined : msg,
1456
1467
  message,
1457
1468
  sourceSeq: tag?.sourceSeq,
1458
- path: tag
1459
- ? [...tag.path, ...(opts?.pathSuffix ? [opts.pathSuffix] : [])]
1460
- : undefined,
1469
+ path: tag ? [...tag.path] : undefined,
1461
1470
  });
1462
1471
  if (!passed) throw new ExpectationError(msg);
1463
1472
  };
@@ -1516,6 +1525,22 @@ function buildCore(
1516
1525
  n,
1517
1526
  );
1518
1527
  },
1528
+ toBeGreaterThanOrEqual(n) {
1529
+ run(
1530
+ "toBeGreaterThanOrEqual",
1531
+ typeof actual === "number" && actual >= n,
1532
+ `expected ${fmt(actual)}${negated ? " not" : ""} to be >= ${n}`,
1533
+ n,
1534
+ );
1535
+ },
1536
+ toBeLessThanOrEqual(n) {
1537
+ run(
1538
+ "toBeLessThanOrEqual",
1539
+ typeof actual === "number" && actual <= n,
1540
+ `expected ${fmt(actual)}${negated ? " not" : ""} to be <= ${n}`,
1541
+ n,
1542
+ );
1543
+ },
1519
1544
  toContain(expected) {
1520
1545
  const exp = readRaw(expected);
1521
1546
  let contained = false;
@@ -1560,13 +1585,19 @@ function buildCore(
1560
1585
  typeof (actual as { length?: unknown }).length === "number"
1561
1586
  ? ((actual as { length: number }).length as number)
1562
1587
  : undefined;
1588
+ // The provenance `path` stays the container's own path — `.length` is
1589
+ // the matcher's internal read, not a property access the author wrote,
1590
+ // so it doesn't belong in the path (recording it produced a redundant
1591
+ // "length toHaveLength N" in the UI, since the matcher name already says
1592
+ // "length"). `actual` is still the length number: that's what's worth
1593
+ // showing on failure, and the `toHaveLength` matcher disambiguates it.
1563
1594
  run(
1564
1595
  "toHaveLength",
1565
1596
  len === n,
1566
1597
  `expected ${fmt(actual)}${negated ? " not" : ""} to have length ${n}` +
1567
1598
  (len === undefined ? " (value has no length)" : ` (got ${len})`),
1568
1599
  n,
1569
- { actual: len, pathSuffix: "length" },
1600
+ { actual: len },
1570
1601
  );
1571
1602
  },
1572
1603
  get not(): Matchers {
package/src/inspect.ts CHANGED
@@ -482,7 +482,7 @@ export interface Carrier<T> {
482
482
  * tag, but `adoptNullishTag` recovers its provenance at runtime, so
483
483
  * `expect(rows[0]?.text)` and `expect(dep.status.readyReplicas)` stay on
484
484
  * `expect`. To assert on a value with no provenance (a computed number, a raw
485
- * WebSocket frame), use `expectRaw(message, value)` instead.
485
+ * WebSocket frame), use `expectRaw(value, message)` instead.
486
486
  */
487
487
  export type Provenanced = { unwrap(): unknown } | null | undefined;
488
488
 
package/src/recorder.ts CHANGED
@@ -84,11 +84,13 @@ export interface AssertionEvent extends BaseEvent {
84
84
  /** Failure message produced by the matcher (only when `passed` is false). */
85
85
  error?: string;
86
86
  /**
87
- * Author-supplied label for a raw assertion made via
88
- * `expectRaw(message, …)`. Renders as the assertion's summary
89
- * ("ASSERT <message>") and serves as its diff-alignment key, since a raw
90
- * assertion has no provenance `path`. Absent for provenance-linked
91
- * `expect(...)` assertions.
87
+ * Author-supplied label for the assertion. **Required** for a raw assertion
88
+ * (`expectRaw(value, message)`), where it renders as the summary
89
+ * ("ASSERT <message>") and serves as the diff-alignment key since a raw
90
+ * assertion has no provenance `path`. **Optional** for a provenance-linked
91
+ * `expect(value, message)`, where it leads the summary as a clarifying note
92
+ * alongside the rendered matcher/target. Absent when `expect(...)` was called
93
+ * without a message.
92
94
  */
93
95
  message?: string;
94
96
  /**