@immense/vue-pom-generator 1.0.38 → 1.0.40

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/dist/index.cjs CHANGED
@@ -26,10 +26,10 @@ const path = require("node:path");
26
26
  const process = require("node:process");
27
27
  const node_url = require("node:url");
28
28
  const fs = require("node:fs");
29
+ const parser = require("@babel/parser");
29
30
  const jsdom = require("jsdom");
30
31
  const compilerCore = require("@vue/compiler-core");
31
32
  const types = require("@babel/types");
32
- const parser = require("@babel/parser");
33
33
  const node_perf_hooks = require("node:perf_hooks");
34
34
  const compilerDom = require("@vue/compiler-dom");
35
35
  const compilerSfc = require("@vue/compiler-sfc");
@@ -2095,9 +2095,6 @@ Fix: either (1) include ${args.bestKeyPlaceholder} in your :${attrLabel} templat
2095
2095
  })();
2096
2096
  const tryMergeWithExistingPrimary = (candidateActionName) => {
2097
2097
  const mergeKey = (args.pomMergeKey ?? "").trim();
2098
- if (isKeyed) {
2099
- return false;
2100
- }
2101
2098
  const existingEntry = primaryByActionName.get(candidateActionName);
2102
2099
  const existingPom = existingEntry?.pom;
2103
2100
  if (!existingEntry || !existingPom) {
@@ -2108,6 +2105,9 @@ Fix: either (1) include ${args.bestKeyPlaceholder} in your :${attrLabel} templat
2108
2105
  ...existingPom.alternateFormattedDataTestIds ?? []
2109
2106
  ];
2110
2107
  const sharesSelectorIdentity = existingSelectors.includes(formattedDataTestIdForPom);
2108
+ if (isKeyed && !sharesSelectorIdentity) {
2109
+ return false;
2110
+ }
2111
2111
  if (!mergeKey && !sharesSelectorIdentity) {
2112
2112
  return false;
2113
2113
  }
@@ -2154,6 +2154,11 @@ Fix: either (1) include ${args.bestKeyPlaceholder} in your :${attrLabel} templat
2154
2154
  conflicts = false;
2155
2155
  }
2156
2156
  }
2157
+ if (conflicts && nameCollisionBehavior === "error" && tryMergeWithExistingPrimary(actionName)) {
2158
+ methodName = candidate;
2159
+ mergedIntoExisting = true;
2160
+ break;
2161
+ }
2157
2162
  if (conflicts && nameCollisionBehavior === "error") {
2158
2163
  const roleSuffix = upperFirst(normalizedRole || "Element");
2159
2164
  const baseNameUpper = upperFirst(baseWithSuffix);
@@ -2200,11 +2205,6 @@ Fix: either (1) include ${args.bestKeyPlaceholder} in your :${attrLabel} templat
2200
2205
  reservedMembers.add(actionName);
2201
2206
  break;
2202
2207
  }
