vite-plugin-vue-testid 1.1.1 → 1.1.2

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 (4) hide show
  1. package/README.md +302 -198
  2. package/README.zh.md +481 -0
  3. package/package.json +3 -2
  4. package/README.en.md +0 -377
package/README.md CHANGED
@@ -1,47 +1,78 @@
1
1
  # vite-plugin-vue-testid
2
2
 
3
- 编译期 + 运行时自动为 Vue 组件模板注入 `data-testid` 的 Vite 插件,专为 ant-design-vue 4.x 适配,大幅降低 E2E 测试中的元素定位成本。
3
+ [中文文档](./README.zh.md)
4
4
 
5
- ## 特性
5
+ A Vite plugin that auto-injects `data-testid` into Vue component templates via compile-time + runtime transforms, tailored for ant-design-vue 4.x. Dramatically reduces the effort of locating elements in E2E tests.
6
6
 
7
- - **编译期注入** — 通过 Vue 编译器 `nodeTransform` 自动为组件模板添加 `data-testid`
8
- - **运行时注入** — 为 ant-design-vue teleport 弹出层(DatePicker、Cascader、Select、TreeSelect 等)动态注入 testId
9
- - **<a-table> 适配**bodyCell 插槽内组件自动生成「列名 + 行号 + 序号」的唯一 testId
10
- - **自动补全 index 解构**即使 `#bodyCell="{ column, record }"` 未写 `index`,插件也会自动补上
11
- - **不会覆盖已有 testId** — 手动标注了 `data-testid` 的元素会被跳过
12
- - **teleport 组件双注入**DatePicker / RangePicker / Modal 同时注入 `id` + `data-testid`
13
- - **日期面板模式感知**date / month / year / decade 面板自动区分
14
- - **MutationObserver 重注入**面板切换、树节点展开/折叠时自动检测并注入新 DOM
15
- - **可扩展**支持自定义组件列表、testId 生成规则、弹出面板注入策略
7
+ ## Features
8
+
9
+ - **Full runtime coverage** MutationObserver-based injection for all ant-design-vue component roots, sub-elements, and teleported panels, no compile-time config required
10
+ - **Optional compile-time enhancement**Inject stable, unique testids into custom business components (e.g., `<MyInput />`) at compile time, automatically passed through to inner antd components via `inheritAttrs`
11
+ - **Multi-root component fix** — Auto-adds `v-bind="$attrs"` to the first antd root element in multi-root child components, ensuring testid pass-through
12
+ - **Custom prefixCls**Full support for ConfigProvider's custom CSS class prefix
13
+ - **Custom component mapping** Extendable matcher rules for third-party UI libraries or custom components
14
+ - **Full panel coverage** DatePicker (date/month/year/decade), RangePicker, TimePicker, Cascader, Select, TreeSelect
15
+ - **Sub-element injection** InputNumber internal input & up/down buttons, Slider handles, Rate stars, Upload file input, Tabs tabs/panes
16
+ - **Menu sub-elements** — Text-based testids for menu-item, sub-menu, and menu-item-group
17
+ - **Preserves existing testIds** — Elements with manually assigned `data-testid` are skipped
18
+ - **MutationObserver dynamic watch** — Panel switching, tree node expand/collapse, and async component loading all auto-trigger re-injection
19
+ - **One-shot injection mode** — SSR/hydration friendly, call directly without MutationObserver
16
20
 
17
21
  ---
18
22
 
19
- ## 安装
23
+ ## Installation
20
24
 
21
25
  ```bash
22
26
  pnpm add -D vite-plugin-vue-testid
23
27
  ```
24
28
 
25
- > 要求:`vite >= 5.0.0`,`vue >= 3.3.0`,`@vue/compiler-core >= 3.5.0`
29
+ > Requirements: `vite >= 5.0.0`, `vue >= 3.3.0`
26
30
 
27
31
  ---
28
32
 
29
- ## 快速开始
33
+ ## Quick Start
34
+
35
+ **Mode A** (runtime-only) is recommended — simplest with full coverage. Choose **Mode B** if you need stable, unchanging testids for custom business components.
36
+
37
+ ### Mode A: Runtime-only (recommended, zero compile-time config)
30
38
 
