@testid/antd-testid-runtime 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -30,6 +30,13 @@ interface TestIdMarkConfig {
30
30
  runtimePagePrefix: string;
31
31
  /** Antd 浮层组件专属前缀映射 */
32
32
  popupPrefixMap: Record<PopupType, string>;
33
+ /**
34
+ * Ant Design Vue CSS 类名前缀 (对应 ConfigProvider 的 prefixCls)
35
+ *
36
+ * 默认: 'ant' → 匹配 .ant-modal, .ant-picker-dropdown 等
37
+ * 若项目使用 <a-config-provider prefixCls="my-ui"> 则设为 'my-ui'
38
+ */
39
+ antdClassPrefix: string;
33
40
  /** 忽略不打标的 HTML 标签名 */
34
41
  ignoreTags: string[];
35
42
  /** 包含此 class 的 DOM 跳过打标 */
@@ -194,6 +201,8 @@ declare function buildPopupTestId(type: PopupType, tag: string, counterId: numbe
194
201
  */
195
202
  declare class TestIdObserver {
196
203
  private state;
204
+ /** 浮层 class 匹配映射 (根据 antdClassPrefix 动态构建) */
205
+ private popupClassMap;
197
206
  constructor();
198
207
  /**
199
208
  * 启动 MutationObserver
@@ -207,6 +216,10 @@ declare class TestIdObserver {
207
216
  * 更新当前路由 (路由切换时调用)
208
217
  */
209
218
  setRoute(routeName: string): void;
219
+ /**
220
+ * 重置浮层子节点计数器 (路由切换时调用)
221
+ */
222
+ resetPopupChildCounter(): void;
210
223
  /**
211
224
  * 全量扫描当前 DOM (用于启动时兜底, 处理 Observer 启动前已渲染的节点)
212
225
  */
@@ -225,8 +238,9 @@ declare class TestIdObserver {
225
238
  * 决策优先级:
226
239
  * 0. 已有 data-testid → 跳过
227
240
  * 1. 带 data-test-base-key → 公共组件实例 (锚点定位)
228
- * 2. body 直系浮层 → 匹配浮层类型 → 独立前缀
229
- * 3. #app 内普通节点dynamic
241
+ * 2. body 直系浮层根节点 → 匹配浮层类型 → 独立前缀
242
+ * 3. 浮层内部子节点 查找浮层祖先 浮层独立前缀 + 计数器
243
+ * 4. #app 内普通节点 → dynamic
230
244
  */
231
245
  private processSingleNode;
232
246
  /**
@@ -254,18 +268,50 @@ declare class TestIdObserver {
254
268
  */
255
269
  private handlePopupNode;
256
270
  /**
257
- * 通过 CSS class 匹配浮层类型
271
+ * 检测节点是否为浮层根节点
272
+ *
273
+ * Ant Design Vue 4.x 可能用一层 wrapper DIV 包裹浮层再 append 到 body:
274
+ * body → DIV(wrapper) → DIV.ant-picker-dropdown → panel
275
+ *
276
+ * 因此不要求 parentElement === document.body,
277
+ * 只要节点 class 匹配浮层类型且不在 #app 内即可。
258
278
  *
259
279
  * @param node - 待检测节点
260
- * @returns 浮层类型或 null (非 Antd 浮层)
280
+ * @returns 浮层类型或 null
261
281
  */
262
282
  private detectPopupType;
283
+ /**
284
+ * 向上遍历 DOM,检测节点是否在某个浮层内部
285
+ *
286
+ * 查找最近的匹配 POPUP_CLASS_MAP 的祖先元素 (不要求它本身是 body 直系,
287
+ * 因为 Ant Design Vue 4 可能在浮层根节点外包一层 wrapper DIV)。
288
+ * 找到浮层根节点后,继续向上验证其祖先链能到达 body (确保不在 #app 内)。
289
+ *
290
+ * @returns 浮层类型或 null (不在任何浮层内)
291
+ */
292
+ private detectPopupAncestor;
293
+ /**
294
+ * 验证节点的祖先链是否能到达 document.body
295
+ * (经过若干层 wrapper 后依然是 body 下的浮层)
296
+ */
297
+ private reachesBody;
298
+ /**
299
+ * 检测节点 class 是否匹配某个浮层类型
300
+ */
301
+ private matchPopupClass;
263
302
  /**
264
303
  * 处理页面内动态新增节点 (v-for / 异步 v-if 等)
265
304
  *
266
305
  * ID 格式: ${runtimePagePrefix}${route}_${tag}_${counter}
267
306
  */
268
307
  private handleDynamicNode;
308
+ /**
309
+ * 处理浮层内部子节点 (Modal/Drawer/Dropdown 内的按钮、输入框等)
310
+ *
311
+ * ID 格式: ${popupPrefix}${popupType}_${tag}_${counter}
312
+ * 例: hall_modal_button_0, hall_select_div_2
313
+ */
314
+ private handlePopupChildNode;
269
315
  /**
270
316
  * 检测元素是否在 #app 容器内
271
317
  */
package/dist/index.d.ts CHANGED
@@ -30,6 +30,13 @@ interface TestIdMarkConfig {
30
30
  runtimePagePrefix: string;
31
31
  /** Antd 浮层组件专属前缀映射 */
32
32
  popupPrefixMap: Record<PopupType, string>;
33
+ /**
34
+ * Ant Design Vue CSS 类名前缀 (对应 ConfigProvider 的 prefixCls)
35
+ *
36
+ * 默认: 'ant' → 匹配 .ant-modal, .ant-picker-dropdown 等
37
+ * 若项目使用 <a-config-provider prefixCls="my-ui"> 则设为 'my-ui'
38
+ */
39
+ antdClassPrefix: string;
33
40
  /** 忽略不打标的 HTML 标签名 */
34
41
  ignoreTags: string[];
35
42
  /** 包含此 class 的 DOM 跳过打标 */
@@ -194,6 +201,8 @@ declare function buildPopupTestId(type: PopupType, tag: string, counterId: numbe
194
201
  */
195
202
  declare class TestIdObserver {
196
203
  private state;
204
+ /** 浮层 class 匹配映射 (根据 antdClassPrefix 动态构建) */
205
+ private popupClassMap;
197
206
  constructor();
198
207
  /**
199
208
  * 启动 MutationObserver
@@ -207,6 +216,10 @@ declare class TestIdObserver {
207
216
  * 更新当前路由 (路由切换时调用)
208
217
  */
209
218
  setRoute(routeName: string): void;
219
+ /**
220
+ * 重置浮层子节点计数器 (路由切换时调用)
221
+ */
222
+ resetPopupChildCounter(): void;
210
223
  /**
211
224
  * 全量扫描当前 DOM (用于启动时兜底, 处理 Observer 启动前已渲染的节点)
212
225
  */
@@ -225,8 +238,9 @@ declare class TestIdObserver {
225
238
  * 决策优先级:
226
239
  * 0. 已有 data-testid → 跳过
227
240
  * 1. 带 data-test-base-key → 公共组件实例 (锚点定位)
228
- * 2. body 直系浮层 → 匹配浮层类型 → 独立前缀
229
- * 3. #app 内普通节点dynamic
241
+ * 2. body 直系浮层根节点 → 匹配浮层类型 → 独立前缀
242
+ * 3. 浮层内部子节点 查找浮层祖先 浮层独立前缀 + 计数器
243
+ * 4. #app 内普通节点 → dynamic
230
244
  */
231
245
  private processSingleNode;
232
246
  /**
@@ -254,18 +268,50 @@ declare class TestIdObserver {
254
268
  */
255
269
  private handlePopupNode;
256
270
  /**
257
- * 通过 CSS class 匹配浮层类型
271
+ * 检测节点是否为浮层根节点
272
+ *
273
+ * Ant Design Vue 4.x 可能用一层 wrapper DIV 包裹浮层再 append 到 body:
274
+ * body → DIV(wrapper) → DIV.ant-picker-dropdown → panel
275
+ *
276
+ * 因此不要求 parentElement === document.body,
277
+ * 只要节点 class 匹配浮层类型且不在 #app 内即可。
258
278
  *
259
279
  * @param node - 待检测节点
260
- * @returns 浮层类型或 null (非 Antd 浮层)
280
+ * @returns 浮层类型或 null
261
281
  */
262
282
  private detectPopupType;
283
+ /**
284
+ * 向上遍历 DOM,检测节点是否在某个浮层内部
285
+ *
286
+ * 查找最近的匹配 POPUP_CLASS_MAP 的祖先元素 (不要求它本身是 body 直系,
287
+ * 因为 Ant Design Vue 4 可能在浮层根节点外包一层 wrapper DIV)。
288
+ * 找到浮层根节点后,继续向上验证其祖先链能到达 body (确保不在 #app 内)。
289
+ *
290
+ * @returns 浮层类型或 null (不在任何浮层内)
291
+ */
292
+ private detectPopupAncestor;
293
+ /**
294
+ * 验证节点的祖先链是否能到达 document.body
295
+ * (经过若干层 wrapper 后依然是 body 下的浮层)
296
+ */
297
+ private reachesBody;
298
+ /**
299
+ * 检测节点 class 是否匹配某个浮层类型
300
+ */
301
+ private matchPopupClass;
263
302
  /**
264
303
  * 处理页面内动态新增节点 (v-for / 异步 v-if 等)
265
304
  *
266
305
  * ID 格式: ${runtimePagePrefix}${route}_${tag}_${counter}
267
306
  */
268
307
  private handleDynamicNode;
308
+ /**
309
+ * 处理浮层内部子节点 (Modal/Drawer/Dropdown 内的按钮、输入框等)
310
+ *
311
+ * ID 格式: ${popupPrefix}${popupType}_${tag}_${counter}
312
+ * 例: hall_modal_button_0, hall_select_div_2
313
+ */
314
+ private handlePopupChildNode;
269
315
  /**
270
316
  * 检测元素是否在 #app 容器内
271
317
  */
package/dist/index.js CHANGED
@@ -60,6 +60,7 @@ var defaultConfig = {
60
60
  globalPrefix: "",
61
61
  compilePrefix: "static_",
62
62
  runtimePagePrefix: "dynamic_",
63
+ antdClassPrefix: "ant",
63
64
  popupPrefixMap: {
64
65
  modal: "modal_",
65
66
  drawer: "drawer_",
@@ -181,15 +182,23 @@ function buildPopupTestId(type, tag, counterId) {
181
182
  }
182
183
 
183
184
  // src/utils/testIdObserver.ts
184
- var POPUP_CLASS_MAP = {
185
- modal: ".ant-modal",
186
- drawer: ".ant-drawer",
187
- select: ".ant-select-dropdown",
188
- datePicker: ".ant-picker-dropdown",
189
- popconfirm: ".ant-popover.ant-popconfirm",
190
- dropdown: ".ant-dropdown",
191
- tooltip: ".ant-tooltip"
185
+ var POPUP_CLASS_SUFFIX_MAP = {
186
+ modal: ".{prefix}-modal",
187
+ drawer: ".{prefix}-drawer",
188
+ select: ".{prefix}-select-dropdown",
189
+ datePicker: ".{prefix}-picker-dropdown",
190
+ popconfirm: ".{prefix}-popover.{prefix}-popconfirm",
191
+ dropdown: ".{prefix}-dropdown",
192
+ tooltip: ".{prefix}-tooltip"
192
193
  };
194
+ function buildPopupClassMap(prefix) {
195
+ const result = {};
196
+ const entries = Object.entries(POPUP_CLASS_SUFFIX_MAP);
197
+ for (const [type, template] of entries) {
198
+ result[type] = template.replace(/\{prefix\}/g, prefix);
199
+ }
200
+ return result;
201
+ }
193
202
  var TestIdObserver = class {
194
203
  constructor() {
195
204
  // ==========================================================
@@ -208,11 +217,14 @@ var TestIdObserver = class {
208
217
  });
209
218
  }
210
219
  };
220
+ const config = getConfig();
221
+ this.popupClassMap = buildPopupClassMap(config.antdClassPrefix);
211
222
  this.state = {
212
223
  observer: null,
213
224
  isRunning: false,
214
225
  currentRoute: "",
215
- dynamicCounter: /* @__PURE__ */ new Map()
226
+ dynamicCounter: /* @__PURE__ */ new Map(),
227
+ popupChildCounter: /* @__PURE__ */ new Map()
216
228
  };
217
229
  }
218
230
  // ==========================================================
@@ -249,6 +261,12 @@ var TestIdObserver = class {
249
261
  setRoute(routeName) {
250
262
  this.state.currentRoute = routeName;
251
263
  }
264
+ /**
265
+ * 重置浮层子节点计数器 (路由切换时调用)
266
+ */
267
+ resetPopupChildCounter() {
268
+ this.state.popupChildCounter.clear();
269
+ }
252
270
  /**
253
271
  * 全量扫描当前 DOM (用于启动时兜底, 处理 Observer 启动前已渲染的节点)
254
272
  */
@@ -286,8 +304,9 @@ var TestIdObserver = class {
286
304
  * 决策优先级:
287
305
  * 0. 已有 data-testid → 跳过
288
306
  * 1. 带 data-test-base-key → 公共组件实例 (锚点定位)
289
- * 2. body 直系浮层 → 匹配浮层类型 → 独立前缀
290
- * 3. #app 内普通节点dynamic
307
+ * 2. body 直系浮层根节点 → 匹配浮层类型 → 独立前缀
308
+ * 3. 浮层内部子节点 查找浮层祖先 浮层独立前缀 + 计数器
309
+ * 4. #app 内普通节点 → dynamic
291
310
  */
292
311
  processSingleNode(node) {
293
312
  const config = getConfig();
@@ -303,6 +322,11 @@ var TestIdObserver = class {
303
322
  this.handlePopupNode(node, popupType);
304
323
  return;
305
324
  }
325
+ const popupAncestorType = this.detectPopupAncestor(node);
326
+ if (popupAncestorType) {
327
+ this.handlePopupChildNode(node, popupAncestorType, config);
328
+ return;
329
+ }
306
330
  if (this.isInsideApp(node)) {
307
331
  this.handleDynamicNode(node, config);
308
332
  }
@@ -373,16 +397,62 @@ var TestIdObserver = class {
373
397
  node.setAttribute("data-testid", testId);
374
398
  }
375
399
  /**
376
- * 通过 CSS class 匹配浮层类型
400
+ * 检测节点是否为浮层根节点
401
+ *
402
+ * Ant Design Vue 4.x 可能用一层 wrapper DIV 包裹浮层再 append 到 body:
403
+ * body → DIV(wrapper) → DIV.ant-picker-dropdown → panel
404
+ *
405
+ * 因此不要求 parentElement === document.body,
406
+ * 只要节点 class 匹配浮层类型且不在 #app 内即可。
377
407
  *
378
408
  * @param node - 待检测节点
379
- * @returns 浮层类型或 null (非 Antd 浮层)
409
+ * @returns 浮层类型或 null
380
410
  */
381
411
  detectPopupType(node) {
382
- if (node.parentElement !== document.body) return null;
412
+ if (this.isInsideApp(node)) return null;
413
+ return this.matchPopupClass(node);
414
+ }
415
+ /**
416
+ * 向上遍历 DOM,检测节点是否在某个浮层内部
417
+ *
418
+ * 查找最近的匹配 POPUP_CLASS_MAP 的祖先元素 (不要求它本身是 body 直系,
419
+ * 因为 Ant Design Vue 4 可能在浮层根节点外包一层 wrapper DIV)。
420
+ * 找到浮层根节点后,继续向上验证其祖先链能到达 body (确保不在 #app 内)。
421
+ *
422
+ * @returns 浮层类型或 null (不在任何浮层内)
423
+ */
424
+ detectPopupAncestor(node) {
425
+ let current = node.parentElement;
426
+ while (current) {
427
+ const type = this.matchPopupClass(current);
428
+ if (type) {
429
+ const reachesBody = this.reachesBody(current);
430
+ if (reachesBody) return type;
431
+ }
432
+ if (current === document.body || current.id === "app") break;
433
+ current = current.parentElement;
434
+ }
435
+ return null;
436
+ }
437
+ /**
438
+ * 验证节点的祖先链是否能到达 document.body
439
+ * (经过若干层 wrapper 后依然是 body 下的浮层)
440
+ */
441
+ reachesBody(node) {
442
+ let current = node;
443
+ while (current && current !== document.body) {
444
+ if (current.id === "app") return false;
445
+ current = current.parentElement;
446
+ }
447
+ return current === document.body;
448
+ }
449
+ /**
450
+ * 检测节点 class 是否匹配某个浮层类型
451
+ */
452
+ matchPopupClass(node) {
383
453
  const classStr = node.className || "";
384
454
  if (typeof classStr !== "string") return null;
385
- const entries = Object.entries(POPUP_CLASS_MAP);
455
+ const entries = Object.entries(this.popupClassMap);
386
456
  for (const [type, selector] of entries) {
387
457
  const classes = selector.replace(/^\./, "").split(".");
388
458
  const allMatch = classes.every(
@@ -412,6 +482,22 @@ var TestIdObserver = class {
412
482
  const testId = `${config.runtimePagePrefix}${route}_${tag}_${current}`;
413
483
  node.setAttribute("data-testid", testId);
414
484
  }
485
+ /**
486
+ * 处理浮层内部子节点 (Modal/Drawer/Dropdown 内的按钮、输入框等)
487
+ *
488
+ * ID 格式: ${popupPrefix}${popupType}_${tag}_${counter}
489
+ * 例: hall_modal_button_0, hall_select_div_2
490
+ */
491
+ handlePopupChildNode(node, popupType, config) {
492
+ if (config.onlyInteractive && !this.isInteractive(node)) return;
493
+ const tag = this.getSimpleTag(node);
494
+ const key = `${popupType}_${tag}`;
495
+ const current = this.state.popupChildCounter.get(key) ?? 0;
496
+ this.state.popupChildCounter.set(key, current + 1);
497
+ const prefix = config.popupPrefixMap[popupType] || `${popupType}_`;
498
+ const testId = `${prefix}${tag}_${current}`;
499
+ node.setAttribute("data-testid", testId);
500
+ }
415
501
  // ==========================================================
416
502
  // 辅助方法
417
503
  // ==========================================================
package/dist/index.mjs CHANGED
@@ -18,6 +18,7 @@ var defaultConfig = {
18
18
  globalPrefix: "",
19
19
  compilePrefix: "static_",
20
20
  runtimePagePrefix: "dynamic_",
21
+ antdClassPrefix: "ant",
21
22
  popupPrefixMap: {
22
23
  modal: "modal_",
23
24
  drawer: "drawer_",
@@ -139,15 +140,23 @@ function buildPopupTestId(type, tag, counterId) {
139
140
  }
140
141
 
141
142
  // src/utils/testIdObserver.ts
142
- var POPUP_CLASS_MAP = {
143
- modal: ".ant-modal",
144
- drawer: ".ant-drawer",
145
- select: ".ant-select-dropdown",
146
- datePicker: ".ant-picker-dropdown",
147
- popconfirm: ".ant-popover.ant-popconfirm",
148
- dropdown: ".ant-dropdown",
149
- tooltip: ".ant-tooltip"
143
+ var POPUP_CLASS_SUFFIX_MAP = {
144
+ modal: ".{prefix}-modal",
145
+ drawer: ".{prefix}-drawer",
146
+ select: ".{prefix}-select-dropdown",
147
+ datePicker: ".{prefix}-picker-dropdown",
148
+ popconfirm: ".{prefix}-popover.{prefix}-popconfirm",
149
+ dropdown: ".{prefix}-dropdown",
150
+ tooltip: ".{prefix}-tooltip"
150
151
  };
152
+ function buildPopupClassMap(prefix) {
153
+ const result = {};
154
+ const entries = Object.entries(POPUP_CLASS_SUFFIX_MAP);
155
+ for (const [type, template] of entries) {
156
+ result[type] = template.replace(/\{prefix\}/g, prefix);
157
+ }
158
+ return result;
159
+ }
151
160
  var TestIdObserver = class {
152
161
  constructor() {
153
162
  // ==========================================================
@@ -166,11 +175,14 @@ var TestIdObserver = class {
166
175
  });
167
176
  }
168
177
  };
178
+ const config = getConfig();
179
+ this.popupClassMap = buildPopupClassMap(config.antdClassPrefix);
169
180
  this.state = {
170
181
  observer: null,
171
182
  isRunning: false,
172
183
  currentRoute: "",
173
- dynamicCounter: /* @__PURE__ */ new Map()
184
+ dynamicCounter: /* @__PURE__ */ new Map(),
185
+ popupChildCounter: /* @__PURE__ */ new Map()
174
186
  };
175
187
  }
176
188
  // ==========================================================
@@ -207,6 +219,12 @@ var TestIdObserver = class {
207
219
  setRoute(routeName) {
208
220
  this.state.currentRoute = routeName;
209
221
  }
222
+ /**
223
+ * 重置浮层子节点计数器 (路由切换时调用)
224
+ */
225
+ resetPopupChildCounter() {
226
+ this.state.popupChildCounter.clear();
227
+ }
210
228
  /**
211
229
  * 全量扫描当前 DOM (用于启动时兜底, 处理 Observer 启动前已渲染的节点)
212
230
  */
@@ -244,8 +262,9 @@ var TestIdObserver = class {
244
262
  * 决策优先级:
245
263
  * 0. 已有 data-testid → 跳过
246
264
  * 1. 带 data-test-base-key → 公共组件实例 (锚点定位)
247
- * 2. body 直系浮层 → 匹配浮层类型 → 独立前缀
248
- * 3. #app 内普通节点dynamic
265
+ * 2. body 直系浮层根节点 → 匹配浮层类型 → 独立前缀
266
+ * 3. 浮层内部子节点 查找浮层祖先 浮层独立前缀 + 计数器
267
+ * 4. #app 内普通节点 → dynamic
249
268
  */
250
269
  processSingleNode(node) {
251
270
  const config = getConfig();
@@ -261,6 +280,11 @@ var TestIdObserver = class {
261
280
  this.handlePopupNode(node, popupType);
262
281
  return;
263
282
  }
283
+ const popupAncestorType = this.detectPopupAncestor(node);
284
+ if (popupAncestorType) {
285
+ this.handlePopupChildNode(node, popupAncestorType, config);
286
+ return;
287
+ }
264
288
  if (this.isInsideApp(node)) {
265
289
  this.handleDynamicNode(node, config);
266
290
  }
@@ -331,16 +355,62 @@ var TestIdObserver = class {
331
355
  node.setAttribute("data-testid", testId);
332
356
  }
333
357
  /**
334
- * 通过 CSS class 匹配浮层类型
358
+ * 检测节点是否为浮层根节点
359
+ *
360
+ * Ant Design Vue 4.x 可能用一层 wrapper DIV 包裹浮层再 append 到 body:
361
+ * body → DIV(wrapper) → DIV.ant-picker-dropdown → panel
362
+ *
363
+ * 因此不要求 parentElement === document.body,
364
+ * 只要节点 class 匹配浮层类型且不在 #app 内即可。
335
365
  *
336
366
  * @param node - 待检测节点
337
- * @returns 浮层类型或 null (非 Antd 浮层)
367
+ * @returns 浮层类型或 null
338
368
  */
339
369
  detectPopupType(node) {
340
- if (node.parentElement !== document.body) return null;
370
+ if (this.isInsideApp(node)) return null;
371
+ return this.matchPopupClass(node);
372
+ }
373
+ /**
374
+ * 向上遍历 DOM,检测节点是否在某个浮层内部
375
+ *
376
+ * 查找最近的匹配 POPUP_CLASS_MAP 的祖先元素 (不要求它本身是 body 直系,
377
+ * 因为 Ant Design Vue 4 可能在浮层根节点外包一层 wrapper DIV)。
378
+ * 找到浮层根节点后,继续向上验证其祖先链能到达 body (确保不在 #app 内)。
379
+ *
380
+ * @returns 浮层类型或 null (不在任何浮层内)
381
+ */
382
+ detectPopupAncestor(node) {
383
+ let current = node.parentElement;
384
+ while (current) {
385
+ const type = this.matchPopupClass(current);
386
+ if (type) {
387
+ const reachesBody = this.reachesBody(current);
388
+ if (reachesBody) return type;
389
+ }
390
+ if (current === document.body || current.id === "app") break;
391
+ current = current.parentElement;
392
+ }
393
+ return null;
394
+ }
395
+ /**
396
+ * 验证节点的祖先链是否能到达 document.body
397
+ * (经过若干层 wrapper 后依然是 body 下的浮层)
398
+ */
399
+ reachesBody(node) {
400
+ let current = node;
401
+ while (current && current !== document.body) {
402
+ if (current.id === "app") return false;
403
+ current = current.parentElement;
404
+ }
405
+ return current === document.body;
406
+ }
407
+ /**
408
+ * 检测节点 class 是否匹配某个浮层类型
409
+ */
410
+ matchPopupClass(node) {
341
411
  const classStr = node.className || "";
342
412
  if (typeof classStr !== "string") return null;
343
- const entries = Object.entries(POPUP_CLASS_MAP);
413
+ const entries = Object.entries(this.popupClassMap);
344
414
  for (const [type, selector] of entries) {
345
415
  const classes = selector.replace(/^\./, "").split(".");
346
416
  const allMatch = classes.every(
@@ -370,6 +440,22 @@ var TestIdObserver = class {
370
440
  const testId = `${config.runtimePagePrefix}${route}_${tag}_${current}`;
371
441
  node.setAttribute("data-testid", testId);
372
442
  }
443
+ /**
444
+ * 处理浮层内部子节点 (Modal/Drawer/Dropdown 内的按钮、输入框等)
445
+ *
446
+ * ID 格式: ${popupPrefix}${popupType}_${tag}_${counter}
447
+ * 例: hall_modal_button_0, hall_select_div_2
448
+ */
449
+ handlePopupChildNode(node, popupType, config) {
450
+ if (config.onlyInteractive && !this.isInteractive(node)) return;
451
+ const tag = this.getSimpleTag(node);
452
+ const key = `${popupType}_${tag}`;
453
+ const current = this.state.popupChildCounter.get(key) ?? 0;
454
+ this.state.popupChildCounter.set(key, current + 1);
455
+ const prefix = config.popupPrefixMap[popupType] || `${popupType}_`;
456
+ const testId = `${prefix}${tag}_${current}`;
457
+ node.setAttribute("data-testid", testId);
458
+ }
373
459
  // ==========================================================
374
460
  // 辅助方法
375
461
  // ==========================================================
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testid/antd-testid-runtime",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "运行时兜底打标模块 — MutationObserver + 锚点计数器 + 浮层计数器 + ID 重复检测",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",