@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 CHANGED
@@ -1,9 +1,166 @@
1
1
  # @atomic-testing/vue-3
2
2
 
3
- Adapter for integrating Atomic Testing with [Vue 3](https://vuejs.org).
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
- //#region rolldown:runtime
2
- var __create = Object.create;
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
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 __atomic_testing_dom_core.DOMInteractor {
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 = __atomic_testing_core.defaultWaitForOption) {
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
- const renderResult = (0, __testing_library_vue.render)(component, { container });
116
- unmount = renderResult.unmount;
108
+ unmount = (0, _testing_library_vue.render)(compiledComponent, { container }).unmount;
117
109
  } catch (_error) {
118
- app = (0, vue.createApp)(component);
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 __atomic_testing_core.TestEngine((0, __atomic_testing_core.byAttribute)(rootElementAttributeName, rootId), new VueInteractor(), { parts: partDefinitions }, cleanup);
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 __atomic_testing_core.TestEngine((0, __atomic_testing_core.byAttribute)(rootElementAttributeName, rootId), new VueInteractor(), { parts: partDefinitions }, cleanup);
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.js.map
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.ts.map
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
- const renderResult = render(component, { container });
93
- unmount = renderResult.unmount;
108
+ unmount = render(compiledComponent, { container }).unmount;
94
109
  } catch (_error) {
95
- app = createApp(component);
110
+ app = createApp(compiledComponent);
96
111
  app.mount(container);
97
112
  unmount = () => {
98
113
  if (app) app.unmount();
@@ -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":";;;;;;AAoBA,IAAa,gBAAb,MAAa,sBAAsB,cAAc;CAC/C,MAAc,QAAQ;AACpB,QAAM,UAAU;CACjB;CAED,MAAe,UAAUA,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,SAA2C,sBAC5B;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,OAAO,WAAW,EAAE,UAAW,EAAC;AACrD,YAAU,aAAa;CACxB,SAAQ,QAAQ;AAEf,QAAM,UAAU,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,IAAI,WACT,YAAY,0BAA0B,OAAO,EAC7C,IAAI,iBACJ,EACE,OAAO,gBACR,GACD;AAEH;AAED,SAAgB,yBACdC,aACAJ,iBACAK,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,IAAI,WACT,YAAY,0BAA0B,OAAO,EAC7C,IAAI,iBACJ,EACE,OAAO,gBACR,GACD;AAEH"}
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.77.0",
4
- "description": "",
5
- "main": "dist/index.js",
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.ts",
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
- "@atomic-testing/core": "0.77.0",
29
- "@atomic-testing/dom-core": "0.77.0"
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",
@@ -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(component, { container });
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(component);
73
+ app = createApp(compiledComponent);
34
74
  app.mount(container);
35
75
  unmount = () => {
36
76
  if (app) {
package/src/index.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  export { createRenderedTestEngine, createTestEngine } from './createTestEngine';
2
2
  export { VueInteractor } from './VueInteractor';
3
- export type { IVueTestEngineOption } from './types';
3
+ export type { IVueTestEngineOption, VueSFCLikeComponent } from './types';
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":"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"}