31
- ### 1. 编译期注入(vite.config.ts
39
+ ```ts
40
+ // vite.config.ts — no plugin config needed
41
+ import { defineConfig } from 'vite'
42
+ import vue from '@vitejs/plugin-vue'
43
+
44
+ export default defineConfig({
45
+ plugins: [vue()]
46
+ })
47
+ ```
48
+
49
+ ```ts
50
+ // main.ts
51
+ import { createApp } from 'vue'
52
+ import App from './App.vue'
53
+ import { setupAntTestIds } from 'vite-plugin-vue-testid/runtime'
54
+
55
+ const app = createApp(App)
56
+ app.mount('#app')
57
+
58
+ // Call once after mount
59
+ setupAntTestIds()
60
+ ```
61
+
62
+ ### Mode B: Compile-time + Runtime (for custom components needing stable testids)
32
63
 
33
64
  ```ts
34
65
  // vite.config.ts
35
66
  import { defineConfig } from 'vite'
36
67
  import vue from '@vitejs/plugin-vue'
37
- import { createVueTestIdTransform } from 'vite-plugin-vue-testid'
68
+ import { testIdTransforms } from 'vite-plugin-vue-testid'
38
69
 
39
70
  export default defineConfig({
40
71
  plugins: [
41
72
  vue({
42
73
  template: {
43
74
  compilerOptions: {
44
- nodeTransforms: [createVueTestIdTransform()]
75
+ nodeTransforms: testIdTransforms()
45
76
  }
46
77
  }
47
78
  })
@@ -49,271 +80,321 @@ export default defineConfig({
49
80
  })
50
81
  ```
51
82
 
52
- ### 2. 运行时注入(main.ts)
53
-
54
83
  ```ts
55
- // main.ts
56
- import { createApp } from 'vue'
57
- import App from './App.vue'
58
- import { setupAntDropdownTestIds } from 'vite-plugin-vue-testid/runtime'
84
+ // main.ts — same as Mode A
85
+ import { setupAntTestIds } from 'vite-plugin-vue-testid/runtime'
86
+ setupAntTestIds()
87
+ ```
59
88
 
60
- const app = createApp(App)
61
- app.mount('#app')
89
+ ### Mode C: Compile-time only (panels not injected, not recommended)
90
+
91
+ ```ts
92
+ // vite.config.ts
93
+ import vue from '@vitejs/plugin-vue'
94
+ import { testIdTransforms } from 'vite-plugin-vue-testid'
62
95
 
63
- // mount 之后调用,监听 teleport 面板并自动注入 testId
64
- setupAntDropdownTestIds()
96
+ export default defineConfig({
97
+ plugins: [
98
+ vue({
99
+ template: {
100
+ compilerOptions: {
101
+ nodeTransforms: testIdTransforms()
102
+ }
103
+ }
104
+ })
105
+ ]
106
+ })
107
+ // main.ts — do NOT call setupAntTestIds()
65
108
  ```
66
109
 
67
110
  ---
68
111
 
69
- ## 生成的 testId 示例
112
+ ## Generated testId Examples
70
113
 
71
- ### 普通组件
114
+ ### Runtime: Component Roots
72
115
 
73
116
  ```html
74
- <!-- 源码 -->
75
- <a-input />
76
- <a-select />
77
- <a-button />
78
-
79
- <!-- 编译后 -->
80
- <a-input data-testid="a-input-0" />
81
- <a-select data-testid="a-select-1" />
82
- <a-button data-testid="a-button-2" />
117
+ <!-- Runtime auto-matches ant-design-vue components, globally incrementing counter -->
118
+ <div class="ant-input-number" data-testid="a-input-number-0">
119
+ <div class="ant-select" data-testid="a-select-1" id="a-select-1">
120
+ <div class="ant-picker" data-testid="a-date-picker-2" id="a-date-picker-2">
121
+ <button class="ant-btn" data-testid="a-button-3">
122
+ <input class="ant-input" data-testid="a-input-4">
123
+ <span class="ant-slider" data-testid="a-slider-5">
83
124
  ```
84
125
 
