@immense/vue-pom-generator 1.0.65 → 1.0.67

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.
Files changed (53) hide show
  1. package/AGENTS.md +21 -0
  2. package/README.md +1 -0
  3. package/RELEASE_NOTES.md +84 -21
  4. package/class-generation/index.ts +23 -9
  5. package/dist/class-generation/index.d.ts.map +1 -1
  6. package/dist/compiler-metadata-utils.d.ts.map +1 -1
  7. package/dist/index.cjs +261 -82
  8. package/dist/index.cjs.map +1 -1
  9. package/dist/index.d.ts +1 -1
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.mjs +261 -82
  12. package/dist/index.mjs.map +1 -1
  13. package/dist/manifest-generator.d.ts +2 -0
  14. package/dist/manifest-generator.d.ts.map +1 -1
  15. package/dist/metadata-collector.d.ts +2 -0
  16. package/dist/metadata-collector.d.ts.map +1 -1
  17. package/dist/plugin/create-vue-pom-generator-plugins.d.ts.map +1 -1
  18. package/dist/plugin/internal/build-plugin.d.ts.map +1 -0
  19. package/dist/plugin/internal/dev-plugin.d.ts.map +1 -0
  20. package/dist/plugin/internal/virtual-modules.d.ts.map +1 -0
  21. package/dist/plugin/{support-plugins.d.ts → internal-plugins.d.ts} +14 -3
  22. package/dist/plugin/internal-plugins.d.ts.map +1 -0
  23. package/dist/plugin/runtime/annotator/client.d.ts +67 -0
  24. package/dist/plugin/runtime/annotator/client.d.ts.map +1 -0
  25. package/dist/plugin/runtime/annotator/format.d.ts +13 -0
  26. package/dist/plugin/runtime/annotator/format.d.ts.map +1 -0
  27. package/dist/plugin/runtime/annotator/plugin.d.ts +12 -0
  28. package/dist/plugin/runtime/annotator/plugin.d.ts.map +1 -0
  29. package/dist/plugin/runtime/annotator/styles.d.ts +3 -0
  30. package/dist/plugin/runtime/annotator/styles.d.ts.map +1 -0
  31. package/dist/plugin/runtime/annotator/vue-detector.d.ts +12 -0
  32. package/dist/plugin/runtime/annotator/vue-detector.d.ts.map +1 -0
  33. package/dist/plugin/types.d.ts +58 -3
  34. package/dist/plugin/types.d.ts.map +1 -1
  35. package/dist/plugin/vue-plugin.d.ts +4 -0
  36. package/dist/plugin/vue-plugin.d.ts.map +1 -1
  37. package/dist/transform.d.ts +4 -0
  38. package/dist/transform.d.ts.map +1 -1
  39. package/dist/utils.d.ts +19 -0
  40. package/dist/utils.d.ts.map +1 -1
  41. package/package.json +3 -1
  42. package/plugin/runtime/annotator/client.ts +1005 -0
  43. package/plugin/runtime/annotator/format.ts +76 -0
  44. package/plugin/runtime/annotator/plugin.ts +109 -0
  45. package/plugin/runtime/annotator/styles.ts +379 -0
  46. package/plugin/runtime/annotator/vue-detector.ts +216 -0
  47. package/dist/plugin/support/build-plugin.d.ts.map +0 -1
  48. package/dist/plugin/support/dev-plugin.d.ts.map +0 -1
  49. package/dist/plugin/support/virtual-modules.d.ts.map +0 -1
  50. package/dist/plugin/support-plugins.d.ts.map +0 -1
  51. /package/dist/plugin/{support → internal}/build-plugin.d.ts +0 -0
  52. /package/dist/plugin/{support → internal}/dev-plugin.d.ts +0 -0
  53. /package/dist/plugin/{support → internal}/virtual-modules.d.ts +0 -0
package/dist/index.d.ts CHANGED
@@ -5,5 +5,5 @@ export { createVuePomGeneratorPlugins as vuePomGenerator };
5
5
  export default createVuePomGeneratorPlugins;
6
6
  export declare function defineVuePomGeneratorConfig(options: VuePomGeneratorPluginOptions): VuePomGeneratorPluginOptions;
7
7
  export declare function defineNuxtPomGeneratorConfig(options: NuxtPomGeneratorPluginOptions): NuxtPomGeneratorPluginOptions;
8
- export type { ExistingIdBehavior, MissingSemanticNameBehavior, NuxtPomGeneratorPluginOptions, PomGeneratorPluginOptions, PomNameCollisionBehavior, VuePomGeneratorPluginOptions } from "./plugin/types";
8
+ export type { ExistingIdBehavior, MissingSemanticNameBehavior, NuxtPomGeneratorPluginOptions, PomGeneratorPluginOptions, PomNameCollisionBehavior, VuePomGeneratorPluginOptions, VuePomGeneratorRuntimeOptions, VuePomGeneratorRuntimeAnnotatorOptions, VuePomGeneratorRuntimeAnnotatorUiOptions, } from "./plugin/types";
9
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,4BAA4B,MAAM,2CAA2C,CAAC;AAErF,OAAO,KAAK,EAAE,6BAA6B,EAAE,4BAA4B,EAAE,MAAM,gBAAgB,CAAC;AAIlG,OAAO,EAAE,4BAA4B,EAAE,CAAC;AACxC,OAAO,EAAE,4BAA4B,IAAI,eAAe,EAAE,CAAC;AAC3D,eAAe,4BAA4B,CAAC;AAE5C,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,4BAA4B,GAAG,4BAA4B,CAE/G;AAED,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,6BAA6B,GAAG,6BAA6B,CAOlH;AAED,YAAY,EAAE,kBAAkB,EAAE,2BAA2B,EAAE,6BAA6B,EAAE,yBAAyB,EAAE,wBAAwB,EAAE,4BAA4B,EAAE,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,4BAA4B,MAAM,2CAA2C,CAAC;AAErF,OAAO,KAAK,EAAE,6BAA6B,EAAE,4BAA4B,EAAE,MAAM,gBAAgB,CAAC;AAIlG,OAAO,EAAE,4BAA4B,EAAE,CAAC;AACxC,OAAO,EAAE,4BAA4B,IAAI,eAAe,EAAE,CAAC;AAC3D,eAAe,4BAA4B,CAAC;AAE5C,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,4BAA4B,GAAG,4BAA4B,CAE/G;AAED,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,6BAA6B,GAAG,6BAA6B,CAOlH;AAED,YAAY,EACV,kBAAkB,EAClB,2BAA2B,EAC3B,6BAA6B,EAC7B,yBAAyB,EACzB,wBAAwB,EACxB,4BAA4B,EAC5B,6BAA6B,EAC7B,sCAAsC,EACtC,wCAAwC,GACzC,MAAM,gBAAgB,CAAC"}
package/dist/index.mjs CHANGED
@@ -645,7 +645,7 @@ function createInlineParameter(name, options = {}) {
645
645
  initializer: options.initializer
646
646
  };
