vite-plugin-vue-testid 1.0.0 → 1.0.1
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/README.en.md +361 -0
- package/dist/runtime.js +1 -1
- package/dist/runtime.js.map +1 -1
- package/package.json +3 -2
package/README.en.md
ADDED
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
# vite-plugin-vue-testid
|
|
2
|
+
|
|
3
|
+
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.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Compile-time injection** — Automatically adds `data-testid` to component templates via Vue compiler `nodeTransform`
|
|
8
|
+
- **Runtime injection** — Dynamically injects testIds into ant-design-vue teleported overlays (DatePicker, Cascader, Select, TreeSelect, etc.)
|
|
9
|
+
- **`<a-table>` support** — bodyCell slot components receive unique testIds in the format `columnName + rowIndex + componentName + counter`
|
|
10
|
+
- **Auto index destructuring** — If `#bodyCell="{ column, record }"` is missing `index`, the plugin patches it automatically
|
|
11
|
+
- **Preserves existing testIds** — Elements with manually assigned `data-testid` are skipped
|
|
12
|
+
- **Dual injection for teleport components** — DatePicker / RangePicker / Modal receive both `id` + `data-testid`
|
|
13
|
+
- **Date panel mode awareness** — date / month / year / decade panels are distinguished automatically
|
|
14
|
+
- **MutationObserver re-injection** — New DOM elements from panel switching, tree node expand/collapse are detected and injected automatically
|
|
15
|
+
- **Extensible** — Custom component lists, testId generation rules, and overlay injection strategies are supported
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pnpm add -D vite-plugin-vue-testid
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
> Requirements: `vite >= 5.0.0`, `vue >= 3.3.0`, `@vue/compiler-core >= 3.5.0`
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
### 1. Compile-time injection (`vite.config.ts`)
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
// vite.config.ts
|
|
35
|
+
import { defineConfig } from 'vite'
|
|
36
|
+
import vue from '@vitejs/plugin-vue'
|
|
37
|
+
import { createVueTestIdTransform } from 'vite-plugin-vue-testid'
|
|
38
|
+
|
|
39
|
+
export default defineConfig({
|
|
40
|
+
plugins: [
|
|
41
|
+
vue({
|
|
42
|
+
template: {
|
|
43
|
+
compilerOptions: {
|
|
44
|
+
nodeTransforms: [createVueTestIdTransform()]
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
]
|
|
49
|
+
})
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### 2. Runtime injection (`main.ts`)
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
// main.ts
|
|
56
|
+
import { createApp } from 'vue'
|
|
57
|
+
import App from './App.vue'
|
|
58
|
+
import { setupAntDropdownTestIds } from 'vite-plugin-vue-testid/runtime'
|
|
59
|
+
|
|
60
|
+
const app = createApp(App)
|
|
61
|
+
app.mount('#app')
|
|
62
|
+
|
|
63
|
+
// Call after mount to observe teleported panels and auto-inject testIds
|
|
64
|
+
setupAntDropdownTestIds()
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Generated testId Examples
|
|
70
|
+
|
|
71
|
+
### Regular Components
|
|
72
|
+
|
|
73
|
+
```html
|
|
74
|
+
<!-- Source -->
|
|
75
|
+
<a-input />
|
|
76
|
+
<a-select />
|
|
77
|
+
<a-button />
|
|
78
|
+
|
|
79
|
+
<!-- Compiled -->
|
|
80
|
+
<a-input data-testid="a-input-0" />
|
|
81
|
+
<a-select data-testid="a-select-1" />
|
|
82
|
+
<a-button data-testid="a-button-2" />
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Teleport Components (Dual Injection)
|
|
86
|
+
|
|
87
|
+
```html
|
|
88
|
+
<!-- Source -->
|
|
89
|
+
<a-date-picker />
|
|
90
|
+
<a-modal v-model:visible="show" title="Notice" />
|
|
91
|
+
|
|
92
|
+
<!-- Compiled -->
|
|
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" />
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### `<a-table>` bodyCell Components
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
Format: ${column.dataIndex}-${index}-${componentName}-${counter}
|
|
101
|
+
|
|
102
|
+
Examples:
|
|
103
|
+
- label-0-a-input-4 (label column, row 0)
|
|
104
|
+
- value-2-a-input-5 (value column, row 2)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Runtime DatePicker Panel
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
a-date-picker-0-dropdown # panel container
|
|
111
|
+
a-date-picker-0-dropdown-prev # previous month
|
|
112
|
+
a-date-picker-0-dropdown-next # next month
|
|
113
|
+
a-date-picker-0-dropdown-header-date # header container
|
|
114
|
+
a-date-picker-0-dropdown-header-month-btn # header month button
|
|
115
|
+
a-date-picker-0-dropdown-header-year-btn # header year button
|
|
116
|
+
a-date-picker-0-dropdown-date-2026-06-15 # date cell
|
|
117
|
+
a-date-picker-0-dropdown-ok # OK button
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
After switching to month/year panel (auto re-injected by MutationObserver):
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
a-date-picker-0-dropdown-header-month # month panel header
|
|
124
|
+
a-date-picker-0-dropdown-month-06 # June
|
|
125
|
+
a-date-picker-0-dropdown-header-year # year panel header
|
|
126
|
+
a-date-picker-0-dropdown-year-2026 # year 2026
|
|
127
|
+
a-date-picker-0-dropdown-decade-2020-2029 # decade 2020-2029
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Runtime RangePicker Panel
|
|
131
|
+
|
|
132
|
+
```
|
|
133
|
+
a-range-picker-0-dropdown-prev-left # left panel prev month
|
|
134
|
+
a-range-picker-0-dropdown-date-2026-05-15-left # left panel date
|
|
135
|
+
a-range-picker-0-dropdown-date-2026-06-20-right # right panel date
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Runtime TimePicker
|
|
139
|
+
|
|
140
|
+
```
|
|
141
|
+
a-time-picker-0-dropdown-time-column-0 # hour column
|
|
142
|
+
a-time-picker-0-dropdown-time-08 # 08 hour
|
|
143
|
+
a-time-picker-0-dropdown-time-column-1 # minute column
|
|
144
|
+
a-time-picker-0-dropdown-time-30 # 30 minutes
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Runtime Cascader
|
|
148
|
+
|
|
149
|
+
```
|
|
150
|
+
a-cascader-0-dropdown # panel container
|
|
151
|
+
a-cascader-0-dropdown-menu-0 # level 1 menu
|
|
152
|
+
a-cascader-0-dropdown-item-Zhejiang # Zhejiang option
|
|
153
|
+
a-cascader-0-dropdown-menu-1 # level 2 menu
|
|
154
|
+
a-cascader-0-dropdown-item-Hangzhou # Hangzhou option
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Runtime Select
|
|
158
|
+
|
|
159
|
+
```
|
|
160
|
+
a-select-0-dropdown # dropdown container
|
|
161
|
+
a-select-0-dropdown-option-Option1 # option
|
|
162
|
+
a-select-0-dropdown-option-Option2 # option
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Runtime TreeSelect
|
|
166
|
+
|
|
167
|
+
```
|
|
168
|
+
a-tree-select-0-dropdown # dropdown container
|
|
169
|
+
a-tree-select-0-dropdown-tree-root-1 # root node
|
|
170
|
+
a-tree-select-0-dropdown-tree-parent-1 # parent node (injected after expand)
|
|
171
|
+
a-tree-select-0-dropdown-tree-my-leaf # leaf node
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## Configuration
|
|
177
|
+
|
|
178
|
+
### Compile-time Options
|
|
179
|
+
|
|
180
|
+
```ts
|
|
181
|
+
import { createVueTestIdTransform } from 'vite-plugin-vue-testid'
|
|
182
|
+
|
|
183
|
+
createVueTestIdTransform({
|
|
184
|
+
/**
|
|
185
|
+
* Component tag names to inject testIds into (kebab-case)
|
|
186
|
+
* @default see "Default Supported Components" below
|
|
187
|
+
*/
|
|
188
|
+
components: ['a-input', 'a-select', 'my-button'],
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Custom testId generation function
|
|
192
|
+
* @param tagName component tag name
|
|
193
|
+
* @param count global increment counter
|
|
194
|
+
* @returns testId string
|
|
195
|
+
*/
|
|
196
|
+
generateId: (tagName, count) => `myapp-${tagName}-${count}`,
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* testId attribute name (use 'data-cy' for Cypress)
|
|
200
|
+
* @default 'data-testid'
|
|
201
|
+
*/
|
|
202
|
+
attributeName: 'data-cy',
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Components that also receive an id attribute
|
|
206
|
+
* @default ['a-date-picker', 'a-range-picker', 'a-time-picker', 'a-modal']
|
|
207
|
+
*/
|
|
208
|
+
idComponents: ['a-date-picker', 'a-modal'],
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* id attribute name
|
|
212
|
+
* @default 'id'
|
|
213
|
+
*/
|
|
214
|
+
idAttributeName: 'id',
|
|
215
|
+
})
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Runtime Options
|
|
219
|
+
|
|
220
|
+
```ts
|
|
221
|
+
import { setupAntDropdownTestIds } from 'vite-plugin-vue-testid/runtime'
|
|
222
|
+
|
|
223
|
+
// Default config
|
|
224
|
+
const cleanup = setupAntDropdownTestIds()
|
|
225
|
+
|
|
226
|
+
// Custom config
|
|
227
|
+
const cleanup = setupAntDropdownTestIds({
|
|
228
|
+
/**
|
|
229
|
+
* testId attribute name, must match compile-time config
|
|
230
|
+
* @default 'data-testid'
|
|
231
|
+
*/
|
|
232
|
+
attributeName: 'data-testid',
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Custom panel selector → injection strategy mapping
|
|
236
|
+
* key: CSS selector
|
|
237
|
+
* value: injection function; set to undefined to disable a built-in panel
|
|
238
|
+
*/
|
|
239
|
+
panels: {
|
|
240
|
+
'.ant-picker-dropdown': undefined, // disable built-in Picker panel injection
|
|
241
|
+
'.my-custom-dropdown': (ctx) => {
|
|
242
|
+
// ctx.container — panel container HTMLElement
|
|
243
|
+
// ctx.trigger — trigger HTMLElement | null
|
|
244
|
+
// ctx.triggerTestId — the trigger's testId value
|
|
245
|
+
// ctx.attrName — testId attribute name
|
|
246
|
+
const prefix = `${ctx.triggerTestId}-dropdown`
|
|
247
|
+
ctx.container.setAttribute(ctx.attrName, prefix)
|
|
248
|
+
// ... custom injection logic
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
// Clean up observer on component unmount
|
|
254
|
+
onUnmounted(cleanup)
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### One-off Manual Injection
|
|
258
|
+
|
|
259
|
+
```ts
|
|
260
|
+
import { injectCurrentDropdowns } from 'vite-plugin-vue-testid/runtime'
|
|
261
|
+
|
|
262
|
+
// For SSR / hydration scenarios where MutationObserver is not desired
|
|
263
|
+
injectCurrentDropdowns({ attributeName: 'data-testid' })
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## Default Supported Components
|
|
269
|
+
|
|
270
|
+
| Component | Compile-time | Runtime |
|
|
271
|
+
|-----------|:------------:|:-------:|
|
|
272
|
+
| `a-input` | ✅ | — |
|
|
273
|
+
| `a-textarea` | ✅ | — |
|
|
274
|
+
| `a-input-number` | ✅ | — |
|
|
275
|
+
| `a-select` | ✅ | ✅ dropdown |
|
|
276
|
+
| `a-cascader` | ✅ | ✅ cascader panel |
|
|
277
|
+
| `a-tree-select` | ✅ | ✅ tree select panel |
|
|
278
|
+
| `a-radio-group` | ✅ | — |
|
|
279
|
+
| `a-checkbox-group` | ✅ | — |
|
|
280
|
+
| `a-date-picker` | ✅ id+testid | ✅ date panel |
|
|
281
|
+
| `a-range-picker` | ✅ id+testid | ✅ dual panel |
|
|
282
|
+
| `a-time-picker` | ✅ id+testid | ✅ time panel |
|
|
283
|
+
| `a-switch` | ✅ | — |
|
|
284
|
+
| `a-slider` | ✅ | — |
|
|
285
|
+
| `a-rate` | ✅ | — |
|
|
286
|
+
| `a-upload` | ✅ | — |
|
|
287
|
+
| `a-form-item` | ✅ | — |
|
|
288
|
+
| `a-button` | ✅ | — |
|
|
289
|
+
| `a-modal` | ✅ id+testid | — |
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
## Custom Panel Injection Strategies
|
|
294
|
+
|
|
295
|
+
Support overlays from other UI libraries by providing custom strategies:
|
|
296
|
+
|
|
297
|
+
```ts
|
|
298
|
+
import type { PanelInjectStrategy } from 'vite-plugin-vue-testid/runtime'
|
|
299
|
+
|
|
300
|
+
// Example: inject testIds into Element Plus overlays
|
|
301
|
+
const injectElDropdown: PanelInjectStrategy = (ctx) => {
|
|
302
|
+
const { container, triggerTestId, attrName } = ctx
|
|
303
|
+
const prefix = `${triggerTestId}-dropdown`
|
|
304
|
+
|
|
305
|
+
container.setAttribute(attrName, prefix)
|
|
306
|
+
|
|
307
|
+
container.querySelectorAll('.el-select-dropdown__item').forEach((item) => {
|
|
308
|
+
const el = item as HTMLElement
|
|
309
|
+
const text = el.textContent?.trim() || ''
|
|
310
|
+
el.setAttribute(attrName, `${prefix}-option-${text}`)
|
|
311
|
+
})
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
setupAntDropdownTestIds({
|
|
315
|
+
panels: {
|
|
316
|
+
'.el-select-dropdown': injectElDropdown,
|
|
317
|
+
'.el-cascader-dropdown': injectElDropdown,
|
|
318
|
+
},
|
|
319
|
+
})
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
---
|
|
323
|
+
|
|
324
|
+
## API Reference
|
|
325
|
+
|
|
326
|
+
### Compile-time
|
|
327
|
+
|
|
328
|
+
| Export | Type | Description |
|
|
329
|
+
|--------|------|-------------|
|
|
330
|
+
| `createVueTestIdTransform(opts?)` | `(node: RootNode \| TemplateChildNode) => void` | Creates a Vue compiler nodeTransform |
|
|
331
|
+
| `VueTestIdTransformOptions` | `interface` | Compile-time options type |
|
|
332
|
+
|
|
333
|
+
### Runtime (`/runtime` subpath)
|
|
334
|
+
|
|
335
|
+
| Export | Type | Description |
|
|
336
|
+
|--------|------|-------------|
|
|
337
|
+
| `setupAntDropdownTestIds(opts?)` | `() => void` | Starts MutationObserver to auto-inject overlay testIds, returns cleanup function |
|
|
338
|
+
| `injectCurrentDropdowns(opts?)` | `void` | One-off manual injection of currently visible overlays |
|
|
339
|
+
| `RuntimeOptions` | `interface` | Runtime options type |
|
|
340
|
+
| `PanelContext` | `interface` | Panel injection context type |
|
|
341
|
+
| `PanelInjectStrategy` | `(ctx: PanelContext) => void` | Panel injection strategy function type |
|
|
342
|
+
|
|
343
|
+
---
|
|
344
|
+
|
|
345
|
+
## How It Works
|
|
346
|
+
|
|
347
|
+
### Compile-time
|
|
348
|
+
|
|
349
|
+
Uses Vue's compiler `nodeTransform` hook to traverse template nodes at AST stage. When target component tags are matched, `data-testid` attribute nodes are appended directly. Since injection happens at compile time, there is zero runtime performance impact.
|
|
350
|
+
|
|
351
|
+
### Runtime
|
|
352
|
+
|
|
353
|
+
ant-design-vue 4.x components like DatePicker, Select, and Cascader use Teleport to render overlays into `document.body`, making them unreachable at compile time. The runtime module uses `MutationObserver` to watch DOM changes under `body`, matches ant-design panel CSS classes, and automatically injects trigger-associated testIds into panel elements.
|
|
354
|
+
|
|
355
|
+
For components like DatePicker with `inheritAttrs: false`, `data-testid` injected at compile time cannot reach the DOM. The plugin works around this by also injecting an `id` attribute (which ant-design-vue explicitly forwards to the internal `<input>`) as a fallback identifier.
|
|
356
|
+
|
|
357
|
+
---
|
|
358
|
+
|
|
359
|
+
## License
|
|
360
|
+
|
|
361
|
+
MIT
|
package/dist/runtime.js
CHANGED
|
@@ -118,7 +118,7 @@ function injectPickerPanel(ctx) {
|
|
|
118
118
|
const li = item;
|
|
119
119
|
if (li.hasAttribute(attrName)) return;
|
|
120
120
|
const text = li.textContent?.trim() || "";
|
|
121
|
-
li.setAttribute(attrName, `${prefix}-time-${text}`);
|
|
121
|
+
li.setAttribute(attrName, `${prefix}-time-${i}-${text}`);
|
|
122
122
|
});
|
|
123
123
|
});
|
|
124
124
|
const setFooterBtn = (selector, name) => {
|
package/dist/runtime.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/runtime.ts"],"sourcesContent":["/**\n * 运行时辅助模块:为 ant-design-vue teleport 弹出层(日期选择器、级联选择器等)\n * 中的 DOM 元素注入 data-testid,解决编译期无法处理的定位问题。\n *\n * 使用方式(在 main.ts 或入口文件中调用一次即可):\n * ```ts\n * import { setupAntDropdownTestIds } from 'vite-plugin-vue-testid/runtime'\n *\n * setupAntDropdownTestIds()\n * ```\n */\n\nexport interface RuntimeOptions {\n /**\n * testId 属性名,需与编译期插件保持一致\n * @default 'data-testid'\n */\n attributeName?: string\n\n /**\n * 需要监听的 teleport 面板 CSS 选择器映射\n * key: 面板容器选择器\n * value: 注入策略(自定义函数 或 undefined 使用内置策略)\n */\n panels?: Record<string, PanelInjectStrategy | undefined>\n}\n\nexport interface PanelContext {\n /** 面板容器 DOM 元素 */\n container: HTMLElement\n /** 当前激活的触发器元素 */\n trigger: HTMLElement | null\n /** 触发器的 testId 值 */\n triggerTestId: string\n /** testId 属性名 */\n attrName: string\n}\n\nexport type PanelInjectStrategy = (ctx: PanelContext) => void\n\n// ──────────────────────── 内置注入策略 ────────────────────────\n\n/**\n * 判断当前 PickerPanel 处于哪种展示模式。\n * ant-design 通过 CSS 类区分:date / month / year / decade\n */\nfunction getPanelMode(panel: Element): 'date' | 'month' | 'year' | 'decade' {\n if (panel.classList.contains('ant-picker-month-panel')) return 'month'\n if (panel.classList.contains('ant-picker-year-panel')) return 'year'\n if (panel.classList.contains('ant-picker-decade-panel')) return 'decade'\n return 'date'\n}\n\n/** 月份英文缩写 */\nconst MONTH_NAMES = ['jan', 'feb', 'mar', 'apr', 'may', 'jun',\n 'jul', 'aug', 'sep', 'oct', 'nov', 'dec']\n\n/**\n * 为 header-view 内部的独立按钮注入 testId\n * ant-design 中 header-view 在 date 模式下包含两个按钮:\n * - 月份按钮(如 \"Jun\" / \"6月\")→ header-month-btn\n * - 年份按钮(如 \"2026\") → header-year-btn\n */\nfunction injectHeaderViewButtons(\n panel: Element,\n prefix: string,\n attrName: string,\n suffix: string,\n): void {\n const headerView = panel.querySelector('.ant-picker-header-view')\n if (!headerView) return\n\n const buttons = headerView.querySelectorAll('button')\n buttons.forEach((btn) => {\n const el = btn as HTMLElement\n if (el.hasAttribute(attrName)) return\n\n const text = el.textContent?.trim().toLowerCase() || ''\n\n let name: string\n if (MONTH_NAMES.some((m) => text.startsWith(m))) {\n // 匹配 Jan/Feb/... 英文月份缩写\n name = 'header-month-btn'\n } else if (/^\\d{1,2}月?$/.test(text)) {\n // 匹配 \"1月\" \"2月\" ... \"12月\" 或纯数字 \"01\" \"1\" ~ \"12\"\n name = 'header-month-btn'\n } else if (/^\\d{4}$/.test(text)) {\n // 4 位数字年份如 \"2026\"\n name = 'header-year-btn'\n } else if (/^\\d{4}-\\d{4}$/.test(text)) {\n // 年代范围如 \"2020-2029\"\n name = 'header-decade-btn'\n } else {\n // 兜底:直接用文本内容\n name = `header-btn-${text}`\n }\n\n el.setAttribute(attrName, `${prefix}-${name}${suffix}`)\n })\n}\n\n/** 已绑定 MutationObserver 的容器,避免重复绑定 */\nconst _observedContainers = new WeakSet<Element>()\n\n/**\n * 为单个 PickerPanel 注入全部 testId:\n * - header 按钮(super-prev/prev/next/super-next)\n * - header-view(带模式后缀:header-date / header-month / header-year)\n * - header-view 内部按钮(header-month-btn / header-year-btn)\n * - 单元格(模式区分:date-xxx / month-xx / year-xxxx 等)\n */\nfunction injectSinglePanel(\n panel: Element,\n prefix: string,\n attrName: string,\n suffix: '' | '-left' | '-right',\n): void {\n const mode = getPanelMode(panel)\n\n const setAttr = (selector: string, name: string) => {\n const el = panel.querySelector(selector) as HTMLElement | null\n if (el && !el.hasAttribute(attrName)) {\n el.setAttribute(attrName, `${prefix}-${name}${suffix}`)\n }\n }\n\n setAttr('.ant-picker-header-super-prev-btn', 'super-prev')\n setAttr('.ant-picker-header-prev-btn', 'prev')\n setAttr('.ant-picker-header-next-btn', 'next')\n setAttr('.ant-picker-header-super-next-btn', 'super-next')\n setAttr('.ant-picker-header-view', `header-${mode}`)\n\n injectHeaderViewButtons(panel, prefix, attrName, suffix)\n injectPanelCells(panel, prefix, attrName, suffix)\n}\n\n/**\n * 根据面板当前模式注入单元格 testId\n *\n * date → data-testid=\"{prefix}-date-2026-06-15{suffix}\"\n * month → data-testid=\"{prefix}-month-01{suffix}\"\n * year → data-testid=\"{prefix}-year-2026{suffix}\"\n * decade → data-testid=\"{prefix}-decade-2020-2029{suffix}\"\n */\nfunction injectPanelCells(\n panel: Element,\n prefix: string,\n attrName: string,\n suffix: string,\n): void {\n const mode = getPanelMode(panel)\n const cells = panel.querySelectorAll('.ant-picker-cell-inner')\n\n cells.forEach((cell) => {\n const el = cell as HTMLElement\n if (el.hasAttribute(attrName)) return\n\n const raw = (el.getAttribute('title') || el.textContent?.trim() || '').replace(/\\s+/g, '-')\n let label: string\n\n switch (mode) {\n case 'month':\n // antd 月份单元格 title 一般是 \"2026-01\",取后两位即可\n label = raw.length >= 2 ? raw.slice(-2) : raw\n el.setAttribute(attrName, `${prefix}-month-${label}${suffix}`)\n break\n case 'year':\n label = raw\n el.setAttribute(attrName, `${prefix}-year-${label}${suffix}`)\n break\n case 'decade':\n label = raw\n el.setAttribute(attrName, `${prefix}-decade-${label}${suffix}`)\n break\n default:\n // date 模式\n label = raw\n el.setAttribute(attrName, `${prefix}-date-${label}${suffix}`)\n break\n }\n })\n}\n\n/**\n * ant-design-vue DatePicker / RangePicker / TimePicker 面板策略\n *\n * DatePicker:单面板,直接注入\n * RangePicker:左右双面板,分别标记 left / right\n * TimePicker:时间列,逐项注入\n */\nfunction injectPickerPanel(ctx: PanelContext): void {\n const { container, triggerTestId, attrName } = ctx\n const prefix = `${triggerTestId}-dropdown`\n\n // 面板容器本身\n container.setAttribute(attrName, prefix)\n\n const isRange = container.classList.contains('ant-picker-dropdown-range')\n\n /**\n * 注入容器内所有子面板(header+单元格)\n * 同时注入顶部/底部固定元素(preset、footer buttons 等)\n */\n const injectAllPanels = () => {\n const panels = container.querySelectorAll('.ant-picker-panel')\n if (isRange && panels.length >= 2) {\n injectSinglePanel(panels[0], prefix, attrName, '-left')\n injectSinglePanel(panels[1], prefix, attrName, '-right')\n } else {\n const panel = panels[0] || container\n injectSinglePanel(panel, prefix, attrName, '')\n }\n\n // preset 快捷范围标签\n const presets = container.querySelectorAll('.ant-picker-presets .ant-tag')\n presets.forEach((tag) => {\n const el = tag as HTMLElement\n if (el.hasAttribute(attrName)) return\n const text = el.textContent?.trim() || ''\n el.setAttribute(attrName, `${prefix}-preset-${text}`)\n })\n\n // 时间选择器的时间列\n const timeColumns = container.querySelectorAll('.ant-picker-time-panel-column')\n timeColumns.forEach((col, i) => {\n const el = col as HTMLElement\n if (el.hasAttribute(attrName)) return\n el.setAttribute(attrName, `${prefix}-time-column-${i}`)\n const items = col.querySelectorAll('li')\n items.forEach((item) => {\n const li = item as HTMLElement\n if (li.hasAttribute(attrName)) return\n const text = li.textContent?.trim() || ''\n li.setAttribute(attrName, `${prefix}-time-${text}`)\n })\n })\n\n // 底部按钮\n const setFooterBtn = (selector: string, name: string) => {\n const btn = container.querySelector(selector) as HTMLElement | null\n if (btn && !btn.hasAttribute(attrName)) {\n btn.setAttribute(attrName, `${prefix}-${name}`)\n }\n }\n setFooterBtn('.ant-picker-ok button', 'ok')\n setFooterBtn('.ant-picker-now button', 'now')\n setFooterBtn('.ant-picker-now-btn', 'now')\n }\n\n // 首次注入\n injectAllPanels()\n\n // ── 容器级 MutationObserver ──\n // 绑定到 dropdown 容器而非单个 panel,这样即便 ant-design\n // 在 date↔month↔year 切换时替换了 panel 元素,也能检测到变化并重注入\n if (!_observedContainers.has(container)) {\n _observedContainers.add(container)\n\n let timer: ReturnType<typeof setTimeout> | null = null\n\n const observer = new MutationObserver(() => {\n if (timer !== null) clearTimeout(timer)\n timer = setTimeout(() => {\n injectAllPanels()\n }, 50) // 等 Vue nextTick + DOM 全部更新\n })\n\n observer.observe(container, { childList: true, subtree: true })\n }\n}\n\n/**\n * ant-design-vue Cascader 面板策略\n */\nfunction injectCascaderPanel(ctx: PanelContext): void {\n const { container, triggerTestId, attrName } = ctx\n const prefix = `${triggerTestId}-dropdown`\n\n container.setAttribute(attrName, prefix)\n\n const menus = container.querySelectorAll('.ant-cascader-menu')\n menus.forEach((menu, i) => {\n const el = menu as HTMLElement\n el.setAttribute(attrName, `${prefix}-menu-${i}`)\n const items = menu.querySelectorAll('.ant-cascader-menu-item')\n items.forEach((item) => {\n const li = item as HTMLElement\n const text = li.textContent?.trim() || ''\n li.setAttribute(attrName, `${prefix}-item-${text}`)\n })\n })\n}\n\n/**\n * ant-design-vue Select / TreeSelect 下拉面板策略\n */\nfunction injectSelectPanel(ctx: PanelContext): void {\n const { container, triggerTestId, attrName } = ctx\n const prefix = `${triggerTestId}-dropdown`\n\n container.setAttribute(attrName, prefix)\n\n /** 注入容器内所有选项/树节点的 testId */\n const injectAllItems = () => {\n const options = container.querySelectorAll('.ant-select-item-option')\n options.forEach((opt) => {\n const el = opt as HTMLElement\n if (el.hasAttribute(attrName)) return\n const title = el.getAttribute('title') || el.textContent?.trim() || ''\n el.setAttribute(attrName, `${prefix}-option-${title}`)\n })\n\n const treeNodes = container.querySelectorAll('.ant-select-tree-node-content-wrapper')\n treeNodes.forEach((node) => {\n const el = node as HTMLElement\n if (el.hasAttribute(attrName)) return\n const title = el.getAttribute('title') || el.textContent?.trim() || ''\n el.setAttribute(attrName, `${prefix}-tree-${title}`)\n })\n }\n\n // 首次注入\n injectAllItems()\n\n // TreeSelect 展开/折叠子节点时会动态增删 DOM,需要 Observer 持续注入\n if (!_observedContainers.has(container)) {\n _observedContainers.add(container)\n\n let timer: ReturnType<typeof setTimeout> | null = null\n\n const observer = new MutationObserver(() => {\n if (timer !== null) clearTimeout(timer)\n timer = setTimeout(() => {\n injectAllItems()\n }, 50)\n })\n\n observer.observe(container, { childList: true, subtree: true })\n }\n}\n\n// ──────────────────────── 默认面板配置 ────────────────────────\n\nconst DEFAULT_PANELS: Record<string, PanelInjectStrategy> = {\n '.ant-picker-dropdown': injectPickerPanel,\n '.ant-cascader-dropdown': injectCascaderPanel,\n '.ant-select-dropdown': injectSelectPanel,\n}\n\n// ──────────────────────── Observer ────────────────────────\n\n/**\n * 获取触发器上的 testId 值。\n * ant-design-vue 4.x DatePicker 由于 inheritAttrs: false,data-testid 被丢弃,\n * 只有 id 被转发到了内部 input。因为编译期插件同时注入 id + data-testid(值相同),\n * 优先读 testid,兜底读 id。\n */\nfunction getTriggerTestId(trigger: HTMLElement | null, attrName: string): string {\n if (!trigger) return 'unknown-picker'\n return trigger.getAttribute(attrName) || trigger.getAttribute('id') || 'unknown-picker'\n}\n\n/**\n * 查找当前激活的触发器元素\n */\nfunction findActiveTrigger(attrName: string): HTMLElement | null {\n // 日期选择器:有 .ant-picker-focused 类\n const focused = document.querySelector('.ant-picker-focused') as HTMLElement | null\n if (focused) {\n // 1) focused 根元素自身有 testid\n if (focused.hasAttribute(attrName)) return focused\n // 2) 内部子元素有 testid(ant-design-vue 4.x 通过 getDataOrAriaProps 转发到 input)\n const inner = focused.querySelector(`[${attrName}]`) as HTMLElement | null\n if (inner) return inner\n // 3) ant-design-vue 4.x DatePicker: data-testid 被丢弃(不在 Picker 的 declared props 中),\n // 但 id 属性被显式转发到了内部 <input>。由于插件同时注入 id + data-testid 且值相同,\n // 可以通过 input[id] 找到触发器\n const input = focused.querySelector('input[id]') as HTMLElement | null\n if (input) return input\n }\n\n // Select / TreeSelect:展开时根 div 有 .ant-select-open 类,且 data-testid 正常到达 DOM\n const selectOpen = document.querySelector('.ant-select-open') as HTMLElement | null\n if (selectOpen?.hasAttribute(attrName)) return selectOpen\n\n // 级联选择器\n const cascader = document.querySelector('.ant-cascader-picker-focused') as HTMLElement | null\n if (cascader?.hasAttribute(attrName)) return cascader\n\n // 通用:查找有 testId 的最近打开的面板触发器\n // ant-design 通常在展开面板的元素上添加 aria-expanded=\"true\"\n const expanded = document.querySelector(\n `[${attrName}][aria-expanded=\"true\"]`,\n ) as HTMLElement | null\n if (expanded) return expanded\n\n return null\n}\n\n/**\n * 为 teleport 弹出层面板注入 testId。\n * 使用 MutationObserver 监听 DOM 新增节点,自动识别 ant-design 弹出面板。\n *\n * @example\n * ```ts\n * // main.ts\n * import { setupAntDropdownTestIds } from 'vite-plugin-vue-testid/runtime'\n *\n * createApp(App).mount('#app')\n * setupAntDropdownTestIds()\n * ```\n *\n * testId 注入结果示例:\n * ```\n * // 日期面板(初始)\n * a-date-picker-0-dropdown // 面板容器\n * a-date-picker-0-dropdown-prev // 上月/上一级\n * a-date-picker-0-dropdown-next // 下月/下一级\n * a-date-picker-0-dropdown-header-date // header 容器\n * a-date-picker-0-dropdown-header-month-btn // header 内的月份按钮(\"Jun\"/\"6月\")\n * a-date-picker-0-dropdown-header-year-btn // header 内的年份按钮(\"2026\")\n * a-date-picker-0-dropdown-date-2026-06-15 // 日期单元格\n * a-date-picker-0-dropdown-ok // 确定按钮\n *\n * // 点击月份按钮 → 月面板(MutationObserver 自动重注入)\n * a-date-picker-0-dropdown-header-month // header 容器\n * a-date-picker-0-dropdown-header-year-btn // header 内的年份按钮\n * a-date-picker-0-dropdown-month-06 // 6月按钮\n *\n * // 点击年份按钮 → 年面板\n * a-date-picker-0-dropdown-header-year // header 容器\n * a-date-picker-0-dropdown-year-2026 // 2026年按钮\n * ```\n */\nexport function setupAntDropdownTestIds(opts: RuntimeOptions = {}): () => void {\n const attrName = opts.attributeName ?? 'data-testid'\n const panels = { ...DEFAULT_PANELS, ...opts.panels }\n\n const observer = new MutationObserver((mutations) => {\n for (const mutation of mutations) {\n for (const node of mutation.addedNodes) {\n if (!(node instanceof HTMLElement)) continue\n\n // 检查新增节点匹配哪种面板选择器\n for (const [selector, strategy] of Object.entries(panels)) {\n const containers = node.matches(selector)\n ? [node]\n : Array.from(node.querySelectorAll(selector))\n\n for (const container of containers) {\n if (!(container instanceof HTMLElement)) continue\n if (container.hasAttribute(attrName)) continue // 已注入过\n\n // 等一个微任务让 CSS 类生效,再找触发器\n if (strategy) {\n queueMicrotask(() => {\n const trigger = findActiveTrigger(attrName)\n const triggerTestId = getTriggerTestId(trigger, attrName)\n\n const ctx: PanelContext = {\n container,\n trigger,\n triggerTestId,\n attrName,\n }\n strategy(ctx)\n })\n }\n }\n }\n }\n }\n })\n\n observer.observe(document.body, { childList: true, subtree: true })\n\n // 返回清理函数\n return () => observer.disconnect()\n}\n\n/**\n * 手动为当前已存在或即将出现的弹出面板注入 testId(一次性补丁)。\n * 适用于 SSR/hydration 或不想用 MutationObserver 的场景。\n */\nexport function injectCurrentDropdowns(opts: RuntimeOptions = {}): void {\n const attrName = opts.attributeName ?? 'data-testid'\n const panels = { ...DEFAULT_PANELS, ...opts.panels }\n\n for (const [selector, strategy] of Object.entries(panels)) {\n const containers = document.querySelectorAll(selector)\n containers.forEach((container) => {\n if (!(container instanceof HTMLElement)) return\n if (container.hasAttribute(attrName)) return\n\n if (!strategy) return\n\n const trigger = findActiveTrigger(attrName)\n const triggerTestId = getTriggerTestId(trigger, attrName)\n\n strategy({ container, trigger, triggerTestId, attrName })\n })\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/runtime.ts"],"sourcesContent":["/**\n * 运行时辅助模块:为 ant-design-vue teleport 弹出层(日期选择器、级联选择器等)\n * 中的 DOM 元素注入 data-testid,解决编译期无法处理的定位问题。\n *\n * 使用方式(在 main.ts 或入口文件中调用一次即可):\n * ```ts\n * import { setupAntDropdownTestIds } from 'vite-plugin-vue-testid/runtime'\n *\n * setupAntDropdownTestIds()\n * ```\n */\n\nexport interface RuntimeOptions {\n /**\n * testId 属性名,需与编译期插件保持一致\n * @default 'data-testid'\n */\n attributeName?: string\n\n /**\n * 需要监听的 teleport 面板 CSS 选择器映射\n * key: 面板容器选择器\n * value: 注入策略(自定义函数 或 undefined 使用内置策略)\n */\n panels?: Record<string, PanelInjectStrategy | undefined>\n}\n\nexport interface PanelContext {\n /** 面板容器 DOM 元素 */\n container: HTMLElement\n /** 当前激活的触发器元素 */\n trigger: HTMLElement | null\n /** 触发器的 testId 值 */\n triggerTestId: string\n /** testId 属性名 */\n attrName: string\n}\n\nexport type PanelInjectStrategy = (ctx: PanelContext) => void\n\n// ──────────────────────── 内置注入策略 ────────────────────────\n\n/**\n * 判断当前 PickerPanel 处于哪种展示模式。\n * ant-design 通过 CSS 类区分:date / month / year / decade\n */\nfunction getPanelMode(panel: Element): 'date' | 'month' | 'year' | 'decade' {\n if (panel.classList.contains('ant-picker-month-panel')) return 'month'\n if (panel.classList.contains('ant-picker-year-panel')) return 'year'\n if (panel.classList.contains('ant-picker-decade-panel')) return 'decade'\n return 'date'\n}\n\n/** 月份英文缩写 */\nconst MONTH_NAMES = ['jan', 'feb', 'mar', 'apr', 'may', 'jun',\n 'jul', 'aug', 'sep', 'oct', 'nov', 'dec']\n\n/**\n * 为 header-view 内部的独立按钮注入 testId\n * ant-design 中 header-view 在 date 模式下包含两个按钮:\n * - 月份按钮(如 \"Jun\" / \"6月\")→ header-month-btn\n * - 年份按钮(如 \"2026\") → header-year-btn\n */\nfunction injectHeaderViewButtons(\n panel: Element,\n prefix: string,\n attrName: string,\n suffix: string,\n): void {\n const headerView = panel.querySelector('.ant-picker-header-view')\n if (!headerView) return\n\n const buttons = headerView.querySelectorAll('button')\n buttons.forEach((btn) => {\n const el = btn as HTMLElement\n if (el.hasAttribute(attrName)) return\n\n const text = el.textContent?.trim().toLowerCase() || ''\n\n let name: string\n if (MONTH_NAMES.some((m) => text.startsWith(m))) {\n // 匹配 Jan/Feb/... 英文月份缩写\n name = 'header-month-btn'\n } else if (/^\\d{1,2}月?$/.test(text)) {\n // 匹配 \"1月\" \"2月\" ... \"12月\" 或纯数字 \"01\" \"1\" ~ \"12\"\n name = 'header-month-btn'\n } else if (/^\\d{4}$/.test(text)) {\n // 4 位数字年份如 \"2026\"\n name = 'header-year-btn'\n } else if (/^\\d{4}-\\d{4}$/.test(text)) {\n // 年代范围如 \"2020-2029\"\n name = 'header-decade-btn'\n } else {\n // 兜底:直接用文本内容\n name = `header-btn-${text}`\n }\n\n el.setAttribute(attrName, `${prefix}-${name}${suffix}`)\n })\n}\n\n/** 已绑定 MutationObserver 的容器,避免重复绑定 */\nconst _observedContainers = new WeakSet<Element>()\n\n/**\n * 为单个 PickerPanel 注入全部 testId:\n * - header 按钮(super-prev/prev/next/super-next)\n * - header-view(带模式后缀:header-date / header-month / header-year)\n * - header-view 内部按钮(header-month-btn / header-year-btn)\n * - 单元格(模式区分:date-xxx / month-xx / year-xxxx 等)\n */\nfunction injectSinglePanel(\n panel: Element,\n prefix: string,\n attrName: string,\n suffix: '' | '-left' | '-right',\n): void {\n const mode = getPanelMode(panel)\n\n const setAttr = (selector: string, name: string) => {\n const el = panel.querySelector(selector) as HTMLElement | null\n if (el && !el.hasAttribute(attrName)) {\n el.setAttribute(attrName, `${prefix}-${name}${suffix}`)\n }\n }\n\n setAttr('.ant-picker-header-super-prev-btn', 'super-prev')\n setAttr('.ant-picker-header-prev-btn', 'prev')\n setAttr('.ant-picker-header-next-btn', 'next')\n setAttr('.ant-picker-header-super-next-btn', 'super-next')\n setAttr('.ant-picker-header-view', `header-${mode}`)\n\n injectHeaderViewButtons(panel, prefix, attrName, suffix)\n injectPanelCells(panel, prefix, attrName, suffix)\n}\n\n/**\n * 根据面板当前模式注入单元格 testId\n *\n * date → data-testid=\"{prefix}-date-2026-06-15{suffix}\"\n * month → data-testid=\"{prefix}-month-01{suffix}\"\n * year → data-testid=\"{prefix}-year-2026{suffix}\"\n * decade → data-testid=\"{prefix}-decade-2020-2029{suffix}\"\n */\nfunction injectPanelCells(\n panel: Element,\n prefix: string,\n attrName: string,\n suffix: string,\n): void {\n const mode = getPanelMode(panel)\n const cells = panel.querySelectorAll('.ant-picker-cell-inner')\n\n cells.forEach((cell) => {\n const el = cell as HTMLElement\n if (el.hasAttribute(attrName)) return\n\n const raw = (el.getAttribute('title') || el.textContent?.trim() || '').replace(/\\s+/g, '-')\n let label: string\n\n switch (mode) {\n case 'month':\n // antd 月份单元格 title 一般是 \"2026-01\",取后两位即可\n label = raw.length >= 2 ? raw.slice(-2) : raw\n el.setAttribute(attrName, `${prefix}-month-${label}${suffix}`)\n break\n case 'year':\n label = raw\n el.setAttribute(attrName, `${prefix}-year-${label}${suffix}`)\n break\n case 'decade':\n label = raw\n el.setAttribute(attrName, `${prefix}-decade-${label}${suffix}`)\n break\n default:\n // date 模式\n label = raw\n el.setAttribute(attrName, `${prefix}-date-${label}${suffix}`)\n break\n }\n })\n}\n\n/**\n * ant-design-vue DatePicker / RangePicker / TimePicker 面板策略\n *\n * DatePicker:单面板,直接注入\n * RangePicker:左右双面板,分别标记 left / right\n * TimePicker:时间列,逐项注入\n */\nfunction injectPickerPanel(ctx: PanelContext): void {\n const { container, triggerTestId, attrName } = ctx\n const prefix = `${triggerTestId}-dropdown`\n\n // 面板容器本身\n container.setAttribute(attrName, prefix)\n\n const isRange = container.classList.contains('ant-picker-dropdown-range')\n\n /**\n * 注入容器内所有子面板(header+单元格)\n * 同时注入顶部/底部固定元素(preset、footer buttons 等)\n */\n const injectAllPanels = () => {\n const panels = container.querySelectorAll('.ant-picker-panel')\n if (isRange && panels.length >= 2) {\n injectSinglePanel(panels[0], prefix, attrName, '-left')\n injectSinglePanel(panels[1], prefix, attrName, '-right')\n } else {\n const panel = panels[0] || container\n injectSinglePanel(panel, prefix, attrName, '')\n }\n\n // preset 快捷范围标签\n const presets = container.querySelectorAll('.ant-picker-presets .ant-tag')\n presets.forEach((tag) => {\n const el = tag as HTMLElement\n if (el.hasAttribute(attrName)) return\n const text = el.textContent?.trim() || ''\n el.setAttribute(attrName, `${prefix}-preset-${text}`)\n })\n\n // 时间选择器的时间列\n const timeColumns = container.querySelectorAll('.ant-picker-time-panel-column')\n timeColumns.forEach((col, i) => {\n const el = col as HTMLElement\n if (el.hasAttribute(attrName)) return\n el.setAttribute(attrName, `${prefix}-time-column-${i}`)\n const items = col.querySelectorAll('li')\n items.forEach((item) => {\n const li = item as HTMLElement\n if (li.hasAttribute(attrName)) return\n const text = li.textContent?.trim() || ''\n li.setAttribute(attrName, `${prefix}-time-${i}-${text}`)\n })\n })\n\n // 底部按钮\n const setFooterBtn = (selector: string, name: string) => {\n const btn = container.querySelector(selector) as HTMLElement | null\n if (btn && !btn.hasAttribute(attrName)) {\n btn.setAttribute(attrName, `${prefix}-${name}`)\n }\n }\n setFooterBtn('.ant-picker-ok button', 'ok')\n setFooterBtn('.ant-picker-now button', 'now')\n setFooterBtn('.ant-picker-now-btn', 'now')\n }\n\n // 首次注入\n injectAllPanels()\n\n // ── 容器级 MutationObserver ──\n // 绑定到 dropdown 容器而非单个 panel,这样即便 ant-design\n // 在 date↔month↔year 切换时替换了 panel 元素,也能检测到变化并重注入\n if (!_observedContainers.has(container)) {\n _observedContainers.add(container)\n\n let timer: ReturnType<typeof setTimeout> | null = null\n\n const observer = new MutationObserver(() => {\n if (timer !== null) clearTimeout(timer)\n timer = setTimeout(() => {\n injectAllPanels()\n }, 50) // 等 Vue nextTick + DOM 全部更新\n })\n\n observer.observe(container, { childList: true, subtree: true })\n }\n}\n\n/**\n * ant-design-vue Cascader 面板策略\n */\nfunction injectCascaderPanel(ctx: PanelContext): void {\n const { container, triggerTestId, attrName } = ctx\n const prefix = `${triggerTestId}-dropdown`\n\n container.setAttribute(attrName, prefix)\n\n const menus = container.querySelectorAll('.ant-cascader-menu')\n menus.forEach((menu, i) => {\n const el = menu as HTMLElement\n el.setAttribute(attrName, `${prefix}-menu-${i}`)\n const items = menu.querySelectorAll('.ant-cascader-menu-item')\n items.forEach((item) => {\n const li = item as HTMLElement\n const text = li.textContent?.trim() || ''\n li.setAttribute(attrName, `${prefix}-item-${text}`)\n })\n })\n}\n\n/**\n * ant-design-vue Select / TreeSelect 下拉面板策略\n */\nfunction injectSelectPanel(ctx: PanelContext): void {\n const { container, triggerTestId, attrName } = ctx\n const prefix = `${triggerTestId}-dropdown`\n\n container.setAttribute(attrName, prefix)\n\n /** 注入容器内所有选项/树节点的 testId */\n const injectAllItems = () => {\n const options = container.querySelectorAll('.ant-select-item-option')\n options.forEach((opt) => {\n const el = opt as HTMLElement\n if (el.hasAttribute(attrName)) return\n const title = el.getAttribute('title') || el.textContent?.trim() || ''\n el.setAttribute(attrName, `${prefix}-option-${title}`)\n })\n\n const treeNodes = container.querySelectorAll('.ant-select-tree-node-content-wrapper')\n treeNodes.forEach((node) => {\n const el = node as HTMLElement\n if (el.hasAttribute(attrName)) return\n const title = el.getAttribute('title') || el.textContent?.trim() || ''\n el.setAttribute(attrName, `${prefix}-tree-${title}`)\n })\n }\n\n // 首次注入\n injectAllItems()\n\n // TreeSelect 展开/折叠子节点时会动态增删 DOM,需要 Observer 持续注入\n if (!_observedContainers.has(container)) {\n _observedContainers.add(container)\n\n let timer: ReturnType<typeof setTimeout> | null = null\n\n const observer = new MutationObserver(() => {\n if (timer !== null) clearTimeout(timer)\n timer = setTimeout(() => {\n injectAllItems()\n }, 50)\n })\n\n observer.observe(container, { childList: true, subtree: true })\n }\n}\n\n// ──────────────────────── 默认面板配置 ────────────────────────\n\nconst DEFAULT_PANELS: Record<string, PanelInjectStrategy> = {\n '.ant-picker-dropdown': injectPickerPanel,\n '.ant-cascader-dropdown': injectCascaderPanel,\n '.ant-select-dropdown': injectSelectPanel,\n}\n\n// ──────────────────────── Observer ────────────────────────\n\n/**\n * 获取触发器上的 testId 值。\n * ant-design-vue 4.x DatePicker 由于 inheritAttrs: false,data-testid 被丢弃,\n * 只有 id 被转发到了内部 input。因为编译期插件同时注入 id + data-testid(值相同),\n * 优先读 testid,兜底读 id。\n */\nfunction getTriggerTestId(trigger: HTMLElement | null, attrName: string): string {\n if (!trigger) return 'unknown-picker'\n return trigger.getAttribute(attrName) || trigger.getAttribute('id') || 'unknown-picker'\n}\n\n/**\n * 查找当前激活的触发器元素\n */\nfunction findActiveTrigger(attrName: string): HTMLElement | null {\n // 日期选择器:有 .ant-picker-focused 类\n const focused = document.querySelector('.ant-picker-focused') as HTMLElement | null\n if (focused) {\n // 1) focused 根元素自身有 testid\n if (focused.hasAttribute(attrName)) return focused\n // 2) 内部子元素有 testid(ant-design-vue 4.x 通过 getDataOrAriaProps 转发到 input)\n const inner = focused.querySelector(`[${attrName}]`) as HTMLElement | null\n if (inner) return inner\n // 3) ant-design-vue 4.x DatePicker: data-testid 被丢弃(不在 Picker 的 declared props 中),\n // 但 id 属性被显式转发到了内部 <input>。由于插件同时注入 id + data-testid 且值相同,\n // 可以通过 input[id] 找到触发器\n const input = focused.querySelector('input[id]') as HTMLElement | null\n if (input) return input\n }\n\n // Select / TreeSelect:展开时根 div 有 .ant-select-open 类,且 data-testid 正常到达 DOM\n const selectOpen = document.querySelector('.ant-select-open') as HTMLElement | null\n if (selectOpen?.hasAttribute(attrName)) return selectOpen\n\n // 级联选择器\n const cascader = document.querySelector('.ant-cascader-picker-focused') as HTMLElement | null\n if (cascader?.hasAttribute(attrName)) return cascader\n\n // 通用:查找有 testId 的最近打开的面板触发器\n // ant-design 通常在展开面板的元素上添加 aria-expanded=\"true\"\n const expanded = document.querySelector(\n `[${attrName}][aria-expanded=\"true\"]`,\n ) as HTMLElement | null\n if (expanded) return expanded\n\n return null\n}\n\n/**\n * 为 teleport 弹出层面板注入 testId。\n * 使用 MutationObserver 监听 DOM 新增节点,自动识别 ant-design 弹出面板。\n *\n * @example\n * ```ts\n * // main.ts\n * import { setupAntDropdownTestIds } from 'vite-plugin-vue-testid/runtime'\n *\n * createApp(App).mount('#app')\n * setupAntDropdownTestIds()\n * ```\n *\n * testId 注入结果示例:\n * ```\n * // 日期面板(初始)\n * a-date-picker-0-dropdown // 面板容器\n * a-date-picker-0-dropdown-prev // 上月/上一级\n * a-date-picker-0-dropdown-next // 下月/下一级\n * a-date-picker-0-dropdown-header-date // header 容器\n * a-date-picker-0-dropdown-header-month-btn // header 内的月份按钮(\"Jun\"/\"6月\")\n * a-date-picker-0-dropdown-header-year-btn // header 内的年份按钮(\"2026\")\n * a-date-picker-0-dropdown-date-2026-06-15 // 日期单元格\n * a-date-picker-0-dropdown-ok // 确定按钮\n *\n * // 点击月份按钮 → 月面板(MutationObserver 自动重注入)\n * a-date-picker-0-dropdown-header-month // header 容器\n * a-date-picker-0-dropdown-header-year-btn // header 内的年份按钮\n * a-date-picker-0-dropdown-month-06 // 6月按钮\n *\n * // 点击年份按钮 → 年面板\n * a-date-picker-0-dropdown-header-year // header 容器\n * a-date-picker-0-dropdown-year-2026 // 2026年按钮\n * ```\n */\nexport function setupAntDropdownTestIds(opts: RuntimeOptions = {}): () => void {\n const attrName = opts.attributeName ?? 'data-testid'\n const panels = { ...DEFAULT_PANELS, ...opts.panels }\n\n const observer = new MutationObserver((mutations) => {\n for (const mutation of mutations) {\n for (const node of mutation.addedNodes) {\n if (!(node instanceof HTMLElement)) continue\n\n // 检查新增节点匹配哪种面板选择器\n for (const [selector, strategy] of Object.entries(panels)) {\n const containers = node.matches(selector)\n ? [node]\n : Array.from(node.querySelectorAll(selector))\n\n for (const container of containers) {\n if (!(container instanceof HTMLElement)) continue\n if (container.hasAttribute(attrName)) continue // 已注入过\n\n // 等一个微任务让 CSS 类生效,再找触发器\n if (strategy) {\n queueMicrotask(() => {\n const trigger = findActiveTrigger(attrName)\n const triggerTestId = getTriggerTestId(trigger, attrName)\n\n const ctx: PanelContext = {\n container,\n trigger,\n triggerTestId,\n attrName,\n }\n strategy(ctx)\n })\n }\n }\n }\n }\n }\n })\n\n observer.observe(document.body, { childList: true, subtree: true })\n\n // 返回清理函数\n return () => observer.disconnect()\n}\n\n/**\n * 手动为当前已存在或即将出现的弹出面板注入 testId(一次性补丁)。\n * 适用于 SSR/hydration 或不想用 MutationObserver 的场景。\n */\nexport function injectCurrentDropdowns(opts: RuntimeOptions = {}): void {\n const attrName = opts.attributeName ?? 'data-testid'\n const panels = { ...DEFAULT_PANELS, ...opts.panels }\n\n for (const [selector, strategy] of Object.entries(panels)) {\n const containers = document.querySelectorAll(selector)\n containers.forEach((container) => {\n if (!(container instanceof HTMLElement)) return\n if (container.hasAttribute(attrName)) return\n\n if (!strategy) return\n\n const trigger = findActiveTrigger(attrName)\n const triggerTestId = getTriggerTestId(trigger, attrName)\n\n strategy({ container, trigger, triggerTestId, attrName })\n })\n }\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vite-plugin-vue-testid",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
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",
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
}
|
|
18
18
|
},
|
|
19
19
|
"files": [
|
|
20
|
-
"dist"
|
|
20
|
+
"dist",
|
|
21
|
+
"README.en.md"
|
|
21
22
|
],
|
|
22
23
|
"scripts": {
|
|
23
24
|
"build": "tsup && tsc -p tsconfig.build.json",
|