@immense/vue-pom-generator 1.0.30 → 1.0.31

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/README.md CHANGED
@@ -35,15 +35,15 @@ Exported entrypoints:
35
35
  - `injection`: how `data-testid` (or your chosen attribute) is derived/injected
36
36
  - `generation`: how Page Object Models (POMs) and Playwright helpers are generated
37
37
 
38
- The generator emits an aggregated output under `generation.outDir` (default `tests/playwright/generated`):
38
+ The generator emits an aggregated output under `generation.outDir` (default `tests/playwright/__generated__`):
39
39
 
40
- - `tests/playwright/generated/page-object-models.g.ts` (generated; do not edit)
41
- - `tests/playwright/generated/index.ts` (generated stable barrel)
42
- - managed `.gitattributes` files alongside generated outputs so GitHub Linguist treats them as generated by default
40
+ - `tests/playwright/__generated__/page-object-models.g.ts` (generated; do not edit)
41
+ - `tests/playwright/__generated__/index.ts` (generated stable barrel)
42
+ - managed `.gitattributes` files only when you emit outside `__generated__`
43
43
 
44
44
  If `generation.playwright.fixtures` is enabled, it also emits:
45
45
 
46
- - `tests/playwright/generated/fixtures.g.ts` (generated; do not edit)
46
+ - `tests/playwright/__generated__/fixtures.g.ts` (generated; do not edit)
47
47
 
48
48
  ### Vite config example
49
49
 
