vite-plugin-vue-testid 1.0.0 → 1.0.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.
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/index.js CHANGED
@@ -24,7 +24,8 @@ var DEFAULT_ID_COMPONENTS = [
24
24
  "a-date-picker",
25
25
  "a-range-picker",
26
26
  "a-time-picker",
27
- "a-modal"
27
+ "a-modal",
28
+ "a-upload"
28
29
  ];
29
30
  function resolveOptions(raw) {
30
31
  return {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { ConstantTypes, NodeTypes } from '@vue/compiler-core'\nimport type {\n AttributeNode,\n DirectiveNode,\n ExpressionNode,\n RootNode,\n SimpleExpressionNode,\n TemplateChildNode,\n} from '@vue/compiler-core'\n\n// ──────────────────────── Types ────────────────────────\n\nexport interface VueTestIdTransformOptions {\n /**\n * 需要注入 testId 的组件标签列表(kebab-case)\n * @default ant-design-vue 常用表单组件\n */\n components?: string[]\n\n /**\n * 自定义 testId 生成函数\n * @param tagName 组件标签名\n * @param count 全局递增序号\n * @returns testId 字符串\n */\n generateId?: (tagName: string, count: number) => string\n\n /**\n * testId 属性名\n * @default 'data-testid'\n */\n attributeName?: string\n\n /**\n * 需要同时注入 id + testId 的组件(如弹窗、日期选择器等 teleport 组件)\n * @default ['a-date-picker', 'a-range-picker', 'a-time-picker', 'a-modal']\n */\n idComponents?: string[]\n\n /**\n * teleport 组件的 id 属性名\n * @default 'id'\n */\n idAttributeName?: string\n}\n\ntype ResolvedOptions = Required<VueTestIdTransformOptions>\n\nconst DEFAULT_COMPONENTS = [\n 'a-input', 'a-textarea', 'a-input-number',\n 'a-select', 'a-cascader', 'a-tree-select',\n 'a-radio-group', 'a-checkbox-group',\n 'a-date-picker', 'a-range-picker', 'a-time-picker',\n 'a-switch', 'a-slider', 'a-rate', 'a-upload',\n 'a-form-item',\n 'a-button',\n 'a-modal',\n]\n\nconst DEFAULT_ID_COMPONENTS = [\n 'a-date-picker', 'a-range-picker', 'a-time-picker', 'a-modal',\n]\n\n// ──────────────────────── Helpers ────────────────────────\n\nfunction resolveOptions(raw?: VueTestIdTransformOptions): ResolvedOptions {\n return {\n components: raw?.components ?? DEFAULT_COMPONENTS,\n generateId: raw?.generateId ?? ((tag, n) => `${tag}-${n}`),\n attributeName: raw?.attributeName ?? 'data-testid',\n idComponents: raw?.idComponents ?? DEFAULT_ID_COMPONENTS,\n idAttributeName: raw?.idAttributeName ?? 'id',\n }\n}\n\n/**\n * 确保 slot props 解构表达式中包含 index。\n * 兼容 SIMPLE_EXPRESSION (type=4) 和 COMPOUND_EXPRESSION (type=8)。\n */\nfunction ensureSlotIndex(exp: ExpressionNode | undefined): void {\n if (!exp) return\n\n if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {\n if (!/\\bindex\\b/.test(exp.content)) {\n exp.content = exp.content.replace(/\\s*\\}$/, ', index}')\n }\n } else if (exp.type === NodeTypes.COMPOUND_EXPRESSION) {\n const hasIndex = exp.children.some(c => {\n if (typeof c === 'string') return /\\bindex\\b/.test(c)\n if (typeof c === 'symbol') return false\n if (c.type === NodeTypes.TEXT || c.type === NodeTypes.SIMPLE_EXPRESSION) {\n return /\\bindex\\b/.test(c.content)\n }\n return false\n })\n if (hasIndex) return\n\n for (let i = exp.children.length - 1; i >= 0; i--) {\n const c = exp.children[i]\n if (typeof c === 'string') {\n exp.children[i] = c.replace(/}$/, ', index}')\n break\n }\n if (typeof c !== 'symbol' && c.type === NodeTypes.TEXT) {\n c.content = c.content.replace(/}$/, ', index}')\n break\n }\n }\n }\n}\n\nfunction hasExistingTestId(\n node: { props: (AttributeNode | DirectiveNode)[] },\n attrName: string,\n): boolean {\n return node.props.some(\n p =>\n (p.type === NodeTypes.ATTRIBUTE && p.name === attrName) ||\n (p.type === NodeTypes.DIRECTIVE &&\n p.name === 'bind' &&\n p.arg &&\n (p.arg as SimpleExpressionNode).type === NodeTypes.SIMPLE_EXPRESSION &&\n (p.arg as SimpleExpressionNode).content === attrName),\n )\n}\n\nfunction createStaticAttr(name: string, value: string, loc: RootNode['loc']): AttributeNode {\n return {\n type: NodeTypes.ATTRIBUTE,\n name,\n nameLoc: loc,\n value: { type: NodeTypes.TEXT, content: value, loc },\n loc,\n }\n}\n\nfunction createBindDirective(\n attrName: string,\n exprContent: string,\n loc: RootNode['loc'],\n): DirectiveNode {\n return {\n type: NodeTypes.DIRECTIVE,\n name: 'bind',\n arg: {\n type: NodeTypes.SIMPLE_EXPRESSION,\n content: attrName,\n isStatic: true,\n constType: ConstantTypes.CAN_STRINGIFY,\n loc,\n },\n exp: {\n type: NodeTypes.SIMPLE_EXPRESSION,\n content: exprContent,\n isStatic: false,\n constType: ConstantTypes.NOT_CONSTANT,\n loc,\n },\n modifiers: [],\n loc,\n }\n}\n\n// ──────────────────────── Core Transform ────────────────────────\n\n/**\n * 创建 Vue 编译器 nodeTransform,自动为模板元素注入 data-testid。\n *\n * 特性:\n * - 自动跳过手动标注了 data-testid 的元素\n * - 支持 <a-table> 内 #bodyCell 插槽的动态 testId(行+列唯一)\n * - 自动补全 bodyCell slot props 中的 index 解构\n * - teleport 组件(日期选择器、弹窗)同时注入 id + data-testid\n */\nfunction createASTTransform(opts: ResolvedOptions) {\n let count = 0\n let insideTableDepth = 0\n let insideBodyCellDepth = 0\n\n const componentSet = new Set(opts.components)\n const idComponentSet = new Set(opts.idComponents)\n\n return (node: RootNode | TemplateChildNode) => {\n if (node.type !== NodeTypes.ELEMENT) return\n\n const tagName = node.tag.toLowerCase()\n\n // ──────────── <a-table> 上下文 ────────────\n if (tagName === 'a-table') {\n insideTableDepth++\n return () => { insideTableDepth-- }\n }\n\n // ──────────── <template #bodyCell> 上下文 ────────────\n if (tagName === 'template' && insideTableDepth > 0) {\n const slotDir = node.props.find(\n p =>\n p.type === NodeTypes.DIRECTIVE &&\n p.name === 'slot' &&\n p.arg &&\n (p.arg as SimpleExpressionNode).type === NodeTypes.SIMPLE_EXPRESSION &&\n (p.arg as SimpleExpressionNode).content === 'bodyCell',\n )\n if (slotDir && slotDir.type === NodeTypes.DIRECTIVE) {\n ensureSlotIndex(slotDir.exp)\n insideBodyCellDepth++\n return () => { insideBodyCellDepth-- }\n }\n }\n\n // table 内部但非 bodyCell → 跳过\n if (insideTableDepth > 0 && insideBodyCellDepth === 0) return\n\n // 非目标组件 → 跳过\n if (!componentSet.has(tagName)) return\n\n // 已有 testId → 跳过\n if (hasExistingTestId(node, opts.attributeName)) return\n\n const testIdValue = opts.generateId(tagName, count++)\n\n // ── bodyCell 内:动态 bind(引用 column.dataIndex + index) ──\n if (insideBodyCellDepth > 0) {\n const expr =\n `\\`\\${column?.dataIndex || column?.key || 'cell'}-\\${index}-${testIdValue}\\``\n node.props.push(createBindDirective(opts.attributeName, expr, node.loc))\n return\n }\n\n // ── teleport 组件:id + data-testid 双注入 ──\n if (idComponentSet.has(tagName)) {\n node.props.push(createStaticAttr(opts.idAttributeName, testIdValue, node.loc))\n node.props.push(createStaticAttr(opts.attributeName, testIdValue, node.loc))\n return\n }\n\n // ── 普通组件:静态 data-testid ──\n node.props.push(createStaticAttr(opts.attributeName, testIdValue, node.loc))\n }\n}\n\n// ──────────────────────── Public API ────────────────────────\n\n/**\n * 创建 Vue 编译器 nodeTransform 函数。\n * 直接传给 `@vitejs/plugin-vue` 的 `template.compilerOptions.nodeTransforms`。\n *\n * @example\n * ```ts\n * // vite.config.ts\n * import vue from '@vitejs/plugin-vue'\n * import { createVueTestIdTransform } from 'vite-plugin-vue-testid'\n *\n * export default defineConfig({\n * plugins: [\n * vue({\n * template: {\n * compilerOptions: {\n * nodeTransforms: [createVueTestIdTransform()]\n * }\n * }\n * })\n * ]\n * })\n * ```\n *\n * @example 自定义配置\n * ```ts\n * createVueTestIdTransform({\n * components: ['a-input', 'a-select', 'el-input'],\n * generateId: (tag, n) => `myapp-${tag}-${n}`,\n * attributeName: 'data-cy',\n * })\n * ```\n */\nexport function createVueTestIdTransform(\n rawOpts?: VueTestIdTransformOptions,\n): (node: RootNode | TemplateChildNode) => void {\n const opts = resolveOptions(rawOpts)\n return createASTTransform(opts)\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { ConstantTypes, NodeTypes } from '@vue/compiler-core'\nimport type {\n AttributeNode,\n DirectiveNode,\n ExpressionNode,\n RootNode,\n SimpleExpressionNode,\n TemplateChildNode,\n} from '@vue/compiler-core'\n\n// ──────────────────────── Types ────────────────────────\n\nexport interface VueTestIdTransformOptions {\n /**\n * 需要注入 testId 的组件标签列表(kebab-case)\n * @default ant-design-vue 常用表单组件\n */\n components?: string[]\n\n /**\n * 自定义 testId 生成函数\n * @param tagName 组件标签名\n * @param count 全局递增序号\n * @returns testId 字符串\n */\n generateId?: (tagName: string, count: number) => string\n\n /**\n * testId 属性名\n * @default 'data-testid'\n */\n attributeName?: string\n\n /**\n * 需要同时注入 id + testId 的组件(如弹窗、日期选择器等 teleport 组件)\n * @default ['a-date-picker', 'a-range-picker', 'a-time-picker', 'a-modal']\n */\n idComponents?: string[]\n\n /**\n * teleport 组件的 id 属性名\n * @default 'id'\n */\n idAttributeName?: string\n}\n\ntype ResolvedOptions = Required<VueTestIdTransformOptions>\n\nconst DEFAULT_COMPONENTS = [\n 'a-input', 'a-textarea', 'a-input-number',\n 'a-select', 'a-cascader', 'a-tree-select',\n 'a-radio-group', 'a-checkbox-group',\n 'a-date-picker', 'a-range-picker', 'a-time-picker',\n 'a-switch', 'a-slider', 'a-rate', 'a-upload',\n 'a-form-item',\n 'a-button',\n 'a-modal',\n]\n\nconst DEFAULT_ID_COMPONENTS = [\n 'a-date-picker', 'a-range-picker', 'a-time-picker', 'a-modal', 'a-upload',\n]\n\n// ──────────────────────── Helpers ────────────────────────\n\nfunction resolveOptions(raw?: VueTestIdTransformOptions): ResolvedOptions {\n return {\n components: raw?.components ?? DEFAULT_COMPONENTS,\n generateId: raw?.generateId ?? ((tag, n) => `${tag}-${n}`),\n attributeName: raw?.attributeName ?? 'data-testid',\n idComponents: raw?.idComponents ?? DEFAULT_ID_COMPONENTS,\n idAttributeName: raw?.idAttributeName ?? 'id',\n }\n}\n\n/**\n * 确保 slot props 解构表达式中包含 index。\n * 兼容 SIMPLE_EXPRESSION (type=4) 和 COMPOUND_EXPRESSION (type=8)。\n */\nfunction ensureSlotIndex(exp: ExpressionNode | undefined): void {\n if (!exp) return\n\n if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {\n if (!/\\bindex\\b/.test(exp.content)) {\n exp.content = exp.content.replace(/\\s*\\}$/, ', index}')\n }\n } else if (exp.type === NodeTypes.COMPOUND_EXPRESSION) {\n const hasIndex = exp.children.some(c => {\n if (typeof c === 'string') return /\\bindex\\b/.test(c)\n if (typeof c === 'symbol') return false\n if (c.type === NodeTypes.TEXT || c.type === NodeTypes.SIMPLE_EXPRESSION) {\n return /\\bindex\\b/.test(c.content)\n }\n return false\n })\n if (hasIndex) return\n\n for (let i = exp.children.length - 1; i >= 0; i--) {\n const c = exp.children[i]\n if (typeof c === 'string') {\n exp.children[i] = c.replace(/}$/, ', index}')\n break\n }\n if (typeof c !== 'symbol' && c.type === NodeTypes.TEXT) {\n c.content = c.content.replace(/}$/, ', index}')\n break\n }\n }\n }\n}\n\nfunction hasExistingTestId(\n node: { props: (AttributeNode | DirectiveNode)[] },\n attrName: string,\n): boolean {\n return node.props.some(\n p =>\n (p.type === NodeTypes.ATTRIBUTE && p.name === attrName) ||\n (p.type === NodeTypes.DIRECTIVE &&\n p.name === 'bind' &&\n p.arg &&\n (p.arg as SimpleExpressionNode).type === NodeTypes.SIMPLE_EXPRESSION &&\n (p.arg as SimpleExpressionNode).content === attrName),\n )\n}\n\nfunction createStaticAttr(name: string, value: string, loc: RootNode['loc']): AttributeNode {\n return {\n type: NodeTypes.ATTRIBUTE,\n name,\n nameLoc: loc,\n value: { type: NodeTypes.TEXT, content: value, loc },\n loc,\n }\n}\n\nfunction createBindDirective(\n attrName: string,\n exprContent: string,\n loc: RootNode['loc'],\n): DirectiveNode {\n return {\n type: NodeTypes.DIRECTIVE,\n name: 'bind',\n arg: {\n type: NodeTypes.SIMPLE_EXPRESSION,\n content: attrName,\n isStatic: true,\n constType: ConstantTypes.CAN_STRINGIFY,\n loc,\n },\n exp: {\n type: NodeTypes.SIMPLE_EXPRESSION,\n content: exprContent,\n isStatic: false,\n constType: ConstantTypes.NOT_CONSTANT,\n loc,\n },\n modifiers: [],\n loc,\n }\n}\n\n// ──────────────────────── Core Transform ────────────────────────\n\n/**\n * 创建 Vue 编译器 nodeTransform,自动为模板元素注入 data-testid。\n *\n * 特性:\n * - 自动跳过手动标注了 data-testid 的元素\n * - 支持 <a-table> 内 #bodyCell 插槽的动态 testId(行+列唯一)\n * - 自动补全 bodyCell slot props 中的 index 解构\n * - teleport 组件(日期选择器、弹窗)同时注入 id + data-testid\n */\nfunction createASTTransform(opts: ResolvedOptions) {\n let count = 0\n let insideTableDepth = 0\n let insideBodyCellDepth = 0\n\n const componentSet = new Set(opts.components)\n const idComponentSet = new Set(opts.idComponents)\n\n return (node: RootNode | TemplateChildNode) => {\n if (node.type !== NodeTypes.ELEMENT) return\n\n const tagName = node.tag.toLowerCase()\n\n // ──────────── <a-table> 上下文 ────────────\n if (tagName === 'a-table') {\n insideTableDepth++\n return () => { insideTableDepth-- }\n }\n\n // ──────────── <template #bodyCell> 上下文 ────────────\n if (tagName === 'template' && insideTableDepth > 0) {\n const slotDir = node.props.find(\n p =>\n p.type === NodeTypes.DIRECTIVE &&\n p.name === 'slot' &&\n p.arg &&\n (p.arg as SimpleExpressionNode).type === NodeTypes.SIMPLE_EXPRESSION &&\n (p.arg as SimpleExpressionNode).content === 'bodyCell',\n )\n if (slotDir && slotDir.type === NodeTypes.DIRECTIVE) {\n ensureSlotIndex(slotDir.exp)\n insideBodyCellDepth++\n return () => { insideBodyCellDepth-- }\n }\n }\n\n // table 内部但非 bodyCell → 跳过\n if (insideTableDepth > 0 && insideBodyCellDepth === 0) return\n\n // 非目标组件 → 跳过\n if (!componentSet.has(tagName)) return\n\n // 已有 testId → 跳过\n if (hasExistingTestId(node, opts.attributeName)) return\n\n const testIdValue = opts.generateId(tagName, count++)\n\n // ── bodyCell 内:动态 bind(引用 column.dataIndex + index) ──\n if (insideBodyCellDepth > 0) {\n const expr =\n `\\`\\${column?.dataIndex || column?.key || 'cell'}-\\${index}-${testIdValue}\\``\n node.props.push(createBindDirective(opts.attributeName, expr, node.loc))\n return\n }\n\n // ── teleport 组件:id + data-testid 双注入 ──\n if (idComponentSet.has(tagName)) {\n node.props.push(createStaticAttr(opts.idAttributeName, testIdValue, node.loc))\n node.props.push(createStaticAttr(opts.attributeName, testIdValue, node.loc))\n return\n }\n\n // ── 普通组件:静态 data-testid ──\n node.props.push(createStaticAttr(opts.attributeName, testIdValue, node.loc))\n }\n}\n\n// ──────────────────────── Public API ────────────────────────\n\n/**\n * 创建 Vue 编译器 nodeTransform 函数。\n * 直接传给 `@vitejs/plugin-vue` 的 `template.compilerOptions.nodeTransforms`。\n *\n * @example\n * ```ts\n * // vite.config.ts\n * import vue from '@vitejs/plugin-vue'\n * import { createVueTestIdTransform } from 'vite-plugin-vue-testid'\n *\n * export default defineConfig({\n * plugins: [\n * vue({\n * template: {\n * compilerOptions: {\n * nodeTransforms: [createVueTestIdTransform()]\n * }\n * }\n * })\n * ]\n * })\n * ```\n *\n * @example 自定义配置\n * ```ts\n * createVueTestIdTransform({\n * components: ['a-input', 'a-select', 'el-input'],\n * generateId: (tag, n) => `myapp-${tag}-${n}`,\n * attributeName: 'data-cy',\n * })\n * ```\n */\nexport function createVueTestIdTransform(\n rawOpts?: VueTestIdTransformOptions,\n): (node: RootNode | TemplateChildNode) => void {\n const opts = resolveOptions(rawOpts)\n return createASTTransform(opts)\n}\n"],"mappings":"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","names":[]}
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IAEtB;;;;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;AAyW7D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,GAAE,cAAmB,GAAG,MAAM,IAAI,CA4C7E;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,GAAE,cAAmB,GAAG,IAAI,CAkBtE"}
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IAEtB;;;;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;AAyW7D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,GAAE,cAAmB,GAAG,MAAM,IAAI,CAwE7E;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,GAAE,cAAmB,GAAG,IAAI,CA4CtE"}
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) => {
@@ -248,6 +248,21 @@ function setupAntDropdownTestIds(opts = {}) {
248
248
  }
249
249
  }
250
250
  }
