@barefootjs/test 0.6.0 → 0.7.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.
Files changed (2) hide show
  1. package/dist/index.js +233 -2
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -190459,7 +190459,6 @@ var UNSUPPORTED_METHODS = new Set([
190459
190459
  "some",
190460
190460
  "forEach",
190461
190461
  "flatMap",
190462
- "flat",
190463
190462
  "replaceAll",
190464
190463
  "charAt",
190465
190464
  "charCodeAt",
@@ -190471,6 +190470,9 @@ var UNSUPPORTED_METHODS = new Set([
190471
190470
  "matchAll",
190472
190471
  "search"
190473
190472
  ]);
190473
+ var UNSUPPORTED_METHOD_REASONS = {
190474
+ forEach: `'.forEach()' returns undefined and has no template-position meaning. ` + `Use it for side effects inside an event handler or createEffect callback ` + `(client JS), or use '.map(...)' if you meant to render each item.`
190475
+ };
190474
190476
  var LOWERED_ARRAY_METHODS = new Set([
190475
190477
  "includes",
190476
190478
  "indexOf",
@@ -190558,6 +190560,32 @@ function convertNode(node, raw) {
190558
190560
  if (callee.property === "reverse" || callee.property === "toReversed") {
190559
190561
  return { kind: "array-method", method: callee.property, object: callee.object, args };
190560
190562
  }
190563
+ if (callee.property === "flat") {
190564
+ const depthNode = node.arguments[0];
190565
+ let flatDepth;
190566
+ if (depthNode === undefined) {
190567
+ flatDepth = 1;
190568
+ } else if (import_typescript8.default.isIdentifier(depthNode) && depthNode.text === "Infinity") {
190569
+ flatDepth = "infinity";
190570
+ } else {
190571
+ let n;
190572
+ if (import_typescript8.default.isNumericLiteral(depthNode)) {
190573
+ n = Number(depthNode.text);
190574
+ } else if (import_typescript8.default.isPrefixUnaryExpression(depthNode) && depthNode.operator === import_typescript8.default.SyntaxKind.MinusToken && import_typescript8.default.isNumericLiteral(depthNode.operand)) {
190575
+ n = -Number(depthNode.operand.text);
190576
+ }
190577
+ if (n === undefined || Number.isNaN(n)) {
190578
+ return {
190579
+ kind: "unsupported",
190580
+ raw,
190581
+ reason: `\`.flat(depth)\` needs a literal integer or \`Infinity\` depth — a computed depth can't be resolved at template time. Use a literal depth, or pre-compute the value before the template.`
190582
+ };
190583
+ }
190584
+ const truncated = Math.trunc(n);
190585
+ flatDepth = truncated < 0 ? 0 : truncated;
190586
+ }
190587
+ return { kind: "array-method", method: "flat", object: callee.object, args: [], flatDepth };
190588
+ }
190561
190589
  if (callee.property === "toLowerCase") {
190562
190590
  return { kind: "array-method", method: "toLowerCase", object: callee.object, args };
190563
190591
  }
@@ -190641,6 +190669,50 @@ function convertNode(node, raw) {
190641
190669
  ` + `(reverse the operands for descending order). ` + `Wrap the call in /* @client */ to evaluate at hydration.`
190642
190670
  };
190643
190671
  }
190672
+ if ((callee.property === "reduce" || callee.property === "reduceRight") && node.arguments.length === 2) {
190673
+ const reduceOp = extractReduceOpFromTS(node.arguments[0], node.arguments[1]);
190674
+ if (reduceOp) {
190675
+ return {
190676
+ kind: "array-method",
190677
+ method: callee.property,
190678
+ object: callee.object,
190679
+ args: [],
190680
+ reduceOp
190681
+ };
190682
+ }
190683
+ const m = callee.property;
190684
+ return {
190685
+ kind: "unsupported",
190686
+ raw,
190687
+ reason: `Reduce shape not supported. Accepted (arithmetic fold, explicit init):
190688
+ ` + ` arr.${m}((acc, x) => acc + x, 0)
190689
+ ` + ` arr.${m}((acc, x) => acc + x.field, 0)
190690
+ ` + ` arr.${m}((acc, x) => acc * x.field, 1)
190691
+ ` + ` arr.${m}((acc, x) => acc + x.field, '') (string concat)
190692
+ ` + `The accumulator must be the left operand and the initial ` + `value a number / string literal. ` + `Wrap the call in /* @client */ to evaluate at hydration.`
190693
+ };
190694
+ }
190695
+ if (callee.property === "flatMap") {
190696
+ const flatMapOp = node.arguments.length === 1 ? extractFlatMapOpFromTS(node.arguments[0]) : null;
190697
+ if (flatMapOp) {
190698
+ return {
190699
+ kind: "array-method",
190700
+ method: "flatMap",
190701
+ object: callee.object,
190702
+ args: [],
190703
+ flatMapOp
190704
+ };
190705
+ }
190706
+ return {
190707
+ kind: "unsupported",
190708
+ raw,
190709
+ reason: `flatMap shape not supported. Accepted (self / field leaves, no thisArg):
190710
+ ` + ` arr.flatMap(i => i) (flatten one level)
190711
+ ` + ` arr.flatMap(i => i.field) (flatten a per-item array field)
190712
+ ` + ` arr.flatMap(i => [i.a, i.b]) (gather per-item fields)
190713
+ ` + `Richer callbacks (computed / nested access, arithmetic, calls, ` + `literal elements) and the 2-arg \`flatMap(fn, thisArg)\` form ` + `aren't lowered. Wrap the call in /* @client */ to evaluate at hydration.`
190714
+ };
190715
+ }
190644
190716
  }
190645
190717
  return { kind: "call", callee, args };
190646
190718
  }
@@ -190959,6 +191031,119 @@ function classifySortOperand(expr, paramA, paramB) {
190959
191031
  }
190960
191032
  return null;
190961
191033
  }
191034
+ function extractReduceOpFromTS(reducerNode, initNode) {
191035
+ const init = classifyReduceInit(initNode);
191036
+ if (!init)
191037
+ return null;
191038
+ if (!import_typescript8.default.isArrowFunction(reducerNode) && !import_typescript8.default.isFunctionExpression(reducerNode))
191039
+ return null;
191040
+ if (reducerNode.parameters.length !== 2)
191041
+ return null;
191042
+ const pAcc = reducerNode.parameters[0];
191043
+ const pItem = reducerNode.parameters[1];
191044
+ if (!import_typescript8.default.isIdentifier(pAcc.name) || !import_typescript8.default.isIdentifier(pItem.name))
191045
+ return null;
191046
+ const paramAcc = pAcc.name.text;
191047
+ const paramItem = pItem.name.text;
191048
+ let body;
191049
+ if (import_typescript8.default.isArrowFunction(reducerNode) && !import_typescript8.default.isBlock(reducerNode.body)) {
191050
+ body = reducerNode.body;
191051
+ } else {
191052
+ const block = reducerNode.body;
191053
+ const stmts = block.statements;
191054
+ if (stmts.length !== 1 || !import_typescript8.default.isReturnStatement(stmts[0]) || !stmts[0].expression)
191055
+ return null;
191056
+ body = stmts[0].expression;
191057
+ }
191058
+ const raw = body.getText();
191059
+ const expr = unwrapParens(body);
191060
+ if (!import_typescript8.default.isBinaryExpression(expr))
191061
+ return null;
191062
+ let op;
191063
+ if (expr.operatorToken.kind === import_typescript8.default.SyntaxKind.PlusToken)
191064
+ op = "+";
191065
+ else if (expr.operatorToken.kind === import_typescript8.default.SyntaxKind.AsteriskToken)
191066
+ op = "*";
191067
+ else
191068
+ return null;
191069
+ const left = unwrapParens(expr.left);
191070
+ if (!import_typescript8.default.isIdentifier(left) || left.text !== paramAcc)
191071
+ return null;
191072
+ const key = classifyReduceKey(unwrapParens(expr.right), paramItem);
191073
+ if (!key)
191074
+ return null;
191075
+ const type2 = init.type;
191076
+ if (type2 === "string" && op !== "+")
191077
+ return null;
191078
+ return { op, key, type: type2, init: init.value, raw, paramAcc, paramItem };
191079
+ }
191080
+ function extractFlatMapOpFromTS(cbNode) {
191081
+ if (!import_typescript8.default.isArrowFunction(cbNode) && !import_typescript8.default.isFunctionExpression(cbNode))
191082
+ return null;
191083
+ if (cbNode.parameters.length !== 1)
191084
+ return null;
191085
+ const p = cbNode.parameters[0];
191086
+ if (!import_typescript8.default.isIdentifier(p.name))
191087
+ return null;
191088
+ const param = p.name.text;
191089
+ let body;
191090
+ if (import_typescript8.default.isArrowFunction(cbNode) && !import_typescript8.default.isBlock(cbNode.body)) {
191091
+ body = cbNode.body;
191092
+ } else {
191093
+ const block = cbNode.body;
191094
+ const stmts = block.statements;
191095
+ if (stmts.length !== 1 || !import_typescript8.default.isReturnStatement(stmts[0]) || !stmts[0].expression)
191096
+ return null;
191097
+ body = stmts[0].expression;
191098
+ }
191099
+ const raw = body.getText();
191100
+ const inner = unwrapParens(body);
191101
+ if (import_typescript8.default.isArrayLiteralExpression(inner)) {
191102
+ if (inner.elements.length === 0)
191103
+ return null;
191104
+ const elements = [];
191105
+ for (const el of inner.elements) {
191106
+ if (import_typescript8.default.isSpreadElement(el) || import_typescript8.default.isOmittedExpression(el))
191107
+ return null;
191108
+ const leaf2 = classifyReduceKey(unwrapParens(el), param);
191109
+ if (!leaf2)
191110
+ return null;
191111
+ elements.push(leaf2);
191112
+ }
191113
+ return { projection: { kind: "tuple", elements }, param, raw };
191114
+ }
191115
+ const leaf = classifyReduceKey(inner, param);
191116
+ if (!leaf)
191117
+ return null;
191118
+ return { projection: leaf, param, raw };
191119
+ }
191120
+ function classifyReduceKey(expr, paramItem) {
191121
+ if (import_typescript8.default.isIdentifier(expr)) {
191122
+ return expr.text === paramItem ? { kind: "self" } : null;
191123
+ }
191124
+ if (import_typescript8.default.isPropertyAccessExpression(expr) && import_typescript8.default.isIdentifier(expr.expression)) {
191125
+ if (expr.expression.text === paramItem)
191126
+ return { kind: "field", field: expr.name.text };
191127
+ }
191128
+ return null;
191129
+ }
191130
+ function classifyReduceInit(node) {
191131
+ let n = unwrapParens(node);
191132
+ if (import_typescript8.default.isPrefixUnaryExpression(n) && n.operator === import_typescript8.default.SyntaxKind.MinusToken) {
191133
+ if (import_typescript8.default.isNumericLiteral(n.operand))
191134
+ return { type: "numeric", value: "-" + n.operand.text };
191135
+ return null;
191136
+ }
191137
+ if (import_typescript8.default.isNumericLiteral(n))
191138
+ return { type: "numeric", value: n.text };
191139
+ if (import_typescript8.default.isStringLiteral(n)) {
191140
+ const raw = n.getText();
191141
+ if (raw.length < 2 || raw.slice(1, -1) !== n.text)
191142
+ return null;
191143
+ return { type: "string", value: n.text };
191144
+ }
191145
+ return null;
191146
+ }
190962
191147
  function collectDestructureBindings(pattern, pathPrefix, fieldMap, raw, excludedTopKeys) {
190963
191148
  let restName;
190964
191149
  for (const el of pattern.elements) {
@@ -191267,6 +191452,15 @@ function substituteDestructuredFields(expr, fieldMap, syntheticParam, restName)
191267
191452
  if (e.method === "sort" || e.method === "toSorted") {
191268
191453
  return { kind: "array-method", method: e.method, object: walk(e.object), args: [], comparator: e.comparator };
191269
191454
  }
191455
+ if (e.method === "reduce" || e.method === "reduceRight") {
191456
+ return { kind: "array-method", method: e.method, object: walk(e.object), args: [], reduceOp: e.reduceOp };
191457
+ }
191458
+ if (e.method === "flat") {
191459
+ return { kind: "array-method", method: "flat", object: walk(e.object), args: [], flatDepth: e.flatDepth };
191460
+ }
191461
+ if (e.method === "flatMap") {
191462
+ return { kind: "array-method", method: "flatMap", object: walk(e.object), args: [], flatMapOp: e.flatMapOp };
191463
+ }
191270
191464
  return { kind: "array-method", method: e.method, object: walk(e.object), args: e.args.map(walk) };
191271
191465
  case "literal":
191272
191466
  case "unsupported":
@@ -191387,7 +191581,8 @@ function checkSupport(expr) {
191387
191581
  return {
191388
191582
  supported: false,
191389
191583
  level: "L5_UNSUPPORTED",
191390
- reason: `Method '${methodName}()' has no template lowering and requires client-side evaluation. Wrap the expression in /* @client */ to defer it to hydration, or pre-compute the value before rendering.`
191584
+ selfContained: true,
191585
+ reason: UNSUPPORTED_METHOD_REASONS[methodName] ?? `'${methodName}()' can't render on the server. Pre-compute the value, or add /* @client */ for client-only (no SSR).`
191391
191586
  };
191392
191587
  }
191393
191588
  }
@@ -192387,6 +192582,7 @@ function transformComponentElement(node, ctx, name) {
192387
192582
  children,
192388
192583
  template: name.toLowerCase(),
192389
192584
  slotId,
192585
+ ...isDynamicTagLocal(name, ctx) ? { dynamicTag: true } : {},
192390
192586
  loc: getSourceLocation(node, ctx.sourceFile, ctx.filePath)
192391
192587
  };
192392
192588
  }
@@ -192402,6 +192598,7 @@ function transformSelfClosingComponent(node, ctx, name) {
192402
192598
  children: [],
192403
192599
  template: name.toLowerCase(),
192404
192600
  slotId,
192601
+ ...isDynamicTagLocal(name, ctx) ? { dynamicTag: true } : {},
192405
192602
  loc: getSourceLocation(node, ctx.sourceFile, ctx.filePath)
192406
192603
  };
192407
192604
  }