85
- ### teleport 组件(双注入)
86
-
87
- ```html
88
- <!-- 源码 -->
89
- <a-date-picker />
90
- <a-modal v-model:visible="show" title="提示" />
126
+ ### Runtime: Sub-elements
91
127
 
92
- <!-- 编译后 -->
93
- <a-date-picker id="a-date-picker-0" data-testid="a-date-picker-0" />
94
- <a-modal id="a-modal-1" data-testid="a-modal-1" />
128
+ ```
129
+ <!-- InputNumber: internal input + up/down handlers -->
130
+ a-input-number-0-input # internal <input>
131
+ a-input-number-0-up # increase button
132
+ a-input-number-0-down # decrease button
133
+
134
+ <!-- Slider: handles -->
135
+ a-slider-5-handle-0 # 1st handle
136
+ a-slider-5-handle-1 # 2nd handle (range mode)
137
+
138
+ <!-- Rate: stars -->
139
+ a-rate-0-star-0 # 1st star
140
+ a-rate-0-star-1 # 2nd star
141
+
142
+ <!-- Upload: internal file input -->
143
+ a-upload-0-input # <input type="file">
144
+
145
+ <!-- Tabs: tab items and panes -->
146
+ a-tabs-0-tab-0-Tab-One # 1st tab
147
+ a-tabs-0-tab-1-Tab-Two # 2nd tab
148
+ a-tabs-0-pane-0-Tab-One # 1st pane
149
+ a-tabs-0-add # add tab button (editable tabs)
150
+ a-tabs-0-more # more button
95
151
  ```
96
152
 
97
- ### <a-table> bodyCell 内组件
153
+ ### Runtime: DatePicker Panel
98
154
 
99
155
  ```
100
- 格式:${column.dataIndex}-${index}-${组件名}-${序号}
101
-
102
- 示例:
103
- - label-0-a-input-4 (label 列,第 0 行)
104
- - value-2-a-input-5 (value 列,第 2 行)
156
+ a-date-picker-0-dropdown # panel container
157
+ a-date-picker-0-dropdown-super-prev # previous year
158
+ a-date-picker-0-dropdown-prev # previous month
159
+ a-date-picker-0-dropdown-next # next month
160
+ a-date-picker-0-dropdown-super-next # next year
161
+ a-date-picker-0-dropdown-header-date # header container
162
+ a-date-picker-0-dropdown-header-month-btn # header month button
163
+ a-date-picker-0-dropdown-header-year-btn # header year button
164
+ a-date-picker-0-dropdown-date-2026-06-15 # date cell
165
+ a-date-picker-0-dropdown-ok # OK button
105
166
  ```
106
167
 
107
- ### 运行时 DatePicker 面板
168
+ After switching to month/year/decade panel (auto re-injected by MutationObserver):
108
169
 
109
170
  ```
110
- a-date-picker-0-dropdown # 面板容器
111
- a-date-picker-0-dropdown-prev # 上一月
112
- a-date-picker-0-dropdown-next # 下一月
113
- a-date-picker-0-dropdown-header-date # header 容器
114
- a-date-picker-0-dropdown-header-month-btn # header 月份按钮
115
- a-date-picker-0-dropdown-header-year-btn # header 年份按钮
116
- a-date-picker-0-dropdown-date-2026-06-15 # 日期单元格
117
- a-date-picker-0-dropdown-ok # 确定按钮
171
+ a-date-picker-0-dropdown-header-month # month panel header
172
+ a-date-picker-0-dropdown-month-06 # June
173
+ a-date-picker-0-dropdown-header-year # year panel header
174
+ a-date-picker-0-dropdown-year-2026 # year 2026
175
+ a-date-picker-0-dropdown-decade-2020-2029 # decade 2020-2029
118
176
  ```
119
177
 
120
- 切换到月面板 / 年面板后(MutationObserver 自动重注入):
178
+ ### Runtime: RangePicker Panel
121
179
 
122
180
  ```
123
- a-date-picker-0-dropdown-header-month # 月面板 header
124
- a-date-picker-0-dropdown-month-06 # 6月
125
- a-date-picker-0-dropdown-header-year # 年面板 header
126
- a-date-picker-0-dropdown-year-2026 # 2026年
127
- a-date-picker-0-dropdown-decade-2020-2029 # 2020-2029 年代
181
+ a-range-picker-0-dropdown-super-prev-left # left panel previous year
182
+ a-range-picker-0-dropdown-prev-left # left panel previous month
183
+ a-range-picker-0-dropdown-next-right # right panel next month
184
+ a-range-picker-0-dropdown-date-2026-05-15-left # left panel date
185
+ a-range-picker-0-dropdown-date-2026-06-20-right # right panel date
128
186
  ```
129
187
 
130
- ### 运行时 RangePicker 面板
188
+ ### Runtime: TimePicker
131
189
 
132
190
  ```
