@immense/vue-pom-generator 1.0.30 → 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/README.md +7 -7
- package/RELEASE_NOTES.md +20 -26
- package/class-generation/index.ts +28 -3
- package/dist/class-generation/index.d.ts.map +1 -1
- package/dist/index.cjs +217 -30
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +218 -31
- package/dist/index.mjs.map +1 -1
- package/dist/plugin/types.d.ts +4 -4
- package/dist/utils.d.ts.map +1 -1
- package/package.json +1 -1
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/
|
|
38
|
+
The generator emits an aggregated output under `generation.outDir` (default `tests/playwright/__generated__`):
|
|
39
39
|
|
|
40
|
-
- `tests/playwright/
|
|
41
|
-
- `tests/playwright/
|
|
42
|
-
- managed `.gitattributes` files
|
|
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/
|
|
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/
|
|
102
|
-
outDir: "tests/playwright/
|
|
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,38 @@
|
|
|
1
|
-
● # Release Notes: v1.0.
|
|
1
|
+
● # Release Notes: v1.0.32
|
|
2
2
|
|
|
3
3
|
## Highlights
|
|
4
4
|
|
|
5
|
-
-
|
|
6
|
-
-
|
|
7
|
-
-
|
|
8
|
-
|
|
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
|
|
9
10
|
|
|
10
11
|
## Changes
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
13
|
+
**Bug Fixes**
|
|
14
|
+
- Tolerate missing helpers and keyed ID branches in class generation logic
|
|
15
|
+
(`class-generation/index.ts`, `utils.ts`)
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
-
|
|
18
|
-
|
|
19
|
-
-
|
|
20
|
-
- Better dev plugin support
|
|
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)
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
-
|
|
24
|
-
- Updated test assertions for improved coverage
|
|
25
|
-
|
|
26
|
-
### Tooling & CI
|
|
27
|
-
- Added automated PR release-notes preview comments
|
|
28
|
-
|
|
29
|
-
### Documentation
|
|
30
|
-
- Updated README with additional information
|
|
22
|
+
**CI/Automation**
|
|
23
|
+
- Added automated PR release-notes preview comments to pull requests
|
|
31
24
|
|
|
32
25
|
## Breaking Changes
|
|
33
26
|
|
|
34
|
-
None
|
|
27
|
+
None
|
|
35
28
|
|
|
36
29
|
## Pull Requests Included
|
|
37
30
|
|
|
38
|
-
- #1
|
|
39
|
-
|
|
31
|
+
- [#1](https://github.com/immense/vue-pom-generator/pull/1) Add PR release-notes preview
|
|
32
|
+
comments (@dkattan)
|
|
40
33
|
|
|
41
34
|
## Testing
|
|
42
35
|
|
|
43
|
-
|
|
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.
|
|
44
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;
|
|
@@ -524,6 +525,11 @@ function escapeGitAttributesPattern(value: string): string {
|
|
|
524
525
|
return output;
|
|
525
526
|
}
|
|
526
527
|
|
|
528
|
+
function pathUsesGeneratedHeuristic(filePath: string): boolean {
|
|
529
|
+
const normalized = path.normalize(filePath);
|
|
530
|
+
return normalized.split(path.sep).includes("__generated__");
|
|
531
|
+
}
|
|
532
|
+
|
|
527
533
|
function buildManagedGitAttributesBlock(entries: string[]): string {
|
|
528
534
|
return [
|
|
529
535
|
GENERATED_GITATTRIBUTES_BLOCK_START,
|
|
@@ -581,6 +587,10 @@ function buildGeneratedGitAttributesFiles(generatedFilePaths: string[]): Generat
|
|
|
581
587
|
continue;
|
|
582
588
|
}
|
|
583
589
|
|
|
590
|
+
if (pathUsesGeneratedHeuristic(resolvedFilePath)) {
|
|
591
|
+
continue;
|
|
592
|
+
}
|
|
593
|
+
|
|
584
594
|
const dir = path.dirname(resolvedFilePath);
|
|
585
595
|
const entry = `${escapeGitAttributesPattern(path.basename(resolvedFilePath))} linguist-generated`;
|
|
586
596
|
const entries = entriesByDir.get(dir) ?? new Set<string>();
|
|
@@ -1144,8 +1154,14 @@ function generateViewObjectModelContent(
|
|
|
1144
1154
|
return false;
|
|
1145
1155
|
};
|
|
1146
1156
|
|
|
1157
|
+
const customPomClassIdentifierMap = options.customPomClassIdentifierMap ?? {};
|
|
1158
|
+
const customPomAvailableClassIdentifiers = options.customPomAvailableClassIdentifiers ?? new Set<string>();
|
|
1159
|
+
|
|
1147
1160
|
const attachmentsForThisClass = customPomAttachments
|
|
1148
1161
|
.filter((a) => {
|
|
1162
|
+
if (!Object.prototype.hasOwnProperty.call(customPomClassIdentifierMap, a.className))
|
|
1163
|
+
return false;
|
|
1164
|
+
|
|
1149
1165
|
const scope = a.attachTo ?? "views";
|
|
1150
1166
|
const scopeOk = isView
|
|
1151
1167
|
? (scope === "views" || scope === "both")
|
|
@@ -1155,7 +1171,7 @@ function generateViewObjectModelContent(
|
|
|
1155
1171
|
return a.attachWhenUsesComponents.some(c => hasChildComponent(c));
|
|
1156
1172
|
})
|
|
1157
1173
|
.map(a => ({
|
|
1158
|
-
className:
|
|
1174
|
+
className: customPomClassIdentifierMap[a.className]!,
|
|
1159
1175
|
propertyName: a.propertyName,
|
|
1160
1176
|
}));
|
|
1161
1177
|
|
|
@@ -1209,7 +1225,7 @@ function generateViewObjectModelContent(
|
|
|
1209
1225
|
content += `\nexport class ${className} extends BasePage {\n`;
|
|
1210
1226
|
|
|
1211
1227
|
const widgetInstances = isView
|
|
1212
|
-
? getWidgetInstancesForView(componentName, dependencies.dataTestIdSet)
|
|
1228
|
+
? getWidgetInstancesForView(componentName, dependencies.dataTestIdSet, customPomAvailableClassIdentifiers)
|
|
1213
1229
|
: [];
|
|
1214
1230
|
|
|
1215
1231
|
// For views, `childrenComponentSet` only includes component tags on which we applied a data-testid.
|
|
@@ -1480,6 +1496,7 @@ async function generateAggregatedFiles(
|
|
|
1480
1496
|
};
|
|
1481
1497
|
|
|
1482
1498
|
const customPomClassIdentifierMap = addCustomPomImports();
|
|
1499
|
+
const customPomAvailableClassIdentifiers = new Set(Object.values(customPomClassIdentifierMap ?? {}));
|
|
1483
1500
|
|
|
1484
1501
|
// Collect any navigation return types referenced by generated methods so we can emit
|
|
1485
1502
|
// stub classes when the destination view has no generated test ids (and therefore no
|
|
@@ -1681,6 +1698,7 @@ async function generateAggregatedFiles(
|
|
|
1681
1698
|
|
|
1682
1699
|
customPomAttachments: options.customPomAttachments ?? [],
|
|
1683
1700
|
customPomClassIdentifierMap,
|
|
1701
|
+
customPomAvailableClassIdentifiers,
|
|
1684
1702
|
testIdAttribute: options.testIdAttribute,
|
|
1685
1703
|
vueRouterFluentChaining: options.vueRouterFluentChaining,
|
|
1686
1704
|
routeMetaByComponent: options.routeMetaByComponent,
|
|
@@ -1808,7 +1826,11 @@ interface WidgetInstance {
|
|
|
1808
1826
|
testId: string;
|
|
1809
1827
|
}
|
|
1810
1828
|
|
|
1811
|
-
function getWidgetInstancesForView(
|
|
1829
|
+
function getWidgetInstancesForView(
|
|
1830
|
+
componentName: string,
|
|
1831
|
+
dataTestIdSet: Set<IDataTestId>,
|
|
1832
|
+
availableClassIdentifiers: Set<string>,
|
|
1833
|
+
): WidgetInstance[] {
|
|
1812
1834
|
const out: WidgetInstance[] = [];
|
|
1813
1835
|
const usedPropNames = new Set<string>();
|
|
1814
1836
|
|
|
@@ -1849,6 +1871,9 @@ function getWidgetInstancesForView(componentName: string, dataTestIdSet: Set<IDa
|
|
|
1849
1871
|
continue;
|
|
1850
1872
|
}
|
|
1851
1873
|
|
|
1874
|
+
if (!availableClassIdentifiers.has(className))
|
|
1875
|
+
continue;
|
|
1876
|
+
|
|
1852
1877
|
// Prefer stripping the view prefix (e.g. PreferencesPage-) for cleaner member names.
|
|
1853
1878
|
const viewPrefix = `${componentName}-`;
|
|
1854
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;
|
|
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
|
@@ -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
|
-
|
|
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 (
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
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
|
}
|
|
@@ -1694,6 +1859,12 @@ function applyResolvedDataTestId(args) {
|
|
|
1694
1859
|
const existingIdBehavior = args.existingIdBehavior ?? "preserve";
|
|
1695
1860
|
const nameCollisionBehavior = args.nameCollisionBehavior ?? "suffix";
|
|
1696
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
|
+
};
|
|
1697
1868
|
let dataTestId = args.preferredGeneratedValue;
|
|
1698
1869
|
let fromExisting = false;
|
|
1699
1870
|
const existing = tryGetExistingElementDataTestId(args.element, testIdAttribute);
|
|
@@ -1716,6 +1887,7 @@ Bulk cleanup: run ESLint with the @immense/vue-pom-generator/remove-existing-tes
|
|
|
1716
1887
|
if (existingIdBehavior === "preserve") {
|
|
1717
1888
|
if (existing.isDynamic) {
|
|
1718
1889
|
if (existing.template) {
|
|
1890
|
+
const existingTemplate = existing.template;
|
|
1719
1891
|
if ((existing.templateExpressionCount ?? 0) !== 1) {
|
|
1720
1892
|
throw new Error(
|
|
1721
1893
|
`[vue-pom-generator] Existing ${attrLabel} is a template literal with multiple interpolations and cannot be preserved safely.
|
|
@@ -1726,8 +1898,8 @@ Existing ${attrLabel}: ${JSON.stringify(existing.value)}
|
|
|
1726
1898
|
Fix: reduce the template to a single key-based interpolation, or remove the explicit ${attrLabel} so it can be auto-generated.`
|
|
1727
1899
|
);
|
|
1728
1900
|
}
|
|
1729
|
-
const hasExact = args.bestKeyPlaceholder &&
|
|
1730
|
-
const hasVarAccess = args.bestKeyVariable
|
|
1901
|
+
const hasExact = args.bestKeyPlaceholder && existingTemplate.includes(args.bestKeyPlaceholder);
|
|
1902
|
+
const hasVarAccess = getBestKeyAccessCandidates(args.bestKeyVariable).some((candidate) => existingTemplate.includes(candidate));
|
|
1731
1903
|
if (!hasExact && !hasVarAccess && args.bestKeyPlaceholder) {
|
|
1732
1904
|
throw new Error(
|
|
1733
1905
|
`[vue-pom-generator] Existing ${attrLabel} appears to be missing the key placeholder needed to keep it unique.
|
|
@@ -3240,6 +3412,10 @@ function escapeGitAttributesPattern(value) {
|
|
|
3240
3412
|
}
|
|
3241
3413
|
return output;
|
|
3242
3414
|
}
|
|
3415
|
+
function pathUsesGeneratedHeuristic(filePath) {
|
|
3416
|
+
const normalized = path.normalize(filePath);
|
|
3417
|
+
return normalized.split(path.sep).includes("__generated__");
|
|
3418
|
+
}
|
|
3243
3419
|
function buildManagedGitAttributesBlock(entries) {
|
|
3244
3420
|
return [
|
|
3245
3421
|
GENERATED_GITATTRIBUTES_BLOCK_START,
|
|
@@ -3287,6 +3463,9 @@ function buildGeneratedGitAttributesFiles(generatedFilePaths) {
|
|
|
3287
3463
|
if (path.basename(resolvedFilePath) === ".gitattributes") {
|
|
3288
3464
|
continue;
|
|
3289
3465
|
}
|
|
3466
|
+
if (pathUsesGeneratedHeuristic(resolvedFilePath)) {
|
|
3467
|
+
continue;
|
|
3468
|
+
}
|
|
3290
3469
|
const dir = path.dirname(resolvedFilePath);
|
|
3291
3470
|
const entry = `${escapeGitAttributesPattern(path.basename(resolvedFilePath))} linguist-generated`;
|
|
3292
3471
|
const entries = entriesByDir.get(dir) ?? /* @__PURE__ */ new Set();
|
|
@@ -3711,14 +3890,18 @@ function generateViewObjectModelContent(componentName, dependencies, componentHi
|
|
|
3711
3890
|
}
|
|
3712
3891
|
return false;
|
|
3713
3892
|
};
|
|
3893
|
+
const customPomClassIdentifierMap = options.customPomClassIdentifierMap ?? {};
|
|
3894
|
+
const customPomAvailableClassIdentifiers = options.customPomAvailableClassIdentifiers ?? /* @__PURE__ */ new Set();
|
|
3714
3895
|
const attachmentsForThisClass = customPomAttachments.filter((a) => {
|
|
3896
|
+
if (!Object.prototype.hasOwnProperty.call(customPomClassIdentifierMap, a.className))
|
|
3897
|
+
return false;
|
|
3715
3898
|
const scope = a.attachTo ?? "views";
|
|
3716
3899
|
const scopeOk = isView ? scope === "views" || scope === "both" : scope === "components" || scope === "both";
|
|
3717
3900
|
if (!scopeOk)
|
|
3718
3901
|
return false;
|
|
3719
3902
|
return a.attachWhenUsesComponents.some((c) => hasChildComponent(c));
|
|
3720
3903
|
}).map((a) => ({
|
|
3721
|
-
className:
|
|
3904
|
+
className: customPomClassIdentifierMap[a.className],
|
|
3722
3905
|
propertyName: a.propertyName
|
|
3723
3906
|
}));
|
|
3724
3907
|
let content = "";
|
|
@@ -3758,7 +3941,7 @@ function generateViewObjectModelContent(componentName, dependencies, componentHi
|
|
|
3758
3941
|
content += `
|
|
3759
3942
|
export class ${className} extends BasePage {
|
|
3760
3943
|
`;
|
|
3761
|
-
const widgetInstances = isView ? getWidgetInstancesForView(componentName, dependencies.dataTestIdSet) : [];
|
|
3944
|
+
const widgetInstances = isView ? getWidgetInstancesForView(componentName, dependencies.dataTestIdSet, customPomAvailableClassIdentifiers) : [];
|
|
3762
3945
|
const componentRefsForInstances = isView ? usedComponentSet?.size ? usedComponentSet : childrenComponentSet : childrenComponentSet;
|
|
3763
3946
|
if (isView && (componentRefsForInstances.size > 0 || attachmentsForThisClass.length > 0 || widgetInstances.length > 0)) {
|
|
3764
3947
|
content += getComponentInstances(componentRefsForInstances, componentHierarchyMap, attachmentsForThisClass, widgetInstances);
|
|
@@ -3916,6 +4099,7 @@ Fix by setting generation.playwright.customPoms.importAliases["${exportName}"] t
|
|
|
3916
4099
|
return customPomClassIdentifierMap2;
|
|
3917
4100
|
};
|
|
3918
4101
|
const customPomClassIdentifierMap = addCustomPomImports();
|
|
4102
|
+
const customPomAvailableClassIdentifiers = new Set(Object.values(customPomClassIdentifierMap ?? {}));
|
|
3919
4103
|
const referencedTargets = /* @__PURE__ */ new Set();
|
|
3920
4104
|
for (const [, deps] of items) {
|
|
3921
4105
|
for (const dt of deps.dataTestIdSet) {
|
|
@@ -4068,6 +4252,7 @@ Fix by setting generation.playwright.customPoms.importAliases["${exportName}"] t
|
|
|
4068
4252
|
aggregated: true,
|
|
4069
4253
|
customPomAttachments: options.customPomAttachments ?? [],
|
|
4070
4254
|
customPomClassIdentifierMap,
|
|
4255
|
+
customPomAvailableClassIdentifiers,
|
|
4071
4256
|
testIdAttribute: options.testIdAttribute,
|
|
4072
4257
|
vueRouterFluentChaining: options.vueRouterFluentChaining,
|
|
4073
4258
|
routeMetaByComponent: options.routeMetaByComponent
|
|
@@ -4162,7 +4347,7 @@ function toPascalCaseLocal(str) {
|
|
|
4162
4347
|
return preserveInternalCaps ? upperFirst(word) : upperFirst(word.toLowerCase());
|
|
4163
4348
|
}).join("");
|
|
4164
4349
|
}
|
|
4165
|
-
function getWidgetInstancesForView(componentName, dataTestIdSet) {
|
|
4350
|
+
function getWidgetInstancesForView(componentName, dataTestIdSet, availableClassIdentifiers) {
|
|
4166
4351
|
const out = [];
|
|
4167
4352
|
const usedPropNames = /* @__PURE__ */ new Set();
|
|
4168
4353
|
const ensureUnique = (base) => {
|
|
@@ -4193,6 +4378,8 @@ function getWidgetInstancesForView(componentName, dataTestIdSet) {
|
|
|
4193
4378
|
} else {
|
|
4194
4379
|
continue;
|
|
4195
4380
|
}
|
|
4381
|
+
if (!availableClassIdentifiers.has(className))
|
|
4382
|
+
continue;
|
|
4196
4383
|
const viewPrefix = `${componentName}-`;
|
|
4197
4384
|
const descriptorRaw = stem.startsWith(viewPrefix) ? stem.slice(viewPrefix.length) : stem;
|
|
4198
4385
|
const descriptorPascal = toPascalCaseLocal(descriptorRaw);
|
|
@@ -6197,7 +6384,7 @@ function createVuePomGeneratorPlugins(options = {}) {
|
|
|
6197
6384
|
const excludedComponents = injection.excludeComponents ?? [];
|
|
6198
6385
|
const testIdAttribute = (injection.attribute ?? "data-testid").trim() || "data-testid";
|
|
6199
6386
|
const existingIdBehavior = injection.existingIdBehavior ?? "preserve";
|
|
6200
|
-
const outDir = (generationOptions?.outDir ?? "tests/playwright/
|
|
6387
|
+
const outDir = (generationOptions?.outDir ?? "tests/playwright/__generated__").trim();
|
|
6201
6388
|
const emitLanguages = generationOptions?.emit && generationOptions.emit.length ? generationOptions.emit : ["ts"];
|
|
6202
6389
|
const nameCollisionBehavior = generationOptions?.nameCollisionBehavior ?? "suffix";
|
|
6203
6390
|
const routerEntry = generationOptions?.router?.entry;
|