@immense/vue-pom-generator 1.0.60 → 1.0.61

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
@@ -219,6 +219,7 @@ function resolveGenerationSupportOptions(options) {
219
219
  nameCollisionBehavior: options.nameCollisionBehavior ?? "error",
220
220
  existingIdBehavior: options.existingIdBehavior ?? "error",
221
221
  testIdAttribute: (options.testIdAttribute ?? "data-testid").trim() || "data-testid",
222
+ accessibilityAudit: options.accessibilityAudit ?? false,
222
223
  routerAwarePoms: options.routerAwarePoms ?? false,
223
224
  routerEntry: options.routerEntry,
224
225
  routerType: options.routerType ?? "vue-router",
@@ -8173,6 +8174,59 @@ function createDevProcessorPlugin(options) {
8173
8174
  }
8174
8175
  };
8175
8176
  }
8177
+ function supportsInlineTextAccessibleName(role) {
8178
+ return role === "button" || role === "radio";
8179
+ }
8180
+ function buildAccessibilityAudit(metadata, inferredRole) {
8181
+ if (!metadata || !inferredRole) {
8182
+ return void 0;
8183
+ }
8184
+ const role = normalizePomRoleLabel(inferredRole).toLowerCase();
8185
+ const dynamicProps = new Set(metadata.dynamicProps ?? []);
8186
+ const hasDynamicAccessibleNameSignal = dynamicProps.has("aria-label") || dynamicProps.has("title") || !!metadata.hasDynamicText;
8187
+ let accessibleNameSource;
8188
+ const reasons = [];
8189
+ if (metadata.staticAriaLabel) {
8190
+ accessibleNameSource = "aria-label";
8191
+ } else if (supportsInlineTextAccessibleName(role) && metadata.staticTextContent) {
8192
+ accessibleNameSource = "text";
8193
+ } else if (metadata.staticTitle) {
8194
+ accessibleNameSource = "title";
8195
+ } else if (hasDynamicAccessibleNameSignal) {
8196
+ accessibleNameSource = "dynamic";
8197
+ } else if (role === "input" || role === "select") {
8198
+ accessibleNameSource = "unknown";
8199
+ reasons.push("No inline accessible-name signal was found; the element may rely on external markup such as a separate <label>.");
8200
+ } else {
8201
+ accessibleNameSource = "missing";
8202
+ reasons.push("No compile-time accessible-name signal was found.");
8203
+ }
8204
+ return {
8205
+ needsReview: accessibleNameSource === "unknown" || accessibleNameSource === "missing",
8206
+ accessibleNameSource,
8207
+ reasons,
8208
+ ...metadata.staticAriaLabel ? { staticAriaLabel: metadata.staticAriaLabel } : {},
8209
+ ...metadata.staticRole ? { staticRole: metadata.staticRole } : {},
8210
+ ...metadata.staticTitle ? { staticTitle: metadata.staticTitle } : {},
8211
+ ...metadata.staticTextContent ? { staticTextContent: metadata.staticTextContent } : {}
8212
+ };
8213
+ }
8214
+ function collectAccessibilityReviewWarnings(manifest) {
8215
+ const warnings = [];
8216
+ for (const [componentName, component] of Object.entries(manifest)) {
8217
+ for (const entry of component.entries) {
8218
+ if (!entry.accessibility?.needsReview) {
8219
+ continue;
8220
+ }
8221
+ const entryLabel = entry.generatedPropertyName ?? entry.testId;
8222
+ const reasonText = entry.accessibility.reasons.join(" ");
8223
+ warnings.push(
8224
+ `[vue-pom-generator] Accessibility review suggested for ${componentName}.${entryLabel} (role=${entry.inferredRole ?? "unknown"}, testId=${JSON.stringify(entry.testId)}): ${reasonText}`
8225
+ );
8226
+ }
8227
+ }
8228
+ return warnings;
8229
+ }
8176
8230
  function removeByKeySegment(value) {
8177
8231
  const idx = value.indexOf("ByKey");
8178
8232
  if (idx < 0) {
@@ -8231,6 +8285,7 @@ function getManifestEntry(componentName, entry, componentMetadata, extraMethods)
8231
8285
  ...generatedActionName ? [generatedActionName] : [],
8232
8286
  ...extraActionNames.filter((name) => name !== generatedActionName)
8233
8287
  ]));