133
- a-range-picker-0-dropdown-prev-left # 左侧面板上一月
134
- a-range-picker-0-dropdown-date-2026-05-15-left # 左侧面板日期
135
- a-range-picker-0-dropdown-date-2026-06-20-right # 右侧面板日期
191
+ a-time-picker-0-dropdown-time-column-0 # hour column
192
+ a-time-picker-0-dropdown-time-0-08 # 08 hour
193
+ a-time-picker-0-dropdown-time-column-1 # minute column
194
+ a-time-picker-0-dropdown-time-1-30 # 30 minutes
136
195
  ```
137
196
 
138
- ### 运行时 TimePicker
197
+ ### Runtime: Preset Shortcuts
139
198
 
140
199
  ```
141
- a-time-picker-0-dropdown-time-column-0 # 时列
142
- a-time-picker-0-dropdown-time-08 # 08时
143
- a-time-picker-0-dropdown-time-column-1 # 分列
144
- a-time-picker-0-dropdown-time-30 # 30分
200
+ a-date-picker-0-dropdown-preset-This Week # preset tag
201
+ a-date-picker-0-dropdown-preset-This Month # preset tag
145
202
  ```
146
203
 
147
- ### 运行时 Cascader
204
+ ### Runtime: Cascader
148
205
 
149
206
  ```
150
- a-cascader-0-dropdown # 面板容器
151
- a-cascader-0-dropdown-menu-0 # 1级菜单
152
- a-cascader-0-dropdown-item-浙江 # 浙江选项
153
- a-cascader-0-dropdown-menu-1 # 2级菜单
154
- a-cascader-0-dropdown-item-杭州 # 杭州选项
207
+ a-cascader-0-dropdown # panel container
208
+ a-cascader-0-dropdown-menu-0 # level 1 menu
209
+ a-cascader-0-dropdown-item-Zhejiang # Zhejiang option
210
+ a-cascader-0-dropdown-menu-1 # level 2 menu
211
+ a-cascader-0-dropdown-item-Hangzhou # Hangzhou option
155
212
  ```
156
213
 
157
- ### 运行时 Select
214
+ ### Runtime: Select / TreeSelect
158
215
 
159
216
  ```
160
- a-select-0-dropdown # 下拉容器
161
- a-select-0-dropdown-option-选项1 # 选项
162
- a-select-0-dropdown-option-选项2 # 选项
217
+ a-select-0-dropdown # dropdown container
218
+ a-select-0-dropdown-option-Option1 # option
219
+ a-select-0-dropdown-option-Option2 # option
220
+
221
+ a-tree-select-0-dropdown # dropdown container
222
+ a-tree-select-0-dropdown-tree-Root-Node # tree node
223
+ a-tree-select-0-dropdown-tree-switcher-expand-Root-Node # expand icon (collapsed)
224
+ a-tree-select-0-dropdown-tree-switcher-collapse-Root-Node # expand icon (expanded)
163
225
  ```
164
226
 
165
- ### 运行时 TreeSelect
227
+ ### Runtime: Menu Sub-elements
166
228
 
167
229
  ```
168
- a-tree-select-0-dropdown # 下拉容器
169
- a-tree-select-0-dropdown-tree-root-1 # 根节点
170
- a-tree-select-0-dropdown-tree-switcher-expand-root-1 # 展开图标(可展开状态)
171
- a-tree-select-0-dropdown-tree-switcher-collapse-root-1 # 展开图标(已展开状态)
172
- a-tree-select-0-dropdown-tree-parent-1 # 父节点(展开后注入)
173
- a-tree-select-0-dropdown-tree-my-leaf # 叶子节点
230
+ a-menu-0 # menu container (by injectComponentRoots)
231
+ a-menu-item-Menu-Item-1 # menu item (runtime, text-based)
232
+ a-menu-item-Sub-Item-3 # submenu item
233
+ a-sub-menu-Submenu # submenu
234
+ a-menu-item-group-Group-Title # menu item group
174
235
  ```
175
236
 
176
- ### 编译期 + 运行时 Menu
237
+ ### Compile-time: Custom Business Components
177
238
 
178
239
  ```
