@immense/vue-pom-generator 1.0.57 → 1.0.59
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -19
- package/RELEASE_NOTES.md +76 -13
- package/class-generation/base-page.ts +6 -13
- package/class-generation/index.ts +226 -317
- package/class-generation/playwright-types.ts +1 -1
- package/click-instrumentation.ts +0 -4
- package/dist/class-generation/base-page.d.ts +1 -0
- 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 +1283 -1019
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +1285 -1021
- package/dist/index.mjs.map +1 -1
- package/dist/method-generation.d.ts +4 -2
- package/dist/method-generation.d.ts.map +1 -1
- package/dist/playwright.config.d.ts.map +1 -1
- package/dist/plugin/create-vue-pom-generator-plugins.d.ts.map +1 -1
- package/dist/plugin/nuxt-discovery.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-plugins.d.ts +2 -32
- 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-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/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]";
|
|
@@ -63,6 +63,21 @@ function createLogger(options) {
|
|
|
63
63
|
};
|
|
64
64
|
}
|
|
65
65
|
const requireFromModule = createRequire(import.meta.url);
|
|
66
|
+
function resolveNuxtKitEntry(cwd) {
|
|
67
|
+
const attemptResolvers = [
|
|
68
|
+
createRequire(path.resolve(cwd, "package.json")),
|
|
69
|
+
requireFromModule
|
|
70
|
+
];
|
|
71
|
+
let lastError;
|
|
72
|
+
for (const resolver of attemptResolvers) {
|
|
73
|
+
try {
|
|
74
|
+
return resolver.resolve("@nuxt/kit");
|
|
75
|
+
} catch (error) {
|
|
76
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
throw lastError ?? new Error("Unknown module resolution error");
|
|
80
|
+
}
|
|
66
81
|
function toUniqueResolvedPaths(paths) {
|
|
67
82
|
return Array.from(new Set(paths.map((value) => path.resolve(value))));
|
|
68
83
|
}
|
|
@@ -173,7 +188,7 @@ async function loadNuxtProjectDiscovery(cwd = process.cwd()) {
|
|
|
173
188
|
let loadNuxtConfig;
|
|
174
189
|
let getLayerDirectories;
|
|
175
190
|
try {
|
|
176
|
-
const nuxtKitEntry =
|
|
191
|
+
const nuxtKitEntry = resolveNuxtKitEntry(cwd);
|
|
177
192
|
({ loadNuxtConfig, getLayerDirectories } = await import(pathToFileURL(nuxtKitEntry).href));
|
|
178
193
|
} catch (error) {
|
|
179
194
|
throw new TypeError(
|
|
@@ -189,6 +204,49 @@ async function loadNuxtProjectDiscovery(cwd = process.cwd()) {
|
|
|
189
204
|
const nuxtOptions = await loadNuxtConfig({ cwd });
|
|
190
205
|
return resolveNuxtProjectDiscovery(nuxtOptions, getLayerDirectories, cwd);
|
|
191
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
|
+
}
|
|
192
250
|
function createTypeScriptProject() {
|
|
193
251
|
return new Project({
|
|
194
252
|
useInMemoryFileSystem: true,
|
|
@@ -280,16 +338,7 @@ function createClassConstructor(constructorDeclaration) {
|
|
|
280
338
|
...constructorDeclaration
|
|
281
339
|
};
|
|
282
340
|
}
|
|
283
|
-
function
|
|
284
|
-
if (!value) {
|
|
285
|
-
return value;
|
|
286
|
-
}
|
|
287
|
-
return value.charAt(0).toUpperCase() + value.slice(1);
|
|
288
|
-
}
|
|
289
|
-
function hasParam(params, name) {
|
|
290
|
-
return Object.prototype.hasOwnProperty.call(params, name);
|
|
291
|
-
}
|
|
292
|
-
function splitTypeAndInitializer(typeExpression) {
|
|
341
|
+
function splitPomParameterTypeExpression(typeExpression) {
|
|
293
342
|
const trimmed = typeExpression.trim();
|
|
294
343
|
const initializerIndex = trimmed.lastIndexOf("=");
|
|
295
344
|
if (initializerIndex < 0) {
|
|
@@ -300,49 +349,237 @@ function splitTypeAndInitializer(typeExpression) {
|
|
|
300
349
|
initializer: trimmed.slice(initializerIndex + 1).trim()
|
|
301
350
|
};
|
|
302
351
|
}
|
|
303
|
-
function
|
|
304
|
-
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 };
|
|
305
355
|
return {
|
|
306
356
|
name,
|
|
307
|
-
|
|
308
|
-
|
|
357
|
+
typeExpression: normalizedTypeExpression,
|
|
358
|
+
type,
|
|
359
|
+
initializer: options.initializer ?? initializer,
|
|
360
|
+
hasQuestionToken: options.hasQuestionToken,
|
|
361
|
+
isRestParameter: options.isRestParameter
|
|
309
362
|
};
|
|
310
363
|
}
|
|
311
|
-
function
|
|
312
|
-
|
|
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
|
+
}));
|
|
313
373
|
}
|
|
314
|
-
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) {
|
|
315
410
|
return {
|
|
316
|
-
|
|
317
|
-
type: options.type,
|
|
318
|
-
initializer: options.initializer
|
|
411
|
+
parameters: normalizePomParameters(parameters)
|
|
319
412
|
};
|
|
320
413
|
}
|
|
321
|
-
function
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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;
|
|
325
422
|
}
|
|
326
|
-
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";
|
|
430
|
+
}
|
|
431
|
+
function getTemplateVariables(formatted) {
|
|
432
|
+
const out = [];
|
|
433
|
+
const seen = /* @__PURE__ */ new Set();
|
|
434
|
+
const matches = formatted.matchAll(/\$\{(\w+)\}/g);
|
|
435
|
+
for (const match of matches) {
|
|
436
|
+
const variableName = match[1];
|
|
437
|
+
if (seen.has(variableName)) {
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
440
|
+
seen.add(variableName);
|
|
441
|
+
out.push(variableName);
|
|
442
|
+
}
|
|
443
|
+
return out;
|
|
327
444
|
}
|
|
328
|
-
function
|
|
445
|
+
function createPomStringPattern(formatted, patternKind) {
|
|
446
|
+
return {
|
|
447
|
+
formatted,
|
|
448
|
+
patternKind,
|
|
449
|
+
templateVariables: getTemplateVariables(formatted)
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
function getPomPatternVariables(patterns, options = {}) {
|
|
329
453
|
const out = [];
|
|
330
454
|
const seen = /* @__PURE__ */ new Set();
|
|
331
|
-
|
|
332
|
-
for (const
|
|
333
|
-
|
|
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);
|
|
334
477
|
continue;
|
|
335
478
|
}
|
|
336
|
-
|
|
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)) {
|
|
337
490
|
continue;
|
|
338
491
|
}
|
|
339
|
-
seen.add(
|
|
340
|
-
|
|
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);
|
|
341
558
|
}
|
|
342
559
|
return out;
|
|
343
560
|
}
|
|
344
|
-
function
|
|
345
|
-
|
|
561
|
+
function upperFirst$1(value) {
|
|
562
|
+
if (!value) {
|
|
563
|
+
return value;
|
|
564
|
+
}
|
|
565
|
+
return value.charAt(0).toUpperCase() + value.slice(1);
|
|
566
|
+
}
|
|
567
|
+
function createParameters(params) {
|
|
568
|
+
return toTypeScriptPomParameterStructures(params);
|
|
569
|
+
}
|
|
570
|
+
function createInlineParameter(name, options = {}) {
|
|
571
|
+
return {
|
|
572
|
+
name,
|
|
573
|
+
type: options.type,
|
|
574
|
+
initializer: options.initializer
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
function removeByKeySegment(value) {
|
|
578
|
+
const idx = value.lastIndexOf("ByKey");
|
|
579
|
+
if (idx < 0) {
|
|
580
|
+
return value;
|
|
581
|
+
}
|
|
582
|
+
return value.slice(0, idx) + value.slice(idx + "ByKey".length);
|
|
346
583
|
}
|
|
347
584
|
function createAsyncMethod(name, parameters, statements) {
|
|
348
585
|
return createClassMethod({
|
|
@@ -352,17 +589,27 @@ function createAsyncMethod(name, parameters, statements) {
|
|
|
352
589
|
statements
|
|
353
590
|
});
|
|
354
591
|
}
|
|
355
|
-
function generateClickMethod(methodName,
|
|
592
|
+
function generateClickMethod(methodName, selector, alternateSelectors, parameters) {
|
|
356
593
|
const name = `click${methodName}`;
|
|
357
594
|
const noWaitName = `${name}NoWait`;
|
|
358
|
-
const
|
|
359
|
-
const
|
|
360
|
-
const
|
|
595
|
+
const selectorParams = orderPomPatternParameters(parameters, [selector]);
|
|
596
|
+
const hasSelectorVariables = hasPomPatternVariables(selector);
|
|
597
|
+
const baseParameters = createParameters(selectorParams);
|
|
598
|
+
const argsForForward = getPomParameterNames(selectorParams).join(", ");
|
|
599
|
+
const alternates = uniquePomStringPatterns(selector, alternateSelectors).slice(1);
|
|
600
|
+
const primaryTestIdExpr = toTypeScriptPomPatternExpression(selector);
|
|
361
601
|
if (alternates.length > 0) {
|
|
362
|
-
const candidatesExpr = [
|
|
602
|
+
const candidatesExpr = [primaryTestIdExpr, ...alternates.map((id) => toTypeScriptPomPatternExpression(id))].join(", ");
|
|
363
603
|
const clickMethod = createAsyncMethod(
|
|
364
604
|
name,
|
|
365
|
-
|
|
605
|
+
hasSelectorVariables ? [
|
|
606
|
+
...baseParameters,
|
|
607
|
+
createInlineParameter("wait", { type: "boolean", initializer: "true" }),
|
|
608
|
+
createInlineParameter("annotationText", { type: "string", initializer: '""' })
|
|
609
|
+
] : [
|
|
610
|
+
createInlineParameter("wait", { type: "boolean", initializer: "true" }),
|
|
611
|
+
createInlineParameter("annotationText", { type: "string", initializer: '""' })
|
|
612
|
+
],
|
|
366
613
|
(writer) => {
|
|
367
614
|
writer.writeLine(`const candidates = [${candidatesExpr}] as const;`);
|
|
368
615
|
writer.writeLine("let lastError: unknown;");
|
|
@@ -370,7 +617,7 @@ function generateClickMethod(methodName, formattedDataTestId, alternateFormatted
|
|
|
370
617
|
writer.writeLine("const locator = this.locatorByTestId(testId);");
|
|
371
618
|
writer.write("try ").block(() => {
|
|
372
619
|
writer.write("if (await locator.count()) ").block(() => {
|
|
373
|
-
writer.writeLine(
|
|
620
|
+
writer.writeLine("await this.clickLocator(locator, annotationText, wait);");
|
|
374
621
|
writer.writeLine("return;");
|
|
375
622
|
});
|
|
376
623
|
});
|
|
@@ -381,60 +628,77 @@ function generateClickMethod(methodName, formattedDataTestId, alternateFormatted
|
|
|
381
628
|
writer.writeLine(`throw (lastError instanceof Error) ? lastError : new Error("[pom] Failed to click any candidate locator for ${name}.");`);
|
|
382
629
|
}
|
|
383
630
|
);
|
|
384
|
-
const noWaitArgs = argsForForward ? `${argsForForward}, false` : "false";
|
|
631
|
+
const noWaitArgs = argsForForward ? `${argsForForward}, false, annotationText` : "false, annotationText";
|
|
385
632
|
const noWaitMethod = createAsyncMethod(
|
|
386
633
|
noWaitName,
|
|
387
|
-
|
|
634
|
+
hasSelectorVariables ? [...baseParameters, createInlineParameter("annotationText", { type: "string", initializer: '""' })] : [createInlineParameter("annotationText", { type: "string", initializer: '""' })],
|
|
388
635
|
(writer) => {
|
|
389
636
|
writer.writeLine(`await this.${name}(${noWaitArgs});`);
|
|
390
637
|
}
|
|
391
638
|
);
|
|
392
639
|
return [clickMethod, noWaitMethod];
|
|
393
640
|
}
|
|
394
|
-
if (
|
|
641
|
+
if (hasSelectorVariables) {
|
|
395
642
|
return [
|
|
396
|
-
createAsyncMethod(
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
643
|
+
createAsyncMethod(
|
|
644
|
+
name,
|
|
645
|
+
[
|
|
646
|
+
...baseParameters,
|
|
647
|
+
createInlineParameter("wait", { type: "boolean", initializer: "true" }),
|
|
648
|
+
createInlineParameter("annotationText", { type: "string", initializer: '""' })
|
|
649
|
+
],
|
|
650
|
+
(writer) => {
|
|
651
|
+
writer.writeLine(`await this.clickByTestId(${primaryTestIdExpr}, annotationText, wait);`);
|
|
652
|
+
}
|
|
653
|
+
),
|
|
654
|
+
createAsyncMethod(
|
|
655
|
+
noWaitName,
|
|
656
|
+
[...baseParameters, createInlineParameter("annotationText", { type: "string", initializer: '""' })],
|
|
657
|
+
(writer) => {
|
|
658
|
+
writer.writeLine(`await this.${name}(${argsForForward}, false, annotationText);`);
|
|
659
|
+
}
|
|
660
|
+
)
|
|
402
661
|
];
|
|
403
662
|
}
|
|
404
663
|
return [
|
|
405
|
-
createAsyncMethod(
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
664
|
+
createAsyncMethod(
|
|
665
|
+
name,
|
|
666
|
+
[
|
|
667
|
+
createInlineParameter("wait", { type: "boolean", initializer: "true" }),
|
|
668
|
+
createInlineParameter("annotationText", { type: "string", initializer: '""' })
|
|
669
|
+
],
|
|
670
|
+
(writer) => {
|
|
671
|
+
writer.writeLine(`await this.clickByTestId(${primaryTestIdExpr}, annotationText, wait);`);
|
|
672
|
+
}
|
|
673
|
+
),
|
|
674
|
+
createAsyncMethod(
|
|
675
|
+
noWaitName,
|
|
676
|
+
[createInlineParameter("annotationText", { type: "string", initializer: '""' })],
|
|
677
|
+
(writer) => {
|
|
678
|
+
writer.writeLine(`await this.${name}(false, annotationText);`);
|
|
679
|
+
}
|
|
680
|
+
)
|
|
411
681
|
];
|
|
412
682
|
}
|
|
413
|
-
function generateRadioMethod(methodName,
|
|
683
|
+
function generateRadioMethod(methodName, selector, parameters) {
|
|
414
684
|
const name = `select${methodName}`;
|
|
415
|
-
const
|
|
416
|
-
const
|
|
417
|
-
|
|
418
|
-
createInlineParameter("annotationText", { type: "string", initializer: '""' })
|
|
419
|
-
] : [createInlineParameter("annotationText", { type: "string", initializer: '""' })];
|
|
420
|
-
const testIdExpr = hasKey ? `\`${formattedDataTestId}\`` : `"${formattedDataTestId}"`;
|
|
685
|
+
const selectorParams = orderPomPatternParameters(parameters, [selector]);
|
|
686
|
+
const methodParameters = createParameters(selectorParams);
|
|
687
|
+
const testIdExpr = toTypeScriptPomPatternExpression(selector);
|
|
421
688
|
return [
|
|
422
|
-
createAsyncMethod(name,
|
|
689
|
+
createAsyncMethod(name, methodParameters, (writer) => {
|
|
423
690
|
writer.writeLine(`await this.clickByTestId(${testIdExpr}, annotationText);`);
|
|
424
691
|
})
|
|
425
692
|
];
|
|
426
693
|
}
|
|
427
|
-
function generateSelectMethod(methodName,
|
|
694
|
+
function generateSelectMethod(methodName, selector, parameters) {
|
|
428
695
|
const name = `select${methodName}`;
|
|
429
|
-
const
|
|
430
|
-
const selectorExpr =
|
|
696
|
+
const selectorParams = orderPomPatternParameters(parameters, [selector]);
|
|
697
|
+
const selectorExpr = `this.selectorForTestId(${toTypeScriptPomPatternExpression(selector)})`;
|
|
431
698
|
return [
|
|
432
699
|
createAsyncMethod(
|
|
433
700
|
name,
|
|
434
|
-
|
|
435
|
-
createInlineParameter("value", { type: "string" }),
|
|
436
|
-
createInlineParameter("annotationText", { type: "string", initializer: '""' })
|
|
437
|
-
],
|
|
701
|
+
createParameters(selectorParams),
|
|
438
702
|
(writer) => {
|
|
439
703
|
writer.writeLine(`const selector = ${selectorExpr};`);
|
|
440
704
|
writer.writeLine("await this.animateCursorToElement(selector, false, 500, annotationText);");
|
|
@@ -443,33 +707,28 @@ function generateSelectMethod(methodName, formattedDataTestId) {
|
|
|
443
707
|
)
|
|
444
708
|
];
|
|
445
709
|
}
|
|
446
|
-
function generateVSelectMethod(methodName,
|
|
710
|
+
function generateVSelectMethod(methodName, selector, parameters) {
|
|
447
711
|
const name = `select${methodName}`;
|
|
712
|
+
const selectorParams = orderPomPatternParameters(parameters, [selector]);
|
|
448
713
|
return [
|
|
449
714
|
createAsyncMethod(
|
|
450
715
|
name,
|
|
451
|
-
|
|
452
|
-
createInlineParameter("value", { type: "string" }),
|
|
453
|
-
createInlineParameter("timeOut", { type: "number", initializer: "500" }),
|
|
454
|
-
createInlineParameter("annotationText", { type: "string", initializer: '""' })
|
|
455
|
-
],
|
|
716
|
+
createParameters(selectorParams),
|
|
456
717
|
(writer) => {
|
|
457
|
-
writer.writeLine(`await this.selectVSelectByTestId(
|
|
718
|
+
writer.writeLine(`await this.selectVSelectByTestId(${toTypeScriptPomPatternExpression(selector)}, value, timeOut, annotationText);`);
|
|
458
719
|
}
|
|
459
720
|
)
|
|
460
721
|
];
|
|
461
722
|
}
|
|
462
|
-
function generateTypeMethod(methodName,
|
|
723
|
+
function generateTypeMethod(methodName, selector, parameters) {
|
|
463
724
|
const name = `type${methodName}`;
|
|
725
|
+
const selectorParams = orderPomPatternParameters(parameters, [selector]);
|
|
464
726
|
return [
|
|
465
727
|
createAsyncMethod(
|
|
466
728
|
name,
|
|
467
|
-
|
|
468
|
-
createInlineParameter("text", { type: "string" }),
|
|
469
|
-
createInlineParameter("annotationText", { type: "string", initializer: '""' })
|
|
470
|
-
],
|
|
729
|
+
createParameters(selectorParams),
|
|
471
730
|
(writer) => {
|
|
472
|
-
writer.writeLine(`await this.fillInputByTestId(
|
|
731
|
+
writer.writeLine(`await this.fillInputByTestId(${toTypeScriptPomPatternExpression(selector)}, text, annotationText);`);
|
|
473
732
|
}
|
|
474
733
|
)
|
|
475
734
|
];
|
|
@@ -484,30 +743,31 @@ function isAllDigits(value) {
|
|
|
484
743
|
}
|
|
485
744
|
return true;
|
|
486
745
|
}
|
|
487
|
-
function generateGetElementByDataTestId(methodName, nativeRole,
|
|
746
|
+
function generateGetElementByDataTestId(methodName, nativeRole, selector, alternateSelectors, getterNameOverride, parameters) {
|
|
488
747
|
const roleSuffix = upperFirst$1(nativeRole || "Element");
|
|
489
748
|
const baseName = upperFirst$1(methodName);
|
|
490
749
|
const numericSuffix = baseName.startsWith(roleSuffix) ? baseName.slice(roleSuffix.length) : "";
|
|
491
750
|
const hasRoleSuffix = baseName.endsWith(roleSuffix) || baseName.startsWith(roleSuffix) && isAllDigits(numericSuffix);
|
|
492
751
|
const propertyName = hasRoleSuffix ? `${baseName}` : `${baseName}${roleSuffix}`;
|
|
493
|
-
const
|
|
494
|
-
|
|
495
|
-
|
|
752
|
+
const selectorParams = orderPomPatternParameters(parameters, [selector]);
|
|
753
|
+
const indexedVariable = getIndexedPomPatternVariable(selector);
|
|
754
|
+
if (indexedVariable) {
|
|
755
|
+
const keyType = getPomParameter(selectorParams, indexedVariable)?.typeExpression || "string";
|
|
496
756
|
const keyedPropertyName = getterNameOverride ?? removeByKeySegment(propertyName);
|
|
497
757
|
return [
|
|
498
758
|
createClassGetter({
|
|
499
759
|
name: keyedPropertyName,
|
|
500
760
|
statements: [
|
|
501
|
-
`return this.keyedLocators((
|
|
761
|
+
`return this.keyedLocators((${indexedVariable}: ${keyType}) => this.locatorByTestId(${toTypeScriptPomPatternExpression(selector)}));`
|
|
502
762
|
]
|
|
503
763
|
})
|
|
504
764
|
];
|
|
505
765
|
}
|
|
506
766
|
const finalPropertyName = getterNameOverride ?? propertyName;
|
|
507
|
-
const alternates =
|
|
767
|
+
const alternates = uniquePomStringPatterns(selector, alternateSelectors).slice(1);
|
|
508
768
|
if (alternates.length > 0) {
|
|
509
|
-
const all = [
|
|
510
|
-
const locatorExpr = all.map((id) => `this.locatorByTestId(${
|
|
769
|
+
const all = [selector, ...alternates];
|
|
770
|
+
const locatorExpr = all.map((id) => `this.locatorByTestId(${toTypeScriptPomPatternExpression(id)})`).reduce((acc, next) => `${acc}.or(${next})`);
|
|
511
771
|
return [
|
|
512
772
|
createClassGetter({
|
|
513
773
|
name: finalPropertyName,
|
|
@@ -518,21 +778,22 @@ function generateGetElementByDataTestId(methodName, nativeRole, formattedDataTes
|
|
|
518
778
|
return [
|
|
519
779
|
createClassGetter({
|
|
520
780
|
name: finalPropertyName,
|
|
521
|
-
statements: [`return this.locatorByTestId(
|
|
781
|
+
statements: [`return this.locatorByTestId(${toTypeScriptPomPatternExpression(selector)});`]
|
|
522
782
|
})
|
|
523
783
|
];
|
|
524
784
|
}
|
|
525
785
|
function generateNavigationMethod(args) {
|
|
526
|
-
const { targetPageObjectModelClass: target, baseMethodName,
|
|
786
|
+
const { targetPageObjectModelClass: target, baseMethodName, selector, alternateSelectors, parameters } = args;
|
|
527
787
|
const methodName = baseMethodName ? `goTo${upperFirst$1(baseMethodName)}` : `goTo${target.endsWith("Page") ? target.slice(0, -"Page".length) : target}`;
|
|
528
|
-
const
|
|
529
|
-
const
|
|
530
|
-
const
|
|
788
|
+
const selectorParams = orderPomPatternParameters(parameters, [selector]);
|
|
789
|
+
const methodParameters = createParameters(selectorParams);
|
|
790
|
+
const alternates = uniquePomStringPatterns(selector, alternateSelectors).slice(1);
|
|
791
|
+
const candidatesExpr = [toTypeScriptPomPatternExpression(selector), ...alternates.map((id) => toTypeScriptPomPatternExpression(id))].join(", ");
|
|
531
792
|
if (alternates.length > 0) {
|
|
532
793
|
return [
|
|
533
794
|
createClassMethod({
|
|
534
795
|
name: methodName,
|
|
535
|
-
parameters,
|
|
796
|
+
parameters: methodParameters,
|
|
536
797
|
returnType: `Fluent<${target}>`,
|
|
537
798
|
statements: (writer) => {
|
|
538
799
|
writer.write("return this.fluent(async () => ").block(() => {
|
|
@@ -560,11 +821,11 @@ function generateNavigationMethod(args) {
|
|
|
560
821
|
return [
|
|
561
822
|
createClassMethod({
|
|
562
823
|
name: methodName,
|
|
563
|
-
parameters,
|
|
824
|
+
parameters: methodParameters,
|
|
564
825
|
returnType: `Fluent<${target}>`,
|
|
565
826
|
statements: (writer) => {
|
|
566
827
|
writer.write("return this.fluent(async () => ").block(() => {
|
|
567
|
-
writer.writeLine(`await this.clickByTestId(
|
|
828
|
+
writer.writeLine(`await this.clickByTestId(${toTypeScriptPomPatternExpression(selector)});`);
|
|
568
829
|
writer.writeLine(`return new ${target}(this.page);`);
|
|
569
830
|
});
|
|
570
831
|
writer.writeLine(");");
|
|
@@ -572,15 +833,15 @@ function generateNavigationMethod(args) {
|
|
|
572
833
|
})
|
|
573
834
|
];
|
|
574
835
|
}
|
|
575
|
-
function generateViewObjectModelMembers(targetPageObjectModelClass, methodName, nativeRole,
|
|
836
|
+
function generateViewObjectModelMembers(targetPageObjectModelClass, methodName, nativeRole, selector, alternateSelectors, getterNameOverride, parameters) {
|
|
576
837
|
const baseMethodName = nativeRole === "radio" ? methodName || "Radio" : methodName;
|
|
577
838
|
const members = generateGetElementByDataTestId(
|
|
578
839
|
baseMethodName,
|
|
579
840
|
nativeRole,
|
|
580
|
-
|
|
581
|
-
|
|
841
|
+
selector,
|
|
842
|
+
alternateSelectors,
|
|
582
843
|
getterNameOverride,
|
|
583
|
-
|
|
844
|
+
parameters
|
|
584
845
|
);
|
|
585
846
|
if (targetPageObjectModelClass) {
|
|
586
847
|
return [
|
|
@@ -588,25 +849,25 @@ function generateViewObjectModelMembers(targetPageObjectModelClass, methodName,
|
|
|
588
849
|
...generateNavigationMethod({
|
|
589
850
|
targetPageObjectModelClass,
|
|
590
851
|
baseMethodName,
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
852
|
+
selector,
|
|
853
|
+
alternateSelectors,
|
|
854
|
+
parameters
|
|
594
855
|
})
|
|
595
856
|
];
|
|
596
857
|
}
|
|
597
858
|
if (nativeRole === "select") {
|
|
598
|
-
return [...members, ...generateSelectMethod(baseMethodName,
|
|
859
|
+
return [...members, ...generateSelectMethod(baseMethodName, selector, parameters)];
|
|
599
860
|
}
|
|
600
861
|
if (nativeRole === "vselect") {
|
|
601
|
-
return [...members, ...generateVSelectMethod(baseMethodName,
|
|
862
|
+
return [...members, ...generateVSelectMethod(baseMethodName, selector, parameters)];
|
|
602
863
|
}
|
|
603
864
|
if (nativeRole === "input") {
|
|
604
|
-
return [...members, ...generateTypeMethod(baseMethodName,
|
|
865
|
+
return [...members, ...generateTypeMethod(baseMethodName, selector, parameters)];
|
|
605
866
|
}
|
|
606
867
|
if (nativeRole === "radio") {
|
|
607
|
-
return [...members, ...generateRadioMethod(baseMethodName || "Radio",
|
|
868
|
+
return [...members, ...generateRadioMethod(baseMethodName || "Radio", selector, parameters)];
|
|
608
869
|
}
|
|
609
|
-
return [...members, ...generateClickMethod(baseMethodName,
|
|
870
|
+
return [...members, ...generateClickMethod(baseMethodName, selector, alternateSelectors, parameters)];
|
|
610
871
|
}
|
|
611
872
|
function isSimpleExpressionNode(value) {
|
|
612
873
|
return value !== null && "type" in value && value.type === NodeTypes.SIMPLE_EXPRESSION;
|
|
@@ -646,43 +907,72 @@ function buildPlaceholderParams(keys) {
|
|
|
646
907
|
params[k] = "__placeholder__";
|
|
647
908
|
return params;
|
|
648
909
|
}
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
910
|
+
const isNodeType = (node, type) => {
|
|
911
|
+
return node !== null && node.type === type;
|
|
912
|
+
};
|
|
913
|
+
const isStringLiteralNode = (node) => {
|
|
914
|
+
return isNodeType(node, "StringLiteral") && typeof node.value === "string";
|
|
915
|
+
};
|
|
916
|
+
const isIdentifierNode = (node) => {
|
|
917
|
+
return isNodeType(node, "Identifier") && typeof node.name === "string";
|
|
918
|
+
};
|
|
919
|
+
const isObjectPropertyNode = (node) => {
|
|
920
|
+
if (!isNodeType(node, "ObjectProperty"))
|
|
921
|
+
return false;
|
|
922
|
+
const n = node;
|
|
923
|
+
return typeof n.key === "object" && n.key !== null && typeof n.value === "object" && n.value !== null;
|
|
924
|
+
};
|
|
925
|
+
const isObjectExpressionNode = (node) => {
|
|
926
|
+
if (!isNodeType(node, "ObjectExpression"))
|
|
927
|
+
return false;
|
|
928
|
+
const n = node;
|
|
929
|
+
return Array.isArray(n.properties);
|
|
930
|
+
};
|
|
931
|
+
function materializeResolvedRouteTarget(target, paramKeys) {
|
|
932
|
+
if (typeof target === "string")
|
|
933
|
+
return target;
|
|
934
|
+
if (!paramKeys.length)
|
|
935
|
+
return target;
|
|
936
|
+
return {
|
|
937
|
+
...target,
|
|
938
|
+
params: buildPlaceholderParams(paramKeys)
|
|
939
|
+
};
|
|
940
|
+
}
|
|
941
|
+
function analyzeToDirectiveTarget(toDirective) {
|
|
942
|
+
if (!toDirective.exp) {
|
|
943
|
+
return {
|
|
944
|
+
kind: "unsupported",
|
|
945
|
+
rawSource: null,
|
|
946
|
+
reason: "missing-expression"
|
|
947
|
+
};
|
|
948
|
+
}
|
|
949
|
+
const rawSource = stringifyExpression(toDirective.exp).trim();
|
|
654
950
|
let expr;
|
|
655
951
|
try {
|
|
656
952
|
expr = parseExpression(rawSource, { plugins: ["typescript"] });
|
|
657
|
-
} catch {
|
|
658
|
-
return
|
|
953
|
+
} catch (error) {
|
|
954
|
+
return {
|
|
955
|
+
kind: "parse-error",
|
|
956
|
+
rawSource,
|
|
957
|
+
reason: "parse-error",
|
|
958
|
+
error: error instanceof Error ? error.message : String(error)
|
|
959
|
+
};
|
|
659
960
|
}
|
|
660
|
-
const isNodeType = (node, type) => {
|
|
661
|
-
return node !== null && node.type === type;
|
|
662
|
-
};
|
|
663
|
-
const isStringLiteralNode = (node) => {
|
|
664
|
-
return isNodeType(node, "StringLiteral") && typeof node.value === "string";
|
|
665
|
-
};
|
|
666
|
-
const isIdentifierNode = (node) => {
|
|
667
|
-
return isNodeType(node, "Identifier") && typeof node.name === "string";
|
|
668
|
-
};
|
|
669
|
-
const isObjectPropertyNode = (node) => {
|
|
670
|
-
if (!isNodeType(node, "ObjectProperty"))
|
|
671
|
-
return false;
|
|
672
|
-
const n = node;
|
|
673
|
-
return typeof n.key === "object" && n.key !== null && typeof n.value === "object" && n.value !== null;
|
|
674
|
-
};
|
|
675
|
-
const isObjectExpressionNode = (node) => {
|
|
676
|
-
if (!isNodeType(node, "ObjectExpression"))
|
|
677
|
-
return false;
|
|
678
|
-
const n = node;
|
|
679
|
-
return Array.isArray(n.properties);
|
|
680
|
-
};
|
|
681
961
|
if (isStringLiteralNode(expr)) {
|
|
682
|
-
return
|
|
962
|
+
return {
|
|
963
|
+
kind: "resolved",
|
|
964
|
+
rawSource,
|
|
965
|
+
target: expr.value,
|
|
966
|
+
routeNameKey: null,
|
|
967
|
+
paramKeys: []
|
|
968
|
+
};
|
|
683
969
|
}
|
|
684
970
|
if (!isObjectExpressionNode(expr)) {
|
|
685
|
-
return
|
|
971
|
+
return {
|
|
972
|
+
kind: "unsupported",
|
|
973
|
+
rawSource,
|
|
974
|
+
reason: "dynamic-expression"
|
|
975
|
+
};
|
|
686
976
|
}
|
|
687
977
|
const getStringField = (fieldName) => {
|
|
688
978
|
const prop = expr.properties.find((p) => {
|
|
@@ -703,7 +993,7 @@ function getRouteLocationLikeFromToDirective(toDirective) {
|
|
|
703
993
|
const key = p.key;
|
|
704
994
|
return isIdentifierNode(key) && key.name === "params" || isStringLiteralNode(key) && key.value === "params";
|
|
705
995
|
});
|
|
706
|
-
let
|
|
996
|
+
let paramKeys = [];
|
|
707
997
|
if (paramsProp && isObjectPropertyNode(paramsProp) && isObjectExpressionNode(paramsProp.value)) {
|
|
708
998
|
const keys = [];
|
|
709
999
|
for (const prop of paramsProp.value.properties) {
|
|
@@ -715,47 +1005,50 @@ function getRouteLocationLikeFromToDirective(toDirective) {
|
|
|
715
1005
|
else if (isStringLiteralNode(key))
|
|
716
1006
|
keys.push(key.value);
|
|
717
1007
|
}
|
|
718
|
-
|
|
719
|
-
params = buildPlaceholderParams(Array.from(new Set(keys)));
|
|
720
|
-
}
|
|
1008
|
+
paramKeys = Array.from(new Set(keys));
|
|
721
1009
|
}
|
|
722
1010
|
if (name) {
|
|
723
|
-
|
|
1011
|
+
const trimmed = name.trim();
|
|
1012
|
+
if (!trimmed.length) {
|
|
1013
|
+
return {
|
|
1014
|
+
kind: "unsupported",
|
|
1015
|
+
rawSource,
|
|
1016
|
+
reason: "missing-name-or-path"
|
|
1017
|
+
};
|
|
1018
|
+
}
|
|
1019
|
+
return {
|
|
1020
|
+
kind: "resolved",
|
|
1021
|
+
rawSource,
|
|
1022
|
+
target: { name },
|
|
1023
|
+
routeNameKey: toPascalCaseRouteKey(trimmed),
|
|
1024
|
+
paramKeys
|
|
1025
|
+
};
|
|
724
1026
|
}
|
|
725
1027
|
if (path2) {
|
|
726
|
-
return {
|
|
1028
|
+
return {
|
|
1029
|
+
kind: "resolved",
|
|
1030
|
+
rawSource,
|
|
1031
|
+
target: { path: path2 },
|
|
1032
|
+
routeNameKey: null,
|
|
1033
|
+
paramKeys
|
|
1034
|
+
};
|
|
727
1035
|
}
|
|
728
|
-
return
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
return null;
|
|
734
|
-
const name = to.name;
|
|
735
|
-
if (typeof name !== "string")
|
|
736
|
-
return null;
|
|
737
|
-
const trimmed = name.trim();
|
|
738
|
-
if (!trimmed.length)
|
|
739
|
-
return null;
|
|
740
|
-
return toPascalCaseRouteKey(trimmed);
|
|
741
|
-
}
|
|
742
|
-
function getRouteNameKeyFromToDirective(toDirective) {
|
|
743
|
-
const objectName = toDirectiveObjectFieldNameValue$1(toDirective);
|
|
744
|
-
if (objectName)
|
|
745
|
-
return objectName;
|
|
746
|
-
return null;
|
|
1036
|
+
return {
|
|
1037
|
+
kind: "unsupported",
|
|
1038
|
+
rawSource,
|
|
1039
|
+
reason: "missing-name-or-path"
|
|
1040
|
+
};
|
|
747
1041
|
}
|
|
748
1042
|
function tryResolveToDirectiveTargetComponentName(toDirective) {
|
|
749
|
-
const
|
|
750
|
-
if (
|
|
751
|
-
const resolved = resolveToComponentName(
|
|
1043
|
+
const analysis = analyzeToDirectiveTarget(toDirective);
|
|
1044
|
+
if (analysis.kind === "resolved" && resolveToComponentName) {
|
|
1045
|
+
const resolved = resolveToComponentName(materializeResolvedRouteTarget(analysis.target, analysis.paramKeys));
|
|
752
1046
|
if (resolved)
|
|
753
1047
|
return resolved;
|
|
754
1048
|
}
|
|
755
|
-
|
|
756
|
-
if (!key || !routeNameToComponentName)
|
|
1049
|
+
if (analysis.kind !== "resolved" || !analysis.routeNameKey || !routeNameToComponentName)
|
|
757
1050
|
return null;
|
|
758
|
-
return routeNameToComponentName.get(
|
|
1051
|
+
return routeNameToComponentName.get(analysis.routeNameKey) ?? null;
|
|
759
1052
|
}
|
|
760
1053
|
function getDataTestIdFromGroupOption(text) {
|
|
761
1054
|
return text.replace(/[-_]/g, " ").split(" ").filter((a) => a).map((str) => {
|
|
@@ -790,11 +1083,81 @@ function staticAttributeValue(value) {
|
|
|
790
1083
|
return { kind: "static", value };
|
|
791
1084
|
}
|
|
792
1085
|
function templateAttributeValue(template) {
|
|
793
|
-
|
|
1086
|
+
const parsedTemplate = tryParseTemplateFragment(template);
|
|
1087
|
+
if (!parsedTemplate) {
|
|
1088
|
+
throw new Error(`[vue-pom-generator] Failed to parse generated template fragment: ${template}`);
|
|
1089
|
+
}
|
|
1090
|
+
return { kind: "template", template, parsedTemplate };
|
|
794
1091
|
}
|
|
795
1092
|
function getAttributeValueText(value) {
|
|
796
1093
|
return value.kind === "static" ? value.value : value.template;
|
|
797
1094
|
}
|
|
1095
|
+
function getVueExpressionSource(expression, ...preferredViews) {
|
|
1096
|
+
if (!expression) {
|
|
1097
|
+
return "";
|
|
1098
|
+
}
|
|
1099
|
+
for (const view of preferredViews) {
|
|
1100
|
+
let value = "";
|
|
1101
|
+
switch (view) {
|
|
1102
|
+
case "content":
|
|
1103
|
+
value = expression.type === NodeTypes.SIMPLE_EXPRESSION ? expression.content : "";
|
|
1104
|
+
break;
|
|
1105
|
+
case "loc":
|
|
1106
|
+
value = expression.loc?.source ?? "";
|
|
1107
|
+
break;
|
|
1108
|
+
case "compiled":
|
|
1109
|
+
try {
|
|
1110
|
+
value = stringifyExpression(expression);
|
|
1111
|
+
} catch {
|
|
1112
|
+
value = "";
|
|
1113
|
+
}
|
|
1114
|
+
break;
|
|
1115
|
+
default:
|
|
1116
|
+
value = "";
|
|
1117
|
+
break;
|
|
1118
|
+
}
|
|
1119
|
+
const trimmed = value.trim();
|
|
1120
|
+
if (trimmed) {
|
|
1121
|
+
return trimmed;
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
return "";
|
|
1125
|
+
}
|
|
1126
|
+
function tryGetExistingVueExpressionAst(expression) {
|
|
1127
|
+
if (!expression) {
|
|
1128
|
+
return null;
|
|
1129
|
+
}
|
|
1130
|
+
const ast = "ast" in expression ? expression.ast : null;
|
|
1131
|
+
return ast && "type" in ast ? ast : null;
|
|
1132
|
+
}
|
|
1133
|
+
function tryParseBabelExpressionFromSource(source, plugins) {
|
|
1134
|
+
const trimmed = source.trim();
|
|
1135
|
+
if (!trimmed) {
|
|
1136
|
+
return null;
|
|
1137
|
+
}
|
|
1138
|
+
try {
|
|
1139
|
+
return parseExpression(trimmed, { plugins });
|
|
1140
|
+
} catch {
|
|
1141
|
+
return null;
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
function tryGetVueExpressionAst(expression, options) {
|
|
1145
|
+
if (!expression) {
|
|
1146
|
+
return null;
|
|
1147
|
+
}
|
|
1148
|
+
if (options?.preferExistingAst !== false) {
|
|
1149
|
+
const existingAst = tryGetExistingVueExpressionAst(expression);
|
|
1150
|
+
if (existingAst) {
|
|
1151
|
+
return existingAst;
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
const source = getVueExpressionSource(expression, ...options?.preferredViews ?? ["content", "loc", "compiled"]);
|
|
1155
|
+
return source ? tryParseBabelExpressionFromSource(source, options?.plugins ?? ["typescript"]) : null;
|
|
1156
|
+
}
|
|
1157
|
+
function tryGetDirectiveBabelAst(directive, options) {
|
|
1158
|
+
const exp = directive.exp && (directive.exp.type === NodeTypes.SIMPLE_EXPRESSION || directive.exp.type === NodeTypes.COMPOUND_EXPRESSION) ? directive.exp : null;
|
|
1159
|
+
return tryGetVueExpressionAst(exp, options);
|
|
1160
|
+
}
|
|
798
1161
|
function toPascalCase(str) {
|
|
799
1162
|
const cleaned = (str ?? "").replace(/\$\{[^}]*\}/g, " ").replace(/[^a-z0-9]+/gi, " ").trim();
|
|
800
1163
|
if (!cleaned) {
|
|
@@ -836,32 +1199,14 @@ function tryGetClickDirective(node) {
|
|
|
836
1199
|
function nodeHasClickDirective(node) {
|
|
837
1200
|
return tryGetClickDirective(node) !== void 0;
|
|
838
1201
|
}
|
|
839
|
-
function
|
|
1202
|
+
function findTemplateSlotScopeExpression(node) {
|
|
840
1203
|
if (node.tag !== "template") {
|
|
841
1204
|
return null;
|
|
842
1205
|
}
|
|
843
1206
|
const slotProp = node.props.find((prop) => {
|
|
844
1207
|
return prop.type === NodeTypes.DIRECTIVE && prop.name === "slot";
|
|
845
1208
|
});
|
|
846
|
-
|
|
847
|
-
if (slotProp.exp.type === NodeTypes.SIMPLE_EXPRESSION) {
|
|
848
|
-
return slotProp.exp.content;
|
|
849
|
-
}
|
|
850
|
-
if (slotProp.exp.type === NodeTypes.COMPOUND_EXPRESSION) {
|
|
851
|
-
return stringifyExpression(slotProp.exp);
|
|
852
|
-
}
|
|
853
|
-
}
|
|
854
|
-
return null;
|
|
855
|
-
}
|
|
856
|
-
function isSimpleScopeIdentifier(value) {
|
|
857
|
-
if (!value) {
|
|
858
|
-
return false;
|
|
859
|
-
}
|
|
860
|
-
try {
|
|
861
|
-
return isIdentifier(parseExpression(value, { plugins: ["typescript"] }));
|
|
862
|
-
} catch {
|
|
863
|
-
return false;
|
|
864
|
-
}
|
|
1209
|
+
return slotProp?.exp && (slotProp.exp.type === NodeTypes.SIMPLE_EXPRESSION || slotProp.exp.type === NodeTypes.COMPOUND_EXPRESSION) ? slotProp.exp : null;
|
|
865
1210
|
}
|
|
866
1211
|
function buildSlotScopeFallbackKeyExpression(identifier) {
|
|
867
1212
|
return `${identifier}.key ?? ${identifier}.data?.id ?? ${identifier}.id ?? ${identifier}.value ?? ${identifier}`;
|
|
@@ -963,43 +1308,64 @@ function tryGetSlotScopeKeyCandidate(node) {
|
|
|
963
1308
|
}
|
|
964
1309
|
return best;
|
|
965
1310
|
}
|
|
966
|
-
function
|
|
967
|
-
const
|
|
968
|
-
if (
|
|
1311
|
+
function tryGetTemplateSlotScopeBindingNode(expression) {
|
|
1312
|
+
const ast = tryGetExistingVueExpressionAst(expression);
|
|
1313
|
+
if (ast) {
|
|
1314
|
+
if (isArrowFunctionExpression(ast)) {
|
|
1315
|
+
return ast.params[0] ?? null;
|
|
1316
|
+
}
|
|
1317
|
+
return ast;
|
|
1318
|
+
}
|
|
1319
|
+
const rawSource = getVueExpressionSource(expression, "content", "loc", "compiled");
|
|
1320
|
+
if (!rawSource) {
|
|
969
1321
|
return null;
|
|
970
1322
|
}
|
|
971
|
-
|
|
972
|
-
return
|
|
1323
|
+
try {
|
|
1324
|
+
return parseExpression(rawSource, { plugins: ["typescript"] });
|
|
1325
|
+
} catch {
|
|
973
1326
|
}
|
|
974
1327
|
try {
|
|
975
|
-
const parsed = parse(`(${
|
|
1328
|
+
const parsed = parse(`(${rawSource}) => {}`, {
|
|
976
1329
|
sourceType: "module",
|
|
977
1330
|
plugins: ["typescript"]
|
|
978
1331
|
});
|
|
979
1332
|
const statement = parsed.program.body[0];
|
|
980
1333
|
if (statement && isExpressionStatement(statement) && isArrowFunctionExpression(statement.expression)) {
|
|
981
|
-
return
|
|
1334
|
+
return statement.expression.params[0] ?? null;
|
|
982
1335
|
}
|
|
983
1336
|
} catch {
|
|
1337
|
+
return null;
|
|
984
1338
|
}
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1339
|
+
return null;
|
|
1340
|
+
}
|
|
1341
|
+
function toResolvedTemplateFragment(source) {
|
|
1342
|
+
const templateLiteral = tryUnwrapTemplateLiteralSource(source);
|
|
1343
|
+
if (templateLiteral) {
|
|
1344
|
+
return {
|
|
1345
|
+
template: templateLiteral.template,
|
|
1346
|
+
rawExpression: null
|
|
1347
|
+
};
|
|
1348
|
+
}
|
|
1349
|
+
return toInterpolatedTemplateFragment(source);
|
|
1350
|
+
}
|
|
1351
|
+
function toResolvedKeyInfo(selectorSource, runtimeSource = selectorSource) {
|
|
1352
|
+
const selectorFragment = selectorSource ? toResolvedTemplateFragment(selectorSource) : null;
|
|
1353
|
+
const runtimeFragment = runtimeSource ? toResolvedTemplateFragment(runtimeSource) : null;
|
|
1354
|
+
const selectorTemplate = selectorFragment?.template ?? runtimeFragment?.template ?? null;
|
|
1355
|
+
const runtimeTemplate = runtimeFragment?.template ?? selectorFragment?.template ?? null;
|
|
1356
|
+
if (!selectorTemplate || !runtimeTemplate) {
|
|
1357
|
+
return null;
|
|
1001
1358
|
}
|
|
1002
|
-
return
|
|
1359
|
+
return {
|
|
1360
|
+
selectorFragment: selectorTemplate,
|
|
1361
|
+
runtimeFragment: runtimeTemplate,
|
|
1362
|
+
rawExpression: runtimeFragment?.rawExpression ?? selectorFragment?.rawExpression ?? null
|
|
1363
|
+
};
|
|
1364
|
+
}
|
|
1365
|
+
function tryGetTemplateSlotScopeKeyInfo(expression) {
|
|
1366
|
+
const bindingNode = tryGetTemplateSlotScopeBindingNode(expression);
|
|
1367
|
+
const candidateExpression = bindingNode ? tryGetSlotScopeKeyCandidate(bindingNode)?.expression ?? null : null;
|
|
1368
|
+
return candidateExpression ? toResolvedKeyInfo(candidateExpression) : null;
|
|
1003
1369
|
}
|
|
1004
1370
|
function nodeHasToDirective(node) {
|
|
1005
1371
|
const toDirective = findDirectiveByName(node, "bind", "to");
|
|
@@ -1008,51 +1374,160 @@ function nodeHasToDirective(node) {
|
|
|
1008
1374
|
}
|
|
1009
1375
|
return void 0;
|
|
1010
1376
|
}
|
|
1011
|
-
function nodeHasForDirective(node) {
|
|
1012
|
-
return node.props.some(
|
|
1013
|
-
(attr) => attr.type === NodeTypes.DIRECTIVE && attr.name === "for"
|
|
1014
|
-
);
|
|
1015
|
-
}
|
|
1016
1377
|
function getKeyDirective(node) {
|
|
1017
1378
|
return findDirectiveByName(node, "bind", "key") ?? null;
|
|
1018
1379
|
}
|
|
1019
|
-
function
|
|
1020
|
-
const
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1380
|
+
function tryUnwrapTemplateLiteralSource(source) {
|
|
1381
|
+
const rawSource = source.trim();
|
|
1382
|
+
if (!rawSource) {
|
|
1383
|
+
return null;
|
|
1384
|
+
}
|
|
1385
|
+
let ast = null;
|
|
1386
|
+
try {
|
|
1387
|
+
ast = parseExpression(rawSource, { plugins: ["typescript"] });
|
|
1388
|
+
} catch {
|
|
1389
|
+
return null;
|
|
1024
1390
|
}
|
|
1025
|
-
if (
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1391
|
+
if (!ast || !isTemplateLiteral(ast)) {
|
|
1392
|
+
return null;
|
|
1393
|
+
}
|
|
1394
|
+
const cooked = ast.quasis.map((quasi) => quasi.value.cooked ?? "").join("");
|
|
1395
|
+
try {
|
|
1396
|
+
const start = typeof ast.start === "number" ? ast.start + 1 : 1;
|
|
1397
|
+
const end = typeof ast.end === "number" ? ast.end - 1 : rawSource.length - 1;
|
|
1398
|
+
return {
|
|
1399
|
+
template: rawSource.slice(start, end) || cooked,
|
|
1400
|
+
expressionCount: ast.expressions.length
|
|
1401
|
+
};
|
|
1402
|
+
} catch {
|
|
1403
|
+
return {
|
|
1404
|
+
template: cooked,
|
|
1405
|
+
expressionCount: ast.expressions.length
|
|
1406
|
+
};
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
function tryUnwrapTemplateLiteralExpressionSource(expression) {
|
|
1410
|
+
const rawSource = getVueExpressionSource(expression, "loc", "compiled");
|
|
1411
|
+
return rawSource ? tryUnwrapTemplateLiteralSource(rawSource) : null;
|
|
1412
|
+
}
|
|
1413
|
+
function tryParseTemplateFragment(fragment) {
|
|
1414
|
+
if (!fragment) {
|
|
1415
|
+
return null;
|
|
1416
|
+
}
|
|
1417
|
+
try {
|
|
1418
|
+
const source = `\`${fragment}\``;
|
|
1419
|
+
const ast = parseExpression(source, { plugins: ["typescript"] });
|
|
1420
|
+
return isTemplateLiteral(ast) ? { source, templateLiteral: ast } : null;
|
|
1421
|
+
} catch {
|
|
1422
|
+
return null;
|
|
1029
1423
|
}
|
|
1030
|
-
|
|
1424
|
+
}
|
|
1425
|
+
function getTemplateExpressionSource(parsedTemplate, index) {
|
|
1426
|
+
const expression = parsedTemplate.templateLiteral.expressions[index];
|
|
1427
|
+
if (!expression) {
|
|
1428
|
+
return null;
|
|
1429
|
+
}
|
|
1430
|
+
const start = typeof expression.start === "number" ? expression.start : null;
|
|
1431
|
+
const end = typeof expression.end === "number" ? expression.end : null;
|
|
1432
|
+
if (start === null || end === null) {
|
|
1433
|
+
return null;
|
|
1434
|
+
}
|
|
1435
|
+
return parsedTemplate.source.slice(start, end);
|
|
1436
|
+
}
|
|
1437
|
+
function getSingleExpressionTemplateFragment(parsedTemplate) {
|
|
1438
|
+
const { templateLiteral } = parsedTemplate;
|
|
1439
|
+
if (templateLiteral.expressions.length !== 1 || templateLiteral.quasis.length !== 2) {
|
|
1440
|
+
return null;
|
|
1441
|
+
}
|
|
1442
|
+
const expressionSource = getTemplateExpressionSource(parsedTemplate, 0);
|
|
1443
|
+
if (expressionSource === null) {
|
|
1444
|
+
return null;
|
|
1445
|
+
}
|
|
1446
|
+
return {
|
|
1447
|
+
prefix: templateLiteral.quasis[0]?.value.raw ?? "",
|
|
1448
|
+
expressionSource,
|
|
1449
|
+
suffix: templateLiteral.quasis[1]?.value.raw ?? ""
|
|
1450
|
+
};
|
|
1451
|
+
}
|
|
1452
|
+
function templateFragmentContainsSingleExpression(container, candidate) {
|
|
1453
|
+
const containerFragment = getSingleExpressionTemplateFragment(container);
|
|
1454
|
+
const candidateFragment = getSingleExpressionTemplateFragment(candidate);
|
|
1455
|
+
if (!containerFragment || !candidateFragment) {
|
|
1456
|
+
return false;
|
|
1457
|
+
}
|
|
1458
|
+
return containerFragment.expressionSource === candidateFragment.expressionSource && containerFragment.prefix.endsWith(candidateFragment.prefix) && containerFragment.suffix.startsWith(candidateFragment.suffix);
|
|
1459
|
+
}
|
|
1460
|
+
function hasTemplateInterpolationExpressions(fragment) {
|
|
1461
|
+
return (tryParseTemplateFragment(fragment)?.templateLiteral.expressions.length ?? 0) > 0;
|
|
1462
|
+
}
|
|
1463
|
+
function toInterpolatedTemplateFragment(fragment) {
|
|
1464
|
+
if (!fragment) {
|
|
1465
|
+
return null;
|
|
1466
|
+
}
|
|
1467
|
+
if (hasTemplateInterpolationExpressions(fragment)) {
|
|
1468
|
+
return { template: fragment, rawExpression: null };
|
|
1469
|
+
}
|
|
1470
|
+
return {
|
|
1471
|
+
template: `\${${fragment}}`,
|
|
1472
|
+
rawExpression: fragment
|
|
1473
|
+
};
|
|
1474
|
+
}
|
|
1475
|
+
function renderTemplateLiteralExpression(templateValue) {
|
|
1476
|
+
const templateLiteralSource = templateValue.parsedTemplate.source;
|
|
1477
|
+
const templateLiteral = templateValue.parsedTemplate.templateLiteral;
|
|
1478
|
+
const writer = createTypeScriptWriter();
|
|
1479
|
+
writer.write("`");
|
|
1480
|
+
for (let i = 0; i < templateLiteral.quasis.length; i += 1) {
|
|
1481
|
+
writer.write(templateLiteral.quasis[i]?.value.raw ?? "");
|
|
1482
|
+
const interpolation = templateLiteral.expressions[i];
|
|
1483
|
+
if (!interpolation) {
|
|
1484
|
+
continue;
|
|
1485
|
+
}
|
|
1486
|
+
const start = typeof interpolation.start === "number" ? interpolation.start : null;
|
|
1487
|
+
const end = typeof interpolation.end === "number" ? interpolation.end : null;
|
|
1488
|
+
if (start === null || end === null) {
|
|
1489
|
+
return templateLiteralSource;
|
|
1490
|
+
}
|
|
1491
|
+
writer.write("${");
|
|
1492
|
+
writer.write(templateLiteralSource.slice(start, end));
|
|
1493
|
+
writer.write("}");
|
|
1494
|
+
}
|
|
1495
|
+
writer.write("`");
|
|
1496
|
+
return writer.toString();
|
|
1497
|
+
}
|
|
1498
|
+
function getKeyDirectiveExpression(node) {
|
|
1499
|
+
const keyDirective = getKeyDirective(node);
|
|
1500
|
+
return keyDirective?.exp && (keyDirective.exp.type === NodeTypes.SIMPLE_EXPRESSION || keyDirective.exp.type === NodeTypes.COMPOUND_EXPRESSION) ? keyDirective.exp : null;
|
|
1501
|
+
}
|
|
1502
|
+
function getKeyDirectiveInfo(node) {
|
|
1503
|
+
const keyExpression = getKeyDirectiveExpression(node);
|
|
1504
|
+
if (!keyExpression) {
|
|
1505
|
+
return null;
|
|
1506
|
+
}
|
|
1507
|
+
const selectorSource = getVueExpressionSource(keyExpression, "compiled", "loc");
|
|
1508
|
+
const runtimeSource = getVueExpressionSource(keyExpression, "loc", "compiled");
|
|
1509
|
+
return toResolvedKeyInfo(selectorSource, runtimeSource);
|
|
1031
1510
|
}
|
|
1032
1511
|
function getModelBindingValues(node) {
|
|
1033
1512
|
let vModel = "";
|
|
1034
1513
|
const vModelDirective = findDirectiveByName(node, "model");
|
|
1035
|
-
if (vModelDirective?.exp
|
|
1036
|
-
vModel = toPascalCase(vModelDirective.exp
|
|
1514
|
+
if (vModelDirective?.exp && (vModelDirective.exp.type === NodeTypes.SIMPLE_EXPRESSION || vModelDirective.exp.type === NodeTypes.COMPOUND_EXPRESSION)) {
|
|
1515
|
+
vModel = toPascalCase(getVueExpressionSource(vModelDirective.exp, "loc", "content"));
|
|
1037
1516
|
}
|
|
1038
1517
|
let modelValue = null;
|
|
1039
1518
|
const modelValueDirective = findDirectiveByName(node, "bind", "modelValue");
|
|
1040
|
-
|
|
1041
|
-
|
|
1519
|
+
const modelValueAst = modelValueDirective ? tryGetDirectiveBabelAst(modelValueDirective, {
|
|
1520
|
+
preferredViews: ["loc", "compiled"],
|
|
1521
|
+
plugins: ["typescript"],
|
|
1522
|
+
preferExistingAst: false
|
|
1523
|
+
}) : null;
|
|
1524
|
+
if (modelValueAst) {
|
|
1525
|
+
const { name: mv } = getClickHandlerNameFromAst(modelValueAst);
|
|
1042
1526
|
modelValue = mv;
|
|
1043
1527
|
}
|
|
1044
1528
|
return { vModel, modelValue };
|
|
1045
1529
|
}
|
|
1046
|
-
function
|
|
1047
|
-
if (node.isSelfClosing) {
|
|
1048
|
-
const hasForDirective = nodeHasForDirective(node);
|
|
1049
|
-
if (hasForDirective) {
|
|
1050
|
-
return getKeyDirectiveValue(node);
|
|
1051
|
-
}
|
|
1052
|
-
}
|
|
1053
|
-
return null;
|
|
1054
|
-
}
|
|
1055
|
-
function getIdOrName(node) {
|
|
1530
|
+
function getStaticIdOrNameHint(node) {
|
|
1056
1531
|
let idAttr = findAttributeByKey(node, "id");
|
|
1057
1532
|
if (!idAttr) {
|
|
1058
1533
|
idAttr = findAttributeByKey(node, "name");
|
|
@@ -1060,8 +1535,9 @@ function getIdOrName(node) {
|
|
|
1060
1535
|
let identifier = idAttr?.value?.content ?? "";
|
|
1061
1536
|
if (!identifier) {
|
|
1062
1537
|
const dynamicIdAttr = findDirectiveByName(node, "bind", "id");
|
|
1063
|
-
|
|
1064
|
-
|
|
1538
|
+
const dynamicNameAttr = findDirectiveByName(node, "bind", "name");
|
|
1539
|
+
if (dynamicIdAttr?.exp || dynamicNameAttr?.exp) {
|
|
1540
|
+
return "";
|
|
1065
1541
|
}
|
|
1066
1542
|
}
|
|
1067
1543
|
if (identifier.includes("-")) {
|
|
@@ -1072,20 +1548,20 @@ function getIdOrName(node) {
|
|
|
1072
1548
|
}
|
|
1073
1549
|
return identifier;
|
|
1074
1550
|
}
|
|
1075
|
-
function
|
|
1551
|
+
function getContainedInSlotDataKeyInfo(node, hierarchyMap2) {
|
|
1076
1552
|
let parent = getParent(hierarchyMap2, node);
|
|
1077
1553
|
while (parent) {
|
|
1078
1554
|
if (parent.type === NodeTypes.ELEMENT && parent.tag === "template") {
|
|
1079
|
-
const
|
|
1080
|
-
if (
|
|
1081
|
-
return
|
|
1555
|
+
const slotScopeExpression = findTemplateSlotScopeExpression(parent);
|
|
1556
|
+
if (slotScopeExpression) {
|
|
1557
|
+
return tryGetTemplateSlotScopeKeyInfo(slotScopeExpression);
|
|
1082
1558
|
}
|
|
1083
1559
|
}
|
|
1084
1560
|
parent = getParent(hierarchyMap2, parent);
|
|
1085
1561
|
}
|
|
1086
1562
|
return null;
|
|
1087
1563
|
}
|
|
1088
|
-
function
|
|
1564
|
+
function getContainedInVForDirectiveKeyInfo(context, node, hierarchyMap2) {
|
|
1089
1565
|
if (!context.scopes.vFor || context.scopes.vFor === 0) {
|
|
1090
1566
|
return null;
|
|
1091
1567
|
}
|
|
@@ -1094,8 +1570,7 @@ function getContainedInVForDirectiveKeyValue(context, node, hierarchyMap2) {
|
|
|
1094
1570
|
if (parent.type === NodeTypes.ELEMENT) {
|
|
1095
1571
|
const forDirective = findDirectiveByName(parent, "for");
|
|
1096
1572
|
if (forDirective) {
|
|
1097
|
-
|
|
1098
|
-
return keyValue;
|
|
1573
|
+
return getKeyDirectiveInfo(parent);
|
|
1099
1574
|
}
|
|
1100
1575
|
}
|
|
1101
1576
|
parent = getParent(hierarchyMap2, parent);
|
|
@@ -1121,13 +1596,7 @@ function tryGetContainedInStaticVForSourceLiteralValues(context, _node, _hierarc
|
|
|
1121
1596
|
if (simpleSourceExp.constType === ConstantTypes.NOT_CONSTANT) {
|
|
1122
1597
|
return null;
|
|
1123
1598
|
}
|
|
1124
|
-
const iterableRaw = (
|
|
1125
|
-
try {
|
|
1126
|
-
return stringifyExpression(simpleSourceExp).trim();
|
|
1127
|
-
} catch {
|
|
1128
|
-
return (simpleSourceExp.loc?.source ?? "").trim();
|
|
1129
|
-
}
|
|
1130
|
-
})();
|
|
1599
|
+
const iterableRaw = getVueExpressionSource(simpleSourceExp, "compiled", "loc");
|
|
1131
1600
|
if (!iterableRaw) {
|
|
1132
1601
|
return null;
|
|
1133
1602
|
}
|
|
@@ -1183,91 +1652,95 @@ function nodeHandlerAttributeInfo(node) {
|
|
|
1183
1652
|
return null;
|
|
1184
1653
|
}
|
|
1185
1654
|
const exp = handlerDirective.exp;
|
|
1186
|
-
const source = (exp
|
|
1655
|
+
const source = getVueExpressionSource(exp, "content", "compiled");
|
|
1187
1656
|
if (!source) {
|
|
1188
1657
|
return null;
|
|
1189
1658
|
}
|
|
1190
1659
|
const mergeKey = `handler:expr:${source}`;
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1660
|
+
const expr = tryGetDirectiveBabelAst(handlerDirective, {
|
|
1661
|
+
preferredViews: ["content", "compiled"],
|
|
1662
|
+
plugins: ["typescript", "jsx"],
|
|
1663
|
+
// Vue's compiler AST can encode `_ctx.foo` as an Identifier name instead of a MemberExpression.
|
|
1664
|
+
// That is fine for Vue codegen, but our semantic-name extraction needs a normal Babel parse tree.
|
|
1665
|
+
preferExistingAst: false
|
|
1666
|
+
});
|
|
1667
|
+
if (!expr) {
|
|
1195
1668
|
return null;
|
|
1196
1669
|
}
|
|
1197
|
-
const
|
|
1670
|
+
const isNodeType2 = (node2, type) => {
|
|
1198
1671
|
return node2 !== null && node2.type === type;
|
|
1199
1672
|
};
|
|
1200
|
-
const
|
|
1201
|
-
return
|
|
1673
|
+
const isIdentifierNode2 = (node2) => {
|
|
1674
|
+
return isNodeType2(node2, "Identifier") && typeof node2.name === "string";
|
|
1202
1675
|
};
|
|
1203
|
-
const
|
|
1204
|
-
return
|
|
1676
|
+
const isStringLiteralNode2 = (node2) => {
|
|
1677
|
+
return isNodeType2(node2, "StringLiteral") && typeof node2.value === "string";
|
|
1205
1678
|
};
|
|
1206
1679
|
const isBooleanLiteralNode = (node2) => {
|
|
1207
|
-
return
|
|
1680
|
+
return isNodeType2(node2, "BooleanLiteral") && typeof node2.value === "boolean";
|
|
1208
1681
|
};
|
|
1209
1682
|
const isNumericLiteralNode = (node2) => {
|
|
1210
|
-
return
|
|
1683
|
+
return isNodeType2(node2, "NumericLiteral") && typeof node2.value === "number";
|
|
1211
1684
|
};
|
|
1212
1685
|
const isNullLiteralNode = (node2) => {
|
|
1213
|
-
return
|
|
1686
|
+
return isNodeType2(node2, "NullLiteral");
|
|
1214
1687
|
};
|
|
1215
1688
|
const isMemberExpressionNode = (node2) => {
|
|
1216
|
-
if (!
|
|
1689
|
+
if (!isNodeType2(node2, "MemberExpression"))
|
|
1217
1690
|
return false;
|
|
1218
1691
|
const n = node2;
|
|
1219
1692
|
return typeof n.computed === "boolean" && typeof n.object === "object" && n.object !== null && typeof n.property === "object" && n.property !== null;
|
|
1220
1693
|
};
|
|
1221
1694
|
const isCallExpressionNode = (node2) => {
|
|
1222
|
-
if (!
|
|
1695
|
+
if (!isNodeType2(node2, "CallExpression"))
|
|
1223
1696
|
return false;
|
|
1224
1697
|
const n = node2;
|
|
1225
1698
|
return typeof n.callee === "object" && n.callee !== null && Array.isArray(n.arguments);
|
|
1226
1699
|
};
|
|
1227
1700
|
const isAwaitExpressionNode = (node2) => {
|
|
1228
|
-
if (!
|
|
1701
|
+
if (!isNodeType2(node2, "AwaitExpression"))
|
|
1229
1702
|
return false;
|
|
1230
1703
|
const n = node2;
|
|
1231
1704
|
return typeof n.argument === "object" && n.argument !== null;
|
|
1232
1705
|
};
|
|
1233
1706
|
const isAssignmentExpressionNode = (node2) => {
|
|
1234
|
-
if (!
|
|
1707
|
+
if (!isNodeType2(node2, "AssignmentExpression"))
|
|
1235
1708
|
return false;
|
|
1236
1709
|
const n = node2;
|
|
1237
1710
|
return typeof n.left === "object" && n.left !== null && typeof n.right === "object" && n.right !== null;
|
|
1238
1711
|
};
|
|
1239
1712
|
const isArrowFunctionExpressionNode = (node2) => {
|
|
1240
|
-
if (!
|
|
1713
|
+
if (!isNodeType2(node2, "ArrowFunctionExpression"))
|
|
1241
1714
|
return false;
|
|
1242
1715
|
const n = node2;
|
|
1243
1716
|
return typeof n.body === "object" && n.body !== null;
|
|
1244
1717
|
};
|
|
1245
1718
|
const isBlockStatementNode = (node2) => {
|
|
1246
|
-
if (!
|
|
1719
|
+
if (!isNodeType2(node2, "BlockStatement"))
|
|
1247
1720
|
return false;
|
|
1248
1721
|
const n = node2;
|
|
1249
1722
|
return Array.isArray(n.body);
|
|
1250
1723
|
};
|
|
1251
1724
|
const isExpressionStatementNode = (node2) => {
|
|
1252
|
-
if (!
|
|
1725
|
+
if (!isNodeType2(node2, "ExpressionStatement"))
|
|
1253
1726
|
return false;
|
|
1254
1727
|
const n = node2;
|
|
1255
1728
|
return typeof n.expression === "object" && n.expression !== null;
|
|
1256
1729
|
};
|
|
1257
1730
|
const isReturnStatementNode = (node2) => {
|
|
1258
|
-
if (!
|
|
1731
|
+
if (!isNodeType2(node2, "ReturnStatement"))
|
|
1259
1732
|
return false;
|
|
1260
1733
|
const n = node2;
|
|
1261
1734
|
return typeof n.argument === "object" || n.argument === null;
|
|
1262
1735
|
};
|
|
1263
|
-
const
|
|
1264
|
-
if (!
|
|
1736
|
+
const isObjectExpressionNode2 = (node2) => {
|
|
1737
|
+
if (!isNodeType2(node2, "ObjectExpression"))
|
|
1265
1738
|
return false;
|
|
1266
1739
|
const n = node2;
|
|
1267
1740
|
return Array.isArray(n.properties);
|
|
1268
1741
|
};
|
|
1269
|
-
const
|
|
1270
|
-
if (!
|
|
1742
|
+
const isObjectPropertyNode2 = (node2) => {
|
|
1743
|
+
if (!isNodeType2(node2, "ObjectProperty"))
|
|
1271
1744
|
return false;
|
|
1272
1745
|
const n = node2;
|
|
1273
1746
|
return typeof n.computed === "boolean" && typeof n.key === "object" && n.key !== null && typeof n.value === "object" && n.value !== null;
|
|
@@ -1275,16 +1748,16 @@ function nodeHandlerAttributeInfo(node) {
|
|
|
1275
1748
|
const getLastIdentifierFromMemberChain = (node2) => {
|
|
1276
1749
|
if (!node2)
|
|
1277
1750
|
return null;
|
|
1278
|
-
if (
|
|
1751
|
+
if (isIdentifierNode2(node2))
|
|
1279
1752
|
return node2.name;
|
|
1280
1753
|
if (isMemberExpressionNode(node2)) {
|
|
1281
1754
|
const prop = node2.property;
|
|
1282
1755
|
if (node2.computed === false) {
|
|
1283
|
-
if (
|
|
1756
|
+
if (isIdentifierNode2(prop))
|
|
1284
1757
|
return prop.name;
|
|
1285
1758
|
}
|
|
1286
1759
|
if (node2.computed === true) {
|
|
1287
|
-
if (
|
|
1760
|
+
if (isStringLiteralNode2(prop))
|
|
1288
1761
|
return prop.value;
|
|
1289
1762
|
}
|
|
1290
1763
|
}
|
|
@@ -1294,7 +1767,7 @@ function nodeHandlerAttributeInfo(node) {
|
|
|
1294
1767
|
if (!node2) {
|
|
1295
1768
|
return null;
|
|
1296
1769
|
}
|
|
1297
|
-
if (
|
|
1770
|
+
if (isIdentifierNode2(node2)) {
|
|
1298
1771
|
return node2.name;
|
|
1299
1772
|
}
|
|
1300
1773
|
if (isMemberExpressionNode(node2)) {
|
|
@@ -1306,11 +1779,11 @@ function nodeHandlerAttributeInfo(node) {
|
|
|
1306
1779
|
if (!lhs) {
|
|
1307
1780
|
return null;
|
|
1308
1781
|
}
|
|
1309
|
-
if (
|
|
1782
|
+
if (isIdentifierNode2(lhs)) {
|
|
1310
1783
|
return lhs.name;
|
|
1311
1784
|
}
|
|
1312
1785
|
if (isMemberExpressionNode(lhs)) {
|
|
1313
|
-
if (lhs.computed === false &&
|
|
1786
|
+
if (lhs.computed === false && isIdentifierNode2(lhs.property) && lhs.property.name === "value") {
|
|
1314
1787
|
return getLastIdentifierFromMemberChain(lhs.object);
|
|
1315
1788
|
}
|
|
1316
1789
|
return getLastIdentifierFromMemberChain(lhs);
|
|
@@ -1318,7 +1791,7 @@ function nodeHandlerAttributeInfo(node) {
|
|
|
1318
1791
|
return null;
|
|
1319
1792
|
};
|
|
1320
1793
|
const isTemplateLiteralNode = (node2) => {
|
|
1321
|
-
if (!
|
|
1794
|
+
if (!isNodeType2(node2, "TemplateLiteral")) {
|
|
1322
1795
|
return false;
|
|
1323
1796
|
}
|
|
1324
1797
|
const n = node2;
|
|
@@ -1337,7 +1810,7 @@ function nodeHandlerAttributeInfo(node) {
|
|
|
1337
1810
|
if (isNullLiteralNode(arg)) {
|
|
1338
1811
|
return "Null";
|
|
1339
1812
|
}
|
|
1340
|
-
if (
|
|
1813
|
+
if (isStringLiteralNode2(arg)) {
|
|
1341
1814
|
const cleaned = (arg.value ?? "").trim();
|
|
1342
1815
|
if (!cleaned) {
|
|
1343
1816
|
return null;
|
|
@@ -1363,7 +1836,7 @@ function nodeHandlerAttributeInfo(node) {
|
|
|
1363
1836
|
return toPascalCase(stableName.slice(0, 24));
|
|
1364
1837
|
}
|
|
1365
1838
|
}
|
|
1366
|
-
if (
|
|
1839
|
+
if (isIdentifierNode2(arg)) {
|
|
1367
1840
|
const firstChar = arg.name.charAt(0);
|
|
1368
1841
|
const isUpperAlpha = firstChar !== "" && firstChar === firstChar.toUpperCase() && firstChar !== firstChar.toLowerCase();
|
|
1369
1842
|
if (isUpperAlpha) {
|
|
@@ -1375,7 +1848,7 @@ function nodeHandlerAttributeInfo(node) {
|
|
|
1375
1848
|
const getStableSuffixFromCall = (call) => {
|
|
1376
1849
|
const args = call.arguments ?? [];
|
|
1377
1850
|
const first = args.length > 0 ? args[0] : null;
|
|
1378
|
-
if (!
|
|
1851
|
+
if (!isObjectExpressionNode2(first)) {
|
|
1379
1852
|
const parts2 = [];
|
|
1380
1853
|
for (const arg of args.slice(0, 4)) {
|
|
1381
1854
|
const w = stableWordFromValue(arg ?? null);
|
|
@@ -1394,20 +1867,20 @@ function nodeHandlerAttributeInfo(node) {
|
|
|
1394
1867
|
}
|
|
1395
1868
|
const parts = [];
|
|
1396
1869
|
for (const prop of first.properties ?? []) {
|
|
1397
|
-
if (!
|
|
1870
|
+
if (!isObjectPropertyNode2(prop)) {
|
|
1398
1871
|
continue;
|
|
1399
1872
|
}
|
|
1400
1873
|
if (prop.computed) {
|
|
1401
1874
|
continue;
|
|
1402
1875
|
}
|
|
1403
|
-
const keyName =
|
|
1876
|
+
const keyName = isIdentifierNode2(prop.key) ? prop.key.name : isStringLiteralNode2(prop.key) ? prop.key.value : null;
|
|
1404
1877
|
if (!keyName) {
|
|
1405
1878
|
continue;
|
|
1406
1879
|
}
|
|
1407
1880
|
let valueWord = null;
|
|
1408
1881
|
if (isBooleanLiteralNode(prop.value)) {
|
|
1409
1882
|
valueWord = prop.value.value ? "True" : "False";
|
|
1410
|
-
} else if (
|
|
1883
|
+
} else if (isStringLiteralNode2(prop.value)) {
|
|
1411
1884
|
const cleaned = (prop.value.value ?? "").trim();
|
|
1412
1885
|
if (cleaned) {
|
|
1413
1886
|
valueWord = toPascalCase(cleaned.slice(0, 24));
|
|
@@ -1536,13 +2009,18 @@ function getDataTestIdValueFromValueAttribute(node, actualFileName, attributeKey
|
|
|
1536
2009
|
return staticAttributeValue(`${actualFileName}-${value}-${role}`);
|
|
1537
2010
|
}
|
|
1538
2011
|
const attrDynamic = findDirectiveByName(node, "bind", attributeKey);
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
2012
|
+
const attrDynamicAst = attrDynamic ? tryGetDirectiveBabelAst(attrDynamic, {
|
|
2013
|
+
preferredViews: ["loc", "compiled"],
|
|
2014
|
+
plugins: ["typescript"],
|
|
2015
|
+
preferExistingAst: false
|
|
2016
|
+
}) : null;
|
|
2017
|
+
if (attrDynamic?.exp && attrDynamicAst) {
|
|
2018
|
+
let value = getVueExpressionSource(attrDynamic.exp, "loc", "compiled");
|
|
2019
|
+
if (isMemberExpression(attrDynamicAst) || isOptionalMemberExpression(attrDynamicAst)) {
|
|
1542
2020
|
return staticAttributeValue(`${actualFileName}-${value.replaceAll(".", "")}-${role}`);
|
|
1543
2021
|
}
|
|
1544
|
-
if (
|
|
1545
|
-
value =
|
|
2022
|
+
if (isCallExpression(attrDynamicAst) || isOptionalCallExpression(attrDynamicAst)) {
|
|
2023
|
+
value = getVueExpressionSource(attrDynamic.exp, "compiled", "loc");
|
|
1546
2024
|
return templateAttributeValue(`${actualFileName}-\${${value}}-${role}`);
|
|
1547
2025
|
}
|
|
1548
2026
|
return staticAttributeValue(`${actualFileName}-${value}-${role}`);
|
|
@@ -1550,16 +2028,16 @@ function getDataTestIdValueFromValueAttribute(node, actualFileName, attributeKey
|
|
|
1550
2028
|
return null;
|
|
1551
2029
|
}
|
|
1552
2030
|
function generateToDirectiveDataTestId(componentName, node, toDirective, context, hierarchyMap2, nativeWrappers) {
|
|
1553
|
-
const
|
|
1554
|
-
if (
|
|
1555
|
-
return templateAttributeValue(`${componentName}-${
|
|
2031
|
+
const keyInfo = getKeyDirectiveInfo(node) || getContainedInVForDirectiveKeyInfo(context, node, hierarchyMap2);
|
|
2032
|
+
if (keyInfo) {
|
|
2033
|
+
return templateAttributeValue(`${componentName}-${keyInfo.selectorFragment}-${formatTagName(node, nativeWrappers)}`);
|
|
1556
2034
|
} else {
|
|
1557
2035
|
let name = toDirectiveObjectFieldNameValue(toDirective);
|
|
1558
2036
|
if (!name) {
|
|
1559
2037
|
if (toDirective.exp == null) {
|
|
1560
2038
|
return null;
|
|
1561
2039
|
}
|
|
1562
|
-
const source =
|
|
2040
|
+
const source = getVueExpressionSource(toDirective.exp, "compiled", "loc");
|
|
1563
2041
|
const toAst = toDirective.exp.ast;
|
|
1564
2042
|
const interpolated = toAst !== void 0 && toAst !== null && toAst !== false && isTemplateLiteral(toAst);
|
|
1565
2043
|
return templateAttributeValue(`${componentName}-\${${source}${interpolated ? ".replaceAll(' ', '')" : "?.name?.replaceAll(' ', '') ?? ''"}}${formatTagName(node, nativeWrappers)}`);
|
|
@@ -1583,44 +2061,46 @@ function toDirectiveObjectFieldNameValue(node) {
|
|
|
1583
2061
|
if (!node.exp || node.exp.type !== NodeTypes.COMPOUND_EXPRESSION && node.exp.type !== NodeTypes.SIMPLE_EXPRESSION) {
|
|
1584
2062
|
return null;
|
|
1585
2063
|
}
|
|
1586
|
-
const
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
const isStringLiteralNode = (n) => {
|
|
1593
|
-
return isNodeType(n, "StringLiteral") && typeof n.value === "string";
|
|
1594
|
-
};
|
|
1595
|
-
const isIdentifierNode = (n) => {
|
|
1596
|
-
return isNodeType(n, "Identifier") && typeof n.name === "string";
|
|
1597
|
-
};
|
|
1598
|
-
const isObjectPropertyNode = (n) => {
|
|
1599
|
-
if (!isNodeType(n, "ObjectProperty"))
|
|
1600
|
-
return false;
|
|
1601
|
-
const nn = n;
|
|
1602
|
-
return typeof nn.key === "object" && nn.key !== null && typeof nn.value === "object" && nn.value !== null;
|
|
1603
|
-
};
|
|
1604
|
-
const isObjectExpressionNode = (n) => {
|
|
1605
|
-
if (!isNodeType(n, "ObjectExpression"))
|
|
1606
|
-
return false;
|
|
1607
|
-
const nn = n;
|
|
1608
|
-
return Array.isArray(nn.properties);
|
|
1609
|
-
};
|
|
1610
|
-
if (!isObjectExpressionNode(expr))
|
|
1611
|
-
return null;
|
|
1612
|
-
const nameProp = expr.properties.find((p) => {
|
|
1613
|
-
if (!isObjectPropertyNode(p))
|
|
1614
|
-
return false;
|
|
1615
|
-
const key = p.key;
|
|
1616
|
-
return isIdentifierNode(key) && key.name === "name" || isStringLiteralNode(key) && key.value === "name";
|
|
1617
|
-
});
|
|
1618
|
-
if (!nameProp || !isObjectPropertyNode(nameProp) || !isStringLiteralNode(nameProp.value))
|
|
1619
|
-
return null;
|
|
1620
|
-
return toPascalCase(nameProp.value.value);
|
|
1621
|
-
} catch {
|
|
2064
|
+
const expr = tryGetDirectiveBabelAst(node, {
|
|
2065
|
+
preferredViews: ["loc", "compiled"],
|
|
2066
|
+
plugins: ["typescript"],
|
|
2067
|
+
preferExistingAst: false
|
|
2068
|
+
});
|
|
2069
|
+
if (!expr) {
|
|
1622
2070
|
return null;
|
|
1623
2071
|
}
|
|
2072
|
+
const isNodeType2 = (n, type) => {
|
|
2073
|
+
return n !== null && n.type === type;
|
|
2074
|
+
};
|
|
2075
|
+
const isStringLiteralNode2 = (n) => {
|
|
2076
|
+
return isNodeType2(n, "StringLiteral") && typeof n.value === "string";
|
|
2077
|
+
};
|
|
2078
|
+
const isIdentifierNode2 = (n) => {
|
|
2079
|
+
return isNodeType2(n, "Identifier") && typeof n.name === "string";
|
|
2080
|
+
};
|
|
2081
|
+
const isObjectPropertyNode2 = (n) => {
|
|
2082
|
+
if (!isNodeType2(n, "ObjectProperty"))
|
|
2083
|
+
return false;
|
|
2084
|
+
const nn = n;
|
|
2085
|
+
return typeof nn.key === "object" && nn.key !== null && typeof nn.value === "object" && nn.value !== null;
|
|
2086
|
+
};
|
|
2087
|
+
const isObjectExpressionNode2 = (n) => {
|
|
2088
|
+
if (!isNodeType2(n, "ObjectExpression"))
|
|
2089
|
+
return false;
|
|
2090
|
+
const nn = n;
|
|
2091
|
+
return Array.isArray(nn.properties);
|
|
2092
|
+
};
|
|
2093
|
+
if (!isObjectExpressionNode2(expr))
|
|
2094
|
+
return null;
|
|
2095
|
+
const nameProp = expr.properties.find((p) => {
|
|
2096
|
+
if (!isObjectPropertyNode2(p))
|
|
2097
|
+
return false;
|
|
2098
|
+
const key = p.key;
|
|
2099
|
+
return isIdentifierNode2(key) && key.name === "name" || isStringLiteralNode2(key) && key.value === "name";
|
|
2100
|
+
});
|
|
2101
|
+
if (!nameProp || !isObjectPropertyNode2(nameProp) || !isStringLiteralNode2(nameProp.value))
|
|
2102
|
+
return null;
|
|
2103
|
+
return toPascalCase(nameProp.value.value);
|
|
1624
2104
|
}
|
|
1625
2105
|
function getComposedClickHandlerContent(node, _context, innerText, clickDirective, _options = {}) {
|
|
1626
2106
|
const click = clickDirective ?? tryGetClickDirective(node);
|
|
@@ -1630,7 +2110,7 @@ function getComposedClickHandlerContent(node, _context, innerText, clickDirectiv
|
|
|
1630
2110
|
let handlerName = "";
|
|
1631
2111
|
if (click.exp) {
|
|
1632
2112
|
const exp = click.exp;
|
|
1633
|
-
const source = (exp
|
|
2113
|
+
const source = getVueExpressionSource(exp, "content", "compiled");
|
|
1634
2114
|
if (source) {
|
|
1635
2115
|
const parsed = tryParseBabelAstFromHandlerSource(source);
|
|
1636
2116
|
if (parsed) {
|
|
@@ -1651,9 +2131,9 @@ function tryParseBabelAstFromHandlerSource(source) {
|
|
|
1651
2131
|
const trimmed = source.trim();
|
|
1652
2132
|
if (!trimmed)
|
|
1653
2133
|
return null;
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
2134
|
+
const expressionAst = tryParseBabelExpressionFromSource(trimmed, ["typescript", "jsx"]);
|
|
2135
|
+
if (expressionAst) {
|
|
2136
|
+
return expressionAst;
|
|
1657
2137
|
}
|
|
1658
2138
|
try {
|
|
1659
2139
|
return parse(trimmed, { sourceType: "module", plugins: ["typescript", "jsx"] });
|
|
@@ -1992,23 +2472,24 @@ function tryGetExistingElementDataTestId(node, attributeName = "data-testid") {
|
|
|
1992
2472
|
return null;
|
|
1993
2473
|
}
|
|
1994
2474
|
const simpleExp = exp;
|
|
1995
|
-
const ast = simpleExp
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
const
|
|
2002
|
-
const unwrappedTemplate = raw2.startsWith("`") && raw2.endsWith("`") && raw2.length >= 2 ? raw2.slice(1, -1) : cooked;
|
|
2475
|
+
const ast = tryGetVueExpressionAst(simpleExp, {
|
|
2476
|
+
preferredViews: ["content", "loc", "compiled"],
|
|
2477
|
+
plugins: ["typescript"]
|
|
2478
|
+
});
|
|
2479
|
+
const unwrappedTemplateLiteral = tryUnwrapTemplateLiteralExpressionSource(simpleExp);
|
|
2480
|
+
if (unwrappedTemplateLiteral) {
|
|
2481
|
+
const isStatic = unwrappedTemplateLiteral.expressionCount === 0;
|
|
2003
2482
|
if (isStatic) {
|
|
2004
|
-
return { value:
|
|
2483
|
+
return { value: unwrappedTemplateLiteral.template, isDynamic: false, isStaticLiteral: true };
|
|
2005
2484
|
}
|
|
2485
|
+
const templateValue = templateAttributeValue(unwrappedTemplateLiteral.template);
|
|
2006
2486
|
return {
|
|
2007
|
-
value:
|
|
2487
|
+
value: templateValue.template,
|
|
2008
2488
|
isDynamic: true,
|
|
2009
2489
|
isStaticLiteral: false,
|
|
2010
|
-
template:
|
|
2011
|
-
|
|
2490
|
+
template: templateValue.template,
|
|
2491
|
+
parsedTemplate: templateValue.parsedTemplate,
|
|
2492
|
+
templateExpressionCount: unwrappedTemplateLiteral.expressionCount
|
|
2012
2493
|
};
|
|
2013
2494
|
}
|
|
2014
2495
|
if (ast && typeof ast === "object" && "type" in ast && ast.type === "StringLiteral") {
|
|
@@ -2021,59 +2502,27 @@ function tryGetExistingElementDataTestId(node, attributeName = "data-testid") {
|
|
|
2021
2502
|
}
|
|
2022
2503
|
const preservableReference = tryGetPreservableDynamicReferenceExpression(ast);
|
|
2023
2504
|
if (preservableReference) {
|
|
2505
|
+
const templateValue = templateAttributeValue(`\${${preservableReference}}`);
|
|
2024
2506
|
return {
|
|
2025
2507
|
value: preservableReference,
|
|
2026
|
-
isDynamic: true,
|
|
2027
|
-
isStaticLiteral: false,
|
|
2028
|
-
template:
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
try {
|
|
2038
|
-
const ast2 = parseExpression(raw, { plugins: ["typescript"] });
|
|
2039
|
-
if (ast2 && typeof ast2 === "object" && "type" in ast2 && ast2.type === "TemplateLiteral") {
|
|
2040
|
-
const tl = ast2;
|
|
2041
|
-
const cooked = (tl.quasis ?? []).map((q) => q.value?.cooked ?? "").join("");
|
|
2042
|
-
const expressionCount = (tl.expressions ?? []).length;
|
|
2043
|
-
const isStatic = expressionCount === 0;
|
|
2044
|
-
const unwrappedTemplate = raw.startsWith("`") && raw.endsWith("`") && raw.length >= 2 ? raw.slice(1, -1) : cooked;
|
|
2045
|
-
if (isStatic) {
|
|
2046
|
-
return { value: unwrappedTemplate, isDynamic: false, isStaticLiteral: true };
|
|
2047
|
-
}
|
|
2048
|
-
return {
|
|
2049
|
-
value: unwrappedTemplate,
|
|
2050
|
-
isDynamic: true,
|
|
2051
|
-
isStaticLiteral: false,
|
|
2052
|
-
template: unwrappedTemplate,
|
|
2053
|
-
templateExpressionCount: expressionCount
|
|
2054
|
-
};
|
|
2055
|
-
}
|
|
2056
|
-
if (ast2 && typeof ast2 === "object" && "type" in ast2 && ast2.type === "StringLiteral") {
|
|
2057
|
-
const sl = ast2;
|
|
2058
|
-
return { value: sl.value ?? "", isDynamic: false, isStaticLiteral: true };
|
|
2059
|
-
}
|
|
2060
|
-
const preservableReference2 = tryGetPreservableDynamicReferenceExpression(ast2);
|
|
2061
|
-
if (preservableReference2) {
|
|
2062
|
-
return {
|
|
2063
|
-
value: preservableReference2,
|
|
2064
|
-
isDynamic: true,
|
|
2065
|
-
isStaticLiteral: false,
|
|
2066
|
-
template: `\${${preservableReference2}}`,
|
|
2067
|
-
templateExpressionCount: 1,
|
|
2068
|
-
rawExpression: preservableReference2
|
|
2069
|
-
};
|
|
2070
|
-
}
|
|
2071
|
-
} catch {
|
|
2508
|
+
isDynamic: true,
|
|
2509
|
+
isStaticLiteral: false,
|
|
2510
|
+
template: templateValue.template,
|
|
2511
|
+
parsedTemplate: templateValue.parsedTemplate,
|
|
2512
|
+
templateExpressionCount: 1,
|
|
2513
|
+
rawExpression: preservableReference
|
|
2514
|
+
};
|
|
2515
|
+
}
|
|
2516
|
+
const raw = (simpleExp.content ?? "").trim();
|
|
2517
|
+
if (!raw) {
|
|
2518
|
+
return null;
|
|
2072
2519
|
}
|
|
2073
2520
|
return { value: raw, isDynamic: true, isStaticLiteral: false, rawExpression: raw };
|
|
2074
2521
|
}
|
|
2075
2522
|
function isTemplatePlaceholder(part) {
|
|
2076
|
-
|
|
2523
|
+
const parsedTemplate = tryParseTemplateFragment(part);
|
|
2524
|
+
const templateFragment = parsedTemplate ? getSingleExpressionTemplateFragment(parsedTemplate) : null;
|
|
2525
|
+
return !!templateFragment && templateFragment.prefix === "" && templateFragment.suffix === "";
|
|
2077
2526
|
}
|
|
2078
2527
|
function isAllCapsOrDigits(value) {
|
|
2079
2528
|
if (value.length <= 1) {
|
|
@@ -2126,33 +2575,14 @@ function safeMethodNameFromParts(parts) {
|
|
|
2126
2575
|
}
|
|
2127
2576
|
return name;
|
|
2128
2577
|
}
|
|
2129
|
-
function
|
|
2578
|
+
function toPomKeyPattern(templateValue) {
|
|
2579
|
+
const { templateLiteral } = templateValue.parsedTemplate;
|
|
2130
2580
|
let out = "";
|
|
2131
|
-
let i = 0;
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
out += template.slice(i);
|
|
2136
|
-
break;
|
|
2137
|
-
}
|
|
2138
|
-
out += template.slice(i, start);
|
|
2139
|
-
let depth = 1;
|
|
2140
|
-
let j = start + 2;
|
|
2141
|
-
while (j < template.length && depth > 0) {
|
|
2142
|
-
if (template[j] === "{") {
|
|
2143
|
-
depth++;
|
|
2144
|
-
} else if (template[j] === "}") {
|
|
2145
|
-
depth--;
|
|
2146
|
-
}
|
|
2147
|
-
j++;
|
|
2148
|
-
}
|
|
2149
|
-
const end = depth === 0 ? j - 1 : -1;
|
|
2150
|
-
if (end < 0) {
|
|
2151
|
-
out += template.slice(start);
|
|
2152
|
-
break;
|
|
2581
|
+
for (let i = 0; i < templateLiteral.quasis.length; i += 1) {
|
|
2582
|
+
out += templateLiteral.quasis[i]?.value.raw ?? "";
|
|
2583
|
+
if (templateLiteral.expressions[i]) {
|
|
2584
|
+
out += "${key}";
|
|
2153
2585
|
}
|
|
2154
|
-
out += "${key}";
|
|
2155
|
-
i = end + 1;
|
|
2156
2586
|
}
|
|
2157
2587
|
return out;
|
|
2158
2588
|
}
|
|
@@ -2160,8 +2590,8 @@ function applyResolvedDataTestId(args) {
|
|
|
2160
2590
|
const addHtmlAttribute = args.addHtmlAttribute ?? true;
|
|
2161
2591
|
const entryOverrides = args.entryOverrides ?? {};
|
|
2162
2592
|
const testIdAttribute = args.testIdAttribute ?? "data-testid";
|
|
2163
|
-
const existingIdBehavior = args.existingIdBehavior ?? "
|
|
2164
|
-
const nameCollisionBehavior = args.nameCollisionBehavior ?? "
|
|
2593
|
+
const existingIdBehavior = args.existingIdBehavior ?? "error";
|
|
2594
|
+
const nameCollisionBehavior = args.nameCollisionBehavior ?? "error";
|
|
2165
2595
|
const warn = args.warn;
|
|
2166
2596
|
const getBestKeyAccessCandidates = (expr) => {
|
|
2167
2597
|
if (!expr) {
|
|
@@ -2170,7 +2600,10 @@ function applyResolvedDataTestId(args) {
|
|
|
2170
2600
|
return splitNullishCoalescingExpression(expr);
|
|
2171
2601
|
};
|
|
2172
2602
|
let dataTestId = args.preferredGeneratedValue;
|
|
2603
|
+
let runtimeDataTestId = args.preferredRuntimeValue ?? args.preferredGeneratedValue;
|
|
2173
2604
|
let fromExisting = false;
|
|
2605
|
+
const bestKeyPreservePlaceholder = args.keyInfo?.runtimeFragment ?? null;
|
|
2606
|
+
const bestKeyVariable = args.keyInfo?.rawExpression ?? null;
|
|
2174
2607
|
const existing = tryGetExistingElementDataTestId(args.element, testIdAttribute);
|
|
2175
2608
|
if (existing) {
|
|
2176
2609
|
const loc = args.element.loc?.start;
|
|
@@ -2191,8 +2624,10 @@ Bulk cleanup: run ESLint with the @immense/vue-pom-generator/remove-existing-tes
|
|
|
2191
2624
|
if (existingIdBehavior === "preserve") {
|
|
2192
2625
|
if (existing.isDynamic) {
|
|
2193
2626
|
if (existing.template) {
|
|
2194
|
-
const
|
|
2195
|
-
|
|
2627
|
+
const existingTemplateValue = existing.parsedTemplate ? { kind: "template", template: existing.template, parsedTemplate: existing.parsedTemplate } : templateAttributeValue(existing.template);
|
|
2628
|
+
const existingTemplateFragment = getSingleExpressionTemplateFragment(existingTemplateValue.parsedTemplate);
|
|
2629
|
+
const requiredKeyTemplateValue = bestKeyPreservePlaceholder ? templateAttributeValue(bestKeyPreservePlaceholder) : null;
|
|
2630
|
+
if ((existing.templateExpressionCount ?? 0) !== 1 || !existingTemplateFragment) {
|
|
2196
2631
|
throw new Error(
|
|
2197
2632
|
`[vue-pom-generator] Existing ${attrLabel} is a template literal with multiple interpolations and cannot be preserved safely.
|
|
2198
2633
|
Component: ${args.componentName}
|
|
@@ -2202,20 +2637,21 @@ Existing ${attrLabel}: ${JSON.stringify(existing.value)}
|
|
|
2202
2637
|
Fix: reduce the template to a single key-based interpolation, or remove the explicit ${attrLabel} so it can be auto-generated.`
|
|
2203
2638
|
);
|
|
2204
2639
|
}
|
|
2205
|
-
const hasExact =
|
|
2206
|
-
const hasVarAccess = getBestKeyAccessCandidates(
|
|
2207
|
-
if (!hasExact && !hasVarAccess &&
|
|
2640
|
+
const hasExact = requiredKeyTemplateValue ? templateFragmentContainsSingleExpression(existingTemplateValue.parsedTemplate, requiredKeyTemplateValue.parsedTemplate) : false;
|
|
2641
|
+
const hasVarAccess = getBestKeyAccessCandidates(bestKeyVariable).some((candidate) => existingTemplateFragment.expressionSource === candidate);
|
|
2642
|
+
if (!hasExact && !hasVarAccess && bestKeyPreservePlaceholder) {
|
|
2208
2643
|
throw new Error(
|
|
2209
2644
|
`[vue-pom-generator] Existing ${attrLabel} appears to be missing the key placeholder needed to keep it unique.
|
|
2210
2645
|
Component: ${args.componentName}
|
|
2211
2646
|
File: ${file}:${locationHint}
|
|
2212
2647
|
Existing ${attrLabel}: ${JSON.stringify(existing.value)}
|
|
2213
|
-
Required placeholder: ${JSON.stringify(
|
|
2648
|
+
Required placeholder: ${JSON.stringify(bestKeyPreservePlaceholder)}${bestKeyVariable ? ` or an access on "${bestKeyVariable}"` : ""}
|
|
2214
2649
|
|
|
2215
|
-
Fix: either (1) include ${
|
|
2650
|
+
Fix: either (1) include ${bestKeyPreservePlaceholder} in your :${attrLabel} template literal, or (2) remove the explicit ${attrLabel} so it can be auto-generated.`
|
|
2216
2651
|
);
|
|
2217
2652
|
}
|
|
2218
|
-
dataTestId =
|
|
2653
|
+
dataTestId = existingTemplateValue;
|
|
2654
|
+
runtimeDataTestId = existingTemplateValue;
|
|
2219
2655
|
fromExisting = true;
|
|
2220
2656
|
} else {
|
|
2221
2657
|
throw new Error(
|
|
@@ -2229,18 +2665,19 @@ If you really need a computed id, do not set existingIdBehavior="preserve".`
|
|
|
2229
2665
|
);
|
|
2230
2666
|
}
|
|
2231
2667
|
} else {
|
|
2232
|
-
if (
|
|
2668
|
+
if (bestKeyPreservePlaceholder && existing.isStaticLiteral) {
|
|
2233
2669
|
throw new Error(
|
|
2234
2670
|
`[vue-pom-generator] Existing ${attrLabel} appears to be missing the key placeholder needed to keep it unique.
|
|
2235
2671
|
Component: ${args.componentName}
|
|
2236
2672
|
File: ${file}:${locationHint}
|
|
2237
2673
|
Existing ${attrLabel}: ${JSON.stringify(existing.value)}
|
|
2238
|
-
Required placeholder: ${JSON.stringify(
|
|
2674
|
+
Required placeholder: ${JSON.stringify(bestKeyPreservePlaceholder)}${bestKeyVariable ? ` or an access on "${bestKeyVariable}"` : ""}
|
|
2239
2675
|
|
|
2240
|
-
Fix: either (1) include ${
|
|
2676
|
+
Fix: either (1) include ${bestKeyPreservePlaceholder} in your :${attrLabel} template literal, or (2) remove the explicit ${attrLabel} so it can be auto-generated.`
|
|
2241
2677
|
);
|
|
2242
2678
|
}
|
|
2243
2679
|
dataTestId = staticAttributeValue(existing.value);
|
|
2680
|
+
runtimeDataTestId = staticAttributeValue(existing.value);
|
|
2244
2681
|
fromExisting = true;
|
|
2245
2682
|
}
|
|
2246
2683
|
}
|
|
@@ -2270,8 +2707,10 @@ Fix: either (1) include ${args.bestKeyPlaceholder} in your :${attrLabel} templat
|
|
|
2270
2707
|
};
|
|
2271
2708
|
const normalizedRole = normalizeNativeRole(args.nativeRole) ?? "button";
|
|
2272
2709
|
const targetPageObjectModelClass = entryOverrides.targetPageObjectModelClass;
|
|
2273
|
-
const formattedDataTestIdForPom = dataTestId.kind === "template" ?
|
|
2274
|
-
const
|
|
2710
|
+
const formattedDataTestIdForPom = dataTestId.kind === "template" ? toPomKeyPattern(dataTestId) : dataTestId.value;
|
|
2711
|
+
const selectorPatternKind = dataTestId.kind === "template" ? "parameterized" : "static";
|
|
2712
|
+
const selectorPattern = createPomStringPattern(formattedDataTestIdForPom, selectorPatternKind);
|
|
2713
|
+
const selectorIsParameterized = selectorPatternKind === "parameterized";
|
|
2275
2714
|
const deriveBaseMethodNameFromHint = (hint) => {
|
|
2276
2715
|
const hintRaw = (hint ?? "").trim();
|
|
2277
2716
|
const trimEdgeSeparators = (value) => {
|
|
@@ -2319,13 +2758,13 @@ Fix: either (1) include ${args.bestKeyPlaceholder} in your :${attrLabel} templat
|
|
|
2319
2758
|
const roleSuffix = upperFirst(normalizedRole || "Element");
|
|
2320
2759
|
const baseName = upperFirst(primaryMethodName);
|
|
2321
2760
|
const propertyName = hasRoleSuffix(baseName, roleSuffix) ? baseName : `${baseName}${roleSuffix}`;
|
|
2322
|
-
return
|
|
2761
|
+
return selectorIsParameterized ? removeByKeySegment2(propertyName) : propertyName;
|
|
2323
2762
|
};
|
|
2324
2763
|
const getPrimaryGetterNameCandidates = (primaryMethodName) => {
|
|
2325
2764
|
const roleSuffix = upperFirst(normalizedRole || "Element");
|
|
2326
2765
|
const baseName = upperFirst(primaryMethodName);
|
|
2327
2766
|
const propertyName = hasRoleSuffix(baseName, roleSuffix) ? baseName : `${baseName}${roleSuffix}`;
|
|
2328
|
-
if (!
|
|
2767
|
+
if (!selectorIsParameterized) {
|
|
2329
2768
|
return { primary: propertyName };
|
|
2330
2769
|
}
|
|
2331
2770
|
const stripped = removeByKeySegment2(propertyName);
|
|
@@ -2387,11 +2826,11 @@ Fix: either (1) include ${args.bestKeyPlaceholder} in your :${attrLabel} templat
|
|
|
2387
2826
|
return false;
|
|
2388
2827
|
}
|
|
2389
2828
|
const existingSelectors = [
|
|
2390
|
-
existingPom.
|
|
2391
|
-
...existingPom.
|
|
2829
|
+
existingPom.selector,
|
|
2830
|
+
...existingPom.alternateSelectors ?? []
|
|
2392
2831
|
];
|
|
2393
|
-
const sharesSelectorIdentity = existingSelectors.
|
|
2394
|
-
if (
|
|
2832
|
+
const sharesSelectorIdentity = existingSelectors.some((existingSelector) => pomStringPatternEquals(existingSelector, selectorPattern));
|
|
2833
|
+
if (selectorIsParameterized && !sharesSelectorIdentity) {
|
|
2395
2834
|
return false;
|
|
2396
2835
|
}
|
|
2397
2836
|
if (!mergeKey && !sharesSelectorIdentity) {
|
|
@@ -2406,12 +2845,15 @@ Fix: either (1) include ${args.bestKeyPlaceholder} in your :${attrLabel} templat
|
|
|
2406
2845
|
if ((existingEntry.targetPageObjectModelClass ?? null) !== (targetPageObjectModelClass ?? null)) {
|
|
2407
2846
|
return false;
|
|
2408
2847
|
}
|
|
2409
|
-
if (existingPom.
|
|
2410
|
-
existingPom.
|
|
2411
|
-
if (!existingPom.
|
|
2412
|
-
existingPom.
|
|
2848
|
+
if (!pomStringPatternEquals(existingPom.selector, selectorPattern)) {
|
|
2849
|
+
existingPom.alternateSelectors ??= [];
|
|
2850
|
+
if (!(existingPom.alternateSelectors ?? []).some((existingSelector) => pomStringPatternEquals(existingSelector, selectorPattern))) {
|
|
2851
|
+
existingPom.alternateSelectors.push(selectorPattern);
|
|
2413
2852
|
}
|
|
2414
2853
|
}
|
|
2854
|
+
if (selectorIsParameterized && !existingPom.parameters.some((param) => param.name === "key")) {
|
|
2855
|
+
existingPom.parameters = [createPomParameterSpec("key", keyTypeFromValues), ...existingPom.parameters];
|
|
2856
|
+
}
|
|
2415
2857
|
return true;
|
|
2416
2858
|
};
|
|
2417
2859
|
let methodName = "";
|
|
@@ -2424,7 +2866,7 @@ Fix: either (1) include ${args.bestKeyPlaceholder} in your :${attrLabel} templat
|
|
|
2424
2866
|
let suffix = 1;
|
|
2425
2867
|
while (true) {
|
|
2426
2868
|
const baseWithSuffix = suffix === 1 ? base : `${base}${suffix}`;
|
|
2427
|
-
const candidate =
|
|
2869
|
+
const candidate = selectorIsParameterized ? `${baseWithSuffix}ByKey` : baseWithSuffix;
|
|
2428
2870
|
const actionName = getPrimaryActionMethodName(candidate);
|
|
2429
2871
|
const getterCandidates = getPrimaryGetterNameCandidates(candidate);
|
|
2430
2872
|
let chosenGetterName = getterCandidates.primary;
|
|
@@ -2450,7 +2892,7 @@ Fix: either (1) include ${args.bestKeyPlaceholder} in your :${attrLabel} templat
|
|
|
2450
2892
|
const baseNameUpper = upperFirst(baseWithSuffix);
|
|
2451
2893
|
if (!hasRoleSuffix(baseNameUpper, roleSuffix)) {
|
|
2452
2894
|
const baseWithRoleSuffix = `${baseWithSuffix}${roleSuffix}`;
|
|
2453
|
-
const candidateWithRoleSuffix =
|
|
2895
|
+
const candidateWithRoleSuffix = selectorIsParameterized ? `${baseWithRoleSuffix}ByKey` : baseWithRoleSuffix;
|
|
2454
2896
|
const actionNameWithRoleSuffix = getPrimaryActionMethodName(candidateWithRoleSuffix);
|
|
2455
2897
|
const getterCandidatesWithRoleSuffix = getPrimaryGetterNameCandidates(candidateWithRoleSuffix);
|
|
2456
2898
|
let chosenGetterNameWithRoleSuffix = getterCandidatesWithRoleSuffix.primary;
|
|
@@ -2518,51 +2960,46 @@ Conflicts: getter=${last.getterName}, method=${last.actionName}
|
|
|
2518
2960
|
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".`
|
|
2519
2961
|
);
|
|
2520
2962
|
}
|
|
2521
|
-
|
|
2522
|
-
if (isKeyed) {
|
|
2523
|
-
params.key = keyTypeFromValues;
|
|
2524
|
-
}
|
|
2963
|
+
let parameters = selectorIsParameterized ? [createPomParameterSpec("key", keyTypeFromValues)] : [];
|
|
2525
2964
|
switch (normalizedRole) {
|
|
2526
2965
|
case "input":
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
if (!isKeyed) delete params.key;
|
|
2966
|
+
parameters = setPomParameter(parameters, "text", "string");
|
|
2967
|
+
parameters = setPomParameter(parameters, "annotationText", 'string = ""');
|
|
2530
2968
|
break;
|
|
2531
2969
|
case "select":
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
if (!isKeyed) delete params.key;
|
|
2970
|
+
parameters = setPomParameter(parameters, "value", "string");
|
|
2971
|
+
parameters = setPomParameter(parameters, "annotationText", 'string = ""');
|
|
2535
2972
|
break;
|
|
2536
2973
|
case "vselect":
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
if (!isKeyed) delete params.key;
|
|
2974
|
+
parameters = setPomParameter(parameters, "value", "string");
|
|
2975
|
+
parameters = setPomParameter(parameters, "timeOut", "number = 500");
|
|
2976
|
+
parameters = setPomParameter(parameters, "annotationText", 'string = ""');
|
|
2541
2977
|
break;
|
|
2542
2978
|
case "radio":
|
|
2543
|
-
|
|
2979
|
+
parameters = setPomParameter(parameters, "annotationText", 'string = ""');
|
|
2544
2980
|
break;
|
|
2545
2981
|
}
|
|
2546
|
-
|
|
2547
|
-
params.key = keyTypeFromValues;
|
|
2548
|
-
}
|
|
2982
|
+
const normalizedParameters = selectorIsParameterized ? setPomParameter(parameters, "key", keyTypeFromValues) : removePomParameter(parameters, "key");
|
|
2549
2983
|
if (addHtmlAttribute && !fromExisting) {
|
|
2550
2984
|
upsertAttribute(args.element, testIdAttribute, dataTestId);
|
|
2551
2985
|
}
|
|
2552
2986
|
const childComponentName = args.element.tag;
|
|
2553
2987
|
const dataTestIdEntry = {
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2988
|
+
selectorValue: entryOverrides.selectorValue ?? createPomStringPattern(
|
|
2989
|
+
getAttributeValueText(dataTestId),
|
|
2990
|
+
dataTestId.kind === "template" ? "parameterized" : "static"
|
|
2991
|
+
),
|
|
2992
|
+
templateLiteral: entryOverrides.templateLiteral,
|
|
2993
|
+
targetPageObjectModelClass: entryOverrides.targetPageObjectModelClass
|
|
2557
2994
|
};
|
|
2558
2995
|
dataTestIdEntry.pom = {
|
|
2559
2996
|
nativeRole: normalizedRole,
|
|
2560
2997
|
methodName,
|
|
2561
2998
|
getterNameOverride,
|
|
2562
|
-
|
|
2563
|
-
|
|
2999
|
+
selector: selectorPattern,
|
|
3000
|
+
alternateSelectors: void 0,
|
|
2564
3001
|
mergeKey: args.pomMergeKey,
|
|
2565
|
-
|
|
3002
|
+
parameters: normalizedParameters,
|
|
2566
3003
|
keyValuesOverride: args.keyValuesOverride ?? null
|
|
2567
3004
|
// emitPrimary defaults to true; special cases (including merge) may set it to false below.
|
|
2568
3005
|
};
|
|
@@ -2593,43 +3030,18 @@ Fix: make the element identifiable (e.g. add id/name/inner text or use a more sp
|
|
|
2593
3030
|
}
|
|
2594
3031
|
};
|
|
2595
3032
|
const getSignatureForGeneratedMethod = () => {
|
|
2596
|
-
|
|
2597
|
-
const isNavigation2 = !!dataTestIdEntry.targetPageObjectModelClass;
|
|
2598
|
-
const needsKey2 = Object.prototype.hasOwnProperty.call(params, "key");
|
|
2599
|
-
const keyType = keyTypeFromValues;
|
|
2600
|
-
if (isNavigation2) {
|
|
2601
|
-
if (needsKey2) {
|
|
2602
|
-
return { params: `key: ${keyType}`, argNames: ["key"] };
|
|
2603
|
-
}
|
|
2604
|
-
return { params: "", argNames: [] };
|
|
2605
|
-
}
|
|
2606
|
-
switch (role) {
|
|
2607
|
-
case "input":
|
|
2608
|
-
return needsKey2 ? { params: `key: ${keyType}, text: string, annotationText: string = ""`, argNames: ["key", "text", "annotationText"] } : { params: 'text: string, annotationText: string = ""', argNames: ["text", "annotationText"] };
|
|
2609
|
-
case "select":
|
|
2610
|
-
return needsKey2 ? { params: `key: ${keyType}, value: string, annotationText: string = ""`, argNames: ["key", "value", "annotationText"] } : { params: 'value: string, annotationText: string = ""', argNames: ["value", "annotationText"] };
|
|
2611
|
-
case "vselect":
|
|
2612
|
-
return needsKey2 ? { params: `key: ${keyType}, value: string, timeOut = 500`, argNames: ["key", "value", "timeOut"] } : { params: "value: string, timeOut = 500", argNames: ["value", "timeOut"] };
|
|
2613
|
-
case "radio":
|
|
2614
|
-
return needsKey2 ? { params: `key: ${keyType}, annotationText: string = ""`, argNames: ["key", "annotationText"] } : { params: 'annotationText: string = ""', argNames: ["annotationText"] };
|
|
2615
|
-
default:
|
|
2616
|
-
if (needsKey2) {
|
|
2617
|
-
return { params: `key: ${keyType}`, argNames: ["key"] };
|
|
2618
|
-
}
|
|
2619
|
-
return { params: "", argNames: [] };
|
|
2620
|
-
}
|
|
3033
|
+
return createPomMethodSignature(normalizedParameters);
|
|
2621
3034
|
};
|
|
2622
3035
|
const registerPrimaryOnce = (pom) => {
|
|
2623
|
-
const
|
|
2624
|
-
const alternates = (pom.alternateFormattedDataTestIds ?? []).slice().sort();
|
|
3036
|
+
const alternates = (pom.alternateSelectors ?? []).slice().sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b)));
|
|
2625
3037
|
const key = JSON.stringify({
|
|
2626
3038
|
kind: "primary",
|
|
2627
3039
|
role: pom.nativeRole,
|
|
2628
3040
|
methodName: pom.methodName,
|
|
2629
3041
|
getterNameOverride: pom.getterNameOverride ?? null,
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
3042
|
+
selector: pom.selector,
|
|
3043
|
+
alternateSelectors: alternates.length ? alternates : void 0,
|
|
3044
|
+
parameters: pom.parameters,
|
|
2633
3045
|
target: dataTestIdEntry.targetPageObjectModelClass ?? null,
|
|
2634
3046
|
emitPrimary: pom.emitPrimary ?? true
|
|
2635
3047
|
});
|
|
@@ -2643,8 +3055,7 @@ Fix: make the element identifiable (e.g. add id/name/inner text or use a more sp
|
|
|
2643
3055
|
}
|
|
2644
3056
|
};
|
|
2645
3057
|
const addExtraClickMethod = (spec) => {
|
|
2646
|
-
const
|
|
2647
|
-
const key = JSON.stringify({ kind: spec.kind, selector: spec.selector, keyLiteral: spec.keyLiteral ?? null, params: stableParams });
|
|
3058
|
+
const key = JSON.stringify({ kind: spec.kind, selector: spec.selector, keyLiteral: spec.keyLiteral ?? null, parameters: spec.parameters });
|
|
2648
3059
|
const seen = args.generatedMethodContentByComponent.get(args.parentComponentName) ?? /* @__PURE__ */ new Set();
|
|
2649
3060
|
if (!args.generatedMethodContentByComponent.has(args.parentComponentName)) {
|
|
2650
3061
|
args.generatedMethodContentByComponent.set(args.parentComponentName, seen);
|
|
@@ -2667,7 +3078,7 @@ Fix: make the element identifiable (e.g. add id/name/inner text or use a more sp
|
|
|
2667
3078
|
if (prev === null) {
|
|
2668
3079
|
return;
|
|
2669
3080
|
}
|
|
2670
|
-
if (signature === null || prev
|
|
3081
|
+
if (signature === null || !pomMethodSignatureEquals(prev, signature)) {
|
|
2671
3082
|
args.dependencies.generatedMethods.set(name, null);
|
|
2672
3083
|
}
|
|
2673
3084
|
};
|
|
@@ -2683,23 +3094,10 @@ Fix: make the element identifiable (e.g. add id/name/inner text or use a more sp
|
|
|
2683
3094
|
return candidate;
|
|
2684
3095
|
};
|
|
2685
3096
|
const tryGetDirectiveExpressionAst = (dir) => {
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
}
|
|
2690
|
-
if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {
|
|
2691
|
-
const simple = exp;
|
|
2692
|
-
const ast = simple.ast;
|
|
2693
|
-
if (ast && "type" in ast) {
|
|
2694
|
-
return ast;
|
|
2695
|
-
}
|
|
2696
|
-
}
|
|
2697
|
-
try {
|
|
2698
|
-
const raw = args.context ? stringifyExpression(exp) : exp.loc.source;
|
|
2699
|
-
return parseExpression(raw, { plugins: ["typescript"] });
|
|
2700
|
-
} catch {
|
|
2701
|
-
return null;
|
|
2702
|
-
}
|
|
3097
|
+
return tryGetDirectiveBabelAst(dir, {
|
|
3098
|
+
preferredViews: args.context ? ["compiled", "loc"] : ["loc", "compiled"],
|
|
3099
|
+
plugins: ["typescript"]
|
|
3100
|
+
});
|
|
2703
3101
|
};
|
|
2704
3102
|
const tryGetStaticStringFromBabel = (node) => {
|
|
2705
3103
|
if (!node) {
|
|
@@ -2796,17 +3194,17 @@ Fix: make the element identifiable (e.g. add id/name/inner text or use a more sp
|
|
|
2796
3194
|
name: generatedName2,
|
|
2797
3195
|
selector: {
|
|
2798
3196
|
kind: "withinTestIdByLabel",
|
|
2799
|
-
|
|
2800
|
-
|
|
3197
|
+
rootTestId: createPomStringPattern(wrapperTestId, selectorPatternKind),
|
|
3198
|
+
label: createPomStringPattern(label, "static"),
|
|
2801
3199
|
exact: true
|
|
2802
3200
|
},
|
|
2803
|
-
|
|
3201
|
+
parameters: [createPomParameterSpec("annotationText", `string = ""`)]
|
|
2804
3202
|
});
|
|
2805
3203
|
if (added2) {
|
|
2806
|
-
registerGeneratedMethodSignature(generatedName2,
|
|
3204
|
+
registerGeneratedMethodSignature(generatedName2, createPomMethodSignature([createPomParameterSpec("annotationText", `string = ""`)]));
|
|
2807
3205
|
}
|
|
2808
3206
|
}
|
|
2809
|
-
return;
|
|
3207
|
+
return { selectorValue: dataTestId, runtimeValue: runtimeDataTestId, fromExisting };
|
|
2810
3208
|
}
|
|
2811
3209
|
const generatedName = ensureUniqueGeneratedName(`select${upperFirst(methodName || "Radio")}`);
|
|
2812
3210
|
if (dataTestIdEntry.pom) {
|
|
@@ -2818,19 +3216,25 @@ Fix: make the element identifiable (e.g. add id/name/inner text or use a more sp
|
|
|
2818
3216
|
name: generatedName,
|
|
2819
3217
|
selector: {
|
|
2820
3218
|
kind: "withinTestIdByLabel",
|
|
2821
|
-
|
|
2822
|
-
|
|
3219
|
+
rootTestId: createPomStringPattern(wrapperTestId, selectorPatternKind),
|
|
3220
|
+
label: createPomStringPattern("${value}", "parameterized"),
|
|
2823
3221
|
exact: true
|
|
2824
3222
|
},
|
|
2825
|
-
|
|
3223
|
+
parameters: [
|
|
3224
|
+
createPomParameterSpec("value", "string"),
|
|
3225
|
+
createPomParameterSpec("annotationText", `string = ""`)
|
|
3226
|
+
]
|
|
2826
3227
|
});
|
|
2827
3228
|
if (added) {
|
|
2828
|
-
registerGeneratedMethodSignature(generatedName,
|
|
3229
|
+
registerGeneratedMethodSignature(generatedName, createPomMethodSignature([
|
|
3230
|
+
createPomParameterSpec("value", "string"),
|
|
3231
|
+
createPomParameterSpec("annotationText", `string = ""`)
|
|
3232
|
+
]));
|
|
2829
3233
|
}
|
|
2830
|
-
return;
|
|
3234
|
+
return { selectorValue: dataTestId, runtimeValue: runtimeDataTestId, fromExisting };
|
|
2831
3235
|
}
|
|
2832
3236
|
const staticKeyValues = args.keyValuesOverride ?? null;
|
|
2833
|
-
const needsKey =
|
|
3237
|
+
const needsKey = hasPomParameter(normalizedParameters, "key") && selectorIsParameterized;
|
|
2834
3238
|
const isNavigation = !!dataTestIdEntry.targetPageObjectModelClass;
|
|
2835
3239
|
if (staticKeyValues && staticKeyValues.length > 0 && needsKey && !isNavigation && normalizedRole !== "input" && normalizedRole !== "select" && normalizedRole !== "vselect" && normalizedRole !== "radio") {
|
|
2836
3240
|
if (dataTestIdEntry.pom) {
|
|
@@ -2849,16 +3253,19 @@ Fix: make the element identifiable (e.g. add id/name/inner text or use a more sp
|
|
|
2849
3253
|
name: generatedName,
|
|
2850
3254
|
selector: {
|
|
2851
3255
|
kind: "testId",
|
|
2852
|
-
|
|
3256
|
+
testId: selectorPattern
|
|
2853
3257
|
},
|
|
2854
3258
|
keyLiteral: rawValue,
|
|
2855
|
-
|
|
3259
|
+
parameters: [createPomParameterSpec("wait", "boolean = true"), createPomParameterSpec("annotationText", 'string = ""')]
|
|
2856
3260
|
});
|
|
2857
3261
|
if (added) {
|
|
2858
|
-
registerGeneratedMethodSignature(generatedName,
|
|
3262
|
+
registerGeneratedMethodSignature(generatedName, createPomMethodSignature([
|
|
3263
|
+
createPomParameterSpec("wait", "boolean = true"),
|
|
3264
|
+
createPomParameterSpec("annotationText", 'string = ""')
|
|
3265
|
+
]));
|
|
2859
3266
|
}
|
|
2860
3267
|
}
|
|
2861
|
-
return;
|
|
3268
|
+
return { selectorValue: dataTestId, runtimeValue: runtimeDataTestId, fromExisting };
|
|
2862
3269
|
}
|
|
2863
3270
|
if (dataTestIdEntry.pom) {
|
|
2864
3271
|
if (dataTestIdEntry.pom.emitPrimary !== false) {
|
|
@@ -2872,6 +3279,7 @@ Fix: make the element identifiable (e.g. add id/name/inner text or use a more sp
|
|
|
2872
3279
|
const generatedName = getGeneratedMethodName();
|
|
2873
3280
|
registerGeneratedMethodSignature(generatedName, signature);
|
|
2874
3281
|
}
|
|
3282
|
+
return { selectorValue: dataTestId, runtimeValue: runtimeDataTestId, fromExisting };
|
|
2875
3283
|
}
|
|
2876
3284
|
function safeRealpath(value) {
|
|
2877
3285
|
try {
|
|
@@ -3859,113 +4267,6 @@ class VuePomGeneratorError extends Error {
|
|
|
3859
4267
|
this.name = "VuePomGeneratorError";
|
|
3860
4268
|
}
|
|
3861
4269
|
}
|
|
3862
|
-
function splitParameterList(parameters) {
|
|
3863
|
-
const parts = [];
|
|
3864
|
-
let current = "";
|
|
3865
|
-
let braceDepth = 0;
|
|
3866
|
-
let bracketDepth = 0;
|
|
3867
|
-
let parenDepth = 0;
|
|
3868
|
-
let angleDepth = 0;
|
|
3869
|
-
let inSingleQuote = false;
|
|
3870
|
-
let inDoubleQuote = false;
|
|
3871
|
-
let inTemplateString = false;
|
|
3872
|
-
for (let index = 0; index < parameters.length; index += 1) {
|
|
3873
|
-
const char = parameters[index];
|
|
3874
|
-
const previous = index > 0 ? parameters[index - 1] : "";
|
|
3875
|
-
if (char === "'" && !inDoubleQuote && !inTemplateString && previous !== "\\") {
|
|
3876
|
-
inSingleQuote = !inSingleQuote;
|
|
3877
|
-
current += char;
|
|
3878
|
-
continue;
|
|
3879
|
-
}
|
|
3880
|
-
if (char === '"' && !inSingleQuote && !inTemplateString && previous !== "\\") {
|
|
3881
|
-
inDoubleQuote = !inDoubleQuote;
|
|
3882
|
-
current += char;
|
|
3883
|
-
continue;
|
|
3884
|
-
}
|
|
3885
|
-
if (char === "`" && !inSingleQuote && !inDoubleQuote && previous !== "\\") {
|
|
3886
|
-
inTemplateString = !inTemplateString;
|
|
3887
|
-
current += char;
|
|
3888
|
-
continue;
|
|
3889
|
-
}
|
|
3890
|
-
if (inSingleQuote || inDoubleQuote || inTemplateString) {
|
|
3891
|
-
current += char;
|
|
3892
|
-
continue;
|
|
3893
|
-
}
|
|
3894
|
-
switch (char) {
|
|
3895
|
-
case "{":
|
|
3896
|
-
braceDepth += 1;
|
|
3897
|
-
break;
|
|
3898
|
-
case "}":
|
|
3899
|
-
braceDepth -= 1;
|
|
3900
|
-
break;
|
|
3901
|
-
case "[":
|
|
3902
|
-
bracketDepth += 1;
|
|
3903
|
-
break;
|
|
3904
|
-
case "]":
|
|
3905
|
-
bracketDepth -= 1;
|
|
3906
|
-
break;
|
|
3907
|
-
case "(":
|
|
3908
|
-
parenDepth += 1;
|
|
3909
|
-
break;
|
|
3910
|
-
case ")":
|
|
3911
|
-
parenDepth -= 1;
|
|
3912
|
-
break;
|
|
3913
|
-
case "<":
|
|
3914
|
-
angleDepth += 1;
|
|
3915
|
-
break;
|
|
3916
|
-
case ">":
|
|
3917
|
-
angleDepth -= 1;
|
|
3918
|
-
break;
|
|
3919
|
-
case ",":
|
|
3920
|
-
if (braceDepth === 0 && bracketDepth === 0 && parenDepth === 0 && angleDepth === 0) {
|
|
3921
|
-
const trimmed2 = current.trim();
|
|
3922
|
-
if (trimmed2) {
|
|
3923
|
-
parts.push(trimmed2);
|
|
3924
|
-
}
|
|
3925
|
-
current = "";
|
|
3926
|
-
continue;
|
|
3927
|
-
}
|
|
3928
|
-
break;
|
|
3929
|
-
}
|
|
3930
|
-
current += char;
|
|
3931
|
-
}
|
|
3932
|
-
const trimmed = current.trim();
|
|
3933
|
-
if (trimmed) {
|
|
3934
|
-
parts.push(trimmed);
|
|
3935
|
-
}
|
|
3936
|
-
return parts;
|
|
3937
|
-
}
|
|
3938
|
-
function parseParameterSignature(parameter) {
|
|
3939
|
-
const colonIndex = parameter.indexOf(":");
|
|
3940
|
-
if (colonIndex < 0) {
|
|
3941
|
-
return { name: parameter.trim() };
|
|
3942
|
-
}
|
|
3943
|
-
const rawName = parameter.slice(0, colonIndex).trim();
|
|
3944
|
-
const hasQuestionToken = rawName.endsWith("?");
|
|
3945
|
-
const name = hasQuestionToken ? rawName.slice(0, -1).trim() : rawName;
|
|
3946
|
-
const remainder = parameter.slice(colonIndex + 1).trim();
|
|
3947
|
-
const initializerIndex = remainder.lastIndexOf("=");
|
|
3948
|
-
if (initializerIndex < 0) {
|
|
3949
|
-
return {
|
|
3950
|
-
name,
|
|
3951
|
-
hasQuestionToken,
|
|
3952
|
-
type: remainder || void 0
|
|
3953
|
-
};
|
|
3954
|
-
}
|
|
3955
|
-
return {
|
|
3956
|
-
name,
|
|
3957
|
-
hasQuestionToken,
|
|
3958
|
-
type: remainder.slice(0, initializerIndex).trim() || void 0,
|
|
3959
|
-
initializer: remainder.slice(initializerIndex + 1).trim() || void 0
|
|
3960
|
-
};
|
|
3961
|
-
}
|
|
3962
|
-
function parseParameterSignatures(parameters) {
|
|
3963
|
-
const trimmed = parameters.trim();
|
|
3964
|
-
if (!trimmed) {
|
|
3965
|
-
return [];
|
|
3966
|
-
}
|
|
3967
|
-
return splitParameterList(trimmed).map(parseParameterSignature);
|
|
3968
|
-
}
|
|
3969
4270
|
function toPosixRelativePath(fromDir, toFile) {
|
|
3970
4271
|
let rel = path.relative(fromDir, toFile).replace(/\\/g, "/");
|
|
3971
4272
|
if (!rel.startsWith(".")) {
|
|
@@ -3985,6 +4286,21 @@ function resolveRouterEntry(projectRoot, routerEntry) {
|
|
|
3985
4286
|
const root = projectRoot ?? process.cwd();
|
|
3986
4287
|
return path.isAbsolute(routerEntry) ? routerEntry : path.resolve(root, routerEntry);
|
|
3987
4288
|
}
|
|
4289
|
+
function createMissingCustomPomDirectoryError(configuredDir, resolvedDir) {
|
|
4290
|
+
return new VuePomGeneratorError(
|
|
4291
|
+
`Custom POM directory "${configuredDir}" does not exist.
|
|
4292
|
+
Resolved path: ${resolvedDir}
|
|
4293
|
+
Create the directory, point generation.playwright.customPoms.dir at the correct location, or remove the customPoms configuration.`
|
|
4294
|
+
);
|
|
4295
|
+
}
|
|
4296
|
+
function createMissingCustomPomAttachmentClassError(missingClassNames, configuredDir) {
|
|
4297
|
+
const renderedClassNames = missingClassNames.map((name) => `"${name}"`).join(", ");
|
|
4298
|
+
return new VuePomGeneratorError(
|
|
4299
|
+
`Custom POM attachments reference missing helper classes: ${renderedClassNames}.
|
|
4300
|
+
Expected matching helper files/exports under "${configuredDir}".
|
|
4301
|
+
Add the missing helper classes or remove the corresponding generation.playwright.customPoms.attachments entries.`
|
|
4302
|
+
);
|
|
4303
|
+
}
|
|
3988
4304
|
function createCustomPomImportCollisionError(exportName, requested) {
|
|
3989
4305
|
return new VuePomGeneratorError(
|
|
3990
4306
|
`Custom POM import name collision detected for "${exportName}".
|
|
@@ -4123,35 +4439,28 @@ function generateGoToSelfMethod(componentName) {
|
|
|
4123
4439
|
})
|
|
4124
4440
|
];
|
|
4125
4441
|
}
|
|
4126
|
-
function
|
|
4127
|
-
|
|
4128
|
-
return "";
|
|
4129
|
-
const preferredOrder = ["key", "value", "text", "timeOut", "annotationText", "wait"];
|
|
4130
|
-
const entries = Object.entries(params);
|
|
4131
|
-
if (!entries.length)
|
|
4132
|
-
return "";
|
|
4133
|
-
const score = (name) => {
|
|
4134
|
-
const idx = preferredOrder.indexOf(name);
|
|
4135
|
-
return idx < 0 ? 999 : idx;
|
|
4136
|
-
};
|
|
4137
|
-
return entries.slice().sort((a, b) => score(a[0]) - score(b[0]) || a[0].localeCompare(b[0])).map(([name, typeExpr]) => `${name}: ${typeExpr}`).join(", ");
|
|
4442
|
+
function getSelectorPatterns(selector) {
|
|
4443
|
+
return selector.kind === "testId" ? [selector.testId] : [selector.rootTestId, selector.label];
|
|
4138
4444
|
}
|
|
4139
4445
|
function generateExtraClickMethodMembers(spec) {
|
|
4140
4446
|
if (spec.kind !== "click") {
|
|
4141
4447
|
return [];
|
|
4142
4448
|
}
|
|
4143
|
-
const
|
|
4144
|
-
const
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4449
|
+
const selectorPatterns = getSelectorPatterns(spec.selector);
|
|
4450
|
+
const signatureSpecs = orderPomPatternParameters(
|
|
4451
|
+
spec.parameters,
|
|
4452
|
+
selectorPatterns,
|
|
4453
|
+
{ omit: spec.keyLiteral !== void 0 ? ["key"] : [] }
|
|
4454
|
+
);
|
|
4455
|
+
const parameters = toTypeScriptPomParameterStructures(signatureSpecs);
|
|
4456
|
+
const hasAnnotationText = signatureSpecs.some((param) => param.name === "annotationText");
|
|
4457
|
+
const hasWait = signatureSpecs.some((param) => param.name === "wait");
|
|
4148
4458
|
const annotationArg = hasAnnotationText ? "annotationText" : '""';
|
|
4149
4459
|
const waitArg = hasWait ? "wait" : "true";
|
|
4150
4460
|
if (spec.selector.kind === "testId") {
|
|
4151
|
-
const
|
|
4152
|
-
const testIdExpr = needsTemplate ? `\`${spec.selector.formattedDataTestId}\`` : JSON.stringify(spec.selector.formattedDataTestId);
|
|
4461
|
+
const testIdBinding = bindTypeScriptPomPattern(spec.selector.testId, "testId");
|
|
4153
4462
|
const clickArgs = [];
|
|
4154
|
-
clickArgs.push(
|
|
4463
|
+
clickArgs.push(testIdBinding.expression);
|
|
4155
4464
|
if (hasAnnotationText || hasWait) {
|
|
4156
4465
|
clickArgs.push(annotationArg);
|
|
4157
4466
|
}
|
|
@@ -4167,20 +4476,16 @@ function generateExtraClickMethodMembers(spec) {
|
|
|
4167
4476
|
if (spec.keyLiteral !== void 0) {
|
|
4168
4477
|
writer.writeLine(`const key = ${JSON.stringify(spec.keyLiteral)};`);
|
|
4169
4478
|
}
|
|
4170
|
-
|
|
4171
|
-
writer.writeLine(
|
|
4479
|
+
for (const statement of testIdBinding.setupStatements) {
|
|
4480
|
+
writer.writeLine(statement);
|
|
4172
4481
|
}
|
|
4173
4482
|
writer.writeLine(`await this.clickByTestId(${clickArgs.join(", ")});`);
|
|
4174
4483
|
}
|
|
4175
4484
|
})
|
|
4176
4485
|
];
|
|
4177
4486
|
}
|
|
4178
|
-
const
|
|
4179
|
-
const
|
|
4180
|
-
const rootExpr = rootNeedsTemplate ? `\`${spec.selector.rootFormattedDataTestId}\`` : JSON.stringify(spec.selector.rootFormattedDataTestId);
|
|
4181
|
-
const labelExpr = labelNeedsTemplate ? `\`${spec.selector.formattedLabel}\`` : JSON.stringify(spec.selector.formattedLabel);
|
|
4182
|
-
const rootArg = rootNeedsTemplate ? "rootTestId" : rootExpr;
|
|
4183
|
-
const labelArg = labelNeedsTemplate ? "label" : labelExpr;
|
|
4487
|
+
const rootBinding = bindTypeScriptPomPattern(spec.selector.rootTestId, "rootTestId");
|
|
4488
|
+
const labelBinding = bindTypeScriptPomPattern(spec.selector.label, "label");
|
|
4184
4489
|
return [
|
|
4185
4490
|
createClassMethod({
|
|
4186
4491
|
name: spec.name,
|
|
@@ -4190,13 +4495,13 @@ function generateExtraClickMethodMembers(spec) {
|
|
|
4190
4495
|
if (spec.keyLiteral !== void 0) {
|
|
4191
4496
|
writer.writeLine(`const key = ${JSON.stringify(spec.keyLiteral)};`);
|
|
4192
4497
|
}
|
|
4193
|
-
|
|
4194
|
-
writer.writeLine(
|
|
4498
|
+
for (const statement of rootBinding.setupStatements) {
|
|
4499
|
+
writer.writeLine(statement);
|
|
4195
4500
|
}
|
|
4196
|
-
|
|
4197
|
-
writer.writeLine(
|
|
4501
|
+
for (const statement of labelBinding.setupStatements) {
|
|
4502
|
+
writer.writeLine(statement);
|
|
4198
4503
|
}
|
|
4199
|
-
writer.writeLine(`await this.clickWithinTestIdByLabel(${
|
|
4504
|
+
writer.writeLine(`await this.clickWithinTestIdByLabel(${rootBinding.expression}, ${labelBinding.expression}, ${annotationArg}, ${waitArg});`);
|
|
4200
4505
|
}
|
|
4201
4506
|
})
|
|
4202
4507
|
];
|
|
@@ -4209,10 +4514,10 @@ function generateMethodMembersFromPom(primary, targetPageObjectModelClass) {
|
|
|
4209
4514
|
targetPageObjectModelClass,
|
|
4210
4515
|
primary.methodName,
|
|
4211
4516
|
primary.nativeRole,
|
|
4212
|
-
primary.
|
|
4213
|
-
primary.
|
|
4517
|
+
primary.selector,
|
|
4518
|
+
primary.alternateSelectors,
|
|
4214
4519
|
primary.getterNameOverride,
|
|
4215
|
-
primary.
|
|
4520
|
+
primary.parameters
|
|
4216
4521
|
);
|
|
4217
4522
|
}
|
|
4218
4523
|
function generateMethodsContentForDependencies(dependencies) {
|
|
@@ -4220,15 +4525,14 @@ function generateMethodsContentForDependencies(dependencies) {
|
|
|
4220
4525
|
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));
|
|
4221
4526
|
const seenPrimaryKeys = /* @__PURE__ */ new Set();
|
|
4222
4527
|
const primarySpecs = primarySpecsAll.filter(({ pom, target }) => {
|
|
4223
|
-
const
|
|
4224
|
-
const alternates = (pom.alternateFormattedDataTestIds ?? []).slice().sort();
|
|
4528
|
+
const alternates = (pom.alternateSelectors ?? []).slice().sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b)));
|
|
4225
4529
|
const key = JSON.stringify({
|
|
4226
4530
|
role: pom.nativeRole,
|
|
4227
4531
|
methodName: pom.methodName,
|
|
4228
4532
|
getterNameOverride: pom.getterNameOverride ?? null,
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
4533
|
+
selector: pom.selector,
|
|
4534
|
+
alternateSelectors: alternates.length ? alternates : void 0,
|
|
4535
|
+
parameters: pom.parameters,
|
|
4232
4536
|
target: target ?? null,
|
|
4233
4537
|
emitPrimary: pom.emitPrimary ?? true
|
|
4234
4538
|
});
|
|
@@ -4255,6 +4559,7 @@ async function generateFiles(componentHierarchyMap, vueFilesPathMap, basePageCla
|
|
|
4255
4559
|
customPomAttachments = [],
|
|
4256
4560
|
projectRoot,
|
|
4257
4561
|
customPomDir,
|
|
4562
|
+
requireCustomPomDir,
|
|
4258
4563
|
customPomImportAliases,
|
|
4259
4564
|
customPomImportNameCollisionBehavior = "error",
|
|
4260
4565
|
testIdAttribute,
|
|
@@ -4287,6 +4592,7 @@ async function generateFiles(componentHierarchyMap, vueFilesPathMap, basePageCla
|
|
|
4287
4592
|
customPomAttachments,
|
|
4288
4593
|
projectRoot,
|
|
4289
4594
|
customPomDir,
|
|
4595
|
+
requireCustomPomDir,
|
|
4290
4596
|
customPomImportAliases,
|
|
4291
4597
|
customPomImportNameCollisionBehavior,
|
|
4292
4598
|
testIdAttribute,
|
|
@@ -4296,6 +4602,7 @@ async function generateFiles(componentHierarchyMap, vueFilesPathMap, basePageCla
|
|
|
4296
4602
|
customPomAttachments,
|
|
4297
4603
|
projectRoot,
|
|
4298
4604
|
customPomDir,
|
|
4605
|
+
requireCustomPomDir,
|
|
4299
4606
|
customPomImportAliases,
|
|
4300
4607
|
customPomImportNameCollisionBehavior,
|
|
4301
4608
|
testIdAttribute,
|
|
@@ -4351,9 +4658,15 @@ async function generateSplitTypeScriptFiles(componentHierarchyMap, vueFilesPathM
|
|
|
4351
4658
|
}
|
|
4352
4659
|
const customPomImportResolution = resolveCustomPomImportResolution(generatedClassNames, projectRoot, {
|
|
4353
4660
|
customPomDir: options.customPomDir,
|
|
4661
|
+
requireCustomPomDir: options.requireCustomPomDir,
|
|
4354
4662
|
customPomImportAliases: options.customPomImportAliases,
|
|
4355
4663
|
customPomImportNameCollisionBehavior: options.customPomImportNameCollisionBehavior
|
|
4356
4664
|
});
|
|
4665
|
+
assertCustomPomAttachmentsResolved(
|
|
4666
|
+
options.customPomAttachments ?? [],
|
|
4667
|
+
customPomImportResolution.classIdentifierMap,
|
|
4668
|
+
options.customPomDir ?? "tests/playwright/pom/custom"
|
|
4669
|
+
);
|
|
4357
4670
|
const runtimeBasePagePath = path.join(base, "_pom-runtime", "class-generation", "base-page.ts");
|
|
4358
4671
|
const files = [];
|
|
4359
4672
|
for (const [name, deps] of entries) {
|
|
@@ -4512,21 +4825,8 @@ function buildGeneratedGitAttributesFiles(generatedFilePaths) {
|
|
|
4512
4825
|
return { filePath, content };
|
|
4513
4826
|
});
|
|
4514
4827
|
}
|
|
4515
|
-
function
|
|
4516
|
-
const
|
|
4517
|
-
if (!needsInterpolation) {
|
|
4518
|
-
return JSON.stringify(formattedDataTestId);
|
|
4519
|
-
}
|
|
4520
|
-
const inner = formattedDataTestId.replace(/\$\{/g, "{");
|
|
4521
|
-
const quoted = JSON.stringify(inner);
|
|
4522
|
-
return `$${quoted}`;
|
|
4523
|
-
}
|
|
4524
|
-
function toCSharpParam(paramTypeExpr) {
|
|
4525
|
-
const trimmed = (paramTypeExpr ?? "").trim();
|
|
4526
|
-
const eqIdx = trimmed.indexOf("=");
|
|
4527
|
-
const left = eqIdx >= 0 ? trimmed.slice(0, eqIdx).trim() : trimmed;
|
|
4528
|
-
const right = eqIdx >= 0 ? trimmed.slice(eqIdx + 1).trim() : void 0;
|
|
4529
|
-
const typePart = left.includes("|") ? "string" : left;
|
|
4828
|
+
function toCSharpParam(param) {
|
|
4829
|
+
const typePart = param.type?.includes("|") ? "string" : param.type ?? "string";
|
|
4530
4830
|
let type = "string";
|
|
4531
4831
|
if (/(?:^|\s)boolean(?:\s|$)/.test(typePart))
|
|
4532
4832
|
type = "bool";
|
|
@@ -4534,19 +4834,17 @@ function toCSharpParam(paramTypeExpr) {
|
|
|
4534
4834
|
type = "string";
|
|
4535
4835
|
else if (/(?:^|\s)number(?:\s|$)/.test(typePart))
|
|
4536
4836
|
type = "int";
|
|
4537
|
-
else if (/\d+/.test(typePart) && typePart === "")
|
|
4538
|
-
type = "int";
|
|
4539
4837
|
else if (/\btimeOut\b/i.test(typePart))
|
|
4540
4838
|
type = "int";
|
|
4541
4839
|
let defaultExpr;
|
|
4542
|
-
if (
|
|
4840
|
+
if (param.initializer !== void 0) {
|
|
4543
4841
|
if (type === "bool") {
|
|
4544
|
-
defaultExpr =
|
|
4842
|
+
defaultExpr = param.initializer.includes("true") ? "true" : param.initializer.includes("false") ? "false" : void 0;
|
|
4545
4843
|
} else if (type === "int") {
|
|
4546
|
-
const m =
|
|
4844
|
+
const m = param.initializer.match(/\d+/);
|
|
4547
4845
|
defaultExpr = m ? m[0] : void 0;
|
|
4548
4846
|
} else {
|
|
4549
|
-
if (
|
|
4847
|
+
if (param.initializer === '""' || param.initializer === "''") {
|
|
4550
4848
|
defaultExpr = '""';
|
|
4551
4849
|
}
|
|
4552
4850
|
}
|
|
@@ -4554,17 +4852,15 @@ function toCSharpParam(paramTypeExpr) {
|
|
|
4554
4852
|
return { type, defaultExpr };
|
|
4555
4853
|
}
|
|
4556
4854
|
function formatCSharpParams(params) {
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
const entries = Object.entries(params);
|
|
4560
|
-
if (!entries.length)
|
|
4855
|
+
const normalizedParams = normalizePomParameters(params);
|
|
4856
|
+
if (!normalizedParams.length)
|
|
4561
4857
|
return { signature: "", argNames: [] };
|
|
4562
4858
|
const signatureParts = [];
|
|
4563
4859
|
const argNames = [];
|
|
4564
|
-
for (const
|
|
4565
|
-
const { type, defaultExpr } = toCSharpParam(
|
|
4566
|
-
argNames.push(name);
|
|
4567
|
-
signatureParts.push(defaultExpr !== void 0 ? `${type} ${name} = ${defaultExpr}` : `${type} ${name}`);
|
|
4860
|
+
for (const param of normalizedParams) {
|
|
4861
|
+
const { type, defaultExpr } = toCSharpParam(param);
|
|
4862
|
+
argNames.push(param.name);
|
|
4863
|
+
signatureParts.push(defaultExpr !== void 0 ? `${type} ${param.name} = ${defaultExpr}` : `${type} ${param.name}`);
|
|
4568
4864
|
}
|
|
4569
4865
|
return { signature: signatureParts.join(", "), argNames };
|
|
4570
4866
|
}
|
|
@@ -4655,23 +4951,13 @@ function generateAggregatedCSharpFiles(componentHierarchyMap, outDir, options =
|
|
|
4655
4951
|
const baseMethodName = upperFirst(pom.methodName);
|
|
4656
4952
|
const baseGetterName = upperFirst(pom.getterNameOverride ?? pom.methodName);
|
|
4657
4953
|
const locatorName = baseGetterName.endsWith(roleSuffix) ? baseGetterName : `${baseGetterName}${roleSuffix}`;
|
|
4658
|
-
const
|
|
4659
|
-
const
|
|
4660
|
-
const
|
|
4661
|
-
const augmentedParams = { ...pom.params };
|
|
4662
|
-
for (const v of templateVars) {
|
|
4663
|
-
if (!Object.prototype.hasOwnProperty.call(augmentedParams, v)) {
|
|
4664
|
-
augmentedParams[v] = "string";
|
|
4665
|
-
}
|
|
4666
|
-
}
|
|
4667
|
-
const orderedParams = Object.fromEntries([
|
|
4668
|
-
...templateVars.map((v) => [v, augmentedParams[v]]),
|
|
4669
|
-
...Object.entries(augmentedParams).filter(([k]) => !templateVars.includes(k))
|
|
4670
|
-
]);
|
|
4954
|
+
const selectorIsParameterized = isParameterizedPomPattern(pom.selector.patternKind);
|
|
4955
|
+
const testIdExpr = toCSharpPomPatternExpression(pom.selector);
|
|
4956
|
+
const orderedParams = orderPomPatternParameters(pom.parameters, [pom.selector]);
|
|
4671
4957
|
const { signature, argNames } = formatCSharpParams(orderedParams);
|
|
4672
4958
|
const args = argNames.join(", ");
|
|
4673
|
-
const allTestIds =
|
|
4674
|
-
if (
|
|
4959
|
+
const allTestIds = uniquePomStringPatterns(pom.selector, pom.alternateSelectors);
|
|
4960
|
+
if (selectorIsParameterized) {
|
|
4675
4961
|
chunks.push(` public ILocator ${locatorName}(${signature}) => LocatorByTestId(${testIdExpr});`);
|
|
4676
4962
|
} else {
|
|
4677
4963
|
chunks.push(` public ILocator ${locatorName} => LocatorByTestId(${testIdExpr});`);
|
|
@@ -4682,12 +4968,12 @@ function generateAggregatedCSharpFiles(componentHierarchyMap, outDir, options =
|
|
|
4682
4968
|
if (target) {
|
|
4683
4969
|
chunks.push(` public async Task<${target}> ${actionName}(${sig})`);
|
|
4684
4970
|
chunks.push(" {");
|
|
4685
|
-
if (
|
|
4686
|
-
chunks.push(` await ${locatorName}${
|
|
4971
|
+
if (selectorIsParameterized || allTestIds.length <= 1) {
|
|
4972
|
+
chunks.push(` await ${locatorName}${selectorIsParameterized ? `(${args})` : ""}.ClickAsync();`);
|
|
4687
4973
|
chunks.push(` return new ${target}(Page);`);
|
|
4688
4974
|
} else {
|
|
4689
4975
|
chunks.push(" Exception? lastError = null;");
|
|
4690
|
-
chunks.push(` foreach (var testId in new[] { ${allTestIds.map(
|
|
4976
|
+
chunks.push(` foreach (var testId in new[] { ${allTestIds.map((testId) => toCSharpPomPatternExpression(testId)).join(", ")} })`);
|
|
4691
4977
|
chunks.push(" {");
|
|
4692
4978
|
chunks.push(" try");
|
|
4693
4979
|
chunks.push(" {");
|
|
@@ -4711,7 +4997,7 @@ function generateAggregatedCSharpFiles(componentHierarchyMap, outDir, options =
|
|
|
4711
4997
|
}
|
|
4712
4998
|
chunks.push(` public async Task ${actionName}(${sig})`);
|
|
4713
4999
|
chunks.push(" {");
|
|
4714
|
-
const callSuffix =
|
|
5000
|
+
const callSuffix = selectorIsParameterized ? `(${args})` : "";
|
|
4715
5001
|
const emitActionCall = (locatorAccess) => {
|
|
4716
5002
|
if (pom.nativeRole === "input") {
|
|
4717
5003
|
chunks.push(` var editableLocator = await ResolveEditableLocatorAsync(${locatorAccess});`);
|
|
@@ -4724,9 +5010,9 @@ function generateAggregatedCSharpFiles(componentHierarchyMap, outDir, options =
|
|
|
4724
5010
|
chunks.push(` await ${locatorAccess}.ClickAsync();`);
|
|
4725
5011
|
}
|
|
4726
5012
|
};
|
|
4727
|
-
if (!
|
|
5013
|
+
if (!selectorIsParameterized && allTestIds.length > 1) {
|
|
4728
5014
|
chunks.push(" Exception? lastError = null;");
|
|
4729
|
-
chunks.push(` foreach (var testId in new[] { ${allTestIds.map(
|
|
5015
|
+
chunks.push(` foreach (var testId in new[] { ${allTestIds.map((testId) => toCSharpPomPatternExpression(testId)).join(", ")} })`);
|
|
4730
5016
|
chunks.push(" {");
|
|
4731
5017
|
chunks.push(" try");
|
|
4732
5018
|
chunks.push(" {");
|
|
@@ -4772,7 +5058,12 @@ function generateAggregatedCSharpFiles(componentHierarchyMap, outDir, options =
|
|
|
4772
5058
|
for (const extra of extras) {
|
|
4773
5059
|
if (extra.kind !== "click")
|
|
4774
5060
|
continue;
|
|
4775
|
-
const
|
|
5061
|
+
const extraParams = orderPomPatternParameters(
|
|
5062
|
+
extra.parameters,
|
|
5063
|
+
getSelectorPatterns(extra.selector),
|
|
5064
|
+
{ omit: extra.keyLiteral !== void 0 ? ["key"] : [] }
|
|
5065
|
+
);
|
|
5066
|
+
const { signature } = formatCSharpParams(extraParams);
|
|
4776
5067
|
const extraName = upperFirst(extra.name);
|
|
4777
5068
|
chunks.push(` public async Task ${extraName}Async(${signature})`);
|
|
4778
5069
|
chunks.push(" {");
|
|
@@ -4780,29 +5071,22 @@ function generateAggregatedCSharpFiles(componentHierarchyMap, outDir, options =
|
|
|
4780
5071
|
chunks.push(` var key = ${JSON.stringify(extra.keyLiteral)};`);
|
|
4781
5072
|
}
|
|
4782
5073
|
if (extra.selector.kind === "testId") {
|
|
4783
|
-
const
|
|
4784
|
-
const
|
|
4785
|
-
|
|
4786
|
-
chunks.push(` var testId = ${testIdExpr};`);
|
|
4787
|
-
chunks.push(" await LocatorByTestId(testId).ClickAsync();");
|
|
4788
|
-
} else {
|
|
4789
|
-
chunks.push(` await LocatorByTestId(${testIdExpr}).ClickAsync();`);
|
|
5074
|
+
const testIdBinding = bindCSharpPomPattern(extra.selector.testId, "testId");
|
|
5075
|
+
for (const statement of testIdBinding.setupStatements) {
|
|
5076
|
+
chunks.push(` ${statement}`);
|
|
4790
5077
|
}
|
|
5078
|
+
chunks.push(` await LocatorByTestId(${testIdBinding.expression}).ClickAsync();`);
|
|
4791
5079
|
} else {
|
|
4792
|
-
const
|
|
4793
|
-
const
|
|
4794
|
-
const rootExpr = toCSharpTestIdExpression(extra.selector.rootFormattedDataTestId);
|
|
4795
|
-
const labelExpr = toCSharpTestIdExpression(extra.selector.formattedLabel);
|
|
5080
|
+
const rootBinding = bindCSharpPomPattern(extra.selector.rootTestId, "rootTestId");
|
|
5081
|
+
const labelBinding = bindCSharpPomPattern(extra.selector.label, "label");
|
|
4796
5082
|
const exactArg = extra.selector.exact === false ? "false" : "true";
|
|
4797
|
-
|
|
4798
|
-
chunks.push(`
|
|
5083
|
+
for (const statement of rootBinding.setupStatements) {
|
|
5084
|
+
chunks.push(` ${statement}`);
|
|
4799
5085
|
}
|
|
4800
|
-
|
|
4801
|
-
chunks.push(`
|
|
5086
|
+
for (const statement of labelBinding.setupStatements) {
|
|
5087
|
+
chunks.push(` ${statement}`);
|
|
4802
5088
|
}
|
|
4803
|
-
|
|
4804
|
-
const labelArg = labelNeedsTemplate ? "label" : labelExpr;
|
|
4805
|
-
chunks.push(` await ClickWithinTestIdByLabelAsync(${rootArg}, ${labelArg}, ${exactArg});`);
|
|
5089
|
+
chunks.push(` await ClickWithinTestIdByLabelAsync(${rootBinding.expression}, ${labelBinding.expression}, ${exactArg});`);
|
|
4806
5090
|
}
|
|
4807
5091
|
chunks.push(" }");
|
|
4808
5092
|
chunks.push("");
|
|
@@ -5246,7 +5530,7 @@ function getViewPassthroughMethods(viewName, viewDependencies, childrenComponent
|
|
|
5246
5530
|
if (existingOnView.has(name) || blockedMethodNames.has(name))
|
|
5247
5531
|
continue;
|
|
5248
5532
|
const list = methodToChildren.get(name) ?? [];
|
|
5249
|
-
list.push({ childProp,
|
|
5533
|
+
list.push({ childProp, signature: sig });
|
|
5250
5534
|
methodToChildren.set(name, list);
|
|
5251
5535
|
}
|
|
5252
5536
|
}
|
|
@@ -5256,12 +5540,12 @@ function getViewPassthroughMethods(viewName, viewDependencies, childrenComponent
|
|
|
5256
5540
|
return [];
|
|
5257
5541
|
}
|
|
5258
5542
|
return passthroughs.map(([methodName, candidates]) => {
|
|
5259
|
-
const { childProp,
|
|
5260
|
-
const callArgs =
|
|
5543
|
+
const { childProp, signature } = candidates[0];
|
|
5544
|
+
const callArgs = getPomParameterArgumentNames(signature.parameters).join(", ");
|
|
5261
5545
|
return createClassMethod({
|
|
5262
5546
|
name: methodName,
|
|
5263
5547
|
isAsync: true,
|
|
5264
|
-
parameters:
|
|
5548
|
+
parameters: toTypeScriptPomParameterStructures(signature.parameters),
|
|
5265
5549
|
statements: [
|
|
5266
5550
|
`return await this.${childProp}.${methodName}(${callArgs});`
|
|
5267
5551
|
]
|
|
@@ -5285,8 +5569,7 @@ function getAttachmentPassthroughMethods(ownerName, ownerDependencies, attachmen
|
|
|
5285
5569
|
const list = methodToAttachments.get(methodName) ?? [];
|
|
5286
5570
|
list.push({
|
|
5287
5571
|
propertyName: attachment.propertyName,
|
|
5288
|
-
|
|
5289
|
-
argNames: signature.argNames
|
|
5572
|
+
signature
|
|
5290
5573
|
});
|
|
5291
5574
|
methodToAttachments.set(methodName, list);
|
|
5292
5575
|
}
|
|
@@ -5297,12 +5580,12 @@ function getAttachmentPassthroughMethods(ownerName, ownerDependencies, attachmen
|
|
|
5297
5580
|
return [];
|
|
5298
5581
|
}
|
|
5299
5582
|
return passthroughs.map(([methodName, candidates]) => {
|
|
5300
|
-
const { propertyName,
|
|
5301
|
-
const callArgs =
|
|
5583
|
+
const { propertyName, signature } = candidates[0];
|
|
5584
|
+
const callArgs = getPomParameterArgumentNames(signature.parameters).join(", ");
|
|
5302
5585
|
const invocation = callArgs ? `this.${propertyName}.${methodName}(${callArgs})` : `this.${propertyName}.${methodName}()`;
|
|
5303
5586
|
return createClassMethod({
|
|
5304
5587
|
name: methodName,
|
|
5305
|
-
parameters:
|
|
5588
|
+
parameters: toTypeScriptPomParameterStructures(signature.parameters),
|
|
5306
5589
|
statements: [
|
|
5307
5590
|
`return ${invocation};`
|
|
5308
5591
|
]
|
|
@@ -5316,15 +5599,44 @@ function sliceNodeSource(source, node) {
|
|
|
5316
5599
|
const snippet = source.slice(node.start, node.end).trim();
|
|
5317
5600
|
return snippet.length ? snippet : null;
|
|
5318
5601
|
}
|
|
5319
|
-
function
|
|
5602
|
+
function getTypeAnnotationSource(source, node) {
|
|
5603
|
+
const rawTypeAnnotation = node.typeAnnotation;
|
|
5604
|
+
if (!rawTypeAnnotation || typeof rawTypeAnnotation !== "object" || !("type" in rawTypeAnnotation) || rawTypeAnnotation.type !== "TSTypeAnnotation" || !("typeAnnotation" in rawTypeAnnotation)) {
|
|
5605
|
+
return void 0;
|
|
5606
|
+
}
|
|
5607
|
+
const typeAnnotation = rawTypeAnnotation.typeAnnotation;
|
|
5608
|
+
return typeAnnotation && typeof typeAnnotation === "object" ? sliceNodeSource(source, typeAnnotation) ?? void 0 : void 0;
|
|
5609
|
+
}
|
|
5610
|
+
function getCustomPomParameterSpec(source, param) {
|
|
5320
5611
|
if (param.type === "Identifier") {
|
|
5321
|
-
return param.name
|
|
5612
|
+
return createPomParameterSpec(param.name, getTypeAnnotationSource(source, param), {
|
|
5613
|
+
hasQuestionToken: !!param.optional
|
|
5614
|
+
});
|
|
5322
5615
|
}
|
|
5323
5616
|
if (param.type === "AssignmentPattern") {
|
|
5324
|
-
|
|
5617
|
+
if (param.left.type !== "Identifier") {
|
|
5618
|
+
return null;
|
|
5619
|
+
}
|
|
5620
|
+
const initializer = sliceNodeSource(source, param.right);
|
|
5621
|
+
if (!initializer) {
|
|
5622
|
+
return null;
|
|
5623
|
+
}
|
|
5624
|
+
return createPomParameterSpec(param.left.name, getTypeAnnotationSource(source, param.left), {
|
|
5625
|
+
initializer,
|
|
5626
|
+
hasQuestionToken: !!param.left.optional
|
|
5627
|
+
});
|
|
5325
5628
|
}
|
|
5326
5629
|
if (param.type === "RestElement") {
|
|
5327
|
-
|
|
5630
|
+
if (param.argument.type !== "Identifier") {
|
|
5631
|
+
return null;
|
|
5632
|
+
}
|
|
5633
|
+
const typeExpression = getTypeAnnotationSource(
|
|
5634
|
+
source,
|
|
5635
|
+
param
|
|
5636
|
+
) ?? getTypeAnnotationSource(source, param.argument);
|
|
5637
|
+
return createPomParameterSpec(param.argument.name, typeExpression, {
|
|
5638
|
+
isRestParameter: true
|
|
5639
|
+
});
|
|
5328
5640
|
}
|
|
5329
5641
|
return null;
|
|
5330
5642
|
}
|
|
@@ -5357,29 +5669,23 @@ function extractCustomPomMethodSignatures(source, exportName) {
|
|
|
5357
5669
|
if (member.key.type !== "Identifier") {
|
|
5358
5670
|
continue;
|
|
5359
5671
|
}
|
|
5360
|
-
const
|
|
5361
|
-
const argNames = [];
|
|
5672
|
+
const parameters = [];
|
|
5362
5673
|
let supported = true;
|
|
5363
5674
|
member.params.forEach((param) => {
|
|
5364
5675
|
if (!supported) {
|
|
5365
5676
|
return;
|
|
5366
5677
|
}
|
|
5367
|
-
const
|
|
5368
|
-
|
|
5369
|
-
if (!paramSource || !argName) {
|
|
5678
|
+
const parameter = getCustomPomParameterSpec(source, param);
|
|
5679
|
+
if (!parameter) {
|
|
5370
5680
|
supported = false;
|
|
5371
5681
|
return;
|
|
5372
5682
|
}
|
|
5373
|
-
|
|
5374
|
-
argNames.push(argName);
|
|
5683
|
+
parameters.push(parameter);
|
|
5375
5684
|
});
|
|
5376
5685
|
if (!supported) {
|
|
5377
5686
|
continue;
|
|
5378
5687
|
}
|
|
5379
|
-
signatures.set(member.key.name,
|
|
5380
|
-
params: params.join(", "),
|
|
5381
|
-
argNames
|
|
5382
|
-
});
|
|
5688
|
+
signatures.set(member.key.name, createPomMethodSignature(parameters));
|
|
5383
5689
|
}
|
|
5384
5690
|
}
|
|
5385
5691
|
return signatures;
|
|
@@ -5562,6 +5868,9 @@ function resolveCustomPomImportResolution(generatedClassNames, projectRoot, opti
|
|
|
5562
5868
|
const customDirRelOrAbs = options.customPomDir ?? "tests/playwright/pom/custom";
|
|
5563
5869
|
const customDirAbs = path.isAbsolute(customDirRelOrAbs) ? customDirRelOrAbs : path.resolve(projectRoot, customDirRelOrAbs);
|
|
5564
5870
|
if (!fs.existsSync(customDirAbs)) {
|
|
5871
|
+
if (options.requireCustomPomDir) {
|
|
5872
|
+
throw createMissingCustomPomDirectoryError(customDirRelOrAbs, customDirAbs);
|
|
5873
|
+
}
|
|
5565
5874
|
return {
|
|
5566
5875
|
classIdentifierMap,
|
|
5567
5876
|
methodSignaturesByClass,
|
|
@@ -5604,6 +5913,14 @@ function resolveCustomPomImportResolution(generatedClassNames, projectRoot, opti
|
|
|
5604
5913
|
importSpecifiersByClass
|
|
5605
5914
|
};
|
|
5606
5915
|
}
|
|
5916
|
+
function assertCustomPomAttachmentsResolved(attachments, classIdentifierMap, configuredDir) {
|
|
5917
|
+
const missingClassNames = Array.from(new Set(
|
|
5918
|
+
attachments.map((attachment) => attachment.className).filter((className) => !Object.prototype.hasOwnProperty.call(classIdentifierMap, className))
|
|
5919
|
+
)).sort((left, right) => left.localeCompare(right));
|
|
5920
|
+
if (missingClassNames.length > 0) {
|
|
5921
|
+
throw createMissingCustomPomAttachmentClassError(missingClassNames, configuredDir);
|
|
5922
|
+
}
|
|
5923
|
+
}
|
|
5607
5924
|
function getComposedStubBody(targetClassName, availableClassNames, depsByClassName, vueFilesPathMap, projectRoot) {
|
|
5608
5925
|
const filePath = resolveVueSourcePath(targetClassName, vueFilesPathMap, projectRoot);
|
|
5609
5926
|
if (!filePath)
|
|
@@ -5632,7 +5949,7 @@ function getComposedStubBody(targetClassName, availableClassNames, depsByClassNa
|
|
|
5632
5949
|
if (!sig)
|
|
5633
5950
|
continue;
|
|
5634
5951
|
const list = methodToChildren.get(name) ?? [];
|
|
5635
|
-
list.push({ child,
|
|
5952
|
+
list.push({ child, signature: sig });
|
|
5636
5953
|
methodToChildren.set(name, list);
|
|
5637
5954
|
}
|
|
5638
5955
|
}
|
|
@@ -5640,12 +5957,12 @@ function getComposedStubBody(targetClassName, availableClassNames, depsByClassNa
|
|
|
5640
5957
|
for (const [methodName, candidatesForMethod] of methodToChildren.entries()) {
|
|
5641
5958
|
if (candidatesForMethod.length !== 1 || methodName === "constructor")
|
|
5642
5959
|
continue;
|
|
5643
|
-
const { child,
|
|
5644
|
-
const callArgs =
|
|
5960
|
+
const { child, signature } = candidatesForMethod[0];
|
|
5961
|
+
const callArgs = getPomParameterArgumentNames(signature.parameters).join(", ");
|
|
5645
5962
|
passthroughMembers.push(createClassMethod({
|
|
5646
5963
|
name: methodName,
|
|
5647
5964
|
isAsync: true,
|
|
5648
|
-
parameters:
|
|
5965
|
+
parameters: toTypeScriptPomParameterStructures(signature.parameters),
|
|
5649
5966
|
statements: [
|
|
5650
5967
|
`return await this.${child}.${methodName}(${callArgs});`
|
|
5651
5968
|
]
|
|
@@ -5694,9 +6011,15 @@ async function generateAggregatedFiles(componentHierarchyMap, vueFilesPathMap, b
|
|
|
5694
6011
|
imports.push(`export * from "${runtimeClassGenRel}/base-page";`);
|
|
5695
6012
|
const customPomImportResolution = resolveCustomPomImportResolution(generatedClassNames, projectRoot, {
|
|
5696
6013
|
customPomDir: options.customPomDir,
|
|
6014
|
+
requireCustomPomDir: options.requireCustomPomDir,
|
|
5697
6015
|
customPomImportAliases: options.customPomImportAliases,
|
|
5698
6016
|
customPomImportNameCollisionBehavior: options.customPomImportNameCollisionBehavior
|
|
5699
6017
|
});
|
|
6018
|
+
assertCustomPomAttachmentsResolved(
|
|
6019
|
+
options.customPomAttachments ?? [],
|
|
6020
|
+
customPomImportResolution.classIdentifierMap,
|
|
6021
|
+
options.customPomDir ?? "tests/playwright/pom/custom"
|
|
6022
|
+
);
|
|
5700
6023
|
const customPomClassIdentifierMap = customPomImportResolution.classIdentifierMap;
|
|
5701
6024
|
const customPomMethodSignaturesByClass = customPomImportResolution.methodSignaturesByClass;
|
|
5702
6025
|
const customPomAvailableClassIdentifiers = customPomImportResolution.availableClassIdentifiers;
|
|
@@ -5846,8 +6169,8 @@ function getWidgetInstancesForView(componentName, dataTestIdSet, availableClassI
|
|
|
5846
6169
|
return candidate;
|
|
5847
6170
|
};
|
|
5848
6171
|
for (const dt of dataTestIdSet) {
|
|
5849
|
-
const raw = dt.
|
|
5850
|
-
if (
|
|
6172
|
+
const raw = dt.selectorValue.formatted;
|
|
6173
|
+
if (isParameterizedPomPattern(dt.selectorValue.patternKind)) {
|
|
5851
6174
|
continue;
|
|
5852
6175
|
}
|
|
5853
6176
|
const toggleSuffix = "-toggle";
|
|
@@ -5937,7 +6260,6 @@ function getConstructor(childrenComponent, componentHierarchyMap, attachmentsFor
|
|
|
5937
6260
|
});
|
|
5938
6261
|
}
|
|
5939
6262
|
const TESTID_CLICK_EVENT_NAME = "__testid_event__";
|
|
5940
|
-
const TESTID_CLICK_EVENT_STRICT_FLAG = "__testid_click_event_strict__";
|
|
5941
6263
|
const CLICK_EVENT_NAME = TESTID_CLICK_EVENT_NAME;
|
|
5942
6264
|
const inferredNativeWrapperConfigByLookup = /* @__PURE__ */ new Map();
|
|
5943
6265
|
const inferredSfcPathByLookup = /* @__PURE__ */ new Map();
|
|
@@ -6199,7 +6521,7 @@ function getConditionalDirectiveInfo(element) {
|
|
|
6199
6521
|
if (directive.name === "else") {
|
|
6200
6522
|
const exp2 = directive.exp;
|
|
6201
6523
|
if (exp2 && (exp2.type === NodeTypes.SIMPLE_EXPRESSION || exp2.type === NodeTypes.COMPOUND_EXPRESSION)) {
|
|
6202
|
-
const source2 = (exp2
|
|
6524
|
+
const source2 = getVueExpressionSource(exp2, "content", "compiled");
|
|
6203
6525
|
return { kind: "else-if", source: source2 };
|
|
6204
6526
|
}
|
|
6205
6527
|
return { kind: "else", source: "" };
|
|
@@ -6208,13 +6530,13 @@ function getConditionalDirectiveInfo(element) {
|
|
|
6208
6530
|
const exp2 = directive.exp;
|
|
6209
6531
|
if (!exp2 || exp2.type !== NodeTypes.SIMPLE_EXPRESSION && exp2.type !== NodeTypes.COMPOUND_EXPRESSION)
|
|
6210
6532
|
return null;
|
|
6211
|
-
const source2 = (exp2
|
|
6533
|
+
const source2 = getVueExpressionSource(exp2, "content", "compiled");
|
|
6212
6534
|
return { kind: "else-if", source: source2 };
|
|
6213
6535
|
}
|
|
6214
6536
|
const exp = directive.exp;
|
|
6215
6537
|
if (!exp || exp.type !== NodeTypes.SIMPLE_EXPRESSION && exp.type !== NodeTypes.COMPOUND_EXPRESSION)
|
|
6216
6538
|
return null;
|
|
6217
|
-
const source = (exp
|
|
6539
|
+
const source = getVueExpressionSource(exp, "content", "compiled");
|
|
6218
6540
|
return { kind: directive.name, source };
|
|
6219
6541
|
}
|
|
6220
6542
|
function tryExtractStableHintFromConditionalExpressionSource(source) {
|
|
@@ -6240,26 +6562,26 @@ function tryExtractStableHintFromConditionalExpressionSource(source) {
|
|
|
6240
6562
|
};
|
|
6241
6563
|
try {
|
|
6242
6564
|
const expr = parseExpression(src, { plugins: ["typescript"] });
|
|
6243
|
-
const
|
|
6565
|
+
const isNodeType2 = (n, type) => {
|
|
6244
6566
|
return n !== null && n.type === type;
|
|
6245
6567
|
};
|
|
6246
|
-
const
|
|
6247
|
-
return
|
|
6568
|
+
const isStringLiteralNode2 = (n) => {
|
|
6569
|
+
return isNodeType2(n, "StringLiteral") && typeof n.value === "string";
|
|
6248
6570
|
};
|
|
6249
|
-
const
|
|
6250
|
-
return
|
|
6571
|
+
const isIdentifierNode2 = (n) => {
|
|
6572
|
+
return isNodeType2(n, "Identifier") && typeof n.name === "string";
|
|
6251
6573
|
};
|
|
6252
6574
|
const results = [];
|
|
6253
6575
|
const walk = (n) => {
|
|
6254
6576
|
if (!n)
|
|
6255
6577
|
return;
|
|
6256
|
-
if (
|
|
6578
|
+
if (isStringLiteralNode2(n)) {
|
|
6257
6579
|
const v = (n.value ?? "").trim();
|
|
6258
6580
|
if (isIdentifierish(v)) {
|
|
6259
6581
|
results.push(v);
|
|
6260
6582
|
}
|
|
6261
6583
|
}
|
|
6262
|
-
if (
|
|
6584
|
+
if (isIdentifierNode2(n)) {
|
|
6263
6585
|
const v = (n.name ?? "").trim();
|
|
6264
6586
|
if (isIdentifierish(v)) {
|
|
6265
6587
|
results.push(v);
|
|
@@ -6409,34 +6731,20 @@ ${buildSearchRootsKey(normalizedSearchRoots)}`;
|
|
|
6409
6731
|
inferredNativeWrapperConfigByLookup.set(cacheKey, { role: "" });
|
|
6410
6732
|
return null;
|
|
6411
6733
|
}
|
|
6412
|
-
function tryWrapClickDirectiveForTestEvents(element, testIdAttribute) {
|
|
6734
|
+
function tryWrapClickDirectiveForTestEvents(element, testIdAttribute, resolvedRuntimeTestId) {
|
|
6413
6735
|
const jsStringLiteral = (value) => {
|
|
6414
6736
|
return JSON.stringify(value);
|
|
6415
6737
|
};
|
|
6416
6738
|
const getTestIdExpressionForNode = () => {
|
|
6417
|
-
|
|
6418
|
-
if (!existing) {
|
|
6419
|
-
return "undefined";
|
|
6420
|
-
}
|
|
6421
|
-
if (existing.type === NodeTypes.ATTRIBUTE) {
|
|
6422
|
-
const v = existing.value?.content;
|
|
6423
|
-
if (!v) {
|
|
6424
|
-
return "undefined";
|
|
6425
|
-
}
|
|
6426
|
-
return jsStringLiteral(v);
|
|
6427
|
-
}
|
|
6428
|
-
const directive = existing;
|
|
6429
|
-
const exp2 = directive.exp;
|
|
6430
|
-
if (!exp2 || exp2.type !== NodeTypes.SIMPLE_EXPRESSION) {
|
|
6739
|
+
if (!resolvedRuntimeTestId) {
|
|
6431
6740
|
return "undefined";
|
|
6432
6741
|
}
|
|
6433
|
-
|
|
6434
|
-
|
|
6435
|
-
return "undefined";
|
|
6742
|
+
if (resolvedRuntimeTestId.kind === "static") {
|
|
6743
|
+
return jsStringLiteral(resolvedRuntimeTestId.value);
|
|
6436
6744
|
}
|
|
6437
|
-
return `(${
|
|
6745
|
+
return `(${renderTemplateLiteralExpression(resolvedRuntimeTestId)})`;
|
|
6438
6746
|
};
|
|
6439
|
-
const
|
|
6747
|
+
const testIdExpression = getTestIdExpressionForNode();
|
|
6440
6748
|
const clickDirective = tryGetClickDirective(element);
|
|
6441
6749
|
if (!clickDirective)
|
|
6442
6750
|
return;
|
|
@@ -6447,10 +6755,10 @@ function tryWrapClickDirectiveForTestEvents(element, testIdAttribute) {
|
|
|
6447
6755
|
const exp = clickDirective.exp;
|
|
6448
6756
|
if (!exp)
|
|
6449
6757
|
return;
|
|
6450
|
-
const existingSource = (exp
|
|
6758
|
+
const existingSource = getVueExpressionSource(exp, "loc", "content");
|
|
6451
6759
|
if (existingSource.includes(CLICK_EVENT_NAME))
|
|
6452
6760
|
return;
|
|
6453
|
-
const originalExpression = (exp
|
|
6761
|
+
const originalExpression = getVueExpressionSource(exp, "content", "loc");
|
|
6454
6762
|
if (!originalExpression)
|
|
6455
6763
|
return;
|
|
6456
6764
|
const isStatementBody = (() => {
|
|
@@ -6467,7 +6775,7 @@ function tryWrapClickDirectiveForTestEvents(element, testIdAttribute) {
|
|
|
6467
6775
|
const statementWrappedHandler = `($event) => {
|
|
6468
6776
|
const __win = ($event && $event.view) ? $event.view : undefined;
|
|
6469
6777
|
const __target = ($event && $event.currentTarget) ? $event.currentTarget : undefined;
|
|
6470
|
-
const __testIdFromNode = ${
|
|
6778
|
+
const __testIdFromNode = ${testIdExpression};
|
|
6471
6779
|
const __testIdFromTarget = (__target && typeof __target.getAttribute === 'function') ? __target.getAttribute(${jsStringLiteral(testIdAttribute)}) : undefined;
|
|
6472
6780
|
const __testId = (__testIdFromNode ?? __testIdFromTarget);
|
|
6473
6781
|
const __emit = (phase, err) => {
|
|
@@ -6478,16 +6786,12 @@ function tryWrapClickDirectiveForTestEvents(element, testIdAttribute) {
|
|
|
6478
6786
|
__w.dispatchEvent(new __CustomEvent('${CLICK_EVENT_NAME}', { detail: { testId: __testId, phase, err: err ? String(err) : undefined } }));
|
|
6479
6787
|
}
|
|
6480
6788
|
} catch (e) {
|
|
6481
|
-
// Instrumentation
|
|
6482
|
-
// In strict mode we rethrow so tests fail fast and the underlying problem is visible.
|
|
6483
|
-
// Outside strict mode we log and continue so we don't break real user clicks.
|
|
6789
|
+
// Instrumentation failures should never be silent. Log the root cause and fail fast.
|
|
6484
6790
|
const __w = __win || (__target && __target.ownerDocument && __target.ownerDocument.defaultView);
|
|
6485
6791
|
if (__w && __w.console && typeof __w.console.error === 'function') {
|
|
6486
6792
|
__w.console.error('[testid-click-event] failed to emit ${CLICK_EVENT_NAME}', e);
|
|
6487
6793
|
}
|
|
6488
|
-
|
|
6489
|
-
throw e;
|
|
6490
|
-
}
|
|
6794
|
+
throw e;
|
|
6491
6795
|
}
|
|
6492
6796
|
};
|
|
6493
6797
|
const __w2 = __win || (__target && __target.ownerDocument && __target.ownerDocument.defaultView);
|
|
@@ -6518,7 +6822,7 @@ function tryWrapClickDirectiveForTestEvents(element, testIdAttribute) {
|
|
|
6518
6822
|
const expressionWrappedHandler = `($event) => {
|
|
6519
6823
|
const __win = ($event && $event.view) ? $event.view : undefined;
|
|
6520
6824
|
const __target = ($event && $event.currentTarget) ? $event.currentTarget : undefined;
|
|
6521
|
-
const __testIdFromNode = ${
|
|
6825
|
+
const __testIdFromNode = ${testIdExpression};
|
|
6522
6826
|
const __testIdFromTarget = (__target && typeof __target.getAttribute === 'function') ? __target.getAttribute(${jsStringLiteral(testIdAttribute)}) : undefined;
|
|
6523
6827
|
const __testId = (__testIdFromNode ?? __testIdFromTarget);
|
|
6524
6828
|
const __emit = (phase, err) => {
|
|
@@ -6529,16 +6833,12 @@ function tryWrapClickDirectiveForTestEvents(element, testIdAttribute) {
|
|
|
6529
6833
|
__w.dispatchEvent(new __CustomEvent('${CLICK_EVENT_NAME}', { detail: { testId: __testId, phase, err: err ? String(err) : undefined } }));
|
|
6530
6834
|
}
|
|
6531
6835
|
} catch (e) {
|
|
6532
|
-
// Instrumentation
|
|
6533
|
-
// In strict mode we rethrow so tests fail fast and the underlying problem is visible.
|
|
6534
|
-
// Outside strict mode we log and continue so we don't break real user clicks.
|
|
6836
|
+
// Instrumentation failures should never be silent. Log the root cause and fail fast.
|
|
6535
6837
|
const __w = __win || (__target && __target.ownerDocument && __target.ownerDocument.defaultView);
|
|
6536
6838
|
if (__w && __w.console && typeof __w.console.error === 'function') {
|
|
6537
6839
|
__w.console.error('[testid-click-event] failed to emit ${CLICK_EVENT_NAME}', e);
|
|
6538
6840
|
}
|
|
6539
|
-
|
|
6540
|
-
throw e;
|
|
6541
|
-
}
|
|
6841
|
+
throw e;
|
|
6542
6842
|
}
|
|
6543
6843
|
};
|
|
6544
6844
|
const __w2 = __win || (__target && __target.ownerDocument && __target.ownerDocument.defaultView);
|
|
@@ -6576,10 +6876,9 @@ function tryWrapClickDirectiveForTestEvents(element, testIdAttribute) {
|
|
|
6576
6876
|
let previousFileName = "";
|
|
6577
6877
|
const hierarchyMap = /* @__PURE__ */ new Map();
|
|
6578
6878
|
function createTestIdTransform(componentName, componentHierarchyMap, nativeWrappers = {}, excludedComponents = [], viewsDirAbs, options = {}) {
|
|
6579
|
-
const existingIdBehavior = options.existingIdBehavior ?? "
|
|
6879
|
+
const existingIdBehavior = options.existingIdBehavior ?? "error";
|
|
6580
6880
|
const testIdAttribute = (options.testIdAttribute || "data-testid").trim() || "data-testid";
|
|
6581
|
-
const nameCollisionBehavior = options.nameCollisionBehavior ?? "
|
|
6582
|
-
const missingSemanticNameBehavior = options.missingSemanticNameBehavior ?? "error";
|
|
6881
|
+
const nameCollisionBehavior = options.nameCollisionBehavior ?? "error";
|
|
6583
6882
|
const warn = options.warn;
|
|
6584
6883
|
const vueFilesPathMap = options.vueFilesPathMap;
|
|
6585
6884
|
const wrapperSearchRoots = options.wrapperSearchRoots ?? [];
|
|
@@ -6655,7 +6954,7 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
|
|
|
6655
6954
|
conditionalHintByIfBranch.set(branch, hint);
|
|
6656
6955
|
continue;
|
|
6657
6956
|
}
|
|
6658
|
-
const condSource = (cond
|
|
6957
|
+
const condSource = getVueExpressionSource(cond, "content", "compiled");
|
|
6659
6958
|
const stable = tryExtractStableHintFromConditionalExpressionSource(condSource);
|
|
6660
6959
|
if (stable) {
|
|
6661
6960
|
conditionalHintByIfBranch.set(branch, stable);
|
|
@@ -6717,17 +7016,18 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
|
|
|
6717
7016
|
nativeWrappers[element.tag] = { role: "grid" };
|
|
6718
7017
|
}
|
|
6719
7018
|
}
|
|
6720
|
-
const
|
|
7019
|
+
const getBestAvailableKeyInfo = () => {
|
|
6721
7020
|
const parentNode = context.parent && typeof context.parent === "object" ? context.parent : null;
|
|
6722
7021
|
const isDirectVForChild = parentNode?.type === NodeTypes.FOR;
|
|
6723
|
-
const
|
|
6724
|
-
if (
|
|
6725
|
-
|
|
7022
|
+
const vForKeyInfo = (isDirectVForChild ? getKeyDirectiveInfo(element) : null) || getContainedInVForDirectiveKeyInfo(context, element, hierarchyMap);
|
|
7023
|
+
if (vForKeyInfo) {
|
|
7024
|
+
return vForKeyInfo;
|
|
7025
|
+
}
|
|
7026
|
+
return getContainedInSlotDataKeyInfo(element, hierarchyMap);
|
|
6726
7027
|
};
|
|
6727
|
-
const
|
|
6728
|
-
const
|
|
6729
|
-
const
|
|
6730
|
-
const bestKeyVariable = isSlotKey ? bestKeyInferred : null;
|
|
7028
|
+
const bestKeyInfo = getBestAvailableKeyInfo();
|
|
7029
|
+
const bestKeyPlaceholder = bestKeyInfo?.selectorFragment ?? null;
|
|
7030
|
+
const bestRuntimeKeyPlaceholder = bestKeyInfo?.runtimeFragment ?? null;
|
|
6731
7031
|
const keyValuesOverride = tryGetContainedInStaticVForSourceLiteralValues(context);
|
|
6732
7032
|
const parentKey = context?.parent ? context.parent : null;
|
|
6733
7033
|
const conditional = getConditionalDirectiveInfo(element);
|
|
@@ -6770,7 +7070,7 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
|
|
|
6770
7070
|
if (!cond) {
|
|
6771
7071
|
conditionalHint = "else";
|
|
6772
7072
|
} else {
|
|
6773
|
-
const condSource = (cond
|
|
7073
|
+
const condSource = getVueExpressionSource(cond, "content", "compiled");
|
|
6774
7074
|
conditionalHint = tryExtractStableHintFromConditionalExpressionSource(condSource) ?? "if";
|
|
6775
7075
|
}
|
|
6776
7076
|
}
|
|
@@ -6780,7 +7080,7 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
|
|
|
6780
7080
|
});
|
|
6781
7081
|
if (showDirective?.exp && (showDirective.exp.type === NodeTypes.SIMPLE_EXPRESSION || showDirective.exp.type === NodeTypes.COMPOUND_EXPRESSION)) {
|
|
6782
7082
|
const exp = showDirective.exp;
|
|
6783
|
-
const source = (exp
|
|
7083
|
+
const source = getVueExpressionSource(exp, "content", "compiled");
|
|
6784
7084
|
const showHint = tryExtractStableHintFromConditionalExpressionSource(source);
|
|
6785
7085
|
if (showHint) {
|
|
6786
7086
|
conditionalHint = conditionalHint ? `${conditionalHint} ${showHint}` : showHint;
|
|
@@ -6814,13 +7114,17 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
|
|
|
6814
7114
|
const tagSuffix = getTagSuffix();
|
|
6815
7115
|
return bestKeyPlaceholder ? templateAttributeValue(`${componentName}-${bestKeyPlaceholder}${clickSuffix}${tagSuffix}`) : staticAttributeValue(`${componentName}${clickSuffix}${tagSuffix}`);
|
|
6816
7116
|
};
|
|
7117
|
+
const getClickRuntimeDataTestId = (clickSuffix) => {
|
|
7118
|
+
const tagSuffix = getTagSuffix();
|
|
7119
|
+
return bestRuntimeKeyPlaceholder ? templateAttributeValue(`${componentName}-${bestRuntimeKeyPlaceholder}${clickSuffix}${tagSuffix}`) : staticAttributeValue(`${componentName}${clickSuffix}${tagSuffix}`);
|
|
7120
|
+
};
|
|
6817
7121
|
const getSubmitDataTestId = (identifier) => {
|
|
6818
7122
|
const tagSuffix = getTagSuffix();
|
|
6819
7123
|
return `${componentName}-${identifier}${tagSuffix}`;
|
|
6820
7124
|
};
|
|
6821
7125
|
const applyResolvedDataTestIdForElement = (args) => {
|
|
6822
7126
|
const nativeRole = args.nativeRoleOverride ?? getNativeRoleFromTagSuffix();
|
|
6823
|
-
applyResolvedDataTestId({
|
|
7127
|
+
return applyResolvedDataTestId({
|
|
6824
7128
|
element,
|
|
6825
7129
|
componentName,
|
|
6826
7130
|
parentComponentName,
|
|
@@ -6830,8 +7134,8 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
|
|
|
6830
7134
|
generatedMethodContentByComponent,
|
|
6831
7135
|
nativeRole,
|
|
6832
7136
|
preferredGeneratedValue: args.preferredGeneratedValue,
|
|
6833
|
-
|
|
6834
|
-
|
|
7137
|
+
preferredRuntimeValue: args.preferredRuntimeValue,
|
|
7138
|
+
keyInfo: bestKeyInfo,
|
|
6835
7139
|
keyValuesOverride,
|
|
6836
7140
|
entryOverrides: args.entryOverrides,
|
|
6837
7141
|
semanticNameHint: args.semanticNameHint,
|
|
@@ -6849,7 +7153,7 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
|
|
|
6849
7153
|
return p.type === NodeTypes.DIRECTIVE && p.name === "bind" && p.arg?.type === NodeTypes.SIMPLE_EXPRESSION && p.arg.content === "handler" && !!p.exp;
|
|
6850
7154
|
}) ?? null;
|
|
6851
7155
|
const handlerInfo = handlerDirective ? nodeHandlerAttributeInfo(element) : null;
|
|
6852
|
-
if (
|
|
7156
|
+
if (nativeWrappers[element.tag]?.role === "button" && handlerDirective && !handlerInfo) {
|
|
6853
7157
|
const loc = element.loc?.start;
|
|
6854
7158
|
const locationHint = loc ? `${loc.line}:${loc.column}` : "unknown";
|
|
6855
7159
|
const handlerSource = (handlerDirective.exp?.loc?.source ?? "").trim() || "<unknown>";
|
|
@@ -6858,7 +7162,7 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
|
|
|
6858
7162
|
Element: <${element.tag}>
|
|
6859
7163
|
Handler: ${handlerSource}
|
|
6860
7164
|
|
|
6861
|
-
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
|
|
7165
|
+
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.`
|
|
6862
7166
|
);
|
|
6863
7167
|
}
|
|
6864
7168
|
if (nativeWrappersValue) {
|
|
@@ -6985,20 +7289,20 @@ Fix: remove the explicit ${attrLabel}, or change existingIdBehavior to "overwrit
|
|
|
6985
7289
|
contextFilename: context.filename
|
|
6986
7290
|
});
|
|
6987
7291
|
const clickHint = trimLeadingSeparators(clickSuffix) || void 0;
|
|
6988
|
-
const idOrName =
|
|
7292
|
+
const idOrName = getStaticIdOrNameHint(element) || void 0;
|
|
6989
7293
|
const semanticHintCandidates = [clickHint, idOrName, innerText, conditionalHint].map((value) => (value ?? "").trim()).filter(Boolean).filter((value, index, values) => values.indexOf(value) === index);
|
|
6990
7294
|
const [semanticNameHint2, ...semanticNameHintAlternates] = semanticHintCandidates;
|
|
6991
7295
|
const pomMergeKey = clickHint ? `click:hint:${clickHint}` : void 0;
|
|
6992
7296
|
const testId = getClickDataTestId(clickSuffix);
|
|
6993
|
-
|
|
7297
|
+
const runtimeTestId = getClickRuntimeDataTestId(clickSuffix);
|
|
7298
|
+
const resolvedDataTestId = applyResolvedDataTestIdForElement({
|
|
6994
7299
|
preferredGeneratedValue: testId,
|
|
7300
|
+
preferredRuntimeValue: runtimeTestId,
|
|
6995
7301
|
semanticNameHint: semanticNameHint2,
|
|
6996
7302
|
semanticNameHintAlternates,
|
|
6997
7303
|
pomMergeKey
|
|
6998
7304
|
});
|
|
6999
|
-
|
|
7000
|
-
tryWrapClickDirectiveForTestEvents(element, testIdAttribute);
|
|
7001
|
-
}
|
|
7305
|
+
tryWrapClickDirectiveForTestEvents(element, testIdAttribute, resolvedDataTestId.runtimeValue);
|
|
7002
7306
|
return;
|
|
7003
7307
|
}
|
|
7004
7308
|
const existingElementDataTestId = tryGetExistingElementDataTestId(element, testIdAttribute);
|
|
@@ -7008,7 +7312,7 @@ Fix: remove the explicit ${attrLabel}, or change existingIdBehavior to "overwrit
|
|
|
7008
7312
|
if (!isRecognizedInteractiveRole) {
|
|
7009
7313
|
return;
|
|
7010
7314
|
}
|
|
7011
|
-
const identifierHint =
|
|
7315
|
+
const identifierHint = getStaticIdOrNameHint(element) || nodeHandlerAttributeValue(element) || innerText || existingElementDataTestId.value || conditionalHint || void 0;
|
|
7012
7316
|
const preferredGeneratedValue = existingElementDataTestId.isDynamic ? templateAttributeValue(existingElementDataTestId.template) : staticAttributeValue(existingElementDataTestId.value);
|
|
7013
7317
|
applyResolvedDataTestIdForElement({
|
|
7014
7318
|
preferredGeneratedValue,
|
|
@@ -7018,7 +7322,7 @@ Fix: remove the explicit ${attrLabel}, or change existingIdBehavior to "overwrit
|
|
|
7018
7322
|
}
|
|
7019
7323
|
const isSubmit = element.props.find((p) => p.type === NodeTypes.ATTRIBUTE && p.name === "type")?.value?.content === "submit";
|
|
7020
7324
|
if (isSubmit) {
|
|
7021
|
-
const identifier =
|
|
7325
|
+
const identifier = getStaticIdOrNameHint(element) || innerText;
|
|
7022
7326
|
if (!identifier) {
|
|
7023
7327
|
const loc = element.loc?.start;
|
|
7024
7328
|
const locationHint = loc ? `${loc.line}:${loc.column}` : "unknown";
|
|
@@ -7070,29 +7374,32 @@ function createBuildProcessorPlugin(options) {
|
|
|
7070
7374
|
getSourceDirs,
|
|
7071
7375
|
basePageClassPath,
|
|
7072
7376
|
normalizedBasePagePath,
|
|
7377
|
+
generation,
|
|
7378
|
+
projectRootRef,
|
|
7379
|
+
nativeWrappers,
|
|
7380
|
+
excludedComponents,
|
|
7381
|
+
getWrapperSearchRoots,
|
|
7382
|
+
getResolvedRouterEntry,
|
|
7383
|
+
loggerRef
|
|
7384
|
+
} = options;
|
|
7385
|
+
const {
|
|
7073
7386
|
outDir,
|
|
7074
7387
|
emitLanguages,
|
|
7075
7388
|
typescriptOutputStructure,
|
|
7076
7389
|
csharp,
|
|
7077
7390
|
generateFixtures,
|
|
7078
7391
|
customPomAttachments,
|
|
7079
|
-
projectRootRef,
|
|
7080
7392
|
customPomDir,
|
|
7393
|
+
requireCustomPomDir,
|
|
7081
7394
|
customPomImportAliases,
|
|
7082
7395
|
customPomImportNameCollisionBehavior,
|
|
7083
7396
|
testIdAttribute,
|
|
7084
7397
|
nameCollisionBehavior,
|
|
7085
|
-
missingSemanticNameBehavior = "error",
|
|
7086
7398
|
existingIdBehavior,
|
|
7087
|
-
nativeWrappers,
|
|
7088
|
-
excludedComponents,
|
|
7089
|
-
getWrapperSearchRoots,
|
|
7090
7399
|
routerAwarePoms,
|
|
7091
|
-
getResolvedRouterEntry,
|
|
7092
7400
|
routerType,
|
|
7093
|
-
routerModuleShims
|
|
7094
|
-
|
|
7095
|
-
} = options;
|
|
7401
|
+
routerModuleShims
|
|
7402
|
+
} = generation;
|
|
7096
7403
|
let lastGeneratedMetrics = {
|
|
7097
7404
|
entryCount: 0,
|
|
7098
7405
|
interactiveComponentCount: 0,
|
|
@@ -7191,10 +7498,9 @@ function createBuildProcessorPlugin(options) {
|
|
|
7191
7498
|
excludedComponents,
|
|
7192
7499
|
getViewsDirAbs(),
|
|
7193
7500
|
{
|
|
7194
|
-
existingIdBehavior: existingIdBehavior ?? "
|
|
7501
|
+
existingIdBehavior: existingIdBehavior ?? "error",
|
|
7195
7502
|
testIdAttribute,
|
|
7196
7503
|
nameCollisionBehavior,
|
|
7197
|
-
missingSemanticNameBehavior,
|
|
7198
7504
|
warn: (message) => loggerRef.current.warn(message),
|
|
7199
7505
|
vueFilesPathMap,
|
|
7200
7506
|
wrapperSearchRoots: getWrapperSearchRoots()
|
|
@@ -7294,6 +7600,7 @@ function createBuildProcessorPlugin(options) {
|
|
|
7294
7600
|
customPomAttachments,
|
|
7295
7601
|
projectRoot: projectRootRef.current,
|
|
7296
7602
|
customPomDir,
|
|
7603
|
+
requireCustomPomDir,
|
|
7297
7604
|
customPomImportAliases,
|
|
7298
7605
|
customPomImportNameCollisionBehavior,
|
|
7299
7606
|
testIdAttribute,
|
|
@@ -7325,6 +7632,11 @@ function createDevProcessorPlugin(options) {
|
|
|
7325
7632
|
projectRootRef,
|
|
7326
7633
|
normalizedBasePagePath,
|
|
7327
7634
|
basePageClassPath,
|
|
7635
|
+
generation,
|
|
7636
|
+
getResolvedRouterEntry,
|
|
7637
|
+
loggerRef
|
|
7638
|
+
} = options;
|
|
7639
|
+
const {
|
|
7328
7640
|
outDir,
|
|
7329
7641
|
emitLanguages,
|
|
7330
7642
|
typescriptOutputStructure,
|
|
@@ -7332,18 +7644,16 @@ function createDevProcessorPlugin(options) {
|
|
|
7332
7644
|
generateFixtures,
|
|
7333
7645
|
customPomAttachments,
|
|
7334
7646
|
customPomDir,
|
|
7647
|
+
requireCustomPomDir,
|
|
7335
7648
|
customPomImportAliases,
|
|
7336
7649
|
customPomImportNameCollisionBehavior,
|
|
7337
|
-
nameCollisionBehavior
|
|
7338
|
-
missingSemanticNameBehavior = "error",
|
|
7650
|
+
nameCollisionBehavior,
|
|
7339
7651
|
existingIdBehavior,
|
|
7340
7652
|
testIdAttribute,
|
|
7341
7653
|
routerAwarePoms,
|
|
7342
|
-
getResolvedRouterEntry,
|
|
7343
7654
|
routerType,
|
|
7344
|
-
routerModuleShims
|
|
7345
|
-
|
|
7346
|
-
} = options;
|
|
7655
|
+
routerModuleShims
|
|
7656
|
+
} = generation;
|
|
7347
7657
|
let scheduleVueFileRegen = null;
|
|
7348
7658
|
const getProjectRootCandidates = () => Array.from(/* @__PURE__ */ new Set([
|
|
7349
7659
|
path.resolve(projectRootRef.current),
|
|
@@ -7530,9 +7840,8 @@ function createDevProcessorPlugin(options) {
|
|
|
7530
7840
|
excludedComponents,
|
|
7531
7841
|
getViewsDirAbs(),
|
|
7532
7842
|
{
|
|
7533
|
-
existingIdBehavior: existingIdBehavior ?? "
|
|
7843
|
+
existingIdBehavior: existingIdBehavior ?? "error",
|
|
7534
7844
|
nameCollisionBehavior,
|
|
7535
|
-
missingSemanticNameBehavior,
|
|
7536
7845
|
testIdAttribute,
|
|
7537
7846
|
warn: (message) => loggerRef.current.warn(message),
|
|
7538
7847
|
vueFilesPathMap: provisionalVuePathMap,
|
|
@@ -7583,6 +7892,7 @@ function createDevProcessorPlugin(options) {
|
|
|
7583
7892
|
customPomAttachments,
|
|
7584
7893
|
projectRoot: projectRootRef.current,
|
|
7585
7894
|
customPomDir,
|
|
7895
|
+
requireCustomPomDir,
|
|
7586
7896
|
customPomImportAliases,
|
|
7587
7897
|
customPomImportNameCollisionBehavior,
|
|
7588
7898
|
pageDirs: getPageDirs(),
|
|
@@ -7818,27 +8128,30 @@ function createSupportPlugins(options) {
|
|
|
7818
8128
|
getViewsDir,
|
|
7819
8129
|
getSourceDirs,
|
|
7820
8130
|
getWrapperSearchRoots,
|
|
7821
|
-
|
|
7822
|
-
|
|
7823
|
-
|
|
8131
|
+
generation,
|
|
8132
|
+
projectRootRef,
|
|
8133
|
+
basePageClassPath: basePageClassPathOverride,
|
|
8134
|
+
loggerRef
|
|
8135
|
+
} = options;
|
|
8136
|
+
const {
|
|
7824
8137
|
outDir,
|
|
7825
8138
|
emitLanguages,
|
|
7826
8139
|
typescriptOutputStructure,
|
|
7827
8140
|
csharp,
|
|
7828
|
-
routerAwarePoms,
|
|
7829
|
-
routerEntry,
|
|
7830
|
-
routerType,
|
|
7831
|
-
routerModuleShims,
|
|
7832
8141
|
generateFixtures,
|
|
7833
8142
|
customPomAttachments,
|
|
7834
|
-
projectRootRef,
|
|
7835
|
-
basePageClassPath: basePageClassPathOverride,
|
|
7836
8143
|
customPomDir,
|
|
8144
|
+
requireCustomPomDir,
|
|
7837
8145
|
customPomImportAliases,
|
|
7838
8146
|
customPomImportNameCollisionBehavior,
|
|
8147
|
+
nameCollisionBehavior,
|
|
8148
|
+
existingIdBehavior,
|
|
7839
8149
|
testIdAttribute,
|
|
7840
|
-
|
|
7841
|
-
|
|
8150
|
+
routerAwarePoms,
|
|
8151
|
+
routerEntry,
|
|
8152
|
+
routerType,
|
|
8153
|
+
routerModuleShims
|
|
8154
|
+
} = generation;
|
|
7842
8155
|
const resolveRouterEntry2 = () => {
|
|
7843
8156
|
if (!routerAwarePoms)
|
|
7844
8157
|
return void 0;
|
|
@@ -7867,27 +8180,12 @@ function createSupportPlugins(options) {
|
|
|
7867
8180
|
getSourceDirs,
|
|
7868
8181
|
basePageClassPath,
|
|
7869
8182
|
normalizedBasePagePath,
|
|
7870
|
-
|
|
7871
|
-
emitLanguages,
|
|
7872
|
-
typescriptOutputStructure,
|
|
7873
|
-
csharp,
|
|
7874
|
-
generateFixtures,
|
|
7875
|
-
customPomAttachments,
|
|
8183
|
+
generation,
|
|
7876
8184
|
projectRootRef,
|
|
7877
|
-
customPomDir,
|
|
7878
|
-
customPomImportAliases,
|
|
7879
|
-
customPomImportNameCollisionBehavior,
|
|
7880
|
-
testIdAttribute,
|
|
7881
|
-
nameCollisionBehavior,
|
|
7882
|
-
missingSemanticNameBehavior,
|
|
7883
|
-
existingIdBehavior,
|
|
7884
8185
|
nativeWrappers,
|
|
7885
8186
|
excludedComponents,
|
|
7886
8187
|
getWrapperSearchRoots,
|
|
7887
|
-
routerAwarePoms,
|
|
7888
|
-
routerType,
|
|
7889
8188
|
getResolvedRouterEntry: resolveRouterEntry2,
|
|
7890
|
-
routerModuleShims,
|
|
7891
8189
|
loggerRef
|
|
7892
8190
|
});
|
|
7893
8191
|
const devProcessor = createDevProcessorPlugin({
|
|
@@ -7902,23 +8200,8 @@ function createSupportPlugins(options) {
|
|
|
7902
8200
|
projectRootRef,
|
|
7903
8201
|
normalizedBasePagePath,
|
|
7904
8202
|
basePageClassPath,
|
|
7905
|
-
|
|
7906
|
-
emitLanguages,
|
|
7907
|
-
typescriptOutputStructure,
|
|
7908
|
-
csharp,
|
|
7909
|
-
generateFixtures,
|
|
7910
|
-
customPomAttachments,
|
|
7911
|
-
customPomDir,
|
|
7912
|
-
customPomImportAliases,
|
|
7913
|
-
customPomImportNameCollisionBehavior,
|
|
7914
|
-
nameCollisionBehavior,
|
|
7915
|
-
missingSemanticNameBehavior,
|
|
7916
|
-
existingIdBehavior,
|
|
7917
|
-
testIdAttribute,
|
|
7918
|
-
routerAwarePoms,
|
|
7919
|
-
routerType,
|
|
8203
|
+
generation,
|
|
7920
8204
|
getResolvedRouterEntry: resolveRouterEntry2,
|
|
7921
|
-
routerModuleShims,
|
|
7922
8205
|
loggerRef
|
|
7923
8206
|
});
|
|
7924
8207
|
const virtualModules = createTestIdsVirtualModulesPlugin(componentTestIds);
|
|
@@ -8019,6 +8302,13 @@ function tryCreateElementMetadata(args) {
|
|
|
8019
8302
|
};
|
|
8020
8303
|
return metadata;
|
|
8021
8304
|
}
|
|
8305
|
+
function resolveCompilerSfcParse(compilerSfc) {
|
|
8306
|
+
const parse2 = compilerSfc.parse ?? compilerSfc.default?.parse;
|
|
8307
|
+
if (typeof parse2 !== "function") {
|
|
8308
|
+
throw new TypeError("[vue-pom-generator] Failed to resolve @vue/compiler-sfc.parse.");
|
|
8309
|
+
}
|
|
8310
|
+
return parse2;
|
|
8311
|
+
}
|
|
8022
8312
|
function extractMetadataAfterTransform(ast, componentName, elementMetadata, semanticNameMap, testIdAttribute) {
|
|
8023
8313
|
const componentMetadata = /* @__PURE__ */ new Map();
|
|
8024
8314
|
function traverseNode(node) {
|
|
@@ -8215,7 +8505,8 @@ function createVuePluginWithTestIds(options) {
|
|
|
8215
8505
|
}
|
|
8216
8506
|
const componentName = getComponentNameFromPath(cleanPath);
|
|
8217
8507
|
loggerRef.current.debug(`Collecting metadata for ${cleanPath} (component: ${componentName})`);
|
|
8218
|
-
const
|
|
8508
|
+
const compilerSfc = await import("@vue/compiler-sfc");
|
|
8509
|
+
const parse2 = resolveCompilerSfcParse(compilerSfc);
|
|
8219
8510
|
const compilerDom2 = await import("@vue/compiler-dom");
|
|
8220
8511
|
const compile = compilerDom2.compile;
|
|
8221
8512
|
const { descriptor } = parse2(code, { filename: cleanPath });
|
|
@@ -8247,8 +8538,7 @@ function createVuePluginWithTestIds(options) {
|
|
|
8247
8538
|
});
|
|
8248
8539
|
const api = viteVuePlugin?.api;
|
|
8249
8540
|
if (!api) {
|
|
8250
|
-
|
|
8251
|
-
return;
|
|
8541
|
+
throw new Error("[vue-pom-generator] Nuxt bridge could not find vite:vue plugin to patch.");
|
|
8252
8542
|
}
|
|
8253
8543
|
const currentOptions = api.options ?? {};
|
|
8254
8544
|
const currentTemplate = currentOptions.template ?? {};
|
|
@@ -8314,33 +8604,6 @@ function assertOneOf(value, allowed, name) {
|
|
|
8314
8604
|
}
|
|
8315
8605
|
throw new TypeError(`${name} must be one of: ${allowed.join(", ")}.`);
|
|
8316
8606
|
}
|
|
8317
|
-
function assertErrorBehavior(value, name) {
|
|
8318
|
-
if (!value) {
|
|
8319
|
-
return;
|
|
8320
|
-
}
|
|
8321
|
-
if (value === "ignore" || value === "error") {
|
|
8322
|
-
return;
|
|
8323
|
-
}
|
|
8324
|
-
if (typeof value !== "object" || Array.isArray(value)) {
|
|
8325
|
-
throw new TypeError(`${name} must be "ignore", "error", or an object.`);
|
|
8326
|
-
}
|
|
8327
|
-
const supportedKeys = /* @__PURE__ */ new Set(["missingSemanticNameBehavior"]);
|
|
8328
|
-
for (const key of Object.keys(value)) {
|
|
8329
|
-
if (!supportedKeys.has(key)) {
|
|
8330
|
-
throw new TypeError(`${name} contains unsupported key "${key}".`);
|
|
8331
|
-
}
|
|
8332
|
-
}
|
|
8333
|
-
assertOneOf(value.missingSemanticNameBehavior, ["ignore", "error"], `${name}.missingSemanticNameBehavior`);
|
|
8334
|
-
}
|
|
8335
|
-
function resolveMissingSemanticNameBehavior(value) {
|
|
8336
|
-
if (!value) {
|
|
8337
|
-
return "error";
|
|
8338
|
-
}
|
|
8339
|
-
if (value === "ignore" || value === "error") {
|
|
8340
|
-
return value;
|
|
8341
|
-
}
|
|
8342
|
-
return value.missingSemanticNameBehavior ?? "error";
|
|
8343
|
-
}
|
|
8344
8607
|
function readPackageJson(projectRoot) {
|
|
8345
8608
|
const packageJsonPath = path.join(projectRoot, "package.json");
|
|
8346
8609
|
if (!fs.existsSync(packageJsonPath)) {
|
|
@@ -8449,7 +8712,7 @@ function applyTemplateCompilerOptionsToResolvedVuePlugin(config, templateCompile
|
|
|
8449
8712
|
'[vue-pom-generator] vuePluginOwnership="external" requires the resolved Vite Vue plugin, but none was found. Add vue() to your Vite plugins before spreading createVuePomGeneratorPlugins(...).'
|
|
8450
8713
|
);
|
|
8451
8714
|
}
|
|
8452
|
-
throw new Error("[vue-pom-generator] Nuxt
|
|
8715
|
+
throw new Error("[vue-pom-generator] Nuxt bridge could not find vite:vue plugin to patch.");
|
|
8453
8716
|
}
|
|
8454
8717
|
const currentOptions = viteVuePlugin.api.options ?? {};
|
|
8455
8718
|
const currentTemplate = currentOptions.template ?? {};
|
|
@@ -8506,18 +8769,23 @@ function createVuePomGeneratorPlugins(options = {}) {
|
|
|
8506
8769
|
const vueGenerationOptions = generationOptions;
|
|
8507
8770
|
const verbosity = options.logging?.verbosity ?? "warn";
|
|
8508
8771
|
const vueOptions = options.vueOptions;
|
|
8509
|
-
const
|
|
8510
|
-
|
|
8511
|
-
|
|
8512
|
-
|
|
8513
|
-
|
|
8514
|
-
|
|
8515
|
-
|
|
8516
|
-
|
|
8517
|
-
|
|
8518
|
-
|
|
8519
|
-
|
|
8520
|
-
|
|
8772
|
+
const resolvedInjectionOptionsRef = {
|
|
8773
|
+
current: resolveInjectionSupportOptions({
|
|
8774
|
+
isNuxt,
|
|
8775
|
+
viewsDir: injection.viewsDir,
|
|
8776
|
+
componentDirs: injection.componentDirs,
|
|
8777
|
+
layoutDirs: injection.layoutDirs,
|
|
8778
|
+
wrapperSearchRoots: injection.wrapperSearchRoots,
|
|
8779
|
+
nativeWrappers: injection.nativeWrappers,
|
|
8780
|
+
excludedComponents: injection.excludeComponents,
|
|
8781
|
+
existingIdBehavior: injection.existingIdBehavior,
|
|
8782
|
+
testIdAttribute: injection.attribute
|
|
8783
|
+
})
|
|
8784
|
+
};
|
|
8785
|
+
const resolvedInjectionOptions = resolvedInjectionOptionsRef.current;
|
|
8786
|
+
const nativeWrappers = resolvedInjectionOptions.nativeWrappers;
|
|
8787
|
+
const excludedComponents = resolvedInjectionOptions.excludedComponents;
|
|
8788
|
+
const testIdAttribute = resolvedInjectionOptions.testIdAttribute;
|
|
8521
8789
|
const routerEntry = !isNuxt ? vueGenerationOptions?.router?.entry : void 0;
|
|
8522
8790
|
const routerType = isNuxt ? "nuxt" : vueGenerationOptions?.router?.type ?? "vue-router";
|
|
8523
8791
|
const routerModuleShims = !isNuxt ? vueGenerationOptions?.router?.moduleShims : void 0;
|
|
@@ -8526,27 +8794,41 @@ function createVuePomGeneratorPlugins(options = {}) {
|
|
|
8526
8794
|
}
|
|
8527
8795
|
const vuePluginOwnership = isNuxt ? "external" : options.vuePluginOwnership ?? "internal";
|
|
8528
8796
|
const usesExternalVuePlugin = vuePluginOwnership === "external";
|
|
8529
|
-
const csharp = generationOptions?.csharp;
|
|
8530
|
-
const errorBehavior = options.errorBehavior;
|
|
8531
|
-
const missingSemanticNameBehavior = resolveMissingSemanticNameBehavior(errorBehavior);
|
|
8532
|
-
const typescriptOutputStructure = generationOptions?.playwright?.outputStructure ?? "aggregated";
|
|
8533
8797
|
const generateFixtures = generationOptions?.playwright?.fixtures;
|
|
8534
8798
|
const customPoms = generationOptions?.playwright?.customPoms;
|
|
8535
8799
|
const resolvedCustomPomAttachments = customPoms?.attachments ?? [];
|
|
8536
|
-
const resolvedCustomPomDir = customPoms?.dir ?? "tests/playwright/pom/custom";
|
|
8537
8800
|
const resolvedCustomPomImportAliases = customPoms?.importAliases;
|
|
8538
|
-
const
|
|
8801
|
+
const requireCustomPomDir = customPoms?.dir !== void 0 || resolvedCustomPomAttachments.length > 0 || Object.keys(resolvedCustomPomImportAliases ?? {}).length > 0;
|
|
8802
|
+
const resolvedGenerationOptions = resolveGenerationSupportOptions({
|
|
8803
|
+
outDir: generationOptions?.outDir,
|
|
8804
|
+
emitLanguages: generationOptions?.emit,
|
|
8805
|
+
typescriptOutputStructure: generationOptions?.playwright?.outputStructure,
|
|
8806
|
+
csharp: generationOptions?.csharp,
|
|
8807
|
+
generateFixtures,
|
|
8808
|
+
customPomAttachments: resolvedCustomPomAttachments,
|
|
8809
|
+
customPomDir: customPoms?.dir,
|
|
8810
|
+
requireCustomPomDir,
|
|
8811
|
+
customPomImportAliases: resolvedCustomPomImportAliases,
|
|
8812
|
+
customPomImportNameCollisionBehavior: customPoms?.importNameCollisionBehavior,
|
|
8813
|
+
nameCollisionBehavior: generationOptions?.nameCollisionBehavior,
|
|
8814
|
+
existingIdBehavior: resolvedInjectionOptions.existingIdBehavior,
|
|
8815
|
+
testIdAttribute,
|
|
8816
|
+
routerAwarePoms: typeof routerEntry === "string" && routerEntry.trim().length > 0 || routerType === "nuxt",
|
|
8817
|
+
routerEntry,
|
|
8818
|
+
routerType,
|
|
8819
|
+
routerModuleShims
|
|
8820
|
+
});
|
|
8539
8821
|
const basePageClassPathOverride = generationOptions?.basePageClassPath;
|
|
8540
|
-
const getPageDirs = () =>
|
|
8822
|
+
const getPageDirs = () => resolvedInjectionOptionsRef.current.pageDirs;
|
|
8541
8823
|
const getViewsDir = () => getPageDirs()[0] ?? "src/views";
|
|
8542
|
-
const getComponentDirs = () =>
|
|
8543
|
-
const getLayoutDirs = () =>
|
|
8824
|
+
const getComponentDirs = () => resolvedInjectionOptionsRef.current.componentDirs;
|
|
8825
|
+
const getLayoutDirs = () => resolvedInjectionOptionsRef.current.layoutDirs;
|
|
8544
8826
|
const getSourceDirs = () => Array.from(/* @__PURE__ */ new Set([
|
|
8545
8827
|
...getPageDirs(),
|
|
8546
8828
|
...getComponentDirs(),
|
|
8547
8829
|
...getLayoutDirs()
|
|
8548
8830
|
]));
|
|
8549
|
-
const getWrapperSearchRoots = () =>
|
|
8831
|
+
const getWrapperSearchRoots = () => resolvedInjectionOptionsRef.current.wrapperSearchRoots;
|
|
8550
8832
|
const sharedStateKey = JSON.stringify({
|
|
8551
8833
|
cwd: process.cwd(),
|
|
8552
8834
|
mode: isNuxt ? "nuxt" : "vue",
|
|
@@ -8554,9 +8836,9 @@ function createVuePomGeneratorPlugins(options = {}) {
|
|
|
8554
8836
|
componentDirs: isNuxt ? null : getComponentDirs(),
|
|
8555
8837
|
layoutDirs: isNuxt ? null : getLayoutDirs(),
|
|
8556
8838
|
wrapperSearchRoots: isNuxt ? null : getWrapperSearchRoots(),
|
|
8557
|
-
outDir,
|
|
8839
|
+
outDir: resolvedGenerationOptions.outDir,
|
|
8558
8840
|
testIdAttribute,
|
|
8559
|
-
routerType,
|
|
8841
|
+
routerType: resolvedGenerationOptions.routerType,
|
|
8560
8842
|
vuePluginOwnership
|
|
8561
8843
|
});
|
|
8562
8844
|
const sharedState = getSharedGeneratorState(sharedStateKey);
|
|
@@ -8577,23 +8859,22 @@ function createVuePomGeneratorPlugins(options = {}) {
|
|
|
8577
8859
|
if (isNuxt) {
|
|
8578
8860
|
const nuxtDiscovery = await loadNuxtProjectDiscovery(process.cwd());
|
|
8579
8861
|
projectRootRef.current = nuxtDiscovery.rootDir;
|
|
8580
|
-
|
|
8581
|
-
|
|
8582
|
-
|
|
8583
|
-
|
|
8862
|
+
resolvedInjectionOptionsRef.current = applyNuxtDiscoveryToInjectionOptions(
|
|
8863
|
+
resolvedInjectionOptionsRef.current,
|
|
8864
|
+
nuxtDiscovery
|
|
8865
|
+
);
|
|
8584
8866
|
}
|
|
8585
8867
|
assertNonEmptyString(testIdAttribute, "[vue-pom-generator] injection.attribute");
|
|
8586
8868
|
assertNonEmptyString(getViewsDir(), "[vue-pom-generator] injection.viewsDir");
|
|
8587
8869
|
assertNonEmptyStringArray(getComponentDirs(), "[vue-pom-generator] injection.componentDirs");
|
|
8588
8870
|
assertNonEmptyStringArray(getLayoutDirs(), "[vue-pom-generator] injection.layoutDirs");
|
|
8589
8871
|
assertNonEmptyStringArray(getWrapperSearchRoots(), "[vue-pom-generator] injection.wrapperSearchRoots");
|
|
8590
|
-
assertErrorBehavior(errorBehavior, "[vue-pom-generator] errorBehavior");
|
|
8591
8872
|
if (generationEnabled) {
|
|
8592
|
-
assertNonEmptyString(outDir, "[vue-pom-generator] generation.outDir");
|
|
8593
|
-
assertOneOf(typescriptOutputStructure, ["aggregated", "split"], "[vue-pom-generator] generation.playwright.outputStructure");
|
|
8594
|
-
assertRouterModuleShims(routerModuleShims, "[vue-pom-generator] generation.router.moduleShims");
|
|
8595
|
-
if (!isNuxt && vueGenerationOptions?.router && routerType === "vue-router") {
|
|
8596
|
-
assertNonEmptyString(routerEntry, "[vue-pom-generator] generation.router.entry");
|
|
8873
|
+
assertNonEmptyString(resolvedGenerationOptions.outDir, "[vue-pom-generator] generation.outDir");
|
|
8874
|
+
assertOneOf(resolvedGenerationOptions.typescriptOutputStructure, ["aggregated", "split"], "[vue-pom-generator] generation.playwright.outputStructure");
|
|
8875
|
+
assertRouterModuleShims(resolvedGenerationOptions.routerModuleShims, "[vue-pom-generator] generation.router.moduleShims");
|
|
8876
|
+
if (!isNuxt && vueGenerationOptions?.router && resolvedGenerationOptions.routerType === "vue-router") {
|
|
8877
|
+
assertNonEmptyString(resolvedGenerationOptions.routerEntry, "[vue-pom-generator] generation.router.entry");
|
|
8597
8878
|
}
|
|
8598
8879
|
}
|
|
8599
8880
|
if (usesExternalVuePlugin) {
|
|
@@ -8615,8 +8896,8 @@ function createVuePomGeneratorPlugins(options = {}) {
|
|
|
8615
8896
|
const { componentTestIds, elementMetadata, semanticNameMap, componentHierarchyMap, vueFilesPathMap } = sharedState;
|
|
8616
8897
|
const { metadataCollectorPlugin, internalVuePlugin, templateCompilerOptions } = createVuePluginWithTestIds({
|
|
8617
8898
|
vueOptions,
|
|
8618
|
-
existingIdBehavior,
|
|
8619
|
-
nameCollisionBehavior,
|
|
8899
|
+
existingIdBehavior: resolvedGenerationOptions.existingIdBehavior,
|
|
8900
|
+
nameCollisionBehavior: resolvedGenerationOptions.nameCollisionBehavior,
|
|
8620
8901
|
nativeWrappers,
|
|
8621
8902
|
elementMetadata,
|
|
8622
8903
|
semanticNameMap,
|
|
@@ -8631,7 +8912,6 @@ function createVuePomGeneratorPlugins(options = {}) {
|
|
|
8631
8912
|
getProjectRoot: () => projectRootRef.current
|
|
8632
8913
|
});
|
|
8633
8914
|
templateCompilerOptionsForResolvedPlugin = templateCompilerOptions;
|
|
8634
|
-
const routerAwarePoms = typeof routerEntry === "string" && routerEntry.trim().length > 0 || routerType === "nuxt";
|
|
8635
8915
|
const supportPlugins = createSupportPlugins({
|
|
8636
8916
|
componentTestIds,
|
|
8637
8917
|
componentHierarchyMap,
|
|
@@ -8644,26 +8924,10 @@ function createVuePomGeneratorPlugins(options = {}) {
|
|
|
8644
8924
|
getViewsDir,
|
|
8645
8925
|
getSourceDirs,
|
|
8646
8926
|
getWrapperSearchRoots: getWrapperSearchRootsAbs,
|
|
8647
|
-
|
|
8648
|
-
missingSemanticNameBehavior,
|
|
8649
|
-
existingIdBehavior,
|
|
8650
|
-
outDir,
|
|
8651
|
-
emitLanguages,
|
|
8652
|
-
typescriptOutputStructure,
|
|
8653
|
-
csharp,
|
|
8654
|
-
routerAwarePoms,
|
|
8655
|
-
routerEntry,
|
|
8656
|
-
generateFixtures,
|
|
8927
|
+
generation: resolvedGenerationOptions,
|
|
8657
8928
|
projectRootRef,
|
|
8658
8929
|
basePageClassPath: basePageClassPathOverride,
|
|
8659
|
-
|
|
8660
|
-
customPomDir: resolvedCustomPomDir,
|
|
8661
|
-
customPomImportAliases: resolvedCustomPomImportAliases,
|
|
8662
|
-
customPomImportNameCollisionBehavior: resolvedCustomPomImportCollisionBehavior,
|
|
8663
|
-
testIdAttribute,
|
|
8664
|
-
loggerRef,
|
|
8665
|
-
routerType,
|
|
8666
|
-
routerModuleShims
|
|
8930
|
+
loggerRef
|
|
8667
8931
|
});
|
|
8668
8932
|
if (isNuxt) {
|
|
8669
8933
|
loggerRef.current.info("Nuxt environment detected. Skipping internal @vitejs/plugin-vue to avoid conflicts.");
|