vite-plugin-vue-testid 1.0.8 → 1.0.10
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 +0 -10
- package/README.md +3 -13
- package/dist/index.d.ts +44 -51
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -152
- package/dist/runtime.d.ts +45 -40
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +357 -270
- package/package.json +1 -4
package/README.en.md
CHANGED
|
@@ -13,7 +13,6 @@ A Vite plugin that auto-injects `data-testid` into Vue component templates via c
|
|
|
13
13
|
- **Date panel mode awareness** — date / month / year / decade panels are distinguished automatically
|
|
14
14
|
- **MutationObserver re-injection** — New DOM elements from panel switching, tree node expand/collapse are detected and injected automatically
|
|
15
15
|
- **Extensible** — Custom component lists, testId generation rules, and overlay injection strategies are supported
|
|
16
|
-
- **prefixCls compatible** — Supports ant-design-vue global ConfigProvider custom `prefixCls` with auto-detection or manual config
|
|
17
16
|
|
|
18
17
|
---
|
|
19
18
|
|
|
@@ -244,14 +243,6 @@ const cleanup = setupAntDropdownTestIds({
|
|
|
244
243
|
*/
|
|
245
244
|
attributeName: 'data-testid',
|
|
246
245
|
|
|
247
|
-
/**
|
|
248
|
-
* ant-design-vue global CSS class prefix.
|
|
249
|
-
* Required when ConfigProvider prefixCls is set to a non-default value.
|
|
250
|
-
* When not set, auto-detects from DOM (CSS vars → button class → fallback 'ant').
|
|
251
|
-
* @default auto-detect || 'ant'
|
|
252
|
-
*/
|
|
253
|
-
prefixCls: 'myapp',
|
|
254
|
-
|
|
255
246
|
/**
|
|
256
247
|
* Custom panel selector → injection strategy mapping
|
|
257
248
|
* key: CSS selector
|
|
@@ -264,7 +255,6 @@ const cleanup = setupAntDropdownTestIds({
|
|
|
264
255
|
// ctx.trigger — trigger HTMLElement | null
|
|
265
256
|
// ctx.triggerTestId — the trigger's testId value
|
|
266
257
|
// ctx.attrName — testId attribute name
|
|
267
|
-
// ctx.prefixCls — ant-design-vue CSS class prefix
|
|
268
258
|
const prefix = `${ctx.triggerTestId}-dropdown`
|
|
269
259
|
ctx.container.setAttribute(ctx.attrName, prefix)
|
|
270
260
|
// ... custom injection logic
|
package/README.md
CHANGED
|
@@ -13,7 +13,6 @@
|
|
|
13
13
|
- **日期面板模式感知** — date / month / year / decade 面板自动区分
|
|
14
14
|
- **MutationObserver 重注入** — 面板切换、树节点展开/折叠时自动检测并注入新 DOM
|
|
15
15
|
- **可扩展** — 支持自定义组件列表、testId 生成规则、弹出面板注入策略
|
|
16
|
-
- **prefixCls 兼容** — 支持 ant-design-vue 全局 ConfigProvider 自定义 `prefixCls`,自动检测或手动配置
|
|
17
16
|
|
|
18
17
|
---
|
|
19
18
|
|
|
@@ -244,14 +243,6 @@ const cleanup = setupAntDropdownTestIds({
|
|
|
244
243
|
*/
|
|
245
244
|
attributeName: 'data-testid',
|
|
246
245
|
|
|
247
|
-
/**
|
|
248
|
-
* ant-design-vue 全局 CSS 类名前缀。
|
|
249
|
-
* 如果通过 ConfigProvider 配置了 prefixCls,必须传入相同值。
|
|
250
|
-
* 未配置时自动从 DOM 检测(扫描 CSS 变量 → button 类名 → 回退 'ant')。
|
|
251
|
-
* @default auto-detect || 'ant'
|
|
252
|
-
*/
|
|
253
|
-
prefixCls: 'myapp',
|
|
254
|
-
|
|
255
246
|
/**
|
|
256
247
|
* 自定义面板选择器 → 注入策略映射
|
|
257
248
|
* key: CSS 选择器
|
|
@@ -260,11 +251,10 @@ const cleanup = setupAntDropdownTestIds({
|
|
|
260
251
|
panels: {
|
|
261
252
|
'.ant-picker-dropdown': undefined, // 禁用内置 Picker 面板注入
|
|
262
253
|
'.my-custom-dropdown': (ctx) => {
|
|
263
|
-
// ctx.container
|
|
264
|
-
// ctx.trigger
|
|
254
|
+
// ctx.container — 面板容器 HTMLElement
|
|
255
|
+
// ctx.trigger — 触发器 HTMLElement | null
|
|
265
256
|
// ctx.triggerTestId — 触发器的 testId 值
|
|
266
|
-
// ctx.attrName
|
|
267
|
-
// ctx.prefixCls — ant-design-vue CSS 类名前缀
|
|
257
|
+
// ctx.attrName — testId 属性名
|
|
268
258
|
const prefix = `${ctx.triggerTestId}-dropdown`
|
|
269
259
|
ctx.container.setAttribute(ctx.attrName, prefix)
|
|
270
260
|
// ... 自定义注入逻辑
|
package/dist/index.d.ts
CHANGED
|
@@ -1,64 +1,57 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
1
|
+
/**
|
|
2
|
+
* vite-plugin-vue-testid
|
|
3
|
+
*
|
|
4
|
+
* 为 ant-design-vue 组件的 DOM 元素自动注入 data-testid 属性,
|
|
5
|
+
* 全运行时实现,无需编译期模版转换。
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* // vite.config.ts — 无需特殊配置
|
|
10
|
+
* import vue from '@vitejs/plugin-vue'
|
|
11
|
+
* export default defineConfig({ plugins: [vue()] })
|
|
12
|
+
*
|
|
13
|
+
* // main.ts — 启动运行时注入
|
|
14
|
+
* import { setupAntTestIds } from 'vite-plugin-vue-testid/runtime'
|
|
15
|
+
* createApp(App).mount('#app')
|
|
16
|
+
* setupAntTestIds()
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
import type { Plugin } from 'vite';
|
|
20
|
+
export type { RuntimeOptions, PanelContext, PanelInjectStrategy } from './runtime';
|
|
21
|
+
export interface VueTestIdPluginOptions {
|
|
15
22
|
/**
|
|
16
23
|
* testId 属性名
|
|
17
24
|
* @default 'data-testid'
|
|
18
25
|
*/
|
|
19
26
|
attributeName?: string;
|
|
20
|
-
/**
|
|
21
|
-
* 需要同时注入 id + testId 的组件(如弹窗、日期选择器等 teleport 组件)
|
|
22
|
-
* @default ['a-date-picker', 'a-range-picker', 'a-time-picker', 'a-modal']
|
|
23
|
-
*/
|
|
24
|
-
idComponents?: string[];
|
|
25
|
-
/**
|
|
26
|
-
* teleport 组件的 id 属性名
|
|
27
|
-
* @default 'id'
|
|
28
|
-
*/
|
|
29
|
-
idAttributeName?: string;
|
|
30
27
|
}
|
|
31
28
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
29
|
+
* Vite 插件(可选)。
|
|
30
|
+
* 当前仅作为占位,实际 testid 注入完全由运行时 `setupAntTestIds()` 完成。
|
|
34
31
|
*
|
|
35
|
-
*
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
*
|
|
32
|
+
* 如果需要在构建时做额外配置,可通过此插件传入选项。
|
|
33
|
+
*/
|
|
34
|
+
export declare function vueTestIdPlugin(_opts?: VueTestIdPluginOptions): Plugin;
|
|
35
|
+
/**
|
|
36
|
+
* @deprecated 此函数已废弃。testid 注入现在完全在运行时进行,
|
|
37
|
+
* 不再需要编译期 AST transform。请从 vite.config.ts 中移除 `nodeTransforms` 配置,
|
|
38
|
+
* 改为在 main.ts 中调用 `setupAntTestIds()`。
|
|
40
39
|
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* })
|
|
52
|
-
* ```
|
|
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()
|
|
53
50
|
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
* components: ['a-input', 'a-select', 'el-input'],
|
|
58
|
-
* generateId: (tag, n) => `myapp-${tag}-${n}`,
|
|
59
|
-
* attributeName: 'data-cy',
|
|
60
|
-
* })
|
|
51
|
+
* // main.ts
|
|
52
|
+
* + import { setupAntTestIds } from 'vite-plugin-vue-testid/runtime'
|
|
53
|
+
* + setupAntTestIds()
|
|
61
54
|
* ```
|
|
62
55
|
*/
|
|
63
|
-
export declare function createVueTestIdTransform(
|
|
56
|
+
export declare function createVueTestIdTransform(): () => void;
|
|
64
57
|
//# 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":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAIlC,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAA;AAIlF,MAAM,WAAW,sBAAsB;IACrC;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,KAAK,CAAC,EAAE,sBAAsB,GAAG,MAAM,CAItE;AAID;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,wBAAwB,IAAI,MAAM,IAAI,CASrD"}
|
package/dist/index.js
CHANGED
|
@@ -1,161 +1,19 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
|
|
3
|
-
var DEFAULT_COMPONENTS = [
|
|
4
|
-
"a-input",
|
|
5
|
-
"a-textarea",
|
|
6
|
-
"a-input-number",
|
|
7
|
-
"a-select",
|
|
8
|
-
"a-cascader",
|
|
9
|
-
"a-tree-select",
|
|
10
|
-
"a-radio-group",
|
|
11
|
-
"a-checkbox-group",
|
|
12
|
-
"a-date-picker",
|
|
13
|
-
"a-range-picker",
|
|
14
|
-
"a-time-picker",
|
|
15
|
-
"a-switch",
|
|
16
|
-
"a-slider",
|
|
17
|
-
"a-rate",
|
|
18
|
-
"a-upload",
|
|
19
|
-
"a-form-item",
|
|
20
|
-
"a-button",
|
|
21
|
-
"a-modal",
|
|
22
|
-
"a-menu",
|
|
23
|
-
"a-menu-item",
|
|
24
|
-
"a-sub-menu",
|
|
25
|
-
"a-menu-item-group"
|
|
26
|
-
];
|
|
27
|
-
var DEFAULT_ID_COMPONENTS = [
|
|
28
|
-
"a-date-picker",
|
|
29
|
-
"a-range-picker",
|
|
30
|
-
"a-time-picker",
|
|
31
|
-
"a-modal",
|
|
32
|
-
"a-upload",
|
|
33
|
-
"a-menu-item",
|
|
34
|
-
"a-sub-menu",
|
|
35
|
-
"a-menu-item-group"
|
|
36
|
-
];
|
|
37
|
-
function resolveOptions(raw) {
|
|
2
|
+
function vueTestIdPlugin(_opts) {
|
|
38
3
|
return {
|
|
39
|
-
|
|
40
|
-
generateId: raw?.generateId ?? ((tag, n) => `${tag}-${n}`),
|
|
41
|
-
attributeName: raw?.attributeName ?? "data-testid",
|
|
42
|
-
idComponents: raw?.idComponents ?? DEFAULT_ID_COMPONENTS,
|
|
43
|
-
idAttributeName: raw?.idAttributeName ?? "id"
|
|
4
|
+
name: "vite-plugin-vue-testid"
|
|
44
5
|
};
|
|
45
6
|
}
|
|
46
|
-
function
|
|
47
|
-
if (
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
} else if (exp.type === NodeTypes.COMPOUND_EXPRESSION) {
|
|
53
|
-
const hasIndex = exp.children.some((c) => {
|
|
54
|
-
if (typeof c === "string") return /\bindex\b/.test(c);
|
|
55
|
-
if (typeof c === "symbol") return false;
|
|
56
|
-
if (c.type === NodeTypes.TEXT || c.type === NodeTypes.SIMPLE_EXPRESSION) {
|
|
57
|
-
return /\bindex\b/.test(c.content);
|
|
58
|
-
}
|
|
59
|
-
return false;
|
|
60
|
-
});
|
|
61
|
-
if (hasIndex) return;
|
|
62
|
-
for (let i = exp.children.length - 1; i >= 0; i--) {
|
|
63
|
-
const c = exp.children[i];
|
|
64
|
-
if (typeof c === "string") {
|
|
65
|
-
exp.children[i] = c.replace(/}$/, ", index}");
|
|
66
|
-
break;
|
|
67
|
-
}
|
|
68
|
-
if (typeof c !== "symbol" && c.type === NodeTypes.TEXT) {
|
|
69
|
-
c.content = c.content.replace(/}$/, ", index}");
|
|
70
|
-
break;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
7
|
+
function createVueTestIdTransform() {
|
|
8
|
+
if (typeof console !== "undefined") {
|
|
9
|
+
console.warn(
|
|
10
|
+
"[vite-plugin-vue-testid] createVueTestIdTransform() is deprecated. Remove nodeTransforms from vite.config.ts and call setupAntTestIds() in main.ts instead. See https://github.com/... for migration guide."
|
|
11
|
+
);
|
|
73
12
|
}
|
|
74
|
-
|
|
75
|
-
function hasExistingTestId(node, attrName) {
|
|
76
|
-
return node.props.some(
|
|
77
|
-
(p) => p.type === NodeTypes.ATTRIBUTE && p.name === attrName || p.type === NodeTypes.DIRECTIVE && p.name === "bind" && p.arg && p.arg.type === NodeTypes.SIMPLE_EXPRESSION && p.arg.content === attrName
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
function createStaticAttr(name, value, loc) {
|
|
81
|
-
return {
|
|
82
|
-
type: NodeTypes.ATTRIBUTE,
|
|
83
|
-
name,
|
|
84
|
-
nameLoc: loc,
|
|
85
|
-
value: { type: NodeTypes.TEXT, content: value, loc },
|
|
86
|
-
loc
|
|
13
|
+
return () => {
|
|
87
14
|
};
|
|
88
15
|
}
|
|
89
|
-
function createBindDirective(attrName, exprContent, loc) {
|
|
90
|
-
return {
|
|
91
|
-
type: NodeTypes.DIRECTIVE,
|
|
92
|
-
name: "bind",
|
|
93
|
-
arg: {
|
|
94
|
-
type: NodeTypes.SIMPLE_EXPRESSION,
|
|
95
|
-
content: attrName,
|
|
96
|
-
isStatic: true,
|
|
97
|
-
constType: ConstantTypes.CAN_STRINGIFY,
|
|
98
|
-
loc
|
|
99
|
-
},
|
|
100
|
-
exp: {
|
|
101
|
-
type: NodeTypes.SIMPLE_EXPRESSION,
|
|
102
|
-
content: exprContent,
|
|
103
|
-
isStatic: false,
|
|
104
|
-
constType: ConstantTypes.NOT_CONSTANT,
|
|
105
|
-
loc
|
|
106
|
-
},
|
|
107
|
-
modifiers: [],
|
|
108
|
-
loc
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
function createASTTransform(opts) {
|
|
112
|
-
let count = 0;
|
|
113
|
-
let insideTableDepth = 0;
|
|
114
|
-
let insideBodyCellDepth = 0;
|
|
115
|
-
const componentSet = new Set(opts.components);
|
|
116
|
-
const idComponentSet = new Set(opts.idComponents);
|
|
117
|
-
return (node) => {
|
|
118
|
-
if (node.type !== NodeTypes.ELEMENT) return;
|
|
119
|
-
const tagName = node.tag.toLowerCase();
|
|
120
|
-
if (tagName === "a-table") {
|
|
121
|
-
insideTableDepth++;
|
|
122
|
-
return () => {
|
|
123
|
-
insideTableDepth--;
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
if (tagName === "template" && insideTableDepth > 0) {
|
|
127
|
-
const slotDir = node.props.find(
|
|
128
|
-
(p) => p.type === NodeTypes.DIRECTIVE && p.name === "slot" && p.arg && p.arg.type === NodeTypes.SIMPLE_EXPRESSION && p.arg.content === "bodyCell"
|
|
129
|
-
);
|
|
130
|
-
if (slotDir && slotDir.type === NodeTypes.DIRECTIVE) {
|
|
131
|
-
ensureSlotIndex(slotDir.exp);
|
|
132
|
-
insideBodyCellDepth++;
|
|
133
|
-
return () => {
|
|
134
|
-
insideBodyCellDepth--;
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
if (insideTableDepth > 0 && insideBodyCellDepth === 0) return;
|
|
139
|
-
if (!componentSet.has(tagName)) return;
|
|
140
|
-
if (hasExistingTestId(node, opts.attributeName)) return;
|
|
141
|
-
const testIdValue = opts.generateId(tagName, count++);
|
|
142
|
-
if (insideBodyCellDepth > 0) {
|
|
143
|
-
const expr = `\`\${column?.dataIndex || column?.key || 'cell'}-\${index}-${testIdValue}\``;
|
|
144
|
-
node.props.push(createBindDirective(opts.attributeName, expr, node.loc));
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
if (idComponentSet.has(tagName)) {
|
|
148
|
-
node.props.push(createStaticAttr(opts.idAttributeName, testIdValue, node.loc));
|
|
149
|
-
node.props.push(createStaticAttr(opts.attributeName, testIdValue, node.loc));
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
node.props.push(createStaticAttr(opts.attributeName, testIdValue, node.loc));
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
function createVueTestIdTransform(rawOpts) {
|
|
156
|
-
const opts = resolveOptions(rawOpts);
|
|
157
|
-
return createASTTransform(opts);
|
|
158
|
-
}
|
|
159
16
|
export {
|
|
160
|
-
createVueTestIdTransform
|
|
17
|
+
createVueTestIdTransform,
|
|
18
|
+
vueTestIdPlugin
|
|
161
19
|
};
|
package/dist/runtime.d.ts
CHANGED
|
@@ -1,30 +1,44 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* 运行时 testid 注入:通过 MutationObserver 为 ant-design-vue 组件的 DOM 元素
|
|
3
|
+
* 自动注入 data-testid,解决编译期注入的诸多限制。
|
|
4
4
|
*
|
|
5
|
-
* 使用方式(在 main.ts
|
|
5
|
+
* 使用方式(在 main.ts 中调用一次即可):
|
|
6
6
|
* ```ts
|
|
7
|
-
* import {
|
|
7
|
+
* import { setupAntTestIds } from 'vite-plugin-vue-testid/runtime'
|
|
8
8
|
*
|
|
9
|
-
*
|
|
9
|
+
* createApp(App).mount('#app')
|
|
10
|
+
* setupAntTestIds()
|
|
10
11
|
*
|
|
11
|
-
* //
|
|
12
|
-
*
|
|
12
|
+
* // 支持自定义 prefixCls(对应 ant-design-vue ConfigProvider 的 prefixCls):
|
|
13
|
+
* setupAntTestIds({ prefixCls: 'custom' })
|
|
13
14
|
* ```
|
|
14
15
|
*/
|
|
15
16
|
export interface RuntimeOptions {
|
|
16
17
|
/**
|
|
17
|
-
* testId
|
|
18
|
+
* testId 属性名
|
|
18
19
|
* @default 'data-testid'
|
|
19
20
|
*/
|
|
20
21
|
attributeName?: string;
|
|
21
22
|
/**
|
|
22
|
-
* ant-design-vue
|
|
23
|
-
*
|
|
24
|
-
* 未配置时自动从 DOM 检测,检测失败回退到 'ant'。
|
|
25
|
-
* @default auto-detect || 'ant'
|
|
23
|
+
* ant-design-vue CSS 类名前缀,对应 ConfigProvider 的 prefixCls
|
|
24
|
+
* @default 'ant'
|
|
26
25
|
*/
|
|
27
26
|
prefixCls?: string;
|
|
27
|
+
/**
|
|
28
|
+
* 自定义组件 CSS 选择器 → testid 前缀映射。
|
|
29
|
+
* 会与默认映射合并(自定义覆盖默认)。
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* setupAntTestIds({
|
|
34
|
+
* components: {
|
|
35
|
+
* '.el-input': 'el-input',
|
|
36
|
+
* '.el-button': 'el-button',
|
|
37
|
+
* }
|
|
38
|
+
* })
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
components?: Record<string, string>;
|
|
28
42
|
/**
|
|
29
43
|
* 需要监听的 teleport 面板 CSS 选择器映射
|
|
30
44
|
* key: 面板容器选择器
|
|
@@ -41,46 +55,37 @@ export interface PanelContext {
|
|
|
41
55
|
triggerTestId: string;
|
|
42
56
|
/** testId 属性名 */
|
|
43
57
|
attrName: string;
|
|
44
|
-
/** ant-design-vue 当前生效的 CSS 类名前缀 */
|
|
45
|
-
prefixCls: string;
|
|
46
58
|
}
|
|
47
59
|
export type PanelInjectStrategy = (ctx: PanelContext) => void;
|
|
48
60
|
/**
|
|
49
|
-
*
|
|
50
|
-
*
|
|
61
|
+
* 启动全运行时 testid 注入。
|
|
62
|
+
*
|
|
63
|
+
* 覆盖范围:
|
|
64
|
+
* - 所有 ant-design-vue 组件根元素(input / select / button / picker / ...)
|
|
65
|
+
* - teleport 弹出层面板(日期选择器 / 级联选择器 / 下拉选择器)
|
|
66
|
+
* - Menu 子元素(inheritAttrs:false 兜底)
|
|
67
|
+
* - InputNumber 内部输入框及增减按钮
|
|
68
|
+
* - Slider handle、Rate 星星、Upload 内部 file input
|
|
69
|
+
*
|
|
70
|
+
* @returns 清理函数,调用后停止监听
|
|
51
71
|
*
|
|
52
72
|
* @example
|
|
53
73
|
* ```ts
|
|
54
74
|
* // main.ts
|
|
55
|
-
* import {
|
|
75
|
+
* import { setupAntTestIds } from 'vite-plugin-vue-testid/runtime'
|
|
56
76
|
*
|
|
57
77
|
* createApp(App).mount('#app')
|
|
58
|
-
*
|
|
59
|
-
* ```
|
|
78
|
+
* setupAntTestIds()
|
|
60
79
|
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
* // 日期面板(初始)
|
|
64
|
-
* a-date-picker-0-dropdown // 面板容器
|
|
65
|
-
* a-date-picker-0-dropdown-prev // 上月/上一级
|
|
66
|
-
* a-date-picker-0-dropdown-next // 下月/下一级
|
|
67
|
-
* a-date-picker-0-dropdown-header-date // header 容器
|
|
68
|
-
* a-date-picker-0-dropdown-header-month-btn // header 内的月份按钮("Jun"/"6月")
|
|
69
|
-
* a-date-picker-0-dropdown-header-year-btn // header 内的年份按钮("2026")
|
|
70
|
-
* a-date-picker-0-dropdown-date-2026-06-15 // 日期单元格
|
|
71
|
-
* a-date-picker-0-dropdown-ok // 确定按钮
|
|
72
|
-
*
|
|
73
|
-
* // 点击月份按钮 → 月面板(MutationObserver 自动重注入)
|
|
74
|
-
* a-date-picker-0-dropdown-header-month // header 容器
|
|
75
|
-
* a-date-picker-0-dropdown-header-year-btn // header 内的年份按钮
|
|
76
|
-
* a-date-picker-0-dropdown-month-06 // 6月按钮
|
|
77
|
-
*
|
|
78
|
-
* // 点击年份按钮 → 年面板
|
|
79
|
-
* a-date-picker-0-dropdown-header-year // header 容器
|
|
80
|
-
* a-date-picker-0-dropdown-year-2026 // 2026年按钮
|
|
80
|
+
* // 配合 ConfigProvider 自定义 prefixCls:
|
|
81
|
+
* setupAntTestIds({ prefixCls: 'custom' })
|
|
81
82
|
* ```
|
|
82
83
|
*/
|
|
83
|
-
export declare function
|
|
84
|
+
export declare function setupAntTestIds(opts?: RuntimeOptions): () => void;
|
|
85
|
+
/**
|
|
86
|
+
* @deprecated 使用 {@link setupAntTestIds} 替代。
|
|
87
|
+
*/
|
|
88
|
+
export declare const setupAntDropdownTestIds: typeof setupAntTestIds;
|
|
84
89
|
/**
|
|
85
90
|
* 手动为当前已存在或即将出现的弹出面板注入 testId(一次性补丁)。
|
|
86
91
|
* 适用于 SSR/hydration 或不想用 MutationObserver 的场景。
|
package/dist/runtime.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA
|
|
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;AAijB7D;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,eAAe,CAAC,IAAI,GAAE,cAAmB,GAAG,MAAM,IAAI,CA6ErE;AAED;;GAEG;AACH,eAAO,MAAM,uBAAuB,wBAAkB,CAAA;AAMtD;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,GAAE,cAAmB,GAAG,IAAI,CAgCtE"}
|
package/dist/runtime.js
CHANGED
|
@@ -1,41 +1,72 @@
|
|
|
1
1
|
// src/runtime.ts
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
var sel = (pc, name) => `.${pc}-${name}`;
|
|
3
|
+
var cls = (pc, name) => `${pc}-${name}`;
|
|
4
|
+
function buildComponentMatchers(pc) {
|
|
5
|
+
return [
|
|
6
|
+
// ── 输入框变体(按 specificity 降序)──
|
|
7
|
+
{ selector: sel(pc, "input-search"), prefix: "a-input-search" },
|
|
8
|
+
{ selector: sel(pc, "input-affix-wrapper"), prefix: "a-input" },
|
|
9
|
+
{ selector: sel(pc, "input-textarea"), prefix: "a-textarea" },
|
|
10
|
+
{ selector: sel(pc, "input-number"), prefix: "a-input-number" },
|
|
11
|
+
// ── 选择器 ──
|
|
12
|
+
{ selector: sel(pc, "select"), prefix: "a-select", withId: true },
|
|
13
|
+
{ selector: sel(pc, "cascader"), prefix: "a-cascader", withId: true },
|
|
14
|
+
{ selector: sel(pc, "tree-select"), prefix: "a-tree-select" },
|
|
15
|
+
// ── 日期/时间 ──
|
|
16
|
+
{ selector: sel(pc, "picker"), prefix: "a-date-picker", withId: true },
|
|
17
|
+
// ── 表单组件 ──
|
|
18
|
+
{ selector: sel(pc, "radio-group"), prefix: "a-radio-group" },
|
|
19
|
+
{ selector: sel(pc, "radio-wrapper"), prefix: "a-radio" },
|
|
20
|
+
{ selector: sel(pc, "checkbox-group"), prefix: "a-checkbox-group" },
|
|
21
|
+
{ selector: sel(pc, "checkbox-wrapper"), prefix: "a-checkbox" },
|
|
22
|
+
{ selector: sel(pc, "switch"), prefix: "a-switch" },
|
|
23
|
+
{ selector: sel(pc, "slider"), prefix: "a-slider" },
|
|
24
|
+
{ selector: sel(pc, "rate"), prefix: "a-rate" },
|
|
25
|
+
{ selector: sel(pc, "form-item"), prefix: "a-form-item" },
|
|
26
|
+
// ── 按钮 & 上传 ──
|
|
27
|
+
{ selector: sel(pc, "btn"), prefix: "a-button" },
|
|
28
|
+
{ selector: sel(pc, "upload"), prefix: "a-upload", withId: true },
|
|
29
|
+
// ── 弹窗 ──
|
|
30
|
+
{ selector: sel(pc, "modal-wrap"), prefix: "a-modal", withId: true },
|
|
31
|
+
// ── 菜单 ──
|
|
32
|
+
{ selector: sel(pc, "menu"), prefix: "a-menu" },
|
|
33
|
+
// ── 纯 input / textarea(必须排在最后)──
|
|
34
|
+
{ selector: `input.${cls(pc, "input")}`, prefix: "a-input" },
|
|
35
|
+
{ selector: `textarea.${cls(pc, "input")}`, prefix: "a-textarea" }
|
|
36
|
+
];
|
|
4
37
|
}
|
|
5
|
-
function
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
38
|
+
function buildSkipSelectors(pc) {
|
|
39
|
+
return [
|
|
40
|
+
sel(pc, "input-affix-wrapper"),
|
|
41
|
+
sel(pc, "input-textarea"),
|
|
42
|
+
sel(pc, "input-search"),
|
|
43
|
+
sel(pc, "input-number"),
|
|
44
|
+
sel(pc, "picker"),
|
|
45
|
+
sel(pc, "select"),
|
|
46
|
+
sel(pc, "cascader")
|
|
47
|
+
];
|
|
48
|
+
}
|
|
49
|
+
function isInnerInput(el, skipSelectors) {
|
|
50
|
+
if (el.tagName !== "INPUT" && el.tagName !== "TEXTAREA") return false;
|
|
51
|
+
for (const s of skipSelectors) {
|
|
52
|
+
if (el.closest(s)) return true;
|
|
53
|
+
}
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
function injectComponentRoots(root, matchers, attrName, counter, skipSelectors) {
|
|
57
|
+
for (const { selector, prefix, withId } of matchers) {
|
|
58
|
+
const candidates = root.matches(selector) ? [root] : Array.from(root.querySelectorAll(selector));
|
|
59
|
+
for (const el of candidates) {
|
|
60
|
+
if (!(el instanceof HTMLElement)) continue;
|
|
61
|
+
if (el.hasAttribute(attrName)) continue;
|
|
62
|
+
if (isInnerInput(el, skipSelectors)) continue;
|
|
63
|
+
const testId = `${prefix}-${counter.value++}`;
|
|
64
|
+
el.setAttribute(attrName, testId);
|
|
65
|
+
if (withId && !el.hasAttribute("id")) {
|
|
66
|
+
el.setAttribute("id", testId);
|
|
19
67
|
}
|
|
20
68
|
}
|
|
21
|
-
const antElements = document.querySelectorAll('[class*="ant-"]');
|
|
22
|
-
if (antElements.length > 0) return "ant";
|
|
23
|
-
} catch {
|
|
24
69
|
}
|
|
25
|
-
return "ant";
|
|
26
|
-
}
|
|
27
|
-
var _cachedPrefixCls = null;
|
|
28
|
-
function resolvePrefixCls(userPrefix) {
|
|
29
|
-
if (userPrefix) return userPrefix;
|
|
30
|
-
if (_cachedPrefixCls !== null) return _cachedPrefixCls;
|
|
31
|
-
_cachedPrefixCls = detectPrefixCls();
|
|
32
|
-
return _cachedPrefixCls;
|
|
33
|
-
}
|
|
34
|
-
function getPanelMode(panel, pfx) {
|
|
35
|
-
if (panel.classList.contains(`${pfx}-picker-month-panel`)) return "month";
|
|
36
|
-
if (panel.classList.contains(`${pfx}-picker-year-panel`)) return "year";
|
|
37
|
-
if (panel.classList.contains(`${pfx}-picker-decade-panel`)) return "decade";
|
|
38
|
-
return "date";
|
|
39
70
|
}
|
|
40
71
|
var MONTH_NAMES = [
|
|
41
72
|
"jan",
|
|
@@ -51,8 +82,14 @@ var MONTH_NAMES = [
|
|
|
51
82
|
"nov",
|
|
52
83
|
"dec"
|
|
53
84
|
];
|
|
54
|
-
function
|
|
55
|
-
|
|
85
|
+
function getPanelMode(panel, pc) {
|
|
86
|
+
if (panel.classList.contains(cls(pc, "picker-month-panel"))) return "month";
|
|
87
|
+
if (panel.classList.contains(cls(pc, "picker-year-panel"))) return "year";
|
|
88
|
+
if (panel.classList.contains(cls(pc, "picker-decade-panel"))) return "decade";
|
|
89
|
+
return "date";
|
|
90
|
+
}
|
|
91
|
+
function injectHeaderViewButtons(panel, prefix, attrName, suffix, pc) {
|
|
92
|
+
const headerView = panel.querySelector(sel(pc, "picker-header-view"));
|
|
56
93
|
if (!headerView) return;
|
|
57
94
|
const buttons = headerView.querySelectorAll("button");
|
|
58
95
|
buttons.forEach((btn) => {
|
|
@@ -74,178 +111,203 @@ function injectHeaderViewButtons(panel, prefix, attrName, suffix, pfx) {
|
|
|
74
111
|
el.setAttribute(attrName, `${prefix}-${name}${suffix}`);
|
|
75
112
|
});
|
|
76
113
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const
|
|
80
|
-
const setAttr = (selector, name) => {
|
|
81
|
-
const el = panel.querySelector(selector);
|
|
82
|
-
if (el && !el.hasAttribute(attrName)) {
|
|
83
|
-
el.setAttribute(attrName, `${prefix}-${name}${suffix}`);
|
|
84
|
-
}
|
|
85
|
-
};
|
|
86
|
-
setAttr(cls(pfx, "picker-header-super-prev-btn"), "super-prev");
|
|
87
|
-
setAttr(cls(pfx, "picker-header-prev-btn"), "prev");
|
|
88
|
-
setAttr(cls(pfx, "picker-header-next-btn"), "next");
|
|
89
|
-
setAttr(cls(pfx, "picker-header-super-next-btn"), "super-next");
|
|
90
|
-
setAttr(cls(pfx, "picker-header-view"), `header-${mode}`);
|
|
91
|
-
injectHeaderViewButtons(panel, prefix, attrName, suffix, pfx);
|
|
92
|
-
injectPanelCells(panel, prefix, attrName, suffix, pfx);
|
|
93
|
-
}
|
|
94
|
-
function injectPanelCells(panel, prefix, attrName, suffix, pfx) {
|
|
95
|
-
const mode = getPanelMode(panel, pfx);
|
|
96
|
-
const cells = panel.querySelectorAll(cls(pfx, "picker-cell-inner"));
|
|
114
|
+
function injectPanelCells(panel, prefix, attrName, suffix, pc) {
|
|
115
|
+
const mode = getPanelMode(panel, pc);
|
|
116
|
+
const cells = panel.querySelectorAll(sel(pc, "picker-cell-inner"));
|
|
97
117
|
cells.forEach((cell) => {
|
|
98
118
|
const el = cell;
|
|
99
119
|
if (el.hasAttribute(attrName)) return;
|
|
100
120
|
const raw = (el.getAttribute("title") || el.textContent?.trim() || "").replace(/\s+/g, "-");
|
|
101
|
-
let label;
|
|
102
121
|
switch (mode) {
|
|
103
122
|
case "month":
|
|
104
|
-
|
|
105
|
-
el.setAttribute(attrName, `${prefix}-month-${label}${suffix}`);
|
|
123
|
+
el.setAttribute(attrName, `${prefix}-month-${raw.length >= 2 ? raw.slice(-2) : raw}${suffix}`);
|
|
106
124
|
break;
|
|
107
125
|
case "year":
|
|
108
|
-
|
|
109
|
-
el.setAttribute(attrName, `${prefix}-year-${label}${suffix}`);
|
|
126
|
+
el.setAttribute(attrName, `${prefix}-year-${raw}${suffix}`);
|
|
110
127
|
break;
|
|
111
128
|
case "decade":
|
|
112
|
-
|
|
113
|
-
el.setAttribute(attrName, `${prefix}-decade-${label}${suffix}`);
|
|
129
|
+
el.setAttribute(attrName, `${prefix}-decade-${raw}${suffix}`);
|
|
114
130
|
break;
|
|
115
131
|
default:
|
|
116
|
-
|
|
117
|
-
el.setAttribute(attrName, `${prefix}-date-${label}${suffix}`);
|
|
132
|
+
el.setAttribute(attrName, `${prefix}-date-${raw}${suffix}`);
|
|
118
133
|
break;
|
|
119
134
|
}
|
|
120
135
|
});
|
|
121
136
|
}
|
|
122
|
-
function
|
|
123
|
-
const
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const panels = container.querySelectorAll(cls(pfx, "picker-panel"));
|
|
129
|
-
if (isRange && panels.length >= 2) {
|
|
130
|
-
injectSinglePanel(panels[0], prefix, attrName, "-left", pfx);
|
|
131
|
-
injectSinglePanel(panels[1], prefix, attrName, "-right", pfx);
|
|
132
|
-
} else {
|
|
133
|
-
const panel = panels[0] || container;
|
|
134
|
-
injectSinglePanel(panel, prefix, attrName, "", pfx);
|
|
137
|
+
function injectSinglePanel(panel, prefix, attrName, suffix, pc) {
|
|
138
|
+
const mode = getPanelMode(panel, pc);
|
|
139
|
+
const setAttr = (selector, name) => {
|
|
140
|
+
const el = panel.querySelector(selector);
|
|
141
|
+
if (el && !el.hasAttribute(attrName)) {
|
|
142
|
+
el.setAttribute(attrName, `${prefix}-${name}${suffix}`);
|
|
135
143
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
144
|
+
};
|
|
145
|
+
setAttr(sel(pc, "picker-header-super-prev-btn"), "super-prev");
|
|
146
|
+
setAttr(sel(pc, "picker-header-prev-btn"), "prev");
|
|
147
|
+
setAttr(sel(pc, "picker-header-next-btn"), "next");
|
|
148
|
+
setAttr(sel(pc, "picker-header-super-next-btn"), "super-next");
|
|
149
|
+
setAttr(sel(pc, "picker-header-view"), `header-${mode}`);
|
|
150
|
+
injectHeaderViewButtons(panel, prefix, attrName, suffix, pc);
|
|
151
|
+
injectPanelCells(panel, prefix, attrName, suffix, pc);
|
|
152
|
+
}
|
|
153
|
+
function makeInjectPickerPanel(pc) {
|
|
154
|
+
return (ctx) => {
|
|
155
|
+
const { container, triggerTestId, attrName } = ctx;
|
|
156
|
+
const prefix = `${triggerTestId}-dropdown`;
|
|
157
|
+
container.setAttribute(attrName, prefix);
|
|
158
|
+
const isRange = container.classList.contains(cls(pc, "picker-dropdown-range"));
|
|
159
|
+
const injectAllPanels = () => {
|
|
160
|
+
const panels = container.querySelectorAll(sel(pc, "picker-panel"));
|
|
161
|
+
if (isRange && panels.length >= 2) {
|
|
162
|
+
injectSinglePanel(panels[0], prefix, attrName, "-left", pc);
|
|
163
|
+
injectSinglePanel(panels[1], prefix, attrName, "-right", pc);
|
|
164
|
+
} else {
|
|
165
|
+
const panel = panels[0] || container;
|
|
166
|
+
injectSinglePanel(panel, prefix, attrName, "", pc);
|
|
167
|
+
}
|
|
168
|
+
const presets = container.querySelectorAll(`${sel(pc, "picker-presets")} .${cls(pc, "tag")}`);
|
|
169
|
+
presets.forEach((tag) => {
|
|
170
|
+
const el = tag;
|
|
171
|
+
if (el.hasAttribute(attrName)) return;
|
|
172
|
+
const text = el.textContent?.trim() || "";
|
|
173
|
+
el.setAttribute(attrName, `${prefix}-preset-${text}`);
|
|
174
|
+
});
|
|
175
|
+
const timeColumns = container.querySelectorAll(sel(pc, "picker-time-panel-column"));
|
|
176
|
+
timeColumns.forEach((col, i) => {
|
|
177
|
+
const el = col;
|
|
178
|
+
if (el.hasAttribute(attrName)) return;
|
|
179
|
+
el.setAttribute(attrName, `${prefix}-time-column-${i}`);
|
|
180
|
+
const items = col.querySelectorAll("li");
|
|
181
|
+
items.forEach((item) => {
|
|
182
|
+
const li = item;
|
|
183
|
+
if (li.hasAttribute(attrName)) return;
|
|
184
|
+
const text = li.textContent?.trim() || "";
|
|
185
|
+
li.setAttribute(attrName, `${prefix}-time-${i}-${text}`);
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
const setFooterBtn = (selector, name) => {
|
|
189
|
+
const btn = container.querySelector(selector);
|
|
190
|
+
if (btn && !btn.hasAttribute(attrName)) {
|
|
191
|
+
btn.setAttribute(attrName, `${prefix}-${name}`);
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
setFooterBtn(`${sel(pc, "picker-ok")} button`, "ok");
|
|
195
|
+
setFooterBtn(`${sel(pc, "picker-now")} button`, "now");
|
|
196
|
+
setFooterBtn(sel(pc, "picker-now-btn"), "now");
|
|
197
|
+
};
|
|
198
|
+
injectAllPanels();
|
|
199
|
+
if (!_observedContainers.has(container)) {
|
|
200
|
+
_observedContainers.add(container);
|
|
201
|
+
let timer = null;
|
|
202
|
+
const observer = new MutationObserver(() => {
|
|
203
|
+
if (timer !== null) clearTimeout(timer);
|
|
204
|
+
timer = setTimeout(() => {
|
|
205
|
+
injectAllPanels();
|
|
206
|
+
}, 50);
|
|
207
|
+
});
|
|
208
|
+
observer.observe(container, { childList: true, subtree: true });
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
function makeInjectCascaderPanel(pc) {
|
|
213
|
+
return (ctx) => {
|
|
214
|
+
const { container, triggerTestId, attrName } = ctx;
|
|
215
|
+
const prefix = `${triggerTestId}-dropdown`;
|
|
216
|
+
container.setAttribute(attrName, prefix);
|
|
217
|
+
const menus = container.querySelectorAll(sel(pc, "cascader-menu"));
|
|
218
|
+
menus.forEach((menu, i) => {
|
|
219
|
+
const el = menu;
|
|
220
|
+
el.setAttribute(attrName, `${prefix}-menu-${i}`);
|
|
221
|
+
const items = menu.querySelectorAll(sel(pc, "cascader-menu-item"));
|
|
149
222
|
items.forEach((item) => {
|
|
150
223
|
const li = item;
|
|
151
|
-
if (li.hasAttribute(attrName)) return;
|
|
152
224
|
const text = li.textContent?.trim() || "";
|
|
153
|
-
li.setAttribute(attrName, `${prefix}-
|
|
225
|
+
li.setAttribute(attrName, `${prefix}-item-${text}`);
|
|
154
226
|
});
|
|
155
227
|
});
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
function makeInjectSelectPanel(pc) {
|
|
231
|
+
return (ctx) => {
|
|
232
|
+
const { container, triggerTestId, attrName } = ctx;
|
|
233
|
+
const prefix = `${triggerTestId}-dropdown`;
|
|
234
|
+
container.setAttribute(attrName, prefix);
|
|
235
|
+
const injectAllItems = () => {
|
|
236
|
+
const options = container.querySelectorAll(sel(pc, "select-item-option"));
|
|
237
|
+
options.forEach((opt) => {
|
|
238
|
+
const el = opt;
|
|
239
|
+
if (el.hasAttribute(attrName)) return;
|
|
240
|
+
const title = el.getAttribute("title") || el.textContent?.trim() || "";
|
|
241
|
+
el.setAttribute(attrName, `${prefix}-option-${title}`);
|
|
242
|
+
});
|
|
243
|
+
const treeNodes = container.querySelectorAll(sel(pc, "select-tree-treenode"));
|
|
244
|
+
treeNodes.forEach((node) => {
|
|
245
|
+
const el = node;
|
|
246
|
+
const content = el.querySelector(sel(pc, "select-tree-node-content-wrapper"));
|
|
247
|
+
const title = content?.getAttribute("title") || content?.textContent?.trim() || "";
|
|
248
|
+
if (content && !content.hasAttribute(attrName)) {
|
|
249
|
+
content.setAttribute(attrName, `${prefix}-tree-${title}`);
|
|
250
|
+
}
|
|
251
|
+
const switcher = el.querySelector(sel(pc, "select-tree-switcher"));
|
|
252
|
+
if (switcher && !switcher.hasAttribute(attrName)) {
|
|
253
|
+
const isOpen = switcher.classList.contains(cls(pc, "select-tree-switcher_open"));
|
|
254
|
+
const state = isOpen ? "collapse" : "expand";
|
|
255
|
+
switcher.setAttribute(attrName, `${prefix}-tree-switcher-${state}-${title}`);
|
|
256
|
+
}
|
|
257
|
+
});
|
|
161
258
|
};
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
259
|
+
injectAllItems();
|
|
260
|
+
if (!_observedContainers.has(container)) {
|
|
261
|
+
_observedContainers.add(container);
|
|
262
|
+
let timer = null;
|
|
263
|
+
const observer = new MutationObserver(() => {
|
|
264
|
+
if (timer !== null) clearTimeout(timer);
|
|
265
|
+
timer = setTimeout(() => {
|
|
266
|
+
injectAllItems();
|
|
267
|
+
}, 50);
|
|
268
|
+
});
|
|
269
|
+
observer.observe(container, { childList: true, subtree: true });
|
|
270
|
+
}
|
|
165
271
|
};
|
|
166
|
-
injectAllPanels();
|
|
167
|
-
if (!_observedContainers.has(container)) {
|
|
168
|
-
_observedContainers.add(container);
|
|
169
|
-
let timer = null;
|
|
170
|
-
const observer = new MutationObserver(() => {
|
|
171
|
-
if (timer !== null) clearTimeout(timer);
|
|
172
|
-
timer = setTimeout(() => {
|
|
173
|
-
injectAllPanels();
|
|
174
|
-
}, 50);
|
|
175
|
-
});
|
|
176
|
-
observer.observe(container, { childList: true, subtree: true });
|
|
177
|
-
}
|
|
178
272
|
}
|
|
179
|
-
function
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
const el = menu;
|
|
186
|
-
el.setAttribute(attrName, `${prefix}-menu-${i}`);
|
|
187
|
-
const items = menu.querySelectorAll(cls(pfx, "cascader-menu-item"));
|
|
188
|
-
items.forEach((item) => {
|
|
189
|
-
const li = item;
|
|
190
|
-
const text = li.textContent?.trim() || "";
|
|
191
|
-
li.setAttribute(attrName, `${prefix}-item-${text}`);
|
|
192
|
-
});
|
|
193
|
-
});
|
|
273
|
+
function buildPanels(pc) {
|
|
274
|
+
return {
|
|
275
|
+
[sel(pc, "picker-dropdown")]: makeInjectPickerPanel(pc),
|
|
276
|
+
[sel(pc, "cascader-dropdown")]: makeInjectCascaderPanel(pc),
|
|
277
|
+
[sel(pc, "select-dropdown")]: makeInjectSelectPanel(pc)
|
|
278
|
+
};
|
|
194
279
|
}
|
|
195
|
-
function
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
280
|
+
function injectMenuItems(attrName, root = document, pc) {
|
|
281
|
+
const itemRules = [
|
|
282
|
+
{ selector: sel(pc, "menu-item"), prefix: "a-menu-item-" },
|
|
283
|
+
{ selector: sel(pc, "menu-submenu"), textSelector: sel(pc, "menu-submenu-title"), prefix: "a-sub-menu-" },
|
|
284
|
+
{ selector: sel(pc, "menu-item-group"), textSelector: sel(pc, "menu-item-group-title"), prefix: "a-menu-item-group-" }
|
|
285
|
+
];
|
|
286
|
+
for (const rule of itemRules) {
|
|
287
|
+
const items = root.querySelectorAll(rule.selector);
|
|
288
|
+
items.forEach((item) => {
|
|
289
|
+
const el = item;
|
|
203
290
|
if (el.hasAttribute(attrName)) return;
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
treeNodes.forEach((node) => {
|
|
209
|
-
const el = node;
|
|
210
|
-
const content = el.querySelector(cls(pfx, "select-tree-node-content-wrapper"));
|
|
211
|
-
const title = content?.getAttribute("title") || content?.textContent?.trim() || "";
|
|
212
|
-
if (content && !content.hasAttribute(attrName)) {
|
|
213
|
-
content.setAttribute(attrName, `${prefix}-tree-${title}`);
|
|
291
|
+
const id = el.getAttribute("id");
|
|
292
|
+
if (id) {
|
|
293
|
+
el.setAttribute(attrName, id);
|
|
294
|
+
return;
|
|
214
295
|
}
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
switcher.setAttribute(attrName, `${prefix}-tree-switcher-${state}-${title}`);
|
|
296
|
+
const textTarget = rule.textSelector ? el.querySelector(rule.textSelector) : el;
|
|
297
|
+
const text = (textTarget?.textContent || "").trim().replace(/\s+/g, "-");
|
|
298
|
+
if (text) {
|
|
299
|
+
el.setAttribute(attrName, rule.prefix + text);
|
|
220
300
|
}
|
|
221
301
|
});
|
|
222
|
-
};
|
|
223
|
-
injectAllItems();
|
|
224
|
-
if (!_observedContainers.has(container)) {
|
|
225
|
-
_observedContainers.add(container);
|
|
226
|
-
let timer = null;
|
|
227
|
-
const observer = new MutationObserver(() => {
|
|
228
|
-
if (timer !== null) clearTimeout(timer);
|
|
229
|
-
timer = setTimeout(() => {
|
|
230
|
-
injectAllItems();
|
|
231
|
-
}, 50);
|
|
232
|
-
});
|
|
233
|
-
observer.observe(container, { childList: true, subtree: true });
|
|
234
302
|
}
|
|
235
303
|
}
|
|
236
|
-
|
|
237
|
-
return {
|
|
238
|
-
[cls(pfx, "picker-dropdown")]: injectPickerPanel,
|
|
239
|
-
[cls(pfx, "cascader-dropdown")]: injectCascaderPanel,
|
|
240
|
-
[cls(pfx, "select-dropdown")]: injectSelectPanel
|
|
241
|
-
};
|
|
242
|
-
}
|
|
304
|
+
var _observedContainers = /* @__PURE__ */ new WeakSet();
|
|
243
305
|
function getTriggerTestId(trigger, attrName) {
|
|
244
|
-
if (!trigger) return "unknown
|
|
245
|
-
return trigger.getAttribute(attrName) || trigger.getAttribute("id") || "unknown
|
|
306
|
+
if (!trigger) return "unknown";
|
|
307
|
+
return trigger.getAttribute(attrName) || trigger.getAttribute("id") || "unknown";
|
|
246
308
|
}
|
|
247
|
-
function findActiveTrigger(attrName,
|
|
248
|
-
const focused = document.querySelector(
|
|
309
|
+
function findActiveTrigger(attrName, pc) {
|
|
310
|
+
const focused = document.querySelector(sel(pc, "picker-focused"));
|
|
249
311
|
if (focused) {
|
|
250
312
|
if (focused.hasAttribute(attrName)) return focused;
|
|
251
313
|
const inner = focused.querySelector(`[${attrName}]`);
|
|
@@ -253,9 +315,9 @@ function findActiveTrigger(attrName, pfx) {
|
|
|
253
315
|
const input = focused.querySelector("input[id]");
|
|
254
316
|
if (input) return input;
|
|
255
317
|
}
|
|
256
|
-
const selectOpen = document.querySelector(
|
|
318
|
+
const selectOpen = document.querySelector(sel(pc, "select-open"));
|
|
257
319
|
if (selectOpen?.hasAttribute(attrName)) return selectOpen;
|
|
258
|
-
const cascader = document.querySelector(
|
|
320
|
+
const cascader = document.querySelector(sel(pc, "cascader-picker-focused"));
|
|
259
321
|
if (cascader?.hasAttribute(attrName)) return cascader;
|
|
260
322
|
const expanded = document.querySelector(
|
|
261
323
|
`[${attrName}][aria-expanded="true"]`
|
|
@@ -263,128 +325,153 @@ function findActiveTrigger(attrName, pfx) {
|
|
|
263
325
|
if (expanded) return expanded;
|
|
264
326
|
return null;
|
|
265
327
|
}
|
|
266
|
-
function
|
|
267
|
-
const
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
if (
|
|
274
|
-
|
|
275
|
-
if (id) {
|
|
276
|
-
el.setAttribute(attrName, id);
|
|
277
|
-
return;
|
|
328
|
+
function injectSubElements(root, attrName, pc) {
|
|
329
|
+
const inputNumbers = root.matches(`${sel(pc, "input-number")}[${attrName}]`) ? [root] : Array.from(root.querySelectorAll(`${sel(pc, "input-number")}[${attrName}]`));
|
|
330
|
+
for (const wrapper of inputNumbers) {
|
|
331
|
+
if (!(wrapper instanceof HTMLElement)) continue;
|
|
332
|
+
const testId = wrapper.getAttribute(attrName) || "";
|
|
333
|
+
if (!testId) continue;
|
|
334
|
+
const input = wrapper.querySelector(sel(pc, "input-number-input"));
|
|
335
|
+
if (input && !input.hasAttribute(attrName)) {
|
|
336
|
+
input.setAttribute(attrName, testId);
|
|
278
337
|
}
|
|
279
|
-
const
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
el.setAttribute(attrName, rule.prefix + text);
|
|
338
|
+
const upHandler = wrapper.querySelector(sel(pc, "input-number-handler-up"));
|
|
339
|
+
if (upHandler && !upHandler.hasAttribute(attrName)) {
|
|
340
|
+
upHandler.setAttribute(attrName, `${testId}-up`);
|
|
283
341
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
injectSingle(root, rule);
|
|
342
|
+
const downHandler = wrapper.querySelector(sel(pc, "input-number-handler-down"));
|
|
343
|
+
if (downHandler && !downHandler.hasAttribute(attrName)) {
|
|
344
|
+
downHandler.setAttribute(attrName, `${testId}-down`);
|
|
288
345
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
346
|
+
}
|
|
347
|
+
const sliders = root.matches(`${sel(pc, "slider")}[${attrName}]`) ? [root] : Array.from(root.querySelectorAll(`${sel(pc, "slider")}[${attrName}]`));
|
|
348
|
+
for (const slider of sliders) {
|
|
349
|
+
if (!(slider instanceof HTMLElement)) continue;
|
|
350
|
+
const testId = slider.getAttribute(attrName) || "";
|
|
351
|
+
if (!testId) continue;
|
|
352
|
+
slider.querySelectorAll(sel(pc, "slider-handle")).forEach((h, i) => {
|
|
353
|
+
const el = h;
|
|
354
|
+
if (!el.hasAttribute(attrName)) {
|
|
355
|
+
el.setAttribute(attrName, `${testId}-handle-${i}`);
|
|
356
|
+
}
|
|
292
357
|
});
|
|
293
358
|
}
|
|
359
|
+
const rates = root.matches(`${sel(pc, "rate")}[${attrName}]`) ? [root] : Array.from(root.querySelectorAll(`${sel(pc, "rate")}[${attrName}]`));
|
|
360
|
+
for (const rate of rates) {
|
|
361
|
+
if (!(rate instanceof HTMLElement)) continue;
|
|
362
|
+
const testId = rate.getAttribute(attrName) || "";
|
|
363
|
+
if (!testId) continue;
|
|
364
|
+
rate.querySelectorAll(sel(pc, "rate-star")).forEach((s, i) => {
|
|
365
|
+
const el = s;
|
|
366
|
+
if (!el.hasAttribute(attrName)) {
|
|
367
|
+
el.setAttribute(attrName, `${testId}-star-${i}`);
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
const uploadByTestId = root.matches(`${sel(pc, "upload")}[${attrName}]`) ? [root] : Array.from(root.querySelectorAll(`${sel(pc, "upload")}[${attrName}]`));
|
|
372
|
+
const uploadById = uploadByTestId.length === 0 ? root.matches(`${sel(pc, "upload")}[id]`) ? [root] : Array.from(root.querySelectorAll(`${sel(pc, "upload")}[id]`)) : [];
|
|
373
|
+
for (const upload of [...uploadByTestId, ...uploadById]) {
|
|
374
|
+
if (!(upload instanceof HTMLElement)) continue;
|
|
375
|
+
const testId = upload.getAttribute(attrName) || upload.getAttribute("id") || "";
|
|
376
|
+
if (!testId) continue;
|
|
377
|
+
const input = upload.querySelector('input[type="file"]');
|
|
378
|
+
if (input && !input.hasAttribute(attrName)) {
|
|
379
|
+
input.setAttribute(attrName, `${testId}-input`);
|
|
380
|
+
if (!upload.hasAttribute(attrName)) {
|
|
381
|
+
upload.setAttribute(attrName, testId);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
294
385
|
}
|
|
295
|
-
function
|
|
386
|
+
function setupAntTestIds(opts = {}) {
|
|
296
387
|
const attrName = opts.attributeName ?? "data-testid";
|
|
297
|
-
const
|
|
298
|
-
const
|
|
299
|
-
const
|
|
388
|
+
const pc = opts.prefixCls ?? "ant";
|
|
389
|
+
const matchers = mergeMatchers(buildComponentMatchers(pc), opts.components);
|
|
390
|
+
const skipSelectors = buildSkipSelectors(pc);
|
|
391
|
+
const panels = { ...buildPanels(pc), ...opts.panels };
|
|
392
|
+
const counter = { value: 0 };
|
|
393
|
+
function processNode(node) {
|
|
394
|
+
if (!(node instanceof HTMLElement)) return;
|
|
395
|
+
injectComponentRoots(node, matchers, attrName, counter, skipSelectors);
|
|
396
|
+
injectMenuItems(attrName, node, pc);
|
|
397
|
+
for (const [selector, strategy] of Object.entries(panels)) {
|
|
398
|
+
const containers = node.matches(selector) ? [node] : Array.from(node.querySelectorAll(selector));
|
|
399
|
+
for (const container of containers) {
|
|
400
|
+
if (!(container instanceof HTMLElement)) continue;
|
|
401
|
+
if (container.hasAttribute(attrName)) continue;
|
|
402
|
+
if (strategy) {
|
|
403
|
+
queueMicrotask(() => {
|
|
404
|
+
const trigger = findActiveTrigger(attrName, pc);
|
|
405
|
+
const triggerTestId = getTriggerTestId(trigger, attrName);
|
|
406
|
+
strategy({ container, trigger, triggerTestId, attrName });
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
injectSubElements(node, attrName, pc);
|
|
412
|
+
}
|
|
413
|
+
let pending = false;
|
|
414
|
+
let pendingNodes = [];
|
|
415
|
+
const flushPending = () => {
|
|
416
|
+
pending = false;
|
|
417
|
+
const nodes = pendingNodes;
|
|
418
|
+
pendingNodes = [];
|
|
419
|
+
for (const node of nodes) {
|
|
420
|
+
processNode(node);
|
|
421
|
+
}
|
|
422
|
+
};
|
|
300
423
|
const observer = new MutationObserver((mutations) => {
|
|
301
424
|
for (const mutation of mutations) {
|
|
302
425
|
for (const node of mutation.addedNodes) {
|
|
303
|
-
|
|
304
|
-
for (const [selector, strategy] of Object.entries(panels)) {
|
|
305
|
-
const containers = node.matches(selector) ? [node] : Array.from(node.querySelectorAll(selector));
|
|
306
|
-
for (const container of containers) {
|
|
307
|
-
if (!(container instanceof HTMLElement)) continue;
|
|
308
|
-
if (container.hasAttribute(attrName)) continue;
|
|
309
|
-
if (strategy) {
|
|
310
|
-
queueMicrotask(() => {
|
|
311
|
-
const trigger = findActiveTrigger(attrName, pfx);
|
|
312
|
-
const triggerTestId = getTriggerTestId(trigger, attrName);
|
|
313
|
-
const ctx = {
|
|
314
|
-
container,
|
|
315
|
-
trigger,
|
|
316
|
-
triggerTestId,
|
|
317
|
-
attrName,
|
|
318
|
-
prefixCls: pfx
|
|
319
|
-
};
|
|
320
|
-
strategy(ctx);
|
|
321
|
-
});
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
const uploadByTestId = node.matches(`${cls(pfx, "upload")}[${attrName}]`) ? [node] : Array.from(node.querySelectorAll(`${cls(pfx, "upload")}[${attrName}]`));
|
|
326
|
-
const uploadById = uploadByTestId.length === 0 ? node.matches(`${cls(pfx, "upload")}[id]`) ? [node] : Array.from(node.querySelectorAll(`${cls(pfx, "upload")}[id]`)) : [];
|
|
327
|
-
const uploads = [...uploadByTestId, ...uploadById];
|
|
328
|
-
for (const upload of uploads) {
|
|
329
|
-
if (!(upload instanceof HTMLElement)) continue;
|
|
330
|
-
const testId = upload.getAttribute(attrName) || upload.getAttribute("id") || "";
|
|
331
|
-
if (!testId) continue;
|
|
332
|
-
const input = upload.querySelector('input[type="file"]');
|
|
333
|
-
if (input && !input.hasAttribute(attrName)) {
|
|
334
|
-
input.setAttribute(attrName, `${testId}-input`);
|
|
335
|
-
if (!upload.hasAttribute(attrName)) {
|
|
336
|
-
upload.setAttribute(attrName, testId);
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
injectMenuItems(attrName, pfx, node instanceof HTMLElement ? node : document);
|
|
426
|
+
pendingNodes.push(node);
|
|
341
427
|
}
|
|
342
428
|
}
|
|
429
|
+
if (!pending) {
|
|
430
|
+
pending = true;
|
|
431
|
+
requestAnimationFrame(flushPending);
|
|
432
|
+
}
|
|
343
433
|
});
|
|
344
434
|
observer.observe(document.body, { childList: true, subtree: true });
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
435
|
+
processNode(document.body);
|
|
436
|
+
return () => {
|
|
437
|
+
observer.disconnect();
|
|
438
|
+
pendingNodes = [];
|
|
439
|
+
};
|
|
349
440
|
}
|
|
441
|
+
var setupAntDropdownTestIds = setupAntTestIds;
|
|
350
442
|
function injectCurrentDropdowns(opts = {}) {
|
|
351
443
|
const attrName = opts.attributeName ?? "data-testid";
|
|
352
|
-
const
|
|
353
|
-
const
|
|
354
|
-
const
|
|
444
|
+
const pc = opts.prefixCls ?? "ant";
|
|
445
|
+
const matchers = mergeMatchers(buildComponentMatchers(pc), opts.components);
|
|
446
|
+
const skipSelectors = buildSkipSelectors(pc);
|
|
447
|
+
const panels = { ...buildPanels(pc), ...opts.panels };
|
|
448
|
+
const counter = { value: 0 };
|
|
449
|
+
injectComponentRoots(document.body, matchers, attrName, counter, skipSelectors);
|
|
355
450
|
for (const [selector, strategy] of Object.entries(panels)) {
|
|
356
451
|
const containers = document.querySelectorAll(selector);
|
|
357
452
|
containers.forEach((container) => {
|
|
358
453
|
if (!(container instanceof HTMLElement)) return;
|
|
359
454
|
if (container.hasAttribute(attrName)) return;
|
|
360
455
|
if (!strategy) return;
|
|
361
|
-
const trigger = findActiveTrigger(attrName,
|
|
456
|
+
const trigger = findActiveTrigger(attrName, pc);
|
|
362
457
|
const triggerTestId = getTriggerTestId(trigger, attrName);
|
|
363
|
-
strategy({ container, trigger, triggerTestId, attrName
|
|
458
|
+
strategy({ container, trigger, triggerTestId, attrName });
|
|
364
459
|
});
|
|
365
460
|
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
const input = upload.querySelector('input[type="file"]');
|
|
378
|
-
if (input && !input.hasAttribute(attrName)) {
|
|
379
|
-
input.setAttribute(attrName, `${testId}-input`);
|
|
380
|
-
if (!upload.hasAttribute(attrName)) {
|
|
381
|
-
upload.setAttribute(attrName, testId);
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
});
|
|
385
|
-
injectMenuItems(attrName, pfx);
|
|
461
|
+
injectSubElements(document.body, attrName, pc);
|
|
462
|
+
injectMenuItems(attrName, document, pc);
|
|
463
|
+
}
|
|
464
|
+
function mergeMatchers(defaults, customs) {
|
|
465
|
+
if (!customs) return defaults;
|
|
466
|
+
const customKeys = new Set(Object.keys(customs));
|
|
467
|
+
const filtered = defaults.filter((m) => !customKeys.has(m.selector));
|
|
468
|
+
for (const [selector, prefix] of Object.entries(customs)) {
|
|
469
|
+
filtered.push({ selector, prefix });
|
|
470
|
+
}
|
|
471
|
+
return filtered;
|
|
386
472
|
}
|
|
387
473
|
export {
|
|
388
474
|
injectCurrentDropdowns,
|
|
389
|
-
setupAntDropdownTestIds
|
|
475
|
+
setupAntDropdownTestIds,
|
|
476
|
+
setupAntTestIds
|
|
390
477
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vite-plugin-vue-testid",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.10",
|
|
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",
|
|
@@ -42,9 +42,6 @@
|
|
|
42
42
|
"vite": ">=5.0.0",
|
|
43
43
|
"vue": ">=3.3.0"
|
|
44
44
|
},
|
|
45
|
-
"dependencies": {
|
|
46
|
-
"@vue/compiler-core": "^3.5.0"
|
|
47
|
-
},
|
|
48
45
|
"devDependencies": {
|
|
49
46
|
"tsup": "^8.5.1",
|
|
50
47
|
"typescript": "~6.0.2",
|