@immense/vue-pom-generator 1.0.32 → 1.0.33

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
@@ -4407,6 +4407,32 @@ function getConstructor(childrenComponent, componentHierarchyMap, attachmentsFor
4407
4407
  return `${content}
4408
4408
  `;
4409
4409
  }
4410
+ function getGenerationMetrics(componentHierarchyMap) {
4411
+ let selectorCount = 0;
4412
+ let generatedMethodCount = 0;
4413
+ for (const deps of componentHierarchyMap.values()) {
4414
+ selectorCount += deps.dataTestIdSet?.size ?? 0;
4415
+ generatedMethodCount += deps.generatedMethods?.size ?? 0;
4416
+ }
4417
+ return {
4418
+ entryCount: componentHierarchyMap.size,
4419
+ selectorCount,
4420
+ generatedMethodCount
4421
+ };
4422
+ }
4423
+ function isLessRich(current, previous) {
4424
+ if (current.entryCount !== previous.entryCount) {
4425
+ return current.entryCount < previous.entryCount;
4426
+ }
4427
+ if (current.selectorCount !== previous.selectorCount) {
4428
+ return current.selectorCount < previous.selectorCount;
4429
+ }
4430
+ return current.generatedMethodCount < previous.generatedMethodCount;
4431
+ }
4432
+ function getGenerationMetricsKey(projectRoot, outDir) {
4433
+ return path.resolve(projectRoot, outDir ?? "./pom");
4434
+ }
4435
+ const buildGenerationMetricsByOutputKey = /* @__PURE__ */ new Map();
4410
4436
  function createBuildProcessorPlugin(options) {
4411
4437
  const {
4412
4438
  componentHierarchyMap,
@@ -4429,7 +4455,6 @@ function createBuildProcessorPlugin(options) {
4429
4455
  routerModuleShims,
4430
4456
  loggerRef
4431
4457
  } = options;
4432
- let lastGeneratedEntryCount = 0;
4433
4458
  return {
4434
4459
  name: "vue-pom-generator-build",
4435
4460
  // This plugin exists to generate code on build output; it is not needed during dev-server HMR.
@@ -4476,11 +4501,13 @@ function createBuildProcessorPlugin(options) {
4476
4501
  this.addWatchFile(pointerPath);
4477
4502
  },
4478
4503
  buildEnd() {
4479
- const entryCount = componentHierarchyMap.size;
4480
- if (entryCount <= 0) {
4504
+ const metrics = getGenerationMetrics(componentHierarchyMap);
4505
+ if (metrics.entryCount <= 0 || metrics.selectorCount <= 0) {
4481
4506
  return;
4482
4507
  }
4483
- if (entryCount < lastGeneratedEntryCount) {
4508
+ const generationMetricsKey = getGenerationMetricsKey(projectRootRef.current, outDir);
4509
+ const previousMetrics = buildGenerationMetricsByOutputKey.get(generationMetricsKey);
4510
+ if (previousMetrics && isLessRich(metrics, previousMetrics)) {
4484
4511
  return;
4485
4512
  }
4486
4513
  generateFiles(componentHierarchyMap, vueFilesPathMap, normalizedBasePagePath, {
@@ -4498,8 +4525,8 @@ function createBuildProcessorPlugin(options) {
4498
4525
  routerEntry: resolvedRouterEntry,
4499
4526
  routerType
4500
4527
  });
4501
- lastGeneratedEntryCount = entryCount;
4502
- loggerRef.current.info(`generated POMs (${entryCount} entries)`);
4528
+ buildGenerationMetricsByOutputKey.set(generationMetricsKey, metrics);
4529
+ loggerRef.current.info(`generated POMs (${metrics.entryCount} entries, ${metrics.selectorCount} selectors)`);
4503
4530
  },
4504
4531
  closeBundle() {
4505
4532
  loggerRef.current.info("build complete");
@@ -4537,6 +4564,89 @@ function toKebabCaseTag(tag) {
4537
4564
  }
4538
4565
  return result;
4539
4566
  }
4567
+ function getStaticAttributeContent(element, name) {
4568
+ const attr = element.props.find((prop) => {
4569
+ return prop.type === NodeTypes.ATTRIBUTE && prop.name === name;
4570
+ });
4571
+ return attr?.value?.content?.trim() || null;
4572
+ }
4573
+ function getNativeHtmlControlRole(element) {
4574
+ const tag = (element.tag || "").toLowerCase();
4575
+ const type = (getStaticAttributeContent(element, "type") || "").toLowerCase();
4576
+ if (tag === "textarea") {
4577
+ return "input";
4578
+ }
4579
+ if (tag === "select") {
4580
+ return "select";
4581
+ }
4582
+ if (tag !== "input") {
4583
+ return null;
4584
+ }
4585
+ if (type === "radio") {
4586
+ return "radio";
4587
+ }
4588
+ if (type === "checkbox") {
4589
+ return "checkbox";
4590
+ }
4591
+ return "input";
4592
+ }
4593
+ function normalizeControlLabelText(value) {
4594
+ const normalized = (value ?? "").replace(/\*/g, " ").replace(/\s+/g, " ").trim();
4595
+ return normalized || null;
4596
+ }
4597
+ function getLabelNodeText(labelNode) {
4598
+ for (const child of labelNode.children || []) {
4599
+ if (child.type === NodeTypes.TEXT) {
4600
+ const normalized2 = normalizeControlLabelText(child.content);
4601
+ if (normalized2) {
4602
+ return normalized2;
4603
+ }
4604
+ continue;
4605
+ }
4606
+ if (child.type !== NodeTypes.ELEMENT) {
4607
+ continue;
4608
+ }
4609
+ if (getNativeHtmlControlRole(child)) {
4610
+ continue;
4611
+ }
4612
+ const normalized = normalizeControlLabelText(getInnerText(child));
4613
+ if (normalized) {
4614
+ return normalized;
4615
+ }
4616
+ }
4617
+ return normalizeControlLabelText(getInnerText(labelNode));
4618
+ }
4619
+ function getAssociatedLabelText(element, hierarchyMap2) {
4620
+ let parent = hierarchyMap2.get(element) || null;
4621
+ while (parent) {
4622
+ if (parent.tag === "label") {
4623
+ return getLabelNodeText(parent);
4624
+ }
4625
+ parent = hierarchyMap2.get(parent) || null;
4626
+ }
4627
+ const id = getStaticAttributeContent(element, "id");
4628
+ if (!id) {
4629
+ return null;
4630
+ }
4631
+ const candidates = /* @__PURE__ */ new Set();
4632
+ for (const child of hierarchyMap2.keys()) {
4633
+ candidates.add(child);
4634
+ }
4635
+ for (const maybeParent of hierarchyMap2.values()) {
4636
+ if (maybeParent) {
4637
+ candidates.add(maybeParent);
4638
+ }
4639
+ }
4640
+ for (const candidate of candidates) {
4641
+ if (candidate.tag !== "label") {
4642
+ continue;
4643
+ }
4644
+ if (getStaticAttributeContent(candidate, "for") === id) {
4645
+ return getLabelNodeText(candidate);
4646
+ }
4647
+ }
4648
+ return null;
4649
+ }
4540
4650
  function normalizeSearchRoots(wrapperSearchRoots) {
4541
4651
  const normalized = /* @__PURE__ */ new Set();
4542
4652
  for (const root of wrapperSearchRoots) {
@@ -5204,7 +5314,9 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
5204
5314
  }
5205
5315
  }
5206
5316
  const getBestAvailableKeyValue = () => {
5207
- const vForKey = getKeyDirectiveValue(element, context) || getSelfClosingForDirectiveKeyAttrValue(element) || getContainedInVForDirectiveKeyValue(context, element, hierarchyMap);
5317
+ const parentNode = context.parent && typeof context.parent === "object" ? context.parent : null;
5318
+ const isDirectVForChild = parentNode?.type === NodeTypes.FOR;
5319
+ const vForKey = (isDirectVForChild ? getKeyDirectiveValue(element, context) : null) || getContainedInVForDirectiveKeyValue(context, element, hierarchyMap);
5208
5320
  if (vForKey) return vForKey;
5209
5321
  return getContainedInSlotDataKeyValue(element, hierarchyMap);
5210
5322
  };
@@ -5361,6 +5473,50 @@ Fix: remove the explicit ${attrLabel}, or change existingIdBehavior to "overwrit
5361
5473
  });
5362
5474
  return;
5363
5475
  }
5476
+ const nativeHtmlRole = getNativeHtmlControlRole(element);
5477
+ if (nativeHtmlRole) {
5478
+ const rawIdentifier = getStaticAttributeContent(element, "id") || getStaticAttributeContent(element, "name");
5479
+ const labelText = getAssociatedLabelText(element, hierarchyMap);
5480
+ const { vModel, modelValue } = getModelBindingValues(element);
5481
+ const bindingHint = modelValue || vModel || null;
5482
+ const labelToken = labelText ? toPascalCase(labelText) : "";
5483
+ const bindingToken = bindingHint ? toPascalCase(bindingHint) : "";
5484
+ let identifierToken = null;
5485
+ let semanticNameHint2;
5486
+ if (nativeHtmlRole === "radio" || nativeHtmlRole === "checkbox") {
5487
+ if (rawIdentifier) {
5488
+ identifierToken = rawIdentifier;
5489
+ semanticNameHint2 = rawIdentifier;
5490
+ } else if (bindingToken && labelToken) {
5491
+ identifierToken = `${bindingToken}${labelToken}`;
5492
+ semanticNameHint2 = `${bindingHint || bindingToken} ${labelText || labelToken}`;
5493
+ } else if (labelToken) {
5494
+ identifierToken = labelToken;
5495
+ semanticNameHint2 = labelText || labelToken;
5496
+ } else if (bindingToken) {
5497
+ identifierToken = bindingToken;
5498
+ semanticNameHint2 = bindingHint || bindingToken;
5499
+ }
5500
+ } else if (rawIdentifier) {
5501
+ identifierToken = rawIdentifier;
5502
+ semanticNameHint2 = rawIdentifier;
5503
+ } else if (labelToken) {
5504
+ identifierToken = labelToken;
5505
+ semanticNameHint2 = labelText || labelToken;
5506
+ } else if (bindingToken) {
5507
+ identifierToken = bindingToken;
5508
+ semanticNameHint2 = bindingHint || bindingToken;
5509
+ }
5510
+ if (identifierToken) {
5511
+ const preferredGeneratedValue = bestKeyPlaceholder ? templateAttributeValue(`${componentName}-${bestKeyPlaceholder}-${identifierToken}-${nativeHtmlRole}`) : staticAttributeValue(`${componentName}-${identifierToken}-${nativeHtmlRole}`);
5512
+ applyResolvedDataTestIdForElement({
5513
+ preferredGeneratedValue,
5514
+ nativeRoleOverride: nativeHtmlRole,
5515
+ semanticNameHint: semanticNameHint2 || conditionalHint || void 0
5516
+ });
5517
+ return;
5518
+ }
5519
+ }
5364
5520
  const innerText = getInnerText(element) || null;
5365
5521
  const toDirective = nodeHasToDirective(element);
5366
5522
  if (toDirective) {
@@ -5479,6 +5635,7 @@ function resolveComponentNameFromPath(options) {
5479
5635
  }
5480
5636
  return toPascalCase(path.parse(absFilename).name);
5481
5637
  }
5638
+ const devStartupMetricsByOutputKey = /* @__PURE__ */ new Map();
5482
5639
  function createDevProcessorPlugin(options) {
5483
5640
  const {
5484
5641
  nativeWrappers,
@@ -5672,6 +5829,21 @@ function createDevProcessorPlugin(options) {
5672
5829
  logInfo(`initial compile: ${compiledCount}/${totalVueFiles} files in ${formatMs(t1 - t0)} (components=${snapshotHierarchy.size})`);
5673
5830
  };
5674
5831
  const generateAggregatedFromSnapshot = (reason) => {
5832
+ const metrics = getGenerationMetrics(snapshotHierarchy);
5833
+ if (metrics.entryCount <= 0 || metrics.selectorCount <= 0) {
5834
+ logInfo(`generate(${reason}): skipped empty snapshot (components=${metrics.entryCount}, selectors=${metrics.selectorCount})`);
5835
+ return;
5836
+ }
5837
+ const generationMetricsKey = getGenerationMetricsKey(projectRootRef.current, outDir);
5838
+ if (reason === "startup") {
5839
+ const previousMetrics = devStartupMetricsByOutputKey.get(generationMetricsKey);
5840
+ if (previousMetrics && isLessRich(metrics, previousMetrics)) {
5841
+ logInfo(
5842
+ `generate(${reason}): skipped smaller snapshot (components=${metrics.entryCount}, selectors=${metrics.selectorCount})`
5843
+ );
5844
+ return;
5845
+ }
5846
+ }
5675
5847
  const t0 = performance.now();
5676
5848
  generateFiles(snapshotHierarchy, snapshotVuePathMap, normalizedBasePagePath, {
5677
5849
  outDir,
@@ -5688,8 +5860,11 @@ function createDevProcessorPlugin(options) {
5688
5860
  routerEntry: resolvedRouterEntry,
5689
5861
  routerType
5690
5862
  });
5863
+ if (reason === "startup") {
5864
+ devStartupMetricsByOutputKey.set(generationMetricsKey, metrics);
5865
+ }
5691
5866
  const t1 = performance.now();
5692
- logInfo(`generate(${reason}): components=${snapshotHierarchy.size} in ${formatMs(t1 - t0)}`);
5867
+ logInfo(`generate(${reason}): components=${metrics.entryCount} selectors=${metrics.selectorCount} in ${formatMs(t1 - t0)}`);
5693
5868
  };
5694
5869
  const initialBuildPromise = (async () => {
5695
5870
  const t0 = performance.now();
@@ -6210,20 +6385,21 @@ function createVuePluginWithTestIds(options) {
6210
6385
  }
6211
6386
  ];
6212
6387
  };
6388
+ const runtimeNodeTransform = (node, context) => {
6389
+ const filename = context.filename;
6390
+ if (!filename || !filename.endsWith(".vue") || !isFileInScope(filename)) {
6391
+ return;
6392
+ }
6393
+ const transforms = getNodeTransforms(filename);
6394
+ const ourTransform = transforms[transforms.length - 1];
6395
+ return ourTransform(node, context);
6396
+ };
6213
6397
  const templateCompilerOptions = {
6214
6398
  ...userCompilerOptions,
6215
6399
  prefixIdentifiers: true,
6216
6400
  nodeTransforms: [
6217
6401
  ...userNodeTransforms,
6218
- (node, context) => {
6219
- const filename = context.filename;
6220
- if (!filename || !filename.endsWith(".vue") || !isFileInScope(filename)) {
6221
- return;
6222
- }
6223
- const transforms = getNodeTransforms(filename);
6224
- const ourTransform = transforms[transforms.length - 1];
6225
- return ourTransform(node, context);
6226
- }
6402
+ runtimeNodeTransform
6227
6403
  ]
6228
6404
  };
6229
6405
  const metadataCollectorPlugin = {
@@ -6262,7 +6438,42 @@ function createVuePluginWithTestIds(options) {
6262
6438
  ...vueOptions,
6263
6439
  template
6264
6440
  });
6265
- return { metadataCollectorPlugin, internalVuePlugin };
6441
+ const nuxtVueBridgePlugin = {
6442
+ name: "vue-pom-generator-nuxt-vue-bridge",
6443
+ apply: "serve",
6444
+ configResolved(config) {
6445
+ const viteVuePlugin = config.plugins.find((plugin) => {
6446
+ return typeof plugin === "object" && plugin !== null && "name" in plugin && plugin.name === "vite:vue" && "api" in plugin;
6447
+ });
6448
+ const api = viteVuePlugin?.api;
6449
+ if (!api) {
6450
+ loggerRef.current.warn("[vue-pom-generator] Nuxt bridge could not find vite:vue plugin to patch.");
6451
+ return;
6452
+ }
6453
+ const currentOptions = api.options ?? {};
6454
+ const currentTemplate = currentOptions.template ?? {};
6455
+ const currentCompilerOptions = currentTemplate.compilerOptions ?? {};
6456
+ const currentNodeTransforms = currentCompilerOptions.nodeTransforms ?? [];
6457
+ if (currentNodeTransforms.includes(runtimeNodeTransform)) {
6458
+ return;
6459
+ }
6460
+ api.options = {
6461
+ ...currentOptions,
6462
+ template: {
6463
+ ...currentTemplate,
6464
+ compilerOptions: {
6465
+ ...currentCompilerOptions,
6466
+ prefixIdentifiers: true,
6467
+ nodeTransforms: [
6468
+ ...currentNodeTransforms,
6469
+ runtimeNodeTransform
6470
+ ]
6471
+ }
6472
+ }
6473
+ };
6474
+ }
6475
+ };
6476
+ return { metadataCollectorPlugin, internalVuePlugin, nuxtVueBridgePlugin };
6266
6477
  }
6267
6478
  function assertNonEmptyString(value, name) {
6268
6479
  if (!value || !value.trim()) {
@@ -6388,7 +6599,7 @@ function createVuePomGeneratorPlugins(options = {}) {
6388
6599
  const semanticNameMap = /* @__PURE__ */ new Map();
6389
6600
  const componentHierarchyMap = /* @__PURE__ */ new Map();
6390
6601
  const vueFilesPathMap = /* @__PURE__ */ new Map();
6391
- const { metadataCollectorPlugin, internalVuePlugin } = createVuePluginWithTestIds({
6602
+ const { metadataCollectorPlugin, internalVuePlugin, nuxtVueBridgePlugin } = createVuePluginWithTestIds({
6392
6603
  vueOptions,
6393
6604
  existingIdBehavior,
6394
6605
  nameCollisionBehavior,
@@ -6439,7 +6650,7 @@ function createVuePomGeneratorPlugins(options = {}) {
6439
6650
  const resultPlugins = [
6440
6651
  configPlugin,
6441
6652
  metadataCollectorPlugin,
6442
- ...isNuxt ? [] : [internalVuePlugin],
6653
+ ...isNuxt ? [nuxtVueBridgePlugin] : [internalVuePlugin],
6443
6654
  ...supportPlugins
6444
6655
  ];
6445
6656
  if (!generationEnabled) {
@@ -6447,7 +6658,7 @@ function createVuePomGeneratorPlugins(options = {}) {
6447
6658
  return [
6448
6659
  configPlugin,
6449
6660
  metadataCollectorPlugin,
6450
- ...isNuxt ? [] : [internalVuePlugin],
6661
+ ...isNuxt ? [nuxtVueBridgePlugin] : [internalVuePlugin],
6451
6662
  virtualModules
6452
6663
  ];
6453
6664
  }