@wcstack/testing 4.7.0 → 4.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +21 -0
- package/README.md +21 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.esm.js +93 -14
- package/dist/index.esm.js.map +1 -1
- package/package.json +3 -3
package/README.ja.md
CHANGED
|
@@ -45,6 +45,7 @@ npm install -D @wcstack/testing @wcstack/state @wcstack/server vitest happy-dom
|
|
|
45
45
|
| `bootstrap` | 先に走らせる登録関数。既定 `[bootstrapState]`(`@wcstack/state` から遅延 import)。ページが使うもの — `bootstrapRouter`・`bootstrapFetch` … — を関数か非同期ローダー `async () => (await import("@wcstack/router")).bootstrapRouter()` で足す。wcstack の bootstrap は全て冪等 |
|
|
46
46
|
| `stateTagName` | `bootstrapState({ tagNames })` で state タグを改名したときの名前(既定 `wcs-state`) |
|
|
47
47
|
| `maxIterations` | 待機中に挿入された要素を拾う安定化ループの回数(既定 10) |
|
|
48
|
+
| `resolveModule` | 4.9 から。`(url: URL) => string \| URL \| null` — `<wcs-state src>` のモジュールを読むローカルのファイル。`@wcstack/server` の `renderToString` と同じオプションで、ページが名指す URL(`document.baseURI` で解決したもの — happy-dom の下では `http:` の URL)を受け取り、パスか `file:` URL を返す。Node のモジュールローダは `file:` と `data:` の URL しか読まない。mount の間だけ置かれ、モジュールは mount ごとに読み直すので、テストの間で状態のオブジェクトは共有されない。これが無いか何も返さないと、状態の初期化はそう言って失敗する — ルートの `<wcs-state>` なら `mount()` がそれで reject する。`.json` の `src` は従来どおり fetch で読む。[自分のファイルにある状態](#自分のファイルにある状態) を参照 |
|
|
48
49
|
|
|
49
50
|
返る `MountedApp`:
|
|
50
51
|
|
|
@@ -63,6 +64,26 @@ npm install -D @wcstack/testing @wcstack/state @wcstack/server vitest happy-dom
|
|
|
63
64
|
- ブラウザ風の `URL.createObjectURL` があればプロセスにつき 1 回それを無効化する — Node は `blob:` URL を import できず、これが無いとインライン `<script type="module">` の state が永久に読み込み中になる(ローダーは SSR と同じく `data:` URL 経路に倒れる)。
|
|
64
65
|
- happy-dom の `textContent` / `innerText` setter を DOM 仕様どおりに文字列化するよう包む — happy-dom は数値 `0` を空文字にし(`innerText` は数値で throw する)、`textContent: count` のバインドが 0 で消えるのは happy-dom 下だけになるため。
|
|
65
66
|
|
|
67
|
+
読み込むものの無い `<wcs-state>` — `state` / `src` / `json` 属性もインライン `<script type="module">` も無いもの — は `setInitialState()` を待ちますが、テストは `mount()` が返る前にそれを呼べません。4.9 から、`mount()` は待たずに reject し、代わりに何を書けばよいか(`json=`、インラインスクリプト、または `src` と `resolveModule`)を言います。以前は resolve せず、テストは何も言わずにタイムアウトしました。ボリューム(`mount=`)も調べます。`bind-component` の要素と `<template>` の中の要素は調べません。
|
|
68
|
+
|
|
69
|
+
#### 自分のファイルにある状態
|
|
70
|
+
|
|
71
|
+
4.9 から、状態をファイルに置くページ — `<wcs-state src="./state.js">`、厳しい CSP の下で文書が勧める形 — は `resolveModule` で mount します。ページが名指す URL をそのファイルへ写します:
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
import { mount } from "@wcstack/testing";
|
|
75
|
+
|
|
76
|
+
// public/index.html に <wcs-state src="./state.js"> があり、このテストは tests/ にある
|
|
77
|
+
const resolveModule = (url: URL) => new URL("../public" + url.pathname, import.meta.url);
|
|
78
|
+
|
|
79
|
+
const app = await mount(`
|
|
80
|
+
<wcs-state src="./state.js"></wcs-state>
|
|
81
|
+
<p data-wcs="textContent: total"></p>
|
|
82
|
+
`, { resolveModule });
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
`(url) => join(publicDir, url.pathname)` でも構いません。URL は上のように連結で書いてください: Vite(したがって vitest)は文字列リテラルやテンプレートリテラルで書いた `new URL("…", import.meta.url)` を、そのファイルではない資産の URL に書き換えます。状態は自分の補助モジュール(`./lib.js`)を `import` してよく、それはファイルの隣で解決されます。インライン `<script type="module">` の状態は、Node の下では相対モジュールを `import` できません: `data:` URL から評価されるので、`./lib.js` を解決する基底がありません(そのときのエラーは CSP の文書を指しますが、テストには当てはまりません)。そうした状態は自分のファイルに置き、`resolveModule` を使ってください。
|
|
86
|
+
|
|
66
87
|
### `settle() → Promise<void>`
|
|
67
88
|
|
|
68
89
|
マイクロタスク 2 段とマクロタスク 1 段 — 書き込みが DOM に届くのに十分な待ち。`write()`・`fire()`・任意の DOM イベントの後に使います。
|
package/README.md
CHANGED
|
@@ -45,6 +45,7 @@ Registers the elements, inserts `html`, and resolves once everything under the r
|
|
|
45
45
|
| `bootstrap` | Registration functions to run first. Default `[bootstrapState]` (imported lazily from `@wcstack/state`). Add what the page uses — `bootstrapRouter`, `bootstrapFetch`, … — as functions or async loaders: `async () => (await import("@wcstack/router")).bootstrapRouter()`. Every wcstack bootstrap is idempotent |
|
|
46
46
|
| `stateTagName` | The state tag when `bootstrapState({ tagNames })` renamed it (default `wcs-state`) |
|
|
47
47
|
| `maxIterations` | Stabilization rounds for elements inserted while waiting (default 10) |
|
|
48
|
+
| `resolveModule` | Since 4.9. `(url: URL) => string \| URL \| null` — the local file a `<wcs-state src>` module is read from, the same option as `@wcstack/server`'s `renderToString`: it gets the URL the page names (resolved against `document.baseURI` — an `http:` URL under happy-dom) and returns a path or a `file:` URL. Node's module loader reads `file:` and `data:` URLs only. Installed for the length of the mount; the module is imported anew for each mount, so tests do not share a state object. Without it, or when it returns nothing, the state fails to initialize with a message saying so — for the root `<wcs-state>`, `mount()` rejects with it. A `.json` `src` is fetched, as before. See [A state in its own file](#a-state-in-its-own-file) |
|
|
48
49
|
|
|
49
50
|
The returned `MountedApp`:
|
|
50
51
|
|
|
@@ -63,6 +64,26 @@ The returned `MountedApp`:
|
|
|
63
64
|
- it disables `URL.createObjectURL` once per process when a browser-style one is present — Node cannot import `blob:` URLs, and without this an inline `<script type="module">` state never finishes loading (the loader then takes its `data:` URL path, as SSR does);
|
|
64
65
|
- it wraps happy-dom's `textContent` / `innerText` setters to stringify like the DOM spec — happy-dom turns a numeric `0` into an empty string (and `innerText` throws on numbers), so a `textContent: count` binding would blank out at zero only under happy-dom.
|
|
65
66
|
|
|
67
|
+
A `<wcs-state>` with nothing to load — no `state`, `src` or `json` attribute and no inline `<script type="module">` — waits for `setInitialState()`, which a test cannot call before `mount()` returns. Since 4.9, `mount()` rejects at once and says what to write instead (`json=`, an inline script, or `src` with `resolveModule`); it used to never resolve, and the test timed out without a word. Volumes (`mount=`) are checked too; `bind-component` elements and elements inside a `<template>` are not.
|
|
68
|
+
|
|
69
|
+
#### A state in its own file
|
|
70
|
+
|
|
71
|
+
Since 4.9, a page that keeps its state in a file — `<wcs-state src="./state.js">`, the form the documents prefer under a strict CSP — mounts with `resolveModule`, which maps the URL the page names to the file:
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
import { mount } from "@wcstack/testing";
|
|
75
|
+
|
|
76
|
+
// public/index.html has <wcs-state src="./state.js">; this test is in tests/
|
|
77
|
+
const resolveModule = (url: URL) => new URL("../public" + url.pathname, import.meta.url);
|
|
78
|
+
|
|
79
|
+
const app = await mount(`
|
|
80
|
+
<wcs-state src="./state.js"></wcs-state>
|
|
81
|
+
<p data-wcs="textContent: total"></p>
|
|
82
|
+
`, { resolveModule });
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
`(url) => join(publicDir, url.pathname)` works as well. Write the URL as a concatenation, as above: Vite (and so vitest) rewrites `new URL("…", import.meta.url)` written with a string or template literal into an asset URL that is not the file. The state may `import` its helpers (`./lib.js`) — they resolve next to the file. An inline `<script type="module">` state cannot `import` a relative module under Node: it is evaluated from a `data:` URL, which has no base for `./lib.js` (the error then points at the CSP guide, which does not apply to a test). Put such a state in its own file and use `resolveModule`.
|
|
86
|
+
|
|
66
87
|
### `settle() → Promise<void>`
|
|
67
88
|
|
|
68
89
|
Two microtask turns and one macrotask — enough for a write to reach the DOM. Use it after `write()`, `fire()`, or any DOM event.
|
package/dist/index.d.ts
CHANGED
|
@@ -28,6 +28,17 @@ interface MountOptions {
|
|
|
28
28
|
readonly stateTagName?: string;
|
|
29
29
|
/** Passed to `waitForReady` (default 10 stabilization rounds). */
|
|
30
30
|
readonly maxIterations?: number;
|
|
31
|
+
/**
|
|
32
|
+
* The local file a `<wcs-state src>` module is read from. It gets the URL the page names (resolved
|
|
33
|
+
* against `document.baseURI`, an `http:` URL under happy-dom, which Node's module loader does not
|
|
34
|
+
* read — it reads `file:` and `data:` URLs only) and returns a path or a `file:` URL, e.g.
|
|
35
|
+
* `(url) => new URL("." + url.pathname, import.meta.url)`. The same contract as
|
|
36
|
+
* `renderToString({ resolveModule })` (`@wcstack/server`). Installed for the length of the mount;
|
|
37
|
+
* each mount imports the module anew, so tests do not share a state object. Without it — or when it
|
|
38
|
+
* returns nothing — a `src` module state fails to initialize with a message saying so. A `.json`
|
|
39
|
+
* `src` is fetched, as before.
|
|
40
|
+
*/
|
|
41
|
+
readonly resolveModule?: (url: URL) => string | URL | null | undefined;
|
|
31
42
|
}
|
|
32
43
|
interface StateHandle {
|
|
33
44
|
/** The `<wcs-state>` element itself. */
|
package/dist/index.esm.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { pathToFileURL } from 'node:url';
|
|
1
2
|
import { waitForReady, installGlobals } from '@wcstack/server';
|
|
2
3
|
|
|
3
4
|
/**
|
|
@@ -66,6 +67,71 @@ function patchTextSetters() {
|
|
|
66
67
|
Object.defineProperty(proto, marker, { value: true, enumerable: false, configurable: true });
|
|
67
68
|
}
|
|
68
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* The global key a renderer installs its module resolver under, for `<wcs-state src>` (the
|
|
72
|
+
* ssr-snapshot protocol's `SSR_MODULE_RESOLVER_KEY`, /protocol/ssr-snapshot.ts): `Symbol.for`, so
|
|
73
|
+
* `@wcstack/state` reads the same key without either package importing the other's copy.
|
|
74
|
+
*/
|
|
75
|
+
const MODULE_RESOLVER_KEY = Symbol.for("wcstack.ssr.resolveModule");
|
|
76
|
+
/** The number of the mount (each mount's `src` modules are its own). */
|
|
77
|
+
let mounts = 0;
|
|
78
|
+
/**
|
|
79
|
+
* Puts a resolver for `<wcs-state src>` modules on the global key for the length of one mount, as
|
|
80
|
+
* `renderToString` does for a render: `file:` / `data:` are imported as they are, any other URL from
|
|
81
|
+
* the file `resolve` returns — with the mount's number in its query, since Node keeps a module per URL
|
|
82
|
+
* (without it, every mount would get the first mount's state object). Without `resolve`, or when it
|
|
83
|
+
* returns nothing, it throws saying what to do (Node's own error was `ERR_UNSUPPORTED_ESM_URL_SCHEME`).
|
|
84
|
+
* Returns the function that puts back what was there.
|
|
85
|
+
*/
|
|
86
|
+
function installModuleResolver(resolve) {
|
|
87
|
+
const g = globalThis;
|
|
88
|
+
const had = Object.prototype.hasOwnProperty.call(g, MODULE_RESOLVER_KEY);
|
|
89
|
+
const previous = g[MODULE_RESOLVER_KEY];
|
|
90
|
+
const n = ++mounts;
|
|
91
|
+
g[MODULE_RESOLVER_KEY] = (url) => {
|
|
92
|
+
const u = new URL(url);
|
|
93
|
+
if (u.protocol === "file:" || u.protocol === "data:")
|
|
94
|
+
return null;
|
|
95
|
+
const local = resolve?.(u);
|
|
96
|
+
if (local === null || local === undefined) {
|
|
97
|
+
throw new Error(`@wcstack/testing: <wcs-state src> names ${url}, which Node cannot import (its loader reads file: and data: URLs). ` +
|
|
98
|
+
(resolve === undefined
|
|
99
|
+
? `Pass mount(html, { resolveModule }) to map it to a local file — e.g. resolveModule: (url) => new URL("." + url.pathname, import.meta.url) — or write the state in the HTML (json=, or an inline <script type="module">).`
|
|
100
|
+
: "resolveModule returned no file for it."));
|
|
101
|
+
}
|
|
102
|
+
const file = local instanceof URL ? new URL(local.href) : /^file:/i.test(local) ? new URL(local) : pathToFileURL(local);
|
|
103
|
+
file.searchParams.set("wcs-mount", String(n));
|
|
104
|
+
return file.href;
|
|
105
|
+
};
|
|
106
|
+
return () => {
|
|
107
|
+
if (had)
|
|
108
|
+
g[MODULE_RESOLVER_KEY] = previous;
|
|
109
|
+
else
|
|
110
|
+
delete g[MODULE_RESOLVER_KEY];
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* A `<wcs-state>` in `html` that would wait for `setInitialState()`: none of `state` / `src` / `json`
|
|
115
|
+
* and no inline `<script type="module">` (the element's resolution order, @wcstack/state README "State
|
|
116
|
+
* Initialization"). A test cannot call `setInitialState()` before `mount()` returns, so `mount()` would
|
|
117
|
+
* never resolve — it used to time out without a word. A root or a volume (`mount=`); not a
|
|
118
|
+
* `bind-component` element (its state is its host's property), and not one inside a `<template>`
|
|
119
|
+
* (template content is not in the page: a `for:` / `if:` row, a route, a DCC definition).
|
|
120
|
+
*/
|
|
121
|
+
function findWaitingState(html, stateTagName) {
|
|
122
|
+
const probe = document.createElement("template");
|
|
123
|
+
probe.innerHTML = html;
|
|
124
|
+
return [...probe.content.querySelectorAll(stateTagName)].find((el) => !el.hasAttribute("bind-component") &&
|
|
125
|
+
!el.hasAttribute("state") &&
|
|
126
|
+
!el.hasAttribute("src") &&
|
|
127
|
+
!el.hasAttribute("json") &&
|
|
128
|
+
el.querySelector('script[type="module"]') === null);
|
|
129
|
+
}
|
|
130
|
+
/** `<wcs-state mount="cfg">` — the element as written (its start tag), for a message. */
|
|
131
|
+
function startTag(el) {
|
|
132
|
+
const attributes = [...el.attributes].map((a) => (a.value === "" ? ` ${a.name}` : ` ${a.name}="${a.value}"`));
|
|
133
|
+
return `<${el.localName}${attributes.join("")}>`;
|
|
134
|
+
}
|
|
69
135
|
async function defaultBootstraps() {
|
|
70
136
|
// Lazy: the element classes bind their base class at module evaluation, which
|
|
71
137
|
// must happen after the DOM globals exist (installDom() in bare Node).
|
|
@@ -73,29 +139,42 @@ async function defaultBootstraps() {
|
|
|
73
139
|
return [bootstrapState];
|
|
74
140
|
}
|
|
75
141
|
async function mount(html, options = {}) {
|
|
142
|
+
const stateTagName = options.stateTagName ?? "wcs-state";
|
|
143
|
+
const waiting = findWaitingState(html, stateTagName);
|
|
144
|
+
if (waiting !== undefined) {
|
|
145
|
+
throw new Error(`@wcstack/testing: ${startTag(waiting)} has no state to load (no state, src or json attribute, and no inline <script type="module">), ` +
|
|
146
|
+
`so it waits for setInitialState() — which a test cannot call before mount() returns: mount() would never resolve. ` +
|
|
147
|
+
`Give it its state in the HTML: json='{"count": 0}', an inline <script type="module">export default { … }</script>, ` +
|
|
148
|
+
`or src="./state.js" with mount(html, { resolveModule }).`);
|
|
149
|
+
}
|
|
76
150
|
patchInlineScriptLoader();
|
|
77
151
|
patchTextSetters();
|
|
78
152
|
for (const bootstrap of options.bootstrap ?? (await defaultBootstraps())) {
|
|
79
153
|
await bootstrap();
|
|
80
154
|
}
|
|
81
|
-
const stateTagName = options.stateTagName ?? "wcs-state";
|
|
82
155
|
let root;
|
|
83
156
|
let container;
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
157
|
+
const restoreResolver = installModuleResolver(options.resolveModule);
|
|
158
|
+
try {
|
|
159
|
+
if (options.root === "shadow") {
|
|
160
|
+
const host = document.createElement("div");
|
|
161
|
+
host.setAttribute("data-wcs-testing-host", "");
|
|
162
|
+
const shadow = host.attachShadow({ mode: "open" });
|
|
163
|
+
shadow.innerHTML = html;
|
|
164
|
+
document.body.appendChild(host);
|
|
165
|
+
root = shadow;
|
|
166
|
+
container = host;
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
document.body.innerHTML = html;
|
|
170
|
+
root = document;
|
|
171
|
+
container = document.body;
|
|
172
|
+
}
|
|
173
|
+
await waitForReady(root, { maxIterations: options.maxIterations });
|
|
92
174
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
root = document;
|
|
96
|
-
container = document.body;
|
|
175
|
+
finally {
|
|
176
|
+
restoreResolver();
|
|
97
177
|
}
|
|
98
|
-
await waitForReady(root, { maxIterations: options.maxIterations });
|
|
99
178
|
return {
|
|
100
179
|
root,
|
|
101
180
|
container,
|
package/dist/index.esm.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.esm.js","sources":["../../src/mount.ts","../../src/settle.ts","../../src/fire.ts","../../src/installDom.ts"],"sourcesContent":["/**\n * mount() — the README recipe (\"Testing Your Page\", @wcstack/state) as one call:\n * register the elements, insert the HTML, wait for every element and binding\n * under it, and hand back typed accessors for the page's `<wcs-state>`s.\n *\n * The wait is `@wcstack/server`'s `waitForReady` — the same stabilization loop\n * `renderToString` performs, so `<wcs-router>`'s first route, a `$connectedCallback`\n * that inserts more elements, and `<wcs-state>`'s binding construction are all\n * covered by the one call (docs/app-testing-and-typescript-impl-plan.md D11).\n */\n\nimport { waitForReady } from \"@wcstack/server\";\n\nexport type BootstrapFunction = () => void | Promise<void>;\n\nexport interface MountOptions {\n /**\n * Where to insert the HTML. `\"document\"` (default) replaces `document.body`'s\n * content; `\"shadow\"` puts it inside an open ShadowRoot on a fresh host element\n * appended to `document.body` (bindings then scope to that root).\n */\n readonly root?: \"document\" | \"shadow\";\n /**\n * Element registrations to run before inserting the HTML. Default:\n * `[bootstrapState]` imported lazily from `@wcstack/state`. Add the packages\n * the page uses — `bootstrapRouter`, `bootstrapFetch`, … — as functions or\n * async loaders (`async () => (await import(\"@wcstack/router\")).bootstrapRouter()`);\n * every wcstack bootstrap is idempotent, so calling them per test is safe.\n */\n readonly bootstrap?: readonly BootstrapFunction[];\n /** Tag name of the state element when `bootstrapState({ tagNames })` renamed it (default `wcs-state`). */\n readonly stateTagName?: string;\n /** Passed to `waitForReady` (default 10 stabilization rounds). */\n readonly maxIterations?: number;\n}\n\nexport interface StateHandle {\n /** The `<wcs-state>` element itself. */\n readonly element: HTMLElement;\n /** Read through a readonly proxy — the value `fn` returns is passed back. */\n read<T>(fn: (state: any) => T): T;\n /** Write through a writable proxy, exactly as a handler does. Follow with `await settle()`. */\n write(fn: (state: any) => void | Promise<void>): Promise<void>;\n}\n\nexport interface MountedApp {\n /** The root the HTML lives under: `document` or the ShadowRoot. Query it. */\n readonly root: Document | ShadowRoot;\n /** The node whose children are the mounted HTML: `document.body` or the shadow host. */\n readonly container: Element;\n /**\n * Accessor for the root `<wcs-state>` (v2: one state tree per root — volumes\n * (`mount=`) and `bind-component` elements are not it). Throws if absent.\n */\n state(): StateHandle;\n /** Remove the mounted HTML. */\n unmount(): void;\n}\n\ninterface StateElementLike extends HTMLElement {\n createState(mutability: \"readonly\" | \"writable\", callback: (state: any) => void): void;\n createStateAsync(mutability: \"readonly\" | \"writable\", callback: (state: any) => Promise<void>): Promise<void>;\n}\n\nlet inlineScriptLoaderPatched = false;\n\n/**\n * Route `<wcs-state>` inline `<script type=\"module\">` through the `data:` URL\n * loader. Node cannot import `blob:` URLs, so the browser path (`URL.createObjectURL`)\n * would leave an inline-script state pending forever; the loader falls back to a\n * `data:` URL when `createObjectURL` is absent — the same switch `@wcstack/server`\n * flips for SSR. Applied once per process, only when a browser-style\n * `createObjectURL` is present (i.e. under vitest's happy-dom environment).\n */\nfunction patchInlineScriptLoader(): void {\n if (inlineScriptLoaderPatched) return;\n inlineScriptLoaderPatched = true;\n if (typeof URL.createObjectURL === \"function\") {\n (URL as unknown as { createObjectURL: unknown }).createObjectURL = undefined;\n }\n}\n\nconst TEXT_SETTER_PATCHED = Symbol.for(\"wcstack.testing.textSetterPatched\");\n\n/**\n * happy-dom's `textContent` setter treats a numeric `0` as empty (and `innerText`\n * throws on any non-string), whereas browsers stringify: `el.textContent = 0`\n * renders \"0\". A `textContent: count` binding assigns the raw number, so a count\n * that reaches zero would vanish only under happy-dom. Wrap the setters on every\n * prototype that owns one (Node, Element, HTMLElement) to coerce the way the\n * DOM spec does (`null` → \"\", anything else → `String(value)`). Idempotent per\n * prototype, so a fresh window from `installDom()` gets patched too.\n */\nfunction patchTextSetters(): void {\n const targets: Array<[unknown, string]> = [\n [globalThis.Node, \"textContent\"],\n [globalThis.Element, \"textContent\"],\n [globalThis.HTMLElement, \"textContent\"],\n [globalThis.HTMLElement, \"innerText\"],\n ];\n for (const [ctor, property] of targets) {\n const proto = (ctor as { prototype?: object } | undefined)?.prototype as (Record<symbol, unknown> & object) | undefined;\n if (proto === undefined) continue;\n const marker = `${String(TEXT_SETTER_PATCHED)}:${property}`;\n // own-property check: Element.prototype inherits Node.prototype's marker, and an\n // inherited marker must not make the Element setter (the one happy-dom uses) skip.\n if (Object.prototype.hasOwnProperty.call(proto, marker)) continue;\n const descriptor = Object.getOwnPropertyDescriptor(proto, property);\n if (descriptor?.set === undefined) continue;\n const originalSet = descriptor.set;\n Object.defineProperty(proto, property, {\n ...descriptor,\n set(this: unknown, value: unknown) {\n originalSet.call(this, value === null || value === undefined ? \"\" : String(value));\n },\n });\n Object.defineProperty(proto, marker, { value: true, enumerable: false, configurable: true });\n }\n}\n\nasync function defaultBootstraps(): Promise<readonly BootstrapFunction[]> {\n // Lazy: the element classes bind their base class at module evaluation, which\n // must happen after the DOM globals exist (installDom() in bare Node).\n const { bootstrapState } = await import(\"@wcstack/state\");\n return [bootstrapState];\n}\n\nexport async function mount(html: string, options: MountOptions = {}): Promise<MountedApp> {\n patchInlineScriptLoader();\n patchTextSetters();\n\n for (const bootstrap of options.bootstrap ?? (await defaultBootstraps())) {\n await bootstrap();\n }\n\n const stateTagName = options.stateTagName ?? \"wcs-state\";\n let root: Document | ShadowRoot;\n let container: Element;\n if (options.root === \"shadow\") {\n const host = document.createElement(\"div\");\n host.setAttribute(\"data-wcs-testing-host\", \"\");\n const shadow = host.attachShadow({ mode: \"open\" });\n shadow.innerHTML = html;\n document.body.appendChild(host);\n root = shadow;\n container = host;\n } else {\n document.body.innerHTML = html;\n root = document;\n container = document.body;\n }\n\n await waitForReady(root, { maxIterations: options.maxIterations });\n\n return {\n root,\n container,\n state(...legacyArgs: never[]): StateHandle {\n if (legacyArgs.length > 0) {\n throw new Error(\n `@wcstack/testing: state(name) was removed in v2 — there is one state tree per root. ` +\n `Mount the named state onto the tree (<wcs-state mount=\"...\">) and read it by its path prefix via state().`,\n );\n }\n const element = [...root.querySelectorAll<StateElementLike>(stateTagName)]\n .find((el) => !el.hasAttribute(\"mount\") && !el.hasAttribute(\"bind-component\"));\n if (element === undefined) {\n throw new Error(`@wcstack/testing: no <${stateTagName}> under the mounted root`);\n }\n return {\n element,\n read(fn) {\n let out!: ReturnType<typeof fn>;\n element.createState(\"readonly\", (state) => {\n out = fn(state);\n });\n return out;\n },\n async write(fn) {\n await element.createStateAsync(\"writable\", async (state) => {\n await fn(state);\n });\n },\n };\n },\n unmount() {\n if (root === document) {\n document.body.innerHTML = \"\";\n } else {\n container.remove();\n }\n },\n };\n}\n","/**\n * Let a state write reach the DOM.\n *\n * `@wcstack/state` applies updates on the microtask queue; two microtask turns\n * cover the write → apply chain, and one macrotask (`setTimeout(0)`) covers\n * anything a binding defers (e.g. a `customElements.whenDefined` re-apply).\n * This is exactly the wait the README recipe uses, fixed in one place.\n */\nexport async function settle(): Promise<void> {\n await Promise.resolve();\n await Promise.resolve();\n await new Promise<void>((resolve) => setTimeout(resolve, 0));\n}\n","/**\n * Dispatch a DOM event the way a user action would: bubbling by default, a\n * `CustomEvent` when `detail` is given, a plain `Event` otherwise.\n *\n * Returns what `dispatchEvent` returns (`false` when a handler called\n * `preventDefault()`). Follow with `await settle()` before asserting the DOM.\n */\nexport function fire(target: EventTarget, type: string, init: EventInit & { detail?: unknown } = {}): boolean {\n const { detail, ...eventInit } = init;\n const event = detail !== undefined\n ? new CustomEvent(type, { bubbles: true, ...eventInit, detail })\n : new Event(type, { bubbles: true, ...eventInit });\n return target.dispatchEvent(event);\n}\n","/**\n * Bare Node (no vitest `environment: 'happy-dom'`): create a happy-dom `Window`\n * and install its globals, using the same `installGlobals` `@wcstack/server` runs\n * for SSR. Returns an async restore function that also closes the window.\n *\n * Import order matters: `@wcstack/state`'s element classes pick their base class\n * when the module is evaluated, so `mount()` imports it lazily — after this\n * function has installed `HTMLElement`. Do the same for any other wcstack\n * package you bootstrap (`async () => (await import(\"@wcstack/router\")).bootstrapRouter()`).\n *\n * `happy-dom` is an optional peer: it is only needed here. A `window` can also be\n * passed in (`installDom({ window })`) to skip the import entirely.\n */\n\nimport { installGlobals } from \"@wcstack/server\";\n\nexport interface InstallDomOptions {\n /** `window.location` for the page (default `http://localhost/`). */\n readonly url?: string;\n /** A ready-made happy-dom `Window`; when given, `happy-dom` is not imported. */\n readonly window?: unknown;\n}\n\ninterface HappyDomWindowLike {\n readonly happyDOM: { close(): Promise<void> };\n}\n\nexport async function installDom(options: InstallDomOptions = {}): Promise<() => Promise<void>> {\n const window = options.window ?? (await createWindow(options.url ?? \"http://localhost/\"));\n const restore = installGlobals(window as Parameters<typeof installGlobals>[0]);\n return async () => {\n restore();\n await (window as HappyDomWindowLike).happyDOM.close();\n };\n}\n\nasync function createWindow(url: string): Promise<unknown> {\n let mod: { Window: new (options: { url: string }) => unknown };\n try {\n mod = (await import(\"happy-dom\")) as typeof mod;\n } catch {\n throw new Error(\n \"@wcstack/testing: installDom() needs happy-dom (npm i -D happy-dom), or pass { window } — under vitest with environment: 'happy-dom' it is not needed at all\",\n );\n }\n return new mod.Window({ url });\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.esm.js","sources":["../../src/mount.ts","../../src/settle.ts","../../src/fire.ts","../../src/installDom.ts"],"sourcesContent":["/**\n * mount() — the README recipe (\"Testing Your Page\", @wcstack/state) as one call:\n * register the elements, insert the HTML, wait for every element and binding\n * under it, and hand back typed accessors for the page's `<wcs-state>`s.\n *\n * The wait is `@wcstack/server`'s `waitForReady` — the same stabilization loop\n * `renderToString` performs, so `<wcs-router>`'s first route, a `$connectedCallback`\n * that inserts more elements, and `<wcs-state>`'s binding construction are all\n * covered by the one call (docs/app-testing-and-typescript-impl-plan.md D11).\n */\n\nimport { pathToFileURL } from \"node:url\";\nimport { waitForReady } from \"@wcstack/server\";\n\nexport type BootstrapFunction = () => void | Promise<void>;\n\nexport interface MountOptions {\n /**\n * Where to insert the HTML. `\"document\"` (default) replaces `document.body`'s\n * content; `\"shadow\"` puts it inside an open ShadowRoot on a fresh host element\n * appended to `document.body` (bindings then scope to that root).\n */\n readonly root?: \"document\" | \"shadow\";\n /**\n * Element registrations to run before inserting the HTML. Default:\n * `[bootstrapState]` imported lazily from `@wcstack/state`. Add the packages\n * the page uses — `bootstrapRouter`, `bootstrapFetch`, … — as functions or\n * async loaders (`async () => (await import(\"@wcstack/router\")).bootstrapRouter()`);\n * every wcstack bootstrap is idempotent, so calling them per test is safe.\n */\n readonly bootstrap?: readonly BootstrapFunction[];\n /** Tag name of the state element when `bootstrapState({ tagNames })` renamed it (default `wcs-state`). */\n readonly stateTagName?: string;\n /** Passed to `waitForReady` (default 10 stabilization rounds). */\n readonly maxIterations?: number;\n /**\n * The local file a `<wcs-state src>` module is read from. It gets the URL the page names (resolved\n * against `document.baseURI`, an `http:` URL under happy-dom, which Node's module loader does not\n * read — it reads `file:` and `data:` URLs only) and returns a path or a `file:` URL, e.g.\n * `(url) => new URL(\".\" + url.pathname, import.meta.url)`. The same contract as\n * `renderToString({ resolveModule })` (`@wcstack/server`). Installed for the length of the mount;\n * each mount imports the module anew, so tests do not share a state object. Without it — or when it\n * returns nothing — a `src` module state fails to initialize with a message saying so. A `.json`\n * `src` is fetched, as before.\n */\n readonly resolveModule?: (url: URL) => string | URL | null | undefined;\n}\n\nexport interface StateHandle {\n /** The `<wcs-state>` element itself. */\n readonly element: HTMLElement;\n /** Read through a readonly proxy — the value `fn` returns is passed back. */\n read<T>(fn: (state: any) => T): T;\n /** Write through a writable proxy, exactly as a handler does. Follow with `await settle()`. */\n write(fn: (state: any) => void | Promise<void>): Promise<void>;\n}\n\nexport interface MountedApp {\n /** The root the HTML lives under: `document` or the ShadowRoot. Query it. */\n readonly root: Document | ShadowRoot;\n /** The node whose children are the mounted HTML: `document.body` or the shadow host. */\n readonly container: Element;\n /**\n * Accessor for the root `<wcs-state>` (v2: one state tree per root — volumes\n * (`mount=`) and `bind-component` elements are not it). Throws if absent.\n */\n state(): StateHandle;\n /** Remove the mounted HTML. */\n unmount(): void;\n}\n\ninterface StateElementLike extends HTMLElement {\n createState(mutability: \"readonly\" | \"writable\", callback: (state: any) => void): void;\n createStateAsync(mutability: \"readonly\" | \"writable\", callback: (state: any) => Promise<void>): Promise<void>;\n}\n\nlet inlineScriptLoaderPatched = false;\n\n/**\n * Route `<wcs-state>` inline `<script type=\"module\">` through the `data:` URL\n * loader. Node cannot import `blob:` URLs, so the browser path (`URL.createObjectURL`)\n * would leave an inline-script state pending forever; the loader falls back to a\n * `data:` URL when `createObjectURL` is absent — the same switch `@wcstack/server`\n * flips for SSR. Applied once per process, only when a browser-style\n * `createObjectURL` is present (i.e. under vitest's happy-dom environment).\n */\nfunction patchInlineScriptLoader(): void {\n if (inlineScriptLoaderPatched) return;\n inlineScriptLoaderPatched = true;\n if (typeof URL.createObjectURL === \"function\") {\n (URL as unknown as { createObjectURL: unknown }).createObjectURL = undefined;\n }\n}\n\nconst TEXT_SETTER_PATCHED = Symbol.for(\"wcstack.testing.textSetterPatched\");\n\n/**\n * happy-dom's `textContent` setter treats a numeric `0` as empty (and `innerText`\n * throws on any non-string), whereas browsers stringify: `el.textContent = 0`\n * renders \"0\". A `textContent: count` binding assigns the raw number, so a count\n * that reaches zero would vanish only under happy-dom. Wrap the setters on every\n * prototype that owns one (Node, Element, HTMLElement) to coerce the way the\n * DOM spec does (`null` → \"\", anything else → `String(value)`). Idempotent per\n * prototype, so a fresh window from `installDom()` gets patched too.\n */\nfunction patchTextSetters(): void {\n const targets: Array<[unknown, string]> = [\n [globalThis.Node, \"textContent\"],\n [globalThis.Element, \"textContent\"],\n [globalThis.HTMLElement, \"textContent\"],\n [globalThis.HTMLElement, \"innerText\"],\n ];\n for (const [ctor, property] of targets) {\n const proto = (ctor as { prototype?: object } | undefined)?.prototype as (Record<symbol, unknown> & object) | undefined;\n if (proto === undefined) continue;\n const marker = `${String(TEXT_SETTER_PATCHED)}:${property}`;\n // own-property check: Element.prototype inherits Node.prototype's marker, and an\n // inherited marker must not make the Element setter (the one happy-dom uses) skip.\n if (Object.prototype.hasOwnProperty.call(proto, marker)) continue;\n const descriptor = Object.getOwnPropertyDescriptor(proto, property);\n if (descriptor?.set === undefined) continue;\n const originalSet = descriptor.set;\n Object.defineProperty(proto, property, {\n ...descriptor,\n set(this: unknown, value: unknown) {\n originalSet.call(this, value === null || value === undefined ? \"\" : String(value));\n },\n });\n Object.defineProperty(proto, marker, { value: true, enumerable: false, configurable: true });\n }\n}\n\n/**\n * The global key a renderer installs its module resolver under, for `<wcs-state src>` (the\n * ssr-snapshot protocol's `SSR_MODULE_RESOLVER_KEY`, /protocol/ssr-snapshot.ts): `Symbol.for`, so\n * `@wcstack/state` reads the same key without either package importing the other's copy.\n */\nconst MODULE_RESOLVER_KEY = Symbol.for(\"wcstack.ssr.resolveModule\");\n\n/** The number of the mount (each mount's `src` modules are its own). */\nlet mounts = 0;\n\n/**\n * Puts a resolver for `<wcs-state src>` modules on the global key for the length of one mount, as\n * `renderToString` does for a render: `file:` / `data:` are imported as they are, any other URL from\n * the file `resolve` returns — with the mount's number in its query, since Node keeps a module per URL\n * (without it, every mount would get the first mount's state object). Without `resolve`, or when it\n * returns nothing, it throws saying what to do (Node's own error was `ERR_UNSUPPORTED_ESM_URL_SCHEME`).\n * Returns the function that puts back what was there.\n */\nfunction installModuleResolver(resolve: MountOptions[\"resolveModule\"]): () => void {\n const g = globalThis as Record<symbol, unknown>;\n const had = Object.prototype.hasOwnProperty.call(g, MODULE_RESOLVER_KEY);\n const previous = g[MODULE_RESOLVER_KEY];\n const n = ++mounts;\n g[MODULE_RESOLVER_KEY] = (url: string): string | null => {\n const u = new URL(url);\n if (u.protocol === \"file:\" || u.protocol === \"data:\") return null;\n const local = resolve?.(u);\n if (local === null || local === undefined) {\n throw new Error(\n `@wcstack/testing: <wcs-state src> names ${url}, which Node cannot import (its loader reads file: and data: URLs). ` +\n (resolve === undefined\n ? `Pass mount(html, { resolveModule }) to map it to a local file — e.g. resolveModule: (url) => new URL(\".\" + url.pathname, import.meta.url) — or write the state in the HTML (json=, or an inline <script type=\"module\">).`\n : \"resolveModule returned no file for it.\"),\n );\n }\n const file = local instanceof URL ? new URL(local.href) : /^file:/i.test(local) ? new URL(local) : pathToFileURL(local);\n file.searchParams.set(\"wcs-mount\", String(n));\n return file.href;\n };\n return () => {\n if (had) g[MODULE_RESOLVER_KEY] = previous;\n else delete g[MODULE_RESOLVER_KEY];\n };\n}\n\n/**\n * A `<wcs-state>` in `html` that would wait for `setInitialState()`: none of `state` / `src` / `json`\n * and no inline `<script type=\"module\">` (the element's resolution order, @wcstack/state README \"State\n * Initialization\"). A test cannot call `setInitialState()` before `mount()` returns, so `mount()` would\n * never resolve — it used to time out without a word. A root or a volume (`mount=`); not a\n * `bind-component` element (its state is its host's property), and not one inside a `<template>`\n * (template content is not in the page: a `for:` / `if:` row, a route, a DCC definition).\n */\nfunction findWaitingState(html: string, stateTagName: string): Element | undefined {\n const probe = document.createElement(\"template\");\n probe.innerHTML = html;\n return [...probe.content.querySelectorAll(stateTagName)].find((el) =>\n !el.hasAttribute(\"bind-component\") &&\n !el.hasAttribute(\"state\") &&\n !el.hasAttribute(\"src\") &&\n !el.hasAttribute(\"json\") &&\n el.querySelector('script[type=\"module\"]') === null);\n}\n\n/** `<wcs-state mount=\"cfg\">` — the element as written (its start tag), for a message. */\nfunction startTag(el: Element): string {\n const attributes = [...el.attributes].map((a) => (a.value === \"\" ? ` ${a.name}` : ` ${a.name}=\"${a.value}\"`));\n return `<${el.localName}${attributes.join(\"\")}>`;\n}\n\nasync function defaultBootstraps(): Promise<readonly BootstrapFunction[]> {\n // Lazy: the element classes bind their base class at module evaluation, which\n // must happen after the DOM globals exist (installDom() in bare Node).\n const { bootstrapState } = await import(\"@wcstack/state\");\n return [bootstrapState];\n}\n\nexport async function mount(html: string, options: MountOptions = {}): Promise<MountedApp> {\n const stateTagName = options.stateTagName ?? \"wcs-state\";\n const waiting = findWaitingState(html, stateTagName);\n if (waiting !== undefined) {\n throw new Error(\n `@wcstack/testing: ${startTag(waiting)} has no state to load (no state, src or json attribute, and no inline <script type=\"module\">), ` +\n `so it waits for setInitialState() — which a test cannot call before mount() returns: mount() would never resolve. ` +\n `Give it its state in the HTML: json='{\"count\": 0}', an inline <script type=\"module\">export default { … }</script>, ` +\n `or src=\"./state.js\" with mount(html, { resolveModule }).`,\n );\n }\n\n patchInlineScriptLoader();\n patchTextSetters();\n\n for (const bootstrap of options.bootstrap ?? (await defaultBootstraps())) {\n await bootstrap();\n }\n\n let root: Document | ShadowRoot;\n let container: Element;\n const restoreResolver = installModuleResolver(options.resolveModule);\n try {\n if (options.root === \"shadow\") {\n const host = document.createElement(\"div\");\n host.setAttribute(\"data-wcs-testing-host\", \"\");\n const shadow = host.attachShadow({ mode: \"open\" });\n shadow.innerHTML = html;\n document.body.appendChild(host);\n root = shadow;\n container = host;\n } else {\n document.body.innerHTML = html;\n root = document;\n container = document.body;\n }\n\n await waitForReady(root, { maxIterations: options.maxIterations });\n } finally {\n restoreResolver();\n }\n\n return {\n root,\n container,\n state(...legacyArgs: never[]): StateHandle {\n if (legacyArgs.length > 0) {\n throw new Error(\n `@wcstack/testing: state(name) was removed in v2 — there is one state tree per root. ` +\n `Mount the named state onto the tree (<wcs-state mount=\"...\">) and read it by its path prefix via state().`,\n );\n }\n const element = [...root.querySelectorAll<StateElementLike>(stateTagName)]\n .find((el) => !el.hasAttribute(\"mount\") && !el.hasAttribute(\"bind-component\"));\n if (element === undefined) {\n throw new Error(`@wcstack/testing: no <${stateTagName}> under the mounted root`);\n }\n return {\n element,\n read(fn) {\n let out!: ReturnType<typeof fn>;\n element.createState(\"readonly\", (state) => {\n out = fn(state);\n });\n return out;\n },\n async write(fn) {\n await element.createStateAsync(\"writable\", async (state) => {\n await fn(state);\n });\n },\n };\n },\n unmount() {\n if (root === document) {\n document.body.innerHTML = \"\";\n } else {\n container.remove();\n }\n },\n };\n}\n","/**\n * Let a state write reach the DOM.\n *\n * `@wcstack/state` applies updates on the microtask queue; two microtask turns\n * cover the write → apply chain, and one macrotask (`setTimeout(0)`) covers\n * anything a binding defers (e.g. a `customElements.whenDefined` re-apply).\n * This is exactly the wait the README recipe uses, fixed in one place.\n */\nexport async function settle(): Promise<void> {\n await Promise.resolve();\n await Promise.resolve();\n await new Promise<void>((resolve) => setTimeout(resolve, 0));\n}\n","/**\n * Dispatch a DOM event the way a user action would: bubbling by default, a\n * `CustomEvent` when `detail` is given, a plain `Event` otherwise.\n *\n * Returns what `dispatchEvent` returns (`false` when a handler called\n * `preventDefault()`). Follow with `await settle()` before asserting the DOM.\n */\nexport function fire(target: EventTarget, type: string, init: EventInit & { detail?: unknown } = {}): boolean {\n const { detail, ...eventInit } = init;\n const event = detail !== undefined\n ? new CustomEvent(type, { bubbles: true, ...eventInit, detail })\n : new Event(type, { bubbles: true, ...eventInit });\n return target.dispatchEvent(event);\n}\n","/**\n * Bare Node (no vitest `environment: 'happy-dom'`): create a happy-dom `Window`\n * and install its globals, using the same `installGlobals` `@wcstack/server` runs\n * for SSR. Returns an async restore function that also closes the window.\n *\n * Import order matters: `@wcstack/state`'s element classes pick their base class\n * when the module is evaluated, so `mount()` imports it lazily — after this\n * function has installed `HTMLElement`. Do the same for any other wcstack\n * package you bootstrap (`async () => (await import(\"@wcstack/router\")).bootstrapRouter()`).\n *\n * `happy-dom` is an optional peer: it is only needed here. A `window` can also be\n * passed in (`installDom({ window })`) to skip the import entirely.\n */\n\nimport { installGlobals } from \"@wcstack/server\";\n\nexport interface InstallDomOptions {\n /** `window.location` for the page (default `http://localhost/`). */\n readonly url?: string;\n /** A ready-made happy-dom `Window`; when given, `happy-dom` is not imported. */\n readonly window?: unknown;\n}\n\ninterface HappyDomWindowLike {\n readonly happyDOM: { close(): Promise<void> };\n}\n\nexport async function installDom(options: InstallDomOptions = {}): Promise<() => Promise<void>> {\n const window = options.window ?? (await createWindow(options.url ?? \"http://localhost/\"));\n const restore = installGlobals(window as Parameters<typeof installGlobals>[0]);\n return async () => {\n restore();\n await (window as HappyDomWindowLike).happyDOM.close();\n };\n}\n\nasync function createWindow(url: string): Promise<unknown> {\n let mod: { Window: new (options: { url: string }) => unknown };\n try {\n mod = (await import(\"happy-dom\")) as typeof mod;\n } catch {\n throw new Error(\n \"@wcstack/testing: installDom() needs happy-dom (npm i -D happy-dom), or pass { window } — under vitest with environment: 'happy-dom' it is not needed at all\",\n );\n }\n return new mod.Window({ url });\n}\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wcstack/testing",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.9.0",
|
|
4
4
|
"description": "Headless test helpers for wcstack pages: mount HTML under happy-dom, await bindings, read/write state, settle updates - the README recipe as one import.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.esm.js",
|
|
@@ -43,8 +43,8 @@
|
|
|
43
43
|
},
|
|
44
44
|
"license": "MIT",
|
|
45
45
|
"peerDependencies": {
|
|
46
|
-
"@wcstack/server": "^4.
|
|
47
|
-
"@wcstack/state": "^4.
|
|
46
|
+
"@wcstack/server": "^4.9.0",
|
|
47
|
+
"@wcstack/state": "^4.9.0",
|
|
48
48
|
"happy-dom": ">=20"
|
|
49
49
|
},
|
|
50
50
|
"peerDependenciesMeta": {
|