@vielzeug/ore 1.0.5 → 1.2.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.md +36 -3
- package/dist/_dev.cjs +1 -1
- package/dist/_dev.cjs.map +1 -1
- package/dist/_dev.js +1 -1
- package/dist/_dev.js.map +1 -1
- package/dist/aria.cjs +1 -1
- package/dist/aria.cjs.map +1 -1
- package/dist/aria.d.ts +16 -8
- package/dist/aria.d.ts.map +1 -1
- package/dist/aria.js +1 -1
- package/dist/aria.js.map +1 -1
- package/dist/base-element.cjs +1 -1
- package/dist/base-element.cjs.map +1 -1
- package/dist/base-element.d.ts +36 -1
- package/dist/base-element.d.ts.map +1 -1
- package/dist/base-element.js +1 -1
- package/dist/base-element.js.map +1 -1
- package/dist/component-types.d.ts +13 -76
- package/dist/component-types.d.ts.map +1 -1
- package/dist/context.cjs +1 -1
- package/dist/context.cjs.map +1 -1
- package/dist/context.d.ts +11 -2
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +1 -1
- package/dist/context.js.map +1 -1
- package/dist/define.cjs.map +1 -1
- package/dist/define.d.ts +13 -12
- package/dist/define.d.ts.map +1 -1
- package/dist/define.js.map +1 -1
- package/dist/directives/each.cjs +1 -1
- package/dist/directives/each.cjs.map +1 -1
- package/dist/directives/each.d.ts +6 -0
- package/dist/directives/each.d.ts.map +1 -1
- package/dist/directives/each.js +1 -1
- package/dist/directives/each.js.map +1 -1
- package/dist/directives/raw.cjs +1 -1
- package/dist/directives/raw.cjs.map +1 -1
- package/dist/directives/raw.d.ts.map +1 -1
- package/dist/directives/raw.js +1 -1
- package/dist/directives/raw.js.map +1 -1
- package/dist/directives/when.cjs +1 -1
- package/dist/directives/when.cjs.map +1 -1
- package/dist/directives/when.d.ts.map +1 -1
- package/dist/directives/when.js +1 -1
- package/dist/directives/when.js.map +1 -1
- package/dist/errors.cjs +2 -2
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.d.ts +23 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +2 -2
- package/dist/errors.js.map +1 -1
- package/dist/forms/context.cjs +2 -0
- package/dist/forms/context.cjs.map +1 -0
- package/dist/{form-context.d.ts → forms/context.d.ts} +5 -5
- package/dist/forms/context.d.ts.map +1 -0
- package/dist/forms/context.js +2 -0
- package/dist/forms/context.js.map +1 -0
- package/dist/forms/field.cjs +2 -0
- package/dist/forms/field.cjs.map +1 -0
- package/dist/{form-field.d.ts → forms/field.d.ts} +19 -2
- package/dist/forms/field.d.ts.map +1 -0
- package/dist/forms/field.js +2 -0
- package/dist/forms/field.js.map +1 -0
- package/dist/forms/index.d.ts +11 -0
- package/dist/forms/index.d.ts.map +1 -0
- package/dist/forms.cjs +1 -0
- package/dist/forms.js +1 -0
- package/dist/host-bind.cjs +1 -1
- package/dist/host-bind.cjs.map +1 -1
- package/dist/host-bind.d.ts +8 -2
- package/dist/host-bind.d.ts.map +1 -1
- package/dist/host-bind.js +1 -1
- package/dist/host-bind.js.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +8 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/ore.cjs +2 -2
- package/dist/ore.cjs.map +1 -1
- package/dist/ore.iife.js +2 -2
- package/dist/ore.iife.js.map +1 -1
- package/dist/ore.js +2 -2
- package/dist/ore.js.map +1 -1
- package/dist/props.cjs +1 -1
- package/dist/props.cjs.map +1 -1
- package/dist/props.d.ts.map +1 -1
- package/dist/props.js +1 -1
- package/dist/props.js.map +1 -1
- package/dist/runtime.cjs +1 -1
- package/dist/runtime.cjs.map +1 -1
- package/dist/runtime.d.ts +32 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +1 -1
- package/dist/runtime.js.map +1 -1
- package/dist/slots.cjs +1 -1
- package/dist/slots.cjs.map +1 -1
- package/dist/slots.d.ts +12 -1
- package/dist/slots.d.ts.map +1 -1
- package/dist/slots.js +1 -1
- package/dist/slots.js.map +1 -1
- package/dist/template/bindings.cjs +1 -1
- package/dist/template/bindings.cjs.map +1 -1
- package/dist/template/bindings.d.ts.map +1 -1
- package/dist/template/bindings.js +1 -1
- package/dist/template/bindings.js.map +1 -1
- package/dist/template/compiler.cjs +1 -1
- package/dist/template/compiler.cjs.map +1 -1
- package/dist/template/compiler.d.ts +11 -1
- package/dist/template/compiler.d.ts.map +1 -1
- package/dist/template/compiler.js +1 -1
- package/dist/template/compiler.js.map +1 -1
- package/dist/template/instantiator.cjs +1 -1
- package/dist/template/instantiator.cjs.map +1 -1
- package/dist/template/instantiator.d.ts +0 -5
- package/dist/template/instantiator.d.ts.map +1 -1
- package/dist/template/instantiator.js +1 -1
- package/dist/template/instantiator.js.map +1 -1
- package/dist/testing/dom.cjs +2 -0
- package/dist/testing/dom.cjs.map +1 -0
- package/dist/testing/dom.d.ts +22 -0
- package/dist/testing/dom.d.ts.map +1 -0
- package/dist/testing/dom.js +2 -0
- package/dist/testing/dom.js.map +1 -0
- package/dist/testing/form-internals-polyfill.cjs +2 -0
- package/dist/testing/form-internals-polyfill.cjs.map +1 -0
- package/dist/testing/form-internals-polyfill.d.ts +17 -0
- package/dist/testing/form-internals-polyfill.d.ts.map +1 -0
- package/dist/testing/form-internals-polyfill.js +2 -0
- package/dist/testing/form-internals-polyfill.js.map +1 -0
- package/dist/testing/mount.cjs.map +1 -1
- package/dist/testing/mount.d.ts +4 -4
- package/dist/testing/mount.d.ts.map +1 -1
- package/dist/testing/mount.js.map +1 -1
- package/dist/testing/render-hook.cjs +1 -1
- package/dist/testing/render-hook.cjs.map +1 -1
- package/dist/testing/render-hook.d.ts +6 -6
- package/dist/testing/render-hook.d.ts.map +1 -1
- package/dist/testing/render-hook.js +1 -1
- package/dist/testing/render-hook.js.map +1 -1
- package/dist/testing/testing.cjs +1 -1
- package/dist/testing/testing.cjs.map +1 -1
- package/dist/testing/testing.d.ts +7 -1
- package/dist/testing/testing.d.ts.map +1 -1
- package/dist/testing/testing.js +1 -1
- package/dist/testing/testing.js.map +1 -1
- package/dist/testing.cjs +1 -1
- package/dist/testing.js +1 -1
- package/dist/utils/emit.cjs +1 -1
- package/dist/utils/emit.cjs.map +1 -1
- package/dist/utils/emit.d.ts +28 -5
- package/dist/utils/emit.d.ts.map +1 -1
- package/dist/utils/emit.js +1 -1
- package/dist/utils/emit.js.map +1 -1
- package/package.json +12 -5
- package/dist/context-bag.cjs +0 -2
- package/dist/context-bag.cjs.map +0 -1
- package/dist/context-bag.d.ts +0 -12
- package/dist/context-bag.d.ts.map +0 -1
- package/dist/context-bag.js +0 -2
- package/dist/context-bag.js.map +0 -1
- package/dist/form-context.cjs +0 -2
- package/dist/form-context.cjs.map +0 -1
- package/dist/form-context.d.ts.map +0 -1
- package/dist/form-context.js +0 -2
- package/dist/form-context.js.map +0 -1
- package/dist/form-field.cjs +0 -2
- package/dist/form-field.cjs.map +0 -1
- package/dist/form-field.d.ts.map +0 -1
- package/dist/form-field.js +0 -2
- package/dist/form-field.js.map +0 -1
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* jsdom implements none of the `ElementInternals` form-association API — not `setFormValue`,
|
|
3
|
+
* not `setValidity`, not the `checkValidity`/`reportValidity`/`validity`/`validationMessage`
|
|
4
|
+
* mixin real browsers put on every `formAssociated: true` element, not even `FormData`
|
|
5
|
+
* collecting a form-associated element's set value, or `<form>.reset()` invoking
|
|
6
|
+
* `formResetCallback()`. Every one of those gaps is exercised by `useField()` (`@vielzeug/ore/forms`),
|
|
7
|
+
* so any package testing a form-associated component needs all of them, not just one — this is
|
|
8
|
+
* called once, automatically, by `install()`.
|
|
9
|
+
*
|
|
10
|
+
* Deliberately lives here rather than in each consuming package's own `vitest.setup.ts`: the
|
|
11
|
+
* gap is in `ore`'s own form-association feature, not in any one consumer, so the fix belongs
|
|
12
|
+
* with the feature. `@vielzeug/refine`'s `<ore-form>` adds one more wrinkle on top — its fields
|
|
13
|
+
* are slotted into a shadow `<form>` — which `walkFlatTree` already handles here, so refine
|
|
14
|
+
* doesn't need a second, package-local copy of this polyfill at all.
|
|
15
|
+
*/
|
|
16
|
+
export declare const installFormInternalsPolyfill: () => void;
|
|
17
|
+
//# sourceMappingURL=form-internals-polyfill.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"form-internals-polyfill.d.ts","sourceRoot":"","sources":["../../src/testing/form-internals-polyfill.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,4BAA4B,QAAO,IAqK/C,CAAC"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{walkFlatTree as e}from"./dom.js";var t=()=>{if(typeof ElementInternals>`u`)return;let t=Symbol.for(`vielzeug.ore.testing.formInternalsPolyfillInstalled`),n=globalThis;if(n[t])return;n[t]=!0;let r=ElementInternals.prototype,i=new WeakMap,a=new WeakMap,o=new WeakMap,s=e=>{let t=i.get(e);return!t||!Object.values(t.flags).some(Boolean)};`setFormValue`in r||Object.defineProperty(r,"setFormValue",{configurable:!0,value:function(e){let t=o.get(this);t&&a.set(t,e)},writable:!0}),`setValidity`in r||Object.defineProperty(r,"setValidity",{configurable:!0,value:function(e={},t=``){if(Object.values(e).some(Boolean)&&!t)throw TypeError(`Failed to execute 'setValidity' on 'ElementInternals': The second argument must not be empty if one or more flags in the first argument are true.`);i.set(this,{flags:e,message:t})},writable:!0}),`checkValidity`in r||Object.defineProperty(r,"checkValidity",{configurable:!0,value:function(){return s(this)},writable:!0}),`reportValidity`in r||Object.defineProperty(r,"reportValidity",{configurable:!0,value:function(){return s(this)},writable:!0}),`validationMessage`in r||Object.defineProperty(r,"validationMessage",{configurable:!0,get(){return s(this)?``:i.get(this)?.message??``}}),`states`in r||Object.defineProperty(r,"states",{configurable:!0,get:function(){return this._states??=new Set,this._states}});let c=HTMLElement.prototype.attachInternals;HTMLElement.prototype.attachInternals=function(...e){let t=c.apply(this,e);return o.set(t,this),Object.defineProperties(this,{checkValidity:{configurable:!0,value:()=>t.checkValidity()},reportValidity:{configurable:!0,value:()=>t.reportValidity()},validationMessage:{configurable:!0,get:()=>t.validationMessage},validity:{configurable:!0,get:()=>t.validity}}),t};let l=(e,t,n)=>{if(n instanceof FormData){for(let[t,r]of n.entries())e.append(t,r);return}e.append(t,n)},u=globalThis.FormData;globalThis.FormData=class extends u{constructor(t,n){super(t,n),t instanceof HTMLFormElement&&e(t,e=>{let t=e.getAttribute(`name`),n=a.get(e);!t||n==null||e.hasAttribute(`disabled`)||l(this,t,n)})}};let d=HTMLFormElement.prototype.reset;HTMLFormElement.prototype.reset=function(...t){d.apply(this,t),e(this,e=>{e.formResetCallback?.()})}};export{t as installFormInternalsPolyfill};
|
|
2
|
+
//# sourceMappingURL=form-internals-polyfill.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"form-internals-polyfill.js","names":[],"sources":["../../src/testing/form-internals-polyfill.ts"],"sourcesContent":["import { walkFlatTree } from './dom';\n\n/**\n * jsdom implements none of the `ElementInternals` form-association API — not `setFormValue`,\n * not `setValidity`, not the `checkValidity`/`reportValidity`/`validity`/`validationMessage`\n * mixin real browsers put on every `formAssociated: true` element, not even `FormData`\n * collecting a form-associated element's set value, or `<form>.reset()` invoking\n * `formResetCallback()`. Every one of those gaps is exercised by `useField()` (`@vielzeug/ore/forms`),\n * so any package testing a form-associated component needs all of them, not just one — this is\n * called once, automatically, by `install()`.\n *\n * Deliberately lives here rather than in each consuming package's own `vitest.setup.ts`: the\n * gap is in `ore`'s own form-association feature, not in any one consumer, so the fix belongs\n * with the feature. `@vielzeug/refine`'s `<ore-form>` adds one more wrinkle on top — its fields\n * are slotted into a shadow `<form>` — which `walkFlatTree` already handles here, so refine\n * doesn't need a second, package-local copy of this polyfill at all.\n */\nexport const installFormInternalsPolyfill = (): void => {\n // Checked *before* consuming the flag below — an environment without `ElementInternals`\n // installs nothing and should stay eligible to install for real later (e.g. a differently\n // configured environment in the same process), not get marked \"already installed\" for having\n // done nothing.\n if (typeof ElementInternals === 'undefined') return;\n\n const INSTALLED_FLAG = Symbol.for('vielzeug.ore.testing.formInternalsPolyfillInstalled');\n const flagHost = globalThis as Record<symbol, boolean | undefined>;\n\n if (flagHost[INSTALLED_FLAG]) return;\n\n flagHost[INSTALLED_FLAG] = true;\n\n const proto = ElementInternals.prototype as unknown as Record<string, unknown>;\n const validityState = new WeakMap<ElementInternals, { flags: ValidityStateFlags; message: string }>();\n const formValueByHost = new WeakMap<HTMLElement, File | FormData | string | null>();\n const hostByInternals = new WeakMap<ElementInternals, HTMLElement>();\n\n const isValid = (internals: ElementInternals): boolean => {\n const state = validityState.get(internals);\n\n return !state || !Object.values(state.flags).some(Boolean);\n };\n\n if (!('setFormValue' in proto)) {\n Object.defineProperty(proto, 'setFormValue', {\n configurable: true,\n value: function (this: ElementInternals, value: File | FormData | string | null) {\n const host = hostByInternals.get(this);\n\n if (host) formValueByHost.set(host, value);\n },\n writable: true,\n });\n }\n\n if (!('setValidity' in proto)) {\n Object.defineProperty(proto, 'setValidity', {\n configurable: true,\n // Matches the real platform contract: throws if any flag is true and message is empty.\n // `useField()` itself already guards against triggering this — see forms/field.ts — but\n // the polyfill enforcing it too means a test would still catch a *different* caller doing\n // the same thing wrong, instead of that bug only surfacing in a real browser.\n value: function (this: ElementInternals, flags: ValidityStateFlags = {}, message = '') {\n if (Object.values(flags).some(Boolean) && !message) {\n throw new TypeError(\n \"Failed to execute 'setValidity' on 'ElementInternals': The second argument must not be empty if \" +\n 'one or more flags in the first argument are true.',\n );\n }\n\n validityState.set(this, { flags, message });\n },\n writable: true,\n });\n }\n\n if (!('checkValidity' in proto)) {\n Object.defineProperty(proto, 'checkValidity', {\n configurable: true,\n value: function (this: ElementInternals) {\n return isValid(this);\n },\n writable: true,\n });\n }\n\n if (!('reportValidity' in proto)) {\n Object.defineProperty(proto, 'reportValidity', {\n configurable: true,\n value: function (this: ElementInternals) {\n return isValid(this);\n },\n writable: true,\n });\n }\n\n if (!('validationMessage' in proto)) {\n Object.defineProperty(proto, 'validationMessage', {\n configurable: true,\n get(this: ElementInternals) {\n return isValid(this) ? '' : (validityState.get(this)?.message ?? '');\n },\n });\n }\n\n if (!('states' in proto)) {\n Object.defineProperty(proto, 'states', {\n configurable: true,\n get: function (this: ElementInternals & { _states?: Set<string> }) {\n this._states ??= new Set();\n\n return this._states;\n },\n });\n }\n\n // Real browsers mix `checkValidity`/`reportValidity`/`validity`/`validationMessage` onto any\n // `formAssociated: true` custom element itself (delegating to its internals) — not just onto\n // `ElementInternals`. jsdom does neither; mirror it here so a component's own\n // `checkValidity()`/`reportValidity()`, and a test asserting against the element directly,\n // behave the same as they would in a real browser.\n const originalAttachInternals = HTMLElement.prototype.attachInternals;\n\n HTMLElement.prototype.attachInternals = function (this: HTMLElement, ...args: []): ElementInternals {\n const internals = originalAttachInternals.apply(this, args);\n\n hostByInternals.set(internals, this);\n\n Object.defineProperties(this, {\n checkValidity: { configurable: true, value: () => internals.checkValidity() },\n reportValidity: { configurable: true, value: () => internals.reportValidity() },\n validationMessage: { configurable: true, get: () => internals.validationMessage },\n validity: { configurable: true, get: () => internals.validity },\n });\n\n return internals;\n };\n\n const appendFormValue = (formData: FormData, name: string, value: File | FormData | string): void => {\n if (value instanceof FormData) {\n for (const [entryName, entryValue] of value.entries()) formData.append(entryName, entryValue);\n\n return;\n }\n\n formData.append(name, value);\n };\n\n // jsdom's `FormData` constructor never collects a form-associated custom element's set form\n // value — real browsers walk the flat tree for every form-associated element with a `name`.\n const NativeFormData = globalThis.FormData;\n\n globalThis.FormData = class FormDataWithFormAssociatedElements extends NativeFormData {\n constructor(form?: HTMLFormElement, submitter?: HTMLElement | null) {\n super(form as HTMLFormElement | undefined, submitter as HTMLElement | null | undefined);\n\n if (!(form instanceof HTMLFormElement)) return;\n\n walkFlatTree(form, (element) => {\n const name = element.getAttribute('name');\n const value = formValueByHost.get(element);\n\n if (!name || value == null || element.hasAttribute('disabled')) return;\n\n appendFormValue(this, name, value);\n });\n }\n };\n\n // jsdom never invokes the native `formResetCallback` lifecycle method on form-associated\n // custom elements when their form resets — patch `reset()` to do it (via the same flat-tree\n // walk, since a reset field can be nested behind a shadow boundary too), so `onFormReset()`\n // (see `@vielzeug/ore`'s runtime.ts) is testable against a real `<form>.reset()` call instead\n // of only by calling `element.formResetCallback()` directly.\n const originalReset = HTMLFormElement.prototype.reset;\n\n HTMLFormElement.prototype.reset = function (this: HTMLFormElement, ...args: []) {\n originalReset.apply(this, args);\n\n walkFlatTree(this, (element) => {\n (element as Partial<{ formResetCallback: () => void }>).formResetCallback?.();\n });\n };\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mount.cjs","names":[],"sources":["../../src/testing/mount.ts"],"sourcesContent":["/**\n * Component mounting utilities for test environments.\n */\n\nimport type { Readable } from '@vielzeug/ripple';\n\nimport { type ComponentDefinition, type SetupContextBag } from '../component-types';\nimport { define } from '../define';\nimport { _resetLiveSignals } from '../directives/live';\nimport { _resetRawSanitizer } from '../directives/raw';\nimport { type HTMLResult } from '../types/bindings';\nimport { _clearStylesheetCache } from '../utils/css';\nimport { _resetIdCounter } from '../utils/id';\nimport { flush, type FlushOptions } from './flush';\nimport { queryAllByText, queryByText, type QueryScope } from './query';\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\nexport interface Fixture<T extends HTMLElement = HTMLElement> {\n /** Delegates to `dispose()`. Enables `using` declarations. */\n [Symbol.dispose](): void;\n /** Run a callback then flush — the standard way to trigger and assert a reactive update */\n act(fn: () => unknown): Promise<void>;\n /** Set an attribute (boolean `false` removes it) then flush */\n attr(name: string, value: string | number | boolean): Promise<void>;\n /** Set multiple attributes then flush */\n attrs(record: Record<string, string | number | boolean>): Promise<void>;\n /** Remove the component from the DOM. Idempotent. */\n dispose(): void;\n /** `true` after `dispose()` has been called. */\n readonly disposed: boolean;\n /** The component element */\n element: T;\n /** Wait for all reactive updates and animation frames */\n flush(options?: FlushOptions): Promise<void>;\n /** Query a single element within shadow root */\n query<E extends Element = Element>(selector: string): E | null;\n /** Query all elements within shadow root */\n queryAll<E extends Element = Element>(selector: string): E[];\n /** Query all elements by their `data-testid` attribute */\n queryAllByTestId<E extends Element = Element>(testId: string): E[];\n /** Query all elements whose trimmed text content matches */\n queryAllByText<E extends Element = Element>(text: string, selector?: string): E[];\n /** Query a single element by its `data-testid` attribute */\n queryByTestId<E extends Element = Element>(testId: string): E | null;\n /** Query the first element whose trimmed text content matches */\n queryByText<E extends Element = Element>(text: string, selector?: string): E | null;\n /** The component's shadow root (null for light-DOM components) */\n readonly shadow: ShadowRoot | null;\n}\n\nexport interface MountOptions {\n /** Properties assigned directly onto the element */\n props?: Record<string, unknown>;\n /** HTML attributes to set on the element */\n attrs?: Record<string, string | number | boolean>;\n /** Inner HTML for slot content */\n html?: string;\n /** Parent container (default: document.body) */\n container?: HTMLElement;\n /** Extra component options when passing an inline setup function */\n componentOptions?: Omit<ComponentDefinition<any, any>, 'setup'>;\n}\n\ntype MountProps = { readonly [x: string]: Readable<unknown> };\n\n// Bivariant callback type keeps inline test callbacks ergonomic across varying setup context specializations.\nexport type MountSetup = {\n bivarianceHack: (props: MountProps, ctx: SetupContextBag<any>) => HTMLResult | null | Promise<HTMLResult | null>;\n}['bivarianceHack'];\n\n// ─── Test environment state ───────────────────────────────────────────────────\n\nexport const _mountedElements: HTMLElement[] = [];\nexport let _componentTagCounter = 0;\n\n/**\n * Resets global test counters used for deterministic IDs/markers.\n * @internal\n */\nexport const _resetCounters = (): void => {\n _resetIdCounter();\n _componentTagCounter = 0;\n};\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\nfunction applyAttr(element: Element, name: string, value: string | number | boolean): void {\n if (value === false) element.removeAttribute(name);\n else element.setAttribute(name, value === true ? '' : String(value));\n}\n\nconst toError = (value: unknown): Error => {\n return value instanceof Error ? value : new Error(String(value));\n};\n\nconst withWindowErrorCapture = async <T>(action: () => Promise<T>): Promise<T> => {\n if (typeof window === 'undefined') return action();\n\n let captured: Error | null = null;\n const onError = (event: ErrorEvent) => {\n captured = toError(event.error ?? event.message);\n event.preventDefault();\n };\n const onUnhandledRejection = (event: PromiseRejectionEvent) => {\n captured = toError(event.reason);\n event.preventDefault();\n };\n\n window.addEventListener('error', onError);\n window.addEventListener('unhandledrejection', onUnhandledRejection);\n\n try {\n const result = await action();\n\n if (captured) throw captured;\n\n return result;\n } finally {\n window.removeEventListener('error', onError);\n window.removeEventListener('unhandledrejection', onUnhandledRejection);\n }\n};\n\n// ─── API ─────────────────────────────────────────────────────────────────────\n\n/**\n * Mount a component into the DOM and return a test fixture.\n *\n * Accepts a registered tag name, an inline setup function, or a component\n * options object. Setup functions are auto-registered with generated tag names.\n *\n * @example — inline setup function\n * const { query } = await mount(() => {\n * const count = signal(0);\n * return html`<button @click=${() => count.value++}>${count}</button>`;\n * });\n *\n * @example — registered tag name\n * const { query } = await mount('my-counter');\n */\nexport async function mount<T extends HTMLElement = HTMLElement>(\n tagOrSetup: string,\n options?: MountOptions,\n): Promise<Fixture<T>>;\nexport async function mount<T extends HTMLElement = HTMLElement>(\n tagOrSetup: MountSetup,\n options?: MountOptions,\n): Promise<Fixture<T>>;\nexport async function mount<T extends HTMLElement = HTMLElement>(\n tagOrSetup: string | MountSetup,\n options: MountOptions = {},\n): Promise<Fixture<T>> {\n const { attrs = {}, componentOptions, container = document.body, html, props = {} } = options;\n\n let tagName: string;\n let inlineDefinition: ComponentDefinition<any, any> | undefined;\n\n if (typeof tagOrSetup === 'string') {\n tagName = tagOrSetup;\n } else {\n tagName = `trial-${++_componentTagCounter}-${Math.random().toString(36).slice(2, 7)}`;\n inlineDefinition = {\n ...(componentOptions ?? {}),\n setup: tagOrSetup as ComponentDefinition<any, any>['setup'],\n };\n }\n\n if (inlineDefinition) {\n define(tagName, inlineDefinition);\n }\n\n const element = document.createElement(tagName) as T;\n\n if (html) element.innerHTML = html;\n\n if (Object.keys(props).length) Object.assign(element, props);\n\n for (const [name, value] of Object.entries(attrs)) applyAttr(element, name, value);\n\n await withWindowErrorCapture(async () => {\n container.appendChild(element);\n _mountedElements.push(element);\n await flush();\n });\n\n let isDisposed = false;\n\n function dispose() {\n if (isDisposed) return;\n\n isDisposed = true;\n element.remove();\n\n const i = _mountedElements.indexOf(element);\n\n if (i !== -1) _mountedElements.splice(i, 1);\n }\n\n return {\n async act(fn) {\n await fn();\n await flush();\n },\n\n async attr(name, value) {\n applyAttr(element, name, value);\n await flush();\n },\n\n async attrs(record) {\n for (const [name, value] of Object.entries(record)) applyAttr(element, name, value);\n await flush();\n },\n\n dispose,\n\n get disposed(): boolean {\n return isDisposed;\n },\n\n element,\n\n flush,\n\n query<E extends Element = Element>(selector: string): E | null {\n return (element.shadowRoot ?? element).querySelector<E>(selector);\n },\n\n queryAll<E extends Element = Element>(selector: string): E[] {\n return Array.from((element.shadowRoot ?? element).querySelectorAll<E>(selector));\n },\n\n queryAllByTestId<E extends Element = Element>(testId: string): E[] {\n return Array.from((element.shadowRoot ?? element).querySelectorAll<E>(`[data-testid=\"${testId}\"]`));\n },\n\n queryAllByText<E extends Element = Element>(text: string, selector = '*'): E[] {\n return queryAllByText<E>((element.shadowRoot ?? element) as Element, text, selector);\n },\n\n queryByTestId<E extends Element = Element>(testId: string): E | null {\n return (element.shadowRoot ?? element).querySelector<E>(`[data-testid=\"${testId}\"]`);\n },\n\n queryByText<E extends Element = Element>(text: string, selector = '*'): E | null {\n return queryByText<E>((element.shadowRoot ?? element) as Element, text, selector);\n },\n\n get shadow(): ShadowRoot | null {\n return element.shadowRoot;\n },\n\n [Symbol.dispose]() {\n dispose();\n },\n };\n}\n\n/**\n * Register and mount a component definition in a single call.\n *\n * Combines `define(tag, definition)` + `mount(tag, options)` — the standard\n * pattern for testing full custom-element lifecycle (props, reconnect, etc.).\n *\n * @example\n * const { query } = await mountComponent('my-counter', {\n * props: { count: prop.number(0) },\n * setup: (props) => html`<div>${props.count}</div>`,\n * });\n */\nexport async function mountComponent<T extends HTMLElement = HTMLElement>(\n tag: string,\n definition: ComponentDefinition<any, any>,\n options?: MountOptions,\n): Promise<Fixture<T>> {\n define(tag, definition);\n\n return mount<T>(tag, options);\n}\n\n/**\n * Register a stub custom element (no-op if already defined).\n *\n * @example\n * mock('child-button', '<slot></slot>');\n */\nexport function mock(tagName: string, template = ''): void {\n if (!customElements.get(tagName)) {\n customElements.define(\n tagName,\n class extends HTMLElement {\n connectedCallback() {\n this.innerHTML = template;\n }\n },\n );\n }\n}\n\n/**\n * Remove all elements mounted via `mount()`.\n * Call in `afterEach` to keep tests isolated.\n *\n * @example\n * afterEach(() => cleanup());\n */\nexport function cleanup(): void {\n for (const el of _mountedElements) el.remove();\n _mountedElements.length = 0;\n _resetRawSanitizer();\n _resetLiveSignals();\n _clearStylesheetCache();\n _resetIdCounter();\n}\n\n/** @internal re-export for within() */\nexport type { QueryScope };\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"mount.cjs","names":[],"sources":["../../src/testing/mount.ts"],"sourcesContent":["/**\n * Component mounting utilities for test environments.\n */\n\nimport type { Readable } from '@vielzeug/ripple';\n\nimport { type ComponentDefinition } from '../component-types';\nimport { define } from '../define';\nimport { _resetLiveSignals } from '../directives/live';\nimport { _resetRawSanitizer } from '../directives/raw';\nimport { type HTMLResult } from '../types/bindings';\nimport { _clearStylesheetCache } from '../utils/css';\nimport { _resetIdCounter } from '../utils/id';\nimport { flush, type FlushOptions } from './flush';\nimport { queryAllByText, queryByText, type QueryScope } from './query';\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\nexport interface Fixture<T extends HTMLElement = HTMLElement> {\n /** Delegates to `dispose()`. Enables `using` declarations. */\n [Symbol.dispose](): void;\n /** Run a callback then flush — the standard way to trigger and assert a reactive update */\n act(fn: () => unknown): Promise<void>;\n /** Set an attribute (boolean `false` removes it) then flush */\n attr(name: string, value: string | number | boolean): Promise<void>;\n /** Set multiple attributes then flush */\n attrs(record: Record<string, string | number | boolean>): Promise<void>;\n /** Remove the component from the DOM. Idempotent. */\n dispose(): void;\n /** `true` after `dispose()` has been called. */\n readonly disposed: boolean;\n /** The component element */\n element: T;\n /** Wait for all reactive updates and animation frames */\n flush(options?: FlushOptions): Promise<void>;\n /** Query a single element within shadow root */\n query<E extends Element = Element>(selector: string): E | null;\n /** Query all elements within shadow root */\n queryAll<E extends Element = Element>(selector: string): E[];\n /** Query all elements by their `data-testid` attribute */\n queryAllByTestId<E extends Element = Element>(testId: string): E[];\n /** Query all elements whose trimmed text content matches */\n queryAllByText<E extends Element = Element>(text: string, selector?: string): E[];\n /** Query a single element by its `data-testid` attribute */\n queryByTestId<E extends Element = Element>(testId: string): E | null;\n /** Query the first element whose trimmed text content matches */\n queryByText<E extends Element = Element>(text: string, selector?: string): E | null;\n /** The component's shadow root (null for light-DOM components) */\n readonly shadow: ShadowRoot | null;\n}\n\nexport interface MountOptions {\n /** Properties assigned directly onto the element */\n props?: Record<string, unknown>;\n /** HTML attributes to set on the element */\n attrs?: Record<string, string | number | boolean>;\n /** Inner HTML for slot content */\n html?: string;\n /** Parent container (default: document.body) */\n container?: HTMLElement;\n /** Extra component options when passing an inline setup function */\n componentOptions?: Omit<ComponentDefinition<any>, 'setup'>;\n}\n\ntype MountProps = { readonly [x: string]: Readable<unknown> };\n\n// Bivariant callback type keeps inline test callbacks ergonomic across varying prop specializations.\nexport type MountSetup = {\n bivarianceHack: (props: MountProps) => HTMLResult | null | Promise<HTMLResult | null>;\n}['bivarianceHack'];\n\n// ─── Test environment state ───────────────────────────────────────────────────\n\nexport const _mountedElements: HTMLElement[] = [];\nexport let _componentTagCounter = 0;\n\n/**\n * Resets global test counters used for deterministic IDs/markers.\n * @internal\n */\nexport const _resetCounters = (): void => {\n _resetIdCounter();\n _componentTagCounter = 0;\n};\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\nfunction applyAttr(element: Element, name: string, value: string | number | boolean): void {\n if (value === false) element.removeAttribute(name);\n else element.setAttribute(name, value === true ? '' : String(value));\n}\n\nconst toError = (value: unknown): Error => {\n return value instanceof Error ? value : new Error(String(value));\n};\n\nconst withWindowErrorCapture = async <T>(action: () => Promise<T>): Promise<T> => {\n if (typeof window === 'undefined') return action();\n\n let captured: Error | null = null;\n const onError = (event: ErrorEvent) => {\n captured = toError(event.error ?? event.message);\n event.preventDefault();\n };\n const onUnhandledRejection = (event: PromiseRejectionEvent) => {\n captured = toError(event.reason);\n event.preventDefault();\n };\n\n window.addEventListener('error', onError);\n window.addEventListener('unhandledrejection', onUnhandledRejection);\n\n try {\n const result = await action();\n\n if (captured) throw captured;\n\n return result;\n } finally {\n window.removeEventListener('error', onError);\n window.removeEventListener('unhandledrejection', onUnhandledRejection);\n }\n};\n\n// ─── API ─────────────────────────────────────────────────────────────────────\n\n/**\n * Mount a component into the DOM and return a test fixture.\n *\n * Accepts a registered tag name, an inline setup function, or a component\n * options object. Setup functions are auto-registered with generated tag names.\n *\n * @example — inline setup function\n * const { query } = await mount(() => {\n * const count = signal(0);\n * return html`<button @click=${() => count.value++}>${count}</button>`;\n * });\n *\n * @example — registered tag name\n * const { query } = await mount('my-counter');\n */\nexport async function mount<T extends HTMLElement = HTMLElement>(\n tagOrSetup: string,\n options?: MountOptions,\n): Promise<Fixture<T>>;\nexport async function mount<T extends HTMLElement = HTMLElement>(\n tagOrSetup: MountSetup,\n options?: MountOptions,\n): Promise<Fixture<T>>;\nexport async function mount<T extends HTMLElement = HTMLElement>(\n tagOrSetup: string | MountSetup,\n options: MountOptions = {},\n): Promise<Fixture<T>> {\n const { attrs = {}, componentOptions, container = document.body, html, props = {} } = options;\n\n let tagName: string;\n let inlineDefinition: ComponentDefinition<any> | undefined;\n\n if (typeof tagOrSetup === 'string') {\n tagName = tagOrSetup;\n } else {\n tagName = `trial-${++_componentTagCounter}-${Math.random().toString(36).slice(2, 7)}`;\n inlineDefinition = {\n ...(componentOptions ?? {}),\n setup: tagOrSetup as ComponentDefinition<any>['setup'],\n };\n }\n\n if (inlineDefinition) {\n define(tagName, inlineDefinition);\n }\n\n const element = document.createElement(tagName) as T;\n\n if (html) element.innerHTML = html;\n\n if (Object.keys(props).length) Object.assign(element, props);\n\n for (const [name, value] of Object.entries(attrs)) applyAttr(element, name, value);\n\n await withWindowErrorCapture(async () => {\n container.appendChild(element);\n _mountedElements.push(element);\n await flush();\n });\n\n let isDisposed = false;\n\n function dispose() {\n if (isDisposed) return;\n\n isDisposed = true;\n element.remove();\n\n const i = _mountedElements.indexOf(element);\n\n if (i !== -1) _mountedElements.splice(i, 1);\n }\n\n return {\n async act(fn) {\n await fn();\n await flush();\n },\n\n async attr(name, value) {\n applyAttr(element, name, value);\n await flush();\n },\n\n async attrs(record) {\n for (const [name, value] of Object.entries(record)) applyAttr(element, name, value);\n await flush();\n },\n\n dispose,\n\n get disposed(): boolean {\n return isDisposed;\n },\n\n element,\n\n flush,\n\n query<E extends Element = Element>(selector: string): E | null {\n return (element.shadowRoot ?? element).querySelector<E>(selector);\n },\n\n queryAll<E extends Element = Element>(selector: string): E[] {\n return Array.from((element.shadowRoot ?? element).querySelectorAll<E>(selector));\n },\n\n queryAllByTestId<E extends Element = Element>(testId: string): E[] {\n return Array.from((element.shadowRoot ?? element).querySelectorAll<E>(`[data-testid=\"${testId}\"]`));\n },\n\n queryAllByText<E extends Element = Element>(text: string, selector = '*'): E[] {\n return queryAllByText<E>((element.shadowRoot ?? element) as Element, text, selector);\n },\n\n queryByTestId<E extends Element = Element>(testId: string): E | null {\n return (element.shadowRoot ?? element).querySelector<E>(`[data-testid=\"${testId}\"]`);\n },\n\n queryByText<E extends Element = Element>(text: string, selector = '*'): E | null {\n return queryByText<E>((element.shadowRoot ?? element) as Element, text, selector);\n },\n\n get shadow(): ShadowRoot | null {\n return element.shadowRoot;\n },\n\n [Symbol.dispose]() {\n dispose();\n },\n };\n}\n\n/**\n * Register and mount a component definition in a single call.\n *\n * Combines `define(tag, definition)` + `mount(tag, options)` — the standard\n * pattern for testing full custom-element lifecycle (props, reconnect, etc.).\n *\n * @example\n * const { query } = await mountComponent('my-counter', {\n * props: { count: prop.number(0) },\n * setup: (props) => html`<div>${props.count}</div>`,\n * });\n */\nexport async function mountComponent<T extends HTMLElement = HTMLElement>(\n tag: string,\n definition: ComponentDefinition<any>,\n options?: MountOptions,\n): Promise<Fixture<T>> {\n define(tag, definition);\n\n return mount<T>(tag, options);\n}\n\n/**\n * Register a stub custom element (no-op if already defined).\n *\n * @example\n * mock('child-button', '<slot></slot>');\n */\nexport function mock(tagName: string, template = ''): void {\n if (!customElements.get(tagName)) {\n customElements.define(\n tagName,\n class extends HTMLElement {\n connectedCallback() {\n this.innerHTML = template;\n }\n },\n );\n }\n}\n\n/**\n * Remove all elements mounted via `mount()`.\n * Call in `afterEach` to keep tests isolated.\n *\n * @example\n * afterEach(() => cleanup());\n */\nexport function cleanup(): void {\n for (const el of _mountedElements) el.remove();\n _mountedElements.length = 0;\n _resetRawSanitizer();\n _resetLiveSignals();\n _clearStylesheetCache();\n _resetIdCounter();\n}\n\n/** @internal re-export for within() */\nexport type { QueryScope };\n"],"mappings":"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"}
|
package/dist/testing/mount.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Component mounting utilities for test environments.
|
|
3
3
|
*/
|
|
4
4
|
import type { Readable } from '@vielzeug/ripple';
|
|
5
|
-
import { type ComponentDefinition
|
|
5
|
+
import { type ComponentDefinition } from '../component-types';
|
|
6
6
|
import { type HTMLResult } from '../types/bindings';
|
|
7
7
|
import { type FlushOptions } from './flush';
|
|
8
8
|
import { type QueryScope } from './query';
|
|
@@ -48,13 +48,13 @@ export interface MountOptions {
|
|
|
48
48
|
/** Parent container (default: document.body) */
|
|
49
49
|
container?: HTMLElement;
|
|
50
50
|
/** Extra component options when passing an inline setup function */
|
|
51
|
-
componentOptions?: Omit<ComponentDefinition<any
|
|
51
|
+
componentOptions?: Omit<ComponentDefinition<any>, 'setup'>;
|
|
52
52
|
}
|
|
53
53
|
type MountProps = {
|
|
54
54
|
readonly [x: string]: Readable<unknown>;
|
|
55
55
|
};
|
|
56
56
|
export type MountSetup = {
|
|
57
|
-
bivarianceHack: (props: MountProps
|
|
57
|
+
bivarianceHack: (props: MountProps) => HTMLResult | null | Promise<HTMLResult | null>;
|
|
58
58
|
}['bivarianceHack'];
|
|
59
59
|
export declare const _mountedElements: HTMLElement[];
|
|
60
60
|
export declare let _componentTagCounter: number;
|
|
@@ -87,7 +87,7 @@ export declare function mount<T extends HTMLElement = HTMLElement>(tagOrSetup: M
|
|
|
87
87
|
* setup: (props) => html`<div>${props.count}</div>`,
|
|
88
88
|
* });
|
|
89
89
|
*/
|
|
90
|
-
export declare function mountComponent<T extends HTMLElement = HTMLElement>(tag: string, definition: ComponentDefinition<any
|
|
90
|
+
export declare function mountComponent<T extends HTMLElement = HTMLElement>(tag: string, definition: ComponentDefinition<any>, options?: MountOptions): Promise<Fixture<T>>;
|
|
91
91
|
/**
|
|
92
92
|
* Register a stub custom element (no-op if already defined).
|
|
93
93
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mount.d.ts","sourceRoot":"","sources":["../../src/testing/mount.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEjD,OAAO,EAAE,KAAK,mBAAmB,EAAE,
|
|
1
|
+
{"version":3,"file":"mount.d.ts","sourceRoot":"","sources":["../../src/testing/mount.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mount.js","names":[],"sources":["../../src/testing/mount.ts"],"sourcesContent":["/**\n * Component mounting utilities for test environments.\n */\n\nimport type { Readable } from '@vielzeug/ripple';\n\nimport { type ComponentDefinition, type SetupContextBag } from '../component-types';\nimport { define } from '../define';\nimport { _resetLiveSignals } from '../directives/live';\nimport { _resetRawSanitizer } from '../directives/raw';\nimport { type HTMLResult } from '../types/bindings';\nimport { _clearStylesheetCache } from '../utils/css';\nimport { _resetIdCounter } from '../utils/id';\nimport { flush, type FlushOptions } from './flush';\nimport { queryAllByText, queryByText, type QueryScope } from './query';\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\nexport interface Fixture<T extends HTMLElement = HTMLElement> {\n /** Delegates to `dispose()`. Enables `using` declarations. */\n [Symbol.dispose](): void;\n /** Run a callback then flush — the standard way to trigger and assert a reactive update */\n act(fn: () => unknown): Promise<void>;\n /** Set an attribute (boolean `false` removes it) then flush */\n attr(name: string, value: string | number | boolean): Promise<void>;\n /** Set multiple attributes then flush */\n attrs(record: Record<string, string | number | boolean>): Promise<void>;\n /** Remove the component from the DOM. Idempotent. */\n dispose(): void;\n /** `true` after `dispose()` has been called. */\n readonly disposed: boolean;\n /** The component element */\n element: T;\n /** Wait for all reactive updates and animation frames */\n flush(options?: FlushOptions): Promise<void>;\n /** Query a single element within shadow root */\n query<E extends Element = Element>(selector: string): E | null;\n /** Query all elements within shadow root */\n queryAll<E extends Element = Element>(selector: string): E[];\n /** Query all elements by their `data-testid` attribute */\n queryAllByTestId<E extends Element = Element>(testId: string): E[];\n /** Query all elements whose trimmed text content matches */\n queryAllByText<E extends Element = Element>(text: string, selector?: string): E[];\n /** Query a single element by its `data-testid` attribute */\n queryByTestId<E extends Element = Element>(testId: string): E | null;\n /** Query the first element whose trimmed text content matches */\n queryByText<E extends Element = Element>(text: string, selector?: string): E | null;\n /** The component's shadow root (null for light-DOM components) */\n readonly shadow: ShadowRoot | null;\n}\n\nexport interface MountOptions {\n /** Properties assigned directly onto the element */\n props?: Record<string, unknown>;\n /** HTML attributes to set on the element */\n attrs?: Record<string, string | number | boolean>;\n /** Inner HTML for slot content */\n html?: string;\n /** Parent container (default: document.body) */\n container?: HTMLElement;\n /** Extra component options when passing an inline setup function */\n componentOptions?: Omit<ComponentDefinition<any, any>, 'setup'>;\n}\n\ntype MountProps = { readonly [x: string]: Readable<unknown> };\n\n// Bivariant callback type keeps inline test callbacks ergonomic across varying setup context specializations.\nexport type MountSetup = {\n bivarianceHack: (props: MountProps, ctx: SetupContextBag<any>) => HTMLResult | null | Promise<HTMLResult | null>;\n}['bivarianceHack'];\n\n// ─── Test environment state ───────────────────────────────────────────────────\n\nexport const _mountedElements: HTMLElement[] = [];\nexport let _componentTagCounter = 0;\n\n/**\n * Resets global test counters used for deterministic IDs/markers.\n * @internal\n */\nexport const _resetCounters = (): void => {\n _resetIdCounter();\n _componentTagCounter = 0;\n};\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\nfunction applyAttr(element: Element, name: string, value: string | number | boolean): void {\n if (value === false) element.removeAttribute(name);\n else element.setAttribute(name, value === true ? '' : String(value));\n}\n\nconst toError = (value: unknown): Error => {\n return value instanceof Error ? value : new Error(String(value));\n};\n\nconst withWindowErrorCapture = async <T>(action: () => Promise<T>): Promise<T> => {\n if (typeof window === 'undefined') return action();\n\n let captured: Error | null = null;\n const onError = (event: ErrorEvent) => {\n captured = toError(event.error ?? event.message);\n event.preventDefault();\n };\n const onUnhandledRejection = (event: PromiseRejectionEvent) => {\n captured = toError(event.reason);\n event.preventDefault();\n };\n\n window.addEventListener('error', onError);\n window.addEventListener('unhandledrejection', onUnhandledRejection);\n\n try {\n const result = await action();\n\n if (captured) throw captured;\n\n return result;\n } finally {\n window.removeEventListener('error', onError);\n window.removeEventListener('unhandledrejection', onUnhandledRejection);\n }\n};\n\n// ─── API ─────────────────────────────────────────────────────────────────────\n\n/**\n * Mount a component into the DOM and return a test fixture.\n *\n * Accepts a registered tag name, an inline setup function, or a component\n * options object. Setup functions are auto-registered with generated tag names.\n *\n * @example — inline setup function\n * const { query } = await mount(() => {\n * const count = signal(0);\n * return html`<button @click=${() => count.value++}>${count}</button>`;\n * });\n *\n * @example — registered tag name\n * const { query } = await mount('my-counter');\n */\nexport async function mount<T extends HTMLElement = HTMLElement>(\n tagOrSetup: string,\n options?: MountOptions,\n): Promise<Fixture<T>>;\nexport async function mount<T extends HTMLElement = HTMLElement>(\n tagOrSetup: MountSetup,\n options?: MountOptions,\n): Promise<Fixture<T>>;\nexport async function mount<T extends HTMLElement = HTMLElement>(\n tagOrSetup: string | MountSetup,\n options: MountOptions = {},\n): Promise<Fixture<T>> {\n const { attrs = {}, componentOptions, container = document.body, html, props = {} } = options;\n\n let tagName: string;\n let inlineDefinition: ComponentDefinition<any, any> | undefined;\n\n if (typeof tagOrSetup === 'string') {\n tagName = tagOrSetup;\n } else {\n tagName = `trial-${++_componentTagCounter}-${Math.random().toString(36).slice(2, 7)}`;\n inlineDefinition = {\n ...(componentOptions ?? {}),\n setup: tagOrSetup as ComponentDefinition<any, any>['setup'],\n };\n }\n\n if (inlineDefinition) {\n define(tagName, inlineDefinition);\n }\n\n const element = document.createElement(tagName) as T;\n\n if (html) element.innerHTML = html;\n\n if (Object.keys(props).length) Object.assign(element, props);\n\n for (const [name, value] of Object.entries(attrs)) applyAttr(element, name, value);\n\n await withWindowErrorCapture(async () => {\n container.appendChild(element);\n _mountedElements.push(element);\n await flush();\n });\n\n let isDisposed = false;\n\n function dispose() {\n if (isDisposed) return;\n\n isDisposed = true;\n element.remove();\n\n const i = _mountedElements.indexOf(element);\n\n if (i !== -1) _mountedElements.splice(i, 1);\n }\n\n return {\n async act(fn) {\n await fn();\n await flush();\n },\n\n async attr(name, value) {\n applyAttr(element, name, value);\n await flush();\n },\n\n async attrs(record) {\n for (const [name, value] of Object.entries(record)) applyAttr(element, name, value);\n await flush();\n },\n\n dispose,\n\n get disposed(): boolean {\n return isDisposed;\n },\n\n element,\n\n flush,\n\n query<E extends Element = Element>(selector: string): E | null {\n return (element.shadowRoot ?? element).querySelector<E>(selector);\n },\n\n queryAll<E extends Element = Element>(selector: string): E[] {\n return Array.from((element.shadowRoot ?? element).querySelectorAll<E>(selector));\n },\n\n queryAllByTestId<E extends Element = Element>(testId: string): E[] {\n return Array.from((element.shadowRoot ?? element).querySelectorAll<E>(`[data-testid=\"${testId}\"]`));\n },\n\n queryAllByText<E extends Element = Element>(text: string, selector = '*'): E[] {\n return queryAllByText<E>((element.shadowRoot ?? element) as Element, text, selector);\n },\n\n queryByTestId<E extends Element = Element>(testId: string): E | null {\n return (element.shadowRoot ?? element).querySelector<E>(`[data-testid=\"${testId}\"]`);\n },\n\n queryByText<E extends Element = Element>(text: string, selector = '*'): E | null {\n return queryByText<E>((element.shadowRoot ?? element) as Element, text, selector);\n },\n\n get shadow(): ShadowRoot | null {\n return element.shadowRoot;\n },\n\n [Symbol.dispose]() {\n dispose();\n },\n };\n}\n\n/**\n * Register and mount a component definition in a single call.\n *\n * Combines `define(tag, definition)` + `mount(tag, options)` — the standard\n * pattern for testing full custom-element lifecycle (props, reconnect, etc.).\n *\n * @example\n * const { query } = await mountComponent('my-counter', {\n * props: { count: prop.number(0) },\n * setup: (props) => html`<div>${props.count}</div>`,\n * });\n */\nexport async function mountComponent<T extends HTMLElement = HTMLElement>(\n tag: string,\n definition: ComponentDefinition<any, any>,\n options?: MountOptions,\n): Promise<Fixture<T>> {\n define(tag, definition);\n\n return mount<T>(tag, options);\n}\n\n/**\n * Register a stub custom element (no-op if already defined).\n *\n * @example\n * mock('child-button', '<slot></slot>');\n */\nexport function mock(tagName: string, template = ''): void {\n if (!customElements.get(tagName)) {\n customElements.define(\n tagName,\n class extends HTMLElement {\n connectedCallback() {\n this.innerHTML = template;\n }\n },\n );\n }\n}\n\n/**\n * Remove all elements mounted via `mount()`.\n * Call in `afterEach` to keep tests isolated.\n *\n * @example\n * afterEach(() => cleanup());\n */\nexport function cleanup(): void {\n for (const el of _mountedElements) el.remove();\n _mountedElements.length = 0;\n _resetRawSanitizer();\n _resetLiveSignals();\n _clearStylesheetCache();\n _resetIdCounter();\n}\n\n/** @internal re-export for within() */\nexport type { QueryScope };\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"mount.js","names":[],"sources":["../../src/testing/mount.ts"],"sourcesContent":["/**\n * Component mounting utilities for test environments.\n */\n\nimport type { Readable } from '@vielzeug/ripple';\n\nimport { type ComponentDefinition } from '../component-types';\nimport { define } from '../define';\nimport { _resetLiveSignals } from '../directives/live';\nimport { _resetRawSanitizer } from '../directives/raw';\nimport { type HTMLResult } from '../types/bindings';\nimport { _clearStylesheetCache } from '../utils/css';\nimport { _resetIdCounter } from '../utils/id';\nimport { flush, type FlushOptions } from './flush';\nimport { queryAllByText, queryByText, type QueryScope } from './query';\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\nexport interface Fixture<T extends HTMLElement = HTMLElement> {\n /** Delegates to `dispose()`. Enables `using` declarations. */\n [Symbol.dispose](): void;\n /** Run a callback then flush — the standard way to trigger and assert a reactive update */\n act(fn: () => unknown): Promise<void>;\n /** Set an attribute (boolean `false` removes it) then flush */\n attr(name: string, value: string | number | boolean): Promise<void>;\n /** Set multiple attributes then flush */\n attrs(record: Record<string, string | number | boolean>): Promise<void>;\n /** Remove the component from the DOM. Idempotent. */\n dispose(): void;\n /** `true` after `dispose()` has been called. */\n readonly disposed: boolean;\n /** The component element */\n element: T;\n /** Wait for all reactive updates and animation frames */\n flush(options?: FlushOptions): Promise<void>;\n /** Query a single element within shadow root */\n query<E extends Element = Element>(selector: string): E | null;\n /** Query all elements within shadow root */\n queryAll<E extends Element = Element>(selector: string): E[];\n /** Query all elements by their `data-testid` attribute */\n queryAllByTestId<E extends Element = Element>(testId: string): E[];\n /** Query all elements whose trimmed text content matches */\n queryAllByText<E extends Element = Element>(text: string, selector?: string): E[];\n /** Query a single element by its `data-testid` attribute */\n queryByTestId<E extends Element = Element>(testId: string): E | null;\n /** Query the first element whose trimmed text content matches */\n queryByText<E extends Element = Element>(text: string, selector?: string): E | null;\n /** The component's shadow root (null for light-DOM components) */\n readonly shadow: ShadowRoot | null;\n}\n\nexport interface MountOptions {\n /** Properties assigned directly onto the element */\n props?: Record<string, unknown>;\n /** HTML attributes to set on the element */\n attrs?: Record<string, string | number | boolean>;\n /** Inner HTML for slot content */\n html?: string;\n /** Parent container (default: document.body) */\n container?: HTMLElement;\n /** Extra component options when passing an inline setup function */\n componentOptions?: Omit<ComponentDefinition<any>, 'setup'>;\n}\n\ntype MountProps = { readonly [x: string]: Readable<unknown> };\n\n// Bivariant callback type keeps inline test callbacks ergonomic across varying prop specializations.\nexport type MountSetup = {\n bivarianceHack: (props: MountProps) => HTMLResult | null | Promise<HTMLResult | null>;\n}['bivarianceHack'];\n\n// ─── Test environment state ───────────────────────────────────────────────────\n\nexport const _mountedElements: HTMLElement[] = [];\nexport let _componentTagCounter = 0;\n\n/**\n * Resets global test counters used for deterministic IDs/markers.\n * @internal\n */\nexport const _resetCounters = (): void => {\n _resetIdCounter();\n _componentTagCounter = 0;\n};\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\nfunction applyAttr(element: Element, name: string, value: string | number | boolean): void {\n if (value === false) element.removeAttribute(name);\n else element.setAttribute(name, value === true ? '' : String(value));\n}\n\nconst toError = (value: unknown): Error => {\n return value instanceof Error ? value : new Error(String(value));\n};\n\nconst withWindowErrorCapture = async <T>(action: () => Promise<T>): Promise<T> => {\n if (typeof window === 'undefined') return action();\n\n let captured: Error | null = null;\n const onError = (event: ErrorEvent) => {\n captured = toError(event.error ?? event.message);\n event.preventDefault();\n };\n const onUnhandledRejection = (event: PromiseRejectionEvent) => {\n captured = toError(event.reason);\n event.preventDefault();\n };\n\n window.addEventListener('error', onError);\n window.addEventListener('unhandledrejection', onUnhandledRejection);\n\n try {\n const result = await action();\n\n if (captured) throw captured;\n\n return result;\n } finally {\n window.removeEventListener('error', onError);\n window.removeEventListener('unhandledrejection', onUnhandledRejection);\n }\n};\n\n// ─── API ─────────────────────────────────────────────────────────────────────\n\n/**\n * Mount a component into the DOM and return a test fixture.\n *\n * Accepts a registered tag name, an inline setup function, or a component\n * options object. Setup functions are auto-registered with generated tag names.\n *\n * @example — inline setup function\n * const { query } = await mount(() => {\n * const count = signal(0);\n * return html`<button @click=${() => count.value++}>${count}</button>`;\n * });\n *\n * @example — registered tag name\n * const { query } = await mount('my-counter');\n */\nexport async function mount<T extends HTMLElement = HTMLElement>(\n tagOrSetup: string,\n options?: MountOptions,\n): Promise<Fixture<T>>;\nexport async function mount<T extends HTMLElement = HTMLElement>(\n tagOrSetup: MountSetup,\n options?: MountOptions,\n): Promise<Fixture<T>>;\nexport async function mount<T extends HTMLElement = HTMLElement>(\n tagOrSetup: string | MountSetup,\n options: MountOptions = {},\n): Promise<Fixture<T>> {\n const { attrs = {}, componentOptions, container = document.body, html, props = {} } = options;\n\n let tagName: string;\n let inlineDefinition: ComponentDefinition<any> | undefined;\n\n if (typeof tagOrSetup === 'string') {\n tagName = tagOrSetup;\n } else {\n tagName = `trial-${++_componentTagCounter}-${Math.random().toString(36).slice(2, 7)}`;\n inlineDefinition = {\n ...(componentOptions ?? {}),\n setup: tagOrSetup as ComponentDefinition<any>['setup'],\n };\n }\n\n if (inlineDefinition) {\n define(tagName, inlineDefinition);\n }\n\n const element = document.createElement(tagName) as T;\n\n if (html) element.innerHTML = html;\n\n if (Object.keys(props).length) Object.assign(element, props);\n\n for (const [name, value] of Object.entries(attrs)) applyAttr(element, name, value);\n\n await withWindowErrorCapture(async () => {\n container.appendChild(element);\n _mountedElements.push(element);\n await flush();\n });\n\n let isDisposed = false;\n\n function dispose() {\n if (isDisposed) return;\n\n isDisposed = true;\n element.remove();\n\n const i = _mountedElements.indexOf(element);\n\n if (i !== -1) _mountedElements.splice(i, 1);\n }\n\n return {\n async act(fn) {\n await fn();\n await flush();\n },\n\n async attr(name, value) {\n applyAttr(element, name, value);\n await flush();\n },\n\n async attrs(record) {\n for (const [name, value] of Object.entries(record)) applyAttr(element, name, value);\n await flush();\n },\n\n dispose,\n\n get disposed(): boolean {\n return isDisposed;\n },\n\n element,\n\n flush,\n\n query<E extends Element = Element>(selector: string): E | null {\n return (element.shadowRoot ?? element).querySelector<E>(selector);\n },\n\n queryAll<E extends Element = Element>(selector: string): E[] {\n return Array.from((element.shadowRoot ?? element).querySelectorAll<E>(selector));\n },\n\n queryAllByTestId<E extends Element = Element>(testId: string): E[] {\n return Array.from((element.shadowRoot ?? element).querySelectorAll<E>(`[data-testid=\"${testId}\"]`));\n },\n\n queryAllByText<E extends Element = Element>(text: string, selector = '*'): E[] {\n return queryAllByText<E>((element.shadowRoot ?? element) as Element, text, selector);\n },\n\n queryByTestId<E extends Element = Element>(testId: string): E | null {\n return (element.shadowRoot ?? element).querySelector<E>(`[data-testid=\"${testId}\"]`);\n },\n\n queryByText<E extends Element = Element>(text: string, selector = '*'): E | null {\n return queryByText<E>((element.shadowRoot ?? element) as Element, text, selector);\n },\n\n get shadow(): ShadowRoot | null {\n return element.shadowRoot;\n },\n\n [Symbol.dispose]() {\n dispose();\n },\n };\n}\n\n/**\n * Register and mount a component definition in a single call.\n *\n * Combines `define(tag, definition)` + `mount(tag, options)` — the standard\n * pattern for testing full custom-element lifecycle (props, reconnect, etc.).\n *\n * @example\n * const { query } = await mountComponent('my-counter', {\n * props: { count: prop.number(0) },\n * setup: (props) => html`<div>${props.count}</div>`,\n * });\n */\nexport async function mountComponent<T extends HTMLElement = HTMLElement>(\n tag: string,\n definition: ComponentDefinition<any>,\n options?: MountOptions,\n): Promise<Fixture<T>> {\n define(tag, definition);\n\n return mount<T>(tag, options);\n}\n\n/**\n * Register a stub custom element (no-op if already defined).\n *\n * @example\n * mock('child-button', '<slot></slot>');\n */\nexport function mock(tagName: string, template = ''): void {\n if (!customElements.get(tagName)) {\n customElements.define(\n tagName,\n class extends HTMLElement {\n connectedCallback() {\n this.innerHTML = template;\n }\n },\n );\n }\n}\n\n/**\n * Remove all elements mounted via `mount()`.\n * Call in `afterEach` to keep tests isolated.\n *\n * @example\n * afterEach(() => cleanup());\n */\nexport function cleanup(): void {\n for (const el of _mountedElements) el.remove();\n _mountedElements.length = 0;\n _resetRawSanitizer();\n _resetLiveSignals();\n _clearStylesheetCache();\n _resetIdCounter();\n}\n\n/** @internal re-export for within() */\nexport type { QueryScope };\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("./flush.cjs"),t=require("../runtime.cjs"),n=require("../
|
|
1
|
+
const e=require("./flush.cjs"),t=require("../runtime.cjs"),n=require("../props.cjs");let r=require("@vielzeug/ripple");async function i(i,a){let o=typeof i!=`function`,s=o?i:void 0,c=o?a:i,l=(0,r.scope)(),u=document.createElement(`div`);document.body.appendChild(u);let d=[],f={element:u,formResetCallbacks:[],mountCallbacks:d},p;try{l.run(()=>{t.runWithContext(f,()=>{let e=s?n.createProps(u,s):{};p=c(e)})});for(let e of d)try{l.run(()=>{t.runWithContext(f,()=>{let t=e();typeof t==`function`&&(0,r.onCleanup)(t)})})}catch{}}catch(e){throw l.dispose(),u.remove(),e}await e.flush();let m=!1;function h(){m||(m=!0,l.dispose(),u.remove())}return{dispose:h,get disposed(){return m},async flush(){await e.flush()},result:p,[Symbol.dispose](){h()}}}exports.renderHook=i;
|
|
2
2
|
//# sourceMappingURL=render-hook.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render-hook.cjs","names":[],"sources":["../../src/testing/render-hook.ts"],"sourcesContent":["/**\n * renderHook — test composable hooks (lifecycle, reactive state, etc.) in isolation\n * without rendering a full component template.\n */\n\nimport { onCleanup as _onCleanup, scope as _scope } from '@vielzeug/ripple';\n\nimport
|
|
1
|
+
{"version":3,"file":"render-hook.cjs","names":[],"sources":["../../src/testing/render-hook.ts"],"sourcesContent":["/**\n * renderHook — test composable hooks (lifecycle, reactive state, etc.) in isolation\n * without rendering a full component template.\n */\n\nimport { onCleanup as _onCleanup, scope as _scope } from '@vielzeug/ripple';\n\nimport { createProps, type InferProps, type PropInputDefs } from '../props';\nimport { runWithContext } from '../runtime';\nimport { flush } from './flush';\n\nexport interface HookFixture<T> {\n /** Delegates to `dispose()`. Enables `using` declarations. */\n [Symbol.dispose](): void;\n /** Teardown the hook (runs cleanups). Idempotent. */\n dispose(): void;\n /** `true` after `dispose()` has been called. */\n readonly disposed: boolean;\n /** Flush pending reactive updates */\n flush(): Promise<void>;\n /** The value returned by the setup function */\n result: T;\n}\n\n/**\n * Run a setup function in a component-like context and return the result.\n * `onMounted`/`onCleanup`/`bind`/... work exactly as they would inside a real\n * `setup()`, since they resolve the same implicit current-component context.\n *\n * @example No props\n * ```ts\n * const { result, flush } = await renderHook(() => {\n * const count = signal(0);\n * onMounted(() => { count.value = 1; });\n * return count;\n * });\n * expect(result.value).toBe(1);\n * ```\n *\n * @example With props\n * ```ts\n * const { result } = await renderHook(\n * { label: prop.string('hello') },\n * (props) => props.label,\n * );\n * expect(result.value).toBe('hello');\n * ```\n */\nexport async function renderHook<T>(setup: (_props: Record<never, never>) => T): Promise<HookFixture<T>>;\nexport async function renderHook<D extends PropInputDefs, T>(\n propDefs: D,\n setup: (props: InferProps<D>) => T,\n): Promise<HookFixture<T>>;\nexport async function renderHook<D extends PropInputDefs, T>(\n setupOrDefs: ((_props: Record<never, never>) => T) | D,\n maybeSetup?: (props: InferProps<D>) => T,\n): Promise<HookFixture<T>> {\n const hasPropDefs = typeof setupOrDefs !== 'function';\n const propDefs = hasPropDefs ? (setupOrDefs as D) : undefined;\n const setup = hasPropDefs ? maybeSetup! : (setupOrDefs as (_props: Record<never, never>) => T);\n\n const hostScope = _scope();\n // Minimal host element so getHost() works inside setup\n const hostEl = document.createElement('div');\n\n document.body.appendChild(hostEl);\n\n const mountCallbacks: Array<() => void | (() => void)> = [];\n const formResetCallbacks: Array<() => void> = [];\n const ctx = { element: hostEl, formResetCallbacks, mountCallbacks };\n\n let result!: T;\n\n try {\n hostScope.run(() => {\n runWithContext(ctx, () => {\n const props = propDefs ? createProps(hostEl, propDefs as PropInputDefs) : ({} as Record<never, never>);\n\n result = setup(props as InferProps<D>);\n });\n });\n\n // Run mount callbacks (simulates connectedCallback → queueMicrotask)\n for (const cb of mountCallbacks) {\n try {\n hostScope.run(() => {\n runWithContext(ctx, () => {\n const cleanup = cb();\n\n if (typeof cleanup === 'function') _onCleanup(cleanup);\n });\n });\n } catch {\n /* test will surface the error via result */\n }\n }\n } catch (err) {\n hostScope.dispose();\n hostEl.remove();\n throw err;\n }\n\n await flush();\n\n let isDisposed = false;\n\n function dispose() {\n if (isDisposed) return;\n\n isDisposed = true;\n hostScope.dispose();\n hostEl.remove();\n }\n\n return {\n dispose,\n\n get disposed(): boolean {\n return isDisposed;\n },\n\n async flush() {\n await flush();\n },\n\n result,\n\n [Symbol.dispose]() {\n dispose();\n },\n };\n}\n"],"mappings":"uHAqDA,eAAsB,EACpB,EACA,EACyB,CACzB,IAAM,EAAc,OAAO,GAAgB,WACrC,EAAW,EAAe,EAAoB,IAAA,GAC9C,EAAQ,EAAc,EAAe,EAErC,GAAA,EAAA,EAAA,MAAA,CAAmB,EAEnB,EAAS,SAAS,cAAc,KAAK,EAE3C,SAAS,KAAK,YAAY,CAAM,EAEhC,IAAM,EAAmD,CAAC,EAEpD,EAAM,CAAE,QAAS,EAAQ,mBAAA,CAAA,EAAoB,gBAAe,EAE9D,EAEJ,GAAI,CACF,EAAU,QAAU,CAClB,EAAA,eAAe,MAAW,CACxB,IAAM,EAAQ,EAAW,EAAA,YAAY,EAAQ,CAAyB,EAAK,CAAC,EAE5E,EAAS,EAAM,CAAsB,CACvC,CAAC,CACH,CAAC,EAGD,IAAK,IAAM,KAAM,EACf,GAAI,CACF,EAAU,QAAU,CAClB,EAAA,eAAe,MAAW,CACxB,IAAM,EAAU,EAAG,EAEf,OAAO,GAAY,aAAY,EAAA,EAAA,UAAA,CAAW,CAAO,CACvD,CAAC,CACH,CAAC,CACH,MAAQ,CAER,CAEJ,OAAS,EAAK,CAGZ,MAFA,EAAU,QAAQ,EAClB,EAAO,OAAO,EACR,CACR,CAEA,MAAM,EAAA,MAAM,EAEZ,IAAI,EAAa,GAEjB,SAAS,GAAU,CACb,IAEJ,EAAa,GACb,EAAU,QAAQ,EAClB,EAAO,OAAO,EAChB,CAEA,MAAO,CACL,UAEA,IAAI,UAAoB,CACtB,OAAO,CACT,EAEA,MAAM,OAAQ,CACZ,MAAM,EAAA,MAAM,CACd,EAEA,SAEA,CAAC,OAAO,UAAW,CACjB,EAAQ,CACV,CACF,CACF"}
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
* renderHook — test composable hooks (lifecycle, reactive state, etc.) in isolation
|
|
3
3
|
* without rendering a full component template.
|
|
4
4
|
*/
|
|
5
|
-
import type { SetupContextBag } from '../component-types';
|
|
6
5
|
import { type InferProps, type PropInputDefs } from '../props';
|
|
7
6
|
export interface HookFixture<T> {
|
|
8
7
|
/** Delegates to `dispose()`. Enables `using` declarations. */
|
|
@@ -18,13 +17,14 @@ export interface HookFixture<T> {
|
|
|
18
17
|
}
|
|
19
18
|
/**
|
|
20
19
|
* Run a setup function in a component-like context and return the result.
|
|
21
|
-
*
|
|
20
|
+
* `onMounted`/`onCleanup`/`bind`/... work exactly as they would inside a real
|
|
21
|
+
* `setup()`, since they resolve the same implicit current-component context.
|
|
22
22
|
*
|
|
23
23
|
* @example No props
|
|
24
24
|
* ```ts
|
|
25
|
-
* const { result, flush } = await renderHook((
|
|
25
|
+
* const { result, flush } = await renderHook(() => {
|
|
26
26
|
* const count = signal(0);
|
|
27
|
-
*
|
|
27
|
+
* onMounted(() => { count.value = 1; });
|
|
28
28
|
* return count;
|
|
29
29
|
* });
|
|
30
30
|
* expect(result.value).toBe(1);
|
|
@@ -39,6 +39,6 @@ export interface HookFixture<T> {
|
|
|
39
39
|
* expect(result.value).toBe('hello');
|
|
40
40
|
* ```
|
|
41
41
|
*/
|
|
42
|
-
export declare function renderHook<T>(setup: (_props: Record<never, never
|
|
43
|
-
export declare function renderHook<D extends PropInputDefs, T>(propDefs: D, setup: (props: InferProps<D
|
|
42
|
+
export declare function renderHook<T>(setup: (_props: Record<never, never>) => T): Promise<HookFixture<T>>;
|
|
43
|
+
export declare function renderHook<D extends PropInputDefs, T>(propDefs: D, setup: (props: InferProps<D>) => T): Promise<HookFixture<T>>;
|
|
44
44
|
//# sourceMappingURL=render-hook.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render-hook.d.ts","sourceRoot":"","sources":["../../src/testing/render-hook.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,
|
|
1
|
+
{"version":3,"file":"render-hook.d.ts","sourceRoot":"","sources":["../../src/testing/render-hook.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,EAAe,KAAK,UAAU,EAAE,KAAK,aAAa,EAAE,MAAM,UAAU,CAAC;AAI5E,MAAM,WAAW,WAAW,CAAC,CAAC;IAC5B,8DAA8D;IAC9D,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;IACzB,qDAAqD;IACrD,OAAO,IAAI,IAAI,CAAC;IAChB,gDAAgD;IAChD,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,qCAAqC;IACrC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,+CAA+C;IAC/C,MAAM,EAAE,CAAC,CAAC;CACX;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAsB,UAAU,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC;AACzG,wBAAsB,UAAU,CAAC,CAAC,SAAS,aAAa,EAAE,CAAC,EACzD,QAAQ,EAAE,CAAC,EACX,KAAK,EAAE,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,GACjC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{flush as e}from"./flush.js";import{runWithContext as t}from"../runtime.js";import{
|
|
1
|
+
import{flush as e}from"./flush.js";import{runWithContext as t}from"../runtime.js";import{createProps as n}from"../props.js";import{onCleanup as r,scope as i}from"@vielzeug/ripple";async function a(a,o){let s=typeof a!=`function`,c=s?a:void 0,l=s?o:a,u=i(),d=document.createElement(`div`);document.body.appendChild(d);let f=[],p={element:d,formResetCallbacks:[],mountCallbacks:f},m;try{u.run(()=>{t(p,()=>{let e=c?n(d,c):{};m=l(e)})});for(let e of f)try{u.run(()=>{t(p,()=>{let t=e();typeof t==`function`&&r(t)})})}catch{}}catch(e){throw u.dispose(),d.remove(),e}await e();let h=!1;function g(){h||(h=!0,u.dispose(),d.remove())}return{dispose:g,get disposed(){return h},async flush(){await e()},result:m,[Symbol.dispose](){g()}}}export{a as renderHook};
|
|
2
2
|
//# sourceMappingURL=render-hook.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render-hook.js","names":[],"sources":["../../src/testing/render-hook.ts"],"sourcesContent":["/**\n * renderHook — test composable hooks (lifecycle, reactive state, etc.) in isolation\n * without rendering a full component template.\n */\n\nimport { onCleanup as _onCleanup, scope as _scope } from '@vielzeug/ripple';\n\nimport
|
|
1
|
+
{"version":3,"file":"render-hook.js","names":[],"sources":["../../src/testing/render-hook.ts"],"sourcesContent":["/**\n * renderHook — test composable hooks (lifecycle, reactive state, etc.) in isolation\n * without rendering a full component template.\n */\n\nimport { onCleanup as _onCleanup, scope as _scope } from '@vielzeug/ripple';\n\nimport { createProps, type InferProps, type PropInputDefs } from '../props';\nimport { runWithContext } from '../runtime';\nimport { flush } from './flush';\n\nexport interface HookFixture<T> {\n /** Delegates to `dispose()`. Enables `using` declarations. */\n [Symbol.dispose](): void;\n /** Teardown the hook (runs cleanups). Idempotent. */\n dispose(): void;\n /** `true` after `dispose()` has been called. */\n readonly disposed: boolean;\n /** Flush pending reactive updates */\n flush(): Promise<void>;\n /** The value returned by the setup function */\n result: T;\n}\n\n/**\n * Run a setup function in a component-like context and return the result.\n * `onMounted`/`onCleanup`/`bind`/... work exactly as they would inside a real\n * `setup()`, since they resolve the same implicit current-component context.\n *\n * @example No props\n * ```ts\n * const { result, flush } = await renderHook(() => {\n * const count = signal(0);\n * onMounted(() => { count.value = 1; });\n * return count;\n * });\n * expect(result.value).toBe(1);\n * ```\n *\n * @example With props\n * ```ts\n * const { result } = await renderHook(\n * { label: prop.string('hello') },\n * (props) => props.label,\n * );\n * expect(result.value).toBe('hello');\n * ```\n */\nexport async function renderHook<T>(setup: (_props: Record<never, never>) => T): Promise<HookFixture<T>>;\nexport async function renderHook<D extends PropInputDefs, T>(\n propDefs: D,\n setup: (props: InferProps<D>) => T,\n): Promise<HookFixture<T>>;\nexport async function renderHook<D extends PropInputDefs, T>(\n setupOrDefs: ((_props: Record<never, never>) => T) | D,\n maybeSetup?: (props: InferProps<D>) => T,\n): Promise<HookFixture<T>> {\n const hasPropDefs = typeof setupOrDefs !== 'function';\n const propDefs = hasPropDefs ? (setupOrDefs as D) : undefined;\n const setup = hasPropDefs ? maybeSetup! : (setupOrDefs as (_props: Record<never, never>) => T);\n\n const hostScope = _scope();\n // Minimal host element so getHost() works inside setup\n const hostEl = document.createElement('div');\n\n document.body.appendChild(hostEl);\n\n const mountCallbacks: Array<() => void | (() => void)> = [];\n const formResetCallbacks: Array<() => void> = [];\n const ctx = { element: hostEl, formResetCallbacks, mountCallbacks };\n\n let result!: T;\n\n try {\n hostScope.run(() => {\n runWithContext(ctx, () => {\n const props = propDefs ? createProps(hostEl, propDefs as PropInputDefs) : ({} as Record<never, never>);\n\n result = setup(props as InferProps<D>);\n });\n });\n\n // Run mount callbacks (simulates connectedCallback → queueMicrotask)\n for (const cb of mountCallbacks) {\n try {\n hostScope.run(() => {\n runWithContext(ctx, () => {\n const cleanup = cb();\n\n if (typeof cleanup === 'function') _onCleanup(cleanup);\n });\n });\n } catch {\n /* test will surface the error via result */\n }\n }\n } catch (err) {\n hostScope.dispose();\n hostEl.remove();\n throw err;\n }\n\n await flush();\n\n let isDisposed = false;\n\n function dispose() {\n if (isDisposed) return;\n\n isDisposed = true;\n hostScope.dispose();\n hostEl.remove();\n }\n\n return {\n dispose,\n\n get disposed(): boolean {\n return isDisposed;\n },\n\n async flush() {\n await flush();\n },\n\n result,\n\n [Symbol.dispose]() {\n dispose();\n },\n };\n}\n"],"mappings":"oLAqDA,eAAsB,EACpB,EACA,EACyB,CACzB,IAAM,EAAc,OAAO,GAAgB,WACrC,EAAW,EAAe,EAAoB,IAAA,GAC9C,EAAQ,EAAc,EAAe,EAErC,EAAY,EAAO,EAEnB,EAAS,SAAS,cAAc,KAAK,EAE3C,SAAS,KAAK,YAAY,CAAM,EAEhC,IAAM,EAAmD,CAAC,EAEpD,EAAM,CAAE,QAAS,EAAQ,mBAAA,CAAA,EAAoB,gBAAe,EAE9D,EAEJ,GAAI,CACF,EAAU,QAAU,CAClB,EAAe,MAAW,CACxB,IAAM,EAAQ,EAAW,EAAY,EAAQ,CAAyB,EAAK,CAAC,EAE5E,EAAS,EAAM,CAAsB,CACvC,CAAC,CACH,CAAC,EAGD,IAAK,IAAM,KAAM,EACf,GAAI,CACF,EAAU,QAAU,CAClB,EAAe,MAAW,CACxB,IAAM,EAAU,EAAG,EAEf,OAAO,GAAY,YAAY,EAAW,CAAO,CACvD,CAAC,CACH,CAAC,CACH,MAAQ,CAER,CAEJ,OAAS,EAAK,CAGZ,MAFA,EAAU,QAAQ,EAClB,EAAO,OAAO,EACR,CACR,CAEA,MAAM,EAAM,EAEZ,IAAI,EAAa,GAEjB,SAAS,GAAU,CACb,IAEJ,EAAa,GACb,EAAU,QAAQ,EAClB,EAAO,OAAO,EAChB,CAEA,MAAO,CACL,UAEA,IAAI,UAAoB,CACtB,OAAO,CACT,EAEA,MAAM,OAAQ,CACZ,MAAM,EAAM,CACd,EAEA,SAEA,CAAC,OAAO,UAAW,CACjB,EAAQ,CACV,CACF,CACF"}
|
package/dist/testing/testing.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("./flush.cjs"),t=require("./
|
|
1
|
+
const e=require("./flush.cjs"),t=require("./dom.cjs"),n=require("./form-internals-polyfill.cjs"),r=require("./query.cjs"),i=require("./mount.cjs"),a=require("./events.cjs"),o=require("./interactions.cjs"),s=require("./wait.cjs"),c=require("./render-hook.cjs");function l(e){e(i.cleanup),n.installFormInternalsPolyfill()}exports.FLUSH_DEEP=e.FLUSH_DEEP,exports.cleanup=i.cleanup,exports.createPointerEvent=a.createPointerEvent,exports.fire=a.fire,exports.flush=e.flush,exports.install=l,exports.installFormInternalsPolyfill=n.installFormInternalsPolyfill,exports.mock=i.mock,exports.mount=i.mount,exports.mountComponent=i.mountComponent,exports.renderHook=c.renderHook,exports.user=o.user,exports.waitFor=s.waitFor,exports.waitForEvent=s.waitForEvent,exports.walkFlatTree=t.walkFlatTree,exports.within=r.within;
|
|
2
2
|
//# sourceMappingURL=testing.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testing.cjs","names":[],"sources":["../../src/testing/testing.ts"],"sourcesContent":["/**\n * Testing utilities for Ore components\n *\n * ⚠️ Requires DOM environment (browser / jsdom / happy-dom)\n *\n * This barrel re-exports the full testing API for convenience.\n * For tree-shaking, import directly from the focused sub-modules.\n */\n\nimport { cleanup } from './mount';\n\nexport { fire, createPointerEvent } from './events';\nexport { FLUSH_DEEP, flush, type FlushOptions } from './flush';\nexport { user } from './interactions';\nexport { cleanup, mock, mount, mountComponent, type Fixture, type MountOptions, type MountSetup } from './mount';\nexport { within, type QueryScope } from './query';\nexport { waitFor, waitForEvent, type WaitOptions } from './wait';\nexport { renderHook, type HookFixture } from './render-hook';\n\n/**\n * Register auto-cleanup after each test
|
|
1
|
+
{"version":3,"file":"testing.cjs","names":[],"sources":["../../src/testing/testing.ts"],"sourcesContent":["/**\n * Testing utilities for Ore components\n *\n * ⚠️ Requires DOM environment (browser / jsdom / happy-dom)\n *\n * This barrel re-exports the full testing API for convenience.\n * For tree-shaking, import directly from the focused sub-modules.\n */\n\nimport { installFormInternalsPolyfill } from './form-internals-polyfill';\nimport { cleanup } from './mount';\n\nexport { fire, createPointerEvent } from './events';\nexport { FLUSH_DEEP, flush, type FlushOptions } from './flush';\nexport { walkFlatTree } from './dom';\nexport { installFormInternalsPolyfill } from './form-internals-polyfill';\nexport { user } from './interactions';\nexport { cleanup, mock, mount, mountComponent, type Fixture, type MountOptions, type MountSetup } from './mount';\nexport { within, type QueryScope } from './query';\nexport { waitFor, waitForEvent, type WaitOptions } from './wait';\nexport { renderHook, type HookFixture } from './render-hook';\n\n/**\n * Register auto-cleanup after each test, and polyfill the jsdom/happy-dom gaps in\n * `ElementInternals`/`FormData`/`<form>.reset()` that `useField()` needs (see\n * `installFormInternalsPolyfill` — safe to call even in a suite with no form-associated\n * components; every patch it installs is a guarded no-op when its target is missing).\n * Call once in your test setup file.\n *\n * @example\n * // vitest.setup.ts\n * import { afterEach } from 'vitest';\n * import { install } from '@vielzeug/ore/testing';\n * install(afterEach);\n */\nexport function install(afterEachHook: (fn: () => void) => void): void {\n afterEachHook(cleanup);\n installFormInternalsPolyfill();\n}\n"],"mappings":"oQAmCA,SAAgB,EAAQ,EAA+C,CACrE,EAAc,EAAA,OAAO,EACrB,EAAA,6BAA6B,CAC/B"}
|
|
@@ -8,13 +8,19 @@
|
|
|
8
8
|
*/
|
|
9
9
|
export { fire, createPointerEvent } from './events';
|
|
10
10
|
export { FLUSH_DEEP, flush, type FlushOptions } from './flush';
|
|
11
|
+
export { walkFlatTree } from './dom';
|
|
12
|
+
export { installFormInternalsPolyfill } from './form-internals-polyfill';
|
|
11
13
|
export { user } from './interactions';
|
|
12
14
|
export { cleanup, mock, mount, mountComponent, type Fixture, type MountOptions, type MountSetup } from './mount';
|
|
13
15
|
export { within, type QueryScope } from './query';
|
|
14
16
|
export { waitFor, waitForEvent, type WaitOptions } from './wait';
|
|
15
17
|
export { renderHook, type HookFixture } from './render-hook';
|
|
16
18
|
/**
|
|
17
|
-
* Register auto-cleanup after each test
|
|
19
|
+
* Register auto-cleanup after each test, and polyfill the jsdom/happy-dom gaps in
|
|
20
|
+
* `ElementInternals`/`FormData`/`<form>.reset()` that `useField()` needs (see
|
|
21
|
+
* `installFormInternalsPolyfill` — safe to call even in a suite with no form-associated
|
|
22
|
+
* components; every patch it installs is a guarded no-op when its target is missing).
|
|
23
|
+
* Call once in your test setup file.
|
|
18
24
|
*
|
|
19
25
|
* @example
|
|
20
26
|
* // vitest.setup.ts
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../../src/testing/testing.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;
|
|
1
|
+
{"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../../src/testing/testing.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,OAAO,EAAE,IAAI,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AACpD,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,KAAK,YAAY,EAAE,MAAM,SAAS,CAAC;AAC/D,OAAO,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AACrC,OAAO,EAAE,4BAA4B,EAAE,MAAM,2BAA2B,CAAC;AACzE,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,cAAc,EAAE,KAAK,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,UAAU,EAAE,MAAM,SAAS,CAAC;AACjH,OAAO,EAAE,MAAM,EAAE,KAAK,UAAU,EAAE,MAAM,SAAS,CAAC;AAClD,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,KAAK,WAAW,EAAE,MAAM,QAAQ,CAAC;AACjE,OAAO,EAAE,UAAU,EAAE,KAAK,WAAW,EAAE,MAAM,eAAe,CAAC;AAE7D;;;;;;;;;;;;GAYG;AACH,wBAAgB,OAAO,CAAC,aAAa,EAAE,CAAC,EAAE,EAAE,MAAM,IAAI,KAAK,IAAI,GAAG,IAAI,CAGrE"}
|
package/dist/testing/testing.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{FLUSH_DEEP as e,flush as t}from"./flush.js";import{
|
|
1
|
+
import{FLUSH_DEEP as e,flush as t}from"./flush.js";import{walkFlatTree as n}from"./dom.js";import{installFormInternalsPolyfill as r}from"./form-internals-polyfill.js";import{within as i}from"./query.js";import{cleanup as a,mock as o,mount as s,mountComponent as c}from"./mount.js";import{createPointerEvent as l,fire as u}from"./events.js";import{user as d}from"./interactions.js";import{waitFor as f,waitForEvent as p}from"./wait.js";import{renderHook as m}from"./render-hook.js";function h(e){e(a),r()}export{e as FLUSH_DEEP,a as cleanup,l as createPointerEvent,u as fire,t as flush,h as install,r as installFormInternalsPolyfill,o as mock,s as mount,c as mountComponent,m as renderHook,d as user,f as waitFor,p as waitForEvent,n as walkFlatTree,i as within};
|
|
2
2
|
//# sourceMappingURL=testing.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testing.js","names":[],"sources":["../../src/testing/testing.ts"],"sourcesContent":["/**\n * Testing utilities for Ore components\n *\n * ⚠️ Requires DOM environment (browser / jsdom / happy-dom)\n *\n * This barrel re-exports the full testing API for convenience.\n * For tree-shaking, import directly from the focused sub-modules.\n */\n\nimport { cleanup } from './mount';\n\nexport { fire, createPointerEvent } from './events';\nexport { FLUSH_DEEP, flush, type FlushOptions } from './flush';\nexport { user } from './interactions';\nexport { cleanup, mock, mount, mountComponent, type Fixture, type MountOptions, type MountSetup } from './mount';\nexport { within, type QueryScope } from './query';\nexport { waitFor, waitForEvent, type WaitOptions } from './wait';\nexport { renderHook, type HookFixture } from './render-hook';\n\n/**\n * Register auto-cleanup after each test
|
|
1
|
+
{"version":3,"file":"testing.js","names":[],"sources":["../../src/testing/testing.ts"],"sourcesContent":["/**\n * Testing utilities for Ore components\n *\n * ⚠️ Requires DOM environment (browser / jsdom / happy-dom)\n *\n * This barrel re-exports the full testing API for convenience.\n * For tree-shaking, import directly from the focused sub-modules.\n */\n\nimport { installFormInternalsPolyfill } from './form-internals-polyfill';\nimport { cleanup } from './mount';\n\nexport { fire, createPointerEvent } from './events';\nexport { FLUSH_DEEP, flush, type FlushOptions } from './flush';\nexport { walkFlatTree } from './dom';\nexport { installFormInternalsPolyfill } from './form-internals-polyfill';\nexport { user } from './interactions';\nexport { cleanup, mock, mount, mountComponent, type Fixture, type MountOptions, type MountSetup } from './mount';\nexport { within, type QueryScope } from './query';\nexport { waitFor, waitForEvent, type WaitOptions } from './wait';\nexport { renderHook, type HookFixture } from './render-hook';\n\n/**\n * Register auto-cleanup after each test, and polyfill the jsdom/happy-dom gaps in\n * `ElementInternals`/`FormData`/`<form>.reset()` that `useField()` needs (see\n * `installFormInternalsPolyfill` — safe to call even in a suite with no form-associated\n * components; every patch it installs is a guarded no-op when its target is missing).\n * Call once in your test setup file.\n *\n * @example\n * // vitest.setup.ts\n * import { afterEach } from 'vitest';\n * import { install } from '@vielzeug/ore/testing';\n * install(afterEach);\n */\nexport function install(afterEachHook: (fn: () => void) => void): void {\n afterEachHook(cleanup);\n installFormInternalsPolyfill();\n}\n"],"mappings":"ieAmCA,SAAgB,EAAQ,EAA+C,CACrE,EAAc,CAAO,EACrB,EAA6B,CAC/B"}
|
package/dist/testing.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./testing/flush.cjs"),t=require("./testing/
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./testing/flush.cjs"),t=require("./testing/dom.cjs"),n=require("./testing/form-internals-polyfill.cjs"),r=require("./testing/query.cjs"),i=require("./testing/mount.cjs"),a=require("./testing/events.cjs"),o=require("./testing/interactions.cjs"),s=require("./testing/wait.cjs"),c=require("./testing/render-hook.cjs"),l=require("./testing/testing.cjs");exports.FLUSH_DEEP=e.FLUSH_DEEP,exports.cleanup=i.cleanup,exports.createPointerEvent=a.createPointerEvent,exports.fire=a.fire,exports.flush=e.flush,exports.install=l.install,exports.installFormInternalsPolyfill=n.installFormInternalsPolyfill,exports.mock=i.mock,exports.mount=i.mount,exports.mountComponent=i.mountComponent,exports.renderHook=c.renderHook,exports.user=o.user,exports.waitFor=s.waitFor,exports.waitForEvent=s.waitForEvent,exports.walkFlatTree=t.walkFlatTree,exports.within=r.within;
|
package/dist/testing.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{FLUSH_DEEP as e,flush as t}from"./testing/flush.js";import{
|
|
1
|
+
import{FLUSH_DEEP as e,flush as t}from"./testing/flush.js";import{walkFlatTree as n}from"./testing/dom.js";import{installFormInternalsPolyfill as r}from"./testing/form-internals-polyfill.js";import{within as i}from"./testing/query.js";import{cleanup as a,mock as o,mount as s,mountComponent as c}from"./testing/mount.js";import{createPointerEvent as l,fire as u}from"./testing/events.js";import{user as d}from"./testing/interactions.js";import{waitFor as f,waitForEvent as p}from"./testing/wait.js";import{renderHook as m}from"./testing/render-hook.js";import{install as h}from"./testing/testing.js";export{e as FLUSH_DEEP,a as cleanup,l as createPointerEvent,u as fire,t as flush,h as install,r as installFormInternalsPolyfill,o as mock,s as mount,c as mountComponent,m as renderHook,d as user,f as waitFor,p as waitForEvent,n as walkFlatTree,i as within};
|
package/dist/utils/emit.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
const e=require("../runtime.cjs");var t={bubbles:!0,cancelable:!0,composed:!1},n=()=>{let n=e.getHost();return((e,...r)=>{let i=r.length>0?{...t,detail:r[0]}:t;return n.dispatchEvent(new CustomEvent(String(e),i))})};exports.useEmit=n;
|
|
2
2
|
//# sourceMappingURL=emit.cjs.map
|
package/dist/utils/emit.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"emit.cjs","names":[],"sources":["../../src/utils/emit.ts"],"sourcesContent":["/**\n * Type-safe custom event emission
|
|
1
|
+
{"version":3,"file":"emit.cjs","names":[],"sources":["../../src/utils/emit.ts"],"sourcesContent":["/**\n * Type-safe custom event emission for components.\n */\n\nimport { getHost } from '../runtime';\n\ntype NoDetail = void | undefined | never;\ntype KeysWithoutDetail<T extends Record<string, unknown>> = {\n [P in keyof T]: [T[P]] extends [NoDetail] ? P : never;\n}[keyof T];\n\ntype StrictEmitFn<T extends Record<string, unknown>> = {\n <K extends KeysWithoutDetail<T>>(event: K): boolean;\n <K extends Exclude<keyof T, KeysWithoutDetail<T>>>(event: K, detail: T[K]): boolean;\n};\n\ntype LooseEmitFn = (event: string, detail?: unknown) => boolean;\n\nexport type EmitFn<T extends Record<string, unknown>> = StrictEmitFn<T> & LooseEmitFn;\n\nconst DEFAULT_FIRE_OPTIONS = { bubbles: true, cancelable: true, composed: false };\n\n/**\n * Returns a typed `emit()` function bound to the current component's host element.\n * Call once during `setup()` — the `Emits` type parameter maps event names to\n * their `detail` payload type.\n *\n * Every event is dispatched `cancelable: true`, and `emit()` returns `dispatchEvent`'s\n * own boolean result — `false` when a listener called `preventDefault()`. Components\n * that need to know whether a listener cancelled an event (e.g. to skip a default\n * action) read this return value directly instead of hand-rolling `dispatchEvent`.\n *\n * @example\n * ```ts\n * type Events = { close: undefined; change: { value: string } };\n *\n * setup(props) {\n * const emit = useEmit<Events>();\n * emit('close');\n *\n * const notCancelled = emit('change', { value: 'ok' });\n * if (notCancelled) props.value.value = 'ok';\n * }\n * ```\n */\nexport const useEmit = <T extends Record<string, unknown> = Record<string, never>>(): EmitFn<T> => {\n const host = getHost();\n\n return ((event: keyof T, ...rest: unknown[]) => {\n const customEventInit = rest.length > 0 ? { ...DEFAULT_FIRE_OPTIONS, detail: rest[0] } : DEFAULT_FIRE_OPTIONS;\n\n return host.dispatchEvent(new CustomEvent<unknown>(String(event), customEventInit));\n }) as EmitFn<T>;\n};\n"],"mappings":"kCAoBA,IAAM,EAAuB,CAAE,QAAS,GAAM,WAAY,GAAM,SAAU,EAAM,EAyBnE,MAAsF,CACjG,IAAM,EAAO,EAAA,QAAQ,EAErB,QAAS,EAAgB,GAAG,IAAoB,CAC9C,IAAM,EAAkB,EAAK,OAAS,EAAI,CAAE,GAAG,EAAsB,OAAQ,EAAK,EAAG,EAAI,EAEzF,OAAO,EAAK,cAAc,IAAI,YAAqB,OAAO,CAAK,EAAG,CAAe,CAAC,CACpF,EACF"}
|
package/dist/utils/emit.d.ts
CHANGED
|
@@ -1,16 +1,39 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Type-safe custom event emission
|
|
2
|
+
* Type-safe custom event emission for components.
|
|
3
3
|
*/
|
|
4
4
|
type NoDetail = void | undefined | never;
|
|
5
5
|
type KeysWithoutDetail<T extends Record<string, unknown>> = {
|
|
6
6
|
[P in keyof T]: [T[P]] extends [NoDetail] ? P : never;
|
|
7
7
|
}[keyof T];
|
|
8
8
|
type StrictEmitFn<T extends Record<string, unknown>> = {
|
|
9
|
-
<K extends KeysWithoutDetail<T>>(event: K):
|
|
10
|
-
<K extends Exclude<keyof T, KeysWithoutDetail<T>>>(event: K, detail: T[K]):
|
|
9
|
+
<K extends KeysWithoutDetail<T>>(event: K): boolean;
|
|
10
|
+
<K extends Exclude<keyof T, KeysWithoutDetail<T>>>(event: K, detail: T[K]): boolean;
|
|
11
11
|
};
|
|
12
|
-
type LooseEmitFn = (event: string, detail?: unknown) =>
|
|
12
|
+
type LooseEmitFn = (event: string, detail?: unknown) => boolean;
|
|
13
13
|
export type EmitFn<T extends Record<string, unknown>> = StrictEmitFn<T> & LooseEmitFn;
|
|
14
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Returns a typed `emit()` function bound to the current component's host element.
|
|
16
|
+
* Call once during `setup()` — the `Emits` type parameter maps event names to
|
|
17
|
+
* their `detail` payload type.
|
|
18
|
+
*
|
|
19
|
+
* Every event is dispatched `cancelable: true`, and `emit()` returns `dispatchEvent`'s
|
|
20
|
+
* own boolean result — `false` when a listener called `preventDefault()`. Components
|
|
21
|
+
* that need to know whether a listener cancelled an event (e.g. to skip a default
|
|
22
|
+
* action) read this return value directly instead of hand-rolling `dispatchEvent`.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* type Events = { close: undefined; change: { value: string } };
|
|
27
|
+
*
|
|
28
|
+
* setup(props) {
|
|
29
|
+
* const emit = useEmit<Events>();
|
|
30
|
+
* emit('close');
|
|
31
|
+
*
|
|
32
|
+
* const notCancelled = emit('change', { value: 'ok' });
|
|
33
|
+
* if (notCancelled) props.value.value = 'ok';
|
|
34
|
+
* }
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export declare const useEmit: <T extends Record<string, unknown> = Record<string, never>>() => EmitFn<T>;
|
|
15
38
|
export {};
|
|
16
39
|
//# sourceMappingURL=emit.d.ts.map
|
package/dist/utils/emit.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"emit.d.ts","sourceRoot":"","sources":["../../src/utils/emit.ts"],"names":[],"mappings":"AAAA;;GAEG;
|
|
1
|
+
{"version":3,"file":"emit.d.ts","sourceRoot":"","sources":["../../src/utils/emit.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,KAAK,QAAQ,GAAG,IAAI,GAAG,SAAS,GAAG,KAAK,CAAC;AACzC,KAAK,iBAAiB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI;KACzD,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,KAAK;CACtD,CAAC,MAAM,CAAC,CAAC,CAAC;AAEX,KAAK,YAAY,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI;IACrD,CAAC,CAAC,SAAS,iBAAiB,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC;IACpD,CAAC,CAAC,SAAS,OAAO,CAAC,MAAM,CAAC,EAAE,iBAAiB,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC;CACrF,CAAC;AAEF,KAAK,WAAW,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC;AAEhE,MAAM,MAAM,MAAM,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,YAAY,CAAC,CAAC,CAAC,GAAG,WAAW,CAAC;AAItF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,OAAO,GAAI,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,OAAK,MAAM,CAAC,CAAC,CAQ7F,CAAC"}
|
package/dist/utils/emit.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var
|
|
1
|
+
import{getHost as e}from"../runtime.js";var t={bubbles:!0,cancelable:!0,composed:!1},n=()=>{let n=e();return((e,...r)=>{let i=r.length>0?{...t,detail:r[0]}:t;return n.dispatchEvent(new CustomEvent(String(e),i))})};export{n as useEmit};
|
|
2
2
|
//# sourceMappingURL=emit.js.map
|
package/dist/utils/emit.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"emit.js","names":[],"sources":["../../src/utils/emit.ts"],"sourcesContent":["/**\n * Type-safe custom event emission
|
|
1
|
+
{"version":3,"file":"emit.js","names":[],"sources":["../../src/utils/emit.ts"],"sourcesContent":["/**\n * Type-safe custom event emission for components.\n */\n\nimport { getHost } from '../runtime';\n\ntype NoDetail = void | undefined | never;\ntype KeysWithoutDetail<T extends Record<string, unknown>> = {\n [P in keyof T]: [T[P]] extends [NoDetail] ? P : never;\n}[keyof T];\n\ntype StrictEmitFn<T extends Record<string, unknown>> = {\n <K extends KeysWithoutDetail<T>>(event: K): boolean;\n <K extends Exclude<keyof T, KeysWithoutDetail<T>>>(event: K, detail: T[K]): boolean;\n};\n\ntype LooseEmitFn = (event: string, detail?: unknown) => boolean;\n\nexport type EmitFn<T extends Record<string, unknown>> = StrictEmitFn<T> & LooseEmitFn;\n\nconst DEFAULT_FIRE_OPTIONS = { bubbles: true, cancelable: true, composed: false };\n\n/**\n * Returns a typed `emit()` function bound to the current component's host element.\n * Call once during `setup()` — the `Emits` type parameter maps event names to\n * their `detail` payload type.\n *\n * Every event is dispatched `cancelable: true`, and `emit()` returns `dispatchEvent`'s\n * own boolean result — `false` when a listener called `preventDefault()`. Components\n * that need to know whether a listener cancelled an event (e.g. to skip a default\n * action) read this return value directly instead of hand-rolling `dispatchEvent`.\n *\n * @example\n * ```ts\n * type Events = { close: undefined; change: { value: string } };\n *\n * setup(props) {\n * const emit = useEmit<Events>();\n * emit('close');\n *\n * const notCancelled = emit('change', { value: 'ok' });\n * if (notCancelled) props.value.value = 'ok';\n * }\n * ```\n */\nexport const useEmit = <T extends Record<string, unknown> = Record<string, never>>(): EmitFn<T> => {\n const host = getHost();\n\n return ((event: keyof T, ...rest: unknown[]) => {\n const customEventInit = rest.length > 0 ? { ...DEFAULT_FIRE_OPTIONS, detail: rest[0] } : DEFAULT_FIRE_OPTIONS;\n\n return host.dispatchEvent(new CustomEvent<unknown>(String(event), customEventInit));\n }) as EmitFn<T>;\n};\n"],"mappings":"wCAoBA,IAAM,EAAuB,CAAE,QAAS,GAAM,WAAY,GAAM,SAAU,EAAM,EAyBnE,MAAsF,CACjG,IAAM,EAAO,EAAQ,EAErB,QAAS,EAAgB,GAAG,IAAoB,CAC9C,IAAM,EAAkB,EAAK,OAAS,EAAI,CAAE,GAAG,EAAsB,OAAQ,EAAK,EAAG,EAAI,EAEzF,OAAO,EAAK,cAAc,IAAI,YAAqB,OAAO,CAAK,EAAG,CAAe,CAAC,CACpF,EACF"}
|