@vielzeug/ore 1.2.2 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/aria.cjs +1 -1
- package/dist/aria.cjs.map +1 -1
- package/dist/aria.d.ts +9 -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.map +1 -1
- package/dist/base-element.js +1 -1
- package/dist/base-element.js.map +1 -1
- package/dist/directives/classMap.cjs +1 -1
- package/dist/directives/classMap.cjs.map +1 -1
- package/dist/directives/classMap.d.ts.map +1 -1
- package/dist/directives/classMap.js +1 -1
- package/dist/directives/classMap.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 +8 -1
- 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/index.d.ts +34 -5
- package/dist/directives/index.d.ts.map +1 -1
- package/dist/directives/raw.cjs +1 -1
- package/dist/directives/raw.cjs.map +1 -1
- package/dist/directives/raw.js +1 -1
- package/dist/directives/raw.js.map +1 -1
- package/dist/directives/styleMap.cjs +1 -1
- package/dist/directives/styleMap.cjs.map +1 -1
- package/dist/directives/styleMap.d.ts.map +1 -1
- package/dist/directives/styleMap.js +1 -1
- package/dist/directives/styleMap.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/directives.cjs +1 -1
- package/dist/directives.js +1 -1
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.d.ts +16 -3
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/host-bind.cjs +1 -1
- package/dist/host-bind.cjs.map +1 -1
- package/dist/host-bind.d.ts +6 -0
- 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 -3
- 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.map +1 -1
- package/dist/props.d.ts +12 -0
- package/dist/props.d.ts.map +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.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.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/instantiator.cjs.map +1 -1
- package/dist/template/instantiator.d.ts.map +1 -1
- package/dist/template/instantiator.js.map +1 -1
- package/dist/testing/flush.cjs +1 -1
- package/dist/testing/flush.cjs.map +1 -1
- package/dist/testing/flush.d.ts +30 -26
- package/dist/testing/flush.d.ts.map +1 -1
- package/dist/testing/flush.js +1 -1
- package/dist/testing/flush.js.map +1 -1
- package/dist/testing/index.d.ts +30 -1
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/interactions.cjs +1 -1
- package/dist/testing/interactions.cjs.map +1 -1
- package/dist/testing/interactions.d.ts.map +1 -1
- package/dist/testing/interactions.js +1 -1
- package/dist/testing/interactions.js.map +1 -1
- package/dist/testing/mount.cjs +1 -1
- package/dist/testing/mount.cjs.map +1 -1
- package/dist/testing/mount.d.ts +8 -14
- package/dist/testing/mount.d.ts.map +1 -1
- package/dist/testing/mount.js +1 -1
- package/dist/testing/mount.js.map +1 -1
- package/dist/testing/render-hook.cjs +1 -1
- package/dist/testing/render-hook.js +1 -1
- package/dist/testing.cjs +2 -1
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.js +2 -1
- package/dist/testing.js.map +1 -0
- package/dist/utils/aria.cjs.map +1 -1
- package/dist/utils/aria.d.ts +1 -1
- package/dist/utils/aria.js.map +1 -1
- package/dist/utils/dom.cjs +1 -1
- package/dist/utils/dom.cjs.map +1 -1
- package/dist/utils/dom.d.ts +36 -0
- package/dist/utils/dom.d.ts.map +1 -1
- package/dist/utils/dom.js +1 -1
- package/dist/utils/dom.js.map +1 -1
- package/dist/utils/id.cjs +1 -1
- package/dist/utils/id.cjs.map +1 -1
- package/dist/utils/id.d.ts +11 -2
- package/dist/utils/id.d.ts.map +1 -1
- package/dist/utils/id.js +1 -1
- package/dist/utils/id.js.map +1 -1
- package/package.json +4 -9
- package/dist/devtools.cjs +0 -2
- package/dist/devtools.cjs.map +0 -1
- package/dist/devtools.d.ts +0 -32
- package/dist/devtools.d.ts.map +0 -1
- package/dist/devtools.js +0 -2
- package/dist/devtools.js.map +0 -1
- package/dist/testing/events.cjs +0 -2
- package/dist/testing/events.cjs.map +0 -1
- package/dist/testing/events.d.ts +0 -37
- package/dist/testing/events.d.ts.map +0 -1
- package/dist/testing/events.js +0 -2
- package/dist/testing/events.js.map +0 -1
- package/dist/testing/query.cjs +0 -2
- package/dist/testing/query.cjs.map +0 -1
- package/dist/testing/query.d.ts +0 -24
- package/dist/testing/query.d.ts.map +0 -1
- package/dist/testing/query.js +0 -2
- package/dist/testing/query.js.map +0 -1
- package/dist/testing/testing.cjs +0 -2
- package/dist/testing/testing.cjs.map +0 -1
- package/dist/testing/testing.d.ts +0 -32
- package/dist/testing/testing.d.ts.map +0 -1
- package/dist/testing/testing.js +0 -2
- package/dist/testing/testing.js.map +0 -1
- package/dist/testing/wait.cjs +0 -2
- package/dist/testing/wait.cjs.map +0 -1
- package/dist/testing/wait.d.ts +0 -35
- package/dist/testing/wait.d.ts.map +0 -1
- package/dist/testing/wait.js +0 -2
- package/dist/testing/wait.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bindings.js","names":[],"sources":["../../src/template/bindings.ts"],"sourcesContent":["/**\n * template/bindings.ts — Runtime binding appliers.\n *\n * Responsibilities:\n * - Apply each Binding variant to the live DOM (text, attr, event, html, ref,\n * directive, spread).\n * - Manage reactive effects and cleanup registration.\n * - Expose `applyBinding()` as the single dispatch entry point.\n */\n\nimport { computed, effect as rawEffect, isReactive, type Readable, untrack } from '@vielzeug/ripple';\n\nimport { isLiveSignal } from '../directives/live';\nimport { invariant } from '../errors';\nimport { getPropMeta } from '../props';\nimport {\n type AttrBinding,\n type AttrPropMeta,\n type Binding,\n type DirectiveBinding,\n type EventBinding,\n type HtmlBinding,\n type HtmlBindingValue,\n isHtmlResult,\n type RefBinding,\n type SpreadBinding,\n type TextBinding,\n} from '../types/bindings';\nimport { isStructuredValue, listen, removeNodes, runAll, setAttr } from '../utils/dom';\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport type RegisterCleanup = (fn: () => void) => void;\n\n// ─── Signal helpers ───────────────────────────────────────────────────────────\n\nconst signalEffect = (\n signal: Readable<unknown>,\n update: (v: unknown) => void,\n registerCleanup: RegisterCleanup,\n): void => {\n const sub = rawEffect(() => update(signal.value));\n\n registerCleanup(() => sub.dispose());\n};\n\n/**\n * Read a signal safely during scope teardown. Disposed signals and computeds\n * return their last known value (or undefined) without throwing — no try/catch needed.\n */\nconst readSignalSafe = <T>(sig: Readable<T>): T | undefined => sig.value;\n\n// ─── Text ─────────────────────────────────────────────────────────────────────\n\nconst applyTextBinding = (binding: TextBinding, registerCleanup: RegisterCleanup): void => {\n signalEffect(\n binding.signal,\n (v) => {\n binding.node.textContent = String(v ?? '');\n },\n registerCleanup,\n );\n};\n\n// ─── Attributes ───────────────────────────────────────────────────────────────\n\nconst isNativeFormInput = (el: HTMLElement): el is HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement =>\n el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement;\n\ntype LiveWriteState = { last: unknown };\n\nconst applyFormValue = (\n el: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement,\n value: unknown,\n isLive: boolean | undefined,\n state: LiveWriteState,\n): void => {\n const next = value == null ? '' : String(value);\n\n if (isLive && state.last !== undefined && !Object.is(el.value, state.last) && !Object.is(el.value, next)) return;\n\n el.value = next;\n\n if (isLive) state.last = next;\n};\n\nconst applyCheckedValue = (\n el: HTMLInputElement,\n value: unknown,\n isLive: boolean | undefined,\n state: LiveWriteState,\n): void => {\n const next = Boolean(value);\n\n if (isLive && state.last !== undefined && el.checked !== Boolean(state.last) && el.checked !== next) return;\n\n el.checked = next;\n\n if (isLive) state.last = next;\n};\n\nconst syncRegisteredProp = (el: HTMLElement, meta: AttrPropMeta, binding: AttrBinding, value: unknown): void => {\n const parsed = isStructuredValue(value)\n ? value\n : meta.parse(\n binding.mode === 'bool' ? (value ? '' : null) : value == null || value === false ? null : String(value),\n );\n\n if (\n !Object.is(\n untrack(() => meta.signal.value),\n parsed,\n )\n ) {\n meta.signal.value = parsed as never;\n }\n\n if (!meta.reflect) {\n if (isStructuredValue(value)) return;\n\n if (binding.mode === 'bool') el.toggleAttribute(binding.name, Boolean(value));\n else setAttr(el, binding.name, value);\n }\n};\n\nexport const applyAttrBinding = (binding: AttrBinding, registerCleanup: RegisterCleanup): void => {\n const { el, mode, name, propMeta } = binding;\n const liveState: LiveWriteState = { last: undefined };\n\n const update = (value: unknown): void => {\n if (propMeta) {\n syncRegisteredProp(el, propMeta, binding, value);\n\n return;\n }\n\n if (!isReactive(value) && isStructuredValue(value)) {\n if (name !== '__proto__' && name !== 'constructor' && name !== 'prototype') {\n (el as unknown as Record<string, unknown>)[name] = value;\n }\n\n return;\n }\n\n if (name === 'value' && isNativeFormInput(el)) {\n applyFormValue(el, value, binding.live, liveState);\n\n return;\n }\n\n if (name === 'checked' && el instanceof HTMLInputElement) {\n applyCheckedValue(el, value, binding.live, liveState);\n\n return;\n }\n\n if (mode === 'bool') el.toggleAttribute(name, Boolean(value));\n else setAttr(el, name, value);\n };\n\n if ('signal' in binding) {\n signalEffect(binding.signal, update, registerCleanup);\n } else {\n update(binding.value);\n }\n};\n\n// ─── Events ───────────────────────────────────────────────────────────────────\n\nconst applyEventBinding = (binding: EventBinding, registerCleanup: RegisterCleanup): void => {\n registerCleanup(listen(binding.el, binding.name, binding.handler, binding.options));\n};\n\n// ─── Refs ─────────────────────────────────────────────────────────────────────\n\nconst applyRefBinding = (binding: RefBinding, registerCleanup: RegisterCleanup): void => {\n const { el, ref } = binding;\n\n if (typeof ref === 'function') {\n ref(el as never);\n registerCleanup(() => ref(null as never));\n\n return;\n }\n\n ref.value = el as never;\n registerCleanup(() => {\n ref.value = null as never;\n });\n};\n\n// ─── HTML (reactive) ─────────────────────────────────────────────────────────\n\nconst insertHtmlValues = (\n values: HtmlBindingValue[],\n insertBefore: ChildNode,\n registerCleanup: RegisterCleanup,\n): Node[] => {\n const nodes: Node[] = [];\n const parent = insertBefore.parentNode;\n\n invariant(parent, 'html binding anchor has no parent node');\n\n for (const v of values) {\n if (isHtmlResult(v)) {\n const captured = Array.from(v.fragment.childNodes);\n\n parent.insertBefore(v.fragment, insertBefore);\n v.apply(registerCleanup);\n nodes.push(...captured);\n } else if (v != null && v !== false) {\n const text = document.createTextNode(String(v));\n\n parent.insertBefore(text, insertBefore);\n nodes.push(text);\n }\n }\n\n return nodes;\n};\n\nexport const applyHtmlBinding = (binding: HtmlBinding, registerCleanup: RegisterCleanup): void => {\n const { anchor, signal } = binding;\n\n let currentCleanups: (() => void)[] = [];\n const clearCurrent = (): void => {\n runAll(currentCleanups);\n currentCleanups = [];\n };\n let currentNodes: Node[] = [];\n\n const stop = rawEffect(() => {\n const raw = readSignalSafe(signal);\n\n clearCurrent();\n removeNodes(currentNodes);\n currentNodes = [];\n\n if (raw == null || raw.length === 0) return;\n\n untrack(() => {\n currentNodes = insertHtmlValues(raw, anchor, (fn) => currentCleanups.push(fn));\n });\n });\n\n registerCleanup(() => {\n stop.dispose();\n clearCurrent();\n removeNodes(currentNodes);\n });\n};\n\n// ─── Directives ───────────────────────────────────────────────────────────────\n\nconst applyDirectiveBinding = (binding: DirectiveBinding, registerCleanup: RegisterCleanup): void => {\n binding.directive.mount(binding.anchor, registerCleanup);\n};\n\n// ─── Spread ───────────────────────────────────────────────────────────────────\n\nconst applySpreadBinding = (binding: SpreadBinding, registerCleanup: RegisterCleanup): void => {\n binding.spread.apply(binding.el, registerCleanup);\n};\n\n// ─── Binding dispatch ─────────────────────────────────────────────────────────\n\nexport const applyBinding = (binding: Binding, registerCleanup: RegisterCleanup): void => {\n switch (binding.type) {\n case 'attr':\n applyAttrBinding(binding, registerCleanup);\n break;\n case 'directive':\n applyDirectiveBinding(binding, registerCleanup);\n break;\n case 'event':\n applyEventBinding(binding, registerCleanup);\n break;\n case 'html':\n applyHtmlBinding(binding, registerCleanup);\n break;\n case 'ref':\n applyRefBinding(binding, registerCleanup);\n break;\n case 'spread':\n applySpreadBinding(binding, registerCleanup);\n break;\n case 'text':\n applyTextBinding(binding, registerCleanup);\n break;\n }\n};\n\n// ─── Attr binding factory (used by instantiator) ──────────────────────────────\n\nexport const createAttrBindingFromValue = (\n el: HTMLElement,\n mode: 'attr' | 'bool',\n name: string,\n value: unknown,\n): AttrBinding => {\n const propMeta = getPropMeta(el, name) as AttrPropMeta | undefined;\n\n if (isLiveSignal(value)) {\n return { el, live: true, mode, name, propMeta, signal: value as Readable<unknown>, type: 'attr' };\n }\n\n if (typeof value === 'function') {\n return { el, mode, name, propMeta, signal: computed(value as () => unknown), type: 'attr' };\n }\n\n if (isReactive(value)) {\n return { el, mode, name, propMeta, signal: value as Readable<unknown>, type: 'attr' };\n }\n\n return { el, mode, name, propMeta, type: 'attr', value };\n};\n\nexport const resolveStaticText = (value: unknown): string => {\n if (value == null) return '';\n\n return String(value);\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"bindings.js","names":[],"sources":["../../src/template/bindings.ts"],"sourcesContent":["/**\n * template/bindings.ts — Runtime binding appliers.\n *\n * Responsibilities:\n * - Apply each Binding variant to the live DOM (text, attr, event, html, ref,\n * directive, spread).\n * - Manage reactive effects and cleanup registration.\n * - Expose `applyBinding()` as the single dispatch entry point.\n */\n\nimport { computed, effect as rawEffect, isReactive, type Readable, untrack } from '@vielzeug/ripple';\n\nimport { isLiveSignal } from '../directives/live';\nimport { invariant } from '../errors';\nimport { getPropMeta } from '../props';\nimport {\n type AttrBinding,\n type AttrPropMeta,\n type Binding,\n type DirectiveBinding,\n type EventBinding,\n type HtmlBinding,\n type HtmlBindingValue,\n isHtmlResult,\n type RefBinding,\n type SpreadBinding,\n type TextBinding,\n} from '../types/bindings';\nimport { createReplaceableSlot, isStructuredValue, listen, setAttr } from '../utils/dom';\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport type RegisterCleanup = (fn: () => void) => void;\n\n// ─── Signal helpers ───────────────────────────────────────────────────────────\n\nconst signalEffect = (\n signal: Readable<unknown>,\n update: (v: unknown) => void,\n registerCleanup: RegisterCleanup,\n): void => {\n const sub = rawEffect(() => update(signal.value));\n\n registerCleanup(() => sub.dispose());\n};\n\n/**\n * Read a signal safely during scope teardown. Disposed signals and computeds\n * return their last known value (or undefined) without throwing — no try/catch needed.\n */\nconst readSignalSafe = <T>(sig: Readable<T>): T | undefined => sig.value;\n\n// ─── Text ─────────────────────────────────────────────────────────────────────\n\nconst applyTextBinding = (binding: TextBinding, registerCleanup: RegisterCleanup): void => {\n signalEffect(\n binding.signal,\n (v) => {\n binding.node.textContent = String(v ?? '');\n },\n registerCleanup,\n );\n};\n\n// ─── Attributes ───────────────────────────────────────────────────────────────\n\nconst isNativeFormInput = (el: HTMLElement): el is HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement =>\n el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement;\n\ntype LiveWriteState = { last: unknown };\n\nconst applyFormValue = (\n el: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement,\n value: unknown,\n isLive: boolean | undefined,\n state: LiveWriteState,\n): void => {\n const next = value == null ? '' : String(value);\n\n if (isLive && state.last !== undefined && !Object.is(el.value, state.last) && !Object.is(el.value, next)) return;\n\n el.value = next;\n\n if (isLive) state.last = next;\n};\n\nconst applyCheckedValue = (\n el: HTMLInputElement,\n value: unknown,\n isLive: boolean | undefined,\n state: LiveWriteState,\n): void => {\n const next = Boolean(value);\n\n if (isLive && state.last !== undefined && el.checked !== Boolean(state.last) && el.checked !== next) return;\n\n el.checked = next;\n\n if (isLive) state.last = next;\n};\n\nconst syncRegisteredProp = (el: HTMLElement, meta: AttrPropMeta, binding: AttrBinding, value: unknown): void => {\n const parsed = isStructuredValue(value)\n ? value\n : meta.parse(\n binding.mode === 'bool' ? (value ? '' : null) : value == null || value === false ? null : String(value),\n );\n\n if (\n !Object.is(\n untrack(() => meta.signal.value),\n parsed,\n )\n ) {\n meta.signal.value = parsed as never;\n }\n\n if (!meta.reflect) {\n if (isStructuredValue(value)) return;\n\n if (binding.mode === 'bool') el.toggleAttribute(binding.name, Boolean(value));\n else setAttr(el, binding.name, value);\n }\n};\n\nexport const applyAttrBinding = (binding: AttrBinding, registerCleanup: RegisterCleanup): void => {\n const { el, mode, name, propMeta } = binding;\n const liveState: LiveWriteState = { last: undefined };\n\n const update = (value: unknown): void => {\n if (propMeta) {\n syncRegisteredProp(el, propMeta, binding, value);\n\n return;\n }\n\n if (!isReactive(value) && isStructuredValue(value)) {\n if (name !== '__proto__' && name !== 'constructor' && name !== 'prototype') {\n (el as unknown as Record<string, unknown>)[name] = value;\n }\n\n return;\n }\n\n if (name === 'value' && isNativeFormInput(el)) {\n applyFormValue(el, value, binding.live, liveState);\n\n return;\n }\n\n if (name === 'checked' && el instanceof HTMLInputElement) {\n applyCheckedValue(el, value, binding.live, liveState);\n\n return;\n }\n\n if (mode === 'bool') el.toggleAttribute(name, Boolean(value));\n else setAttr(el, name, value);\n };\n\n if ('signal' in binding) {\n signalEffect(binding.signal, update, registerCleanup);\n } else {\n update(binding.value);\n }\n};\n\n// ─── Events ───────────────────────────────────────────────────────────────────\n\nconst applyEventBinding = (binding: EventBinding, registerCleanup: RegisterCleanup): void => {\n registerCleanup(listen(binding.el, binding.name, binding.handler, binding.options));\n};\n\n// ─── Refs ─────────────────────────────────────────────────────────────────────\n\nconst applyRefBinding = (binding: RefBinding, registerCleanup: RegisterCleanup): void => {\n const { el, ref } = binding;\n\n if (typeof ref === 'function') {\n ref(el as never);\n registerCleanup(() => ref(null as never));\n\n return;\n }\n\n ref.value = el as never;\n registerCleanup(() => {\n ref.value = null as never;\n });\n};\n\n// ─── HTML (reactive) ─────────────────────────────────────────────────────────\n\nconst insertHtmlValues = (\n values: HtmlBindingValue[],\n insertBefore: ChildNode,\n registerCleanup: RegisterCleanup,\n): Node[] => {\n const nodes: Node[] = [];\n const parent = insertBefore.parentNode;\n\n invariant(parent, 'html binding anchor has no parent node');\n\n for (const v of values) {\n if (isHtmlResult(v)) {\n const captured = Array.from(v.fragment.childNodes);\n\n parent.insertBefore(v.fragment, insertBefore);\n v.apply(registerCleanup);\n nodes.push(...captured);\n } else if (v != null && v !== false) {\n const text = document.createTextNode(String(v));\n\n parent.insertBefore(text, insertBefore);\n nodes.push(text);\n }\n }\n\n return nodes;\n};\n\nexport const applyHtmlBinding = (binding: HtmlBinding, registerCleanup: RegisterCleanup): void => {\n const { anchor, signal } = binding;\n\n const slot = createReplaceableSlot();\n\n const stop = rawEffect(() => {\n const raw = readSignalSafe(signal);\n\n slot.clear();\n\n if (raw == null || raw.length === 0) return;\n\n untrack(() => {\n slot.setNodes(insertHtmlValues(raw, anchor, slot.registerCleanup));\n });\n });\n\n registerCleanup(() => {\n stop.dispose();\n slot.clear();\n });\n};\n\n// ─── Directives ───────────────────────────────────────────────────────────────\n\nconst applyDirectiveBinding = (binding: DirectiveBinding, registerCleanup: RegisterCleanup): void => {\n binding.directive.mount(binding.anchor, registerCleanup);\n};\n\n// ─── Spread ───────────────────────────────────────────────────────────────────\n\nconst applySpreadBinding = (binding: SpreadBinding, registerCleanup: RegisterCleanup): void => {\n binding.spread.apply(binding.el, registerCleanup);\n};\n\n// ─── Binding dispatch ─────────────────────────────────────────────────────────\n\nexport const applyBinding = (binding: Binding, registerCleanup: RegisterCleanup): void => {\n switch (binding.type) {\n case 'attr':\n applyAttrBinding(binding, registerCleanup);\n break;\n case 'directive':\n applyDirectiveBinding(binding, registerCleanup);\n break;\n case 'event':\n applyEventBinding(binding, registerCleanup);\n break;\n case 'html':\n applyHtmlBinding(binding, registerCleanup);\n break;\n case 'ref':\n applyRefBinding(binding, registerCleanup);\n break;\n case 'spread':\n applySpreadBinding(binding, registerCleanup);\n break;\n case 'text':\n applyTextBinding(binding, registerCleanup);\n break;\n }\n};\n\n// ─── Attr binding factory (used by instantiator) ──────────────────────────────\n\nexport const createAttrBindingFromValue = (\n el: HTMLElement,\n mode: 'attr' | 'bool',\n name: string,\n value: unknown,\n): AttrBinding => {\n const propMeta = getPropMeta(el, name) as AttrPropMeta | undefined;\n\n if (isLiveSignal(value)) {\n return { el, live: true, mode, name, propMeta, signal: value as Readable<unknown>, type: 'attr' };\n }\n\n if (typeof value === 'function') {\n return { el, mode, name, propMeta, signal: computed(value as () => unknown), type: 'attr' };\n }\n\n if (isReactive(value)) {\n return { el, mode, name, propMeta, signal: value as Readable<unknown>, type: 'attr' };\n }\n\n return { el, mode, name, propMeta, type: 'attr', value };\n};\n\nexport const resolveStaticText = (value: unknown): string => {\n if (value == null) return '';\n\n return String(value);\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"instantiator.cjs","names":[],"sources":["../../src/template/instantiator.ts"],"sourcesContent":["/**\n * template/instantiator.ts — Template instantiation and the `html` tagged literal.\n *\n * Responsibilities:\n * - Clone a compiled static template and wire up live bindings.\n * - Expose `compileTemplate()` and `html` as the public authoring API.\n */\n\nimport { computed, isReactive, type Readable } from '@vielzeug/ripple';\n\nimport { invariant, ORE_ERRORS, OreApiError } from '../errors';\nimport {\n type Binding,\n createHtmlResult,\n type HtmlBindingValue,\n type HTMLResult,\n isDirectiveResult,\n isHtmlResult,\n isSpreadObject,\n type Ref,\n type RefCallback,\n} from '../types/bindings';\nimport { applyModifiers } from '../utils/event-modifiers';\nimport { applyBinding, createAttrBindingFromValue, resolveStaticText } from './bindings';\nimport { followPath, getStaticTemplate, SlotKind } from './compiler';\n\n// ─── Template instantiation ──────────────────────────────────────────────────\n\n/**\n * Instantiate a compiled template: clone the cached DOM template, navigate\n * to each binding target using pre-recorded paths, and build bindings with\n * direct node references. Returns an HTMLResult whose fragment is ready to insert.\n */\nconst NODE_SLOT_NO_PARENT_MSG = 'html`...`: node-slot comment anchor has no parent node';\n\nexport const compileTemplate = (strings: TemplateStringsArray, values: unknown[]): HTMLResult => {\n const compiled = getStaticTemplate(strings);\n const fragment = compiled.element.content.cloneNode(true) as DocumentFragment;\n const bindings: Binding[] = [];\n // For static HTMLResult embeds: chain their apply calls\n const chainedApplies: Array<(rc: (fn: () => void) => void) => void> = [];\n\n // Phase 1: Resolve all binding targets BEFORE any DOM modifications\n type BoundSlot = { comment?: Comment; el?: HTMLElement; slot: (typeof compiled.slots)[number]; value: unknown };\n\n const boundSlots: BoundSlot[] = compiled.slots.map((slot, i) => {\n const value = values[i];\n\n if (slot.kind === SlotKind.CLOSE_TAG) {\n return { slot, value };\n }\n\n if (slot.kind === SlotKind.NODE) {\n const commentPath = compiled.commentPaths.get(slot.commentId!);\n\n invariant(commentPath, `compiled template is missing a comment path for node slot ${slot.commentId}`);\n\n return { comment: followPath(fragment, commentPath) as Comment, slot, value };\n }\n\n const elementPath = compiled.elementPaths.get(slot.elementId!);\n\n invariant(elementPath, `compiled template is missing an element path for slot ${slot.elementId}`);\n\n return { el: followPath(fragment, elementPath) as HTMLElement, slot, value };\n });\n\n // Phase 2a: Process tagname slots first — replace placeholders with real elements\n const tagReplacements = new Map<HTMLElement, HTMLElement>();\n\n for (const { el, slot, value } of boundSlots) {\n if (slot.kind !== SlotKind.TAG_NAME) continue;\n\n const tagName = String(value);\n\n // Same \"fail immediately, every build\" treatment as each()'s duplicate-key\n // guard: a malformed dynamic tag name is a programming bug in the template,\n // not a runtime race — degrading to a silently-broken partial render would\n // hide it instead of surfacing it at the call site.\n if (!/^[a-z][a-z0-9._-]*$/i.test(tagName)) {\n throw new OreApiError(ORE_ERRORS.invalidDynamicTagName(tagName));\n }\n\n const realEl = document.createElement(tagName);\n\n for (const attr of Array.from(el!.attributes)) realEl.setAttribute(attr.name, attr.value);\n\n while (el!.firstChild) realEl.appendChild(el!.firstChild);\n\n el!.replaceWith(realEl);\n tagReplacements.set(el!, realEl);\n }\n\n // Phase 2b: Build bindings (may modify DOM for static content)\n for (const { comment, el: rawEl, slot, value } of boundSlots) {\n if (slot.kind === SlotKind.TAG_NAME || slot.kind === SlotKind.CLOSE_TAG) continue;\n\n // Resolve element through tagname replacements\n const el = rawEl ? (tagReplacements.get(rawEl) ?? rawEl) : rawEl;\n\n if (slot.kind === SlotKind.NODE) {\n const anchor = comment!;\n\n if (isDirectiveResult(value)) {\n bindings.push({ anchor, directive: value, type: 'directive' });\n continue;\n }\n\n if (isHtmlResult(value)) {\n // Static embed: move fragment children into place, chain apply\n const parent = anchor.parentNode;\n\n invariant(parent, NODE_SLOT_NO_PARENT_MSG);\n\n while (value.fragment.firstChild) parent.insertBefore(value.fragment.firstChild, anchor);\n\n anchor.remove();\n chainedApplies.push(value.apply.bind(value));\n continue;\n }\n\n if (typeof value === 'function') {\n const sig = computed(() => {\n const res = (value as () => unknown)();\n\n return Array.isArray(res) ? (res as HtmlBindingValue[]) : [res as HtmlBindingValue];\n });\n\n bindings.push({ anchor, signal: sig, type: 'html' });\n continue;\n }\n\n if (isReactive(value)) {\n // Always use the html binding for signals — it handles both text values and\n // HTMLResult values, preventing silent \"[object Object]\" corruption when a\n // signal's runtime type changes from null/string to HTMLResult.\n const sig = computed(() => {\n const raw = (value as Readable<HtmlBindingValue | HtmlBindingValue[]>).value;\n\n return Array.isArray(raw) ? (raw as HtmlBindingValue[]) : [raw as HtmlBindingValue];\n });\n\n bindings.push({ anchor, signal: sig, type: 'html' });\n continue;\n }\n\n if (Array.isArray(value)) {\n const parent = anchor.parentNode;\n\n invariant(parent, NODE_SLOT_NO_PARENT_MSG);\n\n for (const item of value) {\n if (isHtmlResult(item)) {\n while (item.fragment.firstChild) parent.insertBefore(item.fragment.firstChild, anchor);\n\n chainedApplies.push(item.apply.bind(item));\n } else {\n parent.insertBefore(document.createTextNode(resolveStaticText(item)), anchor);\n }\n }\n anchor.remove();\n continue;\n }\n\n // Static primitive: replace with text node, no binding\n anchor.replaceWith(document.createTextNode(resolveStaticText(value)));\n continue;\n }\n\n // Element slot\n const target = el!;\n\n if (slot.kind === SlotKind.EVENT) {\n if (typeof value === 'function') {\n const { handler, options } = applyModifiers(value as (e: Event) => void, slot.modifiers ?? []);\n\n bindings.push({ el: target, handler, name: slot.name!, options, type: 'event' });\n } else if (isReactive(value)) {\n const signalValue = value as Readable<unknown>;\n const handler = (e: Event) => {\n const h = signalValue.value;\n\n if (typeof h === 'function') (h as (e: Event) => void)(e);\n };\n const { handler: wrapped, options } = applyModifiers(handler, slot.modifiers ?? []);\n\n bindings.push({ el: target, handler: wrapped, name: slot.name!, options, type: 'event' });\n }\n\n continue;\n }\n\n if (slot.kind === SlotKind.REF) {\n if (value) {\n bindings.push({ el: target, ref: value as Ref<Element> | RefCallback<Element>, type: 'ref' });\n }\n\n continue;\n }\n\n if (slot.kind === SlotKind.SPREAD) {\n if (isSpreadObject(value)) {\n bindings.push({ el: target, spread: value, type: 'spread' });\n }\n\n continue;\n }\n\n // attr / boolAttr\n bindings.push(createAttrBindingFromValue(target, slot.mode ?? 'attr', slot.name!, value));\n }\n\n return createHtmlResult(fragment, (registerCleanup) => {\n for (const binding of bindings) applyBinding(binding, registerCleanup);\n for (const chainedApply of chainedApplies) chainedApply(registerCleanup);\n });\n};\n\nexport const html = (strings: TemplateStringsArray, ...values: unknown[]): HTMLResult =>\n compileTemplate(strings, values);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"instantiator.cjs","names":[],"sources":["../../src/template/instantiator.ts"],"sourcesContent":["/**\n * template/instantiator.ts — Template instantiation and the `html` tagged literal.\n *\n * Responsibilities:\n * - Clone a compiled static template and wire up live bindings.\n * - Expose `compileTemplate()` and `html` as the public authoring API.\n */\n\nimport { computed, isReactive, type Readable } from '@vielzeug/ripple';\n\nimport { invariant, ORE_ERRORS, OreApiError } from '../errors';\nimport {\n type Binding,\n createHtmlResult,\n type HtmlBindingValue,\n type HTMLResult,\n isDirectiveResult,\n isHtmlResult,\n isSpreadObject,\n type Ref,\n type RefCallback,\n} from '../types/bindings';\nimport { applyModifiers } from '../utils/event-modifiers';\nimport { applyBinding, createAttrBindingFromValue, resolveStaticText } from './bindings';\nimport { followPath, getStaticTemplate, SlotKind } from './compiler';\n\n// ─── Template instantiation ──────────────────────────────────────────────────\n\n/**\n * Instantiate a compiled template: clone the cached DOM template, navigate\n * to each binding target using pre-recorded paths, and build bindings with\n * direct node references. Returns an HTMLResult whose fragment is ready to insert.\n */\nconst NODE_SLOT_NO_PARENT_MSG = 'html`...`: node-slot comment anchor has no parent node';\n\nexport const compileTemplate = (strings: TemplateStringsArray, values: unknown[]): HTMLResult => {\n const compiled = getStaticTemplate(strings);\n const fragment = compiled.element.content.cloneNode(true) as DocumentFragment;\n const bindings: Binding[] = [];\n // For static HTMLResult embeds: chain their apply calls\n const chainedApplies: Array<(rc: (fn: () => void) => void) => void> = [];\n\n // Phase 1: Resolve all binding targets BEFORE any DOM modifications\n type BoundSlot = { comment?: Comment; el?: HTMLElement; slot: (typeof compiled.slots)[number]; value: unknown };\n\n const boundSlots: BoundSlot[] = compiled.slots.map((slot, i) => {\n const value = values[i];\n\n if (slot.kind === SlotKind.CLOSE_TAG) {\n return { slot, value };\n }\n\n if (slot.kind === SlotKind.NODE) {\n const commentPath = compiled.commentPaths.get(slot.commentId!);\n\n invariant(commentPath, `compiled template is missing a comment path for node slot ${slot.commentId}`);\n\n return { comment: followPath(fragment, commentPath) as Comment, slot, value };\n }\n\n const elementPath = compiled.elementPaths.get(slot.elementId!);\n\n invariant(elementPath, `compiled template is missing an element path for slot ${slot.elementId}`);\n\n return { el: followPath(fragment, elementPath) as HTMLElement, slot, value };\n });\n\n // Phase 2a: Process tagname slots first — replace placeholders with real elements\n const tagReplacements = new Map<HTMLElement, HTMLElement>();\n\n for (const { el, slot, value } of boundSlots) {\n if (slot.kind !== SlotKind.TAG_NAME) continue;\n\n const tagName = String(value);\n\n // Throws immediately, every build: a malformed dynamic tag name is a programming bug in\n // the template, not a runtime race, and this happens synchronously during instantiation\n // (not inside a reactive effect) — there's no risk of an uncaught throw here corrupting an\n // unrelated batch of effects the way rethrowing from each()'s reconciliation would (see\n // each.ts's duplicate-key handling, which instead reports via reportRuntimeError() since it\n // runs inside a reactive effect).\n if (!/^[a-z][a-z0-9._-]*$/i.test(tagName)) {\n throw new OreApiError(ORE_ERRORS.invalidDynamicTagName(tagName));\n }\n\n const realEl = document.createElement(tagName);\n\n for (const attr of Array.from(el!.attributes)) realEl.setAttribute(attr.name, attr.value);\n\n while (el!.firstChild) realEl.appendChild(el!.firstChild);\n\n el!.replaceWith(realEl);\n tagReplacements.set(el!, realEl);\n }\n\n // Phase 2b: Build bindings (may modify DOM for static content)\n for (const { comment, el: rawEl, slot, value } of boundSlots) {\n if (slot.kind === SlotKind.TAG_NAME || slot.kind === SlotKind.CLOSE_TAG) continue;\n\n // Resolve element through tagname replacements\n const el = rawEl ? (tagReplacements.get(rawEl) ?? rawEl) : rawEl;\n\n if (slot.kind === SlotKind.NODE) {\n const anchor = comment!;\n\n if (isDirectiveResult(value)) {\n bindings.push({ anchor, directive: value, type: 'directive' });\n continue;\n }\n\n if (isHtmlResult(value)) {\n // Static embed: move fragment children into place, chain apply\n const parent = anchor.parentNode;\n\n invariant(parent, NODE_SLOT_NO_PARENT_MSG);\n\n while (value.fragment.firstChild) parent.insertBefore(value.fragment.firstChild, anchor);\n\n anchor.remove();\n chainedApplies.push(value.apply.bind(value));\n continue;\n }\n\n if (typeof value === 'function') {\n const sig = computed(() => {\n const res = (value as () => unknown)();\n\n return Array.isArray(res) ? (res as HtmlBindingValue[]) : [res as HtmlBindingValue];\n });\n\n bindings.push({ anchor, signal: sig, type: 'html' });\n continue;\n }\n\n if (isReactive(value)) {\n // Always use the html binding for signals — it handles both text values and\n // HTMLResult values, preventing silent \"[object Object]\" corruption when a\n // signal's runtime type changes from null/string to HTMLResult.\n const sig = computed(() => {\n const raw = (value as Readable<HtmlBindingValue | HtmlBindingValue[]>).value;\n\n return Array.isArray(raw) ? (raw as HtmlBindingValue[]) : [raw as HtmlBindingValue];\n });\n\n bindings.push({ anchor, signal: sig, type: 'html' });\n continue;\n }\n\n if (Array.isArray(value)) {\n const parent = anchor.parentNode;\n\n invariant(parent, NODE_SLOT_NO_PARENT_MSG);\n\n for (const item of value) {\n if (isHtmlResult(item)) {\n while (item.fragment.firstChild) parent.insertBefore(item.fragment.firstChild, anchor);\n\n chainedApplies.push(item.apply.bind(item));\n } else {\n parent.insertBefore(document.createTextNode(resolveStaticText(item)), anchor);\n }\n }\n anchor.remove();\n continue;\n }\n\n // Static primitive: replace with text node, no binding\n anchor.replaceWith(document.createTextNode(resolveStaticText(value)));\n continue;\n }\n\n // Element slot\n const target = el!;\n\n if (slot.kind === SlotKind.EVENT) {\n if (typeof value === 'function') {\n const { handler, options } = applyModifiers(value as (e: Event) => void, slot.modifiers ?? []);\n\n bindings.push({ el: target, handler, name: slot.name!, options, type: 'event' });\n } else if (isReactive(value)) {\n const signalValue = value as Readable<unknown>;\n const handler = (e: Event) => {\n const h = signalValue.value;\n\n if (typeof h === 'function') (h as (e: Event) => void)(e);\n };\n const { handler: wrapped, options } = applyModifiers(handler, slot.modifiers ?? []);\n\n bindings.push({ el: target, handler: wrapped, name: slot.name!, options, type: 'event' });\n }\n\n continue;\n }\n\n if (slot.kind === SlotKind.REF) {\n if (value) {\n bindings.push({ el: target, ref: value as Ref<Element> | RefCallback<Element>, type: 'ref' });\n }\n\n continue;\n }\n\n if (slot.kind === SlotKind.SPREAD) {\n if (isSpreadObject(value)) {\n bindings.push({ el: target, spread: value, type: 'spread' });\n }\n\n continue;\n }\n\n // attr / boolAttr\n bindings.push(createAttrBindingFromValue(target, slot.mode ?? 'attr', slot.name!, value));\n }\n\n return createHtmlResult(fragment, (registerCleanup) => {\n for (const binding of bindings) applyBinding(binding, registerCleanup);\n for (const chainedApply of chainedApplies) chainedApply(registerCleanup);\n });\n};\n\nexport const html = (strings: TemplateStringsArray, ...values: unknown[]): HTMLResult =>\n compileTemplate(strings, values);\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"instantiator.d.ts","sourceRoot":"","sources":["../../src/template/instantiator.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,EAIL,KAAK,UAAU,EAMhB,MAAM,mBAAmB,CAAC;AAc3B,eAAO,MAAM,eAAe,GAAI,SAAS,oBAAoB,EAAE,QAAQ,OAAO,EAAE,KAAG,
|
|
1
|
+
{"version":3,"file":"instantiator.d.ts","sourceRoot":"","sources":["../../src/template/instantiator.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,EAIL,KAAK,UAAU,EAMhB,MAAM,mBAAmB,CAAC;AAc3B,eAAO,MAAM,eAAe,GAAI,SAAS,oBAAoB,EAAE,QAAQ,OAAO,EAAE,KAAG,UAuLlF,CAAC;AAEF,eAAO,MAAM,IAAI,GAAI,SAAS,oBAAoB,EAAE,GAAG,QAAQ,OAAO,EAAE,KAAG,UACzC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"instantiator.js","names":[],"sources":["../../src/template/instantiator.ts"],"sourcesContent":["/**\n * template/instantiator.ts — Template instantiation and the `html` tagged literal.\n *\n * Responsibilities:\n * - Clone a compiled static template and wire up live bindings.\n * - Expose `compileTemplate()` and `html` as the public authoring API.\n */\n\nimport { computed, isReactive, type Readable } from '@vielzeug/ripple';\n\nimport { invariant, ORE_ERRORS, OreApiError } from '../errors';\nimport {\n type Binding,\n createHtmlResult,\n type HtmlBindingValue,\n type HTMLResult,\n isDirectiveResult,\n isHtmlResult,\n isSpreadObject,\n type Ref,\n type RefCallback,\n} from '../types/bindings';\nimport { applyModifiers } from '../utils/event-modifiers';\nimport { applyBinding, createAttrBindingFromValue, resolveStaticText } from './bindings';\nimport { followPath, getStaticTemplate, SlotKind } from './compiler';\n\n// ─── Template instantiation ──────────────────────────────────────────────────\n\n/**\n * Instantiate a compiled template: clone the cached DOM template, navigate\n * to each binding target using pre-recorded paths, and build bindings with\n * direct node references. Returns an HTMLResult whose fragment is ready to insert.\n */\nconst NODE_SLOT_NO_PARENT_MSG = 'html`...`: node-slot comment anchor has no parent node';\n\nexport const compileTemplate = (strings: TemplateStringsArray, values: unknown[]): HTMLResult => {\n const compiled = getStaticTemplate(strings);\n const fragment = compiled.element.content.cloneNode(true) as DocumentFragment;\n const bindings: Binding[] = [];\n // For static HTMLResult embeds: chain their apply calls\n const chainedApplies: Array<(rc: (fn: () => void) => void) => void> = [];\n\n // Phase 1: Resolve all binding targets BEFORE any DOM modifications\n type BoundSlot = { comment?: Comment; el?: HTMLElement; slot: (typeof compiled.slots)[number]; value: unknown };\n\n const boundSlots: BoundSlot[] = compiled.slots.map((slot, i) => {\n const value = values[i];\n\n if (slot.kind === SlotKind.CLOSE_TAG) {\n return { slot, value };\n }\n\n if (slot.kind === SlotKind.NODE) {\n const commentPath = compiled.commentPaths.get(slot.commentId!);\n\n invariant(commentPath, `compiled template is missing a comment path for node slot ${slot.commentId}`);\n\n return { comment: followPath(fragment, commentPath) as Comment, slot, value };\n }\n\n const elementPath = compiled.elementPaths.get(slot.elementId!);\n\n invariant(elementPath, `compiled template is missing an element path for slot ${slot.elementId}`);\n\n return { el: followPath(fragment, elementPath) as HTMLElement, slot, value };\n });\n\n // Phase 2a: Process tagname slots first — replace placeholders with real elements\n const tagReplacements = new Map<HTMLElement, HTMLElement>();\n\n for (const { el, slot, value } of boundSlots) {\n if (slot.kind !== SlotKind.TAG_NAME) continue;\n\n const tagName = String(value);\n\n // Same \"fail immediately, every build\" treatment as each()'s duplicate-key\n // guard: a malformed dynamic tag name is a programming bug in the template,\n // not a runtime race — degrading to a silently-broken partial render would\n // hide it instead of surfacing it at the call site.\n if (!/^[a-z][a-z0-9._-]*$/i.test(tagName)) {\n throw new OreApiError(ORE_ERRORS.invalidDynamicTagName(tagName));\n }\n\n const realEl = document.createElement(tagName);\n\n for (const attr of Array.from(el!.attributes)) realEl.setAttribute(attr.name, attr.value);\n\n while (el!.firstChild) realEl.appendChild(el!.firstChild);\n\n el!.replaceWith(realEl);\n tagReplacements.set(el!, realEl);\n }\n\n // Phase 2b: Build bindings (may modify DOM for static content)\n for (const { comment, el: rawEl, slot, value } of boundSlots) {\n if (slot.kind === SlotKind.TAG_NAME || slot.kind === SlotKind.CLOSE_TAG) continue;\n\n // Resolve element through tagname replacements\n const el = rawEl ? (tagReplacements.get(rawEl) ?? rawEl) : rawEl;\n\n if (slot.kind === SlotKind.NODE) {\n const anchor = comment!;\n\n if (isDirectiveResult(value)) {\n bindings.push({ anchor, directive: value, type: 'directive' });\n continue;\n }\n\n if (isHtmlResult(value)) {\n // Static embed: move fragment children into place, chain apply\n const parent = anchor.parentNode;\n\n invariant(parent, NODE_SLOT_NO_PARENT_MSG);\n\n while (value.fragment.firstChild) parent.insertBefore(value.fragment.firstChild, anchor);\n\n anchor.remove();\n chainedApplies.push(value.apply.bind(value));\n continue;\n }\n\n if (typeof value === 'function') {\n const sig = computed(() => {\n const res = (value as () => unknown)();\n\n return Array.isArray(res) ? (res as HtmlBindingValue[]) : [res as HtmlBindingValue];\n });\n\n bindings.push({ anchor, signal: sig, type: 'html' });\n continue;\n }\n\n if (isReactive(value)) {\n // Always use the html binding for signals — it handles both text values and\n // HTMLResult values, preventing silent \"[object Object]\" corruption when a\n // signal's runtime type changes from null/string to HTMLResult.\n const sig = computed(() => {\n const raw = (value as Readable<HtmlBindingValue | HtmlBindingValue[]>).value;\n\n return Array.isArray(raw) ? (raw as HtmlBindingValue[]) : [raw as HtmlBindingValue];\n });\n\n bindings.push({ anchor, signal: sig, type: 'html' });\n continue;\n }\n\n if (Array.isArray(value)) {\n const parent = anchor.parentNode;\n\n invariant(parent, NODE_SLOT_NO_PARENT_MSG);\n\n for (const item of value) {\n if (isHtmlResult(item)) {\n while (item.fragment.firstChild) parent.insertBefore(item.fragment.firstChild, anchor);\n\n chainedApplies.push(item.apply.bind(item));\n } else {\n parent.insertBefore(document.createTextNode(resolveStaticText(item)), anchor);\n }\n }\n anchor.remove();\n continue;\n }\n\n // Static primitive: replace with text node, no binding\n anchor.replaceWith(document.createTextNode(resolveStaticText(value)));\n continue;\n }\n\n // Element slot\n const target = el!;\n\n if (slot.kind === SlotKind.EVENT) {\n if (typeof value === 'function') {\n const { handler, options } = applyModifiers(value as (e: Event) => void, slot.modifiers ?? []);\n\n bindings.push({ el: target, handler, name: slot.name!, options, type: 'event' });\n } else if (isReactive(value)) {\n const signalValue = value as Readable<unknown>;\n const handler = (e: Event) => {\n const h = signalValue.value;\n\n if (typeof h === 'function') (h as (e: Event) => void)(e);\n };\n const { handler: wrapped, options } = applyModifiers(handler, slot.modifiers ?? []);\n\n bindings.push({ el: target, handler: wrapped, name: slot.name!, options, type: 'event' });\n }\n\n continue;\n }\n\n if (slot.kind === SlotKind.REF) {\n if (value) {\n bindings.push({ el: target, ref: value as Ref<Element> | RefCallback<Element>, type: 'ref' });\n }\n\n continue;\n }\n\n if (slot.kind === SlotKind.SPREAD) {\n if (isSpreadObject(value)) {\n bindings.push({ el: target, spread: value, type: 'spread' });\n }\n\n continue;\n }\n\n // attr / boolAttr\n bindings.push(createAttrBindingFromValue(target, slot.mode ?? 'attr', slot.name!, value));\n }\n\n return createHtmlResult(fragment, (registerCleanup) => {\n for (const binding of bindings) applyBinding(binding, registerCleanup);\n for (const chainedApply of chainedApplies) chainedApply(registerCleanup);\n });\n};\n\nexport const html = (strings: TemplateStringsArray, ...values: unknown[]): HTMLResult =>\n compileTemplate(strings, values);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"instantiator.js","names":[],"sources":["../../src/template/instantiator.ts"],"sourcesContent":["/**\n * template/instantiator.ts — Template instantiation and the `html` tagged literal.\n *\n * Responsibilities:\n * - Clone a compiled static template and wire up live bindings.\n * - Expose `compileTemplate()` and `html` as the public authoring API.\n */\n\nimport { computed, isReactive, type Readable } from '@vielzeug/ripple';\n\nimport { invariant, ORE_ERRORS, OreApiError } from '../errors';\nimport {\n type Binding,\n createHtmlResult,\n type HtmlBindingValue,\n type HTMLResult,\n isDirectiveResult,\n isHtmlResult,\n isSpreadObject,\n type Ref,\n type RefCallback,\n} from '../types/bindings';\nimport { applyModifiers } from '../utils/event-modifiers';\nimport { applyBinding, createAttrBindingFromValue, resolveStaticText } from './bindings';\nimport { followPath, getStaticTemplate, SlotKind } from './compiler';\n\n// ─── Template instantiation ──────────────────────────────────────────────────\n\n/**\n * Instantiate a compiled template: clone the cached DOM template, navigate\n * to each binding target using pre-recorded paths, and build bindings with\n * direct node references. Returns an HTMLResult whose fragment is ready to insert.\n */\nconst NODE_SLOT_NO_PARENT_MSG = 'html`...`: node-slot comment anchor has no parent node';\n\nexport const compileTemplate = (strings: TemplateStringsArray, values: unknown[]): HTMLResult => {\n const compiled = getStaticTemplate(strings);\n const fragment = compiled.element.content.cloneNode(true) as DocumentFragment;\n const bindings: Binding[] = [];\n // For static HTMLResult embeds: chain their apply calls\n const chainedApplies: Array<(rc: (fn: () => void) => void) => void> = [];\n\n // Phase 1: Resolve all binding targets BEFORE any DOM modifications\n type BoundSlot = { comment?: Comment; el?: HTMLElement; slot: (typeof compiled.slots)[number]; value: unknown };\n\n const boundSlots: BoundSlot[] = compiled.slots.map((slot, i) => {\n const value = values[i];\n\n if (slot.kind === SlotKind.CLOSE_TAG) {\n return { slot, value };\n }\n\n if (slot.kind === SlotKind.NODE) {\n const commentPath = compiled.commentPaths.get(slot.commentId!);\n\n invariant(commentPath, `compiled template is missing a comment path for node slot ${slot.commentId}`);\n\n return { comment: followPath(fragment, commentPath) as Comment, slot, value };\n }\n\n const elementPath = compiled.elementPaths.get(slot.elementId!);\n\n invariant(elementPath, `compiled template is missing an element path for slot ${slot.elementId}`);\n\n return { el: followPath(fragment, elementPath) as HTMLElement, slot, value };\n });\n\n // Phase 2a: Process tagname slots first — replace placeholders with real elements\n const tagReplacements = new Map<HTMLElement, HTMLElement>();\n\n for (const { el, slot, value } of boundSlots) {\n if (slot.kind !== SlotKind.TAG_NAME) continue;\n\n const tagName = String(value);\n\n // Throws immediately, every build: a malformed dynamic tag name is a programming bug in\n // the template, not a runtime race, and this happens synchronously during instantiation\n // (not inside a reactive effect) — there's no risk of an uncaught throw here corrupting an\n // unrelated batch of effects the way rethrowing from each()'s reconciliation would (see\n // each.ts's duplicate-key handling, which instead reports via reportRuntimeError() since it\n // runs inside a reactive effect).\n if (!/^[a-z][a-z0-9._-]*$/i.test(tagName)) {\n throw new OreApiError(ORE_ERRORS.invalidDynamicTagName(tagName));\n }\n\n const realEl = document.createElement(tagName);\n\n for (const attr of Array.from(el!.attributes)) realEl.setAttribute(attr.name, attr.value);\n\n while (el!.firstChild) realEl.appendChild(el!.firstChild);\n\n el!.replaceWith(realEl);\n tagReplacements.set(el!, realEl);\n }\n\n // Phase 2b: Build bindings (may modify DOM for static content)\n for (const { comment, el: rawEl, slot, value } of boundSlots) {\n if (slot.kind === SlotKind.TAG_NAME || slot.kind === SlotKind.CLOSE_TAG) continue;\n\n // Resolve element through tagname replacements\n const el = rawEl ? (tagReplacements.get(rawEl) ?? rawEl) : rawEl;\n\n if (slot.kind === SlotKind.NODE) {\n const anchor = comment!;\n\n if (isDirectiveResult(value)) {\n bindings.push({ anchor, directive: value, type: 'directive' });\n continue;\n }\n\n if (isHtmlResult(value)) {\n // Static embed: move fragment children into place, chain apply\n const parent = anchor.parentNode;\n\n invariant(parent, NODE_SLOT_NO_PARENT_MSG);\n\n while (value.fragment.firstChild) parent.insertBefore(value.fragment.firstChild, anchor);\n\n anchor.remove();\n chainedApplies.push(value.apply.bind(value));\n continue;\n }\n\n if (typeof value === 'function') {\n const sig = computed(() => {\n const res = (value as () => unknown)();\n\n return Array.isArray(res) ? (res as HtmlBindingValue[]) : [res as HtmlBindingValue];\n });\n\n bindings.push({ anchor, signal: sig, type: 'html' });\n continue;\n }\n\n if (isReactive(value)) {\n // Always use the html binding for signals — it handles both text values and\n // HTMLResult values, preventing silent \"[object Object]\" corruption when a\n // signal's runtime type changes from null/string to HTMLResult.\n const sig = computed(() => {\n const raw = (value as Readable<HtmlBindingValue | HtmlBindingValue[]>).value;\n\n return Array.isArray(raw) ? (raw as HtmlBindingValue[]) : [raw as HtmlBindingValue];\n });\n\n bindings.push({ anchor, signal: sig, type: 'html' });\n continue;\n }\n\n if (Array.isArray(value)) {\n const parent = anchor.parentNode;\n\n invariant(parent, NODE_SLOT_NO_PARENT_MSG);\n\n for (const item of value) {\n if (isHtmlResult(item)) {\n while (item.fragment.firstChild) parent.insertBefore(item.fragment.firstChild, anchor);\n\n chainedApplies.push(item.apply.bind(item));\n } else {\n parent.insertBefore(document.createTextNode(resolveStaticText(item)), anchor);\n }\n }\n anchor.remove();\n continue;\n }\n\n // Static primitive: replace with text node, no binding\n anchor.replaceWith(document.createTextNode(resolveStaticText(value)));\n continue;\n }\n\n // Element slot\n const target = el!;\n\n if (slot.kind === SlotKind.EVENT) {\n if (typeof value === 'function') {\n const { handler, options } = applyModifiers(value as (e: Event) => void, slot.modifiers ?? []);\n\n bindings.push({ el: target, handler, name: slot.name!, options, type: 'event' });\n } else if (isReactive(value)) {\n const signalValue = value as Readable<unknown>;\n const handler = (e: Event) => {\n const h = signalValue.value;\n\n if (typeof h === 'function') (h as (e: Event) => void)(e);\n };\n const { handler: wrapped, options } = applyModifiers(handler, slot.modifiers ?? []);\n\n bindings.push({ el: target, handler: wrapped, name: slot.name!, options, type: 'event' });\n }\n\n continue;\n }\n\n if (slot.kind === SlotKind.REF) {\n if (value) {\n bindings.push({ el: target, ref: value as Ref<Element> | RefCallback<Element>, type: 'ref' });\n }\n\n continue;\n }\n\n if (slot.kind === SlotKind.SPREAD) {\n if (isSpreadObject(value)) {\n bindings.push({ el: target, spread: value, type: 'spread' });\n }\n\n continue;\n }\n\n // attr / boolAttr\n bindings.push(createAttrBindingFromValue(target, slot.mode ?? 'attr', slot.name!, value));\n }\n\n return createHtmlResult(fragment, (registerCleanup) => {\n for (const binding of bindings) applyBinding(binding, registerCleanup);\n for (const chainedApply of chainedApplies) chainedApply(registerCleanup);\n });\n};\n\nexport const html = (strings: TemplateStringsArray, ...values: unknown[]): HTMLResult =>\n compileTemplate(strings, values);\n"],"mappings":"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"}
|
package/dist/testing/flush.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
const e=require("../errors.cjs"),t=require("../runtime.cjs");var n=1e3;async function r(r={}){let{logger:i}=r;i?.(`[flush] draining pending component work (async setup, scheduled mount callbacks)`);let a=0;for(;t.hasPendingWork();){if(++a>n)throw new e.OreTimeoutError(`flush(): pending component work did not settle after ${n} microtask turns — check for a component whose onMounted callback keeps re-registering itself, or an async setup() that never resolves.`);i?.(`[flush] pending work remains, turn ${a}`),await Promise.resolve()}i?.(`[flush] draining requestAnimationFrame`),await new Promise(e=>typeof requestAnimationFrame<`u`?requestAnimationFrame(()=>e()):e()),i?.(`[flush] final microtask pass`),await Promise.resolve(),i?.(`[flush] complete`)}async function i(){return r({logger:console.debug})}exports.debugFlush=i,exports.flush=r;
|
|
2
2
|
//# sourceMappingURL=flush.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flush.cjs","names":[],"sources":["../../src/testing/flush.ts"],"sourcesContent":["/**\n * Reactive update flushing utilities for test environments.\n */\n\
|
|
1
|
+
{"version":3,"file":"flush.cjs","names":[],"sources":["../../src/testing/flush.ts"],"sourcesContent":["/**\n * Reactive update flushing utilities for test environments.\n */\n\nimport { OreTimeoutError } from '../errors';\nimport { hasPendingWork } from '../runtime';\n\n// Defensive cap against a genuine bug (e.g. an onMounted callback that always registers\n// another onMounted, looping forever) — not a \"how much is normally enough\" guess like the\n// old fixed turn count was. Microtask polls are cheap, so this is generous.\nconst MAX_FLUSH_ITERATIONS = 1000;\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/**\n * Options for flushing pending reactive updates.\n */\nexport interface FlushOptions {\n /**\n * Logger function called with each diagnostic message during the flush.\n * Pass `console.debug` or any `(msg: string) => void` to enable output.\n * Omit (or pass `undefined`) to disable all logging.\n *\n * Prefer `debugFlush()` (also from `@vielzeug/ore/testing`) over wiring this manually.\n */\n logger?: (msg: string) => void;\n}\n\n// ─── Core ────────────────────────────────────────────────────────────────────\n\n/**\n * Flush pending reactive updates: waits for every tracked in-flight component\n * operation (an awaited async `setup()`, scheduled `onMounted` microtasks — including\n * ones registered by other `onMounted` callbacks) to settle, then yields one animation\n * frame and one final microtask pass for rAF-scheduled work.\n *\n * This is deterministic, not a fixed number of guessed turns — `@vielzeug/ripple`'s\n * reactive graph itself settles synchronously on every signal write, so the only real\n * asynchrony to wait for is ore's own component scheduling (see `runtime.ts`'s\n * `hasPendingWork()`). There is no \"deep\" variant to reach for anymore: `flush()` waits\n * for exactly as much work as is actually pending, however deep that turns out to be.\n *\n * @example\n * ```ts\n * // Standard flush — waits for whatever is actually pending, shallow or deep\n * await flush();\n *\n * // Debug timing issues\n * await flush({ logger: console.debug });\n * ```\n *\n * @throws `OreTimeoutError` if pending work never settles within {@link MAX_FLUSH_ITERATIONS}\n * microtask turns — almost always a sign of a genuine bug (e.g. an `onMounted` callback that\n * keeps re-registering itself), not a case for raising a limit.\n */\nexport async function flush(options: FlushOptions = {}): Promise<void> {\n const { logger } = options;\n\n logger?.('[flush] draining pending component work (async setup, scheduled mount callbacks)');\n\n let turns = 0;\n\n while (hasPendingWork()) {\n if (++turns > MAX_FLUSH_ITERATIONS) {\n throw new OreTimeoutError(\n `flush(): pending component work did not settle after ${MAX_FLUSH_ITERATIONS} microtask turns — ` +\n `check for a component whose onMounted callback keeps re-registering itself, or an async setup() that never resolves.`,\n );\n }\n\n logger?.(`[flush] pending work remains, turn ${turns}`);\n await Promise.resolve();\n }\n\n logger?.('[flush] draining requestAnimationFrame');\n\n await new Promise<void>((resolve) =>\n typeof requestAnimationFrame !== 'undefined' ? requestAnimationFrame(() => resolve()) : resolve(),\n );\n\n logger?.('[flush] final microtask pass');\n\n await Promise.resolve();\n\n logger?.('[flush] complete');\n}\n\n/**\n * `flush()` with debug logging pre-wired to `console.debug`. Logs each phase and pending-work\n * turn so you can diagnose unexpected update order or timing issues.\n *\n * @example\n * ```ts\n * import { debugFlush } from '@vielzeug/ore/testing';\n *\n * // in a test\n * await debugFlush();\n * // [flush] draining pending component work (async setup, scheduled mount callbacks)\n * // [flush] pending work remains, turn 1\n * // ...\n * ```\n */\nexport async function debugFlush(): Promise<void> {\n return flush({ logger: console.debug });\n}\n"],"mappings":"6DAUA,IAAM,EAAuB,IA6C7B,eAAsB,EAAM,EAAwB,CAAC,EAAkB,CACrE,GAAM,CAAE,UAAW,EAEnB,IAAS,kFAAkF,EAE3F,IAAI,EAAQ,EAEZ,KAAO,EAAA,eAAe,GAAG,CACvB,GAAI,EAAE,EAAQ,EACZ,MAAM,IAAI,EAAA,gBACR,wDAAwD,EAAqB,wIAE/E,EAGF,IAAS,sCAAsC,GAAO,EACtD,MAAM,QAAQ,QAAQ,CACxB,CAEA,IAAS,wCAAwC,EAEjD,MAAM,IAAI,QAAe,GACvB,OAAO,sBAA0B,IAAc,0BAA4B,EAAQ,CAAC,EAAI,EAAQ,CAClG,EAEA,IAAS,8BAA8B,EAEvC,MAAM,QAAQ,QAAQ,EAEtB,IAAS,kBAAkB,CAC7B,CAiBA,eAAsB,GAA4B,CAChD,OAAO,EAAM,CAAE,OAAQ,QAAQ,KAAM,CAAC,CACxC"}
|
package/dist/testing/flush.d.ts
CHANGED
|
@@ -5,51 +5,55 @@
|
|
|
5
5
|
* Options for flushing pending reactive updates.
|
|
6
6
|
*/
|
|
7
7
|
export interface FlushOptions {
|
|
8
|
-
/**
|
|
9
|
-
* Maximum number of microtask drain turns.
|
|
10
|
-
* Default: 5, sufficient for normal reactive propagation depth.
|
|
11
|
-
* For deep async chains use `FLUSH_DEEP` (12 turns).
|
|
12
|
-
*/
|
|
13
|
-
maxTurns?: number;
|
|
14
8
|
/**
|
|
15
9
|
* Logger function called with each diagnostic message during the flush.
|
|
16
10
|
* Pass `console.debug` or any `(msg: string) => void` to enable output.
|
|
17
11
|
* Omit (or pass `undefined`) to disable all logging.
|
|
18
12
|
*
|
|
19
|
-
* Prefer `debugFlush()` from `@vielzeug/ore/
|
|
13
|
+
* Prefer `debugFlush()` (also from `@vielzeug/ore/testing`) over wiring this manually.
|
|
20
14
|
*/
|
|
21
15
|
logger?: (msg: string) => void;
|
|
22
16
|
}
|
|
23
17
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
18
|
+
* Flush pending reactive updates: waits for every tracked in-flight component
|
|
19
|
+
* operation (an awaited async `setup()`, scheduled `onMounted` microtasks — including
|
|
20
|
+
* ones registered by other `onMounted` callbacks) to settle, then yields one animation
|
|
21
|
+
* frame and one final microtask pass for rAF-scheduled work.
|
|
22
|
+
*
|
|
23
|
+
* This is deterministic, not a fixed number of guessed turns — `@vielzeug/ripple`'s
|
|
24
|
+
* reactive graph itself settles synchronously on every signal write, so the only real
|
|
25
|
+
* asynchrony to wait for is ore's own component scheduling (see `runtime.ts`'s
|
|
26
|
+
* `hasPendingWork()`). There is no "deep" variant to reach for anymore: `flush()` waits
|
|
27
|
+
* for exactly as much work as is actually pending, however deep that turns out to be.
|
|
26
28
|
*
|
|
27
29
|
* @example
|
|
28
30
|
* ```ts
|
|
29
|
-
*
|
|
31
|
+
* // Standard flush — waits for whatever is actually pending, shallow or deep
|
|
32
|
+
* await flush();
|
|
33
|
+
*
|
|
34
|
+
* // Debug timing issues
|
|
35
|
+
* await flush({ logger: console.debug });
|
|
30
36
|
* ```
|
|
37
|
+
*
|
|
38
|
+
* @throws `OreTimeoutError` if pending work never settles within {@link MAX_FLUSH_ITERATIONS}
|
|
39
|
+
* microtask turns — almost always a sign of a genuine bug (e.g. an `onMounted` callback that
|
|
40
|
+
* keeps re-registering itself), not a case for raising a limit.
|
|
31
41
|
*/
|
|
32
|
-
export declare
|
|
42
|
+
export declare function flush(options?: FlushOptions): Promise<void>;
|
|
33
43
|
/**
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* one final microtask pass for rAF-scheduled work.
|
|
37
|
-
*
|
|
38
|
-
* The default drain count (5) covers typical reactive propagation depth in
|
|
39
|
-
* ore components: effect → queueMicrotask (onMounted) → nested reactive
|
|
40
|
-
* updates. For deeper async chains, use `FLUSH_DEEP`.
|
|
44
|
+
* `flush()` with debug logging pre-wired to `console.debug`. Logs each phase and pending-work
|
|
45
|
+
* turn so you can diagnose unexpected update order or timing issues.
|
|
41
46
|
*
|
|
42
47
|
* @example
|
|
43
48
|
* ```ts
|
|
44
|
-
*
|
|
45
|
-
* await flush();
|
|
46
|
-
*
|
|
47
|
-
* // Shorter flush for simple updates
|
|
48
|
-
* await flush({ maxTurns: 4 });
|
|
49
|
+
* import { debugFlush } from '@vielzeug/ore/testing';
|
|
49
50
|
*
|
|
50
|
-
* //
|
|
51
|
-
* await
|
|
51
|
+
* // in a test
|
|
52
|
+
* await debugFlush();
|
|
53
|
+
* // [flush] draining pending component work (async setup, scheduled mount callbacks)
|
|
54
|
+
* // [flush] pending work remains, turn 1
|
|
55
|
+
* // ...
|
|
52
56
|
* ```
|
|
53
57
|
*/
|
|
54
|
-
export declare function
|
|
58
|
+
export declare function debugFlush(): Promise<void>;
|
|
55
59
|
//# sourceMappingURL=flush.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flush.d.ts","sourceRoot":"","sources":["../../src/testing/flush.ts"],"names":[],"mappings":"AAAA;;GAEG;
|
|
1
|
+
{"version":3,"file":"flush.d.ts","sourceRoot":"","sources":["../../src/testing/flush.ts"],"names":[],"mappings":"AAAA;;GAEG;AAYH;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAChC;AAID;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAsB,KAAK,CAAC,OAAO,GAAE,YAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CA8BrE;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAEhD"}
|
package/dist/testing/flush.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
import{OreTimeoutError as e}from"../errors.js";import{hasPendingWork as t}from"../runtime.js";var n=1e3;async function r(r={}){let{logger:i}=r;i?.(`[flush] draining pending component work (async setup, scheduled mount callbacks)`);let a=0;for(;t();){if(++a>n)throw new e(`flush(): pending component work did not settle after ${n} microtask turns — check for a component whose onMounted callback keeps re-registering itself, or an async setup() that never resolves.`);i?.(`[flush] pending work remains, turn ${a}`),await Promise.resolve()}i?.(`[flush] draining requestAnimationFrame`),await new Promise(e=>typeof requestAnimationFrame<`u`?requestAnimationFrame(()=>e()):e()),i?.(`[flush] final microtask pass`),await Promise.resolve(),i?.(`[flush] complete`)}async function i(){return r({logger:console.debug})}export{i as debugFlush,r as flush};
|
|
2
2
|
//# sourceMappingURL=flush.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flush.js","names":[],"sources":["../../src/testing/flush.ts"],"sourcesContent":["/**\n * Reactive update flushing utilities for test environments.\n */\n\
|
|
1
|
+
{"version":3,"file":"flush.js","names":[],"sources":["../../src/testing/flush.ts"],"sourcesContent":["/**\n * Reactive update flushing utilities for test environments.\n */\n\nimport { OreTimeoutError } from '../errors';\nimport { hasPendingWork } from '../runtime';\n\n// Defensive cap against a genuine bug (e.g. an onMounted callback that always registers\n// another onMounted, looping forever) — not a \"how much is normally enough\" guess like the\n// old fixed turn count was. Microtask polls are cheap, so this is generous.\nconst MAX_FLUSH_ITERATIONS = 1000;\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/**\n * Options for flushing pending reactive updates.\n */\nexport interface FlushOptions {\n /**\n * Logger function called with each diagnostic message during the flush.\n * Pass `console.debug` or any `(msg: string) => void` to enable output.\n * Omit (or pass `undefined`) to disable all logging.\n *\n * Prefer `debugFlush()` (also from `@vielzeug/ore/testing`) over wiring this manually.\n */\n logger?: (msg: string) => void;\n}\n\n// ─── Core ────────────────────────────────────────────────────────────────────\n\n/**\n * Flush pending reactive updates: waits for every tracked in-flight component\n * operation (an awaited async `setup()`, scheduled `onMounted` microtasks — including\n * ones registered by other `onMounted` callbacks) to settle, then yields one animation\n * frame and one final microtask pass for rAF-scheduled work.\n *\n * This is deterministic, not a fixed number of guessed turns — `@vielzeug/ripple`'s\n * reactive graph itself settles synchronously on every signal write, so the only real\n * asynchrony to wait for is ore's own component scheduling (see `runtime.ts`'s\n * `hasPendingWork()`). There is no \"deep\" variant to reach for anymore: `flush()` waits\n * for exactly as much work as is actually pending, however deep that turns out to be.\n *\n * @example\n * ```ts\n * // Standard flush — waits for whatever is actually pending, shallow or deep\n * await flush();\n *\n * // Debug timing issues\n * await flush({ logger: console.debug });\n * ```\n *\n * @throws `OreTimeoutError` if pending work never settles within {@link MAX_FLUSH_ITERATIONS}\n * microtask turns — almost always a sign of a genuine bug (e.g. an `onMounted` callback that\n * keeps re-registering itself), not a case for raising a limit.\n */\nexport async function flush(options: FlushOptions = {}): Promise<void> {\n const { logger } = options;\n\n logger?.('[flush] draining pending component work (async setup, scheduled mount callbacks)');\n\n let turns = 0;\n\n while (hasPendingWork()) {\n if (++turns > MAX_FLUSH_ITERATIONS) {\n throw new OreTimeoutError(\n `flush(): pending component work did not settle after ${MAX_FLUSH_ITERATIONS} microtask turns — ` +\n `check for a component whose onMounted callback keeps re-registering itself, or an async setup() that never resolves.`,\n );\n }\n\n logger?.(`[flush] pending work remains, turn ${turns}`);\n await Promise.resolve();\n }\n\n logger?.('[flush] draining requestAnimationFrame');\n\n await new Promise<void>((resolve) =>\n typeof requestAnimationFrame !== 'undefined' ? requestAnimationFrame(() => resolve()) : resolve(),\n );\n\n logger?.('[flush] final microtask pass');\n\n await Promise.resolve();\n\n logger?.('[flush] complete');\n}\n\n/**\n * `flush()` with debug logging pre-wired to `console.debug`. Logs each phase and pending-work\n * turn so you can diagnose unexpected update order or timing issues.\n *\n * @example\n * ```ts\n * import { debugFlush } from '@vielzeug/ore/testing';\n *\n * // in a test\n * await debugFlush();\n * // [flush] draining pending component work (async setup, scheduled mount callbacks)\n * // [flush] pending work remains, turn 1\n * // ...\n * ```\n */\nexport async function debugFlush(): Promise<void> {\n return flush({ logger: console.debug });\n}\n"],"mappings":"8FAUA,IAAM,EAAuB,IA6C7B,eAAsB,EAAM,EAAwB,CAAC,EAAkB,CACrE,GAAM,CAAE,UAAW,EAEnB,IAAS,kFAAkF,EAE3F,IAAI,EAAQ,EAEZ,KAAO,EAAe,GAAG,CACvB,GAAI,EAAE,EAAQ,EACZ,MAAM,IAAI,EACR,wDAAwD,EAAqB,wIAE/E,EAGF,IAAS,sCAAsC,GAAO,EACtD,MAAM,QAAQ,QAAQ,CACxB,CAEA,IAAS,wCAAwC,EAEjD,MAAM,IAAI,QAAe,GACvB,OAAO,sBAA0B,IAAc,0BAA4B,EAAQ,CAAC,EAAI,EAAQ,CAClG,EAEA,IAAS,8BAA8B,EAEvC,MAAM,QAAQ,QAAQ,EAEtB,IAAS,kBAAkB,CAC7B,CAiBA,eAAsB,GAA4B,CAChD,OAAO,EAAM,CAAE,OAAQ,QAAQ,KAAM,CAAC,CACxC"}
|
package/dist/testing/index.d.ts
CHANGED
|
@@ -1,2 +1,31 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Testing utilities for Ore components
|
|
3
|
+
*
|
|
4
|
+
* ⚠️ Requires DOM environment (browser / jsdom / happy-dom)
|
|
5
|
+
*
|
|
6
|
+
* This barrel re-exports the full testing API for convenience.
|
|
7
|
+
* For tree-shaking, import directly from the focused sub-modules.
|
|
8
|
+
*/
|
|
9
|
+
export { OreTimeoutError } from '../errors';
|
|
10
|
+
export { fire, createPointerEvent, within, waitFor, waitForEvent, type QueryScope, type WaitOptions, } from '@vielzeug/assay';
|
|
11
|
+
export { debugFlush, flush, type FlushOptions } from './flush';
|
|
12
|
+
export { walkFlatTree } from './dom';
|
|
13
|
+
export { installFormInternalsPolyfill } from './form-internals-polyfill';
|
|
14
|
+
export { user } from './interactions';
|
|
15
|
+
export { cleanup, mock, mount, mountComponent, type Fixture, type MountOptions, type MountSetup } from './mount';
|
|
16
|
+
export { renderHook, type HookFixture } from './render-hook';
|
|
17
|
+
/**
|
|
18
|
+
* Register auto-cleanup after each test, and polyfill the jsdom/happy-dom gaps in
|
|
19
|
+
* `ElementInternals`/`FormData`/`<form>.reset()` that `useField()` needs (see
|
|
20
|
+
* `installFormInternalsPolyfill` — safe to call even in a suite with no form-associated
|
|
21
|
+
* components; every patch it installs is a guarded no-op when its target is missing).
|
|
22
|
+
* Call once in your test setup file.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* // vitest.setup.ts
|
|
26
|
+
* import { afterEach } from 'vitest';
|
|
27
|
+
* import { install } from '@vielzeug/ore/testing';
|
|
28
|
+
* install(afterEach);
|
|
29
|
+
*/
|
|
30
|
+
export declare function install(afterEachHook: (fn: () => void) => void): void;
|
|
2
31
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAUH,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAI5C,OAAO,EACL,IAAI,EACJ,kBAAkB,EAClB,MAAM,EACN,OAAO,EACP,YAAY,EACZ,KAAK,UAAU,EACf,KAAK,WAAW,GACjB,MAAM,iBAAiB,CAAC;AAEzB,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,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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("./flush.cjs")
|
|
1
|
+
const e=require("./flush.cjs");let t=require("@vielzeug/assay");var n=()=>e.flush(),r={async clear(e){e.focus(),e.value=``,t.fire.input(e),t.fire.change(e),await n()},async click(e,r){t.fire.pointerEnter(e,r),t.fire.click(e,r),await n()},async dblClick(e){for(let n=0;n<2;n++)t.fire.pointerDown(e),t.fire.pointerUp(e),t.fire.click(e);e.dispatchEvent(new MouseEvent(`dblclick`,{bubbles:!0,cancelable:!0})),await n()},async fill(e,r){e.focus(),e.value=``;for(let i of r)e.value+=i,t.fire.input(e),t.fire.keyDown(e,{key:i}),t.fire.keyUp(e,{key:i}),await n();t.fire.change(e)},async hover(e){t.fire.pointerEnter(e),await n()},async press(e,r,i){t.fire.keyDown(e,{key:r,...i}),t.fire.keyUp(e,{key:r,...i}),await n()},async select(e,r){let i=Array.isArray(r)?r:[r];for(let t of e.options)t.selected=i.includes(t.value);t.fire.change(e),await n()},async type(e,r){e.focus();for(let i of r)e.value+=i,t.fire.input(e),t.fire.keyDown(e,{key:i}),t.fire.keyUp(e,{key:i}),await n();t.fire.change(e)},async unhover(e){t.fire.pointerLeave(e),await n()}};exports.user=r;
|
|
2
2
|
//# sourceMappingURL=interactions.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"interactions.cjs","names":[],"sources":["../../src/testing/interactions.ts"],"sourcesContent":["/**\n * Higher-level async user interaction helpers for test environments.\n */\n\nimport { fire } from '
|
|
1
|
+
{"version":3,"file":"interactions.cjs","names":[],"sources":["../../src/testing/interactions.ts"],"sourcesContent":["/**\n * Higher-level async user interaction helpers for test environments.\n */\n\nimport { fire } from '@vielzeug/assay';\n\nimport { flush } from './flush';\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\nconst tick = (): Promise<void> => flush();\n\n// ─── API ─────────────────────────────────────────────────────────────────────\n\n/**\n * Higher-level async user interactions that mirror real browser behavior.\n *\n * @example\n * await user.type(input, 'hello');\n * await user.fill(input, 'replacement'); // clear then type\n * await user.click(button);\n * await user.press(input, 'Enter');\n */\nexport const user = {\n async clear(el: HTMLInputElement | HTMLTextAreaElement): Promise<void> {\n el.focus();\n el.value = '';\n fire.input(el);\n fire.change(el);\n await tick();\n },\n\n async click(el: Element, opts?: PointerEventInit): Promise<void> {\n fire.pointerEnter(el, opts);\n fire.click(el, opts);\n await tick();\n },\n\n async dblClick(el: Element): Promise<void> {\n for (let i = 0; i < 2; i++) {\n fire.pointerDown(el);\n fire.pointerUp(el);\n fire.click(el);\n }\n el.dispatchEvent(new MouseEvent('dblclick', { bubbles: true, cancelable: true }));\n await tick();\n },\n\n /** Clear existing value and type new text (select-all-and-replace semantics) */\n async fill(el: HTMLInputElement | HTMLTextAreaElement, text: string): Promise<void> {\n el.focus();\n el.value = '';\n for (const char of text) {\n el.value += char;\n fire.input(el);\n fire.keyDown(el, { key: char });\n fire.keyUp(el, { key: char });\n await tick();\n }\n fire.change(el);\n },\n\n async hover(el: Element): Promise<void> {\n fire.pointerEnter(el);\n await tick();\n },\n\n /** Dispatch keydown + keyup for a single key */\n async press(el: Element, key: string, opts?: KeyboardEventInit): Promise<void> {\n fire.keyDown(el, { key, ...opts });\n fire.keyUp(el, { key, ...opts });\n await tick();\n },\n\n async select(el: HTMLSelectElement, value: string | string[]): Promise<void> {\n const values = Array.isArray(value) ? value : [value];\n\n for (const opt of el.options) opt.selected = values.includes(opt.value);\n fire.change(el);\n await tick();\n },\n\n /** Type text character-by-character, appending to any existing value */\n async type(el: HTMLInputElement | HTMLTextAreaElement, text: string): Promise<void> {\n el.focus();\n for (const char of text) {\n el.value += char;\n fire.input(el);\n fire.keyDown(el, { key: char });\n fire.keyUp(el, { key: char });\n await tick();\n }\n fire.change(el);\n },\n\n async unhover(el: Element): Promise<void> {\n fire.pointerLeave(el);\n await tick();\n },\n} as const;\n"],"mappings":"gEAUA,IAAM,MAA4B,EAAA,MAAM,EAa3B,EAAO,CAClB,MAAM,MAAM,EAA2D,CACrE,EAAG,MAAM,EACT,EAAG,MAAQ,GACX,EAAA,KAAK,MAAM,CAAE,EACb,EAAA,KAAK,OAAO,CAAE,EACd,MAAM,EAAK,CACb,EAEA,MAAM,MAAM,EAAa,EAAwC,CAC/D,EAAA,KAAK,aAAa,EAAI,CAAI,EAC1B,EAAA,KAAK,MAAM,EAAI,CAAI,EACnB,MAAM,EAAK,CACb,EAEA,MAAM,SAAS,EAA4B,CACzC,IAAK,IAAI,EAAI,EAAG,EAAI,EAAG,IACrB,EAAA,KAAK,YAAY,CAAE,EACnB,EAAA,KAAK,UAAU,CAAE,EACjB,EAAA,KAAK,MAAM,CAAE,EAEf,EAAG,cAAc,IAAI,WAAW,WAAY,CAAE,QAAS,GAAM,WAAY,EAAK,CAAC,CAAC,EAChF,MAAM,EAAK,CACb,EAGA,MAAM,KAAK,EAA4C,EAA6B,CAClF,EAAG,MAAM,EACT,EAAG,MAAQ,GACX,IAAK,IAAM,KAAQ,EACjB,EAAG,OAAS,EACZ,EAAA,KAAK,MAAM,CAAE,EACb,EAAA,KAAK,QAAQ,EAAI,CAAE,IAAK,CAAK,CAAC,EAC9B,EAAA,KAAK,MAAM,EAAI,CAAE,IAAK,CAAK,CAAC,EAC5B,MAAM,EAAK,EAEb,EAAA,KAAK,OAAO,CAAE,CAChB,EAEA,MAAM,MAAM,EAA4B,CACtC,EAAA,KAAK,aAAa,CAAE,EACpB,MAAM,EAAK,CACb,EAGA,MAAM,MAAM,EAAa,EAAa,EAAyC,CAC7E,EAAA,KAAK,QAAQ,EAAI,CAAE,MAAK,GAAG,CAAK,CAAC,EACjC,EAAA,KAAK,MAAM,EAAI,CAAE,MAAK,GAAG,CAAK,CAAC,EAC/B,MAAM,EAAK,CACb,EAEA,MAAM,OAAO,EAAuB,EAAyC,CAC3E,IAAM,EAAS,MAAM,QAAQ,CAAK,EAAI,EAAQ,CAAC,CAAK,EAEpD,IAAK,IAAM,KAAO,EAAG,QAAS,EAAI,SAAW,EAAO,SAAS,EAAI,KAAK,EACtE,EAAA,KAAK,OAAO,CAAE,EACd,MAAM,EAAK,CACb,EAGA,MAAM,KAAK,EAA4C,EAA6B,CAClF,EAAG,MAAM,EACT,IAAK,IAAM,KAAQ,EACjB,EAAG,OAAS,EACZ,EAAA,KAAK,MAAM,CAAE,EACb,EAAA,KAAK,QAAQ,EAAI,CAAE,IAAK,CAAK,CAAC,EAC9B,EAAA,KAAK,MAAM,EAAI,CAAE,IAAK,CAAK,CAAC,EAC5B,MAAM,EAAK,EAEb,EAAA,KAAK,OAAO,CAAE,CAChB,EAEA,MAAM,QAAQ,EAA4B,CACxC,EAAA,KAAK,aAAa,CAAE,EACpB,MAAM,EAAK,CACb,CACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"interactions.d.ts","sourceRoot":"","sources":["../../src/testing/interactions.ts"],"names":[],"mappings":"AAAA;;GAEG;
|
|
1
|
+
{"version":3,"file":"interactions.d.ts","sourceRoot":"","sources":["../../src/testing/interactions.ts"],"names":[],"mappings":"AAAA;;GAEG;AAYH;;;;;;;;GAQG;AACH,eAAO,MAAM,IAAI;yBACC,gBAAgB,GAAG,mBAAmB,KAAG,OAAO,CAAC,IAAI,CAAC;yBAQtD,OAAO,SAAS,gBAAgB,KAAG,OAAO,CAAC,IAAI,CAAC;4BAM7C,OAAO,KAAG,OAAO,CAAC,IAAI,CAAC;IAU1C,gFAAgF;wBACjE,gBAAgB,GAAG,mBAAmB,QAAQ,MAAM,KAAG,OAAO,CAAC,IAAI,CAAC;yBAanE,OAAO,KAAG,OAAO,CAAC,IAAI,CAAC;IAKvC,gDAAgD;yBAChC,OAAO,OAAO,MAAM,SAAS,iBAAiB,KAAG,OAAO,CAAC,IAAI,CAAC;0BAM7D,iBAAiB,SAAS,MAAM,GAAG,MAAM,EAAE,KAAG,OAAO,CAAC,IAAI,CAAC;IAQ5E,wEAAwE;wBACzD,gBAAgB,GAAG,mBAAmB,QAAQ,MAAM,KAAG,OAAO,CAAC,IAAI,CAAC;2BAYjE,OAAO,KAAG,OAAO,CAAC,IAAI,CAAC;CAIjC,CAAC"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{flush as e}from"./flush.js";import{fire as t}from"
|
|
1
|
+
import{flush as e}from"./flush.js";import{fire as t}from"@vielzeug/assay";var n=()=>e(),r={async clear(e){e.focus(),e.value=``,t.input(e),t.change(e),await n()},async click(e,r){t.pointerEnter(e,r),t.click(e,r),await n()},async dblClick(e){for(let n=0;n<2;n++)t.pointerDown(e),t.pointerUp(e),t.click(e);e.dispatchEvent(new MouseEvent(`dblclick`,{bubbles:!0,cancelable:!0})),await n()},async fill(e,r){e.focus(),e.value=``;for(let i of r)e.value+=i,t.input(e),t.keyDown(e,{key:i}),t.keyUp(e,{key:i}),await n();t.change(e)},async hover(e){t.pointerEnter(e),await n()},async press(e,r,i){t.keyDown(e,{key:r,...i}),t.keyUp(e,{key:r,...i}),await n()},async select(e,r){let i=Array.isArray(r)?r:[r];for(let t of e.options)t.selected=i.includes(t.value);t.change(e),await n()},async type(e,r){e.focus();for(let i of r)e.value+=i,t.input(e),t.keyDown(e,{key:i}),t.keyUp(e,{key:i}),await n();t.change(e)},async unhover(e){t.pointerLeave(e),await n()}};export{r as user};
|
|
2
2
|
//# sourceMappingURL=interactions.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"interactions.js","names":[],"sources":["../../src/testing/interactions.ts"],"sourcesContent":["/**\n * Higher-level async user interaction helpers for test environments.\n */\n\nimport { fire } from '
|
|
1
|
+
{"version":3,"file":"interactions.js","names":[],"sources":["../../src/testing/interactions.ts"],"sourcesContent":["/**\n * Higher-level async user interaction helpers for test environments.\n */\n\nimport { fire } from '@vielzeug/assay';\n\nimport { flush } from './flush';\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\nconst tick = (): Promise<void> => flush();\n\n// ─── API ─────────────────────────────────────────────────────────────────────\n\n/**\n * Higher-level async user interactions that mirror real browser behavior.\n *\n * @example\n * await user.type(input, 'hello');\n * await user.fill(input, 'replacement'); // clear then type\n * await user.click(button);\n * await user.press(input, 'Enter');\n */\nexport const user = {\n async clear(el: HTMLInputElement | HTMLTextAreaElement): Promise<void> {\n el.focus();\n el.value = '';\n fire.input(el);\n fire.change(el);\n await tick();\n },\n\n async click(el: Element, opts?: PointerEventInit): Promise<void> {\n fire.pointerEnter(el, opts);\n fire.click(el, opts);\n await tick();\n },\n\n async dblClick(el: Element): Promise<void> {\n for (let i = 0; i < 2; i++) {\n fire.pointerDown(el);\n fire.pointerUp(el);\n fire.click(el);\n }\n el.dispatchEvent(new MouseEvent('dblclick', { bubbles: true, cancelable: true }));\n await tick();\n },\n\n /** Clear existing value and type new text (select-all-and-replace semantics) */\n async fill(el: HTMLInputElement | HTMLTextAreaElement, text: string): Promise<void> {\n el.focus();\n el.value = '';\n for (const char of text) {\n el.value += char;\n fire.input(el);\n fire.keyDown(el, { key: char });\n fire.keyUp(el, { key: char });\n await tick();\n }\n fire.change(el);\n },\n\n async hover(el: Element): Promise<void> {\n fire.pointerEnter(el);\n await tick();\n },\n\n /** Dispatch keydown + keyup for a single key */\n async press(el: Element, key: string, opts?: KeyboardEventInit): Promise<void> {\n fire.keyDown(el, { key, ...opts });\n fire.keyUp(el, { key, ...opts });\n await tick();\n },\n\n async select(el: HTMLSelectElement, value: string | string[]): Promise<void> {\n const values = Array.isArray(value) ? value : [value];\n\n for (const opt of el.options) opt.selected = values.includes(opt.value);\n fire.change(el);\n await tick();\n },\n\n /** Type text character-by-character, appending to any existing value */\n async type(el: HTMLInputElement | HTMLTextAreaElement, text: string): Promise<void> {\n el.focus();\n for (const char of text) {\n el.value += char;\n fire.input(el);\n fire.keyDown(el, { key: char });\n fire.keyUp(el, { key: char });\n await tick();\n }\n fire.change(el);\n },\n\n async unhover(el: Element): Promise<void> {\n fire.pointerLeave(el);\n await tick();\n },\n} as const;\n"],"mappings":"0EAUA,IAAM,MAA4B,EAAM,EAa3B,EAAO,CAClB,MAAM,MAAM,EAA2D,CACrE,EAAG,MAAM,EACT,EAAG,MAAQ,GACX,EAAK,MAAM,CAAE,EACb,EAAK,OAAO,CAAE,EACd,MAAM,EAAK,CACb,EAEA,MAAM,MAAM,EAAa,EAAwC,CAC/D,EAAK,aAAa,EAAI,CAAI,EAC1B,EAAK,MAAM,EAAI,CAAI,EACnB,MAAM,EAAK,CACb,EAEA,MAAM,SAAS,EAA4B,CACzC,IAAK,IAAI,EAAI,EAAG,EAAI,EAAG,IACrB,EAAK,YAAY,CAAE,EACnB,EAAK,UAAU,CAAE,EACjB,EAAK,MAAM,CAAE,EAEf,EAAG,cAAc,IAAI,WAAW,WAAY,CAAE,QAAS,GAAM,WAAY,EAAK,CAAC,CAAC,EAChF,MAAM,EAAK,CACb,EAGA,MAAM,KAAK,EAA4C,EAA6B,CAClF,EAAG,MAAM,EACT,EAAG,MAAQ,GACX,IAAK,IAAM,KAAQ,EACjB,EAAG,OAAS,EACZ,EAAK,MAAM,CAAE,EACb,EAAK,QAAQ,EAAI,CAAE,IAAK,CAAK,CAAC,EAC9B,EAAK,MAAM,EAAI,CAAE,IAAK,CAAK,CAAC,EAC5B,MAAM,EAAK,EAEb,EAAK,OAAO,CAAE,CAChB,EAEA,MAAM,MAAM,EAA4B,CACtC,EAAK,aAAa,CAAE,EACpB,MAAM,EAAK,CACb,EAGA,MAAM,MAAM,EAAa,EAAa,EAAyC,CAC7E,EAAK,QAAQ,EAAI,CAAE,MAAK,GAAG,CAAK,CAAC,EACjC,EAAK,MAAM,EAAI,CAAE,MAAK,GAAG,CAAK,CAAC,EAC/B,MAAM,EAAK,CACb,EAEA,MAAM,OAAO,EAAuB,EAAyC,CAC3E,IAAM,EAAS,MAAM,QAAQ,CAAK,EAAI,EAAQ,CAAC,CAAK,EAEpD,IAAK,IAAM,KAAO,EAAG,QAAS,EAAI,SAAW,EAAO,SAAS,EAAI,KAAK,EACtE,EAAK,OAAO,CAAE,EACd,MAAM,EAAK,CACb,EAGA,MAAM,KAAK,EAA4C,EAA6B,CAClF,EAAG,MAAM,EACT,IAAK,IAAM,KAAQ,EACjB,EAAG,OAAS,EACZ,EAAK,MAAM,CAAE,EACb,EAAK,QAAQ,EAAI,CAAE,IAAK,CAAK,CAAC,EAC9B,EAAK,MAAM,EAAI,CAAE,IAAK,CAAK,CAAC,EAC5B,MAAM,EAAK,EAEb,EAAK,OAAO,CAAE,CAChB,EAEA,MAAM,QAAQ,EAA4B,CACxC,EAAK,aAAa,CAAE,EACpB,MAAM,EAAK,CACb,CACF"}
|
package/dist/testing/mount.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("
|
|
1
|
+
const e=require("../directives/live.cjs");require("../types/bindings.cjs");const t=require("../directives/raw.cjs"),n=require("../utils/css.cjs"),r=require("../define.cjs"),i=require("../utils/id.cjs"),a=require("./flush.cjs");let o=require("@vielzeug/assay");var s=[],c=0;function l(e,t,n){n===!1?e.removeAttribute(t):e.setAttribute(t,n===!0?``:String(n))}var u=e=>e instanceof Error?e:Error(String(e)),d=async e=>{if(typeof window>`u`)return e();let t=null,n=e=>{t=u(e.error??e.message),e.preventDefault()},r=e=>{t=u(e.reason),e.preventDefault()};window.addEventListener(`error`,n),window.addEventListener(`unhandledrejection`,r);try{let n=await e();if(t)throw t;return n}finally{window.removeEventListener(`error`,n),window.removeEventListener(`unhandledrejection`,r)}};async function f(e,t={}){let{attrs:n={},componentOptions:i,container:u=document.body,html:f,props:p={}}=t,m,h;typeof e==`string`?m=e:(m=`trial-${++c}-${Math.random().toString(36).slice(2,7)}`,h={...i??{},setup:e}),h&&r.define(m,h);let g=document.createElement(m);f&&(g.innerHTML=f),Object.keys(p).length&&Object.assign(g,p);for(let[e,t]of Object.entries(n))l(g,e,t);try{await d(async()=>{u.appendChild(g),s.push(g),await a.flush()})}catch(e){g.remove();let t=s.indexOf(g);throw t!==-1&&s.splice(t,1),e}let _=!1;function v(){if(_)return;_=!0,g.remove();let e=s.indexOf(g);e!==-1&&s.splice(e,1)}return{async act(e){await e(),await a.flush()},async attr(e,t){l(g,e,t),await a.flush()},async attrs(e){for(let[t,n]of Object.entries(e))l(g,t,n);await a.flush()},dispose:v,get disposed(){return _},element:g,flush:a.flush,query(e){return(0,o.query)(g.shadowRoot??g,e)},queryAll(e){return(0,o.queryAll)(g.shadowRoot??g,e)},queryAllByTestId(e){return(0,o.queryAllByTestId)(g.shadowRoot??g,e)},queryAllByText(e,t=`*`){return(0,o.queryAllByText)(g.shadowRoot??g,e,t)},queryByTestId(e){return(0,o.queryByTestId)(g.shadowRoot??g,e)},queryByText(e,t=`*`){return(0,o.queryByText)(g.shadowRoot??g,e,t)},get shadow(){return g.shadowRoot},[Symbol.dispose](){v()}}}async function p(e,t,n){return r.define(e,t),f(e,n)}function m(e,t=``){customElements.get(e)||customElements.define(e,class extends HTMLElement{connectedCallback(){this.innerHTML=t}})}function h(){for(let e of s)e.remove();s.length=0,t._resetRawSanitizer(),e._resetLiveSignals(),n._clearStylesheetCache(),i._resetIdCounter()}Object.defineProperty(exports,"_componentTagCounter",{enumerable:!0,get:function(){return c}}),exports._mountedElements=s,exports.cleanup=h,exports.mock=m,exports.mount=f,exports.mountComponent=p;
|
|
2
2
|
//# sourceMappingURL=mount.cjs.map
|
|
@@ -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 } 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 try {\n await withWindowErrorCapture(async () => {\n container.appendChild(element);\n _mountedElements.push(element);\n await flush();\n });\n } catch (err) {\n element.remove();\n\n const i = _mountedElements.indexOf(element);\n\n if (i !== -1) _mountedElements.splice(i, 1);\n\n throw err;\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":"4PAyEA,IAAa,EAAkC,CAAC,EACrC,EAAuB,EAalC,SAAS,EAAU,EAAkB,EAAc,EAAwC,CACrF,IAAU,GAAO,EAAQ,gBAAgB,CAAI,EAC5C,EAAQ,aAAa,EAAM,IAAU,GAAO,GAAK,OAAO,CAAK,CAAC,CACrE,CAEA,IAAM,EAAW,GACR,aAAiB,MAAQ,EAAY,MAAM,OAAO,CAAK,CAAC,EAG3D,EAAyB,KAAU,IAAyC,CAChF,GAAI,OAAO,OAAW,IAAa,OAAO,EAAO,EAEjD,IAAI,EAAyB,KACvB,EAAW,GAAsB,CACrC,EAAW,EAAQ,EAAM,OAAS,EAAM,OAAO,EAC/C,EAAM,eAAe,CACvB,EACM,EAAwB,GAAiC,CAC7D,EAAW,EAAQ,EAAM,MAAM,EAC/B,EAAM,eAAe,CACvB,EAEA,OAAO,iBAAiB,QAAS,CAAO,EACxC,OAAO,iBAAiB,qBAAsB,CAAoB,EAElE,GAAI,CACF,IAAM,EAAS,MAAM,EAAO,EAE5B,GAAI,EAAU,MAAM,EAEpB,OAAO,CACT,QAAU,CACR,OAAO,oBAAoB,QAAS,CAAO,EAC3C,OAAO,oBAAoB,qBAAsB,CAAoB,CACvE,CACF,EA2BA,eAAsB,EACpB,EACA,EAAwB,CAAC,EACJ,CACrB,GAAM,CAAE,QAAQ,CAAC,EAAG,mBAAkB,YAAY,SAAS,KAAM,OAAM,QAAQ,CAAC,GAAM,EAElF,EACA,EAEA,OAAO,GAAe,SACxB,EAAU,GAEV,EAAU,SAAS,EAAE,EAAqB,GAAG,KAAK,OAAO,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,MAAM,EAAG,CAAC,IAClF,EAAmB,CACjB,GAAI,GAAoB,CAAC,EACzB,MAAO,CACT,GAGE,GACF,EAAA,OAAO,EAAS,CAAgB,EAGlC,IAAM,EAAU,SAAS,cAAc,CAAO,EAE1C,IAAM,EAAQ,UAAY,GAE1B,OAAO,KAAK,CAAK,CAAC,CAAC,QAAQ,OAAO,OAAO,EAAS,CAAK,EAE3D,IAAK,GAAM,CAAC,EAAM,KAAU,OAAO,QAAQ,CAAK,EAAG,EAAU,EAAS,EAAM,CAAK,EAEjF,GAAI,CACF,MAAM,EAAuB,SAAY,CACvC,EAAU,YAAY,CAAO,EAC7B,EAAiB,KAAK,CAAO,EAC7B,MAAM,EAAA,MAAM,CACd,CAAC,CACH,OAAS,EAAK,CACZ,EAAQ,OAAO,EAEf,IAAM,EAAI,EAAiB,QAAQ,CAAO,EAI1C,MAFI,IAAM,IAAI,EAAiB,OAAO,EAAG,CAAC,EAEpC,CACR,CAEA,IAAI,EAAa,GAEjB,SAAS,GAAU,CACjB,GAAI,EAAY,OAEhB,EAAa,GACb,EAAQ,OAAO,EAEf,IAAM,EAAI,EAAiB,QAAQ,CAAO,EAEtC,IAAM,IAAI,EAAiB,OAAO,EAAG,CAAC,CAC5C,CAEA,MAAO,CACL,MAAM,IAAI,EAAI,CACZ,MAAM,EAAG,EACT,MAAM,EAAA,MAAM,CACd,EAEA,MAAM,KAAK,EAAM,EAAO,CACtB,EAAU,EAAS,EAAM,CAAK,EAC9B,MAAM,EAAA,MAAM,CACd,EAEA,MAAM,MAAM,EAAQ,CAClB,IAAK,GAAM,CAAC,EAAM,KAAU,OAAO,QAAQ,CAAM,EAAG,EAAU,EAAS,EAAM,CAAK,EAClF,MAAM,EAAA,MAAM,CACd,EAEA,UAEA,IAAI,UAAoB,CACtB,OAAO,CACT,EAEA,UAEA,MAAA,EAAA,MAEA,MAAmC,EAA4B,CAC7D,OAAQ,EAAQ,YAAc,EAAA,CAAS,cAAiB,CAAQ,CAClE,EAEA,SAAsC,EAAuB,CAC3D,OAAO,MAAM,MAAM,EAAQ,YAAc,EAAA,CAAS,iBAAoB,CAAQ,CAAC,CACjF,EAEA,iBAA8C,EAAqB,CACjE,OAAO,MAAM,MAAM,EAAQ,YAAc,EAAA,CAAS,iBAAoB,iBAAiB,EAAO,GAAG,CAAC,CACpG,EAEA,eAA4C,EAAc,EAAW,IAAU,CAC7E,OAAO,EAAA,eAAmB,EAAQ,YAAc,EAAqB,EAAM,CAAQ,CACrF,EAEA,cAA2C,EAA0B,CACnE,OAAQ,EAAQ,YAAc,EAAA,CAAS,cAAiB,iBAAiB,EAAO,GAAG,CACrF,EAEA,YAAyC,EAAc,EAAW,IAAe,CAC/E,OAAO,EAAA,YAAgB,EAAQ,YAAc,EAAqB,EAAM,CAAQ,CAClF,EAEA,IAAI,QAA4B,CAC9B,OAAO,EAAQ,UACjB,EAEA,CAAC,OAAO,UAAW,CACjB,EAAQ,CACV,CACF,CACF,CAcA,eAAsB,EACpB,EACA,EACA,EACqB,CAGrB,OAFA,EAAA,OAAO,EAAK,CAAU,EAEf,EAAS,EAAK,CAAO,CAC9B,CAQA,SAAgB,EAAK,EAAiB,EAAW,GAAU,CACpD,eAAe,IAAI,CAAO,GAC7B,eAAe,OACb,EACA,cAAc,WAAY,CACxB,mBAAoB,CAClB,KAAK,UAAY,CACnB,CACF,CACF,CAEJ,CASA,SAAgB,GAAgB,CAC9B,IAAK,IAAM,KAAM,EAAkB,EAAG,OAAO,EAC7C,EAAiB,OAAS,EAC1B,EAAA,mBAAmB,EACnB,EAAA,kBAAkB,EAClB,EAAA,sBAAsB,EACtB,EAAA,gBAAgB,CAClB"}
|
|
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 {\n query,\n queryAll,\n queryAllByTestId,\n queryAllByText,\n queryByTestId,\n queryByText,\n type QueryScope,\n} from '@vielzeug/assay';\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';\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n// Fixture's query/queryAll/queryBy*/queryAllBy* methods are exactly QueryScope's surface —\n// extending it (rather than redeclaring all six signatures a second time) means a future\n// addition to one is automatically part of the other, with the type system enforcing it.\n/**\n * A mounted component ready for assertions. Every inherited `QueryScope` method (`query`,\n * `queryAll`, `queryByText`, `queryAllByText`, `queryByTestId`, `queryAllByTestId`) is scoped\n * to `element.shadowRoot` — falling back to `element` itself for light-DOM components\n * (`shadow: false` / no shadow root).\n */\nexport interface Fixture<T extends HTMLElement = HTMLElement> extends QueryScope {\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 /** 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 try {\n await withWindowErrorCapture(async () => {\n container.appendChild(element);\n _mountedElements.push(element);\n await flush();\n });\n } catch (err) {\n element.remove();\n\n const i = _mountedElements.indexOf(element);\n\n if (i !== -1) _mountedElements.splice(i, 1);\n\n throw err;\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 query<E>((element.shadowRoot ?? element) as Element, selector);\n },\n\n queryAll<E extends Element = Element>(selector: string): E[] {\n return queryAll<E>((element.shadowRoot ?? element) as Element, selector);\n },\n\n queryAllByTestId<E extends Element = Element>(testId: string): E[] {\n return queryAllByTestId<E>((element.shadowRoot ?? element) as Element, 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 queryByTestId<E>((element.shadowRoot ?? element) as Element, 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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"}
|