@@ -194094,6 +194291,40 @@ function findLocalConst(name, ctx) {
194094
194291
  const pool = fnScoped.length > 0 ? fnScoped : matches;
194095
194292
  return pool[pool.length - 1];
194096
194293
  }
194294
+ function isDynamicTagLocal(name, ctx) {
194295
+ if (!hasDynamicTagBinding(name, ctx.sourceFile))
194296
+ return false;
194297
+ const a = ctx.analyzer;
194298
+ if (a.jsxConstants?.has(name))
194299
+ return false;
194300
+ if (a.jsxFunctions?.has(name))
194301
+ return false;
194302
+ if (a.jsxMultiReturnFunctions?.has(name))
194303
+ return false;
194304
+ if (a.inlineableJsxConsts?.has(name))
194305
+ return false;
194306
+ return true;
194307
+ }
194308
+ function hasDynamicTagBinding(name, sourceFile) {
194309
+ let found = false;
194310
+ const visit2 = (node) => {
194311
+ if (found)
194312
+ return;
194313
+ if (import_typescript11.default.isVariableDeclaration(node) && import_typescript11.default.isIdentifier(node.name) && node.name.text === name && node.initializer) {
194314
+ let init = node.initializer;
194315
+ while (import_typescript11.default.isAsExpression(init) || import_typescript11.default.isSatisfiesExpression(init) || import_typescript11.default.isParenthesizedExpression(init) || import_typescript11.default.isNonNullExpression(init)) {
194316
+ init = init.expression;
194317
+ }
194318
+ if (import_typescript11.default.isPropertyAccessExpression(init) && init.name.text === "tag") {
194319
+ found = true;
194320
+ return;
194321
+ }
194322
+ }
194323
+ import_typescript11.default.forEachChild(node, visit2);
194324
+ };
194325
+ visit2(sourceFile);
194326
+ return found;
194327
+ }
194097
194328
  function tryResolveIdentifierAsTemplateLiteral(ident, ctx) {
194098
194329
  const constInfo = findLocalConst(ident.text, ctx);
194099
194330
  if (!constInfo)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barefootjs/test",
3
- "version": "0.6.0",
3
+ "version": "0.7.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.6.0"
42
+ "@barefootjs/jsx": "0.7.0"
43
43
  },
44
44
  "devDependencies": {
45
45
  "typescript": "^5.0.0"