@atomic-testing/vue-3 0.77.0 → 0.78.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.d.mts +11 -2
- package/dist/index.d.ts +11 -2
- package/dist/index.js +18 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +19 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -5
- package/src/createTestEngine.ts +45 -5
- package/src/index.ts +1 -1
- package/src/types.ts +11 -0
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
|
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, Function>;
|
|
14
|
+
computed?: Record<string, Function>;
|
|
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 { IVueTestEngineOption, VueInteractor, VueSFCLikeComponent, createRenderedTestEngine, createTestEngine };
|
|
36
45
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.d.ts
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, Function>;
|
|
14
|
+
computed?: Record<string, Function>;
|
|
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 { IVueTestEngineOption, VueInteractor, VueSFCLikeComponent, createRenderedTestEngine, createTestEngine };
|
|
36
45
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -104,6 +104,21 @@ function getNextRootElementId() {
|
|
|
104
104
|
return `${_rootId++}`;
|
|
105
105
|
}
|
|
106
106
|
const rootElementAttributeName = "data-atomic-testing-vue";
|
|
107
|
+
function isSFCLikeObject(component) {
|
|
108
|
+
return component && typeof component === "object" && "template" in component && typeof component.template === "string";
|
|
109
|
+
}
|
|
110
|
+
function createComponentFromSFCLike(sfcObj) {
|
|
111
|
+
const componentOptions = {
|
|
112
|
+
name: sfcObj.name || "SFCComponent",
|
|
113
|
+
template: sfcObj.template
|
|
114
|
+
};
|
|
115
|
+
if (sfcObj.props) componentOptions.props = sfcObj.props;
|
|
116
|
+
if (sfcObj.setup) componentOptions.setup = sfcObj.setup;
|
|
117
|
+
if (sfcObj.data) componentOptions.data = sfcObj.data;
|
|
118
|
+
if (sfcObj.methods) componentOptions.methods = sfcObj.methods;
|
|
119
|
+
if (sfcObj.computed) componentOptions.computed = sfcObj.computed;
|
|
120
|
+
return (0, vue.defineComponent)(componentOptions);
|
|
121
|
+
}
|
|
107
122
|
function createTestEngine(component, partDefinitions, option) {
|
|
108
123
|
const rootEl = option?.rootElement ?? document.body;
|
|
109
124
|
const container = rootEl.appendChild(document.createElement("div"));
|
|
@@ -111,11 +126,12 @@ function createTestEngine(component, partDefinitions, option) {
|
|
|
111
126
|
container.setAttribute(rootElementAttributeName, rootId);
|
|
112
127
|
let unmount;
|
|
113
128
|
let app;
|
|
129
|
+
const compiledComponent = isSFCLikeObject(component) ? createComponentFromSFCLike(component) : component;
|
|
114
130
|
try {
|
|
115
|
-
const renderResult = (0, __testing_library_vue.render)(
|
|
131
|
+
const renderResult = (0, __testing_library_vue.render)(compiledComponent, { container });
|
|
116
132
|
unmount = renderResult.unmount;
|
|
117
133
|
} catch (_error) {
|
|
118
|
-
app = (0, vue.createApp)(
|
|
134
|
+
app = (0, vue.createApp)(compiledComponent);
|
|
119
135
|
app.mount(container);
|
|
120
136
|
unmount = () => {
|
|
121
137
|
if (app) app.unmount();
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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":"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"}
|
|
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: any","sfcObj: VueSFCLikeComponent","componentOptions: any","component: Component | VueSFCLikeComponent","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, defineComponent, h } 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/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,12 @@ 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
|
-
const renderResult = render(
|
|
108
|
+
const renderResult = render(compiledComponent, { container });
|
|
93
109
|
unmount = renderResult.unmount;
|
|
94
110
|
} catch (_error) {
|
|
95
|
-
app = createApp(
|
|
111
|
+
app = createApp(compiledComponent);
|
|
96
112
|
app.mount(container);
|
|
97
113
|
unmount = () => {
|
|
98
114
|
if (app) app.unmount();
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["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>>","option: WaitUntilOption<T>","component: Component","partDefinitions: T","option?: Readonly<Partial<IVueTestEngineOption>>","unmount: () => void","app: App","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":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["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>>","option: WaitUntilOption<T>","component: any","sfcObj: VueSFCLikeComponent","componentOptions: any","component: Component | VueSFCLikeComponent","partDefinitions: T","option?: Readonly<Partial<IVueTestEngineOption>>","unmount: () => void","app: App","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, defineComponent, h } 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,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atomic-testing/vue-3",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "",
|
|
3
|
+
"version": "0.78.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
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
@@ -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.78.0",
|
|
30
|
+
"@atomic-testing/dom-core": "0.78.0"
|
|
30
31
|
},
|
|
31
32
|
"devDependencies": {
|
|
32
33
|
"vue": "^3.5.0"
|
|
@@ -35,7 +36,8 @@
|
|
|
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"
|
|
39
41
|
},
|
|
40
42
|
"scripts": {
|
|
41
43
|
"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, h } 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, Function>;
|
|
13
|
+
computed?: Record<string, Function>;
|
|
14
|
+
name?: string;
|
|
15
|
+
props?: any;
|
|
16
|
+
}
|