@immense/vue-pom-generator 1.0.37 → 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.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");
@@ -3519,7 +3519,8 @@ async function generateFiles(componentHierarchyMap, vueFilesPathMap, basePageCla
3519
3519
  const fixtureRegistryFile = maybeGenerateFixtureRegistry(componentHierarchyMap, {
3520
3520
  generateFixtures,
3521
3521
  pomOutDir: outDir,
3522
- projectRoot
3522
+ projectRoot,
3523
+ customPomDir
3523
3524
  });
3524
3525
  if (fixtureRegistryFile) {
3525
3526
  writeGeneratedFile(fixtureRegistryFile);
@@ -3918,6 +3919,9 @@ function maybeGenerateFixtureRegistry(componentHierarchyMap, options) {
3918
3919
  const fixtureFileName = looksLikeFilePath ? path.basename(fixtureOutRel) : "fixtures.g.ts";
3919
3920
  const root = options.projectRoot ?? process.cwd();
3920
3921
  const fixtureOutDirAbs = path.isAbsolute(fixtureOutDirRel) ? fixtureOutDirRel : path.resolve(root, fixtureOutDirRel);
3922
+ const customPomDirRel = options.customPomDir ?? "tests/playwright/pom/custom";
3923
+ const customPomDirAbs = path.isAbsolute(customPomDirRel) ? customPomDirRel : path.resolve(root, customPomDirRel);
3924
+ const overridePomDirAbs = path.resolve(path.dirname(customPomDirAbs), "overrides");
3921
3925
  const pomDirAbs = path.isAbsolute(pomOutDir) ? pomOutDir : path.resolve(root, pomOutDir);
3922
3926
  const pomImport = toPosixRelativePath(fixtureOutDirAbs, pomDirAbs);
3923
3927
  const viewClassNames = Array.from(componentHierarchyMap.entries()).filter(([, deps]) => !!deps.isView).map(([name]) => name).sort((a, b) => a.localeCompare(b));
@@ -3940,6 +3944,22 @@ function maybeGenerateFixtureRegistry(componentHierarchyMap, options) {
3940
3944
  return false;
3941
3945
  return true;
3942
3946
  }).sort((a, b) => a.localeCompare(b));
3947
+ const fixtureClassNames = [...viewClassNames, ...componentClassNames];
3948
+ const overrideCtorEntries = fixtureClassNames.map((name) => {
3949
+ const overrideFilePath = path.join(overridePomDirAbs, `${name}.ts`);
3950
+ if (!fs.existsSync(overrideFilePath))
3951
+ return null;
3952
+ return {
3953
+ className: name,
3954
+ localIdentifier: `${name}Override`,
3955
+ importSpecifier: stripExtension(toPosixRelativePath(fixtureOutDirAbs, overrideFilePath))
3956
+ };
3957
+ }).filter((entry) => !!entry);
3958
+ const overrideCtorByClassName = new Map(overrideCtorEntries.map((entry) => [entry.className, entry.localIdentifier]));
3959
+ const overrideImports = overrideCtorEntries.length ? `${overrideCtorEntries.map((entry) => `import { ${entry.className} as ${entry.localIdentifier} } from "${entry.importSpecifier}";`).join("\n")}
3960
+
3961
+ ` : "";
3962
+ const fixtureCtorExpression = (name) => overrideCtorByClassName.get(name) ?? `Pom.${name}`;
3943
3963
  const header = `${eslintSuppressionHeader}/**
3944
3964
  * DO NOT MODIFY BY HAND
3945
3965
  *
@@ -3948,8 +3968,8 @@ function maybeGenerateFixtureRegistry(componentHierarchyMap, options) {
3948
3968
  */
3949
3969
 
3950
3970
  `;
3951
- const fixturesTypeEntries = viewClassNames.map((name) => ` ${lowerFirst(name)}: Pom.${name},`).join("\n");
3952
- const componentFixturesTypeEntries = componentClassNames.map((name) => ` ${lowerFirst(name)}: Pom.${name},`).join("\n");
3971
+ const fixturesTypeEntries = viewClassNames.map((name) => ` ${lowerFirst(name)}: ${fixtureCtorExpression(name)},`).join("\n");
3972
+ const componentFixturesTypeEntries = componentClassNames.map((name) => ` ${lowerFirst(name)}: ${fixtureCtorExpression(name)},`).join("\n");
3953
3973
  const pomFactoryType = `export type PomConstructor<T> = new (page: PwPage) => T;
3954
3974
 
3955
3975
  export interface PomFactory {
@@ -3962,8 +3982,7 @@ export interface PomFactory {
3962
3982
  import { expect, test as base } from "@playwright/test";
3963
3983
  import type { Page as PwPage } from "@playwright/test";
3964
3984
  import * as Pom from "${pomImport}";
3965
-
3966
- export interface PlaywrightOptions {
3985
+ ${overrideImports}export interface PlaywrightOptions {
3967
3986
  animation: Pom.PlaywrightAnimationOptions;
3968
3987
  }
3969
3988
 
@@ -4039,6 +4058,7 @@ function generateViewObjectModelContent(componentName, dependencies, componentHi
4039
4058
  };
4040
4059
  const customPomClassIdentifierMap = options.customPomClassIdentifierMap ?? {};
4041
4060
  const customPomAvailableClassIdentifiers = options.customPomAvailableClassIdentifiers ?? /* @__PURE__ */ new Set();
4061
+ const customPomMethodSignaturesByClass = options.customPomMethodSignaturesByClass ?? /* @__PURE__ */ new Map();
4042
4062
  const attachmentsForThisClass = customPomAttachments.filter((a) => {
4043
4063
  if (!Object.prototype.hasOwnProperty.call(customPomClassIdentifierMap, a.className))
4044
4064
  return false;
@@ -4049,7 +4069,9 @@ function generateViewObjectModelContent(componentName, dependencies, componentHi
4049
4069
  return a.attachWhenUsesComponents.some((c) => hasChildComponent(c));
4050
4070
  }).map((a) => ({
4051
4071
  className: customPomClassIdentifierMap[a.className],
4052
- 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()
4053
4075
  }));
4054
4076
  let content = "";
4055
4077
  const sourceRel = toPosixRelativePath(outputDir, filePath);
@@ -4090,6 +4112,15 @@ export class ${className} extends BasePage {
4090
4112
  `;
4091
4113
  const widgetInstances = isView ? getWidgetInstancesForView(componentName, dependencies.dataTestIdSet, customPomAvailableClassIdentifiers) : [];
4092
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
+ ]);
4093
4124
  if (isView && (componentRefsForInstances.size > 0 || attachmentsForThisClass.length > 0 || widgetInstances.length > 0)) {
4094
4125
  content += getComponentInstances(componentRefsForInstances, componentHierarchyMap, attachmentsForThisClass, widgetInstances);
4095
4126
  content += getConstructor(componentRefsForInstances, componentHierarchyMap, attachmentsForThisClass, widgetInstances, { testIdAttribute });
@@ -4098,8 +4129,9 @@ export class ${className} extends BasePage {
4098
4129
  content += getComponentInstances(/* @__PURE__ */ new Set(), componentHierarchyMap, attachmentsForThisClass);
4099
4130
  content += getConstructor(/* @__PURE__ */ new Set(), componentHierarchyMap, attachmentsForThisClass, [], { testIdAttribute });
4100
4131
  }
4132
+ content += getAttachmentPassthroughMethods(componentName, dependencies, attachmentsForThisClass, reservedAttachmentPassthroughNames);
4101
4133
  if (isView && componentRefsForInstances.size === 1) {
4102
- content += getViewPassthroughMethods(componentName, dependencies, componentRefsForInstances, componentHierarchyMap);
4134
+ content += getViewPassthroughMethods(componentName, dependencies, componentRefsForInstances, componentHierarchyMap, blockedViewPassthroughMethodNames);
4103
4135
  }
4104
4136
  if (isView && options.vueRouterFluentChaining) {
4105
4137
  const routeMeta = options.routeMetaByComponent?.[componentName] ?? null;
@@ -4110,7 +4142,7 @@ export class ${className} extends BasePage {
4110
4142
  content += "}\n";
4111
4143
  return content;
4112
4144
  }
4113
- function getViewPassthroughMethods(viewName, viewDependencies, childrenComponentSet, componentHierarchyMap) {
4145
+ function getViewPassthroughMethods(viewName, viewDependencies, childrenComponentSet, componentHierarchyMap, blockedMethodNames = /* @__PURE__ */ new Set()) {
4114
4146
  const existingOnView = viewDependencies.generatedMethods ?? /* @__PURE__ */ new Map();
4115
4147
  const methodToChildren = /* @__PURE__ */ new Map();
4116
4148
  for (const child of childrenComponentSet) {
@@ -4124,7 +4156,7 @@ function getViewPassthroughMethods(viewName, viewDependencies, childrenComponent
4124
4156
  for (const [name, sig] of methods.entries()) {
4125
4157
  if (!sig)
4126
4158
  continue;
4127
- if (existingOnView.has(name))
4159
+ if (existingOnView.has(name) || blockedMethodNames.has(name))
4128
4160
  continue;
4129
4161
  const list = methodToChildren.get(name) ?? [];
4130
4162
  list.push({ childProp, params: sig.params, argNames: sig.argNames });
@@ -4155,6 +4187,130 @@ function getViewPassthroughMethods(viewName, viewDependencies, childrenComponent
4155
4187
  ""
4156
4188
  ].join("\n");
4157
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
+ }
4158
4314
  function ensureDir(dir) {
4159
4315
  const normalized = dir.replace(/\\/g, "/");
4160
4316
  if (!fs.existsSync(normalized)) {
@@ -4198,6 +4354,7 @@ async function generateAggregatedFiles(componentHierarchyMap, vueFilesPathMap, b
4198
4354
  ]);
4199
4355
  const usedImportIdentifiers = /* @__PURE__ */ new Set();
4200
4356
  const customPomClassIdentifierMap2 = {};
4357
+ const customPomMethodSignaturesByClass2 = /* @__PURE__ */ new Map();
4201
4358
  const ensureUniqueIdentifier = (base2) => {
4202
4359
  let candidate = base2;
4203
4360
  let i = 2;
@@ -4211,7 +4368,10 @@ async function generateAggregatedFiles(componentHierarchyMap, vueFilesPathMap, b
4211
4368
  const customDirRelOrAbs = options.customPomDir ?? "tests/playwright/pom/custom";
4212
4369
  const customDirAbs = path.isAbsolute(customDirRelOrAbs) ? customDirRelOrAbs : path.resolve(projectRoot, customDirRelOrAbs);
4213
4370
  if (!fs.existsSync(customDirAbs)) {
4214
- return;
4371
+ return {
4372
+ classIdentifierMap: customPomClassIdentifierMap2,
4373
+ methodSignaturesByClass: customPomMethodSignaturesByClass2
4374
+ };
4215
4375
  }
4216
4376
  const files = fs.readdirSync(customDirAbs).filter((f) => f.endsWith(".ts")).sort((a, b) => a.localeCompare(b));
4217
4377
  for (const file of files) {
@@ -4233,8 +4393,12 @@ Fix by setting generation.playwright.customPoms.importAliases["${exportName}"] t
4233
4393
  } else {
4234
4394
  localIdentifier = ensureUniqueIdentifier(requested);
4235
4395
  }
4236
- customPomClassIdentifierMap2[exportName] = localIdentifier;
4237
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
+ }
4238
4402
  const fromOutputDir = outputDir;
4239
4403
  const importPath = stripExtension(toPosixRelativePath(fromOutputDir, customFileAbs));
4240
4404
  if (localIdentifier !== exportName) {
@@ -4243,10 +4407,15 @@ Fix by setting generation.playwright.customPoms.importAliases["${exportName}"] t
4243
4407
  imports.push(`import { ${exportName} } from "${importPath}";`);
4244
4408
  }
4245
4409
  }
4246
- return customPomClassIdentifierMap2;
4410
+ return {
4411
+ classIdentifierMap: customPomClassIdentifierMap2,
4412
+ methodSignaturesByClass: customPomMethodSignaturesByClass2
4413
+ };
4247
4414
  };
4248
- const customPomClassIdentifierMap = addCustomPomImports();
4249
- 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));
4250
4419
  const referencedTargets = /* @__PURE__ */ new Set();
4251
4420
  for (const [, deps] of items) {
4252
4421
  for (const dt of deps.dataTestIdSet) {
@@ -4400,6 +4569,7 @@ Fix by setting generation.playwright.customPoms.importAliases["${exportName}"] t
4400
4569
  customPomAttachments: options.customPomAttachments ?? [],
4401
4570
  customPomClassIdentifierMap,
4402
4571
  customPomAvailableClassIdentifiers,
4572
+ customPomMethodSignaturesByClass,
4403
4573
  testIdAttribute: options.testIdAttribute,
4404
4574
  vueRouterFluentChaining: options.vueRouterFluentChaining,
4405
4575
  routeMetaByComponent: options.routeMetaByComponent