@orkestrel/test 0.0.12 → 0.0.13

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
@@ -2,15 +2,15 @@
2
2
 
3
3
  The test helpers the `@orkestrel` fleet kept rewriting, published once. A call recorder that is a
4
4
  real callback rather than a spy. A real host delay. A throw-to-value converter and a presence
5
- narrower, so `!` and `as` stay banned in tests. Two async collectors and a JSON copier. A frozen
6
- hostile-value corpus for proving guards are total. A cleanup list that gives every owned resource
7
- back, newest first. A scratch directory the test owns and destroys, a loopback port for a server the
8
- test built, and a symlink-refusing source-file walker. And the browser journey layer, which drives
9
- a real interface by role and accessible name through the installed Vitest provider. A helper ships
10
- here only when enough packages had already written their own; the guide's
5
+ narrower, so `!` and `as` stay banned in tests. An async-iterable collector, a stream collector, and
6
+ a JSON copier. A frozen hostile-value corpus for proving guards are total. A cleanup list that gives
7
+ every owned resource back, newest first. A scratch directory the test owns and destroys, a loopback
8
+ port for a server the test built, and a symlink-refusing source-file walker. And the browser journey
9
+ layer, which drives a real interface by role and accessible name through the installed Vitest
10
+ provider. A helper ships here only when enough packages had already written their own; the guide's
11
11
  [Limits](guides/test.md#limits) section states that rule, what it excluded, and the one door the
12
- journey layer came through instead. Add it as a devDependency; nothing here runs in production
13
- code. Part of the `@orkestrel` line.
12
+ journey layer came through instead. Add it as a devDependency; nothing here runs in production code.
13
+ Part of the `@orkestrel` line.
14
14
 
15
15
  It has **zero runtime dependencies**, and no exported signature names an `@orkestrel/*` type. Both
16
16
  rules exist for one reason: a test helper hands its types straight into the consumer's assertions,
@@ -73,21 +73,27 @@ once, in its own setup, because registering it here would take a runtime depende
73
73
  runner. A handler that throws does not stop the run: one failure is rethrown by identity, so the
74
74
  test can assert on the value it threw, and several arrive as an `AggregateError` in run order.
75
75
 
76
- The rest of core is `collect` (drains an async iterable), `collectStream` (drains a
77
- `ReadableStream`), `roundTripJSON` (copies any value `JSONSafe` accepts, including an
78
- interface-typed one, and throws rather than turning a non-finite number into `null`), `resolveRoot`
79
- (the directory above the calling module, from `import.meta`), and `createHostileValues` (a frozen
80
- array of fresh values that each make a naive reader throw). Beside them sits the statechart
81
- contract the fleet repeats: `StateTransition` and `StateScenario` for one row of a transition table,
82
- `executeScenario` and `executeScenarios` to walk it, and `STATECHART_ATTRIBUTES` and
83
- `STATECHART_STATUSES` for the harness a browser workspace renders that table in.
76
+ Core also carries the wait family (`waitForCondition`, `retryUntil`, `waitForEvent`, `waitForAbort`,
77
+ and the leaves they share), the recorder map and the abort-signal instrument, the unchecked
78
+ boundary, the header flattener, and the JSON Lines decoder the guide's
79
+ [Surface](guides/test.md#surface) section carries every export. Among them are `collect` (drains an
80
+ async iterable), `collectStream` (drains a `ReadableStream`), `roundTripJSON` (copies any value
81
+ `JSONSafe` accepts, including an interface-typed one, and throws rather than turning a non-finite
82
+ number into `null`), `resolveRoot` (the directory above the calling module, from `import.meta`), and
83
+ `createHostileValues` (a frozen array of fresh values that each make a naive read throw or violate a
84
+ naive structural assumption). Beside them sits the statechart contract the fleet repeats:
85
+ `StateTransition` and `StateScenario` for one row of a transition table, `executeScenario` and
86
+ `executeScenarios` to walk it, and `STATECHART_ATTRIBUTES` and `STATECHART_STATUSES` for the harness
87
+ a browser workspace renders that table in.
84
88
 
85
89
  The server face adds `readInventory`, which reads a checkout into a map of root-relative path to
86
90
  file text that a parity suite can assert against, and `createLoopback`, which binds a server the
87
- test built to an ephemeral loopback port. Behind `readInventory` and `createScratch` sit three pure
88
- leaves: `resolveContained`, the lexical check both refuse escapes with; `isExcluded`, the exclusion
89
- rule the walk applies; and `matchesIdentity`, the comparison `destroy()` makes before it removes
90
- anything.
91
+ test built to an ephemeral loopback port. Behind them sit the pure leaves the server face also
92
+ exports on its own: `readInventory` refuses an escape with `resolveContained` and prunes with
93
+ `isExcluded`, while `createScratch` refuses one with `requireContained`, the throwing form, and
94
+ reads and compares its allocation with `readIdentity` and `matchesIdentity` before `destroy()`
95
+ removes anything. `createLink` and `removeTree` do the writing under it, and both decide on the
96
+ errno `readErrorCode` reads off an unknown throw.
91
97
 
92
98
  ```ts
93
99
  import { resolveRoot } from '@orkestrel/test'
@@ -129,21 +135,22 @@ Object.keys(readInventory(root, ['src'], { extensions: ['.ts'], exclude: ['src/s
129
135
  // 'src/core/validators.ts']
130
136
  ```
131
137
 
132
- Keys are root-relative and `/`-separated whatever the host separator is, though this package's own
133
- suite runs on POSIX, where that conversion is an identity, so it proves the key shape and not the
134
- conversion. Keys are inserted in sorted order, and a plain object reads that order back for every
135
- key that is not integer-like.
138
+ Keys are root-relative and `/`-separated whatever the host separator is. The suite gates that proof
139
+ on a runtime reading of `node:path` rather than on a platform name, so it runs only where the host
140
+ separator differs from `/`. Keys are inserted in sorted order, and a plain object reads that order
141
+ back for every key that is not integer-like.
136
142
 
137
- Two boundaries are worth stating up front, because the two filesystem helpers promise different
138
- things. `createScratch` allocates its own directory at POSIX mode `0700` — under the host temporary
143
+ `createScratch` and `readInventory` promise different things, and each boundary is worth stating up
144
+ front. `createScratch` allocates its own directory at POSIX mode `0700` — under the host temporary
139
145
  directory, or under a `parent` you name — and refuses a path that lexically escapes it. The suite
140
- asserts those bits on POSIX and proves nothing about a host that emulates them. The mode keeps
141
- another uid out, and neither a sibling test worker nor the code under test is another uid. It does
142
- not walk segments for symbolic links. A link inside its own allocation was created by the test
143
- process, by the code the test drives, or by this package's own `link` handing that code
144
- `scratch.path` is the ordinary use of this helper and a contained path reaches outside the
145
- allocation through one. The guide's [traversal](guides/test.md#traversal) section states what each
146
- member does with a link it meets. Naming a `parent` inside a package tree costs one more thing:
146
+ gates that assertion on `supportsMode`, the shipped probe that reads whether permission bits
147
+ round-trip on this host, so it runs only where they do and claims nothing about a host that emulates
148
+ them. The mode keeps another uid out, and neither a sibling test worker nor the code under test is
149
+ another uid. It does not walk segments for symbolic links. A link inside its own allocation was
150
+ created by the test process, by the code the test drives, or by this package's own `link` — handing
151
+ that code `scratch.path` is the ordinary use of this helper — and a contained path reaches outside
152
+ the allocation through one. The guide's [traversal](guides/test.md#traversal) section states what
153
+ each member does with a link it meets. Naming a `parent` inside a package tree costs one more thing:
147
154
  while the allocation exists, everything that walks that tree sees it. `destroy()` is unaffected by
148
155
  where the allocation sits, because it matches the allocation's identity rather than its path. One
149
156
  field of that identity is the host's to supply: where a host reports no real creation time, libuv
@@ -203,8 +210,8 @@ rule deciding what ships and what stays in the package that owns it — see
203
210
 
204
211
  ## Package
205
212
 
206
- Published as three typed entry points per the `exports` field in `package.json`: `@orkestrel/test`
207
- for the host-independent core, `@orkestrel/test/browser` for the journey layer, and
213
+ Published as typed entry points per the `exports` field in `package.json`: `@orkestrel/test` for the
214
+ host-independent core, `@orkestrel/test/browser` for the journey layer, and
208
215
  `@orkestrel/test/server` for the Node helpers. Core and server ship ESM and CommonJS; the browser
209
216
  face ships ESM only, because `vitest/browser` is an ES-only module.
210
217
 
@@ -747,7 +747,7 @@ export declare const IMPLICIT_ROLES: Readonly<Record<string, string>>;
747
747
  export declare function isOutsideViewport(rectangle: DOMRectReadOnly): boolean;
748
748
 
749
749
  /**
750
- * Determines whether a person can click one element where it currently sits.
750
+ * Determines whether a person can click one element where it sits.
751
751
  *
752
752
  * @param element - The element to judge.
753
753
  * @returns True if the element is connected, visible, laid out with a non-zero box, in the
@@ -1063,19 +1063,6 @@ export declare interface PortfolioOptions {
1063
1063
  readonly enabled?: boolean;
1064
1064
  }
1065
1065
 
1066
- /**
1067
- * Presses a browser-keyboard sequence using Vitest's installed user-event syntax.
1068
- *
1069
- * @param keys - The keys or key descriptors to press.
1070
- * @returns A promise resolving after the sequence completes.
1071
- *
1072
- * @example
1073
- * ```ts
1074
- * await pressKeys('{ArrowRight}{Enter}')
1075
- * ```
1076
- */
1077
- export declare function pressKeys(keys: string): Promise<void>;
1078
-
1079
1066
  /**
1080
1067
  * Resolves the opaque color standing behind one element.
1081
1068
  *
@@ -1194,7 +1181,7 @@ export declare function readClasses(root: ParentNode): ReadonlySet<string>;
1194
1181
  export declare function readContrast(element: Element, floor?: Color): number;
1195
1182
 
1196
1183
  /**
1197
- * Reads the rendered text of the element that currently holds focus.
1184
+ * Reads the rendered text of the element that holds focus.
1198
1185
  *
1199
1186
  * @returns The focused HTML element's trimmed rendered text, including an empty string, or
1200
1187
  * `undefined` when focus rests on a non-HTML element. When nothing holds focus, the browser
@@ -1357,9 +1344,9 @@ export declare function readPixels(element: Element, property: string): number;
1357
1344
  *
1358
1345
  * @remarks
1359
1346
  * This reads and never acts. Focus arrives through the published verbs — `traverseAccessible`,
1360
- * `pressKeys`, a real click — and this measures what the browser painted once it landed. A control
1361
- * that is not matching `:focus-visible` when the call is made reports nothing, because no
1362
- * measurement taken then would be about focus.
1347
+ * `userEvent.keyboard` from `vitest/browser`, a real click — and this measures what the browser
1348
+ * painted once it landed. A control that is not matching `:focus-visible` when the call is made
1349
+ * reports nothing, because no measurement taken then would be about focus.
1363
1350
  *
1364
1351
  * Some controls are two elements: one that takes the focus and one a reader can see. A hidden radio
1365
1352
  * beside the label that carries every pixel of its chrome is the case `worn` exists for, so a
@@ -1632,32 +1619,47 @@ export declare function releasePane(): Promise<void>;
1632
1619
  export declare function removeDatabase(name: string): Promise<void>;
1633
1620
 
1634
1621
  /**
1635
- * Renders one fixture into the document, from trusted markup or from a tag and its classes.
1622
+ * Renders one fixture into the document from trusted markup.
1636
1623
  *
1637
- * @param first - The fixture markup, or the HTML tag name when `second` is present.
1638
- * @param second - The class list when `first` supplies the tag name.
1639
- * @returns The attached container for the markup form, and the attached element itself for the tag
1640
- * form.
1624
+ * @param markup - The fixture markup to parse.
1625
+ * @returns The attached container holding the fixture's own nodes.
1641
1626
  *
1642
1627
  * @remarks
1643
1628
  * The class list is required in the tag form, which is what keeps the two forms apart: a
1644
- * one-argument call is always markup. A tag with no classes is `mount(build(tag))`.
1629
+ * one-argument call is always markup.
1645
1630
  *
1646
- * The markup form parses `first` into a fresh container and returns that container, so the fixture's
1647
- * own nodes are its children. The tag form returns the element itself, typed as exactly that tag.
1648
- * Both attach to `document.body` and neither records anything, so removal is the caller's, exactly
1649
- * as it is for {@link mount}.
1631
+ * This form parses `markup` into a fresh container and returns that container, so the fixture's own
1632
+ * nodes are its children. It attaches to `document.body` and records nothing, so removal is the
1633
+ * caller's, exactly as it is for {@link mount}.
1650
1634
  *
1651
1635
  * @example
1652
1636
  * ```ts
1653
1637
  * const container = render('<button type="button">Save</button>')
1654
- * const panel = render('section', 'surface muted')
1655
1638
  * container.remove()
1656
- * panel.remove()
1657
1639
  * ```
1658
1640
  */
1659
1641
  export declare function render(markup: string): HTMLDivElement;
1660
1642
 
1643
+ /**
1644
+ * Renders one fixture into the document from a tag name and its class list.
1645
+ *
1646
+ * @param tag - The HTML tag name to create.
1647
+ * @param classes - The class list to place on the created element.
1648
+ * @returns The attached element itself, typed as exactly that tag.
1649
+ *
1650
+ * @remarks
1651
+ * The class list is required in the tag form, which is what keeps the two forms apart: a
1652
+ * one-argument call is always markup. A tag with no classes is `mount(build(tag))`.
1653
+ *
1654
+ * This form returns the element itself rather than a container. It attaches to `document.body` and
1655
+ * records nothing, so removal is the caller's, exactly as it is for {@link mount}.
1656
+ *
1657
+ * @example
1658
+ * ```ts
1659
+ * const panel = render('section', 'surface muted')
1660
+ * panel.remove()
1661
+ * ```
1662
+ */
1661
1663
  export declare function render<K extends keyof HTMLElementTagNameMap>(tag: K, classes: string): HTMLElementTagNameMap[K];
1662
1664
 
1663
1665
  /**
@@ -1744,7 +1746,7 @@ export declare function resolveRendered(first: string, second?: string): HTMLEle
1744
1746
  * therefore unscaled and lifted to the window's own origin for the shot. The `iframe[data-vitest]`
1745
1747
  * selector and the `--tester-transform`, `--tester-margin-left`, `--viewport-width`, and
1746
1748
  * `--viewport-height` custom properties are the runner's, so a Vitest release that renames any of
1747
- * them reddens the size check below rather than writing a wrong frame.
1749
+ * them reddens the size check that follows rather than writing a wrong frame.
1748
1750
  *
1749
1751
  * Hand the pane straight back with {@link releasePane}. A tester pinned at a viewport taller than
1750
1752
  * the window puts its lower half beyond what a pointer can reach, so an ordinary press then fails
@@ -207,7 +207,7 @@ function isOutsideViewport(rectangle) {
207
207
  return rectangle.bottom <= 0 || rectangle.right <= 0 || rectangle.top >= window.innerHeight || rectangle.left >= window.innerWidth;
208
208
  }
209
209
  /**
210
- * Determines whether a person can click one element where it currently sits.
210
+ * Determines whether a person can click one element where it sits.
211
211
  *
212
212
  * @param element - The element to judge.
213
213
  * @returns True if the element is connected, visible, laid out with a non-zero box, in the
@@ -466,20 +466,6 @@ async function fillAccessible(name, text) {
466
466
  await userEvent.fill(resolveRendered(name), text);
467
467
  }
468
468
  /**
469
- * Presses a browser-keyboard sequence using Vitest's installed user-event syntax.
470
- *
471
- * @param keys - The keys or key descriptors to press.
472
- * @returns A promise resolving after the sequence completes.
473
- *
474
- * @example
475
- * ```ts
476
- * await pressKeys('{ArrowRight}{Enter}')
477
- * ```
478
- */
479
- async function pressKeys(keys) {
480
- await userEvent.keyboard(keys);
481
- }
482
- /**
483
469
  * Reaches a named control only through natural forward Tab traversal from the current focus.
484
470
  *
485
471
  * @param name - The target's exact accessible name.
@@ -580,7 +566,7 @@ function readPage() {
580
566
  return document.body.innerText.replaceAll(/\s+/g, " ").trim();
581
567
  }
582
568
  /**
583
- * Reads the rendered text of the element that currently holds focus.
569
+ * Reads the rendered text of the element that holds focus.
584
570
  *
585
571
  * @returns The focused HTML element's trimmed rendered text, including an empty string, or
586
572
  * `undefined` when focus rests on a non-HTML element. When nothing holds focus, the browser
@@ -1329,9 +1315,9 @@ function readContrast(element, floor) {
1329
1315
  *
1330
1316
  * @remarks
1331
1317
  * This reads and never acts. Focus arrives through the published verbs — `traverseAccessible`,
1332
- * `pressKeys`, a real click — and this measures what the browser painted once it landed. A control
1333
- * that is not matching `:focus-visible` when the call is made reports nothing, because no
1334
- * measurement taken then would be about focus.
1318
+ * `userEvent.keyboard` from `vitest/browser`, a real click — and this measures what the browser
1319
+ * painted once it landed. A control that is not matching `:focus-visible` when the call is made
1320
+ * reports nothing, because no measurement taken then would be about focus.
1335
1321
  *
1336
1322
  * Some controls are two elements: one that takes the focus and one a reader can see. A hidden radio
1337
1323
  * beside the label that carries every pixel of its chrome is the case `worn` exists for, so a
@@ -1756,7 +1742,7 @@ function measureContent() {
1756
1742
  * therefore unscaled and lifted to the window's own origin for the shot. The `iframe[data-vitest]`
1757
1743
  * selector and the `--tester-transform`, `--tester-margin-left`, `--viewport-width`, and
1758
1744
  * `--viewport-height` custom properties are the runner's, so a Vitest release that renames any of
1759
- * them reddens the size check below rather than writing a wrong frame.
1745
+ * them reddens the size check that follows rather than writing a wrong frame.
1760
1746
  *
1761
1747
  * Hand the pane straight back with {@link releasePane}. A tester pinned at a viewport taller than
1762
1748
  * the window puts its lower half beyond what a pointer can reach, so an ordinary press then fails
@@ -2256,6 +2242,6 @@ function createJournal() {
2256
2242
  };
2257
2243
  }
2258
2244
  //#endregion
2259
- export { ACCESSIBLE_ROLES, CANVAS_COLOR, CAPTURE_PANE, CAPTURE_STAGINGS, CONTENT_ROLES, FIELD_ROLES, FOCUSABLE_SELECTOR, HEADER_ROLES, IMPLICIT_ROLES, blendColor, build, captureFrame, clearStorage, clickAccessible, clickAccessibleWithin, clickDisclosure, commitInput, computeNamePattern, createChannel, createDragEvent, createJournal, createPointerEvent, createPortfolio, describeFocus, describeTree, expandCaptures, extractOrphans, extractStyles, fillAccessible, findKeyframes, findRule, isOutsideViewport, isReachable, isRendered, matchesColor, measureContent, measureContrast, measureLuminance, mount, parseCSSColor, parseColor, pressKeys, readBackdrop, readCascade, readClasses, readContrast, readFocus, readFrame, readLayers, readName, readPage, readPerception, readPixels, readRing, readRole, readRootToken, readRows, readRules, readStates, readStyle, readText, readToken, readValue, releasePane, removeDatabase, render, resolveAccessible, resolveRendered, stagePane, traverseAccessible, typeAccessible, typeInput, waitForFrame };
2245
+ export { ACCESSIBLE_ROLES, CANVAS_COLOR, CAPTURE_PANE, CAPTURE_STAGINGS, CONTENT_ROLES, FIELD_ROLES, FOCUSABLE_SELECTOR, HEADER_ROLES, IMPLICIT_ROLES, blendColor, build, captureFrame, clearStorage, clickAccessible, clickAccessibleWithin, clickDisclosure, commitInput, computeNamePattern, createChannel, createDragEvent, createJournal, createPointerEvent, createPortfolio, describeFocus, describeTree, expandCaptures, extractOrphans, extractStyles, fillAccessible, findKeyframes, findRule, isOutsideViewport, isReachable, isRendered, matchesColor, measureContent, measureContrast, measureLuminance, mount, parseCSSColor, parseColor, readBackdrop, readCascade, readClasses, readContrast, readFocus, readFrame, readLayers, readName, readPage, readPerception, readPixels, readRing, readRole, readRootToken, readRows, readRules, readStates, readStyle, readText, readToken, readValue, releasePane, removeDatabase, render, resolveAccessible, resolveRendered, stagePane, traverseAccessible, typeAccessible, typeInput, waitForFrame };
2260
2246
 
2261
2247
  //# sourceMappingURL=index.js.map