251
+ const uploadByTestId = node.matches(`.ant-upload[${attrName}]`) ? [node] : Array.from(node.querySelectorAll(`.ant-upload[${attrName}]`));
252
+ const uploadById = uploadByTestId.length === 0 ? node.matches(`.ant-upload[id]`) ? [node] : Array.from(node.querySelectorAll(`.ant-upload[id]`)) : [];
253
+ const uploads = [...uploadByTestId, ...uploadById];
254
+ for (const upload of uploads) {
255
+ if (!(upload instanceof HTMLElement)) continue;
256
+ const testId = upload.getAttribute(attrName) || upload.getAttribute("id") || "";
257
+ if (!testId) continue;
258
+ const input = upload.querySelector('input[type="file"]');
259
+ if (input && !input.hasAttribute(attrName)) {
260
+ input.setAttribute(attrName, `${testId}-input`);
261
+ if (!upload.hasAttribute(attrName)) {
262
+ upload.setAttribute(attrName, testId);
263
+ }
264
+ }
265
+ }
251
266
  }
252
267
  }
253
268
  });
@@ -268,6 +283,25 @@ function injectCurrentDropdowns(opts = {}) {
268
283
  strategy({ container, trigger, triggerTestId, attrName });
269
284
  });
270
285
  }
286
+ const uploads = [
287
+ ...Array.from(document.querySelectorAll(`.ant-upload[${attrName}]`)),
288
+ ...Array.from(document.querySelectorAll(`.ant-upload[id]`))
289
+ ];
290
+ const seen = /* @__PURE__ */ new Set();
291
+ uploads.forEach((upload) => {
292
+ if (!(upload instanceof HTMLElement)) return;
293
+ if (seen.has(upload)) return;
294
+ seen.add(upload);
295
+ const testId = upload.getAttribute(attrName) || upload.getAttribute("id") || "";
296
+ if (!testId) return;
297
+ const input = upload.querySelector('input[type="file"]');
298
+ if (input && !input.hasAttribute(attrName)) {
299
+ input.setAttribute(attrName, `${testId}-input`);
300
+ if (!upload.hasAttribute(attrName)) {
301
+ upload.setAttribute(attrName, testId);
302
+ }
303
+ }
304
+ });
271
305
  }
