@stencil/vitest 0.0.1-dev.20260109124515.90fb962
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 +296 -0
- package/dist/bin/stencil-test.d.ts +3 -0
- package/dist/bin/stencil-test.d.ts.map +1 -0
- package/dist/bin/stencil-test.js +341 -0
- package/dist/config.d.ts +73 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +302 -0
- package/dist/environments/env/happy-dom.d.ts +4 -0
- package/dist/environments/env/happy-dom.d.ts.map +1 -0
- package/dist/environments/env/happy-dom.js +15 -0
- package/dist/environments/env/jsdom.d.ts +4 -0
- package/dist/environments/env/jsdom.d.ts.map +1 -0
- package/dist/environments/env/jsdom.js +32 -0
- package/dist/environments/env/mock-doc.d.ts +4 -0
- package/dist/environments/env/mock-doc.d.ts.map +1 -0
- package/dist/environments/env/mock-doc.js +14 -0
- package/dist/environments/stencil.d.ts +28 -0
- package/dist/environments/stencil.d.ts.map +1 -0
- package/dist/environments/stencil.js +71 -0
- package/dist/environments/types.d.ts +6 -0
- package/dist/environments/types.d.ts.map +1 -0
- package/dist/environments/types.js +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/setup/config-loader.d.ts +19 -0
- package/dist/setup/config-loader.d.ts.map +1 -0
- package/dist/setup/config-loader.js +92 -0
- package/dist/setup/happy-dom-setup.d.ts +12 -0
- package/dist/setup/happy-dom-setup.d.ts.map +1 -0
- package/dist/setup/happy-dom-setup.js +16 -0
- package/dist/setup/jsdom-setup.d.ts +30 -0
- package/dist/setup/jsdom-setup.d.ts.map +1 -0
- package/dist/setup/jsdom-setup.js +95 -0
- package/dist/setup/mock-doc-setup.d.ts +17 -0
- package/dist/setup/mock-doc-setup.d.ts.map +1 -0
- package/dist/setup/mock-doc-setup.js +90 -0
- package/dist/testing/html-serializer.d.ts +27 -0
- package/dist/testing/html-serializer.d.ts.map +1 -0
- package/dist/testing/html-serializer.js +152 -0
- package/dist/testing/matchers.d.ts +181 -0
- package/dist/testing/matchers.d.ts.map +1 -0
- package/dist/testing/matchers.js +460 -0
- package/dist/testing/render.d.ts +11 -0
- package/dist/testing/render.d.ts.map +1 -0
- package/dist/testing/render.js +118 -0
- package/dist/testing/snapshot-serializer.d.ts +17 -0
- package/dist/testing/snapshot-serializer.d.ts.map +1 -0
- package/dist/testing/snapshot-serializer.js +50 -0
- package/dist/types.d.ts +81 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/package.json +133 -0
package/README.md
ADDED
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
# @stencil/vitest
|
|
2
|
+
|
|
3
|
+
First-class testing utilities for Stencil components, powered by Vitest.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
### 1. Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm i --save-dev @stencil/vitest vitest
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
For browser testing, also install:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm i -D @vitest/browser-playwright
|
|
17
|
+
# or
|
|
18
|
+
npm i -D @vitest/browser-webdriverio
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### 2. Create `vitest.config.ts`
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
import { defineVitestConfig } from '@stencil/vitest/config';
|
|
25
|
+
import { playwright } from '@vitest/browser-playwright';
|
|
26
|
+
|
|
27
|
+
export default defineVitestConfig({
|
|
28
|
+
stencilConfig: './stencil.config.ts',
|
|
29
|
+
test: {
|
|
30
|
+
projects: [
|
|
31
|
+
// Unit tests - node environment for functions / logic
|
|
32
|
+
{
|
|
33
|
+
test: {
|
|
34
|
+
name: 'unit',
|
|
35
|
+
include: ['src/**/*.unit.{ts,tsx}'],
|
|
36
|
+
environment: 'node',
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
// Spec tests - via a node DOM of your choice
|
|
40
|
+
{
|
|
41
|
+
test: {
|
|
42
|
+
name: 'spec',
|
|
43
|
+
include: ['src/**/*.spec.{ts,tsx}'],
|
|
44
|
+
environment: 'stencil',
|
|
45
|
+
setupFiles: ['./vitest-setup.ts'],
|
|
46
|
+
|
|
47
|
+
// Optional environment options
|
|
48
|
+
|
|
49
|
+
// environmentOptions: {
|
|
50
|
+
// stencil: {
|
|
51
|
+
// domEnvironment: 'happy-dom' | 'jsdom' | 'mock-doc' (default)
|
|
52
|
+
// ^^ Make sure to install relevant packages
|
|
53
|
+
// },
|
|
54
|
+
// },
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
// Browser tests
|
|
58
|
+
{
|
|
59
|
+
test: {
|
|
60
|
+
name: 'browser',
|
|
61
|
+
include: ['src/**/*.test.{ts,tsx}'],
|
|
62
|
+
setupFiles: ['./vitest-setup.ts'],
|
|
63
|
+
browser: {
|
|
64
|
+
enabled: true,
|
|
65
|
+
provider: playwright(),
|
|
66
|
+
headless: true,
|
|
67
|
+
instances: [{ browser: 'chromium' }],
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
[refer Vitest documentation for all configuration options](https://vitest.dev/config/)
|
|
77
|
+
|
|
78
|
+
### 3. Load your components
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
// vitest-setup.ts
|
|
82
|
+
|
|
83
|
+
// Load Stencil components.
|
|
84
|
+
// Adjust according to your build output of choice *
|
|
85
|
+
await import('./dist/test-components/test-components.esm.js');
|
|
86
|
+
|
|
87
|
+
export {};
|
|
88
|
+
// * Bear in mind, you may need `buildDist: true` (in your stencil.config)
|
|
89
|
+
// or `--prod` to use an output other than the browser lazy-loader
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### 4. Write Tests
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
// src/components/my-button/my-button.spec.tsx
|
|
96
|
+
|
|
97
|
+
import { describe, it, expect } from 'vitest';
|
|
98
|
+
import { render, h } from '@stencil/vitest';
|
|
99
|
+
|
|
100
|
+
describe('my-button', () => {
|
|
101
|
+
it('renders with text', async () => {
|
|
102
|
+
const { root, waitForChanges } = await render(<my-button label="Click me" />);
|
|
103
|
+
root.click();
|
|
104
|
+
await waitForChanges();
|
|
105
|
+
expect(root).toEqualHtml(`
|
|
106
|
+
<my-button class="hydrated">
|
|
107
|
+
<mock:shadow-root>
|
|
108
|
+
<button class="button button--secondary button--small" type="button">
|
|
109
|
+
<slot></slot>
|
|
110
|
+
</button>
|
|
111
|
+
</mock:shadow-root>
|
|
112
|
+
Small
|
|
113
|
+
</my-button>
|
|
114
|
+
`);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### 5. Run tests
|
|
120
|
+
|
|
121
|
+
```json
|
|
122
|
+
// package.json
|
|
123
|
+
{
|
|
124
|
+
"scripts": {
|
|
125
|
+
"test": "stencil-test",
|
|
126
|
+
"test:watch": "stencil-test --watch",
|
|
127
|
+
"test:e2e": "stencil-test --project browser",
|
|
128
|
+
"test:spec": "stencil-test --project spec"
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## API
|
|
134
|
+
|
|
135
|
+
### Rendering
|
|
136
|
+
|
|
137
|
+
#### `render(VNode)`
|
|
138
|
+
|
|
139
|
+
Render a component for testing.
|
|
140
|
+
|
|
141
|
+
```tsx
|
|
142
|
+
import { render, h } from '@stencil/vitest';
|
|
143
|
+
|
|
144
|
+
const { root, waitForChanges, setProps, unmount } = await render(<my-component name="World" />);
|
|
145
|
+
|
|
146
|
+
// Access the element
|
|
147
|
+
expect(root.textContent).toContain('World');
|
|
148
|
+
|
|
149
|
+
// Update props
|
|
150
|
+
root.name = 'Stencil';
|
|
151
|
+
await waitForChanges();
|
|
152
|
+
// or
|
|
153
|
+
await setProps({ name: 'Stencil' });
|
|
154
|
+
|
|
155
|
+
// Unmount component
|
|
156
|
+
unmount();
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Available matchers:
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
// DOM assertions
|
|
163
|
+
expect(element).toHaveClass('active');
|
|
164
|
+
expect(element).toHaveClasses(['active', 'primary']);
|
|
165
|
+
expect(element).toMatchClasses(['active']); // Partial match
|
|
166
|
+
expect(element).toHaveAttribute('aria-label', 'Close');
|
|
167
|
+
expect(element).toEqualAttribute('type', 'button');
|
|
168
|
+
expect(element).toEqualAttributes({ type: 'button', disabled: true });
|
|
169
|
+
expect(element).toHaveProperty('value', 'test');
|
|
170
|
+
expect(element).toHaveTextContent('Hello World');
|
|
171
|
+
expect(element).toEqualText('Exact text match');
|
|
172
|
+
expect(element).toBeVisible();
|
|
173
|
+
|
|
174
|
+
// Shadow DOM
|
|
175
|
+
expect(element).toHaveShadowRoot();
|
|
176
|
+
expect(element).toEqualHtml('<div>Expected HTML</div>');
|
|
177
|
+
expect(element).toEqualLightHtml('<div>Light DOM only</div>');
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Event Testing
|
|
181
|
+
|
|
182
|
+
Test custom events emitted by your components:
|
|
183
|
+
|
|
184
|
+
```tsx
|
|
185
|
+
const { root, spyOnEvent, waitForChanges } = await render(<my-button />);
|
|
186
|
+
|
|
187
|
+
// Spy on events
|
|
188
|
+
const clickSpy = spyOnEvent('buttonClick');
|
|
189
|
+
const changeSpy = spyOnEvent('valueChange');
|
|
190
|
+
|
|
191
|
+
// Trigger events
|
|
192
|
+
root.click();
|
|
193
|
+
await waitForChanges();
|
|
194
|
+
|
|
195
|
+
// Assert events were emitted
|
|
196
|
+
expect(clickSpy).toHaveReceivedEvent();
|
|
197
|
+
expect(clickSpy).toHaveReceivedEventTimes(1);
|
|
198
|
+
expect(clickSpy).toHaveReceivedEventDetail({ buttonId: 'my-button' });
|
|
199
|
+
|
|
200
|
+
// Access event data
|
|
201
|
+
expect(clickSpy.events).toHaveLength(1);
|
|
202
|
+
expect(clickSpy.firstEvent?.detail).toEqual({ buttonId: 'my-button' });
|
|
203
|
+
expect(clickSpy.lastEvent?.detail).toEqual({ buttonId: 'my-button' });
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Snapshots
|
|
207
|
+
|
|
208
|
+
The package includes a custom snapshot serializer for Stencil components that properly handles shadow DOM:
|
|
209
|
+
|
|
210
|
+
```tsx
|
|
211
|
+
import { render, h } from '@stencil/vitest';
|
|
212
|
+
...
|
|
213
|
+
const { root } = await render(<my-component />);
|
|
214
|
+
expect(root).toMatchSnapshot();
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
**Snapshot output example:**
|
|
218
|
+
|
|
219
|
+
```html
|
|
220
|
+
<my-component>
|
|
221
|
+
<mock:shadow-root>
|
|
222
|
+
<button class="primary">
|
|
223
|
+
<slot />
|
|
224
|
+
</button>
|
|
225
|
+
</mock:shadow-root>
|
|
226
|
+
Click me
|
|
227
|
+
</my-component>
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## Screenshot Testing
|
|
231
|
+
|
|
232
|
+
Browser tests can include screenshot comparisons using Vitest's screenshot capabilities:
|
|
233
|
+
|
|
234
|
+
```tsx
|
|
235
|
+
import { render, h } from '@stencil/vitest';
|
|
236
|
+
...
|
|
237
|
+
const { root } = await render(<my-button variant="primary">Primary Button</my-button>);
|
|
238
|
+
await expect(root).toMatchScreenshot();
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Refer to Vitest's [screenshot testing documentation](https://vitest.dev/guide/snapshot.html#visual-snapshots) for more details.
|
|
242
|
+
|
|
243
|
+
## CLI
|
|
244
|
+
|
|
245
|
+
The `stencil-test` CLI wraps both Stencil builds with Vitest testing.
|
|
246
|
+
|
|
247
|
+
### Add to package.json
|
|
248
|
+
|
|
249
|
+
```json
|
|
250
|
+
{
|
|
251
|
+
"scripts": {
|
|
252
|
+
"test": "stencil-test",
|
|
253
|
+
"test:watch": "stencil-test --watch"
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Usage
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
# Build once, test once
|
|
262
|
+
stencil-test
|
|
263
|
+
|
|
264
|
+
# Watch mode (rebuilds on component changes, interactive Vitest)
|
|
265
|
+
stencil-test --watch
|
|
266
|
+
|
|
267
|
+
# Watch mode with dev server
|
|
268
|
+
stencil-test --watch --serve
|
|
269
|
+
|
|
270
|
+
# Production build before testing
|
|
271
|
+
stencil-test --prod
|
|
272
|
+
|
|
273
|
+
# Pass arguments to Vitest
|
|
274
|
+
stencil-test --watch --coverage
|
|
275
|
+
|
|
276
|
+
# Test specific files
|
|
277
|
+
stencil-test button.spec.ts
|
|
278
|
+
|
|
279
|
+
# Test specific project
|
|
280
|
+
stencil-test --project browser
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### CLI Options
|
|
284
|
+
|
|
285
|
+
The `stencil-test` CLI supports most of Stencil's CLI options and all of Vitest CLI options
|
|
286
|
+
|
|
287
|
+
- For full Stencil CLI options, see [Stencil CLI docs](https://stenciljs.com/docs/cli).
|
|
288
|
+
- For full Vitest CLI options, see [Vitest CLI docs](https://vitest.dev/guide/cli.html).
|
|
289
|
+
|
|
290
|
+
## License
|
|
291
|
+
|
|
292
|
+
MIT
|
|
293
|
+
|
|
294
|
+
## Contributing
|
|
295
|
+
|
|
296
|
+
See [CONTRIBUTING.md](./CONTRIBUTING.md) for development setup and guidelines.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stencil-test.d.ts","sourceRoot":"","sources":["../../bin/stencil-test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from 'child_process';
|
|
3
|
+
function parseArgs(argv) {
|
|
4
|
+
const parsed = {
|
|
5
|
+
watch: false,
|
|
6
|
+
serve: false,
|
|
7
|
+
verbose: false,
|
|
8
|
+
debug: false,
|
|
9
|
+
prod: false,
|
|
10
|
+
coverage: false,
|
|
11
|
+
noCoverage: false,
|
|
12
|
+
ui: false,
|
|
13
|
+
help: false,
|
|
14
|
+
vitestArgs: [],
|
|
15
|
+
stencilArgs: [],
|
|
16
|
+
};
|
|
17
|
+
let i = 0;
|
|
18
|
+
while (i < argv.length) {
|
|
19
|
+
const arg = argv[i];
|
|
20
|
+
switch (arg) {
|
|
21
|
+
// Help
|
|
22
|
+
case '--help':
|
|
23
|
+
case '-h':
|
|
24
|
+
parsed.help = true;
|
|
25
|
+
i++;
|
|
26
|
+
break;
|
|
27
|
+
// Stencil flags
|
|
28
|
+
case '--watch':
|
|
29
|
+
parsed.watch = true;
|
|
30
|
+
i++;
|
|
31
|
+
break;
|
|
32
|
+
case '--serve':
|
|
33
|
+
parsed.serve = true;
|
|
34
|
+
i++;
|
|
35
|
+
break;
|
|
36
|
+
case '--port':
|
|
37
|
+
parsed.port = argv[i + 1];
|
|
38
|
+
i += 2;
|
|
39
|
+
break;
|
|
40
|
+
case '--verbose':
|
|
41
|
+
case '-v':
|
|
42
|
+
parsed.verbose = true;
|
|
43
|
+
i++;
|
|
44
|
+
break;
|
|
45
|
+
case '--debug':
|
|
46
|
+
parsed.debug = true;
|
|
47
|
+
i++;
|
|
48
|
+
break;
|
|
49
|
+
case '--prod':
|
|
50
|
+
parsed.prod = true;
|
|
51
|
+
i++;
|
|
52
|
+
break;
|
|
53
|
+
// Vitest flags
|
|
54
|
+
case '--project':
|
|
55
|
+
if (!parsed.project)
|
|
56
|
+
parsed.project = [];
|
|
57
|
+
parsed.project.push(argv[i + 1]);
|
|
58
|
+
parsed.vitestArgs.push(arg, argv[i + 1]);
|
|
59
|
+
i += 2;
|
|
60
|
+
break;
|
|
61
|
+
case '--reporter':
|
|
62
|
+
if (!parsed.reporter)
|
|
63
|
+
parsed.reporter = [];
|
|
64
|
+
parsed.reporter.push(argv[i + 1]);
|
|
65
|
+
parsed.vitestArgs.push(arg, argv[i + 1]);
|
|
66
|
+
i += 2;
|
|
67
|
+
break;
|
|
68
|
+
case '--coverage':
|
|
69
|
+
parsed.coverage = true;
|
|
70
|
+
parsed.vitestArgs.push(arg);
|
|
71
|
+
i++;
|
|
72
|
+
break;
|
|
73
|
+
case '--no-coverage':
|
|
74
|
+
parsed.noCoverage = true;
|
|
75
|
+
parsed.vitestArgs.push(arg);
|
|
76
|
+
i++;
|
|
77
|
+
break;
|
|
78
|
+
case '--ui':
|
|
79
|
+
parsed.ui = true;
|
|
80
|
+
parsed.vitestArgs.push(arg);
|
|
81
|
+
i++;
|
|
82
|
+
break;
|
|
83
|
+
// Unknown flags or test patterns - pass to vitest
|
|
84
|
+
default:
|
|
85
|
+
if (arg.startsWith('-')) {
|
|
86
|
+
// Unknown flag - could be for either, but default to vitest
|
|
87
|
+
parsed.vitestArgs.push(arg);
|
|
88
|
+
// Check if next arg is a value for this flag
|
|
89
|
+
if (i + 1 < argv.length && !argv[i + 1].startsWith('-')) {
|
|
90
|
+
parsed.vitestArgs.push(argv[i + 1]);
|
|
91
|
+
i += 2;
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
i++;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
// Positional arg - likely a test path pattern
|
|
99
|
+
parsed.vitestArgs.push(arg);
|
|
100
|
+
i++;
|
|
101
|
+
}
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return parsed;
|
|
106
|
+
}
|
|
107
|
+
const args = parseArgs(process.argv.slice(2));
|
|
108
|
+
if (args.help) {
|
|
109
|
+
console.log(`
|
|
110
|
+
stencil-test - Integrated Stencil build and Vitest testing
|
|
111
|
+
|
|
112
|
+
Usage:
|
|
113
|
+
stencil-test [options] [testPathPattern]
|
|
114
|
+
|
|
115
|
+
Default (no flags): Build Stencil in dev mode and run tests once
|
|
116
|
+
Watch mode: Build Stencil in watch mode and run Vitest with interactive features
|
|
117
|
+
|
|
118
|
+
Stencil Options:
|
|
119
|
+
--watch Run Stencil in watch mode (enables Vitest interactive mode)
|
|
120
|
+
--prod Build in production mode (default: dev mode)
|
|
121
|
+
--serve Start dev server (requires --watch)
|
|
122
|
+
--port <number> Dev server port (default: 3333)
|
|
123
|
+
--verbose, -v Show detailed logging
|
|
124
|
+
--debug Enable Stencil debug mode
|
|
125
|
+
|
|
126
|
+
Vitest Options:
|
|
127
|
+
--project <name> Run tests for specific project
|
|
128
|
+
--reporter <name> Use specified reporter (default, verbose, dot, json, etc.)
|
|
129
|
+
--coverage Collect coverage information
|
|
130
|
+
--no-coverage Disable coverage
|
|
131
|
+
--ui Enable Vitest UI
|
|
132
|
+
[testPathPattern] Run only tests matching this pattern
|
|
133
|
+
|
|
134
|
+
Interactive Watch Mode (when --watch is enabled):
|
|
135
|
+
Press 'p' to filter by filename pattern
|
|
136
|
+
Press 't' to filter by test name pattern
|
|
137
|
+
Press 'f' to rerun only failed tests
|
|
138
|
+
Press 'u' to update snapshots
|
|
139
|
+
Press 'a' to rerun all tests
|
|
140
|
+
Press 'q' to quit
|
|
141
|
+
|
|
142
|
+
Examples:
|
|
143
|
+
stencil-test # Build once, test once
|
|
144
|
+
stencil-test --watch # Watch mode with auto-testing
|
|
145
|
+
stencil-test --watch --serve # Watch mode with dev server
|
|
146
|
+
stencil-test button.spec.ts # Test specific file
|
|
147
|
+
stencil-test --project browser # Test specific project
|
|
148
|
+
stencil-test --watch --coverage # Watch with coverage
|
|
149
|
+
`);
|
|
150
|
+
process.exit(0);
|
|
151
|
+
}
|
|
152
|
+
let stencilProcess = null;
|
|
153
|
+
let vitestProcess = null;
|
|
154
|
+
let debounceTimer = null;
|
|
155
|
+
let buildCount = 0;
|
|
156
|
+
let isRunningTests = false;
|
|
157
|
+
const DEBOUNCE_MS = 500;
|
|
158
|
+
const cwd = process.cwd();
|
|
159
|
+
const verbose = args.verbose;
|
|
160
|
+
function log(message) {
|
|
161
|
+
console.log(`[stencil-test] ${message}`);
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Runs Vitest tests with the configured arguments
|
|
165
|
+
* In watch mode, starts Vitest in its own watch mode for interactive features
|
|
166
|
+
* In non-watch mode, runs tests once
|
|
167
|
+
*/
|
|
168
|
+
function runTests() {
|
|
169
|
+
// Prevent overlapping test runs in non-watch mode
|
|
170
|
+
if (!args.watch && isRunningTests) {
|
|
171
|
+
if (verbose) {
|
|
172
|
+
log('Tests already running, skipping...');
|
|
173
|
+
}
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
// Kill existing test process if running
|
|
177
|
+
if (vitestProcess) {
|
|
178
|
+
vitestProcess.kill();
|
|
179
|
+
vitestProcess = null;
|
|
180
|
+
}
|
|
181
|
+
isRunningTests = true;
|
|
182
|
+
if (verbose) {
|
|
183
|
+
log(`Running tests (build #${buildCount})...`);
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
log('Running tests...');
|
|
187
|
+
}
|
|
188
|
+
// In watch mode, use Vitest's watch mode for interactive features
|
|
189
|
+
// In non-watch mode, use 'run' for a single test run
|
|
190
|
+
const vitestMode = args.watch ? 'watch' : 'run';
|
|
191
|
+
const commandArgs = ['vitest', vitestMode, ...args.vitestArgs].filter(Boolean);
|
|
192
|
+
if (verbose) {
|
|
193
|
+
log(`Command: npx ${commandArgs.join(' ')}`);
|
|
194
|
+
}
|
|
195
|
+
vitestProcess = spawn('npx', commandArgs, {
|
|
196
|
+
cwd,
|
|
197
|
+
stdio: 'inherit',
|
|
198
|
+
shell: true,
|
|
199
|
+
env: {
|
|
200
|
+
...process.env,
|
|
201
|
+
NODE_ENV: 'test',
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
vitestProcess.on('exit', (code) => {
|
|
205
|
+
isRunningTests = false;
|
|
206
|
+
if (verbose) {
|
|
207
|
+
if (code === 0) {
|
|
208
|
+
log('Tests passed ✓');
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
log(`Tests failed with code ${code}`);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
vitestProcess = null;
|
|
215
|
+
// In one-time mode, exit after tests complete
|
|
216
|
+
if (!args.watch) {
|
|
217
|
+
process.exit(code || 0);
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
vitestProcess.on('error', (error) => {
|
|
221
|
+
isRunningTests = false;
|
|
222
|
+
console.error('[stencil-test] Failed to start test process:', error);
|
|
223
|
+
vitestProcess = null;
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
function handleStencilOutput(data) {
|
|
227
|
+
const output = data.toString();
|
|
228
|
+
process.stdout.write(output);
|
|
229
|
+
// Detect build completion
|
|
230
|
+
// Stencil outputs "build finished" on first build or "rebuild finished" in watch mode
|
|
231
|
+
if (output.includes('build finished') || output.includes('rebuild finished')) {
|
|
232
|
+
buildCount++;
|
|
233
|
+
if (verbose) {
|
|
234
|
+
log(`Build #${buildCount} finished`);
|
|
235
|
+
}
|
|
236
|
+
// Check if there were errors
|
|
237
|
+
const hasErrors = output.toLowerCase().includes('[ error ]');
|
|
238
|
+
if (hasErrors) {
|
|
239
|
+
if (verbose) {
|
|
240
|
+
log('Skipping tests due to build errors');
|
|
241
|
+
}
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
// Run tests after the first successful build
|
|
245
|
+
// In watch mode, Vitest will automatically re-run when the dist/ files change
|
|
246
|
+
if (!vitestProcess) {
|
|
247
|
+
if (debounceTimer) {
|
|
248
|
+
clearTimeout(debounceTimer);
|
|
249
|
+
if (verbose) {
|
|
250
|
+
log('Debouncing initial test run');
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
debounceTimer = setTimeout(() => {
|
|
254
|
+
runTests();
|
|
255
|
+
}, DEBOUNCE_MS);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Clean up child processes and optionally exit with code.
|
|
261
|
+
*/
|
|
262
|
+
function cleanup(exitCode) {
|
|
263
|
+
log('Shutting down...');
|
|
264
|
+
if (debounceTimer) {
|
|
265
|
+
clearTimeout(debounceTimer);
|
|
266
|
+
}
|
|
267
|
+
if (vitestProcess) {
|
|
268
|
+
vitestProcess.kill();
|
|
269
|
+
vitestProcess = null;
|
|
270
|
+
}
|
|
271
|
+
if (stencilProcess) {
|
|
272
|
+
stencilProcess.kill();
|
|
273
|
+
stencilProcess = null;
|
|
274
|
+
}
|
|
275
|
+
if (typeof exitCode === 'number') {
|
|
276
|
+
process.exit(exitCode);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
// Set up signal handlers for clean shutdown
|
|
280
|
+
process.on('SIGINT', () => cleanup());
|
|
281
|
+
process.on('SIGTERM', () => cleanup());
|
|
282
|
+
// Build Stencil arguments
|
|
283
|
+
const stencilArgs = ['stencil', 'build'];
|
|
284
|
+
// Add --dev by default, unless --prod is explicitly passed
|
|
285
|
+
if (args.prod) {
|
|
286
|
+
stencilArgs.push('--prod');
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
stencilArgs.push('--dev');
|
|
290
|
+
}
|
|
291
|
+
if (args.watch) {
|
|
292
|
+
stencilArgs.push('--watch');
|
|
293
|
+
log('Starting Stencil in watch mode...');
|
|
294
|
+
if (args.serve) {
|
|
295
|
+
stencilArgs.push('--serve');
|
|
296
|
+
if (args.port) {
|
|
297
|
+
stencilArgs.push('--port', args.port);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
else {
|
|
302
|
+
log('Building Stencil...');
|
|
303
|
+
}
|
|
304
|
+
if (args.debug) {
|
|
305
|
+
stencilArgs.push('--debug');
|
|
306
|
+
}
|
|
307
|
+
// Add any additional stencil args
|
|
308
|
+
stencilArgs.push(...args.stencilArgs);
|
|
309
|
+
stencilProcess = spawn('npx', stencilArgs, {
|
|
310
|
+
cwd,
|
|
311
|
+
shell: true,
|
|
312
|
+
});
|
|
313
|
+
// Pipe stdout and watch for build completion
|
|
314
|
+
stencilProcess.stdout?.on('data', handleStencilOutput);
|
|
315
|
+
stencilProcess.stderr?.on('data', (data) => {
|
|
316
|
+
process.stderr.write(data);
|
|
317
|
+
});
|
|
318
|
+
stencilProcess.on('error', (error) => {
|
|
319
|
+
console.error('[stencil-test] Failed to start Stencil:', error);
|
|
320
|
+
process.exit(1);
|
|
321
|
+
});
|
|
322
|
+
stencilProcess.on('exit', (code) => {
|
|
323
|
+
if (verbose) {
|
|
324
|
+
log(`Stencil exited with code ${code}`);
|
|
325
|
+
}
|
|
326
|
+
// In one-time build mode, stencil exits after build
|
|
327
|
+
// Don't cleanup immediately - let tests finish first
|
|
328
|
+
if (!args.watch) {
|
|
329
|
+
stencilProcess = null;
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
332
|
+
// In watch mode, stencil shouldn't exit - if it does, something went wrong
|
|
333
|
+
log(`Stencil exited unexpectedly with code ${code}`);
|
|
334
|
+
cleanup(code || 1);
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
// Watch mode: Vitest handles test file watching automatically
|
|
338
|
+
// Stencil handles component file watching automatically
|
|
339
|
+
if (args.watch && verbose) {
|
|
340
|
+
log('Watch mode enabled - Vitest will watch test files and Stencil will watch component files');
|
|
341
|
+
}
|