@atomic-testing/vue-3 0.77.0 → 0.79.0
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.md +158 -1
- package/dist/{index.js → index.cjs} +27 -35
- package/dist/index.cjs.map +1 -0
- package/dist/{index.d.ts → index.d.cts} +12 -3
- package/dist/index.d.mts +11 -2
- package/dist/index.mjs +19 -4
- package/dist/index.mjs.map +1 -1
- package/package.json +16 -7
- package/src/createTestEngine.ts +45 -5
- package/src/index.ts +1 -1
- package/src/types.ts +11 -0
- package/dist/index.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,9 +1,166 @@
|
|
|
1
1
|
# @atomic-testing/vue-3
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Vue 3 test adapter** that extends Atomic Testing's component driver pattern to Vue applications. It enables testing Vue components using the same high-level semantic APIs used across React, Playwright, and DOM environments.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The `@atomic-testing/vue-3` package brings Atomic Testing's "component driver" abstraction to Vue 3, maintaining architectural consistency while respecting Vue's unique reactivity model. It provides reliable, framework-agnostic testing by automatically handling Vue's async reactivity system.
|
|
8
|
+
|
|
9
|
+
### Key Features
|
|
10
|
+
|
|
11
|
+
✅ **Unified API**: Vue components use identical component drivers as other frameworks
|
|
12
|
+
✅ **Reactivity Safety**: Properly handles Vue's async reactivity system using `nextTick()`
|
|
13
|
+
✅ **Cross-Framework**: Test patterns learned in React translate directly to Vue
|
|
14
|
+
✅ **E2E Ready**: Same drivers work in both DOM and Playwright environments
|
|
15
|
+
✅ **Flexible Components**: Supports both compiled Vue components and template-based definitions
|
|
4
16
|
|
|
5
17
|
## Installation
|
|
6
18
|
|
|
7
19
|
```bash
|
|
8
20
|
pnpm add @atomic-testing/vue-3
|
|
9
21
|
```
|
|
22
|
+
|
|
23
|
+
## Architecture
|
|
24
|
+
|
|
25
|
+
### VueInteractor
|
|
26
|
+
|
|
27
|
+
Extends the base DOMInteractor with Vue-specific reactivity handling:
|
|
28
|
+
|
|
29
|
+
- Automatically calls `nextTick()` after each interaction
|
|
30
|
+
- Ensures Vue's reactive updates complete before proceeding
|
|
31
|
+
- Provides reliable, predictable testing experience
|
|
32
|
+
|
|
33
|
+
### Test Engine Factory
|
|
34
|
+
|
|
35
|
+
Two main functions for different testing scenarios:
|
|
36
|
+
|
|
37
|
+
- **`createTestEngine()`**: Renders and tests Vue components
|
|
38
|
+
- **`createRenderedTestEngine()`**: Tests already-rendered components (e.g., Storybook)
|
|
39
|
+
|
|
40
|
+
### Framework Integration
|
|
41
|
+
|
|
42
|
+
- Uses `@testing-library/vue` for component rendering with fallback strategies
|
|
43
|
+
- Maintains the same ComponentDriver API as React adapters
|
|
44
|
+
- Enables cross-framework driver reuse
|
|
45
|
+
|
|
46
|
+
## Usage
|
|
47
|
+
|
|
48
|
+
### Basic Example
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
import { HTMLButtonDriver } from '@atomic-testing/component-driver-html';
|
|
52
|
+
import { byDataTestId } from '@atomic-testing/core';
|
|
53
|
+
import { createTestEngine } from '@atomic-testing/vue-3';
|
|
54
|
+
|
|
55
|
+
import CounterComponent from './Counter.vue';
|
|
56
|
+
|
|
57
|
+
const engine = createTestEngine(CounterComponent, {
|
|
58
|
+
incrementButton: {
|
|
59
|
+
locator: byDataTestId('increment'),
|
|
60
|
+
driver: HTMLButtonDriver,
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Test interaction - automatically handles Vue reactivity
|
|
65
|
+
await engine.parts.incrementButton.click();
|
|
66
|
+
expect(await engine.parts.incrementButton.getText()).toBe('Count: 1');
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### SFC-Like Components
|
|
70
|
+
|
|
71
|
+
For simplified testing, you can define components using template strings:
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
import { createTestEngine } from '@atomic-testing/vue-3';
|
|
75
|
+
|
|
76
|
+
const TestComponent = {
|
|
77
|
+
template: `
|
|
78
|
+
<div>
|
|
79
|
+
<button @click="increment" data-testid="counter">
|
|
80
|
+
Count: {{ count }}
|
|
81
|
+
</button>
|
|
82
|
+
</div>
|
|
83
|
+
`,
|
|
84
|
+
setup() {
|
|
85
|
+
const count = ref(0);
|
|
86
|
+
const increment = () => count.value++;
|
|
87
|
+
return { count, increment };
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const engine = createTestEngine(TestComponent, {
|
|
92
|
+
button: { locator: byDataTestId('counter'), driver: HTMLButtonDriver },
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Testing Rendered Components
|
|
97
|
+
|
|
98
|
+
For components already rendered in the DOM (e.g., Storybook):
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
import { createRenderedTestEngine } from '@atomic-testing/vue-3';
|
|
102
|
+
|
|
103
|
+
const engine = createRenderedTestEngine({
|
|
104
|
+
button: { locator: byDataTestId('counter'), driver: HTMLButtonDriver },
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// Test the already-rendered component
|
|
108
|
+
await engine.parts.button.click();
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Cross-Framework Compatibility
|
|
112
|
+
|
|
113
|
+
The same component drivers work across all Atomic Testing adapters:
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
// Works identically in React, Vue, and Playwright
|
|
117
|
+
const parts = {
|
|
118
|
+
emailInput: { locator: byDataTestId('email'), driver: TextFieldDriver },
|
|
119
|
+
submitButton: { locator: byDataTestId('submit'), driver: ButtonDriver }
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// React
|
|
123
|
+
const reactEngine = createTestEngine(<LoginForm />, parts);
|
|
124
|
+
|
|
125
|
+
// Vue 3
|
|
126
|
+
const vueEngine = createTestEngine(LoginFormVue, parts);
|
|
127
|
+
|
|
128
|
+
// Playwright
|
|
129
|
+
const playwrightEngine = createTestEngine(page, parts);
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Integration with Vue Ecosystem
|
|
133
|
+
|
|
134
|
+
### Vue Testing Library
|
|
135
|
+
|
|
136
|
+
The adapter uses `@testing-library/vue` internally with intelligent fallback strategies for component rendering.
|
|
137
|
+
|
|
138
|
+
### Vue Reactivity
|
|
139
|
+
|
|
140
|
+
All interactions automatically wait for Vue's reactivity cycle to complete:
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
// This automatically calls nextTick() after the click
|
|
144
|
+
await engine.parts.button.click();
|
|
145
|
+
|
|
146
|
+
// Reactive state is guaranteed to be updated here
|
|
147
|
+
const updatedText = await engine.parts.button.getText();
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### TypeScript Support
|
|
151
|
+
|
|
152
|
+
Full TypeScript support with proper Vue component typing:
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
import type { Component } from 'vue';
|
|
156
|
+
|
|
157
|
+
const engine = createTestEngine<Component>(MyVueComponent, sceneParts);
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Why Use @atomic-testing/vue-3?
|
|
161
|
+
|
|
162
|
+
1. **Consistent Testing Patterns**: Learn once, test everywhere - same patterns work across React, Vue, and E2E
|
|
163
|
+
2. **Reliable Vue Integration**: Proper handling of Vue's reactivity ensures tests aren't flaky
|
|
164
|
+
3. **High-Level APIs**: Focus on user interactions, not DOM implementation details
|
|
165
|
+
4. **Framework Agnostic Drivers**: Reuse component drivers across different frameworks and test environments
|
|
166
|
+
5. **Future-Proof**: As your app grows or changes frameworks, your test patterns remain consistent
|
|
@@ -1,33 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __copyProps = (to, from, except, desc) => {
|
|
9
|
-
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
10
|
-
key = keys[i];
|
|
11
|
-
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
12
|
-
get: ((k) => from[k]).bind(null, key),
|
|
13
|
-
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
14
|
-
});
|
|
15
|
-
}
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
19
|
-
value: mod,
|
|
20
|
-
enumerable: true
|
|
21
|
-
}) : target, mod));
|
|
22
|
-
|
|
23
|
-
//#endregion
|
|
24
|
-
const __atomic_testing_core = __toESM(require("@atomic-testing/core"));
|
|
25
|
-
const __testing_library_vue = __toESM(require("@testing-library/vue"));
|
|
26
|
-
const vue = __toESM(require("vue"));
|
|
27
|
-
const __atomic_testing_dom_core = __toESM(require("@atomic-testing/dom-core"));
|
|
1
|
+
let _atomic_testing_core = require("@atomic-testing/core");
|
|
2
|
+
let _testing_library_vue = require("@testing-library/vue");
|
|
3
|
+
let vue = require("vue");
|
|
4
|
+
let _atomic_testing_dom_core = require("@atomic-testing/dom-core");
|
|
28
5
|
|
|
29
6
|
//#region src/VueInteractor.ts
|
|
30
|
-
var VueInteractor = class VueInteractor extends
|
|
7
|
+
var VueInteractor = class VueInteractor extends _atomic_testing_dom_core.DOMInteractor {
|
|
31
8
|
async flush() {
|
|
32
9
|
await (0, vue.nextTick)();
|
|
33
10
|
}
|
|
@@ -83,7 +60,7 @@ var VueInteractor = class VueInteractor extends __atomic_testing_dom_core.DOMInt
|
|
|
83
60
|
await super.wait(ms);
|
|
84
61
|
await this.flush();
|
|
85
62
|
}
|
|
86
|
-
async waitUntilComponentState(locator, option =
|
|
63
|
+
async waitUntilComponentState(locator, option = _atomic_testing_core.defaultWaitForOption) {
|
|
87
64
|
await super.waitUntilComponentState(locator, option);
|
|
88
65
|
await this.flush();
|
|
89
66
|
}
|
|
@@ -104,6 +81,21 @@ function getNextRootElementId() {
|
|
|
104
81
|
return `${_rootId++}`;
|
|
105
82
|
}
|
|
106
83
|
const rootElementAttributeName = "data-atomic-testing-vue";
|
|
84
|
+
function isSFCLikeObject(component) {
|
|
85
|
+
return component && typeof component === "object" && "template" in component && typeof component.template === "string";
|
|
86
|
+
}
|
|
87
|
+
function createComponentFromSFCLike(sfcObj) {
|
|
88
|
+
const componentOptions = {
|
|
89
|
+
name: sfcObj.name || "SFCComponent",
|
|
90
|
+
template: sfcObj.template
|
|
91
|
+
};
|
|
92
|
+
if (sfcObj.props) componentOptions.props = sfcObj.props;
|
|
93
|
+
if (sfcObj.setup) componentOptions.setup = sfcObj.setup;
|
|
94
|
+
if (sfcObj.data) componentOptions.data = sfcObj.data;
|
|
95
|
+
if (sfcObj.methods) componentOptions.methods = sfcObj.methods;
|
|
96
|
+
if (sfcObj.computed) componentOptions.computed = sfcObj.computed;
|
|
97
|
+
return (0, vue.defineComponent)(componentOptions);
|
|
98
|
+
}
|
|
107
99
|
function createTestEngine(component, partDefinitions, option) {
|
|
108
100
|
const rootEl = option?.rootElement ?? document.body;
|
|
109
101
|
const container = rootEl.appendChild(document.createElement("div"));
|
|
@@ -111,11 +103,11 @@ function createTestEngine(component, partDefinitions, option) {
|
|
|
111
103
|
container.setAttribute(rootElementAttributeName, rootId);
|
|
112
104
|
let unmount;
|
|
113
105
|
let app;
|
|
106
|
+
const compiledComponent = isSFCLikeObject(component) ? createComponentFromSFCLike(component) : component;
|
|
114
107
|
try {
|
|
115
|
-
|
|
116
|
-
unmount = renderResult.unmount;
|
|
108
|
+
unmount = (0, _testing_library_vue.render)(compiledComponent, { container }).unmount;
|
|
117
109
|
} catch (_error) {
|
|
118
|
-
app = (0, vue.createApp)(
|
|
110
|
+
app = (0, vue.createApp)(compiledComponent);
|
|
119
111
|
app.mount(container);
|
|
120
112
|
unmount = () => {
|
|
121
113
|
if (app) app.unmount();
|
|
@@ -126,7 +118,7 @@ function createTestEngine(component, partDefinitions, option) {
|
|
|
126
118
|
rootEl.removeChild(container);
|
|
127
119
|
return Promise.resolve();
|
|
128
120
|
};
|
|
129
|
-
return new
|
|
121
|
+
return new _atomic_testing_core.TestEngine((0, _atomic_testing_core.byAttribute)(rootElementAttributeName, rootId), new VueInteractor(), { parts: partDefinitions }, cleanup);
|
|
130
122
|
}
|
|
131
123
|
function createRenderedTestEngine(rootElement, partDefinitions, _option) {
|
|
132
124
|
const rootId = getNextRootElementId();
|
|
@@ -135,11 +127,11 @@ function createRenderedTestEngine(rootElement, partDefinitions, _option) {
|
|
|
135
127
|
rootElement.removeAttribute(rootElementAttributeName);
|
|
136
128
|
return Promise.resolve();
|
|
137
129
|
};
|
|
138
|
-
return new
|
|
130
|
+
return new _atomic_testing_core.TestEngine((0, _atomic_testing_core.byAttribute)(rootElementAttributeName, rootId), new VueInteractor(), { parts: partDefinitions }, cleanup);
|
|
139
131
|
}
|
|
140
132
|
|
|
141
133
|
//#endregion
|
|
142
134
|
exports.VueInteractor = VueInteractor;
|
|
143
135
|
exports.createRenderedTestEngine = createRenderedTestEngine;
|
|
144
136
|
exports.createTestEngine = createTestEngine;
|
|
145
|
-
//# sourceMappingURL=index.
|
|
137
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["DOMInteractor","defaultWaitForOption","componentOptions: any","unmount: () => void","app: App","TestEngine"],"sources":["../src/VueInteractor.ts","../src/createTestEngine.ts"],"sourcesContent":["import {\n ClickOption,\n defaultWaitForOption,\n EnterTextOption,\n FocusOption,\n HoverOption,\n Interactor,\n MouseDownOption,\n MouseEnterOption,\n MouseLeaveOption,\n MouseMoveOption,\n MouseOutOption,\n MouseUpOption,\n PartLocator,\n WaitForOption,\n WaitUntilOption,\n} from '@atomic-testing/core';\nimport { DOMInteractor } from '@atomic-testing/dom-core';\nimport { nextTick } from 'vue';\n\nexport class VueInteractor extends DOMInteractor {\n private async flush() {\n await nextTick();\n }\n\n override async enterText(locator: PartLocator, text: string, option?: Partial<EnterTextOption>): Promise<void> {\n await super.enterText(locator, text, option);\n await this.flush();\n }\n\n override async click(locator: PartLocator, option?: Partial<ClickOption>): Promise<void> {\n await super.click(locator, option);\n await this.flush();\n }\n\n override async hover(locator: PartLocator, option?: Partial<HoverOption>): Promise<void> {\n await super.hover(locator, option);\n await this.flush();\n }\n\n async mouseMove(locator: PartLocator, option?: Partial<MouseMoveOption>): Promise<void> {\n await super.mouseMove(locator, option);\n await this.flush();\n }\n\n async mouseDown(locator: PartLocator, option?: Partial<MouseDownOption>): Promise<void> {\n await super.mouseDown(locator, option);\n await this.flush();\n }\n\n async mouseUp(locator: PartLocator, option?: Partial<MouseUpOption>): Promise<void> {\n await super.mouseUp(locator, option);\n await this.flush();\n }\n\n async mouseOver(locator: PartLocator, option?: Partial<HoverOption>): Promise<void> {\n await super.mouseOver(locator, option);\n await this.flush();\n }\n\n async mouseOut(locator: PartLocator, option?: Partial<MouseOutOption>): Promise<void> {\n await super.mouseOut(locator, option);\n await this.flush();\n }\n\n async mouseEnter(locator: PartLocator, option?: Partial<MouseEnterOption>): Promise<void> {\n await super.mouseEnter(locator, option);\n await this.flush();\n }\n\n async mouseLeave(locator: PartLocator, option?: Partial<MouseLeaveOption>): Promise<void> {\n await super.mouseLeave(locator, option);\n await this.flush();\n }\n\n async focus(locator: PartLocator, option?: Partial<FocusOption>): Promise<void> {\n await super.focus(locator, option);\n await this.flush();\n }\n\n override async selectOptionValue(locator: PartLocator, values: string[]): Promise<void> {\n await super.selectOptionValue(locator, values);\n await this.flush();\n }\n\n override async wait(ms: number): Promise<void> {\n await super.wait(ms);\n await this.flush();\n }\n\n override async waitUntilComponentState(\n locator: PartLocator,\n option: Partial<Readonly<WaitForOption>> = defaultWaitForOption\n ): Promise<void> {\n await super.waitUntilComponentState(locator, option);\n await this.flush();\n }\n\n override async waitUntil<T>(option: WaitUntilOption<T>): Promise<T> {\n const result = await super.waitUntil(option);\n await this.flush();\n return result;\n }\n\n override clone(): Interactor {\n return new VueInteractor();\n }\n}\n","import { byAttribute, ScenePart, TestEngine } from '@atomic-testing/core';\nimport { render } from '@testing-library/vue';\nimport { App, Component, createApp, defineComponent } from 'vue';\n\nimport { VueInteractor } from './VueInteractor';\nimport { IVueTestEngineOption, VueSFCLikeComponent } from './types';\n\nlet _rootId = 0;\nfunction getNextRootElementId() {\n return `${_rootId++}`;\n}\n\nconst rootElementAttributeName = 'data-atomic-testing-vue';\n\nfunction isSFCLikeObject(component: any): component is VueSFCLikeComponent {\n return (\n component && typeof component === 'object' && 'template' in component && typeof component.template === 'string'\n );\n}\n\nfunction createComponentFromSFCLike(sfcObj: VueSFCLikeComponent): Component {\n const componentOptions: any = {\n name: sfcObj.name || 'SFCComponent',\n template: sfcObj.template,\n };\n\n if (sfcObj.props) {\n componentOptions.props = sfcObj.props;\n }\n\n if (sfcObj.setup) {\n componentOptions.setup = sfcObj.setup;\n }\n\n if (sfcObj.data) {\n componentOptions.data = sfcObj.data;\n }\n\n if (sfcObj.methods) {\n componentOptions.methods = sfcObj.methods;\n }\n\n if (sfcObj.computed) {\n componentOptions.computed = sfcObj.computed;\n }\n\n return defineComponent(componentOptions);\n}\n\nexport function createTestEngine<T extends ScenePart>(\n component: Component | VueSFCLikeComponent,\n partDefinitions: T,\n option?: Readonly<Partial<IVueTestEngineOption>>\n): TestEngine<T> {\n const rootEl = option?.rootElement ?? document.body;\n const container = rootEl.appendChild(document.createElement('div'));\n const rootId = getNextRootElementId();\n container.setAttribute(rootElementAttributeName, rootId);\n\n let unmount: () => void;\n let app: App;\n\n // Create component from SFC-like object if needed\n const compiledComponent = isSFCLikeObject(component)\n ? createComponentFromSFCLike(component)\n : (component as Component);\n\n try {\n const renderResult = render(compiledComponent, { container });\n unmount = renderResult.unmount;\n } catch (_error) {\n // Fallback to manual Vue app creation if render fails\n app = createApp(compiledComponent);\n app.mount(container);\n unmount = () => {\n if (app) {\n app.unmount();\n }\n };\n }\n\n const cleanup = () => {\n unmount();\n rootEl.removeChild(container);\n return Promise.resolve();\n };\n\n return new TestEngine(\n byAttribute(rootElementAttributeName, rootId),\n new VueInteractor(),\n {\n parts: partDefinitions,\n },\n cleanup\n );\n}\n\nexport function createRenderedTestEngine<T extends ScenePart>(\n rootElement: HTMLElement,\n partDefinitions: T,\n _option?: Readonly<Partial<IVueTestEngineOption>>\n): TestEngine<T> {\n const rootId = getNextRootElementId();\n rootElement.setAttribute(rootElementAttributeName, rootId);\n\n const cleanup = () => {\n rootElement.removeAttribute(rootElementAttributeName);\n return Promise.resolve();\n };\n\n return new TestEngine(\n byAttribute(rootElementAttributeName, rootId),\n new VueInteractor(),\n {\n parts: partDefinitions,\n },\n cleanup\n );\n}\n"],"mappings":"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"}
|
|
@@ -6,9 +6,18 @@ import { DOMInteractor } from "@atomic-testing/dom-core";
|
|
|
6
6
|
interface IVueTestEngineOption extends IComponentDriverOption {
|
|
7
7
|
rootElement?: Element;
|
|
8
8
|
}
|
|
9
|
+
interface VueSFCLikeComponent {
|
|
10
|
+
template: string;
|
|
11
|
+
setup?: () => any;
|
|
12
|
+
data?: () => any;
|
|
13
|
+
methods?: Record<string, (...args: any[]) => any>;
|
|
14
|
+
computed?: Record<string, () => any>;
|
|
15
|
+
name?: string;
|
|
16
|
+
props?: any;
|
|
17
|
+
}
|
|
9
18
|
//#endregion
|
|
10
19
|
//#region src/createTestEngine.d.ts
|
|
11
|
-
declare function createTestEngine<T extends ScenePart>(component: Component, partDefinitions: T, option?: Readonly<Partial<IVueTestEngineOption>>): TestEngine<T>;
|
|
20
|
+
declare function createTestEngine<T extends ScenePart>(component: Component | VueSFCLikeComponent, partDefinitions: T, option?: Readonly<Partial<IVueTestEngineOption>>): TestEngine<T>;
|
|
12
21
|
declare function createRenderedTestEngine<T extends ScenePart>(rootElement: HTMLElement, partDefinitions: T, _option?: Readonly<Partial<IVueTestEngineOption>>): TestEngine<T>;
|
|
13
22
|
//#endregion
|
|
14
23
|
//#region src/VueInteractor.d.ts
|
|
@@ -32,5 +41,5 @@ declare class VueInteractor extends DOMInteractor {
|
|
|
32
41
|
clone(): Interactor;
|
|
33
42
|
}
|
|
34
43
|
//#endregion
|
|
35
|
-
export { IVueTestEngineOption, VueInteractor, createRenderedTestEngine, createTestEngine };
|
|
36
|
-
//# sourceMappingURL=index.d.
|
|
44
|
+
export { type IVueTestEngineOption, VueInteractor, type VueSFCLikeComponent, createRenderedTestEngine, createTestEngine };
|
|
45
|
+
//# sourceMappingURL=index.d.cts.map
|
package/dist/index.d.mts
CHANGED
|
@@ -6,9 +6,18 @@ import { DOMInteractor } from "@atomic-testing/dom-core";
|
|
|
6
6
|
interface IVueTestEngineOption extends IComponentDriverOption {
|
|
7
7
|
rootElement?: Element;
|
|
8
8
|
}
|
|
9
|
+
interface VueSFCLikeComponent {
|
|
10
|
+
template: string;
|
|
11
|
+
setup?: () => any;
|
|
12
|
+
data?: () => any;
|
|
13
|
+
methods?: Record<string, (...args: any[]) => any>;
|
|
14
|
+
computed?: Record<string, () => any>;
|
|
15
|
+
name?: string;
|
|
16
|
+
props?: any;
|
|
17
|
+
}
|
|
9
18
|
//#endregion
|
|
10
19
|
//#region src/createTestEngine.d.ts
|
|
11
|
-
declare function createTestEngine<T extends ScenePart>(component: Component, partDefinitions: T, option?: Readonly<Partial<IVueTestEngineOption>>): TestEngine<T>;
|
|
20
|
+
declare function createTestEngine<T extends ScenePart>(component: Component | VueSFCLikeComponent, partDefinitions: T, option?: Readonly<Partial<IVueTestEngineOption>>): TestEngine<T>;
|
|
12
21
|
declare function createRenderedTestEngine<T extends ScenePart>(rootElement: HTMLElement, partDefinitions: T, _option?: Readonly<Partial<IVueTestEngineOption>>): TestEngine<T>;
|
|
13
22
|
//#endregion
|
|
14
23
|
//#region src/VueInteractor.d.ts
|
|
@@ -32,5 +41,5 @@ declare class VueInteractor extends DOMInteractor {
|
|
|
32
41
|
clone(): Interactor;
|
|
33
42
|
}
|
|
34
43
|
//#endregion
|
|
35
|
-
export { IVueTestEngineOption, VueInteractor, createRenderedTestEngine, createTestEngine };
|
|
44
|
+
export { type IVueTestEngineOption, VueInteractor, type VueSFCLikeComponent, createRenderedTestEngine, createTestEngine };
|
|
36
45
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { TestEngine, byAttribute, defaultWaitForOption } from "@atomic-testing/core";
|
|
2
2
|
import { render } from "@testing-library/vue";
|
|
3
|
-
import { createApp, nextTick } from "vue";
|
|
3
|
+
import { createApp, defineComponent, nextTick } from "vue";
|
|
4
4
|
import { DOMInteractor } from "@atomic-testing/dom-core";
|
|
5
5
|
|
|
6
6
|
//#region src/VueInteractor.ts
|
|
@@ -81,6 +81,21 @@ function getNextRootElementId() {
|
|
|
81
81
|
return `${_rootId++}`;
|
|
82
82
|
}
|
|
83
83
|
const rootElementAttributeName = "data-atomic-testing-vue";
|
|
84
|
+
function isSFCLikeObject(component) {
|
|
85
|
+
return component && typeof component === "object" && "template" in component && typeof component.template === "string";
|
|
86
|
+
}
|
|
87
|
+
function createComponentFromSFCLike(sfcObj) {
|
|
88
|
+
const componentOptions = {
|
|
89
|
+
name: sfcObj.name || "SFCComponent",
|
|
90
|
+
template: sfcObj.template
|
|
91
|
+
};
|
|
92
|
+
if (sfcObj.props) componentOptions.props = sfcObj.props;
|
|
93
|
+
if (sfcObj.setup) componentOptions.setup = sfcObj.setup;
|
|
94
|
+
if (sfcObj.data) componentOptions.data = sfcObj.data;
|
|
95
|
+
if (sfcObj.methods) componentOptions.methods = sfcObj.methods;
|
|
96
|
+
if (sfcObj.computed) componentOptions.computed = sfcObj.computed;
|
|
97
|
+
return defineComponent(componentOptions);
|
|
98
|
+
}
|
|
84
99
|
function createTestEngine(component, partDefinitions, option) {
|
|
85
100
|
const rootEl = option?.rootElement ?? document.body;
|
|
86
101
|
const container = rootEl.appendChild(document.createElement("div"));
|
|
@@ -88,11 +103,11 @@ function createTestEngine(component, partDefinitions, option) {
|
|
|
88
103
|
container.setAttribute(rootElementAttributeName, rootId);
|
|
89
104
|
let unmount;
|
|
90
105
|
let app;
|
|
106
|
+
const compiledComponent = isSFCLikeObject(component) ? createComponentFromSFCLike(component) : component;
|
|
91
107
|
try {
|
|
92
|
-
|
|
93
|
-
unmount = renderResult.unmount;
|
|
108
|
+
unmount = render(compiledComponent, { container }).unmount;
|
|
94
109
|
} catch (_error) {
|
|
95
|
-
app = createApp(
|
|
110
|
+
app = createApp(compiledComponent);
|
|
96
111
|
app.mount(container);
|
|
97
112
|
unmount = () => {
|
|
98
113
|
if (app) app.unmount();
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["componentOptions: any","unmount: () => void","app: App"],"sources":["../src/VueInteractor.ts","../src/createTestEngine.ts"],"sourcesContent":["import {\n ClickOption,\n defaultWaitForOption,\n EnterTextOption,\n FocusOption,\n HoverOption,\n Interactor,\n MouseDownOption,\n MouseEnterOption,\n MouseLeaveOption,\n MouseMoveOption,\n MouseOutOption,\n MouseUpOption,\n PartLocator,\n WaitForOption,\n WaitUntilOption,\n} from '@atomic-testing/core';\nimport { DOMInteractor } from '@atomic-testing/dom-core';\nimport { nextTick } from 'vue';\n\nexport class VueInteractor extends DOMInteractor {\n private async flush() {\n await nextTick();\n }\n\n override async enterText(locator: PartLocator, text: string, option?: Partial<EnterTextOption>): Promise<void> {\n await super.enterText(locator, text, option);\n await this.flush();\n }\n\n override async click(locator: PartLocator, option?: Partial<ClickOption>): Promise<void> {\n await super.click(locator, option);\n await this.flush();\n }\n\n override async hover(locator: PartLocator, option?: Partial<HoverOption>): Promise<void> {\n await super.hover(locator, option);\n await this.flush();\n }\n\n async mouseMove(locator: PartLocator, option?: Partial<MouseMoveOption>): Promise<void> {\n await super.mouseMove(locator, option);\n await this.flush();\n }\n\n async mouseDown(locator: PartLocator, option?: Partial<MouseDownOption>): Promise<void> {\n await super.mouseDown(locator, option);\n await this.flush();\n }\n\n async mouseUp(locator: PartLocator, option?: Partial<MouseUpOption>): Promise<void> {\n await super.mouseUp(locator, option);\n await this.flush();\n }\n\n async mouseOver(locator: PartLocator, option?: Partial<HoverOption>): Promise<void> {\n await super.mouseOver(locator, option);\n await this.flush();\n }\n\n async mouseOut(locator: PartLocator, option?: Partial<MouseOutOption>): Promise<void> {\n await super.mouseOut(locator, option);\n await this.flush();\n }\n\n async mouseEnter(locator: PartLocator, option?: Partial<MouseEnterOption>): Promise<void> {\n await super.mouseEnter(locator, option);\n await this.flush();\n }\n\n async mouseLeave(locator: PartLocator, option?: Partial<MouseLeaveOption>): Promise<void> {\n await super.mouseLeave(locator, option);\n await this.flush();\n }\n\n async focus(locator: PartLocator, option?: Partial<FocusOption>): Promise<void> {\n await super.focus(locator, option);\n await this.flush();\n }\n\n override async selectOptionValue(locator: PartLocator, values: string[]): Promise<void> {\n await super.selectOptionValue(locator, values);\n await this.flush();\n }\n\n override async wait(ms: number): Promise<void> {\n await super.wait(ms);\n await this.flush();\n }\n\n override async waitUntilComponentState(\n locator: PartLocator,\n option: Partial<Readonly<WaitForOption>> = defaultWaitForOption\n ): Promise<void> {\n await super.waitUntilComponentState(locator, option);\n await this.flush();\n }\n\n override async waitUntil<T>(option: WaitUntilOption<T>): Promise<T> {\n const result = await super.waitUntil(option);\n await this.flush();\n return result;\n }\n\n override clone(): Interactor {\n return new VueInteractor();\n }\n}\n","import { byAttribute, ScenePart, TestEngine } from '@atomic-testing/core';\nimport { render } from '@testing-library/vue';\nimport { App, Component, createApp, defineComponent } from 'vue';\n\nimport { VueInteractor } from './VueInteractor';\nimport { IVueTestEngineOption, VueSFCLikeComponent } from './types';\n\nlet _rootId = 0;\nfunction getNextRootElementId() {\n return `${_rootId++}`;\n}\n\nconst rootElementAttributeName = 'data-atomic-testing-vue';\n\nfunction isSFCLikeObject(component: any): component is VueSFCLikeComponent {\n return (\n component && typeof component === 'object' && 'template' in component && typeof component.template === 'string'\n );\n}\n\nfunction createComponentFromSFCLike(sfcObj: VueSFCLikeComponent): Component {\n const componentOptions: any = {\n name: sfcObj.name || 'SFCComponent',\n template: sfcObj.template,\n };\n\n if (sfcObj.props) {\n componentOptions.props = sfcObj.props;\n }\n\n if (sfcObj.setup) {\n componentOptions.setup = sfcObj.setup;\n }\n\n if (sfcObj.data) {\n componentOptions.data = sfcObj.data;\n }\n\n if (sfcObj.methods) {\n componentOptions.methods = sfcObj.methods;\n }\n\n if (sfcObj.computed) {\n componentOptions.computed = sfcObj.computed;\n }\n\n return defineComponent(componentOptions);\n}\n\nexport function createTestEngine<T extends ScenePart>(\n component: Component | VueSFCLikeComponent,\n partDefinitions: T,\n option?: Readonly<Partial<IVueTestEngineOption>>\n): TestEngine<T> {\n const rootEl = option?.rootElement ?? document.body;\n const container = rootEl.appendChild(document.createElement('div'));\n const rootId = getNextRootElementId();\n container.setAttribute(rootElementAttributeName, rootId);\n\n let unmount: () => void;\n let app: App;\n\n // Create component from SFC-like object if needed\n const compiledComponent = isSFCLikeObject(component)\n ? createComponentFromSFCLike(component)\n : (component as Component);\n\n try {\n const renderResult = render(compiledComponent, { container });\n unmount = renderResult.unmount;\n } catch (_error) {\n // Fallback to manual Vue app creation if render fails\n app = createApp(compiledComponent);\n app.mount(container);\n unmount = () => {\n if (app) {\n app.unmount();\n }\n };\n }\n\n const cleanup = () => {\n unmount();\n rootEl.removeChild(container);\n return Promise.resolve();\n };\n\n return new TestEngine(\n byAttribute(rootElementAttributeName, rootId),\n new VueInteractor(),\n {\n parts: partDefinitions,\n },\n cleanup\n );\n}\n\nexport function createRenderedTestEngine<T extends ScenePart>(\n rootElement: HTMLElement,\n partDefinitions: T,\n _option?: Readonly<Partial<IVueTestEngineOption>>\n): TestEngine<T> {\n const rootId = getNextRootElementId();\n rootElement.setAttribute(rootElementAttributeName, rootId);\n\n const cleanup = () => {\n rootElement.removeAttribute(rootElementAttributeName);\n return Promise.resolve();\n };\n\n return new TestEngine(\n byAttribute(rootElementAttributeName, rootId),\n new VueInteractor(),\n {\n parts: partDefinitions,\n },\n cleanup\n );\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atomic-testing/vue-3",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "",
|
|
5
|
-
"main": "dist/index.
|
|
3
|
+
"version": "0.79.0",
|
|
4
|
+
"description": "The @atomic-testing/vue-3 package is a Vue 3 test adapter that extends Atomic Testing's component driver pattern to Vue applications. It enables testing Vue components using the same high-level semantic APIs used across React, Playwright, and DOM environments.",
|
|
5
|
+
"main": "dist/index.cjs",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
7
|
-
"types": "dist/index.d.
|
|
7
|
+
"types": "dist/index.d.cts",
|
|
8
8
|
"files": [
|
|
9
9
|
"dist",
|
|
10
10
|
"src"
|
|
@@ -25,8 +25,9 @@
|
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"@testing-library/dom": "^10.4.0",
|
|
27
27
|
"@testing-library/vue": "^8.1.0",
|
|
28
|
-
"@
|
|
29
|
-
"@atomic-testing/
|
|
28
|
+
"@vue/compiler-sfc": "^3.5.0",
|
|
29
|
+
"@atomic-testing/core": "0.79.0",
|
|
30
|
+
"@atomic-testing/dom-core": "0.79.0"
|
|
30
31
|
},
|
|
31
32
|
"devDependencies": {
|
|
32
33
|
"vue": "^3.5.0"
|
|
@@ -35,7 +36,15 @@
|
|
|
35
36
|
"@testing-library/dom": ">=10.4.0",
|
|
36
37
|
"@testing-library/vue": ">=8.1.0",
|
|
37
38
|
"@testing-library/user-event": ">=14.0.0",
|
|
38
|
-
"vue": ">=3.0.0"
|
|
39
|
+
"vue": ">=3.0.0",
|
|
40
|
+
"@vue/compiler-sfc": ">=3.0.0"
|
|
41
|
+
},
|
|
42
|
+
"exports": {
|
|
43
|
+
".": {
|
|
44
|
+
"types": "./dist/index.d.mts",
|
|
45
|
+
"import": "./dist/index.mjs",
|
|
46
|
+
"require": "./dist/index.cjs"
|
|
47
|
+
}
|
|
39
48
|
},
|
|
40
49
|
"scripts": {
|
|
41
50
|
"build": "tsdown",
|
package/src/createTestEngine.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { byAttribute, ScenePart, TestEngine } from '@atomic-testing/core';
|
|
2
2
|
import { render } from '@testing-library/vue';
|
|
3
|
-
import { App, Component, createApp } from 'vue';
|
|
3
|
+
import { App, Component, createApp, defineComponent } from 'vue';
|
|
4
4
|
|
|
5
5
|
import { VueInteractor } from './VueInteractor';
|
|
6
|
-
import { IVueTestEngineOption } from './types';
|
|
6
|
+
import { IVueTestEngineOption, VueSFCLikeComponent } from './types';
|
|
7
7
|
|
|
8
8
|
let _rootId = 0;
|
|
9
9
|
function getNextRootElementId() {
|
|
@@ -12,8 +12,43 @@ function getNextRootElementId() {
|
|
|
12
12
|
|
|
13
13
|
const rootElementAttributeName = 'data-atomic-testing-vue';
|
|
14
14
|
|
|
15
|
+
function isSFCLikeObject(component: any): component is VueSFCLikeComponent {
|
|
16
|
+
return (
|
|
17
|
+
component && typeof component === 'object' && 'template' in component && typeof component.template === 'string'
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function createComponentFromSFCLike(sfcObj: VueSFCLikeComponent): Component {
|
|
22
|
+
const componentOptions: any = {
|
|
23
|
+
name: sfcObj.name || 'SFCComponent',
|
|
24
|
+
template: sfcObj.template,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
if (sfcObj.props) {
|
|
28
|
+
componentOptions.props = sfcObj.props;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (sfcObj.setup) {
|
|
32
|
+
componentOptions.setup = sfcObj.setup;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (sfcObj.data) {
|
|
36
|
+
componentOptions.data = sfcObj.data;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (sfcObj.methods) {
|
|
40
|
+
componentOptions.methods = sfcObj.methods;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (sfcObj.computed) {
|
|
44
|
+
componentOptions.computed = sfcObj.computed;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return defineComponent(componentOptions);
|
|
48
|
+
}
|
|
49
|
+
|
|
15
50
|
export function createTestEngine<T extends ScenePart>(
|
|
16
|
-
component: Component,
|
|
51
|
+
component: Component | VueSFCLikeComponent,
|
|
17
52
|
partDefinitions: T,
|
|
18
53
|
option?: Readonly<Partial<IVueTestEngineOption>>
|
|
19
54
|
): TestEngine<T> {
|
|
@@ -25,12 +60,17 @@ export function createTestEngine<T extends ScenePart>(
|
|
|
25
60
|
let unmount: () => void;
|
|
26
61
|
let app: App;
|
|
27
62
|
|
|
63
|
+
// Create component from SFC-like object if needed
|
|
64
|
+
const compiledComponent = isSFCLikeObject(component)
|
|
65
|
+
? createComponentFromSFCLike(component)
|
|
66
|
+
: (component as Component);
|
|
67
|
+
|
|
28
68
|
try {
|
|
29
|
-
const renderResult = render(
|
|
69
|
+
const renderResult = render(compiledComponent, { container });
|
|
30
70
|
unmount = renderResult.unmount;
|
|
31
71
|
} catch (_error) {
|
|
32
72
|
// Fallback to manual Vue app creation if render fails
|
|
33
|
-
app = createApp(
|
|
73
|
+
app = createApp(compiledComponent);
|
|
34
74
|
app.mount(container);
|
|
35
75
|
unmount = () => {
|
|
36
76
|
if (app) {
|
package/src/index.ts
CHANGED
package/src/types.ts
CHANGED
|
@@ -3,3 +3,14 @@ import { IComponentDriverOption } from '@atomic-testing/core';
|
|
|
3
3
|
export interface IVueTestEngineOption extends IComponentDriverOption {
|
|
4
4
|
rootElement?: Element;
|
|
5
5
|
}
|
|
6
|
+
|
|
7
|
+
// Simple SFC-like object interface for template-based components
|
|
8
|
+
export interface VueSFCLikeComponent {
|
|
9
|
+
template: string;
|
|
10
|
+
setup?: () => any;
|
|
11
|
+
data?: () => any;
|
|
12
|
+
methods?: Record<string, (...args: any[]) => any>;
|
|
13
|
+
computed?: Record<string, () => any>;
|
|
14
|
+
name?: string;
|
|
15
|
+
props?: any;
|
|
16
|
+
}
|
package/dist/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["DOMInteractor","locator: PartLocator","text: string","option?: Partial<EnterTextOption>","option?: Partial<ClickOption>","option?: Partial<HoverOption>","option?: Partial<MouseMoveOption>","option?: Partial<MouseDownOption>","option?: Partial<MouseUpOption>","option?: Partial<MouseOutOption>","option?: Partial<MouseEnterOption>","option?: Partial<MouseLeaveOption>","option?: Partial<FocusOption>","values: string[]","ms: number","option: Partial<Readonly<WaitForOption>>","defaultWaitForOption","option: WaitUntilOption<T>","component: Component","partDefinitions: T","option?: Readonly<Partial<IVueTestEngineOption>>","unmount: () => void","app: App","TestEngine","rootElement: HTMLElement","_option?: Readonly<Partial<IVueTestEngineOption>>"],"sources":["../src/VueInteractor.ts","../src/createTestEngine.ts"],"sourcesContent":["import {\n ClickOption,\n defaultWaitForOption,\n EnterTextOption,\n FocusOption,\n HoverOption,\n Interactor,\n MouseDownOption,\n MouseEnterOption,\n MouseLeaveOption,\n MouseMoveOption,\n MouseOutOption,\n MouseUpOption,\n PartLocator,\n WaitForOption,\n WaitUntilOption,\n} from '@atomic-testing/core';\nimport { DOMInteractor } from '@atomic-testing/dom-core';\nimport { nextTick } from 'vue';\n\nexport class VueInteractor extends DOMInteractor {\n private async flush() {\n await nextTick();\n }\n\n override async enterText(locator: PartLocator, text: string, option?: Partial<EnterTextOption>): Promise<void> {\n await super.enterText(locator, text, option);\n await this.flush();\n }\n\n override async click(locator: PartLocator, option?: Partial<ClickOption>): Promise<void> {\n await super.click(locator, option);\n await this.flush();\n }\n\n override async hover(locator: PartLocator, option?: Partial<HoverOption>): Promise<void> {\n await super.hover(locator, option);\n await this.flush();\n }\n\n async mouseMove(locator: PartLocator, option?: Partial<MouseMoveOption>): Promise<void> {\n await super.mouseMove(locator, option);\n await this.flush();\n }\n\n async mouseDown(locator: PartLocator, option?: Partial<MouseDownOption>): Promise<void> {\n await super.mouseDown(locator, option);\n await this.flush();\n }\n\n async mouseUp(locator: PartLocator, option?: Partial<MouseUpOption>): Promise<void> {\n await super.mouseUp(locator, option);\n await this.flush();\n }\n\n async mouseOver(locator: PartLocator, option?: Partial<HoverOption>): Promise<void> {\n await super.mouseOver(locator, option);\n await this.flush();\n }\n\n async mouseOut(locator: PartLocator, option?: Partial<MouseOutOption>): Promise<void> {\n await super.mouseOut(locator, option);\n await this.flush();\n }\n\n async mouseEnter(locator: PartLocator, option?: Partial<MouseEnterOption>): Promise<void> {\n await super.mouseEnter(locator, option);\n await this.flush();\n }\n\n async mouseLeave(locator: PartLocator, option?: Partial<MouseLeaveOption>): Promise<void> {\n await super.mouseLeave(locator, option);\n await this.flush();\n }\n\n async focus(locator: PartLocator, option?: Partial<FocusOption>): Promise<void> {\n await super.focus(locator, option);\n await this.flush();\n }\n\n override async selectOptionValue(locator: PartLocator, values: string[]): Promise<void> {\n await super.selectOptionValue(locator, values);\n await this.flush();\n }\n\n override async wait(ms: number): Promise<void> {\n await super.wait(ms);\n await this.flush();\n }\n\n override async waitUntilComponentState(\n locator: PartLocator,\n option: Partial<Readonly<WaitForOption>> = defaultWaitForOption\n ): Promise<void> {\n await super.waitUntilComponentState(locator, option);\n await this.flush();\n }\n\n override async waitUntil<T>(option: WaitUntilOption<T>): Promise<T> {\n const result = await super.waitUntil(option);\n await this.flush();\n return result;\n }\n\n override clone(): Interactor {\n return new VueInteractor();\n }\n}\n","import { byAttribute, ScenePart, TestEngine } from '@atomic-testing/core';\nimport { render } from '@testing-library/vue';\nimport { App, Component, createApp } from 'vue';\n\nimport { VueInteractor } from './VueInteractor';\nimport { IVueTestEngineOption } from './types';\n\nlet _rootId = 0;\nfunction getNextRootElementId() {\n return `${_rootId++}`;\n}\n\nconst rootElementAttributeName = 'data-atomic-testing-vue';\n\nexport function createTestEngine<T extends ScenePart>(\n component: Component,\n partDefinitions: T,\n option?: Readonly<Partial<IVueTestEngineOption>>\n): TestEngine<T> {\n const rootEl = option?.rootElement ?? document.body;\n const container = rootEl.appendChild(document.createElement('div'));\n const rootId = getNextRootElementId();\n container.setAttribute(rootElementAttributeName, rootId);\n\n let unmount: () => void;\n let app: App;\n\n try {\n const renderResult = render(component, { container });\n unmount = renderResult.unmount;\n } catch (_error) {\n // Fallback to manual Vue app creation if render fails\n app = createApp(component);\n app.mount(container);\n unmount = () => {\n if (app) {\n app.unmount();\n }\n };\n }\n\n const cleanup = () => {\n unmount();\n rootEl.removeChild(container);\n return Promise.resolve();\n };\n\n return new TestEngine(\n byAttribute(rootElementAttributeName, rootId),\n new VueInteractor(),\n {\n parts: partDefinitions,\n },\n cleanup\n );\n}\n\nexport function createRenderedTestEngine<T extends ScenePart>(\n rootElement: HTMLElement,\n partDefinitions: T,\n _option?: Readonly<Partial<IVueTestEngineOption>>\n): TestEngine<T> {\n const rootId = getNextRootElementId();\n rootElement.setAttribute(rootElementAttributeName, rootId);\n\n const cleanup = () => {\n rootElement.removeAttribute(rootElementAttributeName);\n return Promise.resolve();\n };\n\n return new TestEngine(\n byAttribute(rootElementAttributeName, rootId),\n new VueInteractor(),\n {\n parts: partDefinitions,\n },\n cleanup\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoBA,IAAa,gBAAb,MAAa,sBAAsBA,wCAAc;CAC/C,MAAc,QAAQ;AACpB,QAAM,mBAAU;CACjB;CAED,MAAe,UAAUC,SAAsBC,MAAcC,QAAkD;AAC7G,QAAM,MAAM,UAAU,SAAS,MAAM,OAAO;AAC5C,QAAM,KAAK,OAAO;CACnB;CAED,MAAe,MAAMF,SAAsBG,QAA8C;AACvF,QAAM,MAAM,MAAM,SAAS,OAAO;AAClC,QAAM,KAAK,OAAO;CACnB;CAED,MAAe,MAAMH,SAAsBI,QAA8C;AACvF,QAAM,MAAM,MAAM,SAAS,OAAO;AAClC,QAAM,KAAK,OAAO;CACnB;CAED,MAAM,UAAUJ,SAAsBK,QAAkD;AACtF,QAAM,MAAM,UAAU,SAAS,OAAO;AACtC,QAAM,KAAK,OAAO;CACnB;CAED,MAAM,UAAUL,SAAsBM,QAAkD;AACtF,QAAM,MAAM,UAAU,SAAS,OAAO;AACtC,QAAM,KAAK,OAAO;CACnB;CAED,MAAM,QAAQN,SAAsBO,QAAgD;AAClF,QAAM,MAAM,QAAQ,SAAS,OAAO;AACpC,QAAM,KAAK,OAAO;CACnB;CAED,MAAM,UAAUP,SAAsBI,QAA8C;AAClF,QAAM,MAAM,UAAU,SAAS,OAAO;AACtC,QAAM,KAAK,OAAO;CACnB;CAED,MAAM,SAASJ,SAAsBQ,QAAiD;AACpF,QAAM,MAAM,SAAS,SAAS,OAAO;AACrC,QAAM,KAAK,OAAO;CACnB;CAED,MAAM,WAAWR,SAAsBS,QAAmD;AACxF,QAAM,MAAM,WAAW,SAAS,OAAO;AACvC,QAAM,KAAK,OAAO;CACnB;CAED,MAAM,WAAWT,SAAsBU,QAAmD;AACxF,QAAM,MAAM,WAAW,SAAS,OAAO;AACvC,QAAM,KAAK,OAAO;CACnB;CAED,MAAM,MAAMV,SAAsBW,QAA8C;AAC9E,QAAM,MAAM,MAAM,SAAS,OAAO;AAClC,QAAM,KAAK,OAAO;CACnB;CAED,MAAe,kBAAkBX,SAAsBY,QAAiC;AACtF,QAAM,MAAM,kBAAkB,SAAS,OAAO;AAC9C,QAAM,KAAK,OAAO;CACnB;CAED,MAAe,KAAKC,IAA2B;AAC7C,QAAM,MAAM,KAAK,GAAG;AACpB,QAAM,KAAK,OAAO;CACnB;CAED,MAAe,wBACbb,SACAc,SAA2CC,4CAC5B;AACf,QAAM,MAAM,wBAAwB,SAAS,OAAO;AACpD,QAAM,KAAK,OAAO;CACnB;CAED,MAAe,UAAaC,QAAwC;EAClE,MAAM,SAAS,MAAM,MAAM,UAAU,OAAO;AAC5C,QAAM,KAAK,OAAO;AAClB,SAAO;CACR;CAED,AAAS,QAAoB;AAC3B,SAAO,IAAI;CACZ;AACF;;;;ACpGD,IAAI,UAAU;AACd,SAAS,uBAAuB;AAC9B,WAAU;AACX;AAED,MAAM,2BAA2B;AAEjC,SAAgB,iBACdC,WACAC,iBACAC,QACe;CACf,MAAM,SAAS,QAAQ,eAAe,SAAS;CAC/C,MAAM,YAAY,OAAO,YAAY,SAAS,cAAc,MAAM,CAAC;CACnE,MAAM,SAAS,sBAAsB;AACrC,WAAU,aAAa,0BAA0B,OAAO;CAExD,IAAIC;CACJ,IAAIC;AAEJ,KAAI;EACF,MAAM,eAAe,kCAAO,WAAW,EAAE,UAAW,EAAC;AACrD,YAAU,aAAa;CACxB,SAAQ,QAAQ;AAEf,QAAM,mBAAU,UAAU;AAC1B,MAAI,MAAM,UAAU;AACpB,YAAU,MAAM;AACd,OAAI,IACF,KAAI,SAAS;EAEhB;CACF;CAED,MAAM,UAAU,MAAM;AACpB,WAAS;AACT,SAAO,YAAY,UAAU;AAC7B,SAAO,QAAQ,SAAS;CACzB;AAED,QAAO,IAAIC,iCACT,uCAAY,0BAA0B,OAAO,EAC7C,IAAI,iBACJ,EACE,OAAO,gBACR,GACD;AAEH;AAED,SAAgB,yBACdC,aACAL,iBACAM,SACe;CACf,MAAM,SAAS,sBAAsB;AACrC,aAAY,aAAa,0BAA0B,OAAO;CAE1D,MAAM,UAAU,MAAM;AACpB,cAAY,gBAAgB,yBAAyB;AACrD,SAAO,QAAQ,SAAS;CACzB;AAED,QAAO,IAAIF,iCACT,uCAAY,0BAA0B,OAAO,EAC7C,IAAI,iBACJ,EACE,OAAO,gBACR,GACD;AAEH"}
|