@immense/vue-pom-generator 1.0.3 → 1.0.8
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 +85 -71
- package/RELEASE_NOTES.md +38 -0
- package/class-generation/BasePage.ts +17 -2
- package/class-generation/Pointer.ts +1 -1
- package/class-generation/index.ts +77 -24
- package/class-generation/playwright-types.ts +23 -0
- package/dist/index.cjs +583 -208
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +584 -209
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -2
package/dist/index.cjs
CHANGED
|
@@ -26,16 +26,15 @@ const path = require("node:path");
|
|
|
26
26
|
const process = require("node:process");
|
|
27
27
|
const node_url = require("node:url");
|
|
28
28
|
const fs = require("node:fs");
|
|
29
|
+
const jsdom = require("jsdom");
|
|
29
30
|
const compilerCore = require("@vue/compiler-core");
|
|
30
31
|
const types = require("@babel/types");
|
|
31
32
|
const parser = require("@babel/parser");
|
|
32
|
-
const jsdom = require("jsdom");
|
|
33
33
|
const node_perf_hooks = require("node:perf_hooks");
|
|
34
34
|
const compilerDom = require("@vue/compiler-dom");
|
|
35
35
|
const compilerSfc = require("@vue/compiler-sfc");
|
|
36
36
|
const virtualImport = require("vite-plugin-virtual");
|
|
37
37
|
const vue = require("@vitejs/plugin-vue");
|
|
38
|
-
const shared = require("@vue/shared");
|
|
39
38
|
var _documentCurrentScript = typeof document !== "undefined" ? document.currentScript : null;
|
|
40
39
|
function _interopNamespaceDefault(e) {
|
|
41
40
|
const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
|
|
@@ -54,6 +53,56 @@ function _interopNamespaceDefault(e) {
|
|
|
54
53
|
return Object.freeze(n);
|
|
55
54
|
}
|
|
56
55
|
const compilerDom__namespace = /* @__PURE__ */ _interopNamespaceDefault(compilerDom);
|
|
56
|
+
const VUE_POM_GENERATOR_LOG_PREFIX = "[vue-pom-generator]";
|
|
57
|
+
function normalize(message) {
|
|
58
|
+
const trimmed = (message ?? "").trim();
|
|
59
|
+
if (!trimmed)
|
|
60
|
+
return VUE_POM_GENERATOR_LOG_PREFIX;
|
|
61
|
+
if (trimmed.startsWith(VUE_POM_GENERATOR_LOG_PREFIX))
|
|
62
|
+
return trimmed;
|
|
63
|
+
return `${VUE_POM_GENERATOR_LOG_PREFIX} ${trimmed}`;
|
|
64
|
+
}
|
|
65
|
+
function createLogger(options) {
|
|
66
|
+
const { verbosity, viteLogger } = options;
|
|
67
|
+
const sinkInfo = (msg) => {
|
|
68
|
+
if (viteLogger) {
|
|
69
|
+
viteLogger.info(normalize(msg));
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
console.log(normalize(msg));
|
|
73
|
+
};
|
|
74
|
+
const sinkWarn = (msg) => {
|
|
75
|
+
if (viteLogger) {
|
|
76
|
+
viteLogger.warn(normalize(msg));
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
console.warn(normalize(msg));
|
|
80
|
+
};
|
|
81
|
+
const sinkDebug = (msg) => {
|
|
82
|
+
if (viteLogger) {
|
|
83
|
+
viteLogger.info(normalize(msg));
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
console.log(normalize(msg));
|
|
87
|
+
};
|
|
88
|
+
return {
|
|
89
|
+
info(message) {
|
|
90
|
+
if (verbosity === "silent" || verbosity === "warn")
|
|
91
|
+
return;
|
|
92
|
+
sinkInfo(message);
|
|
93
|
+
},
|
|
94
|
+
debug(message) {
|
|
95
|
+
if (verbosity !== "debug")
|
|
96
|
+
return;
|
|
97
|
+
sinkDebug(message);
|
|
98
|
+
},
|
|
99
|
+
warn(message) {
|
|
100
|
+
if (verbosity === "silent")
|
|
101
|
+
return;
|
|
102
|
+
sinkWarn(message);
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
}
|
|
57
106
|
const INDENT = " ";
|
|
58
107
|
const INDENT2 = `${INDENT}${INDENT}`;
|
|
59
108
|
const INDENT3 = `${INDENT2}${INDENT}`;
|
|
@@ -188,10 +237,21 @@ ${INDENT}}
|
|
|
188
237
|
`;
|
|
189
238
|
return content;
|
|
190
239
|
}
|
|
240
|
+
function isAllDigits(value) {
|
|
241
|
+
if (!value)
|
|
242
|
+
return false;
|
|
243
|
+
for (let i = 0; i < value.length; i++) {
|
|
244
|
+
const code = value.charCodeAt(i);
|
|
245
|
+
if (code < 48 || code > 57)
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
return true;
|
|
249
|
+
}
|
|
191
250
|
function generateGetElementByDataTestId(methodName, nativeRole, formattedDataTestId, alternateFormattedDataTestIds, getterNameOverride, params) {
|
|
192
251
|
const roleSuffix = upperFirst$1(nativeRole || "Element");
|
|
193
252
|
const baseName = upperFirst$1(methodName);
|
|
194
|
-
const
|
|
253
|
+
const numericSuffix = baseName.startsWith(roleSuffix) ? baseName.slice(roleSuffix.length) : "";
|
|
254
|
+
const hasRoleSuffix = baseName.endsWith(roleSuffix) || baseName.startsWith(roleSuffix) && isAllDigits(numericSuffix);
|
|
195
255
|
const propertyName = hasRoleSuffix ? `${baseName}` : `${baseName}${roleSuffix}`;
|
|
196
256
|
const needsKey = hasParam(params, "key") || formattedDataTestId.includes("${key}");
|
|
197
257
|
if (needsKey) {
|
|
@@ -497,6 +557,23 @@ function tryGetClickDirective(node) {
|
|
|
497
557
|
function nodeHasClickDirective(node) {
|
|
498
558
|
return tryGetClickDirective(node) !== void 0;
|
|
499
559
|
}
|
|
560
|
+
function getTemplateSlotScope(node) {
|
|
561
|
+
if (node.tag !== "template") {
|
|
562
|
+
return null;
|
|
563
|
+
}
|
|
564
|
+
const slotProp = node.props.find((prop) => {
|
|
565
|
+
return prop.type === compilerCore.NodeTypes.DIRECTIVE && prop.name === "slot";
|
|
566
|
+
});
|
|
567
|
+
if (slotProp?.exp) {
|
|
568
|
+
if (slotProp.exp.type === compilerCore.NodeTypes.SIMPLE_EXPRESSION) {
|
|
569
|
+
return slotProp.exp.content;
|
|
570
|
+
}
|
|
571
|
+
if (slotProp.exp.type === compilerCore.NodeTypes.COMPOUND_EXPRESSION) {
|
|
572
|
+
return compilerCore.stringifyExpression(slotProp.exp);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
return null;
|
|
576
|
+
}
|
|
500
577
|
function nodeHasToDirective(node) {
|
|
501
578
|
const toDirective = findDirectiveByName(node, "bind", "to");
|
|
502
579
|
if (toDirective?.exp) {
|
|
@@ -566,6 +643,37 @@ function getIdOrName(node) {
|
|
|
566
643
|
}
|
|
567
644
|
return identifier;
|
|
568
645
|
}
|
|
646
|
+
function getContainedInSlotDataKeyValue(node, hierarchyMap2) {
|
|
647
|
+
let parent = getParent(hierarchyMap2, node);
|
|
648
|
+
while (parent) {
|
|
649
|
+
if (parent.type === compilerCore.NodeTypes.ELEMENT && parent.tag === "template") {
|
|
650
|
+
const scope = getTemplateSlotScope(parent);
|
|
651
|
+
if (scope) {
|
|
652
|
+
let key = scope.trim();
|
|
653
|
+
if (key.startsWith("{") && key.endsWith("}")) {
|
|
654
|
+
const inner = key.slice(1, -1).trim();
|
|
655
|
+
let cutIdx = -1;
|
|
656
|
+
const commaIdx = inner.indexOf(",");
|
|
657
|
+
const colonIdx = inner.indexOf(":");
|
|
658
|
+
if (commaIdx !== -1 && colonIdx !== -1) {
|
|
659
|
+
cutIdx = Math.min(commaIdx, colonIdx);
|
|
660
|
+
} else if (commaIdx !== -1) {
|
|
661
|
+
cutIdx = commaIdx;
|
|
662
|
+
} else if (colonIdx !== -1) {
|
|
663
|
+
cutIdx = colonIdx;
|
|
664
|
+
}
|
|
665
|
+
const first = (cutIdx === -1 ? inner : inner.slice(0, cutIdx)).trim();
|
|
666
|
+
if (first) {
|
|
667
|
+
key = first;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
return key;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
parent = getParent(hierarchyMap2, parent);
|
|
674
|
+
}
|
|
675
|
+
return null;
|
|
676
|
+
}
|
|
569
677
|
function getContainedInVForDirectiveKeyValue(context, node, hierarchyMap2) {
|
|
570
678
|
if (!context.scopes.vFor || context.scopes.vFor === 0) {
|
|
571
679
|
return null;
|
|
@@ -655,6 +763,9 @@ function tryGetContainedInStaticVForSourceLiteralValues(context, _node, _hierarc
|
|
|
655
763
|
function getParent(hierarchyMap2, node) {
|
|
656
764
|
return hierarchyMap2.get(node) || null;
|
|
657
765
|
}
|
|
766
|
+
function nodeHandlerAttributeValue(node) {
|
|
767
|
+
return nodeHandlerAttributeInfo(node)?.semanticNameHint ?? null;
|
|
768
|
+
}
|
|
658
769
|
function nodeHandlerAttributeInfo(node) {
|
|
659
770
|
const handlerDirective = findDirectiveByName(node, "bind", "handler");
|
|
660
771
|
if (!handlerDirective?.exp) {
|
|
@@ -1013,7 +1124,7 @@ function generateToDirectiveDataTestId(componentName, node, toDirective, context
|
|
|
1013
1124
|
}
|
|
1014
1125
|
const source = compilerCore.stringifyExpression(toDirective.exp);
|
|
1015
1126
|
const toAst = toDirective.exp.ast;
|
|
1016
|
-
const interpolated =
|
|
1127
|
+
const interpolated = toAst !== void 0 && toAst !== null && toAst !== false && types.isTemplateLiteral(toAst);
|
|
1017
1128
|
return templateAttributeValue(`${componentName}-\${${source}${interpolated ? ".replaceAll(' ', '')" : "?.name?.replaceAll(' ', '') ?? ''"}}${formatTagName(node, nativeWrappers)}`);
|
|
1018
1129
|
} else {
|
|
1019
1130
|
const innerText = getInnerText(node);
|
|
@@ -1074,7 +1185,7 @@ function toDirectiveObjectFieldNameValue(node) {
|
|
|
1074
1185
|
return null;
|
|
1075
1186
|
}
|
|
1076
1187
|
}
|
|
1077
|
-
function getComposedClickHandlerContent(node, _context,
|
|
1188
|
+
function getComposedClickHandlerContent(node, _context, innerText, clickDirective, _options = {}) {
|
|
1078
1189
|
const click = clickDirective ?? tryGetClickDirective(node);
|
|
1079
1190
|
if (!click) {
|
|
1080
1191
|
return "";
|
|
@@ -1093,8 +1204,9 @@ function getComposedClickHandlerContent(node, _context, _innerText, clickDirecti
|
|
|
1093
1204
|
}
|
|
1094
1205
|
}
|
|
1095
1206
|
}
|
|
1096
|
-
|
|
1097
|
-
const
|
|
1207
|
+
const hName = normalizeHandlerName(handlerName);
|
|
1208
|
+
const name = hName || innerText || "";
|
|
1209
|
+
const normalizedHandlerSegment = name ? `-${toPascalCase(name)}` : "";
|
|
1098
1210
|
const result = normalizedHandlerSegment;
|
|
1099
1211
|
return result.replace(/[^a-z-]/gi, "");
|
|
1100
1212
|
}
|
|
@@ -1390,6 +1502,31 @@ function tryGetExistingElementDataTestId(node, attributeName = "data-testid") {
|
|
|
1390
1502
|
if (!raw) {
|
|
1391
1503
|
return null;
|
|
1392
1504
|
}
|
|
1505
|
+
try {
|
|
1506
|
+
const ast2 = parser.parseExpression(raw, { plugins: ["typescript"] });
|
|
1507
|
+
if (ast2 && typeof ast2 === "object" && "type" in ast2 && ast2.type === "TemplateLiteral") {
|
|
1508
|
+
const tl = ast2;
|
|
1509
|
+
const cooked = (tl.quasis ?? []).map((q) => q.value?.cooked ?? "").join("");
|
|
1510
|
+
const expressionCount = (tl.expressions ?? []).length;
|
|
1511
|
+
const isStatic = expressionCount === 0;
|
|
1512
|
+
const unwrappedTemplate = raw.startsWith("`") && raw.endsWith("`") && raw.length >= 2 ? raw.slice(1, -1) : cooked;
|
|
1513
|
+
if (isStatic) {
|
|
1514
|
+
return { value: unwrappedTemplate, isDynamic: false, isStaticLiteral: true };
|
|
1515
|
+
}
|
|
1516
|
+
return {
|
|
1517
|
+
value: unwrappedTemplate,
|
|
1518
|
+
isDynamic: true,
|
|
1519
|
+
isStaticLiteral: false,
|
|
1520
|
+
template: unwrappedTemplate,
|
|
1521
|
+
templateExpressionCount: expressionCount
|
|
1522
|
+
};
|
|
1523
|
+
}
|
|
1524
|
+
if (ast2 && typeof ast2 === "object" && "type" in ast2 && ast2.type === "StringLiteral") {
|
|
1525
|
+
const sl = ast2;
|
|
1526
|
+
return { value: sl.value ?? "", isDynamic: false, isStaticLiteral: true };
|
|
1527
|
+
}
|
|
1528
|
+
} catch {
|
|
1529
|
+
}
|
|
1393
1530
|
return { value: raw, isDynamic: true, isStaticLiteral: false, rawExpression: raw };
|
|
1394
1531
|
}
|
|
1395
1532
|
function isTemplatePlaceholder(part) {
|
|
@@ -1517,13 +1654,15 @@ Existing ${attrLabel}: ${JSON.stringify(existing.value)}
|
|
|
1517
1654
|
Fix: reduce the template to a single key-based interpolation, or remove the explicit ${attrLabel} so it can be auto-generated.`
|
|
1518
1655
|
);
|
|
1519
1656
|
}
|
|
1520
|
-
|
|
1657
|
+
const hasExact = args.bestKeyPlaceholder && existing.template.includes(args.bestKeyPlaceholder);
|
|
1658
|
+
const hasVarAccess = args.bestKeyVariable && existing.template.includes(args.bestKeyVariable);
|
|
1659
|
+
if (!hasExact && !hasVarAccess && args.bestKeyPlaceholder) {
|
|
1521
1660
|
throw new Error(
|
|
1522
1661
|
`[vue-pom-generator] Existing ${attrLabel} appears to be missing the key placeholder needed to keep it unique.
|
|
1523
1662
|
Component: ${args.componentName}
|
|
1524
1663
|
File: ${file}:${locationHint}
|
|
1525
1664
|
Existing ${attrLabel}: ${JSON.stringify(existing.value)}
|
|
1526
|
-
Required placeholder: ${JSON.stringify(args.bestKeyPlaceholder)}
|
|
1665
|
+
Required placeholder: ${JSON.stringify(args.bestKeyPlaceholder)}${args.bestKeyVariable ? ` or an access on "${args.bestKeyVariable}"` : ""}
|
|
1527
1666
|
|
|
1528
1667
|
Fix: either (1) include ${args.bestKeyPlaceholder} in your :${attrLabel} template literal, or (2) remove the explicit ${attrLabel} so it can be auto-generated.`
|
|
1529
1668
|
);
|
|
@@ -1548,7 +1687,7 @@ If you really need a computed id, do not set existingIdBehavior="preserve".`
|
|
|
1548
1687
|
Component: ${args.componentName}
|
|
1549
1688
|
File: ${file}:${locationHint}
|
|
1550
1689
|
Existing ${attrLabel}: ${JSON.stringify(existing.value)}
|
|
1551
|
-
Required placeholder: ${JSON.stringify(args.bestKeyPlaceholder)}
|
|
1690
|
+
Required placeholder: ${JSON.stringify(args.bestKeyPlaceholder)}${args.bestKeyVariable ? ` or an access on "${args.bestKeyVariable}"` : ""}
|
|
1552
1691
|
|
|
1553
1692
|
Fix: either (1) include ${args.bestKeyPlaceholder} in your :${attrLabel} template literal, or (2) remove the explicit ${attrLabel} so it can be auto-generated.`
|
|
1554
1693
|
);
|
|
@@ -1575,6 +1714,7 @@ Fix: either (1) include ${args.bestKeyPlaceholder} in your :${attrLabel} templat
|
|
|
1575
1714
|
case "checkbox":
|
|
1576
1715
|
case "toggle":
|
|
1577
1716
|
case "radio":
|
|
1717
|
+
case "grid":
|
|
1578
1718
|
return role;
|
|
1579
1719
|
default:
|
|
1580
1720
|
return void 0;
|
|
@@ -1669,10 +1809,10 @@ Fix: either (1) include ${args.bestKeyPlaceholder} in your :${attrLabel} templat
|
|
|
1669
1809
|
args.dependencies.__pomPrimaryByGetterName ??= /* @__PURE__ */ new Map();
|
|
1670
1810
|
const primaryByActionName = args.dependencies.__pomPrimaryByActionName;
|
|
1671
1811
|
const hintCandidates = (() => {
|
|
1672
|
-
const baseHints = [
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1812
|
+
const baseHints = [
|
|
1813
|
+
args.semanticNameHint,
|
|
1814
|
+
...args.semanticNameHintAlternates ?? []
|
|
1815
|
+
];
|
|
1676
1816
|
const out = [];
|
|
1677
1817
|
const seen = /* @__PURE__ */ new Set();
|
|
1678
1818
|
for (const h of baseHints) {
|
|
@@ -2421,6 +2561,84 @@ async function ensureDomShim() {
|
|
|
2421
2561
|
if (!g.requestAnimationFrame)
|
|
2422
2562
|
g.requestAnimationFrame = (cb) => setTimeout(() => cb(Date.now()), 16);
|
|
2423
2563
|
}
|
|
2564
|
+
async function introspectNuxtPages(projectRoot) {
|
|
2565
|
+
const possiblePagesDirs = ["app/pages", "pages"];
|
|
2566
|
+
let pagesDir = "";
|
|
2567
|
+
for (const dir of possiblePagesDirs) {
|
|
2568
|
+
const abs = path.resolve(projectRoot, dir);
|
|
2569
|
+
if (fs.existsSync(abs) && fs.statSync(abs).isDirectory()) {
|
|
2570
|
+
pagesDir = abs;
|
|
2571
|
+
break;
|
|
2572
|
+
}
|
|
2573
|
+
}
|
|
2574
|
+
if (!pagesDir) {
|
|
2575
|
+
debugLog(`[router-introspection][nuxt] Could not find pages directory in ${projectRoot}`);
|
|
2576
|
+
return { routeNameMap: /* @__PURE__ */ new Map(), routePathMap: /* @__PURE__ */ new Map(), routeMetaEntries: [] };
|
|
2577
|
+
}
|
|
2578
|
+
const routeMetaEntries = [];
|
|
2579
|
+
const walk = (dir, baseRoute) => {
|
|
2580
|
+
const files = fs.readdirSync(dir);
|
|
2581
|
+
for (const file of files) {
|
|
2582
|
+
const fullPath = path.join(dir, file);
|
|
2583
|
+
const stat = fs.statSync(fullPath);
|
|
2584
|
+
if (stat.isDirectory()) {
|
|
2585
|
+
walk(fullPath, `${baseRoute}/${file}`);
|
|
2586
|
+
continue;
|
|
2587
|
+
}
|
|
2588
|
+
if (!file.endsWith(".vue"))
|
|
2589
|
+
continue;
|
|
2590
|
+
const componentName = file.slice(0, -4);
|
|
2591
|
+
if (componentName === "index" && baseRoute === "") {
|
|
2592
|
+
routeMetaEntries.push({
|
|
2593
|
+
componentName: "index",
|
|
2594
|
+
pathTemplate: "/",
|
|
2595
|
+
params: [],
|
|
2596
|
+
query: []
|
|
2597
|
+
});
|
|
2598
|
+
continue;
|
|
2599
|
+
}
|
|
2600
|
+
let routePath = componentName === "index" ? baseRoute : `${baseRoute}/${componentName}`;
|
|
2601
|
+
if (!routePath.startsWith("/"))
|
|
2602
|
+
routePath = `/${routePath}`;
|
|
2603
|
+
const params = [];
|
|
2604
|
+
let pathTemplate = "";
|
|
2605
|
+
for (let i = 0; i < routePath.length; i++) {
|
|
2606
|
+
const ch = routePath[i];
|
|
2607
|
+
if (ch !== "[") {
|
|
2608
|
+
pathTemplate += ch;
|
|
2609
|
+
continue;
|
|
2610
|
+
}
|
|
2611
|
+
let name = "";
|
|
2612
|
+
i++;
|
|
2613
|
+
while (i < routePath.length) {
|
|
2614
|
+
const c = routePath[i];
|
|
2615
|
+
if (c === "]")
|
|
2616
|
+
break;
|
|
2617
|
+
name += c;
|
|
2618
|
+
i++;
|
|
2619
|
+
}
|
|
2620
|
+
if (name) {
|
|
2621
|
+
params.push({ name, optional: false });
|
|
2622
|
+
pathTemplate += `:${name}`;
|
|
2623
|
+
} else {
|
|
2624
|
+
pathTemplate += "[]";
|
|
2625
|
+
}
|
|
2626
|
+
}
|
|
2627
|
+
routeMetaEntries.push({
|
|
2628
|
+
componentName,
|
|
2629
|
+
pathTemplate,
|
|
2630
|
+
params,
|
|
2631
|
+
query: []
|
|
2632
|
+
});
|
|
2633
|
+
}
|
|
2634
|
+
};
|
|
2635
|
+
walk(pagesDir, "");
|
|
2636
|
+
return {
|
|
2637
|
+
routeNameMap: /* @__PURE__ */ new Map(),
|
|
2638
|
+
routePathMap: /* @__PURE__ */ new Map(),
|
|
2639
|
+
routeMetaEntries
|
|
2640
|
+
};
|
|
2641
|
+
}
|
|
2424
2642
|
async function parseRouterFileFromCwd(routerEntryPath) {
|
|
2425
2643
|
return await runRouterIntrospectionExclusive(async () => {
|
|
2426
2644
|
const routerEntry = path.resolve(routerEntryPath);
|
|
@@ -2536,9 +2754,9 @@ function resolveRouterEntry(projectRoot, routerEntry) {
|
|
|
2536
2754
|
const root = projectRoot ?? process.cwd();
|
|
2537
2755
|
return path.isAbsolute(routerEntry) ? routerEntry : path.resolve(root, routerEntry);
|
|
2538
2756
|
}
|
|
2539
|
-
async function getRouteMetaByComponent(projectRoot, routerEntry) {
|
|
2540
|
-
const
|
|
2541
|
-
const { routeMetaEntries } = await parseRouterFileFromCwd(
|
|
2757
|
+
async function getRouteMetaByComponent(projectRoot, routerEntry, routerType) {
|
|
2758
|
+
const root = projectRoot ?? process.cwd();
|
|
2759
|
+
const { routeMetaEntries } = routerType === "nuxt" ? await introspectNuxtPages(root) : await parseRouterFileFromCwd(resolveRouterEntry(root, routerEntry));
|
|
2542
2760
|
const map = /* @__PURE__ */ new Map();
|
|
2543
2761
|
for (const entry of routeMetaEntries) {
|
|
2544
2762
|
const list = map.get(entry.componentName) ?? [];
|
|
@@ -2691,12 +2909,14 @@ async function generateFiles(componentHierarchyMap, vueFilesPathMap, basePageCla
|
|
|
2691
2909
|
customPomImportAliases,
|
|
2692
2910
|
testIdAttribute,
|
|
2693
2911
|
emitLanguages: emitLanguagesOverride,
|
|
2912
|
+
csharp,
|
|
2694
2913
|
vueRouterFluentChaining,
|
|
2695
|
-
routerEntry
|
|
2914
|
+
routerEntry,
|
|
2915
|
+
routerType
|
|
2696
2916
|
} = options;
|
|
2697
2917
|
const emitLanguages = emitLanguagesOverride?.length ? emitLanguagesOverride : ["ts"];
|
|
2698
2918
|
const outDir = outDirOverride ?? "./pom";
|
|
2699
|
-
const routeMetaByComponent = vueRouterFluentChaining ? await getRouteMetaByComponent(projectRoot, routerEntry) : void 0;
|
|
2919
|
+
const routeMetaByComponent = vueRouterFluentChaining ? await getRouteMetaByComponent(projectRoot, routerEntry, routerType) : void 0;
|
|
2700
2920
|
if (emitLanguages.includes("ts")) {
|
|
2701
2921
|
const files = await generateAggregatedFiles(componentHierarchyMap, vueFilesPathMap, basePageClassPath, outDir, {
|
|
2702
2922
|
customPomAttachments,
|
|
@@ -2718,7 +2938,8 @@ async function generateFiles(componentHierarchyMap, vueFilesPathMap, basePageCla
|
|
|
2718
2938
|
}
|
|
2719
2939
|
if (emitLanguages.includes("csharp")) {
|
|
2720
2940
|
const csFiles = generateAggregatedCSharpFiles(componentHierarchyMap, outDir, {
|
|
2721
|
-
|
|
2941
|
+
testIdAttribute,
|
|
2942
|
+
csharp
|
|
2722
2943
|
});
|
|
2723
2944
|
for (const file of csFiles) {
|
|
2724
2945
|
createFile(file.filePath, file.content);
|
|
@@ -2741,11 +2962,11 @@ function toCSharpParam(paramTypeExpr) {
|
|
|
2741
2962
|
const right = eqIdx >= 0 ? trimmed.slice(eqIdx + 1).trim() : void 0;
|
|
2742
2963
|
const typePart = left.includes("|") ? "string" : left;
|
|
2743
2964
|
let type = "string";
|
|
2744
|
-
if (/(
|
|
2965
|
+
if (/(?:^|\s)boolean(?:\s|$)/.test(typePart))
|
|
2745
2966
|
type = "bool";
|
|
2746
|
-
else if (/(
|
|
2967
|
+
else if (/(?:^|\s)string(?:\s|$)/.test(typePart))
|
|
2747
2968
|
type = "string";
|
|
2748
|
-
else if (/(
|
|
2969
|
+
else if (/(?:^|\s)number(?:\s|$)/.test(typePart))
|
|
2749
2970
|
type = "int";
|
|
2750
2971
|
else if (/\d+/.test(typePart) && typePart === "")
|
|
2751
2972
|
type = "int";
|
|
@@ -2782,8 +3003,9 @@ function formatCSharpParams(params) {
|
|
|
2782
3003
|
return { signature: signatureParts.join(", "), argNames };
|
|
2783
3004
|
}
|
|
2784
3005
|
function generateAggregatedCSharpFiles(componentHierarchyMap, outDir, options = {}) {
|
|
2785
|
-
const
|
|
2786
|
-
const
|
|
3006
|
+
const outAbs = ensureDir(outDir);
|
|
3007
|
+
const namespace = options.csharp?.namespace ?? "Playwright.Generated";
|
|
3008
|
+
const testIdAttribute = (options.testIdAttribute || "data-testid").trim() || "data-testid";
|
|
2787
3009
|
const entries = Array.from(componentHierarchyMap.entries()).sort((a, b) => a[0].localeCompare(b[0]));
|
|
2788
3010
|
const header = [
|
|
2789
3011
|
"// <auto-generated>",
|
|
@@ -2797,13 +3019,13 @@ function generateAggregatedCSharpFiles(componentHierarchyMap, outDir, options =
|
|
|
2797
3019
|
"using System.Threading.Tasks;",
|
|
2798
3020
|
"using Microsoft.Playwright;",
|
|
2799
3021
|
"",
|
|
2800
|
-
|
|
3022
|
+
`namespace ${namespace};`,
|
|
2801
3023
|
"",
|
|
2802
|
-
"public abstract class BasePage",
|
|
3024
|
+
"public abstract partial class BasePage",
|
|
2803
3025
|
"{",
|
|
2804
3026
|
" protected BasePage(IPage page) => Page = page;",
|
|
2805
3027
|
" protected IPage Page { get; }",
|
|
2806
|
-
|
|
3028
|
+
` protected ILocator LocatorByTestId(string testId) => Page.Locator($"[${testIdAttribute}=\\"{testId}\\"]");`,
|
|
2807
3029
|
"",
|
|
2808
3030
|
" // Minimal vue-select helper mirroring the TS BasePage.selectVSelectByTestId behavior.",
|
|
2809
3031
|
" // Note: annotationText is currently a no-op in C# output (we don't render a cursor overlay).",
|
|
@@ -2827,10 +3049,11 @@ function generateAggregatedCSharpFiles(componentHierarchyMap, outDir, options =
|
|
|
2827
3049
|
].join("\n");
|
|
2828
3050
|
const chunks = [header];
|
|
2829
3051
|
for (const [componentName, deps] of entries) {
|
|
3052
|
+
const className = toPascalCaseLocal(componentName);
|
|
2830
3053
|
chunks.push(
|
|
2831
|
-
`public
|
|
3054
|
+
`public partial class ${className} : BasePage
|
|
2832
3055
|
{
|
|
2833
|
-
public ${
|
|
3056
|
+
public ${className}(IPage page) : base(page) { }
|
|
2834
3057
|
`
|
|
2835
3058
|
);
|
|
2836
3059
|
const primaries = Array.from(deps.dataTestIdSet ?? []).map((e) => ({ pom: e.pom, target: e.targetPageObjectModelClass })).filter((x) => !!x.pom).sort((a, b) => a.pom.methodName.localeCompare(b.pom.methodName));
|
|
@@ -2969,7 +3192,7 @@ function generateAggregatedCSharpFiles(componentHierarchyMap, outDir, options =
|
|
|
2969
3192
|
return [{ filePath: outputFile, content: chunks.join("\n") }];
|
|
2970
3193
|
}
|
|
2971
3194
|
function maybeGenerateFixtureRegistry(componentHierarchyMap, options) {
|
|
2972
|
-
const { generateFixtures, pomOutDir
|
|
3195
|
+
const { generateFixtures, pomOutDir } = options;
|
|
2973
3196
|
if (!generateFixtures)
|
|
2974
3197
|
return;
|
|
2975
3198
|
const defaultFixtureOutDirRel = pomOutDir;
|
|
@@ -2977,7 +3200,7 @@ function maybeGenerateFixtureRegistry(componentHierarchyMap, options) {
|
|
|
2977
3200
|
const looksLikeFilePath = fixtureOutRel.endsWith(".ts") || fixtureOutRel.endsWith(".tsx") || fixtureOutRel.endsWith(".mts") || fixtureOutRel.endsWith(".cts");
|
|
2978
3201
|
const fixtureOutDirRel = looksLikeFilePath ? path.dirname(fixtureOutRel) : fixtureOutRel;
|
|
2979
3202
|
const fixtureFileName = looksLikeFilePath ? path.basename(fixtureOutRel) : "fixtures.g.ts";
|
|
2980
|
-
const root = projectRoot ?? process.cwd();
|
|
3203
|
+
const root = options.projectRoot ?? process.cwd();
|
|
2981
3204
|
const fixtureOutDirAbs = path.isAbsolute(fixtureOutDirRel) ? fixtureOutDirRel : path.resolve(root, fixtureOutDirRel);
|
|
2982
3205
|
const pomDirAbs = path.isAbsolute(pomOutDir) ? pomOutDir : path.resolve(root, pomOutDir);
|
|
2983
3206
|
const pomImport = toPosixRelativePath(fixtureOutDirAbs, pomDirAbs);
|
|
@@ -3218,7 +3441,7 @@ async function generateAggregatedFiles(componentHierarchyMap, vueFilesPathMap, b
|
|
|
3218
3441
|
const views = entries.filter(([, d]) => d.isView);
|
|
3219
3442
|
const components = entries.filter(([, d]) => !d.isView);
|
|
3220
3443
|
const makeAggregatedContent = (header2, outputDir, items) => {
|
|
3221
|
-
const imports = [
|
|
3444
|
+
const imports = [];
|
|
3222
3445
|
if (!basePageClassPath) {
|
|
3223
3446
|
throw new Error("basePageClassPath is required for aggregated generation");
|
|
3224
3447
|
}
|
|
@@ -3231,6 +3454,16 @@ async function generateAggregatedFiles(componentHierarchyMap, vueFilesPathMap, b
|
|
|
3231
3454
|
" err?: string;",
|
|
3232
3455
|
"}"
|
|
3233
3456
|
].join("\n");
|
|
3457
|
+
const inlinePlaywrightTypesModule = () => {
|
|
3458
|
+
const typesPath = node_url.fileURLToPath(new URL("./playwright-types.ts", typeof document === "undefined" ? require("url").pathToFileURL(__filename).href : _documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === "SCRIPT" && _documentCurrentScript.src || new URL("index.cjs", document.baseURI).href));
|
|
3459
|
+
let typesSource = "";
|
|
3460
|
+
try {
|
|
3461
|
+
typesSource = fs.readFileSync(typesPath, "utf8");
|
|
3462
|
+
} catch {
|
|
3463
|
+
throw new Error(`Failed to read playwright-types.ts at ${typesPath}`);
|
|
3464
|
+
}
|
|
3465
|
+
return typesSource.trim();
|
|
3466
|
+
};
|
|
3234
3467
|
const inlinePointerModule = () => {
|
|
3235
3468
|
const pointerPath = node_url.fileURLToPath(new URL("./Pointer.ts", typeof document === "undefined" ? require("url").pathToFileURL(__filename).href : _documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === "SCRIPT" && _documentCurrentScript.src || new URL("index.cjs", document.baseURI).href));
|
|
3236
3469
|
let pointerSource = "";
|
|
@@ -3253,6 +3486,10 @@ async function generateAggregatedFiles(componentHierarchyMap, vueFilesPathMap, b
|
|
|
3253
3486
|
/import\s+type\s*\{\s*Locator\s+as\s+PwLocator\s*,\s*Page\s+as\s+PwPage\s*\}\s*from\s*["']@playwright\/test["'];?\s*/,
|
|
3254
3487
|
""
|
|
3255
3488
|
);
|
|
3489
|
+
pointerSource = pointerSource.replace(
|
|
3490
|
+
/import\s+type\s*\{\s*PwLocator\s*,\s*PwPage\s*\}\s*from\s*["']\.\/playwright-types["'];?\s*/,
|
|
3491
|
+
""
|
|
3492
|
+
);
|
|
3256
3493
|
return pointerSource.trim();
|
|
3257
3494
|
};
|
|
3258
3495
|
const inlineBasePageModule = () => {
|
|
@@ -3277,18 +3514,23 @@ async function generateAggregatedFiles(componentHierarchyMap, vueFilesPathMap, b
|
|
|
3277
3514
|
""
|
|
3278
3515
|
);
|
|
3279
3516
|
basePageSource = basePageSource.replace(
|
|
3280
|
-
/import\s*\{\s*
|
|
3517
|
+
/import\s+type\s*\{\s*PwLocator\s*,\s*PwPage\s*\}\s*from\s*["']\.\/playwright-types["'];?\s*/,
|
|
3518
|
+
""
|
|
3519
|
+
);
|
|
3520
|
+
basePageSource = basePageSource.replace(
|
|
3521
|
+
/import\s+(?:type\s*)?\{[\s\S]*?\}\s*from\s*["']\.\/Pointer["'];?\s*/g,
|
|
3281
3522
|
""
|
|
3282
3523
|
);
|
|
3283
3524
|
return basePageSource.trim();
|
|
3284
3525
|
};
|
|
3526
|
+
const playwrightTypesInline = inlinePlaywrightTypesModule();
|
|
3285
3527
|
const pointerInline = inlinePointerModule();
|
|
3286
3528
|
const basePageInline = inlineBasePageModule();
|
|
3287
3529
|
const addCustomPomImports = () => {
|
|
3288
3530
|
const importAliases = {
|
|
3289
3531
|
Toggle: "ToggleWidget",
|
|
3290
3532
|
Checkbox: "CheckboxWidget",
|
|
3291
|
-
...options.customPomImportAliases
|
|
3533
|
+
...options.customPomImportAliases
|
|
3292
3534
|
};
|
|
3293
3535
|
const customDirRelOrAbs = options.customPomDir ?? "tests/playwright/pom/custom";
|
|
3294
3536
|
const customDirAbs = path.isAbsolute(customDirRelOrAbs) ? customDirRelOrAbs : path.resolve(projectRoot, customDirRelOrAbs);
|
|
@@ -3471,6 +3713,8 @@ async function generateAggregatedFiles(componentHierarchyMap, vueFilesPathMap, b
|
|
|
3471
3713
|
header2,
|
|
3472
3714
|
...imports,
|
|
3473
3715
|
"",
|
|
3716
|
+
playwrightTypesInline,
|
|
3717
|
+
"",
|
|
3474
3718
|
pointerInline,
|
|
3475
3719
|
"",
|
|
3476
3720
|
basePageInline,
|
|
@@ -3482,7 +3726,8 @@ async function generateAggregatedFiles(componentHierarchyMap, vueFilesPathMap, b
|
|
|
3482
3726
|
};
|
|
3483
3727
|
const base = ensureDir(outDir);
|
|
3484
3728
|
const outputFile = path.join(base, "page-object-models.g.ts");
|
|
3485
|
-
const header =
|
|
3729
|
+
const header = `/// <reference lib="es2015" />
|
|
3730
|
+
${eslintSuppressionHeader}/**
|
|
3486
3731
|
* Aggregated generated POMs
|
|
3487
3732
|
${AUTO_GENERATED_COMMENT}`;
|
|
3488
3733
|
const content = makeAggregatedContent(header, path.dirname(outputFile), [...views, ...components]);
|
|
@@ -3629,6 +3874,7 @@ function createBuildProcessorPlugin(options) {
|
|
|
3629
3874
|
normalizedBasePagePath,
|
|
3630
3875
|
outDir,
|
|
3631
3876
|
emitLanguages,
|
|
3877
|
+
csharp,
|
|
3632
3878
|
generateFixtures,
|
|
3633
3879
|
customPomAttachments,
|
|
3634
3880
|
projectRootRef,
|
|
@@ -3637,6 +3883,7 @@ function createBuildProcessorPlugin(options) {
|
|
|
3637
3883
|
testIdAttribute,
|
|
3638
3884
|
routerAwarePoms,
|
|
3639
3885
|
resolvedRouterEntry,
|
|
3886
|
+
routerType,
|
|
3640
3887
|
loggerRef
|
|
3641
3888
|
} = options;
|
|
3642
3889
|
let lastGeneratedEntryCount = 0;
|
|
@@ -3651,9 +3898,15 @@ function createBuildProcessorPlugin(options) {
|
|
|
3651
3898
|
setResolveToComponentNameFn(() => null);
|
|
3652
3899
|
return;
|
|
3653
3900
|
}
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3901
|
+
let result;
|
|
3902
|
+
if (routerType === "nuxt") {
|
|
3903
|
+
result = await introspectNuxtPages(projectRootRef.current);
|
|
3904
|
+
} else {
|
|
3905
|
+
if (!resolvedRouterEntry)
|
|
3906
|
+
throw new Error("[vue-pom-generator] router.entry is required when router introspection is enabled.");
|
|
3907
|
+
result = await parseRouterFileFromCwd(resolvedRouterEntry);
|
|
3908
|
+
}
|
|
3909
|
+
const { routeNameMap, routePathMap } = result;
|
|
3657
3910
|
setRouteNameToComponentNameMap(routeNameMap);
|
|
3658
3911
|
setResolveToComponentNameFn((to) => {
|
|
3659
3912
|
if (typeof to === "string") {
|
|
@@ -3690,6 +3943,7 @@ function createBuildProcessorPlugin(options) {
|
|
|
3690
3943
|
generateFiles(componentHierarchyMap, vueFilesPathMap, normalizedBasePagePath, {
|
|
3691
3944
|
outDir,
|
|
3692
3945
|
emitLanguages,
|
|
3946
|
+
csharp,
|
|
3693
3947
|
generateFixtures,
|
|
3694
3948
|
customPomAttachments,
|
|
3695
3949
|
projectRoot: projectRootRef.current,
|
|
@@ -3697,7 +3951,8 @@ function createBuildProcessorPlugin(options) {
|
|
|
3697
3951
|
customPomImportAliases,
|
|
3698
3952
|
testIdAttribute,
|
|
3699
3953
|
vueRouterFluentChaining: routerAwarePoms,
|
|
3700
|
-
routerEntry: resolvedRouterEntry
|
|
3954
|
+
routerEntry: resolvedRouterEntry,
|
|
3955
|
+
routerType
|
|
3701
3956
|
});
|
|
3702
3957
|
lastGeneratedEntryCount = entryCount;
|
|
3703
3958
|
loggerRef.current.info(`generated POMs (${entryCount} entries)`);
|
|
@@ -3826,7 +4081,7 @@ function tryExtractStableHintFromConditionalExpressionSource(source) {
|
|
|
3826
4081
|
return null;
|
|
3827
4082
|
}
|
|
3828
4083
|
}
|
|
3829
|
-
function tryInferNativeWrapperRoleFromSfc(tag) {
|
|
4084
|
+
function tryInferNativeWrapperRoleFromSfc(tag, vueFilesPathMap) {
|
|
3830
4085
|
const first = tag.charCodeAt(0);
|
|
3831
4086
|
const isUpper = first >= 65 && first <= 90;
|
|
3832
4087
|
if (!isUpper)
|
|
@@ -3835,8 +4090,14 @@ function tryInferNativeWrapperRoleFromSfc(tag) {
|
|
|
3835
4090
|
if (cached)
|
|
3836
4091
|
return cached;
|
|
3837
4092
|
const candidates = [
|
|
3838
|
-
path.resolve(process.cwd(), "src/components", `${tag}.vue`)
|
|
4093
|
+
path.resolve(process.cwd(), "src/components", `${tag}.vue`),
|
|
4094
|
+
path.resolve(process.cwd(), "components", `${tag}.vue`),
|
|
4095
|
+
path.resolve(process.cwd(), "app/components", `${tag}.vue`)
|
|
3839
4096
|
];
|
|
4097
|
+
const registeredPath = vueFilesPathMap?.get(tag);
|
|
4098
|
+
if (registeredPath) {
|
|
4099
|
+
candidates.unshift(path.resolve(process.cwd(), registeredPath));
|
|
4100
|
+
}
|
|
3840
4101
|
const filePath = candidates.find((p) => fs.existsSync(p));
|
|
3841
4102
|
if (!filePath) {
|
|
3842
4103
|
inferredNativeWrapperConfigByTag.set(tag, { role: "" });
|
|
@@ -3877,14 +4138,22 @@ function tryInferNativeWrapperRoleFromSfc(tag) {
|
|
|
3877
4138
|
inferredNativeWrapperConfigByTag.set(tag, { role: "" });
|
|
3878
4139
|
return null;
|
|
3879
4140
|
}
|
|
3880
|
-
if (rootTag === "input" || rootTag === "textarea") {
|
|
4141
|
+
if (rootTag === "input" || rootTag === "textarea" || rootTag === "uinput" || rootTag === "utextarea") {
|
|
3881
4142
|
inferredNativeWrapperConfigByTag.set(tag, { role: "input" });
|
|
3882
4143
|
return { role: "input" };
|
|
3883
4144
|
}
|
|
3884
|
-
if (rootTag === "select") {
|
|
4145
|
+
if (rootTag === "select" || rootTag === "uselect") {
|
|
3885
4146
|
inferredNativeWrapperConfigByTag.set(tag, { role: "select" });
|
|
3886
4147
|
return { role: "select" };
|
|
3887
4148
|
}
|
|
4149
|
+
if (rootTag === "vselect") {
|
|
4150
|
+
inferredNativeWrapperConfigByTag.set(tag, { role: "vselect" });
|
|
4151
|
+
return { role: "vselect" };
|
|
4152
|
+
}
|
|
4153
|
+
if (rootTag === "button" || rootTag === "ubutton") {
|
|
4154
|
+
inferredNativeWrapperConfigByTag.set(tag, { role: "button" });
|
|
4155
|
+
return { role: "button" };
|
|
4156
|
+
}
|
|
3888
4157
|
inferredNativeWrapperConfigByTag.set(tag, { role: "" });
|
|
3889
4158
|
return null;
|
|
3890
4159
|
}
|
|
@@ -4059,6 +4328,7 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
|
|
|
4059
4328
|
const testIdAttribute = (options.testIdAttribute || "data-testid").trim() || "data-testid";
|
|
4060
4329
|
const nameCollisionBehavior = options.nameCollisionBehavior ?? "suffix";
|
|
4061
4330
|
const warn = options.warn;
|
|
4331
|
+
const vueFilesPathMap = options.vueFilesPathMap;
|
|
4062
4332
|
const safeRealpath = (p) => {
|
|
4063
4333
|
try {
|
|
4064
4334
|
return fs.existsSync(p) ? fs.realpathSync(p) : p;
|
|
@@ -4110,18 +4380,14 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
|
|
|
4110
4380
|
const parentElement = !parentIsRoot && context?.parent?.type === compilerCore.NodeTypes.ELEMENT ? context.parent : null;
|
|
4111
4381
|
hierarchyMap.set(element, parentElement);
|
|
4112
4382
|
const normalizeFilePath = (filePath) => path.normalize(safeRealpath(path.resolve(filePath)));
|
|
4113
|
-
const getParentComponentName = () => {
|
|
4114
|
-
const normalizedFilePath2 = normalizeFilePath(context.filename);
|
|
4115
|
-
return path.basename(normalizedFilePath2, ".vue");
|
|
4116
|
-
};
|
|
4117
|
-
const parentComponentName = getParentComponentName();
|
|
4118
4383
|
const normalizedFilePath = normalizeFilePath(context.filename);
|
|
4119
|
-
const
|
|
4120
|
-
const
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4384
|
+
const parentComponentName = componentName;
|
|
4385
|
+
const dependencies = (() => {
|
|
4386
|
+
let deps = componentHierarchyMap.get(componentName);
|
|
4387
|
+
if (!deps) {
|
|
4388
|
+
const relToViewsDir = path.relative(normalizedViewsDirAbs, normalizedFilePath);
|
|
4389
|
+
const isView = !relToViewsDir.startsWith("..") && !path.isAbsolute(relToViewsDir);
|
|
4390
|
+
deps = {
|
|
4125
4391
|
filePath: context.filename,
|
|
4126
4392
|
childrenComponentSet: /* @__PURE__ */ new Set(),
|
|
4127
4393
|
usedComponentSet: /* @__PURE__ */ new Set(),
|
|
@@ -4129,11 +4395,10 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
|
|
|
4129
4395
|
isView,
|
|
4130
4396
|
methodsContent: ""
|
|
4131
4397
|
};
|
|
4132
|
-
componentHierarchyMap.set(
|
|
4398
|
+
componentHierarchyMap.set(componentName, deps);
|
|
4133
4399
|
}
|
|
4134
|
-
return
|
|
4135
|
-
};
|
|
4136
|
-
const dependencies = ensureDependencies(parentComponentName);
|
|
4400
|
+
return deps;
|
|
4401
|
+
})();
|
|
4137
4402
|
const isComponentLikeTag = (tag) => {
|
|
4138
4403
|
if (!tag) return false;
|
|
4139
4404
|
const first = tag.charCodeAt(0);
|
|
@@ -4144,13 +4409,24 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
|
|
|
4144
4409
|
dependencies.usedComponentSet.add(element.tag);
|
|
4145
4410
|
}
|
|
4146
4411
|
if (!nativeWrappers[element.tag]) {
|
|
4147
|
-
const inferred = tryInferNativeWrapperRoleFromSfc(element.tag);
|
|
4412
|
+
const inferred = tryInferNativeWrapperRoleFromSfc(element.tag, vueFilesPathMap);
|
|
4148
4413
|
if (inferred?.role) {
|
|
4149
4414
|
nativeWrappers[element.tag] = { role: inferred.role };
|
|
4415
|
+
} else if (element.tag.endsWith("Button") || element.tag === "AylaButton") {
|
|
4416
|
+
nativeWrappers[element.tag] = { role: "button" };
|
|
4417
|
+
} else if (element.tag === "DxDataGrid") {
|
|
4418
|
+
nativeWrappers[element.tag] = { role: "grid" };
|
|
4150
4419
|
}
|
|
4151
4420
|
}
|
|
4152
|
-
const getBestAvailableKeyValue = () =>
|
|
4153
|
-
|
|
4421
|
+
const getBestAvailableKeyValue = () => {
|
|
4422
|
+
const vForKey = getKeyDirectiveValue(element, context) || getSelfClosingForDirectiveKeyAttrValue(element) || getContainedInVForDirectiveKeyValue(context, element, hierarchyMap);
|
|
4423
|
+
if (vForKey) return vForKey;
|
|
4424
|
+
return getContainedInSlotDataKeyValue(element, hierarchyMap);
|
|
4425
|
+
};
|
|
4426
|
+
const bestKeyInferred = getBestAvailableKeyValue();
|
|
4427
|
+
const isSlotKey = bestKeyInferred && !bestKeyInferred.startsWith("${");
|
|
4428
|
+
const bestKeyPlaceholder = isSlotKey ? `\${${bestKeyInferred}}` : bestKeyInferred;
|
|
4429
|
+
const bestKeyVariable = isSlotKey ? bestKeyInferred : null;
|
|
4154
4430
|
const keyValuesOverride = tryGetContainedInStaticVForSourceLiteralValues(context);
|
|
4155
4431
|
const parentKey = !parentIsRoot && context?.parent ? context.parent : null;
|
|
4156
4432
|
const conditional = getConditionalDirectiveInfo(element);
|
|
@@ -4235,6 +4511,7 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
|
|
|
4235
4511
|
nativeRole,
|
|
4236
4512
|
preferredGeneratedValue: args.preferredGeneratedValue,
|
|
4237
4513
|
bestKeyPlaceholder,
|
|
4514
|
+
bestKeyVariable,
|
|
4238
4515
|
keyValuesOverride,
|
|
4239
4516
|
entryOverrides: args.entryOverrides,
|
|
4240
4517
|
semanticNameHint: args.semanticNameHint,
|
|
@@ -4263,16 +4540,18 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
|
|
|
4263
4540
|
});
|
|
4264
4541
|
return;
|
|
4265
4542
|
}
|
|
4543
|
+
const innerText = getInnerText(element) || null;
|
|
4266
4544
|
const toDirective = nodeHasToDirective(element);
|
|
4267
4545
|
if (toDirective) {
|
|
4268
4546
|
const dataTestId = generateToDirectiveDataTestId(componentName, element, toDirective, context, hierarchyMap, nativeWrappers);
|
|
4269
4547
|
const target = tryResolveToDirectiveTargetComponentName(toDirective);
|
|
4270
4548
|
const routeNameHint = toDirectiveObjectFieldNameValue(toDirective);
|
|
4271
|
-
const
|
|
4549
|
+
const existing = tryGetExistingElementDataTestId(element, testIdAttribute);
|
|
4550
|
+
const semanticNameHint2 = routeNameHint || target || void 0;
|
|
4551
|
+
const alternates = (target ? [] : [innerText, existing?.value, conditionalHint]).filter(Boolean);
|
|
4272
4552
|
const rawTo = (toDirective.exp?.loc?.source ?? "").trim();
|
|
4273
4553
|
const pomMergeKey = routeNameHint ? `to:name:${routeNameHint}` : rawTo ? `to:expr:${rawTo}` : void 0;
|
|
4274
|
-
const
|
|
4275
|
-
const preferredGeneratedValue = dataTestId ?? (existing ? staticAttributeValue(existing.value) : null);
|
|
4554
|
+
const preferredGeneratedValue = dataTestId ?? (existing ? existing.isDynamic ? templateAttributeValue(existing.template) : staticAttributeValue(existing.value) : null);
|
|
4276
4555
|
if (!preferredGeneratedValue) {
|
|
4277
4556
|
return;
|
|
4278
4557
|
}
|
|
@@ -4280,6 +4559,7 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
|
|
|
4280
4559
|
preferredGeneratedValue,
|
|
4281
4560
|
entryOverrides: target ? { targetPageObjectModelClass: target } : {},
|
|
4282
4561
|
semanticNameHint: semanticNameHint2,
|
|
4562
|
+
semanticNameHintAlternates: alternates,
|
|
4283
4563
|
pomMergeKey
|
|
4284
4564
|
});
|
|
4285
4565
|
return;
|
|
@@ -4294,7 +4574,6 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
|
|
|
4294
4574
|
});
|
|
4295
4575
|
return;
|
|
4296
4576
|
}
|
|
4297
|
-
const innerText = getInnerText(element) || null;
|
|
4298
4577
|
const clickDirective = tryGetClickDirective(element);
|
|
4299
4578
|
if (clickDirective) {
|
|
4300
4579
|
const clickSuffix = getComposedClickHandlerContent(element, context, innerText, clickDirective, {
|
|
@@ -4302,7 +4581,7 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
|
|
|
4302
4581
|
});
|
|
4303
4582
|
const clickHint = trimLeadingSeparators(clickSuffix) || void 0;
|
|
4304
4583
|
const idOrName = getIdOrName(element) || void 0;
|
|
4305
|
-
const semanticNameHint2 = clickHint || idOrName || conditionalHint || void 0;
|
|
4584
|
+
const semanticNameHint2 = clickHint || idOrName || innerText || conditionalHint || void 0;
|
|
4306
4585
|
const pomMergeKey = clickHint ? `click:hint:${clickHint}` : void 0;
|
|
4307
4586
|
const testId = getClickDataTestId(clickSuffix);
|
|
4308
4587
|
applyResolvedDataTestIdForElement({
|
|
@@ -4316,15 +4595,16 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
|
|
|
4316
4595
|
return;
|
|
4317
4596
|
}
|
|
4318
4597
|
const existingElementDataTestId = tryGetExistingElementDataTestId(element, testIdAttribute);
|
|
4319
|
-
if (existingElementDataTestId
|
|
4598
|
+
if (existingElementDataTestId) {
|
|
4320
4599
|
const inferredRole = getNativeRoleFromTagSuffix().toLowerCase();
|
|
4321
|
-
const isRecognizedInteractiveRole = inferredRole === "button" || inferredRole === "input" || inferredRole === "select" || inferredRole === "vselect" || inferredRole === "checkbox" || inferredRole === "toggle" || inferredRole === "radio";
|
|
4600
|
+
const isRecognizedInteractiveRole = inferredRole === "button" || inferredRole === "input" || inferredRole === "select" || inferredRole === "vselect" || inferredRole === "checkbox" || inferredRole === "toggle" || inferredRole === "radio" || inferredRole === "grid" || isComponentLikeTag(element.tag);
|
|
4322
4601
|
if (!isRecognizedInteractiveRole) {
|
|
4323
4602
|
return;
|
|
4324
4603
|
}
|
|
4325
|
-
const identifierHint = getIdOrName(element) || conditionalHint || void 0;
|
|
4604
|
+
const identifierHint = getIdOrName(element) || nodeHandlerAttributeValue(element) || innerText || existingElementDataTestId.value || conditionalHint || void 0;
|
|
4605
|
+
const preferredGeneratedValue = existingElementDataTestId.isDynamic ? templateAttributeValue(existingElementDataTestId.template) : staticAttributeValue(existingElementDataTestId.value);
|
|
4326
4606
|
applyResolvedDataTestIdForElement({
|
|
4327
|
-
preferredGeneratedValue
|
|
4607
|
+
preferredGeneratedValue,
|
|
4328
4608
|
semanticNameHint: identifierHint
|
|
4329
4609
|
});
|
|
4330
4610
|
return;
|
|
@@ -4352,11 +4632,13 @@ function createDevProcessorPlugin(options) {
|
|
|
4352
4632
|
nativeWrappers,
|
|
4353
4633
|
excludedComponents,
|
|
4354
4634
|
viewsDir,
|
|
4635
|
+
scanDirs,
|
|
4355
4636
|
projectRootRef,
|
|
4356
4637
|
normalizedBasePagePath,
|
|
4357
4638
|
basePageClassPath,
|
|
4358
4639
|
outDir,
|
|
4359
4640
|
emitLanguages,
|
|
4641
|
+
csharp,
|
|
4360
4642
|
generateFixtures,
|
|
4361
4643
|
customPomAttachments,
|
|
4362
4644
|
customPomDir,
|
|
@@ -4364,6 +4646,7 @@ function createDevProcessorPlugin(options) {
|
|
|
4364
4646
|
testIdAttribute,
|
|
4365
4647
|
routerAwarePoms,
|
|
4366
4648
|
resolvedRouterEntry,
|
|
4649
|
+
routerType,
|
|
4367
4650
|
loggerRef
|
|
4368
4651
|
} = options;
|
|
4369
4652
|
let scheduleVueFileRegen = null;
|
|
@@ -4377,7 +4660,11 @@ function createDevProcessorPlugin(options) {
|
|
|
4377
4660
|
return;
|
|
4378
4661
|
if (!ctx.file.endsWith(".vue"))
|
|
4379
4662
|
return;
|
|
4380
|
-
|
|
4663
|
+
const isContainedInScanDirs = scanDirs.some((dir) => {
|
|
4664
|
+
const absDir = path.resolve(projectRootRef.current, dir);
|
|
4665
|
+
return ctx.file.startsWith(absDir + path.sep);
|
|
4666
|
+
});
|
|
4667
|
+
if (!isContainedInScanDirs)
|
|
4381
4668
|
return;
|
|
4382
4669
|
scheduleVueFileRegen(ctx.file, "hmr");
|
|
4383
4670
|
},
|
|
@@ -4388,9 +4675,15 @@ function createDevProcessorPlugin(options) {
|
|
|
4388
4675
|
setResolveToComponentNameFn(() => null);
|
|
4389
4676
|
return;
|
|
4390
4677
|
}
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4678
|
+
let result;
|
|
4679
|
+
if (routerType === "nuxt") {
|
|
4680
|
+
result = await introspectNuxtPages(projectRootRef.current);
|
|
4681
|
+
} else {
|
|
4682
|
+
if (!resolvedRouterEntry)
|
|
4683
|
+
throw new Error("[vue-pom-generator] router.entry is required when router introspection is enabled.");
|
|
4684
|
+
result = await parseRouterFileFromCwd(resolvedRouterEntry);
|
|
4685
|
+
}
|
|
4686
|
+
const { routeNameMap, routePathMap } = result;
|
|
4394
4687
|
setRouteNameToComponentNameMap(routeNameMap);
|
|
4395
4688
|
setResolveToComponentNameFn((to) => {
|
|
4396
4689
|
if (typeof to === "string") {
|
|
@@ -4491,29 +4784,34 @@ function createDevProcessorPlugin(options) {
|
|
|
4491
4784
|
return { componentName, ms: node_perf_hooks.performance.now() - started, compiled: true };
|
|
4492
4785
|
};
|
|
4493
4786
|
const fullRebuildSnapshotFromFilesystem = () => {
|
|
4494
|
-
const srcDir = path.resolve(projectRootRef.current, "src");
|
|
4495
|
-
if (!fs.existsSync(srcDir))
|
|
4496
|
-
return;
|
|
4497
4787
|
const t0 = node_perf_hooks.performance.now();
|
|
4498
4788
|
snapshotHierarchy.clear();
|
|
4499
4789
|
snapshotVuePathMap.clear();
|
|
4500
4790
|
filePathToComponentName.clear();
|
|
4501
|
-
|
|
4502
|
-
logInfo(`initial scan: found ${vueFiles.length} .vue files under src/`);
|
|
4791
|
+
let totalVueFiles = 0;
|
|
4503
4792
|
let compiledCount = 0;
|
|
4504
|
-
for (const
|
|
4505
|
-
const
|
|
4506
|
-
if (
|
|
4507
|
-
|
|
4793
|
+
for (const dir of scanDirs) {
|
|
4794
|
+
const absDir = path.resolve(projectRootRef.current, dir);
|
|
4795
|
+
if (!fs.existsSync(absDir))
|
|
4796
|
+
continue;
|
|
4797
|
+
const vueFiles = walkFilesRecursive(absDir);
|
|
4798
|
+
totalVueFiles += vueFiles.length;
|
|
4799
|
+
for (const file of vueFiles) {
|
|
4800
|
+
const res = compileVueFileIntoSnapshot(file);
|
|
4801
|
+
if (res.compiled)
|
|
4802
|
+
compiledCount++;
|
|
4803
|
+
}
|
|
4508
4804
|
}
|
|
4509
4805
|
const t1 = node_perf_hooks.performance.now();
|
|
4510
|
-
logInfo(`initial
|
|
4806
|
+
logInfo(`initial scan: found ${totalVueFiles} .vue files in ${scanDirs.join(", ")}`);
|
|
4807
|
+
logInfo(`initial compile: ${compiledCount}/${totalVueFiles} files in ${formatMs(t1 - t0)} (components=${snapshotHierarchy.size})`);
|
|
4511
4808
|
};
|
|
4512
4809
|
const generateAggregatedFromSnapshot = (reason) => {
|
|
4513
4810
|
const t0 = node_perf_hooks.performance.now();
|
|
4514
4811
|
generateFiles(snapshotHierarchy, snapshotVuePathMap, normalizedBasePagePath, {
|
|
4515
4812
|
outDir,
|
|
4516
4813
|
emitLanguages,
|
|
4814
|
+
csharp,
|
|
4517
4815
|
generateFixtures,
|
|
4518
4816
|
customPomAttachments,
|
|
4519
4817
|
projectRoot: projectRootRef.current,
|
|
@@ -4521,7 +4819,8 @@ function createDevProcessorPlugin(options) {
|
|
|
4521
4819
|
customPomImportAliases,
|
|
4522
4820
|
testIdAttribute,
|
|
4523
4821
|
vueRouterFluentChaining: routerAwarePoms,
|
|
4524
|
-
routerEntry: resolvedRouterEntry
|
|
4822
|
+
routerEntry: resolvedRouterEntry,
|
|
4823
|
+
routerType
|
|
4525
4824
|
});
|
|
4526
4825
|
const t1 = node_perf_hooks.performance.now();
|
|
4527
4826
|
logInfo(`generate(${reason}): components=${snapshotHierarchy.size} in ${formatMs(t1 - t0)}`);
|
|
@@ -4534,9 +4833,9 @@ function createDevProcessorPlugin(options) {
|
|
|
4534
4833
|
const t1 = node_perf_hooks.performance.now();
|
|
4535
4834
|
logInfo(`startup total: ${formatMs(t1 - t0)}`);
|
|
4536
4835
|
})();
|
|
4537
|
-
const
|
|
4836
|
+
const watchedVueGlobs = scanDirs.map((dir) => path.resolve(projectRootRef.current, dir, "**", "*.vue"));
|
|
4538
4837
|
const watchedPluginGlob = path.resolve(projectRootRef.current, "vite-plugins", "vue-pom-generator", "**", "*.ts");
|
|
4539
|
-
server.watcher.add([
|
|
4838
|
+
server.watcher.add([...watchedVueGlobs, watchedPluginGlob, basePageClassPath]);
|
|
4540
4839
|
let timer = null;
|
|
4541
4840
|
let maxWaitTimer = null;
|
|
4542
4841
|
const pendingChangedVueFiles = /* @__PURE__ */ new Set();
|
|
@@ -4635,7 +4934,13 @@ function createDevProcessorPlugin(options) {
|
|
|
4635
4934
|
server.watcher.on("add", (p) => {
|
|
4636
4935
|
if (typeof p !== "string")
|
|
4637
4936
|
return;
|
|
4638
|
-
if (!p.endsWith(".vue")
|
|
4937
|
+
if (!p.endsWith(".vue"))
|
|
4938
|
+
return;
|
|
4939
|
+
const isContainedInScanDirs = scanDirs.some((dir) => {
|
|
4940
|
+
const absDir = path.resolve(projectRootRef.current, dir);
|
|
4941
|
+
return p.startsWith(absDir + path.sep);
|
|
4942
|
+
});
|
|
4943
|
+
if (!isContainedInScanDirs)
|
|
4639
4944
|
return;
|
|
4640
4945
|
void (async () => {
|
|
4641
4946
|
await initialBuildPromise;
|
|
@@ -4646,7 +4951,13 @@ function createDevProcessorPlugin(options) {
|
|
|
4646
4951
|
server.watcher.on("unlink", (p) => {
|
|
4647
4952
|
if (typeof p !== "string")
|
|
4648
4953
|
return;
|
|
4649
|
-
if (!p.endsWith(".vue")
|
|
4954
|
+
if (!p.endsWith(".vue"))
|
|
4955
|
+
return;
|
|
4956
|
+
const isContainedInScanDirs = scanDirs.some((dir) => {
|
|
4957
|
+
const absDir = path.resolve(projectRootRef.current, dir);
|
|
4958
|
+
return p.startsWith(absDir + path.sep);
|
|
4959
|
+
});
|
|
4960
|
+
if (!isContainedInScanDirs)
|
|
4650
4961
|
return;
|
|
4651
4962
|
void (async () => {
|
|
4652
4963
|
await initialBuildPromise;
|
|
@@ -4687,10 +4998,13 @@ function createSupportPlugins(options) {
|
|
|
4687
4998
|
nativeWrappers,
|
|
4688
4999
|
excludedComponents,
|
|
4689
5000
|
viewsDir,
|
|
5001
|
+
scanDirs,
|
|
4690
5002
|
outDir,
|
|
4691
5003
|
emitLanguages,
|
|
5004
|
+
csharp,
|
|
4692
5005
|
routerAwarePoms,
|
|
4693
5006
|
routerEntry,
|
|
5007
|
+
routerType,
|
|
4694
5008
|
generateFixtures,
|
|
4695
5009
|
customPomAttachments,
|
|
4696
5010
|
projectRootRef,
|
|
@@ -4703,6 +5017,8 @@ function createSupportPlugins(options) {
|
|
|
4703
5017
|
const resolveRouterEntry2 = () => {
|
|
4704
5018
|
if (!routerAwarePoms)
|
|
4705
5019
|
return void 0;
|
|
5020
|
+
if (routerType === "nuxt")
|
|
5021
|
+
return void 0;
|
|
4706
5022
|
if (!routerEntry)
|
|
4707
5023
|
throw new Error("[vue-pom-generator] router.entry is required when router introspection is enabled.");
|
|
4708
5024
|
return path.isAbsolute(routerEntry) ? routerEntry : path.resolve(projectRootRef.current, routerEntry);
|
|
@@ -4724,6 +5040,7 @@ function createSupportPlugins(options) {
|
|
|
4724
5040
|
normalizedBasePagePath,
|
|
4725
5041
|
outDir,
|
|
4726
5042
|
emitLanguages,
|
|
5043
|
+
csharp,
|
|
4727
5044
|
generateFixtures,
|
|
4728
5045
|
customPomAttachments,
|
|
4729
5046
|
projectRootRef,
|
|
@@ -4731,6 +5048,7 @@ function createSupportPlugins(options) {
|
|
|
4731
5048
|
customPomImportAliases,
|
|
4732
5049
|
testIdAttribute,
|
|
4733
5050
|
routerAwarePoms,
|
|
5051
|
+
routerType,
|
|
4734
5052
|
resolvedRouterEntry,
|
|
4735
5053
|
loggerRef
|
|
4736
5054
|
});
|
|
@@ -4738,6 +5056,7 @@ function createSupportPlugins(options) {
|
|
|
4738
5056
|
nativeWrappers,
|
|
4739
5057
|
excludedComponents,
|
|
4740
5058
|
viewsDir,
|
|
5059
|
+
scanDirs,
|
|
4741
5060
|
projectRootRef,
|
|
4742
5061
|
normalizedBasePagePath,
|
|
4743
5062
|
basePageClassPath,
|
|
@@ -4749,6 +5068,7 @@ function createSupportPlugins(options) {
|
|
|
4749
5068
|
customPomImportAliases,
|
|
4750
5069
|
testIdAttribute,
|
|
4751
5070
|
routerAwarePoms,
|
|
5071
|
+
routerType,
|
|
4752
5072
|
resolvedRouterEntry,
|
|
4753
5073
|
loggerRef
|
|
4754
5074
|
});
|
|
@@ -4850,7 +5170,7 @@ function tryCreateElementMetadata(args) {
|
|
|
4850
5170
|
};
|
|
4851
5171
|
return metadata;
|
|
4852
5172
|
}
|
|
4853
|
-
function
|
|
5173
|
+
function extractMetadataAfterTransform(ast, componentName, elementMetadata, semanticNameMap, testIdAttribute) {
|
|
4854
5174
|
const componentMetadata = /* @__PURE__ */ new Map();
|
|
4855
5175
|
function traverseNode(node) {
|
|
4856
5176
|
if (node.type === compilerCore.NodeTypes.ELEMENT) {
|
|
@@ -4889,27 +5209,8 @@ function extractMetadataFromAST(ast, componentName, metadataMap, semanticNameMap
|
|
|
4889
5209
|
}
|
|
4890
5210
|
traverseNode(ast);
|
|
4891
5211
|
if (componentMetadata.size > 0) {
|
|
4892
|
-
|
|
4893
|
-
}
|
|
4894
|
-
}
|
|
4895
|
-
function compileWithMetadataExtractionManual(source, options, componentName, metadataMap, semanticNameMap, testIdAttribute = "data-testid") {
|
|
4896
|
-
const normalizedTestIdAttribute = (testIdAttribute ?? "data-testid").trim() || "data-testid";
|
|
4897
|
-
const result = compilerDom.compile(
|
|
4898
|
-
source,
|
|
4899
|
-
shared.extend({}, options, {
|
|
4900
|
-
nodeTransforms: [
|
|
4901
|
-
...options.nodeTransforms || [],
|
|
4902
|
-
(node, _context) => {
|
|
4903
|
-
if (node.type !== compilerCore.NodeTypes.ROOT)
|
|
4904
|
-
return;
|
|
4905
|
-
return () => {
|
|
4906
|
-
extractMetadataFromAST(node, componentName, metadataMap, semanticNameMap, normalizedTestIdAttribute);
|
|
4907
|
-
};
|
|
4908
|
-
}
|
|
4909
|
-
]
|
|
4910
|
-
})
|
|
4911
|
-
);
|
|
4912
|
-
return result;
|
|
5212
|
+
elementMetadata.set(componentName, componentMetadata);
|
|
5213
|
+
}
|
|
4913
5214
|
}
|
|
4914
5215
|
function createVuePluginWithTestIds(options) {
|
|
4915
5216
|
const {
|
|
@@ -4924,27 +5225,91 @@ function createVuePluginWithTestIds(options) {
|
|
|
4924
5225
|
excludedComponents,
|
|
4925
5226
|
getViewsDirAbs,
|
|
4926
5227
|
testIdAttribute,
|
|
4927
|
-
loggerRef
|
|
5228
|
+
loggerRef,
|
|
5229
|
+
scanDirs = ["src"],
|
|
5230
|
+
getProjectRoot
|
|
4928
5231
|
} = options;
|
|
5232
|
+
const getComponentNameFromPath = (filename) => {
|
|
5233
|
+
const cleanPath = filename.includes("?") ? filename.substring(0, filename.indexOf("?")) : filename;
|
|
5234
|
+
const projectRoot = getProjectRoot();
|
|
5235
|
+
const absFilename = path.isAbsolute(cleanPath) ? cleanPath : path.resolve(projectRoot, cleanPath);
|
|
5236
|
+
const viewsDirAbs = getViewsDirAbs();
|
|
5237
|
+
const roots = [viewsDirAbs, ...scanDirs.map((d) => path.resolve(projectRoot, d))];
|
|
5238
|
+
for (const dir of scanDirs) {
|
|
5239
|
+
const absDir = path.resolve(projectRoot, dir);
|
|
5240
|
+
try {
|
|
5241
|
+
const pagesDir = path.join(absDir, "pages");
|
|
5242
|
+
if (fs.existsSync(pagesDir)) {
|
|
5243
|
+
roots.push(pagesDir);
|
|
5244
|
+
}
|
|
5245
|
+
const componentsDir = path.join(absDir, "components");
|
|
5246
|
+
if (fs.existsSync(componentsDir)) {
|
|
5247
|
+
roots.push(componentsDir);
|
|
5248
|
+
}
|
|
5249
|
+
} catch {
|
|
5250
|
+
}
|
|
5251
|
+
}
|
|
5252
|
+
const potentialRoots = Array.from(new Set(roots.map((r) => path.normalize(r)))).sort((a, b) => b.length - a.length);
|
|
5253
|
+
let componentName = "";
|
|
5254
|
+
for (const root of potentialRoots) {
|
|
5255
|
+
if (absFilename.startsWith(root + path.sep) || absFilename === root) {
|
|
5256
|
+
const rel = path.relative(root, absFilename);
|
|
5257
|
+
const parsed = path.parse(rel);
|
|
5258
|
+
const segments = path.join(parsed.dir, parsed.name);
|
|
5259
|
+
componentName = toPascalCase(segments);
|
|
5260
|
+
break;
|
|
5261
|
+
}
|
|
5262
|
+
}
|
|
5263
|
+
if (!componentName) {
|
|
5264
|
+
const parsed = path.parse(absFilename);
|
|
5265
|
+
componentName = toPascalCase(parsed.name);
|
|
5266
|
+
}
|
|
5267
|
+
return componentName;
|
|
5268
|
+
};
|
|
5269
|
+
const isFileInScope = (filename) => {
|
|
5270
|
+
if (!filename)
|
|
5271
|
+
return false;
|
|
5272
|
+
const cleanPath = filename.includes("?") ? filename.substring(0, filename.indexOf("?")) : filename;
|
|
5273
|
+
const projectRoot = getProjectRoot();
|
|
5274
|
+
const absFilename = path.isAbsolute(cleanPath) ? cleanPath : path.resolve(projectRoot, cleanPath);
|
|
5275
|
+
if (absFilename.includes(`${path.sep}node_modules${path.sep}`) || absFilename.includes("/node_modules/"))
|
|
5276
|
+
return false;
|
|
5277
|
+
const viewsDirAbs = getViewsDirAbs();
|
|
5278
|
+
if (absFilename.startsWith(viewsDirAbs + path.sep) || absFilename === viewsDirAbs)
|
|
5279
|
+
return true;
|
|
5280
|
+
const rootsToTry = [projectRoot, process.cwd()];
|
|
5281
|
+
const matched = scanDirs.some((dir) => {
|
|
5282
|
+
return rootsToTry.some((root) => {
|
|
5283
|
+
const absDir = path.resolve(root, dir);
|
|
5284
|
+
if (absFilename.startsWith(absDir + path.sep) || absFilename === absDir)
|
|
5285
|
+
return true;
|
|
5286
|
+
if (dir.startsWith("app/") && root.endsWith("/app")) {
|
|
5287
|
+
const relativeDir = dir.substring(4);
|
|
5288
|
+
const absDirAlt = path.resolve(root, relativeDir);
|
|
5289
|
+
return absFilename.startsWith(absDirAlt + path.sep) || absFilename === absDirAlt;
|
|
5290
|
+
}
|
|
5291
|
+
return false;
|
|
5292
|
+
});
|
|
5293
|
+
});
|
|
5294
|
+
if (cleanPath.endsWith(".vue") && !matched) {
|
|
5295
|
+
loggerRef.current.debug(`[isFileInScope] REJECTED: ${absFilename} (Clean: ${cleanPath})`);
|
|
5296
|
+
}
|
|
5297
|
+
return matched;
|
|
5298
|
+
};
|
|
4929
5299
|
const userTemplate = vueOptions?.template ?? {};
|
|
4930
5300
|
const userCompilerOptions = userTemplate.compilerOptions ?? {};
|
|
4931
5301
|
const userNodeTransforms = userCompilerOptions.nodeTransforms ?? [];
|
|
4932
5302
|
const perFileTransform = /* @__PURE__ */ new Map();
|
|
4933
|
-
const
|
|
4934
|
-
|
|
4935
|
-
|
|
4936
|
-
|
|
4937
|
-
|
|
4938
|
-
prefixIdentifiers: true,
|
|
4939
|
-
nodeTransforms: [
|
|
5303
|
+
const getNodeTransforms = (filename, componentNameOverride) => {
|
|
5304
|
+
const cleanPath = filename.includes("?") ? filename.substring(0, filename.indexOf("?")) : filename;
|
|
5305
|
+
const viewsDirAbs = getViewsDirAbs();
|
|
5306
|
+
const componentName = componentNameOverride || getComponentNameFromPath(cleanPath);
|
|
5307
|
+
return [
|
|
4940
5308
|
...userNodeTransforms,
|
|
4941
5309
|
(node, context) => {
|
|
4942
|
-
if (!context.filename)
|
|
4943
|
-
return;
|
|
4944
|
-
const componentName = path.basename(context.filename, ".vue");
|
|
4945
5310
|
if (node.type === compilerCore.NodeTypes.ROOT) {
|
|
4946
5311
|
componentHierarchyMap.delete(componentName);
|
|
4947
|
-
vueFilesPathMap.set(componentName,
|
|
5312
|
+
vueFilesPathMap.set(componentName, filename);
|
|
4948
5313
|
perFileTransform.set(
|
|
4949
5314
|
componentName,
|
|
4950
5315
|
createTestIdTransform(
|
|
@@ -4952,113 +5317,99 @@ function createVuePluginWithTestIds(options) {
|
|
|
4952
5317
|
componentHierarchyMap,
|
|
4953
5318
|
nativeWrappers,
|
|
4954
5319
|
excludedComponents,
|
|
4955
|
-
|
|
5320
|
+
viewsDirAbs,
|
|
4956
5321
|
{
|
|
4957
5322
|
existingIdBehavior,
|
|
4958
5323
|
testIdAttribute,
|
|
4959
5324
|
nameCollisionBehavior,
|
|
4960
|
-
warn: (message) => loggerRef.current.warn(message)
|
|
5325
|
+
warn: (message) => loggerRef.current.warn(message),
|
|
5326
|
+
vueFilesPathMap
|
|
4961
5327
|
}
|
|
4962
5328
|
)
|
|
4963
5329
|
);
|
|
5330
|
+
return () => {
|
|
5331
|
+
extractMetadataAfterTransform(
|
|
5332
|
+
node,
|
|
5333
|
+
componentName,
|
|
5334
|
+
elementMetadata,
|
|
5335
|
+
semanticNameMap,
|
|
5336
|
+
testIdAttribute
|
|
5337
|
+
);
|
|
5338
|
+
};
|
|
4964
5339
|
}
|
|
4965
5340
|
let transform = perFileTransform.get(componentName);
|
|
4966
5341
|
if (!transform) {
|
|
4967
5342
|
componentHierarchyMap.delete(componentName);
|
|
4968
|
-
vueFilesPathMap.set(componentName,
|
|
5343
|
+
vueFilesPathMap.set(componentName, filename);
|
|
4969
5344
|
transform = createTestIdTransform(
|
|
4970
5345
|
componentName,
|
|
4971
5346
|
componentHierarchyMap,
|
|
4972
5347
|
nativeWrappers,
|
|
4973
5348
|
excludedComponents,
|
|
4974
|
-
|
|
5349
|
+
viewsDirAbs,
|
|
4975
5350
|
{
|
|
4976
5351
|
existingIdBehavior,
|
|
4977
5352
|
testIdAttribute,
|
|
4978
5353
|
nameCollisionBehavior,
|
|
4979
|
-
warn: (message) => loggerRef.current.warn(message)
|
|
5354
|
+
warn: (message) => loggerRef.current.warn(message),
|
|
5355
|
+
vueFilesPathMap
|
|
4980
5356
|
}
|
|
4981
5357
|
);
|
|
4982
5358
|
perFileTransform.set(componentName, transform);
|
|
4983
5359
|
}
|
|
4984
5360
|
return transform(node, context);
|
|
4985
5361
|
}
|
|
5362
|
+
];
|
|
5363
|
+
};
|
|
5364
|
+
const templateCompilerOptions = {
|
|
5365
|
+
...userCompilerOptions,
|
|
5366
|
+
prefixIdentifiers: true,
|
|
5367
|
+
nodeTransforms: [
|
|
5368
|
+
// This will be used if the user uses the returned vue plugin.
|
|
5369
|
+
// We'll populate it via the hook below for better compatibility.
|
|
4986
5370
|
]
|
|
4987
5371
|
};
|
|
5372
|
+
const metadataCollectorPlugin = {
|
|
5373
|
+
name: "vue-pom-generator-metadata-collector",
|
|
5374
|
+
enforce: "pre",
|
|
5375
|
+
async transform(code, id) {
|
|
5376
|
+
const cleanPath = id.includes("?") ? id.substring(0, id.indexOf("?")) : id;
|
|
5377
|
+
if (!cleanPath.endsWith(".vue") || !isFileInScope(id)) {
|
|
5378
|
+
return null;
|
|
5379
|
+
}
|
|
5380
|
+
if (id !== cleanPath) {
|
|
5381
|
+
return null;
|
|
5382
|
+
}
|
|
5383
|
+
const componentName = getComponentNameFromPath(cleanPath);
|
|
5384
|
+
loggerRef.current.debug(`Collecting metadata for ${cleanPath} (component: ${componentName})`);
|
|
5385
|
+
try {
|
|
5386
|
+
const { parse } = await import("@vue/compiler-sfc");
|
|
5387
|
+
const compilerDom2 = await import("@vue/compiler-dom");
|
|
5388
|
+
const compile = compilerDom2.compile;
|
|
5389
|
+
const { descriptor } = parse(code, { filename: cleanPath });
|
|
5390
|
+
if (descriptor.template) {
|
|
5391
|
+
compile(descriptor.template.content, {
|
|
5392
|
+
...userCompilerOptions,
|
|
5393
|
+
filename: cleanPath,
|
|
5394
|
+
nodeTransforms: getNodeTransforms(cleanPath, componentName)
|
|
5395
|
+
});
|
|
5396
|
+
loggerRef.current.debug(`Metadata collected for ${cleanPath}`);
|
|
5397
|
+
}
|
|
5398
|
+
} catch (e) {
|
|
5399
|
+
loggerRef.current.warn(`Metadata collection failed for ${cleanPath}: ${e}`);
|
|
5400
|
+
}
|
|
5401
|
+
return null;
|
|
5402
|
+
}
|
|
5403
|
+
};
|
|
4988
5404
|
const template = {
|
|
4989
5405
|
...userTemplate,
|
|
4990
|
-
compiler: {
|
|
4991
|
-
// Preserve the full compiler-dom module behavior (directiveTransforms, nodeTransforms, etc.).
|
|
4992
|
-
// We only override `compile` to run our metadata extraction after transforms.
|
|
4993
|
-
...compilerDom__namespace,
|
|
4994
|
-
compile(source, compilerOptions) {
|
|
4995
|
-
const componentName = compilerOptions.filename ? path.basename(compilerOptions.filename, ".vue") : "Unknown";
|
|
4996
|
-
return compileWithMetadataExtractionManual(
|
|
4997
|
-
source,
|
|
4998
|
-
compilerOptions,
|
|
4999
|
-
componentName,
|
|
5000
|
-
elementMetadata,
|
|
5001
|
-
semanticNameMap,
|
|
5002
|
-
testIdAttribute
|
|
5003
|
-
);
|
|
5004
|
-
}
|
|
5005
|
-
},
|
|
5006
5406
|
compilerOptions: templateCompilerOptions
|
|
5007
5407
|
};
|
|
5008
|
-
|
|
5408
|
+
const internalVuePlugin = vue({
|
|
5009
5409
|
...vueOptions,
|
|
5010
5410
|
template
|
|
5011
5411
|
});
|
|
5012
|
-
}
|
|
5013
|
-
const VUE_POM_GENERATOR_LOG_PREFIX = "[vue-pom-generator]";
|
|
5014
|
-
function normalize(message) {
|
|
5015
|
-
const trimmed = (message ?? "").trim();
|
|
5016
|
-
if (!trimmed)
|
|
5017
|
-
return VUE_POM_GENERATOR_LOG_PREFIX;
|
|
5018
|
-
if (trimmed.startsWith(VUE_POM_GENERATOR_LOG_PREFIX))
|
|
5019
|
-
return trimmed;
|
|
5020
|
-
return `${VUE_POM_GENERATOR_LOG_PREFIX} ${trimmed}`;
|
|
5021
|
-
}
|
|
5022
|
-
function createLogger(options) {
|
|
5023
|
-
const { verbosity, viteLogger } = options;
|
|
5024
|
-
const sinkInfo = (msg) => {
|
|
5025
|
-
if (viteLogger) {
|
|
5026
|
-
viteLogger.info(normalize(msg));
|
|
5027
|
-
return;
|
|
5028
|
-
}
|
|
5029
|
-
console.log(normalize(msg));
|
|
5030
|
-
};
|
|
5031
|
-
const sinkWarn = (msg) => {
|
|
5032
|
-
if (viteLogger) {
|
|
5033
|
-
viteLogger.warn(normalize(msg));
|
|
5034
|
-
return;
|
|
5035
|
-
}
|
|
5036
|
-
console.warn(normalize(msg));
|
|
5037
|
-
};
|
|
5038
|
-
const sinkDebug = (msg) => {
|
|
5039
|
-
if (viteLogger) {
|
|
5040
|
-
viteLogger.info(normalize(msg));
|
|
5041
|
-
return;
|
|
5042
|
-
}
|
|
5043
|
-
console.log(normalize(msg));
|
|
5044
|
-
};
|
|
5045
|
-
return {
|
|
5046
|
-
info(message) {
|
|
5047
|
-
if (verbosity === "silent")
|
|
5048
|
-
return;
|
|
5049
|
-
sinkInfo(message);
|
|
5050
|
-
},
|
|
5051
|
-
debug(message) {
|
|
5052
|
-
if (verbosity !== "debug")
|
|
5053
|
-
return;
|
|
5054
|
-
sinkDebug(message);
|
|
5055
|
-
},
|
|
5056
|
-
warn(message) {
|
|
5057
|
-
if (verbosity === "silent")
|
|
5058
|
-
return;
|
|
5059
|
-
sinkWarn(message);
|
|
5060
|
-
}
|
|
5061
|
-
};
|
|
5412
|
+
return { metadataCollectorPlugin, internalVuePlugin };
|
|
5062
5413
|
}
|
|
5063
5414
|
function assertNonEmptyString(value, name) {
|
|
5064
5415
|
if (!value || !value.trim()) {
|
|
@@ -5073,9 +5424,10 @@ function createVuePomGeneratorPlugins(options = {}) {
|
|
|
5073
5424
|
const generationSetting = options.generation;
|
|
5074
5425
|
const generationOptions = generationSetting === false ? null : generationSetting ?? {};
|
|
5075
5426
|
const generationEnabled = generationOptions !== null;
|
|
5076
|
-
const verbosity = options.logging?.verbosity ?? "
|
|
5427
|
+
const verbosity = options.logging?.verbosity ?? "warn";
|
|
5077
5428
|
const vueOptions = options.vueOptions;
|
|
5078
5429
|
const viewsDir = injection.viewsDir ?? "src/views";
|
|
5430
|
+
const scanDirs = injection.scanDirs ?? ["src"];
|
|
5079
5431
|
const nativeWrappers = injection.nativeWrappers ?? {};
|
|
5080
5432
|
const excludedComponents = injection.excludeComponents ?? [];
|
|
5081
5433
|
const testIdAttribute = (injection.attribute ?? "data-testid").trim() || "data-testid";
|
|
@@ -5084,6 +5436,8 @@ function createVuePomGeneratorPlugins(options = {}) {
|
|
|
5084
5436
|
const emitLanguages = generationOptions?.emit && generationOptions.emit.length ? generationOptions.emit : ["ts"];
|
|
5085
5437
|
const nameCollisionBehavior = generationOptions?.nameCollisionBehavior ?? "suffix";
|
|
5086
5438
|
const routerEntry = generationOptions?.router?.entry;
|
|
5439
|
+
const routerType = generationOptions?.router?.type ?? "vue-router";
|
|
5440
|
+
const csharp = generationOptions?.csharp;
|
|
5087
5441
|
const generateFixtures = generationOptions?.playwright?.fixtures;
|
|
5088
5442
|
const customPoms = generationOptions?.playwright?.customPoms;
|
|
5089
5443
|
const resolvedCustomPomAttachments = customPoms?.attachments ?? [];
|
|
@@ -5104,11 +5458,12 @@ function createVuePomGeneratorPlugins(options = {}) {
|
|
|
5104
5458
|
assertNonEmptyString(viewsDir, "[vue-pom-generator] injection.viewsDir");
|
|
5105
5459
|
if (generationEnabled) {
|
|
5106
5460
|
assertNonEmptyString(outDir, "[vue-pom-generator] generation.outDir");
|
|
5107
|
-
if (generationOptions?.router) {
|
|
5461
|
+
if (generationOptions?.router && routerType === "vue-router") {
|
|
5108
5462
|
assertNonEmptyString(routerEntry, "[vue-pom-generator] generation.router.entry");
|
|
5109
5463
|
}
|
|
5110
5464
|
}
|
|
5111
|
-
loggerRef.current.
|
|
5465
|
+
loggerRef.current.info(`projectRoot=${projectRootRef.current}`);
|
|
5466
|
+
loggerRef.current.info(`Active plugins: ${config.plugins.map((p) => p.name).filter((n) => n.includes("vue-pom")).join(", ")}`);
|
|
5112
5467
|
}
|
|
5113
5468
|
};
|
|
5114
5469
|
const getViewsDirAbs = () => resolveFromProjectRoot(projectRootRef.current, viewsDir);
|
|
@@ -5117,7 +5472,7 @@ function createVuePomGeneratorPlugins(options = {}) {
|
|
|
5117
5472
|
const semanticNameMap = /* @__PURE__ */ new Map();
|
|
5118
5473
|
const componentHierarchyMap = /* @__PURE__ */ new Map();
|
|
5119
5474
|
const vueFilesPathMap = /* @__PURE__ */ new Map();
|
|
5120
|
-
const
|
|
5475
|
+
const { metadataCollectorPlugin, internalVuePlugin } = createVuePluginWithTestIds({
|
|
5121
5476
|
vueOptions,
|
|
5122
5477
|
existingIdBehavior,
|
|
5123
5478
|
nameCollisionBehavior,
|
|
@@ -5129,13 +5484,11 @@ function createVuePomGeneratorPlugins(options = {}) {
|
|
|
5129
5484
|
excludedComponents,
|
|
5130
5485
|
getViewsDirAbs,
|
|
5131
5486
|
testIdAttribute,
|
|
5132
|
-
loggerRef
|
|
5487
|
+
loggerRef,
|
|
5488
|
+
scanDirs,
|
|
5489
|
+
getProjectRoot: () => projectRootRef.current
|
|
5133
5490
|
});
|
|
5134
|
-
|
|
5135
|
-
const virtualModules = createTestIdsVirtualModulesPlugin(componentTestIds);
|
|
5136
|
-
return [configPlugin, vuePlugin, virtualModules];
|
|
5137
|
-
}
|
|
5138
|
-
const routerAwarePoms = typeof routerEntry === "string" && routerEntry.trim().length > 0;
|
|
5491
|
+
const routerAwarePoms = typeof routerEntry === "string" && routerEntry.trim().length > 0 || routerType === "nuxt";
|
|
5139
5492
|
const supportPlugins = createSupportPlugins({
|
|
5140
5493
|
componentTestIds,
|
|
5141
5494
|
componentHierarchyMap,
|
|
@@ -5143,8 +5496,10 @@ function createVuePomGeneratorPlugins(options = {}) {
|
|
|
5143
5496
|
nativeWrappers,
|
|
5144
5497
|
excludedComponents,
|
|
5145
5498
|
viewsDir,
|
|
5499
|
+
scanDirs,
|
|
5146
5500
|
outDir,
|
|
5147
5501
|
emitLanguages,
|
|
5502
|
+
csharp,
|
|
5148
5503
|
routerAwarePoms,
|
|
5149
5504
|
routerEntry,
|
|
5150
5505
|
generateFixtures,
|
|
@@ -5154,9 +5509,29 @@ function createVuePomGeneratorPlugins(options = {}) {
|
|
|
5154
5509
|
customPomDir: resolvedCustomPomDir,
|
|
5155
5510
|
customPomImportAliases: resolvedCustomPomImportAliases,
|
|
5156
5511
|
testIdAttribute,
|
|
5157
|
-
loggerRef
|
|
5512
|
+
loggerRef,
|
|
5513
|
+
routerType
|
|
5158
5514
|
});
|
|
5159
|
-
|
|
5515
|
+
const isNuxt = routerType === "nuxt";
|
|
5516
|
+
if (isNuxt) {
|
|
5517
|
+
loggerRef.current.info("Nuxt environment detected. Skipping internal @vitejs/plugin-vue to avoid conflicts.");
|
|
5518
|
+
}
|
|
5519
|
+
const resultPlugins = [
|
|
5520
|
+
configPlugin,
|
|
5521
|
+
metadataCollectorPlugin,
|
|
5522
|
+
...isNuxt ? [] : [internalVuePlugin],
|
|
5523
|
+
...supportPlugins
|
|
5524
|
+
];
|
|
5525
|
+
if (!generationEnabled) {
|
|
5526
|
+
const virtualModules = createTestIdsVirtualModulesPlugin(componentTestIds);
|
|
5527
|
+
return [
|
|
5528
|
+
configPlugin,
|
|
5529
|
+
metadataCollectorPlugin,
|
|
5530
|
+
...isNuxt ? [] : [internalVuePlugin],
|
|
5531
|
+
virtualModules
|
|
5532
|
+
];
|
|
5533
|
+
}
|
|
5534
|
+
return resultPlugins;
|
|
5160
5535
|
}
|
|
5161
5536
|
exports.createVuePomGeneratorPlugins = createVuePomGeneratorPlugins;
|
|
5162
5537
|
exports.default = createVuePomGeneratorPlugins;
|