pasika 0.10.23 → 0.10.24

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.
@@ -264,6 +264,86 @@ function findSimpleRoot(component, _text, _filename) {
264
264
  if (roots.some((root) => root.tagName !== firstTag)) return void 0;
265
265
  return roots[0];
266
266
  }
267
+ function literalTestIds(attributes) {
268
+ return attributes.flatMap((candidate) => {
269
+ if (!ts.isJsxAttribute(candidate) || !ts.isIdentifier(candidate.name) || candidate.name.text !== "data-testid") {
270
+ return [];
271
+ }
272
+ return candidate.initializer && ts.isStringLiteral(candidate.initializer) ? [candidate.initializer.text] : [];
273
+ });
274
+ }
275
+ function combineTestIdPaths(left, right) {
276
+ return left.flatMap((leftPath) => right.map((rightPath) => [...leftPath, ...rightPath]));
277
+ }
278
+ function renderedTestIdPathsFromExpression(expression) {
279
+ let unwrapped = expression;
280
+ while (ts.isParenthesizedExpression(unwrapped)) unwrapped = unwrapped.expression;
281
+ if (ts.isConditionalExpression(unwrapped)) {
282
+ return [
283
+ ...renderedTestIdPathsFromExpression(unwrapped.whenTrue),
284
+ ...renderedTestIdPathsFromExpression(unwrapped.whenFalse)
285
+ ];
286
+ }
287
+ if (ts.isJsxSelfClosingElement(unwrapped)) {
288
+ return [literalTestIds(unwrapped.attributes.properties)];
289
+ }
290
+ if (ts.isJsxElement(unwrapped)) {
291
+ let paths = [literalTestIds(unwrapped.openingElement.attributes.properties)];
292
+ for (const child of unwrapped.children) {
293
+ if (ts.isJsxElement(child) || ts.isJsxSelfClosingElement(child) || ts.isJsxFragment(child)) {
294
+ paths = combineTestIdPaths(paths, renderedTestIdPathsFromExpression(child));
295
+ continue;
296
+ }
297
+ if (ts.isJsxExpression(child) && child.expression) {
298
+ const childExpression = child.expression;
299
+ if (ts.isConditionalExpression(childExpression) || ts.isJsxElement(childExpression) || ts.isJsxSelfClosingElement(childExpression) || ts.isJsxFragment(childExpression) || ts.isParenthesizedExpression(childExpression)) {
300
+ paths = combineTestIdPaths(paths, renderedTestIdPathsFromExpression(childExpression));
301
+ }
302
+ }
303
+ }
304
+ return paths;
305
+ }
306
+ if (ts.isJsxFragment(unwrapped)) {
307
+ let paths = [[]];
308
+ for (const child of unwrapped.children) {
309
+ if (ts.isJsxElement(child) || ts.isJsxSelfClosingElement(child) || ts.isJsxFragment(child)) {
310
+ paths = combineTestIdPaths(paths, renderedTestIdPathsFromExpression(child));
311
+ continue;
312
+ }
313
+ if (ts.isJsxExpression(child) && child.expression) {
314
+ const childExpression = child.expression;
315
+ if (ts.isConditionalExpression(childExpression) || ts.isJsxElement(childExpression) || ts.isJsxSelfClosingElement(childExpression) || ts.isJsxFragment(childExpression) || ts.isParenthesizedExpression(childExpression)) {
316
+ paths = combineTestIdPaths(paths, renderedTestIdPathsFromExpression(childExpression));
317
+ }
318
+ }
319
+ }
320
+ return paths;
321
+ }
322
+ return [[]];
323
+ }
324
+ function findRenderedTestIdPaths(component) {
325
+ const declaration = component.declaration;
326
+ let body;
327
+ if (ts.isFunctionDeclaration(declaration)) body = declaration.body;
328
+ else if (declaration.initializer && ts.isArrowFunction(declaration.initializer)) body = declaration.initializer.body;
329
+ else if (declaration.initializer && ts.isFunctionExpression(declaration.initializer)) {
330
+ body = declaration.initializer.body;
331
+ }
332
+ if (!body) return [];
333
+ if (!ts.isBlock(body)) return renderedTestIdPathsFromExpression(body);
334
+ const returns = [];
335
+ const visit = (node) => {
336
+ if (node !== body && (ts.isFunctionLike(node) || ts.isClassLike(node))) return;
337
+ if (ts.isReturnStatement(node)) returns.push(node);
338
+ ts.forEachChild(node, visit);
339
+ };
340
+ visit(body);
341
+ return returns.flatMap((statement) => {
342
+ const expression = statement.expression;
343
+ if (!expression || expression.kind === ts.SyntaxKind.NullKeyword) return [];
344
+ return renderedTestIdPathsFromExpression(expression);
345
+ });
346
+ }
267
347
  function getTestId(root) {
268
348
  const attribute = root.attributes.find(
269
349
  (candidate) => ts.isJsxAttribute(candidate) && ts.isIdentifier(candidate.name) && candidate.name.text === "data-testid"
@@ -2193,7 +2273,6 @@ var NEXT_ROUTING_FILES2 = /* @__PURE__ */ new Set([
2193
2273
  "sitemap",
2194
2274
  "twitter-image"
2195
2275
  ]);
2196
- var isPascalCase5 = (value) => /^[A-Z][A-Za-z0-9]*$/.test(value);
2197
2276
  var isKebabCase3 = (value) => /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/.test(value);
2198
2277
  var dataTestIdCaseRule = {
2199
2278
  meta: {
@@ -2213,28 +2292,34 @@ var dataTestIdCaseRule = {
2213
2292
  return {
2214
2293
  Program(node) {
2215
2294
  for (const component of components) {
2216
- const root = findSimpleRoot(component, text, filename);
2217
- if (root) {
2218
- const { value } = getTestId(root);
2219
- const expected = component.smart ? component.name : toKebabCase(component.name);
2220
- if (component.smart && value === void 0) {
2295
+ if (component.smart) {
2296
+ const expected2 = component.name;
2297
+ const renderedPaths = findRenderedTestIdPaths(component);
2298
+ const hasStableAnchor = renderedPaths.length > 0 && renderedPaths.every((values) => values.filter((value2) => value2 === expected2).length === 1);
2299
+ if (hasStableAnchor) continue;
2300
+ const hasSingleWrongCasedAnchor = renderedPaths.length > 0 && renderedPaths.every((values) => values.length === 1 && values[0] !== expected2) && new Set(renderedPaths.map((values) => values[0])).size === 1;
2301
+ if (hasSingleWrongCasedAnchor) {
2221
2302
  context.report({
2222
2303
  node,
2223
- message: `Smart component "${component.name}" with one outer DOM element must set data-testid="${expected}".`
2304
+ message: `data-testid for smart component "${component.name}" must be PascalCase: expected "${expected2}".`
2224
2305
  });
2225
2306
  continue;
2226
2307
  }
2227
- if (value === void 0 || value === expected) continue;
2228
- if (component.smart ? !isPascalCase5(value) || value !== expected : !isKebabCase3(value)) {
2229
- context.report({
2230
- node,
2231
- message: `data-testid for ${component.smart ? "smart" : "dumb"} component "${component.name}" must be ${component.smart ? "PascalCase" : "kebab-case"}: expected "${expected}".`
2232
- });
2233
- }
2234
- } else if (component.smart) {
2235
2308
  context.report({
2236
2309
  node,
2237
- message: `Smart component "${component.name}" has no single outer element; wrap its content in one outer element with data-testid="${component.name}" instead of rendering multiple roots. See docs/next-codebase-guide/rules/smart-vs-dumb-component-rule.md`
2310
+ message: `Smart component "${component.name}" must expose exactly one stable data-testid="${expected2}" anchor for itself in every rendered result. Place it on the existing DOM surface or on a child component that forwards data-testid instead of adding an artificial wrapper. See docs/next-codebase-guide/rules/smart-vs-dumb-component-rule.md`
2311
+ });
2312
+ continue;
2313
+ }
2314
+ const root = findSimpleRoot(component, text, filename);
2315
+ if (!root) continue;
2316
+ const { value } = getTestId(root);
2317
+ const expected = toKebabCase(component.name);
2318
+ if (value === void 0 || value === expected) continue;
2319
+ if (!isKebabCase3(value)) {
2320
+ context.report({
2321
+ node,
2322
+ message: `data-testid for dumb component "${component.name}" must be kebab-case: expected "${expected}".`
2238
2323
  });
2239
2324
  }
2240
2325
  }
@@ -4837,7 +4922,7 @@ var schemaCasingRule = {
4837
4922
 
4838
4923
  // eslint/rules/component-casing.ts
4839
4924
  import path42 from "path";
4840
- function isPascalCase6(name) {
4925
+ function isPascalCase5(name) {
4841
4926
  return /^[A-Z][A-Za-z0-9]*$/.test(name);
4842
4927
  }
4843
4928
  var componentCasingRule = {
@@ -4856,7 +4941,7 @@ var componentCasingRule = {
4856
4941
  return {
4857
4942
  Program(node) {
4858
4943
  for (const declaration of findJsxReturningDeclarations(context.sourceCode.text, filename)) {
4859
- if (isPascalCase6(declaration.name)) continue;
4944
+ if (isPascalCase5(declaration.name)) continue;
4860
4945
  context.report({
4861
4946
  node,
4862
4947
  loc: { line: declaration.line, column: declaration.column },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pasika",
3
- "version": "0.10.23",
3
+ "version": "0.10.24",
4
4
  "description": "Reusable agent setup package",
5
5
  "repository": {
6
6
  "type": "git",