@barefootjs/test 0.5.3 → 0.6.1

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 +261 -17
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -190459,15 +190459,7 @@ var UNSUPPORTED_METHODS = new Set([
190459
190459
  "some",
190460
190460
  "forEach",
190461
190461
  "flatMap",
190462
- "flat",
190463
- "split",
190464
- "startsWith",
190465
- "endsWith",
190466
- "replace",
190467
190462
  "replaceAll",
190468
- "repeat",
190469
- "padStart",
190470
- "padEnd",
190471
190463
  "charAt",
190472
190464
  "charCodeAt",
190473
190465
  "codePointAt",
@@ -190478,6 +190470,15 @@ var UNSUPPORTED_METHODS = new Set([
190478
190470
  "matchAll",
190479
190471
  "search"
190480
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
+ };
190476
+ var LOWERED_ARRAY_METHODS = new Set([
190477
+ "includes",
190478
+ "indexOf",
190479
+ "lastIndexOf",
190480
+ "concat"
190481
+ ]);
190481
190482
  function parseExpression(expr) {
190482
190483
  const trimmed = expr.trim();
190483
190484
  if (!trimmed) {
@@ -190538,7 +190539,7 @@ function convertNode(node, raw) {
190538
190539
  }
190539
190540
  }
190540
190541
  if (callee.kind === "member" && !callee.computed) {
190541
- if (callee.property === "join" && args.length === 1) {
190542
+ if (callee.property === "join") {
190542
190543
  return { kind: "array-method", method: "join", object: callee.object, args };
190543
190544
  }
190544
190545
  if (callee.property === "includes" && args.length === 1) {
@@ -190547,27 +190548,103 @@ function convertNode(node, raw) {
190547
190548
  if ((callee.property === "indexOf" || callee.property === "lastIndexOf") && args.length === 1) {
190548
190549
  return { kind: "array-method", method: callee.property, object: callee.object, args };
190549
190550
  }
190550
- if (callee.property === "at" && args.length === 1) {
190551
+ if (callee.property === "at") {
190551
190552
  return { kind: "array-method", method: "at", object: callee.object, args };
190552
190553
  }
190553
- if (callee.property === "concat" && args.length === 1) {
190554
+ if (callee.property === "concat" && args.length <= 1) {
190554
190555
  return { kind: "array-method", method: "concat", object: callee.object, args };
190555
190556
  }
190556
- if (callee.property === "slice" && (args.length === 1 || args.length === 2)) {
190557
+ if (callee.property === "slice") {
190557
190558
  return { kind: "array-method", method: "slice", object: callee.object, args };
190558
190559
  }
190559
- if ((callee.property === "reverse" || callee.property === "toReversed") && args.length === 0) {
190560
+ if (callee.property === "reverse" || callee.property === "toReversed") {
190560
190561
  return { kind: "array-method", method: callee.property, object: callee.object, args };
190561
190562
  }
190562
- if (callee.property === "toLowerCase" && args.length === 0) {
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
+ }
190589
+ if (callee.property === "toLowerCase") {
190563
190590
  return { kind: "array-method", method: "toLowerCase", object: callee.object, args };
190564
190591
  }
190565
- if (callee.property === "toUpperCase" && args.length === 0) {
190592
+ if (callee.property === "toUpperCase") {
190566
190593
  return { kind: "array-method", method: "toUpperCase", object: callee.object, args };
190567
190594
  }
190568
- if (callee.property === "trim" && args.length === 0) {
190595
+ if (callee.property === "trim") {
190569
190596
  return { kind: "array-method", method: "trim", object: callee.object, args };
190570
190597
  }
190598
+ if (callee.property === "split") {
190599
+ return { kind: "array-method", method: "split", object: callee.object, args };
190600
+ }
190601
+ if (LOWERED_ARRAY_METHODS.has(callee.property)) {
190602
+ const argName = callee.property === "concat" ? "other" : "x";
190603
+ const detail = callee.property === "concat" ? "the variadic `.concat(a, b, …)` form" : `\`.${callee.property}(…)\` with ${args.length} argument(s)`;
190604
+ return {
190605
+ kind: "unsupported",
190606
+ raw,
190607
+ reason: `${detail} is not yet lowered to the Go/Mojo template adapters. Use the single-argument \`.${callee.property}(${argName})\` form, or pre-compute the value before the template.`
190608
+ };
190609
+ }
190610
+ if (callee.property === "startsWith" || callee.property === "endsWith") {
190611
+ if (args.length === 0) {
190612
+ return {
190613
+ kind: "unsupported",
190614
+ raw,
190615
+ reason: `\`.${callee.property}()\` with no search string is not lowered — JS coerces the missing argument to the string "undefined", a degenerate result. Pass an explicit search string, or pre-compute the value before the template.`
190616
+ };
190617
+ }
190618
+ return { kind: "array-method", method: callee.property, object: callee.object, args };
190619
+ }
190620
+ if (callee.property === "replace") {
190621
+ if (args.length < 2) {
190622
+ return {
190623
+ kind: "unsupported",
190624
+ raw,
190625
+ reason: `\`.replace(${args.length === 0 ? "" : "pattern"})\` needs both a pattern and a replacement — JS coerces the missing argument to the string "undefined", a degenerate result. Pass both arguments, or pre-compute the value before the template.`
190626
+ };
190627
+ }
190628
+ const patternNode = node.arguments[0];
190629
+ if (patternNode && import_typescript8.default.isRegularExpressionLiteral(patternNode)) {
190630
+ return {
190631
+ kind: "unsupported",
190632
+ raw,
190633
+ reason: "String.prototype.replace supports only a string pattern + string replacement (the regex form is deferred); use a string pattern or wrap the expression in /* @client */"
190634
+ };
190635
+ }
190636
+ const badArg = args[0].kind === "unsupported" ? args[0] : args[1].kind === "unsupported" ? args[1] : undefined;
190637
+ if (badArg && badArg.kind === "unsupported") {
190638
+ return { kind: "unsupported", raw, reason: badArg.reason };
190639
+ }
190640
+ return { kind: "array-method", method: "replace", object: callee.object, args };
190641
+ }
190642
+ if (callee.property === "repeat") {
190643
+ return { kind: "array-method", method: "repeat", object: callee.object, args };
190644
+ }
190645
+ if (callee.property === "padStart" || callee.property === "padEnd") {
190646
+ return { kind: "array-method", method: callee.property, object: callee.object, args };
190647
+ }
190571
190648
  if ((callee.property === "sort" || callee.property === "toSorted") && node.arguments.length === 1) {
190572
190649
  const comparator = extractSortComparatorFromTS(node.arguments[0], callee.property);
190573
190650
  if (comparator) {
@@ -190592,6 +190669,50 @@ function convertNode(node, raw) {
190592
190669
  ` + `(reverse the operands for descending order). ` + `Wrap the call in /* @client */ to evaluate at hydration.`
190593
190670
  };
190594
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
+ }
190595
190716
  }
190596
190717
  return { kind: "call", callee, args };
190597
190718
  }
@@ -190910,6 +191031,119 @@ function classifySortOperand(expr, paramA, paramB) {
190910
191031
  }
190911
191032
  return null;
190912
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
+ }
190913
191147
  function collectDestructureBindings(pattern, pathPrefix, fieldMap, raw, excludedTopKeys) {
190914
191148
  let restName;
190915
191149
  for (const el of pattern.elements) {
@@ -191218,6 +191452,15 @@ function substituteDestructuredFields(expr, fieldMap, syntheticParam, restName)
191218
191452
  if (e.method === "sort" || e.method === "toSorted") {
191219
191453
  return { kind: "array-method", method: e.method, object: walk(e.object), args: [], comparator: e.comparator };
191220
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
+ }
191221
191464
  return { kind: "array-method", method: e.method, object: walk(e.object), args: e.args.map(walk) };
191222
191465
  case "literal":
191223
191466
  case "unsupported":
@@ -191338,7 +191581,8 @@ function checkSupport(expr) {
191338
191581
  return {
191339
191582
  supported: false,
191340
191583
  level: "L5_UNSUPPORTED",
191341
- 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).`
191342
191586
  };
191343
191587
  }
191344
191588
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barefootjs/test",
3
- "version": "0.5.3",
3
+ "version": "0.6.1",
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.5.3"
42
+ "@barefootjs/jsx": "0.6.1"
43
43
  },
44
44
  "devDependencies": {
45
45
  "typescript": "^5.0.0"