@stencil/vitest 0.0.5 → 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.
@@ -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 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
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
- // 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
- }
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
- 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}`);
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
- // 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;
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
- // 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);
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.5",
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",