vite-plugin-vue-testid 1.0.9 → 1.0.11

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/runtime.d.ts CHANGED
@@ -8,6 +8,9 @@
8
8
  *
9
9
  * createApp(App).mount('#app')
10
10
  * setupAntTestIds()
11
+ *
12
+ * // 支持自定义 prefixCls(对应 ant-design-vue ConfigProvider 的 prefixCls):
13
+ * setupAntTestIds({ prefixCls: 'custom' })
11
14
  * ```
12
15
  */
13
16
  export interface RuntimeOptions {
@@ -16,6 +19,11 @@ export interface RuntimeOptions {
16
19
  * @default 'data-testid'
17
20
  */
18
21
  attributeName?: string;
22
+ /**
23
+ * ant-design-vue CSS 类名前缀,对应 ConfigProvider 的 prefixCls
24
+ * @default 'ant'
25
+ */
26
+ prefixCls?: string;
19
27
  /**
20
28
  * 自定义组件 CSS 选择器 → testid 前缀映射。
21
29
  * 会与默认映射合并(自定义覆盖默认)。
@@ -56,7 +64,8 @@ export type PanelInjectStrategy = (ctx: PanelContext) => void;
56
64
  * - 所有 ant-design-vue 组件根元素(input / select / button / picker / ...)
57
65
  * - teleport 弹出层面板(日期选择器 / 级联选择器 / 下拉选择器)
58
66
  * - Menu 子元素(inheritAttrs:false 兜底)
59
- * - Upload 内部 <input type="file">
67
+ * - InputNumber 内部输入框及增减按钮
68
+ * - Slider handle、Rate 星星、Upload 内部 file input
60
69
  *
61
70
  * @returns 清理函数,调用后停止监听
62
71
  *
@@ -67,12 +76,14 @@ export type PanelInjectStrategy = (ctx: PanelContext) => void;
67
76
  *
68
77
  * createApp(App).mount('#app')
69
78
  * setupAntTestIds()
79
+ *
80
+ * // 配合 ConfigProvider 自定义 prefixCls:
81
+ * setupAntTestIds({ prefixCls: 'custom' })
70
82
  * ```
71
83
  */
72
84
  export declare function setupAntTestIds(opts?: RuntimeOptions): () => void;
73
85
  /**
74
86
  * @deprecated 使用 {@link setupAntTestIds} 替代。
75
- * 保留此别名以兼容现有代码。
76
87
  */
77
88
  export declare const setupAntDropdownTestIds: typeof setupAntTestIds;
78
89
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IAEtB;;;;;;;;;;;;;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;AA6c7D;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,eAAe,CAAC,IAAI,GAAE,cAAmB,GAAG,MAAM,IAAI,CA4KrE;AAED;;;GAGG;AACH,eAAO,MAAM,uBAAuB,wBAAkB,CAAA;AAMtD;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,GAAE,cAAmB,GAAG,IAAI,CA4GtE"}
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;AAsjB7D;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,eAAe,CAAC,IAAI,GAAE,cAAmB,GAAG,MAAM,IAAI,CA6ErE;AAED;;GAEG;AACH,eAAO,MAAM,uBAAuB,wBAAkB,CAAA;AAMtD;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,GAAE,cAAmB,GAAG,IAAI,CAgCtE"}
package/dist/runtime.js CHANGED
@@ -1,60 +1,65 @@
1
1
  // src/runtime.ts
