@immense/vue-pom-generator 1.0.31 → 1.0.32

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/RELEASE_NOTES.md CHANGED
@@ -1,57 +1,38 @@
1
- I need to see the actual diff content to understand what the fix addressed. Let me get the file
2
- diffs:
3
-
4
- ● Let me look at specific key changes to understand what was fixed:
5
-
6
- ● Based on the commit and PR details, here are the release notes:
7
-
8
- ---
1
+ # Release Notes: v1.0.32
9
2
 
10
3
  ## Highlights
11
4
 
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
5
+ - Fixed handling of missing helpers and keyed ID branches in class generation
6
+ - Added robust error tolerance for edge cases in Vue component transformation
7
+ - Expanded test coverage with 109 new test lines across generated TypeScript and utility
8
+ coverage tests
9
+ - Introduced PR release-notes preview comments via GitHub Actions
20
10
 
21
11
  ## Changes
22
12
 
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__`
13
+ **Bug Fixes**
14
+ - Tolerate missing helpers and keyed ID branches in class generation logic
15
+ (`class-generation/index.ts`, `utils.ts`)
28
16
 
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
17
+ **Testing**
18
+ - Added comprehensive tests for generated TypeScript output (`tests/generated-tsc.test.ts`: +74
19
+ lines)
20
+ - Expanded utility function coverage tests (`tests/utils-coverage.test.ts`: +35 lines)
34
21
 
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
22
+ **CI/Automation**
23
+ - Added automated PR release-notes preview comments to pull requests
40
24
 
41
25
  ## Breaking Changes
42
26
 
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.
27
+ None
47
28
 
48
29
  ## Pull Requests Included
49
30
 
50
- - [#1 Add PR release-notes preview
51
- comments](https://github.com/immense/vue-pom-generator/pull/1) by @dkattan
31
+ - [#1](https://github.com/immense/vue-pom-generator/pull/1) Add PR release-notes preview
32
+ comments (@dkattan)
52
33
 
53
34
  ## Testing
54
35
 
55
- Added test coverage for new utility functions covering class generation and slot scope analysis
56
- edge cases.
36
+ Significant test additions: 74 lines in generated TypeScript tests and 35 lines in utility
37
+ coverage tests validate the fix for missing helpers and keyed ID branches.
57
38
 
@@ -407,6 +407,7 @@ interface GenerateContentOptions {
407
407
  customPomDir?: string;
408
408
  customPomImportAliases?: Record<string, string>;
409
409
  customPomClassIdentifierMap?: Record<string, string>;
410
+ customPomAvailableClassIdentifiers?: Set<string>;
410
411
 
411
412
  /** Attribute name to treat as the test id. Defaults to `data-testid`. */
412
413
  testIdAttribute?: string;
@@ -1153,8 +1154,14 @@ function generateViewObjectModelContent(
1153
1154
  return false;
1154
1155
  };
1155
1156
 
1157
+ const customPomClassIdentifierMap = options.customPomClassIdentifierMap ?? {};
1158
+ const customPomAvailableClassIdentifiers = options.customPomAvailableClassIdentifiers ?? new Set<string>();
1159
+
1156
1160
  const attachmentsForThisClass = customPomAttachments
1157
1161
  .filter((a) => {
1162
+ if (!Object.prototype.hasOwnProperty.call(customPomClassIdentifierMap, a.className))
1163
+ return false;
1164
+
1158
1165
  const scope = a.attachTo ?? "views";
1159
1166
  const scopeOk = isView
1160
1167
  ? (scope === "views" || scope === "both")
@@ -1164,7 +1171,7 @@ function generateViewObjectModelContent(
1164
1171
  return a.attachWhenUsesComponents.some(c => hasChildComponent(c));
1165
1172
  })
1166
1173
  .map(a => ({
1167
- className: options.customPomClassIdentifierMap?.[a.className] ?? a.className,
1174
+ className: customPomClassIdentifierMap[a.className]!,
1168
1175
  propertyName: a.propertyName,
1169
1176
  }));
1170
1177
 
@@ -1218,7 +1225,7 @@ function generateViewObjectModelContent(
1218
1225
  content += `\nexport class ${className} extends BasePage {\n`;
1219
1226
 
1220
1227
  const widgetInstances = isView
1221
- ? getWidgetInstancesForView(componentName, dependencies.dataTestIdSet)
1228
+ ? getWidgetInstancesForView(componentName, dependencies.dataTestIdSet, customPomAvailableClassIdentifiers)
1222
1229
  : [];
1223
1230
 
1224
1231
  // For views, `childrenComponentSet` only includes component tags on which we applied a data-testid.
@@ -1489,6 +1496,7 @@ async function generateAggregatedFiles(
1489
1496
  };
1490
1497
 
1491
1498
  const customPomClassIdentifierMap = addCustomPomImports();
1499
+ const customPomAvailableClassIdentifiers = new Set(Object.values(customPomClassIdentifierMap ?? {}));
1492
1500
 
1493
1501
  // Collect any navigation return types referenced by generated methods so we can emit
1494
1502
  // stub classes when the destination view has no generated test ids (and therefore no
@@ -1690,6 +1698,7 @@ async function generateAggregatedFiles(
1690
1698
 
1691
1699
  customPomAttachments: options.customPomAttachments ?? [],
1692
1700
  customPomClassIdentifierMap,
1701
+ customPomAvailableClassIdentifiers,
1693
1702
  testIdAttribute: options.testIdAttribute,
1694
1703
  vueRouterFluentChaining: options.vueRouterFluentChaining,
1695
1704
  routeMetaByComponent: options.routeMetaByComponent,
@@ -1817,7 +1826,11 @@ interface WidgetInstance {
1817
1826
  testId: string;
1818
1827
  }
1819
1828
 
1820
- function getWidgetInstancesForView(componentName: string, dataTestIdSet: Set<IDataTestId>): WidgetInstance[] {
1829
+ function getWidgetInstancesForView(
1830
+ componentName: string,
1831
+ dataTestIdSet: Set<IDataTestId>,
1832
+ availableClassIdentifiers: Set<string>,
1833
+ ): WidgetInstance[] {
1821
1834
  const out: WidgetInstance[] = [];
1822
1835
  const usedPropNames = new Set<string>();
1823
1836
 
@@ -1858,6 +1871,9 @@ function getWidgetInstancesForView(componentName: string, dataTestIdSet: Set<IDa
1858
1871
  continue;
1859
1872
  }
1860
1873
 
1874
+ if (!availableClassIdentifiers.has(className))
1875
+ continue;
1876
+
1861
1877
  // Prefer stripping the view prefix (e.g. PreferencesPage-) for cleaner member names.
1862
1878
  const viewPrefix = `${componentName}-`;
1863
1879
  const descriptorRaw = stem.startsWith(viewPrefix) ? stem.slice(viewPrefix.length) : stem;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../class-generation/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,oCAAoC,EAAE,MAAM,sBAAsB,CAAC;AAE5E,OAAO,EAAE,sBAAsB,EAAoE,MAAM,UAAU,CAAC;AAQpH,OAAO,EAAE,oCAAoC,EAAE,CAAC;AA8ChD,UAAU,SAAS;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAyOD,MAAM,WAAW,oBAAoB;IACnC;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;;;;;;;;;;OAWG;IACH,gBAAgB,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAE1D;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;;;;;OAOG;IACH,sBAAsB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEhD;;;;;OAKG;IACH,oCAAoC,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC;IAEzD;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,KAAK,CAAC;QAC3B,SAAS,EAAE,MAAM,CAAC;QAClB,YAAY,EAAE,MAAM,CAAC;QACrB,wBAAwB,EAAE,MAAM,EAAE,CAAC;QAEnC;;;WAGG;QACH,QAAQ,CAAC,EAAE,OAAO,GAAG,YAAY,GAAG,MAAM,CAAC;KAC5C,CAAC,CAAC;IAEH,yEAAyE;IACzE,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB,uDAAuD;IACvD,aAAa,CAAC,EAAE,KAAK,CAAC,IAAI,GAAG,QAAQ,CAAC,CAAC;IAEvC,6BAA6B;IAC7B,MAAM,CAAC,EAAE;QACP,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;IAEF,6EAA6E;IAC7E,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAElC,2FAA2F;IAC3F,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,mDAAmD;IACnD,UAAU,CAAC,EAAE,YAAY,GAAG,MAAM,CAAC;IAEnC,oBAAoB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;CAClD;AAuCD,wBAAsB,aAAa,CACjC,qBAAqB,EAAE,GAAG,CAAC,MAAM,EAAE,sBAAsB,CAAC,EAC1D,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EACpC,iBAAiB,EAAE,MAAM,EACzB,OAAO,GAAE,oBAAyB,iBA2EnC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../class-generation/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,oCAAoC,EAAE,MAAM,sBAAsB,CAAC;AAE5E,OAAO,EAAE,sBAAsB,EAAoE,MAAM,UAAU,CAAC;AAQpH,OAAO,EAAE,oCAAoC,EAAE,CAAC;AA8ChD,UAAU,SAAS;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAyOD,MAAM,WAAW,oBAAoB;IACnC;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;;;;;;;;;;OAWG;IACH,gBAAgB,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAE1D;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;;;;;OAOG;IACH,sBAAsB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEhD;;;;;OAKG;IACH,oCAAoC,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC;IAEzD;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,KAAK,CAAC;QAC3B,SAAS,EAAE,MAAM,CAAC;QAClB,YAAY,EAAE,MAAM,CAAC;QACrB,wBAAwB,EAAE,MAAM,EAAE,CAAC;QAEnC;;;WAGG;QACH,QAAQ,CAAC,EAAE,OAAO,GAAG,YAAY,GAAG,MAAM,CAAC;KAC5C,CAAC,CAAC;IAEH,yEAAyE;IACzE,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB,uDAAuD;IACvD,aAAa,CAAC,EAAE,KAAK,CAAC,IAAI,GAAG,QAAQ,CAAC,CAAC;IAEvC,6BAA6B;IAC7B,MAAM,CAAC,EAAE;QACP,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;IAEF,6EAA6E;IAC7E,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAElC,2FAA2F;IAC3F,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,mDAAmD;IACnD,UAAU,CAAC,EAAE,YAAY,GAAG,MAAM,CAAC;IAEnC,oBAAoB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;CAClD;AAwCD,wBAAsB,aAAa,CACjC,qBAAqB,EAAE,GAAG,CAAC,MAAM,EAAE,sBAAsB,CAAC,EAC1D,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EACpC,iBAAiB,EAAE,MAAM,EACzB,OAAO,GAAE,oBAAyB,iBA2EnC"}
package/dist/index.cjs CHANGED
@@ -1859,6 +1859,12 @@ function applyResolvedDataTestId(args) {
1859
1859
  const existingIdBehavior = args.existingIdBehavior ?? "preserve";
1860
1860
  const nameCollisionBehavior = args.nameCollisionBehavior ?? "suffix";
1861
1861
  const warn = args.warn;
1862
+ const getBestKeyAccessCandidates = (expr) => {
1863
+ if (!expr) {
1864
+ return [];
1865
+ }
1866
+ return expr.split("??").map((part) => part.trim()).filter(Boolean);
1867
+ };
1862
1868
  let dataTestId = args.preferredGeneratedValue;
1863
1869
  let fromExisting = false;
1864
1870
  const existing = tryGetExistingElementDataTestId(args.element, testIdAttribute);
@@ -1881,6 +1887,7 @@ Bulk cleanup: run ESLint with the @immense/vue-pom-generator/remove-existing-tes
1881
1887
  if (existingIdBehavior === "preserve") {
1882
1888
  if (existing.isDynamic) {
1883
1889
  if (existing.template) {
1890
+ const existingTemplate = existing.template;
1884
1891
  if ((existing.templateExpressionCount ?? 0) !== 1) {
1885
1892
  throw new Error(
1886
1893
  `[vue-pom-generator] Existing ${attrLabel} is a template literal with multiple interpolations and cannot be preserved safely.
@@ -1891,8 +1898,8 @@ Existing ${attrLabel}: ${JSON.stringify(existing.value)}
1891
1898
  Fix: reduce the template to a single key-based interpolation, or remove the explicit ${attrLabel} so it can be auto-generated.`
1892
1899
  );
1893
1900
  }
1894
- const hasExact = args.bestKeyPlaceholder && existing.template.includes(args.bestKeyPlaceholder);
1895
- const hasVarAccess = args.bestKeyVariable && existing.template.includes(args.bestKeyVariable);
1901
+ const hasExact = args.bestKeyPlaceholder && existingTemplate.includes(args.bestKeyPlaceholder);
1902
+ const hasVarAccess = getBestKeyAccessCandidates(args.bestKeyVariable).some((candidate) => existingTemplate.includes(candidate));
1896
1903
  if (!hasExact && !hasVarAccess && args.bestKeyPlaceholder) {
1897
1904
  throw new Error(
1898
1905
  `[vue-pom-generator] Existing ${attrLabel} appears to be missing the key placeholder needed to keep it unique.
@@ -3883,14 +3890,18 @@ function generateViewObjectModelContent(componentName, dependencies, componentHi
3883
3890
  }
3884
3891
  return false;
3885
3892
  };