8288
+ const accessibility = buildAccessibilityAudit(metadata, pom?.nativeRole ?? null);
8234
8289
  return {
8235
8290
  testId,
8236
8291
  selectorPatternKind: entry.selectorValue.patternKind,
@@ -8241,6 +8296,7 @@ function getManifestEntry(componentName, entry, componentMetadata, extraMethods)
8241
8296
  nativeRole: pom.nativeRole
8242
8297
  }) : componentName,
8243
8298
  inferredRole: pom?.nativeRole ?? null,
8299
+ ...accessibility ? { accessibility } : {},
8244
8300
  generatedPropertyName: pom ? getGeneratedPropertyName(pom) : null,
8245
8301
  generatedActionName,
8246
8302
  generatedActionNames,
@@ -8516,6 +8572,33 @@ function parseDynamicProps(dynamicProps) {
8516
8572
  }
8517
8573
  return void 0;
8518
8574
  }
8575
+ function findStaticAttributeValue(element, attributeName) {
8576
+ const attribute = element.props.find(
8577
+ (prop) => prop.type === NodeTypes.ATTRIBUTE && prop.name === attributeName
8578
+ );
8579
+ const value = attribute?.value?.content?.trim();
8580
+ return value ? value : void 0;
8581
+ }
8582
+ function collectStaticTextContent(children) {
8583
+ const parts = [];
8584
+ const visit = (nodes) => {
8585
+ for (const node of nodes) {
8586
+ if (node.type === NodeTypes.TEXT) {
8587
+ const text2 = node.content.replace(/\s+/g, " ").trim();
8588
+ if (text2) {
8589
+ parts.push(text2);
8590
+ }
8591
+ continue;
8592
+ }
8593
+ if (node.type === NodeTypes.ELEMENT) {
8594
+ visit(node.children);
8595
+ }
8596
+ }
8597
+ };
8598
+ visit(children);
8599
+ const text = parts.join(" ").replace(/\s+/g, " ").trim();
8600
+ return text || void 0;
8601
+ }
8519
8602
  function tryCreateElementMetadata(args) {
8520
8603
  const { element, semanticNameMap } = args;
8521
8604
  const testIdAttribute = (args.testIdAttribute ?? "data-testid").trim() || "data-testid";
@@ -8539,13 +8622,9 @@ function tryCreateElementMetadata(args) {
8539
8622
  dynamicPropsList = [content];
8540
8623
  }
8541
8624
  }
8542
- const semanticName = semanticNameMap.get(testId);
8543
- if (!semanticName) {
8544
- return null;
8545
- }
8546
8625
  const metadata = {
8547
8626
  testId,
8548
- semanticName,
8627
+ semanticName: semanticNameMap.get(testId),
8549
8628
  tag: element.tag,
8550
8629
  tagType: element.tagType,
8551
8630
  patchFlag,
@@ -8554,7 +8633,11 @@ function tryCreateElementMetadata(args) {
8554
8633
  hasClickHandler: patchFlag ? Boolean(patchFlag & 32) : void 0,
8555
8634
  hasDynamicClass: patchFlag ? Boolean(patchFlag & 2) : void 0,
8556
8635
  hasDynamicStyle: patchFlag ? Boolean(patchFlag & 4) : void 0,
8557
- hasDynamicText: patchFlag ? Boolean(patchFlag & 1) : void 0
8636
+ hasDynamicText: patchFlag ? Boolean(patchFlag & 1) : void 0,
8637
+ staticAriaLabel: findStaticAttributeValue(element, "aria-label"),
8638
+ staticRole: findStaticAttributeValue(element, "role"),
8639
+ staticTitle: findStaticAttributeValue(element, "title"),
8640
+ staticTextContent: collectStaticTextContent(element.children)
8558
8641
  };
8559
8642
  return metadata;
8560
8643
  }