2203
- if (nameCollisionBehavior === "error" && tryMergeWithExistingPrimary(actionName)) {
2204
- methodName = candidate;
2205
- mergedIntoExisting = true;
2206
- break;
2207
- }
2208
2208
  if (!collisionDetails) {
2209
2209
  collisionDetails = { getterName: chosenGetterName, actionName };
2210
2210
  collisionHint = hint || (args.semanticNameHint ?? "").trim() || null;
@@ -3781,6 +3781,7 @@ function generateAggregatedCSharpFiles(componentHierarchyMap, outDir, options =
3781
3781
  chunks.push(" {");
3782
3782
  if (pom.formattedDataTestId.includes("${") || allTestIds.length <= 1) {
3783
3783
  chunks.push(` await ${locatorName}${pom.formattedDataTestId.includes("${") ? `(${args})` : ""}.ClickAsync();`);
3784
+ chunks.push(` return new ${target}(Page);`);
3784
3785
  } else {
3785
3786
  chunks.push(" Exception? lastError = null;");
3786
3787
  chunks.push(` foreach (var testId in new[] { ${allTestIds.map(toCSharpTestIdExpression).join(", ")} })`);
@@ -3801,7 +3802,6 @@ function generateAggregatedCSharpFiles(componentHierarchyMap, outDir, options =
3801
3802
  chunks.push(" }");
3802
3803
  chunks.push(' throw lastError ?? new System.Exception("[pom] Failed to navigate using any candidate test id.");');
3803
3804
  }
3804
- chunks.push(` return new ${target}(Page);`);
3805
3805
  chunks.push(" }");
3806
3806
  chunks.push("");
3807
3807
  continue;
@@ -4058,6 +4058,7 @@ function generateViewObjectModelContent(componentName, dependencies, componentHi
4058
4058
  };
4059
4059
  const customPomClassIdentifierMap = options.customPomClassIdentifierMap ?? {};
4060
4060
  const customPomAvailableClassIdentifiers = options.customPomAvailableClassIdentifiers ?? /* @__PURE__ */ new Set();
4061
+ const customPomMethodSignaturesByClass = options.customPomMethodSignaturesByClass ?? /* @__PURE__ */ new Map();
4061
4062
  const attachmentsForThisClass = customPomAttachments.filter((a) => {
4062
4063
  if (!Object.prototype.hasOwnProperty.call(customPomClassIdentifierMap, a.className))
4063
4064
  return false;
@@ -4068,7 +4069,9 @@ function generateViewObjectModelContent(componentName, dependencies, componentHi
4068
4069
  return a.attachWhenUsesComponents.some((c) => hasChildComponent(c));
4069
4070
  }).map((a) => ({
4070
4071
  className: customPomClassIdentifierMap[a.className],
4071
- propertyName: a.propertyName
4072
+ propertyName: a.propertyName,
4073
+ flatten: a.flatten ?? false,
4074
+ methodSignatures: a.flatten ? customPomMethodSignaturesByClass.get(a.className) ?? /* @__PURE__ */ new Map() : /* @__PURE__ */ new Map()
4072
4075
  }));
4073
4076
  let content = "";
4074
4077
  const sourceRel = toPosixRelativePath(outputDir, filePath);
@@ -4109,6 +4112,15 @@ export class ${className} extends BasePage {
4109
4112
  `;
4110
4113
  const widgetInstances = isView ? getWidgetInstancesForView(componentName, dependencies.dataTestIdSet, customPomAvailableClassIdentifiers) : [];
4111
4114
  const componentRefsForInstances = isView ? usedComponentSet?.size ? usedComponentSet : childrenComponentSet : childrenComponentSet;
4115
+ const childInstancePropertyNames = Array.from(componentRefsForInstances).filter((child) => componentHierarchyMap.has(child) && componentHierarchyMap.get(child)?.dataTestIdSet.size).map((child) => child.split(".vue")[0]);
4116
+ const blockedViewPassthroughMethodNames = new Set(
4117
+ attachmentsForThisClass.filter((a) => a.flatten).flatMap((a) => Array.from(a.methodSignatures.keys()))
4118
+ );
4119
+ const reservedAttachmentPassthroughNames = /* @__PURE__ */ new Set([
4120
+ ...attachmentsForThisClass.map((a) => a.propertyName),
4121
+ ...widgetInstances.map((w) => w.propertyName),
4122
+ ...childInstancePropertyNames
4123
+ ]);
4112
4124
  if (isView && (componentRefsForInstances.size > 0 || attachmentsForThisClass.length > 0 || widgetInstances.length > 0)) {
4113
4125
  content += getComponentInstances(componentRefsForInstances, componentHierarchyMap, attachmentsForThisClass, widgetInstances);
4114
4126
  content += getConstructor(componentRefsForInstances, componentHierarchyMap, attachmentsForThisClass, widgetInstances, { testIdAttribute });
@@ -4117,8 +4129,9 @@ export class ${className} extends BasePage {
4117
4129
  content += getComponentInstances(/* @__PURE__ */ new Set(), componentHierarchyMap, attachmentsForThisClass);
4118
4130
  content += getConstructor(/* @__PURE__ */ new Set(), componentHierarchyMap, attachmentsForThisClass, [], { testIdAttribute });
4119
4131
  }
4132
+ content += getAttachmentPassthroughMethods(componentName, dependencies, attachmentsForThisClass, reservedAttachmentPassthroughNames);
4120
4133
  if (isView && componentRefsForInstances.size === 1) {
4121
- content += getViewPassthroughMethods(componentName, dependencies, componentRefsForInstances, componentHierarchyMap);
4134
+ content += getViewPassthroughMethods(componentName, dependencies, componentRefsForInstances, componentHierarchyMap, blockedViewPassthroughMethodNames);
4122
4135
  }
4123
4136
  if (isView && options.vueRouterFluentChaining) {
4124
4137
  const routeMeta = options.routeMetaByComponent?.[componentName] ?? null;
@@ -4129,7 +4142,7 @@ export class ${className} extends BasePage {
4129
4142
  content += "}\n";
4130
4143
  return content;
4131
4144
  }
4132
- function getViewPassthroughMethods(viewName, viewDependencies, childrenComponentSet, componentHierarchyMap) {
4145
+ function getViewPassthroughMethods(viewName, viewDependencies, childrenComponentSet, componentHierarchyMap, blockedMethodNames = /* @__PURE__ */ new Set()) {
4133
4146
  const existingOnView = viewDependencies.generatedMethods ?? /* @__PURE__ */ new Map();
4134
4147
  const methodToChildren = /* @__PURE__ */ new Map();
4135
4148
  for (const child of childrenComponentSet) {
@@ -4143,7 +4156,7 @@ function getViewPassthroughMethods(viewName, viewDependencies, childrenComponent
4143
4156
  for (const [name, sig] of methods.entries()) {
4144
4157
  if (!sig)
4145
4158
  continue;
4146
- if (existingOnView.has(name))
4159
+ if (existingOnView.has(name) || blockedMethodNames.has(name))
4147
4160
  continue;
4148
4161
  const list = methodToChildren.get(name) ?? [];
4149
4162
  list.push({ childProp, params: sig.params, argNames: sig.argNames });
@@ -4174,6 +4187,130 @@ function getViewPassthroughMethods(viewName, viewDependencies, childrenComponent
4174
4187
  ""
4175
4188
  ].join("\n");
4176
4189
  }
4190
+ function getAttachmentPassthroughMethods(ownerName, ownerDependencies, attachmentsForThisClass, reservedMemberNames) {
4191
+ if (!attachmentsForThisClass.some((a) => a.flatten && a.methodSignatures.size > 0)) {
4192
+ return "";
4193
+ }
4194
+ const existingOnClass = ownerDependencies.generatedMethods ?? /* @__PURE__ */ new Map();
4195
+ const methodToAttachments = /* @__PURE__ */ new Map();
4196
+ for (const attachment of attachmentsForThisClass) {
4197
+ if (!attachment.flatten) {
4198
+ continue;
4199
+ }
4200
+ for (const [methodName, signature] of attachment.methodSignatures.entries()) {
4201
+ if (methodName === "constructor" || existingOnClass.has(methodName) || reservedMemberNames.has(methodName)) {
4202
+ continue;
4203
+ }
4204
+ const list = methodToAttachments.get(methodName) ?? [];
4205
+ list.push({
4206
+ propertyName: attachment.propertyName,
4207
+ params: signature.params,
4208
+ argNames: signature.argNames
4209
+ });
4210
+ methodToAttachments.set(methodName, list);
4211
+ }
4212
+ }
4213
+ const sorted = Array.from(methodToAttachments.entries()).sort((a, b) => a[0].localeCompare(b[0]));
4214
+ const lines = [];
4215
+ for (const [methodName, candidates] of sorted) {
4216
+ if (candidates.length !== 1) {
4217
+ continue;
4218
+ }
4219
+ const { propertyName, params, argNames } = candidates[0];
4220
+ const callArgs = argNames.join(", ");
4221
+ const invocation = callArgs ? `this.${propertyName}.${methodName}(${callArgs})` : `this.${propertyName}.${methodName}()`;
4222
+ lines.push(
4223
+ "",
4224
+ ` ${methodName}(${params}) {`,
4225
+ ` return ${invocation};`,
4226
+ " }"
4227
+ );
4228
+ }
4229
+ if (!lines.length) {
4230
+ return "";
4231
+ }
4232
+ return [
4233
+ "",
4234
+ ` // Passthrough methods composed from custom helper attachments of ${ownerName}.`,
4235
+ ...lines,
4236
+ ""
4237
+ ].join("\n");
4238
+ }
4239
+ function sliceNodeSource(source, node) {
4240
+ if (node.start == null || node.end == null) {
4241
+ return null;
4242
+ }
4243
+ const snippet = source.slice(node.start, node.end).trim();
4244
+ return snippet.length ? snippet : null;
4245
+ }
4246
+ function getCustomPomCallArgumentName(param) {
4247
+ if (param.type === "Identifier") {
4248
+ return param.name;
4249
+ }
4250
+ if (param.type === "AssignmentPattern") {
4251
+ return param.left.type === "Identifier" ? param.left.name : null;
4252
+ }
4253
+ if (param.type === "RestElement") {
4254
+ return param.argument.type === "Identifier" ? `...${param.argument.name}` : null;
4255
+ }
4256
+ return null;
4257
+ }
4258
+ function extractCustomPomMethodSignatures(source, exportName) {
4259
+ const signatures = /* @__PURE__ */ new Map();
4260
+ let ast;
4261
+ try {
4262
+ ast = parser.parse(source, {
4263
+ sourceType: "module",
4264
+ plugins: ["typescript", "jsx"]
4265
+ });
4266
+ } catch {
4267
+ return signatures;
4268
+ }
4269
+ for (const statement of ast.program.body) {
4270
+ if (statement.type !== "ExportNamedDeclaration" || !statement.declaration || statement.declaration.type !== "ClassDeclaration") {
4271
+ continue;
4272
+ }
4273
+ const declaration = statement.declaration;
4274
+ if (declaration.id?.name !== exportName) {
4275
+ continue;
4276
+ }
4277
+ for (const member of declaration.body.body) {
4278
+ if (member.type !== "ClassMethod" || member.kind !== "method" || member.static || member.computed) {
4279
+ continue;
4280
+ }
4281
+ if (member.accessibility === "private" || member.accessibility === "protected") {
4282
+ continue;
4283
+ }
4284
+ if (member.key.type !== "Identifier") {
4285
+ continue;
4286
+ }
4287
+ const params = [];
4288
+ const argNames = [];
4289
+ let supported = true;
4290
+ member.params.forEach((param) => {
4291
+ if (!supported) {
4292
+ return;
4293
+ }
4294
+ const paramSource = sliceNodeSource(source, param);
4295
+ const argName = getCustomPomCallArgumentName(param);
4296
+ if (!paramSource || !argName) {
4297
+ supported = false;
4298
+ return;
4299
+ }
4300
+ params.push(paramSource);
4301
+ argNames.push(argName);
4302
+ });
4303
+ if (!supported) {
4304
+ continue;
4305
+ }
4306
+ signatures.set(member.key.name, {
4307
+ params: params.join(", "),
4308
+ argNames
4309
+ });
4310
+ }
4311
+ }
4312
+ return signatures;
4313
+ }
4177
4314
  function ensureDir(dir) {
4178
4315
  const normalized = dir.replace(/\\/g, "/");
4179
4316
  if (!fs.existsSync(normalized)) {
@@ -4217,6 +4354,7 @@ async function generateAggregatedFiles(componentHierarchyMap, vueFilesPathMap, b
4217
4354
  ]);
4218
4355
  const usedImportIdentifiers = /* @__PURE__ */ new Set();
4219
4356
  const customPomClassIdentifierMap2 = {};
4357
+ const customPomMethodSignaturesByClass2 = /* @__PURE__ */ new Map();
4220
4358
  const ensureUniqueIdentifier = (base2) => {
4221
4359
  let candidate = base2;
4222
4360
  let i = 2;
@@ -4230,7 +4368,10 @@ async function generateAggregatedFiles(componentHierarchyMap, vueFilesPathMap, b
4230
4368
  const customDirRelOrAbs = options.customPomDir ?? "tests/playwright/pom/custom";
4231
4369
  const customDirAbs = path.isAbsolute(customDirRelOrAbs) ? customDirRelOrAbs : path.resolve(projectRoot, customDirRelOrAbs);
4232
4370
  if (!fs.existsSync(customDirAbs)) {
4233
- return;
4371
+ return {
4372
+ classIdentifierMap: customPomClassIdentifierMap2,
4373
+ methodSignaturesByClass: customPomMethodSignaturesByClass2
4374
+ };
4234
4375
  }
4235
4376
  const files = fs.readdirSync(customDirAbs).filter((f) => f.endsWith(".ts")).sort((a, b) => a.localeCompare(b));
4236
4377
  for (const file of files) {
@@ -4252,8 +4393,12 @@ Fix by setting generation.playwright.customPoms.importAliases["${exportName}"] t
4252
4393
  } else {
4253
4394
  localIdentifier = ensureUniqueIdentifier(requested);
4254
4395
  }
4255
- customPomClassIdentifierMap2[exportName] = localIdentifier;
4256
4396
  const customFileAbs = path.join(customDirAbs, file);
4397
+ customPomClassIdentifierMap2[exportName] = localIdentifier;
4398
+ const customPomMethodSignatures = extractCustomPomMethodSignatures(fs.readFileSync(customFileAbs, "utf8"), exportName);
4399
+ if (customPomMethodSignatures.size > 0) {
4400
+ customPomMethodSignaturesByClass2.set(exportName, customPomMethodSignatures);
4401
+ }
4257
4402
  const fromOutputDir = outputDir;
4258
4403
  const importPath = stripExtension(toPosixRelativePath(fromOutputDir, customFileAbs));
4259
4404
  if (localIdentifier !== exportName) {
@@ -4262,10 +4407,15 @@ Fix by setting generation.playwright.customPoms.importAliases["${exportName}"] t
4262
4407
  imports.push(`import { ${exportName} } from "${importPath}";`);
4263
4408
  }
4264
4409
  }
4265
- return customPomClassIdentifierMap2;
4410
+ return {
4411
+ classIdentifierMap: customPomClassIdentifierMap2,
4412
+ methodSignaturesByClass: customPomMethodSignaturesByClass2
4413
+ };
4266
4414
  };
4267
- const customPomClassIdentifierMap = addCustomPomImports();
4268
- const customPomAvailableClassIdentifiers = new Set(Object.values(customPomClassIdentifierMap ?? {}));
4415
+ const customPomImportResolution = addCustomPomImports();
4416
+ const customPomClassIdentifierMap = customPomImportResolution?.classIdentifierMap ?? {};
4417
+ const customPomMethodSignaturesByClass = customPomImportResolution?.methodSignaturesByClass ?? /* @__PURE__ */ new Map();
4418
+ const customPomAvailableClassIdentifiers = new Set(Object.values(customPomClassIdentifierMap));
4269
4419
  const referencedTargets = /* @__PURE__ */ new Set();
4270
4420
  for (const [, deps] of items) {
4271
4421
  for (const dt of deps.dataTestIdSet) {
@@ -4419,6 +4569,7 @@ Fix by setting generation.playwright.customPoms.importAliases["${exportName}"] t
4419
4569
  customPomAttachments: options.customPomAttachments ?? [],
4420
4570
  customPomClassIdentifierMap,
4421
4571
  customPomAvailableClassIdentifiers,
4572
+ customPomMethodSignaturesByClass,
4422
4573
  testIdAttribute: options.testIdAttribute,
4423
4574
  vueRouterFluentChaining: options.vueRouterFluentChaining,
4424
4575
  routeMetaByComponent: options.routeMetaByComponent