vite-plugin-vue-testid 1.0.12 → 1.1.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/dist/index.d.ts +69 -26
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +255 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +95 -48
- package/dist/transform.d.ts +69 -0
- package/dist/transform.d.ts.map +1 -0
- package/package.json +2 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,23 +1,76 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* vite-plugin-vue-testid
|
|
3
3
|
*
|
|
4
|
-
* 为 ant-design-vue 组件的 DOM 元素自动注入 data-testid
|
|
5
|
-
*
|
|
4
|
+
* 为 Vue (ant-design-vue) 组件的 DOM 元素自动注入 data-testid 属性。
|
|
5
|
+
*
|
|
6
|
+
* 默认提供全运行时方案,可选启用编译期 transform 以获得稳定的、页面内唯一的 testid。
|
|
7
|
+
*
|
|
8
|
+
* === 使用方式 A:纯运行时 ===
|
|
6
9
|
*
|
|
7
|
-
* @example
|
|
8
10
|
* ```ts
|
|
9
|
-
* // vite.config.ts
|
|
11
|
+
* // vite.config.ts
|
|
10
12
|
* import vue from '@vitejs/plugin-vue'
|
|
11
|
-
*
|
|
13
|
+
* import { vueTestIdPlugin } from 'vite-plugin-vue-testid'
|
|
14
|
+
* export default defineConfig({ plugins: [vue(), vueTestIdPlugin()] })
|
|
12
15
|
*
|
|
13
|
-
* // main.ts
|
|
16
|
+
* // main.ts
|
|
14
17
|
* import { setupAntTestIds } from 'vite-plugin-vue-testid/runtime'
|
|
15
18
|
* createApp(App).mount('#app')
|
|
16
19
|
* setupAntTestIds()
|
|
17
20
|
* ```
|
|
21
|
+
*
|
|
22
|
+
* === 使用方式 B:编译期 + 运行时(推荐,获得唯一稳定 testid) ===
|
|
23
|
+
*
|
|
24
|
+
* ```ts
|
|
25
|
+
* // vite.config.ts
|
|
26
|
+
* import vue from '@vitejs/plugin-vue'
|
|
27
|
+
* import { vueTestIdPlugin, testIdTransforms } from 'vite-plugin-vue-testid'
|
|
28
|
+
* export default defineConfig({
|
|
29
|
+
* plugins: [
|
|
30
|
+
* vue({
|
|
31
|
+
* template: {
|
|
32
|
+
* compilerOptions: {
|
|
33
|
+
* nodeTransforms: testIdTransforms()
|
|
34
|
+
* }
|
|
35
|
+
* }
|
|
36
|
+
* }),
|
|
37
|
+
* vueTestIdPlugin(),
|
|
38
|
+
* ]
|
|
39
|
+
* })
|
|
40
|
+
*
|
|
41
|
+
* // main.ts (与 A 相同)
|
|
42
|
+
* import { setupAntTestIds } from 'vite-plugin-vue-testid/runtime'
|
|
43
|
+
* setupAntTestIds()
|
|
44
|
+
* ```
|
|
45
|
+
*
|
|
46
|
+
* === 使用方式 C:仅编译期(无运行时,面板不会注入 testid) ===
|
|
47
|
+
*
|
|
48
|
+
* ```ts
|
|
49
|
+
* // vite.config.ts
|
|
50
|
+
* import vue from '@vitejs/plugin-vue'
|
|
51
|
+
* import { vueTestIdPlugin, testIdTransforms } from 'vite-plugin-vue-testid'
|
|
52
|
+
* export default defineConfig({
|
|
53
|
+
* plugins: [
|
|
54
|
+
* vue({
|
|
55
|
+
* template: {
|
|
56
|
+
* compilerOptions: {
|
|
57
|
+
* nodeTransforms: testIdTransforms()
|
|
58
|
+
* }
|
|
59
|
+
* }
|
|
60
|
+
* }),
|
|
61
|
+
* vueTestIdPlugin(),
|
|
62
|
+
* ]
|
|
63
|
+
* })
|
|
64
|
+
* // main.ts 不需要调用 setupAntTestIds()
|
|
65
|
+
* ```
|
|
18
66
|
*/
|
|
19
67
|
import type { Plugin } from 'vite';
|
|
20
68
|
export type { RuntimeOptions, PanelContext, PanelInjectStrategy } from './runtime';
|
|
69
|
+
export type { TransformOptions } from './transform';
|
|
70
|
+
import { createTestIdTransforms } from './transform';
|
|
71
|
+
/** 等同于 `createTestIdTransforms()`,更短的导出名 */
|
|
72
|
+
export declare const testIdTransforms: typeof createTestIdTransforms;
|
|
73
|
+
export { createTestIdTransforms, createTestIdInjectTransform, createMultiRootFixTransform, } from './transform';
|
|
21
74
|
export interface VueTestIdPluginOptions {
|
|
22
75
|
/**
|
|
23
76
|
* testId 属性名
|
|
@@ -26,32 +79,22 @@ export interface VueTestIdPluginOptions {
|
|
|
26
79
|
attributeName?: string;
|
|
27
80
|
}
|
|
28
81
|
/**
|
|
29
|
-
* Vite
|
|
30
|
-
* 当前仅作为占位,实际 testid 注入完全由运行时 `setupAntTestIds()` 完成。
|
|
82
|
+
* Vite 插件。
|
|
31
83
|
*
|
|
32
|
-
*
|
|
84
|
+
* 当前作为配置占位符存在;实际的 testid 注入由以下组件完成:
|
|
85
|
+
* - 编译期:`testIdTransforms()` → 注册到 `vue()` 的 compilerOptions
|
|
86
|
+
* - 运行时:`setupAntTestIds()` → 在 main.ts 中调用
|
|
33
87
|
*/
|
|
34
88
|
export declare function vueTestIdPlugin(_opts?: VueTestIdPluginOptions): Plugin;
|
|
35
89
|
/**
|
|
36
|
-
* @deprecated
|
|
37
|
-
* 不再需要编译期 AST transform。请从 vite.config.ts 中移除 `nodeTransforms` 配置,
|
|
38
|
-
* 改为在 main.ts 中调用 `setupAntTestIds()`。
|
|
39
|
-
*
|
|
40
|
-
* @example 迁移方式
|
|
41
|
-
* ```diff
|
|
42
|
-
* // vite.config.ts
|
|
43
|
-
* - import { createVueTestIdTransform } from 'vite-plugin-vue-testid'
|
|
44
|
-
* - vue({ template: { compilerOptions: { nodeTransforms: [
|
|
45
|
-
* - createVueTestIdTransform()
|
|
46
|
-
* - ]}}})
|
|
47
|
-
|
|
48
|
-
* + import vue from '@vitejs/plugin-vue'
|
|
49
|
-
* + vue()
|
|
90
|
+
* @deprecated 此函数已废弃。请使用新的编译期 API:
|
|
50
91
|
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
92
|
+
* ```ts
|
|
93
|
+
* import { testIdTransforms } from 'vite-plugin-vue-testid'
|
|
94
|
+
* // 加入 vue() 的 compilerOptions.nodeTransforms
|
|
54
95
|
* ```
|
|
96
|
+
*
|
|
97
|
+
* 或仅使用运行时 `setupAntTestIds()`。
|
|
55
98
|
*/
|
|
56
99
|
export declare function createVueTestIdTransform(): () => void;
|
|
57
100
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiEG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAIlC,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAA;AAClF,YAAY,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAEnD,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAA;AAEpD,4CAA4C;AAC5C,eAAO,MAAM,gBAAgB,+BAAyB,CAAA;AAEtD,OAAO,EACL,sBAAsB,EACtB,2BAA2B,EAC3B,2BAA2B,GAC5B,MAAM,aAAa,CAAA;AAIpB,MAAM,WAAW,sBAAsB;IACrC;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,KAAK,CAAC,EAAE,sBAAsB,GAAG,MAAM,CAItE;AAID;;;;;;;;;GASG;AACH,wBAAgB,wBAAwB,IAAI,MAAM,IAAI,CASrD"}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,254 @@
|
|
|
1
|
+
// src/transform.ts
|
|
2
|
+
var NodeTypes = {
|
|
3
|
+
ROOT: 0,
|
|
4
|
+
ELEMENT: 1,
|
|
5
|
+
TEXT: 2,
|
|
6
|
+
COMMENT: 3,
|
|
7
|
+
SIMPLE_EXPRESSION: 4,
|
|
8
|
+
INTERPOLATION: 5,
|
|
9
|
+
ATTRIBUTE: 6,
|
|
10
|
+
DIRECTIVE: 7,
|
|
11
|
+
COMPOUND_EXPRESSION: 8,
|
|
12
|
+
IF: 9,
|
|
13
|
+
IF_BRANCH: 10,
|
|
14
|
+
FOR: 11,
|
|
15
|
+
TEXT_CALL: 12
|
|
16
|
+
};
|
|
17
|
+
var ElementTypes = {
|
|
18
|
+
ELEMENT: 0,
|
|
19
|
+
COMPONENT: 1,
|
|
20
|
+
SLOT: 2,
|
|
21
|
+
TEMPLATE: 3
|
|
22
|
+
};
|
|
23
|
+
var NATIVE_TAGS = /* @__PURE__ */ new Set([
|
|
24
|
+
"div",
|
|
25
|
+
"span",
|
|
26
|
+
"p",
|
|
27
|
+
"a",
|
|
28
|
+
"br",
|
|
29
|
+
"hr",
|
|
30
|
+
"img",
|
|
31
|
+
"input",
|
|
32
|
+
"button",
|
|
33
|
+
"select",
|
|
34
|
+
"option",
|
|
35
|
+
"textarea",
|
|
36
|
+
"label",
|
|
37
|
+
"form",
|
|
38
|
+
"table",
|
|
39
|
+
"tr",
|
|
40
|
+
"td",
|
|
41
|
+
"th",
|
|
42
|
+
"thead",
|
|
43
|
+
"tbody",
|
|
44
|
+
"tfoot",
|
|
45
|
+
"ul",
|
|
46
|
+
"ol",
|
|
47
|
+
"li",
|
|
48
|
+
"dl",
|
|
49
|
+
"dt",
|
|
50
|
+
"dd",
|
|
51
|
+
"h1",
|
|
52
|
+
"h2",
|
|
53
|
+
"h3",
|
|
54
|
+
"h4",
|
|
55
|
+
"h5",
|
|
56
|
+
"h6",
|
|
57
|
+
"section",
|
|
58
|
+
"article",
|
|
59
|
+
"header",
|
|
60
|
+
"footer",
|
|
61
|
+
"nav",
|
|
62
|
+
"aside",
|
|
63
|
+
"main",
|
|
64
|
+
"figure",
|
|
65
|
+
"figcaption",
|
|
66
|
+
"details",
|
|
67
|
+
"summary",
|
|
68
|
+
"code",
|
|
69
|
+
"pre",
|
|
70
|
+
"strong",
|
|
71
|
+
"em",
|
|
72
|
+
"i",
|
|
73
|
+
"b",
|
|
74
|
+
"u",
|
|
75
|
+
"s",
|
|
76
|
+
"small",
|
|
77
|
+
"sub",
|
|
78
|
+
"sup",
|
|
79
|
+
"blockquote",
|
|
80
|
+
"q",
|
|
81
|
+
"cite",
|
|
82
|
+
"abbr",
|
|
83
|
+
"time",
|
|
84
|
+
"address",
|
|
85
|
+
"fieldset",
|
|
86
|
+
"legend",
|
|
87
|
+
"progress",
|
|
88
|
+
"meter",
|
|
89
|
+
"iframe",
|
|
90
|
+
"svg",
|
|
91
|
+
"path",
|
|
92
|
+
"video",
|
|
93
|
+
"audio",
|
|
94
|
+
"canvas",
|
|
95
|
+
"template",
|
|
96
|
+
"slot",
|
|
97
|
+
"style",
|
|
98
|
+
"script",
|
|
99
|
+
"meta",
|
|
100
|
+
"link",
|
|
101
|
+
"title",
|
|
102
|
+
"head",
|
|
103
|
+
"html",
|
|
104
|
+
"body",
|
|
105
|
+
"noscript",
|
|
106
|
+
"map",
|
|
107
|
+
"area",
|
|
108
|
+
"base",
|
|
109
|
+
"col",
|
|
110
|
+
"colgroup",
|
|
111
|
+
"data",
|
|
112
|
+
"datalist",
|
|
113
|
+
"del",
|
|
114
|
+
"dfn",
|
|
115
|
+
"dialog",
|
|
116
|
+
"embed",
|
|
117
|
+
"ins",
|
|
118
|
+
"kbd",
|
|
119
|
+
"mark",
|
|
120
|
+
"math",
|
|
121
|
+
"menu",
|
|
122
|
+
"object",
|
|
123
|
+
"output",
|
|
124
|
+
"param",
|
|
125
|
+
"picture",
|
|
126
|
+
"pre",
|
|
127
|
+
"rp",
|
|
128
|
+
"rt",
|
|
129
|
+
"ruby",
|
|
130
|
+
"samp",
|
|
131
|
+
"search",
|
|
132
|
+
"source",
|
|
133
|
+
"track",
|
|
134
|
+
"var",
|
|
135
|
+
"wbr"
|
|
136
|
+
]);
|
|
137
|
+
function isAntdComponent(tag, antPrefix) {
|
|
138
|
+
if (!tag.startsWith(antPrefix)) return false;
|
|
139
|
+
if (tag === antPrefix.slice(0, -1)) return false;
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
function isTargetComponent(el, antPrefix, customPrefixes) {
|
|
143
|
+
const { tag, tagType } = el;
|
|
144
|
+
if (tagType === ElementTypes.ELEMENT && NATIVE_TAGS.has(tag)) return false;
|
|
145
|
+
if (tagType === ElementTypes.SLOT || tagType === ElementTypes.TEMPLATE) return false;
|
|
146
|
+
if (isAntdComponent(tag, antPrefix)) return false;
|
|
147
|
+
if (tagType === ElementTypes.COMPONENT) return true;
|
|
148
|
+
if (customPrefixes.some((p) => tag.startsWith(p))) return true;
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
function hasExistingTestId(el, attrName) {
|
|
152
|
+
return el.props.some((p) => p.type === NodeTypes.ATTRIBUTE && p.name === attrName);
|
|
153
|
+
}
|
|
154
|
+
function hasExistingAttrsBinding(el) {
|
|
155
|
+
return el.props.some(
|
|
156
|
+
(p) => p.type === NodeTypes.DIRECTIVE && p.name === "bind" && p.arg?.type === NodeTypes.SIMPLE_EXPRESSION && p.arg.content === "$attrs"
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
function createStaticAttr(name, value, loc) {
|
|
160
|
+
return {
|
|
161
|
+
type: NodeTypes.ATTRIBUTE,
|
|
162
|
+
name,
|
|
163
|
+
value: {
|
|
164
|
+
type: NodeTypes.TEXT,
|
|
165
|
+
content: value,
|
|
166
|
+
loc: loc || {}
|
|
167
|
+
},
|
|
168
|
+
loc: loc || {}
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
function createVBindAttrsDirective(loc) {
|
|
172
|
+
return {
|
|
173
|
+
type: NodeTypes.DIRECTIVE,
|
|
174
|
+
name: "bind",
|
|
175
|
+
arg: {
|
|
176
|
+
type: NodeTypes.SIMPLE_EXPRESSION,
|
|
177
|
+
content: "$attrs",
|
|
178
|
+
isStatic: true,
|
|
179
|
+
constType: 3,
|
|
180
|
+
// CAN_STRINGIFY
|
|
181
|
+
loc: loc || {}
|
|
182
|
+
},
|
|
183
|
+
exp: {
|
|
184
|
+
type: NodeTypes.SIMPLE_EXPRESSION,
|
|
185
|
+
content: "$attrs",
|
|
186
|
+
isStatic: false,
|
|
187
|
+
constType: 0,
|
|
188
|
+
// NOT_CONSTANT
|
|
189
|
+
loc: loc || {}
|
|
190
|
+
},
|
|
191
|
+
modifiers: [],
|
|
192
|
+
loc: loc || {}
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
function createMultiRootFixTransform(opts = {}) {
|
|
196
|
+
const { antPrefix = "a-" } = opts;
|
|
197
|
+
return (node, _context) => {
|
|
198
|
+
if (node.type !== NodeTypes.ROOT) return;
|
|
199
|
+
const root = node;
|
|
200
|
+
const elementChildren = root.children.filter(
|
|
201
|
+
(c) => c.type === NodeTypes.ELEMENT
|
|
202
|
+
);
|
|
203
|
+
if (elementChildren.length <= 1) return;
|
|
204
|
+
const hasManualBinding = elementChildren.some(
|
|
205
|
+
(el) => hasExistingAttrsBinding(el)
|
|
206
|
+
);
|
|
207
|
+
if (hasManualBinding) return;
|
|
208
|
+
for (const el of elementChildren) {
|
|
209
|
+
if (isAntdComponent(el.tag, antPrefix)) {
|
|
210
|
+
if (hasExistingAttrsBinding(el)) return;
|
|
211
|
+
el.props.push(createVBindAttrsDirective(el.loc));
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
function createTestIdInjectTransform(opts = {}) {
|
|
218
|
+
const {
|
|
219
|
+
attributeName = "data-testid",
|
|
220
|
+
antPrefix = "a-",
|
|
221
|
+
customPrefixes = []
|
|
222
|
+
} = opts;
|
|
223
|
+
const templateCounters = /* @__PURE__ */ new WeakMap();
|
|
224
|
+
return (node, context) => {
|
|
225
|
+
if (node.type === NodeTypes.ROOT) {
|
|
226
|
+
templateCounters.set(node, /* @__PURE__ */ new Map());
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
if (node.type !== NodeTypes.ELEMENT) return;
|
|
230
|
+
const el = node;
|
|
231
|
+
if (!isTargetComponent(el, antPrefix, customPrefixes)) return;
|
|
232
|
+
if (hasExistingTestId(el, attributeName)) return;
|
|
233
|
+
const counters = templateCounters.get(context.root);
|
|
234
|
+
const tagName = el.tag;
|
|
235
|
+
const index = counters.get(tagName) ?? 0;
|
|
236
|
+
counters.set(tagName, index + 1);
|
|
237
|
+
const testId = `${tagName}-${index}`;
|
|
238
|
+
el.props.push(createStaticAttr(attributeName, testId, el.loc));
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
function createTestIdTransforms(opts = {}) {
|
|
242
|
+
return [
|
|
243
|
+
// multiRootFix 必须先注册,因为它的 binding 应出现在
|
|
244
|
+
// testId inject 之前(编译顺序无关,但逻辑上先修复再注入更清晰)
|
|
245
|
+
createMultiRootFixTransform(opts),
|
|
246
|
+
createTestIdInjectTransform(opts)
|
|
247
|
+
];
|
|
248
|
+
}
|
|
249
|
+
|
|
1
250
|
// src/index.ts
|
|
251
|
+
var testIdTransforms = createTestIdTransforms;
|
|
2
252
|
function vueTestIdPlugin(_opts) {
|
|
3
253
|
return {
|
|
4
254
|
name: "vite-plugin-vue-testid"
|
|
@@ -7,13 +257,17 @@ function vueTestIdPlugin(_opts) {
|
|
|
7
257
|
function createVueTestIdTransform() {
|
|
8
258
|
if (typeof console !== "undefined") {
|
|
9
259
|
console.warn(
|
|
10
|
-
"[vite-plugin-vue-testid] createVueTestIdTransform() is deprecated.
|
|
260
|
+
"[vite-plugin-vue-testid] createVueTestIdTransform() is deprecated. Use testIdTransforms() in vue() compilerOptions for compile-time injection, or setupAntTestIds() in main.ts for runtime injection."
|
|
11
261
|
);
|
|
12
262
|
}
|
|
13
263
|
return () => {
|
|
14
264
|
};
|
|
15
265
|
}
|
|
16
266
|
export {
|
|
267
|
+
createMultiRootFixTransform,
|
|
268
|
+
createTestIdInjectTransform,
|
|
269
|
+
createTestIdTransforms,
|
|
17
270
|
createVueTestIdTransform,
|
|
271
|
+
testIdTransforms,
|
|
18
272
|
vueTestIdPlugin
|
|
19
273
|
};
|
package/dist/runtime.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IAEtB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAElB;;;;;;;;;;;;;OAaG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAEnC;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,GAAG,SAAS,CAAC,CAAA;CACzD;AAED,MAAM,WAAW,YAAY;IAC3B,kBAAkB;IAClB,SAAS,EAAE,WAAW,CAAA;IACtB,iBAAiB;IACjB,OAAO,EAAE,WAAW,GAAG,IAAI,CAAA;IAC3B,oBAAoB;IACpB,aAAa,EAAE,MAAM,CAAA;IACrB,iBAAiB;IACjB,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,MAAM,mBAAmB,GAAG,CAAC,GAAG,EAAE,YAAY,KAAK,IAAI,CAAA;
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IAEtB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAElB;;;;;;;;;;;;;OAaG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAEnC;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,GAAG,SAAS,CAAC,CAAA;CACzD;AAED,MAAM,WAAW,YAAY;IAC3B,kBAAkB;IAClB,SAAS,EAAE,WAAW,CAAA;IACtB,iBAAiB;IACjB,OAAO,EAAE,WAAW,GAAG,IAAI,CAAA;IAC3B,oBAAoB;IACpB,aAAa,EAAE,MAAM,CAAA;IACrB,iBAAiB;IACjB,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,MAAM,mBAAmB,GAAG,CAAC,GAAG,EAAE,YAAY,KAAK,IAAI,CAAA;AAqpB7D;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,eAAe,CAAC,IAAI,GAAE,cAAmB,GAAG,MAAM,IAAI,CAgFrE;AAED;;GAEG;AACH,eAAO,MAAM,uBAAuB,wBAAkB,CAAA;AAMtD;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,GAAE,cAAmB,GAAG,IAAI,CAmCtE"}
|
package/dist/runtime.js
CHANGED
|
@@ -30,6 +30,8 @@ function buildComponentMatchers(pc) {
|
|
|
30
30
|
{ selector: sel(pc, "modal-wrap"), prefix: "a-modal", withId: true },
|
|
31
31
|
// ── 菜单 ──
|
|
32
32
|
{ selector: sel(pc, "menu"), prefix: "a-menu" },
|
|
33
|
+
// ── 标签页 ──
|
|
34
|
+
{ selector: sel(pc, "tabs"), prefix: "a-tabs" },
|
|
33
35
|
// ── 纯 input / textarea(必须排在最后)──
|
|
34
36
|
{ selector: `input.${cls(pc, "input")}`, prefix: "a-input" },
|
|
35
37
|
{ selector: `textarea.${cls(pc, "input")}`, prefix: "a-textarea" }
|
|
@@ -58,7 +60,7 @@ function injectComponentRoots(root, matchers, attrName, counter, skipSelectors)
|
|
|
58
60
|
const candidates = root.matches(selector) ? [root] : Array.from(root.querySelectorAll(selector));
|
|
59
61
|
for (const el of candidates) {
|
|
60
62
|
if (!(el instanceof HTMLElement)) continue;
|
|
61
|
-
if (el
|
|
63
|
+
if (hasAttrCompat(el, attrName)) continue;
|
|
62
64
|
if (isInnerInput(el, skipSelectors)) continue;
|
|
63
65
|
const testId = `${prefix}-${counter.value++}`;
|
|
64
66
|
el.setAttribute(attrName, testId);
|
|
@@ -94,7 +96,7 @@ function injectHeaderViewButtons(panel, prefix, attrName, suffix, pc) {
|
|
|
94
96
|
const buttons = headerView.querySelectorAll("button");
|
|
95
97
|
buttons.forEach((btn) => {
|
|
96
98
|
const el = btn;
|
|
97
|
-
if (el
|
|
99
|
+
if (hasAttrCompat(el, attrName)) return;
|
|
98
100
|
const text = el.textContent?.trim().toLowerCase() || "";
|
|
99
101
|
let name;
|
|
100
102
|
if (MONTH_NAMES.some((m) => text.startsWith(m))) {
|
|
@@ -116,7 +118,7 @@ function injectPanelCells(panel, prefix, attrName, suffix, pc) {
|
|
|
116
118
|
const cells = panel.querySelectorAll(sel(pc, "picker-cell-inner"));
|
|
117
119
|
cells.forEach((cell) => {
|
|
118
120
|
const el = cell;
|
|
119
|
-
if (el
|
|
121
|
+
if (hasAttrCompat(el, attrName)) return;
|
|
120
122
|
const raw = (el.getAttribute("title") || el.textContent?.trim() || "").replace(/\s+/g, "-");
|
|
121
123
|
switch (mode) {
|
|
122
124
|
case "month":
|
|
@@ -138,7 +140,7 @@ function injectSinglePanel(panel, prefix, attrName, suffix, pc) {
|
|
|
138
140
|
const mode = getPanelMode(panel, pc);
|
|
139
141
|
const setAttr = (selector, name) => {
|
|
140
142
|
const el = panel.querySelector(selector);
|
|
141
|
-
if (el && !el
|
|
143
|
+
if (el && !hasAttrCompat(el, attrName)) {
|
|
142
144
|
el.setAttribute(attrName, `${prefix}-${name}${suffix}`);
|
|
143
145
|
}
|
|
144
146
|
};
|
|
@@ -168,26 +170,26 @@ function makeInjectPickerPanel(pc) {
|
|
|
168
170
|
const presets = container.querySelectorAll(`${sel(pc, "picker-presets")} .${cls(pc, "tag")}`);
|
|
169
171
|
presets.forEach((tag) => {
|
|
170
172
|
const el = tag;
|
|
171
|
-
if (el
|
|
173
|
+
if (hasAttrCompat(el, attrName)) return;
|
|
172
174
|
const text = el.textContent?.trim() || "";
|
|
173
175
|
el.setAttribute(attrName, `${prefix}-preset-${text}`);
|
|
174
176
|
});
|
|
175
177
|
const timeColumns = container.querySelectorAll(sel(pc, "picker-time-panel-column"));
|
|
176
178
|
timeColumns.forEach((col, i) => {
|
|
177
179
|
const el = col;
|
|
178
|
-
if (el
|
|
180
|
+
if (hasAttrCompat(el, attrName)) return;
|
|
179
181
|
el.setAttribute(attrName, `${prefix}-time-column-${i}`);
|
|
180
182
|
const items = col.querySelectorAll("li");
|
|
181
183
|
items.forEach((item) => {
|
|
182
184
|
const li = item;
|
|
183
|
-
if (li
|
|
185
|
+
if (hasAttrCompat(li, attrName)) return;
|
|
184
186
|
const text = li.textContent?.trim() || "";
|
|
185
187
|
li.setAttribute(attrName, `${prefix}-time-${i}-${text}`);
|
|
186
188
|
});
|
|
187
189
|
});
|
|
188
190
|
const setFooterBtn = (selector, name) => {
|
|
189
191
|
const btn = container.querySelector(selector);
|
|
190
|
-
if (btn && !btn
|
|
192
|
+
if (btn && !hasAttrCompat(btn, attrName)) {
|
|
191
193
|
btn.setAttribute(attrName, `${prefix}-${name}`);
|
|
192
194
|
}
|
|
193
195
|
};
|
|
@@ -236,7 +238,7 @@ function makeInjectSelectPanel(pc) {
|
|
|
236
238
|
const options = container.querySelectorAll(sel(pc, "select-item-option"));
|
|
237
239
|
options.forEach((opt) => {
|
|
238
240
|
const el = opt;
|
|
239
|
-
if (el
|
|
241
|
+
if (hasAttrCompat(el, attrName)) return;
|
|
240
242
|
const title = el.getAttribute("title") || el.textContent?.trim() || "";
|
|
241
243
|
el.setAttribute(attrName, `${prefix}-option-${title}`);
|
|
242
244
|
});
|
|
@@ -245,11 +247,11 @@ function makeInjectSelectPanel(pc) {
|
|
|
245
247
|
const el = node;
|
|
246
248
|
const content = el.querySelector(sel(pc, "select-tree-node-content-wrapper"));
|
|
247
249
|
const title = content?.getAttribute("title") || content?.textContent?.trim() || "";
|
|
248
|
-
if (content && !content
|
|
250
|
+
if (content && !hasAttrCompat(content, attrName)) {
|
|
249
251
|
content.setAttribute(attrName, `${prefix}-tree-${title}`);
|
|
250
252
|
}
|
|
251
253
|
const switcher = el.querySelector(sel(pc, "select-tree-switcher"));
|
|
252
|
-
if (switcher && !switcher
|
|
254
|
+
if (switcher && !hasAttrCompat(switcher, attrName)) {
|
|
253
255
|
const isOpen = switcher.classList.contains(cls(pc, "select-tree-switcher_open"));
|
|
254
256
|
const state = isOpen ? "collapse" : "expand";
|
|
255
257
|
switcher.setAttribute(attrName, `${prefix}-tree-switcher-${state}-${title}`);
|
|
@@ -288,7 +290,7 @@ function injectMenuItems(attrName, root = document, pc) {
|
|
|
288
290
|
const items = root instanceof HTMLElement && root.matches(rule.selector) ? [root, ...descendants] : descendants;
|
|
289
291
|
for (const item of items) {
|
|
290
292
|
const el = item;
|
|
291
|
-
if (el
|
|
293
|
+
if (hasAttrCompat(el, attrName)) return;
|
|
292
294
|
const id = el.getAttribute("id");
|
|
293
295
|
if (id) {
|
|
294
296
|
el.setAttribute(attrName, id);
|
|
@@ -303,67 +305,82 @@ function injectMenuItems(attrName, root = document, pc) {
|
|
|
303
305
|
}
|
|
304
306
|
}
|
|
305
307
|
var _observedContainers = /* @__PURE__ */ new WeakSet();
|
|
308
|
+
function getAttrCompat(el, name) {
|
|
309
|
+
const value = el.getAttribute(name);
|
|
310
|
+
if (value !== null) return value;
|
|
311
|
+
const target = name.toLowerCase();
|
|
312
|
+
for (let i = 0; i < el.attributes.length; i++) {
|
|
313
|
+
const attr = el.attributes[i];
|
|
314
|
+
if (attr.name.toLowerCase() === target) return attr.value;
|
|
315
|
+
}
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
function hasAttrCompat(el, name) {
|
|
319
|
+
return getAttrCompat(el, name) !== null;
|
|
320
|
+
}
|
|
306
321
|
function getTriggerTestId(trigger, attrName) {
|
|
307
322
|
if (!trigger) return "unknown";
|
|
308
|
-
return trigger
|
|
323
|
+
return getAttrCompat(trigger, attrName) || trigger.getAttribute("id") || "unknown";
|
|
324
|
+
}
|
|
325
|
+
function resolveTriggerInContainer(el, attrName) {
|
|
326
|
+
if (hasAttrCompat(el, attrName)) return el;
|
|
327
|
+
const children = el.querySelectorAll("[id], [data-testid], [data-testId], [data-testID]");
|
|
328
|
+
for (const child of children) {
|
|
329
|
+
if (child instanceof HTMLElement && hasAttrCompat(child, attrName)) return child;
|
|
330
|
+
}
|
|
331
|
+
return null;
|
|
309
332
|
}
|
|
310
333
|
function findActiveTrigger(attrName, pc) {
|
|
311
|
-
const resolveInContainer = (el) => {
|
|
312
|
-
if (el.hasAttribute(attrName)) return el;
|
|
313
|
-
const inner = el.querySelector(`[${attrName}]`);
|
|
314
|
-
if (inner) return inner;
|
|
315
|
-
return null;
|
|
316
|
-
};
|
|
317
334
|
const focused = document.querySelector(sel(pc, "picker-focused"));
|
|
318
335
|
if (focused) {
|
|
319
|
-
const found =
|
|
336
|
+
const found = resolveTriggerInContainer(focused, attrName);
|
|
320
337
|
if (found) return found;
|
|
321
338
|
const input = focused.querySelector("input[id]");
|
|
322
339
|
if (input) return input;
|
|
323
340
|
}
|
|
324
341
|
const selectOpen = document.querySelector(sel(pc, "select-open"));
|
|
325
342
|
if (selectOpen) {
|
|
326
|
-
const found =
|
|
343
|
+
const found = resolveTriggerInContainer(selectOpen, attrName);
|
|
327
344
|
if (found) return found;
|
|
328
345
|
}
|
|
329
346
|
const cascader = document.querySelector(sel(pc, "cascader-picker-focused"));
|
|
330
347
|
if (cascader) {
|
|
331
|
-
const found =
|
|
348
|
+
const found = resolveTriggerInContainer(cascader, attrName);
|
|
332
349
|
if (found) return found;
|
|
333
350
|
}
|
|
334
351
|
const expanded = document.querySelector(
|
|
335
|
-
`[
|
|
352
|
+
`[id][aria-expanded="true"], [data-testid][aria-expanded="true"], [data-testId][aria-expanded="true"]`
|
|
336
353
|
);
|
|
337
|
-
if (expanded) return expanded;
|
|
354
|
+
if (expanded && hasAttrCompat(expanded, attrName)) return expanded;
|
|
338
355
|
return null;
|
|
339
356
|
}
|
|
340
357
|
function injectSubElements(root, attrName, pc) {
|
|
341
358
|
const inputNumbers = root.matches(`${sel(pc, "input-number")}[${attrName}]`) ? [root] : Array.from(root.querySelectorAll(`${sel(pc, "input-number")}[${attrName}]`));
|
|
342
359
|
for (const wrapper of inputNumbers) {
|
|
343
360
|
if (!(wrapper instanceof HTMLElement)) continue;
|
|
344
|
-
const testId = wrapper
|
|
361
|
+
const testId = getAttrCompat(wrapper, attrName) || "";
|
|
345
362
|
if (!testId) continue;
|
|
346
363
|
const input = wrapper.querySelector(sel(pc, "input-number-input"));
|
|
347
|
-
if (input && !input
|
|
348
|
-
input.setAttribute(attrName, testId);
|
|
364
|
+
if (input && !hasAttrCompat(input, attrName)) {
|
|
365
|
+
input.setAttribute(attrName, `${testId}-input`);
|
|
349
366
|
}
|
|
350
367
|
const upHandler = wrapper.querySelector(sel(pc, "input-number-handler-up"));
|
|
351
|
-
if (upHandler && !upHandler
|
|
368
|
+
if (upHandler && !hasAttrCompat(upHandler, attrName)) {
|
|
352
369
|
upHandler.setAttribute(attrName, `${testId}-up`);
|
|
353
370
|
}
|
|
354
371
|
const downHandler = wrapper.querySelector(sel(pc, "input-number-handler-down"));
|
|
355
|
-
if (downHandler && !downHandler
|
|
372
|
+
if (downHandler && !hasAttrCompat(downHandler, attrName)) {
|
|
356
373
|
downHandler.setAttribute(attrName, `${testId}-down`);
|
|
357
374
|
}
|
|
358
375
|
}
|
|
359
376
|
const sliders = root.matches(`${sel(pc, "slider")}[${attrName}]`) ? [root] : Array.from(root.querySelectorAll(`${sel(pc, "slider")}[${attrName}]`));
|
|
360
377
|
for (const slider of sliders) {
|
|
361
378
|
if (!(slider instanceof HTMLElement)) continue;
|
|
362
|
-
const testId = slider
|
|
379
|
+
const testId = getAttrCompat(slider, attrName) || "";
|
|
363
380
|
if (!testId) continue;
|
|
364
381
|
slider.querySelectorAll(sel(pc, "slider-handle")).forEach((h, i) => {
|
|
365
382
|
const el = h;
|
|
366
|
-
if (!el
|
|
383
|
+
if (!hasAttrCompat(el, attrName)) {
|
|
367
384
|
el.setAttribute(attrName, `${testId}-handle-${i}`);
|
|
368
385
|
}
|
|
369
386
|
});
|
|
@@ -371,11 +388,11 @@ function injectSubElements(root, attrName, pc) {
|
|
|
371
388
|
const rates = root.matches(`${sel(pc, "rate")}[${attrName}]`) ? [root] : Array.from(root.querySelectorAll(`${sel(pc, "rate")}[${attrName}]`));
|
|
372
389
|
for (const rate of rates) {
|
|
373
390
|
if (!(rate instanceof HTMLElement)) continue;
|
|
374
|
-
const testId = rate
|
|
391
|
+
const testId = getAttrCompat(rate, attrName) || "";
|
|
375
392
|
if (!testId) continue;
|
|
376
393
|
rate.querySelectorAll(sel(pc, "rate-star")).forEach((s, i) => {
|
|
377
394
|
const el = s;
|
|
378
|
-
if (!el
|
|
395
|
+
if (!hasAttrCompat(el, attrName)) {
|
|
379
396
|
el.setAttribute(attrName, `${testId}-star-${i}`);
|
|
380
397
|
}
|
|
381
398
|
});
|
|
@@ -384,17 +401,52 @@ function injectSubElements(root, attrName, pc) {
|
|
|
384
401
|
const uploadById = uploadByTestId.length === 0 ? root.matches(`${sel(pc, "upload")}[id]`) ? [root] : Array.from(root.querySelectorAll(`${sel(pc, "upload")}[id]`)) : [];
|
|
385
402
|
for (const upload of [...uploadByTestId, ...uploadById]) {
|
|
386
403
|
if (!(upload instanceof HTMLElement)) continue;
|
|
387
|
-
const testId = upload
|
|
404
|
+
const testId = getAttrCompat(upload, attrName) || upload.getAttribute("id") || "";
|
|
388
405
|
if (!testId) continue;
|
|
389
406
|
const input = upload.querySelector('input[type="file"]');
|
|
390
|
-
if (input && !input
|
|
407
|
+
if (input && !hasAttrCompat(input, attrName)) {
|
|
391
408
|
input.setAttribute(attrName, `${testId}-input`);
|
|
392
|
-
if (!upload
|
|
409
|
+
if (!hasAttrCompat(upload, attrName)) {
|
|
393
410
|
upload.setAttribute(attrName, testId);
|
|
394
411
|
}
|
|
395
412
|
}
|
|
396
413
|
}
|
|
397
414
|
}
|
|
415
|
+
function injectTabsSubElements(root, attrName, pc) {
|
|
416
|
+
const tabsContainers = root.matches(`${sel(pc, "tabs")}[${attrName}]`) ? [root] : Array.from(root.querySelectorAll(`${sel(pc, "tabs")}[${attrName}]`));
|
|
417
|
+
for (const tabs of tabsContainers) {
|
|
418
|
+
if (!(tabs instanceof HTMLElement)) continue;
|
|
419
|
+
const testId = getAttrCompat(tabs, attrName) || "";
|
|
420
|
+
if (!testId) continue;
|
|
421
|
+
const tabItems = tabs.querySelectorAll(sel(pc, "tabs-tab"));
|
|
422
|
+
tabItems.forEach((tab, i) => {
|
|
423
|
+
const el = tab;
|
|
424
|
+
if (hasAttrCompat(el, attrName)) return;
|
|
425
|
+
const btn = el.querySelector(sel(pc, "tabs-tab-btn"));
|
|
426
|
+
const text = (btn?.textContent || el.textContent || "").trim().replace(/\s+/g, "-");
|
|
427
|
+
el.setAttribute(attrName, `${testId}-tab-${i}-${text}`);
|
|
428
|
+
});
|
|
429
|
+
const panes = tabs.querySelectorAll(sel(pc, "tabs-tabpane"));
|
|
430
|
+
panes.forEach((pane, i) => {
|
|
431
|
+
const el = pane;
|
|
432
|
+
if (hasAttrCompat(el, attrName)) return;
|
|
433
|
+
const tabBtn = tabItems[i]?.querySelector(sel(pc, "tabs-tab-btn"));
|
|
434
|
+
const text = (tabBtn?.textContent || "").trim().replace(/\s+/g, "-");
|
|
435
|
+
el.setAttribute(attrName, `${testId}-pane-${i}-${text}`);
|
|
436
|
+
});
|
|
437
|
+
const navOps = tabs.querySelector(sel(pc, "tabs-nav-operations"));
|
|
438
|
+
if (navOps instanceof HTMLElement) {
|
|
439
|
+
const addBtn = navOps.querySelector(sel(pc, "tabs-nav-add"));
|
|
440
|
+
if (addBtn instanceof HTMLElement && !hasAttrCompat(addBtn, attrName)) {
|
|
441
|
+
addBtn.setAttribute(attrName, `${testId}-add`);
|
|
442
|
+
}
|
|
443
|
+
const moreBtn = navOps.querySelector(sel(pc, "tabs-nav-more"));
|
|
444
|
+
if (moreBtn instanceof HTMLElement && !hasAttrCompat(moreBtn, attrName)) {
|
|
445
|
+
moreBtn.setAttribute(attrName, `${testId}-more`);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
398
450
|
function setupAntTestIds(opts = {}) {
|
|
399
451
|
const attrName = opts.attributeName ?? "data-testid";
|
|
400
452
|
const pc = opts.prefixCls ?? "ant";
|
|
@@ -410,24 +462,18 @@ function setupAntTestIds(opts = {}) {
|
|
|
410
462
|
const containers = node.matches(selector) ? [node] : Array.from(node.querySelectorAll(selector));
|
|
411
463
|
for (const container of containers) {
|
|
412
464
|
if (!(container instanceof HTMLElement)) continue;
|
|
413
|
-
if (container
|
|
465
|
+
if (hasAttrCompat(container, attrName)) continue;
|
|
414
466
|
if (strategy) {
|
|
415
467
|
queueMicrotask(() => {
|
|
416
|
-
const
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
if (triggerTestId === "unknown" && retriesLeft > 0) {
|
|
420
|
-
requestAnimationFrame(() => runPanelInjection(retriesLeft - 1));
|
|
421
|
-
return;
|
|
422
|
-
}
|
|
423
|
-
strategy({ container, trigger, triggerTestId, attrName });
|
|
424
|
-
};
|
|
425
|
-
runPanelInjection(3);
|
|
468
|
+
const trigger = findActiveTrigger(attrName, pc);
|
|
469
|
+
const triggerTestId = getTriggerTestId(trigger, attrName);
|
|
470
|
+
strategy({ container, trigger, triggerTestId, attrName });
|
|
426
471
|
});
|
|
427
472
|
}
|
|
428
473
|
}
|
|
429
474
|
}
|
|
430
475
|
injectSubElements(node, attrName, pc);
|
|
476
|
+
injectTabsSubElements(node, attrName, pc);
|
|
431
477
|
}
|
|
432
478
|
let pending = false;
|
|
433
479
|
let pendingNodes = [];
|
|
@@ -470,7 +516,7 @@ function injectCurrentDropdowns(opts = {}) {
|
|
|
470
516
|
const containers = document.querySelectorAll(selector);
|
|
471
517
|
containers.forEach((container) => {
|
|
472
518
|
if (!(container instanceof HTMLElement)) return;
|
|
473
|
-
if (container
|
|
519
|
+
if (hasAttrCompat(container, attrName)) return;
|
|
474
520
|
if (!strategy) return;
|
|
475
521
|
const trigger = findActiveTrigger(attrName, pc);
|
|
476
522
|
const triggerTestId = getTriggerTestId(trigger, attrName);
|
|
@@ -478,6 +524,7 @@ function injectCurrentDropdowns(opts = {}) {
|
|
|
478
524
|
});
|
|
479
525
|
}
|
|
480
526
|
injectSubElements(document.body, attrName, pc);
|
|
527
|
+
injectTabsSubElements(document.body, attrName, pc);
|
|
481
528
|
injectMenuItems(attrName, document, pc);
|
|
482
529
|
}
|
|
483
530
|
function mergeMatchers(defaults, customs) {
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 编译期模板 transform。
|
|
3
|
+
*
|
|
4
|
+
* 两个独立的 NodeTransform:
|
|
5
|
+
* 1. multiRootFixTransform — 多根子组件上自动补 v-bind="$attrs"
|
|
6
|
+
* 确保 data-testid 能穿透 Fragment 落到 antd 组件 DOM 上。
|
|
7
|
+
* 2. testIdInjectTransform — 在父模板中为每个组件使用点注入唯一的
|
|
8
|
+
* data-testid="{tagName}-{index}",保证同一页面中同类型组件的
|
|
9
|
+
* testid 全局唯一且稳定。
|
|
10
|
+
*
|
|
11
|
+
* 两个 transform 均通过 compileTime: true 下的 createTestIdTransforms() 一键获得。
|
|
12
|
+
*/
|
|
13
|
+
import type { NodeTransform } from '@vue/compiler-core';
|
|
14
|
+
export interface TransformOptions {
|
|
15
|
+
/** testid 属性名,默认 'data-testid' */
|
|
16
|
+
attributeName?: string;
|
|
17
|
+
/** ant-design-vue 的 tag 前缀,默认 'a-' */
|
|
18
|
+
antPrefix?: string;
|
|
19
|
+
/** 额外需要注入 testid 的自定义组件前缀 */
|
|
20
|
+
customPrefixes?: string[];
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* 转换多根子组件的模板,自动给第一个 antd 组件根元素加 v-bind="$attrs"。
|
|
24
|
+
*
|
|
25
|
+
* 为什么需要:多根组件(Fragment)的 inheritAttrs 不生效,父模板编译期
|
|
26
|
+
* 注入的 data-testid 属性会被丢弃。本 transform 确保至少一个 antd 组件根
|
|
27
|
+
* 能收到透传属性。
|
|
28
|
+
*
|
|
29
|
+
* 安全策略:
|
|
30
|
+
* - 先检查用户是否已手动写了任一 v-bind="$attrs",有则不动
|
|
31
|
+
* - 只在模板是多根且至少有一个 antd 组件时才注入
|
|
32
|
+
*/
|
|
33
|
+
export declare function createMultiRootFixTransform(opts?: TransformOptions): NodeTransform;
|
|
34
|
+
/**
|
|
35
|
+
* 在模板编译期为所有(antd + 自定义)组件注入唯一的 data-testid。
|
|
36
|
+
*
|
|
37
|
+
* 格式:`{tagName}-{perTemplateIndex}`
|
|
38
|
+
* 例:`myinput-0`, `myinput-1`, `a-select-0`, `a-button-2`
|
|
39
|
+
*
|
|
40
|
+
* key 设计:
|
|
41
|
+
* - tagName 保持原始大小写,确保一眼认出组件来源
|
|
42
|
+
* - index 是严格按模板 AST 中的出现顺序递增的,不同模板各自重置
|
|
43
|
+
* - 如果用户已经手动写了 data-testid,则跳过(不覆盖)
|
|
44
|
+
*/
|
|
45
|
+
export declare function createTestIdInjectTransform(opts?: TransformOptions): NodeTransform;
|
|
46
|
+
/**
|
|
47
|
+
* 返回编译期所需的全部 NodeTransform。
|
|
48
|
+
*
|
|
49
|
+
* 使用方法:
|
|
50
|
+
* ```ts
|
|
51
|
+
* // vite.config.ts
|
|
52
|
+
* import vue from '@vitejs/plugin-vue'
|
|
53
|
+
* import { testIdTransforms } from 'vite-plugin-vue-testid'
|
|
54
|
+
*
|
|
55
|
+
* export default defineConfig({
|
|
56
|
+
* plugins: [
|
|
57
|
+
* vue({
|
|
58
|
+
* template: {
|
|
59
|
+
* compilerOptions: {
|
|
60
|
+
* nodeTransforms: testIdTransforms()
|
|
61
|
+
* }
|
|
62
|
+
* }
|
|
63
|
+
* }),
|
|
64
|
+
* ]
|
|
65
|
+
* })
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
export declare function createTestIdTransforms(opts?: TransformOptions): NodeTransform[];
|
|
69
|
+
//# sourceMappingURL=transform.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../src/transform.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EACV,aAAa,EAMd,MAAM,oBAAoB,CAAA;AAgD3B,MAAM,WAAW,gBAAgB;IAC/B,kCAAkC;IAClC,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,sCAAsC;IACtC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,6BAA6B;IAC7B,cAAc,CAAC,EAAE,MAAM,EAAE,CAAA;CAC1B;AAyFD;;;;;;;;;;GAUG;AACH,wBAAgB,2BAA2B,CAAC,IAAI,GAAE,gBAAqB,GAAG,aAAa,CA8BtF;AAID;;;;;;;;;;GAUG;AACH,wBAAgB,2BAA2B,CAAC,IAAI,GAAE,gBAAqB,GAAG,aAAa,CAqCtF;AAID;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,GAAE,gBAAqB,GAAG,aAAa,EAAE,CAOnF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vite-plugin-vue-testid",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.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",
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"vue": ">=3.3.0"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
|
+
"@vue/compiler-core": "^3.3.0",
|
|
46
47
|
"tsup": "^8.5.1",
|
|
47
48
|
"typescript": "~6.0.2",
|
|
48
49
|
"vite": "^8.0.12"
|