@stencil/vitest 1.5.0 → 1.6.1

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.
@@ -213,8 +213,8 @@ export default {
213
213
  writeFileSync(join(testDir, 'vitest.config.ts'), vitestConfig);
214
214
  writeFileSync(join(testDir, 'package.json'), JSON.stringify({ name: 'test', type: 'module' }));
215
215
  const result = await runCLIInDir(testDir, ['--watch', '--verbose'], 2000);
216
- // Should add watch ignore patterns
217
- expect(result.stdout).toMatch(/Added.*watch ignore patterns/);
216
+ // Should add watch ignore patterns (screenshot and test file patterns)
217
+ expect(result.stdout).toMatch(/Added.*screenshot patterns and.*test file patterns/);
218
218
  });
219
219
  it('should merge user watchIgnoredRegex with screenshot patterns', async () => {
220
220
  const stencilConfig = `
@@ -250,6 +250,143 @@ export default {
250
250
  }
251
251
  });
252
252
  });
253
+ describe('test file ignore patterns', () => {
254
+ it('should extract test file patterns from vitest config include', async () => {
255
+ const stencilConfig = `
256
+ export const config = {
257
+ namespace: 'test',
258
+ outputTargets: [{ type: 'dist' }]
259
+ };
260
+ `;
261
+ writeFileSync(join(testDir, 'stencil.config.ts'), stencilConfig);
262
+ const vitestConfig = `
263
+ export default {
264
+ test: {
265
+ include: ['**/*.spec.ts', '**/*.test.ts']
266
+ }
267
+ };
268
+ `;
269
+ writeFileSync(join(testDir, 'vitest.config.ts'), vitestConfig);
270
+ writeFileSync(join(testDir, 'package.json'), JSON.stringify({ name: 'test', type: 'module' }));
271
+ const result = await runCLIInDir(testDir, ['--watch', '--verbose'], 2000);
272
+ // Should extract test file patterns
273
+ expect(result.stdout).toMatch(/Extracted.*test file patterns/);
274
+ });
275
+ it('should extract test file patterns from project-level include', async () => {
276
+ const stencilConfig = `
277
+ export const config = {
278
+ namespace: 'test',
279
+ outputTargets: [{ type: 'dist' }]
280
+ };
281
+ `;
282
+ writeFileSync(join(testDir, 'stencil.config.ts'), stencilConfig);
283
+ const vitestConfig = `
284
+ export default {
285
+ test: {
286
+ projects: [
287
+ {
288
+ test: {
289
+ include: ['src/**/*.unit.ts']
290
+ }
291
+ },
292
+ {
293
+ test: {
294
+ include: ['e2e/**/*.e2e.ts']
295
+ }
296
+ }
297
+ ]
298
+ }
299
+ };
300
+ `;
301
+ writeFileSync(join(testDir, 'vitest.config.ts'), vitestConfig);
302
+ writeFileSync(join(testDir, 'package.json'), JSON.stringify({ name: 'test', type: 'module' }));
303
+ const result = await runCLIInDir(testDir, ['--watch', '--verbose'], 2000);
304
+ // Should extract test file patterns from projects
305
+ expect(result.stdout).toMatch(/Extracted.*test file patterns/);
306
+ });
307
+ it('should include test file patterns in temporary config', async () => {
308
+ const stencilConfig = `
309
+ export const config = {
310
+ namespace: 'test',
311
+ outputTargets: [{ type: 'dist' }]
312
+ };
313
+ `;
314
+ writeFileSync(join(testDir, 'stencil.config.ts'), stencilConfig);
315
+ const vitestConfig = `
316
+ export default {
317
+ test: {
318
+ include: ['**/*.mytest.ts']
319
+ }
320
+ };
321
+ `;
322
+ writeFileSync(join(testDir, 'vitest.config.ts'), vitestConfig);
323
+ writeFileSync(join(testDir, 'package.json'), JSON.stringify({ name: 'test', type: 'module' }));
324
+ const result = await runCLIInDir(testDir, ['--watch', '--verbose'], 3000);
325
+ // Check that temp config was created with test file patterns
326
+ expect(result.stdout).toMatch(/Created temporary/);
327
+ const tempConfigs = require('fs')
328
+ .readdirSync(testDir)
329
+ .filter((f) => f.startsWith('.stencil-test-'));
330
+ if (tempConfigs.length > 0) {
331
+ const tempConfigContent = require('fs').readFileSync(join(testDir, tempConfigs[0]), 'utf-8');
332
+ // Should have test file pattern converted to regex
333
+ expect(tempConfigContent).toMatch(/mytest/);
334
+ }
335
+ });
336
+ it('should use default test file patterns when no include specified', async () => {
337
+ const stencilConfig = `
338
+ export const config = {
339
+ namespace: 'test',
340
+ outputTargets: [{ type: 'dist' }]
341
+ };
342
+ `;
343
+ writeFileSync(join(testDir, 'stencil.config.ts'), stencilConfig);
344
+ const vitestConfig = `
345
+ export default {
346
+ test: {
347
+ projects: []
348
+ }
349
+ };
350
+ `;
351
+ writeFileSync(join(testDir, 'vitest.config.ts'), vitestConfig);
352
+ writeFileSync(join(testDir, 'package.json'), JSON.stringify({ name: 'test', type: 'module' }));
353
+ const result = await runCLIInDir(testDir, ['--watch', '--verbose'], 3000);
354
+ // Check that temp config was created
355
+ expect(result.stdout).toMatch(/Created temporary/);
356
+ const tempConfigs = require('fs')
357
+ .readdirSync(testDir)
358
+ .filter((f) => f.startsWith('.stencil-test-'));
359
+ if (tempConfigs.length > 0) {
360
+ const tempConfigContent = require('fs').readFileSync(join(testDir, tempConfigs[0]), 'utf-8');
361
+ // Should have default test file patterns (.spec. and .test.)
362
+ // The patterns are written as regex literals with escaped dots (e.g., /\.spec\.[jt]sx?$/)
363
+ expect(tempConfigContent).toContain('spec');
364
+ expect(tempConfigContent).toContain('test');
365
+ expect(tempConfigContent).toContain('[jt]sx');
366
+ }
367
+ });
368
+ it('should report both screenshot and test file pattern counts', async () => {
369
+ const stencilConfig = `
370
+ export const config = {
371
+ namespace: 'test',
372
+ outputTargets: [{ type: 'dist' }]
373
+ };
374
+ `;
375
+ writeFileSync(join(testDir, 'stencil.config.ts'), stencilConfig);
376
+ const vitestConfig = `
377
+ export default {
378
+ test: {
379
+ include: ['**/*.spec.ts']
380
+ }
381
+ };
382
+ `;
383
+ writeFileSync(join(testDir, 'vitest.config.ts'), vitestConfig);
384
+ writeFileSync(join(testDir, 'package.json'), JSON.stringify({ name: 'test', type: 'module' }));
385
+ const result = await runCLIInDir(testDir, ['--watch', '--verbose'], 2000);
386
+ // Should report both screenshot and test file pattern counts
387
+ expect(result.stdout).toMatch(/Added.*screenshot patterns and.*test file patterns/);
388
+ });
389
+ });
253
390
  describe('temp file cleanup', () => {
254
391
  it('should cleanup temporary config on exit', async () => {
255
392
  const stencilConfig = `
@@ -265,31 +265,98 @@ function handleStencilOutput(data) {
265
265
  }
266
266
  }
267
267
  /**
268
- * Extracts screenshot directory patterns from vitest config to ignore in Stencil watch
268
+ * Resolves the vitest config path from custom path or default locations
269
269
  */
270
- async function getScreenshotPatternsFromVitestConfig(customVitestConfig) {
271
- const patterns = [];
270
+ function resolveVitestConfigPath(customVitestConfig) {
271
+ // Use custom config if provided via --config flag
272
+ if (customVitestConfig) {
273
+ const resolvedPath = join(cwd, customVitestConfig);
274
+ if (existsSync(resolvedPath)) {
275
+ return resolvedPath;
276
+ }
277
+ if (verbose) {
278
+ log(`Specified vitest config not found: ${customVitestConfig}, falling back to defaults`);
279
+ }
280
+ }
281
+ // Look for vitest.config.ts/js in common locations
282
+ const possibleConfigs = [
283
+ join(cwd, 'vitest.config.ts'),
284
+ join(cwd, 'vitest.config.js'),
285
+ join(cwd, 'vitest.config.mjs'),
286
+ ];
287
+ return possibleConfigs.find(existsSync);
288
+ }
289
+ /**
290
+ * Loads and parses the vitest config file
291
+ */
292
+ async function loadVitestConfig(vitestConfigPath) {
293
+ const jiti = createJiti(cwd, { interopDefault: true });
294
+ const vitestConfig = await jiti.import(vitestConfigPath);
295
+ return vitestConfig?.default || vitestConfig;
296
+ }
297
+ /**
298
+ * Converts a glob pattern to a RegExp for use in watchIgnoredRegex
299
+ */
300
+ function globToRegex(glob) {
301
+ // Escape special regex characters except glob wildcards
302
+ let pattern = glob
303
+ .replace(/[.+^${}()|[\]\\]/g, '\\$&') // Escape regex special chars
304
+ .replace(/\*\*/g, '<<GLOBSTAR>>') // Placeholder for **
305
+ .replace(/\*/g, '[^/]*') // * matches anything except /
306
+ .replace(/<<GLOBSTAR>>/g, '.*') // ** matches anything including /
307
+ .replace(/\?/g, '.'); // ? matches single char
308
+ return new RegExp(pattern);
309
+ }
310
+ /**
311
+ * Extracts test file include patterns from vitest config to ignore in Stencil watch
312
+ */
313
+ async function getTestFilePatternsFromVitestConfig(customVitestConfig) {
314
+ const defaultPatterns = [/\.spec\.[jt]sx?$/, /\.test\.[jt]sx?$/];
272
315
  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`);
316
+ const vitestConfigPath = resolveVitestConfigPath(customVitestConfig);
317
+ if (!vitestConfigPath) {
318
+ return defaultPatterns;
319
+ }
320
+ if (verbose) {
321
+ log(`Loading test file patterns from ${vitestConfigPath}`);
322
+ }
323
+ const configObj = await loadVitestConfig(vitestConfigPath);
324
+ const testConfig = configObj?.test || configObj;
325
+ // Extract include patterns from various possible locations
326
+ const includePatterns = [];
327
+ // Root level include
328
+ if (testConfig?.include) {
329
+ includePatterns.push(...(Array.isArray(testConfig.include) ? testConfig.include : [testConfig.include]));
330
+ }
331
+ // Check projects for include patterns
332
+ const projects = testConfig?.projects || [];
333
+ for (const project of projects) {
334
+ const projectTest = project?.test || project;
335
+ if (projectTest?.include) {
336
+ includePatterns.push(...(Array.isArray(projectTest.include) ? projectTest.include : [projectTest.include]));
282
337
  }
283
338
  }
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);
339
+ // Convert glob patterns to RegExp
340
+ const patterns = includePatterns.map(globToRegex);
341
+ if (verbose && patterns.length > 0) {
342
+ log(`Extracted ${patterns.length} test file patterns from vitest config`);
343
+ }
344
+ return patterns.length > 0 ? patterns : defaultPatterns;
345
+ }
346
+ catch (error) {
347
+ if (verbose) {
348
+ log(`Failed to parse vitest config for test patterns: ${error instanceof Error ? error.message : String(error)}`);
292
349
  }
350
+ return defaultPatterns;
351
+ }
352
+ }
353
+ /**
354
+ * Extracts screenshot directory patterns from vitest config to ignore in Stencil watch
355
+ */
356
+ async function getScreenshotPatternsFromVitestConfig(customVitestConfig) {
357
+ const patterns = [];
358
+ try {
359
+ const vitestConfigPath = resolveVitestConfigPath(customVitestConfig);
293
360
  if (!vitestConfigPath) {
294
361
  if (verbose) {
295
362
  log('No vitest config found, using default screenshot patterns');
@@ -300,9 +367,7 @@ async function getScreenshotPatternsFromVitestConfig(customVitestConfig) {
300
367
  if (verbose) {
301
368
  log(`Loading vitest config from ${vitestConfigPath}`);
302
369
  }
303
- // Use jiti to load TypeScript/ESM config
304
- const jiti = createJiti(cwd, { interopDefault: true });
305
- const vitestConfig = await jiti.import(vitestConfigPath);
370
+ const vitestConfig = await loadVitestConfig(vitestConfigPath);
306
371
  // Extract screenshot directory from browser test config
307
372
  const projects = vitestConfig?.default?.test?.projects || vitestConfig?.test?.projects || [];
308
373
  const customScreenshotDirs = new Set();
@@ -341,7 +406,7 @@ async function getScreenshotPatternsFromVitestConfig(customVitestConfig) {
341
406
  }
342
407
  /**
343
408
  * Creates a temporary stencil config that extends the user's config
344
- * and adds watchIgnoredRegex patterns for screenshots
409
+ * and adds watchIgnoredRegex patterns for screenshots and test files
345
410
  */
346
411
  async function createTemporaryStencilConfig(userSpecifiedConfig, vitestConfigPath) {
347
412
  try {
@@ -374,8 +439,11 @@ async function createTemporaryStencilConfig(userSpecifiedConfig, vitestConfigPat
374
439
  return null;
375
440
  }
376
441
  }
377
- // Get screenshot patterns to ignore
378
- const screenshotPatterns = await getScreenshotPatternsFromVitestConfig(vitestConfigPath);
442
+ // Get patterns to ignore from vitest config
443
+ const [screenshotPatterns, testFilePatterns] = await Promise.all([
444
+ getScreenshotPatternsFromVitestConfig(vitestConfigPath),
445
+ getTestFilePatternsFromVitestConfig(vitestConfigPath),
446
+ ]);
379
447
  // Load the user's config using jiti
380
448
  const jiti = createJiti(cwd, { interopDefault: true });
381
449
  const userConfig = await jiti.import(userConfigPath);
@@ -384,7 +452,7 @@ async function createTemporaryStencilConfig(userSpecifiedConfig, vitestConfigPat
384
452
  // Merge with watchIgnoredRegex
385
453
  const mergedConfig = {
386
454
  ...actualConfig,
387
- watchIgnoredRegex: [...(actualConfig?.watchIgnoredRegex || []), ...screenshotPatterns],
455
+ watchIgnoredRegex: [...(actualConfig?.watchIgnoredRegex || []), ...screenshotPatterns, ...testFilePatterns],
388
456
  };
389
457
  // Create temp file as sibling of stencil.config so tsconfig.json can be found
390
458
  // Stencil looks for tsconfig.json relative to the config file location
@@ -415,7 +483,7 @@ async function createTemporaryStencilConfig(userSpecifiedConfig, vitestConfigPat
415
483
  writeFileSync(tempConfigPath, tempConfigContent, 'utf-8');
416
484
  if (verbose) {
417
485
  log(`Created temporary stencil config at ${tempConfigPath}`);
418
- log(`Added ${screenshotPatterns.length} watch ignore patterns`);
486
+ log(`Added ${screenshotPatterns.length} screenshot patterns and ${testFilePatterns.length} test file patterns to watch ignore`);
419
487
  }
420
488
  return tempConfigPath;
421
489
  }
@@ -490,7 +558,7 @@ process.on('SIGTERM', () => cleanup());
490
558
  if (tempStencilConfigPath) {
491
559
  stencilArgs.push('--config', tempStencilConfigPath);
492
560
  if (verbose) {
493
- log('Using temporary config with screenshot ignore patterns');
561
+ log('Using temporary config with watch ignore patterns for screenshots and test files');
494
562
  }
495
563
  }
496
564
  else if (args.stencilConfig) {
package/dist/config.js CHANGED
@@ -162,6 +162,16 @@ function applyStencilDefaults(config, stencilConfig) {
162
162
  if (!result.server.watch) {
163
163
  result.server.watch = {};
164
164
  }
165
+ // Ignore source map files to prevent unnecessary test re-runs
166
+ // Stencil may touch .map files without changing content, triggering Vite's watcher
167
+ if (!result.server.watch.ignored) {
168
+ result.server.watch.ignored = [];
169
+ }
170
+ const ignoredArray = Array.isArray(result.server.watch.ignored)
171
+ ? result.server.watch.ignored
172
+ : [result.server.watch.ignored];
173
+ const mapIgnorePatterns = ['**/*.map', '**/*.map.js'];
174
+ result.server.watch.ignored = [...new Set([...ignoredArray, ...mapIgnorePatterns])];
165
175
  // Ensure test config exists
166
176
  if (!result.test) {
167
177
  result.test = {};
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "type": "git",
5
5
  "url": "https://github.com/stenciljs/vitest"
6
6
  },
7
- "version": "1.5.0",
7
+ "version": "1.6.1",
8
8
  "description": "First-class testing utilities for Stencil design systems with Vitest",
9
9
  "license": "MIT",
10
10
  "type": "module",
@@ -97,7 +97,7 @@
97
97
  "dependencies": {
98
98
  "jiti": "^2.6.1",
99
99
  "local-pkg": "^1.1.2",
100
- "vitest-environment-stencil": "1.5.0"
100
+ "vitest-environment-stencil": "1.6.1"
101
101
  },
102
102
  "devDependencies": {
103
103
  "@eslint/js": "^9.39.2",