@barefootjs/test 0.2.0 → 0.4.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 +201 -35
- 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 +22 -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 +28 -0
package/dist/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
export { renderToTest } from './render';
|
|
5
5
|
export type { TestResult } from './render';
|
|
6
6
|
export { TestNode } from './test-node';
|
|
7
|
-
export type { TestNodeData, TestNodeQuery } from './test-node';
|
|
7
|
+
export type { TestNodeData, TestNodeQuery, EventHandler } from './test-node';
|
|
8
8
|
export { toStructure } from './structure';
|
|
9
9
|
export { resolveConstants } from './resolve-constants';
|
|
10
10
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AACvC,YAAY,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAE1C,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AACtC,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AACvC,YAAY,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAE1C,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AACtC,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE5E,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAEzC,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -192314,7 +192314,7 @@ function transformExpressionInner(expr, ctx, node, isClientOnly) {
|
|
|
192314
192314
|
}
|
|
192315
192315
|
const ir = transformJsxExpression(expr, ctx, isClientOnly);
|
|
192316
192316
|
if (ir !== null) {
|
|
192317
|
-
if (isClientOnly && ir.type === "conditional") {
|
|
192317
|
+
if ((isClientOnly || shouldAutoDeferReactiveBrand(expr, ctx)) && ir.type === "conditional") {
|
|
192318
192318
|
ir.clientOnly = true;
|
|
192319
192319
|
if (!ir.slotId) {
|
|
192320
192320
|
ir.slotId = generateSlotId(ctx);
|
|
@@ -193665,7 +193665,7 @@ function processAttributes(attributes, ctx) {
|
|
|
193665
193665
|
value = { ...value, templateExpr: rewritten };
|
|
193666
193666
|
}
|
|
193667
193667
|
}
|
|
193668
|
-
if (hasLeadingClientDirective(attr.initializer.expression, ctx.sourceFile)) {
|
|
193668
|
+
if (hasLeadingClientDirective(attr.initializer.expression, ctx.sourceFile) || shouldAutoDeferReactiveBrand(attr.initializer.expression, ctx)) {
|
|
193669
193669
|
clientOnly = true;
|
|
193670
193670
|
}
|
|
193671
193671
|
}
|
|
@@ -194069,6 +194069,16 @@ function isReactiveExpression(expr, ctx, astNode) {
|
|
|
194069
194069
|
}
|
|
194070
194070
|
return false;
|
|
194071
194071
|
}
|
|
194072
|
+
function shouldAutoDeferReactiveBrand(expr, ctx) {
|
|
194073
|
+
const checker = ctx.analyzer.checker;
|
|
194074
|
+
if (!checker)
|
|
194075
|
+
return false;
|
|
194076
|
+
if (!containsReactiveExpression(expr, checker))
|
|
194077
|
+
return false;
|
|
194078
|
+
if (isSignalOrMemoReference(ctx.getJS(expr), ctx))
|
|
194079
|
+
return false;
|
|
194080
|
+
return true;
|
|
194081
|
+
}
|
|
194072
194082
|
function isSignalOrMemoReference(expr, ctx, visited) {
|
|
194073
194083
|
for (const { pattern } of ctx.patterns.signals) {
|
|
194074
194084
|
if (pattern.test(expr))
|
|
@@ -194638,6 +194648,97 @@ var CLIENT_PACKAGE_SOURCES = new Set([
|
|
|
194638
194648
|
"@barefootjs/client",
|
|
194639
194649
|
"@barefootjs/client/runtime"
|
|
194640
194650
|
]);
|
|
194651
|
+
// ../jsx/src/debug.ts
|
|
194652
|
+
function escapeForIdBoundary(name) {
|
|
194653
|
+
return name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
194654
|
+
}
|
|
194655
|
+
function makeIdCallRegex(name) {
|
|
194656
|
+
return new RegExp(`(?:^|[^\\w$])${escapeForIdBoundary(name)}\\s*\\(`);
|
|
194657
|
+
}
|
|
194658
|
+
function buildLocalFunctionSetterMap(meta, setterToSignal) {
|
|
194659
|
+
const setterPatterns = [...setterToSignal.keys()].map((s) => ({ name: s, re: makeIdCallRegex(s) }));
|
|
194660
|
+
const bodies = new Map;
|
|
194661
|
+
for (const fn of meta.localFunctions)
|
|
194662
|
+
bodies.set(fn.name, fn.body);
|
|
194663
|
+
for (const c of meta.localConstants) {
|
|
194664
|
+
if (c.containsArrow && c.value)
|
|
194665
|
+
bodies.set(c.name, c.value);
|
|
194666
|
+
}
|
|
194667
|
+
const fnNamePatterns = [...bodies.keys()].map((n) => ({ name: n, re: makeIdCallRegex(n) }));
|
|
194668
|
+
const directSetters = new Map;
|
|
194669
|
+
const directCalls = new Map;
|
|
194670
|
+
for (const [name, body] of bodies) {
|
|
194671
|
+
const setters = [];
|
|
194672
|
+
for (const { name: setter, re } of setterPatterns) {
|
|
194673
|
+
if (re.test(body))
|
|
194674
|
+
setters.push(setter);
|
|
194675
|
+
}
|
|
194676
|
+
directSetters.set(name, setters);
|
|
194677
|
+
const calls = [];
|
|
194678
|
+
for (const { name: callee, re } of fnNamePatterns) {
|
|
194679
|
+
if (callee !== name && re.test(body))
|
|
194680
|
+
calls.push(callee);
|
|
194681
|
+
}
|
|
194682
|
+
directCalls.set(name, calls);
|
|
194683
|
+
}
|
|
194684
|
+
const resolve2 = (name, stack) => {
|
|
194685
|
+
const out = [];
|
|
194686
|
+
const seen = new Set;
|
|
194687
|
+
for (const setter of directSetters.get(name) ?? []) {
|
|
194688
|
+
if (!seen.has(setter)) {
|
|
194689
|
+
out.push({ setter, chain: [] });
|
|
194690
|
+
seen.add(setter);
|
|
194691
|
+
}
|
|
194692
|
+
}
|
|
194693
|
+
for (const callee of directCalls.get(name) ?? []) {
|
|
194694
|
+
if (stack.has(callee))
|
|
194695
|
+
continue;
|
|
194696
|
+
const sub = resolve2(callee, new Set([...stack, callee]));
|
|
194697
|
+
for (const r of sub) {
|
|
194698
|
+
if (!seen.has(r.setter)) {
|
|
194699
|
+
out.push({ setter: r.setter, chain: [callee, ...r.chain] });
|
|
194700
|
+
seen.add(r.setter);
|
|
194701
|
+
}
|
|
194702
|
+
}
|
|
194703
|
+
}
|
|
194704
|
+
return out;
|
|
194705
|
+
};
|
|
194706
|
+
const result = new Map;
|
|
194707
|
+
for (const name of bodies.keys()) {
|
|
194708
|
+
const resolved = resolve2(name, new Set([name]));
|
|
194709
|
+
if (resolved.length > 0)
|
|
194710
|
+
result.set(name, resolved);
|
|
194711
|
+
}
|
|
194712
|
+
return result;
|
|
194713
|
+
}
|
|
194714
|
+
function resolveSetters(handler, setterToSignal, fnSetters) {
|
|
194715
|
+
const refs = [];
|
|
194716
|
+
const seen = new Set;
|
|
194717
|
+
const trimmed = handler.trim();
|
|
194718
|
+
for (const [setter, signal] of setterToSignal) {
|
|
194719
|
+
if (trimmed === setter || makeIdCallRegex(setter).test(handler)) {
|
|
194720
|
+
if (!seen.has(setter)) {
|
|
194721
|
+
refs.push({ setter, signal });
|
|
194722
|
+
seen.add(setter);
|
|
194723
|
+
}
|
|
194724
|
+
}
|
|
194725
|
+
}
|
|
194726
|
+
for (const [fnName, resolutions] of fnSetters) {
|
|
194727
|
+
if (trimmed === fnName || makeIdCallRegex(fnName).test(handler)) {
|
|
194728
|
+
for (const r of resolutions) {
|
|
194729
|
+
if (!seen.has(r.setter)) {
|
|
194730
|
+
refs.push({
|
|
194731
|
+
setter: r.setter,
|
|
194732
|
+
signal: setterToSignal.get(r.setter) ?? null,
|
|
194733
|
+
via: [fnName, ...r.chain]
|
|
194734
|
+
});
|
|
194735
|
+
seen.add(r.setter);
|
|
194736
|
+
}
|
|
194737
|
+
}
|
|
194738
|
+
}
|
|
194739
|
+
}
|
|
194740
|
+
return refs;
|
|
194741
|
+
}
|
|
194641
194742
|
// src/test-node.ts
|
|
194642
194743
|
class TestNode {
|
|
194643
194744
|
tag;
|
|
@@ -194650,8 +194751,24 @@ class TestNode {
|
|
|
194650
194751
|
aria;
|
|
194651
194752
|
dataState;
|
|
194652
194753
|
events;
|
|
194754
|
+
handlers;
|
|
194653
194755
|
reactive;
|
|
194654
194756
|
componentName;
|
|
194757
|
+
get onClick() {
|
|
194758
|
+
return this.handlers.click;
|
|
194759
|
+
}
|
|
194760
|
+
get onInput() {
|
|
194761
|
+
return this.handlers.input;
|
|
194762
|
+
}
|
|
194763
|
+
get onChange() {
|
|
194764
|
+
return this.handlers.change;
|
|
194765
|
+
}
|
|
194766
|
+
get onSubmit() {
|
|
194767
|
+
return this.handlers.submit;
|
|
194768
|
+
}
|
|
194769
|
+
on(event) {
|
|
194770
|
+
return this.handlers[event];
|
|
194771
|
+
}
|
|
194655
194772
|
constructor(data) {
|
|
194656
194773
|
this.tag = data.tag;
|
|
194657
194774
|
this.type = data.type;
|
|
@@ -194663,6 +194780,7 @@ class TestNode {
|
|
|
194663
194780
|
this.aria = data.aria;
|
|
194664
194781
|
this.dataState = data.dataState;
|
|
194665
194782
|
this.events = data.events;
|
|
194783
|
+
this.handlers = data.handlers;
|
|
194666
194784
|
this.reactive = data.reactive;
|
|
194667
194785
|
this.componentName = data.componentName;
|
|
194668
194786
|
}
|
|
@@ -194710,41 +194828,52 @@ class TestNode {
|
|
|
194710
194828
|
}
|
|
194711
194829
|
|
|
194712
194830
|
// src/ir-to-test-node.ts
|
|
194713
|
-
function irNodeToTestNode(node, constantMap) {
|
|
194831
|
+
function irNodeToTestNode(node, constantMap, metadata) {
|
|
194714
194832
|
const cmap = constantMap ?? new Map;
|
|
194715
|
-
|
|
194833
|
+
const setterToSignal = new Map;
|
|
194834
|
+
const fnSetters = new Map;
|
|
194835
|
+
if (metadata) {
|
|
194836
|
+
for (const s of metadata.signals) {
|
|
194837
|
+
if (s.setter)
|
|
194838
|
+
setterToSignal.set(s.setter, s.getter);
|
|
194839
|
+
}
|
|
194840
|
+
for (const [k, v] of buildLocalFunctionSetterMap(metadata, setterToSignal)) {
|
|
194841
|
+
fnSetters.set(k, v);
|
|
194842
|
+
}
|
|
194843
|
+
}
|
|
194844
|
+
return convert(node, { cmap, setterToSignal, fnSetters });
|
|
194716
194845
|
}
|
|
194717
|
-
function convert(node,
|
|
194846
|
+
function convert(node, ctx) {
|
|
194718
194847
|
switch (node.type) {
|
|
194719
194848
|
case "element":
|
|
194720
|
-
return convertElement(node,
|
|
194849
|
+
return convertElement(node, ctx);
|
|
194721
194850
|
case "text":
|
|
194722
194851
|
return convertText(node);
|
|
194723
194852
|
case "expression":
|
|
194724
194853
|
return convertExpression(node);
|
|
194725
194854
|
case "conditional":
|
|
194726
|
-
return convertConditional(node,
|
|
194855
|
+
return convertConditional(node, ctx);
|
|
194727
194856
|
case "loop":
|
|
194728
|
-
return convertLoop(node,
|
|
194857
|
+
return convertLoop(node, ctx);
|
|
194729
194858
|
case "component":
|
|
194730
|
-
return convertComponent(node,
|
|
194859
|
+
return convertComponent(node, ctx);
|
|
194731
194860
|
case "fragment":
|
|
194732
|
-
return convertFragment(node,
|
|
194861
|
+
return convertFragment(node, ctx);
|
|
194733
194862
|
case "slot":
|
|
194734
194863
|
return convertSlot(node);
|
|
194735
194864
|
case "if-statement":
|
|
194736
|
-
return convertIfStatement(node,
|
|
194865
|
+
return convertIfStatement(node, ctx);
|
|
194737
194866
|
case "provider":
|
|
194738
|
-
return convertProvider(node,
|
|
194867
|
+
return convertProvider(node, ctx);
|
|
194739
194868
|
case "async":
|
|
194740
|
-
return convertAsync(node,
|
|
194869
|
+
return convertAsync(node, ctx);
|
|
194741
194870
|
default: {
|
|
194742
194871
|
const _exhaustive = node;
|
|
194743
194872
|
throw new Error(`Unhandled IR node type: ${_exhaustive.type}`);
|
|
194744
194873
|
}
|
|
194745
194874
|
}
|
|
194746
194875
|
}
|
|
194747
|
-
function convertElement(node,
|
|
194876
|
+
function convertElement(node, ctx) {
|
|
194748
194877
|
const props = {};
|
|
194749
194878
|
const aria = {};
|
|
194750
194879
|
let role = null;
|
|
@@ -194755,8 +194884,8 @@ function convertElement(node, cmap) {
|
|
|
194755
194884
|
if (attr.name === "className" || attr.name === "class") {
|
|
194756
194885
|
if (typeof value === "string") {
|
|
194757
194886
|
const isDynamic = attr.value.kind === "expression" || attr.value.kind === "template" || attr.value.kind === "spread";
|
|
194758
|
-
if (isDynamic && cmap.size > 0) {
|
|
194759
|
-
const resolved = resolveClassValue(value, cmap);
|
|
194887
|
+
if (isDynamic && ctx.cmap.size > 0) {
|
|
194888
|
+
const resolved = resolveClassValue(value, ctx.cmap);
|
|
194760
194889
|
if (resolved !== null) {
|
|
194761
194890
|
classes = resolved.split(/\s+/).filter(Boolean);
|
|
194762
194891
|
continue;
|
|
@@ -194787,7 +194916,12 @@ function convertElement(node, cmap) {
|
|
|
194787
194916
|
props[attr.name] = value;
|
|
194788
194917
|
}
|
|
194789
194918
|
const events = node.events.map((e) => e.name);
|
|
194790
|
-
const
|
|
194919
|
+
const handlers = {};
|
|
194920
|
+
for (const event of node.events) {
|
|
194921
|
+
const refs = resolveSetters(event.handler, ctx.setterToSignal, ctx.fnSetters);
|
|
194922
|
+
handlers[event.name] = refsToHandler(refs);
|
|
194923
|
+
}
|
|
194924
|
+
const children = node.children.map((c) => convert(c, ctx));
|
|
194791
194925
|
return new TestNode({
|
|
194792
194926
|
tag: node.tag,
|
|
194793
194927
|
type: "element",
|
|
@@ -194799,6 +194933,7 @@ function convertElement(node, cmap) {
|
|
|
194799
194933
|
aria,
|
|
194800
194934
|
dataState,
|
|
194801
194935
|
events,
|
|
194936
|
+
handlers,
|
|
194802
194937
|
reactive: false,
|
|
194803
194938
|
componentName: null
|
|
194804
194939
|
});
|
|
@@ -194815,6 +194950,7 @@ function convertText(node) {
|
|
|
194815
194950
|
aria: {},
|
|
194816
194951
|
dataState: null,
|
|
194817
194952
|
events: [],
|
|
194953
|
+
handlers: {},
|
|
194818
194954
|
reactive: false,
|
|
194819
194955
|
componentName: null
|
|
194820
194956
|
});
|
|
@@ -194831,14 +194967,15 @@ function convertExpression(node) {
|
|
|
194831
194967
|
aria: {},
|
|
194832
194968
|
dataState: null,
|
|
194833
194969
|
events: [],
|
|
194970
|
+
handlers: {},
|
|
194834
194971
|
reactive: node.reactive,
|
|
194835
194972
|
componentName: null
|
|
194836
194973
|
});
|
|
194837
194974
|
}
|
|
194838
|
-
function convertConditional(node,
|
|
194839
|
-
const children = [convert(node.whenTrue,
|
|
194975
|
+
function convertConditional(node, ctx) {
|
|
194976
|
+
const children = [convert(node.whenTrue, ctx)];
|
|
194840
194977
|
if (node.whenFalse) {
|
|
194841
|
-
children.push(convert(node.whenFalse,
|
|
194978
|
+
children.push(convert(node.whenFalse, ctx));
|
|
194842
194979
|
}
|
|
194843
194980
|
return new TestNode({
|
|
194844
194981
|
tag: null,
|
|
@@ -194851,12 +194988,13 @@ function convertConditional(node, cmap) {
|
|
|
194851
194988
|
aria: {},
|
|
194852
194989
|
dataState: null,
|
|
194853
194990
|
events: [],
|
|
194991
|
+
handlers: {},
|
|
194854
194992
|
reactive: node.reactive,
|
|
194855
194993
|
componentName: null
|
|
194856
194994
|
});
|
|
194857
194995
|
}
|
|
194858
|
-
function convertLoop(node,
|
|
194859
|
-
const children = node.children.map((c) => convert(c,
|
|
194996
|
+
function convertLoop(node, ctx) {
|
|
194997
|
+
const children = node.children.map((c) => convert(c, ctx));
|
|
194860
194998
|
return new TestNode({
|
|
194861
194999
|
tag: null,
|
|
194862
195000
|
type: "loop",
|
|
@@ -194868,12 +195006,15 @@ function convertLoop(node, cmap) {
|
|
|
194868
195006
|
aria: {},
|
|
194869
195007
|
dataState: null,
|
|
194870
195008
|
events: [],
|
|
195009
|
+
handlers: {},
|
|
194871
195010
|
reactive: false,
|
|
194872
195011
|
componentName: null
|
|
194873
195012
|
});
|
|
194874
195013
|
}
|
|
194875
|
-
function convertComponent(node,
|
|
195014
|
+
function convertComponent(node, ctx) {
|
|
194876
195015
|
const props = {};
|
|
195016
|
+
const events = [];
|
|
195017
|
+
const handlers = {};
|
|
194877
195018
|
for (const prop of node.props) {
|
|
194878
195019
|
switch (prop.value.kind) {
|
|
194879
195020
|
case "literal":
|
|
@@ -194894,8 +195035,13 @@ function convertComponent(node, cmap) {
|
|
|
194894
195035
|
props[prop.name] = null;
|
|
194895
195036
|
break;
|
|
194896
195037
|
}
|
|
195038
|
+
if (/^on[A-Z]/.test(prop.name) && prop.value.kind === "expression") {
|
|
195039
|
+
const eventName = prop.name.charAt(2).toLowerCase() + prop.name.slice(3);
|
|
195040
|
+
events.push(eventName);
|
|
195041
|
+
handlers[eventName] = refsToHandler(resolveSetters(prop.value.expr, ctx.setterToSignal, ctx.fnSetters));
|
|
195042
|
+
}
|
|
194897
195043
|
}
|
|
194898
|
-
const children = node.children.map((c) => convert(c,
|
|
195044
|
+
const children = node.children.map((c) => convert(c, ctx));
|
|
194899
195045
|
return new TestNode({
|
|
194900
195046
|
tag: null,
|
|
194901
195047
|
type: "component",
|
|
@@ -194906,13 +195052,14 @@ function convertComponent(node, cmap) {
|
|
|
194906
195052
|
role: null,
|
|
194907
195053
|
aria: {},
|
|
194908
195054
|
dataState: null,
|
|
194909
|
-
events
|
|
195055
|
+
events,
|
|
195056
|
+
handlers,
|
|
194910
195057
|
reactive: false,
|
|
194911
195058
|
componentName: node.name
|
|
194912
195059
|
});
|
|
194913
195060
|
}
|
|
194914
|
-
function convertFragment(node,
|
|
194915
|
-
const children = node.children.map((c) => convert(c,
|
|
195061
|
+
function convertFragment(node, ctx) {
|
|
195062
|
+
const children = node.children.map((c) => convert(c, ctx));
|
|
194916
195063
|
return new TestNode({
|
|
194917
195064
|
tag: null,
|
|
194918
195065
|
type: "fragment",
|
|
@@ -194924,6 +195071,7 @@ function convertFragment(node, cmap) {
|
|
|
194924
195071
|
aria: {},
|
|
194925
195072
|
dataState: null,
|
|
194926
195073
|
events: [],
|
|
195074
|
+
handlers: {},
|
|
194927
195075
|
reactive: false,
|
|
194928
195076
|
componentName: null
|
|
194929
195077
|
});
|
|
@@ -194940,14 +195088,15 @@ function convertSlot(_node) {
|
|
|
194940
195088
|
aria: {},
|
|
194941
195089
|
dataState: null,
|
|
194942
195090
|
events: [],
|
|
195091
|
+
handlers: {},
|
|
194943
195092
|
reactive: false,
|
|
194944
195093
|
componentName: null
|
|
194945
195094
|
});
|
|
194946
195095
|
}
|
|
194947
|
-
function convertIfStatement(node,
|
|
194948
|
-
const children = [convert(node.consequent,
|
|
195096
|
+
function convertIfStatement(node, ctx) {
|
|
195097
|
+
const children = [convert(node.consequent, ctx)];
|
|
194949
195098
|
if (node.alternate) {
|
|
194950
|
-
children.push(convert(node.alternate,
|
|
195099
|
+
children.push(convert(node.alternate, ctx));
|
|
194951
195100
|
}
|
|
194952
195101
|
return new TestNode({
|
|
194953
195102
|
tag: null,
|
|
@@ -194960,12 +195109,13 @@ function convertIfStatement(node, cmap) {
|
|
|
194960
195109
|
aria: {},
|
|
194961
195110
|
dataState: null,
|
|
194962
195111
|
events: [],
|
|
195112
|
+
handlers: {},
|
|
194963
195113
|
reactive: false,
|
|
194964
195114
|
componentName: null
|
|
194965
195115
|
});
|
|
194966
195116
|
}
|
|
194967
|
-
function convertProvider(node,
|
|
194968
|
-
const children = node.children.map((c) => convert(c,
|
|
195117
|
+
function convertProvider(node, ctx) {
|
|
195118
|
+
const children = node.children.map((c) => convert(c, ctx));
|
|
194969
195119
|
if (children.length === 1)
|
|
194970
195120
|
return children[0];
|
|
194971
195121
|
return new TestNode({
|
|
@@ -194979,12 +195129,13 @@ function convertProvider(node, cmap) {
|
|
|
194979
195129
|
aria: {},
|
|
194980
195130
|
dataState: null,
|
|
194981
195131
|
events: [],
|
|
195132
|
+
handlers: {},
|
|
194982
195133
|
reactive: false,
|
|
194983
195134
|
componentName: null
|
|
194984
195135
|
});
|
|
194985
195136
|
}
|
|
194986
|
-
function convertAsync(node,
|
|
194987
|
-
const children = node.children.map((c) => convert(c,
|
|
195137
|
+
function convertAsync(node, ctx) {
|
|
195138
|
+
const children = node.children.map((c) => convert(c, ctx));
|
|
194988
195139
|
return new TestNode({
|
|
194989
195140
|
tag: null,
|
|
194990
195141
|
type: "fragment",
|
|
@@ -194996,10 +195147,24 @@ function convertAsync(node, cmap) {
|
|
|
194996
195147
|
aria: {},
|
|
194997
195148
|
dataState: null,
|
|
194998
195149
|
events: [],
|
|
195150
|
+
handlers: {},
|
|
194999
195151
|
reactive: false,
|
|
195000
195152
|
componentName: null
|
|
195001
195153
|
});
|
|
195002
195154
|
}
|
|
195155
|
+
function refsToHandler(refs) {
|
|
195156
|
+
const setters = [];
|
|
195157
|
+
const via = [];
|
|
195158
|
+
for (const ref of refs) {
|
|
195159
|
+
if (!setters.includes(ref.setter))
|
|
195160
|
+
setters.push(ref.setter);
|
|
195161
|
+
for (const v of ref.via ?? []) {
|
|
195162
|
+
if (!via.includes(v))
|
|
195163
|
+
via.push(v);
|
|
195164
|
+
}
|
|
195165
|
+
}
|
|
195166
|
+
return { setters, via };
|
|
195167
|
+
}
|
|
195003
195168
|
function resolveClassValue(value, cmap) {
|
|
195004
195169
|
if (cmap.has(value)) {
|
|
195005
195170
|
return cmap.get(value);
|
|
@@ -195175,7 +195340,7 @@ function renderToTest(source, filePath, componentName) {
|
|
|
195175
195340
|
}
|
|
195176
195341
|
const metadata = buildMetadata2(ctx);
|
|
195177
195342
|
const constantMap = resolveConstants(metadata.localConstants);
|
|
195178
|
-
const root = irNodeToTestNode(ir, constantMap);
|
|
195343
|
+
const root = irNodeToTestNode(ir, constantMap, metadata);
|
|
195179
195344
|
const signals = metadata.signals.map((s) => s.getter);
|
|
195180
195345
|
const memos = metadata.memos.map((m) => m.name);
|
|
195181
195346
|
const effects = metadata.effects.length;
|
|
@@ -195232,6 +195397,7 @@ function emptyResult(componentName, isClient, errors) {
|
|
|
195232
195397
|
aria: {},
|
|
195233
195398
|
dataState: null,
|
|
195234
195399
|
events: [],
|
|
195400
|
+
handlers: {},
|
|
195235
195401
|
reactive: false,
|
|
195236
195402
|
componentName: null
|
|
195237
195403
|
});
|
|
@@ -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,20 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Produced by converting the compiler IR into a flat, assertion-friendly shape.
|
|
5
5
|
*/
|
|
6
|
+
/**
|
|
7
|
+
* Statically-resolved wiring for a single event handler.
|
|
8
|
+
*
|
|
9
|
+
* `setters`/`via` are populated only for handlers that reach raw signal setters
|
|
10
|
+
* declared in the component — directly (`onClick={() => setX(1)}`) or through
|
|
11
|
+
* local helper functions (`via`). Handlers backed by a library's
|
|
12
|
+
* property-access method — e.g. `@barefootjs/form`'s `name.handleInput` or
|
|
13
|
+
* `form.handleSubmit` — register the event but report empty `setters`/`via`,
|
|
14
|
+
* because the setter calls live inside the library, not the component body.
|
|
15
|
+
*/
|
|
16
|
+
export interface EventHandler {
|
|
17
|
+
setters: string[];
|
|
18
|
+
via: string[];
|
|
19
|
+
}
|
|
6
20
|
export interface TestNodeData {
|
|
7
21
|
tag: string | null;
|
|
8
22
|
type: 'element' | 'text' | 'expression' | 'conditional' | 'loop' | 'component' | 'fragment';
|
|
@@ -14,6 +28,7 @@ export interface TestNodeData {
|
|
|
14
28
|
aria: Record<string, string>;
|
|
15
29
|
dataState: string | null;
|
|
16
30
|
events: string[];
|
|
31
|
+
handlers: Partial<Record<string, EventHandler>>;
|
|
17
32
|
reactive: boolean;
|
|
18
33
|
componentName: string | null;
|
|
19
34
|
}
|
|
@@ -33,8 +48,15 @@ export declare class TestNode implements TestNodeData {
|
|
|
33
48
|
aria: Record<string, string>;
|
|
34
49
|
dataState: string | null;
|
|
35
50
|
events: string[];
|
|
51
|
+
handlers: Partial<Record<string, EventHandler>>;
|
|
36
52
|
reactive: boolean;
|
|
37
53
|
componentName: string | null;
|
|
54
|
+
get onClick(): EventHandler | undefined;
|
|
55
|
+
get onInput(): EventHandler | undefined;
|
|
56
|
+
get onChange(): EventHandler | undefined;
|
|
57
|
+
get onSubmit(): EventHandler | undefined;
|
|
58
|
+
/** Returns the handler wired to `event`, or `undefined` if none — matching the shorthand getters. */
|
|
59
|
+
on(event: string): EventHandler | undefined;
|
|
38
60
|
constructor(data: TestNodeData);
|
|
39
61
|
/** Return the first descendant (or self) matching the query. */
|
|
40
62
|
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;;;;;;;;;GASG;AACH,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,qGAAqG;IACrG,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAE1C;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.4.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.4.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,21 @@
|
|
|
4
4
|
* Produced by converting the compiler IR into a flat, assertion-friendly shape.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Statically-resolved wiring for a single event handler.
|
|
9
|
+
*
|
|
10
|
+
* `setters`/`via` are populated only for handlers that reach raw signal setters
|
|
11
|
+
* declared in the component — directly (`onClick={() => setX(1)}`) or through
|
|
12
|
+
* local helper functions (`via`). Handlers backed by a library's
|
|
13
|
+
* property-access method — e.g. `@barefootjs/form`'s `name.handleInput` or
|
|
14
|
+
* `form.handleSubmit` — register the event but report empty `setters`/`via`,
|
|
15
|
+
* because the setter calls live inside the library, not the component body.
|
|
16
|
+
*/
|
|
17
|
+
export interface EventHandler {
|
|
18
|
+
setters: string[]
|
|
19
|
+
via: string[]
|
|
20
|
+
}
|
|
21
|
+
|
|
7
22
|
export interface TestNodeData {
|
|
8
23
|
tag: string | null
|
|
9
24
|
type: 'element' | 'text' | 'expression' | 'conditional' | 'loop' | 'component' | 'fragment'
|
|
@@ -15,6 +30,7 @@ export interface TestNodeData {
|
|
|
15
30
|
aria: Record<string, string>
|
|
16
31
|
dataState: string | null
|
|
17
32
|
events: string[]
|
|
33
|
+
handlers: Partial<Record<string, EventHandler>>
|
|
18
34
|
reactive: boolean
|
|
19
35
|
componentName: string | null
|
|
20
36
|
}
|
|
@@ -36,9 +52,20 @@ export class TestNode implements TestNodeData {
|
|
|
36
52
|
aria: Record<string, string>
|
|
37
53
|
dataState: string | null
|
|
38
54
|
events: string[]
|
|
55
|
+
handlers: Partial<Record<string, EventHandler>>
|
|
39
56
|
reactive: boolean
|
|
40
57
|
componentName: string | null
|
|
41
58
|
|
|
59
|
+
get onClick(): EventHandler | undefined { return this.handlers.click }
|
|
60
|
+
get onInput(): EventHandler | undefined { return this.handlers.input }
|
|
61
|
+
get onChange(): EventHandler | undefined { return this.handlers.change }
|
|
62
|
+
get onSubmit(): EventHandler | undefined { return this.handlers.submit }
|
|
63
|
+
|
|
64
|
+
/** Returns the handler wired to `event`, or `undefined` if none — matching the shorthand getters. */
|
|
65
|
+
on(event: string): EventHandler | undefined {
|
|
66
|
+
return this.handlers[event]
|
|
67
|
+
}
|
|
68
|
+
|
|
42
69
|
constructor(data: TestNodeData) {
|
|
43
70
|
this.tag = data.tag
|
|
44
71
|
this.type = data.type
|
|
@@ -50,6 +77,7 @@ export class TestNode implements TestNodeData {
|
|
|
50
77
|
this.aria = data.aria
|
|
51
78
|
this.dataState = data.dataState
|
|
52
79
|
this.events = data.events
|
|
80
|
+
this.handlers = data.handlers
|
|
53
81
|
this.reactive = data.reactive
|
|
54
82
|
this.componentName = data.componentName
|
|
55
83
|
}
|