@shivaedev/test-story 0.7.1 → 0.8.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/CHANGELOG.md +11 -0
- package/README.md +42 -29
- package/dist/server/domEnvironment.config.js +1 -1
- package/dist/server/domEnvironment.config.js.map +1 -1
- package/dist/server/internal/installOutbound.d.ts +2 -0
- package/dist/server/internal/installOutbound.d.ts.map +1 -0
- package/dist/server/internal/{routeOutbound.js → installOutbound.js} +1 -5
- package/dist/server/internal/installOutbound.js.map +1 -0
- package/dist/server/internal/screenElements.d.ts +9 -0
- package/dist/server/internal/screenElements.d.ts.map +1 -0
- package/dist/server/internal/screenElements.js +61 -0
- package/dist/server/internal/screenElements.js.map +1 -0
- package/dist/server/internal/start.d.ts.map +1 -1
- package/dist/server/internal/start.js +4 -1
- package/dist/server/internal/start.js.map +1 -1
- package/dist/server/internal/storyIt.d.ts.map +1 -1
- package/dist/server/internal/storyIt.js +1 -1
- package/dist/server/internal/storyIt.js.map +1 -1
- package/dist/server/{internal/routeOutbound.d.ts → routeOutbound.d.ts} +0 -1
- package/dist/server/routeOutbound.d.ts.map +1 -0
- package/dist/server/routeOutbound.js +6 -0
- package/dist/server/routeOutbound.js.map +1 -0
- package/dist/server/screenDriver.d.ts +24 -0
- package/dist/server/screenDriver.d.ts.map +1 -0
- package/dist/server/screenDriver.js +60 -0
- package/dist/server/screenDriver.js.map +1 -0
- package/dist/server/storyKit.d.ts +3 -3
- package/dist/server/storyKit.d.ts.map +1 -1
- package/package.json +5 -6
- package/src/server/domEnvironment.config.ts +1 -1
- package/src/server/internal/{routeOutbound.ts → installOutbound.ts} +0 -8
- package/src/server/internal/screenElements.ts +62 -0
- package/src/server/internal/start.ts +9 -1
- package/src/server/internal/storyIt.ts +1 -1
- package/src/server/routeOutbound.ts +9 -0
- package/src/server/screenDriver.ts +75 -0
- package/src/server/storyKit.ts +5 -5
- package/dist/server/internal/routeOutbound.d.ts.map +0 -1
- package/dist/server/internal/routeOutbound.js.map +0 -1
- package/docs/north-star.md +0 -35
- package/docs/roadmap.md +0 -26
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.8.0 - 2026-10-18
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- `screenDriver(options).accessible(selector)` returns and narrates an element's computed accessible name and role, including label references that take precedence over `aria-label`.
|
|
8
|
+
- Export `screenDriver(options)`, `ScreenDriverOptions` and `ScreenDriver` from `screenDriver.ts`: narrated DOM clicks, text entry, form submission and screen readouts, with settlement driven by the app's pending signal and an optional renderer `act` adapter. Screens use the existing DOM transport to reach fake HTTP services, including identity providers.
|
|
9
|
+
- Screen drivers accept a root getter for document navigation and a per-call `settle({ timeoutMs })` limit.
|
|
10
|
+
- Export `routeOutbound(routes)` from `routeOutbound.ts` to scope outbound routing alongside a story's fake HTTP service.
|
|
11
|
+
- Screen actions and readouts reject hidden targets and targets inside hidden ancestors; form submission also refuses inert ancestors.
|
|
12
|
+
- Accept `stages: []` in `storyKit` definitions for engines initialized entirely in `create`, preserving inferred engine and verb types with no available trait stages.
|
|
13
|
+
|
|
3
14
|
## 0.7.1 - 2026-10-18
|
|
4
15
|
|
|
5
16
|
### Fixed
|
package/README.md
CHANGED
|
@@ -166,7 +166,9 @@ const app = storyKit({
|
|
|
166
166
|
|
|
167
167
|
`appDefinition` holds the application's kit hooks, and `fakeUrl` is the listening fake server's HTTP or HTTPS origin, such as `http://127.0.0.1:4321`. The map matches exact URL hostnames, across ports. It replaces the destination origin while keeping the request's path, query, method, headers and body. For example, a POST to `https://github.com/login/oauth/access_token` reaches the fake's `/login/oauth/access_token`. In Node stories, other hostnames pass through the previous dispatcher unchanged. In the DOM environment, only loopback hosts (`localhost`, IPv4 loopback addresses and `[::1]`) may pass through without a route; every other hostname must have an `outbound` fake. A refused request fails the story and names its hostname, even when application code catches the fetch error.
|
|
168
168
|
|
|
169
|
-
|
|
169
|
+
`routeOutbound(routes)` from `@shivaedev/test-story/routeOutbound.ts` installs the same routing in the current Effect Scope. Acquire a `fakeHttpService` in `create`, then yield `routeOutbound({ "api.example.com": api.url })` so the fake listener and its routing belong to that story. The starter's social sign-in story exercises this path through both document navigation and host-side requests.
|
|
170
|
+
|
|
171
|
+
Routing uses the global dispatcher that Node's built-in `fetch` already uses. The kit's `outbound` option installs routing before `around`, creation, traits and the body, stays active during scoped cleanup, and restores the previous dispatcher after each story, including a failing story. Stories within one worker must run one at a time because that dispatcher is shared; parallelism belongs between workers or inside one story. The DOM environment uses the same routing for host-side Node fetch and document requests, including redirect destinations. It refuses external hosts even when the kit omits `outbound`.
|
|
170
172
|
|
|
171
173
|
#### Asserting on application traces
|
|
172
174
|
|
|
@@ -229,7 +231,7 @@ app.it("a request continues its supplied trace", [], function* ({ request, reque
|
|
|
229
231
|
});
|
|
230
232
|
```
|
|
231
233
|
|
|
232
|
-
|
|
234
|
+
Detachment only removes the incoming parent: spans that application startup explicitly creates still follow native Effect semantics.
|
|
233
235
|
|
|
234
236
|
#### Reading OTLP exports
|
|
235
237
|
|
|
@@ -282,8 +284,6 @@ const user = yield* Fiber.join(pending);
|
|
|
282
284
|
|
|
283
285
|
A `pages` route accepts a one-based `?page=` query and returns `{ items, next }`, with `next: null` on the last page. No query means page 1; a page past the end returns an empty list; a non-positive or non-integer page gets 400. `api.requests` reads a snapshot of the request log, including rejected and held requests, with `method`, `path` (including query), raw `body` Buffer, `headers`, `params` and `query`.
|
|
284
286
|
|
|
285
|
-
The [fake HTTP stories](https://github.com/ShivaeDev/platform/blob/main/packages/test-story/src/server/outboundApi.spec.ts) use Node fetch to check params, credentials, pages, one-shot failure, hold/release ordering, request logs, narration and listener cleanup.
|
|
286
|
-
|
|
287
287
|
### 2. Once per feature: traits and verbs
|
|
288
288
|
|
|
289
289
|
A trait is a line and a change, tagged with a stage:
|
|
@@ -398,7 +398,7 @@ A library package has an engine too: its own services or Layer under test. Keep
|
|
|
398
398
|
|
|
399
399
|
The [`backfill` kit](https://github.com/ShivaeDev/platform/blob/main/packages/effect-test-sql/src/test-support/migrationStory.ts) in `effect-test-sql` is a library example. Its `create` returns the package's `migrationSteps(migrations)` API, its worker Layer provides PostgreSQL, and `around: withIsolatedDatabase` isolates each story. The `oldSchema` and `existingContacts` traits put contacts with email addresses into the old schema; the `backfill` verb calls `steps.migrateToLatest`. Its [story](https://github.com/ShivaeDev/platform/blob/main/packages/effect-test-sql/src/server/migrationStory.spec.ts) proves that those existing addresses survive the migration into a new table. It runs only when `PLATFORM_EFFECT_TEST_SQL_TEST_DATABASE_URL` is configured; a skipped story proves nothing.
|
|
400
400
|
|
|
401
|
-
A fake belongs only at a true external boundary, wired in `create`; keep the package's own engine real. The [resume kit](https://github.com/ShivaeDev/platform/blob/main/packages/effect-contract/src/test-support/resumeStory.ts), for example, creates synthetic browser and native event sources while running the package's real `resumeSignal` API.
|
|
401
|
+
A fake belongs only at a true external boundary, wired in `create`; keep the package's own engine real. The [resume kit](https://github.com/ShivaeDev/platform/blob/main/packages/effect-contract/src/test-support/resumeStory.ts), for example, creates synthetic browser and native event sources while running the package's real `resumeSignal` API.
|
|
402
402
|
|
|
403
403
|
### Whole-stack application kits
|
|
404
404
|
|
|
@@ -406,7 +406,7 @@ Keep one kit in the application’s `test-support/`. `effect-test` owns Vitest r
|
|
|
406
406
|
|
|
407
407
|
Set `runIf: hasPostgres` in the kit definition to gate all its stories once, where `hasPostgres` is a boolean such as `process.env.PLATFORM_EFFECT_SQL_TEST_DATABASE_URL !== undefined`. When false, `it`, `it.fails`, `it.only`, `it.skip`, `it.runIf` and `it.skipIf` all register skipped stories without acquiring the worker Layer or running `around`, creation, traits or bodies. A skipped `it.only` does not focus the file. When true or omitted, each variant keeps its usual Vitest behavior, including explicit skips and `fails` inverting a failure. Per-story conditions can skip more stories; they cannot enable a disabled kit. The engine, traits, verbs and services remain inferred from the definition.
|
|
408
408
|
|
|
409
|
-
The [order feature kit](https://github.com/ShivaeDev/platform/tree/main/packages/effect-react/src/test-support/order-example) runs the
|
|
409
|
+
The [order feature kit](https://github.com/ShivaeDev/platform/tree/main/packages/effect-react/src/test-support/order-example) is an example: it runs the feature’s services, repository, contracts, JSON serialization and authentication middleware over a loopback HTTP listener with bearer tokens.
|
|
410
410
|
|
|
411
411
|
For full-app stories, use `around: withIsolatedDatabase` from `@shivaedev/effect-test-sql/withIsolatedDatabase.ts` with a PostgreSQL test layer prepared from the application's migrations. Each story gets a database cloned from that template, and HTTP and worker pools commit into the same clone. The clone is dropped after scoped hosts and pools close. The starter's [application kit](https://github.com/ShivaeDev/platform/blob/main/examples/starter/src/test-support/application.ts) uses this isolation path.
|
|
412
412
|
|
|
@@ -414,7 +414,7 @@ For repository-level tests on a migrated template, `withTestTransaction` from `@
|
|
|
414
414
|
|
|
415
415
|
React screen traits acquire roots in Scope and use a fresh native atom registry for each screen. Cleanup unmounts roots before disposing registries and servers. Screen verbs dispatch DOM events and use bounded observations of rendered output. Select a live clock once on an HTTP/DOM kit; keep TestClock for deterministic local stories. `runUntil` is for engines with a meaningful step hook, not HTTP or DOM polling. Creation forms can use the same pattern with the existing `useCreate` path.
|
|
416
416
|
|
|
417
|
-
|
|
417
|
+
For cookie sessions, use the DOM host setup below. Native `RpcTest` can exercise handlers in faster local stories, but omits serialization and transport. Failure inspection is synchronous: keep redacted observed values in the engine instead of trying to query PostgreSQL inside `inspect`.
|
|
418
418
|
|
|
419
419
|
### DOM stories with cookie sessions
|
|
420
420
|
|
|
@@ -463,37 +463,48 @@ const cookieApp = storyKit({
|
|
|
463
463
|
|
|
464
464
|
Happy-dom's detached window records a location change without loading the next document. `domNavigate(url = window.location.href)` from `@shivaedev/test-story/domNavigate.ts` performs that load: it follows HTTP redirects through the document transport, keeps cookies for each destination, sets the final location, writes the returned HTML and returns the happy-dom response. Call it after the sign-in client has set the provider location, and before checking the returned page. An unrouted external redirect fails with its hostname.
|
|
465
465
|
|
|
466
|
-
The starter's [social application kit](https://github.com/ShivaeDev/platform/blob/main/examples/starter/src/test-support/socialApp.ts) runs a fixture Better Auth host on real PostgreSQL, with a rendered sign-in button and a fake GitHub provider. Its `
|
|
466
|
+
The starter's [social application kit](https://github.com/ShivaeDev/platform/blob/main/examples/starter/src/test-support/socialApp.ts) runs a fixture Better Auth host on real PostgreSQL, with a rendered sign-in button and a fake GitHub provider. Its `create` hook acquires the provider and uses scoped `routeOutbound` routing for both `github.com` and `api.github.com`. The fake's authorize endpoint returns the application's callback URL with the received state and a test code. The real callback exchanges that code and fetches the user against the fake, creates the account and session in PostgreSQL, and redirects to the signed-in page.
|
|
467
467
|
|
|
468
|
-
|
|
469
|
-
import { domNavigate } from "@shivaedev/test-story/domNavigate.ts";
|
|
468
|
+
The kit renders a Better Auth client whose `customFetchImpl` is `domFetch(baseUrl)`. The story calls `clickGitHubSignIn`, `settle` and `followRedirect` separately, then reads the heading, cookie and session. The screen driver settles on the screen's request state and reads the heading from the current document after navigation. The provider uses `fakeHttpService`. The authorize, token and user requests at the fake and a later session response provide evidence across the provider and application boundaries. The kit does not copy cookies into request headers.
|
|
470
469
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
outbound: { "github.com": fakeUrl, "api.github.com": fakeUrl },
|
|
474
|
-
verbs: (engine, story) => ({
|
|
475
|
-
clickGitHubSignIn: () => Effect.promise(async () => {
|
|
476
|
-
story.tell("Alice clicks Sign in with GitHub");
|
|
477
|
-
await engine.screen.click();
|
|
478
|
-
}),
|
|
479
|
-
followRedirect: () => Effect.promise(() => domNavigate()),
|
|
480
|
-
heading: () => document.querySelector("h1")?.textContent,
|
|
481
|
-
session: () => Effect.promise(async () => (await engine.fetch("/api/auth/get-session")).json()),
|
|
482
|
-
}),
|
|
483
|
-
});
|
|
484
|
-
|
|
485
|
-
socialApp.it("a social sign-in returns with a cookie session", [], function* ({ clickGitHubSignIn, followRedirect, heading, session }) {
|
|
470
|
+
```ts
|
|
471
|
+
socialApp.it("a social sign-in returns with a cookie session", [], function* ({ clickGitHubSignIn, settle, followRedirect, heading, cookie, session }) {
|
|
486
472
|
yield* clickGitHubSignIn();
|
|
487
|
-
yield*
|
|
473
|
+
yield* settle();
|
|
474
|
+
const response = yield* followRedirect();
|
|
475
|
+
expect(response.status).toBe(200);
|
|
488
476
|
expect(heading()).toBe("Signed in as alice@social.test");
|
|
477
|
+
expect(cookie()).not.toContain("session_token");
|
|
489
478
|
expect(yield* session()).toMatchObject({ user: { email: "alice@social.test" } });
|
|
490
479
|
});
|
|
491
480
|
```
|
|
492
481
|
|
|
493
|
-
`socialDefinition` starts the host and renders the screen with a Better Auth client whose `customFetchImpl` is `domFetch(baseUrl)`. The authorize, token and user requests at the fake and a later session response provide evidence across the provider and application boundaries. It does not copy cookies into request headers.
|
|
494
|
-
|
|
495
482
|
`domFetch` uses the current window and its cookie jar; it does not isolate multiple users. Run stories using that window serially, and let the application's cleanup end its sessions, restore the page location and close its host. Cookies follow hostnames, not listener ports, so a different loopback port alone does not isolate accounts.
|
|
496
483
|
|
|
484
|
+
### Driving a screen
|
|
485
|
+
|
|
486
|
+
`screenDriver(options)` supplies DOM verbs and readouts for a kit's `verbs` hook. Give it the mounted `root` (or a getter for a root that navigation replaces), the story's `tell`, and a `pending()` function that reads the application's own request or atom state. `settle()` waits until that signal is false; repeated identical renders never count as completion. The application must keep the signal true until its resulting screen work is ready to render.
|
|
487
|
+
|
|
488
|
+
```ts
|
|
489
|
+
import { screenDriver } from "@shivaedev/test-story/screenDriver.ts";
|
|
490
|
+
|
|
491
|
+
verbs: ({ view, requests }, story) => screenDriver({
|
|
492
|
+
root: view.container,
|
|
493
|
+
pending: () => requests.pending,
|
|
494
|
+
tell: story.tell,
|
|
495
|
+
})
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
`click(selector)`, `enter(selector, value)` and `submit(selector = "form")` return Effects. Selectors identify exactly one descendant of the root; missing or ambiguous controls fail. Hidden targets and targets inside hidden ancestors refuse actions and readouts. Text entry focuses an input or textarea and dispatches input and change events. Disabled controls refuse clicks and entry, inert ancestors also prevent form submission, and read-only fields refuse entry. Actions do not wait for settlement, so a story can release a held request before calling `settle()`.
|
|
499
|
+
|
|
500
|
+
`text(selector?)` returns rendered text from the selected element or the whole root. `value(selector)` returns the current input or textarea value. Both narrate what they read through `tell` and leave assertions to the spec. Entry narration omits the supplied value.
|
|
501
|
+
|
|
502
|
+
`accessible(selector)` returns `{ name, role }` computed by `dom-accessibility-api`, including native roles and label references that take precedence over `aria-label`. It narrates the readout and uses the same single rendered target checks as other readouts. Assert on this result when checking a control's accessible name; reading its raw attributes does not compute that name. This DOM computation does not inspect a browser's accessibility tree or prove speech from a screen reader.
|
|
503
|
+
|
|
504
|
+
For React, pass `act: async (action) => { await act(action); }` using React's `act`. The adapter wraps each action and flushes renderer work before checking the pending signal, including while waiting for an asynchronous response. The [React driver stories](https://github.com/ShivaeDev/platform/blob/main/packages/effect-react/src/screenDriver.dom.spec.tsx) cover a native pending atom, a rendered response and a controlled field without React warnings. The driver itself has no renderer dependency.
|
|
505
|
+
|
|
506
|
+
Settlement has a live-time limit of 3 seconds, configurable with the driver's `timeoutMs` or per call with `settle({ timeoutMs })`, and fails with the current screen text and advice when that limit expires. Use the existing DOM environment and scoped `routeOutbound` routing to reach `fakeHttpService`; the driver does not install another transport. The [screen stories](src/server/screenSignals.dom.spec.ts) hold a real HTTP response across repeated renders and sign in by filling and submitting a form against a fake identity provider. These fixtures prove DOM interactions and loopback HTTP, not a browser or a production authentication system.
|
|
507
|
+
|
|
497
508
|
### API by module
|
|
498
509
|
|
|
499
510
|
Import each module with the prefix `@shivaedev/test-story/`; there is no root entry.
|
|
@@ -502,6 +513,8 @@ Import each module with the prefix `@shivaedev/test-story/`; there is no root en
|
|
|
502
513
|
| --- | --- |
|
|
503
514
|
| `storyKit.ts` | `storyKit`, `Trait`, `RunUntilOptions`, `Story`, `RunHooks`, `StoryKitDefinition`, `Told`, `StoryBody`, `StoryTest`, `StoryIt`, `StoryKit` |
|
|
504
515
|
| `domFetch.ts` | `domFetch(baseUrl)` |
|
|
516
|
+
| `routeOutbound.ts` | `routeOutbound(routes)` |
|
|
517
|
+
| `screenDriver.ts` | `screenDriver(options)`, `ScreenDriverOptions`, `ScreenDriver` |
|
|
505
518
|
| `domNavigate.ts` | `domNavigate(url?)` |
|
|
506
519
|
| `genreTags.ts` | `genreTags(...kitNames)`, `GenreTag` |
|
|
507
520
|
| `domEnvironment.config.ts` | Default Vitest environment preserving Node host globals and happy-dom document transport |
|
|
@@ -517,7 +530,7 @@ pnpm add --save-dev @shivaedev/test-story @effect/vitest@4.0.4 effect@4.0.4 vite
|
|
|
517
530
|
`@effect/vitest`, `effect` and `vitest` are peers, so the application's own versions run the stories.
|
|
518
531
|
|
|
519
532
|
- The package runs inside Vitest. The story reaches the failure by rewriting the first error's message in Vitest's `onTestFailed` hook.
|
|
520
|
-
- Layer acquisition and engine creation failures precede that hook and carry no story appendix. A printed rerun command still needs a Vitest configuration that collects its file
|
|
533
|
+
- Layer acquisition and engine creation failures precede that hook and carry no story appendix. A printed rerun command still needs a Vitest configuration that collects its file.
|
|
521
534
|
- A kit's `it` is created when the kit is defined, so a kit lives in a module that only Vitest test files import.
|
|
522
535
|
- `inspect` runs after the test has failed, so it reads the engine synchronously.
|
|
523
536
|
- The spec line under the `✗` is found by matching the spec's path in the failure's stack, or else in the call site of the last line the story told or the last `runUntil`. A failure raised inside an Effect that the spec does not call directly may have none.
|
|
@@ -7,7 +7,7 @@ const domEnvironment = {
|
|
|
7
7
|
// Vitest loads environments outside the project's source resolution conditions.
|
|
8
8
|
const load = createRequire(import.meta.url);
|
|
9
9
|
const { documentTransport } = load("#internal/documentTransport.ts");
|
|
10
|
-
const { installOutbound } = load("#internal/
|
|
10
|
+
const { installOutbound } = load("#internal/installOutbound.ts");
|
|
11
11
|
const native = Object.fromEntries(["fetch", "AbortController", "AbortSignal", "Headers", "Request", "Response"].map((name) => [name, Reflect.get(global, name)]));
|
|
12
12
|
const restoreOutbound = await installOutbound({}, true);
|
|
13
13
|
const win = new GlobalWindow({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"domEnvironment.config.js","sourceRoot":"","sources":["../../src/server/domEnvironment.config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAoB,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAElE,MAAM,cAAc,GAAgB;IACnC,IAAI,EAAE,gBAAgB;IACtB,KAAK,CAAC,KAAK,CAAC,MAAyB,EAAE,EAAE,QAAQ,GAAG,EAAE,EAAE;QACvD,gFAAgF;QAChF,MAAM,IAAI,GAAG,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;QAC5C,MAAM,EAAE,iBAAiB,EAAE,GAAoD,IAAI,CAAC,gCAAgC,CAAC,CAAC;QACtH,MAAM,EAAE,eAAe,EAAE,
|
|
1
|
+
{"version":3,"file":"domEnvironment.config.js","sourceRoot":"","sources":["../../src/server/domEnvironment.config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAoB,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAElE,MAAM,cAAc,GAAgB;IACnC,IAAI,EAAE,gBAAgB;IACtB,KAAK,CAAC,KAAK,CAAC,MAAyB,EAAE,EAAE,QAAQ,GAAG,EAAE,EAAE;QACvD,gFAAgF;QAChF,MAAM,IAAI,GAAG,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;QAC5C,MAAM,EAAE,iBAAiB,EAAE,GAAoD,IAAI,CAAC,gCAAgC,CAAC,CAAC;QACtH,MAAM,EAAE,eAAe,EAAE,GAAkD,IAAI,CAAC,8BAA8B,CAAC,CAAC;QAChH,MAAM,MAAM,GAAG,MAAM,CAAC,WAAW,CAChC,CAAC,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,SAAS,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,CAC9H,CAAC;QACF,MAAM,eAAe,GAAG,MAAM,eAAe,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;QACxD,MAAM,GAAG,GAAG,IAAI,YAAY,CAAC;YAC5B,GAAG,QAAQ;YACX,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,QAAQ,EAAE,EAAE,GAAG,QAAQ,CAAC,QAAQ,EAAE,qBAAqB,EAAE,IAAI,EAAE;YAC/D,GAAG,EAAE,QAAQ,CAAC,GAAG,IAAI,uBAAuB;SAC5C,CAAC,CAAC;QACH,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,WAAW,GAAG,iBAAiB,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC1E,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,cAAc,CAAC,MAAM,EAAE,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QACjF,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YACpD,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;QACpF,CAAC;QACD,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,QAAQ,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;QAC5F,MAAM,CAAC,cAAc,CAAC,GAAG,CAAC,QAAQ,EAAE,aAAa,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC;QAC3F,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,gBAAgB,CAAC,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACjG,OAAO;YACN,KAAK,CAAC,QAAQ,CAAC,MAAM;gBACpB,IAAI,CAAC;oBACJ,MAAM,GAAG,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;oBAC3B,GAAG,CAAC,KAAK,EAAE,CAAC;gBACb,CAAC;wBAAS,CAAC;oBACV,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;wBACxB,OAAO,CAAC,cAAc,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;oBACrC,CAAC;oBACD,KAAK,MAAM,CAAC,GAAG,EAAE,UAAU,CAAC,IAAI,SAAS,EAAE,CAAC;wBAC3C,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,GAAG,EAAE,UAAU,CAAC,CAAC;oBAChD,CAAC;oBACD,OAAO,CAAC,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,CAAC;oBAC7D,eAAe,EAAE,CAAC;gBACnB,CAAC;YACF,CAAC;SACD,CAAC;IACH,CAAC;IACD,eAAe,EAAE,QAAQ;CACzB,CAAC;AAEF,eAAe,cAAc,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"installOutbound.d.ts","sourceRoot":"","sources":["../../../src/server/internal/installOutbound.ts"],"names":[],"mappings":"AAeA,wBAAsB,eAAe,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,MAAM,UAAQ,uBAgD7F"}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { isIP } from "node:net";
|
|
2
|
-
import { Effect } from "effect";
|
|
3
2
|
import { unrouted } from "#internal/errors.ts";
|
|
4
3
|
function isDispatcher(value) {
|
|
5
4
|
return typeof value === "object" && value !== null && "dispatch" in value && typeof value.dispatch === "function";
|
|
@@ -52,7 +51,4 @@ export async function installOutbound(routes, strict = false) {
|
|
|
52
51
|
}
|
|
53
52
|
};
|
|
54
53
|
}
|
|
55
|
-
|
|
56
|
-
return Effect.acquireRelease(Effect.promise(() => installOutbound(routes, Reflect.get(globalThis, Symbol.for("test-story.dom")) === true)), (restore) => Effect.sync(restore)).pipe(Effect.asVoid);
|
|
57
|
-
}
|
|
58
|
-
//# sourceMappingURL=routeOutbound.js.map
|
|
54
|
+
//# sourceMappingURL=installOutbound.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"installOutbound.js","sourceRoot":"","sources":["../../../src/server/internal/installOutbound.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare function screenElements(root: () => HTMLElement): {
|
|
2
|
+
element: (selector: string) => HTMLElement;
|
|
3
|
+
enabled: (found: HTMLElement, selector: string) => void;
|
|
4
|
+
enter: (selector: string, value: string) => void;
|
|
5
|
+
form: (selector: string) => HTMLFormElement;
|
|
6
|
+
input: (selector: string) => HTMLInputElement | HTMLTextAreaElement;
|
|
7
|
+
rendered: (found: HTMLElement, selector: string) => HTMLElement;
|
|
8
|
+
};
|
|
9
|
+
//# sourceMappingURL=screenElements.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"screenElements.d.ts","sourceRoot":"","sources":["../../../src/server/internal/screenElements.ts"],"names":[],"mappings":"AAAA,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,WAAW;wBAa1B,MAAM;qBAiBT,WAAW,YAAY,MAAM;sBAQlC,MAAM,SAAS,MAAM;qBAYtB,MAAM;sBA3BC,MAAM;sBAtBN,WAAW,YAAY,MAAM;EA4DtD"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export function screenElements(root) {
|
|
2
|
+
function rendered(found, selector) {
|
|
3
|
+
const visibility = getComputedStyle(found).visibility;
|
|
4
|
+
let hidden = visibility === "hidden" || visibility === "collapse";
|
|
5
|
+
for (let ancestor = found; ancestor; ancestor = ancestor.parentElement) {
|
|
6
|
+
const style = getComputedStyle(ancestor);
|
|
7
|
+
hidden ||= Boolean(ancestor.hidden) || style.display === "none" || style.contentVisibility === "hidden";
|
|
8
|
+
}
|
|
9
|
+
if (hidden) {
|
|
10
|
+
throw new Error(`The screen's ${selector} is not rendered.\nhelp: Reveal the target and its ancestors before acting on or reading it.`);
|
|
11
|
+
}
|
|
12
|
+
return found;
|
|
13
|
+
}
|
|
14
|
+
function element(selector) {
|
|
15
|
+
const matches = root().querySelectorAll(selector);
|
|
16
|
+
const found = matches[0];
|
|
17
|
+
if (matches.length !== 1 || found === undefined) {
|
|
18
|
+
throw new Error(`The screen has ${matches.length} matches for ${JSON.stringify(selector)}.\nhelp: Use a selector for exactly one rendered control or readout.`);
|
|
19
|
+
}
|
|
20
|
+
return rendered(found, selector);
|
|
21
|
+
}
|
|
22
|
+
function input(selector) {
|
|
23
|
+
const found = element(selector);
|
|
24
|
+
if (!(found instanceof HTMLInputElement || found instanceof HTMLTextAreaElement)) {
|
|
25
|
+
throw new Error(`The screen's ${selector} is not a text field.\nhelp: Select a rendered input or textarea to enter or read a value.`);
|
|
26
|
+
}
|
|
27
|
+
return found;
|
|
28
|
+
}
|
|
29
|
+
function enabled(found, selector) {
|
|
30
|
+
if (found.matches(":disabled") || found.closest("[inert]")) {
|
|
31
|
+
throw new Error(`The screen's ${selector} is disabled.\nhelp: Wait for the app to enable the control before acting on it.`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
element,
|
|
36
|
+
enabled,
|
|
37
|
+
enter: (selector, value) => {
|
|
38
|
+
const field = input(selector);
|
|
39
|
+
enabled(field, selector);
|
|
40
|
+
if (field.readOnly) {
|
|
41
|
+
throw new Error(`The screen's ${selector} is read-only.\nhelp: Select an editable input or textarea.`);
|
|
42
|
+
}
|
|
43
|
+
field.focus();
|
|
44
|
+
const prototype = field instanceof HTMLInputElement ? HTMLInputElement.prototype : HTMLTextAreaElement.prototype;
|
|
45
|
+
Object.getOwnPropertyDescriptor(prototype, "value")?.set?.call(field, value);
|
|
46
|
+
field.dispatchEvent(new Event("input", { bubbles: true }));
|
|
47
|
+
field.dispatchEvent(new Event("change", { bubbles: true }));
|
|
48
|
+
},
|
|
49
|
+
form: (selector) => {
|
|
50
|
+
const found = element(selector);
|
|
51
|
+
if (!(found instanceof HTMLFormElement)) {
|
|
52
|
+
throw new Error(`The screen's ${selector} is not a form.\nhelp: Select a rendered form to submit.`);
|
|
53
|
+
}
|
|
54
|
+
enabled(found, selector);
|
|
55
|
+
return found;
|
|
56
|
+
},
|
|
57
|
+
input,
|
|
58
|
+
rendered,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=screenElements.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"screenElements.js","sourceRoot":"","sources":["../../../src/server/internal/screenElements.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"start.d.ts","sourceRoot":"","sources":["../../../src/server/internal/start.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,MAAM,EAAU,MAAM,QAAQ,CAAC;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAK1C,OAAO,EAAE,KAAK,eAAe,EAAmB,KAAK,UAAU,EAAQ,MAAM,mBAAmB,CAAC;AAEjG,eAAO,MAAM,KAAK,iGAIJ,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,
|
|
1
|
+
{"version":3,"file":"start.d.ts","sourceRoot":"","sources":["../../../src/server/internal/start.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,MAAM,EAAU,MAAM,QAAQ,CAAC;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAK1C,OAAO,EAAE,KAAK,eAAe,EAAmB,KAAK,UAAU,EAAQ,MAAM,mBAAmB,CAAC;AAEjG,eAAO,MAAM,KAAK,iGAIJ,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,6CA0B/D,CAAC"}
|
|
@@ -19,7 +19,10 @@ export const start = Effect.fnUntraced(function* (definition, given, context, bo
|
|
|
19
19
|
},
|
|
20
20
|
tell: narration.tell,
|
|
21
21
|
};
|
|
22
|
-
return yield* body(
|
|
22
|
+
return yield* body(Object.defineProperties({}, {
|
|
23
|
+
...Object.getOwnPropertyDescriptors(definition.verbs(engine, story)),
|
|
24
|
+
story: { enumerable: true, value: story },
|
|
25
|
+
}));
|
|
23
26
|
}).pipe(Effect.tapCause((cause) => Effect.sync(() => captureFailure(Cause.prettyErrors(cause)[0]?.stack))));
|
|
24
27
|
});
|
|
25
28
|
//# sourceMappingURL=start.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"start.js","sourceRoot":"","sources":["../../../src/server/internal/start.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAExD,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjD,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAC7C,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjD,OAAO,EAA0D,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAEjG,MAAM,CAAC,MAAM,KAAK,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,EAC/C,UAA2B,EAC3B,KAA4B,EAC5B,OAAoB,EACpB,IAAgE;IAEhE,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IAC5G,MAAM,EAAE,SAAS,EAAE,cAAc,EAAE,GAAG,OAAO,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,CAAC,OAAO,EAAE,IAAI,EAAE,UAAU,CAAC,IAAI,EAAE,EAAE,OAAO,CAAC,CAAC;IACvH,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QACjC,KAAK,CAAC,CAAC,IAAI,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC;QAClD,MAAM,KAAK,GAAe;YACzB,MAAM;YACN,KAAK,EAAE,SAAS,CAAC,KAAK;YACtB,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;gBAC5B,MAAM,IAAI,GAAG,QAAQ,EAAE,CAAC;gBACxB,SAAS,CAAC,EAAE,GAAG,IAAI,CAAC;gBACpB,OAAO,QAAQ,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,CAAC,IAAI,EAAE,GAAG,EAAE,UAAU,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;YACvI,CAAC;YACD,IAAI,EAAE,SAAS,CAAC,IAAI;SACpB,CAAC;QACF,OAAO,KAAK,CAAC,CAAC,IAAI,CAAC,EAAE,GAAG,UAAU,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"start.js","sourceRoot":"","sources":["../../../src/server/internal/start.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAExD,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjD,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAC7C,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjD,OAAO,EAA0D,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAEjG,MAAM,CAAC,MAAM,KAAK,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,EAC/C,UAA2B,EAC3B,KAA4B,EAC5B,OAAoB,EACpB,IAAgE;IAEhE,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IAC5G,MAAM,EAAE,SAAS,EAAE,cAAc,EAAE,GAAG,OAAO,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,CAAC,OAAO,EAAE,IAAI,EAAE,UAAU,CAAC,IAAI,EAAE,EAAE,OAAO,CAAC,CAAC;IACvH,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QACjC,KAAK,CAAC,CAAC,IAAI,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC;QAClD,MAAM,KAAK,GAAe;YACzB,MAAM;YACN,KAAK,EAAE,SAAS,CAAC,KAAK;YACtB,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;gBAC5B,MAAM,IAAI,GAAG,QAAQ,EAAE,CAAC;gBACxB,SAAS,CAAC,EAAE,GAAG,IAAI,CAAC;gBACpB,OAAO,QAAQ,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,CAAC,IAAI,EAAE,GAAG,EAAE,UAAU,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;YACvI,CAAC;YACD,IAAI,EAAE,SAAS,CAAC,IAAI;SACpB,CAAC;QACF,OAAO,KAAK,CAAC,CAAC,IAAI,CACjB,MAAM,CAAC,gBAAgB,CACtB,EAAE,EACF;YACC,GAAG,MAAM,CAAC,yBAAyB,CAAC,UAAU,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;YACpE,KAAK,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE;SACzC,CACD,CACD,CAAC;IACH,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,cAAc,CAAC,KAAK,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;AAC7G,CAAC,CAAC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storyIt.d.ts","sourceRoot":"","sources":["../../../src/server/internal/storyIt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAElD,OAAO,KAAK,EAAc,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AAGrF,OAAO,EAAE,KAAK,SAAS,EAAU,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"storyIt.d.ts","sourceRoot":"","sources":["../../../src/server/internal/storyIt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAElD,OAAO,KAAK,EAAc,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AAGrF,OAAO,EAAE,KAAK,SAAS,EAAU,MAAM,qBAAqB,CAAC;AAC7D,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAIrE,KAAK,SAAS,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,KAAK,SAAS,CAAC,SAAS,EAAE,OAAO,EAAE,KAAK,CAAC,GAAG,SAAS,CAAC;AAE1G,KAAK,SAAS,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,UAAU,EAAE,EAAE,IAAI,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,iBAAiB,KAAK,IAAI,CAAC;AAE9H,MAAM,WAAW,OAAQ,SAAQ,SAAS;IACzC,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,SAAS,CAAC;IAClD,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,SAAS,CAAC;CACnD;AAgCD,wBAAgB,OAAO,CAAC,UAAU,EAAE,eAAe,GAAG,OAAO,CA0B5D"}
|
|
@@ -2,8 +2,8 @@ import { Effect, Layer } from "effect";
|
|
|
2
2
|
import { makeEffectIt } from "@shivaedev/effect-test/vitest.ts";
|
|
3
3
|
import { genreOf } from "#internal/genre.ts";
|
|
4
4
|
import { hooked } from "#internal/hooked.ts";
|
|
5
|
-
import { routeOutbound } from "#internal/routeOutbound.ts";
|
|
6
5
|
import { start } from "#internal/start.ts";
|
|
6
|
+
import { routeOutbound } from "#routeOutbound.ts";
|
|
7
7
|
function withTags(options, tags) {
|
|
8
8
|
if (typeof options === "number") {
|
|
9
9
|
return { tags: [...tags], timeout: options };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storyIt.js","sourceRoot":"","sources":["../../../src/server/internal/storyIt.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAEvC,OAAO,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAChE,OAAO,EAAc,OAAO,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAkB,MAAM,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"storyIt.js","sourceRoot":"","sources":["../../../src/server/internal/storyIt.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
1
|
import { Effect } from "effect";
|
|
2
|
-
export declare function installOutbound(routes: Readonly<Record<string, string>>, strict?: boolean): Promise<() => void>;
|
|
3
2
|
export declare function routeOutbound(routes: Readonly<Record<string, string>>): Effect.Effect<void, never, import("effect/Scope").Scope>;
|
|
4
3
|
//# sourceMappingURL=routeOutbound.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routeOutbound.d.ts","sourceRoot":"","sources":["../../src/server/routeOutbound.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAGhC,wBAAgB,aAAa,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,4DAKrE"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { installOutbound } from "#internal/installOutbound.ts";
|
|
3
|
+
export function routeOutbound(routes) {
|
|
4
|
+
return Effect.acquireRelease(Effect.promise(() => installOutbound(routes, Reflect.get(globalThis, Symbol.for("test-story.dom")) === true)), (restore) => Effect.sync(restore)).pipe(Effect.asVoid);
|
|
5
|
+
}
|
|
6
|
+
//# sourceMappingURL=routeOutbound.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routeOutbound.js","sourceRoot":"","sources":["../../src/server/routeOutbound.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAE/D,MAAM,UAAU,aAAa,CAAC,MAAwC;IACrE,OAAO,MAAM,CAAC,cAAc,CAC3B,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,eAAe,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,EAC7G,CAAC,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CACjC,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;AACvB,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
export interface ScreenDriverOptions {
|
|
3
|
+
readonly act?: (action: () => Promise<void>) => PromiseLike<void>;
|
|
4
|
+
readonly pending: () => boolean;
|
|
5
|
+
readonly root: HTMLElement | (() => HTMLElement);
|
|
6
|
+
readonly tell: (line: string) => void;
|
|
7
|
+
readonly timeoutMs?: number;
|
|
8
|
+
}
|
|
9
|
+
export type ScreenDriver = ReturnType<typeof screenDriver>;
|
|
10
|
+
export declare function screenDriver({ root, pending, tell, act, timeoutMs }: ScreenDriverOptions): {
|
|
11
|
+
accessible: (selector: string) => {
|
|
12
|
+
name: string;
|
|
13
|
+
role: string | null;
|
|
14
|
+
};
|
|
15
|
+
click: (selector: string) => Effect.Effect<void, never, never>;
|
|
16
|
+
enter: (selector: string, value: string) => Effect.Effect<void, never, never>;
|
|
17
|
+
settle: (options?: {
|
|
18
|
+
readonly timeoutMs?: number;
|
|
19
|
+
}) => Effect.Effect<void, never, never>;
|
|
20
|
+
submit: (selector?: string) => Effect.Effect<void, never, never>;
|
|
21
|
+
text: (selector?: string) => string;
|
|
22
|
+
value: (selector: string) => string;
|
|
23
|
+
};
|
|
24
|
+
//# sourceMappingURL=screenDriver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"screenDriver.d.ts","sourceRoot":"","sources":["../../src/server/screenDriver.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAIhC,MAAM,WAAW,mBAAmB;IACnC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,KAAK,WAAW,CAAC,IAAI,CAAC,CAAC;IAClE,QAAQ,CAAC,OAAO,EAAE,MAAM,OAAO,CAAC;IAChC,QAAQ,CAAC,IAAI,EAAE,WAAW,GAAG,CAAC,MAAM,WAAW,CAAC,CAAC;IACjD,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,MAAM,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AAE3D,wBAAgB,YAAY,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,GAA0B,EAAE,SAAgB,EAAE,EAAE,mBAAmB;2BAgC9F,MAAM;;;;sBAMX,MAAM;sBAON,MAAM,SAAS,MAAM;uBApCf;QAAE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE;;sBAuCrC,MAAM;sBAKN,MAAM;EAMzB"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { computeAccessibleName, getRole } from "dom-accessibility-api";
|
|
2
|
+
import { Effect } from "effect";
|
|
3
|
+
import { withLive } from "@shivaedev/effect-test/withLive.ts";
|
|
4
|
+
import { screenElements } from "#internal/screenElements.ts";
|
|
5
|
+
export function screenDriver({ root, pending, tell, act = (action) => action(), timeoutMs = 3000 }) {
|
|
6
|
+
const currentRoot = typeof root === "function" ? root : () => root;
|
|
7
|
+
const elements = screenElements(currentRoot);
|
|
8
|
+
function perform(line, action) {
|
|
9
|
+
return Effect.promise(async () => {
|
|
10
|
+
tell(line);
|
|
11
|
+
await act(async () => action());
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
function settle(options = {}) {
|
|
15
|
+
const limit = options.timeoutMs ?? timeoutMs;
|
|
16
|
+
const wait = Effect.gen(function* () {
|
|
17
|
+
tell("wait for the screen's pending work");
|
|
18
|
+
do {
|
|
19
|
+
yield* Effect.promise(() => act(async () => undefined));
|
|
20
|
+
if (!pending()) {
|
|
21
|
+
tell("the screen has no pending work");
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
yield* Effect.promise(() => act(() => new Promise((resolve) => setTimeout(resolve, 10))));
|
|
25
|
+
} while (true);
|
|
26
|
+
});
|
|
27
|
+
function timedOut() {
|
|
28
|
+
return Effect.die(new Error(`The screen did not settle within ${limit}ms.\nhelp: Release held requests and check the app's pending signal and renderer act adapter.\nScreen: ${currentRoot().textContent}`));
|
|
29
|
+
}
|
|
30
|
+
return withLive(wait.pipe(Effect.timeoutOrElse({ duration: limit, orElse: timedOut })));
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
accessible: (selector) => {
|
|
34
|
+
const element = elements.element(selector);
|
|
35
|
+
const accessible = { name: computeAccessibleName(element), role: getRole(element) };
|
|
36
|
+
tell(`read the accessibility of ${selector}: ${JSON.stringify(accessible)}`);
|
|
37
|
+
return accessible;
|
|
38
|
+
},
|
|
39
|
+
click: (selector) => perform(`click ${selector}`, () => {
|
|
40
|
+
const element = elements.element(selector);
|
|
41
|
+
elements.enabled(element, selector);
|
|
42
|
+
element.focus();
|
|
43
|
+
element.click();
|
|
44
|
+
}),
|
|
45
|
+
enter: (selector, value) => perform(`enter a value in ${selector}`, () => elements.enter(selector, value)),
|
|
46
|
+
settle,
|
|
47
|
+
submit: (selector = "form") => perform(`submit ${selector}`, () => elements.form(selector).requestSubmit()),
|
|
48
|
+
text: (selector) => {
|
|
49
|
+
const text = (selector === undefined ? elements.rendered(currentRoot(), "root") : elements.element(selector)).innerText;
|
|
50
|
+
tell(`read ${selector ?? "the screen"}: ${JSON.stringify(text)}`);
|
|
51
|
+
return text;
|
|
52
|
+
},
|
|
53
|
+
value: (selector) => {
|
|
54
|
+
const value = elements.input(selector).value;
|
|
55
|
+
tell(`read the value in ${selector}: ${JSON.stringify(value)}`);
|
|
56
|
+
return value;
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=screenDriver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"screenDriver.js","sourceRoot":"","sources":["../../src/server/screenDriver.ts"],"names":[],"mappings":"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"}
|
|
@@ -37,7 +37,7 @@ export interface StoryKitDefinition<TEngine, TStage extends string, TVerbs, TPro
|
|
|
37
37
|
readonly outbound?: Readonly<Record<string, string>>;
|
|
38
38
|
readonly run?: RunHooks<TEngine, TStep, TFailed, TDiagnose>;
|
|
39
39
|
readonly runIf?: boolean;
|
|
40
|
-
readonly stages: readonly
|
|
40
|
+
readonly stages: readonly TStage[];
|
|
41
41
|
readonly verbs: (engine: TEngine, story: Story<TEngine, TStep | TFailed | TDiagnose>) => TVerbs;
|
|
42
42
|
}
|
|
43
43
|
export type Told<TEngine, TVerbs, TRun extends AnyEffect> = TVerbs & {
|
|
@@ -60,8 +60,8 @@ export interface StoryKit<TEngine, TStage extends string, TVerbs, TRun extends A
|
|
|
60
60
|
};
|
|
61
61
|
readonly traits: <const TGiven extends readonly Trait<TEngine, TStage, unknown>[]>(...given: TGiven) => Trait<TEngine, TStage, TraitServices<TGiven[number]>>;
|
|
62
62
|
}
|
|
63
|
-
export declare function storyKit<TCreate extends () => unknown, const TStage extends string, TVerbs extends object, TProvided = never, TAfter extends AfterResults<TStage> = Record<never, never>, TStep extends AnyEffect = never, TFailed extends AnyEffect = never, TDiagnose extends AnyEffect = never>(definition: InferredDefinition<TCreate, TStage, TVerbs, TProvided, TAfter, TStep, TFailed, TDiagnose>): StoryKit<HookValue<ReturnType<TCreate>>, TStage, TVerbs, TStep | TFailed | TDiagnose, TProvided>;
|
|
64
|
-
export declare function storyKit<TEngine, const TStage extends string, TVerbs extends object, TProvided = never, TCreate extends AnyEffect = never, TAfter extends {
|
|
63
|
+
export declare function storyKit<TCreate extends () => unknown, const TStage extends string = never, TVerbs extends object = object, TProvided = never, TAfter extends AfterResults<TStage> = Record<never, never>, TStep extends AnyEffect = never, TFailed extends AnyEffect = never, TDiagnose extends AnyEffect = never>(definition: InferredDefinition<TCreate, TStage, TVerbs, TProvided, TAfter, TStep, TFailed, TDiagnose>): StoryKit<HookValue<ReturnType<TCreate>>, TStage, TVerbs, TStep | TFailed | TDiagnose, TProvided>;
|
|
64
|
+
export declare function storyKit<TEngine, const TStage extends string = never, TVerbs extends object = object, TProvided = never, TCreate extends AnyEffect = never, TAfter extends {
|
|
65
65
|
readonly [TKey in TStage]?: AnyEffect;
|
|
66
66
|
} = Record<never, never>, TStep extends AnyEffect = never, TFailed extends AnyEffect = never, TDiagnose extends AnyEffect = never>(definition: LegacyDefinition<TEngine, TStage, TVerbs, TProvided, TCreate, TAfter, TStep, TFailed, TDiagnose>): StoryKit<TEngine, TStage, TVerbs, TStep | TFailed | TDiagnose, TProvided>;
|
|
67
67
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storyKit.d.ts","sourceRoot":"","sources":["../../src/server/storyKit.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"storyKit.d.ts","sourceRoot":"","sources":["../../src/server/storyKit.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shivaedev/test-story",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Tests that read like stories over the real engine: a story kit per engine with its own it, traits per feature, and a failure that prints the story, where it stopped and the engine's state",
|
|
5
5
|
"homepage": "https://github.com/ShivaeDev/platform/tree/main/packages/test-story#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -37,21 +37,20 @@
|
|
|
37
37
|
"!src/**/*.test.ts",
|
|
38
38
|
"!src/**/*.spec.ts",
|
|
39
39
|
"!src/**/test-support",
|
|
40
|
-
"docs",
|
|
41
40
|
"CHANGELOG.md",
|
|
42
41
|
"README.md"
|
|
43
42
|
],
|
|
44
43
|
"dependencies": {
|
|
45
|
-
"@shivaedev/effect-test": "0.6.0"
|
|
44
|
+
"@shivaedev/effect-test": "0.6.0",
|
|
45
|
+
"dom-accessibility-api": "0.7.1"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@effect/vitest": "4.0.4",
|
|
49
|
-
"@shivaedev/quality": "0.
|
|
49
|
+
"@shivaedev/quality": "0.26.0",
|
|
50
50
|
"@types/node": "24.19.2",
|
|
51
|
-
"@typescript/native": "npm:typescript@7.0.2",
|
|
52
51
|
"effect": "4.0.4",
|
|
53
52
|
"happy-dom": "20.14.6",
|
|
54
|
-
"typescript": "
|
|
53
|
+
"typescript": "7.0.2",
|
|
55
54
|
"vitest": "5.0.3"
|
|
56
55
|
},
|
|
57
56
|
"peerDependencies": {
|
|
@@ -8,7 +8,7 @@ const domEnvironment: Environment = {
|
|
|
8
8
|
// Vitest loads environments outside the project's source resolution conditions.
|
|
9
9
|
const load = createRequire(import.meta.url);
|
|
10
10
|
const { documentTransport }: typeof import("#internal/documentTransport.ts") = load("#internal/documentTransport.ts");
|
|
11
|
-
const { installOutbound }: typeof import("#internal/
|
|
11
|
+
const { installOutbound }: typeof import("#internal/installOutbound.ts") = load("#internal/installOutbound.ts");
|
|
12
12
|
const native = Object.fromEntries(
|
|
13
13
|
["fetch", "AbortController", "AbortSignal", "Headers", "Request", "Response"].map((name) => [name, Reflect.get(global, name)]),
|
|
14
14
|
);
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { isIP } from "node:net";
|
|
2
|
-
import { Effect } from "effect";
|
|
3
2
|
import { unrouted } from "#internal/errors.ts";
|
|
4
3
|
|
|
5
4
|
interface DispatchOptions {
|
|
@@ -63,10 +62,3 @@ export async function installOutbound(routes: Readonly<Record<string, string>>,
|
|
|
63
62
|
}
|
|
64
63
|
};
|
|
65
64
|
}
|
|
66
|
-
|
|
67
|
-
export function routeOutbound(routes: Readonly<Record<string, string>>) {
|
|
68
|
-
return Effect.acquireRelease(
|
|
69
|
-
Effect.promise(() => installOutbound(routes, Reflect.get(globalThis, Symbol.for("test-story.dom")) === true)),
|
|
70
|
-
(restore) => Effect.sync(restore),
|
|
71
|
-
).pipe(Effect.asVoid);
|
|
72
|
-
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export function screenElements(root: () => HTMLElement) {
|
|
2
|
+
function rendered(found: HTMLElement, selector: string) {
|
|
3
|
+
const visibility = getComputedStyle(found).visibility;
|
|
4
|
+
let hidden = visibility === "hidden" || visibility === "collapse";
|
|
5
|
+
for (let ancestor: HTMLElement | null = found; ancestor; ancestor = ancestor.parentElement) {
|
|
6
|
+
const style = getComputedStyle(ancestor);
|
|
7
|
+
hidden ||= Boolean(ancestor.hidden) || style.display === "none" || style.contentVisibility === "hidden";
|
|
8
|
+
}
|
|
9
|
+
if (hidden) {
|
|
10
|
+
throw new Error(`The screen's ${selector} is not rendered.\nhelp: Reveal the target and its ancestors before acting on or reading it.`);
|
|
11
|
+
}
|
|
12
|
+
return found;
|
|
13
|
+
}
|
|
14
|
+
function element(selector: string) {
|
|
15
|
+
const matches = root().querySelectorAll<HTMLElement>(selector);
|
|
16
|
+
const found = matches[0];
|
|
17
|
+
if (matches.length !== 1 || found === undefined) {
|
|
18
|
+
throw new Error(
|
|
19
|
+
`The screen has ${matches.length} matches for ${JSON.stringify(selector)}.\nhelp: Use a selector for exactly one rendered control or readout.`,
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
return rendered(found, selector);
|
|
23
|
+
}
|
|
24
|
+
function input(selector: string) {
|
|
25
|
+
const found = element(selector);
|
|
26
|
+
if (!(found instanceof HTMLInputElement || found instanceof HTMLTextAreaElement)) {
|
|
27
|
+
throw new Error(`The screen's ${selector} is not a text field.\nhelp: Select a rendered input or textarea to enter or read a value.`);
|
|
28
|
+
}
|
|
29
|
+
return found;
|
|
30
|
+
}
|
|
31
|
+
function enabled(found: HTMLElement, selector: string) {
|
|
32
|
+
if (found.matches(":disabled") || found.closest("[inert]")) {
|
|
33
|
+
throw new Error(`The screen's ${selector} is disabled.\nhelp: Wait for the app to enable the control before acting on it.`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
element,
|
|
38
|
+
enabled,
|
|
39
|
+
enter: (selector: string, value: string) => {
|
|
40
|
+
const field = input(selector);
|
|
41
|
+
enabled(field, selector);
|
|
42
|
+
if (field.readOnly) {
|
|
43
|
+
throw new Error(`The screen's ${selector} is read-only.\nhelp: Select an editable input or textarea.`);
|
|
44
|
+
}
|
|
45
|
+
field.focus();
|
|
46
|
+
const prototype = field instanceof HTMLInputElement ? HTMLInputElement.prototype : HTMLTextAreaElement.prototype;
|
|
47
|
+
Object.getOwnPropertyDescriptor(prototype, "value")?.set?.call(field, value);
|
|
48
|
+
field.dispatchEvent(new Event("input", { bubbles: true }));
|
|
49
|
+
field.dispatchEvent(new Event("change", { bubbles: true }));
|
|
50
|
+
},
|
|
51
|
+
form: (selector: string) => {
|
|
52
|
+
const found = element(selector);
|
|
53
|
+
if (!(found instanceof HTMLFormElement)) {
|
|
54
|
+
throw new Error(`The screen's ${selector} is not a form.\nhelp: Select a rendered form to submit.`);
|
|
55
|
+
}
|
|
56
|
+
enabled(found, selector);
|
|
57
|
+
return found;
|
|
58
|
+
},
|
|
59
|
+
input,
|
|
60
|
+
rendered,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
@@ -26,6 +26,14 @@ export const start = Effect.fnUntraced(function* (
|
|
|
26
26
|
},
|
|
27
27
|
tell: narration.tell,
|
|
28
28
|
};
|
|
29
|
-
return yield* body(
|
|
29
|
+
return yield* body(
|
|
30
|
+
Object.defineProperties(
|
|
31
|
+
{},
|
|
32
|
+
{
|
|
33
|
+
...Object.getOwnPropertyDescriptors(definition.verbs(engine, story)),
|
|
34
|
+
story: { enumerable: true, value: story },
|
|
35
|
+
},
|
|
36
|
+
),
|
|
37
|
+
);
|
|
30
38
|
}).pipe(Effect.tapCause((cause) => Effect.sync(() => captureFailure(Cause.prettyErrors(cause)[0]?.stack))));
|
|
31
39
|
});
|
|
@@ -4,9 +4,9 @@ import type { EffectTest, EffectTestOptions } from "@shivaedev/effect-test/types
|
|
|
4
4
|
import { makeEffectIt } from "@shivaedev/effect-test/vitest.ts";
|
|
5
5
|
import { type Genre, genreOf } from "#internal/genre.ts";
|
|
6
6
|
import { type AnyEffect, hooked } from "#internal/hooked.ts";
|
|
7
|
-
import { routeOutbound } from "#internal/routeOutbound.ts";
|
|
8
7
|
import type { LooseDefinition, LooseTrait } from "#internal/seed.ts";
|
|
9
8
|
import { start } from "#internal/start.ts";
|
|
9
|
+
import { routeOutbound } from "#routeOutbound.ts";
|
|
10
10
|
|
|
11
11
|
type LooseBody = (told: object, context: TestContext) => Generator<AnyEffect, unknown, never> | undefined;
|
|
12
12
|
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { installOutbound } from "#internal/installOutbound.ts";
|
|
3
|
+
|
|
4
|
+
export function routeOutbound(routes: Readonly<Record<string, string>>) {
|
|
5
|
+
return Effect.acquireRelease(
|
|
6
|
+
Effect.promise(() => installOutbound(routes, Reflect.get(globalThis, Symbol.for("test-story.dom")) === true)),
|
|
7
|
+
(restore) => Effect.sync(restore),
|
|
8
|
+
).pipe(Effect.asVoid);
|
|
9
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { computeAccessibleName, getRole } from "dom-accessibility-api";
|
|
2
|
+
import { Effect } from "effect";
|
|
3
|
+
import { withLive } from "@shivaedev/effect-test/withLive.ts";
|
|
4
|
+
import { screenElements } from "#internal/screenElements.ts";
|
|
5
|
+
|
|
6
|
+
export interface ScreenDriverOptions {
|
|
7
|
+
readonly act?: (action: () => Promise<void>) => PromiseLike<void>;
|
|
8
|
+
readonly pending: () => boolean;
|
|
9
|
+
readonly root: HTMLElement | (() => HTMLElement);
|
|
10
|
+
readonly tell: (line: string) => void;
|
|
11
|
+
readonly timeoutMs?: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type ScreenDriver = ReturnType<typeof screenDriver>;
|
|
15
|
+
|
|
16
|
+
export function screenDriver({ root, pending, tell, act = (action) => action(), timeoutMs = 3000 }: ScreenDriverOptions) {
|
|
17
|
+
const currentRoot = typeof root === "function" ? root : () => root;
|
|
18
|
+
const elements = screenElements(currentRoot);
|
|
19
|
+
function perform(line: string, action: () => void) {
|
|
20
|
+
return Effect.promise(async () => {
|
|
21
|
+
tell(line);
|
|
22
|
+
await act(async () => action());
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
function settle(options: { readonly timeoutMs?: number } = {}) {
|
|
26
|
+
const limit = options.timeoutMs ?? timeoutMs;
|
|
27
|
+
const wait = Effect.gen(function* () {
|
|
28
|
+
tell("wait for the screen's pending work");
|
|
29
|
+
do {
|
|
30
|
+
yield* Effect.promise(() => act(async () => undefined));
|
|
31
|
+
if (!pending()) {
|
|
32
|
+
tell("the screen has no pending work");
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
yield* Effect.promise(() => act(() => new Promise<void>((resolve) => setTimeout(resolve, 10))));
|
|
36
|
+
} while (true);
|
|
37
|
+
});
|
|
38
|
+
function timedOut() {
|
|
39
|
+
return Effect.die(
|
|
40
|
+
new Error(
|
|
41
|
+
`The screen did not settle within ${limit}ms.\nhelp: Release held requests and check the app's pending signal and renderer act adapter.\nScreen: ${currentRoot().textContent}`,
|
|
42
|
+
),
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
return withLive(wait.pipe(Effect.timeoutOrElse({ duration: limit, orElse: timedOut })));
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
accessible: (selector: string) => {
|
|
49
|
+
const element = elements.element(selector);
|
|
50
|
+
const accessible = { name: computeAccessibleName(element), role: getRole(element) };
|
|
51
|
+
tell(`read the accessibility of ${selector}: ${JSON.stringify(accessible)}`);
|
|
52
|
+
return accessible;
|
|
53
|
+
},
|
|
54
|
+
click: (selector: string) =>
|
|
55
|
+
perform(`click ${selector}`, () => {
|
|
56
|
+
const element = elements.element(selector);
|
|
57
|
+
elements.enabled(element, selector);
|
|
58
|
+
element.focus();
|
|
59
|
+
element.click();
|
|
60
|
+
}),
|
|
61
|
+
enter: (selector: string, value: string) => perform(`enter a value in ${selector}`, () => elements.enter(selector, value)),
|
|
62
|
+
settle,
|
|
63
|
+
submit: (selector = "form") => perform(`submit ${selector}`, () => elements.form(selector).requestSubmit()),
|
|
64
|
+
text: (selector?: string) => {
|
|
65
|
+
const text = (selector === undefined ? elements.rendered(currentRoot(), "root") : elements.element(selector)).innerText;
|
|
66
|
+
tell(`read ${selector ?? "the screen"}: ${JSON.stringify(text)}`);
|
|
67
|
+
return text;
|
|
68
|
+
},
|
|
69
|
+
value: (selector: string) => {
|
|
70
|
+
const value = elements.input(selector).value;
|
|
71
|
+
tell(`read the value in ${selector}: ${JSON.stringify(value)}`);
|
|
72
|
+
return value;
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
package/src/server/storyKit.ts
CHANGED
|
@@ -55,7 +55,7 @@ export interface StoryKitDefinition<
|
|
|
55
55
|
readonly outbound?: Readonly<Record<string, string>>;
|
|
56
56
|
readonly run?: RunHooks<TEngine, TStep, TFailed, TDiagnose>;
|
|
57
57
|
readonly runIf?: boolean;
|
|
58
|
-
readonly stages: readonly
|
|
58
|
+
readonly stages: readonly TStage[];
|
|
59
59
|
readonly verbs: (engine: TEngine, story: Story<TEngine, TStep | TFailed | TDiagnose>) => TVerbs;
|
|
60
60
|
}
|
|
61
61
|
|
|
@@ -101,8 +101,8 @@ export interface StoryKit<TEngine, TStage extends string, TVerbs, TRun extends A
|
|
|
101
101
|
|
|
102
102
|
export function storyKit<
|
|
103
103
|
TCreate extends () => unknown,
|
|
104
|
-
const TStage extends string,
|
|
105
|
-
TVerbs extends object,
|
|
104
|
+
const TStage extends string = never,
|
|
105
|
+
TVerbs extends object = object,
|
|
106
106
|
TProvided = never,
|
|
107
107
|
TAfter extends AfterResults<TStage> = Record<never, never>,
|
|
108
108
|
TStep extends AnyEffect = never,
|
|
@@ -114,8 +114,8 @@ export function storyKit<
|
|
|
114
114
|
// Keep explicit engine and effect type arguments accepted by existing kits.
|
|
115
115
|
export function storyKit<
|
|
116
116
|
TEngine,
|
|
117
|
-
const TStage extends string,
|
|
118
|
-
TVerbs extends object,
|
|
117
|
+
const TStage extends string = never,
|
|
118
|
+
TVerbs extends object = object,
|
|
119
119
|
TProvided = never,
|
|
120
120
|
TCreate extends AnyEffect = never,
|
|
121
121
|
TAfter extends { readonly [TKey in TStage]?: AnyEffect } = Record<never, never>,
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"routeOutbound.d.ts","sourceRoot":"","sources":["../../../src/server/internal/routeOutbound.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAehC,wBAAsB,eAAe,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,MAAM,UAAQ,uBAgD7F;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,4DAKrE"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"routeOutbound.js","sourceRoot":"","sources":["../../../src/server/internal/routeOutbound.ts"],"names":[],"mappings":"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"}
|
package/docs/north-star.md
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
# North star
|
|
2
|
-
|
|
3
|
-
## The problem
|
|
4
|
-
|
|
5
|
-
Agents now write most of the code, tests included, and a test file an agent writes tends to be hundreds of lines long. Each test rebuilds its setup by hand, a little differently from the last, and where rebuilding a complex setup is too much work, it mocks the part that was hard to build. Nobody reads these files. A human could ask an agent what a suite actually tests, but nobody asks consistently enough for that to be a safety net, so we lose sight of what we test exactly when more code is written than ever.
|
|
6
|
-
|
|
7
|
-
## The ideal
|
|
8
|
-
|
|
9
|
-
A test reads like a short English story: what exists, what someone does, and what is true afterwards. Its setup is a list of traits in the domain's words, such as `ovenIsLit()` and `hasDough(3)`, its actions go through the application's real verbs, and the real engine runs between them. Everything that is not specific to this one test lives elsewhere, written once:
|
|
10
|
-
|
|
11
|
-
- once per engine, a story kit that knows how to create the engine, seed it stage by stage, step it and show its state;
|
|
12
|
-
- once per feature, the traits and verbs that name that feature in domain words;
|
|
13
|
-
- in each test, only the story.
|
|
14
|
-
|
|
15
|
-
This changes the cost and benefit of a good test for both readers. For an agent, a story test is the cheaper one to write and to read on every later visit, because it is a handful of lines that reuse what already exists. Because every story runs the whole engine, each test also checks, almost by accident, the parts its author never thought about and would have mocked. For a human, a spec that reads as English leaves no excuse not to read it. With so little noise around the signal, an agent that writes a sloppy or pointless test produces something a reviewer sees at a glance.
|
|
16
|
-
|
|
17
|
-
## What good looks like
|
|
18
|
-
|
|
19
|
-
- A spec is a few lines of domain words. If a spec needs a helper, a loop or hand-built state, a trait or verb is missing from the kit.
|
|
20
|
-
- An impossible setup refuses loudly instead of seeding a state the application could never reach.
|
|
21
|
-
- A failure is a console version of stopping in a debugger: the whole story, where it stopped and the spec line there, and the engine's state at that moment, written so an agent with no knowledge of this package can fix the test, and complete enough that a CI log alone is enough to reproduce it.
|
|
22
|
-
- The kit author writes hooks and the package infers every type from them. There is one kit, built on Effect: a hook is a plain function, or a generator function when it needs a service or fails with a typed error, so no definition writes `Effect.gen` and a plain engine needs no Effect at all.
|
|
23
|
-
- A spec writes only its story. The kit hands it an `it` that already knows the engine's services, so a spec never builds a test harness, and names every test after its genre, so a report shows which engine it ran and a run can pick one engine's stories.
|
|
24
|
-
|
|
25
|
-
## Trade-offs
|
|
26
|
-
|
|
27
|
-
- **Real engine over speed.** A story is slower than a unit test with a mock. We accept that, and keep engines fast enough to step in a test instead of faking them.
|
|
28
|
-
- **Hooks over freedom.** The kit fixes the shape of an engine: create, stages, after-stage hooks, a step, a state to inspect, verbs. An engine that does not fit should change its kit's hooks, not bypass the kit.
|
|
29
|
-
- **Cheap locations over complete ones.** The failure shows one spec line, where the story stopped, found by matching the spec's path in the failure's stack or in the call site of the story's last line. A failure raised deep inside an Effect verb may have none; finding one would take machinery we do not want.
|
|
30
|
-
|
|
31
|
-
## What it will not do
|
|
32
|
-
|
|
33
|
-
- Run without Vitest, or print a failure anywhere but the failing test's own error.
|
|
34
|
-
- Own any application's traits, verbs or steps; they belong to the application's `test-support/`.
|
|
35
|
-
- Mock the application's own code, or help a test reach into an engine's internals. A fake belongs only at a true external boundary, and the kit's `create` is where it is wired in.
|
package/docs/roadmap.md
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
# Roadmap
|
|
2
|
-
|
|
3
|
-
This file owns the story kit's implementation, next work and open questions. The [framework roadmap](https://github.com/ShivaeDev/platform/blob/main/docs/framework/roadmap.md) owns composed application features and host-level validation. Those roadmaps link to each other rather than repeating a completion checklist.
|
|
4
|
-
|
|
5
|
-
## Built
|
|
6
|
-
|
|
7
|
-
- [x] `storyKit(definition)`: one Effect kit per engine, with every type and the services its layer provides inferred from its hooks. Plain engines plug in with plain functions.
|
|
8
|
-
- [x] Traits seeded stage by stage, with a hook after each stage, and refusal of an impossible setup.
|
|
9
|
-
- [x] The kit's own `it`, built on `@shivaedev/effect-test`, which hands the body the verbs and the story and names every test after the kit's genre. Vitest tags per genre are opt-in through `genreTags(...kitNames)` in the Vitest config.
|
|
10
|
-
- [x] `story.runUntil(condition)`: steps the engine until the condition holds, stops on what `run.failed` reports, and stops after a step budget with a diagnosis, keeping every hook's typed failure in its error channel.
|
|
11
|
-
- [x] A failed test prints a short guide, its story, where it stopped with the spec line there, and the engine's state, inline or in a file under `node_modules/.cache/test-story/`, broken at 120 columns.
|
|
12
|
-
- [x] `domEnvironment` keeps Node fetch classes for an in-process host and happy-dom fetch for the client; `domFetch(baseUrl)` connects the window to that host, with a starter story proving Better Auth sign-up, sign-in, session reads and sign-out on PostgreSQL through cookies.
|
|
13
|
-
- [x] `domNavigate` loads a routed document into happy-dom, updates its location and returns a response whose body remains readable. [Document transport stories](../src/server/documentTransport.dom.spec.ts) assert the rendered heading, location and response body.
|
|
14
|
-
- [x] `outbound` routes exact hostnames to fake HTTP servers while preserving request paths, queries, methods, headers and bodies, and restores Node routing after successful and failed stories. [Outbound stories](../src/server/outbound.spec.ts) assert routing and restoration; [document transport stories](../src/server/documentTransport.dom.spec.ts) cover redirects, cookies and XMLHttpRequest; [DOM refusal tests](../src/server/domOutbound.spec.ts) assert that unrouted external fetches, document requests and redirects fail the story even when application code catches the error.
|
|
15
|
-
- [x] In-process host creation separates request traces from the story span: requests without trace headers get distinct root traces, supplied trace headers continue their trace, request work stays under the request span, and traits and steps stay under the story span. [Host tracing stories](../src/server/hostTracing.spec.ts) assert span parents, trace IDs and completed request spans.
|
|
16
|
-
- [x] Creation, traits and stage `after` hooks can return Effects directly, with services from the kit's Layer and resources released in the story's Scope, covered by [Effect hook stories](../src/server/effectHooks.spec.ts).
|
|
17
|
-
- [x] Kit hooks and steps support a live clock and a per-story TestClock override. [Live-time stories](../src/server/liveTime.spec.ts) cover polling real workers with `withLive` without advancing virtual token expiry.
|
|
18
|
-
|
|
19
|
-
## Next
|
|
20
|
-
|
|
21
|
-
- [ ] Move the Work Fleet engine's hand-written story DSL onto the kit and let what it needs decide the next hook.
|
|
22
|
-
|
|
23
|
-
## Open questions
|
|
24
|
-
|
|
25
|
-
- A long story prints in full. Should a story of thousands of lines print its traits and its last lines, and write the rest to a file the way a large engine state is written?
|
|
26
|
-
- `inspect` runs synchronously when the test has failed. An engine whose state lives behind an Effect service cannot be read then. Is that worth an Effect inspect, captured when the failure happens?
|