@akashjs/runtime 0.1.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/test.cjs ADDED
@@ -0,0 +1,370 @@
1
+ 'use strict';
2
+
3
+ // src/errors.ts
4
+ var errors = {
5
+ // --- Context errors (AK001x) ---
6
+ AK0010: {
7
+ code: "AK0010",
8
+ message: "provide() called outside of component setup.",
9
+ hint: "provide() must be called inside defineComponent()."
10
+ },
11
+ AK0012: {
12
+ code: "AK0012",
13
+ message: "inject() called outside of component setup.",
14
+ hint: "inject() must be called inside defineComponent()."
15
+ },
16
+ AK0013: {
17
+ code: "AK0013",
18
+ message: "No provider found for injected context.",
19
+ hint: "Make sure an ancestor component calls provide() with this key."
20
+ },
21
+ // --- Lifecycle errors (AK002x) ---
22
+ AK0020: {
23
+ code: "AK0020",
24
+ message: "onMount() called outside of component setup.",
25
+ hint: "onMount() must be called inside defineComponent()."
26
+ },
27
+ AK0021: {
28
+ code: "AK0021",
29
+ message: "onUnmount() called outside of component setup.",
30
+ hint: "onUnmount() must be called inside defineComponent()."
31
+ },
32
+ AK0022: {
33
+ code: "AK0022",
34
+ message: "onError() called outside of component setup.",
35
+ hint: "onError() must be called inside defineComponent()."
36
+ },
37
+ // --- Signal errors (AK003x) ---
38
+ AK0030: {
39
+ code: "AK0030",
40
+ message: "Circular dependency detected in computed signal.",
41
+ hint: "A computed signal is reading itself, directly or via other computeds."
42
+ },
43
+ AK0031: {
44
+ code: "AK0031",
45
+ message: "Signal set() called during computation.",
46
+ hint: "Do not set signals inside computed() or during effect execution. Use batch() or set signals in event handlers."
47
+ },
48
+ // --- Component errors (AK004x) ---
49
+ AK0040: {
50
+ code: "AK0040",
51
+ message: "Component setup must return a render function.",
52
+ hint: "The function passed to defineComponent() must return () => AkashNode."
53
+ },
54
+ AK0041: {
55
+ code: "AK0041",
56
+ message: "Required prop is missing.",
57
+ hint: "Check that the parent component is passing all required props."
58
+ },
59
+ // --- Router errors (AK005x) ---
60
+ AK0050: {
61
+ code: "AK0050",
62
+ message: "useRoute() called outside of router context.",
63
+ hint: "Make sure your component is rendered inside a router provider."
64
+ },
65
+ AK0051: {
66
+ code: "AK0051",
67
+ message: "No route matched the current URL.",
68
+ hint: "Add a catch-all route ([...rest]) to handle unmatched paths."
69
+ },
70
+ AK0052: {
71
+ code: "AK0052",
72
+ message: "Route guard threw an error.",
73
+ hint: "Check the guard function for unhandled exceptions."
74
+ },
75
+ // --- Form errors (AK006x) ---
76
+ AK0060: {
77
+ code: "AK0060",
78
+ message: "Form submitted while invalid.",
79
+ hint: "The submit handler was not called because validation failed. Check form.errors()."
80
+ },
81
+ AK0061: {
82
+ code: "AK0061",
83
+ message: "Async validator timed out.",
84
+ hint: "The async validator did not resolve within the expected time. Check your async validation logic."
85
+ },
86
+ // --- HTTP errors (AK007x) ---
87
+ AK0070: {
88
+ code: "AK0070",
89
+ message: "HTTP request failed.",
90
+ hint: "Check the server response status and network connectivity."
91
+ },
92
+ AK0071: {
93
+ code: "AK0071",
94
+ message: "createResource() fetcher threw an error.",
95
+ hint: "Check the fetcher function passed to createResource()."
96
+ }
97
+ };
98
+ var DOC_BASE = "https://akashjs.dev/errors";
99
+ function formatError(code, context) {
100
+ const def = errors[code];
101
+ if (!def) {
102
+ return `[AkashJS ${code}] Unknown error code.`;
103
+ }
104
+ let msg = `[AkashJS ${code}] ${def.message}`;
105
+ msg += `
106
+ ${def.hint}`;
107
+ msg += `
108
+ See: ${DOC_BASE}/${code}`;
109
+ return msg;
110
+ }
111
+ function akashError(code, context) {
112
+ return new Error(formatError(code));
113
+ }
114
+
115
+ // src/context.ts
116
+ var currentScope = null;
117
+ function pushScope(parent = currentScope) {
118
+ const scope = { values: /* @__PURE__ */ new Map(), parent };
119
+ currentScope = scope;
120
+ return scope;
121
+ }
122
+ function popScope(scope) {
123
+ currentScope = scope.parent;
124
+ }
125
+ function getCurrentScope() {
126
+ return currentScope;
127
+ }
128
+ function provide(key, value) {
129
+ if (!currentScope) {
130
+ throw akashError("AK0010");
131
+ }
132
+ currentScope.values.set(key.id, value);
133
+ }
134
+
135
+ // src/dom.ts
136
+ function nodeToDOM(node) {
137
+ if (node == null || typeof node === "boolean") {
138
+ return document.createTextNode("");
139
+ }
140
+ if (typeof node === "string" || typeof node === "number") {
141
+ return document.createTextNode(String(node));
142
+ }
143
+ if (node instanceof Node) {
144
+ return node;
145
+ }
146
+ if (Array.isArray(node)) {
147
+ const fragment = document.createDocumentFragment();
148
+ for (const child of node) {
149
+ fragment.appendChild(nodeToDOM(child));
150
+ }
151
+ return fragment;
152
+ }
153
+ return document.createTextNode(String(node));
154
+ }
155
+ function defineComponent(setup) {
156
+ const component = (rawProps) => {
157
+ const { children: childrenProp, ...restProps } = rawProps ?? {};
158
+ const props = restProps;
159
+ const childrenFn = typeof childrenProp === "function" ? childrenProp : () => childrenProp ?? null;
160
+ const ctx = {
161
+ props,
162
+ children: childrenFn
163
+ };
164
+ const hooks = { mount: [], unmount: [], error: [] };
165
+ const parentScope = getCurrentScope();
166
+ const scope = pushScope(parentScope);
167
+ let renderFn;
168
+ let domNode;
169
+ try {
170
+ renderFn = setup(ctx);
171
+ const rendered = renderFn();
172
+ domNode = nodeToDOM(rendered);
173
+ } catch (err) {
174
+ popScope(scope);
175
+ if (hooks.error.length > 0) {
176
+ for (const handler of hooks.error) {
177
+ handler(err instanceof Error ? err : new Error(String(err)));
178
+ }
179
+ return document.createComment("error");
180
+ }
181
+ throw err;
182
+ }
183
+ popScope(scope);
184
+ if (hooks.mount.length > 0) {
185
+ queueMicrotask(() => {
186
+ for (const mountFn of hooks.mount) {
187
+ try {
188
+ const cleanup = mountFn();
189
+ if (typeof cleanup === "function") {
190
+ hooks.unmount.push(cleanup);
191
+ }
192
+ } catch (err) {
193
+ for (const handler of hooks.error) {
194
+ handler(err instanceof Error ? err : new Error(String(err)));
195
+ }
196
+ }
197
+ }
198
+ });
199
+ }
200
+ return domNode;
201
+ };
202
+ component._akash = true;
203
+ return component;
204
+ }
205
+
206
+ // src/test.ts
207
+ function mount(component, options) {
208
+ const container = document.createElement("div");
209
+ container.setAttribute("data-akash-test-root", "");
210
+ const props = options?.props ?? {};
211
+ const provides = options?.provide;
212
+ let node;
213
+ if (provides && provides.size > 0) {
214
+ const Wrapper = defineComponent(() => {
215
+ for (const [key, value] of provides) {
216
+ provide(key, value);
217
+ }
218
+ return () => component(props);
219
+ });
220
+ node = Wrapper({});
221
+ } else {
222
+ node = component(props);
223
+ }
224
+ container.appendChild(node);
225
+ document.body.appendChild(container);
226
+ return {
227
+ container,
228
+ unmount() {
229
+ container.remove();
230
+ },
231
+ getByText(text) {
232
+ const el = findByText(container, text);
233
+ if (!el) {
234
+ throw new Error(
235
+ `[AkashJS Test] Could not find element with text: "${text}"
236
+ Container HTML: ${container.innerHTML.slice(0, 200)}`
237
+ );
238
+ }
239
+ return el;
240
+ },
241
+ getByRole(role) {
242
+ const el = container.querySelector(`[role="${role}"]`) ?? findImplicitRole(container, role);
243
+ if (!el) {
244
+ throw new Error(
245
+ `[AkashJS Test] Could not find element with role: "${role}"
246
+ Container HTML: ${container.innerHTML.slice(0, 200)}`
247
+ );
248
+ }
249
+ return el;
250
+ },
251
+ getByTestId(id) {
252
+ const el = container.querySelector(`[data-testid="${id}"]`);
253
+ if (!el) {
254
+ throw new Error(
255
+ `[AkashJS Test] Could not find element with data-testid: "${id}"
256
+ Container HTML: ${container.innerHTML.slice(0, 200)}`
257
+ );
258
+ }
259
+ return el;
260
+ },
261
+ queryAll(selector) {
262
+ return Array.from(container.querySelectorAll(selector));
263
+ },
264
+ query(selector) {
265
+ return container.querySelector(selector);
266
+ }
267
+ };
268
+ }
269
+ var fireEvent = {
270
+ async click(el) {
271
+ el.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true }));
272
+ await tick();
273
+ },
274
+ async input(el, value) {
275
+ const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
276
+ el instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype,
277
+ "value"
278
+ )?.set;
279
+ if (nativeInputValueSetter) {
280
+ nativeInputValueSetter.call(el, value);
281
+ } else {
282
+ el.value = value;
283
+ }
284
+ el.dispatchEvent(new Event("input", { bubbles: true }));
285
+ el.dispatchEvent(new Event("change", { bubbles: true }));
286
+ await tick();
287
+ },
288
+ async submit(el) {
289
+ el.dispatchEvent(new Event("submit", { bubbles: true, cancelable: true }));
290
+ await tick();
291
+ },
292
+ async focus(el) {
293
+ el.focus();
294
+ el.dispatchEvent(new FocusEvent("focus", { bubbles: true }));
295
+ await tick();
296
+ },
297
+ async blur(el) {
298
+ el.blur();
299
+ el.dispatchEvent(new FocusEvent("blur", { bubbles: true }));
300
+ await tick();
301
+ },
302
+ async keyDown(el, key, options) {
303
+ el.dispatchEvent(new KeyboardEvent("keydown", { key, bubbles: true, ...options }));
304
+ await tick();
305
+ },
306
+ async keyUp(el, key, options) {
307
+ el.dispatchEvent(new KeyboardEvent("keyup", { key, bubbles: true, ...options }));
308
+ await tick();
309
+ }
310
+ };
311
+ function tick() {
312
+ return new Promise((resolve) => queueMicrotask(resolve));
313
+ }
314
+ function findByText(root, text) {
315
+ let best = null;
316
+ const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
317
+ let node = walker.nextNode();
318
+ while (node) {
319
+ if (node instanceof HTMLElement && node.textContent?.includes(text)) {
320
+ best = node;
321
+ }
322
+ node = walker.nextNode();
323
+ }
324
+ if (!best && root.textContent?.includes(text)) {
325
+ best = root;
326
+ }
327
+ return best;
328
+ }
329
+ var IMPLICIT_ROLES = {
330
+ button: ["button"],
331
+ a: ["link"],
332
+ input: ["textbox", "checkbox", "radio", "spinbutton", "slider"],
333
+ select: ["combobox", "listbox"],
334
+ textarea: ["textbox"],
335
+ img: ["img"],
336
+ form: ["form"],
337
+ nav: ["navigation"],
338
+ main: ["main"],
339
+ header: ["banner"],
340
+ footer: ["contentinfo"],
341
+ aside: ["complementary"],
342
+ section: ["region"],
343
+ article: ["article"],
344
+ ul: ["list"],
345
+ ol: ["list"],
346
+ li: ["listitem"],
347
+ table: ["table"],
348
+ th: ["columnheader"],
349
+ td: ["cell"],
350
+ h1: ["heading"],
351
+ h2: ["heading"],
352
+ h3: ["heading"],
353
+ h4: ["heading"],
354
+ h5: ["heading"],
355
+ h6: ["heading"]
356
+ };
357
+ function findImplicitRole(root, role) {
358
+ const tags = [];
359
+ for (const [tag, roles] of Object.entries(IMPLICIT_ROLES)) {
360
+ if (roles.includes(role)) tags.push(tag);
361
+ }
362
+ if (tags.length === 0) return null;
363
+ const selector = tags.join(", ");
364
+ return root.querySelector(selector);
365
+ }
366
+
367
+ exports.fireEvent = fireEvent;
368
+ exports.mount = mount;
369
+ //# sourceMappingURL=test.cjs.map
370
+ //# sourceMappingURL=test.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors.ts","../src/context.ts","../src/dom.ts","../src/component.ts","../src/test.ts"],"names":[],"mappings":"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","file":"test.cjs","sourcesContent":["/**\n * Error message catalog.\n *\n * Every AkashJS runtime error has a unique code (AK0001, AK0002, etc.),\n * a one-line description, and a link to detailed documentation.\n *\n * Usage:\n * throw akashError('AK0010', 'provide()');\n */\n\n// --- Error registry ---\n\nexport interface ErrorDef {\n /** Error code */\n code: string;\n /** Short description */\n message: string;\n /** Extended explanation and fix suggestion */\n hint: string;\n}\n\nconst errors: Record<string, ErrorDef> = {\n // --- Context errors (AK001x) ---\n AK0010: {\n code: 'AK0010',\n message: 'provide() called outside of component setup.',\n hint: 'provide() must be called inside defineComponent().',\n },\n AK0012: {\n code: 'AK0012',\n message: 'inject() called outside of component setup.',\n hint: 'inject() must be called inside defineComponent().',\n },\n AK0013: {\n code: 'AK0013',\n message: 'No provider found for injected context.',\n hint: 'Make sure an ancestor component calls provide() with this key.',\n },\n\n // --- Lifecycle errors (AK002x) ---\n AK0020: {\n code: 'AK0020',\n message: 'onMount() called outside of component setup.',\n hint: 'onMount() must be called inside defineComponent().',\n },\n AK0021: {\n code: 'AK0021',\n message: 'onUnmount() called outside of component setup.',\n hint: 'onUnmount() must be called inside defineComponent().',\n },\n AK0022: {\n code: 'AK0022',\n message: 'onError() called outside of component setup.',\n hint: 'onError() must be called inside defineComponent().',\n },\n\n // --- Signal errors (AK003x) ---\n AK0030: {\n code: 'AK0030',\n message: 'Circular dependency detected in computed signal.',\n hint: 'A computed signal is reading itself, directly or via other computeds.',\n },\n AK0031: {\n code: 'AK0031',\n message: 'Signal set() called during computation.',\n hint: 'Do not set signals inside computed() or during effect execution. Use batch() or set signals in event handlers.',\n },\n\n // --- Component errors (AK004x) ---\n AK0040: {\n code: 'AK0040',\n message: 'Component setup must return a render function.',\n hint: 'The function passed to defineComponent() must return () => AkashNode.',\n },\n AK0041: {\n code: 'AK0041',\n message: 'Required prop is missing.',\n hint: 'Check that the parent component is passing all required props.',\n },\n\n // --- Router errors (AK005x) ---\n AK0050: {\n code: 'AK0050',\n message: 'useRoute() called outside of router context.',\n hint: 'Make sure your component is rendered inside a router provider.',\n },\n AK0051: {\n code: 'AK0051',\n message: 'No route matched the current URL.',\n hint: 'Add a catch-all route ([...rest]) to handle unmatched paths.',\n },\n AK0052: {\n code: 'AK0052',\n message: 'Route guard threw an error.',\n hint: 'Check the guard function for unhandled exceptions.',\n },\n\n // --- Form errors (AK006x) ---\n AK0060: {\n code: 'AK0060',\n message: 'Form submitted while invalid.',\n hint: 'The submit handler was not called because validation failed. Check form.errors().',\n },\n AK0061: {\n code: 'AK0061',\n message: 'Async validator timed out.',\n hint: 'The async validator did not resolve within the expected time. Check your async validation logic.',\n },\n\n // --- HTTP errors (AK007x) ---\n AK0070: {\n code: 'AK0070',\n message: 'HTTP request failed.',\n hint: 'Check the server response status and network connectivity.',\n },\n AK0071: {\n code: 'AK0071',\n message: 'createResource() fetcher threw an error.',\n hint: 'Check the fetcher function passed to createResource().',\n },\n};\n\n// --- Public API ---\n\nconst DOC_BASE = 'https://akashjs.dev/errors';\n\n/**\n * Format an AkashJS error with code, message, hint, and doc link.\n */\nexport function formatError(code: string, context?: string): string {\n const def = errors[code];\n if (!def) {\n return `[AkashJS ${code}] Unknown error code.`;\n }\n\n let msg = `[AkashJS ${code}] ${def.message}`;\n if (context) {\n msg += `\\n ${context}`;\n }\n msg += `\\n ${def.hint}`;\n msg += `\\n See: ${DOC_BASE}/${code}`;\n return msg;\n}\n\n/**\n * Create and throw an AkashJS error.\n */\nexport function akashError(code: string, context?: string): Error {\n return new Error(formatError(code, context));\n}\n\n/**\n * Get the error definition for a code.\n */\nexport function getErrorDef(code: string): ErrorDef | undefined {\n return errors[code];\n}\n\n/**\n * Get all registered error codes.\n */\nexport function getAllErrorCodes(): string[] {\n return Object.keys(errors);\n}\n","/**\n * Lightweight dependency injection via provide/inject.\n *\n * Context is scoped to the component tree — no injector hierarchy,\n * no decorators, no classes. Just createContext(), provide(), inject().\n */\n\n// --- Types ---\n\nimport { akashError } from './errors.js';\n\nconst CONTEXT_BRAND = Symbol('akash.context');\n\nexport interface InjectionKey<T> {\n readonly [CONTEXT_BRAND]: true;\n readonly _type: T; // phantom type — never used at runtime\n readonly defaultValue: T | undefined;\n readonly id: symbol;\n}\n\n// --- Context stack (managed by component system) ---\n\ninterface ContextScope {\n values: Map<symbol, unknown>;\n parent: ContextScope | null;\n}\n\nlet currentScope: ContextScope | null = null;\n\n/** @internal — called by defineComponent to push/pop context scopes */\nexport function pushScope(parent: ContextScope | null = currentScope): ContextScope {\n const scope: ContextScope = { values: new Map(), parent };\n currentScope = scope;\n return scope;\n}\n\n/** @internal */\nexport function popScope(scope: ContextScope): void {\n currentScope = scope.parent;\n}\n\n/** @internal */\nexport function getCurrentScope(): ContextScope | null {\n return currentScope;\n}\n\n// --- Public API ---\n\n/**\n * Create a typed context key with an optional default value.\n *\n * ```ts\n * const ThemeContext = createContext<'light' | 'dark'>('light');\n * ```\n */\nexport function createContext<T>(defaultValue?: T): InjectionKey<T> {\n return {\n [CONTEXT_BRAND]: true,\n _type: undefined as T,\n defaultValue,\n id: Symbol('context'),\n };\n}\n\n/**\n * Provide a value for a context key in the current component scope.\n * All descendant components can inject() this value.\n *\n * Must be called inside defineComponent() setup.\n */\nexport function provide<T>(key: InjectionKey<T>, value: T): void {\n if (!currentScope) {\n throw akashError('AK0010');\n }\n currentScope.values.set(key.id, value);\n}\n\n/**\n * Inject a value from the nearest ancestor that provided it.\n *\n * Must be called inside defineComponent() setup.\n */\nexport function inject<T>(key: InjectionKey<T>): T;\nexport function inject<T>(key: InjectionKey<T>, fallback: T): T;\nexport function inject<T>(key: InjectionKey<T>, fallback?: T): T {\n if (!currentScope) {\n throw akashError('AK0012');\n }\n\n // Walk up the scope chain\n let scope: ContextScope | null = currentScope;\n while (scope) {\n if (scope.values.has(key.id)) {\n return scope.values.get(key.id) as T;\n }\n scope = scope.parent;\n }\n\n // Check fallback, then default\n if (fallback !== undefined) return fallback;\n if (key.defaultValue !== undefined) return key.defaultValue;\n\n throw akashError('AK0013');\n}\n","/**\n * Direct DOM rendering runtime.\n *\n * No virtual DOM. The compiler generates calls to these helpers.\n * Signal reads inside templates become fine-grained effects that\n * update only the specific DOM node that changed.\n */\n\nimport { effect } from './signals.js';\nimport type { AkashNode } from './types.js';\n\n// --- DOM creation helpers (used by compiler output) ---\n\n/** Create an element and optionally set static attributes */\nexport function createElement(\n tag: string,\n attrs?: Record<string, unknown>,\n): HTMLElement {\n const el = document.createElement(tag);\n if (attrs) {\n for (const [key, value] of Object.entries(attrs)) {\n setProperty(el, key, value);\n }\n }\n return el;\n}\n\n/** Create a text node */\nexport function createText(value: string): Text {\n return document.createTextNode(value);\n}\n\n/** Clone a template element for static structure */\nexport function cloneTemplate(html: string): DocumentFragment {\n const template = document.createElement('template');\n template.innerHTML = html;\n return template.content.cloneNode(true) as DocumentFragment;\n}\n\n// --- Property/attribute setting ---\n\nexport function setProperty(el: HTMLElement, key: string, value: unknown): void {\n if (key === 'class' || key === 'className') {\n el.className = value as string;\n } else if (key === 'style' && typeof value === 'object' && value !== null) {\n Object.assign(el.style, value);\n } else if (key === 'innerHTML') {\n el.innerHTML = value as string;\n } else if (key === 'ref') {\n // Handled separately by component system\n } else if (key.startsWith('on')) {\n const event = key.slice(2).toLowerCase();\n el.addEventListener(event, value as EventListener);\n } else if (key in el) {\n (el as Record<string, unknown>)[key] = value;\n } else if (value === false || value == null) {\n el.removeAttribute(key);\n } else {\n el.setAttribute(key, value === true ? '' : String(value));\n }\n}\n\n// --- Reactive binding (used by compiler for dynamic expressions) ---\n\n/** Bind a reactive expression to a text node's content */\nexport function bindText(node: Text, fn: () => unknown): () => void {\n return effect(\n () => {\n node.textContent = String(fn());\n },\n { render: true },\n );\n}\n\n/** Bind a reactive expression to an element's attribute/property */\nexport function bindProperty(\n el: HTMLElement,\n key: string,\n fn: () => unknown,\n): () => void {\n return effect(\n () => {\n setProperty(el, key, fn());\n },\n { render: true },\n );\n}\n\n/** Bind a reactive expression to an element's visibility (display) */\nexport function bindVisible(el: HTMLElement, fn: () => boolean): () => void {\n return effect(\n () => {\n el.style.display = fn() ? '' : 'none';\n },\n { render: true },\n );\n}\n\n// --- Conditional rendering ---\n\n/** Anchor node type for marking insertion points */\ntype Anchor = Comment;\n\nfunction createAnchor(label = ''): Anchor {\n return document.createComment(label);\n}\n\ninterface ConditionalBlock {\n nodes: Node[];\n dispose: (() => void) | null;\n}\n\n/**\n * Render a conditional block. Swaps DOM fragments based on a reactive\n * condition. Used by the compiler for :if directives and <Show>.\n */\nexport function renderConditional(\n parent: Node,\n anchor: Node,\n condition: () => boolean,\n trueBranch: () => Node,\n falseBranch?: () => Node,\n): () => void {\n let current: ConditionalBlock | null = null;\n\n const dispose = effect(\n () => {\n const value = condition();\n\n // Remove old nodes\n if (current) {\n for (const node of current.nodes) {\n parent.removeChild(node);\n }\n current.dispose?.();\n current = null;\n }\n\n // Insert new nodes\n const branch = value ? trueBranch : falseBranch;\n if (branch) {\n const fragment = branch();\n const nodes = fragment instanceof DocumentFragment\n ? Array.from(fragment.childNodes)\n : [fragment];\n for (const node of nodes) {\n parent.insertBefore(node, anchor);\n }\n current = { nodes, dispose: null };\n }\n },\n { render: true },\n );\n\n return () => {\n dispose();\n if (current) {\n for (const node of current.nodes) {\n if (node.parentNode) node.parentNode.removeChild(node);\n }\n current.dispose?.();\n }\n };\n}\n\n// --- List rendering ---\n\ninterface ListItem<T> {\n key: unknown;\n value: T;\n nodes: Node[];\n dispose: (() => void) | null;\n}\n\n/**\n * Render a reactive list with keyed reconciliation.\n * Used by the compiler for :for directives and <For>.\n */\nexport function renderList<T>(\n parent: Node,\n anchor: Node,\n items: () => T[],\n keyFn: (item: T, index: number) => unknown,\n renderItem: (item: T, index: number) => Node,\n): () => void {\n let currentItems: ListItem<T>[] = [];\n\n const dispose = effect(\n () => {\n const newData = items();\n const newItems: ListItem<T>[] = [];\n const oldMap = new Map<unknown, ListItem<T>>();\n\n for (const item of currentItems) {\n oldMap.set(item.key, item);\n }\n\n // Build new list, reuse existing DOM nodes when keys match\n for (let i = 0; i < newData.length; i++) {\n const data = newData[i];\n const key = keyFn(data, i);\n const existing = oldMap.get(key);\n\n if (existing) {\n oldMap.delete(key);\n existing.value = data;\n newItems.push(existing);\n } else {\n const fragment = renderItem(data, i);\n const nodes = fragment instanceof DocumentFragment\n ? Array.from(fragment.childNodes)\n : [fragment];\n newItems.push({ key, value: data, nodes, dispose: null });\n }\n }\n\n // Remove items that are no longer in the list\n for (const item of oldMap.values()) {\n for (const node of item.nodes) {\n if (node.parentNode) node.parentNode.removeChild(node);\n }\n item.dispose?.();\n }\n\n // Reconcile DOM order — simple approach: re-insert all in order\n for (const item of newItems) {\n for (const node of item.nodes) {\n parent.insertBefore(node, anchor);\n }\n }\n\n currentItems = newItems;\n },\n { render: true },\n );\n\n return () => {\n dispose();\n for (const item of currentItems) {\n for (const node of item.nodes) {\n if (node.parentNode) node.parentNode.removeChild(node);\n }\n item.dispose?.();\n }\n currentItems = [];\n };\n}\n\n// --- Built-in control flow components ---\n\n/** Props for the <Show> component */\nexport interface ShowProps<T> {\n when: T | null | undefined | false;\n fallback?: () => AkashNode;\n children: (value: T) => AkashNode;\n}\n\n/**\n * <Show> component — conditionally renders children with type narrowing.\n * The children callback receives the non-null/undefined value.\n */\nexport function Show<T>(props: ShowProps<T>): Node {\n const anchor = createAnchor('show');\n const container = document.createDocumentFragment();\n container.appendChild(anchor);\n\n renderConditional(\n container,\n anchor,\n () => !!props.when,\n () => nodeToDOM(props.children(props.when as T)),\n props.fallback ? () => nodeToDOM(props.fallback!()) : undefined,\n );\n\n return container;\n}\n\n/** Props for the <For> component */\nexport interface ForProps<T> {\n each: T[];\n key: (item: T) => unknown;\n children: (item: T, index: number) => AkashNode;\n}\n\n/**\n * <For> component — renders a list with keyed reconciliation.\n */\nexport function For<T>(props: ForProps<T>): Node {\n const anchor = createAnchor('for');\n const container = document.createDocumentFragment();\n container.appendChild(anchor);\n\n renderList(\n container,\n anchor,\n () => props.each,\n props.key,\n (item, index) => nodeToDOM(props.children(item, index)),\n );\n\n return container;\n}\n\n// --- Helpers ---\n\n/** Convert an AkashNode to a DOM Node */\nexport function nodeToDOM(node: AkashNode): Node {\n if (node == null || typeof node === 'boolean') {\n return document.createTextNode('');\n }\n if (typeof node === 'string' || typeof node === 'number') {\n return document.createTextNode(String(node));\n }\n if (node instanceof Node) {\n return node;\n }\n if (Array.isArray(node)) {\n const fragment = document.createDocumentFragment();\n for (const child of node) {\n fragment.appendChild(nodeToDOM(child));\n }\n return fragment;\n }\n return document.createTextNode(String(node));\n}\n\n/** Insert a node into a parent before an anchor */\nexport function insert(parent: Node, node: AkashNode, anchor?: Node): void {\n const domNode = nodeToDOM(node);\n if (anchor) {\n parent.insertBefore(domNode, anchor);\n } else {\n parent.appendChild(domNode);\n }\n}\n","/**\n * Component system.\n *\n * Components are functions. defineComponent() wraps a setup function\n * that runs once, establishes signals and effects, and returns a\n * render function that produces DOM nodes.\n */\n\nimport { effect } from './signals.js';\nimport { pushScope, popScope, getCurrentScope } from './context.js';\nimport { nodeToDOM } from './dom.js';\nimport { akashError } from './errors.js';\nimport type { AkashNode } from './types.js';\n\n// --- Types ---\n\nexport interface Ref<T = HTMLElement> {\n current: T | undefined;\n}\n\nexport interface ComponentContext<P extends Record<string, unknown> = Record<string, unknown>> {\n props: Readonly<P>;\n children: () => AkashNode;\n}\n\nexport type Component<P extends Record<string, unknown> = Record<string, unknown>> = {\n (props: P & { children?: AkashNode | (() => AkashNode) }): Node;\n _akash: true;\n};\n\n// --- Lifecycle hook storage ---\n\ninterface LifecycleHooks {\n mount: Array<() => void | (() => void)>;\n unmount: Array<() => void>;\n error: Array<(error: Error) => void>;\n}\n\nlet currentHooks: LifecycleHooks | null = null;\n\n// --- Public lifecycle hooks ---\n\n/**\n * Register a callback to run after the component is mounted to the DOM.\n * If the callback returns a function, it will be called on unmount (cleanup).\n */\nexport function onMount(fn: () => void | (() => void)): void {\n if (!currentHooks) {\n throw akashError('AK0020');\n }\n currentHooks.mount.push(fn);\n}\n\n/**\n * Register a callback to run before the component is unmounted.\n */\nexport function onUnmount(fn: () => void): void {\n if (!currentHooks) {\n throw akashError('AK0021');\n }\n currentHooks.unmount.push(fn);\n}\n\n/**\n * Register an error handler for this component and its descendants.\n */\nexport function onError(fn: (error: Error) => void): void {\n if (!currentHooks) {\n throw akashError('AK0022');\n }\n currentHooks.error.push(fn);\n}\n\n/**\n * Create a ref for accessing a DOM element.\n */\nexport function ref<T = HTMLElement>(): Ref<T> {\n return { current: undefined };\n}\n\n// --- defineComponent ---\n\n/**\n * Define a component. The setup function runs once per instance.\n * It receives a context with typed props and must return a render function.\n *\n * ```ts\n * const Counter = defineComponent<{ initial: number }>((ctx) => {\n * const count = signal(ctx.props.initial);\n * return () => <div>{count()}</div>;\n * });\n * ```\n */\nexport function defineComponent<P extends Record<string, unknown> = Record<string, unknown>>(\n setup: (ctx: ComponentContext<P>) => () => AkashNode,\n): Component<P> {\n const component = (rawProps: P & { children?: AkashNode | (() => AkashNode) }): Node => {\n // Separate children from props\n const { children: childrenProp, ...restProps } = rawProps ?? {};\n const props = restProps as unknown as P;\n\n const childrenFn: () => AkashNode =\n typeof childrenProp === 'function'\n ? childrenProp\n : () => childrenProp ?? null;\n\n const ctx: ComponentContext<P> = {\n props,\n children: childrenFn,\n };\n\n // Set up lifecycle hooks collector\n const hooks: LifecycleHooks = { mount: [], unmount: [], error: [] };\n const prevHooks = currentHooks;\n currentHooks = hooks;\n\n // Push context scope for provide/inject\n const parentScope = getCurrentScope();\n const scope = pushScope(parentScope);\n\n let renderFn: () => AkashNode;\n let domNode: Node;\n\n try {\n renderFn = setup(ctx);\n const rendered = renderFn();\n domNode = nodeToDOM(rendered);\n } catch (err) {\n popScope(scope);\n currentHooks = prevHooks;\n if (hooks.error.length > 0) {\n for (const handler of hooks.error) {\n handler(err instanceof Error ? err : new Error(String(err)));\n }\n return document.createComment('error');\n }\n throw err;\n }\n\n // Restore parent state\n popScope(scope);\n currentHooks = prevHooks;\n\n // Run mount callbacks (microtask to ensure DOM is attached)\n if (hooks.mount.length > 0) {\n queueMicrotask(() => {\n for (const mountFn of hooks.mount) {\n try {\n const cleanup = mountFn();\n if (typeof cleanup === 'function') {\n hooks.unmount.push(cleanup);\n }\n } catch (err) {\n for (const handler of hooks.error) {\n handler(err instanceof Error ? err : new Error(String(err)));\n }\n }\n }\n });\n }\n\n return domNode;\n };\n\n component._akash = true as const;\n return component as Component<P>;\n}\n","/**\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"]}
package/dist/test.js ADDED
@@ -0,0 +1,166 @@
1
+ import { defineComponent, provide } from './chunk-D3IR22HI.js';
2
+
3
+ // src/test.ts
4
+ function mount(component, options) {
5
+ const container = document.createElement("div");
6
+ container.setAttribute("data-akash-test-root", "");
7
+ const props = options?.props ?? {};
8
+ const provides = options?.provide;
9
+ let node;
10
+ if (provides && provides.size > 0) {
11
+ const Wrapper = defineComponent(() => {
12
+ for (const [key, value] of provides) {
13
+ provide(key, value);
14
+ }
15
+ return () => component(props);
16
+ });
17
+ node = Wrapper({});
18
+ } else {
19
+ node = component(props);
20
+ }
21
+ container.appendChild(node);
22
+ document.body.appendChild(container);
23
+ return {
24
+ container,
25
+ unmount() {
26
+ container.remove();
27
+ },
28
+ getByText(text) {
29
+ const el = findByText(container, text);
30
+ if (!el) {
31
+ throw new Error(
32
+ `[AkashJS Test] Could not find element with text: "${text}"
33
+ Container HTML: ${container.innerHTML.slice(0, 200)}`
34
+ );
35
+ }
36
+ return el;
37
+ },
38
+ getByRole(role) {
39
+ const el = container.querySelector(`[role="${role}"]`) ?? findImplicitRole(container, role);
40
+ if (!el) {
41
+ throw new Error(
42
+ `[AkashJS Test] Could not find element with role: "${role}"
43
+ Container HTML: ${container.innerHTML.slice(0, 200)}`
44
+ );
45
+ }
46
+ return el;
47
+ },
48
+ getByTestId(id) {
49
+ const el = container.querySelector(`[data-testid="${id}"]`);
50
+ if (!el) {
51
+ throw new Error(
52
+ `[AkashJS Test] Could not find element with data-testid: "${id}"
53
+ Container HTML: ${container.innerHTML.slice(0, 200)}`
54
+ );
55
+ }
56
+ return el;
57
+ },
58
+ queryAll(selector) {
59
+ return Array.from(container.querySelectorAll(selector));
60
+ },
61
+ query(selector) {
62
+ return container.querySelector(selector);
63
+ }
64
+ };
65
+ }
66
+ var fireEvent = {
67
+ async click(el) {
68
+ el.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true }));
69
+ await tick();
70
+ },
71
+ async input(el, value) {
72
+ const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
73
+ el instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype,
74
+ "value"
75
+ )?.set;
76
+ if (nativeInputValueSetter) {
77
+ nativeInputValueSetter.call(el, value);
78
+ } else {
79
+ el.value = value;
80
+ }
81
+ el.dispatchEvent(new Event("input", { bubbles: true }));
82
+ el.dispatchEvent(new Event("change", { bubbles: true }));
83
+ await tick();
84
+ },
85
+ async submit(el) {
86
+ el.dispatchEvent(new Event("submit", { bubbles: true, cancelable: true }));
87
+ await tick();
88
+ },
89
+ async focus(el) {
90
+ el.focus();
91
+ el.dispatchEvent(new FocusEvent("focus", { bubbles: true }));
92
+ await tick();
93
+ },
94
+ async blur(el) {
95
+ el.blur();
96
+ el.dispatchEvent(new FocusEvent("blur", { bubbles: true }));
97
+ await tick();
98
+ },
99
+ async keyDown(el, key, options) {
100
+ el.dispatchEvent(new KeyboardEvent("keydown", { key, bubbles: true, ...options }));
101
+ await tick();
102
+ },
103
+ async keyUp(el, key, options) {
104
+ el.dispatchEvent(new KeyboardEvent("keyup", { key, bubbles: true, ...options }));
105
+ await tick();
106
+ }
107
+ };
108
+ function tick() {
109
+ return new Promise((resolve) => queueMicrotask(resolve));
110
+ }
111
+ function findByText(root, text) {
112
+ let best = null;
113
+ const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
114
+ let node = walker.nextNode();
115
+ while (node) {
116
+ if (node instanceof HTMLElement && node.textContent?.includes(text)) {
117
+ best = node;
118
+ }
119
+ node = walker.nextNode();
120
+ }
121
+ if (!best && root.textContent?.includes(text)) {
122
+ best = root;
123
+ }
124
+ return best;
125
+ }
126
+ var IMPLICIT_ROLES = {
127
+ button: ["button"],
128
+ a: ["link"],
129
+ input: ["textbox", "checkbox", "radio", "spinbutton", "slider"],
130
+ select: ["combobox", "listbox"],
131
+ textarea: ["textbox"],
132
+ img: ["img"],
133
+ form: ["form"],
134
+ nav: ["navigation"],
135
+ main: ["main"],
136
+ header: ["banner"],
137
+ footer: ["contentinfo"],
138
+ aside: ["complementary"],
139
+ section: ["region"],
140
+ article: ["article"],
141
+ ul: ["list"],
142
+ ol: ["list"],
143
+ li: ["listitem"],
144
+ table: ["table"],
145
+ th: ["columnheader"],
146
+ td: ["cell"],
147
+ h1: ["heading"],
148
+ h2: ["heading"],
149
+ h3: ["heading"],
150
+ h4: ["heading"],
151
+ h5: ["heading"],
152
+ h6: ["heading"]
153
+ };
154
+ function findImplicitRole(root, role) {
155
+ const tags = [];
156
+ for (const [tag, roles] of Object.entries(IMPLICIT_ROLES)) {
157
+ if (roles.includes(role)) tags.push(tag);
158
+ }
159
+ if (tags.length === 0) return null;
160
+ const selector = tags.join(", ");
161
+ return root.querySelector(selector);
162
+ }
163
+
164
+ export { fireEvent, mount };
165
+ //# sourceMappingURL=test.js.map
166
+ //# sourceMappingURL=test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/test.ts"],"names":[],"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"]}