@akashjs/runtime 0.1.35 → 0.1.37
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/chunk-QTPP343X.js +2 -0
- package/dist/chunk-QTPP343X.js.map +1 -0
- package/dist/chunk-UOD4PYAN.cjs +2 -0
- package/dist/chunk-UOD4PYAN.cjs.map +1 -0
- package/dist/index.cjs +8 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +8 -8
- package/dist/index.js.map +1 -1
- package/dist/ssr.cjs +1 -1
- package/dist/ssr.js +1 -1
- package/dist/test.cjs +4 -4
- package/dist/test.cjs.map +1 -1
- package/dist/test.js +4 -4
- package/dist/test.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-VAHJHMWE.js +0 -2
- package/dist/chunk-VAHJHMWE.js.map +0 -1
- package/dist/chunk-YU4YSPD6.cjs +0 -2
- package/dist/chunk-YU4YSPD6.cjs.map +0 -1
package/dist/ssr.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
'use strict';var
|
|
1
|
+
'use strict';var chunkUOD4PYAN_cjs=require('./chunk-UOD4PYAN.cjs');require('./chunk-RTJ6UDGV.cjs');Object.defineProperty(exports,"escapeHtml",{enumerable:true,get:function(){return chunkUOD4PYAN_cjs.a}});Object.defineProperty(exports,"isServerRendering",{enumerable:true,get:function(){return chunkUOD4PYAN_cjs.b}});Object.defineProperty(exports,"nodeToHtml",{enumerable:true,get:function(){return chunkUOD4PYAN_cjs.i}});Object.defineProperty(exports,"renderNodes",{enumerable:true,get:function(){return chunkUOD4PYAN_cjs.j}});Object.defineProperty(exports,"renderToStream",{enumerable:true,get:function(){return chunkUOD4PYAN_cjs.e}});Object.defineProperty(exports,"renderToString",{enumerable:true,get:function(){return chunkUOD4PYAN_cjs.c}});Object.defineProperty(exports,"renderToStringSync",{enumerable:true,get:function(){return chunkUOD4PYAN_cjs.d}});Object.defineProperty(exports,"ssrElement",{enumerable:true,get:function(){return chunkUOD4PYAN_cjs.f}});Object.defineProperty(exports,"ssrRaw",{enumerable:true,get:function(){return chunkUOD4PYAN_cjs.h}});Object.defineProperty(exports,"ssrText",{enumerable:true,get:function(){return chunkUOD4PYAN_cjs.g}});//# sourceMappingURL=ssr.cjs.map
|
|
2
2
|
//# sourceMappingURL=ssr.cjs.map
|
package/dist/ssr.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export{a as escapeHtml,b as isServerRendering,i as nodeToHtml,j as renderNodes,e as renderToStream,c as renderToString,d as renderToStringSync,f as ssrElement,h as ssrRaw,g as ssrText}from'./chunk-
|
|
1
|
+
export{a as escapeHtml,b as isServerRendering,i as nodeToHtml,j as renderNodes,e as renderToStream,c as renderToString,d as renderToStringSync,f as ssrElement,h as ssrRaw,g as ssrText}from'./chunk-QTPP343X.js';import'./chunk-TWA4T6PX.js';//# sourceMappingURL=ssr.js.map
|
|
2
2
|
//# sourceMappingURL=ssr.js.map
|
package/dist/test.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
'use strict';var chunkYIVBLM2A_cjs=require('./chunk-YIVBLM2A.cjs')
|
|
2
|
-
Container HTML: ${
|
|
3
|
-
Container HTML: ${
|
|
4
|
-
Container HTML: ${
|
|
1
|
+
'use strict';var chunkYIVBLM2A_cjs=require('./chunk-YIVBLM2A.cjs'),chunkRTJ6UDGV_cjs=require('./chunk-RTJ6UDGV.cjs');function H(t,n){let e=document.createElement("div");e.setAttribute("data-akash-test-root","");let i=n?.props??{},r=n?.provide,s;return r&&r.size>0?s=chunkYIVBLM2A_cjs.B(()=>{for(let[l,d]of r)chunkYIVBLM2A_cjs.h(l,d);return ()=>t(i)})({}):s=t(i),e.appendChild(s),document.body.appendChild(e),{container:e,unmount(){e.remove();},getByText(o){let l=T(e,o);if(!l)throw new Error(`[AkashJS Test] Could not find element with text: "${o}"
|
|
2
|
+
Container HTML: ${e.innerHTML.slice(0,200)}`);return l},getByRole(o){let l=e.querySelector(`[role="${o}"]`)??f(e,o);if(!l)throw new Error(`[AkashJS Test] Could not find element with role: "${o}"
|
|
3
|
+
Container HTML: ${e.innerHTML.slice(0,200)}`);return l},getByTestId(o){let l=e.querySelector(`[data-testid="${o}"]`);if(!l)throw new Error(`[AkashJS Test] Could not find element with data-testid: "${o}"
|
|
4
|
+
Container HTML: ${e.innerHTML.slice(0,200)}`);return l},queryAll(o){return Array.from(e.querySelectorAll(o))},query(o){return e.querySelector(o)}}}var L={async click(t){t.dispatchEvent(new MouseEvent("click",{bubbles:true,cancelable:true})),await a();},async input(t,n){let e=Object.getOwnPropertyDescriptor(t instanceof HTMLTextAreaElement?HTMLTextAreaElement.prototype:HTMLInputElement.prototype,"value")?.set;e?e.call(t,n):t.value=n,t.dispatchEvent(new Event("input",{bubbles:true})),t.dispatchEvent(new Event("change",{bubbles:true})),await a();},async submit(t){t.dispatchEvent(new Event("submit",{bubbles:true,cancelable:true})),await a();},async focus(t){t.focus(),t.dispatchEvent(new FocusEvent("focus",{bubbles:true})),await a();},async blur(t){t.blur(),t.dispatchEvent(new FocusEvent("blur",{bubbles:true})),await a();},async keyDown(t,n,e){t.dispatchEvent(new KeyboardEvent("keydown",{key:n,bubbles:true,...e})),await a();},async keyUp(t,n,e){t.dispatchEvent(new KeyboardEvent("keyup",{key:n,bubbles:true,...e})),await a();}};function a(){return new Promise(t=>queueMicrotask(t))}function T(t,n){let e=null,i=document.createTreeWalker(t,NodeFilter.SHOW_ELEMENT),r=i.nextNode();for(;r;)r instanceof HTMLElement&&r.textContent?.includes(n)&&(e=r),r=i.nextNode();return !e&&t.textContent?.includes(n)&&(e=t),e}var E={button:["button"],a:["link"],input:["textbox","checkbox","radio","spinbutton","slider"],select:["combobox","listbox"],textarea:["textbox"],img:["img"],form:["form"],nav:["navigation"],main:["main"],header:["banner"],footer:["contentinfo"],aside:["complementary"],section:["region"],article:["article"],ul:["list"],ol:["list"],li:["listitem"],table:["table"],th:["columnheader"],td:["cell"],h1:["heading"],h2:["heading"],h3:["heading"],h4:["heading"],h5:["heading"],h6:["heading"]};function f(t,n){let e=[];for(let[r,s]of Object.entries(E))s.includes(n)&&e.push(r);if(e.length===0)return null;let i=e.join(", ");return t.querySelector(i)}async function y(t,n){let{timeout:e=1e3,interval:i=50}=n??{},r=Date.now();for(;;)try{await t();return}catch(s){if(Date.now()-r>=e)throw s;await new Promise(o=>setTimeout(o,i));}}async function w(t,n,e){let i=null;return await y(()=>{if(i=t.querySelector(n),!i)throw new Error(`Element "${n}" not found`)},e),i}function v(t){let n=chunkRTJ6UDGV_cjs.l(t),e=[t],i=0,r=(()=>n());return r.set=s=>{n.set(s),e.push(s),i++;},r.update=s=>{let o=s(n.peek());r.set(o);},r.peek=()=>n.peek(),Object.defineProperty(r,"history",{get:()=>[...e]}),Object.defineProperty(r,"setCount",{get:()=>i}),r.resetHistory=()=>{e.length=0,e.push(n.peek()),i=0;},r}Object.defineProperty(exports,"flush",{enumerable:true,get:function(){return chunkRTJ6UDGV_cjs.b}});exports.createTestSignal=v;exports.fireEvent=L;exports.mount=H;exports.waitFor=y;exports.waitForElement=w;//# sourceMappingURL=test.cjs.map
|
|
5
5
|
//# sourceMappingURL=test.cjs.map
|
package/dist/test.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/test.ts"],"names":["mount","component","options","container","props","provides","node","defineComponent","key","value","provide","text","el","findByText","role","findImplicitRole","id","selector","fireEvent","tick","nativeInputValueSetter","resolve","root","best","walker","IMPLICIT_ROLES","tags","tag","roles"],"mappings":"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","file":"test.cjs","sourcesContent":["/**\n * @akashjs/runtime/test — Test utilities.\n *\n * Provides mount(), fireEvent, and query helpers for testing\n * AkashJS components with Vitest (or any test runner).\n *\n * No TestBed, no module configuration, no compileComponents().\n * Just mount a component and query the resulting DOM.\n */\n\nimport { defineComponent } from './component.js';\nimport { provide } from './context.js';\nimport type { Component } from './component.js';\nimport type { InjectionKey } from './context.js';\n\n// --- Mount result ---\n\nexport interface MountResult {\n /** The root container element */\n container: HTMLElement;\n /** Unmount the component and clean up */\n unmount(): void;\n /** Find the first element whose text content contains the given string */\n getByText(text: string): HTMLElement;\n /** Find the first element with the given ARIA role */\n getByRole(role: string): HTMLElement;\n /** Find the first element with the given data-testid */\n getByTestId(id: string): HTMLElement;\n /** Query all elements matching a CSS selector */\n queryAll(selector: string): HTMLElement[];\n /** Query the first element matching a CSS selector, or null */\n query(selector: string): HTMLElement | null;\n}\n\n// --- Mount options ---\n\nexport interface MountOptions<P extends Record<string, unknown>> {\n /** Props to pass to the component */\n props?: P;\n /** Context values to provide (Map of InjectionKey -> value) */\n provide?: Map<InjectionKey<any>, any>;\n}\n\n// --- mount() ---\n\n/**\n * Mount a component into a detached DOM element for testing.\n *\n * ```ts\n * const { getByText, getByRole } = mount(Counter, { props: { initial: 5 } });\n * expect(getByText('Count: 5')).toBeTruthy();\n * ```\n */\nexport function mount<P extends Record<string, unknown> = Record<string, unknown>>(\n component: Component<P>,\n options?: MountOptions<P>,\n): MountResult {\n const container = document.createElement('div');\n container.setAttribute('data-akash-test-root', '');\n\n const props = (options?.props ?? {}) as P & { children?: undefined };\n const provides = options?.provide;\n\n let node: Node;\n\n if (provides && provides.size > 0) {\n // Wrap in a provider component to inject context values\n const Wrapper = defineComponent(() => {\n for (const [key, value] of provides) {\n provide(key, value);\n }\n return () => component(props);\n });\n node = Wrapper({});\n } else {\n node = component(props);\n }\n\n container.appendChild(node);\n\n // Attach to document so queries work properly\n document.body.appendChild(container);\n\n return {\n container,\n\n unmount() {\n container.remove();\n },\n\n getByText(text: string): HTMLElement {\n const el = findByText(container, text);\n if (!el) {\n throw new Error(\n `[AkashJS Test] Could not find element with text: \"${text}\"\\n` +\n ` Container HTML: ${container.innerHTML.slice(0, 200)}`,\n );\n }\n return el;\n },\n\n getByRole(role: string): HTMLElement {\n const el = container.querySelector<HTMLElement>(`[role=\"${role}\"]`)\n ?? findImplicitRole(container, role);\n if (!el) {\n throw new Error(\n `[AkashJS Test] Could not find element with role: \"${role}\"\\n` +\n ` Container HTML: ${container.innerHTML.slice(0, 200)}`,\n );\n }\n return el;\n },\n\n getByTestId(id: string): HTMLElement {\n const el = container.querySelector<HTMLElement>(`[data-testid=\"${id}\"]`);\n if (!el) {\n throw new Error(\n `[AkashJS Test] Could not find element with data-testid: \"${id}\"\\n` +\n ` Container HTML: ${container.innerHTML.slice(0, 200)}`,\n );\n }\n return el;\n },\n\n queryAll(selector: string): HTMLElement[] {\n return Array.from(container.querySelectorAll<HTMLElement>(selector));\n },\n\n query(selector: string): HTMLElement | null {\n return container.querySelector<HTMLElement>(selector);\n },\n };\n}\n\n// --- fireEvent ---\n\n/**\n * Fire DOM events on elements. Returns a promise that resolves\n * after a microtask, giving effects time to flush.\n *\n * ```ts\n * await fireEvent.click(button);\n * await fireEvent.input(input, 'hello');\n * ```\n */\nexport const fireEvent = {\n async click(el: HTMLElement): Promise<void> {\n el.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }));\n await tick();\n },\n\n async input(el: HTMLInputElement | HTMLTextAreaElement, value: string): Promise<void> {\n // Set the value property directly (as a user typing would)\n const nativeInputValueSetter = Object.getOwnPropertyDescriptor(\n el instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype,\n 'value',\n )?.set;\n\n if (nativeInputValueSetter) {\n nativeInputValueSetter.call(el, value);\n } else {\n (el as any).value = value;\n }\n\n el.dispatchEvent(new Event('input', { bubbles: true }));\n el.dispatchEvent(new Event('change', { bubbles: true }));\n await tick();\n },\n\n async submit(el: HTMLFormElement): Promise<void> {\n el.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }));\n await tick();\n },\n\n async focus(el: HTMLElement): Promise<void> {\n el.focus();\n el.dispatchEvent(new FocusEvent('focus', { bubbles: true }));\n await tick();\n },\n\n async blur(el: HTMLElement): Promise<void> {\n el.blur();\n el.dispatchEvent(new FocusEvent('blur', { bubbles: true }));\n await tick();\n },\n\n async keyDown(el: HTMLElement, key: string, options?: KeyboardEventInit): Promise<void> {\n el.dispatchEvent(new KeyboardEvent('keydown', { key, bubbles: true, ...options }));\n await tick();\n },\n\n async keyUp(el: HTMLElement, key: string, options?: KeyboardEventInit): Promise<void> {\n el.dispatchEvent(new KeyboardEvent('keyup', { key, bubbles: true, ...options }));\n await tick();\n },\n};\n\n// --- Internal helpers ---\n\n/** Wait one microtask for effects to flush */\nfunction tick(): Promise<void> {\n return new Promise((resolve) => queueMicrotask(resolve));\n}\n\n/** Walk DOM tree to find the deepest element containing text */\nfunction findByText(root: HTMLElement, text: string): HTMLElement | null {\n // Walk all descendant elements; keep the deepest match\n let best: HTMLElement | null = null;\n\n const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);\n let node: Node | null = walker.nextNode(); // skip root itself on first call\n\n while (node) {\n if (node instanceof HTMLElement && node.textContent?.includes(text)) {\n best = node; // deeper elements overwrite shallower ones\n }\n node = walker.nextNode();\n }\n\n // If no descendant matched, check root itself\n if (!best && root.textContent?.includes(text)) {\n best = root;\n }\n\n return best;\n}\n\nfunction getDirectTextContent(el: HTMLElement): string {\n let text = '';\n for (const child of el.childNodes) {\n if (child.nodeType === Node.TEXT_NODE) {\n text += child.textContent ?? '';\n }\n }\n return text;\n}\n\n/** Map of HTML tag names to their implicit ARIA roles */\nconst IMPLICIT_ROLES: Record<string, string[]> = {\n button: ['button'],\n a: ['link'],\n input: ['textbox', 'checkbox', 'radio', 'spinbutton', 'slider'],\n select: ['combobox', 'listbox'],\n textarea: ['textbox'],\n img: ['img'],\n form: ['form'],\n nav: ['navigation'],\n main: ['main'],\n header: ['banner'],\n footer: ['contentinfo'],\n aside: ['complementary'],\n section: ['region'],\n article: ['article'],\n ul: ['list'],\n ol: ['list'],\n li: ['listitem'],\n table: ['table'],\n th: ['columnheader'],\n td: ['cell'],\n h1: ['heading'],\n h2: ['heading'],\n h3: ['heading'],\n h4: ['heading'],\n h5: ['heading'],\n h6: ['heading'],\n};\n\nfunction findImplicitRole(root: HTMLElement, role: string): HTMLElement | null {\n // Find tags that implicitly have this role\n const tags: string[] = [];\n for (const [tag, roles] of Object.entries(IMPLICIT_ROLES)) {\n if (roles.includes(role)) tags.push(tag);\n }\n\n if (tags.length === 0) return null;\n\n const selector = tags.join(', ');\n return root.querySelector<HTMLElement>(selector);\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/test.ts"],"names":["mount","component","options","container","props","provides","node","defineComponent","key","value","provide","text","el","findByText","role","findImplicitRole","id","selector","fireEvent","tick","nativeInputValueSetter","resolve","root","best","walker","IMPLICIT_ROLES","tags","tag","roles","waitFor","assertion","timeout","interval","start","err","r","waitForElement","found","createTestSignal","initialValue","inner","signal","history","setCount","read","fn","newVal"],"mappings":"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","file":"test.cjs","sourcesContent":["/**\n * @akashjs/runtime/test — Test utilities.\n *\n * Provides mount(), fireEvent, and query helpers for testing\n * AkashJS components with Vitest (or any test runner).\n *\n * No TestBed, no module configuration, no compileComponents().\n * Just mount a component and query the resulting DOM.\n */\n\nimport { defineComponent } from './component.js';\nimport { provide } from './context.js';\nimport { signal } from './signals.js';\nimport { flushSync } from './scheduler.js';\nimport type { Signal } from './signals.js';\nimport type { Component } from './component.js';\nimport type { InjectionKey } from './context.js';\n\n// --- Mount result ---\n\nexport interface MountResult {\n /** The root container element */\n container: HTMLElement;\n /** Unmount the component and clean up */\n unmount(): void;\n /** Find the first element whose text content contains the given string */\n getByText(text: string): HTMLElement;\n /** Find the first element with the given ARIA role */\n getByRole(role: string): HTMLElement;\n /** Find the first element with the given data-testid */\n getByTestId(id: string): HTMLElement;\n /** Query all elements matching a CSS selector */\n queryAll(selector: string): HTMLElement[];\n /** Query the first element matching a CSS selector, or null */\n query(selector: string): HTMLElement | null;\n}\n\n// --- Mount options ---\n\nexport interface MountOptions<P extends Record<string, unknown>> {\n /** Props to pass to the component */\n props?: P;\n /** Context values to provide (Map of InjectionKey -> value) */\n provide?: Map<InjectionKey<any>, any>;\n}\n\n// --- mount() ---\n\n/**\n * Mount a component into a detached DOM element for testing.\n *\n * ```ts\n * const { getByText, getByRole } = mount(Counter, { props: { initial: 5 } });\n * expect(getByText('Count: 5')).toBeTruthy();\n * ```\n */\nexport function mount<P extends Record<string, unknown> = Record<string, unknown>>(\n component: Component<P>,\n options?: MountOptions<P>,\n): MountResult {\n const container = document.createElement('div');\n container.setAttribute('data-akash-test-root', '');\n\n const props = (options?.props ?? {}) as P & { children?: undefined };\n const provides = options?.provide;\n\n let node: Node;\n\n if (provides && provides.size > 0) {\n // Wrap in a provider component to inject context values\n const Wrapper = defineComponent(() => {\n for (const [key, value] of provides) {\n provide(key, value);\n }\n return () => component(props);\n });\n node = Wrapper({});\n } else {\n node = component(props);\n }\n\n container.appendChild(node);\n\n // Attach to document so queries work properly\n document.body.appendChild(container);\n\n return {\n container,\n\n unmount() {\n container.remove();\n },\n\n getByText(text: string): HTMLElement {\n const el = findByText(container, text);\n if (!el) {\n throw new Error(\n `[AkashJS Test] Could not find element with text: \"${text}\"\\n` +\n ` Container HTML: ${container.innerHTML.slice(0, 200)}`,\n );\n }\n return el;\n },\n\n getByRole(role: string): HTMLElement {\n const el = container.querySelector<HTMLElement>(`[role=\"${role}\"]`)\n ?? findImplicitRole(container, role);\n if (!el) {\n throw new Error(\n `[AkashJS Test] Could not find element with role: \"${role}\"\\n` +\n ` Container HTML: ${container.innerHTML.slice(0, 200)}`,\n );\n }\n return el;\n },\n\n getByTestId(id: string): HTMLElement {\n const el = container.querySelector<HTMLElement>(`[data-testid=\"${id}\"]`);\n if (!el) {\n throw new Error(\n `[AkashJS Test] Could not find element with data-testid: \"${id}\"\\n` +\n ` Container HTML: ${container.innerHTML.slice(0, 200)}`,\n );\n }\n return el;\n },\n\n queryAll(selector: string): HTMLElement[] {\n return Array.from(container.querySelectorAll<HTMLElement>(selector));\n },\n\n query(selector: string): HTMLElement | null {\n return container.querySelector<HTMLElement>(selector);\n },\n };\n}\n\n// --- fireEvent ---\n\n/**\n * Fire DOM events on elements. Returns a promise that resolves\n * after a microtask, giving effects time to flush.\n *\n * ```ts\n * await fireEvent.click(button);\n * await fireEvent.input(input, 'hello');\n * ```\n */\nexport const fireEvent = {\n async click(el: HTMLElement): Promise<void> {\n el.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }));\n await tick();\n },\n\n async input(el: HTMLInputElement | HTMLTextAreaElement, value: string): Promise<void> {\n // Set the value property directly (as a user typing would)\n const nativeInputValueSetter = Object.getOwnPropertyDescriptor(\n el instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype,\n 'value',\n )?.set;\n\n if (nativeInputValueSetter) {\n nativeInputValueSetter.call(el, value);\n } else {\n (el as any).value = value;\n }\n\n el.dispatchEvent(new Event('input', { bubbles: true }));\n el.dispatchEvent(new Event('change', { bubbles: true }));\n await tick();\n },\n\n async submit(el: HTMLFormElement): Promise<void> {\n el.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }));\n await tick();\n },\n\n async focus(el: HTMLElement): Promise<void> {\n el.focus();\n el.dispatchEvent(new FocusEvent('focus', { bubbles: true }));\n await tick();\n },\n\n async blur(el: HTMLElement): Promise<void> {\n el.blur();\n el.dispatchEvent(new FocusEvent('blur', { bubbles: true }));\n await tick();\n },\n\n async keyDown(el: HTMLElement, key: string, options?: KeyboardEventInit): Promise<void> {\n el.dispatchEvent(new KeyboardEvent('keydown', { key, bubbles: true, ...options }));\n await tick();\n },\n\n async keyUp(el: HTMLElement, key: string, options?: KeyboardEventInit): Promise<void> {\n el.dispatchEvent(new KeyboardEvent('keyup', { key, bubbles: true, ...options }));\n await tick();\n },\n};\n\n// --- Internal helpers ---\n\n/** Wait one microtask for effects to flush */\nfunction tick(): Promise<void> {\n return new Promise((resolve) => queueMicrotask(resolve));\n}\n\n/** Walk DOM tree to find the deepest element containing text */\nfunction findByText(root: HTMLElement, text: string): HTMLElement | null {\n // Walk all descendant elements; keep the deepest match\n let best: HTMLElement | null = null;\n\n const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);\n let node: Node | null = walker.nextNode(); // skip root itself on first call\n\n while (node) {\n if (node instanceof HTMLElement && node.textContent?.includes(text)) {\n best = node; // deeper elements overwrite shallower ones\n }\n node = walker.nextNode();\n }\n\n // If no descendant matched, check root itself\n if (!best && root.textContent?.includes(text)) {\n best = root;\n }\n\n return best;\n}\n\nfunction getDirectTextContent(el: HTMLElement): string {\n let text = '';\n for (const child of el.childNodes) {\n if (child.nodeType === Node.TEXT_NODE) {\n text += child.textContent ?? '';\n }\n }\n return text;\n}\n\n/** Map of HTML tag names to their implicit ARIA roles */\nconst IMPLICIT_ROLES: Record<string, string[]> = {\n button: ['button'],\n a: ['link'],\n input: ['textbox', 'checkbox', 'radio', 'spinbutton', 'slider'],\n select: ['combobox', 'listbox'],\n textarea: ['textbox'],\n img: ['img'],\n form: ['form'],\n nav: ['navigation'],\n main: ['main'],\n header: ['banner'],\n footer: ['contentinfo'],\n aside: ['complementary'],\n section: ['region'],\n article: ['article'],\n ul: ['list'],\n ol: ['list'],\n li: ['listitem'],\n table: ['table'],\n th: ['columnheader'],\n td: ['cell'],\n h1: ['heading'],\n h2: ['heading'],\n h3: ['heading'],\n h4: ['heading'],\n h5: ['heading'],\n h6: ['heading'],\n};\n\nfunction findImplicitRole(root: HTMLElement, role: string): HTMLElement | null {\n // Find tags that implicitly have this role\n const tags: string[] = [];\n for (const [tag, roles] of Object.entries(IMPLICIT_ROLES)) {\n if (roles.includes(role)) tags.push(tag);\n }\n\n if (tags.length === 0) return null;\n\n const selector = tags.join(', ');\n return root.querySelector<HTMLElement>(selector);\n}\n\n// =========================================================================\n// Async helpers\n// =========================================================================\n\nexport interface WaitForOptions {\n /** Timeout in ms (default: 1000) */\n timeout?: number;\n /** Poll interval in ms (default: 50) */\n interval?: number;\n}\n\n/**\n * Wait for an assertion to pass. Retries until it doesn't throw or times out.\n *\n * ```ts\n * await waitFor(() => expect(getByText('loaded')).toBeTruthy());\n * ```\n */\nexport async function waitFor(\n assertion: () => void | Promise<void>,\n options?: WaitForOptions,\n): Promise<void> {\n const { timeout = 1000, interval = 50 } = options ?? {};\n const start = Date.now();\n\n while (true) {\n try {\n await assertion();\n return;\n } catch (err) {\n if (Date.now() - start >= timeout) throw err;\n await new Promise(r => setTimeout(r, interval));\n }\n }\n}\n\n/**\n * Wait for an element matching a selector to appear in the container.\n *\n * ```ts\n * const el = await waitForElement(container, '.loaded');\n * ```\n */\nexport async function waitForElement(\n container: HTMLElement,\n selector: string,\n options?: WaitForOptions,\n): Promise<HTMLElement> {\n let found: HTMLElement | null = null;\n await waitFor(() => {\n found = container.querySelector<HTMLElement>(selector);\n if (!found) throw new Error(`Element \"${selector}\" not found`);\n }, options);\n return found!;\n}\n\n/**\n * Synchronously flush all pending effects. Use after signal writes\n * when you need the DOM to update before asserting.\n *\n * ```ts\n * count.set(5);\n * flush();\n * expect(getByText('5')).toBeTruthy();\n * ```\n */\nexport { flushSync as flush };\n\n// =========================================================================\n// Signal test helpers\n// =========================================================================\n\nexport interface TestSignal<T> extends Signal<T> {\n /** History of all values set on this signal (including initial) */\n history: T[];\n /** Number of times set() or update() was called */\n setCount: number;\n /** Reset history and set count */\n resetHistory(): void;\n}\n\n/**\n * Create a signal with test inspection capabilities.\n *\n * ```ts\n * const count = createTestSignal(0);\n * count.set(1);\n * count.set(2);\n * expect(count.history).toEqual([0, 1, 2]);\n * expect(count.setCount).toBe(2);\n * ```\n */\nexport function createTestSignal<T>(initialValue: T): TestSignal<T> {\n const inner = signal(initialValue);\n const history: T[] = [initialValue];\n let setCount = 0;\n\n const read = (() => inner()) as TestSignal<T>;\n\n read.set = (value: T) => {\n inner.set(value);\n history.push(value);\n setCount++;\n };\n\n read.update = (fn: (prev: T) => T) => {\n const newVal = fn(inner.peek());\n read.set(newVal);\n };\n\n read.peek = () => inner.peek();\n\n Object.defineProperty(read, 'history', { get: () => [...history] });\n Object.defineProperty(read, 'setCount', { get: () => setCount });\n\n read.resetHistory = () => {\n history.length = 0;\n history.push(inner.peek());\n setCount = 0;\n };\n\n return read;\n}\n"]}
|
package/dist/test.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {B,h}from'./chunk-367DJVST.js';import'./chunk-TWA4T6PX.js';function
|
|
2
|
-
Container HTML: ${
|
|
3
|
-
Container HTML: ${
|
|
4
|
-
Container HTML: ${
|
|
1
|
+
import {B,h}from'./chunk-367DJVST.js';import {l}from'./chunk-TWA4T6PX.js';export{b as flush}from'./chunk-TWA4T6PX.js';function H(t,n){let e=document.createElement("div");e.setAttribute("data-akash-test-root","");let i=n?.props??{},r=n?.provide,s;return r&&r.size>0?s=B(()=>{for(let[l,d]of r)h(l,d);return ()=>t(i)})({}):s=t(i),e.appendChild(s),document.body.appendChild(e),{container:e,unmount(){e.remove();},getByText(o){let l=T(e,o);if(!l)throw new Error(`[AkashJS Test] Could not find element with text: "${o}"
|
|
2
|
+
Container HTML: ${e.innerHTML.slice(0,200)}`);return l},getByRole(o){let l=e.querySelector(`[role="${o}"]`)??f(e,o);if(!l)throw new Error(`[AkashJS Test] Could not find element with role: "${o}"
|
|
3
|
+
Container HTML: ${e.innerHTML.slice(0,200)}`);return l},getByTestId(o){let l=e.querySelector(`[data-testid="${o}"]`);if(!l)throw new Error(`[AkashJS Test] Could not find element with data-testid: "${o}"
|
|
4
|
+
Container HTML: ${e.innerHTML.slice(0,200)}`);return l},queryAll(o){return Array.from(e.querySelectorAll(o))},query(o){return e.querySelector(o)}}}var L={async click(t){t.dispatchEvent(new MouseEvent("click",{bubbles:true,cancelable:true})),await a();},async input(t,n){let e=Object.getOwnPropertyDescriptor(t instanceof HTMLTextAreaElement?HTMLTextAreaElement.prototype:HTMLInputElement.prototype,"value")?.set;e?e.call(t,n):t.value=n,t.dispatchEvent(new Event("input",{bubbles:true})),t.dispatchEvent(new Event("change",{bubbles:true})),await a();},async submit(t){t.dispatchEvent(new Event("submit",{bubbles:true,cancelable:true})),await a();},async focus(t){t.focus(),t.dispatchEvent(new FocusEvent("focus",{bubbles:true})),await a();},async blur(t){t.blur(),t.dispatchEvent(new FocusEvent("blur",{bubbles:true})),await a();},async keyDown(t,n,e){t.dispatchEvent(new KeyboardEvent("keydown",{key:n,bubbles:true,...e})),await a();},async keyUp(t,n,e){t.dispatchEvent(new KeyboardEvent("keyup",{key:n,bubbles:true,...e})),await a();}};function a(){return new Promise(t=>queueMicrotask(t))}function T(t,n){let e=null,i=document.createTreeWalker(t,NodeFilter.SHOW_ELEMENT),r=i.nextNode();for(;r;)r instanceof HTMLElement&&r.textContent?.includes(n)&&(e=r),r=i.nextNode();return !e&&t.textContent?.includes(n)&&(e=t),e}var E={button:["button"],a:["link"],input:["textbox","checkbox","radio","spinbutton","slider"],select:["combobox","listbox"],textarea:["textbox"],img:["img"],form:["form"],nav:["navigation"],main:["main"],header:["banner"],footer:["contentinfo"],aside:["complementary"],section:["region"],article:["article"],ul:["list"],ol:["list"],li:["listitem"],table:["table"],th:["columnheader"],td:["cell"],h1:["heading"],h2:["heading"],h3:["heading"],h4:["heading"],h5:["heading"],h6:["heading"]};function f(t,n){let e=[];for(let[r,s]of Object.entries(E))s.includes(n)&&e.push(r);if(e.length===0)return null;let i=e.join(", ");return t.querySelector(i)}async function y(t,n){let{timeout:e=1e3,interval:i=50}=n??{},r=Date.now();for(;;)try{await t();return}catch(s){if(Date.now()-r>=e)throw s;await new Promise(o=>setTimeout(o,i));}}async function w(t,n,e){let i=null;return await y(()=>{if(i=t.querySelector(n),!i)throw new Error(`Element "${n}" not found`)},e),i}function v(t){let n=l(t),e=[t],i=0,r=(()=>n());return r.set=s=>{n.set(s),e.push(s),i++;},r.update=s=>{let o=s(n.peek());r.set(o);},r.peek=()=>n.peek(),Object.defineProperty(r,"history",{get:()=>[...e]}),Object.defineProperty(r,"setCount",{get:()=>i}),r.resetHistory=()=>{e.length=0,e.push(n.peek()),i=0;},r}export{v as createTestSignal,L as fireEvent,H as mount,y as waitFor,w as waitForElement};//# sourceMappingURL=test.js.map
|
|
5
5
|
//# sourceMappingURL=test.js.map
|
package/dist/test.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/test.ts"],"names":["mount","component","options","container","props","provides","node","defineComponent","key","value","provide","text","el","findByText","role","findImplicitRole","id","selector","fireEvent","tick","nativeInputValueSetter","resolve","root","best","walker","IMPLICIT_ROLES","tags","tag","roles"],"mappings":"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","file":"test.js","sourcesContent":["/**\n * @akashjs/runtime/test — Test utilities.\n *\n * Provides mount(), fireEvent, and query helpers for testing\n * AkashJS components with Vitest (or any test runner).\n *\n * No TestBed, no module configuration, no compileComponents().\n * Just mount a component and query the resulting DOM.\n */\n\nimport { defineComponent } from './component.js';\nimport { provide } from './context.js';\nimport type { Component } from './component.js';\nimport type { InjectionKey } from './context.js';\n\n// --- Mount result ---\n\nexport interface MountResult {\n /** The root container element */\n container: HTMLElement;\n /** Unmount the component and clean up */\n unmount(): void;\n /** Find the first element whose text content contains the given string */\n getByText(text: string): HTMLElement;\n /** Find the first element with the given ARIA role */\n getByRole(role: string): HTMLElement;\n /** Find the first element with the given data-testid */\n getByTestId(id: string): HTMLElement;\n /** Query all elements matching a CSS selector */\n queryAll(selector: string): HTMLElement[];\n /** Query the first element matching a CSS selector, or null */\n query(selector: string): HTMLElement | null;\n}\n\n// --- Mount options ---\n\nexport interface MountOptions<P extends Record<string, unknown>> {\n /** Props to pass to the component */\n props?: P;\n /** Context values to provide (Map of InjectionKey -> value) */\n provide?: Map<InjectionKey<any>, any>;\n}\n\n// --- mount() ---\n\n/**\n * Mount a component into a detached DOM element for testing.\n *\n * ```ts\n * const { getByText, getByRole } = mount(Counter, { props: { initial: 5 } });\n * expect(getByText('Count: 5')).toBeTruthy();\n * ```\n */\nexport function mount<P extends Record<string, unknown> = Record<string, unknown>>(\n component: Component<P>,\n options?: MountOptions<P>,\n): MountResult {\n const container = document.createElement('div');\n container.setAttribute('data-akash-test-root', '');\n\n const props = (options?.props ?? {}) as P & { children?: undefined };\n const provides = options?.provide;\n\n let node: Node;\n\n if (provides && provides.size > 0) {\n // Wrap in a provider component to inject context values\n const Wrapper = defineComponent(() => {\n for (const [key, value] of provides) {\n provide(key, value);\n }\n return () => component(props);\n });\n node = Wrapper({});\n } else {\n node = component(props);\n }\n\n container.appendChild(node);\n\n // Attach to document so queries work properly\n document.body.appendChild(container);\n\n return {\n container,\n\n unmount() {\n container.remove();\n },\n\n getByText(text: string): HTMLElement {\n const el = findByText(container, text);\n if (!el) {\n throw new Error(\n `[AkashJS Test] Could not find element with text: \"${text}\"\\n` +\n ` Container HTML: ${container.innerHTML.slice(0, 200)}`,\n );\n }\n return el;\n },\n\n getByRole(role: string): HTMLElement {\n const el = container.querySelector<HTMLElement>(`[role=\"${role}\"]`)\n ?? findImplicitRole(container, role);\n if (!el) {\n throw new Error(\n `[AkashJS Test] Could not find element with role: \"${role}\"\\n` +\n ` Container HTML: ${container.innerHTML.slice(0, 200)}`,\n );\n }\n return el;\n },\n\n getByTestId(id: string): HTMLElement {\n const el = container.querySelector<HTMLElement>(`[data-testid=\"${id}\"]`);\n if (!el) {\n throw new Error(\n `[AkashJS Test] Could not find element with data-testid: \"${id}\"\\n` +\n ` Container HTML: ${container.innerHTML.slice(0, 200)}`,\n );\n }\n return el;\n },\n\n queryAll(selector: string): HTMLElement[] {\n return Array.from(container.querySelectorAll<HTMLElement>(selector));\n },\n\n query(selector: string): HTMLElement | null {\n return container.querySelector<HTMLElement>(selector);\n },\n };\n}\n\n// --- fireEvent ---\n\n/**\n * Fire DOM events on elements. Returns a promise that resolves\n * after a microtask, giving effects time to flush.\n *\n * ```ts\n * await fireEvent.click(button);\n * await fireEvent.input(input, 'hello');\n * ```\n */\nexport const fireEvent = {\n async click(el: HTMLElement): Promise<void> {\n el.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }));\n await tick();\n },\n\n async input(el: HTMLInputElement | HTMLTextAreaElement, value: string): Promise<void> {\n // Set the value property directly (as a user typing would)\n const nativeInputValueSetter = Object.getOwnPropertyDescriptor(\n el instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype,\n 'value',\n )?.set;\n\n if (nativeInputValueSetter) {\n nativeInputValueSetter.call(el, value);\n } else {\n (el as any).value = value;\n }\n\n el.dispatchEvent(new Event('input', { bubbles: true }));\n el.dispatchEvent(new Event('change', { bubbles: true }));\n await tick();\n },\n\n async submit(el: HTMLFormElement): Promise<void> {\n el.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }));\n await tick();\n },\n\n async focus(el: HTMLElement): Promise<void> {\n el.focus();\n el.dispatchEvent(new FocusEvent('focus', { bubbles: true }));\n await tick();\n },\n\n async blur(el: HTMLElement): Promise<void> {\n el.blur();\n el.dispatchEvent(new FocusEvent('blur', { bubbles: true }));\n await tick();\n },\n\n async keyDown(el: HTMLElement, key: string, options?: KeyboardEventInit): Promise<void> {\n el.dispatchEvent(new KeyboardEvent('keydown', { key, bubbles: true, ...options }));\n await tick();\n },\n\n async keyUp(el: HTMLElement, key: string, options?: KeyboardEventInit): Promise<void> {\n el.dispatchEvent(new KeyboardEvent('keyup', { key, bubbles: true, ...options }));\n await tick();\n },\n};\n\n// --- Internal helpers ---\n\n/** Wait one microtask for effects to flush */\nfunction tick(): Promise<void> {\n return new Promise((resolve) => queueMicrotask(resolve));\n}\n\n/** Walk DOM tree to find the deepest element containing text */\nfunction findByText(root: HTMLElement, text: string): HTMLElement | null {\n // Walk all descendant elements; keep the deepest match\n let best: HTMLElement | null = null;\n\n const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);\n let node: Node | null = walker.nextNode(); // skip root itself on first call\n\n while (node) {\n if (node instanceof HTMLElement && node.textContent?.includes(text)) {\n best = node; // deeper elements overwrite shallower ones\n }\n node = walker.nextNode();\n }\n\n // If no descendant matched, check root itself\n if (!best && root.textContent?.includes(text)) {\n best = root;\n }\n\n return best;\n}\n\nfunction getDirectTextContent(el: HTMLElement): string {\n let text = '';\n for (const child of el.childNodes) {\n if (child.nodeType === Node.TEXT_NODE) {\n text += child.textContent ?? '';\n }\n }\n return text;\n}\n\n/** Map of HTML tag names to their implicit ARIA roles */\nconst IMPLICIT_ROLES: Record<string, string[]> = {\n button: ['button'],\n a: ['link'],\n input: ['textbox', 'checkbox', 'radio', 'spinbutton', 'slider'],\n select: ['combobox', 'listbox'],\n textarea: ['textbox'],\n img: ['img'],\n form: ['form'],\n nav: ['navigation'],\n main: ['main'],\n header: ['banner'],\n footer: ['contentinfo'],\n aside: ['complementary'],\n section: ['region'],\n article: ['article'],\n ul: ['list'],\n ol: ['list'],\n li: ['listitem'],\n table: ['table'],\n th: ['columnheader'],\n td: ['cell'],\n h1: ['heading'],\n h2: ['heading'],\n h3: ['heading'],\n h4: ['heading'],\n h5: ['heading'],\n h6: ['heading'],\n};\n\nfunction findImplicitRole(root: HTMLElement, role: string): HTMLElement | null {\n // Find tags that implicitly have this role\n const tags: string[] = [];\n for (const [tag, roles] of Object.entries(IMPLICIT_ROLES)) {\n if (roles.includes(role)) tags.push(tag);\n }\n\n if (tags.length === 0) return null;\n\n const selector = tags.join(', ');\n return root.querySelector<HTMLElement>(selector);\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/test.ts"],"names":["mount","component","options","container","props","provides","node","defineComponent","key","value","provide","text","el","findByText","role","findImplicitRole","id","selector","fireEvent","tick","nativeInputValueSetter","resolve","root","best","walker","IMPLICIT_ROLES","tags","tag","roles","waitFor","assertion","timeout","interval","start","err","r","waitForElement","found","createTestSignal","initialValue","inner","signal","history","setCount","read","fn","newVal"],"mappings":"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","file":"test.js","sourcesContent":["/**\n * @akashjs/runtime/test — Test utilities.\n *\n * Provides mount(), fireEvent, and query helpers for testing\n * AkashJS components with Vitest (or any test runner).\n *\n * No TestBed, no module configuration, no compileComponents().\n * Just mount a component and query the resulting DOM.\n */\n\nimport { defineComponent } from './component.js';\nimport { provide } from './context.js';\nimport { signal } from './signals.js';\nimport { flushSync } from './scheduler.js';\nimport type { Signal } from './signals.js';\nimport type { Component } from './component.js';\nimport type { InjectionKey } from './context.js';\n\n// --- Mount result ---\n\nexport interface MountResult {\n /** The root container element */\n container: HTMLElement;\n /** Unmount the component and clean up */\n unmount(): void;\n /** Find the first element whose text content contains the given string */\n getByText(text: string): HTMLElement;\n /** Find the first element with the given ARIA role */\n getByRole(role: string): HTMLElement;\n /** Find the first element with the given data-testid */\n getByTestId(id: string): HTMLElement;\n /** Query all elements matching a CSS selector */\n queryAll(selector: string): HTMLElement[];\n /** Query the first element matching a CSS selector, or null */\n query(selector: string): HTMLElement | null;\n}\n\n// --- Mount options ---\n\nexport interface MountOptions<P extends Record<string, unknown>> {\n /** Props to pass to the component */\n props?: P;\n /** Context values to provide (Map of InjectionKey -> value) */\n provide?: Map<InjectionKey<any>, any>;\n}\n\n// --- mount() ---\n\n/**\n * Mount a component into a detached DOM element for testing.\n *\n * ```ts\n * const { getByText, getByRole } = mount(Counter, { props: { initial: 5 } });\n * expect(getByText('Count: 5')).toBeTruthy();\n * ```\n */\nexport function mount<P extends Record<string, unknown> = Record<string, unknown>>(\n component: Component<P>,\n options?: MountOptions<P>,\n): MountResult {\n const container = document.createElement('div');\n container.setAttribute('data-akash-test-root', '');\n\n const props = (options?.props ?? {}) as P & { children?: undefined };\n const provides = options?.provide;\n\n let node: Node;\n\n if (provides && provides.size > 0) {\n // Wrap in a provider component to inject context values\n const Wrapper = defineComponent(() => {\n for (const [key, value] of provides) {\n provide(key, value);\n }\n return () => component(props);\n });\n node = Wrapper({});\n } else {\n node = component(props);\n }\n\n container.appendChild(node);\n\n // Attach to document so queries work properly\n document.body.appendChild(container);\n\n return {\n container,\n\n unmount() {\n container.remove();\n },\n\n getByText(text: string): HTMLElement {\n const el = findByText(container, text);\n if (!el) {\n throw new Error(\n `[AkashJS Test] Could not find element with text: \"${text}\"\\n` +\n ` Container HTML: ${container.innerHTML.slice(0, 200)}`,\n );\n }\n return el;\n },\n\n getByRole(role: string): HTMLElement {\n const el = container.querySelector<HTMLElement>(`[role=\"${role}\"]`)\n ?? findImplicitRole(container, role);\n if (!el) {\n throw new Error(\n `[AkashJS Test] Could not find element with role: \"${role}\"\\n` +\n ` Container HTML: ${container.innerHTML.slice(0, 200)}`,\n );\n }\n return el;\n },\n\n getByTestId(id: string): HTMLElement {\n const el = container.querySelector<HTMLElement>(`[data-testid=\"${id}\"]`);\n if (!el) {\n throw new Error(\n `[AkashJS Test] Could not find element with data-testid: \"${id}\"\\n` +\n ` Container HTML: ${container.innerHTML.slice(0, 200)}`,\n );\n }\n return el;\n },\n\n queryAll(selector: string): HTMLElement[] {\n return Array.from(container.querySelectorAll<HTMLElement>(selector));\n },\n\n query(selector: string): HTMLElement | null {\n return container.querySelector<HTMLElement>(selector);\n },\n };\n}\n\n// --- fireEvent ---\n\n/**\n * Fire DOM events on elements. Returns a promise that resolves\n * after a microtask, giving effects time to flush.\n *\n * ```ts\n * await fireEvent.click(button);\n * await fireEvent.input(input, 'hello');\n * ```\n */\nexport const fireEvent = {\n async click(el: HTMLElement): Promise<void> {\n el.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }));\n await tick();\n },\n\n async input(el: HTMLInputElement | HTMLTextAreaElement, value: string): Promise<void> {\n // Set the value property directly (as a user typing would)\n const nativeInputValueSetter = Object.getOwnPropertyDescriptor(\n el instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype,\n 'value',\n )?.set;\n\n if (nativeInputValueSetter) {\n nativeInputValueSetter.call(el, value);\n } else {\n (el as any).value = value;\n }\n\n el.dispatchEvent(new Event('input', { bubbles: true }));\n el.dispatchEvent(new Event('change', { bubbles: true }));\n await tick();\n },\n\n async submit(el: HTMLFormElement): Promise<void> {\n el.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }));\n await tick();\n },\n\n async focus(el: HTMLElement): Promise<void> {\n el.focus();\n el.dispatchEvent(new FocusEvent('focus', { bubbles: true }));\n await tick();\n },\n\n async blur(el: HTMLElement): Promise<void> {\n el.blur();\n el.dispatchEvent(new FocusEvent('blur', { bubbles: true }));\n await tick();\n },\n\n async keyDown(el: HTMLElement, key: string, options?: KeyboardEventInit): Promise<void> {\n el.dispatchEvent(new KeyboardEvent('keydown', { key, bubbles: true, ...options }));\n await tick();\n },\n\n async keyUp(el: HTMLElement, key: string, options?: KeyboardEventInit): Promise<void> {\n el.dispatchEvent(new KeyboardEvent('keyup', { key, bubbles: true, ...options }));\n await tick();\n },\n};\n\n// --- Internal helpers ---\n\n/** Wait one microtask for effects to flush */\nfunction tick(): Promise<void> {\n return new Promise((resolve) => queueMicrotask(resolve));\n}\n\n/** Walk DOM tree to find the deepest element containing text */\nfunction findByText(root: HTMLElement, text: string): HTMLElement | null {\n // Walk all descendant elements; keep the deepest match\n let best: HTMLElement | null = null;\n\n const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);\n let node: Node | null = walker.nextNode(); // skip root itself on first call\n\n while (node) {\n if (node instanceof HTMLElement && node.textContent?.includes(text)) {\n best = node; // deeper elements overwrite shallower ones\n }\n node = walker.nextNode();\n }\n\n // If no descendant matched, check root itself\n if (!best && root.textContent?.includes(text)) {\n best = root;\n }\n\n return best;\n}\n\nfunction getDirectTextContent(el: HTMLElement): string {\n let text = '';\n for (const child of el.childNodes) {\n if (child.nodeType === Node.TEXT_NODE) {\n text += child.textContent ?? '';\n }\n }\n return text;\n}\n\n/** Map of HTML tag names to their implicit ARIA roles */\nconst IMPLICIT_ROLES: Record<string, string[]> = {\n button: ['button'],\n a: ['link'],\n input: ['textbox', 'checkbox', 'radio', 'spinbutton', 'slider'],\n select: ['combobox', 'listbox'],\n textarea: ['textbox'],\n img: ['img'],\n form: ['form'],\n nav: ['navigation'],\n main: ['main'],\n header: ['banner'],\n footer: ['contentinfo'],\n aside: ['complementary'],\n section: ['region'],\n article: ['article'],\n ul: ['list'],\n ol: ['list'],\n li: ['listitem'],\n table: ['table'],\n th: ['columnheader'],\n td: ['cell'],\n h1: ['heading'],\n h2: ['heading'],\n h3: ['heading'],\n h4: ['heading'],\n h5: ['heading'],\n h6: ['heading'],\n};\n\nfunction findImplicitRole(root: HTMLElement, role: string): HTMLElement | null {\n // Find tags that implicitly have this role\n const tags: string[] = [];\n for (const [tag, roles] of Object.entries(IMPLICIT_ROLES)) {\n if (roles.includes(role)) tags.push(tag);\n }\n\n if (tags.length === 0) return null;\n\n const selector = tags.join(', ');\n return root.querySelector<HTMLElement>(selector);\n}\n\n// =========================================================================\n// Async helpers\n// =========================================================================\n\nexport interface WaitForOptions {\n /** Timeout in ms (default: 1000) */\n timeout?: number;\n /** Poll interval in ms (default: 50) */\n interval?: number;\n}\n\n/**\n * Wait for an assertion to pass. Retries until it doesn't throw or times out.\n *\n * ```ts\n * await waitFor(() => expect(getByText('loaded')).toBeTruthy());\n * ```\n */\nexport async function waitFor(\n assertion: () => void | Promise<void>,\n options?: WaitForOptions,\n): Promise<void> {\n const { timeout = 1000, interval = 50 } = options ?? {};\n const start = Date.now();\n\n while (true) {\n try {\n await assertion();\n return;\n } catch (err) {\n if (Date.now() - start >= timeout) throw err;\n await new Promise(r => setTimeout(r, interval));\n }\n }\n}\n\n/**\n * Wait for an element matching a selector to appear in the container.\n *\n * ```ts\n * const el = await waitForElement(container, '.loaded');\n * ```\n */\nexport async function waitForElement(\n container: HTMLElement,\n selector: string,\n options?: WaitForOptions,\n): Promise<HTMLElement> {\n let found: HTMLElement | null = null;\n await waitFor(() => {\n found = container.querySelector<HTMLElement>(selector);\n if (!found) throw new Error(`Element \"${selector}\" not found`);\n }, options);\n return found!;\n}\n\n/**\n * Synchronously flush all pending effects. Use after signal writes\n * when you need the DOM to update before asserting.\n *\n * ```ts\n * count.set(5);\n * flush();\n * expect(getByText('5')).toBeTruthy();\n * ```\n */\nexport { flushSync as flush };\n\n// =========================================================================\n// Signal test helpers\n// =========================================================================\n\nexport interface TestSignal<T> extends Signal<T> {\n /** History of all values set on this signal (including initial) */\n history: T[];\n /** Number of times set() or update() was called */\n setCount: number;\n /** Reset history and set count */\n resetHistory(): void;\n}\n\n/**\n * Create a signal with test inspection capabilities.\n *\n * ```ts\n * const count = createTestSignal(0);\n * count.set(1);\n * count.set(2);\n * expect(count.history).toEqual([0, 1, 2]);\n * expect(count.setCount).toBe(2);\n * ```\n */\nexport function createTestSignal<T>(initialValue: T): TestSignal<T> {\n const inner = signal(initialValue);\n const history: T[] = [initialValue];\n let setCount = 0;\n\n const read = (() => inner()) as TestSignal<T>;\n\n read.set = (value: T) => {\n inner.set(value);\n history.push(value);\n setCount++;\n };\n\n read.update = (fn: (prev: T) => T) => {\n const newVal = fn(inner.peek());\n read.set(newVal);\n };\n\n read.peek = () => inner.peek();\n\n Object.defineProperty(read, 'history', { get: () => [...history] });\n Object.defineProperty(read, 'setCount', { get: () => setCount });\n\n read.resetHistory = () => {\n history.length = 0;\n history.push(inner.peek());\n setCount = 0;\n };\n\n return read;\n}\n"]}
|
package/package.json
CHANGED
package/dist/chunk-VAHJHMWE.js
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
var S={"&":"&","<":"<",">":">",'"':""","'":"'"};function u(e){return e.replace(/[&<>"']/g,n=>S[n])}var i=false;function f(){return i}async function m(e,n){i=true;try{let t=c(e,n??{});return a(t)}finally{i=false;}}function d(e,n){i=true;try{let t=c(e,n??{});return a(t)}finally{i=false;}}function g(e,n){return new ReadableStream({start(t){i=true;try{let r=c(e,n??{}),o=a(r);t.enqueue(o),t.close();}catch(r){t.error(r);}finally{i=false;}}})}function c(e,n){try{let t=e(n);if(t&&typeof t=="object"&&"type"in t)return t;if(typeof t=="string")return {type:"raw",html:t};if(typeof t=="function"){let r=t();if(r&&typeof r=="object"&&"type"in r)return r;if(typeof r=="string")return {type:"raw",html:r}}}catch{}return {type:"raw",html:`<!-- SSR placeholder for ${e.name||"component"} -->`}}function R(e,n,t){let r={};if(n)for(let[o,s]of Object.entries(n))s===true?r[o]="":s!==false&&s!=null&&(r[o]=String(s));return {type:"element",tag:e,attrs:r,children:t==null?[]:Array.isArray(t)?t:[t]}}function y(e){return {type:"text",content:e}}function x(e){return {type:"raw",html:e}}var l=new Set(["area","base","br","col","embed","hr","img","input","link","meta","param","source","track","wbr"]);function a(e){switch(e.type){case "text":return u(e.content);case "raw":return e.html;case "element":{let{tag:n,attrs:t,children:r}=e,o=`<${n}`;for(let[s,p]of Object.entries(t))p===""?o+=` ${s}`:o+=` ${s}="${u(p)}"`;if(l.has(n))return o+=" />",o;o+=">";for(let s of r)o+=a(s);return o+=`</${n}>`,o}}}function w(e){return e.map(a).join("")}export{u as a,f as b,m as c,d,g as e,R as f,y as g,x as h,a as i,w as j};//# sourceMappingURL=chunk-VAHJHMWE.js.map
|
|
2
|
-
//# sourceMappingURL=chunk-VAHJHMWE.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/ssr.ts"],"names":["ESCAPE_MAP","escapeHtml","str","ch","isSSR","isServerRendering","renderToString","component","props","node","renderComponent","nodeToHtml","renderToStringSync","renderToStream","controller","html","err","result","rendered","ssrElement","tag","attrs","children","processedAttrs","key","value","ssrText","content","ssrRaw","VOID_ELEMENTS","child","renderNodes","nodes"],"mappings":"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","file":"chunk-VAHJHMWE.js","sourcesContent":["/**\n * Server-Side Rendering.\n *\n * Renders components to HTML strings (or streams) on the server.\n * No DOM APIs are used — everything is string concatenation.\n *\n * - Effects do NOT run on the server (they are DOM-side only)\n * - Signals and computeds work synchronously\n * - Components run their setup + initial render, producing HTML\n */\n\nimport { signal, computed, untrack } from './signals.js';\nimport type { Component } from './component.js';\nimport type { AkashNode } from './types.js';\n\n// --- Escape helpers ---\n\nconst ESCAPE_MAP: Record<string, string> = {\n '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": ''',\n};\n\nexport function escapeHtml(str: string): string {\n return str.replace(/[&<>\"']/g, (ch) => ESCAPE_MAP[ch]);\n}\n\n// --- SSR context ---\n\nlet isSSR = false;\n\n/** Check if we're currently in an SSR render */\nexport function isServerRendering(): boolean {\n return isSSR;\n}\n\n// --- renderToString ---\n\n/**\n * Render a component to an HTML string on the server.\n *\n * ```ts\n * import { renderToString } from '@akashjs/runtime/ssr';\n * const html = await renderToString(App, { title: 'Home' });\n * ```\n */\nexport async function renderToString<P extends Record<string, unknown>>(\n component: Component<P>,\n props?: P,\n): Promise<string> {\n isSSR = true;\n try {\n const node = renderComponent(component, props ?? ({} as P));\n return nodeToHtml(node);\n } finally {\n isSSR = false;\n }\n}\n\n/**\n * Synchronous version of renderToString.\n */\nexport function renderToStringSync<P extends Record<string, unknown>>(\n component: Component<P>,\n props?: P,\n): string {\n isSSR = true;\n try {\n const node = renderComponent(component, props ?? ({} as P));\n return nodeToHtml(node);\n } finally {\n isSSR = false;\n }\n}\n\n// --- renderToStream ---\n\n/**\n * Render a component to a ReadableStream of HTML chunks.\n *\n * Useful for streaming SSR — flush HTML as data becomes available.\n *\n * ```ts\n * const stream = renderToStream(App, { title: 'Home' });\n * // Pipe to HTTP response\n * ```\n */\nexport function renderToStream<P extends Record<string, unknown>>(\n component: Component<P>,\n props?: P,\n): ReadableStream<string> {\n return new ReadableStream<string>({\n start(controller) {\n isSSR = true;\n try {\n const node = renderComponent(component, props ?? ({} as P));\n const html = nodeToHtml(node);\n\n // For now, emit as a single chunk.\n // With Suspense boundaries, each resolved boundary\n // would flush a separate chunk.\n controller.enqueue(html);\n controller.close();\n } catch (err) {\n controller.error(err);\n } finally {\n isSSR = false;\n }\n },\n });\n}\n\n// --- SSR rendering internals ---\n\n/** Render a component, getting back the AkashNode tree */\nfunction renderComponent<P extends Record<string, unknown>>(\n component: Component<P>,\n props: P,\n): SSRNode {\n // Try calling the component — if it returns an SSR node tree, use it directly\n try {\n const result = (component as Function)(props);\n // If result is an SSR node (has type: 'element'|'text'|'raw'), use it\n if (result && typeof result === 'object' && 'type' in result) {\n return result as SSRNode;\n }\n // If result is a string (server-compiled output), wrap as raw HTML\n if (typeof result === 'string') {\n return { type: 'raw', html: result };\n }\n // If result is a render function (defineComponent pattern), call it\n if (typeof result === 'function') {\n const rendered = result();\n if (rendered && typeof rendered === 'object' && 'type' in rendered) {\n return rendered as SSRNode;\n }\n if (typeof rendered === 'string') {\n return { type: 'raw', html: rendered };\n }\n }\n } catch {\n // Component uses DOM APIs — can't render on server\n }\n\n return { type: 'raw', html: `<!-- SSR placeholder for ${component.name || 'component'} -->` };\n}\n\n// --- SSR Node types ---\n\nexport interface SSRElement {\n type: 'element';\n tag: string;\n attrs: Record<string, string>;\n children: SSRNode[];\n selfClosing?: boolean;\n}\n\nexport interface SSRText {\n type: 'text';\n content: string;\n}\n\nexport interface SSRRaw {\n type: 'raw';\n html: string;\n}\n\nexport type SSRNode = SSRElement | SSRText | SSRRaw;\n\n// --- SSR DOM-less helpers ---\n\n/**\n * Create an SSR element (replaces document.createElement on the server).\n */\nexport function ssrElement(\n tag: string,\n attrs?: Record<string, string | boolean>,\n children?: SSRNode | SSRNode[],\n): SSRElement {\n const processedAttrs: Record<string, string> = {};\n if (attrs) {\n for (const [key, value] of Object.entries(attrs)) {\n if (value === true) {\n processedAttrs[key] = '';\n } else if (value !== false && value != null) {\n processedAttrs[key] = String(value);\n }\n }\n }\n\n return {\n type: 'element',\n tag,\n attrs: processedAttrs,\n children: children == null ? [] : Array.isArray(children) ? children : [children],\n };\n}\n\n/**\n * Create an SSR text node.\n */\nexport function ssrText(content: string): SSRText {\n return { type: 'text', content };\n}\n\n/**\n * Create a raw HTML SSR node (no escaping).\n */\nexport function ssrRaw(html: string): SSRRaw {\n return { type: 'raw', html };\n}\n\n// --- Serialization ---\n\nconst VOID_ELEMENTS = new Set([\n 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',\n 'link', 'meta', 'param', 'source', 'track', 'wbr',\n]);\n\n/**\n * Serialize an SSR node tree to an HTML string.\n */\nexport function nodeToHtml(node: SSRNode): string {\n switch (node.type) {\n case 'text':\n return escapeHtml(node.content);\n\n case 'raw':\n return node.html;\n\n case 'element': {\n const { tag, attrs, children } = node;\n let html = `<${tag}`;\n\n for (const [key, value] of Object.entries(attrs)) {\n if (value === '') {\n html += ` ${key}`;\n } else {\n html += ` ${key}=\"${escapeHtml(value)}\"`;\n }\n }\n\n if (VOID_ELEMENTS.has(tag)) {\n html += ' />';\n return html;\n }\n\n html += '>';\n\n for (const child of children) {\n html += nodeToHtml(child);\n }\n\n html += `</${tag}>`;\n return html;\n }\n }\n}\n\n/**\n * Render a tree of SSR nodes (convenience for arrays).\n */\nexport function renderNodes(nodes: SSRNode[]): string {\n return nodes.map(nodeToHtml).join('');\n}\n"]}
|
package/dist/chunk-YU4YSPD6.cjs
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
'use strict';var S={"&":"&","<":"<",">":">",'"':""","'":"'"};function u(e){return e.replace(/[&<>"']/g,n=>S[n])}var i=false;function f(){return i}async function m(e,n){i=true;try{let t=c(e,n??{});return a(t)}finally{i=false;}}function d(e,n){i=true;try{let t=c(e,n??{});return a(t)}finally{i=false;}}function g(e,n){return new ReadableStream({start(t){i=true;try{let r=c(e,n??{}),o=a(r);t.enqueue(o),t.close();}catch(r){t.error(r);}finally{i=false;}}})}function c(e,n){try{let t=e(n);if(t&&typeof t=="object"&&"type"in t)return t;if(typeof t=="string")return {type:"raw",html:t};if(typeof t=="function"){let r=t();if(r&&typeof r=="object"&&"type"in r)return r;if(typeof r=="string")return {type:"raw",html:r}}}catch{}return {type:"raw",html:`<!-- SSR placeholder for ${e.name||"component"} -->`}}function R(e,n,t){let r={};if(n)for(let[o,s]of Object.entries(n))s===true?r[o]="":s!==false&&s!=null&&(r[o]=String(s));return {type:"element",tag:e,attrs:r,children:t==null?[]:Array.isArray(t)?t:[t]}}function y(e){return {type:"text",content:e}}function x(e){return {type:"raw",html:e}}var l=new Set(["area","base","br","col","embed","hr","img","input","link","meta","param","source","track","wbr"]);function a(e){switch(e.type){case "text":return u(e.content);case "raw":return e.html;case "element":{let{tag:n,attrs:t,children:r}=e,o=`<${n}`;for(let[s,p]of Object.entries(t))p===""?o+=` ${s}`:o+=` ${s}="${u(p)}"`;if(l.has(n))return o+=" />",o;o+=">";for(let s of r)o+=a(s);return o+=`</${n}>`,o}}}function w(e){return e.map(a).join("")}exports.a=u;exports.b=f;exports.c=m;exports.d=d;exports.e=g;exports.f=R;exports.g=y;exports.h=x;exports.i=a;exports.j=w;//# sourceMappingURL=chunk-YU4YSPD6.cjs.map
|
|
2
|
-
//# sourceMappingURL=chunk-YU4YSPD6.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/ssr.ts"],"names":["ESCAPE_MAP","escapeHtml","str","ch","isSSR","isServerRendering","renderToString","component","props","node","renderComponent","nodeToHtml","renderToStringSync","renderToStream","controller","html","err","result","rendered","ssrElement","tag","attrs","children","processedAttrs","key","value","ssrText","content","ssrRaw","VOID_ELEMENTS","child","renderNodes","nodes"],"mappings":"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","file":"chunk-YU4YSPD6.cjs","sourcesContent":["/**\n * Server-Side Rendering.\n *\n * Renders components to HTML strings (or streams) on the server.\n * No DOM APIs are used — everything is string concatenation.\n *\n * - Effects do NOT run on the server (they are DOM-side only)\n * - Signals and computeds work synchronously\n * - Components run their setup + initial render, producing HTML\n */\n\nimport { signal, computed, untrack } from './signals.js';\nimport type { Component } from './component.js';\nimport type { AkashNode } from './types.js';\n\n// --- Escape helpers ---\n\nconst ESCAPE_MAP: Record<string, string> = {\n '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": ''',\n};\n\nexport function escapeHtml(str: string): string {\n return str.replace(/[&<>\"']/g, (ch) => ESCAPE_MAP[ch]);\n}\n\n// --- SSR context ---\n\nlet isSSR = false;\n\n/** Check if we're currently in an SSR render */\nexport function isServerRendering(): boolean {\n return isSSR;\n}\n\n// --- renderToString ---\n\n/**\n * Render a component to an HTML string on the server.\n *\n * ```ts\n * import { renderToString } from '@akashjs/runtime/ssr';\n * const html = await renderToString(App, { title: 'Home' });\n * ```\n */\nexport async function renderToString<P extends Record<string, unknown>>(\n component: Component<P>,\n props?: P,\n): Promise<string> {\n isSSR = true;\n try {\n const node = renderComponent(component, props ?? ({} as P));\n return nodeToHtml(node);\n } finally {\n isSSR = false;\n }\n}\n\n/**\n * Synchronous version of renderToString.\n */\nexport function renderToStringSync<P extends Record<string, unknown>>(\n component: Component<P>,\n props?: P,\n): string {\n isSSR = true;\n try {\n const node = renderComponent(component, props ?? ({} as P));\n return nodeToHtml(node);\n } finally {\n isSSR = false;\n }\n}\n\n// --- renderToStream ---\n\n/**\n * Render a component to a ReadableStream of HTML chunks.\n *\n * Useful for streaming SSR — flush HTML as data becomes available.\n *\n * ```ts\n * const stream = renderToStream(App, { title: 'Home' });\n * // Pipe to HTTP response\n * ```\n */\nexport function renderToStream<P extends Record<string, unknown>>(\n component: Component<P>,\n props?: P,\n): ReadableStream<string> {\n return new ReadableStream<string>({\n start(controller) {\n isSSR = true;\n try {\n const node = renderComponent(component, props ?? ({} as P));\n const html = nodeToHtml(node);\n\n // For now, emit as a single chunk.\n // With Suspense boundaries, each resolved boundary\n // would flush a separate chunk.\n controller.enqueue(html);\n controller.close();\n } catch (err) {\n controller.error(err);\n } finally {\n isSSR = false;\n }\n },\n });\n}\n\n// --- SSR rendering internals ---\n\n/** Render a component, getting back the AkashNode tree */\nfunction renderComponent<P extends Record<string, unknown>>(\n component: Component<P>,\n props: P,\n): SSRNode {\n // Try calling the component — if it returns an SSR node tree, use it directly\n try {\n const result = (component as Function)(props);\n // If result is an SSR node (has type: 'element'|'text'|'raw'), use it\n if (result && typeof result === 'object' && 'type' in result) {\n return result as SSRNode;\n }\n // If result is a string (server-compiled output), wrap as raw HTML\n if (typeof result === 'string') {\n return { type: 'raw', html: result };\n }\n // If result is a render function (defineComponent pattern), call it\n if (typeof result === 'function') {\n const rendered = result();\n if (rendered && typeof rendered === 'object' && 'type' in rendered) {\n return rendered as SSRNode;\n }\n if (typeof rendered === 'string') {\n return { type: 'raw', html: rendered };\n }\n }\n } catch {\n // Component uses DOM APIs — can't render on server\n }\n\n return { type: 'raw', html: `<!-- SSR placeholder for ${component.name || 'component'} -->` };\n}\n\n// --- SSR Node types ---\n\nexport interface SSRElement {\n type: 'element';\n tag: string;\n attrs: Record<string, string>;\n children: SSRNode[];\n selfClosing?: boolean;\n}\n\nexport interface SSRText {\n type: 'text';\n content: string;\n}\n\nexport interface SSRRaw {\n type: 'raw';\n html: string;\n}\n\nexport type SSRNode = SSRElement | SSRText | SSRRaw;\n\n// --- SSR DOM-less helpers ---\n\n/**\n * Create an SSR element (replaces document.createElement on the server).\n */\nexport function ssrElement(\n tag: string,\n attrs?: Record<string, string | boolean>,\n children?: SSRNode | SSRNode[],\n): SSRElement {\n const processedAttrs: Record<string, string> = {};\n if (attrs) {\n for (const [key, value] of Object.entries(attrs)) {\n if (value === true) {\n processedAttrs[key] = '';\n } else if (value !== false && value != null) {\n processedAttrs[key] = String(value);\n }\n }\n }\n\n return {\n type: 'element',\n tag,\n attrs: processedAttrs,\n children: children == null ? [] : Array.isArray(children) ? children : [children],\n };\n}\n\n/**\n * Create an SSR text node.\n */\nexport function ssrText(content: string): SSRText {\n return { type: 'text', content };\n}\n\n/**\n * Create a raw HTML SSR node (no escaping).\n */\nexport function ssrRaw(html: string): SSRRaw {\n return { type: 'raw', html };\n}\n\n// --- Serialization ---\n\nconst VOID_ELEMENTS = new Set([\n 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',\n 'link', 'meta', 'param', 'source', 'track', 'wbr',\n]);\n\n/**\n * Serialize an SSR node tree to an HTML string.\n */\nexport function nodeToHtml(node: SSRNode): string {\n switch (node.type) {\n case 'text':\n return escapeHtml(node.content);\n\n case 'raw':\n return node.html;\n\n case 'element': {\n const { tag, attrs, children } = node;\n let html = `<${tag}`;\n\n for (const [key, value] of Object.entries(attrs)) {\n if (value === '') {\n html += ` ${key}`;\n } else {\n html += ` ${key}=\"${escapeHtml(value)}\"`;\n }\n }\n\n if (VOID_ELEMENTS.has(tag)) {\n html += ' />';\n return html;\n }\n\n html += '>';\n\n for (const child of children) {\n html += nodeToHtml(child);\n }\n\n html += `</${tag}>`;\n return html;\n }\n }\n}\n\n/**\n * Render a tree of SSR nodes (convenience for arrays).\n */\nexport function renderNodes(nodes: SSRNode[]): string {\n return nodes.map(nodeToHtml).join('');\n}\n"]}
|