179
- a-menu-0 # 菜单容器(编译期注入)
180
- a-menu-item-菜单项 1 # 菜单项(运行时注入,基于文本内容)
181
- a-menu-item-子项 3 # 子菜单内的菜单项
182
- a-sub-menu-子菜单 # 子菜单(运行时注入)
183
- a-menu-item-group-分组 # 菜单分组(运行时注入)
240
+ Format: {tagName}-{perTemplateIndex}
241
+
242
+ <!-- Source -->
243
+ <MyInput />
244
+ <MySelect />
245
+ <MyInput />
246
+
247
+ <!-- Compiled -->
248
+ <MyInput data-testid="MyInput-0" />
249
+ <MySelect data-testid="MySelect-0" />
250
+ <MyInput data-testid="MyInput-1" />
184
251
  ```
185
252
 
253
+ > **Design note**: The compile-time transform only injects testids on **custom business components** (passed through to inner antd components via `inheritAttrs`), NOT on antd components directly. All antd component testids are handled by the runtime layer (global counter ensures uniqueness).
254
+
186
255
  ---
187
256
 
188
- ## 配置
257
+ ## Configuration
189
258
 
190
- ### 编译期配置
259
+ ### Compile-time Options
191
260
 
192
261
  ```ts
193
- import { createVueTestIdTransform } from 'vite-plugin-vue-testid'
262
+ import { testIdTransforms } from 'vite-plugin-vue-testid'
194
263
 