2
- var DEFAULT_COMPONENT_MATCHERS = [
3
- // ── 输入框变体(按 specificity 降序)──
4
- { selector: ".ant-input-search", prefix: "a-input-search" },
5
- { selector: ".ant-input-affix-wrapper", prefix: "a-input" },
6
- { selector: ".ant-input-textarea", prefix: "a-textarea" },
7
- { selector: ".ant-input-number", prefix: "a-input-number" },
8
- // ── 选择器 ──
9
- { selector: ".ant-select", prefix: "a-select", withId: true },
10
- { selector: ".ant-cascader", prefix: "a-cascader", withId: true },
11
- { selector: ".ant-tree-select", prefix: "a-tree-select" },
12
- // ── 日期/时间 ──
13
- { selector: ".ant-picker", prefix: "a-date-picker", withId: true },
14
- // ── 表单组件 ──
15
- { selector: ".ant-radio-group", prefix: "a-radio-group" },
16
- { selector: ".ant-radio-wrapper", prefix: "a-radio" },
17
- { selector: ".ant-checkbox-group", prefix: "a-checkbox-group" },
18
- { selector: ".ant-checkbox-wrapper", prefix: "a-checkbox" },
19
- { selector: ".ant-switch", prefix: "a-switch" },
20
- { selector: ".ant-slider", prefix: "a-slider" },
21
- { selector: ".ant-rate", prefix: "a-rate" },
22
- { selector: ".ant-form-item", prefix: "a-form-item" },
23
- // ── 按钮 & 上传 ──
24
- { selector: ".ant-btn", prefix: "a-button" },
25
- { selector: ".ant-upload", prefix: "a-upload", withId: true },
26
- // ── 弹窗 ──
27
- { selector: ".ant-modal-wrap", prefix: "a-modal", withId: true },
28
- // ── 菜单 ──
29
- { selector: ".ant-menu", prefix: "a-menu" },
30
- // ── input / textarea(必须排在最后)──
31
- // 只有不在其他组件包裹内时才注入(通过 closest 排除)
32
- { selector: "input.ant-input", prefix: "a-input" },
33
- { selector: "textarea.ant-input", prefix: "a-textarea" }
34
- ];
35
- var SKIP_PARENT_SELECTORS = [
36
- ".ant-input-affix-wrapper",
37
- ".ant-input-textarea",
38
- ".ant-input-search",
39
- ".ant-input-number",
40
- ".ant-picker",
41
- ".ant-select",
42
- ".ant-cascader"
43
- ];
44
- function isInnerInput(el) {
2
+ var sel = (pc, name) => `.${pc}-${name}`;
3
+ var cls = (pc, name) => `${pc}-${name}`;
4
+ function buildComponentMatchers(pc) {
5
+ return [
6
+ // ── 输入框变体(按 specificity 降序)──
7
+ { selector: sel(pc, "input-search"), prefix: "a-input-search" },
8
+ { selector: sel(pc, "input-affix-wrapper"), prefix: "a-input" },
9
+ { selector: sel(pc, "input-textarea"), prefix: "a-textarea" },
10
+ { selector: sel(pc, "input-number"), prefix: "a-input-number" },
11
+ // ── 选择器 ──
12
+ { selector: sel(pc, "select"), prefix: "a-select", withId: true },
13
+ { selector: sel(pc, "cascader"), prefix: "a-cascader", withId: true },
14
+ { selector: sel(pc, "tree-select"), prefix: "a-tree-select" },
15
+ // ── 日期/时间 ──
16
+ { selector: sel(pc, "picker"), prefix: "a-date-picker", withId: true },
17
+ // ── 表单组件 ──
18
+ { selector: sel(pc, "radio-group"), prefix: "a-radio-group" },
19
+ { selector: sel(pc, "radio-wrapper"), prefix: "a-radio" },
20
+ { selector: sel(pc, "checkbox-group"), prefix: "a-checkbox-group" },
21
+ { selector: sel(pc, "checkbox-wrapper"), prefix: "a-checkbox" },
22
+ { selector: sel(pc, "switch"), prefix: "a-switch" },
23
+ { selector: sel(pc, "slider"), prefix: "a-slider" },
24
+ { selector: sel(pc, "rate"), prefix: "a-rate" },
25
+ { selector: sel(pc, "form-item"), prefix: "a-form-item" },
26
+ // ── 按钮 & 上传 ──
27
+ { selector: sel(pc, "btn"), prefix: "a-button" },
28
+ { selector: sel(pc, "upload"), prefix: "a-upload", withId: true },
29
+ // ── 弹窗 ──
30
+ { selector: sel(pc, "modal-wrap"), prefix: "a-modal", withId: true },
31
+ // ── 菜单 ──
32
+ { selector: sel(pc, "menu"), prefix: "a-menu" },
33
+ // ── input / textarea(必须排在最后)──
34
+ { selector: `input.${cls(pc, "input")}`, prefix: "a-input" },
35
+ { selector: `textarea.${cls(pc, "input")}`, prefix: "a-textarea" }
36
+ ];
37
+ }
38
+ function buildSkipSelectors(pc) {
39
+ return [
40
+ sel(pc, "input-affix-wrapper"),
41
+ sel(pc, "input-textarea"),
42
+ sel(pc, "input-search"),
43
+ sel(pc, "input-number"),
44
+ sel(pc, "picker"),
45
+ sel(pc, "select"),
46
+ sel(pc, "cascader")
47
+ ];
48
+ }
49
+ function isInnerInput(el, skipSelectors) {
45
50
  if (el.tagName !== "INPUT" && el.tagName !== "TEXTAREA") return false;
46
- for (const sel of SKIP_PARENT_SELECTORS) {
47
- if (el.closest(sel)) return true;
51
+ for (const s of skipSelectors) {
52
+ if (el.closest(s)) return true;
48
53
  }
49
54
  return false;
50
55
  }
51
- function injectComponentRoots(root, matchers, attrName, counter) {
56
+ function injectComponentRoots(root, matchers, attrName, counter, skipSelectors) {
52
57
  for (const { selector, prefix, withId } of matchers) {
53
58
  const candidates = root.matches(selector) ? [root] : Array.from(root.querySelectorAll(selector));
54
59
  for (const el of candidates) {
55
60
  if (!(el instanceof HTMLElement)) continue;
56
61
  if (el.hasAttribute(attrName)) continue;
57
- if (isInnerInput(el)) continue;
62
+ if (isInnerInput(el, skipSelectors)) continue;
58
63
  const testId = `${prefix}-${counter.value++}`;
59
64
  el.setAttribute(attrName, testId);
60
65
  if (withId && !el.hasAttribute("id")) {
@@ -63,12 +68,6 @@ function injectComponentRoots(root, matchers, attrName, counter) {
63
68
  }
64
69
  }
65
70
  }
66
- function getPanelMode(panel) {
67
- if (panel.classList.contains("ant-picker-month-panel")) return "month";
68
- if (panel.classList.contains("ant-picker-year-panel")) return "year";
69
- if (panel.classList.contains("ant-picker-decade-panel")) return "decade";
70
- return "date";
71
- }
72
71
  var MONTH_NAMES = [
73
72
  "jan",
74
73
  "feb",
@@ -83,8 +82,14 @@ var MONTH_NAMES = [
83
82
  "nov",
84
83
  "dec"
85
84
  ];
86
- function injectHeaderViewButtons(panel, prefix, attrName, suffix) {
87
- const headerView = panel.querySelector(".ant-picker-header-view");
85
+ function getPanelMode(panel, pc) {
86
+ if (panel.classList.contains(cls(pc, "picker-month-panel"))) return "month";
87
+ if (panel.classList.contains(cls(pc, "picker-year-panel"))) return "year";
88
+ if (panel.classList.contains(cls(pc, "picker-decade-panel"))) return "decade";
89
+ return "date";
90
+ }
91
+ function injectHeaderViewButtons(panel, prefix, attrName, suffix, pc) {
92
+ const headerView = panel.querySelector(sel(pc, "picker-header-view"));
88
93
  if (!headerView) return;
89
94
  const buttons = headerView.querySelectorAll("button");
90
95
  buttons.forEach((btn) => {
@@ -106,18 +111,16 @@ function injectHeaderViewButtons(panel, prefix, attrName, suffix) {
106
111
  el.setAttribute(attrName, `${prefix}-${name}${suffix}`);
107
112
  });
108
113
  }
109
- function injectPanelCells(panel, prefix, attrName, suffix) {
110
- const mode = getPanelMode(panel);
111
- const cells = panel.querySelectorAll(".ant-picker-cell-inner");
114
+ function injectPanelCells(panel, prefix, attrName, suffix, pc) {
115
+ const mode = getPanelMode(panel, pc);
116
+ const cells = panel.querySelectorAll(sel(pc, "picker-cell-inner"));
112
117
  cells.forEach((cell) => {
113
118
  const el = cell;
114
119
  if (el.hasAttribute(attrName)) return;
115
120
  const raw = (el.getAttribute("title") || el.textContent?.trim() || "").replace(/\s+/g, "-");
116
- let label;
117
121
  switch (mode) {
118
122
  case "month":
119
- label = raw.length >= 2 ? raw.slice(-2) : raw;
120
- el.setAttribute(attrName, `${prefix}-month-${label}${suffix}`);
123
+ el.setAttribute(attrName, `${prefix}-month-${raw.length >= 2 ? raw.slice(-2) : raw}${suffix}`);
121
124
  break;
122
125
  case "year":
123
126
  el.setAttribute(attrName, `${prefix}-year-${raw}${suffix}`);
@@ -131,145 +134,159 @@ function injectPanelCells(panel, prefix, attrName, suffix) {
131
134
  }
132
135
  });
133
136
  }
134
- function injectSinglePanel(panel, prefix, attrName, suffix) {
135
- const mode = getPanelMode(panel);
137
+ function injectSinglePanel(panel, prefix, attrName, suffix, pc) {
138
+ const mode = getPanelMode(panel, pc);
136
139
  const setAttr = (selector, name) => {
137
140
  const el = panel.querySelector(selector);
138
141
  if (el && !el.hasAttribute(attrName)) {
139
142
  el.setAttribute(attrName, `${prefix}-${name}${suffix}`);
140
143
  }
141
144
  };
142
- setAttr(".ant-picker-header-super-prev-btn", "super-prev");
143
- setAttr(".ant-picker-header-prev-btn", "prev");
144
- setAttr(".ant-picker-header-next-btn", "next");
145
- setAttr(".ant-picker-header-super-next-btn", "super-next");
146
- setAttr(".ant-picker-header-view", `header-${mode}`);
147
- injectHeaderViewButtons(panel, prefix, attrName, suffix);
148
- injectPanelCells(panel, prefix, attrName, suffix);
145
+ setAttr(sel(pc, "picker-header-super-prev-btn"), "super-prev");
146
+ setAttr(sel(pc, "picker-header-prev-btn"), "prev");
147
+ setAttr(sel(pc, "picker-header-next-btn"), "next");
148
+ setAttr(sel(pc, "picker-header-super-next-btn"), "super-next");
149
+ setAttr(sel(pc, "picker-header-view"), `header-${mode}`);
150
+ injectHeaderViewButtons(panel, prefix, attrName, suffix, pc);
151
+ injectPanelCells(panel, prefix, attrName, suffix, pc);
149
152
  }
150
- function injectPickerPanel(ctx) {
151
- const { container, triggerTestId, attrName } = ctx;
152
- const prefix = `${triggerTestId}-dropdown`;
153
- container.setAttribute(attrName, prefix);
154
- const isRange = container.classList.contains("ant-picker-dropdown-range");
155
- const injectAllPanels = () => {
156
- const panels = container.querySelectorAll(".ant-picker-panel");
157
- if (isRange && panels.length >= 2) {
158
- injectSinglePanel(panels[0], prefix, attrName, "-left");
159
- injectSinglePanel(panels[1], prefix, attrName, "-right");
160
- } else {
161
- const panel = panels[0] || container;
162
- injectSinglePanel(panel, prefix, attrName, "");
153
+ function makeInjectPickerPanel(pc) {
154
+ return (ctx) => {
155
+ const { container, triggerTestId, attrName } = ctx;
156
+ const prefix = `${triggerTestId}-dropdown`;
157
+ container.setAttribute(attrName, prefix);
158
+ const isRange = container.classList.contains(cls(pc, "picker-dropdown-range"));
159
+ const injectAllPanels = () => {
160
+ const panels = container.querySelectorAll(sel(pc, "picker-panel"));
161
+ if (isRange && panels.length >= 2) {
162
+ injectSinglePanel(panels[0], prefix, attrName, "-left", pc);
163
+ injectSinglePanel(panels[1], prefix, attrName, "-right", pc);
164
+ } else {
165
+ const panel = panels[0] || container;
166
+ injectSinglePanel(panel, prefix, attrName, "", pc);
167
+ }
168
+ const presets = container.querySelectorAll(`${sel(pc, "picker-presets")} .${cls(pc, "tag")}`);
169
+ presets.forEach((tag) => {
170
+ const el = tag;
171
+ if (el.hasAttribute(attrName)) return;
172
+ const text = el.textContent?.trim() || "";
173
+ el.setAttribute(attrName, `${prefix}-preset-${text}`);
174
+ });
175
+ const timeColumns = container.querySelectorAll(sel(pc, "picker-time-panel-column"));
176
+ timeColumns.forEach((col, i) => {
177
+ const el = col;
178
+ if (el.hasAttribute(attrName)) return;
179
+ el.setAttribute(attrName, `${prefix}-time-column-${i}`);
180
+ const items = col.querySelectorAll("li");
181
+ items.forEach((item) => {
182
+ const li = item;
183
+ if (li.hasAttribute(attrName)) return;
184
+ const text = li.textContent?.trim() || "";
185
+ li.setAttribute(attrName, `${prefix}-time-${i}-${text}`);
186
+ });
187
+ });
188
+ const setFooterBtn = (selector, name) => {
189
+ const btn = container.querySelector(selector);
190
+ if (btn && !btn.hasAttribute(attrName)) {
191
+ btn.setAttribute(attrName, `${prefix}-${name}`);
192
+ }
193
+ };
194
+ setFooterBtn(`${sel(pc, "picker-ok")} button`, "ok");
195
+ setFooterBtn(`${sel(pc, "picker-now")} button`, "now");
196
+ setFooterBtn(sel(pc, "picker-now-btn"), "now");
197
+ };
198
+ injectAllPanels();
199
+ if (!_observedContainers.has(container)) {
200
+ _observedContainers.add(container);
201
+ let timer = null;
202
+ const observer = new MutationObserver(() => {
203
+ if (timer !== null) clearTimeout(timer);
204
+ timer = setTimeout(() => {
205
+ injectAllPanels();
206
+ }, 50);
207
+ });
208
+ observer.observe(container, { childList: true, subtree: true });
163
209
  }
164
- const presets = container.querySelectorAll(".ant-picker-presets .ant-tag");
165
- presets.forEach((tag) => {
166
- const el = tag;
167
- if (el.hasAttribute(attrName)) return;
168
- const text = el.textContent?.trim() || "";
169
- el.setAttribute(attrName, `${prefix}-preset-${text}`);
170
- });
171
- const timeColumns = container.querySelectorAll(".ant-picker-time-panel-column");
172
- timeColumns.forEach((col, i) => {
173
- const el = col;
174
- if (el.hasAttribute(attrName)) return;
175
- el.setAttribute(attrName, `${prefix}-time-column-${i}`);
176
- const items = col.querySelectorAll("li");
210
+ };
211
+ }
212
+ function makeInjectCascaderPanel(pc) {
213
+ return (ctx) => {
214
+ const { container, triggerTestId, attrName } = ctx;
215
+ const prefix = `${triggerTestId}-dropdown`;
216
+ container.setAttribute(attrName, prefix);
217
+ const menus = container.querySelectorAll(sel(pc, "cascader-menu"));
218
+ menus.forEach((menu, i) => {
219
+ const el = menu;
220
+ el.setAttribute(attrName, `${prefix}-menu-${i}`);
221
+ const items = menu.querySelectorAll(sel(pc, "cascader-menu-item"));
177
222
  items.forEach((item) => {
178
223
  const li = item;
179
- if (li.hasAttribute(attrName)) return;
180
224
  const text = li.textContent?.trim() || "";
181
- li.setAttribute(attrName, `${prefix}-time-${i}-${text}`);
225
+ li.setAttribute(attrName, `${prefix}-item-${text}`);
182
226
  });
183
227
  });
184
- const setFooterBtn = (selector, name) => {
185
- const btn = container.querySelector(selector);
186
- if (btn && !btn.hasAttribute(attrName)) {
187
- btn.setAttribute(attrName, `${prefix}-${name}`);
188
- }
189
- };
190
- setFooterBtn(".ant-picker-ok button", "ok");
191
- setFooterBtn(".ant-picker-now button", "now");
192
- setFooterBtn(".ant-picker-now-btn", "now");
193
228
  };
194
- injectAllPanels();
195
- if (!_observedContainers.has(container)) {
196
- _observedContainers.add(container);
197
- let timer = null;
198
- const observer = new MutationObserver(() => {
199
- if (timer !== null) clearTimeout(timer);
200
- timer = setTimeout(() => {
201
- injectAllPanels();
202
- }, 50);
203
- });
204
- observer.observe(container, { childList: true, subtree: true });
205
- }
206
229
  }
207
- function injectCascaderPanel(ctx) {
208
- const { container, triggerTestId, attrName } = ctx;
209
- const prefix = `${triggerTestId}-dropdown`;
210
- container.setAttribute(attrName, prefix);
211
- const menus = container.querySelectorAll(".ant-cascader-menu");
212
- menus.forEach((menu, i) => {
213
- const el = menu;
214
- el.setAttribute(attrName, `${prefix}-menu-${i}`);
215
- const items = menu.querySelectorAll(".ant-cascader-menu-item");
216
- items.forEach((item) => {
217
- const li = item;
218
- const text = li.textContent?.trim() || "";
219
- li.setAttribute(attrName, `${prefix}-item-${text}`);
220
- });
221
- });
230
+ function makeInjectSelectPanel(pc) {
231
+ return (ctx) => {
232
+ const { container, triggerTestId, attrName } = ctx;
233
+ const prefix = `${triggerTestId}-dropdown`;
234
+ container.setAttribute(attrName, prefix);
235
+ const injectAllItems = () => {
236
+ const options = container.querySelectorAll(sel(pc, "select-item-option"));
237
+ options.forEach((opt) => {
238
+ const el = opt;
239
+ if (el.hasAttribute(attrName)) return;
240
+ const title = el.getAttribute("title") || el.textContent?.trim() || "";
241
+ el.setAttribute(attrName, `${prefix}-option-${title}`);
242
+ });
243
+ const treeNodes = container.querySelectorAll(sel(pc, "select-tree-treenode"));
244
+ treeNodes.forEach((node) => {
245
+ const el = node;
246
+ const content = el.querySelector(sel(pc, "select-tree-node-content-wrapper"));
247
+ const title = content?.getAttribute("title") || content?.textContent?.trim() || "";
248
+ if (content && !content.hasAttribute(attrName)) {
249
+ content.setAttribute(attrName, `${prefix}-tree-${title}`);
250
+ }
251
+ const switcher = el.querySelector(sel(pc, "select-tree-switcher"));
252
+ if (switcher && !switcher.hasAttribute(attrName)) {
253
+ const isOpen = switcher.classList.contains(cls(pc, "select-tree-switcher_open"));
254
+ const state = isOpen ? "collapse" : "expand";
255
+ switcher.setAttribute(attrName, `${prefix}-tree-switcher-${state}-${title}`);
256
+ }
257
+ });
258
+ };
259
+ injectAllItems();
260
+ if (!_observedContainers.has(container)) {
261
+ _observedContainers.add(container);
262
+ let timer = null;
263
+ const observer = new MutationObserver(() => {
264
+ if (timer !== null) clearTimeout(timer);
265
+ timer = setTimeout(() => {
266
+ injectAllItems();
267
+ }, 50);
268
+ });
269
+ observer.observe(container, { childList: true, subtree: true });
270
+ }
271
+ };
222
272
  }
223
- function injectSelectPanel(ctx) {
224
- const { container, triggerTestId, attrName } = ctx;
225
- const prefix = `${triggerTestId}-dropdown`;
226
- container.setAttribute(attrName, prefix);
227
- const injectAllItems = () => {
228
- const options = container.querySelectorAll(".ant-select-item-option");
229
- options.forEach((opt) => {
230
- const el = opt;
231
- if (el.hasAttribute(attrName)) return;
232
- const title = el.getAttribute("title") || el.textContent?.trim() || "";
233
- el.setAttribute(attrName, `${prefix}-option-${title}`);
234
- });
235
- const treeNodes = container.querySelectorAll(".ant-select-tree-treenode");
236
- treeNodes.forEach((node) => {
237
- const el = node;
238
- const content = el.querySelector(".ant-select-tree-node-content-wrapper");
239
- const title = content?.getAttribute("title") || content?.textContent?.trim() || "";
240
- if (content && !content.hasAttribute(attrName)) {
241
- content.setAttribute(attrName, `${prefix}-tree-${title}`);
242
- }
243
- const switcher = el.querySelector(".ant-select-tree-switcher");
244
- if (switcher && !switcher.hasAttribute(attrName)) {
245
- const isOpen = switcher.classList.contains("ant-select-tree-switcher_open");
246
- const state = isOpen ? "collapse" : "expand";
247
- switcher.setAttribute(attrName, `${prefix}-tree-switcher-${state}-${title}`);
248
- }
249
- });
273
+ function buildPanels(pc) {
274
+ return {
275
+ [sel(pc, "picker-dropdown")]: makeInjectPickerPanel(pc),
276
+ [sel(pc, "cascader-dropdown")]: makeInjectCascaderPanel(pc),
277
+ [sel(pc, "select-dropdown")]: makeInjectSelectPanel(pc)
250
278
  };
251
- injectAllItems();
252
- if (!_observedContainers.has(container)) {
253
- _observedContainers.add(container);
254
- let timer = null;
255
- const observer = new MutationObserver(() => {
256
- if (timer !== null) clearTimeout(timer);
257
- timer = setTimeout(() => {
258
- injectAllItems();
259
- }, 50);
260
- });
261
- observer.observe(container, { childList: true, subtree: true });
262
- }
263
279
  }
264
- function injectMenuItems(attrName, root = document) {
280
+ function injectMenuItems(attrName, root = document, pc) {
265
281
  const itemRules = [
266
- { selector: ".ant-menu-item", prefix: "a-menu-item-" },
267
- { selector: ".ant-menu-submenu", textSelector: ".ant-menu-submenu-title", prefix: "a-sub-menu-" },
268
- { selector: ".ant-menu-item-group", textSelector: ".ant-menu-item-group-title", prefix: "a-menu-item-group-" }
282
+ { selector: sel(pc, "menu-item"), prefix: "a-menu-item-" },
283
+ { selector: sel(pc, "menu-submenu"), textSelector: sel(pc, "menu-submenu-title"), prefix: "a-sub-menu-" },
284
+ { selector: sel(pc, "menu-item-group"), textSelector: sel(pc, "menu-item-group-title"), prefix: "a-menu-item-group-" }
269
285
  ];
270
286
  for (const rule of itemRules) {
271
- const items = root.querySelectorAll(rule.selector);
272
- items.forEach((item) => {
287
+ const descendants = Array.from(root.querySelectorAll(rule.selector));
288
+ const items = root instanceof HTMLElement && root.matches(rule.selector) ? [root, ...descendants] : descendants;
289
+ for (const item of items) {
273
290
  const el = item;
274
291
  if (el.hasAttribute(attrName)) return;
275
292
  const id = el.getAttribute("id");
@@ -282,21 +299,16 @@ function injectMenuItems(attrName, root = document) {
282
299
  if (text) {
283
300
  el.setAttribute(attrName, rule.prefix + text);
284
301
  }
285
- });
302
+ }
286
303
  }
287
304
  }
288
- var DEFAULT_PANELS = {
289
- ".ant-picker-dropdown": injectPickerPanel,
290
- ".ant-cascader-dropdown": injectCascaderPanel,
291
- ".ant-select-dropdown": injectSelectPanel
292
- };
293
305
  var _observedContainers = /* @__PURE__ */ new WeakSet();
294
306
  function getTriggerTestId(trigger, attrName) {
295
307
  if (!trigger) return "unknown";
296
308
  return trigger.getAttribute(attrName) || trigger.getAttribute("id") || "unknown";
297
309
  }
298
- function findActiveTrigger(attrName) {
299
- const focused = document.querySelector(".ant-picker-focused");
310
+ function findActiveTrigger(attrName, pc) {
311
+ const focused = document.querySelector(sel(pc, "picker-focused"));
300
312
  if (focused) {
301
313
  if (focused.hasAttribute(attrName)) return focused;
302
314
  const inner = focused.querySelector(`[${attrName}]`);
@@ -304,9 +316,9 @@ function findActiveTrigger(attrName) {
304
316
  const input = focused.querySelector("input[id]");
305
317
  if (input) return input;
306
318
  }
307
- const selectOpen = document.querySelector(".ant-select-open");
319
+ const selectOpen = document.querySelector(sel(pc, "select-open"));
308
320
  if (selectOpen?.hasAttribute(attrName)) return selectOpen;
309
- const cascader = document.querySelector(".ant-cascader-picker-focused");
321
+ const cascader = document.querySelector(sel(pc, "cascader-picker-focused"));
310
322
  if (cascader?.hasAttribute(attrName)) return cascader;
311
323
  const expanded = document.querySelector(
312
324
  `[${attrName}][aria-expanded="true"]`
@@ -314,22 +326,75 @@ function findActiveTrigger(attrName) {
314
326
  if (expanded) return expanded;
315
327
  return null;
316
328
  }
317
- function setupAntTestIds(opts = {}) {
318
- const attrName = opts.attributeName ?? "data-testid";
319
- const panels = { ...DEFAULT_PANELS, ...opts.panels };
320
- let matchers = DEFAULT_COMPONENT_MATCHERS;
321
- if (opts.components) {
322
- const customKeys = new Set(Object.keys(opts.components));
323
- matchers = matchers.filter((m) => !customKeys.has(m.selector));
324
- for (const [selector, prefix] of Object.entries(opts.components)) {
325
- matchers.push({ selector, prefix });
329
+ function injectSubElements(root, attrName, pc) {
330
+ const inputNumbers = root.matches(`${sel(pc, "input-number")}[${attrName}]`) ? [root] : Array.from(root.querySelectorAll(`${sel(pc, "input-number")}[${attrName}]`));
331
+ for (const wrapper of inputNumbers) {
332
+ if (!(wrapper instanceof HTMLElement)) continue;
333
+ const testId = wrapper.getAttribute(attrName) || "";
334
+ if (!testId) continue;
335
+ const input = wrapper.querySelector(sel(pc, "input-number-input"));
336
+ if (input && !input.hasAttribute(attrName)) {
337
+ input.setAttribute(attrName, testId);
326
338
  }
339
+ const upHandler = wrapper.querySelector(sel(pc, "input-number-handler-up"));
340
+ if (upHandler && !upHandler.hasAttribute(attrName)) {
341
+ upHandler.setAttribute(attrName, `${testId}-up`);
342
+ }
343
+ const downHandler = wrapper.querySelector(sel(pc, "input-number-handler-down"));
344
+ if (downHandler && !downHandler.hasAttribute(attrName)) {
345
+ downHandler.setAttribute(attrName, `${testId}-down`);
346
+ }
347
+ }
348
+ const sliders = root.matches(`${sel(pc, "slider")}[${attrName}]`) ? [root] : Array.from(root.querySelectorAll(`${sel(pc, "slider")}[${attrName}]`));
349
+ for (const slider of sliders) {
350
+ if (!(slider instanceof HTMLElement)) continue;
351
+ const testId = slider.getAttribute(attrName) || "";
352
+ if (!testId) continue;
353
+ slider.querySelectorAll(sel(pc, "slider-handle")).forEach((h, i) => {
354
+ const el = h;
355
+ if (!el.hasAttribute(attrName)) {
356
+ el.setAttribute(attrName, `${testId}-handle-${i}`);
357
+ }
358
+ });
327
359
  }
360
+ const rates = root.matches(`${sel(pc, "rate")}[${attrName}]`) ? [root] : Array.from(root.querySelectorAll(`${sel(pc, "rate")}[${attrName}]`));
361
+ for (const rate of rates) {
362
+ if (!(rate instanceof HTMLElement)) continue;
363
+ const testId = rate.getAttribute(attrName) || "";
364
+ if (!testId) continue;
365
+ rate.querySelectorAll(sel(pc, "rate-star")).forEach((s, i) => {
366
+ const el = s;
367
+ if (!el.hasAttribute(attrName)) {
368
+ el.setAttribute(attrName, `${testId}-star-${i}`);
369
+ }
370
+ });
371
+ }
372
+ const uploadByTestId = root.matches(`${sel(pc, "upload")}[${attrName}]`) ? [root] : Array.from(root.querySelectorAll(`${sel(pc, "upload")}[${attrName}]`));
373
+ const uploadById = uploadByTestId.length === 0 ? root.matches(`${sel(pc, "upload")}[id]`) ? [root] : Array.from(root.querySelectorAll(`${sel(pc, "upload")}[id]`)) : [];
374
+ for (const upload of [...uploadByTestId, ...uploadById]) {
375
+ if (!(upload instanceof HTMLElement)) continue;
376
+ const testId = upload.getAttribute(attrName) || upload.getAttribute("id") || "";
377
+ if (!testId) continue;
378
+ const input = upload.querySelector('input[type="file"]');
379
+ if (input && !input.hasAttribute(attrName)) {
380
+ input.setAttribute(attrName, `${testId}-input`);
381
+ if (!upload.hasAttribute(attrName)) {
382
+ upload.setAttribute(attrName, testId);
383
+ }
384
+ }
385
+ }
386
+ }
387
+ function setupAntTestIds(opts = {}) {
388
+ const attrName = opts.attributeName ?? "data-testid";
389
+ const pc = opts.prefixCls ?? "ant";
390
+ const matchers = mergeMatchers(buildComponentMatchers(pc), opts.components);
391
+ const skipSelectors = buildSkipSelectors(pc);
392
+ const panels = { ...buildPanels(pc), ...opts.panels };
328
393
  const counter = { value: 0 };
329
394
  function processNode(node) {
330
395
  if (!(node instanceof HTMLElement)) return;
331
- injectComponentRoots(node, matchers, attrName, counter);
332
- injectMenuItems(attrName, node);
396
+ injectComponentRoots(node, matchers, attrName, counter, skipSelectors);
397
+ injectMenuItems(attrName, node, pc);
333
398
  for (const [selector, strategy] of Object.entries(panels)) {
334
399
  const containers = node.matches(selector) ? [node] : Array.from(node.querySelectorAll(selector));
335
400
  for (const container of containers) {
@@ -337,71 +402,14 @@ function setupAntTestIds(opts = {}) {
337
402
  if (container.hasAttribute(attrName)) continue;
338
403
  if (strategy) {
339
404
  queueMicrotask(() => {
340
- const trigger = findActiveTrigger(attrName);
405
+ const trigger = findActiveTrigger(attrName, pc);
341
406
  const triggerTestId = getTriggerTestId(trigger, attrName);
342
407
  strategy({ container, trigger, triggerTestId, attrName });
343
408
  });
344
409
  }
345
410
  }
346
411
  }
347
- const inputNumbers = node.matches(`.ant-input-number[${attrName}]`) ? [node] : Array.from(node.querySelectorAll(`.ant-input-number[${attrName}]`));
348
- for (const inputNumber of inputNumbers) {
349
- if (!(inputNumber instanceof HTMLElement)) continue;
350
- const testId = inputNumber.getAttribute(attrName) || "";
351
- if (!testId) continue;
352
- const input = inputNumber.querySelector(".ant-input-number-input");
353
- if (input && !input.hasAttribute(attrName)) {
354
- input.setAttribute(attrName, testId);
355
- }
356
- const upHandler = inputNumber.querySelector(".ant-input-number-handler-up");
357
- if (upHandler && !upHandler.hasAttribute(attrName)) {
358
- upHandler.setAttribute(attrName, `${testId}-up`);
359
- }
360
- const downHandler = inputNumber.querySelector(".ant-input-number-handler-down");
361
- if (downHandler && !downHandler.hasAttribute(attrName)) {
362
- downHandler.setAttribute(attrName, `${testId}-down`);
363
- }
364
- }
365
- const sliders = node.matches(`.ant-slider[${attrName}]`) ? [node] : Array.from(node.querySelectorAll(`.ant-slider[${attrName}]`));
366
- for (const slider of sliders) {
367
- if (!(slider instanceof HTMLElement)) continue;
368
- const testId = slider.getAttribute(attrName) || "";
369
- if (!testId) continue;
370
- const handles = slider.querySelectorAll(".ant-slider-handle");
371
- handles.forEach((h, i) => {
372
- const el = h;
373
- if (!el.hasAttribute(attrName)) {
374
- el.setAttribute(attrName, `${testId}-handle-${i}`);
375
- }
376
- });
377
- }
378
- const rates = node.matches(`.ant-rate[${attrName}]`) ? [node] : Array.from(node.querySelectorAll(`.ant-rate[${attrName}]`));
379
- for (const rate of rates) {
380
- if (!(rate instanceof HTMLElement)) continue;
381
- const testId = rate.getAttribute(attrName) || "";
382
- if (!testId) continue;
383
- const stars = rate.querySelectorAll(".ant-rate-star");
384
- stars.forEach((s, i) => {
385
- const el = s;
386
- if (!el.hasAttribute(attrName)) {
387
- el.setAttribute(attrName, `${testId}-star-${i}`);
388
- }
389
- });
390
- }
391
- const uploadByTestId = node.matches(`.ant-upload[${attrName}]`) ? [node] : Array.from(node.querySelectorAll(`.ant-upload[${attrName}]`));
392
- const uploadById = uploadByTestId.length === 0 ? node.matches(".ant-upload[id]") ? [node] : Array.from(node.querySelectorAll(".ant-upload[id]")) : [];
393
- for (const upload of [...uploadByTestId, ...uploadById]) {
394
- if (!(upload instanceof HTMLElement)) continue;
395
- const testId = upload.getAttribute(attrName) || upload.getAttribute("id") || "";
396
- if (!testId) continue;
397
- const input = upload.querySelector('input[type="file"]');
398
- if (input && !input.hasAttribute(attrName)) {
399
- input.setAttribute(attrName, `${testId}-input`);
400
- if (!upload.hasAttribute(attrName)) {
401
- upload.setAttribute(attrName, testId);
402
- }
403
- }
404
- }
412
+ injectSubElements(node, attrName, pc);
405
413
  }
406
414
  let pending = false;
407
415
  let pendingNodes = [];
@@ -434,92 +442,34 @@ function setupAntTestIds(opts = {}) {
434
442
  var setupAntDropdownTestIds = setupAntTestIds;
435
443
  function injectCurrentDropdowns(opts = {}) {
436
444
  const attrName = opts.attributeName ?? "data-testid";
437
- const panels = { ...DEFAULT_PANELS, ...opts.panels };
445
+ const pc = opts.prefixCls ?? "ant";
446
+ const matchers = mergeMatchers(buildComponentMatchers(pc), opts.components);
447
+ const skipSelectors = buildSkipSelectors(pc);
448
+ const panels = { ...buildPanels(pc), ...opts.panels };
438
449
  const counter = { value: 0 };
439
- let matchers = DEFAULT_COMPONENT_MATCHERS;
440
- if (opts.components) {
441
- const customKeys = new Set(Object.keys(opts.components));
442
- matchers = matchers.filter((m) => !customKeys.has(m.selector));
443
- for (const [selector, prefix] of Object.entries(opts.components)) {
444
- matchers.push({ selector, prefix });
445
- }
446
- }
447
- injectComponentRoots(document.body, matchers, attrName, counter);
450
+ injectComponentRoots(document.body, matchers, attrName, counter, skipSelectors);
448
451
  for (const [selector, strategy] of Object.entries(panels)) {
449
452
  const containers = document.querySelectorAll(selector);
450
453
  containers.forEach((container) => {
451
454
  if (!(container instanceof HTMLElement)) return;
452
455
  if (container.hasAttribute(attrName)) return;
453
456
  if (!strategy) return;
454
- const trigger = findActiveTrigger(attrName);
457
+ const trigger = findActiveTrigger(attrName, pc);
455
458
  const triggerTestId = getTriggerTestId(trigger, attrName);
456
459
  strategy({ container, trigger, triggerTestId, attrName });
457
460
  });
458
461
  }
459
- const inputNumbers = document.querySelectorAll(`.ant-input-number[${attrName}]`);
460
- inputNumbers.forEach((inputNumber) => {
461
- if (!(inputNumber instanceof HTMLElement)) return;
462
- const testId = inputNumber.getAttribute(attrName) || "";
463
- if (!testId) return;
464
- const input = inputNumber.querySelector(".ant-input-number-input");
465
- if (input && !input.hasAttribute(attrName)) {
466
- input.setAttribute(attrName, testId);
467
- }
468
- const upHandler = inputNumber.querySelector(".ant-input-number-handler-up");
469
- if (upHandler && !upHandler.hasAttribute(attrName)) {
470
- upHandler.setAttribute(attrName, `${testId}-up`);
471
- }
472
- const downHandler = inputNumber.querySelector(".ant-input-number-handler-down");
473
- if (downHandler && !downHandler.hasAttribute(attrName)) {
474
- downHandler.setAttribute(attrName, `${testId}-down`);
475
- }
476
- });
477
- const sliders = document.querySelectorAll(`.ant-slider[${attrName}]`);
478
- sliders.forEach((slider) => {
479
- if (!(slider instanceof HTMLElement)) return;
480
- const testId = slider.getAttribute(attrName) || "";
481
- if (!testId) return;
482
- const handles = slider.querySelectorAll(".ant-slider-handle");
483
- handles.forEach((h, i) => {
484
- const el = h;
485
- if (!el.hasAttribute(attrName)) {
486
- el.setAttribute(attrName, `${testId}-handle-${i}`);
487
- }
488
- });
489
- });
490
- const rates = document.querySelectorAll(`.ant-rate[${attrName}]`);
491
- rates.forEach((rate) => {
492
- if (!(rate instanceof HTMLElement)) return;
493
- const testId = rate.getAttribute(attrName) || "";
494
- if (!testId) return;
495
- const stars = rate.querySelectorAll(".ant-rate-star");
496
- stars.forEach((s, i) => {
497
- const el = s;
498
- if (!el.hasAttribute(attrName)) {
499
- el.setAttribute(attrName, `${testId}-star-${i}`);
500
- }
501
- });
502
- });
503
- const uploads = [
504
- ...Array.from(document.querySelectorAll(`.ant-upload[${attrName}]`)),
505
- ...Array.from(document.querySelectorAll(".ant-upload[id]"))
506
- ];
507
- const seen = /* @__PURE__ */ new Set();
508
- uploads.forEach((upload) => {
509
- if (!(upload instanceof HTMLElement)) return;
510
- if (seen.has(upload)) return;
511
- seen.add(upload);
512
- const testId = upload.getAttribute(attrName) || upload.getAttribute("id") || "";
513
- if (!testId) return;
514
- const input = upload.querySelector('input[type="file"]');
515
- if (input && !input.hasAttribute(attrName)) {
516
- input.setAttribute(attrName, `${testId}-input`);
517
- if (!upload.hasAttribute(attrName)) {
518
- upload.setAttribute(attrName, testId);
519
- }
520
- }
521
- });
522
- injectMenuItems(attrName);
462
+ injectSubElements(document.body, attrName, pc);
463
+ injectMenuItems(attrName, document, pc);
464
+ }
465
+ function mergeMatchers(defaults, customs) {
466
+ if (!customs) return defaults;
467
+ const customKeys = new Set(Object.keys(customs));
468
+ const filtered = defaults.filter((m) => !customKeys.has(m.selector));
469
+ for (const [selector, prefix] of Object.entries(customs)) {
470
+ filtered.push({ selector, prefix });
471
+ }
472
+ return filtered;
523
473
  }
524
474
  export {
525
475
  injectCurrentDropdowns,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vite-plugin-vue-testid",
3
- "version": "1.0.9",
3
+ "version": "1.0.11",
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",
@@ -47,4 +47,4 @@
47
47
  "typescript": "~6.0.2",
48
48
  "vite": "^8.0.12"
49
49
  }
50
- }
50
+ }