@stencil/vitest 0.0.4 → 0.1.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/dist/bin/stencil-test.js +266 -60
- package/package.json +4 -2
package/dist/bin/stencil-test.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawn } from 'child_process';
|
|
3
|
+
import { existsSync, unlinkSync, writeFileSync } from 'fs';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
import { createJiti } from 'jiti';
|
|
3
6
|
function parseArgs(argv) {
|
|
4
7
|
const parsed = {
|
|
5
8
|
watch: false,
|
|
@@ -37,6 +40,10 @@ function parseArgs(argv) {
|
|
|
37
40
|
parsed.port = argv[i + 1];
|
|
38
41
|
i += 2;
|
|
39
42
|
break;
|
|
43
|
+
case '--stencil-config':
|
|
44
|
+
parsed.stencilConfig = argv[i + 1];
|
|
45
|
+
i += 2;
|
|
46
|
+
break;
|
|
40
47
|
case '--verbose':
|
|
41
48
|
case '-v':
|
|
42
49
|
parsed.verbose = true;
|
|
@@ -116,12 +123,13 @@ Default (no flags): Build Stencil in dev mode and run tests once
|
|
|
116
123
|
Watch mode: Build Stencil in watch mode and run Vitest with interactive features
|
|
117
124
|
|
|
118
125
|
Stencil Options:
|
|
119
|
-
--watch
|
|
120
|
-
--prod
|
|
121
|
-
--serve
|
|
122
|
-
--port <number>
|
|
123
|
-
--
|
|
124
|
-
--
|
|
126
|
+
--watch Run Stencil in watch mode (enables Vitest interactive mode)
|
|
127
|
+
--prod Build in production mode (default: dev mode)
|
|
128
|
+
--serve Start dev server (requires --watch)
|
|
129
|
+
--port <number> Dev server port (default: 3333)
|
|
130
|
+
--stencil-config <path> Path to stencil config file
|
|
131
|
+
--verbose, -v Show detailed logging
|
|
132
|
+
--debug Enable Stencil debug mode
|
|
125
133
|
|
|
126
134
|
Vitest Options:
|
|
127
135
|
--project <name> Run tests for specific project
|
|
@@ -256,6 +264,161 @@ function handleStencilOutput(data) {
|
|
|
256
264
|
}
|
|
257
265
|
}
|
|
258
266
|
}
|
|
267
|
+
/**
|
|
268
|
+
* Extracts screenshot directory patterns from vitest config to ignore in Stencil watch
|
|
269
|
+
*/
|
|
270
|
+
async function getScreenshotPatternsFromVitestConfig(customVitestConfig) {
|
|
271
|
+
const patterns = [];
|
|
272
|
+
try {
|
|
273
|
+
let vitestConfigPath;
|
|
274
|
+
// Use custom config if provided via --config flag
|
|
275
|
+
if (customVitestConfig) {
|
|
276
|
+
const resolvedPath = join(cwd, customVitestConfig);
|
|
277
|
+
if (existsSync(resolvedPath)) {
|
|
278
|
+
vitestConfigPath = resolvedPath;
|
|
279
|
+
}
|
|
280
|
+
else if (verbose) {
|
|
281
|
+
log(`Specified vitest config not found: ${customVitestConfig}, falling back to defaults`);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
// Look for vitest.config.ts/js in common locations if no custom config
|
|
285
|
+
if (!vitestConfigPath) {
|
|
286
|
+
const possibleConfigs = [
|
|
287
|
+
join(cwd, 'vitest.config.ts'),
|
|
288
|
+
join(cwd, 'vitest.config.js'),
|
|
289
|
+
join(cwd, 'vitest.config.mjs'),
|
|
290
|
+
];
|
|
291
|
+
vitestConfigPath = possibleConfigs.find(existsSync);
|
|
292
|
+
}
|
|
293
|
+
if (!vitestConfigPath) {
|
|
294
|
+
if (verbose) {
|
|
295
|
+
log('No vitest config found, using default screenshot patterns');
|
|
296
|
+
}
|
|
297
|
+
// Return sensible defaults
|
|
298
|
+
return [/__screenshots__/, /__snapshots__/, /\.(png|jpg|jpeg|webp|gif)$/];
|
|
299
|
+
}
|
|
300
|
+
if (verbose) {
|
|
301
|
+
log(`Loading vitest config from ${vitestConfigPath}`);
|
|
302
|
+
}
|
|
303
|
+
// Use jiti to load TypeScript/ESM config
|
|
304
|
+
const jiti = createJiti(cwd, { interopDefault: true });
|
|
305
|
+
const vitestConfig = await jiti.import(vitestConfigPath);
|
|
306
|
+
// Extract screenshot directory from browser test config
|
|
307
|
+
const projects = vitestConfig?.default?.test?.projects || vitestConfig?.test?.projects || [];
|
|
308
|
+
const customScreenshotDirs = new Set();
|
|
309
|
+
for (const project of projects) {
|
|
310
|
+
const browserConfig = project?.test?.browser;
|
|
311
|
+
if (browserConfig?.enabled) {
|
|
312
|
+
const screenshotDir = browserConfig.expect?.toMatchScreenshot?.screenshotDirectory;
|
|
313
|
+
if (screenshotDir) {
|
|
314
|
+
customScreenshotDirs.add(screenshotDir);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
// Add custom screenshot directories if found
|
|
319
|
+
for (const dir of customScreenshotDirs) {
|
|
320
|
+
patterns.push(new RegExp(`/${dir.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}/`));
|
|
321
|
+
}
|
|
322
|
+
// Always add default screenshot and snapshot directories
|
|
323
|
+
patterns.push(/__screenshots__/);
|
|
324
|
+
patterns.push(/__snapshots__/);
|
|
325
|
+
patterns.push(/\.vitest-attachments/);
|
|
326
|
+
// Always add common image extensions
|
|
327
|
+
patterns.push(/\.(png|jpg|jpeg|webp|gif)$/);
|
|
328
|
+
if (verbose && patterns.length > 0) {
|
|
329
|
+
log(`Extracted ${patterns.length} screenshot patterns from vitest config`);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
catch (error) {
|
|
333
|
+
if (verbose) {
|
|
334
|
+
log(`Failed to parse vitest config: ${error instanceof Error ? error.message : String(error)}`);
|
|
335
|
+
log('Using default screenshot patterns');
|
|
336
|
+
}
|
|
337
|
+
// Fallback to sensible defaults
|
|
338
|
+
return [/__screenshots__/, /__snapshots__/, /\.(png|jpg|jpeg|webp|gif)$/];
|
|
339
|
+
}
|
|
340
|
+
return patterns.length > 0 ? patterns : [/__screenshots__/, /__snapshots__/, /\.(png|jpg|jpeg|webp|gif)$/];
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Creates a temporary stencil config that extends the user's config
|
|
344
|
+
* and adds watchIgnoredRegex patterns for screenshots
|
|
345
|
+
*/
|
|
346
|
+
async function createTemporaryStencilConfig(userSpecifiedConfig, vitestConfigPath) {
|
|
347
|
+
try {
|
|
348
|
+
let userConfigPath;
|
|
349
|
+
// Use user-specified config if provided
|
|
350
|
+
if (userSpecifiedConfig) {
|
|
351
|
+
const resolvedPath = join(cwd, userSpecifiedConfig);
|
|
352
|
+
if (existsSync(resolvedPath)) {
|
|
353
|
+
userConfigPath = resolvedPath;
|
|
354
|
+
}
|
|
355
|
+
else {
|
|
356
|
+
if (verbose) {
|
|
357
|
+
log(`Specified config file not found: ${userSpecifiedConfig}`);
|
|
358
|
+
}
|
|
359
|
+
return null;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
else {
|
|
363
|
+
// Find the user's stencil config using default locations
|
|
364
|
+
const possibleConfigs = [
|
|
365
|
+
join(cwd, 'stencil.config.ts'),
|
|
366
|
+
join(cwd, 'stencil.config.js'),
|
|
367
|
+
join(cwd, 'stencil.config.mjs'),
|
|
368
|
+
];
|
|
369
|
+
userConfigPath = possibleConfigs.find(existsSync);
|
|
370
|
+
if (!userConfigPath) {
|
|
371
|
+
if (verbose) {
|
|
372
|
+
log('No stencil config found, skipping watchIgnoredRegex injection');
|
|
373
|
+
}
|
|
374
|
+
return null;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
// Get screenshot patterns to ignore
|
|
378
|
+
const screenshotPatterns = await getScreenshotPatternsFromVitestConfig(vitestConfigPath);
|
|
379
|
+
// Load the user's config using jiti
|
|
380
|
+
const jiti = createJiti(cwd, { interopDefault: true });
|
|
381
|
+
const userConfig = await jiti.import(userConfigPath);
|
|
382
|
+
// Extract the actual config object
|
|
383
|
+
const actualConfig = userConfig.config || userConfig.default?.config || userConfig.default || userConfig;
|
|
384
|
+
// Merge with watchIgnoredRegex
|
|
385
|
+
const mergedConfig = {
|
|
386
|
+
...actualConfig,
|
|
387
|
+
watchIgnoredRegex: [...(actualConfig?.watchIgnoredRegex || []), ...screenshotPatterns],
|
|
388
|
+
};
|
|
389
|
+
// Create temp file as sibling of stencil.config so tsconfig.json can be found
|
|
390
|
+
// Stencil looks for tsconfig.json relative to the config file location
|
|
391
|
+
const tempConfigPath = join(cwd, `.stencil-test-${Date.now()}.config.mjs`);
|
|
392
|
+
// Serialize the config - convert RegExp objects to strings for the output
|
|
393
|
+
const patternsArray = mergedConfig.watchIgnoredRegex.map((pattern) => pattern.toString()).join(',\n ');
|
|
394
|
+
// Create config without watchIgnoredRegex first
|
|
395
|
+
const { _watchIgnoredRegex, ...configWithoutWatch } = mergedConfig;
|
|
396
|
+
// Generate a simple config that doesn't need imports
|
|
397
|
+
const tempConfigContent = `
|
|
398
|
+
// Auto-generated temporary config by stencil-test
|
|
399
|
+
// This extends your stencil config and adds watchIgnoredRegex for screenshot files
|
|
400
|
+
export const config = {
|
|
401
|
+
${JSON.stringify(configWithoutWatch, null, 2).slice(2, -2)},
|
|
402
|
+
"watchIgnoredRegex": [
|
|
403
|
+
${patternsArray}
|
|
404
|
+
]
|
|
405
|
+
};
|
|
406
|
+
`;
|
|
407
|
+
writeFileSync(tempConfigPath, tempConfigContent, 'utf-8');
|
|
408
|
+
if (verbose) {
|
|
409
|
+
log(`Created temporary stencil config at ${tempConfigPath}`);
|
|
410
|
+
log(`Added ${screenshotPatterns.length} watch ignore patterns`);
|
|
411
|
+
}
|
|
412
|
+
return tempConfigPath;
|
|
413
|
+
}
|
|
414
|
+
catch (error) {
|
|
415
|
+
if (verbose) {
|
|
416
|
+
log(`Failed to create temporary config: ${error instanceof Error ? error.message : String(error)}`);
|
|
417
|
+
}
|
|
418
|
+
return null;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
let tempStencilConfigPath = null;
|
|
259
422
|
/**
|
|
260
423
|
* Clean up child processes and optionally exit with code.
|
|
261
424
|
*/
|
|
@@ -272,6 +435,18 @@ function cleanup(exitCode) {
|
|
|
272
435
|
stencilProcess.kill();
|
|
273
436
|
stencilProcess = null;
|
|
274
437
|
}
|
|
438
|
+
// Clean up temporary config file
|
|
439
|
+
if (tempStencilConfigPath && existsSync(tempStencilConfigPath)) {
|
|
440
|
+
try {
|
|
441
|
+
unlinkSync(tempStencilConfigPath);
|
|
442
|
+
if (verbose) {
|
|
443
|
+
log('Cleaned up temporary stencil config');
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
catch {
|
|
447
|
+
// Ignore cleanup errors
|
|
448
|
+
}
|
|
449
|
+
}
|
|
275
450
|
if (typeof exitCode === 'number') {
|
|
276
451
|
process.exit(exitCode);
|
|
277
452
|
}
|
|
@@ -279,63 +454,94 @@ function cleanup(exitCode) {
|
|
|
279
454
|
// Set up signal handlers for clean shutdown
|
|
280
455
|
process.on('SIGINT', () => cleanup());
|
|
281
456
|
process.on('SIGTERM', () => cleanup());
|
|
282
|
-
//
|
|
283
|
-
|
|
284
|
-
//
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
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
|
-
}
|
|
457
|
+
// Main async function to setup and start the processes
|
|
458
|
+
(async () => {
|
|
459
|
+
// Extract vitest --config path if provided
|
|
460
|
+
let vitestConfigPath;
|
|
461
|
+
const configIndex = args.vitestArgs.indexOf('--config');
|
|
462
|
+
if (configIndex !== -1 && configIndex + 1 < args.vitestArgs.length) {
|
|
463
|
+
vitestConfigPath = args.vitestArgs[configIndex + 1];
|
|
299
464
|
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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}`);
|
|
465
|
+
// Create temporary stencil config in watch mode to prevent screenshot infinite loops
|
|
466
|
+
if (args.watch) {
|
|
467
|
+
tempStencilConfigPath = await createTemporaryStencilConfig(args.stencilConfig, vitestConfigPath);
|
|
325
468
|
}
|
|
326
|
-
//
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
469
|
+
// Build Stencil arguments
|
|
470
|
+
const stencilArgs = ['stencil', 'build'];
|
|
471
|
+
// Add --dev by default, unless --prod is explicitly passed
|
|
472
|
+
if (args.prod) {
|
|
473
|
+
stencilArgs.push('--prod');
|
|
330
474
|
}
|
|
331
475
|
else {
|
|
332
|
-
|
|
333
|
-
log(`Stencil exited unexpectedly with code ${code}`);
|
|
334
|
-
cleanup(code || 1);
|
|
476
|
+
stencilArgs.push('--dev');
|
|
335
477
|
}
|
|
478
|
+
if (args.watch) {
|
|
479
|
+
stencilArgs.push('--watch');
|
|
480
|
+
log('Starting Stencil in watch mode...');
|
|
481
|
+
// Use temporary config if created, otherwise use user-specified config
|
|
482
|
+
if (tempStencilConfigPath) {
|
|
483
|
+
stencilArgs.push('--config', tempStencilConfigPath);
|
|
484
|
+
if (verbose) {
|
|
485
|
+
log('Using temporary config with screenshot ignore patterns');
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
else if (args.stencilConfig) {
|
|
489
|
+
// If temp config creation failed but user specified a config, use it directly
|
|
490
|
+
stencilArgs.push('--config', args.stencilConfig);
|
|
491
|
+
}
|
|
492
|
+
if (args.serve) {
|
|
493
|
+
stencilArgs.push('--serve');
|
|
494
|
+
if (args.port) {
|
|
495
|
+
stencilArgs.push('--port', args.port);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
else {
|
|
500
|
+
log('Building Stencil...');
|
|
501
|
+
// In non-watch mode, just pass through the user's config if specified
|
|
502
|
+
if (args.stencilConfig) {
|
|
503
|
+
stencilArgs.push('--config', args.stencilConfig);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
if (args.debug) {
|
|
507
|
+
stencilArgs.push('--debug');
|
|
508
|
+
}
|
|
509
|
+
// Add any additional stencil args
|
|
510
|
+
stencilArgs.push(...args.stencilArgs);
|
|
511
|
+
stencilProcess = spawn('npx', stencilArgs, {
|
|
512
|
+
cwd,
|
|
513
|
+
shell: true,
|
|
514
|
+
});
|
|
515
|
+
// Pipe stdout and watch for build completion
|
|
516
|
+
stencilProcess.stdout?.on('data', handleStencilOutput);
|
|
517
|
+
stencilProcess.stderr?.on('data', (data) => {
|
|
518
|
+
process.stderr.write(data);
|
|
519
|
+
});
|
|
520
|
+
stencilProcess.on('error', (error) => {
|
|
521
|
+
console.error('[stencil-test] Failed to start Stencil:', error);
|
|
522
|
+
process.exit(1);
|
|
523
|
+
});
|
|
524
|
+
stencilProcess.on('exit', (code) => {
|
|
525
|
+
if (verbose) {
|
|
526
|
+
log(`Stencil exited with code ${code}`);
|
|
527
|
+
}
|
|
528
|
+
// In one-time build mode, stencil exits after build
|
|
529
|
+
// Don't cleanup immediately - let tests finish first
|
|
530
|
+
if (!args.watch) {
|
|
531
|
+
stencilProcess = null;
|
|
532
|
+
}
|
|
533
|
+
else {
|
|
534
|
+
// In watch mode, stencil shouldn't exit - if it does, something went wrong
|
|
535
|
+
log(`Stencil exited unexpectedly with code ${code}`);
|
|
536
|
+
cleanup(code || 1);
|
|
537
|
+
}
|
|
538
|
+
});
|
|
539
|
+
// Watch mode: Vitest handles test file watching automatically
|
|
540
|
+
// Stencil handles component file watching automatically
|
|
541
|
+
if (args.watch && verbose) {
|
|
542
|
+
log('Watch mode enabled - Vitest will watch test files and Stencil will watch component files');
|
|
543
|
+
}
|
|
544
|
+
})().catch((error) => {
|
|
545
|
+
console.error('[stencil-test] Fatal error:', error);
|
|
546
|
+
cleanup(1);
|
|
336
547
|
});
|
|
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
|
-
}
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"type": "git",
|
|
5
5
|
"url": "https://github.com/stenciljs/vitest"
|
|
6
6
|
},
|
|
7
|
-
"version": "0.0
|
|
7
|
+
"version": "0.1.0",
|
|
8
8
|
"description": "First-class testing utilities for Stencil design systems with Vitest",
|
|
9
9
|
"license": "MIT",
|
|
10
10
|
"type": "module",
|
|
@@ -59,7 +59,9 @@
|
|
|
59
59
|
"scripts": {
|
|
60
60
|
"build": "tsc && tsc -p tsconfig.bin.json",
|
|
61
61
|
"dev": "tsc --watch",
|
|
62
|
-
"test": "pnpm --filter test-project test",
|
|
62
|
+
"test:e2e": "pnpm --filter test-project test",
|
|
63
|
+
"test:unit": "vitest run",
|
|
64
|
+
"test:unit:watch": "vitest",
|
|
63
65
|
"test:engines": "pnpm installed-check --no-workspaces --ignore-dev",
|
|
64
66
|
"lint": "eslint .",
|
|
65
67
|
"lint:fix": "eslint . --fix",
|