@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.mjs
CHANGED
|
@@ -8,8 +8,8 @@ import { parse as parse$2, NodeTypes as NodeTypes$1 } from "@vue/compiler-dom";
|
|
|
8
8
|
import { parse as parse$1, compileScript } from "@vue/compiler-sfc";
|
|
9
9
|
import { parseExpression, parse } from "@babel/parser";
|
|
10
10
|
import { NodeTypes, stringifyExpression, ConstantTypes, createSimpleExpression, ElementTypes } from "@vue/compiler-core";
|
|
11
|
-
import { Project, QuoteKind, NewLineKind, IndentationText,
|
|
12
|
-
import { isArrayExpression, isStringLiteral, isTemplateLiteral,
|
|
11
|
+
import { Project, QuoteKind, NewLineKind, IndentationText, CodeBlockWriter, StructureKind, VariableDeclarationKind } from "ts-morph";
|
|
12
|
+
import { isArrayExpression, isStringLiteral, isTemplateLiteral, isMemberExpression, isOptionalMemberExpression, isCallExpression, isOptionalCallExpression, isAssignmentExpression, isIdentifier, isExpressionStatement, isObjectExpression, isFile, isArrowFunctionExpression, isBlockStatement, isLogicalExpression, isConditionalExpression, isSequenceExpression, isAssignmentPattern, isRestElement, isObjectPattern, isObjectProperty, isProgram, VISITOR_KEYS, isBooleanLiteral, isNumericLiteral, isNullLiteral } from "@babel/types";
|
|
13
13
|
import { performance } from "node:perf_hooks";
|
|
14
14
|
import vue from "@vitejs/plugin-vue";
|
|
15
15
|
const VUE_POM_GENERATOR_LOG_PREFIX = "[vue-pom-generator]";
|
|
@@ -204,6 +204,49 @@ async function loadNuxtProjectDiscovery(cwd = process.cwd()) {
|
|
|
204
204
|
const nuxtOptions = await loadNuxtConfig({ cwd });
|
|
205
205
|
return resolveNuxtProjectDiscovery(nuxtOptions, getLayerDirectories, cwd);
|
|
206
206
|
}
|
|
207
|
+
function resolveGenerationSupportOptions(options) {
|
|
208
|
+
return {
|
|
209
|
+
outDir: (options.outDir ?? "tests/playwright/__generated__").trim(),
|
|
210
|
+
emitLanguages: options.emitLanguages?.length ? options.emitLanguages : ["ts"],
|
|
211
|
+
typescriptOutputStructure: options.typescriptOutputStructure ?? "aggregated",
|
|
212
|
+
csharp: options.csharp,
|
|
213
|
+
generateFixtures: options.generateFixtures,
|
|
214
|
+
customPomAttachments: options.customPomAttachments ?? [],
|
|
215
|
+
customPomDir: options.customPomDir ?? "tests/playwright/pom/custom",
|
|
216
|
+
requireCustomPomDir: options.requireCustomPomDir ?? false,
|
|
217
|
+
customPomImportAliases: options.customPomImportAliases,
|
|
218
|
+
customPomImportNameCollisionBehavior: options.customPomImportNameCollisionBehavior ?? "error",
|
|
219
|
+
nameCollisionBehavior: options.nameCollisionBehavior ?? "error",
|
|
220
|
+
existingIdBehavior: options.existingIdBehavior ?? "error",
|
|
221
|
+
testIdAttribute: (options.testIdAttribute ?? "data-testid").trim() || "data-testid",
|
|
222
|
+
routerAwarePoms: options.routerAwarePoms ?? false,
|
|
223
|
+
routerEntry: options.routerEntry,
|
|
224
|
+
routerType: options.routerType ?? "vue-router",
|
|
225
|
+
routerModuleShims: options.routerModuleShims
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
function resolveInjectionSupportOptions(options) {
|
|
229
|
+
const isNuxt = options.isNuxt ?? false;
|
|
230
|
+
return {
|
|
231
|
+
pageDirs: isNuxt ? ["app/pages"] : [options.viewsDir ?? "src/views"],
|
|
232
|
+
componentDirs: isNuxt ? ["app/components"] : options.componentDirs ?? ["src/components"],
|
|
233
|
+
layoutDirs: isNuxt ? ["app/layouts"] : options.layoutDirs ?? ["src/layouts"],
|
|
234
|
+
wrapperSearchRoots: isNuxt ? [] : options.wrapperSearchRoots ?? [],
|
|
235
|
+
nativeWrappers: options.nativeWrappers ?? {},
|
|
236
|
+
excludedComponents: options.excludedComponents ?? [],
|
|
237
|
+
existingIdBehavior: options.existingIdBehavior ?? "error",
|
|
238
|
+
testIdAttribute: (options.testIdAttribute ?? "data-testid").trim() || "data-testid"
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
function applyNuxtDiscoveryToInjectionOptions(options, discovery) {
|
|
242
|
+
return {
|
|
243
|
+
...options,
|
|
244
|
+
pageDirs: discovery.pageDirs.length ? discovery.pageDirs : [path.resolve(discovery.srcDir, "pages")],
|
|
245
|
+
componentDirs: discovery.componentDirs,
|
|
246
|
+
layoutDirs: discovery.layoutDirs,
|
|
247
|
+
wrapperSearchRoots: discovery.wrapperSearchRoots
|
|
248
|
+
};
|
|
249
|
+
}
|
|
207
250
|
function createTypeScriptProject() {
|
|
208
251
|
return new Project({
|
|
209
252
|
useInMemoryFileSystem: true,
|
|
@@ -295,16 +338,7 @@ function createClassConstructor(constructorDeclaration) {
|
|
|
295
338
|
...constructorDeclaration
|
|
296
339
|
};
|
|
297
340
|
}
|
|
298
|
-
function
|
|
299
|
-
if (!value) {
|
|
300
|
-
return value;
|
|
301
|
-
}
|
|
302
|
-
return value.charAt(0).toUpperCase() + value.slice(1);
|
|
303
|
-
}
|
|
304
|
-
function hasParam(params, name) {
|
|
305
|
-
return Object.prototype.hasOwnProperty.call(params, name);
|
|
306
|
-
}
|
|
307
|
-
function splitTypeAndInitializer(typeExpression) {
|
|
341
|
+
function splitPomParameterTypeExpression(typeExpression) {
|
|
308
342
|
const trimmed = typeExpression.trim();
|
|
309
343
|
const initializerIndex = trimmed.lastIndexOf("=");
|
|
310
344
|
if (initializerIndex < 0) {
|
|
@@ -315,49 +349,306 @@ function splitTypeAndInitializer(typeExpression) {
|
|
|
315
349
|
initializer: trimmed.slice(initializerIndex + 1).trim()
|
|
316
350
|
};
|
|
317
351
|
}
|
|
318
|
-
function
|
|
319
|
-
const
|
|
352
|
+
function createPomParameterSpec(name, typeExpression, options = {}) {
|
|
353
|
+
const normalizedTypeExpression = typeExpression?.trim();
|
|
354
|
+
const { type, initializer } = normalizedTypeExpression ? splitPomParameterTypeExpression(normalizedTypeExpression) : { type: void 0, initializer: void 0 };
|
|
320
355
|
return {
|
|
321
356
|
name,
|
|
322
|
-
|
|
323
|
-
|
|
357
|
+
typeExpression: normalizedTypeExpression,
|
|
358
|
+
type,
|
|
359
|
+
initializer: options.initializer ?? initializer,
|
|
360
|
+
hasQuestionToken: options.hasQuestionToken,
|
|
361
|
+
isRestParameter: options.isRestParameter
|
|
324
362
|
};
|
|
325
363
|
}
|
|
326
|
-
function
|
|
327
|
-
|
|
364
|
+
function normalizePomParameters(params) {
|
|
365
|
+
if (!params) {
|
|
366
|
+
return [];
|
|
367
|
+
}
|
|
368
|
+
return params.map((param) => createPomParameterSpec(param.name, param.typeExpression ?? param.type, {
|
|
369
|
+
initializer: param.initializer,
|
|
370
|
+
hasQuestionToken: param.hasQuestionToken,
|
|
371
|
+
isRestParameter: param.isRestParameter
|
|
372
|
+
}));
|
|
328
373
|
}
|
|
329
|
-
function
|
|
374
|
+
function getPomParameterNames(params) {
|
|
375
|
+
return normalizePomParameters(params).map((param) => param.name);
|
|
376
|
+
}
|
|
377
|
+
function getPomParameter(params, name) {
|
|
378
|
+
return normalizePomParameters(params).find((param) => param.name === name);
|
|
379
|
+
}
|
|
380
|
+
function hasPomParameter(params, name) {
|
|
381
|
+
return !!getPomParameter(params, name);
|
|
382
|
+
}
|
|
383
|
+
function setPomParameter(params, name, typeExpression, options = {}) {
|
|
384
|
+
const nextParam = createPomParameterSpec(name, typeExpression, options);
|
|
385
|
+
const normalizedParams = normalizePomParameters(params);
|
|
386
|
+
const existingIndex = normalizedParams.findIndex((param) => param.name === name);
|
|
387
|
+
if (existingIndex < 0) {
|
|
388
|
+
return [...normalizedParams, nextParam];
|
|
389
|
+
}
|
|
390
|
+
const nextParams = normalizedParams.slice();
|
|
391
|
+
nextParams[existingIndex] = nextParam;
|
|
392
|
+
return nextParams;
|
|
393
|
+
}
|
|
394
|
+
function removePomParameter(params, name) {
|
|
395
|
+
return normalizePomParameters(params).filter((param) => param.name !== name);
|
|
396
|
+
}
|
|
397
|
+
function toTypeScriptPomParameterStructures(params) {
|
|
398
|
+
return normalizePomParameters(params).map((param) => ({
|
|
399
|
+
name: param.name,
|
|
400
|
+
type: param.type || void 0,
|
|
401
|
+
initializer: param.initializer,
|
|
402
|
+
hasQuestionToken: param.hasQuestionToken,
|
|
403
|
+
isRestParameter: param.isRestParameter
|
|
404
|
+
}));
|
|
405
|
+
}
|
|
406
|
+
function getPomParameterArgumentNames(params) {
|
|
407
|
+
return normalizePomParameters(params).map((param) => param.isRestParameter ? `...${param.name}` : param.name);
|
|
408
|
+
}
|
|
409
|
+
function createPomMethodSignature(parameters) {
|
|
330
410
|
return {
|
|
331
|
-
|
|
332
|
-
type: options.type,
|
|
333
|
-
initializer: options.initializer
|
|
411
|
+
parameters: normalizePomParameters(parameters)
|
|
334
412
|
};
|
|
335
413
|
}
|
|
336
|
-
function
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
414
|
+
function pomParameterSpecEquals(left, right) {
|
|
415
|
+
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;
|
|
416
|
+
}
|
|
417
|
+
function pomParameterListEquals(left, right) {
|
|
418
|
+
const leftParams = normalizePomParameters(left);
|
|
419
|
+
const rightParams = normalizePomParameters(right);
|
|
420
|
+
if (leftParams.length !== rightParams.length) {
|
|
421
|
+
return false;
|
|
340
422
|
}
|
|
341
|
-
return
|
|
423
|
+
return leftParams.every((param, index) => pomParameterSpecEquals(param, rightParams[index]));
|
|
424
|
+
}
|
|
425
|
+
function pomMethodSignatureEquals(left, right) {
|
|
426
|
+
return pomParameterListEquals(left.parameters, right.parameters);
|
|
427
|
+
}
|
|
428
|
+
function isParameterizedPomPattern(kind) {
|
|
429
|
+
return kind === "parameterized";
|
|
342
430
|
}
|
|
343
|
-
function
|
|
431
|
+
function getTemplateVariables(formatted) {
|
|
344
432
|
const out = [];
|
|
345
433
|
const seen = /* @__PURE__ */ new Set();
|
|
346
|
-
|
|
347
|
-
for (const
|
|
348
|
-
|
|
434
|
+
const matches = formatted.matchAll(/\$\{(\w+)\}/g);
|
|
435
|
+
for (const match of matches) {
|
|
436
|
+
const variableName = match[1];
|
|
437
|
+
if (seen.has(variableName)) {
|
|
349
438
|
continue;
|
|
350
439
|
}
|
|
351
|
-
|
|
440
|
+
seen.add(variableName);
|
|
441
|
+
out.push(variableName);
|
|
442
|
+
}
|
|
443
|
+
return out;
|
|
444
|
+
}
|
|
445
|
+
function createPomStringPattern(formatted, patternKind) {
|
|
446
|
+
return {
|
|
447
|
+
formatted,
|
|
448
|
+
patternKind,
|
|
449
|
+
templateVariables: getTemplateVariables(formatted)
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
function getPomPatternVariables(patterns, options = {}) {
|
|
453
|
+
const out = [];
|
|
454
|
+
const seen = /* @__PURE__ */ new Set();
|
|
455
|
+
const omitted = new Set(options.omit ?? []);
|
|
456
|
+
for (const pattern of patterns) {
|
|
457
|
+
for (const variableName of pattern.templateVariables) {
|
|
458
|
+
if (omitted.has(variableName) || seen.has(variableName)) {
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
seen.add(variableName);
|
|
462
|
+
out.push(variableName);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
return out;
|
|
466
|
+
}
|
|
467
|
+
function orderPomPatternParameters(params, patterns, options = {}) {
|
|
468
|
+
const currentParams = normalizePomParameters(params);
|
|
469
|
+
const orderedParams = [];
|
|
470
|
+
const seen = /* @__PURE__ */ new Set();
|
|
471
|
+
const missingParams = [];
|
|
472
|
+
for (const variableName of getPomPatternVariables(patterns, options)) {
|
|
473
|
+
seen.add(variableName);
|
|
474
|
+
const existingParam = currentParams.find((param) => param.name === variableName);
|
|
475
|
+
if (!existingParam) {
|
|
476
|
+
missingParams.push(variableName);
|
|
352
477
|
continue;
|
|
353
478
|
}
|
|
354
|
-
|
|
355
|
-
|
|
479
|
+
orderedParams.push(existingParam);
|
|
480
|
+
}
|
|
481
|
+
if (missingParams.length > 0) {
|
|
482
|
+
const availableParams = currentParams.map((param) => JSON.stringify(param.name)).join(", ") || "<none>";
|
|
483
|
+
const patternSummary = patterns.map((pattern) => JSON.stringify(pattern.formatted)).join(", ");
|
|
484
|
+
throw new Error(
|
|
485
|
+
`[vue-pom-generator] Missing selector parameter(s) ${missingParams.map((name) => JSON.stringify(name)).join(", ")} for parameterized pattern(s) ${patternSummary}. Available parameters: ${availableParams}.`
|
|
486
|
+
);
|
|
487
|
+
}
|
|
488
|
+
for (const param of currentParams) {
|
|
489
|
+
if (seen.has(param.name)) {
|
|
490
|
+
continue;
|
|
491
|
+
}
|
|
492
|
+
seen.add(param.name);
|
|
493
|
+
orderedParams.push(param);
|
|
494
|
+
}
|
|
495
|
+
return orderedParams;
|
|
496
|
+
}
|
|
497
|
+
function getIndexedPomPatternVariable(pattern) {
|
|
498
|
+
if (!isParameterizedPomPattern(pattern.patternKind)) {
|
|
499
|
+
return null;
|
|
500
|
+
}
|
|
501
|
+
if (pattern.templateVariables.length !== 1) {
|
|
502
|
+
throw new Error(
|
|
503
|
+
`[vue-pom-generator] Parameterized locator getters require exactly one template variable; got ${pattern.templateVariables.length} in ${JSON.stringify(pattern.formatted)}.`
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
return pattern.templateVariables[0];
|
|
507
|
+
}
|
|
508
|
+
function hasPomPatternVariables(pattern) {
|
|
509
|
+
return pattern.templateVariables.length > 0;
|
|
510
|
+
}
|
|
511
|
+
function toTypeScriptPomPatternExpression(pattern) {
|
|
512
|
+
return isParameterizedPomPattern(pattern.patternKind) ? `\`${pattern.formatted}\`` : JSON.stringify(pattern.formatted);
|
|
513
|
+
}
|
|
514
|
+
function toCSharpPomPatternExpression(pattern) {
|
|
515
|
+
if (!isParameterizedPomPattern(pattern.patternKind)) {
|
|
516
|
+
return JSON.stringify(pattern.formatted);
|
|
517
|
+
}
|
|
518
|
+
const inner = pattern.formatted.replace(/\$\{/g, "{");
|
|
519
|
+
return `$${JSON.stringify(inner)}`;
|
|
520
|
+
}
|
|
521
|
+
function bindTypeScriptPomPattern(pattern, variableName) {
|
|
522
|
+
const expression = toTypeScriptPomPatternExpression(pattern);
|
|
523
|
+
if (!isParameterizedPomPattern(pattern.patternKind)) {
|
|
524
|
+
return { expression, setupStatements: [] };
|
|
525
|
+
}
|
|
526
|
+
return {
|
|
527
|
+
expression: variableName,
|
|
528
|
+
setupStatements: [`const ${variableName} = ${expression};`]
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
function bindCSharpPomPattern(pattern, variableName) {
|
|
532
|
+
const expression = toCSharpPomPatternExpression(pattern);
|
|
533
|
+
if (!isParameterizedPomPattern(pattern.patternKind)) {
|
|
534
|
+
return { expression, setupStatements: [] };
|
|
535
|
+
}
|
|
536
|
+
return {
|
|
537
|
+
expression: variableName,
|
|
538
|
+
setupStatements: [`var ${variableName} = ${expression};`]
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
function pomStringPatternEquals(left, right) {
|
|
542
|
+
return left.formatted === right.formatted && left.patternKind === right.patternKind;
|
|
543
|
+
}
|
|
544
|
+
function uniquePomStringPatterns(primary, alternates) {
|
|
545
|
+
const out = [];
|
|
546
|
+
const seen = /* @__PURE__ */ new Set();
|
|
547
|
+
const add = (pattern) => {
|
|
548
|
+
const key = JSON.stringify(pattern);
|
|
549
|
+
if (seen.has(key)) {
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
seen.add(key);
|
|
553
|
+
out.push(pattern);
|
|
554
|
+
};
|
|
555
|
+
add(primary);
|
|
556
|
+
for (const alternate of alternates ?? []) {
|
|
557
|
+
add(alternate);
|
|
356
558
|
}
|
|
357
559
|
return out;
|
|
358
560
|
}
|
|
359
|
-
function
|
|
360
|
-
|
|
561
|
+
function splitDiscoverabilityWords(value) {
|
|
562
|
+
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();
|
|
563
|
+
if (!normalized) {
|
|
564
|
+
return [];
|
|
565
|
+
}
|
|
566
|
+
return normalized.split(/\s+/).map((word) => word.toLowerCase()).filter(Boolean);
|
|
567
|
+
}
|
|
568
|
+
function joinDiscoverabilityWords(words) {
|
|
569
|
+
return words.join(" ").replace(/\s+/g, " ").trim();
|
|
570
|
+
}
|
|
571
|
+
function toSentenceCase(value) {
|
|
572
|
+
return value.charAt(0).toUpperCase() + value.slice(1);
|
|
573
|
+
}
|
|
574
|
+
function stripComponentKindSuffix(componentName) {
|
|
575
|
+
for (const suffix of ["Page", "Component", "Layout"]) {
|
|
576
|
+
if (componentName.endsWith(suffix) && componentName.length > suffix.length) {
|
|
577
|
+
return componentName.slice(0, -suffix.length);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
return componentName;
|
|
581
|
+
}
|
|
582
|
+
function removeLeadingWords(words, prefixWords) {
|
|
583
|
+
if (!prefixWords.length || words.length < prefixWords.length) {
|
|
584
|
+
return [...words];
|
|
585
|
+
}
|
|
586
|
+
for (let i = 0; i < prefixWords.length; i++) {
|
|
587
|
+
if (words[i] !== prefixWords[i]) {
|
|
588
|
+
return [...words];
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
return words.slice(prefixWords.length);
|
|
592
|
+
}
|
|
593
|
+
function removeTrailingRoleWord(words, roleWord) {
|
|
594
|
+
if (!words.length || words[words.length - 1] !== roleWord) {
|
|
595
|
+
return [...words];
|
|
596
|
+
}
|
|
597
|
+
return words.slice(0, -1);
|
|
598
|
+
}
|
|
599
|
+
function humanizePomMethodName(methodName) {
|
|
600
|
+
return joinDiscoverabilityWords(splitDiscoverabilityWords(methodName));
|
|
601
|
+
}
|
|
602
|
+
function stripPomActionPrefix(actionName) {
|
|
603
|
+
for (const prefix of ["click", "select", "type", "goTo"]) {
|
|
604
|
+
if (actionName.startsWith(prefix) && actionName.length > prefix.length) {
|
|
605
|
+
return actionName.slice(prefix.length);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
return actionName;
|
|
609
|
+
}
|
|
610
|
+
function normalizePomRoleLabel(nativeRole) {
|
|
611
|
+
if (nativeRole === "vselect") {
|
|
612
|
+
return "select";
|
|
613
|
+
}
|
|
614
|
+
return nativeRole || "element";
|
|
615
|
+
}
|
|
616
|
+
function buildPomLocatorDescription(args) {
|
|
617
|
+
const componentWords = splitDiscoverabilityWords(args.componentName ? stripComponentKindSuffix(args.componentName) : "");
|
|
618
|
+
const roleWord = normalizePomRoleLabel(args.nativeRole).toLowerCase();
|
|
619
|
+
const semanticWords = removeLeadingWords(
|
|
620
|
+
removeTrailingRoleWord(splitDiscoverabilityWords(args.methodName), roleWord),
|
|
621
|
+
componentWords
|
|
622
|
+
);
|
|
623
|
+
const phrase = joinDiscoverabilityWords([
|
|
624
|
+
...componentWords,
|
|
625
|
+
...semanticWords,
|
|
626
|
+
roleWord
|
|
627
|
+
]);
|
|
628
|
+
return toSentenceCase(phrase || "Generated element");
|
|
629
|
+
}
|
|
630
|
+
function upperFirst$1(value) {
|
|
631
|
+
if (!value) {
|
|
632
|
+
return value;
|
|
633
|
+
}
|
|
634
|
+
return value.charAt(0).toUpperCase() + value.slice(1);
|
|
635
|
+
}
|
|
636
|
+
function createParameters(params) {
|
|
637
|
+
return toTypeScriptPomParameterStructures(params);
|
|
638
|
+
}
|
|
639
|
+
function createInlineParameter(name, options = {}) {
|
|
640
|
+
return {
|
|
641
|
+
name,
|
|
642
|
+
type: options.type,
|
|
643
|
+
initializer: options.initializer
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
function removeByKeySegment$1(value) {
|
|
647
|
+
const idx = value.lastIndexOf("ByKey");
|
|
648
|
+
if (idx < 0) {
|
|
649
|
+
return value;
|
|
650
|
+
}
|
|
651
|
+
return value.slice(0, idx) + value.slice(idx + "ByKey".length);
|
|
361
652
|
}
|
|
362
653
|
function createAsyncMethod(name, parameters, statements) {
|
|
363
654
|
return createClassMethod({
|
|
@@ -367,17 +658,25 @@ function createAsyncMethod(name, parameters, statements) {
|
|
|
367
658
|
statements
|
|
368
659
|
});
|
|
369
660
|
}
|
|
370
|
-
function generateClickMethod(methodName,
|
|
661
|
+
function generateClickMethod(componentName, methodName, selector, alternateSelectors, parameters) {
|
|
371
662
|
const name = `click${methodName}`;
|
|
372
663
|
const noWaitName = `${name}NoWait`;
|
|
373
|
-
const
|
|
374
|
-
|
|
375
|
-
|
|
664
|
+
const locatorDescription = JSON.stringify(buildPomLocatorDescription({
|
|
665
|
+
componentName,
|
|
666
|
+
methodName,
|
|
667
|
+
nativeRole: "button"
|
|
668
|
+
}));
|
|
669
|
+
const selectorParams = orderPomPatternParameters(parameters, [selector]);
|
|
670
|
+
const hasSelectorVariables = hasPomPatternVariables(selector);
|
|
671
|
+
const baseParameters = createParameters(selectorParams);
|
|
672
|
+
const argsForForward = getPomParameterNames(selectorParams).join(", ");
|
|
673
|
+
const alternates = uniquePomStringPatterns(selector, alternateSelectors).slice(1);
|
|
674
|
+
const primaryTestIdExpr = toTypeScriptPomPatternExpression(selector);
|
|
376
675
|
if (alternates.length > 0) {
|
|
377
|
-
const candidatesExpr = [
|
|
676
|
+
const candidatesExpr = [primaryTestIdExpr, ...alternates.map((id) => toTypeScriptPomPatternExpression(id))].join(", ");
|
|
378
677
|
const clickMethod = createAsyncMethod(
|
|
379
678
|
name,
|
|
380
|
-
|
|
679
|
+
hasSelectorVariables ? [
|
|
381
680
|
...baseParameters,
|
|
382
681
|
createInlineParameter("wait", { type: "boolean", initializer: "true" }),
|
|
383
682
|
createInlineParameter("annotationText", { type: "string", initializer: '""' })
|
|
@@ -389,7 +688,7 @@ function generateClickMethod(methodName, formattedDataTestId, alternateFormatted
|
|
|
389
688
|
writer.writeLine(`const candidates = [${candidatesExpr}] as const;`);
|
|
390
689
|
writer.writeLine("let lastError: unknown;");
|
|
391
690
|
writer.write("for (const testId of candidates) ").block(() => {
|
|
392
|
-
writer.writeLine(
|
|
691
|
+
writer.writeLine(`const locator = this.locatorByTestId(testId, ${locatorDescription});`);
|
|
393
692
|
writer.write("try ").block(() => {
|
|
394
693
|
writer.write("if (await locator.count()) ").block(() => {
|
|
395
694
|
writer.writeLine("await this.clickLocator(locator, annotationText, wait);");
|
|
@@ -406,14 +705,14 @@ function generateClickMethod(methodName, formattedDataTestId, alternateFormatted
|
|
|
406
705
|
const noWaitArgs = argsForForward ? `${argsForForward}, false, annotationText` : "false, annotationText";
|
|
407
706
|
const noWaitMethod = createAsyncMethod(
|
|
408
707
|
noWaitName,
|
|
409
|
-
|
|
708
|
+
hasSelectorVariables ? [...baseParameters, createInlineParameter("annotationText", { type: "string", initializer: '""' })] : [createInlineParameter("annotationText", { type: "string", initializer: '""' })],
|
|
410
709
|
(writer) => {
|
|
411
710
|
writer.writeLine(`await this.${name}(${noWaitArgs});`);
|
|
412
711
|
}
|
|
413
712
|
);
|
|
414
713
|
return [clickMethod, noWaitMethod];
|
|
415
714
|
}
|
|
416
|
-
if (
|
|
715
|
+
if (hasSelectorVariables) {
|
|
417
716
|
return [
|
|
418
717
|
createAsyncMethod(
|
|
419
718
|
name,
|
|
@@ -423,7 +722,7 @@ function generateClickMethod(methodName, formattedDataTestId, alternateFormatted
|
|
|
423
722
|
createInlineParameter("annotationText", { type: "string", initializer: '""' })
|
|
424
723
|
],
|
|
425
724
|
(writer) => {
|
|
426
|
-
writer.writeLine(`await this.clickByTestId(
|
|
725
|
+
writer.writeLine(`await this.clickByTestId(${primaryTestIdExpr}, annotationText, wait, ${locatorDescription});`);
|
|
427
726
|
}
|
|
428
727
|
),
|
|
429
728
|
createAsyncMethod(
|
|
@@ -443,7 +742,7 @@ function generateClickMethod(methodName, formattedDataTestId, alternateFormatted
|
|
|
443
742
|
createInlineParameter("annotationText", { type: "string", initializer: '""' })
|
|
444
743
|
],
|
|
445
744
|
(writer) => {
|
|
446
|
-
writer.writeLine(`await this.clickByTestId(
|
|
745
|
+
writer.writeLine(`await this.clickByTestId(${primaryTestIdExpr}, annotationText, wait, ${locatorDescription});`);
|
|
447
746
|
}
|
|
448
747
|
),
|
|
449
748
|
createAsyncMethod(
|
|
@@ -455,66 +754,76 @@ function generateClickMethod(methodName, formattedDataTestId, alternateFormatted
|
|
|
455
754
|
)
|
|
456
755
|
];
|
|
457
756
|
}
|
|
458
|
-
function generateRadioMethod(methodName,
|
|
757
|
+
function generateRadioMethod(componentName, methodName, selector, parameters) {
|
|
459
758
|
const name = `select${methodName}`;
|
|
460
|
-
const
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
const
|
|
759
|
+
const locatorDescription = JSON.stringify(buildPomLocatorDescription({
|
|
760
|
+
componentName,
|
|
761
|
+
methodName,
|
|
762
|
+
nativeRole: "radio"
|
|
763
|
+
}));
|
|
764
|
+
const selectorParams = orderPomPatternParameters(parameters, [selector]);
|
|
765
|
+
const methodParameters = createParameters(selectorParams);
|
|
766
|
+
const testIdExpr = toTypeScriptPomPatternExpression(selector);
|
|
466
767
|
return [
|
|
467
|
-
createAsyncMethod(name,
|
|
468
|
-
writer.writeLine(`await this.clickByTestId(${testIdExpr}, annotationText);`);
|
|
768
|
+
createAsyncMethod(name, methodParameters, (writer) => {
|
|
769
|
+
writer.writeLine(`await this.clickByTestId(${testIdExpr}, annotationText, true, ${locatorDescription});`);
|
|
469
770
|
})
|
|
470
771
|
];
|
|
471
772
|
}
|
|
472
|
-
function generateSelectMethod(methodName,
|
|
773
|
+
function generateSelectMethod(componentName, methodName, selector, parameters) {
|
|
473
774
|
const name = `select${methodName}`;
|
|
474
|
-
const
|
|
475
|
-
|
|
775
|
+
const locatorDescription = JSON.stringify(buildPomLocatorDescription({
|
|
776
|
+
componentName,
|
|
777
|
+
methodName,
|
|
778
|
+
nativeRole: "select"
|
|
779
|
+
}));
|
|
780
|
+
const selectorParams = orderPomPatternParameters(parameters, [selector]);
|
|
781
|
+
const testIdExpr = toTypeScriptPomPatternExpression(selector);
|
|
476
782
|
return [
|
|
477
783
|
createAsyncMethod(
|
|
478
784
|
name,
|
|
479
|
-
|
|
480
|
-
createInlineParameter("value", { type: "string" }),
|
|
481
|
-
createInlineParameter("annotationText", { type: "string", initializer: '""' })
|
|
482
|
-
],
|
|
785
|
+
createParameters(selectorParams),
|
|
483
786
|
(writer) => {
|
|
484
|
-
writer.writeLine(`const
|
|
485
|
-
writer.writeLine(
|
|
486
|
-
writer.writeLine("await this.
|
|
787
|
+
writer.writeLine(`const testId = ${testIdExpr};`);
|
|
788
|
+
writer.writeLine(`const locator = this.locatorByTestId(testId, ${locatorDescription});`);
|
|
789
|
+
writer.writeLine("await this.animateCursorToElement(locator, false, 500, annotationText);");
|
|
790
|
+
writer.writeLine("await locator.selectOption(value);");
|
|
487
791
|
}
|
|
488
792
|
)
|
|
489
793
|
];
|
|
490
794
|
}
|
|
491
|
-
function generateVSelectMethod(methodName,
|
|
795
|
+
function generateVSelectMethod(componentName, methodName, selector, parameters) {
|
|
492
796
|
const name = `select${methodName}`;
|
|
797
|
+
const locatorDescription = JSON.stringify(buildPomLocatorDescription({
|
|
798
|
+
componentName,
|
|
799
|
+
methodName,
|
|
800
|
+
nativeRole: "vselect"
|
|
801
|
+
}));
|
|
802
|
+
const selectorParams = orderPomPatternParameters(parameters, [selector]);
|
|
493
803
|
return [
|
|
494
804
|
createAsyncMethod(
|
|
495
805
|
name,
|
|
496
|
-
|
|
497
|
-
createInlineParameter("value", { type: "string" }),
|
|
498
|
-
createInlineParameter("timeOut", { type: "number", initializer: "500" }),
|
|
499
|
-
createInlineParameter("annotationText", { type: "string", initializer: '""' })
|
|
500
|
-
],
|
|
806
|
+
createParameters(selectorParams),
|
|
501
807
|
(writer) => {
|
|
502
|
-
writer.writeLine(`await this.selectVSelectByTestId(
|
|
808
|
+
writer.writeLine(`await this.selectVSelectByTestId(${toTypeScriptPomPatternExpression(selector)}, value, timeOut, annotationText, ${locatorDescription});`);
|
|
503
809
|
}
|
|
504
810
|
)
|
|
505
811
|
];
|
|
506
812
|
}
|
|
507
|
-
function generateTypeMethod(methodName,
|
|
813
|
+
function generateTypeMethod(componentName, methodName, selector, parameters) {
|
|
508
814
|
const name = `type${methodName}`;
|
|
815
|
+
const locatorDescription = JSON.stringify(buildPomLocatorDescription({
|
|
816
|
+
componentName,
|
|
817
|
+
methodName,
|
|
818
|
+
nativeRole: "input"
|
|
819
|
+
}));
|
|
820
|
+
const selectorParams = orderPomPatternParameters(parameters, [selector]);
|
|
509
821
|
return [
|
|
510
822
|
createAsyncMethod(
|
|
511
823
|
name,
|
|
512
|
-
|
|
513
|
-
createInlineParameter("text", { type: "string" }),
|
|
514
|
-
createInlineParameter("annotationText", { type: "string", initializer: '""' })
|
|
515
|
-
],
|
|
824
|
+
createParameters(selectorParams),
|
|
516
825
|
(writer) => {
|
|
517
|
-
writer.writeLine(`await this.fillInputByTestId(
|
|
826
|
+
writer.writeLine(`await this.fillInputByTestId(${toTypeScriptPomPatternExpression(selector)}, text, annotationText, ${locatorDescription});`);
|
|
518
827
|
}
|
|
519
828
|
)
|
|
520
829
|
];
|
|
@@ -529,62 +838,74 @@ function isAllDigits(value) {
|
|
|
529
838
|
}
|
|
530
839
|
return true;
|
|
531
840
|
}
|
|
532
|
-
function generateGetElementByDataTestId(methodName, nativeRole,
|
|
841
|
+
function generateGetElementByDataTestId(componentName, methodName, nativeRole, selector, alternateSelectors, getterNameOverride, parameters) {
|
|
842
|
+
const locatorDescription = JSON.stringify(buildPomLocatorDescription({
|
|
843
|
+
componentName,
|
|
844
|
+
methodName,
|
|
845
|
+
nativeRole
|
|
846
|
+
}));
|
|
533
847
|
const roleSuffix = upperFirst$1(nativeRole || "Element");
|
|
534
848
|
const baseName = upperFirst$1(methodName);
|
|
535
849
|
const numericSuffix = baseName.startsWith(roleSuffix) ? baseName.slice(roleSuffix.length) : "";
|
|
536
|
-
const
|
|
537
|
-
const propertyName =
|
|
538
|
-
const
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
const
|
|
850
|
+
const hasRoleSuffix2 = baseName.endsWith(roleSuffix) || baseName.startsWith(roleSuffix) && isAllDigits(numericSuffix);
|
|
851
|
+
const propertyName = hasRoleSuffix2 ? `${baseName}` : `${baseName}${roleSuffix}`;
|
|
852
|
+
const selectorParams = orderPomPatternParameters(parameters, [selector]);
|
|
853
|
+
const indexedVariable = getIndexedPomPatternVariable(selector);
|
|
854
|
+
if (indexedVariable) {
|
|
855
|
+
const keyType = getPomParameter(selectorParams, indexedVariable)?.typeExpression || "string";
|
|
856
|
+
const keyedPropertyName = getterNameOverride ?? removeByKeySegment$1(propertyName);
|
|
542
857
|
return [
|
|
543
858
|
createClassGetter({
|
|
544
859
|
name: keyedPropertyName,
|
|
545
860
|
statements: [
|
|
546
|
-
`return this.keyedLocators((
|
|
861
|
+
`return this.keyedLocators((${indexedVariable}: ${keyType}) => this.locatorByTestId(${toTypeScriptPomPatternExpression(selector)}, ${locatorDescription}));`
|
|
547
862
|
]
|
|
548
863
|
})
|
|
549
864
|
];
|
|
550
865
|
}
|
|
551
866
|
const finalPropertyName = getterNameOverride ?? propertyName;
|
|
552
|
-
const alternates =
|
|
867
|
+
const alternates = uniquePomStringPatterns(selector, alternateSelectors).slice(1);
|
|
553
868
|
if (alternates.length > 0) {
|
|
554
|
-
const all = [
|
|
555
|
-
const locatorExpr = all.map((id) => `this.locatorByTestId(${
|
|
869
|
+
const all = [selector, ...alternates];
|
|
870
|
+
const locatorExpr = all.map((id) => `this.locatorByTestId(${toTypeScriptPomPatternExpression(id)})`).reduce((acc, next) => `${acc}.or(${next})`);
|
|
556
871
|
return [
|
|
557
872
|
createClassGetter({
|
|
558
873
|
name: finalPropertyName,
|
|
559
|
-
statements: [`return ${locatorExpr};`]
|
|
874
|
+
statements: [`return this.describeLocator(${locatorExpr}, ${locatorDescription});`]
|
|
560
875
|
})
|
|
561
876
|
];
|
|
562
877
|
}
|
|
563
878
|
return [
|
|
564
879
|
createClassGetter({
|
|
565
880
|
name: finalPropertyName,
|
|
566
|
-
statements: [`return this.locatorByTestId(
|
|
881
|
+
statements: [`return this.locatorByTestId(${toTypeScriptPomPatternExpression(selector)}, ${locatorDescription});`]
|
|
567
882
|
})
|
|
568
883
|
];
|
|
569
884
|
}
|
|
570
885
|
function generateNavigationMethod(args) {
|
|
571
|
-
const { targetPageObjectModelClass: target, baseMethodName,
|
|
886
|
+
const { componentName, targetPageObjectModelClass: target, baseMethodName, selector, alternateSelectors, parameters } = args;
|
|
572
887
|
const methodName = baseMethodName ? `goTo${upperFirst$1(baseMethodName)}` : `goTo${target.endsWith("Page") ? target.slice(0, -"Page".length) : target}`;
|
|
573
|
-
const
|
|
574
|
-
|
|
575
|
-
|
|
888
|
+
const locatorDescription = JSON.stringify(buildPomLocatorDescription({
|
|
889
|
+
componentName,
|
|
890
|
+
methodName: baseMethodName,
|
|
891
|
+
nativeRole: "button"
|
|
892
|
+
}));
|
|
893
|
+
const selectorParams = orderPomPatternParameters(parameters, [selector]);
|
|
894
|
+
const methodParameters = createParameters(selectorParams);
|
|
895
|
+
const alternates = uniquePomStringPatterns(selector, alternateSelectors).slice(1);
|
|
896
|
+
const candidatesExpr = [toTypeScriptPomPatternExpression(selector), ...alternates.map((id) => toTypeScriptPomPatternExpression(id))].join(", ");
|
|
576
897
|
if (alternates.length > 0) {
|
|
577
898
|
return [
|
|
578
899
|
createClassMethod({
|
|
579
900
|
name: methodName,
|
|
580
|
-
parameters,
|
|
901
|
+
parameters: methodParameters,
|
|
581
902
|
returnType: `Fluent<${target}>`,
|
|
582
903
|
statements: (writer) => {
|
|
583
904
|
writer.write("return this.fluent(async () => ").block(() => {
|
|
584
905
|
writer.writeLine(`const candidates = [${candidatesExpr}] as const;`);
|
|
585
906
|
writer.writeLine("let lastError: unknown;");
|
|
586
907
|
writer.write("for (const testId of candidates) ").block(() => {
|
|
587
|
-
writer.writeLine(
|
|
908
|
+
writer.writeLine(`const locator = this.locatorByTestId(testId, ${locatorDescription});`);
|
|
588
909
|
writer.write("try ").block(() => {
|
|
589
910
|
writer.write("if (await locator.count()) ").block(() => {
|
|
590
911
|
writer.writeLine("await this.clickLocator(locator);");
|
|
@@ -605,11 +926,12 @@ function generateNavigationMethod(args) {
|
|
|
605
926
|
return [
|
|
606
927
|
createClassMethod({
|
|
607
928
|
name: methodName,
|
|
608
|
-
parameters,
|
|
929
|
+
parameters: methodParameters,
|
|
609
930
|
returnType: `Fluent<${target}>`,
|
|
610
931
|
statements: (writer) => {
|
|
611
932
|
writer.write("return this.fluent(async () => ").block(() => {
|
|
612
|
-
writer.writeLine(`
|
|
933
|
+
writer.writeLine(`const locator = this.locatorByTestId(${toTypeScriptPomPatternExpression(selector)}, ${locatorDescription});`);
|
|
934
|
+
writer.writeLine("await this.clickLocator(locator);");
|
|
613
935
|
writer.writeLine(`return new ${target}(this.page);`);
|
|
614
936
|
});
|
|
615
937
|
writer.writeLine(");");
|
|
@@ -617,41 +939,43 @@ function generateNavigationMethod(args) {
|
|
|
617
939
|
})
|
|
618
940
|
];
|
|
619
941
|
}
|
|
620
|
-
function generateViewObjectModelMembers(targetPageObjectModelClass, methodName, nativeRole,
|
|
942
|
+
function generateViewObjectModelMembers(componentName, targetPageObjectModelClass, methodName, nativeRole, selector, alternateSelectors, getterNameOverride, parameters) {
|
|
621
943
|
const baseMethodName = nativeRole === "radio" ? methodName || "Radio" : methodName;
|
|
622
944
|
const members = generateGetElementByDataTestId(
|
|
945
|
+
componentName,
|
|
623
946
|
baseMethodName,
|
|
624
947
|
nativeRole,
|
|
625
|
-
|
|
626
|
-
|
|
948
|
+
selector,
|
|
949
|
+
alternateSelectors,
|
|
627
950
|
getterNameOverride,
|
|
628
|
-
|
|
951
|
+
parameters
|
|
629
952
|
);
|
|
630
953
|
if (targetPageObjectModelClass) {
|
|
631
954
|
return [
|
|
632
955
|
...members,
|
|
633
956
|
...generateNavigationMethod({
|
|
957
|
+
componentName,
|
|
634
958
|
targetPageObjectModelClass,
|
|
635
959
|
baseMethodName,
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
960
|
+
selector,
|
|
961
|
+
alternateSelectors,
|
|
962
|
+
parameters
|
|
639
963
|
})
|
|
640
964
|
];
|
|
641
965
|
}
|
|
642
966
|
if (nativeRole === "select") {
|
|
643
|
-
return [...members, ...generateSelectMethod(baseMethodName,
|
|
967
|
+
return [...members, ...generateSelectMethod(componentName, baseMethodName, selector, parameters)];
|
|
644
968
|
}
|
|
645
969
|
if (nativeRole === "vselect") {
|
|
646
|
-
return [...members, ...generateVSelectMethod(baseMethodName,
|
|
970
|
+
return [...members, ...generateVSelectMethod(componentName, baseMethodName, selector, parameters)];
|
|
647
971
|
}
|
|
648
972
|
if (nativeRole === "input") {
|
|
649
|
-
return [...members, ...generateTypeMethod(baseMethodName,
|
|
973
|
+
return [...members, ...generateTypeMethod(componentName, baseMethodName, selector, parameters)];
|
|
650
974
|
}
|
|
651
975
|
if (nativeRole === "radio") {
|
|
652
|
-
return [...members, ...generateRadioMethod(baseMethodName || "Radio",
|
|
976
|
+
return [...members, ...generateRadioMethod(componentName, baseMethodName || "Radio", selector, parameters)];
|
|
653
977
|
}
|
|
654
|
-
return [...members, ...generateClickMethod(baseMethodName,
|
|
978
|
+
return [...members, ...generateClickMethod(componentName, baseMethodName, selector, alternateSelectors, parameters)];
|
|
655
979
|
}
|
|
656
980
|
function isSimpleExpressionNode(value) {
|
|
657
981
|
return value !== null && "type" in value && value.type === NodeTypes.SIMPLE_EXPRESSION;
|
|
@@ -691,43 +1015,72 @@ function buildPlaceholderParams(keys) {
|
|
|
691
1015
|
params[k] = "__placeholder__";
|
|
692
1016
|
return params;
|
|
693
1017
|
}
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
1018
|
+
const isNodeType = (node, type) => {
|
|
1019
|
+
return node !== null && node.type === type;
|
|
1020
|
+
};
|
|
1021
|
+
const isStringLiteralNode = (node) => {
|
|
1022
|
+
return isNodeType(node, "StringLiteral") && typeof node.value === "string";
|
|
1023
|
+
};
|
|
1024
|
+
const isIdentifierNode = (node) => {
|
|
1025
|
+
return isNodeType(node, "Identifier") && typeof node.name === "string";
|
|
1026
|
+
};
|
|
1027
|
+
const isObjectPropertyNode = (node) => {
|
|
1028
|
+
if (!isNodeType(node, "ObjectProperty"))
|
|
1029
|
+
return false;
|
|
1030
|
+
const n = node;
|
|
1031
|
+
return typeof n.key === "object" && n.key !== null && typeof n.value === "object" && n.value !== null;
|
|
1032
|
+
};
|
|
1033
|
+
const isObjectExpressionNode = (node) => {
|
|
1034
|
+
if (!isNodeType(node, "ObjectExpression"))
|
|
1035
|
+
return false;
|
|
1036
|
+
const n = node;
|
|
1037
|
+
return Array.isArray(n.properties);
|
|
1038
|
+
};
|
|
1039
|
+
function materializeResolvedRouteTarget(target, paramKeys) {
|
|
1040
|
+
if (typeof target === "string")
|
|
1041
|
+
return target;
|
|
1042
|
+
if (!paramKeys.length)
|
|
1043
|
+
return target;
|
|
1044
|
+
return {
|
|
1045
|
+
...target,
|
|
1046
|
+
params: buildPlaceholderParams(paramKeys)
|
|
1047
|
+
};
|
|
1048
|
+
}
|
|
1049
|
+
function analyzeToDirectiveTarget(toDirective) {
|
|
1050
|
+
if (!toDirective.exp) {
|
|
1051
|
+
return {
|
|
1052
|
+
kind: "unsupported",
|
|
1053
|
+
rawSource: null,
|
|
1054
|
+
reason: "missing-expression"
|
|
1055
|
+
};
|
|
1056
|
+
}
|
|
1057
|
+
const rawSource = stringifyExpression(toDirective.exp).trim();
|
|
699
1058
|
let expr;
|
|
700
1059
|
try {
|
|
701
1060
|
expr = parseExpression(rawSource, { plugins: ["typescript"] });
|
|
702
|
-
} catch {
|
|
703
|
-
return
|
|
1061
|
+
} catch (error) {
|
|
1062
|
+
return {
|
|
1063
|
+
kind: "parse-error",
|
|
1064
|
+
rawSource,
|
|
1065
|
+
reason: "parse-error",
|
|
1066
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1067
|
+
};
|
|
704
1068
|
}
|
|
705
|
-
const isNodeType = (node, type) => {
|
|
706
|
-
return node !== null && node.type === type;
|
|
707
|
-
};
|
|
708
|
-
const isStringLiteralNode = (node) => {
|
|
709
|
-
return isNodeType(node, "StringLiteral") && typeof node.value === "string";
|
|
710
|
-
};
|
|
711
|
-
const isIdentifierNode = (node) => {
|
|
712
|
-
return isNodeType(node, "Identifier") && typeof node.name === "string";
|
|
713
|
-
};
|
|
714
|
-
const isObjectPropertyNode = (node) => {
|
|
715
|
-
if (!isNodeType(node, "ObjectProperty"))
|
|
716
|
-
return false;
|
|
717
|
-
const n = node;
|
|
718
|
-
return typeof n.key === "object" && n.key !== null && typeof n.value === "object" && n.value !== null;
|
|
719
|
-
};
|
|
720
|
-
const isObjectExpressionNode = (node) => {
|
|
721
|
-
if (!isNodeType(node, "ObjectExpression"))
|
|
722
|
-
return false;
|
|
723
|
-
const n = node;
|
|
724
|
-
return Array.isArray(n.properties);
|
|
725
|
-
};
|
|
726
1069
|
if (isStringLiteralNode(expr)) {
|
|
727
|
-
return
|
|
1070
|
+
return {
|
|
1071
|
+
kind: "resolved",
|
|
1072
|
+
rawSource,
|
|
1073
|
+
target: expr.value,
|
|
1074
|
+
routeNameKey: null,
|
|
1075
|
+
paramKeys: []
|
|
1076
|
+
};
|
|
728
1077
|
}
|
|
729
1078
|
if (!isObjectExpressionNode(expr)) {
|
|
730
|
-
return
|
|
1079
|
+
return {
|
|
1080
|
+
kind: "unsupported",
|
|
1081
|
+
rawSource,
|
|
1082
|
+
reason: "dynamic-expression"
|
|
1083
|
+
};
|
|
731
1084
|
}
|
|
732
1085
|
const getStringField = (fieldName) => {
|
|
733
1086
|
const prop = expr.properties.find((p) => {
|
|
@@ -748,7 +1101,7 @@ function getRouteLocationLikeFromToDirective(toDirective) {
|
|
|
748
1101
|
const key = p.key;
|
|
749
1102
|
return isIdentifierNode(key) && key.name === "params" || isStringLiteralNode(key) && key.value === "params";
|
|
750
1103
|
});
|
|
751
|
-
let
|
|
1104
|
+
let paramKeys = [];
|
|
752
1105
|
if (paramsProp && isObjectPropertyNode(paramsProp) && isObjectExpressionNode(paramsProp.value)) {
|
|
753
1106
|
const keys = [];
|
|
754
1107
|
for (const prop of paramsProp.value.properties) {
|
|
@@ -760,47 +1113,50 @@ function getRouteLocationLikeFromToDirective(toDirective) {
|
|
|
760
1113
|
else if (isStringLiteralNode(key))
|
|
761
1114
|
keys.push(key.value);
|
|
762
1115
|
}
|
|
763
|
-
|
|
764
|
-
params = buildPlaceholderParams(Array.from(new Set(keys)));
|
|
765
|
-
}
|
|
1116
|
+
paramKeys = Array.from(new Set(keys));
|
|
766
1117
|
}
|
|
767
1118
|
if (name) {
|
|
768
|
-
|
|
1119
|
+
const trimmed = name.trim();
|
|
1120
|
+
if (!trimmed.length) {
|
|
1121
|
+
return {
|
|
1122
|
+
kind: "unsupported",
|
|
1123
|
+
rawSource,
|
|
1124
|
+
reason: "missing-name-or-path"
|
|
1125
|
+
};
|
|
1126
|
+
}
|
|
1127
|
+
return {
|
|
1128
|
+
kind: "resolved",
|
|
1129
|
+
rawSource,
|
|
1130
|
+
target: { name },
|
|
1131
|
+
routeNameKey: toPascalCaseRouteKey(trimmed),
|
|
1132
|
+
paramKeys
|
|
1133
|
+
};
|
|
769
1134
|
}
|
|
770
1135
|
if (path2) {
|
|
771
|
-
return {
|
|
1136
|
+
return {
|
|
1137
|
+
kind: "resolved",
|
|
1138
|
+
rawSource,
|
|
1139
|
+
target: { path: path2 },
|
|
1140
|
+
routeNameKey: null,
|
|
1141
|
+
paramKeys
|
|
1142
|
+
};
|
|
772
1143
|
}
|
|
773
|
-
return
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
return null;
|
|
779
|
-
const name = to.name;
|
|
780
|
-
if (typeof name !== "string")
|
|
781
|
-
return null;
|
|
782
|
-
const trimmed = name.trim();
|
|
783
|
-
if (!trimmed.length)
|
|
784
|
-
return null;
|
|
785
|
-
return toPascalCaseRouteKey(trimmed);
|
|
786
|
-
}
|
|
787
|
-
function getRouteNameKeyFromToDirective(toDirective) {
|
|
788
|
-
const objectName = toDirectiveObjectFieldNameValue$1(toDirective);
|
|
789
|
-
if (objectName)
|
|
790
|
-
return objectName;
|
|
791
|
-
return null;
|
|
1144
|
+
return {
|
|
1145
|
+
kind: "unsupported",
|
|
1146
|
+
rawSource,
|
|
1147
|
+
reason: "missing-name-or-path"
|
|
1148
|
+
};
|
|
792
1149
|
}
|
|
793
1150
|
function tryResolveToDirectiveTargetComponentName(toDirective) {
|
|
794
|
-
const
|
|
795
|
-
if (
|
|
796
|
-
const resolved = resolveToComponentName(
|
|
1151
|
+
const analysis = analyzeToDirectiveTarget(toDirective);
|
|
1152
|
+
if (analysis.kind === "resolved" && resolveToComponentName) {
|
|
1153
|
+
const resolved = resolveToComponentName(materializeResolvedRouteTarget(analysis.target, analysis.paramKeys));
|
|
797
1154
|
if (resolved)
|
|
798
1155
|
return resolved;
|
|
799
1156
|
}
|
|
800
|
-
|
|
801
|
-
if (!key || !routeNameToComponentName)
|
|
1157
|
+
if (analysis.kind !== "resolved" || !analysis.routeNameKey || !routeNameToComponentName)
|
|
802
1158
|
return null;
|
|
803
|
-
return routeNameToComponentName.get(
|
|
1159
|
+
return routeNameToComponentName.get(analysis.routeNameKey) ?? null;
|
|
804
1160
|
}
|
|
805
1161
|
function getDataTestIdFromGroupOption(text) {
|
|
806
1162
|
return text.replace(/[-_]/g, " ").split(" ").filter((a) => a).map((str) => {
|
|
@@ -835,11 +1191,81 @@ function staticAttributeValue(value) {
|
|
|
835
1191
|
return { kind: "static", value };
|
|
836
1192
|
}
|
|
837
1193
|
function templateAttributeValue(template) {
|
|
838
|
-
|
|
1194
|
+
const parsedTemplate = tryParseTemplateFragment(template);
|
|
1195
|
+
if (!parsedTemplate) {
|
|
1196
|
+
throw new Error(`[vue-pom-generator] Failed to parse generated template fragment: ${template}`);
|
|
1197
|
+
}
|
|
1198
|
+
return { kind: "template", template, parsedTemplate };
|
|
839
1199
|
}
|
|
840
1200
|
function getAttributeValueText(value) {
|
|
841
1201
|
return value.kind === "static" ? value.value : value.template;
|
|
842
1202
|
}
|
|
1203
|
+
function getVueExpressionSource(expression, ...preferredViews) {
|
|
1204
|
+
if (!expression) {
|
|
1205
|
+
return "";
|
|
1206
|
+
}
|
|
1207
|
+
for (const view of preferredViews) {
|
|
1208
|
+
let value = "";
|
|
1209
|
+
switch (view) {
|
|
1210
|
+
case "content":
|
|
1211
|
+
value = expression.type === NodeTypes.SIMPLE_EXPRESSION ? expression.content : "";
|
|
1212
|
+
break;
|
|
1213
|
+
case "loc":
|
|
1214
|
+
value = expression.loc?.source ?? "";
|
|
1215
|
+
break;
|
|
1216
|
+
case "compiled":
|
|
1217
|
+
try {
|
|
1218
|
+
value = stringifyExpression(expression);
|
|
1219
|
+
} catch {
|
|
1220
|
+
value = "";
|
|
1221
|
+
}
|
|
1222
|
+
break;
|
|
1223
|
+
default:
|
|
1224
|
+
value = "";
|
|
1225
|
+
break;
|
|
1226
|
+
}
|
|
1227
|
+
const trimmed = value.trim();
|
|
1228
|
+
if (trimmed) {
|
|
1229
|
+
return trimmed;
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
return "";
|
|
1233
|
+
}
|
|
1234
|
+
function tryGetExistingVueExpressionAst(expression) {
|
|
1235
|
+
if (!expression) {
|
|
1236
|
+
return null;
|
|
1237
|
+
}
|
|
1238
|
+
const ast = "ast" in expression ? expression.ast : null;
|
|
1239
|
+
return ast && "type" in ast ? ast : null;
|
|
1240
|
+
}
|
|
1241
|
+
function tryParseBabelExpressionFromSource(source, plugins) {
|
|
1242
|
+
const trimmed = source.trim();
|
|
1243
|
+
if (!trimmed) {
|
|
1244
|
+
return null;
|
|
1245
|
+
}
|
|
1246
|
+
try {
|
|
1247
|
+
return parseExpression(trimmed, { plugins });
|
|
1248
|
+
} catch {
|
|
1249
|
+
return null;
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
function tryGetVueExpressionAst(expression, options) {
|
|
1253
|
+
if (!expression) {
|
|
1254
|
+
return null;
|
|
1255
|
+
}
|
|
1256
|
+
if (options?.preferExistingAst !== false) {
|
|
1257
|
+
const existingAst = tryGetExistingVueExpressionAst(expression);
|
|
1258
|
+
if (existingAst) {
|
|
1259
|
+
return existingAst;
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
const source = getVueExpressionSource(expression, ...options?.preferredViews ?? ["content", "loc", "compiled"]);
|
|
1263
|
+
return source ? tryParseBabelExpressionFromSource(source, options?.plugins ?? ["typescript"]) : null;
|
|
1264
|
+
}
|
|
1265
|
+
function tryGetDirectiveBabelAst(directive, options) {
|
|
1266
|
+
const exp = directive.exp && (directive.exp.type === NodeTypes.SIMPLE_EXPRESSION || directive.exp.type === NodeTypes.COMPOUND_EXPRESSION) ? directive.exp : null;
|
|
1267
|
+
return tryGetVueExpressionAst(exp, options);
|
|
1268
|
+
}
|
|
843
1269
|
function toPascalCase(str) {
|
|
844
1270
|
const cleaned = (str ?? "").replace(/\$\{[^}]*\}/g, " ").replace(/[^a-z0-9]+/gi, " ").trim();
|
|
845
1271
|
if (!cleaned) {
|
|
@@ -881,32 +1307,14 @@ function tryGetClickDirective(node) {
|
|
|
881
1307
|
function nodeHasClickDirective(node) {
|
|
882
1308
|
return tryGetClickDirective(node) !== void 0;
|
|
883
1309
|
}
|
|
884
|
-
function
|
|
1310
|
+
function findTemplateSlotScopeExpression(node) {
|
|
885
1311
|
if (node.tag !== "template") {
|
|
886
1312
|
return null;
|
|
887
1313
|
}
|
|
888
1314
|
const slotProp = node.props.find((prop) => {
|
|
889
1315
|
return prop.type === NodeTypes.DIRECTIVE && prop.name === "slot";
|
|
890
1316
|
});
|
|
891
|
-
|
|
892
|
-
if (slotProp.exp.type === NodeTypes.SIMPLE_EXPRESSION) {
|
|
893
|
-
return slotProp.exp.content;
|
|
894
|
-
}
|
|
895
|
-
if (slotProp.exp.type === NodeTypes.COMPOUND_EXPRESSION) {
|
|
896
|
-
return stringifyExpression(slotProp.exp);
|
|
897
|
-
}
|
|
898
|
-
}
|
|
899
|
-
return null;
|
|
900
|
-
}
|
|
901
|
-
function isSimpleScopeIdentifier(value) {
|
|
902
|
-
if (!value) {
|
|
903
|
-
return false;
|
|
904
|
-
}
|
|
905
|
-
try {
|
|
906
|
-
return isIdentifier(parseExpression(value, { plugins: ["typescript"] }));
|
|
907
|
-
} catch {
|
|
908
|
-
return false;
|
|
909
|
-
}
|
|
1317
|
+
return slotProp?.exp && (slotProp.exp.type === NodeTypes.SIMPLE_EXPRESSION || slotProp.exp.type === NodeTypes.COMPOUND_EXPRESSION) ? slotProp.exp : null;
|
|
910
1318
|
}
|
|
911
1319
|
function buildSlotScopeFallbackKeyExpression(identifier) {
|
|
912
1320
|
return `${identifier}.key ?? ${identifier}.data?.id ?? ${identifier}.id ?? ${identifier}.value ?? ${identifier}`;
|
|
@@ -1008,43 +1416,64 @@ function tryGetSlotScopeKeyCandidate(node) {
|
|
|
1008
1416
|
}
|
|
1009
1417
|
return best;
|
|
1010
1418
|
}
|
|
1011
|
-
function
|
|
1012
|
-
const
|
|
1013
|
-
if (
|
|
1419
|
+
function tryGetTemplateSlotScopeBindingNode(expression) {
|
|
1420
|
+
const ast = tryGetExistingVueExpressionAst(expression);
|
|
1421
|
+
if (ast) {
|
|
1422
|
+
if (isArrowFunctionExpression(ast)) {
|
|
1423
|
+
return ast.params[0] ?? null;
|
|
1424
|
+
}
|
|
1425
|
+
return ast;
|
|
1426
|
+
}
|
|
1427
|
+
const rawSource = getVueExpressionSource(expression, "content", "loc", "compiled");
|
|
1428
|
+
if (!rawSource) {
|
|
1014
1429
|
return null;
|
|
1015
1430
|
}
|
|
1016
|
-
|
|
1017
|
-
return
|
|
1431
|
+
try {
|
|
1432
|
+
return parseExpression(rawSource, { plugins: ["typescript"] });
|
|
1433
|
+
} catch {
|
|
1018
1434
|
}
|
|
1019
1435
|
try {
|
|
1020
|
-
const parsed = parse(`(${
|
|
1436
|
+
const parsed = parse(`(${rawSource}) => {}`, {
|
|
1021
1437
|
sourceType: "module",
|
|
1022
1438
|
plugins: ["typescript"]
|
|
1023
1439
|
});
|
|
1024
1440
|
const statement = parsed.program.body[0];
|
|
1025
1441
|
if (statement && isExpressionStatement(statement) && isArrowFunctionExpression(statement.expression)) {
|
|
1026
|
-
return
|
|
1442
|
+
return statement.expression.params[0] ?? null;
|
|
1027
1443
|
}
|
|
1028
1444
|
} catch {
|
|
1445
|
+
return null;
|
|
1029
1446
|
}
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
} else if (colonIdx !== -1) {
|
|
1040
|
-
cutIdx = colonIdx;
|
|
1041
|
-
}
|
|
1042
|
-
const first = (cutIdx === -1 ? inner : inner.slice(0, cutIdx)).trim();
|
|
1043
|
-
if (first && isSimpleScopeIdentifier(first)) {
|
|
1044
|
-
return buildSlotScopeFallbackKeyExpression(first);
|
|
1045
|
-
}
|
|
1447
|
+
return null;
|
|
1448
|
+
}
|
|
1449
|
+
function toResolvedTemplateFragment(source) {
|
|
1450
|
+
const templateLiteral = tryUnwrapTemplateLiteralSource(source);
|
|
1451
|
+
if (templateLiteral) {
|
|
1452
|
+
return {
|
|
1453
|
+
template: templateLiteral.template,
|
|
1454
|
+
rawExpression: null
|
|
1455
|
+
};
|
|
1046
1456
|
}
|
|
1047
|
-
return
|
|
1457
|
+
return toInterpolatedTemplateFragment(source);
|
|
1458
|
+
}
|
|
1459
|
+
function toResolvedKeyInfo(selectorSource, runtimeSource = selectorSource) {
|
|
1460
|
+
const selectorFragment = selectorSource ? toResolvedTemplateFragment(selectorSource) : null;
|
|
1461
|
+
const runtimeFragment = runtimeSource ? toResolvedTemplateFragment(runtimeSource) : null;
|
|
1462
|
+
const selectorTemplate = selectorFragment?.template ?? runtimeFragment?.template ?? null;
|
|
1463
|
+
const runtimeTemplate = runtimeFragment?.template ?? selectorFragment?.template ?? null;
|
|
1464
|
+
if (!selectorTemplate || !runtimeTemplate) {
|
|
1465
|
+
return null;
|
|
1466
|
+
}
|
|
1467
|
+
return {
|
|
1468
|
+
selectorFragment: selectorTemplate,
|
|
1469
|
+
runtimeFragment: runtimeTemplate,
|
|
1470
|
+
rawExpression: runtimeFragment?.rawExpression ?? selectorFragment?.rawExpression ?? null
|
|
1471
|
+
};
|
|
1472
|
+
}
|
|
1473
|
+
function tryGetTemplateSlotScopeKeyInfo(expression) {
|
|
1474
|
+
const bindingNode = tryGetTemplateSlotScopeBindingNode(expression);
|
|
1475
|
+
const candidateExpression = bindingNode ? tryGetSlotScopeKeyCandidate(bindingNode)?.expression ?? null : null;
|
|
1476
|
+
return candidateExpression ? toResolvedKeyInfo(candidateExpression) : null;
|
|
1048
1477
|
}
|
|
1049
1478
|
function nodeHasToDirective(node) {
|
|
1050
1479
|
const toDirective = findDirectiveByName(node, "bind", "to");
|
|
@@ -1053,51 +1482,160 @@ function nodeHasToDirective(node) {
|
|
|
1053
1482
|
}
|
|
1054
1483
|
return void 0;
|
|
1055
1484
|
}
|
|
1056
|
-
function nodeHasForDirective(node) {
|
|
1057
|
-
return node.props.some(
|
|
1058
|
-
(attr) => attr.type === NodeTypes.DIRECTIVE && attr.name === "for"
|
|
1059
|
-
);
|
|
1060
|
-
}
|
|
1061
1485
|
function getKeyDirective(node) {
|
|
1062
1486
|
return findDirectiveByName(node, "bind", "key") ?? null;
|
|
1063
1487
|
}
|
|
1064
|
-
function
|
|
1065
|
-
const
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
return `\${${rawSource}}`;
|
|
1488
|
+
function tryUnwrapTemplateLiteralSource(source) {
|
|
1489
|
+
const rawSource = source.trim();
|
|
1490
|
+
if (!rawSource) {
|
|
1491
|
+
return null;
|
|
1069
1492
|
}
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1493
|
+
let ast = null;
|
|
1494
|
+
try {
|
|
1495
|
+
ast = parseExpression(rawSource, { plugins: ["typescript"] });
|
|
1496
|
+
} catch {
|
|
1497
|
+
return null;
|
|
1074
1498
|
}
|
|
1075
|
-
|
|
1499
|
+
if (!ast || !isTemplateLiteral(ast)) {
|
|
1500
|
+
return null;
|
|
1501
|
+
}
|
|
1502
|
+
const cooked = ast.quasis.map((quasi) => quasi.value.cooked ?? "").join("");
|
|
1503
|
+
try {
|
|
1504
|
+
const start = typeof ast.start === "number" ? ast.start + 1 : 1;
|
|
1505
|
+
const end = typeof ast.end === "number" ? ast.end - 1 : rawSource.length - 1;
|
|
1506
|
+
return {
|
|
1507
|
+
template: rawSource.slice(start, end) || cooked,
|
|
1508
|
+
expressionCount: ast.expressions.length
|
|
1509
|
+
};
|
|
1510
|
+
} catch {
|
|
1511
|
+
return {
|
|
1512
|
+
template: cooked,
|
|
1513
|
+
expressionCount: ast.expressions.length
|
|
1514
|
+
};
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
function tryUnwrapTemplateLiteralExpressionSource(expression) {
|
|
1518
|
+
const rawSource = getVueExpressionSource(expression, "loc", "compiled");
|
|
1519
|
+
return rawSource ? tryUnwrapTemplateLiteralSource(rawSource) : null;
|
|
1520
|
+
}
|
|
1521
|
+
function tryParseTemplateFragment(fragment) {
|
|
1522
|
+
if (!fragment) {
|
|
1523
|
+
return null;
|
|
1524
|
+
}
|
|
1525
|
+
try {
|
|
1526
|
+
const source = `\`${fragment}\``;
|
|
1527
|
+
const ast = parseExpression(source, { plugins: ["typescript"] });
|
|
1528
|
+
return isTemplateLiteral(ast) ? { source, templateLiteral: ast } : null;
|
|
1529
|
+
} catch {
|
|
1530
|
+
return null;
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
function getTemplateExpressionSource(parsedTemplate, index) {
|
|
1534
|
+
const expression = parsedTemplate.templateLiteral.expressions[index];
|
|
1535
|
+
if (!expression) {
|
|
1536
|
+
return null;
|
|
1537
|
+
}
|
|
1538
|
+
const start = typeof expression.start === "number" ? expression.start : null;
|
|
1539
|
+
const end = typeof expression.end === "number" ? expression.end : null;
|
|
1540
|
+
if (start === null || end === null) {
|
|
1541
|
+
return null;
|
|
1542
|
+
}
|
|
1543
|
+
return parsedTemplate.source.slice(start, end);
|
|
1544
|
+
}
|
|
1545
|
+
function getSingleExpressionTemplateFragment(parsedTemplate) {
|
|
1546
|
+
const { templateLiteral } = parsedTemplate;
|
|
1547
|
+
if (templateLiteral.expressions.length !== 1 || templateLiteral.quasis.length !== 2) {
|
|
1548
|
+
return null;
|
|
1549
|
+
}
|
|
1550
|
+
const expressionSource = getTemplateExpressionSource(parsedTemplate, 0);
|
|
1551
|
+
if (expressionSource === null) {
|
|
1552
|
+
return null;
|
|
1553
|
+
}
|
|
1554
|
+
return {
|
|
1555
|
+
prefix: templateLiteral.quasis[0]?.value.raw ?? "",
|
|
1556
|
+
expressionSource,
|
|
1557
|
+
suffix: templateLiteral.quasis[1]?.value.raw ?? ""
|
|
1558
|
+
};
|
|
1559
|
+
}
|
|
1560
|
+
function templateFragmentContainsSingleExpression(container, candidate) {
|
|
1561
|
+
const containerFragment = getSingleExpressionTemplateFragment(container);
|
|
1562
|
+
const candidateFragment = getSingleExpressionTemplateFragment(candidate);
|
|
1563
|
+
if (!containerFragment || !candidateFragment) {
|
|
1564
|
+
return false;
|
|
1565
|
+
}
|
|
1566
|
+
return containerFragment.expressionSource === candidateFragment.expressionSource && containerFragment.prefix.endsWith(candidateFragment.prefix) && containerFragment.suffix.startsWith(candidateFragment.suffix);
|
|
1567
|
+
}
|
|
1568
|
+
function hasTemplateInterpolationExpressions(fragment) {
|
|
1569
|
+
return (tryParseTemplateFragment(fragment)?.templateLiteral.expressions.length ?? 0) > 0;
|
|
1570
|
+
}
|
|
1571
|
+
function toInterpolatedTemplateFragment(fragment) {
|
|
1572
|
+
if (!fragment) {
|
|
1573
|
+
return null;
|
|
1574
|
+
}
|
|
1575
|
+
if (hasTemplateInterpolationExpressions(fragment)) {
|
|
1576
|
+
return { template: fragment, rawExpression: null };
|
|
1577
|
+
}
|
|
1578
|
+
return {
|
|
1579
|
+
template: `\${${fragment}}`,
|
|
1580
|
+
rawExpression: fragment
|
|
1581
|
+
};
|
|
1582
|
+
}
|
|
1583
|
+
function renderTemplateLiteralExpression(templateValue) {
|
|
1584
|
+
const templateLiteralSource = templateValue.parsedTemplate.source;
|
|
1585
|
+
const templateLiteral = templateValue.parsedTemplate.templateLiteral;
|
|
1586
|
+
const writer = createTypeScriptWriter();
|
|
1587
|
+
writer.write("`");
|
|
1588
|
+
for (let i = 0; i < templateLiteral.quasis.length; i += 1) {
|
|
1589
|
+
writer.write(templateLiteral.quasis[i]?.value.raw ?? "");
|
|
1590
|
+
const interpolation = templateLiteral.expressions[i];
|
|
1591
|
+
if (!interpolation) {
|
|
1592
|
+
continue;
|
|
1593
|
+
}
|
|
1594
|
+
const start = typeof interpolation.start === "number" ? interpolation.start : null;
|
|
1595
|
+
const end = typeof interpolation.end === "number" ? interpolation.end : null;
|
|
1596
|
+
if (start === null || end === null) {
|
|
1597
|
+
return templateLiteralSource;
|
|
1598
|
+
}
|
|
1599
|
+
writer.write("${");
|
|
1600
|
+
writer.write(templateLiteralSource.slice(start, end));
|
|
1601
|
+
writer.write("}");
|
|
1602
|
+
}
|
|
1603
|
+
writer.write("`");
|
|
1604
|
+
return writer.toString();
|
|
1605
|
+
}
|
|
1606
|
+
function getKeyDirectiveExpression(node) {
|
|
1607
|
+
const keyDirective = getKeyDirective(node);
|
|
1608
|
+
return keyDirective?.exp && (keyDirective.exp.type === NodeTypes.SIMPLE_EXPRESSION || keyDirective.exp.type === NodeTypes.COMPOUND_EXPRESSION) ? keyDirective.exp : null;
|
|
1609
|
+
}
|
|
1610
|
+
function getKeyDirectiveInfo(node) {
|
|
1611
|
+
const keyExpression = getKeyDirectiveExpression(node);
|
|
1612
|
+
if (!keyExpression) {
|
|
1613
|
+
return null;
|
|
1614
|
+
}
|
|
1615
|
+
const selectorSource = getVueExpressionSource(keyExpression, "compiled", "loc");
|
|
1616
|
+
const runtimeSource = getVueExpressionSource(keyExpression, "loc", "compiled");
|
|
1617
|
+
return toResolvedKeyInfo(selectorSource, runtimeSource);
|
|
1076
1618
|
}
|
|
1077
1619
|
function getModelBindingValues(node) {
|
|
1078
1620
|
let vModel = "";
|
|
1079
1621
|
const vModelDirective = findDirectiveByName(node, "model");
|
|
1080
|
-
if (vModelDirective?.exp
|
|
1081
|
-
vModel = toPascalCase(vModelDirective.exp
|
|
1622
|
+
if (vModelDirective?.exp && (vModelDirective.exp.type === NodeTypes.SIMPLE_EXPRESSION || vModelDirective.exp.type === NodeTypes.COMPOUND_EXPRESSION)) {
|
|
1623
|
+
vModel = toPascalCase(getVueExpressionSource(vModelDirective.exp, "loc", "content"));
|
|
1082
1624
|
}
|
|
1083
1625
|
let modelValue = null;
|
|
1084
1626
|
const modelValueDirective = findDirectiveByName(node, "bind", "modelValue");
|
|
1085
|
-
|
|
1086
|
-
|
|
1627
|
+
const modelValueAst = modelValueDirective ? tryGetDirectiveBabelAst(modelValueDirective, {
|
|
1628
|
+
preferredViews: ["loc", "compiled"],
|
|
1629
|
+
plugins: ["typescript"],
|
|
1630
|
+
preferExistingAst: false
|
|
1631
|
+
}) : null;
|
|
1632
|
+
if (modelValueAst) {
|
|
1633
|
+
const { name: mv } = getClickHandlerNameFromAst(modelValueAst);
|
|
1087
1634
|
modelValue = mv;
|
|
1088
1635
|
}
|
|
1089
1636
|
return { vModel, modelValue };
|
|
1090
1637
|
}
|
|
1091
|
-
function
|
|
1092
|
-
if (node.isSelfClosing) {
|
|
1093
|
-
const hasForDirective = nodeHasForDirective(node);
|
|
1094
|
-
if (hasForDirective) {
|
|
1095
|
-
return getKeyDirectiveValue(node);
|
|
1096
|
-
}
|
|
1097
|
-
}
|
|
1098
|
-
return null;
|
|
1099
|
-
}
|
|
1100
|
-
function getIdOrName(node) {
|
|
1638
|
+
function getStaticIdOrNameHint(node) {
|
|
1101
1639
|
let idAttr = findAttributeByKey(node, "id");
|
|
1102
1640
|
if (!idAttr) {
|
|
1103
1641
|
idAttr = findAttributeByKey(node, "name");
|
|
@@ -1105,8 +1643,9 @@ function getIdOrName(node) {
|
|
|
1105
1643
|
let identifier = idAttr?.value?.content ?? "";
|
|
1106
1644
|
if (!identifier) {
|
|
1107
1645
|
const dynamicIdAttr = findDirectiveByName(node, "bind", "id");
|
|
1108
|
-
|
|
1109
|
-
|
|
1646
|
+
const dynamicNameAttr = findDirectiveByName(node, "bind", "name");
|
|
1647
|
+
if (dynamicIdAttr?.exp || dynamicNameAttr?.exp) {
|
|
1648
|
+
return "";
|
|
1110
1649
|
}
|
|
1111
1650
|
}
|
|
1112
1651
|
if (identifier.includes("-")) {
|
|
@@ -1117,20 +1656,20 @@ function getIdOrName(node) {
|
|
|
1117
1656
|
}
|
|
1118
1657
|
return identifier;
|
|
1119
1658
|
}
|
|
1120
|
-
function
|
|
1659
|
+
function getContainedInSlotDataKeyInfo(node, hierarchyMap2) {
|
|
1121
1660
|
let parent = getParent(hierarchyMap2, node);
|
|
1122
1661
|
while (parent) {
|
|
1123
1662
|
if (parent.type === NodeTypes.ELEMENT && parent.tag === "template") {
|
|
1124
|
-
const
|
|
1125
|
-
if (
|
|
1126
|
-
return
|
|
1663
|
+
const slotScopeExpression = findTemplateSlotScopeExpression(parent);
|
|
1664
|
+
if (slotScopeExpression) {
|
|
1665
|
+
return tryGetTemplateSlotScopeKeyInfo(slotScopeExpression);
|
|
1127
1666
|
}
|
|
1128
1667
|
}
|
|
1129
1668
|
parent = getParent(hierarchyMap2, parent);
|
|
1130
1669
|
}
|
|
1131
1670
|
return null;
|
|
1132
1671
|
}
|
|
1133
|
-
function
|
|
1672
|
+
function getContainedInVForDirectiveKeyInfo(context, node, hierarchyMap2) {
|
|
1134
1673
|
if (!context.scopes.vFor || context.scopes.vFor === 0) {
|
|
1135
1674
|
return null;
|
|
1136
1675
|
}
|
|
@@ -1139,8 +1678,7 @@ function getContainedInVForDirectiveKeyValue(context, node, hierarchyMap2) {
|
|
|
1139
1678
|
if (parent.type === NodeTypes.ELEMENT) {
|
|
1140
1679
|
const forDirective = findDirectiveByName(parent, "for");
|
|
1141
1680
|
if (forDirective) {
|
|
1142
|
-
|
|
1143
|
-
return keyValue;
|
|
1681
|
+
return getKeyDirectiveInfo(parent);
|
|
1144
1682
|
}
|
|
1145
1683
|
}
|
|
1146
1684
|
parent = getParent(hierarchyMap2, parent);
|
|
@@ -1166,13 +1704,7 @@ function tryGetContainedInStaticVForSourceLiteralValues(context, _node, _hierarc
|
|
|
1166
1704
|
if (simpleSourceExp.constType === ConstantTypes.NOT_CONSTANT) {
|
|
1167
1705
|
return null;
|
|
1168
1706
|
}
|
|
1169
|
-
const iterableRaw = (
|
|
1170
|
-
try {
|
|
1171
|
-
return stringifyExpression(simpleSourceExp).trim();
|
|
1172
|
-
} catch {
|
|
1173
|
-
return (simpleSourceExp.loc?.source ?? "").trim();
|
|
1174
|
-
}
|
|
1175
|
-
})();
|
|
1707
|
+
const iterableRaw = getVueExpressionSource(simpleSourceExp, "compiled", "loc");
|
|
1176
1708
|
if (!iterableRaw) {
|
|
1177
1709
|
return null;
|
|
1178
1710
|
}
|
|
@@ -1228,91 +1760,95 @@ function nodeHandlerAttributeInfo(node) {
|
|
|
1228
1760
|
return null;
|
|
1229
1761
|
}
|
|
1230
1762
|
const exp = handlerDirective.exp;
|
|
1231
|
-
const source = (exp
|
|
1763
|
+
const source = getVueExpressionSource(exp, "content", "compiled");
|
|
1232
1764
|
if (!source) {
|
|
1233
1765
|
return null;
|
|
1234
1766
|
}
|
|
1235
1767
|
const mergeKey = `handler:expr:${source}`;
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1768
|
+
const expr = tryGetDirectiveBabelAst(handlerDirective, {
|
|
1769
|
+
preferredViews: ["content", "compiled"],
|
|
1770
|
+
plugins: ["typescript", "jsx"],
|
|
1771
|
+
// Vue's compiler AST can encode `_ctx.foo` as an Identifier name instead of a MemberExpression.
|
|
1772
|
+
// That is fine for Vue codegen, but our semantic-name extraction needs a normal Babel parse tree.
|
|
1773
|
+
preferExistingAst: false
|
|
1774
|
+
});
|
|
1775
|
+
if (!expr) {
|
|
1240
1776
|
return null;
|
|
1241
1777
|
}
|
|
1242
|
-
const
|
|
1778
|
+
const isNodeType2 = (node2, type) => {
|
|
1243
1779
|
return node2 !== null && node2.type === type;
|
|
1244
1780
|
};
|
|
1245
|
-
const
|
|
1246
|
-
return
|
|
1781
|
+
const isIdentifierNode2 = (node2) => {
|
|
1782
|
+
return isNodeType2(node2, "Identifier") && typeof node2.name === "string";
|
|
1247
1783
|
};
|
|
1248
|
-
const
|
|
1249
|
-
return
|
|
1784
|
+
const isStringLiteralNode2 = (node2) => {
|
|
1785
|
+
return isNodeType2(node2, "StringLiteral") && typeof node2.value === "string";
|
|
1250
1786
|
};
|
|
1251
1787
|
const isBooleanLiteralNode = (node2) => {
|
|
1252
|
-
return
|
|
1788
|
+
return isNodeType2(node2, "BooleanLiteral") && typeof node2.value === "boolean";
|
|
1253
1789
|
};
|
|
1254
1790
|
const isNumericLiteralNode = (node2) => {
|
|
1255
|
-
return
|
|
1791
|
+
return isNodeType2(node2, "NumericLiteral") && typeof node2.value === "number";
|
|
1256
1792
|
};
|
|
1257
1793
|
const isNullLiteralNode = (node2) => {
|
|
1258
|
-
return
|
|
1794
|
+
return isNodeType2(node2, "NullLiteral");
|
|
1259
1795
|
};
|
|
1260
1796
|
const isMemberExpressionNode = (node2) => {
|
|
1261
|
-
if (!
|
|
1797
|
+
if (!isNodeType2(node2, "MemberExpression"))
|
|
1262
1798
|
return false;
|
|
1263
1799
|
const n = node2;
|
|
1264
1800
|
return typeof n.computed === "boolean" && typeof n.object === "object" && n.object !== null && typeof n.property === "object" && n.property !== null;
|
|
1265
1801
|
};
|
|
1266
1802
|
const isCallExpressionNode = (node2) => {
|
|
1267
|
-
if (!
|
|
1803
|
+
if (!isNodeType2(node2, "CallExpression"))
|
|
1268
1804
|
return false;
|
|
1269
1805
|
const n = node2;
|
|
1270
1806
|
return typeof n.callee === "object" && n.callee !== null && Array.isArray(n.arguments);
|
|
1271
1807
|
};
|
|
1272
1808
|
const isAwaitExpressionNode = (node2) => {
|
|
1273
|
-
if (!
|
|
1809
|
+
if (!isNodeType2(node2, "AwaitExpression"))
|
|
1274
1810
|
return false;
|
|
1275
1811
|
const n = node2;
|
|
1276
1812
|
return typeof n.argument === "object" && n.argument !== null;
|
|
1277
1813
|
};
|
|
1278
1814
|
const isAssignmentExpressionNode = (node2) => {
|
|
1279
|
-
if (!
|
|
1815
|
+
if (!isNodeType2(node2, "AssignmentExpression"))
|
|
1280
1816
|
return false;
|
|
1281
1817
|
const n = node2;
|
|
1282
1818
|
return typeof n.left === "object" && n.left !== null && typeof n.right === "object" && n.right !== null;
|
|
1283
1819
|
};
|
|
1284
1820
|
const isArrowFunctionExpressionNode = (node2) => {
|
|
1285
|
-
if (!
|
|
1821
|
+
if (!isNodeType2(node2, "ArrowFunctionExpression"))
|
|
1286
1822
|
return false;
|
|
1287
1823
|
const n = node2;
|
|
1288
1824
|
return typeof n.body === "object" && n.body !== null;
|
|
1289
1825
|
};
|
|
1290
1826
|
const isBlockStatementNode = (node2) => {
|
|
1291
|
-
if (!
|
|
1827
|
+
if (!isNodeType2(node2, "BlockStatement"))
|
|
1292
1828
|
return false;
|
|
1293
1829
|
const n = node2;
|
|
1294
1830
|
return Array.isArray(n.body);
|
|
1295
1831
|
};
|
|
1296
1832
|
const isExpressionStatementNode = (node2) => {
|
|
1297
|
-
if (!
|
|
1833
|
+
if (!isNodeType2(node2, "ExpressionStatement"))
|
|
1298
1834
|
return false;
|
|
1299
1835
|
const n = node2;
|
|
1300
1836
|
return typeof n.expression === "object" && n.expression !== null;
|
|
1301
1837
|
};
|
|
1302
1838
|
const isReturnStatementNode = (node2) => {
|
|
1303
|
-
if (!
|
|
1839
|
+
if (!isNodeType2(node2, "ReturnStatement"))
|
|
1304
1840
|
return false;
|
|
1305
1841
|
const n = node2;
|
|
1306
1842
|
return typeof n.argument === "object" || n.argument === null;
|
|
1307
1843
|
};
|
|
1308
|
-
const
|
|
1309
|
-
if (!
|
|
1844
|
+
const isObjectExpressionNode2 = (node2) => {
|
|
1845
|
+
if (!isNodeType2(node2, "ObjectExpression"))
|
|
1310
1846
|
return false;
|
|
1311
1847
|
const n = node2;
|
|
1312
1848
|
return Array.isArray(n.properties);
|
|
1313
1849
|
};
|
|
1314
|
-
const
|
|
1315
|
-
if (!
|
|
1850
|
+
const isObjectPropertyNode2 = (node2) => {
|
|
1851
|
+
if (!isNodeType2(node2, "ObjectProperty"))
|
|
1316
1852
|
return false;
|
|
1317
1853
|
const n = node2;
|
|
1318
1854
|
return typeof n.computed === "boolean" && typeof n.key === "object" && n.key !== null && typeof n.value === "object" && n.value !== null;
|
|
@@ -1320,16 +1856,16 @@ function nodeHandlerAttributeInfo(node) {
|
|
|
1320
1856
|
const getLastIdentifierFromMemberChain = (node2) => {
|
|
1321
1857
|
if (!node2)
|
|
1322
1858
|
return null;
|
|
1323
|
-
if (
|
|
1859
|
+
if (isIdentifierNode2(node2))
|
|
1324
1860
|
return node2.name;
|
|
1325
1861
|
if (isMemberExpressionNode(node2)) {
|
|
1326
1862
|
const prop = node2.property;
|
|
1327
1863
|
if (node2.computed === false) {
|
|
1328
|
-
if (
|
|
1864
|
+
if (isIdentifierNode2(prop))
|
|
1329
1865
|
return prop.name;
|
|
1330
1866
|
}
|
|
1331
1867
|
if (node2.computed === true) {
|
|
1332
|
-
if (
|
|
1868
|
+
if (isStringLiteralNode2(prop))
|
|
1333
1869
|
return prop.value;
|
|
1334
1870
|
}
|
|
1335
1871
|
}
|
|
@@ -1339,7 +1875,7 @@ function nodeHandlerAttributeInfo(node) {
|
|
|
1339
1875
|
if (!node2) {
|
|
1340
1876
|
return null;
|
|
1341
1877
|
}
|
|
1342
|
-
if (
|
|
1878
|
+
if (isIdentifierNode2(node2)) {
|
|
1343
1879
|
return node2.name;
|
|
1344
1880
|
}
|
|
1345
1881
|
if (isMemberExpressionNode(node2)) {
|
|
@@ -1351,11 +1887,11 @@ function nodeHandlerAttributeInfo(node) {
|
|
|
1351
1887
|
if (!lhs) {
|
|
1352
1888
|
return null;
|
|
1353
1889
|
}
|
|
1354
|
-
if (
|
|
1890
|
+
if (isIdentifierNode2(lhs)) {
|
|
1355
1891
|
return lhs.name;
|
|
1356
1892
|
}
|
|
1357
1893
|
if (isMemberExpressionNode(lhs)) {
|
|
1358
|
-
if (lhs.computed === false &&
|
|
1894
|
+
if (lhs.computed === false && isIdentifierNode2(lhs.property) && lhs.property.name === "value") {
|
|
1359
1895
|
return getLastIdentifierFromMemberChain(lhs.object);
|
|
1360
1896
|
}
|
|
1361
1897
|
return getLastIdentifierFromMemberChain(lhs);
|
|
@@ -1363,7 +1899,7 @@ function nodeHandlerAttributeInfo(node) {
|
|
|
1363
1899
|
return null;
|
|
1364
1900
|
};
|
|
1365
1901
|
const isTemplateLiteralNode = (node2) => {
|
|
1366
|
-
if (!
|
|
1902
|
+
if (!isNodeType2(node2, "TemplateLiteral")) {
|
|
1367
1903
|
return false;
|
|
1368
1904
|
}
|
|
1369
1905
|
const n = node2;
|
|
@@ -1382,7 +1918,7 @@ function nodeHandlerAttributeInfo(node) {
|
|
|
1382
1918
|
if (isNullLiteralNode(arg)) {
|
|
1383
1919
|
return "Null";
|
|
1384
1920
|
}
|
|
1385
|
-
if (
|
|
1921
|
+
if (isStringLiteralNode2(arg)) {
|
|
1386
1922
|
const cleaned = (arg.value ?? "").trim();
|
|
1387
1923
|
if (!cleaned) {
|
|
1388
1924
|
return null;
|
|
@@ -1408,7 +1944,7 @@ function nodeHandlerAttributeInfo(node) {
|
|
|
1408
1944
|
return toPascalCase(stableName.slice(0, 24));
|
|
1409
1945
|
}
|
|
1410
1946
|
}
|
|
1411
|
-
if (
|
|
1947
|
+
if (isIdentifierNode2(arg)) {
|
|
1412
1948
|
const firstChar = arg.name.charAt(0);
|
|
1413
1949
|
const isUpperAlpha = firstChar !== "" && firstChar === firstChar.toUpperCase() && firstChar !== firstChar.toLowerCase();
|
|
1414
1950
|
if (isUpperAlpha) {
|
|
@@ -1420,7 +1956,7 @@ function nodeHandlerAttributeInfo(node) {
|
|
|
1420
1956
|
const getStableSuffixFromCall = (call) => {
|
|
1421
1957
|
const args = call.arguments ?? [];
|
|
1422
1958
|
const first = args.length > 0 ? args[0] : null;
|
|
1423
|
-
if (!
|
|
1959
|
+
if (!isObjectExpressionNode2(first)) {
|
|
1424
1960
|
const parts2 = [];
|
|
1425
1961
|
for (const arg of args.slice(0, 4)) {
|
|
1426
1962
|
const w = stableWordFromValue(arg ?? null);
|
|
@@ -1439,20 +1975,20 @@ function nodeHandlerAttributeInfo(node) {
|
|
|
1439
1975
|
}
|
|
1440
1976
|
const parts = [];
|
|
1441
1977
|
for (const prop of first.properties ?? []) {
|
|
1442
|
-
if (!
|
|
1978
|
+
if (!isObjectPropertyNode2(prop)) {
|
|
1443
1979
|
continue;
|
|
1444
1980
|
}
|
|
1445
1981
|
if (prop.computed) {
|
|
1446
1982
|
continue;
|
|
1447
1983
|
}
|
|
1448
|
-
const keyName =
|
|
1984
|
+
const keyName = isIdentifierNode2(prop.key) ? prop.key.name : isStringLiteralNode2(prop.key) ? prop.key.value : null;
|
|
1449
1985
|
if (!keyName) {
|
|
1450
1986
|
continue;
|
|
1451
1987
|
}
|
|
1452
1988
|
let valueWord = null;
|
|
1453
1989
|
if (isBooleanLiteralNode(prop.value)) {
|
|
1454
1990
|
valueWord = prop.value.value ? "True" : "False";
|
|
1455
|
-
} else if (
|
|
1991
|
+
} else if (isStringLiteralNode2(prop.value)) {
|
|
1456
1992
|
const cleaned = (prop.value.value ?? "").trim();
|
|
1457
1993
|
if (cleaned) {
|
|
1458
1994
|
valueWord = toPascalCase(cleaned.slice(0, 24));
|
|
@@ -1581,13 +2117,18 @@ function getDataTestIdValueFromValueAttribute(node, actualFileName, attributeKey
|
|
|
1581
2117
|
return staticAttributeValue(`${actualFileName}-${value}-${role}`);
|
|
1582
2118
|
}
|
|
1583
2119
|
const attrDynamic = findDirectiveByName(node, "bind", attributeKey);
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
2120
|
+
const attrDynamicAst = attrDynamic ? tryGetDirectiveBabelAst(attrDynamic, {
|
|
2121
|
+
preferredViews: ["loc", "compiled"],
|
|
2122
|
+
plugins: ["typescript"],
|
|
2123
|
+
preferExistingAst: false
|
|
2124
|
+
}) : null;
|
|
2125
|
+
if (attrDynamic?.exp && attrDynamicAst) {
|
|
2126
|
+
let value = getVueExpressionSource(attrDynamic.exp, "loc", "compiled");
|
|
2127
|
+
if (isMemberExpression(attrDynamicAst) || isOptionalMemberExpression(attrDynamicAst)) {
|
|
1587
2128
|
return staticAttributeValue(`${actualFileName}-${value.replaceAll(".", "")}-${role}`);
|
|
1588
2129
|
}
|
|
1589
|
-
if (
|
|
1590
|
-
value =
|
|
2130
|
+
if (isCallExpression(attrDynamicAst) || isOptionalCallExpression(attrDynamicAst)) {
|
|
2131
|
+
value = getVueExpressionSource(attrDynamic.exp, "compiled", "loc");
|
|
1591
2132
|
return templateAttributeValue(`${actualFileName}-\${${value}}-${role}`);
|
|
1592
2133
|
}
|
|
1593
2134
|
return staticAttributeValue(`${actualFileName}-${value}-${role}`);
|
|
@@ -1595,16 +2136,16 @@ function getDataTestIdValueFromValueAttribute(node, actualFileName, attributeKey
|
|
|
1595
2136
|
return null;
|
|
1596
2137
|
}
|
|
1597
2138
|
function generateToDirectiveDataTestId(componentName, node, toDirective, context, hierarchyMap2, nativeWrappers) {
|
|
1598
|
-
const
|
|
1599
|
-
if (
|
|
1600
|
-
return templateAttributeValue(`${componentName}-${
|
|
2139
|
+
const keyInfo = getKeyDirectiveInfo(node) || getContainedInVForDirectiveKeyInfo(context, node, hierarchyMap2);
|
|
2140
|
+
if (keyInfo) {
|
|
2141
|
+
return templateAttributeValue(`${componentName}-${keyInfo.selectorFragment}-${formatTagName(node, nativeWrappers)}`);
|
|
1601
2142
|
} else {
|
|
1602
2143
|
let name = toDirectiveObjectFieldNameValue(toDirective);
|
|
1603
2144
|
if (!name) {
|
|
1604
2145
|
if (toDirective.exp == null) {
|
|
1605
2146
|
return null;
|
|
1606
2147
|
}
|
|
1607
|
-
const source =
|
|
2148
|
+
const source = getVueExpressionSource(toDirective.exp, "compiled", "loc");
|
|
1608
2149
|
const toAst = toDirective.exp.ast;
|
|
1609
2150
|
const interpolated = toAst !== void 0 && toAst !== null && toAst !== false && isTemplateLiteral(toAst);
|
|
1610
2151
|
return templateAttributeValue(`${componentName}-\${${source}${interpolated ? ".replaceAll(' ', '')" : "?.name?.replaceAll(' ', '') ?? ''"}}${formatTagName(node, nativeWrappers)}`);
|
|
@@ -1628,44 +2169,46 @@ function toDirectiveObjectFieldNameValue(node) {
|
|
|
1628
2169
|
if (!node.exp || node.exp.type !== NodeTypes.COMPOUND_EXPRESSION && node.exp.type !== NodeTypes.SIMPLE_EXPRESSION) {
|
|
1629
2170
|
return null;
|
|
1630
2171
|
}
|
|
1631
|
-
const
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
const isStringLiteralNode = (n) => {
|
|
1638
|
-
return isNodeType(n, "StringLiteral") && typeof n.value === "string";
|
|
1639
|
-
};
|
|
1640
|
-
const isIdentifierNode = (n) => {
|
|
1641
|
-
return isNodeType(n, "Identifier") && typeof n.name === "string";
|
|
1642
|
-
};
|
|
1643
|
-
const isObjectPropertyNode = (n) => {
|
|
1644
|
-
if (!isNodeType(n, "ObjectProperty"))
|
|
1645
|
-
return false;
|
|
1646
|
-
const nn = n;
|
|
1647
|
-
return typeof nn.key === "object" && nn.key !== null && typeof nn.value === "object" && nn.value !== null;
|
|
1648
|
-
};
|
|
1649
|
-
const isObjectExpressionNode = (n) => {
|
|
1650
|
-
if (!isNodeType(n, "ObjectExpression"))
|
|
1651
|
-
return false;
|
|
1652
|
-
const nn = n;
|
|
1653
|
-
return Array.isArray(nn.properties);
|
|
1654
|
-
};
|
|
1655
|
-
if (!isObjectExpressionNode(expr))
|
|
1656
|
-
return null;
|
|
1657
|
-
const nameProp = expr.properties.find((p) => {
|
|
1658
|
-
if (!isObjectPropertyNode(p))
|
|
1659
|
-
return false;
|
|
1660
|
-
const key = p.key;
|
|
1661
|
-
return isIdentifierNode(key) && key.name === "name" || isStringLiteralNode(key) && key.value === "name";
|
|
1662
|
-
});
|
|
1663
|
-
if (!nameProp || !isObjectPropertyNode(nameProp) || !isStringLiteralNode(nameProp.value))
|
|
1664
|
-
return null;
|
|
1665
|
-
return toPascalCase(nameProp.value.value);
|
|
1666
|
-
} catch {
|
|
2172
|
+
const expr = tryGetDirectiveBabelAst(node, {
|
|
2173
|
+
preferredViews: ["loc", "compiled"],
|
|
2174
|
+
plugins: ["typescript"],
|
|
2175
|
+
preferExistingAst: false
|
|
2176
|
+
});
|
|
2177
|
+
if (!expr) {
|
|
1667
2178
|
return null;
|
|
1668
2179
|
}
|
|
2180
|
+
const isNodeType2 = (n, type) => {
|
|
2181
|
+
return n !== null && n.type === type;
|
|
2182
|
+
};
|
|
2183
|
+
const isStringLiteralNode2 = (n) => {
|
|
2184
|
+
return isNodeType2(n, "StringLiteral") && typeof n.value === "string";
|
|
2185
|
+
};
|
|
2186
|
+
const isIdentifierNode2 = (n) => {
|
|
2187
|
+
return isNodeType2(n, "Identifier") && typeof n.name === "string";
|
|
2188
|
+
};
|
|
2189
|
+
const isObjectPropertyNode2 = (n) => {
|
|
2190
|
+
if (!isNodeType2(n, "ObjectProperty"))
|
|
2191
|
+
return false;
|
|
2192
|
+
const nn = n;
|
|
2193
|
+
return typeof nn.key === "object" && nn.key !== null && typeof nn.value === "object" && nn.value !== null;
|
|
2194
|
+
};
|
|
2195
|
+
const isObjectExpressionNode2 = (n) => {
|
|
2196
|
+
if (!isNodeType2(n, "ObjectExpression"))
|
|
2197
|
+
return false;
|
|
2198
|
+
const nn = n;
|
|
2199
|
+
return Array.isArray(nn.properties);
|
|
2200
|
+
};
|
|
2201
|
+
if (!isObjectExpressionNode2(expr))
|
|
2202
|
+
return null;
|
|
2203
|
+
const nameProp = expr.properties.find((p) => {
|
|
2204
|
+
if (!isObjectPropertyNode2(p))
|
|
2205
|
+
return false;
|
|
2206
|
+
const key = p.key;
|
|
2207
|
+
return isIdentifierNode2(key) && key.name === "name" || isStringLiteralNode2(key) && key.value === "name";
|
|
2208
|
+
});
|
|
2209
|
+
if (!nameProp || !isObjectPropertyNode2(nameProp) || !isStringLiteralNode2(nameProp.value))
|
|
2210
|
+
return null;
|
|
2211
|
+
return toPascalCase(nameProp.value.value);
|
|
1669
2212
|
}
|
|
1670
2213
|
function getComposedClickHandlerContent(node, _context, innerText, clickDirective, _options = {}) {
|
|
1671
2214
|
const click = clickDirective ?? tryGetClickDirective(node);
|
|
@@ -1675,7 +2218,7 @@ function getComposedClickHandlerContent(node, _context, innerText, clickDirectiv
|
|
|
1675
2218
|
let handlerName = "";
|
|
1676
2219
|
if (click.exp) {
|
|
1677
2220
|
const exp = click.exp;
|
|
1678
|
-
const source = (exp
|
|
2221
|
+
const source = getVueExpressionSource(exp, "content", "compiled");
|
|
1679
2222
|
if (source) {
|
|
1680
2223
|
const parsed = tryParseBabelAstFromHandlerSource(source);
|
|
1681
2224
|
if (parsed) {
|
|
@@ -1696,9 +2239,9 @@ function tryParseBabelAstFromHandlerSource(source) {
|
|
|
1696
2239
|
const trimmed = source.trim();
|
|
1697
2240
|
if (!trimmed)
|
|
1698
2241
|
return null;
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
2242
|
+
const expressionAst = tryParseBabelExpressionFromSource(trimmed, ["typescript", "jsx"]);
|
|
2243
|
+
if (expressionAst) {
|
|
2244
|
+
return expressionAst;
|
|
1702
2245
|
}
|
|
1703
2246
|
try {
|
|
1704
2247
|
return parse(trimmed, { sourceType: "module", plugins: ["typescript", "jsx"] });
|
|
@@ -2037,23 +2580,24 @@ function tryGetExistingElementDataTestId(node, attributeName = "data-testid") {
|
|
|
2037
2580
|
return null;
|
|
2038
2581
|
}
|
|
2039
2582
|
const simpleExp = exp;
|
|
2040
|
-
const ast = simpleExp
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
const
|
|
2047
|
-
const unwrappedTemplate = raw2.startsWith("`") && raw2.endsWith("`") && raw2.length >= 2 ? raw2.slice(1, -1) : cooked;
|
|
2583
|
+
const ast = tryGetVueExpressionAst(simpleExp, {
|
|
2584
|
+
preferredViews: ["content", "loc", "compiled"],
|
|
2585
|
+
plugins: ["typescript"]
|
|
2586
|
+
});
|
|
2587
|
+
const unwrappedTemplateLiteral = tryUnwrapTemplateLiteralExpressionSource(simpleExp);
|
|
2588
|
+
if (unwrappedTemplateLiteral) {
|
|
2589
|
+
const isStatic = unwrappedTemplateLiteral.expressionCount === 0;
|
|
2048
2590
|
if (isStatic) {
|
|
2049
|
-
return { value:
|
|
2591
|
+
return { value: unwrappedTemplateLiteral.template, isDynamic: false, isStaticLiteral: true };
|
|
2050
2592
|
}
|
|
2593
|
+
const templateValue = templateAttributeValue(unwrappedTemplateLiteral.template);
|
|
2051
2594
|
return {
|
|
2052
|
-
value:
|
|
2595
|
+
value: templateValue.template,
|
|
2053
2596
|
isDynamic: true,
|
|
2054
2597
|
isStaticLiteral: false,
|
|
2055
|
-
template:
|
|
2056
|
-
|
|
2598
|
+
template: templateValue.template,
|
|
2599
|
+
parsedTemplate: templateValue.parsedTemplate,
|
|
2600
|
+
templateExpressionCount: unwrappedTemplateLiteral.expressionCount
|
|
2057
2601
|
};
|
|
2058
2602
|
}
|
|
2059
2603
|
if (ast && typeof ast === "object" && "type" in ast && ast.type === "StringLiteral") {
|
|
@@ -2066,11 +2610,13 @@ function tryGetExistingElementDataTestId(node, attributeName = "data-testid") {
|
|
|
2066
2610
|
}
|
|
2067
2611
|
const preservableReference = tryGetPreservableDynamicReferenceExpression(ast);
|
|
2068
2612
|
if (preservableReference) {
|
|
2613
|
+
const templateValue = templateAttributeValue(`\${${preservableReference}}`);
|
|
2069
2614
|
return {
|
|
2070
2615
|
value: preservableReference,
|
|
2071
2616
|
isDynamic: true,
|
|
2072
2617
|
isStaticLiteral: false,
|
|
2073
|
-
template:
|
|
2618
|
+
template: templateValue.template,
|
|
2619
|
+
parsedTemplate: templateValue.parsedTemplate,
|
|
2074
2620
|
templateExpressionCount: 1,
|
|
2075
2621
|
rawExpression: preservableReference
|
|
2076
2622
|
};
|
|
@@ -2079,46 +2625,12 @@ function tryGetExistingElementDataTestId(node, attributeName = "data-testid") {
|
|
|
2079
2625
|
if (!raw) {
|
|
2080
2626
|
return null;
|
|
2081
2627
|
}
|
|
2082
|
-
try {
|
|
2083
|
-
const ast2 = parseExpression(raw, { plugins: ["typescript"] });
|
|
2084
|
-
if (ast2 && typeof ast2 === "object" && "type" in ast2 && ast2.type === "TemplateLiteral") {
|
|
2085
|
-
const tl = ast2;
|
|
2086
|
-
const cooked = (tl.quasis ?? []).map((q) => q.value?.cooked ?? "").join("");
|
|
2087
|
-
const expressionCount = (tl.expressions ?? []).length;
|
|
2088
|
-
const isStatic = expressionCount === 0;
|
|
2089
|
-
const unwrappedTemplate = raw.startsWith("`") && raw.endsWith("`") && raw.length >= 2 ? raw.slice(1, -1) : cooked;
|
|
2090
|
-
if (isStatic) {
|
|
2091
|
-
return { value: unwrappedTemplate, isDynamic: false, isStaticLiteral: true };
|
|
2092
|
-
}
|
|
2093
|
-
return {
|
|
2094
|
-
value: unwrappedTemplate,
|
|
2095
|
-
isDynamic: true,
|
|
2096
|
-
isStaticLiteral: false,
|
|
2097
|
-
template: unwrappedTemplate,
|
|
2098
|
-
templateExpressionCount: expressionCount
|
|
2099
|
-
};
|
|
2100
|
-
}
|
|
2101
|
-
if (ast2 && typeof ast2 === "object" && "type" in ast2 && ast2.type === "StringLiteral") {
|
|
2102
|
-
const sl = ast2;
|
|
2103
|
-
return { value: sl.value ?? "", isDynamic: false, isStaticLiteral: true };
|
|
2104
|
-
}
|
|
2105
|
-
const preservableReference2 = tryGetPreservableDynamicReferenceExpression(ast2);
|
|
2106
|
-
if (preservableReference2) {
|
|
2107
|
-
return {
|
|
2108
|
-
value: preservableReference2,
|
|
2109
|
-
isDynamic: true,
|
|
2110
|
-
isStaticLiteral: false,
|
|
2111
|
-
template: `\${${preservableReference2}}`,
|
|
2112
|
-
templateExpressionCount: 1,
|
|
2113
|
-
rawExpression: preservableReference2
|
|
2114
|
-
};
|
|
2115
|
-
}
|
|
2116
|
-
} catch {
|
|
2117
|
-
}
|
|
2118
2628
|
return { value: raw, isDynamic: true, isStaticLiteral: false, rawExpression: raw };
|
|
2119
2629
|
}
|
|
2120
2630
|
function isTemplatePlaceholder(part) {
|
|
2121
|
-
|
|
2631
|
+
const parsedTemplate = tryParseTemplateFragment(part);
|
|
2632
|
+
const templateFragment = parsedTemplate ? getSingleExpressionTemplateFragment(parsedTemplate) : null;
|
|
2633
|
+
return !!templateFragment && templateFragment.prefix === "" && templateFragment.suffix === "";
|
|
2122
2634
|
}
|
|
2123
2635
|
function isAllCapsOrDigits(value) {
|
|
2124
2636
|
if (value.length <= 1) {
|
|
@@ -2171,33 +2683,14 @@ function safeMethodNameFromParts(parts) {
|
|
|
2171
2683
|
}
|
|
2172
2684
|
return name;
|
|
2173
2685
|
}
|
|
2174
|
-
function
|
|
2686
|
+
function toPomKeyPattern(templateValue) {
|
|
2687
|
+
const { templateLiteral } = templateValue.parsedTemplate;
|
|
2175
2688
|
let out = "";
|
|
2176
|
-
let i = 0;
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
out += template.slice(i);
|
|
2181
|
-
break;
|
|
2182
|
-
}
|
|
2183
|
-
out += template.slice(i, start);
|
|
2184
|
-
let depth = 1;
|
|
2185
|
-
let j = start + 2;
|
|
2186
|
-
while (j < template.length && depth > 0) {
|
|
2187
|
-
if (template[j] === "{") {
|
|
2188
|
-
depth++;
|
|
2189
|
-
} else if (template[j] === "}") {
|
|
2190
|
-
depth--;
|
|
2191
|
-
}
|
|
2192
|
-
j++;
|
|
2193
|
-
}
|
|
2194
|
-
const end = depth === 0 ? j - 1 : -1;
|
|
2195
|
-
if (end < 0) {
|
|
2196
|
-
out += template.slice(start);
|
|
2197
|
-
break;
|
|
2689
|
+
for (let i = 0; i < templateLiteral.quasis.length; i += 1) {
|
|
2690
|
+
out += templateLiteral.quasis[i]?.value.raw ?? "";
|
|
2691
|
+
if (templateLiteral.expressions[i]) {
|
|
2692
|
+
out += "${key}";
|
|
2198
2693
|
}
|
|
2199
|
-
out += "${key}";
|
|
2200
|
-
i = end + 1;
|
|
2201
2694
|
}
|
|
2202
2695
|
return out;
|
|
2203
2696
|
}
|
|
@@ -2205,8 +2698,8 @@ function applyResolvedDataTestId(args) {
|
|
|
2205
2698
|
const addHtmlAttribute = args.addHtmlAttribute ?? true;
|
|
2206
2699
|
const entryOverrides = args.entryOverrides ?? {};
|
|
2207
2700
|
const testIdAttribute = args.testIdAttribute ?? "data-testid";
|
|
2208
|
-
const existingIdBehavior = args.existingIdBehavior ?? "
|
|
2209
|
-
const nameCollisionBehavior = args.nameCollisionBehavior ?? "
|
|
2701
|
+
const existingIdBehavior = args.existingIdBehavior ?? "error";
|
|
2702
|
+
const nameCollisionBehavior = args.nameCollisionBehavior ?? "error";
|
|
2210
2703
|
const warn = args.warn;
|
|
2211
2704
|
const getBestKeyAccessCandidates = (expr) => {
|
|
2212
2705
|
if (!expr) {
|
|
@@ -2215,7 +2708,10 @@ function applyResolvedDataTestId(args) {
|
|
|
2215
2708
|
return splitNullishCoalescingExpression(expr);
|
|
2216
2709
|
};
|
|
2217
2710
|
let dataTestId = args.preferredGeneratedValue;
|
|
2711
|
+
let runtimeDataTestId = args.preferredRuntimeValue ?? args.preferredGeneratedValue;
|
|
2218
2712
|
let fromExisting = false;
|
|
2713
|
+
const bestKeyPreservePlaceholder = args.keyInfo?.runtimeFragment ?? null;
|
|
2714
|
+
const bestKeyVariable = args.keyInfo?.rawExpression ?? null;
|
|
2219
2715
|
const existing = tryGetExistingElementDataTestId(args.element, testIdAttribute);
|
|
2220
2716
|
if (existing) {
|
|
2221
2717
|
const loc = args.element.loc?.start;
|
|
@@ -2236,8 +2732,10 @@ Bulk cleanup: run ESLint with the @immense/vue-pom-generator/remove-existing-tes
|
|
|
2236
2732
|
if (existingIdBehavior === "preserve") {
|
|
2237
2733
|
if (existing.isDynamic) {
|
|
2238
2734
|
if (existing.template) {
|
|
2239
|
-
const
|
|
2240
|
-
|
|
2735
|
+
const existingTemplateValue = existing.parsedTemplate ? { kind: "template", template: existing.template, parsedTemplate: existing.parsedTemplate } : templateAttributeValue(existing.template);
|
|
2736
|
+
const existingTemplateFragment = getSingleExpressionTemplateFragment(existingTemplateValue.parsedTemplate);
|
|
2737
|
+
const requiredKeyTemplateValue = bestKeyPreservePlaceholder ? templateAttributeValue(bestKeyPreservePlaceholder) : null;
|
|
2738
|
+
if ((existing.templateExpressionCount ?? 0) !== 1 || !existingTemplateFragment) {
|
|
2241
2739
|
throw new Error(
|
|
2242
2740
|
`[vue-pom-generator] Existing ${attrLabel} is a template literal with multiple interpolations and cannot be preserved safely.
|
|
2243
2741
|
Component: ${args.componentName}
|
|
@@ -2247,20 +2745,21 @@ Existing ${attrLabel}: ${JSON.stringify(existing.value)}
|
|
|
2247
2745
|
Fix: reduce the template to a single key-based interpolation, or remove the explicit ${attrLabel} so it can be auto-generated.`
|
|
2248
2746
|
);
|
|
2249
2747
|
}
|
|
2250
|
-
const hasExact =
|
|
2251
|
-
const hasVarAccess = getBestKeyAccessCandidates(
|
|
2252
|
-
if (!hasExact && !hasVarAccess &&
|
|
2748
|
+
const hasExact = requiredKeyTemplateValue ? templateFragmentContainsSingleExpression(existingTemplateValue.parsedTemplate, requiredKeyTemplateValue.parsedTemplate) : false;
|
|
2749
|
+
const hasVarAccess = getBestKeyAccessCandidates(bestKeyVariable).some((candidate) => existingTemplateFragment.expressionSource === candidate);
|
|
2750
|
+
if (!hasExact && !hasVarAccess && bestKeyPreservePlaceholder) {
|
|
2253
2751
|
throw new Error(
|
|
2254
2752
|
`[vue-pom-generator] Existing ${attrLabel} appears to be missing the key placeholder needed to keep it unique.
|
|
2255
2753
|
Component: ${args.componentName}
|
|
2256
2754
|
File: ${file}:${locationHint}
|
|
2257
2755
|
Existing ${attrLabel}: ${JSON.stringify(existing.value)}
|
|
2258
|
-
Required placeholder: ${JSON.stringify(
|
|
2756
|
+
Required placeholder: ${JSON.stringify(bestKeyPreservePlaceholder)}${bestKeyVariable ? ` or an access on "${bestKeyVariable}"` : ""}
|
|
2259
2757
|
|
|
2260
|
-
Fix: either (1) include ${
|
|
2758
|
+
Fix: either (1) include ${bestKeyPreservePlaceholder} in your :${attrLabel} template literal, or (2) remove the explicit ${attrLabel} so it can be auto-generated.`
|
|
2261
2759
|
);
|
|
2262
2760
|
}
|
|
2263
|
-
dataTestId =
|
|
2761
|
+
dataTestId = existingTemplateValue;
|
|
2762
|
+
runtimeDataTestId = existingTemplateValue;
|
|
2264
2763
|
fromExisting = true;
|
|
2265
2764
|
} else {
|
|
2266
2765
|
throw new Error(
|
|
@@ -2274,18 +2773,19 @@ If you really need a computed id, do not set existingIdBehavior="preserve".`
|
|
|
2274
2773
|
);
|
|
2275
2774
|
}
|
|
2276
2775
|
} else {
|
|
2277
|
-
if (
|
|
2776
|
+
if (bestKeyPreservePlaceholder && existing.isStaticLiteral) {
|
|
2278
2777
|
throw new Error(
|
|
2279
2778
|
`[vue-pom-generator] Existing ${attrLabel} appears to be missing the key placeholder needed to keep it unique.
|
|
2280
2779
|
Component: ${args.componentName}
|
|
2281
2780
|
File: ${file}:${locationHint}
|
|
2282
2781
|
Existing ${attrLabel}: ${JSON.stringify(existing.value)}
|
|
2283
|
-
Required placeholder: ${JSON.stringify(
|
|
2782
|
+
Required placeholder: ${JSON.stringify(bestKeyPreservePlaceholder)}${bestKeyVariable ? ` or an access on "${bestKeyVariable}"` : ""}
|
|
2284
2783
|
|
|
2285
|
-
Fix: either (1) include ${
|
|
2784
|
+
Fix: either (1) include ${bestKeyPreservePlaceholder} in your :${attrLabel} template literal, or (2) remove the explicit ${attrLabel} so it can be auto-generated.`
|
|
2286
2785
|
);
|
|
2287
2786
|
}
|
|
2288
2787
|
dataTestId = staticAttributeValue(existing.value);
|
|
2788
|
+
runtimeDataTestId = staticAttributeValue(existing.value);
|
|
2289
2789
|
fromExisting = true;
|
|
2290
2790
|
}
|
|
2291
2791
|
}
|
|
@@ -2315,8 +2815,10 @@ Fix: either (1) include ${args.bestKeyPlaceholder} in your :${attrLabel} templat
|
|
|
2315
2815
|
};
|
|
2316
2816
|
const normalizedRole = normalizeNativeRole(args.nativeRole) ?? "button";
|
|
2317
2817
|
const targetPageObjectModelClass = entryOverrides.targetPageObjectModelClass;
|
|
2318
|
-
const formattedDataTestIdForPom = dataTestId.kind === "template" ?
|
|
2319
|
-
const
|
|
2818
|
+
const formattedDataTestIdForPom = dataTestId.kind === "template" ? toPomKeyPattern(dataTestId) : dataTestId.value;
|
|
2819
|
+
const selectorPatternKind = dataTestId.kind === "template" ? "parameterized" : "static";
|
|
2820
|
+
const selectorPattern = createPomStringPattern(formattedDataTestIdForPom, selectorPatternKind);
|
|
2821
|
+
const selectorIsParameterized = selectorPatternKind === "parameterized";
|
|
2320
2822
|
const deriveBaseMethodNameFromHint = (hint) => {
|
|
2321
2823
|
const hintRaw = (hint ?? "").trim();
|
|
2322
2824
|
const trimEdgeSeparators = (value) => {
|
|
@@ -2353,7 +2855,7 @@ Fix: either (1) include ${args.bestKeyPlaceholder} in your :${attrLabel} templat
|
|
|
2353
2855
|
}
|
|
2354
2856
|
return value.slice(0, idx) + value.slice(idx + "ByKey".length);
|
|
2355
2857
|
};
|
|
2356
|
-
const
|
|
2858
|
+
const hasRoleSuffix2 = (baseName, roleSuffix) => {
|
|
2357
2859
|
if (baseName.endsWith(roleSuffix)) {
|
|
2358
2860
|
return true;
|
|
2359
2861
|
}
|
|
@@ -2363,14 +2865,14 @@ Fix: either (1) include ${args.bestKeyPlaceholder} in your :${attrLabel} templat
|
|
|
2363
2865
|
const getPrimaryGetterName = (primaryMethodName) => {
|
|
2364
2866
|
const roleSuffix = upperFirst(normalizedRole || "Element");
|
|
2365
2867
|
const baseName = upperFirst(primaryMethodName);
|
|
2366
|
-
const propertyName =
|
|
2367
|
-
return
|
|
2868
|
+
const propertyName = hasRoleSuffix2(baseName, roleSuffix) ? baseName : `${baseName}${roleSuffix}`;
|
|
2869
|
+
return selectorIsParameterized ? removeByKeySegment2(propertyName) : propertyName;
|
|
2368
2870
|
};
|
|
2369
2871
|
const getPrimaryGetterNameCandidates = (primaryMethodName) => {
|
|
2370
2872
|
const roleSuffix = upperFirst(normalizedRole || "Element");
|
|
2371
2873
|
const baseName = upperFirst(primaryMethodName);
|
|
2372
|
-
const propertyName =
|
|
2373
|
-
if (!
|
|
2874
|
+
const propertyName = hasRoleSuffix2(baseName, roleSuffix) ? baseName : `${baseName}${roleSuffix}`;
|
|
2875
|
+
if (!selectorIsParameterized) {
|
|
2374
2876
|
return { primary: propertyName };
|
|
2375
2877
|
}
|
|
2376
2878
|
const stripped = removeByKeySegment2(propertyName);
|
|
@@ -2432,11 +2934,11 @@ Fix: either (1) include ${args.bestKeyPlaceholder} in your :${attrLabel} templat
|
|
|
2432
2934
|
return false;
|
|
2433
2935
|
}
|
|
2434
2936
|
const existingSelectors = [
|
|
2435
|
-
existingPom.
|
|
2436
|
-
...existingPom.
|
|
2937
|
+
existingPom.selector,
|
|
2938
|
+
...existingPom.alternateSelectors ?? []
|
|
2437
2939
|
];
|
|
2438
|
-
const sharesSelectorIdentity = existingSelectors.
|
|
2439
|
-
if (
|
|
2940
|
+
const sharesSelectorIdentity = existingSelectors.some((existingSelector) => pomStringPatternEquals(existingSelector, selectorPattern));
|
|
2941
|
+
if (selectorIsParameterized && !sharesSelectorIdentity) {
|
|
2440
2942
|
return false;
|
|
2441
2943
|
}
|
|
2442
2944
|
if (!mergeKey && !sharesSelectorIdentity) {
|
|
@@ -2451,12 +2953,15 @@ Fix: either (1) include ${args.bestKeyPlaceholder} in your :${attrLabel} templat
|
|
|
2451
2953
|
if ((existingEntry.targetPageObjectModelClass ?? null) !== (targetPageObjectModelClass ?? null)) {
|
|
2452
2954
|
return false;
|
|
2453
2955
|
}
|
|
2454
|
-
if (existingPom.
|
|
2455
|
-
existingPom.
|
|
2456
|
-
if (!existingPom.
|
|
2457
|
-
existingPom.
|
|
2956
|
+
if (!pomStringPatternEquals(existingPom.selector, selectorPattern)) {
|
|
2957
|
+
existingPom.alternateSelectors ??= [];
|
|
2958
|
+
if (!(existingPom.alternateSelectors ?? []).some((existingSelector) => pomStringPatternEquals(existingSelector, selectorPattern))) {
|
|
2959
|
+
existingPom.alternateSelectors.push(selectorPattern);
|
|
2458
2960
|
}
|
|
2459
2961
|
}
|
|
2962
|
+
if (selectorIsParameterized && !existingPom.parameters.some((param) => param.name === "key")) {
|
|
2963
|
+
existingPom.parameters = [createPomParameterSpec("key", keyTypeFromValues), ...existingPom.parameters];
|
|
2964
|
+
}
|
|
2460
2965
|
return true;
|
|
2461
2966
|
};
|
|
2462
2967
|
let methodName = "";
|
|
@@ -2469,7 +2974,7 @@ Fix: either (1) include ${args.bestKeyPlaceholder} in your :${attrLabel} templat
|
|
|
2469
2974
|
let suffix = 1;
|
|
2470
2975
|
while (true) {
|
|
2471
2976
|
const baseWithSuffix = suffix === 1 ? base : `${base}${suffix}`;
|
|
2472
|
-
const candidate =
|
|
2977
|
+
const candidate = selectorIsParameterized ? `${baseWithSuffix}ByKey` : baseWithSuffix;
|
|
2473
2978
|
const actionName = getPrimaryActionMethodName(candidate);
|
|
2474
2979
|
const getterCandidates = getPrimaryGetterNameCandidates(candidate);
|
|
2475
2980
|
let chosenGetterName = getterCandidates.primary;
|
|
@@ -2493,9 +2998,9 @@ Fix: either (1) include ${args.bestKeyPlaceholder} in your :${attrLabel} templat
|
|
|
2493
2998
|
if (conflicts && nameCollisionBehavior === "error") {
|
|
2494
2999
|
const roleSuffix = upperFirst(normalizedRole || "Element");
|
|
2495
3000
|
const baseNameUpper = upperFirst(baseWithSuffix);
|
|
2496
|
-
if (!
|
|
3001
|
+
if (!hasRoleSuffix2(baseNameUpper, roleSuffix)) {
|
|
2497
3002
|
const baseWithRoleSuffix = `${baseWithSuffix}${roleSuffix}`;
|
|
2498
|
-
const candidateWithRoleSuffix =
|
|
3003
|
+
const candidateWithRoleSuffix = selectorIsParameterized ? `${baseWithRoleSuffix}ByKey` : baseWithRoleSuffix;
|
|
2499
3004
|
const actionNameWithRoleSuffix = getPrimaryActionMethodName(candidateWithRoleSuffix);
|
|
2500
3005
|
const getterCandidatesWithRoleSuffix = getPrimaryGetterNameCandidates(candidateWithRoleSuffix);
|
|
2501
3006
|
let chosenGetterNameWithRoleSuffix = getterCandidatesWithRoleSuffix.primary;
|
|
@@ -2563,51 +3068,46 @@ Conflicts: getter=${last.getterName}, method=${last.actionName}
|
|
|
2563
3068
|
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".`
|
|
2564
3069
|
);
|
|
2565
3070
|
}
|
|
2566
|
-
|
|
2567
|
-
if (isKeyed) {
|
|
2568
|
-
params.key = keyTypeFromValues;
|
|
2569
|
-
}
|
|
3071
|
+
let parameters = selectorIsParameterized ? [createPomParameterSpec("key", keyTypeFromValues)] : [];
|
|
2570
3072
|
switch (normalizedRole) {
|
|
2571
3073
|
case "input":
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
if (!isKeyed) delete params.key;
|
|
3074
|
+
parameters = setPomParameter(parameters, "text", "string");
|
|
3075
|
+
parameters = setPomParameter(parameters, "annotationText", 'string = ""');
|
|
2575
3076
|
break;
|
|
2576
3077
|
case "select":
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
if (!isKeyed) delete params.key;
|
|
3078
|
+
parameters = setPomParameter(parameters, "value", "string");
|
|
3079
|
+
parameters = setPomParameter(parameters, "annotationText", 'string = ""');
|
|
2580
3080
|
break;
|
|
2581
3081
|
case "vselect":
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
if (!isKeyed) delete params.key;
|
|
3082
|
+
parameters = setPomParameter(parameters, "value", "string");
|
|
3083
|
+
parameters = setPomParameter(parameters, "timeOut", "number = 500");
|
|
3084
|
+
parameters = setPomParameter(parameters, "annotationText", 'string = ""');
|
|
2586
3085
|
break;
|
|
2587
3086
|
case "radio":
|
|
2588
|
-
|
|
3087
|
+
parameters = setPomParameter(parameters, "annotationText", 'string = ""');
|
|
2589
3088
|
break;
|
|
2590
3089
|
}
|
|
2591
|
-
|
|
2592
|
-
params.key = keyTypeFromValues;
|
|
2593
|
-
}
|
|
3090
|
+
const normalizedParameters = selectorIsParameterized ? setPomParameter(parameters, "key", keyTypeFromValues) : removePomParameter(parameters, "key");
|
|
2594
3091
|
if (addHtmlAttribute && !fromExisting) {
|
|
2595
3092
|
upsertAttribute(args.element, testIdAttribute, dataTestId);
|
|
2596
3093
|
}
|
|
2597
3094
|
const childComponentName = args.element.tag;
|
|
2598
3095
|
const dataTestIdEntry = {
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
3096
|
+
selectorValue: entryOverrides.selectorValue ?? createPomStringPattern(
|
|
3097
|
+
getAttributeValueText(dataTestId),
|
|
3098
|
+
dataTestId.kind === "template" ? "parameterized" : "static"
|
|
3099
|
+
),
|
|
3100
|
+
templateLiteral: entryOverrides.templateLiteral,
|
|
3101
|
+
targetPageObjectModelClass: entryOverrides.targetPageObjectModelClass
|
|
2602
3102
|
};
|
|
2603
3103
|
dataTestIdEntry.pom = {
|
|
2604
3104
|
nativeRole: normalizedRole,
|
|
2605
3105
|
methodName,
|
|
2606
3106
|
getterNameOverride,
|
|
2607
|
-
|
|
2608
|
-
|
|
3107
|
+
selector: selectorPattern,
|
|
3108
|
+
alternateSelectors: void 0,
|
|
2609
3109
|
mergeKey: args.pomMergeKey,
|
|
2610
|
-
|
|
3110
|
+
parameters: normalizedParameters,
|
|
2611
3111
|
keyValuesOverride: args.keyValuesOverride ?? null
|
|
2612
3112
|
// emitPrimary defaults to true; special cases (including merge) may set it to false below.
|
|
2613
3113
|
};
|
|
@@ -2638,43 +3138,18 @@ Fix: make the element identifiable (e.g. add id/name/inner text or use a more sp
|
|
|
2638
3138
|
}
|
|
2639
3139
|
};
|
|
2640
3140
|
const getSignatureForGeneratedMethod = () => {
|
|
2641
|
-
|
|
2642
|
-
const isNavigation2 = !!dataTestIdEntry.targetPageObjectModelClass;
|
|
2643
|
-
const needsKey2 = Object.prototype.hasOwnProperty.call(params, "key");
|
|
2644
|
-
const keyType = keyTypeFromValues;
|
|
2645
|
-
if (isNavigation2) {
|
|
2646
|
-
if (needsKey2) {
|
|
2647
|
-
return { params: `key: ${keyType}`, argNames: ["key"] };
|
|
2648
|
-
}
|
|
2649
|
-
return { params: "", argNames: [] };
|
|
2650
|
-
}
|
|
2651
|
-
switch (role) {
|
|
2652
|
-
case "input":
|
|
2653
|
-
return needsKey2 ? { params: `key: ${keyType}, text: string, annotationText: string = ""`, argNames: ["key", "text", "annotationText"] } : { params: 'text: string, annotationText: string = ""', argNames: ["text", "annotationText"] };
|
|
2654
|
-
case "select":
|
|
2655
|
-
return needsKey2 ? { params: `key: ${keyType}, value: string, annotationText: string = ""`, argNames: ["key", "value", "annotationText"] } : { params: 'value: string, annotationText: string = ""', argNames: ["value", "annotationText"] };
|
|
2656
|
-
case "vselect":
|
|
2657
|
-
return needsKey2 ? { params: `key: ${keyType}, value: string, timeOut = 500`, argNames: ["key", "value", "timeOut"] } : { params: "value: string, timeOut = 500", argNames: ["value", "timeOut"] };
|
|
2658
|
-
case "radio":
|
|
2659
|
-
return needsKey2 ? { params: `key: ${keyType}, annotationText: string = ""`, argNames: ["key", "annotationText"] } : { params: 'annotationText: string = ""', argNames: ["annotationText"] };
|
|
2660
|
-
default:
|
|
2661
|
-
if (needsKey2) {
|
|
2662
|
-
return { params: `key: ${keyType}`, argNames: ["key"] };
|
|
2663
|
-
}
|
|
2664
|
-
return { params: "", argNames: [] };
|
|
2665
|
-
}
|
|
3141
|
+
return createPomMethodSignature(normalizedParameters);
|
|
2666
3142
|
};
|
|
2667
3143
|
const registerPrimaryOnce = (pom) => {
|
|
2668
|
-
const
|
|
2669
|
-
const alternates = (pom.alternateFormattedDataTestIds ?? []).slice().sort();
|
|
3144
|
+
const alternates = (pom.alternateSelectors ?? []).slice().sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b)));
|
|
2670
3145
|
const key = JSON.stringify({
|
|
2671
3146
|
kind: "primary",
|
|
2672
3147
|
role: pom.nativeRole,
|
|
2673
3148
|
methodName: pom.methodName,
|
|
2674
3149
|
getterNameOverride: pom.getterNameOverride ?? null,
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
3150
|
+
selector: pom.selector,
|
|
3151
|
+
alternateSelectors: alternates.length ? alternates : void 0,
|
|
3152
|
+
parameters: pom.parameters,
|
|
2678
3153
|
target: dataTestIdEntry.targetPageObjectModelClass ?? null,
|
|
2679
3154
|
emitPrimary: pom.emitPrimary ?? true
|
|
2680
3155
|
});
|
|
@@ -2688,8 +3163,7 @@ Fix: make the element identifiable (e.g. add id/name/inner text or use a more sp
|
|
|
2688
3163
|
}
|
|
2689
3164
|
};
|
|
2690
3165
|
const addExtraClickMethod = (spec) => {
|
|
2691
|
-
const
|
|
2692
|
-
const key = JSON.stringify({ kind: spec.kind, selector: spec.selector, keyLiteral: spec.keyLiteral ?? null, params: stableParams });
|
|
3166
|
+
const key = JSON.stringify({ kind: spec.kind, selector: spec.selector, keyLiteral: spec.keyLiteral ?? null, parameters: spec.parameters });
|
|
2693
3167
|
const seen = args.generatedMethodContentByComponent.get(args.parentComponentName) ?? /* @__PURE__ */ new Set();
|
|
2694
3168
|
if (!args.generatedMethodContentByComponent.has(args.parentComponentName)) {
|
|
2695
3169
|
args.generatedMethodContentByComponent.set(args.parentComponentName, seen);
|
|
@@ -2712,7 +3186,7 @@ Fix: make the element identifiable (e.g. add id/name/inner text or use a more sp
|
|
|
2712
3186
|
if (prev === null) {
|
|
2713
3187
|
return;
|
|
2714
3188
|
}
|
|
2715
|
-
if (signature === null || prev
|
|
3189
|
+
if (signature === null || !pomMethodSignatureEquals(prev, signature)) {
|
|
2716
3190
|
args.dependencies.generatedMethods.set(name, null);
|
|
2717
3191
|
}
|
|
2718
3192
|
};
|
|
@@ -2728,23 +3202,10 @@ Fix: make the element identifiable (e.g. add id/name/inner text or use a more sp
|
|
|
2728
3202
|
return candidate;
|
|
2729
3203
|
};
|
|
2730
3204
|
const tryGetDirectiveExpressionAst = (dir) => {
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
}
|
|
2735
|
-
if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {
|
|
2736
|
-
const simple = exp;
|
|
2737
|
-
const ast = simple.ast;
|
|
2738
|
-
if (ast && "type" in ast) {
|
|
2739
|
-
return ast;
|
|
2740
|
-
}
|
|
2741
|
-
}
|
|
2742
|
-
try {
|
|
2743
|
-
const raw = args.context ? stringifyExpression(exp) : exp.loc.source;
|
|
2744
|
-
return parseExpression(raw, { plugins: ["typescript"] });
|
|
2745
|
-
} catch {
|
|
2746
|
-
return null;
|
|
2747
|
-
}
|
|
3205
|
+
return tryGetDirectiveBabelAst(dir, {
|
|
3206
|
+
preferredViews: args.context ? ["compiled", "loc"] : ["loc", "compiled"],
|
|
3207
|
+
plugins: ["typescript"]
|
|
3208
|
+
});
|
|
2748
3209
|
};
|
|
2749
3210
|
const tryGetStaticStringFromBabel = (node) => {
|
|
2750
3211
|
if (!node) {
|
|
@@ -2841,17 +3302,17 @@ Fix: make the element identifiable (e.g. add id/name/inner text or use a more sp
|
|
|
2841
3302
|
name: generatedName2,
|
|
2842
3303
|
selector: {
|
|
2843
3304
|
kind: "withinTestIdByLabel",
|
|
2844
|
-
|
|
2845
|
-
|
|
3305
|
+
rootTestId: createPomStringPattern(wrapperTestId, selectorPatternKind),
|
|
3306
|
+
label: createPomStringPattern(label, "static"),
|
|
2846
3307
|
exact: true
|
|
2847
3308
|
},
|
|
2848
|
-
|
|
3309
|
+
parameters: [createPomParameterSpec("annotationText", `string = ""`)]
|
|
2849
3310
|
});
|
|
2850
3311
|
if (added2) {
|
|
2851
|
-
registerGeneratedMethodSignature(generatedName2,
|
|
3312
|
+
registerGeneratedMethodSignature(generatedName2, createPomMethodSignature([createPomParameterSpec("annotationText", `string = ""`)]));
|
|
2852
3313
|
}
|
|
2853
3314
|
}
|
|
2854
|
-
return;
|
|
3315
|
+
return { selectorValue: dataTestId, runtimeValue: runtimeDataTestId, fromExisting };
|
|
2855
3316
|
}
|
|
2856
3317
|
const generatedName = ensureUniqueGeneratedName(`select${upperFirst(methodName || "Radio")}`);
|
|
2857
3318
|
if (dataTestIdEntry.pom) {
|
|
@@ -2863,19 +3324,25 @@ Fix: make the element identifiable (e.g. add id/name/inner text or use a more sp
|
|
|
2863
3324
|
name: generatedName,
|
|
2864
3325
|
selector: {
|
|
2865
3326
|
kind: "withinTestIdByLabel",
|
|
2866
|
-
|
|
2867
|
-
|
|
3327
|
+
rootTestId: createPomStringPattern(wrapperTestId, selectorPatternKind),
|
|
3328
|
+
label: createPomStringPattern("${value}", "parameterized"),
|
|
2868
3329
|
exact: true
|
|
2869
3330
|
},
|
|
2870
|
-
|
|
3331
|
+
parameters: [
|
|
3332
|
+
createPomParameterSpec("value", "string"),
|
|
3333
|
+
createPomParameterSpec("annotationText", `string = ""`)
|
|
3334
|
+
]
|
|
2871
3335
|
});
|
|
2872
3336
|
if (added) {
|
|
2873
|
-
registerGeneratedMethodSignature(generatedName,
|
|
3337
|
+
registerGeneratedMethodSignature(generatedName, createPomMethodSignature([
|
|
3338
|
+
createPomParameterSpec("value", "string"),
|
|
3339
|
+
createPomParameterSpec("annotationText", `string = ""`)
|
|
3340
|
+
]));
|
|
2874
3341
|
}
|
|
2875
|
-
return;
|
|
3342
|
+
return { selectorValue: dataTestId, runtimeValue: runtimeDataTestId, fromExisting };
|
|
2876
3343
|
}
|
|
2877
3344
|
const staticKeyValues = args.keyValuesOverride ?? null;
|
|
2878
|
-
const needsKey =
|
|
3345
|
+
const needsKey = hasPomParameter(normalizedParameters, "key") && selectorIsParameterized;
|
|
2879
3346
|
const isNavigation = !!dataTestIdEntry.targetPageObjectModelClass;
|
|
2880
3347
|
if (staticKeyValues && staticKeyValues.length > 0 && needsKey && !isNavigation && normalizedRole !== "input" && normalizedRole !== "select" && normalizedRole !== "vselect" && normalizedRole !== "radio") {
|
|
2881
3348
|
if (dataTestIdEntry.pom) {
|
|
@@ -2894,19 +3361,19 @@ Fix: make the element identifiable (e.g. add id/name/inner text or use a more sp
|
|
|
2894
3361
|
name: generatedName,
|
|
2895
3362
|
selector: {
|
|
2896
3363
|
kind: "testId",
|
|
2897
|
-
|
|
3364
|
+
testId: selectorPattern
|
|
2898
3365
|
},
|
|
2899
3366
|
keyLiteral: rawValue,
|
|
2900
|
-
|
|
3367
|
+
parameters: [createPomParameterSpec("wait", "boolean = true"), createPomParameterSpec("annotationText", 'string = ""')]
|
|
2901
3368
|
});
|
|
2902
3369
|
if (added) {
|
|
2903
|
-
registerGeneratedMethodSignature(generatedName,
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
3370
|
+
registerGeneratedMethodSignature(generatedName, createPomMethodSignature([
|
|
3371
|
+
createPomParameterSpec("wait", "boolean = true"),
|
|
3372
|
+
createPomParameterSpec("annotationText", 'string = ""')
|
|
3373
|
+
]));
|
|
2907
3374
|
}
|
|
2908
3375
|
}
|
|
2909
|
-
return;
|
|
3376
|
+
return { selectorValue: dataTestId, runtimeValue: runtimeDataTestId, fromExisting };
|
|
2910
3377
|
}
|
|
2911
3378
|
if (dataTestIdEntry.pom) {
|
|
2912
3379
|
if (dataTestIdEntry.pom.emitPrimary !== false) {
|
|
@@ -2920,6 +3387,7 @@ Fix: make the element identifiable (e.g. add id/name/inner text or use a more sp
|
|
|
2920
3387
|
const generatedName = getGeneratedMethodName();
|
|
2921
3388
|
registerGeneratedMethodSignature(generatedName, signature);
|
|
2922
3389
|
}
|
|
3390
|
+
return { selectorValue: dataTestId, runtimeValue: runtimeDataTestId, fromExisting };
|
|
2923
3391
|
}
|
|
2924
3392
|
function safeRealpath(value) {
|
|
2925
3393
|
try {
|
|
@@ -3892,127 +4360,20 @@ async function parseRouterFileFromCwd(routerEntryPath, options = {}) {
|
|
|
3892
4360
|
debugLog("closing internal vite server");
|
|
3893
4361
|
await server.close();
|
|
3894
4362
|
}
|
|
3895
|
-
} finally {
|
|
3896
|
-
restoreDomShim();
|
|
3897
|
-
}
|
|
3898
|
-
});
|
|
3899
|
-
}
|
|
3900
|
-
const GENERATED_GITATTRIBUTES_BLOCK_START = "# BEGIN vue-pom-generator generated files";
|
|
3901
|
-
const GENERATED_GITATTRIBUTES_BLOCK_END = "# END vue-pom-generator generated files";
|
|
3902
|
-
const VUE_POM_GENERATOR_ERROR_PREFIX = "[vue-pom-generator]";
|
|
3903
|
-
class VuePomGeneratorError extends Error {
|
|
3904
|
-
constructor(message) {
|
|
3905
|
-
const normalized = message.startsWith(VUE_POM_GENERATOR_ERROR_PREFIX) ? message : `${VUE_POM_GENERATOR_ERROR_PREFIX} ${message}`;
|
|
3906
|
-
super(normalized);
|
|
3907
|
-
this.name = "VuePomGeneratorError";
|
|
3908
|
-
}
|
|
3909
|
-
}
|
|
3910
|
-
function splitParameterList(parameters) {
|
|
3911
|
-
const parts = [];
|
|
3912
|
-
let current = "";
|
|
3913
|
-
let braceDepth = 0;
|
|
3914
|
-
let bracketDepth = 0;
|
|
3915
|
-
let parenDepth = 0;
|
|
3916
|
-
let angleDepth = 0;
|
|
3917
|
-
let inSingleQuote = false;
|
|
3918
|
-
let inDoubleQuote = false;
|
|
3919
|
-
let inTemplateString = false;
|
|
3920
|
-
for (let index = 0; index < parameters.length; index += 1) {
|
|
3921
|
-
const char = parameters[index];
|
|
3922
|
-
const previous = index > 0 ? parameters[index - 1] : "";
|
|
3923
|
-
if (char === "'" && !inDoubleQuote && !inTemplateString && previous !== "\\") {
|
|
3924
|
-
inSingleQuote = !inSingleQuote;
|
|
3925
|
-
current += char;
|
|
3926
|
-
continue;
|
|
3927
|
-
}
|
|
3928
|
-
if (char === '"' && !inSingleQuote && !inTemplateString && previous !== "\\") {
|
|
3929
|
-
inDoubleQuote = !inDoubleQuote;
|
|
3930
|
-
current += char;
|
|
3931
|
-
continue;
|
|
3932
|
-
}
|
|
3933
|
-
if (char === "`" && !inSingleQuote && !inDoubleQuote && previous !== "\\") {
|
|
3934
|
-
inTemplateString = !inTemplateString;
|
|
3935
|
-
current += char;
|
|
3936
|
-
continue;
|
|
3937
|
-
}
|
|
3938
|
-
if (inSingleQuote || inDoubleQuote || inTemplateString) {
|
|
3939
|
-
current += char;
|
|
3940
|
-
continue;
|
|
3941
|
-
}
|
|
3942
|
-
switch (char) {
|
|
3943
|
-
case "{":
|
|
3944
|
-
braceDepth += 1;
|
|
3945
|
-
break;
|
|
3946
|
-
case "}":
|
|
3947
|
-
braceDepth -= 1;
|
|
3948
|
-
break;
|
|
3949
|
-
case "[":
|
|
3950
|
-
bracketDepth += 1;
|
|
3951
|
-
break;
|
|
3952
|
-
case "]":
|
|
3953
|
-
bracketDepth -= 1;
|
|
3954
|
-
break;
|
|
3955
|
-
case "(":
|
|
3956
|
-
parenDepth += 1;
|
|
3957
|
-
break;
|
|
3958
|
-
case ")":
|
|
3959
|
-
parenDepth -= 1;
|
|
3960
|
-
break;
|
|
3961
|
-
case "<":
|
|
3962
|
-
angleDepth += 1;
|
|
3963
|
-
break;
|
|
3964
|
-
case ">":
|
|
3965
|
-
angleDepth -= 1;
|
|
3966
|
-
break;
|
|
3967
|
-
case ",":
|
|
3968
|
-
if (braceDepth === 0 && bracketDepth === 0 && parenDepth === 0 && angleDepth === 0) {
|
|
3969
|
-
const trimmed2 = current.trim();
|
|
3970
|
-
if (trimmed2) {
|
|
3971
|
-
parts.push(trimmed2);
|
|
3972
|
-
}
|
|
3973
|
-
current = "";
|
|
3974
|
-
continue;
|
|
3975
|
-
}
|
|
3976
|
-
break;
|
|
3977
|
-
}
|
|
3978
|
-
current += char;
|
|
3979
|
-
}
|
|
3980
|
-
const trimmed = current.trim();
|
|
3981
|
-
if (trimmed) {
|
|
3982
|
-
parts.push(trimmed);
|
|
3983
|
-
}
|
|
3984
|
-
return parts;
|
|
3985
|
-
}
|
|
3986
|
-
function parseParameterSignature(parameter) {
|
|
3987
|
-
const colonIndex = parameter.indexOf(":");
|
|
3988
|
-
if (colonIndex < 0) {
|
|
3989
|
-
return { name: parameter.trim() };
|
|
3990
|
-
}
|
|
3991
|
-
const rawName = parameter.slice(0, colonIndex).trim();
|
|
3992
|
-
const hasQuestionToken = rawName.endsWith("?");
|
|
3993
|
-
const name = hasQuestionToken ? rawName.slice(0, -1).trim() : rawName;
|
|
3994
|
-
const remainder = parameter.slice(colonIndex + 1).trim();
|
|
3995
|
-
const initializerIndex = remainder.lastIndexOf("=");
|
|
3996
|
-
if (initializerIndex < 0) {
|
|
3997
|
-
return {
|
|
3998
|
-
name,
|
|
3999
|
-
hasQuestionToken,
|
|
4000
|
-
type: remainder || void 0
|
|
4001
|
-
};
|
|
4002
|
-
}
|
|
4003
|
-
return {
|
|
4004
|
-
name,
|
|
4005
|
-
hasQuestionToken,
|
|
4006
|
-
type: remainder.slice(0, initializerIndex).trim() || void 0,
|
|
4007
|
-
initializer: remainder.slice(initializerIndex + 1).trim() || void 0
|
|
4008
|
-
};
|
|
4363
|
+
} finally {
|
|
4364
|
+
restoreDomShim();
|
|
4365
|
+
}
|
|
4366
|
+
});
|
|
4009
4367
|
}
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4368
|
+
const GENERATED_GITATTRIBUTES_BLOCK_START = "# BEGIN vue-pom-generator generated files";
|
|
4369
|
+
const GENERATED_GITATTRIBUTES_BLOCK_END = "# END vue-pom-generator generated files";
|
|
4370
|
+
const VUE_POM_GENERATOR_ERROR_PREFIX = "[vue-pom-generator]";
|
|
4371
|
+
class VuePomGeneratorError extends Error {
|
|
4372
|
+
constructor(message) {
|
|
4373
|
+
const normalized = message.startsWith(VUE_POM_GENERATOR_ERROR_PREFIX) ? message : `${VUE_POM_GENERATOR_ERROR_PREFIX} ${message}`;
|
|
4374
|
+
super(normalized);
|
|
4375
|
+
this.name = "VuePomGeneratorError";
|
|
4014
4376
|
}
|
|
4015
|
-
return splitParameterList(trimmed).map(parseParameterSignature);
|
|
4016
4377
|
}
|
|
4017
4378
|
function toPosixRelativePath(fromDir, toFile) {
|
|
4018
4379
|
let rel = path.relative(fromDir, toFile).replace(/\\/g, "/");
|
|
@@ -4033,6 +4394,21 @@ function resolveRouterEntry(projectRoot, routerEntry) {
|
|
|
4033
4394
|
const root = projectRoot ?? process.cwd();
|
|
4034
4395
|
return path.isAbsolute(routerEntry) ? routerEntry : path.resolve(root, routerEntry);
|
|
4035
4396
|
}
|
|
4397
|
+
function createMissingCustomPomDirectoryError(configuredDir, resolvedDir) {
|
|
4398
|
+
return new VuePomGeneratorError(
|
|
4399
|
+
`Custom POM directory "${configuredDir}" does not exist.
|
|
4400
|
+
Resolved path: ${resolvedDir}
|
|
4401
|
+
Create the directory, point generation.playwright.customPoms.dir at the correct location, or remove the customPoms configuration.`
|
|
4402
|
+
);
|
|
4403
|
+
}
|
|
4404
|
+
function createMissingCustomPomAttachmentClassError(missingClassNames, configuredDir) {
|
|
4405
|
+
const renderedClassNames = missingClassNames.map((name) => `"${name}"`).join(", ");
|
|
4406
|
+
return new VuePomGeneratorError(
|
|
4407
|
+
`Custom POM attachments reference missing helper classes: ${renderedClassNames}.
|
|
4408
|
+
Expected matching helper files/exports under "${configuredDir}".
|
|
4409
|
+
Add the missing helper classes or remove the corresponding generation.playwright.customPoms.attachments entries.`
|
|
4410
|
+
);
|
|
4411
|
+
}
|
|
4036
4412
|
function createCustomPomImportCollisionError(exportName, requested) {
|
|
4037
4413
|
return new VuePomGeneratorError(
|
|
4038
4414
|
`Custom POM import name collision detected for "${exportName}".
|
|
@@ -4171,41 +4547,31 @@ function generateGoToSelfMethod(componentName) {
|
|
|
4171
4547
|
})
|
|
4172
4548
|
];
|
|
4173
4549
|
}
|
|
4174
|
-
function
|
|
4175
|
-
|
|
4176
|
-
return "";
|
|
4177
|
-
const preferredOrder = ["key", "value", "text", "timeOut", "annotationText", "wait"];
|
|
4178
|
-
const entries = Object.entries(params);
|
|
4179
|
-
if (!entries.length)
|
|
4180
|
-
return "";
|
|
4181
|
-
const score = (name) => {
|
|
4182
|
-
const idx = preferredOrder.indexOf(name);
|
|
4183
|
-
return idx < 0 ? 999 : idx;
|
|
4184
|
-
};
|
|
4185
|
-
return entries.slice().sort((a, b) => score(a[0]) - score(b[0]) || a[0].localeCompare(b[0])).map(([name, typeExpr]) => `${name}: ${typeExpr}`).join(", ");
|
|
4550
|
+
function getSelectorPatterns(selector) {
|
|
4551
|
+
return selector.kind === "testId" ? [selector.testId] : [selector.rootTestId, selector.label];
|
|
4186
4552
|
}
|
|
4187
|
-
function generateExtraClickMethodMembers(spec) {
|
|
4553
|
+
function generateExtraClickMethodMembers(spec, componentName) {
|
|
4188
4554
|
if (spec.kind !== "click") {
|
|
4189
4555
|
return [];
|
|
4190
4556
|
}
|
|
4191
|
-
const
|
|
4192
|
-
const
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4557
|
+
const selectorPatterns = getSelectorPatterns(spec.selector);
|
|
4558
|
+
const signatureSpecs = orderPomPatternParameters(
|
|
4559
|
+
spec.parameters,
|
|
4560
|
+
selectorPatterns,
|
|
4561
|
+
{ omit: spec.keyLiteral !== void 0 ? ["key"] : [] }
|
|
4562
|
+
);
|
|
4563
|
+
const parameters = toTypeScriptPomParameterStructures(signatureSpecs);
|
|
4564
|
+
const hasAnnotationText = signatureSpecs.some((param) => param.name === "annotationText");
|
|
4565
|
+
const hasWait = signatureSpecs.some((param) => param.name === "wait");
|
|
4196
4566
|
const annotationArg = hasAnnotationText ? "annotationText" : '""';
|
|
4197
4567
|
const waitArg = hasWait ? "wait" : "true";
|
|
4568
|
+
const locatorDescription = JSON.stringify(buildPomLocatorDescription({
|
|
4569
|
+
componentName,
|
|
4570
|
+
methodName: stripPomActionPrefix(spec.name),
|
|
4571
|
+
nativeRole: "button"
|
|
4572
|
+
}));
|
|
4198
4573
|
if (spec.selector.kind === "testId") {
|
|
4199
|
-
const
|
|
4200
|
-
const testIdExpr = needsTemplate ? `\`${spec.selector.formattedDataTestId}\`` : JSON.stringify(spec.selector.formattedDataTestId);
|
|
4201
|
-
const clickArgs = [];
|
|
4202
|
-
clickArgs.push(needsTemplate ? "testId" : testIdExpr);
|
|
4203
|
-
if (hasAnnotationText || hasWait) {
|
|
4204
|
-
clickArgs.push(annotationArg);
|
|
4205
|
-
}
|
|
4206
|
-
if (hasWait) {
|
|
4207
|
-
clickArgs.push(waitArg);
|
|
4208
|
-
}
|
|
4574
|
+
const testIdBinding = bindTypeScriptPomPattern(spec.selector.testId, "testId");
|
|
4209
4575
|
return [
|
|
4210
4576
|
createClassMethod({
|
|
4211
4577
|
name: spec.name,
|
|
@@ -4215,20 +4581,16 @@ function generateExtraClickMethodMembers(spec) {
|
|
|
4215
4581
|
if (spec.keyLiteral !== void 0) {
|
|
4216
4582
|
writer.writeLine(`const key = ${JSON.stringify(spec.keyLiteral)};`);
|
|
4217
4583
|
}
|
|
4218
|
-
|
|
4219
|
-
writer.writeLine(
|
|
4584
|
+
for (const statement of testIdBinding.setupStatements) {
|
|
4585
|
+
writer.writeLine(statement);
|
|
4220
4586
|
}
|
|
4221
|
-
writer.writeLine(`await this.clickByTestId(${
|
|
4587
|
+
writer.writeLine(`await this.clickByTestId(${testIdBinding.expression}, ${annotationArg}, ${waitArg}, ${locatorDescription});`);
|
|
4222
4588
|
}
|
|
4223
4589
|
})
|
|
4224
4590
|
];
|
|
4225
4591
|
}
|
|
4226
|
-
const
|
|
4227
|
-
const
|
|
4228
|
-
const rootExpr = rootNeedsTemplate ? `\`${spec.selector.rootFormattedDataTestId}\`` : JSON.stringify(spec.selector.rootFormattedDataTestId);
|
|
4229
|
-
const labelExpr = labelNeedsTemplate ? `\`${spec.selector.formattedLabel}\`` : JSON.stringify(spec.selector.formattedLabel);
|
|
4230
|
-
const rootArg = rootNeedsTemplate ? "rootTestId" : rootExpr;
|
|
4231
|
-
const labelArg = labelNeedsTemplate ? "label" : labelExpr;
|
|
4592
|
+
const rootBinding = bindTypeScriptPomPattern(spec.selector.rootTestId, "rootTestId");
|
|
4593
|
+
const labelBinding = bindTypeScriptPomPattern(spec.selector.label, "label");
|
|
4232
4594
|
return [
|
|
4233
4595
|
createClassMethod({
|
|
4234
4596
|
name: spec.name,
|
|
@@ -4238,45 +4600,45 @@ function generateExtraClickMethodMembers(spec) {
|
|
|
4238
4600
|
if (spec.keyLiteral !== void 0) {
|
|
4239
4601
|
writer.writeLine(`const key = ${JSON.stringify(spec.keyLiteral)};`);
|
|
4240
4602
|
}
|
|
4241
|
-
|
|
4242
|
-
writer.writeLine(
|
|
4603
|
+
for (const statement of rootBinding.setupStatements) {
|
|
4604
|
+
writer.writeLine(statement);
|
|
4243
4605
|
}
|
|
4244
|
-
|
|
4245
|
-
writer.writeLine(
|
|
4606
|
+
for (const statement of labelBinding.setupStatements) {
|
|
4607
|
+
writer.writeLine(statement);
|
|
4246
4608
|
}
|
|
4247
|
-
writer.writeLine(`await this.clickWithinTestIdByLabel(${
|
|
4609
|
+
writer.writeLine(`await this.clickWithinTestIdByLabel(${rootBinding.expression}, ${labelBinding.expression}, ${annotationArg}, ${waitArg}, { description: ${locatorDescription} });`);
|
|
4248
4610
|
}
|
|
4249
4611
|
})
|
|
4250
4612
|
];
|
|
4251
4613
|
}
|
|
4252
|
-
function generateMethodMembersFromPom(primary, targetPageObjectModelClass) {
|
|
4614
|
+
function generateMethodMembersFromPom(componentName, primary, targetPageObjectModelClass) {
|
|
4253
4615
|
if (primary.emitPrimary === false) {
|
|
4254
4616
|
return [];
|
|
4255
4617
|
}
|
|
4256
4618
|
return generateViewObjectModelMembers(
|
|
4619
|
+
componentName,
|
|
4257
4620
|
targetPageObjectModelClass,
|
|
4258
4621
|
primary.methodName,
|
|
4259
4622
|
primary.nativeRole,
|
|
4260
|
-
primary.
|
|
4261
|
-
primary.
|
|
4623
|
+
primary.selector,
|
|
4624
|
+
primary.alternateSelectors,
|
|
4262
4625
|
primary.getterNameOverride,
|
|
4263
|
-
primary.
|
|
4626
|
+
primary.parameters
|
|
4264
4627
|
);
|
|
4265
4628
|
}
|
|
4266
|
-
function generateMethodsContentForDependencies(dependencies) {
|
|
4629
|
+
function generateMethodsContentForDependencies(componentName, dependencies) {
|
|
4267
4630
|
const entries = Array.from(dependencies.dataTestIdSet ?? []);
|
|
4268
4631
|
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));
|
|
4269
4632
|
const seenPrimaryKeys = /* @__PURE__ */ new Set();
|
|
4270
4633
|
const primarySpecs = primarySpecsAll.filter(({ pom, target }) => {
|
|
4271
|
-
const
|
|
4272
|
-
const alternates = (pom.alternateFormattedDataTestIds ?? []).slice().sort();
|
|
4634
|
+
const alternates = (pom.alternateSelectors ?? []).slice().sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b)));
|
|
4273
4635
|
const key = JSON.stringify({
|
|
4274
4636
|
role: pom.nativeRole,
|
|
4275
4637
|
methodName: pom.methodName,
|
|
4276
4638
|
getterNameOverride: pom.getterNameOverride ?? null,
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4639
|
+
selector: pom.selector,
|
|
4640
|
+
alternateSelectors: alternates.length ? alternates : void 0,
|
|
4641
|
+
parameters: pom.parameters,
|
|
4280
4642
|
target: target ?? null,
|
|
4281
4643
|
emitPrimary: pom.emitPrimary ?? true
|
|
4282
4644
|
});
|
|
@@ -4289,10 +4651,10 @@ function generateMethodsContentForDependencies(dependencies) {
|
|
|
4289
4651
|
const extras = (dependencies.pomExtraMethods ?? []).slice().sort((a, b) => a.name.localeCompare(b.name));
|
|
4290
4652
|
const members = [];
|
|
4291
4653
|
for (const { pom, target } of primarySpecs) {
|
|
4292
|
-
members.push(...generateMethodMembersFromPom(pom, target));
|
|
4654
|
+
members.push(...generateMethodMembersFromPom(componentName, pom, target));
|
|
4293
4655
|
}
|
|
4294
4656
|
for (const extra of extras) {
|
|
4295
|
-
members.push(...generateExtraClickMethodMembers(extra));
|
|
4657
|
+
members.push(...generateExtraClickMethodMembers(extra, componentName));
|
|
4296
4658
|
}
|
|
4297
4659
|
return members;
|
|
4298
4660
|
}
|
|
@@ -4303,6 +4665,7 @@ async function generateFiles(componentHierarchyMap, vueFilesPathMap, basePageCla
|
|
|
4303
4665
|
customPomAttachments = [],
|
|
4304
4666
|
projectRoot,
|
|
4305
4667
|
customPomDir,
|
|
4668
|
+
requireCustomPomDir,
|
|
4306
4669
|
customPomImportAliases,
|
|
4307
4670
|
customPomImportNameCollisionBehavior = "error",
|
|
4308
4671
|
testIdAttribute,
|
|
@@ -4335,6 +4698,7 @@ async function generateFiles(componentHierarchyMap, vueFilesPathMap, basePageCla
|
|
|
4335
4698
|
customPomAttachments,
|
|
4336
4699
|
projectRoot,
|
|
4337
4700
|
customPomDir,
|
|
4701
|
+
requireCustomPomDir,
|
|
4338
4702
|
customPomImportAliases,
|
|
4339
4703
|
customPomImportNameCollisionBehavior,
|
|
4340
4704
|
testIdAttribute,
|
|
@@ -4344,6 +4708,7 @@ async function generateFiles(componentHierarchyMap, vueFilesPathMap, basePageCla
|
|
|
4344
4708
|
customPomAttachments,
|
|
4345
4709
|
projectRoot,
|
|
4346
4710
|
customPomDir,
|
|
4711
|
+
requireCustomPomDir,
|
|
4347
4712
|
customPomImportAliases,
|
|
4348
4713
|
customPomImportNameCollisionBehavior,
|
|
4349
4714
|
testIdAttribute,
|
|
@@ -4399,9 +4764,15 @@ async function generateSplitTypeScriptFiles(componentHierarchyMap, vueFilesPathM
|
|
|
4399
4764
|
}
|
|
4400
4765
|
const customPomImportResolution = resolveCustomPomImportResolution(generatedClassNames, projectRoot, {
|
|
4401
4766
|
customPomDir: options.customPomDir,
|
|
4767
|
+
requireCustomPomDir: options.requireCustomPomDir,
|
|
4402
4768
|
customPomImportAliases: options.customPomImportAliases,
|
|
4403
4769
|
customPomImportNameCollisionBehavior: options.customPomImportNameCollisionBehavior
|
|
4404
4770
|
});
|
|
4771
|
+
assertCustomPomAttachmentsResolved(
|
|
4772
|
+
options.customPomAttachments ?? [],
|
|
4773
|
+
customPomImportResolution.classIdentifierMap,
|
|
4774
|
+
options.customPomDir ?? "tests/playwright/pom/custom"
|
|
4775
|
+
);
|
|
4405
4776
|
const runtimeBasePagePath = path.join(base, "_pom-runtime", "class-generation", "base-page.ts");
|
|
4406
4777
|
const files = [];
|
|
4407
4778
|
for (const [name, deps] of entries) {
|
|
@@ -4560,21 +4931,8 @@ function buildGeneratedGitAttributesFiles(generatedFilePaths) {
|
|
|
4560
4931
|
return { filePath, content };
|
|
4561
4932
|
});
|
|
4562
4933
|
}
|
|
4563
|
-
function
|
|
4564
|
-
const
|
|
4565
|
-
if (!needsInterpolation) {
|
|
4566
|
-
return JSON.stringify(formattedDataTestId);
|
|
4567
|
-
}
|
|
4568
|
-
const inner = formattedDataTestId.replace(/\$\{/g, "{");
|
|
4569
|
-
const quoted = JSON.stringify(inner);
|
|
4570
|
-
return `$${quoted}`;
|
|
4571
|
-
}
|
|
4572
|
-
function toCSharpParam(paramTypeExpr) {
|
|
4573
|
-
const trimmed = (paramTypeExpr ?? "").trim();
|
|
4574
|
-
const eqIdx = trimmed.indexOf("=");
|
|
4575
|
-
const left = eqIdx >= 0 ? trimmed.slice(0, eqIdx).trim() : trimmed;
|
|
4576
|
-
const right = eqIdx >= 0 ? trimmed.slice(eqIdx + 1).trim() : void 0;
|
|
4577
|
-
const typePart = left.includes("|") ? "string" : left;
|
|
4934
|
+
function toCSharpParam(param) {
|
|
4935
|
+
const typePart = param.type?.includes("|") ? "string" : param.type ?? "string";
|
|
4578
4936
|
let type = "string";
|
|
4579
4937
|
if (/(?:^|\s)boolean(?:\s|$)/.test(typePart))
|
|
4580
4938
|
type = "bool";
|
|
@@ -4582,19 +4940,17 @@ function toCSharpParam(paramTypeExpr) {
|
|
|
4582
4940
|
type = "string";
|
|
4583
4941
|
else if (/(?:^|\s)number(?:\s|$)/.test(typePart))
|
|
4584
4942
|
type = "int";
|
|
4585
|
-
else if (/\d+/.test(typePart) && typePart === "")
|
|
4586
|
-
type = "int";
|
|
4587
4943
|
else if (/\btimeOut\b/i.test(typePart))
|
|
4588
4944
|
type = "int";
|
|
4589
4945
|
let defaultExpr;
|
|
4590
|
-
if (
|
|
4946
|
+
if (param.initializer !== void 0) {
|
|
4591
4947
|
if (type === "bool") {
|
|
4592
|
-
defaultExpr =
|
|
4948
|
+
defaultExpr = param.initializer.includes("true") ? "true" : param.initializer.includes("false") ? "false" : void 0;
|
|
4593
4949
|
} else if (type === "int") {
|
|
4594
|
-
const m =
|
|
4950
|
+
const m = param.initializer.match(/\d+/);
|
|
4595
4951
|
defaultExpr = m ? m[0] : void 0;
|
|
4596
4952
|
} else {
|
|
4597
|
-
if (
|
|
4953
|
+
if (param.initializer === '""' || param.initializer === "''") {
|
|
4598
4954
|
defaultExpr = '""';
|
|
4599
4955
|
}
|
|
4600
4956
|
}
|
|
@@ -4602,17 +4958,15 @@ function toCSharpParam(paramTypeExpr) {
|
|
|
4602
4958
|
return { type, defaultExpr };
|
|
4603
4959
|
}
|
|
4604
4960
|
function formatCSharpParams(params) {
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
const entries = Object.entries(params);
|
|
4608
|
-
if (!entries.length)
|
|
4961
|
+
const normalizedParams = normalizePomParameters(params);
|
|
4962
|
+
if (!normalizedParams.length)
|
|
4609
4963
|
return { signature: "", argNames: [] };
|
|
4610
4964
|
const signatureParts = [];
|
|
4611
4965
|
const argNames = [];
|
|
4612
|
-
for (const
|
|
4613
|
-
const { type, defaultExpr } = toCSharpParam(
|
|
4614
|
-
argNames.push(name);
|
|
4615
|
-
signatureParts.push(defaultExpr !== void 0 ? `${type} ${name} = ${defaultExpr}` : `${type} ${name}`);
|
|
4966
|
+
for (const param of normalizedParams) {
|
|
4967
|
+
const { type, defaultExpr } = toCSharpParam(param);
|
|
4968
|
+
argNames.push(param.name);
|
|
4969
|
+
signatureParts.push(defaultExpr !== void 0 ? `${type} ${param.name} = ${defaultExpr}` : `${type} ${param.name}`);
|
|
4616
4970
|
}
|
|
4617
4971
|
return { signature: signatureParts.join(", "), argNames };
|
|
4618
4972
|
}
|
|
@@ -4703,23 +5057,13 @@ function generateAggregatedCSharpFiles(componentHierarchyMap, outDir, options =
|
|
|
4703
5057
|
const baseMethodName = upperFirst(pom.methodName);
|
|
4704
5058
|
const baseGetterName = upperFirst(pom.getterNameOverride ?? pom.methodName);
|
|
4705
5059
|
const locatorName = baseGetterName.endsWith(roleSuffix) ? baseGetterName : `${baseGetterName}${roleSuffix}`;
|
|
4706
|
-
const
|
|
4707
|
-
const
|
|
4708
|
-
const
|
|
4709
|
-
const augmentedParams = { ...pom.params };
|
|
4710
|
-
for (const v of templateVars) {
|
|
4711
|
-
if (!Object.prototype.hasOwnProperty.call(augmentedParams, v)) {
|
|
4712
|
-
augmentedParams[v] = "string";
|
|
4713
|
-
}
|
|
4714
|
-
}
|
|
4715
|
-
const orderedParams = Object.fromEntries([
|
|
4716
|
-
...templateVars.map((v) => [v, augmentedParams[v]]),
|
|
4717
|
-
...Object.entries(augmentedParams).filter(([k]) => !templateVars.includes(k))
|
|
4718
|
-
]);
|
|
5060
|
+
const selectorIsParameterized = isParameterizedPomPattern(pom.selector.patternKind);
|
|
5061
|
+
const testIdExpr = toCSharpPomPatternExpression(pom.selector);
|
|
5062
|
+
const orderedParams = orderPomPatternParameters(pom.parameters, [pom.selector]);
|
|
4719
5063
|
const { signature, argNames } = formatCSharpParams(orderedParams);
|
|
4720
5064
|
const args = argNames.join(", ");
|
|
4721
|
-
const allTestIds =
|
|
4722
|
-
if (
|
|
5065
|
+
const allTestIds = uniquePomStringPatterns(pom.selector, pom.alternateSelectors);
|
|
5066
|
+
if (selectorIsParameterized) {
|
|
4723
5067
|
chunks.push(` public ILocator ${locatorName}(${signature}) => LocatorByTestId(${testIdExpr});`);
|
|
4724
5068
|
} else {
|
|
4725
5069
|
chunks.push(` public ILocator ${locatorName} => LocatorByTestId(${testIdExpr});`);
|
|
@@ -4730,12 +5074,12 @@ function generateAggregatedCSharpFiles(componentHierarchyMap, outDir, options =
|
|
|
4730
5074
|
if (target) {
|
|
4731
5075
|
chunks.push(` public async Task<${target}> ${actionName}(${sig})`);
|
|
4732
5076
|
chunks.push(" {");
|
|
4733
|
-
if (
|
|
4734
|
-
chunks.push(` await ${locatorName}${
|
|
5077
|
+
if (selectorIsParameterized || allTestIds.length <= 1) {
|
|
5078
|
+
chunks.push(` await ${locatorName}${selectorIsParameterized ? `(${args})` : ""}.ClickAsync();`);
|
|
4735
5079
|
chunks.push(` return new ${target}(Page);`);
|
|
4736
5080
|
} else {
|
|
4737
5081
|
chunks.push(" Exception? lastError = null;");
|
|
4738
|
-
chunks.push(` foreach (var testId in new[] { ${allTestIds.map(
|
|
5082
|
+
chunks.push(` foreach (var testId in new[] { ${allTestIds.map((testId) => toCSharpPomPatternExpression(testId)).join(", ")} })`);
|
|
4739
5083
|
chunks.push(" {");
|
|
4740
5084
|
chunks.push(" try");
|
|
4741
5085
|
chunks.push(" {");
|
|
@@ -4759,7 +5103,7 @@ function generateAggregatedCSharpFiles(componentHierarchyMap, outDir, options =
|
|
|
4759
5103
|
}
|
|
4760
5104
|
chunks.push(` public async Task ${actionName}(${sig})`);
|
|
4761
5105
|
chunks.push(" {");
|
|
4762
|
-
const callSuffix =
|
|
5106
|
+
const callSuffix = selectorIsParameterized ? `(${args})` : "";
|
|
4763
5107
|
const emitActionCall = (locatorAccess) => {
|
|
4764
5108
|
if (pom.nativeRole === "input") {
|
|
4765
5109
|
chunks.push(` var editableLocator = await ResolveEditableLocatorAsync(${locatorAccess});`);
|
|
@@ -4772,9 +5116,9 @@ function generateAggregatedCSharpFiles(componentHierarchyMap, outDir, options =
|
|
|
4772
5116
|
chunks.push(` await ${locatorAccess}.ClickAsync();`);
|
|
4773
5117
|
}
|
|
4774
5118
|
};
|
|
4775
|
-
if (!
|
|
5119
|
+
if (!selectorIsParameterized && allTestIds.length > 1) {
|
|
4776
5120
|
chunks.push(" Exception? lastError = null;");
|
|
4777
|
-
chunks.push(` foreach (var testId in new[] { ${allTestIds.map(
|
|
5121
|
+
chunks.push(` foreach (var testId in new[] { ${allTestIds.map((testId) => toCSharpPomPatternExpression(testId)).join(", ")} })`);
|
|
4778
5122
|
chunks.push(" {");
|
|
4779
5123
|
chunks.push(" try");
|
|
4780
5124
|
chunks.push(" {");
|
|
@@ -4820,7 +5164,12 @@ function generateAggregatedCSharpFiles(componentHierarchyMap, outDir, options =
|
|
|
4820
5164
|
for (const extra of extras) {
|
|
4821
5165
|
if (extra.kind !== "click")
|
|
4822
5166
|
continue;
|
|
4823
|
-
const
|
|
5167
|
+
const extraParams = orderPomPatternParameters(
|
|
5168
|
+
extra.parameters,
|
|
5169
|
+
getSelectorPatterns(extra.selector),
|
|
5170
|
+
{ omit: extra.keyLiteral !== void 0 ? ["key"] : [] }
|
|
5171
|
+
);
|
|
5172
|
+
const { signature } = formatCSharpParams(extraParams);
|
|
4824
5173
|
const extraName = upperFirst(extra.name);
|
|
4825
5174
|
chunks.push(` public async Task ${extraName}Async(${signature})`);
|
|
4826
5175
|
chunks.push(" {");
|
|
@@ -4828,29 +5177,22 @@ function generateAggregatedCSharpFiles(componentHierarchyMap, outDir, options =
|
|
|
4828
5177
|
chunks.push(` var key = ${JSON.stringify(extra.keyLiteral)};`);
|
|
4829
5178
|
}
|
|
4830
5179
|
if (extra.selector.kind === "testId") {
|
|
4831
|
-
const
|
|
4832
|
-
const
|
|
4833
|
-
|
|
4834
|
-
chunks.push(` var testId = ${testIdExpr};`);
|
|
4835
|
-
chunks.push(" await LocatorByTestId(testId).ClickAsync();");
|
|
4836
|
-
} else {
|
|
4837
|
-
chunks.push(` await LocatorByTestId(${testIdExpr}).ClickAsync();`);
|
|
5180
|
+
const testIdBinding = bindCSharpPomPattern(extra.selector.testId, "testId");
|
|
5181
|
+
for (const statement of testIdBinding.setupStatements) {
|
|
5182
|
+
chunks.push(` ${statement}`);
|
|
4838
5183
|
}
|
|
5184
|
+
chunks.push(` await LocatorByTestId(${testIdBinding.expression}).ClickAsync();`);
|
|
4839
5185
|
} else {
|
|
4840
|
-
const
|
|
4841
|
-
const
|
|
4842
|
-
const rootExpr = toCSharpTestIdExpression(extra.selector.rootFormattedDataTestId);
|
|
4843
|
-
const labelExpr = toCSharpTestIdExpression(extra.selector.formattedLabel);
|
|
5186
|
+
const rootBinding = bindCSharpPomPattern(extra.selector.rootTestId, "rootTestId");
|
|
5187
|
+
const labelBinding = bindCSharpPomPattern(extra.selector.label, "label");
|
|
4844
5188
|
const exactArg = extra.selector.exact === false ? "false" : "true";
|
|
4845
|
-
|
|
4846
|
-
chunks.push(`
|
|
5189
|
+
for (const statement of rootBinding.setupStatements) {
|
|
5190
|
+
chunks.push(` ${statement}`);
|
|
4847
5191
|
}
|
|
4848
|
-
|
|
4849
|
-
chunks.push(`
|
|
5192
|
+
for (const statement of labelBinding.setupStatements) {
|
|
5193
|
+
chunks.push(` ${statement}`);
|
|
4850
5194
|
}
|
|
4851
|
-
|
|
4852
|
-
const labelArg = labelNeedsTemplate ? "label" : labelExpr;
|
|
4853
|
-
chunks.push(` await ClickWithinTestIdByLabelAsync(${rootArg}, ${labelArg}, ${exactArg});`);
|
|
5195
|
+
chunks.push(` await ClickWithinTestIdByLabelAsync(${rootBinding.expression}, ${labelBinding.expression}, ${exactArg});`);
|
|
4854
5196
|
}
|
|
4855
5197
|
chunks.push(" }");
|
|
4856
5198
|
chunks.push("");
|
|
@@ -5166,7 +5508,7 @@ function prepareViewObjectModelClass(componentName, dependencies, componentHiera
|
|
|
5166
5508
|
members.push(...generateRouteProperty(routeMeta));
|
|
5167
5509
|
members.push(...generateGoToSelfMethod(className));
|
|
5168
5510
|
}
|
|
5169
|
-
members.push(...generateMethodsContentForDependencies(dependencies));
|
|
5511
|
+
members.push(...generateMethodsContentForDependencies(componentName, dependencies));
|
|
5170
5512
|
return {
|
|
5171
5513
|
className,
|
|
5172
5514
|
componentRefsForInstances,
|
|
@@ -5294,7 +5636,7 @@ function getViewPassthroughMethods(viewName, viewDependencies, childrenComponent
|
|
|
5294
5636
|
if (existingOnView.has(name) || blockedMethodNames.has(name))
|
|
5295
5637
|
continue;
|
|
5296
5638
|
const list = methodToChildren.get(name) ?? [];
|
|
5297
|
-
list.push({ childProp,
|
|
5639
|
+
list.push({ childProp, signature: sig });
|
|
5298
5640
|
methodToChildren.set(name, list);
|
|
5299
5641
|
}
|
|
5300
5642
|
}
|
|
@@ -5304,12 +5646,12 @@ function getViewPassthroughMethods(viewName, viewDependencies, childrenComponent
|
|
|
5304
5646
|
return [];
|
|
5305
5647
|
}
|
|
5306
5648
|
return passthroughs.map(([methodName, candidates]) => {
|
|
5307
|
-
const { childProp,
|
|
5308
|
-
const callArgs =
|
|
5649
|
+
const { childProp, signature } = candidates[0];
|
|
5650
|
+
const callArgs = getPomParameterArgumentNames(signature.parameters).join(", ");
|
|
5309
5651
|
return createClassMethod({
|
|
5310
5652
|
name: methodName,
|
|
5311
5653
|
isAsync: true,
|
|
5312
|
-
parameters:
|
|
5654
|
+
parameters: toTypeScriptPomParameterStructures(signature.parameters),
|
|
5313
5655
|
statements: [
|
|
5314
5656
|
`return await this.${childProp}.${methodName}(${callArgs});`
|
|
5315
5657
|
]
|
|
@@ -5333,8 +5675,7 @@ function getAttachmentPassthroughMethods(ownerName, ownerDependencies, attachmen
|
|
|
5333
5675
|
const list = methodToAttachments.get(methodName) ?? [];
|
|
5334
5676
|
list.push({
|
|
5335
5677
|
propertyName: attachment.propertyName,
|
|
5336
|
-
|
|
5337
|
-
argNames: signature.argNames
|
|
5678
|
+
signature
|
|
5338
5679
|
});
|
|
5339
5680
|
methodToAttachments.set(methodName, list);
|
|
5340
5681
|
}
|
|
@@ -5345,12 +5686,12 @@ function getAttachmentPassthroughMethods(ownerName, ownerDependencies, attachmen
|
|
|
5345
5686
|
return [];
|
|
5346
5687
|
}
|
|
5347
5688
|
return passthroughs.map(([methodName, candidates]) => {
|
|
5348
|
-
const { propertyName,
|
|
5349
|
-
const callArgs =
|
|
5689
|
+
const { propertyName, signature } = candidates[0];
|
|
5690
|
+
const callArgs = getPomParameterArgumentNames(signature.parameters).join(", ");
|
|
5350
5691
|
const invocation = callArgs ? `this.${propertyName}.${methodName}(${callArgs})` : `this.${propertyName}.${methodName}()`;
|
|
5351
5692
|
return createClassMethod({
|
|
5352
5693
|
name: methodName,
|
|
5353
|
-
parameters:
|
|
5694
|
+
parameters: toTypeScriptPomParameterStructures(signature.parameters),
|
|
5354
5695
|
statements: [
|
|
5355
5696
|
`return ${invocation};`
|
|
5356
5697
|
]
|
|
@@ -5364,15 +5705,44 @@ function sliceNodeSource(source, node) {
|
|
|
5364
5705
|
const snippet = source.slice(node.start, node.end).trim();
|
|
5365
5706
|
return snippet.length ? snippet : null;
|
|
5366
5707
|
}
|
|
5367
|
-
function
|
|
5708
|
+
function getTypeAnnotationSource(source, node) {
|
|
5709
|
+
const rawTypeAnnotation = node.typeAnnotation;
|
|
5710
|
+
if (!rawTypeAnnotation || typeof rawTypeAnnotation !== "object" || !("type" in rawTypeAnnotation) || rawTypeAnnotation.type !== "TSTypeAnnotation" || !("typeAnnotation" in rawTypeAnnotation)) {
|
|
5711
|
+
return void 0;
|
|
5712
|
+
}
|
|
5713
|
+
const typeAnnotation = rawTypeAnnotation.typeAnnotation;
|
|
5714
|
+
return typeAnnotation && typeof typeAnnotation === "object" ? sliceNodeSource(source, typeAnnotation) ?? void 0 : void 0;
|
|
5715
|
+
}
|
|
5716
|
+
function getCustomPomParameterSpec(source, param) {
|
|
5368
5717
|
if (param.type === "Identifier") {
|
|
5369
|
-
return param.name
|
|
5718
|
+
return createPomParameterSpec(param.name, getTypeAnnotationSource(source, param), {
|
|
5719
|
+
hasQuestionToken: !!param.optional
|
|
5720
|
+
});
|
|
5370
5721
|
}
|
|
5371
5722
|
if (param.type === "AssignmentPattern") {
|
|
5372
|
-
|
|
5723
|
+
if (param.left.type !== "Identifier") {
|
|
5724
|
+
return null;
|
|
5725
|
+
}
|
|
5726
|
+
const initializer = sliceNodeSource(source, param.right);
|
|
5727
|
+
if (!initializer) {
|
|
5728
|
+
return null;
|
|
5729
|
+
}
|
|
5730
|
+
return createPomParameterSpec(param.left.name, getTypeAnnotationSource(source, param.left), {
|
|
5731
|
+
initializer,
|
|
5732
|
+
hasQuestionToken: !!param.left.optional
|
|
5733
|
+
});
|
|
5373
5734
|
}
|
|
5374
5735
|
if (param.type === "RestElement") {
|
|
5375
|
-
|
|
5736
|
+
if (param.argument.type !== "Identifier") {
|
|
5737
|
+
return null;
|
|
5738
|
+
}
|
|
5739
|
+
const typeExpression = getTypeAnnotationSource(
|
|
5740
|
+
source,
|
|
5741
|
+
param
|
|
5742
|
+
) ?? getTypeAnnotationSource(source, param.argument);
|
|
5743
|
+
return createPomParameterSpec(param.argument.name, typeExpression, {
|
|
5744
|
+
isRestParameter: true
|
|
5745
|
+
});
|
|
5376
5746
|
}
|
|
5377
5747
|
return null;
|
|
5378
5748
|
}
|
|
@@ -5405,29 +5775,23 @@ function extractCustomPomMethodSignatures(source, exportName) {
|
|
|
5405
5775
|
if (member.key.type !== "Identifier") {
|
|
5406
5776
|
continue;
|
|
5407
5777
|
}
|
|
5408
|
-
const
|
|
5409
|
-
const argNames = [];
|
|
5778
|
+
const parameters = [];
|
|
5410
5779
|
let supported = true;
|
|
5411
5780
|
member.params.forEach((param) => {
|
|
5412
5781
|
if (!supported) {
|
|
5413
5782
|
return;
|
|
5414
5783
|
}
|
|
5415
|
-
const
|
|
5416
|
-
|
|
5417
|
-
if (!paramSource || !argName) {
|
|
5784
|
+
const parameter = getCustomPomParameterSpec(source, param);
|
|
5785
|
+
if (!parameter) {
|
|
5418
5786
|
supported = false;
|
|
5419
5787
|
return;
|
|
5420
5788
|
}
|
|
5421
|
-
|
|
5422
|
-
argNames.push(argName);
|
|
5789
|
+
parameters.push(parameter);
|
|
5423
5790
|
});
|
|
5424
5791
|
if (!supported) {
|
|
5425
5792
|
continue;
|
|
5426
5793
|
}
|
|
5427
|
-
signatures.set(member.key.name,
|
|
5428
|
-
params: params.join(", "),
|
|
5429
|
-
argNames
|
|
5430
|
-
});
|
|
5794
|
+
signatures.set(member.key.name, createPomMethodSignature(parameters));
|
|
5431
5795
|
}
|
|
5432
5796
|
}
|
|
5433
5797
|
return signatures;
|
|
@@ -5610,6 +5974,9 @@ function resolveCustomPomImportResolution(generatedClassNames, projectRoot, opti
|
|
|
5610
5974
|
const customDirRelOrAbs = options.customPomDir ?? "tests/playwright/pom/custom";
|
|
5611
5975
|
const customDirAbs = path.isAbsolute(customDirRelOrAbs) ? customDirRelOrAbs : path.resolve(projectRoot, customDirRelOrAbs);
|
|
5612
5976
|
if (!fs.existsSync(customDirAbs)) {
|
|
5977
|
+
if (options.requireCustomPomDir) {
|
|
5978
|
+
throw createMissingCustomPomDirectoryError(customDirRelOrAbs, customDirAbs);
|
|
5979
|
+
}
|
|
5613
5980
|
return {
|
|
5614
5981
|
classIdentifierMap,
|
|
5615
5982
|
methodSignaturesByClass,
|
|
@@ -5652,6 +6019,14 @@ function resolveCustomPomImportResolution(generatedClassNames, projectRoot, opti
|
|
|
5652
6019
|
importSpecifiersByClass
|
|
5653
6020
|
};
|
|
5654
6021
|
}
|
|
6022
|
+
function assertCustomPomAttachmentsResolved(attachments, classIdentifierMap, configuredDir) {
|
|
6023
|
+
const missingClassNames = Array.from(new Set(
|
|
6024
|
+
attachments.map((attachment) => attachment.className).filter((className) => !Object.prototype.hasOwnProperty.call(classIdentifierMap, className))
|
|
6025
|
+
)).sort((left, right) => left.localeCompare(right));
|
|
6026
|
+
if (missingClassNames.length > 0) {
|
|
6027
|
+
throw createMissingCustomPomAttachmentClassError(missingClassNames, configuredDir);
|
|
6028
|
+
}
|
|
6029
|
+
}
|
|
5655
6030
|
function getComposedStubBody(targetClassName, availableClassNames, depsByClassName, vueFilesPathMap, projectRoot) {
|
|
5656
6031
|
const filePath = resolveVueSourcePath(targetClassName, vueFilesPathMap, projectRoot);
|
|
5657
6032
|
if (!filePath)
|
|
@@ -5680,7 +6055,7 @@ function getComposedStubBody(targetClassName, availableClassNames, depsByClassNa
|
|
|
5680
6055
|
if (!sig)
|
|
5681
6056
|
continue;
|
|
5682
6057
|
const list = methodToChildren.get(name) ?? [];
|
|
5683
|
-
list.push({ child,
|
|
6058
|
+
list.push({ child, signature: sig });
|
|
5684
6059
|
methodToChildren.set(name, list);
|
|
5685
6060
|
}
|
|
5686
6061
|
}
|
|
@@ -5688,12 +6063,12 @@ function getComposedStubBody(targetClassName, availableClassNames, depsByClassNa
|
|
|
5688
6063
|
for (const [methodName, candidatesForMethod] of methodToChildren.entries()) {
|
|
5689
6064
|
if (candidatesForMethod.length !== 1 || methodName === "constructor")
|
|
5690
6065
|
continue;
|
|
5691
|
-
const { child,
|
|
5692
|
-
const callArgs =
|
|
6066
|
+
const { child, signature } = candidatesForMethod[0];
|
|
6067
|
+
const callArgs = getPomParameterArgumentNames(signature.parameters).join(", ");
|
|
5693
6068
|
passthroughMembers.push(createClassMethod({
|
|
5694
6069
|
name: methodName,
|
|
5695
6070
|
isAsync: true,
|
|
5696
|
-
parameters:
|
|
6071
|
+
parameters: toTypeScriptPomParameterStructures(signature.parameters),
|
|
5697
6072
|
statements: [
|
|
5698
6073
|
`return await this.${child}.${methodName}(${callArgs});`
|
|
5699
6074
|
]
|
|
@@ -5742,9 +6117,15 @@ async function generateAggregatedFiles(componentHierarchyMap, vueFilesPathMap, b
|
|
|
5742
6117
|
imports.push(`export * from "${runtimeClassGenRel}/base-page";`);
|
|
5743
6118
|
const customPomImportResolution = resolveCustomPomImportResolution(generatedClassNames, projectRoot, {
|
|
5744
6119
|
customPomDir: options.customPomDir,
|
|
6120
|
+
requireCustomPomDir: options.requireCustomPomDir,
|
|
5745
6121
|
customPomImportAliases: options.customPomImportAliases,
|
|
5746
6122
|
customPomImportNameCollisionBehavior: options.customPomImportNameCollisionBehavior
|
|
5747
6123
|
});
|
|
6124
|
+
assertCustomPomAttachmentsResolved(
|
|
6125
|
+
options.customPomAttachments ?? [],
|
|
6126
|
+
customPomImportResolution.classIdentifierMap,
|
|
6127
|
+
options.customPomDir ?? "tests/playwright/pom/custom"
|
|
6128
|
+
);
|
|
5748
6129
|
const customPomClassIdentifierMap = customPomImportResolution.classIdentifierMap;
|
|
5749
6130
|
const customPomMethodSignaturesByClass = customPomImportResolution.methodSignaturesByClass;
|
|
5750
6131
|
const customPomAvailableClassIdentifiers = customPomImportResolution.availableClassIdentifiers;
|
|
@@ -5894,8 +6275,8 @@ function getWidgetInstancesForView(componentName, dataTestIdSet, availableClassI
|
|
|
5894
6275
|
return candidate;
|
|
5895
6276
|
};
|
|
5896
6277
|
for (const dt of dataTestIdSet) {
|
|
5897
|
-
const raw = dt.
|
|
5898
|
-
if (
|
|
6278
|
+
const raw = dt.selectorValue.formatted;
|
|
6279
|
+
if (isParameterizedPomPattern(dt.selectorValue.patternKind)) {
|
|
5899
6280
|
continue;
|
|
5900
6281
|
}
|
|
5901
6282
|
const toggleSuffix = "-toggle";
|
|
@@ -5985,7 +6366,6 @@ function getConstructor(childrenComponent, componentHierarchyMap, attachmentsFor
|
|
|
5985
6366
|
});
|
|
5986
6367
|
}
|
|
5987
6368
|
const TESTID_CLICK_EVENT_NAME = "__testid_event__";
|
|
5988
|
-
const TESTID_CLICK_EVENT_STRICT_FLAG = "__testid_click_event_strict__";
|
|
5989
6369
|
const CLICK_EVENT_NAME = TESTID_CLICK_EVENT_NAME;
|
|
5990
6370
|
const inferredNativeWrapperConfigByLookup = /* @__PURE__ */ new Map();
|
|
5991
6371
|
const inferredSfcPathByLookup = /* @__PURE__ */ new Map();
|
|
@@ -6247,7 +6627,7 @@ function getConditionalDirectiveInfo(element) {
|
|
|
6247
6627
|
if (directive.name === "else") {
|
|
6248
6628
|
const exp2 = directive.exp;
|
|
6249
6629
|
if (exp2 && (exp2.type === NodeTypes.SIMPLE_EXPRESSION || exp2.type === NodeTypes.COMPOUND_EXPRESSION)) {
|
|
6250
|
-
const source2 = (exp2
|
|
6630
|
+
const source2 = getVueExpressionSource(exp2, "content", "compiled");
|
|
6251
6631
|
return { kind: "else-if", source: source2 };
|
|
6252
6632
|
}
|
|
6253
6633
|
return { kind: "else", source: "" };
|
|
@@ -6256,13 +6636,13 @@ function getConditionalDirectiveInfo(element) {
|
|
|
6256
6636
|
const exp2 = directive.exp;
|
|
6257
6637
|
if (!exp2 || exp2.type !== NodeTypes.SIMPLE_EXPRESSION && exp2.type !== NodeTypes.COMPOUND_EXPRESSION)
|
|
6258
6638
|
return null;
|
|
6259
|
-
const source2 = (exp2
|
|
6639
|
+
const source2 = getVueExpressionSource(exp2, "content", "compiled");
|
|
6260
6640
|
return { kind: "else-if", source: source2 };
|
|
6261
6641
|
}
|
|
6262
6642
|
const exp = directive.exp;
|
|
6263
6643
|
if (!exp || exp.type !== NodeTypes.SIMPLE_EXPRESSION && exp.type !== NodeTypes.COMPOUND_EXPRESSION)
|
|
6264
6644
|
return null;
|
|
6265
|
-
const source = (exp
|
|
6645
|
+
const source = getVueExpressionSource(exp, "content", "compiled");
|
|
6266
6646
|
return { kind: directive.name, source };
|
|
6267
6647
|
}
|
|
6268
6648
|
function tryExtractStableHintFromConditionalExpressionSource(source) {
|
|
@@ -6288,26 +6668,26 @@ function tryExtractStableHintFromConditionalExpressionSource(source) {
|
|
|
6288
6668
|
};
|
|
6289
6669
|
try {
|
|
6290
6670
|
const expr = parseExpression(src, { plugins: ["typescript"] });
|
|
6291
|
-
const
|
|
6671
|
+
const isNodeType2 = (n, type) => {
|
|
6292
6672
|
return n !== null && n.type === type;
|
|
6293
6673
|
};
|
|
6294
|
-
const
|
|
6295
|
-
return
|
|
6674
|
+
const isStringLiteralNode2 = (n) => {
|
|
6675
|
+
return isNodeType2(n, "StringLiteral") && typeof n.value === "string";
|
|
6296
6676
|
};
|
|
6297
|
-
const
|
|
6298
|
-
return
|
|
6677
|
+
const isIdentifierNode2 = (n) => {
|
|
6678
|
+
return isNodeType2(n, "Identifier") && typeof n.name === "string";
|
|
6299
6679
|
};
|
|
6300
6680
|
const results = [];
|
|
6301
6681
|
const walk = (n) => {
|
|
6302
6682
|
if (!n)
|
|
6303
6683
|
return;
|
|
6304
|
-
if (
|
|
6684
|
+
if (isStringLiteralNode2(n)) {
|
|
6305
6685
|
const v = (n.value ?? "").trim();
|
|
6306
6686
|
if (isIdentifierish(v)) {
|
|
6307
6687
|
results.push(v);
|
|
6308
6688
|
}
|
|
6309
6689
|
}
|
|
6310
|
-
if (
|
|
6690
|
+
if (isIdentifierNode2(n)) {
|
|
6311
6691
|
const v = (n.name ?? "").trim();
|
|
6312
6692
|
if (isIdentifierish(v)) {
|
|
6313
6693
|
results.push(v);
|
|
@@ -6457,34 +6837,20 @@ ${buildSearchRootsKey(normalizedSearchRoots)}`;
|
|
|
6457
6837
|
inferredNativeWrapperConfigByLookup.set(cacheKey, { role: "" });
|
|
6458
6838
|
return null;
|
|
6459
6839
|
}
|
|
6460
|
-
function tryWrapClickDirectiveForTestEvents(element, testIdAttribute) {
|
|
6840
|
+
function tryWrapClickDirectiveForTestEvents(element, testIdAttribute, resolvedRuntimeTestId) {
|
|
6461
6841
|
const jsStringLiteral = (value) => {
|
|
6462
6842
|
return JSON.stringify(value);
|
|
6463
6843
|
};
|
|
6464
6844
|
const getTestIdExpressionForNode = () => {
|
|
6465
|
-
|
|
6466
|
-
if (!existing) {
|
|
6467
|
-
return "undefined";
|
|
6468
|
-
}
|
|
6469
|
-
if (existing.type === NodeTypes.ATTRIBUTE) {
|
|
6470
|
-
const v = existing.value?.content;
|
|
6471
|
-
if (!v) {
|
|
6472
|
-
return "undefined";
|
|
6473
|
-
}
|
|
6474
|
-
return jsStringLiteral(v);
|
|
6475
|
-
}
|
|
6476
|
-
const directive = existing;
|
|
6477
|
-
const exp2 = directive.exp;
|
|
6478
|
-
if (!exp2 || exp2.type !== NodeTypes.SIMPLE_EXPRESSION) {
|
|
6845
|
+
if (!resolvedRuntimeTestId) {
|
|
6479
6846
|
return "undefined";
|
|
6480
6847
|
}
|
|
6481
|
-
|
|
6482
|
-
|
|
6483
|
-
return "undefined";
|
|
6848
|
+
if (resolvedRuntimeTestId.kind === "static") {
|
|
6849
|
+
return jsStringLiteral(resolvedRuntimeTestId.value);
|
|
6484
6850
|
}
|
|
6485
|
-
return `(${
|
|
6851
|
+
return `(${renderTemplateLiteralExpression(resolvedRuntimeTestId)})`;
|
|
6486
6852
|
};
|
|
6487
|
-
const
|
|
6853
|
+
const testIdExpression = getTestIdExpressionForNode();
|
|
6488
6854
|
const clickDirective = tryGetClickDirective(element);
|
|
6489
6855
|
if (!clickDirective)
|
|
6490
6856
|
return;
|
|
@@ -6495,10 +6861,10 @@ function tryWrapClickDirectiveForTestEvents(element, testIdAttribute) {
|
|
|
6495
6861
|
const exp = clickDirective.exp;
|
|
6496
6862
|
if (!exp)
|
|
6497
6863
|
return;
|
|
6498
|
-
const existingSource = (exp
|
|
6864
|
+
const existingSource = getVueExpressionSource(exp, "loc", "content");
|
|
6499
6865
|
if (existingSource.includes(CLICK_EVENT_NAME))
|
|
6500
6866
|
return;
|
|
6501
|
-
const originalExpression = (exp
|
|
6867
|
+
const originalExpression = getVueExpressionSource(exp, "content", "loc");
|
|
6502
6868
|
if (!originalExpression)
|
|
6503
6869
|
return;
|
|
6504
6870
|
const isStatementBody = (() => {
|
|
@@ -6515,7 +6881,7 @@ function tryWrapClickDirectiveForTestEvents(element, testIdAttribute) {
|
|
|
6515
6881
|
const statementWrappedHandler = `($event) => {
|
|
6516
6882
|
const __win = ($event && $event.view) ? $event.view : undefined;
|
|
6517
6883
|
const __target = ($event && $event.currentTarget) ? $event.currentTarget : undefined;
|
|
6518
|
-
const __testIdFromNode = ${
|
|
6884
|
+
const __testIdFromNode = ${testIdExpression};
|
|
6519
6885
|
const __testIdFromTarget = (__target && typeof __target.getAttribute === 'function') ? __target.getAttribute(${jsStringLiteral(testIdAttribute)}) : undefined;
|
|
6520
6886
|
const __testId = (__testIdFromNode ?? __testIdFromTarget);
|
|
6521
6887
|
const __emit = (phase, err) => {
|
|
@@ -6526,16 +6892,12 @@ function tryWrapClickDirectiveForTestEvents(element, testIdAttribute) {
|
|
|
6526
6892
|
__w.dispatchEvent(new __CustomEvent('${CLICK_EVENT_NAME}', { detail: { testId: __testId, phase, err: err ? String(err) : undefined } }));
|
|
6527
6893
|
}
|
|
6528
6894
|
} catch (e) {
|
|
6529
|
-
// Instrumentation
|
|
6530
|
-
// In strict mode we rethrow so tests fail fast and the underlying problem is visible.
|
|
6531
|
-
// Outside strict mode we log and continue so we don't break real user clicks.
|
|
6895
|
+
// Instrumentation failures should never be silent. Log the root cause and fail fast.
|
|
6532
6896
|
const __w = __win || (__target && __target.ownerDocument && __target.ownerDocument.defaultView);
|
|
6533
6897
|
if (__w && __w.console && typeof __w.console.error === 'function') {
|
|
6534
6898
|
__w.console.error('[testid-click-event] failed to emit ${CLICK_EVENT_NAME}', e);
|
|
6535
6899
|
}
|
|
6536
|
-
|
|
6537
|
-
throw e;
|
|
6538
|
-
}
|
|
6900
|
+
throw e;
|
|
6539
6901
|
}
|
|
6540
6902
|
};
|
|
6541
6903
|
const __w2 = __win || (__target && __target.ownerDocument && __target.ownerDocument.defaultView);
|
|
@@ -6566,7 +6928,7 @@ function tryWrapClickDirectiveForTestEvents(element, testIdAttribute) {
|
|
|
6566
6928
|
const expressionWrappedHandler = `($event) => {
|
|
6567
6929
|
const __win = ($event && $event.view) ? $event.view : undefined;
|
|
6568
6930
|
const __target = ($event && $event.currentTarget) ? $event.currentTarget : undefined;
|
|
6569
|
-
const __testIdFromNode = ${
|
|
6931
|
+
const __testIdFromNode = ${testIdExpression};
|
|
6570
6932
|
const __testIdFromTarget = (__target && typeof __target.getAttribute === 'function') ? __target.getAttribute(${jsStringLiteral(testIdAttribute)}) : undefined;
|
|
6571
6933
|
const __testId = (__testIdFromNode ?? __testIdFromTarget);
|
|
6572
6934
|
const __emit = (phase, err) => {
|
|
@@ -6577,16 +6939,12 @@ function tryWrapClickDirectiveForTestEvents(element, testIdAttribute) {
|
|
|
6577
6939
|
__w.dispatchEvent(new __CustomEvent('${CLICK_EVENT_NAME}', { detail: { testId: __testId, phase, err: err ? String(err) : undefined } }));
|
|
6578
6940
|
}
|
|
6579
6941
|
} catch (e) {
|
|
6580
|
-
// Instrumentation
|
|
6581
|
-
// In strict mode we rethrow so tests fail fast and the underlying problem is visible.
|
|
6582
|
-
// Outside strict mode we log and continue so we don't break real user clicks.
|
|
6942
|
+
// Instrumentation failures should never be silent. Log the root cause and fail fast.
|
|
6583
6943
|
const __w = __win || (__target && __target.ownerDocument && __target.ownerDocument.defaultView);
|
|
6584
6944
|
if (__w && __w.console && typeof __w.console.error === 'function') {
|
|
6585
6945
|
__w.console.error('[testid-click-event] failed to emit ${CLICK_EVENT_NAME}', e);
|
|
6586
6946
|
}
|
|
6587
|
-
|
|
6588
|
-
throw e;
|
|
6589
|
-
}
|
|
6947
|
+
throw e;
|
|
6590
6948
|
}
|
|
6591
6949
|
};
|
|
6592
6950
|
const __w2 = __win || (__target && __target.ownerDocument && __target.ownerDocument.defaultView);
|
|
@@ -6624,10 +6982,9 @@ function tryWrapClickDirectiveForTestEvents(element, testIdAttribute) {
|
|
|
6624
6982
|
let previousFileName = "";
|
|
6625
6983
|
const hierarchyMap = /* @__PURE__ */ new Map();
|
|
6626
6984
|
function createTestIdTransform(componentName, componentHierarchyMap, nativeWrappers = {}, excludedComponents = [], viewsDirAbs, options = {}) {
|
|
6627
|
-
const existingIdBehavior = options.existingIdBehavior ?? "
|
|
6985
|
+
const existingIdBehavior = options.existingIdBehavior ?? "error";
|
|
6628
6986
|
const testIdAttribute = (options.testIdAttribute || "data-testid").trim() || "data-testid";
|
|
6629
|
-
const nameCollisionBehavior = options.nameCollisionBehavior ?? "
|
|
6630
|
-
const missingSemanticNameBehavior = options.missingSemanticNameBehavior ?? "error";
|
|
6987
|
+
const nameCollisionBehavior = options.nameCollisionBehavior ?? "error";
|
|
6631
6988
|
const warn = options.warn;
|
|
6632
6989
|
const vueFilesPathMap = options.vueFilesPathMap;
|
|
6633
6990
|
const wrapperSearchRoots = options.wrapperSearchRoots ?? [];
|
|
@@ -6703,7 +7060,7 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
|
|
|
6703
7060
|
conditionalHintByIfBranch.set(branch, hint);
|
|
6704
7061
|
continue;
|
|
6705
7062
|
}
|
|
6706
|
-
const condSource = (cond
|
|
7063
|
+
const condSource = getVueExpressionSource(cond, "content", "compiled");
|
|
6707
7064
|
const stable = tryExtractStableHintFromConditionalExpressionSource(condSource);
|
|
6708
7065
|
if (stable) {
|
|
6709
7066
|
conditionalHintByIfBranch.set(branch, stable);
|
|
@@ -6765,17 +7122,18 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
|
|
|
6765
7122
|
nativeWrappers[element.tag] = { role: "grid" };
|
|
6766
7123
|
}
|
|
6767
7124
|
}
|
|
6768
|
-
const
|
|
7125
|
+
const getBestAvailableKeyInfo = () => {
|
|
6769
7126
|
const parentNode = context.parent && typeof context.parent === "object" ? context.parent : null;
|
|
6770
7127
|
const isDirectVForChild = parentNode?.type === NodeTypes.FOR;
|
|
6771
|
-
const
|
|
6772
|
-
if (
|
|
6773
|
-
|
|
7128
|
+
const vForKeyInfo = (isDirectVForChild ? getKeyDirectiveInfo(element) : null) || getContainedInVForDirectiveKeyInfo(context, element, hierarchyMap);
|
|
7129
|
+
if (vForKeyInfo) {
|
|
7130
|
+
return vForKeyInfo;
|
|
7131
|
+
}
|
|
7132
|
+
return getContainedInSlotDataKeyInfo(element, hierarchyMap);
|
|
6774
7133
|
};
|
|
6775
|
-
const
|
|
6776
|
-
const
|
|
6777
|
-
const
|
|
6778
|
-
const bestKeyVariable = isSlotKey ? bestKeyInferred : null;
|
|
7134
|
+
const bestKeyInfo = getBestAvailableKeyInfo();
|
|
7135
|
+
const bestKeyPlaceholder = bestKeyInfo?.selectorFragment ?? null;
|
|
7136
|
+
const bestRuntimeKeyPlaceholder = bestKeyInfo?.runtimeFragment ?? null;
|
|
6779
7137
|
const keyValuesOverride = tryGetContainedInStaticVForSourceLiteralValues(context);
|
|
6780
7138
|
const parentKey = context?.parent ? context.parent : null;
|
|
6781
7139
|
const conditional = getConditionalDirectiveInfo(element);
|
|
@@ -6818,7 +7176,7 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
|
|
|
6818
7176
|
if (!cond) {
|
|
6819
7177
|
conditionalHint = "else";
|
|
6820
7178
|
} else {
|
|
6821
|
-
const condSource = (cond
|
|
7179
|
+
const condSource = getVueExpressionSource(cond, "content", "compiled");
|
|
6822
7180
|
conditionalHint = tryExtractStableHintFromConditionalExpressionSource(condSource) ?? "if";
|
|
6823
7181
|
}
|
|
6824
7182
|
}
|
|
@@ -6828,7 +7186,7 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
|
|
|
6828
7186
|
});
|
|
6829
7187
|
if (showDirective?.exp && (showDirective.exp.type === NodeTypes.SIMPLE_EXPRESSION || showDirective.exp.type === NodeTypes.COMPOUND_EXPRESSION)) {
|
|
6830
7188
|
const exp = showDirective.exp;
|
|
6831
|
-
const source = (exp
|
|
7189
|
+
const source = getVueExpressionSource(exp, "content", "compiled");
|
|
6832
7190
|
const showHint = tryExtractStableHintFromConditionalExpressionSource(source);
|
|
6833
7191
|
if (showHint) {
|
|
6834
7192
|
conditionalHint = conditionalHint ? `${conditionalHint} ${showHint}` : showHint;
|
|
@@ -6862,13 +7220,17 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
|
|
|
6862
7220
|
const tagSuffix = getTagSuffix();
|
|
6863
7221
|
return bestKeyPlaceholder ? templateAttributeValue(`${componentName}-${bestKeyPlaceholder}${clickSuffix}${tagSuffix}`) : staticAttributeValue(`${componentName}${clickSuffix}${tagSuffix}`);
|
|
6864
7222
|
};
|
|
7223
|
+
const getClickRuntimeDataTestId = (clickSuffix) => {
|
|
7224
|
+
const tagSuffix = getTagSuffix();
|
|
7225
|
+
return bestRuntimeKeyPlaceholder ? templateAttributeValue(`${componentName}-${bestRuntimeKeyPlaceholder}${clickSuffix}${tagSuffix}`) : staticAttributeValue(`${componentName}${clickSuffix}${tagSuffix}`);
|
|
7226
|
+
};
|
|
6865
7227
|
const getSubmitDataTestId = (identifier) => {
|
|
6866
7228
|
const tagSuffix = getTagSuffix();
|
|
6867
7229
|
return `${componentName}-${identifier}${tagSuffix}`;
|
|
6868
7230
|
};
|
|
6869
7231
|
const applyResolvedDataTestIdForElement = (args) => {
|
|
6870
7232
|
const nativeRole = args.nativeRoleOverride ?? getNativeRoleFromTagSuffix();
|
|
6871
|
-
applyResolvedDataTestId({
|
|
7233
|
+
return applyResolvedDataTestId({
|
|
6872
7234
|
element,
|
|
6873
7235
|
componentName,
|
|
6874
7236
|
parentComponentName,
|
|
@@ -6878,8 +7240,8 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
|
|
|
6878
7240
|
generatedMethodContentByComponent,
|
|
6879
7241
|
nativeRole,
|
|
6880
7242
|
preferredGeneratedValue: args.preferredGeneratedValue,
|
|
6881
|
-
|
|
6882
|
-
|
|
7243
|
+
preferredRuntimeValue: args.preferredRuntimeValue,
|
|
7244
|
+
keyInfo: bestKeyInfo,
|
|
6883
7245
|
keyValuesOverride,
|
|
6884
7246
|
entryOverrides: args.entryOverrides,
|
|
6885
7247
|
semanticNameHint: args.semanticNameHint,
|
|
@@ -6897,7 +7259,7 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
|
|
|
6897
7259
|
return p.type === NodeTypes.DIRECTIVE && p.name === "bind" && p.arg?.type === NodeTypes.SIMPLE_EXPRESSION && p.arg.content === "handler" && !!p.exp;
|
|
6898
7260
|
}) ?? null;
|
|
6899
7261
|
const handlerInfo = handlerDirective ? nodeHandlerAttributeInfo(element) : null;
|
|
6900
|
-
if (
|
|
7262
|
+
if (nativeWrappers[element.tag]?.role === "button" && handlerDirective && !handlerInfo) {
|
|
6901
7263
|
const loc = element.loc?.start;
|
|
6902
7264
|
const locationHint = loc ? `${loc.line}:${loc.column}` : "unknown";
|
|
6903
7265
|
const handlerSource = (handlerDirective.exp?.loc?.source ?? "").trim() || "<unknown>";
|
|
@@ -6906,7 +7268,7 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
|
|
|
6906
7268
|
Element: <${element.tag}>
|
|
6907
7269
|
Handler: ${handlerSource}
|
|
6908
7270
|
|
|
6909
|
-
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
|
|
7271
|
+
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.`
|
|
6910
7272
|
);
|
|
6911
7273
|
}
|
|
6912
7274
|
if (nativeWrappersValue) {
|
|
@@ -7033,20 +7395,20 @@ Fix: remove the explicit ${attrLabel}, or change existingIdBehavior to "overwrit
|
|
|
7033
7395
|
contextFilename: context.filename
|
|
7034
7396
|
});
|
|
7035
7397
|
const clickHint = trimLeadingSeparators(clickSuffix) || void 0;
|
|
7036
|
-
const idOrName =
|
|
7398
|
+
const idOrName = getStaticIdOrNameHint(element) || void 0;
|
|
7037
7399
|
const semanticHintCandidates = [clickHint, idOrName, innerText, conditionalHint].map((value) => (value ?? "").trim()).filter(Boolean).filter((value, index, values) => values.indexOf(value) === index);
|
|
7038
7400
|
const [semanticNameHint2, ...semanticNameHintAlternates] = semanticHintCandidates;
|
|
7039
7401
|
const pomMergeKey = clickHint ? `click:hint:${clickHint}` : void 0;
|
|
7040
7402
|
const testId = getClickDataTestId(clickSuffix);
|
|
7041
|
-
|
|
7403
|
+
const runtimeTestId = getClickRuntimeDataTestId(clickSuffix);
|
|
7404
|
+
const resolvedDataTestId = applyResolvedDataTestIdForElement({
|
|
7042
7405
|
preferredGeneratedValue: testId,
|
|
7406
|
+
preferredRuntimeValue: runtimeTestId,
|
|
7043
7407
|
semanticNameHint: semanticNameHint2,
|
|
7044
7408
|
semanticNameHintAlternates,
|
|
7045
7409
|
pomMergeKey
|
|
7046
7410
|
});
|
|
7047
|
-
|
|
7048
|
-
tryWrapClickDirectiveForTestEvents(element, testIdAttribute);
|
|
7049
|
-
}
|
|
7411
|
+
tryWrapClickDirectiveForTestEvents(element, testIdAttribute, resolvedDataTestId.runtimeValue);
|
|
7050
7412
|
return;
|
|
7051
7413
|
}
|
|
7052
7414
|
const existingElementDataTestId = tryGetExistingElementDataTestId(element, testIdAttribute);
|
|
@@ -7056,7 +7418,7 @@ Fix: remove the explicit ${attrLabel}, or change existingIdBehavior to "overwrit
|
|
|
7056
7418
|
if (!isRecognizedInteractiveRole) {
|
|
7057
7419
|
return;
|
|
7058
7420
|
}
|
|
7059
|
-
const identifierHint =
|
|
7421
|
+
const identifierHint = getStaticIdOrNameHint(element) || nodeHandlerAttributeValue(element) || innerText || existingElementDataTestId.value || conditionalHint || void 0;
|
|
7060
7422
|
const preferredGeneratedValue = existingElementDataTestId.isDynamic ? templateAttributeValue(existingElementDataTestId.template) : staticAttributeValue(existingElementDataTestId.value);
|
|
7061
7423
|
applyResolvedDataTestIdForElement({
|
|
7062
7424
|
preferredGeneratedValue,
|
|
@@ -7066,7 +7428,7 @@ Fix: remove the explicit ${attrLabel}, or change existingIdBehavior to "overwrit
|
|
|
7066
7428
|
}
|
|
7067
7429
|
const isSubmit = element.props.find((p) => p.type === NodeTypes.ATTRIBUTE && p.name === "type")?.value?.content === "submit";
|
|
7068
7430
|
if (isSubmit) {
|
|
7069
|
-
const identifier =
|
|
7431
|
+
const identifier = getStaticIdOrNameHint(element) || innerText;
|
|
7070
7432
|
if (!identifier) {
|
|
7071
7433
|
const loc = element.loc?.start;
|
|
7072
7434
|
const locationHint = loc ? `${loc.line}:${loc.column}` : "unknown";
|
|
@@ -7118,29 +7480,32 @@ function createBuildProcessorPlugin(options) {
|
|
|
7118
7480
|
getSourceDirs,
|
|
7119
7481
|
basePageClassPath,
|
|
7120
7482
|
normalizedBasePagePath,
|
|
7483
|
+
generation,
|
|
7484
|
+
projectRootRef,
|
|
7485
|
+
nativeWrappers,
|
|
7486
|
+
excludedComponents,
|
|
7487
|
+
getWrapperSearchRoots,
|
|
7488
|
+
getResolvedRouterEntry,
|
|
7489
|
+
loggerRef
|
|
7490
|
+
} = options;
|
|
7491
|
+
const {
|
|
7121
7492
|
outDir,
|
|
7122
7493
|
emitLanguages,
|
|
7123
7494
|
typescriptOutputStructure,
|
|
7124
7495
|
csharp,
|
|
7125
7496
|
generateFixtures,
|
|
7126
7497
|
customPomAttachments,
|
|
7127
|
-
projectRootRef,
|
|
7128
7498
|
customPomDir,
|
|
7499
|
+
requireCustomPomDir,
|
|
7129
7500
|
customPomImportAliases,
|
|
7130
7501
|
customPomImportNameCollisionBehavior,
|
|
7131
7502
|
testIdAttribute,
|
|
7132
7503
|
nameCollisionBehavior,
|
|
7133
|
-
missingSemanticNameBehavior = "error",
|
|
7134
7504
|
existingIdBehavior,
|
|
7135
|
-
nativeWrappers,
|
|
7136
|
-
excludedComponents,
|
|
7137
|
-
getWrapperSearchRoots,
|
|
7138
7505
|
routerAwarePoms,
|
|
7139
|
-
getResolvedRouterEntry,
|
|
7140
7506
|
routerType,
|
|
7141
|
-
routerModuleShims
|
|
7142
|
-
|
|
7143
|
-
} = options;
|
|
7507
|
+
routerModuleShims
|
|
7508
|
+
} = generation;
|
|
7144
7509
|
let lastGeneratedMetrics = {
|
|
7145
7510
|
entryCount: 0,
|
|
7146
7511
|
interactiveComponentCount: 0,
|
|
@@ -7239,10 +7604,9 @@ function createBuildProcessorPlugin(options) {
|
|
|
7239
7604
|
excludedComponents,
|
|
7240
7605
|
getViewsDirAbs(),
|
|
7241
7606
|
{
|
|
7242
|
-
existingIdBehavior: existingIdBehavior ?? "
|
|
7607
|
+
existingIdBehavior: existingIdBehavior ?? "error",
|
|
7243
7608
|
testIdAttribute,
|
|
7244
7609
|
nameCollisionBehavior,
|
|
7245
|
-
missingSemanticNameBehavior,
|
|
7246
7610
|
warn: (message) => loggerRef.current.warn(message),
|
|
7247
7611
|
vueFilesPathMap,
|
|
7248
7612
|
wrapperSearchRoots: getWrapperSearchRoots()
|
|
@@ -7342,6 +7706,7 @@ function createBuildProcessorPlugin(options) {
|
|
|
7342
7706
|
customPomAttachments,
|
|
7343
7707
|
projectRoot: projectRootRef.current,
|
|
7344
7708
|
customPomDir,
|
|
7709
|
+
requireCustomPomDir,
|
|
7345
7710
|
customPomImportAliases,
|
|
7346
7711
|
customPomImportNameCollisionBehavior,
|
|
7347
7712
|
testIdAttribute,
|
|
@@ -7373,6 +7738,11 @@ function createDevProcessorPlugin(options) {
|
|
|
7373
7738
|
projectRootRef,
|
|
7374
7739
|
normalizedBasePagePath,
|
|
7375
7740
|
basePageClassPath,
|
|
7741
|
+
generation,
|
|
7742
|
+
getResolvedRouterEntry,
|
|
7743
|
+
loggerRef
|
|
7744
|
+
} = options;
|
|
7745
|
+
const {
|
|
7376
7746
|
outDir,
|
|
7377
7747
|
emitLanguages,
|
|
7378
7748
|
typescriptOutputStructure,
|
|
@@ -7380,18 +7750,16 @@ function createDevProcessorPlugin(options) {
|
|
|
7380
7750
|
generateFixtures,
|
|
7381
7751
|
customPomAttachments,
|
|
7382
7752
|
customPomDir,
|
|
7753
|
+
requireCustomPomDir,
|
|
7383
7754
|
customPomImportAliases,
|
|
7384
7755
|
customPomImportNameCollisionBehavior,
|
|
7385
|
-
nameCollisionBehavior
|
|
7386
|
-
missingSemanticNameBehavior = "error",
|
|
7756
|
+
nameCollisionBehavior,
|
|
7387
7757
|
existingIdBehavior,
|
|
7388
7758
|
testIdAttribute,
|
|
7389
7759
|
routerAwarePoms,
|
|
7390
|
-
getResolvedRouterEntry,
|
|
7391
7760
|
routerType,
|
|
7392
|
-
routerModuleShims
|
|
7393
|
-
|
|
7394
|
-
} = options;
|
|
7761
|
+
routerModuleShims
|
|
7762
|
+
} = generation;
|
|
7395
7763
|
let scheduleVueFileRegen = null;
|
|
7396
7764
|
const getProjectRootCandidates = () => Array.from(/* @__PURE__ */ new Set([
|
|
7397
7765
|
path.resolve(projectRootRef.current),
|
|
@@ -7578,9 +7946,8 @@ function createDevProcessorPlugin(options) {
|
|
|
7578
7946
|
excludedComponents,
|
|
7579
7947
|
getViewsDirAbs(),
|
|
7580
7948
|
{
|
|
7581
|
-
existingIdBehavior: existingIdBehavior ?? "
|
|
7949
|
+
existingIdBehavior: existingIdBehavior ?? "error",
|
|
7582
7950
|
nameCollisionBehavior,
|
|
7583
|
-
missingSemanticNameBehavior,
|
|
7584
7951
|
testIdAttribute,
|
|
7585
7952
|
warn: (message) => loggerRef.current.warn(message),
|
|
7586
7953
|
vueFilesPathMap: provisionalVuePathMap,
|
|
@@ -7631,6 +7998,7 @@ function createDevProcessorPlugin(options) {
|
|
|
7631
7998
|
customPomAttachments,
|
|
7632
7999
|
projectRoot: projectRootRef.current,
|
|
7633
8000
|
customPomDir,
|
|
8001
|
+
requireCustomPomDir,
|
|
7634
8002
|
customPomImportAliases,
|
|
7635
8003
|
customPomImportNameCollisionBehavior,
|
|
7636
8004
|
pageDirs: getPageDirs(),
|
|
@@ -7805,8 +8173,119 @@ function createDevProcessorPlugin(options) {
|
|
|
7805
8173
|
}
|
|
7806
8174
|
};
|
|
7807
8175
|
}
|
|
7808
|
-
function
|
|
7809
|
-
const
|
|
8176
|
+
function removeByKeySegment(value) {
|
|
8177
|
+
const idx = value.indexOf("ByKey");
|
|
8178
|
+
if (idx < 0) {
|
|
8179
|
+
return value;
|
|
8180
|
+
}
|
|
8181
|
+
return value.slice(0, idx) + value.slice(idx + "ByKey".length);
|
|
8182
|
+
}
|
|
8183
|
+
function hasRoleSuffix(baseName, roleSuffix) {
|
|
8184
|
+
if (baseName.endsWith(roleSuffix)) {
|
|
8185
|
+
return true;
|
|
8186
|
+
}
|
|
8187
|
+
const re = new RegExp(`^${roleSuffix}\\d+$`);
|
|
8188
|
+
return re.test(baseName);
|
|
8189
|
+
}
|
|
8190
|
+
function getGeneratedPropertyName(pom) {
|
|
8191
|
+
if (pom.getterNameOverride) {
|
|
8192
|
+
return pom.getterNameOverride;
|
|
8193
|
+
}
|
|
8194
|
+
const roleSuffix = upperFirst(pom.nativeRole || "Element");
|
|
8195
|
+
const baseName = upperFirst(pom.methodName);
|
|
8196
|
+
const propertyName = hasRoleSuffix(baseName, roleSuffix) ? baseName : `${baseName}${roleSuffix}`;
|
|
8197
|
+
return pom.selector.patternKind === "parameterized" ? removeByKeySegment(propertyName) : propertyName;
|
|
8198
|
+
}
|
|
8199
|
+
function getGeneratedActionName(entry, pom) {
|
|
8200
|
+
const methodNameUpper = upperFirst(pom.methodName);
|
|
8201
|
+
const radioMethodNameUpper = upperFirst(pom.methodName || "Radio");
|
|
8202
|
+
const isNavigation = !!entry.targetPageObjectModelClass;
|
|
8203
|
+
if (isNavigation) {
|
|
8204
|
+
return `goTo${methodNameUpper}`;
|
|
8205
|
+
}
|
|
8206
|
+
switch (pom.nativeRole) {
|
|
8207
|
+
case "input":
|
|
8208
|
+
return `type${methodNameUpper}`;
|
|
8209
|
+
case "select":
|
|
8210
|
+
case "vselect":
|
|
8211
|
+
return `select${methodNameUpper}`;
|
|
8212
|
+
case "radio":
|
|
8213
|
+
return `select${radioMethodNameUpper}`;
|
|
8214
|
+
default:
|
|
8215
|
+
return `click${methodNameUpper}`;
|
|
8216
|
+
}
|
|
8217
|
+
}
|
|
8218
|
+
function matchesPrimarySelector(extraMethod, pom) {
|
|
8219
|
+
if (extraMethod.selector.kind !== "testId") {
|
|
8220
|
+
return false;
|
|
8221
|
+
}
|
|
8222
|
+
return extraMethod.selector.testId.formatted === pom.selector.formatted && extraMethod.selector.testId.patternKind === pom.selector.patternKind;
|
|
8223
|
+
}
|
|
8224
|
+
function getManifestEntry(componentName, entry, componentMetadata, extraMethods) {
|
|
8225
|
+
const testId = entry.selectorValue.formatted;
|
|
8226
|
+
const metadata = componentMetadata?.get(testId);
|
|
8227
|
+
const pom = entry.pom;
|
|
8228
|
+
const generatedActionName = pom ? getGeneratedActionName(entry, pom) : null;
|
|
8229
|
+
const extraActionNames = pom ? extraMethods.filter((extraMethod) => matchesPrimarySelector(extraMethod, pom)).map((extraMethod) => extraMethod.name).sort((a, b) => a.localeCompare(b)) : [];
|
|
8230
|
+
const generatedActionNames = Array.from(/* @__PURE__ */ new Set([
|
|
8231
|
+
...generatedActionName ? [generatedActionName] : [],
|
|
8232
|
+
...extraActionNames.filter((name) => name !== generatedActionName)
|
|
8233
|
+
]));
|
|
8234
|
+
return {
|
|
8235
|
+
testId,
|
|
8236
|
+
selectorPatternKind: entry.selectorValue.patternKind,
|
|
8237
|
+
semanticName: metadata?.semanticName ?? (pom ? humanizePomMethodName(pom.methodName) : testId),
|
|
8238
|
+
locatorDescription: pom ? buildPomLocatorDescription({
|
|
8239
|
+
componentName,
|
|
8240
|
+
methodName: pom.methodName,
|
|
8241
|
+
nativeRole: pom.nativeRole
|
|
8242
|
+
}) : componentName,
|
|
8243
|
+
inferredRole: pom?.nativeRole ?? null,
|
|
8244
|
+
generatedPropertyName: pom ? getGeneratedPropertyName(pom) : null,
|
|
8245
|
+
generatedActionName,
|
|
8246
|
+
generatedActionNames,
|
|
8247
|
+
emitPrimary: pom?.emitPrimary !== false,
|
|
8248
|
+
...entry.targetPageObjectModelClass ? { targetPageObjectModelClass: entry.targetPageObjectModelClass } : {},
|
|
8249
|
+
...metadata?.tag ? { sourceTag: metadata.tag } : {},
|
|
8250
|
+
...metadata ? { sourceTagType: metadata.tagType } : {},
|
|
8251
|
+
...metadata?.patchFlag !== void 0 ? { patchFlag: metadata.patchFlag } : {},
|
|
8252
|
+
...metadata?.dynamicProps?.length ? { dynamicProps: metadata.dynamicProps } : {},
|
|
8253
|
+
...metadata?.hasClickHandler !== void 0 ? { hasClickHandler: metadata.hasClickHandler } : {},
|
|
8254
|
+
...metadata?.hasDynamicClass !== void 0 ? { hasDynamicClass: metadata.hasDynamicClass } : {},
|
|
8255
|
+
...metadata?.hasDynamicStyle !== void 0 ? { hasDynamicStyle: metadata.hasDynamicStyle } : {},
|
|
8256
|
+
...metadata?.hasDynamicText !== void 0 ? { hasDynamicText: metadata.hasDynamicText } : {}
|
|
8257
|
+
};
|
|
8258
|
+
}
|
|
8259
|
+
function buildPomManifest(componentHierarchyMap, elementMetadata) {
|
|
8260
|
+
const manifestEntries = Array.from(componentHierarchyMap.entries()).sort((a, b) => a[0].localeCompare(b[0])).map(([componentName, dependencies]) => {
|
|
8261
|
+
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 ?? []));
|
|
8262
|
+
if (!entries.length) {
|
|
8263
|
+
return null;
|
|
8264
|
+
}
|
|
8265
|
+
return [componentName, {
|
|
8266
|
+
componentName,
|
|
8267
|
+
className: componentName,
|
|
8268
|
+
sourceFile: dependencies.filePath,
|
|
8269
|
+
kind: dependencies.isView ? "view" : "component",
|
|
8270
|
+
testIds: Array.from(new Set(entries.map((entry) => entry.testId))),
|
|
8271
|
+
entries
|
|
8272
|
+
}];
|
|
8273
|
+
}).filter((entry) => entry !== null);
|
|
8274
|
+
return Object.fromEntries(manifestEntries);
|
|
8275
|
+
}
|
|
8276
|
+
function buildTestIdManifest(pomManifest) {
|
|
8277
|
+
return Object.fromEntries(
|
|
8278
|
+
Object.entries(pomManifest).map(([componentName, component]) => [componentName, Array.from(new Set(component.testIds)).sort((a, b) => a.localeCompare(b))])
|
|
8279
|
+
);
|
|
8280
|
+
}
|
|
8281
|
+
function writeConstJson(value) {
|
|
8282
|
+
return (writer) => {
|
|
8283
|
+
writer.write(`${JSON.stringify(value, null, 2)} as const`);
|
|
8284
|
+
};
|
|
8285
|
+
}
|
|
8286
|
+
function generateTestIdsModule(componentHierarchyMap, elementMetadata) {
|
|
8287
|
+
const pomManifest = buildPomManifest(componentHierarchyMap, elementMetadata);
|
|
8288
|
+
const testIdManifest = buildTestIdManifest(pomManifest);
|
|
7810
8289
|
return renderSourceFile("virtual-testids.ts", (sourceFile) => {
|
|
7811
8290
|
sourceFile.addStatements("// Virtual module: test id manifest");
|
|
7812
8291
|
sourceFile.addVariableStatement({
|
|
@@ -7814,16 +8293,15 @@ function generateTestIdsModule(componentTestIds) {
|
|
|
7814
8293
|
isExported: true,
|
|
7815
8294
|
declarations: [{
|
|
7816
8295
|
name: "testIdManifest",
|
|
7817
|
-
initializer: (
|
|
7818
|
-
|
|
7819
|
-
|
|
7820
|
-
|
|
7821
|
-
|
|
7822
|
-
|
|
7823
|
-
|
|
7824
|
-
|
|
7825
|
-
|
|
7826
|
-
}
|
|
8296
|
+
initializer: writeConstJson(testIdManifest)
|
|
8297
|
+
}]
|
|
8298
|
+
});
|
|
8299
|
+
sourceFile.addVariableStatement({
|
|
8300
|
+
declarationKind: VariableDeclarationKind.Const,
|
|
8301
|
+
isExported: true,
|
|
8302
|
+
declarations: [{
|
|
8303
|
+
name: "pomManifest",
|
|
8304
|
+
initializer: writeConstJson(pomManifest)
|
|
7827
8305
|
}]
|
|
7828
8306
|
});
|
|
7829
8307
|
sourceFile.addTypeAlias({
|
|
@@ -7836,27 +8314,67 @@ function generateTestIdsModule(componentTestIds) {
|
|
|
7836
8314
|
name: "ComponentName",
|
|
7837
8315
|
type: "keyof TestIdManifest"
|
|
7838
8316
|
});
|
|
8317
|
+
sourceFile.addTypeAlias({
|
|
8318
|
+
isExported: true,
|
|
8319
|
+
name: "PomManifest",
|
|
8320
|
+
type: "typeof pomManifest"
|
|
8321
|
+
});
|
|
8322
|
+
sourceFile.addTypeAlias({
|
|
8323
|
+
isExported: true,
|
|
8324
|
+
name: "PomManifestComponentName",
|
|
8325
|
+
type: "keyof PomManifest"
|
|
8326
|
+
});
|
|
8327
|
+
});
|
|
8328
|
+
}
|
|
8329
|
+
function generatePomManifestModule(componentHierarchyMap, elementMetadata) {
|
|
8330
|
+
const pomManifest = buildPomManifest(componentHierarchyMap, elementMetadata);
|
|
8331
|
+
return renderSourceFile("virtual-pom-manifest.ts", (sourceFile) => {
|
|
8332
|
+
sourceFile.addStatements("// Virtual module: richer POM discoverability manifest");
|
|
8333
|
+
sourceFile.addVariableStatement({
|
|
8334
|
+
declarationKind: VariableDeclarationKind.Const,
|
|
8335
|
+
isExported: true,
|
|
8336
|
+
declarations: [{
|
|
8337
|
+
name: "pomManifest",
|
|
8338
|
+
initializer: writeConstJson(pomManifest)
|
|
8339
|
+
}]
|
|
8340
|
+
});
|
|
8341
|
+
sourceFile.addTypeAlias({
|
|
8342
|
+
isExported: true,
|
|
8343
|
+
name: "PomManifest",
|
|
8344
|
+
type: "typeof pomManifest"
|
|
8345
|
+
});
|
|
8346
|
+
sourceFile.addTypeAlias({
|
|
8347
|
+
isExported: true,
|
|
8348
|
+
name: "PomManifestComponentName",
|
|
8349
|
+
type: "keyof PomManifest"
|
|
8350
|
+
});
|
|
7839
8351
|
});
|
|
7840
8352
|
}
|
|
7841
|
-
const
|
|
7842
|
-
const
|
|
7843
|
-
|
|
8353
|
+
const TEST_IDS_VIRTUAL_ID = "virtual:testids";
|
|
8354
|
+
const TEST_IDS_RESOLVED_ID = `\0${TEST_IDS_VIRTUAL_ID}`;
|
|
8355
|
+
const POM_MANIFEST_VIRTUAL_ID = "virtual:pom-manifest";
|
|
8356
|
+
const POM_MANIFEST_RESOLVED_ID = `\0${POM_MANIFEST_VIRTUAL_ID}`;
|
|
8357
|
+
function createTestIdsVirtualModulesPlugin(componentHierarchyMap, elementMetadata) {
|
|
7844
8358
|
return {
|
|
7845
8359
|
name: "vue-pom-generator:virtual-testids",
|
|
7846
8360
|
resolveId(id) {
|
|
7847
|
-
if (id ===
|
|
7848
|
-
return
|
|
8361
|
+
if (id === TEST_IDS_VIRTUAL_ID)
|
|
8362
|
+
return TEST_IDS_RESOLVED_ID;
|
|
8363
|
+
if (id === POM_MANIFEST_VIRTUAL_ID)
|
|
8364
|
+
return POM_MANIFEST_RESOLVED_ID;
|
|
7849
8365
|
},
|
|
7850
8366
|
load(id) {
|
|
7851
|
-
if (id ===
|
|
7852
|
-
return generateTestIdsModule(
|
|
8367
|
+
if (id === TEST_IDS_RESOLVED_ID)
|
|
8368
|
+
return generateTestIdsModule(componentHierarchyMap, elementMetadata);
|
|
8369
|
+
if (id === POM_MANIFEST_RESOLVED_ID)
|
|
8370
|
+
return generatePomManifestModule(componentHierarchyMap, elementMetadata);
|
|
7853
8371
|
}
|
|
7854
8372
|
};
|
|
7855
8373
|
}
|
|
7856
8374
|
function createSupportPlugins(options) {
|
|
7857
8375
|
const {
|
|
7858
|
-
componentTestIds,
|
|
7859
8376
|
componentHierarchyMap,
|
|
8377
|
+
elementMetadata,
|
|
7860
8378
|
vueFilesPathMap,
|
|
7861
8379
|
nativeWrappers,
|
|
7862
8380
|
excludedComponents,
|
|
@@ -7866,27 +8384,30 @@ function createSupportPlugins(options) {
|
|
|
7866
8384
|
getViewsDir,
|
|
7867
8385
|
getSourceDirs,
|
|
7868
8386
|
getWrapperSearchRoots,
|
|
7869
|
-
|
|
7870
|
-
|
|
7871
|
-
|
|
8387
|
+
generation,
|
|
8388
|
+
projectRootRef,
|
|
8389
|
+
basePageClassPath: basePageClassPathOverride,
|
|
8390
|
+
loggerRef
|
|
8391
|
+
} = options;
|
|
8392
|
+
const {
|
|
7872
8393
|
outDir,
|
|
7873
8394
|
emitLanguages,
|
|
7874
8395
|
typescriptOutputStructure,
|
|
7875
8396
|
csharp,
|
|
7876
|
-
routerAwarePoms,
|
|
7877
|
-
routerEntry,
|
|
7878
|
-
routerType,
|
|
7879
|
-
routerModuleShims,
|
|
7880
8397
|
generateFixtures,
|
|
7881
8398
|
customPomAttachments,
|
|
7882
|
-
projectRootRef,
|
|
7883
|
-
basePageClassPath: basePageClassPathOverride,
|
|
7884
8399
|
customPomDir,
|
|
8400
|
+
requireCustomPomDir,
|
|
7885
8401
|
customPomImportAliases,
|
|
7886
8402
|
customPomImportNameCollisionBehavior,
|
|
8403
|
+
nameCollisionBehavior,
|
|
8404
|
+
existingIdBehavior,
|
|
7887
8405
|
testIdAttribute,
|
|
7888
|
-
|
|
7889
|
-
|
|
8406
|
+
routerAwarePoms,
|
|
8407
|
+
routerEntry,
|
|
8408
|
+
routerType,
|
|
8409
|
+
routerModuleShims
|
|
8410
|
+
} = generation;
|
|
7890
8411
|
const resolveRouterEntry2 = () => {
|
|
7891
8412
|
if (!routerAwarePoms)
|
|
7892
8413
|
return void 0;
|
|
@@ -7915,27 +8436,12 @@ function createSupportPlugins(options) {
|
|
|
7915
8436
|
getSourceDirs,
|
|
7916
8437
|
basePageClassPath,
|
|
7917
8438
|
normalizedBasePagePath,
|
|
7918
|
-
|
|
7919
|
-
emitLanguages,
|
|
7920
|
-
typescriptOutputStructure,
|
|
7921
|
-
csharp,
|
|
7922
|
-
generateFixtures,
|
|
7923
|
-
customPomAttachments,
|
|
8439
|
+
generation,
|
|
7924
8440
|
projectRootRef,
|
|
7925
|
-
customPomDir,
|
|
7926
|
-
customPomImportAliases,
|
|
7927
|
-
customPomImportNameCollisionBehavior,
|
|
7928
|
-
testIdAttribute,
|
|
7929
|
-
nameCollisionBehavior,
|
|
7930
|
-
missingSemanticNameBehavior,
|
|
7931
|
-
existingIdBehavior,
|
|
7932
8441
|
nativeWrappers,
|
|
7933
8442
|
excludedComponents,
|
|
7934
8443
|
getWrapperSearchRoots,
|
|
7935
|
-
routerAwarePoms,
|
|
7936
|
-
routerType,
|
|
7937
8444
|
getResolvedRouterEntry: resolveRouterEntry2,
|
|
7938
|
-
routerModuleShims,
|
|
7939
8445
|
loggerRef
|
|
7940
8446
|
});
|
|
7941
8447
|
const devProcessor = createDevProcessorPlugin({
|
|
@@ -7950,26 +8456,11 @@ function createSupportPlugins(options) {
|
|
|
7950
8456
|
projectRootRef,
|
|
7951
8457
|
normalizedBasePagePath,
|
|
7952
8458
|
basePageClassPath,
|
|
7953
|
-
|
|
7954
|
-
emitLanguages,
|
|
7955
|
-
typescriptOutputStructure,
|
|
7956
|
-
csharp,
|
|
7957
|
-
generateFixtures,
|
|
7958
|
-
customPomAttachments,
|
|
7959
|
-
customPomDir,
|
|
7960
|
-
customPomImportAliases,
|
|
7961
|
-
customPomImportNameCollisionBehavior,
|
|
7962
|
-
nameCollisionBehavior,
|
|
7963
|
-
missingSemanticNameBehavior,
|
|
7964
|
-
existingIdBehavior,
|
|
7965
|
-
testIdAttribute,
|
|
7966
|
-
routerAwarePoms,
|
|
7967
|
-
routerType,
|
|
8459
|
+
generation,
|
|
7968
8460
|
getResolvedRouterEntry: resolveRouterEntry2,
|
|
7969
|
-
routerModuleShims,
|
|
7970
8461
|
loggerRef
|
|
7971
8462
|
});
|
|
7972
|
-
const virtualModules = createTestIdsVirtualModulesPlugin(
|
|
8463
|
+
const virtualModules = createTestIdsVirtualModulesPlugin(componentHierarchyMap, elementMetadata);
|
|
7973
8464
|
return [tsProcessor, devProcessor, virtualModules];
|
|
7974
8465
|
}
|
|
7975
8466
|
function findDataTestIdProp(element, attributeName = "data-testid") {
|
|
@@ -8303,8 +8794,7 @@ function createVuePluginWithTestIds(options) {
|
|
|
8303
8794
|
});
|
|
8304
8795
|
const api = viteVuePlugin?.api;
|
|
8305
8796
|
if (!api) {
|
|
8306
|
-
|
|
8307
|
-
return;
|
|
8797
|
+
throw new Error("[vue-pom-generator] Nuxt bridge could not find vite:vue plugin to patch.");
|
|
8308
8798
|
}
|
|
8309
8799
|
const currentOptions = api.options ?? {};
|
|
8310
8800
|
const currentTemplate = currentOptions.template ?? {};
|
|
@@ -8370,33 +8860,6 @@ function assertOneOf(value, allowed, name) {
|
|
|
8370
8860
|
}
|
|
8371
8861
|
throw new TypeError(`${name} must be one of: ${allowed.join(", ")}.`);
|
|
8372
8862
|
}
|
|
8373
|
-
function assertErrorBehavior(value, name) {
|
|
8374
|
-
if (!value) {
|
|
8375
|
-
return;
|
|
8376
|
-
}
|
|
8377
|
-
if (value === "ignore" || value === "error") {
|
|
8378
|
-
return;
|
|
8379
|
-
}
|
|
8380
|
-
if (typeof value !== "object" || Array.isArray(value)) {
|
|
8381
|
-
throw new TypeError(`${name} must be "ignore", "error", or an object.`);
|
|
8382
|
-
}
|
|
8383
|
-
const supportedKeys = /* @__PURE__ */ new Set(["missingSemanticNameBehavior"]);
|
|
8384
|
-
for (const key of Object.keys(value)) {
|
|
8385
|
-
if (!supportedKeys.has(key)) {
|
|
8386
|
-
throw new TypeError(`${name} contains unsupported key "${key}".`);
|
|
8387
|
-
}
|
|
8388
|
-
}
|
|
8389
|
-
assertOneOf(value.missingSemanticNameBehavior, ["ignore", "error"], `${name}.missingSemanticNameBehavior`);
|
|
8390
|
-
}
|
|
8391
|
-
function resolveMissingSemanticNameBehavior(value) {
|
|
8392
|
-
if (!value) {
|
|
8393
|
-
return "error";
|
|
8394
|
-
}
|
|
8395
|
-
if (value === "ignore" || value === "error") {
|
|
8396
|
-
return value;
|
|
8397
|
-
}
|
|
8398
|
-
return value.missingSemanticNameBehavior ?? "error";
|
|
8399
|
-
}
|
|
8400
8863
|
function readPackageJson(projectRoot) {
|
|
8401
8864
|
const packageJsonPath = path.join(projectRoot, "package.json");
|
|
8402
8865
|
if (!fs.existsSync(packageJsonPath)) {
|
|
@@ -8487,7 +8950,6 @@ function getSharedGeneratorState(key) {
|
|
|
8487
8950
|
let state = sharedGeneratorStateRegistry.get(key);
|
|
8488
8951
|
if (!state) {
|
|
8489
8952
|
state = {
|
|
8490
|
-
componentTestIds: /* @__PURE__ */ new Map(),
|
|
8491
8953
|
elementMetadata: /* @__PURE__ */ new Map(),
|
|
8492
8954
|
semanticNameMap: /* @__PURE__ */ new Map(),
|
|
8493
8955
|
componentHierarchyMap: /* @__PURE__ */ new Map(),
|
|
@@ -8505,7 +8967,7 @@ function applyTemplateCompilerOptionsToResolvedVuePlugin(config, templateCompile
|
|
|
8505
8967
|
'[vue-pom-generator] vuePluginOwnership="external" requires the resolved Vite Vue plugin, but none was found. Add vue() to your Vite plugins before spreading createVuePomGeneratorPlugins(...).'
|
|
8506
8968
|
);
|
|
8507
8969
|
}
|
|
8508
|
-
throw new Error("[vue-pom-generator] Nuxt
|
|
8970
|
+
throw new Error("[vue-pom-generator] Nuxt bridge could not find vite:vue plugin to patch.");
|
|
8509
8971
|
}
|
|
8510
8972
|
const currentOptions = viteVuePlugin.api.options ?? {};
|
|
8511
8973
|
const currentTemplate = currentOptions.template ?? {};
|
|
@@ -8562,18 +9024,23 @@ function createVuePomGeneratorPlugins(options = {}) {
|
|
|
8562
9024
|
const vueGenerationOptions = generationOptions;
|
|
8563
9025
|
const verbosity = options.logging?.verbosity ?? "warn";
|
|
8564
9026
|
const vueOptions = options.vueOptions;
|
|
8565
|
-
const
|
|
8566
|
-
|
|
8567
|
-
|
|
8568
|
-
|
|
8569
|
-
|
|
8570
|
-
|
|
8571
|
-
|
|
8572
|
-
|
|
8573
|
-
|
|
8574
|
-
|
|
8575
|
-
|
|
8576
|
-
|
|
9027
|
+
const resolvedInjectionOptionsRef = {
|
|
9028
|
+
current: resolveInjectionSupportOptions({
|
|
9029
|
+
isNuxt,
|
|
9030
|
+
viewsDir: injection.viewsDir,
|
|
9031
|
+
componentDirs: injection.componentDirs,
|
|
9032
|
+
layoutDirs: injection.layoutDirs,
|
|
9033
|
+
wrapperSearchRoots: injection.wrapperSearchRoots,
|
|
9034
|
+
nativeWrappers: injection.nativeWrappers,
|
|
9035
|
+
excludedComponents: injection.excludeComponents,
|
|
9036
|
+
existingIdBehavior: injection.existingIdBehavior,
|
|
9037
|
+
testIdAttribute: injection.attribute
|
|
9038
|
+
})
|
|
9039
|
+
};
|
|
9040
|
+
const resolvedInjectionOptions = resolvedInjectionOptionsRef.current;
|
|
9041
|
+
const nativeWrappers = resolvedInjectionOptions.nativeWrappers;
|
|
9042
|
+
const excludedComponents = resolvedInjectionOptions.excludedComponents;
|
|
9043
|
+
const testIdAttribute = resolvedInjectionOptions.testIdAttribute;
|
|
8577
9044
|
const routerEntry = !isNuxt ? vueGenerationOptions?.router?.entry : void 0;
|
|
8578
9045
|
const routerType = isNuxt ? "nuxt" : vueGenerationOptions?.router?.type ?? "vue-router";
|
|
8579
9046
|
const routerModuleShims = !isNuxt ? vueGenerationOptions?.router?.moduleShims : void 0;
|
|
@@ -8582,27 +9049,41 @@ function createVuePomGeneratorPlugins(options = {}) {
|
|
|
8582
9049
|
}
|
|
8583
9050
|
const vuePluginOwnership = isNuxt ? "external" : options.vuePluginOwnership ?? "internal";
|
|
8584
9051
|
const usesExternalVuePlugin = vuePluginOwnership === "external";
|
|
8585
|
-
const csharp = generationOptions?.csharp;
|
|
8586
|
-
const errorBehavior = options.errorBehavior;
|
|
8587
|
-
const missingSemanticNameBehavior = resolveMissingSemanticNameBehavior(errorBehavior);
|
|
8588
|
-
const typescriptOutputStructure = generationOptions?.playwright?.outputStructure ?? "aggregated";
|
|
8589
9052
|
const generateFixtures = generationOptions?.playwright?.fixtures;
|
|
8590
9053
|
const customPoms = generationOptions?.playwright?.customPoms;
|
|
8591
9054
|
const resolvedCustomPomAttachments = customPoms?.attachments ?? [];
|
|
8592
|
-
const resolvedCustomPomDir = customPoms?.dir ?? "tests/playwright/pom/custom";
|
|
8593
9055
|
const resolvedCustomPomImportAliases = customPoms?.importAliases;
|
|
8594
|
-
const
|
|
9056
|
+
const requireCustomPomDir = customPoms?.dir !== void 0 || resolvedCustomPomAttachments.length > 0 || Object.keys(resolvedCustomPomImportAliases ?? {}).length > 0;
|
|
9057
|
+
const resolvedGenerationOptions = resolveGenerationSupportOptions({
|
|
9058
|
+
outDir: generationOptions?.outDir,
|
|
9059
|
+
emitLanguages: generationOptions?.emit,
|
|
9060
|
+
typescriptOutputStructure: generationOptions?.playwright?.outputStructure,
|
|
9061
|
+
csharp: generationOptions?.csharp,
|
|
9062
|
+
generateFixtures,
|
|
9063
|
+
customPomAttachments: resolvedCustomPomAttachments,
|
|
9064
|
+
customPomDir: customPoms?.dir,
|
|
9065
|
+
requireCustomPomDir,
|
|
9066
|
+
customPomImportAliases: resolvedCustomPomImportAliases,
|
|
9067
|
+
customPomImportNameCollisionBehavior: customPoms?.importNameCollisionBehavior,
|
|
9068
|
+
nameCollisionBehavior: generationOptions?.nameCollisionBehavior,
|
|
9069
|
+
existingIdBehavior: resolvedInjectionOptions.existingIdBehavior,
|
|
9070
|
+
testIdAttribute,
|
|
9071
|
+
routerAwarePoms: typeof routerEntry === "string" && routerEntry.trim().length > 0 || routerType === "nuxt",
|
|
9072
|
+
routerEntry,
|
|
9073
|
+
routerType,
|
|
9074
|
+
routerModuleShims
|
|
9075
|
+
});
|
|
8595
9076
|
const basePageClassPathOverride = generationOptions?.basePageClassPath;
|
|
8596
|
-
const getPageDirs = () =>
|
|
9077
|
+
const getPageDirs = () => resolvedInjectionOptionsRef.current.pageDirs;
|
|
8597
9078
|
const getViewsDir = () => getPageDirs()[0] ?? "src/views";
|
|
8598
|
-
const getComponentDirs = () =>
|
|
8599
|
-
const getLayoutDirs = () =>
|
|
9079
|
+
const getComponentDirs = () => resolvedInjectionOptionsRef.current.componentDirs;
|
|
9080
|
+
const getLayoutDirs = () => resolvedInjectionOptionsRef.current.layoutDirs;
|
|
8600
9081
|
const getSourceDirs = () => Array.from(/* @__PURE__ */ new Set([
|
|
8601
9082
|
...getPageDirs(),
|
|
8602
9083
|
...getComponentDirs(),
|
|
8603
9084
|
...getLayoutDirs()
|
|
8604
9085
|
]));
|
|
8605
|
-
const getWrapperSearchRoots = () =>
|
|
9086
|
+
const getWrapperSearchRoots = () => resolvedInjectionOptionsRef.current.wrapperSearchRoots;
|
|
8606
9087
|
const sharedStateKey = JSON.stringify({
|
|
8607
9088
|
cwd: process.cwd(),
|
|
8608
9089
|
mode: isNuxt ? "nuxt" : "vue",
|
|
@@ -8610,9 +9091,9 @@ function createVuePomGeneratorPlugins(options = {}) {
|
|
|
8610
9091
|
componentDirs: isNuxt ? null : getComponentDirs(),
|
|
8611
9092
|
layoutDirs: isNuxt ? null : getLayoutDirs(),
|
|
8612
9093
|
wrapperSearchRoots: isNuxt ? null : getWrapperSearchRoots(),
|
|
8613
|
-
outDir,
|
|
9094
|
+
outDir: resolvedGenerationOptions.outDir,
|
|
8614
9095
|
testIdAttribute,
|
|
8615
|
-
routerType,
|
|
9096
|
+
routerType: resolvedGenerationOptions.routerType,
|
|
8616
9097
|
vuePluginOwnership
|
|
8617
9098
|
});
|
|
8618
9099
|
const sharedState = getSharedGeneratorState(sharedStateKey);
|
|
@@ -8633,23 +9114,22 @@ function createVuePomGeneratorPlugins(options = {}) {
|
|
|
8633
9114
|
if (isNuxt) {
|
|
8634
9115
|
const nuxtDiscovery = await loadNuxtProjectDiscovery(process.cwd());
|
|
8635
9116
|
projectRootRef.current = nuxtDiscovery.rootDir;
|
|
8636
|
-
|
|
8637
|
-
|
|
8638
|
-
|
|
8639
|
-
|
|
9117
|
+
resolvedInjectionOptionsRef.current = applyNuxtDiscoveryToInjectionOptions(
|
|
9118
|
+
resolvedInjectionOptionsRef.current,
|
|
9119
|
+
nuxtDiscovery
|
|
9120
|
+
);
|
|
8640
9121
|
}
|
|
8641
9122
|
assertNonEmptyString(testIdAttribute, "[vue-pom-generator] injection.attribute");
|
|
8642
9123
|
assertNonEmptyString(getViewsDir(), "[vue-pom-generator] injection.viewsDir");
|
|
8643
9124
|
assertNonEmptyStringArray(getComponentDirs(), "[vue-pom-generator] injection.componentDirs");
|
|
8644
9125
|
assertNonEmptyStringArray(getLayoutDirs(), "[vue-pom-generator] injection.layoutDirs");
|
|
8645
9126
|
assertNonEmptyStringArray(getWrapperSearchRoots(), "[vue-pom-generator] injection.wrapperSearchRoots");
|
|
8646
|
-
assertErrorBehavior(errorBehavior, "[vue-pom-generator] errorBehavior");
|
|
8647
9127
|
if (generationEnabled) {
|
|
8648
|
-
assertNonEmptyString(outDir, "[vue-pom-generator] generation.outDir");
|
|
8649
|
-
assertOneOf(typescriptOutputStructure, ["aggregated", "split"], "[vue-pom-generator] generation.playwright.outputStructure");
|
|
8650
|
-
assertRouterModuleShims(routerModuleShims, "[vue-pom-generator] generation.router.moduleShims");
|
|
8651
|
-
if (!isNuxt && vueGenerationOptions?.router && routerType === "vue-router") {
|
|
8652
|
-
assertNonEmptyString(routerEntry, "[vue-pom-generator] generation.router.entry");
|
|
9128
|
+
assertNonEmptyString(resolvedGenerationOptions.outDir, "[vue-pom-generator] generation.outDir");
|
|
9129
|
+
assertOneOf(resolvedGenerationOptions.typescriptOutputStructure, ["aggregated", "split"], "[vue-pom-generator] generation.playwright.outputStructure");
|
|
9130
|
+
assertRouterModuleShims(resolvedGenerationOptions.routerModuleShims, "[vue-pom-generator] generation.router.moduleShims");
|
|
9131
|
+
if (!isNuxt && vueGenerationOptions?.router && resolvedGenerationOptions.routerType === "vue-router") {
|
|
9132
|
+
assertNonEmptyString(resolvedGenerationOptions.routerEntry, "[vue-pom-generator] generation.router.entry");
|
|
8653
9133
|
}
|
|
8654
9134
|
}
|
|
8655
9135
|
if (usesExternalVuePlugin) {
|
|
@@ -8668,11 +9148,11 @@ function createVuePomGeneratorPlugins(options = {}) {
|
|
|
8668
9148
|
};
|
|
8669
9149
|
const getViewsDirAbs = () => resolveFromProjectRoot(projectRootRef.current, getViewsDir());
|
|
8670
9150
|
const getWrapperSearchRootsAbs = () => getWrapperSearchRoots().map((root) => resolveFromProjectRoot(projectRootRef.current, root));
|
|
8671
|
-
const {
|
|
9151
|
+
const { elementMetadata, semanticNameMap, componentHierarchyMap, vueFilesPathMap } = sharedState;
|
|
8672
9152
|
const { metadataCollectorPlugin, internalVuePlugin, templateCompilerOptions } = createVuePluginWithTestIds({
|
|
8673
9153
|
vueOptions,
|
|
8674
|
-
existingIdBehavior,
|
|
8675
|
-
nameCollisionBehavior,
|
|
9154
|
+
existingIdBehavior: resolvedGenerationOptions.existingIdBehavior,
|
|
9155
|
+
nameCollisionBehavior: resolvedGenerationOptions.nameCollisionBehavior,
|
|
8676
9156
|
nativeWrappers,
|
|
8677
9157
|
elementMetadata,
|
|
8678
9158
|
semanticNameMap,
|
|
@@ -8687,10 +9167,9 @@ function createVuePomGeneratorPlugins(options = {}) {
|
|
|
8687
9167
|
getProjectRoot: () => projectRootRef.current
|
|
8688
9168
|
});
|
|
8689
9169
|
templateCompilerOptionsForResolvedPlugin = templateCompilerOptions;
|
|
8690
|
-
const routerAwarePoms = typeof routerEntry === "string" && routerEntry.trim().length > 0 || routerType === "nuxt";
|
|
8691
9170
|
const supportPlugins = createSupportPlugins({
|
|
8692
|
-
componentTestIds,
|
|
8693
9171
|
componentHierarchyMap,
|
|
9172
|
+
elementMetadata,
|
|
8694
9173
|
vueFilesPathMap,
|
|
8695
9174
|
nativeWrappers,
|
|
8696
9175
|
excludedComponents,
|
|
@@ -8700,26 +9179,10 @@ function createVuePomGeneratorPlugins(options = {}) {
|
|
|
8700
9179
|
getViewsDir,
|
|
8701
9180
|
getSourceDirs,
|
|
8702
9181
|
getWrapperSearchRoots: getWrapperSearchRootsAbs,
|
|
8703
|
-
|
|
8704
|
-
missingSemanticNameBehavior,
|
|
8705
|
-
existingIdBehavior,
|
|
8706
|
-
outDir,
|
|
8707
|
-
emitLanguages,
|
|
8708
|
-
typescriptOutputStructure,
|
|
8709
|
-
csharp,
|
|
8710
|
-
routerAwarePoms,
|
|
8711
|
-
routerEntry,
|
|
8712
|
-
generateFixtures,
|
|
9182
|
+
generation: resolvedGenerationOptions,
|
|
8713
9183
|
projectRootRef,
|
|
8714
9184
|
basePageClassPath: basePageClassPathOverride,
|
|
8715
|
-
|
|
8716
|
-
customPomDir: resolvedCustomPomDir,
|
|
8717
|
-
customPomImportAliases: resolvedCustomPomImportAliases,
|
|
8718
|
-
customPomImportNameCollisionBehavior: resolvedCustomPomImportCollisionBehavior,
|
|
8719
|
-
testIdAttribute,
|
|
8720
|
-
loggerRef,
|
|
8721
|
-
routerType,
|
|
8722
|
-
routerModuleShims
|
|
9185
|
+
loggerRef
|
|
8723
9186
|
});
|
|
8724
9187
|
if (isNuxt) {
|
|
8725
9188
|
loggerRef.current.info("Nuxt environment detected. Skipping internal @vitejs/plugin-vue to avoid conflicts.");
|
|
@@ -8733,7 +9196,7 @@ function createVuePomGeneratorPlugins(options = {}) {
|
|
|
8733
9196
|
...supportPlugins
|
|
8734
9197
|
];
|
|
8735
9198
|
if (!generationEnabled) {
|
|
8736
|
-
const virtualModules = createTestIdsVirtualModulesPlugin(
|
|
9199
|
+
const virtualModules = createTestIdsVirtualModulesPlugin(componentHierarchyMap, elementMetadata);
|
|
8737
9200
|
return [
|
|
8738
9201
|
configPlugin,
|
|
8739
9202
|
metadataCollectorPlugin,
|