vite-plugin-vue-testid 1.0.8 → 1.0.9

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 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 — 面板容器 HTMLElement
264
- // ctx.trigger — 触发器 HTMLElement | null
254
+ // ctx.container — 面板容器 HTMLElement
255
+ // ctx.trigger — 触发器 HTMLElement | null
265
256
  // ctx.triggerTestId — 触发器的 testId 值
266
- // ctx.attrName — testId 属性名
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
- import type { RootNode, TemplateChildNode } from '@vue/compiler-core';
2
- export interface VueTestIdTransformOptions {
3
- /**
4
- * 需要注入 testId 的组件标签列表(kebab-case)
5
- * @default ant-design-vue 常用表单组件
6
- */
7
- components?: string[];
8
- /**
9
- * 自定义 testId 生成函数
10
- * @param tagName 组件标签名
11
- * @param count 全局递增序号
12
- * @returns testId 字符串
13
- */
14
- generateId?: (tagName: string, count: number) => string;
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
- * 创建 Vue 编译器 nodeTransform 函数。
33
- * 直接传给 `@vitejs/plugin-vue` `template.compilerOptions.nodeTransforms`。
29
+ * Vite 插件(可选)。
30
+ * 当前仅作为占位,实际 testid 注入完全由运行时 `setupAntTestIds()` 完成。
34
31
  *
35
- * @example
36
- * ```ts
37
- * // vite.config.ts
38
- * import vue from '@vitejs/plugin-vue'
39
- * import { createVueTestIdTransform } from 'vite-plugin-vue-testid'
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
- * export default defineConfig({
42
- * plugins: [
43
- * vue({
44
- * template: {
45
- * compilerOptions: {
46
- * nodeTransforms: [createVueTestIdTransform()]
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
- * @example 自定义配置
55
- * ```ts
56
- * createVueTestIdTransform({
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(rawOpts?: VueTestIdTransformOptions): (node: RootNode | TemplateChildNode) => void;
56
+ export declare function createVueTestIdTransform(): () => void;
64
57
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAIV,QAAQ,EAER,iBAAiB,EAClB,MAAM,oBAAoB,CAAA;AAI3B,MAAM,WAAW,yBAAyB;IACxC;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;IAErB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;IAEvD;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IAEtB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;IAEvB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAA;CACzB;AAyMD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,CAAC,EAAE,yBAAyB,GAClC,CAAC,IAAI,EAAE,QAAQ,GAAG,iBAAiB,KAAK,IAAI,CAG9C"}
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
- import { ConstantTypes, NodeTypes } from "@vue/compiler-core";
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
- components: raw?.components ?? DEFAULT_COMPONENTS,
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 ensureSlotIndex(exp) {
47
- if (!exp) return;
48
- if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {
49
- if (!/\bindex\b/.test(exp.content)) {
50
- exp.content = exp.content.replace(/\s*\}$/, ", index}");
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,36 @@
1
1
  /**
2
- * 运行时辅助模块:为 ant-design-vue teleport 弹出层(日期选择器、级联选择器等)
3
- * 中的 DOM 元素注入 data-testid,解决编译期无法处理的定位问题。
2
+ * 运行时 testid 注入:通过 MutationObserver 为 ant-design-vue 组件的 DOM 元素
3
+ * 自动注入 data-testid,解决编译期注入的诸多限制。
4
4
  *
5
- * 使用方式(在 main.ts 或入口文件中调用一次即可):
5
+ * 使用方式(在 main.ts 中调用一次即可):
6
6
  * ```ts
7
- * import { setupAntDropdownTestIds } from 'vite-plugin-vue-testid/runtime'
7
+ * import { setupAntTestIds } from 'vite-plugin-vue-testid/runtime'
8
8
  *
9
- * setupAntDropdownTestIds()
10
- *
11
- * // 如果全局配置了 prefixCls(如 ConfigProvider prefixCls="myapp"):
12
- * setupAntDropdownTestIds({ prefixCls: 'myapp' })
9
+ * createApp(App).mount('#app')
10
+ * setupAntTestIds()
13
11
  * ```
14
12
  */
15
13
  export interface RuntimeOptions {
16
14
  /**
17
- * testId 属性名,需与编译期插件保持一致
15
+ * testId 属性名
18
16
  * @default 'data-testid'
19
17
  */
20
18
  attributeName?: string;
21
19
  /**
22
- * ant-design-vue 全局 CSS 类名前缀。
23
- * 如果通过 ConfigProvider 配置了 prefixCls,需要在此传入相同值。
24
- * 未配置时自动从 DOM 检测,检测失败回退到 'ant'。
25
- * @default auto-detect || 'ant'
20
+ * 自定义组件 CSS 选择器 → testid 前缀映射。
21
+ * 会与默认映射合并(自定义覆盖默认)。
22
+ *
23
+ * @example
24
+ * ```ts
25
+ * setupAntTestIds({
26
+ * components: {
27
+ * '.el-input': 'el-input',
28
+ * '.el-button': 'el-button',
29
+ * }
30
+ * })
31
+ * ```
26
32
  */
27
- prefixCls?: string;
33
+ components?: Record<string, string>;
28
34
  /**
29
35
  * 需要监听的 teleport 面板 CSS 选择器映射
30
36
  * key: 面板容器选择器
@@ -41,46 +47,34 @@ export interface PanelContext {
41
47
  triggerTestId: string;
42
48
  /** testId 属性名 */
43
49
  attrName: string;
44
- /** ant-design-vue 当前生效的 CSS 类名前缀 */
45
- prefixCls: string;
46
50
  }
47
51
  export type PanelInjectStrategy = (ctx: PanelContext) => void;
48
52
  /**
49
- * teleport 弹出层面板注入 testId。
50
- * 使用 MutationObserver 监听 DOM 新增节点,自动识别 ant-design 弹出面板。
53
+ * 启动全运行时 testid 注入。
54
+ *
55
+ * 覆盖范围:
56
+ * - 所有 ant-design-vue 组件根元素(input / select / button / picker / ...)
57
+ * - teleport 弹出层面板(日期选择器 / 级联选择器 / 下拉选择器)
58
+ * - Menu 子元素(inheritAttrs:false 兜底)
59
+ * - Upload 内部 &lt;input type="file"&gt;
60
+ *
61
+ * @returns 清理函数,调用后停止监听
51
62
  *
52
63
  * @example
53
64
  * ```ts
54
65
  * // main.ts
55
- * import { setupAntDropdownTestIds } from 'vite-plugin-vue-testid/runtime'
66
+ * import { setupAntTestIds } from 'vite-plugin-vue-testid/runtime'
56
67
  *
57
68
  * createApp(App).mount('#app')
58
- * setupAntDropdownTestIds()
59
- * ```
60
- *
61
- * testId 注入结果示例:
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年按钮
69
+ * setupAntTestIds()
81
70
  * ```
82
71
  */
83
- export declare function setupAntDropdownTestIds(opts?: RuntimeOptions): () => void;
72
+ export declare function setupAntTestIds(opts?: RuntimeOptions): () => void;
73
+ /**
74
+ * @deprecated 使用 {@link setupAntTestIds} 替代。
75
+ * 保留此别名以兼容现有代码。
76
+ */
77
+ export declare const setupAntDropdownTestIds: typeof setupAntTestIds;
84
78
  /**
85
79
  * 手动为当前已存在或即将出现的弹出面板注入 testId(一次性补丁)。
86
80
  * 适用于 SSR/hydration 或不想用 MutationObserver 的场景。
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IAEtB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAElB;;;;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;IAChB,oCAAoC;IACpC,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,MAAM,mBAAmB,GAAG,CAAC,GAAG,EAAE,YAAY,KAAK,IAAI,CAAA;AA4e7D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,GAAE,cAAmB,GAAG,MAAM,IAAI,CAqF7E;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,GAAE,cAAmB,GAAG,IAAI,CAiDtE"}
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IAEtB;;;;;;;;;;;;;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;AA6c7D;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,eAAe,CAAC,IAAI,GAAE,cAAmB,GAAG,MAAM,IAAI,CA4KrE;AAED;;;GAGG;AACH,eAAO,MAAM,uBAAuB,wBAAkB,CAAA;AAMtD;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,GAAE,cAAmB,GAAG,IAAI,CA4GtE"}
package/dist/runtime.js CHANGED
@@ -1,40 +1,72 @@
1
1
  // src/runtime.ts
2
- function cls(prefix, name) {
3
- return `.${prefix}-${name}`;
2
+ var DEFAULT_COMPONENT_MATCHERS = [
3
+ // ── 输入框变体(按 specificity 降序)──
4
+ { selector: ".ant-input-search", prefix: "a-input-search" },
5
+ { selector: ".ant-input-affix-wrapper", prefix: "a-input" },
6
+ { selector: ".ant-input-textarea", prefix: "a-textarea" },
7
+ { selector: ".ant-input-number", prefix: "a-input-number" },
8
+ // ── 选择器 ──
9
+ { selector: ".ant-select", prefix: "a-select", withId: true },
10
+ { selector: ".ant-cascader", prefix: "a-cascader", withId: true },
11
+ { selector: ".ant-tree-select", prefix: "a-tree-select" },
12
+ // ── 日期/时间 ──
13
+ { selector: ".ant-picker", prefix: "a-date-picker", withId: true },
14
+ // ── 表单组件 ──
15
+ { selector: ".ant-radio-group", prefix: "a-radio-group" },
16
+ { selector: ".ant-radio-wrapper", prefix: "a-radio" },
17
+ { selector: ".ant-checkbox-group", prefix: "a-checkbox-group" },
18
+ { selector: ".ant-checkbox-wrapper", prefix: "a-checkbox" },
19
+ { selector: ".ant-switch", prefix: "a-switch" },
20
+ { selector: ".ant-slider", prefix: "a-slider" },
21
+ { selector: ".ant-rate", prefix: "a-rate" },
22
+ { selector: ".ant-form-item", prefix: "a-form-item" },
23
+ // ── 按钮 & 上传 ──
24
+ { selector: ".ant-btn", prefix: "a-button" },
25
+ { selector: ".ant-upload", prefix: "a-upload", withId: true },
26
+ // ── 弹窗 ──
27
+ { selector: ".ant-modal-wrap", prefix: "a-modal", withId: true },
28
+ // ── 菜单 ──
29
+ { selector: ".ant-menu", prefix: "a-menu" },
30
+ // ── 纯 input / textarea(必须排在最后)──
31
+ // 只有不在其他组件包裹内时才注入(通过 closest 排除)
32
+ { selector: "input.ant-input", prefix: "a-input" },
33
+ { selector: "textarea.ant-input", prefix: "a-textarea" }
34
+ ];
35
+ var SKIP_PARENT_SELECTORS = [
36
+ ".ant-input-affix-wrapper",
37
+ ".ant-input-textarea",
38
+ ".ant-input-search",
39
+ ".ant-input-number",
40
+ ".ant-picker",
41
+ ".ant-select",
42
+ ".ant-cascader"
43
+ ];
44
+ function isInnerInput(el) {
45
+ if (el.tagName !== "INPUT" && el.tagName !== "TEXTAREA") return false;
46
+ for (const sel of SKIP_PARENT_SELECTORS) {
47
+ if (el.closest(sel)) return true;
48
+ }
49
+ return false;
4
50
  }
5
- function detectPrefixCls() {
6
- try {
7
- const styles = getComputedStyle(document.documentElement);
8
- for (let i = 0; i < styles.length; i++) {
9
- const prop = styles[i];
10
- const m = prop.match(/^--(ant|[a-z]+)-/);
11
- if (m && m[1]) return m[1];
12
- }
13
- const buttons = document.querySelectorAll('[class*="-btn"]');
14
- for (const btn of buttons) {
15
- for (const clsName of btn.classList) {
16
- if (clsName.endsWith("-btn") && clsName.length > 4) {
17
- return clsName.slice(0, -4);
18
- }
51
+ function injectComponentRoots(root, matchers, attrName, counter) {
52
+ for (const { selector, prefix, withId } of matchers) {
53
+ const candidates = root.matches(selector) ? [root] : Array.from(root.querySelectorAll(selector));
54
+ for (const el of candidates) {
55
+ if (!(el instanceof HTMLElement)) continue;
56
+ if (el.hasAttribute(attrName)) continue;
57
+ if (isInnerInput(el)) continue;
58
+ const testId = `${prefix}-${counter.value++}`;
59
+ el.setAttribute(attrName, testId);
60
+ if (withId && !el.hasAttribute("id")) {
61
+ el.setAttribute("id", testId);
19
62
  }
20
63
  }
21
- const antElements = document.querySelectorAll('[class*="ant-"]');
22
- if (antElements.length > 0) return "ant";
23
- } catch {
24
64
  }
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
65
  }
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";
66
+ function getPanelMode(panel) {
67
+ if (panel.classList.contains("ant-picker-month-panel")) return "month";
68
+ if (panel.classList.contains("ant-picker-year-panel")) return "year";
69
+ if (panel.classList.contains("ant-picker-decade-panel")) return "decade";
38
70
  return "date";
39
71
  }
40
72
  var MONTH_NAMES = [
@@ -51,8 +83,8 @@ var MONTH_NAMES = [
51
83
  "nov",
52
84
  "dec"
53
85
  ];
54
- function injectHeaderViewButtons(panel, prefix, attrName, suffix, pfx) {
55
- const headerView = panel.querySelector(cls(pfx, "picker-header-view"));
86
+ function injectHeaderViewButtons(panel, prefix, attrName, suffix) {
87
+ const headerView = panel.querySelector(".ant-picker-header-view");
56
88
  if (!headerView) return;
57
89
  const buttons = headerView.querySelectorAll("button");
58
90
  buttons.forEach((btn) => {
@@ -74,26 +106,9 @@ function injectHeaderViewButtons(panel, prefix, attrName, suffix, pfx) {
74
106
  el.setAttribute(attrName, `${prefix}-${name}${suffix}`);
75
107
  });
76
108
  }
77
- var _observedContainers = /* @__PURE__ */ new WeakSet();
78
- function injectSinglePanel(panel, prefix, attrName, suffix, pfx) {
79
- const mode = getPanelMode(panel, pfx);
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"));
109
+ function injectPanelCells(panel, prefix, attrName, suffix) {
110
+ const mode = getPanelMode(panel);
111
+ const cells = panel.querySelectorAll(".ant-picker-cell-inner");
97
112
  cells.forEach((cell) => {
98
113
  const el = cell;
99
114
  if (el.hasAttribute(attrName)) return;
@@ -105,42 +120,55 @@ function injectPanelCells(panel, prefix, attrName, suffix, pfx) {
105
120
  el.setAttribute(attrName, `${prefix}-month-${label}${suffix}`);
106
121
  break;
107
122
  case "year":
108
- label = raw;
109
- el.setAttribute(attrName, `${prefix}-year-${label}${suffix}`);
123
+ el.setAttribute(attrName, `${prefix}-year-${raw}${suffix}`);
110
124
  break;
111
125
  case "decade":
112
- label = raw;
113
- el.setAttribute(attrName, `${prefix}-decade-${label}${suffix}`);
126
+ el.setAttribute(attrName, `${prefix}-decade-${raw}${suffix}`);
114
127
  break;
115
128
  default:
116
- label = raw;
117
- el.setAttribute(attrName, `${prefix}-date-${label}${suffix}`);
129
+ el.setAttribute(attrName, `${prefix}-date-${raw}${suffix}`);
118
130
  break;
119
131
  }
120
132
  });
121
133
  }
134
+ function injectSinglePanel(panel, prefix, attrName, suffix) {
135
+ const mode = getPanelMode(panel);
136
+ const setAttr = (selector, name) => {
137
+ const el = panel.querySelector(selector);
138
+ if (el && !el.hasAttribute(attrName)) {
139
+ el.setAttribute(attrName, `${prefix}-${name}${suffix}`);
140
+ }
141
+ };
142
+ setAttr(".ant-picker-header-super-prev-btn", "super-prev");
143
+ setAttr(".ant-picker-header-prev-btn", "prev");
144
+ setAttr(".ant-picker-header-next-btn", "next");
145
+ setAttr(".ant-picker-header-super-next-btn", "super-next");
146
+ setAttr(".ant-picker-header-view", `header-${mode}`);
147
+ injectHeaderViewButtons(panel, prefix, attrName, suffix);
148
+ injectPanelCells(panel, prefix, attrName, suffix);
149
+ }
122
150
  function injectPickerPanel(ctx) {
123
- const { container, triggerTestId, attrName, prefixCls: pfx } = ctx;
151
+ const { container, triggerTestId, attrName } = ctx;
124
152
  const prefix = `${triggerTestId}-dropdown`;
125
153
  container.setAttribute(attrName, prefix);
126
- const isRange = container.classList.contains(`${pfx}-picker-dropdown-range`);
154
+ const isRange = container.classList.contains("ant-picker-dropdown-range");
127
155
  const injectAllPanels = () => {
128
- const panels = container.querySelectorAll(cls(pfx, "picker-panel"));
156
+ const panels = container.querySelectorAll(".ant-picker-panel");
129
157
  if (isRange && panels.length >= 2) {
130
- injectSinglePanel(panels[0], prefix, attrName, "-left", pfx);
131
- injectSinglePanel(panels[1], prefix, attrName, "-right", pfx);
158
+ injectSinglePanel(panels[0], prefix, attrName, "-left");
159
+ injectSinglePanel(panels[1], prefix, attrName, "-right");
132
160
  } else {
133
161
  const panel = panels[0] || container;
134
- injectSinglePanel(panel, prefix, attrName, "", pfx);
162
+ injectSinglePanel(panel, prefix, attrName, "");
135
163
  }
136
- const presets = container.querySelectorAll(`${cls(pfx, "picker-presets")} .${pfx}-tag`);
164
+ const presets = container.querySelectorAll(".ant-picker-presets .ant-tag");
137
165
  presets.forEach((tag) => {
138
166
  const el = tag;
139
167
  if (el.hasAttribute(attrName)) return;
140
168
  const text = el.textContent?.trim() || "";
141
169
  el.setAttribute(attrName, `${prefix}-preset-${text}`);
142
170
  });
143
- const timeColumns = container.querySelectorAll(cls(pfx, "picker-time-panel-column"));
171
+ const timeColumns = container.querySelectorAll(".ant-picker-time-panel-column");
144
172
  timeColumns.forEach((col, i) => {
145
173
  const el = col;
146
174
  if (el.hasAttribute(attrName)) return;
@@ -159,9 +187,9 @@ function injectPickerPanel(ctx) {
159
187
  btn.setAttribute(attrName, `${prefix}-${name}`);
160
188
  }
161
189
  };
162
- setFooterBtn(`${cls(pfx, "picker-ok")} button`, "ok");
163
- setFooterBtn(`${cls(pfx, "picker-now")} button`, "now");
164
- setFooterBtn(cls(pfx, "picker-now-btn"), "now");
190
+ setFooterBtn(".ant-picker-ok button", "ok");
191
+ setFooterBtn(".ant-picker-now button", "now");
192
+ setFooterBtn(".ant-picker-now-btn", "now");
165
193
  };
166
194
  injectAllPanels();
167
195
  if (!_observedContainers.has(container)) {
@@ -177,14 +205,14 @@ function injectPickerPanel(ctx) {
177
205
  }
178
206
  }
179
207
  function injectCascaderPanel(ctx) {
180
- const { container, triggerTestId, attrName, prefixCls: pfx } = ctx;
208
+ const { container, triggerTestId, attrName } = ctx;
181
209
  const prefix = `${triggerTestId}-dropdown`;
182
210
  container.setAttribute(attrName, prefix);
183
- const menus = container.querySelectorAll(cls(pfx, "cascader-menu"));
211
+ const menus = container.querySelectorAll(".ant-cascader-menu");
184
212
  menus.forEach((menu, i) => {
185
213
  const el = menu;
186
214
  el.setAttribute(attrName, `${prefix}-menu-${i}`);
187
- const items = menu.querySelectorAll(cls(pfx, "cascader-menu-item"));
215
+ const items = menu.querySelectorAll(".ant-cascader-menu-item");
188
216
  items.forEach((item) => {
189
217
  const li = item;
190
218
  const text = li.textContent?.trim() || "";
@@ -193,28 +221,28 @@ function injectCascaderPanel(ctx) {
193
221
  });
194
222
  }
195
223
  function injectSelectPanel(ctx) {
196
- const { container, triggerTestId, attrName, prefixCls: pfx } = ctx;
224
+ const { container, triggerTestId, attrName } = ctx;
197
225
  const prefix = `${triggerTestId}-dropdown`;
198
226
  container.setAttribute(attrName, prefix);
199
227
  const injectAllItems = () => {
200
- const options = container.querySelectorAll(cls(pfx, "select-item-option"));
228
+ const options = container.querySelectorAll(".ant-select-item-option");
201
229
  options.forEach((opt) => {
202
230
  const el = opt;
203
231
  if (el.hasAttribute(attrName)) return;
204
232
  const title = el.getAttribute("title") || el.textContent?.trim() || "";
205
233
  el.setAttribute(attrName, `${prefix}-option-${title}`);
206
234
  });
207
- const treeNodes = container.querySelectorAll(cls(pfx, "select-tree-treenode"));
235
+ const treeNodes = container.querySelectorAll(".ant-select-tree-treenode");
208
236
  treeNodes.forEach((node) => {
209
237
  const el = node;
210
- const content = el.querySelector(cls(pfx, "select-tree-node-content-wrapper"));
238
+ const content = el.querySelector(".ant-select-tree-node-content-wrapper");
211
239
  const title = content?.getAttribute("title") || content?.textContent?.trim() || "";
212
240
  if (content && !content.hasAttribute(attrName)) {
213
241
  content.setAttribute(attrName, `${prefix}-tree-${title}`);
214
242
  }
215
- const switcher = el.querySelector(cls(pfx, "select-tree-switcher"));
243
+ const switcher = el.querySelector(".ant-select-tree-switcher");
216
244
  if (switcher && !switcher.hasAttribute(attrName)) {
217
- const isOpen = switcher.classList.contains(`${pfx}-select-tree-switcher_open`);
245
+ const isOpen = switcher.classList.contains("ant-select-tree-switcher_open");
218
246
  const state = isOpen ? "collapse" : "expand";
219
247
  switcher.setAttribute(attrName, `${prefix}-tree-switcher-${state}-${title}`);
220
248
  }
@@ -233,19 +261,42 @@ function injectSelectPanel(ctx) {
233
261
  observer.observe(container, { childList: true, subtree: true });
234
262
  }
235
263
  }
236
- function buildDefaultPanels(pfx) {
237
- return {
238
- [cls(pfx, "picker-dropdown")]: injectPickerPanel,
239
- [cls(pfx, "cascader-dropdown")]: injectCascaderPanel,
240
- [cls(pfx, "select-dropdown")]: injectSelectPanel
241
- };
264
+ function injectMenuItems(attrName, root = document) {
265
+ const itemRules = [
266
+ { selector: ".ant-menu-item", prefix: "a-menu-item-" },
267
+ { selector: ".ant-menu-submenu", textSelector: ".ant-menu-submenu-title", prefix: "a-sub-menu-" },
268
+ { selector: ".ant-menu-item-group", textSelector: ".ant-menu-item-group-title", prefix: "a-menu-item-group-" }
269
+ ];
270
+ for (const rule of itemRules) {
271
+ const items = root.querySelectorAll(rule.selector);
272
+ items.forEach((item) => {
273
+ const el = item;
274
+ if (el.hasAttribute(attrName)) return;
275
+ const id = el.getAttribute("id");
276
+ if (id) {
277
+ el.setAttribute(attrName, id);
278
+ return;
279
+ }
280
+ const textTarget = rule.textSelector ? el.querySelector(rule.textSelector) : el;
281
+ const text = (textTarget?.textContent || "").trim().replace(/\s+/g, "-");
282
+ if (text) {
283
+ el.setAttribute(attrName, rule.prefix + text);
284
+ }
285
+ });
286
+ }
242
287
  }
288
+ var DEFAULT_PANELS = {
289
+ ".ant-picker-dropdown": injectPickerPanel,
290
+ ".ant-cascader-dropdown": injectCascaderPanel,
291
+ ".ant-select-dropdown": injectSelectPanel
292
+ };
293
+ var _observedContainers = /* @__PURE__ */ new WeakSet();
243
294
  function getTriggerTestId(trigger, attrName) {
244
- if (!trigger) return "unknown-picker";
245
- return trigger.getAttribute(attrName) || trigger.getAttribute("id") || "unknown-picker";
295
+ if (!trigger) return "unknown";
296
+ return trigger.getAttribute(attrName) || trigger.getAttribute("id") || "unknown";
246
297
  }
247
- function findActiveTrigger(attrName, pfx) {
248
- const focused = document.querySelector(cls(pfx, "picker-focused"));
298
+ function findActiveTrigger(attrName) {
299
+ const focused = document.querySelector(".ant-picker-focused");
249
300
  if (focused) {
250
301
  if (focused.hasAttribute(attrName)) return focused;
251
302
  const inner = focused.querySelector(`[${attrName}]`);
@@ -253,9 +304,9 @@ function findActiveTrigger(attrName, pfx) {
253
304
  const input = focused.querySelector("input[id]");
254
305
  if (input) return input;
255
306
  }
256
- const selectOpen = document.querySelector(cls(pfx, "select-open"));
307
+ const selectOpen = document.querySelector(".ant-select-open");
257
308
  if (selectOpen?.hasAttribute(attrName)) return selectOpen;
258
- const cascader = document.querySelector(cls(pfx, "cascader-picker-focused"));
309
+ const cascader = document.querySelector(".ant-cascader-picker-focused");
259
310
  if (cascader?.hasAttribute(attrName)) return cascader;
260
311
  const expanded = document.querySelector(
261
312
  `[${attrName}][aria-expanded="true"]`
@@ -263,109 +314,195 @@ function findActiveTrigger(attrName, pfx) {
263
314
  if (expanded) return expanded;
264
315
  return null;
265
316
  }
266
- function injectMenuItems(attrName, pfx, root = document) {
267
- const itemRules = [
268
- { selector: cls(pfx, "menu-item"), prefix: "a-menu-item-" },
269
- { selector: cls(pfx, "menu-submenu"), textSelector: cls(pfx, "menu-submenu-title"), prefix: "a-sub-menu-" },
270
- { selector: cls(pfx, "menu-item-group"), textSelector: cls(pfx, "menu-item-group-title"), prefix: "a-menu-item-group-" }
271
- ];
272
- const injectSingle = (el, rule) => {
273
- if (el.hasAttribute(attrName)) return;
274
- const id = el.getAttribute("id");
275
- if (id) {
276
- el.setAttribute(attrName, id);
277
- return;
317
+ function setupAntTestIds(opts = {}) {
318
+ const attrName = opts.attributeName ?? "data-testid";
319
+ const panels = { ...DEFAULT_PANELS, ...opts.panels };
320
+ let matchers = DEFAULT_COMPONENT_MATCHERS;
321
+ if (opts.components) {
322
+ const customKeys = new Set(Object.keys(opts.components));
323
+ matchers = matchers.filter((m) => !customKeys.has(m.selector));
324
+ for (const [selector, prefix] of Object.entries(opts.components)) {
325
+ matchers.push({ selector, prefix });
278
326
  }
279
- const textTarget = rule.textSelector ? el.querySelector(rule.textSelector) : el;
280
- const text = (textTarget?.textContent || "").trim().replace(/\s+/g, "-");
281
- if (text) {
282
- el.setAttribute(attrName, rule.prefix + text);
327
+ }
328
+ const counter = { value: 0 };
329
+ function processNode(node) {
330
+ if (!(node instanceof HTMLElement)) return;
331
+ injectComponentRoots(node, matchers, attrName, counter);
332
+ injectMenuItems(attrName, node);
333
+ for (const [selector, strategy] of Object.entries(panels)) {
334
+ const containers = node.matches(selector) ? [node] : Array.from(node.querySelectorAll(selector));
335
+ for (const container of containers) {
336
+ if (!(container instanceof HTMLElement)) continue;
337
+ if (container.hasAttribute(attrName)) continue;
338
+ if (strategy) {
339
+ queueMicrotask(() => {
340
+ const trigger = findActiveTrigger(attrName);
341
+ const triggerTestId = getTriggerTestId(trigger, attrName);
342
+ strategy({ container, trigger, triggerTestId, attrName });
343
+ });
344
+ }
345
+ }
283
346
  }
284
- };
285
- for (const rule of itemRules) {
286
- if (root instanceof HTMLElement && root.matches(rule.selector)) {
287
- injectSingle(root, rule);
347
+ const inputNumbers = node.matches(`.ant-input-number[${attrName}]`) ? [node] : Array.from(node.querySelectorAll(`.ant-input-number[${attrName}]`));
348
+ for (const inputNumber of inputNumbers) {
349
+ if (!(inputNumber instanceof HTMLElement)) continue;
350
+ const testId = inputNumber.getAttribute(attrName) || "";
351
+ if (!testId) continue;
352
+ const input = inputNumber.querySelector(".ant-input-number-input");
353
+ if (input && !input.hasAttribute(attrName)) {
354
+ input.setAttribute(attrName, testId);
355
+ }
356
+ const upHandler = inputNumber.querySelector(".ant-input-number-handler-up");
357
+ if (upHandler && !upHandler.hasAttribute(attrName)) {
358
+ upHandler.setAttribute(attrName, `${testId}-up`);
359
+ }
360
+ const downHandler = inputNumber.querySelector(".ant-input-number-handler-down");
361
+ if (downHandler && !downHandler.hasAttribute(attrName)) {
362
+ downHandler.setAttribute(attrName, `${testId}-down`);
363
+ }
364
+ }
365
+ const sliders = node.matches(`.ant-slider[${attrName}]`) ? [node] : Array.from(node.querySelectorAll(`.ant-slider[${attrName}]`));
366
+ for (const slider of sliders) {
367
+ if (!(slider instanceof HTMLElement)) continue;
368
+ const testId = slider.getAttribute(attrName) || "";
369
+ if (!testId) continue;
370
+ const handles = slider.querySelectorAll(".ant-slider-handle");
371
+ handles.forEach((h, i) => {
372
+ const el = h;
373
+ if (!el.hasAttribute(attrName)) {
374
+ el.setAttribute(attrName, `${testId}-handle-${i}`);
375
+ }
376
+ });
377
+ }
378
+ const rates = node.matches(`.ant-rate[${attrName}]`) ? [node] : Array.from(node.querySelectorAll(`.ant-rate[${attrName}]`));
379
+ for (const rate of rates) {
380
+ if (!(rate instanceof HTMLElement)) continue;
381
+ const testId = rate.getAttribute(attrName) || "";
382
+ if (!testId) continue;
383
+ const stars = rate.querySelectorAll(".ant-rate-star");
384
+ stars.forEach((s, i) => {
385
+ const el = s;
386
+ if (!el.hasAttribute(attrName)) {
387
+ el.setAttribute(attrName, `${testId}-star-${i}`);
388
+ }
389
+ });
390
+ }
391
+ const uploadByTestId = node.matches(`.ant-upload[${attrName}]`) ? [node] : Array.from(node.querySelectorAll(`.ant-upload[${attrName}]`));
392
+ const uploadById = uploadByTestId.length === 0 ? node.matches(".ant-upload[id]") ? [node] : Array.from(node.querySelectorAll(".ant-upload[id]")) : [];
393
+ for (const upload of [...uploadByTestId, ...uploadById]) {
394
+ if (!(upload instanceof HTMLElement)) continue;
395
+ const testId = upload.getAttribute(attrName) || upload.getAttribute("id") || "";
396
+ if (!testId) continue;
397
+ const input = upload.querySelector('input[type="file"]');
398
+ if (input && !input.hasAttribute(attrName)) {
399
+ input.setAttribute(attrName, `${testId}-input`);
400
+ if (!upload.hasAttribute(attrName)) {
401
+ upload.setAttribute(attrName, testId);
402
+ }
403
+ }
288
404
  }
289
- const items = root.querySelectorAll(rule.selector);
290
- items.forEach((item) => {
291
- injectSingle(item, rule);
292
- });
293
405
  }
294
- }
295
- function setupAntDropdownTestIds(opts = {}) {
296
- const attrName = opts.attributeName ?? "data-testid";
297
- const pfx = resolvePrefixCls(opts.prefixCls);
298
- const defaultPanels = buildDefaultPanels(pfx);
299
- const panels = { ...defaultPanels, ...opts.panels };
406
+ let pending = false;
407
+ let pendingNodes = [];
408
+ const flushPending = () => {
409
+ pending = false;
410
+ const nodes = pendingNodes;
411
+ pendingNodes = [];
412
+ for (const node of nodes) {
413
+ processNode(node);
414
+ }
415
+ };
300
416
  const observer = new MutationObserver((mutations) => {
301
417
  for (const mutation of mutations) {
302
418
  for (const node of mutation.addedNodes) {
303
- if (!(node instanceof HTMLElement)) continue;
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);
419
+ pendingNodes.push(node);
341
420
  }
342
421
  }
422
+ if (!pending) {
423
+ pending = true;
424
+ requestAnimationFrame(flushPending);
425
+ }
343
426
  });
344
427
  observer.observe(document.body, { childList: true, subtree: true });
345
- requestAnimationFrame(() => {
346
- injectMenuItems(attrName, pfx);
347
- });
348
- return () => observer.disconnect();
428
+ processNode(document.body);
429
+ return () => {
430
+ observer.disconnect();
431
+ pendingNodes = [];
432
+ };
349
433
  }
434
+ var setupAntDropdownTestIds = setupAntTestIds;
350
435
  function injectCurrentDropdowns(opts = {}) {
351
436
  const attrName = opts.attributeName ?? "data-testid";
352
- const pfx = resolvePrefixCls(opts.prefixCls);
353
- const defaultPanels = buildDefaultPanels(pfx);
354
- const panels = { ...defaultPanels, ...opts.panels };
437
+ const panels = { ...DEFAULT_PANELS, ...opts.panels };
438
+ const counter = { value: 0 };
439
+ let matchers = DEFAULT_COMPONENT_MATCHERS;
440
+ if (opts.components) {
441
+ const customKeys = new Set(Object.keys(opts.components));
442
+ matchers = matchers.filter((m) => !customKeys.has(m.selector));
443
+ for (const [selector, prefix] of Object.entries(opts.components)) {
444
+ matchers.push({ selector, prefix });
445
+ }
446
+ }
447
+ injectComponentRoots(document.body, matchers, attrName, counter);
355
448
  for (const [selector, strategy] of Object.entries(panels)) {
356
449
  const containers = document.querySelectorAll(selector);
357
450
  containers.forEach((container) => {
358
451
  if (!(container instanceof HTMLElement)) return;
359
452
  if (container.hasAttribute(attrName)) return;
360
453
  if (!strategy) return;
361
- const trigger = findActiveTrigger(attrName, pfx);
454
+ const trigger = findActiveTrigger(attrName);
362
455
  const triggerTestId = getTriggerTestId(trigger, attrName);
363
- strategy({ container, trigger, triggerTestId, attrName, prefixCls: pfx });
456
+ strategy({ container, trigger, triggerTestId, attrName });
364
457
  });
365
458
  }
459
+ const inputNumbers = document.querySelectorAll(`.ant-input-number[${attrName}]`);
460
+ inputNumbers.forEach((inputNumber) => {
461
+ if (!(inputNumber instanceof HTMLElement)) return;
462
+ const testId = inputNumber.getAttribute(attrName) || "";
463
+ if (!testId) return;
464
+ const input = inputNumber.querySelector(".ant-input-number-input");
465
+ if (input && !input.hasAttribute(attrName)) {
466
+ input.setAttribute(attrName, testId);
467
+ }
468
+ const upHandler = inputNumber.querySelector(".ant-input-number-handler-up");
469
+ if (upHandler && !upHandler.hasAttribute(attrName)) {
470
+ upHandler.setAttribute(attrName, `${testId}-up`);
471
+ }
472
+ const downHandler = inputNumber.querySelector(".ant-input-number-handler-down");
473
+ if (downHandler && !downHandler.hasAttribute(attrName)) {
474
+ downHandler.setAttribute(attrName, `${testId}-down`);
475
+ }
476
+ });
477
+ const sliders = document.querySelectorAll(`.ant-slider[${attrName}]`);
478
+ sliders.forEach((slider) => {
479
+ if (!(slider instanceof HTMLElement)) return;
480
+ const testId = slider.getAttribute(attrName) || "";
481
+ if (!testId) return;
482
+ const handles = slider.querySelectorAll(".ant-slider-handle");
483
+ handles.forEach((h, i) => {
484
+ const el = h;
485
+ if (!el.hasAttribute(attrName)) {
486
+ el.setAttribute(attrName, `${testId}-handle-${i}`);
487
+ }
488
+ });
489
+ });
490
+ const rates = document.querySelectorAll(`.ant-rate[${attrName}]`);
491
+ rates.forEach((rate) => {
492
+ if (!(rate instanceof HTMLElement)) return;
493
+ const testId = rate.getAttribute(attrName) || "";
494
+ if (!testId) return;
495
+ const stars = rate.querySelectorAll(".ant-rate-star");
496
+ stars.forEach((s, i) => {
497
+ const el = s;
498
+ if (!el.hasAttribute(attrName)) {
499
+ el.setAttribute(attrName, `${testId}-star-${i}`);
500
+ }
501
+ });
502
+ });
366
503
  const uploads = [
367
- ...Array.from(document.querySelectorAll(`${cls(pfx, "upload")}[${attrName}]`)),
368
- ...Array.from(document.querySelectorAll(`${cls(pfx, "upload")}[id]`))
504
+ ...Array.from(document.querySelectorAll(`.ant-upload[${attrName}]`)),
505
+ ...Array.from(document.querySelectorAll(".ant-upload[id]"))
369
506
  ];
370
507
  const seen = /* @__PURE__ */ new Set();
371
508
  uploads.forEach((upload) => {
@@ -382,9 +519,10 @@ function injectCurrentDropdowns(opts = {}) {
382
519
  }
383
520
  }
384
521
  });
385
- injectMenuItems(attrName, pfx);
522
+ injectMenuItems(attrName);
386
523
  }
387
524
  export {
388
525
  injectCurrentDropdowns,
389
- setupAntDropdownTestIds
526
+ setupAntDropdownTestIds,
527
+ setupAntTestIds
390
528
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vite-plugin-vue-testid",
3
- "version": "1.0.8",
3
+ "version": "1.0.9",
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,12 +42,9 @@
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",
51
48
  "vite": "^8.0.12"
52
49
  }
53
- }
50
+ }