647
647
  }
648
- function removeByKeySegment$1(value) {
648
+ function removeByKeySegment(value) {
649
649
  const idx = value.lastIndexOf("ByKey");
650
650
  if (idx < 0) {
651
651
  return value;
@@ -849,13 +849,13 @@ function generateGetElementByDataTestId(componentName, methodName, nativeRole, s
849
849
  const roleSuffix = upperFirst$1(nativeRole || "Element");
850
850
  const baseName = upperFirst$1(methodName);
851
851
  const numericSuffix = baseName.startsWith(roleSuffix) ? baseName.slice(roleSuffix.length) : "";
852
- const hasRoleSuffix2 = baseName.endsWith(roleSuffix) || baseName.startsWith(roleSuffix) && isAllDigits(numericSuffix);
853
- const propertyName = hasRoleSuffix2 ? `${baseName}` : `${baseName}${roleSuffix}`;
852
+ const hasRoleSuffix = baseName.endsWith(roleSuffix) || baseName.startsWith(roleSuffix) && isAllDigits(numericSuffix);
853
+ const propertyName = hasRoleSuffix ? `${baseName}` : `${baseName}${roleSuffix}`;
854
854
  const selectorParams = orderPomPatternParameters(parameters, [selector]);
855
855
  const indexedVariable = getIndexedPomPatternVariable(selector);
856
856
  if (indexedVariable) {
857
857
  const keyType = getPomParameter(selectorParams, indexedVariable)?.typeExpression || "string";
858
- const keyedPropertyName = getterNameOverride ?? removeByKeySegment$1(propertyName);
858
+ const keyedPropertyName = getterNameOverride ?? removeByKeySegment(propertyName);
859
859
  return [
860
860
  createClassGetter({
861
861
  name: keyedPropertyName,
@@ -2894,7 +2894,7 @@ Fix: either (1) include ${bestKeyPreservePlaceholder} in your :${attrLabel} temp
2894
2894
  }
2895
2895
  return value.slice(0, idx) + value.slice(idx + "ByKey".length);
2896
2896
  };
2897
- const hasRoleSuffix2 = (baseName, roleSuffix) => {
2897
+ const hasRoleSuffix = (baseName, roleSuffix) => {
2898
2898
  if (baseName.endsWith(roleSuffix)) {
2899
2899
  return true;
2900
2900
  }
@@ -2904,13 +2904,13 @@ Fix: either (1) include ${bestKeyPreservePlaceholder} in your :${attrLabel} temp
2904
2904
  const getPrimaryGetterName = (primaryMethodName) => {
2905
2905
  const roleSuffix = upperFirst(normalizedRole || "Element");
2906
2906
  const baseName = upperFirst(primaryMethodName);
2907
- const propertyName = hasRoleSuffix2(baseName, roleSuffix) ? baseName : `${baseName}${roleSuffix}`;
2907
+ const propertyName = hasRoleSuffix(baseName, roleSuffix) ? baseName : `${baseName}${roleSuffix}`;
2908
2908
  return selectorIsParameterized ? removeByKeySegment2(propertyName) : propertyName;
2909
2909
  };
2910
2910
  const getPrimaryGetterNameCandidates = (primaryMethodName) => {
2911
2911
  const roleSuffix = upperFirst(normalizedRole || "Element");
2912
2912
  const baseName = upperFirst(primaryMethodName);
2913
- const propertyName = hasRoleSuffix2(baseName, roleSuffix) ? baseName : `${baseName}${roleSuffix}`;
2913
+ const propertyName = hasRoleSuffix(baseName, roleSuffix) ? baseName : `${baseName}${roleSuffix}`;
2914
2914
  if (!selectorIsParameterized) {
2915
2915
  return { primary: propertyName };
2916
2916
  }
@@ -3037,7 +3037,7 @@ Fix: either (1) include ${bestKeyPreservePlaceholder} in your :${attrLabel} temp
3037
3037
  if (conflicts && nameCollisionBehavior === "error") {
3038
3038
  const roleSuffix = upperFirst(normalizedRole || "Element");
3039
3039
  const baseNameUpper = upperFirst(baseWithSuffix);
3040
- if (!hasRoleSuffix2(baseNameUpper, roleSuffix)) {
3040
+ if (!hasRoleSuffix(baseNameUpper, roleSuffix)) {
3041
3041
  const baseWithRoleSuffix = `${baseWithSuffix}${roleSuffix}`;
3042
3042
  const candidateWithRoleSuffix = selectorIsParameterized ? `${baseWithRoleSuffix}ByKey` : baseWithRoleSuffix;
3043
3043
  const actionNameWithRoleSuffix = getPrimaryActionMethodName(candidateWithRoleSuffix);
@@ -3147,12 +3147,32 @@ Fix: make the element identifiable (e.g. add id/name/inner text or use a more sp
3147
3147
  alternateSelectors: void 0,
3148
3148
  mergeKey: args.pomMergeKey,
3149
3149
  parameters: normalizedParameters,
3150
- keyValuesOverride: args.keyValuesOverride ?? null
3150
+ keyValuesOverride: args.keyValuesOverride ?? null,
3151
+ generatedActionName: getPrimaryActionMethodName(methodName),
3152
+ generatedPropertyName: getterNameOverride ?? getPrimaryGetterName(methodName)
3151
3153
  // emitPrimary defaults to true; special cases (including merge) may set it to false below.
3152
3154
  };
3153
3155
  if (mergedIntoExisting && dataTestIdEntry.pom) {
3154
3156
  dataTestIdEntry.pom.emitPrimary = false;
3155
3157
  }
3158
+ if (addHtmlAttribute && args.annotatorMetadata && dataTestIdEntry.pom) {
3159
+ const filename = args.contextFilename?.trim();
3160
+ if (!filename) {
3161
+ throw new Error(`[vue-pom-generator] runtime.annotator.enabled requires contextFilename for ${args.componentName}.`);
3162
+ }
3163
+ const sourceLocation = args.element.loc?.start;
3164
+ if (!sourceLocation) {
3165
+ throw new Error(`[vue-pom-generator] runtime.annotator.enabled requires element source locations for ${args.componentName}.`);
3166
+ }
3167
+ const metadataAttributePrefix = args.annotatorMetadata.metadataAttributePrefix;
3168
+ upsertAttribute(args.element, args.annotatorMetadata.sourceAttribute, staticAttributeValue(`${filename}:${sourceLocation.line}:${sourceLocation.column}`));
3169
+ upsertAttribute(args.element, `${metadataAttributePrefix}-component`, staticAttributeValue(args.componentName));
3170
+ upsertAttribute(args.element, `${metadataAttributePrefix}-tag`, staticAttributeValue(args.element.tag));
3171
+ upsertAttribute(args.element, `${metadataAttributePrefix}-testid`, runtimeDataTestId);
3172
+ upsertAttribute(args.element, `${metadataAttributePrefix}-action`, staticAttributeValue(buildPomGeneratedActionName(dataTestIdEntry.pom)));
3173
+ upsertAttribute(args.element, `${metadataAttributePrefix}-property`, staticAttributeValue(buildPomGeneratedPropertyName(dataTestIdEntry.pom)));
3174
+ upsertAttribute(args.element, `${metadataAttributePrefix}-role`, staticAttributeValue(normalizedRole));
3175
+ }
3156
3176
  args.dependencies.childrenComponentSet.add(childComponentName);
3157
3177
  args.dependencies.usedComponentSet.add(childComponentName);
3158
3178
  args.dependencies.dataTestIdSet.add(dataTestIdEntry);
@@ -3351,7 +3371,7 @@ Fix: make the element identifiable (e.g. add id/name/inner text or use a more sp
3351
3371
  registerGeneratedMethodSignature(generatedName2, createPomMethodSignature([createPomParameterSpec("annotationText", `string = ""`)]));
3352
3372
  }
3353
3373
  }
3354
- return { selectorValue: dataTestId, runtimeValue: runtimeDataTestId, fromExisting };
3374
+ return { selectorValue: dataTestId, runtimeValue: runtimeDataTestId, fromExisting, entry: dataTestIdEntry };
3355
3375
  }
3356
3376
  const generatedName = ensureUniqueGeneratedName(`select${upperFirst(methodName || "Radio")}`);
3357
3377
  if (dataTestIdEntry.pom) {
@@ -3378,7 +3398,7 @@ Fix: make the element identifiable (e.g. add id/name/inner text or use a more sp
3378
3398
  createPomParameterSpec("annotationText", `string = ""`)
3379
3399
  ]));
3380
3400
  }
3381
- return { selectorValue: dataTestId, runtimeValue: runtimeDataTestId, fromExisting };
3401
+ return { selectorValue: dataTestId, runtimeValue: runtimeDataTestId, fromExisting, entry: dataTestIdEntry };
3382
3402
  }
3383
3403
  const staticKeyValues = args.keyValuesOverride ?? null;
3384
3404
  const needsKey = hasPomParameter(normalizedParameters, "key") && selectorIsParameterized;
@@ -3412,7 +3432,7 @@ Fix: make the element identifiable (e.g. add id/name/inner text or use a more sp
3412
3432
  ]));
3413
3433
  }
3414
3434
  }
3415
- return { selectorValue: dataTestId, runtimeValue: runtimeDataTestId, fromExisting };
3435
+ return { selectorValue: dataTestId, runtimeValue: runtimeDataTestId, fromExisting, entry: dataTestIdEntry };
3416
3436
  }
3417
3437
  if (dataTestIdEntry.pom) {
3418
3438
  if (dataTestIdEntry.pom.emitPrimary !== false) {
@@ -3426,7 +3446,66 @@ Fix: make the element identifiable (e.g. add id/name/inner text or use a more sp
3426
3446
  const generatedName = getGeneratedMethodName();
3427
3447
  registerGeneratedMethodSignature(generatedName, signature);
3428
3448
  }
3429
- return { selectorValue: dataTestId, runtimeValue: runtimeDataTestId, fromExisting };
3449
+ return { selectorValue: dataTestId, runtimeValue: runtimeDataTestId, fromExisting, entry: dataTestIdEntry };
3450
+ }
3451
+ function requireStructuredPomName(value, fieldName, pom) {
3452
+ const normalized = value?.trim();
3453
+ if (normalized) {
3454
+ return normalized;
3455
+ }
3456
+ throw new Error(`[vue-pom-generator] Missing ${fieldName} for POM spec ${pom.methodName}.`);
3457
+ }
3458
+ function buildPomGeneratedPropertyName(pom) {
3459
+ return requireStructuredPomName(pom.generatedPropertyName, "generatedPropertyName", pom);
3460
+ }
3461
+ function buildPomGeneratedActionName(pom) {
3462
+ return requireStructuredPomName(pom.generatedActionName, "generatedActionName", pom);
3463
+ }
3464
+ const STANDALONE_WRAPPER_FALLBACK_ROLES = /* @__PURE__ */ new Set([
3465
+ "button",
3466
+ "input",
3467
+ "select",
3468
+ "vselect",
3469
+ "checkbox",
3470
+ "toggle",
3471
+ "radio"
3472
+ ]);
3473
+ function stripTrailingAsciiDigits(value) {
3474
+ let end = value.length;
3475
+ while (end > 0 && isAsciiDigitCode(value.charCodeAt(end - 1))) {
3476
+ end -= 1;
3477
+ }
3478
+ return value.slice(0, end);
3479
+ }
3480
+ function matchesStandaloneWrapperFallbackMethodName(methodName, componentClassName) {
3481
+ let normalizedMethodName = methodName;
3482
+ if (normalizedMethodName.endsWith("ByKey")) {
3483
+ normalizedMethodName = normalizedMethodName.slice(0, -"ByKey".length);
3484
+ }
3485
+ normalizedMethodName = stripTrailingAsciiDigits(normalizedMethodName);
3486
+ return normalizedMethodName === componentClassName;
3487
+ }
3488
+ function shouldSuppressStandaloneWrapperFallbackSurface(componentName, dependencies) {
3489
+ if (dependencies.isView || (dependencies.pomExtraMethods?.length ?? 0) > 0) {
3490
+ return false;
3491
+ }
3492
+ const entries = Array.from(dependencies.dataTestIdSet ?? []);
3493
+ if (!entries.length) {
3494
+ return false;
3495
+ }
3496
+ const primaryEntries = entries.filter((entry) => {
3497
+ return !!entry.pom && entry.pom.emitPrimary !== false;
3498
+ });
3499
+ if (!primaryEntries.length || primaryEntries.length !== entries.length) {
3500
+ return false;
3501
+ }
3502
+ const componentClassName = toPascalCase(componentName.endsWith(".vue") ? componentName.slice(0, -4) : componentName);
3503
+ if (!componentClassName) {
3504
+ return false;
3505
+ }
3506
+ return primaryEntries.every(({ pom }) => {
3507
+ return STANDALONE_WRAPPER_FALLBACK_ROLES.has(pom.nativeRole) && matchesStandaloneWrapperFallbackMethodName(pom.methodName, componentClassName);
3508
+ });
3430
3509
  }
3431
3510
  function safeRealpath(value) {
3432
3511
  try {
@@ -4726,6 +4805,11 @@ async function generateFiles(componentHierarchyMap, vueFilesPathMap, basePageCla
4726
4805
  componentDirs,
4727
4806
  layoutDirs
4728
4807
  }) : void 0);
4808
+ const emittableComponentHierarchyMap = new Map(
4809
+ Array.from(componentHierarchyMap.entries()).filter(([componentName, dependencies]) => {
4810
+ return !shouldSuppressStandaloneWrapperFallbackSurface(componentName, dependencies);
4811
+ })
4812
+ );
4729
4813
  const generatedFilePaths = [];
4730
4814
  const writeGeneratedFile = (file) => {
4731
4815
  const resolvedFilePath = path.resolve(file.filePath);
@@ -4733,7 +4817,7 @@ async function generateFiles(componentHierarchyMap, vueFilesPathMap, basePageCla
4733
4817
  generatedFilePaths.push(resolvedFilePath);
4734
4818
  };
4735
4819
  if (emitLanguages.includes("ts")) {
4736
- const files = typescriptOutputStructure === "split" ? await generateSplitTypeScriptFiles(componentHierarchyMap, vueFilesPathMap, basePageClassPath, outDir, {
4820
+ const files = typescriptOutputStructure === "split" ? await generateSplitTypeScriptFiles(emittableComponentHierarchyMap, vueFilesPathMap, basePageClassPath, outDir, {
4737
4821
  customPomAttachments,
4738
4822
  projectRoot,
4739
4823
  customPomDir,
@@ -4743,7 +4827,7 @@ async function generateFiles(componentHierarchyMap, vueFilesPathMap, basePageCla
4743
4827
  testIdAttribute,
4744
4828
  routeMetaByComponent,
4745
4829
  vueRouterFluentChaining
4746
- }) : await generateAggregatedFiles(componentHierarchyMap, vueFilesPathMap, basePageClassPath, outDir, {
4830
+ }) : await generateAggregatedFiles(emittableComponentHierarchyMap, vueFilesPathMap, basePageClassPath, outDir, {
4747
4831
  customPomAttachments,
4748
4832
  projectRoot,
4749
4833
  customPomDir,
@@ -4757,7 +4841,7 @@ async function generateFiles(componentHierarchyMap, vueFilesPathMap, basePageCla
4757
4841
  for (const file of files) {
4758
4842
  writeGeneratedFile(file);
4759
4843
  }
4760
- const fixtureRegistryFile = maybeGenerateFixtureRegistry(componentHierarchyMap, {
4844
+ const fixtureRegistryFile = maybeGenerateFixtureRegistry(emittableComponentHierarchyMap, {
4761
4845
  generateFixtures,
4762
4846
  pomOutDir: outDir,
4763
4847
  projectRoot,
@@ -4768,7 +4852,7 @@ async function generateFiles(componentHierarchyMap, vueFilesPathMap, basePageCla
4768
4852
  }
4769
4853
  }
4770
4854
  if (emitLanguages.includes("csharp")) {
4771
- const csFiles = generateAggregatedCSharpFiles(componentHierarchyMap, outDir, {
4855
+ const csFiles = generateAggregatedCSharpFiles(emittableComponentHierarchyMap, outDir, {
4772
4856
  testIdAttribute,
4773
4857
  csharp
4774
4858
  });
@@ -5505,7 +5589,12 @@ function prepareViewObjectModelClass(componentName, dependencies, componentHiera
5505
5589
  const rawComponentRefsForInstances = isView ? usedComponentSet?.size ? usedComponentSet : childrenComponentSet : childrenComponentSet;
5506
5590
  const componentRefsForInstances = /* @__PURE__ */ new Set();
5507
5591
  for (const ref of rawComponentRefsForInstances) {
5508
- componentRefsForInstances.add(resolveTrackedComponentRef(ref) ?? normalizeTrackedComponentRef(ref));
5592
+ const resolvedRef = resolveTrackedComponentRef(ref) ?? normalizeTrackedComponentRef(ref);
5593
+ const resolvedDependencies = componentHierarchyMap.get(resolvedRef);
5594
+ if (!resolvedDependencies?.dataTestIdSet.size) {
5595
+ continue;
5596
+ }
5597
+ componentRefsForInstances.add(resolvedRef);
5509
5598
  }
5510
5599
  const hasChildComponent = (needle) => {
5511
5600
  const normalizedNeedle = normalizeTrackedComponentRef(needle);
@@ -5560,9 +5649,9 @@ function prepareViewObjectModelClass(componentName, dependencies, componentHiera
5560
5649
  members.push(...getComponentInstances(componentRefsForInstances, componentHierarchyMap, attachmentsForThisClass, widgetInstances));
5561
5650
  members.push(getConstructor(componentRefsForInstances, componentHierarchyMap, attachmentsForThisClass, widgetInstances, { testIdAttribute }));
5562
5651
  }
5563
- if (!isView && attachmentsForThisClass.length > 0) {
5564
- members.push(...getComponentInstances(/* @__PURE__ */ new Set(), componentHierarchyMap, attachmentsForThisClass));
5565
- members.push(getConstructor(/* @__PURE__ */ new Set(), componentHierarchyMap, attachmentsForThisClass, [], { testIdAttribute }));
5652
+ if (!isView && (componentRefsForInstances.size > 0 || attachmentsForThisClass.length > 0)) {
5653
+ members.push(...getComponentInstances(componentRefsForInstances, componentHierarchyMap, attachmentsForThisClass));
5654
+ members.push(getConstructor(componentRefsForInstances, componentHierarchyMap, attachmentsForThisClass, [], { testIdAttribute }));
5566
5655
  }
5567
5656
  members.push(
5568
5657
  ...getAttachmentPassthroughMethods(componentName, dependencies, attachmentsForThisClass, reservedAttachmentPassthroughNames)
@@ -5606,7 +5695,7 @@ function generateViewObjectModelContent(componentName, dependencies, componentHi
5606
5695
  const basePageImport = path.relative(fromAbs, toAbs).replace(/\\/g, "/");
5607
5696
  const basePageImportNoExt = stripExtension(basePageImport).replace(/\\/g, "/");
5608
5697
  const basePageImportSpecifier = basePageImportNoExt.startsWith(".") ? basePageImportNoExt : `./${basePageImportNoExt}`;
5609
- const needsPlaywrightPageImport = prepared.isView || prepared.attachmentsForThisClass.length > 0;
5698
+ const needsPlaywrightPageImport = prepared.isView || prepared.attachmentsForThisClass.length > 0 || prepared.componentRefsForInstances.size > 0 || prepared.widgetInstances.length > 0;
5610
5699
  const customPomImportSpecifiersByClass = options.customPomImportSpecifiersByClass ?? {};
5611
5700
  const customImports = Array.from(
5612
5701
  /* @__PURE__ */ new Set([
@@ -7064,6 +7153,7 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
7064
7153
  const warn = options.warn;
7065
7154
  const vueFilesPathMap = options.vueFilesPathMap;
7066
7155
  const wrapperSearchRoots = options.wrapperSearchRoots ?? [];
7156
+ const annotatorMetadata = options.annotatorMetadata ?? null;
7067
7157
  const safeRealpath2 = (p) => {
7068
7158
  try {
7069
7159
  return fs.existsSync(p) ? fs.realpathSync(p) : p;
@@ -7327,7 +7417,8 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
7327
7417
  testIdAttribute,
7328
7418
  existingIdBehavior,
7329
7419
  nameCollisionBehavior,
7330
- warn
7420
+ warn,
7421
+ annotatorMetadata
7331
7422
  });
7332
7423
  };
7333
7424
  const { nativeWrappersValue, optionDataTestIdPrefixValue, semanticNameHint } = getNativeWrapperTransformInfo(element, componentName, nativeWrappers);
@@ -8318,48 +8409,6 @@ function collectAccessibilityReviewWarnings(manifest) {
8318
8409
  }
8319
8410
  return warnings;
8320
8411
  }
8321
- function removeByKeySegment(value) {
8322
- const idx = value.indexOf("ByKey");
8323
- if (idx < 0) {
8324
- return value;
8325
- }
8326
- return value.slice(0, idx) + value.slice(idx + "ByKey".length);
8327
- }
8328
- function hasRoleSuffix(baseName, roleSuffix) {
8329
- if (baseName.endsWith(roleSuffix)) {
8330
- return true;
8331
- }
8332
- const re = new RegExp(`^${roleSuffix}\\d+$`);
8333
- return re.test(baseName);
8334
- }
8335
- function getGeneratedPropertyName(pom) {
8336
- if (pom.getterNameOverride) {
8337
- return pom.getterNameOverride;
8338
- }
8339
- const roleSuffix = upperFirst(pom.nativeRole || "Element");
8340
- const baseName = upperFirst(pom.methodName);
8341
- const propertyName = hasRoleSuffix(baseName, roleSuffix) ? baseName : `${baseName}${roleSuffix}`;
8342
- return pom.selector.patternKind === "parameterized" ? removeByKeySegment(propertyName) : propertyName;
8343
- }
8344
- function getGeneratedActionName(entry, pom) {
8345
- const methodNameUpper = upperFirst(pom.methodName);
8346
- const radioMethodNameUpper = upperFirst(pom.methodName || "Radio");
8347
- const isNavigation = !!entry.targetPageObjectModelClass;
8348
- if (isNavigation) {
8349
- return `goTo${methodNameUpper}`;
8350
- }
8351
- switch (pom.nativeRole) {
8352
- case "input":
8353
- return `type${methodNameUpper}`;
8354
- case "select":
8355
- case "vselect":
8356
- return `select${methodNameUpper}`;
8357
- case "radio":
8358
- return `select${radioMethodNameUpper}`;
8359
- default:
8360
- return `click${methodNameUpper}`;
8361
- }
8362
- }
8363
8412
  function matchesPrimarySelector(extraMethod, pom) {
8364
8413
  if (extraMethod.selector.kind !== "testId") {
8365
8414
  return false;
@@ -8370,7 +8419,7 @@ function getManifestEntry(componentName, entry, componentMetadata, extraMethods)
8370
8419
  const testId = entry.selectorValue.formatted;
8371
8420
  const metadata = componentMetadata?.get(testId);
8372
8421
  const pom = entry.pom;
8373
- const generatedActionName = pom ? getGeneratedActionName(entry, pom) : null;
8422
+ const generatedActionName = pom ? buildPomGeneratedActionName(pom) : null;
8374
8423
  const extraActionNames = pom ? extraMethods.filter((extraMethod) => matchesPrimarySelector(extraMethod, pom)).map((extraMethod) => extraMethod.name).sort((a, b) => a.localeCompare(b)) : [];
8375
8424
  const generatedActionNames = Array.from(/* @__PURE__ */ new Set([
8376
8425
  ...generatedActionName ? [generatedActionName] : [],
@@ -8388,13 +8437,15 @@ function getManifestEntry(componentName, entry, componentMetadata, extraMethods)
8388
8437
  }) : componentName,
8389
8438
  inferredRole: pom?.nativeRole ?? null,
8390
8439
  ...accessibility ? { accessibility } : {},
8391
- generatedPropertyName: pom ? getGeneratedPropertyName(pom) : null,
8440
+ generatedPropertyName: pom ? buildPomGeneratedPropertyName(pom) : null,
8392
8441
  generatedActionName,
8393
8442
  generatedActionNames,
8394
8443
  emitPrimary: pom?.emitPrimary !== false,
8395
8444
  ...entry.targetPageObjectModelClass ? { targetPageObjectModelClass: entry.targetPageObjectModelClass } : {},
8396
8445
  ...metadata?.tag ? { sourceTag: metadata.tag } : {},
8397
8446
  ...metadata ? { sourceTagType: metadata.tagType } : {},
8447
+ ...metadata?.sourceLine !== void 0 ? { sourceLine: metadata.sourceLine } : {},
8448
+ ...metadata?.sourceColumn !== void 0 ? { sourceColumn: metadata.sourceColumn } : {},
8398
8449
  ...metadata?.patchFlag !== void 0 ? { patchFlag: metadata.patchFlag } : {},
8399
8450
  ...metadata?.dynamicProps?.length ? { dynamicProps: metadata.dynamicProps } : {},
8400
8451
  ...metadata?.hasClickHandler !== void 0 ? { hasClickHandler: metadata.hasClickHandler } : {},
@@ -8404,7 +8455,7 @@ function getManifestEntry(componentName, entry, componentMetadata, extraMethods)
8404
8455
  };
8405
8456
  }
8406
8457
  function buildPomManifest(componentHierarchyMap, elementMetadata) {
8407
- const manifestEntries = Array.from(componentHierarchyMap.entries()).sort((a, b) => a[0].localeCompare(b[0])).map(([componentName, dependencies]) => {
8458
+ const manifestEntries = Array.from(componentHierarchyMap.entries()).filter(([componentName, dependencies]) => !shouldSuppressStandaloneWrapperFallbackSurface(componentName, dependencies)).sort((a, b) => a[0].localeCompare(b[0])).map(([componentName, dependencies]) => {
8408
8459
  const entries = Array.from(dependencies.dataTestIdSet).sort((a, b) => a.selectorValue.formatted.localeCompare(b.selectorValue.formatted)).map((entry) => getManifestEntry(componentName, entry, elementMetadata.get(componentName), dependencies.pomExtraMethods ?? []));
8409
8460
  if (!entries.length) {
8410
8461
  return null;
@@ -8420,9 +8471,15 @@ function buildPomManifest(componentHierarchyMap, elementMetadata) {
8420
8471
  }).filter((entry) => entry !== null);
8421
8472
  return Object.fromEntries(manifestEntries);
8422
8473
  }
8423
- function buildTestIdManifest(pomManifest) {
8474
+ function buildTestIdManifest(componentHierarchyMap) {
8424
8475
  return Object.fromEntries(
8425
- Object.entries(pomManifest).map(([componentName, component]) => [componentName, Array.from(new Set(component.testIds)).sort((a, b) => a.localeCompare(b))])
8476
+ Array.from(componentHierarchyMap.entries()).sort((a, b) => a[0].localeCompare(b[0])).map(([componentName, dependencies]) => {
8477
+ const testIds = Array.from(dependencies.dataTestIdSet).map((entry) => entry.selectorValue.formatted).filter(Boolean);
8478
+ if (!testIds.length) {
8479
+ return null;
8480
+ }
8481
+ return [componentName, Array.from(new Set(testIds)).sort((a, b) => a.localeCompare(b))];
8482
+ }).filter((entry) => entry !== null)
8426
8483
  );
8427
8484
  }
8428
8485
  function writeConstJson(value) {
@@ -8432,7 +8489,7 @@ function writeConstJson(value) {
8432
8489
  }
8433
8490
  function generateTestIdsModule(componentHierarchyMap, elementMetadata) {
8434
8491
  const pomManifest = buildPomManifest(componentHierarchyMap, elementMetadata);
8435
- const testIdManifest = buildTestIdManifest(pomManifest);
8492
+ const testIdManifest = buildTestIdManifest(componentHierarchyMap);
8436
8493
  return renderSourceFile("virtual-testids.ts", (sourceFile) => {
8437
8494
  sourceFile.addStatements("// Virtual module: test id manifest");
8438
8495
  sourceFile.addVariableStatement({
@@ -8518,7 +8575,89 @@ function createTestIdsVirtualModulesPlugin(componentHierarchyMap, elementMetadat
8518
8575
  }
8519
8576
  };
8520
8577
  }
8521
- function createSupportPlugins(options) {
8578
+ const ANNOTATOR_CLIENT_VIRTUAL_ID = "virtual:vue-pom-generator/annotator-client";
8579
+ const ANNOTATOR_CLIENT_RESOLVED_ID = `\0${ANNOTATOR_CLIENT_VIRTUAL_ID}`;
8580
+ const DEFAULT_ENTRY_SUFFIXES = [
8581
+ "/src/main.ts",
8582
+ "/src/main.js",
8583
+ "/src/main.mts",
8584
+ "/src/main.tsx",
8585
+ "/src/main.jsx"
8586
+ ];
8587
+ function toFsImportPath(filePath) {
8588
+ return `/@fs/${filePath.replace(/\\/g, "/")}`;
8589
+ }
8590
+ function resolveAnnotatorClientPath() {
8591
+ const candidates = [
8592
+ fileURLToPath(new URL("./client.ts", import.meta.url)),
8593
+ fileURLToPath(new URL("../plugin/runtime/annotator/client.ts", import.meta.url))
8594
+ ];
8595
+ const resolved = candidates.find((candidate) => fs.existsSync(candidate));
8596
+ if (!resolved) {
8597
+ throw new Error("[vue-pom-generator] Unable to locate annotator client source.");
8598
+ }
8599
+ return path.resolve(resolved);
8600
+ }
8601
+ function createAnnotatorUiPlugin(options) {
8602
+ const clientPath = resolveAnnotatorClientPath();
8603
+ const clientImportPath = toFsImportPath(clientPath);
8604
+ const clientOptions = JSON.stringify({
8605
+ sourceAttribute: options.sourceAttribute,
8606
+ metadataAttributePrefix: options.metadataAttributePrefix,
8607
+ outputDetail: options.outputDetail,
8608
+ copyToClipboard: options.copyToClipboard,
8609
+ showComponentTree: options.showComponentTree
8610
+ });
8611
+ return {
8612
+ name: "vue-pom-generator:annotator-ui",
8613
+ apply: "serve",
8614
+ resolveId(id) {
8615
+ if (id === ANNOTATOR_CLIENT_VIRTUAL_ID) {
8616
+ return ANNOTATOR_CLIENT_RESOLVED_ID;
8617
+ }
8618
+ },
8619
+ load(id) {
8620
+ if (id !== ANNOTATOR_CLIENT_RESOLVED_ID) {
8621
+ return null;
8622
+ }
8623
+ return [
8624
+ `import { mountAnnotatorClient } from ${JSON.stringify(clientImportPath)};`,
8625
+ `mountAnnotatorClient(${clientOptions});`,
8626
+ ""
8627
+ ].join("\n");
8628
+ },
8629
+ transform(code, id) {
8630
+ if (!options.enabled) {
8631
+ return null;
8632
+ }
8633
+ const normalizedId = id.replace(/\\/g, "/");
8634
+ if (!DEFAULT_ENTRY_SUFFIXES.some((suffix) => normalizedId.endsWith(suffix))) {
8635
+ return null;
8636
+ }
8637
+ if (code.includes(ANNOTATOR_CLIENT_VIRTUAL_ID)) {
8638
+ return null;
8639
+ }
8640
+ return {
8641
+ code: `import ${JSON.stringify(ANNOTATOR_CLIENT_VIRTUAL_ID)};
8642
+ ${code}`,
8643
+ map: null
8644
+ };
8645
+ },
8646
+ transformIndexHtml() {
8647
+ if (!options.enabled) {
8648
+ return void 0;
8649
+ }
8650
+ const tag = {
8651
+ tag: "script",
8652
+ attrs: { type: "module" },
8653
+ children: `import ${JSON.stringify(ANNOTATOR_CLIENT_VIRTUAL_ID)};`,
8654
+ injectTo: "body"
8655
+ };
8656
+ return [tag];
8657
+ }
8658
+ };
8659
+ }
8660
+ function createInternalPlugins(options) {
8522
8661
  const {
8523
8662
  componentHierarchyMap,
8524
8663
  elementMetadata,
@@ -8534,7 +8673,8 @@ function createSupportPlugins(options) {
8534
8673
  generation,
8535
8674
  projectRootRef,
8536
8675
  basePageClassPath: basePageClassPathOverride,
8537
- loggerRef
8676
+ loggerRef,
8677
+ annotatorRuntime
8538
8678
  } = options;
8539
8679
  const {
8540
8680
  outDir,
@@ -8608,7 +8748,13 @@ function createSupportPlugins(options) {
8608
8748
  loggerRef
8609
8749
  });
8610
8750
  const virtualModules = createTestIdsVirtualModulesPlugin(componentHierarchyMap, elementMetadata);
8611
- return [tsProcessor, devProcessor, virtualModules];
8751
+ const annotatorUiPlugin = createAnnotatorUiPlugin({
8752
+ ...annotatorRuntime.ui,
8753
+ enabled: annotatorRuntime.enabled && annotatorRuntime.ui.enabled,
8754
+ sourceAttribute: annotatorRuntime.sourceAttribute,
8755
+ metadataAttributePrefix: annotatorRuntime.metadataAttributePrefix
8756
+ });
8757
+ return [tsProcessor, devProcessor, virtualModules, annotatorUiPlugin];
8612
8758
  }
8613
8759
  function findDataTestIdProp(element, attributeName = "data-testid") {
8614
8760
  return element.props.find(
@@ -8728,7 +8874,9 @@ function tryCreateElementMetadata(args) {
8728
8874
  staticAriaLabel: findStaticAttributeValue(element, "aria-label"),
8729
8875
  staticRole: findStaticAttributeValue(element, "role"),
8730
8876
  staticTitle: findStaticAttributeValue(element, "title"),
8731
- staticTextContent: collectStaticTextContent(element.children)
8877
+ staticTextContent: collectStaticTextContent(element.children),
8878
+ sourceLine: element.loc?.start.line,
8879
+ sourceColumn: element.loc?.start.column
8732
8880
  };
8733
8881
  return metadata;
8734
8882
  }
@@ -8800,7 +8948,8 @@ function createVuePluginWithTestIds(options) {
8800
8948
  loggerRef,
8801
8949
  getSourceDirs,
8802
8950
  getWrapperSearchRoots,
8803
- getProjectRoot
8951
+ getProjectRoot,
8952
+ annotatorMetadata
8804
8953
  } = options;
8805
8954
  const lastAccessibilityWarningSignatureByComponent = /* @__PURE__ */ new Map();
8806
8955
  const getComponentNameFromPath = (filename) => {
@@ -8871,7 +9020,8 @@ function createVuePluginWithTestIds(options) {
8871
9020
  missingSemanticNameBehavior,
8872
9021
  warn: (message) => loggerRef.current.warn(message),
8873
9022
  vueFilesPathMap,
8874
- wrapperSearchRoots: getWrapperSearchRoots()
9023
+ wrapperSearchRoots: getWrapperSearchRoots(),
9024
+ annotatorMetadata
8875
9025
  }
8876
9026
  )
8877
9027
  );
@@ -8926,7 +9076,8 @@ function createVuePluginWithTestIds(options) {
8926
9076
  missingSemanticNameBehavior,
8927
9077
  warn: (message) => loggerRef.current.warn(message),
8928
9078
  vueFilesPathMap,
8929
- wrapperSearchRoots: getWrapperSearchRoots()
9079
+ wrapperSearchRoots: getWrapperSearchRoots(),
9080
+ annotatorMetadata
8930
9081
  }
8931
9082
  );
8932
9083
  perFileTransform.set(componentName, transform);
@@ -9071,6 +9222,12 @@ function assertOneOf(value, allowed, name) {
9071
9222
  }
9072
9223
  throw new TypeError(`${name} must be one of: ${allowed.join(", ")}.`);
9073
9224
  }
9225
+ function assertDataAttributeName(value, name) {
9226
+ assertNonEmptyString(value, name);
9227
+ if (!value.startsWith("data-")) {
9228
+ throw new TypeError(`${name} must start with "data-".`);
9229
+ }
9230
+ }
9074
9231
  function readPackageJson(projectRoot) {
9075
9232
  const packageJsonPath = path.join(projectRoot, "package.json");
9076
9233
  if (!fs.existsSync(packageJsonPath)) {
@@ -9234,6 +9391,19 @@ function createVuePomGeneratorPlugins(options = {}) {
9234
9391
  const generationEnabled = generationOptions !== null;
9235
9392
  const vueGenerationOptions = generationOptions;
9236
9393
  const verbosity = options.logging?.verbosity ?? "warn";
9394
+ const annotatorRuntimeOptions = options.runtime?.annotator;
9395
+ const annotatorUiRuntimeOptions = annotatorRuntimeOptions?.ui;
9396
+ const resolvedAnnotatorRuntimeOptions = {
9397
+ enabled: annotatorRuntimeOptions?.enabled === true,
9398
+ sourceAttribute: annotatorRuntimeOptions?.sourceAttribute ?? "data-v-inspector",
9399
+ metadataAttributePrefix: annotatorRuntimeOptions?.metadataAttributePrefix ?? "data-v-pom",
9400
+ ui: {
9401
+ enabled: annotatorUiRuntimeOptions?.enabled === true,
9402
+ outputDetail: annotatorUiRuntimeOptions?.outputDetail ?? "forensic",
9403
+ copyToClipboard: annotatorUiRuntimeOptions?.copyToClipboard ?? false,
9404
+ showComponentTree: annotatorUiRuntimeOptions?.showComponentTree ?? true
9405
+ }
9406
+ };
9237
9407
  const vueOptions = options.vueOptions;
9238
9408
  const resolvedInjectionOptionsRef = {
9239
9409
  current: resolveInjectionSupportOptions({
@@ -9337,6 +9507,10 @@ function createVuePomGeneratorPlugins(options = {}) {
9337
9507
  assertNonEmptyStringArray(getComponentDirs(), "[vue-pom-generator] injection.componentDirs");
9338
9508
  assertNonEmptyStringArray(getLayoutDirs(), "[vue-pom-generator] injection.layoutDirs");
9339
9509
  assertNonEmptyStringArray(getWrapperSearchRoots(), "[vue-pom-generator] injection.wrapperSearchRoots");
9510
+ if (resolvedAnnotatorRuntimeOptions.enabled) {
9511
+ assertDataAttributeName(resolvedAnnotatorRuntimeOptions.sourceAttribute, "[vue-pom-generator] runtime.annotator.sourceAttribute");
9512
+ assertDataAttributeName(resolvedAnnotatorRuntimeOptions.metadataAttributePrefix, "[vue-pom-generator] runtime.annotator.metadataAttributePrefix");
9513
+ }
9340
9514
  if (generationEnabled) {
9341
9515
  assertNonEmptyString(resolvedGenerationOptions.outDir, "[vue-pom-generator] generation.outDir");
9342
9516
  assertOneOf(resolvedGenerationOptions.typescriptOutputStructure, ["aggregated", "split"], "[vue-pom-generator] generation.playwright.outputStructure");
@@ -9379,10 +9553,14 @@ function createVuePomGeneratorPlugins(options = {}) {
9379
9553
  loggerRef,
9380
9554
  getSourceDirs,
9381
9555
  getWrapperSearchRoots: getWrapperSearchRootsAbs,
9382
- getProjectRoot: () => projectRootRef.current
9556
+ getProjectRoot: () => projectRootRef.current,
9557
+ annotatorMetadata: resolvedAnnotatorRuntimeOptions.enabled ? {
9558
+ sourceAttribute: resolvedAnnotatorRuntimeOptions.sourceAttribute,
9559
+ metadataAttributePrefix: resolvedAnnotatorRuntimeOptions.metadataAttributePrefix
9560
+ } : null
9383
9561
  });
9384
9562
  templateCompilerOptionsForResolvedPlugin = templateCompilerOptions;
9385
- const supportPlugins = createSupportPlugins({
9563
+ const internalPlugins = createInternalPlugins({
9386
9564
  componentHierarchyMap,
9387
9565
  elementMetadata,
9388
9566
  vueFilesPathMap,
@@ -9397,7 +9575,8 @@ function createVuePomGeneratorPlugins(options = {}) {
9397
9575
  generation: resolvedGenerationOptions,
9398
9576
  projectRootRef,
9399
9577
  basePageClassPath: basePageClassPathOverride,
9400
- loggerRef
9578
+ loggerRef,
9579
+ annotatorRuntime: resolvedAnnotatorRuntimeOptions
9401
9580
  });
9402
9581
  if (isNuxt) {
9403
9582
  loggerRef.current.info("Nuxt environment detected. Skipping internal @vitejs/plugin-vue to avoid conflicts.");
@@ -9408,7 +9587,7 @@ function createVuePomGeneratorPlugins(options = {}) {
9408
9587
  configPlugin,
9409
9588
  metadataCollectorPlugin,
9410
9589
  ...usesExternalVuePlugin ? [] : [internalVuePlugin],
9411
- ...supportPlugins
9590
+ ...internalPlugins
9412
9591
  ];
9413
9592
  if (!generationEnabled) {
9414
9593
  const virtualModules = createTestIdsVirtualModulesPlugin(componentHierarchyMap, elementMetadata);