@immense/vue-pom-generator 1.0.58 → 1.0.60
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 +36 -25
- package/RELEASE_NOTES.md +29 -31
- package/class-generation/base-page.ts +49 -25
- package/class-generation/index.ts +243 -333
- package/class-generation/playwright-types.ts +1 -1
- package/click-instrumentation.ts +0 -4
- package/dist/class-generation/base-page.d.ts +8 -4
- package/dist/class-generation/base-page.d.ts.map +1 -1
- package/dist/class-generation/index.d.ts +2 -0
- package/dist/class-generation/index.d.ts.map +1 -1
- package/dist/class-generation/playwright-types.d.ts +1 -1
- package/dist/class-generation/playwright-types.d.ts.map +1 -1
- package/dist/click-instrumentation.d.ts +0 -1
- package/dist/click-instrumentation.d.ts.map +1 -1
- package/dist/index.cjs +1527 -1064
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +1529 -1066
- package/dist/index.mjs.map +1 -1
- package/dist/manifest-generator.d.ts +35 -1
- package/dist/manifest-generator.d.ts.map +1 -1
- package/dist/method-generation.d.ts +4 -2
- package/dist/method-generation.d.ts.map +1 -1
- package/dist/plugin/create-vue-pom-generator-plugins.d.ts.map +1 -1
- package/dist/plugin/resolved-generation-options.d.ts +33 -0
- package/dist/plugin/resolved-generation-options.d.ts.map +1 -0
- package/dist/plugin/resolved-injection-options.d.ts +27 -0
- package/dist/plugin/resolved-injection-options.d.ts.map +1 -0
- package/dist/plugin/support/build-plugin.d.ts +2 -29
- package/dist/plugin/support/build-plugin.d.ts.map +1 -1
- package/dist/plugin/support/dev-plugin.d.ts +2 -28
- package/dist/plugin/support/dev-plugin.d.ts.map +1 -1
- package/dist/plugin/support/virtual-modules.d.ts +3 -1
- package/dist/plugin/support/virtual-modules.d.ts.map +1 -1
- package/dist/plugin/support-plugins.d.ts +4 -33
- package/dist/plugin/support-plugins.d.ts.map +1 -1
- package/dist/plugin/types.d.ts +6 -23
- package/dist/plugin/types.d.ts.map +1 -1
- package/dist/plugin/vue-plugin.d.ts.map +1 -1
- package/dist/pom-discoverability.d.ts +10 -0
- package/dist/pom-discoverability.d.ts.map +1 -0
- package/dist/pom-params.d.ts +40 -0
- package/dist/pom-params.d.ts.map +1 -0
- package/dist/pom-patterns.d.ts +31 -0
- package/dist/pom-patterns.d.ts.map +1 -0
- package/dist/routing/to-directive.d.ts +21 -0
- package/dist/routing/to-directive.d.ts.map +1 -1
- package/dist/tests/base-page.test.d.ts +2 -0
- package/dist/tests/base-page.test.d.ts.map +1 -0
- package/dist/tests/fixtures/generated-tsc/base-page.full.d.ts +7 -4
- package/dist/tests/fixtures/generated-tsc/base-page.full.d.ts.map +1 -1
- package/dist/tests/resolved-injection-options.test.d.ts +2 -0
- package/dist/tests/resolved-injection-options.test.d.ts.map +1 -0
- package/dist/transform.d.ts +0 -1
- package/dist/transform.d.ts.map +1 -1
- package/dist/utils.d.ts +129 -63
- package/dist/utils.d.ts.map +1 -1
- package/package.json +6 -4
- package/sequence-diagram.md +6 -6
package/dist/index.cjs
CHANGED
|
@@ -245,6 +245,49 @@ async function loadNuxtProjectDiscovery(cwd = process.cwd()) {
|
|
|
245
245
|
const nuxtOptions = await loadNuxtConfig({ cwd });
|
|
246
246
|
return resolveNuxtProjectDiscovery(nuxtOptions, getLayerDirectories, cwd);
|
|
247
247
|
}
|
|
248
|
+
function resolveGenerationSupportOptions(options) {
|
|
249
|
+
return {
|
|
250
|
+
outDir: (options.outDir ?? "tests/playwright/__generated__").trim(),
|
|
251
|
+
emitLanguages: options.emitLanguages?.length ? options.emitLanguages : ["ts"],
|
|
252
|
+
typescriptOutputStructure: options.typescriptOutputStructure ?? "aggregated",
|
|
253
|
+
csharp: options.csharp,
|
|
254
|
+
generateFixtures: options.generateFixtures,
|
|
255
|
+
customPomAttachments: options.customPomAttachments ?? [],
|
|
256
|
+
customPomDir: options.customPomDir ?? "tests/playwright/pom/custom",
|
|
257
|
+
requireCustomPomDir: options.requireCustomPomDir ?? false,
|
|
258
|
+
customPomImportAliases: options.customPomImportAliases,
|
|
259
|
+
customPomImportNameCollisionBehavior: options.customPomImportNameCollisionBehavior ?? "error",
|
|
260
|
+
nameCollisionBehavior: options.nameCollisionBehavior ?? "error",
|
|
261
|
+
existingIdBehavior: options.existingIdBehavior ?? "error",
|
|
262
|
+
testIdAttribute: (options.testIdAttribute ?? "data-testid").trim() || "data-testid",
|
|
263
|
+
routerAwarePoms: options.routerAwarePoms ?? false,
|
|
264
|
+
routerEntry: options.routerEntry,
|
|
265
|
+
routerType: options.routerType ?? "vue-router",
|
|
266
|
+
routerModuleShims: options.routerModuleShims
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
function resolveInjectionSupportOptions(options) {
|
|
270
|
+
const isNuxt = options.isNuxt ?? false;
|
|
271
|
+
return {
|
|
272
|
+
pageDirs: isNuxt ? ["app/pages"] : [options.viewsDir ?? "src/views"],
|
|
273
|
+
componentDirs: isNuxt ? ["app/components"] : options.componentDirs ?? ["src/components"],
|
|
274
|
+
layoutDirs: isNuxt ? ["app/layouts"] : options.layoutDirs ?? ["src/layouts"],
|
|
275
|
+
wrapperSearchRoots: isNuxt ? [] : options.wrapperSearchRoots ?? [],
|
|
276
|
+
nativeWrappers: options.nativeWrappers ?? {},
|
|
277
|
+
excludedComponents: options.excludedComponents ?? [],
|
|
278
|
+
existingIdBehavior: options.existingIdBehavior ?? "error",
|
|
279
|
+
testIdAttribute: (options.testIdAttribute ?? "data-testid").trim() || "data-testid"
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
function applyNuxtDiscoveryToInjectionOptions(options, discovery) {
|
|
283
|
+
return {
|
|
284
|
+
...options,
|
|
285
|
+
pageDirs: discovery.pageDirs.length ? discovery.pageDirs : [path.resolve(discovery.srcDir, "pages")],
|
|
286
|
+
componentDirs: discovery.componentDirs,
|
|
287
|
+
layoutDirs: discovery.layoutDirs,
|
|
288
|
+
wrapperSearchRoots: discovery.wrapperSearchRoots
|
|
289
|
+
};
|
|
290
|
+
}
|
|
248
291
|
function createTypeScriptProject() {
|
|
249
292
|
return new tsMorph.Project({
|
|
250
293
|
useInMemoryFileSystem: true,
|
|
@@ -336,16 +379,7 @@ function createClassConstructor(constructorDeclaration) {
|
|
|
336
379
|
...constructorDeclaration
|
|
337
380
|
};
|
|
338
381
|
}
|
|
339
|
-
function
|
|
340
|
-
if (!value) {
|
|
341
|
-
return value;
|
|
342
|
-
}
|
|
343
|
-
return value.charAt(0).toUpperCase() + value.slice(1);
|
|
344
|
-
}
|
|
345
|
-
function hasParam(params, name) {
|
|
346
|
-
return Object.prototype.hasOwnProperty.call(params, name);
|
|
347
|
-
}
|
|
348
|
-
function splitTypeAndInitializer(typeExpression) {
|
|
382
|
+
function splitPomParameterTypeExpression(typeExpression) {
|
|
349
383
|
const trimmed = typeExpression.trim();
|
|
350
384
|
const initializerIndex = trimmed.lastIndexOf("=");
|
|
351
385
|
if (initializerIndex < 0) {
|
|
@@ -356,49 +390,306 @@ function splitTypeAndInitializer(typeExpression) {
|
|
|
356
390
|
initializer: trimmed.slice(initializerIndex + 1).trim()
|
|
357
391
|
};
|
|
358
392
|
}
|
|
359
|
-
function
|
|
360
|
-
const
|
|
393
|
+
function createPomParameterSpec(name, typeExpression, options = {}) {
|
|
394
|
+
const normalizedTypeExpression = typeExpression?.trim();
|
|
395
|
+
const { type, initializer } = normalizedTypeExpression ? splitPomParameterTypeExpression(normalizedTypeExpression) : { type: void 0, initializer: void 0 };
|
|
361
396
|
return {
|
|
362
397
|
name,
|
|
363
|
-
|
|
364
|
-
|
|
398
|
+
typeExpression: normalizedTypeExpression,
|
|
399
|
+
type,
|
|
400
|
+
initializer: options.initializer ?? initializer,
|
|
401
|
+
hasQuestionToken: options.hasQuestionToken,
|
|
402
|
+
isRestParameter: options.isRestParameter
|
|
365
403
|
};
|
|
366
404
|
}
|
|
367
|
-
function
|
|
368
|
-
|
|
405
|
+
function normalizePomParameters(params) {
|
|
406
|
+
if (!params) {
|
|
407
|
+
return [];
|
|
408
|
+
}
|
|
409
|
+
return params.map((param) => createPomParameterSpec(param.name, param.typeExpression ?? param.type, {
|
|
410
|
+
initializer: param.initializer,
|
|
411
|
+
hasQuestionToken: param.hasQuestionToken,
|
|
412
|
+
isRestParameter: param.isRestParameter
|
|
413
|
+
}));
|
|
369
414
|
}
|
|
370
|
-
function
|
|
415
|
+
function getPomParameterNames(params) {
|
|
416
|
+
return normalizePomParameters(params).map((param) => param.name);
|
|
417
|
+
}
|
|
418
|
+
function getPomParameter(params, name) {
|
|
419
|
+
return normalizePomParameters(params).find((param) => param.name === name);
|
|
420
|
+
}
|
|
421
|
+
function hasPomParameter(params, name) {
|
|
422
|
+
return !!getPomParameter(params, name);
|
|
423
|
+
}
|
|
424
|
+
function setPomParameter(params, name, typeExpression, options = {}) {
|
|
425
|
+
const nextParam = createPomParameterSpec(name, typeExpression, options);
|
|
426
|
+
const normalizedParams = normalizePomParameters(params);
|
|
427
|
+
const existingIndex = normalizedParams.findIndex((param) => param.name === name);
|
|
428
|
+
if (existingIndex < 0) {
|
|
429
|
+
return [...normalizedParams, nextParam];
|
|
430
|
+
}
|
|
431
|
+
const nextParams = normalizedParams.slice();
|
|
432
|
+
nextParams[existingIndex] = nextParam;
|
|
433
|
+
return nextParams;
|
|
434
|
+
}
|
|
435
|
+
function removePomParameter(params, name) {
|
|
436
|
+
return normalizePomParameters(params).filter((param) => param.name !== name);
|
|
437
|
+
}
|
|
438
|
+
function toTypeScriptPomParameterStructures(params) {
|
|
439
|
+
return normalizePomParameters(params).map((param) => ({
|
|
440
|
+
name: param.name,
|
|
441
|
+
type: param.type || void 0,
|
|
442
|
+
initializer: param.initializer,
|
|
443
|
+
hasQuestionToken: param.hasQuestionToken,
|
|
444
|
+
isRestParameter: param.isRestParameter
|
|
445
|
+
}));
|
|
446
|
+
}
|
|
447
|
+
function getPomParameterArgumentNames(params) {
|
|
448
|
+
return normalizePomParameters(params).map((param) => param.isRestParameter ? `...${param.name}` : param.name);
|
|
449
|
+
}
|
|
450
|
+
function createPomMethodSignature(parameters) {
|
|
371
451
|
return {
|
|
372
|
-
|
|
373
|
-
type: options.type,
|
|
374
|
-
initializer: options.initializer
|
|
452
|
+
parameters: normalizePomParameters(parameters)
|
|
375
453
|
};
|
|
376
454
|
}
|
|
377
|
-
function
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
455
|
+
function pomParameterSpecEquals(left, right) {
|
|
456
|
+
return left.name === right.name && left.typeExpression === right.typeExpression && left.type === right.type && left.initializer === right.initializer && left.hasQuestionToken === right.hasQuestionToken && left.isRestParameter === right.isRestParameter;
|
|
457
|
+
}
|
|
458
|
+
function pomParameterListEquals(left, right) {
|
|
459
|
+
const leftParams = normalizePomParameters(left);
|
|
460
|
+
const rightParams = normalizePomParameters(right);
|
|
461
|
+
if (leftParams.length !== rightParams.length) {
|
|
462
|
+
return false;
|
|
381
463
|
}
|
|
382
|
-
return
|
|
464
|
+
return leftParams.every((param, index) => pomParameterSpecEquals(param, rightParams[index]));
|
|
465
|
+
}
|
|
466
|
+
function pomMethodSignatureEquals(left, right) {
|
|
467
|
+
return pomParameterListEquals(left.parameters, right.parameters);
|
|
468
|
+
}
|
|
469
|
+
function isParameterizedPomPattern(kind) {
|
|
470
|
+
return kind === "parameterized";
|
|
383
471
|
}
|
|
384
|
-
function
|
|
472
|
+
function getTemplateVariables(formatted) {
|
|
385
473
|
const out = [];
|
|
386
474
|
const seen = /* @__PURE__ */ new Set();
|
|
387
|
-
|
|
388
|
-
for (const
|
|
389
|
-
|
|
475
|
+
const matches = formatted.matchAll(/\$\{(\w+)\}/g);
|
|
476
|
+
for (const match of matches) {
|
|
477
|
+
const variableName = match[1];
|
|
478
|
+
if (seen.has(variableName)) {
|
|
390
479
|
continue;
|
|
391
480
|
}
|
|
392
|
-
|
|
481
|
+
seen.add(variableName);
|
|
482
|
+
out.push(variableName);
|
|
483
|
+
}
|
|
484
|
+
return out;
|
|
485
|
+
}
|
|
486
|
+
function createPomStringPattern(formatted, patternKind) {
|
|
487
|
+
return {
|
|
488
|
+
formatted,
|
|
489
|
+
patternKind,
|
|
490
|
+
templateVariables: getTemplateVariables(formatted)
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
function getPomPatternVariables(patterns, options = {}) {
|
|
494
|
+
const out = [];
|
|
495
|
+
const seen = /* @__PURE__ */ new Set();
|
|
496
|
+
const omitted = new Set(options.omit ?? []);
|
|
497
|
+
for (const pattern of patterns) {
|
|
498
|
+
for (const variableName of pattern.templateVariables) {
|
|
499
|
+
if (omitted.has(variableName) || seen.has(variableName)) {
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
502
|
+
seen.add(variableName);
|
|
503
|
+
out.push(variableName);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
return out;
|
|
507
|
+
}
|
|
508
|
+
function orderPomPatternParameters(params, patterns, options = {}) {
|
|
509
|
+
const currentParams = normalizePomParameters(params);
|
|
510
|
+
const orderedParams = [];
|
|
511
|
+
const seen = /* @__PURE__ */ new Set();
|
|
512
|
+
const missingParams = [];
|
|
513
|
+
for (const variableName of getPomPatternVariables(patterns, options)) {
|
|
514
|
+
seen.add(variableName);
|
|
515
|
+
const existingParam = currentParams.find((param) => param.name === variableName);
|
|
516
|
+
if (!existingParam) {
|
|
517
|
+
missingParams.push(variableName);
|
|
393
518
|
continue;
|
|
394
519
|
}
|
|
395
|
-
|
|
396
|
-
|
|
520
|
+
orderedParams.push(existingParam);
|
|
521
|
+
}
|
|
522
|
+
if (missingParams.length > 0) {
|
|
523
|
+
const availableParams = currentParams.map((param) => JSON.stringify(param.name)).join(", ") || "<none>";
|
|
524
|
+
const patternSummary = patterns.map((pattern) => JSON.stringify(pattern.formatted)).join(", ");
|
|
525
|
+
throw new Error(
|
|
526
|
+
`[vue-pom-generator] Missing selector parameter(s) ${missingParams.map((name) => JSON.stringify(name)).join(", ")} for parameterized pattern(s) ${patternSummary}. Available parameters: ${availableParams}.`
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
for (const param of currentParams) {
|
|
530
|
+
if (seen.has(param.name)) {
|
|
531
|
+
continue;
|
|
532
|
+
}
|
|
533
|
+
seen.add(param.name);
|
|
534
|
+
orderedParams.push(param);
|
|
535
|
+
}
|
|
536
|
+
return orderedParams;
|
|
537
|
+
}
|
|
538
|
+
function getIndexedPomPatternVariable(pattern) {
|
|
539
|
+
if (!isParameterizedPomPattern(pattern.patternKind)) {
|
|
540
|
+
return null;
|
|
541
|
+
}
|
|
542
|
+
if (pattern.templateVariables.length !== 1) {
|
|
543
|
+
throw new Error(
|
|
544
|
+
`[vue-pom-generator] Parameterized locator getters require exactly one template variable; got ${pattern.templateVariables.length} in ${JSON.stringify(pattern.formatted)}.`
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
return pattern.templateVariables[0];
|
|
548
|
+
}
|
|
549
|
+
function hasPomPatternVariables(pattern) {
|
|
550
|
+
return pattern.templateVariables.length > 0;
|
|
551
|
+
}
|
|
552
|
+
function toTypeScriptPomPatternExpression(pattern) {
|
|
553
|
+
return isParameterizedPomPattern(pattern.patternKind) ? `\`${pattern.formatted}\`` : JSON.stringify(pattern.formatted);
|
|
554
|
+
}
|
|
555
|
+
function toCSharpPomPatternExpression(pattern) {
|
|
556
|
+
if (!isParameterizedPomPattern(pattern.patternKind)) {
|
|
557
|
+
return JSON.stringify(pattern.formatted);
|
|
558
|
+
}
|
|
559
|
+
const inner = pattern.formatted.replace(/\$\{/g, "{");
|
|
560
|
+
return `$${JSON.stringify(inner)}`;
|
|
561
|
+
}
|
|
562
|
+
function bindTypeScriptPomPattern(pattern, variableName) {
|
|
563
|
+
const expression = toTypeScriptPomPatternExpression(pattern);
|
|
564
|
+
if (!isParameterizedPomPattern(pattern.patternKind)) {
|
|
565
|
+
return { expression, setupStatements: [] };
|
|
566
|
+
}
|
|
567
|
+
return {
|
|
568
|
+
expression: variableName,
|
|
569
|
+
setupStatements: [`const ${variableName} = ${expression};`]
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
function bindCSharpPomPattern(pattern, variableName) {
|
|
573
|
+
const expression = toCSharpPomPatternExpression(pattern);
|
|
574
|
+
if (!isParameterizedPomPattern(pattern.patternKind)) {
|
|
575
|
+
return { expression, setupStatements: [] };
|
|
576
|
+
}
|
|
577
|
+
return {
|
|
578
|
+
expression: variableName,
|
|
579
|
+
setupStatements: [`var ${variableName} = ${expression};`]
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
function pomStringPatternEquals(left, right) {
|
|
583
|
+
return left.formatted === right.formatted && left.patternKind === right.patternKind;
|
|
584
|
+
}
|
|
585
|
+
function uniquePomStringPatterns(primary, alternates) {
|
|
586
|
+
const out = [];
|
|
587
|
+
const seen = /* @__PURE__ */ new Set();
|
|
588
|
+
const add = (pattern) => {
|
|
589
|
+
const key = JSON.stringify(pattern);
|
|
590
|
+
if (seen.has(key)) {
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
seen.add(key);
|
|
594
|
+
out.push(pattern);
|
|
595
|
+
};
|
|
596
|
+
add(primary);
|
|
597
|
+
for (const alternate of alternates ?? []) {
|
|
598
|
+
add(alternate);
|
|
397
599
|
}
|
|
398
600
|
return out;
|
|
399
601
|
}
|
|
400
|
-
function
|
|
401
|
-
|
|
602
|
+
function splitDiscoverabilityWords(value) {
|
|
603
|
+
const normalized = value.replace(/ByKey/g, "").replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/[._-]+/g, " ").trim();
|
|
604
|
+
if (!normalized) {
|
|
605
|
+
return [];
|
|
606
|
+
}
|
|
607
|
+
return normalized.split(/\s+/).map((word) => word.toLowerCase()).filter(Boolean);
|
|
608
|
+
}
|
|
609
|
+
function joinDiscoverabilityWords(words) {
|
|
610
|
+
return words.join(" ").replace(/\s+/g, " ").trim();
|
|
611
|
+
}
|
|
612
|
+
function toSentenceCase(value) {
|
|
613
|
+
return value.charAt(0).toUpperCase() + value.slice(1);
|
|
614
|
+
}
|
|
615
|
+
function stripComponentKindSuffix(componentName) {
|
|
616
|
+
for (const suffix of ["Page", "Component", "Layout"]) {
|
|
617
|
+
if (componentName.endsWith(suffix) && componentName.length > suffix.length) {
|
|
618
|
+
return componentName.slice(0, -suffix.length);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
return componentName;
|
|
622
|
+
}
|
|
623
|
+
function removeLeadingWords(words, prefixWords) {
|
|
624
|
+
if (!prefixWords.length || words.length < prefixWords.length) {
|
|
625
|
+
return [...words];
|
|
626
|
+
}
|
|
627
|
+
for (let i = 0; i < prefixWords.length; i++) {
|
|
628
|
+
if (words[i] !== prefixWords[i]) {
|
|
629
|
+
return [...words];
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
return words.slice(prefixWords.length);
|
|
633
|
+
}
|
|
634
|
+
function removeTrailingRoleWord(words, roleWord) {
|
|
635
|
+
if (!words.length || words[words.length - 1] !== roleWord) {
|
|
636
|
+
return [...words];
|
|
637
|
+
}
|
|
638
|
+
return words.slice(0, -1);
|
|
639
|
+
}
|
|
640
|
+
function humanizePomMethodName(methodName) {
|
|
641
|
+
return joinDiscoverabilityWords(splitDiscoverabilityWords(methodName));
|
|
642
|
+
}
|
|
643
|
+
function stripPomActionPrefix(actionName) {
|
|
644
|
+
for (const prefix of ["click", "select", "type", "goTo"]) {
|
|
645
|
+
if (actionName.startsWith(prefix) && actionName.length > prefix.length) {
|
|
646
|
+
return actionName.slice(prefix.length);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
return actionName;
|
|
650
|
+
}
|
|
651
|
+
function normalizePomRoleLabel(nativeRole) {
|
|
652
|
+
if (nativeRole === "vselect") {
|
|
653
|
+
return "select";
|
|
654
|
+
}
|
|
655
|
+
return nativeRole || "element";
|
|
656
|
+
}
|
|
657
|
+
function buildPomLocatorDescription(args) {
|
|
658
|
+
const componentWords = splitDiscoverabilityWords(args.componentName ? stripComponentKindSuffix(args.componentName) : "");
|
|
659
|
+
const roleWord = normalizePomRoleLabel(args.nativeRole).toLowerCase();
|
|
660
|
+
const semanticWords = removeLeadingWords(
|
|
661
|
+
removeTrailingRoleWord(splitDiscoverabilityWords(args.methodName), roleWord),
|
|
662
|
+
componentWords
|
|
663
|
+
);
|
|
664
|
+
const phrase = joinDiscoverabilityWords([
|
|
665
|
+
...componentWords,
|
|
666
|
+
...semanticWords,
|
|
667
|
+
roleWord
|
|
668
|
+
]);
|
|
669
|
+
return toSentenceCase(phrase || "Generated element");
|
|
670
|
+
}
|
|
671
|
+
function upperFirst$1(value) {
|
|
672
|
+
if (!value) {
|
|
673
|
+
return value;
|
|
674
|
+
}
|
|
675
|
+
return value.charAt(0).toUpperCase() + value.slice(1);
|
|
676
|
+
}
|
|
677
|
+
function createParameters(params) {
|
|
678
|
+
return toTypeScriptPomParameterStructures(params);
|
|
679
|
+
}
|
|
680
|
+
function createInlineParameter(name, options = {}) {
|
|
681
|
+
return {
|
|
682
|
+
name,
|
|
683
|
+
type: options.type,
|
|
684
|
+
initializer: options.initializer
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
function removeByKeySegment$1(value) {
|
|
688
|
+
const idx = value.lastIndexOf("ByKey");
|
|
689
|
+
if (idx < 0) {
|
|
690
|
+
return value;
|
|
691
|
+
}
|
|
692
|
+
return value.slice(0, idx) + value.slice(idx + "ByKey".length);
|
|
402
693
|
}
|
|
403
694
|
function createAsyncMethod(name, parameters, statements) {
|
|
404
695
|
return createClassMethod({
|
|
@@ -408,17 +699,25 @@ function createAsyncMethod(name, parameters, statements) {
|
|
|
408
699
|
statements
|
|
409
700
|
});
|
|
410
701
|
}
|
|
411
|
-
function generateClickMethod(methodName,
|
|
702
|
+
function generateClickMethod(componentName, methodName, selector, alternateSelectors, parameters) {
|
|
412
703
|
const name = `click${methodName}`;
|
|
413
704
|
const noWaitName = `${name}NoWait`;
|
|
414
|
-
const
|
|
415
|
-
|
|
416
|
-
|
|
705
|
+
const locatorDescription = JSON.stringify(buildPomLocatorDescription({
|
|
706
|
+
componentName,
|
|
707
|
+
methodName,
|
|
708
|
+
nativeRole: "button"
|
|
709
|
+
}));
|
|
710
|
+
const selectorParams = orderPomPatternParameters(parameters, [selector]);
|
|
711
|
+
const hasSelectorVariables = hasPomPatternVariables(selector);
|
|
712
|
+
const baseParameters = createParameters(selectorParams);
|
|
713
|
+
const argsForForward = getPomParameterNames(selectorParams).join(", ");
|
|
714
|
+
const alternates = uniquePomStringPatterns(selector, alternateSelectors).slice(1);
|
|
715
|
+
const primaryTestIdExpr = toTypeScriptPomPatternExpression(selector);
|
|
417
716
|
if (alternates.length > 0) {
|
|
418
|
-
const candidatesExpr = [
|
|
717
|
+
const candidatesExpr = [primaryTestIdExpr, ...alternates.map((id) => toTypeScriptPomPatternExpression(id))].join(", ");
|
|
419
718
|
const clickMethod = createAsyncMethod(
|
|
420
719
|
name,
|
|
421
|
-
|
|
720
|
+
hasSelectorVariables ? [
|
|
422
721
|
...baseParameters,
|
|
423
722
|
createInlineParameter("wait", { type: "boolean", initializer: "true" }),
|
|
424
723
|
createInlineParameter("annotationText", { type: "string", initializer: '""' })
|
|
@@ -430,7 +729,7 @@ function generateClickMethod(methodName, formattedDataTestId, alternateFormatted
|
|
|
430
729
|
writer.writeLine(`const candidates = [${candidatesExpr}] as const;`);
|
|
431
730
|
writer.writeLine("let lastError: unknown;");
|
|
432
731
|
writer.write("for (const testId of candidates) ").block(() => {
|
|
433
|
-
writer.writeLine(
|
|
732
|
+
writer.writeLine(`const locator = this.locatorByTestId(testId, ${locatorDescription});`);
|
|
434
733
|
writer.write("try ").block(() => {
|
|
435
734
|
writer.write("if (await locator.count()) ").block(() => {
|
|
436
735
|
writer.writeLine("await this.clickLocator(locator, annotationText, wait);");
|
|
@@ -447,14 +746,14 @@ function generateClickMethod(methodName, formattedDataTestId, alternateFormatted
|
|
|
447
746
|
const noWaitArgs = argsForForward ? `${argsForForward}, false, annotationText` : "false, annotationText";
|
|
448
747
|
const noWaitMethod = createAsyncMethod(
|
|
449
748
|
noWaitName,
|
|
450
|
-
|
|
749
|
+
hasSelectorVariables ? [...baseParameters, createInlineParameter("annotationText", { type: "string", initializer: '""' })] : [createInlineParameter("annotationText", { type: "string", initializer: '""' })],
|
|
451
750
|
(writer) => {
|
|
452
751
|
writer.writeLine(`await this.${name}(${noWaitArgs});`);
|
|
453
752
|
}
|
|
454
753
|
);
|
|
455
754
|
return [clickMethod, noWaitMethod];
|
|
456
755
|
}
|
|
457
|
-
if (
|
|
756
|
+
if (hasSelectorVariables) {
|
|
458
757
|
return [
|
|
459
758
|
createAsyncMethod(
|
|
460
759
|
name,
|
|
@@ -464,7 +763,7 @@ function generateClickMethod(methodName, formattedDataTestId, alternateFormatted
|
|
|
464
763
|
createInlineParameter("annotationText", { type: "string", initializer: '""' })
|
|
465
764
|
],
|
|
466
765
|
(writer) => {
|
|
467
|
-
writer.writeLine(`await this.clickByTestId(
|
|
766
|
+
writer.writeLine(`await this.clickByTestId(${primaryTestIdExpr}, annotationText, wait, ${locatorDescription});`);
|
|
468
767
|
}
|
|
469
768
|
),
|
|
470
769
|
createAsyncMethod(
|
|
@@ -484,7 +783,7 @@ function generateClickMethod(methodName, formattedDataTestId, alternateFormatted
|
|
|
484
783
|
createInlineParameter("annotationText", { type: "string", initializer: '""' })
|
|
485
784
|
],
|
|
486
785
|
(writer) => {
|
|
487
|
-
writer.writeLine(`await this.clickByTestId(
|
|
786
|
+
writer.writeLine(`await this.clickByTestId(${primaryTestIdExpr}, annotationText, wait, ${locatorDescription});`);
|
|
488
787
|
}
|
|
489
788
|
),
|
|
490
789
|
createAsyncMethod(
|
|
@@ -496,66 +795,76 @@ function generateClickMethod(methodName, formattedDataTestId, alternateFormatted
|
|
|
496
795
|
)
|
|
497
796
|
];
|
|
498
797
|
}
|
|
499
|
-
function generateRadioMethod(methodName,
|
|
798
|
+
function generateRadioMethod(componentName, methodName, selector, parameters) {
|
|
500
799
|
const name = `select${methodName}`;
|
|
501
|
-
const
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
const
|
|
800
|
+
const locatorDescription = JSON.stringify(buildPomLocatorDescription({
|
|
801
|
+
componentName,
|
|
802
|
+
methodName,
|
|
803
|
+
nativeRole: "radio"
|
|
804
|
+
}));
|
|
805
|
+
const selectorParams = orderPomPatternParameters(parameters, [selector]);
|
|
806
|
+
const methodParameters = createParameters(selectorParams);
|
|
807
|
+
const testIdExpr = toTypeScriptPomPatternExpression(selector);
|
|
507
808
|
return [
|
|
508
|
-
createAsyncMethod(name,
|
|
509
|
-
writer.writeLine(`await this.clickByTestId(${testIdExpr}, annotationText);`);
|
|
809
|
+
createAsyncMethod(name, methodParameters, (writer) => {
|
|
810
|
+
writer.writeLine(`await this.clickByTestId(${testIdExpr}, annotationText, true, ${locatorDescription});`);
|
|
510
811
|
})
|
|
511
812
|
];
|
|
512
813
|
}
|
|
513
|
-
function generateSelectMethod(methodName,
|
|
814
|
+
function generateSelectMethod(componentName, methodName, selector, parameters) {
|
|
514
815
|
const name = `select${methodName}`;
|
|
515
|
-
const
|
|
516
|
-
|
|
816
|
+
const locatorDescription = JSON.stringify(buildPomLocatorDescription({
|
|
817
|
+
componentName,
|
|
818
|
+
methodName,
|
|
819
|
+
nativeRole: "select"
|
|
820
|
+
}));
|
|
821
|
+
const selectorParams = orderPomPatternParameters(parameters, [selector]);
|
|
822
|
+
const testIdExpr = toTypeScriptPomPatternExpression(selector);
|
|
517
823
|
return [
|
|
518
824
|
createAsyncMethod(
|
|
519
825
|
name,
|
|
520
|
-
|
|
521
|
-
createInlineParameter("value", { type: "string" }),
|
|
522
|
-
createInlineParameter("annotationText", { type: "string", initializer: '""' })
|
|
523
|
-
],
|
|
826
|
+
createParameters(selectorParams),
|
|
524
827
|
(writer) => {
|
|
525
|
-
writer.writeLine(`const
|
|
526
|
-
writer.writeLine(
|
|
527
|
-
writer.writeLine("await this.
|
|
828
|
+
writer.writeLine(`const testId = ${testIdExpr};`);
|
|
829
|
+
writer.writeLine(`const locator = this.locatorByTestId(testId, ${locatorDescription});`);
|
|
830
|
+
writer.writeLine("await this.animateCursorToElement(locator, false, 500, annotationText);");
|
|
831
|
+
writer.writeLine("await locator.selectOption(value);");
|
|
528
832
|
}
|
|
529
833
|
)
|
|
530
834
|
];
|
|
531
835
|
}
|
|
532
|
-
function generateVSelectMethod(methodName,
|
|
836
|
+
function generateVSelectMethod(componentName, methodName, selector, parameters) {
|
|
533
837
|
const name = `select${methodName}`;
|
|
838
|
+
const locatorDescription = JSON.stringify(buildPomLocatorDescription({
|
|
839
|
+
componentName,
|
|
840
|
+
methodName,
|
|
841
|
+
nativeRole: "vselect"
|
|
842
|
+
}));
|
|
843
|
+
const selectorParams = orderPomPatternParameters(parameters, [selector]);
|
|
534
844
|
return [
|
|
535
845
|
createAsyncMethod(
|
|
536
846
|
name,
|
|
537
|
-
|
|
538
|
-
createInlineParameter("value", { type: "string" }),
|
|
539
|
-
createInlineParameter("timeOut", { type: "number", initializer: "500" }),
|
|
540
|
-
createInlineParameter("annotationText", { type: "string", initializer: '""' })
|
|
541
|
-
],
|
|
847
|
+
createParameters(selectorParams),
|
|
542
848
|
(writer) => {
|
|
543
|
-
writer.writeLine(`await this.selectVSelectByTestId(
|
|
849
|
+
writer.writeLine(`await this.selectVSelectByTestId(${toTypeScriptPomPatternExpression(selector)}, value, timeOut, annotationText, ${locatorDescription});`);
|
|
544
850
|
}
|
|
545
851
|
)
|
|
546
852
|
];
|
|
547
853
|
}
|
|
548
|
-
function generateTypeMethod(methodName,
|
|
854
|
+
function generateTypeMethod(componentName, methodName, selector, parameters) {
|
|
549
855
|
const name = `type${methodName}`;
|
|
856
|
+
const locatorDescription = JSON.stringify(buildPomLocatorDescription({
|
|
857
|
+
componentName,
|
|
858
|
+
methodName,
|
|
859
|
+
nativeRole: "input"
|
|
860
|
+
}));
|
|
861
|
+
const selectorParams = orderPomPatternParameters(parameters, [selector]);
|
|
550
862
|
return [
|
|
551
863
|
createAsyncMethod(
|
|
552
864
|
name,
|
|
553
|
-
|
|
554
|
-
createInlineParameter("text", { type: "string" }),
|
|
555
|
-
createInlineParameter("annotationText", { type: "string", initializer: '""' })
|
|
556
|
-
],
|
|
865
|
+
createParameters(selectorParams),
|
|
557
866
|
(writer) => {
|
|
558
|
-
writer.writeLine(`await this.fillInputByTestId(
|
|
867
|
+
writer.writeLine(`await this.fillInputByTestId(${toTypeScriptPomPatternExpression(selector)}, text, annotationText, ${locatorDescription});`);
|
|
559
868
|
}
|
|
560
869
|
)
|
|
561
870
|
];
|
|
@@ -570,62 +879,74 @@ function isAllDigits(value) {
|
|
|
570
879
|
}
|
|
571
880
|
return true;
|
|
572
881
|
}
|
|
573
|
-
function generateGetElementByDataTestId(methodName, nativeRole,
|
|
882
|
+
function generateGetElementByDataTestId(componentName, methodName, nativeRole, selector, alternateSelectors, getterNameOverride, parameters) {
|
|
883
|
+
const locatorDescription = JSON.stringify(buildPomLocatorDescription({
|
|
884
|
+
componentName,
|
|
885
|
+
methodName,
|
|
886
|
+
nativeRole
|
|
887
|
+
}));
|
|
574
888
|
const roleSuffix = upperFirst$1(nativeRole || "Element");
|
|
575
889
|
const baseName = upperFirst$1(methodName);
|
|
576
890
|
const numericSuffix = baseName.startsWith(roleSuffix) ? baseName.slice(roleSuffix.length) : "";
|
|
577
|
-
const
|
|
578
|
-
const propertyName =
|
|
579
|
-
const
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
const
|
|
891
|
+
const hasRoleSuffix2 = baseName.endsWith(roleSuffix) || baseName.startsWith(roleSuffix) && isAllDigits(numericSuffix);
|
|
892
|
+
const propertyName = hasRoleSuffix2 ? `${baseName}` : `${baseName}${roleSuffix}`;
|
|
893
|
+
const selectorParams = orderPomPatternParameters(parameters, [selector]);
|
|
894
|
+
const indexedVariable = getIndexedPomPatternVariable(selector);
|
|
895
|
+
if (indexedVariable) {
|
|
896
|
+
const keyType = getPomParameter(selectorParams, indexedVariable)?.typeExpression || "string";
|
|
897
|
+
const keyedPropertyName = getterNameOverride ?? removeByKeySegment$1(propertyName);
|
|
583
898
|
return [
|
|
584
899
|
createClassGetter({
|
|
585
900
|
name: keyedPropertyName,
|
|
586
901
|
statements: [
|
|
587
|
-
`return this.keyedLocators((
|
|
902
|
+
`return this.keyedLocators((${indexedVariable}: ${keyType}) => this.locatorByTestId(${toTypeScriptPomPatternExpression(selector)}, ${locatorDescription}));`
|
|
588
903
|
]
|
|
589
904
|
})
|
|
590
905
|
];
|
|
591
906
|
}
|
|
592
907
|
const finalPropertyName = getterNameOverride ?? propertyName;
|
|
593
|
-
const alternates =
|
|
908
|
+
const alternates = uniquePomStringPatterns(selector, alternateSelectors).slice(1);
|
|
594
909
|
if (alternates.length > 0) {
|
|
595
|
-
const all = [
|
|
596
|
-
const locatorExpr = all.map((id) => `this.locatorByTestId(${
|
|
910
|
+
const all = [selector, ...alternates];
|
|
911
|
+
const locatorExpr = all.map((id) => `this.locatorByTestId(${toTypeScriptPomPatternExpression(id)})`).reduce((acc, next) => `${acc}.or(${next})`);
|
|
597
912
|
return [
|
|
598
913
|
createClassGetter({
|
|
599
914
|
name: finalPropertyName,
|
|
600
|
-
statements: [`return ${locatorExpr};`]
|
|
915
|
+
statements: [`return this.describeLocator(${locatorExpr}, ${locatorDescription});`]
|
|
601
916
|
})
|
|
602
917
|
];
|
|
603
918
|
}
|
|
604
919
|
return [
|
|
605
920
|
createClassGetter({
|
|
606
921
|
name: finalPropertyName,
|
|
607
|
-
statements: [`return this.locatorByTestId(
|
|
922
|
+
statements: [`return this.locatorByTestId(${toTypeScriptPomPatternExpression(selector)}, ${locatorDescription});`]
|
|
608
923
|
})
|
|
609
924
|
];
|
|
610
925
|
}
|
|
611
926
|
function generateNavigationMethod(args) {
|
|
612
|
-
const { targetPageObjectModelClass: target, baseMethodName,
|
|
927
|
+
const { componentName, targetPageObjectModelClass: target, baseMethodName, selector, alternateSelectors, parameters } = args;
|
|
613
928
|
const methodName = baseMethodName ? `goTo${upperFirst$1(baseMethodName)}` : `goTo${target.endsWith("Page") ? target.slice(0, -"Page".length) : target}`;
|
|
614
|
-
const
|
|
615
|
-
|
|
616
|
-
|
|
929
|
+
const locatorDescription = JSON.stringify(buildPomLocatorDescription({
|
|
930
|
+
componentName,
|
|
931
|
+
methodName: baseMethodName,
|
|
932
|
+
nativeRole: "button"
|
|
933
|
+
}));
|
|
934
|
+
const selectorParams = orderPomPatternParameters(parameters, [selector]);
|
|
935
|
+
const methodParameters = createParameters(selectorParams);
|
|
936
|
+
const alternates = uniquePomStringPatterns(selector, alternateSelectors).slice(1);
|
|
937
|
+
const candidatesExpr = [toTypeScriptPomPatternExpression(selector), ...alternates.map((id) => toTypeScriptPomPatternExpression(id))].join(", ");
|
|
617
938
|
if (alternates.length > 0) {
|
|
618
939
|
return [
|
|
619
940
|
createClassMethod({
|
|
620
941
|
name: methodName,
|
|
621
|
-
parameters,
|
|
942
|
+
parameters: methodParameters,
|
|
622
943
|
returnType: `Fluent<${target}>`,
|
|
623
944
|
statements: (writer) => {
|
|
624
945
|
writer.write("return this.fluent(async () => ").block(() => {
|
|
625
946
|
writer.writeLine(`const candidates = [${candidatesExpr}] as const;`);
|
|
626
947
|
writer.writeLine("let lastError: unknown;");
|
|
627
948
|
writer.write("for (const testId of candidates) ").block(() => {
|
|
628
|
-
writer.writeLine(
|
|
949
|
+
writer.writeLine(`const locator = this.locatorByTestId(testId, ${locatorDescription});`);
|
|
629
950
|
writer.write("try ").block(() => {
|
|
630
951
|
writer.write("if (await locator.count()) ").block(() => {
|
|
631
952
|
writer.writeLine("await this.clickLocator(locator);");
|
|
@@ -646,11 +967,12 @@ function generateNavigationMethod(args) {
|
|
|
646
967
|
return [
|
|
647
968
|
createClassMethod({
|
|
648
969
|
name: methodName,
|
|
649
|
-
parameters,
|
|
970
|
+
parameters: methodParameters,
|
|
650
971
|
returnType: `Fluent<${target}>`,
|
|
651
972
|
statements: (writer) => {
|
|
652
973
|
writer.write("return this.fluent(async () => ").block(() => {
|
|
653
|
-
writer.writeLine(`
|
|
974
|
+
writer.writeLine(`const locator = this.locatorByTestId(${toTypeScriptPomPatternExpression(selector)}, ${locatorDescription});`);
|
|
975
|
+
writer.writeLine("await this.clickLocator(locator);");
|
|
654
976
|
writer.writeLine(`return new ${target}(this.page);`);
|
|
655
977
|
});
|
|
656
978
|
writer.writeLine(");");
|
|
@@ -658,41 +980,43 @@ function generateNavigationMethod(args) {
|
|
|
658
980
|
})
|
|
659
981
|
];
|
|
660
982
|
}
|
|
661
|
-
function generateViewObjectModelMembers(targetPageObjectModelClass, methodName, nativeRole,
|
|
983
|
+
function generateViewObjectModelMembers(componentName, targetPageObjectModelClass, methodName, nativeRole, selector, alternateSelectors, getterNameOverride, parameters) {
|
|
662
984
|
const baseMethodName = nativeRole === "radio" ? methodName || "Radio" : methodName;
|
|
663
985
|
const members = generateGetElementByDataTestId(
|
|
986
|
+
componentName,
|
|
664
987
|
baseMethodName,
|
|
665
988
|
nativeRole,
|
|
666
|
-
|
|
667
|
-
|
|
989
|
+
selector,
|
|
990
|
+
alternateSelectors,
|
|
668
991
|
getterNameOverride,
|
|
669
|
-
|
|
992
|
+
parameters
|
|
670
993
|
);
|
|
671
994
|
if (targetPageObjectModelClass) {
|
|
672
995
|
return [
|
|
673
996
|
...members,
|
|
674
997
|
...generateNavigationMethod({
|
|
998
|
+
componentName,
|
|
675
999
|
targetPageObjectModelClass,
|
|
676
1000
|
baseMethodName,
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
1001
|
+
selector,
|
|
1002
|
+
alternateSelectors,
|
|
1003
|
+
parameters
|
|
680
1004
|
})
|
|
681
1005
|
];
|
|
682
1006
|
}
|
|
683
1007
|
if (nativeRole === "select") {
|
|
684
|
-
return [...members, ...generateSelectMethod(baseMethodName,
|
|
1008
|
+
return [...members, ...generateSelectMethod(componentName, baseMethodName, selector, parameters)];
|
|
685
1009
|
}
|
|
686
1010
|
if (nativeRole === "vselect") {
|
|
687
|
-
return [...members, ...generateVSelectMethod(baseMethodName,
|
|
1011
|
+
return [...members, ...generateVSelectMethod(componentName, baseMethodName, selector, parameters)];
|
|
688
1012
|
}
|
|
689
1013
|
if (nativeRole === "input") {
|
|
690
|
-
return [...members, ...generateTypeMethod(baseMethodName,
|
|
1014
|
+
return [...members, ...generateTypeMethod(componentName, baseMethodName, selector, parameters)];
|
|
691
1015
|
}
|
|
692
1016
|
if (nativeRole === "radio") {
|
|
693
|
-
return [...members, ...generateRadioMethod(baseMethodName || "Radio",
|
|
1017
|
+
return [...members, ...generateRadioMethod(componentName, baseMethodName || "Radio", selector, parameters)];
|
|
694
1018
|
}
|
|
695
|
-
return [...members, ...generateClickMethod(baseMethodName,
|
|
1019
|
+
return [...members, ...generateClickMethod(componentName, baseMethodName, selector, alternateSelectors, parameters)];
|
|
696
1020
|
}
|
|
697
1021
|
function isSimpleExpressionNode(value) {
|
|
698
1022
|
return value !== null && "type" in value && value.type === compilerCore.NodeTypes.SIMPLE_EXPRESSION;
|
|
@@ -732,43 +1056,72 @@ function buildPlaceholderParams(keys) {
|
|
|
732
1056
|
params[k] = "__placeholder__";
|
|
733
1057
|
return params;
|
|
734
1058
|
}
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
1059
|
+
const isNodeType = (node, type) => {
|
|
1060
|
+
return node !== null && node.type === type;
|
|
1061
|
+
};
|
|
1062
|
+
const isStringLiteralNode = (node) => {
|
|
1063
|
+
return isNodeType(node, "StringLiteral") && typeof node.value === "string";
|
|
1064
|
+
};
|
|
1065
|
+
const isIdentifierNode = (node) => {
|
|
1066
|
+
return isNodeType(node, "Identifier") && typeof node.name === "string";
|
|
1067
|
+
};
|
|
1068
|
+
const isObjectPropertyNode = (node) => {
|
|
1069
|
+
if (!isNodeType(node, "ObjectProperty"))
|
|
1070
|
+
return false;
|
|
1071
|
+
const n = node;
|
|
1072
|
+
return typeof n.key === "object" && n.key !== null && typeof n.value === "object" && n.value !== null;
|
|
1073
|
+
};
|
|
1074
|
+
const isObjectExpressionNode = (node) => {
|
|
1075
|
+
if (!isNodeType(node, "ObjectExpression"))
|
|
1076
|
+
return false;
|
|
1077
|
+
const n = node;
|
|
1078
|
+
return Array.isArray(n.properties);
|
|
1079
|
+
};
|
|
1080
|
+
function materializeResolvedRouteTarget(target, paramKeys) {
|
|
1081
|
+
if (typeof target === "string")
|
|
1082
|
+
return target;
|
|
1083
|
+
if (!paramKeys.length)
|
|
1084
|
+
return target;
|
|
1085
|
+
return {
|
|
1086
|
+
...target,
|
|
1087
|
+
params: buildPlaceholderParams(paramKeys)
|
|
1088
|
+
};
|
|
1089
|
+
}
|
|
1090
|
+
function analyzeToDirectiveTarget(toDirective) {
|
|
1091
|
+
if (!toDirective.exp) {
|
|
1092
|
+
return {
|
|
1093
|
+
kind: "unsupported",
|
|
1094
|
+
rawSource: null,
|
|
1095
|
+
reason: "missing-expression"
|
|
1096
|
+
};
|
|
1097
|
+
}
|
|
1098
|
+
const rawSource = compilerCore.stringifyExpression(toDirective.exp).trim();
|
|
740
1099
|
let expr;
|
|
741
1100
|
try {
|
|
742
1101
|
expr = parser.parseExpression(rawSource, { plugins: ["typescript"] });
|
|
743
|
-
} catch {
|
|
744
|
-
return
|
|
1102
|
+
} catch (error) {
|
|
1103
|
+
return {
|
|
1104
|
+
kind: "parse-error",
|
|
1105
|
+
rawSource,
|
|
1106
|
+
reason: "parse-error",
|
|
1107
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1108
|
+
};
|
|
745
1109
|
}
|
|
746
|
-
const isNodeType = (node, type) => {
|
|
747
|
-
return node !== null && node.type === type;
|
|
748
|
-
};
|
|
749
|
-
const isStringLiteralNode = (node) => {
|
|
750
|
-
return isNodeType(node, "StringLiteral") && typeof node.value === "string";
|
|
751
|
-
};
|
|
752
|
-
const isIdentifierNode = (node) => {
|
|
753
|
-
return isNodeType(node, "Identifier") && typeof node.name === "string";
|
|
754
|
-
};
|
|
755
|
-
const isObjectPropertyNode = (node) => {
|
|
756
|
-
if (!isNodeType(node, "ObjectProperty"))
|
|
757
|
-
return false;
|
|
758
|
-
const n = node;
|
|
759
|
-
return typeof n.key === "object" && n.key !== null && typeof n.value === "object" && n.value !== null;
|
|
760
|
-
};
|
|
761
|
-
const isObjectExpressionNode = (node) => {
|
|
762
|
-
if (!isNodeType(node, "ObjectExpression"))
|
|
763
|
-
return false;
|
|
764
|
-
const n = node;
|
|
765
|
-
return Array.isArray(n.properties);
|
|
766
|
-
};
|
|
767
1110
|
if (isStringLiteralNode(expr)) {
|
|
768
|
-
return
|
|
1111
|
+
return {
|
|
1112
|
+
kind: "resolved",
|
|
1113
|
+
rawSource,
|
|
1114
|
+
target: expr.value,
|
|
1115
|
+
routeNameKey: null,
|
|
1116
|
+
paramKeys: []
|
|
1117
|
+
};
|
|
769
1118
|
}
|
|
770
1119
|
if (!isObjectExpressionNode(expr)) {
|
|
771
|
-
return
|
|
1120
|
+
return {
|
|
1121
|
+
kind: "unsupported",
|
|
1122
|
+
rawSource,
|
|
1123
|
+
reason: "dynamic-expression"
|
|
1124
|
+
};
|
|
772
1125
|
}
|
|
773
1126
|
const getStringField = (fieldName) => {
|
|
774
1127
|
const prop = expr.properties.find((p) => {
|
|
@@ -789,7 +1142,7 @@ function getRouteLocationLikeFromToDirective(toDirective) {
|
|
|
789
1142
|
const key = p.key;
|
|
790
1143
|
return isIdentifierNode(key) && key.name === "params" || isStringLiteralNode(key) && key.value === "params";
|
|
791
1144
|
});
|
|
792
|
-
let
|
|
1145
|
+
let paramKeys = [];
|
|
793
1146
|
if (paramsProp && isObjectPropertyNode(paramsProp) && isObjectExpressionNode(paramsProp.value)) {
|
|
794
1147
|
const keys = [];
|
|
795
1148
|
for (const prop of paramsProp.value.properties) {
|
|
@@ -801,47 +1154,50 @@ function getRouteLocationLikeFromToDirective(toDirective) {
|
|
|
801
1154
|
else if (isStringLiteralNode(key))
|
|
802
1155
|
keys.push(key.value);
|
|
803
1156
|
}
|
|
804
|
-
|
|
805
|
-
params = buildPlaceholderParams(Array.from(new Set(keys)));
|
|
806
|
-
}
|
|
1157
|
+
paramKeys = Array.from(new Set(keys));
|
|
807
1158
|
}
|
|
808
1159
|
if (name) {
|
|
809
|
-
|
|
1160
|
+
const trimmed = name.trim();
|
|
1161
|
+
if (!trimmed.length) {
|
|
1162
|
+
return {
|
|
1163
|
+
kind: "unsupported",
|
|
1164
|
+
rawSource,
|
|
1165
|
+
reason: "missing-name-or-path"
|
|
1166
|
+
};
|
|
1167
|
+
}
|
|
1168
|
+
return {
|
|
1169
|
+
kind: "resolved",
|
|
1170
|
+
rawSource,
|
|
1171
|
+
target: { name },
|
|
1172
|
+
routeNameKey: toPascalCaseRouteKey(trimmed),
|
|
1173
|
+
paramKeys
|
|
1174
|
+
};
|
|
810
1175
|
}
|
|
811
1176
|
if (path2) {
|
|
812
|
-
return {
|
|
1177
|
+
return {
|
|
1178
|
+
kind: "resolved",
|
|
1179
|
+
rawSource,
|
|
1180
|
+
target: { path: path2 },
|
|
1181
|
+
routeNameKey: null,
|
|
1182
|
+
paramKeys
|
|
1183
|
+
};
|
|
813
1184
|
}
|
|
814
|
-
return
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
return null;
|
|
820
|
-
const name = to.name;
|
|
821
|
-
if (typeof name !== "string")
|
|
822
|
-
return null;
|
|
823
|
-
const trimmed = name.trim();
|
|
824
|
-
if (!trimmed.length)
|
|
825
|
-
return null;
|
|
826
|
-
return toPascalCaseRouteKey(trimmed);
|
|
827
|
-
}
|
|
828
|
-
function getRouteNameKeyFromToDirective(toDirective) {
|
|
829
|
-
const objectName = toDirectiveObjectFieldNameValue$1(toDirective);
|
|
830
|
-
if (objectName)
|
|
831
|
-
return objectName;
|
|
832
|
-
return null;
|
|
1185
|
+
return {
|
|
1186
|
+
kind: "unsupported",
|
|
1187
|
+
rawSource,
|
|
1188
|
+
reason: "missing-name-or-path"
|
|
1189
|
+
};
|
|
833
1190
|
}
|
|
834
1191
|
function tryResolveToDirectiveTargetComponentName(toDirective) {
|
|
835
|
-
const
|
|
836
|
-
if (
|
|
837
|
-
const resolved = resolveToComponentName(
|
|
1192
|
+
const analysis = analyzeToDirectiveTarget(toDirective);
|
|
1193
|
+
if (analysis.kind === "resolved" && resolveToComponentName) {
|
|
1194
|
+
const resolved = resolveToComponentName(materializeResolvedRouteTarget(analysis.target, analysis.paramKeys));
|
|
838
1195
|
if (resolved)
|
|
839
1196
|
return resolved;
|
|
840
1197
|
}
|
|
841
|
-
|
|
842
|
-
if (!key || !routeNameToComponentName)
|
|
1198
|
+
if (analysis.kind !== "resolved" || !analysis.routeNameKey || !routeNameToComponentName)
|
|
843
1199
|
return null;
|
|
844
|
-
return routeNameToComponentName.get(
|
|
1200
|
+
return routeNameToComponentName.get(analysis.routeNameKey) ?? null;
|
|
845
1201
|
}
|
|
846
1202
|
function getDataTestIdFromGroupOption(text) {
|
|
847
1203
|
return text.replace(/[-_]/g, " ").split(" ").filter((a) => a).map((str) => {
|
|
@@ -876,11 +1232,81 @@ function staticAttributeValue(value) {
|
|
|
876
1232
|
return { kind: "static", value };
|
|
877
1233
|
}
|
|
878
1234
|
function templateAttributeValue(template) {
|
|
879
|
-
|
|
1235
|
+
const parsedTemplate = tryParseTemplateFragment(template);
|
|
1236
|
+
if (!parsedTemplate) {
|
|
1237
|
+
throw new Error(`[vue-pom-generator] Failed to parse generated template fragment: ${template}`);
|
|
1238
|
+
}
|
|
1239
|
+
return { kind: "template", template, parsedTemplate };
|
|
880
1240
|
}
|
|
881
1241
|
function getAttributeValueText(value) {
|
|
882
1242
|
return value.kind === "static" ? value.value : value.template;
|
|
883
1243
|
}
|
|
1244
|
+
function getVueExpressionSource(expression, ...preferredViews) {
|
|
1245
|
+
if (!expression) {
|
|
1246
|
+
return "";
|
|
1247
|
+
}
|
|
1248
|
+
for (const view of preferredViews) {
|
|
1249
|
+
let value = "";
|
|
1250
|
+
switch (view) {
|
|
1251
|
+
case "content":
|
|
1252
|
+
value = expression.type === compilerCore.NodeTypes.SIMPLE_EXPRESSION ? expression.content : "";
|
|
1253
|
+
break;
|
|
1254
|
+
case "loc":
|
|
1255
|
+
value = expression.loc?.source ?? "";
|
|
1256
|
+
break;
|
|
1257
|
+
case "compiled":
|
|
1258
|
+
try {
|
|
1259
|
+
value = compilerCore.stringifyExpression(expression);
|
|
1260
|
+
} catch {
|
|
1261
|
+
value = "";
|
|
1262
|
+
}
|
|
1263
|
+
break;
|
|
1264
|
+
default:
|
|
1265
|
+
value = "";
|
|
1266
|
+
break;
|
|
1267
|
+
}
|
|
1268
|
+
const trimmed = value.trim();
|
|
1269
|
+
if (trimmed) {
|
|
1270
|
+
return trimmed;
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
return "";
|
|
1274
|
+
}
|
|
1275
|
+
function tryGetExistingVueExpressionAst(expression) {
|
|
1276
|
+
if (!expression) {
|
|
1277
|
+
return null;
|
|
1278
|
+
}
|
|
1279
|
+
const ast = "ast" in expression ? expression.ast : null;
|
|
1280
|
+
return ast && "type" in ast ? ast : null;
|
|
1281
|
+
}
|
|
1282
|
+
function tryParseBabelExpressionFromSource(source, plugins) {
|
|
1283
|
+
const trimmed = source.trim();
|
|
1284
|
+
if (!trimmed) {
|
|
1285
|
+
return null;
|
|
1286
|
+
}
|
|
1287
|
+
try {
|
|
1288
|
+
return parser.parseExpression(trimmed, { plugins });
|
|
1289
|
+
} catch {
|
|
1290
|
+
return null;
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
function tryGetVueExpressionAst(expression, options) {
|
|
1294
|
+
if (!expression) {
|
|
1295
|
+
return null;
|
|
1296
|
+
}
|
|
1297
|
+
if (options?.preferExistingAst !== false) {
|
|
1298
|
+
const existingAst = tryGetExistingVueExpressionAst(expression);
|
|
1299
|
+
if (existingAst) {
|
|
1300
|
+
return existingAst;
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
const source = getVueExpressionSource(expression, ...options?.preferredViews ?? ["content", "loc", "compiled"]);
|
|
1304
|
+
return source ? tryParseBabelExpressionFromSource(source, options?.plugins ?? ["typescript"]) : null;
|
|
1305
|
+
}
|
|
1306
|
+
function tryGetDirectiveBabelAst(directive, options) {
|
|
1307
|
+
const exp = directive.exp && (directive.exp.type === compilerCore.NodeTypes.SIMPLE_EXPRESSION || directive.exp.type === compilerCore.NodeTypes.COMPOUND_EXPRESSION) ? directive.exp : null;
|
|
1308
|
+
return tryGetVueExpressionAst(exp, options);
|
|
1309
|
+
}
|
|
884
1310
|
function toPascalCase(str) {
|
|
885
1311
|
const cleaned = (str ?? "").replace(/\$\{[^}]*\}/g, " ").replace(/[^a-z0-9]+/gi, " ").trim();
|
|
886
1312
|
if (!cleaned) {
|
|
@@ -922,32 +1348,14 @@ function tryGetClickDirective(node) {
|
|
|
922
1348
|
function nodeHasClickDirective(node) {
|
|
923
1349
|
return tryGetClickDirective(node) !== void 0;
|
|
924
1350
|
}
|
|
925
|
-
function
|
|
1351
|
+
function findTemplateSlotScopeExpression(node) {
|
|
926
1352
|
if (node.tag !== "template") {
|
|
927
1353
|
return null;
|
|
928
1354
|
}
|
|
929
1355
|
const slotProp = node.props.find((prop) => {
|
|
930
1356
|
return prop.type === compilerCore.NodeTypes.DIRECTIVE && prop.name === "slot";
|
|
931
1357
|
});
|
|
932
|
-
|
|
933
|
-
if (slotProp.exp.type === compilerCore.NodeTypes.SIMPLE_EXPRESSION) {
|
|
934
|
-
return slotProp.exp.content;
|
|
935
|
-
}
|
|
936
|
-
if (slotProp.exp.type === compilerCore.NodeTypes.COMPOUND_EXPRESSION) {
|
|
937
|
-
return compilerCore.stringifyExpression(slotProp.exp);
|
|
938
|
-
}
|
|
939
|
-
}
|
|
940
|
-
return null;
|
|
941
|
-
}
|
|
942
|
-
function isSimpleScopeIdentifier(value) {
|
|
943
|
-
if (!value) {
|
|
944
|
-
return false;
|
|
945
|
-
}
|
|
946
|
-
try {
|
|
947
|
-
return types.isIdentifier(parser.parseExpression(value, { plugins: ["typescript"] }));
|
|
948
|
-
} catch {
|
|
949
|
-
return false;
|
|
950
|
-
}
|
|
1358
|
+
return slotProp?.exp && (slotProp.exp.type === compilerCore.NodeTypes.SIMPLE_EXPRESSION || slotProp.exp.type === compilerCore.NodeTypes.COMPOUND_EXPRESSION) ? slotProp.exp : null;
|
|
951
1359
|
}
|
|
952
1360
|
function buildSlotScopeFallbackKeyExpression(identifier) {
|
|
953
1361
|
return `${identifier}.key ?? ${identifier}.data?.id ?? ${identifier}.id ?? ${identifier}.value ?? ${identifier}`;
|
|
@@ -1049,43 +1457,64 @@ function tryGetSlotScopeKeyCandidate(node) {
|
|
|
1049
1457
|
}
|
|
1050
1458
|
return best;
|
|
1051
1459
|
}
|
|
1052
|
-
function
|
|
1053
|
-
const
|
|
1054
|
-
if (
|
|
1460
|
+
function tryGetTemplateSlotScopeBindingNode(expression) {
|
|
1461
|
+
const ast = tryGetExistingVueExpressionAst(expression);
|
|
1462
|
+
if (ast) {
|
|
1463
|
+
if (types.isArrowFunctionExpression(ast)) {
|
|
1464
|
+
return ast.params[0] ?? null;
|
|
1465
|
+
}
|
|
1466
|
+
return ast;
|
|
1467
|
+
}
|
|
1468
|
+
const rawSource = getVueExpressionSource(expression, "content", "loc", "compiled");
|
|
1469
|
+
if (!rawSource) {
|
|
1055
1470
|
return null;
|
|
1056
1471
|
}
|
|
1057
|
-
|
|
1058
|
-
return
|
|
1472
|
+
try {
|
|
1473
|
+
return parser.parseExpression(rawSource, { plugins: ["typescript"] });
|
|
1474
|
+
} catch {
|
|
1059
1475
|
}
|
|
1060
1476
|
try {
|
|
1061
|
-
const parsed = parser.parse(`(${
|
|
1477
|
+
const parsed = parser.parse(`(${rawSource}) => {}`, {
|
|
1062
1478
|
sourceType: "module",
|
|
1063
1479
|
plugins: ["typescript"]
|
|
1064
1480
|
});
|
|
1065
1481
|
const statement = parsed.program.body[0];
|
|
1066
1482
|
if (statement && types.isExpressionStatement(statement) && types.isArrowFunctionExpression(statement.expression)) {
|
|
1067
|
-
return
|
|
1483
|
+
return statement.expression.params[0] ?? null;
|
|
1068
1484
|
}
|
|
1069
1485
|
} catch {
|
|
1486
|
+
return null;
|
|
1070
1487
|
}
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
} else if (colonIdx !== -1) {
|
|
1081
|
-
cutIdx = colonIdx;
|
|
1082
|
-
}
|
|
1083
|
-
const first = (cutIdx === -1 ? inner : inner.slice(0, cutIdx)).trim();
|
|
1084
|
-
if (first && isSimpleScopeIdentifier(first)) {
|
|
1085
|
-
return buildSlotScopeFallbackKeyExpression(first);
|
|
1086
|
-
}
|
|
1488
|
+
return null;
|
|
1489
|
+
}
|
|
1490
|
+
function toResolvedTemplateFragment(source) {
|
|
1491
|
+
const templateLiteral = tryUnwrapTemplateLiteralSource(source);
|
|
1492
|
+
if (templateLiteral) {
|
|
1493
|
+
return {
|
|
1494
|
+
template: templateLiteral.template,
|
|
1495
|
+
rawExpression: null
|
|
1496
|
+
};
|
|
1087
1497
|
}
|
|
1088
|
-
return
|
|
1498
|
+
return toInterpolatedTemplateFragment(source);
|
|
1499
|
+
}
|
|
1500
|
+
function toResolvedKeyInfo(selectorSource, runtimeSource = selectorSource) {
|
|
1501
|
+
const selectorFragment = selectorSource ? toResolvedTemplateFragment(selectorSource) : null;
|
|
1502
|
+
const runtimeFragment = runtimeSource ? toResolvedTemplateFragment(runtimeSource) : null;
|
|
1503
|
+
const selectorTemplate = selectorFragment?.template ?? runtimeFragment?.template ?? null;
|
|
1504
|
+
const runtimeTemplate = runtimeFragment?.template ?? selectorFragment?.template ?? null;
|
|
1505
|
+
if (!selectorTemplate || !runtimeTemplate) {
|
|
1506
|
+
return null;
|
|
1507
|
+
}
|
|
1508
|
+
return {
|
|
1509
|
+
selectorFragment: selectorTemplate,
|
|
1510
|
+
runtimeFragment: runtimeTemplate,
|
|
1511
|
+
rawExpression: runtimeFragment?.rawExpression ?? selectorFragment?.rawExpression ?? null
|
|
1512
|
+
};
|
|
1513
|
+
}
|
|
1514
|
+
function tryGetTemplateSlotScopeKeyInfo(expression) {
|
|
1515
|
+
const bindingNode = tryGetTemplateSlotScopeBindingNode(expression);
|
|
1516
|
+
const candidateExpression = bindingNode ? tryGetSlotScopeKeyCandidate(bindingNode)?.expression ?? null : null;
|
|
1517
|
+
return candidateExpression ? toResolvedKeyInfo(candidateExpression) : null;
|
|
1089
1518
|
}
|
|
1090
1519
|
function nodeHasToDirective(node) {
|
|
1091
1520
|
const toDirective = findDirectiveByName(node, "bind", "to");
|
|
@@ -1094,51 +1523,160 @@ function nodeHasToDirective(node) {
|
|
|
1094
1523
|
}
|
|
1095
1524
|
return void 0;
|
|
1096
1525
|
}
|
|
1097
|
-
function nodeHasForDirective(node) {
|
|
1098
|
-
return node.props.some(
|
|
1099
|
-
(attr) => attr.type === compilerCore.NodeTypes.DIRECTIVE && attr.name === "for"
|
|
1100
|
-
);
|
|
1101
|
-
}
|
|
1102
1526
|
function getKeyDirective(node) {
|
|
1103
1527
|
return findDirectiveByName(node, "bind", "key") ?? null;
|
|
1104
1528
|
}
|
|
1105
|
-
function
|
|
1106
|
-
const
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
return `\${${rawSource}}`;
|
|
1529
|
+
function tryUnwrapTemplateLiteralSource(source) {
|
|
1530
|
+
const rawSource = source.trim();
|
|
1531
|
+
if (!rawSource) {
|
|
1532
|
+
return null;
|
|
1110
1533
|
}
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1534
|
+
let ast = null;
|
|
1535
|
+
try {
|
|
1536
|
+
ast = parser.parseExpression(rawSource, { plugins: ["typescript"] });
|
|
1537
|
+
} catch {
|
|
1538
|
+
return null;
|
|
1115
1539
|
}
|
|
1116
|
-
|
|
1540
|
+
if (!ast || !types.isTemplateLiteral(ast)) {
|
|
1541
|
+
return null;
|
|
1542
|
+
}
|
|
1543
|
+
const cooked = ast.quasis.map((quasi) => quasi.value.cooked ?? "").join("");
|
|
1544
|
+
try {
|
|
1545
|
+
const start = typeof ast.start === "number" ? ast.start + 1 : 1;
|
|
1546
|
+
const end = typeof ast.end === "number" ? ast.end - 1 : rawSource.length - 1;
|
|
1547
|
+
return {
|
|
1548
|
+
template: rawSource.slice(start, end) || cooked,
|
|
1549
|
+
expressionCount: ast.expressions.length
|
|
1550
|
+
};
|
|
1551
|
+
} catch {
|
|
1552
|
+
return {
|
|
1553
|
+
template: cooked,
|
|
1554
|
+
expressionCount: ast.expressions.length
|
|
1555
|
+
};
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
function tryUnwrapTemplateLiteralExpressionSource(expression) {
|
|
1559
|
+
const rawSource = getVueExpressionSource(expression, "loc", "compiled");
|
|
1560
|
+
return rawSource ? tryUnwrapTemplateLiteralSource(rawSource) : null;
|
|
1561
|
+
}
|
|
1562
|
+
function tryParseTemplateFragment(fragment) {
|
|
1563
|
+
if (!fragment) {
|
|
1564
|
+
return null;
|
|
1565
|
+
}
|
|
1566
|
+
try {
|
|
1567
|
+
const source = `\`${fragment}\``;
|
|
1568
|
+
const ast = parser.parseExpression(source, { plugins: ["typescript"] });
|
|
1569
|
+
return types.isTemplateLiteral(ast) ? { source, templateLiteral: ast } : null;
|
|
1570
|
+
} catch {
|
|
1571
|
+
return null;
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
function getTemplateExpressionSource(parsedTemplate, index) {
|
|
1575
|
+
const expression = parsedTemplate.templateLiteral.expressions[index];
|
|
1576
|
+
if (!expression) {
|
|
1577
|
+
return null;
|
|
1578
|
+
}
|
|
1579
|
+
const start = typeof expression.start === "number" ? expression.start : null;
|
|
1580
|
+
const end = typeof expression.end === "number" ? expression.end : null;
|
|
1581
|
+
if (start === null || end === null) {
|
|
1582
|
+
return null;
|
|
1583
|
+
}
|
|
1584
|
+
return parsedTemplate.source.slice(start, end);
|
|
1585
|
+
}
|
|
1586
|
+
function getSingleExpressionTemplateFragment(parsedTemplate) {
|
|
1587
|
+
const { templateLiteral } = parsedTemplate;
|
|
1588
|
+
if (templateLiteral.expressions.length !== 1 || templateLiteral.quasis.length !== 2) {
|
|
1589
|
+
return null;
|
|
1590
|
+
}
|
|
1591
|
+
const expressionSource = getTemplateExpressionSource(parsedTemplate, 0);
|
|
1592
|
+
if (expressionSource === null) {
|
|
1593
|
+
return null;
|
|
1594
|
+
}
|
|
1595
|
+
return {
|
|
1596
|
+
prefix: templateLiteral.quasis[0]?.value.raw ?? "",
|
|
1597
|
+
expressionSource,
|
|
1598
|
+
suffix: templateLiteral.quasis[1]?.value.raw ?? ""
|
|
1599
|
+
};
|
|
1600
|
+
}
|
|
1601
|
+
function templateFragmentContainsSingleExpression(container, candidate) {
|
|
1602
|
+
const containerFragment = getSingleExpressionTemplateFragment(container);
|
|
1603
|
+
const candidateFragment = getSingleExpressionTemplateFragment(candidate);
|
|
1604
|
+
if (!containerFragment || !candidateFragment) {
|
|
1605
|
+
return false;
|
|
1606
|
+
}
|
|
1607
|
+
return containerFragment.expressionSource === candidateFragment.expressionSource && containerFragment.prefix.endsWith(candidateFragment.prefix) && containerFragment.suffix.startsWith(candidateFragment.suffix);
|
|
1608
|
+
}
|
|
1609
|
+
function hasTemplateInterpolationExpressions(fragment) {
|
|
1610
|
+
return (tryParseTemplateFragment(fragment)?.templateLiteral.expressions.length ?? 0) > 0;
|
|
1611
|
+
}
|
|
1612
|
+
function toInterpolatedTemplateFragment(fragment) {
|
|
1613
|
+
if (!fragment) {
|
|
1614
|
+
return null;
|
|
1615
|
+
}
|
|
1616
|
+
if (hasTemplateInterpolationExpressions(fragment)) {
|
|
1617
|
+
return { template: fragment, rawExpression: null };
|
|
1618
|
+
}
|
|
1619
|
+
return {
|
|
1620
|
+
template: `\${${fragment}}`,
|
|
1621
|
+
rawExpression: fragment
|
|
1622
|
+
};
|
|
1623
|
+
}
|
|
1624
|
+
function renderTemplateLiteralExpression(templateValue) {
|
|
1625
|
+
const templateLiteralSource = templateValue.parsedTemplate.source;
|
|
1626
|
+
const templateLiteral = templateValue.parsedTemplate.templateLiteral;
|
|
1627
|
+
const writer = createTypeScriptWriter();
|
|
1628
|
+
writer.write("`");
|
|
1629
|
+
for (let i = 0; i < templateLiteral.quasis.length; i += 1) {
|
|
1630
|
+
writer.write(templateLiteral.quasis[i]?.value.raw ?? "");
|
|
1631
|
+
const interpolation = templateLiteral.expressions[i];
|
|
1632
|
+
if (!interpolation) {
|
|
1633
|
+
continue;
|
|
1634
|
+
}
|
|
1635
|
+
const start = typeof interpolation.start === "number" ? interpolation.start : null;
|
|
1636
|
+
const end = typeof interpolation.end === "number" ? interpolation.end : null;
|
|
1637
|
+
if (start === null || end === null) {
|
|
1638
|
+
return templateLiteralSource;
|
|
1639
|
+
}
|
|
1640
|
+
writer.write("${");
|
|
1641
|
+
writer.write(templateLiteralSource.slice(start, end));
|
|
1642
|
+
writer.write("}");
|
|
1643
|
+
}
|
|
1644
|
+
writer.write("`");
|
|
1645
|
+
return writer.toString();
|
|
1646
|
+
}
|
|
1647
|
+
function getKeyDirectiveExpression(node) {
|
|
1648
|
+
const keyDirective = getKeyDirective(node);
|
|
1649
|
+
return keyDirective?.exp && (keyDirective.exp.type === compilerCore.NodeTypes.SIMPLE_EXPRESSION || keyDirective.exp.type === compilerCore.NodeTypes.COMPOUND_EXPRESSION) ? keyDirective.exp : null;
|
|
1650
|
+
}
|
|
1651
|
+
function getKeyDirectiveInfo(node) {
|
|
1652
|
+
const keyExpression = getKeyDirectiveExpression(node);
|
|
1653
|
+
if (!keyExpression) {
|
|
1654
|
+
return null;
|
|
1655
|
+
}
|
|
1656
|
+
const selectorSource = getVueExpressionSource(keyExpression, "compiled", "loc");
|
|
1657
|
+
const runtimeSource = getVueExpressionSource(keyExpression, "loc", "compiled");
|
|
1658
|
+
return toResolvedKeyInfo(selectorSource, runtimeSource);
|
|
1117
1659
|
}
|
|
1118
1660
|
function getModelBindingValues(node) {
|
|
1119
1661
|
let vModel = "";
|
|
1120
1662
|
const vModelDirective = findDirectiveByName(node, "model");
|
|
1121
|
-
if (vModelDirective?.exp
|
|
1122
|
-
vModel = toPascalCase(vModelDirective.exp
|
|
1663
|
+
if (vModelDirective?.exp && (vModelDirective.exp.type === compilerCore.NodeTypes.SIMPLE_EXPRESSION || vModelDirective.exp.type === compilerCore.NodeTypes.COMPOUND_EXPRESSION)) {
|
|
1664
|
+
vModel = toPascalCase(getVueExpressionSource(vModelDirective.exp, "loc", "content"));
|
|
1123
1665
|
}
|
|
1124
1666
|
let modelValue = null;
|
|
1125
1667
|
const modelValueDirective = findDirectiveByName(node, "bind", "modelValue");
|
|
1126
|
-
|
|
1127
|
-
|
|
1668
|
+
const modelValueAst = modelValueDirective ? tryGetDirectiveBabelAst(modelValueDirective, {
|
|
1669
|
+
preferredViews: ["loc", "compiled"],
|
|
1670
|
+
plugins: ["typescript"],
|
|
1671
|
+
preferExistingAst: false
|
|
1672
|
+
}) : null;
|
|
1673
|
+
if (modelValueAst) {
|
|
1674
|
+
const { name: mv } = getClickHandlerNameFromAst(modelValueAst);
|
|
1128
1675
|
modelValue = mv;
|
|
1129
1676
|
}
|
|
1130
1677
|
return { vModel, modelValue };
|
|
1131
1678
|
}
|
|
1132
|
-
function
|
|
1133
|
-
if (node.isSelfClosing) {
|
|
1134
|
-
const hasForDirective = nodeHasForDirective(node);
|
|
1135
|
-
if (hasForDirective) {
|
|
1136
|
-
return getKeyDirectiveValue(node);
|
|
1137
|
-
}
|
|
1138
|
-
}
|
|
1139
|
-
return null;
|
|
1140
|
-
}
|
|
1141
|
-
function getIdOrName(node) {
|
|
1679
|
+
function getStaticIdOrNameHint(node) {
|
|
1142
1680
|
let idAttr = findAttributeByKey(node, "id");
|
|
1143
1681
|
if (!idAttr) {
|
|
1144
1682
|
idAttr = findAttributeByKey(node, "name");
|
|
@@ -1146,8 +1684,9 @@ function getIdOrName(node) {
|
|
|
1146
1684
|
let identifier = idAttr?.value?.content ?? "";
|
|
1147
1685
|
if (!identifier) {
|
|
1148
1686
|
const dynamicIdAttr = findDirectiveByName(node, "bind", "id");
|
|
1149
|
-
|
|
1150
|
-
|
|
1687
|
+
const dynamicNameAttr = findDirectiveByName(node, "bind", "name");
|
|
1688
|
+
if (dynamicIdAttr?.exp || dynamicNameAttr?.exp) {
|
|
1689
|
+
return "";
|
|
1151
1690
|
}
|
|
1152
1691
|
}
|
|
1153
1692
|
if (identifier.includes("-")) {
|
|
@@ -1158,20 +1697,20 @@ function getIdOrName(node) {
|
|
|
1158
1697
|
}
|
|
1159
1698
|
return identifier;
|
|
1160
1699
|
}
|
|
1161
|
-
function
|
|
1700
|
+
function getContainedInSlotDataKeyInfo(node, hierarchyMap2) {
|
|
1162
1701
|
let parent = getParent(hierarchyMap2, node);
|
|
1163
1702
|
while (parent) {
|
|
1164
1703
|
if (parent.type === compilerCore.NodeTypes.ELEMENT && parent.tag === "template") {
|
|
1165
|
-
const
|
|
1166
|
-
if (
|
|
1167
|
-
return
|
|
1704
|
+
const slotScopeExpression = findTemplateSlotScopeExpression(parent);
|
|
1705
|
+
if (slotScopeExpression) {
|
|
1706
|
+
return tryGetTemplateSlotScopeKeyInfo(slotScopeExpression);
|
|
1168
1707
|
}
|
|
1169
1708
|
}
|
|
1170
1709
|
parent = getParent(hierarchyMap2, parent);
|
|
1171
1710
|
}
|
|
1172
1711
|
return null;
|
|
1173
1712
|
}
|
|
1174
|
-
function
|
|
1713
|
+
function getContainedInVForDirectiveKeyInfo(context, node, hierarchyMap2) {
|
|
1175
1714
|
if (!context.scopes.vFor || context.scopes.vFor === 0) {
|
|
1176
1715
|
return null;
|
|
1177
1716
|
}
|
|
@@ -1180,8 +1719,7 @@ function getContainedInVForDirectiveKeyValue(context, node, hierarchyMap2) {
|
|
|
1180
1719
|
if (parent.type === compilerCore.NodeTypes.ELEMENT) {
|
|
1181
1720
|
const forDirective = findDirectiveByName(parent, "for");
|
|
1182
1721
|
if (forDirective) {
|
|
1183
|
-
|
|
1184
|
-
return keyValue;
|
|
1722
|
+
return getKeyDirectiveInfo(parent);
|
|
1185
1723
|
}
|
|
1186
1724
|
}
|
|
1187
1725
|
parent = getParent(hierarchyMap2, parent);
|
|
@@ -1207,13 +1745,7 @@ function tryGetContainedInStaticVForSourceLiteralValues(context, _node, _hierarc
|
|
|
1207
1745
|
if (simpleSourceExp.constType === compilerCore.ConstantTypes.NOT_CONSTANT) {
|
|
1208
1746
|
return null;
|
|
1209
1747
|
}
|
|
1210
|
-
const iterableRaw = (
|
|
1211
|
-
try {
|
|
1212
|
-
return compilerCore.stringifyExpression(simpleSourceExp).trim();
|
|
1213
|
-
} catch {
|
|
1214
|
-
return (simpleSourceExp.loc?.source ?? "").trim();
|
|
1215
|
-
}
|
|
1216
|
-
})();
|
|
1748
|
+
const iterableRaw = getVueExpressionSource(simpleSourceExp, "compiled", "loc");
|
|
1217
1749
|
if (!iterableRaw) {
|
|
1218
1750
|
return null;
|
|
1219
1751
|
}
|
|
@@ -1269,91 +1801,95 @@ function nodeHandlerAttributeInfo(node) {
|
|
|
1269
1801
|
return null;
|
|
1270
1802
|
}
|
|
1271
1803
|
const exp = handlerDirective.exp;
|
|
1272
|
-
const source = (exp
|
|
1804
|
+
const source = getVueExpressionSource(exp, "content", "compiled");
|
|
1273
1805
|
if (!source) {
|
|
1274
1806
|
return null;
|
|
1275
1807
|
}
|
|
1276
1808
|
const mergeKey = `handler:expr:${source}`;
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1809
|
+
const expr = tryGetDirectiveBabelAst(handlerDirective, {
|
|
1810
|
+
preferredViews: ["content", "compiled"],
|
|
1811
|
+
plugins: ["typescript", "jsx"],
|
|
1812
|
+
// Vue's compiler AST can encode `_ctx.foo` as an Identifier name instead of a MemberExpression.
|
|
1813
|
+
// That is fine for Vue codegen, but our semantic-name extraction needs a normal Babel parse tree.
|
|
1814
|
+
preferExistingAst: false
|
|
1815
|
+
});
|
|
1816
|
+
if (!expr) {
|
|
1281
1817
|
return null;
|
|
1282
1818
|
}
|
|
1283
|
-
const
|
|
1819
|
+
const isNodeType2 = (node2, type) => {
|
|
1284
1820
|
return node2 !== null && node2.type === type;
|
|
1285
1821
|
};
|
|
1286
|
-
const
|
|
1287
|
-
return
|
|
1822
|
+
const isIdentifierNode2 = (node2) => {
|
|
1823
|
+
return isNodeType2(node2, "Identifier") && typeof node2.name === "string";
|
|
1288
1824
|
};
|
|
1289
|
-
const
|
|
1290
|
-
return
|
|
1825
|
+
const isStringLiteralNode2 = (node2) => {
|
|
1826
|
+
return isNodeType2(node2, "StringLiteral") && typeof node2.value === "string";
|
|
1291
1827
|
};
|
|
1292
1828
|
const isBooleanLiteralNode = (node2) => {
|
|
1293
|
-
return
|
|
1829
|
+
return isNodeType2(node2, "BooleanLiteral") && typeof node2.value === "boolean";
|
|
1294
1830
|
};
|
|
1295
1831
|
const isNumericLiteralNode = (node2) => {
|
|
1296
|
-
return
|
|
1832
|
+
return isNodeType2(node2, "NumericLiteral") && typeof node2.value === "number";
|
|
1297
1833
|
};
|
|
1298
1834
|
const isNullLiteralNode = (node2) => {
|
|
1299
|
-
return
|
|
1835
|
+
return isNodeType2(node2, "NullLiteral");
|
|
1300
1836
|
};
|
|
1301
1837
|
const isMemberExpressionNode = (node2) => {
|
|
1302
|
-
if (!
|
|
1838
|
+
if (!isNodeType2(node2, "MemberExpression"))
|
|
1303
1839
|
return false;
|
|
1304
1840
|
const n = node2;
|
|
1305
1841
|
return typeof n.computed === "boolean" && typeof n.object === "object" && n.object !== null && typeof n.property === "object" && n.property !== null;
|
|
1306
1842
|
};
|
|
1307
1843
|
const isCallExpressionNode = (node2) => {
|
|
1308
|
-
if (!
|
|
1844
|
+
if (!isNodeType2(node2, "CallExpression"))
|
|
1309
1845
|
return false;
|
|
1310
1846
|
const n = node2;
|
|
1311
1847
|
return typeof n.callee === "object" && n.callee !== null && Array.isArray(n.arguments);
|
|
1312
1848
|
};
|
|
1313
1849
|
const isAwaitExpressionNode = (node2) => {
|
|
1314
|
-
if (!
|
|
1850
|
+
if (!isNodeType2(node2, "AwaitExpression"))
|
|
1315
1851
|
return false;
|
|
1316
1852
|
const n = node2;
|
|
1317
1853
|
return typeof n.argument === "object" && n.argument !== null;
|
|
1318
1854
|
};
|
|
1319
1855
|
const isAssignmentExpressionNode = (node2) => {
|
|
1320
|
-
if (!
|
|
1856
|
+
if (!isNodeType2(node2, "AssignmentExpression"))
|
|
1321
1857
|
return false;
|
|
1322
1858
|
const n = node2;
|
|
1323
1859
|
return typeof n.left === "object" && n.left !== null && typeof n.right === "object" && n.right !== null;
|
|
1324
1860
|
};
|
|
1325
1861
|
const isArrowFunctionExpressionNode = (node2) => {
|
|
1326
|
-
if (!
|
|
1862
|
+
if (!isNodeType2(node2, "ArrowFunctionExpression"))
|
|
1327
1863
|
return false;
|
|
1328
1864
|
const n = node2;
|
|
1329
1865
|
return typeof n.body === "object" && n.body !== null;
|
|
1330
1866
|
};
|
|
1331
1867
|
const isBlockStatementNode = (node2) => {
|
|
1332
|
-
if (!
|
|
1868
|
+
if (!isNodeType2(node2, "BlockStatement"))
|
|
1333
1869
|
return false;
|
|
1334
1870
|
const n = node2;
|
|
1335
1871
|
return Array.isArray(n.body);
|
|
1336
1872
|
};
|
|
1337
1873
|
const isExpressionStatementNode = (node2) => {
|
|
1338
|
-
if (!
|
|
1874
|
+
if (!isNodeType2(node2, "ExpressionStatement"))
|
|
1339
1875
|
return false;
|
|
1340
1876
|
const n = node2;
|
|
1341
1877
|
return typeof n.expression === "object" && n.expression !== null;
|
|
1342
1878
|
};
|
|
1343
1879
|
const isReturnStatementNode = (node2) => {
|
|
1344
|
-
if (!
|
|
1880
|
+
if (!isNodeType2(node2, "ReturnStatement"))
|
|
1345
1881
|
return false;
|
|
1346
1882
|
const n = node2;
|
|
1347
1883
|
return typeof n.argument === "object" || n.argument === null;
|
|
1348
1884
|
};
|
|
1349
|
-
const
|
|
1350
|
-
if (!
|
|
1885
|
+
const isObjectExpressionNode2 = (node2) => {
|
|
1886
|
+
if (!isNodeType2(node2, "ObjectExpression"))
|
|
1351
1887
|
return false;
|
|
1352
1888
|
const n = node2;
|
|
1353
1889
|
return Array.isArray(n.properties);
|
|
1354
1890
|
};
|
|
1355
|
-
const
|
|
1356
|
-
if (!
|
|
1891
|
+
const isObjectPropertyNode2 = (node2) => {
|
|
1892
|
+
if (!isNodeType2(node2, "ObjectProperty"))
|
|
1357
1893
|
return false;
|
|
1358
1894
|
const n = node2;
|
|
1359
1895
|
return typeof n.computed === "boolean" && typeof n.key === "object" && n.key !== null && typeof n.value === "object" && n.value !== null;
|
|
@@ -1361,16 +1897,16 @@ function nodeHandlerAttributeInfo(node) {
|
|
|
1361
1897
|
const getLastIdentifierFromMemberChain = (node2) => {
|
|
1362
1898
|
if (!node2)
|
|
1363
1899
|
return null;
|
|
1364
|
-
if (
|
|
1900
|
+
if (isIdentifierNode2(node2))
|
|
1365
1901
|
return node2.name;
|
|
1366
1902
|
if (isMemberExpressionNode(node2)) {
|
|
1367
1903
|
const prop = node2.property;
|
|
1368
1904
|
if (node2.computed === false) {
|
|
1369
|
-
if (
|
|
1905
|
+
if (isIdentifierNode2(prop))
|
|
1370
1906
|
return prop.name;
|
|
1371
1907
|
}
|
|
1372
1908
|
if (node2.computed === true) {
|
|
1373
|
-
if (
|
|
1909
|
+
if (isStringLiteralNode2(prop))
|
|
1374
1910
|
return prop.value;
|
|
1375
1911
|
}
|
|
1376
1912
|
}
|
|
@@ -1380,7 +1916,7 @@ function nodeHandlerAttributeInfo(node) {
|
|
|
1380
1916
|
if (!node2) {
|
|
1381
1917
|
return null;
|
|
1382
1918
|
}
|
|
1383
|
-
if (
|
|
1919
|
+
if (isIdentifierNode2(node2)) {
|
|
1384
1920
|
return node2.name;
|
|
1385
1921
|
}
|
|
1386
1922
|
if (isMemberExpressionNode(node2)) {
|
|
@@ -1392,11 +1928,11 @@ function nodeHandlerAttributeInfo(node) {
|
|
|
1392
1928
|
if (!lhs) {
|
|
1393
1929
|
return null;
|
|
1394
1930
|
}
|
|
1395
|
-
if (
|
|
1931
|
+
if (isIdentifierNode2(lhs)) {
|
|
1396
1932
|
return lhs.name;
|
|
1397
1933
|
}
|
|
1398
1934
|
if (isMemberExpressionNode(lhs)) {
|
|
1399
|
-
if (lhs.computed === false &&
|
|
1935
|
+
if (lhs.computed === false && isIdentifierNode2(lhs.property) && lhs.property.name === "value") {
|
|
1400
1936
|
return getLastIdentifierFromMemberChain(lhs.object);
|
|
1401
1937
|
}
|
|
1402
1938
|
return getLastIdentifierFromMemberChain(lhs);
|
|
@@ -1404,7 +1940,7 @@ function nodeHandlerAttributeInfo(node) {
|
|
|
1404
1940
|
return null;
|
|
1405
1941
|
};
|
|
1406
1942
|
const isTemplateLiteralNode = (node2) => {
|
|
1407
|
-
if (!
|
|
1943
|
+
if (!isNodeType2(node2, "TemplateLiteral")) {
|
|
1408
1944
|
return false;
|
|
1409
1945
|
}
|
|
1410
1946
|
const n = node2;
|
|
@@ -1423,7 +1959,7 @@ function nodeHandlerAttributeInfo(node) {
|
|
|
1423
1959
|
if (isNullLiteralNode(arg)) {
|
|
1424
1960
|
return "Null";
|
|
1425
1961
|
}
|
|
1426
|
-
if (
|
|
1962
|
+
if (isStringLiteralNode2(arg)) {
|
|
1427
1963
|
const cleaned = (arg.value ?? "").trim();
|
|
1428
1964
|
if (!cleaned) {
|
|
1429
1965
|
return null;
|
|
@@ -1449,7 +1985,7 @@ function nodeHandlerAttributeInfo(node) {
|
|
|
1449
1985
|
return toPascalCase(stableName.slice(0, 24));
|
|
1450
1986
|
}
|
|
1451
1987
|
}
|
|
1452
|
-
if (
|
|
1988
|
+
if (isIdentifierNode2(arg)) {
|
|
1453
1989
|
const firstChar = arg.name.charAt(0);
|
|
1454
1990
|
const isUpperAlpha = firstChar !== "" && firstChar === firstChar.toUpperCase() && firstChar !== firstChar.toLowerCase();
|
|
1455
1991
|
if (isUpperAlpha) {
|
|
@@ -1461,7 +1997,7 @@ function nodeHandlerAttributeInfo(node) {
|
|
|
1461
1997
|
const getStableSuffixFromCall = (call) => {
|
|
1462
1998
|
const args = call.arguments ?? [];
|
|
1463
1999
|
const first = args.length > 0 ? args[0] : null;
|
|
1464
|
-
if (!
|
|
2000
|
+
if (!isObjectExpressionNode2(first)) {
|
|
1465
2001
|
const parts2 = [];
|
|
1466
2002
|
for (const arg of args.slice(0, 4)) {
|
|
1467
2003
|
const w = stableWordFromValue(arg ?? null);
|
|
@@ -1480,20 +2016,20 @@ function nodeHandlerAttributeInfo(node) {
|
|
|
1480
2016
|
}
|
|
1481
2017
|
const parts = [];
|
|
1482
2018
|
for (const prop of first.properties ?? []) {
|
|
1483
|
-
if (!
|
|
2019
|
+
if (!isObjectPropertyNode2(prop)) {
|
|
1484
2020
|
continue;
|
|
1485
2021
|
}
|
|
1486
2022
|
if (prop.computed) {
|
|
1487
2023
|
continue;
|
|
1488
2024
|
}
|
|
1489
|
-
const keyName =
|
|
2025
|
+
const keyName = isIdentifierNode2(prop.key) ? prop.key.name : isStringLiteralNode2(prop.key) ? prop.key.value : null;
|
|
1490
2026
|
if (!keyName) {
|
|
1491
2027
|
continue;
|
|
1492
2028
|
}
|
|
1493
2029
|
let valueWord = null;
|
|
1494
2030
|
if (isBooleanLiteralNode(prop.value)) {
|
|
1495
2031
|
valueWord = prop.value.value ? "True" : "False";
|
|
1496
|
-
} else if (
|
|
2032
|
+
} else if (isStringLiteralNode2(prop.value)) {
|
|
1497
2033
|
const cleaned = (prop.value.value ?? "").trim();
|
|
1498
2034
|
if (cleaned) {
|
|
1499
2035
|
valueWord = toPascalCase(cleaned.slice(0, 24));
|
|
@@ -1622,13 +2158,18 @@ function getDataTestIdValueFromValueAttribute(node, actualFileName, attributeKey
|
|
|
1622
2158
|
return staticAttributeValue(`${actualFileName}-${value}-${role}`);
|
|
1623
2159
|
}
|
|
1624
2160
|
const attrDynamic = findDirectiveByName(node, "bind", attributeKey);
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
2161
|
+
const attrDynamicAst = attrDynamic ? tryGetDirectiveBabelAst(attrDynamic, {
|
|
2162
|
+
preferredViews: ["loc", "compiled"],
|
|
2163
|
+
plugins: ["typescript"],
|
|
2164
|
+
preferExistingAst: false
|
|
2165
|
+
}) : null;
|
|
2166
|
+
if (attrDynamic?.exp && attrDynamicAst) {
|
|
2167
|
+
let value = getVueExpressionSource(attrDynamic.exp, "loc", "compiled");
|
|
2168
|
+
if (types.isMemberExpression(attrDynamicAst) || types.isOptionalMemberExpression(attrDynamicAst)) {
|
|
1628
2169
|
return staticAttributeValue(`${actualFileName}-${value.replaceAll(".", "")}-${role}`);
|
|
1629
2170
|
}
|
|
1630
|
-
if (
|
|
1631
|
-
value =
|
|
2171
|
+
if (types.isCallExpression(attrDynamicAst) || types.isOptionalCallExpression(attrDynamicAst)) {
|
|
2172
|
+
value = getVueExpressionSource(attrDynamic.exp, "compiled", "loc");
|
|
1632
2173
|
return templateAttributeValue(`${actualFileName}-\${${value}}-${role}`);
|
|
1633
2174
|
}
|
|
1634
2175
|
return staticAttributeValue(`${actualFileName}-${value}-${role}`);
|
|
@@ -1636,16 +2177,16 @@ function getDataTestIdValueFromValueAttribute(node, actualFileName, attributeKey
|
|
|
1636
2177
|
return null;
|
|
1637
2178
|
}
|
|
1638
2179
|
function generateToDirectiveDataTestId(componentName, node, toDirective, context, hierarchyMap2, nativeWrappers) {
|
|
1639
|
-
const
|
|
1640
|
-
if (
|
|
1641
|
-
return templateAttributeValue(`${componentName}-${
|
|
2180
|
+
const keyInfo = getKeyDirectiveInfo(node) || getContainedInVForDirectiveKeyInfo(context, node, hierarchyMap2);
|
|
2181
|
+
if (keyInfo) {
|
|
2182
|
+
return templateAttributeValue(`${componentName}-${keyInfo.selectorFragment}-${formatTagName(node, nativeWrappers)}`);
|
|
1642
2183
|
} else {
|
|
1643
2184
|
let name = toDirectiveObjectFieldNameValue(toDirective);
|
|
1644
2185
|
if (!name) {
|
|
1645
2186
|
if (toDirective.exp == null) {
|
|
1646
2187
|
return null;
|
|
1647
2188
|
}
|
|
1648
|
-
const source =
|
|
2189
|
+
const source = getVueExpressionSource(toDirective.exp, "compiled", "loc");
|
|
1649
2190
|
const toAst = toDirective.exp.ast;
|
|
1650
2191
|
const interpolated = toAst !== void 0 && toAst !== null && toAst !== false && types.isTemplateLiteral(toAst);
|
|
1651
2192
|
return templateAttributeValue(`${componentName}-\${${source}${interpolated ? ".replaceAll(' ', '')" : "?.name?.replaceAll(' ', '') ?? ''"}}${formatTagName(node, nativeWrappers)}`);
|
|
@@ -1669,44 +2210,46 @@ function toDirectiveObjectFieldNameValue(node) {
|
|
|
1669
2210
|
if (!node.exp || node.exp.type !== compilerCore.NodeTypes.COMPOUND_EXPRESSION && node.exp.type !== compilerCore.NodeTypes.SIMPLE_EXPRESSION) {
|
|
1670
2211
|
return null;
|
|
1671
2212
|
}
|
|
1672
|
-
const
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
const isStringLiteralNode = (n) => {
|
|
1679
|
-
return isNodeType(n, "StringLiteral") && typeof n.value === "string";
|
|
1680
|
-
};
|
|
1681
|
-
const isIdentifierNode = (n) => {
|
|
1682
|
-
return isNodeType(n, "Identifier") && typeof n.name === "string";
|
|
1683
|
-
};
|
|
1684
|
-
const isObjectPropertyNode = (n) => {
|
|
1685
|
-
if (!isNodeType(n, "ObjectProperty"))
|
|
1686
|
-
return false;
|
|
1687
|
-
const nn = n;
|
|
1688
|
-
return typeof nn.key === "object" && nn.key !== null && typeof nn.value === "object" && nn.value !== null;
|
|
1689
|
-
};
|
|
1690
|
-
const isObjectExpressionNode = (n) => {
|
|
1691
|
-
if (!isNodeType(n, "ObjectExpression"))
|
|
1692
|
-
return false;
|
|
1693
|
-
const nn = n;
|
|
1694
|
-
return Array.isArray(nn.properties);
|
|
1695
|
-
};
|
|
1696
|
-
if (!isObjectExpressionNode(expr))
|
|
1697
|
-
return null;
|
|
1698
|
-
const nameProp = expr.properties.find((p) => {
|
|
1699
|
-
if (!isObjectPropertyNode(p))
|
|
1700
|
-
return false;
|
|
1701
|
-
const key = p.key;
|
|
1702
|
-
return isIdentifierNode(key) && key.name === "name" || isStringLiteralNode(key) && key.value === "name";
|
|
1703
|
-
});
|
|
1704
|
-
if (!nameProp || !isObjectPropertyNode(nameProp) || !isStringLiteralNode(nameProp.value))
|
|
1705
|
-
return null;
|
|
1706
|
-
return toPascalCase(nameProp.value.value);
|
|
1707
|
-
} catch {
|
|
2213
|
+
const expr = tryGetDirectiveBabelAst(node, {
|
|
2214
|
+
preferredViews: ["loc", "compiled"],
|
|
2215
|
+
plugins: ["typescript"],
|
|
2216
|
+
preferExistingAst: false
|
|
2217
|
+
});
|
|
2218
|
+
if (!expr) {
|
|
1708
2219
|
return null;
|
|
1709
2220
|
}
|
|
2221
|
+
const isNodeType2 = (n, type) => {
|
|
2222
|
+
return n !== null && n.type === type;
|
|
2223
|
+
};
|
|
2224
|
+
const isStringLiteralNode2 = (n) => {
|
|
2225
|
+
return isNodeType2(n, "StringLiteral") && typeof n.value === "string";
|
|
2226
|
+
};
|
|
2227
|
+
const isIdentifierNode2 = (n) => {
|
|
2228
|
+
return isNodeType2(n, "Identifier") && typeof n.name === "string";
|
|
2229
|
+
};
|
|
2230
|
+
const isObjectPropertyNode2 = (n) => {
|
|
2231
|
+
if (!isNodeType2(n, "ObjectProperty"))
|
|
2232
|
+
return false;
|
|
2233
|
+
const nn = n;
|
|
2234
|
+
return typeof nn.key === "object" && nn.key !== null && typeof nn.value === "object" && nn.value !== null;
|
|
2235
|
+
};
|
|
2236
|
+
const isObjectExpressionNode2 = (n) => {
|
|
2237
|
+
if (!isNodeType2(n, "ObjectExpression"))
|
|
2238
|
+
return false;
|
|
2239
|
+
const nn = n;
|
|
2240
|
+
return Array.isArray(nn.properties);
|
|
2241
|
+
};
|
|
2242
|
+
if (!isObjectExpressionNode2(expr))
|
|
2243
|
+
return null;
|
|
2244
|
+
const nameProp = expr.properties.find((p) => {
|
|
2245
|
+
if (!isObjectPropertyNode2(p))
|
|
2246
|
+
return false;
|
|
2247
|
+
const key = p.key;
|
|
2248
|
+
return isIdentifierNode2(key) && key.name === "name" || isStringLiteralNode2(key) && key.value === "name";
|
|
2249
|
+
});
|
|
2250
|
+
if (!nameProp || !isObjectPropertyNode2(nameProp) || !isStringLiteralNode2(nameProp.value))
|
|
2251
|
+
return null;
|
|
2252
|
+
return toPascalCase(nameProp.value.value);
|
|
1710
2253
|
}
|
|
1711
2254
|
function getComposedClickHandlerContent(node, _context, innerText, clickDirective, _options = {}) {
|
|
1712
2255
|
const click = clickDirective ?? tryGetClickDirective(node);
|
|
@@ -1716,7 +2259,7 @@ function getComposedClickHandlerContent(node, _context, innerText, clickDirectiv
|
|
|
1716
2259
|
let handlerName = "";
|
|
1717
2260
|
if (click.exp) {
|
|
1718
2261
|
const exp = click.exp;
|
|
1719
|
-
const source = (exp
|
|
2262
|
+
const source = getVueExpressionSource(exp, "content", "compiled");
|
|
1720
2263
|
if (source) {
|
|
1721
2264
|
const parsed = tryParseBabelAstFromHandlerSource(source);
|
|
1722
2265
|
if (parsed) {
|
|
@@ -1737,9 +2280,9 @@ function tryParseBabelAstFromHandlerSource(source) {
|
|
|
1737
2280
|
const trimmed = source.trim();
|
|
1738
2281
|
if (!trimmed)
|
|
1739
2282
|
return null;
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
2283
|
+
const expressionAst = tryParseBabelExpressionFromSource(trimmed, ["typescript", "jsx"]);
|
|
2284
|
+
if (expressionAst) {
|
|
2285
|
+
return expressionAst;
|
|
1743
2286
|
}
|
|
1744
2287
|
try {
|
|
1745
2288
|
return parser.parse(trimmed, { sourceType: "module", plugins: ["typescript", "jsx"] });
|
|
@@ -2078,23 +2621,24 @@ function tryGetExistingElementDataTestId(node, attributeName = "data-testid") {
|
|
|
2078
2621
|
return null;
|
|
2079
2622
|
}
|
|
2080
2623
|
const simpleExp = exp;
|
|
2081
|
-
const ast = simpleExp
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
const
|
|
2088
|
-
const unwrappedTemplate = raw2.startsWith("`") && raw2.endsWith("`") && raw2.length >= 2 ? raw2.slice(1, -1) : cooked;
|
|
2624
|
+
const ast = tryGetVueExpressionAst(simpleExp, {
|
|
2625
|
+
preferredViews: ["content", "loc", "compiled"],
|
|
2626
|
+
plugins: ["typescript"]
|
|
2627
|
+
});
|
|
2628
|
+
const unwrappedTemplateLiteral = tryUnwrapTemplateLiteralExpressionSource(simpleExp);
|
|
2629
|
+
if (unwrappedTemplateLiteral) {
|
|
2630
|
+
const isStatic = unwrappedTemplateLiteral.expressionCount === 0;
|
|
2089
2631
|
if (isStatic) {
|
|
2090
|
-
return { value:
|
|
2632
|
+
return { value: unwrappedTemplateLiteral.template, isDynamic: false, isStaticLiteral: true };
|
|
2091
2633
|
}
|
|
2634
|
+
const templateValue = templateAttributeValue(unwrappedTemplateLiteral.template);
|
|
2092
2635
|
return {
|
|
2093
|
-
value:
|
|
2636
|
+
value: templateValue.template,
|
|
2094
2637
|
isDynamic: true,
|
|
2095
2638
|
isStaticLiteral: false,
|
|
2096
|
-
template:
|
|
2097
|
-
|
|
2639
|
+
template: templateValue.template,
|
|
2640
|
+
parsedTemplate: templateValue.parsedTemplate,
|
|
2641
|
+
templateExpressionCount: unwrappedTemplateLiteral.expressionCount
|
|
2098
2642
|
};
|
|
2099
2643
|
}
|
|
2100
2644
|
if (ast && typeof ast === "object" && "type" in ast && ast.type === "StringLiteral") {
|
|
@@ -2107,11 +2651,13 @@ function tryGetExistingElementDataTestId(node, attributeName = "data-testid") {
|
|
|
2107
2651
|
}
|
|
2108
2652
|
const preservableReference = tryGetPreservableDynamicReferenceExpression(ast);
|
|
2109
2653
|
if (preservableReference) {
|
|
2654
|
+
const templateValue = templateAttributeValue(`\${${preservableReference}}`);
|
|
2110
2655
|
return {
|
|
2111
2656
|
value: preservableReference,
|
|
2112
2657
|
isDynamic: true,
|
|
2113
2658
|
isStaticLiteral: false,
|
|
2114
|
-
template:
|
|
2659
|
+
template: templateValue.template,
|
|
2660
|
+
parsedTemplate: templateValue.parsedTemplate,
|
|
2115
2661
|
templateExpressionCount: 1,
|
|
2116
2662
|
rawExpression: preservableReference
|
|
2117
2663
|
};
|
|
@@ -2120,46 +2666,12 @@ function tryGetExistingElementDataTestId(node, attributeName = "data-testid") {
|
|
|
2120
2666
|
if (!raw) {
|
|
2121
2667
|
return null;
|
|
2122
2668
|
}
|
|
2123
|
-
try {
|
|
2124
|
-
const ast2 = parser.parseExpression(raw, { plugins: ["typescript"] });
|
|
2125
|
-
if (ast2 && typeof ast2 === "object" && "type" in ast2 && ast2.type === "TemplateLiteral") {
|
|
2126
|
-
const tl = ast2;
|
|
2127
|
-
const cooked = (tl.quasis ?? []).map((q) => q.value?.cooked ?? "").join("");
|
|
2128
|
-
const expressionCount = (tl.expressions ?? []).length;
|
|
2129
|
-
const isStatic = expressionCount === 0;
|
|
2130
|
-
const unwrappedTemplate = raw.startsWith("`") && raw.endsWith("`") && raw.length >= 2 ? raw.slice(1, -1) : cooked;
|
|
2131
|
-
if (isStatic) {
|
|
2132
|
-
return { value: unwrappedTemplate, isDynamic: false, isStaticLiteral: true };
|
|
2133
|
-
}
|
|
2134
|
-
return {
|
|
2135
|
-
value: unwrappedTemplate,
|
|
2136
|
-
isDynamic: true,
|
|
2137
|
-
isStaticLiteral: false,
|
|
2138
|
-
template: unwrappedTemplate,
|
|
2139
|
-
templateExpressionCount: expressionCount
|
|
2140
|
-
};
|
|
2141
|
-
}
|
|
2142
|
-
if (ast2 && typeof ast2 === "object" && "type" in ast2 && ast2.type === "StringLiteral") {
|
|
2143
|
-
const sl = ast2;
|
|
2144
|
-
return { value: sl.value ?? "", isDynamic: false, isStaticLiteral: true };
|
|
2145
|
-
}
|
|
2146
|
-
const preservableReference2 = tryGetPreservableDynamicReferenceExpression(ast2);
|
|
2147
|
-
if (preservableReference2) {
|
|
2148
|
-
return {
|
|
2149
|
-
value: preservableReference2,
|
|
2150
|
-
isDynamic: true,
|
|
2151
|
-
isStaticLiteral: false,
|
|
2152
|
-
template: `\${${preservableReference2}}`,
|
|
2153
|
-
templateExpressionCount: 1,
|
|
2154
|
-
rawExpression: preservableReference2
|
|
2155
|
-
};
|
|
2156
|
-
}
|
|
2157
|
-
} catch {
|
|
2158
|
-
}
|
|
2159
2669
|
return { value: raw, isDynamic: true, isStaticLiteral: false, rawExpression: raw };
|
|
2160
2670
|
}
|
|
2161
2671
|
function isTemplatePlaceholder(part) {
|
|
2162
|
-
|
|
2672
|
+
const parsedTemplate = tryParseTemplateFragment(part);
|
|
2673
|
+
const templateFragment = parsedTemplate ? getSingleExpressionTemplateFragment(parsedTemplate) : null;
|
|
2674
|
+
return !!templateFragment && templateFragment.prefix === "" && templateFragment.suffix === "";
|
|
2163
2675
|
}
|
|
2164
2676
|
function isAllCapsOrDigits(value) {
|
|
2165
2677
|
if (value.length <= 1) {
|
|
@@ -2212,33 +2724,14 @@ function safeMethodNameFromParts(parts) {
|
|
|
2212
2724
|
}
|
|
2213
2725
|
return name;
|
|
2214
2726
|
}
|
|
2215
|
-
function
|
|
2727
|
+
function toPomKeyPattern(templateValue) {
|
|
2728
|
+
const { templateLiteral } = templateValue.parsedTemplate;
|
|
2216
2729
|
let out = "";
|
|
2217
|
-
let i = 0;
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
out += template.slice(i);
|
|
2222
|
-
break;
|
|
2223
|
-
}
|
|
2224
|
-
out += template.slice(i, start);
|
|
2225
|
-
let depth = 1;
|
|
2226
|
-
let j = start + 2;
|
|
2227
|
-
while (j < template.length && depth > 0) {
|
|
2228
|
-
if (template[j] === "{") {
|
|
2229
|
-
depth++;
|
|
2230
|
-
} else if (template[j] === "}") {
|
|
2231
|
-
depth--;
|
|
2232
|
-
}
|
|
2233
|
-
j++;
|
|
2234
|
-
}
|
|
2235
|
-
const end = depth === 0 ? j - 1 : -1;
|
|
2236
|
-
if (end < 0) {
|
|
2237
|
-
out += template.slice(start);
|
|
2238
|
-
break;
|
|
2730
|
+
for (let i = 0; i < templateLiteral.quasis.length; i += 1) {
|
|
2731
|
+
out += templateLiteral.quasis[i]?.value.raw ?? "";
|
|
2732
|
+
if (templateLiteral.expressions[i]) {
|
|
2733
|
+
out += "${key}";
|
|
2239
2734
|
}
|
|
2240
|
-
out += "${key}";
|
|
2241
|
-
i = end + 1;
|
|
2242
2735
|
}
|
|
2243
2736
|
return out;
|
|
2244
2737
|
}
|
|
@@ -2246,8 +2739,8 @@ function applyResolvedDataTestId(args) {
|
|
|
2246
2739
|
const addHtmlAttribute = args.addHtmlAttribute ?? true;
|
|
2247
2740
|
const entryOverrides = args.entryOverrides ?? {};
|
|
2248
2741
|
const testIdAttribute = args.testIdAttribute ?? "data-testid";
|
|
2249
|
-
const existingIdBehavior = args.existingIdBehavior ?? "
|
|
2250
|
-
const nameCollisionBehavior = args.nameCollisionBehavior ?? "
|
|
2742
|
+
const existingIdBehavior = args.existingIdBehavior ?? "error";
|
|
2743
|
+
const nameCollisionBehavior = args.nameCollisionBehavior ?? "error";
|
|
2251
2744
|
const warn = args.warn;
|
|
2252
2745
|
const getBestKeyAccessCandidates = (expr) => {
|
|
2253
2746
|
if (!expr) {
|
|
@@ -2256,7 +2749,10 @@ function applyResolvedDataTestId(args) {
|
|
|
2256
2749
|
return splitNullishCoalescingExpression(expr);
|
|
2257
2750
|
};
|
|
2258
2751
|
let dataTestId = args.preferredGeneratedValue;
|
|
2752
|
+
let runtimeDataTestId = args.preferredRuntimeValue ?? args.preferredGeneratedValue;
|
|
2259
2753
|
let fromExisting = false;
|
|
2754
|
+
const bestKeyPreservePlaceholder = args.keyInfo?.runtimeFragment ?? null;
|
|
2755
|
+
const bestKeyVariable = args.keyInfo?.rawExpression ?? null;
|
|
2260
2756
|
const existing = tryGetExistingElementDataTestId(args.element, testIdAttribute);
|
|
2261
2757
|
if (existing) {
|
|
2262
2758
|
const loc = args.element.loc?.start;
|
|
@@ -2277,8 +2773,10 @@ Bulk cleanup: run ESLint with the @immense/vue-pom-generator/remove-existing-tes
|
|
|
2277
2773
|
if (existingIdBehavior === "preserve") {
|
|
2278
2774
|
if (existing.isDynamic) {
|
|
2279
2775
|
if (existing.template) {
|
|
2280
|
-
const
|
|
2281
|
-
|
|
2776
|
+
const existingTemplateValue = existing.parsedTemplate ? { kind: "template", template: existing.template, parsedTemplate: existing.parsedTemplate } : templateAttributeValue(existing.template);
|
|
2777
|
+
const existingTemplateFragment = getSingleExpressionTemplateFragment(existingTemplateValue.parsedTemplate);
|
|
2778
|
+
const requiredKeyTemplateValue = bestKeyPreservePlaceholder ? templateAttributeValue(bestKeyPreservePlaceholder) : null;
|
|
2779
|
+
if ((existing.templateExpressionCount ?? 0) !== 1 || !existingTemplateFragment) {
|
|
2282
2780
|
throw new Error(
|
|
2283
2781
|
`[vue-pom-generator] Existing ${attrLabel} is a template literal with multiple interpolations and cannot be preserved safely.
|
|
2284
2782
|
Component: ${args.componentName}
|
|
@@ -2288,20 +2786,21 @@ Existing ${attrLabel}: ${JSON.stringify(existing.value)}
|
|
|
2288
2786
|
Fix: reduce the template to a single key-based interpolation, or remove the explicit ${attrLabel} so it can be auto-generated.`
|
|
2289
2787
|
);
|
|
2290
2788
|
}
|
|
2291
|
-
const hasExact =
|
|
2292
|
-
const hasVarAccess = getBestKeyAccessCandidates(
|
|
2293
|
-
if (!hasExact && !hasVarAccess &&
|
|
2789
|
+
const hasExact = requiredKeyTemplateValue ? templateFragmentContainsSingleExpression(existingTemplateValue.parsedTemplate, requiredKeyTemplateValue.parsedTemplate) : false;
|
|
2790
|
+
const hasVarAccess = getBestKeyAccessCandidates(bestKeyVariable).some((candidate) => existingTemplateFragment.expressionSource === candidate);
|
|
2791
|
+
if (!hasExact && !hasVarAccess && bestKeyPreservePlaceholder) {
|
|
2294
2792
|
throw new Error(
|
|
2295
2793
|
`[vue-pom-generator] Existing ${attrLabel} appears to be missing the key placeholder needed to keep it unique.
|
|
2296
2794
|
Component: ${args.componentName}
|
|
2297
2795
|
File: ${file}:${locationHint}
|
|
2298
2796
|
Existing ${attrLabel}: ${JSON.stringify(existing.value)}
|
|
2299
|
-
Required placeholder: ${JSON.stringify(
|
|
2797
|
+
Required placeholder: ${JSON.stringify(bestKeyPreservePlaceholder)}${bestKeyVariable ? ` or an access on "${bestKeyVariable}"` : ""}
|
|
2300
2798
|
|
|
2301
|
-
Fix: either (1) include ${
|
|
2799
|
+
Fix: either (1) include ${bestKeyPreservePlaceholder} in your :${attrLabel} template literal, or (2) remove the explicit ${attrLabel} so it can be auto-generated.`
|
|
2302
2800
|
);
|
|
2303
2801
|
}
|
|
2304
|
-
dataTestId =
|
|
2802
|
+
dataTestId = existingTemplateValue;
|
|
2803
|
+
runtimeDataTestId = existingTemplateValue;
|
|
2305
2804
|
fromExisting = true;
|
|
2306
2805
|
} else {
|
|
2307
2806
|
throw new Error(
|
|
@@ -2315,18 +2814,19 @@ If you really need a computed id, do not set existingIdBehavior="preserve".`
|
|
|
2315
2814
|
);
|
|
2316
2815
|
}
|
|
2317
2816
|
} else {
|
|
2318
|
-
if (
|
|
2817
|
+
if (bestKeyPreservePlaceholder && existing.isStaticLiteral) {
|
|
2319
2818
|
throw new Error(
|
|
2320
2819
|
`[vue-pom-generator] Existing ${attrLabel} appears to be missing the key placeholder needed to keep it unique.
|
|
2321
2820
|
Component: ${args.componentName}
|
|
2322
2821
|
File: ${file}:${locationHint}
|
|
2323
2822
|
Existing ${attrLabel}: ${JSON.stringify(existing.value)}
|
|
2324
|
-
Required placeholder: ${JSON.stringify(
|
|
2823
|
+
Required placeholder: ${JSON.stringify(bestKeyPreservePlaceholder)}${bestKeyVariable ? ` or an access on "${bestKeyVariable}"` : ""}
|
|
2325
2824
|
|
|
2326
|
-
Fix: either (1) include ${
|
|
2825
|
+
Fix: either (1) include ${bestKeyPreservePlaceholder} in your :${attrLabel} template literal, or (2) remove the explicit ${attrLabel} so it can be auto-generated.`
|
|
2327
2826
|
);
|
|
2328
2827
|
}
|
|
2329
2828
|
dataTestId = staticAttributeValue(existing.value);
|
|
2829
|
+
runtimeDataTestId = staticAttributeValue(existing.value);
|
|
2330
2830
|
fromExisting = true;
|
|
2331
2831
|
}
|
|
2332
2832
|
}
|
|
@@ -2356,8 +2856,10 @@ Fix: either (1) include ${args.bestKeyPlaceholder} in your :${attrLabel} templat
|
|
|
2356
2856
|
};
|
|
2357
2857
|
const normalizedRole = normalizeNativeRole(args.nativeRole) ?? "button";
|
|
2358
2858
|
const targetPageObjectModelClass = entryOverrides.targetPageObjectModelClass;
|
|
2359
|
-
const formattedDataTestIdForPom = dataTestId.kind === "template" ?
|
|
2360
|
-
const
|
|
2859
|
+
const formattedDataTestIdForPom = dataTestId.kind === "template" ? toPomKeyPattern(dataTestId) : dataTestId.value;
|
|
2860
|
+
const selectorPatternKind = dataTestId.kind === "template" ? "parameterized" : "static";
|
|
2861
|
+
const selectorPattern = createPomStringPattern(formattedDataTestIdForPom, selectorPatternKind);
|
|
2862
|
+
const selectorIsParameterized = selectorPatternKind === "parameterized";
|
|
2361
2863
|
const deriveBaseMethodNameFromHint = (hint) => {
|
|
2362
2864
|
const hintRaw = (hint ?? "").trim();
|
|
2363
2865
|
const trimEdgeSeparators = (value) => {
|
|
@@ -2394,7 +2896,7 @@ Fix: either (1) include ${args.bestKeyPlaceholder} in your :${attrLabel} templat
|
|
|
2394
2896
|
}
|
|
2395
2897
|
return value.slice(0, idx) + value.slice(idx + "ByKey".length);
|
|
2396
2898
|
};
|
|
2397
|
-
const
|
|
2899
|
+
const hasRoleSuffix2 = (baseName, roleSuffix) => {
|
|
2398
2900
|
if (baseName.endsWith(roleSuffix)) {
|
|
2399
2901
|
return true;
|
|
2400
2902
|
}
|
|
@@ -2404,14 +2906,14 @@ Fix: either (1) include ${args.bestKeyPlaceholder} in your :${attrLabel} templat
|
|
|
2404
2906
|
const getPrimaryGetterName = (primaryMethodName) => {
|
|
2405
2907
|
const roleSuffix = upperFirst(normalizedRole || "Element");
|
|
2406
2908
|
const baseName = upperFirst(primaryMethodName);
|
|
2407
|
-
const propertyName =
|
|
2408
|
-
return
|
|
2909
|
+
const propertyName = hasRoleSuffix2(baseName, roleSuffix) ? baseName : `${baseName}${roleSuffix}`;
|
|
2910
|
+
return selectorIsParameterized ? removeByKeySegment2(propertyName) : propertyName;
|
|
2409
2911
|
};
|
|
2410
2912
|
const getPrimaryGetterNameCandidates = (primaryMethodName) => {
|
|
2411
2913
|
const roleSuffix = upperFirst(normalizedRole || "Element");
|
|
2412
2914
|
const baseName = upperFirst(primaryMethodName);
|
|
2413
|
-
const propertyName =
|
|
2414
|
-
if (!
|
|
2915
|
+
const propertyName = hasRoleSuffix2(baseName, roleSuffix) ? baseName : `${baseName}${roleSuffix}`;
|
|
2916
|
+
if (!selectorIsParameterized) {
|
|
2415
2917
|
return { primary: propertyName };
|
|
2416
2918
|
}
|
|
2417
2919
|
const stripped = removeByKeySegment2(propertyName);
|
|
@@ -2473,11 +2975,11 @@ Fix: either (1) include ${args.bestKeyPlaceholder} in your :${attrLabel} templat
|
|
|
2473
2975
|
return false;
|
|
2474
2976
|
}
|
|
2475
2977
|
const existingSelectors = [
|
|
2476
|
-
existingPom.
|
|
2477
|
-
...existingPom.
|
|
2978
|
+
existingPom.selector,
|
|
2979
|
+
...existingPom.alternateSelectors ?? []
|
|
2478
2980
|
];
|
|
2479
|
-
const sharesSelectorIdentity = existingSelectors.
|
|
2480
|
-
if (
|
|
2981
|
+
const sharesSelectorIdentity = existingSelectors.some((existingSelector) => pomStringPatternEquals(existingSelector, selectorPattern));
|
|
2982
|
+
if (selectorIsParameterized && !sharesSelectorIdentity) {
|
|
2481
2983
|
return false;
|
|
2482
2984
|
}
|
|
2483
2985
|
if (!mergeKey && !sharesSelectorIdentity) {
|
|
@@ -2492,12 +2994,15 @@ Fix: either (1) include ${args.bestKeyPlaceholder} in your :${attrLabel} templat
|
|
|
2492
2994
|
if ((existingEntry.targetPageObjectModelClass ?? null) !== (targetPageObjectModelClass ?? null)) {
|
|
2493
2995
|
return false;
|
|
2494
2996
|
}
|
|
2495
|
-
if (existingPom.
|
|
2496
|
-
existingPom.
|
|
2497
|
-
if (!existingPom.
|
|
2498
|
-
existingPom.
|
|
2997
|
+
if (!pomStringPatternEquals(existingPom.selector, selectorPattern)) {
|
|
2998
|
+
existingPom.alternateSelectors ??= [];
|
|
2999
|
+
if (!(existingPom.alternateSelectors ?? []).some((existingSelector) => pomStringPatternEquals(existingSelector, selectorPattern))) {
|
|
3000
|
+
existingPom.alternateSelectors.push(selectorPattern);
|
|
2499
3001
|
}
|
|
2500
3002
|
}
|
|
3003
|
+
if (selectorIsParameterized && !existingPom.parameters.some((param) => param.name === "key")) {
|
|
3004
|
+
existingPom.parameters = [createPomParameterSpec("key", keyTypeFromValues), ...existingPom.parameters];
|
|
3005
|
+
}
|
|
2501
3006
|
return true;
|
|
2502
3007
|
};
|
|
2503
3008
|
let methodName = "";
|
|
@@ -2510,7 +3015,7 @@ Fix: either (1) include ${args.bestKeyPlaceholder} in your :${attrLabel} templat
|
|
|
2510
3015
|
let suffix = 1;
|
|
2511
3016
|
while (true) {
|
|
2512
3017
|
const baseWithSuffix = suffix === 1 ? base : `${base}${suffix}`;
|
|
2513
|
-
const candidate =
|
|
3018
|
+
const candidate = selectorIsParameterized ? `${baseWithSuffix}ByKey` : baseWithSuffix;
|
|
2514
3019
|
const actionName = getPrimaryActionMethodName(candidate);
|
|
2515
3020
|
const getterCandidates = getPrimaryGetterNameCandidates(candidate);
|
|
2516
3021
|
let chosenGetterName = getterCandidates.primary;
|
|
@@ -2534,9 +3039,9 @@ Fix: either (1) include ${args.bestKeyPlaceholder} in your :${attrLabel} templat
|
|
|
2534
3039
|
if (conflicts && nameCollisionBehavior === "error") {
|
|
2535
3040
|
const roleSuffix = upperFirst(normalizedRole || "Element");
|
|
2536
3041
|
const baseNameUpper = upperFirst(baseWithSuffix);
|
|
2537
|
-
if (!
|
|
3042
|
+
if (!hasRoleSuffix2(baseNameUpper, roleSuffix)) {
|
|
2538
3043
|
const baseWithRoleSuffix = `${baseWithSuffix}${roleSuffix}`;
|
|
2539
|
-
const candidateWithRoleSuffix =
|
|
3044
|
+
const candidateWithRoleSuffix = selectorIsParameterized ? `${baseWithRoleSuffix}ByKey` : baseWithRoleSuffix;
|
|
2540
3045
|
const actionNameWithRoleSuffix = getPrimaryActionMethodName(candidateWithRoleSuffix);
|
|
2541
3046
|
const getterCandidatesWithRoleSuffix = getPrimaryGetterNameCandidates(candidateWithRoleSuffix);
|
|
2542
3047
|
let chosenGetterNameWithRoleSuffix = getterCandidatesWithRoleSuffix.primary;
|
|
@@ -2604,51 +3109,46 @@ Conflicts: getter=${last.getterName}, method=${last.actionName}
|
|
|
2604
3109
|
Fix: make the element identifiable (e.g. add id/name/inner text or use a more specific click handler name), or switch generation.nameCollisionBehavior to "warn"/"suffix".`
|
|
2605
3110
|
);
|
|
2606
3111
|
}
|
|
2607
|
-
|
|
2608
|
-
if (isKeyed) {
|
|
2609
|
-
params.key = keyTypeFromValues;
|
|
2610
|
-
}
|
|
3112
|
+
let parameters = selectorIsParameterized ? [createPomParameterSpec("key", keyTypeFromValues)] : [];
|
|
2611
3113
|
switch (normalizedRole) {
|
|
2612
3114
|
case "input":
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
if (!isKeyed) delete params.key;
|
|
3115
|
+
parameters = setPomParameter(parameters, "text", "string");
|
|
3116
|
+
parameters = setPomParameter(parameters, "annotationText", 'string = ""');
|
|
2616
3117
|
break;
|
|
2617
3118
|
case "select":
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
if (!isKeyed) delete params.key;
|
|
3119
|
+
parameters = setPomParameter(parameters, "value", "string");
|
|
3120
|
+
parameters = setPomParameter(parameters, "annotationText", 'string = ""');
|
|
2621
3121
|
break;
|
|
2622
3122
|
case "vselect":
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
if (!isKeyed) delete params.key;
|
|
3123
|
+
parameters = setPomParameter(parameters, "value", "string");
|
|
3124
|
+
parameters = setPomParameter(parameters, "timeOut", "number = 500");
|
|
3125
|
+
parameters = setPomParameter(parameters, "annotationText", 'string = ""');
|
|
2627
3126
|
break;
|
|
2628
3127
|
case "radio":
|
|
2629
|
-
|
|
3128
|
+
parameters = setPomParameter(parameters, "annotationText", 'string = ""');
|
|
2630
3129
|
break;
|
|
2631
3130
|
}
|
|
2632
|
-
|
|
2633
|
-
params.key = keyTypeFromValues;
|
|
2634
|
-
}
|
|
3131
|
+
const normalizedParameters = selectorIsParameterized ? setPomParameter(parameters, "key", keyTypeFromValues) : removePomParameter(parameters, "key");
|
|
2635
3132
|
if (addHtmlAttribute && !fromExisting) {
|
|
2636
3133
|
upsertAttribute(args.element, testIdAttribute, dataTestId);
|
|
2637
3134
|
}
|
|
2638
3135
|
const childComponentName = args.element.tag;
|
|
2639
3136
|
const dataTestIdEntry = {
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
3137
|
+
selectorValue: entryOverrides.selectorValue ?? createPomStringPattern(
|
|
3138
|
+
getAttributeValueText(dataTestId),
|
|
3139
|
+
dataTestId.kind === "template" ? "parameterized" : "static"
|
|
3140
|
+
),
|
|
3141
|
+
templateLiteral: entryOverrides.templateLiteral,
|
|
3142
|
+
targetPageObjectModelClass: entryOverrides.targetPageObjectModelClass
|
|
2643
3143
|
};
|
|
2644
3144
|
dataTestIdEntry.pom = {
|
|
2645
3145
|
nativeRole: normalizedRole,
|
|
2646
3146
|
methodName,
|
|
2647
3147
|
getterNameOverride,
|
|
2648
|
-
|
|
2649
|
-
|
|
3148
|
+
selector: selectorPattern,
|
|
3149
|
+
alternateSelectors: void 0,
|
|
2650
3150
|
mergeKey: args.pomMergeKey,
|
|
2651
|
-
|
|
3151
|
+
parameters: normalizedParameters,
|
|
2652
3152
|
keyValuesOverride: args.keyValuesOverride ?? null
|
|
2653
3153
|
// emitPrimary defaults to true; special cases (including merge) may set it to false below.
|
|
2654
3154
|
};
|
|
@@ -2679,43 +3179,18 @@ Fix: make the element identifiable (e.g. add id/name/inner text or use a more sp
|
|
|
2679
3179
|
}
|
|
2680
3180
|
};
|
|
2681
3181
|
const getSignatureForGeneratedMethod = () => {
|
|
2682
|
-
|
|
2683
|
-
const isNavigation2 = !!dataTestIdEntry.targetPageObjectModelClass;
|
|
2684
|
-
const needsKey2 = Object.prototype.hasOwnProperty.call(params, "key");
|
|
2685
|
-
const keyType = keyTypeFromValues;
|
|
2686
|
-
if (isNavigation2) {
|
|
2687
|
-
if (needsKey2) {
|
|
2688
|
-
return { params: `key: ${keyType}`, argNames: ["key"] };
|
|
2689
|
-
}
|
|
2690
|
-
return { params: "", argNames: [] };
|
|
2691
|
-
}
|
|
2692
|
-
switch (role) {
|
|
2693
|
-
case "input":
|
|
2694
|
-
return needsKey2 ? { params: `key: ${keyType}, text: string, annotationText: string = ""`, argNames: ["key", "text", "annotationText"] } : { params: 'text: string, annotationText: string = ""', argNames: ["text", "annotationText"] };
|
|
2695
|
-
case "select":
|
|
2696
|
-
return needsKey2 ? { params: `key: ${keyType}, value: string, annotationText: string = ""`, argNames: ["key", "value", "annotationText"] } : { params: 'value: string, annotationText: string = ""', argNames: ["value", "annotationText"] };
|
|
2697
|
-
case "vselect":
|
|
2698
|
-
return needsKey2 ? { params: `key: ${keyType}, value: string, timeOut = 500`, argNames: ["key", "value", "timeOut"] } : { params: "value: string, timeOut = 500", argNames: ["value", "timeOut"] };
|
|
2699
|
-
case "radio":
|
|
2700
|
-
return needsKey2 ? { params: `key: ${keyType}, annotationText: string = ""`, argNames: ["key", "annotationText"] } : { params: 'annotationText: string = ""', argNames: ["annotationText"] };
|
|
2701
|
-
default:
|
|
2702
|
-
if (needsKey2) {
|
|
2703
|
-
return { params: `key: ${keyType}`, argNames: ["key"] };
|
|
2704
|
-
}
|
|
2705
|
-
return { params: "", argNames: [] };
|
|
2706
|
-
}
|
|
3182
|
+
return createPomMethodSignature(normalizedParameters);
|
|
2707
3183
|
};
|
|
2708
3184
|
const registerPrimaryOnce = (pom) => {
|
|
2709
|
-
const
|
|
2710
|
-
const alternates = (pom.alternateFormattedDataTestIds ?? []).slice().sort();
|
|
3185
|
+
const alternates = (pom.alternateSelectors ?? []).slice().sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b)));
|
|
2711
3186
|
const key = JSON.stringify({
|
|
2712
3187
|
kind: "primary",
|
|
2713
3188
|
role: pom.nativeRole,
|
|
2714
3189
|
methodName: pom.methodName,
|
|
2715
3190
|
getterNameOverride: pom.getterNameOverride ?? null,
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
3191
|
+
selector: pom.selector,
|
|
3192
|
+
alternateSelectors: alternates.length ? alternates : void 0,
|
|
3193
|
+
parameters: pom.parameters,
|
|
2719
3194
|
target: dataTestIdEntry.targetPageObjectModelClass ?? null,
|
|
2720
3195
|
emitPrimary: pom.emitPrimary ?? true
|
|
2721
3196
|
});
|
|
@@ -2729,8 +3204,7 @@ Fix: make the element identifiable (e.g. add id/name/inner text or use a more sp
|
|
|
2729
3204
|
}
|
|
2730
3205
|
};
|
|
2731
3206
|
const addExtraClickMethod = (spec) => {
|
|
2732
|
-
const
|
|
2733
|
-
const key = JSON.stringify({ kind: spec.kind, selector: spec.selector, keyLiteral: spec.keyLiteral ?? null, params: stableParams });
|
|
3207
|
+
const key = JSON.stringify({ kind: spec.kind, selector: spec.selector, keyLiteral: spec.keyLiteral ?? null, parameters: spec.parameters });
|
|
2734
3208
|
const seen = args.generatedMethodContentByComponent.get(args.parentComponentName) ?? /* @__PURE__ */ new Set();
|
|
2735
3209
|
if (!args.generatedMethodContentByComponent.has(args.parentComponentName)) {
|
|
2736
3210
|
args.generatedMethodContentByComponent.set(args.parentComponentName, seen);
|
|
@@ -2753,7 +3227,7 @@ Fix: make the element identifiable (e.g. add id/name/inner text or use a more sp
|
|
|
2753
3227
|
if (prev === null) {
|
|
2754
3228
|
return;
|
|
2755
3229
|
}
|
|
2756
|
-
if (signature === null || prev
|
|
3230
|
+
if (signature === null || !pomMethodSignatureEquals(prev, signature)) {
|
|
2757
3231
|
args.dependencies.generatedMethods.set(name, null);
|
|
2758
3232
|
}
|
|
2759
3233
|
};
|
|
@@ -2769,23 +3243,10 @@ Fix: make the element identifiable (e.g. add id/name/inner text or use a more sp
|
|
|
2769
3243
|
return candidate;
|
|
2770
3244
|
};
|
|
2771
3245
|
const tryGetDirectiveExpressionAst = (dir) => {
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
}
|
|
2776
|
-
if (exp.type === compilerCore.NodeTypes.SIMPLE_EXPRESSION) {
|
|
2777
|
-
const simple = exp;
|
|
2778
|
-
const ast = simple.ast;
|
|
2779
|
-
if (ast && "type" in ast) {
|
|
2780
|
-
return ast;
|
|
2781
|
-
}
|
|
2782
|
-
}
|
|
2783
|
-
try {
|
|
2784
|
-
const raw = args.context ? compilerCore.stringifyExpression(exp) : exp.loc.source;
|
|
2785
|
-
return parser.parseExpression(raw, { plugins: ["typescript"] });
|
|
2786
|
-
} catch {
|
|
2787
|
-
return null;
|
|
2788
|
-
}
|
|
3246
|
+
return tryGetDirectiveBabelAst(dir, {
|
|
3247
|
+
preferredViews: args.context ? ["compiled", "loc"] : ["loc", "compiled"],
|
|
3248
|
+
plugins: ["typescript"]
|
|
3249
|
+
});
|
|
2789
3250
|
};
|
|
2790
3251
|
const tryGetStaticStringFromBabel = (node) => {
|
|
2791
3252
|
if (!node) {
|
|
@@ -2882,17 +3343,17 @@ Fix: make the element identifiable (e.g. add id/name/inner text or use a more sp
|
|
|
2882
3343
|
name: generatedName2,
|
|
2883
3344
|
selector: {
|
|
2884
3345
|
kind: "withinTestIdByLabel",
|
|
2885
|
-
|
|
2886
|
-
|
|
3346
|
+
rootTestId: createPomStringPattern(wrapperTestId, selectorPatternKind),
|
|
3347
|
+
label: createPomStringPattern(label, "static"),
|
|
2887
3348
|
exact: true
|
|
2888
3349
|
},
|
|
2889
|
-
|
|
3350
|
+
parameters: [createPomParameterSpec("annotationText", `string = ""`)]
|
|
2890
3351
|
});
|
|
2891
3352
|
if (added2) {
|
|
2892
|
-
registerGeneratedMethodSignature(generatedName2,
|
|
3353
|
+
registerGeneratedMethodSignature(generatedName2, createPomMethodSignature([createPomParameterSpec("annotationText", `string = ""`)]));
|
|
2893
3354
|
}
|
|
2894
3355
|
}
|
|
2895
|
-
return;
|
|
3356
|
+
return { selectorValue: dataTestId, runtimeValue: runtimeDataTestId, fromExisting };
|
|
2896
3357
|
}
|
|
2897
3358
|
const generatedName = ensureUniqueGeneratedName(`select${upperFirst(methodName || "Radio")}`);
|
|
2898
3359
|
if (dataTestIdEntry.pom) {
|
|
@@ -2904,19 +3365,25 @@ Fix: make the element identifiable (e.g. add id/name/inner text or use a more sp
|
|
|
2904
3365
|
name: generatedName,
|
|
2905
3366
|
selector: {
|
|
2906
3367
|
kind: "withinTestIdByLabel",
|
|
2907
|
-
|
|
2908
|
-
|
|
3368
|
+
rootTestId: createPomStringPattern(wrapperTestId, selectorPatternKind),
|
|
3369
|
+
label: createPomStringPattern("${value}", "parameterized"),
|
|
2909
3370
|
exact: true
|
|
2910
3371
|
},
|
|
2911
|
-
|
|
3372
|
+
parameters: [
|
|
3373
|
+
createPomParameterSpec("value", "string"),
|
|
3374
|
+
createPomParameterSpec("annotationText", `string = ""`)
|
|
3375
|
+
]
|
|
2912
3376
|
});
|
|
2913
3377
|
if (added) {
|
|
2914
|
-
registerGeneratedMethodSignature(generatedName,
|
|
3378
|
+
registerGeneratedMethodSignature(generatedName, createPomMethodSignature([
|
|
3379
|
+
createPomParameterSpec("value", "string"),
|
|
3380
|
+
createPomParameterSpec("annotationText", `string = ""`)
|
|
3381
|
+
]));
|
|
2915
3382
|
}
|
|
2916
|
-
return;
|
|
3383
|
+
return { selectorValue: dataTestId, runtimeValue: runtimeDataTestId, fromExisting };
|
|
2917
3384
|
}
|
|
2918
3385
|
const staticKeyValues = args.keyValuesOverride ?? null;
|
|
2919
|
-
const needsKey =
|
|
3386
|
+
const needsKey = hasPomParameter(normalizedParameters, "key") && selectorIsParameterized;
|
|
2920
3387
|
const isNavigation = !!dataTestIdEntry.targetPageObjectModelClass;
|
|
2921
3388
|
if (staticKeyValues && staticKeyValues.length > 0 && needsKey && !isNavigation && normalizedRole !== "input" && normalizedRole !== "select" && normalizedRole !== "vselect" && normalizedRole !== "radio") {
|
|
2922
3389
|
if (dataTestIdEntry.pom) {
|
|
@@ -2935,19 +3402,19 @@ Fix: make the element identifiable (e.g. add id/name/inner text or use a more sp
|
|
|
2935
3402
|
name: generatedName,
|
|
2936
3403
|
selector: {
|
|
2937
3404
|
kind: "testId",
|
|
2938
|
-
|
|
3405
|
+
testId: selectorPattern
|
|
2939
3406
|
},
|
|
2940
3407
|
keyLiteral: rawValue,
|
|
2941
|
-
|
|
3408
|
+
parameters: [createPomParameterSpec("wait", "boolean = true"), createPomParameterSpec("annotationText", 'string = ""')]
|
|
2942
3409
|
});
|
|
2943
3410
|
if (added) {
|
|
2944
|
-
registerGeneratedMethodSignature(generatedName,
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
3411
|
+
registerGeneratedMethodSignature(generatedName, createPomMethodSignature([
|
|
3412
|
+
createPomParameterSpec("wait", "boolean = true"),
|
|
3413
|
+
createPomParameterSpec("annotationText", 'string = ""')
|
|
3414
|
+
]));
|
|
2948
3415
|
}
|
|
2949
3416
|
}
|
|
2950
|
-
return;
|
|
3417
|
+
return { selectorValue: dataTestId, runtimeValue: runtimeDataTestId, fromExisting };
|
|
2951
3418
|
}
|
|
2952
3419
|
if (dataTestIdEntry.pom) {
|
|
2953
3420
|
if (dataTestIdEntry.pom.emitPrimary !== false) {
|
|
@@ -2961,6 +3428,7 @@ Fix: make the element identifiable (e.g. add id/name/inner text or use a more sp
|
|
|
2961
3428
|
const generatedName = getGeneratedMethodName();
|
|
2962
3429
|
registerGeneratedMethodSignature(generatedName, signature);
|
|
2963
3430
|
}
|
|
3431
|
+
return { selectorValue: dataTestId, runtimeValue: runtimeDataTestId, fromExisting };
|
|
2964
3432
|
}
|
|
2965
3433
|
function safeRealpath(value) {
|
|
2966
3434
|
try {
|
|
@@ -3933,127 +4401,20 @@ async function parseRouterFileFromCwd(routerEntryPath, options = {}) {
|
|
|
3933
4401
|
debugLog("closing internal vite server");
|
|
3934
4402
|
await server.close();
|
|
3935
4403
|
}
|
|
3936
|
-
} finally {
|
|
3937
|
-
restoreDomShim();
|
|
3938
|
-
}
|
|
3939
|
-
});
|
|
3940
|
-
}
|
|
3941
|
-
const GENERATED_GITATTRIBUTES_BLOCK_START = "# BEGIN vue-pom-generator generated files";
|
|
3942
|
-
const GENERATED_GITATTRIBUTES_BLOCK_END = "# END vue-pom-generator generated files";
|
|
3943
|
-
const VUE_POM_GENERATOR_ERROR_PREFIX = "[vue-pom-generator]";
|
|
3944
|
-
class VuePomGeneratorError extends Error {
|
|
3945
|
-
constructor(message) {
|
|
3946
|
-
const normalized = message.startsWith(VUE_POM_GENERATOR_ERROR_PREFIX) ? message : `${VUE_POM_GENERATOR_ERROR_PREFIX} ${message}`;
|
|
3947
|
-
super(normalized);
|
|
3948
|
-
this.name = "VuePomGeneratorError";
|
|
3949
|
-
}
|
|
3950
|
-
}
|
|
3951
|
-
function splitParameterList(parameters) {
|
|
3952
|
-
const parts = [];
|
|
3953
|
-
let current = "";
|
|
3954
|
-
let braceDepth = 0;
|
|
3955
|
-
let bracketDepth = 0;
|
|
3956
|
-
let parenDepth = 0;
|
|
3957
|
-
let angleDepth = 0;
|
|
3958
|
-
let inSingleQuote = false;
|
|
3959
|
-
let inDoubleQuote = false;
|
|
3960
|
-
let inTemplateString = false;
|
|
3961
|
-
for (let index = 0; index < parameters.length; index += 1) {
|
|
3962
|
-
const char = parameters[index];
|
|
3963
|
-
const previous = index > 0 ? parameters[index - 1] : "";
|
|
3964
|
-
if (char === "'" && !inDoubleQuote && !inTemplateString && previous !== "\\") {
|
|
3965
|
-
inSingleQuote = !inSingleQuote;
|
|
3966
|
-
current += char;
|
|
3967
|
-
continue;
|
|
3968
|
-
}
|
|
3969
|
-
if (char === '"' && !inSingleQuote && !inTemplateString && previous !== "\\") {
|
|
3970
|
-
inDoubleQuote = !inDoubleQuote;
|
|
3971
|
-
current += char;
|
|
3972
|
-
continue;
|
|
3973
|
-
}
|
|
3974
|
-
if (char === "`" && !inSingleQuote && !inDoubleQuote && previous !== "\\") {
|
|
3975
|
-
inTemplateString = !inTemplateString;
|
|
3976
|
-
current += char;
|
|
3977
|
-
continue;
|
|
3978
|
-
}
|
|
3979
|
-
if (inSingleQuote || inDoubleQuote || inTemplateString) {
|
|
3980
|
-
current += char;
|
|
3981
|
-
continue;
|
|
3982
|
-
}
|
|
3983
|
-
switch (char) {
|
|
3984
|
-
case "{":
|
|
3985
|
-
braceDepth += 1;
|
|
3986
|
-
break;
|
|
3987
|
-
case "}":
|
|
3988
|
-
braceDepth -= 1;
|
|
3989
|
-
break;
|
|
3990
|
-
case "[":
|
|
3991
|
-
bracketDepth += 1;
|
|
3992
|
-
break;
|
|
3993
|
-
case "]":
|
|
3994
|
-
bracketDepth -= 1;
|
|
3995
|
-
break;
|
|
3996
|
-
case "(":
|
|
3997
|
-
parenDepth += 1;
|
|
3998
|
-
break;
|
|
3999
|
-
case ")":
|
|
4000
|
-
parenDepth -= 1;
|
|
4001
|
-
break;
|
|
4002
|
-
case "<":
|
|
4003
|
-
angleDepth += 1;
|
|
4004
|
-
break;
|
|
4005
|
-
case ">":
|
|
4006
|
-
angleDepth -= 1;
|
|
4007
|
-
break;
|
|
4008
|
-
case ",":
|
|
4009
|
-
if (braceDepth === 0 && bracketDepth === 0 && parenDepth === 0 && angleDepth === 0) {
|
|
4010
|
-
const trimmed2 = current.trim();
|
|
4011
|
-
if (trimmed2) {
|
|
4012
|
-
parts.push(trimmed2);
|
|
4013
|
-
}
|
|
4014
|
-
current = "";
|
|
4015
|
-
continue;
|
|
4016
|
-
}
|
|
4017
|
-
break;
|
|
4018
|
-
}
|
|
4019
|
-
current += char;
|
|
4020
|
-
}
|
|
4021
|
-
const trimmed = current.trim();
|
|
4022
|
-
if (trimmed) {
|
|
4023
|
-
parts.push(trimmed);
|
|
4024
|
-
}
|
|
4025
|
-
return parts;
|
|
4026
|
-
}
|
|
4027
|
-
function parseParameterSignature(parameter) {
|
|
4028
|
-
const colonIndex = parameter.indexOf(":");
|
|
4029
|
-
if (colonIndex < 0) {
|
|
4030
|
-
return { name: parameter.trim() };
|
|
4031
|
-
}
|
|
4032
|
-
const rawName = parameter.slice(0, colonIndex).trim();
|
|
4033
|
-
const hasQuestionToken = rawName.endsWith("?");
|
|
4034
|
-
const name = hasQuestionToken ? rawName.slice(0, -1).trim() : rawName;
|
|
4035
|
-
const remainder = parameter.slice(colonIndex + 1).trim();
|
|
4036
|
-
const initializerIndex = remainder.lastIndexOf("=");
|
|
4037
|
-
if (initializerIndex < 0) {
|
|
4038
|
-
return {
|
|
4039
|
-
name,
|
|
4040
|
-
hasQuestionToken,
|
|
4041
|
-
type: remainder || void 0
|
|
4042
|
-
};
|
|
4043
|
-
}
|
|
4044
|
-
return {
|
|
4045
|
-
name,
|
|
4046
|
-
hasQuestionToken,
|
|
4047
|
-
type: remainder.slice(0, initializerIndex).trim() || void 0,
|
|
4048
|
-
initializer: remainder.slice(initializerIndex + 1).trim() || void 0
|
|
4049
|
-
};
|
|
4404
|
+
} finally {
|
|
4405
|
+
restoreDomShim();
|
|
4406
|
+
}
|
|
4407
|
+
});
|
|
4050
4408
|
}
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4409
|
+
const GENERATED_GITATTRIBUTES_BLOCK_START = "# BEGIN vue-pom-generator generated files";
|
|
4410
|
+
const GENERATED_GITATTRIBUTES_BLOCK_END = "# END vue-pom-generator generated files";
|
|
4411
|
+
const VUE_POM_GENERATOR_ERROR_PREFIX = "[vue-pom-generator]";
|
|
4412
|
+
class VuePomGeneratorError extends Error {
|
|
4413
|
+
constructor(message) {
|
|
4414
|
+
const normalized = message.startsWith(VUE_POM_GENERATOR_ERROR_PREFIX) ? message : `${VUE_POM_GENERATOR_ERROR_PREFIX} ${message}`;
|
|
4415
|
+
super(normalized);
|
|
4416
|
+
this.name = "VuePomGeneratorError";
|
|
4055
4417
|
}
|
|
4056
|
-
return splitParameterList(trimmed).map(parseParameterSignature);
|
|
4057
4418
|
}
|
|
4058
4419
|
function toPosixRelativePath(fromDir, toFile) {
|
|
4059
4420
|
let rel = path.relative(fromDir, toFile).replace(/\\/g, "/");
|
|
@@ -4074,6 +4435,21 @@ function resolveRouterEntry(projectRoot, routerEntry) {
|
|
|
4074
4435
|
const root = projectRoot ?? process.cwd();
|
|
4075
4436
|
return path.isAbsolute(routerEntry) ? routerEntry : path.resolve(root, routerEntry);
|
|
4076
4437
|
}
|
|
4438
|
+
function createMissingCustomPomDirectoryError(configuredDir, resolvedDir) {
|
|
4439
|
+
return new VuePomGeneratorError(
|
|
4440
|
+
`Custom POM directory "${configuredDir}" does not exist.
|
|
4441
|
+
Resolved path: ${resolvedDir}
|
|
4442
|
+
Create the directory, point generation.playwright.customPoms.dir at the correct location, or remove the customPoms configuration.`
|
|
4443
|
+
);
|
|
4444
|
+
}
|
|
4445
|
+
function createMissingCustomPomAttachmentClassError(missingClassNames, configuredDir) {
|
|
4446
|
+
const renderedClassNames = missingClassNames.map((name) => `"${name}"`).join(", ");
|
|
4447
|
+
return new VuePomGeneratorError(
|
|
4448
|
+
`Custom POM attachments reference missing helper classes: ${renderedClassNames}.
|
|
4449
|
+
Expected matching helper files/exports under "${configuredDir}".
|
|
4450
|
+
Add the missing helper classes or remove the corresponding generation.playwright.customPoms.attachments entries.`
|
|
4451
|
+
);
|
|
4452
|
+
}
|
|
4077
4453
|
function createCustomPomImportCollisionError(exportName, requested) {
|
|
4078
4454
|
return new VuePomGeneratorError(
|
|
4079
4455
|
`Custom POM import name collision detected for "${exportName}".
|
|
@@ -4212,41 +4588,31 @@ function generateGoToSelfMethod(componentName) {
|
|
|
4212
4588
|
})
|
|
4213
4589
|
];
|
|
4214
4590
|
}
|
|
4215
|
-
function
|
|
4216
|
-
|
|
4217
|
-
return "";
|
|
4218
|
-
const preferredOrder = ["key", "value", "text", "timeOut", "annotationText", "wait"];
|
|
4219
|
-
const entries = Object.entries(params);
|
|
4220
|
-
if (!entries.length)
|
|
4221
|
-
return "";
|
|
4222
|
-
const score = (name) => {
|
|
4223
|
-
const idx = preferredOrder.indexOf(name);
|
|
4224
|
-
return idx < 0 ? 999 : idx;
|
|
4225
|
-
};
|
|
4226
|
-
return entries.slice().sort((a, b) => score(a[0]) - score(b[0]) || a[0].localeCompare(b[0])).map(([name, typeExpr]) => `${name}: ${typeExpr}`).join(", ");
|
|
4591
|
+
function getSelectorPatterns(selector) {
|
|
4592
|
+
return selector.kind === "testId" ? [selector.testId] : [selector.rootTestId, selector.label];
|
|
4227
4593
|
}
|
|
4228
|
-
function generateExtraClickMethodMembers(spec) {
|
|
4594
|
+
function generateExtraClickMethodMembers(spec, componentName) {
|
|
4229
4595
|
if (spec.kind !== "click") {
|
|
4230
4596
|
return [];
|
|
4231
4597
|
}
|
|
4232
|
-
const
|
|
4233
|
-
const
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
|
|
4598
|
+
const selectorPatterns = getSelectorPatterns(spec.selector);
|
|
4599
|
+
const signatureSpecs = orderPomPatternParameters(
|
|
4600
|
+
spec.parameters,
|
|
4601
|
+
selectorPatterns,
|
|
4602
|
+
{ omit: spec.keyLiteral !== void 0 ? ["key"] : [] }
|
|
4603
|
+
);
|
|
4604
|
+
const parameters = toTypeScriptPomParameterStructures(signatureSpecs);
|
|
4605
|
+
const hasAnnotationText = signatureSpecs.some((param) => param.name === "annotationText");
|
|
4606
|
+
const hasWait = signatureSpecs.some((param) => param.name === "wait");
|
|
4237
4607
|
const annotationArg = hasAnnotationText ? "annotationText" : '""';
|
|
4238
4608
|
const waitArg = hasWait ? "wait" : "true";
|
|
4609
|
+
const locatorDescription = JSON.stringify(buildPomLocatorDescription({
|
|
4610
|
+
componentName,
|
|
4611
|
+
methodName: stripPomActionPrefix(spec.name),
|
|
4612
|
+
nativeRole: "button"
|
|
4613
|
+
}));
|
|
4239
4614
|
if (spec.selector.kind === "testId") {
|
|
4240
|
-
const
|
|
4241
|
-
const testIdExpr = needsTemplate ? `\`${spec.selector.formattedDataTestId}\`` : JSON.stringify(spec.selector.formattedDataTestId);
|
|
4242
|
-
const clickArgs = [];
|
|
4243
|
-
clickArgs.push(needsTemplate ? "testId" : testIdExpr);
|
|
4244
|
-
if (hasAnnotationText || hasWait) {
|
|
4245
|
-
clickArgs.push(annotationArg);
|
|
4246
|
-
}
|
|
4247
|
-
if (hasWait) {
|
|
4248
|
-
clickArgs.push(waitArg);
|
|
4249
|
-
}
|
|
4615
|
+
const testIdBinding = bindTypeScriptPomPattern(spec.selector.testId, "testId");
|
|
4250
4616
|
return [
|
|
4251
4617
|
createClassMethod({
|
|
4252
4618
|
name: spec.name,
|
|
@@ -4256,20 +4622,16 @@ function generateExtraClickMethodMembers(spec) {
|
|
|
4256
4622
|
if (spec.keyLiteral !== void 0) {
|
|
4257
4623
|
writer.writeLine(`const key = ${JSON.stringify(spec.keyLiteral)};`);
|
|
4258
4624
|
}
|
|
4259
|
-
|
|
4260
|
-
writer.writeLine(
|
|
4625
|
+
for (const statement of testIdBinding.setupStatements) {
|
|
4626
|
+
writer.writeLine(statement);
|
|
4261
4627
|
}
|
|
4262
|
-
writer.writeLine(`await this.clickByTestId(${
|
|
4628
|
+
writer.writeLine(`await this.clickByTestId(${testIdBinding.expression}, ${annotationArg}, ${waitArg}, ${locatorDescription});`);
|
|
4263
4629
|
}
|
|
4264
4630
|
})
|
|
4265
4631
|
];
|
|
4266
4632
|
}
|
|
4267
|
-
const
|
|
4268
|
-
const
|
|
4269
|
-
const rootExpr = rootNeedsTemplate ? `\`${spec.selector.rootFormattedDataTestId}\`` : JSON.stringify(spec.selector.rootFormattedDataTestId);
|
|
4270
|
-
const labelExpr = labelNeedsTemplate ? `\`${spec.selector.formattedLabel}\`` : JSON.stringify(spec.selector.formattedLabel);
|
|
4271
|
-
const rootArg = rootNeedsTemplate ? "rootTestId" : rootExpr;
|
|
4272
|
-
const labelArg = labelNeedsTemplate ? "label" : labelExpr;
|
|
4633
|
+
const rootBinding = bindTypeScriptPomPattern(spec.selector.rootTestId, "rootTestId");
|
|
4634
|
+
const labelBinding = bindTypeScriptPomPattern(spec.selector.label, "label");
|
|
4273
4635
|
return [
|
|
4274
4636
|
createClassMethod({
|
|
4275
4637
|
name: spec.name,
|
|
@@ -4279,45 +4641,45 @@ function generateExtraClickMethodMembers(spec) {
|
|
|
4279
4641
|
if (spec.keyLiteral !== void 0) {
|
|
4280
4642
|
writer.writeLine(`const key = ${JSON.stringify(spec.keyLiteral)};`);
|
|
4281
4643
|
}
|
|
4282
|
-
|
|
4283
|
-
writer.writeLine(
|
|
4644
|
+
for (const statement of rootBinding.setupStatements) {
|
|
4645
|
+
writer.writeLine(statement);
|
|
4284
4646
|
}
|
|
4285
|
-
|
|
4286
|
-
writer.writeLine(
|
|
4647
|
+
for (const statement of labelBinding.setupStatements) {
|
|
4648
|
+
writer.writeLine(statement);
|
|
4287
4649
|
}
|
|
4288
|
-
writer.writeLine(`await this.clickWithinTestIdByLabel(${
|
|
4650
|
+
writer.writeLine(`await this.clickWithinTestIdByLabel(${rootBinding.expression}, ${labelBinding.expression}, ${annotationArg}, ${waitArg}, { description: ${locatorDescription} });`);
|
|
4289
4651
|
}
|
|
4290
4652
|
})
|
|
4291
4653
|
];
|
|
4292
4654
|
}
|
|
4293
|
-
function generateMethodMembersFromPom(primary, targetPageObjectModelClass) {
|
|
4655
|
+
function generateMethodMembersFromPom(componentName, primary, targetPageObjectModelClass) {
|
|
4294
4656
|
if (primary.emitPrimary === false) {
|
|
4295
4657
|
return [];
|
|
4296
4658
|
}
|
|
4297
4659
|
return generateViewObjectModelMembers(
|
|
4660
|
+
componentName,
|
|
4298
4661
|
targetPageObjectModelClass,
|
|
4299
4662
|
primary.methodName,
|
|
4300
4663
|
primary.nativeRole,
|
|
4301
|
-
primary.
|
|
4302
|
-
primary.
|
|
4664
|
+
primary.selector,
|
|
4665
|
+
primary.alternateSelectors,
|
|
4303
4666
|
primary.getterNameOverride,
|
|
4304
|
-
primary.
|
|
4667
|
+
primary.parameters
|
|
4305
4668
|
);
|
|
4306
4669
|
}
|
|
4307
|
-
function generateMethodsContentForDependencies(dependencies) {
|
|
4670
|
+
function generateMethodsContentForDependencies(componentName, dependencies) {
|
|
4308
4671
|
const entries = Array.from(dependencies.dataTestIdSet ?? []);
|
|
4309
4672
|
const primarySpecsAll = entries.map((e) => ({ pom: e.pom, target: e.targetPageObjectModelClass })).filter((x) => !!x.pom).sort((a, b) => a.pom.methodName.localeCompare(b.pom.methodName));
|
|
4310
4673
|
const seenPrimaryKeys = /* @__PURE__ */ new Set();
|
|
4311
4674
|
const primarySpecs = primarySpecsAll.filter(({ pom, target }) => {
|
|
4312
|
-
const
|
|
4313
|
-
const alternates = (pom.alternateFormattedDataTestIds ?? []).slice().sort();
|
|
4675
|
+
const alternates = (pom.alternateSelectors ?? []).slice().sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b)));
|
|
4314
4676
|
const key = JSON.stringify({
|
|
4315
4677
|
role: pom.nativeRole,
|
|
4316
4678
|
methodName: pom.methodName,
|
|
4317
4679
|
getterNameOverride: pom.getterNameOverride ?? null,
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4680
|
+
selector: pom.selector,
|
|
4681
|
+
alternateSelectors: alternates.length ? alternates : void 0,
|
|
4682
|
+
parameters: pom.parameters,
|
|
4321
4683
|
target: target ?? null,
|
|
4322
4684
|
emitPrimary: pom.emitPrimary ?? true
|
|
4323
4685
|
});
|
|
@@ -4330,10 +4692,10 @@ function generateMethodsContentForDependencies(dependencies) {
|
|
|
4330
4692
|
const extras = (dependencies.pomExtraMethods ?? []).slice().sort((a, b) => a.name.localeCompare(b.name));
|
|
4331
4693
|
const members = [];
|
|
4332
4694
|
for (const { pom, target } of primarySpecs) {
|
|
4333
|
-
members.push(...generateMethodMembersFromPom(pom, target));
|
|
4695
|
+
members.push(...generateMethodMembersFromPom(componentName, pom, target));
|
|
4334
4696
|
}
|
|
4335
4697
|
for (const extra of extras) {
|
|
4336
|
-
members.push(...generateExtraClickMethodMembers(extra));
|
|
4698
|
+
members.push(...generateExtraClickMethodMembers(extra, componentName));
|
|
4337
4699
|
}
|
|
4338
4700
|
return members;
|
|
4339
4701
|
}
|
|
@@ -4344,6 +4706,7 @@ async function generateFiles(componentHierarchyMap, vueFilesPathMap, basePageCla
|
|
|
4344
4706
|
customPomAttachments = [],
|
|
4345
4707
|
projectRoot,
|
|
4346
4708
|
customPomDir,
|
|
4709
|
+
requireCustomPomDir,
|
|
4347
4710
|
customPomImportAliases,
|
|
4348
4711
|
customPomImportNameCollisionBehavior = "error",
|
|
4349
4712
|
testIdAttribute,
|
|
@@ -4376,6 +4739,7 @@ async function generateFiles(componentHierarchyMap, vueFilesPathMap, basePageCla
|
|
|
4376
4739
|
customPomAttachments,
|
|
4377
4740
|
projectRoot,
|
|
4378
4741
|
customPomDir,
|
|
4742
|
+
requireCustomPomDir,
|
|
4379
4743
|
customPomImportAliases,
|
|
4380
4744
|
customPomImportNameCollisionBehavior,
|
|
4381
4745
|
testIdAttribute,
|
|
@@ -4385,6 +4749,7 @@ async function generateFiles(componentHierarchyMap, vueFilesPathMap, basePageCla
|
|
|
4385
4749
|
customPomAttachments,
|
|
4386
4750
|
projectRoot,
|
|
4387
4751
|
customPomDir,
|
|
4752
|
+
requireCustomPomDir,
|
|
4388
4753
|
customPomImportAliases,
|
|
4389
4754
|
customPomImportNameCollisionBehavior,
|
|
4390
4755
|
testIdAttribute,
|
|
@@ -4440,9 +4805,15 @@ async function generateSplitTypeScriptFiles(componentHierarchyMap, vueFilesPathM
|
|
|
4440
4805
|
}
|
|
4441
4806
|
const customPomImportResolution = resolveCustomPomImportResolution(generatedClassNames, projectRoot, {
|
|
4442
4807
|
customPomDir: options.customPomDir,
|
|
4808
|
+
requireCustomPomDir: options.requireCustomPomDir,
|
|
4443
4809
|
customPomImportAliases: options.customPomImportAliases,
|
|
4444
4810
|
customPomImportNameCollisionBehavior: options.customPomImportNameCollisionBehavior
|
|
4445
4811
|
});
|
|
4812
|
+
assertCustomPomAttachmentsResolved(
|
|
4813
|
+
options.customPomAttachments ?? [],
|
|
4814
|
+
customPomImportResolution.classIdentifierMap,
|
|
4815
|
+
options.customPomDir ?? "tests/playwright/pom/custom"
|
|
4816
|
+
);
|
|
4446
4817
|
const runtimeBasePagePath = path.join(base, "_pom-runtime", "class-generation", "base-page.ts");
|
|
4447
4818
|
const files = [];
|
|
4448
4819
|
for (const [name, deps] of entries) {
|
|
@@ -4601,21 +4972,8 @@ function buildGeneratedGitAttributesFiles(generatedFilePaths) {
|
|
|
4601
4972
|
return { filePath, content };
|
|
4602
4973
|
});
|
|
4603
4974
|
}
|
|
4604
|
-
function
|
|
4605
|
-
const
|
|
4606
|
-
if (!needsInterpolation) {
|
|
4607
|
-
return JSON.stringify(formattedDataTestId);
|
|
4608
|
-
}
|
|
4609
|
-
const inner = formattedDataTestId.replace(/\$\{/g, "{");
|
|
4610
|
-
const quoted = JSON.stringify(inner);
|
|
4611
|
-
return `$${quoted}`;
|
|
4612
|
-
}
|
|
4613
|
-
function toCSharpParam(paramTypeExpr) {
|
|
4614
|
-
const trimmed = (paramTypeExpr ?? "").trim();
|
|
4615
|
-
const eqIdx = trimmed.indexOf("=");
|
|
4616
|
-
const left = eqIdx >= 0 ? trimmed.slice(0, eqIdx).trim() : trimmed;
|
|
4617
|
-
const right = eqIdx >= 0 ? trimmed.slice(eqIdx + 1).trim() : void 0;
|
|
4618
|
-
const typePart = left.includes("|") ? "string" : left;
|
|
4975
|
+
function toCSharpParam(param) {
|
|
4976
|
+
const typePart = param.type?.includes("|") ? "string" : param.type ?? "string";
|
|
4619
4977
|
let type = "string";
|
|
4620
4978
|
if (/(?:^|\s)boolean(?:\s|$)/.test(typePart))
|
|
4621
4979
|
type = "bool";
|
|
@@ -4623,19 +4981,17 @@ function toCSharpParam(paramTypeExpr) {
|
|
|
4623
4981
|
type = "string";
|
|
4624
4982
|
else if (/(?:^|\s)number(?:\s|$)/.test(typePart))
|
|
4625
4983
|
type = "int";
|
|
4626
|
-
else if (/\d+/.test(typePart) && typePart === "")
|
|
4627
|
-
type = "int";
|
|
4628
4984
|
else if (/\btimeOut\b/i.test(typePart))
|
|
4629
4985
|
type = "int";
|
|
4630
4986
|
let defaultExpr;
|
|
4631
|
-
if (
|
|
4987
|
+
if (param.initializer !== void 0) {
|
|
4632
4988
|
if (type === "bool") {
|
|
4633
|
-
defaultExpr =
|
|
4989
|
+
defaultExpr = param.initializer.includes("true") ? "true" : param.initializer.includes("false") ? "false" : void 0;
|
|
4634
4990
|
} else if (type === "int") {
|
|
4635
|
-
const m =
|
|
4991
|
+
const m = param.initializer.match(/\d+/);
|
|
4636
4992
|
defaultExpr = m ? m[0] : void 0;
|
|
4637
4993
|
} else {
|
|
4638
|
-
if (
|
|
4994
|
+
if (param.initializer === '""' || param.initializer === "''") {
|
|
4639
4995
|
defaultExpr = '""';
|
|
4640
4996
|
}
|
|
4641
4997
|
}
|
|
@@ -4643,17 +4999,15 @@ function toCSharpParam(paramTypeExpr) {
|
|
|
4643
4999
|
return { type, defaultExpr };
|
|
4644
5000
|
}
|
|
4645
5001
|
function formatCSharpParams(params) {
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
const entries = Object.entries(params);
|
|
4649
|
-
if (!entries.length)
|
|
5002
|
+
const normalizedParams = normalizePomParameters(params);
|
|
5003
|
+
if (!normalizedParams.length)
|
|
4650
5004
|
return { signature: "", argNames: [] };
|
|
4651
5005
|
const signatureParts = [];
|
|
4652
5006
|
const argNames = [];
|
|
4653
|
-
for (const
|
|
4654
|
-
const { type, defaultExpr } = toCSharpParam(
|
|
4655
|
-
argNames.push(name);
|
|
4656
|
-
signatureParts.push(defaultExpr !== void 0 ? `${type} ${name} = ${defaultExpr}` : `${type} ${name}`);
|
|
5007
|
+
for (const param of normalizedParams) {
|
|
5008
|
+
const { type, defaultExpr } = toCSharpParam(param);
|
|
5009
|
+
argNames.push(param.name);
|
|
5010
|
+
signatureParts.push(defaultExpr !== void 0 ? `${type} ${param.name} = ${defaultExpr}` : `${type} ${param.name}`);
|
|
4657
5011
|
}
|
|
4658
5012
|
return { signature: signatureParts.join(", "), argNames };
|
|
4659
5013
|
}
|
|
@@ -4744,23 +5098,13 @@ function generateAggregatedCSharpFiles(componentHierarchyMap, outDir, options =
|
|
|
4744
5098
|
const baseMethodName = upperFirst(pom.methodName);
|
|
4745
5099
|
const baseGetterName = upperFirst(pom.getterNameOverride ?? pom.methodName);
|
|
4746
5100
|
const locatorName = baseGetterName.endsWith(roleSuffix) ? baseGetterName : `${baseGetterName}${roleSuffix}`;
|
|
4747
|
-
const
|
|
4748
|
-
const
|
|
4749
|
-
const
|
|
4750
|
-
const augmentedParams = { ...pom.params };
|
|
4751
|
-
for (const v of templateVars) {
|
|
4752
|
-
if (!Object.prototype.hasOwnProperty.call(augmentedParams, v)) {
|
|
4753
|
-
augmentedParams[v] = "string";
|
|
4754
|
-
}
|
|
4755
|
-
}
|
|
4756
|
-
const orderedParams = Object.fromEntries([
|
|
4757
|
-
...templateVars.map((v) => [v, augmentedParams[v]]),
|
|
4758
|
-
...Object.entries(augmentedParams).filter(([k]) => !templateVars.includes(k))
|
|
4759
|
-
]);
|
|
5101
|
+
const selectorIsParameterized = isParameterizedPomPattern(pom.selector.patternKind);
|
|
5102
|
+
const testIdExpr = toCSharpPomPatternExpression(pom.selector);
|
|
5103
|
+
const orderedParams = orderPomPatternParameters(pom.parameters, [pom.selector]);
|
|
4760
5104
|
const { signature, argNames } = formatCSharpParams(orderedParams);
|
|
4761
5105
|
const args = argNames.join(", ");
|
|
4762
|
-
const allTestIds =
|
|
4763
|
-
if (
|
|
5106
|
+
const allTestIds = uniquePomStringPatterns(pom.selector, pom.alternateSelectors);
|
|
5107
|
+
if (selectorIsParameterized) {
|
|
4764
5108
|
chunks.push(` public ILocator ${locatorName}(${signature}) => LocatorByTestId(${testIdExpr});`);
|
|
4765
5109
|
} else {
|
|
4766
5110
|
chunks.push(` public ILocator ${locatorName} => LocatorByTestId(${testIdExpr});`);
|
|
@@ -4771,12 +5115,12 @@ function generateAggregatedCSharpFiles(componentHierarchyMap, outDir, options =
|
|
|
4771
5115
|
if (target) {
|
|
4772
5116
|
chunks.push(` public async Task<${target}> ${actionName}(${sig})`);
|
|
4773
5117
|
chunks.push(" {");
|
|
4774
|
-
if (
|
|
4775
|
-
chunks.push(` await ${locatorName}${
|
|
5118
|
+
if (selectorIsParameterized || allTestIds.length <= 1) {
|
|
5119
|
+
chunks.push(` await ${locatorName}${selectorIsParameterized ? `(${args})` : ""}.ClickAsync();`);
|
|
4776
5120
|
chunks.push(` return new ${target}(Page);`);
|
|
4777
5121
|
} else {
|
|
4778
5122
|
chunks.push(" Exception? lastError = null;");
|
|
4779
|
-
chunks.push(` foreach (var testId in new[] { ${allTestIds.map(
|
|
5123
|
+
chunks.push(` foreach (var testId in new[] { ${allTestIds.map((testId) => toCSharpPomPatternExpression(testId)).join(", ")} })`);
|
|
4780
5124
|
chunks.push(" {");
|
|
4781
5125
|
chunks.push(" try");
|
|
4782
5126
|
chunks.push(" {");
|
|
@@ -4800,7 +5144,7 @@ function generateAggregatedCSharpFiles(componentHierarchyMap, outDir, options =
|
|
|
4800
5144
|
}
|
|
4801
5145
|
chunks.push(` public async Task ${actionName}(${sig})`);
|
|
4802
5146
|
chunks.push(" {");
|
|
4803
|
-
const callSuffix =
|
|
5147
|
+
const callSuffix = selectorIsParameterized ? `(${args})` : "";
|
|
4804
5148
|
const emitActionCall = (locatorAccess) => {
|
|
4805
5149
|
if (pom.nativeRole === "input") {
|
|
4806
5150
|
chunks.push(` var editableLocator = await ResolveEditableLocatorAsync(${locatorAccess});`);
|
|
@@ -4813,9 +5157,9 @@ function generateAggregatedCSharpFiles(componentHierarchyMap, outDir, options =
|
|
|
4813
5157
|
chunks.push(` await ${locatorAccess}.ClickAsync();`);
|
|
4814
5158
|
}
|
|
4815
5159
|
};
|
|
4816
|
-
if (!
|
|
5160
|
+
if (!selectorIsParameterized && allTestIds.length > 1) {
|
|
4817
5161
|
chunks.push(" Exception? lastError = null;");
|
|
4818
|
-
chunks.push(` foreach (var testId in new[] { ${allTestIds.map(
|
|
5162
|
+
chunks.push(` foreach (var testId in new[] { ${allTestIds.map((testId) => toCSharpPomPatternExpression(testId)).join(", ")} })`);
|
|
4819
5163
|
chunks.push(" {");
|
|
4820
5164
|
chunks.push(" try");
|
|
4821
5165
|
chunks.push(" {");
|
|
@@ -4861,7 +5205,12 @@ function generateAggregatedCSharpFiles(componentHierarchyMap, outDir, options =
|
|
|
4861
5205
|
for (const extra of extras) {
|
|
4862
5206
|
if (extra.kind !== "click")
|
|
4863
5207
|
continue;
|
|
4864
|
-
const
|
|
5208
|
+
const extraParams = orderPomPatternParameters(
|
|
5209
|
+
extra.parameters,
|
|
5210
|
+
getSelectorPatterns(extra.selector),
|
|
5211
|
+
{ omit: extra.keyLiteral !== void 0 ? ["key"] : [] }
|
|
5212
|
+
);
|
|
5213
|
+
const { signature } = formatCSharpParams(extraParams);
|
|
4865
5214
|
const extraName = upperFirst(extra.name);
|
|
4866
5215
|
chunks.push(` public async Task ${extraName}Async(${signature})`);
|
|
4867
5216
|
chunks.push(" {");
|
|
@@ -4869,29 +5218,22 @@ function generateAggregatedCSharpFiles(componentHierarchyMap, outDir, options =
|
|
|
4869
5218
|
chunks.push(` var key = ${JSON.stringify(extra.keyLiteral)};`);
|
|
4870
5219
|
}
|
|
4871
5220
|
if (extra.selector.kind === "testId") {
|
|
4872
|
-
const
|
|
4873
|
-
const
|
|
4874
|
-
|
|
4875
|
-
chunks.push(` var testId = ${testIdExpr};`);
|
|
4876
|
-
chunks.push(" await LocatorByTestId(testId).ClickAsync();");
|
|
4877
|
-
} else {
|
|
4878
|
-
chunks.push(` await LocatorByTestId(${testIdExpr}).ClickAsync();`);
|
|
5221
|
+
const testIdBinding = bindCSharpPomPattern(extra.selector.testId, "testId");
|
|
5222
|
+
for (const statement of testIdBinding.setupStatements) {
|
|
5223
|
+
chunks.push(` ${statement}`);
|
|
4879
5224
|
}
|
|
5225
|
+
chunks.push(` await LocatorByTestId(${testIdBinding.expression}).ClickAsync();`);
|
|
4880
5226
|
} else {
|
|
4881
|
-
const
|
|
4882
|
-
const
|
|
4883
|
-
const rootExpr = toCSharpTestIdExpression(extra.selector.rootFormattedDataTestId);
|
|
4884
|
-
const labelExpr = toCSharpTestIdExpression(extra.selector.formattedLabel);
|
|
5227
|
+
const rootBinding = bindCSharpPomPattern(extra.selector.rootTestId, "rootTestId");
|
|
5228
|
+
const labelBinding = bindCSharpPomPattern(extra.selector.label, "label");
|
|
4885
5229
|
const exactArg = extra.selector.exact === false ? "false" : "true";
|
|
4886
|
-
|
|
4887
|
-
chunks.push(`
|
|
5230
|
+
for (const statement of rootBinding.setupStatements) {
|
|
5231
|
+
chunks.push(` ${statement}`);
|
|
4888
5232
|
}
|
|
4889
|
-
|
|
4890
|
-
chunks.push(`
|
|
5233
|
+
for (const statement of labelBinding.setupStatements) {
|
|
5234
|
+
chunks.push(` ${statement}`);
|
|
4891
5235
|
}
|
|
4892
|
-
|
|
4893
|
-
const labelArg = labelNeedsTemplate ? "label" : labelExpr;
|
|
4894
|
-
chunks.push(` await ClickWithinTestIdByLabelAsync(${rootArg}, ${labelArg}, ${exactArg});`);
|
|
5236
|
+
chunks.push(` await ClickWithinTestIdByLabelAsync(${rootBinding.expression}, ${labelBinding.expression}, ${exactArg});`);
|
|
4895
5237
|
}
|
|
4896
5238
|
chunks.push(" }");
|
|
4897
5239
|
chunks.push("");
|
|
@@ -5207,7 +5549,7 @@ function prepareViewObjectModelClass(componentName, dependencies, componentHiera
|
|
|
5207
5549
|
members.push(...generateRouteProperty(routeMeta));
|
|
5208
5550
|
members.push(...generateGoToSelfMethod(className));
|
|
5209
5551
|
}
|
|
5210
|
-
members.push(...generateMethodsContentForDependencies(dependencies));
|
|
5552
|
+
members.push(...generateMethodsContentForDependencies(componentName, dependencies));
|
|
5211
5553
|
return {
|
|
5212
5554
|
className,
|
|
5213
5555
|
componentRefsForInstances,
|
|
@@ -5335,7 +5677,7 @@ function getViewPassthroughMethods(viewName, viewDependencies, childrenComponent
|
|
|
5335
5677
|
if (existingOnView.has(name) || blockedMethodNames.has(name))
|
|
5336
5678
|
continue;
|
|
5337
5679
|
const list = methodToChildren.get(name) ?? [];
|
|
5338
|
-
list.push({ childProp,
|
|
5680
|
+
list.push({ childProp, signature: sig });
|
|
5339
5681
|
methodToChildren.set(name, list);
|
|
5340
5682
|
}
|
|
5341
5683
|
}
|
|
@@ -5345,12 +5687,12 @@ function getViewPassthroughMethods(viewName, viewDependencies, childrenComponent
|
|
|
5345
5687
|
return [];
|
|
5346
5688
|
}
|
|
5347
5689
|
return passthroughs.map(([methodName, candidates]) => {
|
|
5348
|
-
const { childProp,
|
|
5349
|
-
const callArgs =
|
|
5690
|
+
const { childProp, signature } = candidates[0];
|
|
5691
|
+
const callArgs = getPomParameterArgumentNames(signature.parameters).join(", ");
|
|
5350
5692
|
return createClassMethod({
|
|
5351
5693
|
name: methodName,
|
|
5352
5694
|
isAsync: true,
|
|
5353
|
-
parameters:
|
|
5695
|
+
parameters: toTypeScriptPomParameterStructures(signature.parameters),
|
|
5354
5696
|
statements: [
|
|
5355
5697
|
`return await this.${childProp}.${methodName}(${callArgs});`
|
|
5356
5698
|
]
|
|
@@ -5374,8 +5716,7 @@ function getAttachmentPassthroughMethods(ownerName, ownerDependencies, attachmen
|
|
|
5374
5716
|
const list = methodToAttachments.get(methodName) ?? [];
|
|
5375
5717
|
list.push({
|
|
5376
5718
|
propertyName: attachment.propertyName,
|
|
5377
|
-
|
|
5378
|
-
argNames: signature.argNames
|
|
5719
|
+
signature
|
|
5379
5720
|
});
|
|
5380
5721
|
methodToAttachments.set(methodName, list);
|
|
5381
5722
|
}
|
|
@@ -5386,12 +5727,12 @@ function getAttachmentPassthroughMethods(ownerName, ownerDependencies, attachmen
|
|
|
5386
5727
|
return [];
|
|
5387
5728
|
}
|
|
5388
5729
|
return passthroughs.map(([methodName, candidates]) => {
|
|
5389
|
-
const { propertyName,
|
|
5390
|
-
const callArgs =
|
|
5730
|
+
const { propertyName, signature } = candidates[0];
|
|
5731
|
+
const callArgs = getPomParameterArgumentNames(signature.parameters).join(", ");
|
|
5391
5732
|
const invocation = callArgs ? `this.${propertyName}.${methodName}(${callArgs})` : `this.${propertyName}.${methodName}()`;
|
|
5392
5733
|
return createClassMethod({
|
|
5393
5734
|
name: methodName,
|
|
5394
|
-
parameters:
|
|
5735
|
+
parameters: toTypeScriptPomParameterStructures(signature.parameters),
|
|
5395
5736
|
statements: [
|
|
5396
5737
|
`return ${invocation};`
|
|
5397
5738
|
]
|
|
@@ -5405,15 +5746,44 @@ function sliceNodeSource(source, node) {
|
|
|
5405
5746
|
const snippet = source.slice(node.start, node.end).trim();
|
|
5406
5747
|
return snippet.length ? snippet : null;
|
|
5407
5748
|
}
|
|
5408
|
-
function
|
|
5749
|
+
function getTypeAnnotationSource(source, node) {
|
|
5750
|
+
const rawTypeAnnotation = node.typeAnnotation;
|
|
5751
|
+
if (!rawTypeAnnotation || typeof rawTypeAnnotation !== "object" || !("type" in rawTypeAnnotation) || rawTypeAnnotation.type !== "TSTypeAnnotation" || !("typeAnnotation" in rawTypeAnnotation)) {
|
|
5752
|
+
return void 0;
|
|
5753
|
+
}
|
|
5754
|
+
const typeAnnotation = rawTypeAnnotation.typeAnnotation;
|
|
5755
|
+
return typeAnnotation && typeof typeAnnotation === "object" ? sliceNodeSource(source, typeAnnotation) ?? void 0 : void 0;
|
|
5756
|
+
}
|
|
5757
|
+
function getCustomPomParameterSpec(source, param) {
|
|
5409
5758
|
if (param.type === "Identifier") {
|
|
5410
|
-
return param.name
|
|
5759
|
+
return createPomParameterSpec(param.name, getTypeAnnotationSource(source, param), {
|
|
5760
|
+
hasQuestionToken: !!param.optional
|
|
5761
|
+
});
|
|
5411
5762
|
}
|
|
5412
5763
|
if (param.type === "AssignmentPattern") {
|
|
5413
|
-
|
|
5764
|
+
if (param.left.type !== "Identifier") {
|
|
5765
|
+
return null;
|
|
5766
|
+
}
|
|
5767
|
+
const initializer = sliceNodeSource(source, param.right);
|
|
5768
|
+
if (!initializer) {
|
|
5769
|
+
return null;
|
|
5770
|
+
}
|
|
5771
|
+
return createPomParameterSpec(param.left.name, getTypeAnnotationSource(source, param.left), {
|
|
5772
|
+
initializer,
|
|
5773
|
+
hasQuestionToken: !!param.left.optional
|
|
5774
|
+
});
|
|
5414
5775
|
}
|
|
5415
5776
|
if (param.type === "RestElement") {
|
|
5416
|
-
|
|
5777
|
+
if (param.argument.type !== "Identifier") {
|
|
5778
|
+
return null;
|
|
5779
|
+
}
|
|
5780
|
+
const typeExpression = getTypeAnnotationSource(
|
|
5781
|
+
source,
|
|
5782
|
+
param
|
|
5783
|
+
) ?? getTypeAnnotationSource(source, param.argument);
|
|
5784
|
+
return createPomParameterSpec(param.argument.name, typeExpression, {
|
|
5785
|
+
isRestParameter: true
|
|
5786
|
+
});
|
|
5417
5787
|
}
|
|
5418
5788
|
return null;
|
|
5419
5789
|
}
|
|
@@ -5446,29 +5816,23 @@ function extractCustomPomMethodSignatures(source, exportName) {
|
|
|
5446
5816
|
if (member.key.type !== "Identifier") {
|
|
5447
5817
|
continue;
|
|
5448
5818
|
}
|
|
5449
|
-
const
|
|
5450
|
-
const argNames = [];
|
|
5819
|
+
const parameters = [];
|
|
5451
5820
|
let supported = true;
|
|
5452
5821
|
member.params.forEach((param) => {
|
|
5453
5822
|
if (!supported) {
|
|
5454
5823
|
return;
|
|
5455
5824
|
}
|
|
5456
|
-
const
|
|
5457
|
-
|
|
5458
|
-
if (!paramSource || !argName) {
|
|
5825
|
+
const parameter = getCustomPomParameterSpec(source, param);
|
|
5826
|
+
if (!parameter) {
|
|
5459
5827
|
supported = false;
|
|
5460
5828
|
return;
|
|
5461
5829
|
}
|
|
5462
|
-
|
|
5463
|
-
argNames.push(argName);
|
|
5830
|
+
parameters.push(parameter);
|
|
5464
5831
|
});
|
|
5465
5832
|
if (!supported) {
|
|
5466
5833
|
continue;
|
|
5467
5834
|
}
|
|
5468
|
-
signatures.set(member.key.name,
|
|
5469
|
-
params: params.join(", "),
|
|
5470
|
-
argNames
|
|
5471
|
-
});
|
|
5835
|
+
signatures.set(member.key.name, createPomMethodSignature(parameters));
|
|
5472
5836
|
}
|
|
5473
5837
|
}
|
|
5474
5838
|
return signatures;
|
|
@@ -5651,6 +6015,9 @@ function resolveCustomPomImportResolution(generatedClassNames, projectRoot, opti
|
|
|
5651
6015
|
const customDirRelOrAbs = options.customPomDir ?? "tests/playwright/pom/custom";
|
|
5652
6016
|
const customDirAbs = path.isAbsolute(customDirRelOrAbs) ? customDirRelOrAbs : path.resolve(projectRoot, customDirRelOrAbs);
|
|
5653
6017
|
if (!fs.existsSync(customDirAbs)) {
|
|
6018
|
+
if (options.requireCustomPomDir) {
|
|
6019
|
+
throw createMissingCustomPomDirectoryError(customDirRelOrAbs, customDirAbs);
|
|
6020
|
+
}
|
|
5654
6021
|
return {
|
|
5655
6022
|
classIdentifierMap,
|
|
5656
6023
|
methodSignaturesByClass,
|
|
@@ -5693,6 +6060,14 @@ function resolveCustomPomImportResolution(generatedClassNames, projectRoot, opti
|
|
|
5693
6060
|
importSpecifiersByClass
|
|
5694
6061
|
};
|
|
5695
6062
|
}
|
|
6063
|
+
function assertCustomPomAttachmentsResolved(attachments, classIdentifierMap, configuredDir) {
|
|
6064
|
+
const missingClassNames = Array.from(new Set(
|
|
6065
|
+
attachments.map((attachment) => attachment.className).filter((className) => !Object.prototype.hasOwnProperty.call(classIdentifierMap, className))
|
|
6066
|
+
)).sort((left, right) => left.localeCompare(right));
|
|
6067
|
+
if (missingClassNames.length > 0) {
|
|
6068
|
+
throw createMissingCustomPomAttachmentClassError(missingClassNames, configuredDir);
|
|
6069
|
+
}
|
|
6070
|
+
}
|
|
5696
6071
|
function getComposedStubBody(targetClassName, availableClassNames, depsByClassName, vueFilesPathMap, projectRoot) {
|
|
5697
6072
|
const filePath = resolveVueSourcePath(targetClassName, vueFilesPathMap, projectRoot);
|
|
5698
6073
|
if (!filePath)
|
|
@@ -5721,7 +6096,7 @@ function getComposedStubBody(targetClassName, availableClassNames, depsByClassNa
|
|
|
5721
6096
|
if (!sig)
|
|
5722
6097
|
continue;
|
|
5723
6098
|
const list = methodToChildren.get(name) ?? [];
|
|
5724
|
-
list.push({ child,
|
|
6099
|
+
list.push({ child, signature: sig });
|
|
5725
6100
|
methodToChildren.set(name, list);
|
|
5726
6101
|
}
|
|
5727
6102
|
}
|
|
@@ -5729,12 +6104,12 @@ function getComposedStubBody(targetClassName, availableClassNames, depsByClassNa
|
|
|
5729
6104
|
for (const [methodName, candidatesForMethod] of methodToChildren.entries()) {
|
|
5730
6105
|
if (candidatesForMethod.length !== 1 || methodName === "constructor")
|
|
5731
6106
|
continue;
|
|
5732
|
-
const { child,
|
|
5733
|
-
const callArgs =
|
|
6107
|
+
const { child, signature } = candidatesForMethod[0];
|
|
6108
|
+
const callArgs = getPomParameterArgumentNames(signature.parameters).join(", ");
|
|
5734
6109
|
passthroughMembers.push(createClassMethod({
|
|
5735
6110
|
name: methodName,
|
|
5736
6111
|
isAsync: true,
|
|
5737
|
-
parameters:
|
|
6112
|
+
parameters: toTypeScriptPomParameterStructures(signature.parameters),
|
|
5738
6113
|
statements: [
|
|
5739
6114
|
`return await this.${child}.${methodName}(${callArgs});`
|
|
5740
6115
|
]
|
|
@@ -5783,9 +6158,15 @@ async function generateAggregatedFiles(componentHierarchyMap, vueFilesPathMap, b
|
|
|
5783
6158
|
imports.push(`export * from "${runtimeClassGenRel}/base-page";`);
|
|
5784
6159
|
const customPomImportResolution = resolveCustomPomImportResolution(generatedClassNames, projectRoot, {
|
|
5785
6160
|
customPomDir: options.customPomDir,
|
|
6161
|
+
requireCustomPomDir: options.requireCustomPomDir,
|
|
5786
6162
|
customPomImportAliases: options.customPomImportAliases,
|
|
5787
6163
|
customPomImportNameCollisionBehavior: options.customPomImportNameCollisionBehavior
|
|
5788
6164
|
});
|
|
6165
|
+
assertCustomPomAttachmentsResolved(
|
|
6166
|
+
options.customPomAttachments ?? [],
|
|
6167
|
+
customPomImportResolution.classIdentifierMap,
|
|
6168
|
+
options.customPomDir ?? "tests/playwright/pom/custom"
|
|
6169
|
+
);
|
|
5789
6170
|
const customPomClassIdentifierMap = customPomImportResolution.classIdentifierMap;
|
|
5790
6171
|
const customPomMethodSignaturesByClass = customPomImportResolution.methodSignaturesByClass;
|
|
5791
6172
|
const customPomAvailableClassIdentifiers = customPomImportResolution.availableClassIdentifiers;
|
|
@@ -5935,8 +6316,8 @@ function getWidgetInstancesForView(componentName, dataTestIdSet, availableClassI
|
|
|
5935
6316
|
return candidate;
|
|
5936
6317
|
};
|
|
5937
6318
|
for (const dt of dataTestIdSet) {
|
|
5938
|
-
const raw = dt.
|
|
5939
|
-
if (
|
|
6319
|
+
const raw = dt.selectorValue.formatted;
|
|
6320
|
+
if (isParameterizedPomPattern(dt.selectorValue.patternKind)) {
|
|
5940
6321
|
continue;
|
|
5941
6322
|
}
|
|
5942
6323
|
const toggleSuffix = "-toggle";
|
|
@@ -6026,7 +6407,6 @@ function getConstructor(childrenComponent, componentHierarchyMap, attachmentsFor
|
|
|
6026
6407
|
});
|
|
6027
6408
|
}
|
|
6028
6409
|
const TESTID_CLICK_EVENT_NAME = "__testid_event__";
|
|
6029
|
-
const TESTID_CLICK_EVENT_STRICT_FLAG = "__testid_click_event_strict__";
|
|
6030
6410
|
const CLICK_EVENT_NAME = TESTID_CLICK_EVENT_NAME;
|
|
6031
6411
|
const inferredNativeWrapperConfigByLookup = /* @__PURE__ */ new Map();
|
|
6032
6412
|
const inferredSfcPathByLookup = /* @__PURE__ */ new Map();
|
|
@@ -6288,7 +6668,7 @@ function getConditionalDirectiveInfo(element) {
|
|
|
6288
6668
|
if (directive.name === "else") {
|
|
6289
6669
|
const exp2 = directive.exp;
|
|
6290
6670
|
if (exp2 && (exp2.type === compilerCore.NodeTypes.SIMPLE_EXPRESSION || exp2.type === compilerCore.NodeTypes.COMPOUND_EXPRESSION)) {
|
|
6291
|
-
const source2 = (exp2
|
|
6671
|
+
const source2 = getVueExpressionSource(exp2, "content", "compiled");
|
|
6292
6672
|
return { kind: "else-if", source: source2 };
|
|
6293
6673
|
}
|
|
6294
6674
|
return { kind: "else", source: "" };
|
|
@@ -6297,13 +6677,13 @@ function getConditionalDirectiveInfo(element) {
|
|
|
6297
6677
|
const exp2 = directive.exp;
|
|
6298
6678
|
if (!exp2 || exp2.type !== compilerCore.NodeTypes.SIMPLE_EXPRESSION && exp2.type !== compilerCore.NodeTypes.COMPOUND_EXPRESSION)
|
|
6299
6679
|
return null;
|
|
6300
|
-
const source2 = (exp2
|
|
6680
|
+
const source2 = getVueExpressionSource(exp2, "content", "compiled");
|
|
6301
6681
|
return { kind: "else-if", source: source2 };
|
|
6302
6682
|
}
|
|
6303
6683
|
const exp = directive.exp;
|
|
6304
6684
|
if (!exp || exp.type !== compilerCore.NodeTypes.SIMPLE_EXPRESSION && exp.type !== compilerCore.NodeTypes.COMPOUND_EXPRESSION)
|
|
6305
6685
|
return null;
|
|
6306
|
-
const source = (exp
|
|
6686
|
+
const source = getVueExpressionSource(exp, "content", "compiled");
|
|
6307
6687
|
return { kind: directive.name, source };
|
|
6308
6688
|
}
|
|
6309
6689
|
function tryExtractStableHintFromConditionalExpressionSource(source) {
|
|
@@ -6329,26 +6709,26 @@ function tryExtractStableHintFromConditionalExpressionSource(source) {
|
|
|
6329
6709
|
};
|
|
6330
6710
|
try {
|
|
6331
6711
|
const expr = parser.parseExpression(src, { plugins: ["typescript"] });
|
|
6332
|
-
const
|
|
6712
|
+
const isNodeType2 = (n, type) => {
|
|
6333
6713
|
return n !== null && n.type === type;
|
|
6334
6714
|
};
|
|
6335
|
-
const
|
|
6336
|
-
return
|
|
6715
|
+
const isStringLiteralNode2 = (n) => {
|
|
6716
|
+
return isNodeType2(n, "StringLiteral") && typeof n.value === "string";
|
|
6337
6717
|
};
|
|
6338
|
-
const
|
|
6339
|
-
return
|
|
6718
|
+
const isIdentifierNode2 = (n) => {
|
|
6719
|
+
return isNodeType2(n, "Identifier") && typeof n.name === "string";
|
|
6340
6720
|
};
|
|
6341
6721
|
const results = [];
|
|
6342
6722
|
const walk = (n) => {
|
|
6343
6723
|
if (!n)
|
|
6344
6724
|
return;
|
|
6345
|
-
if (
|
|
6725
|
+
if (isStringLiteralNode2(n)) {
|
|
6346
6726
|
const v = (n.value ?? "").trim();
|
|
6347
6727
|
if (isIdentifierish(v)) {
|
|
6348
6728
|
results.push(v);
|
|
6349
6729
|
}
|
|
6350
6730
|
}
|
|
6351
|
-
if (
|
|
6731
|
+
if (isIdentifierNode2(n)) {
|
|
6352
6732
|
const v = (n.name ?? "").trim();
|
|
6353
6733
|
if (isIdentifierish(v)) {
|
|
6354
6734
|
results.push(v);
|
|
@@ -6498,34 +6878,20 @@ ${buildSearchRootsKey(normalizedSearchRoots)}`;
|
|
|
6498
6878
|
inferredNativeWrapperConfigByLookup.set(cacheKey, { role: "" });
|
|
6499
6879
|
return null;
|
|
6500
6880
|
}
|
|
6501
|
-
function tryWrapClickDirectiveForTestEvents(element, testIdAttribute) {
|
|
6881
|
+
function tryWrapClickDirectiveForTestEvents(element, testIdAttribute, resolvedRuntimeTestId) {
|
|
6502
6882
|
const jsStringLiteral = (value) => {
|
|
6503
6883
|
return JSON.stringify(value);
|
|
6504
6884
|
};
|
|
6505
6885
|
const getTestIdExpressionForNode = () => {
|
|
6506
|
-
|
|
6507
|
-
if (!existing) {
|
|
6508
|
-
return "undefined";
|
|
6509
|
-
}
|
|
6510
|
-
if (existing.type === compilerCore.NodeTypes.ATTRIBUTE) {
|
|
6511
|
-
const v = existing.value?.content;
|
|
6512
|
-
if (!v) {
|
|
6513
|
-
return "undefined";
|
|
6514
|
-
}
|
|
6515
|
-
return jsStringLiteral(v);
|
|
6516
|
-
}
|
|
6517
|
-
const directive = existing;
|
|
6518
|
-
const exp2 = directive.exp;
|
|
6519
|
-
if (!exp2 || exp2.type !== compilerCore.NodeTypes.SIMPLE_EXPRESSION) {
|
|
6886
|
+
if (!resolvedRuntimeTestId) {
|
|
6520
6887
|
return "undefined";
|
|
6521
6888
|
}
|
|
6522
|
-
|
|
6523
|
-
|
|
6524
|
-
return "undefined";
|
|
6889
|
+
if (resolvedRuntimeTestId.kind === "static") {
|
|
6890
|
+
return jsStringLiteral(resolvedRuntimeTestId.value);
|
|
6525
6891
|
}
|
|
6526
|
-
return `(${
|
|
6892
|
+
return `(${renderTemplateLiteralExpression(resolvedRuntimeTestId)})`;
|
|
6527
6893
|
};
|
|
6528
|
-
const
|
|
6894
|
+
const testIdExpression = getTestIdExpressionForNode();
|
|
6529
6895
|
const clickDirective = tryGetClickDirective(element);
|
|
6530
6896
|
if (!clickDirective)
|
|
6531
6897
|
return;
|
|
@@ -6536,10 +6902,10 @@ function tryWrapClickDirectiveForTestEvents(element, testIdAttribute) {
|
|
|
6536
6902
|
const exp = clickDirective.exp;
|
|
6537
6903
|
if (!exp)
|
|
6538
6904
|
return;
|
|
6539
|
-
const existingSource = (exp
|
|
6905
|
+
const existingSource = getVueExpressionSource(exp, "loc", "content");
|
|
6540
6906
|
if (existingSource.includes(CLICK_EVENT_NAME))
|
|
6541
6907
|
return;
|
|
6542
|
-
const originalExpression = (exp
|
|
6908
|
+
const originalExpression = getVueExpressionSource(exp, "content", "loc");
|
|
6543
6909
|
if (!originalExpression)
|
|
6544
6910
|
return;
|
|
6545
6911
|
const isStatementBody = (() => {
|
|
@@ -6556,7 +6922,7 @@ function tryWrapClickDirectiveForTestEvents(element, testIdAttribute) {
|
|
|
6556
6922
|
const statementWrappedHandler = `($event) => {
|
|
6557
6923
|
const __win = ($event && $event.view) ? $event.view : undefined;
|
|
6558
6924
|
const __target = ($event && $event.currentTarget) ? $event.currentTarget : undefined;
|
|
6559
|
-
const __testIdFromNode = ${
|
|
6925
|
+
const __testIdFromNode = ${testIdExpression};
|
|
6560
6926
|
const __testIdFromTarget = (__target && typeof __target.getAttribute === 'function') ? __target.getAttribute(${jsStringLiteral(testIdAttribute)}) : undefined;
|
|
6561
6927
|
const __testId = (__testIdFromNode ?? __testIdFromTarget);
|
|
6562
6928
|
const __emit = (phase, err) => {
|
|
@@ -6567,16 +6933,12 @@ function tryWrapClickDirectiveForTestEvents(element, testIdAttribute) {
|
|
|
6567
6933
|
__w.dispatchEvent(new __CustomEvent('${CLICK_EVENT_NAME}', { detail: { testId: __testId, phase, err: err ? String(err) : undefined } }));
|
|
6568
6934
|
}
|
|
6569
6935
|
} catch (e) {
|
|
6570
|
-
// Instrumentation
|
|
6571
|
-
// In strict mode we rethrow so tests fail fast and the underlying problem is visible.
|
|
6572
|
-
// Outside strict mode we log and continue so we don't break real user clicks.
|
|
6936
|
+
// Instrumentation failures should never be silent. Log the root cause and fail fast.
|
|
6573
6937
|
const __w = __win || (__target && __target.ownerDocument && __target.ownerDocument.defaultView);
|
|
6574
6938
|
if (__w && __w.console && typeof __w.console.error === 'function') {
|
|
6575
6939
|
__w.console.error('[testid-click-event] failed to emit ${CLICK_EVENT_NAME}', e);
|
|
6576
6940
|
}
|
|
6577
|
-
|
|
6578
|
-
throw e;
|
|
6579
|
-
}
|
|
6941
|
+
throw e;
|
|
6580
6942
|
}
|
|
6581
6943
|
};
|
|
6582
6944
|
const __w2 = __win || (__target && __target.ownerDocument && __target.ownerDocument.defaultView);
|
|
@@ -6607,7 +6969,7 @@ function tryWrapClickDirectiveForTestEvents(element, testIdAttribute) {
|
|
|
6607
6969
|
const expressionWrappedHandler = `($event) => {
|
|
6608
6970
|
const __win = ($event && $event.view) ? $event.view : undefined;
|
|
6609
6971
|
const __target = ($event && $event.currentTarget) ? $event.currentTarget : undefined;
|
|
6610
|
-
const __testIdFromNode = ${
|
|
6972
|
+
const __testIdFromNode = ${testIdExpression};
|
|
6611
6973
|
const __testIdFromTarget = (__target && typeof __target.getAttribute === 'function') ? __target.getAttribute(${jsStringLiteral(testIdAttribute)}) : undefined;
|
|
6612
6974
|
const __testId = (__testIdFromNode ?? __testIdFromTarget);
|
|
6613
6975
|
const __emit = (phase, err) => {
|
|
@@ -6618,16 +6980,12 @@ function tryWrapClickDirectiveForTestEvents(element, testIdAttribute) {
|
|
|
6618
6980
|
__w.dispatchEvent(new __CustomEvent('${CLICK_EVENT_NAME}', { detail: { testId: __testId, phase, err: err ? String(err) : undefined } }));
|
|
6619
6981
|
}
|
|
6620
6982
|
} catch (e) {
|
|
6621
|
-
// Instrumentation
|
|
6622
|
-
// In strict mode we rethrow so tests fail fast and the underlying problem is visible.
|
|
6623
|
-
// Outside strict mode we log and continue so we don't break real user clicks.
|
|
6983
|
+
// Instrumentation failures should never be silent. Log the root cause and fail fast.
|
|
6624
6984
|
const __w = __win || (__target && __target.ownerDocument && __target.ownerDocument.defaultView);
|
|
6625
6985
|
if (__w && __w.console && typeof __w.console.error === 'function') {
|
|
6626
6986
|
__w.console.error('[testid-click-event] failed to emit ${CLICK_EVENT_NAME}', e);
|
|
6627
6987
|
}
|
|
6628
|
-
|
|
6629
|
-
throw e;
|
|
6630
|
-
}
|
|
6988
|
+
throw e;
|
|
6631
6989
|
}
|
|
6632
6990
|
};
|
|
6633
6991
|
const __w2 = __win || (__target && __target.ownerDocument && __target.ownerDocument.defaultView);
|
|
@@ -6665,10 +7023,9 @@ function tryWrapClickDirectiveForTestEvents(element, testIdAttribute) {
|
|
|
6665
7023
|
let previousFileName = "";
|
|
6666
7024
|
const hierarchyMap = /* @__PURE__ */ new Map();
|
|
6667
7025
|
function createTestIdTransform(componentName, componentHierarchyMap, nativeWrappers = {}, excludedComponents = [], viewsDirAbs, options = {}) {
|
|
6668
|
-
const existingIdBehavior = options.existingIdBehavior ?? "
|
|
7026
|
+
const existingIdBehavior = options.existingIdBehavior ?? "error";
|
|
6669
7027
|
const testIdAttribute = (options.testIdAttribute || "data-testid").trim() || "data-testid";
|
|
6670
|
-
const nameCollisionBehavior = options.nameCollisionBehavior ?? "
|
|
6671
|
-
const missingSemanticNameBehavior = options.missingSemanticNameBehavior ?? "error";
|
|
7028
|
+
const nameCollisionBehavior = options.nameCollisionBehavior ?? "error";
|
|
6672
7029
|
const warn = options.warn;
|
|
6673
7030
|
const vueFilesPathMap = options.vueFilesPathMap;
|
|
6674
7031
|
const wrapperSearchRoots = options.wrapperSearchRoots ?? [];
|
|
@@ -6744,7 +7101,7 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
|
|
|
6744
7101
|
conditionalHintByIfBranch.set(branch, hint);
|
|
6745
7102
|
continue;
|
|
6746
7103
|
}
|
|
6747
|
-
const condSource = (cond
|
|
7104
|
+
const condSource = getVueExpressionSource(cond, "content", "compiled");
|
|
6748
7105
|
const stable = tryExtractStableHintFromConditionalExpressionSource(condSource);
|
|
6749
7106
|
if (stable) {
|
|
6750
7107
|
conditionalHintByIfBranch.set(branch, stable);
|
|
@@ -6806,17 +7163,18 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
|
|
|
6806
7163
|
nativeWrappers[element.tag] = { role: "grid" };
|
|
6807
7164
|
}
|
|
6808
7165
|
}
|
|
6809
|
-
const
|
|
7166
|
+
const getBestAvailableKeyInfo = () => {
|
|
6810
7167
|
const parentNode = context.parent && typeof context.parent === "object" ? context.parent : null;
|
|
6811
7168
|
const isDirectVForChild = parentNode?.type === compilerCore.NodeTypes.FOR;
|
|
6812
|
-
const
|
|
6813
|
-
if (
|
|
6814
|
-
|
|
7169
|
+
const vForKeyInfo = (isDirectVForChild ? getKeyDirectiveInfo(element) : null) || getContainedInVForDirectiveKeyInfo(context, element, hierarchyMap);
|
|
7170
|
+
if (vForKeyInfo) {
|
|
7171
|
+
return vForKeyInfo;
|
|
7172
|
+
}
|
|
7173
|
+
return getContainedInSlotDataKeyInfo(element, hierarchyMap);
|
|
6815
7174
|
};
|
|
6816
|
-
const
|
|
6817
|
-
const
|
|
6818
|
-
const
|
|
6819
|
-
const bestKeyVariable = isSlotKey ? bestKeyInferred : null;
|
|
7175
|
+
const bestKeyInfo = getBestAvailableKeyInfo();
|
|
7176
|
+
const bestKeyPlaceholder = bestKeyInfo?.selectorFragment ?? null;
|
|
7177
|
+
const bestRuntimeKeyPlaceholder = bestKeyInfo?.runtimeFragment ?? null;
|
|
6820
7178
|
const keyValuesOverride = tryGetContainedInStaticVForSourceLiteralValues(context);
|
|
6821
7179
|
const parentKey = context?.parent ? context.parent : null;
|
|
6822
7180
|
const conditional = getConditionalDirectiveInfo(element);
|
|
@@ -6859,7 +7217,7 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
|
|
|
6859
7217
|
if (!cond) {
|
|
6860
7218
|
conditionalHint = "else";
|
|
6861
7219
|
} else {
|
|
6862
|
-
const condSource = (cond
|
|
7220
|
+
const condSource = getVueExpressionSource(cond, "content", "compiled");
|
|
6863
7221
|
conditionalHint = tryExtractStableHintFromConditionalExpressionSource(condSource) ?? "if";
|
|
6864
7222
|
}
|
|
6865
7223
|
}
|
|
@@ -6869,7 +7227,7 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
|
|
|
6869
7227
|
});
|
|
6870
7228
|
if (showDirective?.exp && (showDirective.exp.type === compilerCore.NodeTypes.SIMPLE_EXPRESSION || showDirective.exp.type === compilerCore.NodeTypes.COMPOUND_EXPRESSION)) {
|
|
6871
7229
|
const exp = showDirective.exp;
|
|
6872
|
-
const source = (exp
|
|
7230
|
+
const source = getVueExpressionSource(exp, "content", "compiled");
|
|
6873
7231
|
const showHint = tryExtractStableHintFromConditionalExpressionSource(source);
|
|
6874
7232
|
if (showHint) {
|
|
6875
7233
|
conditionalHint = conditionalHint ? `${conditionalHint} ${showHint}` : showHint;
|
|
@@ -6903,13 +7261,17 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
|
|
|
6903
7261
|
const tagSuffix = getTagSuffix();
|
|
6904
7262
|
return bestKeyPlaceholder ? templateAttributeValue(`${componentName}-${bestKeyPlaceholder}${clickSuffix}${tagSuffix}`) : staticAttributeValue(`${componentName}${clickSuffix}${tagSuffix}`);
|
|
6905
7263
|
};
|
|
7264
|
+
const getClickRuntimeDataTestId = (clickSuffix) => {
|
|
7265
|
+
const tagSuffix = getTagSuffix();
|
|
7266
|
+
return bestRuntimeKeyPlaceholder ? templateAttributeValue(`${componentName}-${bestRuntimeKeyPlaceholder}${clickSuffix}${tagSuffix}`) : staticAttributeValue(`${componentName}${clickSuffix}${tagSuffix}`);
|
|
7267
|
+
};
|
|
6906
7268
|
const getSubmitDataTestId = (identifier) => {
|
|
6907
7269
|
const tagSuffix = getTagSuffix();
|
|
6908
7270
|
return `${componentName}-${identifier}${tagSuffix}`;
|
|
6909
7271
|
};
|
|
6910
7272
|
const applyResolvedDataTestIdForElement = (args) => {
|
|
6911
7273
|
const nativeRole = args.nativeRoleOverride ?? getNativeRoleFromTagSuffix();
|
|
6912
|
-
applyResolvedDataTestId({
|
|
7274
|
+
return applyResolvedDataTestId({
|
|
6913
7275
|
element,
|
|
6914
7276
|
componentName,
|
|
6915
7277
|
parentComponentName,
|
|
@@ -6919,8 +7281,8 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
|
|
|
6919
7281
|
generatedMethodContentByComponent,
|
|
6920
7282
|
nativeRole,
|
|
6921
7283
|
preferredGeneratedValue: args.preferredGeneratedValue,
|
|
6922
|
-
|
|
6923
|
-
|
|
7284
|
+
preferredRuntimeValue: args.preferredRuntimeValue,
|
|
7285
|
+
keyInfo: bestKeyInfo,
|
|
6924
7286
|
keyValuesOverride,
|
|
6925
7287
|
entryOverrides: args.entryOverrides,
|
|
6926
7288
|
semanticNameHint: args.semanticNameHint,
|
|
@@ -6938,7 +7300,7 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
|
|
|
6938
7300
|
return p.type === compilerCore.NodeTypes.DIRECTIVE && p.name === "bind" && p.arg?.type === compilerCore.NodeTypes.SIMPLE_EXPRESSION && p.arg.content === "handler" && !!p.exp;
|
|
6939
7301
|
}) ?? null;
|
|
6940
7302
|
const handlerInfo = handlerDirective ? nodeHandlerAttributeInfo(element) : null;
|
|
6941
|
-
if (
|
|
7303
|
+
if (nativeWrappers[element.tag]?.role === "button" && handlerDirective && !handlerInfo) {
|
|
6942
7304
|
const loc = element.loc?.start;
|
|
6943
7305
|
const locationHint = loc ? `${loc.line}:${loc.column}` : "unknown";
|
|
6944
7306
|
const handlerSource = (handlerDirective.exp?.loc?.source ?? "").trim() || "<unknown>";
|
|
@@ -6947,7 +7309,7 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
|
|
|
6947
7309
|
Element: <${element.tag}>
|
|
6948
7310
|
Handler: ${handlerSource}
|
|
6949
7311
|
|
|
6950
|
-
Fix: move complex inline logic into a named function (for example, const onAction = () => ...; then bind :handler="onAction"), or simplify the handler to a direct identifier/call the generator can name
|
|
7312
|
+
Fix: move complex inline logic into a named function (for example, const onAction = () => ...; then bind :handler="onAction"), or simplify the handler to a direct identifier/call the generator can name.`
|
|
6951
7313
|
);
|
|
6952
7314
|
}
|
|
6953
7315
|
if (nativeWrappersValue) {
|
|
@@ -7074,20 +7436,20 @@ Fix: remove the explicit ${attrLabel}, or change existingIdBehavior to "overwrit
|
|
|
7074
7436
|
contextFilename: context.filename
|
|
7075
7437
|
});
|
|
7076
7438
|
const clickHint = trimLeadingSeparators(clickSuffix) || void 0;
|
|
7077
|
-
const idOrName =
|
|
7439
|
+
const idOrName = getStaticIdOrNameHint(element) || void 0;
|
|
7078
7440
|
const semanticHintCandidates = [clickHint, idOrName, innerText, conditionalHint].map((value) => (value ?? "").trim()).filter(Boolean).filter((value, index, values) => values.indexOf(value) === index);
|
|
7079
7441
|
const [semanticNameHint2, ...semanticNameHintAlternates] = semanticHintCandidates;
|
|
7080
7442
|
const pomMergeKey = clickHint ? `click:hint:${clickHint}` : void 0;
|
|
7081
7443
|
const testId = getClickDataTestId(clickSuffix);
|
|
7082
|
-
|
|
7444
|
+
const runtimeTestId = getClickRuntimeDataTestId(clickSuffix);
|
|
7445
|
+
const resolvedDataTestId = applyResolvedDataTestIdForElement({
|
|
7083
7446
|
preferredGeneratedValue: testId,
|
|
7447
|
+
preferredRuntimeValue: runtimeTestId,
|
|
7084
7448
|
semanticNameHint: semanticNameHint2,
|
|
7085
7449
|
semanticNameHintAlternates,
|
|
7086
7450
|
pomMergeKey
|
|
7087
7451
|
});
|
|
7088
|
-
|
|
7089
|
-
tryWrapClickDirectiveForTestEvents(element, testIdAttribute);
|
|
7090
|
-
}
|
|
7452
|
+
tryWrapClickDirectiveForTestEvents(element, testIdAttribute, resolvedDataTestId.runtimeValue);
|
|
7091
7453
|
return;
|
|
7092
7454
|
}
|
|
7093
7455
|
const existingElementDataTestId = tryGetExistingElementDataTestId(element, testIdAttribute);
|
|
@@ -7097,7 +7459,7 @@ Fix: remove the explicit ${attrLabel}, or change existingIdBehavior to "overwrit
|
|
|
7097
7459
|
if (!isRecognizedInteractiveRole) {
|
|
7098
7460
|
return;
|
|
7099
7461
|
}
|
|
7100
|
-
const identifierHint =
|
|
7462
|
+
const identifierHint = getStaticIdOrNameHint(element) || nodeHandlerAttributeValue(element) || innerText || existingElementDataTestId.value || conditionalHint || void 0;
|
|
7101
7463
|
const preferredGeneratedValue = existingElementDataTestId.isDynamic ? templateAttributeValue(existingElementDataTestId.template) : staticAttributeValue(existingElementDataTestId.value);
|
|
7102
7464
|
applyResolvedDataTestIdForElement({
|
|
7103
7465
|
preferredGeneratedValue,
|
|
@@ -7107,7 +7469,7 @@ Fix: remove the explicit ${attrLabel}, or change existingIdBehavior to "overwrit
|
|
|
7107
7469
|
}
|
|
7108
7470
|
const isSubmit = element.props.find((p) => p.type === compilerCore.NodeTypes.ATTRIBUTE && p.name === "type")?.value?.content === "submit";
|
|
7109
7471
|
if (isSubmit) {
|
|
7110
|
-
const identifier =
|
|
7472
|
+
const identifier = getStaticIdOrNameHint(element) || innerText;
|
|
7111
7473
|
if (!identifier) {
|
|
7112
7474
|
const loc = element.loc?.start;
|
|
7113
7475
|
const locationHint = loc ? `${loc.line}:${loc.column}` : "unknown";
|
|
@@ -7159,29 +7521,32 @@ function createBuildProcessorPlugin(options) {
|
|
|
7159
7521
|
getSourceDirs,
|
|
7160
7522
|
basePageClassPath,
|
|
7161
7523
|
normalizedBasePagePath,
|
|
7524
|
+
generation,
|
|
7525
|
+
projectRootRef,
|
|
7526
|
+
nativeWrappers,
|
|
7527
|
+
excludedComponents,
|
|
7528
|
+
getWrapperSearchRoots,
|
|
7529
|
+
getResolvedRouterEntry,
|
|
7530
|
+
loggerRef
|
|
7531
|
+
} = options;
|
|
7532
|
+
const {
|
|
7162
7533
|
outDir,
|
|
7163
7534
|
emitLanguages,
|
|
7164
7535
|
typescriptOutputStructure,
|
|
7165
7536
|
csharp,
|
|
7166
7537
|
generateFixtures,
|
|
7167
7538
|
customPomAttachments,
|
|
7168
|
-
projectRootRef,
|
|
7169
7539
|
customPomDir,
|
|
7540
|
+
requireCustomPomDir,
|
|
7170
7541
|
customPomImportAliases,
|
|
7171
7542
|
customPomImportNameCollisionBehavior,
|
|
7172
7543
|
testIdAttribute,
|
|
7173
7544
|
nameCollisionBehavior,
|
|
7174
|
-
missingSemanticNameBehavior = "error",
|
|
7175
7545
|
existingIdBehavior,
|
|
7176
|
-
nativeWrappers,
|
|
7177
|
-
excludedComponents,
|
|
7178
|
-
getWrapperSearchRoots,
|
|
7179
7546
|
routerAwarePoms,
|
|
7180
|
-
getResolvedRouterEntry,
|
|
7181
7547
|
routerType,
|
|
7182
|
-
routerModuleShims
|
|
7183
|
-
|
|
7184
|
-
} = options;
|
|
7548
|
+
routerModuleShims
|
|
7549
|
+
} = generation;
|
|
7185
7550
|
let lastGeneratedMetrics = {
|
|
7186
7551
|
entryCount: 0,
|
|
7187
7552
|
interactiveComponentCount: 0,
|
|
@@ -7280,10 +7645,9 @@ function createBuildProcessorPlugin(options) {
|
|
|
7280
7645
|
excludedComponents,
|
|
7281
7646
|
getViewsDirAbs(),
|
|
7282
7647
|
{
|
|
7283
|
-
existingIdBehavior: existingIdBehavior ?? "
|
|
7648
|
+
existingIdBehavior: existingIdBehavior ?? "error",
|
|
7284
7649
|
testIdAttribute,
|
|
7285
7650
|
nameCollisionBehavior,
|
|
7286
|
-
missingSemanticNameBehavior,
|
|
7287
7651
|
warn: (message) => loggerRef.current.warn(message),
|
|
7288
7652
|
vueFilesPathMap,
|
|
7289
7653
|
wrapperSearchRoots: getWrapperSearchRoots()
|
|
@@ -7383,6 +7747,7 @@ function createBuildProcessorPlugin(options) {
|
|
|
7383
7747
|
customPomAttachments,
|
|
7384
7748
|
projectRoot: projectRootRef.current,
|
|
7385
7749
|
customPomDir,
|
|
7750
|
+
requireCustomPomDir,
|
|
7386
7751
|
customPomImportAliases,
|
|
7387
7752
|
customPomImportNameCollisionBehavior,
|
|
7388
7753
|
testIdAttribute,
|
|
@@ -7414,6 +7779,11 @@ function createDevProcessorPlugin(options) {
|
|
|
7414
7779
|
projectRootRef,
|
|
7415
7780
|
normalizedBasePagePath,
|
|
7416
7781
|
basePageClassPath,
|
|
7782
|
+
generation,
|
|
7783
|
+
getResolvedRouterEntry,
|
|
7784
|
+
loggerRef
|
|
7785
|
+
} = options;
|
|
7786
|
+
const {
|
|
7417
7787
|
outDir,
|
|
7418
7788
|
emitLanguages,
|
|
7419
7789
|
typescriptOutputStructure,
|
|
@@ -7421,18 +7791,16 @@ function createDevProcessorPlugin(options) {
|
|
|
7421
7791
|
generateFixtures,
|
|
7422
7792
|
customPomAttachments,
|
|
7423
7793
|
customPomDir,
|
|
7794
|
+
requireCustomPomDir,
|
|
7424
7795
|
customPomImportAliases,
|
|
7425
7796
|
customPomImportNameCollisionBehavior,
|
|
7426
|
-
nameCollisionBehavior
|
|
7427
|
-
missingSemanticNameBehavior = "error",
|
|
7797
|
+
nameCollisionBehavior,
|
|
7428
7798
|
existingIdBehavior,
|
|
7429
7799
|
testIdAttribute,
|
|
7430
7800
|
routerAwarePoms,
|
|
7431
|
-
getResolvedRouterEntry,
|
|
7432
7801
|
routerType,
|
|
7433
|
-
routerModuleShims
|
|
7434
|
-
|
|
7435
|
-
} = options;
|
|
7802
|
+
routerModuleShims
|
|
7803
|
+
} = generation;
|
|
7436
7804
|
let scheduleVueFileRegen = null;
|
|
7437
7805
|
const getProjectRootCandidates = () => Array.from(/* @__PURE__ */ new Set([
|
|
7438
7806
|
path.resolve(projectRootRef.current),
|
|
@@ -7619,9 +7987,8 @@ function createDevProcessorPlugin(options) {
|
|
|
7619
7987
|
excludedComponents,
|
|
7620
7988
|
getViewsDirAbs(),
|
|
7621
7989
|
{
|
|
7622
|
-
existingIdBehavior: existingIdBehavior ?? "
|
|
7990
|
+
existingIdBehavior: existingIdBehavior ?? "error",
|
|
7623
7991
|
nameCollisionBehavior,
|
|
7624
|
-
missingSemanticNameBehavior,
|
|
7625
7992
|
testIdAttribute,
|
|
7626
7993
|
warn: (message) => loggerRef.current.warn(message),
|
|
7627
7994
|
vueFilesPathMap: provisionalVuePathMap,
|
|
@@ -7672,6 +8039,7 @@ function createDevProcessorPlugin(options) {
|
|
|
7672
8039
|
customPomAttachments,
|
|
7673
8040
|
projectRoot: projectRootRef.current,
|
|
7674
8041
|
customPomDir,
|
|
8042
|
+
requireCustomPomDir,
|
|
7675
8043
|
customPomImportAliases,
|
|
7676
8044
|
customPomImportNameCollisionBehavior,
|
|
7677
8045
|
pageDirs: getPageDirs(),
|
|
@@ -7846,8 +8214,119 @@ function createDevProcessorPlugin(options) {
|
|
|
7846
8214
|
}
|
|
7847
8215
|
};
|
|
7848
8216
|
}
|
|
7849
|
-
function
|
|
7850
|
-
const
|
|
8217
|
+
function removeByKeySegment(value) {
|
|
8218
|
+
const idx = value.indexOf("ByKey");
|
|
8219
|
+
if (idx < 0) {
|
|
8220
|
+
return value;
|
|
8221
|
+
}
|
|
8222
|
+
return value.slice(0, idx) + value.slice(idx + "ByKey".length);
|
|
8223
|
+
}
|
|
8224
|
+
function hasRoleSuffix(baseName, roleSuffix) {
|
|
8225
|
+
if (baseName.endsWith(roleSuffix)) {
|
|
8226
|
+
return true;
|
|
8227
|
+
}
|
|
8228
|
+
const re = new RegExp(`^${roleSuffix}\\d+$`);
|
|
8229
|
+
return re.test(baseName);
|
|
8230
|
+
}
|
|
8231
|
+
function getGeneratedPropertyName(pom) {
|
|
8232
|
+
if (pom.getterNameOverride) {
|
|
8233
|
+
return pom.getterNameOverride;
|
|
8234
|
+
}
|
|
8235
|
+
const roleSuffix = upperFirst(pom.nativeRole || "Element");
|
|
8236
|
+
const baseName = upperFirst(pom.methodName);
|
|
8237
|
+
const propertyName = hasRoleSuffix(baseName, roleSuffix) ? baseName : `${baseName}${roleSuffix}`;
|
|
8238
|
+
return pom.selector.patternKind === "parameterized" ? removeByKeySegment(propertyName) : propertyName;
|
|
8239
|
+
}
|
|
8240
|
+
function getGeneratedActionName(entry, pom) {
|
|
8241
|
+
const methodNameUpper = upperFirst(pom.methodName);
|
|
8242
|
+
const radioMethodNameUpper = upperFirst(pom.methodName || "Radio");
|
|
8243
|
+
const isNavigation = !!entry.targetPageObjectModelClass;
|
|
8244
|
+
if (isNavigation) {
|
|
8245
|
+
return `goTo${methodNameUpper}`;
|
|
8246
|
+
}
|
|
8247
|
+
switch (pom.nativeRole) {
|
|
8248
|
+
case "input":
|
|
8249
|
+
return `type${methodNameUpper}`;
|
|
8250
|
+
case "select":
|
|
8251
|
+
case "vselect":
|
|
8252
|
+
return `select${methodNameUpper}`;
|
|
8253
|
+
case "radio":
|
|
8254
|
+
return `select${radioMethodNameUpper}`;
|
|
8255
|
+
default:
|
|
8256
|
+
return `click${methodNameUpper}`;
|
|
8257
|
+
}
|
|
8258
|
+
}
|
|
8259
|
+
function matchesPrimarySelector(extraMethod, pom) {
|
|
8260
|
+
if (extraMethod.selector.kind !== "testId") {
|
|
8261
|
+
return false;
|
|
8262
|
+
}
|
|
8263
|
+
return extraMethod.selector.testId.formatted === pom.selector.formatted && extraMethod.selector.testId.patternKind === pom.selector.patternKind;
|
|
8264
|
+
}
|
|
8265
|
+
function getManifestEntry(componentName, entry, componentMetadata, extraMethods) {
|
|
8266
|
+
const testId = entry.selectorValue.formatted;
|
|
8267
|
+
const metadata = componentMetadata?.get(testId);
|
|
8268
|
+
const pom = entry.pom;
|
|
8269
|
+
const generatedActionName = pom ? getGeneratedActionName(entry, pom) : null;
|
|
8270
|
+
const extraActionNames = pom ? extraMethods.filter((extraMethod) => matchesPrimarySelector(extraMethod, pom)).map((extraMethod) => extraMethod.name).sort((a, b) => a.localeCompare(b)) : [];
|
|
8271
|
+
const generatedActionNames = Array.from(/* @__PURE__ */ new Set([
|
|
8272
|
+
...generatedActionName ? [generatedActionName] : [],
|
|
8273
|
+
...extraActionNames.filter((name) => name !== generatedActionName)
|
|
8274
|
+
]));
|
|
8275
|
+
return {
|
|
8276
|
+
testId,
|
|
8277
|
+
selectorPatternKind: entry.selectorValue.patternKind,
|
|
8278
|
+
semanticName: metadata?.semanticName ?? (pom ? humanizePomMethodName(pom.methodName) : testId),
|
|
8279
|
+
locatorDescription: pom ? buildPomLocatorDescription({
|
|
8280
|
+
componentName,
|
|
8281
|
+
methodName: pom.methodName,
|
|
8282
|
+
nativeRole: pom.nativeRole
|
|
8283
|
+
}) : componentName,
|
|
8284
|
+
inferredRole: pom?.nativeRole ?? null,
|
|
8285
|
+
generatedPropertyName: pom ? getGeneratedPropertyName(pom) : null,
|
|
8286
|
+
generatedActionName,
|
|
8287
|
+
generatedActionNames,
|
|
8288
|
+
emitPrimary: pom?.emitPrimary !== false,
|
|
8289
|
+
...entry.targetPageObjectModelClass ? { targetPageObjectModelClass: entry.targetPageObjectModelClass } : {},
|
|
8290
|
+
...metadata?.tag ? { sourceTag: metadata.tag } : {},
|
|
8291
|
+
...metadata ? { sourceTagType: metadata.tagType } : {},
|
|
8292
|
+
...metadata?.patchFlag !== void 0 ? { patchFlag: metadata.patchFlag } : {},
|
|
8293
|
+
...metadata?.dynamicProps?.length ? { dynamicProps: metadata.dynamicProps } : {},
|
|
8294
|
+
...metadata?.hasClickHandler !== void 0 ? { hasClickHandler: metadata.hasClickHandler } : {},
|
|
8295
|
+
...metadata?.hasDynamicClass !== void 0 ? { hasDynamicClass: metadata.hasDynamicClass } : {},
|
|
8296
|
+
...metadata?.hasDynamicStyle !== void 0 ? { hasDynamicStyle: metadata.hasDynamicStyle } : {},
|
|
8297
|
+
...metadata?.hasDynamicText !== void 0 ? { hasDynamicText: metadata.hasDynamicText } : {}
|
|
8298
|
+
};
|
|
8299
|
+
}
|
|
8300
|
+
function buildPomManifest(componentHierarchyMap, elementMetadata) {
|
|
8301
|
+
const manifestEntries = Array.from(componentHierarchyMap.entries()).sort((a, b) => a[0].localeCompare(b[0])).map(([componentName, dependencies]) => {
|
|
8302
|
+
const entries = Array.from(dependencies.dataTestIdSet).sort((a, b) => a.selectorValue.formatted.localeCompare(b.selectorValue.formatted)).map((entry) => getManifestEntry(componentName, entry, elementMetadata.get(componentName), dependencies.pomExtraMethods ?? []));
|
|
8303
|
+
if (!entries.length) {
|
|
8304
|
+
return null;
|
|
8305
|
+
}
|
|
8306
|
+
return [componentName, {
|
|
8307
|
+
componentName,
|
|
8308
|
+
className: componentName,
|
|
8309
|
+
sourceFile: dependencies.filePath,
|
|
8310
|
+
kind: dependencies.isView ? "view" : "component",
|
|
8311
|
+
testIds: Array.from(new Set(entries.map((entry) => entry.testId))),
|
|
8312
|
+
entries
|
|
8313
|
+
}];
|
|
8314
|
+
}).filter((entry) => entry !== null);
|
|
8315
|
+
return Object.fromEntries(manifestEntries);
|
|
8316
|
+
}
|
|
8317
|
+
function buildTestIdManifest(pomManifest) {
|
|
8318
|
+
return Object.fromEntries(
|
|
8319
|
+
Object.entries(pomManifest).map(([componentName, component]) => [componentName, Array.from(new Set(component.testIds)).sort((a, b) => a.localeCompare(b))])
|
|
8320
|
+
);
|
|
8321
|
+
}
|
|
8322
|
+
function writeConstJson(value) {
|
|
8323
|
+
return (writer) => {
|
|
8324
|
+
writer.write(`${JSON.stringify(value, null, 2)} as const`);
|
|
8325
|
+
};
|
|
8326
|
+
}
|
|
8327
|
+
function generateTestIdsModule(componentHierarchyMap, elementMetadata) {
|
|
8328
|
+
const pomManifest = buildPomManifest(componentHierarchyMap, elementMetadata);
|
|
8329
|
+
const testIdManifest = buildTestIdManifest(pomManifest);
|
|
7851
8330
|
return renderSourceFile("virtual-testids.ts", (sourceFile) => {
|
|
7852
8331
|
sourceFile.addStatements("// Virtual module: test id manifest");
|
|
7853
8332
|
sourceFile.addVariableStatement({
|
|
@@ -7855,16 +8334,15 @@ function generateTestIdsModule(componentTestIds) {
|
|
|
7855
8334
|
isExported: true,
|
|
7856
8335
|
declarations: [{
|
|
7857
8336
|
name: "testIdManifest",
|
|
7858
|
-
initializer: (
|
|
7859
|
-
|
|
7860
|
-
|
|
7861
|
-
|
|
7862
|
-
|
|
7863
|
-
|
|
7864
|
-
|
|
7865
|
-
|
|
7866
|
-
|
|
7867
|
-
}
|
|
8337
|
+
initializer: writeConstJson(testIdManifest)
|
|
8338
|
+
}]
|
|
8339
|
+
});
|
|
8340
|
+
sourceFile.addVariableStatement({
|
|
8341
|
+
declarationKind: tsMorph.VariableDeclarationKind.Const,
|
|
8342
|
+
isExported: true,
|
|
8343
|
+
declarations: [{
|
|
8344
|
+
name: "pomManifest",
|
|
8345
|
+
initializer: writeConstJson(pomManifest)
|
|
7868
8346
|
}]
|
|
7869
8347
|
});
|
|
7870
8348
|
sourceFile.addTypeAlias({
|
|
@@ -7877,27 +8355,67 @@ function generateTestIdsModule(componentTestIds) {
|
|
|
7877
8355
|
name: "ComponentName",
|
|
7878
8356
|
type: "keyof TestIdManifest"
|
|
7879
8357
|
});
|
|
8358
|
+
sourceFile.addTypeAlias({
|
|
8359
|
+
isExported: true,
|
|
8360
|
+
name: "PomManifest",
|
|
8361
|
+
type: "typeof pomManifest"
|
|
8362
|
+
});
|
|
8363
|
+
sourceFile.addTypeAlias({
|
|
8364
|
+
isExported: true,
|
|
8365
|
+
name: "PomManifestComponentName",
|
|
8366
|
+
type: "keyof PomManifest"
|
|
8367
|
+
});
|
|
8368
|
+
});
|
|
8369
|
+
}
|
|
8370
|
+
function generatePomManifestModule(componentHierarchyMap, elementMetadata) {
|
|
8371
|
+
const pomManifest = buildPomManifest(componentHierarchyMap, elementMetadata);
|
|
8372
|
+
return renderSourceFile("virtual-pom-manifest.ts", (sourceFile) => {
|
|
8373
|
+
sourceFile.addStatements("// Virtual module: richer POM discoverability manifest");
|
|
8374
|
+
sourceFile.addVariableStatement({
|
|
8375
|
+
declarationKind: tsMorph.VariableDeclarationKind.Const,
|
|
8376
|
+
isExported: true,
|
|
8377
|
+
declarations: [{
|
|
8378
|
+
name: "pomManifest",
|
|
8379
|
+
initializer: writeConstJson(pomManifest)
|
|
8380
|
+
}]
|
|
8381
|
+
});
|
|
8382
|
+
sourceFile.addTypeAlias({
|
|
8383
|
+
isExported: true,
|
|
8384
|
+
name: "PomManifest",
|
|
8385
|
+
type: "typeof pomManifest"
|
|
8386
|
+
});
|
|
8387
|
+
sourceFile.addTypeAlias({
|
|
8388
|
+
isExported: true,
|
|
8389
|
+
name: "PomManifestComponentName",
|
|
8390
|
+
type: "keyof PomManifest"
|
|
8391
|
+
});
|
|
7880
8392
|
});
|
|
7881
8393
|
}
|
|
7882
|
-
const
|
|
7883
|
-
const
|
|
7884
|
-
|
|
8394
|
+
const TEST_IDS_VIRTUAL_ID = "virtual:testids";
|
|
8395
|
+
const TEST_IDS_RESOLVED_ID = `\0${TEST_IDS_VIRTUAL_ID}`;
|
|
8396
|
+
const POM_MANIFEST_VIRTUAL_ID = "virtual:pom-manifest";
|
|
8397
|
+
const POM_MANIFEST_RESOLVED_ID = `\0${POM_MANIFEST_VIRTUAL_ID}`;
|
|
8398
|
+
function createTestIdsVirtualModulesPlugin(componentHierarchyMap, elementMetadata) {
|
|
7885
8399
|
return {
|
|
7886
8400
|
name: "vue-pom-generator:virtual-testids",
|
|
7887
8401
|
resolveId(id) {
|
|
7888
|
-
if (id ===
|
|
7889
|
-
return
|
|
8402
|
+
if (id === TEST_IDS_VIRTUAL_ID)
|
|
8403
|
+
return TEST_IDS_RESOLVED_ID;
|
|
8404
|
+
if (id === POM_MANIFEST_VIRTUAL_ID)
|
|
8405
|
+
return POM_MANIFEST_RESOLVED_ID;
|
|
7890
8406
|
},
|
|
7891
8407
|
load(id) {
|
|
7892
|
-
if (id ===
|
|
7893
|
-
return generateTestIdsModule(
|
|
8408
|
+
if (id === TEST_IDS_RESOLVED_ID)
|
|
8409
|
+
return generateTestIdsModule(componentHierarchyMap, elementMetadata);
|
|
8410
|
+
if (id === POM_MANIFEST_RESOLVED_ID)
|
|
8411
|
+
return generatePomManifestModule(componentHierarchyMap, elementMetadata);
|
|
7894
8412
|
}
|
|
7895
8413
|
};
|
|
7896
8414
|
}
|
|
7897
8415
|
function createSupportPlugins(options) {
|
|
7898
8416
|
const {
|
|
7899
|
-
componentTestIds,
|
|
7900
8417
|
componentHierarchyMap,
|
|
8418
|
+
elementMetadata,
|
|
7901
8419
|
vueFilesPathMap,
|
|
7902
8420
|
nativeWrappers,
|
|
7903
8421
|
excludedComponents,
|
|
@@ -7907,27 +8425,30 @@ function createSupportPlugins(options) {
|
|
|
7907
8425
|
getViewsDir,
|
|
7908
8426
|
getSourceDirs,
|
|
7909
8427
|
getWrapperSearchRoots,
|
|
7910
|
-
|
|
7911
|
-
|
|
7912
|
-
|
|
8428
|
+
generation,
|
|
8429
|
+
projectRootRef,
|
|
8430
|
+
basePageClassPath: basePageClassPathOverride,
|
|
8431
|
+
loggerRef
|
|
8432
|
+
} = options;
|
|
8433
|
+
const {
|
|
7913
8434
|
outDir,
|
|
7914
8435
|
emitLanguages,
|
|
7915
8436
|
typescriptOutputStructure,
|
|
7916
8437
|
csharp,
|
|
7917
|
-
routerAwarePoms,
|
|
7918
|
-
routerEntry,
|
|
7919
|
-
routerType,
|
|
7920
|
-
routerModuleShims,
|
|
7921
8438
|
generateFixtures,
|
|
7922
8439
|
customPomAttachments,
|
|
7923
|
-
projectRootRef,
|
|
7924
|
-
basePageClassPath: basePageClassPathOverride,
|
|
7925
8440
|
customPomDir,
|
|
8441
|
+
requireCustomPomDir,
|
|
7926
8442
|
customPomImportAliases,
|
|
7927
8443
|
customPomImportNameCollisionBehavior,
|
|
8444
|
+
nameCollisionBehavior,
|
|
8445
|
+
existingIdBehavior,
|
|
7928
8446
|
testIdAttribute,
|
|
7929
|
-
|
|
7930
|
-
|
|
8447
|
+
routerAwarePoms,
|
|
8448
|
+
routerEntry,
|
|
8449
|
+
routerType,
|
|
8450
|
+
routerModuleShims
|
|
8451
|
+
} = generation;
|
|
7931
8452
|
const resolveRouterEntry2 = () => {
|
|
7932
8453
|
if (!routerAwarePoms)
|
|
7933
8454
|
return void 0;
|
|
@@ -7956,27 +8477,12 @@ function createSupportPlugins(options) {
|
|
|
7956
8477
|
getSourceDirs,
|
|
7957
8478
|
basePageClassPath,
|
|
7958
8479
|
normalizedBasePagePath,
|
|
7959
|
-
|
|
7960
|
-
emitLanguages,
|
|
7961
|
-
typescriptOutputStructure,
|
|
7962
|
-
csharp,
|
|
7963
|
-
generateFixtures,
|
|
7964
|
-
customPomAttachments,
|
|
8480
|
+
generation,
|
|
7965
8481
|
projectRootRef,
|
|
7966
|
-
customPomDir,
|
|
7967
|
-
customPomImportAliases,
|
|
7968
|
-
customPomImportNameCollisionBehavior,
|
|
7969
|
-
testIdAttribute,
|
|
7970
|
-
nameCollisionBehavior,
|
|
7971
|
-
missingSemanticNameBehavior,
|
|
7972
|
-
existingIdBehavior,
|
|
7973
8482
|
nativeWrappers,
|
|
7974
8483
|
excludedComponents,
|
|
7975
8484
|
getWrapperSearchRoots,
|
|
7976
|
-
routerAwarePoms,
|
|
7977
|
-
routerType,
|
|
7978
8485
|
getResolvedRouterEntry: resolveRouterEntry2,
|
|
7979
|
-
routerModuleShims,
|
|
7980
8486
|
loggerRef
|
|
7981
8487
|
});
|
|
7982
8488
|
const devProcessor = createDevProcessorPlugin({
|
|
@@ -7991,26 +8497,11 @@ function createSupportPlugins(options) {
|
|
|
7991
8497
|
projectRootRef,
|
|
7992
8498
|
normalizedBasePagePath,
|
|
7993
8499
|
basePageClassPath,
|
|
7994
|
-
|
|
7995
|
-
emitLanguages,
|
|
7996
|
-
typescriptOutputStructure,
|
|
7997
|
-
csharp,
|
|
7998
|
-
generateFixtures,
|
|
7999
|
-
customPomAttachments,
|
|
8000
|
-
customPomDir,
|
|
8001
|
-
customPomImportAliases,
|
|
8002
|
-
customPomImportNameCollisionBehavior,
|
|
8003
|
-
nameCollisionBehavior,
|
|
8004
|
-
missingSemanticNameBehavior,
|
|
8005
|
-
existingIdBehavior,
|
|
8006
|
-
testIdAttribute,
|
|
8007
|
-
routerAwarePoms,
|
|
8008
|
-
routerType,
|
|
8500
|
+
generation,
|
|
8009
8501
|
getResolvedRouterEntry: resolveRouterEntry2,
|
|
8010
|
-
routerModuleShims,
|
|
8011
8502
|
loggerRef
|
|
8012
8503
|
});
|
|
8013
|
-
const virtualModules = createTestIdsVirtualModulesPlugin(
|
|
8504
|
+
const virtualModules = createTestIdsVirtualModulesPlugin(componentHierarchyMap, elementMetadata);
|
|
8014
8505
|
return [tsProcessor, devProcessor, virtualModules];
|
|
8015
8506
|
}
|
|
8016
8507
|
function findDataTestIdProp(element, attributeName = "data-testid") {
|
|
@@ -8344,8 +8835,7 @@ function createVuePluginWithTestIds(options) {
|
|
|
8344
8835
|
});
|
|
8345
8836
|
const api = viteVuePlugin?.api;
|
|
8346
8837
|
if (!api) {
|
|
8347
|
-
|
|
8348
|
-
return;
|
|
8838
|
+
throw new Error("[vue-pom-generator] Nuxt bridge could not find vite:vue plugin to patch.");
|
|
8349
8839
|
}
|
|
8350
8840
|
const currentOptions = api.options ?? {};
|
|
8351
8841
|
const currentTemplate = currentOptions.template ?? {};
|
|
@@ -8411,33 +8901,6 @@ function assertOneOf(value, allowed, name) {
|
|
|
8411
8901
|
}
|
|
8412
8902
|
throw new TypeError(`${name} must be one of: ${allowed.join(", ")}.`);
|
|
8413
8903
|
}
|
|
8414
|
-
function assertErrorBehavior(value, name) {
|
|
8415
|
-
if (!value) {
|
|
8416
|
-
return;
|
|
8417
|
-
}
|
|
8418
|
-
if (value === "ignore" || value === "error") {
|
|
8419
|
-
return;
|
|
8420
|
-
}
|
|
8421
|
-
if (typeof value !== "object" || Array.isArray(value)) {
|
|
8422
|
-
throw new TypeError(`${name} must be "ignore", "error", or an object.`);
|
|
8423
|
-
}
|
|
8424
|
-
const supportedKeys = /* @__PURE__ */ new Set(["missingSemanticNameBehavior"]);
|
|
8425
|
-
for (const key of Object.keys(value)) {
|
|
8426
|
-
if (!supportedKeys.has(key)) {
|
|
8427
|
-
throw new TypeError(`${name} contains unsupported key "${key}".`);
|
|
8428
|
-
}
|
|
8429
|
-
}
|
|
8430
|
-
assertOneOf(value.missingSemanticNameBehavior, ["ignore", "error"], `${name}.missingSemanticNameBehavior`);
|
|
8431
|
-
}
|
|
8432
|
-
function resolveMissingSemanticNameBehavior(value) {
|
|
8433
|
-
if (!value) {
|
|
8434
|
-
return "error";
|
|
8435
|
-
}
|
|
8436
|
-
if (value === "ignore" || value === "error") {
|
|
8437
|
-
return value;
|
|
8438
|
-
}
|
|
8439
|
-
return value.missingSemanticNameBehavior ?? "error";
|
|
8440
|
-
}
|
|
8441
8904
|
function readPackageJson(projectRoot) {
|
|
8442
8905
|
const packageJsonPath = path.join(projectRoot, "package.json");
|
|
8443
8906
|
if (!fs.existsSync(packageJsonPath)) {
|
|
@@ -8528,7 +8991,6 @@ function getSharedGeneratorState(key) {
|
|
|
8528
8991
|
let state = sharedGeneratorStateRegistry.get(key);
|
|
8529
8992
|
if (!state) {
|
|
8530
8993
|
state = {
|
|
8531
|
-
componentTestIds: /* @__PURE__ */ new Map(),
|
|
8532
8994
|
elementMetadata: /* @__PURE__ */ new Map(),
|
|
8533
8995
|
semanticNameMap: /* @__PURE__ */ new Map(),
|
|
8534
8996
|
componentHierarchyMap: /* @__PURE__ */ new Map(),
|
|
@@ -8546,7 +9008,7 @@ function applyTemplateCompilerOptionsToResolvedVuePlugin(config, templateCompile
|
|
|
8546
9008
|
'[vue-pom-generator] vuePluginOwnership="external" requires the resolved Vite Vue plugin, but none was found. Add vue() to your Vite plugins before spreading createVuePomGeneratorPlugins(...).'
|
|
8547
9009
|
);
|
|
8548
9010
|
}
|
|
8549
|
-
throw new Error("[vue-pom-generator] Nuxt
|
|
9011
|
+
throw new Error("[vue-pom-generator] Nuxt bridge could not find vite:vue plugin to patch.");
|
|
8550
9012
|
}
|
|
8551
9013
|
const currentOptions = viteVuePlugin.api.options ?? {};
|
|
8552
9014
|
const currentTemplate = currentOptions.template ?? {};
|
|
@@ -8603,18 +9065,23 @@ function createVuePomGeneratorPlugins(options = {}) {
|
|
|
8603
9065
|
const vueGenerationOptions = generationOptions;
|
|
8604
9066
|
const verbosity = options.logging?.verbosity ?? "warn";
|
|
8605
9067
|
const vueOptions = options.vueOptions;
|
|
8606
|
-
const
|
|
8607
|
-
|
|
8608
|
-
|
|
8609
|
-
|
|
8610
|
-
|
|
8611
|
-
|
|
8612
|
-
|
|
8613
|
-
|
|
8614
|
-
|
|
8615
|
-
|
|
8616
|
-
|
|
8617
|
-
|
|
9068
|
+
const resolvedInjectionOptionsRef = {
|
|
9069
|
+
current: resolveInjectionSupportOptions({
|
|
9070
|
+
isNuxt,
|
|
9071
|
+
viewsDir: injection.viewsDir,
|
|
9072
|
+
componentDirs: injection.componentDirs,
|
|
9073
|
+
layoutDirs: injection.layoutDirs,
|
|
9074
|
+
wrapperSearchRoots: injection.wrapperSearchRoots,
|
|
9075
|
+
nativeWrappers: injection.nativeWrappers,
|
|
9076
|
+
excludedComponents: injection.excludeComponents,
|
|
9077
|
+
existingIdBehavior: injection.existingIdBehavior,
|
|
9078
|
+
testIdAttribute: injection.attribute
|
|
9079
|
+
})
|
|
9080
|
+
};
|
|
9081
|
+
const resolvedInjectionOptions = resolvedInjectionOptionsRef.current;
|
|
9082
|
+
const nativeWrappers = resolvedInjectionOptions.nativeWrappers;
|
|
9083
|
+
const excludedComponents = resolvedInjectionOptions.excludedComponents;
|
|
9084
|
+
const testIdAttribute = resolvedInjectionOptions.testIdAttribute;
|
|
8618
9085
|
const routerEntry = !isNuxt ? vueGenerationOptions?.router?.entry : void 0;
|
|
8619
9086
|
const routerType = isNuxt ? "nuxt" : vueGenerationOptions?.router?.type ?? "vue-router";
|
|
8620
9087
|
const routerModuleShims = !isNuxt ? vueGenerationOptions?.router?.moduleShims : void 0;
|
|
@@ -8623,27 +9090,41 @@ function createVuePomGeneratorPlugins(options = {}) {
|
|
|
8623
9090
|
}
|
|
8624
9091
|
const vuePluginOwnership = isNuxt ? "external" : options.vuePluginOwnership ?? "internal";
|
|
8625
9092
|
const usesExternalVuePlugin = vuePluginOwnership === "external";
|
|
8626
|
-
const csharp = generationOptions?.csharp;
|
|
8627
|
-
const errorBehavior = options.errorBehavior;
|
|
8628
|
-
const missingSemanticNameBehavior = resolveMissingSemanticNameBehavior(errorBehavior);
|
|
8629
|
-
const typescriptOutputStructure = generationOptions?.playwright?.outputStructure ?? "aggregated";
|
|
8630
9093
|
const generateFixtures = generationOptions?.playwright?.fixtures;
|
|
8631
9094
|
const customPoms = generationOptions?.playwright?.customPoms;
|
|
8632
9095
|
const resolvedCustomPomAttachments = customPoms?.attachments ?? [];
|
|
8633
|
-
const resolvedCustomPomDir = customPoms?.dir ?? "tests/playwright/pom/custom";
|
|
8634
9096
|
const resolvedCustomPomImportAliases = customPoms?.importAliases;
|
|
8635
|
-
const
|
|
9097
|
+
const requireCustomPomDir = customPoms?.dir !== void 0 || resolvedCustomPomAttachments.length > 0 || Object.keys(resolvedCustomPomImportAliases ?? {}).length > 0;
|
|
9098
|
+
const resolvedGenerationOptions = resolveGenerationSupportOptions({
|
|
9099
|
+
outDir: generationOptions?.outDir,
|
|
9100
|
+
emitLanguages: generationOptions?.emit,
|
|
9101
|
+
typescriptOutputStructure: generationOptions?.playwright?.outputStructure,
|
|
9102
|
+
csharp: generationOptions?.csharp,
|
|
9103
|
+
generateFixtures,
|
|
9104
|
+
customPomAttachments: resolvedCustomPomAttachments,
|
|
9105
|
+
customPomDir: customPoms?.dir,
|
|
9106
|
+
requireCustomPomDir,
|
|
9107
|
+
customPomImportAliases: resolvedCustomPomImportAliases,
|
|
9108
|
+
customPomImportNameCollisionBehavior: customPoms?.importNameCollisionBehavior,
|
|
9109
|
+
nameCollisionBehavior: generationOptions?.nameCollisionBehavior,
|
|
9110
|
+
existingIdBehavior: resolvedInjectionOptions.existingIdBehavior,
|
|
9111
|
+
testIdAttribute,
|
|
9112
|
+
routerAwarePoms: typeof routerEntry === "string" && routerEntry.trim().length > 0 || routerType === "nuxt",
|
|
9113
|
+
routerEntry,
|
|
9114
|
+
routerType,
|
|
9115
|
+
routerModuleShims
|
|
9116
|
+
});
|
|
8636
9117
|
const basePageClassPathOverride = generationOptions?.basePageClassPath;
|
|
8637
|
-
const getPageDirs = () =>
|
|
9118
|
+
const getPageDirs = () => resolvedInjectionOptionsRef.current.pageDirs;
|
|
8638
9119
|
const getViewsDir = () => getPageDirs()[0] ?? "src/views";
|
|
8639
|
-
const getComponentDirs = () =>
|
|
8640
|
-
const getLayoutDirs = () =>
|
|
9120
|
+
const getComponentDirs = () => resolvedInjectionOptionsRef.current.componentDirs;
|
|
9121
|
+
const getLayoutDirs = () => resolvedInjectionOptionsRef.current.layoutDirs;
|
|
8641
9122
|
const getSourceDirs = () => Array.from(/* @__PURE__ */ new Set([
|
|
8642
9123
|
...getPageDirs(),
|
|
8643
9124
|
...getComponentDirs(),
|
|
8644
9125
|
...getLayoutDirs()
|
|
8645
9126
|
]));
|
|
8646
|
-
const getWrapperSearchRoots = () =>
|
|
9127
|
+
const getWrapperSearchRoots = () => resolvedInjectionOptionsRef.current.wrapperSearchRoots;
|
|
8647
9128
|
const sharedStateKey = JSON.stringify({
|
|
8648
9129
|
cwd: process.cwd(),
|
|
8649
9130
|
mode: isNuxt ? "nuxt" : "vue",
|
|
@@ -8651,9 +9132,9 @@ function createVuePomGeneratorPlugins(options = {}) {
|
|
|
8651
9132
|
componentDirs: isNuxt ? null : getComponentDirs(),
|
|
8652
9133
|
layoutDirs: isNuxt ? null : getLayoutDirs(),
|
|
8653
9134
|
wrapperSearchRoots: isNuxt ? null : getWrapperSearchRoots(),
|
|
8654
|
-
outDir,
|
|
9135
|
+
outDir: resolvedGenerationOptions.outDir,
|
|
8655
9136
|
testIdAttribute,
|
|
8656
|
-
routerType,
|
|
9137
|
+
routerType: resolvedGenerationOptions.routerType,
|
|
8657
9138
|
vuePluginOwnership
|
|
8658
9139
|
});
|
|
8659
9140
|
const sharedState = getSharedGeneratorState(sharedStateKey);
|
|
@@ -8674,23 +9155,22 @@ function createVuePomGeneratorPlugins(options = {}) {
|
|
|
8674
9155
|
if (isNuxt) {
|
|
8675
9156
|
const nuxtDiscovery = await loadNuxtProjectDiscovery(process.cwd());
|
|
8676
9157
|
projectRootRef.current = nuxtDiscovery.rootDir;
|
|
8677
|
-
|
|
8678
|
-
|
|
8679
|
-
|
|
8680
|
-
|
|
9158
|
+
resolvedInjectionOptionsRef.current = applyNuxtDiscoveryToInjectionOptions(
|
|
9159
|
+
resolvedInjectionOptionsRef.current,
|
|
9160
|
+
nuxtDiscovery
|
|
9161
|
+
);
|
|
8681
9162
|
}
|
|
8682
9163
|
assertNonEmptyString(testIdAttribute, "[vue-pom-generator] injection.attribute");
|
|
8683
9164
|
assertNonEmptyString(getViewsDir(), "[vue-pom-generator] injection.viewsDir");
|
|
8684
9165
|
assertNonEmptyStringArray(getComponentDirs(), "[vue-pom-generator] injection.componentDirs");
|
|
8685
9166
|
assertNonEmptyStringArray(getLayoutDirs(), "[vue-pom-generator] injection.layoutDirs");
|
|
8686
9167
|
assertNonEmptyStringArray(getWrapperSearchRoots(), "[vue-pom-generator] injection.wrapperSearchRoots");
|
|
8687
|
-
assertErrorBehavior(errorBehavior, "[vue-pom-generator] errorBehavior");
|
|
8688
9168
|
if (generationEnabled) {
|
|
8689
|
-
assertNonEmptyString(outDir, "[vue-pom-generator] generation.outDir");
|
|
8690
|
-
assertOneOf(typescriptOutputStructure, ["aggregated", "split"], "[vue-pom-generator] generation.playwright.outputStructure");
|
|
8691
|
-
assertRouterModuleShims(routerModuleShims, "[vue-pom-generator] generation.router.moduleShims");
|
|
8692
|
-
if (!isNuxt && vueGenerationOptions?.router && routerType === "vue-router") {
|
|
8693
|
-
assertNonEmptyString(routerEntry, "[vue-pom-generator] generation.router.entry");
|
|
9169
|
+
assertNonEmptyString(resolvedGenerationOptions.outDir, "[vue-pom-generator] generation.outDir");
|
|
9170
|
+
assertOneOf(resolvedGenerationOptions.typescriptOutputStructure, ["aggregated", "split"], "[vue-pom-generator] generation.playwright.outputStructure");
|
|
9171
|
+
assertRouterModuleShims(resolvedGenerationOptions.routerModuleShims, "[vue-pom-generator] generation.router.moduleShims");
|
|
9172
|
+
if (!isNuxt && vueGenerationOptions?.router && resolvedGenerationOptions.routerType === "vue-router") {
|
|
9173
|
+
assertNonEmptyString(resolvedGenerationOptions.routerEntry, "[vue-pom-generator] generation.router.entry");
|
|
8694
9174
|
}
|
|
8695
9175
|
}
|
|
8696
9176
|
if (usesExternalVuePlugin) {
|
|
@@ -8709,11 +9189,11 @@ function createVuePomGeneratorPlugins(options = {}) {
|
|
|
8709
9189
|
};
|
|
8710
9190
|
const getViewsDirAbs = () => resolveFromProjectRoot(projectRootRef.current, getViewsDir());
|
|
8711
9191
|
const getWrapperSearchRootsAbs = () => getWrapperSearchRoots().map((root) => resolveFromProjectRoot(projectRootRef.current, root));
|
|
8712
|
-
const {
|
|
9192
|
+
const { elementMetadata, semanticNameMap, componentHierarchyMap, vueFilesPathMap } = sharedState;
|
|
8713
9193
|
const { metadataCollectorPlugin, internalVuePlugin, templateCompilerOptions } = createVuePluginWithTestIds({
|
|
8714
9194
|
vueOptions,
|
|
8715
|
-
existingIdBehavior,
|
|
8716
|
-
nameCollisionBehavior,
|
|
9195
|
+
existingIdBehavior: resolvedGenerationOptions.existingIdBehavior,
|
|
9196
|
+
nameCollisionBehavior: resolvedGenerationOptions.nameCollisionBehavior,
|
|
8717
9197
|
nativeWrappers,
|
|
8718
9198
|
elementMetadata,
|
|
8719
9199
|
semanticNameMap,
|
|
@@ -8728,10 +9208,9 @@ function createVuePomGeneratorPlugins(options = {}) {
|
|
|
8728
9208
|
getProjectRoot: () => projectRootRef.current
|
|
8729
9209
|
});
|
|
8730
9210
|
templateCompilerOptionsForResolvedPlugin = templateCompilerOptions;
|
|
8731
|
-
const routerAwarePoms = typeof routerEntry === "string" && routerEntry.trim().length > 0 || routerType === "nuxt";
|
|
8732
9211
|
const supportPlugins = createSupportPlugins({
|
|
8733
|
-
componentTestIds,
|
|
8734
9212
|
componentHierarchyMap,
|
|
9213
|
+
elementMetadata,
|
|
8735
9214
|
vueFilesPathMap,
|
|
8736
9215
|
nativeWrappers,
|
|
8737
9216
|
excludedComponents,
|
|
@@ -8741,26 +9220,10 @@ function createVuePomGeneratorPlugins(options = {}) {
|
|
|
8741
9220
|
getViewsDir,
|
|
8742
9221
|
getSourceDirs,
|
|
8743
9222
|
getWrapperSearchRoots: getWrapperSearchRootsAbs,
|
|
8744
|
-
|
|
8745
|
-
missingSemanticNameBehavior,
|
|
8746
|
-
existingIdBehavior,
|
|
8747
|
-
outDir,
|
|
8748
|
-
emitLanguages,
|
|
8749
|
-
typescriptOutputStructure,
|
|
8750
|
-
csharp,
|
|
8751
|
-
routerAwarePoms,
|
|
8752
|
-
routerEntry,
|
|
8753
|
-
generateFixtures,
|
|
9223
|
+
generation: resolvedGenerationOptions,
|
|
8754
9224
|
projectRootRef,
|
|
8755
9225
|
basePageClassPath: basePageClassPathOverride,
|
|
8756
|
-
|
|
8757
|
-
customPomDir: resolvedCustomPomDir,
|
|
8758
|
-
customPomImportAliases: resolvedCustomPomImportAliases,
|
|
8759
|
-
customPomImportNameCollisionBehavior: resolvedCustomPomImportCollisionBehavior,
|
|
8760
|
-
testIdAttribute,
|
|
8761
|
-
loggerRef,
|
|
8762
|
-
routerType,
|
|
8763
|
-
routerModuleShims
|
|
9226
|
+
loggerRef
|
|
8764
9227
|
});
|
|
8765
9228
|
if (isNuxt) {
|
|
8766
9229
|
loggerRef.current.info("Nuxt environment detected. Skipping internal @vitejs/plugin-vue to avoid conflicts.");
|
|
@@ -8774,7 +9237,7 @@ function createVuePomGeneratorPlugins(options = {}) {
|
|
|
8774
9237
|
...supportPlugins
|
|
8775
9238
|
];
|
|
8776
9239
|
if (!generationEnabled) {
|
|
8777
|
-
const virtualModules = createTestIdsVirtualModulesPlugin(
|
|
9240
|
+
const virtualModules = createTestIdsVirtualModulesPlugin(componentHierarchyMap, elementMetadata);
|
|
8778
9241
|
return [
|
|
8779
9242
|
configPlugin,
|
|
8780
9243
|
metadataCollectorPlugin,
|