@barefootjs/test 0.1.3 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1022 -696
- package/dist/ir-to-test-node.d.ts +2 -2
- package/dist/ir-to-test-node.d.ts.map +1 -1
- package/dist/test-node.d.ts +11 -0
- package/dist/test-node.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +1 -1
- package/src/ir-to-test-node.ts +94 -33
- package/src/render.ts +2 -1
- package/src/test-node.ts +17 -0
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Recursively transforms each IRNode variant into a TestNode.
|
|
5
5
|
*/
|
|
6
|
-
import type { IRNode } from '@barefootjs/jsx';
|
|
6
|
+
import type { IRNode, IRMetadata } from '@barefootjs/jsx';
|
|
7
7
|
import { TestNode } from './test-node';
|
|
8
|
-
export declare function irNodeToTestNode(node: IRNode, constantMap?: Map<string, string
|
|
8
|
+
export declare function irNodeToTestNode(node: IRNode, constantMap?: Map<string, string>, metadata?: IRMetadata): TestNode;
|
|
9
9
|
//# sourceMappingURL=ir-to-test-node.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ir-to-test-node.d.ts","sourceRoot":"","sources":["../src/ir-to-test-node.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EACV,MAAM,
|
|
1
|
+
{"version":3,"file":"ir-to-test-node.d.ts","sourceRoot":"","sources":["../src/ir-to-test-node.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EACV,MAAM,EAYN,UAAU,EACX,MAAM,iBAAiB,CAAA;AAMxB,OAAO,EAAE,QAAQ,EAAqB,MAAM,aAAa,CAAA;AAQzD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,QAAQ,CAAC,EAAE,UAAU,GAAG,QAAQ,CAajH"}
|
package/dist/test-node.d.ts
CHANGED
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Produced by converting the compiler IR into a flat, assertion-friendly shape.
|
|
5
5
|
*/
|
|
6
|
+
export interface EventHandler {
|
|
7
|
+
setters: string[];
|
|
8
|
+
via: string[];
|
|
9
|
+
}
|
|
6
10
|
export interface TestNodeData {
|
|
7
11
|
tag: string | null;
|
|
8
12
|
type: 'element' | 'text' | 'expression' | 'conditional' | 'loop' | 'component' | 'fragment';
|
|
@@ -14,6 +18,7 @@ export interface TestNodeData {
|
|
|
14
18
|
aria: Record<string, string>;
|
|
15
19
|
dataState: string | null;
|
|
16
20
|
events: string[];
|
|
21
|
+
handlers: Partial<Record<string, EventHandler>>;
|
|
17
22
|
reactive: boolean;
|
|
18
23
|
componentName: string | null;
|
|
19
24
|
}
|
|
@@ -33,8 +38,14 @@ export declare class TestNode implements TestNodeData {
|
|
|
33
38
|
aria: Record<string, string>;
|
|
34
39
|
dataState: string | null;
|
|
35
40
|
events: string[];
|
|
41
|
+
handlers: Partial<Record<string, EventHandler>>;
|
|
36
42
|
reactive: boolean;
|
|
37
43
|
componentName: string | null;
|
|
44
|
+
get onClick(): EventHandler | undefined;
|
|
45
|
+
get onInput(): EventHandler | undefined;
|
|
46
|
+
get onChange(): EventHandler | undefined;
|
|
47
|
+
get onSubmit(): EventHandler | undefined;
|
|
48
|
+
on(event: string): EventHandler | null;
|
|
38
49
|
constructor(data: TestNodeData);
|
|
39
50
|
/** Return the first descendant (or self) matching the query. */
|
|
40
51
|
find(query: TestNodeQuery): TestNode | null;
|
package/dist/test-node.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"test-node.d.ts","sourceRoot":"","sources":["../src/test-node.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAClB,IAAI,EAAE,SAAS,GAAG,MAAM,GAAG,YAAY,GAAG,aAAa,GAAG,MAAM,GAAG,WAAW,GAAG,UAAU,CAAA;IAC3F,QAAQ,EAAE,QAAQ,EAAE,CAAA;IACpB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC,CAAA;IAC9C,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC5B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,QAAQ,EAAE,OAAO,CAAA;IACjB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;CAC7B;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,qBAAa,QAAS,YAAW,YAAY;IAC3C,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAClB,IAAI,EAAE,YAAY,CAAC,MAAM,CAAC,CAAA;IAC1B,QAAQ,EAAE,QAAQ,EAAE,CAAA;IACpB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC,CAAA;IAC9C,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC5B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,QAAQ,EAAE,OAAO,CAAA;IACjB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;IAE5B,YAAY,IAAI,EAAE,YAAY,
|
|
1
|
+
{"version":3,"file":"test-node.d.ts","sourceRoot":"","sources":["../src/test-node.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,GAAG,EAAE,MAAM,EAAE,CAAA;CACd;AAED,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAClB,IAAI,EAAE,SAAS,GAAG,MAAM,GAAG,YAAY,GAAG,aAAa,GAAG,MAAM,GAAG,WAAW,GAAG,UAAU,CAAA;IAC3F,QAAQ,EAAE,QAAQ,EAAE,CAAA;IACpB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC,CAAA;IAC9C,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC5B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,CAAA;IAC/C,QAAQ,EAAE,OAAO,CAAA;IACjB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;CAC7B;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,qBAAa,QAAS,YAAW,YAAY;IAC3C,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAClB,IAAI,EAAE,YAAY,CAAC,MAAM,CAAC,CAAA;IAC1B,QAAQ,EAAE,QAAQ,EAAE,CAAA;IACpB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC,CAAA;IAC9C,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC5B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,CAAA;IAC/C,QAAQ,EAAE,OAAO,CAAA;IACjB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;IAE5B,IAAI,OAAO,IAAI,YAAY,GAAG,SAAS,CAA+B;IACtE,IAAI,OAAO,IAAI,YAAY,GAAG,SAAS,CAA+B;IACtE,IAAI,QAAQ,IAAI,YAAY,GAAG,SAAS,CAAgC;IACxE,IAAI,QAAQ,IAAI,YAAY,GAAG,SAAS,CAAgC;IAExE,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAErC;IAED,YAAY,IAAI,EAAE,YAAY,EAc7B;IAED,gEAAgE;IAChE,IAAI,CAAC,KAAK,EAAE,aAAa,GAAG,QAAQ,GAAG,IAAI,CAO1C;IAED,4DAA4D;IAC5D,OAAO,CAAC,KAAK,EAAE,aAAa,GAAG,QAAQ,EAAE,CAIxC;IAED,wEAAwE;IACxE,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAOxC;IAED,OAAO,CAAC,OAAO;IAOf,OAAO,CAAC,cAAc;CAMvB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@barefootjs/test",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Test utilities for BarefootJS - IR-based component testing without a browser",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"directory": "packages/test"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@barefootjs/jsx": "0.
|
|
42
|
+
"@barefootjs/jsx": "0.3.0"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"typescript": "^5.0.0"
|
package/src/index.ts
CHANGED
|
@@ -6,7 +6,7 @@ export { renderToTest } from './render'
|
|
|
6
6
|
export type { TestResult } from './render'
|
|
7
7
|
|
|
8
8
|
export { TestNode } from './test-node'
|
|
9
|
-
export type { TestNodeData, TestNodeQuery } from './test-node'
|
|
9
|
+
export type { TestNodeData, TestNodeQuery, EventHandler } from './test-node'
|
|
10
10
|
|
|
11
11
|
export { toStructure } from './structure'
|
|
12
12
|
|
package/src/ir-to-test-node.ts
CHANGED
|
@@ -17,42 +17,60 @@ import type {
|
|
|
17
17
|
IRIfStatement,
|
|
18
18
|
IRProvider,
|
|
19
19
|
IRAsync,
|
|
20
|
+
IRMetadata,
|
|
20
21
|
} from '@barefootjs/jsx'
|
|
22
|
+
import { resolveSetters, buildLocalFunctionSetterMap, type SetterRef, type FnSetterResolution } from '@barefootjs/jsx'
|
|
21
23
|
|
|
22
24
|
type IRAttribute = IRElement['attrs'][number]
|
|
23
25
|
type AttrValue = IRAttribute['value']
|
|
24
26
|
type TemplateAttr = Extract<AttrValue, { kind: 'template' }>
|
|
25
|
-
import { TestNode } from './test-node'
|
|
27
|
+
import { TestNode, type EventHandler } from './test-node'
|
|
26
28
|
|
|
27
|
-
|
|
29
|
+
interface ConvertContext {
|
|
30
|
+
cmap: Map<string, string>
|
|
31
|
+
setterToSignal: Map<string, string>
|
|
32
|
+
fnSetters: Map<string, FnSetterResolution[]>
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function irNodeToTestNode(node: IRNode, constantMap?: Map<string, string>, metadata?: IRMetadata): TestNode {
|
|
28
36
|
const cmap = constantMap ?? new Map<string, string>()
|
|
29
|
-
|
|
37
|
+
const setterToSignal = new Map<string, string>()
|
|
38
|
+
const fnSetters = new Map<string, FnSetterResolution[]>()
|
|
39
|
+
if (metadata) {
|
|
40
|
+
for (const s of metadata.signals) {
|
|
41
|
+
if (s.setter) setterToSignal.set(s.setter, s.getter)
|
|
42
|
+
}
|
|
43
|
+
for (const [k, v] of buildLocalFunctionSetterMap(metadata, setterToSignal)) {
|
|
44
|
+
fnSetters.set(k, v)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return convert(node, { cmap, setterToSignal, fnSetters })
|
|
30
48
|
}
|
|
31
49
|
|
|
32
|
-
function convert(node: IRNode,
|
|
50
|
+
function convert(node: IRNode, ctx: ConvertContext): TestNode {
|
|
33
51
|
switch (node.type) {
|
|
34
52
|
case 'element':
|
|
35
|
-
return convertElement(node,
|
|
53
|
+
return convertElement(node, ctx)
|
|
36
54
|
case 'text':
|
|
37
55
|
return convertText(node)
|
|
38
56
|
case 'expression':
|
|
39
57
|
return convertExpression(node)
|
|
40
58
|
case 'conditional':
|
|
41
|
-
return convertConditional(node,
|
|
59
|
+
return convertConditional(node, ctx)
|
|
42
60
|
case 'loop':
|
|
43
|
-
return convertLoop(node,
|
|
61
|
+
return convertLoop(node, ctx)
|
|
44
62
|
case 'component':
|
|
45
|
-
return convertComponent(node,
|
|
63
|
+
return convertComponent(node, ctx)
|
|
46
64
|
case 'fragment':
|
|
47
|
-
return convertFragment(node,
|
|
65
|
+
return convertFragment(node, ctx)
|
|
48
66
|
case 'slot':
|
|
49
67
|
return convertSlot(node)
|
|
50
68
|
case 'if-statement':
|
|
51
|
-
return convertIfStatement(node,
|
|
69
|
+
return convertIfStatement(node, ctx)
|
|
52
70
|
case 'provider':
|
|
53
|
-
return convertProvider(node,
|
|
71
|
+
return convertProvider(node, ctx)
|
|
54
72
|
case 'async':
|
|
55
|
-
return convertAsync(node,
|
|
73
|
+
return convertAsync(node, ctx)
|
|
56
74
|
default: {
|
|
57
75
|
const _exhaustive: never = node
|
|
58
76
|
throw new Error(`Unhandled IR node type: ${(_exhaustive as IRNode).type}`)
|
|
@@ -64,7 +82,7 @@ function convert(node: IRNode, cmap: Map<string, string>): TestNode {
|
|
|
64
82
|
// Element
|
|
65
83
|
// ---------------------------------------------------------------------------
|
|
66
84
|
|
|
67
|
-
function convertElement(node: IRElement,
|
|
85
|
+
function convertElement(node: IRElement, ctx: ConvertContext): TestNode {
|
|
68
86
|
const props: Record<string, string | boolean | null> = {}
|
|
69
87
|
const aria: Record<string, string> = {}
|
|
70
88
|
let role: string | null = null
|
|
@@ -77,8 +95,8 @@ function convertElement(node: IRElement, cmap: Map<string, string>): TestNode {
|
|
|
77
95
|
if (attr.name === 'className' || attr.name === 'class') {
|
|
78
96
|
if (typeof value === 'string') {
|
|
79
97
|
const isDynamic = attr.value.kind === 'expression' || attr.value.kind === 'template' || attr.value.kind === 'spread'
|
|
80
|
-
if (isDynamic && cmap.size > 0) {
|
|
81
|
-
const resolved = resolveClassValue(value, cmap)
|
|
98
|
+
if (isDynamic && ctx.cmap.size > 0) {
|
|
99
|
+
const resolved = resolveClassValue(value, ctx.cmap)
|
|
82
100
|
if (resolved !== null) {
|
|
83
101
|
classes = resolved.split(/\s+/).filter(Boolean)
|
|
84
102
|
continue
|
|
@@ -110,7 +128,12 @@ function convertElement(node: IRElement, cmap: Map<string, string>): TestNode {
|
|
|
110
128
|
}
|
|
111
129
|
|
|
112
130
|
const events = node.events.map(e => e.name)
|
|
113
|
-
const
|
|
131
|
+
const handlers: Record<string, EventHandler> = {}
|
|
132
|
+
for (const event of node.events) {
|
|
133
|
+
const refs = resolveSetters(event.handler, ctx.setterToSignal, ctx.fnSetters)
|
|
134
|
+
handlers[event.name] = refsToHandler(refs)
|
|
135
|
+
}
|
|
136
|
+
const children = node.children.map(c => convert(c, ctx))
|
|
114
137
|
|
|
115
138
|
return new TestNode({
|
|
116
139
|
tag: node.tag,
|
|
@@ -123,6 +146,7 @@ function convertElement(node: IRElement, cmap: Map<string, string>): TestNode {
|
|
|
123
146
|
aria,
|
|
124
147
|
dataState,
|
|
125
148
|
events,
|
|
149
|
+
handlers,
|
|
126
150
|
reactive: false,
|
|
127
151
|
componentName: null,
|
|
128
152
|
})
|
|
@@ -144,6 +168,7 @@ function convertText(node: IRText): TestNode {
|
|
|
144
168
|
aria: {},
|
|
145
169
|
dataState: null,
|
|
146
170
|
events: [],
|
|
171
|
+
handlers: {},
|
|
147
172
|
reactive: false,
|
|
148
173
|
componentName: null,
|
|
149
174
|
})
|
|
@@ -165,6 +190,7 @@ function convertExpression(node: IRExpression): TestNode {
|
|
|
165
190
|
aria: {},
|
|
166
191
|
dataState: null,
|
|
167
192
|
events: [],
|
|
193
|
+
handlers: {},
|
|
168
194
|
reactive: node.reactive,
|
|
169
195
|
componentName: null,
|
|
170
196
|
})
|
|
@@ -174,10 +200,10 @@ function convertExpression(node: IRExpression): TestNode {
|
|
|
174
200
|
// Conditional
|
|
175
201
|
// ---------------------------------------------------------------------------
|
|
176
202
|
|
|
177
|
-
function convertConditional(node: IRConditional,
|
|
178
|
-
const children: TestNode[] = [convert(node.whenTrue,
|
|
203
|
+
function convertConditional(node: IRConditional, ctx: ConvertContext): TestNode {
|
|
204
|
+
const children: TestNode[] = [convert(node.whenTrue, ctx)]
|
|
179
205
|
if (node.whenFalse) {
|
|
180
|
-
children.push(convert(node.whenFalse,
|
|
206
|
+
children.push(convert(node.whenFalse, ctx))
|
|
181
207
|
}
|
|
182
208
|
|
|
183
209
|
return new TestNode({
|
|
@@ -191,6 +217,7 @@ function convertConditional(node: IRConditional, cmap: Map<string, string>): Tes
|
|
|
191
217
|
aria: {},
|
|
192
218
|
dataState: null,
|
|
193
219
|
events: [],
|
|
220
|
+
handlers: {},
|
|
194
221
|
reactive: node.reactive,
|
|
195
222
|
componentName: null,
|
|
196
223
|
})
|
|
@@ -200,8 +227,8 @@ function convertConditional(node: IRConditional, cmap: Map<string, string>): Tes
|
|
|
200
227
|
// Loop
|
|
201
228
|
// ---------------------------------------------------------------------------
|
|
202
229
|
|
|
203
|
-
function convertLoop(node: IRLoop,
|
|
204
|
-
const children = node.children.map(c => convert(c,
|
|
230
|
+
function convertLoop(node: IRLoop, ctx: ConvertContext): TestNode {
|
|
231
|
+
const children = node.children.map(c => convert(c, ctx))
|
|
205
232
|
|
|
206
233
|
return new TestNode({
|
|
207
234
|
tag: null,
|
|
@@ -214,6 +241,7 @@ function convertLoop(node: IRLoop, cmap: Map<string, string>): TestNode {
|
|
|
214
241
|
aria: {},
|
|
215
242
|
dataState: null,
|
|
216
243
|
events: [],
|
|
244
|
+
handlers: {},
|
|
217
245
|
reactive: false,
|
|
218
246
|
componentName: null,
|
|
219
247
|
})
|
|
@@ -223,8 +251,10 @@ function convertLoop(node: IRLoop, cmap: Map<string, string>): TestNode {
|
|
|
223
251
|
// Component
|
|
224
252
|
// ---------------------------------------------------------------------------
|
|
225
253
|
|
|
226
|
-
function convertComponent(node: IRComponent,
|
|
254
|
+
function convertComponent(node: IRComponent, ctx: ConvertContext): TestNode {
|
|
227
255
|
const props: Record<string, string | boolean | null> = {}
|
|
256
|
+
const events: string[] = []
|
|
257
|
+
const handlers: Record<string, EventHandler> = {}
|
|
228
258
|
for (const prop of node.props) {
|
|
229
259
|
switch (prop.value.kind) {
|
|
230
260
|
case 'literal':
|
|
@@ -245,9 +275,22 @@ function convertComponent(node: IRComponent, cmap: Map<string, string>): TestNod
|
|
|
245
275
|
props[prop.name] = null
|
|
246
276
|
break
|
|
247
277
|
}
|
|
278
|
+
|
|
279
|
+
// Component callback props that look like event handlers
|
|
280
|
+
// (`<Button onClick={...}>`). The parent IR sees these as props, but for
|
|
281
|
+
// wiring they behave like events: when the callback fires, which setters
|
|
282
|
+
// run. Keyed by the DOM-style event name (`onClick` -> `click`) so the
|
|
283
|
+
// shorthand getters and `on()` work the same as for native elements.
|
|
284
|
+
if (/^on[A-Z]/.test(prop.name) && prop.value.kind === 'expression') {
|
|
285
|
+
const eventName = prop.name.charAt(2).toLowerCase() + prop.name.slice(3)
|
|
286
|
+
events.push(eventName)
|
|
287
|
+
handlers[eventName] = refsToHandler(
|
|
288
|
+
resolveSetters(prop.value.expr, ctx.setterToSignal, ctx.fnSetters),
|
|
289
|
+
)
|
|
290
|
+
}
|
|
248
291
|
}
|
|
249
292
|
|
|
250
|
-
const children = node.children.map(c => convert(c,
|
|
293
|
+
const children = node.children.map(c => convert(c, ctx))
|
|
251
294
|
|
|
252
295
|
return new TestNode({
|
|
253
296
|
tag: null,
|
|
@@ -259,7 +302,8 @@ function convertComponent(node: IRComponent, cmap: Map<string, string>): TestNod
|
|
|
259
302
|
role: null,
|
|
260
303
|
aria: {},
|
|
261
304
|
dataState: null,
|
|
262
|
-
events
|
|
305
|
+
events,
|
|
306
|
+
handlers,
|
|
263
307
|
reactive: false,
|
|
264
308
|
componentName: node.name,
|
|
265
309
|
})
|
|
@@ -269,8 +313,8 @@ function convertComponent(node: IRComponent, cmap: Map<string, string>): TestNod
|
|
|
269
313
|
// Fragment
|
|
270
314
|
// ---------------------------------------------------------------------------
|
|
271
315
|
|
|
272
|
-
function convertFragment(node: IRFragment,
|
|
273
|
-
const children = node.children.map(c => convert(c,
|
|
316
|
+
function convertFragment(node: IRFragment, ctx: ConvertContext): TestNode {
|
|
317
|
+
const children = node.children.map(c => convert(c, ctx))
|
|
274
318
|
|
|
275
319
|
return new TestNode({
|
|
276
320
|
tag: null,
|
|
@@ -283,6 +327,7 @@ function convertFragment(node: IRFragment, cmap: Map<string, string>): TestNode
|
|
|
283
327
|
aria: {},
|
|
284
328
|
dataState: null,
|
|
285
329
|
events: [],
|
|
330
|
+
handlers: {},
|
|
286
331
|
reactive: false,
|
|
287
332
|
componentName: null,
|
|
288
333
|
})
|
|
@@ -304,6 +349,7 @@ function convertSlot(_node: IRSlot): TestNode {
|
|
|
304
349
|
aria: {},
|
|
305
350
|
dataState: null,
|
|
306
351
|
events: [],
|
|
352
|
+
handlers: {},
|
|
307
353
|
reactive: false,
|
|
308
354
|
componentName: null,
|
|
309
355
|
})
|
|
@@ -313,10 +359,10 @@ function convertSlot(_node: IRSlot): TestNode {
|
|
|
313
359
|
// IfStatement
|
|
314
360
|
// ---------------------------------------------------------------------------
|
|
315
361
|
|
|
316
|
-
function convertIfStatement(node: IRIfStatement,
|
|
317
|
-
const children: TestNode[] = [convert(node.consequent,
|
|
362
|
+
function convertIfStatement(node: IRIfStatement, ctx: ConvertContext): TestNode {
|
|
363
|
+
const children: TestNode[] = [convert(node.consequent, ctx)]
|
|
318
364
|
if (node.alternate) {
|
|
319
|
-
children.push(convert(node.alternate,
|
|
365
|
+
children.push(convert(node.alternate, ctx))
|
|
320
366
|
}
|
|
321
367
|
|
|
322
368
|
return new TestNode({
|
|
@@ -330,6 +376,7 @@ function convertIfStatement(node: IRIfStatement, cmap: Map<string, string>): Tes
|
|
|
330
376
|
aria: {},
|
|
331
377
|
dataState: null,
|
|
332
378
|
events: [],
|
|
379
|
+
handlers: {},
|
|
333
380
|
reactive: false,
|
|
334
381
|
componentName: null,
|
|
335
382
|
})
|
|
@@ -339,9 +386,9 @@ function convertIfStatement(node: IRIfStatement, cmap: Map<string, string>): Tes
|
|
|
339
386
|
// Provider
|
|
340
387
|
// ---------------------------------------------------------------------------
|
|
341
388
|
|
|
342
|
-
function convertProvider(node: IRProvider,
|
|
389
|
+
function convertProvider(node: IRProvider, ctx: ConvertContext): TestNode {
|
|
343
390
|
// Transparent — just pass through children
|
|
344
|
-
const children = node.children.map(c => convert(c,
|
|
391
|
+
const children = node.children.map(c => convert(c, ctx))
|
|
345
392
|
|
|
346
393
|
if (children.length === 1) return children[0]
|
|
347
394
|
|
|
@@ -356,6 +403,7 @@ function convertProvider(node: IRProvider, cmap: Map<string, string>): TestNode
|
|
|
356
403
|
aria: {},
|
|
357
404
|
dataState: null,
|
|
358
405
|
events: [],
|
|
406
|
+
handlers: {},
|
|
359
407
|
reactive: false,
|
|
360
408
|
componentName: null,
|
|
361
409
|
})
|
|
@@ -365,9 +413,9 @@ function convertProvider(node: IRProvider, cmap: Map<string, string>): TestNode
|
|
|
365
413
|
// Async
|
|
366
414
|
// ---------------------------------------------------------------------------
|
|
367
415
|
|
|
368
|
-
function convertAsync(node: IRAsync,
|
|
416
|
+
function convertAsync(node: IRAsync, ctx: ConvertContext): TestNode {
|
|
369
417
|
// Represent the resolved content as a fragment; tests assert on final state.
|
|
370
|
-
const children = node.children.map(c => convert(c,
|
|
418
|
+
const children = node.children.map(c => convert(c, ctx))
|
|
371
419
|
|
|
372
420
|
return new TestNode({
|
|
373
421
|
tag: null,
|
|
@@ -380,6 +428,7 @@ function convertAsync(node: IRAsync, cmap: Map<string, string>): TestNode {
|
|
|
380
428
|
aria: {},
|
|
381
429
|
dataState: null,
|
|
382
430
|
events: [],
|
|
431
|
+
handlers: {},
|
|
383
432
|
reactive: false,
|
|
384
433
|
componentName: null,
|
|
385
434
|
})
|
|
@@ -389,6 +438,18 @@ function convertAsync(node: IRAsync, cmap: Map<string, string>): TestNode {
|
|
|
389
438
|
// Constant-based class value resolution
|
|
390
439
|
// ---------------------------------------------------------------------------
|
|
391
440
|
|
|
441
|
+
function refsToHandler(refs: SetterRef[]): EventHandler {
|
|
442
|
+
const setters: string[] = []
|
|
443
|
+
const via: string[] = []
|
|
444
|
+
for (const ref of refs) {
|
|
445
|
+
if (!setters.includes(ref.setter)) setters.push(ref.setter)
|
|
446
|
+
for (const v of ref.via ?? []) {
|
|
447
|
+
if (!via.includes(v)) via.push(v)
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
return { setters, via }
|
|
451
|
+
}
|
|
452
|
+
|
|
392
453
|
function resolveClassValue(value: string, cmap: Map<string, string>): string | null {
|
|
393
454
|
// Simple identifier lookup
|
|
394
455
|
if (cmap.has(value)) {
|
package/src/render.ts
CHANGED
|
@@ -52,7 +52,7 @@ export function renderToTest(source: string, filePath: string, componentName?: s
|
|
|
52
52
|
|
|
53
53
|
const metadata = buildMetadata(ctx)
|
|
54
54
|
const constantMap = resolveConstants(metadata.localConstants)
|
|
55
|
-
const root = irNodeToTestNode(ir, constantMap)
|
|
55
|
+
const root = irNodeToTestNode(ir, constantMap, metadata)
|
|
56
56
|
const signals = metadata.signals.map(s => s.getter)
|
|
57
57
|
const memos = metadata.memos.map(m => m.name)
|
|
58
58
|
const effects = metadata.effects.length
|
|
@@ -119,6 +119,7 @@ function emptyResult(
|
|
|
119
119
|
aria: {},
|
|
120
120
|
dataState: null,
|
|
121
121
|
events: [],
|
|
122
|
+
handlers: {},
|
|
122
123
|
reactive: false,
|
|
123
124
|
componentName: null,
|
|
124
125
|
})
|
package/src/test-node.ts
CHANGED
|
@@ -4,6 +4,11 @@
|
|
|
4
4
|
* Produced by converting the compiler IR into a flat, assertion-friendly shape.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
export interface EventHandler {
|
|
8
|
+
setters: string[]
|
|
9
|
+
via: string[]
|
|
10
|
+
}
|
|
11
|
+
|
|
7
12
|
export interface TestNodeData {
|
|
8
13
|
tag: string | null
|
|
9
14
|
type: 'element' | 'text' | 'expression' | 'conditional' | 'loop' | 'component' | 'fragment'
|
|
@@ -15,6 +20,7 @@ export interface TestNodeData {
|
|
|
15
20
|
aria: Record<string, string>
|
|
16
21
|
dataState: string | null
|
|
17
22
|
events: string[]
|
|
23
|
+
handlers: Partial<Record<string, EventHandler>>
|
|
18
24
|
reactive: boolean
|
|
19
25
|
componentName: string | null
|
|
20
26
|
}
|
|
@@ -36,9 +42,19 @@ export class TestNode implements TestNodeData {
|
|
|
36
42
|
aria: Record<string, string>
|
|
37
43
|
dataState: string | null
|
|
38
44
|
events: string[]
|
|
45
|
+
handlers: Partial<Record<string, EventHandler>>
|
|
39
46
|
reactive: boolean
|
|
40
47
|
componentName: string | null
|
|
41
48
|
|
|
49
|
+
get onClick(): EventHandler | undefined { return this.handlers.click }
|
|
50
|
+
get onInput(): EventHandler | undefined { return this.handlers.input }
|
|
51
|
+
get onChange(): EventHandler | undefined { return this.handlers.change }
|
|
52
|
+
get onSubmit(): EventHandler | undefined { return this.handlers.submit }
|
|
53
|
+
|
|
54
|
+
on(event: string): EventHandler | null {
|
|
55
|
+
return this.handlers[event] ?? null
|
|
56
|
+
}
|
|
57
|
+
|
|
42
58
|
constructor(data: TestNodeData) {
|
|
43
59
|
this.tag = data.tag
|
|
44
60
|
this.type = data.type
|
|
@@ -50,6 +66,7 @@ export class TestNode implements TestNodeData {
|
|
|
50
66
|
this.aria = data.aria
|
|
51
67
|
this.dataState = data.dataState
|
|
52
68
|
this.events = data.events
|
|
69
|
+
this.handlers = data.handlers
|
|
53
70
|
this.reactive = data.reactive
|
|
54
71
|
this.componentName = data.componentName
|
|
55
72
|
}
|