272
306
  export {
273
307
  injectCurrentDropdowns,
@@ -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 // Upload 组件:需要将 testId 注入到内部 <input type=\"file\">\n // ant-design-vue Upload 可能 inheritAttrs: false,data-testid 不会到达 DOM,\n // 但 id 属性会被转发。编译期双注入保证 id 与 data-testid 值相同。\n const uploadByTestId = node.matches(`.ant-upload[${attrName}]`)\n ? [node]\n : Array.from(node.querySelectorAll(`.ant-upload[${attrName}]`))\n const uploadById = uploadByTestId.length === 0\n ? (node.matches(`.ant-upload[id]`)\n ? [node]\n : Array.from(node.querySelectorAll(`.ant-upload[id]`)))\n : []\n\n const uploads = [...uploadByTestId, ...uploadById]\n\n for (const upload of uploads) {\n if (!(upload instanceof HTMLElement)) continue\n const testId = upload.getAttribute(attrName) || upload.getAttribute('id') || ''\n if (!testId) continue\n const input = upload.querySelector('input[type=\"file\"]') as HTMLElement | null\n if (input && !input.hasAttribute(attrName)) {\n input.setAttribute(attrName, `${testId}-input`)\n // 给外层容器也补上 testId(如果还没有)\n if (!upload.hasAttribute(attrName)) {\n upload.setAttribute(attrName, testId)\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 // Upload 组件:需要将 testId 注入到内部 <input type=\"file\">\n // ant-design-vue Upload 可能 inheritAttrs: false,data-testid 不会到达 DOM,\n // 但 id 属性会被转发。编译期双注入保证 id 与 data-testid 值相同。\n const uploads = [\n ...Array.from(document.querySelectorAll(`.ant-upload[${attrName}]`)),\n ...Array.from(document.querySelectorAll(`.ant-upload[id]`)),\n ]\n // 去重\n const seen = new Set<Element>()\n uploads.forEach((upload) => {\n if (!(upload instanceof HTMLElement)) return\n if (seen.has(upload)) return\n seen.add(upload)\n\n const testId = upload.getAttribute(attrName) || upload.getAttribute('id') || ''\n if (!testId) return\n const input = upload.querySelector('input[type=\"file\"]') as HTMLElement | null\n if (input && !input.hasAttribute(attrName)) {\n input.setAttribute(attrName, `${testId}-input`)\n // 给外层容器也补上 testId(如果还没有)\n if (!upload.hasAttribute(attrName)) {\n upload.setAttribute(attrName, testId)\n }\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.0",
3
+ "version": "1.0.2",
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",