vite-plugin-vue-testid 1.1.2 → 1.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +9 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -1
- package/dist/plugin.d.ts +34 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +22 -0
- package/dist/runtime.d.ts +5 -2
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +232 -1
- package/package.json +5 -1
package/dist/index.d.ts
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* setupAntTestIds()
|
|
20
20
|
* ```
|
|
21
21
|
*
|
|
22
|
-
* === 使用方式 B
|
|
22
|
+
* === 使用方式 B:编译器 + Vue 插件 + 运行时(推荐,稳定 testid) ===
|
|
23
23
|
*
|
|
24
24
|
* ```ts
|
|
25
25
|
* // vite.config.ts
|
|
@@ -38,9 +38,13 @@
|
|
|
38
38
|
* ]
|
|
39
39
|
* })
|
|
40
40
|
*
|
|
41
|
-
* // main.ts
|
|
41
|
+
* // main.ts
|
|
42
|
+
* import { createTestIdBridge } from 'vite-plugin-vue-testid/plugin'
|
|
42
43
|
* import { setupAntTestIds } from 'vite-plugin-vue-testid/runtime'
|
|
43
|
-
*
|
|
44
|
+
*
|
|
45
|
+
* createApp(App).mount('#app')
|
|
46
|
+
* app.use(createTestIdBridge()) // 编译器 testid → DOM(稳定)
|
|
47
|
+
* setupAntTestIds() // 面板 / 子元素 / 兜底
|
|
44
48
|
* ```
|
|
45
49
|
*
|
|
46
50
|
* === 使用方式 C:仅编译期(无运行时,面板不会注入 testid) ===
|
|
@@ -67,10 +71,12 @@
|
|
|
67
71
|
import type { Plugin } from 'vite';
|
|
68
72
|
export type { RuntimeOptions, PanelContext, PanelInjectStrategy } from './runtime';
|
|
69
73
|
export type { TransformOptions } from './transform';
|
|
74
|
+
export type { TestIdBridgeOptions } from './plugin';
|
|
70
75
|
import { createTestIdTransforms } from './transform';
|
|
71
76
|
/** 等同于 `createTestIdTransforms()`,更短的导出名 */
|
|
72
77
|
export declare const testIdTransforms: typeof createTestIdTransforms;
|
|
73
78
|
export { createTestIdTransforms, createTestIdInjectTransform, createMultiRootFixTransform, } from './transform';
|
|
79
|
+
export { createTestIdBridge } from './plugin';
|
|
74
80
|
export interface VueTestIdPluginOptions {
|
|
75
81
|
/**
|
|
76
82
|
* testId 属性名
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqEG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAIlC,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAA;AAClF,YAAY,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AACnD,YAAY,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAA;AAEnD,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAA;AAEpD,4CAA4C;AAC5C,eAAO,MAAM,gBAAgB,+BAAyB,CAAA;AAEtD,OAAO,EACL,sBAAsB,EACtB,2BAA2B,EAC3B,2BAA2B,GAC5B,MAAM,aAAa,CAAA;AAEpB,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAA;AAI7C,MAAM,WAAW,sBAAsB;IACrC;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,KAAK,CAAC,EAAE,sBAAsB,GAAG,MAAM,CAItE;AAID;;;;;;;;;GASG;AACH,wBAAgB,wBAAwB,IAAI,MAAM,IAAI,CASrD"}
|
package/dist/index.js
CHANGED
|
@@ -143,7 +143,7 @@ function isTargetComponent(el, antPrefix, customPrefixes) {
|
|
|
143
143
|
const { tag, tagType } = el;
|
|
144
144
|
if (tagType === ElementTypes.ELEMENT && NATIVE_TAGS.has(tag)) return false;
|
|
145
145
|
if (tagType === ElementTypes.SLOT || tagType === ElementTypes.TEMPLATE) return false;
|
|
146
|
-
if (isAntdComponent(tag, antPrefix)) return
|
|
146
|
+
if (isAntdComponent(tag, antPrefix)) return true;
|
|
147
147
|
if (tagType === ElementTypes.COMPONENT) return true;
|
|
148
148
|
if (customPrefixes.some((p) => tag.startsWith(p))) return true;
|
|
149
149
|
return false;
|
|
@@ -247,6 +247,26 @@ function createTestIdTransforms(opts = {}) {
|
|
|
247
247
|
];
|
|
248
248
|
}
|
|
249
249
|
|
|
250
|
+
// src/plugin.ts
|
|
251
|
+
function createTestIdBridge(opts = {}) {
|
|
252
|
+
const attrName = opts.attributeName ?? "data-testid";
|
|
253
|
+
return {
|
|
254
|
+
install(app) {
|
|
255
|
+
app.mixin({
|
|
256
|
+
mounted() {
|
|
257
|
+
const testId = this.$attrs?.[attrName];
|
|
258
|
+
if (!testId) return;
|
|
259
|
+
if (this.$el instanceof HTMLElement) {
|
|
260
|
+
if (!this.$el.hasAttribute(attrName)) {
|
|
261
|
+
this.$el.setAttribute(attrName, testId);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
250
270
|
// src/index.ts
|
|
251
271
|
var testIdTransforms = createTestIdTransforms;
|
|
252
272
|
function vueTestIdPlugin(_opts) {
|
|
@@ -265,6 +285,7 @@ function createVueTestIdTransform() {
|
|
|
265
285
|
}
|
|
266
286
|
export {
|
|
267
287
|
createMultiRootFixTransform,
|
|
288
|
+
createTestIdBridge,
|
|
268
289
|
createTestIdInjectTransform,
|
|
269
290
|
createTestIdTransforms,
|
|
270
291
|
createVueTestIdTransform,
|
package/dist/plugin.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vue 插件:读取编译器注入的 data-testid 属性,手动写入组件的根 DOM 元素。
|
|
3
|
+
*
|
|
4
|
+
* 为什么需要:
|
|
5
|
+
* 编译器在模板中为每个 (antd + 自定义) 组件生成稳定的 {tagName}-{index} testid,
|
|
6
|
+
* 但这些 testid 通过 $attrs 传递。部分 ant-design-vue 组件(Modal、Menu 等)
|
|
7
|
+
* 使用了 inheritAttrs: false,或将其 DOM 根通过 teleport 渲染到 body 下,
|
|
8
|
+
* 导致 testid 无法自动到达目标 DOM。
|
|
9
|
+
*
|
|
10
|
+
* 本插件在组件 mounted 时读取 $attrs['data-testid'],手动写入 $el,
|
|
11
|
+
* 绕过 inheritAttrs 限制,确保编译期生成的稳定 testid 最终出现在 DOM 上。
|
|
12
|
+
*
|
|
13
|
+
* 使用方式:
|
|
14
|
+
* ```ts
|
|
15
|
+
* // main.ts
|
|
16
|
+
* import { createTestIdBridge } from 'vite-plugin-vue-testid/plugin'
|
|
17
|
+
*
|
|
18
|
+
* createApp(App).mount('#app')
|
|
19
|
+
* app.use(createTestIdBridge())
|
|
20
|
+
* app.use(createTestIdBridge({ attributeName: 'data-testid' }))
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
import type { App } from 'vue';
|
|
24
|
+
export interface TestIdBridgeOptions {
|
|
25
|
+
/**
|
|
26
|
+
* testId 属性名
|
|
27
|
+
* @default 'data-testid'
|
|
28
|
+
*/
|
|
29
|
+
attributeName?: string;
|
|
30
|
+
}
|
|
31
|
+
export declare function createTestIdBridge(opts?: TestIdBridgeOptions): {
|
|
32
|
+
install(app: App): void;
|
|
33
|
+
};
|
|
34
|
+
//# sourceMappingURL=plugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,KAAK,CAAA;AAE9B,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,wBAAgB,kBAAkB,CAAC,IAAI,GAAE,mBAAwB;iBAIhD,GAAG;EAenB"}
|
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// src/plugin.ts
|
|
2
|
+
function createTestIdBridge(opts = {}) {
|
|
3
|
+
const attrName = opts.attributeName ?? "data-testid";
|
|
4
|
+
return {
|
|
5
|
+
install(app) {
|
|
6
|
+
app.mixin({
|
|
7
|
+
mounted() {
|
|
8
|
+
const testId = this.$attrs?.[attrName];
|
|
9
|
+
if (!testId) return;
|
|
10
|
+
if (this.$el instanceof HTMLElement) {
|
|
11
|
+
if (!this.$el.hasAttribute(attrName)) {
|
|
12
|
+
this.$el.setAttribute(attrName, testId);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export {
|
|
21
|
+
createTestIdBridge
|
|
22
|
+
};
|
package/dist/runtime.d.ts
CHANGED
|
@@ -61,11 +61,14 @@ export type PanelInjectStrategy = (ctx: PanelContext) => void;
|
|
|
61
61
|
* 启动全运行时 testid 注入。
|
|
62
62
|
*
|
|
63
63
|
* 覆盖范围:
|
|
64
|
-
* - 所有 ant-design-vue 组件根元素(input / select / button / picker / ...)
|
|
65
|
-
* - teleport 弹出层面板(日期选择器 / 级联选择器 /
|
|
64
|
+
* - 所有 ant-design-vue 组件根元素(input / select / button / picker / auto-complete / mentions / transfer ...)
|
|
65
|
+
* - teleport 弹出层面板(日期选择器 / 级联选择器 / 下拉选择器 / 提及)
|
|
66
66
|
* - Menu 子元素(inheritAttrs:false 兜底)
|
|
67
67
|
* - InputNumber 内部输入框及增减按钮
|
|
68
68
|
* - Slider handle、Rate 星星、Upload 内部 file input
|
|
69
|
+
* - AutoComplete 搜索输入框及清除按钮
|
|
70
|
+
* - Mentions textarea 及下拉面板选项
|
|
71
|
+
* - Transfer 左右面板 / 列表项 / 复选框 / 移除按钮 / 操作区
|
|
69
72
|
*
|
|
70
73
|
* @returns 清理函数,调用后停止监听
|
|
71
74
|
*
|
package/dist/runtime.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IAEtB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAElB;;;;;;;;;;;;;OAaG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAEnC;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,GAAG,SAAS,CAAC,CAAA;CACzD;AAED,MAAM,WAAW,YAAY;IAC3B,kBAAkB;IAClB,SAAS,EAAE,WAAW,CAAA;IACtB,iBAAiB;IACjB,OAAO,EAAE,WAAW,GAAG,IAAI,CAAA;IAC3B,oBAAoB;IACpB,aAAa,EAAE,MAAM,CAAA;IACrB,iBAAiB;IACjB,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,MAAM,mBAAmB,GAAG,CAAC,GAAG,EAAE,YAAY,KAAK,IAAI,CAAA;
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IAEtB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAElB;;;;;;;;;;;;;OAaG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAEnC;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,GAAG,SAAS,CAAC,CAAA;CACzD;AAED,MAAM,WAAW,YAAY;IAC3B,kBAAkB;IAClB,SAAS,EAAE,WAAW,CAAA;IACtB,iBAAiB;IACjB,OAAO,EAAE,WAAW,GAAG,IAAI,CAAA;IAC3B,oBAAoB;IACpB,aAAa,EAAE,MAAM,CAAA;IACrB,iBAAiB;IACjB,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,MAAM,mBAAmB,GAAG,CAAC,GAAG,EAAE,YAAY,KAAK,IAAI,CAAA;AAi+B7D;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,eAAe,CAAC,IAAI,GAAE,cAAmB,GAAG,MAAM,IAAI,CA4FrE;AAED;;GAEG;AACH,eAAO,MAAM,uBAAuB,wBAAkB,CAAA;AAMtD;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,GAAE,cAAmB,GAAG,IAAI,CA+CtE"}
|
package/dist/runtime.js
CHANGED
|
@@ -8,10 +8,14 @@ function buildComponentMatchers(pc) {
|
|
|
8
8
|
{ selector: sel(pc, "input-affix-wrapper"), prefix: "a-input" },
|
|
9
9
|
{ selector: sel(pc, "input-textarea"), prefix: "a-textarea" },
|
|
10
10
|
{ selector: sel(pc, "input-number"), prefix: "a-input-number" },
|
|
11
|
+
// ── AutoComplete(必须在 select 之前,因为同时也有 .ant-select 类)──
|
|
12
|
+
{ selector: sel(pc, "select-auto-complete"), prefix: "a-auto-complete", withId: true },
|
|
11
13
|
// ── 选择器 ──
|
|
12
14
|
{ selector: sel(pc, "select"), prefix: "a-select", withId: true },
|
|
13
15
|
{ selector: sel(pc, "cascader"), prefix: "a-cascader", withId: true },
|
|
14
16
|
{ selector: sel(pc, "tree-select"), prefix: "a-tree-select" },
|
|
17
|
+
// ── Mentions ──
|
|
18
|
+
{ selector: sel(pc, "mentions"), prefix: "a-mentions", withId: true },
|
|
15
19
|
// ── 日期/时间 ──
|
|
16
20
|
{ selector: sel(pc, "picker"), prefix: "a-date-picker", withId: true },
|
|
17
21
|
// ── 表单组件 ──
|
|
@@ -32,6 +36,9 @@ function buildComponentMatchers(pc) {
|
|
|
32
36
|
{ selector: sel(pc, "menu"), prefix: "a-menu" },
|
|
33
37
|
// ── 标签页 ──
|
|
34
38
|
{ selector: sel(pc, "tabs"), prefix: "a-tabs" },
|
|
39
|
+
{ selector: sel(pc, "pagination"), prefix: "a-pagination" },
|
|
40
|
+
// ── Transfer ──
|
|
41
|
+
{ selector: sel(pc, "transfer"), prefix: "a-transfer" },
|
|
35
42
|
// ── 纯 input / textarea(必须排在最后)──
|
|
36
43
|
{ selector: `input.${cls(pc, "input")}`, prefix: "a-input" },
|
|
37
44
|
{ selector: `textarea.${cls(pc, "input")}`, prefix: "a-textarea" }
|
|
@@ -272,11 +279,42 @@ function makeInjectSelectPanel(pc) {
|
|
|
272
279
|
}
|
|
273
280
|
};
|
|
274
281
|
}
|
|
282
|
+
function makeInjectMentionsPanel(pc) {
|
|
283
|
+
return (ctx) => {
|
|
284
|
+
const { container, triggerTestId, attrName } = ctx;
|
|
285
|
+
const prefix = `${triggerTestId}-dropdown`;
|
|
286
|
+
container.setAttribute(attrName, prefix);
|
|
287
|
+
const injectAllItems = () => {
|
|
288
|
+
const items = container.querySelectorAll(sel(pc, "mentions-dropdown-menu-item"));
|
|
289
|
+
items.forEach((item) => {
|
|
290
|
+
const el = item;
|
|
291
|
+
if (hasAttrCompat(el, attrName)) return;
|
|
292
|
+
const text = el.textContent?.trim() || "";
|
|
293
|
+
if (text) {
|
|
294
|
+
el.setAttribute(attrName, `${prefix}-option-${text}`);
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
};
|
|
298
|
+
injectAllItems();
|
|
299
|
+
if (!_observedContainers.has(container)) {
|
|
300
|
+
_observedContainers.add(container);
|
|
301
|
+
let timer = null;
|
|
302
|
+
const observer = new MutationObserver(() => {
|
|
303
|
+
if (timer !== null) clearTimeout(timer);
|
|
304
|
+
timer = setTimeout(() => {
|
|
305
|
+
injectAllItems();
|
|
306
|
+
}, 50);
|
|
307
|
+
});
|
|
308
|
+
observer.observe(container, { childList: true, subtree: true });
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
}
|
|
275
312
|
function buildPanels(pc) {
|
|
276
313
|
return {
|
|
277
314
|
[sel(pc, "picker-dropdown")]: makeInjectPickerPanel(pc),
|
|
278
315
|
[sel(pc, "cascader-dropdown")]: makeInjectCascaderPanel(pc),
|
|
279
|
-
[sel(pc, "select-dropdown")]: makeInjectSelectPanel(pc)
|
|
316
|
+
[sel(pc, "select-dropdown")]: makeInjectSelectPanel(pc),
|
|
317
|
+
[sel(pc, "mentions-dropdown")]: makeInjectMentionsPanel(pc)
|
|
280
318
|
};
|
|
281
319
|
}
|
|
282
320
|
function injectMenuItems(attrName, root = document, pc) {
|
|
@@ -348,6 +386,14 @@ function findActiveTrigger(attrName, pc) {
|
|
|
348
386
|
const found = resolveTriggerInContainer(cascader, attrName);
|
|
349
387
|
if (found) return found;
|
|
350
388
|
}
|
|
389
|
+
const mentionsDropdown = document.querySelector(sel(pc, "mentions-dropdown"));
|
|
390
|
+
if (mentionsDropdown) {
|
|
391
|
+
const mentionsRoot = document.querySelector(sel(pc, "mentions"));
|
|
392
|
+
if (mentionsRoot) {
|
|
393
|
+
const found = resolveTriggerInContainer(mentionsRoot, attrName);
|
|
394
|
+
if (found) return found;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
351
397
|
const expanded = document.querySelector(
|
|
352
398
|
`[id][aria-expanded="true"], [data-testid][aria-expanded="true"], [data-testId][aria-expanded="true"]`
|
|
353
399
|
);
|
|
@@ -355,6 +401,23 @@ function findActiveTrigger(attrName, pc) {
|
|
|
355
401
|
return null;
|
|
356
402
|
}
|
|
357
403
|
function injectSubElements(root, attrName, pc) {
|
|
404
|
+
const inputWrapperSelectors = [
|
|
405
|
+
sel(pc, "input-affix-wrapper"),
|
|
406
|
+
sel(pc, "input-search"),
|
|
407
|
+
sel(pc, "input-textarea")
|
|
408
|
+
];
|
|
409
|
+
for (const wrapperSel of inputWrapperSelectors) {
|
|
410
|
+
const wrappers = root.matches(`${wrapperSel}[${attrName}]`) ? [root] : Array.from(root.querySelectorAll(`${wrapperSel}[${attrName}]`));
|
|
411
|
+
for (const wrapper of wrappers) {
|
|
412
|
+
if (!(wrapper instanceof HTMLElement)) continue;
|
|
413
|
+
const testId = getAttrCompat(wrapper, attrName) || "";
|
|
414
|
+
if (!testId) continue;
|
|
415
|
+
const innerInput = wrapper.querySelector('input:not([type="file"]), textarea');
|
|
416
|
+
if (innerInput && !hasAttrCompat(innerInput, attrName)) {
|
|
417
|
+
innerInput.setAttribute(attrName, `${testId}-input`);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
358
421
|
const inputNumbers = root.matches(`${sel(pc, "input-number")}[${attrName}]`) ? [root] : Array.from(root.querySelectorAll(`${sel(pc, "input-number")}[${attrName}]`));
|
|
359
422
|
for (const wrapper of inputNumbers) {
|
|
360
423
|
if (!(wrapper instanceof HTMLElement)) continue;
|
|
@@ -397,6 +460,22 @@ function injectSubElements(root, attrName, pc) {
|
|
|
397
460
|
}
|
|
398
461
|
});
|
|
399
462
|
}
|
|
463
|
+
const selects = root.matches(`${sel(pc, "select")}[${attrName}]`) ? [root] : Array.from(root.querySelectorAll(`${sel(pc, "select")}[${attrName}]`));
|
|
464
|
+
for (const select of selects) {
|
|
465
|
+
if (!(select instanceof HTMLElement)) continue;
|
|
466
|
+
const testId = getAttrCompat(select, attrName) || "";
|
|
467
|
+
if (!testId) continue;
|
|
468
|
+
const removeIcons = select.querySelectorAll(sel(pc, "select-selection-item-remove"));
|
|
469
|
+
removeIcons.forEach((icon) => {
|
|
470
|
+
const el = icon;
|
|
471
|
+
if (hasAttrCompat(el, attrName)) return;
|
|
472
|
+
const item = el.closest(sel(pc, "select-selection-item"));
|
|
473
|
+
const text = (item?.textContent || "").replace(el.textContent || "", "").trim().replace(/\s+/g, "-");
|
|
474
|
+
if (text) {
|
|
475
|
+
el.setAttribute(attrName, `${testId}-item-remove-${text}`);
|
|
476
|
+
}
|
|
477
|
+
});
|
|
478
|
+
}
|
|
400
479
|
const uploadByTestId = root.matches(`${sel(pc, "upload")}[${attrName}]`) ? [root] : Array.from(root.querySelectorAll(`${sel(pc, "upload")}[${attrName}]`));
|
|
401
480
|
const uploadById = uploadByTestId.length === 0 ? root.matches(`${sel(pc, "upload")}[id]`) ? [root] : Array.from(root.querySelectorAll(`${sel(pc, "upload")}[id]`)) : [];
|
|
402
481
|
for (const upload of [...uploadByTestId, ...uploadById]) {
|
|
@@ -447,6 +526,150 @@ function injectTabsSubElements(root, attrName, pc) {
|
|
|
447
526
|
}
|
|
448
527
|
}
|
|
449
528
|
}
|
|
529
|
+
function injectPaginationSubElements(root, attrName, pc) {
|
|
530
|
+
const paginations = root.matches(`${sel(pc, "pagination")}[${attrName}]`) ? [root] : Array.from(root.querySelectorAll(`${sel(pc, "pagination")}[${attrName}]`));
|
|
531
|
+
for (const pagination of paginations) {
|
|
532
|
+
if (!(pagination instanceof HTMLElement)) continue;
|
|
533
|
+
const testId = getAttrCompat(pagination, attrName) || "";
|
|
534
|
+
if (!testId) continue;
|
|
535
|
+
const prevEl = pagination.querySelector(sel(pc, "pagination-prev"));
|
|
536
|
+
if (prevEl && !hasAttrCompat(prevEl, attrName)) {
|
|
537
|
+
prevEl.setAttribute(attrName, `${testId}-prev`);
|
|
538
|
+
}
|
|
539
|
+
const nextEl = pagination.querySelector(sel(pc, "pagination-next"));
|
|
540
|
+
if (nextEl && !hasAttrCompat(nextEl, attrName)) {
|
|
541
|
+
nextEl.setAttribute(attrName, `${testId}-next`);
|
|
542
|
+
}
|
|
543
|
+
pagination.querySelectorAll(sel(pc, "pagination-item")).forEach((item) => {
|
|
544
|
+
const el = item;
|
|
545
|
+
if (hasAttrCompat(el, attrName)) return;
|
|
546
|
+
const pageNum = el.textContent?.trim() || "";
|
|
547
|
+
if (pageNum) {
|
|
548
|
+
el.setAttribute(attrName, `${testId}-item-${pageNum}`);
|
|
549
|
+
}
|
|
550
|
+
});
|
|
551
|
+
const jumpPrev = pagination.querySelector(sel(pc, "pagination-jump-prev"));
|
|
552
|
+
if (jumpPrev && !hasAttrCompat(jumpPrev, attrName)) {
|
|
553
|
+
jumpPrev.setAttribute(attrName, `${testId}-jump-prev`);
|
|
554
|
+
}
|
|
555
|
+
const jumpNext = pagination.querySelector(sel(pc, "pagination-jump-next"));
|
|
556
|
+
if (jumpNext && !hasAttrCompat(jumpNext, attrName)) {
|
|
557
|
+
jumpNext.setAttribute(attrName, `${testId}-jump-next`);
|
|
558
|
+
}
|
|
559
|
+
const sizeChanger = pagination.querySelector(sel(pc, "pagination-options-size-changer"));
|
|
560
|
+
if (sizeChanger && !hasAttrCompat(sizeChanger, attrName)) {
|
|
561
|
+
sizeChanger.setAttribute(attrName, `${testId}-size-changer`);
|
|
562
|
+
}
|
|
563
|
+
const quickJumper = pagination.querySelector(sel(pc, "pagination-options-quick-jumper"));
|
|
564
|
+
if (quickJumper) {
|
|
565
|
+
const tryQuickJumperInput = () => {
|
|
566
|
+
const input = quickJumper.querySelector("input");
|
|
567
|
+
if (input && !hasAttrCompat(input, attrName)) {
|
|
568
|
+
input.setAttribute(attrName, `${testId}-quick-jumper`);
|
|
569
|
+
return true;
|
|
570
|
+
}
|
|
571
|
+
return false;
|
|
572
|
+
};
|
|
573
|
+
if (!tryQuickJumperInput()) {
|
|
574
|
+
const subObs = new MutationObserver(() => {
|
|
575
|
+
if (tryQuickJumperInput()) subObs.disconnect();
|
|
576
|
+
});
|
|
577
|
+
subObs.observe(quickJumper, { childList: true, subtree: true });
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
const totalText = pagination.querySelector(sel(pc, "pagination-total-text"));
|
|
581
|
+
if (totalText && !hasAttrCompat(totalText, attrName)) {
|
|
582
|
+
totalText.setAttribute(attrName, `${testId}-total`);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
function injectAutoCompleteSubElements(root, attrName, pc) {
|
|
587
|
+
const wrappers = root.matches(`${sel(pc, "select-auto-complete")}[${attrName}]`) ? [root] : Array.from(root.querySelectorAll(`${sel(pc, "select-auto-complete")}[${attrName}]`));
|
|
588
|
+
for (const wrapper of wrappers) {
|
|
589
|
+
if (!(wrapper instanceof HTMLElement)) continue;
|
|
590
|
+
const testId = getAttrCompat(wrapper, attrName) || "";
|
|
591
|
+
if (!testId) continue;
|
|
592
|
+
const searchInput = wrapper.querySelector(
|
|
593
|
+
`${sel(pc, "select-selection-search")} input`
|
|
594
|
+
);
|
|
595
|
+
if (searchInput && !hasAttrCompat(searchInput, attrName)) {
|
|
596
|
+
searchInput.setAttribute(attrName, `${testId}-input`);
|
|
597
|
+
}
|
|
598
|
+
const clearBtn = wrapper.querySelector(sel(pc, "select-clear"));
|
|
599
|
+
if (clearBtn && !hasAttrCompat(clearBtn, attrName)) {
|
|
600
|
+
clearBtn.setAttribute(attrName, `${testId}-clear`);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
function injectMentionsSubElements(root, attrName, pc) {
|
|
605
|
+
const mentions = root.matches(`${sel(pc, "mentions")}[${attrName}]`) ? [root] : Array.from(root.querySelectorAll(`${sel(pc, "mentions")}[${attrName}]`));
|
|
606
|
+
for (const wrapper of mentions) {
|
|
607
|
+
if (!(wrapper instanceof HTMLElement)) continue;
|
|
608
|
+
const testId = getAttrCompat(wrapper, attrName) || "";
|
|
609
|
+
if (!testId) continue;
|
|
610
|
+
const textarea = wrapper.querySelector(":scope > textarea");
|
|
611
|
+
if (textarea && !hasAttrCompat(textarea, attrName)) {
|
|
612
|
+
textarea.setAttribute(attrName, `${testId}-input`);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
function injectTransferSubElements(root, attrName, pc) {
|
|
617
|
+
const transfers = root.matches(`${sel(pc, "transfer")}[${attrName}]`) ? [root] : Array.from(root.querySelectorAll(`${sel(pc, "transfer")}[${attrName}]`));
|
|
618
|
+
for (const transfer of transfers) {
|
|
619
|
+
if (!(transfer instanceof HTMLElement)) continue;
|
|
620
|
+
const testId = getAttrCompat(transfer, attrName) || "";
|
|
621
|
+
if (!testId) continue;
|
|
622
|
+
const lists = transfer.querySelectorAll(`:scope > ${sel(pc, "transfer-list")}`);
|
|
623
|
+
const directions = ["left", "right"];
|
|
624
|
+
for (let i = 0; i < Math.min(lists.length, 2); i++) {
|
|
625
|
+
const list = lists[i];
|
|
626
|
+
const dir = directions[i];
|
|
627
|
+
const listTestId = `${testId}-list-${dir}`;
|
|
628
|
+
if (!hasAttrCompat(list, attrName)) {
|
|
629
|
+
list.setAttribute(attrName, listTestId);
|
|
630
|
+
}
|
|
631
|
+
const headerCheckbox = list.querySelector(
|
|
632
|
+
`${sel(pc, "transfer-list-header")} ${sel(pc, "transfer-list-checkbox")}`
|
|
633
|
+
);
|
|
634
|
+
if (headerCheckbox && !hasAttrCompat(headerCheckbox, attrName)) {
|
|
635
|
+
headerCheckbox.setAttribute(attrName, `${listTestId}-check-all`);
|
|
636
|
+
}
|
|
637
|
+
const searchInput = list.querySelector(
|
|
638
|
+
`${sel(pc, "transfer-list-body-search-wrapper")} input`
|
|
639
|
+
);
|
|
640
|
+
if (searchInput && !hasAttrCompat(searchInput, attrName)) {
|
|
641
|
+
searchInput.setAttribute(attrName, `${listTestId}-search`);
|
|
642
|
+
}
|
|
643
|
+
const items = list.querySelectorAll(sel(pc, "transfer-list-content-item"));
|
|
644
|
+
items.forEach((item) => {
|
|
645
|
+
const el = item;
|
|
646
|
+
if (hasAttrCompat(el, attrName)) return;
|
|
647
|
+
const text = (el.querySelector(sel(pc, "transfer-list-content-item-text"))?.textContent || el.textContent || "").trim().replace(/\s+/g, "-");
|
|
648
|
+
if (text) {
|
|
649
|
+
el.setAttribute(attrName, `${listTestId}-item-${text}`);
|
|
650
|
+
}
|
|
651
|
+
const checkbox = el.querySelector(
|
|
652
|
+
sel(pc, "transfer-list-checkbox")
|
|
653
|
+
);
|
|
654
|
+
if (checkbox && !hasAttrCompat(checkbox, attrName) && text) {
|
|
655
|
+
checkbox.setAttribute(attrName, `${listTestId}-item-check-${text}`);
|
|
656
|
+
}
|
|
657
|
+
const removeBtn = el.querySelector(
|
|
658
|
+
sel(pc, "transfer-list-content-item-remove")
|
|
659
|
+
);
|
|
660
|
+
if (removeBtn && !hasAttrCompat(removeBtn, attrName) && text) {
|
|
661
|
+
removeBtn.setAttribute(attrName, `${listTestId}-item-remove-${text}`);
|
|
662
|
+
}
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
const operation = transfer.querySelector(
|
|
666
|
+
`:scope > ${sel(pc, "transfer-operation")}`
|
|
667
|
+
);
|
|
668
|
+
if (operation && !hasAttrCompat(operation, attrName)) {
|
|
669
|
+
operation.setAttribute(attrName, `${testId}-operation`);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
}
|
|
450
673
|
function setupAntTestIds(opts = {}) {
|
|
451
674
|
const attrName = opts.attributeName ?? "data-testid";
|
|
452
675
|
const pc = opts.prefixCls ?? "ant";
|
|
@@ -474,6 +697,10 @@ function setupAntTestIds(opts = {}) {
|
|
|
474
697
|
}
|
|
475
698
|
injectSubElements(node, attrName, pc);
|
|
476
699
|
injectTabsSubElements(node, attrName, pc);
|
|
700
|
+
injectPaginationSubElements(node, attrName, pc);
|
|
701
|
+
injectAutoCompleteSubElements(node, attrName, pc);
|
|
702
|
+
injectMentionsSubElements(node, attrName, pc);
|
|
703
|
+
injectTransferSubElements(node, attrName, pc);
|
|
477
704
|
}
|
|
478
705
|
let pending = false;
|
|
479
706
|
let pendingNodes = [];
|
|
@@ -525,7 +752,11 @@ function injectCurrentDropdowns(opts = {}) {
|
|
|
525
752
|
}
|
|
526
753
|
injectSubElements(document.body, attrName, pc);
|
|
527
754
|
injectTabsSubElements(document.body, attrName, pc);
|
|
755
|
+
injectPaginationSubElements(document.body, attrName, pc);
|
|
528
756
|
injectMenuItems(attrName, document, pc);
|
|
757
|
+
injectAutoCompleteSubElements(document.body, attrName, pc);
|
|
758
|
+
injectMentionsSubElements(document.body, attrName, pc);
|
|
759
|
+
injectTransferSubElements(document.body, attrName, pc);
|
|
529
760
|
}
|
|
530
761
|
function mergeMatchers(defaults, customs) {
|
|
531
762
|
if (!customs) return defaults;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vite-plugin-vue-testid",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.4",
|
|
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",
|
|
@@ -14,6 +14,10 @@
|
|
|
14
14
|
"./runtime": {
|
|
15
15
|
"import": "./dist/runtime.js",
|
|
16
16
|
"types": "./dist/runtime.d.ts"
|
|
17
|
+
},
|
|
18
|
+
"./plugin": {
|
|
19
|
+
"import": "./dist/plugin.js",
|
|
20
|
+
"types": "./dist/plugin.d.ts"
|
|
17
21
|
}
|
|
18
22
|
},
|
|
19
23
|
"files": [
|