195
- createVueTestIdTransform({
264
+ testIdTransforms({
196
265
  /**
197
- * 需要注入 testId 的组件标签列表(kebab-case)
198
- * @default 见下方「默认支持的组件」
199
- */
200
- components: ['a-input', 'a-select', 'my-button'],
201
-
202
- /**
203
- * 自定义 testId 生成函数
204
- * @param tagName 组件标签名
205
- * @param count 全局递增序号
206
- * @returns testId 字符串
207
- */
208
- generateId: (tagName, count) => `myapp-${tagName}-${count}`,
209
-
210
- /**
211
- * testId 属性名(可改为 data-cy 适配 Cypress)
266
+ * testId attribute name
212
267
  * @default 'data-testid'
213
268
  */
214
- attributeName: 'data-cy',
269
+ attributeName: 'data-testid',
215
270
 
216
271
  /**
217
- * 需要同时注入 id + testId 的组件
218
- * @default ['a-date-picker', 'a-range-picker', 'a-time-picker', 'a-modal']
272
+ * ant-design-vue tag prefix
273
+ * @default 'a-'
219
274
  */
220
- idComponents: ['a-date-picker', 'a-modal'],
275
+ antPrefix: 'a-',
221
276
 
222
277
  /**
223
- * id 属性名
224
- * @default 'id'
278
+ * Additional custom component prefixes to inject testids for
279
+ * @default []
225
280
  */
226
- idAttributeName: 'id',
281
+ customPrefixes: ['myapp-', 'el-'],
227
282
  })
228
283
  ```
229
284
 
230
- ### 运行时配置
285
+ ### Runtime Options
231
286
 
232
287
  ```ts
233
- import { setupAntDropdownTestIds } from 'vite-plugin-vue-testid/runtime'
288
+ import { setupAntTestIds } from 'vite-plugin-vue-testid/runtime'
234
289
 
235
- // 默认配置
236
- const cleanup = setupAntDropdownTestIds()
237
-
238
- // 自定义配置
239
- const cleanup = setupAntDropdownTestIds({
290
+ const cleanup = setupAntTestIds({
240
291
  /**
241
- * testId 属性名,需与编译期配置一致
292
+ * testId attribute name, must match compile-time config
242
293
  * @default 'data-testid'
243
294
  */
244
295
  attributeName: 'data-testid',
245
296
 
246
297
  /**
247
- * 自定义面板选择器 注入策略映射
248
- * key: CSS 选择器
249
- * value: 注入函数,设为 undefined 可禁用某个默认面板
298
+ * ant-design-vue CSS class prefix, matching ConfigProvider's prefixCls
299
+ * @default 'ant'
300
+ */
301
+ prefixCls: 'ant',
302
+
303
+ /**
304
+ * Custom CSS selector → testid prefix mapping
305
+ * Merged with defaults (custom overrides default)
306
+ *
307
+ * @example
308
+ * components: {
309
+ * '.el-input': 'el-input',
310
+ * '.el-button': 'el-button',
311
+ * }
312
+ */
313
+ components: {
314
+ '.el-input': 'el-input',
315
+ },
316
+
317
+ /**
318
+ * Custom panel selector → injection strategy mapping
319
+ * key: CSS selector
320
+ * value: injection function; set to undefined to disable a built-in panel
250
321
  */
251
322
  panels: {
252
- '.ant-picker-dropdown': undefined, // 禁用内置 Picker 面板注入
323
+ '.ant-picker-dropdown': undefined, // disable built-in Picker panel
253
324
  '.my-custom-dropdown': (ctx) => {
254
- // ctx.container — 面板容器 HTMLElement
255
- // ctx.trigger — 触发器 HTMLElement | null
256
- // ctx.triggerTestId — 触发器的 testId 值
257
- // ctx.attrName — testId 属性名
258
325
  const prefix = `${ctx.triggerTestId}-dropdown`
259
326
  ctx.container.setAttribute(ctx.attrName, prefix)
260
- // ... 自定义注入逻辑
327
+ // ... custom injection logic
261
328
  },
262
329
  },
263
330
  })
264
331
 
265
- // 组件卸载时清理 Observer
266
- onUnmounted(cleanup)
332
+ // Clean up observer on component unmount
333
+ // onUnmounted(cleanup)
267
334
  ```
268
335
 
269
- ### 一次性手动注入
336
+ ### One-shot Manual Injection
270
337
 
271
338
  ```ts
272
339
  import { injectCurrentDropdowns } from 'vite-plugin-vue-testid/runtime'
273
340
 
274
- // SSR / hydration 场景下,不想用 MutationObserver 时直接调用
275
- injectCurrentDropdowns({ attributeName: 'data-testid' })
341
+ // For SSR / hydration scenarios where MutationObserver is not desired
342
+ injectCurrentDropdowns({ prefixCls: 'ant' })
276
343
  ```
277
344
 
278
345
  ---
279
346
 
280
- ## 默认支持的组件
281
-
282
- | 组件 | 编译期注入 | 运行时注入 |
283
- |------|:---------:|:---------:|
284
- | `a-input` | | |
285
- | `a-textarea` | ✅ | — |
286
- | `a-input-number` | | — |
287
- | `a-select` | | 下拉面板 |
288
- | `a-cascader` | | 级联面板 |
289
- | `a-tree-select` | | 树选择面板 |
290
- | `a-radio-group` | | — |
291
- | `a-checkbox-group` | | |
292
- | `a-date-picker` | id+testid | ✅ 日期面板 |
293
- | `a-range-picker` | id+testid | ✅ 双面板 |
294
- | `a-time-picker` | id+testid | ✅ 时间面板 |
295
- | `a-switch` | | |
296
- | `a-slider` | | |
297
- | `a-rate` | | — |
298
- | `a-upload` | | — |
299
- | `a-form-item` | | — |
300
- | `a-button` | | — |
301
- | `a-modal` | id+testid | — |
302
- | `a-menu` | | — |
303
- | `a-menu-item` | id | 🔧 运行时(文本) |
304
- | `a-sub-menu` | id | 🔧 运行时(文本) |
305
- | `a-menu-item-group` | id | 🔧 运行时(文本) |
347
+ ## Default Supported Components
348
+
349
+ ### Runtime (component roots + sub-elements + panels)
350
+
351
+ | Component | Root testid | Sub-elements | Panels |
352
+ |-----------|:---:|:---:|:---:|
353
+ | Input | `a-input-N` | | — |
354
+ | Input.Search | `a-input-search-N` | | |
355
+ | Input.Password (affix-wrapper) | `a-input-N` | | |
356
+ | Textarea | `a-textarea-N` | | |
357
+ | InputNumber | `a-input-number-N` | input / up / down | — |
358
+ | Select | `a-select-N` | | dropdown |
359
+ | Cascader | `a-cascader-N` | | ✅ cascader panel |
360
+ | TreeSelect | `a-tree-select-N` | | ✅ tree select panel |
361
+ | DatePicker | `a-date-picker-N` | | ✅ date/month/year/decade |
362
+ | RangePicker | `a-range-picker-N` | | dual panel + time columns |
363
+ | TimePicker | `a-time-picker-N` | | time columns |
364
+ | Radio.Group | `a-radio-group-N` | | — |
365
+ | Radio | `a-radio-N` | | — |
366
+ | Checkbox.Group | `a-checkbox-group-N` | | — |
367
+ | Checkbox | `a-checkbox-N` | | — |
368
+ | Switch | `a-switch-N` | | — |
369
+ | Slider | `a-slider-N` | handle-0 / handle-1 | — |
370
+ | Rate | `a-rate-N` | star-0 / star-1 / ... | — |
371
+ | Upload | `a-upload-N` | file input | |
372
+ | Button | `a-button-N` | | |
373
+ | Form.Item | `a-form-item-N` | — | — |
374
+ | Modal | `a-modal-N` | — | — |
375
+ | Menu | `a-menu-N` | menu-item / sub-menu / group | — |
376
+ | Tabs | `a-tabs-N` | tab / pane / add / more | — |
377
+
378
+ > Components with `id` injection (Select, Cascader, DatePicker, RangePicker, TimePicker, Modal, Upload) also have the `id` attribute set, with the same value as testid.
379
+
380
+ ### Compile-time (custom business components)
381
+
382
+ The compile-time transform injects testids on components matching:
383
+ - Custom components with `tagType === Component` (e.g., `<MyInput />`)
384
+ - Components matching any prefix in `customPrefixes`
385
+ - **Excluding** antd components (`a-*`), which are handled by the runtime
306
386
 
307
387
  ---
308
388
 
309
- ## 自定义面板注入策略
389
+ ## Custom Panel Injection Strategies
310
390
 
311
- 如果需要支持其他 UI 库的弹出面板,可以自定义策略:
391
+ Support overlays from other UI libraries by providing custom strategies:
312
392
 
313
393
  ```ts
314
394
  import type { PanelInjectStrategy } from 'vite-plugin-vue-testid/runtime'
395
+ import { setupAntTestIds } from 'vite-plugin-vue-testid/runtime'
315
396
 
316
- // 示例:为 Element Plus 的弹出层注入 testId
397
+ // Example: inject testIds into Element Plus overlays
317
398
  const injectElDropdown: PanelInjectStrategy = (ctx) => {
318
399
  const { container, triggerTestId, attrName } = ctx
319
400
  const prefix = `${triggerTestId}-dropdown`
@@ -327,7 +408,7 @@ const injectElDropdown: PanelInjectStrategy = (ctx) => {
327
408
  })
328
409
  }
329
410
 
330
- setupAntDropdownTestIds({
411
+ setupAntTestIds({
331
412
  panels: {
332
413
  '.el-select-dropdown': injectElDropdown,
333
414
  '.el-cascader-dropdown': injectElDropdown,
@@ -337,38 +418,61 @@ setupAntDropdownTestIds({
337
418
 
338
419
  ---
339
420
 
340
- ## API 参考
421
+ ## API Reference
422
+
423
+ ### Compile-time
424
+
425
+ | Export | Type | Description |
426
+ |--------|------|-------------|
427
+ | `testIdTransforms(opts?)` | `() => NodeTransform[]` | Returns `[multiRootFixTransform, testIdInjectTransform]`, register in `vue()`'s `compilerOptions.nodeTransforms` |
428
+ | `createTestIdTransforms(opts?)` | `() => NodeTransform[]` | Same as above, full export name |
429
+ | `createTestIdInjectTransform(opts?)` | `() => NodeTransform` | Create only the testid inject transform (without multi-root fix) |
430
+ | `createMultiRootFixTransform(opts?)` | `() => NodeTransform` | Create only the multi-root fix transform |
431
+ | `TransformOptions` | `interface` | `{ attributeName?, antPrefix?, customPrefixes? }` |
341
432
 
342
- ### 编译期
433
+ ### Runtime (`/runtime` subpath)
343
434
 
344
- | 导出 | 类型 | 说明 |
345
- |------|------|------|
346
- | `createVueTestIdTransform(opts?)` | `(node: RootNode \| TemplateChildNode) => void` | 创建 Vue 编译器 nodeTransform |
347
- | `VueTestIdTransformOptions` | `interface` | 编译期配置类型 |
435
+ | Export | Type | Description |
436
+ |--------|------|-------------|
437
+ | `setupAntTestIds(opts?)` | `() => () => void` | Starts MutationObserver, returns cleanup function |
438
+ | `injectCurrentDropdowns(opts?)` | `() => void` | One-shot manual injection of current DOM components & panels |
439
+ | `RuntimeOptions` | `interface` | `{ attributeName?, prefixCls?, components?, panels? }` |
440
+ | `PanelContext` | `interface` | `{ container, trigger, triggerTestId, attrName }` |
441
+ | `PanelInjectStrategy` | `(ctx: PanelContext) => void` | Panel injection strategy function type |
348
442
 
349
- ### 运行时(`/runtime` 子路径)
443
+ ### Vite Plugin
350
444
 
351
- | 导出 | 类型 | 说明 |
352
- |------|------|------|
353
- | `setupAntDropdownTestIds(opts?)` | `() => void` | 启动 MutationObserver,自动注入弹出面板 testId,返回清理函数 |
354
- | `injectCurrentDropdowns(opts?)` | `void` | 一次性手动注入当前 DOM 中已有的弹出面板 |
355
- | `RuntimeOptions` | `interface` | 运行时配置类型 |
356
- | `PanelContext` | `interface` | 面板注入上下文类型 |
357
- | `PanelInjectStrategy` | `(ctx: PanelContext) => void` | 面板注入策略函数类型 |
445
+ | Export | Type | Description |
446
+ |--------|------|-------------|
447
+ | `vueTestIdPlugin(opts?)` | `Plugin` | Vite plugin (placeholder, currently no functionality) |
448
+
449
+ ### Deprecated
450
+
451
+ | Export | Replacement |
452
+ |--------|-------------|
453
+ | `setupAntDropdownTestIds` | Use `setupAntTestIds` |
454
+ | `createVueTestIdTransform` | Use `testIdTransforms` |
358
455
 
359
456
  ---
360
457
 
361
- ## 原理
458
+ ## How It Works
459
+
460
+ ### Runtime (Core)
461
+
462
+ `setupAntTestIds()` uses `MutationObserver` to watch DOM changes on `document.body`, processing newly inserted nodes in sequence:
362
463
 
363
- ### 编译期
464
+ 1. **Component root injection** — Iterates CSS selectors matching antd component root elements, injecting globally incrementing testids. Internal inputs/textareas inside wrappers are excluded via a skip list
465
+ 2. **Panel injection** — Detects teleported panel containers (picker-dropdown / cascader-dropdown / select-dropdown, etc.), resolves the triggering component via `findActiveTrigger`, and injects associated testids
466
+ 3. **Sub-element injection** — Injects suffixed testids into special child elements of InputNumber / Slider / Rate / Upload / Tabs
467
+ 4. **Menu injection** — Since Menu components have `inheritAttrs: false`, additionally traverses menu-item / sub-menu / menu-item-group to inject text-based testids
364
468
 
365
- 通过 Vue 编译器的 `nodeTransform` 钩子在 AST 阶段遍历模板节点,匹配目标组件标签后直接追加 `data-testid` 属性节点。由于是编译期注入,对运行时没有性能影响。
469
+ Panel containers also have their own MutationObserver to handle child node changes (panel mode switches, tree node expand/collapse) with automatic re-injection.
366
470
 
367
- ### 运行时
471
+ ### Compile-time (Optional)
368
472
 
369
- ant-design-vue 4.x DatePicker / Select / Cascader 等组件使用 Teleport 将弹出层渲染到 `document.body`,编译期无法触及这些 DOM。运行时模块通过 `MutationObserver` 监听 body 下的 DOM 变化,匹配 ant-design 面板的 CSS class,自动为面板内元素注入与触发器关联的 testId。
473
+ Uses Vue's compiler `nodeTransform` hook at AST stage to inject `data-testid="{tagName}-{index}"` on custom business components. Since injection happens in the parent template, testids are automatically passed through to the child component's root element via Vue's `fallthroughAttrs` mechanism.
370
474
 
371
- 对于 DatePicker 这种 `inheritAttrs: false` 的组件,`data-testid` 在编译期注入后无法到达 DOM。插件通过同时注入 `id` 属性(该属性被 ant-design-vue 显式转发到内部 `<input>`)作为兜底标识。
475
+ For multi-root components (Fragment), `inheritAttrs` is not effective. `createMultiRootFixTransform` automatically adds `v-bind="$attrs"` to the first antd component root element to ensure pass-through.
372
476
 
373
477
  ---
374
478