3893
+ const customPomClassIdentifierMap = options.customPomClassIdentifierMap ?? {};
3894
+ const customPomAvailableClassIdentifiers = options.customPomAvailableClassIdentifiers ?? /* @__PURE__ */ new Set();
3886
3895
  const attachmentsForThisClass = customPomAttachments.filter((a) => {
3896
+ if (!Object.prototype.hasOwnProperty.call(customPomClassIdentifierMap, a.className))
3897
+ return false;
3887
3898
  const scope = a.attachTo ?? "views";
3888
3899
  const scopeOk = isView ? scope === "views" || scope === "both" : scope === "components" || scope === "both";
3889
3900
  if (!scopeOk)
3890
3901
  return false;
3891
3902
  return a.attachWhenUsesComponents.some((c) => hasChildComponent(c));
3892
3903
  }).map((a) => ({
3893
- className: options.customPomClassIdentifierMap?.[a.className] ?? a.className,
3904
+ className: customPomClassIdentifierMap[a.className],
3894
3905
  propertyName: a.propertyName
3895
3906
  }));
3896
3907
  let content = "";
@@ -3930,7 +3941,7 @@ function generateViewObjectModelContent(componentName, dependencies, componentHi
3930
3941
  content += `
3931
3942
  export class ${className} extends BasePage {
3932
3943
  `;
3933
- const widgetInstances = isView ? getWidgetInstancesForView(componentName, dependencies.dataTestIdSet) : [];
3944
+ const widgetInstances = isView ? getWidgetInstancesForView(componentName, dependencies.dataTestIdSet, customPomAvailableClassIdentifiers) : [];
3934
3945
  const componentRefsForInstances = isView ? usedComponentSet?.size ? usedComponentSet : childrenComponentSet : childrenComponentSet;
3935
3946
  if (isView && (componentRefsForInstances.size > 0 || attachmentsForThisClass.length > 0 || widgetInstances.length > 0)) {
3936
3947
  content += getComponentInstances(componentRefsForInstances, componentHierarchyMap, attachmentsForThisClass, widgetInstances);
@@ -4088,6 +4099,7 @@ Fix by setting generation.playwright.customPoms.importAliases["${exportName}"] t
4088
4099
  return customPomClassIdentifierMap2;
4089
4100
  };
4090
4101
  const customPomClassIdentifierMap = addCustomPomImports();
4102
+ const customPomAvailableClassIdentifiers = new Set(Object.values(customPomClassIdentifierMap ?? {}));
4091
4103
  const referencedTargets = /* @__PURE__ */ new Set();
4092
4104
  for (const [, deps] of items) {
4093
4105
  for (const dt of deps.dataTestIdSet) {
@@ -4240,6 +4252,7 @@ Fix by setting generation.playwright.customPoms.importAliases["${exportName}"] t
4240
4252
  aggregated: true,
4241
4253
  customPomAttachments: options.customPomAttachments ?? [],
4242
4254
  customPomClassIdentifierMap,
4255
+ customPomAvailableClassIdentifiers,
4243
4256
  testIdAttribute: options.testIdAttribute,
4244
4257
  vueRouterFluentChaining: options.vueRouterFluentChaining,
4245
4258
  routeMetaByComponent: options.routeMetaByComponent
@@ -4334,7 +4347,7 @@ function toPascalCaseLocal(str) {
4334
4347
  return preserveInternalCaps ? upperFirst(word) : upperFirst(word.toLowerCase());
4335
4348
  }).join("");
4336
4349
  }
4337
- function getWidgetInstancesForView(componentName, dataTestIdSet) {
4350
+ function getWidgetInstancesForView(componentName, dataTestIdSet, availableClassIdentifiers) {
4338
4351
  const out = [];
4339
4352
  const usedPropNames = /* @__PURE__ */ new Set();
4340
4353
  const ensureUnique = (base) => {
@@ -4365,6 +4378,8 @@ function getWidgetInstancesForView(componentName, dataTestIdSet) {
4365
4378
  } else {
4366
4379
  continue;
4367
4380
  }
4381
+ if (!availableClassIdentifiers.has(className))
4382
+ continue;
4368
4383
  const viewPrefix = `${componentName}-`;
4369
4384
  const descriptorRaw = stem.startsWith(viewPrefix) ? stem.slice(viewPrefix.length) : stem;
4370
4385
  const descriptorPascal = toPascalCaseLocal(descriptorRaw);