@immense/vue-pom-generator 1.0.38 → 1.0.39

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.mjs CHANGED
@@ -2,10 +2,10 @@ import path from "node:path";
2
2
  import process from "node:process";
3
3
  import { pathToFileURL, fileURLToPath } from "node:url";
4
4
  import fs from "node:fs";
5
+ import { parseExpression, parse } from "@babel/parser";
5
6
  import { JSDOM } from "jsdom";
6
7
  import { NodeTypes, stringifyExpression, ConstantTypes, createSimpleExpression } from "@vue/compiler-core";
7
8
  import { isArrayExpression, isStringLiteral, isTemplateLiteral, isAssignmentExpression, isIdentifier, isMemberExpression, isCallExpression, isExpressionStatement, isArrowFunctionExpression, isOptionalMemberExpression, isObjectExpression, isFile, isBlockStatement, isOptionalCallExpression, isLogicalExpression, isConditionalExpression, isSequenceExpression, isAssignmentPattern, isRestElement, isObjectPattern, isObjectProperty, isProgram, VISITOR_KEYS, isBooleanLiteral, isNumericLiteral, isNullLiteral } from "@babel/types";
8
- import { parseExpression, parse } from "@babel/parser";
9
9
  import { performance } from "node:perf_hooks";
10
10
  import * as compilerDom from "@vue/compiler-dom";
11
11
  import { parse as parse$2 } from "@vue/compiler-dom";
@@ -4017,6 +4017,7 @@ function generateViewObjectModelContent(componentName, dependencies, componentHi
4017
4017
  };
4018
4018
  const customPomClassIdentifierMap = options.customPomClassIdentifierMap ?? {};
4019
4019
  const customPomAvailableClassIdentifiers = options.customPomAvailableClassIdentifiers ?? /* @__PURE__ */ new Set();
