@immense/vue-pom-generator 1.0.31 → 1.0.33
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 +27 -41
- package/class-generation/index.ts +19 -3
- package/dist/class-generation/index.d.ts.map +1 -1
- package/dist/index.cjs +252 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +252 -26
- package/dist/index.mjs.map +1 -1
- package/dist/plugin/support/build-plugin.d.ts.map +1 -1
- package/dist/plugin/support/dev-plugin.d.ts.map +1 -1
- package/dist/plugin/support/generation-metrics.d.ts +10 -0
- package/dist/plugin/support/generation-metrics.d.ts.map +1 -0
- package/dist/plugin/vue-plugin.d.ts +1 -0
- package/dist/plugin/vue-plugin.d.ts.map +1 -1
- package/dist/tests/generation-metrics.test.d.ts +2 -0
- package/dist/tests/generation-metrics.test.d.ts.map +1 -0
- package/dist/transform.d.ts.map +1 -1
- package/dist/utils.d.ts +10 -0
- package/dist/utils.d.ts.map +1 -1
- package/package.json +1 -1
package/RELEASE_NOTES.md
CHANGED
|
@@ -1,57 +1,43 @@
|
|
|
1
|
-
●
|
|
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.33
|
|
9
2
|
|
|
10
3
|
## Highlights
|
|
11
4
|
|
|
12
|
-
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
`
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
-
|
|
19
|
-
and binding identifiers
|
|
5
|
+
- Fixed singleton key test-id inference to correctly handle edge cases
|
|
6
|
+
- Added comprehensive generation metrics and diagnostics system for better visibility into
|
|
7
|
+
transform operations
|
|
8
|
+
- Introduced new `generation-metrics.ts` module for tracking and reporting code generation
|
|
9
|
+
statistics
|
|
10
|
+
- Expanded transform logic with 192 additional lines of processing logic
|
|
11
|
+
- Enhanced plugin architecture with improved metric collection in dev and build modes
|
|
20
12
|
|
|
21
13
|
## Changes
|
|
22
14
|
|
|
23
|
-
**
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
- `.gitattributes` entries are now only generated when using custom output directories outside
|
|
27
|
-
`__generated__`
|
|
28
|
-
|
|
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
|
|
15
|
+
**Core Transformation**
|
|
16
|
+
- Fixed singleton key test-id inference logic
|
|
17
|
+
- Expanded `transform.ts` with enhanced processing capabilities
|
|
34
18
|
|
|
35
|
-
**
|
|
36
|
-
- Added
|
|
37
|
-
`
|
|
38
|
-
-
|
|
39
|
-
-
|
|
19
|
+
**Metrics & Diagnostics**
|
|
20
|
+
- Added `plugin/support/generation-metrics.ts` for tracking generation statistics
|
|
21
|
+
- Updated `plugin/vue-plugin.ts` with enhanced metrics collection (85 lines modified)
|
|
22
|
+
- Improved diagnostic output in `plugin/support/dev-plugin.ts` and
|
|
23
|
+
`plugin/support/build-plugin.ts`
|
|
40
24
|
|
|
41
|
-
|
|
25
|
+
**Testing**
|
|
26
|
+
- Added comprehensive test suite in `tests/generation-metrics.test.ts` (47 tests)
|
|
27
|
+
- Expanded `tests/transform.test.ts` with 131 additional test cases
|
|
28
|
+
- Enhanced `tests/options.test.ts` with 72 new test cases
|
|
42
29
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
"tests/playwright/generated"` in your config.
|
|
30
|
+
**Infrastructure**
|
|
31
|
+
- Updated plugin creation in `plugin/create-vue-pom-generator-plugins.ts`
|
|
32
|
+
- Minor utility adjustments in `utils.ts`
|
|
47
33
|
|
|
48
34
|
## Pull Requests Included
|
|
49
35
|
|
|
50
|
-
-
|
|
51
|
-
|
|
36
|
+
- #1 Add PR release-notes preview comments (https://github.com/immense/vue-pom-generator/pull/1)
|
|
37
|
+
by @dkattan
|
|
52
38
|
|
|
53
39
|
## Testing
|
|
54
40
|
|
|
55
|
-
Added
|
|
56
|
-
|
|
41
|
+
Added 250+ new test cases across generation metrics, transform logic, and options handling. All
|
|
42
|
+
tests passing.
|
|
57
43
|
|
|
@@ -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:
|
|
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(
|
|
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;
|
|
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 &&
|
|
1895
|
-
const hasVarAccess = 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:
|
|
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);
|
|
@@ -4433,6 +4448,32 @@ function getConstructor(childrenComponent, componentHierarchyMap, attachmentsFor
|
|
|
4433
4448
|
return `${content}
|
|
4434
4449
|
`;
|
|
4435
4450
|
}
|
|
4451
|
+
function getGenerationMetrics(componentHierarchyMap) {
|
|
4452
|
+
let selectorCount = 0;
|
|
4453
|
+
let generatedMethodCount = 0;
|
|
4454
|
+
for (const deps of componentHierarchyMap.values()) {
|
|
4455
|
+
selectorCount += deps.dataTestIdSet?.size ?? 0;
|
|
4456
|
+
generatedMethodCount += deps.generatedMethods?.size ?? 0;
|
|
4457
|
+
}
|
|
4458
|
+
return {
|
|
4459
|
+
entryCount: componentHierarchyMap.size,
|
|
4460
|
+
selectorCount,
|
|
4461
|
+
generatedMethodCount
|
|
4462
|
+
};
|
|
4463
|
+
}
|
|
4464
|
+
function isLessRich(current, previous) {
|
|
4465
|
+
if (current.entryCount !== previous.entryCount) {
|
|
4466
|
+
return current.entryCount < previous.entryCount;
|
|
4467
|
+
}
|
|
4468
|
+
if (current.selectorCount !== previous.selectorCount) {
|
|
4469
|
+
return current.selectorCount < previous.selectorCount;
|
|
4470
|
+
}
|
|
4471
|
+
return current.generatedMethodCount < previous.generatedMethodCount;
|
|
4472
|
+
}
|
|
4473
|
+
function getGenerationMetricsKey(projectRoot, outDir) {
|
|
4474
|
+
return path.resolve(projectRoot, outDir ?? "./pom");
|
|
4475
|
+
}
|
|
4476
|
+
const buildGenerationMetricsByOutputKey = /* @__PURE__ */ new Map();
|
|
4436
4477
|
function createBuildProcessorPlugin(options) {
|
|
4437
4478
|
const {
|
|
4438
4479
|
componentHierarchyMap,
|
|
@@ -4455,7 +4496,6 @@ function createBuildProcessorPlugin(options) {
|
|
|
4455
4496
|
routerModuleShims,
|
|
4456
4497
|
loggerRef
|
|
4457
4498
|
} = options;
|
|
4458
|
-
let lastGeneratedEntryCount = 0;
|
|
4459
4499
|
return {
|
|
4460
4500
|
name: "vue-pom-generator-build",
|
|
4461
4501
|
// This plugin exists to generate code on build output; it is not needed during dev-server HMR.
|
|
@@ -4502,11 +4542,13 @@ function createBuildProcessorPlugin(options) {
|
|
|
4502
4542
|
this.addWatchFile(pointerPath);
|
|
4503
4543
|
},
|
|
4504
4544
|
buildEnd() {
|
|
4505
|
-
const
|
|
4506
|
-
if (entryCount <= 0) {
|
|
4545
|
+
const metrics = getGenerationMetrics(componentHierarchyMap);
|
|
4546
|
+
if (metrics.entryCount <= 0 || metrics.selectorCount <= 0) {
|
|
4507
4547
|
return;
|
|
4508
4548
|
}
|
|
4509
|
-
|
|
4549
|
+
const generationMetricsKey = getGenerationMetricsKey(projectRootRef.current, outDir);
|
|
4550
|
+
const previousMetrics = buildGenerationMetricsByOutputKey.get(generationMetricsKey);
|
|
4551
|
+
if (previousMetrics && isLessRich(metrics, previousMetrics)) {
|
|
4510
4552
|
return;
|
|
4511
4553
|
}
|
|
4512
4554
|
generateFiles(componentHierarchyMap, vueFilesPathMap, normalizedBasePagePath, {
|
|
@@ -4524,8 +4566,8 @@ function createBuildProcessorPlugin(options) {
|
|
|
4524
4566
|
routerEntry: resolvedRouterEntry,
|
|
4525
4567
|
routerType
|
|
4526
4568
|
});
|
|
4527
|
-
|
|
4528
|
-
loggerRef.current.info(`generated POMs (${entryCount} entries)`);
|
|
4569
|
+
buildGenerationMetricsByOutputKey.set(generationMetricsKey, metrics);
|
|
4570
|
+
loggerRef.current.info(`generated POMs (${metrics.entryCount} entries, ${metrics.selectorCount} selectors)`);
|
|
4529
4571
|
},
|
|
4530
4572
|
closeBundle() {
|
|
4531
4573
|
loggerRef.current.info("build complete");
|
|
@@ -4563,6 +4605,89 @@ function toKebabCaseTag(tag) {
|
|
|
4563
4605
|
}
|
|
4564
4606
|
return result;
|
|
4565
4607
|
}
|
|
4608
|
+
function getStaticAttributeContent(element, name) {
|
|
4609
|
+
const attr = element.props.find((prop) => {
|
|
4610
|
+
return prop.type === compilerCore.NodeTypes.ATTRIBUTE && prop.name === name;
|
|
4611
|
+
});
|
|
4612
|
+
return attr?.value?.content?.trim() || null;
|
|
4613
|
+
}
|
|
4614
|
+
function getNativeHtmlControlRole(element) {
|
|
4615
|
+
const tag = (element.tag || "").toLowerCase();
|
|
4616
|
+
const type = (getStaticAttributeContent(element, "type") || "").toLowerCase();
|
|
4617
|
+
if (tag === "textarea") {
|
|
4618
|
+
return "input";
|
|
4619
|
+
}
|
|
4620
|
+
if (tag === "select") {
|
|
4621
|
+
return "select";
|
|
4622
|
+
}
|
|
4623
|
+
if (tag !== "input") {
|
|
4624
|
+
return null;
|
|
4625
|
+
}
|
|
4626
|
+
if (type === "radio") {
|
|
4627
|
+
return "radio";
|
|
4628
|
+
}
|
|
4629
|
+
if (type === "checkbox") {
|
|
4630
|
+
return "checkbox";
|
|
4631
|
+
}
|
|
4632
|
+
return "input";
|
|
4633
|
+
}
|
|
4634
|
+
function normalizeControlLabelText(value) {
|
|
4635
|
+
const normalized = (value ?? "").replace(/\*/g, " ").replace(/\s+/g, " ").trim();
|
|
4636
|
+
return normalized || null;
|
|
4637
|
+
}
|
|
4638
|
+
function getLabelNodeText(labelNode) {
|
|
4639
|
+
for (const child of labelNode.children || []) {
|
|
4640
|
+
if (child.type === compilerCore.NodeTypes.TEXT) {
|
|
4641
|
+
const normalized2 = normalizeControlLabelText(child.content);
|
|
4642
|
+
if (normalized2) {
|
|
4643
|
+
return normalized2;
|
|
4644
|
+
}
|
|
4645
|
+
continue;
|
|
4646
|
+
}
|
|
4647
|
+
if (child.type !== compilerCore.NodeTypes.ELEMENT) {
|
|
4648
|
+
continue;
|
|
4649
|
+
}
|
|
4650
|
+
if (getNativeHtmlControlRole(child)) {
|
|
4651
|
+
continue;
|
|
4652
|
+
}
|
|
4653
|
+
const normalized = normalizeControlLabelText(getInnerText(child));
|
|
4654
|
+
if (normalized) {
|
|
4655
|
+
return normalized;
|
|
4656
|
+
}
|
|
4657
|
+
}
|
|
4658
|
+
return normalizeControlLabelText(getInnerText(labelNode));
|
|
4659
|
+
}
|
|
4660
|
+
function getAssociatedLabelText(element, hierarchyMap2) {
|
|
4661
|
+
let parent = hierarchyMap2.get(element) || null;
|
|
4662
|
+
while (parent) {
|
|
4663
|
+
if (parent.tag === "label") {
|
|
4664
|
+
return getLabelNodeText(parent);
|
|
4665
|
+
}
|
|
4666
|
+
parent = hierarchyMap2.get(parent) || null;
|
|
4667
|
+
}
|
|
4668
|
+
const id = getStaticAttributeContent(element, "id");
|
|
4669
|
+
if (!id) {
|
|
4670
|
+
return null;
|
|
4671
|
+
}
|
|
4672
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
4673
|
+
for (const child of hierarchyMap2.keys()) {
|
|
4674
|
+
candidates.add(child);
|
|
4675
|
+
}
|
|
4676
|
+
for (const maybeParent of hierarchyMap2.values()) {
|
|
4677
|
+
if (maybeParent) {
|
|
4678
|
+
candidates.add(maybeParent);
|
|
4679
|
+
}
|
|
4680
|
+
}
|
|
4681
|
+
for (const candidate of candidates) {
|
|
4682
|
+
if (candidate.tag !== "label") {
|
|
4683
|
+
continue;
|
|
4684
|
+
}
|
|
4685
|
+
if (getStaticAttributeContent(candidate, "for") === id) {
|
|
4686
|
+
return getLabelNodeText(candidate);
|
|
4687
|
+
}
|
|
4688
|
+
}
|
|
4689
|
+
return null;
|
|
4690
|
+
}
|
|
4566
4691
|
function normalizeSearchRoots(wrapperSearchRoots) {
|
|
4567
4692
|
const normalized = /* @__PURE__ */ new Set();
|
|
4568
4693
|
for (const root of wrapperSearchRoots) {
|
|
@@ -5230,7 +5355,9 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
|
|
|
5230
5355
|
}
|
|
5231
5356
|
}
|
|
5232
5357
|
const getBestAvailableKeyValue = () => {
|
|
5233
|
-
const
|
|
5358
|
+
const parentNode = context.parent && typeof context.parent === "object" ? context.parent : null;
|
|
5359
|
+
const isDirectVForChild = parentNode?.type === compilerCore.NodeTypes.FOR;
|
|
5360
|
+
const vForKey = (isDirectVForChild ? getKeyDirectiveValue(element, context) : null) || getContainedInVForDirectiveKeyValue(context, element, hierarchyMap);
|
|
5234
5361
|
if (vForKey) return vForKey;
|
|
5235
5362
|
return getContainedInSlotDataKeyValue(element, hierarchyMap);
|
|
5236
5363
|
};
|
|
@@ -5387,6 +5514,50 @@ Fix: remove the explicit ${attrLabel}, or change existingIdBehavior to "overwrit
|
|
|
5387
5514
|
});
|
|
5388
5515
|
return;
|
|
5389
5516
|
}
|
|
5517
|
+
const nativeHtmlRole = getNativeHtmlControlRole(element);
|
|
5518
|
+
if (nativeHtmlRole) {
|
|
5519
|
+
const rawIdentifier = getStaticAttributeContent(element, "id") || getStaticAttributeContent(element, "name");
|
|
5520
|
+
const labelText = getAssociatedLabelText(element, hierarchyMap);
|
|
5521
|
+
const { vModel, modelValue } = getModelBindingValues(element);
|
|
5522
|
+
const bindingHint = modelValue || vModel || null;
|
|
5523
|
+
const labelToken = labelText ? toPascalCase(labelText) : "";
|
|
5524
|
+
const bindingToken = bindingHint ? toPascalCase(bindingHint) : "";
|
|
5525
|
+
let identifierToken = null;
|
|
5526
|
+
let semanticNameHint2;
|
|
5527
|
+
if (nativeHtmlRole === "radio" || nativeHtmlRole === "checkbox") {
|
|
5528
|
+
if (rawIdentifier) {
|
|
5529
|
+
identifierToken = rawIdentifier;
|
|
5530
|
+
semanticNameHint2 = rawIdentifier;
|
|
5531
|
+
} else if (bindingToken && labelToken) {
|
|
5532
|
+
identifierToken = `${bindingToken}${labelToken}`;
|
|
5533
|
+
semanticNameHint2 = `${bindingHint || bindingToken} ${labelText || labelToken}`;
|
|
5534
|
+
} else if (labelToken) {
|
|
5535
|
+
identifierToken = labelToken;
|
|
5536
|
+
semanticNameHint2 = labelText || labelToken;
|
|
5537
|
+
} else if (bindingToken) {
|
|
5538
|
+
identifierToken = bindingToken;
|
|
5539
|
+
semanticNameHint2 = bindingHint || bindingToken;
|
|
5540
|
+
}
|
|
5541
|
+
} else if (rawIdentifier) {
|
|
5542
|
+
identifierToken = rawIdentifier;
|
|
5543
|
+
semanticNameHint2 = rawIdentifier;
|
|
5544
|
+
} else if (labelToken) {
|
|
5545
|
+
identifierToken = labelToken;
|
|
5546
|
+
semanticNameHint2 = labelText || labelToken;
|
|
5547
|
+
} else if (bindingToken) {
|
|
5548
|
+
identifierToken = bindingToken;
|
|
5549
|
+
semanticNameHint2 = bindingHint || bindingToken;
|
|
5550
|
+
}
|
|
5551
|
+
if (identifierToken) {
|
|
5552
|
+
const preferredGeneratedValue = bestKeyPlaceholder ? templateAttributeValue(`${componentName}-${bestKeyPlaceholder}-${identifierToken}-${nativeHtmlRole}`) : staticAttributeValue(`${componentName}-${identifierToken}-${nativeHtmlRole}`);
|
|
5553
|
+
applyResolvedDataTestIdForElement({
|
|
5554
|
+
preferredGeneratedValue,
|
|
5555
|
+
nativeRoleOverride: nativeHtmlRole,
|
|
5556
|
+
semanticNameHint: semanticNameHint2 || conditionalHint || void 0
|
|
5557
|
+
});
|
|
5558
|
+
return;
|
|
5559
|
+
}
|
|
5560
|
+
}
|
|
5390
5561
|
const innerText = getInnerText(element) || null;
|
|
5391
5562
|
const toDirective = nodeHasToDirective(element);
|
|
5392
5563
|
if (toDirective) {
|
|
@@ -5505,6 +5676,7 @@ function resolveComponentNameFromPath(options) {
|
|
|
5505
5676
|
}
|
|
5506
5677
|
return toPascalCase(path.parse(absFilename).name);
|
|
5507
5678
|
}
|
|
5679
|
+
const devStartupMetricsByOutputKey = /* @__PURE__ */ new Map();
|
|
5508
5680
|
function createDevProcessorPlugin(options) {
|
|
5509
5681
|
const {
|
|
5510
5682
|
nativeWrappers,
|
|
@@ -5698,6 +5870,21 @@ function createDevProcessorPlugin(options) {
|
|
|
5698
5870
|
logInfo(`initial compile: ${compiledCount}/${totalVueFiles} files in ${formatMs(t1 - t0)} (components=${snapshotHierarchy.size})`);
|
|
5699
5871
|
};
|
|
5700
5872
|
const generateAggregatedFromSnapshot = (reason) => {
|
|
5873
|
+
const metrics = getGenerationMetrics(snapshotHierarchy);
|
|
5874
|
+
if (metrics.entryCount <= 0 || metrics.selectorCount <= 0) {
|
|
5875
|
+
logInfo(`generate(${reason}): skipped empty snapshot (components=${metrics.entryCount}, selectors=${metrics.selectorCount})`);
|
|
5876
|
+
return;
|
|
5877
|
+
}
|
|
5878
|
+
const generationMetricsKey = getGenerationMetricsKey(projectRootRef.current, outDir);
|
|
5879
|
+
if (reason === "startup") {
|
|
5880
|
+
const previousMetrics = devStartupMetricsByOutputKey.get(generationMetricsKey);
|
|
5881
|
+
if (previousMetrics && isLessRich(metrics, previousMetrics)) {
|
|
5882
|
+
logInfo(
|
|
5883
|
+
`generate(${reason}): skipped smaller snapshot (components=${metrics.entryCount}, selectors=${metrics.selectorCount})`
|
|
5884
|
+
);
|
|
5885
|
+
return;
|
|
5886
|
+
}
|
|
5887
|
+
}
|
|
5701
5888
|
const t0 = node_perf_hooks.performance.now();
|
|
5702
5889
|
generateFiles(snapshotHierarchy, snapshotVuePathMap, normalizedBasePagePath, {
|
|
5703
5890
|
outDir,
|
|
@@ -5714,8 +5901,11 @@ function createDevProcessorPlugin(options) {
|
|
|
5714
5901
|
routerEntry: resolvedRouterEntry,
|
|
5715
5902
|
routerType
|
|
5716
5903
|
});
|
|
5904
|
+
if (reason === "startup") {
|
|
5905
|
+
devStartupMetricsByOutputKey.set(generationMetricsKey, metrics);
|
|
5906
|
+
}
|
|
5717
5907
|
const t1 = node_perf_hooks.performance.now();
|
|
5718
|
-
logInfo(`generate(${reason}): components=${
|
|
5908
|
+
logInfo(`generate(${reason}): components=${metrics.entryCount} selectors=${metrics.selectorCount} in ${formatMs(t1 - t0)}`);
|
|
5719
5909
|
};
|
|
5720
5910
|
const initialBuildPromise = (async () => {
|
|
5721
5911
|
const t0 = node_perf_hooks.performance.now();
|
|
@@ -6236,20 +6426,21 @@ function createVuePluginWithTestIds(options) {
|
|
|
6236
6426
|
}
|
|
6237
6427
|
];
|
|
6238
6428
|
};
|
|
6429
|
+
const runtimeNodeTransform = (node, context) => {
|
|
6430
|
+
const filename = context.filename;
|
|
6431
|
+
if (!filename || !filename.endsWith(".vue") || !isFileInScope(filename)) {
|
|
6432
|
+
return;
|
|
6433
|
+
}
|
|
6434
|
+
const transforms = getNodeTransforms(filename);
|
|
6435
|
+
const ourTransform = transforms[transforms.length - 1];
|
|
6436
|
+
return ourTransform(node, context);
|
|
6437
|
+
};
|
|
6239
6438
|
const templateCompilerOptions = {
|
|
6240
6439
|
...userCompilerOptions,
|
|
6241
6440
|
prefixIdentifiers: true,
|
|
6242
6441
|
nodeTransforms: [
|
|
6243
6442
|
...userNodeTransforms,
|
|
6244
|
-
|
|
6245
|
-
const filename = context.filename;
|
|
6246
|
-
if (!filename || !filename.endsWith(".vue") || !isFileInScope(filename)) {
|
|
6247
|
-
return;
|
|
6248
|
-
}
|
|
6249
|
-
const transforms = getNodeTransforms(filename);
|
|
6250
|
-
const ourTransform = transforms[transforms.length - 1];
|
|
6251
|
-
return ourTransform(node, context);
|
|
6252
|
-
}
|
|
6443
|
+
runtimeNodeTransform
|
|
6253
6444
|
]
|
|
6254
6445
|
};
|
|
6255
6446
|
const metadataCollectorPlugin = {
|
|
@@ -6288,7 +6479,42 @@ function createVuePluginWithTestIds(options) {
|
|
|
6288
6479
|
...vueOptions,
|
|
6289
6480
|
template
|
|
6290
6481
|
});
|
|
6291
|
-
|
|
6482
|
+
const nuxtVueBridgePlugin = {
|
|
6483
|
+
name: "vue-pom-generator-nuxt-vue-bridge",
|
|
6484
|
+
apply: "serve",
|
|
6485
|
+
configResolved(config) {
|
|
6486
|
+
const viteVuePlugin = config.plugins.find((plugin) => {
|
|
6487
|
+
return typeof plugin === "object" && plugin !== null && "name" in plugin && plugin.name === "vite:vue" && "api" in plugin;
|
|
6488
|
+
});
|
|
6489
|
+
const api = viteVuePlugin?.api;
|
|
6490
|
+
if (!api) {
|
|
6491
|
+
loggerRef.current.warn("[vue-pom-generator] Nuxt bridge could not find vite:vue plugin to patch.");
|
|
6492
|
+
return;
|
|
6493
|
+
}
|
|
6494
|
+
const currentOptions = api.options ?? {};
|
|
6495
|
+
const currentTemplate = currentOptions.template ?? {};
|
|
6496
|
+
const currentCompilerOptions = currentTemplate.compilerOptions ?? {};
|
|
6497
|
+
const currentNodeTransforms = currentCompilerOptions.nodeTransforms ?? [];
|
|
6498
|
+
if (currentNodeTransforms.includes(runtimeNodeTransform)) {
|
|
6499
|
+
return;
|
|
6500
|
+
}
|
|
6501
|
+
api.options = {
|
|
6502
|
+
...currentOptions,
|
|
6503
|
+
template: {
|
|
6504
|
+
...currentTemplate,
|
|
6505
|
+
compilerOptions: {
|
|
6506
|
+
...currentCompilerOptions,
|
|
6507
|
+
prefixIdentifiers: true,
|
|
6508
|
+
nodeTransforms: [
|
|
6509
|
+
...currentNodeTransforms,
|
|
6510
|
+
runtimeNodeTransform
|
|
6511
|
+
]
|
|
6512
|
+
}
|
|
6513
|
+
}
|
|
6514
|
+
};
|
|
6515
|
+
}
|
|
6516
|
+
};
|
|
6517
|
+
return { metadataCollectorPlugin, internalVuePlugin, nuxtVueBridgePlugin };
|
|
6292
6518
|
}
|
|
6293
6519
|
function assertNonEmptyString(value, name) {
|
|
6294
6520
|
if (!value || !value.trim()) {
|
|
@@ -6414,7 +6640,7 @@ function createVuePomGeneratorPlugins(options = {}) {
|
|
|
6414
6640
|
const semanticNameMap = /* @__PURE__ */ new Map();
|
|
6415
6641
|
const componentHierarchyMap = /* @__PURE__ */ new Map();
|
|
6416
6642
|
const vueFilesPathMap = /* @__PURE__ */ new Map();
|
|
6417
|
-
const { metadataCollectorPlugin, internalVuePlugin } = createVuePluginWithTestIds({
|
|
6643
|
+
const { metadataCollectorPlugin, internalVuePlugin, nuxtVueBridgePlugin } = createVuePluginWithTestIds({
|
|
6418
6644
|
vueOptions,
|
|
6419
6645
|
existingIdBehavior,
|
|
6420
6646
|
nameCollisionBehavior,
|
|
@@ -6465,7 +6691,7 @@ function createVuePomGeneratorPlugins(options = {}) {
|
|
|
6465
6691
|
const resultPlugins = [
|
|
6466
6692
|
configPlugin,
|
|
6467
6693
|
metadataCollectorPlugin,
|
|
6468
|
-
...isNuxt ? [] : [internalVuePlugin],
|
|
6694
|
+
...isNuxt ? [nuxtVueBridgePlugin] : [internalVuePlugin],
|
|
6469
6695
|
...supportPlugins
|
|
6470
6696
|
];
|
|
6471
6697
|
if (!generationEnabled) {
|
|
@@ -6473,7 +6699,7 @@ function createVuePomGeneratorPlugins(options = {}) {
|
|
|
6473
6699
|
return [
|
|
6474
6700
|
configPlugin,
|
|
6475
6701
|
metadataCollectorPlugin,
|
|
6476
|
-
...isNuxt ? [] : [internalVuePlugin],
|
|
6702
|
+
...isNuxt ? [nuxtVueBridgePlugin] : [internalVuePlugin],
|
|
6477
6703
|
virtualModules
|
|
6478
6704
|
];
|
|
6479
6705
|
}
|