vite-plugin-vue-testid 1.1.3 → 1.1.4

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.d.ts CHANGED
@@ -19,7 +19,7 @@
19
19
  * setupAntTestIds()
20
20
  * ```
21
21
  *
22
- * === 使用方式 B:编译期 + 运行时(推荐,获得唯一稳定 testid) ===
22
+ * === 使用方式 B:编译器 + Vue 插件 + 运行时(推荐,稳定 testid) ===
23
23
  *
24
24
  * ```ts
25
25
  * // vite.config.ts
@@ -38,9 +38,13 @@
38
38
  * ]
39
39
  * })
40
40
  *
41
- * // main.ts (与 A 相同)
41
+ * // main.ts
42
+ * import { createTestIdBridge } from 'vite-plugin-vue-testid/plugin'
42
43
  * import { setupAntTestIds } from 'vite-plugin-vue-testid/runtime'
43
- * setupAntTestIds()
44
+ *
45
+ * createApp(App).mount('#app')
46
+ * app.use(createTestIdBridge()) // 编译器 testid → DOM(稳定)
47
+ * setupAntTestIds() // 面板 / 子元素 / 兜底
44
48
  * ```
45
49
  *
46
50
  * === 使用方式 C:仅编译期(无运行时,面板不会注入 testid) ===
@@ -67,10 +71,12 @@
67
71
  import type { Plugin } from 'vite';
68
72
  export type { RuntimeOptions, PanelContext, PanelInjectStrategy } from './runtime';
69
73
  export type { TransformOptions } from './transform';
74
+ export type { TestIdBridgeOptions } from './plugin';
70
75
  import { createTestIdTransforms } from './transform';
71
76
  /** 等同于 `createTestIdTransforms()`,更短的导出名 */
72
77
  export declare const testIdTransforms: typeof createTestIdTransforms;
73
78
  export { createTestIdTransforms, createTestIdInjectTransform, createMultiRootFixTransform, } from './transform';
79
+ export { createTestIdBridge } from './plugin';
74
80
  export interface VueTestIdPluginOptions {
75
81
  /**
76
82
  * testId 属性名
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiEG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAIlC,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAA;AAClF,YAAY,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAEnD,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAA;AAEpD,4CAA4C;AAC5C,eAAO,MAAM,gBAAgB,+BAAyB,CAAA;AAEtD,OAAO,EACL,sBAAsB,EACtB,2BAA2B,EAC3B,2BAA2B,GAC5B,MAAM,aAAa,CAAA;AAIpB,MAAM,WAAW,sBAAsB;IACrC;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,KAAK,CAAC,EAAE,sBAAsB,GAAG,MAAM,CAItE;AAID;;;;;;;;;GASG;AACH,wBAAgB,wBAAwB,IAAI,MAAM,IAAI,CASrD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqEG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAIlC,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAA;AAClF,YAAY,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AACnD,YAAY,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAA;AAEnD,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAA;AAEpD,4CAA4C;AAC5C,eAAO,MAAM,gBAAgB,+BAAyB,CAAA;AAEtD,OAAO,EACL,sBAAsB,EACtB,2BAA2B,EAC3B,2BAA2B,GAC5B,MAAM,aAAa,CAAA;AAEpB,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAA;AAI7C,MAAM,WAAW,sBAAsB;IACrC;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,KAAK,CAAC,EAAE,sBAAsB,GAAG,MAAM,CAItE;AAID;;;;;;;;;GASG;AACH,wBAAgB,wBAAwB,IAAI,MAAM,IAAI,CASrD"}
package/dist/index.js CHANGED
@@ -143,7 +143,7 @@ function isTargetComponent(el, antPrefix, customPrefixes) {
143
143
  const { tag, tagType } = el;
144
144
  if (tagType === ElementTypes.ELEMENT && NATIVE_TAGS.has(tag)) return false;
145
145
  if (tagType === ElementTypes.SLOT || tagType === ElementTypes.TEMPLATE) return false;
146
- if (isAntdComponent(tag, antPrefix)) return false;
146
+ if (isAntdComponent(tag, antPrefix)) return true;
147
147
  if (tagType === ElementTypes.COMPONENT) return true;
148
148
  if (customPrefixes.some((p) => tag.startsWith(p))) return true;
149
149
  return false;
@@ -247,6 +247,26 @@ function createTestIdTransforms(opts = {}) {
247
247
  ];
248
248
  }
249
249
 
250
+ // src/plugin.ts
251
+ function createTestIdBridge(opts = {}) {
252
+ const attrName = opts.attributeName ?? "data-testid";
253
+ return {
254
+ install(app) {
255
+ app.mixin({
256
+ mounted() {
257
+ const testId = this.$attrs?.[attrName];
258
+ if (!testId) return;
259
+ if (this.$el instanceof HTMLElement) {
260
+ if (!this.$el.hasAttribute(attrName)) {
261
+ this.$el.setAttribute(attrName, testId);
262
+ }
263
+ }
264
+ }
265
+ });
266
+ }
267
+ };
268
+ }
269
+
250
270
  // src/index.ts
251
271
  var testIdTransforms = createTestIdTransforms;
252
272
  function vueTestIdPlugin(_opts) {
@@ -265,6 +285,7 @@ function createVueTestIdTransform() {
265
285
  }
266
286
  export {
267
287
  createMultiRootFixTransform,
288
+ createTestIdBridge,
268
289
  createTestIdInjectTransform,
269
290
  createTestIdTransforms,
270
291
  createVueTestIdTransform,
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Vue 插件:读取编译器注入的 data-testid 属性,手动写入组件的根 DOM 元素。
3
+ *
4
+ * 为什么需要:
5
+ * 编译器在模板中为每个 (antd + 自定义) 组件生成稳定的 {tagName}-{index} testid,
6
+ * 但这些 testid 通过 $attrs 传递。部分 ant-design-vue 组件(Modal、Menu 等)
7
+ * 使用了 inheritAttrs: false,或将其 DOM 根通过 teleport 渲染到 body 下,
8
+ * 导致 testid 无法自动到达目标 DOM。
9
+ *
10
+ * 本插件在组件 mounted 时读取 $attrs['data-testid'],手动写入 $el,
11
+ * 绕过 inheritAttrs 限制,确保编译期生成的稳定 testid 最终出现在 DOM 上。
12
+ *
13
+ * 使用方式:
14
+ * ```ts
15
+ * // main.ts
16
+ * import { createTestIdBridge } from 'vite-plugin-vue-testid/plugin'
17
+ *
18
+ * createApp(App).mount('#app')
19
+ * app.use(createTestIdBridge())
20
+ * app.use(createTestIdBridge({ attributeName: 'data-testid' }))
21
+ * ```
22
+ */
23
+ import type { App } from 'vue';
24
+ export interface TestIdBridgeOptions {
25
+ /**
26
+ * testId 属性名
27
+ * @default 'data-testid'
28
+ */
29
+ attributeName?: string;
30
+ }
31
+ export declare function createTestIdBridge(opts?: TestIdBridgeOptions): {
32
+ install(app: App): void;
33
+ };
34
+ //# sourceMappingURL=plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,KAAK,CAAA;AAE9B,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,wBAAgB,kBAAkB,CAAC,IAAI,GAAE,mBAAwB;iBAIhD,GAAG;EAenB"}
package/dist/plugin.js ADDED
@@ -0,0 +1,22 @@
1
+ // src/plugin.ts
2
+ function createTestIdBridge(opts = {}) {
3
+ const attrName = opts.attributeName ?? "data-testid";
4
+ return {
5
+ install(app) {
6
+ app.mixin({
7
+ mounted() {
8
+ const testId = this.$attrs?.[attrName];
9
+ if (!testId) return;
10
+ if (this.$el instanceof HTMLElement) {
11
+ if (!this.$el.hasAttribute(attrName)) {
12
+ this.$el.setAttribute(attrName, testId);
13
+ }
14
+ }
15
+ }
16
+ });
17
+ }
18
+ };
19
+ }
20
+ export {
21
+ createTestIdBridge
22
+ };
package/dist/runtime.d.ts CHANGED
@@ -61,11 +61,14 @@ export type PanelInjectStrategy = (ctx: PanelContext) => void;
61
61
  * 启动全运行时 testid 注入。
62
62
  *
63
63
  * 覆盖范围:
64
- * - 所有 ant-design-vue 组件根元素(input / select / button / picker / ...)
65
- * - teleport 弹出层面板(日期选择器 / 级联选择器 / 下拉选择器)
64
+ * - 所有 ant-design-vue 组件根元素(input / select / button / picker / auto-complete / mentions / transfer ...)
65
+ * - teleport 弹出层面板(日期选择器 / 级联选择器 / 下拉选择器 / 提及)
66
66
  * - Menu 子元素(inheritAttrs:false 兜底)
67
67
  * - InputNumber 内部输入框及增减按钮
68
68
  * - Slider handle、Rate 星星、Upload 内部 file input
69
+ * - AutoComplete 搜索输入框及清除按钮
70
+ * - Mentions textarea 及下拉面板选项
71
+ * - Transfer 左右面板 / 列表项 / 复选框 / 移除按钮 / 操作区
69
72
  *
70
73
  * @returns 清理函数,调用后停止监听
71
74
  *
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IAEtB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAElB;;;;;;;;;;;;;OAaG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAEnC;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,GAAG,SAAS,CAAC,CAAA;CACzD;AAED,MAAM,WAAW,YAAY;IAC3B,kBAAkB;IAClB,SAAS,EAAE,WAAW,CAAA;IACtB,iBAAiB;IACjB,OAAO,EAAE,WAAW,GAAG,IAAI,CAAA;IAC3B,oBAAoB;IACpB,aAAa,EAAE,MAAM,CAAA;IACrB,iBAAiB;IACjB,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,MAAM,mBAAmB,GAAG,CAAC,GAAG,EAAE,YAAY,KAAK,IAAI,CAAA;AAosB7D;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,eAAe,CAAC,IAAI,GAAE,cAAmB,GAAG,MAAM,IAAI,CAgFrE;AAED;;GAEG;AACH,eAAO,MAAM,uBAAuB,wBAAkB,CAAA;AAMtD;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,GAAE,cAAmB,GAAG,IAAI,CAmCtE"}
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IAEtB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAElB;;;;;;;;;;;;;OAaG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAEnC;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,GAAG,SAAS,CAAC,CAAA;CACzD;AAED,MAAM,WAAW,YAAY;IAC3B,kBAAkB;IAClB,SAAS,EAAE,WAAW,CAAA;IACtB,iBAAiB;IACjB,OAAO,EAAE,WAAW,GAAG,IAAI,CAAA;IAC3B,oBAAoB;IACpB,aAAa,EAAE,MAAM,CAAA;IACrB,iBAAiB;IACjB,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,MAAM,mBAAmB,GAAG,CAAC,GAAG,EAAE,YAAY,KAAK,IAAI,CAAA;AAi+B7D;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,eAAe,CAAC,IAAI,GAAE,cAAmB,GAAG,MAAM,IAAI,CA4FrE;AAED;;GAEG;AACH,eAAO,MAAM,uBAAuB,wBAAkB,CAAA;AAMtD;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,GAAE,cAAmB,GAAG,IAAI,CA+CtE"}
package/dist/runtime.js CHANGED
@@ -8,10 +8,14 @@ function buildComponentMatchers(pc) {
8
8
  { selector: sel(pc, "input-affix-wrapper"), prefix: "a-input" },
9
9
  { selector: sel(pc, "input-textarea"), prefix: "a-textarea" },
10
10
  { selector: sel(pc, "input-number"), prefix: "a-input-number" },
11
+ // ── AutoComplete(必须在 select 之前,因为同时也有 .ant-select 类)──
12
+ { selector: sel(pc, "select-auto-complete"), prefix: "a-auto-complete", withId: true },
11
13
  // ── 选择器 ──
12
14
  { selector: sel(pc, "select"), prefix: "a-select", withId: true },
13
15
  { selector: sel(pc, "cascader"), prefix: "a-cascader", withId: true },
14
16
  { selector: sel(pc, "tree-select"), prefix: "a-tree-select" },
17
+ // ── Mentions ──
18
+ { selector: sel(pc, "mentions"), prefix: "a-mentions", withId: true },
15
19
  // ── 日期/时间 ──
16
20
  { selector: sel(pc, "picker"), prefix: "a-date-picker", withId: true },
17
21
  // ── 表单组件 ──
@@ -32,6 +36,9 @@ function buildComponentMatchers(pc) {
32
36
  { selector: sel(pc, "menu"), prefix: "a-menu" },
33
37
  // ── 标签页 ──
34
38
  { selector: sel(pc, "tabs"), prefix: "a-tabs" },
39
+ { selector: sel(pc, "pagination"), prefix: "a-pagination" },
40
+ // ── Transfer ──
41
+ { selector: sel(pc, "transfer"), prefix: "a-transfer" },
35
42
  // ── 纯 input / textarea(必须排在最后)──
36
43
  { selector: `input.${cls(pc, "input")}`, prefix: "a-input" },
37
44
  { selector: `textarea.${cls(pc, "input")}`, prefix: "a-textarea" }
@@ -272,11 +279,42 @@ function makeInjectSelectPanel(pc) {
272
279
  }
273
280
  };
274
281
  }
282
+ function makeInjectMentionsPanel(pc) {
283
+ return (ctx) => {
284
+ const { container, triggerTestId, attrName } = ctx;
285
+ const prefix = `${triggerTestId}-dropdown`;
286
+ container.setAttribute(attrName, prefix);
287
+ const injectAllItems = () => {
288
+ const items = container.querySelectorAll(sel(pc, "mentions-dropdown-menu-item"));
289
+ items.forEach((item) => {
290
+ const el = item;
291
+ if (hasAttrCompat(el, attrName)) return;
292
+ const text = el.textContent?.trim() || "";
293
+ if (text) {
294
+ el.setAttribute(attrName, `${prefix}-option-${text}`);
295
+ }
296
+ });
297
+ };
298
+ injectAllItems();
299
+ if (!_observedContainers.has(container)) {
300
+ _observedContainers.add(container);
301
+ let timer = null;
302
+ const observer = new MutationObserver(() => {
303
+ if (timer !== null) clearTimeout(timer);
304
+ timer = setTimeout(() => {
305
+ injectAllItems();
306
+ }, 50);
307
+ });
308
+ observer.observe(container, { childList: true, subtree: true });
309
+ }
310
+ };
311
+ }
275
312
  function buildPanels(pc) {
276
313
  return {
277
314
  [sel(pc, "picker-dropdown")]: makeInjectPickerPanel(pc),
278
315
  [sel(pc, "cascader-dropdown")]: makeInjectCascaderPanel(pc),
279
- [sel(pc, "select-dropdown")]: makeInjectSelectPanel(pc)
316
+ [sel(pc, "select-dropdown")]: makeInjectSelectPanel(pc),
317
+ [sel(pc, "mentions-dropdown")]: makeInjectMentionsPanel(pc)
280
318
  };
281
319
  }
282
320
  function injectMenuItems(attrName, root = document, pc) {
@@ -348,6 +386,14 @@ function findActiveTrigger(attrName, pc) {
348
386
  const found = resolveTriggerInContainer(cascader, attrName);
349
387
  if (found) return found;
350
388
  }
389
+ const mentionsDropdown = document.querySelector(sel(pc, "mentions-dropdown"));
390
+ if (mentionsDropdown) {
391
+ const mentionsRoot = document.querySelector(sel(pc, "mentions"));
392
+ if (mentionsRoot) {
393
+ const found = resolveTriggerInContainer(mentionsRoot, attrName);
394
+ if (found) return found;
395
+ }
396
+ }
351
397
  const expanded = document.querySelector(
352
398
  `[id][aria-expanded="true"], [data-testid][aria-expanded="true"], [data-testId][aria-expanded="true"]`
353
399
  );
@@ -480,6 +526,150 @@ function injectTabsSubElements(root, attrName, pc) {
480
526
  }
481
527
  }
482
528
  }
529
+ function injectPaginationSubElements(root, attrName, pc) {
530
+ const paginations = root.matches(`${sel(pc, "pagination")}[${attrName}]`) ? [root] : Array.from(root.querySelectorAll(`${sel(pc, "pagination")}[${attrName}]`));
531
+ for (const pagination of paginations) {
532
+ if (!(pagination instanceof HTMLElement)) continue;
533
+ const testId = getAttrCompat(pagination, attrName) || "";
534
+ if (!testId) continue;
535
+ const prevEl = pagination.querySelector(sel(pc, "pagination-prev"));
536
+ if (prevEl && !hasAttrCompat(prevEl, attrName)) {
537
+ prevEl.setAttribute(attrName, `${testId}-prev`);
538
+ }
539
+ const nextEl = pagination.querySelector(sel(pc, "pagination-next"));
540
+ if (nextEl && !hasAttrCompat(nextEl, attrName)) {
541
+ nextEl.setAttribute(attrName, `${testId}-next`);
542
+ }
543
+ pagination.querySelectorAll(sel(pc, "pagination-item")).forEach((item) => {
544
+ const el = item;
545
+ if (hasAttrCompat(el, attrName)) return;
546
+ const pageNum = el.textContent?.trim() || "";
547
+ if (pageNum) {
548
+ el.setAttribute(attrName, `${testId}-item-${pageNum}`);
549
+ }
550
+ });
551
+ const jumpPrev = pagination.querySelector(sel(pc, "pagination-jump-prev"));
552
+ if (jumpPrev && !hasAttrCompat(jumpPrev, attrName)) {
553
+ jumpPrev.setAttribute(attrName, `${testId}-jump-prev`);
554
+ }
555
+ const jumpNext = pagination.querySelector(sel(pc, "pagination-jump-next"));
556
+ if (jumpNext && !hasAttrCompat(jumpNext, attrName)) {
557
+ jumpNext.setAttribute(attrName, `${testId}-jump-next`);
558
+ }
559
+ const sizeChanger = pagination.querySelector(sel(pc, "pagination-options-size-changer"));
560
+ if (sizeChanger && !hasAttrCompat(sizeChanger, attrName)) {
561
+ sizeChanger.setAttribute(attrName, `${testId}-size-changer`);
562
+ }
563
+ const quickJumper = pagination.querySelector(sel(pc, "pagination-options-quick-jumper"));
564
+ if (quickJumper) {
565
+ const tryQuickJumperInput = () => {
566
+ const input = quickJumper.querySelector("input");
567
+ if (input && !hasAttrCompat(input, attrName)) {
568
+ input.setAttribute(attrName, `${testId}-quick-jumper`);
569
+ return true;
570
+ }
571
+ return false;
572
+ };
573
+ if (!tryQuickJumperInput()) {
574
+ const subObs = new MutationObserver(() => {
575
+ if (tryQuickJumperInput()) subObs.disconnect();
576
+ });
577
+ subObs.observe(quickJumper, { childList: true, subtree: true });
578
+ }
579
+ }
580
+ const totalText = pagination.querySelector(sel(pc, "pagination-total-text"));
581
+ if (totalText && !hasAttrCompat(totalText, attrName)) {
582
+ totalText.setAttribute(attrName, `${testId}-total`);
583
+ }
584
+ }
585
+ }
586
+ function injectAutoCompleteSubElements(root, attrName, pc) {
587
+ const wrappers = root.matches(`${sel(pc, "select-auto-complete")}[${attrName}]`) ? [root] : Array.from(root.querySelectorAll(`${sel(pc, "select-auto-complete")}[${attrName}]`));
588
+ for (const wrapper of wrappers) {
589
+ if (!(wrapper instanceof HTMLElement)) continue;
590
+ const testId = getAttrCompat(wrapper, attrName) || "";
591
+ if (!testId) continue;
592
+ const searchInput = wrapper.querySelector(
593
+ `${sel(pc, "select-selection-search")} input`
594
+ );
595
+ if (searchInput && !hasAttrCompat(searchInput, attrName)) {
596
+ searchInput.setAttribute(attrName, `${testId}-input`);
597
+ }
598
+ const clearBtn = wrapper.querySelector(sel(pc, "select-clear"));
599
+ if (clearBtn && !hasAttrCompat(clearBtn, attrName)) {
600
+ clearBtn.setAttribute(attrName, `${testId}-clear`);
601
+ }
602
+ }
603
+ }
604
+ function injectMentionsSubElements(root, attrName, pc) {
605
+ const mentions = root.matches(`${sel(pc, "mentions")}[${attrName}]`) ? [root] : Array.from(root.querySelectorAll(`${sel(pc, "mentions")}[${attrName}]`));
606
+ for (const wrapper of mentions) {
607
+ if (!(wrapper instanceof HTMLElement)) continue;
608
+ const testId = getAttrCompat(wrapper, attrName) || "";
609
+ if (!testId) continue;
610
+ const textarea = wrapper.querySelector(":scope > textarea");
611
+ if (textarea && !hasAttrCompat(textarea, attrName)) {
612
+ textarea.setAttribute(attrName, `${testId}-input`);
613
+ }
614
+ }
615
+ }
616
+ function injectTransferSubElements(root, attrName, pc) {
617
+ const transfers = root.matches(`${sel(pc, "transfer")}[${attrName}]`) ? [root] : Array.from(root.querySelectorAll(`${sel(pc, "transfer")}[${attrName}]`));
618
+ for (const transfer of transfers) {
619
+ if (!(transfer instanceof HTMLElement)) continue;
620
+ const testId = getAttrCompat(transfer, attrName) || "";
621
+ if (!testId) continue;
622
+ const lists = transfer.querySelectorAll(`:scope > ${sel(pc, "transfer-list")}`);
623
+ const directions = ["left", "right"];
624
+ for (let i = 0; i < Math.min(lists.length, 2); i++) {
625
+ const list = lists[i];
626
+ const dir = directions[i];
627
+ const listTestId = `${testId}-list-${dir}`;
628
+ if (!hasAttrCompat(list, attrName)) {
629
+ list.setAttribute(attrName, listTestId);
630
+ }
631
+ const headerCheckbox = list.querySelector(
632
+ `${sel(pc, "transfer-list-header")} ${sel(pc, "transfer-list-checkbox")}`
633
+ );
634
+ if (headerCheckbox && !hasAttrCompat(headerCheckbox, attrName)) {
635
+ headerCheckbox.setAttribute(attrName, `${listTestId}-check-all`);
636
+ }
637
+ const searchInput = list.querySelector(
638
+ `${sel(pc, "transfer-list-body-search-wrapper")} input`
639
+ );
640
+ if (searchInput && !hasAttrCompat(searchInput, attrName)) {
641
+ searchInput.setAttribute(attrName, `${listTestId}-search`);
642
+ }
643
+ const items = list.querySelectorAll(sel(pc, "transfer-list-content-item"));
644
+ items.forEach((item) => {
645
+ const el = item;
646
+ if (hasAttrCompat(el, attrName)) return;
647
+ const text = (el.querySelector(sel(pc, "transfer-list-content-item-text"))?.textContent || el.textContent || "").trim().replace(/\s+/g, "-");
648
+ if (text) {
649
+ el.setAttribute(attrName, `${listTestId}-item-${text}`);
650
+ }
651
+ const checkbox = el.querySelector(
652
+ sel(pc, "transfer-list-checkbox")
653
+ );
654
+ if (checkbox && !hasAttrCompat(checkbox, attrName) && text) {
655
+ checkbox.setAttribute(attrName, `${listTestId}-item-check-${text}`);
656
+ }
657
+ const removeBtn = el.querySelector(
658
+ sel(pc, "transfer-list-content-item-remove")
659
+ );
660
+ if (removeBtn && !hasAttrCompat(removeBtn, attrName) && text) {
661
+ removeBtn.setAttribute(attrName, `${listTestId}-item-remove-${text}`);
662
+ }
663
+ });
664
+ }
665
+ const operation = transfer.querySelector(
666
+ `:scope > ${sel(pc, "transfer-operation")}`
667
+ );
668
+ if (operation && !hasAttrCompat(operation, attrName)) {
669
+ operation.setAttribute(attrName, `${testId}-operation`);
670
+ }
671
+ }
672
+ }
483
673
  function setupAntTestIds(opts = {}) {
484
674
  const attrName = opts.attributeName ?? "data-testid";
485
675
  const pc = opts.prefixCls ?? "ant";
@@ -507,6 +697,10 @@ function setupAntTestIds(opts = {}) {
507
697
  }
508
698
  injectSubElements(node, attrName, pc);
509
699
  injectTabsSubElements(node, attrName, pc);
700
+ injectPaginationSubElements(node, attrName, pc);
701
+ injectAutoCompleteSubElements(node, attrName, pc);
702
+ injectMentionsSubElements(node, attrName, pc);
703
+ injectTransferSubElements(node, attrName, pc);
510
704
  }
511
705
  let pending = false;
512
706
  let pendingNodes = [];
@@ -558,7 +752,11 @@ function injectCurrentDropdowns(opts = {}) {
558
752
  }
559
753
  injectSubElements(document.body, attrName, pc);
560
754
  injectTabsSubElements(document.body, attrName, pc);
755
+ injectPaginationSubElements(document.body, attrName, pc);
561
756
  injectMenuItems(attrName, document, pc);
757
+ injectAutoCompleteSubElements(document.body, attrName, pc);
758
+ injectMentionsSubElements(document.body, attrName, pc);
759
+ injectTransferSubElements(document.body, attrName, pc);
562
760
  }
563
761
  function mergeMatchers(defaults, customs) {
564
762
  if (!customs) return defaults;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vite-plugin-vue-testid",
3
- "version": "1.1.3",
3
+ "version": "1.1.4",
4
4
  "description": "Vite plugin to auto-inject data-testid into Vue component templates for E2E testing",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -14,6 +14,10 @@
14
14
  "./runtime": {
15
15
  "import": "./dist/runtime.js",
16
16
  "types": "./dist/runtime.d.ts"
17
+ },
18
+ "./plugin": {
19
+ "import": "./dist/plugin.js",
20
+ "types": "./dist/plugin.d.ts"
17
21
  }
18
22
  },
19
23
  "files": [