@@ -98,8 +98,8 @@ export default defineConfig(() => {
98
98
  namespace: "MyProject.Tests.Generated",
99
99
  },
100
100
 
101
- // Default: tests/playwright/generated
102
- outDir: "tests/playwright/generated",
101
+ // Default: tests/playwright/__generated__
102
+ outDir: "tests/playwright/__generated__",
103
103
 
104
104
  // Controls how to handle duplicate generated member names within a single POM class.
105
105
  // - "error": fail compilation
package/RELEASE_NOTES.md CHANGED
@@ -1,44 +1,57 @@
1
- # Release Notes: v1.0.30
1
+ I need to see the actual diff content to understand what the fix addressed. Let me get the file
2
+ diffs:
2
3
 
3
- ## Highlights
4
+ Let me look at specific key changes to understand what was fixed:
4
5
 
5
- - Refactored wrapper search roots configuration for improved flexibility
6
- - Enhanced plugin system with better type definitions and support
7
- - Improved test coverage with expanded transform test cases
8
- - Added CI automation for PR release note previews
6
+ Based on the commit and PR details, here are the release notes:
9
7
 
10
- ## Changes
8
+ ---
11
9
 
12
- ### Core Functionality
13
- - Refactored wrapper search roots configuration in transform logic
14
- - Enhanced transform logic with improved structure (198 additions, significant reorganization)
10
+ ## Highlights
15
11
 
16
- ### Plugin System
17
- - Updated plugin types with refined interfaces
18
- - Improved Vue plugin implementation
19
- - Enhanced plugin creation utilities
20
- - Better dev plugin support
12
+ - Changed default output directory from `tests/playwright/generated` to
13
+ `tests/playwright/__generated__` for better convention adherence
14
+ - Improved `.gitattributes` generation: now skips managed entries for paths using
15
+ `__generated__` heuristic (GitHub Linguist auto-detects these)
16
+ - Enhanced slot scope key extraction logic for more robust click handler naming in generated
17
+ POMs
18
+ - Added comprehensive utility functions for analyzing assignment patterns, object destructuring,
19
+ and binding identifiers
20
+
21
+ ## Changes
21
22
 
22
- ### Testing & Quality
23
- - Expanded transform test suite with additional test cases
24
- - Updated test assertions for improved coverage
23
+ **Output Directory Convention**
24
+ - Updated default `generation.outDir` to `tests/playwright/__generated__`
25
+ - Updated default Playwright fixtures output to use `__generated__` subdirectory
26
+ - `.gitattributes` entries are now only generated when using custom output directories outside
27
+ `__generated__`
25
28
 
26
- ### Tooling & CI
27
- - Added automated PR release-notes preview comments
29
+ **Click Handler Naming**
30
+ - Improved slot scope key candidate extraction with priority-based selection
31
+ - Added support for object destructuring patterns in slot scope analysis
32
+ - Enhanced fallback key expression generation for slot scoped elements
33
+ - Better handling of assignment patterns, rest elements, and object properties
28
34
 
29
- ### Documentation
30
- - Updated README with additional information
35
+ **Code Quality**
36
+ - Added test coverage for new utility functions in `class-generation-coverage.test.ts` and
37
+ `utils-coverage.test.ts`
38
+ - Expanded Babel type guards usage for more precise AST node detection
39
+ - Refactored slot scope analysis with dedicated helper functions
31
40
 
32
41
  ## Breaking Changes
33
42
 
34
- None identified in this release.
43
+ - **Default output directory** changed from `tests/playwright/generated` to
44
+ `tests/playwright/__generated__`. If you're relying on the default, generated files will move to
45
+ the new location. To preserve the old behavior, explicitly set `generation.outDir:
46
+ "tests/playwright/generated"` in your config.
35
47
 
36
48
  ## Pull Requests Included
37
49
 
38
- - #1 Add PR release-notes preview comments (https://github.com/immense/vue-pom-generator/pull/1)
39
- by @dkattan
50
+ - [#1 Add PR release-notes preview
51
+ comments](https://github.com/immense/vue-pom-generator/pull/1) by @dkattan
40
52
 
41
53
  ## Testing
42
54
 
43
- Existing test suite updated and expanded with new test cases in `transform.test.ts`.
55
+ Added test coverage for new utility functions covering class generation and slot scope analysis
56
+ edge cases.
44
57
 
@@ -524,6 +524,11 @@ function escapeGitAttributesPattern(value: string): string {
524
524
  return output;
525
525
  }
526
526
 
527
+ function pathUsesGeneratedHeuristic(filePath: string): boolean {
528
+ const normalized = path.normalize(filePath);
529
+ return normalized.split(path.sep).includes("__generated__");
530
+ }
531
+
527
532
  function buildManagedGitAttributesBlock(entries: string[]): string {
528
533
  return [
529
534
  GENERATED_GITATTRIBUTES_BLOCK_START,
@@ -581,6 +586,10 @@ function buildGeneratedGitAttributesFiles(generatedFilePaths: string[]): Generat
581
586
  continue;
582
587
  }
583
588
 
589
+ if (pathUsesGeneratedHeuristic(resolvedFilePath)) {
590
+ continue;
591
+ }
592
+
584
593
  const dir = path.dirname(resolvedFilePath);
585
594
  const entry = `${escapeGitAttributesPattern(path.basename(resolvedFilePath))} linguist-generated`;
586
595
  const entries = entriesByDir.get(dir) ?? new Set<string>();
package/dist/index.cjs CHANGED
@@ -606,6 +606,124 @@ function getTemplateSlotScope(node) {
606
606
  }
607
607
  return null;
608
608
  }
609
+ function isSimpleScopeIdentifier(value) {
610
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(value);
611
+ }
612
+ function buildSlotScopeFallbackKeyExpression(identifier) {
613
+ return `${identifier}.key ?? ${identifier}.data?.id ?? ${identifier}.id ?? ${identifier}.value ?? ${identifier}`;
614
+ }
615
+ function tryGetBindingIdentifierName(node) {
616
+ if (!node) {
617
+ return null;
618
+ }
619
+ if (types.isIdentifier(node)) {
620
+ return node.name;
621
+ }
622
+ if (types.isAssignmentPattern(node)) {
623
+ return tryGetBindingIdentifierName(node.left);
624
+ }
625
+ if (types.isRestElement(node)) {
626
+ return tryGetBindingIdentifierName(node.argument);
627
+ }
628
+ return null;
629
+ }
630
+ function getSlotScopeObjectPropertyKeyName(node) {
631
+ if (types.isIdentifier(node)) {
632
+ return node.name;
633
+ }
634
+ if (types.isStringLiteral(node)) {
635
+ return node.value;
636
+ }
637
+ return null;
638
+ }
639
+ function tryGetSlotScopeKeyCandidate(node) {
640
+ if (!node) {
641
+ return null;
642
+ }
643
+ if (types.isIdentifier(node)) {
644
+ return {
645
+ priority: 2,
646
+ expression: buildSlotScopeFallbackKeyExpression(node.name)
647
+ };
648
+ }
649
+ if (types.isAssignmentPattern(node)) {
650
+ return tryGetSlotScopeKeyCandidate(node.left);
651
+ }
652
+ if (types.isRestElement(node)) {
653
+ return tryGetSlotScopeKeyCandidate(node.argument);
654
+ }
655
+ if (!types.isObjectPattern(node)) {
656
+ return null;
657
+ }
658
+ let best = null;
659
+ for (const property of node.properties) {
660
+ let candidate = null;
661
+ if (types.isRestElement(property)) {
662
+ candidate = tryGetSlotScopeKeyCandidate(property.argument);
663
+ if (candidate) {
664
+ candidate = { ...candidate, priority: Math.max(candidate.priority, 2) };
665
+ }
666
+ } else if (types.isObjectProperty(property)) {
667
+ const keyName = getSlotScopeObjectPropertyKeyName(property.key);
668
+ const bindingName = tryGetBindingIdentifierName(property.value) ?? tryGetBindingIdentifierName(property.key);
669
+ if (bindingName) {
670
+ if (keyName === "key" || bindingName === "key") {
671
+ candidate = {
672
+ priority: 0,
673
+ expression: bindingName
674
+ };
675
+ } else {
676
+ candidate = {
677
+ priority: keyName === "data" ? 1 : 2,
678
+ expression: buildSlotScopeFallbackKeyExpression(bindingName)
679
+ };
680
+ }
681
+ }
682
+ }
683
+ if (candidate && (!best || candidate.priority < best.priority)) {
684
+ best = candidate;
685
+ }
686
+ }
687
+ return best;
688
+ }
689
+ function tryGetTemplateSlotScopeKeyExpression(scope) {
690
+ const trimmed = scope.trim();
691
+ if (!trimmed) {
692
+ return null;
693
+ }
694
+ if (isSimpleScopeIdentifier(trimmed)) {
695
+ return buildSlotScopeFallbackKeyExpression(trimmed);
696
+ }
697
+ try {
698
+ const parsed = parser.parse(`(${trimmed}) => {}`, {
699
+ sourceType: "module",
700
+ plugins: ["typescript"]
701
+ });
702
+ const statement = parsed.program.body[0];
703
+ if (statement && types.isExpressionStatement(statement) && types.isArrowFunctionExpression(statement.expression)) {
704
+ return tryGetSlotScopeKeyCandidate(statement.expression.params[0])?.expression ?? null;
705
+ }
706
+ } catch {
707
+ }
708
+ if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
709
+ const inner = trimmed.slice(1, -1).trim();
710
+ let cutIdx = -1;
711
+ const commaIdx = inner.indexOf(",");
712
+ const colonIdx = inner.indexOf(":");
713
+ if (commaIdx !== -1 && colonIdx !== -1) {
714
+ cutIdx = Math.min(commaIdx, colonIdx);
715
+ } else if (commaIdx !== -1) {
716
+ cutIdx = commaIdx;
717
+ } else if (colonIdx !== -1) {
718
+ cutIdx = colonIdx;
719
+ }
720
+ const first = (cutIdx === -1 ? inner : inner.slice(0, cutIdx)).trim();
721
+ if (first && isSimpleScopeIdentifier(first)) {
722
+ return buildSlotScopeFallbackKeyExpression(first);
723
+ }
724
+ }
725
+ return trimmed;
726
+ }
609
727
  function nodeHasToDirective(node) {
610
728
  const toDirective = findDirectiveByName(node, "bind", "to");
611
729
  if (toDirective?.exp) {
@@ -681,25 +799,7 @@ function getContainedInSlotDataKeyValue(node, hierarchyMap2) {
681
799
  if (parent.type === compilerCore.NodeTypes.ELEMENT && parent.tag === "template") {
682
800
  const scope = getTemplateSlotScope(parent);
683
801
  if (scope) {
684
- let key = scope.trim();
685
- if (key.startsWith("{") && key.endsWith("}")) {
686
- const inner = key.slice(1, -1).trim();
687
- let cutIdx = -1;
688
- const commaIdx = inner.indexOf(",");
689
- const colonIdx = inner.indexOf(":");
690
- if (commaIdx !== -1 && colonIdx !== -1) {
691
- cutIdx = Math.min(commaIdx, colonIdx);
692
- } else if (commaIdx !== -1) {
693
- cutIdx = commaIdx;
694
- } else if (colonIdx !== -1) {
695
- cutIdx = colonIdx;
696
- }
697
- const first = (cutIdx === -1 ? inner : inner.slice(0, cutIdx)).trim();
698
- if (first) {
699
- key = first;
700
- }
701
- }
702
- return key;
802
+ return tryGetTemplateSlotScopeKeyExpression(scope);
703
803
  }
704
804
  }
705
805
  parent = getParent(hierarchyMap2, parent);
@@ -1350,11 +1450,11 @@ function getStableClickHandlerNameFromExpression(exp) {
1350
1450
  return extractNameFromCallee(callee);
1351
1451
  }
1352
1452
  if (types.isAssignmentExpression(exp)) {
1353
- const left = exp.left;
1354
- if (types.isIdentifier(left))
1355
- return left.name;
1356
- if (types.isMemberExpression(left) || types.isOptionalMemberExpression(left))
1357
- return extractMemberPropertyName(left);
1453
+ const left = getAssignmentTargetNameFromBabel(exp.left);
1454
+ if (left) {
1455
+ const right = getStableAssignmentValueSuffixFromBabel(exp.right);
1456
+ return `Set${toPascalCase(left)}${right}`;
1457
+ }
1358
1458
  return "";
1359
1459
  }
1360
1460
  if (types.isOptionalMemberExpression(exp)) {
@@ -1429,6 +1529,71 @@ function extractMemberPropertyName(member) {
1429
1529
  }
1430
1530
  return "";
1431
1531
  }
1532
+ function getLastIdentifierFromMemberChainBabel(node) {
1533
+ if (!node) {
1534
+ return "";
1535
+ }
1536
+ if (types.isIdentifier(node)) {
1537
+ return node.name;
1538
+ }
1539
+ if (types.isMemberExpression(node) || types.isOptionalMemberExpression(node)) {
1540
+ if (!node.computed) {
1541
+ if (types.isIdentifier(node.property)) {
1542
+ return node.property.name;
1543
+ }
1544
+ } else if (types.isStringLiteral(node.property)) {
1545
+ return node.property.value;
1546
+ }
1547
+ }
1548
+ return "";
1549
+ }
1550
+ function getAssignmentTargetNameFromBabel(node) {
1551
+ if (!node) {
1552
+ return "";
1553
+ }
1554
+ if (types.isIdentifier(node)) {
1555
+ return node.name;
1556
+ }
1557
+ if (types.isMemberExpression(node) || types.isOptionalMemberExpression(node)) {
1558
+ if (!node.computed && types.isIdentifier(node.property) && node.property.name === "value") {
1559
+ return getLastIdentifierFromMemberChainBabel(node.object);
1560
+ }
1561
+ return getLastIdentifierFromMemberChainBabel(node);
1562
+ }
1563
+ return "";
1564
+ }
1565
+ function getStableAssignmentValueSuffixFromBabel(node) {
1566
+ if (!node) {
1567
+ return "";
1568
+ }
1569
+ if (types.isBooleanLiteral(node)) {
1570
+ return node.value ? "True" : "False";
1571
+ }
1572
+ if (types.isNumericLiteral(node)) {
1573
+ return `Value${String(node.value)}`;
1574
+ }
1575
+ if (types.isNullLiteral(node)) {
1576
+ return "Null";
1577
+ }
1578
+ if (types.isStringLiteral(node)) {
1579
+ const cleaned = (node.value ?? "").trim();
1580
+ return cleaned ? toPascalCase(cleaned.slice(0, 24)) : "";
1581
+ }
1582
+ if (types.isTemplateLiteral(node) && node.expressions.length === 0) {
1583
+ const value = node.quasis.map((q) => q.value?.cooked ?? "").join("").trim();
1584
+ return value ? toPascalCase(value.slice(0, 24)) : "";
1585
+ }
1586
+ if (types.isMemberExpression(node) || types.isOptionalMemberExpression(node)) {
1587
+ const name = getLastIdentifierFromMemberChainBabel(node);
1588
+ return name ? toPascalCase(name.slice(0, 24)) : "";
1589
+ }
1590
+ if (types.isIdentifier(node)) {
1591
+ const firstChar = node.name.charAt(0);
1592
+ const isUpperAlpha = firstChar !== "" && firstChar === firstChar.toUpperCase() && firstChar !== firstChar.toLowerCase();
1593
+ return isUpperAlpha ? toPascalCase(node.name.slice(0, 24)) : "";
1594
+ }
1595
+ return "";
1596
+ }
1432
1597
  function isCompilerGeneratedReferenceRoot(name) {
1433
1598
  return name.startsWith("_") || name.startsWith("$");
1434
1599
  }
@@ -3240,6 +3405,10 @@ function escapeGitAttributesPattern(value) {
3240
3405
  }
3241
3406
  return output;
3242
3407
  }
3408
+ function pathUsesGeneratedHeuristic(filePath) {
3409
+ const normalized = path.normalize(filePath);
3410
+ return normalized.split(path.sep).includes("__generated__");
3411
+ }
3243
3412
  function buildManagedGitAttributesBlock(entries) {
3244
3413
  return [
3245
3414
  GENERATED_GITATTRIBUTES_BLOCK_START,
@@ -3287,6 +3456,9 @@ function buildGeneratedGitAttributesFiles(generatedFilePaths) {
3287
3456
  if (path.basename(resolvedFilePath) === ".gitattributes") {
3288
3457
  continue;
3289
3458
  }
3459
+ if (pathUsesGeneratedHeuristic(resolvedFilePath)) {
3460
+ continue;
3461
+ }
3290
3462
  const dir = path.dirname(resolvedFilePath);
3291
3463
  const entry = `${escapeGitAttributesPattern(path.basename(resolvedFilePath))} linguist-generated`;
3292
3464
  const entries = entriesByDir.get(dir) ?? /* @__PURE__ */ new Set();
@@ -6197,7 +6369,7 @@ function createVuePomGeneratorPlugins(options = {}) {
6197
6369
  const excludedComponents = injection.excludeComponents ?? [];
6198
6370
  const testIdAttribute = (injection.attribute ?? "data-testid").trim() || "data-testid";
6199
6371
  const existingIdBehavior = injection.existingIdBehavior ?? "preserve";
6200
- const outDir = (generationOptions?.outDir ?? "tests/playwright/generated").trim();
6372
+ const outDir = (generationOptions?.outDir ?? "tests/playwright/__generated__").trim();
6201
6373
  const emitLanguages = generationOptions?.emit && generationOptions.emit.length ? generationOptions.emit : ["ts"];
6202
6374
  const nameCollisionBehavior = generationOptions?.nameCollisionBehavior ?? "suffix";
6203
6375
  const routerEntry = generationOptions?.router?.entry;