@@ -8606,6 +8689,7 @@ function extractMetadataAfterTransform(ast, componentName, elementMetadata, sema
8606
8689
  if (componentMetadata.size > 0) {
8607
8690
  elementMetadata.set(componentName, componentMetadata);
8608
8691
  }
8692
+ return componentMetadata;
8609
8693
  }
8610
8694
  function createVuePluginWithTestIds(options) {
8611
8695
  const {
@@ -8620,11 +8704,13 @@ function createVuePluginWithTestIds(options) {
8620
8704
  excludedComponents,
8621
8705
  getViewsDirAbs,
8622
8706
  testIdAttribute,
8707
+ accessibilityAudit,
8623
8708
  loggerRef,
8624
8709
  getSourceDirs,
8625
8710
  getWrapperSearchRoots,
8626
8711
  getProjectRoot
8627
8712
  } = options;
8713
+ const lastAccessibilityWarningSignatureByComponent = /* @__PURE__ */ new Map();
8628
8714
  const getComponentNameFromPath = (filename) => {
8629
8715
  return resolveComponentNameFromPath({
8630
8716
  filename,
@@ -8697,13 +8783,37 @@ function createVuePluginWithTestIds(options) {
8697
8783
  )
8698
8784
  );
8699
8785
  return () => {
8700
- extractMetadataAfterTransform(
8786
+ const componentMetadata = extractMetadataAfterTransform(
8701
8787
  node,
8702
8788
  componentName,
8703
8789
  elementMetadata,
8704
8790
  semanticNameMap,
8705
8791
  testIdAttribute
8706
8792
  );
8793
+ if (!accessibilityAudit) {
8794
+ return;
8795
+ }
8796
+ const dependencies = componentHierarchyMap.get(componentName);
8797
+ if (!dependencies || componentMetadata.size === 0) {
8798
+ return;
8799
+ }
8800
+ const manifest = buildPomManifest(
8801
+ /* @__PURE__ */ new Map([[componentName, dependencies]]),
8802
+ /* @__PURE__ */ new Map([[componentName, componentMetadata]])
8803
+ );
8804
+ const warnings = collectAccessibilityReviewWarnings(manifest);
8805
+ const signature = warnings.join("\n");
8806
+ if (!signature) {
8807
+ lastAccessibilityWarningSignatureByComponent.delete(componentName);
8808
+ return;
8809
+ }
8810
+ if (lastAccessibilityWarningSignatureByComponent.get(componentName) === signature) {
8811
+ return;
8812
+ }
8813
+ lastAccessibilityWarningSignatureByComponent.set(componentName, signature);
8814
+ for (const warning of warnings) {
8815
+ loggerRef.current.warn(warning);
8816
+ }
8707
8817
  };
8708
8818
  }
8709
8819
  let transform = perFileTransform.get(componentName);
@@ -9068,6 +9178,7 @@ function createVuePomGeneratorPlugins(options = {}) {
9068
9178
  nameCollisionBehavior: generationOptions?.nameCollisionBehavior,
9069
9179
  existingIdBehavior: resolvedInjectionOptions.existingIdBehavior,
9070
9180
  testIdAttribute,
9181
+ accessibilityAudit: generationOptions?.accessibilityAudit,
9071
9182
  routerAwarePoms: typeof routerEntry === "string" && routerEntry.trim().length > 0 || routerType === "nuxt",
9072
9183
  routerEntry,
9073
9184
  routerType,
@@ -9161,6 +9272,7 @@ function createVuePomGeneratorPlugins(options = {}) {
9161
9272
  excludedComponents,
9162
9273
  getViewsDirAbs,
9163
9274
  testIdAttribute,
9275
+ accessibilityAudit: resolvedGenerationOptions.accessibilityAudit,
9164
9276
  loggerRef,
9165
9277
  getSourceDirs,
9166
9278
  getWrapperSearchRoots: getWrapperSearchRootsAbs,