@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 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
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)(component, { container });
131
+ const renderResult = (0, __testing_library_vue.render)(compiledComponent, { container });
116
132
  unmount = renderResult.unmount;
117
133
  } catch (_error) {
118
- app = (0, vue.createApp)(component);
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(component, { container });
108
+ const renderResult = render(compiledComponent, { container });
93
109
  unmount = renderResult.unmount;
94
110
  } catch (_error) {
95
- app = createApp(component);
111
+ app = createApp(compiledComponent);
96
112
  app.mount(container);
97
113
  unmount = () => {
98
114
  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":"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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.77.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
- "@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.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",
@@ -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(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, Function>;
13
+ computed?: Record<string, Function>;
14
+ name?: string;
15
+ props?: any;
16
+ }