4020
+ const customPomMethodSignaturesByClass = options.customPomMethodSignaturesByClass ?? /* @__PURE__ */ new Map();
4020
4021
  const attachmentsForThisClass = customPomAttachments.filter((a) => {
4021
4022
  if (!Object.prototype.hasOwnProperty.call(customPomClassIdentifierMap, a.className))
4022
4023
  return false;
@@ -4027,7 +4028,9 @@ function generateViewObjectModelContent(componentName, dependencies, componentHi
4027
4028
  return a.attachWhenUsesComponents.some((c) => hasChildComponent(c));
4028
4029
  }).map((a) => ({
4029
4030
  className: customPomClassIdentifierMap[a.className],
4030
- propertyName: a.propertyName
4031
+ propertyName: a.propertyName,
4032
+ flatten: a.flatten ?? false,
4033
+ methodSignatures: a.flatten ? customPomMethodSignaturesByClass.get(a.className) ?? /* @__PURE__ */ new Map() : /* @__PURE__ */ new Map()
4031
4034
  }));
4032
4035
  let content = "";
4033
4036
  const sourceRel = toPosixRelativePath(outputDir, filePath);
@@ -4068,6 +4071,15 @@ export class ${className} extends BasePage {
4068
4071
  `;
4069
4072
  const widgetInstances = isView ? getWidgetInstancesForView(componentName, dependencies.dataTestIdSet, customPomAvailableClassIdentifiers) : [];
4070
4073
  const componentRefsForInstances = isView ? usedComponentSet?.size ? usedComponentSet : childrenComponentSet : childrenComponentSet;
4074
+ const childInstancePropertyNames = Array.from(componentRefsForInstances).filter((child) => componentHierarchyMap.has(child) && componentHierarchyMap.get(child)?.dataTestIdSet.size).map((child) => child.split(".vue")[0]);
4075
+ const blockedViewPassthroughMethodNames = new Set(
4076
+ attachmentsForThisClass.filter((a) => a.flatten).flatMap((a) => Array.from(a.methodSignatures.keys()))
4077
+ );
4078
+ const reservedAttachmentPassthroughNames = /* @__PURE__ */ new Set([
4079
+ ...attachmentsForThisClass.map((a) => a.propertyName),
4080
+ ...widgetInstances.map((w) => w.propertyName),
4081
+ ...childInstancePropertyNames
4082
+ ]);
4071
4083
  if (isView && (componentRefsForInstances.size > 0 || attachmentsForThisClass.length > 0 || widgetInstances.length > 0)) {
4072
4084
  content += getComponentInstances(componentRefsForInstances, componentHierarchyMap, attachmentsForThisClass, widgetInstances);
4073
4085
  content += getConstructor(componentRefsForInstances, componentHierarchyMap, attachmentsForThisClass, widgetInstances, { testIdAttribute });
@@ -4076,8 +4088,9 @@ export class ${className} extends BasePage {
4076
4088
  content += getComponentInstances(/* @__PURE__ */ new Set(), componentHierarchyMap, attachmentsForThisClass);
4077
4089
  content += getConstructor(/* @__PURE__ */ new Set(), componentHierarchyMap, attachmentsForThisClass, [], { testIdAttribute });
4078
4090
  }
4091
+ content += getAttachmentPassthroughMethods(componentName, dependencies, attachmentsForThisClass, reservedAttachmentPassthroughNames);
4079
4092
  if (isView && componentRefsForInstances.size === 1) {
4080
- content += getViewPassthroughMethods(componentName, dependencies, componentRefsForInstances, componentHierarchyMap);
4093
+ content += getViewPassthroughMethods(componentName, dependencies, componentRefsForInstances, componentHierarchyMap, blockedViewPassthroughMethodNames);
4081
4094
  }
4082
4095
  if (isView && options.vueRouterFluentChaining) {
4083
4096
  const routeMeta = options.routeMetaByComponent?.[componentName] ?? null;
@@ -4088,7 +4101,7 @@ export class ${className} extends BasePage {
4088
4101
  content += "}\n";
4089
4102
  return content;
4090
4103
  }
4091
- function getViewPassthroughMethods(viewName, viewDependencies, childrenComponentSet, componentHierarchyMap) {
4104
+ function getViewPassthroughMethods(viewName, viewDependencies, childrenComponentSet, componentHierarchyMap, blockedMethodNames = /* @__PURE__ */ new Set()) {
4092
4105
  const existingOnView = viewDependencies.generatedMethods ?? /* @__PURE__ */ new Map();
4093
4106
  const methodToChildren = /* @__PURE__ */ new Map();
4094
4107
  for (const child of childrenComponentSet) {
@@ -4102,7 +4115,7 @@ function getViewPassthroughMethods(viewName, viewDependencies, childrenComponent
4102
4115
  for (const [name, sig] of methods.entries()) {
4103
4116
  if (!sig)
4104
4117
  continue;
4105
- if (existingOnView.has(name))
4118
+ if (existingOnView.has(name) || blockedMethodNames.has(name))
4106
4119
  continue;
4107
4120
  const list = methodToChildren.get(name) ?? [];
4108
4121
  list.push({ childProp, params: sig.params, argNames: sig.argNames });
@@ -4133,6 +4146,130 @@ function getViewPassthroughMethods(viewName, viewDependencies, childrenComponent
4133
4146
  ""
4134
4147
  ].join("\n");
4135
4148
  }
4149
+ function getAttachmentPassthroughMethods(ownerName, ownerDependencies, attachmentsForThisClass, reservedMemberNames) {
4150
+ if (!attachmentsForThisClass.some((a) => a.flatten && a.methodSignatures.size > 0)) {
4151
+ return "";
4152
+ }
4153
+ const existingOnClass = ownerDependencies.generatedMethods ?? /* @__PURE__ */ new Map();
4154
+ const methodToAttachments = /* @__PURE__ */ new Map();
4155
+ for (const attachment of attachmentsForThisClass) {
4156
+ if (!attachment.flatten) {
4157
+ continue;
4158
+ }
4159
+ for (const [methodName, signature] of attachment.methodSignatures.entries()) {
4160
+ if (methodName === "constructor" || existingOnClass.has(methodName) || reservedMemberNames.has(methodName)) {
4161
+ continue;
4162
+ }
4163
+ const list = methodToAttachments.get(methodName) ?? [];
4164
+ list.push({
4165
+ propertyName: attachment.propertyName,
4166
+ params: signature.params,
4167
+ argNames: signature.argNames
4168
+ });
4169
+ methodToAttachments.set(methodName, list);
4170
+ }
4171
+ }
4172
+ const sorted = Array.from(methodToAttachments.entries()).sort((a, b) => a[0].localeCompare(b[0]));
4173
+ const lines = [];
4174
+ for (const [methodName, candidates] of sorted) {
4175
+ if (candidates.length !== 1) {
4176
+ continue;
4177
+ }
4178
+ const { propertyName, params, argNames } = candidates[0];
4179
+ const callArgs = argNames.join(", ");
4180
+ const invocation = callArgs ? `this.${propertyName}.${methodName}(${callArgs})` : `this.${propertyName}.${methodName}()`;
4181
+ lines.push(
4182
+ "",
4183
+ ` ${methodName}(${params}) {`,
4184
+ ` return ${invocation};`,
4185
+ " }"
4186
+ );
4187
+ }
4188
+ if (!lines.length) {
4189
+ return "";
4190
+ }
4191
+ return [
4192
+ "",
4193
+ ` // Passthrough methods composed from custom helper attachments of ${ownerName}.`,
4194
+ ...lines,
4195
+ ""
4196
+ ].join("\n");
4197
+ }
4198
+ function sliceNodeSource(source, node) {
4199
+ if (node.start == null || node.end == null) {
4200
+ return null;
4201
+ }
4202
+ const snippet = source.slice(node.start, node.end).trim();
4203
+ return snippet.length ? snippet : null;
4204
+ }
4205
+ function getCustomPomCallArgumentName(param) {
4206
+ if (param.type === "Identifier") {
4207
+ return param.name;
4208
+ }
4209
+ if (param.type === "AssignmentPattern") {
4210
+ return param.left.type === "Identifier" ? param.left.name : null;
4211
+ }
4212
+ if (param.type === "RestElement") {
4213
+ return param.argument.type === "Identifier" ? `...${param.argument.name}` : null;
4214
+ }
4215
+ return null;
4216
+ }
4217
+ function extractCustomPomMethodSignatures(source, exportName) {
4218
+ const signatures = /* @__PURE__ */ new Map();
4219
+ let ast;
4220
+ try {
4221
+ ast = parse(source, {
4222
+ sourceType: "module",
4223
+ plugins: ["typescript", "jsx"]
4224
+ });
4225
+ } catch {
4226
+ return signatures;
4227
+ }
4228
+ for (const statement of ast.program.body) {
4229
+ if (statement.type !== "ExportNamedDeclaration" || !statement.declaration || statement.declaration.type !== "ClassDeclaration") {
4230
+ continue;
4231
+ }
4232
+ const declaration = statement.declaration;
4233
+ if (declaration.id?.name !== exportName) {
4234
+ continue;
4235
+ }
4236
+ for (const member of declaration.body.body) {
4237
+ if (member.type !== "ClassMethod" || member.kind !== "method" || member.static || member.computed) {
4238
+ continue;
4239
+ }
4240
+ if (member.accessibility === "private" || member.accessibility === "protected") {
4241
+ continue;
4242
+ }
4243
+ if (member.key.type !== "Identifier") {
4244
+ continue;
4245
+ }
4246
+ const params = [];
4247
+ const argNames = [];
4248
+ let supported = true;
4249
+ member.params.forEach((param) => {
4250
+ if (!supported) {
4251
+ return;
4252
+ }
4253
+ const paramSource = sliceNodeSource(source, param);
4254
+ const argName = getCustomPomCallArgumentName(param);
4255
+ if (!paramSource || !argName) {
4256
+ supported = false;
4257
+ return;
4258
+ }
4259
+ params.push(paramSource);
4260
+ argNames.push(argName);
4261
+ });
4262
+ if (!supported) {
4263
+ continue;
4264
+ }
4265
+ signatures.set(member.key.name, {
4266
+ params: params.join(", "),
4267
+ argNames
4268
+ });
4269
+ }
4270
+ }
4271
+ return signatures;
4272
+ }
4136
4273
  function ensureDir(dir) {
4137
4274
  const normalized = dir.replace(/\\/g, "/");
4138
4275
  if (!fs.existsSync(normalized)) {
@@ -4176,6 +4313,7 @@ async function generateAggregatedFiles(componentHierarchyMap, vueFilesPathMap, b
4176
4313
  ]);
4177
4314
  const usedImportIdentifiers = /* @__PURE__ */ new Set();
4178
4315
  const customPomClassIdentifierMap2 = {};
4316
+ const customPomMethodSignaturesByClass2 = /* @__PURE__ */ new Map();
4179
4317
  const ensureUniqueIdentifier = (base2) => {
4180
4318
  let candidate = base2;
4181
4319
  let i = 2;
@@ -4189,7 +4327,10 @@ async function generateAggregatedFiles(componentHierarchyMap, vueFilesPathMap, b
4189
4327
  const customDirRelOrAbs = options.customPomDir ?? "tests/playwright/pom/custom";
4190
4328
  const customDirAbs = path.isAbsolute(customDirRelOrAbs) ? customDirRelOrAbs : path.resolve(projectRoot, customDirRelOrAbs);
4191
4329
  if (!fs.existsSync(customDirAbs)) {
4192
- return;
4330
+ return {
4331
+ classIdentifierMap: customPomClassIdentifierMap2,
4332
+ methodSignaturesByClass: customPomMethodSignaturesByClass2
4333
+ };
4193
4334
  }
4194
4335
  const files = fs.readdirSync(customDirAbs).filter((f) => f.endsWith(".ts")).sort((a, b) => a.localeCompare(b));
4195
4336
  for (const file of files) {
@@ -4211,8 +4352,12 @@ Fix by setting generation.playwright.customPoms.importAliases["${exportName}"] t
4211
4352
  } else {
4212
4353
  localIdentifier = ensureUniqueIdentifier(requested);
4213
4354
  }
4214
- customPomClassIdentifierMap2[exportName] = localIdentifier;
4215
4355
  const customFileAbs = path.join(customDirAbs, file);
4356
+ customPomClassIdentifierMap2[exportName] = localIdentifier;
4357
+ const customPomMethodSignatures = extractCustomPomMethodSignatures(fs.readFileSync(customFileAbs, "utf8"), exportName);
4358
+ if (customPomMethodSignatures.size > 0) {
4359
+ customPomMethodSignaturesByClass2.set(exportName, customPomMethodSignatures);
4360
+ }
4216
4361
  const fromOutputDir = outputDir;
4217
4362
  const importPath = stripExtension(toPosixRelativePath(fromOutputDir, customFileAbs));
4218
4363
  if (localIdentifier !== exportName) {
@@ -4221,10 +4366,15 @@ Fix by setting generation.playwright.customPoms.importAliases["${exportName}"] t
4221
4366
  imports.push(`import { ${exportName} } from "${importPath}";`);
4222
4367
  }
4223
4368
  }
4224
- return customPomClassIdentifierMap2;
4369
+ return {
4370
+ classIdentifierMap: customPomClassIdentifierMap2,
4371
+ methodSignaturesByClass: customPomMethodSignaturesByClass2
4372
+ };
4225
4373
  };
4226
- const customPomClassIdentifierMap = addCustomPomImports();
4227
- const customPomAvailableClassIdentifiers = new Set(Object.values(customPomClassIdentifierMap ?? {}));
4374
+ const customPomImportResolution = addCustomPomImports();
4375
+ const customPomClassIdentifierMap = customPomImportResolution?.classIdentifierMap ?? {};
4376
+ const customPomMethodSignaturesByClass = customPomImportResolution?.methodSignaturesByClass ?? /* @__PURE__ */ new Map();
4377
+ const customPomAvailableClassIdentifiers = new Set(Object.values(customPomClassIdentifierMap));
4228
4378
  const referencedTargets = /* @__PURE__ */ new Set();
4229
4379
  for (const [, deps] of items) {
4230
4380
  for (const dt of deps.dataTestIdSet) {
@@ -4378,6 +4528,7 @@ Fix by setting generation.playwright.customPoms.importAliases["${exportName}"] t
4378
4528
  customPomAttachments: options.customPomAttachments ?? [],
4379
4529
  customPomClassIdentifierMap,
4380
4530
  customPomAvailableClassIdentifiers,
4531
+ customPomMethodSignaturesByClass,
4381
4532
  testIdAttribute: options.testIdAttribute,
4382
4533
  vueRouterFluentChaining: options.vueRouterFluentChaining,
4383
4534
  routeMetaByComponent: options.routeMetaByComponent