@moku-labs/web 1.15.1 → 1.16.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/dist/browser.d.mts +216 -26
- package/dist/browser.mjs +445 -84
- package/dist/index.cjs +445 -84
- package/dist/index.d.cts +216 -26
- package/dist/index.d.mts +216 -26
- package/dist/index.mjs +445 -84
- package/dist/{render-DLZEOe4M.cjs → render-KdufA3_b.cjs} +23 -0
- package/dist/{render-BNe0s7fr.mjs → render-UO4nimWr.mjs} +23 -1
- package/dist/testing.d.mts +383 -0
- package/dist/testing.mjs +847 -0
- package/package.json +7 -1
|
@@ -28,5 +28,28 @@ function renderVNode(vnode, region) {
|
|
|
28
28
|
region.replaceChildren();
|
|
29
29
|
(0, preact.render)(vnode, region);
|
|
30
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Commit a component's VNode into its host as a DIFF — the in-interaction render
|
|
33
|
+
* path. Unlike {@link renderVNode} (which RESETS the region: unmount → clear → mount,
|
|
34
|
+
* correct for a navigation swap of static SSR markup), this is a plain Preact
|
|
35
|
+
* `render(vnode, host)` that diffs against Preact's retained vdom, preserving focus,
|
|
36
|
+
* scroll, and uncontrolled input state across re-renders triggered by `ctx.set`.
|
|
37
|
+
*
|
|
38
|
+
* Reached ONLY through the lazy `await import("./render")` gate (the component render
|
|
39
|
+
* scheduler in `components.ts`), so an app whose islands never return a VNode never
|
|
40
|
+
* pulls Preact's `render` into its main bundle.
|
|
41
|
+
*
|
|
42
|
+
* @param vnode - The VNode produced by a component's `render(state, ctx)`.
|
|
43
|
+
* @param host - The island's host element to render into.
|
|
44
|
+
* @example
|
|
45
|
+
* ```ts
|
|
46
|
+
* const { commitVNode } = await import("./render");
|
|
47
|
+
* commitVNode(h(BoardView, { snapshot }), host);
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
function commitVNode(vnode, host) {
|
|
51
|
+
(0, preact.render)(vnode, host);
|
|
52
|
+
}
|
|
31
53
|
//#endregion
|
|
54
|
+
exports.commitVNode = commitVNode;
|
|
32
55
|
exports.renderVNode = renderVNode;
|
|
@@ -28,5 +28,27 @@ function renderVNode(vnode, region) {
|
|
|
28
28
|
region.replaceChildren();
|
|
29
29
|
render(vnode, region);
|
|
30
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Commit a component's VNode into its host as a DIFF — the in-interaction render
|
|
33
|
+
* path. Unlike {@link renderVNode} (which RESETS the region: unmount → clear → mount,
|
|
34
|
+
* correct for a navigation swap of static SSR markup), this is a plain Preact
|
|
35
|
+
* `render(vnode, host)` that diffs against Preact's retained vdom, preserving focus,
|
|
36
|
+
* scroll, and uncontrolled input state across re-renders triggered by `ctx.set`.
|
|
37
|
+
*
|
|
38
|
+
* Reached ONLY through the lazy `await import("./render")` gate (the component render
|
|
39
|
+
* scheduler in `components.ts`), so an app whose islands never return a VNode never
|
|
40
|
+
* pulls Preact's `render` into its main bundle.
|
|
41
|
+
*
|
|
42
|
+
* @param vnode - The VNode produced by a component's `render(state, ctx)`.
|
|
43
|
+
* @param host - The island's host element to render into.
|
|
44
|
+
* @example
|
|
45
|
+
* ```ts
|
|
46
|
+
* const { commitVNode } = await import("./render");
|
|
47
|
+
* commitVNode(h(BoardView, { snapshot }), host);
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
function commitVNode(vnode, host) {
|
|
51
|
+
render(vnode, host);
|
|
52
|
+
}
|
|
31
53
|
//#endregion
|
|
32
|
-
export { renderVNode };
|
|
54
|
+
export { commitVNode, renderVNode };
|
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
//#region src/plugins/spa/types.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* What a component's `render` may return:
|
|
4
|
+
* - a Preact `VNode` — committed into the host through the lazy Preact gate (`commitVNode`);
|
|
5
|
+
* - a `Node` — replaces the host's children;
|
|
6
|
+
* - a `string` — set as the host's `innerHTML`;
|
|
7
|
+
* - `void`/`undefined` — the render mutated the DOM itself (DOM-only islands → no Preact loaded).
|
|
8
|
+
*/
|
|
9
|
+
type RenderResult = import("preact").VNode | Node | string | void;
|
|
10
|
+
/**
|
|
11
|
+
* Factory that builds a component's typed per-instance state (mirrors a plugin's
|
|
12
|
+
* `createState`). Called ONCE at mount; the returned object is stored on the
|
|
13
|
+
* {@link ComponentInstance} and exposed read-only as `ctx.state`.
|
|
14
|
+
*
|
|
15
|
+
* @param ctx - The component context for this instance (state is not yet set).
|
|
16
|
+
* @returns The initial per-instance state.
|
|
17
|
+
* @example
|
|
18
|
+
* state: (ctx): BoardState => ({ boardId: ctx.params.id ?? "", cards: [] })
|
|
19
|
+
*/
|
|
20
|
+
type ComponentStateFactory<S extends object> = (ctx: ComponentContext<S>) => S;
|
|
21
|
+
/**
|
|
22
|
+
* Pure render of `(state, ctx)` → {@link RenderResult}. Called after mount-state-init
|
|
23
|
+
* and again (microtask-batched) after every `ctx.set`. Must be free of side effects
|
|
24
|
+
* beyond producing its result.
|
|
25
|
+
*
|
|
26
|
+
* @param state - The current per-instance state (read-only).
|
|
27
|
+
* @param ctx - The component context for this instance.
|
|
28
|
+
* @returns The render result to commit into the host.
|
|
29
|
+
* @example
|
|
30
|
+
* render: (state) => h(BoardView, { snapshot: state.snapshot })
|
|
31
|
+
*/
|
|
32
|
+
type ComponentRender<S extends object> = (state: Readonly<S>, ctx: ComponentContext<S>) => RenderResult;
|
|
33
|
+
/**
|
|
34
|
+
* A delegated DOM event handler. `target` is the element matched by the key's selector
|
|
35
|
+
* (already resolved via `closest` — no `instanceof`/`closest` ceremony in the body).
|
|
36
|
+
*
|
|
37
|
+
* Typed `void` for ergonomics (the void-return rule accepts async handlers returning
|
|
38
|
+
* `Promise<void>` too); the kernel ignores any returned value.
|
|
39
|
+
*
|
|
40
|
+
* @param ctx - The component context (carries the live per-instance `state`).
|
|
41
|
+
* @param event - The raw DOM event.
|
|
42
|
+
* @param target - The element matched by the selector (the host when no selector).
|
|
43
|
+
* @returns void (a returned promise is ignored by the kernel).
|
|
44
|
+
* @example
|
|
45
|
+
* (ctx, event, button) => { event.preventDefault(); ctx.set({ open: true }); }
|
|
46
|
+
*/
|
|
47
|
+
type ComponentEventHandler<S extends object> = (ctx: ComponentContext<S>, event: Event, target: Element) => void;
|
|
48
|
+
/**
|
|
49
|
+
* Declarative delegated event map. Each key is `"<type> <selector>"` (the selector is
|
|
50
|
+
* optional → a host-level listener). ONE real listener per event TYPE is attached to
|
|
51
|
+
* the host; dispatch walks `event.target.closest(selector)` within the host. All
|
|
52
|
+
* listeners are auto-removed on destroy.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* events: {
|
|
56
|
+
* "click [data-action='delete']": (ctx, _e, btn) => ctx.set(removeCard(ctx.state, btn)),
|
|
57
|
+
* "submit [data-add]": (ctx, e) => { e.preventDefault(); add(ctx); }
|
|
58
|
+
* }
|
|
59
|
+
*/
|
|
60
|
+
type ComponentEvents<S extends object> = Record<string, ComponentEventHandler<S>>;
|
|
61
|
+
/**
|
|
62
|
+
* Context handed to every component lifecycle hook, render, and event handler — the
|
|
63
|
+
* bound element + page data, plus the matched route's `params`/`meta`/`locale` and a
|
|
64
|
+
* link builder, so an island can read its route context (e.g. a `card` route's
|
|
65
|
+
* `ctx.meta.focus` + `ctx.params.id`) directly, without the page bridging it through
|
|
66
|
+
* `data-*` attributes.
|
|
67
|
+
*
|
|
68
|
+
* Generic over the per-instance state `S` (default `undefined` so every existing
|
|
69
|
+
* hooks-only island still type-checks). The additive members (`state`/`set`/`flush`/
|
|
70
|
+
* `cleanup`/`component`) are ALWAYS-PRESENT functions — never optional keys — so they
|
|
71
|
+
* never trip `exactOptionalPropertyTypes`.
|
|
72
|
+
*/
|
|
73
|
+
interface ComponentContext<S = undefined> {
|
|
74
|
+
/** The element the component instance is bound to. */
|
|
75
|
+
el: Element;
|
|
76
|
+
/** Page data extracted from the `script#__DATA__` payload. */
|
|
77
|
+
data: PageData;
|
|
78
|
+
/** Resolved path params of the route matched for the current URL (empty if unmatched). */
|
|
79
|
+
readonly params: Record<string, string | undefined>;
|
|
80
|
+
/** The matched route's `.meta()` bag (empty if unmatched). */
|
|
81
|
+
readonly meta: Record<string, unknown>;
|
|
82
|
+
/** Active locale for the current route (empty string if unknown). */
|
|
83
|
+
readonly locale: string;
|
|
84
|
+
/** Build a link to a named route by pattern substitution (same output as `app.router.toUrl`). */
|
|
85
|
+
readonly url: (name: string, params?: Record<string, string>) => string;
|
|
86
|
+
/** The live per-instance state (the object returned by `spec.state`). `undefined` for legacy hooks-only islands. */
|
|
87
|
+
readonly state: S;
|
|
88
|
+
/**
|
|
89
|
+
* Merge a patch into the per-instance state, then schedule ONE batched render.
|
|
90
|
+
* Accepts a partial object or an updater `(prev) => partial`. A no-op for legacy
|
|
91
|
+
* islands with no `state`/`render`.
|
|
92
|
+
*
|
|
93
|
+
* @param patch - A partial state object, or an updater returning one.
|
|
94
|
+
* @returns void
|
|
95
|
+
* @example
|
|
96
|
+
* ctx.set({ open: true });
|
|
97
|
+
* ctx.set(prev => ({ count: prev.count + 1 }));
|
|
98
|
+
*/
|
|
99
|
+
set(patch: Partial<S> | ((prev: Readonly<S>) => Partial<S>)): void;
|
|
100
|
+
/**
|
|
101
|
+
* Force a synchronous render now (drains any pending scheduled render). Rarely
|
|
102
|
+
* needed in app code — `ctx.set` already schedules one; mainly a test seam.
|
|
103
|
+
*
|
|
104
|
+
* @returns void
|
|
105
|
+
* @example
|
|
106
|
+
* ctx.flush();
|
|
107
|
+
*/
|
|
108
|
+
flush(): void;
|
|
109
|
+
/**
|
|
110
|
+
* Register a disposer run on `onDestroy` (subscriptions, timers, manual/global
|
|
111
|
+
* listeners the declarative `events` map cannot cover). Disposers run LIFO.
|
|
112
|
+
*
|
|
113
|
+
* @param dispose - The teardown function.
|
|
114
|
+
* @returns void
|
|
115
|
+
* @example
|
|
116
|
+
* ctx.cleanup(onPatch(p => applyPatch(ctx, p)));
|
|
117
|
+
*/
|
|
118
|
+
cleanup(dispose: () => void): void;
|
|
119
|
+
/**
|
|
120
|
+
* Resolve another island's registered `api` by name. Returns `undefined` when no
|
|
121
|
+
* provider is registered (optional-dependency semantics, mirroring `ctx.has`).
|
|
122
|
+
*
|
|
123
|
+
* @param name - The provider island's component name.
|
|
124
|
+
* @returns The provider's api, or `undefined`.
|
|
125
|
+
* @example
|
|
126
|
+
* ctx.component<LightboxApi>("lightbox")?.open(slides, index);
|
|
127
|
+
*/
|
|
128
|
+
component<T = unknown>(name: string): T | undefined;
|
|
129
|
+
}
|
|
130
|
+
/** Lifecycle hooks a component may implement. Generic over the per-instance state `S`. */
|
|
131
|
+
interface ComponentHooks<S = undefined> {
|
|
132
|
+
/**
|
|
133
|
+
* Called once when the instance is created (before DOM attach).
|
|
134
|
+
*
|
|
135
|
+
* @param ctx - The component context for this instance.
|
|
136
|
+
* @returns void
|
|
137
|
+
* @example
|
|
138
|
+
* onCreate({ el }) { el.dataset.ready = "1"; }
|
|
139
|
+
*/
|
|
140
|
+
onCreate?(ctx: ComponentContext<S>): void;
|
|
141
|
+
/**
|
|
142
|
+
* Called after the instance is attached to its element.
|
|
143
|
+
*
|
|
144
|
+
* @param ctx - The component context for this instance.
|
|
145
|
+
* @returns void
|
|
146
|
+
* @example
|
|
147
|
+
* onMount({ el }) { el.textContent = "0"; }
|
|
148
|
+
* @example
|
|
149
|
+
* async onMount(ctx) { ctx.set({ items: await load() }); } // async is allowed; the harness awaits it via settle()
|
|
150
|
+
*/
|
|
151
|
+
onMount?(ctx: ComponentContext<S>): void;
|
|
152
|
+
/**
|
|
153
|
+
* Called when a navigation begins while this instance is mounted.
|
|
154
|
+
*
|
|
155
|
+
* @param ctx - The component context for this instance.
|
|
156
|
+
* @returns void
|
|
157
|
+
* @example
|
|
158
|
+
* onNavStart({ el }) { el.dataset.loading = ""; }
|
|
159
|
+
*/
|
|
160
|
+
onNavStart?(ctx: ComponentContext<S>): void;
|
|
161
|
+
/**
|
|
162
|
+
* Called when a navigation completes while this instance is mounted.
|
|
163
|
+
*
|
|
164
|
+
* @param ctx - The component context for this instance.
|
|
165
|
+
* @returns void
|
|
166
|
+
* @example
|
|
167
|
+
* onNavEnd({ el }) { delete el.dataset.loading; }
|
|
168
|
+
*/
|
|
169
|
+
onNavEnd?(ctx: ComponentContext<S>): void;
|
|
170
|
+
/**
|
|
171
|
+
* Called before the instance is detached from its element.
|
|
172
|
+
*
|
|
173
|
+
* @param ctx - The component context for this instance.
|
|
174
|
+
* @returns void
|
|
175
|
+
* @example
|
|
176
|
+
* onUnMount({ el }) { el.replaceChildren(); }
|
|
177
|
+
*/
|
|
178
|
+
onUnMount?(ctx: ComponentContext<S>): void;
|
|
179
|
+
/**
|
|
180
|
+
* Called once when the instance is destroyed (after detach).
|
|
181
|
+
*
|
|
182
|
+
* @param ctx - The component context for this instance.
|
|
183
|
+
* @returns void
|
|
184
|
+
* @example
|
|
185
|
+
* onDestroy({ el }) { delete el.dataset.ready; }
|
|
186
|
+
*/
|
|
187
|
+
onDestroy?(ctx: ComponentContext<S>): void;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* The spec extras carried on a {@link ComponentDef}, type-erased to `object` state
|
|
191
|
+
* (authors keep full `S` inference at the `createComponent` call site; the registry
|
|
192
|
+
* stores the runtime-only erased form). Absent for legacy `(name, hooks)` defs.
|
|
193
|
+
*/
|
|
194
|
+
interface ComponentSpecExtras {
|
|
195
|
+
/** Per-instance state factory. */
|
|
196
|
+
state?: ComponentStateFactory<object>;
|
|
197
|
+
/** Render called on mount + after every `ctx.set`. */
|
|
198
|
+
render?: ComponentRender<object>;
|
|
199
|
+
/** Declarative delegated events. */
|
|
200
|
+
events?: ComponentEvents<object>;
|
|
201
|
+
/** Public api factory registered under the component name. */
|
|
202
|
+
api?: (ctx: ComponentContext<object>) => unknown;
|
|
203
|
+
}
|
|
204
|
+
/** A registered component definition (an opaque token; author inference lives on `createComponent`). */
|
|
205
|
+
interface ComponentDef {
|
|
206
|
+
/** Unique component name (matched against `data-component`). */
|
|
207
|
+
name: string;
|
|
208
|
+
/** Lifecycle hooks (the subset shared with the legacy form). */
|
|
209
|
+
hooks: ComponentHooks<object>;
|
|
210
|
+
/** Plugin-mirror extras (state/render/events/api). Absent for legacy `(name, hooks)` defs. */
|
|
211
|
+
spec?: ComponentSpecExtras;
|
|
212
|
+
}
|
|
213
|
+
/** The matched-route slice carried on a live instance (params/meta/locale + link builder). */
|
|
214
|
+
type ComponentRouteSlice = Pick<ComponentContext, "params" | "meta" | "locale" | "url">;
|
|
215
|
+
/** Page data payload parsed from the inline `script#__DATA__` element. */
|
|
216
|
+
type PageData = Record<string, unknown>;
|
|
217
|
+
//#endregion
|
|
218
|
+
//#region src/plugins/spa/components.d.ts
|
|
219
|
+
/** The matched-route slice merged onto the component context (params/meta/locale + link builder). */
|
|
220
|
+
type RouteSlice = ComponentRouteSlice;
|
|
221
|
+
//#endregion
|
|
222
|
+
//#region src/testing.d.ts
|
|
223
|
+
/** One captured spa emit (the kernel's `spa:component-mount` / `-unmount`). */
|
|
224
|
+
interface CapturedEmit {
|
|
225
|
+
/** The event name. */
|
|
226
|
+
readonly event: string;
|
|
227
|
+
/** The event payload. */
|
|
228
|
+
readonly payload: unknown;
|
|
229
|
+
}
|
|
230
|
+
/** A mounted island a unit/integration test drives without booting `createApp`. */
|
|
231
|
+
interface IslandHandle<S extends object = object, A = unknown> {
|
|
232
|
+
/** The host element the island bound to (already in `document.body`). */
|
|
233
|
+
readonly el: HTMLElement;
|
|
234
|
+
/** Live per-instance state (typed). `undefined` for legacy hooks-only islands. */
|
|
235
|
+
readonly state: S | undefined;
|
|
236
|
+
/** The island's registered api (typed), if it declared `api`. */
|
|
237
|
+
readonly api: A | undefined;
|
|
238
|
+
/**
|
|
239
|
+
* Dispatch a delegated event by spec: `fire("click [data-action='delete']")` clicks
|
|
240
|
+
* the first matching element inside the host (or the host itself when no selector).
|
|
241
|
+
*
|
|
242
|
+
* @param spec - `"<type>"` or `"<type> <selector>"` (same grammar as the `events` map).
|
|
243
|
+
* @param init - Optional event init (bubbles/cancelable are defaulted true).
|
|
244
|
+
* @example
|
|
245
|
+
* handle.fire("submit [data-create]");
|
|
246
|
+
*/
|
|
247
|
+
fire(spec: string, init?: EventInit): void;
|
|
248
|
+
/**
|
|
249
|
+
* Dispatch a RAW, pre-built event at a selector — full control for events the
|
|
250
|
+
* synthetic `fire` cannot build (DragEvent/dataTransfer/clientY).
|
|
251
|
+
*
|
|
252
|
+
* @param selector - The element to dispatch on (the host when no match).
|
|
253
|
+
* @param event - The pre-constructed event.
|
|
254
|
+
* @example
|
|
255
|
+
* handle.dispatch('[data-cards]', Object.assign(new Event("drop", { bubbles: true }), { dataTransfer }));
|
|
256
|
+
*/
|
|
257
|
+
dispatch(selector: string, event: Event): void;
|
|
258
|
+
/**
|
|
259
|
+
* Synchronously drain any pending render now (mutate → flush → assert, no `await`).
|
|
260
|
+
* For VNode-returning islands call {@link IslandHandle.settle} first so the lazy
|
|
261
|
+
* Preact render chunk is loaded.
|
|
262
|
+
*
|
|
263
|
+
* @example
|
|
264
|
+
* handle.flush();
|
|
265
|
+
*/
|
|
266
|
+
flush(): void;
|
|
267
|
+
/**
|
|
268
|
+
* Await `onMount`'s returned promise + the render-chunk load + a microtask, then
|
|
269
|
+
* flush — the deterministic settle for async mounts and VNode renders.
|
|
270
|
+
*
|
|
271
|
+
* @returns A promise resolving once the island is fully mounted and rendered.
|
|
272
|
+
* @example
|
|
273
|
+
* await handle.settle();
|
|
274
|
+
*/
|
|
275
|
+
settle(): Promise<void>;
|
|
276
|
+
/**
|
|
277
|
+
* Fire `onNavStart` on the instance (persistent + page-specific receive it).
|
|
278
|
+
*
|
|
279
|
+
* @example
|
|
280
|
+
* handle.navStart();
|
|
281
|
+
*/
|
|
282
|
+
navStart(): void;
|
|
283
|
+
/**
|
|
284
|
+
* Fire `onNavEnd` on a persistent instance, with an optional destination route slice.
|
|
285
|
+
*
|
|
286
|
+
* @param route - Partial route slice to merge onto the current one.
|
|
287
|
+
* @example
|
|
288
|
+
* handle.navEnd({ params: { id: "b2" } });
|
|
289
|
+
*/
|
|
290
|
+
navEnd(route?: Partial<RouteSlice>): void;
|
|
291
|
+
/**
|
|
292
|
+
* Run `onUnMount` + `onDestroy` (asserts auto-teardown of `events` + `ctx.cleanup`).
|
|
293
|
+
*
|
|
294
|
+
* @example
|
|
295
|
+
* handle.unmount();
|
|
296
|
+
*/
|
|
297
|
+
unmount(): void;
|
|
298
|
+
/** Captured `spa:component-mount` / `-unmount` emits, in order. */
|
|
299
|
+
readonly emitted: ReadonlyArray<CapturedEmit>;
|
|
300
|
+
}
|
|
301
|
+
/** Options for {@link mountIsland}. All optional; sensible headless defaults. */
|
|
302
|
+
interface MountIslandOptions {
|
|
303
|
+
/** Inner HTML placed INSIDE the host before mount (the SSR markup the island enhances). */
|
|
304
|
+
html?: string;
|
|
305
|
+
/** Mount into THIS element instead of creating one (sandbox: a real page host). */
|
|
306
|
+
el?: HTMLElement;
|
|
307
|
+
/** Route params (→ `ctx.params`). */
|
|
308
|
+
params?: Record<string, string>;
|
|
309
|
+
/** Route meta (→ `ctx.meta`). */
|
|
310
|
+
meta?: Record<string, unknown>;
|
|
311
|
+
/** Page data (→ `ctx.data`; serialized into `#__DATA__` so `extractPageData` sees it). */
|
|
312
|
+
data?: PageData;
|
|
313
|
+
/** Route locale (→ `ctx.locale`). */
|
|
314
|
+
locale?: string;
|
|
315
|
+
/** Link builder (→ `ctx.url`); defaults to `/<name>`. */
|
|
316
|
+
url?: (name: string, params?: Record<string, string>) => string;
|
|
317
|
+
/** Mount OUTSIDE the swap area so the instance is persistent (gets `onNavEnd`). */
|
|
318
|
+
persistent?: boolean;
|
|
319
|
+
/** Stubbed sibling-island apis resolved by `ctx.component(name)`. */
|
|
320
|
+
components?: Record<string, unknown>;
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Mount ONE island headlessly through the REAL spa kernel internals under a DOM. The
|
|
324
|
+
* unit + light-integration tier: no `createApp`, no router, no network.
|
|
325
|
+
*
|
|
326
|
+
* @param definition - The component definition under test (from `createComponent`).
|
|
327
|
+
* @param options - Host HTML/element, route slice, page data, persistence, stub apis.
|
|
328
|
+
* @returns A handle exposing the instance's `state`/`api` + event/nav/flush drivers.
|
|
329
|
+
* @example
|
|
330
|
+
* const h = mountIsland(tabNav, { html: "<a></a><a></a><a></a>", persistent: true });
|
|
331
|
+
* h.navEnd({ locale: "en" });
|
|
332
|
+
* expect(h.el.querySelector("[aria-current]")).toBeTruthy();
|
|
333
|
+
*/
|
|
334
|
+
declare function mountIsland<S extends object = object, A = unknown>(definition: ComponentDef, options?: MountIslandOptions): IslandHandle<S, A>;
|
|
335
|
+
/** The result of {@link renderIsland} — a host plus query/flush/teardown helpers. */
|
|
336
|
+
interface RenderIslandResult {
|
|
337
|
+
/** The host element the view rendered into. */
|
|
338
|
+
readonly host: HTMLElement;
|
|
339
|
+
/**
|
|
340
|
+
* The host's current `innerHTML`.
|
|
341
|
+
*
|
|
342
|
+
* @returns The serialized markup.
|
|
343
|
+
* @example
|
|
344
|
+
* expect(result.html()).toContain("Alpha");
|
|
345
|
+
*/
|
|
346
|
+
html(): string;
|
|
347
|
+
/**
|
|
348
|
+
* Query the host for the first element matching a selector.
|
|
349
|
+
*
|
|
350
|
+
* @param selector - A CSS selector.
|
|
351
|
+
* @returns The first match, or `null`.
|
|
352
|
+
* @example
|
|
353
|
+
* result.find("[data-board]");
|
|
354
|
+
*/
|
|
355
|
+
find<E extends Element = Element>(selector: string): E | null;
|
|
356
|
+
/**
|
|
357
|
+
* Unmount the Preact tree and remove the host from the document.
|
|
358
|
+
*
|
|
359
|
+
* @example
|
|
360
|
+
* result.unmount();
|
|
361
|
+
*/
|
|
362
|
+
unmount(): void;
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* The cheapest unit tier: render a controller/view island's pure `render(state, ctx)`
|
|
366
|
+
* against fixture state, with no kernel and no `mountIsland`. Uses `preact/test-utils`
|
|
367
|
+
* `act` (which ships WITH Preact — no new dependency) so effects flush deterministically.
|
|
368
|
+
*
|
|
369
|
+
* @param render - The island's `render` function (e.g. `boardList.spec.render`).
|
|
370
|
+
* @param input - Fixture inputs.
|
|
371
|
+
* @param input.state - The fixture per-instance state to render.
|
|
372
|
+
* @param input.ctx - Optional partial context overrides.
|
|
373
|
+
* @returns A {@link RenderIslandResult} for asserting the rendered DOM.
|
|
374
|
+
* @example
|
|
375
|
+
* const r = renderIsland(render, { state: { boards: [{ id: "1", title: "Alpha" }] } });
|
|
376
|
+
* expect(r.find("[data-board]")).toBeTruthy();
|
|
377
|
+
*/
|
|
378
|
+
declare function renderIsland<S extends object>(render: ComponentRender<S>, input: {
|
|
379
|
+
state: S;
|
|
380
|
+
ctx?: Partial<ComponentContext<S>>;
|
|
381
|
+
}): RenderIslandResult;
|
|
382
|
+
//#endregion
|
|
383
|
+
export { CapturedEmit, IslandHandle, MountIslandOptions, RenderIslandResult, mountIsland, renderIsland };
|